dot-shorthand and more

This commit is contained in:
agra
2026-02-25 15:51:22 +02:00
parent 4abc7abb54
commit f0569a8a3e
9 changed files with 576 additions and 43 deletions

View File

@@ -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 {

View File

@@ -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| {

View File

@@ -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 => {

View File

@@ -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,