conditionally unwrapping while loops
This commit is contained in:
+5
-5
@@ -39,7 +39,7 @@ roadmap and milestone history.
|
||||
- unsafe `constcast!(value)` for restoring mutability to pointers, optional pointers, and slices without changing their child type or shape
|
||||
- UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, plus raw backtick multiline strings
|
||||
- narrow immutable zero-terminated byte pointer/slice conversion to `*c_char` / `?*c_char` without general `u8`/`c_char` interchange
|
||||
- optionals with `null`, `orelse`, postfix `?`, conditional unwraps, guarded unwraps, and left-to-right short-circuiting multi-unwraps
|
||||
- optionals with `null`, `orelse`, postfix `?`, conditional `if`/`while` unwraps, guarded unwraps, and left-to-right short-circuiting multi-unwraps
|
||||
- nominal distinct types with explicit scalar backing conversion during construction and explicit scalar backing extraction, native enums with optional explicit integer backing and explicit backing-to-scalar casts, contextual enum literals, and imported C enums as target-backed integer aliases
|
||||
- compiler-reordered native structs with fields laid out by decreasing alignment (declaration order breaks ties and remains the reflection/diagnostic order), opaque nominal records with `Name :: opaque`, complete source-order `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
|
||||
- named native struct fields may declare defaults with `field T = expression`; keyed literals use defaults for omitted fields and explicit initializers override them
|
||||
@@ -119,7 +119,7 @@ fields. `_` is not a keyword member name.
|
||||
- assignments and compound assignments `+= -= *= /= &= |= xor= <<= >>= <<|=` with single evaluation of complex lvalues; `/=` is float-only and `xor=` is contiguous
|
||||
- field access through struct values and pointers, index/slice bounds contextually coerced to `usize`, and unsigned narrower index support
|
||||
- boolean `if` / `else if` / `else` and `for` loops with braceless single-statement bodies when the preceding expression is parenthesized or a function call
|
||||
- `while` loops with optional post-iteration update clauses
|
||||
- `while` loops with conditional unwrap captures and guards plus optional post-iteration update clauses
|
||||
- `for` loops over ranges, arrays, slices, and pointers-to-arrays with copy captures, pointer captures `|@item|`, and optional `usize` index captures; `inline for` specializes a comptime aggregate into one checked body per element
|
||||
- `break`, `continue`, labeled `break :label`, labeled `continue :label`, and labeled plain blocks; `break :label` can cross nested scopes to exit a labeled block
|
||||
- bare block scopes, `defer`, and fallible-function `errdefer` with optional error capture; cleanup is block-scoped and LIFO
|
||||
@@ -154,9 +154,9 @@ and
|
||||
or
|
||||
```
|
||||
|
||||
Each level is left-associative. Because `|` also delimits `if` and `for` captures, a bitwise-OR
|
||||
header expression must be parenthesized before a capture list, for example
|
||||
`if (flags | mask) |value| { ... }`.
|
||||
Each level is left-associative. Because `|` also delimits `if`, `while`, and `for` captures, a
|
||||
bitwise-OR header expression must be parenthesized before a capture list, for example
|
||||
`while (flags | mask) |value| { ... }`.
|
||||
|
||||
#### division
|
||||
|
||||
|
||||
@@ -202,13 +202,12 @@ Stmt :: struct {
|
||||
assignment_op: Assignment_Op,
|
||||
target: Expr_Id,
|
||||
expr: Expr_Id,
|
||||
// `If` statements use `expr` as the condition, `captures` as optional
|
||||
// unwrap binding names, `guard` as the optional post-unwrap boolean
|
||||
// condition, `body` as the then-block, and `else_body` as the else-block.
|
||||
// An `else if` chain is represented as an `else_body` holding a single
|
||||
// nested `If` statement.
|
||||
// `While` statements use `expr` as the condition, `body` as the loop body,
|
||||
// and `update` as the optional post-iteration statement.
|
||||
// `If` and `While` statements use `expr` as the condition, `captures` as
|
||||
// optional unwrap binding names, and `guard` as the optional post-unwrap
|
||||
// boolean condition. `If` uses `body` as the then-block and `else_body` as
|
||||
// the else-block; an `else if` chain is represented as an `else_body`
|
||||
// holding a single nested `If` statement. `While` uses `body` as the loop
|
||||
// body and `update` as the optional post-iteration statement.
|
||||
// `For` statements use `expr` as the iterable, `name` as the item capture,
|
||||
// `index_name` as the optional index capture, and `pointer_capture` to
|
||||
// distinguish `|@item|` from copy capture.
|
||||
|
||||
+186
-104
@@ -363,7 +363,7 @@ block_reads_name :: proc(checker: ^Checker, statements: []ast.Stmt_Id, name: sym
|
||||
append(&statement_stack, ..statement.body)
|
||||
append(&statement_stack, ..statement.else_body)
|
||||
case .While:
|
||||
append(&expr_stack, statement.expr)
|
||||
append(&expr_stack, statement.expr, statement.guard)
|
||||
append(&statement_stack, ..statement.body)
|
||||
if statement.update != ast.INVALID_STMT {
|
||||
append(&statement_stack, statement.update)
|
||||
@@ -3800,6 +3800,9 @@ mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, fi
|
||||
mark_block_imports_used(checker, statement.else_body, file)
|
||||
case .While:
|
||||
mark_expr_imports_used(checker, statement.expr, file)
|
||||
if statement.guard != ast.INVALID_EXPR {
|
||||
mark_expr_imports_used(checker, statement.guard, file)
|
||||
}
|
||||
mark_block_imports_used(checker, statement.body, file)
|
||||
if statement.update != ast.INVALID_STMT {
|
||||
update := [1]ast.Stmt_Id{statement.update}
|
||||
@@ -6309,12 +6312,40 @@ infer_statements :: proc(
|
||||
}
|
||||
}
|
||||
case .While:
|
||||
capture_start := len(locals^)
|
||||
if len(statement.captures) > 0 {
|
||||
operands: [dynamic]ast.Expr_Id
|
||||
operands.allocator = checker.allocator
|
||||
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &operands)
|
||||
operand_types := make([]types.Type, len(operands), checker.allocator)
|
||||
for operand, index in operands {
|
||||
operand_types[index] = infer_expr(checker, operand, locals^[:], pkg, file, demanded, local_types)
|
||||
}
|
||||
for capture, index in statement.captures {
|
||||
if capture == checker.sink_symbol {
|
||||
continue
|
||||
}
|
||||
capture_type := types.INVALID
|
||||
if index < len(operand_types) &&
|
||||
types.is_optional(operand_types[index], &checker.module.types) {
|
||||
capture_type = types.child_type(operand_types[index], &checker.module.types)
|
||||
}
|
||||
append(locals, Infer_Local{name=capture, type=capture_type, declared=capture_type, statement=ast.INVALID_STMT})
|
||||
}
|
||||
if statement.guard != ast.INVALID_EXPR {
|
||||
_ = infer_expr(checker, statement.guard, locals^[:], pkg, file, demanded, local_types)
|
||||
}
|
||||
delete(operand_types, checker.allocator)
|
||||
delete(operands)
|
||||
} else {
|
||||
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||
}
|
||||
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
if statement.update != ast.INVALID_STMT {
|
||||
update := [1]ast.Stmt_Id{statement.update}
|
||||
infer_statements(checker, update[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
}
|
||||
resize(locals, capture_start)
|
||||
case .For:
|
||||
if statement.expand {
|
||||
bindings, expand_error := expand_field_bindings(checker, statement.expr, statement.name, pkg, file)
|
||||
@@ -11042,6 +11073,122 @@ flatten_expand_iteration :: proc(
|
||||
return .Normal
|
||||
}
|
||||
|
||||
build_conditional_unwrap_header :: proc(
|
||||
ctx: ^Build_Ctx,
|
||||
statement: ast.Stmt,
|
||||
keyword: string,
|
||||
) -> (
|
||||
unwraps: []hir.Conditional_Unwrap,
|
||||
guard: hir.Expr_Id,
|
||||
capture_start: int,
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
valid: bool,
|
||||
) {
|
||||
checker := ctx.checker
|
||||
ast_operands: [dynamic]ast.Expr_Id
|
||||
ast_operands.allocator = checker.allocator
|
||||
defer delete(ast_operands)
|
||||
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &ast_operands)
|
||||
|
||||
valid = true
|
||||
diagnostic = source.INVALID_DIAGNOSTIC
|
||||
if len(ast_operands) != len(statement.captures) {
|
||||
diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
statement.span,
|
||||
"'%s' unwrap has %d operands but %d captures",
|
||||
keyword,
|
||||
len(ast_operands),
|
||||
len(statement.captures),
|
||||
)
|
||||
valid = false
|
||||
}
|
||||
|
||||
values := make([]hir.Expr_Id, len(ast_operands), checker.allocator)
|
||||
defer delete(values, checker.allocator)
|
||||
child_types := make([]types.Type, len(ast_operands), checker.allocator)
|
||||
defer delete(child_types, checker.allocator)
|
||||
for operand, index in ast_operands {
|
||||
child_types[index] = types.INVALID
|
||||
value := build_expr(
|
||||
checker, operand, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
types.INVALID, ctx.pkg, ctx.file,
|
||||
)
|
||||
values[index] = value
|
||||
value_type := checker.module.exprs[value].type
|
||||
if checker.module.exprs[value].kind == .Invalid {
|
||||
valid = false
|
||||
if diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
diagnostic = checker.module.exprs[value].diagnostic
|
||||
}
|
||||
} else if !types.is_optional(value_type, &checker.module.types) {
|
||||
diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
checker.ast_module.exprs[operand].span,
|
||||
"'%s' unwrap requires an optional value (operand %d)",
|
||||
keyword,
|
||||
index + 1,
|
||||
)
|
||||
valid = false
|
||||
} else {
|
||||
child_types[index] = types.child_type(value_type, &checker.module.types)
|
||||
}
|
||||
}
|
||||
|
||||
capture_start = len(ctx.locals^)
|
||||
unwrap_list: [dynamic]hir.Conditional_Unwrap
|
||||
unwrap_list.allocator = checker.allocator
|
||||
for capture, index in statement.captures {
|
||||
child := child_types[index] if index < len(child_types) else types.INVALID
|
||||
local := hir.INVALID_LOCAL
|
||||
if capture != checker.sink_symbol {
|
||||
if _, duplicate := find_build_local(ctx.locals^[capture_start:], capture); duplicate {
|
||||
diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
statement.span,
|
||||
"'%s' unwrap captures must have distinct names",
|
||||
keyword,
|
||||
)
|
||||
valid = false
|
||||
} else if id := add_shadow_diagnostic(
|
||||
checker, statement.span, capture, "capture",
|
||||
ctx.pkg, ctx.file, ctx.locals^[:capture_start], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
||||
); id != source.INVALID_DIAGNOSTIC {
|
||||
diagnostic = id
|
||||
valid = false
|
||||
}
|
||||
local = append_build_local(ctx, capture, child, false, statement.span)
|
||||
}
|
||||
if index < len(values) {
|
||||
append(&unwrap_list, hir.Conditional_Unwrap{expr=values[index], local=local})
|
||||
}
|
||||
}
|
||||
|
||||
guard = hir.INVALID_EXPR
|
||||
if statement.guard != ast.INVALID_EXPR {
|
||||
guard = build_expr(
|
||||
checker, statement.guard, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
types.BOOL, ctx.pkg, ctx.file,
|
||||
)
|
||||
if checker.module.exprs[guard].kind == .Invalid {
|
||||
valid = false
|
||||
if diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
diagnostic = checker.module.exprs[guard].diagnostic
|
||||
}
|
||||
} else if !types.is_bool(checker.module.exprs[guard].type) &&
|
||||
!types.is_noreturn(checker.module.exprs[guard].type) {
|
||||
diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
checker.ast_module.exprs[statement.guard].span,
|
||||
"'%s' unwrap guard must be a bool",
|
||||
keyword,
|
||||
)
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
return unwrap_list[:], guard, capture_start, diagnostic, valid
|
||||
}
|
||||
|
||||
build_block :: proc(
|
||||
ctx: ^Build_Ctx,
|
||||
statements: []ast.Stmt_Id,
|
||||
@@ -11763,101 +11910,8 @@ build_block :: proc(
|
||||
}
|
||||
}
|
||||
if len(statement.captures) > 0 {
|
||||
ast_operands: [dynamic]ast.Expr_Id
|
||||
ast_operands.allocator = checker.allocator
|
||||
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &ast_operands)
|
||||
|
||||
valid_unwrap := true
|
||||
diagnostic := source.INVALID_DIAGNOSTIC
|
||||
if len(ast_operands) != len(statement.captures) {
|
||||
diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
statement.span,
|
||||
"'if' unwrap has %d operands but %d captures",
|
||||
len(ast_operands),
|
||||
len(statement.captures),
|
||||
)
|
||||
valid_unwrap = false
|
||||
}
|
||||
|
||||
values := make([]hir.Expr_Id, len(ast_operands), checker.allocator)
|
||||
child_types := make([]types.Type, len(ast_operands), checker.allocator)
|
||||
for operand, index in ast_operands {
|
||||
child_types[index] = types.INVALID
|
||||
value := build_expr(
|
||||
checker, operand, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
types.INVALID, ctx.pkg, ctx.file,
|
||||
)
|
||||
values[index] = value
|
||||
value_type := checker.module.exprs[value].type
|
||||
if checker.module.exprs[value].kind == .Invalid {
|
||||
valid_unwrap = false
|
||||
if diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
diagnostic = checker.module.exprs[value].diagnostic
|
||||
}
|
||||
} else if !types.is_optional(value_type, &checker.module.types) {
|
||||
diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
checker.ast_module.exprs[operand].span,
|
||||
"'if' unwrap requires an optional value (operand %d)",
|
||||
index + 1,
|
||||
)
|
||||
valid_unwrap = false
|
||||
} else {
|
||||
child_types[index] = types.child_type(value_type, &checker.module.types)
|
||||
}
|
||||
}
|
||||
|
||||
capture_start := len(ctx.locals^)
|
||||
unwraps: [dynamic]hir.Conditional_Unwrap
|
||||
unwraps.allocator = checker.allocator
|
||||
for capture, index in statement.captures {
|
||||
child := child_types[index] if index < len(child_types) else types.INVALID
|
||||
local := hir.INVALID_LOCAL
|
||||
if capture != checker.sink_symbol {
|
||||
if _, duplicate := find_build_local(ctx.locals^[capture_start:], capture); duplicate {
|
||||
diagnostic = source.add(
|
||||
checker.diagnostics,
|
||||
statement.span,
|
||||
"'if' unwrap captures must have distinct names",
|
||||
)
|
||||
valid_unwrap = false
|
||||
} else if id := add_shadow_diagnostic(
|
||||
checker, statement.span, capture, "capture",
|
||||
ctx.pkg, ctx.file, ctx.locals^[:capture_start], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
||||
); id != source.INVALID_DIAGNOSTIC {
|
||||
diagnostic = id
|
||||
valid_unwrap = false
|
||||
}
|
||||
local = append_build_local(ctx, capture, child, false, statement.span)
|
||||
}
|
||||
if index < len(values) {
|
||||
append(&unwraps, hir.Conditional_Unwrap{expr=values[index], local=local})
|
||||
}
|
||||
}
|
||||
|
||||
guard := hir.INVALID_EXPR
|
||||
if statement.guard != ast.INVALID_EXPR {
|
||||
guard = build_expr(
|
||||
checker, statement.guard, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
types.BOOL, ctx.pkg, ctx.file,
|
||||
)
|
||||
if checker.module.exprs[guard].kind == .Invalid {
|
||||
valid_unwrap = false
|
||||
if diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
diagnostic = checker.module.exprs[guard].diagnostic
|
||||
}
|
||||
} else if !types.is_bool(checker.module.exprs[guard].type) &&
|
||||
!types.is_noreturn(checker.module.exprs[guard].type) {
|
||||
diagnostic = source.add(
|
||||
checker.diagnostics,
|
||||
checker.ast_module.exprs[statement.guard].span,
|
||||
"'if' unwrap guard must be a bool",
|
||||
)
|
||||
valid_unwrap = false
|
||||
}
|
||||
}
|
||||
|
||||
unwraps, guard, capture_start, diagnostic, valid_unwrap :=
|
||||
build_conditional_unwrap_header(ctx, statement, "if")
|
||||
then_body := build_block(ctx, statement.body, capture_start)
|
||||
resize(ctx.locals, capture_start)
|
||||
else_body: []hir.Stmt_Id = nil
|
||||
@@ -11870,7 +11924,7 @@ build_block :: proc(
|
||||
kind=.If,
|
||||
span=statement.span,
|
||||
expr=hir.INVALID_EXPR,
|
||||
unwraps=unwraps[:],
|
||||
unwraps=unwraps,
|
||||
guard=guard,
|
||||
then_body=then_body,
|
||||
else_body=else_body,
|
||||
@@ -11879,7 +11933,7 @@ build_block :: proc(
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
delete(unwraps)
|
||||
delete(unwraps, checker.allocator)
|
||||
delete(then_body, checker.allocator)
|
||||
delete(else_body, checker.allocator)
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
@@ -11893,9 +11947,6 @@ build_block :: proc(
|
||||
})
|
||||
ctx.problematic^ = true
|
||||
}
|
||||
delete(values, checker.allocator)
|
||||
delete(child_types, checker.allocator)
|
||||
delete(ast_operands)
|
||||
continue
|
||||
}
|
||||
condition := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
|
||||
@@ -11921,7 +11972,17 @@ build_block :: proc(
|
||||
})
|
||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
||||
case .While:
|
||||
condition := build_expr(
|
||||
condition := hir.INVALID_EXPR
|
||||
unwraps: []hir.Conditional_Unwrap = nil
|
||||
guard := hir.INVALID_EXPR
|
||||
capture_start := len(ctx.locals^)
|
||||
unwrap_diagnostic := source.INVALID_DIAGNOSTIC
|
||||
valid_unwrap := true
|
||||
if len(statement.captures) > 0 {
|
||||
unwraps, guard, capture_start, unwrap_diagnostic, valid_unwrap =
|
||||
build_conditional_unwrap_header(ctx, statement, "while")
|
||||
} else {
|
||||
condition = build_expr(
|
||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
types.BOOL, ctx.pkg, ctx.file,
|
||||
)
|
||||
@@ -11932,6 +11993,7 @@ build_block :: proc(
|
||||
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
||||
ctx.problematic^ = true
|
||||
}
|
||||
}
|
||||
if id := add_label_shadow_diagnostic(ctx, statement.span, statement.label);
|
||||
id != source.INVALID_DIAGNOSTIC {
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
@@ -11944,32 +12006,52 @@ build_block :: proc(
|
||||
append(ctx.loop_defer_starts, len(ctx.defers^))
|
||||
append(ctx.loop_labels, statement.label)
|
||||
append(ctx.loop_is_loop, true)
|
||||
loop_body := build_block(ctx, statement.body)
|
||||
loop_body := build_block(ctx, statement.body, capture_start)
|
||||
pop(ctx.loop_is_loop)
|
||||
pop(ctx.loop_labels)
|
||||
pop(ctx.loop_defer_starts)
|
||||
update := hir.INVALID_STMT
|
||||
if statement.update != ast.INVALID_STMT {
|
||||
update_ast := [1]ast.Stmt_Id{statement.update}
|
||||
update_body := build_block(ctx, update_ast[:])
|
||||
update_body := build_block(ctx, update_ast[:], capture_start)
|
||||
if len(update_body) > 0 {
|
||||
update = update_body[0]
|
||||
}
|
||||
delete(update_body, checker.allocator)
|
||||
}
|
||||
resize(ctx.locals, capture_start)
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
if valid_unwrap {
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind=.While,
|
||||
span=statement.span,
|
||||
label=statement.label,
|
||||
expr=condition,
|
||||
unwraps=unwraps,
|
||||
guard=guard,
|
||||
then_body=loop_body,
|
||||
update=update,
|
||||
local=hir.INVALID_LOCAL,
|
||||
target=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
delete(unwraps, checker.allocator)
|
||||
delete(loop_body, checker.allocator)
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind=.Trap,
|
||||
span=statement.span,
|
||||
expr=hir.INVALID_EXPR,
|
||||
guard=hir.INVALID_EXPR,
|
||||
local=hir.INVALID_LOCAL,
|
||||
target=hir.INVALID_EXPR,
|
||||
diagnostic=unwrap_diagnostic,
|
||||
})
|
||||
ctx.problematic^ = true
|
||||
}
|
||||
if condition != hir.INVALID_EXPR {
|
||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
||||
}
|
||||
case .For:
|
||||
if statement.expand {
|
||||
if statement.pointer_capture {
|
||||
|
||||
@@ -5258,6 +5258,70 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
|
||||
return ct_flow(.Normal), true
|
||||
}
|
||||
|
||||
ct_eval_conditional_unwrap :: proc(
|
||||
state: ^Ct_State,
|
||||
statement: ast.Stmt,
|
||||
keyword: string,
|
||||
depth: int,
|
||||
) -> (matched: bool, scope_start: int, flow: Ct_Flow, ok: bool) {
|
||||
checker := state.checker
|
||||
operands: [dynamic]ast.Expr_Id
|
||||
operands.allocator = checker.allocator
|
||||
defer delete(operands)
|
||||
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &operands)
|
||||
if len(operands) != len(statement.captures) {
|
||||
return false, len(state.bindings), ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"'%s' unwrap capture count mismatch",
|
||||
keyword,
|
||||
)
|
||||
}
|
||||
scope_start = len(state.bindings)
|
||||
matched = true
|
||||
for operand, index in operands {
|
||||
value, operand_flow, operand_ok := ct_eval_expr(state, operand, types.INVALID, depth+1)
|
||||
if !operand_ok || operand_flow.kind != .Normal {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return false, scope_start, operand_flow, operand_ok
|
||||
}
|
||||
v := state.values[value]
|
||||
if v.kind == .Null {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
if v.kind != .Optional_Some {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return false, scope_start, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"'%s' unwrap requires an optional value",
|
||||
keyword,
|
||||
)
|
||||
}
|
||||
children := ct_child_slice(state, v)
|
||||
if len(children) > 0 && statement.captures[index] != checker.sink_symbol {
|
||||
ct_bind_value(state, statement.captures[index], state.values[children[0]].type, children[0], false)
|
||||
}
|
||||
}
|
||||
if matched && statement.guard != ast.INVALID_EXPR {
|
||||
guard, guard_flow, guard_ok := ct_eval_expr(state, statement.guard, types.BOOL, depth+1)
|
||||
if !guard_ok || guard_flow.kind != .Normal {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return false, scope_start, guard_flow, guard_ok
|
||||
}
|
||||
guard_value, bool_ok := ct_bool_value(state, guard)
|
||||
if !bool_ok {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return false, scope_start, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"'%s' unwrap guard must be a bool",
|
||||
keyword,
|
||||
)
|
||||
}
|
||||
matched = guard_value
|
||||
}
|
||||
return matched, scope_start, ct_flow(.Normal), true
|
||||
}
|
||||
|
||||
ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) {
|
||||
checker := state.checker
|
||||
if yield_returns && statement.else_body == nil {
|
||||
@@ -5284,47 +5348,9 @@ ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, d
|
||||
}
|
||||
return ct_exec_statements(state, body, false, depth+1)
|
||||
}
|
||||
operands: [dynamic]ast.Expr_Id
|
||||
operands.allocator = checker.allocator
|
||||
defer delete(operands)
|
||||
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &operands)
|
||||
if len(operands) != len(statement.captures) {
|
||||
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' unwrap capture count mismatch")
|
||||
}
|
||||
scope_start := len(state.bindings)
|
||||
matched := true
|
||||
for operand, index in operands {
|
||||
value, flow, ok := ct_eval_expr(state, operand, types.INVALID, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return flow, ok
|
||||
}
|
||||
v := state.values[value]
|
||||
if v.kind == .Null {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
if v.kind != .Optional_Some {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' unwrap requires an optional value")
|
||||
}
|
||||
children := ct_child_slice(state, v)
|
||||
if len(children) > 0 && statement.captures[index] != checker.sink_symbol {
|
||||
ct_bind_value(state, statement.captures[index], state.values[children[0]].type, children[0], false)
|
||||
}
|
||||
}
|
||||
if matched && statement.guard != ast.INVALID_EXPR {
|
||||
guard, flow, ok := ct_eval_expr(state, statement.guard, types.BOOL, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return flow, ok
|
||||
}
|
||||
guard_value, guard_ok := ct_bool_value(state, guard)
|
||||
if !guard_ok {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' unwrap guard must be a bool")
|
||||
}
|
||||
matched = guard_value
|
||||
matched, scope_start, unwrap_flow, unwrap_ok := ct_eval_conditional_unwrap(state, statement, "if", depth)
|
||||
if !unwrap_ok || unwrap_flow.kind != .Normal {
|
||||
return unwrap_flow, unwrap_ok
|
||||
}
|
||||
body := statement.body if matched else statement.else_body
|
||||
flow := ct_flow(.Normal)
|
||||
@@ -5357,6 +5383,19 @@ ct_exec_while :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool
|
||||
if !ct_step(state, statement.span) {
|
||||
return ct_flow(.Normal), false
|
||||
}
|
||||
scope_start := len(state.bindings)
|
||||
if len(statement.captures) > 0 {
|
||||
matched, unwrap_scope_start, flow, ok :=
|
||||
ct_eval_conditional_unwrap(state, statement, "while", depth)
|
||||
scope_start = unwrap_scope_start
|
||||
if !ok || flow.kind != .Normal {
|
||||
return flow, ok
|
||||
}
|
||||
if !matched {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return ct_flow(.Normal), true
|
||||
}
|
||||
} else {
|
||||
condition, flow, ok := ct_eval_expr(state, statement.expr, types.BOOL, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return flow, ok
|
||||
@@ -5368,23 +5407,29 @@ ct_exec_while :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool
|
||||
if !value {
|
||||
return ct_flow(.Normal), true
|
||||
}
|
||||
}
|
||||
body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
||||
if !body_ok {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return body_flow, false
|
||||
}
|
||||
if ct_loop_consumes_flow(body_flow, statement.label, false) {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return ct_flow(.Normal), true
|
||||
}
|
||||
if body_flow.kind != .Normal && !ct_loop_consumes_flow(body_flow, statement.label, true) {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return body_flow, true
|
||||
}
|
||||
if statement.update != ast.INVALID_STMT {
|
||||
update := [1]ast.Stmt_Id{statement.update}
|
||||
update_flow, update_ok := ct_exec_statements(state, update[:], false, depth+1)
|
||||
if !update_ok || update_flow.kind != .Normal {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return update_flow, update_ok
|
||||
}
|
||||
}
|
||||
ct_pop_bindings(state, scope_start)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -229,12 +229,12 @@ Stmt :: struct {
|
||||
// by computing the target address once, loading its current value, applying
|
||||
// the operation to `expr`, and storing through the original address.
|
||||
assignment_op: Assignment_Op,
|
||||
// Boolean `If` statements use `expr` as the condition. Conditional unwraps
|
||||
// use `unwraps` for the ordered optional expressions and capture locals, and
|
||||
// `guard` for the optional boolean checked after every unwrap succeeds.
|
||||
// Both forms use `then_body`/`else_body` as the branch statement lists.
|
||||
// `While` statements use `expr` as the condition, `then_body` as the loop
|
||||
// body, and `update` as the optional post-iteration statement.
|
||||
// Boolean `If` and `While` statements use `expr` as the condition.
|
||||
// Conditional unwraps use `unwraps` for the ordered optional expressions
|
||||
// and capture locals, and `guard` for the optional boolean checked after
|
||||
// every unwrap succeeds. `If` uses `then_body`/`else_body` as its branches.
|
||||
// `While` uses `then_body` as its loop body and `update` as the optional
|
||||
// post-iteration statement.
|
||||
// `For` statements use `expr` as the iterable, `local` as the item capture,
|
||||
// `index_local` as the optional sequence index, and `iterator_type` as the
|
||||
// normalized many-item pointer type for sequence iteration.
|
||||
|
||||
+100
-91
@@ -924,6 +924,101 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
return last
|
||||
}
|
||||
|
||||
lower_conditional_unwrap_header :: proc(
|
||||
state: ^State,
|
||||
statement: hir.Stmt,
|
||||
success_lbl, false_lbl: i64,
|
||||
) {
|
||||
for unwrap in statement.unwraps {
|
||||
optional := lower_expr(state, unwrap.expr)
|
||||
present := append_instruction(state, ir.Instruction{
|
||||
op=.Optional_Is_Some,
|
||||
span=statement.span,
|
||||
type=types.BOOL,
|
||||
target=ir.INVALID_REF,
|
||||
a=optional,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
next_lbl := fresh_label(state)
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Cond_Br,
|
||||
span=statement.span,
|
||||
type=types.VOID,
|
||||
a=present,
|
||||
integer=next_lbl,
|
||||
target=ir.Ref(u32(false_lbl)),
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Label,
|
||||
span=statement.span,
|
||||
type=types.VOID,
|
||||
integer=next_lbl,
|
||||
target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
if unwrap.local != hir.INVALID_LOCAL && int(unwrap.local) < len(state.func_locals) {
|
||||
local := state.func_locals[unwrap.local]
|
||||
slot := append_instruction(state, ir.Instruction{
|
||||
op=.Alloca,
|
||||
span=statement.span,
|
||||
type=local.type,
|
||||
target=ir.local_ref(ir.Local_Id(unwrap.local)),
|
||||
a=ir.INVALID_INSTRUCTION,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
state.local_slots[unwrap.local] = slot
|
||||
inner := append_instruction(state, ir.Instruction{
|
||||
op=.Optional_Value,
|
||||
span=statement.span,
|
||||
type=local.type,
|
||||
target=ir.INVALID_REF,
|
||||
a=optional,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Store,
|
||||
span=statement.span,
|
||||
type=local.type,
|
||||
target=ir.INVALID_REF,
|
||||
a=slot,
|
||||
b=inner,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
}
|
||||
if statement.guard != hir.INVALID_EXPR {
|
||||
guard := lower_expr(state, statement.guard)
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Cond_Br,
|
||||
span=statement.span,
|
||||
type=types.VOID,
|
||||
a=guard,
|
||||
integer=success_lbl,
|
||||
target=ir.Ref(u32(false_lbl)),
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Br,
|
||||
span=statement.span,
|
||||
type=types.VOID,
|
||||
integer=success_lbl,
|
||||
target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||
hir_module := state.hir_module
|
||||
for statement_id in statements {
|
||||
@@ -1136,97 +1231,7 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||
false_target := else_lbl if has_else else merge_lbl
|
||||
|
||||
if len(statement.unwraps) > 0 {
|
||||
// Evaluate each optional exactly once, entering the next operand only
|
||||
// after the previous one is present. Capture storage is initialized in
|
||||
// these success blocks so the optional guard can use every binding.
|
||||
for unwrap in statement.unwraps {
|
||||
optional := lower_expr(state, unwrap.expr)
|
||||
present := append_instruction(state, ir.Instruction{
|
||||
op=.Optional_Is_Some,
|
||||
span=statement.span,
|
||||
type=types.BOOL,
|
||||
target=ir.INVALID_REF,
|
||||
a=optional,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
success_lbl := fresh_label(state)
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Cond_Br,
|
||||
span=statement.span,
|
||||
type=types.VOID,
|
||||
a=present,
|
||||
integer=success_lbl,
|
||||
target=ir.Ref(u32(false_target)),
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Label,
|
||||
span=statement.span,
|
||||
type=types.VOID,
|
||||
integer=success_lbl,
|
||||
target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
if unwrap.local != hir.INVALID_LOCAL && int(unwrap.local) < len(state.func_locals) {
|
||||
local := state.func_locals[unwrap.local]
|
||||
slot := append_instruction(state, ir.Instruction{
|
||||
op=.Alloca,
|
||||
span=statement.span,
|
||||
type=local.type,
|
||||
target=ir.local_ref(ir.Local_Id(unwrap.local)),
|
||||
a=ir.INVALID_INSTRUCTION,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
state.local_slots[unwrap.local] = slot
|
||||
inner := append_instruction(state, ir.Instruction{
|
||||
op=.Optional_Value,
|
||||
span=statement.span,
|
||||
type=local.type,
|
||||
target=ir.INVALID_REF,
|
||||
a=optional,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Store,
|
||||
span=statement.span,
|
||||
type=local.type,
|
||||
target=ir.INVALID_REF,
|
||||
a=slot,
|
||||
b=inner,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
}
|
||||
if statement.guard != hir.INVALID_EXPR {
|
||||
guard := lower_expr(state, statement.guard)
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Cond_Br,
|
||||
span=statement.span,
|
||||
type=types.VOID,
|
||||
a=guard,
|
||||
integer=then_lbl,
|
||||
target=ir.Ref(u32(false_target)),
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Br,
|
||||
span=statement.span,
|
||||
type=types.VOID,
|
||||
integer=then_lbl,
|
||||
target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
lower_conditional_unwrap_header(state, statement, then_lbl, false_target)
|
||||
} else {
|
||||
cond := lower_expr(state, statement.expr)
|
||||
append_instruction(state, ir.Instruction{
|
||||
@@ -1282,12 +1287,16 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
if len(statement.unwraps) > 0 {
|
||||
lower_conditional_unwrap_header(state, statement, body_lbl, exit_lbl)
|
||||
} else {
|
||||
condition := lower_expr(state, statement.expr)
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Cond_Br, span=statement.span, type=types.VOID,
|
||||
a=condition, integer=body_lbl, target=ir.Ref(u32(exit_lbl)),
|
||||
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Label, span=statement.span, type=types.VOID, integer=body_lbl,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
|
||||
+26
-15
@@ -2229,19 +2229,7 @@ parse_control_body :: proc(parser: ^Parser, header: ast.Expr_Id, diagnostic: str
|
||||
return single
|
||||
}
|
||||
|
||||
parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
start := advance(parser) // consume 'if'
|
||||
skip_newlines(parser)
|
||||
saved := parser.no_struct_literal
|
||||
parser.no_struct_literal = true
|
||||
saved_capture_pipe := parser.capture_pipe
|
||||
saved_if_condition := parser.if_condition
|
||||
parser.capture_pipe = true
|
||||
parser.if_condition = true
|
||||
condition := parse_expression(parser)
|
||||
parser.if_condition = saved_if_condition
|
||||
parser.capture_pipe = saved_capture_pipe
|
||||
parser.no_struct_literal = saved
|
||||
parse_conditional_captures :: proc(parser: ^Parser) -> ([]symbol.Id, ast.Expr_Id) {
|
||||
captures: [dynamic]symbol.Id
|
||||
captures.allocator = parser.module.allocator
|
||||
guard := ast.INVALID_EXPR
|
||||
@@ -2267,9 +2255,9 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
if current(parser).kind == .Pipe {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected a guard expression after ':'")
|
||||
} else {
|
||||
saved = parser.no_struct_literal
|
||||
saved := parser.no_struct_literal
|
||||
parser.no_struct_literal = true
|
||||
saved_capture_pipe = parser.capture_pipe
|
||||
saved_capture_pipe := parser.capture_pipe
|
||||
parser.capture_pipe = true
|
||||
guard = parse_expression(parser)
|
||||
parser.capture_pipe = saved_capture_pipe
|
||||
@@ -2280,6 +2268,23 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '|' to close unwrap captures")
|
||||
}
|
||||
}
|
||||
return captures[:], guard
|
||||
}
|
||||
|
||||
parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
start := advance(parser) // consume 'if'
|
||||
skip_newlines(parser)
|
||||
saved := parser.no_struct_literal
|
||||
parser.no_struct_literal = true
|
||||
saved_capture_pipe := parser.capture_pipe
|
||||
saved_if_condition := parser.if_condition
|
||||
parser.capture_pipe = true
|
||||
parser.if_condition = true
|
||||
condition := parse_expression(parser)
|
||||
parser.if_condition = saved_if_condition
|
||||
parser.capture_pipe = saved_capture_pipe
|
||||
parser.no_struct_literal = saved
|
||||
captures, guard := parse_conditional_captures(parser)
|
||||
then_body := parse_control_body(
|
||||
parser,
|
||||
condition,
|
||||
@@ -2608,8 +2613,12 @@ parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
skip_newlines(parser)
|
||||
saved := parser.no_struct_literal
|
||||
parser.no_struct_literal = true
|
||||
saved_capture_pipe := parser.capture_pipe
|
||||
parser.capture_pipe = true
|
||||
condition := parse_expression(parser)
|
||||
parser.capture_pipe = saved_capture_pipe
|
||||
parser.no_struct_literal = saved
|
||||
captures, guard := parse_conditional_captures(parser)
|
||||
skip_newlines(parser)
|
||||
|
||||
update := ast.INVALID_STMT
|
||||
@@ -2631,6 +2640,8 @@ parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
span=span_from(start.span, previous(parser).span),
|
||||
expr=condition,
|
||||
body=body,
|
||||
captures=captures,
|
||||
guard=guard,
|
||||
label=label,
|
||||
update=update,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
|
||||
@@ -11514,6 +11514,39 @@ braceless_while_compiles_and_runs :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, state.exit_code, 42)
|
||||
}
|
||||
|
||||
@(test)
|
||||
while_unwrap_compiles_and_runs_at_runtime_and_comptime :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-while-unwrap"
|
||||
main_path := "/tmp/brolang-test-while-unwrap/main.bro"
|
||||
output := "/tmp/brolang-test-while-unwrap-output"
|
||||
text := `next func(index @mut usize, limit usize) ?i32 {
|
||||
if (index^ == limit) return null
|
||||
index^ += 1
|
||||
return i32(index^)
|
||||
}
|
||||
sum func(limit, cap usize) i32 {
|
||||
index usize := 0
|
||||
total i32 := 0
|
||||
while next(&index, limit) |value : usize(value) <= cap| : total += value {}
|
||||
return total
|
||||
}
|
||||
COMPTIME_SUM :: $sum(4, 4)
|
||||
COMPTIME_GUARDED :: $sum(5, 3)
|
||||
main func() i32 {
|
||||
if COMPTIME_SUM != 10 or COMPTIME_GUARDED != 6 { return 1 }
|
||||
return sum(4, 4) + sum(5, 3) - 16
|
||||
}
|
||||
`
|
||||
_ = os2.remove_all(directory)
|
||||
defer _ = os2.remove_all(directory)
|
||||
defer _ = os.remove(output)
|
||||
testing.expect(t, os.make_directory(directory) == nil)
|
||||
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
parser_diagnoses_braceless_for_and_unwrap_without_parens_or_call :: proc(t: ^testing.T) {
|
||||
text := `main func() void {
|
||||
|
||||
Reference in New Issue
Block a user