fix comptime specialization, implement io.print
This commit is contained in:
+1
-1
@@ -152,7 +152,7 @@ as `math.divfloor(a, b)` resolve to ordinary functions.
|
|||||||
- explicit comptime type parameters such as `max func($T type, a, b T) T`, specialized by type and omitted from the runtime ABI
|
- explicit comptime type parameters such as `max func($T type, a, b T) T`, specialized by type and omitted from the runtime ABI
|
||||||
- comptime type/integer/string parameters may appear anywhere, are erased from the runtime ABI, and may be omitted when uniquely recoverable from runtime arguments or the immediate expected result; `_` is an explicit inference hole
|
- comptime type/integer/string parameters may appear anywhere, are erased from the runtime ABI, and may be omitted when uniquely recoverable from runtime arguments or the immediate expected result; `_` is an explicit inference hole
|
||||||
- forced typed comptime expressions such as `$sum(1, 2)`, `$Point { x = 1, y = 2 }`, and comptime value blocks such as `${ yield 4 }`
|
- forced typed comptime expressions such as `$sum(1, 2)`, `$Point { x = 1, y = 2 }`, and comptime value blocks such as `${ yield 4 }`
|
||||||
- comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values
|
- comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values; `undefined` storage may be initialized at comptime, but remaining poison cannot be observed
|
||||||
- comptime type factories such as `Box func($T type) type { return struct { value T } }`; calls like `Box(i32)` are concrete nominal types and may appear anywhere a type is expected
|
- comptime type factories such as `Box func($T type) type { return struct { value T } }`; calls like `Box(i32)` are concrete nominal types and may appear anywhere a type is expected
|
||||||
- tuple types are unnamed-field structs (`struct { i32, []u8 }`), tuple values use `{1, "bro"}` / `{1,}` / `{}`, and fields use canonical numeric names such as `.0`
|
- tuple types are unnamed-field structs (`struct { i32, []u8 }`), tuple values use `{1, "bro"}` / `{1,}` / `{}`, and fields use canonical numeric names such as `.0`
|
||||||
- `typeinfo!`, `field!`, `compile_error!`, and semantic `inline for` provide compile-time record reflection and heterogeneous static expansion without runtime metadata; reflected aggregates remain persistent compile-time values, and inline-loop `break` / `continue` must be selected entirely at comptime
|
- `typeinfo!`, `field!`, `compile_error!`, and semantic `inline for` provide compile-time record reflection and heterogeneous static expansion without runtime metadata; reflected aggregates remain persistent compile-time values, and inline-loop `break` / `continue` must be selected entirely at comptime
|
||||||
|
|||||||
@@ -867,6 +867,8 @@
|
|||||||
38. place every intrinsic behind direct unqualified `name!(...)` syntax, freeing the bare names for
|
38. place every intrinsic behind direct unqualified `name!(...)` syntax, freeing the bare names for
|
||||||
user functions (implemented)
|
user functions (implemented)
|
||||||
|
|
||||||
|
39. aggregate comptime parameters and richer formatting
|
||||||
|
|
||||||
## A word on unchecked casts
|
## A word on unchecked casts
|
||||||
|
|
||||||
For casts that bypass safety checks, Honey provides builtin functions:
|
For casts that bypass safety checks, Honey provides builtin functions:
|
||||||
|
|||||||
+407
-93
@@ -59,6 +59,11 @@ Static_Binding :: struct {
|
|||||||
value: Ct_Value_Id,
|
value: Ct_Value_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Inline_Expansion :: struct {
|
||||||
|
statement: ast.Stmt_Id,
|
||||||
|
index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
Entry_Point_Kind :: enum u8 {
|
Entry_Point_Kind :: enum u8 {
|
||||||
Invalid,
|
Invalid,
|
||||||
Plain,
|
Plain,
|
||||||
@@ -167,6 +172,15 @@ Type_Factory_Origin :: struct {
|
|||||||
values: []Comptime_Value,
|
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 {
|
Checker :: struct {
|
||||||
ast_module: ^ast.Module,
|
ast_module: ^ast.Module,
|
||||||
diagnostics: ^source.Diagnostics,
|
diagnostics: ^source.Diagnostics,
|
||||||
@@ -218,9 +232,11 @@ Checker :: struct {
|
|||||||
current_comptime_values: []Comptime_Value,
|
current_comptime_values: []Comptime_Value,
|
||||||
static_state: Ct_State,
|
static_state: Ct_State,
|
||||||
static_bindings: [dynamic]Static_Binding,
|
static_bindings: [dynamic]Static_Binding,
|
||||||
|
inline_context: [dynamic]Inline_Expansion,
|
||||||
type_factories: [dynamic]Type_Factory_Entry,
|
type_factories: [dynamic]Type_Factory_Entry,
|
||||||
generated_types: [dynamic]Generated_Type_Entry,
|
generated_types: [dynamic]Generated_Type_Entry,
|
||||||
type_factory_origins: [dynamic]Type_Factory_Origin,
|
type_factory_origins: [dynamic]Type_Factory_Origin,
|
||||||
|
call_resolutions: [dynamic]Call_Resolution,
|
||||||
target: target.Target,
|
target: target.Target,
|
||||||
allocator: mem.Allocator,
|
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(
|
record_unused_locals :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
locals: []hir.Local,
|
locals: []hir.Local,
|
||||||
@@ -1520,6 +1616,18 @@ next_runtime_call_arg :: proc(function: ast.Function, mapping: []int, start, sou
|
|||||||
return index
|
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) {
|
comptime_binding_index :: proc(function: ast.Function, _: int, name: symbol.Id) -> (int, bool) {
|
||||||
ordinal := 0
|
ordinal := 0
|
||||||
for param in function.params {
|
for param in function.params {
|
||||||
@@ -1600,29 +1708,6 @@ explicit_comptime_argument_valid :: proc(
|
|||||||
return false
|
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) {
|
search_call_mappings :: proc(search: ^Call_Mapping_Search, param_index, source_index: int) {
|
||||||
if len(search.candidates^) >= COMPTIME_EVAL_QUOTA {
|
if len(search.candidates^) >= COMPTIME_EVAL_QUOTA {
|
||||||
return
|
return
|
||||||
@@ -1664,7 +1749,7 @@ call_mapping_semantically_valid :: proc(
|
|||||||
locals: []Infer_Local,
|
locals: []Infer_Local,
|
||||||
pkg: ast.Package_Id,
|
pkg: ast.Package_Id,
|
||||||
file: ast.File_Id,
|
file: ast.File_Id,
|
||||||
) -> bool {
|
) -> (bool, string) {
|
||||||
actual_args := make([]types.Type, len(function.params), checker.allocator)
|
actual_args := make([]types.Type, len(function.params), checker.allocator)
|
||||||
defer delete(actual_args, checker.allocator)
|
defer delete(actual_args, checker.allocator)
|
||||||
for source_index in 0..<len(args) {
|
for source_index in 0..<len(args) {
|
||||||
@@ -1676,20 +1761,29 @@ call_mapping_semantically_valid :: proc(
|
|||||||
if !explicit_comptime_argument_valid(
|
if !explicit_comptime_argument_valid(
|
||||||
checker, function, function.params[param_index], args[source_index], pkg, file,
|
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
|
continue
|
||||||
}
|
}
|
||||||
actual_args[param_index] = infer_expr(checker, args[source_index], locals, pkg, file)
|
actual_args[param_index] = infer_expr(checker, args[source_index], locals, pkg, file)
|
||||||
}
|
}
|
||||||
|
inference_failure := ""
|
||||||
values, ok := infer_call_comptime_values(
|
values, ok := infer_call_comptime_values(
|
||||||
checker, function, comptime_param_count(function), mapping, args, actual_args,
|
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)
|
defer delete(values, checker.allocator)
|
||||||
if !ok {
|
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
|
previous := checker.current_comptime_values
|
||||||
checker.current_comptime_values = values
|
checker.current_comptime_values = values
|
||||||
defer checker.current_comptime_values = previous
|
defer checker.current_comptime_values = previous
|
||||||
@@ -1699,35 +1793,50 @@ call_mapping_semantically_valid :: proc(
|
|||||||
}
|
}
|
||||||
actual := actual_args[index]
|
actual := actual_args[index]
|
||||||
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
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) {
|
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_constraint(declared) {
|
||||||
if !types.is_valid(types.constraint_target(declared, actual, &checker.module.types)) {
|
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) {
|
} 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 ||
|
if source_index < 0 ||
|
||||||
!is_numeric_constant_expr(checker, args[source_index]) ||
|
!is_numeric_constant_expr(checker, args[source_index]) ||
|
||||||
!expr_accepts_numeric_demand(checker, args[source_index], declared, locals, pkg, file) {
|
!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) {
|
if is_runtime_type(checker, expected) {
|
||||||
result := function_channel_type(checker, function)
|
result := function_channel_type(checker, function)
|
||||||
if !is_runtime_type(checker, result) || !can_implicitly_convert_type(checker, result, expected) {
|
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(
|
call_argument_mapping :: proc(
|
||||||
@@ -1738,16 +1847,16 @@ call_argument_mapping :: proc(
|
|||||||
file: ast.File_Id,
|
file: ast.File_Id,
|
||||||
expected := types.INVALID,
|
expected := types.INVALID,
|
||||||
locals: []Infer_Local = nil,
|
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 function.variadic || function.c_abi {
|
||||||
if !valid_call_arity(function^, len(args)) {
|
if !valid_call_arity(function^, len(args)) {
|
||||||
return nil, .Invalid, 0
|
return nil, .Invalid, 0, ""
|
||||||
}
|
}
|
||||||
mapping = make([]int, len(args), checker.allocator)
|
mapping = make([]int, len(args), checker.allocator)
|
||||||
for &value, index in mapping {
|
for &value, index in mapping {
|
||||||
value = index
|
value = index
|
||||||
}
|
}
|
||||||
return mapping, .Explicit, 0
|
return mapping, .Explicit, 0, ""
|
||||||
}
|
}
|
||||||
current := make([]int, len(args), checker.allocator)
|
current := make([]int, len(args), checker.allocator)
|
||||||
defer delete(current, checker.allocator)
|
defer delete(current, checker.allocator)
|
||||||
@@ -1771,36 +1880,50 @@ call_argument_mapping :: proc(
|
|||||||
}
|
}
|
||||||
search_call_mappings(&search, 0, 0)
|
search_call_mappings(&search, 0, 0)
|
||||||
selected_index := -1
|
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 {
|
if len(candidates) == 1 {
|
||||||
selected_index = 0
|
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 {
|
if selected_index < 0 && len(candidates) > 1 {
|
||||||
for candidate, index in candidates {
|
for candidate, index in candidates {
|
||||||
if !mapping_explicit_arguments_valid(checker, function^, candidate, args, pkg, file) {
|
valid, reason := call_mapping_semantically_valid(
|
||||||
continue
|
checker, function^, candidate, args, expected, locals, pkg, file,
|
||||||
}
|
)
|
||||||
if call_mapping_semantically_valid(checker, function^, candidate, args, expected, locals, pkg, file) {
|
if valid {
|
||||||
if selected_index >= 0 {
|
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
|
selected_index = index
|
||||||
|
} else {
|
||||||
|
append(&failures, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if selected_index < 0 {
|
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)
|
selected := make([]int, len(args), checker.allocator)
|
||||||
copy(selected, candidates[selected_index])
|
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(
|
bind_inferred_comptime :: proc(
|
||||||
@@ -2082,6 +2205,7 @@ infer_call_comptime_values :: proc(
|
|||||||
pkg: ast.Package_Id,
|
pkg: ast.Package_Id,
|
||||||
file: ast.File_Id,
|
file: ast.File_Id,
|
||||||
diagnose := false,
|
diagnose := false,
|
||||||
|
failure: ^string = nil,
|
||||||
) -> ([]Comptime_Value, bool) {
|
) -> ([]Comptime_Value, bool) {
|
||||||
values := make([]Comptime_Value, prefix, checker.allocator)
|
values := make([]Comptime_Value, prefix, checker.allocator)
|
||||||
bound := make([]bool, 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) {
|
if is_comptime_type_param(checker, param) {
|
||||||
actual, ok := resolve_type_argument(checker, arg_id, pkg, file)
|
actual, ok := resolve_type_argument(checker, arg_id, pkg, file)
|
||||||
if !ok {
|
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 {
|
if diagnose {
|
||||||
source.addf(checker.diagnostics, expr.span,
|
source.addf(checker.diagnostics, expr.span,
|
||||||
"argument for comptime type parameter '%s' must be a type",
|
"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) {
|
} else if is_comptime_string_param(checker, param, function) {
|
||||||
text, text_ok := comptime_string_argument(checker, arg_id, pkg, file)
|
text, text_ok := comptime_string_argument(checker, arg_id, pkg, file)
|
||||||
if !text_ok {
|
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 {
|
if diagnose {
|
||||||
source.addf(checker.diagnostics, expr.span,
|
source.addf(checker.diagnostics, expr.span,
|
||||||
"argument for comptime string parameter '%s' must evaluate to immutable bytes",
|
"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)
|
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||||
constant := eval_integer_constant_in_context(checker, arg_id, pkg, file)
|
constant := eval_integer_constant_in_context(checker, arg_id, pkg, file)
|
||||||
if constant.kind != .Value {
|
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 {
|
if diagnose {
|
||||||
source.addf(checker.diagnostics, expr.span,
|
source.addf(checker.diagnostics, expr.span,
|
||||||
"argument for comptime parameter '%s' must be a compile-time integer expression",
|
"argument for comptime parameter '%s' must be a compile-time integer expression",
|
||||||
@@ -2164,6 +2306,12 @@ infer_call_comptime_values :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !fits_integer_type(constant.value, declared, checker.target) {
|
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 {
|
if diagnose {
|
||||||
source.addf(checker.diagnostics, expr.span,
|
source.addf(checker.diagnostics, expr.span,
|
||||||
"integer constant %d does not fit in %s", constant.value, types.name(declared))
|
"integer constant %d does not fit in %s", constant.value, types.name(declared))
|
||||||
@@ -2243,6 +2391,12 @@ infer_call_comptime_values :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
matched = false
|
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 {
|
if diagnose {
|
||||||
source.addf(
|
source.addf(
|
||||||
checker.diagnostics, param.span,
|
checker.diagnostics, param.span,
|
||||||
@@ -2253,6 +2407,9 @@ infer_call_comptime_values :: proc(
|
|||||||
ordinal += 1
|
ordinal += 1
|
||||||
}
|
}
|
||||||
if !matched {
|
if !matched {
|
||||||
|
if failure != nil && len(failure^) == 0 {
|
||||||
|
failure^ = fmt.aprintf("comptime inference produced conflicting bindings", allocator=checker.allocator)
|
||||||
|
}
|
||||||
delete(values, checker.allocator)
|
delete(values, checker.allocator)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
@@ -2329,6 +2486,77 @@ clone_comptime_values :: proc(values: []Comptime_Value, allocator: mem.Allocator
|
|||||||
return result
|
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 {
|
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 {
|
for entry in checker.generated_types {
|
||||||
if entry.expr == expr_id && comptime_values_equal(entry.values, checker.current_comptime_values) {
|
if entry.expr == expr_id && comptime_values_equal(entry.values, checker.current_comptime_values) {
|
||||||
@@ -4273,9 +4501,10 @@ infer_expr :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
function := checker.ast_module.functions[template]
|
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,
|
checker, &function, expr.args, pkg, file, frame.expected, locals,
|
||||||
)
|
)
|
||||||
|
delete(mapping_failure, checker.allocator)
|
||||||
if mode == .Invalid {
|
if mode == .Invalid {
|
||||||
last = types.INVALID
|
last = types.INVALID
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
@@ -4388,6 +4617,10 @@ infer_expr :: proc(
|
|||||||
defer delete(comptime_values, checker.allocator)
|
defer delete(comptime_values, checker.allocator)
|
||||||
if comptime_ok &&
|
if comptime_ok &&
|
||||||
can_specialize(checker, function, stack[frame_index].args, comptime_values) {
|
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
|
previous_comptime := checker.current_comptime_values
|
||||||
checker.current_comptime_values = comptime_values
|
checker.current_comptime_values = comptime_values
|
||||||
for source_index in 0..<len(expr.args) {
|
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)
|
bindings, inline_error := inline_field_bindings(checker, statement.expr, statement.name, pkg, file)
|
||||||
if inline_error == .None {
|
if inline_error == .None {
|
||||||
for binding, inline_index in bindings {
|
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)
|
||||||
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
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)
|
pop_inline_binding(checker, binding_start)
|
||||||
|
if control == .Break || control == .Invalid {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(bindings, checker.allocator)
|
delete(bindings, checker.allocator)
|
||||||
@@ -5891,6 +6135,7 @@ Build_Expr_Frame :: struct {
|
|||||||
built_args: []hir.Expr_Id,
|
built_args: []hir.Expr_Id,
|
||||||
arg_types: []types.Type,
|
arg_types: []types.Type,
|
||||||
template: ast.Function_Id,
|
template: ast.Function_Id,
|
||||||
|
resolution: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: []Build_Local) -> bool {
|
hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: []Build_Local) -> bool {
|
||||||
@@ -7586,14 +7831,24 @@ build_expr :: proc(
|
|||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
infer_locals := make([]Infer_Local, len(locals), checker.allocator)
|
resolution_index, resolved := find_call_resolution(checker, frame.expr)
|
||||||
for local, index in locals {
|
mapping: []int
|
||||||
infer_locals[index] = Infer_Local{name=local.name, type=local.type, declared=local.type}
|
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 {
|
if mode == .Invalid {
|
||||||
id := source.INVALID_DIAGNOSTIC
|
id := source.INVALID_DIAGNOSTIC
|
||||||
if !function_has_comptime_params(function) {
|
if !function_has_comptime_params(function) {
|
||||||
@@ -7603,6 +7858,12 @@ build_expr :: proc(
|
|||||||
checker.diagnostics, expr.span, message,
|
checker.diagnostics, expr.span, message,
|
||||||
symbol_text(checker, expr.name), len(function.params), len(expr.args),
|
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 {
|
} else {
|
||||||
id = source.addf(
|
id = source.addf(
|
||||||
checker.diagnostics, expr.span,
|
checker.diagnostics, expr.span,
|
||||||
@@ -7610,6 +7871,7 @@ build_expr :: proc(
|
|||||||
symbol_text(checker, expr.name),
|
symbol_text(checker, expr.name),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
delete(mapping_failure, checker.allocator)
|
||||||
last = invalid_hir_expr(checker, expr.span, id)
|
last = invalid_hir_expr(checker, expr.span, id)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
continue
|
||||||
@@ -7617,6 +7879,7 @@ build_expr :: proc(
|
|||||||
stack[frame_index].template = template
|
stack[frame_index].template = template
|
||||||
stack[frame_index].arg_mode = mode
|
stack[frame_index].arg_mode = mode
|
||||||
stack[frame_index].prefix = prefix
|
stack[frame_index].prefix = prefix
|
||||||
|
stack[frame_index].resolution = resolution_index if resolved else -1
|
||||||
stack[frame_index].mapping = mapping
|
stack[frame_index].mapping = mapping
|
||||||
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator)
|
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)
|
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
|
stack[frame_index].stage = 3
|
||||||
if stack[frame_index].arg_index < len(expr.args) {
|
if stack[frame_index].arg_index < len(expr.args) {
|
||||||
param_index := call_param_index(mapping, stack[frame_index].arg_index)
|
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) {
|
if !is_runtime_type(checker, arg_expected) {
|
||||||
arg_expected = types.INVALID
|
arg_expected = types.INVALID
|
||||||
}
|
}
|
||||||
@@ -7693,7 +7958,10 @@ build_expr :: proc(
|
|||||||
stack[frame_index].arg_index = next
|
stack[frame_index].arg_index = next
|
||||||
if next < len(expr.args) {
|
if next < len(expr.args) {
|
||||||
next_param := call_param_index(frame.mapping, next)
|
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) {
|
if !is_runtime_type(checker, arg_expected) {
|
||||||
arg_expected = types.INVALID
|
arg_expected = types.INVALID
|
||||||
}
|
}
|
||||||
@@ -7704,10 +7972,18 @@ build_expr :: proc(
|
|||||||
function := checker.ast_module.functions[frame.template]
|
function := checker.ast_module.functions[frame.template]
|
||||||
comptime_values: []Comptime_Value
|
comptime_values: []Comptime_Value
|
||||||
comptime_ok := false
|
comptime_ok := false
|
||||||
comptime_values, comptime_ok = infer_call_comptime_values(
|
if frame.resolution >= 0 && frame.resolution < len(checker.call_resolutions) {
|
||||||
checker, function, frame.prefix, frame.mapping, expr.args, stack[frame_index].arg_types,
|
comptime_values = clone_comptime_values(
|
||||||
frame.expected, pkg, file, diagnose=true,
|
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)
|
defer delete(comptime_values, checker.allocator)
|
||||||
if comptime_ok {
|
if comptime_ok {
|
||||||
previous_comptime := checker.current_comptime_values
|
previous_comptime := checker.current_comptime_values
|
||||||
@@ -7759,8 +8035,12 @@ build_expr :: proc(
|
|||||||
checker.current_comptime_values = previous_comptime
|
checker.current_comptime_values = previous_comptime
|
||||||
}
|
}
|
||||||
spec := INVALID_SPEC
|
spec := INVALID_SPEC
|
||||||
if comptime_ok {
|
if comptime_ok && frame.resolution >= 0 && frame.resolution < len(checker.call_resolutions) {
|
||||||
spec = find_spec(checker, frame.template, stack[frame_index].arg_types, comptime_values)
|
spec = find_spec(
|
||||||
|
checker, frame.template,
|
||||||
|
checker.call_resolutions[frame.resolution].runtime_types,
|
||||||
|
comptime_values,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
delete(stack[frame_index].arg_types, checker.allocator)
|
delete(stack[frame_index].arg_types, checker.allocator)
|
||||||
stack[frame_index].arg_types = nil
|
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
|
return nil, .Diagnosed if state.diagnostic != source.INVALID_DIAGNOSTIC else .Invalid
|
||||||
}
|
}
|
||||||
value := state.values[value_id]
|
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 {
|
if value.kind == .Range {
|
||||||
parts := ct_child_slice(&state, value)
|
parts := ct_child_slice(&state, value)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
@@ -8242,9 +8528,16 @@ inline_field_bindings :: proc(
|
|||||||
return bindings, .None
|
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)
|
start := len(checker.static_bindings)
|
||||||
append(&checker.static_bindings, binding)
|
append(&checker.static_bindings, binding)
|
||||||
|
append(&checker.inline_context, Inline_Expansion{statement=statement, index=u32(index)})
|
||||||
if symbol.is_valid(index_name) {
|
if symbol.is_valid(index_name) {
|
||||||
value := ct_add_value(&checker.static_state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
|
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})
|
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) {
|
pop_inline_binding :: proc(checker: ^Checker, start: int) {
|
||||||
resize(&checker.static_bindings, start)
|
resize(&checker.static_bindings, start)
|
||||||
|
_ = pop(&checker.inline_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
Inline_Control :: enum u8 {
|
Inline_Control :: enum u8 {
|
||||||
@@ -8406,10 +8700,12 @@ flatten_inline_iteration :: proc(
|
|||||||
}
|
}
|
||||||
if contains_inline_control(checker, statement.body, target_label, true) ||
|
if contains_inline_control(checker, statement.body, target_label, true) ||
|
||||||
contains_inline_control(checker, statement.else_body, target_label, true) {
|
contains_inline_control(checker, statement.else_body, target_label, true) {
|
||||||
diagnostic^ = source.add(
|
if diagnostic != nil {
|
||||||
checker.diagnostics, statement.span,
|
diagnostic^ = source.add(
|
||||||
"break or continue targeting an inline loop must be compile-time-resolvable",
|
checker.diagnostics, statement.span,
|
||||||
)
|
"break or continue targeting an inline loop must be compile-time-resolvable",
|
||||||
|
)
|
||||||
|
}
|
||||||
return .Invalid
|
return .Invalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8443,10 +8739,12 @@ flatten_inline_iteration :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if contains_inline_control(checker, statement.body, target_label, true) {
|
if contains_inline_control(checker, statement.body, target_label, true) {
|
||||||
diagnostic^ = source.add(
|
if diagnostic != nil {
|
||||||
checker.diagnostics, statement.span,
|
diagnostic^ = source.add(
|
||||||
"break or continue targeting an inline loop must be compile-time-resolvable",
|
checker.diagnostics, statement.span,
|
||||||
)
|
"break or continue targeting an inline loop must be compile-time-resolvable",
|
||||||
|
)
|
||||||
|
}
|
||||||
return .Invalid
|
return .Invalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8467,10 +8765,12 @@ flatten_inline_iteration :: proc(
|
|||||||
}
|
}
|
||||||
if (statement.kind == .For || statement.kind == .While || statement.kind == .Defer) &&
|
if (statement.kind == .For || statement.kind == .While || statement.kind == .Defer) &&
|
||||||
contains_inline_control(checker, []ast.Stmt_Id{statement_id}, target_label, false) {
|
contains_inline_control(checker, []ast.Stmt_Id{statement_id}, target_label, false) {
|
||||||
diagnostic^ = source.add(
|
if diagnostic != nil {
|
||||||
checker.diagnostics, statement.span,
|
diagnostic^ = source.add(
|
||||||
"break or continue targeting an inline loop must be compile-time-resolvable",
|
checker.diagnostics, statement.span,
|
||||||
)
|
"break or continue targeting an inline loop must be compile-time-resolvable",
|
||||||
|
)
|
||||||
|
}
|
||||||
return .Invalid
|
return .Invalid
|
||||||
}
|
}
|
||||||
append(out, statement_id)
|
append(out, statement_id)
|
||||||
@@ -9274,7 +9574,7 @@ build_block :: proc(
|
|||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
} else if inline_error == .None {
|
} else if inline_error == .None {
|
||||||
for binding, inline_index in bindings {
|
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: [dynamic]ast.Stmt_Id
|
||||||
iteration.allocator = checker.allocator
|
iteration.allocator = checker.allocator
|
||||||
diagnostic := source.INVALID_DIAGNOSTIC
|
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},
|
hir.Local{name = param.name, type = param_type, parameter = true},
|
||||||
param.span,
|
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(&locals, Build_Local{name = param.name, type = param_type, id = local_id})
|
||||||
append(¶ms, local_id)
|
append(¶ms, local_id)
|
||||||
runtime_index += 1
|
runtime_index += 1
|
||||||
@@ -11765,7 +12068,9 @@ check :: proc(
|
|||||||
checker.type_factories.allocator = allocator
|
checker.type_factories.allocator = allocator
|
||||||
checker.generated_types.allocator = allocator
|
checker.generated_types.allocator = allocator
|
||||||
checker.type_factory_origins.allocator = allocator
|
checker.type_factory_origins.allocator = allocator
|
||||||
|
checker.call_resolutions.allocator = allocator
|
||||||
checker.static_bindings.allocator = allocator
|
checker.static_bindings.allocator = allocator
|
||||||
|
checker.inline_context.allocator = allocator
|
||||||
checker.static_state = ct_state_make(&checker, 0, ast.INVALID_FILE)
|
checker.static_state = ct_state_make(&checker, 0, ast.INVALID_FILE)
|
||||||
build_symbol_indexes(&checker)
|
build_symbol_indexes(&checker)
|
||||||
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
||||||
@@ -11828,11 +12133,20 @@ check :: proc(
|
|||||||
for origin in checker.type_factory_origins {
|
for origin in checker.type_factory_origins {
|
||||||
delete(origin.values, allocator)
|
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.type_factories)
|
||||||
delete(checker.generated_types)
|
delete(checker.generated_types)
|
||||||
delete(checker.type_factory_origins)
|
delete(checker.type_factory_origins)
|
||||||
|
delete(checker.call_resolutions)
|
||||||
ct_state_destroy(&checker.static_state)
|
ct_state_destroy(&checker.static_state)
|
||||||
delete(checker.static_bindings)
|
delete(checker.static_bindings)
|
||||||
|
delete(checker.inline_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
for function, index in ast_module.functions {
|
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 {
|
Ct_Value_Kind :: enum u8 {
|
||||||
Invalid,
|
Invalid,
|
||||||
Void,
|
Void,
|
||||||
|
Undefined,
|
||||||
Integer,
|
Integer,
|
||||||
Float,
|
Float,
|
||||||
Bool,
|
Bool,
|
||||||
@@ -460,6 +461,34 @@ ct_child_slice :: proc(state: ^Ct_State, value: Ct_Value) -> []Ct_Value_Id {
|
|||||||
return state.children[start:end]
|
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 {
|
ct_fail :: proc(state: ^Ct_State, kind: Ct_Error_Kind, span: source.Span, message: string) -> bool {
|
||||||
if state.error == .None {
|
if state.error == .None {
|
||||||
state.error = kind
|
state.error = kind
|
||||||
@@ -795,6 +824,11 @@ ct_materialize_value :: proc(
|
|||||||
}
|
}
|
||||||
value := state.values[materialized]
|
value := state.values[materialized]
|
||||||
#partial switch value.kind {
|
#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:
|
case .Integer:
|
||||||
if types.is_enum(value.type, &checker.module.types) {
|
if types.is_enum(value.type, &checker.module.types) {
|
||||||
int_value := i64(value.integer) if value.integer < 0 else transmute(i64)u64(value.integer)
|
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
|
return INVALID_CT_VALUE, false
|
||||||
}
|
}
|
||||||
store := &state.checker.module.types
|
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)
|
item, ok := types.node(store, value_type)
|
||||||
if !ok {
|
if ok && item.kind == .Array {
|
||||||
return INVALID_CT_VALUE, false
|
|
||||||
}
|
|
||||||
if item.kind == .Array {
|
|
||||||
children := make([]Ct_Value_Id, int(item.count), state.checker.allocator)
|
children := make([]Ct_Value_Id, int(item.count), state.checker.allocator)
|
||||||
defer delete(children, state.checker.allocator)
|
defer delete(children, state.checker.allocator)
|
||||||
for &child in children {
|
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)
|
append(&state.children, ..children)
|
||||||
return ct_add_value(state, Ct_Value{kind=.Array, type=value_type, start=start, count=u32(len(children))}), true
|
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)
|
fields := types.fields_for(store, value_type)
|
||||||
children := make([]Ct_Value_Id, len(fields), state.checker.allocator)
|
children := make([]Ct_Value_Id, len(fields), state.checker.allocator)
|
||||||
defer delete(children, 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)
|
append(&state.children, ..children)
|
||||||
return ct_add_value(state, Ct_Value{kind=.Struct, type=value_type, start=start, count=u32(len(children))}), true
|
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(
|
ct_eval_expr :: proc(
|
||||||
@@ -992,7 +1014,7 @@ ct_eval_expr :: proc(
|
|||||||
case .Name:
|
case .Name:
|
||||||
if !symbol.is_valid(expr.qualifier) {
|
if !symbol.is_valid(expr.qualifier) {
|
||||||
if index, ok := ct_find_binding_index(state, expr.name); ok {
|
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, ok := current_comptime_value(checker, expr.name); ok {
|
||||||
if value.kind == .Integer {
|
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")
|
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)
|
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 {
|
if base.kind == .Pointer {
|
||||||
pointer_item, pointer_ok := types.node(store, base.type)
|
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")
|
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)
|
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 {
|
if array_item, array_ok := types.node(store, pointer_item.child); array_ok && array_item.kind == .Array {
|
||||||
base_place, _, _ := ct_pointer_place(state, base)
|
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)
|
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)
|
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 {
|
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 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:
|
case .Slice:
|
||||||
return ct_eval_slice_expr(state, expr, depth+1)
|
return ct_eval_slice_expr(state, expr, depth+1)
|
||||||
case .Undefined:
|
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)
|
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 {
|
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 {
|
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 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) {
|
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 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) {
|
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)
|
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)
|
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)
|
field_index, _, ok := find_tuple_field(state.checker, base_type, index)
|
||||||
children := ct_child_slice(state, base)
|
children := ct_child_slice(state, base)
|
||||||
if !ok || field_index < 0 || field_index >= len(children) {
|
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 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) {
|
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) {
|
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 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.kind == .String {
|
||||||
if base.index >= u64(len(checker.ast_module.strings)) || index < 0 || index >= len(checker.ast_module.strings[base.index]) {
|
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 {
|
if !ok {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
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_values, value)
|
||||||
append(&runtime_types, param_type)
|
append(&runtime_types, param_type)
|
||||||
append(&runtime_names, param.name)
|
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))
|
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
|
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) {
|
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")
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime function returned a pointer to expired storage")
|
||||||
}
|
}
|
||||||
|
|||||||
+204
-1
@@ -2425,6 +2425,7 @@ milestone_37_tuples_reflection_inline_for_and_debug_print_compile_and_run :: pro
|
|||||||
testing.expect_value(t, len(diagnostics.items), 0)
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
testing.expect(t, !strings.contains(llvm_text, "FormatToken"))
|
testing.expect(t, !strings.contains(llvm_text, "FormatToken"))
|
||||||
testing.expect(t, !strings.contains(llvm_text, "parse_format"))
|
testing.expect(t, !strings.contains(llvm_text, "parse_format"))
|
||||||
|
testing.expect(t, !strings.contains(llvm_text, "format_field_name"))
|
||||||
testing.expect(t, !strings.contains(llvm_text, "FieldInfo"))
|
testing.expect(t, !strings.contains(llvm_text, "FieldInfo"))
|
||||||
testing.expect(t, !strings.contains(llvm_text, "RecordInfo"))
|
testing.expect(t, !strings.contains(llvm_text, "RecordInfo"))
|
||||||
|
|
||||||
@@ -2441,7 +2442,7 @@ milestone_37_tuples_reflection_inline_for_and_debug_print_compile_and_run :: pro
|
|||||||
defer delete(stderr)
|
defer delete(stderr)
|
||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
testing.expect_value(t, string(stdout), "")
|
testing.expect_value(t, string(stdout), "")
|
||||||
testing.expect_value(t, string(stderr), "tuple=40/bro, limits=-9223372036854775808/18446744073709551615")
|
testing.expect_value(t, string(stderr), "hello!\ntuple=40/bro, limits=-9223372036854775808/18446744073709551615")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -2531,6 +2532,171 @@ milestone_37_inline_loop_control_must_be_statically_resolvable :: proc(t: ^testi
|
|||||||
testing.expect(t, found)
|
testing.expect(t, found)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
milestone_37_comptime_undefined_aggregates_support_full_initialization :: proc(t: ^testing.T) {
|
||||||
|
text := `Token :: struct {
|
||||||
|
text []u8
|
||||||
|
count usize
|
||||||
|
}
|
||||||
|
Partial :: struct { initialized i32, text []u8 }
|
||||||
|
make_tokens func() [2]mut Token {
|
||||||
|
tokens [2]mut Token = undefined
|
||||||
|
tokens[0] = Token {text = "a", count = 1}
|
||||||
|
tokens[1].text = "bro"
|
||||||
|
tokens[1].count = 3
|
||||||
|
return tokens
|
||||||
|
}
|
||||||
|
read_initialized_sibling func() i32 {
|
||||||
|
value Partial = undefined
|
||||||
|
value.initialized = 42
|
||||||
|
return value.initialized
|
||||||
|
}
|
||||||
|
answer :: $read_initialized_sibling()
|
||||||
|
main func() i32 {
|
||||||
|
total usize = 0
|
||||||
|
inline for make_tokens() |token| {
|
||||||
|
total += token.text.len + token.count
|
||||||
|
}
|
||||||
|
if answer != 42 or total != 8 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
milestone_37_comptime_undefined_values_cannot_be_observed :: proc(t: ^testing.T) {
|
||||||
|
text := `Bad :: struct { value i32, text []u8 }
|
||||||
|
read_scalar func() i32 {
|
||||||
|
value i32 = undefined
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return_partial func() Bad {
|
||||||
|
value Bad = undefined
|
||||||
|
value.value = 1
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
take_bad func(value Bad) i32 {
|
||||||
|
return value.value
|
||||||
|
}
|
||||||
|
pass_partial func() i32 {
|
||||||
|
value Bad = undefined
|
||||||
|
value.value = 1
|
||||||
|
return take_bad(value)
|
||||||
|
}
|
||||||
|
bad_scalar :: $read_scalar()
|
||||||
|
bad_record :: $return_partial()
|
||||||
|
bad_argument :: $pass_partial()
|
||||||
|
main func() void {}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
found_read := false
|
||||||
|
found_return := false
|
||||||
|
found_pass := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found_read = found_read || strings.contains(diagnostic.message, "cannot read an undefined value at comptime")
|
||||||
|
found_return = found_return || strings.contains(diagnostic.message, "comptime function returned an undefined value")
|
||||||
|
found_pass = found_pass || strings.contains(diagnostic.message, "cannot pass an undefined value at comptime")
|
||||||
|
}
|
||||||
|
testing.expect(t, found_read)
|
||||||
|
testing.expect(t, found_return)
|
||||||
|
testing.expect(t, found_pass)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
milestone_37_inline_expansions_keep_distinct_call_resolutions :: proc(t: ^testing.T) {
|
||||||
|
text := `identity func($T type, value T) T {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
main func() i32 {
|
||||||
|
total i64 = 0
|
||||||
|
inline for {{i8(1), i16(2)}, {i32(3), i64(4)}} |row| {
|
||||||
|
inline for row |value| {
|
||||||
|
total += i64(identity(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i32(total - 10)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
identity_symbol := symbol.intern(&symbols, "identity")
|
||||||
|
specializations := 0
|
||||||
|
for function in hir_module.functions {
|
||||||
|
specializations += 1 if function.name == identity_symbol else 0
|
||||||
|
}
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, specializations, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
milestone_37_inline_control_prunes_inference_after_static_exit :: proc(t: ^testing.T) {
|
||||||
|
text := `take_i8 func(value i8) void { _ = value }
|
||||||
|
main func() void {
|
||||||
|
inline for {i8(1), "skip"} |value, index| {
|
||||||
|
if index == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
take_i8(value)
|
||||||
|
}
|
||||||
|
inline for {i8(1), "stop"} |value, index| {
|
||||||
|
if index == 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
take_i8(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
milestone_33_rejects_an_incompatible_runtime_write_declaration :: proc(t: ^testing.T) {
|
milestone_33_rejects_an_incompatible_runtime_write_declaration :: proc(t: ^testing.T) {
|
||||||
text := `write c_func(_ c_int, _ c_int, _ c_ulong) c_long
|
text := `write c_func(_ c_int, _ c_int, _ c_ulong) c_long
|
||||||
@@ -2877,6 +3043,36 @@ main func() void {
|
|||||||
testing.expect(t, found_function)
|
testing.expect(t, found_function)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
generic_parameter_usage_is_source_based :: proc(t: ^testing.T) {
|
||||||
|
text := `choose func($N usize, used, unused i32) i32 {
|
||||||
|
if N > 0 {
|
||||||
|
return used
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
main func() void {
|
||||||
|
_ = choose(0, 1, 2)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 1)
|
||||||
|
testing.expect_value(t, diagnostics.items[0].severity, source.Severity.Warning)
|
||||||
|
testing.expect(t, strings.contains(diagnostics.items[0].message, "unused parameter 'unused'"))
|
||||||
|
testing.expect(t, !strings.contains(diagnostics.items[0].message, "unused parameter 'used'"))
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
recursive_specialization_reaches_a_fixed_point :: proc(t: ^testing.T) {
|
recursive_specialization_reaches_a_fixed_point :: proc(t: ^testing.T) {
|
||||||
text := `a func(value int) i32 {
|
text := `a func(value int) i32 {
|
||||||
@@ -3204,6 +3400,7 @@ unknown func($T type) T { value T = undefined; return value }
|
|||||||
partial func($T type, $N usize, value T) T { return value }
|
partial func($T type, $N usize, value T) T { return value }
|
||||||
use_ignored func($T type, value Ignored(T)) i32 { return value }
|
use_ignored func($T type, value Ignored(T)) i32 { return value }
|
||||||
use_alias func($T type, value BoxAlias(T)) T { return value.value }
|
use_alias func($T type, value BoxAlias(T)) T { return value.value }
|
||||||
|
mapping_fail func($A usize, value i32, $B usize) void {}
|
||||||
main func() void {
|
main func() void {
|
||||||
a i32 :: 1
|
a i32 :: 1
|
||||||
b u32 :: 2
|
b u32 :: 2
|
||||||
@@ -3213,6 +3410,7 @@ main func() void {
|
|||||||
_ = use_ignored(a)
|
_ = use_ignored(a)
|
||||||
box Box(i32) :: Box(i32) { value = 1 }
|
box Box(i32) :: Box(i32) { value = 1 }
|
||||||
_ = use_alias(box)
|
_ = use_alias(box)
|
||||||
|
mapping_fail(true, false)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
source_file := source.Source{path="test.bro", text=text}
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
@@ -3230,12 +3428,16 @@ main func() void {
|
|||||||
found_conflict := false
|
found_conflict := false
|
||||||
found_unknown := false
|
found_unknown := false
|
||||||
found_partial := false
|
found_partial := false
|
||||||
|
found_candidate_failures := false
|
||||||
unrecoverable := 0
|
unrecoverable := 0
|
||||||
for diagnostic in diagnostics.items {
|
for diagnostic in diagnostics.items {
|
||||||
message := diagnostic.message
|
message := diagnostic.message
|
||||||
found_conflict = found_conflict || strings.contains(message, "conflicting inference for comptime parameter 'T': i32 and u32")
|
found_conflict = found_conflict || strings.contains(message, "conflicting inference for comptime parameter 'T': i32 and u32")
|
||||||
found_unknown = found_unknown || strings.contains(message, "cannot infer comptime parameter 'T'")
|
found_unknown = found_unknown || strings.contains(message, "cannot infer comptime parameter 'T'")
|
||||||
found_partial = found_partial || strings.contains(message, "cannot infer comptime parameter 'N'")
|
found_partial = found_partial || strings.contains(message, "cannot infer comptime parameter 'N'")
|
||||||
|
found_candidate_failures = found_candidate_failures ||
|
||||||
|
strings.contains(message, "candidate 1: cannot infer comptime parameter 'B'") &&
|
||||||
|
strings.contains(message, "candidate 2: cannot infer comptime parameter 'A'")
|
||||||
if strings.contains(message, "cannot infer comptime parameter 'T'") {
|
if strings.contains(message, "cannot infer comptime parameter 'T'") {
|
||||||
unrecoverable += 1
|
unrecoverable += 1
|
||||||
}
|
}
|
||||||
@@ -3243,6 +3445,7 @@ main func() void {
|
|||||||
testing.expect(t, found_conflict)
|
testing.expect(t, found_conflict)
|
||||||
testing.expect(t, found_unknown)
|
testing.expect(t, found_unknown)
|
||||||
testing.expect(t, found_partial)
|
testing.expect(t, found_partial)
|
||||||
|
testing.expect(t, found_candidate_failures)
|
||||||
testing.expect(t, unrecoverable >= 3)
|
testing.expect(t, unrecoverable >= 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ main func() i32 {
|
|||||||
return 4
|
return 4
|
||||||
}
|
}
|
||||||
field!(&numbers, "2") += 1
|
field!(&numbers, "2") += 1
|
||||||
|
debug.print("hello!\n", {})
|
||||||
debug.print(format(), {
|
debug.print(format(), {
|
||||||
numbers.2,
|
numbers.2,
|
||||||
"bro",
|
"bro",
|
||||||
|
|||||||
+47
-34
@@ -130,13 +130,13 @@ hide FormatToken :: struct {
|
|||||||
kind FormatTokenKind
|
kind FormatTokenKind
|
||||||
start usize
|
start usize
|
||||||
end usize
|
end usize
|
||||||
arg usize
|
field []u8
|
||||||
}
|
}
|
||||||
|
|
||||||
hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
||||||
tokens [N]mut FormatToken = undefined
|
tokens [N]mut FormatToken = undefined
|
||||||
for (usize(0))..format.len |index| {
|
for (usize(0))..format.len |index| {
|
||||||
tokens[index] = FormatToken {kind = .unused, start = 0, end = 0, arg = 0}
|
tokens[index] = FormatToken {kind = .unused, start = 0, end = 0, field = ""}
|
||||||
}
|
}
|
||||||
field_count usize = 0
|
field_count usize = 0
|
||||||
match typeinfo!(Args) {
|
match typeinfo!(Args) {
|
||||||
@@ -160,12 +160,12 @@ hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
|||||||
compile_error!("io.print format has an unmatched '{'")
|
compile_error!("io.print format has an unmatched '{'")
|
||||||
}
|
}
|
||||||
if cursor > literal_start {
|
if cursor > literal_start {
|
||||||
tokens[token_count] = FormatToken {kind = .literal, start = literal_start, end = cursor, arg = 0}
|
tokens[token_count] = FormatToken {kind = .literal, start = literal_start, end = cursor, field = ""}
|
||||||
token_count += 1
|
token_count += 1
|
||||||
}
|
}
|
||||||
next :: format[cursor + 1]
|
next :: format[cursor + 1]
|
||||||
if next == '{' {
|
if next == '{' {
|
||||||
tokens[token_count] = FormatToken {kind = .literal, start = cursor, end = cursor + 1, arg = 0}
|
tokens[token_count] = FormatToken {kind = .literal, start = cursor, end = cursor + 1, field = ""}
|
||||||
token_count += 1
|
token_count += 1
|
||||||
cursor += 2
|
cursor += 2
|
||||||
literal_start = cursor
|
literal_start = cursor
|
||||||
@@ -182,7 +182,15 @@ hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
|||||||
} else {
|
} else {
|
||||||
compile_error!("io.print format has an unknown specifier")
|
compile_error!("io.print format has an unknown specifier")
|
||||||
}
|
}
|
||||||
tokens[token_count] = FormatToken {kind = kind, start = 0, end = 0, arg = argument_count}
|
if argument_count >= field_count {
|
||||||
|
compile_error!("io.print format argument count does not match the tuple")
|
||||||
|
}
|
||||||
|
tokens[token_count] = FormatToken {
|
||||||
|
kind = kind,
|
||||||
|
start = 0,
|
||||||
|
end = 0,
|
||||||
|
field = format_field_name(Args, argument_count),
|
||||||
|
}
|
||||||
token_count += 1
|
token_count += 1
|
||||||
argument_count += 1
|
argument_count += 1
|
||||||
cursor += 3
|
cursor += 3
|
||||||
@@ -194,10 +202,10 @@ hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
|||||||
compile_error!("io.print format has an unmatched '}'")
|
compile_error!("io.print format has an unmatched '}'")
|
||||||
}
|
}
|
||||||
if cursor > literal_start {
|
if cursor > literal_start {
|
||||||
tokens[token_count] = FormatToken {kind = .literal, start = literal_start, end = cursor, arg = 0}
|
tokens[token_count] = FormatToken {kind = .literal, start = literal_start, end = cursor, field = ""}
|
||||||
token_count += 1
|
token_count += 1
|
||||||
}
|
}
|
||||||
tokens[token_count] = FormatToken {kind = .literal, start = cursor, end = cursor + 1, arg = 0}
|
tokens[token_count] = FormatToken {kind = .literal, start = cursor, end = cursor + 1, field = ""}
|
||||||
token_count += 1
|
token_count += 1
|
||||||
cursor += 2
|
cursor += 2
|
||||||
literal_start = cursor
|
literal_start = cursor
|
||||||
@@ -206,7 +214,7 @@ hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
|||||||
cursor += 1
|
cursor += 1
|
||||||
}
|
}
|
||||||
if literal_start < format.len {
|
if literal_start < format.len {
|
||||||
tokens[token_count] = FormatToken {kind = .literal, start = literal_start, end = format.len, arg = 0}
|
tokens[token_count] = FormatToken {kind = .literal, start = literal_start, end = format.len, field = ""}
|
||||||
}
|
}
|
||||||
if argument_count != field_count {
|
if argument_count != field_count {
|
||||||
compile_error!("io.print format argument count does not match the tuple")
|
compile_error!("io.print format argument count does not match the tuple")
|
||||||
@@ -214,39 +222,44 @@ hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
|||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hide format_field_name func($T type, index usize) []u8 {
|
||||||
|
match typeinfo!(T) {
|
||||||
|
.record |record|: return record.fields[index].name
|
||||||
|
else: compile_error!("io.print arguments must be a tuple")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hide write_decimal func(writer Writer, $T type, value T) void ! WriteError {
|
||||||
|
match typeinfo!(T) {
|
||||||
|
.integer: if minval!(T) < 0 {
|
||||||
|
try write_decimal_signed(writer, i64(value))
|
||||||
|
} else {
|
||||||
|
try write_decimal_unsigned(writer, u64(value))
|
||||||
|
}
|
||||||
|
else: compile_error!("io.print '{d}' requires an integer argument")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
print func(
|
print func(
|
||||||
writer Writer,
|
writer Writer,
|
||||||
$format []u8,
|
$format []u8,
|
||||||
$Args type,
|
$Args type,
|
||||||
args Args,
|
args Args,
|
||||||
) void ! WriteError {
|
) void ! WriteError {
|
||||||
match typeinfo!(Args) {
|
inline for parse_format(format.len, format, Args) |token| {
|
||||||
.record |record|: inline for $parse_format(format.len, format, Args) |token| {
|
if (token.kind == .unused) {
|
||||||
if (token.kind == .literal) {
|
break
|
||||||
try write_all(writer, format[token.start..token.end])
|
|
||||||
}
|
|
||||||
if (token.kind == .string or token.kind == .decimal) {
|
|
||||||
inline for record.fields |field| {
|
|
||||||
if (field.index == token.arg) {
|
|
||||||
if (token.kind == .string) {
|
|
||||||
try write_all(writer, field!(args, field.name))
|
|
||||||
} else {
|
|
||||||
match typeinfo!(field.type) {
|
|
||||||
.integer: {
|
|
||||||
if minval!(field.type) < 0 {
|
|
||||||
try write_decimal_signed(writer, i64(field!(args, field.name)))
|
|
||||||
} else {
|
|
||||||
try write_decimal_unsigned(writer, u64(field!(args, field.name)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else: compile_error!("io.print '{d}' requires an integer argument")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else: compile_error!("io.print arguments must be a tuple")
|
if (token.kind == .literal) {
|
||||||
|
try write_all(writer, format[token.start..token.end])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (token.kind == .string) {
|
||||||
|
try write_all(writer, field!(args, token.field))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
try write_decimal(writer, field!(args, token.field))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user