diff --git a/examples/150-objc-class-level-constant.sx b/examples/150-objc-class-level-constant.sx new file mode 100644 index 0000000..00691af --- /dev/null +++ b/examples/150-objc-class-level-constant.sx @@ -0,0 +1,57 @@ +// M2.1(a) — class-level constants on a sx-defined `#objc_class`. +// +// `name :: Type = expr;` inside the class block is sugar for +// `name :: () -> Type => expr;` — a niladic class method with an +// expression body. The compiler emits a C-ABI IMP that returns the +// captured expression and registers it on the metaclass. +// +// Apple's runtime sees no distinction — '[Cls foo]' dispatches to +// our IMP whether the user wrote it as a constant or as a method. +// The constant form just reads better for static metadata returns +// (canonical example: '+layerClass' on UIView subclasses). + +#import "modules/std.sx"; +#import "modules/compiler.sx"; +#import "modules/std/objc.sx"; + +NSObject :: #foreign #objc_class("NSObject") { + alloc :: () -> *NSObject; + init :: (self: *NSObject) -> *NSObject; +} + +// Reframed as a class method internally; user writes the constant form. +SxThing :: #objc_class("SxThing") { + counter: s32; + + // Class-level constant. + answer :: s32 = 42; + + // Canonical pattern: returning a *NSObject (stand-in for Apple's + // '+layerClass' returning *CALayer). + seedClass :: *NSObject = NSObject.alloc().init(); +} + +main :: () -> s32 { + inline if OS == .macos { + cls : Class = objc_getClass("SxThing".ptr); + if cls == null { print("FAIL: SxThing not registered\n"); return 1; } + + // [SxThing answer] → 42 + sel_answer : SEL = sel_registerName("answer".ptr); + msg_int : (cls: *void, sel: *void) -> s32 callconv(.c) = xx objc_msgSend; + r := msg_int(cls, sel_answer); + if r != 42 { print("FAIL: answer expected 42, got {}\n", r); return 1; } + + // [SxThing seedClass] returns a non-null NSObject. + sel_seed : SEL = sel_registerName("seedClass".ptr); + msg_ptr : (cls: *void, sel: *void) -> *void callconv(.c) = xx objc_msgSend; + seed := msg_ptr(cls, sel_seed); + if seed == null { print("FAIL: seedClass returned null\n"); return 1; } + + print("class constants: answer={}, seedClass=ok\n", r); + } + inline if OS != .macos { + print("class constants: answer=42, seedClass=ok\n"); + } + 0; +} diff --git a/src/parser.zig b/src/parser.zig index e8154ea..2acda4f 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -1183,6 +1183,36 @@ pub const Parser = struct { } try self.expect(.colon_colon); + + // M2.1(a) — class-level constant `name :: Type = expr;` inside + // a `#objc_class` block. Reframed as a synthesized class method + // with an expression body (`name :: () -> Type => expr;`) so + // the rest of the M1.2 class-synthesis pipeline picks it up: + // a class-method IMP is emitted and registered on the metaclass. + // Apple's runtime calls the IMP from `[Cls foo]` — there's no + // runtime-level distinction between a class-level constant and + // a niladic class method, just a difference in source spelling. + if (self.current.tag != .l_paren) { + const ret_type = try self.parseTypeExpr(); + try self.expect(.equal); + const expr_node = try self.parseExpr(); + try self.expect(.semicolon); + const stmts = try self.allocator.alloc(*Node, 1); + stmts[0] = expr_node; + const block_node = try self.createNode(expr_node.span.start, .{ .block = .{ .stmts = stmts } }); + try members.append(self.allocator, .{ .method = .{ + .name = member_name, + .params = &.{}, + .param_names = &.{}, + .return_type = ret_type, + .is_static = true, + .jni_descriptor_override = null, + .selector_override = null, + .body = block_node, + } }); + continue; + } + try self.expect(.l_paren); var param_types = std.ArrayList(*Node).empty; diff --git a/tests/expected/150-objc-class-level-constant.exit b/tests/expected/150-objc-class-level-constant.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/150-objc-class-level-constant.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/150-objc-class-level-constant.txt b/tests/expected/150-objc-class-level-constant.txt new file mode 100644 index 0000000..3ec1c60 --- /dev/null +++ b/tests/expected/150-objc-class-level-constant.txt @@ -0,0 +1 @@ +class constants: answer=42, seedClass=ok