Codegraph Dart 内核移植完全指南:从 bug-for-bug 检查清单到 wasm 字节级对等
Codegraph 正把各语言的提取逻辑从 TypeScript/wasm 一侧逐语言下沉到 Rust 内核(codegraph-kernel)。Dart 是 R7b 批次的收官语言,也是整个移植计划中"表面最怪"的一个:它的语法把函数体挂在签名节点的兄弟位上,导致同一份 body 被走两遍、同一节点以相同 id 被铸造两次。docs/design/dart-kernel-port-checklist.md 是这份移植的权威清单——它把 TS 侧 .dart 文件会触及的每个分支都用真实 dist/ 提取器钉死(pin)、每条语法规格都用生产 wasm 的完整 CST dump 验证过。本文以该清单为骨架,讲清楚 Dart 移植为什么难、难在哪、怎么验证,并指出当前仓库中已经落地的移植产物(codegraph-kernel/src/dart.rs、vendored 语法、parity 测试)。
移植契约:SURVEY COMPLETE 的 bug-for-bug 基线
清单开头声明了移植方法论,这是理解全文的关键:
- 每个提取行为声明都钉在真实提取器上——不是读代码推断,而是用
extract-*.txtground-truth dump 逐条对照; - 每个语法规格声明都经生产 wasm 探针验证——tree-sitter-wasms 0.1.13 的 dart 构建,通过完整 CST dump 而非源码阅读得出;
- 字段存在性声明有
fieldNameForChilddump + 提取器行为双重佐证(文档特别提到 swift 的教训:单靠 dump 标签会撒谎); - 所有结论锚定在调研时 HEAD
45a53eb(clean main)上的 file:line,需与 rust-kernel-migration-plan.md(§0a 配方、§2 边界、§4 tracker、§5 门禁)以及两个姊妹清单一起读:kotlin-kernel-port-checklist.md(结构上最接近的兄弟:visitNode hook 常量 + vendored-C 构建)和 swift-kernel-port-checklist.md(共享分支活性风格与高 defer 姿态)。
清单的三大 blocking findings 决定了整个移植的形态:
- sibling-body double-walk 是重心:Dart 语法把每个函数/方法的 BODY 作为签名的下一个兄弟节点挂载,TS walker 会消费 body 两次——一次经
resolveBody(归属于函数/方法),一次经由外层通用遍历(归属于文件/类)。结果是确定性的重复节点模式(同名同 id 的本地函数挂在不同父节点下)、重复的instantiates/callsrefs、以文件/类为归属的function_ref双胞胎。没有任何已移植 walker 有过这个表面,内核侧必须逐字节、按观察到的交错顺序复现。 - 双臂 parse-error 发生率 3.4–20.7%:flutter/flutter 高达 20.73%,因为 2025-10 的语法解析不了空对象模式(
TimerInitial() =>,主流 sealed-class 状态匹配惯用法)和匿名library;指令。parity 扫频必须带--max-deferral 0.3(swift 式豁免)。3 个仓库 + 46 段 snippet 电池里 0 个 phantom error(不像 kotlin),但 defer 规则仍是"信 flag"。 - "语法升级"是已出货 wasm 的字节拷贝(对未固定依赖的去风险,而非版本变更),所以 bump 门禁平凡化——期待全部字节一致;真正的语法门禁只剩 kernel-grammar-parity(vendored-C 构建 ↔ vendored wasm 表恒等)。
语法准备:wasm 字节拷贝 + vendored-C 内核构建
调研时 dart 不在 VENDORED_WASM_LANGS 中(grammars.ts 当时的清单),生产路径解析 require.resolve('tree-sitter-wasms/out/tree-sitter-dart.wasm')(dart: 'tree-sitter-dart.wasm' 映射在 grammars.ts:36;.dart → dart 在 grammars.ts:108,无内容嗅探、无方言)。隐患在于:tree-sitter-wasms 0.1.13 的 dart 依赖是未固定的 github:UserNobody14/tree-sitter-dart,任何一次 tree-sitter-wasms 升级都会静默改变 dart 语法——这正是 vendor 机制要消灭的风险。
调研锁定的溯源信息(批次 4 语法探针验证):
- UserNobody14/tree-sitter-dart master@
d4d8f3e337d8be23be27ffc35a0aef972343cd54(2025-10-04,"Fixsetandgetcontextual keywords…" #89)——parser.c 的表与出货 wasm 位置级一致(ABI 15,STATE_COUNT 3640,SYMBOL_COUNT 524 + ALIAS_COUNT 1,TOKEN_COUNT 161,22 字段,EXTERNAL_TOKEN_COUNT 7)。 - 生产 wasm sha256
7f5364e4256cf7e55efd01dd52421ef2663caa8061b82659b7e4bf61064545ec(984,666 字节)。vendor 方案:把这个文件字节拷贝到src/extraction/wasm/tree-sitter-dart.wasm,加VENDORED_WASM_LANGS += 'dart'。不重建、不tree-sitter generate——出货字节就是参考物。copy-assets已经 globsrc/extraction/wasm/*.wasm。MIT 许可。 - 外部扫描器:有——固定 commit 上存在
src/scanner.c,负责 7 个外部 token:5 个字符串模板字符类(_template_chars_*)、_block_comment、_documentation_block_comment。也就是说/** */文档注释是扫描器产物;vendored C 必须包含 scanner.c,否则所有字符串/注释形态全毁。 - 调研时记录的五份 vendor 文件 sha256(
parser.c、scanner.c、tree_sitter/parser.h、alloc.h、array.h)列在清单 §Grammar prep 中,供逐字节核对。 - crates.io 的
tree-sitter-dart0.20.0 是 nielsenko 的 fork——拒绝(谱系不同,批次 4 探针测出 480 处位置表不匹配)。这取代了计划 §4 长尾行里 dart 的 "crates.io" 路线。
内核侧复用 kotlin #1382 的 vendored-grammar-C 机制(第二次使用):把三件套(parser.c、scanner.c、tree_sitter/*.h)拷到 codegraph-kernel/grammars/dart/(当前仓库中该目录已就位),扩展 codegraph-kernel/build.rs 的 cc::Build 以该语法自带的 flag 编译两个 C 文件(其 check-in 的 bindings/rust/build.rs 使用 -Wno-unused-parameter、-Wno-unused-but-set-variable、-Wno-trigraphs、msvc -utf-8——与 kotlin vendored 同一组)。langs.rs 注册:
extern "C" { fn tree_sitter_dart() -> *const (); }
// …
"dart" => Some(unsafe { tree_sitter_language::LanguageFn::from_raw(tree_sitter_dart) }.into()),
外加 LANGUAGES 数组追加 "dart",以及 __tests__/kernel-grammar-parity.test.ts 中 GRAMMAR_LANGUAGES += 'dart'——对 vendored wasm 逐 id 比较 ABI/kind/field 表就是这里的整套语法门禁(两个制品按构造同 commit;parity 测试证明它保持如此)。当前仓库的 langs.rs 中已能看到该 extern "C" 声明与 "dart" => 分支落地。
暂存顺序:wasm 字节拷贝 + VENDORED_WASM_LANGS 可以独立先行落地(全套测试绿;"新旧" dump 门禁退化为 cmp 一致——在一个仓库上跑一次冒烟、期待零 diff)。内核 C vendor + build.rs + langs.rs + grammar-parity 行随 walker 或与其同期落地——第一天起就同 commit。
双臂 parse-error 现实与 defer 政策
同一语法字节同时作用于两臂(按构造),调研用 error-sweep.cjs 扫了所有 ≤1 MiB 的 .dart 文件(跳过 .git/node_modules/.dart_tool):
| 仓库 | 文件数 | hasError | 比例 | phantom |
|---|---|---|---|---|
| dart-lang/shelf(小,服务端 dart) | 99 | 10 | 10.10% | 0 |
| felangel/bloc(中,Flutter + 包) | 616 | 21 | 3.41% | 0 |
| flutter/flutter(大,框架本身) | 6,465 | 1,340 | 20.73% | 0 |
误差类(经采样 + 最小化复现,dart3-errors.txt + error-diag.cjs):
- (a) 空对象模式:
Init() =>/case Init():/if (x case Init())必错(constant_pattern缺 identifier);带字段的模式(Point(x: var a))解析干净。这是现代 sealed-class 状态匹配惯用法,单独驱动了 flutter 的 20.73% 和 bloc 的 flutter 示例; - (b) 匿名
library;(悬空文档惯用法,dart-lang 风格指南)→ 缺 identifier;library foo.bar;则干净; - (c) null-aware 集合元素(
[?x]、key: ?value,Dart 3.8)→ ERROR,经常掀翻整个文件根; - (d) mustache 模板
.dart文件(bloc 的bricks/里{{name.snakeCase()}})——不是真 dart; - (e)
augment class(罕见)。
其余探测全部干净:records、非空模式、switch 表达式、sealed/base/final/interface/mixin 类修饰、extension types、super-params、单行类体(无 kotlin 式 phantom)、shebang #!、数字分隔符、CRLF。注意非 ASCII 标识符会报错(String séance)——但那本来就是非法 Dart(标识符按规范只有 ASCII);fixture 标识符保持 ASCII,非 ASCII 文本放注释/字符串里。
Defer 政策:逐文件 has_error() → defer:(信 flag,绝不看 ERROR 节点存在与否);扫频用 --max-deferral 0.3(给 flutter 的 20.7% 留余量,shelf/bloc 远低于此)。defer 率相对表格跳变是 walker bug;率本身是语法现实。
架构决策:单 walker、无 hook、双路径
清单列出的六条架构决策,直接决定 dart.rs 的形态:
- 无 preParse、无 POST_PASS。
dartExtractor没有preParsehook(languages/dart.ts 全文)→preParsedSource(kernel/index.ts)是 no-op;两臂都解析原始字节。POST_PASSES为空 →tryKernelExtractRaw保持合格。 - 不存在 dart framework resolver(grep
src/resolution/frameworks/无果,没有 Flutter resolver)。但通用的vue/astroresolver(无languages列表 ⇒ 对每种语言生效)带extract()hook → 在检测到 vue/astro 的仓库中,parse-worker 会把.dart文件强行切到解码后的extractFromSource路径(它们的 extract() hook 对.dart内容是 no-op——传输路径不同,输出必须相同)。纯 dart/Flutter 仓库走 raw buffers 路径。三个门禁仓库预期全部走 raw 路径;混合 Vue+dart 仓库就是解码路径的冒烟检查。 - 单 walker 模块(建议
codegraph-kernel/src/dart.rs),注册进 langs.rs;逐文件has_error()→defer:。抄写蓝本:kotlin.rs 的 visitNode-hook 消费常量 + hook/scan 交互;java.rs 的类式作用域栈、静态成员 refs、装饰器、类型注解遍历。三个表面在所有已移植 walker 中无先例,只能从本文档转录:(a)extractBareCall选择器遍历(dart 是第一个callTypes=[]的语言);(b) sibling-body double-walk 复现;(c) 构造函数命名/跳过 hook(resolveName/isMisparsedFunction)。 - 接线备忘(KERNEL_ABI_VERSION 2,layout.ts:20):没有任何 dart 提取路径发出携带
filePath的 ref(全部 ground-truth dump 验证为零)→REF_FLAG_FILE_PATH保持未用,每个 ref 都是 flag 0(同 swift/rust)。function_ref线码 200。节点decorators线字段永不为 dart 填充(无extractModifiershook——注解只是decoratesREFS)。节点signature字段重度使用(函数/方法以及 hook 铸造的常量)。 - MAX_FILE_SIZE (1 MiB) 与跳过目录(
.dart_tool、.pub-cache)在编排器/TS 侧,共享。isGeneratedFile匹配.g.dart、.freezed.dart、.pb.dart、.pbgrpc.dart、.chopper.dart——这些文件照常提取但跳过 function_ref flush 和 value-ref pass;build_runner 产物在 Flutter 仓库是巨大族群,移植时必须保留这个 skip。 - Kind 清单(dart 能发出的节点类型):file、import、function、method、class、enum、enum_member、type_alias、constant。绝不发出:field、property、variable、struct、interface、trait、namespace、module。实例字段完全不铸造节点(§Constants);没有 namespace/package 节点(无
packageTypeshook——library foo.bar;不可见);接口不是 dart 概念(abstract 类就是 kindclass)。
Extractor 配置与语法事实(languages/dart.ts,380 行)
配置侧的类型表(锚点 src/extraction/languages/dart.ts):
- functionTypes=[
function_signature];classTypes=[class_definition];methodTypes=[method_signature,constructor_signature];interfaceTypes=[];structTypes=[];enumTypes=[enum_declaration];enumMemberTypes=[enum_constant];typeAliasTypes=[type_alias];importTypes=[import_or_export];callTypes=[](extractCall对 dart 永不运行,所有调用 ref 走 body walker 的extractBareCall);variableTypes=[];extraClassNodeTypes=[mixin_declaration,extension_declaration]。nameField=name,bodyField=body,paramsField=formal_parameter_list(死),returnField=type(同样死)。
配置依赖的关键语法规格事实(全部经探针,mini-cst.txt/torture-cst.txt):
- 签名/体分离:每个函数/方法是一个 SIGNATURE 节点(
function_signature/method_signature/ 裸constructor_signature),其 body 是下一个具名兄弟function_body。function_body包一个block或裸=>表达式(箭头体没有 block 子节点)。async/async*/sync*关键字是 function_body 的匿名子节点。 - 字段真值表(
fieldNameForChild+ 行为钉死):function_signature.name→ identifier ✓(formal_parameter_list 是无字段子节点;返回类型是名字之前的无字段type_identifier/void_type);method_signature无任何字段(其单子节点是内层签名);getter_signature.name/setter_signature.name✓;constructor_signature对两个 identifier 都有name字段(name: Widget、name: named——childForFieldName('name')返回第一个 = 类)+parameters:字段;factory_constructor_signature子是无字段 identifier;class_definition:name:✓、superclass:、interfaces:、body:✓;extension_declaration:name:(匿名扩展时缺失)、class:(on 类型)、body:;mixin_declaration:完全无字段;enum_declaration:name:+body:;enum_constant.name✓;type_alias无字段(现代typedef X = T与遗留typedef void X(int)都是同一节点);initialized_variable_definition:name:+ 一个或多个value:字段(RHS 后置链被摊平成多个带 value 字段的兄弟)。 - 类内成员形态:方法/getter/setter/operator/factory 与带体构造函数是
method_signature > (function_signature | getter_signature | setter_signature | operator_signature | constructor_signature | factory_constructor_signature)+ 兄弟function_body,两者都是 class_body 的直接子节点。无体构造函数(Widget(this.size);、Widget._() : x = 0;)包在declaration节点里(declaration > constructor_signature [+ initializers]),walker 靠对declaration的普通递归到达。const构造函数是declaration > constant_constructor_signature,重定向工厂是declaration > redirecting_factory_constructor_signature——两者都不在 methodTypes 里 ⇒ const ctor 与const factory X.r() = Impl;不可见(无节点、无 refs)。 - 语句/表达式:调用没有 call 节点——后置链是扁平兄弟:
identifier selector selector …,每个selector包unconditional_assignable_selector(.name)/conditional_assignable_selector(?.name)/argument_part > arguments > argument*。new Foo(1)是单个new_expression(子:无字段 type_identifier + arguments)。const Foo.bar(1)在表达式位是const_object_expression;在const x = …声明里 const 挂在声明上,值是普通扁平链。级联是cascade_section > cascade_selector + argument_part(无 selector 节点 ⇒ 对一切都不可见)。赋值是assignment_expression带left:/right:字段。局部变量是local_variable_declaration > initialized_variable_definition(即使无初始化器)。Lambda 是function_expression(含body: function_expression_body子)。字符串模板:string_literal > template_substitution包完整表达式(${…})或identifier_dollar_escaped($name)。
存在的 hooks(逐个精确移植)
- visitNode(:144-157)——常量分支。
node.type === 'static_final_declaration'→ nameNode = 首个identifier具名子;若存在:valueNode =nameNode.nextNamedSibling(只取第一个 value 子——摊平链如WidgetT(0)只捕获WidgetT;包裹表达式如SHARED_MAX + 1捕获整个表达式节点);initValue = 其文本.slice(0,100)(UTF-16);ctx.createNode('constant', name, node, { signature: initValue ? \= {initValue.length >= 100 ? '...' : ''}` : undefined })。返回 **true**(消费——name 找到才 createNode,但 hook 对**每一个**到达的 static_final_declaration 都返回 true,铸造与否皆然;精确转录 early-return 形状:if (nameNode) {…}; return true)。dispatcher 随后运行scanFnRefSubtree(node, 0)且不再下探。 **节点类型的现实**(probe2-cst.txt探针):static_final_declaration= 带初始化器的final/const声明,且是**顶层**(const SHARED_MAX = 10;、final typedTop = compute();,有类型无类型均可,含多声明final multiA = 1, multiB = 2;→ 每个列表项一个节点)**或**类级**带static**(static const int K_MAX = 9;、static final sharedInst = WidgetT(0);)。实例final untyped = 5;/final int size;/ 任何var/带类型 var 成员 →initialized_identifier→ **无节点**(dart 永远零field节点)。顶层var topVar = 5;/int topTyped = 6;→ initialized_identifier → 同样不可见。常量**无 docstring、无 visibility、无 isExported**——只有 signature(钉死:/// Doc on num const?` 被丢弃)。 - resolveBody(:158-171)——
function_signature/method_signature→ 若node.nextNamedSibling是function_body则取之,否则 null(注意:裸constructor_signature——declaration 包裹的无体 ctor——走另一分支:childForFieldName('body') → null → 在子中找 class_body/extension_body → null ⇒ 具名无体 ctor 从不走 body,其initializers兄弟留给普通 visitNode 递归)。对 class/mixin/extension 节点:先标准body字段(class_definition ✓、extension_declaration ✓),否则找class_body|extension_body类型的具名子(mixin_declaration 无 body 字段 → 按类型找)。它被 extractClass/extractEnum 的 body 解析使用,也被 createNode 的 endLine 扩展使用(tree-sitter.ts:1322-1334——对 dart 活跃且承重:那里的注释点名了 Dart;方法/函数节点的 endLine 延伸到兄弟 body 的结尾,如namedL123-125 覆盖其 body)。 - getReturnType = extractDartReturnType(:80-92)——ctor = dartCtorInfo(§下):验证过的 ctor 返回类名(具名 ctor、factory → ret = 外层类)。否则 sig = dartInnerSignature(:9——method_signature 解包到 function/getter/setter 签名;不含 constructor/factory/operator——那些保持 sig = method_signature,类型搜索找不到东西)→ 首个
type_identifier具名子 → 文本,.replace(/<[^>]*>/g,'')、.trim()、取最后一段.、须匹配/^[A-Za-z_]\w*$/。钉死:Future<void>→Future(type_arguments 是兄弟节点,所以 strip 是 no-op);List<WidgetT>→List;WidgetT?→WidgetT(nullable_type 位置级解包——type_identifier 仍是第一个);void→ undefined(void_type ≠ type_identifier);num/dynamic/Object→ 自身;T(泛型参数)→T(泄漏);前缀other.OtherClass→other(前缀和名字是两个 type_identifier 叶子,第一个是前缀——.pop()永远看不到点。BUG,保留)。getter → 其类型;setter → undefined;operator → undefined(内层解包漏掉 operator_signature);ctor → 类名。 - isMisparsedFunction(:177-188)——dartCtorInfo != null 且 ctorName === className → 跳过无名构造函数
Widget(this.size)(无节点;extractFunction/extractMethod 仍解析+遍历 body——对 declaration 包裹形态是 null,所以实际什么都不发)。具名 ctor/factory 保留。@override (T) m()误解析(注解吞掉 record 返回类型后m()长得像单 identifier 的 constructor_signature)不跳过——dartCtorInfo 经 dartEnclosingTypeName(:38——向上走 class_definition/mixin_declaration/extension_declaration/enum_declaration 并读其 name 字段)验证 ids[0] 与外层类型名,reduce≠Action→ 当作它本来的方法(钉死于extract-probe4.txt:方法reduce,位置从reduce开始,sig/ret undefined,decorates override 完好)。 - getSignature(:189-208)——method_signature 解包到 function/getter/setter 签名(不含 ctor/factory/operator);params = 找具名子
formal_parameter_list;retType = 找具名子type_identifier|void_type;两者皆无 → undefined;结果 =[retType 文本 + 空格] + [params 文本],trim。钉死形状:void (int a, String b);getter →int(仅 retType);setter →(int v)(仅 params);泛型返回Future<void> load()→Future ()(type_arguments 不含在内——retType 就是那个 identifier);完整参数文本是原始源码切片——具名/可选括号、默认值、嵌套函数类型参数原样照搬(void ({int? named, required WidgetT child, String note = 'x'}));带体具名 ctor(method_signature > constructor_signature)→ undefined(解包漏掉 ctor;method_signature 上没有直接的 formal_parameter_list);无体具名 ctor(declaration > constructor_signature)→"()"式仅参数 sig(节点本身是 ctor 签名;其 formal_parameter_list 按类型找到)——两种 ctor sig 形状都要钉;operator → undefined。 - getVisibility(:209-222)——method_signature → 解包到 function/getter/setter 签名 → 其首个
identifier子;其他节点 → childForFieldName('name')。名字以_开头 → 'private',否则 'public'。怪癖,保留:每个构造函数都是 'public'——method_signature 包裹的 ctor 解包漏掉 constructor_signature(nameNode null → public);裸 constructor_signature 的name字段是类标识符(Widget而非_)→Widget._()也是 'public'(钉死)。operator → public。_check/_privateMethod→ private ✓。类/enum 在各自提取器上经同一 hook 获得 visibility。 - isAsync(:223-233)——nextNamedSibling 是 function_body → 扫描其所有子(含匿名)找 child.type ===
'async'→ true。钉死:async→ TRUE;async*和sync*→ FALSE(不同 token 文本 → 不同匿名类型——生成器不算 "async");无体(external/abstract)→ false。 - isStatic(:234-243)——node.type === 'method_signature' → 扫描所有子找类型
'static'→ true;否则 false。static WidgetT make()→ true ✓(static关键字是 method_signature 的匿名子)。裸 constructor_signature → false。顶层函数 → false。 - resolveName(:244-260)——dartCtorInfo:具名 ctor/factory → ctor 名(
named、create、_);无名 ctor 或非 ctor → undefined(落到 extractName)。 - extractImport(:261-304)——importText = trim 后整节点切片;moduleName = URI 字符串内容:
library_import > import_specification > configurable_uri > uri > string_literal(否则library_export下的同链),引号经.replace(/['"]/g,'')剥离。返回 {moduleName, signature}(无 handledRefs)→ import 节点(name = URI,如package:torture/other.dart、dart:async、src/reexported.dart)+ 通用importsref(tree-sitter.ts:3183-3194,{from: 文件节点(无 namespace 存在), name = URI, import_or_export 节点的 line/col})。as alias、show/hide组合子:忽略(不读)。import 'x.dart' deferred as y;不可见——deferred 形态的 import_specification 持裸uri(无 configurable_uri 包裹)→ hook 返回 null → 落到多 import 内联处理器(无一是 import_or_export)→ 无节点无 ref(钉死,extract-mini.txt)。part/part of/library指令是不同节点类型(part_directive、part_of_directive、library_name)不在任何类型表 → 不可见。 - extractBareCall(:305-379)——§Calls,完整矩阵见后。
不存在的 hooks(walker 绝不做这些):preParse、recoverMangledName、classifyClassNode(class_definition 永远是 kind class——abstract/sealed/base/final/interface-modified 全含)、classifyMethodNode、extractPropertyName、propertyTypes、fieldTypes、getReceiverType(永不转向 extractFunction;无 receiver QN;无 owner-contains——extension 成员挂在 extension 自己的 class 节点上)、isConst、isExported(除文件节点的字面 false 外处处 undefined)、interfaceKind、extractPackage/packageTypes(无 namespace 节点——顶层 QN 是裸的)、extractModifiers(节点 decorators 永不设置)、synthesizeMembers、skipBodilessClass(无体 class Base {} 照样铸造——dart 类总有 {} 嘛)、methodsAreTopLevel、resolveTypeAliasKind(type_alias 永远 kind type_alias)。
注册点:EXTRACTORS.dart(languages/index.ts)、FN_REF_SPECS.dart(function-ref.ts)。
重心:THE SIBLING-BODY DOUBLE-WALK(逐字复现)
因为 body 是兄弟,每个消费签名的提取器经 resolveBody 走一遍 body,随后外层循环(program 级、class-body 循环、或外层 visitFunctionBody)又把同一 function_body 节点当普通子访问一次。每趟各触发什么:
- Pass 1(visitFunctionBody,归属于函数/方法):完整 body 矩阵——extractBareCall 调用、instantiates、静态成员 refs、嵌套具名函数、fn-ref 捕获。
- Pass 2a(visitNode 递归——program/class-body 兄弟访问):只有 visitNode 分派的分支:
new_expression→ 来自 FILE(顶层)或 CLASS(类体)的 instantiates;嵌套function_signature→ 第二次 extractFunction(functionTypes 命中)铸造重复节点——同 (kind, name, line) ⇒ 同一节点 id——但其父是文件/类(contains 边来自文件/类;顶层 QNlocalFn,类内Holder::methodLocal),且它自己的 body walk 重新发出归属于重复节点的 refs;maybeCaptureFnRefs → 来自文件/类的 fn-ref 候选双胞胎;static_final_declaration从不出现在 body 里(局部是 initialized_variable_definition)→ 无常量重复。裸调用、静态读、级联:什么都不是(visitNode 里没有 extractBareCall/extractStaticMemberRef)。 - Pass 2b(外层 visitFunctionBody 的兄弟访问——LOCAL 函数 body 被其外层 body walker 看到):完整 body 矩阵再来一遍,归属于外层函数——于是局部函数里的裸调用从局部函数(pass 1)和外层函数(pass 2b)以及重复局部函数(pass 2a 自己的 body walk)三方发出。
钉死的交错(extract-probe2.txt,作为 parity 期望逐字转录):
- 顶层
hostFn含局部localFn { inner(n); }与局部localWithNew { new Holder(1); }:节点hostFn::localFn(contains 于 hostFn)……然后是localFn(裸 QN,contains 于文件,同 id,在 pass-2a 重访后);refs 顺序:calls inner from=localFn(嵌套 extract 的 pass 1)、calls inner from=hostFn(pass 2b)、calls localFn from=hostFn、instantiates Holder from=localWithNew、instantiates Holder from=hostFn、calls localWithNew from=hostFn——然后 program 级重访:calls inner from=localFn(第二节点的 walk)、instantiates Holder from=localWithNew(第二)、instantiates Holder from=file。 - 类内
useNew含局部methodLocal { h.touch(); }与new Holder(2):instantiates Holder from=useNew、calls h.touch from=methodLocal、calls h.touch from=useNew、calls methodLocal from=useNew,然后 class-body 重访:instantiates Holder from=class:Holder、calls h.touch from=methodLocal(第二)——且节点Holder::useNew::methodLocal+ 重复的Holder::methodLocal(两者 kind 都是 function,同 id)。 - fn-ref 双胞胎:顶层函数里的
register(seedValue)→function_ref seedValue from=refTaker且from=file:…(flush 顺序:先全部作用域内候选,再重访的文件/类双胞胎——捕获顺序);类方法里 →from=method:wire且from=class:H双胞胎(fnref2.dartpin)。 - 逐声明顺序:一个函数的 pass-1 refs 全部先于其 pass-2a refs,后者先于下一个声明的 refs(program 循环访问签名、然后 body、然后下一个签名)。class_body 内类成员同理。
去重现实:节点重复共享 id(generateNodeId 无唯一化器)——两条节点行、两条 contains 边;下游 store 按 id last-writer-wins,但 WIRE/parity dump 携带两行——字节对等要求两行都发、按序发。内核 node_ids vec 因此不得自去重:TS 发什么就发什么。
调用矩阵:extractBareCall 的完整行为表
只有 visitFunctionBody 咨询 extractBareCall;ref 是 {from: nodeStack 顶, name, 匹配节点(selector/const-object 节点,不是链头)的 line/col}。矩阵(全部钉死于 extract-torture.txt / extract-mini.txt):
| 源码形态 | 匹配节点 | 发出的 calls ref |
|---|---|---|
helper(count) |
selector>argument_part;prev = identifier | selector 位置的 helper |
WidgetT(1) |
同上 | WidgetT(ctor = 普通大写调用;解析侧的 CONSTRUCTS_VIA_BARE_CALL——name-matcher.ts:918——正依赖这个形状) |
obj.method(x) |
args selector;prev = .method selector;accessorPrev = identifier |
obj.method(receiver 保留,大小写不论) |
ConfigT.load() |
同上 | ConfigT.load + 来自 .load selector 的 references ConfigT 静态成员 ref(§静态成员)——双重发出 |
other.OtherClass()(前缀 ctor) |
同上 | other.OtherClass |
this.own() / super.parent() |
args selector;accessorPrev = this/super 节点(非 identifier) |
裸 own / parent |
a.b.call3(x)(2+ 跳) |
accessorPrev = 另一个非 argument_part selector | 裸 call3 |
FactoryT.create().run() |
外层 args selector;accessorPrev = 内层 argument_part selector | FactoryT.create().run——#750 经 dartCalleeOfArgPart(:100-116)重编码:innerCallee FactoryT.create 匹配 /^[A-Z]/ → `${innerCallee}().${method}`;加上来自其自身 selector 的内层 FactoryT.create ref;加上 references FactoryT |
w?.render() |
conditional_assignable_selector | w.render——?. 编码得与 . 完全相同 |
y2..add(1)..add(2)(级联) |
cascade_section(argument_part 不在 selector 内) | 无——级联调用完全不可见 |
new WidgetT(2) |
new_expression → INSTANTIATION 分支优先 | instantiates WidgetT(extractBareCall 的 new_expression 臂是死的——else-if 永远到不了);args 仍递归 |
pad(const EdgeInsetsT.all(8.0)) |
const_object_expression | const 节点位置的 EdgeInsetsT.all(typeId + '.' + nameId;仅类型形态 → EdgeInsetsT);children 递归 |
'sum ${a + compute()}' |
template_substitution 递归 | compute(插值调用发;$name → identifier_dollar_escaped → 无) |
局部 lambda 体 final lam = (int a) { helper(a); } |
function_expression 透明递归 | helper 归属于外层函数;lam(5) → lam |
ctor initializers(: size = seed())、enum 常量参数(ok(200))、默认参数值、hook 消费常量初始化器 |
永不 body-walk | 无 |
extractCall、LITERAL_RECEIVER_TYPES、SKIP_RECEIVERS、括号转换正则、模板剥离——对 dart 全部不可达(callTypes 为空)。不要移植。
静态成员/value-read refs:dart 专属分支(dart 在 STATIC_MEMBER_LANGS 中)——node.type === selector 且无 argument_part 子且 previousNamedSibling 是匹配 /^[A-Z][A-Za-z0-9_]*$/ 的 identifier → 从 enclosing 符号在 identifier(receiver)位置发 references <identifier 文本>。钉死:
ConfigT.setting;→ references ConfigT(value read);ConfigT.load()同样发 references ConfigT——.loadselector 无 argument_part(args 是下一个 selector),且 dart 分支没有 callee-of-call 跳过 → 每个大写 receiver 方法调用都双发(references + calls)。保留——这是真实仓库上最大的 ref 量怪癖;util.Config.load()→ 无(首 selector 的 prev = 小写util);this.x→ prev 是this节点 → 无。case 模式(case ColorT.blue:)→constant_pattern > qualified形状,无 selector → 无(钉死的缺口)。
类型注解 refs:dart ∈ TYPE_ANNOTATION_LANGUAGES,dart 分支活跃——每个函数/方法节点,extractTypeAnnotations 走 DART 路径:sig = 节点;method_signature → 首个内层 function/getter/setter/constructor/factory_constructor 签名(operator 回落到 method_signature 自身)。然后 extractTypeRefsFromSubtree(sig)——签名子树中每个 type_identifier 叶子一条 references ref(跳过 BUILTIN_TYPES)。后果(钉死):返回类型和参数类型一次扫完(List<WidgetT> listRet(Map<String, WidgetT> m) → references List——List 不是 builtin!、WidgetT、Map、WidgetT);抑制的 builtin:int、double、String(大写 S——Scala 行)、Boolean、bool、float、long、char;不抑制(噪声 refs,保留):num、dynamic、Object、List、Map、Set、Future、Stream、Iterable、T 式泛型参数;void 是 void_type 节点 → 结构上静默。泛型声明自引用:T generic<T>(T v) → references T ×3。前缀类型:other.OtherClass → references other 且 OtherClass(两个叶子)。extractVariableTypeAnnotation 需要 type_annotation 子——本语法无此节点类型 → 死;body walker 的 variable_declarator 分支同死。局部 WidgetT w = …;、字段类型、is/as 类型、body 里集合类型参数 → 无 refs(钉死)。
装饰器:dart 注解经兄弟扫描发 decorates。注解是 annotation 节点(带 name: 字段;带参形态有 arguments 子)——@override(裸)与 @Deprecated('x')(带参)都是 annotation,是被修饰声明的前置兄弟。extractDecoratorsFor 对类、函数、方法调用——不对 hook 铸造的常量、enum(!)或 type alias(带注解的 enum 什么都不发)。机制:扫描 #1(直接子)惰性(注解从不是签名的子);扫描 #2(前置兄弟)从声明向后走;annotation 在接受集合中;在首个非注解兄弟处停。consider():target = 首个具名子 → identifier → < 剥离 + 取最后 . 段(@ui.Widget 式剥成 Widget)→ decorates ref {from 被修饰节点, name, ANNOTATION 节点的 line/col}。带参注解发其名字;参数字表达式永不访问。叠放注解按源码逆序发出(后向遍历):@Deprecated('x') @pragma('vm:entry-point') void f() → decorates pragma 先,然后 Deprecated(钉死)。无体 ctor(declaration 包裹):extractMethod 跑在父为 declaration 的 constructor_signature 上——后向扫描扫的是 declaration 的子(constructor_signature 是 namedChild(0) → declIdx 0 → 不扫兄弟)→ declaration 前的注解挂不到任何东西。
Docstrings:dart 注释节点种类:comment(// 与 /* */)和 documentation_comment(/// 与 /** */——块式是扫描器产物,外部 _documentation_block_comment)。两种都在 getPrecedingDocstring 接受集合 → /// 行、/** */ 块、普通 // 注释都成为 docstring 且累积在一起(钉死:/// Line doc kept. + // Plain comment also kept? → 合并两行 doc;/** Block dartdoc kept. */ → 保留)。cleanCommentMarkers 的各 gm 剥离全部走 docstring.rs 的 js_multiline_strip(#1329 CRLF 语义)——调共享代码,不单独移植。注释行与声明之间的 annotation 会打断链(钉死:/// Broken by annotation. @deprecated void annotated() → doc undefined——@override 无处不在,这是主导性的真实世界损失)。Docstring 挂到:函数、方法(含 <anonymous> operator)、类/mixin/extension、enum、type alias。不挂到:hook 常量(extra 只带 signature——钉死的丢弃)、enum 成员、imports、文件节点。
Value-reference edges:dart ∈ VALUE_REF_LANGS——移植全套机制(蓝本 java.rs/kotlin.rs):CODEGRAPH_VALUE_REFS=0 kill;MAX_VALUE_REF_NODES = 20,000 封顶两趟 DFS;isGeneratedFile skip(.g.dart 之流!)。要点:
- 目标(captureValueRefScope):kind constant|variable、名字 ≥3 字符且匹配
/[A-Z_]/、父 id 前缀 ∈ {file:, class:, module:, struct:, enum:}。dart 只铸造constant→ 目标 = 文件: 或类: 下的 hook 常量(enum 作用域的static const走 enum:)。lowercase_const/plain/low(无大写无_)→ 非目标;kLimit/typedTop(内嵌大写)→ 目标。 - 读者作用域:每个 function/method/constant 节点。dart 兄弟体拉取(:883-892)活跃且承重:函数/方法读者作用域的节点是 SIGNATURE——其
nextNamedSibling若是function_body(或block)就压入读者 DFS;没有它,所有方法/函数 body 读都不可见。常量的读者子树是 static_final_declaration 本身(初始化器读:钉死DERIVED_VAL → SHARED_MAX边)。重复局部函数节点(§double-walk)也是读者作用域——同读、双作用域(按 (scope,target) 去重,作用域不同 ⇒ 局部函数读目标时来自孪生作用域的重复 value-ref 边——若 fixture 里局部函数读常量,包含此形状)。 - Shadow prune:dart 声明器情形——
static_final_declaration(目标本身)、initialized_identifier(字段/顶层 var)、initialized_variable_definition(局部)——各对其首个identifier类型具名子 bump。未初始化局部(int DERIVED;)也 bump(钉死 prune);方法局部final SHARED_MAX = 1;剪掉文件级目标(钉死)。assignment_expression不是 prune 情形(swift 式教训检查对 dart 是死的):纯赋值重绑定从不 bump,low = 5;式写对 prune 不可见。 - 发射:每读者作用域,栈 DFS(具名子按序压入、弹出——逆源序访问;边序随之);匹配节点类型
identifier。任何映射到活目标的 identifier 文本都发——包括Table.COL_LIMIT导航的成员半边(钉死)和${SHARED_MAX}插值(template_substitution > identifier)。$SHARED_MAX(无花括号)是identifier_dollar_escaped——不接受 → 无读(钉死的不称性)。跳过 self-id + 同名,按 (scope,target) 去重 → EDGE {kind:'references', metadata:{valueRef:true}},追加在所有其他边之后(每个 dump 的最后行)。
函数即值捕获(#756)— DART_SPEC:idTypes = {identifier}(裸 identifier 就是候选)。分派:arguments → args;assignment_expression → rhs(字段 right);pair → value(字段 value);list_literal → list;static_final_declaration → varinit(无字段 → 最后一个具名子规则:要求 ≥2 个具名子;name 字段守卫惰性——static_final_declaration 无 name 字段)。layers:argument → null(扇出)。无 special、无 unwrap、无 ungatedModes、无 addressOfOnly。钉死:
register(topLevel)→ 经 arguments→argument→identifier ✓;- 具名参数不捕获:
reg(cb: onlyNamed)→ 子是named_argument——不在 layers 不是 idType → 无(钉死,namedarg.dart)。Flutter 的onPressed: handler惯用法因此不可见——bug-for-bug,移植中不要"修"(候选未来精度 PR:TS 侧先加named_argument/label处理); obj.cb = assigned→ rhs 捕获 ✓;param-storage skip 比较 LHS 尾部 identifier 与 rhs 文本——this.cb = assigned保留(cb ≠ assigned),假想的this.cb = cb跳过。cb = selfStore中 selfStore 是遮蔽同名函数的参数 → 捕获 + 过 gate(假阳性,保留——钉死);[topLevel, blockDoc]列表 ✓(局部的 list_literals 也捕获——分派在节点被走到处就发);{'k': topLevel}pair ✓;final aliasTop = aliased;顶层/static → varinit 裸 identifier ✓(钉死 from=file / from=class);局部final alias = topLevel;→ initialized_variable_definition 不在分派中 → 不捕获;- 捕获点:visitFunctionBody(body)、visitNode(§double-walk 的文件/类孪生)、scanFnRefSubtree(hook 消费常量初始化器——在
function_expression处停,常量初始化器里的 lambda 不泄漏候选)。 - Flush gate:实际就是"定义在本文件"——dart import ref 是 URI(
package:foo/util.dart、dart:async),既不匹配 SIMPLE_NAME 也不匹配 QUALIFIED_IMPORT(排除:和/)→ importedNames 对 dart 永远空。definedHere = 同文件函数/方法名——包括单字母方法名(方法a会 gate 任何裸参数a——钉死假阳性,保留)和<anonymous>。每个候选都走 definedHere gate。按${fromNodeId}|${name}去重 → 幸存者作为 {referenceKind:'function_ref'}(线码 200)追加在所有 walk refs 之后——先作用域内候选,后 double-walk 文件/类孪生(捕获顺序)。
节点身份、行号与发射顺序
- createNode:id =
generateNodeId(filePath, kind, name, startRow+1)=`${kind}:${sha256(`${filePath}:${kind}:${name}:${line}`).hex.slice(0,32)}`。FILE 节点 id = 字面file:${filePath},name = basename,qualifiedName = filePath,endLine =source.split('\n').length,isExported false。 - endLine 扩展对 function/method 种类活跃:resolveBody 的兄弟 function_body 把 endLine 延伸到签名之外(
namedL123→125;bodyShapesL60→96)。类/enum 等跨度是各自节点区间。 - 每个创建节点一条 contains 边(来自 nodeStack 顶);qualifiedName = nodeStack 名字以
::连接(文件除外);无 package 节点 → 顶层 QN 裸;成员WidgetT::render;嵌套WidgetT::useNew::methodLocal(及重复的更浅WidgetT::methodLocal——§double-walk)。 - isInsideClassLikeNode:栈顶 kind ∈ {class, struct, interface, trait, enum, module}——对 dart 意味着 class(含 mixin/extension)和 enum。
- 节点位置 = SIGNATURE 节点(函数/方法):
externalFn起于 col9(external关键字在 function_signature 之外);@override (T) m()误解析起于m(col16 钉死);常量在 static_final_declaration(名字起点);enum 成员在 enum_constant;import 在 import_or_export。 - 类家族细节:extractClass 对 class_definition + mixin_declaration + extension_declaration 运行;extractInheritance 之前于 body walk;
extractCsharpPrimaryCtorParamRefs有语言门(this.language !== 'csharp'提前返回——移植为 no-op)。扩展名:extension WidgetTExt on WidgetT→ name 字段 →WidgetTExt;匿名extension on String→ extractName 回落找到首个 type_identifier = ON 类型 → 名为String的类节点(钉死——成员变成String::anonExt);被扩展类型本身不可见(无 ref——extractInheritance 对 extension/mixin 节点什么都匹配不到)。mixin:mixin MixA on BaseT→ classMixA;on类型是裸 type_identifier 子,匹配不到继承子句 → 无 refs;mixin MixB implements DrawT→interfaces子匹配 → implements DrawT ✓。extractEnum:body 必须(body字段的 enum_body);extractEnumMembers用getChildByField(node,'name')——enum_constant 有name字段 → 每常量一个 enum_member 节点,定位在 ENUM_CONSTANT 节点上(区间覆盖ok(200)——id 行 = 常量的行),然后 RETURN。ctor 参数(argument_part)永不遍历 → enum 常量参数无 refs。继承:superclass子(仅 class_definition)→ dart 分支:superclass 节点每个具名子——直接type_identifier→extendsref;mixins子 → 每内部type_identifier一条implementsref。class WidgetT extends BaseT with MixA, MixB→ extends BaseT + implements MixA + implements MixB;class OnlyMix with MixA {}→ superclass 只含 mixins → implements MixA、无 extends(钉死)。interfaces子:每子一条 implements。顺序:extends+mixins 先(superclass 子在源码中先于 interfaces),然后 interfaces——全在 decorates 和 body 成员之前(钉死 ref 顺序:extends BaseT、implements MixA、implements MixB、implements DrawT、decorates immutable、然后成员 refs)。enum 侧:interfaces子 → implements ✓;mixins子是 enum_declaration 的直接子(无 superclass 包裹)且匹配不到任何子句 →enum StatusT with MixB对 mixin 什么都不发(钉死——类侧 mixin 处理只在superclass分支内)。 - 常量发射钉(
extract-torture.txt):顶层const SHARED_MAX = 10→ constant、QN 裸、sig= 10;final DERIVED_VAL = SHARED_MAX + 1→ sig= SHARED_MAX + 1(整个表达式节点);final typedTop = compute()→ sig= compute(摊平链 → 只取第一个 value 子);static final sharedInst = WidgetT(0)→ 类下 constant、sig= WidgetT;多声明 → 各一个节点带各自列。永不铸造:实例字段(带/不带类型、late、var)、static var、顶层 var/带类型 var、顶层 getter/setter、const 构造、重定向工厂、extension_type 容器、part/part of/library/deferred import。初始化器副作用:hook 消费常量的初始化器不被遍历 → 无来自它们的 calls/instantiates(只有 scanFnRefSubtree 捕获);initialized_identifier 字段的初始化器被 visitNode 递归但只有 INSTANTIATION_KINDS 在那里发 →int counter = 0;无输出,但字段final w = new Widget();会从类发instantiates Widget(顶层var w = new Widget();从文件发)。 - visitFunctionBody 的 dart 行:maybeCaptureFnRefs 每节点;callTypes 死;INSTANTIATION_KINDS → new_expression → extractInstantiation(children 仍递归——嵌套
new Foo(bar())两发 instantiates 和内层调用);extractBareCall 无 skip/return → 子总在其后递归;variable_declarator 分支死(无此 kind);嵌套function_signature→ 具名 → extractFunction → 局部函数成function节点(QN 经栈嵌套),然后外层 walker 重走其兄弟 body(pass 2b)、外层 visitNode 重访重新提取它(pass 2a);body 内 classTypes/enumTypes 语法上不可能(dart 无局部类/enum)→ 死。递归透明穿越 if/for/while/switch(但 case 模式内部用 pattern 节点种类——constant_pattern > qualified——extractBareCall 和静态 refs 都不匹配 → case 标签里的 enum 读不可见,钉死)、try/catch、await/unary、throw、字符串模板、function_expression lambda(调用归属于外层符号)、集合字面量(<Widget>[…]类型参数静默)。 - 杂项共享路径:位置
line = startPosition.row + 1、column = startPosition.column——UTF-16 码元(textutil::col16);startIndex/endIndex 子串同样(getNodeText 处处:签名、常量初始化器.slice(0,100)、import 签名.trim())。extract() 包装:文件节点 →(无 package hook → 无 namespace)→ 根 visitNode walk → flushFnRefCandidates → flushValueRefs → pop。表顺序:节点按创建顺序(含 double-walk 重复);contains 边与创建交错;value-ref 边最后追加;walk 顺序 refs 然后 flush 时的 function_ref refs。Store/harness 对行序敏感——精确复现。CRLF:端到端探明干净——CRLF 转换的 torture fixture 与 LF 提取字节一致(extract-crlf.txt文件名归一后 diff-0)。dart.ts 无多行正则;唯一 CRLF 敏感共享路径是 cleanCommentMarkers 的gm剥离 → docstring.rs 的js_multiline_strip。多行签名切片和 ≥100 字符初始化器捕获在 CRLF 源上含原始\r字节——两臂按构造一致(同一源切片)。语言显示名Dart(grammars.ts:626)——纯装饰。
解析合约:walker 输出喂给谁(保持 TS 侧)
无 dart framework resolver、无 dart callback-synthesizer pass、dart ∉ CC_LANGUAGES——grep 验证。walker 输出喂的解析合约(行为保持稳态):
CONSTRUCTS_VIA_BARE_CALL(name-matcher.ts)——裸大写calls WidgetTrefs 解析为构造;依赖 ctor 调用保持为普通 calls ref(dart 只对new_expression发instantiates);matchDottedCallChain(name-matcher.ts,dart 在列)——消费Foo.create().run重编码 + 方法节点的returnType(extractDartReturnType 的 ctor 返类名规则正是让WidgetT.named(3).chainTail()能解析的原因——#750/#645);- 局部变量 receiver 推断——对原始源码的正则(
var lg = Logger(、Logger lg,)——除节点名外无提取依赖; - CHAIN_LANGUAGES(resolution/index.ts)含 dart——相同输入;
matchMethodCall的 bare-fn-only 豁免排除 dart——裸 fn-ref 名可解析到方法(隐式 self)——walker 只需供给名字。
门禁与 fixture:parity 机制("全都咬过")
- Vendor 门禁(替代常规 bump 门禁):wasm 字节拷贝 +
VENDORED_WASM_LANGS += 'dart'+ 内核 C vendor + langs.rs +GRAMMAR_LANGUAGES += 'dart'——全套测试绿;一次冒烟 dump-diff(shelf 上旧解析路径 vs vendored 路径)预期字节一致(同字节);kernel-grammar-parity 证明 C 构建 ↔ wasm 表恒等。 - Torture fixtures:
__tests__/fixtures/kernel-parity/torture.dart(调研的 231 行 torture.dart 播种;其extract-torture.txt是期望输出 pin),按分支清单覆盖:imports(dart:、带as+show的 package:、带hide的 export、deferred → 不可见、part → 不可见);doc 形状(///行、/** */保留、//保留、annotation 断链);注解(裸、带参、叠放 → 逆序、挂类上);顶层常量(大写/小写/多/带类型/派生、≥100 字符 sig 截断);顶层 var/带类型 var/getter/setter(全不可见);async vs async*/sync*;returnType 矩阵(builtin、泛型容器、nullable、前缀 →otherbug、T泄漏、void);signature 矩阵(参数原样——含具名/可选/默认/函数类型、getter 仅类型、setter 仅参数、泛型返回Future ());external fn(关键字后位置);完整 ctor 集合(无名跳过;具名带体——无 sig;具名无体——()sig + 初始化器调用不可见;factory;const factory + const ctor 不可见);operator →<anonymous>方法;私有_名 → visibility;extends+with+implements 类(ref 种类/顺序)+ with-only 类;mixinon(无)+ mixin implements;具名 + 匿名扩展(以 ON 类型命名的类);abstract 类(无体成员 → kindfunction;abstract getter 不可见);sealed/base 修饰(普通类);enum(简单 + 增强:常定位的 ctor'd 常量、参数不遍历、with缺口 vsimplements✓、;后成员);typedef ×3(现代/遗留/泛型——零 refs);extension type(容器不可见、成员泄漏为裸function);完整调用矩阵(§Calls 行逐字——含级联不可见、?.、const-object 参数、链重编码、插值调用、$xvs${x});静态读(X.member、X.method()双发、3 段无、case-pattern 缺口);局部形状(局部/未初始化/lambda/局部函数——精确钉死 §double-walk 交错);fn-refs(位置参数、具名参数不捕获、rhs + param-storage-skip + shadow 假阳性、list、pair、顶层/static 裸 identifier varinit、局部 alias 不捕获、未定义名被 gate、file/class 孪生);value refs(目标 CAPS + kName;经兄弟拉取的读者;constant→constant;${X}读 +$X非读;X.member成员半边读;局部 shadow prune;未初始化局部 prune;assignment 非 prune;小写非目标);符号前 unicode 注释 + 字符串(UTF-16 列)。 - CRLF 变体内存派生(kernel-tsjs-parity 模式)。
- Defer fixture #1:
switch (x) { Init() => 1, _ => 0 }(空对象模式)——内核 defer(defer:),wasm 提供恢复输出。Defer fixture #2:library;头文件——同合约。 - 生成文件 fixture(
x.g.dart命名)——提取正常但无 function_refs/value-refs(isGeneratedFile skip)。 - Parity 扫频(
scripts/kernel-parity.mjs <dir>,顺序敏感的完整对象,--max-deferral 0.3):shelf(小,99 文件)、bloc(中,616 文件——Flutter widgets + 纯 dart 包 + 生成文件族群)、flutter(大,6,465 文件——框架;defer 重臂)。预期每个非 defer 文件 0-diff,且 defer 计数接近上表。然后同一三仓库 full-init dump-diff 字节一致(内核臂 vsCODEGRAPH_KERNEL=0,scripts/dump-graph.mjs,cmp)。 - 普查抽查(double-walk 双保险):bloc 内核臂索引后,
select kind, count(*) from nodes where language='dart' group by kind必须与 wasm 臂精确相等——重复节点行正是"有帮助的"去重在 dump 门禁运行前静默分叉的地方。 - 套件:torture + CRLF + defer fixture(空对象模式文件——内核 defer、wasm 输出顶上)+
library;defer 形状;CODEGRAPH_KERNEL_EXPECT=1下全套 ×2 绿。 DEFAULT_ROUTED += 'dart'(kernel/index.ts)仅在以上全部之后;changelog 随现有内核条目走。路由后性能 sanity:三个门禁仓库全走 raw 路径(§arch-2);混合 Vue+dart 仓库是解码路径冒烟检查。dart 加速落在约 79–97% 文件上(defer 成本)。
当前仓库中的落地证据
截至本文撰写,清单描述的移植产物已可在仓库中直接查验:
- codegraph-kernel/src/dart.rs:约 1,550 行的 Rust walker,文件头注释明确自称"dart 路径的忠实 Rust 移植",并把"权威怪癖清单指向 docs/design/dart-kernel-port-checklist.md";注释逐条列出了 double-walk、
callTypes为空、ConfigT.load()双发、operator<anonymous>、无名 ctor 跳过、前缀返回类型 bug 保留、enumwith静默、deferred import 不可见、具名参数不捕获、async*/sync*非 async 等全部承重怪癖; - grammars.ts:
VENDORED_WASM_LANGS已含'dart',注释与清单 §Grammar prep 逐字对应(byte-copied 0.1.13 制品、未固定 github 依赖的去风险、crates.io fork 被拒); - codegraph-kernel/grammars/dart/:vendored C 三件套(parser.c、scanner.c、tree_sitter/ 头文件)+ LICENSE,即内核 cc::Build 编译源;
- langs.rs:
extern "C" { fn tree_sitter_dart() }与"dart" => ...LanguageFn::from_raw注册分支已落地; - kernel-dart-parity.test.ts:测试头注释逐一对应清单的 fixture 设计——torture.dart 主清单、TortureDoubleWalk.dart(double-walk 同 id 重复节点与精确重复 ref 交错)、TortureFnrefDart.dart(fn-ref 捕获通道含具名参数非捕获与 file/class 孪生)、CRLF 变体(#1329)与 defer/生成文件 pin;并声明全仓库扫频由 scripts/kernel-parity.mjs 承载(shelf/bloc/flutter,
--max-deferral 0.3); - tests/fixtures/kernel-parity/:torture.dart、TortureDoubleWalk.dart、TortureFnrefDart.dart、TortureMini.dart、TortureSigs.dart、TortureCtors.dart、TortureVrefDart.dart 等 fixture 均已入库。
这套"文档钉死行为 → Rust 转录复现 → 多仓库字节级扫频"的流程,是 codegraph 内核迁移中每个语言通行的范式;而 Dart 检查清单因其 double-walk 机制在全部已移植语言中独一份,成为该范式里信息密度最高的一份。
小结
Dart 移植的三条主线可以浓缩为:vendor 是字节拷贝去风险(wasm 字节不变、C 同 commit 编译、parity 测试守恒等)、walker 是行为转录(380 行 TS 配置的每个 hook 精确移植,callTypes 为空下由 extractBareCall + 静态成员 dart 分支承担全部调用/引用语义)、验证是顺序敏感的字节对等(double-walk 重复节点按观察交错全量发出,node_ids 不自行去重,defer 率即语法现实)。对想跟进其他语言移植的读者,这份清单的写法本身值得借鉴:每个声明都给出 file:line 锚点、探针产物名、"保留"与"死代码"的明确裁定,以及为什么安全的理由——这正是 bug-for-bug 移植能够被第三方复核的前提。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00