struct type construction

This commit is contained in:
2026-07-19 21:35:48 +02:00
parent 944648fce6
commit 59bbb197e0
15 changed files with 331415 additions and 253804 deletions
+304 -3
View File
@@ -1323,7 +1323,7 @@ ct_eval_expr :: proc(
}
return ct_eval_expr(state, expr.right, types.BOOL, depth+1)
case .Call:
return ct_eval_call_expr(state, expr, expected, depth+1)
return ct_eval_call_expr(state, expr, expected, depth+1, expr_id)
case .Cast:
target := type_from_syntax(checker, expr.type, state.pkg, state.file)
value, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
@@ -1531,6 +1531,14 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
if !has_default {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name)))
}
if field_default.static_value != INVALID_CT_VALUE {
value := ct_clone_graph(state, &checker.static_state, field_default.static_value)
if value == INVALID_CT_VALUE {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
values[index] = value
continue
}
previous_pkg, previous_file := state.pkg, state.file
previous_comptime := checker.current_comptime_values
state.pkg, state.file = field_default.pkg, field_default.file
@@ -1541,7 +1549,7 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
value, ok = ct_coerce_value(state, value, field.type, checker.ast_module.exprs[field_default.expr].span)
value, ok = ct_coerce_value(state, value, field.type, field_default.span)
if !ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
@@ -2612,7 +2620,277 @@ ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Spa
}), ct_flow(.Normal), true
}
ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
valid_generated_field_name :: proc(name: string) -> bool {
if len(name) == 0 {
return false
}
is_start := proc(value: byte) -> bool {
return value == '_' || value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z'
}
if !is_start(name[0]) {
return false
}
for value in transmute([]byte)name[1:] {
if !is_start(value) && !(value >= '0' && value <= '9') {
return false
}
}
return true
}
ct_collection_children :: proc(
state: ^Ct_State,
expr_id: ast.Expr_Id,
depth: int,
label: string,
) -> ([]Ct_Value_Id, Ct_Flow, bool) {
value, flow, ok := ct_eval_expr(state, expr_id, types.INVALID, depth+1)
if !ok || flow.kind != .Normal {
return nil, flow, ok
}
if value == INVALID_CT_VALUE || int(value) >= len(state.values) {
return nil, ct_flow(.Normal), false
}
item := state.values[value]
if item.kind != .Array && item.kind != .Struct {
return nil, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, state.checker.ast_module.exprs[expr_id].span,
"struct_type! %s must be a fixed array or tuple", label,
)
}
if item.kind == .Struct {
node, node_ok := types.node(&state.checker.module.types, item.type)
if !node_ok || !node.tuple {
return nil, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, state.checker.ast_module.exprs[expr_id].span,
"struct_type! %s must be a fixed array or tuple", label,
)
}
}
children := ct_child_slice(state, item)
result := make([]Ct_Value_Id, len(children), state.checker.allocator)
copy(result, children)
return result, ct_flow(.Normal), true
}
ct_literal_collection :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> ([]ast.Expr_Id, bool) {
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return nil, false
}
expr := checker.ast_module.exprs[expr_id]
if expr.kind == .Array || expr.kind == .Struct_Literal && expr.tuple {
return expr.args, true
}
return nil, false
}
ct_struct_type :: proc(
state: ^Ct_State,
expr_id: ast.Expr_Id,
expr: ast.Expr,
depth: int,
) -> (Ct_Value_Id, Ct_Flow, bool) {
checker := state.checker
if len(expr.args) != 4 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, expr.span,
"struct_type! expects 4 arguments, got %d", len(expr.args),
)
}
for entry in checker.generated_types {
if entry.expr == expr_id && comptime_values_equal(entry.values, checker.current_comptime_values) {
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(entry.result)}), ct_flow(.Normal), true
}
}
layout_expr := checker.ast_module.exprs[expr.args[0]]
c_layout := false
layout_ok := false
if layout_expr.kind == .Enum_Literal {
name := symbol_text(checker, layout_expr.name)
c_layout = name == "c"
layout_ok = name == "auto" || name == "c"
} else {
layout_value, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
if layout_value != INVALID_CT_VALUE && int(layout_value) < len(state.values) {
value := state.values[layout_value]
if value.kind == .Integer && types.is_enum(value.type, &checker.module.types) {
if name, found := enum_member_name_from_value(checker, value.type, value.integer); found {
c_layout = name == "c"
layout_ok = name == "auto" || name == "c"
}
}
}
}
if !layout_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, layout_expr.span,
"struct_type! layout must be .auto or .c",
)
}
name_values, name_flow, names_ok := ct_collection_children(state, expr.args[1], depth+1, "field names")
defer delete(name_values, checker.allocator)
if !names_ok || name_flow.kind != .Normal {
return INVALID_CT_VALUE, name_flow, names_ok
}
names := make([]string, len(name_values), checker.allocator)
defer delete(names, checker.allocator)
for value, index in name_values {
name, ok := ct_value_bytes(state, value)
if !ok || !valid_generated_field_name(name) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, checker.ast_module.exprs[expr.args[1]].span,
"struct_type! field names must be valid comptime immutable byte strings",
)
}
for previous in names[:index] {
if previous == name {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, checker.ast_module.exprs[expr.args[1]].span,
"duplicate struct_type! field name '%s'", name,
)
}
}
names[index] = name
}
field_types: []types.Type
if type_exprs, literal := ct_literal_collection(checker, expr.args[2]); literal {
field_types = make([]types.Type, len(type_exprs), checker.allocator)
for item, index in type_exprs {
field_types[index], _ = resolve_type_argument(checker, item, state.pkg, state.file)
}
} else {
type_values, flow, ok := ct_collection_children(state, expr.args[2], depth+1, "field types")
defer delete(type_values, checker.allocator)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
field_types = make([]types.Type, len(type_values), checker.allocator)
for value, index in type_values {
if value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type {
field_types[index] = types.Type(state.values[value].index)
}
}
}
defer delete(field_types, checker.allocator)
if len(names) != len(field_types) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, expr.span,
"struct_type! collection lengths differ: %d names and %d types", len(names), len(field_types),
)
}
if c_layout && len(names) == 0 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "c struct types require at least one field")
}
fields := make([]types.Field, len(names), checker.allocator)
defer delete(fields, checker.allocator)
for field_type, index in field_types {
valid := types.is_valid(field_type) && !types.is_void(field_type)
if c_layout {
valid = valid && types.is_runtime_value(field_type, &checker.module.types) &&
types.is_c_record_field_type(field_type, &checker.module.types)
} else {
valid = valid && is_runtime_type(checker, field_type)
}
if !valid {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, checker.ast_module.exprs[expr.args[2]].span,
"struct_type! field '%s' has invalid %s type %s",
names[index], "C-layout" if c_layout else "value", type_label(checker, field_type),
)
}
fields[index] = types.Field{name=u32(symbol.intern(checker.symbols, names[index])), type=field_type}
}
default_exprs, literal_defaults := ct_literal_collection(checker, expr.args[3])
default_values: []Ct_Value_Id
if !literal_defaults {
flow: Ct_Flow
ok: bool
default_values, flow, ok = ct_collection_children(state, expr.args[3], depth+1, "field defaults")
if !ok || flow.kind != .Normal {
delete(default_values, checker.allocator)
return INVALID_CT_VALUE, flow, ok
}
}
defer delete(default_values, checker.allocator)
default_count := len(default_exprs) if literal_defaults else len(default_values)
if default_count != len(fields) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, expr.span,
"struct_type! collection lengths differ: %d fields and %d defaults", len(fields), default_count,
)
}
persistent_defaults := make([]Ct_Value_Id, len(fields), checker.allocator)
for &value in persistent_defaults {
value = INVALID_CT_VALUE
}
for field, index in fields {
expected := types.optional(&checker.module.types, field.type)
value := INVALID_CT_VALUE
if literal_defaults {
flow: Ct_Flow
ok: bool
value, flow, ok = ct_eval_expr(state, default_exprs[index], expected, depth+1)
if !ok || flow.kind != .Normal {
delete(persistent_defaults, checker.allocator)
return INVALID_CT_VALUE, flow, ok
}
} else {
value = default_values[index]
}
if value == INVALID_CT_VALUE {
delete(persistent_defaults, checker.allocator)
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, expr.span,
"struct_type! field defaults contain an invalid comptime value",
)
}
coerced, ok := ct_coerce_value(state, value, expected, expr.span)
value = coerced
key := strings.builder_make(checker.allocator)
undefined := ct_value_contains_undefined(state, value)
key_ok := ok && ct_write_comptime_key(state, value, &key)
stable := ok && !undefined && key_ok
strings.builder_destroy(&key)
if !stable {
delete(persistent_defaults, checker.allocator)
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, expr.span,
"struct_type! default for field '%s' must have a stable comptime identity coercible to ?%s",
names[index], type_label(checker, field.type),
)
}
default := state.values[value]
if default.kind == .None {
continue
}
children := ct_child_slice(state, default)
if default.kind != .Optional_Some || len(children) != 1 {
delete(persistent_defaults, checker.allocator)
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
persistent_defaults[index] = ct_clone_graph(&checker.static_state, state, children[0])
}
result := types.struct_generated(&checker.module.types, fields, c_layout=c_layout)
append(&checker.generated_types, Generated_Type_Entry{
expr=expr_id,
values=clone_comptime_values(checker.current_comptime_values, checker.allocator),
result=result,
pkg=state.pkg,
file=state.file,
defaults=persistent_defaults,
})
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(result)}), ct_flow(.Normal), true
}
ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int, expr_id := ast.INVALID_EXPR) -> (Ct_Value_Id, Ct_Flow, bool) {
checker := state.checker
if expr.left != ast.INVALID_EXPR {
callee, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
@@ -2636,6 +2914,29 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
}
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, message)
}
if is_intrinsic_call(checker, expr, "some") {
if len(expr.args) != 1 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "some! expects 1 argument, got %d", len(expr.args))
}
if !types.is_optional(expected, &checker.module.types) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "some! requires an optional context")
}
child_type := types.child_type(expected, &checker.module.types)
child, flow, ok := ct_eval_expr(state, expr.args[0], child_type, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
child, ok = ct_coerce_value(state, child, child_type, checker.ast_module.exprs[expr.args[0]].span)
if !ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
start := u32(len(state.children))
append(&state.children, child)
return ct_add_value(state, Ct_Value{kind=.Optional_Some, type=expected, start=start, count=1}), ct_flow(.Normal), true
}
if is_intrinsic_call(checker, expr, "struct_type") {
return ct_struct_type(state, expr_id, expr, depth+1)
}
if is_intrinsic_call(checker, expr, "field") {
if len(expr.args) != 2 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "field! expects 2 arguments, got %d", len(expr.args))