feat(kernel): R7b Dart walker — dart module, vendored-grammar-C d4d8f3e + wasm byte-copy vendor, dart default-routed (#1386)

R7b batch 4 #4 — the FINAL R7b language (docs/design/dart-kernel-port-checklist.md
is the authoritative quirk list). The fourth vendored-grammar-C language,
with a twist: production dart resolved its wasm from tree-sitter-wasms,
whose dart dependency is an UNPINNED github:UserNobody14/tree-sitter-dart —
a routine dependency update would have silently changed dart's grammar.
This PR byte-copies the shipping 0.1.13 artifact into src/extraction/wasm/
(VENDORED_WASM_LANGS += dart) and compiles the same-commit (d4d8f3e337d8)
parser.c/scanner.c in the kernel — table identity proven by the
kernel-grammar-parity row. crates.io tree-sitter-dart is the nielsenko
fork (different lineage) — rejected.

The center of gravity is THE SIBLING-BODY DOUBLE-WALK, reproduced
bug-for-bug: dart attaches every function/method body as a NEXT SIBLING of
its signature, and the TS walkers consume each body TWICE — once via
resolveBody (attributed to the function/method) and once via the enclosing
generic walk (attributed to the file/class). Duplicate local-function
nodes with the SAME id under different parents, duplicated
calls/instantiates refs, and file/class-attributed fn-ref twins all emit
in the exact observed interleave (a dedicated fixture pins the
duplicate-id rows; the bloc kind-census spot-check pins the counts).

Also preserved (probe-pinned): the extractBareCall selector matrix (the
first callTypes=[] language — cascades completely invisible, `?.` encodes
like `.`, the `ConfigT.load()` calls+references double emission with no
callee-of-call skip, capitalized-chain `Foo.create().run` re-encode,
const-object callee names); the constructor hooks (unnamed ctor skipped,
named ctors/factories renamed to the CTOR name with the class as
returnType, `@override (T) m()` record-misparse rescued by class-name
validation); operator methods minting `method "<anonymous>"`;
static_final_declaration constants via the visitNode hook while instance
fields mint NOTHING; the prefixed-return-type prefix bug (`other.OtherClass
f()` → returnType `other`); enum `with` mixins silent vs `implements`
working; anonymous extensions named after the ON type; deferred imports
invisible; named-argument callbacks NOT fn-ref-captured (the Flutter
`onPressed:` idiom — future accuracy PR, TS-side first); `async*`/`sync*`
NOT async; value-refs with the LIVE dart sibling-body pull and the
`$X`-vs-`${X}` interpolation asymmetry; dartdoc kept in all three comment
forms with the annotation-broken chain.

Gates: parity sweeps first-run 0-diff on shelf/bloc/flutter — 5,815 clean
files byte-parity, deferrals 10/21/1341 ≈ the survey's 10/21/~1340
(both-arm grammar reality: empty object patterns — the sealed-class
idiom — and unnamed `library;` dominate; --max-deferral 0.3); full-init
dumps byte-identical ×3 (shelf 7,959 / bloc 40,026 / flutter 1,855,319
dump lines); bloc per-kind node census identical across arms (the
double-walk duplicate rows survive the store identically);
kernel-dart-parity suite (7 fixtures + in-memory CRLF variants +
double-walk duplicate-id pin + generated-file skip pin + two defer pins);
full suite 2,688 green ×2 with CODEGRAPH_KERNEL_EXPECT=1.
DEFAULT_ROUTED += dart (20 langs — R7b COMPLETE).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-20 19:55:48 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent bdd687b49f
commit d1b75a1a27
26 changed files with 204077 additions and 6 deletions
@@ -0,0 +1,39 @@
/** Block dartdoc for blockDoc. */
void blockDoc() {}
/// Line doc kept.
// Plain comment also kept?
void mixedDoc() {}
// Only plain.
void plainDoc() {}
extension type Meters(double value) {
double get km => value / 1000;
void report() {
print(km);
}
}
class Action {
@override
(int, String) reduce() => (1, 'a');
void caller() {
this.own();
super.parent();
generic<int>(5);
final t = reduce();
}
}
enum Flag { on, off }
void flagUse(Flag f) {
final v = Flag.on;
switch (f) {
case Flag.off:
use(v);
default:
break;
}
}
@@ -0,0 +1,82 @@
final topFinal = seedValue();
const topConst = 42;
var topVar = 7;
int topTyped = 8;
final multiA = 1, multiB = 2;
int seedValue() => 9;
void hostFn() {
void localFn(int n) {
inner(n);
}
localFn(3);
int localWithNew() {
final w = new Holder(1);
return w.n;
}
localWithNew();
}
class Holder {
final int n;
final untypedInit = 5;
static var mutable = 3;
Holder(this.n);
Holder.other() : n = compute();
void useNew() {
final h = new Holder(2);
void methodLocal() {
h.touch();
}
methodLocal();
}
int get computed => helperCall();
}
void takeConst() {
pad(const EdgeInsets.all(8.0));
make(const Holder(3));
}
Future<int> asyncFn() async => 1;
Stream<int> genFn() async* {
yield 1;
}
Iterable<int> syncGen() sync* {
yield 2;
}
/// Doc for annotated.
@deprecated
void annotated() {}
@Deprecated('use other')
class OldClass {}
@pragma('vm:entry-point')
void pragged() {}
class Cfg {
static const retries = 3;
}
void reader() {
use(Cfg.retries);
final msg = 'retry $topConst times ${topFinal}';
print(msg);
}
void shadower() {
final topConst = 1;
use(topConst);
}
void refTaker() {
register(seedValue);
obj.cb = seedValue;
final table = [seedValue, hostFn];
final map = {'k': seedValue, 'j': notDefinedHere};
reg2(cb: seedValue);
final alias = seedValue;
}
@@ -0,0 +1,26 @@
void assigned() {}
void listed() {}
void mapped() {}
void selfStore() {}
class H {
var cb;
void wire(dynamic selfStore) {
this.cb = assigned;
cb = selfStore;
}
}
final tableTop = [listed];
final mapTop = {'k': mapped};
void aliased() {}
final aliasTop = aliased;
class K {
static final aliasStatic = aliased;
}
void onlyNamed() {}
void otherFn() {}
void positional() {}
void taker() {
reg(cb: onlyNamed);
reg2(handler: otherFn, plain: 1);
reg3(positional);
}
@@ -0,0 +1,77 @@
import 'dart:async';
import 'package:foo/bar.dart' as bar show Baz hide Qux;
import 'pkg.dart' deferred as lazy;
export 'src/out.dart' show Pub;
part 'part1.dart';
/// Doc line one.
/// Doc line two.
void topFn(int a, String b) {
var local = 5;
int typed = 6;
final con = 7;
int uninit;
uninit = 8;
local = a;
helper(a);
obj.method(a);
Config.setting;
Config.load();
w?.render();
list..add(1)..add(2);
final w2 = Widget(1);
final w3 = new Widget(2);
const e = EdgeInsets.all(8.0);
Foo.create().run();
lower().chain();
print('sum ${a + compute()} $local');
}
// plain comment
int get topGetter => 42;
set topSetter(int v) {}
class Widget extends Base with Mix1, Mix2 implements Draw, Paint {
static const int kMax = 10;
static final shared = Widget(0);
final int size;
int count = 0;
var loose;
late String name;
Widget(this.size);
Widget.named(this.size) { init(); }
factory Widget.create() => Widget(1);
Widget._() : size = 0;
@override
void render(Canvas c) { c.draw(); }
static Widget make() => Widget(3);
int get area => size * size;
set area(int v) { count = v; }
Future<void> load() async { await fetch(); }
operator +(Widget o) => Widget(size + o.size);
}
mixin Mix1 on Base {
void mixMethod() { helper(0); }
}
extension WidgetExt on Widget {
void extMethod() { render(null); }
}
enum Color { red, green, blue }
enum Status with Mix1 implements Draw {
ok(200),
err(500);
final int code;
const Status(this.code);
bool get good => code < 400;
static Status parse(int c) => ok;
}
typedef IntFn = int Function(int);
typedef void OldStyle(int x);
abstract class Base {}
class Draw {}
@@ -0,0 +1,42 @@
library my.lib;
part of 'other.dart';
void params({int? a, required Widget child, String note = 'x'}) {}
void optional([int b = 0, Widget? w]) {}
void fnTypedParam(void cb(int x), int Function(String) modern) {}
num numRet(num n) => n;
dynamic dynRet(dynamic d) => d;
Object objRet(Object o) => o;
double dblRet(double d) => d;
external void externalFn();
class Redir {
Redir();
Redir.a() : this();
const factory Redir.b() = RedirImpl;
}
void lambdas(List<int> xs) {
xs.forEach((e) => use(e));
final f = (int a) {
helper(a);
};
f(1);
xs.map((e) => e * 2).toList();
}
void bodyTypes(Object x) {
if (x is Widget) {
use(x);
}
final y = x as Widget;
final list = <Widget>[];
final map = <String, Widget>{};
throw StateError('bad');
}
@override
@protected
void doubleAnno() {}
@@ -0,0 +1,61 @@
import 'package:foo/util.dart' as util;
const SHARED_MAX = 10;
const kLimit = 20;
final DERIVED = SHARED_MAX + 1;
const low = 30;
class Table {
static const COL_LIMIT = 5;
static const plain = 6;
void reads() {
use(SHARED_MAX);
use(Table.COL_LIMIT);
log('cap $kLimit and ${SHARED_MAX}');
}
void shadowed() {
final SHARED_MAX = 1;
use(SHARED_MAX);
}
}
void freeReader() {
use(kLimit);
}
void uninitShadow() {
int DERIVED;
DERIVED = 2;
use(DERIVED);
}
void assignOnly() {
low = 5;
}
void prefixedCalls() {
util.helper(1);
util.Config.load();
Widget.named(2);
}
/// Doc on classy.
class Classy {}
/// Doc on num const?
const DOCED = 1;
/// Doc on enum.
enum E2 { a }
/// Doc broken.
@override
void afterAnno() {}
/// Kept doc.
void unicodeNext() {}
// π and émoji 🎯 in a comment
void afterUnicode(String s) {
emit('café ☕ done');
}
@@ -0,0 +1,231 @@
/// File-level doc for torture (glued to import? no — imports precede nothing).
import 'dart:async';
import 'package:torture/other.dart' as other show OtherClass;
export 'src/reexported.dart' hide Hidden;
part 'torture_part.dart';
/// Doc line one.
/// Doc line two.
void topLevel(int count, String label) {
helper(count);
}
/** Block dartdoc kept. */
int blockDoc() => 1;
// Plain comment doc.
String plainDoc() => 'x';
/// Broken by annotation.
@deprecated
void annotated() {}
@Deprecated('with args')
@pragma('vm:entry-point')
void doubleAnno() {}
const SHARED_MAX = 10;
final DERIVED_VAL = SHARED_MAX + 1;
const lowercase_const = 1;
final typedTop = compute();
var topVar = 5;
int topTyped = 6;
final multiA = 1, multiB = 2;
int get topGetter => 7;
set topSetter(int v) {}
Future<String> asyncFn() async => 'a';
Stream<int> genStar() async* {
yield 1;
}
Iterable<int> syncStar() sync* {
yield 2;
}
num numback(num n) => n;
dynamic dyn(dynamic d) => d;
Object obj(Object o) => o;
List<WidgetT> listRet(Map<String, WidgetT> m) => [];
WidgetT? nullableRet() => null;
other.OtherClass prefixedRet() => other.OtherClass();
T generic<T>(T v) => v;
external void externalFn();
void params({int? named, required WidgetT child, String note = 'x'}) {}
void optionals([int pos = 0, WidgetT? w]) {}
void fnTyped(void cb(int x), int Function(String) modern) {}
void bodyShapes(List<int> xs, Object o) {
var local = 1;
int typed = 2;
final con = 3;
int uninit;
uninit = 4;
local = uninit;
helper(local);
obj.method(local);
this_like.deep.call3(local);
ConfigT.load();
ConfigT.setting;
w?.render();
y2..add(1)..add(2);
final w1 = WidgetT(1);
final w2 = new WidgetT(2);
pad(const EdgeInsetsT.all(8.0));
FactoryT.create().run();
lower().chain();
WidgetT.named(3).chainTail();
xs.map((e) => e * 2).toList();
xs.forEach((e) => use(e));
final lam = (int a) {
helper(a);
};
lam(5);
void localFn(int n) {
inner(n);
}
localFn(6);
if (o is WidgetT) {
use(o);
}
final cast = o as WidgetT;
final tl = <WidgetT>[];
throw StateError('bad');
}
void interpolation(int count) {
log('count $count and ${SHARED_MAX} via ${refresh()}');
}
void refTaker() {
register(topLevel);
obj.cb = topLevel;
final table = [topLevel, blockDoc];
final m = {'k': topLevel, 'x': undefinedName};
reg2(cb: topLevel);
forward(topLevel: topLevel);
final alias = topLevel;
}
/// Class doc.
@immutable
class WidgetT extends BaseT with MixA, MixB implements DrawT {
static const int K_MAX = 9;
static final sharedInst = WidgetT(0);
static var mutableStatic = 1;
final int size;
final untyped = 5;
int counter = 0;
late String title;
WidgetT(this.size);
WidgetT.named(this.size) {
init();
}
WidgetT.bodilessNamed() : size = seed();
factory WidgetT.create() => WidgetT(1);
const factory WidgetT.redir() = WidgetT2;
@override
void render(CanvasT c) {
c.draw();
}
static WidgetT make() => WidgetT(3);
int get area => size * size;
set area(int v) {
counter = v;
}
bool get privado => _check();
bool _check() => true;
Future<void> load() async {
await fetch();
}
WidgetT operator +(WidgetT o) => WidgetT(size + o.size);
void useLocalNew() {
final h = new HolderT(2);
use(h);
}
void readsConst() {
use(K_MAX);
}
}
class WidgetT2 extends WidgetT {
WidgetT2() : super(0);
void _privateMethod() {}
}
class BaseT {}
class DrawT {}
class OnlyMix with MixA {}
abstract class AbstractT {
void mustImpl(WidgetT w);
int get abstractGetter;
}
sealed class ShapeT {}
class CircleT extends ShapeT {}
mixin MixA on BaseT {
void mixMethod() {
helper(1);
}
}
mixin MixB implements DrawT {
int get mixGetter => 2;
}
extension WidgetTExt on WidgetT {
void extMethod() {
render(CanvasT());
}
int get extGetter => 4;
}
extension on String {
void anonExt() {}
}
enum ColorT { red, green, blue }
/// Enum doc.
enum StatusT with MixB implements DrawT {
ok(200),
err(500);
final int code;
const StatusT(this.code);
bool get good => code < 400;
static StatusT parse(int c) => ok;
}
typedef IntFn = int Function(int);
typedef void LegacyCb(int x);
typedef MapAlias = Map<String, WidgetT>;
extension type MetersT(double value) {
double get km => value / 1000;
}
void patternUser(Object o) {
final v = ColorT.red;
switch (o) {
case ColorT.blue:
use(v);
default:
break;
}
final (a, b) = (1, 2);
use(a);
}
// π unicode: “smart quotes” précède — column check ☕
void afterUnicode(String seance) {
emit('café ☕ done');
}
+178
View File
@@ -0,0 +1,178 @@
/**
* Kernel↔wasm Dart extraction parity (R7b batch 4 of the kernel migration —
* the final R7b language).
*
* Asserts the native walker (codegraph-kernel/src/dart.rs) produces the SAME
* ExtractionResult as the wasm TreeSitterExtractor — nodes, edges, and
* unresolved refs compared as canonicalized multisets — over the checked-in
* fixtures (torture.dart: the master inventory — imports incl. deferred
* invisibility, dartdoc in all three comment forms with the
* annotation-broken chain, stacked annotations in reverse order, the
* static_final_declaration constants hook, the full ctor set with the
* unnamed-ctor skip and named-ctor renaming, operator methods as
* `<anonymous>`, the extractBareCall matrix incl. cascade invisibility and
* `?.`-as-`.`, the `ConfigT.load()` calls+references double emission,
* extends/with/implements ref kinds, enum `with` silence, anonymous
* extensions named after the ON type, value-ref targets with the sibling
* body pull; TortureDoubleWalk.dart: THE SIBLING-BODY DOUBLE-WALK — the
* duplicate local-function nodes with the same id under different parents
* and the exact duplicated-ref interleave; TortureFnrefDart.dart: fn-ref
* capture channels incl. named-argument non-capture and the file/class
* twins; TortureMini/TortureSigs/TortureCtors/TortureVrefDart: signatures
* verbatim, prefixed-return-type prefix bug, const factories invisible,
* value-ref matrix with `$X` vs `${X}` asymmetry) and their CRLF variants
* (derived in-memory — #1329), plus defer and generated-file pins.
*
* The full-repo sweeps live in scripts/kernel-parity.mjs (shelf/bloc/flutter
* with --max-deferral 0.3); this suite keeps the invariant alive in
* `npm test`. Skips when no kernel binary is staged; CODEGRAPH_KERNEL_EXPECT=1
* turns that into a failure.
*/
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import { extractFromSource } from '../src/extraction';
import { initGrammars, loadGrammarsForLanguages } from '../src/extraction/grammars';
import { tryKernelExtract, resetKernelForTests } from '../src/extraction/kernel';
import type { ExtractionResult } from '../src/types';
const KERNEL_PATH = path.join(
__dirname,
'..',
'codegraph-kernel',
'prebuilds',
`${process.platform}-${process.arch}`,
'codegraph-kernel.node'
);
const kernelBuilt = fs.existsSync(KERNEL_PATH);
const FIXTURE_DIR = path.join(__dirname, 'fixtures', 'kernel-parity');
function canon(result: ExtractionResult): { nodes: string[]; edges: string[]; refs: string[] } {
return {
nodes: result.nodes
.map(({ updatedAt: _u, ...n }) => JSON.stringify(n, Object.keys(n).sort()))
.sort(),
edges: result.edges.map((e) => JSON.stringify(e, Object.keys(e).sort())).sort(),
refs: result.unresolvedReferences
.map((r) => JSON.stringify(r, Object.keys(r).sort()))
.sort(),
};
}
const ENV_KEYS = ['CODEGRAPH_KERNEL', 'CODEGRAPH_KERNEL_LANGS'] as const;
let savedEnv: Record<string, string | undefined>;
describe.skipIf(!kernelBuilt)('kernel Dart extraction parity', () => {
beforeAll(async () => {
await initGrammars();
await loadGrammarsForLanguages(['dart']);
});
beforeEach(() => {
savedEnv = Object.fromEntries(ENV_KEYS.map((k) => [k, process.env[k]]));
resetKernelForTests();
});
afterEach(() => {
for (const k of ENV_KEYS) {
if (savedEnv[k] === undefined) delete process.env[k];
else process.env[k] = savedEnv[k];
}
resetKernelForTests();
});
function assertParity(filePath: string, source: string, minNodes = 2): ExtractionResult {
process.env.CODEGRAPH_KERNEL_LANGS = 'all';
delete process.env.CODEGRAPH_KERNEL;
const viaKernel = tryKernelExtract(filePath, source, 'dart');
expect(viaKernel, `kernel extraction failed for ${filePath}`).not.toBeNull();
process.env.CODEGRAPH_KERNEL = '0';
const viaWasm = extractFromSource(filePath, source, 'dart');
delete process.env.CODEGRAPH_KERNEL;
const k = canon(viaKernel!);
const w = canon(viaWasm);
expect(k.nodes, `${filePath}: nodes`).toEqual(w.nodes);
expect(k.edges, `${filePath}: edges`).toEqual(w.edges);
expect(k.refs, `${filePath}: refs`).toEqual(w.refs);
expect(viaWasm.nodes.length).toBeGreaterThanOrEqual(minNodes);
return viaKernel!;
}
const FIXTURES = [
['torture.dart', 30],
['TortureDoubleWalk.dart', 5],
['TortureFnrefDart.dart', 3],
['TortureMini.dart', 5],
['TortureSigs.dart', 4],
['TortureCtors.dart', 3],
['TortureVrefDart.dart', 4],
] as const;
for (const [file, minNodes] of FIXTURES) {
it(`${file}: parity`, () => {
const src = fs.readFileSync(path.join(FIXTURE_DIR, file), 'utf8');
assertParity(`fixtures/${file}`, src, minNodes);
});
it(`${file}: CRLF parity`, () => {
const src = fs.readFileSync(path.join(FIXTURE_DIR, file), 'utf8');
const crlf = src.replace(/(?<!\r)\n/g, '\r\n');
assertParity(`fixtures/${file} (crlf)`, crlf, minNodes);
});
}
it('double-walk pins: duplicate local-fn nodes share an id; refs interleave', () => {
const src = fs.readFileSync(path.join(FIXTURE_DIR, 'TortureDoubleWalk.dart'), 'utf8');
const result = assertParity('fixtures/TortureDoubleWalk.dart', src, 5);
// Local functions are minted TWICE — same (kind,name,line) → the SAME id
// — once under the enclosing function, once under the file/class (the
// sibling-body revisit). A dedupe here would silently diverge.
const byId = new Map<string, number>();
for (const n of result.nodes) byId.set(n.id, (byId.get(n.id) ?? 0) + 1);
const dupes = [...byId.values()].filter((c) => c > 1);
expect(dupes.length).toBeGreaterThan(0);
});
it('generated files extract but skip fn-ref and value-ref flushes', () => {
const src = fs.readFileSync(path.join(FIXTURE_DIR, 'TortureVrefDart.dart'), 'utf8');
process.env.CODEGRAPH_KERNEL_LANGS = 'all';
delete process.env.CODEGRAPH_KERNEL;
const viaKernel = tryKernelExtract('lib/model.g.dart', src, 'dart');
expect(viaKernel).not.toBeNull();
process.env.CODEGRAPH_KERNEL = '0';
const viaWasm = extractFromSource('lib/model.g.dart', src, 'dart');
delete process.env.CODEGRAPH_KERNEL;
const k = canon(viaKernel!);
const w = canon(viaWasm);
expect(k.nodes).toEqual(w.nodes);
expect(k.edges).toEqual(w.edges);
expect(k.refs).toEqual(w.refs);
// The skips: no function_ref refs, no valueRef edges.
expect(viaKernel!.unresolvedReferences.some((r) => r.referenceKind === 'function_ref')).toBe(
false
);
expect(viaKernel!.edges.some((e) => e.metadata?.valueRef === true)).toBe(false);
});
it('empty object patterns defer (the dominant dart-3 error class)', () => {
const broken = 'int f(Object x) => switch (x) { Init() => 1, _ => 0 };\n';
process.env.CODEGRAPH_KERNEL_LANGS = 'all';
delete process.env.CODEGRAPH_KERNEL;
expect(tryKernelExtract('lib/pat.dart', broken, 'dart')).toBeNull();
process.env.CODEGRAPH_KERNEL = '0';
const viaWasm = extractFromSource('lib/pat.dart', broken, 'dart');
delete process.env.CODEGRAPH_KERNEL;
expect(viaWasm.nodes.some((n) => n.kind === 'file')).toBe(true);
});
it('unnamed `library;` defers', () => {
const broken = '/// Doc.\nlibrary;\n\nvoid f() {}\n';
process.env.CODEGRAPH_KERNEL_LANGS = 'all';
delete process.env.CODEGRAPH_KERNEL;
expect(tryKernelExtract('lib/lib.dart', broken, 'dart')).toBeNull();
});
});
+1 -1
View File
@@ -36,7 +36,7 @@ const kernelBuilt = fs.existsSync(KERNEL_PATH);
// Every kernel-capable language. `jsx` shares the javascript grammar on BOTH
// paths (langs.rs mirrors WASM_GRAMMAR_FILES), so the distinct grammars are:
const GRAMMAR_LANGUAGES: Language[] = ['typescript', 'tsx', 'javascript', 'java', 'python', 'go', 'c', 'cpp', 'rust', 'csharp', 'ruby', 'php', 'swift', 'kotlin', 'r', 'lua', 'luau', 'scala'];
const GRAMMAR_LANGUAGES: Language[] = ['typescript', 'tsx', 'javascript', 'java', 'python', 'go', 'c', 'cpp', 'rust', 'csharp', 'ruby', 'php', 'swift', 'kotlin', 'r', 'lua', 'luau', 'scala', 'dart'];
describe.skipIf(!kernelBuilt)('kernel↔wasm grammar parity', () => {
beforeAll(async () => {