Skip to content

fix: Go receiver method symbol resolution returns full body (issue #461)#462

Merged
buger merged 1 commit intomainfrom
fix/461-go-receiver-method-symbol-resolution
Feb 27, 2026
Merged

fix: Go receiver method symbol resolution returns full body (issue #461)#462
buger merged 1 commit intomainfrom
fix/461-go-receiver-method-symbol-resolution

Conversation

@buger
Copy link
Collaborator

@buger buger commented Feb 27, 2026

Summary

  • Fixes Bug: Go method symbol resolution returns only function signature (1 line) instead of full body #461: Go receiver methods (func (r *Type) Method()) now resolve via AST instead of falling back to text search, returning the full method body instead of just the signature line
  • Fixes PHP symbol resolution: PHP's tree-sitter uses "name" for identifiers — added to identifier kind checks so Class.method lookups work
  • Fixes disambiguation: get_qualified_name now checks field_identifier and name node kinds for proper symbol qualification across all languages

Root cause

Go receiver methods are declared at the module level as method_declaration siblings of type_declaration, not children. The symbol finder only searched children of parent types, so Type.Method lookups always failed → text search fallback → 1-line result.

Changes

File Change
src/language/language_trait.rs Added get_receiver_type() trait method
src/language/go.rs Implemented get_receiver_type() (pointer/value/generic receivers) and get_symbol_signature()
src/extract/symbol_finder.rs Receiver-based resolution pass + PHP "name" fix + get_qualified_name fix

Test plan

  • 30 new integration tests in tests/symbol_resolution_tests.rs covering:
    • Go: pointer receivers, value receivers, generic receivers, single-line methods, multi-line signatures, method name disambiguation, bare name lookups, function vs method collision
    • Rust: impl method resolution and disambiguation
    • Python: class method resolution and disambiguation
    • JavaScript/TypeScript: class method resolution
    • Java, C#, PHP: class method resolution and disambiguation
  • 6 new language-specific mock files for test coverage
  • All 250 unit tests pass
  • All 8 integration tests pass
  • Pre-commit hooks pass (fmt + clippy + tests)

🤖 Generated with Claude Code

…f signature only (issue #461)

Go receiver methods (e.g., `func (r *Type) Method()`) are declared at
the module level as siblings of type declarations, not children. The
symbol finder only searched children, so `Type.Method` lookups failed
and fell back to text search returning just the signature line.

Changes:
- Add `get_receiver_type()` trait method to LanguageImpl for languages
  where methods aren't nested inside type definitions
- Implement `get_receiver_type()` and `get_symbol_signature()` for Go,
  handling pointer, value, and generic receivers
- Add receiver-based resolution pass in symbol_finder after AST child
  search fails for nested symbols
- Fix `get_qualified_name` to check `field_identifier` and `name` kinds
  for proper disambiguation across all languages
- Fix PHP symbol resolution by adding `name` to identifier kind checks

Test coverage: 30 new tests across Go, Rust, Python, JavaScript,
TypeScript, Java, C#, and PHP with mock files for each language.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@probelabs
Copy link
Contributor

probelabs bot commented Feb 27, 2026

  • ERROR: ProbeAgent execution failed: Error: Failed to get response from AI model during iteration 1. No output generated. Check the stream for errors. (system:0)

Powered by Visor from Probelabs

Last updated: 2026-02-27T10:20:14.764Z | Triggered by: pr_opened | Commit: 2c2d198

💡 TIP: You can chat with Visor using /visor ask <your question>

@probelabs
Copy link
Contributor

probelabs bot commented Feb 27, 2026

✅ Security Check Passed

No security issues found – changes LGTM.

Architecture Issues (5)

Severity Location Issue
🟢 Info probe/src/language/go.rs:151-206
The get_symbol_signature implementation for Go only handles function_declaration, method_declaration, and type_declaration/type_spec. Other acceptable parent types from is_acceptable_parent (struct_type, interface_type) are not handled, returning None. This inconsistency means some symbol types won't have proper signature extraction.
💡 SuggestionAdd signature extraction cases for struct_type and interface_type nodes to ensure complete coverage of all acceptable parent types. For structs, extract the struct name and field summary; for interfaces, extract the interface name and method signatures.
🟢 Info probe/src/extract/symbol_finder.rs:452-454
The receiver-based resolution checks for field_identifier, identifier, and property_identifier but omits type_identifier and name which are checked elsewhere in the same file. This inconsistency could cause receiver-based resolution to fail for languages that use different identifier node types.
💡 SuggestionUse the same identifier kind set as find_all_symbol_nodes (which includes type_identifier and name) for consistency. Better yet, extract to a shared constant or helper function.
🟡 Warning probe/src/extract/symbol_finder.rs:22-26
Identifier kind checks (identifier, type_identifier, field_identifier, name, property_identifier) are duplicated across three functions: get_qualified_name (lines 22-26), find_all_symbol_nodes (lines 87-91), and find_all_symbols_in_file receiver resolution (lines 452-454). This violates DRY and creates maintenance burden when adding new identifier types.
💡 SuggestionExtract identifier kind checking into a shared helper function or constant array. Consider: `fn is_identifier_kind(kind: &str) -> bool { matches!(kind, "identifier" | "type_identifier" | "field_identifier" | "name" | "property_identifier") }`
🟡 Warning probe/src/extract/symbol_finder.rs:435-488
The receiver-based method resolution block (56 lines) introduces Go-specific logic in the generic symbol_finder.rs. This creates a leaky abstraction where language-specific resolution patterns bleed into the core module. The pattern of 'try AST → try receiver-based → fallback to text search' adds complexity to the main function flow.
💡 SuggestionConsider moving receiver-based resolution into the LanguageImpl trait as a `find_symbol_by_receiver` method with a default no-op implementation. This would keep Go-specific logic in go.rs and allow other languages with similar patterns (e.g., Rust's impl blocks) to implement their own resolution strategies.
🟡 Warning probe/src/language/go.rs:68-149
The get_receiver_type implementation has 5 levels of nesting with multiple cursor traversals. The logic for handling pointer_type containing generic_type (lines 102-120) is particularly dense and duplicates the generic_type extraction logic that also exists in the generic_type case (lines 134-142).
💡 SuggestionExtract helper functions for type extraction: `fn extract_type_from_generic_type(node: &Node, source: &[u8]) -> Option<String>` and `fn extract_type_from_pointer_type(node: &Node, source: &[u8]) -> Option<String>`. This would reduce nesting and eliminate the duplicated generic_type extraction logic.

Performance Issues (3)

Severity Location Issue
🟢 Info src/language/go.rs:94-98
String allocation via String::from() occurs even when the receiver type comparison will fail. In the receiver-based resolution loop, get_receiver_type() is called for every acceptable parent node, potentially allocating strings that are immediately discarded.
💡 SuggestionConsider returning a Cow<str> or comparing directly against the expected type name without allocating. For the hot path in receiver resolution, pass the expected parent_name to get_receiver_type() and return bool instead of String.
🟡 Warning src/language/go.rs:82-141
Multiple nested cursor allocations in get_receiver_type() create up to 4 cursors per method_declaration node. Each walk() call allocates a new Tree-sitter cursor, and in the worst case (pointer_type with generic_type), this creates 4 levels of cursor nesting.
💡 SuggestionConsider reusing a single cursor by resetting it with cursor.reset(node) instead of creating new cursors at each nesting level. Alternatively, use Tree-sitter's cursor API more efficiently by navigating directly to child nodes when possible.
🟡 Warning src/extract/symbol_finder.rs:454-484
The receiver-based resolution loop continues iterating through all root children even after finding a match. While the inner loop breaks, the outer loop over root_node.children() continues checking remaining nodes unnecessarily.
💡 SuggestionAdd a check after the inner loop to break out of the outer loop when matched_nodes is no longer empty. For single-result lookups, this avoids unnecessary iterations through remaining top-level nodes.

Quality Issues (1)

Severity Location Issue
🟠 Error system:0
ProbeAgent execution failed: Error: Failed to get response from AI model during iteration 1. No output generated. Check the stream for errors.

Powered by Visor from Probelabs

Last updated: 2026-02-27T10:20:17.822Z | Triggered by: pr_opened | Commit: 2c2d198

💡 TIP: You can chat with Visor using /visor ask <your question>

@buger buger merged commit 8d5e0d4 into main Feb 27, 2026
16 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Go method symbol resolution returns only function signature (1 line) instead of full body

1 participant