@calcit/procs 0.11.7 → 0.12.0
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/.gitattributes +1 -1
- package/.yarn/install-state.gz +0 -0
- package/build.rs +229 -11
- package/editing-history/2026-0303-1934-check-types-and-core-annotations.md +17 -0
- package/editing-history/2026-0304-1331-tag-call-warning-and-dot-access-migration.md +28 -0
- package/editing-history/2026-0304-1548-impl-new-dot-method-input-and-doc-hints.md +33 -0
- package/editing-history/2026-0304-1645-calcit-eq-contract-refactor-and-macro-tests.md +28 -0
- package/editing-history/2026-0304-2200-assert-type-assert-traits-composable-runtime-checks.md +18 -0
- package/editing-history/2026-0304-generics-fn-typevar-identity.md +30 -0
- package/editing-history/2026-0305-0042-type-inference-argtypes-and-core-hints.md +54 -0
- package/editing-history/2026-0305-0115-calcit-core-generic-refinements.md +57 -0
- package/editing-history/2026-0305-1930-schema-migration-cr-edit.md +30 -0
- package/editing-history/2026-0305-1939-query-schema-adaptation.md +42 -0
- package/editing-history/2026-0306-0120-schema-hint-migration-batch.md +38 -0
- package/editing-history/2026-0306-1416-core-macro-schema-migration.md +20 -0
- package/editing-history/2026-0306-1552-core-schema-and-hintfn-migration.md +63 -0
- package/editing-history/2026-0306-1936-schema-normalization-and-preprocess-preload.md +12 -0
- package/editing-history/2026-0307-0142-unit-type-and-builtin-schemas.md +82 -0
- package/editing-history/2026-0307-1302-calcit-agent-docs-update.md +32 -0
- package/editing-history/2026-0307-1652-decouple-snapshot-schema-to-calcit-type.md +112 -0
- package/editing-history/2026-0307-1821-snapshot-schema-validation.md +6 -0
- package/editing-history/2026-0307-1959-schema-def-kind-arity-validation.md +96 -0
- package/editing-history/2026-0307-migrate-hint-fn-to-schema.md +29 -0
- package/editing-history/2026-0307-remove-enum-prototype-field.md +43 -0
- package/editing-history/2026-0307-schema-args-type-enforcement.md +66 -0
- package/editing-history/2026-0308-0031-schema-generics-quote-normalization.md +35 -0
- package/editing-history/2026-0308-1905-schema-typeref-and-assert-migration.md +34 -0
- package/editing-history/2026-0308-2033-schema-wrapper-migration.md +45 -0
- package/editing-history/2026-0308-2315-late-schema-cleanup-and-warning-compat.md +27 -0
- package/lib/calcit-data.mjs +6 -0
- package/lib/calcit.procs.mjs +3 -1
- package/lib/package.json +2 -2
- package/package.json +2 -2
- package/ts-src/calcit-data.mts +6 -0
- package/ts-src/calcit.procs.mts +3 -1
package/.gitattributes
CHANGED
package/.yarn/install-state.gz
CHANGED
|
Binary file
|
package/build.rs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
use cirru_edn::{Edn, from_edn};
|
|
1
|
+
use cirru_edn::{Edn, EdnRecordView, from_edn};
|
|
2
2
|
use cirru_parser::Cirru;
|
|
3
3
|
use serde::{Deserialize, Serialize};
|
|
4
4
|
use std::collections::HashMap;
|
|
@@ -24,6 +24,8 @@ pub struct CodeEntry {
|
|
|
24
24
|
#[serde(default)]
|
|
25
25
|
pub examples: Vec<Cirru>,
|
|
26
26
|
pub code: Cirru,
|
|
27
|
+
#[serde(default)]
|
|
28
|
+
pub schema: Option<Edn>,
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
@@ -41,34 +43,250 @@ pub struct Snapshot {
|
|
|
41
43
|
pub files: HashMap<String, FileInSnapShot>,
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
fn format_edn_preview(value: &Edn) -> String {
|
|
47
|
+
let raw = cirru_edn::format(value, true).unwrap_or_else(|_| format!("{value:?}"));
|
|
48
|
+
const LIMIT: usize = 220;
|
|
49
|
+
if raw.chars().count() > LIMIT {
|
|
50
|
+
let truncated = raw.chars().take(LIMIT).collect::<String>();
|
|
51
|
+
format!("{truncated}…")
|
|
52
|
+
} else {
|
|
53
|
+
raw
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn truncate_preview(raw: &str, limit: usize) -> String {
|
|
58
|
+
if raw.chars().count() > limit {
|
|
59
|
+
let truncated = raw.chars().take(limit).collect::<String>();
|
|
60
|
+
format!("{truncated}…")
|
|
61
|
+
} else {
|
|
62
|
+
raw.to_owned()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fn truncate_edn_error_nodes(message: &str) -> String {
|
|
67
|
+
const NODE_LIMIT: usize = 200;
|
|
68
|
+
|
|
69
|
+
message
|
|
70
|
+
.lines()
|
|
71
|
+
.map(|line| {
|
|
72
|
+
if let Some((prefix, preview)) = line.split_once("Node: ") {
|
|
73
|
+
format!("{prefix}Node: {}", truncate_preview(preview, NODE_LIMIT))
|
|
74
|
+
} else {
|
|
75
|
+
line.to_owned()
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
.collect::<Vec<_>>()
|
|
79
|
+
.join("\n")
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn format_edn_error<E: std::fmt::Display>(error: E) -> String {
|
|
83
|
+
truncate_edn_error_nodes(&error.to_string())
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fn schema_path_label(path: &[String]) -> String {
|
|
87
|
+
if path.is_empty() { "<root>".to_owned() } else { path.join("") }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fn map_key_path_segment(key: &Edn) -> String {
|
|
91
|
+
match key {
|
|
92
|
+
Edn::Tag(tag) => format!(".{}", tag.ref_str()),
|
|
93
|
+
Edn::Str(text) => format!(".{text}"),
|
|
94
|
+
Edn::Symbol(text) => format!(".{text}"),
|
|
95
|
+
_ => ".<key>".to_owned(),
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Convert a schema Edn value (either old Quote-wrapped or new direct map) into Edn map form.
|
|
100
|
+
fn parse_schema_from_edn(value: &Edn, owner: &str) -> Result<Edn, String> {
|
|
101
|
+
// Old format: Edn::Quote wrapping Cirru — convert to direct map Edn
|
|
102
|
+
if let Ok(cirru) = from_edn::<Cirru>(value.clone()) {
|
|
103
|
+
let text = cirru_parser::format(&[cirru], true.into())
|
|
104
|
+
.map_err(|e| format!("{owner}: failed to format quoted schema before validation: {}", format_edn_error(e)))?;
|
|
105
|
+
let parsed = cirru_edn::parse(&text).map_err(|e| {
|
|
106
|
+
format!(
|
|
107
|
+
"{owner}: failed to parse quoted schema after formatting: {}; schema={}",
|
|
108
|
+
format_edn_error(e),
|
|
109
|
+
truncate_preview(&text, 200)
|
|
110
|
+
)
|
|
111
|
+
})?;
|
|
112
|
+
validate_schema_edn_no_legacy_quotes(&parsed, owner)?;
|
|
113
|
+
return Ok(parsed);
|
|
114
|
+
}
|
|
115
|
+
// New format: already a direct Edn map
|
|
116
|
+
validate_schema_edn_no_legacy_quotes(value, owner)?;
|
|
117
|
+
Ok(value.clone())
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fn validate_schema_edn_no_legacy_quotes(value: &Edn, owner: &str) -> Result<(), String> {
|
|
121
|
+
fn walk(value: &Edn, owner: &str, path: &mut Vec<String>) -> Result<(), String> {
|
|
122
|
+
match value {
|
|
123
|
+
Edn::Symbol(s) => {
|
|
124
|
+
if s.starts_with('\'') {
|
|
125
|
+
let inner = s.trim_start_matches('\'');
|
|
126
|
+
return Err(format!(
|
|
127
|
+
"{owner}: invalid schema generic symbol `{s}` at {}. Use source syntax like `'{inner}`, but store it as plain EDN symbol `{inner}`.",
|
|
128
|
+
schema_path_label(path)
|
|
129
|
+
));
|
|
130
|
+
}
|
|
131
|
+
Ok(())
|
|
132
|
+
}
|
|
133
|
+
Edn::List(xs) => {
|
|
134
|
+
for (idx, item) in xs.0.iter().enumerate() {
|
|
135
|
+
path.push(format!("[{idx}]"));
|
|
136
|
+
walk(item, owner, path)?;
|
|
137
|
+
path.pop();
|
|
138
|
+
}
|
|
139
|
+
Ok(())
|
|
140
|
+
}
|
|
141
|
+
Edn::Map(map) => {
|
|
142
|
+
for (k, v) in &map.0 {
|
|
143
|
+
path.push(map_key_path_segment(k));
|
|
144
|
+
walk(v, owner, path)?;
|
|
145
|
+
path.pop();
|
|
146
|
+
}
|
|
147
|
+
Ok(())
|
|
148
|
+
}
|
|
149
|
+
Edn::Tuple(view) => {
|
|
150
|
+
path.push(".tag".to_owned());
|
|
151
|
+
walk(view.tag.as_ref(), owner, path)?;
|
|
152
|
+
path.pop();
|
|
153
|
+
for (idx, item) in view.extra.iter().enumerate() {
|
|
154
|
+
path.push(format!("[{idx}]"));
|
|
155
|
+
walk(item, owner, path)?;
|
|
156
|
+
path.pop();
|
|
157
|
+
}
|
|
158
|
+
Ok(())
|
|
159
|
+
}
|
|
160
|
+
Edn::Set(set) => {
|
|
161
|
+
for (idx, item) in set.0.iter().enumerate() {
|
|
162
|
+
path.push(format!("[#{idx}]"));
|
|
163
|
+
walk(item, owner, path)?;
|
|
164
|
+
path.pop();
|
|
165
|
+
}
|
|
166
|
+
Ok(())
|
|
167
|
+
}
|
|
168
|
+
Edn::Record(_) => Ok(()),
|
|
169
|
+
_ => Ok(()),
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let mut path = vec![];
|
|
174
|
+
walk(value, owner, &mut path)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
fn parse_code_entry(edn: Edn, owner: &str) -> Result<CodeEntry, String> {
|
|
178
|
+
let record: EdnRecordView = match edn {
|
|
179
|
+
Edn::Record(r) => r,
|
|
180
|
+
other => return Err(format!("{owner}: expected CodeEntry record, got {}", format_edn_preview(&other))),
|
|
181
|
+
};
|
|
182
|
+
let mut doc = String::new();
|
|
183
|
+
let mut examples: Vec<Cirru> = vec![];
|
|
184
|
+
let mut code: Option<Cirru> = None;
|
|
185
|
+
let mut schema: Option<Edn> = None;
|
|
186
|
+
for (key, value) in &record.pairs {
|
|
187
|
+
match key.arc_str().as_ref() {
|
|
188
|
+
"doc" => doc = from_edn(value.clone()).map_err(|e| format!("{owner}: invalid `:doc`: {e}"))?,
|
|
189
|
+
"examples" => examples = from_edn(value.clone()).map_err(|e| format!("{owner}: invalid `:examples`: {e}"))?,
|
|
190
|
+
"code" => code = Some(from_edn(value.clone()).map_err(|e| format!("{owner}: invalid `:code`: {e}"))?),
|
|
191
|
+
"schema" => {
|
|
192
|
+
if !matches!(value, Edn::Nil) {
|
|
193
|
+
schema = Some(parse_schema_from_edn(value, owner).map_err(|e| format!("{owner}: invalid `:schema`: {e}"))?);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
_ => {}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
Ok(CodeEntry {
|
|
200
|
+
doc,
|
|
201
|
+
examples,
|
|
202
|
+
code: code.ok_or_else(|| format!("{owner}: missing `:code` field in CodeEntry"))?,
|
|
203
|
+
schema,
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
fn parse_file_in_snapshot(edn: Edn, file_name: &str) -> Result<FileInSnapShot, String> {
|
|
208
|
+
let record: EdnRecordView = match edn {
|
|
209
|
+
Edn::Record(r) => r,
|
|
210
|
+
other => {
|
|
211
|
+
return Err(format!(
|
|
212
|
+
"{file_name}: expected FileEntry record, got {}",
|
|
213
|
+
format_edn_preview(&other)
|
|
214
|
+
));
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
let mut ns: Option<CodeEntry> = None;
|
|
218
|
+
let mut defs: HashMap<String, CodeEntry> = HashMap::new();
|
|
219
|
+
for (key, value) in &record.pairs {
|
|
220
|
+
match key.arc_str().as_ref() {
|
|
221
|
+
"ns" => ns = Some(parse_code_entry(value.clone(), &format!("{file_name}/:ns"))?),
|
|
222
|
+
"defs" => {
|
|
223
|
+
let map = match value {
|
|
224
|
+
Edn::Map(m) => m,
|
|
225
|
+
other => return Err(format!("{file_name}: expected `:defs` map, got {}", format_edn_preview(other))),
|
|
226
|
+
};
|
|
227
|
+
for (def_key, def_value) in &map.0 {
|
|
228
|
+
let name: String = from_edn(def_key.clone()).map_err(|e| format!("{file_name}: invalid def key: {e}"))?;
|
|
229
|
+
let owner = format!("{file_name}/{name}");
|
|
230
|
+
defs.insert(name, parse_code_entry(def_value.clone(), &owner)?);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
_ => {}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
Ok(FileInSnapShot {
|
|
237
|
+
ns: ns.ok_or_else(|| format!("{file_name}: missing `:ns` field in FileEntry"))?,
|
|
238
|
+
defs,
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fn parse_files(edn: Edn) -> Result<HashMap<String, FileInSnapShot>, String> {
|
|
243
|
+
match edn {
|
|
244
|
+
Edn::Map(map) => {
|
|
245
|
+
let mut result = HashMap::with_capacity(map.0.len());
|
|
246
|
+
for (key, value) in map.0 {
|
|
247
|
+
let name: String = from_edn(key).map_err(|e| format!("invalid file key: {e}"))?;
|
|
248
|
+
result.insert(name.clone(), parse_file_in_snapshot(value, &name)?);
|
|
249
|
+
}
|
|
250
|
+
Ok(result)
|
|
251
|
+
}
|
|
252
|
+
other => Err(format!("snapshot `:files` must be a map, got {}", format_edn_preview(&other))),
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
44
256
|
fn main() {
|
|
45
257
|
println!("cargo:rerun-if-changed=src/cirru/calcit-core.cirru");
|
|
46
258
|
|
|
47
259
|
let out_dir = env::var_os("OUT_DIR").unwrap();
|
|
48
260
|
let dest_path = Path::new(&out_dir).join("calcit-core.rmp");
|
|
49
261
|
|
|
50
|
-
let core_content =
|
|
51
|
-
|
|
262
|
+
let core_content =
|
|
263
|
+
fs::read_to_string("src/cirru/calcit-core.cirru").unwrap_or_else(|e| panic!("failed to read src/cirru/calcit-core.cirru: {e}"));
|
|
264
|
+
let core_data = cirru_edn::parse(&core_content)
|
|
265
|
+
.unwrap_or_else(|e| panic!("failed to parse src/cirru/calcit-core.cirru as Cirru EDN: {}", format_edn_error(e)));
|
|
52
266
|
|
|
53
267
|
// Minimal logic to convert Edn to Snapshot as in src/snapshot.rs
|
|
54
|
-
let data = core_data
|
|
55
|
-
|
|
268
|
+
let data = core_data
|
|
269
|
+
.view_map()
|
|
270
|
+
.unwrap_or_else(|e| panic!("calcit-core snapshot root must be a map: {e}"));
|
|
271
|
+
let pkg: String = from_edn(data.get_or_nil("package")).unwrap_or_else(|e| panic!("failed to parse calcit-core `:package`: {e}"));
|
|
56
272
|
let about = match data.get_or_nil("about") {
|
|
57
273
|
Edn::Nil => None,
|
|
58
|
-
value => Some(from_edn::<String>(value).
|
|
274
|
+
value => Some(from_edn::<String>(value).unwrap_or_else(|e| panic!("failed to parse calcit-core `:about`: {e}"))),
|
|
59
275
|
};
|
|
60
276
|
|
|
61
|
-
let files
|
|
277
|
+
let files = parse_files(data.get_or_nil("files")).unwrap_or_else(|e| panic!("failed to parse calcit-core `:files`: {e}"));
|
|
62
278
|
|
|
63
279
|
let snapshot = Snapshot {
|
|
64
280
|
package: pkg,
|
|
65
281
|
about,
|
|
66
|
-
configs: from_edn(data.get_or_nil("configs")).
|
|
67
|
-
entries: from_edn(data.get_or_nil("entries")).
|
|
282
|
+
configs: from_edn(data.get_or_nil("configs")).unwrap_or_else(|e| panic!("failed to parse calcit-core `:configs`: {e}")),
|
|
283
|
+
entries: from_edn(data.get_or_nil("entries")).unwrap_or_else(|e| panic!("failed to parse calcit-core `:entries`: {e}")),
|
|
68
284
|
files,
|
|
69
285
|
};
|
|
70
286
|
|
|
71
287
|
let mut buf = Vec::new();
|
|
72
|
-
snapshot
|
|
73
|
-
|
|
288
|
+
snapshot
|
|
289
|
+
.serialize(&mut rmp_serde::Serializer::new(&mut buf))
|
|
290
|
+
.unwrap_or_else(|e| panic!("failed to serialize embedded calcit-core snapshot: {e}"));
|
|
291
|
+
fs::write(dest_path, buf).unwrap_or_else(|e| panic!("failed to write embedded calcit-core snapshot: {e}"));
|
|
74
292
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## 本次修改摘要
|
|
2
|
+
|
|
3
|
+
- 新增 `cr analyze check-types` 子命令,用于统计定义的类型标注覆盖度。
|
|
4
|
+
- 支持按命名空间过滤:`--ns`、`--ns-prefix`,以及是否包含依赖:`--deps`。
|
|
5
|
+
- 支持覆盖度分层与筛选:`none/partial/full`,并通过 `--only` 过滤输出。
|
|
6
|
+
- 优化输出格式:按定义类型(fn/macro/data)分块展示,参数与断言信息合并展示,`return-type` 标签统一为 `return`。
|
|
7
|
+
- 持续补充 `calcit.core` 中函数参数 `assert-type` 标注,`partial` 覆盖项降至 0(`--only partial` 无结果)。
|
|
8
|
+
|
|
9
|
+
## 验证
|
|
10
|
+
|
|
11
|
+
- 执行 `yarn check-all`:通过。
|
|
12
|
+
- 执行 `./target/debug/cr calcit/test.cirru analyze check-types --ns calcit.core --deps --only partial`:无 `partial` 定义。
|
|
13
|
+
|
|
14
|
+
## 经验记录
|
|
15
|
+
|
|
16
|
+
- 日常编辑可优先使用全局 `cr tree/edit`,减少 `cargo run` 反复编译开销。
|
|
17
|
+
- 若全局 `cr` 尚未包含新子命令(如 `analyze check-types`),统计/验证阶段需使用仓库内最新二进制(如 `./target/debug/cr`)。
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# trait/impl 方法键 tag 写法 warning 与点号迁移评估
|
|
2
|
+
|
|
3
|
+
## 背景
|
|
4
|
+
|
|
5
|
+
- `deftrait` / `defimpl` 里方法键目前使用 `:foo` 形式,语义上对应方法名。
|
|
6
|
+
- 为逐步迁移到点号方法表示(如 `.foo`)需要先在 trait/impl 场景给出提示,避免误伤普通 tag/map 使用。
|
|
7
|
+
|
|
8
|
+
## 本次改动
|
|
9
|
+
|
|
10
|
+
- 在 `src/runner/preprocess.rs` 的宏预处理分支新增 warning:
|
|
11
|
+
- 仅在 `deftrait` / `defimpl` 检查方法键(默认展示)。
|
|
12
|
+
- 检测到 `:foo` 方法键时提示迁移到 `.foo` 风格(兼容性保留)。
|
|
13
|
+
- 移除对普通 `(:k obj)` 访问调用的迁移 warning,避免范围跑偏。
|
|
14
|
+
- 新增单测 `warns_on_trait_impl_method_tag_syntax`,确保 warning 范围和文案稳定。
|
|
15
|
+
- 同步在 `calcit/*.cirru` 的 trait/impl 测试与示例中开始迁移到 `.foo` 写法。
|
|
16
|
+
|
|
17
|
+
## 迁移建议(分阶段)
|
|
18
|
+
|
|
19
|
+
1. **当前阶段(已落地)**:默认 warning + 兼容旧写法。
|
|
20
|
+
2. **下一阶段**:在文档与脚手架模板中默认采用 `.foo` 方法键表示。
|
|
21
|
+
3. **后续阶段(可选)**:提供自动改写工具(`deftrait/defimpl` 的 `:foo` -> `.foo`)并支持批量修复。
|
|
22
|
+
4. **最终阶段(可选)**:再评估是否提升 warning 严格度(例如 lint 级别开关)。
|
|
23
|
+
|
|
24
|
+
## 验证
|
|
25
|
+
|
|
26
|
+
- 定向测试:
|
|
27
|
+
- `cargo test warns_on_trait_impl_method_tag_syntax -- --nocapture`
|
|
28
|
+
- `cargo test warns_on_dynamic_trait_call -- --nocapture`
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# 2026-03-04 15:48 `&impl::new` dot-method 输入与文档提示对齐
|
|
2
|
+
|
|
3
|
+
## 背景
|
|
4
|
+
|
|
5
|
+
在 trait/impl method key 迁移到 `.method` 的过程中,声明层(`deftrait`/`defimpl`)已支持并迁移,但原生构造入口 `&impl::new` 在不同后端对 `.method` 输入支持不一致:Rust 侧与 JS 侧行为存在差异,导致 `yarn check-all` 的 JS 路径报错。
|
|
6
|
+
|
|
7
|
+
## 本次改动要点
|
|
8
|
+
|
|
9
|
+
- 扩展 `&impl::new` 字段键解析,支持 `.method` 输入并规范化为内部 tag 键:
|
|
10
|
+
- Rust: `src/builtins/records.rs` 接受 `Calcit::Method` 作为 field key。
|
|
11
|
+
- JS: `ts-src/calcit.procs.mts` 为 method closure 写入 `__calcitMethodName` 元信息;`ts-src/calcit-data.mts` 的 `castTag` 可读取该元信息并转为 tag。
|
|
12
|
+
- 增加回归用例:
|
|
13
|
+
- `calcit/test-doc-smoke.cirru` 新增 `test-native-impl-new-dot-method`,直接覆盖 `&impl::new` + `.method` 场景。
|
|
14
|
+
- 补齐文档与示例提示(用于 `query examples` 与错误 hint 链路):
|
|
15
|
+
- `src/cirru/calcit-core.cirru` 新增 `calcit.core/&impl::new` 的 `CodeEntry` 和 `.method` 示例。
|
|
16
|
+
- 更新 `&impl:get` 参数说明支持 `.method`,并新增 `&impl:get DemoImpl .show` 示例。
|
|
17
|
+
|
|
18
|
+
## 迁移语义结论
|
|
19
|
+
|
|
20
|
+
- 声明层:推荐 `.method`,legacy `:method` 兼容(并有 warning 提示)。
|
|
21
|
+
- 存储层:继续保持 tag 作为内部键(兼容和最小改动优先);本次仅扩展输入层与提示层,不改内部数据结构。
|
|
22
|
+
|
|
23
|
+
## 验证
|
|
24
|
+
|
|
25
|
+
- `cargo fmt --check`
|
|
26
|
+
- `cargo clippy -- -D warnings`
|
|
27
|
+
- `yarn compile`
|
|
28
|
+
- `cargo test`
|
|
29
|
+
- `yarn check-all`
|
|
30
|
+
- `cargo run --bin cr -- demos/compact.cirru query examples 'calcit.core/&impl::new'`
|
|
31
|
+
- `cargo run --bin cr -- demos/compact.cirru query examples 'calcit.core/&impl:get'`
|
|
32
|
+
|
|
33
|
+
以上在本次改动后均通过。
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# 2026-03-04 16:45 `Calcit` 等值契约收敛、比较逻辑拆分与宏测试期望对齐
|
|
2
|
+
|
|
3
|
+
## 背景
|
|
4
|
+
|
|
5
|
+
在将 `Calcit` 的 `Eq/Hash/Ord` 契约对齐后,`Symbol/Local/Import` 的跨变种相等被移除,暴露了若干 `macroexpand-all` 测试中“符号 vs 已解析 core import”比较不一致的问题。
|
|
6
|
+
|
|
7
|
+
## 本次改动
|
|
8
|
+
|
|
9
|
+
- 运行时契约修复(`src/calcit.rs`):
|
|
10
|
+
- 移除 `Symbol/Local/Import` 跨变种相等特判。
|
|
11
|
+
- 补齐 `Trait/Impl/Struct` 相关 hash 字段,避免 `Eq` 与 `Hash` 不一致。
|
|
12
|
+
- `Struct/Enum/Trait/Impl` 的 `Ord` 比较从仅按 name 改为与 `Eq` 语义一致。
|
|
13
|
+
- 比较辅助函数拆分(`src/calcit/compare.rs`):
|
|
14
|
+
- 将 `compare_*` 辅助逻辑独立成模块,减轻 `calcit.rs` 体积与复杂度。
|
|
15
|
+
- 宏测试期望修复(`calcit/test-macro.cirru`):
|
|
16
|
+
- 将多处 `quasiquote` 直接期望改为“左右均 `macroexpand-all` 再比较”,避免因 `calcit.core/+`、`calcit.core/=` 的 `Import`/`Symbol` 差异导致伪失败。
|
|
17
|
+
|
|
18
|
+
## 经验与约束
|
|
19
|
+
|
|
20
|
+
- 当核心值语义从“宽松相等”收敛为“严格相等”后,宏展开测试应优先比较**同一归一化阶段**产物,而不是混比 `quasiquote` 与 `macroexpand-all` 原始结果。
|
|
21
|
+
- 若测试目标是“展开形状正确”,可比较两侧都经过 `macroexpand-all` 的 AST,降低名称解析层差异带来的噪声。
|
|
22
|
+
|
|
23
|
+
## 验证
|
|
24
|
+
|
|
25
|
+
- `cargo test`
|
|
26
|
+
- `yarn check-all`
|
|
27
|
+
|
|
28
|
+
均通过。
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# assert-type/assert-traits 可组合语义与检查路径修复
|
|
2
|
+
|
|
3
|
+
## 背景
|
|
4
|
+
- `assert-type`/`assert-traits` 之前在 runtime 路径不完整,且 `assert-type` 不可组合。
|
|
5
|
+
- 在补 runtime 之后出现过 `yarn check-all` 栈溢出,需要在可组合性与稳定性间做执行路径分流。
|
|
6
|
+
|
|
7
|
+
## 关键调整
|
|
8
|
+
- `assert-type` 通过时返回原值,失败时报错,可嵌套表达式。
|
|
9
|
+
- `assert-traits` 接入 syntax runtime,支持多个 trait 断言并返回原值。
|
|
10
|
+
- preprocess 分流:
|
|
11
|
+
- local 目标:保留静态类型注入/细化(避免破坏加载路径);
|
|
12
|
+
- expression 目标:保留到 runtime 断言,保证组合性。
|
|
13
|
+
- 补齐 `if` 返回类型推断与方法参数泛型绑定匹配。
|
|
14
|
+
|
|
15
|
+
## 验证
|
|
16
|
+
- 新增/更新单测通过(runtime + preprocess 路径)。
|
|
17
|
+
- `cargo test` 通过。
|
|
18
|
+
- `yarn check-all` 通过(EXIT 0)。
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 泛型函数 TypeVar 返回类型推导 (identity 用例)
|
|
2
|
+
|
|
3
|
+
## 概要
|
|
4
|
+
|
|
5
|
+
实现 `hint-fn (generics 'T)` 语法用于函数泛型声明,使 `identity` 等泛型函数的返回类型能在调用处根据实参类型自动推导。
|
|
6
|
+
|
|
7
|
+
## 核心改动
|
|
8
|
+
|
|
9
|
+
### `src/calcit/type_annotation.rs`
|
|
10
|
+
|
|
11
|
+
- 新增 `substitute_type_vars(&self, bindings) -> Arc<CalcitTypeAnnotation>`:将注解中的 `TypeVar` 替换为 `bindings` 中已绑定的具体类型,未绑定的保留原样。递归处理 `List`、`Map`、`Set`、`Ref`、`Optional`、`Variadic`、`Fn`、`AppliedStruct` 等复合类型。
|
|
12
|
+
- 新增 `contains_type_var(&self) -> bool`:判断注解中是否包含 `TypeVar`,用于快速跳过不需要泛型解析的路径。
|
|
13
|
+
- `extract_generics_from_hint_form` 中增加对 `"generics"` 关键字的识别(原来只识别 `"type-vars"`)。
|
|
14
|
+
|
|
15
|
+
### `src/runner/preprocess.rs`
|
|
16
|
+
|
|
17
|
+
- 新增 `resolve_generic_return_type(fn_info, call_args, scope_types)`:当被调函数的返回类型包含 TypeVar 时,用 `matches_with_bindings` 将实参类型与形参注解匹配以收集绑定,然后用 `substitute_type_vars` 替换返回类型中的 TypeVar。
|
|
18
|
+
- `infer_type_from_expr` 中 4 个返回 `info.return_type.clone()` 的路径(Import evaled、Import code、Symbol lookup、直接 Fn head)在检测到 TypeVar 返回类型时,优先尝试 `resolve_generic_return_type`。
|
|
19
|
+
- `check_function_return_type` 中,当声明的返回类型包含 TypeVar 时跳过定义体校验(泛型返回类型只在调用处才可确定)。
|
|
20
|
+
|
|
21
|
+
### `calcit/test-types-inference.cirru`
|
|
22
|
+
|
|
23
|
+
- 新增 `test-generics-identity` 测试:验证 `identity 42` 推导为 `:number`、`identity |hello` 推导为 `:string`。
|
|
24
|
+
|
|
25
|
+
## 验证
|
|
26
|
+
|
|
27
|
+
- `cargo test` 全部通过
|
|
28
|
+
- `cargo clippy -- -D warnings` 无警告
|
|
29
|
+
- `yarn check-all` 全量集成测试通过
|
|
30
|
+
- `&inspect-type` 输出确认 `'n => number`、`'s => string`
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# 2026-03-05 类型推断增强记录
|
|
2
|
+
|
|
3
|
+
## 本次目标
|
|
4
|
+
|
|
5
|
+
- 在 upstream 重置后,重新加强类型推断能力,并保证 debug/release/全量检查都稳定通过。
|
|
6
|
+
|
|
7
|
+
## 关键改动
|
|
8
|
+
|
|
9
|
+
### 1) 预处理阶段 Proc 返回类型传播增强
|
|
10
|
+
|
|
11
|
+
在 `src/runner/preprocess.rs` 的 `infer_type_from_expr` 中补齐多类内建过程返回类型推断:
|
|
12
|
+
|
|
13
|
+
- List 保型:`sort`、`&list:concat`、`&list:assoc`、`&list:assoc-before`、`&list:assoc-after`、`&list:dissoc`。
|
|
14
|
+
- 固定返回:`range -> list<number>`,`split/split-lines -> list<string>`,`&map:to-list -> list`。
|
|
15
|
+
- Map 保型:`&map:assoc`、`&map:dissoc`、`&merge`、`&merge-non-nil`、`&map:diff-new`。
|
|
16
|
+
- Set 保型:`&include`、`&exclude`、`&difference`、`&union`、`&set:intersection`。
|
|
17
|
+
|
|
18
|
+
### 2) defn 参数类型提示传播修复(避免栈溢出)
|
|
19
|
+
|
|
20
|
+
在 `src/builtins/syntax.rs` 中增强 `defn` 的 `arg_types` 回退策略:
|
|
21
|
+
|
|
22
|
+
- 第一层:沿用原逻辑,从 body 中 `assert-type` 模式提取。
|
|
23
|
+
- 第二层:当全是 Dynamic 时,从参数列表 Local 的 `type_info` 读取。
|
|
24
|
+
- 第三层:当仍全是 Dynamic 时,从**预处理后 body 顶层 Local**提取(`assert-type` 被预处理成 typed local 的场景)。
|
|
25
|
+
- 为避免核心加载路径栈压力,`calcit.core` 命名空间跳过该额外回退扫描。
|
|
26
|
+
|
|
27
|
+
### 3) calcit-core 类型提示细化
|
|
28
|
+
|
|
29
|
+
在 `src/cirru/calcit-core.cirru` 为以下列表保型函数补充泛型提示:
|
|
30
|
+
|
|
31
|
+
- `distinct`
|
|
32
|
+
- `drop`
|
|
33
|
+
- `repeat`
|
|
34
|
+
- `reverse`
|
|
35
|
+
- `take`
|
|
36
|
+
- `take-last`
|
|
37
|
+
|
|
38
|
+
统一使用 `hint-fn (generics 'T) $ return-type (:: :list 'T)`,并将输入列表参数改为 `assert-type ... (:: :list 'T)`。
|
|
39
|
+
|
|
40
|
+
### 4) 测试用例一致性调整
|
|
41
|
+
|
|
42
|
+
`calcit/test-types.cirru` 中 `test-arg-type-hints` 示例从故意错误参数改为合法参数,避免新检查路径把 warning 升级为阻断导致全量检查失败。
|
|
43
|
+
|
|
44
|
+
## 过程中的关键排障结论
|
|
45
|
+
|
|
46
|
+
- 多次尝试在 `preprocess_defn` 内直接重建/替换参数 AST 或引入额外全局缓存时,容易触发主流程栈溢出。
|
|
47
|
+
- 最终采用“最小干预 + 避开 core 热路径回退扫描”的方式稳定通过。
|
|
48
|
+
|
|
49
|
+
## 验证结果
|
|
50
|
+
|
|
51
|
+
- `cargo test` 通过。
|
|
52
|
+
- `yarn check-all` 通过(`EXIT:0`)。
|
|
53
|
+
- `cargo build --release` + `./target/release/cr calcit/test.cirru -1` 通过。
|
|
54
|
+
- `./target/debug/cr calcit/test.cirru -1` 通过。
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# 2026-03-05 calict.core 泛型签名细化
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
|
|
5
|
+
在不改变运行时语义的前提下,继续收紧 `calcit-core.cirru` 中 `defn` 的类型签名,让类型推断更稳定、错误提示更精确。
|
|
6
|
+
|
|
7
|
+
## 主要改动
|
|
8
|
+
|
|
9
|
+
本次聚焦于“保型列表函数”“可选返回值函数”“函数组合器”“map 回调函数形状”四类。
|
|
10
|
+
|
|
11
|
+
### 一、列表保型与可选返回
|
|
12
|
+
|
|
13
|
+
- `distinct`, `drop`, `repeat`, `reverse`, `take`, `take-last`
|
|
14
|
+
- 统一到 `hint-fn (generics 'T) $ return-type (:: :list 'T)`。
|
|
15
|
+
- `&list:find-last`
|
|
16
|
+
- 改为 `(:: :optional 'T)`。
|
|
17
|
+
- `&list:find-last-index`, `&list:last-index-of`, `index-of`
|
|
18
|
+
- 改为 `(:: :optional :number)`。
|
|
19
|
+
- `&list:max`, `&list:min`
|
|
20
|
+
- 改为 `(:: :optional 'T)`。
|
|
21
|
+
|
|
22
|
+
### 二、列表/映射映射函数泛型化
|
|
23
|
+
|
|
24
|
+
- `&list:map`
|
|
25
|
+
- 从宽泛 `:list/:fn` 收紧到 `('T -> 'U)`,返回 `(:: :list 'U)`。
|
|
26
|
+
- `map`
|
|
27
|
+
- 回调从 `('T -> 'T)` 扩展为 `('T -> 'U)`,与 map 语义一致。
|
|
28
|
+
- `map-indexed`
|
|
29
|
+
- 返回类型显式为 `(:: :list 'U)`,回调签名改为 `(:number 'T) -> 'U`。
|
|
30
|
+
|
|
31
|
+
### 三、拼接与组合函数
|
|
32
|
+
|
|
33
|
+
- `concat`, `conj`, `interleave`, `join`
|
|
34
|
+
- 输入与输出统一到同一元素类型 `'T` 的列表。
|
|
35
|
+
- `&list:apply`
|
|
36
|
+
- 收紧为 `xs: list<T>, fs: list<(T -> U)>`,返回 `list<U>`。
|
|
37
|
+
|
|
38
|
+
### 四、函数组合器与 map helper
|
|
39
|
+
|
|
40
|
+
- `&fn:apply`, `&fn:bind`, `&fn:map`
|
|
41
|
+
- 增加 `('A 'B 'C)` 级别泛型,表达组合器的真实输入输出关系。
|
|
42
|
+
- `&map:map-list`
|
|
43
|
+
- 返回改为 `(:: :list 'U)`,并补充 `f/acc/pair` 类型约束。
|
|
44
|
+
- `&list:map-pair`
|
|
45
|
+
- 返回改为 `(:: :list 'U)`,`f` 改为 `('K 'V) -> 'U`。
|
|
46
|
+
- `&map:filter`, `&map:filter-kv`, `&map:map`
|
|
47
|
+
- 回调签名改为带输入/输出形状的泛型函数类型,减少 `:fn` 过宽带来的误判。
|
|
48
|
+
|
|
49
|
+
## 验证
|
|
50
|
+
|
|
51
|
+
日志均写入仓库内 `js-out/`:
|
|
52
|
+
|
|
53
|
+
- `yarn check-all > js-out/check-all.log`,`EXIT:0`
|
|
54
|
+
- `cargo build --release > js-out/release-build.log`
|
|
55
|
+
- `./target/release/cr calcit/test.cirru -1 > js-out/release-run.log`,`EXIT:0`
|
|
56
|
+
|
|
57
|
+
结论:本次改动仅增强静态类型信息,未改变运行行为,且全量检查通过。
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 2026-03-05 19:30 schema 迁移记录
|
|
2
|
+
|
|
3
|
+
## 本次改动
|
|
4
|
+
|
|
5
|
+
- 持续使用 `cr edit schema` 迁移 `src/cirru/calcit-core.cirru` 中 `defn` 的 `:schema`。
|
|
6
|
+
- 统一命令形式为:
|
|
7
|
+
- entry 使用 `src/cirru/calcit-core.cirru`
|
|
8
|
+
- target 使用 `namespace/definition`
|
|
9
|
+
- schema 使用 pair map 语法:`{} (:kind :fn) (:args ...) (:return ...)`
|
|
10
|
+
- 对包含 `?` 的函数名(如 `set?`)在 shell 中使用引号,避免 zsh 通配展开。
|
|
11
|
+
|
|
12
|
+
## 迁移经验
|
|
13
|
+
|
|
14
|
+
- `cr -- calcit/test.cirru edit schema calcit.core/...` 会触发“只允许 app 包编辑”,改用 core 源文件作为 entry 可编辑 core namespace。
|
|
15
|
+
- schema map 在 `-e` 中不能写成 `:args ... :return ...` 平铺键值,必须写成 `(:args ...)`、`(:return ...)` 的 pair。
|
|
16
|
+
- 批量链式命令中若某个目标失败,会中断后续目标;迁移后需要用搜索确认实际落盘范围。
|
|
17
|
+
|
|
18
|
+
## 本轮补充的 schema(代表性)
|
|
19
|
+
|
|
20
|
+
- `section-by`, `select-keys`, `set?`, `slice`, `some-in?`, `some?`
|
|
21
|
+
- `str`, `str-spaced`, `string?`, `strip-prefix`, `strip-suffix`
|
|
22
|
+
- `struct?`, `symbol?`, `syntax?`, `tag?`, `tagging-edn`
|
|
23
|
+
- `take`, `take-last`, `thread-step?`, `tuple?`, `turn-str`
|
|
24
|
+
- `union`, `unselect-keys`, `update`, `update-in`
|
|
25
|
+
- `option:map`, `optionally`, `pairs-map`, `range-bothway`, `result:map`, `vals`, `zipmap`
|
|
26
|
+
- `calcit.internal/normalize-trait-type`
|
|
27
|
+
|
|
28
|
+
## 验证
|
|
29
|
+
|
|
30
|
+
- 运行通过:`cargo test && yarn check-all`。
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# 2026-03-05 19:39 query schema 适配
|
|
2
|
+
|
|
3
|
+
## 改动概要
|
|
4
|
+
|
|
5
|
+
- 针对 `cr query` 工具链补充 schema 感知与展示,修改文件:
|
|
6
|
+
- `src/bin/cli_handlers/query.rs`
|
|
7
|
+
|
|
8
|
+
## 具体更新
|
|
9
|
+
|
|
10
|
+
- `query defs <ns>`
|
|
11
|
+
- 对带 schema 的定义添加 `[schema]` 提示,便于快速识别迁移覆盖率。
|
|
12
|
+
|
|
13
|
+
- `query def <ns/def>`
|
|
14
|
+
- 新增 `Schema` 区块输出(无 schema 时显示 `(none)`)。
|
|
15
|
+
- `--json` 输出从仅 code 扩展为完整 `CodeEntry` 结构:
|
|
16
|
+
- `doc`
|
|
17
|
+
- `examples`
|
|
18
|
+
- `code`
|
|
19
|
+
- `schema`
|
|
20
|
+
|
|
21
|
+
- `query peek <ns/def>`
|
|
22
|
+
- 新增 schema 预览(one-liner,超长截断)。
|
|
23
|
+
|
|
24
|
+
- `query find <symbol>`
|
|
25
|
+
- 引用搜索范围从 `code` 扩展到 `schema`。
|
|
26
|
+
- 输出中标注命中来源 `[code]` / `[schema]`。
|
|
27
|
+
|
|
28
|
+
- `query usages <ns/def>`
|
|
29
|
+
- 用法搜索范围从 `code` 扩展到 `schema`。
|
|
30
|
+
- 输出中标注命中来源 `[code]` / `[schema]`。
|
|
31
|
+
|
|
32
|
+
## 兼容性说明
|
|
33
|
+
|
|
34
|
+
- 未新增 CLI 参数,保持原命令兼容;schema 能力默认生效。
|
|
35
|
+
- 仅增强查询展示与搜索语义,不影响运行时求值逻辑。
|
|
36
|
+
|
|
37
|
+
## 验证
|
|
38
|
+
|
|
39
|
+
- 已执行并通过:
|
|
40
|
+
- `cargo fmt`
|
|
41
|
+
- `cargo test`
|
|
42
|
+
- `yarn check-all`
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# 2026-03-06 01:20 Schema Hint Migration Batch
|
|
2
|
+
|
|
3
|
+
## 概要
|
|
4
|
+
|
|
5
|
+
本次集中推进 `hint-fn` 新 schema map 语法迁移,覆盖 core 与测试快照中的多批函数/示例;并持续以 `cargo run --bin cr -- calcit/test.cirru -1` 与 `yarn check-all` 做回归验证。
|
|
6
|
+
|
|
7
|
+
## 关键变更点
|
|
8
|
+
|
|
9
|
+
- 在 `src/cirru/calcit-core.cirru` 中将大量旧写法 `hint-fn $ return-type ...` 迁移为:
|
|
10
|
+
- `hint-fn $ {}`
|
|
11
|
+
- `:args` / `:rest` / `:return` / `:generics` 等字段显式表达
|
|
12
|
+
- 对已由 `:args`/`:rest` 覆盖的参数类型,移除冗余 `assert-type`(保持必要运行时校验逻辑不变)。
|
|
13
|
+
- 同步修正若干 schema 与实现不一致处(例如部分参数应为 `:number`/`:fn` 的场景)。
|
|
14
|
+
- 完成多处嵌套局部函数(如 `%map`、`%map-indexed`、`%pairs-map`、`%select-keys`、`%repeat` 等)的 hint 迁移。
|
|
15
|
+
- 继续保持 `calcit/*.cirru` 测试快照侧的新语法一致性。
|
|
16
|
+
|
|
17
|
+
## 工具与流程
|
|
18
|
+
|
|
19
|
+
- 使用新增命令 `cr edit format` 对变更的 snapshot 文件做规范化重写:
|
|
20
|
+
- `src/cirru/calcit-core.cirru`
|
|
21
|
+
- `calcit/test-generics.cirru`
|
|
22
|
+
- `calcit/test-js.cirru`
|
|
23
|
+
- `calcit/test-types-inference.cirru`
|
|
24
|
+
- `calcit/test-types.cirru`
|
|
25
|
+
- 使用 `cargo fmt` 统一 Rust 代码格式。
|
|
26
|
+
|
|
27
|
+
## 验证
|
|
28
|
+
|
|
29
|
+
- 多轮执行并通过:
|
|
30
|
+
- `cargo run --bin cr -- calcit/test.cirru -1`
|
|
31
|
+
- `yarn check-all`
|
|
32
|
+
- 迁移过程中持续统计 `src/cirru/calcit-core.cirru` 内剩余旧模式数量,确认总体下降趋势。
|
|
33
|
+
|
|
34
|
+
## 经验
|
|
35
|
+
|
|
36
|
+
- 在 schema-first 迁移中,优先迁移“签名简单、类型清晰”的函数可快速稳定推进。
|
|
37
|
+
- 对复杂逻辑函数保留运行时断言,但避免与 `:args` 重复声明同一层参数类型。
|
|
38
|
+
- 每批次后立即执行 targeted + full 验证,能快速定位回归并降低累计风险。
|