@calcit/procs 0.12.57 → 0.12.59
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/RFCs/02-04-runtime-traits-plan.md +67 -573
- package/editing-history/202608012344-release-main-policy.md +5 -0
- package/editing-history/202608020058-data-api-and-trait-coverage.md +26 -0
- package/history/202608021153-runtime-traits.md +23 -0
- package/history/202608021227-format-markdown-cirru.md +18 -0
- package/history/202608021239-review-trait-followups.md +19 -0
- package/history/202608021251-defimpl-symbol-traits.md +8 -0
- package/history/202608021255-doc-type-symbols.md +6 -0
- package/history/202608021322-postfix-enum-methods.md +12 -0
- package/history/202608021327-postfix-unwrap-examples.md +10 -0
- package/history/202608021507-receiver-first-static-inference.md +21 -0
- package/history/202608021550-review-type-correctness.md +6 -0
- package/history/202608021730-release-0.12.58.md +5 -0
- package/history/202608021922-top-level-nominal-schema.md +6 -0
- package/history/202608021928-release-0.12.59.md +5 -0
- package/lib/calcit.procs.d.mts +1 -0
- package/lib/calcit.procs.mjs +57 -47
- package/lib/package.json +1 -1
- package/package.json +1 -1
- package/ts-src/calcit.procs.mts +62 -44
package/.yarn/install-state.gz
CHANGED
|
Binary file
|
|
@@ -1,613 +1,107 @@
|
|
|
1
|
-
# Runtime Traits for Calcit
|
|
1
|
+
# Runtime Traits for Calcit
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> 状态:已落地的设计基线,更新于 2026-08-02。用户语法与示例以 [`docs/features/traits.md`](../docs/features/traits.md) 为准;本文件记录实现边界与后续工作。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 目标
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Calcit 从动态原型式方法分派演进到 trait 模型,但不把语言变成复杂的全静态类型系统。当前设计追求三点:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
1. 能力约束是 nominal 的,能从 impl 来源推导,不依赖“恰好有同名方法”。
|
|
10
|
+
2. 普通方法调用仍保持轻量、可组合,旧代码可以逐步迁移。
|
|
11
|
+
3. native、JS 的语义一致;WASM 只承担内部 codegen 验证,不新增完整 trait runtime。
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
## 正交的三个层次
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
### Trait definition
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
| --------- | ------------------------ | -------------------- | ------------ | --------- |
|
|
17
|
-
| `Show` | `(show x) -> :string` | 人类可读的字符串表示 | `Show` | `Display` |
|
|
18
|
-
| `Inspect` | `(inspect x) -> :string` | 调试用的详细表示 | `Show` | `Debug` |
|
|
17
|
+
`deftrait` 产生 trait value,包含方法名与方法类型。运行时每次求值得到新的 nominal identity;克隆保留 identity,重新加载定义会产生新 identity,因此旧 impl 不会自动满足新 trait。
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
Snapshot schema 暂时只保存 symbol 形式的 trait reference。预处理在 trait value 已求值时使用 nominal metadata;只有尚未求值的 schema placeholder 才按定义结构或 bare name 回退。把 namespace-qualified trait id 持久化到 schema 是后续工作。
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
### Trait impl
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
| --------- | ----------------------- | ---------------------- | ------------ | ----------- |
|
|
26
|
-
| `Eq` | `(eq? x y) -> :bool` | 相等性判断 | `Eq` | `PartialEq` |
|
|
27
|
-
| `Compare` | `(compare x y) -> :tag` | 返回 `:lt`/`:eq`/`:gt` | `Ord` | `Ord` |
|
|
23
|
+
`defimpl ImplName Trait ...` 在第二个参数是具体 trait value 时产生 nominal impl:
|
|
28
24
|
|
|
29
|
-
|
|
25
|
+
- impl origin 是该 trait value;
|
|
26
|
+
- 方法集合必须与 trait 声明完全一致;
|
|
27
|
+
- 每个方法值必须 callable;
|
|
28
|
+
- native 能取得函数签名时检查其与 trait method schema 是否匹配。
|
|
30
29
|
|
|
31
|
-
- `
|
|
32
|
-
- `Compare`: `number`, `string`, `tag`, `list`(字典序)
|
|
30
|
+
`assert-traits`、`:where` 和 `&trait-call` 都只接受这种 impl 作为能力证据。不同 impl 的方法不会被拼成一个虚构实现。
|
|
33
31
|
|
|
34
|
-
|
|
32
|
+
### Inherent method bag
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
| ---------- | ---------------------- | --------- | ------------ | --------- |
|
|
38
|
-
| `Add` | `(add x y) -> 'T` | 加法/拼接 | `Num (+)` | `Add` |
|
|
39
|
-
| `Subtract` | `(subtract x y) -> 'T` | 减法 | `Num (-)` | `Sub` |
|
|
40
|
-
| `Multiply` | `(multiply x y) -> 'T` | 乘法 | `Num (*)` | `Mul` |
|
|
41
|
-
| `Divide` | `(divide x y) -> 'T` | 除法 | `Fractional` | `Div` |
|
|
42
|
-
| `Negate` | `(negate x) -> 'T` | 取负 | `Num negate` | `Neg` |
|
|
34
|
+
历史写法允许把 tag 传给 `defimpl`。它继续产生 originless method bag,并参与 `.method` 查找,以保证旧项目可运行;但它不是 trait impl,不能满足能力约束。`cr edit format` 对此给出非阻断迁移告警。
|
|
43
35
|
|
|
44
|
-
|
|
36
|
+
这个边界取代旧的“class/prototype”概念:底层仍复用有序 impl record 作为方法表,但语言层不再把方法存在性当作 trait 身份。
|
|
45
37
|
|
|
46
|
-
|
|
47
|
-
- `string`: `Add`(字符串拼接)
|
|
48
|
-
- `list`: `Add`(列表连接)
|
|
38
|
+
## 分派规则
|
|
49
39
|
|
|
50
|
-
|
|
40
|
+
### `.method`
|
|
51
41
|
|
|
52
|
-
|
|
53
|
-
| ---------- | ----------------------------- | ------------- | ------------ | ------------ |
|
|
54
|
-
| `Len` | `(len x) -> :number` | 长度/大小 | `length` | `len()` |
|
|
55
|
-
| `Empty` | `(empty? x) -> :bool` | 是否为空 | `null` | `is_empty()` |
|
|
56
|
-
| `Contains` | `(contains? x item) -> :bool` | 包含检查 | `elem` | `contains()` |
|
|
57
|
-
| `Get` | `(get x key) -> 'V` | 按键/索引取值 | `lookup` | `get()` |
|
|
42
|
+
普通 `.method` 是按方法名查找,适合日常动态调用:
|
|
58
43
|
|
|
59
|
-
|
|
44
|
+
- 用户 struct/enum 附加的 impl:从后向前,last-wins;
|
|
45
|
+
- core builtin impl list:从前向后,first-wins,保持内建方法优先级兼容性。
|
|
60
46
|
|
|
61
|
-
|
|
47
|
+
它可以命中 nominal trait impl 或 inherent method bag。相同方法名存在多个候选时,顺序决定结果。
|
|
62
48
|
|
|
63
|
-
|
|
64
|
-
| ---------- | ----------------------- | ---------- | ------------ | ---------------- | ------------------------------------ |
|
|
65
|
-
| `Foldable` | `(fold x init f) -> 'A` | 折叠/归约 | `Foldable` | `Iterator::fold` | ✅ 命名来自 Haskell |
|
|
66
|
-
| `Functor` | `(fmap x f) -> 'T` | 保结构映射 | `Functor` | `Iterator::map` | 🎯 **改名建议**:用 Haskell 正统命名 |
|
|
67
|
-
| `Iterable` | `(iter x) -> iterator` | 获取迭代器 | - | `IntoIterator` | 统一迭代抽象 |
|
|
49
|
+
### `&trait-call`
|
|
68
50
|
|
|
69
|
-
|
|
51
|
+
`&trait-call Trait :method receiver ...` 先按 trait nominal identity 选择单个 impl,再从该 impl 取方法。它用于消歧义和表达“调用哪个能力”是契约的一部分。
|
|
70
52
|
|
|
71
|
-
-
|
|
72
|
-
- 或统一为 `Collection` trait,包含 `map`, `filter`, `fold` 全套操作
|
|
73
|
-
- 类似 Rust 的 `Iterator` 或 JavaScript 的 `Array` 方法
|
|
53
|
+
### `assert-traits` 与 `:where`
|
|
74
54
|
|
|
75
|
-
|
|
55
|
+
- `assert-traits` 在 preprocess 提供 local type hint,在 runtime 查找 origin 与目标 trait 一致的单个完整 impl。
|
|
56
|
+
- `:where` 使用同一能力关系做 generic substitution/checking。
|
|
57
|
+
- 内建类型使用 `calcit.core` 中带真实 origin 的 impl list;初始化期静态检查有一份很小的 bootstrap capability map,避免求值顺序改变告警结果。
|
|
76
58
|
|
|
77
|
-
|
|
59
|
+
## 内建能力
|
|
78
60
|
|
|
79
|
-
|
|
80
|
-
- 动态语言无法静态检查这些定律,过度抽象可能适得其反
|
|
81
|
-
- 推荐:借鉴概念,但保持实用导向(用户更关心"能不能 map",而非"是不是 Functor")
|
|
61
|
+
当前主要 core traits:
|
|
82
62
|
|
|
83
|
-
|
|
63
|
+
| Trait | 方法 | 主要内建实现 |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| `Show` | `.show` | nil/bool/tag/symbol/CirruQuote,以及 number/string/list/map/set/fn/record/tuple |
|
|
66
|
+
| `Eq` | `.eq?` | 与 `Show` 对齐的 scalar/collection/record/tuple 类别 |
|
|
67
|
+
| `Add` | `.add` | number/string/list |
|
|
68
|
+
| `Multiply` | `.multiply` | number |
|
|
69
|
+
| `Compare` | `.compare` | number/string |
|
|
70
|
+
| `Len` | `.len` | list/map/set/string |
|
|
71
|
+
| `Mappable` | `.map` | list/map/set,以及 Option/Result 自定义 impl |
|
|
72
|
+
| `Countable` | `.count` | list/map/set/string/record/tuple |
|
|
73
|
+
| `Contains` | `.contains?` | list/map/set/string/record/tuple |
|
|
84
74
|
|
|
85
|
-
|
|
86
|
-
| ------- | --------------------- | ------ | ------------ | --------- |
|
|
87
|
-
| `Hash` | `(hash x) -> :number` | 哈希值 | `Hashable` | `Hash` |
|
|
88
|
-
| `Clone` | `(clone x) -> 'T` | 深拷贝 | - | `Clone` |
|
|
75
|
+
`calcit.internal` 保留不具名的原始方法包,`calcit.core` 在公开 builtin impl list 中通过 `&impl::new Trait method-bag` 将其提升为 nominal impl。scalar literals 共享只含 `Show`/`Eq` 的 impl list。这样不会制造 `calcit.internal -> calcit.core` 的 JS 模块循环,同时 native 宏预处理能看到完整 trait origin。
|
|
89
76
|
|
|
90
|
-
|
|
77
|
+
## 后端约束
|
|
91
78
|
|
|
92
|
-
|
|
79
|
+
### Native
|
|
93
80
|
|
|
94
|
-
|
|
95
|
-
| --------- | ----------------------- | ---------- | ------------ | ----------- |
|
|
96
|
-
| `Default` | `(default T) -> 'T` | 类型默认值 | `Default` | `Default` |
|
|
97
|
-
| `From` | `(from T source) -> 'T` | 类型转换 | - | `From/Into` |
|
|
81
|
+
native 是语义基准,也是宏执行、预处理和签名校验的必经目标。trait runtime identity、impl conformance、`assert-traits` 和 `&trait-call` 都完整执行。
|
|
98
82
|
|
|
99
|
-
|
|
83
|
+
### JavaScript
|
|
100
84
|
|
|
101
|
-
|
|
102
|
-
- `From`: 常见转换如 `number->string`, `list->set`, `map->list`
|
|
85
|
+
JS 是主要业务运行目标。trait identity 使用对象身份;builtin 注册使用完整 impl list;impl 方法集合与 callable 校验和 native 对齐。类型签名的主要校验发生在生成 JS 之前的 native preprocess。
|
|
103
86
|
|
|
104
|
-
###
|
|
87
|
+
### WASM
|
|
105
88
|
|
|
106
|
-
|
|
89
|
+
WASM 是内部验证后端。预处理已消除的 trait metadata 不影响 codegen;若运行路径仍残留 `&impl::new`、struct/enum `impl-traits` 或 `&assert-traits`,codegen 明确失败。这里不实现 JS/native 等价的 runtime trait table,也不允许静默返回 `nil` 掩盖语义缺失。
|
|
107
90
|
|
|
108
|
-
|
|
109
|
-
deftrait Show :show
|
|
110
|
-
deftrait Eq :eq?
|
|
111
|
-
deftrait Compare :compare
|
|
112
|
-
```
|
|
91
|
+
## 已完成的验收口径
|
|
113
92
|
|
|
114
|
-
|
|
93
|
+
- 两个拥有相同方法名的 trait 不能互相满足 `assert-traits`。
|
|
94
|
+
- `&trait-call` 只调用目标 trait 的 impl;只有另一个 trait impl 时必须失败。
|
|
95
|
+
- concrete `defimpl` 拒绝 missing/extra/non-callable 方法,并在可用时拒绝签名不匹配。
|
|
96
|
+
- list 等 builtin 可以通过 `assert-traits` 和 `&trait-call Countable :count` 在 native/JS 一致工作。
|
|
97
|
+
- 方法 introspection 显示 builtin method bag 与 origin-carrying trait impl 的明确分层。
|
|
98
|
+
- legacy tag-based method bag 继续支持 `.method`,同时触发迁移告警。
|
|
115
99
|
|
|
116
|
-
|
|
100
|
+
## 后续工作(控制复杂度)
|
|
117
101
|
|
|
118
|
-
|
|
102
|
+
1. 在 snapshot/schema 中持久化 namespace-qualified trait reference,删除 bare-name 静态回退。
|
|
103
|
+
2. 评估 trait default methods 与 `requires`;只有 native/JS 能共同给出简单、可推导的规则时才开放语法。
|
|
104
|
+
3. 观察 method lookup 成本后再决定是否缓存,不预先引入 vtable/trait-object 层。
|
|
105
|
+
4. 保持 capability 粒度小;优先增加能改善真实 generic API 的 trait,避免照搬 Rust/Haskell 的完整层级。
|
|
119
106
|
|
|
120
|
-
|
|
121
|
-
; 完整示例:为 Point 类型实现 Show 和 Eq trait
|
|
122
|
-
let
|
|
123
|
-
; 1. 定义基础 struct
|
|
124
|
-
Point0 $ defstruct Point (:x :number) (:y :number)
|
|
125
|
-
|
|
126
|
-
ShowTrait $ deftrait Show
|
|
127
|
-
EqTrait $ deftrait Eq
|
|
128
|
-
|
|
129
|
-
; 2. 定义 Show trait 的实现 (record 形式)
|
|
130
|
-
show-impl $ %{} ShowTrait
|
|
131
|
-
:show $ fn (p)
|
|
132
|
-
str "|Point(" (.x p) ", " (.y p) ")"
|
|
133
|
-
|
|
134
|
-
; 3. 定义 Eq trait 的实现
|
|
135
|
-
eq-impl $ %{} EqTrait
|
|
136
|
-
:eq? $ fn (a b)
|
|
137
|
-
and (= (:x a) (:x b)) (= (:y a) (:y b))
|
|
138
|
-
|
|
139
|
-
; 4. 使用 impl-traits 组合,得到带 trait 实现的 struct
|
|
140
|
-
Point $ impl-traits Point0 show-impl eq-impl
|
|
141
|
-
|
|
142
|
-
; 5. 用 struct 创建 record 实例
|
|
143
|
-
p1 $ %{} Point (:x 3) (:y 4)
|
|
144
|
-
p2 $ %{} Point (:x 3) (:y 4)
|
|
145
|
-
|
|
146
|
-
; 6. 调用 trait 方法
|
|
147
|
-
println (.show p1) ; => "Point(3, 4)"
|
|
148
|
-
println (.eq? p1 p2) ; => true
|
|
149
|
-
|
|
150
|
-
; impl-traits 可以接受多个 trait impl
|
|
151
|
-
; (impl-traits struct-def impl1 impl2 impl3 ...)
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
**约束(更新)**:`impl-traits` 只接受 **struct/enum** 作为输入。record/tuple 是实例值,必须从已经挂载 impl 的 struct/enum 创建(如 `%{} Struct ...` 或 `%:: Enum ...`)。
|
|
155
|
-
|
|
156
|
-
### Trait 约束与标注(新增)
|
|
157
|
-
|
|
158
|
-
`assert-traits` 用于在编译期提供“trait 标注”,并在运行时进行检查:
|
|
159
|
-
|
|
160
|
-
- **编译期标记**:将本地变量标注为 trait 类型,供方法解析与类型提示使用。
|
|
161
|
-
- **运行时检查**:确保值确实实现该 trait(缺失方法时直接报错)。
|
|
162
|
-
|
|
163
|
-
补充(与当前实现一致):
|
|
164
|
-
|
|
165
|
-
- `assert-traits` 是一个语法(syntax),会在 preprocess 阶段被展开为对内建过程 `&assert-traits` 的调用;runtime 并不直接执行 `assert-traits` 语法本身。
|
|
166
|
-
- 当前实现要求第一个参数必须是 **local**,用于把 type-info 写回作用域类型表并用于后续方法校验/类型提示。
|
|
167
|
-
|
|
168
|
-
**行为细则(基于当前实现,补充更具体的定义):**
|
|
169
|
-
|
|
170
|
-
1. **两种写法与作用范围**
|
|
171
|
-
|
|
172
|
-
- **函数 body 顶层写法**(推荐,作为函数前置约束):
|
|
173
|
-
- 写在函数 body 顶层的 `assert-traits x Trait` 被视为“参数/局部的全局约束”。
|
|
174
|
-
- 静态分析会把它当作参数类型提示来源(与 `assert-type` 同级别),影响整个函数体内的 `.method` 校验与提示。
|
|
175
|
-
- 运行时会在执行到该表达式时进行检查(缺失方法则抛错)。
|
|
176
|
-
|
|
177
|
-
- **函数 body 内部嵌套写法**(表达式/返回值约束):
|
|
178
|
-
- `assert-traits` 作为表达式参与计算时,主要用于约束该表达式(及其返回值链路);适用于在局部链路上增加约束,减少 `let _ ...` 这类“仅为检查而写”的绑定。
|
|
179
|
-
- 当前实现的参数类型提示扫描会遍历函数体中的 `assert-traits`,因此严格来说嵌套写法也可能被识别为参数提示;但约定上仅将顶层写法视为“函数级前置约束”。
|
|
180
|
-
|
|
181
|
-
2. **静态分析(preprocess)行为**
|
|
182
|
-
|
|
183
|
-
- 会在当前作用域把 local 的 type-info 写入 `scope_types`,供后续 `.method` 校验/补全使用。
|
|
184
|
-
- `assert-traits` 参数中的 trait 解析规则:
|
|
185
|
-
- 能解析为 trait 定义的,进入 trait 集合。
|
|
186
|
-
- 若解析不到 trait,会降级为自定义类型标注,仅用于类型提示(不参与方法校验)。
|
|
187
|
-
- 多个 trait 通过 **append** 形成 `TraitSet`;当多次 `assert-traits` 作用于同一个 local 时,后者的 type-info 会覆盖前者。
|
|
188
|
-
- 若 local 已有**具体类型**标注(如 record/struct/enum 或内置类型),`assert-traits` 不会覆盖该具体类型,以保证后续方法内联与 impl 查找仍可进行。
|
|
189
|
-
|
|
190
|
-
**静态分析可执行边界(重要)**
|
|
191
|
-
|
|
192
|
-
- 只有顶层 `ns/def` 可在构建快照阶段执行;`defn`/`defmacro`/thunk 内部表达式不会被执行,只做浅层预处理。
|
|
193
|
-
- 因此 `defstruct`/`defenum`/`impl-traits` 若写在函数体内,静态分析阶段无法拿到 impl 列表,也就无法做方法内联与精确分派。
|
|
194
|
-
- 建议把结构定义与 impl 绑定放在**顶层定义**(单独 `def`),使 preprocess 可稳定使用这些值进行分析与优化。
|
|
195
|
-
|
|
196
|
-
3. **运行时行为**
|
|
197
|
-
|
|
198
|
-
- 预处理会把 `assert-traits` 展开成一连串 `&assert-traits` 调用,运行时逐个检查 trait 是否实现。
|
|
199
|
-
- 检查顺序按写入顺序执行;当 trait 列表存在重复方法时,实际方法分派仍遵循 impl 分派规则(user impls last-wins / core impls first-wins)。
|
|
200
|
-
- **内置类型限制**:list/map/set/string/number 等内置数据结构的 impl 列表是固定的,`assert-traits` **不会** 在运行时扩充它们的可用方法,只做“默认实现是否存在”的校验。
|
|
201
|
-
- **静态/运行时不一致**:由于静态分析信息有限,preprocess 的 trait 标注可能比运行时更宽松;当前允许这种不一致存在。
|
|
202
|
-
|
|
203
|
-
4. **顺序与覆盖**
|
|
204
|
-
|
|
205
|
-
- trait 信息的记录是 **append 模式**,但对方法分派的“命中优先级”仍以 impl 链为准;当存在多重 trait/impl 时,**从末尾开始命中**(last-wins)是用户自定义 impl 的生效方向。
|
|
206
|
-
- `assert-traits` 只影响“能不能调用/能否单态化”的判断,不改变实际方法分派的实现来源。
|
|
207
|
-
|
|
208
|
-
```cirru
|
|
209
|
-
; 在调用点标注并断言 trait 能力
|
|
210
|
-
assert-traits x Show
|
|
211
|
-
; 允许编译期把 x 视作 trait object,方便后续的 .show/.eq? 等方法校验
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
**类型标注支持**:trait 定义可作为类型标注值使用(与 struct/enum 类似)。
|
|
215
|
-
|
|
216
|
-
- 例:`assert-traits x Show` 会把 `x` 的类型标注为 `trait Show`。
|
|
217
|
-
|
|
218
|
-
### 分派规则(当前实现)
|
|
219
|
-
|
|
220
|
-
当前 `.method` 调用(包含 trait 方法)本质上是对“impl records 列表”的查找。
|
|
221
|
-
|
|
222
|
-
分派来源与优先级:
|
|
223
|
-
|
|
224
|
-
| 场景 | impl 来源 | 覆盖策略 | 扫描方向 | 备注 |
|
|
225
|
-
| ------------ | ---------------------------------------------------------------- | -------------- | -------- | ------------------------------------------------------------ |
|
|
226
|
-
| 用户自定义值 | Record/Tuple/Struct/Enum 实例的 `impls`(由 `impl-traits` 追加) | **last-wins** | 从尾到头 | 支持“后追加覆盖前实现”(append-to-override) |
|
|
227
|
-
| 内置类型 | `calcit.core` 中的 `&core-*-impls`(core impl list) | **first-wins** | 从头到尾 | 保持 core 列表顺序语义(如 list `.add` vs Add trait `:add`) |
|
|
228
|
-
|
|
229
|
-
1. **用户自定义值的 `impls`(Record/Tuple/Struct/Enum 实例)**
|
|
230
|
-
|
|
231
|
-
- `impl-traits` 会把新的 impl record **追加** 到值的 `impls` 末尾(不可变地返回新值)。
|
|
232
|
-
- 查找策略:**last-wins**(从尾到头扫描,先命中先调用)。
|
|
233
|
-
- 目的:支持稳定的“后追加覆盖前已有实现”的工作流。
|
|
234
|
-
|
|
235
|
-
2. **内置类型的 core impl 列表(list/map/number/string/set/fn 等)**
|
|
236
|
-
|
|
237
|
-
- runtime 会把值映射到 `calcit.core` 中的 `&core-*-impls`(这是一个 record 或 record 列表)。
|
|
238
|
-
- 查找策略:**first-wins**(从头到尾扫描,先命中先调用)。
|
|
239
|
-
- 目的:保持 `calcit.core` 中现有 impl 列表的顺序语义(已存在依赖顺序的案例:list 的 `.add` 与 Add trait 的 `:add` 同名时,core 列表顺序决定行为)。
|
|
240
|
-
|
|
241
|
-
一致性要求:preprocess 的方法校验/内联与 JS backend 的 `invoke_method` 需要与上述规则保持一致(当前已对齐)。
|
|
242
|
-
|
|
243
|
-
```cirru
|
|
244
|
-
; 分派示例
|
|
245
|
-
.show 42 ; → 查找 number 的 Show 实现 → "42"
|
|
246
|
-
.show my-point ; → 查找 Point record 的 Show 实现 → "Point(1, 2)"
|
|
247
|
-
|
|
248
|
-
; 显式 Trait 调用(计划,用于消歧义)
|
|
249
|
-
; (trait-call Show :show 42)
|
|
250
|
-
; (Show/show 42)
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
### 内置类型的 Trait 实现映射
|
|
254
|
-
|
|
255
|
-
- `nil`: Show, Inspect, Eq, Hash
|
|
256
|
-
- `bool`: Show, Inspect, Eq, Hash
|
|
257
|
-
- `number`: Show, Inspect, Eq, Compare, Add, Multiply, Hash
|
|
258
|
-
- `string`: Show, Inspect, Eq, Compare, Add, Len, Foldable, Functor, Hash
|
|
259
|
-
- `tag`: Show, Inspect, Eq, Compare, Hash
|
|
260
|
-
- `symbol`: Show, Inspect, Eq, Hash
|
|
261
|
-
- `list`: Show, Inspect, Eq, Compare, Add, Len, Foldable, Functor, Hash
|
|
262
|
-
- `map`: Show, Inspect, Eq, Len, Foldable, Functor, Hash
|
|
263
|
-
- `set`: Show, Inspect, Eq, Len, Foldable, Functor, Hash
|
|
264
|
-
- `tuple`: Show, Inspect, Eq, Len, Hash
|
|
265
|
-
- `record`: Show, Inspect, Eq, Len, Hash
|
|
266
|
-
- `fn`: Show, Inspect
|
|
267
|
-
|
|
268
|
-
### 实施阶段(对照当前进度)
|
|
269
|
-
|
|
270
|
-
#### Phase 1: 基础 Trait 结构 ✅ **已完成**
|
|
271
|
-
|
|
272
|
-
1. ✅ 定义 `CalcitTrait` 数据结构 (src/calcit/calcit_trait.rs)
|
|
273
|
-
2. ✅ 在 `Calcit` enum 中添加 `Trait(CalcitTrait)` 变体
|
|
274
|
-
3. ✅ 在 `calcit.core` 中定义 `Show`, `Eq`, `Add`, `Multiply`, `Len` 等核心 trait
|
|
275
|
-
4. ✅ 内置类型自动拥有对应实现(已在运行时与 JS backend 对齐)
|
|
276
|
-
5. ✅ `invoke_method` 支持 Trait 方法查找
|
|
277
|
-
6. ✅ 从 `class` 系统迁移到 `trait` 和 `impls` (commit 73aa249)
|
|
278
|
-
7. ✅ `impl-traits` 函数支持为 record/tuple/struct/enum 追加 impl
|
|
279
|
-
8. ✅ JS backend 完整支持 CalcitTrait 及相关操作
|
|
280
|
-
9. ✅ 移动内部实现到 `calcit.internal` 命名空间以清理代码结构
|
|
281
|
-
|
|
282
|
-
#### Phase 2: 语法支持 🔄 **部分完成**
|
|
283
|
-
|
|
284
|
-
1. ✅ `deftrait` 宏定义(支持方法名 + 类型签名)
|
|
285
|
-
2. ✅ 基础 trait 实现语法(通过 record + `impl-traits`)
|
|
286
|
-
3. ✅ 测试覆盖:`test-traits.cirru` 包含 Show/Eq/Compare/Add/Len 等基础测试
|
|
287
|
-
4. ✅ `defimpl` 最小宏定义(当前展开为 `defrecord!`,用于更清晰地定义 impl record)
|
|
288
|
-
5. ⏳ `defimpl ... for ...`(让宏直接表达“trait + 目标类型/值”,并减少手写 `impl-traits` 的样板)
|
|
289
|
-
6. ✅ `assert-traits` 运行时检查与编译期标注(当前限制:第一个参数需要是 local;preprocess 展开为 `&assert-traits`)
|
|
290
|
-
7. ⏳ Trait 方法的显式调用语法(如 `Show/show` 或 `trait-call`)
|
|
291
|
-
|
|
292
|
-
#### Phase 3: 扩展 📋 **待实现**
|
|
293
|
-
|
|
294
|
-
1. ⏳ 完整的集合 Trait (`Foldable`, `Mappable`, `Filterable`)
|
|
295
|
-
2. ⏳ `Default`, `From` 转换 Trait
|
|
296
|
-
3. ⏳ Trait 依赖(`requires`)与默认实现(`defaults`)
|
|
297
|
-
4. ⏳ Trait 继承/组合
|
|
298
|
-
5. ⏳ 更好的错误消息(trait 不匹配时的详细提示)
|
|
299
|
-
6. ⏳ 性能优化(trait 查找缓存)
|
|
300
|
-
|
|
301
|
-
---
|
|
302
|
-
|
|
303
|
-
## 范围评估
|
|
304
|
-
|
|
305
|
-
### 1) 核心数据结构与运行时
|
|
306
|
-
|
|
307
|
-
- **`Calcit` 新增变种**:`Trait`(已完成),新增 `Impl` 变种用于承载 trait impl(从 record 迁移)。
|
|
308
|
-
- **运行时上下文**:不单独引入 registry,内置 Trait 直接放在 `calcit.core`,其他 Trait 作为普通值附着在各自命名空间上。
|
|
309
|
-
- **调用分派**:方法调用/操作符调用在当前命名空间与 `calcit.core` 中解析 Trait 值,再执行绑定实现。
|
|
310
|
-
- **Trait 实现承载**:Traits 以组合方式使用,携带实现时统一使用 `Vec`;计划引入 `CalcitImpl` 作为一等结构,替代 record 承载(同时保留向后兼容的过渡层)。
|
|
311
|
-
- **错误模型**:为 trait 查找失败、约束不满足等错误引入新的错误类别与消息格式。
|
|
312
|
-
|
|
313
|
-
### 2) 标准库/内建能力定义
|
|
314
|
-
|
|
315
|
-
- **内建类型能力**:
|
|
316
|
-
- `Show`:所有类型默认实现(或至少 `nil/bool/number/string/list/map/set/tuple`)。
|
|
317
|
-
- `Add`/`Multiply`:`number` 实现;`string` 可选实现 `Add`(拼接)但需明确语义。
|
|
318
|
-
- 其他可选:`Compare`、`Eq`、`Hash`、`Len`、`Index` 等。
|
|
319
|
-
- **内建函数/语法桥接**:
|
|
320
|
-
- `+`, `*`, `str` 等需要改为通过 trait 调用或保留内建分支 + trait 兜底。
|
|
321
|
-
- 现有 `method`/`record`/`tuple` 行为需要确定与 trait 的交互规则。
|
|
322
|
-
|
|
323
|
-
### 3) 语言层定义与语法
|
|
324
|
-
|
|
325
|
-
- **Trait 定义**:动态类型前提下,可先按普通值定义 Trait,后续再考虑是否需要专用语法(如 `deftrait`)。
|
|
326
|
-
- **Trait 实现**:满足 Trait 的实现直接使用 record 表达,运行时通过 `with-class` 之类的机制挂上去(未来可能调整)。
|
|
327
|
-
- **Trait 约束表达**:考虑引入 `assert-traits`,用法类似 `assert-type`,用于声明约束与在调用处触发运行时检查。
|
|
328
|
-
|
|
329
|
-
### 4) 运行时行为变更
|
|
330
|
-
|
|
331
|
-
- **左移报错**:
|
|
332
|
-
- 在调用处,若目标类型未实现 trait,直接抛错(避免进入执行体)。
|
|
333
|
-
- 在加载时注册 trait 实现并检测冲突(重复实现、签名不匹配等)。
|
|
334
|
-
|
|
335
|
-
## 修改范围与复杂度
|
|
336
|
-
|
|
337
|
-
### 高风险/广泛影响
|
|
338
|
-
|
|
339
|
-
- `Calcit` enum 变更(新增变种) → **所有 pattern match 需要更新**。
|
|
340
|
-
- `runner`/`preprocess`/`builtins` 的调用逻辑需要接入 trait 解析。
|
|
341
|
-
- `codegen` 需要考虑 trait 分派(JS backend 与 runtime 协议)。
|
|
342
|
-
- 现有内建操作(`+`, `*`, `.` method)需重新定义分派规则。
|
|
343
|
-
|
|
344
|
-
### 中等影响
|
|
345
|
-
|
|
346
|
-
- `calcit.core` 标准库结构可能需要新增 trait 定义与实现。
|
|
347
|
-
- 错误与警告格式新增类型。
|
|
348
|
-
|
|
349
|
-
### 低风险
|
|
350
|
-
|
|
351
|
-
- 文档、示例与 tests 的补充。
|
|
352
|
-
|
|
353
|
-
## Breaking Changes 预估
|
|
354
|
-
|
|
355
|
-
- `Calcit` 匹配逻辑:新增 `Impl` 变种会导致编译错误与运行时路径调整(允许少量 breaking)。
|
|
356
|
-
- 内建操作行为:如果 `+/*` 改为 trait 分派,某些动态调用会改变错误时机。
|
|
357
|
-
- `method` 分派:同名方法在不同 impl 记录之间的覆盖策略会改变行为(当前已确定规则,见“分派规则”)。
|
|
358
|
-
- `str`/`format` 与 `Show` 的统一:输出可能略有不同。
|
|
359
|
-
|
|
360
|
-
## 设计决策待确认
|
|
361
|
-
|
|
362
|
-
1. Trait 分派优先级与覆盖策略:
|
|
363
|
-
|
|
364
|
-
- 已确定:用户自定义值 `impls` 采用 last-wins;内置类型 core impl 列表采用 first-wins。
|
|
365
|
-
- 待补:提供显式调用语法(`trait-call` / `Show/show`)用于消歧义,以及冲突时的告警/错误策略。
|
|
366
|
-
|
|
367
|
-
2. Trait 的定义方式:
|
|
368
|
-
- 新语法 `deftrait` / `defimpl` vs 复用 record/defn
|
|
369
|
-
3. Trait 是否支持泛型:
|
|
370
|
-
- 初期可不支持,后续扩展。
|
|
371
|
-
4. 多实现冲突处理:
|
|
372
|
-
- 同一类型是否允许多个实现?冲突如何处理?
|
|
373
|
-
|
|
374
|
-
## 建议实施阶段(草案)
|
|
375
|
-
|
|
376
|
-
### Phase 1:基础结构
|
|
377
|
-
|
|
378
|
-
- 引入 `Trait` 数据结构,内置 Trait 放在 `calcit.core`(不新增 registry)。
|
|
379
|
-
- 内建 `Show` + `Number` 的 `Add/Multiply` 实现。
|
|
380
|
-
- 调整 `+`/`*` 使用 trait 分派(保留内建快速路径)。
|
|
381
|
-
|
|
382
|
-
### Phase 2:语言层支持
|
|
383
|
-
|
|
384
|
-
- `deftrait` / `defimpl` 语法与 runtime 注册。
|
|
385
|
-
- 迁移 trait impl 承载:record -> `CalcitImpl`(保留兼容路径,逐步淘汰 record impl)。
|
|
386
|
-
- `assert-traits` / `requires` 运行时检查。
|
|
387
|
-
|
|
388
|
-
### Phase 3:扩展与稳定
|
|
389
|
-
|
|
390
|
-
- 覆盖更多内建类型能力。
|
|
391
|
-
- 增加 tests(cirru 文件)覆盖 trait 注册、冲突、失败路径。
|
|
392
|
-
|
|
393
|
-
---
|
|
394
|
-
|
|
395
|
-
## 当前实现要点(补充)
|
|
396
|
-
|
|
397
|
-
- `deftrait` 已存在,展开为 `&trait::new`。
|
|
398
|
-
- `defimpl` 已存在(最小实现):展开为 `defrecord!`,用于更清晰地定义“trait impl record”(计划引入 `CalcitImpl` 后更新宏展开目标)。
|
|
399
|
-
- `impl-traits` 已在 Rust 与 JS backend 支持,可对 record/tuple/struct/enum 追加 impl。
|
|
400
|
-
- JS 侧已补齐 `CalcitTrait` 类型、`type-of`、`toString` 与 `&trait::new`、`&record:impl-traits` 等对应实现。
|
|
401
|
-
- `.method` 分派采用“混合优先级”:用户自定义值(record/tuple/struct/enum 实例)为 last-wins,内置类型 core impl 列表为 first-wins。
|
|
402
|
-
- `assert-traits` 已作为 preprocess 语法落地:写入 local 的 type-info,并展开为 `&assert-traits` 做运行时检查(当前限制:第一个参数需要是 local)。
|
|
403
|
-
|
|
404
|
-
---
|
|
405
|
-
|
|
406
|
-
## 近期执行计划(从简单稳定开始)
|
|
407
|
-
|
|
408
|
-
> 目标:先把行为与回归边界钉死,再推进语言层能力(`defimpl` / 显式 trait-call)。
|
|
409
|
-
|
|
410
|
-
1. **清理与基线(半天内)**
|
|
411
|
-
|
|
412
|
-
- [ ] 把这轮 traits 改动按边界整理成 3 组:语义实现 / 测试 / 文档(便于 review)。
|
|
413
|
-
- [x] 检查是否有生成物被误纳入改动(如 `js-out/` 等):当前工作区改动仅包含计划/测试/core,未发现产物噪音需要纳入跟踪。
|
|
414
|
-
|
|
415
|
-
2. **固化“分派优先级”规范 + 回归测试(1 天)**
|
|
416
|
-
|
|
417
|
-
- [x] 文档固化规则:builtin core impl list = first-wins;record/tuple impls = last-wins(append-to-override)。
|
|
418
|
-
- [x] 回归:core list 的 `.add` 不被 Add trait `:add` 覆盖(靠 core 列表顺序 + first-wins)。
|
|
419
|
-
- [x] 回归:record 上 `impl-traits` 追加实现能稳定覆盖旧实现(last-wins)。
|
|
420
|
-
- [x] 回归:tuple 上 `impl-traits` 覆盖链同样稳定(last-wins)。
|
|
421
|
-
- [x] 再补 1 个“更尖锐”的 Rust/JS 共用断言:不同 trait 提供同名方法时,仍以 `impl-traits` 追加顺序决定命中(覆盖 record + tuple)。
|
|
422
|
-
|
|
423
|
-
3. **动态 trait 调用告警(1 天)**
|
|
424
|
-
|
|
425
|
-
- [x] 引入 `--warn-dyn-method`:在 `cr` 正常执行流程中,对无法单态化的 `.method` 调用给出 warning。
|
|
426
|
-
- [x] 规则:当 receiver 无法解析到具体 impl(类型为 `:dynamic`/未知),且未被 `assert-traits` 标注时触发告警。
|
|
427
|
-
- [x] 验收:warning 包含方法名与位置;`assert-traits` 后 warning 消失;Rust/JS 预处理行为一致。
|
|
428
|
-
|
|
429
|
-
4. **补齐语言层能力:`defimpl` + 显式 trait-call(2~4 天)**
|
|
430
|
-
|
|
431
|
-
- [x] `defimpl`(v0):让“写 impl record”有标准入口,减少手写 `defrecord!` 的样板。
|
|
432
|
-
- [ ] `defimpl ... for ...`:让宏直接表达“trait + 目标类型/值”,并自动完成挂载(减少手写 `impl-traits`)。
|
|
433
|
-
- [ ] 显式 trait-call(如 `Show/show` 或 `trait-call`):提供绕开 `.method` 分派的稳定通道,便于 debug override 链。
|
|
434
|
-
- [ ] 验收:同一段代码在 preprocess 校验、Rust runtime、JS runtime 行为一致。
|
|
435
|
-
|
|
436
|
-
5. **`assert-traits` 易用性补强(可选,1~2 天)**
|
|
437
|
-
|
|
438
|
-
- [ ] 支持 `assert-traits` 的第一个参数是任意表达式:preprocess 自动提升为临时 local 再做检查(纯语法糖)。
|
|
439
|
-
- [ ] 验收:`assert-traits (+ 1 2) ...` 可用,且错误位置/提示依然清晰。
|
|
440
|
-
|
|
441
|
-
6. **冲突策略与可观测性(可选,2~3 天)**
|
|
442
|
-
|
|
443
|
-
- [ ] 提供“冲突诊断”开关:当同名方法多次出现时,打印候选列表/命中来源(对 last-wins 特别有帮助)。
|
|
444
|
-
- [x] 可选 debug proc:`&methods-of` / `&inspect-methods`,返回/打印某值当前 impl 链与可用方法集合,帮助定位“为什么命中这个实现”。
|
|
445
|
-
|
|
446
|
-
---
|
|
447
|
-
|
|
448
|
-
## Checklist(后续跟踪)
|
|
449
|
-
|
|
450
|
-
### 🎯 推荐优先实现(短期,1-2周)
|
|
451
|
-
|
|
452
|
-
**理由:完善当前已有的 trait 机制,提升用户体验**
|
|
453
|
-
|
|
454
|
-
- [ ] **`defimpl` 宏**:简化 trait 实现语法
|
|
455
|
-
- 当前(已实现 v0):`defimpl MyTrait MyImpl (:method value) ...`(展开为 `defrecord!`,产出“impl record”)
|
|
456
|
-
- 仍待补齐:`defimpl MyTrait for MyType ...`(自动挂载/注册,减少显式 `impl-traits`)
|
|
457
|
-
- 优势:语义更清晰,自动完成 impl-traits 步骤
|
|
458
|
-
- 验收:宏展开在 Rust/JS 下语义一致;错误信息包含 trait/类型/缺失方法;`test-traits.cirru` 覆盖正常/冲突路径
|
|
459
|
-
- [ ] **显式 trait 调用语法**:解决方法名冲突
|
|
460
|
-
- 语法选项:`(trait-call Show :show x)` 或 `(Show/show x)`
|
|
461
|
-
- 用例:当一个类型实现多个 trait,且方法名冲突时
|
|
462
|
-
- 验收:可在运行时绕开 `.method` 的歧义(不受 impl 覆盖影响);preprocess 能做方法存在性与参数数量/类型校验;Rust/JS 行为一致
|
|
463
|
-
- [x] **`assert-traits` 运行时检查**:前移错误发现时机(已实现;当前限制:第一个参数需要是 local)
|
|
464
|
-
- 语法:`(assert-traits x Show)` 或 `(requires x Show)`
|
|
465
|
-
- 在函数入口检查参数是否满足 trait 约束
|
|
466
|
-
- 提供清晰的错误消息
|
|
467
|
-
- 下一步:扩展到函数参数/模式绑定等更多场景(不止 local)
|
|
468
|
-
|
|
469
|
-
### 🔧 中期实现(3-4周)
|
|
470
|
-
|
|
471
|
-
**理由:扩展 trait 系统能力,支持更复杂的场景**
|
|
472
|
-
|
|
473
|
-
- [ ] **Trait 依赖(`requires`)**:声明 trait 之间的依赖关系
|
|
474
|
-
- 例:`Ord` 依赖 `Eq`
|
|
475
|
-
- 实现时自动检查依赖是否满足
|
|
476
|
-
- [ ] **默认实现(`defaults`)**:减少重复代码
|
|
477
|
-
- 在 trait 定义中提供默认方法实现
|
|
478
|
-
- 类型可以选择性覆盖
|
|
479
|
-
- [ ] **完整的集合 Trait(重新评估设计)**:
|
|
480
|
-
- **方案 A(Haskell 风格)**:独立 `Functor`/`Foldable` trait
|
|
481
|
-
- 优点:概念纯粹,严格分离关注点
|
|
482
|
-
- 缺点:在动态语言中过度抽象,用户学习成本高
|
|
483
|
-
- **方案 B(Rust/JS 风格)**:统一 `Collection` trait
|
|
484
|
-
- 包含 `map`, `filter`, `fold`, `count`, `empty?` 等全套操作
|
|
485
|
-
- 优点:实用导向,一站式接口
|
|
486
|
-
- 缺点:trait 体积大,部分类型可能只能实现子集
|
|
487
|
-
- **方案 C(混合)**:保留 `Foldable` 基础,`map`/`filter` 作为可选扩展
|
|
488
|
-
- 最小公约数是 `fold`(可实现 map/filter)
|
|
489
|
-
- 类型按需实现 map/filter 优化版本
|
|
490
|
-
- 🎯 **推荐**:先实现方案 B(务实路线),观察实际使用后再考虑拆分
|
|
491
|
-
|
|
492
|
-
- [ ] **冲突检测与覆盖策略**:
|
|
493
|
-
- 同一类型多 impl 时的优先级规则
|
|
494
|
-
- 重复注册时的警告机制
|
|
495
|
-
|
|
496
|
-
**Functor/Monad 补充说明:**
|
|
497
|
-
|
|
498
|
-
- 在 Calcit 这样的动态语言中,Monad 的核心价值(`>>=` 的类型组合)基本丧失
|
|
499
|
-
- 但 `Functor` (fmap) 仍有意义:统一"保结构变换"的概念
|
|
500
|
-
- 实际实现时可考虑:
|
|
501
|
-
- ✅ 提供 `fmap` 作为标准方法名(对 FP 用户友好)
|
|
502
|
-
- ✅ 同时保留 `.map` 别名(对主流用户友好)
|
|
503
|
-
- ❌ 不强制实现完整 Monad(`return`/`>>=` 在无类型约束时意义不大)
|
|
504
|
-
|
|
505
|
-
### 🚀 长期规划(1-2月)
|
|
506
|
-
|
|
507
|
-
**理由:提升系统稳定性和性能**
|
|
508
|
-
|
|
509
|
-
- [ ] **转换 Trait (`Default`, `From`)**:
|
|
510
|
-
- 类型间的标准转换接口
|
|
511
|
-
- 减少手写转换函数
|
|
512
|
-
- [ ] **Trait 继承/组合**:
|
|
513
|
-
- 支持 trait 继承(如 `trait Ord extends Eq`)
|
|
514
|
-
- 或 trait 组合(如 `trait Num = Add + Multiply + ...`)
|
|
515
|
-
- [ ] **性能优化**:
|
|
516
|
-
- Trait 查找缓存(避免重复遍历)
|
|
517
|
-
- 内联常见 trait 方法(如 `show`, `eq?`)
|
|
518
|
-
- [ ] **更好的错误消息**:
|
|
519
|
-
- Trait 不匹配时显示期望 vs 实际
|
|
520
|
-
- 建议可能的解决方案
|
|
521
|
-
- [ ] **文档与示例**:
|
|
522
|
-
- 完整的 trait 使用指南
|
|
523
|
-
- 常见模式与最佳实践
|
|
524
|
-
- 更多 `test-traits.cirru` 测试用例
|
|
525
|
-
|
|
526
|
-
### 📝 技术债务清理
|
|
527
|
-
|
|
528
|
-
- [x] ~~从 `class` 迁移到 `trait`~~ (已完成,commit 73aa249)
|
|
529
|
-
- [x] ~~移动内部函数到 `calcit.internal`~~ (已完成,commit fc78725)
|
|
530
|
-
- [ ] 解决 JS 编译模式的循环依赖问题
|
|
531
|
-
- 当前问题:`calcit.internal.mjs` 引用 `calcit.core.mjs` 导致初始化失败
|
|
532
|
-
- 可能方案:调整模块加载顺序或使用延迟初始化
|
|
533
|
-
|
|
534
|
-
---
|
|
535
|
-
|
|
536
|
-
## 实施建议
|
|
537
|
-
|
|
538
|
-
**下一步行动(按优先级):**
|
|
539
|
-
|
|
540
|
-
1. **先做(低风险)**:清理基线 + 补齐“分派优先级”尖锐回归
|
|
541
|
-
|
|
542
|
-
- 把行为边界钉死,避免后续 `defimpl` / trait-call 引入难定位回归
|
|
543
|
-
|
|
544
|
-
2. **然后(核心能力)**:`defimpl` 宏
|
|
545
|
-
|
|
546
|
-
- 降低写 impl 的样板,提高可读性
|
|
547
|
-
|
|
548
|
-
3. **再做(可观测性/可维护)**:显式 trait 调用语法
|
|
549
|
-
|
|
550
|
-
- 解决方法名冲突与 override 链难 debug 的实际问题
|
|
551
|
-
- 为后续 trait 组合打基础
|
|
552
|
-
|
|
553
|
-
3. **然后**:Trait 依赖 + 默认实现
|
|
554
|
-
- 这是更复杂的功能,依赖前面的基础
|
|
555
|
-
- 可以大幅减少样板代码
|
|
556
|
-
|
|
557
|
-
4. **最后**:集合 Trait + 性能优化
|
|
558
|
-
- 在系统稳定后进行性能调优
|
|
559
|
-
- 逐步扩展 trait 覆盖范围
|
|
560
|
-
|
|
561
|
-
---
|
|
562
|
-
|
|
563
|
-
## 原有 Checklist(归档)
|
|
564
|
-
|
|
565
|
-
以下是原计划中的项目,已整合到上面的分类中(按真实实现状态更新):
|
|
566
|
-
|
|
567
|
-
- [ ] `defimpl ... for ...`(包含方法名校验/去重规则)→ 短期优先(注:defimpl v0 已存在,仅产出 impl record)
|
|
568
|
-
- [x] `assert-traits` 运行时检查与错误消息格式(已实现;当前限制:第一个参数需要是 local)
|
|
569
|
-
- [ ] 显式 trait 调用语法(`trait-call` / `Show/show` 语法)→ 短期优先
|
|
570
|
-
- [ ] trait 依赖(`requires`)与默认实现(`defaults`)的表达与存储 → 中期实现
|
|
571
|
-
- [ ] 统一 `Compare` 的三态返回与 `&compare` 的关系(`<`/`>` 仅数字)→ 中期实现
|
|
572
|
-
- [ ] 冲突检测:同一对象多 impl 的覆盖顺序与警告策略 → 中期实现
|
|
573
|
-
- [x] ~~JS backend 与 Rust 行为一致性验证(新增 tests)~~ → 已有 test-traits.cirru
|
|
574
|
-
- [ ] 文档示例与 `test-traits.cirru` 覆盖更多失败路径 → 长期规划
|
|
575
|
-
|
|
576
|
-
## 测试补充建议(cirru)
|
|
577
|
-
|
|
578
|
-
- `test-traits.cirru`:
|
|
579
|
-
- `Show` for number/string/list/map
|
|
580
|
-
- `Add`/`Multiply` for number
|
|
581
|
-
- 未实现时的错误提示
|
|
582
|
-
- 多实现冲突/重复注册
|
|
583
|
-
|
|
584
|
-
## 附录:符号解析与 runtime-resolved symbols
|
|
585
|
-
|
|
586
|
-
补充说明:在 trait/runtime 相关排障里,经常会遇到“文档里能看到/看不到某个符号”的认知差异。这里统一记录符号解析口径。
|
|
587
|
-
|
|
588
|
-
目前符号大致分为:
|
|
589
|
-
|
|
590
|
-
- raw syntax symbols(例如 `&` `?` `~` `~@`)
|
|
591
|
-
- data symbol(通常由 `turn-symbol` 创建)
|
|
592
|
-
- local variables
|
|
593
|
-
- local definitions
|
|
594
|
-
- imported variables
|
|
595
|
-
- namespaced imported symbols
|
|
596
|
-
- imported default variables
|
|
597
|
-
- imported host variables
|
|
598
|
-
|
|
599
|
-
当前它们在实现层很多都共享 `Calcit::Symbol{..}` 路径(这是已知历史包袱,后续仍需重构)。
|
|
600
|
-
|
|
601
|
-
另外有一类 **runtime-resolved symbols**:由 parser/runtime 直接识别,不一定在 `calcit.core` 里作为普通 `CodeEntry` 出现。
|
|
602
|
-
|
|
603
|
-
- syntax symbols 来自 `CalcitSyntax`(例如 `assert-traits`)
|
|
604
|
-
- proc symbols 来自 `CalcitProc`(例如 `&trait-call`、`&inspect-type`、`register-calcit-builtin-impls`)
|
|
605
|
-
|
|
606
|
-
因此检查符号可用性时,建议同时看:
|
|
607
|
-
|
|
608
|
-
- `src/cirru/calcit-core.cirru`(macro/def 暴露面)
|
|
609
|
-
- `src/calcit/syntax_name.rs` 与 `src/calcit/proc_name.rs`(runtime-native 符号)
|
|
610
|
-
|
|
611
|
-
---
|
|
612
|
-
|
|
613
|
-
> 备注:该方案涉及 runtime/stdlib/codegen 全链路改造,建议先从最小可运行集开始迭代。
|
|
107
|
+
不计划恢复 class/prototype 作为第二套公开多态系统,也不计划为 WASM 单独维护一套功能不完整但表面可运行的 trait runtime。
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Release main-branch policy
|
|
2
|
+
|
|
3
|
+
- Main no longer requires pull-request protection for verified changes.
|
|
4
|
+
- The release flow now requires the stable version commit to be on `main`, followed by a fresh main-branch CI run before tagging and creating the release.
|
|
5
|
+
- Corrected the npm verification command to use the published package name, `@calcit/procs`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Data API and trait coverage
|
|
2
|
+
|
|
3
|
+
## Review outcome
|
|
4
|
+
|
|
5
|
+
- Corrected the core `Option` and `Result` declarations to carry their real generic parameters instead of dynamic payloads.
|
|
6
|
+
- Added the missing high-frequency predicate, fallback, chaining, and error-mapping helpers, with schemas, method bags, docs, and queryable examples.
|
|
7
|
+
- Added the documented-but-missing `Compare` trait for Number and String.
|
|
8
|
+
- Connected the existing `Countable` and `Contains` traits to List, Map, Set, String, Record, and Tuple/enum values for method dispatch, static `:where` checks, and runtime `assert-traits`.
|
|
9
|
+
|
|
10
|
+
## Cross-platform consistency
|
|
11
|
+
|
|
12
|
+
- Made Number and String `.compare` visible through the shared built-in method bags so native, JavaScript, and WASM preprocessing agree.
|
|
13
|
+
- Aligned Rust and JavaScript trait introspection for Record/Struct and Tuple/Enum by merging their built-in and attached impl records.
|
|
14
|
+
- Added native, JavaScript, and WASM regression coverage for the new APIs and trait capabilities.
|
|
15
|
+
|
|
16
|
+
## Type-system fix
|
|
17
|
+
|
|
18
|
+
- Named generic enum values now safely satisfy builtin dynamic-tuple parameters, while a dynamic tuple still cannot satisfy a concrete named enum.
|
|
19
|
+
- Type-definition resolution now unwraps `def` and `impl-traits` and resolves unqualified core enum references.
|
|
20
|
+
- Tracked the underlying generic-enum diagnostic defect in <https://github.com/calcit-lang/calcit/issues/287>.
|
|
21
|
+
|
|
22
|
+
## Documentation
|
|
23
|
+
|
|
24
|
+
- Expanded the data-type overview to distinguish persistent values, named data, executable values, and explicitly stateful containers.
|
|
25
|
+
- Documented Cirru EDN versus JSON fidelity, unsupported values, and restoring declared Record/Enum identity during parsing.
|
|
26
|
+
- Added the built-in trait matrix and the Option/Result helper surface to the polymorphism guide.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Runtime traits: nominal implementations
|
|
2
|
+
|
|
3
|
+
## What changed
|
|
4
|
+
|
|
5
|
+
- Runtime `CalcitTrait` values now have nominal identity, so independently evaluated traits with the same displayed name and method shape do not match accidentally.
|
|
6
|
+
- Concrete `defimpl` values carry their exact trait origin, require the complete declared method set, and reject non-callable methods. Native preprocessing additionally verifies signatures when metadata is available.
|
|
7
|
+
- Tag-based `defimpl` remains compatible as an originless inherent method bag. It continues to support ordinary `.method` dispatch, but cannot satisfy trait bounds, `assert-traits`, or `&trait-call`.
|
|
8
|
+
- Core builtin capability implementations now preserve trait origins in native and JS runtimes, including scalar values. WASM remains an internal validation backend and reports an explicit unsupported-runtime-trait error when preprocessing cannot eliminate trait operations.
|
|
9
|
+
- `cr edit format` emits a non-blocking migration advisory for legacy tag-based trait arguments.
|
|
10
|
+
|
|
11
|
+
## Verification
|
|
12
|
+
|
|
13
|
+
- `cargo fmt --check`
|
|
14
|
+
- `cargo clippy -- -D warnings`
|
|
15
|
+
- `cargo test` (298 library tests, 176 CLI tests)
|
|
16
|
+
- `yarn compile`
|
|
17
|
+
- `yarn check-all` (Agent interface, native, JS, IR, and WASM)
|
|
18
|
+
- `cr docs check-md` for traits, polymorphism, and upgrade documentation; all 24 Cirru blocks round-trip through the current formatter.
|
|
19
|
+
- External Respo check-only, type summary, and targeted example regression.
|
|
20
|
+
|
|
21
|
+
## Compatibility
|
|
22
|
+
|
|
23
|
+
This intentionally tightens concrete trait implementation validation. Existing tag-based method bags keep running, with a migration advisory. Code that treated unrelated same-named or partial implementations as satisfying a trait must migrate to a real `deftrait` plus complete nominal `defimpl`.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Markdown Cirru formatter
|
|
2
|
+
|
|
3
|
+
## What changed
|
|
4
|
+
|
|
5
|
+
- Added `cr docs format-md <file>` to canonicalize fenced `cirru`, `cirru.no-run`, `cirru.no-check`, and `cirru.cli` blocks in Markdown.
|
|
6
|
+
- Added `--check` for CI: it reports non-canonical blocks without writing the Markdown file.
|
|
7
|
+
- Formatting preserves non-Cirru Markdown and fence labels, parses code directly as multiple Cirru AST roots, and writes using the existing atomic replacement helper.
|
|
8
|
+
- Added CLI help, command-echo support, unit coverage for formatting, parse errors, idempotence, and the non-writing check mode.
|
|
9
|
+
- Documented usage in CLI options and library quality guidance.
|
|
10
|
+
|
|
11
|
+
## Verification
|
|
12
|
+
|
|
13
|
+
- `cargo fmt --check`
|
|
14
|
+
- `cargo clippy -- -D warnings`
|
|
15
|
+
- `cargo test`
|
|
16
|
+
- `yarn compile`
|
|
17
|
+
- `yarn check-all`
|
|
18
|
+
- `cr docs format-md --check` and `cr docs check-md` on affected documentation.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Trait review follow-ups
|
|
2
|
+
|
|
3
|
+
## What changed
|
|
4
|
+
|
|
5
|
+
- Corrected the public `Mappable` capability table: Result joins List, Map, Set, and Option.
|
|
6
|
+
- Documented why `calcit.internal` is excluded from the legacy inherent-impl advisory: bootstrap method bags precede public nominal trait availability.
|
|
7
|
+
- Made builtin literal method introspection tolerant of unavailable core impl lists, matching record and tuple behavior in embedding/unit-test startup states.
|
|
8
|
+
- Corrected `&str:contains?` documentation and schema to describe numeric character-index bounds checking; substring membership remains `&str:includes?`.
|
|
9
|
+
- Restrict primitive trait-name bootstrap fallback to the period before its real core impl list is evaluated, and added a test that reads the embedded core Snapshot to detect table drift.
|
|
10
|
+
|
|
11
|
+
## Verification
|
|
12
|
+
|
|
13
|
+
- `cr src/cirru/calcit-core.cirru edit format`
|
|
14
|
+
- `cargo fmt --check`
|
|
15
|
+
- `cargo clippy -- -D warnings`
|
|
16
|
+
- `cargo test` (300 library tests, 179 CLI tests)
|
|
17
|
+
- `yarn compile`
|
|
18
|
+
- `yarn check-all` (Agent interface, native, JS, IR, WASM)
|
|
19
|
+
- `cr docs check-md --entry calcit/test.cirru docs/features/polymorphism.md`
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# `defimpl` trait arguments use raw symbols
|
|
2
|
+
|
|
3
|
+
- Migrated ordinary test fixtures from tag-based `defimpl` trait arguments to named `deftrait` values referenced directly as symbols.
|
|
4
|
+
- Kept the new trait definitions' entry schemas as `'Trait`, so the snapshot metadata matches their runtime role.
|
|
5
|
+
- Updated the `defimpl` macro documentation and diagnostics: raw symbols are the standard syntax; tag arguments remain only as legacy inherent-method-bag compatibility.
|
|
6
|
+
- Updated the tuple test to assert the new nominal trait origin rather than the old tag representation.
|
|
7
|
+
|
|
8
|
+
`&impl::new :name` bootstrap method bags are intentionally unchanged: unlike the macro form, a raw symbol there would be evaluated rather than captured as syntax.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Documentation type syntax audit
|
|
2
|
+
|
|
3
|
+
- Updated the trait guide and quick reference so schema and data-declaration type positions use quoted symbols (`'String`, `'Number`, `'Fn`, and so on).
|
|
4
|
+
- Kept ordinary tag data unchanged, including enum variants, record keys, and schema-map keys such as `:return`.
|
|
5
|
+
- Ran `docs format-md` and `docs check-md` with `calcit/test.cirru` for both updated guides.
|
|
6
|
+
- The wider audit still finds legacy type-tag examples in feature, run, and migration pages. Upgrade/compatibility explanations deliberately retain old spellings; the remaining tutorial/reference pages should be migrated in a dedicated documentation sweep with code-block validation.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Receiver-first enum method calls
|
|
2
|
+
|
|
3
|
+
- Extended preprocess-time postfix method rewriting to recognize nominal enum
|
|
4
|
+
receivers as well as records and traits.
|
|
5
|
+
- During core bootstrapping, infer the declared result type of `%some`, `%none`,
|
|
6
|
+
`%ok`, and `%err` from their schemas, so their values keep enough type
|
|
7
|
+
information for receiver-first calls such as `res-ok .unwrap-or 9`.
|
|
8
|
+
- Updated Option/Result trait tests and polymorphism documentation to use the
|
|
9
|
+
receiver-first form, and verified the JavaScript backend emits normal method
|
|
10
|
+
invocation code.
|
|
11
|
+
- Updated a formatting-advisory fixture assertion whose legacy inherent-impl
|
|
12
|
+
warning was intentionally removed by the earlier symbol-type migration.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Receiver-first unwrap examples
|
|
2
|
+
|
|
3
|
+
- Migrated the built-in `option:unwrap-or` and `result:unwrap-or` API examples
|
|
4
|
+
to the typed receiver-first method form.
|
|
5
|
+
- Verified the examples through `analyze check-examples`, the Markdown snippet
|
|
6
|
+
through `docs check-md`, and generated JavaScript by executing the trait test
|
|
7
|
+
bundle with Node.js.
|
|
8
|
+
- Kept the two WASM fixtures in function form: the internal WASM backend does
|
|
9
|
+
not yet lower enum constructor receiver-method calls, while its complete
|
|
10
|
+
verification suite continues to pass with the supported form.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Receiver-first calls with stronger static inference
|
|
2
|
+
|
|
3
|
+
- Extended receiver-first rewriting from Result/Option to every receiver whose
|
|
4
|
+
static method table is known, including built-in collections, primitives,
|
|
5
|
+
traits, structs, records, and enums. Prefix calls remain compatible.
|
|
6
|
+
- Preserved concrete `deftrait`, `defimpl`, `defstruct`, and `defenum` metadata
|
|
7
|
+
through local bindings and imported definitions, so `impl-traits` no longer
|
|
8
|
+
collapses nominal values to `Dynamic` or a broad `Custom` type.
|
|
9
|
+
- Propagated generic `:where` capabilities into function bodies and enum
|
|
10
|
+
`match` payloads, including lexical data definitions and nested named type
|
|
11
|
+
references. `%::` now retains enum identity even when a payload-free variant
|
|
12
|
+
cannot determine every generic argument.
|
|
13
|
+
- Inferred typed trait-method returns, required struct fields read through
|
|
14
|
+
`get`, and body-hinted parameter types. Broad `assert-type` checks no longer
|
|
15
|
+
erase a more precise compatible inferred type.
|
|
16
|
+
- Migrated representative Calcit snapshots and Markdown examples to
|
|
17
|
+
receiver-first syntax, formatted every touched Cirru block, and executed the
|
|
18
|
+
documented outputs on native and JavaScript targets.
|
|
19
|
+
- Kept conservative `Dynamic` fallback for opaque FFI values, heterogeneous or
|
|
20
|
+
genuinely unresolved nested data, and metadata that cannot be resolved
|
|
21
|
+
without executing arbitrary user code.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Review type-correctness follow-ups
|
|
2
|
+
|
|
3
|
+
- Kept named enum compatibility directional: a resolved enum `TypeRef` may satisfy a dynamic tuple parameter, but a dynamic tuple may not satisfy the named enum.
|
|
4
|
+
- Added namespace-qualified source identity to pre-runtime trait references so bootstrap fallback cannot confuse a user trait with a same-named core trait.
|
|
5
|
+
- Let unresolved core Option/Result constructors fall through to schema inference and taught method return inference to read impl methods from resolved enum `TypeRef` values.
|
|
6
|
+
- Added regressions for nominal trait fallback, source trait identity, and chained receiver-first Option/Result calls.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Release 0.12.58
|
|
2
|
+
|
|
3
|
+
- Release the receiver-first method inference and trait nominal-identity fixes merged in PR #288.
|
|
4
|
+
- Synchronize the crate, npm package, and Cargo lockfile versions at `0.12.58`.
|
|
5
|
+
- The merge commit passed the main Test and CodeQL workflows before this release commit.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Top-level nominal value schemas
|
|
2
|
+
|
|
3
|
+
- `cr edit schema` now accepts a fully qualified quoted nominal type such as `'app.schema/Store` for a top-level `def` value backed by `defstruct` or `defenum`.
|
|
4
|
+
- Centralize schema write validation and parsing so tag leaves, quoted builtin symbols, and named type references retain their intended annotations.
|
|
5
|
+
- Keep unqualified custom names rejected at the CLI boundary; a stored top-level nominal schema must include its namespace.
|
|
6
|
+
- Add schema validation and binary/text round-trip coverage, then verify the command on Calcit and Respo temporary Snapshot copies.
|
package/lib/calcit.procs.d.mts
CHANGED
|
@@ -266,6 +266,7 @@ declare let calcit_builtin_impls: {
|
|
|
266
266
|
fn: CalcitImplEntry;
|
|
267
267
|
tuple: CalcitImplEntry;
|
|
268
268
|
record: CalcitImplEntry;
|
|
269
|
+
scalar: CalcitImplEntry;
|
|
269
270
|
};
|
|
270
271
|
export declare let register_calcit_builtin_impls: (options: typeof calcit_builtin_impls) => void;
|
|
271
272
|
/** method used as closure */
|
package/lib/calcit.procs.mjs
CHANGED
|
@@ -176,47 +176,27 @@ export let _$n_assert_traits = function (value, traitDef) {
|
|
|
176
176
|
if (!(traitDef instanceof CalcitTrait)) {
|
|
177
177
|
throw new Error(`&assert-traits expected a trait definition, but received: ${toString(traitDef, true)}`);
|
|
178
178
|
}
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
else if (value instanceof CalcitTuple) {
|
|
186
|
-
impls = value.impls ?? [];
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
const pair = lookup_impls(value);
|
|
190
|
-
if (pair == null) {
|
|
191
|
-
throw new Error(`&assert-traits cannot resolve impls for: ${toString(value, true)}`);
|
|
192
|
-
}
|
|
193
|
-
impls = pair[0];
|
|
194
|
-
}
|
|
195
|
-
const missing = [];
|
|
196
|
-
for (let i = 0; i < traitDef.methods.length; i++) {
|
|
197
|
-
const method = traitDef.methods[i];
|
|
198
|
-
let exists = false;
|
|
199
|
-
for (let j = 0; j < impls.length; j++) {
|
|
200
|
-
const impl = impls[j];
|
|
201
|
-
if (impl != null && impl.getOrNil(method) != null) {
|
|
202
|
-
exists = true;
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
if (!exists)
|
|
207
|
-
missing.push(method.toString());
|
|
179
|
+
// Use the same merged builtin + attached impl list as method dispatch.
|
|
180
|
+
// Otherwise records and tuples can call a builtin method while
|
|
181
|
+
// `assert-traits` incorrectly claims that the corresponding trait is absent.
|
|
182
|
+
const pair = lookup_impls(value);
|
|
183
|
+
if (pair == null) {
|
|
184
|
+
throw new Error(`&assert-traits cannot resolve impls for: ${toString(value, true)}`);
|
|
208
185
|
}
|
|
186
|
+
const impls = pair[0];
|
|
187
|
+
const reverse = value instanceof CalcitRecord || value instanceof CalcitTuple || value instanceof CalcitStruct || value instanceof CalcitEnum;
|
|
188
|
+
const ordered = reverse ? [...impls].reverse() : impls;
|
|
189
|
+
const selected = ordered.find((impl) => impl != null && impl.origin === traitDef);
|
|
190
|
+
if (selected == null) {
|
|
191
|
+
const available = impls
|
|
192
|
+
.filter((impl) => impl?.origin != null)
|
|
193
|
+
.map((impl) => impl.origin.name.toString())
|
|
194
|
+
.join(" ");
|
|
195
|
+
throw new Error(`assert-traits failed: ${toString(value, true)} does not nominally implement ${traitDef.toString()}. Available trait impls: ${available || "(none)"}`);
|
|
196
|
+
}
|
|
197
|
+
const missing = traitDef.methods.filter((method) => selected.getOrNil(method) == null);
|
|
209
198
|
if (missing.length > 0) {
|
|
210
|
-
|
|
211
|
-
for (let j = 0; j < impls.length; j++) {
|
|
212
|
-
const impl = impls[j];
|
|
213
|
-
if (impl == null)
|
|
214
|
-
continue;
|
|
215
|
-
for (let k = 0; k < impl.fields.length; k++) {
|
|
216
|
-
available.push(impl.fields[k].toString());
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
throw new Error(`assert-traits failed: ${toString(value, true)} does not implement ${traitDef.toString()}. Missing: ${missing.join(" ")}. Available: ${available.join(" ")}`);
|
|
199
|
+
throw new Error(`assert-traits failed: impl ${selected.name.toString()} for trait ${traitDef.name.toString()} is incomplete. Missing: ${missing.join(" ")}`);
|
|
220
200
|
}
|
|
221
201
|
return value;
|
|
222
202
|
};
|
|
@@ -272,12 +252,14 @@ export let _$n_impl_$o__$o_new = (name, ...pairs) => {
|
|
|
272
252
|
throw new Error("&impl::new expected arguments");
|
|
273
253
|
const origin = name instanceof CalcitTrait ? name : null;
|
|
274
254
|
const implName = origin ? origin.name : castTag(name);
|
|
275
|
-
if (pairs.length === 0) {
|
|
276
|
-
return new CalcitImpl(implName, [], [], origin);
|
|
277
|
-
}
|
|
278
255
|
const entries = [];
|
|
279
|
-
|
|
280
|
-
|
|
256
|
+
let sourcePairs = pairs;
|
|
257
|
+
if (pairs.length === 1 && pairs[0] instanceof CalcitImpl) {
|
|
258
|
+
const sourceImpl = pairs[0];
|
|
259
|
+
sourcePairs = sourceImpl.fields.map((field, idx) => new CalcitTuple(field, [sourceImpl.values[idx]], null));
|
|
260
|
+
}
|
|
261
|
+
for (let idx = 0; idx < sourcePairs.length; idx++) {
|
|
262
|
+
const pairValue = sourcePairs[idx];
|
|
281
263
|
let fieldTag;
|
|
282
264
|
let value;
|
|
283
265
|
if (pairValue instanceof CalcitTuple) {
|
|
@@ -305,6 +287,23 @@ export let _$n_impl_$o__$o_new = (name, ...pairs) => {
|
|
|
305
287
|
}
|
|
306
288
|
const fields = entries.map((entry) => entry.tag);
|
|
307
289
|
const values = entries.map((entry) => entry.value);
|
|
290
|
+
if (origin != null) {
|
|
291
|
+
const missing = origin.methods.filter((method) => !fields.some((field) => field.value === method.value));
|
|
292
|
+
const unexpected = fields.filter((field) => !origin.methods.some((method) => method.value === field.value));
|
|
293
|
+
if (missing.length > 0 || unexpected.length > 0) {
|
|
294
|
+
const details = [];
|
|
295
|
+
if (missing.length > 0)
|
|
296
|
+
details.push(`missing methods: ${missing.join(" ")}`);
|
|
297
|
+
if (unexpected.length > 0)
|
|
298
|
+
details.push(`methods not declared by the trait: ${unexpected.join(" ")}`);
|
|
299
|
+
throw new Error(`&impl::new does not conform to trait ${origin.name.toString()}: ${details.join("; ")}`);
|
|
300
|
+
}
|
|
301
|
+
for (const entry of entries) {
|
|
302
|
+
if (typeof entry.value !== "function") {
|
|
303
|
+
throw new Error(`&impl::new expects trait method .${entry.tag.value} to be a function, but received: ${toString(entry.value, true)}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
308
307
|
return new CalcitImpl(implName, fields, values, origin);
|
|
309
308
|
};
|
|
310
309
|
export let _$n_struct_$o__$o_new = (name, ...entries) => {
|
|
@@ -1764,6 +1763,7 @@ let calcit_builtin_impls = {
|
|
|
1764
1763
|
fn: null,
|
|
1765
1764
|
tuple: null,
|
|
1766
1765
|
record: null,
|
|
1766
|
+
scalar: null,
|
|
1767
1767
|
};
|
|
1768
1768
|
// need to register code from outside
|
|
1769
1769
|
export let register_calcit_builtin_impls = (options) => {
|
|
@@ -1839,11 +1839,13 @@ function lookup_impls(obj) {
|
|
|
1839
1839
|
// impls, so introspection tools like `&methods-of` can answer "what
|
|
1840
1840
|
// methods will instances of this type have" without a concrete instance.
|
|
1841
1841
|
tag = obj.name.toString();
|
|
1842
|
-
|
|
1842
|
+
const builtinRecordImpls = normalize_builtin_impls(calcit_builtin_impls.record) ?? [];
|
|
1843
|
+
impls = [...builtinRecordImpls, ...(obj.impls ?? [])];
|
|
1843
1844
|
}
|
|
1844
1845
|
else if (obj instanceof CalcitEnum) {
|
|
1845
1846
|
tag = obj.name();
|
|
1846
|
-
|
|
1847
|
+
const builtinTupleImpls = normalize_builtin_impls(calcit_builtin_impls.tuple) ?? [];
|
|
1848
|
+
impls = [...builtinTupleImpls, ...(obj.impls ?? [])];
|
|
1847
1849
|
}
|
|
1848
1850
|
else if (typeof obj === "number") {
|
|
1849
1851
|
tag = "&core-number-methods";
|
|
@@ -1857,6 +1859,14 @@ function lookup_impls(obj) {
|
|
|
1857
1859
|
tag = "&core-fn-methods";
|
|
1858
1860
|
impls = normalize_builtin_impls(calcit_builtin_impls.fn);
|
|
1859
1861
|
}
|
|
1862
|
+
else if (obj == null ||
|
|
1863
|
+
typeof obj === "boolean" ||
|
|
1864
|
+
obj instanceof CalcitTag ||
|
|
1865
|
+
obj instanceof CalcitSymbol ||
|
|
1866
|
+
obj instanceof CalcitCirruQuote) {
|
|
1867
|
+
tag = "&core-scalar-impls";
|
|
1868
|
+
impls = normalize_builtin_impls(calcit_builtin_impls.scalar);
|
|
1869
|
+
}
|
|
1860
1870
|
else {
|
|
1861
1871
|
return null;
|
|
1862
1872
|
}
|
|
@@ -1997,7 +2007,7 @@ export function _$n_trait_call(traitDef, method, obj, ...args) {
|
|
|
1997
2007
|
let idx = reverse ? impls.length - 1 : 0;
|
|
1998
2008
|
while (reverse ? idx >= 0 : idx < impls.length) {
|
|
1999
2009
|
const impl = impls[idx];
|
|
2000
|
-
if (impl != null && impl.origin
|
|
2010
|
+
if (impl != null && impl.origin === traitDef) {
|
|
2001
2011
|
const fn = impl.getOrNil(methodName);
|
|
2002
2012
|
if (fn != null) {
|
|
2003
2013
|
if (typeof fn !== "function") {
|
package/lib/package.json
CHANGED
package/package.json
CHANGED
package/ts-src/calcit.procs.mts
CHANGED
|
@@ -202,46 +202,35 @@ export let _$n_assert_traits = function (value: CalcitValue, traitDef: CalcitVal
|
|
|
202
202
|
if (!(traitDef instanceof CalcitTrait)) {
|
|
203
203
|
throw new Error(`&assert-traits expected a trait definition, but received: ${toString(traitDef, true)}`);
|
|
204
204
|
}
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
break;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
if (!exists) missing.push(method.toString());
|
|
205
|
+
// Use the same merged builtin + attached impl list as method dispatch.
|
|
206
|
+
// Otherwise records and tuples can call a builtin method while
|
|
207
|
+
// `assert-traits` incorrectly claims that the corresponding trait is absent.
|
|
208
|
+
const pair = lookup_impls(value);
|
|
209
|
+
if (pair == null) {
|
|
210
|
+
throw new Error(`&assert-traits cannot resolve impls for: ${toString(value, true)}`);
|
|
211
|
+
}
|
|
212
|
+
const impls = pair[0];
|
|
213
|
+
const reverse =
|
|
214
|
+
value instanceof CalcitRecord || value instanceof CalcitTuple || value instanceof CalcitStruct || value instanceof CalcitEnum;
|
|
215
|
+
const ordered = reverse ? [...impls].reverse() : impls;
|
|
216
|
+
const selected = ordered.find((impl) => impl != null && impl.origin === traitDef);
|
|
217
|
+
if (selected == null) {
|
|
218
|
+
const available = impls
|
|
219
|
+
.filter((impl) => impl?.origin != null)
|
|
220
|
+
.map((impl) => impl.origin!.name.toString())
|
|
221
|
+
.join(" ");
|
|
222
|
+
throw new Error(
|
|
223
|
+
`assert-traits failed: ${toString(value, true)} does not nominally implement ${traitDef.toString()}. Available trait impls: ${
|
|
224
|
+
available || "(none)"
|
|
225
|
+
}`
|
|
226
|
+
);
|
|
231
227
|
}
|
|
228
|
+
const missing = traitDef.methods.filter((method) => selected.getOrNil(method) == null);
|
|
232
229
|
if (missing.length > 0) {
|
|
233
|
-
const available: string[] = [];
|
|
234
|
-
for (let j = 0; j < impls.length; j++) {
|
|
235
|
-
const impl = impls[j];
|
|
236
|
-
if (impl == null) continue;
|
|
237
|
-
for (let k = 0; k < impl.fields.length; k++) {
|
|
238
|
-
available.push(impl.fields[k].toString());
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
230
|
throw new Error(
|
|
242
|
-
`assert-traits failed: ${toString(
|
|
231
|
+
`assert-traits failed: impl ${selected.name.toString()} for trait ${traitDef.name.toString()} is incomplete. Missing: ${missing.join(
|
|
243
232
|
" "
|
|
244
|
-
)}
|
|
233
|
+
)}`
|
|
245
234
|
);
|
|
246
235
|
}
|
|
247
236
|
return value;
|
|
@@ -306,12 +295,14 @@ export let _$n_impl_$o__$o_new = (name: CalcitValue, ...pairs: CalcitValue[]): C
|
|
|
306
295
|
if (name === undefined) throw new Error("&impl::new expected arguments");
|
|
307
296
|
const origin = name instanceof CalcitTrait ? name : null;
|
|
308
297
|
const implName = origin ? origin.name : castTag(name);
|
|
309
|
-
if (pairs.length === 0) {
|
|
310
|
-
return new CalcitImpl(implName, [], [], origin);
|
|
311
|
-
}
|
|
312
298
|
const entries: Array<{ tag: CalcitTag; value: CalcitValue }> = [];
|
|
313
|
-
|
|
314
|
-
|
|
299
|
+
let sourcePairs = pairs;
|
|
300
|
+
if (pairs.length === 1 && pairs[0] instanceof CalcitImpl) {
|
|
301
|
+
const sourceImpl = pairs[0];
|
|
302
|
+
sourcePairs = sourceImpl.fields.map((field, idx) => new CalcitTuple(field, [sourceImpl.values[idx]], null));
|
|
303
|
+
}
|
|
304
|
+
for (let idx = 0; idx < sourcePairs.length; idx++) {
|
|
305
|
+
const pairValue = sourcePairs[idx];
|
|
315
306
|
let fieldTag: CalcitTag;
|
|
316
307
|
let value: CalcitValue;
|
|
317
308
|
if (pairValue instanceof CalcitTuple) {
|
|
@@ -338,6 +329,21 @@ export let _$n_impl_$o__$o_new = (name: CalcitValue, ...pairs: CalcitValue[]): C
|
|
|
338
329
|
}
|
|
339
330
|
const fields = entries.map((entry) => entry.tag);
|
|
340
331
|
const values = entries.map((entry) => entry.value);
|
|
332
|
+
if (origin != null) {
|
|
333
|
+
const missing = origin.methods.filter((method) => !fields.some((field) => field.value === method.value));
|
|
334
|
+
const unexpected = fields.filter((field) => !origin.methods.some((method) => method.value === field.value));
|
|
335
|
+
if (missing.length > 0 || unexpected.length > 0) {
|
|
336
|
+
const details: string[] = [];
|
|
337
|
+
if (missing.length > 0) details.push(`missing methods: ${missing.join(" ")}`);
|
|
338
|
+
if (unexpected.length > 0) details.push(`methods not declared by the trait: ${unexpected.join(" ")}`);
|
|
339
|
+
throw new Error(`&impl::new does not conform to trait ${origin.name.toString()}: ${details.join("; ")}`);
|
|
340
|
+
}
|
|
341
|
+
for (const entry of entries) {
|
|
342
|
+
if (typeof entry.value !== "function") {
|
|
343
|
+
throw new Error(`&impl::new expects trait method .${entry.tag.value} to be a function, but received: ${toString(entry.value, true)}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
341
347
|
return new CalcitImpl(implName, fields, values, origin);
|
|
342
348
|
};
|
|
343
349
|
|
|
@@ -1884,6 +1890,7 @@ let calcit_builtin_impls = {
|
|
|
1884
1890
|
fn: null as CalcitImplEntry,
|
|
1885
1891
|
tuple: null as CalcitImplEntry,
|
|
1886
1892
|
record: null as CalcitImplEntry,
|
|
1893
|
+
scalar: null as CalcitImplEntry,
|
|
1887
1894
|
};
|
|
1888
1895
|
|
|
1889
1896
|
// need to register code from outside
|
|
@@ -1951,10 +1958,12 @@ function lookup_impls(obj: CalcitValue): [CalcitImpl[], string] {
|
|
|
1951
1958
|
// impls, so introspection tools like `&methods-of` can answer "what
|
|
1952
1959
|
// methods will instances of this type have" without a concrete instance.
|
|
1953
1960
|
tag = obj.name.toString();
|
|
1954
|
-
|
|
1961
|
+
const builtinRecordImpls = normalize_builtin_impls(calcit_builtin_impls.record) ?? [];
|
|
1962
|
+
impls = [...builtinRecordImpls, ...(obj.impls ?? [])];
|
|
1955
1963
|
} else if (obj instanceof CalcitEnum) {
|
|
1956
1964
|
tag = obj.name();
|
|
1957
|
-
|
|
1965
|
+
const builtinTupleImpls = normalize_builtin_impls(calcit_builtin_impls.tuple) ?? [];
|
|
1966
|
+
impls = [...builtinTupleImpls, ...(obj.impls ?? [])];
|
|
1958
1967
|
} else if (typeof obj === "number") {
|
|
1959
1968
|
tag = "&core-number-methods";
|
|
1960
1969
|
impls = normalize_builtin_impls(calcit_builtin_impls.number);
|
|
@@ -1964,6 +1973,15 @@ function lookup_impls(obj: CalcitValue): [CalcitImpl[], string] {
|
|
|
1964
1973
|
} else if (typeof obj === "function") {
|
|
1965
1974
|
tag = "&core-fn-methods";
|
|
1966
1975
|
impls = normalize_builtin_impls(calcit_builtin_impls.fn);
|
|
1976
|
+
} else if (
|
|
1977
|
+
obj == null ||
|
|
1978
|
+
typeof obj === "boolean" ||
|
|
1979
|
+
obj instanceof CalcitTag ||
|
|
1980
|
+
obj instanceof CalcitSymbol ||
|
|
1981
|
+
obj instanceof CalcitCirruQuote
|
|
1982
|
+
) {
|
|
1983
|
+
tag = "&core-scalar-impls";
|
|
1984
|
+
impls = normalize_builtin_impls(calcit_builtin_impls.scalar);
|
|
1967
1985
|
} else {
|
|
1968
1986
|
return null;
|
|
1969
1987
|
}
|
|
@@ -2110,7 +2128,7 @@ export function _$n_trait_call(traitDef: CalcitValue, method: CalcitValue, obj:
|
|
|
2110
2128
|
let idx = reverse ? impls.length - 1 : 0;
|
|
2111
2129
|
while (reverse ? idx >= 0 : idx < impls.length) {
|
|
2112
2130
|
const impl = impls[idx];
|
|
2113
|
-
if (impl != null && impl.origin
|
|
2131
|
+
if (impl != null && impl.origin === traitDef) {
|
|
2114
2132
|
const fn = impl.getOrNil(methodName);
|
|
2115
2133
|
if (fn != null) {
|
|
2116
2134
|
if (typeof fn !== "function") {
|