fix comptime specialization, implement io.print

This commit is contained in:
2026-07-15 08:24:50 +02:00
parent 0b2055d64b
commit 1165cfb7c0
7 changed files with 733 additions and 155 deletions
+407 -93
View File
@@ -59,6 +59,11 @@ Static_Binding :: struct {
value: Ct_Value_Id,
}
Inline_Expansion :: struct {
statement: ast.Stmt_Id,
index: u32,
}
Entry_Point_Kind :: enum u8 {
Invalid,
Plain,
@@ -167,6 +172,15 @@ Type_Factory_Origin :: struct {
values: []Comptime_Value,
}
Call_Resolution :: struct {
expr: ast.Expr_Id,
ctx: []Comptime_Value,
inline_ctx: []Inline_Expansion,
mapping: []int,
comptime_values: []Comptime_Value,
runtime_types: []types.Type,
}
Checker :: struct {
ast_module: ^ast.Module,
diagnostics: ^source.Diagnostics,
@@ -218,9 +232,11 @@ Checker :: struct {
current_comptime_values: []Comptime_Value,
static_state: Ct_State,
static_bindings: [dynamic]Static_Binding,
inline_context: [dynamic]Inline_Expansion,
type_factories: [dynamic]Type_Factory_Entry,
generated_types: [dynamic]Generated_Type_Entry,
type_factory_origins: [dynamic]Type_Factory_Origin,
call_resolutions: [dynamic]Call_Resolution,
target: target.Target,
allocator: mem.Allocator,
}
@@ -289,6 +305,86 @@ build_local_expr :: proc(checker: ^Checker, local: Build_Local, span: source.Spa
})
}
block_reads_name :: proc(checker: ^Checker, statements: []ast.Stmt_Id, name: symbol.Id) -> bool {
// Imported C expressions can be deeply nested, so walk the source graph iteratively.
statement_stack: [dynamic]ast.Stmt_Id
statement_stack.allocator = checker.allocator
defer delete(statement_stack)
expr_stack: [dynamic]ast.Expr_Id
expr_stack.allocator = checker.allocator
defer delete(expr_stack)
append(&statement_stack, ..statements)
for len(statement_stack) > 0 || len(expr_stack) > 0 {
if len(statement_stack) > 0 {
statement_id := pop(&statement_stack)
if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) {
continue
}
statement := checker.ast_module.statements[statement_id]
#partial switch statement.kind {
case .Declaration, .Assignment, .Return, .Expression, .Yield:
append(&expr_stack, statement.expr, statement.target)
append(&statement_stack, ..statement.body)
case .If:
append(&expr_stack, statement.expr, statement.guard)
append(&statement_stack, ..statement.body)
append(&statement_stack, ..statement.else_body)
case .While:
append(&expr_stack, statement.expr)
append(&statement_stack, ..statement.body)
if statement.update != ast.INVALID_STMT {
append(&statement_stack, statement.update)
}
case .For:
append(&expr_stack, statement.expr)
append(&statement_stack, ..statement.body)
case .Block:
append(&statement_stack, ..statement.body)
case .Defer:
if statement.update != ast.INVALID_STMT {
append(&statement_stack, statement.update)
}
case .Match, .Match_Arm:
append(&expr_stack, statement.expr)
append(&expr_stack, ..statement.patterns)
append(&statement_stack, ..statement.body)
case .Break, .Continue, .Invalid:
}
continue
}
expr_id := pop(&expr_stack)
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
continue
}
expr := checker.ast_module.exprs[expr_id]
if expr.kind == .Name || expr.kind == .Call {
if !symbol.is_valid(expr.qualifier) && expr.name == name || expr.qualifier == name {
return true
}
}
switch expr.kind {
case .Call, .Array, .Struct_Literal, .Slice:
append(&expr_stack, ..expr.args)
append(&expr_stack, expr.left)
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
append(&expr_stack, expr.left)
case .Comptime:
append(&expr_stack, expr.left)
append(&statement_stack, ..expr.body)
case .Catch:
append(&expr_stack, expr.left, expr.right)
append(&statement_stack, ..expr.body)
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
append(&expr_stack, expr.left, expr.right)
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Inference_Hole,
.Type, .Name, .Function_Literal, .Anonymous_Struct_Type:
}
}
return false
}
record_unused_locals :: proc(
checker: ^Checker,
locals: []hir.Local,
@@ -1520,6 +1616,18 @@ next_runtime_call_arg :: proc(function: ast.Function, mapping: []int, start, sou
return index
}
call_mapping_mode :: proc(function: ast.Function, mapping: []int) -> Call_Argument_Mode {
if len(mapping) != len(function.params) {
return .Inferred
}
for value, index in mapping {
if value != index {
return .Inferred
}
}
return .Explicit
}
comptime_binding_index :: proc(function: ast.Function, _: int, name: symbol.Id) -> (int, bool) {
ordinal := 0
for param in function.params {
@@ -1600,29 +1708,6 @@ explicit_comptime_argument_valid :: proc(
return false
}
mapping_explicit_arguments_valid :: proc(
checker: ^Checker,
function: ast.Function,
mapping: []int,
args: []ast.Expr_Id,
pkg: ast.Package_Id,
file: ast.File_Id,
) -> bool {
for source_index in 0..<len(args) {
param_index := call_param_index(mapping, source_index)
if param_index < 0 || param_index >= len(function.params) ||
!function.params[param_index].comptime_value {
continue
}
if !explicit_comptime_argument_valid(
checker, function, function.params[param_index], args[source_index], pkg, file,
) {
return false
}
}
return true
}
search_call_mappings :: proc(search: ^Call_Mapping_Search, param_index, source_index: int) {
if len(search.candidates^) >= COMPTIME_EVAL_QUOTA {
return
@@ -1664,7 +1749,7 @@ call_mapping_semantically_valid :: proc(
locals: []Infer_Local,
pkg: ast.Package_Id,
file: ast.File_Id,
) -> bool {
) -> (bool, string) {
actual_args := make([]types.Type, len(function.params), checker.allocator)
defer delete(actual_args, checker.allocator)
for source_index in 0..<len(args) {
@@ -1676,20 +1761,29 @@ call_mapping_semantically_valid :: proc(
if !explicit_comptime_argument_valid(
checker, function, function.params[param_index], args[source_index], pkg, file,
) {
return false
return false, fmt.aprintf(
"argument %d is not a valid comptime value for parameter '%s'",
source_index+1, symbol_text(checker, function.params[param_index].name),
allocator=checker.allocator,
)
}
continue
}
actual_args[param_index] = infer_expr(checker, args[source_index], locals, pkg, file)
}
inference_failure := ""
values, ok := infer_call_comptime_values(
checker, function, comptime_param_count(function), mapping, args, actual_args,
expected, pkg, file, diagnose=false,
expected, pkg, file, diagnose=false, failure=&inference_failure,
)
defer delete(values, checker.allocator)
if !ok {
return false
if len(inference_failure) > 0 {
return false, inference_failure
}
return false, fmt.aprintf("could not infer or evaluate every comptime parameter", allocator=checker.allocator)
}
delete(inference_failure, checker.allocator)
previous := checker.current_comptime_values
checker.current_comptime_values = values
defer checker.current_comptime_values = previous
@@ -1699,35 +1793,50 @@ call_mapping_semantically_valid :: proc(
}
actual := actual_args[index]
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
source_index := -1
for mapped_param, candidate_source in mapping {
if mapped_param == index {
source_index = candidate_source
break
}
}
if !is_runtime_type(checker, actual) {
return false
return false, fmt.aprintf(
"argument %d is not a runtime value", source_index+1,
allocator=checker.allocator,
)
}
if types.is_constraint(declared) {
if !types.is_valid(types.constraint_target(declared, actual, &checker.module.types)) {
return false
return false, fmt.aprintf(
"argument %d of type %s does not satisfy %s",
source_index+1, type_label(checker, actual), type_label(checker, declared),
allocator=checker.allocator,
)
}
} else if !can_implicitly_convert_type(checker, actual, declared) {
source_index := -1
for mapped_param, candidate_source in mapping {
if mapped_param == index {
source_index = candidate_source
break
}
}
if source_index < 0 ||
!is_numeric_constant_expr(checker, args[source_index]) ||
!expr_accepts_numeric_demand(checker, args[source_index], declared, locals, pkg, file) {
return false
return false, fmt.aprintf(
"argument %d of type %s cannot convert to %s",
source_index+1, type_label(checker, actual), type_label(checker, declared),
allocator=checker.allocator,
)
}
}
}
if is_runtime_type(checker, expected) {
result := function_channel_type(checker, function)
if !is_runtime_type(checker, result) || !can_implicitly_convert_type(checker, result, expected) {
return false
return false, fmt.aprintf(
"result type %s cannot convert to expected type %s",
type_label(checker, result), type_label(checker, expected),
allocator=checker.allocator,
)
}
}
return true
return true, ""
}
call_argument_mapping :: proc(
@@ -1738,16 +1847,16 @@ call_argument_mapping :: proc(
file: ast.File_Id,
expected := types.INVALID,
locals: []Infer_Local = nil,
) -> (mapping: []int, mode: Call_Argument_Mode, comptime_count: int) {
) -> (mapping: []int, mode: Call_Argument_Mode, comptime_count: int, failure: string) {
if function.variadic || function.c_abi {
if !valid_call_arity(function^, len(args)) {
return nil, .Invalid, 0
return nil, .Invalid, 0, ""
}
mapping = make([]int, len(args), checker.allocator)
for &value, index in mapping {
value = index
}
return mapping, .Explicit, 0
return mapping, .Explicit, 0, ""
}
current := make([]int, len(args), checker.allocator)
defer delete(current, checker.allocator)
@@ -1771,36 +1880,50 @@ call_argument_mapping :: proc(
}
search_call_mappings(&search, 0, 0)
selected_index := -1
failures: [dynamic]string
failures.allocator = checker.allocator
defer {
for item in failures {
delete(item, checker.allocator)
}
delete(failures)
}
if len(candidates) == 1 {
selected_index = 0
} else {
explicit_candidate := -1
explicit_count := 0
for candidate, index in candidates {
if mapping_explicit_arguments_valid(checker, function^, candidate, args, pkg, file) {
explicit_candidate = index
explicit_count += 1
}
}
if explicit_count == 1 {
selected_index = explicit_candidate
}
}
if selected_index < 0 && len(candidates) > 1 {
for candidate, index in candidates {
if !mapping_explicit_arguments_valid(checker, function^, candidate, args, pkg, file) {
continue
}
if call_mapping_semantically_valid(checker, function^, candidate, args, expected, locals, pkg, file) {
valid, reason := call_mapping_semantically_valid(
checker, function^, candidate, args, expected, locals, pkg, file,
)
if valid {
if selected_index >= 0 {
return nil, .Invalid, comptime_param_count(function^)
return nil, .Invalid, comptime_param_count(function^), fmt.aprintf(
"multiple complete argument mappings satisfy the call",
allocator=checker.allocator,
)
}
selected_index = index
} else {
append(&failures, reason)
}
}
}
if selected_index < 0 {
return nil, .Invalid, comptime_param_count(function^)
if len(failures) > 0 {
builder := strings.builder_make(checker.allocator)
defer strings.builder_destroy(&builder)
for reason, index in failures {
if index > 0 {
strings.write_string(&builder, "; ")
}
fmt.sbprintf(&builder, "candidate %d: %s", index+1, reason)
}
return nil, .Invalid, comptime_param_count(function^), fmt.aprintf(
"%s", strings.to_string(builder), allocator=checker.allocator,
)
}
return nil, .Invalid, comptime_param_count(function^), ""
}
selected := make([]int, len(args), checker.allocator)
copy(selected, candidates[selected_index])
@@ -1813,7 +1936,7 @@ call_argument_mapping :: proc(
}
}
}
return selected, .Explicit if identity else .Inferred, comptime_param_count(function^)
return selected, .Explicit if identity else .Inferred, comptime_param_count(function^), ""
}
bind_inferred_comptime :: proc(
@@ -2082,6 +2205,7 @@ infer_call_comptime_values :: proc(
pkg: ast.Package_Id,
file: ast.File_Id,
diagnose := false,
failure: ^string = nil,
) -> ([]Comptime_Value, bool) {
values := make([]Comptime_Value, prefix, checker.allocator)
bound := make([]bool, prefix, checker.allocator)
@@ -2123,6 +2247,12 @@ infer_call_comptime_values :: proc(
if is_comptime_type_param(checker, param) {
actual, ok := resolve_type_argument(checker, arg_id, pkg, file)
if !ok {
if failure != nil && len(failure^) == 0 {
failure^ = fmt.aprintf(
"argument %d for comptime type parameter '%s' is not a type",
source_index+1, symbol_text(checker, param.name), allocator=checker.allocator,
)
}
if diagnose {
source.addf(checker.diagnostics, expr.span,
"argument for comptime type parameter '%s' must be a type",
@@ -2136,6 +2266,12 @@ infer_call_comptime_values :: proc(
} else if is_comptime_string_param(checker, param, function) {
text, text_ok := comptime_string_argument(checker, arg_id, pkg, file)
if !text_ok {
if failure != nil && len(failure^) == 0 {
failure^ = fmt.aprintf(
"argument %d for comptime string parameter '%s' does not evaluate to immutable bytes",
source_index+1, symbol_text(checker, param.name), allocator=checker.allocator,
)
}
if diagnose {
source.addf(checker.diagnostics, expr.span,
"argument for comptime string parameter '%s' must evaluate to immutable bytes",
@@ -2155,6 +2291,12 @@ infer_call_comptime_values :: proc(
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
constant := eval_integer_constant_in_context(checker, arg_id, pkg, file)
if constant.kind != .Value {
if failure != nil && len(failure^) == 0 {
failure^ = fmt.aprintf(
"argument %d for comptime parameter '%s' is not a compile-time integer expression",
source_index+1, symbol_text(checker, param.name), allocator=checker.allocator,
)
}
if diagnose {
source.addf(checker.diagnostics, expr.span,
"argument for comptime parameter '%s' must be a compile-time integer expression",
@@ -2164,6 +2306,12 @@ infer_call_comptime_values :: proc(
continue
}
if !fits_integer_type(constant.value, declared, checker.target) {
if failure != nil && len(failure^) == 0 {
failure^ = fmt.aprintf(
"argument %d integer constant %d does not fit in %s",
source_index+1, constant.value, types.name(declared), allocator=checker.allocator,
)
}
if diagnose {
source.addf(checker.diagnostics, expr.span,
"integer constant %d does not fit in %s", constant.value, types.name(declared))
@@ -2243,6 +2391,12 @@ infer_call_comptime_values :: proc(
continue
}
matched = false
if failure != nil && len(failure^) == 0 {
failure^ = fmt.aprintf(
"cannot infer comptime parameter '%s'",
symbol_text(checker, param.name), allocator=checker.allocator,
)
}
if diagnose {
source.addf(
checker.diagnostics, param.span,
@@ -2253,6 +2407,9 @@ infer_call_comptime_values :: proc(
ordinal += 1
}
if !matched {
if failure != nil && len(failure^) == 0 {
failure^ = fmt.aprintf("comptime inference produced conflicting bindings", allocator=checker.allocator)
}
delete(values, checker.allocator)
return nil, false
}
@@ -2329,6 +2486,77 @@ clone_comptime_values :: proc(values: []Comptime_Value, allocator: mem.Allocator
return result
}
inline_expansions_equal :: proc(left, right: []Inline_Expansion) -> bool {
if len(left) != len(right) {
return false
}
for value, index in left {
if value != right[index] {
return false
}
}
return true
}
find_call_resolution :: proc(
checker: ^Checker,
expr: ast.Expr_Id,
) -> (int, bool) {
for index := len(checker.call_resolutions)-1; index >= 0; index -= 1 {
entry := checker.call_resolutions[index]
if entry.expr == expr &&
comptime_values_equal(entry.ctx, checker.current_comptime_values) &&
inline_expansions_equal(entry.inline_ctx, checker.inline_context[:]) {
return index, true
}
}
return -1, false
}
store_call_resolution :: proc(
checker: ^Checker,
expr: ast.Expr_Id,
mapping: []int,
comptime_values: []Comptime_Value,
runtime_types: []types.Type,
) {
entry := Call_Resolution{
expr=expr,
ctx=clone_comptime_values(checker.current_comptime_values, checker.allocator),
inline_ctx=slice.clone(checker.inline_context[:], checker.allocator),
mapping=slice.clone(mapping, checker.allocator),
comptime_values=clone_comptime_values(comptime_values, checker.allocator),
runtime_types=slice.clone(runtime_types, checker.allocator),
}
if index, ok := find_call_resolution(checker, expr); ok {
previous := checker.call_resolutions[index]
delete(previous.ctx, checker.allocator)
delete(previous.inline_ctx, checker.allocator)
delete(previous.mapping, checker.allocator)
delete(previous.comptime_values, checker.allocator)
delete(previous.runtime_types, checker.allocator)
checker.call_resolutions[index] = entry
return
}
append(&checker.call_resolutions, entry)
}
resolved_call_arg_expected :: proc(
checker: ^Checker,
function: ast.Function,
param_index: int,
resolution_index: int,
) -> types.Type {
if resolution_index < 0 || resolution_index >= len(checker.call_resolutions) {
return call_arg_expected(checker, function, param_index)
}
previous := checker.current_comptime_values
checker.current_comptime_values = checker.call_resolutions[resolution_index].comptime_values
result := call_arg_expected(checker, function, param_index)
checker.current_comptime_values = previous
return result
}
resolve_generated_struct_type :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id) -> types.Type {
for entry in checker.generated_types {
if entry.expr == expr_id && comptime_values_equal(entry.values, checker.current_comptime_values) {
@@ -4273,9 +4501,10 @@ infer_expr :: proc(
continue
}
function := checker.ast_module.functions[template]
mapping, mode, prefix := call_argument_mapping(
mapping, mode, prefix, mapping_failure := call_argument_mapping(
checker, &function, expr.args, pkg, file, frame.expected, locals,
)
delete(mapping_failure, checker.allocator)
if mode == .Invalid {
last = types.INVALID
_ = pop(&stack)
@@ -4388,6 +4617,10 @@ infer_expr :: proc(
defer delete(comptime_values, checker.allocator)
if comptime_ok &&
can_specialize(checker, function, stack[frame_index].args, comptime_values) {
store_call_resolution(
checker, frame.expr, frame.mapping,
comptime_values, stack[frame_index].args,
)
previous_comptime := checker.current_comptime_values
checker.current_comptime_values = comptime_values
for source_index in 0..<len(expr.args) {
@@ -4756,9 +4989,20 @@ infer_statements :: proc(
bindings, inline_error := inline_field_bindings(checker, statement.expr, statement.name, pkg, file)
if inline_error == .None {
for binding, inline_index in bindings {
binding_start := push_inline_binding(checker, binding, statement.index_name, inline_index)
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
binding_start := push_inline_binding(checker, binding, statement.index_name, inline_index, statement_id)
iteration: [dynamic]ast.Stmt_Id
iteration.allocator = checker.allocator
control := flatten_inline_iteration(
checker, statement.body, pkg, file, &iteration, statement.label, nil,
)
if control != .Invalid {
infer_statements(checker, iteration[:], locals, local_types, pkg, file, demanded, result, result_hint)
}
delete(iteration)
pop_inline_binding(checker, binding_start)
if control == .Break || control == .Invalid {
break
}
}
}
delete(bindings, checker.allocator)
@@ -5891,6 +6135,7 @@ Build_Expr_Frame :: struct {
built_args: []hir.Expr_Id,
arg_types: []types.Type,
template: ast.Function_Id,
resolution: int,
}
hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: []Build_Local) -> bool {
@@ -7586,14 +7831,24 @@ build_expr :: proc(
_ = pop(&stack)
continue
}
infer_locals := make([]Infer_Local, len(locals), checker.allocator)
for local, index in locals {
infer_locals[index] = Infer_Local{name=local.name, type=local.type, declared=local.type}
resolution_index, resolved := find_call_resolution(checker, frame.expr)
mapping: []int
mode := Call_Argument_Mode.Invalid
prefix := comptime_param_count(function)
mapping_failure := ""
if resolved {
mapping = slice.clone(checker.call_resolutions[resolution_index].mapping, checker.allocator)
mode = call_mapping_mode(function, mapping)
} else {
infer_locals := make([]Infer_Local, len(locals), checker.allocator)
for local, index in locals {
infer_locals[index] = Infer_Local{name=local.name, type=local.type, declared=local.type}
}
mapping, mode, prefix, mapping_failure = call_argument_mapping(
checker, &function, expr.args, pkg, file, frame.expected, infer_locals,
)
delete(infer_locals, checker.allocator)
}
mapping, mode, prefix := call_argument_mapping(
checker, &function, expr.args, pkg, file, frame.expected, infer_locals,
)
delete(infer_locals, checker.allocator)
if mode == .Invalid {
id := source.INVALID_DIAGNOSTIC
if !function_has_comptime_params(function) {
@@ -7603,6 +7858,12 @@ build_expr :: proc(
checker.diagnostics, expr.span, message,
symbol_text(checker, expr.name), len(function.params), len(expr.args),
)
} else if len(mapping_failure) > 0 {
id = source.addf(
checker.diagnostics, expr.span,
"call to '%s' has no unique complete argument mapping: %s",
symbol_text(checker, expr.name), mapping_failure,
)
} else {
id = source.addf(
checker.diagnostics, expr.span,
@@ -7610,6 +7871,7 @@ build_expr :: proc(
symbol_text(checker, expr.name),
)
}
delete(mapping_failure, checker.allocator)
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
@@ -7617,6 +7879,7 @@ build_expr :: proc(
stack[frame_index].template = template
stack[frame_index].arg_mode = mode
stack[frame_index].prefix = prefix
stack[frame_index].resolution = resolution_index if resolved else -1
stack[frame_index].mapping = mapping
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator)
stack[frame_index].arg_types = make([]types.Type, len(function.params), checker.allocator)
@@ -7627,7 +7890,9 @@ build_expr :: proc(
stack[frame_index].stage = 3
if stack[frame_index].arg_index < len(expr.args) {
param_index := call_param_index(mapping, stack[frame_index].arg_index)
arg_expected := call_arg_expected(checker, function, param_index)
arg_expected := resolved_call_arg_expected(
checker, function, param_index, stack[frame_index].resolution,
)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
@@ -7693,7 +7958,10 @@ build_expr :: proc(
stack[frame_index].arg_index = next
if next < len(expr.args) {
next_param := call_param_index(frame.mapping, next)
arg_expected := call_arg_expected(checker, checker.ast_module.functions[frame.template], next_param)
arg_expected := resolved_call_arg_expected(
checker, checker.ast_module.functions[frame.template], next_param,
frame.resolution,
)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
@@ -7704,10 +7972,18 @@ build_expr :: proc(
function := checker.ast_module.functions[frame.template]
comptime_values: []Comptime_Value
comptime_ok := false
comptime_values, comptime_ok = infer_call_comptime_values(
checker, function, frame.prefix, frame.mapping, expr.args, stack[frame_index].arg_types,
frame.expected, pkg, file, diagnose=true,
)
if frame.resolution >= 0 && frame.resolution < len(checker.call_resolutions) {
comptime_values = clone_comptime_values(
checker.call_resolutions[frame.resolution].comptime_values,
checker.allocator,
)
comptime_ok = true
} else {
comptime_values, comptime_ok = infer_call_comptime_values(
checker, function, frame.prefix, frame.mapping, expr.args, stack[frame_index].arg_types,
frame.expected, pkg, file, diagnose=true,
)
}
defer delete(comptime_values, checker.allocator)
if comptime_ok {
previous_comptime := checker.current_comptime_values
@@ -7759,8 +8035,12 @@ build_expr :: proc(
checker.current_comptime_values = previous_comptime
}
spec := INVALID_SPEC
if comptime_ok {
spec = find_spec(checker, frame.template, stack[frame_index].arg_types, comptime_values)
if comptime_ok && frame.resolution >= 0 && frame.resolution < len(checker.call_resolutions) {
spec = find_spec(
checker, frame.template,
checker.call_resolutions[frame.resolution].runtime_types,
comptime_values,
)
}
delete(stack[frame_index].arg_types, checker.allocator)
stack[frame_index].arg_types = nil
@@ -8178,6 +8458,12 @@ inline_field_bindings :: proc(
return nil, .Diagnosed if state.diagnostic != source.INVALID_DIAGNOSTIC else .Invalid
}
value := state.values[value_id]
if ct_value_contains_undefined(&state, value_id) {
if diagnose {
_ = ct_fail(&state, .Not_Comptime, checker.ast_module.exprs[expr].span, "inline for cannot expand an undefined comptime value")
}
return nil, .Diagnosed if state.diagnostic != source.INVALID_DIAGNOSTIC else .Invalid
}
if value.kind == .Range {
parts := ct_child_slice(&state, value)
if len(parts) != 2 {
@@ -8242,9 +8528,16 @@ inline_field_bindings :: proc(
return bindings, .None
}
push_inline_binding :: proc(checker: ^Checker, binding: Static_Binding, index_name: symbol.Id, index: int) -> int {
push_inline_binding :: proc(
checker: ^Checker,
binding: Static_Binding,
index_name: symbol.Id,
index: int,
statement: ast.Stmt_Id,
) -> int {
start := len(checker.static_bindings)
append(&checker.static_bindings, binding)
append(&checker.inline_context, Inline_Expansion{statement=statement, index=u32(index)})
if symbol.is_valid(index_name) {
value := ct_add_value(&checker.static_state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
append(&checker.static_bindings, Static_Binding{name=index_name, type=types.USIZE, value=value})
@@ -8254,6 +8547,7 @@ push_inline_binding :: proc(checker: ^Checker, binding: Static_Binding, index_na
pop_inline_binding :: proc(checker: ^Checker, start: int) {
resize(&checker.static_bindings, start)
_ = pop(&checker.inline_context)
}
Inline_Control :: enum u8 {
@@ -8406,10 +8700,12 @@ flatten_inline_iteration :: proc(
}
if contains_inline_control(checker, statement.body, target_label, true) ||
contains_inline_control(checker, statement.else_body, target_label, true) {
diagnostic^ = source.add(
checker.diagnostics, statement.span,
"break or continue targeting an inline loop must be compile-time-resolvable",
)
if diagnostic != nil {
diagnostic^ = source.add(
checker.diagnostics, statement.span,
"break or continue targeting an inline loop must be compile-time-resolvable",
)
}
return .Invalid
}
}
@@ -8443,10 +8739,12 @@ flatten_inline_iteration :: proc(
continue
}
if contains_inline_control(checker, statement.body, target_label, true) {
diagnostic^ = source.add(
checker.diagnostics, statement.span,
"break or continue targeting an inline loop must be compile-time-resolvable",
)
if diagnostic != nil {
diagnostic^ = source.add(
checker.diagnostics, statement.span,
"break or continue targeting an inline loop must be compile-time-resolvable",
)
}
return .Invalid
}
}
@@ -8467,10 +8765,12 @@ flatten_inline_iteration :: proc(
}
if (statement.kind == .For || statement.kind == .While || statement.kind == .Defer) &&
contains_inline_control(checker, []ast.Stmt_Id{statement_id}, target_label, false) {
diagnostic^ = source.add(
checker.diagnostics, statement.span,
"break or continue targeting an inline loop must be compile-time-resolvable",
)
if diagnostic != nil {
diagnostic^ = source.add(
checker.diagnostics, statement.span,
"break or continue targeting an inline loop must be compile-time-resolvable",
)
}
return .Invalid
}
append(out, statement_id)
@@ -9274,7 +9574,7 @@ build_block :: proc(
ctx.problematic^ = true
} else if inline_error == .None {
for binding, inline_index in bindings {
binding_start := push_inline_binding(checker, binding, statement.index_name, inline_index)
binding_start := push_inline_binding(checker, binding, statement.index_name, inline_index, statement_id)
iteration: [dynamic]ast.Stmt_Id
iteration.allocator = checker.allocator
diagnostic := source.INVALID_DIAGNOSTIC
@@ -11166,6 +11466,9 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
hir.Local{name = param.name, type = param_type, parameter = true},
param.span,
)
if param.name != checker.sink_symbol && block_reads_name(checker, function.body, param.name) {
local_used[int(local_id)] = true
}
append(&locals, Build_Local{name = param.name, type = param_type, id = local_id})
append(&params, local_id)
runtime_index += 1
@@ -11765,7 +12068,9 @@ check :: proc(
checker.type_factories.allocator = allocator
checker.generated_types.allocator = allocator
checker.type_factory_origins.allocator = allocator
checker.call_resolutions.allocator = allocator
checker.static_bindings.allocator = allocator
checker.inline_context.allocator = allocator
checker.static_state = ct_state_make(&checker, 0, ast.INVALID_FILE)
build_symbol_indexes(&checker)
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
@@ -11828,11 +12133,20 @@ check :: proc(
for origin in checker.type_factory_origins {
delete(origin.values, allocator)
}
for resolution in checker.call_resolutions {
delete(resolution.ctx, allocator)
delete(resolution.inline_ctx, allocator)
delete(resolution.mapping, allocator)
delete(resolution.comptime_values, allocator)
delete(resolution.runtime_types, allocator)
}
delete(checker.type_factories)
delete(checker.generated_types)
delete(checker.type_factory_origins)
delete(checker.call_resolutions)
ct_state_destroy(&checker.static_state)
delete(checker.static_bindings)
delete(checker.inline_context)
}
for function, index in ast_module.functions {
+71 -26
View File
@@ -210,6 +210,7 @@ ct_place_id :: proc(index: int) -> Ct_Place_Id {
Ct_Value_Kind :: enum u8 {
Invalid,
Void,
Undefined,
Integer,
Float,
Bool,
@@ -460,6 +461,34 @@ ct_child_slice :: proc(state: ^Ct_State, value: Ct_Value) -> []Ct_Value_Id {
return state.children[start:end]
}
ct_value_contains_undefined :: proc(state: ^Ct_State, id: Ct_Value_Id, depth := 0) -> bool {
if depth > 64 || id == INVALID_CT_VALUE || int(id) >= len(state.values) {
return false
}
value := state.values[id]
if value.kind == .Undefined {
return true
}
for child in ct_child_slice(state, value) {
if ct_value_contains_undefined(state, child, depth+1) {
return true
}
}
return false
}
ct_observe_value :: proc(state: ^Ct_State, id: Ct_Value_Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
if state.values[id].kind == .Undefined {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, span, "cannot read an undefined value at comptime",
)
}
return id, ct_flow(.Normal), true
}
ct_fail :: proc(state: ^Ct_State, kind: Ct_Error_Kind, span: source.Span, message: string) -> bool {
if state.error == .None {
state.error = kind
@@ -795,6 +824,11 @@ ct_materialize_value :: proc(
}
value := state.values[materialized]
#partial switch value.kind {
case .Undefined:
if state.diagnostic == source.INVALID_DIAGNOSTIC {
state.diagnostic = source.add(checker.diagnostics, span, "cannot materialize an undefined comptime value")
}
return invalid_hir_expr(checker, span, state.diagnostic, value.type)
case .Integer:
if types.is_enum(value.type, &checker.module.types) {
int_value := i64(value.integer) if value.integer < 0 else transmute(i64)u64(value.integer)
@@ -920,20 +954,8 @@ ct_undefined_value :: proc(state: ^Ct_State, value_type: types.Type, depth := 0)
return INVALID_CT_VALUE, false
}
store := &state.checker.module.types
if types.is_concrete_integer(value_type) || types.is_enum(value_type, store) {
return ct_add_value(state, Ct_Value{kind=.Integer, type=value_type}), true
}
if types.is_bool(value_type) {
return ct_add_value(state, Ct_Value{kind=.Bool, type=value_type}), true
}
if types.is_float(value_type, state.checker.target) {
return ct_add_value(state, Ct_Value{kind=.Float, type=value_type}), true
}
item, ok := types.node(store, value_type)
if !ok {
return INVALID_CT_VALUE, false
}
if item.kind == .Array {
if ok && item.kind == .Array {
children := make([]Ct_Value_Id, int(item.count), state.checker.allocator)
defer delete(children, state.checker.allocator)
for &child in children {
@@ -944,7 +966,7 @@ ct_undefined_value :: proc(state: ^Ct_State, value_type: types.Type, depth := 0)
append(&state.children, ..children)
return ct_add_value(state, Ct_Value{kind=.Array, type=value_type, start=start, count=u32(len(children))}), true
}
if item.kind == .Struct && !item.opaque {
if ok && item.kind == .Struct && !item.opaque {
fields := types.fields_for(store, value_type)
children := make([]Ct_Value_Id, len(fields), state.checker.allocator)
defer delete(children, state.checker.allocator)
@@ -956,7 +978,7 @@ ct_undefined_value :: proc(state: ^Ct_State, value_type: types.Type, depth := 0)
append(&state.children, ..children)
return ct_add_value(state, Ct_Value{kind=.Struct, type=value_type, start=start, count=u32(len(children))}), true
}
return INVALID_CT_VALUE, false
return ct_add_value(state, Ct_Value{kind=.Undefined, type=value_type}), true
}
ct_eval_expr :: proc(
@@ -992,7 +1014,7 @@ ct_eval_expr :: proc(
case .Name:
if !symbol.is_valid(expr.qualifier) {
if index, ok := ct_find_binding_index(state, expr.name); ok {
return ct_binding_value(state, index), ct_flow(.Normal), true
return ct_observe_value(state, ct_binding_value(state, index), expr.span)
}
if value, ok := current_comptime_value(checker, expr.name); ok {
if value.kind == .Integer {
@@ -1113,7 +1135,10 @@ ct_eval_expr :: proc(
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice index out of bounds")
}
value, value_ok := ct_place_get(state, place)
return value, ct_flow(.Normal), value_ok
if !value_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
return ct_observe_value(state, value, expr.span)
}
if base.kind == .Pointer {
pointer_item, pointer_ok := types.node(store, base.type)
@@ -1124,7 +1149,10 @@ ct_eval_expr :: proc(
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime pointer index out of bounds")
}
value, value_ok := ct_place_get(state, place)
return value, ct_flow(.Normal), value_ok
if !value_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
return ct_observe_value(state, value, expr.span)
}
if array_item, array_ok := types.node(store, pointer_item.child); array_ok && array_item.kind == .Array {
base_place, _, _ := ct_pointer_place(state, base)
@@ -1133,7 +1161,10 @@ ct_eval_expr :: proc(
}
place := ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Index, index=u32(index_value)}, array_item.child, pointer_item.mutable && array_item.mutable)
value, value_ok := ct_place_get(state, place)
return value, ct_flow(.Normal), value_ok
if !value_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
return ct_observe_value(state, value, expr.span)
}
}
}
@@ -1249,7 +1280,7 @@ ct_eval_expr :: proc(
if !value_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime pointer no longer points to live storage")
}
return value, ct_flow(.Normal), true
return ct_observe_value(state, value, expr.span)
case .Slice:
return ct_eval_slice_expr(state, expr, depth+1)
case .Undefined:
@@ -1611,7 +1642,7 @@ ct_eval_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, name: symbol
}
field_place := ct_extend_place(state, place, Ct_Path_Elem{kind=.Field, index=u32(index)}, field.type, false)
if value, value_ok := ct_place_get(state, field_place); value_ok {
return value, ct_flow(.Normal), true
return ct_observe_value(state, value, span)
}
}
}
@@ -1627,12 +1658,12 @@ ct_eval_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, name: symbol
if int(base.active) != index || len(children) == 0 || children[0] == INVALID_CT_VALUE {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "variant '%s' has no payload to read", field_name)
}
return children[0], ct_flow(.Normal), true
return ct_observe_value(state, children[0], span)
}
if index < 0 || index >= len(children) || types.is_void(field.type) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "field '%s' has no value", field_name)
}
return children[index], ct_flow(.Normal), true
return ct_observe_value(state, children[index], span)
}
ct_eval_tuple_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index: u64, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
@@ -1649,14 +1680,17 @@ ct_eval_tuple_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index:
}
field_place := ct_extend_place(state, place, Ct_Path_Elem{kind=.Field, index=u32(field_index)}, field.type, false)
value, value_ok := ct_place_get(state, field_place)
return value, ct_flow(.Normal), value_ok
if !value_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
return ct_observe_value(state, value, span)
}
field_index, _, ok := find_tuple_field(state.checker, base_type, index)
children := ct_child_slice(state, base)
if !ok || field_index < 0 || field_index >= len(children) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "tuple field index is out of bounds")
}
return children[field_index], ct_flow(.Normal), true
return ct_observe_value(state, children[field_index], span)
}
ct_eval_index_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index: int, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
@@ -1670,7 +1704,7 @@ ct_eval_index_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index: int,
if index < 0 || index >= len(children) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime array index out of bounds")
}
return children[index], ct_flow(.Normal), true
return ct_observe_value(state, children[index], span)
}
if base.kind == .String {
if base.index >= u64(len(checker.ast_module.strings)) || index < 0 || index >= len(checker.ast_module.strings[base.index]) {
@@ -2546,6 +2580,12 @@ ct_eval_template_call :: proc(
if !ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
if ct_value_contains_undefined(state, value) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, checker.ast_module.exprs[args[index]].span,
"cannot pass an undefined value at comptime",
)
}
append(&runtime_values, value)
append(&runtime_types, param_type)
append(&runtime_names, param.name)
@@ -2583,6 +2623,11 @@ ct_eval_template_call :: proc(
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "comptime function '%s' did not return a value", symbol_text(checker, function.name))
}
result := flow.value
if ct_value_contains_undefined(state, result) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, span, "comptime function returned an undefined value",
)
}
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")
}