A parser with no changes to its lowering layer fixed a JIT failure. Sounds improbable? That's what we tackled recently in Cx development. Throughout our session, four issues were closed in two subsystems, focusing on correctness and stability, on the submain branch.
The Trailing-Builtin Parser Fix
Consider a print(x) as the final line of a function without a trailing semicolon. Previously, this line would be promoted to a ret_expr by our parser's func_body combinator — resulting in semantic artifacts that disrupted the lowering system. This oversight led to an unsettling "unresolved semantic artifact reached lowering" error.
The solution was a simple yet effective new predicate, is_statement_level_builtin_call(), in src/frontend/parser.rs. It specifically recognizes trailing calls like print, println, printn, assert, and assert_eq, and ensures they're treated as standard statements rather than being prematurely promoted. This fix bypasses unnecessary promotions and channels the calls through the existing lower_stmt dispatcher. Interestingly, this was purely a parser misclassification error, not a failure of the lowering layer.
Here's what changed: 5 JIT-SKIP fixtures are now back in action: t29_forward_decl, t31_strref_forward_combined, t67_macro_outer_test, t68_macro_outer_deprecated, and t_array_elem_arg_in_range. Initially, the prediction was 8, but two fixtures were disqualified earlier in macro processing, and t50_nested_func_no_leak remains on hold. JIT parity advanced from 261/60/0 to 267/55/0.
Scope Boundaries for Control-Flow Bodies
Variables declared within if, else, else-if, while, loop, and while-in bodies were unintentionally bleeding out into the surrounding scope. This problem boiled down to missing push_scope() and pop_scope() calls in src/frontend/semantic.rs. By adding these, we ensured proper scope boundaries.
Four new test fixtures (t179_if_scope, t180_while_scope, t181_loop_scope, t182_whilein_scope) now check that names declared within those bodies aren't accessible outside of them. Previously, incorrect programs could silently pass by misusing variables beyond their intended scope.
Width-Range Enforcement at Missed Sites
In check_semantic_num_fits(), our system ensures numeric values are within target type ranges. However, three assignment contexts were slipping through without these checks:
- Variable reassignment (e.g.,
x = 300wherex: u8) - Array-index assignment (e.g.,
arr[0] = 300wherearr: [u8; 4]) - Method-call arguments (e.g.,
obj.method(300)expecting au8)
We addressed each of these scenarios. Six new test fixtures now verify both in-range and out-of-range values across these contexts. It's all about defensive correctness — repairs that should've been in the type system from the start.
Direction
We've consciously shifted focus from the challenging tasks ahead (like known-issues #5 I128 ABI sizing and #4 per-enum tag-to-name tables) toward more tractable correctness fixes. The semantic audit findings and parser adjustments made were straightforward but necessary. This involved four commits, 11 new test fixtures, and the closure of two known issues and two audit findings.
Currently, everything remains on the submain, with main topping at v0.3.1.
What's Next
Submain leads the main by four commits, all with clean fixture runs, setting the stage for a potential merge to main and possibly v0.3.2 in the future. Known-issue #3 (explicit return + trailing expression) becomes the natural follow-up, sitting right within the modified parser framework. Major items, like the I128 ABI and enum tag-to-name tables, will stay on the backburner for now, while we concentrate on feasible victories.
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-07-11
Top comments (0)