Files
sx/src/ast.zig
agra 4d75b9323c feat(asm): Phase F — global (module-scope) asm
A top-level `asm { "tmpl", };` block (template only) lowers to LLVM `module asm`;
a lib-less `extern` declaration calls into the symbols it defines (the import
direction reuses the existing C-FFI extern path — no new surface).

- ast.zig: asm_global node (AsmGlobal { template }).
- parser.zig: parseAsmGlobal, dispatched from parseTopLevel on kw_asm — rejects
  `volatile` and any operands/clobbers (template only). The in-function asm
  expression form stays in parsePrimary.
- module.zig: Module.global_asm list; lower/decl.zig captures each template in
  lowerMainAndComptime (the real top-level pass — lowerDecls is dead for
  top-level); emit_llvm.zig emit() appends each via LLVMAppendModuleInlineAsm in
  source order.
- the new node forced asm_global arms in sema.zig (analyzeNode +
  findNodeAtOffset) and semantic_diagnostics.zig (checkBindingNames).

Verified end-to-end: an aarch64 `_my_add` global routine, called via `extern`,
returns 42 — AOT only (the ORC JIT doesn't link module-asm symbols; global-asm
symbols live in the final linked binary). Locked with 1648-platform-asm-global
({ "aot": true, "target": "macos" } → AOT build+run on aarch64, ir-only else).

zig build test green (656 corpus, 446 unit).
2026-06-15 22:22:29 +03:00

963 lines
35 KiB
Zig
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const std = @import("std");
pub const Span = struct {
start: u32,
end: u32,
};
pub const Node = struct {
span: Span,
data: Data,
source_file: ?[]const u8 = null,
pub const Data = union(enum) {
root: Root,
fn_decl: FnDecl,
block: Block,
int_literal: IntLiteral,
float_literal: FloatLiteral,
bool_literal: BoolLiteral,
string_literal: StringLiteral,
identifier: Identifier,
enum_literal: EnumLiteral,
binary_op: BinaryOp,
chained_comparison: ChainedComparison,
unary_op: UnaryOp,
call: Call,
field_access: FieldAccess,
if_expr: IfExpr,
match_expr: MatchExpr,
match_arm: MatchArm,
const_decl: ConstDecl,
var_decl: VarDecl,
assignment: Assignment,
multi_assign: MultiAssign,
destructure_decl: DestructureDecl,
enum_decl: EnumDecl,
struct_decl: StructDecl,
struct_literal: StructLiteral,
union_decl: UnionDecl,
error_set_decl: ErrorSetDecl,
lambda: Lambda,
type_expr: TypeExpr,
param: Param,
defer_stmt: DeferStmt,
push_stmt: PushStmt,
comptime_expr: ComptimeExpr,
insert_expr: InsertExpr,
return_stmt: ReturnStmt,
import_decl: ImportDecl,
namespace_decl: NamespaceDecl,
array_type_expr: ArrayTypeExpr,
slice_type_expr: SliceTypeExpr,
array_literal: ArrayLiteral,
parameterized_type_expr: ParameterizedTypeExpr,
index_expr: IndexExpr,
slice_expr: SliceExpr,
pointer_type_expr: PointerTypeExpr,
many_pointer_type_expr: ManyPointerTypeExpr,
optional_type_expr: OptionalTypeExpr,
error_type_expr: ErrorTypeExpr,
raise_stmt: RaiseStmt,
try_expr: TryExpr,
catch_expr: CatchExpr,
onfail_stmt: OnFailStmt,
/// `#caller_location` — a marker that, as a parameter default, resolves
/// to a `Source_Location` of the call site (ERR E4.1b). The node's
/// `span`/`source_file` carry the location (rewritten to the call site
/// during default expansion). No payload.
caller_location: void,
pack_index_type_expr: PackIndexTypeExpr,
comptime_pack_ref: ComptimePackRef,
force_unwrap: ForceUnwrap,
null_coalesce: NullCoalesce,
deref_expr: DerefExpr,
null_literal: void,
while_expr: WhileExpr,
for_expr: ForExpr,
spread_expr: SpreadExpr,
break_expr: void,
continue_expr: void,
undef_literal: void,
inferred_type: void,
builtin_expr: void,
compiler_expr: void,
library_decl: LibraryDecl,
framework_decl: FrameworkDecl,
function_type_expr: FunctionTypeExpr,
closure_type_expr: ClosureTypeExpr,
tuple_type_expr: TupleTypeExpr,
tuple_literal: TupleLiteral,
ufcs_alias: UfcsAlias,
c_import_decl: CImportDecl,
protocol_decl: ProtocolDecl,
impl_block: ImplBlock,
ffi_intrinsic_call: FfiIntrinsicCall,
runtime_class_decl: RuntimeClassDecl,
jni_env_block: JniEnvBlock,
asm_expr: AsmExpr,
asm_global: AsmGlobal,
pub fn declName(self: Data) ?[]const u8 {
return switch (self) {
.fn_decl => |d| d.name,
.const_decl => |d| d.name,
.var_decl => |d| d.name,
.enum_decl => |d| d.name,
.struct_decl => |d| d.name,
.union_decl => |d| d.name,
.error_set_decl => |d| d.name,
.namespace_decl => |d| d.name,
.ufcs_alias => |d| d.name,
.c_import_decl => |d| d.name,
.protocol_decl => |d| d.name,
.runtime_class_decl => |d| d.name,
else => null,
};
}
};
};
pub const Root = struct {
decls: []const *Node,
};
pub const CallingConvention = enum { default, c };
/// Linkage modifier written in the postfix slot after `callconv(...)`:
/// `name :: (sig) -> Ret [callconv(.x)] [extern | export] [;|{…}];`
/// `extern` = import (external linkage, C ABI, no sx ctx — `extern`'s role);
/// `export` = define + expose (body + external linkage + C ABI + no ctx).
/// Both imply `callconv(.c)`. Variants carry a trailing `_` to dodge the Zig
/// keywords. `.none` = no linkage modifier (the ordinary sx-internal decl).
pub const ExternExportModifier = enum { none, extern_, export_ };
pub const FnDecl = struct {
name: []const u8,
params: []const Param,
return_type: ?*Node,
body: *Node,
type_params: []const StructTypeParam = &.{},
is_arrow: bool = false,
call_conv: CallingConvention = .default,
/// Postfix linkage modifier (`extern`/`export`) written after the
/// `callconv(...)` slot. `.none` for an ordinary sx-internal function.
/// Parsed in Phase 0.1; not consumed by the fn-decl path until Phase 1.
extern_export: ExternExportModifier = .none,
/// Optional library reference + symbol-name override for an `extern`/`export`
/// function, the optional library + symbol-name override. Both
/// optional: `extern` alone resolves the sx name against the default-linked
/// libs; `extern LIB` names the source library; `extern "csym"` renames the
/// symbol. Required for `extern` to be a behavior-equivalent superset of
/// `extern` (Gate A→B) — the migration of 466 `extern` uses across 6 libs
/// must preserve each symbol's library. Parsed/consumed in Phase 1.2.
extern_lib: ?[]const u8 = null,
extern_name: ?[]const u8 = null,
/// Span of the function's name token, for the reserved-type-name decl
/// diagnostic. Synthesized decls (e.g. `#import c` extern
/// functions, lowering-time objc/protocol method synthesis) leave it zero.
name_span: Span = .{ .start = 0, .end = 0 },
/// True when the function NAME was written as a backtick raw identifier
/// (`` `i2 :: … ``) or synthesized by a `#import c` extern decl. A raw
/// name is exempt from the reserved-type-name binding check.
/// Every PARSER fn_decl is built through `parseFnDecl`, whose `name_is_raw`
/// is a REQUIRED parameter, so a parser site cannot drop it; the default
/// here serves only post-check synthesized decls (which are never raw).
is_raw: bool = false,
/// `name :: ufcs (params) { body }` — the fn opted into dot-call
/// dispatch (`recv.name(args)`). Dot-calls on free functions are
/// OPT-IN: only `is_ufcs` fns and `ufcs` aliases dispatch; a plain
/// fn is callable directly or via `|>` only.
is_ufcs: bool = false,
};
pub const Param = struct {
name: []const u8,
name_span: Span,
type_expr: *Node,
is_variadic: bool = false,
is_comptime: bool = false,
/// Heterogeneous protocol-constrained variadic pack: `..xs: Protocol`
/// (no `[]`, no `$`). The annotation is a bare protocol the trailing args
/// each conform to with their own type-arg — distinct from a slice variadic
/// (`..xs: []T`, `is_pack == false`) and from the comptime type-pack
/// (`..$xs`, `is_comptime == true`). Always implies `is_variadic`.
is_pack: bool = false,
/// Optional default value expression. When the caller omits this
/// parameter, lowering substitutes this expression in its place.
default_expr: ?*Node = null,
/// True when the param name was written as a backtick raw identifier
/// (`` `i2 ``) or synthesized by a `#import c` extern decl. A raw name is
/// exempt from the reserved-type-name binding check.
is_raw: bool = false,
};
pub const Block = struct {
stmts: []const *Node,
/// True when the block's last statement is its value — i.e. a trailing
/// expression with NO `;`. A trailing `;` (or a non-expression last
/// statement) discards the value and leaves the block void. Match-arm and
/// else-arm bodies are built with this forced true (the arm `;` is an arm
/// terminator, not a value-discard).
produces_value: bool = false,
/// When `produces_value` is false *because* the last statement was an
/// expression terminated by `;` (as opposed to a decl/return/empty block),
/// the span of that discarding `;`. Lets a value-position diagnostic point
/// precisely at the semicolon to drop. Null otherwise.
discarded_semi: ?Span = null,
};
pub const IntLiteral = struct {
value: i64,
};
pub const FloatLiteral = struct {
value: f64,
};
pub const BoolLiteral = struct {
value: bool,
};
pub const StringLiteral = struct {
raw: []const u8,
is_raw: bool = false,
};
/// Inline assembly expression: `asm volatile? { "tmpl", <operands…>,
/// clobbers(.…) }` (ASM stream, design §II.3). A flat `operands` list in source
/// order — that order keys the `%N`/`%[name]` indices and the LLVM constraint
/// string. The result type is derived in Sema from the `out_value` operands
/// (0→void, 1→T, N→tuple). Parsed in Phase A.1; lowering bails loudly until the
/// IR op + emit land (Phases CE).
pub const AsmExpr = struct {
/// Template: a string-literal / `#string` heredoc node (a comptime string).
template: *Node,
is_volatile: bool = false,
/// Declaration order preserved (= `%N` indexing).
operands: []const AsmOperand,
/// Dot-names from `clobbers(.…)`: e.g. "rcx", "cc", "memory".
clobbers: []const []const u8,
};
pub const AsmOperand = struct {
/// Optional `[name]`; null when not written. The *effective* name (for
/// `%[name]` and the result tuple field) is computed in Sema: explicit
/// `[name]`, else auto-derived from a `{reg}` pin in `constraint` (design
/// §II.5 naming rule).
name: ?[]const u8 = null,
/// Verbatim constraint, e.g. "={rax}", "=r", "+r", "{rdi}", "r".
constraint: []const u8,
role: Role,
/// `out_value` → a Type node; `input` → an expression node. (`out_place`
/// payload is a write-through place expr — Phase 2, not parsed in A.1.)
payload: *Node,
pub const Role = enum {
out_value, // `-> Type` value output; N of these → a tuple result
out_place, // `-> @place` write-through to storage (Phase 2)
input, // `= expr`
};
};
/// Top-level (module-scope) global assembly: `asm { "tmpl", };` (ASM stream
/// design §II.2 Deviation 6). Template only — no operands, no `volatile`, no
/// `clobbers`, no `%` substitution. Lowers to `LLVMAppendModuleInlineAsm`;
/// multiple blocks concatenate in source order. Symbols it defines are reached
/// with a lib-less `extern` declaration.
pub const AsmGlobal = struct {
template: *Node, // string-literal / `#string` heredoc node
};
pub const Identifier = struct {
name: []const u8,
/// True when written as a backtick raw identifier (`` `i2 ``). Carried so a
/// destructure target (`` `i2, b := … ``) can be recognised as raw and
/// exempted from the reserved-type-name binding check.
is_raw: bool = false,
};
pub const EnumLiteral = struct {
name: []const u8, // without the leading dot
};
pub const BinaryOp = struct {
op: Op,
lhs: *Node,
rhs: *Node,
pub const Op = enum {
add,
sub,
mul,
div,
mod,
eq,
neq,
lt,
lte,
gt,
gte,
and_op,
or_op,
bit_and,
bit_or,
bit_xor,
shl,
shr,
in_op,
};
};
pub const ChainedComparison = struct {
operands: []const *Node,
ops: []const BinaryOp.Op,
};
pub const UnaryOp = struct {
op: Op,
operand: *Node,
pub const Op = enum {
negate,
not,
bit_not,
xx,
address_of,
};
};
pub const Call = struct {
callee: *Node,
args: []const *Node,
};
/// `#objc_call(T)(recv, "sel:", args...)`,
/// `#jni_call(T)(env, target, "name", "(Sig)R", args...)`,
/// `#jni_static_call(T)(class, "name", "(Sig)R", args...)`.
/// The return-type T sits in the first parens; the actual call args
/// follow in the second parens. Codegen branches on `kind` to pick
/// the lowering (objc_msgSend / CallXxxMethod / CallStaticXxxMethod).
pub const FfiIntrinsicKind = enum {
objc_call,
jni_call,
jni_static_call,
};
pub const FfiIntrinsicCall = struct {
kind: FfiIntrinsicKind,
return_type: *Node,
args: []const *Node,
};
pub const FieldAccess = struct {
object: *Node,
field: []const u8,
is_optional: bool = false,
};
pub const IfExpr = struct {
condition: *Node,
then_branch: *Node,
else_branch: ?*Node,
is_inline: bool, // true for `if cond then a else b`
is_comptime: bool = false, // true for `inline if` — compile-time branch elimination
binding_name: ?[]const u8 = null, // for `if val := expr { ... }` optional binding
binding_span: ?Span = null, // span of `binding_name` (set iff `binding_name` is)
/// True when the optional binding was a backtick raw identifier
/// (`` if `i2 := … ``) — exempt from the reserved-type-name check.
binding_is_raw: bool = false,
};
pub const MatchExpr = struct {
subject: *Node,
arms: []const MatchArm,
is_comptime: bool = false,
};
pub const MatchArm = struct {
pattern: ?*Node, // null = else (default) arm
body: *Node,
is_break: bool,
capture: ?[]const u8 = null, // payload binding name: case .variant: (name) { ... }
capture_span: ?Span = null, // span of `capture` (set iff `capture` is)
/// True when the capture was a backtick raw identifier
/// (`` case .v: (`i2) ``) — exempt from the reserved-type-name check.
capture_is_raw: bool = false,
};
pub const ConstDecl = struct {
name: []const u8,
type_annotation: ?*Node,
value: *Node,
/// Span of the constant's name token, for the reserved-type-name decl
/// diagnostic. NO default: every construction site must set
/// it explicitly, so a struct-body const can't silently fall back to a
/// 1:1 caret (the finding-1 bug).
name_span: Span,
/// True when the constant NAME was written as a backtick raw identifier
/// (`` `i2 :: … ``). NO default: required at every site so the reserved-
/// name exemption can't be dropped — mirrors `checkBindingName`'s required
/// `is_raw` argument so the parser and the check can't desync.
is_raw: bool,
};
pub const VarDecl = struct {
name: []const u8,
name_span: Span,
type_annotation: ?*Node,
value: ?*Node,
/// `extern`-global form `g : T extern [LIB] ["csym"];` — a reference to a
/// global defined elsewhere (external linkage, resolved at link time).
/// `extern_lib` is the optional source-library reference and `extern_name`
/// the optional symbol-name override.
is_extern: bool = false,
extern_lib: ?[]const u8 = null,
extern_name: ?[]const u8 = null,
/// True when the binding name was written as a backtick raw identifier
/// (`` `i2 := … ``). A raw name is exempt from the reserved-type-name
/// binding check.
is_raw: bool = false,
};
pub const Assignment = struct {
target: *Node,
op: Op,
value: *Node,
pub const Op = enum {
assign,
add_assign,
sub_assign,
mul_assign,
div_assign,
mod_assign,
and_assign,
or_assign,
xor_assign,
shl_assign,
shr_assign,
};
};
pub const MultiAssign = struct {
targets: []const *Node,
values: []const *Node,
};
pub const DestructureDecl = struct {
names: []const []const u8,
name_spans: []const Span, // one per entry in `names`, same order
/// One per entry in `names`, same order: true when that target was a
/// backtick raw identifier (`` `i2, b := … ``) — exempt from the
/// reserved-type-name binding check.
name_is_raw: []const bool,
value: *Node,
};
pub const EnumDecl = struct {
name: []const u8,
variant_names: []const []const u8,
variant_types: []const ?*Node = &.{}, // null entries = no payload; empty = payload-less enum
is_flags: bool = false,
variant_values: []const ?*Node = &.{}, // explicit value per variant (null = auto), empty = all auto
backing_type: ?*Node = null, // optional backing type: enum u8 { ... }
/// True when the declared NAME was a backtick raw identifier
/// (`` `i2 :: enum { … } ``) — exempt from the reserved-type-name decl
/// check. A bare reserved-name decl still errors.
is_raw: bool = false,
};
pub const UnionDecl = struct {
name: []const u8,
field_names: []const []const u8,
field_types: []const *Node,
/// True when the declared NAME was a backtick raw identifier — exempt from
/// the reserved-type-name decl check.
is_raw: bool = false,
};
/// `Foo :: error { TagA, TagB }` — a named error set. Tags are bare
/// identifiers (no payload, no explicit value), unlike enum variants.
pub const ErrorSetDecl = struct {
name: []const u8,
tag_names: []const []const u8,
/// True when the declared NAME was a backtick raw identifier — exempt from
/// the reserved-type-name decl check.
is_raw: bool = false,
};
pub const StructTypeParam = struct {
name: []const u8, // e.g. "N" or "T" (without $)
constraint: *Node, // type_expr: "u32" for value param, "Type" for type param
protocol_constraints: []const []const u8 = &.{}, // e.g. ["Eq", "Hashable"] for $T/Eq/Hashable
/// `..$Ts: []Type` — a pack type-param binding the remaining type args as a
/// sequence (must be last). Field types reference it via `(..$Ts)` etc.
is_variadic: bool = false,
};
pub const UsingEntry = struct {
insert_index: u32, // position in field_names where used fields are spliced
type_name: []const u8, // struct type to inline
};
pub const StructDecl = struct {
name: []const u8,
field_names: []const []const u8,
field_types: []const *Node, // type_expr nodes
field_defaults: []const ?*Node, // default value per field, null if none
type_params: []const StructTypeParam = &.{},
using_entries: []const UsingEntry = &.{},
methods: []const *Node = &.{}, // fn_decl nodes for struct methods
constants: []const *Node = &.{}, // const_decl nodes for struct-level constants
/// True when the declared NAME was a backtick raw identifier
/// (`` `i2 :: struct { … } ``) — exempt from the reserved-type-name decl
/// check. A bare reserved-name decl still errors.
is_raw: bool = false,
};
pub const StructFieldInit = struct {
name: ?[]const u8, // null for positional, non-null for named/shorthand
value: *Node,
};
pub const StructLiteral = struct {
struct_name: ?[]const u8, // null for anonymous `.{ ... }`
type_expr: ?*Node = null, // for GenericType(args).{ ... }
field_inits: []const StructFieldInit,
init_block: ?*Node = null, // optional `{ stmts }` block after struct literal
};
pub const Lambda = struct {
params: []const Param,
return_type: ?*Node,
body: *Node,
type_params: []const StructTypeParam = &.{},
call_conv: CallingConvention = .default,
};
pub const TypeExpr = struct {
name: []const u8,
is_generic: bool = false,
protocol_constraints: []const []const u8 = &.{}, // e.g. ["Eq", "Hashable"] for $T/Eq/Hashable
/// True when written as a backtick raw identifier in type position
/// (`` `i2 ``). Such a reference is the LITERAL name `i2` used as a type —
/// resolution skips the builtin/reserved classifier and looks up a
/// `` `i2 ``-declared type (struct/enum/union/alias), else "unknown
/// type". A bare `i2` keeps `is_raw = false` and is the int type.
is_raw: bool = false,
};
/// `$<pack_name>[<index>]` in type position. Resolves to the i-th
/// element type of the active pack binding. Step 3 of the variadic
/// heterogeneous type packs feature — used in trampoline bodies,
/// generic conversions, struct fields parameterised over the pack.
pub const PackIndexTypeExpr = struct {
pack_name: []const u8,
index: u32,
};
/// `$<pack_name>` (no indexing) in expression position. Evaluates
/// to a comptime `[]Type` slice — the WHOLE pack as data. Step 4
/// final slice: lets builder fns walk the pack types and emit
/// per-position code (the shape step 5's generic Into(Block) needs
/// for its trampoline body).
pub const ComptimePackRef = struct {
pack_name: []const u8,
};
pub const DeferStmt = struct {
expr: *Node,
};
// ── Error handling (ERR stream) ──────────────────────────────────────────
/// `raise EXPR;` — terminates control flow like `return`, populating the
/// error channel. `tag` is a tag-typed expression: `error.X` (a field
/// access on the `error` keyword) or a tag-bound variable (`raise e`).
pub const RaiseStmt = struct {
tag: *Node,
};
/// `try X` — a failable attempt. Unary prefix, binds tighter than any
/// binary operator. Sema (E1.4) rejects a non-failable operand.
pub const TryExpr = struct {
operand: *Node,
};
/// `X catch [e] BODY` — inline failure handler (postfix). The binding is a
/// bare name (no parens) and optional. Body is a block, a bare expression,
/// or — when `is_match_body` — a `match_expr` from the `== { case ... }`
/// sugar (whose subject is the binding).
pub const CatchExpr = struct {
operand: *Node,
binding: ?[]const u8 = null,
binding_span: ?Span = null, // span of `binding` (set iff `binding` is)
/// True when the binding was a backtick raw identifier
/// (`` x catch `i2 { … } ``) — exempt from the reserved-type-name check.
binding_is_raw: bool = false,
body: *Node,
is_match_body: bool = false,
};
/// `onfail [e] BODY` — cleanup run on error-exit of the enclosing block.
/// Binding optional (bare name). Body is a block (`onfail [e] { ... }`) or
/// a bare expression (`onfail EXPR;`).
pub const OnFailStmt = struct {
binding: ?[]const u8 = null,
binding_span: ?Span = null, // span of `binding` (set iff `binding` is)
/// True when the binding was a backtick raw identifier
/// (`` onfail `i2 { … } ``) — exempt from the reserved-type-name check.
binding_is_raw: bool = false,
body: *Node,
};
pub const PushStmt = struct {
context_expr: *Node,
body: *Node,
};
pub const ComptimeExpr = struct {
expr: *Node,
};
pub const InsertExpr = struct {
expr: *Node,
};
pub const ReturnStmt = struct {
value: ?*Node,
};
pub const ImportDecl = struct {
path: []const u8,
name: ?[]const u8,
/// True when the namespace NAME was a backtick raw identifier
/// (`` `i2 :: #import "…" ``) — exempt from the reserved-type-name decl
/// check. A flat `#import` (name == null) binds nothing.
is_raw: bool = false,
};
pub const ArrayTypeExpr = struct {
length: *Node, // int_literal for the size
element_type: *Node, // type_expr for the element type
};
pub const SliceTypeExpr = struct {
element_type: *Node, // type_expr for the element type
};
pub const ArrayLiteral = struct {
elements: []const *Node,
type_expr: ?*Node = null,
};
pub const ParameterizedTypeExpr = struct {
name: []const u8, // e.g. "Vector", or later generic struct names
args: []const *Node, // e.g. [int_literal(3), type_expr("f32")]
/// True when the base name was a backtick raw identifier in type position
/// (`` `i2(i64) ``). Such a reference is the LITERAL name `i2` used as a
/// parameterized type — resolution skips the builtin parameterized
/// classifier (e.g. the `Vector` intrinsic) and instantiates a
/// `` `i2 ``-declared generic template.
is_raw: bool = false,
};
pub const IndexExpr = struct {
object: *Node,
index: *Node,
};
pub const SliceExpr = struct {
object: *Node,
start: ?*Node = null, // null = 0
end: ?*Node = null, // null = len
/// `<..` family — slice begins one past `start`.
start_exclusive: bool = false,
/// `..=` family — slice includes `end`.
end_inclusive: bool = false,
};
pub const PointerTypeExpr = struct {
pointee_type: *Node,
};
pub const ManyPointerTypeExpr = struct {
element_type: *Node,
};
pub const OptionalTypeExpr = struct {
inner_type: *Node,
};
/// The error channel of a multi-return result list: bare `!` (inferred
/// set) or `!Named` (a declared `error { ... }` set). Appears only as
/// the trailing result element; the parser enforces the position and
/// sema (E1) restricts it to return positions.
pub const ErrorTypeExpr = struct {
/// `null` = inferred set (bare `!`); non-null = named set (`!Named`).
name: ?[]const u8 = null,
};
pub const ForceUnwrap = struct {
operand: *Node,
};
pub const NullCoalesce = struct {
lhs: *Node,
rhs: *Node,
};
pub const DerefExpr = struct {
operand: *Node,
};
pub const WhileExpr = struct {
condition: *Node,
body: *Node,
binding_name: ?[]const u8 = null, // for `while val := expr { ... }` optional binding
binding_span: ?Span = null, // span of `binding_name` (set iff `binding_name` is)
/// True when the optional binding was a backtick raw identifier
/// (`` while `i2 := … ``) — exempt from the reserved-type-name check.
binding_is_raw: bool = false,
};
/// One position of a (possibly multi-iterable) `for` header.
pub const ForIterable = struct {
/// Collection expression, or the range START for the range forms.
expr: *Node,
/// Range end. Null for a plain collection AND for the open-ended range
/// `a..` (distinguished by `is_range`).
range_end: ?*Node = null,
/// True for any range form. Each side of `..` takes an optional bound
/// marker — `=` inclusive, `<` exclusive — with defaults start-inclusive,
/// end-exclusive: `a..b` ≡ `a=..<b`; `a<..<b` is 1-past-start to
/// end-1; `a=..=b` includes both ends; `a..=b` keeps the short
/// end-inclusive spelling.
is_range: bool = false,
/// `<..` family — start is exclusive (cursor begins at start+1).
start_exclusive: bool = false,
/// `..=` family — end is inclusive.
end_inclusive: bool = false,
};
/// One capture of a `for` header: `(x)`, `(*x)`, `(x, y, ...)`.
pub const ForCapture = struct {
name: []const u8,
span: ?Span = null,
/// True when the name was a backtick raw identifier (`` for xs (`i2) ``)
/// — exempt from the reserved-type-name check.
is_raw: bool = false,
/// `(*x)` — bind a pointer into the collection (no per-element copy).
by_ref: bool = false,
};
/// `for it1, it2, ... (c1, c2, ...) { }` — parallel iteration. The FIRST
/// iterable's length drives the loop (first-iterable-wins); the others are
/// indexed along it, and a non-first range's end is not consulted. The
/// capture group is positional: empty (no bindings) or one capture per
/// iterable. The body is a block or an `=> expr;` arrow body.
pub const ForExpr = struct {
iterables: []ForIterable,
captures: []ForCapture,
body: *Node,
/// `inline for` — comptime-unrolled (single bounded range, comptime bounds).
is_inline: bool = false,
};
pub const SpreadExpr = struct {
operand: *Node,
};
pub const NamespaceDecl = struct {
name: []const u8,
decls: []const *Node,
/// Decls AUTHORED in the namespaced module itself (its `own_decls`), a
/// subset of `decls` (which also carries the module's transitive flat
/// imports). Lowering registers these under their module-qualified name
/// (`ns.fn`) so `pkg.fn(...)` resolves to a unique FuncId distinct from a
/// same-named function in another module.
own_decls: []const *Node = &.{},
/// The resolved path of the module this alias targets — the importing file's
/// own path for a `#import c` namespace (its members are synthesized there).
/// Captured at import-resolution time (the `resolved_path` that is otherwise
/// not retained on the node) so `buildImportFacts` can record the namespace
/// edge without re-walking the import graph.
target_module_path: []const u8,
/// True when the namespace NAME was a backtick raw identifier — exempt
/// from the reserved-type-name decl check.
is_raw: bool = false,
};
pub const LibraryDecl = struct {
lib_name: []const u8,
name: []const u8, // sx-side constant name
/// True when the constant NAME was a backtick raw identifier — exempt from
/// the reserved-type-name decl check.
is_raw: bool = false,
};
pub const FrameworkDecl = struct {
name: []const u8, // framework name, e.g. "Foundation"
};
pub const FunctionTypeExpr = struct {
param_types: []const *Node,
param_names: ?[]const ?[]const u8 = null, // optional documentation names
return_type: ?*Node, // null = void return
call_conv: CallingConvention = .default,
};
pub const ClosureTypeExpr = struct {
param_types: []const *Node,
param_names: ?[]const ?[]const u8 = null, // optional documentation names
return_type: ?*Node, // null = void return
/// Variadic heterogeneous type pack trailing the param list.
/// `Closure(..$args) -> R` ⇒ pack_name = "args", param_types = [].
/// `Closure(Prefix, ..$args)` ⇒ pack_name = "args", param_types = [Prefix].
pack_name: ?[]const u8 = null,
/// Projection on the pack: `Closure(..sources.T) -> R` ⇒ pack_name =
/// "sources", pack_projection = "T". Null for a bare `..pack`.
pack_projection: ?[]const u8 = null,
};
pub const TupleTypeExpr = struct {
field_types: []const *Node,
field_names: ?[]const []const u8, // null for positional
};
pub const TupleLiteral = struct {
elements: []const TupleElement,
};
pub const TupleElement = struct {
name: ?[]const u8, // null for positional
value: *Node,
};
pub const UfcsAlias = struct {
name: []const u8,
target: []const u8,
/// True when the alias NAME was a backtick raw identifier — exempt from
/// the reserved-type-name decl check.
is_raw: bool = false,
};
pub const CImportDecl = struct {
includes: []const []const u8,
sources: []const []const u8,
defines: []const []const u8,
flags: []const []const u8,
name: ?[]const u8 = null,
bitcode_paths: []const []const u8 = &.{}, // populated during import resolution
/// True when the namespace NAME was a backtick raw identifier — exempt
/// from the reserved-type-name decl check.
is_raw: bool = false,
};
pub const ProtocolMethodDecl = struct {
name: []const u8,
params: []const *Node, // type_expr nodes for parameter types (excluding implicit self)
param_names: []const []const u8, // parameter names (excluding implicit self)
param_name_spans: []const Span = &.{}, // one per `param_names` entry; empty for synthesized methods
/// One per `param_names` entry: true when written as a backtick raw
/// identifier — exempt from the reserved-type-name check.
/// Empty for synthesized methods (treated as all-false).
param_name_is_raw: []const bool = &.{},
return_type: ?*Node, // null = void return
default_body: ?*Node, // null = required method, non-null = default implementation
};
pub const ProtocolDecl = struct {
name: []const u8,
methods: []const ProtocolMethodDecl,
is_inline: bool = false, // #inline — embedded fn ptrs instead of vtable pointer
type_params: []const StructTypeParam = &.{}, // for `protocol(Target: Type) { ... }`
/// True when the declared NAME was a backtick raw identifier — exempt from
/// the reserved-type-name decl check.
is_raw: bool = false,
/// Defining module path (stamped by `resolveImports`), so a parameterized
/// protocol instantiated cross-module resolves its method signature types in
/// the module that declares it (E4 — the protocol analog of
/// `StructTemplate.source_file`). Null for a synthesized/sourceless decl.
source_file: ?[]const u8 = null,
};
pub const RuntimeKind = enum {
jni_class,
jni_interface,
objc_class,
objc_protocol,
swift_class,
swift_struct,
swift_protocol,
};
pub const RuntimeMethodDecl = struct {
name: []const u8,
params: []const *Node, // type_expr nodes — first is `*Self` for instance methods
param_names: []const []const u8,
param_name_spans: []const Span = &.{}, // one per `param_names` entry; empty for synthesized methods
/// One per `param_names` entry: true when written as a backtick raw
/// identifier — exempt from the reserved-type-name check.
/// Empty for synthesized methods (treated as all-false).
param_name_is_raw: []const bool = &.{},
return_type: ?*Node, // null = void
is_static: bool = false, // true for `static name :: ...`
jni_descriptor_override: ?[]const u8 = null, // `#jni_method_descriptor("(Sig)Ret")` — JNI runtime only
selector_override: ?[]const u8 = null, // `#selector("explicit:string")` — Obj-C runtime only (Phase 3.2)
body: ?*Node = null, // sx-side implementation (defined-class only). null = `;`-terminated decl referencing inherited / external method.
};
pub const RuntimeFieldDecl = struct {
name: []const u8,
field_type: *Node, // type_expr node
/// True iff the declaration carries a `#property[(...)]` directive
/// (M2.2). For runtime classes, that means synthesize getter/setter
/// dispatch through `objc_msgSend`; for sx-defined classes it adds
/// runtime-introspectable property metadata + ARC-aware setter
/// emission (Month 4 wires the latter).
is_property: bool = false,
/// Comma-separated modifier names from `#property(strong, weak, ...)`.
/// Stored verbatim; semantic interpretation lands in M4.2.
property_modifiers: []const []const u8 = &.{},
};
pub const RuntimeClassMember = union(enum) {
method: RuntimeMethodDecl,
field: RuntimeFieldDecl, // JNI runtime only (sema-checked in later step)
extends: []const u8, // sx-side alias name (right of `#extends`)
implements: []const u8, // sx-side alias name (right of `#implements`)
};
pub const RuntimeClassDecl = struct {
name: []const u8, // sx-side alias (left of `::`)
runtime_path: []const u8, // directive arg: "java/path/Foo" / "NSString" / "Foundation.URL"
runtime: RuntimeKind,
members: []const RuntimeClassMember = &.{},
is_extern: bool = false, // `#objc_class(…) extern` — class is provided by the runtime; we only reference it (vs `export`, which defines + registers a new sx class)
is_main: bool = false, // `#jni_main` / `#objc_main` — class is the launchable entry (Activity / UIApplicationDelegate / ...)
/// True when the sx-side alias NAME was a backtick raw identifier — exempt
/// from the reserved-type-name decl check.
is_raw: bool = false,
/// Defining module path (stamped by `resolveImports`), so the IMP trampolines
/// emitted for an sx-defined class resolve their method-signature types in the
/// module that declares the class — not the (cross-module) lowering site that
/// happens to trigger emission (E4). Null for a synthesized/sourceless decl.
source_file: ?[]const u8 = null,
};
pub const JniEnvBlock = struct {
env: *Node, // expression yielding the *JNIEnv for this scope
body: *Node, // block (or expression) — runs with `env` scoped via TL push/pop
};
pub const ImplBlock = struct {
protocol_name: []const u8,
target_type: []const u8,
target_type_params: []const StructTypeParam = &.{}, // for `impl P for List($T)`
methods: []const *Node, // fn_decl nodes
protocol_type_args: []const *Node = &.{}, // for `impl Into(Block) for Source` — type args on the protocol side
target_type_expr: ?*Node = null, // populated for parameterised-protocol impls; carries non-identifier source spellings (e.g. `Closure() -> void`)
};