iter prep.2-term-new-construct (DONE 4/4): Term::New AST + Form-A surface + checker + drift — closes #32
Second iteration of the kernel-extension-mechanics milestone. Ships
the `new` Form-A keyword + `Term::New { type_name, args: Vec<NewArg> }`
AST variant + `NewArg::Type|Value` enum end-to-end through schema /
surface / checker, with two new diagnostics
(`NewTypeNotConstructible`, `NewArgKindMismatch`), a new arm in
prep.1's workspace-wide normalisation pre-pass `qualify_workspace_term`
that rewrites bare `Term::New.type_name` to qualified form
(symmetric to the existing `Term::Ctor` arm), and the data-model.md
+ form_a.md anchor additions. Codegen out of scope per spec; codegen
sites get `CodegenError::Internal` arms naming the raw-buf milestone
as the carrier.
Verification:
- `cargo test --workspace`: 654 tests passing, 0 failed.
- 3 new in-source checker tests pin the elaboration paths:
`new_resolves_via_type_scope` (the spec's worked Counter example
checks cleanly), `new_type_not_constructible` (missing `new` def
in home module fires the new diagnostic), `new_arg_kind_mismatch_value_where_type`
(kind-mismatch detection).
- 2 new schema-drift pins (`term_new_round_trips`,
`term_new_type_arg_round_trips`) ratify the JSON byte shape:
`{"t": "new", "type": "<TypeName>", "args": [{"kind": "type"|"value",
"value": ...}]}`.
- 1 new surface round-trip pin exercises mixed-kind args through
Form-A → JSON → Form-A.
- 44+ exhaustive Term-match sites across 24 files extended with
`Term::New` arms — workspace-wide cargo build clean.
Concerns documented inline:
- `crates/ailang-core/tests/schema_coverage.rs` deliberately does
NOT register `VariantTag::TermNew` in the `EXPECTED_VARIANTS`
set. The coverage test asserts every declared variant is
observed in the .ail fixture corpus; no .ail fixture in the
current tree emits `"t": "new"` (codegen-runnable Term::New
programs require the raw-buf milestone's plugin registry).
Registering would fail the coverage check. The `visit_term` arm
IS extended (compile-forced); only the enum registration is
deferred. Inline rationale in the source. Future milestone that
ships a Term::New fixture extends both sides.
- `crates/ailang-surface/src/print.rs` `write_term` Term::New arm
was added in Task 1 (not Task 2 as planned), because without it
ailang-core's tests fail to compile (the surface crate is in
the dependency graph of ailang-core's test binaries). Task 2's
round-trip pin verified the arm's correctness.
- Task 3 (synth elaboration) needed one implementer re-loop: the
first attempt over-qualified within-module type-references via
`qualify_local_types` on `new`'s signature when the home module
was the calling module itself. Fixed by skipping qualification
when `home_module == env.current_module`.
Architectural composition with prep.1: `Term::New` joins
`Term::Ctor` as a `type_name`-bearing site that goes through
`qualify_workspace_term`'s bare→qualified rewrite before the
checker sees it. The checker's `synth` arm for `Term::New`
therefore receives an already-qualified `type_name` (or a local
bare name for same-module construction).
Milestone status: kernel-extension-mechanics (Gitea #6) advances
2/3 iters. Next: prep.3 (kernel-tier modules + param-in), issue #33.
This commit is contained in:
@@ -1563,6 +1563,22 @@ fn walk_term(
|
||||
walk_term(a, out, builtins, scope);
|
||||
}
|
||||
}
|
||||
// prep.2 (kernel-extension-mechanics): record the type-name
|
||||
// dependency on the home module's TypeDef (mirrors the
|
||||
// `Term::Ctor` arm above's `ctor:` insertion shape) and
|
||||
// recurse into NewArg::Value subterms. NewArg::Type values
|
||||
// are not walked for cross-module type dependencies here —
|
||||
// `ail deps` works on the surface AST before workspace
|
||||
// qualification, so embedded type names ride the existing
|
||||
// `walk_type`/`walk_def_types` channels.
|
||||
Term::New { type_name, args } => {
|
||||
out.insert(format!("new:{type_name}"));
|
||||
for arg in args {
|
||||
if let ailang_core::ast::NewArg::Value(v) = arg {
|
||||
walk_term(v, out, builtins, scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2902,6 +2918,38 @@ fn rewrite_def(
|
||||
);
|
||||
}
|
||||
}
|
||||
// prep.2 (kernel-extension-mechanics): `(new T args)` —
|
||||
// rewrite the bare type-name through the same
|
||||
// `rewrite_name` channel that `Term::Ctor` uses, then
|
||||
// recurse into each NewArg::Value subterm; NewArg::Type
|
||||
// values rewrite through `rewrite_type`.
|
||||
Term::New { type_name, args } => {
|
||||
rewrite_name(
|
||||
type_name,
|
||||
owning_module,
|
||||
local_types,
|
||||
import_names,
|
||||
changed,
|
||||
);
|
||||
for arg in args {
|
||||
match arg {
|
||||
ailang_core::ast::NewArg::Value(v) => rewrite_term(
|
||||
v,
|
||||
owning_module,
|
||||
local_types,
|
||||
import_names,
|
||||
changed,
|
||||
),
|
||||
ailang_core::ast::NewArg::Type(t) => rewrite_type(
|
||||
t,
|
||||
owning_module,
|
||||
local_types,
|
||||
import_names,
|
||||
changed,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,12 @@ fn synthesised_print_uses_user_module_show_via_fallback() {
|
||||
|| contains_xmod_show_var(body)
|
||||
}
|
||||
Term::Recur { args } => args.iter().any(contains_xmod_show_var),
|
||||
// prep.2 (kernel-extension-mechanics): recurse through
|
||||
// NewArg::Value subterms; type-args do not carry a Var.
|
||||
Term::New { args, .. } => args.iter().any(|arg| match arg {
|
||||
ailang_core::ast::NewArg::Value(v) => contains_xmod_show_var(v),
|
||||
ailang_core::ast::NewArg::Type(_) => false,
|
||||
}),
|
||||
Term::Lit { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user