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
|
- 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
|
- 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
|
- 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
|
- 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)`
|
- 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
|
- 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
|
- 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
|
- 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
|
- 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
|
- `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
|
- `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
|
- bare block scopes, `defer`, and fallible-function `errdefer` with optional error capture; cleanup is block-scoped and LIFO
|
||||||
@@ -154,9 +154,9 @@ and
|
|||||||
or
|
or
|
||||||
```
|
```
|
||||||
|
|
||||||
Each level is left-associative. Because `|` also delimits `if` and `for` captures, a bitwise-OR
|
Each level is left-associative. Because `|` also delimits `if`, `while`, and `for` captures, a
|
||||||
header expression must be parenthesized before a capture list, for example
|
bitwise-OR header expression must be parenthesized before a capture list, for example
|
||||||
`if (flags | mask) |value| { ... }`.
|
`while (flags | mask) |value| { ... }`.
|
||||||
|
|
||||||
#### division
|
#### division
|
||||||
|
|
||||||
|
|||||||
@@ -202,13 +202,12 @@ Stmt :: struct {
|
|||||||
assignment_op: Assignment_Op,
|
assignment_op: Assignment_Op,
|
||||||
target: Expr_Id,
|
target: Expr_Id,
|
||||||
expr: Expr_Id,
|
expr: Expr_Id,
|
||||||
// `If` statements use `expr` as the condition, `captures` as optional
|
// `If` and `While` statements use `expr` as the condition, `captures` as
|
||||||
// unwrap binding names, `guard` as the optional post-unwrap boolean
|
// optional unwrap binding names, and `guard` as the optional post-unwrap
|
||||||
// condition, `body` as the then-block, and `else_body` as the else-block.
|
// boolean condition. `If` uses `body` as the then-block and `else_body` as
|
||||||
// An `else if` chain is represented as an `else_body` holding a single
|
// the else-block; an `else if` chain is represented as an `else_body`
|
||||||
// nested `If` statement.
|
// holding a single nested `If` statement. `While` uses `body` as the loop
|
||||||
// `While` statements use `expr` as the condition, `body` as the loop body,
|
// body and `update` as the optional post-iteration statement.
|
||||||
// and `update` as the optional post-iteration statement.
|
|
||||||
// `For` statements use `expr` as the iterable, `name` as the item capture,
|
// `For` statements use `expr` as the iterable, `name` as the item capture,
|
||||||
// `index_name` as the optional index capture, and `pointer_capture` to
|
// `index_name` as the optional index capture, and `pointer_capture` to
|
||||||
// distinguish `|@item|` from copy capture.
|
// distinguish `|@item|` from copy capture.
|
||||||
|
|||||||
+208
-126
@@ -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.body)
|
||||||
append(&statement_stack, ..statement.else_body)
|
append(&statement_stack, ..statement.else_body)
|
||||||
case .While:
|
case .While:
|
||||||
append(&expr_stack, statement.expr)
|
append(&expr_stack, statement.expr, statement.guard)
|
||||||
append(&statement_stack, ..statement.body)
|
append(&statement_stack, ..statement.body)
|
||||||
if statement.update != ast.INVALID_STMT {
|
if statement.update != ast.INVALID_STMT {
|
||||||
append(&statement_stack, statement.update)
|
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)
|
mark_block_imports_used(checker, statement.else_body, file)
|
||||||
case .While:
|
case .While:
|
||||||
mark_expr_imports_used(checker, statement.expr, file)
|
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)
|
mark_block_imports_used(checker, statement.body, file)
|
||||||
if statement.update != ast.INVALID_STMT {
|
if statement.update != ast.INVALID_STMT {
|
||||||
update := [1]ast.Stmt_Id{statement.update}
|
update := [1]ast.Stmt_Id{statement.update}
|
||||||
@@ -6309,12 +6312,40 @@ infer_statements :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .While:
|
case .While:
|
||||||
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
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)
|
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||||
if statement.update != ast.INVALID_STMT {
|
if statement.update != ast.INVALID_STMT {
|
||||||
update := [1]ast.Stmt_Id{statement.update}
|
update := [1]ast.Stmt_Id{statement.update}
|
||||||
infer_statements(checker, update[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
infer_statements(checker, update[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
||||||
}
|
}
|
||||||
|
resize(locals, capture_start)
|
||||||
case .For:
|
case .For:
|
||||||
if statement.expand {
|
if statement.expand {
|
||||||
bindings, expand_error := expand_field_bindings(checker, statement.expr, statement.name, pkg, file)
|
bindings, expand_error := expand_field_bindings(checker, statement.expr, statement.name, pkg, file)
|
||||||
@@ -11042,6 +11073,122 @@ flatten_expand_iteration :: proc(
|
|||||||
return .Normal
|
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(
|
build_block :: proc(
|
||||||
ctx: ^Build_Ctx,
|
ctx: ^Build_Ctx,
|
||||||
statements: []ast.Stmt_Id,
|
statements: []ast.Stmt_Id,
|
||||||
@@ -11763,101 +11910,8 @@ build_block :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(statement.captures) > 0 {
|
if len(statement.captures) > 0 {
|
||||||
ast_operands: [dynamic]ast.Expr_Id
|
unwraps, guard, capture_start, diagnostic, valid_unwrap :=
|
||||||
ast_operands.allocator = checker.allocator
|
build_conditional_unwrap_header(ctx, statement, "if")
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
then_body := build_block(ctx, statement.body, capture_start)
|
then_body := build_block(ctx, statement.body, capture_start)
|
||||||
resize(ctx.locals, capture_start)
|
resize(ctx.locals, capture_start)
|
||||||
else_body: []hir.Stmt_Id = nil
|
else_body: []hir.Stmt_Id = nil
|
||||||
@@ -11870,7 +11924,7 @@ build_block :: proc(
|
|||||||
kind=.If,
|
kind=.If,
|
||||||
span=statement.span,
|
span=statement.span,
|
||||||
expr=hir.INVALID_EXPR,
|
expr=hir.INVALID_EXPR,
|
||||||
unwraps=unwraps[:],
|
unwraps=unwraps,
|
||||||
guard=guard,
|
guard=guard,
|
||||||
then_body=then_body,
|
then_body=then_body,
|
||||||
else_body=else_body,
|
else_body=else_body,
|
||||||
@@ -11879,7 +11933,7 @@ build_block :: proc(
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
delete(unwraps)
|
delete(unwraps, checker.allocator)
|
||||||
delete(then_body, checker.allocator)
|
delete(then_body, checker.allocator)
|
||||||
delete(else_body, checker.allocator)
|
delete(else_body, checker.allocator)
|
||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
@@ -11893,9 +11947,6 @@ build_block :: proc(
|
|||||||
})
|
})
|
||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
}
|
}
|
||||||
delete(values, checker.allocator)
|
|
||||||
delete(child_types, checker.allocator)
|
|
||||||
delete(ast_operands)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
condition := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
|
condition := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
|
||||||
@@ -11921,16 +11972,27 @@ build_block :: proc(
|
|||||||
})
|
})
|
||||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
||||||
case .While:
|
case .While:
|
||||||
condition := build_expr(
|
condition := hir.INVALID_EXPR
|
||||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
unwraps: []hir.Conditional_Unwrap = nil
|
||||||
types.BOOL, ctx.pkg, ctx.file,
|
guard := hir.INVALID_EXPR
|
||||||
)
|
capture_start := len(ctx.locals^)
|
||||||
if checker.module.exprs[condition].kind != .Invalid &&
|
unwrap_diagnostic := source.INVALID_DIAGNOSTIC
|
||||||
!types.is_bool(checker.module.exprs[condition].type) &&
|
valid_unwrap := true
|
||||||
!types.is_noreturn(checker.module.exprs[condition].type) {
|
if len(statement.captures) > 0 {
|
||||||
id := source.add(checker.diagnostics, statement.span, "'while' condition must be a bool")
|
unwraps, guard, capture_start, unwrap_diagnostic, valid_unwrap =
|
||||||
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
build_conditional_unwrap_header(ctx, statement, "while")
|
||||||
ctx.problematic^ = true
|
} else {
|
||||||
|
condition = build_expr(
|
||||||
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
|
types.BOOL, ctx.pkg, ctx.file,
|
||||||
|
)
|
||||||
|
if checker.module.exprs[condition].kind != .Invalid &&
|
||||||
|
!types.is_bool(checker.module.exprs[condition].type) &&
|
||||||
|
!types.is_noreturn(checker.module.exprs[condition].type) {
|
||||||
|
id := source.add(checker.diagnostics, statement.span, "'while' condition must be a bool")
|
||||||
|
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
||||||
|
ctx.problematic^ = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if id := add_label_shadow_diagnostic(ctx, statement.span, statement.label);
|
if id := add_label_shadow_diagnostic(ctx, statement.span, statement.label);
|
||||||
id != source.INVALID_DIAGNOSTIC {
|
id != source.INVALID_DIAGNOSTIC {
|
||||||
@@ -11944,32 +12006,52 @@ build_block :: proc(
|
|||||||
append(ctx.loop_defer_starts, len(ctx.defers^))
|
append(ctx.loop_defer_starts, len(ctx.defers^))
|
||||||
append(ctx.loop_labels, statement.label)
|
append(ctx.loop_labels, statement.label)
|
||||||
append(ctx.loop_is_loop, true)
|
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_is_loop)
|
||||||
pop(ctx.loop_labels)
|
pop(ctx.loop_labels)
|
||||||
pop(ctx.loop_defer_starts)
|
pop(ctx.loop_defer_starts)
|
||||||
update := hir.INVALID_STMT
|
update := hir.INVALID_STMT
|
||||||
if statement.update != ast.INVALID_STMT {
|
if statement.update != ast.INVALID_STMT {
|
||||||
update_ast := [1]ast.Stmt_Id{statement.update}
|
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 {
|
if len(update_body) > 0 {
|
||||||
update = update_body[0]
|
update = update_body[0]
|
||||||
}
|
}
|
||||||
delete(update_body, checker.allocator)
|
delete(update_body, checker.allocator)
|
||||||
}
|
}
|
||||||
|
resize(ctx.locals, capture_start)
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
append(&checker.module.statements, hir.Stmt{
|
if valid_unwrap {
|
||||||
kind=.While,
|
append(&checker.module.statements, hir.Stmt{
|
||||||
span=statement.span,
|
kind=.While,
|
||||||
label=statement.label,
|
span=statement.span,
|
||||||
expr=condition,
|
label=statement.label,
|
||||||
then_body=loop_body,
|
expr=condition,
|
||||||
update=update,
|
unwraps=unwraps,
|
||||||
local=hir.INVALID_LOCAL,
|
guard=guard,
|
||||||
target=hir.INVALID_EXPR,
|
then_body=loop_body,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
update=update,
|
||||||
})
|
local=hir.INVALID_LOCAL,
|
||||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
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:
|
case .For:
|
||||||
if statement.expand {
|
if statement.expand {
|
||||||
if statement.pointer_capture {
|
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
|
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) {
|
ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) {
|
||||||
checker := state.checker
|
checker := state.checker
|
||||||
if yield_returns && statement.else_body == nil {
|
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)
|
return ct_exec_statements(state, body, false, depth+1)
|
||||||
}
|
}
|
||||||
operands: [dynamic]ast.Expr_Id
|
matched, scope_start, unwrap_flow, unwrap_ok := ct_eval_conditional_unwrap(state, statement, "if", depth)
|
||||||
operands.allocator = checker.allocator
|
if !unwrap_ok || unwrap_flow.kind != .Normal {
|
||||||
defer delete(operands)
|
return unwrap_flow, unwrap_ok
|
||||||
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
|
|
||||||
}
|
}
|
||||||
body := statement.body if matched else statement.else_body
|
body := statement.body if matched else statement.else_body
|
||||||
flow := ct_flow(.Normal)
|
flow := ct_flow(.Normal)
|
||||||
@@ -5357,34 +5383,53 @@ ct_exec_while :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool
|
|||||||
if !ct_step(state, statement.span) {
|
if !ct_step(state, statement.span) {
|
||||||
return ct_flow(.Normal), false
|
return ct_flow(.Normal), false
|
||||||
}
|
}
|
||||||
condition, flow, ok := ct_eval_expr(state, statement.expr, types.BOOL, depth+1)
|
scope_start := len(state.bindings)
|
||||||
if !ok || flow.kind != .Normal {
|
if len(statement.captures) > 0 {
|
||||||
return flow, ok
|
matched, unwrap_scope_start, flow, ok :=
|
||||||
}
|
ct_eval_conditional_unwrap(state, statement, "while", depth)
|
||||||
value, bool_ok := ct_bool_value(state, condition)
|
scope_start = unwrap_scope_start
|
||||||
if !bool_ok {
|
if !ok || flow.kind != .Normal {
|
||||||
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'while' condition must be a bool")
|
return flow, ok
|
||||||
}
|
}
|
||||||
if !value {
|
if !matched {
|
||||||
return ct_flow(.Normal), true
|
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
|
||||||
|
}
|
||||||
|
value, bool_ok := ct_bool_value(state, condition)
|
||||||
|
if !bool_ok {
|
||||||
|
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'while' condition must be a bool")
|
||||||
|
}
|
||||||
|
if !value {
|
||||||
|
return ct_flow(.Normal), true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
||||||
if !body_ok {
|
if !body_ok {
|
||||||
|
ct_pop_bindings(state, scope_start)
|
||||||
return body_flow, false
|
return body_flow, false
|
||||||
}
|
}
|
||||||
if ct_loop_consumes_flow(body_flow, statement.label, false) {
|
if ct_loop_consumes_flow(body_flow, statement.label, false) {
|
||||||
|
ct_pop_bindings(state, scope_start)
|
||||||
return ct_flow(.Normal), true
|
return ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
if body_flow.kind != .Normal && !ct_loop_consumes_flow(body_flow, statement.label, 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
|
return body_flow, true
|
||||||
}
|
}
|
||||||
if statement.update != ast.INVALID_STMT {
|
if statement.update != ast.INVALID_STMT {
|
||||||
update := [1]ast.Stmt_Id{statement.update}
|
update := [1]ast.Stmt_Id{statement.update}
|
||||||
update_flow, update_ok := ct_exec_statements(state, update[:], false, depth+1)
|
update_flow, update_ok := ct_exec_statements(state, update[:], false, depth+1)
|
||||||
if !update_ok || update_flow.kind != .Normal {
|
if !update_ok || update_flow.kind != .Normal {
|
||||||
|
ct_pop_bindings(state, scope_start)
|
||||||
return update_flow, update_ok
|
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
|
// by computing the target address once, loading its current value, applying
|
||||||
// the operation to `expr`, and storing through the original address.
|
// the operation to `expr`, and storing through the original address.
|
||||||
assignment_op: Assignment_Op,
|
assignment_op: Assignment_Op,
|
||||||
// Boolean `If` statements use `expr` as the condition. Conditional unwraps
|
// Boolean `If` and `While` statements use `expr` as the condition.
|
||||||
// use `unwraps` for the ordered optional expressions and capture locals, and
|
// Conditional unwraps use `unwraps` for the ordered optional expressions
|
||||||
// `guard` for the optional boolean checked after every unwrap succeeds.
|
// and capture locals, and `guard` for the optional boolean checked after
|
||||||
// Both forms use `then_body`/`else_body` as the branch statement lists.
|
// every unwrap succeeds. `If` uses `then_body`/`else_body` as its branches.
|
||||||
// `While` statements use `expr` as the condition, `then_body` as the loop
|
// `While` uses `then_body` as its loop body and `update` as the optional
|
||||||
// body, and `update` as the optional post-iteration statement.
|
// post-iteration statement.
|
||||||
// `For` statements use `expr` as the iterable, `local` as the item capture,
|
// `For` statements use `expr` as the iterable, `local` as the item capture,
|
||||||
// `index_local` as the optional sequence index, and `iterator_type` as the
|
// `index_local` as the optional sequence index, and `iterator_type` as the
|
||||||
// normalized many-item pointer type for sequence iteration.
|
// normalized many-item pointer type for sequence iteration.
|
||||||
|
|||||||
+106
-97
@@ -924,6 +924,101 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
return last
|
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) {
|
lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||||
hir_module := state.hir_module
|
hir_module := state.hir_module
|
||||||
for statement_id in statements {
|
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
|
false_target := else_lbl if has_else else merge_lbl
|
||||||
|
|
||||||
if len(statement.unwraps) > 0 {
|
if len(statement.unwraps) > 0 {
|
||||||
// Evaluate each optional exactly once, entering the next operand only
|
lower_conditional_unwrap_header(state, statement, then_lbl, false_target)
|
||||||
// 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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
cond := lower_expr(state, statement.expr)
|
cond := lower_expr(state, statement.expr)
|
||||||
append_instruction(state, ir.Instruction{
|
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,
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
condition := lower_expr(state, statement.expr)
|
if len(statement.unwraps) > 0 {
|
||||||
append_instruction(state, ir.Instruction{
|
lower_conditional_unwrap_header(state, statement, body_lbl, exit_lbl)
|
||||||
op=.Cond_Br, span=statement.span, type=types.VOID,
|
} else {
|
||||||
a=condition, integer=body_lbl, target=ir.Ref(u32(exit_lbl)),
|
condition := lower_expr(state, statement.expr)
|
||||||
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
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{
|
append_instruction(state, ir.Instruction{
|
||||||
op=.Label, span=statement.span, type=types.VOID, integer=body_lbl,
|
op=.Label, span=statement.span, type=types.VOID, integer=body_lbl,
|
||||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
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
|
return single
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
parse_conditional_captures :: proc(parser: ^Parser) -> ([]symbol.Id, ast.Expr_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: [dynamic]symbol.Id
|
captures: [dynamic]symbol.Id
|
||||||
captures.allocator = parser.module.allocator
|
captures.allocator = parser.module.allocator
|
||||||
guard := ast.INVALID_EXPR
|
guard := ast.INVALID_EXPR
|
||||||
@@ -2267,9 +2255,9 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
if current(parser).kind == .Pipe {
|
if current(parser).kind == .Pipe {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected a guard expression after ':'")
|
source.add(parser.diagnostics, current(parser).span, "expected a guard expression after ':'")
|
||||||
} else {
|
} else {
|
||||||
saved = parser.no_struct_literal
|
saved := parser.no_struct_literal
|
||||||
parser.no_struct_literal = true
|
parser.no_struct_literal = true
|
||||||
saved_capture_pipe = parser.capture_pipe
|
saved_capture_pipe := parser.capture_pipe
|
||||||
parser.capture_pipe = true
|
parser.capture_pipe = true
|
||||||
guard = parse_expression(parser)
|
guard = parse_expression(parser)
|
||||||
parser.capture_pipe = saved_capture_pipe
|
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")
|
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(
|
then_body := parse_control_body(
|
||||||
parser,
|
parser,
|
||||||
condition,
|
condition,
|
||||||
@@ -2608,8 +2613,12 @@ parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
saved := parser.no_struct_literal
|
saved := parser.no_struct_literal
|
||||||
parser.no_struct_literal = true
|
parser.no_struct_literal = true
|
||||||
|
saved_capture_pipe := parser.capture_pipe
|
||||||
|
parser.capture_pipe = true
|
||||||
condition := parse_expression(parser)
|
condition := parse_expression(parser)
|
||||||
|
parser.capture_pipe = saved_capture_pipe
|
||||||
parser.no_struct_literal = saved
|
parser.no_struct_literal = saved
|
||||||
|
captures, guard := parse_conditional_captures(parser)
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
|
|
||||||
update := ast.INVALID_STMT
|
update := ast.INVALID_STMT
|
||||||
@@ -2631,6 +2640,8 @@ parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
span=span_from(start.span, previous(parser).span),
|
span=span_from(start.span, previous(parser).span),
|
||||||
expr=condition,
|
expr=condition,
|
||||||
body=body,
|
body=body,
|
||||||
|
captures=captures,
|
||||||
|
guard=guard,
|
||||||
label=label,
|
label=label,
|
||||||
update=update,
|
update=update,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
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)
|
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)
|
@(test)
|
||||||
parser_diagnoses_braceless_for_and_unwrap_without_parens_or_call :: proc(t: ^testing.T) {
|
parser_diagnoses_braceless_for_and_unwrap_without_parens_or_call :: proc(t: ^testing.T) {
|
||||||
text := `main func() void {
|
text := `main func() void {
|
||||||
|
|||||||
Reference in New Issue
Block a user