mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-09 03:17:54 +00:00
fix(dart): resolve calls through a closure-valued binding (#2693)
Dart needed more than the shared gate fix: neither of its closure-binding forms could resolve, for two different reasons, and the plan's one-line diagnosis turned out to be incomplete. TOP-LEVEL `var f = (x) => x;` A graph Function node already existed (#2687), but no `@declaration.*` matched the binding, so scope resolution had no SymbolDefinition to attach a flow seed to. Adding the declaration exposed a second problem: Dart's `initialized_identifier` is FIELDLESS, so the shared field-based assignment fallback (`left`/`name`/`value`/…) decomposed nothing and the binding still emitted no flow captures at all. Kotlin's fieldless `assignment` node hit exactly this and took the same remedy — a provider `extractAssignment`. FUNCTION-LOCAL `void m() { var f = (x) => x; }` Locals parse as `initialized_variable_definition`, which the top-level graph-node rules are deliberately anchored under (program) to avoid, so a local closure had no graph node at all — nothing for the widened `buildGraphTargetIndex` gate to admit. Both new rules are restricted to a `function_expression` value. Declaring every Dart variable would mint defs and nodes repo-wide for no resolution benefit; ordinary locals stay unindexed exactly as before. The top-level declaration reuses the (program) anchor the graph-node query already relies on, so class-body fields — which share `initialized_identifier_list` and are already `@declaration.property` — are never matched twice. Also drops the now-false note in tree-sitter-queries.ts claiming `f()` does not resolve for Dart. That node is now the evidence that makes it resolve.
This commit is contained in:
parent
cacc99bc8e
commit
18757c048d
4 changed files with 70 additions and 5 deletions
|
|
@ -58,9 +58,25 @@ const DART_CALLABLE_CAPTURE_OPTIONS = {
|
|||
callNodeTypes: new Set(['selector']),
|
||||
parameterListNodeTypes: new Set(['formal_parameter_list', 'arguments']),
|
||||
parameterNodeTypes: new Set(['formal_parameter']),
|
||||
bindingNodeTypes: new Set(['initialized_variable_definition']),
|
||||
// `initialized_identifier` covers TOP-LEVEL bindings, which Dart parses as a
|
||||
// loose initialized_identifier_list under program rather than wrapping them
|
||||
// in the local form. Without it a top-level `var f = (x) => x;` emitted no
|
||||
// flow captures at all, so `f()` never resolved (#2693).
|
||||
bindingNodeTypes: new Set(['initialized_variable_definition', 'initialized_identifier']),
|
||||
assignmentNodeTypes: new Set(['assignment_expression']),
|
||||
identifierNodeTypes: new Set(['identifier', 'type_identifier']),
|
||||
// `initialized_identifier` is FIELDLESS, so the shared field-based fallback
|
||||
// (`left`/`name`/`value`/…) decomposes nothing and a top-level binding
|
||||
// produced no flow facts at all — the same shape as Kotlin's fieldless
|
||||
// `assignment` node. Positional: first named child is the bound name, last is
|
||||
// the initializer. `initialized_variable_definition` carries real `name:` /
|
||||
// `value:` fields, so it is left to the shared path by returning undefined.
|
||||
extractAssignment: (node: SyntaxNode) => {
|
||||
if (node.type !== 'initialized_identifier') return undefined;
|
||||
const named = node.namedChildren.filter((child): child is SyntaxNode => child !== null);
|
||||
if (named.length < 2) return undefined;
|
||||
return { destination: named[0]!, source: named[named.length - 1]! };
|
||||
},
|
||||
lexicalFunctionOwner: (node: SyntaxNode) => dartLexicalFunctionOwner(node),
|
||||
isCallNode: (node: SyntaxNode) => node.namedChild(0)?.type === 'argument_part',
|
||||
extractCallCallee: (node: SyntaxNode) => dartCallableCallee(node) ?? undefined,
|
||||
|
|
|
|||
|
|
@ -126,6 +126,25 @@ const DART_SCOPE_QUERY = `
|
|||
(initialized_identifier
|
||||
. (identifier) @declaration.name))) @declaration.property
|
||||
|
||||
; ── Declarations — closure bindings (#2693) ──────────────────────────────────
|
||||
; \`var f = (x) => x;\` binds a callable. Without a declaration the binding has
|
||||
; no SymbolDefinition, so callable-value-flow has nothing to attach its seed to
|
||||
; and \`f()\` stays unresolved even though the graph emits a Function node for it.
|
||||
;
|
||||
; Restricted to a function_expression value on purpose: declaring every Dart
|
||||
; variable would mint defs repo-wide for no resolution benefit. The top-level
|
||||
; rule is anchored under (program) — the same disambiguation the graph-node
|
||||
; query uses — so class-body fields, which reuse initialized_identifier_list
|
||||
; and are already @declaration.property, are never matched twice.
|
||||
(program
|
||||
(initialized_identifier_list
|
||||
(initialized_identifier
|
||||
(identifier) @declaration.name
|
||||
(function_expression))) @declaration.variable)
|
||||
(initialized_variable_definition
|
||||
name: (identifier) @declaration.name
|
||||
value: (function_expression)) @declaration.variable
|
||||
|
||||
; ── Imports / re-exports ─────────────────────────────────────────────────────
|
||||
(import_or_export
|
||||
(library_import
|
||||
|
|
|
|||
|
|
@ -1719,15 +1719,25 @@ export const DART_QUERIES = `
|
|||
(initialized_identifier
|
||||
(identifier) @name)) @definition.variable)
|
||||
; Closure bindings: \`var f = (x) => x;\` binds a CALLABLE, so it emits Function
|
||||
; rather than Variable, matching TS/JS. This aligns the LABEL only — call
|
||||
; resolution runs off the scope-resolution query, which still models the binding
|
||||
; as a value, so \`f()\` does not resolve here yet. Overlap with the pattern
|
||||
; above is collapsed by the parse-worker dedup (#2687).
|
||||
; rather than Variable, matching TS/JS. Overlap with the pattern above is
|
||||
; collapsed by the parse-worker dedup (#2687). Since #2693 this node is also
|
||||
; what makes \`f()\` resolve: the scope-resolution query declares the binding as
|
||||
; a value, and callable-value-flow admits it as a call target precisely because
|
||||
; the node it resolves to is a Function.
|
||||
(program
|
||||
(initialized_identifier_list
|
||||
(initialized_identifier
|
||||
(identifier) @name
|
||||
(function_expression))) @definition.function)
|
||||
|
||||
; ── Function-local closure bindings (#2693) ─────────────────────────────────
|
||||
; \`void m() { var f = (x) => x; }\` — locals parse as initialized_variable_
|
||||
; definition, which the top-level rules above never reach, so a local closure
|
||||
; had no graph node at all and \`f()\` could not resolve. Restricted to a
|
||||
; function_expression value: ordinary locals stay unindexed, as before.
|
||||
(initialized_variable_definition
|
||||
name: (identifier) @name
|
||||
value: (function_expression)) @definition.function
|
||||
(program
|
||||
(static_final_declaration_list
|
||||
(static_final_declaration
|
||||
|
|
|
|||
|
|
@ -215,6 +215,26 @@ describeIfWorkerBuilt('calls to a closure binding resolve to its Function node',
|
|||
|
||||
expect(targets).toContain('Function:App.swift:handler');
|
||||
});
|
||||
it('Dart: a top-level closure binding resolves', async () => {
|
||||
// The top-level form parses as `initialized_identifier`; the function-local
|
||||
// form as `initialized_variable_definition`. Only the latter was in Dart's
|
||||
// `bindingNodeTypes`, so the top-level binding emitted no flow captures.
|
||||
const targets = await callTargetsFor(
|
||||
'app.dart',
|
||||
'var handler = (int x) => x;\n\nint caller() {\n return handler(1);\n}\n',
|
||||
);
|
||||
|
||||
expect(targets).toContain('Function:app.dart:handler');
|
||||
});
|
||||
|
||||
it('Dart: a function-local closure binding resolves', async () => {
|
||||
const targets = await callTargetsFor(
|
||||
'local.dart',
|
||||
'int caller() {\n var handler = (int x) => x;\n return handler(1);\n}\n',
|
||||
);
|
||||
|
||||
expect(targets).toContain('Function:local.dart:handler');
|
||||
});
|
||||
});
|
||||
|
||||
describeIfWorkerBuilt('a non-callable value binding stays edge-free', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue