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
+205 -22
View File
@@ -166,6 +166,15 @@ Generated_Type_Entry :: struct {
result: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
defaults: []Ct_Value_Id,
}
Resolved_Field_Default :: struct {
expr: ast.Expr_Id,
pkg: ast.Package_Id,
file: ast.File_Id,
static_value: Ct_Value_Id,
span: source.Span,
}
Type_Factory_Origin :: struct {
@@ -1227,6 +1236,9 @@ is_comptime_value_type :: proc(checker: ^Checker, value: types.Type, depth := 0)
if depth > 256 || !types.is_valid(value) {
return false
}
if is_type_metatype_syntax(checker, value) {
return true
}
if is_runtime_type(checker, value) {
return true
}
@@ -1930,12 +1942,15 @@ call_mapping_mode :: proc(function: ast.Function, mapping: []int) -> Call_Argume
return .Explicit
}
comptime_binding_index :: proc(function: ast.Function, _: int, name: symbol.Id) -> (int, bool) {
comptime_binding_index :: proc(function: ast.Function, prefix: int, name: symbol.Id) -> (int, bool) {
ordinal := 0
for param in function.params {
if !param.comptime_value {
continue
}
if ordinal >= prefix {
return -1, false
}
if param.name == name {
return ordinal, true
}
@@ -2299,6 +2314,7 @@ type_pattern_mentions_comptime :: proc(
prefix: int,
pattern: types.Type,
depth := 0,
type_params_only := false,
) -> bool {
if depth > 64 {
return false
@@ -2309,7 +2325,13 @@ type_pattern_mentions_comptime :: proc(
}
if item.qualifier == 0 && item.name != 0 {
if _, found := comptime_binding_index(function, prefix, symbol.Id(item.name)); found {
return true
if !type_params_only {
return true
}
param, param_ok := comptime_param_for_name(function, symbol.Id(item.name))
if param_ok && is_comptime_type_param(checker, param) {
return true
}
}
}
if item.kind == .Array && item.unresolved_count {
@@ -2318,7 +2340,13 @@ type_pattern_mentions_comptime :: proc(
expr := checker.ast_module.exprs[expr_id]
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
if _, found := comptime_binding_index(function, prefix, expr.name); found {
return true
if !type_params_only {
return true
}
param, param_ok := comptime_param_for_name(function, expr.name)
if param_ok && is_comptime_type_param(checker, param) {
return true
}
}
}
}
@@ -2333,21 +2361,27 @@ type_pattern_mentions_comptime :: proc(
arg := checker.ast_module.exprs[arg_id]
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
if _, found := comptime_binding_index(function, prefix, arg.name); found {
return true
if !type_params_only {
return true
}
param, param_ok := comptime_param_for_name(function, arg.name)
if param_ok && is_comptime_type_param(checker, param) {
return true
}
}
}
}
}
}
if types.is_valid(item.child) && type_pattern_mentions_comptime(checker, function, prefix, item.child, depth+1) {
if types.is_valid(item.child) && type_pattern_mentions_comptime(checker, function, prefix, item.child, depth+1, type_params_only) {
return true
}
if types.is_valid(item.extra) && type_pattern_mentions_comptime(checker, function, prefix, item.extra, depth+1) {
if types.is_valid(item.extra) && type_pattern_mentions_comptime(checker, function, prefix, item.extra, depth+1, type_params_only) {
return true
}
if item.kind == .Function {
for field in types.params_for(&checker.module.types, pattern) {
if type_pattern_mentions_comptime(checker, function, prefix, field.type, depth+1) {
if type_pattern_mentions_comptime(checker, function, prefix, field.type, depth+1, type_params_only) {
return true
}
}
@@ -2690,6 +2724,10 @@ infer_call_comptime_values :: proc(
) && matched
}
}
all_bound = true
for value_bound in bound {
all_bound = all_bound && value_bound
}
// Concrete arguments bind first. Numeric constants are contextual and therefore
// only contribute their default type after stronger evidence has had a chance.
weak_passes := [2]bool{false, true}
@@ -2709,6 +2747,14 @@ infer_call_comptime_values :: proc(
if !type_pattern_mentions_comptime(checker, function, prefix, function.params[param_index].type) {
continue
}
// Once the result type has fixed every comptime parameter, an anonymous
// keyed record must be checked against the specialized parameter type.
// Its provisional structural type intentionally contains only the supplied
// fields, so comparing that type here would reject omitted defaulted fields.
arg_expr := checker.ast_module.exprs[arg_id]
if all_bound && arg_expr.kind == .Struct_Literal && !arg_expr.tuple && !symbol.is_valid(arg_expr.name) {
continue
}
if weak {
if item, ok := types.node(&checker.module.types, function.params[param_index].type); ok &&
item.qualifier == 0 && item.name != 0 {
@@ -2951,6 +2997,19 @@ resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg:
source.add(checker.diagnostics, expr.span, "type position requires a direct type-factory call")
return types.INVALID
}
if expr.intrinsic {
if !is_intrinsic_call(checker, expr, "struct_type") {
return types.INVALID
}
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values)
value, flow, ok := ct_eval_call_expr(&state, expr, types.INVALID, 0, expr_id)
result := types.INVALID
if ok && flow.kind == .Normal && value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type {
result = types.Type(state.values[value].index)
}
ct_state_destroy(&state)
return result
}
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
_ = add_package_resolution_diagnostic(checker, expr, file)
@@ -3009,7 +3068,7 @@ resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg:
resolving=true,
})
state := ct_state_make(checker, pkg, file)
value, flow, eval_ok := ct_eval_call_expr(&state, expr, function.result, 0)
value, flow, eval_ok := ct_eval_call_expr(&state, expr, function.result, 0, expr_id)
result := types.INVALID
if eval_ok && flow.kind == .Normal && value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type {
result = types.Type(state.values[value].index)
@@ -3502,12 +3561,16 @@ validate_declarations :: proc(checker: ^Checker) {
signature_poisoned := function.diagnostic != source.INVALID_DIAGNOSTIC
locals: [dynamic]symbol.Id
locals.allocator = checker.allocator
comptime_prefix := 0
for param in function.params {
param_type := types.INVALID
if param.comptime_value || !has_comptime {
param_type = type_from_syntax(checker, param.type, function.pkg, function.file)
}
if param.comptime_value && !signature_poisoned {
dependent := type_pattern_mentions_comptime(
checker, function, comptime_prefix, param.type, type_params_only=true,
)
if function.c_abi {
checker.template_diagnostics[function_id] = source.add(
checker.diagnostics,
@@ -3516,7 +3579,7 @@ validate_declarations :: proc(checker: ^Checker) {
)
}
if !is_type_metatype_syntax(checker, param.type) &&
!is_comptime_value_type(checker, param_type) && param_type != types.RANGE {
!dependent && !is_comptime_value_type(checker, param_type) && param_type != types.RANGE {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
@@ -3538,6 +3601,9 @@ validate_declarations :: proc(checker: ^Checker) {
continue
}
}
if param.comptime_value {
comptime_prefix += 1
}
if param.type == types.VOID {
source.add(
checker.diagnostics,
@@ -4594,6 +4660,26 @@ infer_compound_expr :: proc(
}
return value
}
if !types.is_valid(value) {
fields := make([]types.Field, len(expr.args), checker.allocator)
defer delete(fields, checker.allocator)
valid := true
for keyed, index in expr.args {
keyed_expr := checker.ast_module.exprs[keyed]
fields[index].name = u32(keyed_expr.name)
if keyed_expr.left == ast.INVALID_EXPR {
valid = false
continue
}
for previous in fields[:index] {
valid = valid && previous.name != fields[index].name
}
fields[index].type = infer_nested_expr(
checker, keyed_expr.left, locals, pkg, file, demanded, local_types,
)
}
return types.struct_anonymous(store, fields) if valid else types.INVALID
}
fields := types.fields_for(store, value)
item, item_ok := types.node(store, value)
initialized := make([]bool, len(fields), checker.allocator)
@@ -4627,6 +4713,9 @@ infer_compound_expr :: proc(
continue
}
if field_default, default_values, ok := find_struct_field_default(checker, value, symbol.Id(field.name)); ok {
if field_default.static_value != INVALID_CT_VALUE {
continue
}
previous := checker.current_comptime_values
checker.current_comptime_values = default_values
_ = infer_nested_expr(
@@ -4893,6 +4982,17 @@ infer_expr :: proc(
_ = pop(&stack)
continue
}
if is_intrinsic_call(checker, expr, "some") {
if len(expr.args) == 1 && types.is_optional(frame.expected, &checker.module.types) {
child := types.child_type(frame.expected, &checker.module.types)
_ = infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types, child)
last = frame.expected
} else {
last = types.INVALID
}
_ = pop(&stack)
continue
}
if field_expr, handled := field_intrinsic_expr(checker, expr, pkg, file); handled {
if enum_type, ok := resolve_type_argument(checker, field_expr.left, pkg, file);
ok && types.is_enum(enum_type, &checker.module.types) {
@@ -6925,12 +7025,18 @@ find_struct_field_default :: proc(
checker: ^Checker,
struct_type: types.Type,
name: symbol.Id,
) -> (ast.Struct_Field_Default, []Comptime_Value, bool) {
) -> (Resolved_Field_Default, []Comptime_Value, bool) {
resolved := types.resolve_alias(struct_type, &checker.module.types)
for field_default in checker.ast_module.struct_field_defaults {
if types.resolve_alias(field_default.record, &checker.module.types) == resolved &&
field_default.field == name {
return field_default, nil, true
return Resolved_Field_Default{
expr=field_default.expr,
pkg=field_default.pkg,
file=field_default.file,
static_value=INVALID_CT_VALUE,
span=checker.ast_module.exprs[field_default.expr].span,
}, nil, true
}
}
for entry in checker.generated_types {
@@ -6941,16 +7047,30 @@ find_struct_field_default :: proc(
expr := checker.ast_module.exprs[entry.expr]
fields := types.fields_for(&checker.module.types, resolved)
for field, index in fields {
if field.name != u32(name) || index >= len(expr.args) || expr.args[index] == ast.INVALID_EXPR {
if field.name != u32(name) {
continue
}
return ast.Struct_Field_Default{
record=resolved,
field=name,
expr=expr.args[index],
pkg=entry.pkg,
file=entry.file,
}, entry.values, true
if index < len(entry.defaults) && entry.defaults[index] != INVALID_CT_VALUE {
return Resolved_Field_Default{
expr=ast.INVALID_EXPR,
pkg=entry.pkg,
file=entry.file,
static_value=entry.defaults[index],
span=expr.span,
}, entry.values, true
}
if is_intrinsic_call(checker, expr, "struct_type") {
continue
}
if index < len(expr.args) && expr.args[index] != ast.INVALID_EXPR {
return Resolved_Field_Default{
expr=expr.args[index],
pkg=entry.pkg,
file=entry.file,
static_value=INVALID_CT_VALUE,
span=checker.ast_module.exprs[expr.args[index]].span,
}, entry.values, true
}
}
}
return {}, nil, false
@@ -8083,10 +8203,11 @@ build_compound_expr :: proc(
return invalid_hir_expr(checker, expr.span, id)
}
} else if expr.tuple {
struct_type = types.INVALID
struct_type = types.resolve_alias(expected, store)
} else {
struct_type = types.resolve_alias(expected, store)
if !types.is_struct(struct_type, store) || types.is_opaque_struct(struct_type, store) {
if types.is_valid(struct_type) &&
(!types.is_struct(struct_type, store) || types.is_opaque_struct(struct_type, store)) {
id := source.add(checker.diagnostics, expr.span, "keyed contextual payload requires a struct payload")
return invalid_hir_expr(checker, expr.span, id, expected)
}
@@ -8131,6 +8252,34 @@ build_compound_expr :: proc(
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if !types.is_valid(struct_type) {
fields := make([]types.Field, len(expr.args), checker.allocator)
defer delete(fields, checker.allocator)
values := make([]hir.Expr_Id, len(expr.args), checker.allocator)
for keyed, index in expr.args {
keyed_expr := checker.ast_module.exprs[keyed]
fields[index].name = u32(keyed_expr.name)
for previous in fields[:index] {
if previous.name == fields[index].name {
source.addf(
checker.diagnostics, keyed_expr.span,
"duplicate initializer for struct field '%s'",
symbol_text(checker, keyed_expr.name),
)
}
}
values[index] = build_nested_expr(
checker, keyed_expr.left, locals, global_reads, calls, types.INVALID, pkg, file,
)
fields[index].type = checker.module.exprs[values[index]].type
}
struct_type = types.struct_anonymous(store, fields)
return add_hir_expr(checker, hir.Expr{
kind=.Struct, span=expr.span, type=struct_type, args=values,
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
fields := types.fields_for(store, struct_type)
union_record := types.is_union(struct_type, store)
if union_record && len(expr.args) != 1 {
@@ -8177,6 +8326,16 @@ build_compound_expr :: proc(
continue
}
if field_default, default_values, ok := find_struct_field_default(checker, struct_type, symbol.Id(field.name)); ok {
if field_default.static_value != INVALID_CT_VALUE {
if int(field_default.static_value) >= len(checker.static_state.values) {
continue
}
values[index] = build_static_value(
checker, checker.static_state.values[field_default.static_value], field_default.span, field.type,
)
values[index] = coerce_expr(checker, values[index], field.type, field_default.span)
continue
}
previous := checker.current_comptime_values
checker.current_comptime_values = default_values
values[index] = build_nested_expr(
@@ -8186,7 +8345,7 @@ build_compound_expr :: proc(
checker.current_comptime_values = previous
values[index] = coerce_expr(
checker, values[index], field.type,
checker.ast_module.exprs[field_default.expr].span,
field_default.span,
)
continue
}
@@ -8565,6 +8724,29 @@ build_expr :: proc(
_ = pop(&stack)
continue
}
if is_intrinsic_call(checker, expr, "some") {
if len(expr.args) != 1 {
id := source.addf(checker.diagnostics, expr.span, "some! expects 1 argument, got %d", len(expr.args))
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if !types.is_optional(frame.expected, &checker.module.types) {
id := source.add(checker.diagnostics, expr.span, "some! requires an optional context")
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
child_type := types.child_type(frame.expected, &checker.module.types)
child := build_nested_expr(checker, expr.args[0], locals, global_reads, calls, child_type, pkg, file)
child = coerce_expr(checker, child, child_type, checker.ast_module.exprs[expr.args[0]].span)
last = add_hir_expr(checker, hir.Expr{
kind=.Optional_Some, span=expr.span, type=frame.expected, left=child,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
_ = pop(&stack)
continue
}
if field_expr, handled := field_intrinsic_expr(checker, expr, pkg, file); handled {
if enum_type, ok := resolve_type_argument(checker, field_expr.left, pkg, file);
ok && types.is_enum(enum_type, &checker.module.types) {
@@ -13436,6 +13618,7 @@ check :: proc(
}
for entry in checker.generated_types {
delete(entry.values, allocator)
delete(entry.defaults, allocator)
}
for origin in checker.type_factory_origins {
delete(origin.values, allocator)