Merge pull request #7087 from korvahkh/const-union-fixes

Implement fixes for constant unions
This commit is contained in:
Jeroen van Rijn
2026-08-20 03:35:36 +02:00
committed by GitHub
9 changed files with 395 additions and 141 deletions

View File

@@ -689,7 +689,7 @@ gb_internal bool check_proc_params_assignable(CheckerContext *c, Type *x, Type *
#define MAXIMUM_TYPE_DISTANCE 10
gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type, bool allow_array_programming) {
gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type, bool allow_array_programming, bool allow_unions=true) {
if (c == nullptr) {
GB_ASSERT(operand->mode == Addressing_Value);
GB_ASSERT(is_type_typed(operand->type));
@@ -895,7 +895,7 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand
}
}
if (is_type_union(dst)) {
if (is_type_union(dst) && allow_unions) {
for (Type *vt : dst->Union.variants) {
if (are_types_identical(vt, s)) {
return 1;
@@ -917,7 +917,7 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand
i64 prev_lowest_score = -1;
i64 lowest_score = -1;
for (Type *vt : dst->Union.variants) {
i64 score = check_distance_between_types(c, operand, vt, allow_array_programming);
i64 score = check_distance_between_types(c, operand, vt, allow_array_programming, /*allow_unions*/false);
if (score >= 0) {
if (lowest_score < 0) {
lowest_score = score;
@@ -1035,7 +1035,7 @@ gb_internal i64 assign_score_function(i64 distance, bool is_variadic=false) {
}
gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *operand, Type *type, i64 *score_, bool is_variadic=false, bool allow_array_programming=true) {
gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *operand, Type *type, i64 *score_, bool is_variadic=false, bool allow_array_programming=true, bool allow_unions=true) {
if (c == nullptr) {
GB_ASSERT(operand->mode == Addressing_Value);
GB_ASSERT(is_type_typed(operand->type));
@@ -1045,7 +1045,7 @@ gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *o
return false;
}
i64 score = check_distance_between_types(c, operand, type, allow_array_programming);
i64 score = check_distance_between_types(c, operand, type, allow_array_programming, allow_unions);
if (score >= 0) {
if (score_) *score_ = assign_score_function(score, is_variadic);
return true;
@@ -1159,7 +1159,11 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ
if (is_type_untyped(operand->type)) {
Type *target_type = type;
if (type == nullptr || is_type_any(type)) {
Type *elem_type = core_broadcastable_elem_type(type);
if (is_type_union(elem_type)) {
target_type = elem_type;
}
if (type == nullptr || is_type_any(elem_type)) {
if (type == nullptr && is_type_untyped_uninit(operand->type)) {
String article = error_article(context_name); // Grab definite or indefinite article matching `context_name`, or "" if not found.
@@ -1252,6 +1256,11 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ
}
if (check_is_assignable_to(c, operand, type)) {
if (operand->mode == Addressing_Constant && type_conversion_is_variant(type, operand->type)) {
Operand o = {};
check_expr_with_type_hint(c, &o, operand->expr, type);
operand->value = exact_value_variant(operand->expr);
}
if (operand->mode == Addressing_Type && is_type_typeid(type)) {
add_type_info_type(c, operand->type);
add_type_and_value(c, operand->expr, Addressing_Value, type, exact_value_typeid(operand->type));
@@ -3925,6 +3934,7 @@ gb_internal bool check_cast_internal(CheckerContext *c, Operand *x, Type *type)
x->mode = Addressing_Value;
} else if (is_type_union(type)) {
if (is_type_union_constantable(type)) {
x->value = exact_value_variant(x->expr);
return true;
}
x->mode = Addressing_Value;
@@ -3991,8 +4001,9 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type, bool forb
if (is_type_untyped(x->type)) {
Type *final_type = type;
if (is_const_expr && !is_type_constant_type(type)) {
if (is_type_union(type)) {
convert_to_typed(c, x, type);
Type *elem_type = core_broadcastable_elem_type(type);
if (is_type_union(elem_type)) {
convert_to_typed(c, x, elem_type);
}
final_type = default_type(x->type);
}
@@ -5185,11 +5196,12 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar
case Type_Array: {
Type *elem = base_array_type(t);
if (check_is_assignable_to(c, operand, elem)) {
while (is_type_array(elem)) {
elem = base_array_type(elem);
}
elem = core_broadcastable_elem_type(elem);
operand->mode = Addressing_Value;
convert_to_typed(c, operand, elem, /*no_final_update*/true);
if (is_type_union(elem)) {
target_type = operand->type;
}
} else {
if (operand->value.kind == ExactValue_String) {
String s = operand->value.value_string;
@@ -5294,7 +5306,7 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar
for_array(i, t->Union.variants) {
Type *vt = t->Union.variants[i];
i64 score = 0;
if (check_is_assignable_to_with_score(c, operand, vt, &score)) {
if (check_is_assignable_to_with_score(c, operand, vt, &score, /*is_variadic*/false, /*allow_array_programming*/true, /*allow_unions*/t->Union.variants.count == 1)) {
valids[valid_count].index = i;
valids[valid_count].score = score;
valid_count += 1;
@@ -5328,7 +5340,7 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar
operand->mode = Addressing_Value;
}
convert_to_typed(c, operand, new_type, /*no_final_update*/true);
target_type = new_type;
target_type = operand->type;
break;
} else if (valid_count > 1) {
ERROR_BLOCK();
@@ -12851,8 +12863,21 @@ gb_internal ExprKind check_expr_base(CheckerContext *c, Operand *o, Ast *node, T
}
gb_string_free(xs);
}
if (o->type != nullptr && is_type_untyped(o->type)) {
add_untyped(c, node, o->mode, o->type, o->value);
if (o->type != nullptr) {
if (type_hint != nullptr) {
Type *elem_type = core_broadcastable_elem_type(type_hint);
if (is_type_untyped(o->type)) {
if (is_type_union(elem_type)) {
convert_to_typed(c, o, elem_type);
}
}
if (type_conversion_is_variant(elem_type, o->type)) {
o->value.variant_type = o->type;
}
}
if (is_type_untyped(o->type)) {
add_untyped(c, node, o->mode, o->type, o->value);
}
}
check_rtti_type_disallowed(node, o->type, "An expression is using a type, %s, which has been disallowed");
@@ -12986,6 +13011,14 @@ gb_internal bool is_exact_value_zero(ExactValue const &v) {
return v.value_procedure == nullptr;
case ExactValue_Typeid:
return v.value_typeid == nullptr;
case ExactValue_Variant:
if (v.value_variant == nullptr) {
return true;
}
if (v.value_variant->tav.mode != Addressing_Constant) {
return false;
}
return is_exact_value_zero(v.value_variant->tav.value);
}
return true;
@@ -13013,8 +13046,26 @@ gb_internal bool compare_exact_values_compound_lit(TokenKind op, ExactValue x, E
return test;
}
gb_internal bool compare_exact_values_variant(TokenKind op, ExactValue x, ExactValue y) {
Ast *lhs = x.value_variant;
Ast *rhs = y.value_variant;
return compare_exact_values(op, lhs->tav.value, rhs->tav.value);
}
gb_internal void match_exact_values_variant(ExactValue *x, ExactValue *y) {
GB_ASSERT(x->kind == ExactValue_Variant);
while (x->value_variant != nullptr &&
x->value_variant->tav.mode == Addressing_Constant) {
*x = x->value_variant->tav.value;
}
while (y->kind == ExactValue_Variant &&
y->value_variant != nullptr &&
y->value_variant->tav.mode == Addressing_Constant) {
*y = y->value_variant->tav.value;
}
match_exact_values(x, y);
}
gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthand);

View File

@@ -29,6 +29,7 @@ enum ExactValueKind {
ExactValue_Typeid = 10,
ExactValue_String16 = 11,
ExactValue_AsmTemplate = 12,
ExactValue_Variant = 13,
ExactValue_Count,
};
@@ -47,6 +48,8 @@ gb_global char const *exact_value_kind_string[ExactValue_Count] = {
"Procedure",
"Typeid",
"String16",
"AsmTemplate",
"Variant",
};
struct ExactValue {
@@ -64,7 +67,9 @@ struct ExactValue {
Type * value_typeid;
String16 value_string16;
Ast * value_asm_template;
Ast * value_variant;
};
Type *variant_type;
};
gb_global ExactValue const empty_exact_value = {};
@@ -115,6 +120,9 @@ gb_internal uintptr hash_exact_value(ExactValue v) {
case ExactValue_Typeid:
res = ptr_map_hash_key(v.value_typeid);
break;
case ExactValue_Variant:
res = ptr_map_hash_key(v.value_variant);
break;
default:
res = gb_fnv32a(&v, gb_size_of(ExactValue));
}
@@ -202,6 +210,11 @@ gb_internal ExactValue exact_value_typeid(Type *type) {
return result;
}
gb_internal ExactValue exact_value_variant(Ast *node) {
ExactValue result = {ExactValue_Variant};
result.value_variant = node;
return result;
}
gb_internal ExactValue exact_value_integer_from_string(String const &string) {
ExactValue result = {ExactValue_Integer};
@@ -688,6 +701,7 @@ gb_internal i32 exact_value_order(ExactValue const &v) {
switch (v.kind) {
case ExactValue_Invalid:
case ExactValue_Compound:
case ExactValue_Variant:
return 0;
case ExactValue_Bool:
case ExactValue_String:
@@ -712,6 +726,8 @@ gb_internal i32 exact_value_order(ExactValue const &v) {
}
}
gb_internal void match_exact_values_variant(ExactValue *x, ExactValue *y);
gb_internal void match_exact_values(ExactValue *x, ExactValue *y) {
if (exact_value_order(*y) < exact_value_order(*x)) {
match_exact_values(y, x);
@@ -772,6 +788,10 @@ gb_internal void match_exact_values(ExactValue *x, ExactValue *y) {
return;
}
break;
case ExactValue_Variant:
match_exact_values_variant(x, y);
return;
}
compiler_error("match_exact_values: How'd you get here? Invalid ExactValueKind %d", x->kind);
@@ -973,6 +993,7 @@ gb_internal gb_inline i32 cmp_f64(f64 a, f64 b) {
}
gb_internal bool compare_exact_values_compound_lit(TokenKind op, ExactValue x, ExactValue y);
gb_internal bool compare_exact_values_variant(TokenKind op, ExactValue x, ExactValue y);
gb_internal bool compare_exact_values(TokenKind op, ExactValue x, ExactValue y) {
match_exact_values(&x, &y);
@@ -1119,6 +1140,16 @@ gb_internal bool compare_exact_values(TokenKind op, ExactValue x, ExactValue y)
return false;
}
return compare_exact_values_compound_lit(op, x, y);
case ExactValue_Variant:
if (op != Token_CmpEq && op != Token_NotEq) {
return false;
}
if (x.kind != y.kind) {
return op == Token_NotEq;
}
return compare_exact_values_variant(op, x, y);
}
GB_PANIC("Invalid comparison: %d", x.kind);
@@ -1184,6 +1215,8 @@ gb_internal gbString write_exact_value_to_string(gbString str, ExactValue const
return write_expr_to_string(str, v.value_compound, false);
case ExactValue_Procedure:
return write_expr_to_string(str, v.value_procedure, false);
case ExactValue_Variant:
return write_expr_to_string(str, v.value_variant, false);
}
return str;
};

View File

@@ -3439,7 +3439,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
cc.link_section = e->Variable.link_section;
ExactValue v = tav.value;
lbValue init = lb_const_value(m, e->type, v, lb_build_expr_original_const_type(decl->init_expr), cc);
lbValue init = lb_const_value(m, e->type, v, cc);
LLVMDeleteGlobal(g.value);

View File

@@ -418,7 +418,7 @@ static lbConstContext const LB_CONST_CONTEXT_DEFAULT_NO_LOCAL = {false, false, {
gb_internal lbValue lb_const_nil(lbModule *m, Type *type);
gb_internal lbValue lb_const_undef(lbModule *m, Type *type);
gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Type *value_type=nullptr, lbConstContext cc = LB_CONST_CONTEXT_DEFAULT);
gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lbConstContext cc = LB_CONST_CONTEXT_DEFAULT);
gb_internal lbValue lb_const_bool(lbModule *m, Type *type, bool value);
gb_internal lbValue lb_const_int(lbModule *m, Type *type, u64 value);

View File

@@ -206,7 +206,7 @@ gb_internal LLVMValueRef llvm_const_named_struct_internal(lbModule *m, LLVMTypeR
GB_ASSERT_MSG(value_count == elem_count, "%s %u %u", LLVMPrintTypeToString(t), value_count, elem_count);
if (force_non_named) {
return LLVMConstStructInContext(m->ctx, values, value_count, true);
return LLVMConstStructInContext(m->ctx, values, value_count, LLVMIsPackedStruct(t));
}
bool failure = false;
@@ -216,7 +216,7 @@ gb_internal LLVMValueRef llvm_const_named_struct_internal(lbModule *m, LLVMTypeR
}
if (failure) {
return LLVMConstStructInContext(m->ctx, values, value_count, true);
return LLVMConstStructInContext(m->ctx, values, value_count, LLVMIsPackedStruct(t));
}
return LLVMConstNamedStruct(t, values, value_count);
}
@@ -582,13 +582,13 @@ gb_internal bool lb_is_nested_possibly_constant(Type *ft, Selection const &sel,
return lb_is_elem_const(elem, ft);
}
gb_internal void lb_const_array_spread(lbModule *m, lbConstContext cc, Type *array, ExactValue value, lbValue *res, Type *value_type) {
gb_internal void lb_const_array_spread(lbModule *m, lbConstContext cc, Type *array, ExactValue value, lbValue *res) {
GB_ASSERT(array->kind == Type_Array);
i64 count = array->Array.count;
Type *elem = array->Array.elem;
lbValue single_elem = lb_const_value(m, elem, value, value_type, cc);
lbValue single_elem = lb_const_value(m, elem, value, cc);
LLVMValueRef *elems = gb_alloc_array(permanent_allocator(), LLVMValueRef, cast(isize)count);
for (i64 i = 0; i < count; i++) {
@@ -661,7 +661,7 @@ gb_internal lbValue lb_const_value_bit_field(lbModule *m, Type *type, Ast *value
if (fv->value->tav.mode != Addressing_Constant) {
continue;
}
lbValue field_expr = lb_const_value(m, field_type, fv->value->tav.value, field_type);
lbValue field_expr = lb_const_value(m, field_type, fv->value->tav.value);
array_add(&values, field_expr);
array_add(&fields, FieldData{field_type, cast(u64)bit_offset, cast(u64)bit_size});
}
@@ -791,7 +791,7 @@ gb_internal lbValue lb_const_value_bit_field(lbModule *m, Type *type, Ast *value
}
gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Type *value_type, lbConstContext cc) {
gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lbConstContext cc) {
if (cc.allow_local) {
cc.is_rodata = false;
}
@@ -803,6 +803,11 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
lbValue res = {};
res.type = original_type;
while (value.kind == ExactValue_Variant &&
(value.variant_type == nullptr ||
are_types_identical(value.variant_type, original_type))) {
value = value.value_variant->tav.value;
}
if (!is_type_bit_field(original_type)) {
type = core_type(type);
value = convert_exact_value_for_type(value, type);
@@ -816,25 +821,36 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
GB_ASSERT(bt->kind == Type_Union);
if (bt->Union.variants.count == 0) {
return lb_const_nil(m, original_type);
} else if (bt->Union.variants.count == 1) {
if (value.kind == ExactValue_Compound) {
ast_node(cl, CompoundLit, value.value_compound);
if (cl->elems.count == 0) {
if (cl->type == nullptr) {
return lb_const_nil(m, original_type);
}
if (are_types_identical(type_of_expr(cl->type), original_type)) {
return lb_const_nil(m, original_type);
}
}
}
}
if (value_type == t_untyped_nil) {
Type *value_type = value.variant_type;
switch (value.kind) {
case ExactValue_Invalid:
return lb_const_nil(m, original_type);
case ExactValue_Compound: {
ast_node(cl, CompoundLit, value.value_compound);
if (value_type == nullptr || are_types_identical(value_type, original_type)) {
GB_ASSERT(cl->elems.count == 0);
return lb_const_nil(m, original_type);
}
break;
}
case ExactValue_Variant:
value = value.value_variant->tav.value;
break;
}
GB_ASSERT_MSG(value_type != nullptr, "%s :: %s", type_to_string(original_type), exact_value_to_string(value));
GB_ASSERT(!are_types_identical(value_type, original_type));
if (value_type == t_untyped_nil) {
return lb_const_nil(m, original_type);
}
if (bt->Union.variants.count == 1) {
Type *t = bt->Union.variants[0];
lbValue cv = lb_const_value(m, t, value, value_type, cc);
lbValue cv = lb_const_value(m, t, value, cc);
GB_ASSERT(LLVMIsConstant(cv.value));
LLVMTypeRef llvm_type = lb_type(m, original_type);
@@ -870,51 +886,11 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
return res;
}
} else {
if (value_type == nullptr) {
if (value.kind == ExactValue_Compound) {
ast_node(cl, CompoundLit, value.value_compound);
if (cl->elems.count == 0) {
return lb_const_nil(m, original_type);
}
value_type = type_of_expr(value.value_compound);
} else if (value.kind == ExactValue_Invalid) {
return lb_const_nil(m, original_type);
}
} else if (value_type == t_untyped_nil) {
return lb_const_nil(m, original_type);
}
GB_ASSERT_MSG(value_type != nullptr, "%s :: %s", type_to_string(original_type), exact_value_to_string(value));
// NOTE(korvahkh): forces calculation of variant_block_size
type_size_of(bt);
i64 block_size = bt->Union.variant_block_size;
while (are_types_identical(value_type, original_type)) {
if (value.kind == ExactValue_Compound) {
ast_node(cl, CompoundLit, value.value_compound);
if (cl->elems.count == 0) {
return lb_const_nil(m, original_type);
}
value_type = type_of_expr(value.value_compound);
if (!are_types_identical(value_type, original_type)) {
break;
}
GB_PANIC("%s --> %s vs %s",
expr_to_string(value.value_compound),
temp_canonical_string(value_type), temp_canonical_string(original_type));
} else if (value.kind == ExactValue_Invalid) {
return lb_const_nil(m, original_type);
}
GB_PANIC("(value.kind=%s) %s vs %s",
exact_value_kind_string[value.kind],
temp_canonical_string(value_type), temp_canonical_string(original_type));
}
// union_multiple_allow_compound:;
lbValue cv = lb_const_value(m, value_type, value, value_type, cc);
lbValue cv = lb_const_value(m, value_type, value, cc);
Type *variant_type = cv.type;
LLVMValueRef values[4] = {};
@@ -1015,7 +991,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
count = gb_max(cast(isize)cl->max_count, count);
Type *elem = base_type(type)->Slice.elem;
Type *t = alloc_type_array(elem, count);
lbValue backing_array = lb_const_value(m, t, value, nullptr, cc);
lbValue backing_array = lb_const_value(m, t, value, cc);
LLVMValueRef array_data = nullptr;
@@ -1154,7 +1130,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
value.kind != ExactValue_Invalid &&
value.kind != ExactValue_Compound) {
lb_const_array_spread(m, cc, type, value, &res, value_type);
lb_const_array_spread(m, cc, type, value, &res);
return res;
} else if (is_type_matrix(type) &&
value.kind != ExactValue_Invalid &&
@@ -1165,7 +1141,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
Type *elem = type->Matrix.elem;
lbValue single_elem = lb_const_value(m, elem, value, value_type, cc);
lbValue single_elem = lb_const_value(m, elem, value, cc);
single_elem.value = llvm_const_cast(m, single_elem.value, lb_type(m, elem), /*failure_*/nullptr);
i64 total_elem_count = matrix_type_total_internal_elems(type);
@@ -1187,7 +1163,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
i64 count = type->SimdVector.count;
Type *elem = type->SimdVector.elem;
lbValue single_elem = lb_const_value(m, elem, value, value_type, cc);
lbValue single_elem = lb_const_value(m, elem, value, cc);
single_elem.value = llvm_const_cast(m, single_elem.value, lb_type(m, elem), /*failure_*/nullptr);
LLVMValueRef *elems = gb_alloc_array(permanent_allocator(), LLVMValueRef, count);
@@ -1374,7 +1350,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
if (is_type_bit_field(original_type)) {
return lb_const_value_bit_field(m, original_type, value.value_compound);
} else if (is_type_slice(type)) {
return lb_const_value(m, type, value, value_type, cc);
return lb_const_value(m, type, value, cc);
} else if (is_type_soa_struct(type)) {
GB_ASSERT(type->kind == Type_Struct);
GB_ASSERT(type->Struct.soa_kind == StructSoa_Fixed);
@@ -1415,7 +1391,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
}
if (lo == i) {
TypeAndValue tav = fv->value->tav;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
for (i64 k = lo; k < hi; k++) {
aos_values[value_index++] = val;
}
@@ -1430,7 +1406,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
i64 index = exact_value_to_i64(index_tav.value);
if (index == i) {
TypeAndValue tav = fv->value->tav;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
aos_values[value_index++] = val;
found = true;
break;
@@ -1485,7 +1461,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
for (isize i = 0; i < elem_count; i++) {
TypeAndValue tav = cl->elems[i]->tav;
GB_ASSERT(tav.mode != Addressing_Invalid);
aos_values[i] = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(cl->elems[i]), cc).value;
aos_values[i] = lb_const_value(m, elem_type, tav.value, cc).value;
}
for (isize i = elem_count; i < type->Struct.soa_count; i++) {
aos_values[i] = nullptr;
@@ -1533,7 +1509,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
LLVMValueRef* values = gb_alloc_array(temporary_allocator(), LLVMValueRef, cast(isize)type->Array.count);
for (isize i = 0; i < type->Array.count; i++) {
values[i] = lb_const_value(m, elem_type, value, elem_type, cc).value;
values[i] = lb_const_value(m, elem_type, value, cc).value;
}
res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, cc);
@@ -1564,7 +1540,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
}
if (lo == i) {
TypeAndValue tav = fv->value->tav;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
for (i64 k = lo; k < hi; k++) {
values[value_index++] = val;
}
@@ -1579,7 +1555,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
i64 index = exact_value_to_i64(index_tav.value);
if (index == i) {
TypeAndValue tav = fv->value->tav;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
values[value_index++] = val;
found = true;
break;
@@ -1607,7 +1583,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
if (is_type_tuple(tav.type)) {
elem_index += tav.type->Tuple.variables.count;
} else {
values[elem_index++] = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(cl->elems[i]), cc).value;
values[elem_index++] = lb_const_value(m, elem_type, tav.value, cc).value;
}
}
for (isize i = 0; i < type->Array.count; i++) {
@@ -1656,7 +1632,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
}
if (lo == i) {
TypeAndValue tav = fv->value->tav;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
for (i64 k = lo; k < hi; k++) {
values[value_index++] = val;
}
@@ -1671,7 +1647,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
i64 index = exact_value_to_i64(index_tav.value);
if (index == i) {
TypeAndValue tav = fv->value->tav;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
values[value_index++] = val;
found = true;
break;
@@ -1699,7 +1675,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
if (is_type_tuple(tav.type)) {
elem_index += tav.type->Tuple.variables.count;
} else {
values[elem_index++] = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(cl->elems[i]), cc).value;
values[elem_index++] = lb_const_value(m, elem_type, tav.value, cc).value;
}
}
for (isize i = 0; i < type->EnumeratedArray.count; i++) {
@@ -1748,7 +1724,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
if (lo == i) {
TypeAndValue tav = fv->value->tav;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
for (i64 k = lo; k < hi; k++) {
values[value_index++] = val;
}
@@ -1766,7 +1742,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
if (index == i) {
TypeAndValue tav = fv->value->tav;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
values[value_index++] = val;
found = true;
break;
@@ -1790,7 +1766,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
LLVMValueRef* values = gb_alloc_array(temporary_allocator(), LLVMValueRef, cast(isize)capacity);
for (isize i = 0; i < capacity; i++) {
values[i] = lb_const_value(m, elem_type, value, elem_type, cc).value;
values[i] = lb_const_value(m, elem_type, value, cc).value;
}
res.value = lb_fill_fixed_capacity_dynamic_array(m, capacity, original_type, values, cc);
@@ -1808,7 +1784,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
if (is_type_tuple(tav.type)) {
elem_index += tav.type->Tuple.variables.count;
} else {
values[elem_index++] = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(cl->elems[i]), cc).value;
values[elem_index++] = lb_const_value(m, elem_type, tav.value, cc).value;
}
}
for (isize i = 0; i < capacity; i++) {
@@ -1856,7 +1832,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
}
if (lo == i) {
TypeAndValue tav = fv->value->tav;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
for (i64 k = lo; k < hi; k++) {
values[value_index++] = val;
}
@@ -1871,7 +1847,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
i64 index = exact_value_to_i64(index_tav.value);
if (index == i) {
TypeAndValue tav = fv->value->tav;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
values[value_index++] = val;
found = true;
break;
@@ -1890,7 +1866,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
for (isize i = 0; i < elem_count; i++) {
TypeAndValue tav = cl->elems[i]->tav;
GB_ASSERT(tav.mode != Addressing_Invalid);
values[i] = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(cl->elems[i]), cc).value;
values[i] = lb_const_value(m, elem_type, tav.value, cc).value;
}
LLVMTypeRef et = lb_type(m, elem_type);
@@ -1920,7 +1896,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
TypeAndValue tav = fv->value->tav;
if (tav.value.kind != ExactValue_Invalid) {
lbValue value = lb_const_value(m, f->type, tav.value, f->type, cc);
lbValue value = lb_const_value(m, f->type, tav.value, cc);
LLVMValueRef values[2];
unsigned value_count = 0;
@@ -1975,7 +1951,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
i32 index = field_remapping[f->Variable.field_index];
if (elem_type_can_be_constant(f->type)) {
if (sel.index.count == 1) {
lbValue value = lb_const_value(m, f->type, tav.value, lb_build_expr_original_const_type(fv->value), cc);
lbValue value = lb_const_value(m, f->type, tav.value, cc);
LLVMTypeRef value_type = LLVMTypeOf(value.value);
GB_ASSERT_MSG(lb_sizeof(value_type) == type_size_of(f->type), "%s vs %s", LLVMPrintTypeToString(value_type), type_to_string(f->type));
values[index] = value.value;
@@ -1984,7 +1960,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
if (!visited[index]) {
auto new_cc = cc;
new_cc.allow_local = false;
values[index] = lb_const_value(m, f->type, {}, nullptr, new_cc).value;
values[index] = lb_const_value(m, f->type, {}, new_cc).value;
visited[index] = true;
}
@@ -2023,7 +1999,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
}
}
if (is_constant) {
LLVMValueRef elem_value = lb_const_value(m, cv_type, tav.value, tav.type, cc).value;
LLVMValueRef elem_value = lb_const_value(m, cv_type, tav.value, cc).value;
if (LLVMIsConstant(elem_value) && LLVMIsConstant(values[index])) {
if (is_type_union(cv_type) || is_type_raw_union(cv_type)) {
force_non_named = true;
@@ -2085,7 +2061,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
if (elem_type_can_be_constant(f->type)) {
lbValue value = lb_const_value(m, f->type, tav.value, lb_build_expr_original_const_type(cl->elems[i]), cc);
lbValue value = lb_const_value(m, f->type, tav.value, cc);
LLVMTypeRef value_type = LLVMTypeOf(value.value);
isize lb_sizeof_value_type = lb_sizeof(value_type);
isize type_size_of_f_type = type_size_of(f->type);
@@ -2226,7 +2202,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
TypeAndValue tav = fv->value->tav;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
for (i64 k = lo; k < hi; k++) {
i64 offset = matrix_row_major_index_to_offset(type, k);
GB_ASSERT(values[offset] == nullptr);
@@ -2238,7 +2214,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
i64 index = exact_value_to_i64(index_tav.value);
GB_ASSERT(index < max_count);
TypeAndValue tav = fv->value->tav;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(fv->value), cc).value;
LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
i64 offset = matrix_row_major_index_to_offset(type, index);
GB_ASSERT(values[offset] == nullptr);
values[offset] = val;
@@ -2262,7 +2238,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty
GB_ASSERT(tav.mode != Addressing_Invalid);
i64 offset = 0;
offset = matrix_row_major_index_to_offset(type, i);
values[offset] = lb_const_value(m, elem_type, tav.value, lb_build_expr_original_const_type(cl->elems[i]), cc).value;
values[offset] = lb_const_value(m, elem_type, tav.value, cc).value;
}
for (isize i = 0; i < total_count; i++) {
if (values[i] == nullptr) {

View File

@@ -3207,7 +3207,7 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) {
Type *elem = base_array_type(dst);
lbValue e = lb_emit_conv(p, value, elem);
lbAddr v = lb_add_local_generated(p, t, false);
lbValue zero = lb_const_value(p->module, elem, exact_value_i64(0), elem, LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL);
lbValue zero = lb_const_value(p->module, elem, exact_value_i64(0), LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL);
for (i64 j = 0; j < dst->Matrix.column_count; j++) {
for (i64 i = 0; i < dst->Matrix.row_count; i++) {
lbValue ptr = lb_emit_matrix_epi(p, v.addr, i, j);
@@ -3244,7 +3244,7 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) {
lb_emit_store(p, d, s);
} else if (i == j) {
lbValue d = lb_emit_matrix_epi(p, v.addr, i, j);
lbValue s = lb_const_value(p->module, dst->Matrix.elem, exact_value_i64(1), dst->Matrix.elem, LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL);
lbValue s = lb_const_value(p->module, dst->Matrix.elem, exact_value_i64(1), LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL);
lb_emit_store(p, d, s);
}
}
@@ -3439,11 +3439,12 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left
if (are_types_identical(a, b)) {
// NOTE(bill): No need for a conversion
} else if ((lb_is_const(left) && !is_type_array(left.type)) || lb_is_const_nil(left)) {
} else if ((lb_is_const(left) && !is_type_array(left.type) && !is_type_union(left.type)) || lb_is_const_nil(left)) {
// NOTE(karl): !is_type_array(left.type) is there to avoid lb_emit_conv
// trying to convert a constant array into a non-array. In that case we
// want the `else` branch to happen, so it can try to convert the
// non-array into an array instead.
// NOTE(korvahkh): We also need !is_type_union(left.type).
if (lb_is_const_nil(left)) {
if (internal_check_is_assignable_to(right.type, left.type)) {
@@ -3454,7 +3455,7 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left
}
}
left = lb_emit_conv(p, left, right.type);
} else if ((lb_is_const(right) && !is_type_array(right.type)) || lb_is_const_nil(right)) {
} else if ((lb_is_const(right) && !is_type_array(right.type) && !is_type_union(right.type)) || lb_is_const_nil(right)) {
if (lb_is_const_nil(right)) {
if (internal_check_is_assignable_to(left.type, right.type)) {
@@ -4462,28 +4463,6 @@ gb_internal lbValue lb_build_expr(lbProcedure *p, Ast *expr) {
return res;
}
gb_internal Type *lb_build_expr_original_const_type(Ast *expr) {
expr = unparen_expr(expr);
Type *type = type_of_expr(expr);
if (is_type_union(type)) {
if (expr->kind == Ast_CallExpr) {
if (expr->CallExpr.proc->tav.mode == Addressing_Type) {
Type *res = lb_build_expr_original_const_type(expr->CallExpr.args[0]);
return res;
}
} else if (expr->kind == Ast_Ident || expr->kind == Ast_SelectorExpr) {
// a named constant carries the union as its type, so follow it to the declaration the
// checker resolved: `C :: U(3)` is the variant `int`, not `U`
Entity *e = entity_of_node(expr);
if (e != nullptr && e->kind == Entity_Constant &&
e->decl_info != nullptr && e->decl_info->init_expr != nullptr) {
return lb_build_expr_original_const_type(e->decl_info->init_expr);
}
}
}
return type_of_expr(expr);
}
gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) {
lbModule *m = p->module;
@@ -4496,9 +4475,8 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) {
if (tv.value.kind != ExactValue_Invalid) {
Type *original_type = lb_build_expr_original_const_type(expr);
// NOTE(bill): Short on constant values
return lb_const_value(p->module, type, tv.value, original_type, LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL);
return lb_const_value(p->module, type, tv.value, LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL);
} else if (tv.mode == Addressing_Type) {
// NOTE(bill, 2023-01-16): is this correct? I hope so at least
return lb_typeid(m, tv.type);
@@ -4579,7 +4557,7 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) {
TypeAndValue tav = type_and_value_of_expr(expr);
GB_ASSERT(tav.mode == Addressing_Constant);
return lb_const_value(p->module, type, tv.value, tv.type, LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL);
return lb_const_value(p->module, type, tv.value, LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL);
case_end;
case_ast_node(se, SelectorCallExpr, expr);
@@ -4862,7 +4840,7 @@ gb_internal lbAddr lb_build_addr_from_entity(lbProcedure *p, Entity *e, Ast *exp
GB_ASSERT(e != nullptr);
if (e->kind == Entity_Constant) {
Type *t = default_type(type_of_expr(expr));
lbValue v = lb_const_value(p->module, t, e->Constant.value, lb_build_expr_original_const_type(expr), LB_CONST_CONTEXT_DEFAULT_NO_LOCAL);
lbValue v = lb_const_value(p->module, t, e->Constant.value, LB_CONST_CONTEXT_DEFAULT_NO_LOCAL);
if (LLVMIsConstant(v.value)) {
lbAddr g = lb_add_global_generated_from_procedure(p, t, v);
return g;

View File

@@ -2434,7 +2434,7 @@ gb_internal void lb_build_static_variables(lbProcedure *p, AstValueDecl *vd) {
if (e->Variable.is_rodata) {
cc.is_rodata = true;
}
value = lb_const_value(p->module, ast_value->tav.type, ast_value->tav.value, nullptr, cc);
value = lb_const_value(p->module, ast_value->tav.type, ast_value->tav.value, cc);
}
String mangled_name = {};

View File

@@ -1951,6 +1951,13 @@ gb_internal Type *core_array_type(Type *t) {
}
}
gb_internal Type *core_broadcastable_elem_type(Type *t) {
while (is_type_array(t)) {
t = base_array_type(t);
}
return t;
}
gb_internal i32 type_math_rank(Type *t) {
i32 rank = 0;
for (;;) {
@@ -3609,6 +3616,23 @@ gb_internal Type *union_tag_type(Type *u) {
return t_uint;
}
gb_internal bool type_conversion_is_variant(Type *dst, Type *src) {
dst = base_type(core_broadcastable_elem_type(dst));
if (dst == nullptr) { return false; }
switch (dst->kind) {
case Type_Union:
if (union_is_variant_of(dst, src)) {
return true;
}
if (dst->Union.variants.count == 1) {
return type_conversion_is_variant(dst->Union.variants[0], src);
}
return false;
}
return false;
}
gb_internal int matched_target_features(TypeProc *t) {
if (t->require_target_feature.len == 0) {
return 0;

View File

@@ -52,3 +52,195 @@ union_constant_as_a_global_initializer :: proc(t: ^testing.T) {
if v, ok := G.(int); testing.expect(t, ok, "global lost its variant") { testing.expect_value(t, v, 3) }
if v, ok := GS.(string); testing.expect(t, ok, "string global lost its variant") { testing.expect_value(t, v, "hello") }
}
A :: union {B, bool}
B :: union {C, int }
C :: struct{}
@(test)
union_const_access :: proc(t: ^testing.T) {
X :: struct{x: A}{B(C{})}
testing.expect_value(t, X.x, A(B(C{})))
E :: enum {y}
Y : [E]A : {.y = true}
testing.expect_value(t, Y[.y], A(true))
}
@(test)
nested_union_implicit_cast :: proc(t: ^testing.T) {
Av: A: Bv
Bv: B: Cv
Cv: C: {}
testing.expect_value(t, Av, A(B(C{})))
}
@(test)
union_ternary :: proc(t: ^testing.T) {
E0 : A = B(C{}) if true else B(C{})
E1 : A = true if true else true
E2 := A(B(C{}) if true else B(C{}))
E3 := A(true if true else true)
testing.expect_value(t, E0, A(B(C{})))
testing.expect_value(t, E1, A(true))
testing.expect_value(t, E2, A(B(C{})))
testing.expect_value(t, E3, A(true))
}
@(test)
union_array :: proc(t: ^testing.T) {
C0: [2][2]A: B(C{})
C1: struct {x: [2][2]A} : {B(C{})}
testing.expect_value(t, C0, [2][2]A{0..<2 = B(C{})})
}
@(test)
union_multi_level_cast :: proc(t: ^testing.T) {
UI :: union {int, bool}
foo: Maybe(UI): 1
bar: union{UI}: 2
baz: UI: 3
qux: struct{ui: UI}: {4}
testing.expect_value(t, foo, 1)
testing.expect_value(t, bar, 2)
testing.expect_value(t, baz, 3)
testing.expect_value(t, qux.ui, 4)
}
@(test)
union_args :: proc(t: ^testing.T) {
implicit_bool :: proc(a: A = true ) -> A { return a.(bool) }
explicit_bool :: proc(a: A = A(true)) -> A { return a.(bool) }
nil_union :: proc(a: A = B{} ) -> A { return a.(B ) }
struct_union :: proc(a: A = B(C{}) ) -> A { return a.(B ) }
testing.expect_value(t, implicit_bool(), true)
testing.expect_value(t, explicit_bool(), true)
testing.expect_value(t, nil_union (), B{})
testing.expect_value(t, struct_union (), B(C{}))
testing.expect_value(t, implicit_bool(true ), true)
testing.expect_value(t, explicit_bool(A(true)), true)
testing.expect_value(t, nil_union (B{} ), B{})
testing.expect_value(t, struct_union (B(C{}) ), B(C{}))
testing.expect_value(t, implicit_bool(a=true ), true)
testing.expect_value(t, explicit_bool(a=A(true)), true)
testing.expect_value(t, nil_union (a=B{} ), B{})
testing.expect_value(t, struct_union (a=B(C{}) ), B(C{}))
}
@(test)
union_in_aggregates :: proc(t: ^testing.T) {
S :: struct {
x : union { int, string },
y : u128,
}
U :: struct #packed {v: [2]S, n: i64}
V :: struct {x: [dynamic; 2]S}
s := [dynamic; 2]S { {x=1} }
u := U { {{x=1}, {x=2}}, 2 }
v := V{x = { {x=1} }}
testing.expect_value(t, s [0], S{x=1})
testing.expect_value(t, u.v[1], S{x=2})
testing.expect_value(t, v.x[0], S{x=1})
}
@(test)
union_named_constants :: proc(t: ^testing.T) {
Inner_Left :: enum {
a,
b,
}
Inner_Right :: enum {
c,
d,
}
Inner :: union {
Inner_Left,
Inner_Right,
}
Outer :: union {
Inner,
int,
}
Atom :: struct {
token: Outer,
}
Promoted_Value :: union {
int,
f32,
string,
}
Promoted_Inner :: struct {
value: Promoted_Value,
padding0: int,
padding1: int,
}
Promoted_Outer :: struct {
using inner: Promoted_Inner,
}
NAMED_INNER :: Inner(Inner_Left.a)
DIRECT_ATOMS :: [?]Atom{{token = Inner(Inner_Left.a)}}
NAMED_ATOMS :: [?]Atom{{token = NAMED_INNER}}
INDEXED_OUTERS :: [1]Outer {
0 = Inner(Inner_Left.a),
}
RANGED_OUTERS :: [1]Outer {
0..=0 = Inner(Inner_Left.a),
}
Outer_Index :: enum {first}
ENUMERATED_OUTERS :: [Outer_Index]Outer {
.first = Inner(Inner_Left.a),
}
RANGED_ENUMERATED_OUTERS :: [Outer_Index]Outer {
.first..=.first = Inner(Inner_Left.a),
}
FIXED_CAPACITY_OUTERS :: [dynamic; 1]Outer{
0 = Inner(Inner_Left.a),
}
RANGED_FIXED_CAPACITY_OUTERS :: [dynamic; 1]Outer{
0..=0 = Inner(Inner_Left.a),
}
POSITIONAL_FIXED_CAPACITY_OUTERS :: [dynamic; 1]Outer{
Inner(Inner_Left.a),
}
testing.expect_value(t, DIRECT_ATOMS[0], Atom{token = Inner(.a)})
testing.expect_value(t, NAMED_ATOMS[0], Atom{token = NAMED_INNER})
testing.expect_value(t, INDEXED_OUTERS[0], Inner(.a))
testing.expect_value(t, RANGED_OUTERS[0], Inner(.a))
testing.expect_value(t, ENUMERATED_OUTERS[.first], Inner(.a))
testing.expect_value(t, RANGED_ENUMERATED_OUTERS[.first], Inner(.a))
fco := FIXED_CAPACITY_OUTERS
rfco := RANGED_FIXED_CAPACITY_OUTERS
testing.expect_value(t, fco[0], Inner(.a))
testing.expect_value(t, rfco[0], Inner(.a))
testing.expect_value(t, POSITIONAL_FIXED_CAPACITY_OUTERS[0], Inner(.a))
testing.expect_value(t, Promoted_Outer {
value = Promoted_Value(int(1)),
}, Promoted_Outer {
inner = {Promoted_Value(int(1)), 0, 0},
})
}