@calcit/procs 0.12.17 → 0.12.18
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 +0 -0
- package/editing-history/{202604132309-typeslot-enum-compile-safety.md → 2026-0413-2309-typeslot-enum-compile-safety.md} +5 -0
- package/editing-history/2026-0414-1500-loose-record-syntax.md +32 -0
- package/editing-history/2026-0414-2259-bidir-type-check-refactor.md +63 -0
- package/editing-history/2026-0415-0120-match-syntax-exhaustiveness.md +20 -0
- package/lib/js-record.mjs +23 -1
- package/lib/package.json +2 -2
- package/package.json +2 -2
- package/ts-src/js-record.mts +23 -0
- /package/editing-history/{202504121453-map-to-record-rewrite.md → 2025-0412-1453-map-to-record-rewrite.md} +0 -0
- /package/editing-history/{202604121524-js-codegen-map-to-record.md → 2026-0412-1524-js-codegen-map-to-record.md} +0 -0
- /package/editing-history/{202604121549-map-to-record-field-validation.md → 2026-0412-1549-map-to-record-field-validation.md} +0 -0
- /package/editing-history/{202604131600-tuple-to-enum-rewrite.md → 2026-0413-1600-tuple-to-enum-rewrite.md} +0 -0
package/.yarn/install-state.gz
CHANGED
|
Binary file
|
|
@@ -7,22 +7,27 @@
|
|
|
7
7
|
## 修改的文件
|
|
8
8
|
|
|
9
9
|
### `src/calcit/type_annotation.rs`
|
|
10
|
+
|
|
10
11
|
- `resolve_to_enum_with_ref()`: 新增 `TypeSlot` 分支,通过 `resolve_type_slot()` 解析绑定类型后委托
|
|
11
12
|
- `matches_with_bindings()`: 双向 `Tuple/Enum` 匹配 — 原先只有 `(Tuple, Enum)` 方向,TypeSlot 解析后顺序会翻转,新增 `(Enum, Tuple)` 方向
|
|
12
13
|
|
|
13
14
|
### `src/calcit.rs`
|
|
15
|
+
|
|
14
16
|
- 将 `resolve_type_slot` 加入 `pub use` 导出列表
|
|
15
17
|
|
|
16
18
|
### `src/runner/preprocess.rs`
|
|
19
|
+
|
|
17
20
|
- `resolve_enum_value()`: 在 `as_struct()` 调用前增加 TypeSlot 解析(避免 `&CalcitStruct` 生命周期问题)
|
|
18
21
|
- 新增 `try_rewrite_local_fn_tuple_args_to_enum_tuples()`: 对本地函数调用(如回调 `d!`)也执行 tuple→enum 自动重写
|
|
19
22
|
- 主预处理 `Calcit::Local` 分支: 增加 local fn 调用的重写+类型检查(clone 避免借用冲突)
|
|
20
23
|
- `try_rewrite_single_tuple_to_enum_tuple()`: 增加变体验证 — 重写后立即检查 tag 是否是 enum 的合法 variant,不合法则发出警告
|
|
21
24
|
|
|
22
25
|
### `src/bin/cr_tests/type_fail.rs`
|
|
26
|
+
|
|
23
27
|
- 新增 `type_fail_type_slot_enum_invalid_variant` 测试,验证 TypeSlot 绑定的 enum 在自动重写路径下能正确检测不存在的 variant
|
|
24
28
|
|
|
25
29
|
### `calcit/type-fail/type-slot-enum-invalid-variant.cirru`
|
|
30
|
+
|
|
26
31
|
- 新增测试 fixture:声明 `defenum Action`、通过 TypeSlot 绑定、调用 `takes-action $ :: :nonexistent |hello`,期望产生 variant 不存在警告
|
|
27
32
|
|
|
28
33
|
## 知识点
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 2026-04-14 15:00 — `?{}` 松散 Record 语法
|
|
2
|
+
|
|
3
|
+
## 概要
|
|
4
|
+
|
|
5
|
+
新增 `?{}` 松散 record 语法,允许不声明 struct 就创建 record,与 `::` 无类型 tuple 对称。
|
|
6
|
+
在函数参数有 struct 类型标注时,预处理阶段自动改写为 `%{} StructDef ...`。
|
|
7
|
+
|
|
8
|
+
## 改动文件
|
|
9
|
+
|
|
10
|
+
| 文件 | 变更 |
|
|
11
|
+
|------|------|
|
|
12
|
+
| `src/calcit/proc_name.rs` | 新增 `NativeLooseRecord` proc 变体,`?{}` 序列化,类型签名 |
|
|
13
|
+
| `src/data/cirru.rs` | `"?{}"` token 解析为 `Calcit::Proc(NativeLooseRecord)` |
|
|
14
|
+
| `src/calcit/record.rs` | `LOOSE_RECORD_NAME`、`is_loose()`、`from_loose_pairs()` |
|
|
15
|
+
| `src/builtins/records.rs` | `call_loose_record()` 运行时:验证 tag 键、排序、排重 |
|
|
16
|
+
| `src/builtins.rs` | 分发 `NativeLooseRecord => call_loose_record(args)` |
|
|
17
|
+
| `src/calcit.rs` | Display: 松散 record 显示为 `(?{} ...)` |
|
|
18
|
+
| `src/runner/preprocess.rs` | `try_rewrite_loose_record_args_to_struct_records`、类型推断、skip core arg check |
|
|
19
|
+
| `ts-src/js-record.mts` | JS 运行时 `_$q__$M_` 函数 |
|
|
20
|
+
| `calcit/test-record.cirru` | `test-loose-record-rewrite` 集成测试 |
|
|
21
|
+
| `docs/features/records.md` | 文档:松散 record 语法、自动改写、类型对称表 |
|
|
22
|
+
|
|
23
|
+
## 性能优化
|
|
24
|
+
|
|
25
|
+
- **预处理改写链合并重建**:三步改写(map→record、loose→struct、tuple→enum)原先每步都重建 `ys` (CalcitList),优化为仅在有改写时统一重建一次,减少 CalcitList push 操作。
|
|
26
|
+
- **JS 排序比较器修正**:将重复字段检测从 sort comparator 中移出(comparator 内 throw 违反排序契约),改为排序后线性扫描相邻元素,行为更可靠。
|
|
27
|
+
|
|
28
|
+
## 知识点
|
|
29
|
+
|
|
30
|
+
- `EdnTag::cmp` 按全局 tag 池分配顺序排序(整数 index),**不是**字典序。record 字段排序必须用 `ref_str().cmp()` 保持字母序。
|
|
31
|
+
- CalcitList 基于 persistent ternary tree,`.clone()` 是 O(1) 引用计数操作。
|
|
32
|
+
- Sort comparator 不应抛异常——不同排序算法对异常行为未定义。
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# 202604142259 — Bidirectional Type Checking Architecture Refactoring
|
|
2
|
+
|
|
3
|
+
## 概要
|
|
4
|
+
|
|
5
|
+
将 `src/runner/preprocess.rs`(6199 行单体文件)拆分为模块化目录结构,
|
|
6
|
+
按双向类型检查(bidirectional type checking)的概念分离关注点。
|
|
7
|
+
|
|
8
|
+
## 改动详情
|
|
9
|
+
|
|
10
|
+
### 模块拆分
|
|
11
|
+
|
|
12
|
+
`preprocess.rs` → `preprocess/` 目录模块:
|
|
13
|
+
|
|
14
|
+
| 模块 | 行数 | 职责 |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `mod.rs` | ~4744 | 预处理核心:符号解析、宏展开、作用域管理、特殊语法 |
|
|
17
|
+
| `type_inference.rs` | ~693 | 类型合成(synthesis):自下而上推导表达式类型 |
|
|
18
|
+
| `type_checking.rs` | ~402 | 类型检查(checking):自上而下验证参数类型 |
|
|
19
|
+
| `type_rewriting.rs` | ~398 | 类型定向重写:根据期望类型重写 AST 字面量 |
|
|
20
|
+
|
|
21
|
+
### 代码去重
|
|
22
|
+
|
|
23
|
+
1. **统一参数重写循环** (`rewrite_args_by_expected_type`):
|
|
24
|
+
- 原先 4 个 `try_rewrite_*_args_to_*` 函数各自包含相同的外层循环(~30 LOC × 4)
|
|
25
|
+
- 提取为泛型 `rewrite_args_by_expected_type<F>` 函数,4 个入口各只保留闭包调用
|
|
26
|
+
|
|
27
|
+
2. **统一参数类型检查循环** (`check_arg_types_loop`):
|
|
28
|
+
- 原先 `check_local_fn_call_arg_types` 和 `check_user_fn_arg_types` 共享相同的
|
|
29
|
+
zip → resolve_type_value → matches_with_bindings → gen_check_warning 模式
|
|
30
|
+
- 提取为泛型 `check_arg_types_loop<F>` 函数,包含 variadic 处理和 spread 跳过逻辑
|
|
31
|
+
|
|
32
|
+
3. **统一引用节点构建** (`build_struct_ref_node` / `build_enum_ref_node`):
|
|
33
|
+
- 原先 map→record 和 loose-record→struct 各自复制 Import vs Struct/Enum 分支
|
|
34
|
+
- 提取为共享的 `build_struct_ref_node` 和 `build_enum_ref_node` 函数
|
|
35
|
+
|
|
36
|
+
### Proc 推导提取
|
|
37
|
+
|
|
38
|
+
- `infer_type_from_expr` 中的 `Calcit::Proc` 分支(~100 行 match arms)
|
|
39
|
+
提取为独立的 `infer_proc_call_return_type` 函数,提高可读性
|
|
40
|
+
|
|
41
|
+
## 双向类型检查映射
|
|
42
|
+
|
|
43
|
+
参照 Dunfield & Krishnaswami 的框架:
|
|
44
|
+
|
|
45
|
+
| 方向 | 模块 | 入口函数 |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| **Synthesis** (⇒) | `type_inference` | `infer_type_from_expr`, `resolve_type_value` |
|
|
48
|
+
| **Checking** (⇐) | `type_checking` | `check_*_arg_types`, `check_function_return_type` |
|
|
49
|
+
| **Rewriting** (结构适配) | `type_rewriting` | `try_rewrite_*` |
|
|
50
|
+
| **Context** (线程局部) | `mod.rs` | `EXPECTED_FN_TYPE`, `EXPECTED_STRUCT_TYPE` |
|
|
51
|
+
|
|
52
|
+
## 为后续 match 语法扩展铺路
|
|
53
|
+
|
|
54
|
+
- `type_checking.rs` 的 `check_arg_types_loop` 模式可直接复用于 match 分支检查
|
|
55
|
+
- `type_inference.rs` 中已有 `infer_if_return_type` 作为分支合并的模板
|
|
56
|
+
- 未来只需在相应模块中添加新函数,无需修改 `mod.rs` 的核心调度
|
|
57
|
+
|
|
58
|
+
## 验证
|
|
59
|
+
|
|
60
|
+
- `cargo check --lib` ✅ 零警告
|
|
61
|
+
- `cargo clippy --lib -- -D warnings` ✅ 零警告
|
|
62
|
+
- `cargo fmt` ✅ 格式一致
|
|
63
|
+
- 链接器问题(`ld: library 'System' not found`)是环境问题,非代码变更引起
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# match syntax with exhaustiveness checking
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Added native `match` syntax for enum pattern matching with compile-time exhaustiveness detection,
|
|
6
|
+
as a safer alternative to `tag-match` macro. See `drafts/match-syntax-rfc.md` for full design.
|
|
7
|
+
|
|
8
|
+
## Files modified
|
|
9
|
+
|
|
10
|
+
- `src/calcit/syntax_name.rs` — `CalcitSyntax::Match`
|
|
11
|
+
- `src/builtins/syntax.rs` — `syntax_match()` runtime
|
|
12
|
+
- `src/builtins.rs` — dispatch entry
|
|
13
|
+
- `src/runner/preprocess/mod.rs` — `preprocess_match()` with exhaustiveness
|
|
14
|
+
- `src/codegen/emit_js.rs` — `gen_match_code()` JS output
|
|
15
|
+
- `calcit/test-enum.cirru` — test cases
|
|
16
|
+
|
|
17
|
+
## Key takeaway
|
|
18
|
+
|
|
19
|
+
Cirru indentation creates pair-based `(pattern body)` children — same format as `tag-match`.
|
|
20
|
+
Flat alternating format doesn't survive Cirru serialization round-trips.
|
package/lib/js-record.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CalcitImpl } from "./js-impl.mjs";
|
|
2
|
-
import { castTag, toString, findInFields } from "./calcit-data.mjs";
|
|
2
|
+
import { newTag, castTag, toString, getStringName, findInFields } from "./calcit-data.mjs";
|
|
3
3
|
import { CalcitMap, CalcitSliceMap } from "./js-map.mjs";
|
|
4
4
|
import { CalcitStruct } from "./js-struct.mjs";
|
|
5
5
|
export class CalcitRecord {
|
|
@@ -116,6 +116,28 @@ export let new_impl_record = (impl, name, ...fields) => {
|
|
|
116
116
|
let structRef = new CalcitStruct(nameTag, fieldNames, new Array(fieldNames.length).fill(null), [impl]);
|
|
117
117
|
return new CalcitRecord(nameTag, fieldNames, undefined, structRef);
|
|
118
118
|
};
|
|
119
|
+
/** Loose record: `?{} :field1 val1 :field2 val2` – record without a declared struct.
|
|
120
|
+
* Fields are sorted by tag index. The record name is "?" (sentinel). */
|
|
121
|
+
export let _$q__$M_ = (...xs) => {
|
|
122
|
+
if (xs.length % 2 !== 0) {
|
|
123
|
+
throw new Error("?{} expected pairs of :field value");
|
|
124
|
+
}
|
|
125
|
+
let pairs = [];
|
|
126
|
+
for (let i = 0; i < xs.length; i += 2) {
|
|
127
|
+
pairs.push([castTag(xs[i]), xs[i + 1]]);
|
|
128
|
+
}
|
|
129
|
+
pairs.sort((a, b) => a[0].idx - b[0].idx);
|
|
130
|
+
// Check for duplicate fields after sorting
|
|
131
|
+
for (let i = 1; i < pairs.length; i++) {
|
|
132
|
+
if (pairs[i][0].idx === pairs[i - 1][0].idx) {
|
|
133
|
+
throw new Error(`?{} received duplicate field: :${getStringName(pairs[i][0])}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
let fieldNames = pairs.map((p) => p[0]);
|
|
137
|
+
let values = pairs.map((p) => p[1]);
|
|
138
|
+
let looseTag = newTag("?");
|
|
139
|
+
return new CalcitRecord(looseTag, fieldNames, values);
|
|
140
|
+
};
|
|
119
141
|
export let fieldsEqual = (xs, ys) => {
|
|
120
142
|
if (xs === ys) {
|
|
121
143
|
return true; // special case, referential equal
|
package/lib/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calcit/procs",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.18",
|
|
4
4
|
"main": "./lib/calcit.procs.mjs",
|
|
5
5
|
"devDependencies": {
|
|
6
6
|
"@types/node": "^25.0.9",
|
|
7
|
-
"typescript": "^
|
|
7
|
+
"typescript": "^6.0.2"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"compile": "rm -rf lib && tsc",
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calcit/procs",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.18",
|
|
4
4
|
"main": "./lib/calcit.procs.mjs",
|
|
5
5
|
"devDependencies": {
|
|
6
6
|
"@types/node": "^25.0.9",
|
|
7
|
-
"typescript": "^
|
|
7
|
+
"typescript": "^6.0.2"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"compile": "rm -rf lib && tsc",
|
package/ts-src/js-record.mts
CHANGED
|
@@ -119,6 +119,29 @@ export let new_impl_record = (impl: CalcitImpl, name: CalcitValue, ...fields: Ar
|
|
|
119
119
|
return new CalcitRecord(nameTag, fieldNames, undefined, structRef);
|
|
120
120
|
};
|
|
121
121
|
|
|
122
|
+
/** Loose record: `?{} :field1 val1 :field2 val2` – record without a declared struct.
|
|
123
|
+
* Fields are sorted by tag index. The record name is "?" (sentinel). */
|
|
124
|
+
export let _$q__$M_ = (...xs: Array<CalcitValue>): CalcitValue => {
|
|
125
|
+
if (xs.length % 2 !== 0) {
|
|
126
|
+
throw new Error("?{} expected pairs of :field value");
|
|
127
|
+
}
|
|
128
|
+
let pairs: Array<[CalcitTag, CalcitValue]> = [];
|
|
129
|
+
for (let i = 0; i < xs.length; i += 2) {
|
|
130
|
+
pairs.push([castTag(xs[i]), xs[i + 1]]);
|
|
131
|
+
}
|
|
132
|
+
pairs.sort((a, b) => a[0].idx - b[0].idx);
|
|
133
|
+
// Check for duplicate fields after sorting
|
|
134
|
+
for (let i = 1; i < pairs.length; i++) {
|
|
135
|
+
if (pairs[i][0].idx === pairs[i - 1][0].idx) {
|
|
136
|
+
throw new Error(`?{} received duplicate field: :${getStringName(pairs[i][0])}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
let fieldNames = pairs.map((p) => p[0]);
|
|
140
|
+
let values = pairs.map((p) => p[1]);
|
|
141
|
+
let looseTag = newTag("?");
|
|
142
|
+
return new CalcitRecord(looseTag, fieldNames, values);
|
|
143
|
+
};
|
|
144
|
+
|
|
122
145
|
export let fieldsEqual = (xs: Array<CalcitTag>, ys: Array<CalcitTag>): boolean => {
|
|
123
146
|
if (xs === ys) {
|
|
124
147
|
return true; // special case, referential equal
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|