fix(resolution): literal-receiver builtins and nested locals stop fabricating call edges (#1317)

", ".join(sorted(x)) resolved by bare name to a project function named
join — one nested inside a DIFFERENT function, so scope alone rules the
edge out. Both defects from #1230, fixed independently:

1. Extraction: a member call on a LITERAL receiver (string, number,
   collection, regex — across grammars) emits no call ref at all. A
   literal's methods are the language's builtins, never project
   symbols; the bare-name fallback let them exact-match any same-named
   project function. Silent miss, never a wrong edge.

2. Resolution: matchByExactName filters out candidates nested inside a
   same-file FUNCTION container unless the ref originates within that
   container's line range. Class members (parent is a class-like node),
   top-level symbols, and C++ namespace prefixes (no parent node) are
   untouched.

requests re-index: byte-identical (813 calls edges). excalidraw: -27
edges, all literal-receiver refs by construction. The issue's repro is
pinned: join has exactly one caller (format_fields), report_missing
has zero project callees.

Fixes #1230

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-16 15:55:38 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent 41c2029798
commit c472cfb52e
4 changed files with 125 additions and 1 deletions
+34
View File
@@ -362,6 +362,30 @@ const INSTANTIATION_KINDS: ReadonlySet<string> = new Set([
/**
* TreeSitterExtractor - Main extraction class
*/
/**
* tree-sitter node types (across grammars) for literal expressions in method-
* call RECEIVER position. A literal's methods are the language's builtins
* `", ".join`, `"x".toUpperCase()`, `5.times`, `[].concat` never project
* symbols, so a member call on one must not emit a `calls` ref that bare-name
* matching could bind to an unrelated same-named project function (#1230).
*/
const LITERAL_RECEIVER_TYPES = new Set([
// strings
'string', 'string_literal', 'interpreted_string_literal', 'raw_string_literal',
'template_string', 'concatenated_string', 'formatted_string', 'f_string',
'line_string_literal', 'string_content', 'heredoc_body',
// numbers
'number', 'number_literal', 'integer', 'integer_literal', 'float',
'float_literal', 'int_literal', 'decimal_integer_literal', 'real_literal',
// chars / runes / regex / booleans / null-likes
'char_literal', 'character_literal', 'rune_literal', 'regex', 'regex_literal',
'true', 'false', 'boolean_literal', 'bool_literal', 'none', 'null', 'nil',
'null_literal', 'undefined',
// collection literals
'list', 'list_literal', 'array', 'array_literal', 'array_creation_expression',
'dictionary', 'dict_literal', 'object', 'tuple', 'set',
]);
export class TreeSitterExtractor {
private filePath: string;
private language: Language;
@@ -4351,6 +4375,16 @@ export class TreeSitterExtractor {
getChildByField(func, 'operand') ||
getChildByField(func, 'argument') ||
func.namedChild(0);
// A LITERAL receiver — `", ".join(...)`, `"x".toUpperCase()`,
// `5.times`, `[].concat(...)` — calls a builtin of the literal's
// type, never a project symbol. The bare-name fallback below let
// these exact-match an unrelated same-named project function
// (`", ".join` bound to a local `join` defined inside a DIFFERENT
// function, #1230). Emit nothing: a silent miss, never a wrong
// edge. Nested calls in the arguments are visited independently.
if (receiver && LITERAL_RECEIVER_TYPES.has(receiver.type)) {
return;
}
const SKIP_RECEIVERS = new Set(['self', 'this', 'cls', 'super']);
if (receiver && (receiver.type === 'identifier' || receiver.type === 'simple_identifier' || receiver.type === 'field_identifier')) {
const receiverName = getNodeText(receiver, this.source);