condtional multi-unwrap and guard clauses
This commit is contained in:
+1
-1
@@ -25,7 +25,7 @@
|
|||||||
- information-preserving and information-forgetting pointer-to-array decay and sentinel slice/pointer weakening; array values never implicitly decay
|
- information-preserving and information-forgetting pointer-to-array decay and sentinel slice/pointer weakening; array values never implicitly decay
|
||||||
- narrow immutable zero-terminated byte pointer conversion to `*c_char` and `[*;0]c_char`, without general `u8`/`c_char` interchange
|
- narrow immutable zero-terminated byte pointer conversion to `*c_char` and `[*;0]c_char`, without general `u8`/`c_char` interchange
|
||||||
- optionals with trapping postfix `?`, `orelse`, and nullable pointer representation
|
- optionals with trapping postfix `?`, `orelse`, and nullable pointer representation
|
||||||
- conditional optional unwrapping with immutable then-block bindings: `if value |binding| { ... }`
|
- conditional optional unwrapping with immutable guard/then-block bindings, guarded captures, and left-to-right short-circuiting multi-unwrap: `if first and second |a, b : guard| { ... }`
|
||||||
- source-order native structs, defined or opaque `c_struct`, and keyed record literals
|
- source-order native structs, defined or opaque `c_struct`, and keyed record literals
|
||||||
- complete plain imported C structs and unions as runtime values; incomplete or unsupported-layout records remain pointer-only
|
- complete plain imported C structs and unions as runtime values; incomplete or unsupported-layout records remain pointer-only
|
||||||
- C function pointer types as pointer-sized runtime values, including manual `*c_func(...) T` spelling and nullable imported callback typedefs
|
- C function pointer types as pointer-sized runtime values, including manual `*c_func(...) T` spelling and nullable imported callback typedefs
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
- function-like macros and non-literal macro expressions remain unsupported
|
- function-like macros and non-literal macro expressions remain unsupported
|
||||||
- static inline functions (implemented)
|
- static inline functions (implemented)
|
||||||
|
|
||||||
5. control flow
|
5. control flow (implemented)
|
||||||
- boolean expressions (implemented)
|
- boolean expressions (implemented)
|
||||||
- `bool` type with `true` / `false` literals
|
- `bool` type with `true` / `false` literals
|
||||||
- comparison operators: `==`, `!=`, `<`, `<=`, `>`, `>=` (numeric operands widen; `bool` supports only `==` / `!=`)
|
- comparison operators: `==`, `!=`, `<`, `<=`, `>`, `>=` (numeric operands widen; `bool` supports only `==` / `!=`)
|
||||||
@@ -93,8 +93,8 @@
|
|||||||
- single immutable binding scoped to the then-block; `v` not visible in `else` or after the `if`
|
- single immutable binding scoped to the then-block; `v` not visible in `else` or after the `if`
|
||||||
- `|` lexes as a new `Pipe` token; the `.If` reuses AST `name` / HIR `local` to carry the binding (no new statement kind)
|
- `|` lexes as a new `Pipe` token; the `.If` reuses AST `name` / HIR `local` to carry the binding (no new statement kind)
|
||||||
- new `Optional_Is_Some` / `Optional_Value` IR opcodes (the `Unwrap` presence-test + extract, minus the trap)
|
- new `Optional_Is_Some` / `Optional_Value` IR opcodes (the `Unwrap` presence-test + extract, minus the trap)
|
||||||
- conditional unwrapping with guard clause: `if val |v : v >= 10| { ... } else { ... }` - unwrap `val` into `v` if it is not `none`
|
- conditional unwrapping with guard clause (implemented): `if val |v : v >= 10| { ... } else { ... }` - enter the then-block when `val` is not `none` and the guard is true
|
||||||
- multi-unwrap (see section below)
|
- multi-unwrap (implemented; see section below)
|
||||||
- while loops (implemented; operates on boolean conditions). examples:
|
- while loops (implemented; operates on boolean conditions). examples:
|
||||||
- `while condition { ... }` - iterate while the condition is true
|
- `while condition { ... }` - iterate while the condition is true
|
||||||
- `while condition : i = i + 1 { ... }` - execute the update after each completed iteration
|
- `while condition : i = i + 1 { ... }` - execute the update after each completed iteration
|
||||||
@@ -179,7 +179,7 @@ Ranges represent a sequence of values, commonly used in for loops, and is itself
|
|||||||
(a + 1)..(b - 1) # OK: both sides parenthesized
|
(a + 1)..(b - 1) # OK: both sides parenthesized
|
||||||
# 0..n + 1 # ERROR: must parenthesize complex expressions
|
# 0..n + 1 # ERROR: must parenthesize complex expressions
|
||||||
```
|
```
|
||||||
|
|
||||||
This rule keeps the grammar simple and forces clarity at the call site — no precedence rules to remember. Also, being a value type, ranges can be assigned to variables and passed around like any other value. Range bounds are evaluated once, must have compatible concrete integer types, and descending ranges are empty.
|
This rule keeps the grammar simple and forces clarity at the call site — no precedence rules to remember. Also, being a value type, ranges can be assigned to variables and passed around like any other value. Range bounds are evaluated once, must have compatible concrete integer types, and descending ranges are empty.
|
||||||
|
|
||||||
For-loop captures are immutable and scoped to the loop body. Sequence index captures are `usize`. Pointer capture uses `|@item|`; arrays must be passed by pointer (for example `&items`), while slices can be used directly. Sentinel elements are not included in iteration.
|
For-loop captures are immutable and scoped to the loop body. Sequence index captures are `usize`. Pointer capture uses `|@item|`; arrays must be passed by pointer (for example `&items`), while slices can be used directly. Sentinel elements are not included in iteration.
|
||||||
|
|||||||
@@ -136,14 +136,18 @@ Stmt :: struct {
|
|||||||
pointer_capture: bool,
|
pointer_capture: bool,
|
||||||
target: Expr_Id,
|
target: Expr_Id,
|
||||||
expr: Expr_Id,
|
expr: Expr_Id,
|
||||||
// `If` statements use `expr` as the condition, `body` as the then-block, and
|
// `If` statements use `expr` as the condition, `captures` as optional
|
||||||
// `else_body` as the else-block. An `else if` chain is represented as an
|
// unwrap binding names, `guard` as the optional post-unwrap boolean
|
||||||
// `else_body` holding a single nested `If` statement.
|
// 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,
|
// `While` statements use `expr` as the condition, `body` as the loop 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.
|
||||||
|
captures: []symbol.Id,
|
||||||
|
guard: Expr_Id,
|
||||||
body: []Stmt_Id,
|
body: []Stmt_Id,
|
||||||
else_body: []Stmt_Id,
|
else_body: []Stmt_Id,
|
||||||
update: Stmt_Id,
|
update: Stmt_Id,
|
||||||
@@ -263,6 +267,7 @@ destroy_module :: proc(module: ^Module) {
|
|||||||
delete(expr.args, module.allocator)
|
delete(expr.args, module.allocator)
|
||||||
}
|
}
|
||||||
for statement in module.statements {
|
for statement in module.statements {
|
||||||
|
delete(statement.captures, module.allocator)
|
||||||
delete(statement.body, module.allocator)
|
delete(statement.body, module.allocator)
|
||||||
delete(statement.else_body, module.allocator)
|
delete(statement.else_body, module.allocator)
|
||||||
}
|
}
|
||||||
|
|||||||
+172
-32
@@ -629,6 +629,9 @@ mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, fi
|
|||||||
}
|
}
|
||||||
case .If:
|
case .If:
|
||||||
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)
|
||||||
mark_block_imports_used(checker, statement.else_body, file)
|
mark_block_imports_used(checker, statement.else_body, file)
|
||||||
case .While:
|
case .While:
|
||||||
@@ -1403,6 +1406,24 @@ infer_expr :: proc(
|
|||||||
return last
|
return last
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flatten_conditional_unwrap_operands :: proc(
|
||||||
|
module: ^ast.Module,
|
||||||
|
expr_id: ast.Expr_Id,
|
||||||
|
operands: ^[dynamic]ast.Expr_Id,
|
||||||
|
) {
|
||||||
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(module.exprs) {
|
||||||
|
append(operands, expr_id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expr := module.exprs[expr_id]
|
||||||
|
if expr.kind == .And {
|
||||||
|
flatten_conditional_unwrap_operands(module, expr.left, operands)
|
||||||
|
flatten_conditional_unwrap_operands(module, expr.right, operands)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
append(operands, expr_id)
|
||||||
|
}
|
||||||
|
|
||||||
infer_statements :: proc(
|
infer_statements :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
statements: []ast.Stmt_Id,
|
statements: []ast.Stmt_Id,
|
||||||
@@ -1438,16 +1459,36 @@ infer_statements :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .If:
|
case .If:
|
||||||
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
if len(statement.captures) > 0 {
|
||||||
if statement.name != symbol.INVALID {
|
operands: [dynamic]ast.Expr_Id
|
||||||
// Conditional unwrap: `v` is in scope (with the unwrapped type) only inside the then-block.
|
operands.allocator = checker.allocator
|
||||||
child := types.child_type(value_type, &checker.module.types) if types.is_optional(value_type, &checker.module.types) else types.INVALID
|
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &operands)
|
||||||
binding_start := len(locals^)
|
operand_types := make([]types.Type, len(operands), checker.allocator)
|
||||||
append(locals, Infer_Local{name = statement.name, type = child})
|
for operand, index in operands {
|
||||||
|
operand_types[index] = infer_expr(checker, operand, locals^[:], pkg, file, demanded)
|
||||||
|
}
|
||||||
|
capture_start := len(locals^)
|
||||||
|
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})
|
||||||
|
}
|
||||||
|
if statement.guard != ast.INVALID_EXPR {
|
||||||
|
_ = infer_expr(checker, statement.guard, locals^[:], pkg, file, demanded)
|
||||||
|
}
|
||||||
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
||||||
resize(locals, binding_start)
|
resize(locals, capture_start)
|
||||||
infer_statements(checker, statement.else_body, locals, pkg, file, demanded, result)
|
infer_statements(checker, statement.else_body, locals, pkg, file, demanded, result)
|
||||||
|
delete(operand_types, checker.allocator)
|
||||||
|
delete(operands)
|
||||||
} else {
|
} else {
|
||||||
|
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
||||||
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
||||||
infer_statements(checker, statement.else_body, locals, pkg, file, demanded, result)
|
infer_statements(checker, statement.else_body, locals, pkg, file, demanded, result)
|
||||||
}
|
}
|
||||||
@@ -3142,37 +3183,135 @@ build_block :: proc(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
case .If:
|
case .If:
|
||||||
if statement.name != symbol.INVALID {
|
if len(statement.captures) > 0 {
|
||||||
// Conditional unwrap `if opt |v| { ... }`: `expr` is the optional, `v`
|
ast_operands: [dynamic]ast.Expr_Id
|
||||||
// binds the unwrapped value (immutable) for the duration of the then-block.
|
ast_operands.allocator = checker.allocator
|
||||||
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &ast_operands)
|
||||||
value_type := checker.module.exprs[value].type
|
|
||||||
child := types.INVALID
|
valid_unwrap := true
|
||||||
if checker.module.exprs[value].kind != .Invalid && !types.is_optional(value_type, &checker.module.types) {
|
diagnostic := source.INVALID_DIAGNOSTIC
|
||||||
id := source.add(checker.diagnostics, statement.span, "'if' unwrap requires an optional value")
|
if len(ast_operands) != len(statement.captures) {
|
||||||
value = invalid_hir_expr(checker, statement.span, id)
|
diagnostic = source.addf(
|
||||||
ctx.problematic^ = true
|
checker.diagnostics,
|
||||||
} else if checker.module.exprs[value].kind != .Invalid {
|
statement.span,
|
||||||
child = types.child_type(value_type, &checker.module.types)
|
"'if' unwrap has %d operands but %d captures",
|
||||||
|
len(ast_operands),
|
||||||
|
len(statement.captures),
|
||||||
|
)
|
||||||
|
valid_unwrap = false
|
||||||
}
|
}
|
||||||
binding := hir.local_id(len(ctx.hir_locals^))
|
|
||||||
append(ctx.hir_locals, hir.Local{name = statement.name, type = child, mutable = false})
|
values := make([]hir.Expr_Id, len(ast_operands), checker.allocator)
|
||||||
locals_before := len(ctx.locals^)
|
child_types := make([]types.Type, len(ast_operands), checker.allocator)
|
||||||
append(ctx.locals, Build_Local{name = statement.name, type = child, mutable = false, id = binding})
|
for operand, index in ast_operands {
|
||||||
then_body := build_block(ctx, statement.body)
|
child_types[index] = types.INVALID
|
||||||
resize(ctx.locals, locals_before)
|
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
|
||||||
|
}
|
||||||
|
local = hir.local_id(len(ctx.hir_locals^))
|
||||||
|
append(ctx.hir_locals, hir.Local{name=capture, type=child, mutable=false})
|
||||||
|
append(ctx.locals, Build_Local{name=capture, type=child, mutable=false, id=local})
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
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)
|
||||||
|
resize(ctx.locals, capture_start)
|
||||||
else_body: []hir.Stmt_Id = nil
|
else_body: []hir.Stmt_Id = nil
|
||||||
if statement.else_body != nil {
|
if statement.else_body != nil {
|
||||||
else_body = build_block(ctx, statement.else_body)
|
else_body = build_block(ctx, statement.else_body)
|
||||||
}
|
}
|
||||||
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 = .If, span = statement.span, expr = value,
|
append(&checker.module.statements, hir.Stmt{
|
||||||
then_body = then_body, else_body = else_body,
|
kind=.If,
|
||||||
local = binding, target = hir.INVALID_EXPR,
|
span=statement.span,
|
||||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
expr=hir.INVALID_EXPR,
|
||||||
})
|
unwraps=unwraps[:],
|
||||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
guard=guard,
|
||||||
|
then_body=then_body,
|
||||||
|
else_body=else_body,
|
||||||
|
local=hir.INVALID_LOCAL,
|
||||||
|
target=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
delete(unwraps)
|
||||||
|
delete(then_body, checker.allocator)
|
||||||
|
delete(else_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=diagnostic,
|
||||||
|
})
|
||||||
|
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)
|
||||||
@@ -3189,6 +3328,7 @@ build_block :: proc(
|
|||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
kind = .If, span = statement.span, expr = condition,
|
kind = .If, span = statement.span, expr = condition,
|
||||||
|
guard = hir.INVALID_EXPR,
|
||||||
then_body = then_body, else_body = else_body,
|
then_body = then_body, else_body = else_body,
|
||||||
local = hir.INVALID_LOCAL, target = hir.INVALID_EXPR,
|
local = hir.INVALID_LOCAL, target = hir.INVALID_EXPR,
|
||||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
|
|||||||
+12
-2
@@ -134,6 +134,11 @@ Local :: struct {
|
|||||||
parameter: bool,
|
parameter: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Conditional_Unwrap :: struct {
|
||||||
|
expr: Expr_Id,
|
||||||
|
local: Local_Id,
|
||||||
|
}
|
||||||
|
|
||||||
Stmt_Kind :: enum u8 {
|
Stmt_Kind :: enum u8 {
|
||||||
Declaration,
|
Declaration,
|
||||||
Assignment,
|
Assignment,
|
||||||
@@ -155,13 +160,17 @@ Stmt :: struct {
|
|||||||
expr: Expr_Id,
|
expr: Expr_Id,
|
||||||
iterator_type: types.Type,
|
iterator_type: types.Type,
|
||||||
pointer_capture: bool,
|
pointer_capture: bool,
|
||||||
// `If` statements use `expr` as the condition and `then_body`/`else_body` as
|
// Boolean `If` statements use `expr` as the condition. Conditional unwraps
|
||||||
// the branch statement lists.
|
// 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
|
// `While` statements use `expr` as the condition, `then_body` as the loop
|
||||||
// body, and `update` as the optional post-iteration statement.
|
// body, and `update` as the optional 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.
|
||||||
|
unwraps: []Conditional_Unwrap,
|
||||||
|
guard: Expr_Id,
|
||||||
then_body: []Stmt_Id,
|
then_body: []Stmt_Id,
|
||||||
else_body: []Stmt_Id,
|
else_body: []Stmt_Id,
|
||||||
update: Stmt_Id,
|
update: Stmt_Id,
|
||||||
@@ -232,6 +241,7 @@ destroy_module :: proc(module: ^Module) {
|
|||||||
delete(expr.args, module.allocator)
|
delete(expr.args, module.allocator)
|
||||||
}
|
}
|
||||||
for statement in module.statements {
|
for statement in module.statements {
|
||||||
|
delete(statement.unwraps, module.allocator)
|
||||||
delete(statement.then_body, module.allocator)
|
delete(statement.then_body, module.allocator)
|
||||||
delete(statement.else_body, module.allocator)
|
delete(statement.else_body, module.allocator)
|
||||||
}
|
}
|
||||||
|
|||||||
+101
-41
@@ -669,57 +669,117 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
|||||||
diagnostic=statement.diagnostic,
|
diagnostic=statement.diagnostic,
|
||||||
})
|
})
|
||||||
case .If:
|
case .If:
|
||||||
// A conditional unwrap (`if opt |v| { ... }`) carries the binding local id in
|
|
||||||
// `statement.local`; `expr` is then the optional, not a bool condition. Test it
|
|
||||||
// for presence, and inside the then-block bind the unwrapped value to the local.
|
|
||||||
is_unwrap := statement.local != hir.INVALID_LOCAL
|
|
||||||
opt := ir.INVALID_INSTRUCTION
|
|
||||||
cond: ir.Instruction_Id
|
|
||||||
if is_unwrap {
|
|
||||||
opt = lower_expr(state, statement.expr)
|
|
||||||
cond = append_instruction(state, ir.Instruction{
|
|
||||||
op=.Optional_Is_Some, span=statement.span, type=types.BOOL,
|
|
||||||
target=ir.INVALID_REF, a=opt, b=ir.INVALID_INSTRUCTION,
|
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
cond = lower_expr(state, statement.expr)
|
|
||||||
}
|
|
||||||
has_else := statement.else_body != nil
|
has_else := statement.else_body != nil
|
||||||
then_lbl := fresh_label(state)
|
then_lbl := fresh_label(state)
|
||||||
else_lbl := fresh_label(state) if has_else else then_lbl
|
else_lbl := fresh_label(state) if has_else else then_lbl
|
||||||
merge_lbl := fresh_label(state)
|
merge_lbl := fresh_label(state)
|
||||||
false_target := else_lbl if has_else else merge_lbl
|
false_target := else_lbl if has_else else merge_lbl
|
||||||
append_instruction(state, ir.Instruction{
|
|
||||||
op=.Cond_Br, span=statement.span, type=types.VOID,
|
if len(statement.unwraps) > 0 {
|
||||||
a=cond, integer=then_lbl, target=ir.Ref(u32(false_target)),
|
// Evaluate each optional exactly once, entering the next operand only
|
||||||
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
// 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 {
|
||||||
|
cond := lower_expr(state, statement.expr)
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Cond_Br, span=statement.span, type=types.VOID,
|
||||||
|
a=cond, integer=then_lbl, target=ir.Ref(u32(false_target)),
|
||||||
|
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=then_lbl,
|
op=.Label, span=statement.span, type=types.VOID, integer=then_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,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
if is_unwrap && int(statement.local) < len(state.func_locals) {
|
|
||||||
local := state.func_locals[statement.local]
|
|
||||||
slot := append_instruction(state, ir.Instruction{
|
|
||||||
op=.Alloca, span=statement.span, type=local.type,
|
|
||||||
target=ir.local_ref(ir.Local_Id(statement.local)),
|
|
||||||
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
||||||
})
|
|
||||||
state.local_slots[statement.local] = slot
|
|
||||||
inner := append_instruction(state, ir.Instruction{
|
|
||||||
op=.Optional_Value, span=statement.span, type=local.type,
|
|
||||||
target=ir.INVALID_REF, a=opt, 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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
lower_statements(state, statement.then_body)
|
lower_statements(state, statement.then_body)
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
op=.Br, span=statement.span, type=types.VOID, integer=merge_lbl,
|
op=.Br, span=statement.span, type=types.VOID, integer=merge_lbl,
|
||||||
|
|||||||
@@ -1129,16 +1129,39 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
parser.no_struct_literal = true
|
parser.no_struct_literal = true
|
||||||
condition := parse_expression(parser)
|
condition := parse_expression(parser)
|
||||||
parser.no_struct_literal = saved
|
parser.no_struct_literal = saved
|
||||||
binding := symbol.INVALID
|
captures: [dynamic]symbol.Id
|
||||||
|
captures.allocator = parser.module.allocator
|
||||||
|
guard := ast.INVALID_EXPR
|
||||||
if _, ok := allow(parser, .Pipe); ok {
|
if _, ok := allow(parser, .Pipe); ok {
|
||||||
name_tok, name_ok := allow(parser, .Identifier)
|
for {
|
||||||
if name_ok {
|
name_tok := current(parser)
|
||||||
binding = name_tok.symbol
|
if name_tok.kind == .Identifier || name_tok.kind == .Underscore {
|
||||||
} else {
|
advance(parser)
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected a binding name after '|'")
|
append(&captures, name_tok.symbol)
|
||||||
|
} else {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected an unwrap capture name")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, comma_ok := allow(parser, .Comma); !comma_ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if current(parser).kind == .Colon || current(parser).kind == .Pipe {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected an unwrap capture after ','")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, guard_ok := allow(parser, .Colon); guard_ok {
|
||||||
|
if current(parser).kind == .Pipe {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected a guard expression after ':'")
|
||||||
|
} else {
|
||||||
|
saved = parser.no_struct_literal
|
||||||
|
parser.no_struct_literal = true
|
||||||
|
guard = parse_expression(parser)
|
||||||
|
parser.no_struct_literal = saved
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if _, close_ok := allow(parser, .Pipe); !close_ok {
|
if _, close_ok := allow(parser, .Pipe); !close_ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected '|' to close the unwrap binding")
|
source.add(parser.diagnostics, current(parser).span, "expected '|' to close unwrap captures")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
@@ -1164,8 +1187,9 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
append(&parser.module.statements, ast.Stmt{
|
append(&parser.module.statements, ast.Stmt{
|
||||||
kind=.If,
|
kind=.If,
|
||||||
span=span_from(start.span, previous(parser).span),
|
span=span_from(start.span, previous(parser).span),
|
||||||
name=binding,
|
|
||||||
expr=condition,
|
expr=condition,
|
||||||
|
captures=captures[:],
|
||||||
|
guard=guard,
|
||||||
body=then_body,
|
body=then_body,
|
||||||
else_body=else_body,
|
else_body=else_body,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
|||||||
+181
-2
@@ -3823,11 +3823,121 @@ conditional_unwrap_compiles_and_runs :: proc(t: ^testing.T) {
|
|||||||
status := compiler_core.compile_package("examples/programs/conditional_unwrap", output)
|
status := compiler_core.compile_package("examples/programs/conditional_unwrap", output)
|
||||||
testing.expect_value(t, status, 0)
|
testing.expect_value(t, status, 0)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
// present scalar binds and unwraps (40), none takes the else (+2), a present
|
// Single unwrap, guarded two/three-value unwraps, optional pointers, false
|
||||||
// optional pointer binds and derefs (+0), a none optional pointer is skipped.
|
// guards, and failed short-circuit chains preserve the expected total.
|
||||||
testing.expect_value(t, state.exit_code, 42)
|
testing.expect_value(t, state.exit_code, 42)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
conditional_unwrap_parser_captures_guard_and_parenthesized_chain :: proc(t: ^testing.T) {
|
||||||
|
text := `main :: func() void {
|
||||||
|
first ?i32 = 1
|
||||||
|
second ?i32 = 2
|
||||||
|
if (first and second) |a, b : a == 1 and b == 2| {
|
||||||
|
_ = a
|
||||||
|
_ = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
statement := module.statements[module.functions[0].body[2]]
|
||||||
|
testing.expect_value(t, statement.kind, ast.Stmt_Kind.If)
|
||||||
|
testing.expect_value(t, len(statement.captures), 2)
|
||||||
|
testing.expect_value(t, module.exprs[statement.expr].kind, ast.Expr_Kind.And)
|
||||||
|
testing.expect(t, module.exprs[statement.expr].parenthesized)
|
||||||
|
testing.expect(t, statement.guard != ast.INVALID_EXPR)
|
||||||
|
testing.expect_value(t, module.exprs[statement.guard].kind, ast.Expr_Kind.And)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
conditional_unwrap_allows_sink_captures :: proc(t: ^testing.T) {
|
||||||
|
text := `main :: func() void {
|
||||||
|
first ?i32 = 1
|
||||||
|
second ?i32 = 2
|
||||||
|
if first and second |_, value : value == 2| {
|
||||||
|
_ = value
|
||||||
|
}
|
||||||
|
if first |_| {}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
main := hir_module.functions[0]
|
||||||
|
first_if := hir_module.statements[main.body[2]]
|
||||||
|
second_if := hir_module.statements[main.body[3]]
|
||||||
|
testing.expect_value(t, first_if.unwraps[0].local, hir.INVALID_LOCAL)
|
||||||
|
testing.expect(t, first_if.unwraps[1].local != hir.INVALID_LOCAL)
|
||||||
|
testing.expect_value(t, second_if.unwraps[0].local, hir.INVALID_LOCAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
parser_diagnoses_malformed_conditional_unwrap_captures_and_guards :: proc(t: ^testing.T) {
|
||||||
|
cases := [4]struct {
|
||||||
|
text: string,
|
||||||
|
needle: string,
|
||||||
|
}{
|
||||||
|
{`main :: func() void {
|
||||||
|
value ?i32 = 1
|
||||||
|
if value || {}
|
||||||
|
}
|
||||||
|
`, "expected an unwrap capture name"},
|
||||||
|
{`main :: func() void {
|
||||||
|
value ?i32 = 1
|
||||||
|
if value |capture,| {}
|
||||||
|
}
|
||||||
|
`, "expected an unwrap capture after ','"},
|
||||||
|
{`main :: func() void {
|
||||||
|
value ?i32 = 1
|
||||||
|
if value |capture :| {}
|
||||||
|
}
|
||||||
|
`, "expected a guard expression after ':'"},
|
||||||
|
{`main :: func() void {
|
||||||
|
value ?i32 = 1
|
||||||
|
if value |capture {}
|
||||||
|
}
|
||||||
|
`, "expected '|' to close unwrap captures"},
|
||||||
|
}
|
||||||
|
for test_case in cases {
|
||||||
|
source_file := source.Source{path="test.bro", text=test_case.text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found = found || strings.contains(diagnostic.message, test_case.needle)
|
||||||
|
}
|
||||||
|
testing.expect(t, found)
|
||||||
|
|
||||||
|
ast.destroy_module(&module)
|
||||||
|
delete(stream.items)
|
||||||
|
symbol.destroy_table(&symbols)
|
||||||
|
source.destroy_diagnostics(&diagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
if_unwrap_on_non_optional_is_diagnosed :: proc(t: ^testing.T) {
|
if_unwrap_on_non_optional_is_diagnosed :: proc(t: ^testing.T) {
|
||||||
text := `main :: func() i32 {
|
text := `main :: func() i32 {
|
||||||
@@ -3857,6 +3967,75 @@ if_unwrap_on_non_optional_is_diagnosed :: proc(t: ^testing.T) {
|
|||||||
testing.expect(t, found)
|
testing.expect(t, found)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
conditional_unwrap_diagnostics_cover_counts_guards_and_capture_scope :: proc(t: ^testing.T) {
|
||||||
|
text := `main :: func() void {
|
||||||
|
first ?i32 = 1
|
||||||
|
second ?i32 = 2
|
||||||
|
plain i32 = 3
|
||||||
|
|
||||||
|
if first and second |one| {}
|
||||||
|
if first |one, two| {}
|
||||||
|
if first and second |same, same| {}
|
||||||
|
if plain |value| {}
|
||||||
|
if first |value : value| {}
|
||||||
|
if first and earlier |earlier, later| {}
|
||||||
|
|
||||||
|
if first |value| {
|
||||||
|
value = 2
|
||||||
|
}
|
||||||
|
if first |value| {
|
||||||
|
value i32 = 2
|
||||||
|
_ = value
|
||||||
|
}
|
||||||
|
if first |value| {
|
||||||
|
_ = value
|
||||||
|
} else {
|
||||||
|
_ = value
|
||||||
|
}
|
||||||
|
_ = value
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
count_mismatches := 0
|
||||||
|
duplicate := false
|
||||||
|
non_optional := false
|
||||||
|
guard := false
|
||||||
|
outer_operand_scope := false
|
||||||
|
immutable := false
|
||||||
|
redeclaration := false
|
||||||
|
capture_scope := 0
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
count_mismatches += 1 if strings.contains(diagnostic.message, "unwrap has") else 0
|
||||||
|
duplicate = duplicate || strings.contains(diagnostic.message, "unwrap captures must have distinct names")
|
||||||
|
non_optional = non_optional || strings.contains(diagnostic.message, "unwrap requires an optional value")
|
||||||
|
guard = guard || strings.contains(diagnostic.message, "unwrap guard must be a bool")
|
||||||
|
outer_operand_scope = outer_operand_scope || strings.contains(diagnostic.message, "unresolved global 'earlier'")
|
||||||
|
immutable = immutable || strings.contains(diagnostic.message, "cannot assign immutable local 'value'")
|
||||||
|
redeclaration = redeclaration || strings.contains(diagnostic.message, "duplicate local 'value'")
|
||||||
|
capture_scope += 1 if strings.contains(diagnostic.message, "unresolved global 'value'") else 0
|
||||||
|
}
|
||||||
|
testing.expect_value(t, count_mismatches, 2)
|
||||||
|
testing.expect(t, duplicate)
|
||||||
|
testing.expect(t, non_optional)
|
||||||
|
testing.expect(t, guard)
|
||||||
|
testing.expect(t, outer_operand_scope)
|
||||||
|
testing.expect(t, immutable)
|
||||||
|
testing.expect(t, redeclaration)
|
||||||
|
testing.expect_value(t, capture_scope, 2)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
if_unwrap_binding_is_scoped_to_then_block :: proc(t: ^testing.T) {
|
if_unwrap_binding_is_scoped_to_then_block :: proc(t: ^testing.T) {
|
||||||
// The binding `v` is usable in the then-block but not in the else-block.
|
// The binding `v` is usable in the then-block but not in the else-block.
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# Milestone 5: conditional unwrapping `if opt |v| { ... }`.
|
# Milestone 5: conditional optional unwrapping, guards, and multi-unwrap.
|
||||||
# Tests a present optional binds and unwraps, a none takes the else branch, and
|
|
||||||
# that optional pointers (?*T) unwrap the same way.
|
observe :: func(counter @mut i32, value ?i32) ?i32 {
|
||||||
|
counter^ = counter^ + 1
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
main :: func() i32 {
|
main :: func() i32 {
|
||||||
total i32 = 0
|
total i32 = 0
|
||||||
@@ -34,5 +37,55 @@ main :: func() i32 {
|
|||||||
total = total + 1000
|
total = total + 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# guarded multi-unwrap exposes every capture to the guard and then-block
|
||||||
|
age ?i32 = 2
|
||||||
|
if a and age |value, years : value + years == 42| {
|
||||||
|
total = total
|
||||||
|
} else {
|
||||||
|
total = total + 100
|
||||||
|
}
|
||||||
|
|
||||||
|
# parenthesized chains and three-value unwraps are equivalent
|
||||||
|
bonus ?i32 = 0
|
||||||
|
if (a and age and bonus) |value, years, extra : value + years + extra == 42| {
|
||||||
|
total = total
|
||||||
|
} else {
|
||||||
|
total = total + 100
|
||||||
|
}
|
||||||
|
|
||||||
|
# false guards use the else branch, or simply fall through without one
|
||||||
|
if a |value : value == 0| {
|
||||||
|
total = total + 100
|
||||||
|
} else {
|
||||||
|
total = total
|
||||||
|
}
|
||||||
|
if a |value : value == 0| {
|
||||||
|
total = total + 100
|
||||||
|
}
|
||||||
|
|
||||||
|
# a failed unwrap prevents later expressions from being evaluated
|
||||||
|
calls i32 = 0
|
||||||
|
if b and observe(&calls, age) |missing, observed| {
|
||||||
|
total = total + missing + observed
|
||||||
|
}
|
||||||
|
if calls != 0 {
|
||||||
|
total = total + 100
|
||||||
|
}
|
||||||
|
|
||||||
|
# short-circuiting also applies after an earlier successful unwrap
|
||||||
|
if a and b and observe(&calls, age) |value, missing, observed| {
|
||||||
|
total = total + value + missing + observed
|
||||||
|
}
|
||||||
|
if calls != 0 {
|
||||||
|
total = total + 100
|
||||||
|
}
|
||||||
|
|
||||||
|
# optional pointers participate in multi-unwrap and guards
|
||||||
|
if a and p |value, q : value == 40 and q^ == 0| {
|
||||||
|
total = total
|
||||||
|
} else {
|
||||||
|
total = total + 100
|
||||||
|
}
|
||||||
|
|
||||||
return total # expect exit code 42
|
return total # expect exit code 42
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user