@calcit/procs 0.12.14 → 0.12.17

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
@@ -0,0 +1,33 @@
1
+ # 202504121453 - Automatic map-to-record rewrite in preprocessing
2
+
3
+ ## Summary
4
+
5
+ When a function parameter is typed as a struct type in its schema, but the caller passes a hashmap literal `{}`, the preprocessor now automatically rewrites the hashmap to a record construction `%{}` so that runtime gets a proper record with full type checking.
6
+
7
+ ## Key implementation details
8
+
9
+ ### type_annotation.rs
10
+ - Added `CalcitTypeAnnotation::resolve_to_struct()` — resolves `Struct`, `Record`, `TypeRef("ns/def")`, and `Optional(inner)` variants to concrete `CalcitStruct` definitions
11
+ - Added `resolve_struct_from_program(ns, def)` helper — looks up struct definitions from the program registry via `lookup_runtime_ready_registered` with fallback to `lookup_def_code_registered`
12
+
13
+ ### preprocess.rs
14
+ - Added `try_rewrite_map_args_to_records()` — iterates processed args, checks each against `fn_info.arg_types[idx]`, calls `try_rewrite_single_map_to_record()` for potential rewrites
15
+ - Added `try_rewrite_single_map_to_record()` — validates arg is `List` with `Proc(NativeMap)` head, resolves expected type to struct, validates all keys are tags, builds `[Proc(NativeRecord), Struct(def), k1, v1, ...]`
16
+ - Integration point: `preprocess_list_call()` → `Calcit::Fn` match arm, between arg preprocessing and type checking
17
+
18
+ ### AST transformation
19
+ - Input (hashmap literal): `[Proc(NativeMap), Tag(:x), 10, Tag(:y), 20]`
20
+ - Output (record literal): `[Proc(NativeRecord), Struct(Point2D), Tag(:x), 10, Tag(:y), 20]`
21
+
22
+ ### Rewrite conditions
23
+ - Function has schema with `:args` referencing a struct type (TypeRef, Struct, or Record)
24
+ - Argument is a hashmap literal (`{}`) with tag keys
25
+ - Struct definition is resolvable at preprocess time
26
+ - If any condition fails, argument is left unchanged (safe fallback)
27
+
28
+ ## Test
29
+ - Added `Point2D`, `sum-point`, `check-point-type`, `test-map-to-record` to `calcit/test-record.cirru`
30
+ - `check-point-type` uses `record?` to verify the rewrite produces actual records, not just maps
31
+
32
+ ## Gotcha
33
+ - `cr tree insert-after` can create doubly-nested nodes — always verify with `tree show` after insertion
@@ -55,4 +55,4 @@
55
55
  ## 后续经验
56
56
 
57
57
  - docs 元数据一旦进入 CLI 行为,就应该配套规范页与验证页,否则后续加字段很容易出现“文档能写、检索却不稳定”的分叉。
58
- - 模块文档检索要尽量和 core docs 走同一套 resolver,这样使用者不需要记两套命令心智模型。
58
+ - 模块文档检索要尽量和 core docs 走同一套 resolver,这样使用者不需要记两套命令心智模型。
@@ -0,0 +1,23 @@
1
+ # 2026-0410-0011 — 修复 snapshot `configs.version` 对 `||` 空字符串的漏判
2
+
3
+ ## 背景
4
+
5
+ PR `snapshot-format` 新增了空 `configs.version` 的回归测试,但 GitHub Actions 上加载 `compact.cirru` 时,`(:version ||)` 会解析成字符串 `"|"`,没有命中原先仅检查空白字符串的校验。
6
+
7
+ ## 知识点
8
+
9
+ - Cirru 的空字符串字面量 `||` 在这条解析路径里会落成 `"|"`,不能只靠 `trim().is_empty()` 判断是否为空。
10
+ - `configs.version` 的校验要同时覆盖真正空串和这个 pipe marker,才能在加载阶段稳定报出 `configs.version cannot be empty`。
11
+
12
+ ## 修改
13
+
14
+ - `src/snapshot.rs`
15
+ - 将 `parse_snapshot_config_string_field` 中 `version` 的空值判断扩展为:`text.trim().is_empty() || text == "|"`。
16
+
17
+ ## 验证
18
+
19
+ ```bash
20
+ cargo fmt -- src/snapshot.rs
21
+ ```
22
+
23
+ 本地 `cargo test` 在当前机器仍受 macOS 链接器环境影响(`ld: library 'System' not found`),实际正确性改由 GitHub Actions 复核。
@@ -0,0 +1,26 @@
1
+ # JS Codegen Fix for Map-to-Record Rewrite
2
+
3
+ ## Problem
4
+ The map-to-record rewrite (from previous commit) inserted `Calcit::Struct` directly into the AST. The interpreter handles this, but JS codegen (`emit_js.rs` line 319) has no arm for `Calcit::Struct` → panics with `unreachable!`.
5
+
6
+ ## Key Insight
7
+ The `%{}` macro normally expands to `&%{}` with a **symbol/import reference** as the first arg (not a literal `Calcit::Struct`). JS codegen relies on this — it emits the symbol as a variable reference (e.g., `Element`), and the JS runtime `_$n__PCT__$M_` function accepts the struct object at runtime.
8
+
9
+ ## Solution
10
+ 1. Added `resolve_to_struct_with_ref()` to `CalcitTypeAnnotation` — returns `Option<(CalcitStruct, Option<(Arc<str>, Arc<str>)>)>` with ns/def path from TypeRef annotations.
11
+ 2. Modified `try_rewrite_single_map_to_record()` to emit `Calcit::Import(CalcitImport { ... })` instead of `Calcit::Struct`:
12
+ - `ImportInfo::SameFile` when struct is in same namespace (avoids self-import duplicate declaration)
13
+ - `ImportInfo::NsReferDef` when struct is in a different namespace (generates proper cross-ns import)
14
+ - Falls back to `Calcit::Struct` only when no ns/def path is available
15
+
16
+ ## Pitfall: Self-Import Duplicate Declaration
17
+ First attempt used `NsReferDef` for all imports — caused `SyntaxError: Identifier 'Point2D' has already been declared` because a self-import was generated when struct is in same namespace. Fix: detect `ns == file_ns` and use `SameFile`.
18
+
19
+ ## Verification in Respo
20
+ - Typed `element->string` as `'respo.schema/Element`, `make-string` as `'respo.schema/Component`
21
+ - Test passes `{}` map to `element->string` → auto-rewritten to `%{} Element ...` record construction
22
+ - Generated JS: `import { Element } from "./respo.schema.mjs"` + `$clt._$n__PCT__$M_(Element, ...)`
23
+
24
+ ## Files Changed
25
+ - `src/calcit/type_annotation.rs`: Added `resolve_to_struct_with_ref()`
26
+ - `src/runner/preprocess.rs`: Emit Import reference in `try_rewrite_single_map_to_record()`
@@ -0,0 +1,29 @@
1
+ # Map-to-Record Field Validation and Nil Fill
2
+
3
+ ## Problem
4
+ Map-to-record rewrite was passing through ALL map keys without validation, and emitting only
5
+ the keys present in the map. This caused:
6
+ 1. Maps with keys not in the struct (e.g., `:x`) being incorrectly rewritten to records
7
+ 2. "fields size does not match" runtime error when the map has fewer keys than struct fields
8
+
9
+ ## Fix (preprocess.rs)
10
+ 1. **Field validation**: Check each map key against `struct_def.fields`. If any key is not a
11
+ valid struct field, skip the rewrite entirely (stay as map).
12
+ 2. **Nil fill**: Emit ALL struct fields in definition order. For fields not present in the map,
13
+ emit `Calcit::Nil`. This ensures the record always has the correct field count.
14
+ 3. **HashMap tracking**: Build a `HashMap<EdnTag, &Calcit>` of provided fields for O(1) lookup
15
+ during emission.
16
+
17
+ ## Key Types
18
+ - `struct_def.fields` is `Vec<EdnTag>` — compare with `EdnTag` directly, not `Arc<str>`
19
+ - `provided_fields` is `HashMap<EdnTag, &Calcit>` — avoids the `Arc<str>: Borrow<EdnTag>` issue
20
+
21
+ ## Respo DomProps Design
22
+ - 29 fields with `(:: :optional <type>)` annotations
23
+ - Strings: class-name, id, type, href, src, placeholder, name, title, data-name, data-comp, target
24
+ - Dynamic: style (map), value, inner-text
25
+ - Bools: disabled, checked, spell-check, read-only, selected
26
+ - Number: tab-index
27
+ - Fns: on-click, on-input, on-focus, on-blur, on-keydown, on-keyup, on-change
28
+ - Maps: on, event
29
+ - `create-element`/`create-list-element` convert record props to map via `&record:to-map`
@@ -0,0 +1,13 @@
1
+ ## 概要
2
+
3
+ 为 tuple-to-enum 自动改写实现预处理阶段支持。当函数参数 schema 标注为 enum 类型时,
4
+ 调用者传递的 untyped tuple `:: :tag payload` 自动改写为 `%:: Enum :tag payload`。
5
+
6
+ ## 知识点
7
+
8
+ - `CalcitTypeAnnotation::resolve_to_enum_with_ref()` 对应 struct 端已有的 `resolve_to_struct_with_ref()`
9
+ - `resolve_enum_from_program()` 通过 `lookup_runtime_ready_registered` + `lookup_def_code_registered` 两级缓存查找
10
+ - tuple 改写只替换 head proc (`NativeTuple` → `NativeEnumTupleNew`) 并插入 enum 引用,不对 tag/payload 做验证(由 `check_enum_tuple_construction` 兜底)
11
+ - JS codegen 需要 `Calcit::Import` 而非 `Calcit::Enum` 内联值,通过 TypeRef 路径判断走 SameFile 还是 NsReferDef
12
+ - map-to-record 改写验证 key 合法性,tuple-to-enum 改写不验证(差异源于 struct field 是封闭集合,enum tag 验证已有独立检查)
13
+ - Cirru tag-match 零载荷变体的 pattern 是 `(:ok)` 而非 `:ok`(后者在 pair 中被当作 body)
@@ -0,0 +1,40 @@
1
+ # TypeSlot + Enum 编译期安全增强
2
+
3
+ ## 概要
4
+
5
+ 让 `deftype-slot` / `bind-type` 机制真正实现编译期 enum variant 校验。此前 TypeSlot 绑定的 enum 在自动推导 (`:: → %::`) 路径上缺乏变体验证,导致拼写错误或不存在的 variant 无法在 `cr js` / `--check-only` 时报出。
6
+
7
+ ## 修改的文件
8
+
9
+ ### `src/calcit/type_annotation.rs`
10
+ - `resolve_to_enum_with_ref()`: 新增 `TypeSlot` 分支,通过 `resolve_type_slot()` 解析绑定类型后委托
11
+ - `matches_with_bindings()`: 双向 `Tuple/Enum` 匹配 — 原先只有 `(Tuple, Enum)` 方向,TypeSlot 解析后顺序会翻转,新增 `(Enum, Tuple)` 方向
12
+
13
+ ### `src/calcit.rs`
14
+ - 将 `resolve_type_slot` 加入 `pub use` 导出列表
15
+
16
+ ### `src/runner/preprocess.rs`
17
+ - `resolve_enum_value()`: 在 `as_struct()` 调用前增加 TypeSlot 解析(避免 `&CalcitStruct` 生命周期问题)
18
+ - 新增 `try_rewrite_local_fn_tuple_args_to_enum_tuples()`: 对本地函数调用(如回调 `d!`)也执行 tuple→enum 自动重写
19
+ - 主预处理 `Calcit::Local` 分支: 增加 local fn 调用的重写+类型检查(clone 避免借用冲突)
20
+ - `try_rewrite_single_tuple_to_enum_tuple()`: 增加变体验证 — 重写后立即检查 tag 是否是 enum 的合法 variant,不合法则发出警告
21
+
22
+ ### `src/bin/cr_tests/type_fail.rs`
23
+ - 新增 `type_fail_type_slot_enum_invalid_variant` 测试,验证 TypeSlot 绑定的 enum 在自动重写路径下能正确检测不存在的 variant
24
+
25
+ ### `calcit/type-fail/type-slot-enum-invalid-variant.cirru`
26
+ - 新增测试 fixture:声明 `defenum Action`、通过 TypeSlot 绑定、调用 `takes-action $ :: :nonexistent |hello`,期望产生 variant 不存在警告
27
+
28
+ ## 知识点
29
+
30
+ 1. **TypeSlot 解析链**: `TypeSlot(name) → resolve_type_slot(name) → CalcitTypeAnnotation (通常是 TypeRef)` → 再通过 `resolve_to_enum_with_ref()` 得到 enum 定义。这是一个两步间接过程。
31
+
32
+ 2. **`matches_with_bindings` 双向性**: TypeSlot 被解析后会调 `bound.matches_with_bindings(other, bindings)`,此时 self/other 顺序可能翻转。所有组合型匹配(如 Tuple/Enum)都需要双向 pattern。
33
+
34
+ 3. **`as_struct()` 返回引用**: `CalcitTypeAnnotation::as_struct()` 返回 `Option<&CalcitStruct>`,无法从临时解析结果中返回引用。修复方案是在调用点先解析 TypeSlot 再调 `as_struct()`。
35
+
36
+ 4. **自动重写的 `%::` 不经过 `preprocess_expr`**: `try_rewrite_single_tuple_to_enum_tuple` 构造的 `[NativeEnumTupleNew, ...]` 列表是在预处理完参数后生成的,不会经过 `check_proc_arg_types` 中的 `check_enum_tuple_construction`。因此变体验证必须在重写函数内部完成。
37
+
38
+ 5. **Local fn 调用缺少自动重写**: 原先只有 `CalcitFn` 调用走 `try_rewrite_tuple_args_to_enum_tuples`。`Calcit::Local` 调用(如回调参数 `d!`)需要单独处理。新函数接受 `CalcitFnTypeAnnotation` 而非 `CalcitFn`。
39
+
40
+ 6. **Lambda-in-map 的 EXPECTED_FN_TYPE 盲区**: `fn (e d!)` 在 hashmap 值(如 `:on-click`)中时,`d!` 不会获得 `EXPECTED_FN_TYPE` 注入的类型信息。目前通过显式 `%:: Op :variant` 写法绕过,属于已知架构限制。
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calcit/procs",
3
- "version": "0.12.14",
3
+ "version": "0.12.17",
4
4
  "main": "./lib/calcit.procs.mjs",
5
5
  "devDependencies": {
6
6
  "@types/node": "^25.0.9",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calcit/procs",
3
- "version": "0.12.14",
3
+ "version": "0.12.17",
4
4
  "main": "./lib/calcit.procs.mjs",
5
5
  "devDependencies": {
6
6
  "@types/node": "^25.0.9",
@@ -22,7 +22,7 @@ import {
22
22
  } from "./calcit-data.mjs";
23
23
 
24
24
  import { CalcitRef } from "./js-ref.mjs";
25
- import { fieldsEqual, CalcitRecord } from "./js-record.mjs";
25
+ import { CalcitRecord } from "./js-record.mjs";
26
26
  import { CalcitImpl } from "./js-impl.mjs";
27
27
  import { CalcitStruct } from "./js-struct.mjs";
28
28
  import { CalcitEnum } from "./js-enum.mjs";