Commit Graph

475 Commits

Author SHA1 Message Date
agra
0cb4a5a342 lsp: go-to-definition on a field through a *Struct variable
resolveStructTypeName returned null unless the variable's type was exactly a struct, so 'board.castling' (board: *Board) couldn't locate the Board declaration. It now also returns the pointee struct name for a pointer-to-struct, read from the resolved symbol type. Verified: board.castling navigates to board.sx's castling field.
2026-05-31 11:39:52 +03:00
agra
ef0d9a9477 lsp/sema: resolve pointer-typed params and field access through pointers
Two gaps made 'piece := board.squares[move.from.index]' (board: *Board) <unresolved>: analyzeParams typed params with fromTypeExpr (bare-name only), so *Board / []T / *List params became null; and field_access only handled a struct value, not a *Struct. Params now resolve via fieldType, and field_access auto-derefs a pointer object (p.field on *T resolves on T). Regression test added.
2026-05-31 11:22:24 +03:00
agra
5c9d8c23ca lang: for-loop over List(T); deref a *T method receiver
The collection for-loop now iterates a List(T)-like struct ({ items: [*]T, len, … }) — and a *List — by viewing it as items[0..len]. So 'for legal: (m)' / 'for pieces: (*p)' work like iterating a slice, with by-ref captures writing back into the backing.

fixupMethodReceiver also derefs a *T receiver when the method takes T by value, so a 'for xs: (*x)' capture can call value-self methods (x.method()). Regression: examples/for-list.sx.
2026-05-31 11:13:57 +03:00
agra
6b5edc77b4 lang: require ':' before a for-loop range cursor
The cursor clause now matches the collection form's ': (capture)' — 'for 0..N: (i)' instead of 'for 0..N (i)'. The colon is required when a cursor is present; the no-cursor form 'for 0..N { }' is unchanged. Updated examples/200, the pack-index doc comment, and the spec.
2026-05-31 10:57:21 +03:00
agra
c08433b345 ui: drop redundant .* on pointer match subjects
event_position and translate_sdl_event matched on e.* / sdl.*; lowerMatch now auto-derefs a pointer subject, so 'if e ==' / 'if sdl ==' are equivalent (same load + tag-switch in IR). Pure cleanup.
2026-05-31 10:46:51 +03:00
agra
cd2ab1c4b6 ui: platform-neutral Keycode enum; map SDL keycodes once
KeyData.key was a raw u32 carrying SDL_Keycode values, so app code had to reinterpret it as SDL_Keycode (xx e.key) — a leaky, unchecked cross-platform cast only valid because the backend happened to be SDL. Add a neutral Keycode enum; translate_sdl_event maps SDL_Keycode to it via keycode_from_sdl. App code compares e.key == .escape with no platform type and no cast; a new backend maps its own native codes in one place.
2026-05-31 10:42:55 +03:00
agra
d70a7084ff specs: document for-loop by-reference capture (for xs: (*elem))
Covers the *elem pointer binding: zero-copy pass to *T params, write-back via elem.*, value-position auto-deref, and pointer-subject match.
2026-05-31 10:32:05 +03:00
agra
185df9afb7 lang: for-loop by-ref element capture (for xs: (*x))
(*x) binds x to a pointer into the collection (index_gep) instead of a per-element value copy: passing it on (e.g. to a *T param) is zero-copy and mutations write back. In a value position x auto-derefs — a binary-op operand loads the element, a pointer-typed slot keeps the pointer, and an 'if x == {...}' match derefs the pointee for its tag/payload. Arrays GEP through their storage so writes hit the original. Regression test: examples/for-by-ref-capture.sx.
2026-05-31 10:29:16 +03:00
agra
4415274894 lsp/sema: resolve method-return types, slice .ptr/.len, tagged enums
ev := events.ptr[i] (events := g_plat.poll_events()) was <unresolved> through three gaps:

1. Return types went through Type.fromTypeExpr, which only handles a bare type_expr — so any []T / *T / List(T) return became void. An impl method 'poll_events -> []Event' registered as void and, merged after the protocol's correct signature, clobbered it. resolveReturnType now uses fieldType.

2. Struct/protocol methods were never put in fn_signatures, so recv.method() and Type.static() return types never resolved. registerMethodSig now adds them by bare name (first-wins), which is what resolveCalleeName already assumed.

3. .ptr/.len field access was string-only (and string.ptr wrongly returned string_type); now handles slices/arrays and returns the proper many-pointer element.

4. Tagged enums (payload variants) were only a symbol, never in a lookup registry; now also recorded in enum_types so the name resolves as a type.

Net: events -> []Event, events.ptr -> [*]Event, ev -> Event. Regression test added; confirmed end-to-end via the LSP inlay hint.
2026-05-31 10:04:08 +03:00
agra
8be1deea93 claude.md: register LANG and ERR workstreams
Add LANG (already had files in current/ but missing from the workstream
list) and ERR (new error-handling design, plan + checkpoint in current/
PLAN-ERR.md and CHECKPOINT-ERR.md — gitignored).

Updates the "On every session start" enumeration, the per-step
checkpoint-update guidance, and the File roles table to reference all
five streams.
2026-05-31 09:40:52 +03:00
agra
13bd3c85ea lsp/sema: regression tests for generic indexing through import merge
Covers List(Move).items[i] -> Move via the LSP's flat-import struct_types merge (pre-registered, not self-declared) and with realistic methods/cross-referencing fields. Confirmed end-to-end against the real binary: the inlay hint for 'm := legal.items[i]' now resolves to Move.
2026-05-31 09:40:05 +03:00
agra
7c7a5ad5c7 lsp/sema: resolve generic-struct field indexing in hover
inferExprType returned <unresolved> for 'legal.items[i]' (a List(Move) indexed) for two reasons: index_expr only handled string/array — not many-pointers/slices — and generic instantiation was dropped (List(Move) tracked as bare List, so T never bound to Move).

Fixes: (1) fieldType preserves pointer/slice element names (the old Type.fromTypeExpr only handled plain type_expr nodes, so [*]T became unresolved); (2) index_expr/slice_expr resolve many-pointer + slice elements via a registry-aware resolveTypeNameStr that knows user structs/enums (unlike Type.fromName); (3) instantiateGeneric monomorphizes List(Move) into a struct_types entry with T->Move substituted. So legal.items -> [*]Move and m -> Move. Regression test added.
2026-05-30 18:25:28 +03:00
agra
fb8a5399f1 objc: remove ns_string/c_string helpers
ns_string's only caller was impl Into(*NSString) for string, so +stringWithUTF8String: is inlined there. c_string's one use (NSBundle.resourcePath in uikit) becomes rsrc.UTF8String() with resourcePath retyped *NSString. ffi-objc-call-06 and ffi-objc-dsl-07 .ir snapshots regenerated — they only drop the now-absent extern declares.
2026-05-30 18:01:27 +03:00
agra
a29ede0383 objc: migrate remaining ns_string call sites to xx NSString
NSLog's fmt, addObserver's name, UIApplicationMain's principal-class, CADisplayLink's run-loop mode, and metal's newLibraryWithSource/newFunctionWithName string args are retyped *NSString, so their call sites read xx "..." instead of ns_string("...".ptr). ns_string is now used only by impl Into(*NSString) for string.
2026-05-30 17:54:23 +03:00
agra
8e3c3ae981 objc: NSString type + Into(*NSString) for string
Adds an NSString foreign class and impl Into(*NSString) for string so a string literal flows into any *NSString slot via xx. uikit's keyboard userInfo lookups now read objectForKey(xx "...") instead of ns_string("...".ptr), and objectForKey's key param is retyped *NSString.

ffi-objc-call-06 .ir snapshot regenerated: declaring the NSString type adds its reflection thunks (struct_to_string/pointer_to_string), same as the existing NSObject/NSDictionary. Runtime output unchanged.
2026-05-30 17:39:38 +03:00
agra
29a4891374 imports: dedup flat decl list by node identity (issue 0056 FIXED)
Impl blocks are anonymous (no declName), so a parameterised-protocol impl in a module reached via a diamond import was appended once per path and registered twice — 'duplicate impl Into for source s64'. mergeFlat and the directory-import merge loop now also dedup by node pointer; a physical AST node is lowered once regardless of how many import paths reach it.

Regression: examples/issue-0056-diamond-param-impl.sx.
2026-05-30 17:36:35 +03:00
agra
ac7f1d10e5 lang: extend operand-type check to ordering + bitwise/shift (issue 0055 follow-up)
The arithmetic-only check from the previous commit shared a hole with the
comparison and bitwise/shift ops: lowerBinaryOp derives the result type
from the LHS, so `s64 < string` fed mismatched types to `icmp` (LLVM
verifier failure) and `s64 & string` reinterpreted the string's bytes.

Add isOrderingOperand (numeric / enum / pointer / bool / vector) and
isBitwiseOperand (integer / enum / bool / vector), and route `< <= > >=`
and `& | ^ << >>` through them alongside the existing arithmetic check, all
sharing one diagnostic + placeholder-sentinel path. Flags-enum bitwise
(`.read | .write`, `perm & .read`), enum/pointer comparison, and int
literals stay legal (50-smoke unaffected).

Equality `== / !=` is deliberately left unchecked — its path is heavily
special-cased (str_eq, Any unbox, optional == null); folding a check in
without regressing those is a separate change, noted in the issue.

Regression test renamed arith→binop and broadened to cover `+ * < & <<`
against a string operand: examples/214-binop-operand-type-check.sx.
2026-05-30 10:30:57 +03:00
agra
6016b08712 lang: reject mismatched operand types in scalar arithmetic (issue 0055)
lowerBinaryOp derived the result type from the LHS alone and emitted
add/sub/mul/div/mod without checking the RHS, so `s64 + string` lowered
as `add : s64` and reinterpreted the string's bytes — printing garbage
instead of erroring.

Add isArithOperand (int / float / vector / pointer, plus custom int
widths) and, for `+ - * / %`, diagnose `cannot apply '<op>' to operands
of type '<lhs>' and '<rhs>'` and return a placeholder sentinel instead of
the corrupting op. `.unresolved` operands pass through so a type we
couldn't infer is never falsely rejected; the existing optional-unwrap
and int×float promotion are accounted for before the check.

Ordering (`< <= > >=`) and bitwise/shift (`& | ^ << >>`) ops share the
same LHS-derived-type hole and are left as a noted follow-up in the issue.

Regression: examples/214-arith-operand-type-check.sx (s64 + string, and
non-numeric LHS string * s64).
2026-05-30 09:56:32 +03:00
agra
8e74e4acb2 lang F1 Phase 6: canonical heterogeneous map — $R inference through closure params
The full canonical `map` now compiles and runs (examples/213 → 42):

    map :: (mapper: Closure(..sources.T) -> $R, ..sources: VL) -> VL($R)

Final piece: infer a pack-fn's generic return `$R` from a closure-typed
prefix param's lowered return type.

- collectGenericNames descends into closure_type_expr (params + return),
  so `$R` in `Closure(..) -> $R` registers as a function type-param.
- matchTypeParam/extractTypeParam descend into closures: `$R` is extracted
  from the lowered mapper's closure `.ret`.
- lowerPackFnCall infers type-param bindings from the lowered prefix args,
  folds them into the mangle, and threads them into monomorphizePackFn,
  which installs self.type_bindings for return-type resolution + body
  lowering (`-> VL($R)` ⇒ VL(s64); `Combined($R, ..)` ⇒ Combined(s64, ..)).

s64-elimination follow-through:

- An unbound generic `$R` resolves to `.unresolved` in resolveTypeWithBindings
  rather than fabricating an empty-struct stub (`R{}`).
- Lambda return-type inference skips an `.unresolved` target-closure ret and
  infers from the body, so the concrete return drives `$R`.
- The `.unresolved` codegen tripwire then caught a latent bug: a generic-struct
  source impl (`impl VL($R) for Combined($R, ..$Ts)`) was declaring its template
  method `Combined.get` (`-> $R`) as a standalone IR function. Fixed: a
  generic-struct source registers methods as TEMPLATES only (findable in
  fn_ast_map for per-instance monomorphization via createProtocolThunk), never
  declareFunction'd.

Feature 1 (heterogeneous variadic packs) all six phases complete.
248 examples + all unit tests green.
2026-05-30 03:46:46 +03:00
agra
f2e1f401ce issues/0054: mark FIXED (generic-struct -> param-protocol erasure) 2026-05-30 03:26:24 +03:00
agra
1f6e27d8f2 lang F1 6: generic-struct -> parameterized-protocol erasure (issue 0054 FIXED)
Two fixes, root-caused from xx Combined -> VL(s64) trapping:
- instantiateGenericStruct binds the template name to the concrete instance
  (tb.put(tmpl.name, id)), so an impl method self: *Combined resolves self.field
  to the instance (Combined__s64_s64), not the 0-field generic stub. This was a
  general pre-existing bug: self.x on ANY generic-struct impl method failed.
- createProtocolThunk monomorphizes the template method for a generic-struct
  instance (Combined.get -> Combined__s64_s64.get with the instance bindings),
  so the erasure vtable dispatches instead of hitting an unreachable thunk.

xx c on a generic Combined now dispatches correctly (examples/212 -> 99).
247 examples + unit green.
2026-05-30 03:25:04 +03:00
agra
e96e76f6b0 issues/0054: generic-struct -> parameterized-protocol erasure traps (canonical xx c) 2026-05-30 03:16:55 +03:00
agra
66c4ee168b lang F1 6: contextually type pack-fn prefix args (mapper lambda)
lowerPackFnCall lowered the runtime prefix args with no target_type, so a
lambda arg (mapper: Closure(...) -> ...) could not infer its param types.
Now set target_type to the param type while lowering each prefix arg. With
the existing value-projection call-arg spread, mapper(..sources.get) works:
the lambda is contextually typed and the projected values spread into the
call. examples/211 ((a,b)=>a+b over two sources -> 42). 246 + unit green.
2026-05-30 03:15:07 +03:00
agra
87ee3d3e65 lang F1 6: (..sources) materializes a pack into a protocol-typed tuple field
lowerTupleLiteral now coerces/erases each spliced spread element to the
contextual target tuple field type (computed even when a spread is present,
indexed by output position). New coerceOrErase: protocol target -> xx-erase
via buildProtocolErasure, else coerceToType. So c.sources = (..sources) on a
(..VL(Ts)) field erases each concrete pack element to its VL(Ti) slot.

examples/210 (build(IntCell, StrCell) -> 10 hi). 245 examples + unit green.
2026-05-30 03:11:55 +03:00
agra
e395a08331 lang F1 6: pack-spread in parameterized-type args (Combined($R, ..sources.T))
Parser now accepts a `..` spread in a parameterized-type arg list; in
instantiateGenericStruct a spread arg bound to the variadic type-param expands
via packTypeElems (so `..sources.T` projects each source pack element protocol
type-arg into ..$Ts). `Combined(s64, ..sources.T)` for a VL(s64) source
instantiates Combined(s64, s64). examples/209 (with explicit per-element xx
erase). 244 examples + unit green.

Next: (..sources) whole-pack materialization with per-element erasure into the
protocol-typed field (c.sources = (..sources) currently segfaults).
2026-05-30 03:06:03 +03:00
agra
39d77ff886 lang: tuple element assignment + named-tuple field names
Two fixes:
- Element assignment `t.0 = v` (the known Phase-4.2 gap): the lvalue path
  looked the element up by NAME via getStructFields, never matched a tuple
  (positional), and left field_ty .unresolved -> ptr(.unresolved) -> codegen
  panic. Added a tuple branch to the field-assignment lowering that indexes by
  position (numeric) or name (tup.names), mirroring the read path. Fixes
  `c.sources.0 = v` on a generic-instance pack field too.
- Named tuples: the parser dropped captured field names for a tuple TYPE
  `(x: T, y: U)` (passed field_names=null), and resolveTupleTypeWithBindings
  also nulled them. Both now preserve names (synthesizing _<i> for any unnamed
  slot), so `t.x` reads/writes by name and `.0` by position.

examples/208. 243 examples + unit green.
2026-05-30 03:00:58 +03:00
agra
a922814ba3 lang F1 4.2: (..F(Ts)) per-element type application in pack-shaped fields
packTypeElems now handles a parameterized spread operand F(Ts): for each pack
element T_i it temporarily binds the pack name to T_i and resolves F(T_i),
yielding (VL(T0), VL(T1), ...). Combined with parameterized-protocol value
types, the canonical Combined struct field sources: (..VL(Ts)) now resolves to
a tuple of real protocol values.

End-to-end (examples/207): instantiate Combined(s64, s64, string), whole-store
c.sources = (xx IntCell, xx StrCell), and per-element dispatch c.sources.0.get()
/ c.sources.1.get() all work. 242 examples + unit green.
2026-05-30 02:45:46 +03:00
agra
2f27f93bcf lang F1 4.2: parameterized protocol as a runtime value type
VL(s64) used as a value/field type resolved to a 0-field stub (size 0); a
plain protocol was already a 16-byte {ctx,vtable} value. New
instantiateParamProtocol materializes a parameterized protocol per
instantiation: a 16-byte protocol value (is_protocol), protocol_decl_map
methods resolved under the type-arg binding (get -> T becomes get -> s64 for
VL(s64)), a vtable struct, and the type-arg binding recorded for projection.
Hooked into resolveParameterizedWithBindings before the empty-struct fallback.

xx-erasing a conforming struct into VL(s64)/VL(string) + method dispatch now
works (examples/206). This is the keystone for the canonical Combined field
(..VL(Ts)). 241 examples + unit green.
2026-05-30 02:41:01 +03:00
agra
b48766d153 lang F1 4.2 (core): generic struct pack type-param + (..$Ts) tuple field
A generic struct can take a pack type-param ..$Ts: []Type that binds the
remaining type args as a sequence, and a pack-shaped tuple field (..$Ts)
resolves to a tuple of those per-position types.

- parser/ast: accept a leading .. on a struct generic param; StructTypeParam
  gains is_variadic.
- registration: TemplateParam carries is_variadic (and is a type param).
- instantiateGenericStruct: a variadic type-param consumes the remaining args
  into pack_bindings + pack_arg_types (mangled into the name); restored after.
- resolveTypeWithBindings: a tuple-literal-as-type containing a pack spread
  (e.g. (..$Ts)) expands via packTypeElems.

Instantiate + correct per-position field types + whole-tuple store + element
read all work (examples/205). Not yet: protocol-applied field (..F(Ts)) (the
canonical (..VL(Ts)) shape) and nested element assignment b.pair.0 = v.
240 examples + unit green.
2026-05-30 02:30:49 +03:00
agra
82b46bc412 lang: xx <pack> materializes a comptime pack into a runtime slice (issue 0053)
xx args with a slice target now bridges a comptime pack to a runtime slice:
[]Any boxes each element to Any; []P xx-erases each to the protocol (reusing
the slice-of-protocol erasure from 0052). New lowerPackToSlice; the unary-op
arm intercepts xx <pack> before the pack-as-value diagnostic. This is the
working forward to a runtime []Any/[]P helper -- log_count(xx args) -> 3 --
so the 2.7 pack-as-value diagnostics now suggest xx <name> for the call case.

examples/204-pack-xx-to-slice.sx (both []Any and []P paths); 203 help text
updated. issue 0053 FIXED. 239 examples + unit green.
2026-05-30 02:17:55 +03:00
agra
8a875d354c lang F1 2.7: pack-as-value diagnostics (Phase 2 complete)
Using a bare pack name where a runtime value is required was silent garbage
(f(xs)/return xs produced a stray pointer). Now a clear, context-tailored
compile error: isPackName + diagPackAsValue, caught at lowerVarDecl (storage),
lowerReturn (return), lowerFor (iterate), and an identifier-arm catch-all for
call/other. Storage binds a placeholder so there is no cascade error.

Suggestions point at WORKING fixes -- materialize (..xs), or declare the slice
form ..xs: []P for runtime use. The plan category-B "spread ..xs" is broken
(spreading a comptime pack into a []Any param crashes the LLVM verifier; filed
issue 0053), so the diagnostics steer to the slice-of-protocol variadic instead.

Repurposed examples/162-pack-bare-args.sx (was an aspirational bare-$args->[]Any
auto-materialise, contradicting Decision 1) into the slice-form forward
(..args: []Any). examples/203 is the four-category negative test. specs.md "Pack
as value" updated. 238 examples + unit green.
2026-05-30 02:09:41 +03:00
agra
ab572359ae lang: slice-of-protocol variadic ..xs: []P erases each arg to the protocol
packVariadicCallArgs stored the raw concrete arg into a [N x P] array when the
element type was a protocol, so an 8-byte struct landed in a 16-byte {ctx,
vtable} slot -> garbage vtable -> Bus error on dispatch. Now, when the slice
element type is a protocol, each arg is xx-erased to the protocol value via
buildProtocolErasure (same impl-driven machinery as the xx cast). This makes
..xs: []P the runtime, protocol-erased counterpart to the comptime
heterogeneous pack ..xs: P (which stays comptime-only): xs[runtime_i].method()
now works in an ordinary loop.

specs.md: full variadic/pack form-comparison table (concrete-vs-erased,
comptime-vs-runtime). Regression: examples/202. Issue 0052 (FIXED). 237 green.
2026-05-30 01:50:29 +03:00
agra
82bdcd634a lang F1 2.6: pack-index edge cases (runtime-index error, comptime OOB)
Per locked Decision 1 a pack is comptime-only with no runtime value, so xs[i]
is valid only for a comptime index. lowerIndexExpr now emits a clear error
("pack <p> must be indexed by a compile-time constant ...") for a runtime
index, instead of the confusing "unresolved <p>" the slice-index fall-through
produced. diagPackIndexOOB switched from int-literal-only to comptimeIndexOf so
an inline-for cursor that goes out of bounds is also caught.

Repurposed examples/163-pack-runtime-index.sx (was aspirational: expected
runtime indexing to materialise a []Any slice and print 4, contradicting
Decision 1) into the runtime-index error test. Comptime + OOB cases already
covered by examples/199/200/161. 236 examples + unit green.
2026-05-30 01:30:11 +03:00
agra
9e38bb924a ir: resolveTypeArg failure paths return .unresolved, not .void
The three post-diagnostic failure returns in resolveTypeArg (pack-index OOB,
no active pack binding, unresolved type name) returned .void as a sentinel.
Per the CLAUDE.md rule (.void is unacceptable for a failed type lookup -- it
conflates with the real void type), use the dedicated .unresolved sentinel.
They follow addFmt(.err) so compilation aborts before codegen; behavior is
unchanged, the sentinel is now correct. 236 + unit green.
2026-05-30 01:21:34 +03:00
agra
76e0e97bfa issues/0051: mark FIXED (sdl3 chdir + ad-hoc signing) 2026-05-30 01:16:09 +03:00
agra
b31fbae757 platform/sdl3: chdir to .app bundle on macOS so CWD-relative assets resolve
A macOS .app launched with CWD=/ (Finder/open) could not find CWD-relative
assets (read_file_bytes("assets/...")) and crashed in stbtt with a null font.
SdlPlatform.init now chdirs to SDL_GetBasePath() when running from inside a
.app bundle (detected by ".app" in the base path), mirroring uikit.sx s iOS
chdir_to_bundle. Gated so the sx run dev flow (binary not bundled) keeps the
project CWD. Verified: direct-exec with CWD=/ now stays alive (was: instant
stbtt segfault). Filed issue 0051 with the analysis.

Note: launching via Finder/open additionally triggers Gatekeeper App
Translocation for the dev-signed bundle (separate code-signing concern, not
the asset path).
2026-05-30 01:10:13 +03:00
agra
3731a200c3 ir: convert remaining s64 var-init fallbacks + fix stale s64 sentinel checks
Var-init placeholders that could leak when a lookup failed now init to
.unresolved: struct field-not-found (lowerFieldAccess/store), match payload
variant-not-found, deref-of-non-pointer pointee, array-literal element type.

Also fixes checks that used .s64 as the "resolution failed" sentinel and broke
when the producing functions started returning .unresolved instead:
- array-literal: `resolved != .s64` -> `!= .unresolved`.
- parameterized type-alias registration and pack-fn return-type resolution:
  `!= .s64` -> `!= .unresolved` (also fixes a latent bug where a genuine
  `s64` alias / `-> s64` return was treated as a failure).
- the variadic Any-boxing refinement (infer, then upgrade via getRefType) now
  triggers on .unresolved, not .s64, matching the honest inferExprType.

Every silent s64 fallback in the codebase is now gone; only genuine s64<->name
mappings and the defined int-literal/tag-width defaults remain. 236 + unit green.
2026-05-30 00:54:07 +03:00
agra
f21b99c811 sema/ir: kill remaining s64 fallbacks (sema Type + getRefType)
- types.Type: add dedicated `unresolved` variant (mirrors ir.TypeId.unresolved)
  with eql/displayName arms; bridgeType maps it to TypeId.unresolved.
- sema.inferExprType + signature/field resolution: every Type.fromTypeExpr /
  fromName / symbol lookup miss and call/field/index fallthrough now yields
  Type.unresolved instead of a fabricated s(64). A variadic `..xs: []T` slice
  element is taken from T, not a guessed "s32". Genuine literal defaults
  (int=>s64, float=>f32, .len=>s64) kept.
- Builder.getRefType: an unlocatable ref (no active function / out-of-range)
  returns .unresolved, not .s64 -- this is the accurate type source the pack
  mono / binop / null-cmp fixes rely on, so it must not fabricate.

236 examples + unit tests (incl sema) green.
2026-05-30 00:38:23 +03:00
agra
8bc2ed4c49 ir: bridgeType non-standard int widths intern the exact width, not s64
A signed/unsigned width other than 8/16/32/64 quantised to s64/u64, silently
changing the size. Intern the exact .signed/.unsigned width instead (the IR
supports arbitrary-width ints). The default tagged-union tag width
(tag_type orelse .s64) is kept -- it is a defined language default, not a
failed lookup. 236 + unit green.
2026-05-30 00:30:48 +03:00
agra
d018541917 ir: remaining lowering .s64 fallbacks -> .unresolved
Converts the leftover silent s64 guesses in lowering/type-resolution paths:
- target_type orelse .s64 in struct/tagged-union/enum-literal lowering and the
  xx-cast destination (the isBuiltin-guarded ones skip cleanly; the rest now
  surface instead of fabricating an int).
- resolveTypeArg / parameterized-type callee-name else arms.
- generic-mangle type-param binding miss (bindings.get orelse .s64).
- optional-child helper fallthrough.
Kept the genuine int/float-literal defaults (info.ty orelse .s64/.f64) which
are the language rule, not a lookup failure. 236 examples + unit green.
2026-05-30 00:29:03 +03:00
agra
c6626b4f1a ir: make inferExprType honest (.unresolved, not .s64) + fix its consumers
inferExprType now returns .unresolved when it genuinely cannot infer a type,
instead of silently guessing .s64. To keep codegen correct, every consumer
that turns inference into a concrete type was fixed to resolve it properly
rather than lean on the fake s64:

- pack-fn mono: value-pack params type from the lowered Ref (getRefType);
  comptime ..$args prefers inference (int-literal default is s64) and falls
  back to the lowered type only when inference cannot tell.
- if-expr / match merge result type: fall back to the contextual target_type
  when the branch/arm type is not statically inferable; a statement match with
  non-value arms stays void (do not let a leaked target_type make it a value).
- inferExprType call arm: resolve a not-yet-lowered function return type from
  fn_ast_map (void for a return-less fn) instead of falling through.
- lowerBinaryOp: type the result from the lowered LHS when inference is
  unresolved (e.g. #objc_call(...) * 2).
- null comparison (x == null): lower the non-null side first and take the
  null type from it, never a guess.

A consequence: `xx enum` with no target type now boxes as Any (prints the
variant name) instead of the silent-s64 int -- examples/52 snapshot updated to
the honest output. 236 examples + unit tests green.
2026-05-30 00:26:51 +03:00
agra
a9c116ebb1 ir: type value-pack mono params from lowered args, not pre-lowering inference
lowerPackFnCall computed pack arg types via inferExprType *before* lowering
the args, then lowered them anyway. For a value-pack (..xs: P) the lowered
value has an authoritative concrete type, so take the pack type from
getRefType of the lowered Ref instead of a speculative inferExprType guess --
this removes the dependency that made a monomorphised pack param able to end
up wrong/.unresolved from incomplete static inference. Comptime ..$args packs
keep inferExprType (their args may be type-position). Also drops the dead
runtime_arg_types list (collected, never read). 236/236 green.
2026-05-29 23:19:02 +03:00
agra
8681b72b47 ir: type-resolution fallbacks return .unresolved, not .s64 (batch A)
resolveFieldType (field-not-found, tuple OOB/parse-fail), getElementType
(element-of-a-non-collection), resolveArrayLiteralType, and the named-type
lookup in the type-call resolver all guessed .s64 when resolution failed --
the issue-0042 silent-default class. Return .unresolved so a genuine
resolution failure surfaces (and trips the sizeOf/toLLVMType panic) instead
of fabricating an 8-byte int. Genuine results (.len => .s64) unchanged.
2026-05-29 22:53:53 +03:00
agra
99baabd93f ir: resolveTypeWithBindings pack-index errors return .unresolved, not .s64
The OOB-index and missing-binding cases already emit a real user-facing
diagnostic, but returned a plausible .s64 -- which would silently fabricate
an 8-byte int if compilation continued past the error. Return the
.unresolved sentinel instead (trips the sizeOf/toLLVMType panic at codegen).
Diagnostic text unchanged, so snapshots are unaffected.
2026-05-29 22:41:03 +03:00
agra
2836fe4d8d ir: resolveAstType pack-index arm returns .unresolved, not .s64
The pack-aware caller (resolveTypeWithBindings) resolves pack-index type
exprs against the active binding before delegating, so reaching this bare
type_bridge path means the binding was missing. .s64 silently fabricated
an 8-byte int; return the .unresolved sentinel so it surfaces (trips the
sizeOf/toLLVMType panic at codegen). Closes the last .s64 escape in
resolveAstType.
2026-05-29 22:35:32 +03:00
agra
b91b7f882c ir: resolveAstType null-node returns .unresolved, not .s64
A null type node means a caller reached type resolution without a type
node. Every current caller passes a non-optional node or handles the
"no type" case itself (returning .void), so a null here is a caller bug;
.s64 silently fabricated an 8-byte int. Return the .unresolved sentinel
so it surfaces (trips the sizeOf/toLLVMType panic at codegen).

The only thing relying on the old behavior was a unit test asserting
null => .s64 -- i.e. a test pinning the silent default. Updated it to
pin .unresolved.
2026-05-29 22:33:47 +03:00
agra
171c694f6c ir: resolveAstType unhandled-node else arm returns .unresolved, not .s64
A non-type AST node reaching type resolution is a caller bug; returning a
plausible .s64 silently fabricated an 8-byte int. Return the .unresolved
sentinel so it surfaces (and trips the sizeOf/toLLVMType panic if it ever
reaches codegen). The stderr breadcrumb stays. No test exercised this arm
(suite unchanged), so nothing was relying on the fabricated s64.
2026-05-29 22:29:45 +03:00
agra
55e62694d1 ir: dedicated TypeId.unresolved sentinel; kill inferred_type => .s64
An unannotated param resolving to a plausible .s64 was the classic
silent-default trap (root of the 2.5 multi-param-closure bug). Replace it
with a dedicated TypeId.unresolved at slot 0, so a zero-initialised or
forgotten TypeId trips the sentinel instead of masquerading as a real type.

- types.zig: TypeId.unresolved = 0 (void moves to 17); TypeInfo.unresolved;
  sizeOf/toLLVMType @panic on it (codegen tripwire); hash/eql/printer cover it.
- type_bridge: inferred_type => .unresolved (was .s64).
- resolveParamType: emit "parameter 'x' has no type annotation" for a
  genuinely-unannotated value param (comptime/variadic/pack params exempt --
  they resolve via per-call substitution).
- lowerLambda: resolve unannotated params from the target closure signature;
  otherwise emit "cannot infer type of lambda parameter".
- CLAUDE.md: .void documented as an UNACCEPTABLE failed-type sentinel (it
  conflates with a real, heavily-checked type); prescribe a distinct
  .unresolved-style value + codegen tripwire.

Snapshot churn: one .ir (ffi-objc-call-06) -- the runtime type-name table and
typeof match arms renumber by the new builtin slot; program output unchanged.
2026-05-29 22:25:45 +03:00
agra
5fd513466f lang F1 2.5: contextual typing for multi-param closure literals
An untyped lambda (a, b, c) => ... now takes each param's type
positionally from the expected Closure(T0, T1, T2) -> R signature, for
heterogeneous param types, in both assignment and argument position.

Previously only the first param (or all-same-typed params) resolved:
lowerLambda's signature loop applied contextual typing into params, but
the return-type-inference temp scope and the body param binding both
re-resolved each param via resolveParamType -- which defaults an untyped
(inferred_type) param to s64. So b in Closure(s64, string) bound as s64
and b.len errored. Both sites now read the already-resolved signature
types params.items[user_param_base + i].ty (user_param_base skips the
pre-populated ctx/env slots).

Regression: examples/201-closure-contextual-params.sx.

Note: a generic return $R inferred through a closure-typed parameter is
still unresolved (folds into Phase 4 function monomorphization); concrete
returns work.
2026-05-29 22:00:42 +03:00
agra
27c88d4d26 lang F1: range-based for + inline-for unroll over packs
Add range loop syntax:
- runtime  for start..end (i) { }   counting loop, cursor optional, end exclusive
- comptime inline for start..end (i) { }   comptime-unrolled body

The inline form binds the cursor as an int_val comptime constant per
iteration, so xs[i] over a heterogeneous pack substitutes the concrete
per-position element -- the canonical's pack-iteration vehicle
(inline for 0..sources.len (i) { sources[i].addListener(...) }).

- AST: ForExpr.range_end, ForExpr.is_inline
- parser: parseForExpr range vs collection form; suppress_call flag so
  N (i) is not read as a call N(i) while parsing a range bound
- lower: lowerRuntimeRangeFor / lowerInlineRangeFor; evalComptimeInt;
  comptimeIndexOf extends pack-index resolution beyond int literals

Revises spec's inline for i in 0..N to the no-in, range-first, paren-cursor
form. Regression: examples/200-for-range.sx.
2026-05-29 21:36:17 +03:00