dot-shorthand and more
This commit is contained in:
295
examples/37-dot-shorthand.sx
Normal file
295
examples/37-dot-shorthand.sx
Normal file
@@ -0,0 +1,295 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
// ============================================================
|
||||
// Dot-shorthand tests: .identifier(args) unification
|
||||
// Tests both tagged enum backward compat and struct static methods
|
||||
// ============================================================
|
||||
|
||||
// --- Type declarations ---
|
||||
|
||||
Color :: enum { red; green; blue; }
|
||||
|
||||
Shape :: enum {
|
||||
circle: f32;
|
||||
rect: struct { w, h: f32; };
|
||||
none;
|
||||
}
|
||||
|
||||
Vec2 :: struct {
|
||||
x: f32;
|
||||
y: f32;
|
||||
|
||||
create :: (x: f32, y: f32) -> Vec2 { Vec2.{ x = x, y = y }; }
|
||||
zero :: () -> Vec2 { Vec2.{ x = 0.0, y = 0.0 }; }
|
||||
unit_x :: () -> Vec2 { Vec2.{ x = 1.0, y = 0.0 }; }
|
||||
add :: (a: Vec2, b: Vec2) -> Vec2 { Vec2.{ x = a.x + b.x, y = a.y + b.y }; }
|
||||
scale :: (v: Vec2, s: f32) -> Vec2 { Vec2.{ x = v.x * s, y = v.y * s }; }
|
||||
len :: (v: Vec2) -> s32 { xx (v.x + v.y); }
|
||||
}
|
||||
|
||||
EdgeInsets :: struct {
|
||||
top: f32;
|
||||
right: f32;
|
||||
bottom: f32;
|
||||
left: f32;
|
||||
|
||||
all :: (v: f32) -> EdgeInsets { EdgeInsets.{ top = v, right = v, bottom = v, left = v }; }
|
||||
symmetric :: (h: f32, v: f32) -> EdgeInsets { EdgeInsets.{ top = v, right = h, bottom = v, left = h }; }
|
||||
horizontal :: (h: f32) -> EdgeInsets { EdgeInsets.{ top = 0.0, right = h, bottom = 0.0, left = h }; }
|
||||
}
|
||||
|
||||
Trio :: struct {
|
||||
a: s32;
|
||||
b: s32;
|
||||
c: s32;
|
||||
|
||||
make :: (a: s32, b: s32, c: s32) -> Trio { Trio.{ a = a, b = b, c = c }; }
|
||||
sum :: (t: Trio) -> s32 { t.a + t.b + t.c; }
|
||||
}
|
||||
|
||||
Result :: enum {
|
||||
ok: s32;
|
||||
err: string;
|
||||
}
|
||||
|
||||
main :: () {
|
||||
// ============================================================
|
||||
// SECTION 1: Tagged enum backward compatibility
|
||||
// ============================================================
|
||||
print("--- tagged enum compat ---\n");
|
||||
|
||||
// T1: .variant(payload) in typed variable declaration
|
||||
{
|
||||
sh : Shape = .circle(3.14);
|
||||
print("T1: {}\n", sh.circle);
|
||||
}
|
||||
|
||||
// T2: Bare .variant (no payload) in typed variable
|
||||
{
|
||||
sh : Shape = .none;
|
||||
ms := if sh == {
|
||||
case .circle: 1;
|
||||
case .rect: 2;
|
||||
case .none: 3;
|
||||
};
|
||||
print("T2: {}\n", ms);
|
||||
}
|
||||
|
||||
// T3: .variant with struct payload
|
||||
{
|
||||
sh : Shape = .rect(.{ 5.0, 3.0 });
|
||||
print("T3: {} {}\n", sh.rect.w, sh.rect.h);
|
||||
}
|
||||
|
||||
// T4: Qualified Type.variant(payload) still works
|
||||
{
|
||||
sh := Shape.circle(2.71);
|
||||
print("T4: {}\n", sh.circle);
|
||||
}
|
||||
|
||||
// T5: Match with payload capture
|
||||
{
|
||||
sh : Shape = .circle(9.5);
|
||||
if sh == {
|
||||
case .circle: (r) { print("T5: {}\n", r); }
|
||||
case .rect: (sz) { print("T5: rect\n"); }
|
||||
case .none: print("T5: none\n");
|
||||
}
|
||||
}
|
||||
|
||||
// T6: Return .variant(payload) from function
|
||||
{
|
||||
make_shape :: (r: f32) -> Shape { .circle(r); }
|
||||
sh := make_shape(4.2);
|
||||
print("T6: {}\n", sh.circle);
|
||||
}
|
||||
|
||||
// T7: Reassignment with .variant(payload) and bare .variant
|
||||
{
|
||||
sh : Shape = .circle(1.0);
|
||||
print("T7a: {}\n", sh.circle);
|
||||
sh = .rect(.{ 2.0, 3.0 });
|
||||
print("T7b: {} {}\n", sh.rect.w, sh.rect.h);
|
||||
sh = .none;
|
||||
ms := if sh == {
|
||||
case .circle: 1;
|
||||
case .rect: 2;
|
||||
case .none: 3;
|
||||
};
|
||||
print("T7c: {}\n", ms);
|
||||
}
|
||||
|
||||
// T8: .variant(payload) as function argument (match-as-expression)
|
||||
{
|
||||
describe :: (sh: Shape) -> s32 {
|
||||
if sh == {
|
||||
case .circle: 10;
|
||||
case .rect: 20;
|
||||
case .none: 30;
|
||||
};
|
||||
}
|
||||
print("T8a: {}\n", describe(.circle(7.0)));
|
||||
print("T8b: {}\n", describe(.rect(.{ 3.0, 4.0 })));
|
||||
print("T8c: {}\n", describe(.none));
|
||||
}
|
||||
|
||||
// T9: Tagged enum with string payload
|
||||
{
|
||||
r : Result = .ok(42);
|
||||
ms := if r == {
|
||||
case .ok: (v) { v; }
|
||||
case .err: (e) { -1; }
|
||||
};
|
||||
print("T9: {}\n", ms);
|
||||
}
|
||||
|
||||
// T10: Match as expression returning tagged enum
|
||||
{
|
||||
select :: (n: s32) -> Shape {
|
||||
if n == {
|
||||
case 0: .none;
|
||||
case 1: .circle(1.0);
|
||||
else: .rect(.{ 9.0, 9.0 });
|
||||
};
|
||||
}
|
||||
print("T10a: {}\n", if select(0) == { case .none: 1; else: 0; });
|
||||
print("T10b: {}\n", select(1).circle);
|
||||
print("T10c: {}\n", select(2).rect.w);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SECTION 2: Struct static method shorthand
|
||||
// ============================================================
|
||||
print("--- struct static shorthand ---\n");
|
||||
|
||||
// S1: .method(args) as function argument (the motivating use case)
|
||||
{
|
||||
print_vec :: (v: Vec2) { print("S1: {} {}\n", v.x, v.y); }
|
||||
print_vec(.create(3.0, 4.0));
|
||||
}
|
||||
|
||||
// S2: .method(args) in typed variable declaration
|
||||
{
|
||||
v : Vec2 = .create(5.0, 6.0);
|
||||
print("S2: {} {}\n", v.x, v.y);
|
||||
}
|
||||
|
||||
// S3: Return .method(args) from function with return type
|
||||
{
|
||||
make_vec :: () -> Vec2 { .create(7.0, 8.0); }
|
||||
v := make_vec();
|
||||
print("S3: {} {}\n", v.x, v.y);
|
||||
}
|
||||
|
||||
// S4: Zero-arg static method (factory)
|
||||
{
|
||||
print_vec :: (v: Vec2) { print("S4: {} {}\n", v.x, v.y); }
|
||||
print_vec(.zero());
|
||||
print_vec(.unit_x());
|
||||
}
|
||||
|
||||
// S5: Three-arg static method (proves multi-arg works)
|
||||
{
|
||||
print_trio :: (t: Trio) { print("S5: {}\n", t.a + t.b + t.c); }
|
||||
print_trio(.make(10, 20, 30));
|
||||
}
|
||||
|
||||
// S6: Two-arg shorthand matching the EdgeInsets use case
|
||||
{
|
||||
apply_insets :: (ei: EdgeInsets) { print("S6: {} {} {} {}\n", ei.top, ei.right, ei.bottom, ei.left); }
|
||||
apply_insets(.all(8.0));
|
||||
apply_insets(.symmetric(16.0, 8.0));
|
||||
apply_insets(.horizontal(12.0));
|
||||
}
|
||||
|
||||
// S7: Result of .method() used in further computation
|
||||
{
|
||||
v : Vec2 = .create(3.0, 4.0);
|
||||
print("S7: {}\n", Vec2.len(v));
|
||||
}
|
||||
|
||||
// S8: Chained qualified + shorthand — ensure both work together
|
||||
{
|
||||
v1 := Vec2.create(1.0, 2.0);
|
||||
print_vec :: (v: Vec2) { print("S8: {} {}\n", v.x, v.y); }
|
||||
print_vec(.create(3.0, 4.0));
|
||||
print("S8q: {} {}\n", v1.x, v1.y);
|
||||
}
|
||||
|
||||
// S9: Static method taking struct of same type as args
|
||||
{
|
||||
v : Vec2 = .add(.create(1.0, 2.0), .create(3.0, 4.0));
|
||||
print("S9: {} {}\n", v.x, v.y);
|
||||
}
|
||||
|
||||
// S10: Static method + piped result
|
||||
{
|
||||
v := Vec2.create(2.0, 3.0) |> Vec2.scale(2.0);
|
||||
print("S10: {} {}\n", v.x, v.y);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SECTION 3: Edge cases mixing both
|
||||
// ============================================================
|
||||
print("--- edge cases ---\n");
|
||||
|
||||
// E1: Both tagged enum and struct shorthand in same scope
|
||||
{
|
||||
sh : Shape = .circle(5.0);
|
||||
v : Vec2 = .create(1.0, 2.0);
|
||||
print("E1: {} {} {}\n", sh.circle, v.x, v.y);
|
||||
}
|
||||
|
||||
// E2: Function taking both types — each resolves correctly
|
||||
{
|
||||
use_both :: (sh: Shape, v: Vec2) {
|
||||
ms : s32 = 0;
|
||||
if sh == { case .circle: (r) { ms = xx r; } else: {} }
|
||||
print("E2: {} {} {}\n", ms, v.x, v.y);
|
||||
}
|
||||
use_both(.circle(9.0), .create(1.0, 2.0));
|
||||
}
|
||||
|
||||
// E3: Bare .variant (no parens) as function arg
|
||||
{
|
||||
check_none :: (sh: Shape) -> s32 {
|
||||
if sh == { case .none: 1; else: 0; };
|
||||
}
|
||||
print("E3: {}\n", check_none(.none));
|
||||
}
|
||||
|
||||
// E4: Nested shorthand — .method takes a struct param created with shorthand
|
||||
// (inner .create must resolve via the method's parameter type, not the outer type)
|
||||
{
|
||||
v : Vec2 = .add(Vec2.create(1.0, 2.0), Vec2.create(3.0, 4.0));
|
||||
print("E4: {} {}\n", v.x, v.y);
|
||||
}
|
||||
|
||||
// E5: Tagged enum .variant(payload) in match-as-expression
|
||||
{
|
||||
sh : Shape = .circle(42.0);
|
||||
r : s32 = 0;
|
||||
if sh == {
|
||||
case .circle: (v) { r = xx v; }
|
||||
case .rect: (sz) { r = xx sz.w; }
|
||||
case .none: r = xx -1;
|
||||
}
|
||||
print("E5: {}\n", r);
|
||||
}
|
||||
|
||||
// E6: Color enum (plain, not tagged) still works with bare .variant
|
||||
{
|
||||
c : Color = .green;
|
||||
ci : s32 = xx c;
|
||||
print("E6: {}\n", ci);
|
||||
}
|
||||
|
||||
// E7: Struct shorthand in typed variable, then pass to function
|
||||
{
|
||||
ei : EdgeInsets = .symmetric(10.0, 20.0);
|
||||
show :: (e: EdgeInsets) { print("E7: {} {}\n", e.top, e.left); }
|
||||
show(ei);
|
||||
}
|
||||
|
||||
print("=== DONE ===\n");
|
||||
}
|
||||
@@ -277,6 +277,13 @@ SumBox :: struct ($T: Type/Summable) {
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Struct constants test
|
||||
Phys :: struct {
|
||||
x, y: f32;
|
||||
GRAVITY :f32: 9.81;
|
||||
MAX_SPEED :: 100;
|
||||
}
|
||||
|
||||
// Init block test struct
|
||||
Builder :: struct {
|
||||
total: s32;
|
||||
@@ -1702,6 +1709,15 @@ END;
|
||||
b := y ?? 99;
|
||||
print("coalesce a: {}\n", a); // coalesce a: 42
|
||||
print("coalesce b: {}\n", b); // coalesce b: 99
|
||||
|
||||
// Chained ?? (right-associative): a ?? b ?? c
|
||||
z: ?s32 = null;
|
||||
c := x ?? y ?? 0;
|
||||
d := z ?? y ?? 99;
|
||||
e := z ?? z ?? 0;
|
||||
print("chained ?? c: {}\n", c); // chained ?? c: 42
|
||||
print("chained ?? d: {}\n", d); // chained ?? d: 99
|
||||
print("chained ?? e: {}\n", e); // chained ?? e: 0
|
||||
}
|
||||
|
||||
// If-binding (safe unwrap)
|
||||
@@ -2902,6 +2918,15 @@ END;
|
||||
print("AE5: {}\n", acc.total);
|
||||
}
|
||||
|
||||
// --- Struct Constants ---
|
||||
print("=== Struct Constants ===\n");
|
||||
{
|
||||
print("gravity: {}\n", Phys.GRAVITY); // gravity: 9.810000
|
||||
print("max speed: {}\n", Phys.MAX_SPEED); // max speed: 100
|
||||
p := Phys.{ x = 0.0, y = Phys.GRAVITY };
|
||||
print("p.y: {}\n", p.y); // p.y: 9.810000
|
||||
}
|
||||
|
||||
// --- Init Blocks (IB) ---
|
||||
print("=== Init Blocks ===\n");
|
||||
|
||||
@@ -2954,5 +2979,43 @@ END;
|
||||
print("IB5: {}\n", result);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SECTION: Struct static method shorthand (.method(args) syntax)
|
||||
// ============================================================
|
||||
print("--- struct static method shorthand ---\n");
|
||||
|
||||
// SM1: Basic shorthand — .create(args) resolves to Dims.create(args)
|
||||
{
|
||||
Dims :: struct {
|
||||
w: f32;
|
||||
h: f32;
|
||||
|
||||
create :: (w: f32, h: f32) -> Dims {
|
||||
Dims.{ w = w, h = h };
|
||||
}
|
||||
|
||||
square :: (size: f32) -> Dims {
|
||||
Dims.{ w = size, h = size };
|
||||
}
|
||||
}
|
||||
use_dims :: (d: Dims) { print("SM1: {} {}\n", d.w, d.h); }
|
||||
use_dims(.create(16.0, 8.0));
|
||||
use_dims(.square(5.0));
|
||||
}
|
||||
|
||||
// SM2: Shorthand in variable declaration with explicit type
|
||||
{
|
||||
Pair :: struct {
|
||||
a: s64;
|
||||
b: s64;
|
||||
|
||||
make :: (a: s64, b: s64) -> Pair {
|
||||
Pair.{ a = a, b = b };
|
||||
}
|
||||
}
|
||||
p : Pair = .make(10, 20);
|
||||
print("SM2: {} {}\n", p.a, p.b);
|
||||
}
|
||||
|
||||
print("=== DONE ===\n");
|
||||
}
|
||||
|
||||
@@ -144,7 +144,6 @@ pub const Identifier = struct {
|
||||
|
||||
pub const EnumLiteral = struct {
|
||||
name: []const u8, // without the leading dot
|
||||
payload: ?*Node = null, // non-null for enum variants with payloads (tagged unions)
|
||||
};
|
||||
|
||||
pub const BinaryOp = struct {
|
||||
@@ -295,6 +294,7 @@ pub const StructDecl = struct {
|
||||
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 {
|
||||
|
||||
155
src/codegen.zig
155
src/codegen.zig
@@ -1398,7 +1398,10 @@ pub const CodeGen = struct {
|
||||
_ = try self.registerFnDecl(fd, fd.name);
|
||||
}
|
||||
},
|
||||
.struct_decl => |sd| try self.registerStructMethods(sd),
|
||||
.struct_decl => |sd| {
|
||||
try self.registerStructMethods(sd);
|
||||
try self.registerStructConstants(sd);
|
||||
},
|
||||
.const_decl => |cd| {
|
||||
if (cd.value.data == .builtin_expr or cd.value.data == .type_expr) {
|
||||
// already handled
|
||||
@@ -2501,6 +2504,7 @@ pub const CodeGen = struct {
|
||||
},
|
||||
.struct_decl => |sd| {
|
||||
try self.registerStructMethods(sd);
|
||||
try self.registerStructConstants(sd);
|
||||
},
|
||||
.const_decl => |cd| {
|
||||
if (cd.value.data == .builtin_expr) {
|
||||
@@ -3141,6 +3145,16 @@ pub const CodeGen = struct {
|
||||
},
|
||||
.struct_decl => |sd| {
|
||||
try self.registerStructType(sd);
|
||||
try self.registerStructMethods(sd);
|
||||
// Method bodies are deferred — they'll be generated via genStructMethodBodies
|
||||
// in registerStructMethods (non-generic methods are registered with registerFnDecl,
|
||||
// and their bodies are deferred via shouldDeferFnBody or generated inline)
|
||||
for (sd.methods) |method_node| {
|
||||
const fd = method_node.data.fn_decl;
|
||||
if (fd.type_params.len > 0) continue;
|
||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ sd.name, fd.name });
|
||||
try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = qualified, .namespace = sd.name, .source_file = self.current_source_file });
|
||||
}
|
||||
return null;
|
||||
},
|
||||
.union_decl => {
|
||||
@@ -3344,6 +3358,13 @@ pub const CodeGen = struct {
|
||||
const lit_alloca = try self.genStructLiteral(vd.value.?.data.struct_literal, sname);
|
||||
try self.registerVariable(vd.name, lit_alloca, sx_ty);
|
||||
return null;
|
||||
} else if (vd.value.?.data == .call and vd.value.?.data.call.callee.data == .enum_literal) {
|
||||
// .method(args) — struct static method shorthand with inferred type
|
||||
const cn = vd.value.?.data.call;
|
||||
const method_name = cn.callee.data.enum_literal.name;
|
||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ sname, method_name });
|
||||
const val = try self.genCallByName(qualified, cn);
|
||||
_ = c.LLVMBuildStore(self.builder, val, alloca);
|
||||
} else if (vd.value.?.data == .call) {
|
||||
// Function call returning a struct — result is a value, store to alloca
|
||||
const val = try self.genExpr(vd.value.?);
|
||||
@@ -3425,14 +3446,18 @@ pub const CodeGen = struct {
|
||||
} else if (vd.value.?.data == .undef_literal) {
|
||||
self.storeUndef(info.llvm_type, alloca);
|
||||
} else if (vd.value.?.data == .enum_literal) {
|
||||
const el = vd.value.?.data.enum_literal;
|
||||
const lit_alloca = try self.genTaggedEnumLiteral(el, uname);
|
||||
try self.registerVariable(vd.name, lit_alloca, sx_ty);
|
||||
return null;
|
||||
const val = try self.genTaggedEnumLiteral(vd.value.?.data.enum_literal.name, null, uname);
|
||||
_ = c.LLVMBuildStore(self.builder, val, alloca);
|
||||
} else if (vd.value.?.data == .call and vd.value.?.data.call.callee.data == .enum_literal) {
|
||||
// .variant(payload) — tagged enum construction with inferred type
|
||||
const cn = vd.value.?.data.call;
|
||||
const payload_node: ?*Node = if (cn.args.len > 0) cn.args[0] else null;
|
||||
const val = try self.genTaggedEnumLiteral(cn.callee.data.enum_literal.name, payload_node, uname);
|
||||
_ = c.LLVMBuildStore(self.builder, val, alloca);
|
||||
} else if (vd.value.?.data == .call) {
|
||||
// Call returning a union — could be enum construction (alloca) or function call (value)
|
||||
// Call returning a union — function call (value)
|
||||
const result = try self.genExpr(vd.value.?);
|
||||
_ = c.LLVMBuildStore(self.builder, self.loadIfPointer(result, info.llvm_type, "union_load"), alloca);
|
||||
_ = c.LLVMBuildStore(self.builder, result, alloca);
|
||||
} else {
|
||||
// Other expression — try genExprAsType
|
||||
const result = try self.genExprAsType(vd.value.?, sx_ty);
|
||||
@@ -4349,7 +4374,7 @@ pub const CodeGen = struct {
|
||||
},
|
||||
.enum_literal => |el| {
|
||||
if (self.current_return_type.isUnion()) {
|
||||
return self.genTaggedEnumLiteral(el, self.current_return_type.union_type);
|
||||
return self.genTaggedEnumLiteral(el.name, null, self.current_return_type.union_type);
|
||||
}
|
||||
if (self.current_return_type.isEnum()) {
|
||||
return self.genEnumLiteral(el.name, self.current_return_type.enum_type);
|
||||
@@ -4932,6 +4957,38 @@ pub const CodeGen = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn registerStructConstants(self: *CodeGen, sd: ast.StructDecl) !void {
|
||||
if (sd.constants.len == 0) return;
|
||||
|
||||
try self.namespaces.put(sd.name, {});
|
||||
|
||||
for (sd.constants) |const_node| {
|
||||
const cd = const_node.data.const_decl;
|
||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ sd.name, cd.name });
|
||||
|
||||
// Reuse the same registration logic as top-level constants
|
||||
const sx_ty = if (cd.type_annotation) |ta|
|
||||
self.resolveType(ta)
|
||||
else
|
||||
self.inferType(cd.value);
|
||||
if (sx_ty == .void_type) continue;
|
||||
|
||||
const const_val = self.evalConstant(cd.value, sx_ty) orelse continue;
|
||||
|
||||
const name_z = try self.allocator.dupeZ(u8, qualified);
|
||||
const global = c.LLVMAddGlobal(self.module, self.typeToLLVM(sx_ty), name_z.ptr);
|
||||
c.LLVMSetInitializer(global, const_val);
|
||||
c.LLVMSetGlobalConstant(global, 1);
|
||||
|
||||
try self.comptime_globals.put(qualified, .{
|
||||
.global = global,
|
||||
.ty = sx_ty,
|
||||
.expr = cd.value,
|
||||
.is_resolved = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a protocol declaration. For #inline protocols, this generates
|
||||
/// a struct type with ctx + fn-ptr fields and wrapper methods.
|
||||
fn registerProtocolDecl(self: *CodeGen, pd: ast.ProtocolDecl) !void {
|
||||
@@ -5843,7 +5900,7 @@ pub const CodeGen = struct {
|
||||
try self.type_registry.put(ud.name, .{ .union_info = uinfo });
|
||||
}
|
||||
|
||||
fn genTaggedEnumLiteral(self: *CodeGen, el: ast.EnumLiteral, expected_union_name: ?[]const u8) !c.LLVMValueRef {
|
||||
fn genTaggedEnumLiteral(self: *CodeGen, variant_name: []const u8, payload_node: ?*Node, expected_union_name: ?[]const u8) !c.LLVMValueRef {
|
||||
const uname = expected_union_name orelse
|
||||
(if (self.current_return_type.isUnion()) self.current_return_type.union_type else null) orelse
|
||||
return self.emitError("cannot infer enum type for literal");
|
||||
@@ -5853,12 +5910,12 @@ pub const CodeGen = struct {
|
||||
// Find variant index
|
||||
var variant_idx: ?u32 = null;
|
||||
for (info.variant_names, 0..) |vn, i| {
|
||||
if (std.mem.eql(u8, vn, el.name)) {
|
||||
if (std.mem.eql(u8, vn, variant_name)) {
|
||||
variant_idx = @intCast(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const idx = variant_idx orelse return self.emitErrorFmt("no variant '{s}' in enum '{s}'", .{ el.name, resolved_name });
|
||||
const idx = variant_idx orelse return self.emitErrorFmt("no variant '{s}' in enum '{s}'", .{ variant_name, resolved_name });
|
||||
|
||||
// Alloca union
|
||||
const alloca = self.buildEntryBlockAlloca(info.llvm_type, "union_tmp");
|
||||
@@ -5870,15 +5927,15 @@ pub const CodeGen = struct {
|
||||
_ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(tag_ty, tag_val, 0), tag_gep);
|
||||
|
||||
// Store payload (field 1) if not void
|
||||
if (el.payload) |payload_node| {
|
||||
if (payload_node) |pnode| {
|
||||
const variant_ty = info.variant_types[idx];
|
||||
if (variant_ty != .void_type) {
|
||||
const payload_val = try self.genExprAsType(payload_node, variant_ty);
|
||||
const payload_val = try self.genExprAsType(pnode, variant_ty);
|
||||
self.storeStructField(info.llvm_type, alloca, info.payload_field_index, payload_val);
|
||||
}
|
||||
}
|
||||
|
||||
return alloca;
|
||||
return c.LLVMBuildLoad2(self.builder, info.llvm_type, alloca, "union_val");
|
||||
}
|
||||
|
||||
fn genStructLiteral(self: *CodeGen, sl: ast.StructLiteral, expected_struct_name: ?[]const u8) anyerror!c.LLVMValueRef {
|
||||
@@ -6176,8 +6233,24 @@ pub const CodeGen = struct {
|
||||
|
||||
// Enum/union literal assigned to union type: construct tagged enum
|
||||
if (node.data == .enum_literal and target_ty.isUnion()) {
|
||||
const el = node.data.enum_literal;
|
||||
return self.genTaggedEnumLiteral(el, target_ty.union_type);
|
||||
return self.genTaggedEnumLiteral(node.data.enum_literal.name, null, target_ty.union_type);
|
||||
}
|
||||
|
||||
// Call with enum_literal callee: .variant(payload) or .method(args) with known target type
|
||||
if (node.data == .call and node.data.call.callee.data == .enum_literal) {
|
||||
const call_node = node.data.call;
|
||||
const el_name = call_node.callee.data.enum_literal.name;
|
||||
|
||||
if (target_ty.isUnion()) {
|
||||
const payload_node: ?*Node = if (call_node.args.len > 0) call_node.args[0] else null;
|
||||
return self.genTaggedEnumLiteral(el_name, payload_node, target_ty.union_type);
|
||||
}
|
||||
|
||||
if (target_ty.isStruct()) {
|
||||
const struct_name = self.resolveAlias(target_ty.struct_type);
|
||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ struct_name, el_name });
|
||||
return self.genCallByName(qualified, call_node);
|
||||
}
|
||||
}
|
||||
|
||||
// Struct literal targeting union type: .Variant.{fields} pattern
|
||||
@@ -7393,6 +7466,15 @@ pub const CodeGen = struct {
|
||||
return self.emitErrorFmt("no field '{s}' on Any (available: .tag, .value)", .{fa.field});
|
||||
}
|
||||
}
|
||||
// Namespace constant: TypeName.CONSTANT
|
||||
const ns_name = fa.object.data.identifier.name;
|
||||
if (self.namespaces.contains(ns_name) or self.type_registry.contains(ns_name)) {
|
||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns_name, fa.field });
|
||||
if (self.comptime_globals.getPtr(qualified)) |ct| {
|
||||
if (!ct.is_resolved) try self.resolveComptimeGlobal(ct);
|
||||
return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(ct.ty), ct.global, "struct_const");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Non-identifier object: evaluate expression and check type
|
||||
const obj_val = try self.genExpr(fa.object);
|
||||
@@ -7966,6 +8048,24 @@ pub const CodeGen = struct {
|
||||
}
|
||||
|
||||
fn genCall(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef {
|
||||
// Dot-shorthand call: .variant(payload) or .method(args) with type inferred from context
|
||||
if (call_node.callee.data == .enum_literal) {
|
||||
const el_name = call_node.callee.data.enum_literal.name;
|
||||
|
||||
if (self.current_return_type.isUnion()) {
|
||||
const payload_node: ?*Node = if (call_node.args.len > 0) call_node.args[0] else null;
|
||||
return self.genTaggedEnumLiteral(el_name, payload_node, self.current_return_type.union_type);
|
||||
}
|
||||
|
||||
if (self.current_return_type.isStruct()) {
|
||||
const struct_name = self.resolveAlias(self.current_return_type.struct_type);
|
||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ struct_name, el_name });
|
||||
return self.genCallByName(qualified, call_node);
|
||||
}
|
||||
|
||||
return self.emitErrorFmt("cannot infer type for '.{s}(...)' call", .{el_name});
|
||||
}
|
||||
|
||||
if (call_node.callee.data == .field_access) {
|
||||
const fa = call_node.callee.data.field_access;
|
||||
|
||||
@@ -7988,10 +8088,7 @@ pub const CodeGen = struct {
|
||||
if (rty.isUnion()) {
|
||||
const type_name = rty.union_type;
|
||||
const payload_node: ?*Node = if (call_node.args.len > 0) call_node.args[0] else null;
|
||||
return self.genTaggedEnumLiteral(.{
|
||||
.name = fa.field,
|
||||
.payload = payload_node,
|
||||
}, type_name);
|
||||
return self.genTaggedEnumLiteral(fa.field, payload_node, type_name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11158,6 +11255,14 @@ pub const CodeGen = struct {
|
||||
return .void_type;
|
||||
},
|
||||
.call => |call_node| {
|
||||
// Dot-shorthand call: .variant(payload) — type from context
|
||||
if (call_node.callee.data == .enum_literal) {
|
||||
if (self.current_return_type.isEnum()) return self.current_return_type;
|
||||
if (self.current_return_type.isUnion()) return self.current_return_type;
|
||||
if (self.current_return_type.isStruct()) return self.current_return_type;
|
||||
return .{ .enum_type = "" };
|
||||
}
|
||||
|
||||
// Check for union literal pattern: Type.variant(payload)
|
||||
if (call_node.callee.data == .field_access) {
|
||||
const fa = call_node.callee.data.field_access;
|
||||
@@ -11438,6 +11543,16 @@ pub const CodeGen = struct {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Namespace constant: TypeName.CONSTANT
|
||||
if (fa.object.data == .identifier) {
|
||||
const ns_name = fa.object.data.identifier.name;
|
||||
if (self.namespaces.contains(ns_name) or self.type_registry.contains(ns_name)) {
|
||||
const qualified = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns_name, fa.field }) catch return Type.s(64);
|
||||
if (self.comptime_globals.getPtr(qualified)) |ct| {
|
||||
return ct.ty;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Type.s(64);
|
||||
},
|
||||
.index_expr => |ie| {
|
||||
|
||||
@@ -742,6 +742,7 @@ pub const Parser = struct {
|
||||
var field_defaults = std.ArrayList(?*Node).empty;
|
||||
var using_entries = std.ArrayList(ast.UsingEntry).empty;
|
||||
var methods = std.ArrayList(*Node).empty;
|
||||
var constants = std.ArrayList(*Node).empty;
|
||||
|
||||
while (self.current.tag != .r_brace and self.current.tag != .eof) {
|
||||
// Check for #using directive
|
||||
@@ -769,17 +770,26 @@ pub const Parser = struct {
|
||||
if (self.current.tag == .l_paren and self.isFunctionDef()) {
|
||||
try methods.append(self.allocator, try self.parseFnDecl(method_name, method_start));
|
||||
} else {
|
||||
return self.fail("only function declarations are allowed inside struct bodies");
|
||||
// Non-function constant: name :: value;
|
||||
const value = try self.parseExpr();
|
||||
if (self.current.tag == .semicolon) self.advance();
|
||||
try constants.append(self.allocator, try self.createNode(method_start, .{ .const_decl = .{
|
||||
.name = method_name,
|
||||
.type_annotation = null,
|
||||
.value = value,
|
||||
} }));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse field group: name1, name2, ...: type (= default)?;
|
||||
// Or typed constant: name :Type: value;
|
||||
var group_names = std.ArrayList([]const u8).empty;
|
||||
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected field name in struct");
|
||||
}
|
||||
const field_start = self.current.loc.start;
|
||||
try group_names.append(self.allocator, self.tokenSlice(self.current));
|
||||
self.advance();
|
||||
|
||||
@@ -795,6 +805,19 @@ pub const Parser = struct {
|
||||
try self.expect(.colon);
|
||||
const field_type = try self.parseTypeExpr();
|
||||
|
||||
// Typed constant: name :Type: value; (second colon after type)
|
||||
if (self.current.tag == .colon and group_names.items.len == 1) {
|
||||
self.advance(); // skip second ':'
|
||||
const value = try self.parseExpr();
|
||||
if (self.current.tag == .semicolon) self.advance();
|
||||
try constants.append(self.allocator, try self.createNode(field_start, .{ .const_decl = .{
|
||||
.name = group_names.items[0],
|
||||
.type_annotation = field_type,
|
||||
.value = value,
|
||||
} }));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for default value: = expr
|
||||
var default_val: ?*Node = null;
|
||||
if (self.current.tag == .equal) {
|
||||
@@ -828,6 +851,7 @@ pub const Parser = struct {
|
||||
.type_params = try type_params.toOwnedSlice(self.allocator),
|
||||
.using_entries = try using_entries.toOwnedSlice(self.allocator),
|
||||
.methods = try methods.toOwnedSlice(self.allocator),
|
||||
.constants = try constants.toOwnedSlice(self.allocator),
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -1441,7 +1465,7 @@ pub const Parser = struct {
|
||||
// Null coalescing: expr ?? default
|
||||
if (self.current.tag == .question_question and Prec.null_coalesce >= min_prec) {
|
||||
self.advance();
|
||||
const rhs = try self.parseBinary(Prec.null_coalesce + 1);
|
||||
const rhs = try self.parseBinary(Prec.null_coalesce);
|
||||
lhs = try self.createNode(lhs.span.start, .{ .null_coalesce = .{ .lhs = lhs, .rhs = rhs } });
|
||||
continue;
|
||||
}
|
||||
@@ -1760,16 +1784,7 @@ pub const Parser = struct {
|
||||
}
|
||||
const name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
// Enum literal with payload: .variant(payload) — tagged enum (formerly union literal)
|
||||
if (self.current.tag == .l_paren) {
|
||||
self.advance(); // skip '('
|
||||
const payload = try self.parseExpr();
|
||||
try self.expect(.r_paren);
|
||||
return try self.createNode(start, .{ .enum_literal = .{
|
||||
.name = name,
|
||||
.payload = payload,
|
||||
} });
|
||||
}
|
||||
// Enum literal: .variant_name — parsePostfix handles optional (...) as a call
|
||||
return try self.createNode(start, .{ .enum_literal = .{ .name = name } });
|
||||
},
|
||||
.l_paren => {
|
||||
|
||||
12
src/sema.zig
12
src/sema.zig
@@ -850,12 +850,8 @@ pub const Analyzer = struct {
|
||||
.union_decl => |ud| {
|
||||
try self.addSymbol(ud.name, .enum_type, .{ .union_type = ud.name }, node.span);
|
||||
},
|
||||
.enum_literal => |el| {
|
||||
if (el.payload) |p| {
|
||||
try self.analyzeNode(p);
|
||||
}
|
||||
},
|
||||
// Leaf nodes — nothing to recurse into
|
||||
.enum_literal,
|
||||
.int_literal,
|
||||
.float_literal,
|
||||
.bool_literal,
|
||||
@@ -1255,12 +1251,8 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
|
||||
if (findNodeAtOffset(fi.value, offset)) |found| return found;
|
||||
}
|
||||
},
|
||||
.enum_literal => |el| {
|
||||
if (el.payload) |p| {
|
||||
if (findNodeAtOffset(p, offset)) |found| return found;
|
||||
}
|
||||
},
|
||||
// Leaf nodes
|
||||
.enum_literal,
|
||||
.identifier,
|
||||
.int_literal,
|
||||
.float_literal,
|
||||
|
||||
1
tests/expected/37-dot-shorthand.exit
Normal file
1
tests/expected/37-dot-shorthand.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
41
tests/expected/37-dot-shorthand.txt
Normal file
41
tests/expected/37-dot-shorthand.txt
Normal file
@@ -0,0 +1,41 @@
|
||||
--- tagged enum compat ---
|
||||
T1: 3.140000
|
||||
T2: 3
|
||||
T3: 5.000000 3.000000
|
||||
T4: 2.710000
|
||||
T5: 9.500000
|
||||
T6: 4.199999
|
||||
T7a: 1.000000
|
||||
T7b: 2.000000 3.000000
|
||||
T7c: 3
|
||||
T8a: 10
|
||||
T8b: 20
|
||||
T8c: 30
|
||||
T9: 42
|
||||
T10a: 1
|
||||
T10b: 1.000000
|
||||
T10c: 9.000000
|
||||
--- struct static shorthand ---
|
||||
S1: 3.000000 4.000000
|
||||
S2: 5.000000 6.000000
|
||||
S3: 7.000000 8.000000
|
||||
S4: 0.000000 0.000000
|
||||
S4: 1.000000 0.000000
|
||||
S5: 60
|
||||
S6: 8.000000 8.000000 8.000000 8.000000
|
||||
S6: 8.000000 16.000000 8.000000 16.000000
|
||||
S6: 0.000000 12.000000 0.000000 12.000000
|
||||
S7: 7
|
||||
S8: 3.000000 4.000000
|
||||
S8q: 1.000000 2.000000
|
||||
S9: 4.000000 6.000000
|
||||
S10: 4.000000 6.000000
|
||||
--- edge cases ---
|
||||
E1: 5.000000 1.000000 2.000000
|
||||
E2: 9 1.000000 2.000000
|
||||
E3: 1
|
||||
E4: 4.000000 6.000000
|
||||
E5: 42
|
||||
E6: 1
|
||||
E7: 20.000000 10.000000
|
||||
=== DONE ===
|
||||
@@ -396,6 +396,9 @@ opt y: null
|
||||
unwrap: 10
|
||||
coalesce a: 42
|
||||
coalesce b: 99
|
||||
chained ?? c: 42
|
||||
chained ?? d: 99
|
||||
chained ?? e: 0
|
||||
if-bind x: 7
|
||||
if-bind y: none
|
||||
match some: 55
|
||||
@@ -569,10 +572,18 @@ AE2: 8
|
||||
AE3: 102
|
||||
AE4: 51
|
||||
AE5: 15
|
||||
=== Struct Constants ===
|
||||
gravity: 9.810000
|
||||
max speed: 100
|
||||
p.y: 9.810000
|
||||
=== Init Blocks ===
|
||||
IB1: 60 3
|
||||
IB2: 142 2
|
||||
IB3: 5 1
|
||||
IB4: 100
|
||||
IB5: 52
|
||||
--- struct static method shorthand ---
|
||||
SM1: 16.000000 8.000000
|
||||
SM1: 5.000000 5.000000
|
||||
SM2: 10 20
|
||||
=== DONE ===
|
||||
|
||||
Reference in New Issue
Block a user