Repository navigation
language: cast, Self, receiver rules, AST spans, and line-table removal - #222
Merged
Merged
Conversation
res_imports now builds a graph of SourceManager files instead of concatenating imports into <combined>. Each file keeps its own token stream and line base, diagnostics resolve to the real path and local line, and the CIR cache hashes every graph file. Manifests contribute import edges only; paths and manifest ids stay out of the intern table so .ccr bytes do not depend on the build directory. Canary re-locked: the ELF is unchanged; the four .ccr values are updated with attribution and old-value records.
PINNED_REV moves from 25e7019 (local-only v4 T6 commit) to 7eb9669, the v4 migration squash merge already reachable from develop. The three rebuilt binary hashes are re-locked from the new revision. parity_run.sh CORPUS_TOTAL follows the current corpus: 34 suite files, 2 compiler entries, 26 backend/kernel units, 19 stdlib files, and 4 examples, for 85 total. The pinned baseline runs the 85-file check with rc distribution 49x0 / 36x1.
The static runtime is now a SourceManager file parsed before main instead of text prepended to the main source. It has no fileid and does not shift user line numbers, so static diagnostics stay on the user file's local line. LSP diagnostic ranges now resolve the owning file for both span and line-only diagnostics. Imported function and struct/enum definitions resolve through the import graph; the main token stream is restored after a cross-file scan. The lexer/diagnostic/S1P paths no longer fall back to scanning the main source text. AST nodes still store line/column and recover the file through the SourceManager line table; that remains the documented boundary. Static canary values are re-locked after the prelude change. The ELF is unchanged; gt_static_ccr loses one TYPE term because compiler-global intern ids shifted, with function and instruction counts unchanged.
The grammar and developer docs use `enum Mode:\n | Server\n | Client`, and the Python bootstrap parser already accepts that surface. The self-hosted parser consumed the pipe as a variant name. Accept the optional T_PIPE before each variant; bare layout variants remain accepted for existing corpora. Add enum-impl coverage for pipe variants, enum interface implementations, generic enum methods, and the bare-layout compatibility path.
The import-graph branch and develop's library work both changed the compiler, so the frozen canary values and the parity corpus count have to be re-measured on the merged tree. Canary (D1 5/5, each artifact built twice byte-identical): canary_elf unchanged 4e79ced2... 49334 pa_ccr unchanged 271d0c1b... 97509 pa_static_ccr sha changed, size unchanged 97731 gt_ccr unchanged 96cc9e75... 131023 gt_static_ccr 131285 -> 131245 (-40) Only the `--static` artifacts move: the prelude now lives in its own SourceManager file instead of being concatenated into the main source, so line coordinates shift. The gt_static -40B is one deduplicated TYPE entry, the same mechanism recorded for the 2026-10-25 batch. A control build at the commit before the enum-pipe change reproduces both values byte for byte, so the enum `|` change contributes nothing to the canary. Parity: t4 grew from 19 to 23 files (bits.cr, hash.cr, hashmap.cr, rawbuf.cr), so CORPUS_TOTAL goes 85 -> 89. Measured 89 files, rc 54x0 / 35x1. Per tier the failure counts stay t1 4 / t2 1 / t3 20 / t5 2; t4 drops from 9 to 8 because one stdlib unit that used to fail now passes.
`as` never converted anything. The checker accepted every type pair and the
generator emitted one IR_LOAD, so `1.5 as int` returned 96 (1500000 mod 256).
`cast<T>(expr)` was a V3 leftover that produced the same node and violated the
V4 ban on `<T>`.
Syntax:
- `expr cast Type`, same precedence position as `as`.
- `as` leaves the keyword table; `cast` enters it. The count stays 39.
- `cast<T>(expr)` is deleted.
- `CastExpr` added to grammar/core.ebnf between Unary and CallOrField.
Semantics (whitelist, anything else is error[TA09]):
- T -> T identity, no instruction.
- int -> dex: exact form, scaled by 10^6.
- dex -> int: divide out the scale, truncate toward zero. `1.5 cast int`
is now 1. `-1.5 cast int` is -1.
- char <-> int: kept temporarily; the value is the intern index, not a code
point. match literals depend on this.
- pointer family (int/string/*T -> *T, *T -> *U, &T -> *T): unchanged
semantics. The cast is not the safety boundary; the external dereference
gate (TU03) still requires `unsafe`.
- `*T cast int` is deliberately not offered. Measured: a full round trip
returns 2n, because the tagged-int representation and the raw pointer word
do not line up. It has no consumers; the open question is recorded in the
plan.
Tests: new tests/selfhost/test_cast.py (12 cases) covers the spellings, the
whitelist, truncation in both directions, the char round trip, pointer casts,
and the two removed forms. Existing corpus and probes move from `as` to
`cast`; the iface-ops pin that expected string->int to be accepted now asserts
TA09 instead.
Canary is unchanged (5/5, no re-lock). check src/compiler passes. The
selfhost suites touched by the change pass: cast 12/12, pointer-safety 16,
diag-gate 28/28, hit-table 24/24, iface-ops 73/73, multibranch 11/11,
import-graph 7/7, lib-manager, enum-impl 4/4, impl 8/8, pub 3x.
Also replace the `loop` + index + break idiom with `while` in the functions
this batch touched (build_line_fileid, global_seg_index).
…d calls drop the receiver Four separate defects made methods and associated functions on structs either unusable or silently wrong. 1. The parser discarded the type annotation of every `self` parameter (all three receiver branches called parse_type() and threw the node away), and it took the `first_param` index before parsing the type, so the recorded first parameter pointed at a type node. Both are fixed: the annotation is stored, and `pf` is taken right before the parameter node is allocated. 2. The checker derived a receiver's type only from the mangled "Type.method" name. A free function `fn get(self: P)` therefore had an untyped receiver, and a method on a generic struct got the bare named type, so `self.field` could not substitute an inferred (`.`) field. The annotation now wins, and the field path keeps the generic-apply row across the `&self` auto-deref before unwrapping it for the struct lookup. 3. Generic inference ignored method receivers. `infer_gen_call` maps parameters to arguments 1:1, but a method's argument list excludes the receiver, so a method on a generic struct had no binding and its instance fell back to unit. `infer_gen_call_recv` binds the receiver against the first parameter and startsthe argument walk at the second. 4. The generator prepended a receiver for every `Type.fn(...)` call, including associated functions that have no `self` parameter. Every argument shifted by one slot and the real first argument was dropped: `Point.make(9)` returned a zeroed Point. It now checks the callee's first parameter. Method calls are also specialized now. A method on a generic struct inherits the struct's parameters, so the template has no emitted body; without redirecting the call to the monomorphized instance the call targeted a missing symbol and the program segfaulted. Tests: tests/selfhost/test_struct_impl.py (7 cases) covers inferred fields read through a method and through an interface impl, associated functions returning a struct, methods returning a struct, `self: T` on a free function, and `&mut self`. check src/compiler passes; impl 8/8, enum-impl 4/4, cast 12/12, field-inference 5/5, generic-enum 2/2, iface-ops 73/73, hit-table 24/24, diag-gate 28/28, pointer-safety, import-graph 7/7, lib-manager, pub-visibility all pass. Canary 5/5 unchanged. Known gaps, not addressed here: a `&self` / `&mut self` receiver on a struct with an inferred field still fails unless the receiver is annotated (`fn f(&self: Box)`), and `Self` as a type name is not resolved.
`self: T` was never part of the surface. It existed because the parser discarded every receiver annotation and the checker reconstructed the type from the mangled "Type.method" name, so the annotation was invisible either way. The receiver type now comes from the enclosing `impl Type:` only: - `self` by value - `&self` read-only reference - `&mut self` writable reference The parser rejects `self: T` with a located error, and rejects `self` outside an impl. Inside an impl it records the receiver type from the impl header, so a `&self` method on a struct with an inferred (`.`) field resolves the field the same way a by-value receiver does. Tests and docs drop the annotated form: all receiver parameters in the selfhost suites, the bootstrap pipeline fixture, and `tests/suite/apx_conversion_test.cr` now read `self` / `&self` / `&mut self`. Two negative cases pin the removal: `self: P` and a free function taking `self`. `Box` is gone from the current language docs. It was never a language type; it is only a user-chosen struct name that appears in `tests/suite/generics_test.cr` and in the V3 generics proposal, which now says it is retired. check src/compiler passes. impl 8/8, enum-impl 4/4, struct-impl 9/9, field-inference 5/5, ambiguous-constraint 2/2, auto-constraint 3/3, apx-conversion 23 cases, cast 12/12, iface-ops 73/73, hit-table 24/24, diag-gate 28/28, pointer-safety, import-graph 7/7, lib-manager, pub-visibility, s1p-liveness, arg-inference 18 cases, ccr-types 53/53, bootstrap-pipeline 34/34. Canary 5/5 unchanged.
… real error `Self` was an unknown name. It now resolves to the enclosing impl type, both in return position (`-> Self`) and in parameter position (`other: Self`). The parser rewrites the type node while parsing the method, so declaration collection never sees an unresolved `Self`; the checker keeps a fallback that reports a located error when `Self` appears outside an impl. `enum Shape: | Circle(r: int)` used to be read as a payload of type `r`, which surfaced as "Undefined type 'r'". Payload fields are positional in this language; the parser now reports "named enum payload fields are not supported; write the payload type positionally" at the offending name. The syntax doc says the same and shows the positional form. Tests: struct-impl 12/12 (adds `Self` in return and parameter position, `Self` outside an impl, and keeps the earlier receiver cases), enum-impl 5/5 (adds the named-payload rejection). check src/compiler passes; impl 8/8, field-inference 5/5, ambiguous-constraint 2/2, auto-constraint 3/3, cast 12/12, iface-ops 73/73, diag-gate 28/28, import-graph 7/7, lib-manager, ccr-types 53/53. Canary 5/5 unchanged.
…ence
A struct whose field type is `.` is a family, not a single type: the
construction site fixes the instantiation. Naming the bare struct in `-> T`
therefore cannot match, and the caller would receive the template (its field
type is still the placeholder). The form that works is to omit the return type
and let the body infer it.
`fn make(n: int):` with `return Holder { v = n }` now checks, builds, and runs.
Associated functions follow the same rule.
The diagnostic for the annotated form says so instead of the generic mismatch:
"Returned an instantiation of this struct, but the declared return type names
the family; omit the return type to infer it from the body". The generic TF01
message now also prints both types, which is what identified the mismatch
(`Holder[int]` vs `Holder`).
Docs: section 9.1 shows the working form and states the rule. Tests: struct-impl
14/14 adds the inferred-return associated function and pins the diagnostic.
check src/compiler passes; impl 8/8, enum-impl 5/5, field-inference 5/5, cast
12/12, iface-ops 73/73, diag-gate 28/28, hit-table 24/24, pointer-safety,
import-graph 7/7, ccr-types 53/53, auto-constraint 3/3. Canary 5/5 unchanged.
Groundwork for the AST span migration. Each registered file now records the global byte offset of its first byte, and three readers map a global offset back to a file, a line, and a column by binary search over the file order. Token `lo` / `hi` become global byte offsets (file byte_base + local offset). Nothing reads them today, so this step is behavior-neutral; the AST layout is the first consumer. The plan is in docs/superpowers/plans/2026-10-27-ast-span.md: 8-byte `lo`/`hi` replace the 16-byte line/column pair, taking the node from 72 to 64 bytes, with `ast_line` / `ast_col` kept as wrappers so call sites do not change. check src/compiler passes and canary is 5/5 unchanged, which is the expected result for an additive step.
Follows docs/doc-style.md: no metaphors, no in-group shorthand, no arrows for reasoning. - 门禁 -> 前提 / 要求 unsafe - 落地 -> 实施 - 消费方对齐 -> 更新使用位置信息的模块 - 加字节轴 -> 增加字节偏移 - 收尾 -> 剩余工作 - 换代纪律 -> 按 REBUILD.md 的规则记录旧值和原因 - 未对齐的转换 -> 转换不正确 - 闸门 -> 检查(canary note, checker comment, test name) The pointer rows in the cast plan said `unsafe` was required for the cast. That was true of an earlier draft, not of the implementation: the cast may appear anywhere and only the external dereference requires `unsafe`. The rows now say "任意位置,解引用时检查", matching docs/developer/syntax.md and the test that builds a pointer cast outside `unsafe`. Text only. No behavior change: cast suite 12/12.
Scope: every .cr file this branch touches. Four of them carried the terms that docs/doc-style.md rules out; 41 comment fragments across 34 lines are rewritten. No code is changed. - 判据 -> 判断依据 / 条件 / 判断 - 口径 -> 规则 - 收口 -> 统一处理 - 落点 -> 位置 - 腿 -> 路径 / 用例 - 对齐 -> 对应 / 处理 - 机械判据 -> 自动检查 Affected: src/compiler/checker.cr, src/compiler/ir_gen.cr, tests/suite/apx_conversion_test.cr, src/compiler/globals.cr. check src/compiler passes and canary is 5/5 unchanged, which is the expected result for comment-only edits.
Second pass after the touched-file cleanup: the whole repository now has zero occurrences of the metaphor and in-group set in .cr files (闸门, 腿, 牙, 咬, 钉, 判据, 落点, 口径, 收口, 归口, 收编, 门禁, 骨架, 载体). 40 fragments across 12 files: regalloc.cr, cir_cache.cr, ty_shadow.cr, type_selftest.cr, and the suite and fixture programs under tests/. Comments only; no code and no test expectations change. - 判据 -> 判断依据 / 判断 / 检查项 - 口径 -> 规则 / 计算方式 - 收口 -> 已处理 / 处理 - 落点 -> 位置 - 腿 -> 路径 / 用例 - 骨架 -> 结构 - 载体 -> 包含 / 改为 - 钉住/钉死 -> 固定 build passes, check src/compiler passes, apx-conversion, view-builtins, opt-dex and cast suites pass, canary 5/5 unchanged. Remaining outside .cr, not changed: process records under docs/superpowers (plans, implementation), TODO.md, tools/ scripts, src/ci/run.sh and the Python test comments, about 1900 fragments in total.
Replaces metaphors and shorthand with plain wording in the CI dispatcher comments. Comments only; `bash -n` passes. Done in this pass: 铁律 -> 硬性规则, 判据/口径/收口/腿/牙/钉/闸门/载体 and the 批 suffix -> 阶段 or 本次, plus the long block comments above the bootstrap and selfhost hooks. Not finished: 75 occurrences of the same set and 57 occurrences of 批 remain in this file. The rest is being done file by file, not by a global replacement.
Applies docs/doc-style.md to every tracked file except docs/superpowers/plans, which is out of scope by request. Replaced uniformly: 铁律 -> 硬性规则 闸门 -> 检查 门禁 -> 前提 判据 -> 判定依据 口径 -> 规则 收口 -> 处理 落点 -> 位置 腿 -> 路径 牙齿 -> 有效性用例 咬 -> 检出 钉 -> 固定 骨架 -> 结构 载体 -> 文件 收编 -> 纳入 归口 -> 归属 批 -> 阶段(本批 -> 本次,一批 -> 一组,批次 -> 阶段) 批准, 批注, 批量, 批复 and 批处理 are unchanged: they are ordinary words, not the work-batch shorthand. Scope: 191 files in the first pass, 77 in the second (Python docstrings and text files), plus the remaining occurrences in shell scripts, fixtures and the CI workflow. Comments and prose only; no code, no test expectation and no data row changes. Verification: build passes, check src/compiler passes, bash -n passes on the edited shell scripts, canary 5/5 unchanged. doc-protected-surface, canary carrier, ci-hook coverage, TODO-id migration, cast, struct-impl and import-graph suites pass. Remaining: docs/superpowers/plans, 258 occurrences, excluded by request.
Symbols, per docs/doc-style.md (no emoji, no warning marks, no check marks, no 【】 labels). 363 removed across 44 files, including three files under docs/superpowers/plans: ⚠ -> 警告: ✓ -> 通过 ✗ -> 失败 ✅ -> 通过 ❌ -> 失败 ⛔ -> 禁止 ★ -> (需裁定) ⬜ removed 【】 -> 「」 Literal translations, outside docs/superpowers/plans: - 基于 -> 使用 / 根据 / 在 ...(19 occurrences) - 使得 -> 使 - 作为一个 -> 合并成一条 / 作为单独的一遍 - 对于 ... -> 对 ... - 进行函数级增量编译 -> 做函数级增量编译 - 进行指针算术 -> 做指针算术 - 在最终目标编码之前进行 -> 在最终目标编码之前完成 Text only. build passes, check src/compiler passes, canary 5/5 unchanged, doc-protected-surface and canary-carrier pass. Remaining in docs/superpowers/plans by request: terminology and the two remaining 基于 sentences were not rewritten there.
The layout switch built and gave correct positions for a single file, but checking the compiler produced 4414 diagnostics: AST `line` is also the global flat line index used by diag_fileid_for_line, g_line_lib, lib_of_node and the .ccr line fields. Returning a file-local line made file and library ownership resolve against the wrong line space, so symbols looked undefined. The change was reverted; the tree builds and check src/compiler passes. Revised order: keep line and span side by side (72 -> 80 bytes), migrate every consumer to span lookup, then drop line and column (-> 64 bytes).
The node position pair (line, col) is replaced by one packed 64-bit span field: `lo + hi * 2^32`, holding the start and end global byte offsets. Token offsets already carry the file byte_base, and the +1 shift keeps 0 meaning "no position". `ast_line` keeps its historical meaning: the global flat line index that diagnostics, g_line_lib, library ownership and the .ccr line fields index by. It is now decoded from the span through SourceManager; `ast_col` reads the column. `ast_lo` / `ast_hi` expose the span itself. Two latent defects surfaced once ownership resolved correctly, and both are fixed here: 1. def_type_sym built a synthetic declaration node with position 0 and derived the owning library from it, so every type symbol had owner -1. Owner -1 makes sym_visible return true, which exposed non-pub library types: `x : Argv4` became a type mismatch instead of "undefined type". The owner now comes from the declaration line directly. 2. find_struct_by_name looked the parser's source name up as if it were the library-qualified unique name. With correct owners, a user struct shadowing a library type resolved to the library's entry. It now resolves (source name, current library) first. Method and associated-function calls needed their function node to carry a byte offset rather than a line; `parse_body` receives token spans now. Performance: sm_span_file keeps the last hit, and lib_of_node resolves the file from the span instead of going through the line table. `check src/compiler`, cold cache, 44.9 s against 52.7 s for the previous commit. Verification: check src/compiler passes; pub-visibility, pub-label, lib-manager, cast 12/12, struct-impl 14/14, import-graph 7/7, diag-gate 28/28, impl 8/8, enum-impl 5/5, iface-ops 73/73, hit-table 24/24, ccr-types 53/53, apx-conversion, s1p-liveness, field-inference 5/5. Canary 5/5 unchanged.
Second attempt succeeded by keeping ast_line's historical meaning (global flat line index) and decoding it from the span. Node layout is now 64 bytes. Records the two latent defects fixed on the way: type symbols always had owner -1 (making non-pub library types visible), and find_struct_by_name looked up the source name as a unique name. Cold `check src/compiler`: 44.9 s against 52.7 s before.
`g_line_fileid` and `g_line_lib` duplicated information the SourceManager already
holds: a global line belongs to the file whose [line_base, line_base + count)
range contains it, and that file owns the library id.
Replaced:
- diag_fileid_for_line -> fileid_of_line (sm_for_line, then the g_files row
with that SourceManager id)
- lib_of_line -> sm_file_lib(sm_for_line(line))
- ir_gen.global_seg_index -> sm_span_file(ast_lo - 1)
- lsp analysis line-to-path and line offset -> fileid_of_line / sm_for_line
Deleted: build_line_fileid, grow_line_file, g_line_fileid, g_line_lib,
g_line_cap. `g_line_count` is now set once from g_sm_line_count.
The .ccr line fields are unchanged: they store ast_line, which still returns the
global flat line index.
check src/compiler passes; diag-gate 28/28, import-graph 7/7, pub-visibility,
cast 12/12, struct-impl 14/14, lib-manager, lsp-visibility, impl 8/8, enum-impl
5/5, iface-ops 73/73, hit-table 24/24, pointer-safety, ccr-types 53/53,
apx-conversion, field-inference 5/5, s1p-liveness, pub-label. Canary 5/5
unchanged. Cold `check src/compiler`: 44.2 s.
Line-to-file and line-to-library now come from the SourceManager line ranges. Lists the four migrated consumers and the deleted globals.
Diagnostics already stored start and end byte offsets. The JSON emitter ignored the end offset and always produced a one-character range. It now maps the last covered byte (hi - 1) back to a line and column, so a token-level diagnostic underlines the token instead of its first character. Falls back to the old single-character range when the diagnostic has no span or the span is empty. check src/compiler passes; lsp-visibility and import-graph pass. test_lsp still shows the same six pre-existing failures as develop, with identical values. Canary 5/5 unchanged.
The associated-function guard added earlier treated any call whose target was not a declared function as "no receiver". A dynamic dispatch call carries the method name rather than a function link, so `d.bump()` on a `dyn` value lost its receiver and lowered to a static IR_CALL instead of IR_DYN_DISPATCH. Unknown targets now default to "has receiver". Declared functions still decide by their first parameter, so associated functions keep dropping it. Fixes test_v7_edge_semantics_effect_call_in_chain (27/27 in test_ccr_v7). check src/compiler passes; cast 12/12, struct-impl 14/14, pub-visibility, iface-ops 73/73, hit-table 24/24, import-graph 7/7, diag-gate 28/28, impl 8/8, enum-impl 5/5, pointer-safety, ccr-types 53/53, apx-conversion, lib-manager. Canary 5/5 unchanged.
tests/probes/warm/tk01_width.cr still used `as *[int; 2]`, which the language no longer accepts. It failed with N01 + TB01 instead of the expected TK01, so the warm-cache gate reported a cold/warm mismatch that was really a parse failure. The probe now uses `cast`. test_warm_cache_gate 10/10.
dslsdzc
added a commit
that referenced
this pull request
Oct 28, 2026
PINNED_REV 7eb9669 -> 28483a4 (the squash commit of #222). The old revision predates develop's library visibility batch, so the pinned compiler could not compile the current sources and the fixed-baseline comparison leg was unusable. Old generation (7eb9669): corec 4234b7a6254ad3b7a890268c462dbe3dbd269a991f0deaa12832e154dbe26265 corearch 3786eb4a8a500158d22a0193b9174c2f4228b5c8d379a4d992ef920b28a9c421 corelsp 845ac0485f2fede878f18bdbacb49e9c24b62ddf4c3180e2b5d411e60467c0eb New generation (28483a4): corec 5fe3de891380c5909e6f5cefccac53781acbe1eccf4540f568988290467a6726 corearch e8b3e30b116c09200dfb9b2d023b0c1608f4f917e89b3f7dabf74ee6e295c542 corelsp db51fdc7c420396152f363385174a96fed770af951339482cbb42376b62908e2 rebuild.sh now rebuilds from 28483a4 and the three shas match the whitelist; the smoke program returns 42. REBUILD.md records the new current generation and keeps the old row in the history table.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
内容
这条分支把四块工作合在一起,都在 AST 与语言面,互相有依赖。
1. 导入图(保留自 feature/import-graph)
每个文件独立 SourceManager 身份、独立 tokenize、独立行号;诊断按真实文件和文件内行号;LSP 定义可跨文件跳转;静态构建的
rt.cr作为独立前导文件。2. 语言面
as删除,改成中缀cast;cast<T>(expr)一并删除。转换改为白名单:int与dex有真实语义,白名单外报error[TA09]。self/&self/&mut self,不再接受self: T;self不能出现在 impl 之外。Self解析到 impl 类型,可用于返回位置和参数位置。-> T会提示省略返回型。3. AST span
ast_line/ast_col语义不变(全局行号),由 span 解码。g_line_fileid/g_line_lib/build_line_fileid。4. 文档与注释
按
docs/doc-style.md清理全仓比喻与行话(docs/superpowers/plans除外):铁律、闸门、判据、口径、腿、牙、钉、批 等。验证
check src/compiler通过;冷缓存 44.2 秒(改造前 52.7 秒)。54×0 / 35×1,暖态 89 档 FAIL=0。test_lsp的 6 个失败在 develop 上同样存在,值逐字相同。注意
tools/baseline/REBUILD.md换代固定基线:PINNED_REV仍是7eb9669d,它早于 develop 的库可见性批,固定基线对拍在当前树上不可用。