Adds:
field: T #property[(modifier, modifier, ...)];
inside #objc_class declarations. For FOREIGN classes (this slice),
'obj.field' and 'obj.field = x' lower as objc_msgSend dispatches —
no struct GEP, no per-field storage on the sx side. The receiver
is opaque and the Obj-C runtime owns the data.
Selector mangling (Apple convention):
getter: <fieldName> (e.g. 'count')
setter: set<FieldName>: (e.g. 'setBackgroundColor:')
So:
view.backgroundColor → [view backgroundColor]
view.backgroundColor = red → [view setBackgroundColor:red]
Plumbing:
- New token hash_property + lexer entry + LSP keyword classification.
- ForeignFieldDecl gains 'is_property' + 'property_modifiers' slice;
the parser captures both. Modifiers are recorded verbatim (strong,
weak, copy, readonly, getter("name"), ...) — semantic interpretation
lands with M4.2 ARC wiring.
- lowerFieldAccess: lookupObjcPropertyOnPointer() detects the case
before the auto-deref / struct-GEP path and dispatches via
lowerObjcPropertyGetter (objc_msg_send).
- lowerAssignment: same check on the field_access LHS routes to
lowerObjcPropertySetter (objc_msg_send with set<Field>:).
- inferExprType: 'obj.field' returns the property's declared type
so chained access / coerced assignment work.
151-objc-property-foreign.sx round-trips:
inst.tag → [inst tag] → reads g_probe_tag → 0
inst.tag = 42 → [inst setTag:42] → writes g_probe_tag
inst.tag = -7 → ditto
Final: 0 -> 42 -> -7 (real Obj-C runtime dispatch).
DEFERRED for M2.2 (later passes):
- Sx-defined property IMPs (synthesized getter/setter trampolines
reading/writing the state struct).
- Modifier-driven setter behavior: readonly (compile error on
write), copy (deep-copy), weak (objc_storeWeak), strong/assign
(Month 4.2 ARC ops).
- getter("name") / setter("name:") selector overrides.
181 example tests pass (+1). zig build test green.
604 lines
16 KiB
Zig
604 lines
16 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,
|
|
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,
|
|
/// 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
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
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
|
|
};
|
|
|
|
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`)
|
|
};
|