@calcit/procs 0.12.53 → 0.12.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/README.md +12 -12
  3. package/RFCs/04-13-type-slot-mechanism-rfc.md +519 -115
  4. package/RFCs/04-16-wasm-data-structures.md +1 -1
  5. package/RFCs/07-26-agent-docs-and-evaluation-rfc.md +33 -0
  6. package/RFCs/07-26-agent-machine-protocol-rfc.md +102 -0
  7. package/RFCs/07-26-safe-structured-editing-rfc.md +66 -0
  8. package/RFCs/07-26-static-semantic-analysis-rfc.md +105 -0
  9. package/RFCs/07-28-git-module-store-rfc.md +59 -0
  10. package/RFCs/07-28-persistent-tree-cursor-rfc.md +287 -0
  11. package/RFCs/README.md +9 -3
  12. package/build.rs +89 -7
  13. package/editing-history/2026-07-28-1346-edit-transaction.md +27 -0
  14. package/editing-history/2026-07-28-1454-persistent-tree-cursor.md +30 -0
  15. package/editing-history/2026-07-28-1651-cursor-focus-stack-clipboard.md +30 -0
  16. package/editing-history/2026-07-28-1719-cursor-navigation-search-selection.md +21 -0
  17. package/editing-history/2026-07-28-1958-agent-rfc-split.md +14 -0
  18. package/editing-history/2026-07-28-2004-cursor-editing-docs.md +14 -0
  19. package/editing-history/2026-07-28-2132-cursor-recoverable-clipboard.md +18 -0
  20. package/editing-history/2026-07-28-2151-cursor-native-structural-editing.md +20 -0
  21. package/editing-history/2026-07-28-2158-document-cursor-native-workflows.md +13 -0
  22. package/editing-history/2026-07-29-0022-add-cursor-cli-options.md +7 -0
  23. package/editing-history/2026-07-29-0022-audit-cursor-development-scenarios.md +6 -0
  24. package/editing-history/2026-07-29-0022-complete-cursor-structural-edits.md +8 -0
  25. package/editing-history/2026-07-29-0022-document-cursor-edit-recipes.md +8 -0
  26. package/editing-history/2026-07-29-0022-echo-cursor-commands.md +6 -0
  27. package/editing-history/2026-07-29-0022-edit-target-cursor-alias.md +6 -0
  28. package/editing-history/2026-07-29-0022-guide-agents-through-cursor-workflows.md +7 -0
  29. package/editing-history/2026-07-29-0022-query-from-active-cursor.md +8 -0
  30. package/editing-history/2026-07-29-0022-tree-target-cursor-alias.md +5 -0
  31. package/editing-history/2026-07-29-1242-agent-guide-cold-start-validation.md +15 -0
  32. package/editing-history/2026-07-29-1242-cirru-quote-input-errors.md +13 -0
  33. package/editing-history/2026-07-29-1242-internal-wasm-doc-boundary.md +13 -0
  34. package/editing-history/2026-07-30-1130-pr-281-review-fixes.md +5 -0
  35. package/editing-history/20260729-1421-consolidate-local-state-and-cursor-tools.md +22 -0
  36. package/editing-history/20260729-1422-document-calcit-local-state.md +15 -0
  37. package/editing-history/20260729-1952-strengthen-polymorphism-diagnostics.md +21 -0
  38. package/editing-history/202607301429-entry-type-slots.md +29 -0
  39. package/editing-history/202607301435-release-0.12.54.md +22 -0
  40. package/editing-history/202607301620-program-diff-type-slots.md +6 -0
  41. package/editing-history/202607301959-unify-snapshot-entries.md +7 -0
  42. package/editing-history/202607310027-entry-description.md +7 -0
  43. package/editing-history/202607310032-initialize-entry-descriptions.md +6 -0
  44. package/editing-history/202607310041-entry-functions-as-symbols.md +7 -0
  45. package/editing-history/202607310052-release-0.12.55.md +5 -0
  46. package/lib/package.json +1 -1
  47. package/package.json +1 -1
  48. package/RFCs/07-26-agent-semantic-interface-roadmap-rfc.md +0 -793
@@ -1,194 +1,598 @@
1
- # RFC: Type Slot 机制 — 库-应用间的类型注入
1
+ # RFC: Type Slot 作为显式编译上下文
2
2
 
3
- 状态:Draft
4
- 日期:2025-07-13
3
+ 状态:Partially Implemented(Revision 3)
4
+
5
+ 初版日期:2025-07-13
6
+
7
+ 修订日期:2026-07-30
5
8
 
6
9
  ---
7
10
 
8
- ## 1. 概要
11
+ ## 1. 摘要
12
+
13
+ Type slot 是 Calcit 为避免在 UI 框架、dispatch 回调等调用链中大量传递泛型而采用的工程折中:库声明一个类型位置,应用为一次编译选择具体的 enum、struct 或 record 类型。
14
+
15
+ 这个方向仍有价值,但当前 `with-type-slot` 把“编译期类型环境”伪装成“带运行时 body 的函数调用”,产生了两个已经在真实项目中暴露的问题:
16
+
17
+ 1. 单 body 会在预处理阶段擦除,多 body 会残留运行时调用;额外包一层 `do` 才能改变生成结果。
18
+ 2. slot 绑定保存在 thread-local 栈中,而 definition 编译缓存只按 `ns/def` 复用;所谓局部作用域实际上受首次懒编译顺序影响。
19
+
20
+ 本 RFC 把决策拆成两层:
21
+
22
+ - **已落地的最小方案**:`with-type-slot` 在预处理后无条件擦除;entry 的 `:type-slots` 在任何 definition 预处理前安装,作为整次运行/检查/codegen 的默认绑定。
23
+ - **继续保留为 Draft 的强化方案**:把当前 registry 重构为显式 `TypeSlotEnv`、将环境指纹纳入 compiled cache,并把 slot identity 升级为声明命名空间限定的身份。
24
+
25
+ 最小方案已经消除“为什么加 `do` 才成功”、`@calcit/procs` 版本耦合以及 entry 函数必须包裹整个调用链的困惑。每次 CLI invocation 只选择一个 entry,加载配置后会清空并重建程序状态,因此当前不要求环境指纹进入缓存;同一进程多环境增量编译、同名 slot 冲突等更复杂场景仍由强化方案处理。
26
+
27
+ ## 2. 决策请求
28
+
29
+ 本 RFC 请求分别确认以下决策,不把它们捆绑为一次大改:
30
+
31
+ | 决策 | 建议 | 兼容性 | 解决的问题 |
32
+ | --- | --- | --- | --- |
33
+ | A1. `with-type-slot` 无条件擦除 | 已实现 | 源码兼容 | `do` 差异、JS runtime 缺失、协议版本耦合 |
34
+ | A2. codegen 遇到残留 slot form 时硬错误 | 已实现 | 仅影响此前偶然可运行的错误路径 | 防止编译器 invariant 再次泄漏 |
35
+ | A3. 增加 AST/Rust 回归测试 | 已实现 | 无破坏 | 固化编译期-only 语义 |
36
+ | B1. binding 移到 entry 配置 | 已实现最小版本 | 配置格式新增 | 去掉入口 wrapper 和懒编译顺序依赖 |
37
+ | B2. 显式 `TypeSlotEnv` 与缓存指纹 | 暂缓 | Rust 内部 Breaking | 多环境增量编译正确性 |
38
+ | B3. slot 使用声明命名空间作为身份 | 暂缓 | Schema/序列化可能 Breaking | 跨库同名冲突 |
39
+ | B4. 未绑定 slot 不再静默退化 | 暂缓 | 行为 Breaking | 防止类型检查悄悄失效 |
9
40
 
10
- 实现 `deftype-slot` / `bind-type` proc 对,允许库代码声明类型占位符(slot),应用代码在启动时绑定具体类型(enum/struct/record)。预处理阶段自动解析 slot 引用,使跨包边界的类型检查成为可能。
41
+ 现有 `with-type-slot` 源码仍可继续使用,但新项目应通过 `cr config set-type-slot` 维护 entry 配置。实现 entry 配置 **不代表批准** 显式环境对象、缓存指纹或 namespaced identity 的重构预算;这些内容仍需满足第 8.7 节的证据门槛。
11
42
 
12
- ## 2. 动机
43
+ ## 3. 背景与原始动机
13
44
 
14
- ### 问题
45
+ ### 3.1 问题场景
15
46
 
16
- Calcit 的静态类型分析在单包内工作良好:`defenum` 定义的变体名、载荷数量和类型都能在预处理阶段被检查。但当**库**(如 Respo)定义回调签名时,它无法知道**应用**层会使用哪个 enum 作为 dispatch 操作类型。
47
+ Calcit 的静态分析能检查 enum variant、载荷数量和载荷类型。但库定义回调签名时,通常不知道应用将使用哪个具体 enum
17
48
 
18
- Respo `EventHandler` 为例:
49
+ 例如 Respo 一类库希望表达 dispatch callback 接收应用的 `Op`:
19
50
 
20
51
  ```cirru
21
- ;; respo.schema — 库代码
22
- ;; dispatch 回调的签名原先只能标注为 :tuple(即 Dynamic)
52
+ ;; library schema
53
+ deftype-slot :dispatch-op
54
+
23
55
  :: :fn $ {} (:return :unit)
24
56
  :args $ [] '*dispatch-op
25
57
  ```
26
58
 
27
- 应用层定义了自己的 Op enum:
59
+ 应用定义自己的操作类型:
28
60
 
29
61
  ```cirru
30
- ;; app.schema
31
- defenum Op (:add :string) (:remove :tag) (:toggle :map) (:clear) ...
62
+ defenum Op
63
+ :add :string
64
+ :remove :tag
65
+ :clear
32
66
  ```
33
67
 
34
- 没有 type-slot 机制时,`d! (: add ...)` 这类调用无法被类型检查,因为预处理器不知道 dispatch 参数应该是 `Op` 类型。
68
+ 如果把 `Op` 做成普通泛型,它会出现在 EventHandler、component、renderer、dispatch helper 以及中间数据结构的大量 schema 中。Type slot 的目标是让这类“整个应用统一选择一次”的类型不必逐层出现在源码 API 上。
69
+
70
+ ### 3.2 Type slot 不是什么
71
+
72
+ Type slot 不是:
73
+
74
+ - 运行时依赖注入;
75
+ - 每次函数调用独立推导的泛型参数;
76
+ - 可在同一个编译产物中任意切换的动态类型变量;
77
+ - 用来绕过所有跨包泛型的通用机制。
78
+
79
+ 它更接近“编译这个 entry 时采用的一组类型配置”。这一定位决定了它不应产生 runtime ABI,也不应由运行时版本决定能否工作。
35
80
 
36
- ### 为什么不用泛型
81
+ ## 4. 原实现与故障链
37
82
 
38
- Calcit 目前没有完整的泛型(type parameter)系统。引入泛型会大幅增加语言复杂性,而 type-slot 解决的是一个更窄的问题:**跨编译单元的单一类型注入**。它更像 dependency injection 而非 parametric polymorphism。
83
+ ### 4.1 原源码模型
39
84
 
40
- ## 3. 设计
85
+ 应用目前这样绑定 slot:
41
86
 
42
- ### 核心概念
87
+ ```cirru
88
+ defn main! () $ with-type-slot (:dispatch-op Op)
89
+ setup!
90
+ render-app!
91
+ ```
92
+
93
+ 预处理器解析 binding,压入 [`TYPE_SLOT_OVERRIDES`](../src/calcit/type_annotation.rs),在该栈生效时预处理 body,最后弹出 binding。
43
94
 
44
- | 概念 | 说明 |
45
- |---|---|
46
- | **Type Slot** | 一个命名占位符,声明时值为 `None`,绑定后值为具体类型注解 |
47
- | `deftype-slot :name` | 在库代码中声明 slot(通常放在 schema 命名空间) |
48
- | `bind-type :name ConcreteType` | 在应用入口绑定具体类型(通常放在 `main!` 函数体) |
49
- | `*name` | 在 schema 类型标注中引用 slot(解析为 `TypeSlot(name)`) |
95
+ ### 4.2 body 与多 body 分叉
50
96
 
51
- ### 生命周期
97
+ 当前 [`preprocess_with_type_slot_block`](../src/runner/preprocess/mod.rs) 的尾部逻辑等价于:
52
98
 
99
+ ```text
100
+ if body.len == 1:
101
+ return body[0]
102
+ else:
103
+ return (with-type-slot body...)
53
104
  ```
54
- 声明 (deftype-slot) → 绑定 (bind-type) → 解析 (*name 引用) → 类型检查
55
- ↑ 库代码 ↑ 应用入口 ↑ 预处理阶段 ↑ 预处理阶段
105
+
106
+ 于是:
107
+
108
+ ```cirru
109
+ with-type-slot (:dispatch-op Op)
110
+ do
111
+ setup!
112
+ render-app!
56
113
  ```
57
114
 
58
- 1. **声明**:`deftype-slot :dispatch-op` `TYPE_SLOTS` 注册表中插入 `("dispatch-op", None)`。
59
- 2. **绑定**:`bind-type :dispatch-op Op` 将 slot 值设为 `Some(Enum(Op, []))`。绑定发生在预处理阶段(通过 `resolve_program_value_for_preprocess` 提前求值),确保后续同一编译 pass 的类型检查能立即使用。
60
- 3. **解析**:当类型标注遇到 `*dispatch-op` 时,`resolve_type_slot("dispatch-op")` 返回绑定的 `Enum(Op, [])`。
61
- 4. **检查**:解析后的类型委托给标准的 `matches_with_bindings` / `value_matches_type_annotation` 进行检查。
115
+ 会因 body 只有一个 `do` 而完全擦除;没有 `do` 的同义代码却会残留 `with-type-slot`。
62
116
 
63
- ### 约束
117
+ 这不是 `do` 修复了类型,而是源码形状选择了不同的编译路径。
64
118
 
65
- - 每个 slot 只能声明一次(重复声明报错)。
66
- - 每个 slot 只能绑定一次(重复绑定报错)。预处理阶段已完成绑定后,运行时再次执行 `bind-type` 会静默跳过(no-op),不会重复报错。
67
- - 未绑定的 slot 在类型检查时等同于 `:dynamic`(不报错但不检查)。
68
- - 绑定必须是 enum、struct 或 record 类型。
119
+ ### 4.3 Runtime 与 JS codegen 泄漏
69
120
 
70
- ## 4. API 参考
121
+ Rust interpreter 注册了 [`with_type_slot_runtime`](../src/builtins/meta.rs),依次接收已经求值的 body 并返回最后一个值。[JS codegen](../src/codegen/emit_js.rs) 没有对应的 compile-time 分支,因此残留 form 会落入通用 proc 生成路径,并依赖 `@calcit/procs` 导出同名函数。
71
122
 
72
- ### `deftype-slot`
123
+ 这造成三层语义不一致:
73
124
 
74
- 声明一个类型占位符。
125
+ | 层 | 当前行为 |
126
+ | --- | --- |
127
+ | 预处理单 body | 完全擦除 |
128
+ | Rust 多 body | 依赖 runtime stub,偶然可运行 |
129
+ | JS 多 body | 生成 runtime proc 调用,可能在模块加载或调用时失败 |
75
130
 
76
- ```cirru
77
- deftype-slot :dispatch-op
131
+ 即使 CLI 与 `@calcit/procs` 版本完全一致,这个 compile-time-only form 也不应要求 runtime 提供实现。版本不一致会扩大故障面,但不是根因。
132
+
133
+ ### 4.4 “局部作用域”并不完全成立
134
+
135
+ slot override 是 thread-local 栈;definition 一旦完成预处理,compiled result 按 `ns/def` 缓存。一个依赖在 slot A 下首次编译后,在 slot B 下再次引用时会直接命中缓存,而不会重新应用 B。
136
+
137
+ 因此当前语义更准确地说是:
138
+
139
+ > `with-type-slot` 影响其 body 触发的、尚未进入缓存的传递依赖。
140
+
141
+ 它不是普通词法作用域。下列因素都可能改变结果:
142
+
143
+ - definition 是否在进入 block 前已经被预热;
144
+ - client/server entry 是否在同一进程、同一 compiled cache 中处理;
145
+ - watch reload 清除了哪些 definition;
146
+ - 两条调用链是否共享一个首次被特化的 dependency。
147
+
148
+ 现有 [entry 测试](../src/bin/cr_tests/type_fail.rs)分别重新加载 client 与 server,只证明“独立加载时不冲突”,没有证明“同一缓存中两个环境可安全复用”。
149
+
150
+ ### 4.5 其他可解释性问题
151
+
152
+ - `deftype-slot` 被描述为可选声明,导致 slot 名称缺少可靠的声明身份。
153
+ - slot 以短字符串作为全局 key,不同库的 `:dispatch-op` 可能冲突。
154
+ - 未绑定 slot 静默视为 `:dynamic`,用户无法区分“有意动态”与“绑定没有覆盖到这里”。
155
+ - `push` / `pop` 依赖手动配对;普通错误路径已清理,但 panic 或未来新增早退分支可能污染同线程后续编译。
156
+
157
+ ## 5. 设计目标
158
+
159
+ 本提案必须满足以下目标:
160
+
161
+ 1. **不传播源码泛型**:应用级统一类型不需要出现在每层函数和数据结构的参数列表中。
162
+ 2. **纯编译期**:type slot 不产生 runtime value、runtime proc 或 JS package ABI。
163
+ 3. **源码形状无关**:单 body、多 body、显式 `do` 的编译期类型效果一致。
164
+ 4. **缓存可证明正确**:编译结果必须能说明它在哪个 slot 环境中产生。
165
+ 5. **entry 可隔离**:client/server 可选择不同具体类型,但不能靠首次编译顺序碰运气。
166
+ 6. **失败可见**:缺失绑定、冲突绑定和环境复用错误必须给出明确诊断。
167
+ 7. **分阶段交付**:修复当前 bug 不依赖完整架构重构。
168
+
169
+ ## 6. 非目标
170
+
171
+ 本 RFC 不试图:
172
+
173
+ - 用 type slot 替代普通函数/数据类型泛型;
174
+ - 在一个函数的不同调用点自动生成任意数量的泛型特化;
175
+ - 允许运行时改变 slot binding;
176
+ - 为同一个 JS module 自动复制并重命名整条依赖图;
177
+ - 在阶段 A 改变现有 `*name` schema 语法。
178
+
179
+ ## 7. 提案 A:无条件擦除 `with-type-slot`
180
+
181
+ ### 7.1 规范语义
182
+
183
+ `with-type-slot (:name TypeExpr) body...` 只在预处理期间存在:
184
+
185
+ 1. 解析并校验 binding;
186
+ 2. 在 slot override 下预处理全部 body;
187
+ 3. 恢复之前的 slot 环境;
188
+ 4. 返回不含 `with-type-slot` 的普通 Calcit IR。
189
+
190
+ 预处理输出规则:
191
+
192
+ | body 数量 | 输出 |
193
+ | --- | --- |
194
+ | 0 | arity error:至少需要一个 body expression |
195
+ | 1 | 该 expression |
196
+ | 2+ | 内部顺序表达式,语义等价于已展开的 `do` / `&let () body...` |
197
+
198
+ 实现应直接构造已经展开的顺序 IR,而不是重新插入源码层 `do` macro,避免 body 被第二次宏展开或预处理。
199
+
200
+ ### 7.2 编译器 invariant
201
+
202
+ 预处理完成后的 AST/IR 中不得出现:
203
+
204
+ - `CalcitProc::WithTypeSlot`;
205
+ - slot binding pair 的运行时求值;
206
+ - 指向 `@calcit/procs` 的 `with-type-slot` 调用。
207
+
208
+ Rust evaluator、JS codegen 或 WASM codegen 如果收到残留 form,应返回内部编译错误,例如:
209
+
210
+ ```text
211
+ internal compiler error: with-type-slot escaped preprocessing
78
212
  ```
79
213
 
80
- - **参数**:1 个 tag 或 string,作为 slot 名称。
81
- - **返回**:`nil`
82
- - **副作用**:在全局 `TYPE_SLOTS` 注册表中注册 slot。
214
+ 不能继续生成一个看似合法但依赖特定 runtime 版本的调用。
83
215
 
84
- ### `bind-type`
216
+ ### 7.3 内部表示
85
217
 
86
- 将具体类型绑定到已声明的 slot
218
+ 阶段 A 可以保留 `CalcitProc::WithTypeSlot` 作为 parser/preprocessor 入口,以降低改动范围。后续可把它改成专用 syntax 节点,彻底删除 runtime dispatch
87
219
 
88
- ```cirru
89
- bind-type :dispatch-op Op
220
+ 推荐的兼容顺序:
221
+
222
+ 1. 先保证所有正常编译路径都擦除;
223
+ 2. codegen 对泄漏 form 硬错误;
224
+ 3. 一个发布周期后删除 `with_type_slot_runtime` 与 builtins dispatch;
225
+ 4. 最后再决定是否将内部枚举从 Proc 移到 Syntax。
226
+
227
+ 这些内部调整不要求应用改源码。
228
+
229
+ ### 7.4 为什么不只给 JS runtime 补函数
230
+
231
+ 为 `@calcit/procs` 增加 `with_type_slot(...xs) => xs.at(-1)` 只能掩盖泄漏:
232
+
233
+ - 仍然保留单 body/多 body 两条路径;
234
+ - 仍然让纯类型功能进入 runtime 协议;
235
+ - 仍然要求 CLI/runtime 同步升级;
236
+ - WASM 与后续 backend 也必须重复实现无业务含义的 stub;
237
+ - 不能解决 compiled cache 与 slot 环境不匹配。
238
+
239
+ 可以在过渡发布中临时提供 JS stub 以改善旧产物错误信息,但它不属于最终设计。
240
+
241
+ ### 7.5 阶段 A 的原实施边界
242
+
243
+ 阶段 A 作为局部编译器修复已经完成,原定边界只触及:
244
+
245
+ - `src/runner/preprocess/mod.rs`:把多 body 输出改为普通顺序 IR,并拒绝空 body;
246
+ - `src/codegen/emit_js.rs` 及必要的 backend guard:阻止残留 form 进入 runtime;
247
+ - 定向测试 fixture / Rust tests:覆盖 AST 擦除、返回值和 JS 输出;
248
+ - 静态分析文档:删除需要额外 `do` 的暗示,明确 compile-time-only。
249
+
250
+ 阶段 A 本身没有修改:
251
+
252
+ - snapshot 的 entry schema;
253
+ - `TypeSlot` 的序列化格式;
254
+ - compiled cache key;
255
+ - 应用源码调用方式;
256
+ - 普通泛型或 trait 机制。
257
+
258
+ 后续又独立落地了第 8.2 节的最小 entry 配置,但缓存指纹与 namespaced identity 仍停留在 RFC,不因配置功能存在而自动排期。
259
+
260
+ ## 8. Entry 级配置与显式 `TypeSlotEnv`
261
+
262
+ 本节分为已经实现的 entry 配置信息模型,以及仍待证据支持的内部环境重构。
263
+
264
+ ### 8.1 语义边界
265
+
266
+ 一个 codegen/check invocation 选择一个 entry,也选择一份不可变的 type-slot environment。该环境在任何 definition 预处理前建立,在整个可达调用图中保持一致。
267
+
268
+ 概念上:
269
+
270
+ ```text
271
+ compile(snapshot, selected_entry, type_slot_env) -> artifact
272
+ ```
273
+
274
+ 而不是:
275
+
276
+ ```text
277
+ evaluate(with-type-slot body) -> 在遇到 dependency 时临时影响编译
90
278
  ```
91
279
 
92
- - **参数**:
93
- 1. tag 或 string — slot 名称(必须已通过 `deftype-slot` 声明)。
94
- 2. enum / struct / record 定义值。
95
- - **返回**:`nil`
96
- - **副作用**:将类型绑定写入 `TYPE_SLOTS`。
97
- - **错误**:slot 未声明、slot 已绑定、第二参数类型不对。
280
+ 这与 type slot 的真实用途一致:应用为一次构建选择统一类型,而不是函数执行到某一行时改变类型。
98
281
 
99
- ### `*name` 类型引用语法
282
+ ### 8.2 已实现的配置语法
100
283
 
101
- schema 类型标注中使用 `*name` 引用 slot
284
+ 默认 entry `:entries.default` 中绑定短 slot 名,值必须是完整 definition path 或 `:dynamic`:
102
285
 
103
286
  ```cirru
104
- ;; EventHandler 的 schema 中
105
- :args $ [] '*dispatch-op
287
+ :entries $ {}
288
+ :default $ {}
289
+ :mode :native
290
+ :init-fn |app.main/main!
291
+ :type-slots $ {}
292
+ :dispatch-op |app.schema/Op
106
293
  ```
107
294
 
108
- Cirru EDN 序列化为 `'*dispatch-op`(`'` 是 EDN symbol 前缀,`*` 是 type-slot 标记)。
295
+ 命名 entry 使用自己的完整配置,可以选择另一类型:
109
296
 
110
- ## 5. 使用示例
297
+ ```cirru
298
+ :entries $ {}
299
+ :server $ {}
300
+ :mode :native
301
+ :init-fn |app.server/main!
302
+ :type-slots $ {}
303
+ :dispatch-op |app.schema/ServerOp
304
+ ```
305
+
306
+ 命名 entry 不继承 `:entries.default.type-slots`。这里使用完整 definition path,而不是在配置解析阶段求值任意 Calcit expression,因此配置可序列化、可查询,也不依赖入口函数的执行顺序。对应命令为:
307
+
308
+ ```bash
309
+ cr config set-type-slot :dispatch-op app.schema/Op
310
+ cr config set-type-slot --entry server :dispatch-op app.schema/ServerOp
311
+ cr config type-slots --entry server
312
+ cr config rm-type-slot --entry server :dispatch-op
313
+ ```
111
314
 
112
- ### Respo EventHandler 场景
315
+ 程序加载模块后验证 type path 存在,再在任何 definition 预处理前安装所选 entry 的绑定。局部 `with-type-slot` 兼容形式仍可覆盖 entry 默认值,但新入口不需要 wrapper。
113
316
 
114
- **库端(respo.schema):**
317
+ ### 8.3 Slot 身份(暂缓)
318
+
319
+ 当前实现继续使用裸 slot 名,以控制迁移范围。若真实项目出现两个依赖库同名 slot 冲突,内部 identity 再升级为:
320
+
321
+ ```text
322
+ TypeSlotId { declaring_ns, name }
323
+ ```
324
+
325
+ 库内仍可书写简短引用 `'*dispatch-op`;在读取该 namespace 的 schema 时解析为完整 identity。entry 配置绑定完整路径,避免两个依赖库都声明 `:dispatch-op` 时互相覆盖。
326
+
327
+ 跨 namespace 显式引用可保留扩展空间,例如:
115
328
 
116
329
  ```cirru
117
- ;; 声明 slot
118
- deftype-slot :dispatch-op
330
+ '*respo.schema/dispatch-op
331
+ ```
119
332
 
120
- ;; EventHandler schema 引用 slot
121
- :: :fn $ {} (:return :unit)
122
- :args $ [] 'respo.schema/RespoEvent
123
- :: :fn $ {} (:return :unit)
124
- :args $ [] '*dispatch-op
333
+ 是否公开该语法不影响内部先采用 namespaced identity。
334
+
335
+ ### 8.4 编译上下文(暂缓)
336
+
337
+ 当前 CLI 在一次 invocation 加载一个 entry,并在提取程序时重置 registry;它没有把环境对象逐层传入预处理 API。若要支持同一进程中的多 entry 增量编译,应显式接收不可变环境,而不是读取 registry:
338
+
339
+ ```rust
340
+ struct TypeSlotEnv {
341
+ bindings: HashMap<TypeSlotId, Arc<CalcitTypeAnnotation>>,
342
+ fingerprint: TypeSlotEnvId,
343
+ }
125
344
  ```
126
345
 
127
- **应用端(app.main):**
346
+ `TypeSlotEnvId` 必须稳定反映所有 slot identity 与 concrete type reference。它用于:
347
+
348
+ - compiled cache 校验;
349
+ - watch reload 失效判断;
350
+ - query/debug 输出;
351
+ - compiled snapshot 元数据;
352
+ - 诊断“该 def 已在另一环境下编译”。
353
+
354
+ ### 8.5 缓存策略(暂缓)
355
+
356
+ 强化方案不做多环境自动特化。采用更保守的规则:
357
+
358
+ > 一个输出 artifact 只允许一个 `TypeSlotEnvId`。
359
+
360
+ compiled definition 记录环境 ID。遇到不同环境时:
361
+
362
+ - 新的独立 build/check invocation:清理或使用另一份 compiled cache;
363
+ - 同一 artifact 内:明确拒绝,提示为两个 entry 分别生成产物;
364
+ - watch 中 entry binding 改变:使全部依赖 slot 的 compiled definitions 失效;第一版可以安全地全量失效,后续再缩小范围。
365
+
366
+ 这避免在尚未设计稳定符号命名和共享策略前,暗中生成多份 specialized JS definitions。
367
+
368
+ ### 8.6 未绑定行为(当前保持兼容)
369
+
370
+ 当前未绑定 slot 静默退化为 `:dynamic`,很容易让用户误以为检查已经生效。
371
+
372
+ 当前未绑定 slot 仍退化为 `:dynamic`;配置也允许显式写 `:dynamic`。是否升级诊断继续保留以下建议规则:
373
+
374
+ - 可达 schema 引用了已声明 slot,但 entry 未绑定:默认 hard error;
375
+ - 应用确实希望关闭检查时,在 entry 中显式绑定为 `:dynamic`,让意图可见;
376
+ - 迁移期可先发结构化 warning,再在下一个 breaking release 升级为 error。
377
+
378
+ 示例诊断:
379
+
380
+ ```text
381
+ E_UNBOUND_TYPE_SLOT: `respo.schema/dispatch-op` is required by
382
+ respo.schema/EventHandler but is not bound for entry `app.main/main!`.
383
+ Bind it in :type-slots or explicitly select :dynamic.
384
+ ```
385
+
386
+ ### 8.7 阶段 B 的证据门槛
387
+
388
+ 阶段 B 只有在至少满足下列一项时才进入实现评审:
389
+
390
+ 1. 有最小复现证明同一 compiled cache 中,definition 在 slot A 下编译后被 slot B 错误复用;
391
+ 2. watch reload 改变 binding 后出现可重复的陈旧类型检查或错误 codegen;
392
+ 3. 一个受支持的正式工作流需要在同一进程连续构建多个不同 slot 环境,并且简单清理缓存不可接受;
393
+ 4. 两个真实依赖库的同名 slot 已产生冲突,而不是仅有理论可能。
394
+
395
+ 进入实现评审前还必须提供基线数据:受 slot 影响的 definition 数量、全量失效耗时、独立 entry 构建耗时,以及在 Respo 等真实项目上的产物差异。若问题可由更小的缓存失效修复解决,应优先另提小改,不直接启用完整 `TypeSlotEnv` 重构。
396
+
397
+ ## 9. 多 Entry 与共享代码
398
+
399
+ ### 9.1 推荐模型
400
+
401
+ client 与 server 若使用不同的 `Op`,应作为两个独立 compilation units 生成产物:
402
+
403
+ ```text
404
+ client entry + ClientOp -> client artifact
405
+ server entry + ServerOp -> server artifact
406
+ ```
407
+
408
+ 它们可以共享源码和普通缓存输入,但不能共享已经受 type-slot 驱动重写影响的 compiled definition。
409
+
410
+ ### 9.2 为什么不能无成本共享
411
+
412
+ Type slot 不只影响 warning;它还可能参与 enum tuple 识别和类型导向重写。相同源码 definition 在两个环境下可能生成结构不同的 IR。
413
+
414
+ 若要求一个 artifact 同时包含两种环境,编译器必须选择以下一种复杂策略:
415
+
416
+ - 复制整条受影响调用图并生成稳定的 specialized symbol;
417
+ - 保留统一动态表示,放弃相关静态重写;
418
+ - 重新引入显式泛型/类型参数。
419
+
420
+ 本 RFC 不隐式承诺这些能力。第一版选择独立 artifact,行为简单且可验证。
421
+
422
+ ## 10. 迁移方案与当前进度
423
+
424
+ ### 已实现:修复语义泄漏
425
+
426
+ - 已实施提案 A;
427
+ - 保持现有 `with-type-slot` 源码有效;
428
+ - 多 body 无需 `do`;
429
+ - 增加泄漏 invariant 与后端测试;
430
+ - 文档明确 type slot 是 compile-time-only。
431
+
432
+ 应用无需迁移。
433
+
434
+ ### 已实现:引入最小 entry 配置
435
+
436
+ - 已增加 `:type-slots` 配置;
437
+ - `with-type-slot` 继续可用并始终在预处理阶段擦除;
438
+ - `cr config type-slots [--entry name]` 展示 bindings;
439
+ - `set-type-slot` / `rm-type-slot` 提供安全的 snapshot 修改入口;
440
+ - 未绑定 slot 暂时保持 `:dynamic` 兼容行为;
441
+ - `TypeSlotEnvId` 和自动提取 wrapper 的迁移命令尚未实现。
442
+
443
+ 迁移前:
128
444
 
129
445
  ```cirru
130
- ;; app.schema 定义 Op enum
131
- defenum Op
132
- :add :string
133
- :remove :tag
134
- :toggle :map
135
- :update :tag :string
136
- :clear
137
- :states-merge :any :any :any
446
+ defn main! () $ with-type-slot (:dispatch-op Op)
447
+ setup!
448
+ render-app!
449
+ ```
138
450
 
139
- ;; main! 中绑定
451
+ 迁移后:
452
+
453
+ ```cirru
454
+ ;; cr config set-type-slot :dispatch-op app.schema/Op
140
455
  defn main! ()
141
- bind-type :dispatch-op Op
142
- ;; ... 后续代码
456
+ setup!
457
+ render-app!
143
458
  ```
144
459
 
145
- **效果**:
460
+ ### 候选 breaking release(未排期)
146
461
 
147
- ```cirru
148
- ;; 正确 编译通过
149
- d! $ %:: Op :toggle (:id task)
462
+ - entry 可达的未绑定 slot 升级为 error;
463
+ - 删除 runtime stub 和 runtime proc dispatch;
464
+ - 视迁移情况决定删除 `with-type-slot` 源码语法,或仅保留为 entry 配置的兼容展开形式;
465
+ - slot identity 固定为 namespaced declaration。
466
+
467
+ ## 11. 验证计划
468
+
469
+ ### 11.1 阶段 A 必须覆盖
470
+
471
+ 1. 单 body 预处理结果不含 `WithTypeSlot`。
472
+ 2. 多 body 预处理结果不含 `WithTypeSlot`,保持求值顺序并返回最后值。
473
+ 3. 空 body 给出明确 arity error。
474
+ 4. 显式 `do` 与直接多 body 产生等价行为。
475
+ 5. JS 输出中不存在 `with_type_slot` / `with-type-slot` runtime reference。
476
+ 6. Rust once、JS once 与 WASM 内部验证结果一致。
477
+ 7. 使用不包含 type-slot runtime proc 的旧 `@calcit/procs` 仍可运行新生成产物。
478
+ 8. codegen 人工收到泄漏 form 时稳定返回 internal compiler error。
479
+
480
+ ### 11.2 Entry 配置必须覆盖
481
+
482
+ 1. client/server 在不同 invocation 中绑定同名 slot 到不同类型,结果独立且确定。
483
+ 2. 默认与命名 entry 的配置 round-trip 不丢失,旧 snapshot 缺少字段时按空 map 读取。
484
+ 3. 完整 type path、不存在 definition 与 `:dynamic` 有确定行为。
485
+ 4. entry 默认 binding 可被兼容的局部 `with-type-slot` 覆盖并在退出后恢复。
486
+ 5. `--check-only`、Rust 与 JS 对同一 entry 使用同一绑定。
487
+ 6. config set/list/remove 命令不会破坏 snapshot 其他字段。
488
+
489
+ ### 11.3 强化方案启用前必须覆盖
490
+
491
+ 1. 相同 definition 在不同环境间不会从 compiled cache 静默复用。
492
+ 2. watch 中修改 entry binding 后正确失效。
493
+ 3. 两个库声明同名短 slot 时,完整 identity 不冲突。
494
+ 4. compiled snapshot/query 输出可报告 environment fingerprint。
495
+ 5. 同一 artifact 尝试混入两种 environment 时明确拒绝并给出拆分 entry 的建议。
150
496
 
151
- ;; 错误变体名 — 预处理警告 "does not have variant :delete"
152
- d! $ %:: Op :delete (:id task)
497
+ ### 11.4 仓库验证
153
498
 
154
- ;; ❌ 载荷数量错 — 预处理警告 "expects 1 payload(s), got 2"
155
- d! $ %:: Op :clear 42
499
+ 实现完成后按仓库流程执行:
156
500
 
157
- ;; ❌ 载荷类型错 — 预处理警告 "expects :string, got :number"
158
- d! $ %:: Op :add 42
501
+ ```bash
502
+ cargo fmt
503
+ cargo clippy -- -D warnings
504
+ yarn compile
505
+ cargo test
506
+ yarn check-agent-interface
507
+ yarn check-all
159
508
  ```
160
509
 
161
- ## 6. 实现细节
510
+ 涉及 CLI 查询、编辑或类型分析时,再使用新构建的 `cr` 在 Respo 等真实项目回归。应验证 Respo 的 entry 配置能让大量 `d! $ :: ...` 调用共享同一 `Op` 检查,并验证旧的多 body 兼容 form 生成 JS 不再依赖 `do`。
162
511
 
163
- ### 修改的文件
512
+ ## 12. 备选方案
513
+
514
+ ### 12.1 保持现状,只在文档要求包 `do`
515
+
516
+ 拒绝。它把编译器内部 body-count 分支变成用户必须记忆的语法仪式,也无法解释 Rust/JS 差异。
517
+
518
+ ### 12.2 给所有 runtime 增加 `with-type-slot` stub
519
+
520
+ 拒绝作为最终方案。可以短期兼容旧生成物,但会永久扩大 runtime ABI,并掩盖预处理泄漏。
521
+
522
+ ### 12.3 只把 Proc 改成 Syntax
523
+
524
+ 不充分。专用 syntax 能表达 compile-time-only 意图,但若多 body 仍残留,或编译缓存仍忽略 slot 环境,核心问题依然存在。
525
+
526
+ ### 12.4 恢复全局 `bind-type`
527
+
528
+ 拒绝。它无法支持 client/server 独立选择,也使加载顺序和全局副作用更严重。
529
+
530
+ ### 12.5 全面改用泛型
531
+
532
+ 类型理论上最直接,但违背 type slot 的原始工程目标:一个应用统一选择的 dispatch 类型会沿大量库 API 传播。普通局部关系仍应使用泛型;应用级配置关系保留 type slot。
533
+
534
+ ### 12.6 自动按环境做多版本单态化
535
+
536
+ 暂不采用。它需要调用图复制、symbol mangling、增量缓存和 JS module export 规则,复杂度显著高于当前需求。未来若出现“单 artifact 必须容纳多套 slot”的真实用例,再另立 RFC。
537
+
538
+ ## 13. 风险与缓解
539
+
540
+ | 风险 | 缓解 |
541
+ | --- | --- |
542
+ | 阶段 A 构造顺序 IR 时丢失 location | 复用 wrapper/head location,并测试 warning/call stack 位置 |
543
+ | 删除 runtime stub 影响旧 compiled snapshot | 延迟一个发布周期删除;加载旧 snapshot 时给明确升级提示 |
544
+ | Entry 配置增加项目元数据复杂度 | 只接受完整 slot/type path,不引入可执行配置表达式 |
545
+ | watch 中改变配置后需要重建环境 | snapshot reload 会重新提取程序并清理编译状态;若未来保留跨环境缓存,再引入环境指纹 |
546
+ | 未绑定升级为 error 破坏旧项目 | 先 warning;支持显式 `:dynamic` opt-out;提供迁移查询 |
547
+ | Namespaced identity 改变 schema 序列化 | 旧 `'*name` 按声明 namespace 兼容读取,新写出格式另行确定 |
548
+ | 两个 entry 误共享 artifact | 当前一次 CLI invocation 只选择一个 entry;支持同进程多 entry artifact 前必须实现 environment ID |
549
+
550
+ ## 14. 可观测性与 Agent 接口
551
+
552
+ 为避免 type slot 再次成为只能从生成代码猜测的隐藏状态,当前已经提供面向人的只读查询:
553
+
554
+ ```bash
555
+ cr config type-slots
556
+ cr config type-slots --entry server
557
+ ```
558
+
559
+ 后续若 Agent 接口确有需要,再增加单 JSON 的机器输出,例如:
560
+
561
+ ```json
562
+ {
563
+ "entry": "app.main/main!",
564
+ "environment_id": "...",
565
+ "bindings": [
566
+ {
567
+ "slot": "dispatch-op",
568
+ "type": "app.schema/Op",
569
+ "status": "bound"
570
+ }
571
+ ],
572
+ "unbound": []
573
+ }
574
+ ```
164
575
 
165
- | 文件 | 变更 |
166
- |---|---|
167
- | `src/calcit/type_annotation.rs` | `TYPE_SLOTS` 注册表, `TypeSlot` 变体, slot 解析/匹配/序列化 |
168
- | `src/calcit/proc_name.rs` | `DeftypeSlot` / `BindType` proc 名称 |
169
- | `src/builtins.rs` | dispatch arms |
170
- | `src/builtins/meta.rs` | `deftype_slot()` / `bind_type()` 实现 |
171
- | `src/calcit.rs` | re-export `register_type_slot`, `bind_type_slot` |
172
- | `src/runner.rs` | `clear_type_slots()` 在程序启动时调用(避免跨次运行残留) |
173
- | `src/runner/preprocess.rs` | 预处理阶段提前执行 `deftype-slot` / `bind-type` |
576
+ 机器查询不是最小 entry 配置的前置条件。若实现,stdout 必须保持单个 JSON;计时和人类提示写 stderr,遵循现有 Agent CLI 协议。
174
577
 
175
- ### 关键实现点
578
+ ## 15. 尚待决定的问题
176
579
 
177
- 1. **Thread-local 注册表**:`TYPE_SLOTS` 使用 `thread_local! { RefCell<HashMap<...>> }`,因为 `HashMap::new()` 不是 const fn,不能用 `const { ... }` 初始化。
580
+ 已决定:所有入口统一存放在 `:entries`,`:default` 是无参数入口;每个 entry 各自保存完整 `:type-slots`,不做隐式继承;配置值只接受完整 definition path `:dynamic`。
178
581
 
179
- 2. **预处理时绑定**:`bind-type` 在预处理阶段通过 `resolve_program_value_for_preprocess()` 提前求值,确保同一编译 pass 内类型检查可以立即使用绑定结果。这是整个机制的关键——如果在运行时才绑定,预处理阶段的类型检查无法看到具体类型。
582
+ 仍待决定:
180
583
 
181
- 3. **类型匹配委托**:`TypeSlot` `matches_with_bindings` `value_matches_type_annotation` 中解析后直接委托给标准匹配逻辑,不引入新的匹配分支。
584
+ 1. `deftype-slot` 是否默认 required?当前保持兼容,并以 entry 显式 `:dynamic` 作为 opt-out。
585
+ 2. 是否在迁移完成后删除 `with-type-slot` 源码语法?当前保留为局部兼容形式。
586
+ 3. `TypeSlotEnvId` 写入 compiled snapshot 后,旧 CLI 应拒绝还是忽略未知字段?需要结合 snapshot 兼容策略单独确认。
587
+ 4. slot identity 是否升级为 namespace-qualified declaration,以及源码是否公开跨 namespace slot 引用语法?
588
+ 5. slot declaration 是否最终改成 namespace metadata,而不再作为 `deftype-slot` form?
182
589
 
183
- 4. **序列化**:`TypeSlot(name)` 序列化为 `Edn::Symbol("*name")`,反序列化时 `*` 前缀触发 `TypeSlot` 解析。
590
+ ## 16. 推荐结论
184
591
 
185
- ## 7. 局限性与未来方向
592
+ A1-A3 与最小 B1 已实现。它们建立了最重要的 invariant:
186
593
 
187
- - **单绑定约束**:每个 slot 只能绑定一次。如果需要一个库支持多个不同 dispatch 类型或一个项目中有多个不同的 EventHandler,需要声明多个 slot
188
- - **无运行时效果**:`deftype-slot` 和 `bind-type` 在运行时是无操作的(返回 nil),它们的作用完全在预处理阶段。
189
- - **仅支持 enum/struct/record**:不能绑定基础类型(如 `:number`)到 slot,因为 slot 的主要场景是复合类型注入。
190
- - **未来可扩展**:如果未来 Calcit 引入泛型或 trait 约束,type-slot 可以作为特化机制的基础。
594
+ > Type slot 只属于编译器,不属于 runtime
191
595
 
192
- ## 8. 从 editing-history 迁移说明
596
+ 建议在 Respo 与 client/server fixture 验证通过后采用这套 entry 配置作为当前公开方案。显式环境对象、缓存指纹、namespaced identity 与未绑定 hard error 暂不捆绑发布;只有出现第 8.7 节的证据再继续推进。
193
597
 
194
- 本提案内容源自 `editing-history/202507131553-type-slot-mechanism.md`,已扩充为完整 RFC 格式。原始文件可在合并后删除。
598
+ 这个分阶段方案保留了 type slot “不大量传递泛型”的价值,同时停止让用户依赖 `do`、运行时版本和懒编译顺序理解它。