diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 2e0c63f98..a0f0f48a3 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -3390,7 +3390,10 @@ gb_internal void lb_build_stmt(lbProcedure *p, Ast *node) { // NOTE(bill, 2023-02-17): lb_const_value might produce a stack local variable for the // compound literal, so reusing that variable should minimize the stack wastage lbAddr *comp_lit_addr = map_get(&p->module->exact_value_compound_literal_addr_map, rhs); - if (comp_lit_addr) { + // Only when the variable IS the literal. `x: U = []int{0}` declares a union and + // the literal is one of its variants, so reusing that storage would bind the + // variable to a bare `[]int` and never build the union at all + if (comp_lit_addr && are_types_identical(lb_addr_type(*comp_lit_addr), type_of_expr(vd->names[lval_index]))) { if (Entity *e = entity_of_node(vd->names[lval_index])) { lbValue val = comp_lit_addr->addr; lb_add_entity(p->module, e, val); diff --git a/tests/internal/test_union_compound_lit.odin b/tests/internal/test_union_compound_lit.odin new file mode 100644 index 000000000..5a643c06d --- /dev/null +++ b/tests/internal/test_union_compound_lit.odin @@ -0,0 +1,45 @@ +package test_internal + +import "core:testing" + +// `x := []int{0}` lets the variable reuse the compound literal's storage, which is a stack saving. +// `x: U = []int{0}` is not the same thing: the variable is a union and the literal is one of its +// variants, so reusing that storage bound the variable to a bare `[]int` and never built the union. +// A later type assertion then reached the backend with the wrong operand type +@(test) +union_declared_from_a_variant_compound_literal :: proc(t: ^testing.T) { + U :: union { []int, int } + S :: struct { a: int } + V :: union { S, int } + A :: union { [2]int, int } + + x: U = []int{7} + if v, ok := x.([]int); testing.expect(t, ok, "slice variant lost") { + testing.expect_value(t, len(v), 1) + testing.expect_value(t, v[0], 7) + } + + // the same shape through a switch, which is how the union is usually consumed + got := 0 + switch v in x { + case []int: got = v[0] + case int: got = -1 + } + testing.expect_value(t, got, 7) + + s: V = S{5} + if v, ok := s.(S); testing.expect(t, ok, "struct variant lost") { + testing.expect_value(t, v.a, 5) + } + + a: A = [2]int{3, 4} + if v, ok := a.([2]int); testing.expect(t, ok, "array variant lost") { + testing.expect_value(t, v[1], 4) + } + + // the reuse is still correct when the variable IS the literal + plain := []int{1, 2} + testing.expect_value(t, plain[1], 2) + ps := S{9} + testing.expect_value(t, ps.a, 9) +}