@calcit/procs 0.12.57 → 0.12.58

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.
Binary file
@@ -1,613 +1,107 @@
1
- # Runtime Traits for Calcit (Draft)
1
+ # Runtime Traits for Calcit
2
2
 
3
- > 目标:在运行时提供一套 Trait 机制,即使无法静态检查,也能把错误前移到调用点,并允许类型级别的能力声明(如 `Show`、`Add`、`Multiply` 等)。
3
+ > 状态:已落地的设计基线,更新于 2026-08-02。用户语法与示例以 [`docs/features/traits.md`](../docs/features/traits.md) 为准;本文件记录实现边界与后续工作。
4
4
 
5
- > 文档说明(更新于 2026-02-08):本文件用于“解释当前实现 + 修正计划”。其中“当前实现/分派规则”以现有 Rust runtime / preprocess / JS backend 的真实行为为准;计划部分提供分阶段目标与验收口径。
5
+ ## 目标
6
6
 
7
- ## 内置 Trait 设计
7
+ Calcit 从动态原型式方法分派演进到 trait 模型,但不把语言变成复杂的全静态类型系统。当前设计追求三点:
8
8
 
9
- 参考 Haskell (Eq, Ord, Show, Num, Foldable, Functor) 与 Rust (Display, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Iterator, Add, Mul, From/Into) 的设计,结合 Calcit 的动态类型特性,定义以下内置 Trait:
9
+ 1. 能力约束是 nominal 的,能从 impl 来源推导,不依赖“恰好有同名方法”。
10
+ 2. 普通方法调用仍保持轻量、可组合,旧代码可以逐步迁移。
11
+ 3. native、JS 的语义一致;WASM 只承担内部 codegen 验证,不新增完整 trait runtime。
10
12
 
11
- ### 核心 Trait 分类
13
+ ## 正交的三个层次
12
14
 
13
- #### 1. 显示与调试 (Display & Debug)
15
+ ### Trait definition
14
16
 
15
- | Trait | 方法签名 | 描述 | Haskell 对应 | Rust 对应 |
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
- **默认实现**: 所有内置类型 (`nil`, `bool`, `number`, `string`, `tag`, `symbol`, `list`, `map`, `set`, `tuple`, `record`, `fn`) 都有默认实现。
19
+ Snapshot schema 暂时只保存 symbol 形式的 trait reference。预处理在 trait value 已求值时使用 nominal metadata;只有尚未求值的 schema placeholder 才按定义结构或 bare name 回退。把 namespace-qualified trait id 持久化到 schema 是后续工作。
21
20
 
22
- #### 2. 相等与比较 (Equality & Ordering)
21
+ ### Trait impl
23
22
 
24
- | Trait | 方法签名 | 描述 | Haskell 对应 | Rust 对应 |
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
- - `Eq`: 所有值类型
32
- - `Compare`: `number`, `string`, `tag`, `list`(字典序)
30
+ `assert-traits`、`:where` 和 `&trait-call` 都只接受这种 impl 作为能力证据。不同 impl 的方法不会被拼成一个虚构实现。
33
31
 
34
- #### 3. 算术运算 (Arithmetic)
32
+ ### Inherent method bag
35
33
 
36
- | Trait | 方法签名 | 描述 | Haskell 对应 | Rust 对应 |
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
- - `number`: 全部算术 Trait
47
- - `string`: `Add`(字符串拼接)
48
- - `list`: `Add`(列表连接)
38
+ ## 分派规则
49
39
 
50
- #### 4. 集合操作 (Collection)
40
+ ### `.method`
51
41
 
52
- | Trait | 方法签名 | 描述 | Haskell 对应 | Rust 对应 |
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
- **默认实现**: `string`, `list`, `map`, `set`
44
+ - 用户 struct/enum 附加的 impl:从后向前,last-wins;
45
+ - core builtin impl list:从前向后,first-wins,保持内建方法优先级兼容性。
60
46
 
61
- #### 5. 可迭代 (Iterable)
47
+ 它可以命中 nominal trait impl 或 inherent method bag。相同方法名存在多个候选时,顺序决定结果。
62
48
 
63
- | Trait | 方法签名 | 描述 | Haskell 对应 | Rust 对应 | 备注 |
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
- - 不单独定义 `Functor`/`Filterable`,直接在集合类型上提供 `.map`/`.filter` 方法
72
- - 或统一为 `Collection` trait,包含 `map`, `filter`, `fold` 全套操作
73
- - 类似 Rust 的 `Iterator` 或 JavaScript 的 `Array` 方法
53
+ ### `assert-traits` `:where`
74
54
 
75
- **默认实现**: `list`, `map`, `set`, `string`
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
- - Haskell 的 `Functor`/`Monad` 威力在于类型系统保证代数定律
80
- - 动态语言无法静态检查这些定律,过度抽象可能适得其反
81
- - 推荐:借鉴概念,但保持实用导向(用户更关心"能不能 map",而非"是不是 Functor")
61
+ 当前主要 core traits:
82
62
 
83
- #### 6. 哈希与克隆 (Hash & Clone)
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
- | Trait | 方法签名 | 描述 | Haskell 对应 | Rust 对应 |
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
- **默认实现**: 所有不可变值类型自动满足(Calcit 默认 immutable)
77
+ ## 后端约束
91
78
 
92
- #### 7. 默认值与转换 (Default & Conversion)
79
+ ### Native
93
80
 
94
- | Trait | 方法签名 | 描述 | Haskell 对应 | Rust 对应 |
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
- - `Default`: `nil`→`nil`, `number`→`0`, `string`→`""`, `list`→`[]`, `map`→`{}`, `set`→`#{}`
102
- - `From`: 常见转换如 `number->string`, `list->set`, `map->list`
85
+ JS 是主要业务运行目标。trait identity 使用对象身份;builtin 注册使用完整 impl list;impl 方法集合与 callable 校验和 native 对齐。类型签名的主要校验发生在生成 JS 之前的 native preprocess。
103
86
 
104
- ### Trait 定义语法(当前实现)
87
+ ### WASM
105
88
 
106
- 当前 `deftrait` 宏仅用于声明 trait 名称与方法列表,内部调用 `&trait::new`。
89
+ WASM 是内部验证后端。预处理已消除的 trait metadata 不影响 codegen;若运行路径仍残留 `&impl::new`、struct/enum `impl-traits` `&assert-traits`,codegen 明确失败。这里不实现 JS/native 等价的 runtime trait table,也不允许静默返回 `nil` 掩盖语义缺失。
107
90
 
108
- ```cirru
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
- ### Trait 实现语法(当前实现)
100
+ ## 后续工作(控制复杂度)
117
101
 
118
- 使用 `impl-traits` 函数式组合的方式为类型添加 Trait 实现(当前仍以 record 承载 impl,计划升级为独立 impl 结构,见后文):
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
- ```cirru
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.
@@ -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 */
@@ -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
- // For records/tuples, only check instance impls (not builtin fallbacks),
180
- // matching the Rust runtime behavior of collect_impl_records_for_value.
181
- let impls;
182
- if (value instanceof CalcitRecord) {
183
- impls = value.structRef.impls ?? [];
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
- const available = [];
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
- for (let idx = 0; idx < pairs.length; idx++) {
280
- const pairValue = pairs[idx];
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
- impls = obj.impls;
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
- impls = obj.impls;
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 != null && impl.origin.name.value === traitDef.name.value) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calcit/procs",
3
- "version": "0.12.57",
3
+ "version": "0.12.58",
4
4
  "main": "./lib/calcit.procs.mjs",
5
5
  "devDependencies": {
6
6
  "@types/node": "^25.7.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calcit/procs",
3
- "version": "0.12.57",
3
+ "version": "0.12.58",
4
4
  "main": "./lib/calcit.procs.mjs",
5
5
  "devDependencies": {
6
6
  "@types/node": "^25.7.0",
@@ -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
- // For records/tuples, only check instance impls (not builtin fallbacks),
206
- // matching the Rust runtime behavior of collect_impl_records_for_value.
207
- let impls: CalcitImpl[];
208
- if (value instanceof CalcitRecord) {
209
- impls = value.structRef.impls ?? [];
210
- } else if (value instanceof CalcitTuple) {
211
- impls = value.impls ?? [];
212
- } else {
213
- const pair = lookup_impls(value);
214
- if (pair == null) {
215
- throw new Error(`&assert-traits cannot resolve impls for: ${toString(value, true)}`);
216
- }
217
- impls = pair[0];
218
- }
219
- const missing: string[] = [];
220
- for (let i = 0; i < traitDef.methods.length; i++) {
221
- const method = traitDef.methods[i];
222
- let exists = false;
223
- for (let j = 0; j < impls.length; j++) {
224
- const impl = impls[j];
225
- if (impl != null && impl.getOrNil(method) != null) {
226
- exists = true;
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(value, true)} does not implement ${traitDef.toString()}. Missing: ${missing.join(
231
+ `assert-traits failed: impl ${selected.name.toString()} for trait ${traitDef.name.toString()} is incomplete. Missing: ${missing.join(
243
232
  " "
244
- )}. Available: ${available.join(" ")}`
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
- for (let idx = 0; idx < pairs.length; idx++) {
314
- const pairValue = pairs[idx];
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
- impls = obj.impls;
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
- impls = obj.impls;
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 != null && impl.origin.name.value === traitDef.name.value) {
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") {