@calcit/procs 0.12.30 → 0.12.32
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.
package/.yarn/install-state.gz
CHANGED
|
Binary file
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Fix: bind-type 类型槽预编译顺序问题
|
|
2
|
+
|
|
3
|
+
## 问题描述
|
|
4
|
+
|
|
5
|
+
`cr js` 在 tiye-index 项目中,`reel.app.comp.todolist.mjs` 和 `respo.schema.mjs` 仍然不稳定——有时生成 `Op`-aware 版本(带 `import { Op }` 和 `$clt._PCT__$o__$o_(Op, ...)`),有时生成不带 Op 的版本(使用 `$clt._$o__$o_(...)`)。
|
|
6
|
+
|
|
7
|
+
## 根本原因
|
|
8
|
+
|
|
9
|
+
`bind-type :dispatch-op Op` 在 `respo.main/main!` 函数体中,是一个预处理阶段的副作用操作:当 `preprocess_expr` 遇到 `bind-type` 调用时,立即执行 `bind_type_slot("dispatch-op", Op)`,使 `*dispatch-op` 类型槽指向 Op 枚举。
|
|
10
|
+
|
|
11
|
+
问题根源:`reel.app.comp.todolist` 等组件 def 会作为入口函数 `app.main/main!` 的传递依赖被提前编译。由于 HashMap 迭代顺序不确定,某些运行中 `todolist` 在 `respo.main/main!` 之前编译——此时 `*dispatch-op` 类型槽尚未绑定,导致 `d! $ :: :states cursor ...` 中的元组字面量无法被改写为 `%:: Op :states cursor ...`,生成结构上不同的 JS 代码。
|
|
12
|
+
|
|
13
|
+
## 修复方法
|
|
14
|
+
|
|
15
|
+
**双重修复:**
|
|
16
|
+
|
|
17
|
+
### 1. 预处理阶段:`precompile_bind_type_defs` (主要修复)
|
|
18
|
+
|
|
19
|
+
在 `src/lib.rs` 的 `run_program_with_docs` 中,在开始主入口预处理之前,先扫描**所有**程序源代码,找出含有 `bind-type` 调用的 def,并提前编译它们。
|
|
20
|
+
|
|
21
|
+
这确保 `respo.main/main!`(含 `bind-type :dispatch-op Op`)在 `app.main/main!` 的依赖树遍历之前被编译,从而使 `:dispatch-op` 类型槽在所有组件编译前就已绑定。
|
|
22
|
+
|
|
23
|
+
### 2. 快照填充阶段:任务排序 (辅助修复)
|
|
24
|
+
|
|
25
|
+
在 `src/program.rs` 的 `collect_snapshot_fill_tasks` 中,对快照填充任务排序,确保含 `bind-type` 的 def 在快照阶段也优先处理,同时按 (ns, def) 字典序排序保证完全确定性。
|
|
26
|
+
|
|
27
|
+
## 已修改文件
|
|
28
|
+
|
|
29
|
+
- `src/lib.rs`: 在 `run_program_with_docs` 中调用新增的 `precompile_bind_type_defs`
|
|
30
|
+
- `src/runner/preprocess/mod.rs`: 新增 `precompile_bind_type_defs` 公开函数(扫描所有源 def,提前编译含 `bind-type` 的 def)
|
|
31
|
+
- `src/program.rs`:
|
|
32
|
+
- `calcit_contains_bind_type` 改为 `pub fn`(供 preprocess 模块使用)
|
|
33
|
+
- `collect_snapshot_fill_tasks` 中对任务排序(bind-type 优先,其余按字典序)
|
|
34
|
+
|
|
35
|
+
## 验证
|
|
36
|
+
|
|
37
|
+
- `cr js` 在 tiye-index 项目连续 8 次运行,第 1 次(旧输出重新生成)后无任何文件变化
|
|
38
|
+
- `cargo test --lib` 179 tests passed
|
package/lib/calcit.procs.mjs
CHANGED
|
@@ -1961,6 +1961,38 @@ export let _$n_map_$o_common_keys = (a, b) => {
|
|
|
1961
1961
|
throw new Error("expected 2 maps");
|
|
1962
1962
|
}
|
|
1963
1963
|
};
|
|
1964
|
+
/** Single-pass diff: returns [drop-keys, new-diff, common-triples] in two traversals instead of 3+ */
|
|
1965
|
+
export let _$n_map_$o_diff_triple = (a, b) => {
|
|
1966
|
+
if ((a instanceof CalcitMap || a instanceof CalcitSliceMap) && (b instanceof CalcitMap || b instanceof CalcitSliceMap)) {
|
|
1967
|
+
let dropKeys = [];
|
|
1968
|
+
let commonTriples = [];
|
|
1969
|
+
// One pass over a: split into drop-keys and common-triples
|
|
1970
|
+
let aKeys = a.keysArray();
|
|
1971
|
+
for (let i = 0; i < aKeys.length; i++) {
|
|
1972
|
+
let k = aKeys[i];
|
|
1973
|
+
if (b.contains(k)) {
|
|
1974
|
+
commonTriples.push(new CalcitSliceList([k, a.get(k), b.get(k)]));
|
|
1975
|
+
}
|
|
1976
|
+
else {
|
|
1977
|
+
dropKeys.push(k);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
// One pass over b: collect entries not in a
|
|
1981
|
+
let newDiffPairs = [];
|
|
1982
|
+
let bKeys = b.keysArray();
|
|
1983
|
+
for (let i = 0; i < bKeys.length; i++) {
|
|
1984
|
+
let k = bKeys[i];
|
|
1985
|
+
if (!a.contains(k)) {
|
|
1986
|
+
newDiffPairs.push(k);
|
|
1987
|
+
newDiffPairs.push(b.get(k));
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
return new CalcitSliceList([new CalcitSet(dropKeys), new CalcitSliceMap(newDiffPairs), new CalcitSliceList(commonTriples)]);
|
|
1991
|
+
}
|
|
1992
|
+
else {
|
|
1993
|
+
throw new Error("&map:diff-triple expected 2 maps");
|
|
1994
|
+
}
|
|
1995
|
+
};
|
|
1964
1996
|
export let bit_shr = (base, step) => {
|
|
1965
1997
|
return base >> step;
|
|
1966
1998
|
};
|
package/lib/package.json
CHANGED
package/package.json
CHANGED
|
@@ -36,12 +36,13 @@ sum-point $ %{} Point (:x 10) (:y 20)
|
|
|
36
36
|
|
|
37
37
|
预处理器根据函数参数的 schema 类型标注,自动将简写形式改写为完整形式:
|
|
38
38
|
|
|
39
|
-
| 简写
|
|
40
|
-
|
|
39
|
+
| 简写 | 改写为 | 触发条件 |
|
|
40
|
+
| ------------------ | ------------------------- | ------------------------ |
|
|
41
41
|
| `{} (:x 1) (:y 2)` | `%{} Point (:x 1) (:y 2)` | 参数类型为 struct/record |
|
|
42
|
-
| `:: :ok`
|
|
42
|
+
| `:: :ok` | `%:: Result0 :ok` | 参数类型为 enum |
|
|
43
43
|
|
|
44
44
|
改写后的 AST 能正常参与:
|
|
45
|
+
|
|
45
46
|
- 运行时类型验证(field 校验、variant 校验)
|
|
46
47
|
- 预处理阶段类型检查(`check_user_fn_arg_types`)
|
|
47
48
|
- JS codegen(通过 Import 引用而非内联 Struct/Enum 值)
|
|
@@ -75,12 +76,15 @@ sum-point $ %{} Point (:x 10) (:y 20)
|
|
|
75
76
|
defstruct Point (:x :number) (:y :number)
|
|
76
77
|
|
|
77
78
|
defn sum-point (p)
|
|
78
|
-
:: :fn $ {} (:return :number)
|
|
79
|
-
:args $ [] 'app.main/Point
|
|
80
79
|
&+ (:x p) (:y p)
|
|
81
80
|
|
|
81
|
+
;; :schema 中标注参数类型
|
|
82
|
+
:: :fn $ {} (:return :number)
|
|
83
|
+
:args $ [] 'app.main/Point
|
|
84
|
+
|
|
82
85
|
;; 简写
|
|
83
86
|
sum-point $ {} (:x 10) (:y 20)
|
|
87
|
+
|
|
84
88
|
;; 预处理改写为
|
|
85
89
|
sum-point $ %{} Point (:x 10) (:y 20)
|
|
86
90
|
```
|
|
@@ -109,10 +113,12 @@ sum-point $ %{} Point (:x 10) (:y 20)
|
|
|
109
113
|
defenum Result0 (:err :string) (:ok)
|
|
110
114
|
|
|
111
115
|
defn takes-result (r)
|
|
112
|
-
:: :fn $ {} (:return :dynamic)
|
|
113
|
-
:args $ [] 'app.main/Result0
|
|
114
116
|
tag-match r ((:ok) :ok) ((:err msg) msg) $ _ :unknown
|
|
115
117
|
|
|
118
|
+
;; :schema 中标注参数类型
|
|
119
|
+
:: :fn $ {} (:return :dynamic)
|
|
120
|
+
:args $ [] 'app.main/Result0
|
|
121
|
+
|
|
116
122
|
;; 简写
|
|
117
123
|
takes-result $ :: :ok
|
|
118
124
|
;; 预处理改写为
|
|
@@ -130,28 +136,28 @@ takes-result $ %:: Result0 :err |error-msg
|
|
|
130
136
|
|
|
131
137
|
在 `CalcitTypeAnnotation` 上新增方法:
|
|
132
138
|
|
|
133
|
-
| 方法
|
|
134
|
-
|
|
139
|
+
| 方法 | 用途 |
|
|
140
|
+
| ------------------------------ | ----------------------------------------- |
|
|
135
141
|
| `resolve_to_struct_with_ref()` | 解析 struct + 可选 (ns, def) 路径(已有) |
|
|
136
|
-
| `resolve_to_enum_with_ref()`
|
|
142
|
+
| `resolve_to_enum_with_ref()` | 解析 enum + 可选 (ns, def) 路径(新增) |
|
|
137
143
|
|
|
138
144
|
两者都处理 `Struct/Record`↔`Enum/Tuple` 直接值、`TypeRef("ns/def")` 程序查找、以及 `Optional(inner)` 解包。
|
|
139
145
|
|
|
140
146
|
底层依赖:
|
|
141
147
|
|
|
142
|
-
| 函数
|
|
143
|
-
|
|
148
|
+
| 函数 | 用途 |
|
|
149
|
+
| -------------------------------------- | -------------------------------------------- |
|
|
144
150
|
| `resolve_struct_from_program(ns, def)` | 从 program registry 查找 struct 定义(已有) |
|
|
145
|
-
| `resolve_enum_from_program(ns, def)`
|
|
151
|
+
| `resolve_enum_from_program(ns, def)` | 从 program registry 查找 enum 定义(新增) |
|
|
146
152
|
|
|
147
153
|
### 4.2 改写函数
|
|
148
154
|
|
|
149
|
-
| 函数
|
|
150
|
-
|
|
151
|
-
| `try_rewrite_map_args_to_records()`
|
|
152
|
-
| `try_rewrite_single_map_to_record()`
|
|
153
|
-
| `try_rewrite_tuple_args_to_enum_tuples()`
|
|
154
|
-
| `try_rewrite_single_tuple_to_enum_tuple()` | 单个参数的 tuple→enum-tuple 改写(新增)
|
|
155
|
+
| 函数 | 用途 |
|
|
156
|
+
| ------------------------------------------ | --------------------------------------------- |
|
|
157
|
+
| `try_rewrite_map_args_to_records()` | 遍历参数列表,对 map 字面量尝试改写(已有) |
|
|
158
|
+
| `try_rewrite_single_map_to_record()` | 单个参数的 map→record 改写(已有) |
|
|
159
|
+
| `try_rewrite_tuple_args_to_enum_tuples()` | 遍历参数列表,对 tuple 字面量尝试改写(新增) |
|
|
160
|
+
| `try_rewrite_single_tuple_to_enum_tuple()` | 单个参数的 tuple→enum-tuple 改写(新增) |
|
|
155
161
|
|
|
156
162
|
### 4.3 集成点
|
|
157
163
|
|
|
@@ -169,18 +175,19 @@ takes-result $ %:: Result0 :err |error-msg
|
|
|
169
175
|
改写时若从 `TypeRef` 解析出 (ns, def) 路径,会构造 `Calcit::Import` 而非内联的 `Calcit::Struct`/`Calcit::Enum`。这是因为 JS codegen 不支持直接 emit `Struct`/`Enum` 字面量——它需要一个变量引用。
|
|
170
176
|
|
|
171
177
|
Import 策略:
|
|
178
|
+
|
|
172
179
|
- 同 namespace → `ImportInfo::SameFile`
|
|
173
180
|
- 跨 namespace → `ImportInfo::NsReferDef`
|
|
174
181
|
|
|
175
182
|
### 修改的文件
|
|
176
183
|
|
|
177
|
-
| 文件
|
|
178
|
-
|
|
179
|
-
| `src/calcit/type_annotation.rs` | `resolve_to_enum_with_ref()`, `resolve_enum_from_program()`
|
|
180
|
-
| `src/runner/preprocess.rs`
|
|
181
|
-
| `docs/features/enums.md`
|
|
182
|
-
| `docs/features/records.md`
|
|
183
|
-
| `calcit/test-enum.cirru`
|
|
184
|
+
| 文件 | 变更 |
|
|
185
|
+
| ------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
186
|
+
| `src/calcit/type_annotation.rs` | `resolve_to_enum_with_ref()`, `resolve_enum_from_program()` |
|
|
187
|
+
| `src/runner/preprocess.rs` | `try_rewrite_tuple_args_to_enum_tuples()`, `try_rewrite_single_tuple_to_enum_tuple()`,集成到 Fn 分支 |
|
|
188
|
+
| `docs/features/enums.md` | 新增 "Automatic Tuple-to-Enum Rewrite" 章节 |
|
|
189
|
+
| `docs/features/records.md` | "Automatic Map-to-Record Rewrite" 章节(已有) |
|
|
190
|
+
| `calcit/test-enum.cirru` | 新增 `test-tuple-to-enum` 测试 |
|
|
184
191
|
|
|
185
192
|
## 5. 测试覆盖
|
|
186
193
|
|
package/ts-src/calcit.procs.mts
CHANGED
|
@@ -2075,6 +2075,40 @@ export let _$n_map_$o_common_keys = (a: CalcitValue, b: CalcitValue): CalcitSet
|
|
|
2075
2075
|
}
|
|
2076
2076
|
};
|
|
2077
2077
|
|
|
2078
|
+
/** Single-pass diff: returns [drop-keys, new-diff, common-triples] in two traversals instead of 3+ */
|
|
2079
|
+
export let _$n_map_$o_diff_triple = (a: CalcitValue, b: CalcitValue): CalcitSliceList => {
|
|
2080
|
+
if ((a instanceof CalcitMap || a instanceof CalcitSliceMap) && (b instanceof CalcitMap || b instanceof CalcitSliceMap)) {
|
|
2081
|
+
let dropKeys: CalcitValue[] = [];
|
|
2082
|
+
let commonTriples: CalcitValue[] = [];
|
|
2083
|
+
|
|
2084
|
+
// One pass over a: split into drop-keys and common-triples
|
|
2085
|
+
let aKeys = a.keysArray();
|
|
2086
|
+
for (let i = 0; i < aKeys.length; i++) {
|
|
2087
|
+
let k = aKeys[i];
|
|
2088
|
+
if (b.contains(k)) {
|
|
2089
|
+
commonTriples.push(new CalcitSliceList([k, a.get(k), b.get(k)]));
|
|
2090
|
+
} else {
|
|
2091
|
+
dropKeys.push(k);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
// One pass over b: collect entries not in a
|
|
2096
|
+
let newDiffPairs: CalcitValue[] = [];
|
|
2097
|
+
let bKeys = b.keysArray();
|
|
2098
|
+
for (let i = 0; i < bKeys.length; i++) {
|
|
2099
|
+
let k = bKeys[i];
|
|
2100
|
+
if (!a.contains(k)) {
|
|
2101
|
+
newDiffPairs.push(k);
|
|
2102
|
+
newDiffPairs.push(b.get(k));
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
return new CalcitSliceList([new CalcitSet(dropKeys), new CalcitSliceMap(newDiffPairs), new CalcitSliceList(commonTriples)]);
|
|
2107
|
+
} else {
|
|
2108
|
+
throw new Error("&map:diff-triple expected 2 maps");
|
|
2109
|
+
}
|
|
2110
|
+
};
|
|
2111
|
+
|
|
2078
2112
|
export let bit_shr = (base: number, step: number): number => {
|
|
2079
2113
|
return base >> step;
|
|
2080
2114
|
};
|