close some gaps in the type system
This commit is contained in:
+156
-57
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -625,14 +625,19 @@ ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type,
|
||||
}
|
||||
if value.kind == .Function {
|
||||
actual_item, actual_ok := types.node(store, value.type)
|
||||
_, _, expected_function, expected_ok := types.function_pointer(expected, store)
|
||||
_, _, expected_function, expected_pointer := types.function_pointer(expected, store)
|
||||
expected_ok := expected_pointer
|
||||
if expected_item, ok := types.node(store, expected); ok && expected_item.kind == .Function {
|
||||
expected_function = expected
|
||||
expected_ok = true
|
||||
}
|
||||
if actual_ok && actual_item.kind == .Function && expected_ok &&
|
||||
types.equal(value.type, expected_function) {
|
||||
value.type = expected
|
||||
return ct_add_value(state, value), true
|
||||
}
|
||||
_, _, actual_function, actual_pointer := types.function_pointer(value.type, store)
|
||||
if actual_pointer && expected_ok && types.equal(actual_function, expected_function) {
|
||||
if actual_pointer && expected_pointer && types.equal(actual_function, expected_function) {
|
||||
value.type = expected
|
||||
return ct_add_value(state, value), true
|
||||
}
|
||||
@@ -1522,14 +1527,17 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
|
||||
if values[index] != INVALID_CT_VALUE {
|
||||
continue
|
||||
}
|
||||
field_default, has_default := find_struct_field_default(checker, struct_type, symbol.Id(field.name))
|
||||
field_default, default_values, has_default := find_struct_field_default(checker, struct_type, symbol.Id(field.name))
|
||||
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)))
|
||||
}
|
||||
previous_pkg, previous_file := state.pkg, state.file
|
||||
previous_comptime := checker.current_comptime_values
|
||||
state.pkg, state.file = field_default.pkg, field_default.file
|
||||
checker.current_comptime_values = default_values
|
||||
value, flow, ok := ct_eval_expr(state, field_default.expr, field.type, depth+1)
|
||||
state.pkg, state.file = previous_pkg, previous_file
|
||||
checker.current_comptime_values = previous_comptime
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
@@ -2102,6 +2110,16 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
left := state.values[left_id]
|
||||
right := state.values[right_id]
|
||||
is_compare := op == .Eq || op == .Ne || op == .Lt || op == .Le || op == .Gt || op == .Ge
|
||||
if left.kind == .Type || right.kind == .Type {
|
||||
if left.kind != .Type || right.kind != .Type || (op != .Eq && op != .Ne) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "type values only support '==' and '!=' with another type")
|
||||
}
|
||||
ok := types.equal(types.Type(left.index), types.Type(right.index))
|
||||
if op == .Ne {
|
||||
ok = !ok
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
|
||||
}
|
||||
if left.kind == .Bool && right.kind == .Bool {
|
||||
if op != .Eq && op != .Ne {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "bool values only support '==' and '!='")
|
||||
@@ -2897,6 +2915,12 @@ ct_eval_template_call :: proc(
|
||||
if ct_value_references_dead_storage(state, result) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime function returned a pointer to expired storage")
|
||||
}
|
||||
if is_type_metatype_syntax(checker, function.result) && result != INVALID_CT_VALUE &&
|
||||
int(result) < len(state.values) && state.values[result].kind == .Type {
|
||||
record_type_factory_origin(
|
||||
checker, types.Type(state.values[result].index), template, comptime_values,
|
||||
)
|
||||
}
|
||||
if types.is_valid(expected) {
|
||||
return ct_coerce_expr_value(state, result, expected, span)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user