首页
/ Codegraph Scala 内核移植清单:从 tree-sitter wasm 到 Rust 原生内核的 bug-for-bug 迁移全解

Codegraph Scala 内核移植清单:从 tree-sitter wasm 到 Rust 原生内核的 bug-for-bug 迁移全解

2026-09-06 13:07:49作者:明树来

本文围绕 docs/design/scala-kernel-port-checklist.md 展开:这是 codegraph 项目把 TypeScript 侧(tree-sitter wasm 解析)的 Scala 提取器逐行为移植到 Rust 原生内核(codegraph-kernel)之前所做的一份"逐 bug 保真"(bug-for-bug)移植清单。读完你能掌握:如何验证 vendored 语法版本与生产 wasm 的逐表一致性、如何用生产 wasm 做 ground-truth 探针固化"怪癖即契约"的行为、Scala 提取器全部 hook 与主遍历分支的精确语义,以及移植落地时的门控(parity sweep、fixture、defer 策略)设计。

需要说明文档的时间线:该清单成文于 2026-07-20(当时 HEAD 为 45a53eb),状态为"调研完成、移植未开始";而在当前仓库中,移植已经落地——codegraph-kernel/src/scala.rs 已存在(约 1400 行,文件头注释明确写道"权威怪癖清单即本文档"),'scala' 已加入 DEFAULT_ROUTED 路由列表。因此本文既可以当作一份设计文档来读,也可以当作"清单如何变成实现"的范本来对照。

一、调研方法与证据边界:为什么必须"探针优先"

清单的开篇就交代了它的证据标准,这也是 codegraph 每份内核移植清单(如 kotlin-kernel-port-checklist.mdswift-kernel-port-checklist.md)的共同方法论:

  • 每一条语法形态断言都对照生产 vendored wasm 实测dist/extraction/wasm/tree-sitter-scala.wasm,sha256 7945b13e…,与 src/extraction/wasm/tree-sitter-scala.wasm 字节一致),而不是从代码阅读推导;
  • 每一条提取行为断言都用真实 dist/ 提取器钉死extract-*.txt ground-truth dump);
  • childForFieldName 真值表直接探针获得brace-field.outcst-snippets.txt 的 FIELDS 段),原因是这个语法把字段名挂在匿名 token 上——只看 CST dump 的节点标签会误判(这正是 swift 清单里踩过的坑)。

清单要求与 docs/design/rust-kernel-migration-plan.md 配套阅读(§0a 移植配方、§2 边界、§4 tracker 的 "scala" 行、§5 门控),并以 kotlin 清单为"最近亲兄弟"(同 JVM 家族、hook 属性分支、re-encode 门控),swift 清单为格式先例。

三大阻塞性发现(无阻塞项,但有三个需要睁眼看的事项)

  1. wasm 已经 vendored,不需要 bumpVENDORED_WASM_LANGS 自 2026-05-07(#91)起就包含 scala,vendored wasm 与 tree-sitter/tree-sitter-scala master@0aca5d0a6f 逐表一致(两次验证:batch-4 位置表对比 + 本次调研 clone-sha 匹配)。因此移植只是 vendored-grammar-C 路线(kotlin 机制),没有行为差量门控要跑。
  2. 错误发生率呈双峰分布:主流 Scala-2 风格仓库很干净(os-lib 0.00%、cats 1.80%),但前沿 Scala-3 代码不干净(scala3 compiler/src 9.88%,library/src 17.79%——capture-checking ^ 类型所致),且出错文件中约 40–60% 是 PHANTOMhasError=true 但零 ERROR/missing 节点——要相信 flag)。
  3. scala 有 32 个真实字段,但存在三处 load-bearing 的"首字段命中"陷阱(import 的 path、curried 的 parameters、extension 的 body),childForFieldName 会返回同名字段中的第一个——甚至可能是一个匿名的 { token。walker 必须对整个(named + anonymous)子节点列表复现 first-match-wins,而不是取"那个"字段。

二、语法准备:不需要 wasm bump,只需 vendored C 编译

2.1 生产 wasm 的身份与出处(已验证)

  • 生产 wasm:src/extraction/wasm/tree-sitter-scala.wasm,sha256 7945b13e6f9b15b578c5e5e4e60253c049fec07c531518163f3415a76c0621aa(src 与 dist 字节一致),ABI 15,STATE_COUNT 26650,357+5 symbols,FIELD_COUNT 32
  • 映射 scala: 'tree-sitter-scala.wasm'src/extraction/grammars.tsWASM_GRAMMAR_FILESVENDORED_WASM_LANGS)。
  • 出处:tree-sitter/tree-sitter-scala master@0aca5d0a6fe115b16d55cb100e1bb05e7fb11385(2026-04-22 的 "chore: generate and sync"——v0.26.0 之后的修复批次:scala2-compiler-100%、lambda-body restrict、wildcard self-type)。clone 的 src/parser.c sha256 等于 batch-4 探针的位置表验证副本(bc3c3c79…),该探针确认 wasm 的 kind/field 表与其位置一致。
  • 为什么不能直接 pin crate:v0.26.0 tag 对应的 crate 0.26.0 只有 26620 个状态——比我们的 wasm 少 30 个状态——pin crate 会是一次"静默降级";更晚的 master(fc99b1bd,Apr-27)是 23959 状态的重构,属于未来的 bump 候选,不属于本次移植。

2.2 Vendored-C 路线(第二次使用 kotlin 机制)

0aca5d0a6f 的 clone 拷贝生成物到 codegraph-kernel/grammars/scala/,sha 记录在注释里。当前仓库中这条路线已经写死在 codegraph-kernel/build.rs

// Scala grammar — vendored C (third vendored-grammar-C language): the
// vendored wasm is tree-sitter/tree-sitter-scala master@0aca5d0a6f (the
// 2026-04-22 generation sync — 30 states PAST the v0.26.0 tag/crate, so a
// crate pin would be a silent downgrade).
//   parser.c  bc3c3c794f19461d99d04de6c31d57fa3e41243509b9ab023a9b88ed3273d102
//   scanner.c e4ba242568ee3493015598997bf60f613802616eade62717c21109287ef64752
// parser.c is 35 MB — the biggest grammar in the tree; expect a slow cc
// step on clean builds.
let mut scala = cc::Build::new();
scala.include("grammars/scala");
scala.file("grammars/scala/parser.c");
scala.file("grammars/scala/scanner.c");
scala.flag_if_supported("-Wno-unused-parameter");
scala.flag_if_supported("-Wno-unused-but-set-variable");
scala.flag_if_supported("-Wno-trigraphs");
scala.flag_if_supported("-utf-8"); // msvc
scala.compile("tree-sitter-scala");

要点逐项对照:

文件 sha256 备注
src/parser.c bc3c3c79…(34,970,232 字节,35 MB,全树最大语法) 干净构建时 cc 步骤会明显变慢
src/scanner.c e4ba2425…(17,731 字节) 真实的外部 scanner:significant-indentation + 字符串插值;显式处理 \r(scanner.c:476)
src/tree_sitter/alloc.h / array.h / parser.h b29c1c9f… / 31e60a1b… / 180b893c… tree-sitter 头文件

build.rs 的 flag 集合抄自上游 bindings/rust/build.rscc::Build-Wno-unused*-Wno-trigraphs、msvc 下 -utf-8;编译 parser.c + scanner.c;导出符号 tree_sitter_scala(parser.c:1199034)。Cargo 侧:tree-sitter-language shim 已存在(kotlin 引入),在 codegraph-kernel/src/langs.rs 声明:

extern "C" {
    fn tree_sitter_scala() -> *const ();
}

并在 grammar_for 中注册(当前仓库 langs.rs L86-L90):

"scala" => {
    Some(unsafe { tree_sitter_language::LanguageFn::from_raw(tree_sitter_scala) }.into())
}

清单写作时要求 LANGUAGES 从 15 增至 16;当前仓库已增至 20(typescript、tsx、javascript、jsx、java、python、go、c、cpp、rust、csharp、ruby、php、swift、kotlin、r、lua、luau、scala、dart),说明清单落地后又有新语言加入。

对齐证明由 tests/kernel-grammar-parity.test.ts 承担:GRAMMAR_LANGUAGES 加入 'scala' 后,对 vendored wasm 做逐 id 的 kind/field 表对比——ABI 15 / 26650 状态 / 32 字段必须一致。许可为 MIT(tree-sitter org),仓库是 tree-sitter.json 时代布局,永远不跑 tree-sitter generate,无需 metadata shim。

2.3 没有 grammar-bump 门控

与 R7b 其他语言不同,scala 没有"新旧 wasm diff"要跑——生产已经用这个精确 revision 在解析。kernel-grammar-parity 这一行本身就是对齐证明。

2.4 scanner 状态一致性:CRLF 探针干净

  • 缩进 scanner 处理 CRLF:LF 与 CRLF 的解析在每个 fixture 上(包括 Scala-3 缩进语法)s-expression 完全一致crlf-cst.cjs:indent/torture/docs/vref/misc/ext/script 全部 sexpEqual=true,无错误翻转)。
  • CRLF 下的提取与 LF 逐字节一致,唯一例外是多行块注释 docstring 保留 \r(§六的 Docstrings——这是共享的 js_multiline_strip 问题,见 codegraph-kernel/src/docstring.rs,不是 scanner 问题)。
  • 内核按 UTF-8 解析而 wasm 按 UTF-16 解析,错误恢复差异正是"逐文件 has_error() → defer"所防护的对象;除下述错误率升高外没有 scala 特有内容。

三、错误发生率:双峰分布与 PHANTOM 问题

用生产 wasm 对全部 .scala/.sc(≤1 MiB)做 error-sweep.cjs 扫出的数据:

仓库 文件数 hasError 比率 其中 PHANTOM
os-lib(小) 59 0 0.00%
cats(中) 835 15 1.80% 4(27%)
scala3 compiler/src 577 57 9.88%
scala3 library/src 652 116 17.79% 69(59%)
scala3 全仓库 18,411 1,991 10.81% (含 tests/ 故意非法的 neg fixture,11.78%)

.sc 文件:scala3 中 14 个(1 个出错);os-lib/cats 没有。错误分类(采样自 err-samples.out,最小化自 errvariants.out / phantom-min.out):

  • (a) PHANTOM hasError——scala-3 的主导错误类hasError=true 但 CST 完整正确、零 ERROR/missing 节点。最小复现是 capture-checking 的后缀 ^def f(x: List[Int]^): Int = 1(整个 scala3 library 都用了 import language.experimental.captureChecking)。cats 的 scala-2 宏文件(FreeFoldStep.scala)也是 phantom。内核必须按 flag defer,永远不要按 ERROR 节点存在与否判断(kotlin 的教训,这里更严重)。
  • (b) end 被当作标识符end 被语法硬保留(end matchval end = 1 都 ERROR)。见 cats AndThen.scala
  • (c) 泛型 + curried 超构造器参数extends Eq[A]()(ev) ERROR(普通 Base(1)(2) 解析干净)。见 cats 的 kernel 实例。
  • (d) Unicode 符号类型名type ⊥ = Nothing(cats package.scala)。
  • (e) scala 3.0–3.3 的 given … with { } 语法given x: C with { … } ERROR(= new C {} 与冒号形式解析正常)。
  • (f) 各种 dotty 前沿语法(union-of-singleton .type 联合、紧凑 catch case、margin 处的无花括号 match)——见 scala3 compiler 文件。

所有类别都是语法固有的,且由于同一 revision 编译了两次,在两个臂(kernel/wasm)之间构造上相同

四、架构决策(6 条)

  1. 无 preParse、无 POST_PASSESsrc/extraction/languages/scala.ts 全文件没有 preParsesrc/extraction/kernel/index.ts 的 preParse hoist 是 no-op;没有 POST_PASSES 条目 → tryKernelExtractRaw 保持可用。
  2. 一个框架 resolver 可能把 scala 逼上 DECODED 路径:playResolversrc/resolution/frameworks/play.tslanguages: ['scala', 'java', 'yaml'] 且有 extract()),经由 parse-worker 的 frameworksNeedDecode 检查。detect() 条件:build.sbt 匹配 /playframework|"play"|sbt-plugin|PlayScala|PlayJava/i,或存在 conf/routes,或存在 conf/application.conf三个门控仓库都不触发(无 conf/、build.sbt 无 playframework)——它们走 raw buffers-to-store 传输;一个 Play 应用(或任何根目录有 conf/application.conf 的 akka 风格仓库)就是 decoded-path 的冒烟检查。Play 的 extract() 本身只对 conf/routes/*.routes 产出(isPlayRoutesFile),这些不是 scala 文件(无扩展名 → no-grammar 路径)——检测的代价只有 decode,不会产生错误输出。
  3. 单一 walker 模块codegraph-kernel/src/scala.rs),在 langs.rs 注册;逐文件 has_error()defer:kotlin.rs 是最近的抄本(visitNode-hook 属性分支、按节点类型分类、re-encode 门控、JVM import 形态),但 scala 在十处分叉:(a) 永不产生 namespace 节点(无 packageTypes,package 头被忽略,QN 都是裸名);(b) functionTypes 为空 → 所有 def 走 methodTypes 分支(extractMethod → 顶层回退 extractFunction);(c) val/var hook 按外层定义节点类型向上走定位,而不是按栈;(d) getSignature 是活的(字段存在),带 curried/type-params 首字段怪癖;(e) extension/given/package_object 没有 ladder 分支——它们的泄漏行为是移植最难的部分;(f) instance_expression ∈ INSTANTIATION_KINDS + scalaBaseTypeName;(g) scala 的 extends 分支迭代所有超类型;(h) scala 专属的类型标注遍历(每个 parameters + type_parameters 的上下文边界)加 hook 自己的 emitScalaTypeRefs;(i) fn-ref 规格:裸标识符 idTypes + postfix eta 解包;(j) import 按第一个 path 段命名。没有 c/cpp 式方言、没有内容嗅探:.scala.sc 都映射到 scala(grammars.ts:120-121;.sbt 不映射——build.sbt 永远不被索引)。
  4. .sc 文件是普通 scala 文件,其顶层语句把调用归属到 FILE 节点(钉死的 extract-script.txtcalls println/runTop from=file,顶层 val → constant 且 file 为 parent)。顶层语句出现在 .scala 中同理(语法接受)。
  5. 不需要 REF_FLAG_FILE_PATH(wire v2 slot):没有任何 scala 路径产出携带 filePath 的 ref(hook ref 只带 fromNodeId/name/kind/line/column;extractImport 不设 handledRefs——所有 ground-truth dump 零 filePath 输出)。节点 decorators 也从不设置(无 extractModifiers hook)——decorator 通道只有 decorates REFS。
  6. Deferral 预期:os-lib 0、cats 15/835 ≈1.8%,但 Scala-3 重度仓库 10–18% 且错误集以 phantom 为主。默认 --max-deferral 0.1 在 os-lib/cats 与主流 Scala-2 风格下成立;对 scala3 风格仓库的 sweep 需要 --max-deferral 0.3(swift 先例)。cats/os-lib 上 deferral 率跳升才是 bug 信号;dotty 前沿代码上的大数字是语法现实。

这条决策在当前仓库中可见于路由列表注释(src/extraction/kernel/index.ts#L97-L105):"Error incidence is bimodal … PHANTOM-dominated error sets … defer on the FLAG. Sweeps over scala3-style repos use --max-deferral 0.3 (swift precedent); a deferral JUMP on cats/os-lib-style code is the bug signal."

五、提取器配置:212 行 scala.ts 的完整语义

5.1 类型声明(src/extraction/languages/scala.ts

配置 说明
functionTypes [] 注释:"top-level function_definition is handled via methodTypes"
classTypes class_definitionobject_definitiontrait_definition
methodTypes function_definitionfunction_declaration 所有 def 先走 extractMethod
interfaceTypes / structTypes / enumMemberTypes / variableTypes / fieldTypes [] enumMember/variable/field 由 hook 处理
enumTypes enum_definition
typeAliasTypes type_definition
importTypes import_declaration
callTypes call_expression
nameField / bodyField / paramsField / returnField name / body / parameters / return_type
interfaceKind trait 实践中不用——extractInterface 不可达(见 §六 dispatch)

5.2 字段语义:32 个真实字段,但 first-match-wins 咬了三口

childForFieldName(f) 返回携带字段 f 的第一个子节点,而在这个语法里:(i) 多个父节点把同名字段挂在多个子节点上;(ii) 匿名 token 可以携带字段(探针 brace-field.out 证明):带花括号的 extension (t: Int) { … } 把字段 body 同时挂在 {、function_definition 和 } 上——首个命中是 { token,namedChildCount 为 0for 头把 enumerators 挂在 {/enumerators/} 上。walker 的字段查找必须按顺序扫描完整子节点列表(named + anonymous),与 tree-sitter 的 ts_node_child_by_field_name 完全一致。

5.3 visitNode hook(scala.ts#L131-L198)——每个主遍历访问的节点都会跑(tree-sitter.ts:943-953;不在 visitFunctionBody 里)

分支 1:val_definition / var_definition(活分支)。

  • 名字经 getValVarName(scala.ts:5-11):取 pattern 字段;identifier → 其文本;否则取 pattern 第一个类型为 identifier直接 namedChild。钉死行为:val (ta, tb)一个名为 ta 的节点横跨整个 val;val Some(v)vval multiA, multiB = 5 → 只有 multiA(与 kotlin 的"解构什么都不产出"不同);无 identifier → 返回 false(落入……什么都不匹配,子节点继续递归)。
  • 外层定义向上走(scala.ts:146-156):沿 node.parent 向上,class_definition | trait_definition | enum_definition | given_definition | object_definition第一个命中者胜出。class/trait/enum/given → kind field;object_definition 或什么都没找到(顶层、package_object、带花括号的 package)→ valconstant / varvariable。注意与 kotlin 的差异:没有 'local' 分支——hook 从不在函数体内部运行(visitFunctionBody 不调用它),所以体内局部 val 由普通递归处理。lazy val → constant(修饰符无关紧要)。
  • 附加项:signature = `val|var ${name}: ${typeText}` 仅当存在 type 字段(否则 undefined——val x = 1 没有 signature,已钉死);visibility 经 extractVisibility。
  • emitScalaTypeRefs(typeNode, created.id)(scala.ts:27-45):类型子树中每个 type_identifier(除 SCALA_BUILTIN_TYPES(scala.ts:14-17:Int/Long/Short/Byte/Float/Double/Boolean/Char/Unit/String/Any/AnyRef/AnyVal/Nothing/Null))→ 一条从 val 节点出发references ref,位置在 type_identifier 处。钉死:val SHARED_TABLE: Map[String, Int] → 只 reference Map(String/Int 被内置集跳过);var cb: () => Unit → 无输出。
  • 返回 true → dispatcher 跑 scanFnRefSubtree(node, 0)永不下降顶层/类作用域的属性初始化器不产出任何 calls/instantiates refval topInit = WidgetS.create()val topLazy = compute()val n = new Foo {…} → 全部无输出,已钉死)——但扫描仍捕获 fn-ref 候选(§6.10)。注意:扫描的嵌套 def 停止检查的是 functionTypes,scala 中为,所以会下降到 hook 消费的 val 内部的嵌套 function_definition,只在 lambda_expression 处停止:val fnField = (x) => runLam(x) 什么都不捕获(钉死 extract-edge2.txt)。

分支 2:enum_case_definitions(scala.ts:173-183)。 对每个直接的 simple_enum_case | full_enum_case 子节点:以 case 的 name 字段命名 enum_member 节点,位置在 CASE 节点上(所以 case Custom(rgb: Int) 横跨参数,case Earth extends Planet(5.9) 横跨 extends——钉死于 extract-torture.txt 的列号)。一行 case 一个 wrapper;case Red, Green = 一个 wrapper、两个 case。返回 true → scanFnRefSubtree;后果:case 参数不可见、case 的 extends_clause 不产出 extends ref、case 构造器参数内的调用不产出任何东西

分支 3:extension_definition(scala.ts:186-195)。 body = childForFieldName('body') 后访问 body 的 namedChildren。由于字段 body 挂在每个 def 上(花括号形式还挂在 {/} 上),这是一个三重怪癖,全部钉死(extract-ext.txtbrace-field.out):

  • 括号/缩进形式:body = 第一个 function_definition → 访问其子节点任何 extension 方法都不会铸出节点;第一个 def 的体表达式到达 ladder → 调用从外层作用域(file/class)按自身位置发出(calls concat from=file);非调用体(s.length)什么都不产出。
  • 第一个之后的所有 def 完全不可见(永不被访问)。
  • 花括号形式(extension (t) { … }):body 解析到 { token → namedChildCount 0 → 整个 extension 不可见(零节点、零 ref——extract-ext.txt ext2)。
  • 无论 body 查找是否命中,一律返回 true。

5.4 其余小 hook

  • getSignature(scala.ts:110-117,活的)params = childForFieldName('parameters')ret = childForFieldName('return_type');都没有 → undefined;sig = paramsText +(ret ? : ${retText} : '')。怪癖需保留(钉死于 extract-torture.txt):
    • Curried def 只取第一个参数列表def curried(a: Int)(b: String)(implicit ord: Ordering[Int]): Int → sig (a: Int): Int
    • 带类型参数的 def:类型参数列表胜出——在 function_definition 上 type_parameters 节点携带字段名 parameters 且先于值参数列表 → def genericDefA: Numeric, B <: BoundT: B → sig [A: Numeric, B <: BoundT]: Bdef genericLeakT: T[T]: T
    • 无参数、只有返回 → : IntRichIntS::twice);空括号 → ();次构造器 def this()()
  • getReturnType = extractScalaReturnType(scala.ts:56-67)return_type 字段文本 trim 后:this. 前缀(fluent this.type)→ undefined;replace(/\[[^\]]*\]/g, '') 剥离泛型参数(**非贪婪单趟:List[Bar]List);剥离所有 \s;取最后一个 . 段;必须匹配 /^[A-Za-z_]\w*$/。钉死:: WidgetS→WidgetS;: com.example.other.RemoteRemote: TT(泛型泄漏,保留);推断 → undefined;Unit/Nothing 不过滤(与 kotlin 不同——unitRet 有 ret="Unit",已钉死)。此值喂给 matchDottedCallChain(§七)。
  • getVisibility → extractVisibility(scala.ts:69-80):扫描类型 modifiersaccess_modifier 的直接 namedChild;文本 .includes('private') → 'private',.includes('protected') → 'protected';默认 'public'。kotlin 式的对原始文本做 includes:private[b]/private[this] → private(钉死 QualPriv)。应用于 function/method/class(+object)/enum 以及(经 hook)val/var。注意:真实 CST 里 access_modifier 位于 modifiers 内部——实际触发的是 modifiers 分支;但两个分支都要保留。
  • isAsync(scala.ts:121):字面 () => false——每个 function/method 都带 isAsync: false
  • isStatic(scala.ts:123-129):modifiers 文本 .includes('static') → scala 没有 static 关键字 → 实践中恒 false(此语法中注解不在 modifiers 内,所以没有 kotlin 式的文本误报通道——但要移植文本扫描,而不是移植成常量)。
  • classifyClassNode(scala.ts:105-108)trait_definition → 'trait',否则 'class'。所以 object_definition → kind class(含伴生对象/case object),trait → kind trait(经 extractClass(node, 'trait')——extractInterface/interfaceKind 对 scala 是死代码)。
  • extractImport(scala.ts:200-211):signature = 整节点文本 trim;moduleName = childForFieldName('path') 文本。每个点分段都是独立的带 path 字段的 identifier → first-match-wins → import 节点/ref 命名为第一段import com.example.other.OtherClass → name/ref comextract-torture.txt 钉死 ×5;import singlesingleimport a.ba)。identifier/stable_identifier 回退(:204-209)是死的(path 永远存在)。对 fn-ref 门的后果:importedNames = {com, single, …}——导入的类简单名永远进不了门(不同于 kotlin 的"最后一段"规则——flushFnRefCandidates 的 QUALIFIED_IMPORT 永远看不到完整路径,因为 ref name 只有第一段)。

5.5 不存在的 hook(walker 不得发明)

preParseresolveNamerecoverMangledNameisMisparsedFunctionisConstisExported除 file 节点的字面 false 外处处 undefined)、classifyMethodNodeextractPropertyNamepropertyTypespackageTypes/extractPackage→ extractFilePackage 返回 null → 永不产生 namespace 节点——package 头被忽略,每个顶层 QN 都是裸名,已钉死)、getReceiverType(无 receiver-QN 表面,无 owner-contains 回退)、resolveBody(body 处处经 body 字段)、extractModifiers(无 node.decorators)、extractBareCallsynthesizeMembersskipBodilessClass(无体的 class Foo 仍铸节点——tree-sitter.ts:1685 的注释点名 Scala)、methodsAreTopLevelresolveTypeAliasKindinterfaceTypes 机制。

六、主遍历行为:逐节点对照(锚点以 45a53eb 为准,位于 src/extraction/tree-sitter.ts

6.1 visitNode 分发表(ladder 936-1303)

节点 分支 行为
每个节点 visitNode hook 优先(:943) val/var、enum_case_definitions、extension 被消费;handled → scanFnRefSubtree + 停止
每个节点 maybeCaptureFnRefs(:990) 在 visitNode 上下文中也针对 arguments/assignment_expression/val_definition(SCALA_SPEC 键)触发
function_definition/function_declaration methodTypes:1027(functionTypes 为空,:994 永不触发) extractMethod:1737 → 门 :1747:类内 → method;顶层(无 receiver hook、无 methodsAreTopLevel、parent 永不是 object/object_expression)→ 回退 extractFunction:1517 → functionfunction_declaration = 无体 def(trait/抽象类中的 def m(): Int)——同一路由、无体遍历
class_definition classTypes:1005 → classify 'class' → extractClass:1679。含 case classimplicit classabstract class
object_definition classTypes:1005 extractClass → kind class(伴生对象、case objectobject X extends App
trait_definition classTypes:1005 classify 'trait' → extractClass(node, 'trait') → kind trait(extractInterface:1834 不可达)
enum_definition enumTypes:1064 → extractEnum:1914 见 §6.5
type_definition typeAliasTypes:1071 → extractTypeAlias:2890 普通 type_alias 节点(顶层与类成员皆可——Outer2::Member 已钉死)。怪癖:alias 值 ref 遍历读字段 'value' → scala 的字段是 'type'不产出对别名类型的 reference(返回 false → 子节点重访问、无匹配)。opaque type 相同
import_declaration importTypes:1209 → extractImport:3170 见 §6.6
package_clause 无分支 被递归。头形式:什么都不提取、什么都不压栈——内容保持 file-parent 且 QN 裸名。花括号形式 package a.b { class X }:递归到达成员(class X → file-parent,QN X——钉死 extract-misc.txt)。多重/链式 package 同样被忽略
package_object 无分支 被递归 → template_body 成员在 FILE 在栈时被访问:defs → extractMethod → 非类式 → function,vals → hook(enclosingDef 向上走找不到——package_object 不在列表里)→ constant/variable;全部裸 QN file 子节点(钉死:pkgHelper function、pkgShared constant)
given_definition 无分支 被递归。given x: T = new T {…} → instance_expression 子节点 → :1255 → 从外层作用域 instantiates +(匿名体经递归)见 instance_expression 行。given T = expr → expr 被递归(调用从外层作用域发出——summonOrd() 已钉死)。永不铸出 given 节点;given 的名字不绑定任何东西
extension_definition hook §5.3——首 def 泄漏 / 花括号形式不可见
call_expression(顶层 / template_body 语句) callTypes:1248 → extractCall:3684 类体语句(主构造器后的 require(size > 0)object Boot extends App { bootUp() })归属到 CLASS 节点——钉死 extract-edge2.txt
instance_expression INSTANTIATION_KINDS:1255(:360 点名 scala)→ extractInstantiation:4610 见 §6.7
infix_expression 无分支 被递归;left/operator/right 字段。中缀调用不可见list map transforma foo b1 :: restx + ycounter += 1 什么都不产出(无 calls ref、无 fn-ref——已钉死)
assignment_expression 无 ladder 分支;SCALA_SPEC 派发 fn-ref rhs 捕获(§6.10);子节点被递归(LHS field_expression 在体内到达 extractStaticMemberRef——§6.7)
annotation 无分支(是定义节点的子节点) 由 extractDecoratorsFor 从被装饰节点消费(§6.8)
lambda_expression / case_block / match_expression / for_expression / indented_block / block 无分支 透明递归(体内经 visitForCallsAndStructure)
INSTANTIATION_KINDS 其他 / impl_item / swift property / property_signature / export_statement(TS) 永不 非 scala 节点类型;swift property 分支 :1121 有语言门控

6.2 节点创建、ID 与限定名

  • createNode(:1308):id = generateNodeId(filePath, kind, name, startRow+1) = `${kind}:${sha256(`${filePath}:${kind}:${name}:${line}`).hex.slice(0,32)}`。FILE 节点 id = file:${filePath}(:509),name = basename,qualifiedName = filePath,endLine = source.split('\n').length,isExported false。
  • endLine 扩展经 resolveBody(:1329-1334)调用 hook——scala 无此 hook → getChildByField(node, 'body');:1330 只调 this.extractor.resolveBody?.()——hook 缺失 → 永不扩展(scala 的 body 是子节点,本就在范围内)。内核侧:跳过。
  • 每个节点从栈顶发 contains 边(:1363);extractModifiers 合并(:1355)失效;captureValueRefScope(:1374)是活的(§6.9)。
  • buildQualifiedName(:1447-1460):栈名用 :: 连接,排除 file 节点,namespacePrefix 恒空。无 namespace 节点 → 顶层 QN 在带 package 的文件中也是裸名(处处钉死)。伴生对象:class + object 是两个同名 class kind 节点(不同起始行 → 不同 id)且它们的成员共享同一 QN 命名空间——WidgetS::render(class)与 WidgetS::create(companion)无法按 QN 区分(已钉死;无消歧——保留)。
  • isInsideClassLikeNode(:1486):栈顶 kind ∈ {class, struct, interface, trait, enum, module}——object 算(kind class)、trait 算、enum 算。
  • isClassScopeConstantAssignment(:1508):要求节点类型 assignment——scala 的是 assignment_expression → 恒 false(Ruby 专属,死代码)。

6.3 extractFunction / extractMethod(:1517 / :1737)——所有 def

路由回顾:所有 def 先走 extractMethod。类内 → method 节点。顶层 → :1747 门失败 → extractFunction(object/object_expression parent 检查 :1751 从不匹配 scala)→ function 节点。extras(两者):docstring(§6.8)、signature(活 hook)、visibility、isAsync false、isStatic false、returnType(hook);isExported 只在 extractFunction 路径 → undefined(hook 缺失)。extractTypeAnnotations(§6.11)后 extractDecoratorsFor(§6.8),压栈,body = getChildByField(node, 'body')(block / indented_block / 表达式——单表达式体如 = new WidgetS(a)= a + 1 作为 body 被遍历;钉死 topLevel 发出的 instantiates),visitFunctionBody,弹栈。

  • 名字:name 字段。运算符 def 保留运算符文本作为名字+::——operator_identifier 节点;unary_- 是普通 identifier;钉死 method WidgetS::+)。反引号名字保留反引号(class `Weird Name` / method `strange def` 已钉死)。次构造器是名为 this 的 methoddef this() = this(0) → method WidgetS::this + calls ref this——已钉死;NAME_STOPLIST 只作用于 fn-ref,不作用于 calls)。
  • function_declaration(无体 def):铸出 method 节点,sig/ret 完整,无体遍历(AbsS::abstractM 已钉死)。

6.4 extractClass(:1679)——class、object、trait 与有体/无体头部的不对称

resolvedBody = hook(缺失)?? getChildByField(node, 'body')template_body(花括号或 scala-3 冒号形式——同一种节点类型)/ enum 的 enum_body。无 skipBodilessClass → 无体也铸节点。extras:docstring、visibility、isExported undefined。然后 extractInheritance(§6.10)——extends refs 先于成员发出。extractCsharpPrimaryCtorParamRefs——csharp 门控的 no-op。extractDecoratorsFor(class 上的注解——@deprecated class Old → decorates)。压栈,经 visitNode 遍历 body 子节点,弹栈。

  • 有体的 class:只访问 template_body 子节点class_parameters(主构造器)子节点永不被遍历构造器参数不铸节点、其类型不产出 reference、其默认值调用什么都不产出(钉死:WidgetS 的 label: String = defaultLabel() → 静默;case-class 字段不可见——DataS 零成员)。
  • 无体的 class/object:body = 节点自身(:1714)→ 头部子节点被访问:class_parameters 递归到达默认值的 call_expression从 CLASS 节点发出的 calls(钉死:case class DataS(x: Int, y: String = mkY())calls mkY from=class:DataS),extends_clause 的 arguments 递归同样到达超构造器参数调用。必须精确复现这一不对称。(被重访问的 extends_clause 不额外产出——extends refs 只来自 extractInheritance;type 节点/class_parameter 子节点不匹配任何 ladder 分支。)
  • 类体成员:val/var → hook(field——或经 enclosingDef 向上走在 OBJECT 内的 constant/variable,使其成为 value-ref 目标);defs → extractMethod;嵌套 class/object/trait/enum/type_definition → 各自分支(QN 链已钉死:Outer2::InnerObj::IC);template_body 语句(calls)→ 从 class 走 extractCall(§6.1);次构造器 → method this
  • Trait:extractClass 且 kind trait——trait 的 visibility 确实发出(extractClass 路径而非 extractInterface——vis="public" 已钉死),不同于 kotlin 的 interface 路径。Trait 的 vals → hook 'instance' → fieldDrawable::traitVal 已钉死);无体 trait 的 defs → methods。
  • Self-type(trait X { self: Y => … }):self-type 不可见(无 refs、无节点);成员正常提取(钉死 extract-misc.txt)。

6.5 Enums(extractEnum :1914 + hook 分支 2)

body = enum_body必需——无体 enum 不会铸任何东西;实际不存在)。extras:docstring、visibility、isExported undefined。extractInheritance 运行(enum 自己的 extends_clause)。body 循环(:1941-1950):enumMemberTypes 为空 → 每个子节点走 visitNode:enum_case_definitions → hook → 位于 case 节点位置的 enum_member 节点(简单 case = 仅名字范围;带参数/extends 的 case 横跨尾部——列号已钉死);function_definition → extractMethod(enum 是类式 → Http::describe);vals → hook → fields。extractEnumMembers(:1958)对 scala 是死代码。怪癖需保留:case 参数(Custom(rgb: Int))与逐 case 的 extends(case Earth extends Planet(5.9))完全不可见——无 field 节点、无 extends refs、构造器参数内的调用不产出(hook 消费所致)。Enum 的 class_parameters(enum Planet(mass: Double))与任何有体 class 一样不可见。

6.6 Imports(:3170-3236)

Hook 返回 {moduleName: 第一个 path 段,signature: 全文 trim} → import 节点(name/QN = 第一段)+ 通用 imports ref(:3183-3194):{fromNodeId: 永远是 file 节点(无 namespace),referenceName: 第一段,import_declaration 的行/列}。无 scala 专属 emit pass(:3197-3234 全部门控到其他语言)。形态(CST 钉死于 cst-snippets.txt §imports):

  • import a.b.C → 三个 path identifier → name a
  • 选择器 {C, D}(namespace_selectors)、通配 _/*/given(namespace_wildcard)、重命名 {X => Y}(arrow_renamed_identifier)/ {X as Y}(as_renamed_identifier,name/alias 字段)——全部不可见:名字仍是第一个 path 段;选择器/别名不绑定任何东西。
  • import single(单段)→ single
  • 无注释粘连(kotlin 的怪癖不复现——scala 注释保持兄弟节点;docs 单独钉死)。

6.7 extractCall(:3684)——scala 的路径

入口:非 vbnet/erlang/ruby/arkts。func = getChildByField(node, 'function') ?? namedChild(0)(:4313)——scala 的 call_expression 有真实的 function 字段。cpp 运算符恢复 :4324 被门控关闭。

成员分支(:4364)——func.type === field_expression(在 :4364 列表中):

  1. property = getChildByField('property') → null;getChildByField('field') → 成员 identifier(scala field_expression 字段:value + field)。kotlin 的 navigation_suffix 回退是死的。
  2. receiver = object/operand/argument 字段 → 全 null → func.namedChild(0) = value 子节点。
  3. LITERAL_RECEIVER_TYPES(:4397,集合在 :373-388):scala 命中 string"lit".toUpperCase())与 integer_literal5.toString())→ 什么都不产出(已钉死)。移植整个集合。
  4. receiver identifier(:4401)且不在 SKIP_RECEIVERS {self,this,cls,super} → `${recv}.${method}`w.renderRegistry.registerobj.method)。this/super receiver 是带这些文本的普通 identifier 节点 → SKIP → 裸 methodName(this.mine()minesuper.hashCode()hashCode——已钉死;不同于 kotlin 的 this_expression 路径,净效果相同)。
  5. receiver call_expression + scala 在门内(:4408-4418)→ #750 re-encode 的 scala 臂(:4443-4464):innerFn = getChildByField(receiver, 'function')(这里是真实字段——不是 kotlin 的 namedChild(0))→ 文本 ->. 然后 \s+ 剥离;仅当 /^[A-Z]/ 时 re-encode(:4461)→ `${innerCallee}().${methodName}`。钉死:WidgetS.create().render()WidgetS.create().render + 内层 WidgetS.create(递归);Foo(1).bar()Foo().bar + Foo(companion-apply 链);lowerFactory().chain() → 裸 chain + lowerFactory
  6. receiver field_expression(两跳 a.b.method3())或其他 → 裸 methodName(已钉死)。

Else 分支(:4518-4520)——calleeName = func 原始文本:裸 helperapply 语法糖 WidgetS(1) → calls ref WidgetS(大写裸名——resolution 的 CONSTRUCTS_VIA_BARE_CALL 处理它,§七);次构造器内的 this(0) → calls this。怪癖需保留(已钉死):

  • generic_function callee 保留类型参数genericCallInt → calls genericCall[Int](c/cpp 的 < 剥离 :4542 被门控关闭,且目标是 < 而非 [;确定性的"垃圾"——逐字节复现)。
  • Curried 调用发出原始文本内层curried(1)(2) → 外层 callee = func(一个 call_expression)原始文本 curried(1) + 内层 curried(递归);Foo(1)(2)Foo(1) + Foo(钉死 extract-misc.txt)。
  • 括号转换正则(:4530)适用(单名括号改写)——移植它。
  • 最终 ref:{callerId = 栈顶,name,line = call startRow+1,column = call startColumn(UTF-16)}。链/参数由递归重访问。
  • 字符串插值内的调用会发出(在体内):s"… ${w.render()} …" → 在内层调用位置 calls w.render(interpolation > block > call 递归);$id(interpolation > identifier)不发调用(已钉死)。
  • Lambda 参数(xs.map(el => …) / { el => … } / 部分函数 { case q => … })——arguments 节点分别是 arguments/block/case_block;全部被递归;内层调用归属外层 function(lambda 什么都不铸)。

6.8 Instantiation(instance_expression,:4610,scala 臂 :4647-4662)与静态成员/值读取 refs

  • ctor = constructor/type/name 字段 → 在 instance_expression 上全 null(它只有 arguments 字段)→ namedChild(0) = 类型节点 → scalaBaseTypeName(:201-224):type_identifier/identifier → 文本;generic_type → 递归 namedChild(0)(new Monoid[Int]Monoid);stable_type_identifier/stable_identifier → 最后一个 identifier 段(new a.b.C()C);默认 → 第一个直接 type_identifier 子节点 ?? null。→ 位于 instance_expression 位置(new 处)的 instantiates ref。发出位置:表达式体(def f = new W(a)——从 function)、体内语句/初始化器、任意作用域的 given 右值(从 file/class)、ladder 访问的语句位置。从 hook 消费的 val 初始化器发出(val topInit = new W(…) 在顶层/类作用域 → 无输出——已钉死)。匿名类体:见 §6.1——永不产生匿名 class 节点;成员泄漏到外层作用域;无对实例化类型的 extends ref(extractAnonymousClass 永不运行:findAnonymousClassBody:4815 找 class_body/declaration_list,而 scala 的匿名体是 template_body → null → skipChildren 保持 false → 子节点被递归:template_body 的 defs 命中 methodTypes → extractMethod →(顶层非类式)→ 匿名类方法作为外层作用域的 function/method 泄漏出来——钉死:顶层 given 的 comparefunction compare(裸 QN);object 内 given 的 compare/innerVal该 object 的 method/fieldRegistry::compareextract-given2.txt))。
  • 静态成员/值读取 refs(:4750-4808)——scala ∈ STATIC_MEMBER_LANGS(:346):只从 body walker 调用(:5218)。field_expression ∈ MEMBER_ACCESS_TYPES(:327)。机制:调用 callee 跳过(:4772-4778——Registry.register(w) 的 callee nav 不产出);recv = object/expression/scope 字段(null)?? namedChild(0);接受类型 identifier(:4792);文本 /^[A-Z][A-Za-z0-9_]*$/位于 receiver 位置的 references ref。钉死(extract-torture.txt StaticReads):val c1 = Registry.count → references RegistryHttp.Ok → references Httpprocess(Registry.count)(参数位置)→ references Registry(外加 process 调用)。com.example.Fq.CONST_READ无输出(外层 recv 是 field_expression;最内层 com 小写)。赋值写入会发出(不同于 kotlin):Registry.count = 5——LHS field_expression 是 assignment_expression 的普通子节点,被 body 递归访问 → references Registry(已钉死)。顶层/类作用域的读取不发(仅 body walker);hook 消费的初始化器更不发。

6.9 Decorators、Inheritance 与类型标注 refs

  • Decorators——scala 注解确实发 decorates,含参数:extractDecoratorsFor(:4897)针对 function/method/class/object/trait/enum 运行(针对 hook 创建的 val/enum 成员——钉死:@volatile var → 无输出)。Scala 注解是定义节点的直接子节点annotation 节点,字段 name: type_identifier、arguments)——扫描 #1(:4976-4978)命中;consider() 接受类型 annotation(:4928);目标循环找到 type_identifier name 子节点(:4951)→ 名字文本 → < 剥离 + 最后 ./:: 段规范化(:4959-4962)→ decorates ref {from: 被装饰节点,name,ANNOTATION 节点的行/列}。钉死:@main def entry → decorates main@inline def fastinline@deprecated("gone", "1.0") def old → decorates deprecated(带参数的注解仍发出——name 字段先于 arguments;kotlin 的 constructor_invocation 丢发不复现)。注解参数表达式永不被访问(无 calls refs)。modifiers 下降(:4983)与向前兄弟扫描(:5013)对 scala 失效(注解既不在 modifiers 内也不是前导兄弟)。
  • Inheritance——extends_clause 的 scala 分支(:5339-5360):extractInheritance 循环定义节点直接 namedChild 中的 extends_clause(字段 extend;class/object/trait/enum 定义上的直接子节点)。scala 分支迭代 extends_clause 的所有 namedChild,每个经 scalaBaseTypeName 映射 → 每个超类型一条 extends ref {name,超类型节点的行/列}。钉死:extends BaseW(size) with Drawable with Ordered[WidgetS] → extends BaseW + Drawable + Ordered(泛型解包;arguments 子节点经默认分支得 null → 跳过)。scala-3 逗号形式 extends B, C → 两者。object 上的单个 extends Shapeobject Circle extends Shape)✓(已钉死)。curried 超构造器参数 extends Base(1)(2)(普通形式、解析干净)→ 只 extends Base(第二个 arguments 子节点 → null → 跳过;extract-super2.txt)。derives Show(derives_clause,字段 derive)什么都不发出——不是 extends_clause;保留静默。Scala 永不发出 implements——trait 走 extends。Enum CASE 的 extends(case Earth extends Planet(5.9))——被 hook 消费、无输出(§6.5)。匿名 new T {…}——无 class 节点、无 extends(§6.8)。
  • 类型标注 refs——scala ∈ TYPE_ANNOTATION_LANGUAGES(:5753):三条活遍历 + hook 自己的(extractTypeAnnotations :5788,针对 function/method):
    1. scala 专属参数遍历(:5839-5842):每个直接的 parameters 子节点(所有 curried 列表——含尾随 implicit 列表)→ extractTypeRefsFromSubtree(:6090):除 BUILTIN_TYPES(:5768-5782——含 scala 块 Int/Long/…/Null 加跨语言条目;注意小写 error 等不可能出现)外每个 type_identifier 叶子 → 叶子处的 references ref。type_parameters 节点也携带字段名 parameters 但不是类型 parameters → 不被此遍历匹配(由遍历 3 匹配)。
    2. 返回遍历(:5851):getChildByField('return_type') 子树 → refs(泛型返回 Option[A]Option + A)。
    3. scala 专属类型参数遍历(:5863-5870):第一个 type_parameters 子节点 → 子树 refs——上下文/上界会发出[A: Numeric, B <: BoundT]Numeric + BoundT;声明名 A/B 是 identifier 节点 → 静默)。genericDef 钉死顺序:参数遍历 refs(A)→ 返回(B)→ 边界(NumericBoundT)。
    4. type_annotation 直接子节点搜索(:5873)——scala 无此节点类型 → 死。
    • 加上 hook 的 emitScalaTypeRefs 作用于 val/var 类型标注(§5.3——自己的内置集 SCALA_BUILTIN_TYPES,缺跨语言条目;val m: Monoid[Int] → references Monoid)。extractVariableTypeAnnotation(:6074)需要 type_annotation 子节点 → 死;body-walker 的 variable_declarator 分支(:5230)——无此类型 → 死。**净效果:def 上的参数/返回/边界类型 + 标注 val/var 类型发出;class_parameters 类型(有体 class)、体内局部 val 类型(那里无 hook、无 variable_declarator)、模式类型(case ws: WidgetS)不发出任何东西。**class 主构造器类型是显著的覆盖空洞——保留它。

6.10 Docstrings:Scaladoc 被保留(src/extraction/tree-sitter-helpers.ts

Scala 注释节点类型:comment//)与 block_comment/* */ 与 Scaladoc /** */)。两者都在 getPrecedingDocstring 的接受集内(:110-115)——不同于 kotlin,Scaladoc 存活,且 block/行注释序列可按任意顺序链式拼接。cleanCommentMarkers(:77-90):/* 开头剥离 + 逐行 * 续行剥离 + // 剥离。钉死(extract-docs.txt):Scaladoc → "Scaladoc kept?\nsecond line with star"// 序列用 \n 连接;/** block */ + // trailing"block doc\ntrailing line"空行不打断链(两行之上的 // detached 仍会附着——previousNamedSibling 跳过空白)。hook 创建的 val 拿不到 docstring(docVal 已钉死),但 class/method/function/trait/enum/type_alias 可以。DOCSTRING_WRAPPER_TYPES 不含任何 scala 类型 → 不向上爬。CRLF:多行块注释在每个内部换行保留一个残留 \r"Scaladoc kept?\rsecond line with star"——钉死于 extract-docs-crlf.txt diff)——JS 的 \r^/m 匹配语义(#1329):调用 codegraph-kernel/src/docstring.rs 中的共享 js_multiline_strip,什么都不移植。行注释序列对 CRLF 干净(逐注释 trim 吃掉 \r)。

6.11 Value-reference edges(:398-931)——scala ∈ VALUE_REF_LANGS(:401)

移植全部机制(抄本 kotlin.rs):CODEGRAPH_VALUE_REFS=0 杀开关;MAX_VALUE_REF_NODES 20,000(当前 scala.rs 中的常量即 const MAX_VALUE_REF_NODES: usize = 20_000);isGeneratedFile 跳过。

  • 目标(captureValueRefScope:735):kind constant|variable,名字长 ≥3 且 /[A-Z_]/,parent id 前缀 ∈ {file:, class:, module:, struct:, enum:}。因为 scala 永不铸 namespace 节点,顶层 constant 就是目标(kotlin 的 namespace-drop 怪癖不适用——TOP_LIMIT 已钉死且有类方法读取者)。object/class/trait 成员(class: parent)是目标;count(无大写/下划线)不是(已钉死)。同名目标:最后一次注册赢得 map 槽——同文件有 Config.TIMEOUT_MSConfig2.TIMEOUT_MS 时,两者的读取者都得到指向 Config2 节点的边(fileScopeValues.set 覆盖——钉死 vref2.out;错目标怪癖,保留)。
  • 读取作用域:每个 function/method/constant/variable 节点(:764)——field 不是读取者。
  • 遮蔽修剪(:803-878):scala 声明器情形是 val_definition/var_definition(:838-843)——pattern 字段,仅当 pattern 是 identifier bump(元组/case-class 模式什么都不 bump → 解构的局部遮蔽永不修剪)。bump() 数 identifier 节点(:807)。任意位置的每个 val/var(目标自己的声明器 + 体内局部——hook 看不到体内局部但修剪 DFS 看得到)——钉死:伴生式 Config.RETRY_MAX + 方法局部 val RETRY_MAX = 9 → RETRY_MAX 被修剪(readBoth 只发 TIMEOUT_MS)。kotlin/swift 的 property_declaration 情形与 Dart/Pascal 情形在此是空路径。
  • 发出(:880-930):逐读取作用域,栈式 DFS(逆源码序弹出——ruby 先例;边序随之),读取节点类型 identifier(:907——simple_identifier/constant/name 永不出现在 scala 树中)。穿过 Config.TIMEOUT_MS 成员位置的读取计入(成员半边是 identifier)。字符串插值:$CONST${CONST} 都计入(interpolation > identifier / > block > identifier——钉死 readInterp ×2;kotlin 的 $X 失活怪癖不适用)。跳过自身/同名,逐 (scope,target) 去重 → EDGE {kind:'references', metadata:{valueRef:true}},在遍历之后追加(§八 发出顺序)。Dart/Pascal 的 next-sibling 上拉(:891)失效(scala 体是嵌套的)。

6.12 Function-as-value 捕获(#756)——SCALA_SPEC(src/extraction/function-ref.ts

idTypes = {identifier}(裸标识符就是候选——不同于 kotlin)。派发:arguments → args;assignment_expression → rhs 字段 rightval_definition → varinit 字段 value(var_definition 缺席——var cb = other 初始化器永不捕获,钉死 extract-fnref.txt)。解包:postfix_expression → null 字段 → 第一个 namedChild(eta 扩展 handler _handler,explicitRef=true)。无 layers/special/ungatedModes/addressOfOnly。NAME_STOPLIST 适用(thisnull、…)。

  • 捕获点:ladder :990(顶层/template 语句)、visitFunctionBody :5137(体节点)、scanFnRefSubtree(hook 消费的 val/enum-case/extension 子树——扫描在 depth 0 访问 val_definition 本身,所以类/object/顶层的 val x = fn 会被捕获;嵌套 def 停止检查 functionTypes=[] 所以只有 lambda_expression 能停住它——val fnField = (x) => runLam(x) 不捕获,已钉死)。
  • 钉死通道(extract-fnref.txtextract-fnref2.txtextract-edge2.txt):args 裸 id register(handler) ✓;args eta registerEta(other _) ✓;体 varinit val stored = alpha ✓;类作用域 varinit val topStored = handler ✓(从 CLASS 节点发出);assignment rhs obj.cb = beta ✓;具名参数 wire(cb = cbTarget)——解析为 arguments 内的 assignment_expression → rhs 捕获 ✓(在 rhs id 位置),且 cb = cb 时参数转发跳过(lhs 尾部 == rhs 文本,:430-443);hook 消费 val 内的 List(delta) ✓(经 arguments 的 args 模式)。
  • 不捕获:fn.member 值(field_expression——无 special)、中缀位置(list map transform——无派发键)、var 初始化器、hook 消费 val 下的 lambda 体。
  • Flush 门(:639-728):definedHere(同文件 function/method 名——顶层 def 是 kind function,所以 register(missing) 被丢弃但同文件名通过)∪ importedNames——对 scala 是仅第一个 path 段(§6.6;效果上:跨文件可调用物永远过不了门)。不产生任何 ::/this. 恒 flush 形式。${fromNodeId}|${name} 去重(钉死:同作用域三个 handler 捕获 → 一条 ref)。referenceKind function_ref(wire code 200)。

6.13 visitFunctionBody(:5129-5286)——scala 行为

  • 逐节点 maybeCaptureFnRefs(:5137)(体内的 assignment/varinit/args 捕获)。
  • call_expression → extractCall(:5143),随后递归子节点。
  • instance_expression → :5145 extractInstantiation;findAnonymousClassBody → null(template_body)→ 不返回 → 子节点被递归 → 匿名体 defs 不在此派发(functionTypes 为空,此 walker 不检查 methodTypes)→ 体内 new Ordering[Int] { def compare … }:只有 instantiates ref;compare 什么都不铸、其体内的调用归属外层 method(已钉死)。
  • extractBareCall——缺失。
  • 嵌套具名 def 什么都不铸(:5245 检查 functionTypes——为空):体内的 def innerFn(k) = … 被递归穿过;其调用归属外层 method(钉死:innerFn/lam/helperCall 全部来自 CallSites::localHost)。这与 kotlin 相反(kotlin 铸局部 function)——保留。
  • classTypes(:5255):体局部 class LocalClass/object LocalObj/trait → 完整 extractClass(kind class/trait)包含于外层 method,成员正常提取(钉死 QN CallSites::localHost::LocalClass::lm)。enumTypes(:5268)同理。typeAliasTypes 不检查 → 体局部 type X = … 不可见。
  • 每节点 extractStaticMemberRef(:5218)(§6.8)。variable_declarator 分支(:5230)死。体局部 val/var:此处无 hook → 普通递归 → 初始化器的调用/实例化从外层 function 发出val local = compute() → calls compute——已钉死),不铸局部节点。Match/for/try/if 透明递归(case_clause 模式 Some(n) 不发出;for … yield combine(x,y) 发出调用——已钉死)。

6.14 共享路径杂项

  • 位置:line = startRow+1,column = startColumn——UTF-16 code unit(钉死 extract-uni.txt😀 计 2,é 计 1),startIndex/endIndex 子串(getNodeText)与 import-signature trim 同理。内核侧对应 codegraph-kernel/src/textutil.rscol16/slice_utf16
  • Refs 不携带 filePath/language(§四-5)。
  • extract() 包裹(:454-577):file 节点 →(无 namespace)→ 遍历 → flushFnRefCandidatesflushValueRefs → 弹栈。表序:节点按创建顺序;contains 边交错;value-ref 边最后追加(已钉死——所有 contains 之后);遍历序 refs,然后 flush 时的 function_ref refs(每个 dump 中钉死在最后)。存储/harness 对 rowid 顺序敏感——精确复
登录后查看全文
热门项目推荐
相关项目推荐