Commit Graph

35 Commits

Author SHA1 Message Date
agra
d6a9c4f0c4 fix(diagnostics): locate import parse errors in the imported file
A parse error raised while resolving an `#import` was rendered against the
ROOT file's source — the caret landed on an unrelated line (often a comment)
even though the message named the correct imported file.

Two compounding causes:
- core.zig wired `diagnostics.import_sources` only AFTER import resolution
  returned, but a parse error aborts mid-resolution (before that wiring), so
  the renderer had no imported sources and fell back to the root file. Wire it
  (and seed the main-file source) BEFORE resolving.
- imports.zig emitted the diagnostic at the importer's `#import` span instead
  of the parser's actual error offset inside the imported file, and didn't pin
  the diagnostic's source_file to that file.

parser.zig now records `err_end` alongside `err_offset` for a proper caret
width. New `DiagnosticList.addFmtInFile` renders against an explicit source
file; imports.zig uses it with `importErrSpan(&p)`.

Regression test: examples/1176-diagnostics-import-parse-error-location
(importer + deliberately-broken companion; caret must land in the companion).
2026-06-15 15:09:40 +03:00
agra
dc51c4b5bf refactor(ffi-linkage): Phase 9.3-src — purge 'foreign' from src/ comments + a user-facing diagnostic
Reword every 'foreign' comment to the extern/runtime-class vocabulary matching the
renamed identifiers (foreign call→extern call, foreign class→runtime class, foreign
path→runtime path, the #foreign-literal comment mentions → extern, etc.). Also fixes
two USER-FACING issues: the 'expected … #foreign … after type annotation' parse error
no longer advertises the removed keyword, and the Android 'no #jni_main' help
diagnostic now shows '#jni_class(…) extern' instead of the rejected '#foreign
#jni_class'. Removed the now-dead prefix-#foreign-vs-postfix conflict branch in
parseRuntimeClassDecl (the caller rejects #foreign before it runs).

src/ now contains 'foreign' ONLY in the hash_foreign token machinery + its 4
rejection messages — the deprecation mechanism (kept per the 9.0 recommendation; the
message MUST name #foreign to guide migration). Snapshot-neutral; suite green
(646 corpus / 444 unit, 0 failed).
2026-06-15 09:35:00 +03:00
agra
5c8af6eb73 refactor(ffi-linkage): Phase 9.2b — rename runtime-class fns + state → runtime_* / is_reference
The runtime-class object-model identifiers (Decision 5): parse/lower/find/resolve/
register/stamp fns Foreign→Runtime (parseRuntimeClassDecl, lowerRuntimeMethodCall,
findRuntimeMethodInChain, resolveRuntimeMethodReturnType, registerRuntimeClassDecl,
runtimeClassStructType, runtimeKindForOffset, …); state foreign_class_map→
runtime_class_map, current_foreign_class/_method→current_runtime_*, the
foreign_class_decl union variant→runtime_class_decl, foreign_method/static/instance/
class→runtime_*; and the reference-vs-define flag is_foreign→is_reference (+
is_foreign_eff→is_reference_eff) now that it only lives on RuntimeClassDecl.
Snapshot-neutral; suite green (646/444).

Remaining 9.2: the foreign_path family (coupled .sx hooks: jni_main_foreign_path_at
spans build.sx/bundle.sx/compiler_hooks.zig/specs.md) + the extern-ref validators
(checkForeignRefs etc. → Extern, linkage not runtime) + bare 'foreign' comments.
2026-06-15 09:01:04 +03:00
agra
3354446412 refactor(ffi-linkage): Phase 9.2a — rename runtime-class TYPE names → Runtime* (Decision 5)
Mechanical, collision-free PascalCase renames (object-model axis, not linkage):
ForeignClassDecl→RuntimeClassDecl, ForeignMethodDecl→RuntimeMethodDecl,
ForeignClassMember→RuntimeClassMember, ForeignFieldDecl→RuntimeFieldDecl,
ForeignRuntime→RuntimeKind, ForeignClassPrefix→RuntimeClassPrefix. Snapshot-neutral;
suite green (646/444). Remaining 9.2: snake_case state (foreign_class_map,
current_foreign_class, foreign_path [coupled to .sx hooks], the foreign_class_decl
union variant) + the parse/lower/resolve fn names + ForeignClassDecl.is_foreign flag.
2026-06-15 08:57:53 +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
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
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
2b8041a828 cleanup: drop resolved-issue citations from src comments
Sweep all src/**.zig comments that cite resolved issues (issue NNNN /
fix-NNNN / KB-N): the invariant or mechanism each comment states is
kept; the historical citation is dropped, per the no-conclusion-comments
rule. Pure-history parentheticals are removed outright. References to
the 16 still-open issues (0030, 0041-0056) are untouched, as are test
NAMES carrying regression provenance (matching the sanctioned
"Regression (issue NNNN)" example-header convention).

Also removes the issues/0019-import-non-transitive-c-scope/ fixture dir
— the issue is superseded and its behavior is covered by
examples/0706-modules-import-non-transitive.sx (the .md writeup stays).
issues/0030's repro .sx stays: that issue is an open feature request.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 16:34:17 +03:00
agra
cd7510067f refactor(stdlib/S1.2): delete module_fns; dissolve legacy_direct_any [additive]
Delete module_fns as a separate function-author fact source. Its authors
already live in the module_decls raw facts, so lowerRetainedSameNameAuthors now
reads function authors straight out of module_decls (filtered to *FnDecl via
fnDeclOfRaw) — the same path → name → RawDeclRef store, fn-filtered. Remove
imports.ModuleFns / FnIndex / indexModuleFns / buildModuleFns / fnDeclOf, the
Compilation.module_fns field + its build + wiring, and ProgramIndex.module_fns.

Remove VisibilityMode.legacy_direct_any (the quarantined own-scope-plus-full-
import_graph mode): no production caller passed it, so the collectVisibleAuthors
and isVisible switch arms that handled it are dead and go too, collapsing
VisEdgeSet to the single flat-import walk. No semantic fallback is introduced;
import_graph stays the transitive-visibility source for findVisibleImpls.

Additive: the old maps stay active and lowering still consumes them — no
lowering consumer is cut over to the DeclTable (that is S3), and no resolution
behavior changes. Tests that drove the removed symbols are rerouted through
module_decls / the flat-edge walk.

Gate over the baseline-green corpus: zig build, zig build test (424/424),
bash tests/run_examples.sh (540 passed) — all exit 0; single-author output
byte-identical; multi-author 0722–0740 stdout/exit unchanged.
2026-06-09 11:36:04 +03:00
agra
8058be2538 feat(stdlib/S1.1): DeclId for every declaration — additive DeclTable [additive]
Build a DeclTable in parallel with the import facts: every RawDeclRef
(source / imported / namespaced / C-imported) gets a stable DeclId carrying
source path, display name, AST node identity, span, and DeclKind. Namespace
targets record their members' DeclIds (NamespaceTarget.member_ids). A generic
struct's template is keyed by DeclId in a parallel struct_template_by_decl
store, written alongside the live name-keyed struct_template_map.

A Debug-only round-trip cross-check (RawDeclRef -> DeclId -> AST node ptr)
asserts the table identifies the same node across the corpus, run from
buildDeclTable and pinned by a unit test.

Additive (S0.1 class: mirror): the old maps stay active and lowering still
consumes them; nothing reads the DeclTable / struct_template_by_decl for
selection yet (the S4 cutover does). Generated IR + output bytes are unchanged
by construction.

Gate over the baseline-green corpus: zig build, zig build test (424/424),
bash tests/run_examples.sh (540 passed) — all exit 0; single-author output
byte-identical (37 .ir snapshots unchanged).
2026-06-09 11:25:04 +03:00
agra
5df4ac61a7 fix(stdlib/E5): source-aware same-name VALUE consts (own-wins / ambiguous / cross-module expr-chains)
Re-land the value-const analog of the E1-E4 type work, reconciled onto the
current source-keyed resolver and hardened. A same-name VALUE const declared in
multiple flat-imported modules is now resolved per declaring source, not the
global last-wins `module_const_map`.

- imports.zig: `isPerSourceDecl` retains every non-function `const_decl`
  per-source (value consts + type aliases), so each same-name author reaches
  registration as a distinct author of its own module. Functions and var_decls
  keep first-wins.
- lower.zig:
  * `selectModuleConst` over `module_consts_by_source` — own-wins; exactly one
    flat-visible resolves; >=2 flat-visible bare -> loud ambiguous (consistent
    with the 0755 type / 0724 fn / 0782 generic ambiguities). Rewires every
    consumer: `comptimeIntNamed`, the runtime-id read, the global-init read,
    and the float-name path (`lookupFloatName` / `nameIsFloatTyped`).
  * `SourceConstCtx` + `foldSourceConstInt`/`Float` + `sourceConstIsFloatTyped`
    fold a selected const's RHS with nested same-name leaves re-selected in
    their own author source, so VALUE and array-DIMENSION results are coherent.
  * `pinConstAuthorSource` pins each fold level to the SELECTED const's author
    (F1), including multi-level cross-module chains.
  * cycle guard keyed on (name, author-source), not name alone (F3), so
    same-name nested consts across modules do not trip a false cycle.
  * `emitModuleConst` takes the author source and pins while folding/lowering.
  Registration-time struct/inline-type field dimensions route through the now
  source-aware stateful reader; the type-alias dimension path resolves each
  alias against its own author's consts.
- program_index.zig: expose `isFloatConstType` / `isCountableConstType` for the
  source-aware folds.

examples: 0786 own-wins, 0787 ambiguous (exit 1), 0788 expr-chain value+dim
coherent, 0789 leaf-author-pin, 0790 cross-module cycle-guard (F3), 0791
multi-level cross-module chain, 0792 struct-field registration-time dim.
Single-author corpus byte-identical (524 prior markers green); 531 total.
2026-06-08 21:29:31 +03:00
agra
9d5143aee6 fix(stdlib/E4): source-pin sx-defined objc-class IMP trampolines + finish non-transitive bare-TYPE gate
Final E4 piece: the IMP trampolines emitted for an sx-defined #objc_class
resolved their method-signature types (e.g. -> BOOL) at whatever lowering
site triggered emission, not the class's defining module — so under the
single-hop bare-TYPE gate a 2-flat-hop objc type (BOOL via uikit->objc)
leaked as 'not visible' when m3te's main triggered emission.

- ast.ForeignClassDecl gains source_file (stamped by resolveImports, like
  ProtocolDecl/StructTemplate); stampFnBodySource stamps the decl + each
  bodied method body.
- emitObjcDefinedClassImps pins current_source_file to fcd.source_file for
  the whole per-class emission (alloc/dealloc/method/property IMPs).
- Removes the BOOLLEAF debug probe.

Completes E4: bare-TYPE visibility is single-hop non-transitive across all
member kinds; every instantiation kind (generic struct/fn, pack fn, param
protocol, type fn, objc-block, objc-class IMP) is source-pinned to its
defining module. Full gate green; m3te ios-sim builds + launches (exit 0).
2026-06-08 11:22:05 +03:00
agra
33a6f5c650 wip(E4): partial source-pin + non-transitive flip [stdlib E4 attempt-1 WIP checkpoint]
Incomplete WIP from a worker killed at the 55-min wall (large blast radius:
core source-pin + ~8 example migrations + ~10 library module migrations).
Committed so the resumed session continues on a clean tree. May not build.
2026-06-08 11:12:08 +03:00
agra
f8efa25416 revert(stdlib): narrow E2 to the 0105 type/alias close; defer value consts to E5 [stdlib E2 attempt-6]
Scope-narrowing revert of the value-const same-name sub-area (attempts 3-5),
per PO/Agra ruling. The 0105 type/alias close (per-source nominal struct
identity, source-keyed type aliases, F1 self/mutual refs, anon-struct
regression) is kept intact; cross-module same-name VALUE consts move to step E5.

- imports.zig: narrow `isPerSourceDecl` so a `const_decl` is retained
  per-source ONLY when its RHS introduces a TYPE (alias / inline type decl).
  VALUE consts (literal / value-expression RHS) and functions keep the pre-E2
  first-wins name-merge. Restores value-const reads to exactly the
  wt-stdlib-base (pre-E2) first-wins behavior.
- lower.zig / program_index.zig: restored to the pre-value-const state
  (66d10c0) — removes selectModuleConst / SourceConstCtx / pinConstAuthorSource
  / SelectedConst and the rewired comptimeIntNamed / float / runtime /
  global-init const reads; value-const reads return to the global path.
- examples: drop 0759-0762 (value-const own-wins / ambiguous / expr-chain-dim
  / leaf-author-pin) — they move to E5.

Kept green: 0752-0758 (same-name structs distinct + own-wins + ambiguous + self
/mutual ref), 0756 (alias per-source), 0170 (anon-struct field distinct).

Gate: zig build + zig build test (423/423, LSP sweep 513 no-crash) +
run_examples (496/0, prior markers byte-identical) + m3te ios-sim build exit 0.
2026-06-08 07:28:31 +03:00
agra
d98ad5c14f feat(stdlib): per-decl nominal identity + same-name shadows — close 0105 [stdlib E2]
Make same-name top-level types in different sources DISTINCT nominal types
instead of collapsing last-wins in the type table (issue 0105).

Registration:
- internNamedTypeDecl assigns a per-decl nominal_id and populates
  type_decl_tids. The first author of a name keeps nominal_id 0 (byte-identical
  to pre-E2); a genuine cross-module shadow (>=2 distinct normalized-path
  authors per the import facts) gets a fresh id -> a distinct TypeId.
- mergeFlat/addOwnDecl stop first-wins-dropping per-source decls (named types +
  non-fn const_decls) so every same-name author reaches registration; functions
  and var_decls (incl. #foreign extern globals) keep first-wins.

Resolution (selectNominalLeaf):
- own-author wins; else flatTypeAuthorCount over the transitive flat closure:
  >=2 distinct -> .ambiguous (loud diagnostic + poison); exactly one -> resolved;
  a flat author not yet findByName-registered -> .undeclared stub (not a leak).
- struct-literal type names route through the same source-aware leaf.
- lazyLowerFunction pins the function's own source before resolving its return
  type, so a shadowed signature type resolves in its module, not the caller's.

Codegen:
- mangleTypeName appends __n<id> for nonzero nominal_id so same-name shadows get
  distinct monomorph symbols (struct_to_string__Box vs __Box__n1).

Library hygiene:
- rename trace.sx's compiler-contracted Frame -> TraceFrame (+ the two compiler
  findByName sites) so it never collides with a UI/geometry Frame; the layout is
  structural (getFrameStructType / SxFrame), name-independent.

Examples: 0752-0756 pin the five 0105 cases (distinct fields / same fields /
own-wins / ambiguous / alias per-source); 0170 pins the folded anon-struct-field
regression.
2026-06-07 22:57:28 +03:00
agra
b62223edaf fix(lower): pin defining-module context for pack/comptime metaprograms; drop #insert exemption [stdlib B attempt-3]
ROOT FIX for issue 0106's library-metaprogram half — no exemption.

attempt-2 masked the 0106 fallout with an `in_insert_expansion` flag that
made the visibility adapters fall open during ANY `#insert` expansion,
including a USER's `#insert <expr>` — so a bare reach into a namespaced-only
import from user `#insert` code wrongly compiled (Adi's blocker). The flag
was the wrong shape. This removes it and fixes the real cause.

Root cause: a metaprogram's body (`std.print` / `std.format` / `log.*`,
whose `#insert build_format(fmt)` + `#insert "out(result);"` reference
std-internal bare names) was lowered under the CALL SITE's
`current_source_file`, so those names were policed against the consumer's
imports. Normal functions get this right via `lowerFunctionBodyInto`, which
pins `func.source_file`; the two monomorphizers don't:
  - `monomorphizePackFn`   — bare `print(...)` / `format(...)` (pack path).
  - `lowerComptimeCall`    — namespaced `std.print` / `log.warn` (reached via
                             the field-access `hasComptimeParams` branch).

Fix: both paths now save/set/restore `current_source_file` to the body's
DEFINING module around the BODY lowering only (call-site args stay in the
caller's context). The defining path is stamped onto each function body node
by `resolveImports` (`stampFnBodySource`), mirroring `Function.source_file`.
So library internals resolve in std.sx/log.sx naturally, while a USER's
`#insert <expr>` is still checked in the user's context.

- Exemption GONE: `in_insert_expansion` flag + both adapter fall-open checks
  deleted; `isNameVisible`/`isCImportVisible` are byte-identical adapters.
- New pinned regression: examples/0737-modules-insert-bare-not-visible.sx
  (+ a.sx) — a USER `#insert secret()` into a namespaced-only import errors
  ('secret' is not visible). fail-before exit 0 on the attempt-2 binary /
  pass-after exit 1.
- face #1 (0736) still errors; face #2 (0015/0700/0718/1030) pass again WITH
  NO exemption — the metaprogram body resolves in its own module.
- run_examples 472 -> 473; zig build test 412/412; m3te ios-sim build exit 0.
- issues/0106 RESOLVED banner updated (root cause + no-exemption fix).
2026-06-07 06:09:28 +03:00
agra
7158337c73 wip(resolver): collectors + unified predicate + tightened adapters [stdlib B, BLOCKED on 0106]
Collectors (resolver.zig: collectVisibleAuthors/collectNamespaceAuthors + AuthorSet
+ VisibilityMode, 4 unit tests) + unified visibility predicate + isNameVisible/
isCImportVisible adapters routed to flat modes. Tightening surfaces issue 0106
(stdlib comptime expansion relies on the over-permissive import_graph join), so
run_examples is 467/471 here. attempt-2 folds in the coupled comptime-context fix.
2026-06-07 04:52:56 +03:00
agra
5f06b6504f fix(imports): diagnose namespace-alias dup + propagate buildImportFacts errors [stdlib A attempt-2]
Two defects from the Phase A attempt-1 review.

F1 — duplicate-name diagnostic missed NAMESPACE ALIASES (silent first-win).
`addNamespace` unconditionally put the alias into scope/own_decls, so a
same-module collision between an authored decl and a `dup :: #import "…"`
alias compiled clean in the fn-then-alias order (the scalar
ModuleRawDeclIndex silently first-won). Now `addNamespace` returns a bool
and refuses a same-module duplicate (mirroring addOwnDecl); the call site
surfaces it via the new `reportDuplicateName` (the import_decl node has no
declName, so the alias name is passed explicitly). The C-import namespace
site gets the same guard. Both orders now emit "duplicate top-level
declaration 'X'" and exit nonzero (alias-then-fn was already caught by
addOwnDecl seeing the alias in scope).

F2 — buildImportFacts errors were swallowed by `else |_| {}` in core.zig
(REJECTED-PATTERN catch-all leaving the borrowed store silently empty).
`resolveImports` returns !void, so the call is now a plain `try` and a
build failure propagates instead of producing a stale/empty store.

Tests: extend the dup-name regression with fn-vs-namespace-alias
collisions in both orders. No resolution behavior change (no lower.zig
edits; run_examples 471 byte-identical); m3te ios-sim builds via the
worktree binary.
2026-06-06 23:54:51 +03:00
agra
b5ec121645 feat(imports): buildImportFacts raw-fact store (ModuleRawDeclIndex + NamespaceEdges) [stdlib A]
Phase A of the unified resolver (R5 locked design). Additive infrastructure
with NO behavior change — builds the import-side raw-fact store; nothing
consumes it yet.

- imports.zig: add RawDeclRef / RawAuthor / ModuleRawDeclIndex / ModuleDecls /
  NamespaceTarget / NamespaceEdges, plus buildImportFacts (mirrors
  buildModuleFns) producing a scalar per-module name→RawDeclRef index + the
  namespace edges. Callable without IR lowering (LSP reuses it later).
- ast.zig: NamespaceDecl gains target_module_path, captured at resolution time
  (the resolved_path otherwise lost on the node) so the namespace edge records
  the alias target.
- imports.zig: same-module duplicate top-level name is now DIAGNOSED
  ("duplicate top-level declaration 'X'") where addOwnDecl would silently drop
  the second author — replaces the discarded `_ =` at the three call sites.
- program_index.zig: borrowed views module_decls / namespace_edges (like
  module_fns); deinit does not free them.
- core.zig: build the facts alongside buildModuleFns and point the borrowed
  views at them.
- imports.test.zig: index unit tests (flat / directory / namespaced file /
  namespaced directory / C-import namespace / same-name fn / same-name struct /
  value-vs-type same spelling / raw const_decl) + the duplicate-name diagnostic
  regression (fails pre-fix, passes after).

Gate (worktree): zig build, zig build test (incl. LSP corpus sweep), and
run_examples (471, byte-identical) all green; m3te ios-sim build exits 0.
2026-06-06 23:34:32 +03:00
agra
3dbc6f8434 fix(imports): keep merged scope first-wins; index dups in module_fns only [0102a]
Attempt-1 retained a same-name cross-module FUNCTION author in the merged
decl list (mergeFlat + the directory merge), which is the list the existing
first-wins resolver consumes. That changed the data feeding resolution
(`mod.decls` carried two `greet`), violating this step's purpose: additive
indexes with ZERO resolution change.

Revert both merge sites to byte-for-byte first-wins, exactly as on
wt-fix-0102-base. The dropped same-name author is still retained — but only
in the SEPARATE `module_fns` index, which is built from each module's
`own_decls` (un-deduped, per-path) and which nothing reads yet. The
`flat_import_graph` side data is likewise untouched. Both are foundation
for fix-0102c's bare-name disambiguation; current resolution is unchanged.

Drop the now-unused `declAuthorsFn` helper (its only callers were the two
merge sites). `fnDeclOf` stays — it feeds the index.

Tests: the existing unit test now asserts the merged scope stays first-wins
(one `greet`, a.sx's author) while `module_fns` still retains BOTH authors
and `flat_import_graph` excludes the namespaced edge. Add a mixed non-fn/fn
collision test asserting the merged scope keeps a.sx's struct (first-wins),
unchanged by the function author.
2026-06-06 11:53:16 +03:00
agra
ff9cb50079 refactor(imports): retain dup same-name fn authors + build identity indexes [0102a]
First of four fix-0102 sub-steps. Purely additive: retains data that the
flat/directory merge currently first-wins-drops and builds two identity
indexes for later bare-name disambiguation (fix-0102c). No resolution
change — the existing first-wins bare path still wins; suite unchanged.

- mergeFlat + directory merge: stop dropping a same-name FUNCTION authored
  by a different module/file. Non-function decls keep first-wins dedup; node
  identity dedup is untouched.
- flat_import_graph: a flat-only subset of import_graph, recording an edge
  only for a bare `#import` (imp.name == null), never a namespaced
  `ns :: #import`. Threaded through resolveImports/resolveDirectoryImport
  and into ProgramIndex.
- module_fns (path -> name -> *const FnDecl): per-module authored-function
  index mirroring module_scopes, built in core.zig from the main module +
  cache. Same-name cross-module authors stay distinct under their own paths.
- imports.test.zig: asserts both a.sx/b.sx greet authors are retained in
  module_fns and in the global flat list, and that flat_import_graph
  excludes the namespaced edge while import_graph includes it.

Gate (this worktree): zig build, zig build test (398/398),
bash tests/run_examples.sh (457 passed) all green.
2026-06-06 11:27:11 +03:00
agra
3edc67521b fix(lower): resolve cross-module same-name functions by identity [0100]
Two modules each exporting a top-level function with the same short name
(std.cli.parse 3-param, std.json.parse 2-param) collided in IR lowering's
bare-name function table. fn_ast_map (name -> AST) was last-wins while
module.functions / resolveFuncByName are first-wins, so importing both and
calling one bound one function's AST against the other's FuncId and tripped
lazyLowerFunction's param-count assert (lower.zig:1606) — reached
unreachable code.

Fix:
- Register a namespaced import's OWN plain functions under their qualified
  name (ns.fn) in fn_ast_map, giving cli.parse / json.parse independent
  identities. The qualified resolution paths in CallResolver.plan /
  lowerCall already prefer ns.fn. NamespaceDecl now carries own_decls
  (populated in imports.addNamespace). Generic/comptime/pack/foreign
  functions are excluded (they dispatch by monomorphization off the bare
  template name); no eager declareFunction (it would resolve types before
  the forward-alias fixpoint).
- Make scanDecls' bare fn_ast_map registration first-wins so a later
  namespace recursion cannot clobber an earlier (flat) entry, aligning it
  with mergeFlat / resolveFuncByName.

Regression: examples/0719-modules-cli-and-json.sx imports both std.cli and
std.json under distinct namespaces and calls both parses; panics pre-fix,
passes after. issues/0100 marked RESOLVED.
2026-06-06 02:30:19 +03:00
agra
023971cae5 feat(lang): universal backtick raw identifier — valid in value, decl, AND type position [F0.6]
AGRA ruling (attempt 4): `` `name `` is THE LITERAL identifier `name`, usable in
EVERY position — the backtick only means "treat this token as a plain identifier,
never the reserved keyword/type", and is never part of the name's text.

- Raw in TYPE position is now VALID (reverses attempt-2 "raw is not a type"):
  `parseTypeExpr` emits a raw `type_expr`; `TypeResolver.resolveNamed` gains a
  `skip_builtin` flag (threaded from `te.is_raw` via lower.zig + type_bridge) so a
  `` `s2 `` reference resolves to a `` `s2 ``-declared type (struct/enum/union/alias),
  else a normal "unknown type 's2'" error (reportIfUnknownType skips the builtin
  exemption when raw). Bare `s2` in type position stays the builtin int.
- Every declaration-name site is is_raw-exemptible: `is_raw` added to TypeExpr +
  StructDecl/EnumDecl/UnionDecl/ErrorSetDecl/ProtocolDecl/ForeignClassDecl/UfcsAlias/
  NamespaceDecl/ImportDecl/CImportDecl/LibraryDecl; parser threads name_is_raw to
  every decl parse fn; namespace imports carry it through imports.addNamespace.
  Typed-const path (`` `s2 : s64 : 5 ``) now threads name_span+is_raw (fixes the
  1:1-caret bug).
- Check<->exemption made structurally symmetric: checkBindingName/checkDeclName take
  is_raw as a REQUIRED argument and skip inside the check, so no call site can
  validate a name without honoring the exemption (the desync cause of prior rounds).
- Bare reserved-name declarations of every kind still error (0076 preserved);
  `#import c` foreign names stay auto-raw + bare-callable.

specs.md + readme.md updated to the universal model. issue 0089 RESOLVED banner
rewritten. Examples: replace 1139 (raw-not-a-type) with 0154 (raw type reference);
add 0155 (typed const + union tag) and 1141 (bare type-decl negatives).
Gate: zig build + zig build test + run_examples (426 passed, 0 failed).
2026-06-04 20:27:53 +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
29784c22a8 mem: implicit-context foundation + many compiler fixes
The session-long set of changes that lay the groundwork for the
Jai-literal implicit-Context-parameter refactor. Lots of accumulated
work; the new arrival is the implicit-ctx foundation (steps 1+2 of
the plan in current/CHECKPOINT-MEM.md):

  Step 1 — `CAllocator :: struct {}` stateless allocator in
    library/modules/allocators.sx, delegating directly to
    libc_malloc/libc_free. `ConstantValue` in src/ir/inst.zig gains a
    `func_ref: FuncId` leaf so nested aggregates can carry function
    pointers (the inline Allocator value's fn-ptr fields). Switch
    sites updated in emit_llvm.zig, print.zig, interp.zig.

  Step 2 — `emitDefaultContextGlobal` in src/ir/lower.zig synthesises
    a static `__sx_default_context` global with a nested-aggregate
    init_val pointing at the CAllocator → Allocator thunks. The
    second-pass `initVtableGlobals` in emit_llvm.zig is generalised
    to handle `.aggregate` init_vals (re-emits after func_map is
    populated so func_ref leaves resolve to real symbols).

Also folded in from earlier work this session:

  - Phase 1.1: `xx value` heap-copy in `buildProtocolValue` routes
    through `context.allocator` via the new `allocViaContext` helper.
  - interp.zig: `marshalForeignArg` double-offset bug fixed —
    `heapSlice` already adds `hp.offset` to the slice ptr, so the
    extra `+ hp.offset` was scribbling memcpy/memset into adjacent
    heap state, corrupting `heap.items[0]`. Symptom: `build_format`
    at comptime produced zero bytes, all `print` calls failed.
  - Lazy lowering: `lazyLowerFunction` now declares foreign-body
    functions as extern stubs in the local (comptime) module so
    cross-module foreign calls resolve.
  - Allocator API: all stdlib allocators on one-line `init() -> *T`
    (CAllocator/GPA: libc-backed; Arena/TrackingAllocator: parent-
    backed; BufAlloc: embeds state at head of user buffer).
  - issues 0038 (transitive #import), 0039 (chess + stdlib migration
    fallout), 0040 (generic struct method dot-dispatch), 0041
    (pointer types as type-arg), 0042 (alias name resolution) — all
    fixed; regression tests in examples/.
  - Diagnostic: `emitError` now embeds the lowering's
    `current_source_file` and enclosing function in the literal
    message; SX_TRACE_UNRESOLVED=1 dumps a Zig stack trace at the
    emit site so misattributed spans can't hide where the failure
    is.
  - tools/verify-step.sh (all-platforms gate) and tools/scratch.sh
    (interp/codegen parity tester) added.

Test suite: 152 example tests pass; chess builds + screenshots on
macOS / iOS sim / Android.
2026-05-24 22:59:20 +03:00
agra
632e64512b bundling: Android APK pipeline moved into sx; android.sx state-on-plat
Week 7 of /Users/agra/.claude/plans/lets-plan-to-move-splendid-pumpkin.md
plus the android.sx refactor + three sx-compiler fixes hit along the way
to get chess on Pixel 7 Pro responding to touch end-to-end.

library/modules/platform/bundle.sx now covers the Android APK shape
alongside macOS / iOS-sim / iOS-device. `android_bundle_main` discovers
the SDK ($ANDROID_HOME / $ANDROID_SDK_ROOT / $HOME/Library/Android/sdk),
picks the highest-versioned build-tools + platforms via
`process.run("ls .. | sort -V | tail -1")`, stages
`<apk>.stage/lib/arm64-v8a/<libfoo.so>`, synthesizes
AndroidManifest.xml (NativeActivity vs `#jni_main` Activity branch),
writes each `#jni_main` decl's Java source under
`<stage>/java/<pkg>/<Cls>.java`, runs javac --release 11 + d8 to
produce classes.dex, aapt2-links the unaligned APK, appends lib/ +
classes.dex + each registered asset tree via zip, zipalign + ensure
debug keystore via keytool + apksigner sign.

Compiler-side accessors (src/ir/compiler_hooks.zig + library/modules/compiler.sx):
- is_android predicate.
- set_manifest_path / manifest_path + set_keystore_path / keystore_path.
- jni_main_count / jni_main_foreign_path_at(i) /
  jni_main_java_source_at(i) surface the `#jni_main` emissions that
  the Zig createApk previously consumed directly.
- main.zig wires manifest_path, keystore_path, and the per-decl
  (foreign_path, java_source) parallel slices into BuildConfig before
  invoking the post-link callback.

CLI `--apk <path>` keeps working as a transitional alias: it now feeds
bundle_path so the existing auto-`post_link_module = "platform.bundle"`
shim fires the same way as `--bundle`. main.zig no longer calls
target.createApk directly.

Deletions in src/target.zig: createApk, compileJniMainSources,
buildJniMainManifest, buildAndroidManifest, ensureDebugKeystore,
libNameFromSoBasename, plus helpers splitForeignPath / discoverJavac /
discoverAndroidSdk / findHighestSubdir / runProcess / runProcessIn
(~400 lines). git grep returns only the obituary comment.

library/modules/platform/android.sx refactor (chess Android dependency):
- Module-level globals retired (g_app_window, g_egl_*, g_viewport_*,
  g_dpi_scale, g_should_stop, g_render_thread*, g_user_main_fn,
  g_touch_*) → AndroidPlatform struct fields.
- All sx_android_* helpers take `plat: *AndroidPlatform` as first arg.
  Render thread receives plat via pthread_create's arg.
- New `logical_w: f32 = 0.0` field. Consumers set it before init() to
  define the design width in points; `recompute_scale` derives
  `dpi_scale = pixel_w / logical_w` (or 1.0 if unset). Called on
  init / set_viewport / egl_init. drain_touches divides incoming
  physical pixel coords by dpi_scale so chess sees logical-space
  positions matching its layout. Touch lands on the right squares.

Three sx-compiler bugs hit + fixed along the way:

1. Top-level `inline if OS == .X { decls }` body decls were silently
   dropped because scanDecls/lowerDecls had no .if_expr arm. New
   `flattenComptimeConditionals` pre-pass in src/imports.zig
   (threaded via ComptimeContext from core.zig) hoists matching arms
   recursively. Regression at examples/124-inline-if-hoist-toplevel.sx.

2. Parser rejected `#import` / `#framework` inside inline-if bodies
   because parseStmt in src/parser.zig only had arms for `#insert`.
   Added the missing arms. Regression at
   examples/123-inline-if-import-in-body.sx (landed earlier).

3. JNI `Call<T>Method` switches in src/ir/emit_llvm.zig (instance /
   nonvirtual / static) were missing `.f32` rows — jfloat returns
   (e.g. MotionEvent.getX/getY) fell into the silent-undef else arm.
   Chess's sx_android_push_touch(plat, getAction(), getX(), getY())
   delivered garbage f32 coords to the touch ring, so taps landed
   nowhere recognisable. Added `.f32 => Jni.Call{Static,Nonvirtual,}FloatMethod`
   rows to all three switches; lifted unsupported-type detection
   from emit_llvm into lowerForeignMethodCall with proper
   source-spanned diagnostics (`isJniReturnTypeSupported`). Regressions
   at examples/ffi-jni-call-10-jfloat-return.sx,
   examples/ffi-jni-class-09-multi-float-args.sx,
   examples/ffi-jni-call-11-unsupported-return-diag.sx.

Stale-snapshot drift in tests/expected/ffi-objc-call-03-selector-sharing.ir
and ffi-objc-call-06-sret-return.ir picks up the new BuildOptions
accessor extern decls (is_android, set_manifest_path,
set_keystore_path, jni_main_count, jni_main_foreign_path_at,
jni_main_java_source_at). Verified diff is dead-decl-only.

Chess on Pixel 7 Pro: tap on e2 white pawn -> yellow selection +
green dots on legal e3/e4 targets; tap on e4 -> board updates with
1. e4, "Black to move" + "1. e4" in info panel.

zig build && zig build test && bash tests/run_examples.sh -> 145/145
green. bash tests/cross_compile.sh -> 7/7 green.
2026-05-23 01:28:32 +03:00
agra
43a30e727d imports: read resolved C-import paths after mutation, not before
Latent bug from the stdlib-path resolution introduced in 4849cfb.
The earlier shape captured `const ci = decl.data.c_import_decl;`
BEFORE mutating `decl.data.c_import_decl.{sources,includes}` with
the resolved paths, then passed the stale `ci.includes` to
`c_import.processCImport`. Result: `#include "vendors/..."` paths
that resolved via the stdlib branch (i.e. only existed under
sx/library/vendors/) reached clang as the original unresolved
string and failed to parse — silently producing no synthesized
`#foreign` decls.

`#source` survived because the source list is re-read from
decl.data later (collectCImportSources walks the AST), so it
picked up the mutated value. Only `#include`'s synthesis path was
broken.

Fix: do the resolution first inside its own scope, then re-bind
`ci` from `decl.data.c_import_decl` so the include list passed to
processCImport sees the resolved paths.

Caught by ffi-07 baseline (next commit) — the test deliberately
puts its C helper only under library/vendors/ so the path is
findable solely via the stdlib chain.
2026-05-19 11:51:20 +03:00
agra
4849cfb904 android: dpi_scale, scissor, JNI safe-insets, touch input
Four Android UX wins landing together; all verified end-to-end on a
Pixel 7 Pro (board fills width, info-panel text renders, status bar
inset honored, tap-to-select + tap-to-move plays 1. e4).

- AndroidPlatform.init reads density via AConfiguration_getDensity
  (app->config at offset 32) and sets dpi_scale = density / 160. The
  hardcoded 1.0 had been making every logical unit equal one physical
  pixel; ChessBoardView's 520-default size_that_fits fallback then
  rendered at ~half the framebuffer width on the device, and glyphs
  rasterized at literal 11-13 physical pixels were essentially invisible
  on a 2340-tall display.
- gles3.sx set_scissor un-stubbed; with dpi_scale right the renderer
  feeds in valid pixel bounds and the Y-flip math lands inside the
  framebuffer.
- New library/vendors/sx_android_jni/sx_android_jni.c walks
  activity -> window -> decorView -> rootWindowInsets via JNI and
  publishes the system-bar insets. safe_insets() lazy-queries the
  first call after EGL is up (decor view isn't attached at bootstrap).
- sx_android_install_input_handler sets app->onInputEvent; sx-side
  sx_android_input_event translates AMotionEvent DOWN/MOVE/UP/CANCEL
  into existing mouse_down/mouse_moved/mouse_up Events so the chess
  board's tap-to-select + ScrollView drag path Just Works. Coordinates
  divided by dpi_scale so layout-side hit tests match. poll_events
  drains its slice after returning (mirrors the SDL pattern).
- src/imports.zig now routes #import c { #source / #include } paths
  through the same chain as #import (importing dir -> CWD -> stdlib
  search paths). Lets library-owned C helpers like the JNI bridge
  live in sx/library/vendors/ without forcing consumers to vendor a
  copy. Existing CWD-relative consumer layouts (chess's vendors/...)
  still resolve first, so no regression.

86/86 regression tests pass.
2026-05-19 11:09:41 +03:00
agra
f9ecf9d00e iOS lock step keyboard + metal 2026-05-18 17:40:10 +03:00
agra
c027e1969b stdlib: relocate modules under library/
- examples/modules/ -> library/modules/ (top-level, no more
  symlink hacks in consumer projects)
- compiler discovers stdlib via _NSGetExecutablePath / readlink
  /proc/self/exe; searches dev layout (../../library), install
  layout (../library), and alongside-binary fallback
- SX_STDLIB_PATH env var overrides for tests / dev convenience
- SX_DEBUG_STDLIB env var dumps the discovery results
- build.zig installs library/ alongside the binary
- Compilation gains stdlib_paths field threaded through resolveImports
- 50 tests pass; consumer projects can now build from any cwd
2026-05-17 13:49:25 +03:00
agra
bfc784734c lsp import 2026-02-24 20:05:24 +02:00
agra
b98711a1d3 imports 2026-02-24 13:37:27 +02:00
agra
d3e574eae5 import c 2026-02-22 17:24:04 +02:00
agra
5956303366 dir import 2026-02-20 12:27:06 +02:00
agra
55fc5790e4 so... jai :D 2026-02-09 18:07:41 +02:00