enum, union
This commit is contained in:
@@ -4,7 +4,7 @@ Vec4 :: struct {
|
||||
}
|
||||
|
||||
Complex :: struct {
|
||||
foo : union {
|
||||
foo : enum {
|
||||
S: s32;
|
||||
B: struct {
|
||||
val: string;
|
||||
|
||||
@@ -71,7 +71,7 @@ main :: () {
|
||||
|
||||
// inline generic type
|
||||
Sx :: (user: $T) -> Type {
|
||||
return union {
|
||||
return enum {
|
||||
counter: s32;
|
||||
user: T;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
Shape :: union {
|
||||
Shape :: enum {
|
||||
circle: f32;
|
||||
rect: s32;
|
||||
rect: struct { w, h: f32;};
|
||||
none;
|
||||
}
|
||||
|
||||
@@ -20,19 +20,19 @@ main :: () {
|
||||
print("none: {}\n", s);
|
||||
|
||||
// Reassign with payload
|
||||
s = .rect(42);
|
||||
s = .rect(.{4, 2});
|
||||
print("rect: {}\n", s);
|
||||
|
||||
// Explicit prefix construction
|
||||
sh :Shape = Shape.circle(2.71);
|
||||
print("sh: {}\n", sh);
|
||||
|
||||
// Field access on second union variable
|
||||
sh2 :Shape = .rect(10);
|
||||
// Field access on second variable
|
||||
sh2 :Shape = .rect(.{2,4});
|
||||
val := sh2.rect;
|
||||
print("rect val: {}\n", val);
|
||||
|
||||
// Match on union
|
||||
// Match on enum
|
||||
if sh2 == {
|
||||
case .circle: print("matched circle\n");
|
||||
case .rect: print("matched rect\n");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
list := List(s32).{};
|
||||
list : List(s32) = .{};
|
||||
|
||||
append(list, 1);
|
||||
|
||||
|
||||
@@ -153,9 +153,10 @@ main :: () {
|
||||
#version 330 core
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec3 aNormal;
|
||||
uniform mat4 uMVP;
|
||||
uniform mat4 uMVP;
|
||||
out vec3 vNormal;
|
||||
out vec3 vPos;
|
||||
|
||||
void main() {
|
||||
gl_Position = uMVP * vec4(aPos, 1.0);
|
||||
vNormal = aNormal;
|
||||
|
||||
28
examples/30-union.sx
Normal file
28
examples/30-union.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
Overlay :: union {
|
||||
f: f32;
|
||||
i: s32;
|
||||
}
|
||||
|
||||
Vec2 :: union {
|
||||
data: [2]f32;
|
||||
struct { x, y: f32; };
|
||||
}
|
||||
|
||||
main :: () {
|
||||
// Basic union: type punning
|
||||
o :Overlay = ---;
|
||||
o.f = 3.14;
|
||||
print("f={}\n", o.f);
|
||||
print("i={}\n", o.i);
|
||||
|
||||
// Union with anonymous struct: member promotion
|
||||
v :Vec2 = ---;
|
||||
v.x = 1.0;
|
||||
v.y = 2.0;
|
||||
print("x={}\n", v.x);
|
||||
print("y={}\n", v.y);
|
||||
print("data[0]={}\n", v.data[0]);
|
||||
print("data[1]={}\n", v.data[1]);
|
||||
}
|
||||
@@ -161,10 +161,6 @@ struct_to_string :: (s: $T) -> string {
|
||||
concat(result, "}");
|
||||
}
|
||||
|
||||
enum_to_string :: (e: $T) -> string {
|
||||
concat(".", field_name(T, cast(s64) e));
|
||||
}
|
||||
|
||||
vector_to_string :: (v: $T) -> string {
|
||||
result := "[";
|
||||
i := 0;
|
||||
@@ -205,7 +201,7 @@ pointer_to_string :: (p: $T) -> string {
|
||||
}
|
||||
}
|
||||
|
||||
union_to_string :: (u: $T) -> string {
|
||||
enum_to_string :: (u: $T) -> string {
|
||||
tag := cast(s64) u;
|
||||
result := concat(".", field_name(T, tag));
|
||||
payload := field_value(u, tag);
|
||||
@@ -230,7 +226,6 @@ any_to_string :: (val: Any) -> string {
|
||||
case vector: result = vector_to_string(cast(type) val);
|
||||
case array: result = array_to_string(cast(type) val);
|
||||
case slice: result = slice_to_string(cast(type) val);
|
||||
case union: result = union_to_string(cast(type) val);
|
||||
case pointer: result = pointer_to_string(cast(type) val);
|
||||
case type: { s : string = xx val; result = s; }
|
||||
}
|
||||
@@ -314,7 +309,7 @@ List :: struct ($T: Type) {
|
||||
cap: s64 = 0;
|
||||
}
|
||||
|
||||
append :: (list: *List($T), item: T) {
|
||||
append ::(list: *List($T), item: T) {
|
||||
if list.len >= list.cap {
|
||||
new_cap := if list.cap == 0 then 4 else list.cap * 2;
|
||||
new_items : [*]T = xx malloc(new_cap * size_of(T));
|
||||
|
||||
126
specs.md
126
specs.md
@@ -48,6 +48,8 @@ GLSL;
|
||||
### Keywords
|
||||
`if`, `else`, `then`, `while`, `break`, `continue`, `true`, `false`, `enum`, `struct`, `union`, `case`, `return`, `defer`, `xx`, `and`, `or`
|
||||
|
||||
> Note: `enum` is used for both payload-less and payload-bearing sum types (tagged unions). `union` is reserved for C-style untagged unions (memory overlays).
|
||||
|
||||
### Operators
|
||||
|
||||
| Operator | Meaning |
|
||||
@@ -102,15 +104,88 @@ GLSL;
|
||||
- `Type` — compile-time type value. At runtime, represented as an `i64` type tag (same tag space as `Any`).
|
||||
|
||||
### Enum Types
|
||||
User-defined sum types with named variants.
|
||||
User-defined sum types with named variants. Variants may optionally carry typed data (tagged unions). Internally, payload-less enums are represented as `i64` (variant index). Enums with payloads are represented as `{ i64, [max_payload_size x i8] }` (tag + data).
|
||||
|
||||
#### Declaration
|
||||
```sx
|
||||
Foo :: enum {
|
||||
variant1;
|
||||
variant2;
|
||||
// Payload-less enum
|
||||
Color :: enum {
|
||||
red;
|
||||
green;
|
||||
blue;
|
||||
}
|
||||
|
||||
// Enum with payloads (tagged union)
|
||||
Shape :: enum {
|
||||
circle: f32; // typed variant
|
||||
rect: s32; // typed variant
|
||||
none; // void variant
|
||||
}
|
||||
```
|
||||
Variants are referenced with dot-prefix syntax: `.variant1`
|
||||
|
||||
#### Construction
|
||||
```sx
|
||||
c := Color.red; // payload-less
|
||||
s :Shape = .circle(3.14); // inferred from context
|
||||
s = .none; // void variant
|
||||
s = Shape.rect(42); // explicit prefix
|
||||
```
|
||||
|
||||
#### Payload Access
|
||||
```sx
|
||||
r := s.circle; // load payload as f32 (undefined behavior if wrong variant active)
|
||||
```
|
||||
|
||||
#### Pattern Matching
|
||||
```sx
|
||||
if s == {
|
||||
case .circle: print("circle\n");
|
||||
case .rect: print("rect\n");
|
||||
case .none: print("none\n");
|
||||
}
|
||||
```
|
||||
|
||||
#### Enum Interpolation
|
||||
Payload-less enums print as `.variant`. Enums with payloads print as `.variant(value)` or `<TypeName tag=N>`:
|
||||
```sx
|
||||
print("{}", s); // .circle(3.140000)
|
||||
```
|
||||
|
||||
### Union Types (Untagged)
|
||||
C-style untagged unions for zero-cost memory overlays (type punning). All fields share the same memory — no tag, no runtime overhead. The LLVM representation is `[max_field_size x i8]`.
|
||||
|
||||
#### Declaration
|
||||
```sx
|
||||
Overlay :: union {
|
||||
f: f32;
|
||||
i: s32;
|
||||
}
|
||||
```
|
||||
All fields must have types (unlike enums, which may have void variants).
|
||||
|
||||
#### Anonymous Struct Fields (Member Promotion)
|
||||
Anonymous `struct` fields inside a union have their members promoted to the union namespace:
|
||||
```sx
|
||||
Vec2 :: union {
|
||||
data: [2]f32;
|
||||
struct { x, y: f32; };
|
||||
}
|
||||
```
|
||||
Access promoted members directly: `v.x`, `v.y` — these are zero-cost GEPs into the same underlying memory as `v.data[0]`, `v.data[1]`.
|
||||
|
||||
#### Initialization
|
||||
Unions must be initialized with `---` (undefined) and then assigned per-field:
|
||||
```sx
|
||||
o :Overlay = ---;
|
||||
o.f = 3.14;
|
||||
print("{}\n", o.i); // reinterpret bits as s32
|
||||
```
|
||||
|
||||
#### Restrictions
|
||||
- Pattern matching (`if x == { case ... }`) is not supported on unions.
|
||||
- Unions cannot be printed directly via `print("{}", union_val)` — access individual fields instead.
|
||||
|
||||
### Struct Types
|
||||
User-defined product types with named fields.
|
||||
```sx
|
||||
@@ -159,45 +234,6 @@ Struct values in string interpolation print as `TypeName{field:value, ...}`:
|
||||
print("{}", v1); // Vec4{x:1.0, y:2.0, z:3.0, w:0.0}
|
||||
```
|
||||
|
||||
### Union Types (Tagged Unions)
|
||||
Sum types where each variant can carry typed data or be void. Internally represented as `{ i64, [max_payload_size x i8] }`.
|
||||
|
||||
#### Declaration
|
||||
```sx
|
||||
Shape :: union {
|
||||
circle: f32; // typed variant
|
||||
rect: s32; // typed variant
|
||||
none; // void variant
|
||||
}
|
||||
```
|
||||
|
||||
#### Construction
|
||||
```sx
|
||||
s :Shape = .circle(3.14); // inferred from context
|
||||
s = .none; // void variant (enum literal syntax)
|
||||
s = Shape.rect(42); // explicit prefix
|
||||
```
|
||||
|
||||
#### Payload Access
|
||||
```sx
|
||||
r := s.circle; // load payload as f32 (undefined behavior if wrong variant active)
|
||||
```
|
||||
|
||||
#### Pattern Matching
|
||||
```sx
|
||||
if s == {
|
||||
case .circle: print("circle\n");
|
||||
case .rect: print("rect\n");
|
||||
case .none: print("none\n");
|
||||
}
|
||||
```
|
||||
|
||||
#### Union Interpolation
|
||||
Union values in string interpolation print as `<TypeName tag=N>`:
|
||||
```sx
|
||||
print("{}", s); // <Shape tag=0>
|
||||
```
|
||||
|
||||
### Array Types
|
||||
Fixed-size arrays with element type and length.
|
||||
```sx
|
||||
@@ -623,7 +659,9 @@ if type == {
|
||||
case enum: result = enum_to_string(cast(type) val);
|
||||
}
|
||||
```
|
||||
Available categories: `int`, `float`, `bool`, `string`, `struct`, `enum`, `union`.
|
||||
Available categories: `int`, `float`, `bool`, `string`, `struct`, `enum`, `vector`, `array`, `slice`, `pointer`, `type`.
|
||||
|
||||
> Note: `case enum:` matches both payload-less enums and tagged enums (enums with payloads). C-style untagged unions are not registered with the Any type system and cannot be matched by category.
|
||||
|
||||
Inside a category arm, `cast(type) val` performs **runtime generic dispatch**: the compiler generates a switch over all types in the category, monomorphizing the callee for each concrete type.
|
||||
|
||||
|
||||
23
src/ast.zig
23
src/ast.zig
@@ -34,7 +34,6 @@ pub const Node = struct {
|
||||
struct_decl: StructDecl,
|
||||
struct_literal: StructLiteral,
|
||||
union_decl: UnionDecl,
|
||||
union_literal: UnionLiteral,
|
||||
lambda: Lambda,
|
||||
type_expr: TypeExpr,
|
||||
param: Param,
|
||||
@@ -127,6 +126,7 @@ 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 {
|
||||
@@ -225,7 +225,14 @@ pub const Assignment = struct {
|
||||
|
||||
pub const EnumDecl = struct {
|
||||
name: []const u8,
|
||||
variants: []const []const u8,
|
||||
variant_names: []const []const u8,
|
||||
variant_types: []const ?*Node = &.{}, // null entries = no payload; empty = payload-less enum
|
||||
};
|
||||
|
||||
pub const UnionDecl = struct {
|
||||
name: []const u8,
|
||||
field_names: []const []const u8,
|
||||
field_types: []const *Node,
|
||||
};
|
||||
|
||||
pub const StructTypeParam = struct {
|
||||
@@ -341,18 +348,6 @@ pub const SpreadExpr = struct {
|
||||
operand: *Node,
|
||||
};
|
||||
|
||||
pub const UnionDecl = struct {
|
||||
name: []const u8,
|
||||
variant_names: []const []const u8,
|
||||
variant_types: []const ?*Node, // null for void variants
|
||||
};
|
||||
|
||||
pub const UnionLiteral = struct {
|
||||
union_name: ?[]const u8, // null for anonymous `.variant(expr)`
|
||||
variant_name: []const u8,
|
||||
payload: ?*Node, // null for void variants
|
||||
};
|
||||
|
||||
pub const NamespaceDecl = struct {
|
||||
name: []const u8,
|
||||
decls: []const *Node,
|
||||
|
||||
425
src/codegen.zig
425
src/codegen.zig
@@ -108,7 +108,9 @@ pub const CodeGen = struct {
|
||||
type_aliases: std.StringHashMap([]const u8),
|
||||
// Struct type registry: maps struct name to field info + LLVM type
|
||||
struct_types: std.StringHashMap(StructInfo),
|
||||
// Union type registry: maps union name to variant info + LLVM type
|
||||
// Tagged enum registry: maps name to variant info + LLVM type (enums with payloads)
|
||||
tagged_enum_types: std.StringHashMap(TaggedEnumInfo),
|
||||
// Union registry: maps name to field info + LLVM type (untagged, C-style)
|
||||
union_types: std.StringHashMap(UnionInfo),
|
||||
// Built-in functions (printf, etc.)
|
||||
builtins: ?Builtins,
|
||||
@@ -193,7 +195,6 @@ pub const CodeGen = struct {
|
||||
const TypeCategory = enum {
|
||||
struct_cat,
|
||||
enum_cat,
|
||||
union_cat,
|
||||
vector_cat,
|
||||
array_cat,
|
||||
slice_cat,
|
||||
@@ -237,13 +238,27 @@ pub const CodeGen = struct {
|
||||
template_name: ?[]const u8 = null, // original template name (e.g. "List")
|
||||
};
|
||||
|
||||
const UnionInfo = struct {
|
||||
const TaggedEnumInfo = struct {
|
||||
variant_names: []const []const u8,
|
||||
variant_types: []const Type, // void_type for void variants
|
||||
llvm_type: c.LLVMTypeRef, // { i32, [max_payload_size x i8] }
|
||||
max_payload_size: u64,
|
||||
};
|
||||
|
||||
const PromotedField = struct {
|
||||
struct_name: []const u8, // the anonymous struct type name
|
||||
field_index: usize, // field index within that struct
|
||||
field_type: Type, // type of the promoted field
|
||||
};
|
||||
|
||||
const UnionInfo = struct {
|
||||
field_names: []const []const u8,
|
||||
field_types: []const Type,
|
||||
llvm_type: c.LLVMTypeRef, // [max_size x i8]
|
||||
total_size: u64,
|
||||
promoted_fields: std.StringHashMap(PromotedField),
|
||||
};
|
||||
|
||||
// Scope stack entry: records what a name mapped to before being shadowed
|
||||
const ScopeEntry = struct {
|
||||
name: []const u8,
|
||||
@@ -277,6 +292,7 @@ pub const CodeGen = struct {
|
||||
.enum_types = std.StringHashMap([]const []const u8).init(allocator),
|
||||
.type_aliases = std.StringHashMap([]const u8).init(allocator),
|
||||
.struct_types = std.StringHashMap(StructInfo).init(allocator),
|
||||
.tagged_enum_types = std.StringHashMap(TaggedEnumInfo).init(allocator),
|
||||
.union_types = std.StringHashMap(UnionInfo).init(allocator),
|
||||
.builtins = null,
|
||||
.current_function = null,
|
||||
@@ -308,6 +324,7 @@ pub const CodeGen = struct {
|
||||
self.enum_types.deinit();
|
||||
self.type_aliases.deinit();
|
||||
self.struct_types.deinit();
|
||||
self.tagged_enum_types.deinit();
|
||||
self.union_types.deinit();
|
||||
self.comptime_globals.deinit();
|
||||
self.generic_templates.deinit();
|
||||
@@ -363,7 +380,7 @@ pub const CodeGen = struct {
|
||||
.string_type, .slice_type => self.getStringStructType(), // slices use same {ptr, i32} layout
|
||||
.enum_type => c.LLVMInt64TypeInContext(self.context),
|
||||
.struct_type => |name| if (self.struct_types.get(name)) |info| info.llvm_type else unreachable,
|
||||
.union_type => |name| if (self.union_types.get(name)) |info| info.llvm_type else unreachable,
|
||||
.union_type => |name| if (self.tagged_enum_types.get(name)) |info| info.llvm_type else if (self.union_types.get(name)) |info| info.llvm_type else unreachable,
|
||||
.array_type => |info| {
|
||||
const elem_ty = Type.fromName(info.element_name) orelse unreachable;
|
||||
return c.LLVMArrayType2(self.typeToLLVM(elem_ty), info.length);
|
||||
@@ -413,7 +430,7 @@ pub const CodeGen = struct {
|
||||
const category: TypeCategory = switch (sx_type) {
|
||||
.struct_type => .struct_cat,
|
||||
.enum_type => .enum_cat,
|
||||
.union_type => .union_cat,
|
||||
.union_type => .enum_cat,
|
||||
.vector_type => .vector_cat,
|
||||
.array_type => .array_cat,
|
||||
.slice_type => .slice_cat,
|
||||
@@ -541,7 +558,7 @@ pub const CodeGen = struct {
|
||||
},
|
||||
.union_type => |uname| blk: {
|
||||
// Union — store to alloca, pass pointer as i64
|
||||
const info = self.union_types.get(uname) orelse
|
||||
const info = self.tagged_enum_types.get(uname) orelse
|
||||
return c.LLVMGetUndef(any_ty);
|
||||
const alloca = self.buildEntryBlockAlloca(info.llvm_type, "any_union_tmp");
|
||||
_ = c.LLVMBuildStore(self.builder, val, alloca);
|
||||
@@ -685,8 +702,14 @@ pub const CodeGen = struct {
|
||||
try self.foreign_libraries.append(self.allocator, ld.lib_name);
|
||||
},
|
||||
.enum_decl => |ed| {
|
||||
try self.enum_types.put(ed.name, ed.variants);
|
||||
_ = try self.getAnyTypeId(ed.name, .{ .enum_type = ed.name });
|
||||
if (ed.variant_types.len > 0) {
|
||||
// Tagged enum with payloads
|
||||
try self.registerTaggedEnum(ed);
|
||||
} else {
|
||||
// Payload-less enum
|
||||
try self.enum_types.put(ed.name, ed.variant_names);
|
||||
_ = try self.getAnyTypeId(ed.name, .{ .enum_type = ed.name });
|
||||
}
|
||||
},
|
||||
.struct_decl => |sd| try self.registerStructType(sd),
|
||||
.union_decl => |ud| try self.registerUnionType(ud),
|
||||
@@ -768,7 +791,7 @@ pub const CodeGen = struct {
|
||||
}
|
||||
}
|
||||
{
|
||||
var it = self.union_types.iterator();
|
||||
var it = self.tagged_enum_types.iterator();
|
||||
while (it.next()) |entry| {
|
||||
_ = try self.getAnyTypeId(entry.key_ptr.*, .{ .union_type = entry.key_ptr.* });
|
||||
}
|
||||
@@ -1015,13 +1038,15 @@ pub const CodeGen = struct {
|
||||
if (self.type_aliases.get(name)) |target| {
|
||||
if (Type.fromName(target)) |t| return t;
|
||||
if (self.struct_types.contains(target)) return .{ .struct_type = target };
|
||||
if (self.tagged_enum_types.contains(target)) return .{ .union_type = target };
|
||||
if (self.union_types.contains(target)) return .{ .union_type = target };
|
||||
}
|
||||
// Check enum types
|
||||
if (self.enum_types.contains(name)) return .{ .enum_type = name };
|
||||
// Check struct types
|
||||
if (self.struct_types.contains(name)) return .{ .struct_type = name };
|
||||
// Check union types
|
||||
// Check union types (tagged enums and C-style unions)
|
||||
if (self.tagged_enum_types.contains(name)) return .{ .union_type = name };
|
||||
if (self.union_types.contains(name)) return .{ .union_type = name };
|
||||
}
|
||||
// Safety net: inline declarations that should have been hoisted
|
||||
@@ -1029,12 +1054,9 @@ pub const CodeGen = struct {
|
||||
const sn = tn.data.struct_decl.name;
|
||||
if (self.struct_types.contains(sn)) return .{ .struct_type = sn };
|
||||
}
|
||||
if (tn.data == .union_decl) {
|
||||
const un = tn.data.union_decl.name;
|
||||
if (self.union_types.contains(un)) return .{ .union_type = un };
|
||||
}
|
||||
if (tn.data == .enum_decl) {
|
||||
const en = tn.data.enum_decl.name;
|
||||
if (self.tagged_enum_types.contains(en)) return .{ .union_type = en };
|
||||
if (self.enum_types.contains(en)) return .{ .enum_type = en };
|
||||
}
|
||||
return .void_type;
|
||||
@@ -1178,13 +1200,13 @@ pub const CodeGen = struct {
|
||||
|
||||
// Try union
|
||||
if (self.findUnionInBody(fd.body)) |union_decl| {
|
||||
if (self.union_types.contains(mangled_name)) {
|
||||
if (self.tagged_enum_types.contains(mangled_name)) {
|
||||
return .{ .union_type = mangled_name };
|
||||
}
|
||||
return self.registerInstantiatedUnion(mangled_name, union_decl);
|
||||
return self.registerInstantiatedTaggedEnum(mangled_name, union_decl);
|
||||
}
|
||||
|
||||
return self.emitErrorFmt("type function '{s}' does not return a struct or union", .{template_name});
|
||||
return self.emitErrorFmt("type function '{s}' does not return a struct or enum", .{template_name});
|
||||
}
|
||||
|
||||
fn registerInstantiatedStruct(self: *CodeGen, mangled_name: []const u8, alias_name: []const u8, struct_decl: ast.StructDecl) !Type {
|
||||
@@ -1205,10 +1227,10 @@ pub const CodeGen = struct {
|
||||
return .{ .struct_type = mangled_name };
|
||||
}
|
||||
|
||||
fn registerInstantiatedUnion(self: *CodeGen, mangled_name: []const u8, union_decl: ast.UnionDecl) !Type {
|
||||
fn registerInstantiatedTaggedEnum(self: *CodeGen, mangled_name: []const u8, union_decl: ast.EnumDecl) !Type {
|
||||
const build = try self.buildUnionFields(mangled_name, union_decl.variant_types);
|
||||
|
||||
try self.union_types.put(mangled_name, .{
|
||||
try self.tagged_enum_types.put(mangled_name, .{
|
||||
.variant_names = union_decl.variant_names,
|
||||
.variant_types = build.variant_sx_types,
|
||||
.llvm_type = build.llvm_type,
|
||||
@@ -1248,8 +1270,27 @@ pub const CodeGen = struct {
|
||||
return findDeclInBody(ast.StructDecl, .struct_decl, body);
|
||||
}
|
||||
|
||||
fn findUnionInBody(_: *CodeGen, body: *Node) ?ast.UnionDecl {
|
||||
return findDeclInBody(ast.UnionDecl, .union_decl, body);
|
||||
fn findUnionInBody(_: *CodeGen, body: *Node) ?ast.EnumDecl {
|
||||
// Tagged enums with payloads are now stored as .enum_decl with variant_types populated
|
||||
const isTaggedEnum = struct {
|
||||
fn check(node: *Node) ?ast.EnumDecl {
|
||||
if (node.data == .enum_decl and node.data.enum_decl.variant_types.len > 0) {
|
||||
return node.data.enum_decl;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
if (isTaggedEnum.check(body)) |ed| return ed;
|
||||
const stmts = if (body.data == .block) body.data.block.stmts else return null;
|
||||
for (stmts) |stmt| {
|
||||
if (stmt.data == .return_stmt) {
|
||||
if (stmt.data.return_stmt.value) |val| {
|
||||
if (isTaggedEnum.check(val)) |ed| return ed;
|
||||
}
|
||||
}
|
||||
if (isTaggedEnum.check(stmt)) |ed| return ed;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn buildFnType(self: *CodeGen, params: []const ast.Param, return_type: ?*Node, name: []const u8) !c.LLVMTypeRef {
|
||||
@@ -1555,8 +1596,15 @@ pub const CodeGen = struct {
|
||||
}
|
||||
},
|
||||
.enum_decl => |ed| {
|
||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name });
|
||||
try self.enum_types.put(qualified, ed.variants);
|
||||
if (ed.variant_types.len > 0) {
|
||||
// Tagged enum with payloads
|
||||
try self.registerTaggedEnum(ed);
|
||||
const qualified_u = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name });
|
||||
try self.type_aliases.put(qualified_u, ed.name);
|
||||
} else {
|
||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name });
|
||||
try self.enum_types.put(qualified, ed.variant_names);
|
||||
}
|
||||
},
|
||||
.struct_decl => |sd| {
|
||||
try self.registerStructType(sd);
|
||||
@@ -1663,7 +1711,7 @@ pub const CodeGen = struct {
|
||||
if (name_ptr != null) {
|
||||
const name = std.mem.span(name_ptr);
|
||||
if (self.struct_types.contains(name)) return .{ .struct_type = name };
|
||||
if (self.union_types.contains(name)) return .{ .union_type = name };
|
||||
if (self.tagged_enum_types.contains(name)) return .{ .union_type = name };
|
||||
}
|
||||
}
|
||||
// Check for array types
|
||||
@@ -1970,8 +2018,8 @@ pub const CodeGen = struct {
|
||||
try self.registerStructType(sd);
|
||||
return null;
|
||||
},
|
||||
.union_decl => |ud| {
|
||||
try self.registerUnionType(ud);
|
||||
.union_decl => {
|
||||
// C-style union — registration handled in type pass
|
||||
return null;
|
||||
},
|
||||
.assignment => |asgn| {
|
||||
@@ -2111,11 +2159,31 @@ pub const CodeGen = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Union-typed variable
|
||||
// Union-typed variable (tagged enum or C-style union)
|
||||
if (sx_ty.isUnion()) {
|
||||
const uname = self.type_aliases.get(sx_ty.union_type) orelse sx_ty.union_type;
|
||||
sx_ty = .{ .union_type = uname };
|
||||
const info = self.union_types.get(uname) orelse return self.emitErrorFmt("unknown union type '{s}'", .{uname});
|
||||
|
||||
// C-style (untagged) union
|
||||
if (self.union_types.get(uname)) |info| {
|
||||
const name_z = try self.allocator.dupeZ(u8, vd.name);
|
||||
const alloca = self.buildEntryBlockAlloca(info.llvm_type, name_z.ptr);
|
||||
|
||||
if (vd.value == null) {
|
||||
_ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(info.llvm_type), alloca);
|
||||
} else if (vd.value.?.data == .undef_literal) {
|
||||
_ = c.LLVMBuildStore(self.builder, c.LLVMGetUndef(info.llvm_type), alloca);
|
||||
} else {
|
||||
return self.emitErrorFmt("union '{s}' must be initialized with '---' or field assignment", .{uname});
|
||||
}
|
||||
|
||||
try self.saveShadowed(vd.name);
|
||||
try self.named_values.put(vd.name, .{ .ptr = alloca, .ty = sx_ty });
|
||||
return null;
|
||||
}
|
||||
|
||||
// Tagged enum
|
||||
const info = self.tagged_enum_types.get(uname) orelse return self.emitErrorFmt("unknown enum type '{s}'", .{uname});
|
||||
const name_z = try self.allocator.dupeZ(u8, vd.name);
|
||||
const alloca = self.buildEntryBlockAlloca(info.llvm_type, name_z.ptr);
|
||||
|
||||
@@ -2124,19 +2192,9 @@ pub const CodeGen = struct {
|
||||
_ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(info.llvm_type), alloca);
|
||||
} else if (vd.value.?.data == .undef_literal) {
|
||||
_ = c.LLVMBuildStore(self.builder, c.LLVMGetUndef(info.llvm_type), alloca);
|
||||
} else if (vd.value.?.data == .union_literal) {
|
||||
const lit_alloca = try self.genUnionLiteral(vd.value.?.data.union_literal, uname);
|
||||
try self.saveShadowed(vd.name);
|
||||
try self.named_values.put(vd.name, .{ .ptr = lit_alloca, .ty = sx_ty });
|
||||
return null;
|
||||
} else if (vd.value.?.data == .enum_literal) {
|
||||
// Void variant: .none assigned to union variable
|
||||
const ul = ast.UnionLiteral{
|
||||
.union_name = uname,
|
||||
.variant_name = vd.value.?.data.enum_literal.name,
|
||||
.payload = null,
|
||||
};
|
||||
const lit_alloca = try self.genUnionLiteral(ul, uname);
|
||||
const el = vd.value.?.data.enum_literal;
|
||||
const lit_alloca = try self.genTaggedEnumLiteral(el, uname);
|
||||
try self.saveShadowed(vd.name);
|
||||
try self.named_values.put(vd.name, .{ .ptr = lit_alloca, .ty = sx_ty });
|
||||
return null;
|
||||
@@ -2330,7 +2388,7 @@ pub const CodeGen = struct {
|
||||
sx_ty = self.inferType(cd.value);
|
||||
}
|
||||
|
||||
// Union-typed constant: delegate to genExprAsType which handles enum_literal + union_literal
|
||||
// Enum-typed constant: delegate to genExprAsType which handles enum_literal
|
||||
if (sx_ty.isUnion()) {
|
||||
const val = try self.genExprAsType(cd.value, sx_ty);
|
||||
try self.saveShadowed(cd.name);
|
||||
@@ -2452,14 +2510,16 @@ pub const CodeGen = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Union reassignment: s = .circle(3.14) or s = .none
|
||||
// Tagged enum reassignment: s = .circle(3.14) or s = .none
|
||||
if (entry.ty.isUnion() and asgn.op == .assign) {
|
||||
const new_alloca = try self.genExprAsType(asgn.value, entry.ty);
|
||||
// Copy from new alloca to existing alloca
|
||||
const info = self.union_types.get(entry.ty.union_type).?;
|
||||
const loaded = c.LLVMBuildLoad2(self.builder, info.llvm_type, new_alloca, "union_load");
|
||||
_ = c.LLVMBuildStore(self.builder, loaded, entry.ptr);
|
||||
return null;
|
||||
if (self.tagged_enum_types.get(entry.ty.union_type)) |info| {
|
||||
const new_alloca = try self.genExprAsType(asgn.value, entry.ty);
|
||||
// Copy from new alloca to existing alloca
|
||||
const loaded = c.LLVMBuildLoad2(self.builder, info.llvm_type, new_alloca, "union_load");
|
||||
_ = c.LLVMBuildStore(self.builder, loaded, entry.ptr);
|
||||
return null;
|
||||
}
|
||||
// C-style union: full assignment not supported, use field assignment
|
||||
}
|
||||
|
||||
const new_val = try self.genExpr(asgn.value);
|
||||
@@ -2507,6 +2567,42 @@ pub const CodeGen = struct {
|
||||
return self.emitError("field assignment through pointer requires a struct pointee");
|
||||
}
|
||||
|
||||
// C-style union field assignment
|
||||
if (entry.ty.isUnion()) {
|
||||
const uname = entry.ty.union_type;
|
||||
if (self.union_types.get(uname)) |info| {
|
||||
if (self.findUnionFieldIndex(info, fa.field)) |fidx| {
|
||||
const field_ty = info.field_types[fidx];
|
||||
const rhs = try self.genExprAsType(asgn.value, field_ty);
|
||||
if (asgn.op == .assign) {
|
||||
_ = c.LLVMBuildStore(self.builder, rhs, entry.ptr);
|
||||
} else {
|
||||
const field_llvm_ty = self.typeToLLVM(field_ty);
|
||||
const cur = c.LLVMBuildLoad2(self.builder, field_llvm_ty, entry.ptr, "ucur");
|
||||
_ = c.LLVMBuildStore(self.builder, self.genCompoundOp(asgn.op, cur, rhs, field_ty), entry.ptr);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// Check promoted fields from anonymous structs
|
||||
if (info.promoted_fields.get(fa.field)) |pf| {
|
||||
const sinfo = self.struct_types.get(pf.struct_name) orelse
|
||||
return self.emitErrorFmt("unknown promoted struct '{s}'", .{pf.struct_name});
|
||||
const gep = c.LLVMBuildStructGEP2(self.builder, sinfo.llvm_type, entry.ptr, @intCast(pf.field_index), "promoted_ptr");
|
||||
const rhs = try self.genExprAsType(asgn.value, pf.field_type);
|
||||
if (asgn.op == .assign) {
|
||||
_ = c.LLVMBuildStore(self.builder, rhs, gep);
|
||||
} else {
|
||||
const field_llvm_ty = self.typeToLLVM(pf.field_type);
|
||||
const cur = c.LLVMBuildLoad2(self.builder, field_llvm_ty, gep, "ucur");
|
||||
_ = c.LLVMBuildStore(self.builder, self.genCompoundOp(asgn.op, cur, rhs, pf.field_type), gep);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return self.emitErrorFmt("no field '{s}' in union '{s}'", .{ fa.field, uname });
|
||||
}
|
||||
return self.emitErrorFmt("field assignment not supported on tagged enum '{s}'", .{uname});
|
||||
}
|
||||
|
||||
if (!entry.ty.isStruct()) return self.emitErrorFmt("field access on non-struct variable '{s}'", .{obj_name});
|
||||
|
||||
const sname = entry.ty.struct_type;
|
||||
@@ -2749,9 +2845,6 @@ pub const CodeGen = struct {
|
||||
const ctx_name: ?[]const u8 = if (self.current_return_type.isStruct()) self.current_return_type.struct_type else null;
|
||||
return self.genStructLiteral(sl, ctx_name);
|
||||
},
|
||||
.union_literal => |ul| {
|
||||
return self.genUnionLiteral(ul, null);
|
||||
},
|
||||
.array_literal => |al| {
|
||||
// Typed array/vector/slice literal: Type.[elems]
|
||||
if (al.type_expr) |te| {
|
||||
@@ -2905,6 +2998,19 @@ pub const CodeGen = struct {
|
||||
const idx = self.findFieldIndex(info, fa.field) orelse return self.emitErrorFmt("no field '{s}' in struct '{s}'", .{ fa.field, sname });
|
||||
return c.LLVMBuildStructGEP2(self.builder, info.llvm_type, entry.ptr, @intCast(idx), "addr_field");
|
||||
}
|
||||
// &u.field where u is a C-style union — all fields at offset 0
|
||||
if (entry.ty.isUnion()) {
|
||||
if (self.union_types.get(entry.ty.union_type)) |info| {
|
||||
if (self.findUnionFieldIndex(info, fa.field) != null) {
|
||||
return entry.ptr;
|
||||
}
|
||||
if (info.promoted_fields.get(fa.field)) |pf| {
|
||||
const sinfo = self.struct_types.get(pf.struct_name) orelse
|
||||
return self.emitErrorFmt("unknown promoted struct '{s}'", .{pf.struct_name});
|
||||
return c.LLVMBuildStructGEP2(self.builder, sinfo.llvm_type, entry.ptr, @intCast(pf.field_index), "addr_promoted");
|
||||
}
|
||||
}
|
||||
}
|
||||
// &p.field where p is *Struct — auto-deref through pointer
|
||||
if (entry.ty.isPointer()) {
|
||||
const pointee_name = entry.ty.pointer_type.pointee_name;
|
||||
@@ -2995,14 +3101,21 @@ pub const CodeGen = struct {
|
||||
type_node.data = .{ .type_expr = .{ .name = synthetic_name } };
|
||||
},
|
||||
.union_decl => |inline_ud| {
|
||||
var hoisted = inline_ud;
|
||||
hoisted.name = synthetic_name;
|
||||
try self.registerUnionType(hoisted);
|
||||
var hoisted_ud = inline_ud;
|
||||
hoisted_ud.name = synthetic_name;
|
||||
try self.registerUnionType(hoisted_ud);
|
||||
type_node.data = .{ .type_expr = .{ .name = synthetic_name } };
|
||||
},
|
||||
.enum_decl => |inline_ed| {
|
||||
try self.enum_types.put(synthetic_name, inline_ed.variants);
|
||||
_ = try self.getAnyTypeId(synthetic_name, .{ .enum_type = synthetic_name });
|
||||
if (inline_ed.variant_types.len > 0) {
|
||||
// Tagged enum with payloads
|
||||
var hoisted = inline_ed;
|
||||
hoisted.name = synthetic_name;
|
||||
try self.registerTaggedEnum(hoisted);
|
||||
} else {
|
||||
try self.enum_types.put(synthetic_name, inline_ed.variant_names);
|
||||
_ = try self.getAnyTypeId(synthetic_name, .{ .enum_type = synthetic_name });
|
||||
}
|
||||
type_node.data = .{ .type_expr = .{ .name = synthetic_name } };
|
||||
},
|
||||
else => {},
|
||||
@@ -3047,7 +3160,7 @@ pub const CodeGen = struct {
|
||||
_ = try self.getAnyTypeId(sd.name, .{ .struct_type = sd.name });
|
||||
}
|
||||
|
||||
fn registerUnionType(self: *CodeGen, ud: ast.UnionDecl) !void {
|
||||
fn registerTaggedEnum(self: *CodeGen, ud: ast.EnumDecl) !void {
|
||||
// Pre-pass: hoist inline type declarations from variant types
|
||||
for (ud.variant_types, 0..) |vt_opt, i| {
|
||||
if (vt_opt) |vt| {
|
||||
@@ -3057,7 +3170,7 @@ pub const CodeGen = struct {
|
||||
|
||||
const build = try self.buildUnionFields(ud.name, ud.variant_types);
|
||||
|
||||
try self.union_types.put(ud.name, .{
|
||||
try self.tagged_enum_types.put(ud.name, .{
|
||||
.variant_names = ud.variant_names,
|
||||
.variant_types = build.variant_sx_types,
|
||||
.llvm_type = build.llvm_type,
|
||||
@@ -3066,22 +3179,78 @@ pub const CodeGen = struct {
|
||||
_ = try self.getAnyTypeId(ud.name, .{ .union_type = ud.name });
|
||||
}
|
||||
|
||||
fn genUnionLiteral(self: *CodeGen, ul: ast.UnionLiteral, expected_union_name: ?[]const u8) !c.LLVMValueRef {
|
||||
const uname = ul.union_name orelse expected_union_name orelse
|
||||
fn registerUnionType(self: *CodeGen, ud: ast.UnionDecl) !void {
|
||||
// Hoist inline type declarations from field types
|
||||
for (ud.field_types, 0..) |ft, i| {
|
||||
try self.hoistInlineTypeDecl(ud.name, ud.field_names[i], ft);
|
||||
}
|
||||
|
||||
// Compute max field size and resolve field types
|
||||
const data_layout = c.LLVMGetModuleDataLayout(self.module);
|
||||
var field_sx_types = std.ArrayList(Type).empty;
|
||||
var max_size: u64 = 0;
|
||||
for (ud.field_types) |ft| {
|
||||
const resolved = self.resolveType(ft);
|
||||
try field_sx_types.append(self.allocator, resolved);
|
||||
const llvm_ty = self.typeToLLVM(resolved);
|
||||
const size = c.LLVMABISizeOfType(data_layout, llvm_ty);
|
||||
if (size > max_size) max_size = size;
|
||||
}
|
||||
|
||||
// LLVM type: byte array sized to the largest field
|
||||
const byte_ty = c.LLVMInt8TypeInContext(self.context);
|
||||
const llvm_type = c.LLVMArrayType(byte_ty, @intCast(max_size));
|
||||
|
||||
const resolved_field_types = try field_sx_types.toOwnedSlice(self.allocator);
|
||||
|
||||
// Build promoted fields map from anonymous struct members
|
||||
var promoted = std.StringHashMap(PromotedField).init(self.allocator);
|
||||
for (ud.field_names, 0..) |_, i| {
|
||||
const fty = resolved_field_types[i];
|
||||
if (fty.isStruct()) {
|
||||
// Check if this is an anonymous struct (name contains __anon_)
|
||||
const sname = fty.struct_type;
|
||||
if (std.mem.indexOf(u8, sname, ".__anon_") != null) {
|
||||
if (self.struct_types.get(sname)) |sinfo| {
|
||||
for (sinfo.field_names, 0..) |sf_name, sf_idx| {
|
||||
try promoted.put(sf_name, .{
|
||||
.struct_name = sname,
|
||||
.field_index = sf_idx,
|
||||
.field_type = sinfo.field_types[sf_idx],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try self.union_types.put(ud.name, .{
|
||||
.field_names = ud.field_names,
|
||||
.field_types = resolved_field_types,
|
||||
.llvm_type = llvm_type,
|
||||
.total_size = max_size,
|
||||
.promoted_fields = promoted,
|
||||
});
|
||||
// Note: C-style unions are not registered with the Any type system.
|
||||
// They can't be meaningfully printed as a whole — access individual fields instead.
|
||||
}
|
||||
|
||||
fn genTaggedEnumLiteral(self: *CodeGen, el: ast.EnumLiteral, 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 union type for literal");
|
||||
return self.emitError("cannot infer enum type for literal");
|
||||
const resolved_name = self.type_aliases.get(uname) orelse uname;
|
||||
const info = self.union_types.get(resolved_name) orelse return self.emitErrorFmt("unknown union type '{s}'", .{resolved_name});
|
||||
const info = self.tagged_enum_types.get(resolved_name) orelse return self.emitErrorFmt("unknown enum type '{s}'", .{resolved_name});
|
||||
|
||||
// Find variant index
|
||||
var variant_idx: ?u32 = null;
|
||||
for (info.variant_names, 0..) |vn, i| {
|
||||
if (std.mem.eql(u8, vn, ul.variant_name)) {
|
||||
if (std.mem.eql(u8, vn, el.name)) {
|
||||
variant_idx = @intCast(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const idx = variant_idx orelse return self.emitErrorFmt("no variant '{s}' in union '{s}'", .{ ul.variant_name, resolved_name });
|
||||
const idx = variant_idx orelse return self.emitErrorFmt("no variant '{s}' in enum '{s}'", .{ el.name, resolved_name });
|
||||
|
||||
// Alloca union
|
||||
const alloca = self.buildEntryBlockAlloca(info.llvm_type, "union_tmp");
|
||||
@@ -3092,20 +3261,13 @@ pub const CodeGen = struct {
|
||||
_ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i64_ty, idx, 0), tag_gep);
|
||||
|
||||
// Store payload (field 1) if not void
|
||||
if (ul.payload) |payload_node| {
|
||||
if (el.payload) |payload_node| {
|
||||
const variant_ty = info.variant_types[idx];
|
||||
if (variant_ty != .void_type) {
|
||||
const payload_val = try self.genExprAsType(payload_node, variant_ty);
|
||||
const payload_gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, alloca, 1, "payload");
|
||||
const payload_llvm_ty = self.typeToLLVM(variant_ty);
|
||||
// Bitcast payload area to the variant's type pointer and store
|
||||
if (variant_ty.isStruct()) {
|
||||
// Struct payload: load from alloca, store to payload area
|
||||
const struct_val = c.LLVMBuildLoad2(self.builder, payload_llvm_ty, payload_val, "struct_load");
|
||||
_ = c.LLVMBuildStore(self.builder, struct_val, payload_gep);
|
||||
} else {
|
||||
_ = c.LLVMBuildStore(self.builder, payload_val, payload_gep);
|
||||
}
|
||||
// genExprAsType returns a loaded value for all types (including structs)
|
||||
_ = c.LLVMBuildStore(self.builder, payload_val, payload_gep);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3284,19 +3446,10 @@ pub const CodeGen = struct {
|
||||
return c.LLVMBuildGlobalStringPtr(self.builder, str_z.ptr, "str");
|
||||
}
|
||||
|
||||
// Enum literal assigned to union type: construct tag-only (void variant) union
|
||||
// Enum/union literal assigned to union type: construct tagged enum
|
||||
if (node.data == .enum_literal and target_ty.isUnion()) {
|
||||
const ul = ast.UnionLiteral{
|
||||
.union_name = null,
|
||||
.variant_name = node.data.enum_literal.name,
|
||||
.payload = null,
|
||||
};
|
||||
return self.genUnionLiteral(ul, target_ty.union_type);
|
||||
}
|
||||
|
||||
// Union literal with target union type: pass context
|
||||
if (node.data == .union_literal and target_ty.isUnion()) {
|
||||
return self.genUnionLiteral(node.data.union_literal, target_ty.union_type);
|
||||
const el = node.data.enum_literal;
|
||||
return self.genTaggedEnumLiteral(el, target_ty.union_type);
|
||||
}
|
||||
|
||||
// Struct literal targeting union type: .Variant.{fields} pattern
|
||||
@@ -3308,8 +3461,8 @@ pub const CodeGen = struct {
|
||||
if (te.data == .enum_literal) {
|
||||
const variant_name = te.data.enum_literal.name;
|
||||
const uname = self.type_aliases.get(target_ty.union_type) orelse target_ty.union_type;
|
||||
const info = self.union_types.get(uname) orelse
|
||||
return self.emitErrorFmt("unknown union type '{s}'", .{uname});
|
||||
const info = self.tagged_enum_types.get(uname) orelse
|
||||
return self.emitErrorFmt("unknown enum type '{s}'", .{uname});
|
||||
|
||||
// Find variant index
|
||||
var variant_idx: ?u32 = null;
|
||||
@@ -3320,7 +3473,7 @@ pub const CodeGen = struct {
|
||||
}
|
||||
}
|
||||
const idx = variant_idx orelse
|
||||
return self.emitErrorFmt("no variant '{s}' in union '{s}'", .{ variant_name, uname });
|
||||
return self.emitErrorFmt("no variant '{s}' in enum '{s}'", .{ variant_name, uname });
|
||||
|
||||
const variant_ty = info.variant_types[idx];
|
||||
|
||||
@@ -3554,7 +3707,7 @@ pub const CodeGen = struct {
|
||||
}
|
||||
if (target_ty.isUnion()) {
|
||||
const uname = target_ty.union_type;
|
||||
if (self.union_types.get(uname)) |info| {
|
||||
if (self.tagged_enum_types.get(uname)) |info| {
|
||||
const ptr = c.LLVMBuildIntToPtr(self.builder, i64_val, c.LLVMPointerTypeInContext(self.context, 0), "any_union_ptr");
|
||||
return c.LLVMBuildLoad2(self.builder, info.llvm_type, ptr, "any_to_union");
|
||||
}
|
||||
@@ -3604,7 +3757,7 @@ pub const CodeGen = struct {
|
||||
// Union → int: extract the tag field (index 0)
|
||||
if (src_ty.isUnion() and target_ty.isInt()) {
|
||||
const uname = src_ty.union_type;
|
||||
if (self.union_types.get(uname)) |info| {
|
||||
if (self.tagged_enum_types.get(uname)) |info| {
|
||||
const tmp = self.buildEntryBlockAlloca(info.llvm_type, "union_cast");
|
||||
_ = c.LLVMBuildStore(self.builder, val, tmp);
|
||||
const tag_ptr = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, tmp, 0, "tag_ptr");
|
||||
@@ -3667,6 +3820,13 @@ pub const CodeGen = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
fn findUnionFieldIndex(_: *CodeGen, info: UnionInfo, name: []const u8) ?usize {
|
||||
for (info.field_names, 0..) |fn_name, i| {
|
||||
if (std.mem.eql(u8, fn_name, name)) return i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn componentToIndex(ch: u8) ?u32 {
|
||||
return switch (ch) {
|
||||
'x', 'r', 'u' => 0,
|
||||
@@ -3819,14 +3979,14 @@ pub const CodeGen = struct {
|
||||
return c.LLVMConstInt(i64_ty, ty.vector_type.length, 0);
|
||||
}
|
||||
if (ty.isUnion()) {
|
||||
const info = self.union_types.get(ty.union_type) orelse
|
||||
return self.emitErrorFmt("unknown union type '{s}'", .{ty.union_type});
|
||||
const info = self.tagged_enum_types.get(ty.union_type) orelse
|
||||
return self.emitErrorFmt("unknown enum type '{s}'", .{ty.union_type});
|
||||
return c.LLVMConstInt(i64_ty, info.variant_names.len, 0);
|
||||
}
|
||||
if (ty.isArray()) {
|
||||
return c.LLVMConstInt(i64_ty, ty.array_type.length, 0);
|
||||
}
|
||||
return self.emitError("field_count requires a struct, enum, vector, union, or array type");
|
||||
return self.emitError("field_count requires a struct, enum, vector, or array type");
|
||||
}
|
||||
|
||||
fn genFieldName(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef {
|
||||
@@ -3843,10 +4003,10 @@ pub const CodeGen = struct {
|
||||
return self.emitErrorFmt("unknown enum type '{s}'", .{ty.enum_type});
|
||||
break :blk .{ variants, ty.enum_type };
|
||||
} else if (ty.isUnion()) blk: {
|
||||
const info = self.union_types.get(ty.union_type) orelse
|
||||
return self.emitErrorFmt("unknown union type '{s}'", .{ty.union_type});
|
||||
const info = self.tagged_enum_types.get(ty.union_type) orelse
|
||||
return self.emitErrorFmt("unknown enum type '{s}'", .{ty.union_type});
|
||||
break :blk .{ info.variant_names, ty.union_type };
|
||||
} else return self.emitError("field_name requires a struct, enum, or union type");
|
||||
} else return self.emitError("field_name requires a struct or enum type");
|
||||
|
||||
// Build a global array of string slices
|
||||
const n = names.len;
|
||||
@@ -3891,10 +4051,15 @@ pub const CodeGen = struct {
|
||||
return self.buildAnyValue(elem, elem_ty);
|
||||
}
|
||||
|
||||
// Union: switch over tag, extract payload with correct type
|
||||
// Payload-less enum: return void Any (no payload to extract)
|
||||
if (val_ty.isEnum() and !val_ty.isUnion()) {
|
||||
return self.buildAnyValue(c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), 0, 0), .void_type);
|
||||
}
|
||||
|
||||
// Tagged enum (with payloads): switch over tag, extract payload with correct type
|
||||
if (val_ty.isUnion()) {
|
||||
const uinfo = self.union_types.get(val_ty.union_type) orelse
|
||||
return self.emitErrorFmt("unknown union type '{s}'", .{val_ty.union_type});
|
||||
const uinfo = self.tagged_enum_types.get(val_ty.union_type) orelse
|
||||
return self.emitErrorFmt("unknown enum type '{s}'", .{val_ty.union_type});
|
||||
|
||||
const union_alloca = self.buildEntryBlockAlloca(uinfo.llvm_type, "fv_union");
|
||||
_ = c.LLVMBuildStore(self.builder, val, union_alloca);
|
||||
@@ -3986,7 +4151,7 @@ pub const CodeGen = struct {
|
||||
// Struct: switch over field indices
|
||||
const struct_val = val;
|
||||
const struct_ty = val_ty;
|
||||
if (!struct_ty.isStruct()) return self.emitError("field_value requires a struct, vector, union, or array value");
|
||||
if (!struct_ty.isStruct()) return self.emitError("field_value requires a struct, vector, enum, or array value");
|
||||
|
||||
const info = self.struct_types.get(struct_ty.struct_type) orelse
|
||||
return self.emitErrorFmt("unknown struct type '{s}'", .{struct_ty.struct_type});
|
||||
@@ -4143,7 +4308,24 @@ pub const CodeGen = struct {
|
||||
}
|
||||
if (entry.ty.isUnion()) {
|
||||
const uname = entry.ty.union_type;
|
||||
const info = self.union_types.get(uname) orelse return self.emitErrorFmt("unknown union type '{s}'", .{uname});
|
||||
// C-style (untagged) union: bitcast pointer and load
|
||||
if (self.union_types.get(uname)) |info| {
|
||||
if (self.findUnionFieldIndex(info, fa.field)) |fidx| {
|
||||
const field_ty = info.field_types[fidx];
|
||||
return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(field_ty), entry.ptr, "union_field");
|
||||
}
|
||||
// Check promoted fields from anonymous structs
|
||||
if (info.promoted_fields.get(fa.field)) |pf| {
|
||||
const sinfo = self.struct_types.get(pf.struct_name) orelse
|
||||
return self.emitErrorFmt("unknown promoted struct '{s}'", .{pf.struct_name});
|
||||
// GEP through union pointer as struct type, then access field
|
||||
const gep = c.LLVMBuildStructGEP2(self.builder, sinfo.llvm_type, entry.ptr, @intCast(pf.field_index), "promoted_field");
|
||||
return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(pf.field_type), gep, "promoted_val");
|
||||
}
|
||||
return self.emitErrorFmt("no field '{s}' in union '{s}'", .{ fa.field, uname });
|
||||
}
|
||||
// Tagged enum: GEP to payload area
|
||||
const info = self.tagged_enum_types.get(uname) orelse return self.emitErrorFmt("unknown enum type '{s}'", .{uname});
|
||||
// Find variant by name to determine payload type
|
||||
var vidx: ?usize = null;
|
||||
for (info.variant_names, 0..) |vn, i| {
|
||||
@@ -4152,7 +4334,7 @@ pub const CodeGen = struct {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const idx = vidx orelse return self.emitErrorFmt("no variant '{s}' in union '{s}'", .{ fa.field, uname });
|
||||
const idx = vidx orelse return self.emitErrorFmt("no variant '{s}' in enum '{s}'", .{ fa.field, uname });
|
||||
const variant_ty = info.variant_types[idx];
|
||||
if (variant_ty == .void_type) return self.emitErrorFmt("cannot access payload of void variant '{s}'", .{fa.field});
|
||||
// GEP to field 1 (payload area), load as variant type
|
||||
@@ -4515,7 +4697,7 @@ pub const CodeGen = struct {
|
||||
const resolved_type: ?Type = blk: {
|
||||
if (fa.object.data == .identifier) {
|
||||
const name = self.type_aliases.get(fa.object.data.identifier.name) orelse fa.object.data.identifier.name;
|
||||
if (self.union_types.contains(name)) break :blk .{ .union_type = name };
|
||||
if (self.tagged_enum_types.contains(name)) break :blk .{ .union_type = name };
|
||||
if (self.struct_types.contains(name)) break :blk .{ .struct_type = name };
|
||||
} else {
|
||||
const ty = self.resolveType(fa.object);
|
||||
@@ -4527,9 +4709,8 @@ 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.genUnionLiteral(.{
|
||||
.union_name = type_name,
|
||||
.variant_name = fa.field,
|
||||
return self.genTaggedEnumLiteral(.{
|
||||
.name = fa.field,
|
||||
.payload = payload_node,
|
||||
}, type_name);
|
||||
}
|
||||
@@ -4882,7 +5063,7 @@ pub const CodeGen = struct {
|
||||
Type.fromName(name) == null and
|
||||
!self.struct_types.contains(name) and
|
||||
!self.enum_types.contains(name) and
|
||||
!self.union_types.contains(name) and
|
||||
!self.tagged_enum_types.contains(name) and
|
||||
!self.type_aliases.contains(name))
|
||||
{
|
||||
return self.genGenericCallWithRuntimeDispatch(template, call_node, match_tags);
|
||||
@@ -5323,7 +5504,7 @@ pub const CodeGen = struct {
|
||||
},
|
||||
.enum_type => any_i64,
|
||||
.union_type => |uname| blk: {
|
||||
const info = self.union_types.get(uname) orelse return self.emitErrorFmt("unknown union '{s}'", .{uname});
|
||||
const info = self.tagged_enum_types.get(uname) orelse return self.emitErrorFmt("unknown enum '{s}'", .{uname});
|
||||
const ptr = c.LLVMBuildIntToPtr(self.builder, any_i64, c.LLVMPointerTypeInContext(self.context, 0), "any_to_union_ptr");
|
||||
break :blk c.LLVMBuildLoad2(self.builder, info.llvm_type, ptr, "any_to_union");
|
||||
},
|
||||
@@ -5764,13 +5945,13 @@ pub const CodeGen = struct {
|
||||
const subject_val: c.LLVMValueRef = if (union_name != null) blk: {
|
||||
// Union: load tag from field 0 of the alloca
|
||||
const entry = self.named_values.get(match.subject.data.identifier.name).?;
|
||||
const info = self.union_types.get(union_name.?).?;
|
||||
const info = self.tagged_enum_types.get(union_name.?).?;
|
||||
const tag_gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, entry.ptr, 0, "tag");
|
||||
break :blk c.LLVMBuildLoad2(self.builder, c.LLVMInt64TypeInContext(self.context), tag_gep, "tag_val");
|
||||
} else try self.genExpr(match.subject);
|
||||
|
||||
const variants: ?[]const []const u8 = if (union_name) |un|
|
||||
(if (self.union_types.get(un)) |info| info.variant_names else null)
|
||||
(if (self.tagged_enum_types.get(un)) |info| info.variant_names else null)
|
||||
else if (enum_name) |en|
|
||||
self.enum_types.get(en)
|
||||
else
|
||||
@@ -5925,7 +6106,7 @@ pub const CodeGen = struct {
|
||||
else if (std.mem.eql(u8, name, "enum"))
|
||||
.enum_cat
|
||||
else if (std.mem.eql(u8, name, "union"))
|
||||
.union_cat
|
||||
.enum_cat
|
||||
else if (std.mem.eql(u8, name, "vector"))
|
||||
.vector_cat
|
||||
else if (std.mem.eql(u8, name, "array"))
|
||||
@@ -5979,7 +6160,7 @@ pub const CodeGen = struct {
|
||||
.{ .struct_type = name }
|
||||
else if (self.enum_types.contains(name))
|
||||
.{ .enum_type = name }
|
||||
else if (self.union_types.contains(name))
|
||||
else if (self.tagged_enum_types.contains(name))
|
||||
.{ .union_type = name }
|
||||
else
|
||||
.{ .struct_type = name }; // fallback
|
||||
@@ -6183,7 +6364,8 @@ pub const CodeGen = struct {
|
||||
if (Type.fromName(name)) |t| return t;
|
||||
// Structs
|
||||
if (self.struct_types.contains(name)) return .{ .struct_type = name };
|
||||
// Unions
|
||||
// Unions (tagged enums and C-style)
|
||||
if (self.tagged_enum_types.contains(name)) return .{ .union_type = name };
|
||||
if (self.union_types.contains(name)) return .{ .union_type = name };
|
||||
// Enums
|
||||
if (self.enum_types.contains(name)) return .{ .enum_type = name };
|
||||
@@ -6236,11 +6418,6 @@ pub const CodeGen = struct {
|
||||
}
|
||||
return .void_type;
|
||||
},
|
||||
.union_literal => |ul| {
|
||||
if (ul.union_name) |uname| return .{ .union_type = uname };
|
||||
if (self.current_return_type.isUnion()) return self.current_return_type;
|
||||
return .void_type;
|
||||
},
|
||||
.enum_literal => {
|
||||
if (self.current_return_type.isEnum()) return self.current_return_type;
|
||||
if (self.current_return_type.isUnion()) return self.current_return_type;
|
||||
@@ -6259,7 +6436,7 @@ pub const CodeGen = struct {
|
||||
const obj_ty = blk: {
|
||||
if (fa.object.data == .identifier) {
|
||||
const name = self.type_aliases.get(fa.object.data.identifier.name) orelse fa.object.data.identifier.name;
|
||||
if (self.union_types.contains(name)) break :blk Type{ .union_type = name };
|
||||
if (self.tagged_enum_types.contains(name)) break :blk Type{ .union_type = name };
|
||||
}
|
||||
const ty = self.resolveType(fa.object);
|
||||
if (ty.isUnion()) break :blk ty;
|
||||
@@ -6422,6 +6599,14 @@ pub const CodeGen = struct {
|
||||
}
|
||||
if (obj_ty.isUnion()) {
|
||||
if (self.union_types.get(obj_ty.union_type)) |info| {
|
||||
if (self.findUnionFieldIndex(info, fa.field)) |idx| {
|
||||
return info.field_types[idx];
|
||||
}
|
||||
if (info.promoted_fields.get(fa.field)) |pf| {
|
||||
return pf.field_type;
|
||||
}
|
||||
}
|
||||
if (self.tagged_enum_types.get(obj_ty.union_type)) |info| {
|
||||
for (info.variant_names, 0..) |vn, i| {
|
||||
if (std.mem.eql(u8, vn, fa.field)) {
|
||||
return info.variant_types[i];
|
||||
|
||||
@@ -1616,7 +1616,8 @@ pub const VM = struct {
|
||||
},
|
||||
.enum_decl => |ed| {
|
||||
if (std.mem.eql(u8, ed.name, name)) {
|
||||
const val = Value{ .type_val = .{ .enum_type = name } };
|
||||
const ty: Type = if (ed.variant_types.len > 0) .{ .union_type = name } else .{ .enum_type = name };
|
||||
const val = Value{ .type_val = ty };
|
||||
try self.globals.put(name, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
@@ -553,7 +553,7 @@ pub const Server = struct {
|
||||
.union_decl => |ud| {
|
||||
try items.append(allocator, .{
|
||||
.label = ud.name,
|
||||
.kind = @intFromEnum(lsp.CompletionItemKind.Enum),
|
||||
.kind = @intFromEnum(lsp.CompletionItemKind.Struct),
|
||||
});
|
||||
},
|
||||
.var_decl => |vd| {
|
||||
@@ -602,7 +602,7 @@ pub const Server = struct {
|
||||
if (sx.sema.findNodeAtOffset(lookup_root, sym.def_span.start)) |node| {
|
||||
if (node.data == .enum_decl) {
|
||||
const ed = node.data.enum_decl;
|
||||
for (ed.variants) |variant| {
|
||||
for (ed.variant_names) |variant| {
|
||||
try items.append(self.allocator, .{
|
||||
.label = variant,
|
||||
.kind = @intFromEnum(lsp.CompletionItemKind.EnumMember),
|
||||
@@ -1212,7 +1212,7 @@ pub const Server = struct {
|
||||
if (sx.sema.findNodeAtOffset(lookup_root, sym.def_span.start)) |node| {
|
||||
if (node.data == .enum_decl) {
|
||||
const ed = node.data.enum_decl;
|
||||
for (ed.variants) |v| {
|
||||
for (ed.variant_names) |v| {
|
||||
if (!std.mem.eql(u8, v, variant_name)) continue;
|
||||
|
||||
var buf = std.ArrayList(u8).empty;
|
||||
@@ -1633,7 +1633,7 @@ pub const Server = struct {
|
||||
.enum_decl => |ed| {
|
||||
try buf.appendSlice(allocator, ed.name);
|
||||
try buf.appendSlice(allocator, " :: enum { ");
|
||||
for (ed.variants, 0..) |v, i| {
|
||||
for (ed.variant_names, 0..) |v, i| {
|
||||
if (i > 0) try buf.appendSlice(allocator, ", ");
|
||||
try buf.append(allocator, '.');
|
||||
try buf.appendSlice(allocator, v);
|
||||
@@ -1658,9 +1658,24 @@ pub const Server = struct {
|
||||
.union_decl => |ud| {
|
||||
try buf.appendSlice(allocator, ud.name);
|
||||
try buf.appendSlice(allocator, " :: union { ");
|
||||
for (ud.variant_names, 0..) |vn, i| {
|
||||
for (ud.field_names, 0..) |fn_name, i| {
|
||||
if (i > 0) try buf.appendSlice(allocator, ", ");
|
||||
try buf.appendSlice(allocator, vn);
|
||||
// Anonymous struct fields: show as "struct { ... }"
|
||||
if (std.mem.startsWith(u8, fn_name, "__anon_")) {
|
||||
if (i < ud.field_types.len and ud.field_types[i].data == .type_expr) {
|
||||
try buf.appendSlice(allocator, ud.field_types[i].data.type_expr.name);
|
||||
} else {
|
||||
try buf.appendSlice(allocator, "struct { ... }");
|
||||
}
|
||||
} else {
|
||||
try buf.appendSlice(allocator, fn_name);
|
||||
if (i < ud.field_types.len) {
|
||||
try buf.appendSlice(allocator, ": ");
|
||||
if (ud.field_types[i].data == .type_expr) {
|
||||
try buf.appendSlice(allocator, ud.field_types[i].data.type_expr.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try buf.appendSlice(allocator, " }");
|
||||
},
|
||||
|
||||
@@ -160,7 +160,7 @@ pub const Parser = struct {
|
||||
return self.parseStructDecl(name, start_pos);
|
||||
}
|
||||
|
||||
// Union declaration
|
||||
// C-style union declaration
|
||||
if (self.current.tag == .kw_union) {
|
||||
return self.parseUnionDecl(name, start_pos);
|
||||
}
|
||||
@@ -368,7 +368,7 @@ pub const Parser = struct {
|
||||
if (self.current.tag == .kw_struct) {
|
||||
return try self.parseStructDecl("__anon", start);
|
||||
}
|
||||
// Inline union type in type position: union { ... }
|
||||
// Inline C-style union in type position: union { ... }
|
||||
if (self.current.tag == .kw_union) {
|
||||
return try self.parseUnionDecl("__anon", start);
|
||||
}
|
||||
@@ -382,30 +382,13 @@ pub const Parser = struct {
|
||||
fn parseEnumDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
|
||||
self.advance(); // skip 'enum'
|
||||
try self.expect(.l_brace);
|
||||
var variants = std.ArrayList([]const u8).empty;
|
||||
var variant_names = std.ArrayList([]const u8).empty;
|
||||
var variant_types = std.ArrayList(?*Node).empty;
|
||||
var has_any_type = false;
|
||||
while (self.current.tag != .r_brace and self.current.tag != .eof) {
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected variant name");
|
||||
}
|
||||
try variants.append(self.allocator, self.tokenSlice(self.current));
|
||||
self.advance();
|
||||
if (self.current.tag == .semicolon) {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
try self.expect(.r_brace);
|
||||
return try self.createNode(start_pos, .{ .enum_decl = .{ .name = name, .variants = try variants.toOwnedSlice(self.allocator) } });
|
||||
}
|
||||
|
||||
fn parseUnionDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
|
||||
self.advance(); // skip 'union'
|
||||
try self.expect(.l_brace);
|
||||
var variant_names = std.ArrayList([]const u8).empty;
|
||||
var variant_types = std.ArrayList(?*Node).empty;
|
||||
while (self.current.tag != .r_brace and self.current.tag != .eof) {
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected variant name in union");
|
||||
}
|
||||
try variant_names.append(self.allocator, self.tokenSlice(self.current));
|
||||
self.advance();
|
||||
if (self.current.tag == .colon) {
|
||||
@@ -413,6 +396,7 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
const vtype = try self.parseTypeExpr();
|
||||
try variant_types.append(self.allocator, vtype);
|
||||
has_any_type = true;
|
||||
} else {
|
||||
// Void variant: name;
|
||||
try variant_types.append(self.allocator, null);
|
||||
@@ -422,10 +406,54 @@ pub const Parser = struct {
|
||||
}
|
||||
}
|
||||
try self.expect(.r_brace);
|
||||
return try self.createNode(start_pos, .{ .union_decl = .{
|
||||
// Always produce enum_decl; variant_types distinguishes payload-less from tagged
|
||||
return try self.createNode(start_pos, .{ .enum_decl = .{
|
||||
.name = name,
|
||||
.variant_names = try variant_names.toOwnedSlice(self.allocator),
|
||||
.variant_types = try variant_types.toOwnedSlice(self.allocator),
|
||||
.variant_types = if (has_any_type) try variant_types.toOwnedSlice(self.allocator) else &.{},
|
||||
} });
|
||||
}
|
||||
|
||||
fn parseUnionDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
|
||||
self.advance(); // skip 'union'
|
||||
try self.expect(.l_brace);
|
||||
var field_names = std.ArrayList([]const u8).empty;
|
||||
var field_types = std.ArrayList(*Node).empty;
|
||||
var anon_idx: u32 = 0;
|
||||
while (self.current.tag != .r_brace and self.current.tag != .eof) {
|
||||
// Anonymous struct field: struct { x, y: f32; };
|
||||
if (self.current.tag == .kw_struct) {
|
||||
const anon_field = try std.fmt.allocPrint(self.allocator, "__anon_{d}", .{anon_idx});
|
||||
anon_idx += 1;
|
||||
const anon_struct_name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ name, anon_field });
|
||||
const struct_node = try self.parseStructDecl(anon_struct_name, self.current.loc.start);
|
||||
try field_names.append(self.allocator, anon_field);
|
||||
try field_types.append(self.allocator, struct_node);
|
||||
if (self.current.tag == .semicolon) {
|
||||
self.advance();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected field name or 'struct'");
|
||||
}
|
||||
try field_names.append(self.allocator, self.tokenSlice(self.current));
|
||||
self.advance();
|
||||
if (self.current.tag != .colon) {
|
||||
return self.fail("union fields must have a type");
|
||||
}
|
||||
self.advance();
|
||||
const ftype = try self.parseTypeExpr();
|
||||
try field_types.append(self.allocator, ftype);
|
||||
if (self.current.tag == .semicolon) {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
try self.expect(.r_brace);
|
||||
return try self.createNode(start_pos, .{ .union_decl = .{
|
||||
.name = name,
|
||||
.field_names = try field_names.toOwnedSlice(self.allocator),
|
||||
.field_types = try field_types.toOwnedSlice(self.allocator),
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -1141,14 +1169,13 @@ pub const Parser = struct {
|
||||
}
|
||||
const name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
// Union literal: .variant(payload)
|
||||
// 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, .{ .union_literal = .{
|
||||
.union_name = null,
|
||||
.variant_name = name,
|
||||
return try self.createNode(start, .{ .enum_literal = .{
|
||||
.name = name,
|
||||
.payload = payload,
|
||||
} });
|
||||
}
|
||||
@@ -1175,8 +1202,12 @@ pub const Parser = struct {
|
||||
// Anonymous struct expression: struct { value: T; count: u32; }
|
||||
return try self.parseStructDecl("__anon", start);
|
||||
},
|
||||
.kw_enum => {
|
||||
// Anonymous enum expression: enum { variant: T; other: u32; }
|
||||
return try self.parseEnumDecl("__anon", start);
|
||||
},
|
||||
.kw_union => {
|
||||
// Anonymous union expression: union { variant: T; other: u32; }
|
||||
// Anonymous C-style union expression: union { f: f32; i: s32; }
|
||||
return try self.parseUnionDecl("__anon", start);
|
||||
},
|
||||
.kw_if => {
|
||||
|
||||
31
src/sema.zig
31
src/sema.zig
@@ -179,8 +179,14 @@ pub const Analyzer = struct {
|
||||
try self.addSymbol(vd.name, .variable, ty, node.span);
|
||||
},
|
||||
.enum_decl => |ed| {
|
||||
try self.addSymbol(ed.name, .enum_type, .{ .enum_type = ed.name }, node.span);
|
||||
try self.enum_types.put(ed.name, ed.variants);
|
||||
if (ed.variant_types.len > 0) {
|
||||
// Tagged enum with payloads
|
||||
try self.addSymbol(ed.name, .enum_type, .{ .union_type = ed.name }, node.span);
|
||||
} else {
|
||||
// Payload-less enum
|
||||
try self.addSymbol(ed.name, .enum_type, .{ .enum_type = ed.name }, node.span);
|
||||
try self.enum_types.put(ed.name, ed.variant_names);
|
||||
}
|
||||
},
|
||||
.struct_decl => |sd| {
|
||||
try self.addSymbol(sd.name, .struct_type, .{ .struct_type = sd.name }, node.span);
|
||||
@@ -392,10 +398,6 @@ pub const Analyzer = struct {
|
||||
.break_expr => .void_type,
|
||||
.continue_expr => .void_type,
|
||||
.enum_literal => .{ .enum_type = "" },
|
||||
.union_literal => |ul| {
|
||||
if (ul.union_name) |name| return .{ .union_type = name };
|
||||
return .void_type;
|
||||
},
|
||||
.struct_literal => |sl| {
|
||||
if (sl.struct_name) |name| {
|
||||
if (self.struct_types.contains(name)) return .{ .struct_type = name };
|
||||
@@ -592,7 +594,11 @@ pub const Analyzer = struct {
|
||||
try self.addSymbol(vd.name, .variable, ty, node.span);
|
||||
},
|
||||
.enum_decl => |ed| {
|
||||
try self.addSymbol(ed.name, .enum_type, .{ .enum_type = ed.name }, node.span);
|
||||
if (ed.variant_types.len > 0) {
|
||||
try self.addSymbol(ed.name, .enum_type, .{ .union_type = ed.name }, node.span);
|
||||
} else {
|
||||
try self.addSymbol(ed.name, .enum_type, .{ .enum_type = ed.name }, node.span);
|
||||
}
|
||||
},
|
||||
.struct_decl => |sd| {
|
||||
try self.addSymbol(sd.name, .struct_type, .{ .struct_type = sd.name }, node.span);
|
||||
@@ -680,8 +686,8 @@ pub const Analyzer = struct {
|
||||
.union_decl => |ud| {
|
||||
try self.addSymbol(ud.name, .enum_type, .{ .union_type = ud.name }, node.span);
|
||||
},
|
||||
.union_literal => |ul| {
|
||||
if (ul.payload) |p| {
|
||||
.enum_literal => |el| {
|
||||
if (el.payload) |p| {
|
||||
try self.analyzeNode(p);
|
||||
}
|
||||
},
|
||||
@@ -690,7 +696,6 @@ pub const Analyzer = struct {
|
||||
.float_literal,
|
||||
.bool_literal,
|
||||
.string_literal,
|
||||
.enum_literal,
|
||||
.type_expr,
|
||||
.param,
|
||||
.match_arm,
|
||||
@@ -741,7 +746,6 @@ pub const Analyzer = struct {
|
||||
.comptime_expr,
|
||||
.enum_literal,
|
||||
.struct_literal,
|
||||
.union_literal,
|
||||
.array_literal,
|
||||
.index_expr,
|
||||
.slice_expr,
|
||||
@@ -936,8 +940,8 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
|
||||
if (findNodeAtOffset(fi.value, offset)) |found| return found;
|
||||
}
|
||||
},
|
||||
.union_literal => |ul| {
|
||||
if (ul.payload) |p| {
|
||||
.enum_literal => |el| {
|
||||
if (el.payload) |p| {
|
||||
if (findNodeAtOffset(p, offset)) |found| return found;
|
||||
}
|
||||
},
|
||||
@@ -947,7 +951,6 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
|
||||
.float_literal,
|
||||
.bool_literal,
|
||||
.string_literal,
|
||||
.enum_literal,
|
||||
.type_expr,
|
||||
.param,
|
||||
.match_arm,
|
||||
|
||||
Reference in New Issue
Block a user