condtional multi-unwrap and guard clauses
This commit is contained in:
+172
-32
@@ -629,6 +629,9 @@ mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, fi
|
||||
}
|
||||
case .If:
|
||||
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.else_body, file)
|
||||
case .While:
|
||||
@@ -1403,6 +1406,24 @@ infer_expr :: proc(
|
||||
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(
|
||||
checker: ^Checker,
|
||||
statements: []ast.Stmt_Id,
|
||||
@@ -1438,16 +1459,36 @@ infer_statements :: proc(
|
||||
}
|
||||
}
|
||||
case .If:
|
||||
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
||||
if statement.name != symbol.INVALID {
|
||||
// Conditional unwrap: `v` is in scope (with the unwrapped type) only inside the then-block.
|
||||
child := types.child_type(value_type, &checker.module.types) if types.is_optional(value_type, &checker.module.types) else types.INVALID
|
||||
binding_start := len(locals^)
|
||||
append(locals, Infer_Local{name = statement.name, type = child})
|
||||
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)
|
||||
}
|
||||
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)
|
||||
resize(locals, binding_start)
|
||||
resize(locals, capture_start)
|
||||
infer_statements(checker, statement.else_body, locals, pkg, file, demanded, result)
|
||||
delete(operand_types, checker.allocator)
|
||||
delete(operands)
|
||||
} else {
|
||||
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
||||
infer_statements(checker, statement.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:
|
||||
if statement.name != symbol.INVALID {
|
||||
// Conditional unwrap `if opt |v| { ... }`: `expr` is the optional, `v`
|
||||
// binds the unwrapped value (immutable) for the duration of the then-block.
|
||||
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
||||
value_type := checker.module.exprs[value].type
|
||||
child := types.INVALID
|
||||
if checker.module.exprs[value].kind != .Invalid && !types.is_optional(value_type, &checker.module.types) {
|
||||
id := source.add(checker.diagnostics, statement.span, "'if' unwrap requires an optional value")
|
||||
value = invalid_hir_expr(checker, statement.span, id)
|
||||
ctx.problematic^ = true
|
||||
} else if checker.module.exprs[value].kind != .Invalid {
|
||||
child = types.child_type(value_type, &checker.module.types)
|
||||
if len(statement.captures) > 0 {
|
||||
ast_operands: [dynamic]ast.Expr_Id
|
||||
ast_operands.allocator = checker.allocator
|
||||
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &ast_operands)
|
||||
|
||||
valid_unwrap := true
|
||||
diagnostic := source.INVALID_DIAGNOSTIC
|
||||
if len(ast_operands) != len(statement.captures) {
|
||||
diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
statement.span,
|
||||
"'if' unwrap has %d operands but %d captures",
|
||||
len(ast_operands),
|
||||
len(statement.captures),
|
||||
)
|
||||
valid_unwrap = false
|
||||
}
|
||||
binding := hir.local_id(len(ctx.hir_locals^))
|
||||
append(ctx.hir_locals, hir.Local{name = statement.name, type = child, mutable = false})
|
||||
locals_before := len(ctx.locals^)
|
||||
append(ctx.locals, Build_Local{name = statement.name, type = child, mutable = false, id = binding})
|
||||
then_body := build_block(ctx, statement.body)
|
||||
resize(ctx.locals, locals_before)
|
||||
|
||||
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
|
||||
}
|
||||
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
|
||||
if statement.else_body != nil {
|
||||
else_body = build_block(ctx, statement.else_body)
|
||||
}
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind = .If, span = statement.span, expr = value,
|
||||
then_body = then_body, else_body = else_body,
|
||||
local = binding, target = hir.INVALID_EXPR,
|
||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
||||
if valid_unwrap {
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind=.If,
|
||||
span=statement.span,
|
||||
expr=hir.INVALID_EXPR,
|
||||
unwraps=unwraps[:],
|
||||
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
|
||||
}
|
||||
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(&checker.module.statements, hir.Stmt{
|
||||
kind = .If, span = statement.span, expr = condition,
|
||||
guard = hir.INVALID_EXPR,
|
||||
then_body = then_body, else_body = else_body,
|
||||
local = hir.INVALID_LOCAL, target = hir.INVALID_EXPR,
|
||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
|
||||
Reference in New Issue
Block a user