conditionally unwrapping while loops
This commit is contained in:
+208
-126
@@ -363,7 +363,7 @@ block_reads_name :: proc(checker: ^Checker, statements: []ast.Stmt_Id, name: sym
|
||||
append(&statement_stack, ..statement.body)
|
||||
append(&statement_stack, ..statement.else_body)
|
||||
case .While:
|
||||
append(&expr_stack, statement.expr)
|
||||
append(&expr_stack, statement.expr, statement.guard)
|
||||
append(&statement_stack, ..statement.body)
|
||||
if statement.update != ast.INVALID_STMT {
|
||||
append(&statement_stack, statement.update)
|
||||
@@ -3800,6 +3800,9 @@ mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, fi
|
||||
mark_block_imports_used(checker, statement.else_body, file)
|
||||
case .While:
|
||||
mark_expr_imports_used(checker, statement.expr, file)
|
||||
if statement.guard != ast.INVALID_EXPR {
|
||||
mark_expr_imports_used(checker, statement.guard, file)
|
||||
}
|
||||
mark_block_imports_used(checker, statement.body, file)
|
||||
if statement.update != ast.INVALID_STMT {
|
||||
update := [1]ast.Stmt_Id{statement.update}
|
||||
@@ -6309,12 +6312,40 @@ infer_statements :: proc(
|
||||
}
|
||||
}
|
||||
case .While:
|
||||
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||
capture_start := len(locals^)
|
||||
if len(statement.captures) > 0 {
|
||||
operands: [dynamic]ast.Expr_Id
|
||||
operands.allocator = checker.allocator
|
||||
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &operands)
|
||||
operand_types := make([]types.Type, len(operands), checker.allocator)
|
||||
for operand, index in operands {
|
||||
operand_types[index] = infer_expr(checker, operand, locals^[:], pkg, file, demanded, local_types)
|
||||
}
|
||||
for capture, index in statement.captures {
|
||||
if capture == checker.sink_symbol {
|
||||
continue
|
||||
}
|
||||
capture_type := types.INVALID
|
||||
if index < len(operand_types) &&
|
||||
types.is_optional(operand_types[index], &checker.module.types) {
|
||||
capture_type = types.child_type(operand_types[index], &checker.module.types)
|
||||
}
|
||||
append(locals, Infer_Local{name=capture, type=capture_type, declared=capture_type, statement=ast.INVALID_STMT})
|
||||
}
|
||||
if statement.guard != ast.INVALID_EXPR {
|
||||
_ = infer_expr(checker, statement.guard, locals^[:], pkg, file, demanded, local_types)
|
||||
}
|
||||
delete(operand_types, checker.allocator)
|
||||
delete(operands)
|
||||
} else {
|
||||
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||
}
|
||||
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
if statement.update != ast.INVALID_STMT {
|
||||
update := [1]ast.Stmt_Id{statement.update}
|
||||
infer_statements(checker, update[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
}
|
||||
resize(locals, capture_start)
|
||||
case .For:
|
||||
if statement.expand {
|
||||
bindings, expand_error := expand_field_bindings(checker, statement.expr, statement.name, pkg, file)
|
||||
@@ -11042,6 +11073,122 @@ flatten_expand_iteration :: proc(
|
||||
return .Normal
|
||||
}
|
||||
|
||||
build_conditional_unwrap_header :: proc(
|
||||
ctx: ^Build_Ctx,
|
||||
statement: ast.Stmt,
|
||||
keyword: string,
|
||||
) -> (
|
||||
unwraps: []hir.Conditional_Unwrap,
|
||||
guard: hir.Expr_Id,
|
||||
capture_start: int,
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
valid: bool,
|
||||
) {
|
||||
checker := ctx.checker
|
||||
ast_operands: [dynamic]ast.Expr_Id
|
||||
ast_operands.allocator = checker.allocator
|
||||
defer delete(ast_operands)
|
||||
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &ast_operands)
|
||||
|
||||
valid = true
|
||||
diagnostic = source.INVALID_DIAGNOSTIC
|
||||
if len(ast_operands) != len(statement.captures) {
|
||||
diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
statement.span,
|
||||
"'%s' unwrap has %d operands but %d captures",
|
||||
keyword,
|
||||
len(ast_operands),
|
||||
len(statement.captures),
|
||||
)
|
||||
valid = false
|
||||
}
|
||||
|
||||
values := make([]hir.Expr_Id, len(ast_operands), checker.allocator)
|
||||
defer delete(values, checker.allocator)
|
||||
child_types := make([]types.Type, len(ast_operands), checker.allocator)
|
||||
defer delete(child_types, checker.allocator)
|
||||
for operand, index in ast_operands {
|
||||
child_types[index] = types.INVALID
|
||||
value := build_expr(
|
||||
checker, operand, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
types.INVALID, ctx.pkg, ctx.file,
|
||||
)
|
||||
values[index] = value
|
||||
value_type := checker.module.exprs[value].type
|
||||
if checker.module.exprs[value].kind == .Invalid {
|
||||
valid = false
|
||||
if diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
diagnostic = checker.module.exprs[value].diagnostic
|
||||
}
|
||||
} else if !types.is_optional(value_type, &checker.module.types) {
|
||||
diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
checker.ast_module.exprs[operand].span,
|
||||
"'%s' unwrap requires an optional value (operand %d)",
|
||||
keyword,
|
||||
index + 1,
|
||||
)
|
||||
valid = false
|
||||
} else {
|
||||
child_types[index] = types.child_type(value_type, &checker.module.types)
|
||||
}
|
||||
}
|
||||
|
||||
capture_start = len(ctx.locals^)
|
||||
unwrap_list: [dynamic]hir.Conditional_Unwrap
|
||||
unwrap_list.allocator = checker.allocator
|
||||
for capture, index in statement.captures {
|
||||
child := child_types[index] if index < len(child_types) else types.INVALID
|
||||
local := hir.INVALID_LOCAL
|
||||
if capture != checker.sink_symbol {
|
||||
if _, duplicate := find_build_local(ctx.locals^[capture_start:], capture); duplicate {
|
||||
diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
statement.span,
|
||||
"'%s' unwrap captures must have distinct names",
|
||||
keyword,
|
||||
)
|
||||
valid = false
|
||||
} else if id := add_shadow_diagnostic(
|
||||
checker, statement.span, capture, "capture",
|
||||
ctx.pkg, ctx.file, ctx.locals^[:capture_start], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
||||
); id != source.INVALID_DIAGNOSTIC {
|
||||
diagnostic = id
|
||||
valid = false
|
||||
}
|
||||
local = append_build_local(ctx, capture, child, false, statement.span)
|
||||
}
|
||||
if index < len(values) {
|
||||
append(&unwrap_list, hir.Conditional_Unwrap{expr=values[index], local=local})
|
||||
}
|
||||
}
|
||||
|
||||
guard = hir.INVALID_EXPR
|
||||
if statement.guard != ast.INVALID_EXPR {
|
||||
guard = build_expr(
|
||||
checker, statement.guard, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
types.BOOL, ctx.pkg, ctx.file,
|
||||
)
|
||||
if checker.module.exprs[guard].kind == .Invalid {
|
||||
valid = false
|
||||
if diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
diagnostic = checker.module.exprs[guard].diagnostic
|
||||
}
|
||||
} else if !types.is_bool(checker.module.exprs[guard].type) &&
|
||||
!types.is_noreturn(checker.module.exprs[guard].type) {
|
||||
diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
checker.ast_module.exprs[statement.guard].span,
|
||||
"'%s' unwrap guard must be a bool",
|
||||
keyword,
|
||||
)
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
return unwrap_list[:], guard, capture_start, diagnostic, valid
|
||||
}
|
||||
|
||||
build_block :: proc(
|
||||
ctx: ^Build_Ctx,
|
||||
statements: []ast.Stmt_Id,
|
||||
@@ -11763,101 +11910,8 @@ build_block :: proc(
|
||||
}
|
||||
}
|
||||
if len(statement.captures) > 0 {
|
||||
ast_operands: [dynamic]ast.Expr_Id
|
||||
ast_operands.allocator = checker.allocator
|
||||
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &ast_operands)
|
||||
|
||||
valid_unwrap := true
|
||||
diagnostic := source.INVALID_DIAGNOSTIC
|
||||
if len(ast_operands) != len(statement.captures) {
|
||||
diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
statement.span,
|
||||
"'if' unwrap has %d operands but %d captures",
|
||||
len(ast_operands),
|
||||
len(statement.captures),
|
||||
)
|
||||
valid_unwrap = false
|
||||
}
|
||||
|
||||
values := make([]hir.Expr_Id, len(ast_operands), checker.allocator)
|
||||
child_types := make([]types.Type, len(ast_operands), checker.allocator)
|
||||
for operand, index in ast_operands {
|
||||
child_types[index] = types.INVALID
|
||||
value := build_expr(
|
||||
checker, operand, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
types.INVALID, ctx.pkg, ctx.file,
|
||||
)
|
||||
values[index] = value
|
||||
value_type := checker.module.exprs[value].type
|
||||
if checker.module.exprs[value].kind == .Invalid {
|
||||
valid_unwrap = false
|
||||
if diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
diagnostic = checker.module.exprs[value].diagnostic
|
||||
}
|
||||
} else if !types.is_optional(value_type, &checker.module.types) {
|
||||
diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
checker.ast_module.exprs[operand].span,
|
||||
"'if' unwrap requires an optional value (operand %d)",
|
||||
index + 1,
|
||||
)
|
||||
valid_unwrap = false
|
||||
} else {
|
||||
child_types[index] = types.child_type(value_type, &checker.module.types)
|
||||
}
|
||||
}
|
||||
|
||||
capture_start := len(ctx.locals^)
|
||||
unwraps: [dynamic]hir.Conditional_Unwrap
|
||||
unwraps.allocator = checker.allocator
|
||||
for capture, index in statement.captures {
|
||||
child := child_types[index] if index < len(child_types) else types.INVALID
|
||||
local := hir.INVALID_LOCAL
|
||||
if capture != checker.sink_symbol {
|
||||
if _, duplicate := find_build_local(ctx.locals^[capture_start:], capture); duplicate {
|
||||
diagnostic = source.add(
|
||||
checker.diagnostics,
|
||||
statement.span,
|
||||
"'if' unwrap captures must have distinct names",
|
||||
)
|
||||
valid_unwrap = false
|
||||
} else if id := add_shadow_diagnostic(
|
||||
checker, statement.span, capture, "capture",
|
||||
ctx.pkg, ctx.file, ctx.locals^[:capture_start], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
||||
); id != source.INVALID_DIAGNOSTIC {
|
||||
diagnostic = id
|
||||
valid_unwrap = false
|
||||
}
|
||||
local = append_build_local(ctx, capture, child, false, statement.span)
|
||||
}
|
||||
if index < len(values) {
|
||||
append(&unwraps, hir.Conditional_Unwrap{expr=values[index], local=local})
|
||||
}
|
||||
}
|
||||
|
||||
guard := hir.INVALID_EXPR
|
||||
if statement.guard != ast.INVALID_EXPR {
|
||||
guard = build_expr(
|
||||
checker, statement.guard, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
types.BOOL, ctx.pkg, ctx.file,
|
||||
)
|
||||
if checker.module.exprs[guard].kind == .Invalid {
|
||||
valid_unwrap = false
|
||||
if diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
diagnostic = checker.module.exprs[guard].diagnostic
|
||||
}
|
||||
} else if !types.is_bool(checker.module.exprs[guard].type) &&
|
||||
!types.is_noreturn(checker.module.exprs[guard].type) {
|
||||
diagnostic = source.add(
|
||||
checker.diagnostics,
|
||||
checker.ast_module.exprs[statement.guard].span,
|
||||
"'if' unwrap guard must be a bool",
|
||||
)
|
||||
valid_unwrap = false
|
||||
}
|
||||
}
|
||||
|
||||
unwraps, guard, capture_start, diagnostic, valid_unwrap :=
|
||||
build_conditional_unwrap_header(ctx, statement, "if")
|
||||
then_body := build_block(ctx, statement.body, capture_start)
|
||||
resize(ctx.locals, capture_start)
|
||||
else_body: []hir.Stmt_Id = nil
|
||||
@@ -11870,7 +11924,7 @@ build_block :: proc(
|
||||
kind=.If,
|
||||
span=statement.span,
|
||||
expr=hir.INVALID_EXPR,
|
||||
unwraps=unwraps[:],
|
||||
unwraps=unwraps,
|
||||
guard=guard,
|
||||
then_body=then_body,
|
||||
else_body=else_body,
|
||||
@@ -11879,7 +11933,7 @@ build_block :: proc(
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
delete(unwraps)
|
||||
delete(unwraps, checker.allocator)
|
||||
delete(then_body, checker.allocator)
|
||||
delete(else_body, checker.allocator)
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
@@ -11893,9 +11947,6 @@ build_block :: proc(
|
||||
})
|
||||
ctx.problematic^ = true
|
||||
}
|
||||
delete(values, checker.allocator)
|
||||
delete(child_types, checker.allocator)
|
||||
delete(ast_operands)
|
||||
continue
|
||||
}
|
||||
condition := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
|
||||
@@ -11921,16 +11972,27 @@ build_block :: proc(
|
||||
})
|
||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
||||
case .While:
|
||||
condition := build_expr(
|
||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
types.BOOL, ctx.pkg, ctx.file,
|
||||
)
|
||||
if checker.module.exprs[condition].kind != .Invalid &&
|
||||
!types.is_bool(checker.module.exprs[condition].type) &&
|
||||
!types.is_noreturn(checker.module.exprs[condition].type) {
|
||||
id := source.add(checker.diagnostics, statement.span, "'while' condition must be a bool")
|
||||
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
||||
ctx.problematic^ = true
|
||||
condition := hir.INVALID_EXPR
|
||||
unwraps: []hir.Conditional_Unwrap = nil
|
||||
guard := hir.INVALID_EXPR
|
||||
capture_start := len(ctx.locals^)
|
||||
unwrap_diagnostic := source.INVALID_DIAGNOSTIC
|
||||
valid_unwrap := true
|
||||
if len(statement.captures) > 0 {
|
||||
unwraps, guard, capture_start, unwrap_diagnostic, valid_unwrap =
|
||||
build_conditional_unwrap_header(ctx, statement, "while")
|
||||
} else {
|
||||
condition = build_expr(
|
||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
types.BOOL, ctx.pkg, ctx.file,
|
||||
)
|
||||
if checker.module.exprs[condition].kind != .Invalid &&
|
||||
!types.is_bool(checker.module.exprs[condition].type) &&
|
||||
!types.is_noreturn(checker.module.exprs[condition].type) {
|
||||
id := source.add(checker.diagnostics, statement.span, "'while' condition must be a bool")
|
||||
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
||||
ctx.problematic^ = true
|
||||
}
|
||||
}
|
||||
if id := add_label_shadow_diagnostic(ctx, statement.span, statement.label);
|
||||
id != source.INVALID_DIAGNOSTIC {
|
||||
@@ -11944,32 +12006,52 @@ build_block :: proc(
|
||||
append(ctx.loop_defer_starts, len(ctx.defers^))
|
||||
append(ctx.loop_labels, statement.label)
|
||||
append(ctx.loop_is_loop, true)
|
||||
loop_body := build_block(ctx, statement.body)
|
||||
loop_body := build_block(ctx, statement.body, capture_start)
|
||||
pop(ctx.loop_is_loop)
|
||||
pop(ctx.loop_labels)
|
||||
pop(ctx.loop_defer_starts)
|
||||
update := hir.INVALID_STMT
|
||||
if statement.update != ast.INVALID_STMT {
|
||||
update_ast := [1]ast.Stmt_Id{statement.update}
|
||||
update_body := build_block(ctx, update_ast[:])
|
||||
update_body := build_block(ctx, update_ast[:], capture_start)
|
||||
if len(update_body) > 0 {
|
||||
update = update_body[0]
|
||||
}
|
||||
delete(update_body, checker.allocator)
|
||||
}
|
||||
resize(ctx.locals, capture_start)
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind=.While,
|
||||
span=statement.span,
|
||||
label=statement.label,
|
||||
expr=condition,
|
||||
then_body=loop_body,
|
||||
update=update,
|
||||
local=hir.INVALID_LOCAL,
|
||||
target=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
||||
if valid_unwrap {
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind=.While,
|
||||
span=statement.span,
|
||||
label=statement.label,
|
||||
expr=condition,
|
||||
unwraps=unwraps,
|
||||
guard=guard,
|
||||
then_body=loop_body,
|
||||
update=update,
|
||||
local=hir.INVALID_LOCAL,
|
||||
target=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
delete(unwraps, checker.allocator)
|
||||
delete(loop_body, checker.allocator)
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind=.Trap,
|
||||
span=statement.span,
|
||||
expr=hir.INVALID_EXPR,
|
||||
guard=hir.INVALID_EXPR,
|
||||
local=hir.INVALID_LOCAL,
|
||||
target=hir.INVALID_EXPR,
|
||||
diagnostic=unwrap_diagnostic,
|
||||
})
|
||||
ctx.problematic^ = true
|
||||
}
|
||||
if condition != hir.INVALID_EXPR {
|
||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
||||
}
|
||||
case .For:
|
||||
if statement.expand {
|
||||
if statement.pointer_capture {
|
||||
|
||||
Reference in New Issue
Block a user