diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 3783a190d..ea4e52a3f 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4613,6 +4613,9 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) { if (is_type_internally_pointer_like(type)) { incoming_values[0] = LLVMBuildBitCast(p->builder, incoming_values[0], llvm_type, ""); } + // A union constant is built as an anonymous packed struct, which a phi cannot accept + // alongside the named type it results in, even though the two are laid out identically + incoming_values[0] = OdinLLVMBuildTransmute(p, incoming_values[0], llvm_type); lb_emit_jump(p, done); lb_start_block(p, else_); @@ -4622,6 +4625,7 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) { if (is_type_internally_pointer_like(type)) { incoming_values[1] = LLVMBuildBitCast(p->builder, incoming_values[1], llvm_type, ""); } + incoming_values[1] = OdinLLVMBuildTransmute(p, incoming_values[1], llvm_type); lb_emit_jump(p, done); lb_start_block(p, done); diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index c71d16122..ce7f54e52 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -532,18 +532,22 @@ gb_internal lbValue lb_emit_or_else(lbProcedure *p, Ast *arg, Ast *else_expr, Ty lb_emit_if(p, lb_emit_try_has_value(p, rhs), then, else_); lb_start_block(p, then); - incoming_values[0] = lb_emit_conv(p, lhs, type).value; + LLVMTypeRef llvm_type = lb_type(p->module, type); + + // A union constant is built as an anonymous packed struct, which a phi cannot accept + // alongside the named type it results in, even though the two are laid out identically + incoming_values[0] = OdinLLVMBuildTransmute(p, lb_emit_conv(p, lhs, type).value, llvm_type); lb_emit_jump(p, done); lb_start_block(p, else_); - incoming_values[1] = lb_emit_conv(p, lb_build_expr(p, else_expr), type).value; + incoming_values[1] = OdinLLVMBuildTransmute(p, lb_emit_conv(p, lb_build_expr(p, else_expr), type).value, llvm_type); lb_emit_jump(p, done); lb_start_block(p, done); lbValue res = {}; - res.value = LLVMBuildPhi(p->builder, lb_type(p->module, type), ""); + res.value = LLVMBuildPhi(p->builder, llvm_type, ""); res.type = type; GB_ASSERT(p->curr_block->preds.count >= 2); diff --git a/tests/internal/test_union_ternary.odin b/tests/internal/test_union_ternary.odin new file mode 100644 index 000000000..60f4e2daa --- /dev/null +++ b/tests/internal/test_union_ternary.odin @@ -0,0 +1,49 @@ +package test_internal + +import "core:testing" + +// A union constant is built as an anonymous packed struct, which is fine everywhere that adopts the +// value's own type -- a global takes it verbatim. A phi cannot: its incoming values must have the +// phi's own named type, even though the two layouts are identical. Both the ternary and `or_else` +// build one +@(test) +union_constant_in_a_ternary :: proc(t: ^testing.T) { + E :: enum { None, Bad, Worse } + EU :: union { E, string } + + f := true + g := false + + a: EU = EU(E.Worse) if f else nil + b: EU = EU("s") if f else nil + c: EU = nil if g else EU(E.Worse) + d: EU = EU(E.Worse) if f else EU(E.Bad) + e: EU = EU(E.Worse) if g else EU(E.Bad) + + if v, ok := a.(E); testing.expect(t, ok, "a lost its variant") { testing.expect(t, v == E.Worse, "a") } + if v, ok := b.(string); testing.expect(t, ok, "b lost its variant") { testing.expect_value(t, v, "s") } + if v, ok := c.(E); testing.expect(t, ok, "c lost its variant") { testing.expect(t, v == E.Worse, "c") } + if v, ok := d.(E); testing.expect(t, ok, "d lost its variant") { testing.expect(t, v == E.Worse, "d") } + if v, ok := e.(E); testing.expect(t, ok, "e lost its variant") { testing.expect(t, v == E.Bad, "e") } + + // the arm that is not taken must not decide the result + n: EU = EU(E.Bad) if g else nil + _, is_enum := n.(E) + testing.expect(t, !is_enum, "the untaken arm was selected") +} + +@(test) +union_constant_in_or_else :: proc(t: ^testing.T) { + E :: enum { None, Bad } + EU :: union { E, string } + + m: map[int]EU + defer delete(m) + + missing := m[0] or_else EU(E.Bad) + if v, ok := missing.(E); testing.expect(t, ok, "or_else default lost its variant") { testing.expect(t, v == E.Bad, "default") } + + m[1] = EU("here") + present := m[1] or_else EU(E.Bad) + if v, ok := present.(string); testing.expect(t, ok, "or_else present lost its variant") { testing.expect_value(t, v, "here") } +}