From 4ebe9c90e9f230ef521303dd7a19154f44b7587f Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Fri, 3 Jul 2026 23:23:36 +0200 Subject: [PATCH] comptime storage and function values --- LANGUAGE.md | 5 +- README.md | 12 +- TODO.md | 30 +- compiler/checker/checker.odin | 62 +- compiler/checker/comptime.odin | 1093 ++++++++++++++++++++++-- compiler/parser/parser.odin | 17 +- compiler_tests.odin | 84 +- examples/programs/comptime_v1/main.bro | 71 ++ 8 files changed, 1255 insertions(+), 119 deletions(-) diff --git a/LANGUAGE.md b/LANGUAGE.md index 2aebf98..5af1844 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -55,9 +55,10 @@ roadmap and milestone history. - integer comptime value parameters such as `make_array func($N usize) [N]u8`, specialized by value 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 - 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`, and `try`/`catch` +- 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 - bodyful `c_func` definitions and bodyless `c_func` declarations with exact external symbol names - concrete-only C signatures, C variadic declarations/calls, and C default argument promotions +- native function pointer values and types with `*func(...) R`, fallible `*func(...) R ! E`, optional `?*func(...) R`, and non-variadic native indirect calls - Apple Silicon C ABI lowering for scalars, pointers, fixed-signature plain records/unions, small aggregates, homogeneous float aggregates, and indirect aggregate returns - imported C typedefs, scalar constants, enum constants, fixed arrays, complete plain structs/unions, and pointers to opaque records - imported external C object variables, including mutable variables and immutable object globals @@ -83,7 +84,7 @@ roadmap and milestone history. ## PLANNED / DEFERRED -- comptime pointers, slices, aggregate comptime parameters, and calls through comptime-known function values/function pointers +- aggregate comptime parameters and stable aggregate specialization keys - tuples and native Brolang variadic functions - exporting Brolang functions to C and broader target-specific C ABI lowering - non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments diff --git a/README.md b/README.md index 4130b7e..d73f03a 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,15 @@ call_mapper func(mapper native.Imported_Mapper) c_int { } ``` +Native Brolang function pointer values use `*func(...) R`, with fallible +channels written on the result: + +```bro +call func(callback *func(value i32) i32, value i32) i32 { + return callback(value) +} +``` + Bodyless manual and imported C functions may be variadic: ```bro @@ -138,7 +147,8 @@ Current prototype features: - Demand-monomorphized Brolang and C-ABI functions - Integer and type comptime parameters (`func($N usize) [N]u8`, `func($T type, value T) T`) specialized by comptime argument - Forced typed comptime expressions (`$sum(1, 2)`, `$Point { x = 1, y = 2 }`) and comptime value blocks (`${ yield 4 }`) -- Comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, and `try`/`catch` +- 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 +- Native function pointer values and types (`*func(...) R`, `*func(...) R ! E`, `?*func(...) R`) - Bodyless concrete C function declarations with exact external symbol names - Bodyless manual and imported C variadic declarations with default argument promotions - Ordered linking of additional C sources, objects, archives, and libraries diff --git a/TODO.md b/TODO.md index 2092cb3..f7187ca 100644 --- a/TODO.md +++ b/TODO.md @@ -676,11 +676,9 @@ - immutable locals/globals with comptime-known initializers may feed comptime evaluation; runtime-dependent values remain invalid in comptime contexts - runtime-only behavior is rejected in comptime: external/bodyless `c_func`, - writable globals, pointers/slices, address/deref storage APIs, pointer captures, - and function-pointer calls - - v1 keeps integer-only `$N` specialization keys; aggregate comptime parameters, - stable aggregate serialization, comptime pointers/slices, and calls through - comptime-known function values/function pointers are deferred + writable globals, and materializing comptime storage pointers/slices as runtime memory + - v1 keeps integer-only `$N` specialization keys; aggregate comptime parameters + and stable aggregate serialization are deferred 27.8 source-defined mutable runtime globals (implemented) - allow mutable global declarations in Brolang source for process-global runtime @@ -698,15 +696,19 @@ - reject user-visible name shadowing across imports, named types, globals, functions, params, locals, comptime params, captures, and labels; `_` remains reusable -27.9 comptime storage and function values - - add a comptime pointer/storage model for pointers, slices, address/deref, - pointer captures, lifetimes, aliasing, mutability, and escape rules - - define what `$&value` and other comptime addresses can legally materialize into, - without exposing compiler-owned memory as runtime memory - - add first-class comptime function values and calls through comptime-known function - pointers - - resolve function-pointer targets during comptime execution, apply ABI/runtime - restrictions, and reliably reject imported/runtime callbacks +27.9 comptime storage and function values (implemented; practical v1) + - comptime locals, params, and immutable globals can own evaluator storage cells + addressable through places instead of compiler-owned memory + - comptime supports address/deref, mutable pointer and slice mutation, field/index + places, slicing, `.len`, `.ptr`, pointer captures, and pointer-param aliasing + - comptime storage pointers/slices cannot materialize as runtime memory; escaped + dead storage is rejected + - bare concrete non-comptime function names are values; native function pointer + types use `*func(...) R` and fallible `*func(...) R ! E` + - comptime-known native/bodyful `c_func` values can be called; bodyless/imported + callbacks remain runtime-only + - native function pointers are non-variadic v1; C variadic function pointers stay + under `*c_func(...) R` 28. brolang build system (requires comptime execution) diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index c33476b..9ff98e5 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -903,13 +903,16 @@ function_value_signature :: proc( return nil, types.INVALID, false } function := checker.ast_module.functions[template] - if !function.c_abi || types.is_valid(function.error) { - return nil, types.INVALID, false - } if function_has_comptime_params(function) { return nil, types.INVALID, false } - result = type_from_syntax(checker, function.result, function.pkg, function.file) + if function.c_abi && types.is_valid(function.error) { + return nil, types.INVALID, false + } + if !function.c_abi && (!function.has_body || function.variadic) { + return nil, types.INVALID, false + } + result = function_channel_type(checker, function) if !types.is_void(result) && !is_runtime_type(checker, result) { return nil, types.INVALID, false } @@ -929,6 +932,7 @@ function_pointer_type_for_template :: proc( checker: ^Checker, template: ast.Function_Id, demanded: ^[dynamic]Spec_Id = nil, + demand_spec := true, ) -> (types.Type, Spec_Id, bool) { params, result, ok := function_value_signature(checker, template) if !ok { @@ -936,16 +940,20 @@ function_pointer_type_for_template :: proc( } defer delete(params, checker.allocator) function := checker.ast_module.functions[template] - function_type := types.function(&checker.module.types, params, result, true, function.variadic) + function_type := types.function(&checker.module.types, params, result, function.c_abi, function.variadic) pointer_type := types.pointer(&checker.module.types, function_type, false, true) spec := INVALID_SPEC if demanded == nil { - spec = ensure_spec(checker, template, params) + if demand_spec { + spec = ensure_spec(checker, template, params) + } else { + spec = find_spec(checker, template, params) + } } else { spec = find_spec(checker, template, params) mark_spec_demanded(checker, spec, demanded) } - return pointer_type, spec, spec != INVALID_SPEC + return pointer_type, spec, spec != INVALID_SPEC || !demand_spec } contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool { @@ -1391,16 +1399,30 @@ validate_type_nodes :: proc(checker: ^Checker) { } } if item.kind == .Function { - if !item.c_abi { - source.add(checker.diagnostics, source.Span{}, "only c_func function pointer types are supported") - } - for param in types.params_for(&checker.module.types, id) { - if types.is_void(param.type) || !types.is_c_signature_type(param.type, &checker.module.types) { - source.add(checker.diagnostics, source.Span{}, "function pointer parameters must be concrete C signature types") + if item.c_abi { + if types.kind(item.child, &checker.module.types) == .Fallible { + source.add(checker.diagnostics, source.Span{}, "c_func pointer results cannot be fallible") + } + for param in types.params_for(&checker.module.types, id) { + if types.is_void(param.type) || !types.is_c_signature_type(param.type, &checker.module.types) { + source.add(checker.diagnostics, source.Span{}, "c_func pointer parameters must be concrete C signature types") + } + } + if !types.is_c_signature_type(item.child, &checker.module.types, true) { + source.add(checker.diagnostics, source.Span{}, "c_func pointer results must be concrete C signature types or void") + } + } else { + if item.variadic { + source.add(checker.diagnostics, source.Span{}, "native function pointer types do not support variadic parameters") + } + for param in types.params_for(&checker.module.types, id) { + if types.is_void(param.type) || !is_runtime_type(checker, param.type) { + source.add(checker.diagnostics, source.Span{}, "native function pointer parameters must be concrete runtime types") + } + } + if !types.is_void(item.child) && !is_runtime_type(checker, item.child) { + source.add(checker.diagnostics, source.Span{}, "native function pointer results must be concrete runtime types or void") } - } - if !types.is_c_signature_type(item.child, &checker.module.types, true) { - source.add(checker.diagnostics, source.Span{}, "function pointer results must be concrete C signature types or void") } } } @@ -1597,7 +1619,7 @@ infer_compound_expr :: proc( store := &checker.module.types #partial switch expr.kind { case .Comptime: - return infer_comptime_expr_type(checker, expr, pkg, file) + return infer_comptime_expr_type(checker, expr, pkg, file, demanded) case .Bool: return types.BOOL case .Not: @@ -3535,13 +3557,13 @@ build_function_value :: proc( id := source.addf( checker.diagnostics, span, - "function '%s' cannot be used as a C callback; expected a concrete c_func", + "function '%s' cannot be used as a function value; expected a concrete non-comptime signature", symbol_text(checker, function.name), ) return invalid_hir_expr(checker, span, id) } defer delete(params, checker.allocator) - function_type := types.function(&checker.module.types, params, result, true, function.variadic) + function_type := types.function(&checker.module.types, params, result, function.c_abi, function.variadic) pointer_type := types.pointer(&checker.module.types, function_type, false, true) spec := find_spec(checker, template, params) if spec == INVALID_SPEC { @@ -4444,7 +4466,7 @@ build_expr :: proc( last = build_global_reference(checker, global, expr.span, global_reads) } else { template := find_template(checker, expr.name, target_pkg) - if template != ast.INVALID_FUNCTION && checker.ast_module.functions[template].c_abi { + if template != ast.INVALID_FUNCTION { last = build_function_value(checker, template, expr.span, frame.expected) } else { id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name) diff --git a/compiler/checker/comptime.odin b/compiler/checker/comptime.odin index c4b3e48..d74b9b9 100644 --- a/compiler/checker/comptime.odin +++ b/compiler/checker/comptime.odin @@ -186,6 +186,22 @@ ct_value_id :: proc(index: int) -> Ct_Value_Id { return Ct_Value_Id(index) } +Ct_Cell_Id :: distinct u32 +INVALID_CT_CELL :: Ct_Cell_Id(0xffff_ffff) + +ct_cell_id :: proc(index: int) -> Ct_Cell_Id { + assert(index >= 0 && u64(index) < u64(INVALID_CT_CELL)) + return Ct_Cell_Id(index) +} + +Ct_Place_Id :: distinct u32 +INVALID_CT_PLACE :: Ct_Place_Id(0xffff_ffff) + +ct_place_id :: proc(index: int) -> Ct_Place_Id { + assert(index >= 0 && u64(index) < u64(INVALID_CT_PLACE)) + return Ct_Place_Id(index) +} + Ct_Value_Kind :: enum u8 { Invalid, Void, @@ -196,6 +212,9 @@ Ct_Value_Kind :: enum u8 { Range, Array, Struct, + Pointer, + Slice, + Function, None, Optional_Some, Fallible, @@ -220,10 +239,35 @@ Ct_Value :: struct { active: i64, } +Ct_Path_Kind :: enum u8 { + Index, + Field, +} + +Ct_Path_Elem :: struct { + kind: Ct_Path_Kind, + index: u32, +} + +Ct_Cell :: struct { + value: Ct_Value_Id, + mutable: bool, + live: bool, +} + +Ct_Place :: struct { + cell: Ct_Cell_Id, + type: types.Type, + start: u32, + count: u32, + writable: bool, +} + Ct_Binding :: struct { name: symbol.Id, type: types.Type, value: Ct_Value_Id, + cell: Ct_Cell_Id, mutable: bool, } @@ -248,12 +292,16 @@ Ct_State :: struct { result: types.Type, values: [dynamic]Ct_Value, children: [dynamic]Ct_Value_Id, + cells: [dynamic]Ct_Cell, + places: [dynamic]Ct_Place, + paths: [dynamic]Ct_Path_Elem, bindings: [dynamic]Ct_Binding, defers: [dynamic]ast.Stmt_Id, steps: int, error: Ct_Error_Kind, diagnostic: source.Diagnostic_Id, silent: bool, + demanded: ^[dynamic]Spec_Id, } ct_state_make :: proc( @@ -263,6 +311,7 @@ ct_state_make :: proc( result := types.INVALID, values: []Comptime_Value = nil, diagnose := true, + demanded: ^[dynamic]Spec_Id = nil, ) -> Ct_State { state: Ct_State state.checker = checker @@ -272,14 +321,18 @@ ct_state_make :: proc( state.error = .None state.diagnostic = source.INVALID_DIAGNOSTIC state.silent = !diagnose + state.demanded = demanded state.values.allocator = checker.allocator state.children.allocator = checker.allocator + state.cells.allocator = checker.allocator + state.places.allocator = checker.allocator + state.paths.allocator = checker.allocator state.bindings.allocator = checker.allocator state.defers.allocator = checker.allocator for value in values { if value.kind == .Integer { id := ct_add_value(&state, Ct_Value{kind=.Integer, type=value.type, integer=value.value}) - append(&state.bindings, Ct_Binding{name=value.name, type=value.type, value=id, mutable=false}) + ct_bind_value(&state, value.name, value.type, id, false) } } return state @@ -288,6 +341,9 @@ ct_state_make :: proc( ct_state_destroy :: proc(state: ^Ct_State) { delete(state.values) delete(state.children) + delete(state.cells) + delete(state.places) + delete(state.paths) delete(state.bindings) delete(state.defers) } @@ -298,6 +354,71 @@ ct_add_value :: proc(state: ^Ct_State, value: Ct_Value) -> Ct_Value_Id { return id } +ct_add_cell :: proc(state: ^Ct_State, value: Ct_Value_Id, mutable: bool) -> Ct_Cell_Id { + id := ct_cell_id(len(state.cells)) + append(&state.cells, Ct_Cell{value=value, mutable=mutable, live=true}) + return id +} + +ct_add_place :: proc( + state: ^Ct_State, + cell: Ct_Cell_Id, + value_type: types.Type, + writable: bool, + path: []Ct_Path_Elem = nil, +) -> Ct_Place_Id { + id := ct_place_id(len(state.places)) + start := u32(len(state.paths)) + append(&state.paths, ..path) + append(&state.places, Ct_Place{ + cell=cell, type=value_type, start=start, count=u32(len(path)), writable=writable, + }) + return id +} + +ct_place_path :: proc(state: ^Ct_State, place: Ct_Place) -> []Ct_Path_Elem { + start := int(place.start) + end := start+int(place.count) + if start < 0 || end > len(state.paths) { + return nil + } + return state.paths[start:end] +} + +ct_extend_place :: proc( + state: ^Ct_State, + base_id: Ct_Place_Id, + elem: Ct_Path_Elem, + value_type: types.Type, + writable: bool, +) -> Ct_Place_Id { + if base_id == INVALID_CT_PLACE || int(base_id) >= len(state.places) { + return INVALID_CT_PLACE + } + base := state.places[base_id] + path := ct_place_path(state, base) + extended := make([]Ct_Path_Elem, len(path)+1, state.checker.allocator) + defer delete(extended, state.checker.allocator) + copy(extended, path) + extended[len(path)] = elem + return ct_add_place(state, base.cell, value_type, writable, extended[:]) +} + +ct_bind_value :: proc(state: ^Ct_State, name: symbol.Id, value_type: types.Type, value: Ct_Value_Id, mutable: bool) { + cell := ct_add_cell(state, value, mutable) + append(&state.bindings, Ct_Binding{name=name, type=value_type, value=value, cell=cell, mutable=mutable}) +} + +ct_pop_bindings :: proc(state: ^Ct_State, start: int) { + for index := start; index < len(state.bindings); index += 1 { + cell := state.bindings[index].cell + if cell != INVALID_CT_CELL && int(cell) < len(state.cells) { + state.cells[cell].live = false + } + } + resize(&state.bindings, start) +} + ct_child_slice :: proc(state: ^Ct_State, value: Ct_Value) -> []Ct_Value_Id { start := int(value.start) end := start+int(value.count) @@ -344,6 +465,28 @@ ct_find_binding_index :: proc(state: ^Ct_State, name: symbol.Id) -> (int, bool) return -1, false } +ct_binding_value :: proc(state: ^Ct_State, index: int) -> Ct_Value_Id { + if index < 0 || index >= len(state.bindings) { + return INVALID_CT_VALUE + } + binding := state.bindings[index] + if binding.cell != INVALID_CT_CELL && int(binding.cell) < len(state.cells) && state.cells[binding.cell].live { + return state.cells[binding.cell].value + } + return binding.value +} + +ct_binding_place :: proc(state: ^Ct_State, index: int) -> Ct_Place_Id { + if index < 0 || index >= len(state.bindings) { + return INVALID_CT_PLACE + } + binding := state.bindings[index] + if binding.cell == INVALID_CT_CELL || int(binding.cell) >= len(state.cells) || !state.cells[binding.cell].live { + return INVALID_CT_PLACE + } + return ct_add_place(state, binding.cell, binding.type, binding.mutable) +} + ct_flow :: proc(kind: Ct_Flow_Kind, value := INVALID_CT_VALUE, label := symbol.INVALID) -> Ct_Flow { return Ct_Flow{kind=kind, value=value, label=label} } @@ -393,6 +536,14 @@ ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type, return id, true } store := &state.checker.module.types + if value.kind == .Pointer && types.can_weaken_pointer(value.type, expected, store) { + value.type = expected + return ct_add_value(state, value), true + } + if value.kind == .Slice && types.can_weaken_slice(value.type, expected, store) { + value.type = expected + return ct_add_value(state, value), true + } if value.kind == .Array { expected_item, expected_ok := types.node(store, expected) value_item, value_ok := types.node(store, value.type) @@ -449,6 +600,140 @@ ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type, ) } +ct_place_get :: proc(state: ^Ct_State, place_id: Ct_Place_Id) -> (Ct_Value_Id, bool) { + if place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) { + return INVALID_CT_VALUE, false + } + place := state.places[place_id] + if place.cell == INVALID_CT_CELL || int(place.cell) >= len(state.cells) || !state.cells[place.cell].live { + return INVALID_CT_VALUE, false + } + current := state.cells[place.cell].value + for elem in ct_place_path(state, place) { + if current == INVALID_CT_VALUE || int(current) >= len(state.values) { + return INVALID_CT_VALUE, false + } + value := state.values[current] + children := ct_child_slice(state, value) + #partial switch elem.kind { + case .Index: + index := int(elem.index) + if value.kind != .Array || index < 0 || index >= len(children) { + return INVALID_CT_VALUE, false + } + current = children[index] + case .Field: + index := int(elem.index) + if value.kind != .Struct || index < 0 { + return INVALID_CT_VALUE, false + } + if types.is_union(value.type, &state.checker.module.types) { + if value.active != i64(index) || len(children) == 0 { + return INVALID_CT_VALUE, false + } + current = children[0] + } else { + if index >= len(children) { + return INVALID_CT_VALUE, false + } + current = children[index] + } + } + } + return current, true +} + +ct_update_path :: proc(state: ^Ct_State, current: Ct_Value_Id, path: []Ct_Path_Elem, replacement: Ct_Value_Id) -> (Ct_Value_Id, bool) { + if len(path) == 0 { + return replacement, true + } + if current == INVALID_CT_VALUE || int(current) >= len(state.values) { + return INVALID_CT_VALUE, false + } + value := state.values[current] + children := ct_child_slice(state, value) + if len(children) == 0 { + return INVALID_CT_VALUE, false + } + next_index := -1 + elem := path[0] + #partial switch elem.kind { + case .Index: + if value.kind != .Array { + return INVALID_CT_VALUE, false + } + next_index = int(elem.index) + case .Field: + if value.kind != .Struct { + return INVALID_CT_VALUE, false + } + if types.is_union(value.type, &state.checker.module.types) { + if value.active != i64(elem.index) { + return INVALID_CT_VALUE, false + } + next_index = 0 + } else { + next_index = int(elem.index) + } + } + if next_index < 0 || next_index >= len(children) { + return INVALID_CT_VALUE, false + } + updated_child, ok := ct_update_path(state, children[next_index], path[1:], replacement) + if !ok { + return INVALID_CT_VALUE, false + } + copied := make([]Ct_Value_Id, len(children), state.checker.allocator) + defer delete(copied, state.checker.allocator) + copy(copied, children) + copied[next_index] = updated_child + value.start = u32(len(state.children)) + append(&state.children, ..copied) + return ct_add_value(state, value), true +} + +ct_place_set :: proc(state: ^Ct_State, place_id: Ct_Place_Id, replacement: Ct_Value_Id) -> bool { + if place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) { + return false + } + place := state.places[place_id] + if !place.writable || place.cell == INVALID_CT_CELL || + int(place.cell) >= len(state.cells) || !state.cells[place.cell].live { + return false + } + path := ct_place_path(state, place) + root, ok := ct_update_path(state, state.cells[place.cell].value, path, replacement) + if !ok { + return false + } + state.cells[place.cell].value = root + return true +} + +ct_place_live :: proc(state: ^Ct_State, place_id: Ct_Place_Id) -> bool { + if place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) { + return false + } + cell := state.places[place_id].cell + return cell != INVALID_CT_CELL && int(cell) < len(state.cells) && state.cells[cell].live +} + +ct_value_references_dead_storage :: proc(state: ^Ct_State, id: Ct_Value_Id) -> bool { + if id == INVALID_CT_VALUE || int(id) >= len(state.values) { + return false + } + value := state.values[id] + if value.kind == .Pointer || value.kind == .Slice { + return !ct_place_live(state, Ct_Place_Id(value.index)) + } + for child in ct_child_slice(state, value) { + if child != INVALID_CT_VALUE && ct_value_references_dead_storage(state, child) { + return true + } + } + return false +} + ct_materialize_value :: proc( state: ^Ct_State, id: Ct_Value_Id, @@ -545,6 +830,13 @@ ct_materialize_value :: proc( target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) + case .Pointer, .Slice: + if state.diagnostic == source.INVALID_DIAGNOSTIC { + state.diagnostic = source.add(checker.diagnostics, span, "comptime storage pointers and slices cannot materialize as runtime memory") + } + return invalid_hir_expr(checker, span, state.diagnostic, value.type) + case .Function: + return build_function_value(checker, ast.Function_Id(u32(value.index)), span, expected) case .None: return add_hir_expr(checker, hir.Expr{ kind=.None, span=span, type=value.type, @@ -615,7 +907,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 state.bindings[index].value, ct_flow(.Normal), true + return ct_binding_value(state, index), ct_flow(.Normal), true } if value, ok := current_comptime_value(checker, expr.name); ok { if value.kind == .Integer { @@ -624,6 +916,11 @@ ct_eval_expr :: proc( } return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "type parameter '%s' is not a runtime value", symbol_text(checker, expr.name)) } + } else if find_import(checker, state.file, expr.qualifier) == ast.INVALID_IMPORT { + if index, ok := ct_find_binding_index(state, expr.qualifier); ok { + base := ct_binding_value(state, index) + return ct_eval_field_value(state, base, expr.name, expr.span) + } } if enum_type, enum_ok := enum_type_from_name_expr(checker, expr, state.pkg, state.file); enum_ok { member, ok := find_enum_member(checker, enum_type, expr.name) @@ -638,6 +935,21 @@ ct_eval_expr :: proc( } global := find_global(checker, expr.name, target_pkg) if global == ast.INVALID_GLOBAL || int(global) >= len(checker.ast_module.globals) { + template := find_template(checker, expr.name, target_pkg) + if template != ast.INVALID_FUNCTION { + pointer_type, _, function_ok := function_pointer_type_for_template( + checker, + template, + state.demanded, + state.demanded != nil, + ) + if function_ok { + return ct_add_value(state, Ct_Value{ + kind=.Function, type=pointer_type, index=u64(template), + }), ct_flow(.Normal), true + } + return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "function '%s' is not comptime-callable as a value", symbol_text(checker, expr.name)) + } return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unresolved comptime value '%s'", symbol_text(checker, expr.name)) } g := checker.ast_module.globals[global] @@ -680,10 +992,6 @@ ct_eval_expr :: proc( } return ct_eval_field_value(state, base_id, expr.name, expr.span) case .Index: - base_id, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) - if !ok || flow.kind != .Normal { - return INVALID_CT_VALUE, flow, ok - } index_id, index_flow, index_ok := ct_eval_expr(state, expr.right, types.USIZE, depth+1) if !index_ok || index_flow.kind != .Normal { return INVALID_CT_VALUE, index_flow, index_ok @@ -692,6 +1000,43 @@ ct_eval_expr :: proc( if !index_is_int || index_value < 0 || index_value > i128(0x7fff_ffff) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be a non-negative integer") } + base_id, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) + if !ok || flow.kind != .Normal { + return INVALID_CT_VALUE, flow, ok + } + if base_id != INVALID_CT_VALUE && int(base_id) < len(state.values) { + base := state.values[base_id] + if base.kind == .Slice { + place, _, _ := ct_slice_element_place(state, base, int(index_value)) + if place == INVALID_CT_PLACE { + 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 base.kind == .Pointer { + pointer_item, pointer_ok := types.node(store, base.type) + if pointer_ok && pointer_item.kind == .Pointer { + if pointer_item.many { + place, _, _ := ct_pointer_place(state, base, int(index_value)) + if place == INVALID_CT_PLACE { + 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 array_item, array_ok := types.node(store, pointer_item.child); array_ok && array_item.kind == .Array { + base_place, _, _ := ct_pointer_place(state, base) + if base_place == INVALID_CT_PLACE || int(index_value) >= int(array_item.count) { + return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime array index out of bounds") + } + 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 + } + } + } + } return ct_eval_index_value(state, base_id, int(index_value), expr.span) case .Unwrap: value, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) @@ -781,8 +1126,30 @@ ct_eval_expr :: proc( return INVALID_CT_VALUE, flow, ok } return ct_scalar_cast(state, value, target, expr.span) - case .Address, .Deref, .Slice: - return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "pointers and slices are not supported in comptime evaluation yet") + case .Address: + place, place_type, writable, flow, ok := ct_eval_place(state, expr.left, depth+1) + if !ok || flow.kind != .Normal { + return INVALID_CT_VALUE, flow, ok + } + result_type := types.pointer(store, place_type, writable, false) + if types.is_pointer(expected, store) && + types.equal(types.child_type(expected, store), place_type) && + (!types.is_mutable(expected, store) || writable) { + result_type = expected + } + return ct_add_value(state, Ct_Value{kind=.Pointer, type=result_type, index=u64(place), active=-1}), ct_flow(.Normal), true + case .Deref: + place, _, _, flow, ok := ct_eval_place(state, expr_id, depth+1) + if !ok || flow.kind != .Normal { + return INVALID_CT_VALUE, flow, ok + } + value, value_ok := ct_place_get(state, place) + 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 + case .Slice: + return ct_eval_slice_expr(state, expr, depth+1) case .Type, .Undefined, .Keyed: return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression cannot be evaluated at comptime") } @@ -914,6 +1281,90 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty return ct_add_value(state, Ct_Value{kind=.Struct, type=struct_type, start=start, count=u32(len(values)), active=active_field}), ct_flow(.Normal), true } +ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) { + checker := state.checker + store := &checker.module.types + base_value, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) + if !ok || flow.kind != .Normal { + return INVALID_CT_VALUE, flow, ok + } + container_place := INVALID_CT_PLACE + container_start := 0 + container_len := 0 + item: types.Node + item_ok := false + if base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) { + base := state.values[base_value] + if base.kind == .Slice { + container_place = Ct_Place_Id(base.index) + container_start = int(base.start) + container_len = int(base.count) + item, item_ok = types.node(store, base.type) + } else if base.kind == .Pointer { + pointer_item, pointer_ok := types.node(store, base.type) + array_item: types.Node + array_ok := false + if pointer_ok { + array_item, array_ok = types.node(store, pointer_item.child) + } + if pointer_ok && !pointer_item.many && array_ok && array_item.kind == .Array { + container_place, _, _ = ct_pointer_place(state, base) + container_len = int(array_item.count) + item = array_item + item.mutable = pointer_item.mutable && array_item.mutable + item_ok = true + } + } + } + if container_place == INVALID_CT_PLACE { + place, place_type, writable, place_flow, place_ok := ct_eval_place(state, expr.left, depth+1) + if !place_ok || place_flow.kind != .Normal { + return INVALID_CT_VALUE, place_flow, place_ok + } + array_item, array_ok := types.node(store, place_type) + if !array_ok || array_item.kind != .Array { + return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slicing requires a comptime array, slice, or pointer-to-array") + } + container_place = place + container_len = int(array_item.count) + item = array_item + item.mutable = writable && array_item.mutable + item_ok = true + } + if !item_ok { + return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slicing requires a comptime array, slice, or pointer-to-array") + } + start := 0 + end := container_len + for bound, index in expr.args { + if bound == ast.INVALID_EXPR { + continue + } + value, bound_flow, bound_ok := ct_eval_expr(state, bound, types.USIZE, depth+1) + if !bound_ok || bound_flow.kind != .Normal { + return INVALID_CT_VALUE, bound_flow, bound_ok + } + integer, integer_ok := ct_integer_value(state, value) + if !integer_ok || integer < 0 || integer > i128(0x7fff_ffff) { + return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slice bounds must be non-negative integers") + } + if index == 0 { + start = int(integer) + } else { + end = int(integer) + } + } + if start < 0 || end < start || end > container_len { + return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice bounds out of range") + } + preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR + result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, item.sentinel) + return ct_add_value(state, Ct_Value{ + kind=.Slice, type=result_type, index=u64(container_place), + start=u32(container_start+start), count=u32(end-start), + }), ct_flow(.Normal), true +} + ct_eval_enum_literal :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) { checker := state.checker store := &checker.module.types @@ -969,6 +1420,51 @@ ct_eval_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, name: symbol } return ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=length}), ct_flow(.Normal), true } + if base.kind == .Slice && field_name == "len" { + return ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(base.count)}), ct_flow(.Normal), true + } + if base.kind == .Pointer && field_name == "len" { + if length, ok := ct_sequence_length(state, base); ok { + return ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(length)}), ct_flow(.Normal), true + } + } + if (base.kind == .Slice || base.kind == .Pointer) && field_name == "ptr" { + item, ok := types.container(base.type, &checker.module.types) + if !ok || item.kind == .Pointer { + return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime value does not expose '.ptr'") + } + container_place := INVALID_CT_PLACE + offset := 0 + if base.kind == .Slice { + container_place = Ct_Place_Id(base.index) + offset = int(base.start) + } else { + container_place, _, _ = ct_pointer_place(state, base) + } + if container_place == INVALID_CT_PLACE { + return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime pointer no longer points to live storage") + } + return ct_add_value(state, Ct_Value{ + kind=.Pointer, type=container_pointer_type(&checker.module.types, item), + index=u64(container_place), active=i64(offset), + }), ct_flow(.Normal), true + } + if base.kind == .Pointer { + place, child, _ := ct_pointer_place(state, base) + if place == INVALID_CT_PLACE { + return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime pointer no longer points to live storage") + } + if types.is_record(child, &checker.module.types) { + index, field, ok := find_struct_field(checker, child, name) + if !ok { + return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "unknown struct field '%s'", field_name) + } + 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 + } + } + } if base.kind != .Struct { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "unknown comptime field '%s'", field_name) } @@ -1011,6 +1507,238 @@ ct_eval_index_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index: int, return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "indexing requires a comptime array or string") } +ct_sequence_length :: proc(state: ^Ct_State, value: Ct_Value) -> (int, bool) { + checker := state.checker + #partial switch value.kind { + case .Array: + return int(value.count), true + case .String: + if value.index < u64(len(checker.ast_module.strings)) { + return len(checker.ast_module.strings[value.index]), true + } + case .Slice: + return int(value.count), true + case .Pointer: + child := types.child_type(value.type, &checker.module.types) + item, ok := types.node(&checker.module.types, child) + if ok && item.kind == .Array { + return int(item.count), true + } + } + return 0, false +} + +ct_pointer_place :: proc(state: ^Ct_State, value: Ct_Value, extra_index := 0) -> (Ct_Place_Id, types.Type, bool) { + if value.kind != .Pointer { + return INVALID_CT_PLACE, types.INVALID, false + } + store := &state.checker.module.types + pointer_item, ok := types.node(store, value.type) + if !ok || pointer_item.kind != .Pointer { + return INVALID_CT_PLACE, types.INVALID, false + } + base := Ct_Place_Id(value.index) + child := pointer_item.child + if base == INVALID_CT_PLACE || int(base) >= len(state.places) || !ct_place_live(state, base) { + return INVALID_CT_PLACE, types.INVALID, false + } + if pointer_item.many { + index := int(value.active)+extra_index + if index < 0 { + return INVALID_CT_PLACE, types.INVALID, false + } + place := state.places[base] + item, item_ok := types.node(store, place.type) + if !item_ok || item.kind != .Array || index >= int(item.count) { + return INVALID_CT_PLACE, types.INVALID, false + } + return ct_extend_place( + state, base, Ct_Path_Elem{kind=.Index, index=u32(index)}, + child, place.writable && item.mutable, + ), child, pointer_item.mutable + } + return base, child, pointer_item.mutable +} + +ct_slice_element_place :: proc(state: ^Ct_State, value: Ct_Value, index: int) -> (Ct_Place_Id, types.Type, bool) { + if value.kind != .Slice || index < 0 || index >= int(value.count) { + return INVALID_CT_PLACE, types.INVALID, false + } + store := &state.checker.module.types + item, ok := types.node(store, value.type) + if !ok || item.kind != .Slice { + return INVALID_CT_PLACE, types.INVALID, false + } + base := Ct_Place_Id(value.index) + return ct_extend_place( + state, base, Ct_Path_Elem{kind=.Index, index=u32(int(value.start)+index)}, + item.child, item.mutable, + ), item.child, item.mutable +} + +ct_eval_place :: proc( + state: ^Ct_State, + expr_id: ast.Expr_Id, + depth: int, +) -> (Ct_Place_Id, types.Type, bool, Ct_Flow, bool) { + checker := state.checker + if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), false + } + expr := checker.ast_module.exprs[expr_id] + store := &checker.module.types + #partial switch expr.kind { + case .Name: + if !symbol.is_valid(expr.qualifier) { + if index, ok := ct_find_binding_index(state, expr.name); ok { + place := ct_binding_place(state, index) + if place != INVALID_CT_PLACE { + return place, state.bindings[index].type, state.bindings[index].mutable, ct_flow(.Normal), true + } + } + } else if find_import(checker, state.file, expr.qualifier) == ast.INVALID_IMPORT { + if index, ok := ct_find_binding_index(state, expr.qualifier); ok { + base_value := ct_binding_value(state, index) + base_place := INVALID_CT_PLACE + base_type := state.bindings[index].type + base_writable := state.bindings[index].mutable + if base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) && + state.values[base_value].kind == .Pointer { + base_place, base_type, base_writable = ct_pointer_place(state, state.values[base_value]) + } else { + base_place = ct_binding_place(state, index) + } + if base_place != INVALID_CT_PLACE && types.is_record(base_type, store) { + field_index, field, field_ok := find_struct_field(checker, base_type, expr.name) + if !field_ok { + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name)) + } + return ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Field, index=u32(field_index)}, field.type, base_writable), + field.type, base_writable, ct_flow(.Normal), true + } + } + } + target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false) + if !available { + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable imported package") + } + global := find_global(checker, expr.name, target_pkg) + if global == ast.INVALID_GLOBAL || int(global) >= len(checker.ast_module.globals) { + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unresolved comptime value '%s'", symbol_text(checker, expr.name)) + } + g := checker.ast_module.globals[global] + if g.external || !g.immutable || g.writable { + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "global '%s' is not comptime-known", symbol_text(checker, expr.name)) + } + global_expected := type_from_syntax(checker, g.type, g.pkg, g.file) + value, flow, ok := ct_eval_expr(state, g.expr, global_expected, depth+1) + if !ok || flow.kind != .Normal { + return INVALID_CT_PLACE, types.INVALID, false, flow, ok + } + cell := ct_add_cell(state, value, false) + place := ct_add_place(state, cell, state.values[value].type, false) + return place, state.values[value].type, false, ct_flow(.Normal), true + case .Deref: + pointer, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) + if !ok || flow.kind != .Normal { + return INVALID_CT_PLACE, types.INVALID, false, flow, ok + } + if pointer == INVALID_CT_VALUE || int(pointer) >= len(state.values) || state.values[pointer].kind != .Pointer { + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "postfix '^' requires a comptime pointer") + } + place, child, writable := ct_pointer_place(state, state.values[pointer]) + if place == INVALID_CT_PLACE { + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime pointer no longer points to live storage") + } + return place, child, writable, ct_flow(.Normal), true + case .Field: + base_value, flow, value_ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) + if value_ok && flow.kind == .Normal && base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) && + state.values[base_value].kind == .Pointer { + base_place, base_type, base_writable := ct_pointer_place(state, state.values[base_value]) + if base_place != INVALID_CT_PLACE && types.is_record(base_type, store) { + index, field, field_ok := find_struct_field(checker, base_type, expr.name) + if !field_ok { + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name)) + } + return ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Field, index=u32(index)}, field.type, base_writable), + field.type, base_writable, ct_flow(.Normal), true + } + } + if !value_ok || flow.kind != .Normal { + return INVALID_CT_PLACE, types.INVALID, false, flow, value_ok + } + base_place, base_type, base_writable, place_flow, place_ok := ct_eval_place(state, expr.left, depth+1) + if !place_ok || place_flow.kind != .Normal { + return INVALID_CT_PLACE, types.INVALID, false, place_flow, place_ok + } + index, field, field_ok := find_struct_field(checker, base_type, expr.name) + if !field_ok { + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name)) + } + return ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Field, index=u32(index)}, field.type, base_writable), + field.type, base_writable, ct_flow(.Normal), true + case .Index: + index_value, index_flow, index_ok := ct_eval_expr(state, expr.right, types.USIZE, depth+1) + if !index_ok || index_flow.kind != .Normal { + return INVALID_CT_PLACE, types.INVALID, false, index_flow, index_ok + } + index_int, int_ok := ct_integer_value(state, index_value) + if !int_ok || index_int < 0 || index_int > i128(0x7fff_ffff) { + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be a non-negative integer") + } + base_value, flow, value_ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) + if !value_ok || flow.kind != .Normal { + return INVALID_CT_PLACE, types.INVALID, false, flow, value_ok + } + if base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) { + base := state.values[base_value] + if base.kind == .Slice { + place, child, writable := ct_slice_element_place(state, base, int(index_int)) + if place == INVALID_CT_PLACE { + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice index out of bounds") + } + return place, child, writable, ct_flow(.Normal), true + } + if base.kind == .Pointer { + pointer_item, pointer_ok := types.node(store, base.type) + if pointer_ok && pointer_item.kind == .Pointer { + if pointer_item.many { + place, child, writable := ct_pointer_place(state, base, int(index_int)) + if place == INVALID_CT_PLACE { + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime pointer index out of bounds") + } + return place, child, writable, ct_flow(.Normal), true + } + array_item, array_ok := types.node(store, pointer_item.child) + if array_ok && array_item.kind == .Array { + base_place, _, writable := ct_pointer_place(state, base) + if int(index_int) >= int(array_item.count) { + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime array index out of bounds") + } + return ct_extend_place( + state, base_place, Ct_Path_Elem{kind=.Index, index=u32(index_int)}, + array_item.child, writable && array_item.mutable, + ), array_item.child, writable && array_item.mutable, ct_flow(.Normal), true + } + } + } + } + base_place, base_type, base_writable, place_flow, place_ok := ct_eval_place(state, expr.left, depth+1) + if !place_ok || place_flow.kind != .Normal { + return INVALID_CT_PLACE, types.INVALID, false, place_flow, place_ok + } + item, item_ok := types.node(store, base_type) + if !item_ok || item.kind != .Array || int(index_int) >= int(item.count) { + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime array index out of bounds") + } + writable := base_writable && item.mutable + return ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Index, index=u32(index_int)}, item.child, writable), + item.child, writable, ct_flow(.Normal), true + } + return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression is not addressable at comptime") +} + ct_unwrap_optional :: 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 @@ -1172,7 +1900,14 @@ ct_scalar_cast :: proc(state: ^Ct_State, id: Ct_Value_Id, target: types.Type, sp ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) { checker := state.checker if expr.left != ast.INVALID_EXPR { - return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "function-pointer calls are not supported in comptime evaluation yet") + callee, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) + if !ok || flow.kind != .Normal { + return INVALID_CT_VALUE, flow, ok + } + if callee == INVALID_CT_VALUE || int(callee) >= len(state.values) || state.values[callee].kind != .Function { + return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime function-pointer call requires a known function value") + } + return ct_eval_template_call(state, ast.Function_Id(u32(state.values[callee].index)), expr.args, expr.span, expected, depth+1) } target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false) if !available { @@ -1180,60 +1915,126 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type } template := find_template(checker, expr.name, target_pkg) if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) { + if !symbol.is_valid(expr.qualifier) { + if index, ok := ct_find_binding_index(state, expr.name); ok { + value := ct_binding_value(state, index) + if value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Function { + return ct_eval_template_call(state, ast.Function_Id(u32(state.values[value].index)), expr.args, expr.span, expected, depth+1) + } + } + } + global := find_global(checker, expr.name, target_pkg) + if global != ast.INVALID_GLOBAL && int(global) < len(checker.ast_module.globals) { + g := checker.ast_module.globals[global] + if !g.external && g.immutable && !g.writable { + global_expected := type_from_syntax(checker, g.type, g.pkg, g.file) + value, flow, ok := ct_eval_expr(state, g.expr, global_expected, depth+1) + if !ok || flow.kind != .Normal { + return INVALID_CT_VALUE, flow, ok + } + if value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Function { + return ct_eval_template_call(state, ast.Function_Id(u32(state.values[value].index)), expr.args, expr.span, expected, depth+1) + } + } + } return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name)) } + return ct_eval_template_call(state, template, expr.args, expr.span, expected, depth+1) +} + +ct_eval_template_call :: proc( + state: ^Ct_State, + template: ast.Function_Id, + args: []ast.Expr_Id, + span: source.Span, + expected: types.Type, + depth: int, +) -> (Ct_Value_Id, Ct_Flow, bool) { + checker := state.checker + if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) { + return INVALID_CT_VALUE, ct_flow(.Normal), false + } function := checker.ast_module.functions[template] - if function.c_abi || !function.has_body || len(function.unsupported_reason) > 0 { - return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "function '%s' is runtime-only", symbol_text(checker, expr.name)) + if !function.has_body || len(function.unsupported_reason) > 0 { + return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "function '%s' is runtime-only", symbol_text(checker, function.name)) } - if !valid_call_arity(function, len(expr.args)) { - return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "function '%s' arity mismatch", symbol_text(checker, expr.name)) + if !valid_call_arity(function, len(args)) { + return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "function '%s' arity mismatch", symbol_text(checker, function.name)) } - comptime_values, comptime_ok := collect_comptime_values(checker, function, expr.args, state.pkg, state.file, false, checker.current_comptime_values) + comptime_values, comptime_ok := collect_comptime_values(checker, function, args, state.pkg, state.file, false, checker.current_comptime_values) defer delete(comptime_values, checker.allocator) if !comptime_ok { - return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "invalid comptime argument for '%s'", symbol_text(checker, expr.name)) + return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "invalid comptime argument for '%s'", symbol_text(checker, function.name)) } previous_comptime := checker.current_comptime_values checker.current_comptime_values = comptime_values defer checker.current_comptime_values = previous_comptime result_type := function_channel_type(checker, function) - call_state := ct_state_make(checker, function.pkg, function.file, result_type, comptime_values, diagnose=!state.silent) - defer ct_state_destroy(&call_state) - call_state.steps = state.steps + runtime_values: [dynamic]Ct_Value_Id + runtime_values.allocator = checker.allocator + runtime_types: [dynamic]types.Type + runtime_types.allocator = checker.allocator + runtime_names: [dynamic]symbol.Id + runtime_names.allocator = checker.allocator + defer { + delete(runtime_values) + delete(runtime_types) + delete(runtime_names) + } for param, index in function.params { if param.comptime_value { continue } param_type := type_from_syntax(checker, param.type, function.pkg, function.file) - if index >= len(expr.args) { + if index >= len(args) { return INVALID_CT_VALUE, ct_flow(.Normal), false } - value, flow, ok := ct_eval_expr(state, expr.args[index], param_type, depth+1) + value, flow, ok := ct_eval_expr(state, args[index], param_type, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } - value, ok = ct_coerce_value(state, value, param_type, checker.ast_module.exprs[expr.args[index]].span) + value, ok = ct_coerce_value(state, value, param_type, checker.ast_module.exprs[args[index]].span) if !ok { return INVALID_CT_VALUE, ct_flow(.Normal), false } - copied := ct_clone_value(&call_state, state, value) - append(&call_state.bindings, Ct_Binding{name=param.name, type=param_type, value=copied, mutable=false}) + append(&runtime_values, value) + append(&runtime_types, param_type) + append(&runtime_names, param.name) } - flow, ok := ct_exec_statements(&call_state, function.body, false, depth+1) - state.steps = call_state.steps + previous_pkg := state.pkg + previous_file := state.file + previous_result := state.result + state.pkg = function.pkg + state.file = function.file + state.result = result_type + defer { + state.pkg = previous_pkg + state.file = previous_file + state.result = previous_result + } + param_start := len(state.bindings) + for value, index in runtime_values { + ct_bind_value(state, runtime_names[index], runtime_types[index], value, false) + } + flow, ok := ct_exec_statements(state, function.body, false, depth+1) + ct_pop_bindings(state, param_start) if !ok { - state.error = call_state.error - state.diagnostic = call_state.diagnostic return INVALID_CT_VALUE, ct_flow(.Normal), false } if flow.kind != .Return { - return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "comptime function '%s' did not return a value", symbol_text(checker, expr.name)) + if flow.kind == .Normal && types.is_void(result_type) { + result := ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID}) + return result, ct_flow(.Normal), true + } + 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_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") } - result := ct_clone_value(state, &call_state, flow.value) if types.is_valid(expected) { - return ct_coerce_expr_value(state, result, expected, expr.span) + return ct_coerce_expr_value(state, result, expected, span) } return result, ct_flow(.Normal), true } @@ -1318,10 +2119,10 @@ ct_eval_catch_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Typ scope_start := len(state.bindings) if symbol.is_valid(expr.name) && expr.name != checker.sink_symbol && payload != INVALID_CT_VALUE { error_type := types.fallible_error(value.type, &checker.module.types) - append(&state.bindings, Ct_Binding{name=expr.name, type=error_type, value=payload, mutable=false}) + ct_bind_value(state, expr.name, error_type, payload, false) } handler, handler_ok := ct_exec_statements(state, expr.body, true, depth+1) - resize(&state.bindings, scope_start) + ct_pop_bindings(state, scope_start) if !handler_ok { return INVALID_CT_VALUE, handler, false } @@ -1403,7 +2204,7 @@ ct_exec_statements :: proc( scope_start := len(state.bindings) defer_start := len(state.defers) defer { - resize(&state.bindings, scope_start) + ct_pop_bindings(state, scope_start) resize(&state.defers, defer_start) } for statement_id in statements { @@ -1428,7 +2229,7 @@ ct_exec_statements :: proc( value, ok = ct_coerce_value(state, value, declared, statement.span) } if ok && statement.name != checker.sink_symbol { - append(&state.bindings, Ct_Binding{name=statement.name, type=state.values[value].type, value=value, mutable=!statement.immutable}) + ct_bind_value(state, statement.name, state.values[value].type, value, !statement.immutable) } } else if ok { ok = ct_fail(state, .Not_Comptime, statement.span, "comptime value block must yield") @@ -1444,7 +2245,7 @@ ct_exec_statements :: proc( value, ok = ct_coerce_value(state, value, expected, statement.span) } if ok && statement.name != checker.sink_symbol { - append(&state.bindings, Ct_Binding{name=statement.name, type=state.values[value].type, value=value, mutable=!statement.immutable}) + ct_bind_value(state, statement.name, state.values[value].type, value, !statement.immutable) } } } @@ -1540,12 +2341,11 @@ ct_flush_defers :: proc(state: ^Ct_State, start: int, depth: int) -> bool { ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) -> (Ct_Flow, bool) { checker := state.checker name := statement.name + target_expr := statement.target if statement.target != ast.INVALID_EXPR { target := checker.ast_module.exprs[statement.target] if target.kind == .Name && !symbol.is_valid(target.qualifier) { name = target.name - } else { - return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime assignment only supports local targets in V1") } } if name == checker.sink_symbol { @@ -1556,14 +2356,28 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) -> _, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1) return flow, ok } - index, found := ct_find_binding_index(state, name) - if !found { - return ct_flow(.Normal), ct_failf(state, .Not_Comptime, statement.span, "cannot assign unresolved comptime local '%s'", symbol_text(checker, name)) + place := INVALID_CT_PLACE + expected := types.INVALID + writable := false + if target_expr != ast.INVALID_EXPR { + place_flow: Ct_Flow + place_ok := false + place, expected, writable, place_flow, place_ok = ct_eval_place(state, target_expr, depth+1) + if !place_ok || place_flow.kind != .Normal { + return place_flow, place_ok + } + } else { + index, found := ct_find_binding_index(state, name) + if !found { + return ct_flow(.Normal), ct_failf(state, .Not_Comptime, statement.span, "cannot assign unresolved comptime local '%s'", symbol_text(checker, name)) + } + place = ct_binding_place(state, index) + expected = state.bindings[index].type + writable = state.bindings[index].mutable } - if !state.bindings[index].mutable { - return ct_flow(.Normal), ct_failf(state, .Not_Comptime, statement.span, "cannot assign immutable comptime local '%s'", symbol_text(checker, name)) + if !writable { + return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime assignment target is not writable") } - expected := state.bindings[index].type value := INVALID_CT_VALUE flow := ct_flow(.Normal) ok := true @@ -1580,7 +2394,10 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) -> return flow, ok } if statement.assignment_op != .Set { - current := state.bindings[index].value + current, current_ok := ct_place_get(state, place) + if !current_ok { + return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime assignment target no longer points to live storage") + } op := ast.Expr_Kind.Add #partial switch statement.assignment_op { case .Sub: op = .Sub @@ -1598,8 +2415,9 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) -> if !ok { return ct_flow(.Normal), false } - state.bindings[index].value = value - state.bindings[index].type = state.values[value].type + if !ct_place_set(state, place, value) { + return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime assignment target is not writable") + } return ct_flow(.Normal), true } @@ -1629,7 +2447,7 @@ ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, d for operand, index in operands { value, flow, ok := ct_eval_expr(state, operand, types.INVALID, depth+1) if !ok || flow.kind != .Normal { - resize(&state.bindings, scope_start) + ct_pop_bindings(state, scope_start) return flow, ok } v := state.values[value] @@ -1638,30 +2456,30 @@ ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, d break } if v.kind != .Optional_Some { - resize(&state.bindings, scope_start) + ct_pop_bindings(state, scope_start) return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' unwrap requires an optional value") } children := ct_child_slice(state, v) if len(children) > 0 && statement.captures[index] != checker.sink_symbol { - append(&state.bindings, Ct_Binding{name=statement.captures[index], type=state.values[children[0]].type, value=children[0], mutable=false}) + ct_bind_value(state, statement.captures[index], state.values[children[0]].type, children[0], false) } } if matched && statement.guard != ast.INVALID_EXPR { guard, flow, ok := ct_eval_expr(state, statement.guard, types.BOOL, depth+1) if !ok || flow.kind != .Normal { - resize(&state.bindings, scope_start) + ct_pop_bindings(state, scope_start) return flow, ok } guard_value, guard_ok := ct_bool_value(state, guard) if !guard_ok { - resize(&state.bindings, scope_start) + ct_pop_bindings(state, scope_start) return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' unwrap guard must be a bool") } matched = guard_value } body := statement.body if matched else statement.else_body flow, ok := ct_exec_statements(state, body, yield_returns, depth+1) - resize(&state.bindings, scope_start) + ct_pop_bindings(state, scope_start) return flow, ok } @@ -1713,15 +2531,16 @@ ct_exec_while :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool ct_exec_for :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) { checker := state.checker - if statement.pointer_capture { - return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime for-loops do not support pointer captures") - } + store := &checker.module.types iterable, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return flow, ok } value := state.values[iterable] if value.kind == .Range { + if statement.pointer_capture { + return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "range loops do not support pointer captures") + } children := ct_child_slice(state, value) if len(children) < 2 { return ct_flow(.Normal), false @@ -1735,13 +2554,13 @@ ct_exec_for :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, for current := start; current < end || (value.active != 0 && current == end); current += 1 { scope_start := len(state.bindings) item := ct_add_value(state, Ct_Value{kind=.Integer, type=types.child_type(value.type, &checker.module.types), integer=current}) - append(&state.bindings, Ct_Binding{name=statement.name, type=state.values[item].type, value=item, mutable=false}) + ct_bind_value(state, statement.name, state.values[item].type, item, false) if symbol.is_valid(statement.index_name) { idx := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=index}) - append(&state.bindings, Ct_Binding{name=statement.index_name, type=types.USIZE, value=idx, mutable=false}) + ct_bind_value(state, statement.index_name, types.USIZE, idx, false) } body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1) - resize(&state.bindings, scope_start) + ct_pop_bindings(state, scope_start) if !body_ok { return body_flow, false } @@ -1757,15 +2576,49 @@ ct_exec_for :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, } if value.kind == .Array { children := ct_child_slice(state, value) + if statement.pointer_capture { + base_place, _, base_writable, place_flow, place_ok := ct_eval_place(state, statement.expr, depth+1) + if !place_ok || place_flow.kind != .Normal { + return place_flow, place_ok + } + item, item_ok := types.node(store, value.type) + if !item_ok || item.kind != .Array { + return ct_flow(.Normal), false + } + for _, index in children { + scope_start := len(state.bindings) + elem_writable := base_writable && item.mutable + elem_place := ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Index, index=u32(index)}, item.child, elem_writable) + pointer_type := types.pointer(store, item.child, elem_writable, false) + pointer := ct_add_value(state, Ct_Value{kind=.Pointer, type=pointer_type, index=u64(elem_place), active=-1}) + ct_bind_value(state, statement.name, pointer_type, pointer, false) + if symbol.is_valid(statement.index_name) { + idx := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)}) + ct_bind_value(state, statement.index_name, types.USIZE, idx, false) + } + body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1) + ct_pop_bindings(state, scope_start) + if !body_ok { + return body_flow, false + } + if ct_loop_consumes_flow(body_flow, statement.label, false) { + return ct_flow(.Normal), true + } + if body_flow.kind != .Normal && !ct_loop_consumes_flow(body_flow, statement.label, true) { + return body_flow, true + } + } + return ct_flow(.Normal), true + } for child, index in children { scope_start := len(state.bindings) - append(&state.bindings, Ct_Binding{name=statement.name, type=state.values[child].type, value=child, mutable=false}) + ct_bind_value(state, statement.name, state.values[child].type, child, false) if symbol.is_valid(statement.index_name) { idx := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)}) - append(&state.bindings, Ct_Binding{name=statement.index_name, type=types.USIZE, value=idx, mutable=false}) + ct_bind_value(state, statement.index_name, types.USIZE, idx, false) } body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1) - resize(&state.bindings, scope_start) + ct_pop_bindings(state, scope_start) if !body_ok { return body_flow, false } @@ -1778,7 +2631,71 @@ ct_exec_for :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, } return ct_flow(.Normal), true } - return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime for-loop iterable must be a range or array") + if value.kind == .Slice || value.kind == .Pointer { + count := 0 + array_item: types.Node + is_pointer_array := false + if value.kind == .Slice { + count = int(value.count) + } else { + pointer_item, pointer_ok := types.node(store, value.type) + if pointer_ok && !pointer_item.many { + array_item, is_pointer_array = types.node(store, pointer_item.child) + is_pointer_array = is_pointer_array && array_item.kind == .Array + if is_pointer_array { + count = int(array_item.count) + } + } + } + if value.kind == .Pointer && !is_pointer_array { + return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime for-loop pointer iterable must point to an array") + } + for index in 0.. (Ct_Flow, bool) { @@ -1788,6 +2705,25 @@ ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool return flow, ok } subject_value := state.values[subject] + wants_pointer := false + for arm_id in statement.body { + arm := checker.ast_module.statements[arm_id] + wants_pointer = wants_pointer || (arm.kind == .Match_Arm && arm.pointer_capture) + } + subject_place := INVALID_CT_PLACE + subject_writable := false + if wants_pointer { + place_type: types.Type + place_flow: Ct_Flow + place_ok := false + subject_place, place_type, subject_writable, place_flow, place_ok = ct_eval_place(state, statement.expr, depth+1) + if !place_ok || place_flow.kind != .Normal { + return place_flow, place_ok + } + if !types.is_tagged_union(place_type, &checker.module.types) { + return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime match pointer captures require an addressable tagged-union subject") + } + } for arm_id in statement.body { arm := checker.ast_module.statements[arm_id] if arm.kind != .Match_Arm { @@ -1795,6 +2731,8 @@ ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool } matched := len(arm.patterns) == 0 payload := INVALID_CT_VALUE + payload_field := -1 + payload_type := types.INVALID if !matched { for pattern_id in arm.patterns { pattern := checker.ast_module.exprs[pattern_id] @@ -1802,8 +2740,10 @@ ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool if pattern.kind != .Enum_Literal { continue } - if field_index, _, found := find_struct_field(checker, subject_value.type, pattern.name); found && field_index == int(subject_value.active) { + if field_index, field, found := find_struct_field(checker, subject_value.type, pattern.name); found && field_index == int(subject_value.active) { matched = true + payload_field = field_index + payload_type = field.type children := ct_child_slice(state, subject_value) if len(children) > 0 { payload = children[0] @@ -1832,24 +2772,34 @@ ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool if len(arm.captures) > 0 && payload != INVALID_CT_VALUE { capture := arm.captures[0] if arm.pointer_capture { - resize(&state.bindings, scope_start) - return ct_flow(.Normal), ct_fail(state, .Not_Comptime, arm.span, "comptime match does not support pointer captures") - } - if capture != checker.sink_symbol { - append(&state.bindings, Ct_Binding{name=capture, type=state.values[payload].type, value=payload, mutable=false}) + if subject_place == INVALID_CT_PLACE || payload_field < 0 || !types.is_valid(payload_type) { + ct_pop_bindings(state, scope_start) + return ct_flow(.Normal), ct_fail(state, .Not_Comptime, arm.span, "comptime match pointer capture requires a tagged-union payload") + } + payload_place := ct_extend_place( + state, subject_place, Ct_Path_Elem{kind=.Field, index=u32(payload_field)}, + payload_type, subject_writable, + ) + pointer_type := types.pointer(&checker.module.types, payload_type, subject_writable, false) + pointer := ct_add_value(state, Ct_Value{kind=.Pointer, type=pointer_type, index=u64(payload_place), active=-1}) + if capture != checker.sink_symbol { + ct_bind_value(state, capture, pointer_type, pointer, false) + } + } else if capture != checker.sink_symbol { + ct_bind_value(state, capture, state.values[payload].type, payload, false) } } if yield_returns && len(arm.body) == 1 && checker.ast_module.statements[arm.body[0]].kind == .Expression { expr_stmt := checker.ast_module.statements[arm.body[0]] value, expr_flow, expr_ok := ct_eval_expr(state, expr_stmt.expr, types.INVALID, depth+1) - resize(&state.bindings, scope_start) + ct_pop_bindings(state, scope_start) if !expr_ok || expr_flow.kind != .Normal { return expr_flow, expr_ok } return ct_flow(.Yield, value), true } arm_flow, arm_ok := ct_exec_statements(state, arm.body, yield_returns, depth+1) - resize(&state.bindings, scope_start) + ct_pop_bindings(state, scope_start) return arm_flow, arm_ok } return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime match did not select an arm") @@ -1989,8 +2939,9 @@ infer_comptime_expr_type :: proc( expr: ast.Expr, pkg: ast.Package_Id, file: ast.File_Id, + demanded: ^[dynamic]Spec_Id = nil, ) -> types.Type { - state := ct_state_make(checker, pkg, file, diagnose=false) + state := ct_state_make(checker, pkg, file, diagnose=false, demanded=demanded) defer ct_state_destroy(&state) value := INVALID_CT_VALUE flow := ct_flow(.Normal) diff --git a/compiler/parser/parser.odin b/compiler/parser/parser.odin index db0f85c..857b816 100644 --- a/compiler/parser/parser.odin +++ b/compiler/parser/parser.odin @@ -113,7 +113,7 @@ is_type_token :: proc(kind: token.Kind) -> bool { .Keyword_C_Short, .Keyword_C_Ushort, .Keyword_C_Int, .Keyword_C_Uint, .Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong, .Keyword_C_Float, .Keyword_C_Double, .Keyword_C_Longdouble, - .Keyword_Void, .Keyword_Bool, .Keyword_C_Func, .Identifier, .Question, .At, .Star, .Left_Bracket: + .Keyword_Void, .Keyword_Bool, .Keyword_Func, .Keyword_C_Func, .Identifier, .Question, .At, .Star, .Left_Bracket: return true } return false @@ -329,10 +329,11 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax { case .Keyword_Bool: advance(parser) return types.BOOL - case .Keyword_C_Func: + case .Keyword_Func, .Keyword_C_Func: + c_abi := tok.kind == .Keyword_C_Func advance(parser) if _, ok := allow(parser, .Left_Paren); !ok { - source.add(parser.diagnostics, current(parser).span, "expected '(' after c_func type") + source.add(parser.diagnostics, current(parser).span, "expected '(' after function type") return types.INVALID } params, variadic := parse_params(parser) @@ -340,11 +341,19 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax { source.add(parser.diagnostics, current(parser).span, "expected ')' after function type parameters") } result := parse_type(parser) + if _, ok := allow(parser, .Bang); ok { + error_type := parse_error_type(parser) + if c_abi { + source.add(parser.diagnostics, current(parser).span, "c_func pointer types cannot be fallible") + } else { + result = types.fallible(&parser.module.type_store, result, error_type) + } + } param_types := make([]types.Type, len(params), parser.module.allocator) for param, index in params { param_types[index] = param.type } - function_type := types.function(&parser.module.type_store, param_types, result, true, variadic) + function_type := types.function(&parser.module.type_store, param_types, result, c_abi, variadic) delete(param_types, parser.module.allocator) delete(params, parser.module.allocator) return function_type diff --git a/compiler_tests.odin b/compiler_tests.odin index 1999753..5eb4978 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -293,6 +293,43 @@ main func() void {} testing.expect(t, params[0].type == types.C_INT) } +@(test) +parser_accepts_native_function_pointer_types :: proc(t: ^testing.T) { + text := `Error :: enum { + bad +} +take func(callback ?*func(value i32) i32, fallible *func() i32 ! Error) void +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) + module := parser.parse(&stream, &source_file, &diagnostics) + defer ast.destroy_module(&module) + + optional, optional_ok := types.node(&module.type_store, module.functions[0].params[0].type) + pointer, pointer_ok := types.node(&module.type_store, optional.child) + function, function_ok := types.node(&module.type_store, pointer.child) + params := types.params_for(&module.type_store, pointer.child) + fallible_pointer, fallible_pointer_ok := types.node(&module.type_store, module.functions[0].params[1].type) + fallible_function, fallible_function_ok := types.node(&module.type_store, fallible_pointer.child) + fallible, fallible_ok := types.node(&module.type_store, fallible_function.child) + testing.expect_value(t, len(diagnostics.items), 0) + testing.expect(t, optional_ok && optional.kind == .Optional) + testing.expect(t, pointer_ok && pointer.kind == .Pointer && pointer.many && !pointer.mutable) + testing.expect(t, function_ok && function.kind == .Function && !function.c_abi && !function.variadic) + testing.expect(t, function.child == types.I32) + testing.expect_value(t, len(params), 1) + testing.expect(t, params[0].type == types.I32) + testing.expect(t, fallible_pointer_ok && fallible_pointer.kind == .Pointer) + testing.expect(t, fallible_function_ok && fallible_function.kind == .Function && !fallible_function.c_abi) + testing.expect(t, fallible_ok && fallible.kind == .Fallible && fallible.child == types.I32) +} + @(test) parser_accepts_c_function_pointer_alias_types :: proc(t: ^testing.T) { text := `callback_alias :: alias ?*c_func(value i32) i32 @@ -882,13 +919,15 @@ main func() void { defer hir.destroy_module(&hir_module) found_global := false - found_function := false + found_old_function_error := false for diagnostic in diagnostics.items { found_global = found_global || strings.contains(diagnostic.message, "'value' is a global, not a function") - found_function = found_function || strings.contains(diagnostic.message, "'give' is a function, not a global value") + found_old_function_error = + found_old_function_error || + strings.contains(diagnostic.message, "'give' is a function, not a global value") } testing.expect(t, found_global) - testing.expect(t, found_function) + testing.expect(t, !found_old_function_error) } @(test) @@ -2354,6 +2393,10 @@ main func() void { runtime i32 = 1 _ = $runtime _ = $native() + _ = ${ + callback :: native + yield callback() + } _ = $&GLOBAL _ = $spin() _ = $missing() @@ -2375,7 +2418,7 @@ main func() void { defer hir.destroy_module(&hir_module) found_runtime := false - found_external := false + runtime_only_count := 0 found_pointer := false found_quota := false found_missing := false @@ -2383,20 +2426,47 @@ main func() void { for diagnostic in diagnostics.items { message := diagnostic.message found_runtime = found_runtime || strings.contains(message, "unresolved comptime value 'runtime'") - found_external = found_external || strings.contains(message, "runtime-only") - found_pointer = found_pointer || strings.contains(message, "pointers and slices are not supported") + runtime_only_count += 1 if strings.contains(message, "runtime-only") else 0 + found_pointer = found_pointer || strings.contains(message, "comptime storage pointers and slices cannot materialize as runtime memory") found_quota = found_quota || strings.contains(message, "comptime evaluation exceeded the step quota") found_missing = found_missing || strings.contains(message, "did not return a value") found_yield = found_yield || strings.contains(message, "comptime block must yield a value") } testing.expect(t, found_runtime) - testing.expect(t, found_external) + testing.expect(t, runtime_only_count >= 2) testing.expect(t, found_pointer) testing.expect(t, found_quota) testing.expect(t, found_missing) testing.expect(t, found_yield) } +@(test) +native_function_pointer_type_restrictions_are_diagnosed :: proc(t: ^testing.T) { + text := `main func() void { + callback *func(...) void = undefined +} +` + 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_variadic := false + for diagnostic in diagnostics.items { + found_variadic = + found_variadic || + strings.contains(diagnostic.message, "native function pointer types do not support variadic parameters") + } + testing.expect(t, found_variadic) +} + @(test) unused_function_signatures_are_validated_eagerly :: proc(t: ^testing.T) { text := `broken func(value, value i8, nope void) void {} diff --git a/examples/programs/comptime_v1/main.bro b/examples/programs/comptime_v1/main.bro index 4e23e13..a295de5 100644 --- a/examples/programs/comptime_v1/main.bro +++ b/examples/programs/comptime_v1/main.bro @@ -74,6 +74,18 @@ may_fail func(flag bool) i32 ! Error { return 7 } +increment func(value i32) i32 { + return value + 1 +} + +call_native func(callback *func(value i32) i32, value i32) i32 { + return callback(value) +} + +call_fallible func(callback *func(flag bool) i32 ! Error, flag bool) i32 ! Error { + return try callback(flag) +} + use_try func() i32 ! Error { value :: try may_fail(false) return value + 1 @@ -87,6 +99,41 @@ recover func() i32 { } } +bump_ptr func(value @mut i32) void { + value^ += 1 +} + +alias_add func(left @mut i32, right @mut i32) void { + left^ += 2 + right^ += 3 +} + +storage_mutation func() i32 { + values [3]mut i32 = [1, 2, 3] + values[0] += 1 + bump_ptr(&values[1]) + + view []mut i32 = values[..] + for view |@item| { + item^ += 1 + } + + pointer *mut i32 = view.ptr + pointer[2] += 1 + alias_add(&values[0], &view[0]) + + box Box = Box { point = Point { x = 2, y = 3 } } + match box { + .point |@p|: p.x += values[1] + .empty: values[0] = values[0] + } + + if view.len != 3 { + return 0 + } + return values[0] + view[1] + pointer[2] + box.point.x +} + GLOBAL :: $sum_loop(4) main func() i32 { @@ -100,6 +147,15 @@ main func() i32 { optional i32 :: $maybe(true)? tried i32 :: $use_try() catch 0 recovered i32 :: $recover() + storage i32 :: $storage_mutation() + called i32 :: $call_native(increment, 11) + fallible_ok i32 :: $call_fallible(may_fail, false) catch 0 + fallible_err i32 :: $call_fallible(may_fail, true) catch |e| { + result i32 :: match e { + .bad: 13 + } + yield result + } if point.x + point.y != 7 { return 1 @@ -134,5 +190,20 @@ main func() i32 { if GLOBAL != 8 { return 11 } + if storage != 23 { + return 12 + } + if called != 12 { + return 13 + } + if call_native(increment, 20) != 21 { + return 14 + } + if fallible_ok != 7 { + return 15 + } + if fallible_err != 13 { + return 16 + } return 0 }