diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 7a72dd1f4..b81e6366e 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -791,6 +791,20 @@ gb_internal lbValue lb_const_value_bit_field(lbModule *m, Type *type, Ast *value } +// A value assigned to an array is broadcast to every element, and the checker peels every array +// level before matching it, so the literal may be for a type below the immediate element type. +gb_internal bool lb_const_value_is_broadcast(Type *elem_type, Type *lit_type) { + if (are_types_identical(lit_type, elem_type)) { + return true; + } + // `[4][8]Item = Item{...}`, one level is consumed per recursion + if (are_types_identical(lit_type, core_broadcastable_elem_type(elem_type))) { + return true; + } + // `[8]U = U(Item{...})`, a union element takes the value as one of its variants + return type_conversion_is_variant(elem_type, lit_type); +} + gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lbConstContext cc) { if (cc.allow_local) { cc.is_rodata = false; @@ -1504,7 +1518,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb if (elem_count == 0 || !elem_type_can_be_constant(elem_type)) { return lb_const_nil(m, original_type); } - if (are_types_identical(value.value_compound->tav.type, elem_type)) { + if (lb_const_value_is_broadcast(elem_type, value.value_compound->tav.type)) { // Compound is of array item type; expand its value to all items in array. LLVMValueRef* values = gb_alloc_array(temporary_allocator(), LLVMValueRef, cast(isize)type->Array.count); diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 11df5bed0..1beb880d9 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -43,7 +43,9 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin test ..\test_issue_7008.odin %COMMON% || exit /b ..\..\..\odin check ..\test_issue_7012.odin -no-entry-point %COMMON% || exit /b ..\..\..\odin check ..\test_issue_7260.odin -no-entry-point %COMMON% || exit /b +..\..\..\odin test ..\test_issue_bool_to_be_conversion.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_bool_comparison_truthiness.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_const_array_broadcast.odin %COMMON% || exit /b ..\..\..\odin check ..\test_issue_7336.odin -no-entry-point %COMMON% || exit /b ..\..\..\odin check ..\test_issue_ellipsis_type_call.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "10" || exit /b ..\..\..\odin check ..\test_issue_foreign_redeclaration.odin -no-entry-point %COMMON% || exit /b diff --git a/tests/issues/run.sh b/tests/issues/run.sh index a22693db8..41a0835b4 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -98,6 +98,8 @@ $ODIN build ../test_issue_7167.odin $COMMON $ODIN build ../test_issue_7188.odin $COMMON $ODIN check ../test_issue_7260.odin -no-entry-point $COMMON_CHECK $ODIN test ../test_issue_bool_to_be_conversion.odin $COMMON +$ODIN test ../test_issue_bool_comparison_truthiness.odin $COMMON +$ODIN test ../test_issue_const_array_broadcast.odin $COMMON $ODIN test ../test_issue_swizzle_multi_assign.odin $COMMON $ODIN check ../test_issue_foreign_redeclaration.odin -no-entry-point $COMMON_CHECK diff --git a/tests/issues/test_issue_const_array_broadcast.odin b/tests/issues/test_issue_const_array_broadcast.odin new file mode 100644 index 000000000..5960e2f0c --- /dev/null +++ b/tests/issues/test_issue_const_array_broadcast.odin @@ -0,0 +1,82 @@ +package test_issues + +import "core:testing" + +// A value assigned to an array is broadcast to every element of every level, but the constant +// lowering only recognised a literal whose type was the immediate element type. `[4][8]Item` has +// two levels to cross and `[8]U` has a union variant to reach, so both were taken for array +// literals and tripped an assertion in the compiler. + +@(private="file") +Item :: struct { + stage: int, + size: int, +} + +@(private="file") +U :: union { + Item, +} + +@(private="file") +Desc :: struct { + items: [4][8]Item, +} + +@(private="file") +nested_global: [4][8]Item = Item{stage = 1, size = 64} + +@(private="file") +union_global: [8]U = U(Item{stage = 2, size = 65}) + +@(test) +const_array_broadcast_nested :: proc(t: ^testing.T) { + expected :: Item{stage = 1, size = 64} + + for row in nested_global { + for item in row { + testing.expect_value(t, item, expected) + } + } + + nested_local: [4][8]Item = Item{stage = 1, size = 64} + testing.expect_value(t, nested_local, nested_global) + + d := Desc{items = Item{stage = 1, size = 64}} + testing.expect_value(t, d.items, nested_global) + + // positional fields reach the same path + positional: [4][8]Item = Item{1, 64} + testing.expect_value(t, positional, nested_global) + + deep: [2][3][4]int = 7 + testing.expect_value(t, deep[1][2][3], 7) +} + +@(test) +const_array_broadcast_union :: proc(t: ^testing.T) { + expected :: Item{stage = 2, size = 65} + + for u in union_global { + item, ok := u.(Item) + testing.expect(t, ok, "every element should hold the Item variant") + testing.expect_value(t, item, expected) + } + + union_local: [8]U = U(Item{stage = 2, size = 65}) + testing.expect_value(t, union_local, union_global) +} + +@(test) +const_array_literals_unchanged :: proc(t: ^testing.T) { + // a literal for the array's own type is not a broadcast + indexed: [4]Item = {0..<2 = Item{stage = 3, size = 66}, 3 = Item{stage = 4, size = 67}} + testing.expect_value(t, indexed[0], Item{stage = 3, size = 66}) + testing.expect_value(t, indexed[1], Item{stage = 3, size = 66}) + testing.expect_value(t, indexed[2], Item{}) + testing.expect_value(t, indexed[3], Item{stage = 4, size = 67}) + + positional: [3]Item = {Item{stage = 5, size = 68}, {}, {}} + testing.expect_value(t, positional[0], Item{stage = 5, size = 68}) + testing.expect_value(t, positional[2], Item{}) +}