close some gaps in the type system

This commit is contained in:
2026-07-19 01:01:11 +02:00
parent c7e3162ecb
commit 95f90cc306
12 changed files with 367 additions and 78 deletions
+156 -57
View File
@@ -164,6 +164,8 @@ Generated_Type_Entry :: struct {
expr: ast.Expr_Id,
values: []Comptime_Value,
result: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
}
Type_Factory_Origin :: struct {
@@ -493,6 +495,10 @@ write_type_label :: proc(checker: ^Checker, builder: ^strings.Builder, value: ty
write_type_label(checker, builder, item.child)
strings.write_string(builder, " ! ")
write_type_label(checker, builder, item.extra)
case .Sum:
write_type_label(checker, builder, item.child)
strings.write_string(builder, " | ")
write_type_label(checker, builder, item.extra)
case .Type_Call:
strings.write_string(builder, "<type factory call>")
case .Struct:
@@ -1136,7 +1142,14 @@ type_from_syntax :: proc(
return types.INVALID
}
}
case .Pointer, .Slice, .Optional, .Range, .Distinct, .Enum, .Fallible:
return types.intern(store, item)
case .Pointer, .Slice, .Optional, .Range, .Fallible:
child := type_from_syntax(checker, item.child, pkg, file, depth+1)
extra := type_from_syntax(checker, item.extra, pkg, file, depth+1)
item.child = child
item.extra = extra
return types.intern(store, item)
case .Distinct, .Enum:
child := type_from_syntax(checker, item.child, pkg, file, depth+1)
extra := type_from_syntax(checker, item.extra, pkg, file, depth+1)
changed = child != item.child || extra != item.extra
@@ -1146,15 +1159,24 @@ type_from_syntax :: proc(
params := types.params_for(store, value)
resolved_params := make([]types.Type, len(params), checker.allocator)
defer delete(resolved_params, checker.allocator)
params_changed := false
for param, index in params {
resolved_params[index] = type_from_syntax(checker, param.type, pkg, file, depth+1)
params_changed = params_changed || resolved_params[index] != param.type
}
result := type_from_syntax(checker, item.child, pkg, file, depth+1)
if params_changed || result != item.child {
return types.function(store, resolved_params, result, item.c_abi, item.variadic)
return types.function(store, resolved_params, result, item.c_abi, item.variadic)
case .Sum:
left := type_from_syntax(checker, item.child, pkg, file, depth+1)
right := type_from_syntax(checker, item.extra, pkg, file, depth+1)
composed, compose_error := types.compose_sum(store, left, right)
if compose_error == .Unsupported {
source.add(checker.diagnostics, source.Span{}, "only native unbacked enums and tagged unions can be composed with '|'")
return types.INVALID
}
if compose_error == .Conflict {
source.add(checker.diagnostics, source.Span{}, "sum composition contains the same variant name with different payload types")
return types.INVALID
}
return composed
case .Type_Call:
return resolve_type_factory_call(checker, ast.Expr_Id(item.count_expr), pkg, file)
}
@@ -1176,6 +1198,31 @@ is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool {
return types.is_runtime_value(value, &checker.module.types)
}
type_contains_unresolved_named :: proc(checker: ^Checker, value: types.Type, depth := 0) -> bool {
if depth > 64 {
return true
}
item, ok := types.node(&checker.module.types, value)
if !ok {
return false
}
if item.kind == .Named && !item.declared {
return true
}
if type_contains_unresolved_named(checker, item.child, depth+1) ||
type_contains_unresolved_named(checker, item.extra, depth+1) {
return true
}
if item.kind == .Function {
for param in types.params_for(&checker.module.types, value) {
if type_contains_unresolved_named(checker, param.type, depth+1) {
return true
}
}
}
return false
}
is_comptime_value_type :: proc(checker: ^Checker, value: types.Type, depth := 0) -> bool {
if depth > 256 || !types.is_valid(value) {
return false
@@ -1960,6 +2007,9 @@ explicit_comptime_argument_valid :: proc(
if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) {
return eval_integer_constant_in_context(checker, arg, pkg, file).kind == .Value
}
if type_pattern_mentions_comptime(checker, function, comptime_param_count(function), param.type) {
return true
}
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
_, ok := eval_static_comptime_value(checker, param.name, arg, declared, pkg, file)
return ok
@@ -2324,11 +2374,14 @@ match_inferred_type_pattern :: proc(
pattern_item, pattern_ok := types.node(store, pattern)
if pattern_ok && pattern_item.qualifier == 0 && pattern_item.name != 0 {
name := symbol.Id(pattern_item.name)
if _, is_binding := comptime_binding_index(function, prefix, name); is_binding {
if binding_index, is_binding := comptime_binding_index(function, prefix, name); is_binding {
param, param_ok := comptime_param_for_name(function, name)
if !param_ok || !is_comptime_type_param(checker, param) {
return false
}
if bound[binding_index] && can_implicitly_convert_type(checker, actual_type, values[binding_index].type) {
return true
}
return bind_inferred_comptime(
checker, function, prefix, values, bound, name,
Comptime_Value{type=actual_type, kind=.Type}, span, diagnose,
@@ -2584,7 +2637,6 @@ infer_call_comptime_values :: proc(
values[binding_index] = Comptime_Value{name=param.name, type=declared, value=constant.value, kind=.Integer}
bound[binding_index] = true
} else {
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
available: [dynamic]Comptime_Value
available.allocator = checker.allocator
append(&available, ..checker.current_comptime_values)
@@ -2593,6 +2645,10 @@ infer_call_comptime_values :: proc(
append(&available, prior)
}
}
previous := checker.current_comptime_values
checker.current_comptime_values = available[:]
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
checker.current_comptime_values = previous
value, value_ok := eval_static_comptime_value(
checker, param.name, arg_id, declared, pkg, file, available[:], diagnose,
)
@@ -2880,6 +2936,8 @@ resolve_generated_struct_type :: proc(checker: ^Checker, expr_id: ast.Expr_Id, p
expr=expr_id,
values=clone_comptime_values(checker.current_comptime_values, checker.allocator),
result=result,
pkg=pkg,
file=file,
})
return result
}
@@ -2961,32 +3019,40 @@ resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg:
ct_state_destroy(&state)
checker.type_factories[entry_index].result = result
checker.type_factories[entry_index].resolving = false
if types.is_valid(result) {
generated := false
for entry in checker.generated_types {
if types.equal(entry.result, result) {
generated = true
break
}
}
if generated {
has_origin := false
for origin in checker.type_factory_origins {
if types.equal(origin.result, result) {
has_origin = true
break
}
}
if !has_origin {
append(&checker.type_factory_origins, Type_Factory_Origin{
result=result,
template=template,
values=clone_comptime_values(values, checker.allocator),
})
}
record_type_factory_origin(checker, result, template, values)
return result
}
record_type_factory_origin :: proc(
checker: ^Checker,
result: types.Type,
template: ast.Function_Id,
values: []Comptime_Value,
) {
if !types.is_valid(result) {
return
}
generated := false
for entry in checker.generated_types {
if types.equal(entry.result, result) {
generated = true
break
}
}
return result
if !generated {
return
}
for origin in checker.type_factory_origins {
if origin.template == template && types.equal(origin.result, result) &&
comptime_values_equal(origin.values, values) {
return
}
}
append(&checker.type_factory_origins, Type_Factory_Origin{
result=result,
template=template,
values=clone_comptime_values(values, checker.allocator),
})
}
collect_comptime_values :: proc(
@@ -3056,7 +3122,15 @@ collect_comptime_values :: proc(
})
continue
}
resolution_values: [dynamic]Comptime_Value
resolution_values.allocator = checker.allocator
append(&resolution_values, ..extra_values)
append(&resolution_values, ..values[:])
previous := checker.current_comptime_values
checker.current_comptime_values = resolution_values[:]
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
checker.current_comptime_values = previous
delete(resolution_values)
if types.is_concrete_integer(declared) {
constant := Constant{kind = .Not_Constant}
if index < len(args) {
@@ -4037,6 +4111,13 @@ validate_type_nodes :: proc(checker: ^Checker) {
}
}
if item.kind == .Function {
unresolved := type_contains_unresolved_named(checker, item.child)
for param in types.params_for(&checker.module.types, id) {
unresolved = unresolved || type_contains_unresolved_named(checker, param.type)
}
if unresolved {
continue
}
if item.c_abi {
if types.kind(item.child, &checker.module.types) == .Fallible {
source.add(checker.diagnostics, source.Span{}, "c_func pointer results cannot be fallible")
@@ -4545,11 +4626,14 @@ infer_compound_expr :: proc(
if initialized[index] {
continue
}
if field_default, ok := find_struct_field_default(checker, value, symbol.Id(field.name)); ok {
if field_default, default_values, ok := find_struct_field_default(checker, value, symbol.Id(field.name)); ok {
previous := checker.current_comptime_values
checker.current_comptime_values = default_values
_ = infer_nested_expr(
checker, field_default.expr, nil, field_default.pkg, field_default.file,
demanded, expected=field.type,
)
checker.current_comptime_values = previous
}
}
}
@@ -6841,15 +6925,35 @@ find_struct_field_default :: proc(
checker: ^Checker,
struct_type: types.Type,
name: symbol.Id,
) -> (ast.Struct_Field_Default, bool) {
) -> (ast.Struct_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, true
return field_default, nil, true
}
}
return {}, false
for entry in checker.generated_types {
if !types.equal(entry.result, resolved) || entry.expr == ast.INVALID_EXPR ||
int(entry.expr) >= len(checker.ast_module.exprs) {
continue
}
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 {
continue
}
return ast.Struct_Field_Default{
record=resolved,
field=name,
expr=expr.args[index],
pkg=entry.pkg,
file=entry.file,
}, entry.values, true
}
}
return {}, nil, false
}
find_tuple_field :: proc(checker: ^Checker, tuple_type: types.Type, index: u64) -> (int, types.Field, bool) {
@@ -7656,10 +7760,6 @@ build_compound_expr :: proc(
id := source.add(checker.diagnostics, expr.span, "'try' requires an enclosing fallible function")
return invalid_hir_expr(checker, expr.span, id, success)
}
if !types.equal(success, enclosing_success) {
id := source.add(checker.diagnostics, expr.span, "'try' success type must match the enclosing fallible result")
return invalid_hir_expr(checker, expr.span, id, success)
}
error_type := types.fallible_error(channel_type, store)
if !types.equal(error_type, enclosing_error) &&
!types.can_sum_widen(error_type, enclosing_error, store) {
@@ -8076,11 +8176,14 @@ build_compound_expr :: proc(
if values[index] != hir.INVALID_EXPR {
continue
}
if field_default, ok := find_struct_field_default(checker, struct_type, symbol.Id(field.name)); ok {
if field_default, default_values, ok := find_struct_field_default(checker, struct_type, symbol.Id(field.name)); ok {
previous := checker.current_comptime_values
checker.current_comptime_values = default_values
values[index] = build_nested_expr(
checker, field_default.expr, nil, global_reads, calls,
field.type, field_default.pkg, field_default.file,
)
checker.current_comptime_values = previous
values[index] = coerce_expr(
checker, values[index], field.type,
checker.ast_module.exprs[field_default.expr].span,
@@ -11385,6 +11488,18 @@ emit_value_branch :: proc(
) -> bool {
checker := ctx.checker
n := len(branch_stmts)
if n == 1 && checker.ast_module.statements[branch_stmts[0]].kind == .Expression {
expr_stmt := checker.ast_module.statements[branch_stmts[0]]
expected := slot_type^ if slot^ != hir.INVALID_LOCAL else types.INVALID
value := build_expr(checker, expr_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, expected, ctx.pkg, ctx.file)
if checker.module.exprs[value].kind == .Invalid {
ctx.problematic^ = true
return false
}
value = adopt_value_slot(ctx, slot, slot_type, value, checker.module.exprs[value].type, span)
emit_slot_assign(checker, out, slot^, value, span)
return true
}
ends_in_yield := n > 0 &&
checker.ast_module.statements[branch_stmts[n - 1]].kind == .Yield &&
!symbol.is_valid(checker.ast_module.statements[branch_stmts[n - 1]].label)
@@ -11992,9 +12107,7 @@ build_match_arm_body :: proc(
return result[:], body_ok
}
// build_value_arm appends a value-match arm's slot assignment(s) to `out`: a single bare
// expression yields implicitly; anything else reuses the value-branch rule (trailing
// `yield`, or exit on every path).
// build_value_arm appends a value-match arm's slot assignment(s) to `out`.
build_value_arm :: proc(
ctx: ^Build_Ctx,
out: ^[dynamic]hir.Stmt_Id,
@@ -12003,20 +12116,6 @@ build_value_arm :: proc(
slot_type: ^types.Type,
span: source.Span,
) -> bool {
checker := ctx.checker
if len(arm_body) == 1 && checker.ast_module.statements[arm_body[0]].kind == .Expression {
expr_stmt := checker.ast_module.statements[arm_body[0]]
expected := slot_type^ if slot^ != hir.INVALID_LOCAL else types.INVALID
value := build_expr(checker, expr_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, expected, ctx.pkg, ctx.file)
if checker.module.exprs[value].kind == .Invalid {
ctx.problematic^ = true
return false
}
vtype := checker.module.exprs[value].type
value = adopt_value_slot(ctx, slot, slot_type, value, vtype, span)
emit_slot_assign(checker, out, slot^, value, span)
return true
}
return emit_value_branch(ctx, out, arm_body, slot, slot_type, span)
}