@@ -38,14 +38,41 @@ use crate::Backend;
3838use crate :: completion:: class_completion:: {
3939 ClassCompletionParams , ClassNameContext , detect_class_name_context, is_class_declaration_name,
4040} ;
41- use crate :: completion:: named_args:: { NamedArgContext , parse_existing_args} ;
41+ use crate :: completion:: named_args:: {
42+ NamedArgContext , cursor_inside_nested_bracket, parse_existing_args,
43+ } ;
4244use crate :: docblock:: types:: PHPDOC_TYPE_KEYWORDS ;
4345use crate :: php_type:: PhpType ;
4446use crate :: symbol_map:: SymbolKind ;
4547use crate :: types:: { ClassInfo , ResolvedType } ;
4648use crate :: types:: { CompletionTarget , FileContext } ;
4749use crate :: util:: { find_class_at_offset, position_to_byte_offset, position_to_offset} ;
4850
51+ /// Append named-argument items into an existing [`CompletionResponse`].
52+ ///
53+ /// If `named_arg_items` is empty the response is returned unchanged.
54+ /// Otherwise the items are appended to the response's item list,
55+ /// preserving the `is_incomplete` flag when the response is a
56+ /// [`CompletionList`].
57+ fn merge_named_args_into_response (
58+ response : CompletionResponse ,
59+ named_arg_items : Vec < CompletionItem > ,
60+ ) -> CompletionResponse {
61+ if named_arg_items. is_empty ( ) {
62+ return response;
63+ }
64+ match response {
65+ CompletionResponse :: Array ( mut items) => {
66+ items. extend ( named_arg_items) ;
67+ CompletionResponse :: Array ( items)
68+ }
69+ CompletionResponse :: List ( mut list) => {
70+ list. items . extend ( named_arg_items) ;
71+ CompletionResponse :: List ( list)
72+ }
73+ }
74+ }
75+
4976/// Check whether a `(` immediately follows the cursor position (past any
5077/// partial identifier the user has already typed).
5178///
@@ -296,10 +323,12 @@ impl Backend {
296323 return Ok ( self . complete_type_hint ( & content, & th_ctx, & ctx, position, & uri) ) ;
297324 }
298325
299- // ── Named argument completion ───────────────────────────
300- if let Some ( response) = self . try_named_arg_completion ( & uri, & content, position, & ctx) {
301- return Ok ( Some ( response) ) ;
302- }
326+ // ── Named argument completion (collected, not short-circuited) ──
327+ // Named arg items are always valid alongside normal
328+ // completions, so collect them here and merge them into
329+ // whatever strategy wins below.
330+ let named_arg_items =
331+ self . collect_named_arg_items ( & uri, & content, position, & ctx) ;
303332
304333 // ── String context detection ────────────────────────────
305334 // Classify once and use throughout the remaining pipeline.
@@ -368,7 +397,7 @@ impl Backend {
368397
369398 // ── Smart catch clause completion ───────────────────────
370399 if let Some ( response) = self . try_catch_completion ( & content, position, & ctx, & uri) {
371- return Ok ( Some ( response) ) ;
400+ return Ok ( Some ( merge_named_args_into_response ( response, named_arg_items ) ) ) ;
372401 }
373402
374403 // ── `throw new` completion ──────────────────────────────
@@ -388,7 +417,12 @@ impl Backend {
388417 if let Some ( response) =
389418 self . try_class_constant_function_completion ( & content, position, & ctx, & uri)
390419 {
391- return Ok ( Some ( response) ) ;
420+ return Ok ( Some ( merge_named_args_into_response ( response, named_arg_items) ) ) ;
421+ }
422+
423+ // No strategy matched, but we may still have named arg items.
424+ if !named_arg_items. is_empty ( ) {
425+ return Ok ( Some ( CompletionResponse :: Array ( named_arg_items) ) ) ;
392426 }
393427 }
394428
@@ -671,28 +705,33 @@ impl Backend {
671705
672706 // ─── Strategy: named argument completion ─────────────────────────────
673707
674- /// Try to offer `name:` argument completions inside function/method
708+ /// Collect `name:` argument completion items inside function/method
675709 /// call parentheses.
676710 ///
677- /// Returns `None` when the cursor is not in a named-argument context
678- /// or when no parameters could be resolved.
679- fn try_named_arg_completion (
711+ /// Returns an empty `Vec` when the cursor is not in a named-argument
712+ /// context or when no parameters could be resolved. The items are
713+ /// meant to be **merged** into whatever other completion strategy
714+ /// wins — named args are always valid alongside normal completions.
715+ fn collect_named_arg_items (
680716 & self ,
681717 uri : & str ,
682718 content : & str ,
683719 position : Position ,
684720 ctx : & FileContext ,
685- ) -> Option < CompletionResponse > {
721+ ) -> Vec < CompletionItem > {
686722 // ── Primary path: AST-based detection via symbol map ────────
687723 // The symbol map's `CallSite` data handles chains, nesting,
688724 // and strings correctly. Fall back to text scanning when the
689725 // AST has no hit (typically because the parser couldn't recover
690726 // from incomplete code).
691- let na_ctx = self
727+ let na_ctx = match self
692728 . detect_named_arg_from_symbol_map ( uri, content, position)
693729 . or_else ( || {
694730 crate :: completion:: named_args:: detect_named_arg_context ( content, position)
695- } ) ?;
731+ } ) {
732+ Some ( ctx) => ctx,
733+ None => return Vec :: new ( ) ,
734+ } ;
696735
697736 let mut params = self . resolve_named_arg_params ( & na_ctx, content, position, ctx) ;
698737
@@ -719,16 +758,7 @@ impl Backend {
719758 }
720759 }
721760
722- if params. is_empty ( ) {
723- return None ;
724- }
725-
726- let items = crate :: completion:: named_args:: build_named_arg_completions ( & na_ctx, & params) ;
727- if items. is_empty ( ) {
728- None
729- } else {
730- Some ( CompletionResponse :: Array ( items) )
731- }
761+ crate :: completion:: named_args:: build_named_arg_completions ( & na_ctx, & params)
732762 }
733763
734764 /// Detect a named-argument context using precomputed [`CallSite`] data
@@ -749,6 +779,16 @@ impl Backend {
749779 let cursor_byte_offset = position_to_offset ( content, position) ;
750780 let cs = symbol_map. find_enclosing_call_site ( cursor_byte_offset) ?;
751781
782+ // ── Bail out when cursor is inside a nested `[…]` or `{…}` ─
783+ // If the cursor sits inside an array literal or braced
784+ // expression that is itself an argument, named-arg completion
785+ // for the outer call must not fire — the user wants normal
786+ // value completion, not parameter names.
787+ if cursor_inside_nested_bracket ( content, cs. args_start as usize , cursor_byte_offset as usize )
788+ {
789+ return None ;
790+ }
791+
752792 // ── Check eligibility at cursor ─────────────────────────────
753793 // Walk backward from cursor through identifier chars to find the
754794 // start of the current "word" in the raw source text.
0 commit comments