The eval snapshot test is faster and more appropriate since the bug
is in the canonicalization phase. The test verifies both that the
code canonicalizes correctly (no incorrect captures) and that it
evaluates correctly at runtime.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The fix correctly identifies that variables bound inside nominal
patterns (like `s` in `Container.Box(s)`) are bound in the pattern
scope and don't need to be captured. This results in cleaner
canonical IR where functions that use nominal pattern matching
are now represented as pure lambdas instead of closures with
unnecessary captures.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
collectBoundVarsToScratch was not recursing into the backing pattern
for nominal and nominal_external patterns. This caused variables
bound in nominal patterns (e.g., Wrapper.Simple(s)) to not be tracked
as bound variables, leading to them incorrectly being included in the
free variables of match expression bodies.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The layout computation's addTypeVar function was clearing work fields
(pending_record_fields, pending_tuple_fields, pending_tags, etc.) at
the start of every call via clearRetainingCapacity(). This caused
corruption when processing nested types that trigger recursive calls.
Example problem case:
{ tag: Str, attrs: List([StringAttr(Str, Str), BoolAttr(Str, Bool)]) }
When processing this record:
1. Record handling pushes fields to pending_record_fields
2. Processing the List element triggers tag union handling
3. Tag union handling makes recursive addTypeVar calls for variant payloads
4. These recursive calls cleared pending_record_fields
5. When returning to finalize the outer record, pop() found empty stack
The fix removes clearRetainingCapacity entirely. All work fields now
persist across recursive calls and are cleaned up individually:
- pending_containers: pop() when container layout is finalized
- in_progress_vars: swapRemove() when type is cached
- pending_record_fields: pop() when field is resolved
- pending_tags: shrinkRetainingCapacity() via defer
This fixes the unreachable panic in roc-dom when rendering elements
with complex nested types like List([String(Str,Str), Bool(Str,Bool)]).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fix a bug where platforms exposing multiple modules would fail at runtime
with "nested value not found" errors when modules call each other's methods.
Root cause: compileAndSerializeModulesForEmbedding was passing module names
in the wrong order (exposed_modules.items vs sorted_modules), and wasn't
passing type module names when compiling sibling platform modules.
Changes:
- src/cli/main.zig: Pass sorted_modules as type module names when compiling
platform modules, platform main, and app modules
- src/eval/interpreter.zig: Improve error messages for nested_value_not_found
- src/canonicalize/Can.zig: Add trace output for nested_value_not_found
Test infrastructure:
- Add SimpleTestSpec and simple_list variant to platform_config.zig
- Add 8 str platform tests covering direct calls, transitive calls, and
diamond dependency patterns
- Update test_runner.zig to handle simple_list
- Add test_runner invocations for int and str platforms to build.zig
- Make CLI tests run sequentially to avoid cache race conditions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fix "TypeMismatch in body evaluation" panic when platform modules make
transitive calls (e.g., app → Helper → Core).
Changes:
- Pass sibling modules during platform module compilation so each module
can resolve imports to previously compiled siblings
- Detect type modules (using `:= [].{}` syntax) and set statement_idx
for proper qualified name lookup during canonicalization
- Pre-allocate compiled_modules ArrayList to avoid pointer invalidation
during reallocation
- Add fallback to env.imports.getResolvedModule in evalLookupExternal
for cross-module calls when current module context has changed
- Fix use-after-free bug in low_level_interp_test.zig by heap-allocating
imported_envs array
- Add integration test for transitive module imports
Note: Modules must be listed in dependency order in the platform's
exposes list (e.g., if Helper imports Core, Core must come before Helper).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The root cause was that numeric literals with `from_numeral` constraints
were being generalized (let-polymorphism), causing each lookup to create
a fresh instantiation. This meant constraints from later usage (like
List.get expecting U64) didn't propagate back to the original definition,
leaving the value as an unconstrained flex var that defaulted to Dec.
Fix:
1. In generalize.zig: Don't generalize flex vars with `from_numeral`
constraints at ANY rank (not just top_level)
2. In Check.zig: Don't instantiate during lookup if the var has a
`from_numeral` constraint - unify directly instead
This aligns with the design that let-generalization should only work for
things that are syntactically lambdas (e.g. `foo = |arg| ...`).
Also reverts the interpreter workaround - the proper fix is in the type
checker, not working around type system bugs in the interpreter.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When calling List.get with method syntax (my_list.get(0)), the
interpreter was causing a cycle in layout computation because rigid
type variables weren't being properly resolved.
The fix unifies the method's first parameter type with a copy of the
receiver type before instantiation. This properly resolves rigid type
variables (like `item` in List.get) to concrete types. A copy of the
receiver type is created before unification to avoid corrupting the
original type, since unification modifies both sides.
This is the same approach used for no-args method dispatch (like
List.first), but with the additional copy step needed because the
multi-args path may reuse types across multiple method invocations.
Fixes#8662🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>