diff --git a/src/builtins/builtins.zig b/src/builtins/builtins.zig index 9e2212af7..8ca941c27 100644 --- a/src/builtins/builtins.zig +++ b/src/builtins/builtins.zig @@ -32,6 +32,7 @@ const funcs = [_]C.HostFuncEntry{ // Utils. func("bitcast_", zErrFunc(bitcast)), func("copy_", zErrFunc(copy)), + func("choicetag_", zErrFunc(choicetag)), func("dump", zErrFunc(dump)), func("eprint", eprint), func("errorReport", zErrFunc(errorReport)), @@ -596,6 +597,16 @@ pub fn copy(vm: *cy.VM) anyerror!Value { return cy.value.shallowCopy(vm, type_id, val); } +pub fn choicetag(vm: *cy.VM) anyerror!Value { + const tag_t: cy.TypeId = @intCast(vm.getInt(0)); + const type_e = vm.sema.getType(tag_t); + if (type_e.kind != .enum_t) { + return error.InvalidArgument; + } + const choice_tag = vm.getValue(1).castHeapObject(*cy.heap.Object).getValue(0).asInt(); + return cy.Value.initEnum(@intCast(tag_t), @intCast(choice_tag)); +} + pub fn errorReport(vm: *cy.VM) anyerror!Value { const curFrameLen = vm.compactTrace.len; diff --git a/src/builtins/builtins_vm.cy b/src/builtins/builtins_vm.cy index ed6e55b19..b85d4e558 100644 --- a/src/builtins/builtins_vm.cy +++ b/src/builtins/builtins_vm.cy @@ -13,6 +13,13 @@ func copy[T](val T) T: @host -func copy_[T](type_id int, val T) T +--| Returns the tag of a choice. +--| TODO: This will be moved to `T.tag()`. +func choicetag[T](choice T) T.Tag: + return choicetag_(typeid[T.Tag], choice) + +@host -func choicetag_[T](tag_t int, choice T) T.Tag + --| Dumps a detailed description of a value. @host func dump(val any) void diff --git a/src/module.zig b/src/module.zig index 872fdfebc..b5c39a4b0 100644 --- a/src/module.zig +++ b/src/module.zig @@ -311,19 +311,19 @@ pub const ChunkExt = struct { pub fn reserveEnumType(c: *cy.Chunk, parent: *cy.Sym, name: []const u8, isChoiceType: bool, decl: *ast.EnumDecl) !*cy.sym.EnumType { const sym = try c.createEnumType(parent, name, isChoiceType, decl); - _ = try addUniqueSym(c, c.sym.getMod(), name, @ptrCast(sym), @ptrCast(decl)); + _ = try addUniqueSym(c, parent.getMod().?, name, @ptrCast(sym), @ptrCast(decl)); return sym; } pub fn reserveTraitType(c: *cy.Chunk, parent: *cy.Sym, name: []const u8, decl: *ast.TraitDecl) !*cy.sym.TraitType { const sym = try c.createTraitType(parent, name, decl); - _ = try addUniqueSym(c, c.sym.getMod(), name, @ptrCast(sym), @ptrCast(decl)); + _ = try addUniqueSym(c, parent.getMod().?, name, @ptrCast(sym), @ptrCast(decl)); return sym; } pub fn reserveObjectType(c: *cy.Chunk, parent: *cy.Sym, name: []const u8, decl: *ast.Node) !*cy.sym.ObjectType { const sym = try c.createObjectType(parent, name, decl); - _ = try addUniqueSym(c, c.sym.getMod(), name, @ptrCast(sym), decl); + _ = try addUniqueSym(c, parent.getMod().?, name, @ptrCast(sym), decl); return sym; } diff --git a/src/sema.zig b/src/sema.zig index 0df64757c..beb53a552 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -1654,7 +1654,11 @@ pub fn declareModuleAlias(c: *cy.Chunk, node: *ast.ImportStmt) !void { pub fn reserveEnum(c: *cy.Chunk, node: *ast.EnumDecl) !*cy.sym.EnumType { const name = c.ast.nodeString(node.name); - return c.reserveEnumType(@ptrCast(c.sym), name, node.isChoiceType, node); + const sym = try c.reserveEnumType(@ptrCast(c.sym), name, node.isChoiceType, node); + if (node.isChoiceType) { + _ = try c.reserveEnumType(@ptrCast(sym), "Tag", false, node); + } + return sym; } pub fn resolveEnumType(c: *cy.Chunk, sym: *cy.sym.EnumType, decl: *ast.EnumDecl) !void { @@ -1666,6 +1670,11 @@ pub fn resolveEnumType(c: *cy.Chunk, sym: *cy.sym.EnumType, decl: *ast.EnumDecl) defer popResolveContext(c); try declareEnumMembers(c, sym, decl); + + if (sym.isChoiceType) { + const tag_sym = sym.getMod().getSym("Tag").?.cast(.enum_t); + try declareEnumMembers(c, tag_sym, decl); + } } /// Explicit `decl` node for distinct type declarations. Must belong to `c`. @@ -1677,7 +1686,7 @@ pub fn declareEnumMembers(c: *cy.Chunk, sym: *cy.sym.EnumType, decl: *ast.EnumDe for (decl.members, 0..) |member, i| { const mName = c.ast.nodeString(member.name); var payloadType: cy.TypeId = cy.NullId; - if (member.typeSpec != null) { + if (sym.isChoiceType and member.typeSpec != null) { payloadType = try resolveTypeSpecNode(c, member.typeSpec); } const modSymId = try c.declareEnumMember(@ptrCast(sym), mName, sym.type, sym.isChoiceType, @intCast(i), payloadType, member); @@ -1948,6 +1957,9 @@ pub fn reserveTemplateVariant(c: *cy.Chunk, template: *cy.sym.Template, opt_head if (template == c.sema.option_tmpl) { c.compiler.sema.types.items[sym.type].kind = .option; } + if (decl.isChoiceType) { + _ = try c.reserveEnumType(@ptrCast(sym), "Tag", false, decl); + } return @ptrCast(sym); }, .distinct_t => { diff --git a/src/sym.zig b/src/sym.zig index 47e234e4d..918261923 100644 --- a/src/sym.zig +++ b/src/sym.zig @@ -1727,7 +1727,7 @@ pub const ChunkExt = struct { @as(*cy.Module, @ptrCast(&sym.mod)).* = cy.Module.init(c); c.compiler.sema.types.items[typeId] = .{ .sym = @ptrCast(sym), - .kind = if (isChoiceType) .choice else .@"enum", + .kind = if (isChoiceType) .choice else .enum_t, .info = .{}, .data = undefined, }; diff --git a/src/types.zig b/src/types.zig index 65e4741d8..ac2b53a9a 100644 --- a/src/types.zig +++ b/src/types.zig @@ -20,7 +20,7 @@ pub const TypeKind = enum(u8) { float, object, host_object, - @"enum", + enum_t, choice, struct_t, option, @@ -335,7 +335,7 @@ pub const SemaExt = struct { if (typeId < PrimitiveEnd) { return false; } - return s.types.items[typeId].kind == .@"enum"; + return s.types.items[typeId].kind == .enum_t; } pub fn isChoiceType(s: *cy.Sema, typeId: TypeId) bool { diff --git a/src/vm.zig b/src/vm.zig index feb6e25f5..f6c75b33b 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -2073,7 +2073,7 @@ pub fn unbox(vm: *VM, val: Value, type_id: cy.TypeId) cy.Value { fn canInferArg(vm: *VM, arg: Value, target_t: cy.TypeId) bool { if (arg.getTypeId() == bt.TagLit) { const type_e = vm.c.types[target_t]; - if (type_e.kind == .@"enum") { + if (type_e.kind == .enum_t) { const name = vm.syms.buf[arg.asSymbolId()].name; if (type_e.sym.cast(.enum_t).getMemberTag(name)) |value| { _ = value; @@ -2090,7 +2090,7 @@ fn canInferArg(vm: *VM, arg: Value, target_t: cy.TypeId) bool { fn inferArg(vm: *VM, arg: Value, target_t: cy.TypeId) ?Value { if (arg.getTypeId() == bt.TagLit) { const type_e = vm.c.types[target_t]; - if (type_e.kind == .@"enum") { + if (type_e.kind == .enum_t) { const name = vm.syms.buf[arg.asSymbolId()].name; if (type_e.sym.cast(.enum_t).getMemberTag(name)) |value| { return Value.initEnum(target_t, @intCast(value)); diff --git a/test/modules/core.cy b/test/modules/core.cy index f7c8bebc1..7eb8eba21 100644 --- a/test/modules/core.cy +++ b/test/modules/core.cy @@ -31,6 +31,8 @@ foo() -- bool(), see truthy_test.cy +-- choicetag(), see choice_type.cy + -- copy() t.eq(copy(123), 123) type S: diff --git a/test/types/choice_type.cy b/test/types/choice_type.cy index 70eec4d9d..8f4d28963 100644 --- a/test/types/choice_type.cy +++ b/test/types/choice_type.cy @@ -92,4 +92,12 @@ var rect = Rectangle{width=123, height=234} s = Shape.rectangle(rect) test.assert(s.!rectangle == rect) +-- Builtin choicetag. +s = Shape.line(123) +test.eq(choicetag(s), .line) + +-- Choice type also generates a enum `Tag` type. +var tag = Shape.Tag.rectangle +test.eq(tag, .rectangle) + --cytest: pass \ No newline at end of file