Commit Graph

516 Commits

Author SHA1 Message Date
agra
b06776d6e9 library: vendors/kb_text_shape + vendors/file_utils; modules/ffi/stb_truetype.sx retired
kb_text_shape (v2.10, JimmyLefevre) had been LOST from the sx tree —
ffi/stb_truetype.sx referenced repo paths that no longer existed (and
nothing runs glyph_cache, so the dangling unit never fired). The
trimmed copy returns from the m3te project as a proper vendor:
curated c/kbts_api.h decls over the full upstream header, README with
provenance, and examples/1627 pinning context + font creation so the
unit compiles and runs in-suite. file_utils (in-house asset-read
helper with the Android AAssetManager hook) gets the same unit shape.

modules/ffi/stb_truetype.sx is gone: glyph_cache imports the three
vendored units (stb_truetype, kb_text_shape, file_utils) directly.
2026-06-12 17:58:23 +03:00
agra
58af806b7a library: vendors/stb_image + vendors/stb_truetype — stb ships with sx
The stb headers move from the repo-root vendors/ (resolvable only
with CWD = sx repo) into library/vendors/ following the sqlite
convention — bindings module + c/ sources + provenance README — so
'#import "vendors/stb_image/stb_image.sx"' (image v2.30 + image_write
v1.16) and '#import "vendors/stb_truetype/stb_truetype.sx"' (v1.26)
work from any consumer via the stdlib search paths. modules/ffi/stb.sx
dissolves into the stb_image vendor; modules/ffi/stb_truetype.sx keeps
its non-stb text-shaping companions and re-imports the vendored unit.
examples/1625 pins a deterministic in-memory BMP decode; examples/1626
pins font init + metric invariants against the system Helvetica.
2026-06-12 17:50:21 +03:00
agra
2097772ec9 library: vendors/sqlite — SQLite ships with sx
The vendored amalgamation (3.53.2, public domain) plus the curated
bindings move from the distribution repo into the sx library:
'#import "vendors/sqlite/sqlite.sx"' gives any sx program SQLite
with no system dependency and no build flags — the bindings declare
the C as a named #import c unit (pinned defines + -O2), compiled
through the object cache and shadow-proof via unit-first resolution.
examples/1624 pins the version and a typed round trip in-suite.
2026-06-12 17:39:26 +03:00
agra
e9b500a232 fix(C4): collectCImportSources recurses into nested namespaces
A named #import c unit declared inside an aliased module sits two
namespace levels deep in the merged tree; the one-level walk (the
extractLibraries/0130 pattern in c_import form) never collected it,
so the unit silently never compiled and its symbols resolved from
whatever process image carried the same names — surfaced by C4's
sqlite migration, where only the version pin could tell the OS copy
from the vendored one.
2026-06-12 17:16:03 +03:00
agra
44f4aab51c test(C3.3): #define/#flags reach a unit consumed only by hand-curated #foreign decls 2026-06-12 17:09:07 +03:00
agra
1bad29e72e feat(C3.1): #foreign refs are validated — must name a #library or a named #import c unit
validateForeignRefs walks the merged tree (libraries + named c units,
nested namespaces included) and diagnoses any #foreign whose ref names
neither — a typo'd ref previously compiled and resolved silently
through whatever image carried the symbol. Decls synthesized from
#include headers carry no ref and are exempt. Flips the C0.2b pin;
zero collateral across the 608 other examples.
2026-06-12 17:09:07 +03:00
agra
0bd8f3e5ce feat(C2): unit-first JIT symbol resolution — program-owned dylibs beat process images
runJITFromObject now takes priority dylibs (the #import c unit's
linked objects first, then #library deps in declaration order) and
attaches a per-path search generator for each AHEAD of the
process-wide fallback, so a vendored symbol can never lose to a
same-named export of an image the host process happens to carry
(libz via LLVM, libsqlite3 via CoreServices). loadLibrary reports
the name dlopen succeeded on; the c-import handle records its dylib
path; temp link inputs are per-pid so concurrent runs can't clobber
each other. Flips the C0.3 shadowing pin to from_unit: true.
2026-06-12 16:56:35 +03:00
agra
1009181638 test(C0.3): pin JIT shadowing — a #source unit's zlibCompileFlags loses to the process-loaded OS libz (xfail for C2.1) 2026-06-12 16:38:02 +03:00
agra
22455c706b test(C0.2): pin curated-bindings shape — #source-only unit + hand #foreign works (JIT+AOT), but the ref is decorative (xfail for C3.1) 2026-06-12 16:38:02 +03:00
agra
2808d5df82 test(C0.1): pin named #import c block — name namespaces decls, #define/#flags reach the unit's compile 2026-06-12 16:38:02 +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
1d17b0abcf lang: introduce cstring — the C-boundary string (Odin model)
cstring is ONE pointer to a null-terminated u8 buffer, C's char*: thin
(8 bytes, no length; cstring_len walks to the terminator), crossing
#foreign boundaries verbatim in both directions, with ?cstring as the
nullable case lowering to the same bare pointer (null = absent).

Conversion discipline mirrors Odin: a string LITERAL coerces implicitly
(its bytes are terminated constants); any other string is rejected with
a diagnostic naming to_cstring (it may be an unterminated view); and
cstring never coerces to string implicitly — from_cstring(c) is the
explicit zero-copy view, pricing the strlen.

Plumbing: TypeId/TypeInfo builtin slot 18 (first_user 19), name
classifiers, size/align/name tables, LLVM ptr lowering, the ?T pointer
niche, the xx pointer ladder, the literal-gated coercion plan
(isConstString + data_ptr), and the reserved-spelling set. std gains
cstring_len/from_cstring/to_cstring (fmt.sx, re-exported); the old
cstring(size) allocator helper is renamed alloc_string everywhere;
getenv migrates to (name: cstring) -> ?cstring as the canonical user
and env() drops its manual strlen/memcpy.

Pinned: examples/1222 (FFI both directions, literal coercion,
?cstring null paths, round trip) and examples/1173 (both coercion
diagnostics); FAIL pre-feature. The alloc_string rename + getenv
signature shift the .ir snapshots — regenerated. zig build test
426/426; run_examples 604/604.

Spec: reserved spelling + cstring section + C-interop rows.
2026-06-12 14:50:53 +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
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
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
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
5b18048abb ir: AST-callee param typing pins the callee module's source context
The two not-yet-lowered fn_ast_map paths in resolveCallParamTypes (the
qualified `ns.f(...)` call and the plain free-fn call) resolved each
param type in the CALL SITE's visibility context, so a namespaced
import's param type that is bare-visible only in its own module
diagnosed "type 'X' is not visible" at calls whose caller never names
the type bare. Route both through the E4 source pin
(resolveParamTypeInSource), as the method paths already do.

A generic callee's bare T leaves are not nominal names in that module:
astCalleeParamTypes installs the call's inferred $T -> concrete
bindings (the one binding builder) before resolving, or the pin turns
the unbound leaf into "unknown type 'T'" (regressed examples/0129
through math/scalar.sx's clamp).

Regression: examples/0840 (namespaced fn with a module-bare param
type; failed "not visible" pre-pin).
2026-06-12 07:41:18 +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
b3b78e25c0 std: organise the facade — thematic groups, aligned columns
Same 47 re-exports, regrouped: compiler-resolved types, type-system/
reflection builtins, output + libc escape hatch, formatting (print/
format up front, the *_to_string family together), string ops +
allocation helpers, fmt internals marked as legacy-surface, List, tail.
Zero output-stream diffs; 37 .ir snapshots re-pinned (renumbering from
decl reordering). Gates: suite 588/588, zig build test 0, m3te 23/23,
game builds + bundles.
2026-06-11 21:46:03 +03:00
agra
c75cd9c63c std: drop the redundant flat mem.sx import from the facade
The flat #import of mem.sx predated the namespace tail — the tail's
mem :: #import already puts mem.sx in the program graph, which is all
the ufcs helpers (context.allocator.create/alloc/free/clone) and the
CAllocator default-context machinery need; std.sx itself references no
mem name. Probe-verified the full mem surface + all gates: suite
588/588, zig build test 0, m3te 23/23, game builds + bundles. The
double import was also duplicating lowered IR — the 37 re-pinned .ir
snapshots net ~2.5k lines smaller; output streams byte-identical.
2026-06-11 21:25:32 +03:00
agra
49a36bb492 std: the prelude becomes a pure re-export facade — implementations move to std/core.sx, std/fmt.sx, std/list.sx
std.sx now contains only alias declarations (the re-export mechanism:
own decls carry one flat-import level) over three part-files: core.sx
(builtins, libc escape hatch, Source_Location/Allocator/Context/Into,
the reserved `string` decl — which needs and permits no alias), fmt.sx
(print/format/any_to_string/string ops/cstring/alloc_slice), list.sx
(List). The namespace tail is unchanged; the part-file namespaces
(core/fmt/list) carry alongside it. Consumer surface is byte-identical
— every bare prelude name resolves through the aliases (0120/0121
machinery). 37 .ir snapshots re-pinned: pure string-constant
renumbering from the changed import graph (digit-normalized diff is
empty). Gates: zig build test 426/426, suite 588/588, m3te 23/23,
game SxChess builds + bundles.
2026-06-11 19:25:49 +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
51194a26d8 mem: BufAlloc.init returns the state by value — full buffer usable, no header carve 2026-06-11 17:31:20 +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
88bae3c9f5 mem: rename Allocator primitives to alloc_bytes/dealloc_bytes (Phase 4 naming pulled forward, Agra-approved) 2026-06-11 15:33:35 +03:00
agra
ca5bd52262 lang: reject unbindable $T-only generic returns at declaration (audit follow-up) 2026-06-11 15:00:52 +03:00
agra
40805e08ec lang: inline for element form over packs — multi-iterable parity 2026-06-11 14:42:59 +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
679653fda8 lang: struct consts migrate to const globals, inline fallback (PLAN-CONST-AGG step 4)
A struct constant whose every field serializes — literals, enum tags,
nested aggregates, and (new) const EXPRESSIONS over named consts /
const-aggregate leaves ('r = K + 1', 'g = LIT.r', 'b = A[1]') — becomes
an immutable global: one storage, reads load/GEP it, '@LIT' is
addressable, dead-global elimination drops unused ones. constExprValue
gained a fold-through tail (evalConstIntExpr/evalConstFloatExpr,
source-aware), which also enables const-expression ELEMENTS in array
consts.

A const with a NON-serializable field (a call, a runtime read) keeps
inline re-lowering, and that per-use evaluation is now the documented
contract for the class (pinned: 'CALL.r' reads 1 then 2, side effects
run per use; '#run' is the evaluate-once tool).

Examples: 0180 (migrated shapes + @ptr + copy independence),
0181 (the inline-fallback contract). m3te (23/23) + game rebuilt green.
2026-06-11 12:58:17 +03:00
agra
c23b76c7d6 lang: const-aggregate comptime folds (PLAN-CONST-AGG step 3)
An array const's '.len' and 'K[<const idx>]' element reads, and a
struct const's field ('LIT.r'), fold as compile-time integer leaves —
usable in array dimensions and other constants' initializers. All
source-aware (the SELECTED author's elements, folded in the author's
context with the cyclic-definition frame); a const out-of-range index
diagnoses at fold time, never wraps.

- evalConstIntExpr gains the three ctx hooks (lookupConstAggLen /
  lookupConstArrayElem / lookupConstStructField) + an index_expr arm;
  all five ctx implementations extended (stateless tiers fold null).
- Array consts dual-register in module_const_map (value = the literal
  node) so the folders see elements; bare reads still hit the GLOBAL
  arm first, so no double emission.
- Untyped consts whose RHS is a const-aggregate leaf ('L :: K.len',
  'E :: K[1]', 'R :: LIT.r') register in a pass 2b AFTER aggregates,
  gated on the receiver naming a const aggregate — a namespaced member
  ('F :: m.PI_ISH') is never mis-typed by the count placeholder.

Examples: 0179 (folds in dims + const exprs), 1163 (OOB diagnostic).
2026-06-11 12:44:43 +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
8908e78943 lang: array-typed '::' consts as immutable globals (PLAN-CONST-AGG step 1)
K : [4]s64 : .[...] and the untyped A :: .[1, 2, 3] register as
is_const globals: one storage, reads GEP it, dead-global elimination
drops unused ones, source-aware reads come free via selectGlobalAuthor.

- registerConstArrayGlobal (scanDecls pass 2): typed via the annotation
  (array-ness + dimension/count checked), untyped via element-type
  unification — all ints s64; ANY float promotes the element type to
  f64 with ints converting exactly; bool/string homogeneous; a
  non-numeric mix or non-inferable element asks for an annotation.
- constExprValue converts int elements into float destinations exactly
  (the int+float promotion rule, element-wise).
- emitGlobals marks is_const globals LLVMSetGlobalConstant — also flips
  the comptime-backed #run globals and __sx_default_context to
  'constant' (37 pinned IR snapshots regenerated; runtime unchanged).
- Element shapes: nested arrays, struct elements, strings, bools.
  Non-constant elements / dim mismatch / mixed types diagnose loudly.

Examples: 0177 (feature matrix incl. @K reads through *[4]s64 — needs
the 0117 fix), 1159/1160/1161 (diagnostics), 0837 repointed to values.
2026-06-11 12:26:26 +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
cfe5804288 examples: un-rot 1606-platform-metal-clear
ShaderHandle lives in modules/gpu/types.sx; bare-type visibility is
non-transitive (0763), so the example imports it directly. Builds for
ios-sim again.
2026-06-11 10:51:01 +03:00
agra
330c3aeef7 std: full namespace tail — fs/process/socket/json/cli/hash/test
With 0115's own-wins globals landed, the remaining tail modules join
std.sx: every '#import "modules/std.sx"' now carries mem/xml/log/fs/
process/socket/json/cli/hash/test as namespaces (trace stays a direct
import).

Enablers in the same change:
- emit: dead-global elimination — a plain-data global no instruction
  references is not emitted, so tail modules' data (hash's 64-entry K
  table, OS/ARCH/POINTER_SIZE) stays out of binaries that don't use it.
  Comptime-backed globals keep their #run evaluation. 37 pinned IR
  snapshots regenerated (dead globals dropped + string renumbering from
  the larger module).
- 1055/1056 stop pinning the global error-tag ordinal (it shifts with
  program composition); they assert nonzero + tag identity + name.
- specs/readme/CLAUDE.md tail docs updated.
2026-06-11 10:49:39 +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
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
698f75d79a lang: reject dir-vs-file ambiguous #import
An extensionless import path that names a directory next to a same-named
.sx file ('modules/std' with both modules/std.sx and modules/std/ present)
no longer silently resolves to the directory — it errors and asks for the
explicit .sx spelling. Exemption: a file importing its own companion
directory (X.sx importing X/, the multi-file test layout) stays legal —
the sibling file is the importer itself, so the directory is the only
sensible target.
2026-06-11 08:37:36 +03:00
agra
12bf61a9fc std: restructure step 3 — ffi/ moves, build.sx, math dir spelling, fixtures
- objc.sx, objc_block.sx (from std/) + sdl3/opengl/raylib/stb/stb_truetype/
  wasm vendor bindings (from modules/ root) -> modules/ffi/
- std/uikit.sx deleted: platform/uikit.sx already declares UIApplicationMain
  and imports objc; '#framework "UIKit"' cannot live in a file imported on
  macOS targets (unconditional link directive, UIKit is iOS-only), so the
  three iOS-only examples carry the 3-line glue inline. 1607/1608/1616 also
  un-rotted (dead ns_string -> 'xx "..."' Into conversions, callconv(.c)
  msgSend fn-ptrs) — all three build for ios-sim/ios again.
- math/math.sx -> math/scalar.sx; one spelling '#import "modules/math"'
  everywhere (4 pinned IR snapshots regenerated: dir import adds Vec2/Mat4
  to the type tables).
- compiler.sx -> build.sx (imports, CLAUDE.md bundling table, specs.md).
- testpkg/ + test_c.sx -> tests/fixtures/ (resolve CWD-relative from repo
  root, same as vendors/).
- library-internal imports use full modules/... paths (std.sx tail,
  platform/bundle.sx, fixtures).
2026-06-11 08:37:22 +03:00
agra
59f0aa7716 std: restructure — std/ modules, namespace tail, std/xml.sx
allocators/fs/process/socket/log/trace/test move under modules/std/
(allocators.sx becomes std/mem.sx; the Allocator protocol moves into
the std.sx prelude, impls stay in mem.sx). New std/xml.sx holds
xml_escape as xml.escape. std.sx gains the carried namespace tail —
flat-importing std.sx now also provides mem./xml./log. — with the
remaining modules (fs/process/socket/json/cli/hash/test) deferred from
the tail until the global last-wins maps are fully own-wins (pulling
them into every closure collides bare names corpus-wide; they stay
direct imports: modules/std/fs.sx etc.). log.sx's internal emit
renamed log_emit (it clobbered consumer fns named emit program-wide).
bundle.sx uses xml.escape via the carried alias. Consumer import paths
swept mechanically; .ir snapshots recaptured for the larger std
closure. m3te + game build unchanged.
2026-06-11 06:10:59 +03:00
agra
ee00db849c lang: qualified namespace members in value position + alias carry
Two coupled capabilities on the road to the std restructure
(current/PLAN-STDLIB.md, issue 0114):

1. alias.Type.method() / alias.Type as a call head, alias.CONST, and
   alias.Enum.variant now resolve — previously only alias.fn() and
   type-position alias.Type worked. objectIsValue treats an
   alias-rooted field_access as a type head; the call path strips the
   alias to the existing Type.method machinery; lowerFieldAccess
   resolves alias.CONST pinned to the target module and alias.Enum.x
   as a typed enum literal; resolveTypeWithBindings resolves qualified
   type_exprs pinned to the target.

2. The carry rule: namespaceAliasTarget resolves an alias from the
   file's own edges first, then from DIRECT flat imports (one level),
   diagnosing two distinct carried targets as ambiguous. All qualified
   shapes work through a carried alias — the std.sx namespace tail
   (mem.GPA.init() etc.) is now expressible.

Regression: examples/0831-modules-namespace-alias-carry.sx (direct +
carried, all seven shapes).
2026-06-11 05:52:10 +03:00
agra
83ec2536af lang: catch/onfail error bindings take parens
try foo() catch (e) { }   // legal
try foo() catch e { }     // parse error with a migration hint

Same capture style as the for-loop. All four catch shapes keep working
with the parenthesized binding — block, bare-expression body, and the
== match sugar — and the no-binding forms are unchanged. onfail follows
the same rule (onfail (e) { }); its expression-cleanup form is
disambiguated by the paren-group-before-brace lookahead, so
onfail (f()); stays an expression cleanup.

AST unchanged; the printer renders the parens; the #run escape help
text updated. Corpus migrated (57 catch + 3 onfail bindings, in-source
parser test strings, specs incl. grammar rules, readme untouched —
no catch examples there).

Regression: examples/1157-diagnostics-catch-binding-needs-parens.sx;
re-captured stderr for 1010/1013/1037/1123 (migrated source echoed in
carets + help text).
2026-06-10 23:05:02 +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
fea5617e4e lang: slice ranges take the same bound markers as for-header ranges
xs[1..=3] (end inclusive), xs[0<..<4] (both exclusive), xs[..=2]
(prefix form with markers, implicit 0 start), xs[2<..] (open end,
exclusive start), and xs[..] (whole collection) — lowered as lo+1 /
hi+1 on the existing subslice op. Strings slice through the same path.
An explicit end marker requires an end expression, matching the
for-header rule.

Regression: examples/0052-basic-slice-range-bounds.sx.
2026-06-10 22:12:45 +03:00