@calcit/procs 0.12.49 → 0.12.50
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/07-19-doc-knowledge-index-rfc.md +195 -0
- package/RFCs/07-19-type-introspection-consistency-rfc.md +73 -0
- package/RFCs/README.md +2 -0
- package/build.rs +2 -4
- package/editing-history/2026-04-07-1550-path-format-at-prefix-migration.md +40 -0
- package/editing-history/2026-07-07-0000-semantic-tree-navigation.md +14 -9
- package/editing-history/2026-07-08-1650-features-and-js-object-type.md +2 -0
- package/editing-history/2026-07-09-1700-concise-method-representation.md +8 -0
- package/editing-history/2026-07-19-1919-type-introspection-consistency.md +24 -0
- package/editing-history/2026-07-19-1930-schema-tag-roundtrip-fix.md +64 -0
- package/editing-history/20260719-2236-doc-knowledge-graph.md +27 -0
- package/editing-history/20260719-2245-doc-graph-missing-filter.md +17 -0
- package/editing-history/20260719-2310-doc-graph-explain-full.md +16 -0
- package/lib/calcit-data.mjs +20 -1
- package/lib/calcit.procs.mjs +37 -7
- package/lib/package.json +1 -1
- package/package.json +1 -1
- package/ts-src/calcit-data.mts +20 -1
- package/ts-src/calcit.procs.mts +35 -7
package/.yarn/install-state.gz
CHANGED
|
Binary file
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# RFC: 文档知识节点与增量关系索引
|
|
2
|
+
|
|
3
|
+
状态:Draft
|
|
4
|
+
日期:2026-07-19
|
|
5
|
+
关联:`docs/docs-indexing.md`、`07-06-semantic-tree-navigation-rfc.md`
|
|
6
|
+
|
|
7
|
+
## 1. 概要
|
|
8
|
+
|
|
9
|
+
在保持 Markdown 可读、可 Git 管理的前提下,为 `cr docs` 增加一层结构化的文档知识索引:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
Markdown / Calcit snapshot
|
|
13
|
+
↓
|
|
14
|
+
文档节点与关系解析
|
|
15
|
+
↓
|
|
16
|
+
~/.config/calcit/docs-cache/
|
|
17
|
+
↓
|
|
18
|
+
搜索、跳转、LLM context、缺失检查
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Markdown 和 Calcit 源码仍然是事实来源;缓存只是可以删除并重新生成的派生数据。
|
|
22
|
+
|
|
23
|
+
## 2. 目标
|
|
24
|
+
|
|
25
|
+
1. 文档可以表达稳定的知识节点,而不要求一次性拆分所有 Markdown。
|
|
26
|
+
2. 通过 `id`、`parent`、`related`、`requires`、`leads_to` 组织树与图关系。
|
|
27
|
+
3. 通过 `namespace/definition` 将文档节点关联到真实 Calcit 定义。
|
|
28
|
+
4. 使用用户级 JSON 文本缓存,不引入 SQLite 或其他二进制数据库。
|
|
29
|
+
5. 文件内容变化时增量失效;解析器或 schema 变化时全量失效。
|
|
30
|
+
6. 能发现孤立节点、断链、未文档化定义和缺少必要章节。
|
|
31
|
+
|
|
32
|
+
## 3. 非目标
|
|
33
|
+
|
|
34
|
+
- 当前不引入 RDF、SPARQL、Neo4j 或 SurrealDB。
|
|
35
|
+
- 当前不强制把每个章节拆成独立文件。
|
|
36
|
+
- 当前不把缓存当作用户需要手工维护的知识源。
|
|
37
|
+
- 当前不改变既有 `cr docs search/read` 的默认输出语义。
|
|
38
|
+
|
|
39
|
+
## 4. 文档元数据
|
|
40
|
+
|
|
41
|
+
现有字段继续保留:`title`、`summary`、`scope`、`kind`、`category`、`aliases`、`entry_for`。
|
|
42
|
+
|
|
43
|
+
新增字段采用稳定 ID 和关系字段:
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
id: api/list/nth
|
|
47
|
+
code_refs:
|
|
48
|
+
- calcit.core/nth
|
|
49
|
+
parent: structure/list
|
|
50
|
+
related:
|
|
51
|
+
- api/list/get
|
|
52
|
+
requires:
|
|
53
|
+
- concept/indexing
|
|
54
|
+
leads_to:
|
|
55
|
+
- example/list-access
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
实现阶段允许使用 `x-calcit` / `extensions` 命名空间保存项目专属字段,避免污染通用 frontmatter。
|
|
59
|
+
|
|
60
|
+
`id` 是语义身份;`namespace/definition` 只是代码引用,不能互相替代。
|
|
61
|
+
|
|
62
|
+
## 5. 缓存模型
|
|
63
|
+
|
|
64
|
+
默认位置:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
~/.config/calcit/docs-cache/v1/<project-id>/
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
项目身份至少包含项目根目录,避免不同项目的相对路径发生冲突。缓存还记录解析器/schema 版本和内置 Calcit core snapshot 的定义指纹;任一输入变化都会触发重建。
|
|
71
|
+
|
|
72
|
+
缓存由两部分组成:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"nodes": [],
|
|
77
|
+
"edges": []
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
每个文件还需要记录:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"path": "docs/structures/list.md",
|
|
86
|
+
"content_hash": "...",
|
|
87
|
+
"parser_version": 1,
|
|
88
|
+
"schema_version": 1,
|
|
89
|
+
"node_ids": ["structure/list"]
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
内容 hash 不变时可以复用文件级解析结果;parser/schema 版本变化时必须重新解析。
|
|
94
|
+
|
|
95
|
+
## 6. 关系模型
|
|
96
|
+
|
|
97
|
+
树关系:
|
|
98
|
+
|
|
99
|
+
- `parent`
|
|
100
|
+
- `contains`
|
|
101
|
+
- `leads_to`
|
|
102
|
+
|
|
103
|
+
语义关系:
|
|
104
|
+
|
|
105
|
+
- `related`
|
|
106
|
+
- `requires`
|
|
107
|
+
- `documents`
|
|
108
|
+
- `implements`
|
|
109
|
+
- `example_of`
|
|
110
|
+
|
|
111
|
+
节点采用树形入口,关系采用独立边表,避免把循环关系硬塞进嵌套结构。
|
|
112
|
+
|
|
113
|
+
## 7. 分阶段计划
|
|
114
|
+
|
|
115
|
+
### Phase A:元数据兼容与增量缓存基础
|
|
116
|
+
|
|
117
|
+
- 扩展 frontmatter 解析器,保留未知扩展字段的兼容空间;
|
|
118
|
+
- 定义节点、边、文件缓存的 JSON schema;
|
|
119
|
+
- 增加内容 hash、parser version、schema version;
|
|
120
|
+
- 保持现有搜索和读取命令兼容;
|
|
121
|
+
- 添加解析和失效测试。
|
|
122
|
+
|
|
123
|
+
### Phase B:导航查询
|
|
124
|
+
|
|
125
|
+
已完成第一版面向知识节点的查询:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
cr docs graph build
|
|
129
|
+
cr docs graph check
|
|
130
|
+
cr docs graph children <node>
|
|
131
|
+
cr docs graph related <node>
|
|
132
|
+
cr docs graph path <from> <to>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
查询当前从 Markdown 构建 JSON cache,并通过双向 BFS 查找关系路径:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
cr docs graph path core/features/list core/run/edit-tree
|
|
139
|
+
# core/features/list -> core/run/query -> core/run/edit-tree
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
当前已支持通过 `code_refs` 反查关联文档节点;后续再增加源码定义、示例和正文聚合:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
cr docs graph explain <definition>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Phase C:完整性检查
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
cr docs graph check
|
|
152
|
+
cr docs graph orphans
|
|
153
|
+
cr docs graph missing
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
检查断链、孤立节点、无正文定义、缺少示例和缺少必要章节。
|
|
157
|
+
|
|
158
|
+
当前已实现:
|
|
159
|
+
|
|
160
|
+
- `cr docs graph check`:检查关系边是否指向已知文档节点;
|
|
161
|
+
- `cr docs graph missing [--ns <prefix>] [--limit <n>]`:按 namespace 分批检查带有 snapshot 文档说明、但没有 `code_refs` 的定义;
|
|
162
|
+
- `cr docs graph orphans`:检查没有任何关系的文档节点。
|
|
163
|
+
|
|
164
|
+
## 8. 验证标准
|
|
165
|
+
|
|
166
|
+
- 删除缓存后,查询结果与重新构建结果一致;
|
|
167
|
+
- 只修改一个 Markdown 文件时,不重新解析其他未变化文件;
|
|
168
|
+
- 修改 parser/schema 版本时会全量失效;
|
|
169
|
+
- 不带新增元数据的旧 Markdown 行为保持不变;
|
|
170
|
+
- 缓存只包含 JSON/文本,不提交到 Git;
|
|
171
|
+
- `cargo fmt`、`cargo clippy -- -D warnings`、`cargo test` 通过。
|
|
172
|
+
|
|
173
|
+
### 当前验证矩阵
|
|
174
|
+
|
|
175
|
+
Phase A 当前已验证:
|
|
176
|
+
|
|
177
|
+
| 案例 | 结果 |
|
|
178
|
+
| --- | --- |
|
|
179
|
+
| 没有 frontmatter 的旧文档 | 保持空知识元数据,不影响旧搜索 |
|
|
180
|
+
| 只有 `id` / `parent` | 正确解析树入口 |
|
|
181
|
+
| 单值和多值关系混用 | 保持声明顺序并正确合并 |
|
|
182
|
+
| `related` / `requires` / `leads_to` 交叉关系 | 可表达树外关系 |
|
|
183
|
+
| 旧的 `title` / `aliases` / `entry_for` | 不会误解析为知识关系 |
|
|
184
|
+
| 未闭合 frontmatter | 丢弃知识元数据,避免部分解析污染索引 |
|
|
185
|
+
| 文件内容变化 | 内容 hash 失效 |
|
|
186
|
+
| parser/schema 版本变化 | 缓存失效 |
|
|
187
|
+
| `code_refs` 反查 definition | 返回关联文档节点 |
|
|
188
|
+
| 不同项目根目录 | 缓存路径隔离 |
|
|
189
|
+
| nodes/edges/files JSON 往返 | 保持结构和关系信息 |
|
|
190
|
+
|
|
191
|
+
当前仍未接入的能力:
|
|
192
|
+
|
|
193
|
+
- 从 Calcit snapshot 自动生成 `code_refs`(当前已加载内置 core snapshot,并校验引用是否可解析;应用/模块 snapshot 仍未接入);
|
|
194
|
+
- 自动根据 definition 的 `:examples` 检查文档示例覆盖;
|
|
195
|
+
- 从 graph 节点聚合正文、schema、examples 的 LLM context。
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# RFC: 类型自省(Type Introspection)一致性改进
|
|
2
|
+
|
|
3
|
+
状态:Implemented(1-3 项已落地,第 4 项按计划延后)
|
|
4
|
+
日期:2026-07-19
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. 概要
|
|
9
|
+
|
|
10
|
+
调研 Calcit 当前"查询一个类型能怎么交互"的能力——有哪些字段、有哪些方法(无论是内建还是通过 trait/impl 附加的)——发现相关能力**分散在多个不对齐的 API 里**,且存在几处"数据已经在运行时结构里,但没有暴露给 Calcit 代码"的具体 gap。本 RFC 记录调研结论,并给出可落地的修复项清单。
|
|
11
|
+
|
|
12
|
+
## 2. 调研结论
|
|
13
|
+
|
|
14
|
+
### 2.1 方法(methods)查询——统一性最好的部分
|
|
15
|
+
|
|
16
|
+
`&methods-of` / `&inspect-methods`([meta.rs](../src/builtins/meta.rs))把"内建方法"(list/map/string/number/set/fn 各自的 `&core-*-impls`)和"用户 `defimpl`/`impl-traits`"统一表示成 `CalcitImpl`,按优先级顺序去重合并,`&methods-of` 对内建/自定义一视同仁。
|
|
17
|
+
|
|
18
|
+
局限:`collect_impl_records_for_value` 只接受**实例值**(`Tuple`/`Record`/`List`/`Map`/`Number`/`Str`/`Set`/`Fn`/`Proc`),不接受裸的 `Calcit::Struct`/`Calcit::Enum`/`Calcit::Trait`(类型定义本身)。也就是说 `&methods-of Person`(还没造实例)会直接报错,必须先构造一个实例才能查询"这个类型将来会有哪些方法"。
|
|
19
|
+
|
|
20
|
+
`NativeMethodsOf` 的类型签名是 `arg_types: vec![dynamic_tag()]`([proc_name.rs](../src/calcit/proc_name.rs)),说明这一限制纯粹是**运行时实现**的限制,不是类型检查器强加的——修复它不需要碰类型系统。
|
|
21
|
+
|
|
22
|
+
### 2.2 字段(fields)查询——能用,但类型签名与实现脱节
|
|
23
|
+
|
|
24
|
+
`to-pairs`/`keys`/`&record:to-map` 在运行时(`to_pairs` in [maps.rs](../src/builtins/maps.rs))原生支持 `Calcit::Record`,能拿到字段名(tag)。
|
|
25
|
+
|
|
26
|
+
但 `ToPairs` 的类型签名声明 `arg_types: vec![some_tag("map")]`,而 `matches_with_bindings`([type_annotation.rs](../src/calcit/type_annotation.rs))里 `(TypeRef("map"), Record(base))` 这一分支是拿 `"map"` 去匹配 `base.name`(即结构体自己的名字,比如 `"Person"`),显然不相等 → **对 record 调用 `to-pairs`/`keys` 会在类型检查阶段被判定为类型不匹配并产生告警**,即便运行时完全正确。这是文档/类型签名与实际能力脱节的具体例子。
|
|
27
|
+
|
|
28
|
+
没有实例、只有裸类型定义时(刚 `defstruct Person ...` 还没构造实例),**没有**可编程 API 能拿到字段名列表当数据用;唯一能看到字段的方式是 `println`/`str` 对 struct 值做格式化输出人肉读。
|
|
29
|
+
|
|
30
|
+
### 2.3 `Display`(`println`/`str`)在三种类型定义值之间不对称
|
|
31
|
+
|
|
32
|
+
`impl fmt::Display for Calcit`([calcit.rs](../src/calcit.rs)):
|
|
33
|
+
|
|
34
|
+
| 类型定义值 | 输出示例 | 包含信息 |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `Struct` | `(%struct :Person (:name string) (:age number))` | 字段名 + 类型 |
|
|
37
|
+
| `Trait` | `(trait Name :m1 :m2)` | 仅方法名,**没有**方法签名类型 |
|
|
38
|
+
| `Enum` | `(%enum :Name)` | **仅名字**,连 variant 列表都没有 |
|
|
39
|
+
|
|
40
|
+
`CalcitEnum` 内部其实完整保存着 `variants()`(tag + payload_types,见 [sum_type.rs](../src/calcit/sum_type.rs)),数据都在,只是没接到 `Display` 里——这是一处"能力已具备却未暴露"的明显 gap,且没有任何测试覆盖这个输出格式(`grep "%struct"`/`"%enum"` 在 `calcit/*.cirru` 测试里零命中)。
|
|
41
|
+
|
|
42
|
+
### 2.4 结论
|
|
43
|
+
|
|
44
|
+
- 没有一个统一的"describe 类型"入口能同时列出 fields + methods(不管来源);需要组合调用不同风格的 API。
|
|
45
|
+
- 对"类型定义本身"(还未实例化)的支持最弱:`&methods-of` 不接受、字段列表没有编程接口、`Enum`/`Trait` 的打印格式也比 `Struct` 缺信息。
|
|
46
|
+
|
|
47
|
+
## 3. 修复计划(按优先级/风险排序)
|
|
48
|
+
|
|
49
|
+
1. ✅ **`Enum` 的 `Display` 补上 variants**(最小、最安全,纯展示层修复,无 API 变化)。
|
|
50
|
+
- 实现:[src/calcit.rs](../src/calcit.rs) `impl fmt::Display for Calcit` 的 `Enum` 分支,现在遍历 `enum_def.variants()` 输出 `(:tag type1 type2 ...)`。
|
|
51
|
+
- 测试:`enum_display_includes_variants`(同文件 `#[cfg(test)] mod tests`)。
|
|
52
|
+
2. ✅ **`&methods-of` 接受裸 `Struct`/`Enum`/`Trait` 值**(直接读它们自带的 `impls` 字段),不再强制要求实例。
|
|
53
|
+
- 实现:[src/builtins/meta.rs](../src/builtins/meta.rs) `collect_impl_records_for_value` 新增 `Struct`/`Enum` 分支;`iter_impls_in_precedence_order` 把 `Struct`/`Enum` 纳入与 `Tuple`/`Record` 相同的“后定义优先”倒序规则;`Trait` 单独处理(直接读 `trait_def.methods`,因为 trait 本身没有 `impls` 字段,是直接声明方法名)。`methods_of`/`inspect_methods` 都做了对应分支。
|
|
54
|
+
- JS 同步:[ts-src/calcit.procs.mts](../ts-src/calcit.procs.mts) 的 `lookup_impls`/`_$n_methods_of`/`_$n_inspect_methods` 同步补齐 `CalcitStruct`/`CalcitEnum`/`CalcitTrait` 分支。
|
|
55
|
+
- 测试:[calcit/test-traits.cirru](../calcit/test-traits.cirru) 的 `test-method-introspection` 新增对 `impl-traits Person0 MyFooImpl`(裸 struct)、`DemoBar`(裸 enum)、`MyFoo`(裸 trait)调用 `&methods-of` 的断言,`yarn check-all`(rs/js/ir/wasm 四目标)全部通过。
|
|
56
|
+
3. ✅ **修正 `to-pairs`/`keys` 的类型签名**,让 record 也能匹配,消除虚假类型告警。
|
|
57
|
+
- 实现:[src/calcit/type_annotation.rs](../src/calcit/type_annotation.rs) `matches_with_bindings` 里 `(TypeRef, Record)` 分支新增:当 `TypeRef` 名字就是泛化的 `"map"` 时,结构性地匹配任意 record(不要求 record 名字等于 `"map"`)。
|
|
58
|
+
- 测试:新增单元测试 `generic_map_type_ref_accepts_records_structurally`,直接验证 `TypeRef("map")` 与 `Record(Person)` 现在双向匹配,同时确认无关的 `TypeRef` 名字仍然不会误匹配。
|
|
59
|
+
- 备注:实测 `cr --check-only` 在当前仓库测试集里并未因这个 gap 产生可观察的告警(`test-record.cirru` 里 `keys p2` 这行本来就没有触发过告警,猜测是这条路径上的静态类型推断没有把 `p2` 识别为具体的 `Record` 类型,所以没有触发 `check_proc_arg_types` 这条检查分支)。但 `matches_with_bindings` 的错误比较逻辑本身是真实存在的 bug,属于防御性修复:一旦未来静态推断能力增强(例如给变量加显式类型标注后传入 `to-pairs`/`keys`),就不会再误报。
|
|
60
|
+
4. ⏸️ **(可选,视时间,本轮未实现)** 新增 `&struct:fields`/`&enum:variants` 之类的编程接口,让“裸类型定义”的字段/variant 列表也能被程序消费,而不仅仅是打印文本。
|
|
61
|
+
- 未实现原因:新增一个 proc 需要贯穿 `proc_name.rs`(注册+类型签名)、`builtins.rs`(分发)、`builtins/records.rs`或`meta.rs`(实现)、以及 JS/IR/WASM 三个 codegen 目标的同步实现,工作量与收益相比前三项更低(前三项已经解决了运行时能力不对齐的核心问题;字段/variant 名字目前仍可通过 `Display`/`println` 人肉获取,只是没有编程接口)。留作后续独立迭代。
|
|
62
|
+
|
|
63
|
+
## 4. 兼容性 / 风险
|
|
64
|
+
|
|
65
|
+
- 第 1、2、3 项都已实现并通过 `cargo test`、`cargo run --bin cr -- calcit/test.cirru`、`yarn check-all`、`cargo clippy -- -D warnings`、`cargo fmt` 验证,纯粹的能力扩展/告警范围放宽,不改变现有行为,向后兼容。
|
|
66
|
+
- 第 4 项延后,不影响现有行为。
|
|
67
|
+
|
|
68
|
+
## 5. 验证方式(已执行)
|
|
69
|
+
|
|
70
|
+
- `cargo test`:新增/更新的 Rust 单元测试全部通过,覆盖 `Display for Enum`、`&methods-of` 对裸类型定义的调用、`to-pairs`/`keys` 的类型匹配放宽。
|
|
71
|
+
- `cargo run --bin cr -- calcit/test.cirru`:Cirru 集成测试套件全部通过,包括新增的裸类型 `&methods-of` 断言。
|
|
72
|
+
- `yarn check-all`:JS/IR/WASM 三个目标同步验证通过(`&methods-of` 的 JS 实现已同步补齐)。
|
|
73
|
+
- `cargo clippy -- -D warnings` / `cargo fmt`:均无告警。
|
package/RFCs/README.md
CHANGED
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
| `06-15-effects-graph-rfc.md` | Draft | `cr analyze effects-graph`:State/Transform/Effect 语义分解图与类型驱动 effect 标注路线。 |
|
|
31
31
|
| `06-29-cr-exec-cli-builtins-rfc.md` | **Active** | `cr exec` + `calcit.cli/*` 内建函数:绕过 Shell 转义的 Cirru 函数调用方案。 |
|
|
32
32
|
| `07-06-semantic-tree-navigation-rfc.md` | Draft | 语义化树形导航与编辑:路径标注、多候选交互、锚点搜索替换、结构化查询语言。 |
|
|
33
|
+
| `07-19-doc-knowledge-index-rfc.md` | Draft | Markdown/Calcit snapshot 的知识节点、关系索引与用户级增量缓存方案。 |
|
|
34
|
+
| `07-19-type-introspection-consistency-rfc.md` | Implemented | 类型自省一致性改进:`&methods-of` 支持裸类型定义、`Enum` Display 补 variants、`to-pairs`/`keys` 类型签名修正(第 4 项可选新增 proc 延后)。 |
|
|
33
35
|
|
|
34
36
|
## 已执行的清理
|
|
35
37
|
|
package/build.rs
CHANGED
|
@@ -305,10 +305,8 @@ fn parse_code_entry(edn: Edn, owner: &str) -> Result<CodeEntry, String> {
|
|
|
305
305
|
"examples" => examples = from_edn(value.clone()).map_err(|e| format!("{owner}: invalid `:examples`: {e}"))?,
|
|
306
306
|
"tags" => tags = parse_tags_from_edn(value, owner)?,
|
|
307
307
|
"code" => code = Some(from_edn(value.clone()).map_err(|e| format!("{owner}: invalid `:code`: {e}"))?),
|
|
308
|
-
"schema" => {
|
|
309
|
-
|
|
310
|
-
schema = Some(parse_schema_from_edn(value, owner).map_err(|e| format!("{owner}: invalid `:schema`: {e}"))?);
|
|
311
|
-
}
|
|
308
|
+
"schema" if !matches!(value, Edn::Nil) => {
|
|
309
|
+
schema = Some(parse_schema_from_edn(value, owner).map_err(|e| format!("{owner}: invalid `:schema`: {e}"))?);
|
|
312
310
|
}
|
|
313
311
|
_ => {}
|
|
314
312
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
## Path format migration: `@` prefix for display paths
|
|
2
|
+
|
|
3
|
+
### What changed
|
|
4
|
+
|
|
5
|
+
All display-level Cirru tree paths now use `@` prefix notation when rendered to user.
|
|
6
|
+
|
|
7
|
+
### Display format change
|
|
8
|
+
- `1.2.3.4` → `@1.2.3.4` (bare path)
|
|
9
|
+
- `[1.2.3.4]` → `[@1.2.3.4]` (bracketed path)
|
|
10
|
+
- `NodeLocation::Display` now outputs `ns/def [@1.2.3]` instead of `ns/def [1.2.3]`
|
|
11
|
+
|
|
12
|
+
### Backward compatibility
|
|
13
|
+
- `parse_path()` strips leading `@` if present, so CLI input still accepts both `@1.2.3` and `1.2.3`
|
|
14
|
+
- Error messages and help text updated to show `@` format
|
|
15
|
+
|
|
16
|
+
### Files modified
|
|
17
|
+
|
|
18
|
+
**Core functions:**
|
|
19
|
+
- `src/bin/cli_handlers/common.rs`: `format_path()` returns `@1.2.3`; `parse_path()` strips `@` prefix; test assertions updated
|
|
20
|
+
- `src/calcit.rs`: `NodeLocation::Display` outputs `[@1.2.3]`; test assertion updated
|
|
21
|
+
- `src/bin/cli_handlers/cirru_validator.rs`: local `format_path()` updated
|
|
22
|
+
- `src/type_coverage.rs`: `format_cirru_path()` updated
|
|
23
|
+
- `src/bin/cli_handlers/chunk_display.rs`: `compare_coords()` strips `@`; test `fragment()` helper uses `@`
|
|
24
|
+
- `src/runner/preprocess/mod.rs`: `[&inspect-type]` debug output uses `@`
|
|
25
|
+
|
|
26
|
+
**Tree/edit handlers:**
|
|
27
|
+
- `src/bin/cli_handlers/tree.rs`: inline path formatting in swap handler
|
|
28
|
+
- `src/bin/cli_handlers/edit.rs`: error message example
|
|
29
|
+
- `src/cli_args.rs`: help text examples (3 locations)
|
|
30
|
+
|
|
31
|
+
**Documentation:**
|
|
32
|
+
- `docs/CalcitAgent.md`: 19 path examples updated
|
|
33
|
+
- `docs/run/agent-advanced.md`: 23 path examples updated
|
|
34
|
+
- `docs/run/edit-tree.md`: 6 path examples updated
|
|
35
|
+
- `docs/Respo-Agent.md` (respo project): 26 path examples updated
|
|
36
|
+
|
|
37
|
+
### Why
|
|
38
|
+
- `@` prefix helps LLMs distinguish path coordinates from other dotted numeric patterns (like version numbers or floats)
|
|
39
|
+
- `@` is safe in shell (no escaping needed)
|
|
40
|
+
- All display output now consistent with `@` prefix
|
|
@@ -7,38 +7,43 @@
|
|
|
7
7
|
## 实现功能
|
|
8
8
|
|
|
9
9
|
### L1: `--path-annotations`
|
|
10
|
+
|
|
10
11
|
- `cr tree show` 新增 `--path-annotations` flag
|
|
11
12
|
- 开启后递归为所有嵌套 `Cirru::List` 末尾追加 `; "previous node path: X.Y.Z"` 注释节点
|
|
12
13
|
- AST 层面操作:`children.push(Cirru::List([Cirru::Leaf(";"), Cirru::Leaf(path_string)]))`
|
|
13
14
|
- 子节点 > 8 时底部 tip 提示可开启
|
|
14
15
|
|
|
15
16
|
### L2: `--pick` 多候选选择
|
|
17
|
+
|
|
16
18
|
- `search-replace` 多匹配时展示带路径、上下文的候选列表
|
|
17
19
|
- `--pick <N>` 直接选择第 N 个候选执行替换
|
|
18
20
|
|
|
19
21
|
### L4: 语义路径表达式
|
|
22
|
+
|
|
20
23
|
- `cr query path <ns> --selector 'path heading ... nth ...'`
|
|
21
24
|
- 三个选择器:裸叶子(精确匹配)、`heading`(前缀匹配)、`nth`(位置导航)
|
|
22
25
|
- `resolve_path_expression` 函数解析并返回数字路径
|
|
23
26
|
|
|
24
27
|
### L4: `--selector` 限定搜索范围
|
|
28
|
+
|
|
25
29
|
- `search-replace --selector 'path ...'` 在语义路径定位的子树内搜索替换
|
|
26
30
|
- 与 `cr query path` 共用同一套选择器语法
|
|
27
31
|
|
|
28
32
|
### Phase 4: `cr query anchors`
|
|
33
|
+
|
|
29
34
|
- 遍历 AST 中 `noted @anchor:<name>` 调用,输出锚点名称和路径
|
|
30
35
|
|
|
31
36
|
## 修改文件
|
|
32
37
|
|
|
33
|
-
| 文件
|
|
34
|
-
|
|
35
|
-
| `src/cli_args.rs`
|
|
36
|
-
| `src/bin/cli_handlers/tree.rs`
|
|
37
|
-
| `src/bin/cli_handlers/query.rs`
|
|
38
|
-
| `src/bin/cli_handlers/command_echo.rs`
|
|
39
|
-
| `docs/CalcitAgent.md`
|
|
40
|
-
| `docs/run/agent-advanced.md`
|
|
41
|
-
| `RFCs/07-06-semantic-tree-navigation-rfc.md` | 新增 RFC
|
|
38
|
+
| 文件 | 变更 |
|
|
39
|
+
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
40
|
+
| `src/cli_args.rs` | 新增 `path_annotations`, `pick`, `selector` 字段;新增 `QueryPathCommand`, `QueryAnchorsCommand` |
|
|
41
|
+
| `src/bin/cli_handlers/tree.rs` | `annotate_paths` 函数;`handle_show` 路径标注 + tip;`handle_search_replace` 多候选 + selector |
|
|
42
|
+
| `src/bin/cli_handlers/query.rs` | `resolve_path_expression`(pub), `starts_with_pattern`, `handle_query_path`, `handle_query_anchors`, `find_anchors` |
|
|
43
|
+
| `src/bin/cli_handlers/command_echo.rs` | Path/Anchors 子命令 echo 支持 |
|
|
44
|
+
| `docs/CalcitAgent.md` | 精简引用新功能 |
|
|
45
|
+
| `docs/run/agent-advanced.md` | 详细示例文档 |
|
|
46
|
+
| `RFCs/07-06-semantic-tree-navigation-rfc.md` | 新增 RFC |
|
|
42
47
|
|
|
43
48
|
## 设计决策
|
|
44
49
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
## Changes
|
|
4
4
|
|
|
5
5
|
### Feature: `:features` and `:js-object` type system
|
|
6
|
+
|
|
6
7
|
- Added `features: Arc<HashSet<EdnTag>>` to `CalcitFnTypeAnnotation` for marking function capabilities
|
|
7
8
|
- Added `CalcitTypeAnnotation::JsObject` variant for opaque JS FFI data
|
|
8
9
|
- EDN round-trip for both (`features_to_edn` + `parse_fn_features_from_form`)
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
- All direct FFI callers marked with `:features $ #{} :js-ffi`
|
|
15
16
|
|
|
16
17
|
### Refactor: CLI simplification
|
|
18
|
+
|
|
17
19
|
- Removed `--json` option and `--json-input` switch from all edit/tree subcommands
|
|
18
20
|
- Auto-detect JSON (`[` prefix) vs Cirru EDN (`quote` prefix) in `parse_input_to_cirru`
|
|
19
21
|
- Simplified `read_code_input` to `(file, code)` params; stdin fallback
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Edit History - Concise Method Representation and Introspection Refactoring
|
|
2
|
+
|
|
3
|
+
- **Objective**: Improve the representation of methods inside the VM to return first-class `Calcit::Method` values rather than strings from `&methods-of`, and provide a concise, user-facing rendering suitable for REPL and dynamic inspection rather than verbose list expressions, all while keeping internal preprocessing unaffected.
|
|
4
|
+
- **Actions**:
|
|
5
|
+
- Refactored `methods_of` in `src/builtins/meta.rs` to return native `Calcit::Method` values directly with leading dots stripped.
|
|
6
|
+
- Implemented custom `turn_string` in `src/calcit.rs` for `Calcit::Method` to format method values concisely (e.g., `.add`, `.-field`, `.!native`) for `str` conversions, prints, and dynamic assertions.
|
|
7
|
+
- Updated `format_to_lisp` in `src/calcit.rs` to keep `format_to_lisp` compatible with internal macros (like `deftrait` which dynamically slices `format-to-lisp` output starting from the 9th character).
|
|
8
|
+
- Cleaned up assertions inside `calcit/test-traits.cirru`'s `test-method-introspection` to map `&methods-of` outputs to string using `str` rather than raw assertion of methods list, ensuring absolute compatibility and safety.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Edit History - Type Introspection Consistency Fixes
|
|
2
|
+
|
|
3
|
+
- **Objective**: Close gaps found while researching "how to discover a type's fields/methods in Calcit" (methods via traits/impls, fields via records, Display formatting). Documented findings and a prioritized fix plan in `RFCs/07-19-type-introspection-consistency-rfc.md`, then implemented items 1-3 (item 4 deferred as optional/out of scope for this pass).
|
|
4
|
+
|
|
5
|
+
- **Fix 1 — `Enum` `Display` now includes variants** (`src/calcit.rs`):
|
|
6
|
+
- Previously `Calcit::Enum` printed only `(%enum :Name)`, discarding the already-tracked `variants()` (tag + payload types). Now prints `(%enum :Name (:variant1 type ...) (:variant2 ...) ...)`, matching the richness of `Struct`'s Display.
|
|
7
|
+
- Test: `enum_display_includes_variants` in `src/calcit.rs`'s test module.
|
|
8
|
+
|
|
9
|
+
- **Fix 2 — `&methods-of`/`&inspect-methods` accept bare `Struct`/`Enum`/`Trait` values** (not just instances):
|
|
10
|
+
- `src/builtins/meta.rs`: `collect_impl_records_for_value` gained `Calcit::Struct`/`Calcit::Enum` arms (read their own `impls` field directly). `iter_impls_in_precedence_order` now treats `Struct`/`Enum` the same as `Tuple`/`Record` (last-attached impl wins, i.e. reversed iteration) since `&struct:impl-traits`/`&enum:impl-traits` also `.extend()` impls onto the end.
|
|
11
|
+
- `Calcit::Trait` handled as a special case (new helper `trait_dot_method_names`) since traits declare methods directly, not via an `impls` list — no `impls` field exists on `CalcitTrait`.
|
|
12
|
+
- JS target parity: `ts-src/calcit.procs.mts`'s `lookup_impls` gained `CalcitStruct`/`CalcitEnum` branches; the `reverse`-order flag (previously only `CalcitRecord || CalcitTuple`) now also includes `CalcitStruct || CalcitEnum`; `_$n_methods_of`/`_$n_inspect_methods` special-case `CalcitTrait` the same way as the Rust side.
|
|
13
|
+
- Test: `calcit/test-traits.cirru`'s `test-method-introspection` — new `let` block calling `&methods-of` on `(impl-traits Person0 MyFooImpl)` (bare struct), `DemoBar` (bare enum), `MyFoo` (bare trait), asserting the expected method tags show up. Edited via `cr tree insert-after` (module entry must be the module's own file, e.g. `cr calcit/test-traits.cirru ...`, NOT the aggregated `calcit/test.cirru` — `cr tree`/`cr edit` couldn't resolve the namespace when invoked through the aggregator, while `cr query` could).
|
|
14
|
+
|
|
15
|
+
- **Fix 3 — `to-pairs`/`keys` type signature no longer false-warns on records**:
|
|
16
|
+
- `src/calcit/type_annotation.rs`: `matches_with_bindings`'s `(TypeRef, Record)` arm previously only matched if the `TypeRef` name equaled the record's own struct name (e.g. `"map"` vs `"Person"` → false). Added: when the `TypeRef` name is the generic `"map"` placeholder, match any record structurally (records are field-name → value, i.e. map-like), in addition to the existing exact-name match.
|
|
17
|
+
- Test: `generic_map_type_ref_accepts_records_structurally` in `src/calcit/type_annotation.rs`'s test module — verifies `TypeRef("map")` matches `Record(Person)` both directions, and confirms unrelated `TypeRef` names still don't match structurally (no over-broadening).
|
|
18
|
+
- Note: empirically, `cr --check-only` on `test-record.cirru` didn't show a warning for `keys p2` even before this fix (likely because that call site's static type inference doesn't currently narrow `p2` to `Record`), so this is a defensive/forward-looking fix for when static inference improves or explicit type annotations are used, not a fix for an observed regression today.
|
|
19
|
+
|
|
20
|
+
- **Deferred (RFC item 4, optional)**: `&struct:fields`/`&enum:variants` new introspection procs — would require registering a new `CalcitProc` end-to-end (proc_name.rs signature + string mapping, builtins.rs dispatch, records.rs/meta.rs impl, JS/IR/WASM codegen parity). Judged lower priority/cost-to-benefit than items 1-3 which fixed real capability gaps; left as a follow-up.
|
|
21
|
+
|
|
22
|
+
- **Tooling note**: `cr tree`/`cr edit` subcommands need the entry file to be the *specific module's own `.cirru` file* (e.g. `calcit/test-traits.cirru`) to resolve `<ns>/<def>` targets — passing the aggregator entry (`calcit/test.cirru`, which just lists the module in `:modules`) causes `Error: "Namespace '...' not found"` for `tree`/`edit` even though `cr query defs/def` works fine through the aggregator. `--file` snippet content must be `quote`-prefixed (e.g. `quote (let (...) ...)`) even though the repo's `Agents.md` `--stdin` removal note doesn't mention this requirement explicitly.
|
|
23
|
+
|
|
24
|
+
- **Validation**: `cargo fmt`, `cargo clippy -- -D warnings`, `cargo test` (239 lib tests incl. 2 new), `cargo run --bin cr -- calcit/test.cirru`, `yarn check-all` (compile + try-rs + try-js + try-ir + try-wasm) — all clean/passing.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Schema kind tag round-trip regression fix (2026-07-19 19:30)
|
|
2
|
+
|
|
3
|
+
## Symptom
|
|
4
|
+
|
|
5
|
+
Running `cr tree insert-after`/`cr edit` on [calcit/test-traits.cirru](../calcit/test-traits.cirru)
|
|
6
|
+
(to add new `test-method-introspection` assertions) silently rewrote the `:schema`
|
|
7
|
+
field of *every other, unrelated* def in the same file from its specific kind tag
|
|
8
|
+
(`:enum`, `:trait`, `:impl`, `:struct`) down to `:schema :dynamic`. This affected
|
|
9
|
+
~15 defs (`Demo0`, `DemoBar`, `MyBar`, `MyBarImpl`, `MyFoo`, `MyFooImpl`, `Person0`,
|
|
10
|
+
etc.) that were never touched by the edit itself — any save of the file degraded
|
|
11
|
+
their schema fidelity as a side effect.
|
|
12
|
+
|
|
13
|
+
## Root cause
|
|
14
|
+
|
|
15
|
+
`:struct`/`:enum`/`:trait`/`:impl`/`:record` shorthand schema tags load into
|
|
16
|
+
`CalcitTypeAnnotation::Custom(Arc<Calcit>)` (see `CalcitTypeAnnotation::from_tag_name`
|
|
17
|
+
in [src/calcit/type_annotation.rs](../src/calcit/type_annotation.rs)), wrapping the
|
|
18
|
+
kind as a bare `Calcit::Tag`. On save, `schema_annotation_to_edn` (in
|
|
19
|
+
[src/snapshot.rs](../src/snapshot.rs)) converted the in-memory schema back to EDN via
|
|
20
|
+
`CalcitTypeAnnotation::builtin_tag_name()`, which only handles primitive scalar
|
|
21
|
+
types (`bool`, `number`, `string`, ...) and has no arm for `Custom`, `Record`,
|
|
22
|
+
`Struct`, `Enum`, or `Trait` — those all fell through the `_ => None` catch-all,
|
|
23
|
+
so `schema_annotation_to_edn` silently defaulted to `Edn::tag("dynamic")`.
|
|
24
|
+
|
|
25
|
+
Since `code_entry_edn_pairs` recomputes every entry's schema EDN from its in-memory
|
|
26
|
+
`CalcitTypeAnnotation` on *every* file save (not just for touched defs), any
|
|
27
|
+
`cr edit`/`cr tree` write to a file containing these shorthand-tagged schemas
|
|
28
|
+
degrades them all to `:dynamic`, regardless of which def was actually edited.
|
|
29
|
+
|
|
30
|
+
Note `builtin_tag_name()` is also used by `to_brief_string()` and other call
|
|
31
|
+
sites that intentionally want more specific output (e.g. `struct Person` instead
|
|
32
|
+
of a bare `:struct`) for those variants — so widening `builtin_tag_name()` itself
|
|
33
|
+
to cover them would have regressed those call sites. The fix instead lives
|
|
34
|
+
directly in `schema_annotation_to_edn`, which is the one place that needs the
|
|
35
|
+
short kind-tag representation for the snapshot file format.
|
|
36
|
+
|
|
37
|
+
## Fix
|
|
38
|
+
|
|
39
|
+
Added explicit match arms to `schema_annotation_to_edn` in [src/snapshot.rs](../src/snapshot.rs):
|
|
40
|
+
- `Custom(value)`: coerce the wrapped `Calcit::Tag` back into its `Edn::Tag` (falls
|
|
41
|
+
back to `:dynamic` only if the wrapped value isn't a bare tag, which shouldn't
|
|
42
|
+
happen via `from_tag_name`).
|
|
43
|
+
- `Record(_)` → `:record`, `Struct(..)` → `:struct`, `Enum(..)` → `:enum`,
|
|
44
|
+
`Trait(_)` → `:trait`.
|
|
45
|
+
|
|
46
|
+
This preserves the user's original expressed intent (e.g. `:schema :impl`) instead
|
|
47
|
+
of silently downgrading it to `:dynamic`, which would otherwise turn off
|
|
48
|
+
schema-based type-checking coverage for those defs.
|
|
49
|
+
|
|
50
|
+
Recovered the previously-corrupted `calcit/test-traits.cirru` by reverting to
|
|
51
|
+
`git checkout` and reapplying the new `test-method-introspection` assertions with
|
|
52
|
+
the fixed binary — confirmed via `git diff` that this time only the intended
|
|
53
|
+
7 new lines changed, with zero incidental `:schema` churn.
|
|
54
|
+
|
|
55
|
+
## Regression test
|
|
56
|
+
|
|
57
|
+
`test_custom_kind_schema_tags_round_trip_instead_of_degrading_to_dynamic` in
|
|
58
|
+
[src/snapshot.rs](../src/snapshot.rs) asserts `schema_annotation_to_edn(CalcitTypeAnnotation::from_tag_name(kind))`
|
|
59
|
+
round-trips to `Edn::tag(kind)` for `struct`/`enum`/`trait`/`impl`/`record`.
|
|
60
|
+
|
|
61
|
+
## Validation
|
|
62
|
+
|
|
63
|
+
`cargo fmt`, `cargo clippy --all-targets -- -D warnings`, `cargo test` (all pass),
|
|
64
|
+
`yarn check-all` (exit 0).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# 2026-07-19 22:36 文档知识图谱与增量缓存
|
|
2
|
+
|
|
3
|
+
## 修改概要
|
|
4
|
+
|
|
5
|
+
- 为 `cr docs` 增加文档知识图构建、检查、关系遍历、路径查询、定义反查、缺失定义和孤立节点命令。
|
|
6
|
+
- 使用 Markdown frontmatter 的 `id`、`parent`、`related`、`requires`、`leads_to`、`code_refs` 表达可 Git 管理的知识关系。
|
|
7
|
+
- 在 `~/.config/calcit/docs-cache/` 生成 JSON 文本缓存,按文件内容、解析器/schema 版本和内置 Calcit snapshot 指纹失效。
|
|
8
|
+
- 为文档索引 RFC、`CalcitAgent.md`、索引规范和验证案例补充使用说明。
|
|
9
|
+
- 为核心数据结构文档补充稳定节点 ID 与 Calcit 定义引用,验证从概念到 API 和编辑工作流的跳转。
|
|
10
|
+
|
|
11
|
+
## 验证
|
|
12
|
+
|
|
13
|
+
- `cargo fmt --all`
|
|
14
|
+
- `cargo clippy --all-targets -- -D warnings`
|
|
15
|
+
- `cargo test -q`
|
|
16
|
+
- `yarn compile`
|
|
17
|
+
- `cr docs graph build`
|
|
18
|
+
- `cr docs graph check`
|
|
19
|
+
- `cr docs graph path core/features/list core/run/edit-tree`
|
|
20
|
+
- `cr docs graph explain calcit.core/nth`
|
|
21
|
+
- `cr docs graph missing`
|
|
22
|
+
- `cr docs graph orphans`
|
|
23
|
+
|
|
24
|
+
## 后续方向
|
|
25
|
+
|
|
26
|
+
- 将 `missing` 从总量报告扩展为按公共 API、语法符号和内部定义分类。
|
|
27
|
+
- 接入模块或应用级 Calcit snapshot,并增加示例覆盖检查与 LLM context 聚合。
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# 2026-07-19 22:45 graph missing 分批筛选
|
|
2
|
+
|
|
3
|
+
## 修改概要
|
|
4
|
+
|
|
5
|
+
- 为 `cr docs graph missing` 增加 `--ns` namespace 前缀过滤。
|
|
6
|
+
- 增加 `--limit` 输出数量限制,便于按批次补充公共 API 的 `code_refs`。
|
|
7
|
+
- 在 `CalcitAgent.md` 和知识图谱 RFC 中补充该操作说明。
|
|
8
|
+
- 增加 namespace 过滤、数量限制和内部命名排除的单元测试。
|
|
9
|
+
|
|
10
|
+
## 验证
|
|
11
|
+
|
|
12
|
+
- `cargo fmt --all`
|
|
13
|
+
- `cargo test -q`
|
|
14
|
+
- `cargo clippy --all-targets -- -D warnings`
|
|
15
|
+
- `yarn compile`
|
|
16
|
+
- `cr docs graph missing --ns calcit.core --limit 5`
|
|
17
|
+
- `git diff --check`
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# 2026-07-19 23:10 graph explain 定义摘要
|
|
2
|
+
|
|
3
|
+
## 修改概要
|
|
4
|
+
|
|
5
|
+
- 为 `cr docs graph explain` 增加 `--full`,输出 Calcit 定义的 doc 和 examples 可用性。
|
|
6
|
+
- 缓存定义记录保存 doc 文本,并将 cache schema 升级为 5,确保旧缓存自动重建。
|
|
7
|
+
- 更新 command echo、`CalcitAgent.md` 和 graph 验证案例。
|
|
8
|
+
|
|
9
|
+
## 验证
|
|
10
|
+
|
|
11
|
+
- `cargo fmt --all`
|
|
12
|
+
- `cargo test -q`
|
|
13
|
+
- `cargo clippy --all-targets -- -D warnings`
|
|
14
|
+
- `yarn compile`
|
|
15
|
+
- `cr docs graph explain calcit.core/nth --full`
|
|
16
|
+
- `git diff --check`
|
package/lib/calcit-data.mjs
CHANGED
|
@@ -210,6 +210,14 @@ export let hashFunction = (x) => {
|
|
|
210
210
|
return h;
|
|
211
211
|
}
|
|
212
212
|
if (typeof x === "function") {
|
|
213
|
+
// method values are closures created on the fly (see invoke_method_closure);
|
|
214
|
+
// hash by method name so equal methods share the same hash, matching isEqual
|
|
215
|
+
const methodName = x.__calcitMethodName;
|
|
216
|
+
if (methodName != null) {
|
|
217
|
+
let h = mergeValueHash(defaultHash_fn, methodName);
|
|
218
|
+
x[calcit_dirty_hash_key] = h;
|
|
219
|
+
return h;
|
|
220
|
+
}
|
|
213
221
|
fnHashCounter = fnHashCounter + 1;
|
|
214
222
|
let h = mergeValueHash(defaultHash_fn, fnHashCounter);
|
|
215
223
|
x[calcit_dirty_hash_key] = h;
|
|
@@ -383,6 +391,10 @@ export let toString = (x, escaped, disableJsDataWarning = false) => {
|
|
|
383
391
|
return x.toString();
|
|
384
392
|
}
|
|
385
393
|
if (typeof x === "function") {
|
|
394
|
+
const methodName = x.__calcitMethodName;
|
|
395
|
+
if (methodName != null) {
|
|
396
|
+
return "." + methodName;
|
|
397
|
+
}
|
|
386
398
|
return `(&fn ...)`;
|
|
387
399
|
}
|
|
388
400
|
if (x instanceof CalcitSymbol) {
|
|
@@ -541,7 +553,14 @@ export let _$n__$e_ = (x, y) => {
|
|
|
541
553
|
return false;
|
|
542
554
|
}
|
|
543
555
|
if (tx === "function") {
|
|
544
|
-
//
|
|
556
|
+
// method values are closures created on the fly (see invoke_method_closure),
|
|
557
|
+
// so two methods with the same name must compare equal by name, not by reference
|
|
558
|
+
const mx = x.__calcitMethodName;
|
|
559
|
+
const my = y.__calcitMethodName;
|
|
560
|
+
if (mx != null || my != null) {
|
|
561
|
+
return mx === my;
|
|
562
|
+
}
|
|
563
|
+
// comparing plain functions by reference
|
|
545
564
|
return x === y;
|
|
546
565
|
}
|
|
547
566
|
if (x instanceof CalcitTag) {
|
package/lib/calcit.procs.mjs
CHANGED
|
@@ -1834,6 +1834,17 @@ function lookup_impls(obj) {
|
|
|
1834
1834
|
tag = "&core-set-methods";
|
|
1835
1835
|
impls = normalize_builtin_impls(calcit_builtin_impls.set);
|
|
1836
1836
|
}
|
|
1837
|
+
else if (obj instanceof CalcitStruct) {
|
|
1838
|
+
// Bare type definitions (not yet instantiated) carry their own attached
|
|
1839
|
+
// impls, so introspection tools like `&methods-of` can answer "what
|
|
1840
|
+
// methods will instances of this type have" without a concrete instance.
|
|
1841
|
+
tag = obj.name.toString();
|
|
1842
|
+
impls = obj.impls;
|
|
1843
|
+
}
|
|
1844
|
+
else if (obj instanceof CalcitEnum) {
|
|
1845
|
+
tag = obj.name();
|
|
1846
|
+
impls = obj.impls;
|
|
1847
|
+
}
|
|
1837
1848
|
else if (typeof obj === "number") {
|
|
1838
1849
|
tag = "&core-number-methods";
|
|
1839
1850
|
impls = normalize_builtin_impls(calcit_builtin_impls.number);
|
|
@@ -1862,7 +1873,7 @@ export function invoke_method(p, obj, ...args) {
|
|
|
1862
1873
|
let tag = pair[1];
|
|
1863
1874
|
// builtin impl lists are ordered by priority in calcit-core.
|
|
1864
1875
|
// user-defined values use impl-traits append, so later impls override earlier ones.
|
|
1865
|
-
let reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple;
|
|
1876
|
+
let reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple || obj instanceof CalcitStruct || obj instanceof CalcitEnum;
|
|
1866
1877
|
let idx = reverse ? impls.length - 1 : 0;
|
|
1867
1878
|
while (reverse ? idx >= 0 : idx < impls.length) {
|
|
1868
1879
|
let klass = impls[idx];
|
|
@@ -1882,12 +1893,16 @@ export function invoke_method(p, obj, ...args) {
|
|
|
1882
1893
|
export function _$n_methods_of(obj) {
|
|
1883
1894
|
if (arguments.length !== 1)
|
|
1884
1895
|
throw new Error("&methods-of expected 1 argument");
|
|
1896
|
+
// Traits declare methods directly rather than through attached impls.
|
|
1897
|
+
if (obj instanceof CalcitTrait) {
|
|
1898
|
+
return new CalcitSliceList(obj.methods.map((m) => invoke_method_closure(m.value)));
|
|
1899
|
+
}
|
|
1885
1900
|
let pair = lookup_impls(obj);
|
|
1886
1901
|
if (pair == null) {
|
|
1887
1902
|
throw new Error(`&methods-of cannot resolve impls for: ${toString(obj, true)}`);
|
|
1888
1903
|
}
|
|
1889
1904
|
let impls = pair[0];
|
|
1890
|
-
let reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple;
|
|
1905
|
+
let reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple || obj instanceof CalcitStruct || obj instanceof CalcitEnum;
|
|
1891
1906
|
let seen = new Set();
|
|
1892
1907
|
let ys = [];
|
|
1893
1908
|
let idx = reverse ? impls.length - 1 : 0;
|
|
@@ -1895,10 +1910,11 @@ export function _$n_methods_of(obj) {
|
|
|
1895
1910
|
let impl = impls[idx];
|
|
1896
1911
|
if (impl != null) {
|
|
1897
1912
|
for (let k = 0; k < impl.fields.length; k++) {
|
|
1898
|
-
let
|
|
1913
|
+
let rawName = impl.fields[k].value;
|
|
1914
|
+
let name = "." + rawName;
|
|
1899
1915
|
if (!seen.has(name)) {
|
|
1900
1916
|
seen.add(name);
|
|
1901
|
-
ys.push(
|
|
1917
|
+
ys.push(invoke_method_closure(rawName));
|
|
1902
1918
|
}
|
|
1903
1919
|
}
|
|
1904
1920
|
}
|
|
@@ -1909,12 +1925,26 @@ export function _$n_methods_of(obj) {
|
|
|
1909
1925
|
export function _$n_inspect_methods(obj, note) {
|
|
1910
1926
|
if (arguments.length !== 2)
|
|
1911
1927
|
throw new Error("&inspect-methods expected 2 arguments");
|
|
1928
|
+
if (obj instanceof CalcitTrait) {
|
|
1929
|
+
console.log("\n&inspect-methods");
|
|
1930
|
+
console.log(`Note: ${toString(note, true)}`);
|
|
1931
|
+
console.log(`Value type: ${type_of(obj).toString()}`);
|
|
1932
|
+
console.log(`Value: ${toString(obj, true)}`);
|
|
1933
|
+
console.log("Method call syntax: `.method self p1 p2`");
|
|
1934
|
+
console.log(" - dot is part of the method name, first arg is the receiver\n");
|
|
1935
|
+
const names = obj.methods.map((m) => "." + m.value);
|
|
1936
|
+
console.log(`Trait methods declared directly (no impls): ${names.length}`);
|
|
1937
|
+
console.log(`\nAll methods (unique, high → low): ${names.length}`);
|
|
1938
|
+
console.log(" " + names.join(" "));
|
|
1939
|
+
console.log("\n");
|
|
1940
|
+
return obj;
|
|
1941
|
+
}
|
|
1912
1942
|
let pair = lookup_impls(obj);
|
|
1913
1943
|
if (pair == null) {
|
|
1914
1944
|
throw new Error(`&inspect-methods cannot resolve impls for: ${toString(obj, true)}`);
|
|
1915
1945
|
}
|
|
1916
1946
|
let impls = pair[0];
|
|
1917
|
-
let reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple;
|
|
1947
|
+
let reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple || obj instanceof CalcitStruct || obj instanceof CalcitEnum;
|
|
1918
1948
|
console.log("\n&inspect-methods");
|
|
1919
1949
|
console.log(`Note: ${toString(note, true)}`);
|
|
1920
1950
|
console.log(`Value type: ${type_of(obj).toString()}`);
|
|
@@ -1941,7 +1971,7 @@ export function _$n_inspect_methods(obj, note) {
|
|
|
1941
1971
|
}
|
|
1942
1972
|
let ms = _$n_methods_of(obj);
|
|
1943
1973
|
console.log(`\nAll methods (unique, high → low): ${ms.len()}`);
|
|
1944
|
-
console.log(" " + Array.from(ms.items()).join(" "));
|
|
1974
|
+
console.log(" " + Array.from(ms.items()).map((m) => toString(m, false)).join(" "));
|
|
1945
1975
|
console.log("\n");
|
|
1946
1976
|
return obj;
|
|
1947
1977
|
}
|
|
@@ -1963,7 +1993,7 @@ export function _$n_trait_call(traitDef, method, obj, ...args) {
|
|
|
1963
1993
|
throw new Error(`&trait-call cannot resolve impls for: ${toString(obj, true)}`);
|
|
1964
1994
|
}
|
|
1965
1995
|
const impls = pair[0];
|
|
1966
|
-
const reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple;
|
|
1996
|
+
const reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple || obj instanceof CalcitStruct || obj instanceof CalcitEnum;
|
|
1967
1997
|
let idx = reverse ? impls.length - 1 : 0;
|
|
1968
1998
|
while (reverse ? idx >= 0 : idx < impls.length) {
|
|
1969
1999
|
const impl = impls[idx];
|
package/lib/package.json
CHANGED
package/package.json
CHANGED
package/ts-src/calcit-data.mts
CHANGED
|
@@ -238,6 +238,14 @@ export let hashFunction = (x: CalcitValue): Hash => {
|
|
|
238
238
|
return h;
|
|
239
239
|
}
|
|
240
240
|
if (typeof x === "function") {
|
|
241
|
+
// method values are closures created on the fly (see invoke_method_closure);
|
|
242
|
+
// hash by method name so equal methods share the same hash, matching isEqual
|
|
243
|
+
const methodName = (x as { __calcitMethodName?: string }).__calcitMethodName;
|
|
244
|
+
if (methodName != null) {
|
|
245
|
+
let h = mergeValueHash(defaultHash_fn, methodName);
|
|
246
|
+
(x as any)[calcit_dirty_hash_key] = h;
|
|
247
|
+
return h;
|
|
248
|
+
}
|
|
241
249
|
fnHashCounter = fnHashCounter + 1;
|
|
242
250
|
let h = mergeValueHash(defaultHash_fn, fnHashCounter);
|
|
243
251
|
(x as any)[calcit_dirty_hash_key] = h;
|
|
@@ -412,6 +420,10 @@ export let toString = (x: CalcitValue, escaped: boolean, disableJsDataWarning: b
|
|
|
412
420
|
return x.toString();
|
|
413
421
|
}
|
|
414
422
|
if (typeof x === "function") {
|
|
423
|
+
const methodName = (x as { __calcitMethodName?: string }).__calcitMethodName;
|
|
424
|
+
if (methodName != null) {
|
|
425
|
+
return "." + methodName;
|
|
426
|
+
}
|
|
415
427
|
return `(&fn ...)`;
|
|
416
428
|
}
|
|
417
429
|
if (x instanceof CalcitSymbol) {
|
|
@@ -579,7 +591,14 @@ export let _$n__$e_ = (x: CalcitValue, y: CalcitValue): boolean => {
|
|
|
579
591
|
return false;
|
|
580
592
|
}
|
|
581
593
|
if (tx === "function") {
|
|
582
|
-
//
|
|
594
|
+
// method values are closures created on the fly (see invoke_method_closure),
|
|
595
|
+
// so two methods with the same name must compare equal by name, not by reference
|
|
596
|
+
const mx = (x as { __calcitMethodName?: string }).__calcitMethodName;
|
|
597
|
+
const my = (y as { __calcitMethodName?: string }).__calcitMethodName;
|
|
598
|
+
if (mx != null || my != null) {
|
|
599
|
+
return mx === my;
|
|
600
|
+
}
|
|
601
|
+
// comparing plain functions by reference
|
|
583
602
|
return x === y;
|
|
584
603
|
}
|
|
585
604
|
if (x instanceof CalcitTag) {
|
package/ts-src/calcit.procs.mts
CHANGED
|
@@ -1946,6 +1946,15 @@ function lookup_impls(obj: CalcitValue): [CalcitImpl[], string] {
|
|
|
1946
1946
|
} else if (obj instanceof CalcitSet) {
|
|
1947
1947
|
tag = "&core-set-methods";
|
|
1948
1948
|
impls = normalize_builtin_impls(calcit_builtin_impls.set);
|
|
1949
|
+
} else if (obj instanceof CalcitStruct) {
|
|
1950
|
+
// Bare type definitions (not yet instantiated) carry their own attached
|
|
1951
|
+
// impls, so introspection tools like `&methods-of` can answer "what
|
|
1952
|
+
// methods will instances of this type have" without a concrete instance.
|
|
1953
|
+
tag = obj.name.toString();
|
|
1954
|
+
impls = obj.impls;
|
|
1955
|
+
} else if (obj instanceof CalcitEnum) {
|
|
1956
|
+
tag = obj.name();
|
|
1957
|
+
impls = obj.impls;
|
|
1949
1958
|
} else if (typeof obj === "number") {
|
|
1950
1959
|
tag = "&core-number-methods";
|
|
1951
1960
|
impls = normalize_builtin_impls(calcit_builtin_impls.number);
|
|
@@ -1971,7 +1980,7 @@ export function invoke_method(p: string, obj: CalcitValue, ...args: CalcitValue[
|
|
|
1971
1980
|
let tag = pair[1];
|
|
1972
1981
|
// builtin impl lists are ordered by priority in calcit-core.
|
|
1973
1982
|
// user-defined values use impl-traits append, so later impls override earlier ones.
|
|
1974
|
-
let reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple;
|
|
1983
|
+
let reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple || obj instanceof CalcitStruct || obj instanceof CalcitEnum;
|
|
1975
1984
|
let idx = reverse ? impls.length - 1 : 0;
|
|
1976
1985
|
while (reverse ? idx >= 0 : idx < impls.length) {
|
|
1977
1986
|
let klass = impls[idx];
|
|
@@ -1991,12 +2000,16 @@ export function invoke_method(p: string, obj: CalcitValue, ...args: CalcitValue[
|
|
|
1991
2000
|
|
|
1992
2001
|
export function _$n_methods_of(obj: CalcitValue): CalcitSliceList {
|
|
1993
2002
|
if (arguments.length !== 1) throw new Error("&methods-of expected 1 argument");
|
|
2003
|
+
// Traits declare methods directly rather than through attached impls.
|
|
2004
|
+
if (obj instanceof CalcitTrait) {
|
|
2005
|
+
return new CalcitSliceList(obj.methods.map((m) => invoke_method_closure(m.value)));
|
|
2006
|
+
}
|
|
1994
2007
|
let pair = lookup_impls(obj);
|
|
1995
2008
|
if (pair == null) {
|
|
1996
2009
|
throw new Error(`&methods-of cannot resolve impls for: ${toString(obj, true)}`);
|
|
1997
2010
|
}
|
|
1998
2011
|
let impls = pair[0];
|
|
1999
|
-
let reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple;
|
|
2012
|
+
let reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple || obj instanceof CalcitStruct || obj instanceof CalcitEnum;
|
|
2000
2013
|
let seen = new Set<string>();
|
|
2001
2014
|
let ys: CalcitValue[] = [];
|
|
2002
2015
|
|
|
@@ -2005,10 +2018,11 @@ export function _$n_methods_of(obj: CalcitValue): CalcitSliceList {
|
|
|
2005
2018
|
let impl = impls[idx];
|
|
2006
2019
|
if (impl != null) {
|
|
2007
2020
|
for (let k = 0; k < impl.fields.length; k++) {
|
|
2008
|
-
let
|
|
2021
|
+
let rawName = impl.fields[k].value;
|
|
2022
|
+
let name = "." + rawName;
|
|
2009
2023
|
if (!seen.has(name)) {
|
|
2010
2024
|
seen.add(name);
|
|
2011
|
-
ys.push(
|
|
2025
|
+
ys.push(invoke_method_closure(rawName));
|
|
2012
2026
|
}
|
|
2013
2027
|
}
|
|
2014
2028
|
}
|
|
@@ -2019,12 +2033,26 @@ export function _$n_methods_of(obj: CalcitValue): CalcitSliceList {
|
|
|
2019
2033
|
|
|
2020
2034
|
export function _$n_inspect_methods(obj: CalcitValue, note: CalcitValue): CalcitValue {
|
|
2021
2035
|
if (arguments.length !== 2) throw new Error("&inspect-methods expected 2 arguments");
|
|
2036
|
+
if (obj instanceof CalcitTrait) {
|
|
2037
|
+
console.log("\n&inspect-methods");
|
|
2038
|
+
console.log(`Note: ${toString(note, true)}`);
|
|
2039
|
+
console.log(`Value type: ${type_of(obj).toString()}`);
|
|
2040
|
+
console.log(`Value: ${toString(obj, true)}`);
|
|
2041
|
+
console.log("Method call syntax: `.method self p1 p2`");
|
|
2042
|
+
console.log(" - dot is part of the method name, first arg is the receiver\n");
|
|
2043
|
+
const names = obj.methods.map((m) => "." + m.value);
|
|
2044
|
+
console.log(`Trait methods declared directly (no impls): ${names.length}`);
|
|
2045
|
+
console.log(`\nAll methods (unique, high → low): ${names.length}`);
|
|
2046
|
+
console.log(" " + names.join(" "));
|
|
2047
|
+
console.log("\n");
|
|
2048
|
+
return obj;
|
|
2049
|
+
}
|
|
2022
2050
|
let pair = lookup_impls(obj);
|
|
2023
2051
|
if (pair == null) {
|
|
2024
2052
|
throw new Error(`&inspect-methods cannot resolve impls for: ${toString(obj, true)}`);
|
|
2025
2053
|
}
|
|
2026
2054
|
let impls = pair[0];
|
|
2027
|
-
let reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple;
|
|
2055
|
+
let reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple || obj instanceof CalcitStruct || obj instanceof CalcitEnum;
|
|
2028
2056
|
|
|
2029
2057
|
console.log("\n&inspect-methods");
|
|
2030
2058
|
console.log(`Note: ${toString(note, true)}`);
|
|
@@ -2054,7 +2082,7 @@ export function _$n_inspect_methods(obj: CalcitValue, note: CalcitValue): Calcit
|
|
|
2054
2082
|
|
|
2055
2083
|
let ms = _$n_methods_of(obj);
|
|
2056
2084
|
console.log(`\nAll methods (unique, high → low): ${ms.len()}`);
|
|
2057
|
-
console.log(" " +
|
|
2085
|
+
console.log(" " + Array.from(ms.items()).map((m) => toString(m, false)).join(" "));
|
|
2058
2086
|
console.log("\n");
|
|
2059
2087
|
|
|
2060
2088
|
return obj;
|
|
@@ -2078,7 +2106,7 @@ export function _$n_trait_call(traitDef: CalcitValue, method: CalcitValue, obj:
|
|
|
2078
2106
|
throw new Error(`&trait-call cannot resolve impls for: ${toString(obj, true)}`);
|
|
2079
2107
|
}
|
|
2080
2108
|
const impls = pair[0];
|
|
2081
|
-
const reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple;
|
|
2109
|
+
const reverse = obj instanceof CalcitRecord || obj instanceof CalcitTuple || obj instanceof CalcitStruct || obj instanceof CalcitEnum;
|
|
2082
2110
|
let idx = reverse ? impls.length - 1 : 0;
|
|
2083
2111
|
while (reverse ? idx >= 0 : idx < impls.length) {
|
|
2084
2112
|
const impl = impls[idx];
|