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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
+170
-30
@@ -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)))
|
||||||
|
if valid_unwrap {
|
||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
kind = .If, span = statement.span, expr = value,
|
kind=.If,
|
||||||
then_body = then_body, else_body = else_body,
|
span=statement.span,
|
||||||
local = binding, target = hir.INVALID_EXPR,
|
expr=hir.INVALID_EXPR,
|
||||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
unwraps=unwraps[:],
|
||||||
|
guard=guard,
|
||||||
|
then_body=then_body,
|
||||||
|
else_body=else_body,
|
||||||
|
local=hir.INVALID_LOCAL,
|
||||||
|
target=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
} 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)
|
||||||
}
|
}
|
||||||
|
|||||||
+96
-36
@@ -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
|
||||||
|
|
||||||
|
if len(statement.unwraps) > 0 {
|
||||||
|
// Evaluate each optional exactly once, entering the next operand only
|
||||||
|
// after the previous one is present. Capture storage is initialized in
|
||||||
|
// these success blocks so the optional guard can use every binding.
|
||||||
|
for unwrap in statement.unwraps {
|
||||||
|
optional := lower_expr(state, unwrap.expr)
|
||||||
|
present := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Optional_Is_Some,
|
||||||
|
span=statement.span,
|
||||||
|
type=types.BOOL,
|
||||||
|
target=ir.INVALID_REF,
|
||||||
|
a=optional,
|
||||||
|
b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
success_lbl := fresh_label(state)
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Cond_Br,
|
||||||
|
span=statement.span,
|
||||||
|
type=types.VOID,
|
||||||
|
a=present,
|
||||||
|
integer=success_lbl,
|
||||||
|
target=ir.Ref(u32(false_target)),
|
||||||
|
b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Label,
|
||||||
|
span=statement.span,
|
||||||
|
type=types.VOID,
|
||||||
|
integer=success_lbl,
|
||||||
|
target=ir.INVALID_REF,
|
||||||
|
a=ir.INVALID_INSTRUCTION,
|
||||||
|
b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
if unwrap.local != hir.INVALID_LOCAL && int(unwrap.local) < len(state.func_locals) {
|
||||||
|
local := state.func_locals[unwrap.local]
|
||||||
|
slot := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Alloca,
|
||||||
|
span=statement.span,
|
||||||
|
type=local.type,
|
||||||
|
target=ir.local_ref(ir.Local_Id(unwrap.local)),
|
||||||
|
a=ir.INVALID_INSTRUCTION,
|
||||||
|
b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
state.local_slots[unwrap.local] = slot
|
||||||
|
inner := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Optional_Value,
|
||||||
|
span=statement.span,
|
||||||
|
type=local.type,
|
||||||
|
target=ir.INVALID_REF,
|
||||||
|
a=optional,
|
||||||
|
b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Store,
|
||||||
|
span=statement.span,
|
||||||
|
type=local.type,
|
||||||
|
target=ir.INVALID_REF,
|
||||||
|
a=slot,
|
||||||
|
b=inner,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if statement.guard != hir.INVALID_EXPR {
|
||||||
|
guard := lower_expr(state, statement.guard)
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Cond_Br,
|
||||||
|
span=statement.span,
|
||||||
|
type=types.VOID,
|
||||||
|
a=guard,
|
||||||
|
integer=then_lbl,
|
||||||
|
target=ir.Ref(u32(false_target)),
|
||||||
|
b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Br,
|
||||||
|
span=statement.span,
|
||||||
|
type=types.VOID,
|
||||||
|
integer=then_lbl,
|
||||||
|
target=ir.INVALID_REF,
|
||||||
|
a=ir.INVALID_INSTRUCTION,
|
||||||
|
b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cond := lower_expr(state, statement.expr)
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
op=.Cond_Br, span=statement.span, type=types.VOID,
|
op=.Cond_Br, span=statement.span, type=types.VOID,
|
||||||
a=cond, integer=then_lbl, target=ir.Ref(u32(false_target)),
|
a=cond, integer=then_lbl, target=ir.Ref(u32(false_target)),
|
||||||
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
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 {
|
||||||
|
advance(parser)
|
||||||
|
append(&captures, name_tok.symbol)
|
||||||
} else {
|
} else {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected a binding name after '|'")
|
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