167 Commits

Author SHA1 Message Date
agra
2a954ceeb6 fix(0138): diagnose @scalar-const address-of (no storage)
A scalar `::` constant folds to its value and has no storage. The
unary `.address_of` lowering (src/ir/lower/expr.zig) skipped the
alloca path (is_alloca == false) and resolveGlobalRef (scalar consts
get no storage global), falling through to the generic addr_of arm,
which reinterpreted the folded value as a pointer:
`inttoptr (i64 <value> to ptr)`. That wild pointer segfaulted on
deref and emitted invalid stores for inline-asm `-> @const`.

Diagnose instead, in the address_of(identifier) path: a non-alloca,
non-ref-capture, non-pack-elem scope binding (local scalar const) and
a module_const_map name not backed by storage (module scalar const)
both report "cannot take the address of constant '<name>' — a scalar
'::' constant has no storage …" and return a placeholder Ref. Chose
diagnose over materializing read-only storage (consistent with the
fold-only scalar model). Array/struct consts keep real storage and
stay addressable (@K/@LIT unchanged).

Also gives the ASM stream's planned output-to-const rejection for
free — asm `-> @const` lowers through the same path. Regression:
examples/1177-diagnostics-addr-of-const-rejected.sx. Resolves 0138.
2026-06-16 06:29:36 +03:00
agra
c760b92548 issue(0138): @const address-of yields wild pointer; ASM output-to-const BLOCKED
Filed issues/0138: `@const` (address-of a `::` comptime constant) lowers
to `inttoptr (i64 <value> to ptr)` — segfaults on deref, invalid store for
asm `-> @const`. Root cause in src/ir/lower/expr.zig .address_of (not asm).
Marked CHECKPOINT-ASM Next step BLOCKED on 0138 for the output-to-const
rejection item.
2026-06-15 23:18:37 +03:00
agra
f8e029d719 feat(asm): Phase A.1 — parse asm { … } into AsmExpr; loud lowering bail
`asm volatile? { "tmpl", [name]? "constraint" (-> Type | = expr), …,
clobbers(.…) }` now parses into a flat-operand AsmExpr/AsmOperand (ast.zig +
parser.zig parseAsmExpr, dispatched from parsePrimary on .kw_asm). `volatile`
and `clobbers` are recognized contextually (not reserved). `-> @place`
write-through is rejected with a clear "Phase 2" parse error.

Codegen is not implemented yet (IR op + LLVM emit are Phases C–E), so lowering
bails LOUD + named via an explicit .asm_expr arm in lower/expr.zig (not the
generic unknown_expr else) — emitPlaceholder makes hasErrors() abort the build
on the message.

The new asm_expr tag forced (and got) arms in three exhaustive Node.Data
switches: sema.zig analyzeNode + findNodeAtOffset, semantic_diagnostics.zig
checkBindingNames — each recurses into template + operand payloads.

Design: adopted the operand auto-naming rule (design §II.5) — name auto-derived
from a {reg} pin, explicit [name] only when it differs or for register-class
operands, echo form rejected. Typing-stage rule; parser stores name: ?[]const u8.

Locked with examples/1640-platform-asm-parse.sx (multi-output divmod: named
operands, register pins, clobbers — parses then bails, called from main).

Also files issue 0137 (pre-existing, orthogonal: `sx run` with no `main`
segfaults via an unguarded JIT entry lookup in target.zig — not an asm bug).

zig build test green (648 corpus, 445 unit).
2026-06-15 20:21:25 +03:00
agra
b9cfe2554f refactor(ffi-linkage): Phase 9.3/9.4 — purge 'foreign' from issues/*.md; GATE PASS
Rewrote 20 issue writeups to the extern/runtime-class vocabulary (#foreign→extern,
foreign_class_map→runtime_class_map, parseForeignClassDecl→parseRuntimeClassDecl,
findForeignMethodInChain→findRuntimeMethodInChain, dedupeForeignSymbol→
dedupeExternSymbol, is_foreign_c_api→is_extern_c_api, stale filename refs to the
renamed examples, foreign-class→runtime-class, bare foreign→extern). Renamed
issues/0043-…-foreign-class-…→…-runtime-class-….

PHASE 9 COMPLETE — 9.4 GATE PASSES: zero 'foreign' across src/library/examples/
issues/docs/editors/specs/readme/CLAUDE, excluding only the SQLite API constant
SQLITE_CONSTRAINT_FOREIGNKEY + vendored sqlite3.c/.h (upstream third-party).
Suite green (644 corpus / 443 unit, 0 failed).
2026-06-15 11:18:35 +03:00
agra
b52d424369 refactor(ffi-linkage): Phase 9.3 — rename *-foreign* example files → extern/runtime names
git-mv the 10 foreign-named example families to extern/runtime-class names + update
every #import/#include/#source ref, stale comment ref, and the 1172 stderr snapshot
(path + 'extern symbol' message). Renames: 0729…-foreign→…-extern, 1172-diagnostics-
foreign-symbol-conflict→…-extern-symbol-conflict, 1205/1207 ffi-foreign-global→
ffi-extern-global, 1216/1217 ffi-…-foreign-(in-method|result-chain)→…-extern-…,
1219-ffi-foreign→1219-ffi-extern, 1306 objc-foreign-class-chained→objc-runtime-class-
chained, 1318 objc-property-foreign→objc-property-extern-class. DEDUP: deleted
1218-ffi-foreign-cvariadic (identical to 1229-ffi-extern-cvariadic; updated 1229's
twin ref) + the orphaned 1620 dir. Also purged editors/vscode tmLanguage (#foreign
dropped from the directive highlighter) + 1220.h/issues-0030.sx comment refs. Suite
green (644 corpus / 443 unit, 0 failed).
2026-06-15 11:14:35 +03:00
agra
e386a0d0b4 fix: reject direct assignment to a tagged-union variant member
A tagged union (enum-with-payload) is laid out { tag, payload }, but a
direct member write `s.rect = payload` lowered to a payload-only store
(union_gep into field 1) with no tag store — the discriminant went stale,
so a later match/== took the wrong arm with no diagnostic (issue 0136).
The read path already distinguishes tagged unions (enum_payload/enum_tag);
the write path treated them like plain unions.

A variant is set via construction (`s = .variant(payload)`, which writes
both tag and payload). A direct member write can't safely set the tag (the
active variant isn't known at the write site), so it is now rejected with a
diagnostic pointing to construction. A new diagTaggedUnionVariantWrite guard
— reusing the shared fieldLvalueResolve matcher, applied at both store sites
(lowerAssignment, lowerMultiAssign) — fires only for a whole-variant write
on a tagged union. Plain `union` writes and nested sub-field writes
(`s.rect.w = ...`) are unaffected.

Resolves issue 0136. Tests: examples/0185 (rejected), 0186 (nested write +
construction still work). specs.md / readme.md updated.
2026-06-13 21:18:40 +03:00
agra
4d32a4d4fb fix: propagate union-member type to a struct-literal RHS
Assigning a struct literal to a named-struct member of a plain union
(`u.b = .{ ... }`) lowered the RHS as .unresolved and tripped the
LLVM-emission tripwire: lowerAssignment's .field_access target-type
path used getStructFields, which returns nothing for a union, so the
literal never received its target type.

Unify the lvalue field matcher into a pure fieldLvalueResolve consumed
by both fieldLvaluePtr (GEP builder) and the target-type path, so the
store slot and the RHS target type can't diverge (covers union direct +
promoted members, tuple/vector lanes, and structs).

Resolves issue 0133 (depended on 0135). Regression test: examples/0184.
Notes the now end-to-end union path in issue 0132.
2026-06-13 18:55:41 +03:00
agra
8c47268539 fix: xx pack[i] to a protocol target heap-copies the element
Erasing a single comptime-pack element to a protocol value
(`xx sources[0]` with a protocol target) tripped the pack-as-value
error: buildProtocolErasure treated the index_expr as an lvalue and
took its address via lowerExprAsPtr, whose .index_expr arm lowers the
bare pack as a value (a pack is comptime-only with no runtime storage).

isLvalueExpr now reports a comptime pack index as an rvalue, decided
via the same packArgNodeAt predicate the value path uses — so the value
and lvalue paths can't diverge on what counts as a pack element — and
erasure heap-copies the already-materialized element instead.

Resolves issue 0135. Regression tests: examples/0547, 0548.
2026-06-13 18:55:10 +03:00
agra
d3f5cb20cb fix: visibility-aware type resolution for protocol method signatures
`registerProtocolDecl` resolved each method's param/return type NAME
through the flat, visibility-unaware `type_bridge.resolveAstType`, so a
type name colliding across modules bound to the wrong author. In the
repro the user's `Event` enum collides with the stdlib `event.Event`
struct (pulled in by `modules/std.sx`): the protocol grabbed the stdlib
struct, typed an inferred `g_plat.one_event()` as a fieldless struct,
bound the `case .key_up:(e)` payload to `.unresolved`, and emitted
"enum literal '.escape' has no destination type to resolve against".

Resolve both param and return types through
`resolveTypeInSource(pd.source_file, …)` — the visibility-aware resolver
pinned to the protocol's own declaring module, keeping the `Self → *void`
short-circuit. Brings the non-parameterized path to parity with
`instantiateParamProtocol` and concrete-fn signatures. No silent default:
not-visible / ambiguous names still diagnose and poison with `.unresolved`.

Closes issue 0132 — the protocol-return case left open by f13f4ab (which
fixed the enum/union/inline/error-set registration class). Regression
test: examples/0417-protocols-protocol-return-name-collision.sx.
2026-06-13 15:44:11 +03:00
agra
45befed698 docs(issues): correct 0132 root cause; file 0133 and 0134
- 0132: rewrite to the verified root cause -- protocol method signature
  registration resolves type names via flat findByName and picks the wrong
  same-name author. Original payload-field hypothesis kept as superseded;
  repro switched to canonical `impl ... for` syntax. Still open (the
  protocol path is unchanged).
- 0133: assigning a struct literal to a union member panics ("unresolved
  type reached LLVM emission"); pre-existing, surfaced while testing.
- 0134: a same-name `error` set collapses into a namespaced import's set --
  error-set declarations lack per-decl nominal identity (E6a gap); this is
  what keeps the 0132-class error-ref resolution dormant.
2026-06-13 13:41:30 +03:00
agra
fe4f059a52 issue 0131: RESOLVED (d7808f6) 2026-06-12 21:52:11 +03:00
agra
ff94b004c4 issue 0131: protocol method calls silently drop extra arguments 2026-06-12 21:44:07 +03:00
agra
d739c5bf11 fix(0130): #library/#framework collection recurses into nested namespaces
extractLibraries/extractFrameworks walked the merged root plus exactly
one namespace_decl level, so a #library reached through two or more
aliased imports never made it to the AOT link line or the JIT dlopen
list. Both walks now recurse over namespace_decl children.

Regression: examples/1617-modules-library-nested-namespace.sx binds
libpcap (not in the compiler's loaded images, so the JIT cannot mask
the miss via RTLD_DEFAULT) behind two aliased imports.
2026-06-12 15:59:36 +03:00
agra
d88bdd7242 fix(0128): foreign cstring returns + conflicting same-symbol bindings
Two genuine defects behind the 0128 filing (whose original repros were
both poisoned by binding getenv, which std already declares -> *u8):

1. Re-declaring a C symbol was silent first-wins: every call through
   the later declaration was typed by the older signature. Foreign
   registration now dedupes — equal signatures share one FuncId,
   conflicting ones are diagnosed.

2. Foreign -> string / -> ?string returns read garbage: C returns one
   char*, but the LLVM signature declared the fat {ptr,i64} (len =
   register garbage), and ?string was mis-declared SRET (the hidden
   out-pointer landed in the callee's first arg register). cstrRetKind
   now classifies such returns, declares them as plain ptr (never
   sret), and the call site synthesizes {ptr, strlen} via a
   branch-guarded strlen (NULL -> {null,0} / optional null), wrapping
   {string, i1} for ?string.

?[:0]u8 itself resolves fine (it is ?string); the spelling works in
return, param, local, and alias positions.

Regression: examples/1221 (plain + optional non-null + NULL paths) and
examples/1172 (conflict diagnostic); both FAIL pre-fix. The extern
dedupe collapses duplicate libc decls, so affected .ir snapshots were
regenerated. zig build test 426/426; run_examples 602/602;
distribution suite 21/21.
2026-06-12 14:13:01 +03:00
agra
a8fbded567 fix(0129): logical not is truthiness-aware, not a bit flip
The unary .not arm emitted bool_not (LLVM bitwise Not) for every
operand. Correct on i1; on an error binding — an error-set value, u32
tag at the LLVM level — a bitwise not of a nonzero tag stays nonzero,
so 'if !e' held even on a SET error and its branch read the
uninitialized success value (real segfault in the distribution repo's
sqlite tests). Plain integers had the same hole ('!7' was '~7').

Now: bool keeps bool_not; integers and error-set operands lower as the
truthiness complement (cmp_eq against a typed zero); anything else is
diagnosed instead of silently bit-flipped.

Regression: examples/1057 (set error: !e must not hold; success: !e
holds with a real value; integer truthiness) + examples/1171 (!"text"
diagnosed); both FAIL pre-fix. zig build test 426/426;
tests/run_examples.sh 600/600.
2026-06-12 13:36:54 +03:00
agra
ba37d0b393 issues: file 0128 — [:0]u8 FFI returns silently u8; ?[:0]u8 unresolved panic
[:0]u8 aliases string (fat) and params already ABI-thin to char*, but
a foreign -> [:0]u8 return silently resolves to plain u8, and ?[:0]u8
never resolves at all (LLVM emission panic) even though ?string works.
Design contract recorded: ?[:0]u8 lowers to a nullable char* at the
boundary, length synthesized on the sx side; until then such returns
must be diagnosed, not mis-typed.
2026-06-12 13:21:19 +03:00
agra
1bc60d3a35 fix(0098): enum literal resolves against the unwrapped optional child; non-enum targets are diagnosed
lowerEnumLiteral resolved the variant against the raw destination type,
so any non-enum destination fell into resolveVariantValue's silent
return-0 tail with the enum_init stamped as the wrong type:

  - ?E destinations produced variant 0 mis-typed as the optional
    (observed as variant 0 OR null, layout-dependent);
  - builtin destinations (i64) silently became 0;
  - unknown variants of real enums silently became variant 0;
  - a destination-less literal panicked LLVM emission (unresolved
    type reached codegen).

Now: optional destinations unwrap to the child enum (the coercion
layer's .optional_wrap handles E -> ?E), and the remaining shapes are
diagnosed — unknown variant (with the variant list, via the new
emitBadEnumVariant twin of emitBadVariant), non-enum destination, and
no destination (cascade-guarded: silent when the destination's type
already failed to resolve and was reported).

Regression tests: examples/0183 (return/assign/reassign into ?Enum,
non-zero variants, null path) + examples/1169/1170 (each diagnostic);
all three FAIL on pre-fix master. zig build test 426/426;
tests/run_examples.sh 598/598.
2026-06-12 12:35:20 +03:00
agra
d8076b9333 lang: rename signed integer types sN -> iN
Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.

Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).

Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.

zig build test: 426/426; examples suite: 595/595.
2026-06-12 09:31:53 +03:00
agra
515ecebea7 fix(0127): namespaced generic calls type their result from the call's bindings
The plan producer's namespace-fn arms returned the declared return type
without checking type_params, so a qualified generic call's result
carried the unbound T stub: print boxed it as 'T{}', and a non-s64
binding failed LLVM verification (pack monomorphized for the stub,
call returning double). Both fn_ast_map-backed arms now classify
generic callees as generic_fn and infer the return through
inferGenericReturnType, mirroring the bare-identifier path.
2026-06-12 08:53:25 +03:00
agra
70363dda56 issues: file 0127 — namespaced generic call's result mis-types as the unbound T stub 2026-06-12 08:40:45 +03:00
agra
309f48e1b5 fix(0126): array args bind []$T generic params; uninferrable type params diagnose at the call
extractTypeParam's slice arm only extracted from slice-typed args, so
first(a) with a : [3]s64 at first :: (xs: []$T) -> T left T unbound
and the mono body reached LLVM emission carrying the .unresolved
sentinel (panic). The arm now also extracts from array args via the
array's element type — mirroring the array→slice promotion concrete
slice params already perform; the existing arg coercion handles the
rest.

lowerGenericCall additionally diagnoses any still-uninferrable TYPE
param at the call site instead of monomorphizing unbound — the
deliberate string-at-[]$T gap used to hit the same sentinel panic and
now errors with a source-located message. Comptime value params
($N: u32) and ..$Ts packs bind through their own dispatch and stay
exempt.

Regressions: examples/0212-generics-array-arg-slice-param.sx (scalar /
u8 / struct elements + the slice spelling) and
examples/1168-diagnostics-generic-param-uninferrable.sx (string arg
diagnostic) — both failed pre-fix.
2026-06-12 08:31:45 +03:00
agra
b625b74046 issues: file 0126 — array arg at a []$T param leaves T unbound, panics LLVM emission 2026-06-12 08:21:42 +03:00
agra
837b5d375f fix(0124): large stack arrays lower to in-place access, not first-class values
Two lowering sites materialized a local array as a whole LLVM value;
the legalizer scalarizes each such op into one SelectionDAG node per
element, and at ~64K elements the DAG combiner segfaults
(DAGCombiner::visitMERGE_VALUES → ReplaceAllUsesWith).

- lowerVarDecl: an array-typed `---` initializer emits NO store — the
  slot stays uninitialized instead of receiving a whole-array undef
  store. The tuple zero-init carve-out stays; non-array `---` keeps
  the undef store. The interp is unchanged either way (slots start
  .undef).
- lowerIndexExpr: element reads on an array with addressable storage
  GEP the storage and load one element — the general-expression
  sibling of 0110's lowerFor fix — without value-lowering the object
  (a dead whole-array load would still reach the DAG). Storage-less
  arrays keep the index_get fallback.

Sibling shape filed as 0125: any_to_string's per-array-type arms still
pass the array by value, so a 64K+ array type + any {} print crashes.

Regression: examples/0055-basic-large-stack-array.sx (sx build
segfaulted pre-fix). 22 .ir snapshots re-pinned: removed undef stores
and ig.tmp spills, in-place gep+load (instruction-shape-only churn,
reviewed).
2026-06-12 08:19:20 +03:00
agra
7c7bb2076a issues: file 0125 — any_to_string's array arms materialize every interned array type by value 2026-06-12 08:18:32 +03:00
agra
47110b37cf issues: file 0124 — 64K+ stack arrays emit whole-aggregate ops that segfault LLVM 2026-06-12 07:55:46 +03:00
agra
7f2b8b5cde fix(0123): wrong arg counts to fixed-arity fns error at the call site
checkCallArity compares the supplied count against the declared params
(min = params without trailing defaults, max = params.len, unbounded
past a variadic) at the five plain dispatch sites in lowerCall — bare
selected-author + lazy, namespace alias-gate + qualified, struct
method, ufcs. Pack / comptime / generic / #compiler / #builtin callees
keep their own dispatch. The method/ufcs sites also gain the
appendDefaultArgs fill the generic-instance leg already had, so
trailing defaults work on dot-calls instead of emitting under-arity
calls. lowerStmt's local fn_decl arm now registers a pointer into the
AST node in fn_ast_map, not a stack temporary that aliased every later
local fn.
2026-06-12 01:42:59 +03:00
agra
7d1e23ecc6 issues: file 0123 — wrong arg counts to fixed-arity fns reach LLVM emission 2026-06-12 00:40:00 +03:00
agra
340be402a5 ir: whole-program passes pin the source context per decl (fix 0122)
convergeClosureShapeSets, checkErrorFlow, and the unknown-type loop ran
under whatever current_source_file the previous phase left behind —
closure-literal annotations resolved (and reject/unknown-type
diagnostics rendered) against an arbitrary module. Latent while std.sx
was a single file (the ambient happened to be the main file); the
re-export facade restructure exposed it. Each walk now pins
setCurrentSourceFile per decl / per fn (body.source_file is already
stamped by resolveImports). Coverage: examples 0129/1047/1049/1052/
1053/1056 against the facade std.sx. Gates: zbt 426/426, suite 588/588.
2026-06-11 19:24:46 +03:00
agra
721369a711 lang: fn aliases dispatch like their target (fix 0121) — scan-time registration through the shared alias-chain walk
Renamed fn aliases failed for EVERY kind (the filed pack-only scope was
a same-name confound: same-name re-exports already resolved through the
name-keyed fn_ast_map). scanDecls now follows ident-/ns.X-RHS const
alias chains (aliasedFnDecl; 0120's hop walk extracted as
followAliasChain) and registers the alias name in fn_ast_map
(absent-only), so every dispatch path — early pack/comptime/generic,
plain lazy-lower, plan-side typing — sees the target decl unchanged.
my_print :: s.print; / my_format :: s.format; now work (the std.sx
re-export shape). Regression: examples/0546 (+rich). Gates: zig build
test 0, suite 588/588.
2026-06-11 18:47:16 +03:00
agra
f2db8ecc53 lang: generic struct head aliases bind the template (fix 0120) — alias-follow from each author's source in head selection; loud unknown-type on the .call type tail
BoxAlias :: Box; / Box :: r.Box; now resolve instantiation, methods,
annotations, and chains through the aliased template, and re-export one
flat-import level as ordinary own decls (the facade shape the std.sx
restructure needs). selectGenericStructHead consults aliasedStructTemplate
(nominal.zig) before the global template map — own-wins/single-flat alias
author, each hop pinned to the alias author's source, ns.X RHS through
namespaceAliasVerdictFrom, depth-capped. resolveTypeCallWithBindings'
silent .unresolved tail (panicked in LLVM emission) now diagnoses
"unknown type". Also aligns the stale pre-existing calls.test.zig UFCS
plan test with the opt-in model (a47ea14). Regression: examples/0211
(+rich/+facade). Gates: zig build test 426/426, suite 587/587.
2026-06-11 18:09:01 +03:00
agra
a47ea1416e lang: opt-in UFCS — ufcs-marked fns + alias dot-dispatch, generic binding via receiver; one binding builder for plan-side generic returns 2026-06-11 17:04:51 +03:00
agra
84e0fb0752 mem: typed allocation helpers + drop bare malloc/free (Phase 2.2); resolve 0119 as |>-contract clarification 2026-06-11 16:17:39 +03:00
agra
3e10809d7e issues: file 0119 — UFCS generic free functions unresolved 2026-06-11 15:46:49 +03:00
agra
03dc10bba3 fix(0118): cast accepts compound type args; compound type literals are first-class Type values 2026-06-11 14:09:22 +03:00
agra
c229f697bd docs: const-aggregate semantics + unchecked-pointer contract (PLAN-CONST-AGG step 6) 2026-06-11 13:54:35 +03:00
agra
40a94c4734 issues: file 0118 — cast(<compound type>) unresolved 2026-06-11 13:47:38 +03:00
agra
7f3bd69bd9 lang: reject writes through constants (PLAN-CONST-AGG step 2, fixes 0116)
Any assignment / compound-assignment whose target chain is ROOTED at a
constant — a const-flagged global (array consts, #run consts) or a
module value const (struct consts incl.) — diagnoses 'cannot assign
through constant X' at compile time. A struct const's field write used
to compile and bus-error at runtime (issue 0116); scalars misfired
silently. A deref along the chain (p.*) breaks the root — pointer
writes stay the documented escape until the const-ness steps; a local
shadowing the const name stays writable.

Also: typed struct constants ('W : Color : Color.{...}') register —
the shape list skipped struct_literal, leaving the typed form
unresolved while the untyped one worked.

Examples: 1162 (all rejection shapes incl. the 0116 crash repro),
0178 (typed struct const reads + copy independence).
2026-06-11 12:33:34 +03:00
agra
82d6b8da0e fix(0117): pointer-to-array indexing auto-derefs
A '*[N]T' receiver in an index expression reached LLVM emission with an
unresolved element type and tripped the panic sentinel — no read or
write spelling worked. ptrToArrayElem on Lowering recognises the shape;
the index READ path GEPs the pointee array through the pointer value
and loads the element; the write / compound-assign / lvalue /
addr-of-element paths and the expression typer resolve the element type
through the same helper (their GEP machinery already handled a pointer
base). Kept out of getElementType so slice paths don't half-accept a
raw pointer base.

Regression: examples/0176 (read, write, compound, element ptr + deref).
2026-06-11 12:15:45 +03:00
agra
57979ed8e6 issues: file 0117 — indexing through *[N]T panics at LLVM emission
Pre-existing (plain locals repro it); found pinning @K reads for
PLAN-CONST-AGG step 1, which is now blocked on it. No deref spelling
works: p[2] hits the unresolved-type tripwire, (*p)[2] doesn't parse.
2026-06-11 12:02:03 +03:00
agra
4f1a9738c7 plan: aggregate consts (PLAN-CONST-AGG) + file 0116 const-write hole
Array-typed '::' consts as immutable globals (approved design: storage
global + untyped inference + comptime-fold layer + const-write rejection
covering the pre-existing struct-const write crash, issue 0116).
2026-06-11 11:18:43 +03:00
agra
0b13498e25 fix(0115): source-aware global selection — own-wins for module globals
The globals registry (global_names) was last-wins across modules with no
per-importer gate: any module's bare K could read/write/type against an
unrelated module's same-named global (hash.sx's K table hijacked every
user K once std's namespace tail pulled hash into the program), and an
own const of an unsupported shape borrowed another module's const and
panicked at the unresolved-type tripwire.

- var_decl joins RawDeclRef: module globals are selectable raw authors.
- selectGlobalAuthor (the globals analogue of F2's selectModuleConst):
  own author wins, one flat-visible author resolves, >=2 distinct flat
  authors diagnose loudly, authored-but-not-visible diagnoses, and a
  compiler-synthesized global (no raw author) emits untracked. A var_decl
  author whose per-source registration was deduped at flat-merge (two
  modules declaring the same extern symbol) serves the symbol's
  registration.
- All bare-identifier global sites route through it: value read, addr-of,
  assignment (store + compound), lvalue address, fn-ptr call, call param
  typing, and expression type inference.
- selectModuleConst gains .own_opaque: an own const author with no
  materialized per-source value (e.g. an array '::' const) blocks
  borrowing another module's same-named const — the read diagnoses
  cleanly instead of panicking.
- The fn-as-VALUE arm admits raw-facts-only authors: an own fn whose name
  a flat-merge collision dropped from the global decl list (first-wins)
  now resolves via author selection for func_ref/closure/Any shapes too.

Regressions: examples 0835 (own const vs flat array global), 0836 (main
const vs namespaced array global, incl. inference), 0837 (own array
const never borrows cross-module — clean unresolved).
2026-06-11 10:47:30 +03:00
agra
37bea63302 issues: file 0115 — same-name consts of different shapes collide across modules
Scalar K vs array K in two modules: minimal repro panics (unresolved-type
LLVM tripwire), the std-tail topology silently clobbers (0786 family reads
hash.sx's SHA table as its own K). Blocks the PLAN-STDLIB full-tail
follow-up; co-blockers (eager global emission, 0601 comptime-meta,
error-int shifts) noted in the issue.
2026-06-11 09:24:11 +03:00
agra
fbbfcb268c fix(0114): gate alias-qualified calls to one-level carry, pin to target
The lowerCall namespace branch routed alias.fn() through the global
qualified registration (first-wins) at any import depth, and through the
global last-wins bare map for comptime/generic members. Plain-identifier
alias roots now resolve via the carry-aware namespaceAliasVerdict:

- visible alias (own edge or ONE flat hop): the member dispatches the
  TARGET module's own fn (namespaceFnMember + fd-keyed bareAuthorFuncId),
  so two modules' same-named aliases each call their own target.
- two direct flat imports carrying the alias to distinct targets:
  loud ambiguity diagnostic.
- alias only reachable beyond one hop: "namespace 'X' is not visible".
- foreign / builtin / #compiler members keep the literal-symbol path.

Regressions: examples 0832 (two-hop), 0833 (carried collision),
0834 (own-target pin / first-wins repair).
2026-06-11 09:16:03 +03:00
agra
2025bb361b issues: file 0114 — namespace aliases leak transitively, collide first-wins
Found while probing the alias-carry design for the stdlib restructure
(plan in current/PLAN-STDLIB.md): qualified members register globally
with no per-importer gate, so an alias is usable any number of flat
hops away, and same-name registrations silently first-win. The carry
rule's one-level + ambiguity semantics fix both; repro and fix shape
in the issue.
2026-06-11 05:36:22 +03:00
agra
12149eb548 fix(0113): negated-literal global initializers fold as constants
globalInitValue had no unary_op arm, so g : s64 = -1; fell into the
catch-all 'must be initialized by a compile-time constant' even though
constExprValue already folds negate(literal) for the module-const
identifier route. The new arm routes through constExprValue and applies
the direct-literal rules to the folded value: checkIntLiteralFits on
ints (g : s8 = -300 gets the range diagnostic), and a negated float at
an integer global narrows only when integral (-4.0 folds to -4, -4.5
errors). Binary-op initializers keep the specific non-constant
diagnostic.

Regression: examples/0175-types-negative-literal-global.sx.
2026-06-10 22:39:52 +03:00
agra
67313e1dad fix(0112): out-of-range int literals error instead of silently wrapping
checkIntLiteralFits range-checks a literal against its integer target
(builtins + custom widths via intLiteralRange; width-64 types skip —
every representable literal is a legal bit pattern there) and diagnoses
with the type's range and an xx/cast hint. Wired into the .int_literal
arm (covers decls, assignments, call args, struct-literal fields),
lowerStructConstant, and globalInitValue.

A negated literal now folds to a single constant so -128 range-checks
as -128 rather than as an out-of-range +128 intermediate. An explicit
xx operand skips the check — truncation stays available on request
(cast(T) was already exempt: its value arg lowers without the target).

examples/0300-closures-lambda.sx pinned 133 wrapping to -3 through an
s3 param — the exact class this outlaws; updated to a fitting value.

Found during the fix and filed separately: issue 0113 (negated-literal
global initializers rejected as non-constant; pre-existing).

Regressions: examples/1156-diagnostics-int-literal-out-of-range.sx,
examples/0174-types-int-literal-boundaries.sx.
2026-06-10 22:28:24 +03:00
agra
3cc34d54c1 fix(0108): break/continue run the loop body's pending defers
lowerBreak/lowerContinue emitted a bare br, and the enclosing block's
emitBlockDefers — seeing the terminator — discarded the pending entries
on the assumption a return had already drained them. The breaking
iteration's defers were silently skipped, leaking whatever the cleanup
released.

Lowering.loop_defer_base records the defer-stack height at each loop's
body start (while / for / range-for, saved and restored alongside
break_target); break/continue drain non-onfail entries down to it in
LIFO order via the non-truncating emitLoopExitDefers before branching.
Truncation stays with the lexical block exits — the same entries still
belong to the fall-through path after the branch containing the break.
break/continue outside a loop now diagnose instead of no-op'ing.

Regression: examples/0049-basic-defer-break-continue.sx (for and while,
break and continue, nested-block LIFO drain).
2026-06-10 17:43:58 +03:00
agra
bf47146085 fix(0110): for-over-array by-value fetch reads one element, not a full copy
lowerFor's by-value element fetch emitted index_get on the array VALUE;
the emitter realizes that as a whole-array spill to a stack temp + GEP,
per iteration — O(N^2) bytes copied per loop (and pre-0109 it also grew
the stack per iteration, segfaulting a [4096]s64 loop).

When the iterable is an array with addressable storage (and not deref'd
from a pointer, whose identifier alloca holds the pointer rather than
the array), the fetch is now index_gep on the storage + one element
load. Storage-less arrays keep the index_get fallback. The loaded
element remains a copy — mutating the capture does not write back.

Regression: examples/0048-basic-for-array-large.sx (sum over 4096
elements + by-value copy-guard).
2026-06-10 17:34:35 +03:00
agra
878c4226a6 fix(0109): hoist all per-instruction allocas to the function entry block
An alloca built at its use site re-executes on every pass through that
block, and LLVM reclaims allocas only at ret — so loop-body locals,
nested-loop index slots, and emitter spill temps (ig.tmp, sret slots, ABI
coercion temps, byval materialization) grew the stack per iteration and
long loops segfaulted on stack exhaustion.

New LLVMEmitter.buildEntryAlloca inserts after existing entry-block
allocas and restores the builder position; every LLVMBuildAlloca site
reachable during instruction emission now routes through it.
Initialization stores stay at the use site (per-iteration re-init is
unchanged), and entry slots become mem2reg-promotable. The 35 .ir
snapshot diffs are pure alloca position moves (type multisets verified
identical per file).

Regression: examples/0047-basic-loop-local-stack-reuse.sx (segfaulted
pre-fix on both the 1M-iteration body-local loop and the 3M-iteration
nested loop).
2026-06-10 17:27:11 +03:00
agra
e81780e32e fix(0111): unannotated decl literals no longer adopt the fn return type
lowerVarDecl (unannotated) and lowerDestructureDecl now clear target_type
around the initializer lowering: a declaration without annotation provides
no target, so int/float literals take their spec defaults (s64/f64) instead
of the enclosing function's implicit-return type (x := 0 in a -> s8 fn was
s8; big := 3000000000 in -> s32 silently wrapped to -1294967296).

Regression: examples/0173-types-int-literal-default-s64.sx. The remaining
explicit-annotation wrap (x : s8 = 300) is filed as issue 0112.
2026-06-10 17:21:44 +03:00