mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-17 05:07:37 +08:00
fix(dart): link generic calls the parser reads as two comparisons (#2375)
tree-sitter-dart parses a generic call with arguments as two comparisons about as often as it parses it as a call, depending on the code around it: `ref.read<Repo>(p)` comes out as `relational_expression(relational_expression(ref.read < Repo) > (p))`. Generic constructors (`Provider<int>((ref) => 0)`), named arguments (which then parse as a `record_literal`) and calls after `await` or a prefix operator are hit too. Neither extractor recorded a call for that shape, only a member read of `ref.read` that linked nothing, so providers, repository reads and bloc lookups written with a type argument had no callers. Across felangel/bloc, rrousselGit/riverpod and flutter/samples there are 2,853 such calls, against 2,836 generic calls with arguments that parse as calls. - Both extractors (the TS walker and the native kernel) recover the call at its `<` when the type argument names a type and the code is laid out as a call: `<` against the callee and `(` against the `>`. A comparison such as `a < b` is left alone. The recovered call gets what a parsed one does: the call at the `<`, a reference to the type argument, and no member read of the callee or of a prefixed type argument. - A call chained on a recovered call keeps its bare name. The `BlocProvider.of().increment` encoding a parsed chain gets resolves through what `of` returns, which for a generic factory is its type parameter, so it would drop the edge the bare name finds. - When the grammar ends an arrow function in front of a generic call (`(ref) => ref.watch<int>(p)` as `((ref) => ref).watch<int>(p)`), the receiver is read from the end of the arrow's body, so the call is `ref.watch` rather than a bare `watch`. Validated with the kernel loaded: bloc +85, riverpod +1,580 and flutter/samples +54 edges, none lost; kernel/wasm parity has 0 diffs on all three repos. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -701,6 +701,7 @@ extract-mini.txt):
|
||||
| `BlocProviderT<CounterT>.value(…);` as a statement | args selector; prev = `.value` selector; accessorPrev = a selector holding only `type_arguments`, whose prev is the identifier | `BlocProviderT.value` (dartTypeArgumentsReceiver, since 2026-10-06 — bare `value` before); the `.value().tail` re-encode reads it the same way |
|
||||
| `=> BlocProviderT<CounterT>.value(…)`, and as an initializer, argument or `return` value | `constructor_invocation` (type_identifier [`.` type_identifier] type_arguments `.` identifier arguments) | `BlocProviderT.value` at the node position — the LAST type_identifier, so `p.X<T>.named()` → `X.named` (since 2026-10-06; nothing before) |
|
||||
| `generic<int>(5)` | args selector (type args ride argument_part) | bare `generic` |
|
||||
| `ref.read<Repo>(p)`, `Provider<int>((ref) => 0)`, `X<T>(name: v)` parsed as two comparisons, `(ref.read < Repo) > (p)` (§Misparsed generic calls) | the `<` relational_operator (dartMisparsedGenericCall) | the parsed call's ref — `ref.read` / `Provider` / `X`, at the `<` |
|
||||
| `await fetch()` | recursion through unary/await_expression | `fetch` at the selector |
|
||||
| `throw StateError('bad')` | recursion | `StateError` |
|
||||
| `'sum ${a + compute()}'` | template_substitution recursion | `compute` (interpolation calls EMIT); `$name` → identifier_dollar_escaped → nothing |
|
||||
@@ -712,6 +713,46 @@ extractCall (:3684), LITERAL_RECEIVER_TYPES (:373-388), SKIP_RECEIVERS, the
|
||||
parenthesized-conversion regex (:4530), template-strip — ALL UNREACHABLE for
|
||||
dart (callTypes empty). Do not port them.
|
||||
|
||||
#### Misparsed generic calls (dartMisparsedGenericCall / misparsed_generic_call)
|
||||
|
||||
tree-sitter-dart parses a generic call with arguments as two comparisons about
|
||||
as often as it parses it as a call — the GLR tie goes either way with the code
|
||||
around it: `final a = ref.watch<int>(p);`, `ref.read<Repo>(p);`, `int g(Ref
|
||||
ref) => ref.read<int>(1);` and `Provider<int>((ref) => 0)` come out as
|
||||
`relational_expression(relational_expression(<callee> < <type>) > <args>)`,
|
||||
while `print(ref.watch<int>(p) + 1)`, `obj.a.b<Repo>(p)` and
|
||||
`ref.read<Repo>(a, b)` parse as calls (2,853 nests vs 2,836 parsed generic
|
||||
calls with arguments across bloc, riverpod and flutter/samples). The args are
|
||||
a `parenthesized_expression`, or a `record_literal` when named
|
||||
(`DropdownButton<String>(items: …)`); the type part is `identifier`, a `.Name`
|
||||
selector (`p.Repo`) and/or a type_arguments-only selector (`Map<K, V>`); the
|
||||
callee part sits under `unary_expression` / `await_expression` for `await` and
|
||||
prefix operators. Recovered at the `<` when the type names a type (`/^[_$]*[A-Z]/`
|
||||
or `int double num bool dynamic void`), `<` touches the callee and `(` touches
|
||||
the `>`; `a < B > (c)`, `a<b>(c)` and `a <B>(c)` stay comparisons. The
|
||||
recovered form emits what the parsed call does:
|
||||
|
||||
- `calls` at the `<` (the parsed call's argument_part starts there);
|
||||
- `references <Type>` for the type argument, via isDartTypeName (§Type refs);
|
||||
the static-member path skips `Map` in `<Map<K, V>>` so it is not doubled;
|
||||
- no member read of the callee (`ref.read`, also under `await`) or of a
|
||||
prefixed type (`p.Repo`).
|
||||
|
||||
A call chained on a recovered one keeps its bare name (`increment` in
|
||||
`BlocProvider.of<CounterCubit>(context).increment()`), unlike the parsed
|
||||
chain's `BlocProvider.of().increment`: that encoding resolves through `of`'s
|
||||
return type, which for generic factories is the type parameter, so it would
|
||||
lose the edge the bare name finds.
|
||||
|
||||
The grammar can also end an arrow function in front of a generic call:
|
||||
`(ref) => ref.watch<int>(p)` → `((ref) => ref).watch<int>(p)`, in the
|
||||
relational and the selector form alike. dartReceiverOf / receiver_of read a
|
||||
receiver in that place from the end of the arrow's body (Dart writes a member
|
||||
access on an arrow itself in parentheses), for callee names and static-member
|
||||
refs: `ref.watch`, `BlocProvider.of`. dartCalleeOfArgPart / callee_of_arg_part
|
||||
do not, so `() => BlocProvider.of<CounterCubit>(context).increment()` keeps
|
||||
`increment` bare, for the reason above.
|
||||
|
||||
### Static-member / value-read refs — dart branch (:4759-4767), STATIC_MEMBER_LANGS:346
|
||||
|
||||
Called from the body walker only (:5218). The DART-SPECIFIC branch (the
|
||||
|
||||
Reference in New Issue
Block a user