Compare commits

...

45 Commits

Author SHA1 Message Date
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
0e890b9992 checkpoint: MEM record-keeping — facade completion, phase ground truth, 2.2 state
Brings the MEM checkpoint up to the 2026-06-11 sessions: the std.sx
pure-re-export facade arc (49a36bb/c75cd9c + issues 0120-0122), the
allocator primitive rename (88bae3c), opt-in UFCS (a47ea14), Phase 2.2
typed helpers (84e0fb0), BufAlloc by-value init (51194a2); next step
Phase 2.3 diagnostic wrappers. Older 2026-05-25-era records fold into
collapsed details blocks.
2026-06-12 07:41:48 +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
7d1e23ecc6 issues: file 0123 — wrong arg counts to fixed-arity fns reach LLVM emission 2026-06-12 00:40:00 +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
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
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
3e10809d7e issues: file 0119 — UFCS generic free functions unresolved 2026-06-11 15:46:49 +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
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
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
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
a2bd5b0438 plan: expand PLAN-CONST-AGG scope — struct-const migration, full pointer const-ness, const decay 2026-06-11 11:40:08 +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
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
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
22552075d5 imports: retire the is_pub sketch on NamespaceTarget
Every namespace alias is module surface under the carry rule — the
planned pub-import front-end form is superseded; no per-edge visibility
flag is needed.
2026-06-11 08:49:17 +03:00
agra
b8748aee16 docs: alias carry semantics + stdlib layout (step 4)
- specs §9: Namespace Alias Carry section (one level, own-wins, ambiguity,
  no chaining — 0114 noted for the still-ungated bare-call path), the
  three-tier import resolution (file dir -> cwd -> stdlib search path /
  SX_STDLIB_PATH), a Standard Library Layout section, real-layout examples
  replacing the stale modules/std/std.sx ones.
- readme: carry-rule teaser with the std namespace-tail example (verified
  to compile and run as written).
- CLAUDE.md: file-roles rows for std.sx/std//ffi//math//build.sx,
  tests/fixtures, and the PLAN-STDLIB tracker.
2026-06-11 08:46:17 +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
1207 changed files with 147117 additions and 43912 deletions

View File

@@ -16,7 +16,7 @@ Procedure:
- **Symptom** — one-line summary + observed vs expected.
- **Reproduction** — minimal sx code (inline fenced block). Must
reproduce the bug standalone, no project dependencies beyond
`modules/std.sx` / `modules/allocators.sx`.
`modules/std.sx` / `modules/std/mem.sx`.
- **Investigation prompt** — a ready-to-paste prompt the user can
drop into a fresh session to fix the bug. Should include: the
suspected area of the compiler (file + function), what the
@@ -73,9 +73,9 @@ There is no "wrap up first" option.
a lookup fails in the compiler. Examples of the pattern to root out:
```zig
// NEVER write this — lookup fails, return s64 and pretend nothing
// NEVER write this — lookup fails, return i64 and pretend nothing
// happened. Any caller asking `what type is this?` gets a lie.
return self.module.types.findByName(name_id) orelse .s64;
return self.module.types.findByName(name_id) orelse .i64;
// NEVER write this — same shape, dressed up:
return scope.lookup(name) orelse default_type;
@@ -86,9 +86,9 @@ These defaults silently produce wrong results in cases the implementer
didn't think of. The classic failure mode: the default coincidentally
matches the size/shape of one common case, so the test suite passes
and the bug ships invisibly. issue-0042 lived for years because
`resolveTypeArg`'s `orelse .s64` returned 8 bytes for unresolved
`resolveTypeArg`'s `orelse .i64` returned 8 bytes for unresolved
type-alias names — coincidentally correct for any 8-byte target
(`s64`, `*T`, `f64`, function pointers), and silently wrong for
(`i64`, `*T`, `f64`, function pointers), and silently wrong for
everything else.
**Required:** when a lookup that *must* succeed fails, emit a
@@ -102,7 +102,7 @@ as a silently-corrupted size or alignment.
`void` is a real, heavily-checked type (void returns, void params, "no
value" markers), and pervasive `if (ty == .void) { skip / return-nothing }`
checks would silently swallow the failure — trading one silent default
(`.s64`) for another (`.void`) one layer down. The same objection rules
(`.i64`) for another (`.void`) one layer down. The same objection rules
out `noreturn` (diverging expressions) and any other load-bearing builtin.
Instead, add a **distinct** `.unresolved`-style `TypeId` whose sole meaning
is "resolution failed". A dedicated value (1) can't be mistaken for a real
@@ -311,7 +311,7 @@ LongLived :: struct {
}
// Direct allocs too:
grow_buf :: (self: *LongLived, n: s64) {
grow_buf :: (self: *LongLived, n: i64) {
self.buf = self.own_allocator.alloc(n);
}
}
@@ -541,9 +541,9 @@ foreign calls.
| File | Role |
|------|------|
| [library/modules/platform/bundle.sx](library/modules/platform/bundle.sx) | All four targets (macOS, iOS sim, iOS device, Android). Branches on `BuildOptions.is_macos / is_ios / is_ios_device / is_ios_simulator / is_android` accessors. |
| [library/modules/fs.sx](library/modules/fs.sx) | POSIX file stdlib (open / read / write / copy / mkdir / unlink / chmod / rename / exists / basename / dirname). |
| [library/modules/process.sx](library/modules/process.sx) | popen-based `run(cmd) -> ?ProcessResult` + `env(name)` + `find_executable(name)`. |
| [library/modules/compiler.sx](library/modules/compiler.sx) | `BuildOptions` setters + accessors. Adding a new bundling parameter = add a setter here + a hook in compiler_hooks.zig. |
| [library/modules/std/fs.sx](library/modules/std/fs.sx) | POSIX file stdlib (open / read / write / copy / mkdir / unlink / chmod / rename / exists / basename / dirname). |
| [library/modules/std/process.sx](library/modules/std/process.sx) | popen-based `run(cmd) -> ?ProcessResult` + `env(name)` + `find_executable(name)`. |
| [library/modules/build.sx](library/modules/build.sx) | `BuildOptions` setters + accessors. Adding a new bundling parameter = add a setter here + a hook in compiler_hooks.zig. |
| [library/modules/platform/android.sx](library/modules/platform/android.sx) | `AndroidPlatform` (state-on-struct, no module globals). `sx_android_*` helpers take `plat: *AndroidPlatform` as first arg. `logical_w` field drives `dpi_scale = pixel_w / logical_w` so consumer's design-width fits any physical resolution. |
| [src/ir/compiler_hooks.zig](src/ir/compiler_hooks.zig) | `BuildConfig` + every `BuildOptions.*` hook. Hook registry is in `Registry.registerDefaults`. |
| [src/ir/host_ffi.zig](src/ir/host_ffi.zig) | `dlsym(RTLD_DEFAULT)` + arity-switched cdecl trampolines. Lets `#foreign("c")` decls resolve at `#run` / post-link time against host libc. |
@@ -554,7 +554,7 @@ spec — what runs per Apple target vs Android, what each accessor
returns, the BuildConfig forwarded from main.zig — lives there.
Wiring a new bundling step:
1. Add the parameter as a setter on `BuildOptions :: struct #compiler { ... }` in [library/modules/compiler.sx](library/modules/compiler.sx).
1. Add the parameter as a setter on `BuildOptions :: struct #compiler { ... }` in [library/modules/build.sx](library/modules/build.sx).
2. Add the `BuildConfig` field + setter hook + accessor hook in [src/ir/compiler_hooks.zig](src/ir/compiler_hooks.zig). Register both in `Registry.registerDefaults`.
3. Optionally forward a CLI flag in [src/main.zig](src/main.zig) before the post-link invocation.
4. Read the accessor from [library/modules/platform/bundle.sx](library/modules/platform/bundle.sx).
@@ -572,8 +572,16 @@ Wiring a new bundling step:
| `current/CHECKPOINT-LANG.md` | **Active** LANG progress tracker. Update after every step. |
| `current/PLAN-ERR.md` | **Active** ERR implementation plan (`!` errors, `try` / `catch` / `or` / `onfail`, return traces). |
| `current/CHECKPOINT-ERR.md` | **Active** ERR progress tracker. Update after every step. |
| `current/PLAN-STDLIB.md` | STDLIB restructure plan — **COMPLETE** (alias carry rule + std/ffi/math layout + full namespace tail). |
| `current/PLAN-CONST-AGG.md` | **Active** aggregate-consts + const-ness plan (array/struct `::` consts as immutable globals, const-write rejection, comptime folds, `*const`/`[]const` with full propagation, const decay/slicing). Progress tracked in its `## Status` section — no separate checkpoint file. |
| `implementation_plan.md` | Archive of completed work (closures, protocols, etc.). Do not pick up tasks from here. |
| `readme.md` | User-facing language overview — **maintained**. Update it whenever a user-facing sx change lands (new/changed syntax, semantics, gating diagnostics, language behavior), per the docs-track-changes rule. |
| `CLAUDE.md` | This file. Session instructions. |
| `library/modules/std.sx` | The prelude FACADE — pure re-exports (alias decls) over the part-files `std/core.sx` (builtins, libc escape hatch, Context/Allocator/Into/Source_Location/string), `std/fmt.sx` (print/format/*_to_string/string ops), `std/list.sx` (List) + the namespace tail (`mem`/`xml`/`log`/`fs`/`process`/`socket`/`json`/`cli`/`hash`/`test` carried to flat importers). No implementations live here. |
| `library/modules/std/` | Stdlib modules: core, fmt, list (the prelude part-files — consumers reach them through std.sx, not directly), mem (allocators), fs, process, socket, json, cli, hash, xml, log, trace, test — all but trace and the part-files carried by the std.sx tail; direct file imports give bare access. |
| `library/modules/ffi/` | FFI bindings: objc, objc_block, sdl3, opengl, raylib, stb, stb_truetype, wasm. |
| `library/modules/math/` | scalar / vector2 / matrix44 — one spelling: `#import "modules/math"` (directory import). |
| `library/modules/build.sx` | `BuildOptions` compile-time build DSL. See "Bundling lives in sx" above. |
| `library/modules/platform/bundle.sx` | sx-side `.app` / `.apk` bundler. See "Bundling lives in sx" above. |
| `library/modules/fs.sx`, `library/modules/process.sx` | POSIX stdlib for the bundler + general consumer use. |
| `library/modules/std/fs.sx`, `library/modules/std/process.sx` | POSIX stdlib for the bundler + general consumer use. |
| `tests/fixtures/` | Test-only import fixtures (testpkg/, test_c.sx) — resolve CWD-relative from the repo root, not via the stdlib search path. |

View File

@@ -30,7 +30,7 @@ build_block_convert($args, $R)`.
| issue-0050 fix | `5316bf7` | Same isolation pattern as 0048 applied to `monomorphizeFunction`. |
| 5.1.A xfail | `3bd6f26` | `build_block_convert(args: []Type, $ret: Type) -> string` undefined — pin output format via `examples/176-build-block-convert.sx` across 3 void shapes + 1 non-void shape. |
| 5.1.B fix | `aeb950b` | Builder added to `library/modules/std/objc_block.sx`. Emits nested `callconv(.c)` trampoline + Block literal source. |
| 5.2.A xfail | `f5342e9` | Generic `Into(Block)` impl absent — `Closure(s64, s64) -> void` (uncovered by hand-rolled impls) emits the "no Into(Block) for cl_s64_s64__void" diagnostic per `examples/177-generic-into-block.sx`. |
| 5.2.A xfail | `f5342e9` | Generic `Into(Block)` impl absent — `Closure(i64, i64) -> void` (uncovered by hand-rolled impls) emits the "no Into(Block) for cl_i64_i64__void" diagnostic per `examples/177-generic-into-block.sx`. |
| 5.2.B fix | `165b621` | Generic impl `Closure(..$args) -> $R` added with `#insert build_block_convert($args, $R)`. `lowerExpr`'s `.comptime_pack_ref` + `resolveTypeArg` + `type_bridge.isTypeShapedAstNode` extended so impl-mono `$args` (pack_bindings) and `$R` (type_bindings) resolve in both expr and type positions. |
| 5.3 | `2eaf932` | Delete hand-rolled `__block_invoke_void` + `__block_invoke_bool` + the two per-shape impls. The generic impl covers both at runtime. |
@@ -40,11 +40,11 @@ What's now possible end-to-end (from
```sx
#import "modules/std/objc_block.sx";
main :: () -> s32 {
cl := (a: s64, b: s64) => { g_a = a; g_b = b; };
main :: () -> i32 {
cl := (a: i64, b: i64) => { g_a = a; g_b = b; };
blk : Block = xx cl; // generic impl mono'd for
// Closure(s64, s64) -> void
invoke_fn : (*Block, s64, s64) -> void callconv(.c) = xx blk.invoke;
// Closure(i64, i64) -> void
invoke_fn : (*Block, i64, i64) -> void callconv(.c) = xx blk.invoke;
invoke_fn(@blk, 10, 20);
0;
}
@@ -53,9 +53,9 @@ main :: () -> s32 {
The `xx cl : Block` site monomorphises the generic
`Into(Block) for Closure(..$args) -> $R` impl. Inside the impl
mono, `#insert build_block_convert($args, $R)` evaluates the
builder at comptime with `$args = [s64, s64]` and `$R = void`,
builder at comptime with `$args = [i64, i64]` and `$R = void`,
and substitutes the resulting source — a nested
`__invoke :: (block_self: *Block, arg0: s64, arg1: s64) -> void
`__invoke :: (block_self: *Block, arg0: i64, arg1: i64) -> void
callconv(.c) { ... }` trampoline plus the Block literal that
points its `invoke` slot at `@__invoke`. Stack-local block layout
matches Apple's published spec; UIKit / Foundation consumers can
@@ -96,7 +96,7 @@ generic Into(Block) builder body rests on.
|---|---|---|
| 4A.bare.1.A | `c792642` | Expected-failing lock-in for bare `$args` (parser rejection diff). |
| 4A.bare.1.B | `5a4a19b` | Parser makes `[` optional after `$<pack_name>`; new `ComptimePackRef` AST node + sema no-op arms + `lowerExpr` arm calling new `buildPackSliceValue(arg_types)` helper. Helper emits `alloca [N x Any]`, one `const_type(arg_tys[i])` per slot, then a `{data_ptr, len}` slice aggregate. emit_llvm's `const_type` arm relaxed to silent undef-i64 (storage of Type values in runtime aggregates is harmless; loud bail moves to USE sites). |
| 4A.bare.4.A | `95e61d8` | Expected-failing lock-in for `type_name(list[i])` silently returning "s64" via `resolveTypeArg`'s catch-all `else => .s64`. |
| 4A.bare.4.A | `95e61d8` | Expected-failing lock-in for `type_name(list[i])` silently returning "i64" via `resolveTypeArg`'s catch-all `else => .i64`. |
| 4A.bare.4.B | `d99c0fd` | `tryLowerReflectionCall` splits on new `isStaticTypeArg(node)` helper. Static args fold to const_string (today's fast path); dynamic args emit `callBuiltin(.type_name, [arg_ref])` for the interp's arm. emit_llvm's reflection-builtin arm relaxed to silent undef-i64 — same reasoning as const_type: storage-position misuse is impossible, use-site misuse caught by the interp arm's `asTypeId orelse bailDetail`. |
| 4A.bare.5 | `2162662` | End-to-end smoke `examples/172-pack-builder-smoke.sx`. `describe(..$args)` walks `$args` at #run time, calls `type_name(list[i])` per position. Four call shapes (empty, one-arg, two-arg, four-mixed) verify the full chain works. |
@@ -106,7 +106,7 @@ What now works end-to-end (from `examples/172-pack-builder-smoke.sx`):
describe :: (..$args) -> string {
list := $args;
s := "[";
i : s64 = 0;
i : i64 = 0;
while i < list.len {
if i > 0 { s = concat(s, ", "); }
s = concat(s, type_name(list[i]));
@@ -117,7 +117,7 @@ describe :: (..$args) -> string {
}
#run { print("{}\n", describe(true, 3.14, "x", 99)); }
// → [bool, f64, string, s64]
// → [bool, f64, string, i64]
```
The pack flows through a real `[]Type` slice value; the loop
@@ -130,7 +130,7 @@ Known follow-ups (not blocking step 5):
- `type_eq` / `has_impl` dynamic-arg dispatch — should follow
the same `isStaticTypeArg` split that `type_name` got in
4A.bare.4.B. Today their dynamic-arg case still silently
folds via the same `resolveTypeArg .s64` fall-through.
folds via the same `resolveTypeArg .i64` fall-through.
Wire when a real use case needs them.
- `has_impl` interp arm — still bails "not yet wired".
Needs a protocol-map snapshot on `Interpreter.init`.
@@ -160,7 +160,7 @@ helpers, source-language `$args[$i]` in expression position.
| 4.0 foundation | `ac60d98` | New `Op.const_type: TypeId` opcode (dedicated, never piggybacks on `const_int`). Interp emits `Value.type_tag(tid)`. emit_llvm bails loudly (Type is comptime-only; LLVM never sees one). `Value.asTypeId() ?TypeId` helper. `evalCmp` arm for `.type_tag, .type_tag` — TypeId equality. Mixed `.type_tag` vs `.int` falls through to `typeErrorDetail`. Zig unit tests confirm the variant. |
| 4.1 reflection arms | `9600ba5` | `BuiltinId.type_name` / `.type_eq` / `.has_impl` for the interp-time fallback when lowering can't fold the call statically. Static-arg calls keep the existing `tryLowerReflectionCall` const-emission fast path. `has_impl` interp arm bails with "not yet wired" — interp-time has_impl needs a queryable snapshot of the host's protocol maps (its own follow-up). emit_llvm bails loudly on all three (comptime-only). |
| 4.2 audit + bitcast guard | `55c72af` | `box_any`/`unbox_any` audit: layout was already correct (tag stays `.int`; value field can be `.type_tag`). `bitcast` interp arm guards against `.type_tag → <non-Any, non-identity>` casts — catches the `xx val to string` shape in `any_to_string`'s `case type:` arm that pre-dates type_tag and would silently mis-coerce. |
| 4.3 source construction | `fd03b58` | Parser accepts `$<pack>[<int_literal>]` in expression position (yields the same `pack_index_type_expr` AST node already used in type positions in step 3). Lowering: `lowerExpr` arm emits `const_type(arg_tys[index])`; `resolveTypeArg` arm reads `pack_arg_types[name][index]` directly so lower-time fold paths (`tryLowerReflectionCall`, `tryConstBoolCondition`) see the bound TypeId rather than falling through to the `.s64` silent-arm default. |
| 4.3 source construction | `fd03b58` | Parser accepts `$<pack>[<int_literal>]` in expression position (yields the same `pack_index_type_expr` AST node already used in type positions in step 3). Lowering: `lowerExpr` arm emits `const_type(arg_tys[index])`; `resolveTypeArg` arm reads `pack_arg_types[name][index]` directly so lower-time fold paths (`tryLowerReflectionCall`, `tryConstBoolCondition`) see the bound TypeId rather than falling through to the `.i64` silent-arm default. |
Audit summary — every Value-switch in interp.zig was checked
for silent fall-through. Findings:
@@ -180,11 +180,11 @@ What's now possible end-to-end (from `examples/169-pack-value-dispatch.sx`):
```sx
show :: (..$args) -> string => type_name($args[0]);
show(42) // "s64"
show(42) // "i64"
show("hi") // "string"
describe :: (..$args) -> string {
inline if type_eq($args[0], s64) { return "got s64"; }
inline if type_eq($args[0], i64) { return "got i64"; }
inline if type_eq($args[0], string) { return "got string"; }
...
}
@@ -231,29 +231,29 @@ What works:
- `x : $args[1] = args[1]` — local-var annotation.
- `fp : (*void, $args[0]) -> $args[1] = handler;` — fn-pointer
type literal (the shape step 5's generic trampoline body needs).
- `inline if type_eq($args[0], s64) { ... }` (when the `$args[0]`
- `inline if type_eq($args[0], i64) { ... }` (when the `$args[0]`
argument is in a type position — `type_eq` reads call args via
`resolveTypeArg` which routes to `resolveTypeWithBindings`).
- `has_impl(Hash, s64)` (plain protocols).
- `has_impl(Into(Block), s64)` (parameterised protocols).
- `has_impl(Hash, i64)` (plain protocols).
- `has_impl(Into(Block), i64)` (parameterised protocols).
New tests:
- `examples/165-pack-type-position.sx` — return type + local
var annotation; two heterogeneous call shapes (s64+string,
string+s64) confirm distinct monos.
var annotation; two heterogeneous call shapes (i64+string,
string+i64) confirm distinct monos.
- `examples/166-pack-type-position-three.sx` — `args[2]` (third
element) as return type across three (s64,s64,string),
(bool,f64,s64), (string,string,bool) shapes.
element) as return type across three (i64,i64,string),
(bool,f64,i64), (string,string,bool) shapes.
- `examples/167-pack-type-fnptr.sx` — fn-pointer type literal
with `$args[$i]` in both param + return positions.
- `examples/168-pack-reflection-intrinsics.sx` — type_name,
type_eq (with inline-if folding), has_impl for both plain
(Allocator/CAllocator) and parameterised (custom Wrap(s64)
for s32) protocols.
(Allocator/CAllocator) and parameterised (custom Wrap(i64)
for i32) protocols.
Out of scope (deferred):
- `$args[$i]` in EXPRESSION position (the parser only accepts
it in type positions today — `type_eq($args[0], s64)` works
it in type positions today — `type_eq($args[0], i64)` works
because the call-arg path resolves through `resolveTypeArg`,
but bare `$args[0]` as a value would need an extra parser arm).
- `$args[$i]` in struct field types.
@@ -280,7 +280,7 @@ $args[1], ...) -> $R` per-position types.
Closes the nested-comptime-call + return bug. The pack-fn face
was incidentally fixed by step 2b's mono refactor (pack-fn
calls now bypass the inline-return-slot setup that leaked into
nested comptime). The plain `($x: s32)` comptime face stayed
nested comptime). The plain `($x: i32)` comptime face stayed
on the inline path until this fix.
`createComptimeFunction` wraps a comptime expression into a
@@ -305,7 +305,7 @@ only `func` / `current_block` / `inst_counter` / `scope` /
fn-local flags the wrapper's lowering needs fresh.
`examples/issue-0046.sx` (regression test): `helper :: ($x:
s32) -> s64 { print("inside\n"); return 42; }` called from
i32) -> i64 { print("inside\n"); return 42; }` called from
`main`. Pre-fix: interp panic at storeAtRawPtr. Post-fix:
prints "inside" then "n=42".
@@ -339,7 +339,7 @@ New tests:
`log_count(items: []Any)`.
- `examples/163-pack-runtime-index.sx` — `while i < args.len
{ args[i] }` over a 4-arg pack.
- `examples/164-pack-mixed-comptime.sx` — `tagged($tag: s32,
- `examples/164-pack-mixed-comptime.sx` — `tagged($tag: i32,
..$args)` called with different `tag` values gets distinct
monos (`tagged__ct_7__pack_*`, `tagged__ct_9__pack`).
@@ -364,7 +364,7 @@ caller's basic block.
`examples/158-pack-mono-dedup.sx` confirms end-to-end:
`count(), count(1), count(2), count(1,2,3), count("x", true)`
produces `0 1 1 3 2` at runtime AND emits exactly 4 monos in
IR — the two `s64` calls share one mono.
IR — the two `i64` calls share one mono.
Plumbing in `src/ir/lower.zig`:
- `isPackFn(fd)` — true when the only comptime param is a
@@ -492,7 +492,7 @@ Out of scope:
"unresolved 'result'" because nested comptime inlining
loses the scope where stdlib's `#insert build_format`
declared `result`. Same class as the
`helper :: ($x: s32) -> s64 { print(...); return 42; }`
`helper :: ($x: i32) -> i64 { print(...); return 42; }`
pattern; pre-dates step 2. Worth filing if step 2's later
slices need it; today's typed-indexing test exercises only
field access and arithmetic, no nested print.
@@ -516,7 +516,7 @@ Root cause was broader than packs: `format`/`print` use arrow
form (`=> expr`) or `#insert`-only bodies, so no stdlib comptime
fn took the `return`-with-trailing-statements path. Step 1.b
made `..$args` parseable; the natural smoke test
`foo :: (..$args) -> s64 { return 42; }` was the first body to
`foo :: (..$args) -> i64 { return 42; }` was the first body to
hit it.
Fix in `src/ir/lower.zig`:
@@ -575,10 +575,10 @@ New plumbing in [src/ir/lower.zig](../src/ir/lower.zig):
concrete source closure during monomorphisation.
`examples/155-pack-impl-match.sx` flips from the
"no Into(Block) for cl_s32_bool__bool" lock-in diagnostic to
"no Into(Block) for cl_i32_bool__bool" lock-in diagnostic to
"pack impl match ok": one user-declared
`impl Into(Block) for Closure(..$args) -> $R` covers a
`Closure(s32, bool) -> bool` source that stdlib has no
`Closure(i32, bool) -> bool` source that stdlib has no
hand-rolled impl for. The constructed Block isn't invoked
(invoke=null) — the test exercises matching + monomorphisation,
not the trampoline (step 5 of the plan).
@@ -607,9 +607,9 @@ Replaces a hand-rolled Into impl in stdlib once step 2 + step
Pinned today's matching behaviour ahead of 1d.B. A user-declared
`impl Into(Block) for Closure(..$args) -> $R` registers under a
pack-shaped source key in `param_impl_map`; the xx site mangles
the concrete `Closure(s32, bool) -> bool` source and finds
the concrete `Closure(i32, bool) -> bool` source and finds
nothing → the existing focused diagnostic fires ("no `Into(Block)
for cl_s32_bool__bool` impl — add a per-signature
for cl_i32_bool__bool` impl — add a per-signature
`__block_invoke_<sig>` trampoline + Into impl..."). The pack impl
is reachable in the file but never considered.
@@ -691,7 +691,7 @@ current parser-rejection behavior so the next commit's parser
extension shows up as a behavior shift.
New: `examples/150-pack-parse.sx` declares
`foo :: (..$args) -> s64`. Today's parser hits `..` where it
`foo :: (..$args) -> i64`. Today's parser hits `..` where it
expects a parameter name (after parsing the leading `dollar`
sigil that doesn't appear) and emits "expected parameter name"
at column 9 of line 15. Expected output captures this rejection.
@@ -742,7 +742,7 @@ lookup. Infrastructure only; populated but not yet read.
Added stand-ins for the opaque Obj-C runtime types to
`library/modules/std/objc.sx`: `id`, `Class`, `SEL` resolve to
`*void`; `BOOL` to `s8`. All zero-cost at the LLVM layer; the
`*void`; `BOOL` to `i8`. All zero-cost at the LLVM layer; the
header's old caveat about lacking aliases is gone.
`141-objc-type-aliases.sx` exercises them against the real macOS
Obj-C runtime via `isKindOfClass`.
@@ -793,13 +793,13 @@ Four design questions still open (see roadmap).
A trailing variadic param on a `#foreign` declaration now maps to the
C calling convention's `...` instead of sx's slice-packing path. Drops
the existing per-arity shim pattern (`__log_2i :: (prio, tag, fmt, a:
s32, b: s32) -> s32 #foreign __android_log_print;`) for a single
i32, b: i32) -> i32 #foreign __android_log_print;`) for a single
declarative form:
```sx
sx_ffi_sum_ints :: (n: s32, args: ..s32) -> s64 #foreign;
sx_ffi_sum_ints :: (n: i32, args: ..i32) -> i64 #foreign;
main :: () -> s32 {
main :: () -> i32 {
print("{}\n", sx_ffi_sum_ints(3, 10, 20, 30)); // → 60
}
```
@@ -821,15 +821,15 @@ locks in the green state in one commit):
getting boxed into a typed slice.
3. **C default argument promotion**. New `promoteCVariadicArgs`
([src/ir/lower.zig](src/ir/lower.zig)) applies the standard
promotions to args past the fixed param count: `bool/s8/s16/u8/u16
s32` via sext/zext, `f32 → f64` via fpext. Wired into the two
promotions to args past the fixed param count: `bool/i8/i16/u8/u16
i32` via sext/zext, `f32 → f64` via fpext. Wired into the two
`lowerCall` paths right after `coerceCallArgs`.
`examples/ffi-foreign-cvariadic.sx` + `.c` lock the matrix end-to-end:
`sum_ints(3, 10, 20, 30) → 60`, `sum_ints(0) → 0`, `avg_doubles(2,
1.5, 2.5) → 2.0`, `avg_doubles(3, 1.0, 2.0, 3.0) → 2.0`, and a
null-terminated `count_args` chain of `*u8` strings → `3`. All four
return shapes (s64 / f64 / s32) and three element types (s32 / f64 /
return shapes (i64 / f64 / i32) and three element types (i32 / f64 /
*u8) exercise the variadic-slot ABI through the C `va_arg` machinery
in the .c helper.
@@ -887,7 +887,7 @@ plus 2 codegen fixes surfaced along the way.**
|------|-------------------------------|---------------------------------------------------------------------------------------|
| 0.0 | tests/cross_compile.sh | empty tuple list, exits 0; skip-with-warning when toolchains missing |
| 0.1 | ffi-01-primitives.sx | every primitive type round-trips through `#import c { #source / #include }` |
| 0.2 | ffi-02-small-struct.sx | Vec2 (8 B), Vec4f (16 B HFA), Pair64 (2×s64), Quad32 (4×s32) — four ABI slots |
| 0.2 | ffi-02-small-struct.sx | Vec2 (8 B), Vec4f (16 B HFA), Pair64 (2×i64), Quad32 (4×i32) — four ABI slots |
| 0.3 | ffi-03-large-struct.sx | Big24 (24 B), Big48 (48 B) via byval params + sret return |
| 0.4 | ffi-04-fp-struct.sx | FQuad (16 B HFA), DQuad (32 B HFA — UIEdgeInsets-shape) |
| 0.5 | ffi-05-string-args.sx | [:0]u8, sx `string` slice-decay, [*]u8 + len, mutate-via-C, C-returned pointer |
@@ -1110,8 +1110,8 @@ describes is implemented in [src/ir/lower.zig](../src/ir/lower.zig)
Apple's runtime DSL encoding table:
- `v` = void, `i` = s32, `q` = s64, `f` = f32, `d` = f64, `B` = bool,
- `c` = s8/BOOL, `C` = u8, `s` = s16, `S` = u16, `l/L` = long,
- `v` = void, `i` = i32, `q` = i64, `f` = f32, `d` = f64, `B` = bool,
- `c` = i8/BOOL, `C` = u8, `s` = i16, `S` = u16, `l/L` = long,
`Q` = u64, `*` = `[*]u8`,
- `@` = id (object), `#` = Class, `:` = SEL, `^v` = `*void`.
- Struct: `{Name=field0field1...}`, nested + cycle-broken.
@@ -1159,7 +1159,7 @@ landing the parallel JNI codegen.
| 1.29 | `uikit_create_gl_context` — `alloc` / `initWithAPI:` / `setCurrentContext:` + duplicate of 1.27's screen-scale read | done |
| 1.30 | `uikit_subscribe_keyboard_notifications` — first standalone 4-keyword selector exercise (`addObserver:selector:name:object:`) | done |
| 1.31 | `uikit_scene_will_connect_ios` — biggest cluster; the iOS scene-lifecycle entry. UIWindow / UIViewController / SxGLView wiring; EAGL drawable-properties dict build; `nativeScale` + `setContentScaleFactor:` DPI path; `displayLinkWithTarget:selector:` + run-loop install. Exercises every return shape used in uikit.sx. Net -44 lines (104 → 60). | done (b3558c3) |
| 1.32 | `uikit_keyboard_will_change_frame` — `userInfo` / `objectForKey:` / `CGRectValue` / `doubleValue` / `unsignedLongValue` / `screen.bounds`. First standalone exercise of `#objc_call(CGRect)` (HFA, structurally equivalent to UIEdgeInsets) and `#objc_call(u64)` (LLVM-equivalent to s64). Net -14 lines. Runtime-verified by the locked-in test `examples/ffi-objc-call-12-rect-u64-returns.sx` (ac78490). | done (e1d300c) |
| 1.32 | `uikit_keyboard_will_change_frame` — `userInfo` / `objectForKey:` / `CGRectValue` / `doubleValue` / `unsignedLongValue` / `screen.bounds`. First standalone exercise of `#objc_call(CGRect)` (HFA, structurally equivalent to UIEdgeInsets) and `#objc_call(u64)` (LLVM-equivalent to i64). Net -14 lines. Runtime-verified by the locked-in test `examples/ffi-objc-call-12-rect-u64-returns.sx` (ac78490). | done (e1d300c) |
| 1.33 | **uikit.sx sweep — all remaining dispatch sites.** `renderbufferStorage:fromDrawable:` (bool, GL setup); `presentRenderbuffer:` (bool, every frame); `targetTimestamp` / `duration` (f64, every frame in `uikit_gl_view_tick`); `bounds` (CGRect, `uikit_compute_layer_pixel_size`); `locationInView:` (CGPoint HFA, every touch); `anyObject` (*void, every touch). First standalone `#objc_call(CGPoint)` exercise. Net -15 lines. Runtime-verified end-to-end: tapped a black pawn in iOS-sim chess and the move played correctly (1...d5, 2...d4). | done |
Verification per cluster: zig build / zig test / run_examples /
@@ -1178,9 +1178,9 @@ the work that remains is lowering + emit_llvm.
| 1.15 | `#jni_call(void)` codegen — new `.jni_msg_send` IR opcode + emit_llvm expansion: load `*env` for the vtable, GEP into slots 31 (GetObjectClass), 33 (GetMethodID), 61 (CallVoidMethod). No method-ID caching yet; static dispatch + non-void returns drop to `LLVMGetUndef` until 1.18+. | done (134c197 xfail + 9afcaa5 fix) |
| 1.16 | Lock in pre-caching IR shape — two `#jni_call` sites with literal `("noop", "()V")` emit two independent `GetMethodID` calls. IR snapshot at `tests/expected/ffi-jni-call-03-methodid-sharing.ir`. | done (13018ef) |
| 1.17 | Literal-keyed slot interning — `JniMsgSend.cache_key: ?CacheKey` carries the literal `(name, sig)` pair from `lower.zig`; `emit_llvm.getOrCreateJniSlots` interns `@SX_JNI_CLS_<key>` and `@SX_JNI_MID_<key>` globals per unique pair; per-call lowering does null-check + lazy populate via `GetObjectClass` → `NewGlobalRef` (slot 21) → `GetMethodID` on miss. Two literal sites now share one slot pair. | done (0d883b4) |
| 1.18 | `#jni_call(s32)` → CallIntMethod (vtable slot 49). One arm added to the `call_method_offset` switch; reuses the 1.17 cache. | done (1d7ea72 xfail + ebcfe4c fix) |
| 1.18 | `#jni_call(i32)` → CallIntMethod (vtable slot 49). One arm added to the `call_method_offset` switch; reuses the 1.17 cache. | done (1d7ea72 xfail + ebcfe4c fix) |
| 1.18+ | Lift JNI vtable offsets into a `const Jni` named-constants struct. Pre-loaded Object/Boolean/Long/Float/Double slots so 1.191.22 are one-line switch arms. | done (c1877fc) |
| 1.19 | `#jni_call(s64)` → CallLongMethod (vtable slot 52). One arm added. | done (da5b635 xfail + 5945a8c fix) |
| 1.19 | `#jni_call(i64)` → CallLongMethod (vtable slot 52). One arm added. | done (da5b635 xfail + 5945a8c fix) |
| 1.20 | `#jni_call(f64)` → CallDoubleMethod (vtable slot 58). First non-integer JNI return. | done (xfail + ca4ba75 fix) |
| 1.21 | `#jni_call(bool)` → CallBooleanMethod (vtable slot 37). | done (xfail + b0e8659 fix) |
| 1.22 | `#jni_call(*void)` → CallObjectMethod (vtable slot 34). Pointer-return detected via `TypeInfo.pointer | .many_pointer` ahead of the primitive switch. LocalRef cleanup deferred — chess consumes objects inline. | done (xfail + b5694cc fix) |
@@ -1200,7 +1200,7 @@ All ten sub-steps (1.151.24) shipped. `#jni_call(T)` and
`#jni_static_call(T)` lower to JNI vtable indirection with shared
`(name, sig)` literal-keyed slot interning (one `jclass GlobalRef` +
one `jmethodID` per unique pair, populated lazily on the first
matching call). Return-type matrix covers `void` / `s32` / `s64` /
matching call). Return-type matrix covers `void` / `i32` / `i64` /
`f64` / `bool` / `*T`. Static dispatch skips `GetObjectClass` and
uses the parallel `GetStaticMethodID` + `CallStatic<Type>Method`
family. Both OS gates verified by `cross_compile.sh` (3/3 tuples
@@ -1284,14 +1284,14 @@ alias; no lowering yet.
| # | What | Status |
|-----|---|---|
| 2.8 | `src/ir/jni_descriptor.zig` + `.test.zig`. `writeType` appends one JNI descriptor for an sx type AST node; `deriveMethod` returns the full `(args)ret` descriptor for a `ForeignMethodDecl`, skipping the implicit `self` on instance methods. `Context.enclosing_path` resolves `*Self` to its `L<path>;` form. Primitive table-driven (void→V, bool→Z, s8/u8→B, s16→S, u16→C, s32→I, s64→J, f32→F, f64→D); arrays `[]T`/`[*]T`/`[N]T` → `[<elem>`. Cross-class `*Foo` → explicit error (lands in 2.9). 10 unit tests pass. **Cadence note**: landed as single commit since internal compiler functions don't have a sx-level snapshot surface yet — the rule re-applies at 2.11 where call-site lowering becomes end-to-end observable. | done (21c4906) |
| 2.8 | `src/ir/jni_descriptor.zig` + `.test.zig`. `writeType` appends one JNI descriptor for an sx type AST node; `deriveMethod` returns the full `(args)ret` descriptor for a `ForeignMethodDecl`, skipping the implicit `self` on instance methods. `Context.enclosing_path` resolves `*Self` to its `L<path>;` form. Primitive table-driven (void→V, bool→Z, i8/u8→B, i16→S, u16→C, i32→I, i64→J, f32→F, f64→D); arrays `[]T`/`[*]T`/`[N]T` → `[<elem>`. Cross-class `*Foo` → explicit error (lands in 2.9). 10 unit tests pass. **Cadence note**: landed as single commit since internal compiler functions don't have a sx-level snapshot surface yet — the rule re-applies at 2.11 where call-site lowering becomes end-to-end observable. | done (21c4906) |
| 2.9 | Cross-class `*Foo` resolves via `Context.classes: ?*const ClassRegistry` (a `StringHashMap` of sx alias → foreign path). `*Self` and `*Foo` share one code path. Retired `CrossClassRefNotYetSupported` in favour of `UnknownClassAlias`, which fires for both "no registry provided" and "alias not in registry". | done (5188265) |
| 2.10 | `deriveMethod` short-circuits to the `jni_descriptor_override` (2.6 escape-hatch) when present, returning the override verbatim through an `allocator.dupe`. Bypasses normal derivation entirely — including resolution failures, which lets users escape `UnknownClassAlias` errors for synthetic-method cases. | done (ca840ff) |
## Phase 2B complete (signature derivation)
`src/ir/jni_descriptor.zig` handles every shape the parser can hand it:
- Primitive types: `void/bool/s8..s64/u8/u16/f32/f64` → JNI single-char.
- Primitive types: `void/bool/i8..i64/u8/u16/f32/f64` → JNI single-char.
- Arrays / slices / many-pointers: `[<elem>` (recursive).
- `*Self` → `L<enclosing_path>;`.
- `*Foo` → looks up Foo's foreign path in the supplied registry.
@@ -1431,8 +1431,8 @@ When sx grows the cross-target story far enough:
forwarding, R.1R.5 retiring the legacy NativeActivity surface — all
landed; chess on Pixel runs end-to-end as the integration witness).
JNI return + parameter type validation lives in lowering with source-
spanned diagnostics; Call<T>Method coverage spans bool / s8 / s16 /
u16 / s32 / s64 / f32 / f64 / pointer; varargs promotion is wired.
spanned diagnostics; Call<T>Method coverage spans bool / i8 / i16 /
u16 / i32 / i64 / f32 / f64 / pointer; varargs promotion is wired.
Phase 3 step 3.0 landed (for real this time): `inst.method(args)` on
an `#objc_class` / `#objc_protocol` receiver derives the selector via
@@ -1534,7 +1534,7 @@ type-check.
turned out to be a red herring: the actual root cause was that
`inferExprType` for a chained call `Cls.static().instance(...)` never
looked the inner call's foreign-class declaration up, so the outer
dispatch saw a `.s64` receiver, the `foreign_class_map.get(...)` lookup
dispatch saw a `.i64` receiver, the `foreign_class_map.get(...)` lookup
missed, and lowering emitted `error: unresolved 'method'`. The macOS
target appeared to work because `inline if OS == .ios { ... }` strips
the gated body before lowering — eliding every call that would have
@@ -1832,11 +1832,11 @@ zig build && zig build test && bash tests/run_examples.sh && bash tests/cross_co
`emitFunctionDecl` ([src/ir/emit_llvm.zig:682](src/ir/emit_llvm.zig#L682))
passes `is_var_arg=1` to `LLVMFunctionType` accordingly. New
`promoteCVariadicArgs` applies C default argument promotion
(`bool/s8/s16/u8/u16 → s32`, `f32 → f64`) to extras past the fixed
(`bool/i8/i16/u8/u16 → i32`, `f32 → f64`) to extras past the fixed
param count. `packVariadicCallArgs` early-outs for foreign+variadic
so the slice-packing path is bypassed entirely. New test
`examples/ffi-foreign-cvariadic.sx` + `.c` exercise s64 / f64 / s32
returns through C `va_arg` over s32 / f64 / `*u8` element types.
`examples/ffi-foreign-cvariadic.sx` + `.c` exercise i64 / f64 / i32
returns through C `va_arg` over i32 / f64 / `*u8` element types.
Stale-snapshot drift from in-progress std.sx additions (`xml_escape`,
`path_join`, `BuildOptions.set_post_link_*`) re-pinned in 12
expected files — verified all diffs were dead-decl additions, string
@@ -1878,10 +1878,10 @@ zig build && zig build test && bash tests/run_examples.sh && bash tests/cross_co
nonvirt : CallNonvirtualByteMethod=70 / Char=73 / Short=76
static : CallStaticByteMethod=120 / Char=123 / Short=126
Each variant's `.jni_msg_send` return-type switch grew rows for
`.s8` / `.s16` / `.u16` (jbyte / jshort / jchar). New
`.i8` / `.i16` / `.u16` (jbyte / jshort / jchar). New
`LLVMEmitter.jniPromoteVararg(val, raw_ty)` handles the call-site
promotion that JNI's variadic Call<T>Method runtime expects:
s8 / s16 → SExt to i32
i8 / i16 → SExt to i32
u8 / u16 / bool → ZExt to i32
f32 → FPExt to f64
Pointers and wide types pass through unchanged. Wired into all
@@ -1895,8 +1895,8 @@ zig build && zig build test && bash tests/run_examples.sh && bash tests/cross_co
hiding the more useful "unsupported return/parameter type at
this token" diagnostic. New cross-compile test
`examples/114-jni-promoted-narrow-types.sx` exercises a
`#jni_class` returning `s8 / s16 / u16` and a varargs method
taking `(s8, s16, u16, f32)`; IR shows the expected
`#jni_class` returning `i8 / i16 / u16` and a varargs method
taking `(i8, i16, u16, f32)`; IR shows the expected
`sext i8 → i32`, `sext i16 → i32`, `zext i16 → i32`, and
`double 1.5e+00` (FPExt folded for the constant) at the call
site. Tests 112 / 113 migrated to use `u32` (Java has no
@@ -1910,7 +1910,7 @@ zig build && zig build test && bash tests/run_examples.sh && bash tests/cross_co
methods) and rejects unsupported parameter types at the type
token's span; `lowerJniCall` validates each method arg's TypeId
post-lowering against the arg expression's span. Same supported
set as returns (bool / s32 / s64 / f32 / f64 / pointer) minus
set as returns (bool / i32 / i64 / f32 / f64 / pointer) minus
`void` for params. Refactor splits `validateJniReturnType` /
`validateJniParamType` over a shared `validateJniType` core that
formats the diagnostic with a "return type" / "parameter type"
@@ -1923,7 +1923,7 @@ zig build && zig build test && bash tests/run_examples.sh && bash tests/cross_co
`examples/113-jni-unsupported-param-type.sx` locks in the
parameter-type diagnostic shape (e.g.
`examples/113-jni-unsupported-param-type.sx:16:30: error: JNI
call 'Foo.take': unsupported parameter type 's8' (...)`). 143
call 'Foo.take': unsupported parameter type 'i8' (...)`). 143
host + 9 cross tests green; chess on Pixel still builds clean.
- 2026-05-20: JNI return-type validation lifted from emit_llvm
@@ -1931,11 +1931,11 @@ zig build && zig build test && bash tests/run_examples.sh && bash tests/cross_co
in `src/ir/lower.zig`) so the diagnostic carries the
return-type slot's source span. New
`Lowering.validateJniReturnType` helper mirrors the supported
set in emit_llvm's `.jni_msg_send` switch (void / bool / s32 /
s64 / f32 / f64 / pointer types); a `*Foo.bad()` call where
set in emit_llvm's `.jni_msg_send` switch (void / bool / i32 /
i64 / f32 / f64 / pointer types); a `*Foo.bad()` call where
`bad()` returns an unsupported type now produces e.g.
`examples/112-jni-unsupported-return-type.sx:15:29: error:
JNI call 'Foo.bad': unsupported return type 's8' (JNI lowering
JNI call 'Foo.bad': unsupported return type 'i8' (JNI lowering
supports ...)`. emit_llvm's diagnostic stays as defense in
depth — it would only fire if a future IR path bypasses the
lowering check. New focused test
@@ -1969,13 +1969,13 @@ zig build && zig build test && bash tests/run_examples.sh && bash tests/cross_co
- 2026-05-20: chess-on-Pixel size bug fixed by refactoring
`library/modules/platform/android.sx` to zero module-level
globals. Root cause: android.sx exported `g_viewport_w : s32 = 0`
and `g_viewport_h : s32 = 0` at module scope; chess's `main.sx`
globals. Root cause: android.sx exported `g_viewport_w : i32 = 0`
and `g_viewport_h : i32 = 0` at module scope; chess's `main.sx`
declared its own `g_viewport_w : f32 = 800.0` at module scope.
When chess `#import`ed android.sx, the imported public global
shadowed chess's local decl for the unqualified name resolution,
so chess's writes (`g_viewport_w = fc.viewport_w`) silently
clobbered android.sx's s32 with the logical f32 cast to s32 (414
clobbered android.sx's i32 with the logical f32 cast to i32 (414
instead of 1080). `Gles3Gpu.pixel_w` then fed `glViewport(0,0,
414,831)`, clipping rendering to a 414-pixel box in the GL-
bottom-left. Refactor moved every piece of Android backend state
@@ -2008,7 +2008,7 @@ zig build && zig build test && bash tests/run_examples.sh && bash tests/cross_co
`lowerObjcStaticCall` route through the same helper so the IR Ref's
recorded ret_ty matches what `inferExprType` reports. Pre-fix:
`UIWindow.alloc().initWithWindowScene(scene)` (and any other chained
shape) collapsed the inner ret to `.s64`, the next dispatch's
shape) collapsed the inner ret to `.i64`, the next dispatch's
`foreign_class_map.get(...)` missed, and lowering emitted
`error: unresolved 'initWithWindowScene'`. The "lazy-lower" wording in
the issue file is a red herring — the bug fires on direct calls too;

View File

@@ -5,8 +5,41 @@ Tracking checkpoint for the mem.sx Zig-aligned implementation
## Last completed step
- **`resolveType(null) → .s64` silent fallback removed.** `resolveType`
now takes a non-optional `*const Node`; the `null → .s64` branch is
**Allocator primitive rename — `alloc``alloc_bytes`,
`dealloc``dealloc_bytes` (`88bae3c`, 2026-06-11).** Phase 4's naming
piece pulled forward per Agra's call (Option A in the 2.2 naming fork):
the bare names free up NOW so the Phase 2.2 helpers land under their
FINAL Appendix-A names (`alloc(T,n)` / `free(s)` / `create` / `destroy`
/ `clone` / `resize` / `mem_realloc`) with zero later churn. Phase 4
shrinks to signature expansion (size/align params + resize/remap +
deinit) only.
What changed (signatures unchanged — 2-method era continues):
- `std.sx` protocol decl; `std/mem.sx` 6 impls + internals; library
call sites (glyph_cache/json/state/renderer); 13 example .sx files
(incl. the two custom `Tracer` impls in 0306/0808 — caught broken
mid-step by stdout-diff review BEFORE pinning; the first `--update`
had captured their broken output, restored + fixed + re-pinned).
- Compiler: `interp.zig` thunk-name lookups
(`__thunk_CAllocator_Allocator_alloc_bytes`/`_dealloc_bytes`) — the
ONE hard name coupling; `allocViaContext` + default-context emission
are slot-positional (rename-safe); ffi.zig's `"alloc"` check is the
Obj-C `Cls.alloc()` intercept (unrelated, untouched).
- 37 `.ir` snapshots re-pinned (thunk symbols + reflection
field-name strings); all 37 verified stdout-clean before update;
path-noise churn reverted.
- External repos migrated + gated: game (`main.sx`, SxChess.app
builds + bundles) and m3te (`board_fx.sx`, `main.sx`,
`tools/key_particle.sx`; tools/run_tests.sh 23/23). Obj-C zero-arg
`.alloc()` calls are NOT protocol calls — excluded by pattern.
Gates: zig build 0, zig build test 0, suite 582/582, m3te 23/23,
game rebuilt.
<details><summary>Prior steps (2026-05-25 era)</summary>
- **`resolveType(null) → .i64` silent fallback removed.** `resolveType`
now takes a non-optional `*const Node`; the `null → .i64` branch is
gone. Callers that legitimately had no annotation handle it
themselves: top-level `var_decl` at `lower.zig:630` infers from the
initializer or diagnoses if neither is present (matches the
@@ -21,7 +54,7 @@ Tracking checkpoint for the mem.sx Zig-aligned implementation
...`), which makes the always-non-null path obvious from the type.
Real-world impact: `g_pi := 3.14;` at the top level used to be
silently typed as `s64`. Now it infers as `f64`. Regression at
silently typed as `i64`. Now it infers as `f64`. Regression at
`examples/137-toplevel-var-type-inference.sx` (count/pi/flag — int /
float / bool inferred correctly). 159/159 example tests + chess
clean.
@@ -128,7 +161,7 @@ Tracking checkpoint for the mem.sx Zig-aligned implementation
recursive heap content).
Also closes the type-inference half of the same bug: `NAME :: #run
expr;` with no annotation used to default to `s64` (silent fallback
expr;` with no annotation used to default to `i64` (silent fallback
in `resolveType(null)`). `lowerComptimeGlobal` now infers from the
expression's return type when no annotation is provided. The
silent fallback in `resolveType` itself is left in place for other
@@ -140,8 +173,66 @@ Tracking checkpoint for the mem.sx Zig-aligned implementation
of zeros, on both interp and codegen paths. 156/156 example tests
+ chess clean.
</details>
## Current state
**std.sx-as-pure-re-exports plan COMPLETE** (2026-06-11,
Agra-directed end-to-end). std.sx is now a facade of alias
declarations only (`49a36bb`): implementations live in std/core.sx
(builtins, libc escape hatch, Context/Allocator/Into/Source_Location/
`string` — the reserved name needs and permits no alias), std/fmt.sx
(print/format/any_to_string/string ops), std/list.sx (List); the
namespace tail is unchanged and `core`/`fmt`/`list` carry alongside
it. Consumer surface byte-identical; 37 .ir snapshots re-pinned
(pure renumbering, digit-normalized diff empty).
Issues filed AND resolved along the way (all same-day,
Agra-authorized): 0120 generic-struct head aliases (`f2db8ec`,
example 0211), 0121 fn aliases of every kind incl. comptime-pack
(`721369a`, example 0546), 0122 whole-program passes pinning the
source context per decl (`340be40` — latent on master, exposed by
the facade; coverage 0129/1047/1049/1052/1053/1056). Protocol
aliases (plain + Into's xx path) and #builtin/#foreign aliases
probe-verified working; param-protocol impl dot-calls are a designed
opt-in gap, not a bug.
Gates at completion: zig build test 426/426, suite 588/588,
m3te 23/23, game SxChess builds + bundles. Suite baseline 588.
**(2026-06-11) Phase-by-phase ground truth** (verified against the
tree; the sections below this one are the 2026-05-25 era record):
- Phase 1 DONE (xx heap-copy via context.allocator; serializer; the
whole implicit-Context refactor).
- Phase 2.1 DONE-equivalent: allocators.sx became `std/mem.sx` via the
STDLIB restructure (`59f0aa7`).
- **Primitive rename DONE (`88bae3c`)** — see "Last completed step".
Protocol is `alloc_bytes(size)` / `dealloc_bytes(ptr)` (2-method
era signatures).
- Phase 2.2 helpers: NOT YET — next step. Final names per Appendix A:
`create(a,$T)->*T`, `alloc(a,$T,n)->[]T`, `destroy(a,ptr)`,
`free(a,slice)`, `clone(src,a)`, `resize(slice,a,n)`,
`mem_realloc(a,ptr,old,new,align)`. The `free` helper requires
REMOVING std.sx's bare `malloc`/`free` foreign decls (lines ~13-16;
`libc_malloc`/`libc_free` aliases already exist) and migrating ~30
bare uses (mostly examples) — fold into the helpers step.
- Phase 2.3: TrackingAllocator exists; FailingAllocator +
LoggingAllocator missing.
- Phase 3 (caller migration to helpers): not started.
- Phase 4: now signature-expansion only (alloc_bytes gains alignment;
dealloc_bytes gains size+align; + resize/remap/deinit) — naming
piece already landed.
- Phase 5 (--leak-check + specs Memory chapter): not started.
- NOTE: plan's "init returns Allocator via xx heap-copy" section is
SUPERSEDED by the by-value convention (CLAUDE.md "Allocator
construction"); BufAlloc.init still returns *BufAlloc (state lives
in the caller's buffer — review at Phase 4 whether to align).
- Suite baseline 582; gates now: zig build && zig build test &&
bash tests/run_examples.sh (+ m3te 23/23 + game build for
std-touching steps). The old 148-159 counts below are historical.
<details><summary>2026-05-25 era state (historical)</summary>
Phase 0.0c shipped (allocator API on one-line `init` returning `*T`;
TrackingAllocator added). 148/148 tests pass.
@@ -164,7 +255,7 @@ Phase 0 spike outcomes:
inst.zig BuiltinId, lower.zig (registry + return-type + reflection
handler), interp.zig (fallback), sema.zig (allowed-builtins list),
lsp/server.zig (both completion tables), library/modules/std.sx.
Smoke coverage added in `examples/50-smoke.sx` (u8/s32/s64/Point).
Smoke coverage added in `examples/50-smoke.sx` (u8/i32/i64/Point).
- **0.7** `#import` transitivity — surfaced and fixed via issue-0038.
- **0.8** `#foreign("c")` rename syntax — confirmed
`#foreign libc "name"`.
@@ -179,7 +270,7 @@ verification: `size_of(*u8)=8`, `size_of(Ptr where Ptr::*u8)=8`,
`size_of(?u8)=2`, `size_of(Maybe where Maybe::?u8)=2` — all clean on
interp + codegen.
Also landed during 0041/0042: the silent `.s64` fallback in
Also landed during 0041/0042: the silent `.i64` fallback in
`resolveTypeArg` is gone — unresolved type names now emit a real
diagnostic. Surfaced and removed two bogus `size_of(Complex)` /
`size_of(Sx)` calls in `examples/10-generic-struct.sx` that were
@@ -243,7 +334,7 @@ What landed:
store honours the destination width — no more "assume 8 bytes"
silent clobber. Regression test at
`examples/132-comptime-typed-store-widths.sx` exercises every
primitive width (u8/u16/u32/u64, s8..s64, bool, f32, f64) via
primitive width (u8/u16/u32/u64, i8..i64, bool, f32, f64) via
comptime checksums compared to runtime checksums.
- Call-convention mismatch at bare-fn → fn-pointer coercion is now
a compile error (commit `f886d5f`). The chess-debug sweep that
@@ -270,8 +361,77 @@ bypass) is FULLY CLOSED. User-typed `context.allocator.X` flows
through the real protocol vtable at codegen *and* runs the same
chain at comptime in the interp. No remaining shortcut.
</details>
## Current state
**Opt-in UFCS landed (`a47ea14`, 2026-06-11)** — the canonical dot
surface now works: `context.allocator.create(Session)`,
`slice.clone(a)`. Agra specified the model in-session (three
clarifying rulings): free-fn dot-calls are OPT-IN via
`name :: ufcs (params) { body }` (NEW declaration form) or
`name :: ufcs target;` (alias); plain fns are direct/`|>`-only with a
tailored rejection. Implementation inverted TWO pre-existing gaps:
unannotated fns used to dot-dispatch (removed; 6 example files
audited + migrated, ZERO reliance in m3te/game) and aliases did NOT
dot-dispatch at all (0036 only ever pinned direct+pipe). Generic ufcs
fns bind `$T` from the receiver; protocol receivers dispatch own
methods first, fall through to ufcs fns for non-members
(protocolHasMethod gate in lower/call.zig). Root-cause bonus:
`inferGenericReturnType` now delegates to `buildTypeBindings` (ONE
binding builder) — structured generic params (`[]$T`) no longer type
direct calls as `T{}` stubs. mem.sx helpers marked `ufcs`; specs.md
§UFCS rewritten around the opt-in matrix. Tests: 0053 (matrix), 1166
(rejection), 0838 re-pinned (dot+pipe+direct). Gates: 585/585, zbt 0,
m3te 23/23, game builds. 0119 RESOLVED (final banner).
**Phase 2.2 DONE (`84e0fb0`, 2026-06-11).** The 0119 block resolved as
a LANGUAGE RULING, not a compiler fix (Agra, in-session): dot-form UFCS
on generic free functions is not the contract — UFCS free-fn
dot-dispatch is the annotated `ufcs` alias mechanism (concrete
targets), and the FLUENT spelling for free functions is the pipe:
`context.allocator |> create(Session)` desugars at parse time to the
direct call, which dispatches generics through normal monomorphization
(verified for protocol + slice receivers). specs.md §UFCS corrected
(it overstated "generic functions" for the dot form). Issue 0119
carries the RESOLVED banner; residual unfiled corner: a `ufcs` alias
naming a generic target doesn't dot-dispatch either.
Landed:
- `std/mem.sx` typed helpers, era-complete bodies, final names:
`create(a,$T)->*T`, `destroy(a,*$T)`, `alloc(a,$T,n)->[]T`,
`free(a,[]$T)`, `clone(src,a)`, `resize(slice,a,n)` (fresh storage +
copy + free-old; old slice dangles), `mem_realloc(a,ptr,old,new,
align)` (alloc+copy+dealloc; align unhonored until the protocol
carries alignment — documented inline). NO zero-init (Zig-aligned).
- std.sx bare `malloc`/`free` decls REMOVED (libc_malloc/libc_free
stay as the raw escape hatch); users migrated: examples
0205/0604/0804/0806/0808/1610 + game/chess/pieces.sx.
- Regression: examples/0838-memory-helpers.sx (whole surface, direct +
`|>` spellings, TrackingAllocator balances 8/8). 37 .ir re-pins
(constant-pool renumbering from the removed decls — the
ISSUE-MEM-004 cascade; all verified stdout-clean pre-update).
- KNOWN GAP: `string` does NOT bind a `[]$T` param (probe: "unknown
type 'T'") — string-clone story deferred (sx string is [:0]u8-shaped;
decide at Phase 4/5).
Gates: zig build 0, zbt 0, suite 583/583, m3te 23/23, game SxChess.app
builds.
## Next step
**Phase 2.3 — diagnostic wrappers**: `FailingAllocator` (delegates to
parent while budget remains, then alloc returns null) and
`LoggingAllocator` (tag-prefixed prints, delegates) in `std/mem.sx`,
2-method-era bodies, by-value `init` per the CLAUDE.md convention.
Then Phase 3 (migrate std.sx/library/example callers to the helpers —
NOTE std.sx itself cannot import mem.sx (circular); its internals keep
alloc_bytes), Phase 4 (protocol signature expansion: alignment + size
on the primitives, resize/remap/deinit — naming already landed),
Phase 5 (--leak-check + specs Memory chapter).
<details><summary>2026-05-25 era next-step record (historical)</summary>
Phase 1.3 (closure env allocation through context) shipped in commit
`8e21cc5`. Phase 1.4 (codegen serializer for all interp Value
variants) shipped this session. Phase 1.2 (free / malloc through
@@ -300,6 +460,8 @@ Open follow-ups, in roughly the order they make sense:
the canonical buffer/string case is already handled by `[]T` /
`string`.
</details>
## Phase 0.3 audit findings — chess allocator usage (closed)
After Step 5 / matchContextAllocCall removal, every consumer call
@@ -324,7 +486,84 @@ Allocator value naturally.
## Log
- **2026-05-25 (latest)** — `resolveType(null) → .s64` removed.
- **2026-06-11 (latest)** — Redundant flat `#import "modules/std/mem.sx"`
dropped from the facade (`c75cd9c`, Agra spotted it): the tail's
`mem ::` import already covers the graph needs (ufcs helpers +
CAllocator); the double import was duplicating lowered IR (~2.5k
lines across 37 re-pinned .ir snapshots, output byte-identical).
Gates: suite 588/588, zbt 0, m3te 23/23, game builds.
- **2026-06-11 (prior)** — std.sx restructured to a pure re-export
facade (`49a36bb`): all implementations moved to std/core.sx /
std/fmt.sx / std/list.sx; std.sx = alias decls + namespace tail.
En route, two more issues filed AND resolved: 0121 fn aliases
(`721369a` — renamed aliases were broken for EVERY fn kind, not
just packs; scan-time fn_ast_map registration via the shared
alias-chain walk; example 0546) and 0122 ambient source-context
bugs in convergeClosureShapeSets / checkErrorFlow / unknown-type
loop (`340be40` — latent on master, exposed by the facade).
Probe-verified before executing: protocols (plain + Into xx path),
#builtin / #foreign aliases, reserved `string` (no alias needed or
possible). 37 .ir re-pins (pure renumbering). Gates: zbt 426/426,
suite 588/588, m3te 23/23, game builds + bundles.
- **2026-06-11 (prior)** — Issue 0120 filed AND resolved (Agra-directed
same-session fix). Found probing the std.sx-as-pure-re-exports
restructure: generic-struct head alias (`BoxAlias :: Box;`) lowered
silently to `.unresolved` → LLVM backend panic; cross-module
`Box :: r.Box;` re-export invisible. Fix: `selectGenericStructHead`
follows const-alias decls hop-by-hop from each ALIAS AUTHOR's source
(`aliasedStructTemplate`, nominal.zig; `namespaceAliasVerdictFrom`
for `ns.X` RHS), checked before the template map so a facade's
same-name re-export beats an invisible global template; plus the
missing "unknown type" diagnostic on the `.call` type-head tail
(resolveTypeCallWithBindings). Also fixed PRE-EXISTING stale unit
test (calls.test.zig UFCS plan — predated a47ea14's opt-in model;
master was 425/426). specs.md Type Aliases + readme re-export
section + Decisions Log updated. Regression: examples/0211 (+rich/
+facade companions). Gates: zbt 426/426, suite 587/587.
- **2026-06-11 (prior)** — BufAlloc.init by-value (`51194a2`, Agra
request): init no longer carves its state struct off the buffer's
head (`-> BufAlloc`, plain literal; the old `-> *BufAlloc` cost 24
bytes of every buffer and returned null under min-size). The
CLAUDE.md by-value convention now holds for ALL allocators.
Regression: examples/0839 (full-capacity 64+64 on a 128 buffer —
failed pre-fix; exact-fit, overflow, reset). 0129's pinned output
unchanged. .ir churn: init's signature (sret) + renumbering.
Gates: 586/586, zbt 0, m3te 23/23, game builds.
- **2026-06-11 (earlier)** — Opt-in UFCS (`a47ea14`). Agra's model:
dot-calls opt-in via `:: ufcs (...)` marker or `:: ufcs target;`
alias; plain fns direct/`|>`-only. Parser (ufcs-fn form,
FnDecl.is_ufcs), call-plan + lowering gates (calls.zig,
lower/call.zig), protocol-receiver fall-through, generic dispatch
with receiver-bound `$T`, inferGenericReturnType → buildTypeBindings
(fixes pre-existing `T{}` mis-typing of structured-param generics).
6 reliant examples migrated; mem helpers marked ufcs; specs §UFCS
rewritten; tests 0053+1166+0838. 585/585, zbt 0, m3te 23/23, game
builds. 0119 closed with the full arc in its banner.
- **2026-06-11 (later)** — Phase 2.2 shipped (`84e0fb0`). Typed helpers
in std/mem.sx (create/destroy/alloc/free/clone/resize/mem_realloc,
era-complete bodies, no zero-init); bare malloc/free dropped from
std.sx (6 example files + game pieces.sx migrated to libc_*). The
0119 "blocker" resolved as Agra's language ruling: generic free fns
are NOT dot-rewritten — fluent spelling is `|>` (parse-time desugar
→ direct call → normal monomorphization; verified on protocol +
slice receivers). specs.md §UFCS corrected; 0119 RESOLVED banner.
Regression examples/0838 (direct + |> spellings; tracker 8/8).
37 .ir re-pins (const-pool renumbering). Gates: 583/583, zbt 0,
m3te 23/23, game builds. String-clone deferred (string doesn't bind
[]$T — known gap noted).
- **2026-06-11** — Allocator primitive rename (`88bae3c`): protocol
`alloc``alloc_bytes`, `dealloc``dealloc_bytes` (2-method-era
signatures unchanged). Phase 4's naming piece pulled forward (Agra
Option A) so Phase 2.2 helpers land final-named once. Touched:
std.sx decl, mem.sx 6 impls, 4 library files, 13 examples (incl.
two custom Tracer impls — initially missed, caught by pre-pin
stdout review after the first --update captured their broken
output; restored, fixed, re-pinned), interp.zig thunk-name strings,
37 .ir snapshots. Externals migrated + gated: game (SxChess.app
builds) + m3te (23/23). Suite 582/582, zbt 0. Discriminator note:
Obj-C `.alloc()` is zero-arg; Allocator `.alloc(size)` has an arg —
the sed keyed on `\.alloc\((?!\))`.
- **2026-05-25 (latest)** — `resolveType(null) → .i64` removed.
Signature changed to non-optional `*const Node`; 12 callers
surveyed and classified. The three unguarded ones — top-level
`var_decl` at `lower.zig:630` (now mirrors lowerVarDecl's
@@ -334,7 +573,7 @@ Allocator value naturally.
`if (x != null)` blocks; cleaned up to optional-payload syntax.
`examples/137-toplevel-var-type-inference.sx` proves the visible
win: `g_pi := 3.14;` at module scope now infers `f64` (used to be
silent `s64`). 159/159 + chess clean.
silent `i64`). 159/159 + chess clean.
- **2026-05-25 (penultimate)** — Phase 1.4a shipped. `valueToLLVMConst`
takes IR `TypeId` (not LLVM type) + an interpreter handle.
String/slice fat pointers are serialized by capturing the
@@ -377,8 +616,8 @@ Allocator value naturally.
at line 676 now passes `global.name` so the diagnostic locates the
offending `#run` site. `lowerComptimeGlobal` (`lower.zig:6384`)
infers the return type from the expression when the user omits
the type annotation — closes the silent-s64 default for `NAME ::
#run expr;` bindings. The broader `resolveType(null) -> .s64`
the type annotation — closes the silent-i64 default for `NAME ::
#run expr;` bindings. The broader `resolveType(null) -> .i64`
fallback is left in place for other callers — flagged for a
follow-up audit. Regression at
`examples/134-comptime-aggregate-global.sx`. 156/156 + chess green.
@@ -448,7 +687,7 @@ Allocator value naturally.
(`Tracer.count = 1`) — interp + codegen parity. 152/152 +
chess green.
- **2026-05-24** — issue-0041 and issue-0042 both fixed end-to-end.
Also removed the silent `.s64` fallback in `resolveTypeArg`,
Also removed the silent `.i64` fallback in `resolveTypeArg`,
guarded the two upstream callers (`buildTypeBindings`,
`inferGenericReturnType`) with `type_bridge.isTypeShapedAstNode`,
and fixed three parser regressions introduced by the 0041 work
@@ -520,8 +759,8 @@ Allocator value naturally.
`mi.ret_type == void_ptr`, but `*void` is overloaded — both
Self-disguised-as-*void AND a literal `-> *void` return appear as
the same `TypeId`. With `target_type` leaking from the enclosing
function's return type (e.g. `main -> s32`), every `*void` return
was loaded as `s32`, yielding 0 → null. Fix: stash `ret_is_self`
function's return type (e.g. `main -> i32`), every `*void` return
was loaded as `i32`, yielding 0 → null. Fix: stash `ret_is_self`
on `ProtocolMethodInfo` during `registerProtocolDecl` (set when
the AST return-type node is the `Self` type-expr), and gate the
unbox on that flag. Regression at
@@ -542,12 +781,12 @@ Allocator value naturally.
## Known issues (discovered during execution)
### ISSUE-MEM-001: Type inference defaults `p := malloc(64)` to `s64`
### ISSUE-MEM-001: Type inference defaults `p := malloc(64)` to `i64`
**Severity:** medium (workaround exists; bites unexpectedly).
**Symptom:** Writing `p := malloc(64)` (no explicit type) infers
`p: s64` instead of `p: *void`. Subsequent `free(p)` then fails LLVM
`p: i64` instead of `p: *void`. Subsequent `free(p)` then fails LLVM
verification with "Call parameter type does not match function
signature!" because `free` expects `ptr` but receives `i64`.
@@ -556,8 +795,8 @@ or `xx malloc(64);` at the call site.
**Reproduction:**
```sx
main :: () -> s32 {
p := malloc(64); // p inferred as s64
main :: () -> i32 {
p := malloc(64); // p inferred as i64
free(p); // LLVM verify fails: ptr expected, i64 given
0;
}
@@ -565,7 +804,7 @@ main :: () -> s32 {
**Root cause:** Likely in the inference path for `:=` declarations
when the RHS is a `*void`-returning #builtin. The compiler defaults
the binding to s64 instead of matching the return type. To
the binding to i64 instead of matching the return type. To
investigate in a future session.
**Status:** Open. Not blocking mem.sx work but worth fixing as a
@@ -681,9 +920,9 @@ where users need the underlying state (TrackingAllocator).
its auto-unbox path on `mi.ret_type == void_ptr`, but the same
`TypeId` covers both Self-disguised-as-*void and a literal
`-> *void`. With `target_type` leaking from the surrounding
function (e.g. `main -> s32`), every protocol call returning
function (e.g. `main -> i32`), every protocol call returning
`*void` got its result loaded as `sizeof(target_type)` bytes — for
`s32` that's the first 4 bytes of the malloc'd block, which were
`i32` that's the first 4 bytes of the malloc'd block, which were
zero, comparing equal to null.
**Fix:** Tag `ProtocolMethodInfo` with `ret_is_self: bool`, set in

View File

@@ -12,14 +12,14 @@ wrapped around them. A function that can fail adds a trailing `!` to its
return type:
```sx
parse_digit :: (s: string) -> (s32, !) {
parse_digit :: (s: string) -> (i32, !) {
if s.len == 0 raise error.Empty;
if !is_digit(s[0]) raise error.BadDigit;
return s[0] - '0';
}
```
The `(s32, !)` says "returns an `s32` on success, or an error." The `!`
The `(i32, !)` says "returns an `i32` on success, or an error." The `!`
is one more slot in sx's normal multi-return — the error rides
alongside the values, it doesn't replace them.
@@ -61,7 +61,7 @@ in the signature:
```sx
ParseErr :: error { Empty, BadDigit, Overflow };
parse_int :: (s: string) -> (s32, !ParseErr) {
parse_int :: (s: string) -> (i32, !ParseErr) {
if s.len == 0 raise error.Empty;
if overflowed raise error.Overflow;
...
@@ -109,7 +109,7 @@ When you call a failable function and want its error to bubble up to
*your* caller, prefix the call with `try`:
```sx
two_digits :: (s: string) -> (s32, !) {
two_digits :: (s: string) -> (i32, !) {
a := try parse_digit(s); // if this fails, two_digits fails
b := try parse_digit(s[1..]);
return a * 10 + b;
@@ -153,7 +153,7 @@ attempt.
port := parse_port(s) or 8080; // if parsing fails, port = 8080
```
The error is absorbed; `port` is a plain `s32`.
The error is absorbed; `port` is a plain `i32`.
### Chain attempts — first success wins
@@ -347,7 +347,7 @@ For human-readable context, use `log` on the error path — the tag tells
you *what* failed, the log tells you the *details*:
```sx
parse :: (s: string) -> (s32, !) {
parse :: (s: string) -> (i32, !) {
onfail e { log.warn("parsing {}: {}", s, e); }
...
}

View File

@@ -1,5 +1,5 @@
#import "modules/std.sx";
main :: () -> s32 {
main :: () -> i32 {
if false then 40 else 42
}

View File

@@ -1,5 +1,5 @@
#import "modules/std.sx";
main :: () -> s32 {
main :: () -> i32 {
x := 42;
{
print("scope opened\n");

View File

@@ -1,5 +1,5 @@
#import "modules/std.sx";
main :: () -> s32 {
main :: () -> i32 {
defer print("still here\n");
return 42;
}

View File

@@ -1,6 +1,6 @@
#import "modules/std.sx";
sumOf10 :: () -> s32 {
sumOf10 :: () -> i32 {
i:= 1;
s:=0;
while i <= 10 {

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
quick_sort :: (items: []$T) {
partition :: (items: []T, lo: s64, hi: s64) -> s64 {
partition :: (items: []T, lo: i64, hi: i64) -> i64 {
pivot := items[hi];
i := lo - 1;
j := lo;
@@ -17,7 +17,7 @@ quick_sort :: (items: []$T) {
i
}
sort :: (items: []T, lo: s64, hi: s64) {
sort :: (items: []T, lo: i64, hi: i64) {
if lo < hi {
pi := partition(items, lo, hi);
sort(items, lo, pi - 1);
@@ -29,7 +29,7 @@ quick_sort :: (items: []$T) {
}
main :: () {
arr : []s64 = .[333, 2, 3, 5, 2, 2, 3, 4, 5, 6, 6, 1];
arr : []i64 = .[333, 2, 3, 5, 2, 2, 3, 4, 5, 6, 6, 1];
quick_sort(arr);
print("{}\n", arr);
}

View File

@@ -24,7 +24,7 @@ Vec2 :: struct {
unit_x :: () -> Vec2 { Vec2.{ x = 1.0, y = 0.0 } }
add :: (a: Vec2, b: Vec2) -> Vec2 { Vec2.{ x = a.x + b.x, y = a.y + b.y } }
scale :: (v: Vec2, s: f32) -> Vec2 { Vec2.{ x = v.x * s, y = v.y * s } }
len :: (v: Vec2) -> s32 { xx (v.x + v.y) }
len :: (v: Vec2) -> i32 { xx (v.x + v.y) }
}
EdgeInsets :: struct {
@@ -39,16 +39,16 @@ EdgeInsets :: struct {
}
Trio :: struct {
a: s32;
b: s32;
c: s32;
a: i32;
b: i32;
c: i32;
make :: (a: s32, b: s32, c: s32) -> Trio { Trio.{ a = a, b = b, c = c } }
sum :: (t: Trio) -> s32 { t.a + t.b + t.c }
make :: (a: i32, b: i32, c: i32) -> Trio { Trio.{ a = a, b = b, c = c } }
sum :: (t: Trio) -> i32 { t.a + t.b + t.c }
}
Result :: enum {
ok: s32;
ok: i32;
err: string;
}
@@ -121,7 +121,7 @@ main :: () {
// T8: .variant(payload) as function argument (match-as-expression)
{
describe :: (sh: Shape) -> s32 {
describe :: (sh: Shape) -> i32 {
if sh == {
case .circle: 10;
case .rect: 20;
@@ -145,7 +145,7 @@ main :: () {
// T10: Match as expression returning tagged enum
{
select :: (n: s32) -> Shape {
select :: (n: i32) -> Shape {
if n == {
case 0: .none;
case 1: .circle(1.0);
@@ -243,7 +243,7 @@ main :: () {
// E2: Function taking both types — each resolves correctly
{
use_both :: (sh: Shape, v: Vec2) {
ms : s32 = 0;
ms : i32 = 0;
if sh == { case .circle: (r) { ms = xx r; } else: {} }
print("E2: {} {} {}\n", ms, v.x, v.y);
}
@@ -252,7 +252,7 @@ main :: () {
// E3: Bare .variant (no parens) as function arg
{
check_none :: (sh: Shape) -> s32 {
check_none :: (sh: Shape) -> i32 {
if sh == { case .none: 1; else: 0; }
}
print("E3: {}\n", check_none(.none));
@@ -268,7 +268,7 @@ main :: () {
// E5: Tagged enum .variant(payload) in match-as-expression
{
sh : Shape = .circle(42.0);
r : s32 = 0;
r : i32 = 0;
if sh == {
case .circle: (v) { r = xx v; }
case .rect: (sz) { r = xx sz.w; }
@@ -280,7 +280,7 @@ main :: () {
// E6: Color enum (plain, not tagged) still works with bare .variant
{
c : Color = .green;
ci : s32 = xx c;
ci : i32 = xx c;
print("E6: {}\n", ci);
}

View File

@@ -4,13 +4,13 @@
// block (only one terminator per block).
#import "modules/std.sx";
#import "modules/compiler.sx";
#import "modules/build.sx";
do_it :: () -> bool {
inline if OS != .ios { return false; }
true
}
main :: () -> s32 {
main :: () -> i32 {
if do_it() then 0 else 1
}

View File

@@ -9,21 +9,21 @@
#import "modules/std.sx";
double :: (x: s32) -> s32 => x * 2;
double :: (x: i32) -> i32 => x * 2;
sum :: (a: s32, b: s32) -> s32 => a + b;
sum :: (a: i32, b: i32) -> i32 => a + b;
answer :: () -> s32 => 42;
answer :: () -> i32 => 42;
Point :: struct {
x: s32;
y: s32;
x: i32;
y: i32;
total :: (self: *Point) -> s32 => self.x + self.y;
scaled :: (self: *Point, by: s32) -> s32 => (self.x + self.y) * by;
total :: (self: *Point) -> i32 => self.x + self.y;
scaled :: (self: *Point, by: i32) -> i32 => (self.x + self.y) * by;
}
main :: () -> s32 {
main :: () -> i32 {
print("double: {}\n", double(7));
print("sum: {}\n", sum(3, 4));
print("answer: {}\n", answer());

View File

@@ -9,7 +9,7 @@
Show :: protocol {
show :: () -> string;
}
A :: struct { x: s64; }
A :: struct { x: i64; }
B :: struct { s: string; }
impl Show for A { show :: (self: *A) -> string => "A"; }
impl Show for B { show :: (self: *B) -> string => "B"; }
@@ -21,7 +21,7 @@ each :: (..xs: Show) -> void {
}
}
main :: () -> s32 {
main :: () -> i32 {
// Runtime range, cursor used.
for 0..3 (i) { print("i={}\n", i); }

View File

@@ -7,9 +7,9 @@ Shape :: enum {
none;
}
main :: () -> s32 {
main :: () -> i32 {
// By-ref mutation writes back into the array (impossible with a value copy).
xs : [3]s64 = .[1, 2, 3];
xs : [3]i64 = .[1, 2, 3];
for xs (*x) { x.* = x + 100; }
print("{} {} {}\n", xs[0], xs[1], xs[2]);

View File

@@ -4,23 +4,23 @@
#import "modules/std.sx";
Box :: struct {
v: s64;
boxed :: (self: Box) -> s64 { self.v } // value receiver
v: i64;
boxed :: (self: Box) -> i64 { self.v } // value receiver
}
sum_ptr :: (xs: *List(s64)) -> s64 {
total : s64 = 0;
sum_ptr :: (xs: *List(i64)) -> i64 {
total : i64 = 0;
for xs (n) { total = total + n; } // iterate through a *List
total
}
main :: () -> s32 {
xs := List(s64).{};
main :: () -> i32 {
xs := List(i64).{};
xs.append(10);
xs.append(20);
xs.append(30);
s : s64 = 0;
s : i64 = 0;
for xs (n) { s = s + n; } // value capture
print("sum {}\n", s); // 60
@@ -33,7 +33,7 @@ main :: () -> s32 {
bs := List(Box).{};
bs.append(.{ v = 7 });
bt : s64 = 0;
bt : i64 = 0;
for bs (*b) { bt = bt + b.boxed(); } // *Box receiver, value-self method
print("boxes {}\n", bt); // 7
0

View File

@@ -1,8 +1,8 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
Color :: enum { red; green; blue; }
@@ -45,7 +45,7 @@ END;
print("heredoc: {}\n", hd);
// Undefined with type
undef_val : s32 = ---;
undef_val : i32 = ---;
undef_val = 77;
print("undef-then-set: {}\n", undef_val);
@@ -54,7 +54,7 @@ END;
print("enum-lit: {}\n", c);
// Null pointer
np : *s32 = null;
np : *i32 = null;
print("null-ptr: {}\n", np);
// String .len

View File

@@ -1,20 +1,20 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
add :: (a: s32, b: s32) -> s32 { a + b }
add :: (a: i32, b: i32) -> i32 { a + b }
mul :: (a: s32, b: s32) -> s32 { a * b }
mul :: (a: i32, b: i32) -> i32 { a * b }
// P4 edge: Chained default→default calls
Chained :: protocol {
base :: (msg: string) -> s32;
wrap :: (msg: string) -> s32 {
base :: (msg: string) -> i32;
wrap :: (msg: string) -> i32 {
self.base(msg) + 1
}
double_wrap :: (msg: string) -> s32 {
double_wrap :: (msg: string) -> i32 {
self.wrap(msg) + self.wrap(msg)
}
}
@@ -136,31 +136,31 @@ main :: () {
// Implicit widening conversions
wu : u8 = 200;
ws : s64 = wu;
print("widen-u8-s64: {}\n", ws);
ws : i64 = wu;
print("widen-u8-i64: {}\n", ws);
wi3 : s32 = 42;
wi3 : i32 = 42;
wf : f64 = wi3;
print("widen-s32-f64: {}\n", wf);
print("widen-i32-f64: {}\n", wf);
wf32 : f32 = 1.5;
wf64 : f64 = wf32;
print("widen-f32-f64: {}\n", wf64);
wu2 : u8 = 100;
ws2 : s16 = wu2;
print("widen-u8-s16: {}\n", ws2);
ws2 : i16 = wu2;
print("widen-u8-i16: {}\n", ws2);
// More xx narrowing
xl : s64 = 12345;
xs : s32 = xx xl;
print("xx-s64-s32: {}\n", xs);
xl : i64 = 12345;
xs : i32 = xx xl;
print("xx-i64-i32: {}\n", xs);
xd : f64 = 1.5;
xf : f32 = xx xd;
print("xx-f64-f32: {}\n", xf);
xdf : f64 = 7.9;
xdi : s32 = xx xdf;
print("xx-f64-s32: {}\n", xdi);
xdi : i32 = xx xdf;
print("xx-f64-i32: {}\n", xdi);
}

View File

@@ -1,8 +1,8 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
@@ -116,7 +116,7 @@ main :: () {
print("nested-break: {} {}\n", nb_outer, nb_icount);
// For loop basic
farr : [4]s32 = .[10, 20, 30, 40];
farr : [4]i32 = .[10, 20, 30, 40];
out("for:");
for farr (it) {
out(" ");
@@ -163,7 +163,7 @@ main :: () {
out("\n");
// For on slice
fsl : []s32 = .[10, 20, 30];
fsl : []i32 = .[10, 20, 30];
out("for-slice:");
for fsl (it) {
print(" {}", it);
@@ -178,8 +178,8 @@ main :: () {
out("\n");
// Nested for
nf_a : [2]s32 = .[0, 1];
nf_b : [2]s32 = .[0, 1];
nf_a : [2]i32 = .[0, 1];
nf_b : [2]i32 = .[0, 1];
out("for-nested:");
for nf_a (oa) {
for nf_b (ob) {
@@ -189,7 +189,7 @@ main :: () {
out("\n");
// For with break preserving index
fbi : [5]s32 = .[10, 20, 30, 40, 50];
fbi : [5]i32 = .[10, 20, 30, 40, 50];
fbi_idx := 0;
for fbi, 0.. (it, ix) {
if it == 30 { fbi_idx = ix; break; }

View File

@@ -1,26 +1,26 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
add :: (a: s32, b: s32) -> s32 { a + b }
add :: (a: i32, b: i32) -> i32 { a + b }
mul :: (a: s32, b: s32) -> s32 { a * b }
mul :: (a: i32, b: i32) -> i32 { a * b }
identity :: (x: $T) -> T { x }
pair_add :: (a: $T, b: $U) -> s64 {
cast(s64) a + cast(s64) b
pair_add :: (a: $T, b: $U) -> i64 {
cast(i64) a + cast(i64) b
}
typed_sum :: (..args: []s32) -> s32 {
typed_sum :: (..args: []i32) -> i32 {
result := 0;
for args (it) { result = result + it; }
result
}
apply :: (f: (s32, s32) -> s32, x: s32, y: s32) -> s32 {
apply :: (f: (i32, i32) -> i32, x: i32, y: i32) -> i32 {
f(x, y)
}
@@ -28,11 +28,11 @@ void_return :: () {
return;
}
implicit_return :: (x: s32) -> s32 {
implicit_return :: (x: i32) -> i32 {
x * 2
}
early_return :: (x: s32) -> s32 {
early_return :: (x: i32) -> i32 {
if x > 10 { return 99; }
x
}
@@ -53,7 +53,7 @@ main :: () {
print("typed-const: {}\n", TYPED_PI);
// Variable with default init
di : s32;
di : i32;
print("default-init: {}\n", di);
// Implicit return
@@ -68,7 +68,7 @@ main :: () {
print("void-return: ok\n");
// Generic — single param
print("generic-s32: {}\n", identity(42));
print("generic-i32: {}\n", identity(42));
print("generic-f32: {}\n", identity(1.5));
print("generic-bool: {}\n", identity(true));
@@ -76,7 +76,7 @@ main :: () {
print("generic-multi: {}\n", pair_add(10, 20));
// Lambda
double :: (x: s32) => x * 2;
double :: (x: i32) => x * 2;
print("lambda: {}\n", double(7));
// Lambda with return type
@@ -84,7 +84,7 @@ main :: () {
print("lambda-ret: {}\n", halve(10.0));
// Local function (non-lambda)
local_add :: (a: s32, b: s32) -> s32 { a + b }
local_add :: (a: i32, b: i32) -> i32 { a + b }
print("local-fn: {}\n", local_add(3, 4));
// Nested function calls
@@ -94,11 +94,11 @@ main :: () {
print("varargs: {}\n", typed_sum(1, 2, 3, 4, 5));
// Spread
spread_arr : [3]s32 = .[10, 20, 30];
spread_arr : [3]i32 = .[10, 20, 30];
print("spread: {}\n", typed_sum(..spread_arr));
// Function pointers
fp : (s32, s32) -> s32 = add;
fp : (i32, i32) -> i32 = add;
print("fp: {}\n", fp(3, 4));
fp = mul;
print("fp-reassign: {}\n", fp(3, 4));

View File

@@ -1,8 +1,8 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
main :: () {

View File

@@ -1,10 +1,10 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
Point :: struct { x, y: s32; }
Point :: struct { x, y: i32; }
Color :: enum { red; green; blue; }
@@ -29,14 +29,14 @@ main :: () {
print("sqrt-f64: {}\n", sqrt(16.0));
// size_of
print("sizeof-s32: {}\n", size_of(s32));
print("sizeof-i32: {}\n", size_of(i32));
print("sizeof-f64: {}\n", size_of(f64));
print("sizeof-struct: {}\n", size_of(Point));
// align_of
print("alignof-u8: {}\n", align_of(u8));
print("alignof-s32: {}\n", align_of(s32));
print("alignof-s64: {}\n", align_of(s64));
print("alignof-i32: {}\n", align_of(i32));
print("alignof-i64: {}\n", align_of(i64));
print("alignof-struct: {}\n", align_of(Point));
// type_of + category matching
@@ -121,7 +121,7 @@ main :: () {
// cast
cval : f64 = 3.7;
print("cast: {}\n", cast(s32) cval);
cv2 : s32 = 42;
print("cast: {}\n", cast(i32) cval);
cv2 : i32 = 42;
print("cast-int-f64: {}\n", cast(f64) cv2);
}

View File

@@ -1,10 +1,10 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
Point :: struct { x, y: s32; }
Point :: struct { x, y: i32; }
Shape :: enum {
circle: f32;

View File

@@ -1,12 +1,12 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
Point :: struct { x, y: s32; }
Point :: struct { x, y: i32; }
point_sum :: (p: Point) -> s32 { p.x + p.y }
point_sum :: (p: Point) -> i32 { p.x + p.y }
// #run compile-time constants

View File

@@ -1,10 +1,10 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
Point :: struct { x, y: s32; }
Point :: struct { x, y: i32; }
main :: () {

View File

@@ -1,8 +1,8 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
main :: () {

View File

@@ -1,8 +1,8 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
@@ -11,7 +11,7 @@ main :: () {
// ========================================================
print("=== 25. Array Loop Mutation ===\n");
{
arr : [4]s32 = .[0, 0, 0, 0];
arr : [4]i32 = .[0, 0, 0, 0];
i := 0;
while i < 4 {
arr[i] = xx (i + 1);

View File

@@ -1,8 +1,8 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
@@ -10,7 +10,7 @@ main :: () {
{
print("=== UFCS Aliases ===\n");
num_sum :: (a: s64, b: s64) -> s64 { a + b }
num_sum :: (a: i64, b: i64) -> i64 { a + b }
sum :: ufcs num_sum;
print("{}\n", num_sum(40, 2)); // 42 — direct call
@@ -20,7 +20,7 @@ main :: () {
print("{}\n", num_sum(40, 2)); // 42 — direct (was tuple full-splat)
print("{}\n", 40 |> sum(2)); // 42 — pipe (was tuple partial-splat)
compute :: (a: s64, b: s64, c: s64, d: s64) -> s64 { a + b * c - d }
compute :: (a: i64, b: i64, c: i64, d: i64) -> i64 { a + b * c - d }
calc :: ufcs compute;
print("{}\n", compute(1, 2, 3, 4)); // 1+2*3-4 = 3 (was tuple full-splat)
@@ -28,14 +28,14 @@ main :: () {
print("{}\n", 1 |> calc(2, 3, 4)); // same = 3 — pipe UFCS
// Tuple return type
swap :: (a: s64, b: s64) -> (s64, s64) { (b, a) }
swap :: (a: i64, b: i64) -> (i64, i64) { (b, a) }
s := swap(1, 2);
a := s.0;
b := s.1;
print("{}\n", a); // 2
print("{}\n", b); // 1
wrap :: (x: s64) -> (s64) { (x,) }
wrap :: (x: i64) -> (i64) { (x,) }
t := wrap(99);
print("{}\n", t.0); // 99
}

View File

@@ -1,10 +1,10 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
add :: (a: s32, b: s32) -> s32 { a + b }
add :: (a: i32, b: i32) -> i32 { a + b }
main :: () {
@@ -23,12 +23,12 @@ main :: () {
assert(v.w == 4.0);
// Function call with trailing comma
add :: (a: s64, b: s64) -> s64 { return a + b; }
add :: (a: i64, b: i64) -> i64 { return a + b; }
r := add(10, 20,);
assert(r == 30);
// Array literal with trailing comma
arr := s64.[1, 2, 3,];
arr := i64.[1, 2, 3,];
assert(arr[2] == 3);
print("trailing commas ok\n");

View File

@@ -14,15 +14,15 @@
E :: error { Neg }
// dead `return 99;` after an unconditional return
const_one :: () -> s64 { return 1; return 99; }
const_one :: () -> i64 { return 1; return 99; }
// dead `return x;` after an unconditional raise (the failable closure shape)
always_raise :: (x: s64) -> (s64, !E) { raise error.Neg; return x; }
always_raise :: (x: i64) -> (i64, !E) { raise error.Neg; return x; }
// guard: a conditional return must still fall through to the trailing return
clamp :: (x: s64) -> s64 { if x > 10 { return 10; } return x; }
clamp :: (x: i64) -> i64 { if x > 10 { return 10; } return x; }
main :: () -> s32 {
main :: () -> i32 {
print("const_one={}\n", const_one()); // 1
print("raised={}\n", always_raise(5) catch (e) 0); // 0
print("clamp_hi={}\n", clamp(42)); // 10

View File

@@ -7,14 +7,14 @@
#import "modules/std.sx";
Counter :: struct { n: s32; }
Counter :: struct { n: i32; }
// FREE functions (defined outside the struct), pointer first param.
bump :: (c: *Counter) -> s32 { c.n += 1; return c.n; }
bump :: ufcs (c: *Counter) -> i32 { c.n += 1; return c.n; }
// reached ONLY via UFCS — must still be emitted.
reset :: (c: *Counter) { c.n = 0; }
reset :: ufcs (c: *Counter) { c.n = 0; }
main :: () -> s32 {
main :: () -> i32 {
c := Counter.{ n = 10 };
a := c.bump(); // 11, mutates c
b := c.bump(); // 12

View File

@@ -12,22 +12,22 @@
#import "modules/std.sx";
// Implicit return: trailing expression, no `;`.
double :: (n: s32) -> s32 { n * 2 }
double :: (n: i32) -> i32 { n * 2 }
// if/else as a value — each branch's last expression has no `;`.
sign :: (n: s32) -> s32 {
sign :: (n: i32) -> i32 {
if n < 0 { -1 } else if n > 0 { 1 } else { 0 }
}
// A value-producing block bound to a name.
sum3 :: (a: s32, b: s32, c: s32) -> s32 {
sum3 :: (a: i32, b: i32, c: i32) -> i32 {
t := { x := a + b; x + c }; // block value is `x + c`
t
}
// Match arms keep their `;` (exempt): the arm `;` is an arm terminator, so each
// arm still yields its expression as the match value.
classify :: (n: s32) -> s32 {
classify :: (n: i32) -> i32 {
if n == {
case 0: 100;
case 1: 10;
@@ -35,8 +35,8 @@ classify :: (n: s32) -> s32 {
}
}
main :: () -> s32 {
total : s32 = 0;
main :: () -> i32 {
total : i32 = 0;
total = total + double(10); // 20
total = total + sign(-7); // -1
total = total + sum3(1, 2, 3); // 6

View File

@@ -6,8 +6,8 @@
#import "modules/std.sx";
// `n * 2;` discards the value → the function returns nothing.
double :: (n: s32) -> s32 {
double :: (n: i32) -> i32 {
n * 2;
}
main :: () -> s32 { double(5) }
main :: () -> i32 { double(5) }

View File

@@ -8,9 +8,9 @@
#import "modules/std.sx";
pair :: () -> (s32, s32) { (5, 7) }
pair :: () -> (i32, i32) { (5, 7) }
main :: () -> s32 {
main :: () -> i32 {
// destructure decl inside a value-bound block
sum := {
a, b := pair();

View File

@@ -9,14 +9,14 @@
#import "modules/std.sx";
sign :: (n: s32) -> s32 {
sign :: (n: i32) -> i32 {
if n == {
case 0: 0;
else: if n > 0 then 1 else -1;
}
}
classify :: (n: s32) -> s32 {
classify :: (n: i32) -> i32 {
if n == {
case 0: 100;
case 1: 10;
@@ -24,7 +24,7 @@ classify :: (n: s32) -> s32 {
}
}
main :: () -> s32 {
main :: () -> i32 {
print("sign: {} {} {}\n", sign(-9), sign(0), sign(9)); // -1 0 1
print("classify: {} {} {}\n", classify(0), classify(1), classify(5)); // 100 10 -1
0

View File

@@ -6,9 +6,9 @@
#import "modules/std.sx";
scale :: (n: s32, factor: s32 = 2) -> s32 { n * factor }
scale :: (n: i32, factor: i32 = 2) -> i32 { n * factor }
label :: (n: s32, prefix: string = "v", suffix: string = "!") -> s32 {
label :: (n: i32, prefix: string = "v", suffix: string = "!") -> i32 {
print("{}{}{}\n", prefix, n, suffix);
n
}

View File

@@ -11,7 +11,7 @@
Json :: enum {
str: string;
int_: s64;
int_: i64;
null_;
}

View File

@@ -1,9 +1,9 @@
// Integer `{}` formatting across the full signed/unsigned range.
//
// Regression (issue 0090): the `{}` formatter was s64-based — it negated
// the value to print the sign (so s64::MIN, whose magnitude is
// unrepresentable as a positive s64, rendered as a bare "-"), and it had
// no unsigned-aware path (so a u64 all-ones value printed as the s64
// Regression (issue 0090): the `{}` formatter was i64-based — it negated
// the value to print the sign (so i64::MIN, whose magnitude is
// unrepresentable as a positive i64, rendered as a bare "-"), and it had
// no unsigned-aware path (so a u64 all-ones value printed as the i64
// reinterpretation, "-1"). Both extremes now render correctly: signed
// MIN prints all its digits, and unsigned integers print as unsigned
// decimal across all 64 bits.
@@ -12,16 +12,16 @@
main :: () {
// Signed extreme: magnitude is never negated, so MIN survives.
print("s64.min={}\n", s64.min);
print("s64.max={}\n", s64.max);
print("i64.min={}\n", i64.min);
print("i64.max={}\n", i64.max);
// Unsigned extreme: all 64 bits as unsigned decimal, not -1.
print("u64.max={}\n", u64.max);
// Spread across widths — signed.
print("s8.min={} s8.max={}\n", s8.min, s8.max);
print("s16.min={} s16.max={}\n", s16.min, s16.max);
print("s32.min={} s32.max={}\n", s32.min, s32.max);
print("i8.min={} i8.max={}\n", i8.min, i8.max);
print("i16.min={} i16.max={}\n", i16.min, i16.max);
print("i32.min={} i32.max={}\n", i32.min, i32.max);
// Spread across widths — unsigned (max is all-ones for that width).
print("u8.max={} u16.max={}\n", u8.max, u16.max);
@@ -31,7 +31,7 @@ main :: () {
print("u8.min={} u64.min={} zero={}\n", u8.min, u64.min, 0);
// Ordinary signed/unsigned values still print correctly.
neg : s32 = -42;
neg : i32 = -42;
pos : u32 = 4000000000;
print("neg={} pos={}\n", neg, pos);
}

View File

@@ -8,10 +8,10 @@
#import "modules/std.sx";
main :: () -> s32 {
main :: () -> i32 {
sum := 0;
for 0..1000000 (i) {
buf : [128]s64 = ---;
buf : [128]i64 = ---;
buf[0] = i;
sum += buf[0];
}

View File

@@ -8,8 +8,8 @@
#import "modules/std.sx";
main :: () -> s32 {
arr : [4096]s64 = ---;
main :: () -> i32 {
arr : [4096]i64 = ---;
i := 0;
while i < 4096 { arr[i] = i; i += 1; }
sum := 0;
@@ -17,7 +17,7 @@ main :: () -> s32 {
print("sum={}\n", sum);
// By-value capture is a copy: mutating it leaves the array untouched.
small : [3]s64 = .[10, 20, 30];
small : [3]i64 = .[10, 20, 30];
for small (x) { x += 100; }
print("copy-guard: {} {} {}\n", small[0], small[1], small[2]);
0

View File

@@ -6,7 +6,7 @@
#import "modules/std.sx";
main :: () -> s32 {
main :: () -> i32 {
for 0..3 (i) {
defer print("cleanup {}\n", i);
if i == 1 { break; }

View File

@@ -1,29 +1,29 @@
#import "modules/std.sx";
pair_sum :: (xs: []s64, ys: []s64) -> s64 {
pair_sum :: (xs: []i64, ys: []i64) -> i64 {
total := 0;
for xs, ys (x, y) { total += x * y; }
total
}
make :: () -> [3]s64 {
r : [3]s64 = .[7, 8, 9];
make :: () -> [3]i64 {
r : [3]i64 = .[7, 8, 9];
r
}
main :: () -> s32 {
main :: () -> i32 {
// Agra's example: a 1..5 inclusive, b open-ended following along.
for 1..=5, 0.. (a, b) { print("{}:{} ", a, b); }
print("\n");
// Index idiom replacing the old (x, i) form.
xs : [3]s64 = .[10, 20, 30];
xs : [3]i64 = .[10, 20, 30];
for xs, 0.. (x, i) { print("[{}]={} ", i, x); }
print("\n");
// Parallel slices.
a4 : [4]s64 = .[1, 2, 3, 4];
b4 : [4]s64 = .[10, 20, 30, 40];
a4 : [4]i64 = .[1, 2, 3, 4];
b4 : [4]i64 = .[10, 20, 30, 40];
print("dot={}\n", pair_sum(a4, b4));
// Arrow bodies.

View File

@@ -7,7 +7,7 @@
#import "modules/std.sx";
main :: () -> s32 {
main :: () -> i32 {
for 0<..<5 (i) { print("{} ", i); }
print("| 0<..<5\n");
for 0=..=5 (i) { print("{} ", i); }
@@ -22,7 +22,7 @@ main :: () -> s32 {
print("| 0..=5\n");
// Exclusive-start open range following a bounded first iterable.
xs : [3]s64 = .[10, 20, 30];
xs : [3]i64 = .[10, 20, 30];
for xs, 2<.. (x, i) { print("{}@{} ", x, i); }
print("| xs, 2<..\n");

View File

@@ -6,15 +6,15 @@
#import "modules/std.sx";
dump :: (s: []s64, tag: string) {
dump :: (s: []i64, tag: string) {
print("{}: ", tag);
for s (v) { print("{} ", v); }
print("(len {})\n", s.len);
}
main :: () -> s32 {
xs : [6]s64 = .[10, 11, 12, 13, 14, 15];
full : []s64 = xs[0..6];
main :: () -> i32 {
xs : [6]i64 = .[10, 11, 12, 13, 14, 15];
full : []i64 = xs[0..6];
dump(full[1..=3], "1..=3"); // 11 12 13
dump(full[0<..<4], "0<..<4"); // 11 12 13

View File

@@ -0,0 +1,35 @@
// Free-function dot-calls are OPT-IN. Two opt-in spellings:
// name :: ufcs (params) { body } — the fn itself is dot-callable
// name :: ufcs target; — dot-callable (renaming) alias
// A plain fn is callable directly or via `|>` only (see 1166 for the
// rejection). Generic ufcs fns dispatch through normal monomorphization,
// and the plan-side return type binds from the receiver (structured
// params like `[]$T` included).
#import "modules/std.sx";
bump :: (x: i64) -> i64 { x + 1 }
bump2 :: ufcs (x: i64) -> i64 { x + 2 }
bump3 :: ufcs bump;
Counter :: struct { n: i64; }
inc :: ufcs (c: *Counter, by: i64) -> i64 { c.n += by; c.n }
gfirst :: ufcs (xs: []$T) -> T { xs[0] }
main :: () {
f : i64 = 40;
print("marked: {}\n", f.bump2()); // 42
print("alias: {}\n", f.bump3()); // 41
print("direct: {}\n", bump(f)); // 41 — plain fn, direct
print("pipe: {}\n", f |> bump()); // 41 — plain fn, pipe
print("marked-direct: {}\n", bump2(f)); // 42 — marked fn callable directly
c := Counter.{ n = 10 };
print("ptr-recv: {}\n", c.inc(5)); // 15 — auto address-of receiver
arr := .[7, 8, 9];
xs : []i64 = arr;
print("generic-dot: {}\n", xs.gfirst()); // 7
print("generic-direct: {}\n", gfirst(xs)); // 7 — plan types it i64, not a T stub
}

View File

@@ -0,0 +1,33 @@
// Trailing parameter defaults fill on method and ufcs dot-calls (the
// receiver-prepending dispatch paths), matching bare-call expansion (0044);
// a `#caller_location` default and a slice variadic keep their flexible
// arity under the call-arity check.
// Regression (issue 0123).
#import "modules/std.sx";
Point :: struct {
x: i64;
scaled :: (self: Point, k: i64 = 2) -> i64 { return self.x * k; }
}
bump :: ufcs (p: Point, by: i64 = 10) -> i64 { return p.x + by; }
sum_var :: (..xs: []i64) -> i64 {
t := 0;
for xs (x) { t = t + x; }
return t;
}
here :: (loc: Source_Location = #caller_location) -> i64 { return loc.line; }
main :: () {
p := Point.{ x = 5 };
print("{}\n", p.scaled()); // default filled on method dispatch
print("{}\n", p.scaled(3)); // explicit overrides
print("{}\n", p.bump()); // default filled on ufcs dispatch
print("{}\n", p.bump(1));
print("{}\n", sum_var()); // variadic: zero args
print("{}\n", sum_var(1, 2, 3)); // variadic: many args
print("{}\n", here() > 0); // #caller_location default
}

View File

@@ -0,0 +1,45 @@
// Large (64KB+) stack arrays compile and are accessed in place: `---`
// emits no initializer store, and element reads GEP the array's storage
// instead of loading the whole array as a value.
//
// Regression (issue 0124): both whole-aggregate shapes — the undef
// store from `---` and `index_get` on the loaded array value —
// scalarized into one SelectionDAG node per element and segfaulted
// `sx build` at [65536]u8.
//
// Results print via out/int_to_string: `{}` formatting would pull the
// any_to_string dispatcher, whose array arms materialize every interned
// array type BY VALUE — the separate issue 0125.
#import "modules/std.sx";
checksum :: () -> i64 {
buf : [65536]u8 = ---;
i := 0;
while i < 65536 {
buf[i] = xx (i % 251);
i += 1;
}
sum := 0;
i = 0;
while i < 65536 {
sum += xx buf[i];
i += 1;
}
return sum;
}
big :: () -> i64 {
buf : [131072]i64 = ---;
buf[0] = 11;
buf[131071] = 31;
return buf[0] + buf[131071];
}
main :: () -> i32 {
out(int_to_string(checksum()));
out("\n");
out(int_to_string(big()));
out("\n");
return 0;
}

View File

@@ -5,7 +5,7 @@ Vec4 :: struct {
Complex :: struct {
foo : enum {
S: s32;
S: i32;
B: struct {
val: string;
};

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
SPECIAL_VALUE :u8: 42;
resolve :: (x: u8) -> s32 {
resolve :: (x: u8) -> i32 {
return 12 + x;
}
@@ -9,7 +9,7 @@ Foo :: struct {
a : u2; // this will have 0 as default
b : u8 = SPECIAL_VALUE;
c : u8 = ---; // default for c is undefined
d : u8 = #run xx resolve(5); // converts s32 to u8
d : u8 = #run xx resolve(5); // converts i32 to u8
}
main :: () {
@@ -38,6 +38,6 @@ Pack :: struct {
c: u8;
d: u32;
f: u64;
v: s32;
v: i32;
x: f32;
}

View File

@@ -1,11 +1,11 @@
#import "modules/std.sx";
Point :: struct {
x, y: s32;
x, y: i32;
}
Color :: struct {
r, g, b: s32;
r, g, b: i32;
}
main :: () {

View File

@@ -2,7 +2,7 @@
Overlay :: union {
f: f32;
i: s32;
i: i32;
}
Vec2 :: union {

View File

@@ -54,12 +54,12 @@ main :: () {
// Explicit values
w :WindowFlags = .vsync | .resizable;
print("\nwindow: {}\n", w);
print("raw value: {}\n", cast(s64) w);
print("raw value: {}\n", cast(i64) w);
// Backing type on plain enums
c :Color = .blue;
print("\ncolor: {}\n", c);
print("raw: {}\n", cast(s64) c);
print("raw: {}\n", cast(i64) c);
// Bitwise ops work on plain integers too
x := 0xFF & 0x0F;

View File

@@ -3,16 +3,16 @@
#import "modules/std.sx";
g_add : s64 = 10;
g_sub : s64 = 10;
g_mul : s64 = 10;
g_div : s64 = 100;
g_mod : s64 = 10;
g_and : s64 = 0xff;
g_or : s64 = 0x0f;
g_xor : s64 = 0xff;
g_shl : s64 = 1;
g_shr : s64 = 256;
g_add : i64 = 10;
g_sub : i64 = 10;
g_mul : i64 = 10;
g_div : i64 = 100;
g_mod : i64 = 10;
g_and : i64 = 0xff;
g_or : i64 = 0x0f;
g_xor : i64 = 0xff;
g_shl : i64 = 1;
g_shr : i64 = 256;
main :: () -> void {
// += repeated: should accumulate, not reset

View File

@@ -5,7 +5,7 @@
#import "modules/std.sx";
main :: () -> void {
x : s64 = 42;
x : i64 = 42;
// OK: comparison in statement context
if x != 0 { out("ok\n"); }

View File

@@ -5,19 +5,19 @@
#import "modules/std.sx";
// 40-byte struct — triggers the bug.
// Shrink to [4]s64 (32 bytes) and the bug goes away.
// Shrink to [4]i64 (32 bytes) and the bug goes away.
BigNode :: struct {
data: [5]s64; // 40 bytes
data: [5]i64; // 40 bytes
}
Tree :: struct {
nodes: List(BigNode); // items(8) + len(8) + cap(8) = 24 bytes
generation: s64; // 8 bytes — total 32 bytes
generation: i64; // 8 bytes — total 32 bytes
}
Container :: struct {
tree: Tree;
sentinel: s64;
sentinel: i64;
do_work :: (self: *Container) {
self.tree.nodes.items = xx 0; // BUG: corrupts self.sentinel
@@ -25,7 +25,7 @@ Container :: struct {
}
main :: () -> void {
obj : *Container = xx context.allocator.alloc(size_of(Container));
obj : *Container = xx context.allocator.alloc_bytes(size_of(Container));
memset(obj, 0, size_of(Container));
obj.sentinel = 0xDEADBEEF;

View File

@@ -3,7 +3,7 @@
#import "modules/std.sx";
g_counter : s64 = 0;
g_counter : i64 = 0;
tick :: () {
g_counter += 1;
@@ -14,7 +14,7 @@ main :: () -> void {
// Test 1: += always produces 1 (BUG)
out("--- Test 1: += (broken) ---\n");
out("Expected: 1, 2, 3\n");
i : s64 = 0;
i : i64 = 0;
while i < 3 {
tick();
i += 1;

View File

@@ -3,7 +3,7 @@
#import "modules/std.sx";
VALS : [4]s32 = .[-2, -1, 42, 99];
VALS : [4]i32 = .[-2, -1, 42, 99];
main :: () {
out("VALS: ");

View File

@@ -5,7 +5,7 @@
Foo :: struct {
running: bool = true;
x: s32 = 42;
x: i32 = 42;
name: string = "default";
}

View File

@@ -10,7 +10,7 @@ ok :: () -> Handle { 0 }
g : Handle = 0;
main :: () -> s32 {
main :: () -> i32 {
g = ok();
if g == 0 then 0 else 1
}

View File

@@ -1,7 +1,7 @@
// Pre-fix: `resolveType(null)` silently returned `.s64`, so a top-level
// var without a type annotation got typed as `s64` regardless of what
// Pre-fix: `resolveType(null)` silently returned `.i64`, so a top-level
// var without a type annotation got typed as `i64` regardless of what
// the initializer was. For `g_pi := 3.14;` this meant the float literal
// was assigned to an s64 slot, producing a wrong value at runtime or
// was assigned to an i64 slot, producing a wrong value at runtime or
// the wrong codegen shape.
//
// After the fix `lowerVarDecl` at the top level mirrors the local-scope
@@ -9,11 +9,11 @@
// the initializer's type. Mirrors how `:=` already worked for locals.
#import "modules/std.sx";
g_count := 42; // inferred s64
g_pi := 3.14; // inferred f64 — used to silently become s64
g_count := 42; // inferred i64
g_pi := 3.14; // inferred f64 — used to silently become i64
g_flag := true; // inferred bool
main :: () -> s32 {
main :: () -> i32 {
print("count = {}\n", g_count);
print("pi = {}\n", g_pi);
print("flag = {}\n", g_flag);

View File

@@ -7,28 +7,28 @@
//
// This test exercises the builder directly (no `#insert`, no impl
// wiring) — three pack shapes through the same `void`-returning
// wrapper plus one non-void `s32`-returning wrapper to pin the
// wrapper plus one non-void `i32`-returning wrapper to pin the
// `return typed_fn(...)` branch. The expected output captures the
// generated source verbatim so any formatting drift surfaces here
// rather than as a downstream compile error inside the eventual
// step-5.2 impl.
#import "modules/std.sx";
#import "modules/std/objc_block.sx";
#import "modules/ffi/objc_block.sx";
preview_void :: (..$args) -> string {
return build_block_convert($args, void);
}
preview_s32 :: (..$args) -> string {
return build_block_convert($args, s32);
preview_i32 :: (..$args) -> string {
return build_block_convert($args, i32);
}
run_all :: () {
print("--- void / 0 args ---\n{}\n", preview_void());
print("--- void / bool ---\n{}\n", preview_void(true));
print("--- void / s64, string ---\n{}\n", preview_void(42, "hi"));
print("--- s32 / f64 ---\n{}\n", preview_s32(3.14));
print("--- void / i64, string ---\n{}\n", preview_void(42, "hi"));
print("--- i32 / f64 ---\n{}\n", preview_i32(3.14));
}
#run run_all();

View File

@@ -2,7 +2,7 @@
// `align_of` accept pointer (`*T`), optional (`?T`), array (`[N]T`),
// function (`(A) -> B`), and tuple (`(A, B)`) types directly. Also
// const-decl RHS aliases through the same forms (`Ptr :: *u8;` etc).
// Same shape as the existing `size_of(s32)` baseline path.
// Same shape as the existing `size_of(i32)` baseline path.
#import "modules/std.sx";
@@ -10,9 +10,9 @@
Ptr :: *u8;
Maybe :: ?u8;
Arr :: [3]u8;
Cb :: (s32) -> s32;
Cb :: (i32) -> i32;
main :: () -> s32 {
main :: () -> i32 {
// Direct: parser fix for *T, ?T + existing [N]T path.
print("size_of(*u8) = {}\n", size_of(*u8));
print("align_of(*u8) = {}\n", align_of(*u8));
@@ -20,10 +20,10 @@ main :: () -> s32 {
print("size_of([3]u8) = {}\n", size_of([3]u8));
// Function-type literal in expression position.
print("size_of((s32)->s32) = {}\n", size_of((s32) -> s32));
print("size_of((i32)->i32) = {}\n", size_of((i32) -> i32));
// Tuple literal reinterpreted as tuple type at the type-demanding site.
print("size_of((s32, s32)) = {}\n", size_of((s32, s32)));
print("size_of((i32, i32)) = {}\n", size_of((i32, i32)));
// Aliases.
print("size_of(Ptr) = {}\n", size_of(Ptr));

View File

@@ -1,19 +1,19 @@
// Type alias resolution through `size_of` / `align_of` — a const-decl
// alias (`MyInt :: s32;`, `MyChain :: MyInt;`, `WideAlias :: Wide;`)
// alias (`MyInt :: i32;`, `MyChain :: MyInt;`, `WideAlias :: Wide;`)
// resolves through `type_alias_map` when used as a `$T: Type` argument.
// Covers chains and struct-name aliases, not just structural-type
// aliases (those land in `examples/182-compound-type-in-expression.sx`).
#import "modules/std.sx";
MyInt :: s32;
MyInt :: i32;
MyChain :: MyInt;
Wide :: struct { a: s64; b: s64; }
Wide :: struct { a: i64; b: i64; }
WideAlias :: Wide;
main :: () -> s32 {
print("direct s32: {}\n", size_of(s32));
print("alias s32: {}\n", size_of(MyInt));
print("chain s32: {}\n", size_of(MyChain));
main :: () -> i32 {
print("direct i32: {}\n", size_of(i32));
print("alias i32: {}\n", size_of(MyInt));
print("chain i32: {}\n", size_of(MyChain));
print("align alias: {}\n", align_of(MyInt));
print("align chain: {}\n", align_of(MyChain));
print("size struct-alias: {}\n", size_of(WideAlias));

View File

@@ -13,11 +13,11 @@
// and matches the caller's two-register pass on AArch64.
#import "modules/std.sx";
#import "modules/std/objc_block.sx";
#import "modules/ffi/objc_block.sx";
g_s: string = "";
main :: () -> s32 {
main :: () -> i32 {
cl := (s: string) => { g_s = s; };
b : Block = xx cl;
invoke_fn : (*Block, string) -> void callconv(.c) = xx b.invoke;

View File

@@ -12,7 +12,7 @@
// ── Test fixtures ─────────────────────────────────────────────
Point :: struct { x: s32; y: s32; }
Point :: struct { x: i32; y: i32; }
Color :: enum { red; green; blue; }
@@ -24,7 +24,7 @@ identity :: ($T: Type, val: T) -> T => val;
// time fold via type_eq → const_bool → inline-if folds the
// branch away).
describe :: ($T: Type) -> string {
inline if type_eq(T, s64) { return "int64"; }
inline if type_eq(T, i64) { return "int64"; }
inline if type_eq(T, string) { return "text"; }
inline if type_eq(T, bool) { return "boolean"; }
return "other";
@@ -34,7 +34,7 @@ describe :: ($T: Type) -> string {
type_list :: (..$args) -> string {
list := $args;
s := "[";
i : s64 = 0;
i : i64 = 0;
while i < list.len {
if i > 0 { s = concat(s, ", "); }
s = concat(s, type_name(list[i]));
@@ -46,21 +46,21 @@ type_list :: (..$args) -> string {
// Type stored in a struct field.
TypeHolder :: struct { t: Type; }
main :: () -> s32 {
main :: () -> i32 {
// ── 1. Type literal equality ────────────────────────────
print("=== 1. literal == ===\n");
print("s64 == s64: {}\n", s64 == s64);
print("s64 == string: {}\n", s64 == string);
print("i64 == i64: {}\n", i64 == i64);
print("i64 == string: {}\n", i64 == string);
print("*u8 == *u8: {}\n", *u8 == *u8);
print("?s64 == ?s64: {}\n", ?s64 == ?s64);
print("?s64 == ?s32: {}\n", ?s64 == ?s32);
print("?i64 == ?i64: {}\n", ?i64 == ?i64);
print("?i64 == ?i32: {}\n", ?i64 == ?i32);
// ── 2. type_of(value) ───────────────────────────────────
print("=== 2. type_of(value) == T ===\n");
a : s64 = 42;
a : i64 = 42;
b : f64 = 3.14;
s : string = "hi";
print("type_of(a) == s64: {}\n", type_of(a) == s64);
print("type_of(a) == i64: {}\n", type_of(a) == i64);
print("type_of(b) == f64: {}\n", type_of(b) == f64);
print("type_of(s) == string: {}\n", type_of(s) == string);
print("type_of(a) == f64: {}\n", type_of(a) == f64);
@@ -77,7 +77,7 @@ main :: () -> s32 {
// ── 4. type_name on literals + variables ────────────────
print("=== 4. type_name ===\n");
print("type_name(s64): {}\n", type_name(s64));
print("type_name(i64): {}\n", type_name(i64));
print("type_name(*u8): {}\n", type_name(*u8));
print("type_name(Point): {}\n", type_name(Point));
print("type_name(Color): {}\n", type_name(Color));
@@ -86,39 +86,39 @@ main :: () -> s32 {
// ── 5. Print Type values directly ───────────────────────
print("=== 5. print Type values ===\n");
print("literal: {}\n", s64);
print("literal: {}\n", i64);
t = string;
print("var: {}\n", t);
print("type_of(b): {}\n", type_of(b));
// ── 6. Generic dispatch via $T: Type ────────────────────
print("=== 6. generic dispatch ===\n");
print("describe(s64): {}\n", describe(s64));
print("describe(i64): {}\n", describe(i64));
print("describe(string): {}\n", describe(string));
print("describe(bool): {}\n", describe(bool));
print("describe(f64): {}\n", describe(f64));
// ── 7. identity(T, val) ─────────────────────────────────
print("=== 7. identity($T, val) ===\n");
print("identity(s64, 7): {}\n", identity(s64, 7));
print("identity(i64, 7): {}\n", identity(i64, 7));
print("identity(string, hi): {}\n", identity(string, "hi"));
print("identity(bool, true): {}\n", identity(bool, true));
// ── 8. Comptime-generated struct (Wrap($T)) ─────────────
print("=== 8. Wrap($T) ===\n");
w_int := Wrap(s64).{ v = 42 };
w_int := Wrap(i64).{ v = 42 };
w_str := Wrap(string).{ v = "wrapped" };
print("Wrap(s64).v: {}\n", w_int.v);
print("Wrap(i64).v: {}\n", w_int.v);
print("Wrap(string).v: {}\n", w_str.v);
// ── 9. Reflection builtins on Types ─────────────────────
print("=== 9. reflection on Type ===\n");
print("size_of(s64): {}\n", size_of(s64));
print("size_of(i64): {}\n", size_of(i64));
print("size_of(*u8): {}\n", size_of(*u8));
print("align_of(f64): {}\n", align_of(f64));
print("field_count(Point): {}\n", field_count(Point));
print("type_eq(s64, s64): {}\n", type_eq(s64, s64));
print("type_eq(s64, string): {}\n", type_eq(s64, string));
print("type_eq(i64, i64): {}\n", type_eq(i64, i64));
print("type_eq(i64, string): {}\n", type_eq(i64, string));
// ── 10. Type pack (..$args) walking ─────────────────────
print("=== 10. ..$args walking ===\n");
@@ -129,15 +129,15 @@ main :: () -> s32 {
// ── 11. Type in struct field ────────────────────────────
print("=== 11. Type in struct field ===\n");
h := TypeHolder.{ t = s64 };
print("h.t == s64: {}\n", h.t == s64);
h := TypeHolder.{ t = i64 };
print("h.t == i64: {}\n", h.t == i64);
print("h.t == string: {}\n", h.t == string);
print("type_name(h.t): {}\n", type_name(h.t));
// ── 12. Compound type literals ──────────────────────────
print("=== 12. compound literals ===\n");
print("type_name(*Point): {}\n", type_name(*Point));
print("type_name([4]s32): {}\n", type_name([4]s32));
print("type_name([4]i32): {}\n", type_name([4]i32));
print("type_name([]bool): {}\n", type_name([]bool));
print("type_name(?f64): {}\n", type_name(?f64));
@@ -146,13 +146,13 @@ main :: () -> s32 {
// ** stdout **
// === 1. literal == ===
// s64 == s64: true
// s64 == string: false
// i64 == i64: true
// i64 == string: false
// *u8 == *u8: true
// ?s64 == ?s64: true
// ?s64 == ?s32: false
// ?i64 == ?i64: true
// ?i64 == ?i32: false
// === 2. type_of(value) == T ===
// type_of(a) == s64: true
// type_of(a) == i64: true
// type_of(b) == f64: true
// type_of(s) == string: true
// type_of(a) == f64: false
@@ -162,45 +162,45 @@ main :: () -> s32 {
// after reassign t == string: true
// t == bool: true
// === 4. type_name ===
// type_name(s64): s64
// type_name(i64): i64
// type_name(*u8): *u8
// type_name(Point): Point
// type_name(Color): Color
// type_name(t): f64
// === 5. print Type values ===
// literal: s64
// literal: i64
// var: string
// type_of(b): f64
// === 6. generic dispatch ===
// describe(s64): int64
// describe(i64): int64
// describe(string): text
// describe(bool): boolean
// describe(f64): other
// === 7. identity($T, val) ===
// identity(s64, 7): 7
// identity(i64, 7): 7
// identity(string, hi): hi
// identity(bool, true): true
// === 8. Wrap($T) ===
// Wrap(s64).v: 42
// Wrap(i64).v: 42
// Wrap(string).v: wrapped
// === 9. reflection on Type ===
// size_of(s64): 8
// size_of(i64): 8
// size_of(*u8): 8
// align_of(f64): 8
// field_count(Point): 2
// type_eq(s64, s64): true
// type_eq(s64, string): false
// type_eq(i64, i64): true
// type_eq(i64, string): false
// === 10. ..$args walking ===
// type_list(): []
// type_list(1): [s64]
// type_list(1, "x"): [s64, string]
// type_list(1): [i64]
// type_list(1, "x"): [i64, string]
// type_list(true, 3.14): [bool, f64]
// === 11. Type in struct field ===
// h.t == s64: true
// h.t == i64: true
// h.t == string: false
// type_name(h.t): s64
// type_name(h.t): i64
// === 12. compound literals ===
// type_name(*Point): *Point
// type_name([4]s32): [4]s32
// type_name([4]i32): [4]i32
// type_name([]bool): []bool
// type_name(?f64): ?f64

View File

@@ -2,16 +2,16 @@
// return, and operators. Regression for the tuple-construction bug where
// an inferred `:=` tuple literal lowered its element values under the
// enclosing fn's (narrower) return `target_type`, mismatching the
// independently-inferred s64 field types and yielding garbage on read.
// independently-inferred i64 field types and yielding garbage on read.
#import "modules/std.sx";
Box :: struct { xs: (s32, s32); }
Box :: struct { xs: (i32, i32); }
swap :: (a: s64, b: s64) -> (s64, s64) { (b, a) }
fst :: (t: (s64, s64)) -> s64 { t.0 }
swap :: (a: i64, b: i64) -> (i64, i64) { (b, a) }
fst :: (t: (i64, i64)) -> i64 { t.0 }
main :: () -> s32 {
main :: () -> i32 {
// Inferred positional tuple + numeric field access.
pair := (40, 2);
print("pair {} {}\n", pair.0, pair.1);
@@ -21,8 +21,8 @@ main :: () -> s32 {
print("named {} {} {}\n", named.x, named.0, named.1);
// Element into a typed local (access path, not just print).
a : s64 = pair.0;
b : s64 = pair.1;
a : i64 = pair.0;
b : i64 = pair.1;
print("locals {} {}\n", a, b);
// Tuple-typed struct field: store a tuple value, read both elements.
@@ -46,9 +46,9 @@ main :: () -> s32 {
print("mem {}\n", 3 in (1, 2, 3));
print("lex {}\n", (1, 2) < (1, 3));
// Mixed-size fields: a tuple with both an s64 and a string (16-byte fat
// Mixed-size fields: a tuple with both an i64 and a string (16-byte fat
// pointer). Field types are tracked per-position, so reading each back is
// typed correctly (s64 prints as a number, string as text).
// typed correctly (i64 prints as a number, string as text).
mixed := (42, "hi");
print("mixed {} {}\n", mixed.0, mixed.1);
0

View File

@@ -7,15 +7,15 @@
#import "modules/std.sx";
main :: () -> s32 {
main :: () -> i32 {
// Positional element assignment.
a : (s32, string) = ---;
a : (i32, string) = ---;
a.0 = 11;
a.1 = "x";
print("a: {} {}\n", a.0, a.1);
// Named tuple: write + read by name, and read by position.
p : (x: s32, y: string) = ---;
p : (x: i32, y: string) = ---;
p.x = 22;
p.y = "y";
print("p: x={} y={} .0={}\n", p.x, p.y, p.0);

View File

@@ -1,10 +1,10 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
Point :: struct { x, y: s32; }
Point :: struct { x, y: i32; }
Color :: enum { red; green; blue; }
@@ -16,7 +16,7 @@ Shape :: enum {
Overlay :: union {
f: f32;
i: s32;
i: i32;
}
Vec2 :: union {
@@ -25,27 +25,27 @@ Vec2 :: union {
}
Defaults :: struct {
a: s32;
b: s32 = 99;
c: s32 = ---;
a: i32;
b: i32 = 99;
c: i32 = ---;
}
MyFloat :: f64;
Status :: enum u8 { ok; err; timeout; }
add :: (a: s32, b: s32) -> s32 { a + b }
add :: (a: i32, b: i32) -> i32 { a + b }
mul :: (a: s32, b: s32) -> s32 { a * b }
mul :: (a: i32, b: i32) -> i32 { a * b }
vec3 :: (x: f32, y: f32, z: f32) -> Vector(3, f32) {
.[x, y, z]
}
// Global variable for address-of test
g_smoke_val : s32 = 42;
g_smoke_val : i32 = 42;
write_to_ptr :: (p: *s32) {
write_to_ptr :: (p: *i32) {
p.* = 99;
}
@@ -57,15 +57,15 @@ main :: () {
print("=== 3. Types ===\n");
// Primitive types
v_s8 : s8 = 127;
v_s16 : s16 = 32000;
v_s32 : s32 = 100000;
v_i8 : i8 = 127;
v_i16 : i16 = 32000;
v_i32 : i32 = 100000;
v_u8 : u8 = 255;
v_u16 : u16 = 65000;
v_u32 : u32 = 4000000;
print("s8: {}\n", v_s8);
print("s16: {}\n", v_s16);
print("s32: {}\n", v_s32);
print("i8: {}\n", v_i8);
print("i16: {}\n", v_i16);
print("i32: {}\n", v_i32);
print("u8: {}\n", v_u8);
print("u16: {}\n", v_u16);
print("u32: {}\n", v_u32);
@@ -88,8 +88,8 @@ main :: () {
print("struct-named: {}\n", p3);
// Shorthand (variable name = field name)
x : s32 = 5;
y : s32 = 6;
x : i32 = 5;
y : i32 = 6;
p4 := Point.{ x, y };
print("struct-shorthand: {}\n", p4);
@@ -226,7 +226,7 @@ main :: () {
o : Overlay = ---;
o.f = 3.14;
print("union-f: {}\n", o.f);
// Type punning — read same bits as s32
// Type punning — read same bits as i32
print("union-i: {}\n", o.i);
// Union member promotion
@@ -237,22 +237,22 @@ main :: () {
print("promoted-data0: {}\n", uv.data[0]);
// --- Arrays ---
arr : [5]s32 = .[10, 20, 30, 40, 50];
arr : [5]i32 = .[10, 20, 30, 40, 50];
print("arr[2]: {}\n", arr[2]);
print("arr.len: {}\n", arr.len);
// Array element assignment
aa : [3]s32 = .[1, 2, 3];
aa : [3]i32 = .[1, 2, 3];
aa[1] = 99;
print("arr-assign: {}\n", aa);
// --- Slices ---
sl : []s32 = .[1, 2, 3, 4, 5];
sl : []i32 = .[1, 2, 3, 4, 5];
print("sl[0]: {}\n", sl[0]);
print("sl.len: {}\n", sl.len);
// Slice element write
sla : []s32 = .[10, 20, 30];
sla : []i32 = .[10, 20, 30];
sla[1] = 55;
print("sl-assign: {}\n", sla);
@@ -265,7 +265,7 @@ main :: () {
print("tail: {}\n", tail);
// Slice of slice
sos : []s32 = .[10, 20, 30, 40, 50];
sos : []i32 = .[10, 20, 30, 40, 50];
mid := sos[1..4];
inner := mid[0..2];
print("slice-of-slice: {}\n", inner);
@@ -289,18 +289,18 @@ main :: () {
print("auto-deref: {}\n", ptr.x);
// Many-pointer
mp : [*]s32 = @arr[0];
mp : [*]i32 = @arr[0];
print("mp[0]: {}\n", mp[0]);
print("mp[3]: {}\n", mp[3]);
// Many-pointer write
mpw : [5]s32 = .[10, 20, 30, 40, 50];
mpw_ptr : [*]s32 = @mpw[0];
mpw : [5]i32 = .[10, 20, 30, 40, 50];
mpw_ptr : [*]i32 = @mpw[0];
mpw_ptr[2] = 99;
print("mp-write: {}\n", mpw[2]);
// Pointer-null comparison
np : *s32 = null;
np : *i32 = null;
print("ptr==null: {}\n", np == null);
print("ptr!=null: {}\n", np != null);
np2 := @pv.x;
@@ -309,13 +309,13 @@ main :: () {
// Pointer to nested struct field
Inner3 :: struct { a: f32; b: f32; c: f32; }
Outer3 :: struct { key: s32; inner: Inner3; }
Outer3 :: struct { key: i32; inner: Inner3; }
out3 := Outer3.{ key = 42, inner = Inner3.{ a = 1.0, b = 2.0, c = 3.0 } };
ip3 := @out3.inner;
print("ptr-nested-field: {} {} {}\n", ip3.a, ip3.b, ip3.c);
// Store to many-pointer field must not corrupt adjacent memory
MpHolder :: struct { items: [*]s64; sentinel: s64; }
MpHolder :: struct { items: [*]i64; sentinel: i64; }
mph := MpHolder.{ items = xx 0, sentinel = 42 };
mph.items = xx 0;
print("mp-store-sentinel: {}\n", mph.sentinel);

View File

@@ -1,8 +1,8 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
Perms :: enum flags { read; write; execute; }
@@ -42,12 +42,12 @@ main :: () {
print("flags-all: {}\n", pall);
// Cast to int
print("flags-raw: {}\n", cast(s64) perm);
print("flags-raw: {}\n", cast(i64) perm);
// Flags with explicit values
wf : WindowFlags = .vsync | .resizable;
print("flags-explicit: {}\n", wf);
print("flags-explicit-raw: {}\n", cast(s64) wf);
print("flags-explicit-raw: {}\n", cast(i64) wf);
// --- Multi-target assignment (swap) ---
print("--- swap ---\n");
@@ -62,7 +62,7 @@ main :: () {
// Array element swap
{
sarr : [3]s64 = .[1, 2, 3];
sarr : [3]i64 = .[1, 2, 3];
sarr[0], sarr[2] = sarr[2], sarr[0];
print("arr swap: {} {}\n", sarr[0], sarr[2]);
}
@@ -87,7 +87,7 @@ main :: () {
// Destructure from function return
{
dswap :: (a: s64, b: s64) -> (s64, s64) { (b, a) }
dswap :: (a: i64, b: i64) -> (i64, i64) { (b, a) }
dx, dy := dswap(1, 2);
print("fn: {} {}\n", dx, dy);
}

View File

@@ -1,8 +1,8 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
@@ -16,9 +16,9 @@ main :: () {
ca_a += ca_b;
print("f64+=f32: {}\n", ca_a);
ca_c : s64 = 100;
ca_d : s32 = 7;
ca_c : i64 = 100;
ca_d : i32 = 7;
ca_c -= ca_d;
print("s64-=s32: {}\n", ca_c);
print("i64-=i32: {}\n", ca_c);
}
}

View File

@@ -1,10 +1,10 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
Point :: struct { x, y: s32; }
Point :: struct { x, y: i32; }
main :: () {

View File

@@ -1,4 +1,4 @@
// A local declared with a reserved/builtin type-name spelling (`s2` is the
// A local declared with a reserved/builtin type-name spelling (`i2` is the
// arbitrary-width `sN` integer type) is rejected at the declaration site.
// Previously such a name parsed as a `.type_expr`, so address-of sites
// mis-lowered it (load-by-value to a `ptr` param → LLVM verifier abort, or a
@@ -6,8 +6,8 @@
// error at the declaration; exit 1.
#import "modules/std.sx";
main :: () -> s32 {
s2 := 42;
print("s2: {}\n", s2);
main :: () -> i32 {
i2 := 42;
print("i2: {}\n", i2);
return 0;
}

View File

@@ -1,8 +1,8 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
@@ -11,7 +11,7 @@ main :: () {
// ========================================================
print("=== 23. Nested Arrays ===\n");
{
matrix : [2][3]s32 = .[ .[1, 2, 3], .[4, 5, 6] ];
matrix : [2][3]i32 = .[ .[1, 2, 3], .[4, 5, 6] ];
print("m[0][0]: {}\n", matrix[0][0]);
print("m[0][2]: {}\n", matrix[0][2]);
print("m[1][0]: {}\n", matrix[1][0]);

View File

@@ -1,33 +1,33 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
// === 26. #using struct composition ===
print("=== 26. #using ===\n");
{
UBase :: struct { x: s32; y: s32; }
UExt :: struct { #using UBase; z: s32; }
UBase :: struct { x: i32; y: i32; }
UExt :: struct { #using UBase; z: i32; }
e := UExt.{ x = 1, y = 2, z = 3 };
print("using-x: {}\n", e.x);
print("using-y: {}\n", e.y);
print("using-z: {}\n", e.z);
// #using in middle position
UHeader :: struct { version: s32; }
UPacket :: struct { id: s32; #using UHeader; payload: s32; }
UHeader :: struct { version: i32; }
UPacket :: struct { id: i32; #using UHeader; payload: i32; }
p := UPacket.{ id = 10, version = 42, payload = 99 };
print("pkt-id: {}\n", p.id);
print("pkt-ver: {}\n", p.version);
print("pkt-pay: {}\n", p.payload);
// Multiple #using
UPos :: struct { px: s32; py: s32; }
UCol :: struct { r: s32; g: s32; }
USprite :: struct { #using UPos; #using UCol; scale: s32; }
UPos :: struct { px: i32; py: i32; }
UCol :: struct { r: i32; g: i32; }
USprite :: struct { #using UPos; #using UCol; scale: i32; }
s := USprite.{ px = 10, py = 20, r = 255, g = 128, scale = 1 };
print("sprite-px: {}\n", s.px);
print("sprite-r: {}\n", s.r);

View File

@@ -1,8 +1,8 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
@@ -20,7 +20,7 @@ main :: () {
single := (42,);
print("{}\n", single.0);
zeroed : (s32, s32) = ---;
zeroed : (i32, i32) = ---;
print("{}\n", zeroed.0);
print("{}\n", zeroed.1);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
// ============================================================
// Struct constants test

View File

@@ -1,31 +1,31 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
pkg :: #import "tests/fixtures/testpkg";
Point :: struct { x, y: s32; }
Point :: struct { x, y: i32; }
add :: (a: s32, b: s32) -> s32 { a + b }
add :: (a: i32, b: i32) -> i32 { a + b }
Counter :: protocol {
inc :: ();
get :: () -> s32;
get :: () -> i32;
}
Summable :: protocol {
sum :: () -> s32;
sum :: () -> i32;
}
SimpleCounter :: struct { val: s32; }
SimpleCounter :: struct { val: i32; }
impl Counter for SimpleCounter {
inc :: (self: *SimpleCounter) { self.val += 1; }
get :: (self: *SimpleCounter) -> s32 { self.val }
get :: (self: *SimpleCounter) -> i32 { self.val }
}
impl Summable for Point {
sum :: (self: *Point) -> s32 { self.x + self.y }
sum :: (self: *Point) -> i32 { self.x + self.y }
}
// Phase 2: #inline protocol for dynamic dispatch
@@ -37,7 +37,7 @@ Pair :: struct ($T: Type) {
}
impl Summable for Pair($T) {
sum :: (self: *Pair(T)) -> s32 {
sum :: (self: *Pair(T)) -> i32 {
xx self.a + xx self.b
}
}
@@ -46,10 +46,10 @@ impl Summable for Pair($T) {
// Init block test struct
Builder :: struct {
total: s32;
count: s32;
total: i32;
count: i32;
add :: (self: *Builder, val: s32) {
add :: (self: *Builder, val: i32) {
self.total += val;
self.count += 1;
}
@@ -104,7 +104,7 @@ main :: () {
// IB5: init block + auto type erasure combined
{
use_counter :: (c: Counter) -> s32 { c.inc(); c.inc(); c.get() }
use_counter :: (c: Counter) -> i32 { c.inc(); c.inc(); c.get() }
result := use_counter(SimpleCounter.{ val = 0 } {
self.val = 50;
});
@@ -138,10 +138,10 @@ main :: () {
// SM2: Shorthand in variable declaration with explicit type
{
Pair :: struct {
a: s64;
b: s64;
a: i64;
b: i64;
make :: (a: s64, b: s64) -> Pair {
make :: (a: i64, b: i64) -> Pair {
Pair.{ a = a, b = b }
}
}
@@ -188,13 +188,13 @@ main :: () {
c : usize = a + 8;
print("usize+8: {}\n", c);
// coercion from s32
x : s32 = 10;
// coercion from i32
x : i32 = 10;
y : usize = xx x;
print("s32->usize: {}\n", y);
print("i32->usize: {}\n", y);
// coercion to s64
z : s64 = xx a;
print("usize->s64: {}\n", z);
// coercion to i64
z : i64 = xx a;
print("usize->i64: {}\n", z);
}
}

View File

@@ -1,6 +1,6 @@
// Forward identifier type alias — an alias whose target is declared LATER
// in the file resolves the same as an ordered one. `MyChain :: MyInt;`
// appears before `MyInt :: s32;`, yet `MyChain` resolves to `s32` and a
// appears before `MyInt :: i32;`, yet `MyChain` resolves to `i32` and a
// forward chain (`A :: B; B :: C; C :: u8;`) converges too.
// Regression (issue 0069): the scan only registered identifier aliases whose
// target was already known, so a forward alias was falsely flagged
@@ -8,17 +8,17 @@
#import "modules/std.sx";
MyChain :: MyInt;
MyInt :: s32;
MyInt :: i32;
A :: B;
B :: C;
C :: u8;
main :: () -> s32 {
main :: () -> i32 {
v: MyChain = 7;
n: A = 3;
print("chain s32: {}\n", size_of(MyChain));
print("chain i32: {}\n", size_of(MyChain));
print("forward u8: {}\n", size_of(A));
print("v + n: {}\n", v + cast(s32) n);
print("v + n: {}\n", v + cast(i32) n);
return v;
}

View File

@@ -1,6 +1,6 @@
// Forward identifier type alias as a TOP-LEVEL annotation — a global var
// and a typed module constant whose annotation is a forward alias
// (`A :: B; B :: s32;`) resolve to the alias target, the same as the
// (`A :: B; B :: i32;`) resolve to the alias target, the same as the
// ordered form, instead of a fabricated stub.
// Regression (issue 0070): top-level global / typed-const annotations were
// resolved inside the scan loop BEFORE the forward-alias fixpoint ran, so
@@ -10,12 +10,12 @@
#import "modules/std.sx";
A :: B;
B :: s32;
B :: i32;
g : A = 7;
K : A : 35;
main :: () -> s32 {
main :: () -> i32 {
print("global g: {}\n", g);
print("const K: {}\n", K);
return g + K;

View File

@@ -1,18 +1,18 @@
// Top-level global initialized from a module constant copies the constant's
// value (not a silent zero). `K : A : 42; g : A = K;` resolves the forward
// alias `A` to `s32` and materializes `g`'s static initializer from `K`.
// alias `A` to `i32` and materializes `g`'s static initializer from `K`.
// Regression (issue 0071): `registerTopLevelGlobal`'s init_val switch only
// handled literals/array/struct literals; an identifier initializer fell
// through to a null payload and the global silently zero-initialized.
#import "modules/std.sx";
A :: B;
B :: s32;
B :: i32;
K : A : 42;
g : A = K;
main :: () -> s32 {
main :: () -> i32 {
print("g={}\n", g);
return g;
}

View File

@@ -7,14 +7,14 @@
// type-named identifiers).
#import "modules/std.sx";
Hasher :: struct { total: s64 = 0; count: s64 = 0; }
Hasher :: struct { total: i64 = 0; count: i64 = 0; }
update :: (self: *Hasher, n: s64) {
update :: ufcs (self: *Hasher, n: i64) {
self.total += n;
self.count += 1;
}
main :: () -> s32 {
main :: () -> i32 {
hasher := Hasher.{ total = 0, count = 0 };
update(@hasher, 10); // explicit address-of receiver
hasher.update(20); // autoref receiver

View File

@@ -9,14 +9,14 @@
#import "modules/std.sx";
g : [3]s64 = .[10, 20, 30];
g : [3]i64 = .[10, 20, 30];
Pair :: struct { a: s64; b: s64; }
Pair :: struct { a: i64; b: i64; }
gp : [2]Pair = .[ .{ a = 1, b = 2 }, .{ a = 3, b = 4 } ];
grid : [2][3]s64 = .[ .[0, 0, 0], .[0, 0, 0] ];
grid : [2][3]i64 = .[ .[0, 0, 0], .[0, 0, 0] ];
write_global :: (i: s64, v: s64) { g[i] = v; }
write_global :: (i: i64, v: i64) { g[i] = v; }
main :: () {
// Scalar global array — const index.
@@ -39,7 +39,7 @@ main :: () {
print("gp[0]={},{}\n", gp[0].a, gp[0].b); // 10,20
print("gp[j]={},{}\n", gp[j].a, gp[j].b); // 30,40
// Nested-array global — element is [3]s64, recursive indexed lvalue.
// Nested-array global — element is [3]i64, recursive indexed lvalue.
grid[1][2] = 7;
r := 0;
grid[r][0] = 5;

View File

@@ -11,8 +11,8 @@
#import "modules/std.sx";
Pair :: struct { a: s64; b: s64; }
WithArr :: struct { id: s64; xs: [3]s64; }
Pair :: struct { a: i64; b: i64; }
WithArr :: struct { id: i64; xs: [3]i64; }
// global array of struct literals
pairs : [2]Pair = .[ .{ a = 1, b = 2 }, .{ a = 3, b = 4 } ];

View File

@@ -7,19 +7,19 @@
// `.null_literal` arm, so a `null` in a pointer field made the whole aggregate
// look non-constant and the global was rejected with "must be initialized by a
// compile-time constant". The fix serializes a null literal to a constant zero
// pointer (the same way a top-level pointer global `p : *s64 = null;` does)
// pointer (the same way a top-level pointer global `p : *i64 = null;` does)
// while still rejecting genuinely non-constant fields (see diagnostics 1126).
#import "modules/std.sx";
Box :: struct { p: *s64; marker: s64; }
Inner :: struct { q: *s64; tag: s64; }
Outer :: struct { inner: Inner; label: s64; }
Box :: struct { p: *i64; marker: i64; }
Inner :: struct { q: *i64; tag: i64; }
Outer :: struct { inner: Inner; label: i64; }
// array-of-struct with null pointer fields + scalar neighbors
boxes : [2]Box = .[ .{ p = null, marker = 11 }, .{ p = null, marker = 22 } ];
// global array of all-null pointers
ptrs : [3]*s64 = .[ null, null, null ];
ptrs : [3]*i64 = .[ null, null, null ];
// nested: struct containing a struct with a null pointer field
nested : [2]Outer = .[
.{ inner = .{ q = null, tag = 1 }, label = 100 },

View File

@@ -19,7 +19,7 @@ Color :: enum u8 { red; green; blue; }
Code :: enum u16 { ok :: 200; not_found :: 404; teapot :: 418; }
Pair :: struct { a: Color; b: Color; }
Row :: struct { status: Code; pad: s64; }
Row :: struct { status: Code; pad: i64; }
// scalar enum global
chosen : Color = .green;

View File

@@ -15,18 +15,18 @@
N :: 4;
M :: 3;
P :: struct { x: s64; y: s64; }
P :: struct { x: i64; y: i64; }
// Type aliases whose dimension is the named const N (stateless registration).
Arr :: [N]s64;
Arr :: [N]i64;
SArr :: [N]string;
// Inline union field with a named-const dimension (stateless registration).
U :: union { a: [N]s64; tag: s64; }
U :: union { a: [N]i64; tag: i64; }
main :: () {
// Scalar elements (direct local): store then read back.
a : [N]s64 = ---;
a : [N]i64 = ---;
a[0] = 7;
a[3] = 42;
print("scalar a0={} a3={}\n", a[0], a[3]);
@@ -35,7 +35,7 @@ main :: () {
s : [N]string = ---;
s[0] = "hi";
s[1] = "yo";
print("string s0={} s1={}\n", s[0], s[1]);
print("string i0={} i1={}\n", s[0], s[1]);
// Struct elements (direct local).
ps : [N]P = ---;
@@ -43,7 +43,7 @@ main :: () {
ps[2] = P.{ x = 5, y = 6 };
print("struct p0x={} p0y={} p2x={}\n", ps[0].x, ps[0].y, ps[2].x);
// Type-alias dimension (scalar): same layout as the direct `[N]s64`.
// Type-alias dimension (scalar): same layout as the direct `[N]i64`.
aa : Arr = ---;
aa[0] = 11;
aa[3] = 99;
@@ -53,10 +53,10 @@ main :: () {
sa : SArr = ---;
sa[0] = "al";
sa[2] = "ok";
print("alias s0={} s2={}\n", sa[0], sa[2]);
print("alias i0={} i2={}\n", sa[0], sa[2]);
// Nested fixed array `[N][M]s64`: both dimensions are named consts.
grid : [N][M]s64 = ---;
// Nested fixed array `[N][M]i64`: both dimensions are named consts.
grid : [N][M]i64 = ---;
grid[0][0] = 1;
grid[3][2] = 8;
print("nested g00={} g32={}\n", grid[0][0], grid[3][2]);

View File

@@ -7,14 +7,14 @@
// bytes and returned garbage (0).
#import "modules/std.sx";
count_nope :: (xs: []string) -> s64 {
count_nope :: (xs: []string) -> i64 {
n := 0;
i := 0;
while i < xs.len { if xs[i] == "nope" { n += 1; } i += 1; }
return n;
}
sum :: (xs: []s64) -> s64 {
sum :: (xs: []i64) -> i64 {
s := 0;
i := 0;
while i < xs.len { s += xs[i]; i += 1; }
@@ -29,6 +29,6 @@ main :: () {
// numeric slice: direct literal vs local-bound — both sum to 100.
print("num direct={}\n", sum(.[10, 20, 30, 40]));
nums : []s64 = .[10, 20, 30, 40];
nums : []i64 = .[10, 20, 30, 40];
print("num local={}\n", sum(nums));
}

View File

@@ -1,5 +1,5 @@
// A nested array/slice literal (`.[.[1, 2], .[3, 4]]`) at an expected slice-of-
// slices type (`[][]s64`) materializes each inner `[N]T` literal as a real `[]T`
// slices type (`[][]i64`) materializes each inner `[N]T` literal as a real `[]T`
// slice, so indexing the inner slice in the callee reads element contents
// correctly — for both the local-bound form and the direct-call-argument form.
// Regression (issue 0085): inner literals were appended as raw `[N]T` arrays
@@ -9,7 +9,7 @@
// recurses with the nesting, so every level coerces.
#import "modules/std.sx";
sum_nested :: (xss: [][]s64) -> s64 {
sum_nested :: (xss: [][]i64) -> i64 {
total := 0;
i := 0;
while i < xss.len {
@@ -20,7 +20,7 @@ sum_nested :: (xss: [][]s64) -> s64 {
return total;
}
count_x :: (xss: [][]string) -> s64 {
count_x :: (xss: [][]string) -> i64 {
n := 0;
i := 0;
while i < xss.len {
@@ -32,8 +32,8 @@ count_x :: (xss: [][]string) -> s64 {
}
main :: () {
// numeric [][]s64 — local-bound vs direct-arg both sum to 10.
local : [][]s64 = .[.[1, 2], .[3, 4]];
// numeric [][]i64 — local-bound vs direct-arg both sum to 10.
local : [][]i64 = .[.[1, 2], .[3, 4]];
print("num local={}\n", sum_nested(local));
print("num direct={}\n", sum_nested(.[.[1, 2], .[3, 4]]));

View File

@@ -1,5 +1,5 @@
// A named-const array dimension lays out identically whether the const is
// TYPED (`N : s64 : 16`) or untyped (`N :: 16`), used DIRECTLY (`a : [N]T`) or
// TYPED (`N : i64 : 16`) or untyped (`N :: 16`), used DIRECTLY (`a : [N]T`) or
// through a type alias (`Arr :: [N]T`), and regardless of whether the const is
// declared before or after the alias that consumes it.
//
@@ -13,22 +13,22 @@
// stateful and stateless paths share one dimension resolver.
#import "modules/std.sx";
NT : s64 : 8; // typed const used as a dimension
NT : i64 : 8; // typed const used as a dimension
P :: struct { x: s64; y: s64; }
P :: struct { x: i64; y: i64; }
// Type aliases whose dimension is the TYPED const NT (stateless registration).
TArr :: [NT]s64;
TArr :: [NT]i64;
TSArr :: [NT]string;
TPArr :: [NT]P;
// Forward reference: this alias is declared BEFORE its dimension const NF.
FArr :: [NF]s64;
FArr :: [NF]i64;
NF :: 5;
main :: () {
// Typed-const dimension, DIRECT local decl.
d : [NT]s64 = ---;
d : [NT]i64 = ---;
d[0] = 3;
d[7] = 21;
print("direct d0={} d7={} len={}\n", d[0], d[7], d.len);
@@ -43,7 +43,7 @@ main :: () {
s : TSArr = ---;
s[0] = "hi";
s[7] = "yo";
print("alias s0={} s7={}\n", s[0], s[7]);
print("alias i0={} i7={}\n", s[0], s[7]);
// Typed-const dimension via ALIAS (struct elements).
ps : TPArr = ---;
@@ -52,7 +52,7 @@ main :: () {
print("alias p0x={} p0y={} p7x={}\n", ps[0].x, ps[0].y, ps[7].x);
// Nested fixed array whose both dimensions are the typed const NT.
grid : [NT][NT]s64 = ---;
grid : [NT][NT]i64 = ---;
grid[0][0] = 1;
grid[7][7] = 10;
print("nested g00={} g77={}\n", grid[0][0], grid[7][7]);

View File

@@ -16,50 +16,50 @@
M :: 4;
N :: 6;
TK : s64 : 2; // typed const, used inside an expression dimension
TK : i64 : 2; // typed const, used inside an expression dimension
P :: struct { x: s64; y: s64; }
P :: struct { x: i64; y: i64; }
AddAlias :: [M + 1]s64; // 5
MulAlias :: [M * N]s64; // 24
SubAlias :: [N - M]s64; // 2
NestAlias :: [M + N - 1]s64; // 9
ParenAlias :: [(M + 1) * 2]s64; // 10
TypedAlias :: [M + TK]s64; // 6
AddAlias :: [M + 1]i64; // 5
MulAlias :: [M * N]i64; // 24
SubAlias :: [N - M]i64; // 2
NestAlias :: [M + N - 1]i64; // 9
ParenAlias :: [(M + 1) * 2]i64; // 10
TypedAlias :: [M + TK]i64; // 6
StrAlias :: [M + 1]string; // 5, slice/pointer elements
StructAlias :: [M + 1]P; // 5, struct elements
main :: () {
// const + literal: direct and via alias resolve to the same length.
add_d : [M + 1]s64 = ---;
add_d : [M + 1]i64 = ---;
add_a : AddAlias = ---;
add_d[4] = 7;
add_a[4] = 7;
print("add direct.len={} alias.len={} d4={} a4={}\n", add_d.len, add_a.len, add_d[4], add_a[4]);
// const * const.
mul_d : [M * N]s64 = ---;
mul_d : [M * N]i64 = ---;
mul_a : MulAlias = ---;
mul_d[23] = 230;
mul_a[23] = 230;
print("mul direct.len={} alias.len={} d23={} a23={}\n", mul_d.len, mul_a.len, mul_d[23], mul_a[23]);
// const - const.
sub_d : [N - M]s64 = ---;
sub_d : [N - M]i64 = ---;
sub_a : SubAlias = ---;
sub_d[1] = 9;
sub_a[1] = 9;
print("sub direct.len={} alias.len={} d1={} a1={}\n", sub_d.len, sub_a.len, sub_d[1], sub_a[1]);
// nested and parenthesised forms (direct vs alias).
nest_d : [M + N - 1]s64 = ---;
nest_d : [M + N - 1]i64 = ---;
nest_a : NestAlias = ---;
paren_d : [(M + 1) * 2]s64 = ---;
paren_d : [(M + 1) * 2]i64 = ---;
paren_a : ParenAlias = ---;
print("nest direct.len={} alias.len={} paren direct.len={} alias.len={}\n", nest_d.len, nest_a.len, paren_d.len, paren_a.len);
// typed const inside the expression dimension.
typ_d : [M + TK]s64 = ---;
typ_d : [M + TK]i64 = ---;
typ_a : TypedAlias = ---;
print("typed direct.len={} alias.len={}\n", typ_d.len, typ_a.len);
@@ -67,7 +67,7 @@ main :: () {
str_a : StrAlias = ---;
str_a[0] = "hi";
str_a[4] = "yo";
print("str alias.len={} s0={} s4={}\n", str_a.len, str_a[0], str_a[4]);
print("str alias.len={} i0={} i4={}\n", str_a.len, str_a[0], str_a[4]);
// struct elements.
ps : StructAlias = ---;

View File

@@ -1,8 +1,8 @@
// An array dimension accepts any compile-time numeric constant whose value is a
// positive INTEGRAL number — an integral float (`4.0`) folds to its integer just
// like `4`. A float-typed const (`N : f64 : 4.0`), an untyped-float const
// (`M :: 4.0`), and a direct float literal (`[4.0]s64`) all lay out the same
// `[4]s64` as the integer spelling, so element store/read is in bounds.
// (`M :: 4.0`), and a direct float literal (`[4.0]i64`) all lay out the same
// `[4]i64` as the integer spelling, so element store/read is in bounds.
//
// Regression (issue 0083 / F0.4 attempt 8, Agra ruling): an integral float used
// as a dimension was wrongly rejected "must be a compile-time integer constant".
@@ -15,15 +15,15 @@ N : f64 : 4.0; // float-typed const
M :: 4.0; // untyped float const
main :: () {
a : [N]s64 = ---; // dim from a float-typed const
a : [N]i64 = ---; // dim from a float-typed const
a[0] = 10; a[3] = 40;
print("a len={} a0={} a3={}\n", a.len, a[0], a[3]);
b : [M]s64 = ---; // dim from an untyped float const
b : [M]i64 = ---; // dim from an untyped float const
b[1] = 21;
print("b len={} b1={}\n", b.len, b[1]);
c : [4.0]s64 = ---; // direct integral-float-literal dim
c : [4.0]i64 = ---; // direct integral-float-literal dim
c[2] = 32;
print("c len={} c2={}\n", c.len, c[2]);
}

View File

@@ -4,9 +4,9 @@
// SAME leaf forms to the SAME value through one shared evaluator
// (`program_index.evalConstIntExpr` / `moduleConstInt`). The leaf forms
// exercised here: untyped int const (`M`), a named const with an EXPRESSION RHS
// (`N :: M + 1`), a typed-int const (`S : s64 : 5`), an integral float const
// (`N :: M + 1`), a typed-int const (`S : i64 : 5`), an integral float const
// (`F :: 4.0` ≡ 4), and an ALIASED integer constraint (`Count :: u32`,
// `Small :: s8`) on a value-param.
// `Small :: i8`) on a value-param.
//
// Regression (issue 0083): two cells of this surface diverged from the rest.
// (1) A named const whose RHS is an expression (`N :: M + 1`) did not fold as a
@@ -20,27 +20,27 @@
M :: 2; // untyped int const
N :: M + 1; // named const, EXPRESSION RHS (== 3)
S : s64 : 5; // typed-int const
S : i64 : 5; // typed-int const
KU : u32 : 3; // typed-u32 const
F :: 4.0; // integral float const (== 4)
Count :: u32; // integer ALIAS — value-param constraint
Small :: s8; // integer ALIAS — value-param constraint
Small :: i8; // integer ALIAS — value-param constraint
ArrN :: [N]s64; // array dim via alias: expression const (3)
ArrF :: [F]s64; // array dim via alias: integral float (4)
ArrS :: [S]s64; // array dim via alias: typed const (5)
ArrN :: [N]i64; // array dim via alias: expression const (3)
ArrF :: [F]i64; // array dim via alias: integral float (4)
ArrS :: [S]i64; // array dim via alias: typed const (5)
Buf :: struct ($K: u32, $T: Type) { data: [K]T; }
BufC :: struct ($K: Count, $T: Type) { data: [K]T; } // ALIASED u32 constraint
BufS :: struct ($K: Small, $T: Type) { data: [K]T; } // ALIASED s8 constraint
BufS :: struct ($K: Small, $T: Type) { data: [K]T; } // ALIASED i8 constraint
Make :: ($K: u32, $T: Type) -> Type { return [K]T; } // type-fn value-param
main :: () {
// array dimension — DIRECT
a : [N]s64 = ---; a[0] = 7; a[2] = 9;
a : [N]i64 = ---; a[0] = 7; a[2] = 9;
print("dim.direct.expr: len={} a0={} a2={}\n", a.len, a[0], a[2]);
f : [F]s64 = ---; f[3] = 40;
f : [F]i64 = ---; f[3] = 40;
print("dim.direct.float: len={} f3={}\n", f.len, f[3]);
// array dimension — via type ALIAS
@@ -54,13 +54,13 @@ main :: () {
v4 : Vector(F, f32) = .[1.0, 2.0, 3.0, 4.0];
print("lane.float4: {}\n", v4.w);
// generic value-param — struct binder: expr const, aliased u32, aliased s8
bn : Buf(N, s64) = ---; bn.data[2] = 30; print("vp.struct.expr: len={} v={}\n", bn.data.len, bn.data[2]);
bc : BufC(KU, s64) = ---; bc.data[2] = 31; print("vp.struct.alias.u32: len={} v={}\n", bc.data.len, bc.data[2]);
bs : BufS(4, s64) = ---; bs.data[3] = 32; print("vp.struct.alias.s8: len={} v={}\n", bs.data.len, bs.data[3]);
// generic value-param — struct binder: expr const, aliased u32, aliased i8
bn : Buf(N, i64) = ---; bn.data[2] = 30; print("vp.struct.expr: len={} v={}\n", bn.data.len, bn.data[2]);
bc : BufC(KU, i64) = ---; bc.data[2] = 31; print("vp.struct.alias.u32: len={} v={}\n", bc.data.len, bc.data[2]);
bs : BufS(4, i64) = ---; bs.data[3] = 32; print("vp.struct.alias.i8: len={} v={}\n", bs.data.len, bs.data[3]);
// generic value-param — type-fn binder: expr const
mk : Make(N, s64) = ---; mk[2] = 33; print("vp.typefn.expr: len={} v={}\n", mk.len, mk[2]);
mk : Make(N, i64) = ---; mk[2] = 33; print("vp.typefn.expr: len={} v={}\n", mk.len, mk[2]);
// inline-for bound — expr const (3) and integral float (4)
s := 0; inline for 0..N (i) { s += i; } print("for.expr: {}\n", s); // 0+1+2 = 3

View File

@@ -8,10 +8,10 @@
// "positive integral", which wrongly implied `[0]T` / `Box(0)` are illegal.
#import "modules/std.sx";
Box :: struct($N: u32) { items: [N]s64; }
Box :: struct($N: u32) { items: [N]i64; }
main :: () {
a : [0]s64 = ---;
a : [0]i64 = ---;
print("array_dim={}\n", a.len);
b : Box(0) = ---;

View File

@@ -5,61 +5,61 @@
// `usize`/`isize` (target-width). Usable in expressions and in array-dimension
// position via the comptime-int path (`[u8.max]T`).
//
// The extreme values that the s64-based integer formatter cannot render
// directly — `s64.min` (i64::MIN) and the all-ones `u64.max`/`usize.max` — are
// The extreme values that the i64-based integer formatter cannot render
// directly — `i64.min` (i64::MIN) and the all-ones `u64.max`/`usize.max` — are
// asserted EXACTLY via comparison and untagged-union bit reinterpret, never via
// the formatter (which prints i64::MIN as a bare "-" and u64.max as "-1").
#import "modules/std.sx";
// Untagged union for the exact u64.max bit-reinterpret check.
UU :: union { u: u64; s: s64; }
UU :: union { u: u64; s: i64; }
main :: () -> s32 {
main :: () -> i32 {
// Sub-byte widths — arbitrary bit-width arithmetic, not a per-name table.
print("s1.min={} s1.max={}\n", s1.min, s1.max); // -1 0
print("s2.min={} s2.max={}\n", s2.min, s2.max); // -2 1
print("s3.max={}\n", s3.max); // 3
print("i1.min={} i1.max={}\n", i1.min, i1.max); // -1 0
print("i2.min={} i2.max={}\n", i2.min, i2.max); // -2 1
print("i3.max={}\n", i3.max); // 3
print("u1.min={} u1.max={}\n", u1.min, u1.max); // 0 1
print("u2.max={}\n", u2.max); // 3
// Byte / word widths.
print("s8.min={} s8.max={}\n", s8.min, s8.max); // -128 127
print("i8.min={} i8.max={}\n", i8.min, i8.max); // -128 127
print("u8.max={}\n", u8.max); // 255
print("s32.min={} s32.max={}\n", s32.min, s32.max); // -2147483648 2147483647
print("i32.min={} i32.max={}\n", i32.min, i32.max); // -2147483648 2147483647
// s64 extremes: max prints; min (i64::MIN) is pinned by relation since the
// i64 extremes: max prints; min (i64::MIN) is pinned by relation since the
// formatter cannot render it (this is independent of this feature).
print("s64.max={}\n", s64.max); // 9223372036854775807
print("s64.min+1 == -(s64.max): {}\n", s64.min + 1 == -9223372036854775807); // true
print("s64.min + s64.max == -1: {}\n", s64.min + s64.max == -1); // true
print("i64.max={}\n", i64.max); // 9223372036854775807
print("i64.min+1 == -(i64.max): {}\n", i64.min + 1 == -9223372036854775807); // true
print("i64.min + i64.max == -1: {}\n", i64.min + i64.max == -1); // true
// u64.max / usize.max = all-ones (18446744073709551615); reinterpret to s64
// u64.max / usize.max = all-ones (18446744073709551615); reinterpret to i64
// to confirm the bit pattern is -1 (and NOT a mangled value).
o : UU = ---;
o.u = u64.max;
print("u64.max as s64 == -1: {}\n", o.s == -1); // true
print("u64.max as i64 == -1: {}\n", o.s == -1); // true
o.u = usize.max;
print("usize.max as s64 == -1: {}\n", o.s == -1); // true (host = u64)
print("usize.max as i64 == -1: {}\n", o.s == -1); // true (host = u64)
print("usize.max == u64.max: {}\n", usize.max == u64.max); // true
print("isize.min == s64.min: {}\n", isize.min == s64.min); // true (host = s64)
print("isize.min == i64.min: {}\n", isize.min == i64.min); // true (host = i64)
// Result carries the QUERIED type: each binding is declared with the queried
// type and round-trips, so a mistyped fold (e.g. boxed as Any / widened)
// would not type-check here.
m3 : s3 = s3.max;
m3 : i3 = i3.max;
mu : u8 = u8.max;
ms : s8 = s8.min;
ms : i8 = i8.min;
print("typed: m3={} mu={} ms={}\n", m3, mu, ms); // 3 255 -128
// Array-dimension / comptime-int path: `[u8.max]T` and `[s16.max]T` are
// Array-dimension / comptime-int path: `[u8.max]T` and `[i16.max]T` are
// valid counts (255 and 32767), usable end-to-end.
a : [u8.max]u8 = ---;
a[254] = 7;
print("[u8.max]u8 len={} a[254]={}\n", a.len, a[254]); // 255 7
b : [s16.max]u8 = ---;
b : [i16.max]u8 = ---;
b[32766] = 9;
print("[s16.max]u8 len={} b[32766]={}\n", b.len, b[32766]); // 32767 9
print("[i16.max]u8 len={} b[32766]={}\n", b.len, b[32766]); // 32767 9
return 0;
}

View File

@@ -8,9 +8,9 @@
// Each case is accurate and located at the access; the program exits non-zero.
#import "modules/std.sx";
MyStruct :: struct { a: s64; }
MyStruct :: struct { a: i64; }
main :: () -> s32 {
main :: () -> i32 {
b := bool.max;
s := MyStruct.min;
v := void.max;

View File

@@ -1,10 +1,10 @@
// Backtick raw-identifier escape: a leading backtick makes the following
// identifier RAW — its text excludes the backtick and it is never the
// reserved/builtin keyword, so a reserved type-name spelling (`s2`, `u8`, …)
// reserved/builtin keyword, so a reserved type-name spelling (`i2`, `u8`, …)
// can be used as an ordinary identifier. Exercised in every VALUE position:
// global, local, param, struct field + member access, function name + call,
// and a later reference. (A raw identifier in TYPE position references a
// backtick-declared type instead — see examples/0154.) A *bare* `s2` is still
// backtick-declared type instead — see examples/0154.) A *bare* `i2` is still
// the reserved type name (see examples/1119), so the escape is the only way to
// spell these as values.
// Regression (issue 0089).
@@ -14,22 +14,22 @@
`u8 := 100;
// Function whose name is a reserved type spelling, with a reserved-name param.
`s2 :: (`s1: s64) -> s64 { return `s1 * 2; }
`i2 :: (`i1: i64) -> i64 { return `i1 * 2; }
Point :: struct {
`s2: f64; // field name is a reserved type spelling
`u16: s64;
`i2: f64; // field name is a reserved type spelling
`u16: i64;
}
main :: () {
// Local with a reserved type spelling; later reference resolves to it.
`s64 := 7;
`s64 = `s64 + 1;
print("local = {}\n", `s64);
`i64 := 7;
`i64 = `i64 + 1;
print("local = {}\n", `i64);
print("global = {}\n", `u8);
print("fn = {}\n", `s2(21)); // calls the `s2 function
print("fn = {}\n", `i2(21)); // calls the `i2 function
p := Point.{ `s2 = 2.5, `u16 = 9 };
print("field = {} {}\n", p.`s2, p.`u16);
p := Point.{ `i2 = 2.5, `u16 = 9 };
print("field = {} {}\n", p.`i2, p.`u16);
}

View File

@@ -1,5 +1,5 @@
// Backtick raw identifier across every control-flow / capture / binding form,
// plus bare later uses. A reserved type-name spelling (`s2`, `u8`, …) works as a
// plus bare later uses. A reserved type-name spelling (`i2`, `u8`, …) works as a
// binding name in a destructure, an `if`/`while` optional binding, a `for`
// capture + index, and a match-arm capture; a backtick-named function is
// bare-callable; and a backtick struct field is bare- or backtick-accessible.
@@ -10,37 +10,37 @@
// Regression (issue 0089 — attempt-2 completeness across binding forms).
#import "modules/std.sx";
pair :: () -> (s64, s64) { (1, 2) }
maybe :: () -> ?s64 { return 42; }
pair :: () -> (i64, i64) { (1, 2) }
maybe :: () -> ?i64 { return 42; }
// Function named with a reserved spelling — bare-callable (no backtick at call).
`s2 :: (n: s64) -> s64 { return n + 1; }
`i2 :: (n: i64) -> i64 { return n + 1; }
Quad :: struct { `s1: s32; `s2: s32; }
Quad :: struct { `i1: i32; `i2: i32; }
main :: () -> s32 {
main :: () -> i32 {
// destructure binding names
`u8, rest := pair();
print("dstr = {} {}\n", `u8, rest);
// if optional binding + bare-position reference inside the branch
if `s16 := maybe() {
print("if = {}\n", `s16);
if `i16 := maybe() {
print("if = {}\n", `i16);
}
// while optional binding (name only — the while binding isn't body-exposed)
while `s32 := maybe() {
while `i32 := maybe() {
break;
}
// for capture + index names
xs := [3]s64.{ 10, 20, 30 };
xs := [3]i64.{ 10, 20, 30 };
for xs, 0.. (`bool, `u16) {
print("for = {} @ {}\n", `bool, `u16);
}
// match-arm capture
opt: ?s64 = 5;
opt: ?i64 = 5;
m := if opt == {
case .some: (`string) { `string * 2 }
case .none: { 0 }
@@ -48,10 +48,10 @@ main :: () -> s32 {
print("match = {}\n", m);
// backtick function called BARE and via backtick — both resolve to the fn
print("call = {} {}\n", s2(10), `s2(10));
print("call = {} {}\n", i2(10), `i2(10));
// struct field named with a reserved spelling: bare + backtick member access
q := Quad.{ `s1 = 7, `s2 = 9 };
print("field = {} {} | {} {}\n", q.s1, q.s2, q.`s1, q.`s2);
q := Quad.{ `i1 = 7, `i2 = 9 };
print("field = {} {} | {} {}\n", q.i1, q.i2, q.`i1, q.`i2);
return 0;
}

View File

@@ -1,22 +1,22 @@
// Backtick raw-identifier escape at the `::` declaration sites: a leading
// backtick makes a CONSTANT name and a FUNCTION name raw, so a reserved type
// spelling (`s2`, `u8`) can be declared and used. Complements examples/0151
// spelling (`i2`, `u8`) can be declared and used. Complements examples/0151
// (var / param / field / global). The backtick fn is callable both via the
// backtick (`` `u8(5) ``) and bare (`u8(5)`) — the bare reserved-name callee
// resolves to the raw fn because its declaration is raw (issue 0089). A *bare*
// `s2 :: …` / `u8 :: …` declaration is still the reserved-name error (see
// `i2 :: …` / `u8 :: …` declaration is still the reserved-name error (see
// examples/1140).
// Regression (issue 0089).
#import "modules/std.sx";
// Constant whose name is a reserved type spelling.
`s2 :: 2.5;
`i2 :: 2.5;
// Function whose name is a reserved type spelling.
`u8 :: (n: s64) -> s64 { return n + 7; }
`u8 :: (n: i64) -> i64 { return n + 7; }
main :: () -> s32 {
print("const = {}\n", `s2);
main :: () -> i32 {
print("const = {}\n", `i2);
print("fn tick = {}\n", `u8(5));
print("fn bare = {}\n", u8(5));
return 0;

View File

@@ -1,8 +1,8 @@
// Backtick raw identifier in TYPE position (the universal model, issue 0089):
// `` `name `` is the LITERAL identifier `name` used as a type reference, never
// the builtin/reserved spelling. A reserved type spelling (`s2`, `u8`, …) can
// the builtin/reserved spelling. A reserved type spelling (`i2`, `u8`, …) can
// therefore both DECLARE a type (struct / enum / union / error-set / alias) and
// be REFERENCED as that type via the backtick — while a BARE `s2` in type
// be REFERENCED as that type via the backtick — while a BARE `i2` in type
// position remains the signed-int type (see `add` below) and a bare reserved-
// name declaration still errors (see examples/1141). The backtick is required
// to declare or reference these names; it is never part of the name's text.
@@ -10,18 +10,18 @@
#import "modules/std.sx";
// Type-introducing decls whose NAME is a reserved spelling.
`s2 :: struct { x: s64; }
`s8 :: enum { A; B; }
`u16 :: union { i: s32; f: f32; }
`i2 :: struct { x: i64; }
`i8 :: enum { A; B; }
`u16 :: union { i: i32; f: f32; }
`u32 :: error { Bad, Empty }
RawAlias :: `s2; // alias to a backtick-declared struct
RawAlias :: `i2; // alias to a backtick-declared struct
// A bare `s2` in type position is still the 2-bit signed int.
add :: (a: s2, b: s2) -> s2 { return a + b; }
// A bare `i2` in type position is still the 2-bit signed int.
add :: (a: i2, b: i2) -> i2 { return a + b; }
main :: () -> s32 {
main :: () -> i32 {
// Reference the backtick struct as a type; field access works.
v : `s2 = ---;
v : `i2 = ---;
v.x = 7;
// Reference via a normal alias too.
@@ -29,7 +29,7 @@ main :: () -> s32 {
a.x = 11;
// Backtick enum / union type references.
e : `s8 = .A;
e : `i8 = .A;
u : `u16 = ---;
u.i = 5;
@@ -37,6 +37,6 @@ main :: () -> s32 {
print("alias = {}\n", a.x);
print("enum = {}\n", e == .A);
print("union = {}\n", u.i);
print("bare = {}\n", add(1, 0)); // bare s2 = the 2-bit int type
print("bare = {}\n", add(1, 0)); // bare i2 = the 2-bit int type
return 0;
}

View File

@@ -1,24 +1,24 @@
// Backtick raw identifier at the two remaining binding positions (issue 0089,
// attempt-4): a TYPED constant (`` `s2 : s64 : 5 ``) and a union TAG / field
// (`` `s2: s32 ``). The typed-const form previously slipped past the decl check
// without a name span (caret at 1:1); a bare `s2 : s64 : 5` is still rejected
// attempt-4): a TYPED constant (`` `i2 : i64 : 5 ``) and a union TAG / field
// (`` `i2: i32 ``). The typed-const form previously slipped past the decl check
// without a name span (caret at 1:1); a bare `i2 : i64 : 5` is still rejected
// with the caret ON the name (see examples/1141). A union tag spelled with a
// reserved name works and is accessible bare or backticked.
// Regression (issue 0089 — attempt-4 typed const + union tag).
#import "modules/std.sx";
// Typed constant whose name is a reserved type spelling.
`s2 : s64 : 5;
`i2 : i64 : 5;
// Union whose tags are reserved type spellings.
Mix :: union { `s1: s32; `u8: f32; }
Mix :: union { `i1: i32; `u8: f32; }
main :: () -> s32 {
print("typed const = {}\n", `s2);
main :: () -> i32 {
print("typed const = {}\n", `i2);
m : Mix = ---;
m.`s1 = 42;
print("union tick = {}\n", m.`s1); // backtick member access
print("union bare = {}\n", m.s1); // bare member access — same field
m.`i1 = 42;
print("union tick = {}\n", m.`i1); // backtick member access
print("union bare = {}\n", m.i1); // bare member access — same field
return 0;
}

Some files were not shown because too many files have changed in this diff Show More