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).
963 lines
35 KiB
Zig
963 lines
35 KiB
Zig
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 C–E).
|
||
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`)
|
||
};
|