fix(dart): resolve chained static-factory / constructor calls Foo.create().bar() (#750) (#762)

Ports the #645/#608 chained-receiver mechanism to Dart, plus makes Dart factory
and named constructors first-class so their chains can resolve at all. A call
whose receiver is itself a call — `Foo.create().bar()` (static factory or
factory/named constructor) — used to drop the receiver to a bare `bar`, which
name-matched a same-named method on an unrelated type (commonly a stdlib
`Option`/`Iterator` `.map`/`.where` mis-tied to the project's own class).

- dart.ts: extractBareCall now re-encodes `Foo.create().bar` when the chain
  starts with a capitalized type; getReturnType captures the return type (generic
  `List<Foo>` → `List`); factory (`factory Foo.create()`) and named (`Foo._()`)
  constructors are indexed as `Foo::create` / `Foo::_` with return type = the
  class (via resolveName + getReturnType + constructor_signature in methodTypes).
- The UNNAMED ctor `Foo()` is deliberately NOT extracted (isMisparsedFunction),
  so plain construction stays an `instantiates` edge to the class rather than a
  call to a phantom `Foo::Foo` method.
- dartCtorInfo validates a "constructor" against the enclosing class name, so a
  method tree-sitter MISPARSES as a constructor — `@override (A, B) m()`, where
  the annotation swallows the record return type and `m()` looks like a one-id
  constructor_signature — is still extracted as the method it is (regression
  found on localsend; covered by a new test).
- name-matcher.ts / index.ts: `dart` joins the dotted-chain gate,
  CONSTRUCTS_VIA_BARE_CALL (case construction), and CHAIN_LANGUAGES (conformance
  for superclass/mixin methods). resolveMethodOnType validates, so a wrong
  inference yields no edge.

Validation: 7 synthetic tests (static factory, factory/named ctor, construction,
conformance, absent-method safety, the misparse regression, instantiation-not-
hijacked). Real-repo A/B on localsend (368 Dart files): hand-written +17/-10 — all
corrections (the -10 = 7 wrong stdlib/extension misattributions removed + 3 ctor
source-renames), plus additive factory/named-ctor call resolution. Instantiation
preserved; no node explosion. EXTRACTION_VERSION 13->14. Full suite green.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-06-09 12:53:04 -04:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 2f96f58cbb
commit 16c73e2b0e
6 changed files with 343 additions and 9 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ const SUPERTYPE_BEARING_KINDS = new Set<Node['kind']>([
* second pass. Dotted-receiver languages resolve via matchDottedCallChain; the
* `::`-receiver ones (Rust) via matchScopedCallChain.
*/
const CHAIN_LANGUAGES = new Set(['java', 'kotlin', 'csharp', 'swift', 'rust', 'go', 'scala']);
const CHAIN_LANGUAGES = new Set(['java', 'kotlin', 'csharp', 'swift', 'rust', 'go', 'scala', 'dart']);
const SCOPED_CHAIN_LANGUAGES = new Set(['rust']);
/** The extractor's chained-receiver encoding: `<inner>().<method>`. */
+8 -6
View File
@@ -605,7 +605,7 @@ export function matchScopedCallChain(
* `Foo` — and resolveMethodOnType validates, so a non-conventional `apply` that
* returns another type simply yields no edge rather than a wrong one.
*/
const CONSTRUCTS_VIA_BARE_CALL = new Set(['kotlin', 'swift', 'scala']);
const CONSTRUCTS_VIA_BARE_CALL = new Set(['kotlin', 'swift', 'scala', 'dart']);
/**
* Resolve a dotted chained call whose receiver is a static factory / fluent call —
@@ -1123,17 +1123,19 @@ export function matchReference(
}
// 1d. Dotted chained static-factory / fluent call (Java / Kotlin / C# / Swift /
// Go / Scala) — `Foo.getInstance().bar()` encoded as `Foo.getInstance().bar`,
// Go's bare-factory `New().Method()` as `New().Method`, or Scala's companion
// factory `Foo.create().bar()` (#645/#608 mechanism). Resolve the method's class
// from the inner call's declared return type, then validate it.
// Go / Scala / Dart) — `Foo.getInstance().bar()` encoded as `Foo.getInstance().bar`,
// Go's bare-factory `New().Method()` as `New().Method`, Scala's companion factory
// `Foo.create().bar()`, or Dart's static factory / factory-constructor
// `Foo.create().bar()` (#645/#608 mechanism). Resolve the method's class from the
// inner call's declared return type, then validate it.
if (
ref.language === 'java' ||
ref.language === 'kotlin' ||
ref.language === 'csharp' ||
ref.language === 'swift' ||
ref.language === 'go' ||
ref.language === 'scala'
ref.language === 'scala' ||
ref.language === 'dart'
) {
result = matchDottedCallChain(ref, context);
if (result) return result;