Add range loop syntax:
- runtime for start..end (i) { } counting loop, cursor optional, end exclusive
- comptime inline for start..end (i) { } comptime-unrolled body
The inline form binds the cursor as an int_val comptime constant per
iteration, so xs[i] over a heterogeneous pack substitutes the concrete
per-position element -- the canonical's pack-iteration vehicle
(inline for 0..sources.len (i) { sources[i].addListener(...) }).
- AST: ForExpr.range_end, ForExpr.is_inline
- parser: parseForExpr range vs collection form; suppress_call flag so
N (i) is not read as a call N(i) while parsing a range bound
- lower: lowerRuntimeRangeFor / lowerInlineRangeFor; evalComptimeInt;
comptimeIndexOf extends pack-index resolution beyond int literals
Revises spec's inline for i in 0..N to the no-in, range-first, paren-cursor
form. Regression: examples/200-for-range.sx.
644 lines
18 KiB
Zig
644 lines
18 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,
|
|
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,
|
|
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,
|
|
foreign_expr: ForeignExpr,
|
|
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,
|
|
foreign_class_decl: ForeignClassDecl,
|
|
jni_env_block: JniEnvBlock,
|
|
|
|
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,
|
|
.namespace_decl => |d| d.name,
|
|
.ufcs_alias => |d| d.name,
|
|
.c_import_decl => |d| d.name,
|
|
.protocol_decl => |d| d.name,
|
|
.foreign_class_decl => |d| d.name,
|
|
else => null,
|
|
};
|
|
}
|
|
};
|
|
};
|
|
|
|
pub const Root = struct {
|
|
decls: []const *Node,
|
|
};
|
|
|
|
pub const CallingConvention = enum { default, c };
|
|
|
|
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,
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
pub const Block = struct {
|
|
stmts: []const *Node,
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
pub const Identifier = struct {
|
|
name: []const u8,
|
|
};
|
|
|
|
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
|
|
};
|
|
|
|
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) { ... }
|
|
};
|
|
|
|
pub const ConstDecl = struct {
|
|
name: []const u8,
|
|
type_annotation: ?*Node,
|
|
value: *Node,
|
|
};
|
|
|
|
pub const VarDecl = struct {
|
|
name: []const u8,
|
|
type_annotation: ?*Node,
|
|
value: ?*Node,
|
|
is_foreign: bool = false,
|
|
foreign_lib: ?[]const u8 = null,
|
|
foreign_name: ?[]const u8 = null,
|
|
};
|
|
|
|
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,
|
|
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 { ... }
|
|
};
|
|
|
|
pub const UnionDecl = struct {
|
|
name: []const u8,
|
|
field_names: []const []const u8,
|
|
field_types: []const *Node,
|
|
};
|
|
|
|
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
|
|
};
|
|
|
|
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
|
|
};
|
|
|
|
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
|
|
};
|
|
|
|
/// `$<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,
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
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")]
|
|
};
|
|
|
|
pub const IndexExpr = struct {
|
|
object: *Node,
|
|
index: *Node,
|
|
};
|
|
|
|
pub const SliceExpr = struct {
|
|
object: *Node,
|
|
start: ?*Node = null,
|
|
end: ?*Node = null,
|
|
};
|
|
|
|
pub const PointerTypeExpr = struct {
|
|
pointee_type: *Node,
|
|
};
|
|
|
|
pub const ManyPointerTypeExpr = struct {
|
|
element_type: *Node,
|
|
};
|
|
|
|
pub const OptionalTypeExpr = struct {
|
|
inner_type: *Node,
|
|
};
|
|
|
|
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
|
|
};
|
|
|
|
pub const ForExpr = struct {
|
|
iterable: *Node,
|
|
body: *Node,
|
|
capture_name: []const u8,
|
|
index_name: ?[]const u8 = null,
|
|
/// Range form `for start..end (i) { }`: `iterable` is the start, `range_end`
|
|
/// the (exclusive) end. Null for the iterate-a-collection form
|
|
/// (`for coll : (x) { }`). For the range form `capture_name` is the cursor
|
|
/// (empty when omitted, `for 0..N { }`).
|
|
range_end: ?*Node = null,
|
|
/// `inline for` — comptime-unrolled (range bounds must be comptime).
|
|
is_inline: bool = false,
|
|
};
|
|
|
|
pub const SpreadExpr = struct {
|
|
operand: *Node,
|
|
};
|
|
|
|
pub const NamespaceDecl = struct {
|
|
name: []const u8,
|
|
decls: []const *Node,
|
|
};
|
|
|
|
pub const ForeignExpr = struct {
|
|
library_ref: ?[]const u8 = null, // identifier name of library constant
|
|
c_name: ?[]const u8 = null, // C symbol name override
|
|
};
|
|
|
|
pub const LibraryDecl = struct {
|
|
lib_name: []const u8,
|
|
name: []const u8, // sx-side constant name
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
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
|
|
};
|
|
|
|
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)
|
|
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) { ... }`
|
|
};
|
|
|
|
pub const ForeignRuntime = enum {
|
|
jni_class,
|
|
jni_interface,
|
|
objc_class,
|
|
objc_protocol,
|
|
swift_class,
|
|
swift_struct,
|
|
swift_protocol,
|
|
};
|
|
|
|
pub const ForeignMethodDecl = struct {
|
|
name: []const u8,
|
|
params: []const *Node, // type_expr nodes — first is `*Self` for instance methods
|
|
param_names: []const []const u8,
|
|
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 ForeignFieldDecl = struct {
|
|
name: []const u8,
|
|
field_type: *Node, // type_expr node
|
|
/// True iff the declaration carries a `#property[(...)]` directive
|
|
/// (M2.2). For foreign 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 ForeignClassMember = union(enum) {
|
|
method: ForeignMethodDecl,
|
|
field: ForeignFieldDecl, // 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 ForeignClassDecl = struct {
|
|
name: []const u8, // sx-side alias (left of `::`)
|
|
foreign_path: []const u8, // directive arg: "java/path/Foo" / "NSString" / "Foundation.URL"
|
|
runtime: ForeignRuntime,
|
|
members: []const ForeignClassMember = &.{},
|
|
is_foreign: bool = false, // `#foreign #...` prefix — class is provided by the foreign runtime; we only reference it
|
|
is_main: bool = false, // `#jni_main` / `#objc_main` — class is the launchable entry (Activity / UIApplicationDelegate / ...)
|
|
};
|
|
|
|
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`)
|
|
};
|