2121/// - **Array destructuring**: `[$a, $b] = …` / `list($a, $b) = …`
2222///
2323/// When the cursor is already at the definition site (e.g. on a
24- /// parameter), the module falls through to type-hint resolution:
25- /// it extracts the type hint and jumps to the first class-like type
26- /// in it (e.g. `HtmlString` in `HtmlString|string $content`).
24+ /// parameter or assignment LHS), GTD returns `None` — the user is
25+ /// already at the definition. Type hints next to the variable
26+ /// (e.g. `Throwable` in `catch (Throwable $it)`) are separate
27+ /// symbol spans that the user can click directly.
2728///
2829/// When the AST parse fails (malformed PHP, parser panic), the function
2930/// returns `None` rather than falling back to text heuristics.
3334/// - [`var_definition`]: AST walk that finds variable definition sites
3435/// (assignments, parameters, foreach, catch, static/global,
3536/// destructuring).
36- /// - [`type_hint`]: AST walk that extracts the type hint string at a
37- /// variable's definition site (parameter, property, closure/arrow
38- /// function parameter).
3937use tower_lsp:: lsp_types:: * ;
4038
4139use crate :: Backend ;
42- use crate :: composer;
4340use crate :: parser:: with_parsed_program;
4441use crate :: util:: { offset_to_position, position_to_offset} ;
4542
46- mod type_hint;
4743mod var_definition;
4844
49- use type_hint:: find_type_hint_at_definition;
5045use var_definition:: find_variable_definition_in_program;
5146
5247// ═══════════════════════════════════════════════════════════════════════
@@ -60,8 +55,7 @@ pub(super) enum VarDefSearchResult {
6055 #[ default]
6156 NotFound ,
6257 /// The cursor is already sitting on the definition site (e.g. on a
63- /// parameter declaration). The caller should fall through to
64- /// type-hint resolution.
58+ /// parameter declaration). The caller should return `None`.
6559 AtDefinition ,
6660 /// Found a prior definition at the given byte offset.
6761 /// `offset` is the start of the `$var` token, `end_offset` is the end.
@@ -92,8 +86,8 @@ impl Backend {
9286 ///
9387 /// Returns:
9488 /// - `Some(Some(location))` — found a prior definition, jump there
95- /// - `Some(None)` — cursor is at a definition site (fall through to type-hint)
96- /// OR no definition found in the AST
89+ /// - `Some(None)` — cursor is at a definition site or no definition
90+ /// found in the AST
9791 /// - `None` — AST parse failed
9892 fn resolve_variable_definition_ast (
9993 content : & str ,
@@ -118,8 +112,7 @@ impl Backend {
118112 Some ( None )
119113 }
120114 VarDefSearchResult :: AtDefinition => {
121- // Cursor is at the definition — return Some(None) so the
122- // caller falls through to type-hint resolution.
115+ // Cursor is at the definition — return Some(None).
123116 Some ( None )
124117 }
125118 VarDefSearchResult :: FoundAt { offset, end_offset } => {
@@ -136,118 +129,6 @@ impl Backend {
136129 }
137130 }
138131 }
139-
140- // ─── Type-Hint Resolution at Variable Definition ────────────────────
141-
142- /// When the cursor is on a variable that is already at its definition
143- /// site (parameter, property, promoted property, catch variable),
144- /// extract the type hint and jump to the first class-like type in it.
145- ///
146- /// For example, given `public readonly HtmlString|string $content,`
147- /// this returns the location of the `HtmlString` class definition.
148- pub ( super ) fn resolve_type_hint_at_variable (
149- & self ,
150- uri : & str ,
151- content : & str ,
152- position : Position ,
153- var_name : & str ,
154- ) -> Option < Location > {
155- self . resolve_type_hint_at_variable_ast ( uri, content, position, var_name)
156- }
157-
158- /// AST-based type-hint resolution: extract the type hint from the AST
159- /// node where the variable is defined (parameter, catch, property).
160- fn resolve_type_hint_at_variable_ast (
161- & self ,
162- uri : & str ,
163- content : & str ,
164- position : Position ,
165- var_name : & str ,
166- ) -> Option < Location > {
167- let cursor_offset = position_to_offset ( content, position) ;
168-
169- let type_hint_str: Option < String > = with_parsed_program (
170- content,
171- "resolve_type_hint_at_variable_ast" ,
172- |program, _| find_type_hint_at_definition ( program, var_name, cursor_offset) ,
173- ) ;
174-
175- let type_hint = type_hint_str?;
176- self . resolve_type_hint_string_to_location ( uri, content, & type_hint)
177- }
178-
179- /// Given a type-hint string (e.g. `HtmlString|string`, `?Foo`),
180- /// resolve it to the definition location of the first class-like type.
181- fn resolve_type_hint_string_to_location (
182- & self ,
183- uri : & str ,
184- content : & str ,
185- type_hint : & str ,
186- ) -> Option < Location > {
187- let scalars = [
188- "string" , "int" , "float" , "bool" , "array" , "callable" , "iterable" , "object" , "mixed" ,
189- "void" , "never" , "null" , "false" , "true" , "self" , "static" , "parent" ,
190- ] ;
191-
192- let class_name = type_hint
193- . split ( [ '|' , '&' ] )
194- . map ( |t| t. trim_start_matches ( '?' ) )
195- . find ( |t| !t. is_empty ( ) && !scalars. contains ( & t. to_lowercase ( ) . as_str ( ) ) ) ?;
196-
197- let ctx = self . file_context ( uri) ;
198-
199- let fqn = Self :: resolve_to_fqn ( class_name, & ctx. use_map , & ctx. namespace ) ;
200-
201- let mut candidates = vec ! [ fqn] ;
202- if class_name. contains ( '\\' ) && !candidates. contains ( & class_name. to_string ( ) ) {
203- candidates. push ( class_name. to_string ( ) ) ;
204- }
205-
206- // Try same-file first.
207- for fqn in & candidates {
208- if let Some ( location) = self . find_definition_in_ast_map ( fqn, content, uri) {
209- return Some ( location) ;
210- }
211- }
212-
213- // Try Composer classmap: direct FQN → file path lookup.
214- // This covers vendor classes that haven't been loaded into ast_map
215- // yet (cold Ctrl+Click on a type hint never used in completion).
216- for fqn in & candidates {
217- if let Ok ( cmap) = self . classmap . lock ( )
218- && let Some ( file_path) = cmap. get ( fqn. as_str ( ) ) . cloned ( )
219- {
220- drop ( cmap) ;
221- if let Some ( location) = self . resolve_class_in_file ( & file_path, fqn) {
222- return Some ( location) ;
223- }
224- }
225- }
226-
227- // Try PSR-4 resolution as a last resort.
228- // PSR-4 mappings only cover user code (from composer.json).
229- // Vendor classes are resolved by the classmap above.
230- let workspace_root = self
231- . workspace_root
232- . lock ( )
233- . ok ( )
234- . and_then ( |guard| guard. clone ( ) ) ;
235-
236- if let Some ( workspace_root) = workspace_root
237- && let Ok ( mappings) = self . psr4_mappings . lock ( )
238- {
239- for fqn in & candidates {
240- if let Some ( file_path) =
241- composer:: resolve_class_path ( & mappings, & workspace_root, fqn)
242- && let Some ( location) = self . resolve_class_in_file ( & file_path, fqn)
243- {
244- return Some ( location) ;
245- }
246- }
247- }
248-
249- None
250- }
251132}
252133
253134// ═══════════════════════════════════════════════════════════════════════
0 commit comments