more comptime eval
This commit is contained in:
+3
-2
@@ -54,7 +54,8 @@ roadmap and milestone history.
|
||||
- demand-monomorphized Brolang and C-ABI functions
|
||||
- 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 comptime expressions such as `$sum(1, 2)` and comptime value blocks such as `${ yield 4 }` for integer constant contexts
|
||||
- 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`
|
||||
- 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
|
||||
- Apple Silicon C ABI lowering for scalars, pointers, fixed-signature plain records/unions, small aggregates, homogeneous float aggregates, and indirect aggregate returns
|
||||
@@ -82,7 +83,7 @@ roadmap and milestone history.
|
||||
|
||||
## PLANNED / DEFERRED
|
||||
|
||||
- broader Zig-style comptime execution
|
||||
- comptime pointers, slices, aggregate comptime parameters, and calls through comptime-known function values/function pointers
|
||||
- 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
|
||||
|
||||
@@ -137,7 +137,8 @@ Current prototype features:
|
||||
- Qualified imported globals and functions with package-aware symbol mangling
|
||||
- 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 comptime expressions (`$sum(1, 2)`) and comptime value blocks (`${ yield 4 }`) for integer constant contexts
|
||||
- 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`
|
||||
- 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
|
||||
|
||||
@@ -650,30 +650,38 @@
|
||||
- v1 intentionally keeps `type` contextual to comptime parameter declarations; no
|
||||
inferred type parameters, first-class type values, or comptime execution
|
||||
|
||||
27.6 comptime-evaluable constants/functions
|
||||
27.6 comptime-evaluable constants/functions (implemented)
|
||||
- `$expr` forces comptime evaluation of an expression:
|
||||
`x :: $32`, `n :: $sum(1, 2)`, and `res :: ${ ... }`
|
||||
- constant contexts such as array counts and comptime value arguments implicitly
|
||||
require comptime evaluation; ordinary immutable bindings remain ordinary bindings
|
||||
- ordinary `func` calls are comptime-evaluable when reached from a comptime context;
|
||||
do not add a separate `$sum func(...)` declaration form
|
||||
- v1 evaluator supports integer literals/arithmetic, boolean conditions, immutable
|
||||
- v1 evaluator supported integer literals/arithmetic, boolean conditions, immutable
|
||||
locals, `return`, `if`/`else`, comptime blocks, and direct calls to other evaluable
|
||||
brolang functions
|
||||
- broader Zig-style comptime execution is milestone 27.7
|
||||
- broader typed execution is milestone 27.7
|
||||
|
||||
27.7 broader Zig-style comptime execution
|
||||
- extend the comptime evaluator from integer scalars into a real compile-time value
|
||||
model: bools, floats, strings, arrays/slices, structs/unions/enums, optionals,
|
||||
fallibles, and pointers to comptime storage
|
||||
- support mutable comptime locals/assignment, loops with an evaluation quota,
|
||||
`defer`, `match`, value blocks/`yield`, and `try`/`catch`
|
||||
- allow calls through comptime-known function values/function pointers; keep external
|
||||
`c_func` calls runtime-only unless a future compiler intrinsic explicitly models
|
||||
their behavior
|
||||
27.7 broader Zig-style comptime execution (implemented; v1)
|
||||
- `compiler/checker/comptime.odin` owns checker-local evaluator state, typed
|
||||
comptime values, execution, and HIR materialization; `checker.odin` keeps type
|
||||
checking, inference, specialization, and build orchestration
|
||||
- typed `$` values cover bools, integers, floats, strings, arrays, structs, tagged
|
||||
unions, enums, optionals, and fallibles
|
||||
- supports mutable comptime locals/assignment, `if`, `while`, `for`,
|
||||
`break`/`continue`, `defer`, `match`, value blocks/`yield`, direct calls to
|
||||
bodyful Brolang functions, and `try`/`catch`
|
||||
- successful `$` results materialize back into ordinary HIR expressions so lowering
|
||||
and LLVM stay unchanged
|
||||
- evaluation uses a fixed `100_000` step quota
|
||||
- immutable locals/globals with comptime-known initializers may feed comptime
|
||||
evaluation; runtime-dependent values remain invalid in comptime contexts
|
||||
- no runtime side effects during comptime evaluation
|
||||
- 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
|
||||
|
||||
28. brolang build system (requires comptime execution)
|
||||
|
||||
@@ -1270,6 +1278,8 @@ max func($T type, a, b T) T { ... } # implemented: comptime type params
|
||||
x :: $32 # implemented: force comptime expression evaluation
|
||||
n :: $sum(1, 2) # implemented: ordinary functions can run at comptime
|
||||
res :: ${ yield 4 } # implemented: comptime value block
|
||||
p :: $Point { x = 1, y = 2 } # implemented: typed aggregate comptime values
|
||||
total :: $sum_loop(4) # implemented: mutable locals/loops/defer/match/try/catch
|
||||
```
|
||||
|
||||
## A word on memory allocation
|
||||
|
||||
@@ -6,7 +6,6 @@ import "../source"
|
||||
import "../symbol"
|
||||
import "../target"
|
||||
import "../types"
|
||||
import "base:intrinsics"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:slice"
|
||||
@@ -33,18 +32,6 @@ Spec :: struct {
|
||||
hir_id: hir.Function_Id,
|
||||
}
|
||||
|
||||
Comptime_Value_Kind :: enum u8 {
|
||||
Integer,
|
||||
Type,
|
||||
}
|
||||
|
||||
Comptime_Value :: struct {
|
||||
name: symbol.Id,
|
||||
type: types.Type,
|
||||
value: i128,
|
||||
kind: Comptime_Value_Kind,
|
||||
}
|
||||
|
||||
Infer_Local :: struct {
|
||||
name: symbol.Id,
|
||||
type: types.Type,
|
||||
@@ -118,19 +105,6 @@ Build_Ctx :: struct {
|
||||
loop_floor: int,
|
||||
}
|
||||
|
||||
Constant_Kind :: enum {
|
||||
Unknown,
|
||||
Not_Constant,
|
||||
Value,
|
||||
Overflow,
|
||||
Div_By_Zero,
|
||||
}
|
||||
|
||||
Constant :: struct {
|
||||
kind: Constant_Kind,
|
||||
value: i128,
|
||||
}
|
||||
|
||||
Function_Index_Entry :: struct {
|
||||
scope: ast.Package_Id,
|
||||
name: symbol.Id,
|
||||
@@ -202,26 +176,6 @@ type_label :: proc(checker: ^Checker, value: types.Type) -> string {
|
||||
return types.name(value)
|
||||
}
|
||||
|
||||
find_comptime_value :: proc(values: []Comptime_Value, name: symbol.Id) -> (Comptime_Value, bool) {
|
||||
for index := len(values) - 1; index >= 0; index -= 1 {
|
||||
if values[index].name == name {
|
||||
return values[index], true
|
||||
}
|
||||
}
|
||||
return {}, false
|
||||
}
|
||||
|
||||
current_comptime_value :: proc(checker: ^Checker, name: symbol.Id) -> (Comptime_Value, bool) {
|
||||
return find_comptime_value(checker.current_comptime_values, name)
|
||||
}
|
||||
|
||||
current_comptime_type :: proc(checker: ^Checker, name: symbol.Id) -> (types.Type, bool) {
|
||||
if value, ok := current_comptime_value(checker, name); ok && value.kind == .Type {
|
||||
return value.type, true
|
||||
}
|
||||
return types.INVALID, false
|
||||
}
|
||||
|
||||
is_type_metatype_syntax :: proc(checker: ^Checker, value: ast.Type_Syntax) -> bool {
|
||||
item, ok := types.node(&checker.module.types, value)
|
||||
return ok && item.name == u32(checker.type_symbol) && item.qualifier == 0
|
||||
@@ -260,379 +214,6 @@ comptime_param_count :: proc(function: ast.Function) -> int {
|
||||
return count
|
||||
}
|
||||
|
||||
comptime_values_equal :: proc(left, right: []Comptime_Value) -> bool {
|
||||
if len(left) != len(right) {
|
||||
return false
|
||||
}
|
||||
for value, index in left {
|
||||
other := right[index]
|
||||
if value.name != other.name || value.kind != other.kind || !types.equal(value.type, other.type) ||
|
||||
(value.kind == .Integer && value.value != other.value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
Constant_Frame :: struct {
|
||||
expr: ast.Expr_Id,
|
||||
stage: u8,
|
||||
}
|
||||
|
||||
eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
|
||||
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
||||
return Constant{kind = .Not_Constant}
|
||||
}
|
||||
stack := checker.constant_stack
|
||||
clear_dynamic_array(&stack)
|
||||
defer {
|
||||
clear_dynamic_array(&stack)
|
||||
checker.constant_stack = stack
|
||||
}
|
||||
append(&stack, Constant_Frame{expr=expr_id})
|
||||
|
||||
for len(stack) > 0 {
|
||||
frame_index := len(stack)-1
|
||||
frame := stack[frame_index]
|
||||
if checker.constants[frame.expr].kind != .Unknown {
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
expr := checker.ast_module.exprs[frame.expr]
|
||||
if expr.kind != .Add && expr.kind != .Sub && expr.kind != .Mul &&
|
||||
expr.kind != .Div && expr.kind != .Negate {
|
||||
result := Constant{kind = .Not_Constant}
|
||||
if expr.kind == .Integer {
|
||||
result = Constant{kind = .Value, value = i128(expr.integer)}
|
||||
}
|
||||
checker.constants[frame.expr] = result
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if frame.stage == 0 {
|
||||
stack[frame_index].stage = 1
|
||||
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.ast_module.exprs) &&
|
||||
checker.constants[expr.left].kind == .Unknown {
|
||||
append(&stack, Constant_Frame{expr=expr.left})
|
||||
}
|
||||
continue
|
||||
}
|
||||
if frame.stage == 1 && expr.kind == .Negate {
|
||||
operand := Constant{kind = .Not_Constant}
|
||||
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) {
|
||||
operand = checker.constants[expr.left]
|
||||
}
|
||||
result := Constant{kind = .Not_Constant}
|
||||
if operand.kind == .Div_By_Zero {
|
||||
result = Constant{kind = .Div_By_Zero}
|
||||
} else if operand.kind == .Overflow {
|
||||
result = Constant{kind = .Overflow}
|
||||
} else if operand.kind == .Value {
|
||||
value, overflow := intrinsics.overflow_sub(i128(0), operand.value)
|
||||
result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
|
||||
}
|
||||
checker.constants[frame.expr] = result
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if frame.stage == 1 {
|
||||
stack[frame_index].stage = 2
|
||||
if expr.right != ast.INVALID_EXPR && int(expr.right) < len(checker.ast_module.exprs) &&
|
||||
checker.constants[expr.right].kind == .Unknown {
|
||||
append(&stack, Constant_Frame{expr=expr.right})
|
||||
}
|
||||
continue
|
||||
}
|
||||
left := Constant{kind = .Not_Constant}
|
||||
right := Constant{kind = .Not_Constant}
|
||||
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) {
|
||||
left = checker.constants[expr.left]
|
||||
}
|
||||
if expr.right != ast.INVALID_EXPR && int(expr.right) < len(checker.constants) {
|
||||
right = checker.constants[expr.right]
|
||||
}
|
||||
result := Constant{kind = .Not_Constant}
|
||||
if left.kind == .Div_By_Zero || right.kind == .Div_By_Zero {
|
||||
result = Constant{kind = .Div_By_Zero}
|
||||
} else if left.kind == .Overflow || right.kind == .Overflow {
|
||||
result = Constant{kind = .Overflow}
|
||||
} else if left.kind == .Value && right.kind == .Value {
|
||||
value: i128
|
||||
overflow: bool
|
||||
div_by_zero: bool
|
||||
#partial switch expr.kind {
|
||||
case .Sub: value, overflow = intrinsics.overflow_sub(left.value, right.value)
|
||||
case .Mul: value, overflow = intrinsics.overflow_mul(left.value, right.value)
|
||||
case .Div:
|
||||
if right.value == 0 {
|
||||
div_by_zero = true
|
||||
} else {
|
||||
value = left.value / right.value
|
||||
}
|
||||
case: value, overflow = intrinsics.overflow_add(left.value, right.value)
|
||||
}
|
||||
switch {
|
||||
case div_by_zero: result = Constant{kind = .Div_By_Zero}
|
||||
case overflow: result = Constant{kind = .Overflow}
|
||||
case: result = Constant{kind = .Value, value = value}
|
||||
}
|
||||
}
|
||||
checker.constants[frame.expr] = result
|
||||
_ = pop(&stack)
|
||||
}
|
||||
return checker.constants[expr_id]
|
||||
}
|
||||
|
||||
eval_integer_constant_in_context :: proc(
|
||||
checker: ^Checker,
|
||||
expr_id: ast.Expr_Id,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
depth := 0,
|
||||
values: []Comptime_Value = nil,
|
||||
) -> Constant {
|
||||
if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
||||
return Constant{kind = .Not_Constant}
|
||||
}
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
#partial switch expr.kind {
|
||||
case .Integer:
|
||||
return Constant{kind = .Value, value = i128(expr.integer)}
|
||||
case .Bool:
|
||||
return Constant{kind = .Value, value = i128(expr.integer)}
|
||||
case .Name:
|
||||
if !symbol.is_valid(expr.qualifier) {
|
||||
if value, ok := find_comptime_value(values, expr.name); ok {
|
||||
if value.kind == .Integer {
|
||||
return Constant{kind = .Value, value = value.value}
|
||||
}
|
||||
}
|
||||
if value, ok := current_comptime_value(checker, expr.name); ok {
|
||||
if value.kind == .Integer {
|
||||
return Constant{kind = .Value, value = value.value}
|
||||
}
|
||||
}
|
||||
}
|
||||
target_pkg, available := expr_package(checker, expr, pkg, file, false)
|
||||
if !available {
|
||||
return Constant{kind = .Not_Constant}
|
||||
}
|
||||
global := find_global(checker, expr.name, target_pkg)
|
||||
if global == ast.INVALID_GLOBAL || int(global) >= len(checker.ast_module.globals) {
|
||||
return Constant{kind = .Not_Constant}
|
||||
}
|
||||
g := checker.ast_module.globals[global]
|
||||
if g.external || !g.immutable {
|
||||
return Constant{kind = .Not_Constant}
|
||||
}
|
||||
return eval_integer_constant_in_context(checker, g.expr, g.pkg, g.file, depth+1, values)
|
||||
case .Comptime:
|
||||
if expr.left != ast.INVALID_EXPR {
|
||||
return eval_integer_constant_in_context(checker, expr.left, pkg, file, depth+1, values)
|
||||
}
|
||||
result, produced, ok := eval_comptime_statements(checker, expr.body, pkg, file, depth+1, values, true)
|
||||
if ok && produced {
|
||||
return result
|
||||
}
|
||||
return Constant{kind = .Not_Constant}
|
||||
case .Negate:
|
||||
operand := eval_integer_constant_in_context(checker, expr.left, pkg, file, depth+1, values)
|
||||
if operand.kind == .Value {
|
||||
value, overflow := intrinsics.overflow_sub(i128(0), operand.value)
|
||||
return Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
|
||||
}
|
||||
return operand
|
||||
case .Not:
|
||||
operand := eval_integer_constant_in_context(checker, expr.left, pkg, file, depth+1, values)
|
||||
if operand.kind == .Value {
|
||||
return Constant{kind = .Value, value = 1 if operand.value == 0 else 0}
|
||||
}
|
||||
return operand
|
||||
case .Add, .Sub, .Mul, .Div:
|
||||
left := eval_integer_constant_in_context(checker, expr.left, pkg, file, depth+1, values)
|
||||
right := eval_integer_constant_in_context(checker, expr.right, pkg, file, depth+1, values)
|
||||
if left.kind == .Div_By_Zero || right.kind == .Div_By_Zero {
|
||||
return Constant{kind = .Div_By_Zero}
|
||||
}
|
||||
if left.kind == .Overflow || right.kind == .Overflow {
|
||||
return Constant{kind = .Overflow}
|
||||
}
|
||||
if left.kind != .Value || right.kind != .Value {
|
||||
return Constant{kind = .Not_Constant}
|
||||
}
|
||||
value: i128
|
||||
overflow: bool
|
||||
#partial switch expr.kind {
|
||||
case .Sub:
|
||||
value, overflow = intrinsics.overflow_sub(left.value, right.value)
|
||||
case .Mul:
|
||||
value, overflow = intrinsics.overflow_mul(left.value, right.value)
|
||||
case .Div:
|
||||
if right.value == 0 {
|
||||
return Constant{kind = .Div_By_Zero}
|
||||
}
|
||||
value = left.value / right.value
|
||||
case:
|
||||
value, overflow = intrinsics.overflow_add(left.value, right.value)
|
||||
}
|
||||
return Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
|
||||
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
||||
left := eval_integer_constant_in_context(checker, expr.left, pkg, file, depth+1, values)
|
||||
right := eval_integer_constant_in_context(checker, expr.right, pkg, file, depth+1, values)
|
||||
if left.kind != .Value || right.kind != .Value {
|
||||
if left.kind == .Div_By_Zero || right.kind == .Div_By_Zero {
|
||||
return Constant{kind = .Div_By_Zero}
|
||||
}
|
||||
if left.kind == .Overflow || right.kind == .Overflow {
|
||||
return Constant{kind = .Overflow}
|
||||
}
|
||||
return Constant{kind = .Not_Constant}
|
||||
}
|
||||
ok := false
|
||||
#partial switch expr.kind {
|
||||
case .Eq: ok = left.value == right.value
|
||||
case .Ne: ok = left.value != right.value
|
||||
case .Lt: ok = left.value < right.value
|
||||
case .Le: ok = left.value <= right.value
|
||||
case .Gt: ok = left.value > right.value
|
||||
case .Ge: ok = left.value >= right.value
|
||||
}
|
||||
return Constant{kind = .Value, value = 1 if ok else 0}
|
||||
case .And, .Or:
|
||||
left := eval_integer_constant_in_context(checker, expr.left, pkg, file, depth+1, values)
|
||||
if left.kind != .Value {
|
||||
return left
|
||||
}
|
||||
if expr.kind == .And && left.value == 0 {
|
||||
return Constant{kind = .Value, value = 0}
|
||||
}
|
||||
if expr.kind == .Or && left.value != 0 {
|
||||
return Constant{kind = .Value, value = 1}
|
||||
}
|
||||
right := eval_integer_constant_in_context(checker, expr.right, pkg, file, depth+1, values)
|
||||
if right.kind == .Value {
|
||||
return Constant{kind = .Value, value = 1 if right.value != 0 else 0}
|
||||
}
|
||||
return right
|
||||
case .Call:
|
||||
return eval_comptime_call(checker, expr, pkg, file, depth+1, values)
|
||||
}
|
||||
return Constant{kind = .Not_Constant}
|
||||
}
|
||||
|
||||
eval_comptime_statements :: proc(
|
||||
checker: ^Checker,
|
||||
statements: []ast.Stmt_Id,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
depth: int,
|
||||
values: []Comptime_Value,
|
||||
yield_returns: bool,
|
||||
) -> (Constant, bool, bool) {
|
||||
env: [dynamic]Comptime_Value
|
||||
env.allocator = checker.allocator
|
||||
append(&env, ..values)
|
||||
defer delete(env)
|
||||
for statement_id in statements {
|
||||
if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) {
|
||||
return Constant{kind=.Not_Constant}, false, false
|
||||
}
|
||||
statement := checker.ast_module.statements[statement_id]
|
||||
#partial switch statement.kind {
|
||||
case .Declaration:
|
||||
if statement.expr == ast.INVALID_EXPR || !statement.immutable {
|
||||
return Constant{kind=.Not_Constant}, false, false
|
||||
}
|
||||
value := eval_integer_constant_in_context(checker, statement.expr, pkg, file, depth+1, env[:])
|
||||
if value.kind != .Value || statement.name == checker.sink_symbol {
|
||||
return value, false, false
|
||||
}
|
||||
append(&env, Comptime_Value{name=statement.name, type=types.I64, value=value.value})
|
||||
case .Return:
|
||||
if yield_returns || statement.expr == ast.INVALID_EXPR {
|
||||
return Constant{kind=.Not_Constant}, false, false
|
||||
}
|
||||
value := eval_integer_constant_in_context(checker, statement.expr, pkg, file, depth+1, env[:])
|
||||
return value, value.kind == .Value, value.kind == .Value
|
||||
case .Yield:
|
||||
if !yield_returns || statement.expr == ast.INVALID_EXPR {
|
||||
return Constant{kind=.Not_Constant}, false, false
|
||||
}
|
||||
value := eval_integer_constant_in_context(checker, statement.expr, pkg, file, depth+1, env[:])
|
||||
return value, value.kind == .Value, value.kind == .Value
|
||||
case .If:
|
||||
if len(statement.captures) > 0 || statement.guard != ast.INVALID_EXPR {
|
||||
return Constant{kind=.Not_Constant}, false, false
|
||||
}
|
||||
condition := eval_integer_constant_in_context(checker, statement.expr, pkg, file, depth+1, env[:])
|
||||
if condition.kind != .Value {
|
||||
return condition, false, false
|
||||
}
|
||||
body := statement.body if condition.value != 0 else statement.else_body
|
||||
value, produced, ok := eval_comptime_statements(checker, body, pkg, file, depth+1, env[:], yield_returns)
|
||||
if !ok || produced {
|
||||
return value, produced, ok
|
||||
}
|
||||
case:
|
||||
return Constant{kind=.Not_Constant}, false, false
|
||||
}
|
||||
}
|
||||
return Constant{kind=.Not_Constant}, false, true
|
||||
}
|
||||
|
||||
eval_comptime_call :: proc(
|
||||
checker: ^Checker,
|
||||
expr: ast.Expr,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
depth: int,
|
||||
values: []Comptime_Value,
|
||||
) -> Constant {
|
||||
if expr.left != ast.INVALID_EXPR || depth > 64 {
|
||||
return Constant{kind=.Not_Constant}
|
||||
}
|
||||
target_pkg, available := expr_package(checker, expr, pkg, file, false)
|
||||
if !available {
|
||||
return Constant{kind=.Not_Constant}
|
||||
}
|
||||
template := find_template(checker, expr.name, target_pkg)
|
||||
if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) {
|
||||
return Constant{kind=.Not_Constant}
|
||||
}
|
||||
function := checker.ast_module.functions[template]
|
||||
if function.c_abi || !function.has_body || types.is_valid(function.error) ||
|
||||
len(function.unsupported_reason) > 0 || !valid_call_arity(function, len(expr.args)) {
|
||||
return Constant{kind=.Not_Constant}
|
||||
}
|
||||
comptime_values, comptime_ok := collect_comptime_values(checker, function, expr.args, pkg, file, false, values)
|
||||
defer delete(comptime_values, checker.allocator)
|
||||
if !comptime_ok {
|
||||
return Constant{kind=.Not_Constant}
|
||||
}
|
||||
env: [dynamic]Comptime_Value
|
||||
env.allocator = checker.allocator
|
||||
defer delete(env)
|
||||
append(&env, ..comptime_values)
|
||||
for param, index in function.params {
|
||||
if param.comptime_value {
|
||||
continue
|
||||
}
|
||||
if index >= len(expr.args) {
|
||||
return Constant{kind=.Not_Constant}
|
||||
}
|
||||
value := eval_integer_constant_in_context(checker, expr.args[index], pkg, file, depth+1, values)
|
||||
if value.kind != .Value {
|
||||
return value
|
||||
}
|
||||
append(&env, Comptime_Value{name=param.name, type=types.I64, value=value.value})
|
||||
}
|
||||
result, produced, ok := eval_comptime_statements(checker, function.body, function.pkg, function.file, depth+1, env[:], false)
|
||||
if !ok || !produced {
|
||||
return Constant{kind=.Not_Constant}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fits_signed_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool {
|
||||
if !types.is_signed(value_type, selected) {
|
||||
return false
|
||||
@@ -1927,24 +1508,7 @@ infer_compound_expr :: proc(
|
||||
store := &checker.module.types
|
||||
#partial switch expr.kind {
|
||||
case .Comptime:
|
||||
constant := Constant{kind=.Not_Constant}
|
||||
if expr.left != ast.INVALID_EXPR {
|
||||
constant = eval_integer_constant_in_context(checker, expr.left, pkg, file)
|
||||
}
|
||||
if expr.left == ast.INVALID_EXPR {
|
||||
value, produced, ok := eval_comptime_statements(checker, expr.body, pkg, file, 0, nil, true)
|
||||
if ok && produced {
|
||||
constant = value
|
||||
}
|
||||
}
|
||||
if constant.kind == .Overflow || constant.kind == .Div_By_Zero ||
|
||||
(constant.kind == .Value && !fits_i64(constant.value)) {
|
||||
return types.I64
|
||||
}
|
||||
if constant.kind == .Value {
|
||||
return types.smallest_signed_for_literal(i64(constant.value))
|
||||
}
|
||||
return types.INVALID
|
||||
return infer_comptime_expr_type(checker, expr, pkg, file)
|
||||
case .Bool:
|
||||
return types.BOOL
|
||||
case .Not:
|
||||
@@ -4322,20 +3886,7 @@ build_compound_expr :: proc(
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Comptime:
|
||||
constant := Constant{kind=.Not_Constant}
|
||||
if expr.left != ast.INVALID_EXPR {
|
||||
constant = eval_integer_constant_in_context(checker, expr.left, pkg, file)
|
||||
} else {
|
||||
value, produced, ok := eval_comptime_statements(checker, expr.body, pkg, file, 0, nil, true)
|
||||
if ok && produced {
|
||||
constant = value
|
||||
}
|
||||
}
|
||||
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero {
|
||||
return build_constant_expr(checker, expr, constant, expected)
|
||||
}
|
||||
id := source.add(checker.diagnostics, expr.span, "expression cannot be evaluated at comptime")
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
return build_comptime_expr(checker, expr, expected, pkg, file)
|
||||
case .Bool:
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Bool, span=expr.span, type=types.BOOL, integer=i64(expr.integer),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+37
-14
@@ -2338,26 +2338,25 @@ main func() i32 {
|
||||
}
|
||||
|
||||
@(test)
|
||||
comptime_expression_diagnoses_unsupported_v1_evaluation :: proc(t: ^testing.T) {
|
||||
text := `loop func() int {
|
||||
comptime_expression_diagnoses_runtime_only_and_quota :: proc(t: ^testing.T) {
|
||||
text := `native c_func() i32
|
||||
spin func() i32 {
|
||||
while true {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
missing func() int {
|
||||
missing func() i32 {
|
||||
if true {
|
||||
}
|
||||
}
|
||||
recurse func(value int) int {
|
||||
return recurse(value)
|
||||
}
|
||||
GLOBAL :: 1
|
||||
main func() void {
|
||||
runtime i32 = 1
|
||||
_ = $runtime
|
||||
_ = $loop()
|
||||
_ = $native()
|
||||
_ = $&GLOBAL
|
||||
_ = $spin()
|
||||
_ = $missing()
|
||||
_ = $recurse(1)
|
||||
_ = ${
|
||||
value :: 1
|
||||
}
|
||||
@@ -2375,13 +2374,27 @@ main func() void {
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found := 0
|
||||
found_runtime := false
|
||||
found_external := false
|
||||
found_pointer := false
|
||||
found_quota := false
|
||||
found_missing := false
|
||||
found_yield := false
|
||||
for diagnostic in diagnostics.items {
|
||||
if strings.contains(diagnostic.message, "expression cannot be evaluated at comptime") {
|
||||
found += 1
|
||||
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")
|
||||
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 >= 5)
|
||||
testing.expect(t, found_runtime)
|
||||
testing.expect(t, found_external)
|
||||
testing.expect(t, found_pointer)
|
||||
testing.expect(t, found_quota)
|
||||
testing.expect(t, found_missing)
|
||||
testing.expect(t, found_yield)
|
||||
}
|
||||
|
||||
@(test)
|
||||
@@ -5774,6 +5787,16 @@ comptime_eval_compile_and_run :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
comptime_v1_compile_and_run :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-comptime-v1"
|
||||
defer _ = os.remove(output)
|
||||
status := compiler_core.compile_package("examples/programs/comptime_v1", output)
|
||||
testing.expect_value(t, status, 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
lazy_function_body_marks_import_as_used_without_resolving_it :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-package-lazy-import"
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
State :: enum {
|
||||
idle
|
||||
ready
|
||||
failed
|
||||
}
|
||||
|
||||
Point :: struct {
|
||||
x i32
|
||||
y i32
|
||||
}
|
||||
|
||||
Box :: union(enum) {
|
||||
point Point
|
||||
empty void
|
||||
}
|
||||
|
||||
Error :: enum {
|
||||
bad
|
||||
}
|
||||
|
||||
make_point func() Point {
|
||||
return Point { x = 3, y = 4 }
|
||||
}
|
||||
|
||||
sum_loop func(limit i32) i32 {
|
||||
total i32 = 0
|
||||
i i32 = 0
|
||||
while i < limit {
|
||||
i += 1
|
||||
if i == 2 {
|
||||
continue
|
||||
}
|
||||
total += i
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
sum_for func() i32 {
|
||||
total i32 = 0
|
||||
values [_]i32 = [1, 2, 3]
|
||||
for values |value, index| {
|
||||
total += value + index
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
defer_value func() i32 {
|
||||
value i32 = 1
|
||||
{
|
||||
defer value += 10
|
||||
value += 1
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
describe func(box Box) i32 {
|
||||
return match box {
|
||||
.point |p|: p.x + p.y
|
||||
.empty: 0
|
||||
}
|
||||
}
|
||||
|
||||
maybe func(flag bool) ?i32 {
|
||||
if flag {
|
||||
return 9
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
may_fail func(flag bool) i32 ! Error {
|
||||
if flag {
|
||||
return .bad
|
||||
}
|
||||
return 7
|
||||
}
|
||||
|
||||
use_try func() i32 ! Error {
|
||||
value :: try may_fail(false)
|
||||
return value + 1
|
||||
}
|
||||
|
||||
recover func() i32 {
|
||||
return may_fail(true) catch |e| {
|
||||
match e {
|
||||
.bad: yield 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GLOBAL :: $sum_loop(4)
|
||||
|
||||
main func() i32 {
|
||||
point Point :: $make_point()
|
||||
numbers [_]i32 :: $[4, 5, 6]
|
||||
box Box :: $Box { point = Point { x = 8, y = 1 } }
|
||||
state State :: $State.ready
|
||||
name :: $"bro"
|
||||
value i32 :: $sum_for()
|
||||
deferred i32 :: $defer_value()
|
||||
optional i32 :: $maybe(true)?
|
||||
tried i32 :: $use_try() catch 0
|
||||
recovered i32 :: $recover()
|
||||
|
||||
if point.x + point.y != 7 {
|
||||
return 1
|
||||
}
|
||||
if numbers.len != 3 or numbers[2] != 6 {
|
||||
return 2
|
||||
}
|
||||
if describe(box) != 9 {
|
||||
return 3
|
||||
}
|
||||
if state != State.ready {
|
||||
return 4
|
||||
}
|
||||
if name.len != 3 {
|
||||
return 5
|
||||
}
|
||||
if value != 9 {
|
||||
return 6
|
||||
}
|
||||
if deferred != 12 {
|
||||
return 7
|
||||
}
|
||||
if optional != 9 {
|
||||
return 8
|
||||
}
|
||||
if tried != 8 {
|
||||
return 9
|
||||
}
|
||||
if recovered != 5 {
|
||||
return 10
|
||||
}
|
||||
if GLOBAL != 8 {
|
||||
return 11
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user