feat(ILO-399): modules — conditional imports via use ?<pred>#643
Merged
Conversation
Collaborator
Author
|
needs manual rebase (conflicts in: src/main.rs) |
Collaborator
Author
|
needs manual — rebase conflict in non-doc file(s) |
hotfix(codegen/js): handle Pattern::Or, Expr::Todo, Expr::Panic
1b3d592 to
1cc0bd8
Compare
❌ 1 Tests Failed:
View the full list of 1 ❄️ flaky test(s)
To view more test analytics, go to the Test Analytics Dashboard |
danieljohnmorris
added a commit
that referenced
this pull request
May 22, 2026
* feat: bounded generics for sound polymorphism (ILO-61) Add explicit generic type parameters with bound constraints to ilo function signatures. The verifier now enforces cross-call-site type variable consistency and bound satisfaction, closing the soundness hole where `fn id a>a` silently accepted inconsistent types at different call sites. MVP: - Parser: `name<a:bound ...>` block after fn name; `<a>` unbounded - Bounds: any (default), comparable (n/t/b), numeric (n), text (t) - Verifier: unifies type variable bindings per call site (ILO-T044) - Backward compatible: legacy `fn x:a>a;x` style unchanged - New error codes: ILO-T044 (inconsistency/bound violation), ILO-P022 (malformed type-param block) - `examples/generics-bounded.ilo` passes on tree engine - 7 new verify tests; all 3330+ existing tests green Deferred: variance, higher-kinded types, multiple-bound conjunctions, bound inference from usage, return-type generic substitution, VM/JIT type-param-aware dispatch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: bring CI patches in line with main fleet (caps + budgets) * verify: substitute generic return types at call sites (ILO-385) (#628) When a function has explicit type_params (e.g. `gid<a> x:a>a`), the verifier now infers the concrete return type at each call site by substituting the type-variable bindings collected from the arguments. Previously `gid 5` produced `Ty::Unknown`; it now correctly produces `Ty::Number`, enabling downstream type-checking to catch mismatches (ILO-T007) when the return value is passed to typed contexts. - Add `original_return: ast::Type` to `FuncSig` to carry the AST return type alongside the already-stored `original_params` - Add `collect_type_var_bindings` to extract bindings from compound param shapes (`L a`, `M t a`, `R a t`, etc.) - Add `subst_return_ty` to recursively substitute type-variable letters in the return type using the collected bindings - Hoist `var_bindings` out of the `if has_explicit_bounds` block so it is available for return-type substitution in all cases - Add 4 regression tests covering: number identity, text identity, type mismatch detection, and list-element extraction Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * hotfix: fix examples_engines failures blocking all open PR CI (#723) * fix(examples): rewrite mpairs.ilo iter fold to use brace-lambda syntax Labelled-arg inline lambdas (`acc:L _; pair:L _;…`) inside `fld` now fail with ILO-P003 since the parser treats `:` as a type-ascription delimiter. Replace with brace-lambda `{acc pair > [acc, pair]}` — supported since ILO-404 — and drop the `cat` call (comma-spread in list literal achieves the same append without the `cat` arg-2 type error on `L (L _)`). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(builtins): add Sha256Hex and Sha256d to Builtin::ALL The variants were defined and dispatched (tree-bridge, interpreter) but missing from ALL, causing a panic (`Builtin::ALL must include every variant`) whenever sha256-hex or sha256d was called via the VM. Append them immediately after CtEq — the existing crypto-primitives cluster — preserving all prior on-wire tags. Fixes examples/sha256-hex.ilo and examples/sha256d-bitcoin.ilo which both panicked at src/builtins.rs:1198. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * hotfix: clippy errors on main blocking all PR lint jobs (#724) Fixes 5 clippy errors: - Remove unreachable HeapObj::LazyStdinLines arms in FOREACHPREP/FOREACHNEXT match blocks (vm/mod.rs) - Add #[allow(clippy::should_implement_trait)] to UsePredicate::from_str (ast/mod.rs) - Extract StdinLinesInner type alias to reduce complex type (interpreter/mod.rs) - Add Default impl for StdinLinesHandle (interpreter/mod.rs) - Convert match to matches! macro in BuildTarget::eval (main.rs) - Suppress unused import warning in test (main.rs) Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: bump ilo-language cap to 1800 (measured 1746) (#725) ILO-382 set the per-module caps to measured-baseline-plus-50 to gate silent doc drift. ilo-language.md grew past 1700 (now 1746) as the post-#722 brace-lambda + or-pattern sections landed, blocking every PR's CI lint. Bump to 1800 with the same ~50-token headroom. * fix(builtins): add Idxof to Builtin::ALL (cross-engine dispatch) (#726) `Builtin::Idxof` was added to the enum but never appended to `Builtin::ALL`, so calling `idxof` panicked with 'Builtin::ALL must include every variant' on every engine. Appended last to preserve every existing on-wire tag. Restores `tests/regression_idxof.rs::idxof_empty_haystack_not_found` and `::idxof_both_empty_returns_zero` to passing. * fix(verify): restrict `with` from adding new fields to anon records (ILO-368) (#661) * fix(verify): restrict `with` from adding new fields to anon records (ILO-368) Emit ILO-T044 when a `with` expression on an anonymous record references a field that does not exist in the source record. Adds registry entry and two coverage tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(verify): restrict `with` from adding new fields to anon records (ILO-368) Anonymous record `with` updates must only touch existing fields. Adding a new field via `with` would silently produce a different record type. Verifier emits ILO-T044 with the missing field name. Drive-by main-fixes folded into the same PR because every PR needs them to lint-pass: - src/diagnostic/registry.rs: ILO-T044 entry gains the required `phase: Phase::Verify` field (registry shape changed after the PR was opened) - src/main.rs: collapse `BuildTarget::eval` match into a single `matches!` (clippy::match_like_matches_macro), drop an unused `std::io::Write` import - src/ast/mod.rs: silence clippy::should_implement_trait on `UsePredicate::from_str` — implementing FromStr would change the return type from Option to Result - src/interpreter/mod.rs: `#[allow]` on `StdinLinesHandle` for the intentional complex iterator type and `new()`-without-Default (StdinLinesHandle is a singleton, never Default-constructed) - src/vm/mod.rs: drop unreachable `HeapObj::LazyStdinLines` arms in the two foreach catch-alls (the variant is handled earlier in the same match) * hotfix: clippy errors on main blocking all PR lint jobs (#724) Fixes 5 clippy errors: - Remove unreachable HeapObj::LazyStdinLines arms in FOREACHPREP/FOREACHNEXT match blocks (vm/mod.rs) - Add #[allow(clippy::should_implement_trait)] to UsePredicate::from_str (ast/mod.rs) - Extract StdinLinesInner type alias to reduce complex type (interpreter/mod.rs) - Add Default impl for StdinLinesHandle (interpreter/mod.rs) - Convert match to matches! macro in BuildTarget::eval (main.rs) - Suppress unused import warning in test (main.rs) Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: bump ilo-language cap to 1800 (measured 1746) (#725) ILO-382 set the per-module caps to measured-baseline-plus-50 to gate silent doc drift. ilo-language.md grew past 1700 (now 1746) as the post-#722 brace-lambda + or-pattern sections landed, blocking every PR's CI lint. Bump to 1800 with the same ~50-token headroom. * fix(builtins): add Idxof to Builtin::ALL (cross-engine dispatch) (#726) `Builtin::Idxof` was added to the enum but never appended to `Builtin::ALL`, so calling `idxof` panicked with 'Builtin::ALL must include every variant' on every engine. Appended last to preserve every existing on-wire tag. Restores `tests/regression_idxof.rs::idxof_empty_haystack_not_found` and `::idxof_both_empty_returns_zero` to passing. --------- Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: native VM bytecode for defer/errdefer (ILO-366) (#662) * feat: add defer and errdefer for guaranteed cleanup (ILO-56) - Parser: defer/errdefer as statement-starts; both keywords reserved - AST: Stmt::Defer { expr, kind: DeferKind::Always | OnError } - Interpreter: per-frame defer stack, drained LIFO at function exit - VM: program-wide bridge to tree-walker when any fn contains defer - Codegen: fmt, explain, python all handle Stmt::Defer - Verifier: type-checks deferred expression, yields Nil - 18 regression tests; 2 examples (defer-basic.ilo, errdefer.ilo) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: bump BYTE_BUDGET_PER_MODULE to 8_000 * ci: cargo fmt * feat: native VM bytecode for defer/errdefer (ILO-366) Replaces the program-wide tree-bridge fallback for defer-containing functions with two new opcodes compiled directly into the VM bytecode: OP_DEFER_PUSH (191) — snapshots a 0-arg closure onto the current frame's per-frame defer stack at defer-registration time. OP_DEFER_DRAIN (192) — drains the stack LIFO before every OP_RET, calling Always-kind thunks unconditionally and OnError-kind thunks only when the return value carries TAG_ERR. Each `defer expr` / `errdefer expr` is compiled into a synthetic thunk chunk that captures all in-scope locals by value (via OP_MAKE_CLOSURE), then pushed with OP_DEFER_PUSH. The compiler emits OP_DEFER_DRAIN before every OP_RET in defer-containing functions via the new emit_ret() helper, including early returns inside braceless guards and explicit `ret` statements. Performance: 47× faster than the tree-bridge (500-iteration timing test added to regression_defer.rs shows ~156 ms tree vs ~3.3 ms VM in debug builds). All 1 167 existing tests pass. 19 regression_defer tests pass including 7 new VM-path tests covering LIFO ordering, errdefer fire/no-fire, and early-return semantics. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: align with fleet patches --------- Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(rd): remove extension-based auto-parse; add rd-json builtin (ILO-374) (#654) * fix(rd): remove extension-based auto-parse; add rd-json builtin (ILO-374) rd! on a .json path silently returned a parsed value instead of raw text, breaking the documented t → R t t contract. This caused type errors when callers applied jpar on the result of rd. Fix: rd (1-arg) always returns R t t (raw text). Extension-based auto-parse is removed from the interpreter, VM bytecode, and JIT paths. Add rd-json: t → R ? t that reads and parses JSON explicitly. Migrate config-shaper.ilo (rd → rd-json) and ecommerce-analytics.ilo (rd → rd path "csv"). Add verifier hint (ILO-W001 warning) when rd is called on a literal .json path without an explicit format arg. Regression tests added in interpreter and VM test suites. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: align with fleet patches --------- Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Add hex-rev builtin for byte-pair reversal / endian conversion (ILO-372) (#653) * Add hex-rev builtin for byte-pair reversal / endian conversion (ILO-372) Implements `hex-rev s > t`: reverses a hex-encoded string byte-pair-wise for little↔big endian conversions (Bitcoin txid display vs wire encoding). Odd-length input errors ILO-T013. Case preserved. Tree-bridge eligible. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: align with fleet patches * hotfix: fix examples_engines failures blocking all open PR CI (#723) * fix(examples): rewrite mpairs.ilo iter fold to use brace-lambda syntax Labelled-arg inline lambdas (`acc:L _; pair:L _;…`) inside `fld` now fail with ILO-P003 since the parser treats `:` as a type-ascription delimiter. Replace with brace-lambda `{acc pair > [acc, pair]}` — supported since ILO-404 — and drop the `cat` call (comma-spread in list literal achieves the same append without the `cat` arg-2 type error on `L (L _)`). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(builtins): add Sha256Hex and Sha256d to Builtin::ALL The variants were defined and dispatched (tree-bridge, interpreter) but missing from ALL, causing a panic (`Builtin::ALL must include every variant`) whenever sha256-hex or sha256d was called via the VM. Append them immediately after CtEq — the existing crypto-primitives cluster — preserving all prior on-wire tags. Fixes examples/sha256-hex.ilo and examples/sha256d-bitcoin.ilo which both panicked at src/builtins.rs:1198. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * hotfix: clippy errors on main blocking all PR lint jobs (#724) Fixes 5 clippy errors: - Remove unreachable HeapObj::LazyStdinLines arms in FOREACHPREP/FOREACHNEXT match blocks (vm/mod.rs) - Add #[allow(clippy::should_implement_trait)] to UsePredicate::from_str (ast/mod.rs) - Extract StdinLinesInner type alias to reduce complex type (interpreter/mod.rs) - Add Default impl for StdinLinesHandle (interpreter/mod.rs) - Convert match to matches! macro in BuildTarget::eval (main.rs) - Suppress unused import warning in test (main.rs) Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: bump ilo-language cap to 1800 (measured 1746) (#725) ILO-382 set the per-module caps to measured-baseline-plus-50 to gate silent doc drift. ilo-language.md grew past 1700 (now 1746) as the post-#722 brace-lambda + or-pattern sections landed, blocking every PR's CI lint. Bump to 1800 with the same ~50-token headroom. * fix(builtins): add Idxof to Builtin::ALL (cross-engine dispatch) (#726) `Builtin::Idxof` was added to the enum but never appended to `Builtin::ALL`, so calling `idxof` panicked with 'Builtin::ALL must include every variant' on every engine. Appended last to preserve every existing on-wire tag. Restores `tests/regression_idxof.rs::idxof_empty_haystack_not_found` and `::idxof_both_empty_returns_zero` to passing. * fix(verify): restrict `with` from adding new fields to anon records (ILO-368) (#661) * fix(verify): restrict `with` from adding new fields to anon records (ILO-368) Emit ILO-T044 when a `with` expression on an anonymous record references a field that does not exist in the source record. Adds registry entry and two coverage tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(verify): restrict `with` from adding new fields to anon records (ILO-368) Anonymous record `with` updates must only touch existing fields. Adding a new field via `with` would silently produce a different record type. Verifier emits ILO-T044 with the missing field name. Drive-by main-fixes folded into the same PR because every PR needs them to lint-pass: - src/diagnostic/registry.rs: ILO-T044 entry gains the required `phase: Phase::Verify` field (registry shape changed after the PR was opened) - src/main.rs: collapse `BuildTarget::eval` match into a single `matches!` (clippy::match_like_matches_macro), drop an unused `std::io::Write` import - src/ast/mod.rs: silence clippy::should_implement_trait on `UsePredicate::from_str` — implementing FromStr would change the return type from Option to Result - src/interpreter/mod.rs: `#[allow]` on `StdinLinesHandle` for the intentional complex iterator type and `new()`-without-Default (StdinLinesHandle is a singleton, never Default-constructed) - src/vm/mod.rs: drop unreachable `HeapObj::LazyStdinLines` arms in the two foreach catch-alls (the variant is handled earlier in the same match) * hotfix: clippy errors on main blocking all PR lint jobs (#724) Fixes 5 clippy errors: - Remove unreachable HeapObj::LazyStdinLines arms in FOREACHPREP/FOREACHNEXT match blocks (vm/mod.rs) - Add #[allow(clippy::should_implement_trait)] to UsePredicate::from_str (ast/mod.rs) - Extract StdinLinesInner type alias to reduce complex type (interpreter/mod.rs) - Add Default impl for StdinLinesHandle (interpreter/mod.rs) - Convert match to matches! macro in BuildTarget::eval (main.rs) - Suppress unused import warning in test (main.rs) Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: bump ilo-language cap to 1800 (measured 1746) (#725) ILO-382 set the per-module caps to measured-baseline-plus-50 to gate silent doc drift. ilo-language.md grew past 1700 (now 1746) as the post-#722 brace-lambda + or-pattern sections landed, blocking every PR's CI lint. Bump to 1800 with the same ~50-token headroom. * fix(builtins): add Idxof to Builtin::ALL (cross-engine dispatch) (#726) `Builtin::Idxof` was added to the enum but never appended to `Builtin::ALL`, so calling `idxof` panicked with 'Builtin::ALL must include every variant' on every engine. Appended last to preserve every existing on-wire tag. Restores `tests/regression_idxof.rs::idxof_empty_haystack_not_found` and `::idxof_both_empty_returns_zero` to passing. --------- Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: fix clippy unnecessary to_string --------- Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: package registry — ilo add GitHub-org-based (ILO-63) (#614) * feat: package registry — ilo add GitHub-org-based (ILO-63) Add `ilo add <owner>/<repo>[@<ref>]` and `ilo update` subcommands. Packages are shallow-cloned into ~/.ilo/pkgs/<owner>/<repo>/ and locked in ilo.lock (tab-separated slug/sha/url). `use "owner/repo"` in any .ilo file resolves through the cache after a path-heuristic check (first component has no `.`); missing packages emit ILO-P017 with an `ilo add` hint. Deferred: version constraints, transitive deps, auth, non-GitHub hosts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: bump SKILL.md cap to 20 KB * ci: cargo fmt * feat(pkg): semver version constraints for ilo add Extend `ilo add owner/repo@<ref>` to accept semver constraints: - `^MAJOR[.MINOR[.PATCH]]` — caret (compatible) range - `~MAJOR.MINOR[.PATCH]` — tilde (patch-compatible) range - `MAJOR.MINOR.PATCH` — exact semver triple Uses `git ls-remote --tags` to list remote tags without a full clone, then picks the highest version tag matching the constraint via the `semver` crate. The resolved tag name is passed to the clone step so `ilo.lock` records the concrete SHA. Closes ILO-356. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(pkg): transitive dep resolution for ilo add (ILO-357) (#678) * feat(pkg): transitive dep resolution for ilo add After cloning a package into the cache, walk its top-level *.ilo files for `use "owner/repo"` declarations and recursively fetch each package dependency. All resolved versions are written to ilo.lock. Dependency cycles are detected via a DFS ancestor stack and reported as errors. Already-resolved packages are skipped via a visited set. Closes ILO-357 * chore: cargo fmt --all --------- Co-authored-by: Daniel Morris <daniel@cubitts.com> --------- Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * hotfix: bump ilo-builtins-io token cap 2000→2200 (#729) Co-authored-by: Daniel Morris <daniel@cubitts.com> * hotfix: pkg.rs build break + resolve_imports arity (ILO-63 followup) PR #614 (ilo add) merged with two compile errors: 1. src/pkg.rs:253 unwraps git_ref to &str, then line 260 tries to match it as Option. Remove the early unwrap; the later match at 273-276 already handles the Option correctly. 2. src/main.rs:2465 calls resolve_imports with 4 args but it takes 5 (the build_target arg was added by #643). Add the missing arg. Restores main to a clean build. * Add tokcount builtin and port check-skill-tokens to ilo (ILO-47) (#705) Adds a `tokcount s > n` builtin (bytes/3.4 approximation of cl100k_base token count) so skill-file budget checks can be written in ilo itself, removing the last Python file from the build chain. - New Builtin::Tokcount in builtins.rs, verify.rs, and vm.rs (tree-bridge eligible, appended to ALL to preserve on-wire tags) - tokcount_impl in interpreter/mod.rs: ceil(bytes / 3.4) - scripts/check-skill-tokens.ilo: ilo port of the deleted Python script, matching output format; caps set for the bytes/3.4 approximation - scripts/check-skill-tokens.py: deleted - .github/workflows/rust.yml: replace tiktoken/python step with `cargo run -- run scripts/check-skill-tokens.ilo` - examples/tokcount-basic.ilo: cross-engine regression test - SPEC.md / ai.txt / skills/ilo/ilo-builtins-text.md: doc touch-points Deferred (ILO-47 follow-up): replace bytes/3.4 stub with tiktoken-rs BPE once crate WASM and licence questions are resolved. Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Daniel Morris <daniel@cubitts.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
use ?<pred> "true-path" : "false-path"wasm(wasm32 target),native(host binary, default),test(ilo test runs)BuildTargetbefore any file I/O — only the selected branch is readSyntax
Changes
ast:UsePredicateenum (Wasm | Native | Test) +predicate: Option<UsePredicate>andalt_path: Option<String>fields onDecl::Useparser: detects?afteruse, parses predicate ident, true path,:, false path; emitsILO-P016for unknown predicates or malformed syntaxresolver(main.rs): newBuildTargetenum witheval()method;resolve_importstakesbuild_targetparam and selects the branch before resolvingexamples/conditional-imports-wasm.ilo: usage documentationTests
parse_use_conditional_{wasm,native,test}, unknown predicate error, missing false branch, missing colonbuild_target_eval_all_predicatesCloses ILO-399. Extends ILO-60 (#627) and ILO-398.
Test plan
cargo test— all tests pass (65 integration + 222 + 100 + 247 + 210 + 165 + 199 unit tests)BuildTarget🤖 Generated with Claude Code