Codegraph Scala 内核移植清单:从 tree-sitter wasm 到 Rust 原生内核的 bug-for-bug 迁移全解
本文围绕 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.md、swift-kernel-port-checklist.md)的共同方法论:
- 每一条语法形态断言都对照生产 vendored wasm 实测(
dist/extraction/wasm/tree-sitter-scala.wasm,sha2567945b13e…,与 src/extraction/wasm/tree-sitter-scala.wasm 字节一致),而不是从代码阅读推导; - 每一条提取行为断言都用真实
dist/提取器钉死(extract-*.txtground-truth dump); - childForFieldName 真值表直接探针获得(
brace-field.out、cst-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 清单为格式先例。
三大阻塞性发现(无阻塞项,但有三个需要睁眼看的事项):
- wasm 已经 vendored,不需要 bump:
VENDORED_WASM_LANGS自 2026-05-07(#91)起就包含scala,vendored wasm 与 tree-sitter/tree-sitter-scala master@0aca5d0a6f逐表一致(两次验证:batch-4 位置表对比 + 本次调研 clone-sha 匹配)。因此移植只是 vendored-grammar-C 路线(kotlin 机制),没有行为差量门控要跑。 - 错误发生率呈双峰分布:主流 Scala-2 风格仓库很干净(os-lib 0.00%、cats 1.80%),但前沿 Scala-3 代码不干净(scala3 compiler/src 9.88%,library/src 17.79%——capture-checking
^类型所致),且出错文件中约 40–60% 是 PHANTOM(hasError=true但零 ERROR/missing 节点——要相信 flag)。 - 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.ts(WASM_GRAMMAR_FILES与VENDORED_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.csha256 等于 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.rs:cc::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 match、val end = 1都 ERROR)。见 catsAndThen.scala。 - (c) 泛型 + curried 超构造器参数:
extends Eq[A]()(ev)ERROR(普通Base(1)(2)解析干净)。见 cats 的 kernel 实例。 - (d) Unicode 符号类型名:
type ⊥ = Nothing(catspackage.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 条)
- 无 preParse、无 POST_PASSES。src/extraction/languages/scala.ts 全文件没有
preParse→ src/extraction/kernel/index.ts 的 preParse hoist 是 no-op;没有POST_PASSES条目 →tryKernelExtractRaw保持可用。 - 一个框架 resolver 可能把 scala 逼上 DECODED 路径:
playResolver(src/resolution/frameworks/play.ts:languages: ['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,不会产生错误输出。 - 单一 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 永远不被索引)。 .sc文件是普通 scala 文件,其顶层语句把调用归属到 FILE 节点(钉死的extract-script.txt:calls println/runTop from=file,顶层val→ constant 且 file 为 parent)。顶层语句出现在.scala中同理(语法接受)。- 不需要 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 通道只有decoratesREFS。 - 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_definition、object_definition、trait_definition |
— |
methodTypes |
function_definition、function_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 为 0;for 头把 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)→v;val 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 → kindfield;object_definition 或什么都没找到(顶层、package_object、带花括号的 package)→val→constant/var→variable。注意与 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 节点出发的referencesref,位置在 type_identifier 处。钉死:val SHARED_TABLE: Map[String, Int]→ 只 referenceMap(String/Int 被内置集跳过);var cb: () => Unit→ 无输出。- 返回 true → dispatcher 跑
scanFnRefSubtree(node, 0)且永不下降 → 顶层/类作用域的属性初始化器不产出任何 calls/instantiates ref(val 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.txt、brace-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.txtext2)。 - 无论 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]: B;def genericLeakT: T→[T]: T。 - 无参数、只有返回 →
: Int(RichIntS::twice);空括号 →();次构造器def this()→()。
- Curried def 只取第一个参数列表:
- getReturnType = extractScalaReturnType(scala.ts:56-67):
return_type字段文本 trim 后:this.前缀(fluentthis.type)→ undefined;replace(/\[[^\]]*\]/g, '')剥离泛型参数(**非贪婪单趟:List[Bar]→List);剥离所有\s;取最后一个.段;必须匹配/^[A-Za-z_]\w*$/。钉死:: WidgetS→WidgetS;: com.example.other.Remote→Remote;: T→T(泛型泄漏,保留);推断 → undefined;Unit/Nothing不过滤(与 kotlin 不同——unitRet有 ret="Unit",已钉死)。此值喂给 matchDottedCallChain(§七)。 - getVisibility → extractVisibility(scala.ts:69-80):扫描类型
modifiers或access_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 → kindclass(含伴生对象/case object),trait → kindtrait(经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/refcom(extract-torture.txt钉死 ×5;import single→single;import a.b→a)。identifier/stable_identifier 回退(:204-209)是死的(path 永远存在)。对 fn-ref 门的后果:importedNames = {com,single, …}——导入的类简单名永远进不了门(不同于 kotlin 的"最后一段"规则——flushFnRefCandidates 的 QUALIFIED_IMPORT 永远看不到完整路径,因为 ref name 只有第一段)。
5.5 不存在的 hook(walker 不得发明)
preParse、resolveName、recoverMangledName、isMisparsedFunction、isConst、isExported(除 file 节点的字面 false 外处处 undefined)、classifyMethodNode、extractPropertyName、propertyTypes、packageTypes/extractPackage(→ extractFilePackage 返回 null → 永不产生 namespace 节点——package 头被忽略,每个顶层 QN 都是裸名,已钉死)、getReceiverType(无 receiver-QN 表面,无 owner-contains 回退)、resolveBody(body 处处经 body 字段)、extractModifiers(无 node.decorators)、extractBareCall、synthesizeMembers、skipBodilessClass(无体的 class Foo 仍铸节点——tree-sitter.ts:1685 的注释点名 Scala)、methodsAreTopLevel、resolveTypeAliasKind、interfaceTypes 机制。
六、主遍历行为:逐节点对照(锚点以 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 → function。function_declaration = 无体 def(trait/抽象类中的 def m(): Int)——同一路由、无体遍历 |
class_definition |
classTypes:1005 → classify | 'class' → extractClass:1679。含 case class、implicit class、abstract class |
object_definition |
classTypes:1005 | extractClass → kind class(伴生对象、case object、object 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 transform、a foo b、1 :: rest、x + y、counter += 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 是两个同名classkind 节点(不同起始行 → 不同 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;钉死 methodWidgetS::+)。反引号名字保留反引号(class `Weird Name`/method `strange def`已钉死)。次构造器是名为this的 method(def this() = this(0)→ methodWidgetS::this+ calls refthis——已钉死;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);次构造器 → methodthis。 - Trait:extractClass 且 kind trait——trait 的 visibility 确实发出(extractClass 路径而非 extractInterface——vis="public" 已钉死),不同于 kotlin 的 interface 路径。Trait 的 vals → hook 'instance' → field(
Drawable::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→ 三个pathidentifier → namea。- 选择器
{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 列表中):
- property = getChildByField('property') → null;getChildByField('field') → 成员 identifier(scala field_expression 字段:
value+field)。kotlin 的 navigation_suffix 回退是死的。 - receiver = object/operand/argument 字段 → 全 null →
func.namedChild(0)=value子节点。 - LITERAL_RECEIVER_TYPES(:4397,集合在 :373-388):scala 命中
string("lit".toUpperCase())与integer_literal(5.toString())→ 什么都不产出(已钉死)。移植整个集合。 - receiver
identifier(:4401)且不在 SKIP_RECEIVERS {self,this,cls,super} →`${recv}.${method}`(w.render、Registry.register、obj.method)。this/superreceiver 是带这些文本的普通identifier节点 → SKIP → 裸 methodName(this.mine()→mine,super.hashCode()→hashCode——已钉死;不同于 kotlin 的 this_expression 路径,净效果相同)。 - 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。 - receiver
field_expression(两跳a.b.method3())或其他 → 裸 methodName(已钉死)。
Else 分支(:4518-4520)——calleeName = func 原始文本:裸 helper;apply 语法糖 WidgetS(1) → calls ref WidgetS(大写裸名——resolution 的 CONSTRUCTS_VIA_BARE_CALL 处理它,§七);次构造器内的 this(0) → calls this。怪癖需保留(已钉死):
generic_functioncallee 保留类型参数:genericCallInt→ callsgenericCall[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()} …"→ 在内层调用位置 callsw.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处)的instantiatesref。发出位置:表达式体(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 的compare→function compare(裸 QN);object 内 given 的compare/innerVal→ 该 object 的 method/field(Registry::compare,extract-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 位置的referencesref。钉死(extract-torture.txtStaticReads):val c1 = Registry.count→ referencesRegistry;Http.Ok→ referencesHttp;process(Registry.count)(参数位置)→ referencesRegistry(外加process调用)。com.example.Fq.CONST_READ→ 无输出(外层 recv 是 field_expression;最内层com小写)。赋值写入会发出(不同于 kotlin):Registry.count = 5——LHS field_expression 是assignment_expression的普通子节点,被 body 递归访问 → referencesRegistry(已钉死)。顶层/类作用域的读取不发(仅 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_identifiername 子节点(:4951)→ 名字文本 →<剥离 + 最后./::段规范化(:4959-4962)→decoratesref {from: 被装饰节点,name,ANNOTATION 节点的行/列}。钉死:@main def entry→ decoratesmain;@inline def fast→inline;@deprecated("gone", "1.0") def old→ decoratesdeprecated(带参数的注解仍发出——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 映射 → 每个超类型一条extendsref {name,超类型节点的行/列}。钉死:extends BaseW(size) with Drawable with Ordered[WidgetS]→ extendsBaseW+Drawable+Ordered(泛型解包;arguments子节点经默认分支得 null → 跳过)。scala-3 逗号形式extends B, C→ 两者。object 上的单个extends Shape(object Circle extends Shape)✓(已钉死)。curried 超构造器参数extends Base(1)(2)(普通形式、解析干净)→ 只 extendsBase(第二个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):
- scala 专属参数遍历(:5839-5842):每个直接的
parameters子节点(所有 curried 列表——含尾随 implicit 列表)→ extractTypeRefsFromSubtree(:6090):除 BUILTIN_TYPES(:5768-5782——含 scala 块 Int/Long/…/Null 加跨语言条目;注意小写error等不可能出现)外每个type_identifier叶子 → 叶子处的referencesref。type_parameters 节点也携带字段名parameters但不是类型parameters→ 不被此遍历匹配(由遍历 3 匹配)。 - 返回遍历(:5851):
getChildByField('return_type')子树 → refs(泛型返回Option[A]→Option+A)。 - scala 专属类型参数遍历(:5863-5870):第一个
type_parameters子节点 → 子树 refs——上下文/上界会发出([A: Numeric, B <: BoundT]→Numeric+BoundT;声明名 A/B 是identifier节点 → 静默)。genericDef钉死顺序:参数遍历 refs(A)→ 返回(B)→ 边界(Numeric、BoundT)。 type_annotation直接子节点搜索(:5873)——scala 无此节点类型 → 死。
- 加上 hook 的 emitScalaTypeRefs 作用于 val/var 类型标注(§5.3——自己的内置集 SCALA_BUILTIN_TYPES,缺跨语言条目;
val m: Monoid[Int]→ referencesMonoid)。extractVariableTypeAnnotation(:6074)需要type_annotation子节点 → 死;body-walker 的 variable_declarator 分支(:5230)——无此类型 → 死。**净效果:def 上的参数/返回/边界类型 + 标注 val/var 类型发出;class_parameters 类型(有体 class)、体内局部 val 类型(那里无 hook、无 variable_declarator)、模式类型(case ws: WidgetS)不发出任何东西。**class 主构造器类型是显著的覆盖空洞——保留它。
- scala 专属参数遍历(:5839-5842):每个直接的
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_MS与Config2.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 字段 right;val_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 适用(this、null、…)。
- 捕获点: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.txt、extract-fnref2.txt、extract-edge2.txt):args 裸 idregister(handler)✓;args etaregisterEta(other _)✓;体 varinitval stored = alpha✓;类作用域 varinitval topStored = handler✓(从 CLASS 节点发出);assignment rhsobj.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)。referenceKindfunction_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,成员正常提取(钉死 QNCallSites::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.rs 的col16/slice_utf16。 - Refs 不携带 filePath/language(§四-5)。
extract()包裹(:454-577):file 节点 →(无 namespace)→ 遍历 →flushFnRefCandidates→flushValueRefs→ 弹栈。表序:节点按创建顺序;contains 边交错;value-ref 边最后追加(已钉死——所有 contains 之后);遍历序 refs,然后 flush 时的 function_ref refs(每个 dump 中钉死在最后)。存储/harness 对 rowid 顺序敏感——精确复
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