v0.8.0
[0.8.0] — 2026-05-20
Added
-
Native
^^XOR operator. Two adjacent carets lex as a single
^^token (the lexer pairs them only when adjacent —^ ^with a
space is still two return tokens).^^is a strictly-binary
operator lowered to LLVMxor: bitwise XOR on integer operands,
logical XOR onboperands. Float operands are a compile error
(LLVM has no floatxor). Replaces the old(a | b) - (a & b)
identity workaround.^alone remains the return operator.
Grammar (spec/grammar.ebnf) andnurlfmtupdated; regression
testsxor_op.nu+should_fail_xor_float.nu. -
inoutandsinkparameter conventions (BORROW.md Phase 4,
Option B — mutable value semantics).in/inout/sinkare
contextual keywords recognised only as a parameter's leading token
(no lexer change);inis the default.
A parameter markedinoutis an exclusive mutable borrow: the
callee mutates the caller's binding in place.inout Tlowers to a
by-address<T>*parameter — the body reads/writes the caller's
storage with no local copy — replacing the*T-parameter and
return-the-struct mutation idioms. The argument must be a mutable
(: ~) binding; aninoutfunction must be defined before it is
called. Exclusive-access check (BORROW.md Phase 5): a binding
passedinoutmust be the only argument path to its value at that
call — passing it again, as a secondinoutor a plain by-value
argument, is awarning:.
A parameter markedsinkconsumes (takes ownership of) its
argument: it lowers to an ordinary by-value parameter, and the
borrow checker records the argument binding as moved so a later
use is a use-after-move.sinkv1 applies toVecand other
manually-managed handles; passing a compiler-auto-dropped value
(owned string / slice /Dropvalue / struct with owned fields)
to asinkparameter is rejected pending drop-ownership transfer. -
Static borrow checker, on by default (BORROW.md Phases 0-3 + 6 +
8-partial). A diagnostic analysis pass (disable with
--no-borrowck) that never changes generated code — a
borrow-clean program compiles to byte-identical IR. Closes four
bug classes withwarning:diagnostics: use-after-move (a binding
read after its ownership moved), alias double-free (: T b aof an
owned heap value movesa), stack-reference escape (a closure
capturing a: ~-mutable struct by pointer that is returned,
pushed into a container, spawned onto a thread, or assigned into a
longer-lived binding — a region-based check), and iterator
invalidation (mutating a container —vec_push/vec_free/… — from
inside a~-foreach that iterates it). Ownership + borrow rules
documented in the newdocs/MEMORY.md. -
Tail-call optimisation in the @-fn dispatch path.
gen_ret
now flags the upcoming return-value expression as
tail-position;gen_callsnapshots + clears the flag on entry,
so only the outermost call in the return expression is treated
as tail (argument-evaluation recursions stay non-tail). In the
regular @-fn dispatch path the LLVMcallbecomestail call
when (a) the flag was set, (b)rlt == fn_ret_tyso LLVM
accepts the marker, (c) the callee is not variadic, and (d)
gen_retsaw no pending owned-string / owned-slice / owned-
struct-field / user-drop / defer in scope at flag-set time
(any of those would emit drop calls between the tail call and
ret, which LLVM would silently demote).Deliberately chose
tailovermusttail:tailis a hint
LLVM may drop when its safety analysis can't confirm the
rewrite (alloca-escape through an arg, etc.), so a
misclassification only costs an optimisation.musttailis
verifier-enforced and would fail on NURL's owning ABI where
the same source-level signature lowers to different LLVM
types across call sites.Effect: tail-recursive functions no longer blow the stack —
compiler/tests/tco_deep_recursion.nuruns a 5_000_000-deep
countdown in O(1) stack (~7 ms wall-clock). Trait/impl,
closure-loaded var, and fn-pointer-parameter dispatch paths
intentionally still emit a plaincall(different shapes; not
the deep-recursion targets TCO exists for).Coexists with
--gDWARF emission:tools/dwarf_test.shstill
passes all five phases. -
DWARF Phase 6 composite-type rendering. User structs and
generic-instantiation handles (%Vec__u8,%String,%FmtTok,
user% Point, …) now resolve undernurlc --gto a
!DICompositeType(tag: DW_TAG_structure_type, …)carrying one
!DIDerivedType(tag: DW_TAG_member, …)per field — instead of
the previous i64 placeholder.gdb ptype Pointlists the fields
with their NURL names + base types;print prenders the value
as{x = 3, y = 7};print p.xevaluates a single field.Field roster lives in the existing symbol table next to the
per-field__idx_N__typeentries —gen_struct_decland the
generic-instantiation emitter now also record
<sname>__field_countand<sname>__idx_N__name. New helpers
dbg_size_bits/dbg_align_bits/dbg_align_upcompute
LLVM-natural cumulative field offsets so the emitted
!DIDerivedTypemember offsets match the actual layout
clang/LLVM uses. Self-referential structs (a cell holding a
pointer to itself, etc.) are safe — the composite id is interned
ing_dbg_type_symsbefore the per-field recursion descends
throughdbg_type_id_for, so a back-edge returns the cached id
instead of looping.Regression:
compiler/tests/dwarf_struct.nuexercises the
codegen path in the standard test corpus;tools/dwarf_test.sh
picks up a fifth phase that drives gdb in batch mode to assert
ptype+print+ field-access over the new test. Bootstrap
fixed point holds — non-debug IR is byte-identical.Closes the open Phase 6 follow-up in
DWARF.md. Phase 7
(per-instantiation source-line precision for generics) remains
deferred.