Five days of silence on our submain have finally been broken — our audit Part 2 sprint nailed down three out of the four issues from Part 1. It’s not committed or merged yet, but it’s significant: the test matrix quietly climbed from 110 passing tests to a full 116/116 once these changes were staged.
Recursive Type Parser
Turns out our type_parser in parser.rs needed a little overhaul. Previously, we had a flat combinator chain using scalar.clone().or(named_type.clone()) to handle inner types like Result. Problem was, it couldn’t match the Result keyword recursively, which rendered expressions like Result> unparseable.
Enter Chumsky’s recursive() combinator. With it, we've refactored so ty can now get passed into all our sub-parsers. And the result? We’re now unlocking some cool stuff like nested Result types, treating Handle as a first-class type, and dealing with arrays of complex types like [N: Result]. It’s a quieter change, but an essential structure shift — we’re now supporting recursive type compositions across the board.
Struct Field Overflow Truncation
We did some overflow enforcement work in dc564a7 for arithmetic expressions, but oops — missed a spot with struct field writes. When you did something like c.value += 10 on a t8 field, it skipped width truncation and returned a full i128.
The fix is in. Now, the semantic pass in semantic.rs gets the real field type for DotAccess LValues by looking up the field in self.structs and ensuring types are compatible. It also addresses CompoundAssign targets, so now the ty field on a SemanticLValue::DotAccess reflects the actual field type. Runtime also got some love, with apply_numeric_cast(val, ty) making sure both Assign and CompoundAssign filter through the LValue’s type before storing. Truncation gap, consider yourself closed.
64 MB Interpreter Thread
Our old setup saw fib(10) generating about 177 recursive calls — enough to hit a stack overflow when it reached fib(15) on a mere 1 MB Windows thread stack.
Solution? We went for the big guns: increased stack size to 64 MB with std::thread::Builder::new().stack_size(64 * 1024 * 1024). It’s a stop-gap that hands us the room needed to handle any reasonable recursive Cx program. We’ll work on reducing frame stack consumption down the line, but for now, it does the job flawlessly. fib(15) cranks out 610 calls without a hitch.
New Tests and Examples
Our suite gained six new tests: struct field compound assign overflow (t109), struct field direct assign overflow (t110), nested Result round-trip (t111), array of Result (t112), recursive fib to 610 (t113), and a field type mismatch rejection (t114).
Six brand-new example programs also joined our stable: hello, fibonacci, fizzbuzz, arrays and loops, error handling, and tbool uncertainty. Each example program helps spotlight our dialect’s syntax and features.
What’s Next
One issue lingers from Part 1 (audit_03 and audit_05), but they’re intentional untyped-assignment rejections, not bugs. Part 2 has us laser-focused on arrays, imports with generics, nested generics, and copy_into handling under Result.
We’re 15 commits short of merging submain into main, plus whatever Part 2 culminates in. Next steps? Pushing through that integration, updating the roadmap, and pivoting to IR lowering for Result. The lower.rs file is still holding onto some unsupported! arms for ResultOk/ResultErr/Try, so that's on our radar too.
Follow the Cx language project:
- Website: cx-lang.com
- GitHub: github.com/COMMENTERTHE9/Cx_lang
- Dev.to: dev.to/commenterthe9
- Bluesky: thecomment.bsky.social
- Twitter/X: @commenterthe9
Originally published at https://cx-lang.com/blog/2026-04-18
Top comments (0)