feat: execute short-circuit boolean expressions
This commit is contained in:
@@ -43,6 +43,8 @@ function calls are postfix expressions: `f()`, `f(a, b)`, and `factory()(x)`. ca
|
|||||||
|
|
||||||
`true` and `false` are boolean literals; `bool` is available for parameters, storage and results. `==`, `!=`, `<`, `<=`, `>` and `>=` compare compatible numeric operands and produce `bool`; `==` and `!=` also accept boolean operands. comparisons preserve contextual numeric inference without making their operands boolean. arithmetic binds tighter than relational comparisons, which bind tighter than equality; comparisons associate left to right. boolean arithmetic/ordering and string/function/void comparisons are invalid; there is no implicit numeric-to-boolean conversion.
|
`true` and `false` are boolean literals; `bool` is available for parameters, storage and results. `==`, `!=`, `<`, `<=`, `>` and `>=` compare compatible numeric operands and produce `bool`; `==` and `!=` also accept boolean operands. comparisons preserve contextual numeric inference without making their operands boolean. arithmetic binds tighter than relational comparisons, which bind tighter than equality; comparisons associate left to right. boolean arithmetic/ordering and string/function/void comparisons are invalid; there is no implicit numeric-to-boolean conversion.
|
||||||
|
|
||||||
|
`!`, `and` and `or` require boolean operands and produce `bool`. prefix `!` and `-` bind tighter than arithmetic; equality binds tighter than `and`, which binds tighter than `or`. binary operators associate left to right, while prefixes nest right to left. operands evaluate left to right: `false and rhs` and `true or rhs` skip the right operand, including calls, lazy global initialization and runtime traps. both operands are still parsed, resolved and type-checked; errors in a skipped operand remain source diagnostics without forcing a runtime trap. this applies in initializers, assignments, returns, call arguments and conditions. [programs/boolean_conditions.hon](programs/boolean_conditions.hon) demonstrates combined predicates; `./build/honey run programs/boolean_conditions.hon` prints `true`.
|
||||||
|
|
||||||
`if`/`else` supports braced arms, single statements and `else if` chains. conditions must have type `bool`. a braceless guard requires parentheses unless it is a function call: `if (n <= 1) return 1` or `if ready() return`. `else` binds to the nearest `if`; after a braced arm it may start on the next line. branches are represented as AST blocks even when braceless.
|
`if`/`else` supports braced arms, single statements and `else if` chains. conditions must have type `bool`. a braceless guard requires parentheses unless it is a function call: `if (n <= 1) return 1` or `if ready() return`. `else` binds to the nearest `if`; after a braced arm it may start on the next line. branches are represented as AST blocks even when braceless.
|
||||||
|
|
||||||
`name = expression` updates a local `:=` binding. `::` bindings, parameters, named functions, builtin types and globals are not assignment targets. the established binding type is preserved: assignment inputs do not widen or infer that type, though numeric literals can adopt its float context. function-valued locals accept replacement functions with the same parameter and result types.
|
`name = expression` updates a local `:=` binding. `::` bindings, parameters, named functions, builtin types and globals are not assignment targets. the established binding type is preserved: assignment inputs do not widen or infer that type, though numeric literals can adopt its float context. function-valued locals accept replacement functions with the same parameter and result types.
|
||||||
@@ -59,8 +61,8 @@ numeric typing follows contextual rules within Honey's current `int` and `float`
|
|||||||
|
|
||||||
type errors carry source spans and diagnostic identities; invalid operands propagate their existing identity without duplicate errors, while independent expressions, arguments and nested bodies are still checked. inference cycles without a type anchor are diagnosed. function signatures are checked independently of their bodies. bare `return` and same-line `return expression` are supported inside function bodies and must match the declared result type. nonvoid return-path checking is structural: every path must return or terminate in a diagnostic trap, and both conditional successors are considered without constant folding. a missing `else` or a loop permits fallthrough, even for a literal `true` condition; a later return can close that path. statements after a return and untaken branches or loops are still type-checked but are not executed.
|
type errors carry source spans and diagnostic identities; invalid operands propagate their existing identity without duplicate errors, while independent expressions, arguments and nested bodies are still checked. inference cycles without a type anchor are diagnosed. function signatures are checked independently of their bodies. bare `return` and same-line `return expression` are supported inside function bodies and must match the declared result type. nonvoid return-path checking is structural: every path must return or terminate in a diagnostic trap, and both conditional successors are considered without constant folding. a missing `else` or a loop permits fallthrough, even for a literal `true` condition; a later return can close that path. statements after a return and untaken branches or loops are still type-checked but are not executed.
|
||||||
|
|
||||||
`lower.lower` turns the checked AST and binding table into an owned `@source/ir` module. functions, globals, local slots and instruction-result temporaries have explicit IDs; typed instructions execute in source evaluation order and basic blocks end in jump, conditional-branch, return or diagnostic-trap terminators. conditional arms join only when execution can continue; loops use condition/body/exit blocks and backedges. local slots and temporaries are function-wide, without SSA or phi nodes. literals are parsed and strings decoded during lowering; the module retains no AST, binding-table or source-buffer references. release it with `ir.deinit`; keep and reuse the diagnostic store used during lowering so diagnostic IDs remain valid. the IR is target-independent and is intended to feed later native backends as well as the current interpreter.
|
`lower.lower` turns the checked AST and binding table into an owned `@source/ir` module. functions, globals, local slots and instruction-result temporaries have explicit IDs; typed instructions execute in source evaluation order and basic blocks end in jump, conditional-branch, return or diagnostic-trap terminators. conditional arms join only when execution can continue; loops use condition/body/exit blocks and backedges. short-circuit expressions use branches and synthetic boolean local slots to merge results; call arguments are packed in the block reached after evaluating them. local slots and temporaries are function-wide, without SSA or phi nodes. literals are parsed and strings decoded during lowering; the module retains no AST, binding-table or source-buffer references. release it with `ir.deinit`; keep and reuse the diagnostic store used during lowering so diagnostic IDs remain valid. the IR is target-independent and is intended to feed later native backends as well as the current interpreter.
|
||||||
|
|
||||||
`interpreter.run` consumes only IR and returns a value or a diagnostic trap. string values carry a module-local `ir.StringId`, not a slice; resolve bytes with `ir.string_bytes(&module, id)` before releasing the module. decoded bytes and their full backing allocations remain module-owned, without copying strings during calls or returns. heap-backed invocation frames track their current block and resume it after calls or global initialization; loop iterations reuse the invocation's storage without allocating new frames. parameters are initialized on entry and local declarations execute in statement order. premature local reads and assignments trap; assignment evaluates its right-hand side before checking the destination's initialization. globals initialize lazily on their first read, cache their result for that run, and trap on initialization cycles. integers use signed 64-bit storage with checked arithmetic; floats use 64-bit IEEE arithmetic and comparisons, including NaN behavior. numeric literal overflow and float underflow to zero are diagnosed; representable subnormals are accepted. strings support `\\`, `\"`, `\n`, `\r`, `\t`, `\0` and `\xNN` escapes.
|
`interpreter.run` consumes only IR and returns a value or a diagnostic trap. string values carry a module-local `ir.StringId`, not a slice; resolve bytes with `ir.string_bytes(&module, id)` before releasing the module. decoded bytes and their full backing allocations remain module-owned, without copying strings during calls or returns. heap-backed invocation frames track their current block and resume it after calls or global initialization; loop iterations reuse the invocation's storage without allocating new frames. parameters are initialized on entry and local declarations execute in statement order. premature local reads and assignments trap; assignment evaluates its right-hand side before checking the destination's initialization. globals initialize lazily on their first read, cache their result for that run, and trap on initialization cycles. integers use signed 64-bit storage with checked arithmetic; floats use 64-bit IEEE arithmetic and comparisons, including NaN behavior. numeric literal overflow and float underflow to zero are diagnosed; representable subnormals are accepted. strings support `\\`, `\"`, `\n`, `\r`, `\t`, `\0` and `\xNN` escapes.
|
||||||
|
|
||||||
recoverable source errors do not stop execution of unaffected code. unused invalid functions, globals and nested bodies retain diagnostic traps; reaching invalid code reports its original diagnostic identity. missing-return traps affect only paths that fall through, not paths that already return or trap. diagnostics include the filename, line and byte column. exit status is `0` for clean execution, `1` for source diagnostics or a runtime trap, and `2` for usage or infrastructure failure. native code generation, `for`, `break`/`continue`, compound assignment and boolean logical operators remain unimplemented.
|
recoverable source errors do not stop execution of unaffected code. unused invalid functions, globals and nested bodies retain diagnostic traps; reaching invalid code reports its original diagnostic identity. missing-return traps affect only paths that fall through, not paths that already return or trap. diagnostics include the filename, line and byte column. exit status is `0` for clean execution, `1` for source diagnostics or a runtime trap, and `2` for usage or infrastructure failure. native code generation, `for`, `break`/`continue` and compound assignment remain unimplemented.
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
in_range proc(value, low, high int) bool {
|
||||||
|
return value >= low and value < high
|
||||||
|
}
|
||||||
|
|
||||||
|
main proc() bool {
|
||||||
|
return (
|
||||||
|
in_range(5, 0, 10)
|
||||||
|
and !in_range(12, 0, 10)
|
||||||
|
or in_range(20, 0, 10)
|
||||||
|
)
|
||||||
|
}
|
||||||
+6
-3
@@ -2,13 +2,15 @@
|
|||||||
#! * type ..... -> identifier
|
#! * type ..... -> identifier
|
||||||
#! * binding ... -> :: | :=
|
#! * binding ... -> :: | :=
|
||||||
#! * literal .. -> int | float | string | 'true' | 'false'
|
#! * literal .. -> int | float | string | 'true' | 'false'
|
||||||
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>='
|
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
|
||||||
#! * expr ..... -> identifier | literal | '(' expr ')' | '-' expr | expr op expr | expr '(' [expr {',' expr} [',']] ')'
|
#! * expr ..... -> identifier | literal | '(' expr ')' | ('-' | '!') expr | expr op expr | expr '(' [expr {',' expr} [',']] ')'
|
||||||
#! * decl ..... -> identifier [type] binding expr
|
#! * decl ..... -> identifier [type] binding expr
|
||||||
#! * block .... -> '{' {stmt} '}'
|
#! * block .... -> '{' {stmt} '}'
|
||||||
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
||||||
#! * stmt ..... -> decl | expr | identifier '=' expr | 'return' [expr] | 'if' expr arm ['else' arm] | 'while' expr arm
|
#! * stmt ..... -> decl | expr | identifier '=' expr | 'return' [expr] | 'if' expr arm ['else' arm] | 'while' expr arm
|
||||||
#! braceless guards require parentheses unless the condition is a call; else binds to the nearest if.
|
#! braceless guards require parentheses unless the condition is a call; else binds to the nearest if.
|
||||||
|
#! precedence (tightest first): calls, unary -/!, multiplication/division, addition/subtraction, relational comparisons, equality, and, or.
|
||||||
|
#! binary operators associate left; unary operators associate right.
|
||||||
|
|
||||||
import "@std/debug"
|
import "@std/debug"
|
||||||
import "@std/mem"
|
import "@std/mem"
|
||||||
@@ -58,7 +60,7 @@ Mutability :: enum {
|
|||||||
constant, variable
|
constant, variable
|
||||||
}
|
}
|
||||||
|
|
||||||
UnaryOperator :: enum { negate }
|
UnaryOperator :: enum { negate, logical_not }
|
||||||
|
|
||||||
BinaryOperator :: enum {
|
BinaryOperator :: enum {
|
||||||
add, subtract,
|
add, subtract,
|
||||||
@@ -66,6 +68,7 @@ BinaryOperator :: enum {
|
|||||||
equal, not_equal,
|
equal, not_equal,
|
||||||
less, less_equal,
|
less, less_equal,
|
||||||
greater, greater_equal
|
greater, greater_equal
|
||||||
|
logical_and, logical_or
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
|
|||||||
@@ -170,6 +170,10 @@ run proc(
|
|||||||
value = ir.Value{ type = .int, data = ir.ValueData{ integer = -operand.data.integer } }
|
value = ir.Value{ type = .int, data = ir.ValueData{ integer = -operand.data.integer } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.logical_not: {
|
||||||
|
operand :: frames.items[frame_index].values[usize(instruction.left)]
|
||||||
|
value = ir.Value{ type = .bool, data = ir.ValueData{ boolean = !operand.data.boolean } }
|
||||||
|
}
|
||||||
.add, .subtract, .multiply, .divide: {
|
.add, .subtract, .multiply, .divide: {
|
||||||
left :: frames.items[frame_index].values[usize(instruction.left)]
|
left :: frames.items[frame_index].values[usize(instruction.left)]
|
||||||
right :: frames.items[frame_index].values[usize(instruction.right)]
|
right :: frames.items[frame_index].values[usize(instruction.right)]
|
||||||
|
|||||||
@@ -779,3 +779,34 @@ assignment_diagnostic_allocation_failure_releases_runtime_storage test {
|
|||||||
}
|
}
|
||||||
return .expectation_failed
|
return .expectation_failed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logical_not_inverts_boolean_payload_and_composes_in_typed_ir test {
|
||||||
|
operands [2]bool :: [false, true]
|
||||||
|
expected [2]bool :: [true, false]
|
||||||
|
for (operands) |operand, index| {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
main :: try add_function(&module, mem.empty(ir.Type), 0, .bool, 3)
|
||||||
|
module.entry = main
|
||||||
|
try emit(&module, main, ir.Instruction{
|
||||||
|
op = .constant,
|
||||||
|
type = .bool,
|
||||||
|
destination = ir.ValueId(0),
|
||||||
|
constant = ir.Value{ type = .bool, data = ir.ValueData{ boolean = operand } },
|
||||||
|
})
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .logical_not, type = .bool, destination = ir.ValueId(1), left = ir.ValueId(0) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .logical_not, type = .bool, destination = ir.ValueId(2), left = ir.ValueId(1) })
|
||||||
|
return_value(&module, main, ir.ValueId(1))
|
||||||
|
inverted :: try run(mem.c_allocator, &module, &store)
|
||||||
|
try testing.expect(inverted.trap == null)
|
||||||
|
try testing.expect_equal(ir.Type.bool, inverted.value.type)
|
||||||
|
try testing.expect_equal(expected[index], inverted.value.data.boolean)
|
||||||
|
return_value(&module, main, ir.ValueId(2))
|
||||||
|
composed :: try run(mem.c_allocator, &module, &store)
|
||||||
|
try testing.expect(composed.trap == null)
|
||||||
|
try testing.expect_equal(operand, composed.value.data.boolean)
|
||||||
|
try testing.expect_equal(usize(0), diagnostics.count(&store))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ Op :: enum {
|
|||||||
reset_local
|
reset_local
|
||||||
load_global
|
load_global
|
||||||
negate
|
negate
|
||||||
|
logical_not
|
||||||
add
|
add
|
||||||
subtract
|
subtract
|
||||||
multiply
|
multiply
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
|||||||
{ "while", .while },
|
{ "while", .while },
|
||||||
{ "true", .true },
|
{ "true", .true },
|
||||||
{ "false", .false },
|
{ "false", .false },
|
||||||
|
{ "and", .and },
|
||||||
|
{ "or", .or },
|
||||||
])
|
])
|
||||||
|
|
||||||
#! owns token storage and retains its allocator; release with deinit.
|
#! owns token storage and retains its allocator; release with deinit.
|
||||||
@@ -225,6 +227,7 @@ scan proc(
|
|||||||
|
|
||||||
kind ?TokenKind :: match char {
|
kind ?TokenKind :: match char {
|
||||||
'=': .equal
|
'=': .equal
|
||||||
|
'!': .bang
|
||||||
'<': .less
|
'<': .less
|
||||||
'>': .greater
|
'>': .greater
|
||||||
',': .comma
|
',': .comma
|
||||||
|
|||||||
@@ -53,3 +53,29 @@ reserves_boolean_keywords_and_scans_comparisons test {
|
|||||||
try testing.expect_equal("bool", strpool.get_str(&strpool.strings, state.tokens.items[2].data.str_id)?)
|
try testing.expect_equal("bool", strpool.get_str(&strpool.strings, state.tokens.items[2].data.str_id)?)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scans_logical_operators_without_splitting_identifiers test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
state := init(mem.c_allocator)
|
||||||
|
defer deinit(&state)
|
||||||
|
source :: "and or android orbit and_1 or2 ! != !! !true"
|
||||||
|
try scan(&state, source, &diagnostic_store)
|
||||||
|
expected [13]TokenKind :: [
|
||||||
|
TokenKind.and, .or, .ident, .ident, .ident, .ident,
|
||||||
|
.bang, .not_equal, .bang, .bang, .bang, .true, .eof,
|
||||||
|
]
|
||||||
|
try testing.expect_equal(expected.len, state.tokens.items.len)
|
||||||
|
for (expected) |kind, index| try testing.expect_equal(kind, state.tokens.items[index].kind)
|
||||||
|
try testing.expect_equal("android", strpool.get_str(&strpool.strings, state.tokens.items[2].data.str_id)?)
|
||||||
|
try testing.expect_equal("orbit", strpool.get_str(&strpool.strings, state.tokens.items[3].data.str_id)?)
|
||||||
|
try testing.expect_equal("and_1", strpool.get_str(&strpool.strings, state.tokens.items[4].data.str_id)?)
|
||||||
|
try testing.expect_equal("or2", strpool.get_str(&strpool.strings, state.tokens.items[5].data.str_id)?)
|
||||||
|
try testing.expect_equal(u32(31), state.tokens.items[6].span.start)
|
||||||
|
try testing.expect_equal(u32(32), state.tokens.items[6].span.end)
|
||||||
|
try testing.expect_equal(u32(33), state.tokens.items[7].span.start)
|
||||||
|
try testing.expect_equal(u32(35), state.tokens.items[7].span.end)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ TokenKind :: enum {
|
|||||||
for, while
|
for, while
|
||||||
proc, return
|
proc, return
|
||||||
true, false
|
true, false
|
||||||
|
and, or
|
||||||
|
|
||||||
# literals
|
# literals
|
||||||
ident, string
|
ident, string
|
||||||
@@ -40,6 +41,9 @@ TokenKind :: enum {
|
|||||||
plus, minus
|
plus, minus
|
||||||
star, slash
|
star, slash
|
||||||
|
|
||||||
|
# logical
|
||||||
|
bang
|
||||||
|
|
||||||
# assignment & arithmetic
|
# assignment & arithmetic
|
||||||
plus_equal, minus_equal
|
plus_equal, minus_equal
|
||||||
star_equal, slash_equal
|
star_equal, slash_equal
|
||||||
|
|||||||
+68
-8
@@ -24,6 +24,7 @@ State :: struct {
|
|||||||
diagnostics @mut diagnostics.Store
|
diagnostics @mut diagnostics.Store
|
||||||
module @mut ir.Module
|
module @mut ir.Module
|
||||||
storage []mut Storage
|
storage []mut Storage
|
||||||
|
arguments arraylist.ArrayList(ir.ValueId)
|
||||||
function usize = 0
|
function usize = 0
|
||||||
block ir.BlockId = ir.BlockId(0)
|
block ir.BlockId = ir.BlockId(0)
|
||||||
terminated bool = false
|
terminated bool = false
|
||||||
@@ -50,7 +51,9 @@ lower proc(
|
|||||||
diagnostics = diagnostic_store,
|
diagnostics = diagnostic_store,
|
||||||
module = &module,
|
module = &module,
|
||||||
storage = storage,
|
storage = storage,
|
||||||
|
arguments = arraylist.init(ir.ValueId, allocator),
|
||||||
}
|
}
|
||||||
|
defer arraylist.deinit(&state.arguments)
|
||||||
|
|
||||||
# allocate every package identity before lowering any initializer or body.
|
# allocate every package identity before lowering any initializer or body.
|
||||||
for (0..ast.package_decl_count(tree)) |i| {
|
for (0..ast.package_decl_count(tree)) |i| {
|
||||||
@@ -344,9 +347,10 @@ lower_while proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagn
|
|||||||
state.block = guard
|
state.block = guard
|
||||||
condition :: try lower_expression(state, loop.condition)
|
condition :: try lower_expression(state, loop.condition)
|
||||||
if (state.terminated) return
|
if (state.terminated) return
|
||||||
|
guard_end :: state.block
|
||||||
body :: try add_block(state)
|
body :: try add_block(state)
|
||||||
exit :: try add_block(state)
|
exit :: try add_block(state)
|
||||||
state.module.functions.items[state.function].blocks.items[usize(guard)].terminator = ir.Terminator{
|
state.module.functions.items[state.function].blocks.items[usize(guard_end)].terminator = ir.Terminator{
|
||||||
kind = .branch,
|
kind = .branch,
|
||||||
value = condition,
|
value = condition,
|
||||||
target = body,
|
target = body,
|
||||||
@@ -413,6 +417,48 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
lower_logical proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
expression :: ast.binary_expr(state.tree, id)
|
||||||
|
span :: ast.node_span(state.tree, id)
|
||||||
|
left :: try lower_expression(state, expression.left)
|
||||||
|
if (state.terminated) return ir.no_value
|
||||||
|
origin :: state.block
|
||||||
|
slot :: u32(state.module.functions.items[state.function].locals.items.len)
|
||||||
|
try arraylist.append(&state.module.functions.items[state.function].locals, ir.Type.bool)
|
||||||
|
right_block :: try add_block(state)
|
||||||
|
skip :: try add_block(state)
|
||||||
|
join :: try add_block(state)
|
||||||
|
conjunction :: expression.operator == .logical_and
|
||||||
|
target :: if (conjunction) right_block else skip
|
||||||
|
alternative :: if (conjunction) skip else right_block
|
||||||
|
state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{
|
||||||
|
kind = .branch,
|
||||||
|
value = left,
|
||||||
|
target = target,
|
||||||
|
alternative = alternative,
|
||||||
|
span = ast.node_span(state.tree, expression.left),
|
||||||
|
}
|
||||||
|
state.block = skip
|
||||||
|
_ = try emit(state, ir.Instruction{ op = .store_local, type = .bool, left = left, slot = slot, span = span })
|
||||||
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||||
|
kind = .jump, target = join, span = span,
|
||||||
|
}
|
||||||
|
state.block = right_block
|
||||||
|
right :: try lower_expression(state, expression.right)
|
||||||
|
if (!state.terminated) {
|
||||||
|
_ = try emit(state, ir.Instruction{ op = .store_local, type = .bool, left = right, slot = slot, span = span })
|
||||||
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||||
|
kind = .jump, target = join, span = span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# the skip edge reaches the join even when the demanded right edge traps.
|
||||||
|
state.block = join
|
||||||
|
state.terminated = false
|
||||||
|
if (trap_type(state, id)) return ir.no_value
|
||||||
|
return try emit(state, ir.Instruction{ op = .load_local, type = .bool, slot = slot, span = span })
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
tree :: state.tree
|
tree :: state.tree
|
||||||
@@ -454,7 +500,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
|||||||
}
|
}
|
||||||
.unary: {
|
.unary: {
|
||||||
expression :: ast.unary_expr(tree, id)
|
expression :: ast.unary_expr(tree, id)
|
||||||
if (type == .int and ast.expr_kind(tree, expression.operand) == .literal_int) {
|
if (expression.operator == .negate and type == .int and ast.expr_kind(tree, expression.operand) == .literal_int) {
|
||||||
text :: ast.source_text(tree, ast.node_span(tree, expression.operand))
|
text :: ast.source_text(tree, ast.node_span(tree, expression.operand))
|
||||||
if (integer_magnitude(text, u64(maxval!(i64)) + 1)) |magnitude| {
|
if (integer_magnitude(text, u64(maxval!(i64)) + 1)) |magnitude| {
|
||||||
if (magnitude == u64(maxval!(i64)) + 1) {
|
if (magnitude == u64(maxval!(i64)) + 1) {
|
||||||
@@ -464,10 +510,17 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
|||||||
}
|
}
|
||||||
operand :: try lower_expression(state, expression.operand)
|
operand :: try lower_expression(state, expression.operand)
|
||||||
if (state.terminated or trap_type(state, id)) return ir.no_value
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||||
return try emit(state, ir.Instruction{ op = .negate, type = type, left = operand, span = span })
|
op ir.Op :: match expression.operator {
|
||||||
|
.negate: .negate
|
||||||
|
.logical_not: .logical_not
|
||||||
|
}
|
||||||
|
return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span })
|
||||||
}
|
}
|
||||||
.binary: {
|
.binary: {
|
||||||
expression :: ast.binary_expr(tree, id)
|
expression :: ast.binary_expr(tree, id)
|
||||||
|
if (expression.operator == .logical_and or expression.operator == .logical_or) {
|
||||||
|
return try lower_logical(state, id)
|
||||||
|
}
|
||||||
left :: try lower_expression(state, expression.left)
|
left :: try lower_expression(state, expression.left)
|
||||||
if (state.terminated) return ir.no_value
|
if (state.terminated) return ir.no_value
|
||||||
right :: try lower_expression(state, expression.right)
|
right :: try lower_expression(state, expression.right)
|
||||||
@@ -483,23 +536,30 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
|||||||
.less_equal: .less_equal
|
.less_equal: .less_equal
|
||||||
.greater: .greater
|
.greater: .greater
|
||||||
.greater_equal: .greater_equal
|
.greater_equal: .greater_equal
|
||||||
|
.logical_and, .logical_or: unreachable
|
||||||
}
|
}
|
||||||
return try emit(state, ir.Instruction{ op = op, type = type, left = left, right = right, span = span })
|
return try emit(state, ir.Instruction{ op = op, type = type, left = left, right = right, span = span })
|
||||||
}
|
}
|
||||||
.call: {
|
.call: {
|
||||||
callee :: try lower_expression(state, ast.call_expr(tree, id).callee)
|
callee :: try lower_expression(state, ast.call_expr(tree, id).callee)
|
||||||
if (state.terminated) return ir.no_value
|
if (state.terminated) return ir.no_value
|
||||||
# reserve this call's packed slice before nested calls append theirs.
|
# recursion borrows later scratch slots; no items pointer survives a child.
|
||||||
block := &state.module.functions.items[state.function].blocks.items[usize(state.block)]
|
scratch_start :: state.arguments.items.len
|
||||||
start :: block.arguments.items.len
|
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
|
||||||
count :: ast.call_arg_count(tree, id)
|
count :: ast.call_arg_count(tree, id)
|
||||||
for (0..count) |_| try arraylist.append(&block.arguments, ir.no_value)
|
try arraylist.reserve(&state.arguments, scratch_start + count)
|
||||||
|
for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value)
|
||||||
for (0..count) |i| {
|
for (0..count) |i| {
|
||||||
argument :: try lower_expression(state, ast.call_arg(tree, id, i))
|
argument :: try lower_expression(state, ast.call_arg(tree, id, i))
|
||||||
if (state.terminated) return ir.no_value
|
if (state.terminated) return ir.no_value
|
||||||
block.arguments.items[start + i] = argument
|
state.arguments.items[scratch_start + i] = argument
|
||||||
}
|
}
|
||||||
if (trap_type(state, id)) return ir.no_value
|
if (trap_type(state, id)) return ir.no_value
|
||||||
|
# branchful arguments end in a different block from the callee.
|
||||||
|
block := &state.module.functions.items[state.function].blocks.items[usize(state.block)]
|
||||||
|
start :: block.arguments.items.len
|
||||||
|
try arraylist.reserve(&block.arguments, start + count)
|
||||||
|
for (0..count) |i| try arraylist.append(&block.arguments, state.arguments.items[scratch_start + i])
|
||||||
return try emit(state, ir.Instruction{
|
return try emit(state, ir.Instruction{
|
||||||
op = .call,
|
op = .call,
|
||||||
type = type,
|
type = type,
|
||||||
|
|||||||
+272
-5
@@ -422,6 +422,13 @@ releases_temporaries_and_partially_owned_modules_on_allocation_failure test {
|
|||||||
source ::
|
source ::
|
||||||
`text :: "owned\nstring"
|
`text :: "owned\nstring"
|
||||||
`factor float :: 3.5
|
`factor float :: 3.5
|
||||||
|
`enabled :: true or false and false
|
||||||
|
`compose proc(left bool, right bool) bool {
|
||||||
|
` return left and right
|
||||||
|
`}
|
||||||
|
`broken proc() bool {
|
||||||
|
` return compose(true and absent, false or true)
|
||||||
|
`}
|
||||||
`identity proc(value string) string {
|
`identity proc(value string) string {
|
||||||
` if false {
|
` if false {
|
||||||
` return value
|
` return value
|
||||||
@@ -432,12 +439,12 @@ releases_temporaries_and_partially_owned_modules_on_allocation_failure test {
|
|||||||
`main proc() string {
|
`main proc() string {
|
||||||
` computed float :: factor * 2
|
` computed float :: factor * 2
|
||||||
` remaining := 2
|
` remaining := 2
|
||||||
` while remaining > 0 {
|
` while compose(remaining > 0 and enabled, compose(!false, compose(true or false, compose(true, compose(true, true))))) {
|
||||||
` pass :: identity(text)
|
` pass :: identity(text)
|
||||||
` identity(pass)
|
` identity(pass)
|
||||||
` remaining = remaining - 1
|
` remaining = remaining - 1
|
||||||
` }
|
` }
|
||||||
` if computed > 0 {
|
` if computed > 0 and (enabled or false) {
|
||||||
` saved :: identity(text)
|
` saved :: identity(text)
|
||||||
` if computed < 10 {
|
` if computed < 10 {
|
||||||
` return saved
|
` return saved
|
||||||
@@ -459,7 +466,7 @@ releases_temporaries_and_partially_owned_modules_on_allocation_failure test {
|
|||||||
defer typecheck.deinit(&checked)
|
defer typecheck.deinit(&checked)
|
||||||
succeeded := false
|
succeeded := false
|
||||||
failures := usize(0)
|
failures := usize(0)
|
||||||
for (usize(0)..usize(128)) |limit| {
|
for (usize(0)..usize(512)) |limit| {
|
||||||
state := AllocationState{ remaining = limit }
|
state := AllocationState{ remaining = limit }
|
||||||
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
module := lower(allocator, &tree, &resolution, &checked, &diagnostic_store) catch |error| {
|
module := lower(allocator, &tree, &resolution, &checked, &diagnostic_store) catch |error| {
|
||||||
@@ -856,8 +863,8 @@ loop_returns_do_not_guarantee_return_and_assignment_arithmetic_still_checks_over
|
|||||||
|
|
||||||
bounded_pure_loops_do_not_allocate_per_iteration_and_release_runtime_storage test {
|
bounded_pure_loops_do_not_allocate_per_iteration_and_release_runtime_storage test {
|
||||||
sources [2][]u8 :: [
|
sources [2][]u8 :: [
|
||||||
"main proc() int {\n n := 0\n while n < 1 {\n local :: n\n n = local + 1\n }\n return n\n}\n",
|
"main proc() int {\n n := 0\n while (n < 1 and !(n == 1)) or false {\n local :: n\n n = local + 1\n }\n return n\n}\n",
|
||||||
"main proc() int {\n n := 0\n while n < 10000 {\n local :: n\n n = local + 1\n }\n return n\n}\n",
|
"main proc() int {\n n := 0\n while (n < 10000 and !(n == 10000)) or false {\n local :: n\n n = local + 1\n }\n return n\n}\n",
|
||||||
]
|
]
|
||||||
expected [2]i64 :: [1, 10000]
|
expected [2]i64 :: [1, 10000]
|
||||||
allocations := usize(0)
|
allocations := usize(0)
|
||||||
@@ -877,3 +884,263 @@ bounded_pure_loops_do_not_allocate_per_iteration_and_release_runtime_storage tes
|
|||||||
else try testing.expect_equal(allocations, usize(64) - state.remaining)
|
else try testing.expect_equal(allocations, usize(64) - state.remaining)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean_truth_tables_nesting_negation_and_precedence_execute test {
|
||||||
|
sources [18][]u8 :: [
|
||||||
|
"main proc() bool { return false and false }\n",
|
||||||
|
"main proc() bool { return false and true }\n",
|
||||||
|
"main proc() bool { return true and false }\n",
|
||||||
|
"main proc() bool { return true and true }\n",
|
||||||
|
"main proc() bool { return false or false }\n",
|
||||||
|
"main proc() bool { return false or true }\n",
|
||||||
|
"main proc() bool { return true or false }\n",
|
||||||
|
"main proc() bool { return true or true }\n",
|
||||||
|
"main proc() bool { return !false }\n",
|
||||||
|
"main proc() bool { return !true }\n",
|
||||||
|
"main proc() bool { return !!true }\n",
|
||||||
|
"main proc() bool { return !!!true }\n",
|
||||||
|
"main proc() bool { return true or false and false }\n",
|
||||||
|
"main proc() bool { return (true or false) and false }\n",
|
||||||
|
"main proc() bool { return !(1 + 2 < 3) and 4 == 4 or false }\n",
|
||||||
|
"main proc() bool { return false or (true and (false or !false)) }\n",
|
||||||
|
"main proc() bool { return true and false or true and true }\n",
|
||||||
|
"main proc() bool { return !(-9223372036854775808 < 0) }\n",
|
||||||
|
]
|
||||||
|
expected [18]bool :: [
|
||||||
|
false, false, false, true, false, true, true, true,
|
||||||
|
true, false, true, false, true, false, true, true, true, false,
|
||||||
|
]
|
||||||
|
for (sources) |source, index| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
||||||
|
try testing.expect_equal(expected[index], outcome.value.data.boolean)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
branchful_calls_preserve_earlier_temps_nested_arguments_and_function_callees test {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`integer proc(value int) int { return value }
|
||||||
|
`boolean proc(value bool) bool { return value }
|
||||||
|
`score proc(first int, left bool, right bool, last int) int {
|
||||||
|
` if left and right { return first + last }
|
||||||
|
` return 0
|
||||||
|
`}
|
||||||
|
`invoke :: score
|
||||||
|
`main proc() int {
|
||||||
|
` local :: invoke
|
||||||
|
` return local(integer(20), boolean(false or true), boolean(true and (false or true)), local(integer(0), false or boolean(true), true and !false, local(integer(5), true or false, true and true, integer(17))))
|
||||||
|
`}
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
|
}
|
||||||
|
|
||||||
|
short_circuit_source_errors_remain_checked_but_skipped_in_every_consumer test {
|
||||||
|
sources [10][]u8 :: [
|
||||||
|
"main proc() bool { return false and absent or true }\n",
|
||||||
|
"main proc() bool { return true or 1 }\n",
|
||||||
|
"main proc() bool { return !(false and absent) }\n",
|
||||||
|
"main proc() bool { return (false and absent) == false }\n",
|
||||||
|
"flag :: true or absent\nmain proc() bool { return flag }\n",
|
||||||
|
"main proc() bool {\n flag :: true or absent\n return flag\n}\n",
|
||||||
|
"main proc() bool {\n flag := false\n flag = true or absent\n return flag\n}\n",
|
||||||
|
"identity proc(value bool) bool { return value }\nmain proc() bool { return identity(true or absent) }\n",
|
||||||
|
"main proc() bool {\n if true or absent { return true }\n return false\n}\n",
|
||||||
|
"main proc() bool {\n true or absent\n while false and absent {}\n return true\n}\n",
|
||||||
|
]
|
||||||
|
for (sources) |source| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
count :: diagnostics.count(&diagnostic_store)
|
||||||
|
try testing.expect(count > 0)
|
||||||
|
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
||||||
|
try testing.expect(outcome.value.data.boolean)
|
||||||
|
try testing.expect_equal(count, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
demanded_boolean_source_errors_keep_the_original_diagnostic_and_operand_span test {
|
||||||
|
sources [8][]u8 :: [
|
||||||
|
"main proc() bool { return true and absent }\n",
|
||||||
|
"main proc() bool { return false or absent }\n",
|
||||||
|
"main proc() bool { return true and 1 }\n",
|
||||||
|
"main proc() bool { return false or 1 }\n",
|
||||||
|
"main proc() bool { return !1 }\n",
|
||||||
|
"main proc() bool { return !absent }\n",
|
||||||
|
"main proc() bool { return absent and false }\n",
|
||||||
|
"main proc() bool { return absent or true }\n",
|
||||||
|
]
|
||||||
|
for (sources) |source, index| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
|
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
||||||
|
code diagnostics.Code :: if (index >= 2 and index <= 4) .invalid_operand else .unknown_name
|
||||||
|
operand []u8 :: if (index >= 2 and index <= 4) "1" else "absent"
|
||||||
|
try testing.expect_equal(code, original.code)
|
||||||
|
try testing.expect(mem.eql(source[usize(original.span.start)..usize(original.span.end)], operand))
|
||||||
|
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
||||||
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
branchful_call_arguments_evaluate_left_to_right_before_later_errors test {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`both proc(left bool, right bool) bool { return left and right }
|
||||||
|
`main proc() bool {
|
||||||
|
` return both(false or (true and first_absent), true and second_absent)
|
||||||
|
`}
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
||||||
|
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
original :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
||||||
|
try testing.expect_equal(diagnostics.Code.unknown_name, original.code)
|
||||||
|
try testing.expect(mem.eql(source[usize(original.span.start)..usize(original.span.end)], "first_absent"))
|
||||||
|
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
|
||||||
|
short_circuit_protects_uninitialized_reads_global_cycles_and_overflow test {
|
||||||
|
sources [6][]u8 :: [
|
||||||
|
"main proc() bool {\n before :: false and later\n later :: true\n return !before\n}\n",
|
||||||
|
"main proc() bool {\n before :: true or later\n later :: false\n return before\n}\n",
|
||||||
|
"flag bool :: true or flag\nmain proc() bool { return flag }\n",
|
||||||
|
"left bool :: false and right\nright bool :: left\nmain proc() bool { return !left }\n",
|
||||||
|
"main proc() bool { return !(false and 9223372036854775807 + 1 == 0) }\n",
|
||||||
|
"main proc() bool { return true or -(-9223372036854775808) == 0 }\n",
|
||||||
|
]
|
||||||
|
for (sources) |source| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect(outcome.value.data.boolean)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
demanded_boolean_runtime_errors_keep_the_instruction_span test {
|
||||||
|
sources [4][]u8 :: [
|
||||||
|
"main proc() bool {\n before :: true and later\n later :: true\n return before\n}\n",
|
||||||
|
"flag bool :: false or flag\nmain proc() bool { return flag }\n",
|
||||||
|
"main proc() bool { return true and 9223372036854775807 + 1 == 0 }\n",
|
||||||
|
"main proc() bool {\n minimum :: -9223372036854775808\n return false or -minimum == 0\n}\n",
|
||||||
|
]
|
||||||
|
codes [4]diagnostics.Code :: [.read_before_initialization, .initialization_cycle, .integer_overflow, .integer_overflow]
|
||||||
|
spans [4][]u8 :: ["later", "flag", "9223372036854775807 + 1", "-minimum"]
|
||||||
|
for (sources) |source, index| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
try expect_trap(outcome, &diagnostic_store, codes[index])
|
||||||
|
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
||||||
|
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], spans[index]))
|
||||||
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
composed_loop_guards_recompute_and_overwrite_synthetic_results test {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`enabled :: false or true and !false
|
||||||
|
`positive proc(n int) bool { return n > 0 }
|
||||||
|
`main proc() int {
|
||||||
|
` n := 4
|
||||||
|
` total := 0
|
||||||
|
` while (enabled and positive(n)) or n > 4 {
|
||||||
|
` if (n == 4 or n == 2) and !(n == 3) {
|
||||||
|
` total = total + 21
|
||||||
|
` }
|
||||||
|
` n = n - 1
|
||||||
|
` }
|
||||||
|
` return total + n
|
||||||
|
`}
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_branchful_global_initializers_and_calls_release_every_runtime_allocation test {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`first :: true and later
|
||||||
|
`later :: false or !false
|
||||||
|
`both proc(left bool, right bool) bool { return left and right }
|
||||||
|
`main proc() bool {
|
||||||
|
` return both(first and first, both(false or later, !false and later))
|
||||||
|
`}
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
failures := usize(0)
|
||||||
|
succeeded := false
|
||||||
|
for (usize(0)..usize(64)) |limit| {
|
||||||
|
state := AllocationState{ remaining = limit }
|
||||||
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
|
outcome :: interpreter.run(allocator, &module, &diagnostic_store) catch |error| {
|
||||||
|
try testing.expect(error == .out_of_memory)
|
||||||
|
failures += 1
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect(outcome.value.data.boolean)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
succeeded = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
try testing.expect(succeeded)
|
||||||
|
try testing.expect(failures > 0)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
|
||||||
|
skipped_rhs_does_not_hide_a_demanded_outer_type_mismatch test {
|
||||||
|
sources [3][]u8 :: [
|
||||||
|
"main proc() int { return true or absent }\n",
|
||||||
|
"main proc() void {\n number := 0\n number = true or absent\n}\n",
|
||||||
|
"integer proc(value int) int { return value }\nmain proc() int { return integer(true or absent) }\n",
|
||||||
|
]
|
||||||
|
codes [3]diagnostics.Code :: [.return_type_mismatch, .type_mismatch, .type_mismatch]
|
||||||
|
for (sources) |source, index| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
count :: diagnostics.count(&diagnostic_store)
|
||||||
|
try testing.expect_equal(2, count)
|
||||||
|
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
try expect_trap(outcome, &diagnostic_store, codes[index])
|
||||||
|
try testing.expect_equal(count, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+19
-4
@@ -185,7 +185,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
|||||||
.if: return try parse_if(state)
|
.if: return try parse_if(state)
|
||||||
.while: return try parse_while(state, allow_else)
|
.while: return try parse_while(state, allow_else)
|
||||||
.return: return try parse_return(state, allow_else)
|
.return: return try parse_return(state, allow_else)
|
||||||
.ident, .int, .float, .string, .true, .false, .minus, .open_paren: {
|
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren: {
|
||||||
expression :: try parse_expr(state)
|
expression :: try parse_expr(state)
|
||||||
return try ast.add_expression_stmt(&state.builder, expression)
|
return try ast.add_expression_stmt(&state.builder, expression)
|
||||||
}
|
}
|
||||||
@@ -418,10 +418,11 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
|||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
operand :: try parse_expr_bp(state, right_bp)
|
operand :: try parse_expr_bp(state, right_bp)
|
||||||
operand_span :: ast.builder_span(&state.builder, operand)
|
operand_span :: ast.builder_span(&state.builder, operand)
|
||||||
|
operator ast.UnaryOperator :: if (token.kind == .bang) .logical_not else .negate
|
||||||
yield try ast.add_unary(
|
yield try ast.add_unary(
|
||||||
&state.builder,
|
&state.builder,
|
||||||
ast.Span{ start = token.span.start, end = operand_span.end },
|
ast.Span{ start = token.span.start, end = operand_span.end },
|
||||||
.negate,
|
operator,
|
||||||
operand,
|
operand,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -525,7 +526,7 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
else
|
else
|
||||||
try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
||||||
|
|
||||||
if token.kind != .comma and !is_expression_boundary(token.kind) {
|
if !is_expression_recovery_boundary(token.kind) {
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,6 +543,16 @@ is_expression_boundary proc(kind lexer.TokenKind) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
is_expression_recovery_boundary proc(kind lexer.TokenKind) bool {
|
||||||
|
return (
|
||||||
|
kind == .comma
|
||||||
|
or kind == .and
|
||||||
|
or kind == .or
|
||||||
|
or is_expression_boundary(kind)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
recover_decl proc(
|
recover_decl proc(
|
||||||
state @mut State,
|
state @mut State,
|
||||||
@@ -625,7 +636,7 @@ consume_either proc(state @mut State, kinds []lexer.TokenKind) lexer.Token ! Syn
|
|||||||
@hide:file
|
@hide:file
|
||||||
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
|
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
|
||||||
return match kind {
|
return match kind {
|
||||||
.minus: BindingPowerScalar(50)
|
.minus, .bang: BindingPowerScalar(50)
|
||||||
else: null
|
else: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -633,6 +644,8 @@ prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
|
|||||||
@hide:file
|
@hide:file
|
||||||
infix_binding_power proc(kind lexer.TokenKind) ?BindingPower {
|
infix_binding_power proc(kind lexer.TokenKind) ?BindingPower {
|
||||||
return match kind {
|
return match kind {
|
||||||
|
.or: BindingPower{ left = BindingPowerScalar(2), right = BindingPowerScalar(3) }
|
||||||
|
.and: BindingPower{ left = BindingPowerScalar(4), right = BindingPowerScalar(5) }
|
||||||
.equal_equal, .not_equal: BindingPower{ left = BindingPowerScalar(10), right = BindingPowerScalar(11) }
|
.equal_equal, .not_equal: BindingPower{ left = BindingPowerScalar(10), right = BindingPowerScalar(11) }
|
||||||
.less, .less_equal, .greater, .greater_equal: BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) }
|
.less, .less_equal, .greater, .greater_equal: BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) }
|
||||||
.plus, .minus: BindingPower{ left = BindingPowerScalar(30), right = BindingPowerScalar(31) }
|
.plus, .minus: BindingPower{ left = BindingPowerScalar(30), right = BindingPowerScalar(31) }
|
||||||
@@ -644,6 +657,8 @@ infix_binding_power proc(kind lexer.TokenKind) ?BindingPower {
|
|||||||
@hide:file
|
@hide:file
|
||||||
binary_operator proc(kind lexer.TokenKind) ast.BinaryOperator {
|
binary_operator proc(kind lexer.TokenKind) ast.BinaryOperator {
|
||||||
return match kind {
|
return match kind {
|
||||||
|
.and: .logical_and
|
||||||
|
.or: .logical_or
|
||||||
.plus: .add
|
.plus: .add
|
||||||
.minus: .subtract
|
.minus: .subtract
|
||||||
.star: .multiply
|
.star: .multiply
|
||||||
|
|||||||
@@ -739,3 +739,163 @@ recovers_nested_braces_in_malformed_loop_body_and_assignment test {
|
|||||||
tail :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
tail :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
||||||
try testing.expect_equal("tail", ast.source_text(&tree, tail.name))
|
try testing.expect_equal("tail", ast.source_text(&tree, tail.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parses_logical_precedence_and_associativity test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source :: "value :: !probe() == a + b * c < d and e and f or g or h\n"
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
value :: ast.statement_decl(&tree, ast.package_decl(&tree, 0))
|
||||||
|
outer_or :: ast.binary_expr(&tree, value.initializer)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.logical_or, outer_or.operator)
|
||||||
|
try testing.expect_equal("h", ast.source_text(&tree, ast.node_span(&tree, outer_or.right)))
|
||||||
|
inner_or :: ast.binary_expr(&tree, outer_or.left)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.logical_or, inner_or.operator)
|
||||||
|
outer_and :: ast.binary_expr(&tree, inner_or.left)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.logical_and, outer_and.operator)
|
||||||
|
try testing.expect_equal("f", ast.source_text(&tree, ast.node_span(&tree, outer_and.right)))
|
||||||
|
inner_and :: ast.binary_expr(&tree, outer_and.left)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.logical_and, inner_and.operator)
|
||||||
|
equality :: ast.binary_expr(&tree, inner_and.left)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.equal, equality.operator)
|
||||||
|
logical_not :: ast.unary_expr(&tree, equality.left)
|
||||||
|
try testing.expect_equal(ast.UnaryOperator.logical_not, logical_not.operator)
|
||||||
|
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, logical_not.operand))
|
||||||
|
try testing.expect_equal("!probe()", ast.source_text(&tree, logical_not.span))
|
||||||
|
comparison :: ast.binary_expr(&tree, equality.right)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.less, comparison.operator)
|
||||||
|
addition :: ast.binary_expr(&tree, comparison.left)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.add, addition.operator)
|
||||||
|
multiply :: ast.binary_expr(&tree, addition.right)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.multiply, multiply.operator)
|
||||||
|
}
|
||||||
|
|
||||||
|
parses_right_associative_mixed_prefix_operators test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source :: "value :: !-!probe()\nother :: -!false\n"
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
value :: ast.statement_decl(&tree, ast.package_decl(&tree, 0))
|
||||||
|
first :: ast.unary_expr(&tree, value.initializer)
|
||||||
|
second :: ast.unary_expr(&tree, first.operand)
|
||||||
|
third :: ast.unary_expr(&tree, second.operand)
|
||||||
|
try testing.expect_equal(ast.UnaryOperator.logical_not, first.operator)
|
||||||
|
try testing.expect_equal(ast.UnaryOperator.negate, second.operator)
|
||||||
|
try testing.expect_equal(ast.UnaryOperator.logical_not, third.operator)
|
||||||
|
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, third.operand))
|
||||||
|
try testing.expect_equal("!-!probe()", ast.source_text(&tree, first.span))
|
||||||
|
try testing.expect_equal("-!probe()", ast.source_text(&tree, second.span))
|
||||||
|
other :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
||||||
|
negate :: ast.unary_expr(&tree, other.initializer)
|
||||||
|
try testing.expect_equal(ast.UnaryOperator.negate, negate.operator)
|
||||||
|
try testing.expect_equal(ast.UnaryOperator.logical_not, ast.unary_expr(&tree, negate.operand).operator)
|
||||||
|
}
|
||||||
|
|
||||||
|
parses_logical_expressions_in_all_expression_positions test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`global :: false and absent
|
||||||
|
`main proc() bool {
|
||||||
|
` value := true or absent
|
||||||
|
` value = !value and ready()
|
||||||
|
` !value
|
||||||
|
` if !value or ready() { value = false }
|
||||||
|
` while value and !done() { value = false }
|
||||||
|
` return choose(value or false, nested(!value and true))
|
||||||
|
`}
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
global :: ast.statement_decl(&tree, ast.package_decl(&tree, 0))
|
||||||
|
global_and :: ast.binary_expr(&tree, global.initializer)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.logical_and, global_and.operator)
|
||||||
|
try testing.expect_equal("absent", ast.source_text(&tree, ast.node_span(&tree, global_and.right)))
|
||||||
|
function :: ast.function_decl(&tree, ast.package_decl(&tree, 1))
|
||||||
|
try testing.expect_equal(6, ast.block_statement_count(&tree, function.body))
|
||||||
|
local :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0))
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.logical_or, ast.binary_expr(&tree, local.initializer).operator)
|
||||||
|
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, function.body, 1))
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.logical_and, ast.binary_expr(&tree, assignment.value).operator)
|
||||||
|
expression :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 2))
|
||||||
|
try testing.expect_equal(ast.UnaryOperator.logical_not, ast.unary_expr(&tree, expression).operator)
|
||||||
|
conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, function.body, 3))
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.logical_or, ast.binary_expr(&tree, conditional.condition).operator)
|
||||||
|
loop :: ast.while_stmt(&tree, ast.block_statement(&tree, function.body, 4))
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.logical_and, ast.binary_expr(&tree, loop.condition).operator)
|
||||||
|
returned :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 5)).value?
|
||||||
|
try testing.expect_equal(2, ast.call_arg_count(&tree, returned))
|
||||||
|
first_argument :: ast.call_arg(&tree, returned, 0)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.logical_or, ast.binary_expr(&tree, first_argument).operator)
|
||||||
|
nested :: ast.call_arg(&tree, returned, 1)
|
||||||
|
nested_argument :: ast.call_arg(&tree, nested, 0)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.logical_and, ast.binary_expr(&tree, nested_argument).operator)
|
||||||
|
}
|
||||||
|
|
||||||
|
recovers_missing_logical_operands_and_preserves_enclosing_nodes test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`main proc() bool {
|
||||||
|
` first :: true and
|
||||||
|
` second :: true and or false
|
||||||
|
` third :: choose(!, false or)
|
||||||
|
` if ! { return false }
|
||||||
|
` return true or }
|
||||||
|
`after :: true
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
|
try testing.expect_equal(6, diagnostics.count(&diagnostic_store))
|
||||||
|
try testing.expect_equal(2, ast.package_decl_count(&tree))
|
||||||
|
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||||
|
try testing.expect_equal(5, ast.block_statement_count(&tree, function.body))
|
||||||
|
first :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0))
|
||||||
|
first_and :: ast.binary_expr(&tree, first.initializer)
|
||||||
|
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, first_and.right))
|
||||||
|
second :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1))
|
||||||
|
second_or :: ast.binary_expr(&tree, second.initializer)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.logical_or, second_or.operator)
|
||||||
|
second_and :: ast.binary_expr(&tree, second_or.left)
|
||||||
|
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, second_and.right))
|
||||||
|
try testing.expect_equal(ast.ExprKind.literal_bool, ast.expr_kind(&tree, second_or.right))
|
||||||
|
third :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 2))
|
||||||
|
try testing.expect_equal(2, ast.call_arg_count(&tree, third.initializer))
|
||||||
|
missing_prefix :: ast.unary_expr(&tree, ast.call_arg(&tree, third.initializer, 0))
|
||||||
|
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing_prefix.operand))
|
||||||
|
missing_rhs :: ast.binary_expr(&tree, ast.call_arg(&tree, third.initializer, 1))
|
||||||
|
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing_rhs.right))
|
||||||
|
conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, function.body, 3))
|
||||||
|
try testing.expect_equal(1, ast.block_statement_count(&tree, conditional.then_block))
|
||||||
|
returned :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 4)).value?
|
||||||
|
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, ast.binary_expr(&tree, returned).right))
|
||||||
|
after :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
||||||
|
try testing.expect_equal("after", ast.source_text(&tree, after.name))
|
||||||
|
}
|
||||||
|
|||||||
@@ -199,10 +199,13 @@ check proc(
|
|||||||
id :: ast.NodeId(i)
|
id :: ast.NodeId(i)
|
||||||
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values) continue
|
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values) continue
|
||||||
match ast.expr_kind(tree, id) {
|
match ast.expr_kind(tree, id) {
|
||||||
.unary: try require_numeric(&state, id, ast.unary_expr(tree, id).operand)
|
.unary: {
|
||||||
|
expression :: ast.unary_expr(tree, id)
|
||||||
|
if (expression.operator == .negate) try require_numeric(&state, id, expression.operand)
|
||||||
|
}
|
||||||
.binary: {
|
.binary: {
|
||||||
expression :: ast.binary_expr(tree, id)
|
expression :: ast.binary_expr(tree, id)
|
||||||
if (bool_equality(&state, id)) continue
|
if (logical(expression.operator) or bool_equality(&state, id)) continue
|
||||||
try require_numeric(&state, id, expression.left)
|
try require_numeric(&state, id, expression.left)
|
||||||
try require_numeric(&state, id, expression.right)
|
try require_numeric(&state, id, expression.right)
|
||||||
}
|
}
|
||||||
@@ -216,9 +219,13 @@ check proc(
|
|||||||
id :: ast.NodeId(i)
|
id :: ast.NodeId(i)
|
||||||
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values) continue
|
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values) continue
|
||||||
match ast.expr_kind(tree, id) {
|
match ast.expr_kind(tree, id) {
|
||||||
.unary: try numeric_operand(&state, id, ast.unary_expr(tree, id).operand)
|
.unary: {
|
||||||
|
expression :: ast.unary_expr(tree, id)
|
||||||
|
if (expression.operator == .negate) try numeric_operand(&state, id, expression.operand)
|
||||||
|
}
|
||||||
.binary: {
|
.binary: {
|
||||||
expression :: ast.binary_expr(tree, id)
|
expression :: ast.binary_expr(tree, id)
|
||||||
|
if (logical(expression.operator)) continue
|
||||||
if (comparison(expression.operator)) {
|
if (comparison(expression.operator)) {
|
||||||
if (!bool_equality(&state, id)) try comparison_operands(&state, id)
|
if (!bool_equality(&state, id)) try comparison_operands(&state, id)
|
||||||
} else {
|
} else {
|
||||||
@@ -599,14 +606,19 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.unary: {
|
.unary: {
|
||||||
state.cells[usize(id)].numeric = true
|
expression :: ast.unary_expr(tree, id)
|
||||||
operand :: ast.unary_expr(tree, id).operand
|
if (expression.operator == .logical_not) {
|
||||||
|
state.cells[usize(id)].type = Type{ kind = .bool }
|
||||||
|
} else {
|
||||||
|
state.cells[usize(id)].numeric = true
|
||||||
|
}
|
||||||
|
operand :: expression.operand
|
||||||
try collect_expression(state, operand)
|
try collect_expression(state, operand)
|
||||||
try depend(state, operand, id)
|
try depend(state, operand, id)
|
||||||
}
|
}
|
||||||
.binary: {
|
.binary: {
|
||||||
expression :: ast.binary_expr(tree, id)
|
expression :: ast.binary_expr(tree, id)
|
||||||
if (comparison(expression.operator)) {
|
if (comparison(expression.operator) or logical(expression.operator)) {
|
||||||
state.cells[usize(id)].type = Type{ kind = .bool }
|
state.cells[usize(id)].type = Type{ kind = .bool }
|
||||||
} else {
|
} else {
|
||||||
state.cells[usize(id)].numeric = true
|
state.cells[usize(id)].numeric = true
|
||||||
@@ -614,7 +626,8 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
|||||||
try collect_expression(state, expression.left)
|
try collect_expression(state, expression.left)
|
||||||
try collect_expression(state, expression.right)
|
try collect_expression(state, expression.right)
|
||||||
try depend(state, expression.left, id)
|
try depend(state, expression.left, id)
|
||||||
try depend(state, expression.right, id)
|
# the RHS may be skipped, so its errors do not erase the bool result.
|
||||||
|
if (!logical(expression.operator)) try depend(state, expression.right, id)
|
||||||
}
|
}
|
||||||
.call: {
|
.call: {
|
||||||
callee :: ast.call_expr(tree, id).callee
|
callee :: ast.call_expr(tree, id).callee
|
||||||
@@ -643,6 +656,11 @@ comparison proc(operator ast.BinaryOperator) bool {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
logical proc(operator ast.BinaryOperator) bool {
|
||||||
|
return operator == .logical_and or operator == .logical_or
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
bool_equality proc(state @mut State, id ast.NodeId) bool {
|
bool_equality proc(state @mut State, id ast.NodeId) bool {
|
||||||
expression :: ast.binary_expr(state.tree, id)
|
expression :: ast.binary_expr(state.tree, id)
|
||||||
@@ -722,6 +740,14 @@ fail proc(state @mut State, id ast.NodeId, code diagnostics.Code, span ast.Span)
|
|||||||
state.types[usize(id)] = invalid_type(try diagnostics.add(state.diagnostics, code, span))
|
state.types[usize(id)] = invalid_type(try diagnostics.add(state.diagnostics, code, span))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
require_bool proc(state @mut State, operand ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
type :: state.types[usize(operand)]
|
||||||
|
if (type.kind != .invalid and type.kind != .bool) {
|
||||||
|
try fail(state, operand, .invalid_operand, ast.node_span(state.tree, operand))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
mutable_target proc(state @State, id ast.NodeId) bool {
|
mutable_target proc(state @State, id ast.NodeId) bool {
|
||||||
binding :: sema.binding(state.resolution, id)
|
binding :: sema.binding(state.resolution, id)
|
||||||
@@ -793,13 +819,23 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
|||||||
.expression: {
|
.expression: {
|
||||||
match ast.expr_kind(tree, id) {
|
match ast.expr_kind(tree, id) {
|
||||||
.unary: {
|
.unary: {
|
||||||
operand :: state.types[usize(ast.unary_expr(tree, id).operand)]
|
expression :: ast.unary_expr(tree, id)
|
||||||
|
if (expression.operator == .logical_not) {
|
||||||
|
try require_bool(state, expression.operand)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operand :: state.types[usize(expression.operand)]
|
||||||
if (operand.kind != .invalid and !numeric_kind(operand.kind)) {
|
if (operand.kind != .invalid and !numeric_kind(operand.kind)) {
|
||||||
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
|
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.binary: {
|
.binary: {
|
||||||
expression :: ast.binary_expr(tree, id)
|
expression :: ast.binary_expr(tree, id)
|
||||||
|
if (logical(expression.operator)) {
|
||||||
|
try require_bool(state, expression.left)
|
||||||
|
try require_bool(state, expression.right)
|
||||||
|
return
|
||||||
|
}
|
||||||
left :: state.types[usize(expression.left)]
|
left :: state.types[usize(expression.left)]
|
||||||
right :: state.types[usize(expression.right)]
|
right :: state.types[usize(expression.right)]
|
||||||
if (left.kind == .invalid or right.kind == .invalid) return
|
if (left.kind == .invalid or right.kind == .invalid) return
|
||||||
|
|||||||
@@ -1249,3 +1249,302 @@ while_checks_nested_bodies_without_claiming_a_zero_iteration_return test {
|
|||||||
try testing.expect_equal(1, code_count(&diagnostic_store, .condition_not_bool))
|
try testing.expect_equal(1, code_count(&diagnostic_store, .condition_not_bool))
|
||||||
try testing.expect_equal(3, code_count(&diagnostic_store, .invalid_operand))
|
try testing.expect_equal(3, code_count(&diagnostic_store, .invalid_operand))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logical_types_preserve_comparison_context_and_assignment_isolation test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`seed :: 3
|
||||||
|
`forward :: seed
|
||||||
|
`composed :: !(forward < 4.0) and true or false
|
||||||
|
`equal :: !(true and false) == (true or false)
|
||||||
|
`nested :: !!!true
|
||||||
|
`identity proc(value bool) bool { return value }
|
||||||
|
`main proc() bool {
|
||||||
|
` count := 1
|
||||||
|
` flag := true
|
||||||
|
` flag = !(count < 2) and identity(true) or false
|
||||||
|
` return flag
|
||||||
|
`}
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||||
|
defer sema.deinit(&resolution)
|
||||||
|
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
|
||||||
|
defer deinit(&result)
|
||||||
|
|
||||||
|
for (usize(0)..usize(2)) |index| try expect_kind(&result, ast.package_decl(&tree, index), .float)
|
||||||
|
for usize(2)..usize(5) |index| {
|
||||||
|
declaration :: ast.package_decl(&tree, index)
|
||||||
|
try expect_kind(&result, declaration, .bool)
|
||||||
|
try expect_kind(&result, ast.statement_decl(&tree, declaration).initializer, .bool)
|
||||||
|
}
|
||||||
|
composed :: ast.binary_expr(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 2)).initializer)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.logical_or, composed.operator)
|
||||||
|
conjunction :: ast.binary_expr(&tree, composed.left)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.logical_and, conjunction.operator)
|
||||||
|
inverted :: ast.unary_expr(&tree, conjunction.left)
|
||||||
|
try expect_kind(&result, conjunction.left, .bool)
|
||||||
|
comparison_view :: ast.binary_expr(&tree, inverted.operand)
|
||||||
|
try expect_kind(&result, comparison_view.left, .float)
|
||||||
|
try expect_kind(&result, comparison_view.right, .float)
|
||||||
|
nested := ast.statement_decl(&tree, ast.package_decl(&tree, 4)).initializer
|
||||||
|
for 0..3 |_| {
|
||||||
|
try expect_kind(&result, nested, .bool)
|
||||||
|
view :: ast.unary_expr(&tree, nested)
|
||||||
|
try testing.expect_equal(ast.UnaryOperator.logical_not, view.operator)
|
||||||
|
nested = view.operand
|
||||||
|
}
|
||||||
|
try expect_kind(&result, nested, .bool)
|
||||||
|
body :: ast.function_decl(&tree, ast.package_decl(&tree, 6)).body
|
||||||
|
count :: ast.block_statement(&tree, body, 0)
|
||||||
|
try expect_kind(&result, count, .int)
|
||||||
|
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 2))
|
||||||
|
try expect_kind(&result, assignment.value, .bool)
|
||||||
|
disjunction :: ast.binary_expr(&tree, assignment.value)
|
||||||
|
assignment_conjunction :: ast.binary_expr(&tree, disjunction.left)
|
||||||
|
assignment_not :: ast.unary_expr(&tree, assignment_conjunction.left)
|
||||||
|
assignment_comparison :: ast.binary_expr(&tree, assignment_not.operand)
|
||||||
|
try expect_kind(&result, assignment_comparison.left, .int)
|
||||||
|
try expect_kind(&result, assignment_comparison.right, .int)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
|
||||||
|
logical_operand_errors_are_independent_and_skipped_rhs_preserves_bool test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`number :: 1
|
||||||
|
`number_alias :: number
|
||||||
|
`skipped_unknown :: false and absent
|
||||||
|
`skipped_kind :: true or number
|
||||||
|
`both_bad :: 1 and "wrong"
|
||||||
|
`demanded_unknown :: missing or true
|
||||||
|
`demanded_not :: !number_alias
|
||||||
|
`nested :: !(false and absent_nested) == true
|
||||||
|
`independent :: left_missing and 1
|
||||||
|
`identity proc(value bool) bool { return value }
|
||||||
|
`skips proc() bool {
|
||||||
|
` local :: true or 1
|
||||||
|
` value := false
|
||||||
|
` value = false and absent_assignment
|
||||||
|
` identity(true or absent_argument)
|
||||||
|
` if true or 1 { value = true }
|
||||||
|
` while false and 1 { value = false }
|
||||||
|
` return false and absent_return
|
||||||
|
`}
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||||
|
defer sema.deinit(&resolution)
|
||||||
|
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
|
||||||
|
defer deinit(&result)
|
||||||
|
|
||||||
|
for usize(0)..usize(2) |index| {
|
||||||
|
declaration :: ast.package_decl(&tree, index)
|
||||||
|
try expect_kind(&result, declaration, .int)
|
||||||
|
try expect_kind(&result, ast.statement_decl(&tree, declaration).initializer, .int)
|
||||||
|
}
|
||||||
|
skipped_indices [3]usize :: [2, 3, 7]
|
||||||
|
for skipped_indices |index| {
|
||||||
|
declaration :: ast.package_decl(&tree, index)
|
||||||
|
try expect_kind(&result, declaration, .bool)
|
||||||
|
try expect_kind(&result, ast.statement_decl(&tree, declaration).initializer, .bool)
|
||||||
|
}
|
||||||
|
unknown :: ast.binary_expr(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 2)).initializer)
|
||||||
|
try testing.expect_equal(sema.binding(&resolution, unknown.right).data.diagnostic, type_of(&result, unknown.right).diagnostic)
|
||||||
|
_ = try expect_error(&result, &diagnostic_store, unknown.right, .unknown_name, ast.node_span(&tree, unknown.right))
|
||||||
|
wrong :: ast.binary_expr(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 3)).initializer)
|
||||||
|
_ = try expect_error(&result, &diagnostic_store, wrong.right, .invalid_operand, ast.node_span(&tree, wrong.right))
|
||||||
|
both_declaration :: ast.package_decl(&tree, 4)
|
||||||
|
both_id :: ast.statement_decl(&tree, both_declaration).initializer
|
||||||
|
both :: ast.binary_expr(&tree, both_id)
|
||||||
|
left_error :: try expect_error(&result, &diagnostic_store, both.left, .invalid_operand, ast.node_span(&tree, both.left))
|
||||||
|
right_error :: try expect_error(&result, &diagnostic_store, both.right, .invalid_operand, ast.node_span(&tree, both.right))
|
||||||
|
try testing.expect(left_error != right_error)
|
||||||
|
try testing.expect_equal(left_error, type_of(&result, both_id).diagnostic)
|
||||||
|
try testing.expect_equal(left_error, type_of(&result, both_declaration).diagnostic)
|
||||||
|
demanded_id :: ast.statement_decl(&tree, ast.package_decl(&tree, 5)).initializer
|
||||||
|
demanded :: ast.binary_expr(&tree, demanded_id)
|
||||||
|
try testing.expect_equal(sema.binding(&resolution, demanded.left).data.diagnostic, type_of(&result, demanded_id).diagnostic)
|
||||||
|
not_id :: ast.statement_decl(&tree, ast.package_decl(&tree, 6)).initializer
|
||||||
|
not_view :: ast.unary_expr(&tree, not_id)
|
||||||
|
not_error :: try expect_error(&result, &diagnostic_store, not_view.operand, .invalid_operand, ast.node_span(&tree, not_view.operand))
|
||||||
|
try testing.expect_equal(not_error, type_of(&result, not_id).diagnostic)
|
||||||
|
independent_id :: ast.statement_decl(&tree, ast.package_decl(&tree, 8)).initializer
|
||||||
|
independent :: ast.binary_expr(&tree, independent_id)
|
||||||
|
try testing.expect_equal(sema.binding(&resolution, independent.left).data.diagnostic, type_of(&result, independent_id).diagnostic)
|
||||||
|
_ = try expect_error(&result, &diagnostic_store, independent.right, .invalid_operand, ast.node_span(&tree, independent.right))
|
||||||
|
body :: ast.function_decl(&tree, ast.package_decl(&tree, 10)).body
|
||||||
|
try expect_kind(&result, body, .none)
|
||||||
|
try expect_kind(&result, ast.block_statement(&tree, body, 0), .bool)
|
||||||
|
assignment_id :: ast.block_statement(&tree, body, 2)
|
||||||
|
assignment :: ast.assignment_stmt(&tree, assignment_id)
|
||||||
|
try expect_kind(&result, assignment_id, .void)
|
||||||
|
try expect_kind(&result, assignment.value, .bool)
|
||||||
|
call :: ast.statement_expr(&tree, ast.block_statement(&tree, body, 3))
|
||||||
|
try expect_kind(&result, call, .bool)
|
||||||
|
try expect_kind(&result, ast.call_arg(&tree, call, 0), .bool)
|
||||||
|
conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 4))
|
||||||
|
loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 5))
|
||||||
|
try expect_kind(&result, conditional.condition, .bool)
|
||||||
|
try expect_kind(&result, loop.condition, .bool)
|
||||||
|
returned :: ast.block_statement(&tree, body, 6)
|
||||||
|
try expect_kind(&result, returned, .void)
|
||||||
|
if (ast.return_stmt(&tree, returned).value) |value| {
|
||||||
|
try expect_kind(&result, value, .bool)
|
||||||
|
} else {
|
||||||
|
return .expectation_failed
|
||||||
|
}
|
||||||
|
try testing.expect_equal(7, code_count(&diagnostic_store, .unknown_name))
|
||||||
|
try testing.expect_equal(8, code_count(&diagnostic_store, .invalid_operand))
|
||||||
|
try testing.expect_equal(15, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
|
||||||
|
logical_external_mismatches_and_always_demanded_not_preserve_errors test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`accept_bool proc(value bool) bool { return value }
|
||||||
|
`accept_int proc(value int) bool { return true }
|
||||||
|
`noop proc() void {}
|
||||||
|
`wrong_decl int :: true or 1
|
||||||
|
`wrong_return proc() int { return false and absent_return }
|
||||||
|
`wrong_argument proc() bool { return accept_int(false and absent_argument) }
|
||||||
|
`wrong_assignment proc() void {
|
||||||
|
` count := 1
|
||||||
|
` count = true or 1
|
||||||
|
`}
|
||||||
|
`unary_bad proc() void {
|
||||||
|
` !1.0
|
||||||
|
` !"text"
|
||||||
|
` !accept_bool
|
||||||
|
` !noop()
|
||||||
|
`}
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||||
|
defer sema.deinit(&resolution)
|
||||||
|
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
|
||||||
|
defer deinit(&result)
|
||||||
|
|
||||||
|
declaration :: ast.package_decl(&tree, 3)
|
||||||
|
initializer :: ast.statement_decl(&tree, declaration).initializer
|
||||||
|
declaration_error :: try expect_error(&result, &diagnostic_store, initializer, .type_mismatch, ast.node_span(&tree, initializer))
|
||||||
|
try testing.expect_equal(declaration_error, type_of(&result, declaration).diagnostic)
|
||||||
|
return_body :: ast.function_decl(&tree, ast.package_decl(&tree, 4)).body
|
||||||
|
return_id :: ast.block_statement(&tree, return_body, 0)
|
||||||
|
if (ast.return_stmt(&tree, return_id).value) |value| {
|
||||||
|
return_error :: try expect_error(&result, &diagnostic_store, value, .return_type_mismatch, ast.node_span(&tree, value))
|
||||||
|
try testing.expect_equal(return_error, type_of(&result, return_id).diagnostic)
|
||||||
|
operand :: ast.binary_expr(&tree, value).right
|
||||||
|
try testing.expect_equal(sema.binding(&resolution, operand).data.diagnostic, type_of(&result, operand).diagnostic)
|
||||||
|
try testing.expect(return_error != type_of(&result, operand).diagnostic)
|
||||||
|
} else {
|
||||||
|
return .expectation_failed
|
||||||
|
}
|
||||||
|
argument_body :: ast.function_decl(&tree, ast.package_decl(&tree, 5)).body
|
||||||
|
argument_return :: ast.block_statement(&tree, argument_body, 0)
|
||||||
|
if (ast.return_stmt(&tree, argument_return).value) |call| {
|
||||||
|
argument :: ast.call_arg(&tree, call, 0)
|
||||||
|
argument_error :: try expect_error(&result, &diagnostic_store, argument, .type_mismatch, ast.node_span(&tree, argument))
|
||||||
|
try testing.expect_equal(argument_error, type_of(&result, call).diagnostic)
|
||||||
|
try testing.expect_equal(argument_error, type_of(&result, argument_return).diagnostic)
|
||||||
|
} else {
|
||||||
|
return .expectation_failed
|
||||||
|
}
|
||||||
|
assignment_body :: ast.function_decl(&tree, ast.package_decl(&tree, 6)).body
|
||||||
|
assignment_id :: ast.block_statement(&tree, assignment_body, 1)
|
||||||
|
assignment :: ast.assignment_stmt(&tree, assignment_id)
|
||||||
|
assignment_error :: try expect_error(&result, &diagnostic_store, assignment.value, .type_mismatch, ast.node_span(&tree, assignment.value))
|
||||||
|
try testing.expect_equal(assignment_error, type_of(&result, assignment_id).diagnostic)
|
||||||
|
try expect_kind(&result, assignment.target, .int)
|
||||||
|
try expect_kind(&result, ast.block_statement(&tree, assignment_body, 0), .int)
|
||||||
|
unary_body :: ast.function_decl(&tree, ast.package_decl(&tree, 7)).body
|
||||||
|
for usize(0)..usize(4) |index| {
|
||||||
|
expression :: ast.statement_expr(&tree, ast.block_statement(&tree, unary_body, index))
|
||||||
|
operand :: ast.unary_expr(&tree, expression).operand
|
||||||
|
original :: try expect_error(&result, &diagnostic_store, operand, .invalid_operand, ast.node_span(&tree, operand))
|
||||||
|
try testing.expect_equal(original, type_of(&result, expression).diagnostic)
|
||||||
|
}
|
||||||
|
for (usize(0)..usize(3)) |index| try expect_kind(&result, ast.package_decl(&tree, index), .function)
|
||||||
|
try testing.expect_equal(6, code_count(&diagnostic_store, .invalid_operand))
|
||||||
|
try testing.expect_equal(2, code_count(&diagnostic_store, .unknown_name))
|
||||||
|
try testing.expect_equal(3, code_count(&diagnostic_store, .type_mismatch))
|
||||||
|
try testing.expect_equal(1, code_count(&diagnostic_store, .return_type_mismatch))
|
||||||
|
try testing.expect_equal(12, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
|
||||||
|
short_circuit_skip_paths_do_not_guarantee_a_return test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`expression proc() int { false and missing_expression }
|
||||||
|
`declaration proc() int { local :: true or 1 }
|
||||||
|
`assignment proc() int {
|
||||||
|
` value := false
|
||||||
|
` value = false and missing_assignment
|
||||||
|
`}
|
||||||
|
`call proc() int { take(true or 1) }
|
||||||
|
`conditional proc() int { if false and missing_condition { return 1 } }
|
||||||
|
`loop proc() int { while true or missing_guard { return 1 } }
|
||||||
|
`loop_body proc() int { while true { true or 1 } }
|
||||||
|
`arms proc(flag bool) int {
|
||||||
|
` if flag { false and missing_arm } else { return 1 }
|
||||||
|
`}
|
||||||
|
`demanded proc() int { missing_left or true }
|
||||||
|
`successful proc() bool { return true or missing_return }
|
||||||
|
`take proc(value bool) void {}
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||||
|
defer sema.deinit(&resolution)
|
||||||
|
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
|
||||||
|
defer deinit(&result)
|
||||||
|
|
||||||
|
for usize(0)..usize(8) |index| {
|
||||||
|
function :: ast.package_decl(&tree, index)
|
||||||
|
try expect_kind(&result, function, .function)
|
||||||
|
body :: ast.function_decl(&tree, function).body
|
||||||
|
_ = try expect_error(&result, &diagnostic_store, body, .missing_return, ast.node_span(&tree, body))
|
||||||
|
}
|
||||||
|
expression_body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
|
||||||
|
expression :: ast.statement_expr(&tree, ast.block_statement(&tree, expression_body, 0))
|
||||||
|
try expect_kind(&result, expression, .bool)
|
||||||
|
loop_body :: ast.function_decl(&tree, ast.package_decl(&tree, 6)).body
|
||||||
|
loop_id :: ast.block_statement(&tree, loop_body, 0)
|
||||||
|
try expect_kind(&result, loop_id, .void)
|
||||||
|
demanded_body :: ast.function_decl(&tree, ast.package_decl(&tree, 8)).body
|
||||||
|
demanded :: ast.statement_expr(&tree, ast.block_statement(&tree, demanded_body, 0))
|
||||||
|
left :: ast.binary_expr(&tree, demanded).left
|
||||||
|
original :: sema.binding(&resolution, left).data.diagnostic
|
||||||
|
try testing.expect_equal(original, type_of(&result, demanded).diagnostic)
|
||||||
|
try testing.expect_equal(original, type_of(&result, demanded_body).diagnostic)
|
||||||
|
successful_body :: ast.function_decl(&tree, ast.package_decl(&tree, 9)).body
|
||||||
|
try expect_kind(&result, successful_body, .none)
|
||||||
|
try expect_kind(&result, ast.block_statement(&tree, successful_body, 0), .void)
|
||||||
|
try testing.expect_equal(8, code_count(&diagnostic_store, .missing_return))
|
||||||
|
try testing.expect_equal(7, code_count(&diagnostic_store, .unknown_name))
|
||||||
|
try testing.expect_equal(3, code_count(&diagnostic_store, .invalid_operand))
|
||||||
|
try testing.expect_equal(18, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user