fix comptime specialization, implement io.print
This commit is contained in:
+407
-93
@@ -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(¶ms, 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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user