bare func declaration identities (comptime)

This commit is contained in:
2026-07-18 14:05:55 +02:00
parent 85693e57e1
commit e889a99e55
8 changed files with 512 additions and 74 deletions
+192 -39
View File
@@ -597,7 +597,8 @@ build_static_value :: proc(checker: ^Checker, value: Ct_Value, span: source.Span
})
}
if value.kind == .Function {
return build_function_value(checker, ast.Function_Id(u32(value.index)), span, expected)
value_expected := expected if types.is_valid(expected) else value.type
return build_function_value(checker, ast.Function_Id(u32(value.index)), span, value_expected)
}
if value.kind == .Array || value.kind == .Struct || value.kind == .Range {
children := ct_child_slice(&checker.static_state, value)
@@ -1174,6 +1175,38 @@ is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool {
return types.is_runtime_value(value, &checker.module.types)
}
is_comptime_value_type :: proc(checker: ^Checker, value: types.Type, depth := 0) -> bool {
if depth > 256 || !types.is_valid(value) {
return false
}
if is_runtime_type(checker, value) {
return true
}
item, ok := types.node(&checker.module.types, value)
if !ok {
return false
}
if item.kind == .Function {
return true
}
if item.kind == .Array || item.kind == .Optional || item.kind == .Alias || item.kind == .Distinct {
return is_comptime_value_type(checker, item.child, depth+1)
}
if item.kind == .Struct || item.kind == .Union {
if !item.declared || item.opaque || item.c_layout ||
(item.kind == .Union && !types.is_tagged_union(value, &checker.module.types)) {
return false
}
for field in types.fields_for(&checker.module.types, value) {
if !types.is_void(field.type) && !is_comptime_value_type(checker, field.type, depth+1) {
return false
}
}
return true
}
return false
}
is_undefined_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> bool {
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return false
@@ -2006,7 +2039,7 @@ call_mapping_semantically_valid :: proc(
break
}
}
if !is_runtime_type(checker, actual) {
if !is_runtime_type(checker, actual) && !can_implicitly_convert_type(checker, actual, declared) {
return false, fmt.aprintf(
"argument %d is not a runtime value", source_index+1,
allocator=checker.allocator,
@@ -2814,7 +2847,7 @@ resolve_generated_struct_type :: proc(checker: ^Checker, expr_id: ast.Expr_Id, p
defer delete(fields, checker.allocator)
for field, index in template_fields {
resolved := type_from_syntax(checker, field.type, pkg, file)
if !is_runtime_type(checker, resolved) || types.is_void(resolved) {
if (!is_runtime_type(checker, resolved) && !is_comptime_value_type(checker, resolved)) || types.is_void(resolved) {
if expr.tuple {
source.addf(checker.diagnostics, expr.span, "tuple element %d requires a concrete runtime type, got %s", index, type_label(checker, resolved))
} else {
@@ -3112,7 +3145,7 @@ function_value_signature :: proc(
return params, result, true
}
function_pointer_type_for_template :: proc(
function_type_for_template :: proc(
checker: ^Checker,
template: ast.Function_Id,
demanded: ^[dynamic]Spec_Id = nil,
@@ -3125,7 +3158,6 @@ function_pointer_type_for_template :: proc(
defer delete(params, checker.allocator)
function := checker.ast_module.functions[template]
function_type := types.function(&checker.module.types, params, result, function.c_abi, function.variadic)
pointer_type := types.pointer(&checker.module.types, function_type, false, false)
spec := INVALID_SPEC
if demanded == nil {
if demand_spec {
@@ -3137,7 +3169,40 @@ function_pointer_type_for_template :: proc(
spec = find_spec(checker, template, params)
mark_spec_demanded(checker, spec, demanded)
}
return pointer_type, spec, spec != INVALID_SPEC || !demand_spec
return function_type, spec, spec != INVALID_SPEC || !demand_spec
}
function_pointer_type_for_template :: proc(
checker: ^Checker,
template: ast.Function_Id,
demanded: ^[dynamic]Spec_Id = nil,
demand_spec := true,
) -> (types.Type, Spec_Id, bool) {
function_type, spec, ok := function_type_for_template(checker, template, demanded, demand_spec)
if !ok {
return types.INVALID, spec, false
}
return types.pointer(&checker.module.types, function_type, false, false), spec, true
}
function_expr_type_for_template :: proc(
checker: ^Checker,
template: ast.Function_Id,
expected: types.Type,
demanded: ^[dynamic]Spec_Id = nil,
) -> (types.Type, bool) {
function_type, _, ok := function_type_for_template(checker, template, demanded)
if !ok {
return types.INVALID, false
}
pointer_expected := expected
if types.is_optional(pointer_expected, &checker.module.types) {
pointer_expected = types.child_type(pointer_expected, &checker.module.types)
}
if types.can_coerce_function_pointer(function_type, pointer_expected, &checker.module.types) {
return pointer_expected, true
}
return function_type, true
}
contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
@@ -3358,7 +3423,7 @@ validate_declarations :: proc(checker: ^Checker) {
)
}
if !is_type_metatype_syntax(checker, param.type) &&
!is_runtime_type(checker, param_type) && param_type != types.RANGE {
!is_comptime_value_type(checker, param_type) && param_type != types.RANGE {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
@@ -3366,6 +3431,13 @@ validate_declarations :: proc(checker: ^Checker) {
symbol_text(checker, param.name),
)
}
} else if !has_comptime && !signature_poisoned && types.is_comptime_only(param_type, &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
"parameter '%s' has a comptime-only type; prefix it with '$'",
symbol_text(checker, param.name),
)
} else if !has_comptime && !signature_poisoned {
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, param_type);
diagnostic != source.INVALID_DIAGNOSTIC {
@@ -3904,7 +3976,14 @@ validate_type_nodes :: proc(checker: ^Checker) {
// not enter this state and retain the existing validation below.
} else if comptime_meta {
// Reflection metadata is compile-time-only and may contain `type`.
} else if !types.is_runtime_value(field.type, &checker.module.types) {
} else if item.c_layout && !types.is_runtime_value(field.type, &checker.module.types) {
source.add(
checker.diagnostics,
source.Span{},
"c_struct fields must have C-layout-compatible types",
)
} else if !types.is_runtime_value(field.type, &checker.module.types) &&
!is_comptime_value_type(checker, field.type) {
source.add(
checker.diagnostics,
source.Span{},
@@ -4505,8 +4584,8 @@ infer_expr :: proc(
_ = pop(&stack)
case .Function_Literal:
template := ast.Function_Id(u32(expr.integer))
pointer_type, _, ok := function_pointer_type_for_template(checker, template, demanded)
last = pointer_type if ok else types.INVALID
function_type, ok := function_expr_type_for_template(checker, template, frame.expected, demanded)
last = function_type if ok else types.INVALID
_ = pop(&stack)
case .Name:
last = types.INVALID
@@ -4614,9 +4693,9 @@ infer_expr :: proc(
if template != ast.INVALID_FUNCTION &&
len(checker.ast_module.functions[template].unsupported_reason) == 0 &&
checker.template_diagnostics[template] == source.INVALID_DIAGNOSTIC {
pointer_type, _, ok := function_pointer_type_for_template(checker, template, demanded)
function_type, ok := function_expr_type_for_template(checker, template, frame.expected, demanded)
if ok {
last = pointer_type
last = function_type
}
}
}
@@ -4631,7 +4710,7 @@ infer_expr :: proc(
case .Call:
if expr.left != ast.INVALID_EXPR {
callee_type := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
_, function_item, function_type, ok := types.callable_function(callee_type, &checker.module.types)
if !ok {
last = types.INVALID
_ = pop(&stack)
@@ -4732,7 +4811,7 @@ infer_expr :: proc(
continue
}
if callee_type, handled := infer_qualified_value_field_type(checker, expr, locals, pkg, file, demanded); handled {
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
_, function_item, function_type, ok := types.callable_function(callee_type, &checker.module.types)
if !ok {
last = types.INVALID
_ = pop(&stack)
@@ -4760,6 +4839,19 @@ infer_expr :: proc(
callee_type := types.INVALID
if !symbol.is_valid(expr.qualifier) {
callee_type = find_infer_local(locals, expr.name)
if !types.is_valid(callee_type) {
if binding, ok := current_static_binding(checker, expr.name); ok {
callee_type = binding.type
if binding.value != INVALID_CT_VALUE && int(binding.value) < len(checker.static_state.values) {
function_value := checker.static_state.values[binding.value]
if function_value.kind == .Function {
_, _, _ = function_pointer_type_for_template(
checker, ast.Function_Id(u32(function_value.index)), demanded,
)
}
}
}
}
if !types.is_valid(callee_type) {
if value, ok := current_comptime_value(checker, expr.name); ok && value.kind == .Static {
callee_type = value.type
@@ -4780,7 +4872,7 @@ infer_expr :: proc(
callee_type = checker.global_types[global]
}
}
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
_, function_item, function_type, ok := types.callable_function(callee_type, &checker.module.types)
if !ok {
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type)
@@ -6269,6 +6361,7 @@ can_implicitly_convert_type :: proc(checker: ^Checker, actual, expected: types.T
types.can_coerce_c_integer(actual, expected, checker.target) ||
types.can_coerce_c_scalar(actual, expected, checker.target) ||
types.can_weaken_pointer(actual, expected, store) ||
types.can_coerce_function_pointer(actual, expected, store) ||
types.can_weaken_slice(actual, expected, store) ||
types.can_decay_slice_c_string(actual, expected, store) ||
types.can_decay_array_pointer(actual, expected, store) ||
@@ -6297,6 +6390,13 @@ coerce_expr :: proc(
if types.equal(actual, expected) {
return expr_id
}
if types.can_coerce_function_pointer(actual, expected, &checker.module.types) {
expr := checker.module.exprs[expr_id]
if expr.kind == .Function {
expr.type = expected
return add_hir_expr(checker, expr)
}
}
if types.can_weaken_pointer(actual, expected, &checker.module.types) {
return add_hir_expr(checker, hir.Expr{
kind=.Weaken_Pointer,
@@ -6355,6 +6455,7 @@ coerce_expr :: proc(
if types.is_optional(expected, &checker.module.types) {
child := types.child_type(expected, &checker.module.types)
if types.equal(actual, child) ||
types.can_coerce_function_pointer(actual, child, &checker.module.types) ||
types.can_widen(actual, child) ||
types.can_coerce_c_integer(actual, child, checker.target) ||
types.can_coerce_c_scalar(actual, child, checker.target) ||
@@ -6933,14 +7034,16 @@ build_function_value :: proc(
}
defer delete(params, checker.allocator)
function_type := types.function(&checker.module.types, params, result, function.c_abi, function.variadic)
pointer_type := types.pointer(&checker.module.types, function_type, false, false)
value_type := function_type
expected_pointer := expected
if types.is_optional(expected_pointer, &checker.module.types) {
expected_pointer = types.child_type(expected_pointer, &checker.module.types)
}
if _, _, expected_function, ok := types.function_pointer(expected_pointer, &checker.module.types); ok &&
types.equal(expected_function, function_type) {
pointer_type = expected_pointer
value_type = expected_pointer
} else if types.equal(expected, function_type) {
value_type = expected
}
spec := find_spec(checker, template, params)
if spec == INVALID_SPEC {
@@ -6950,14 +7053,14 @@ build_function_value :: proc(
"could not resolve callback specialization of '%s'",
symbol_text(checker, function.name),
)
return invalid_hir_expr(checker, span, id, pointer_type)
return invalid_hir_expr(checker, span, id, value_type)
}
function_id := checker.specs[spec].hir_id
assert(function_id != hir.INVALID_FUNCTION)
return add_hir_expr(checker, hir.Expr{
kind=.Function,
span=span,
type=pointer_type,
type=value_type,
target=hir.function_ref(function_id),
left=hir.INVALID_EXPR,
right=hir.INVALID_EXPR,
@@ -7917,6 +8020,15 @@ build_expr :: proc(
}
expr := checker.ast_module.exprs[frame.expr]
if frame.stage == 0 {
if expr.kind == .Name || expr.kind == .Field || expr.kind == .Index || expr.kind == .Unwrap {
if specialized, ok := try_build_specialization_expr(
checker, frame.expr, frame.expected, pkg, file,
); ok {
last = specialized
_ = pop(&stack)
continue
}
}
constant := Constant{}
_, static_name := current_static_binding(checker, expr.name)
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
@@ -8257,12 +8369,20 @@ build_expr :: proc(
non_callable_global := false
if !symbol.is_valid(expr.qualifier) {
if local, ok := find_build_local(locals, expr.name); ok {
if _, _, _, callable := types.function_pointer(local.type, &checker.module.types); callable {
if _, _, _, callable := types.callable_function(local.type, &checker.module.types); callable {
callee = build_local_expr(checker, local, expr.span)
} else {
non_callable = true
}
}
if callee == hir.INVALID_EXPR && !non_callable {
if binding, ok := current_static_binding(checker, expr.name); ok &&
binding.value != INVALID_CT_VALUE && int(binding.value) < len(checker.static_state.values) {
callee = build_static_value(
checker, checker.static_state.values[binding.value], expr.span, binding.type,
)
}
}
if callee == hir.INVALID_EXPR && !non_callable {
if value, ok := current_comptime_value(checker, expr.name);
ok && value.kind == .Static && value.static_value != INVALID_CT_VALUE &&
@@ -8275,7 +8395,7 @@ build_expr :: proc(
}
if callee == hir.INVALID_EXPR && !non_callable {
if global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file)); global != ast.INVALID_GLOBAL {
if _, _, _, callable := types.function_pointer(checker.global_types[global], &checker.module.types); callable {
if _, _, _, callable := types.callable_function(checker.global_types[global], &checker.module.types); callable {
callee = build_global_reference(checker, global, expr.span, global_reads)
} else {
non_callable = true
@@ -8322,7 +8442,7 @@ build_expr :: proc(
id := source.INVALID_DIAGNOSTIC
if non_callable {
id = add_call_resolution_diagnostic(checker, expr, target_pkg, file) if non_callable_global else
source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
source.add(checker.diagnostics, expr.span, "call target is not callable")
} else {
id = add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
if id == source.INVALID_DIAGNOSTIC {
@@ -8333,10 +8453,10 @@ build_expr :: proc(
_ = pop(&stack)
continue
}
_, function_item, function_type, _ := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types)
_, function_item, function_type, _ := types.callable_function(checker.module.exprs[callee].type, &checker.module.types)
if !valid_callable_arity(function_item, len(expr.args)) {
message := "function pointer expects at least %d arguments, got %d" if function_item.variadic else
"function pointer expects %d arguments, got %d"
message := "function expects at least %d arguments, got %d" if function_item.variadic else
"function expects %d arguments, got %d"
id := source.addf(checker.diagnostics, expr.span, message, function_item.field_count, len(expr.args))
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
@@ -8705,16 +8825,16 @@ build_expr :: proc(
_ = pop(&stack)
continue
}
_, function_item, function_type, ok := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types)
_, function_item, function_type, ok := types.callable_function(checker.module.exprs[callee].type, &checker.module.types)
if !ok {
id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
id := source.add(checker.diagnostics, expr.span, "call target is not callable")
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if !valid_callable_arity(function_item, len(expr.args)) {
message := "function pointer expects at least %d arguments, got %d" if function_item.variadic else
"function pointer expects %d arguments, got %d"
message := "function expects at least %d arguments, got %d" if function_item.variadic else
"function expects %d arguments, got %d"
id := source.addf(checker.diagnostics, expr.span, message, function_item.field_count, len(expr.args))
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
@@ -8739,7 +8859,7 @@ build_expr :: proc(
if frame.arg_index+1 < len(expr.args) {
next := frame.arg_index+1
callee_type := checker.module.exprs[frame.left].type
_, function_item, function_type, _ := types.function_pointer(callee_type, &checker.module.types)
_, function_item, function_type, _ := types.callable_function(callee_type, &checker.module.types)
arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, next)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
@@ -8760,9 +8880,9 @@ build_expr :: proc(
continue
}
callee_type := checker.module.exprs[frame.left].type
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
_, function_item, function_type, ok := types.callable_function(callee_type, &checker.module.types)
if !ok {
id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
id := source.add(checker.diagnostics, expr.span, "call target is not callable")
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid_hir_expr(checker, expr.span, id)
@@ -8789,7 +8909,7 @@ build_expr :: proc(
}
result := function_item.child
if !types.is_valid(result) {
id := source.add(checker.diagnostics, expr.span, "could not resolve function pointer result type")
id := source.add(checker.diagnostics, expr.span, "could not resolve callable result type")
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid_hir_expr(checker, expr.span, id)
@@ -9524,6 +9644,21 @@ build_block :: proc(
value_type = types.INVALID
}
}
if types.is_comptime_only(value_type, &checker.module.types) {
id := source.addf(
checker.diagnostics,
statement.span,
"local '%s' has a comptime-only type and cannot be stored at runtime",
symbol_text(checker, statement.name),
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
continue
}
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found {
id := source.addf(
checker.diagnostics, statement.span,
@@ -12145,6 +12280,14 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
signature_diagnostic := source.INVALID_DIAGNOSTIC
unresolved_result := !types.is_void(spec.result) && !is_runtime_type(checker, spec.result)
if unresolved_result {
if types.is_comptime_only(spec.result, &checker.module.types) {
signature_diagnostic = source.addf(
checker.diagnostics,
function.span,
"function '%s' has a comptime-only result and cannot return it through the runtime ABI",
symbol_text(checker, function.name),
)
}
checker.specs[id].result = types.I64
spec.result = types.I64
}
@@ -12554,12 +12697,22 @@ build_globals :: proc(checker: ^Checker) {
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
}
if diagnostic == source.INVALID_DIAGNOSTIC && !is_runtime_type(checker, global_type) {
diagnostic = source.addf(
checker.diagnostics,
global.span,
"could not resolve a concrete type for global '%s'",
symbol_text(checker, global.name),
)
if types.is_comptime_only(global_type, &checker.module.types) ||
types.is_comptime_only(declared, &checker.module.types) {
diagnostic = source.addf(
checker.diagnostics,
global.span,
"global '%s' has a comptime-only type and cannot be stored at runtime",
symbol_text(checker, global.name),
)
} else {
diagnostic = source.addf(
checker.diagnostics,
global.span,
"could not resolve a concrete type for global '%s'",
symbol_text(checker, global.name),
)
}
global_type = types.I64
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
}