fix comptime yield targeting
This commit is contained in:
+496
-42
@@ -318,6 +318,7 @@ Ct_State :: struct {
|
||||
bindings: [dynamic]Ct_Binding,
|
||||
error_refinements: [dynamic]Ct_Error_Refinement,
|
||||
defers: [dynamic]Ct_Defer,
|
||||
yield_targets: [dynamic]symbol.Id,
|
||||
defer_depth: int,
|
||||
value_return_depth: int,
|
||||
steps: int,
|
||||
@@ -361,6 +362,7 @@ ct_state_make :: proc(
|
||||
state.bindings.allocator = checker.allocator
|
||||
state.error_refinements.allocator = checker.allocator
|
||||
state.defers.allocator = checker.allocator
|
||||
state.yield_targets.allocator = checker.allocator
|
||||
state.promoted_cells.allocator = checker.allocator
|
||||
state.promoted_globals.allocator = checker.allocator
|
||||
for value in values {
|
||||
@@ -401,6 +403,7 @@ ct_state_destroy :: proc(state: ^Ct_State) {
|
||||
delete(state.bindings)
|
||||
delete(state.error_refinements)
|
||||
delete(state.defers)
|
||||
delete(state.yield_targets)
|
||||
delete(state.promoted_cells)
|
||||
delete(state.promoted_globals)
|
||||
}
|
||||
@@ -1271,7 +1274,7 @@ ct_eval_expr :: proc(
|
||||
if expr.left != ast.INVALID_EXPR {
|
||||
return ct_eval_expr(state, expr.left, expected, depth+1)
|
||||
}
|
||||
flow, ok := ct_exec_statements(state, expr.body, true, depth+1)
|
||||
flow, ok := ct_exec_value_source(state, expr.body, symbol.INVALID, false, false, depth+1)
|
||||
if ok && flow.kind == .Yield {
|
||||
return flow.value, ct_flow(.Normal), true
|
||||
}
|
||||
@@ -3934,7 +3937,9 @@ ct_eval_catch_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Typ
|
||||
return result, result_flow, result_ok
|
||||
}
|
||||
state.value_return_depth += 1
|
||||
handler, handler_ok := ct_exec_statements(state, expr.body, true, depth+1)
|
||||
handler, handler_ok := ct_exec_value_source(
|
||||
state, expr.body, symbol.INVALID, expr.integer != 0, true, depth+1,
|
||||
)
|
||||
state.value_return_depth -= 1
|
||||
ct_pop_bindings(state, scope_start)
|
||||
if !handler_ok {
|
||||
@@ -4123,6 +4128,397 @@ ct_return_value :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, span: source.Spa
|
||||
return ct_flow(.Return, value), true
|
||||
}
|
||||
|
||||
ct_has_yield_target :: proc(state: ^Ct_State, label: symbol.Id) -> bool {
|
||||
for index := len(state.yield_targets) - 1; index >= 0; index -= 1 {
|
||||
if state.yield_targets[index] == label {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
ct_statements_exit_value_source :: proc(state: ^Ct_State, statements: []ast.Stmt_Id) -> bool {
|
||||
checker := state.checker
|
||||
for statement_id in statements {
|
||||
if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) {
|
||||
return false
|
||||
}
|
||||
statement := checker.ast_module.statements[statement_id]
|
||||
#partial switch statement.kind {
|
||||
case .Return, .Break, .Continue:
|
||||
return true
|
||||
case .Yield:
|
||||
if symbol.is_valid(statement.label) && ct_has_yield_target(state, statement.label) {
|
||||
return true
|
||||
}
|
||||
case .If:
|
||||
if statement.else_body != nil &&
|
||||
ct_statements_exit_value_source(state, statement.body) &&
|
||||
ct_statements_exit_value_source(state, statement.else_body) {
|
||||
return true
|
||||
}
|
||||
case .Block:
|
||||
if ct_statements_exit_value_source(state, statement.body) {
|
||||
return true
|
||||
}
|
||||
case .Match:
|
||||
exits := len(statement.body) > 0
|
||||
for arm_id in statement.body {
|
||||
arm := checker.ast_module.statements[arm_id]
|
||||
exits = exits && arm.kind == .Match_Arm && ct_statements_exit_value_source(state, arm.body)
|
||||
}
|
||||
if exits {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
ct_validate_ordinary_statements :: proc(state: ^Ct_State, statements: []ast.Stmt_Id) -> bool {
|
||||
checker := state.checker
|
||||
for statement_id in statements {
|
||||
if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) {
|
||||
return false
|
||||
}
|
||||
statement := checker.ast_module.statements[statement_id]
|
||||
#partial switch statement.kind {
|
||||
case .Declaration, .Assignment:
|
||||
if statement.expr == ast.INVALID_EXPR && !ct_validate_value_source_structure(
|
||||
state, statement.body, statement.label, statement.value_control_flow, false,
|
||||
) {
|
||||
return false
|
||||
}
|
||||
case .Return:
|
||||
if statement.value_control_flow && !ct_validate_value_source_structure(
|
||||
state, statement.body, symbol.INVALID, true, false,
|
||||
) {
|
||||
return false
|
||||
}
|
||||
case .Yield:
|
||||
if !symbol.is_valid(statement.label) {
|
||||
return ct_fail(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"'yield' is only valid as the final statement of a value block",
|
||||
)
|
||||
}
|
||||
if !ct_has_yield_target(state, statement.label) {
|
||||
return ct_failf(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"no enclosing value loop or block is labeled '%s'",
|
||||
symbol_text(checker, statement.label),
|
||||
)
|
||||
}
|
||||
if statement.value_control_flow && !ct_validate_value_source_structure(
|
||||
state, statement.body, symbol.INVALID, true, false,
|
||||
) {
|
||||
return false
|
||||
}
|
||||
case .If:
|
||||
if !ct_validate_ordinary_statements(state, statement.body) ||
|
||||
!ct_validate_ordinary_statements(state, statement.else_body) {
|
||||
return false
|
||||
}
|
||||
case .While, .For:
|
||||
if !ct_validate_ordinary_statements(state, statement.body) {
|
||||
return false
|
||||
}
|
||||
if statement.update != ast.INVALID_STMT {
|
||||
update := [1]ast.Stmt_Id{statement.update}
|
||||
if !ct_validate_ordinary_statements(state, update[:]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
case .Block:
|
||||
if !ct_validate_ordinary_statements(state, statement.body) {
|
||||
return false
|
||||
}
|
||||
case .Defer:
|
||||
if statement.update != ast.INVALID_STMT {
|
||||
deferred := [1]ast.Stmt_Id{statement.update}
|
||||
if !ct_validate_ordinary_statements(state, deferred[:]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
case .Match:
|
||||
for arm_id in statement.body {
|
||||
arm := checker.ast_module.statements[arm_id]
|
||||
if arm.kind == .Match_Arm && !ct_validate_ordinary_statements(state, arm.body) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
ct_validate_value_branch_structure :: proc(state: ^Ct_State, statements: []ast.Stmt_Id) -> bool {
|
||||
checker := state.checker
|
||||
if len(statements) == 1 && checker.ast_module.statements[statements[0]].kind == .Expression {
|
||||
return true
|
||||
}
|
||||
n := len(statements)
|
||||
if n > 0 {
|
||||
last := checker.ast_module.statements[statements[n-1]]
|
||||
if last.kind == .Yield && !symbol.is_valid(last.label) {
|
||||
if !ct_validate_ordinary_statements(state, statements[:n-1]) {
|
||||
return false
|
||||
}
|
||||
return !last.value_control_flow || ct_validate_value_source_structure(
|
||||
state, last.body, symbol.INVALID, true, false,
|
||||
)
|
||||
}
|
||||
}
|
||||
if ct_statements_exit_value_source(state, statements) {
|
||||
return ct_validate_ordinary_statements(state, statements)
|
||||
}
|
||||
return ct_fail(
|
||||
state, .Not_Comptime, source.Span{},
|
||||
"a value branch must end with 'yield' or exit on every path (return/break/continue)",
|
||||
)
|
||||
}
|
||||
|
||||
ct_validate_value_if_structure :: proc(state: ^Ct_State, statement: ast.Stmt) -> bool {
|
||||
checker := state.checker
|
||||
if statement.else_body == nil {
|
||||
return ct_fail(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"an 'if' used as a value must have an 'else' so every path yields",
|
||||
)
|
||||
}
|
||||
if !ct_validate_value_branch_structure(state, statement.body) {
|
||||
return false
|
||||
}
|
||||
if len(statement.else_body) == 1 {
|
||||
else_statement := checker.ast_module.statements[statement.else_body[0]]
|
||||
if else_statement.kind == .If {
|
||||
return ct_validate_value_if_structure(state, else_statement)
|
||||
}
|
||||
}
|
||||
return ct_validate_value_branch_structure(state, statement.else_body)
|
||||
}
|
||||
|
||||
ct_validate_value_source_structure :: proc(
|
||||
state: ^Ct_State,
|
||||
statements: []ast.Stmt_Id,
|
||||
label: symbol.Id,
|
||||
value_control_flow: bool,
|
||||
allow_exit: bool,
|
||||
) -> bool {
|
||||
checker := state.checker
|
||||
if symbol.is_valid(label) {
|
||||
target_start := len(state.yield_targets)
|
||||
append(&state.yield_targets, label)
|
||||
valid := ct_validate_ordinary_statements(state, statements) &&
|
||||
ct_statements_exit_value_source(state, statements)
|
||||
resize(&state.yield_targets, target_start)
|
||||
if valid {
|
||||
return true
|
||||
}
|
||||
if state.error == .None {
|
||||
return ct_fail(state, .Not_Comptime, source.Span{}, "a labeled value block must 'yield' on every path")
|
||||
}
|
||||
return false
|
||||
}
|
||||
if value_control_flow && len(statements) == 1 {
|
||||
statement := checker.ast_module.statements[statements[0]]
|
||||
#partial switch statement.kind {
|
||||
case .If:
|
||||
return ct_validate_value_if_structure(state, statement)
|
||||
case .For, .While:
|
||||
n := len(statement.body)
|
||||
if !symbol.is_valid(statement.label) {
|
||||
return ct_fail(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"a value loop must label its body (e.g. 'blk:') so a 'yield :blk' can exit it",
|
||||
)
|
||||
}
|
||||
if n == 0 {
|
||||
return ct_fail(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"a value loop's body must end with a 'yield' for when the loop completes",
|
||||
)
|
||||
}
|
||||
last := checker.ast_module.statements[statement.body[n-1]]
|
||||
if last.kind != .Yield || symbol.is_valid(last.label) {
|
||||
return ct_fail(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"a value loop's body must end with a 'yield' for when the loop completes",
|
||||
)
|
||||
}
|
||||
target_start := len(state.yield_targets)
|
||||
append(&state.yield_targets, statement.label)
|
||||
valid := ct_validate_ordinary_statements(state, statement.body[:n-1])
|
||||
if valid && statement.update != ast.INVALID_STMT {
|
||||
update := [1]ast.Stmt_Id{statement.update}
|
||||
valid = ct_validate_ordinary_statements(state, update[:])
|
||||
}
|
||||
resize(&state.yield_targets, target_start)
|
||||
if valid && last.value_control_flow {
|
||||
valid = ct_validate_value_source_structure(
|
||||
state, last.body, symbol.INVALID, true, false,
|
||||
)
|
||||
}
|
||||
return valid
|
||||
case .Match:
|
||||
for arm_id in statement.body {
|
||||
arm := checker.ast_module.statements[arm_id]
|
||||
if arm.kind == .Match_Arm && !ct_validate_value_branch_structure(state, arm.body) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
n := len(statements)
|
||||
if n > 0 {
|
||||
last := checker.ast_module.statements[statements[n-1]]
|
||||
if last.kind == .Yield && !symbol.is_valid(last.label) {
|
||||
if !ct_validate_ordinary_statements(state, statements[:n-1]) {
|
||||
return false
|
||||
}
|
||||
return !last.value_control_flow || ct_validate_value_source_structure(
|
||||
state, last.body, symbol.INVALID, true, false,
|
||||
)
|
||||
}
|
||||
}
|
||||
if allow_exit {
|
||||
return ct_validate_ordinary_statements(state, statements)
|
||||
}
|
||||
return ct_fail(state, .Not_Comptime, source.Span{}, "a value block must end with an explicit 'yield'")
|
||||
}
|
||||
|
||||
ct_exec_value_branch :: proc(state: ^Ct_State, statements: []ast.Stmt_Id, depth: int) -> (Ct_Flow, bool) {
|
||||
checker := state.checker
|
||||
if len(statements) == 1 {
|
||||
statement := checker.ast_module.statements[statements[0]]
|
||||
if statement.kind == .Expression {
|
||||
value, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return flow, ok
|
||||
}
|
||||
return ct_flow(.Yield, value), true
|
||||
}
|
||||
}
|
||||
flow, ok := ct_exec_statements(state, statements, true, depth+1)
|
||||
if ok && flow.kind == .Normal {
|
||||
return flow, ct_fail(
|
||||
state, .Not_Comptime, source.Span{},
|
||||
"a value branch must end with 'yield' or exit on every path (return/break/continue)",
|
||||
)
|
||||
}
|
||||
return flow, ok
|
||||
}
|
||||
|
||||
ct_exec_value_loop :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) -> (Ct_Flow, bool) {
|
||||
checker := state.checker
|
||||
n := len(statement.body)
|
||||
if !symbol.is_valid(statement.label) {
|
||||
return ct_flow(.Normal), ct_fail(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"a value loop must label its body (e.g. 'blk:') so a 'yield :blk' can exit it",
|
||||
)
|
||||
}
|
||||
if n == 0 {
|
||||
return ct_flow(.Normal), ct_fail(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"a value loop's body must end with a 'yield' for when the loop completes",
|
||||
)
|
||||
}
|
||||
fall_stmt := checker.ast_module.statements[statement.body[n-1]]
|
||||
if fall_stmt.kind != .Yield || symbol.is_valid(fall_stmt.label) {
|
||||
return ct_flow(.Normal), ct_fail(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"a value loop's body must end with a 'yield' for when the loop completes",
|
||||
)
|
||||
}
|
||||
fallback := ct_flow(.Normal)
|
||||
fallback_ok := true
|
||||
if fall_stmt.value_control_flow {
|
||||
fallback, fallback_ok = ct_exec_value_source(
|
||||
state, fall_stmt.body, symbol.INVALID, true, false, depth+1,
|
||||
)
|
||||
} else {
|
||||
value, flow, ok := ct_eval_expr(state, fall_stmt.expr, types.INVALID, depth+1)
|
||||
fallback_ok = ok
|
||||
fallback = flow
|
||||
if ok && flow.kind == .Normal {
|
||||
fallback = ct_flow(.Yield, value)
|
||||
}
|
||||
}
|
||||
if !fallback_ok || fallback.kind != .Yield {
|
||||
return fallback, fallback_ok
|
||||
}
|
||||
peeled := statement
|
||||
peeled.body = statement.body[:n-1]
|
||||
target_start := len(state.yield_targets)
|
||||
append(&state.yield_targets, statement.label)
|
||||
defer resize(&state.yield_targets, target_start)
|
||||
flow := ct_flow(.Normal)
|
||||
ok := false
|
||||
if statement.kind == .For {
|
||||
flow, ok = ct_exec_for(state, peeled, false, depth+1)
|
||||
} else {
|
||||
flow, ok = ct_exec_while(state, peeled, false, depth+1)
|
||||
}
|
||||
if !ok {
|
||||
return flow, false
|
||||
}
|
||||
if flow.kind == .Yield && flow.label == statement.label {
|
||||
return ct_flow(.Yield, flow.value), true
|
||||
}
|
||||
if flow.kind == .Normal {
|
||||
return fallback, true
|
||||
}
|
||||
return flow, true
|
||||
}
|
||||
|
||||
ct_exec_value_source :: proc(
|
||||
state: ^Ct_State,
|
||||
statements: []ast.Stmt_Id,
|
||||
label: symbol.Id,
|
||||
value_control_flow: bool,
|
||||
allow_exit: bool,
|
||||
depth: int,
|
||||
) -> (Ct_Flow, bool) {
|
||||
checker := state.checker
|
||||
if !ct_validate_value_source_structure(state, statements, label, value_control_flow, allow_exit) {
|
||||
return ct_flow(.Normal), false
|
||||
}
|
||||
if symbol.is_valid(label) {
|
||||
target_start := len(state.yield_targets)
|
||||
append(&state.yield_targets, label)
|
||||
flow, ok := ct_exec_statements(state, statements, false, depth+1)
|
||||
resize(&state.yield_targets, target_start)
|
||||
if !ok {
|
||||
return flow, false
|
||||
}
|
||||
if flow.kind == .Yield && flow.label == label {
|
||||
return ct_flow(.Yield, flow.value), true
|
||||
}
|
||||
if flow.kind == .Normal {
|
||||
return flow, ct_fail(state, .Not_Comptime, source.Span{}, "a labeled value block must 'yield' on every path")
|
||||
}
|
||||
return flow, true
|
||||
}
|
||||
if value_control_flow && len(statements) == 1 {
|
||||
statement := checker.ast_module.statements[statements[0]]
|
||||
#partial switch statement.kind {
|
||||
case .If:
|
||||
return ct_exec_if(state, statement, true, depth+1)
|
||||
case .For, .While:
|
||||
return ct_exec_value_loop(state, statement, depth+1)
|
||||
case .Match:
|
||||
return ct_exec_match(state, statement, true, depth+1)
|
||||
}
|
||||
}
|
||||
flow, ok := ct_exec_statements(state, statements, true, depth+1)
|
||||
if ok && flow.kind == .Normal && !allow_exit {
|
||||
return flow, ct_fail(state, .Not_Comptime, source.Span{}, "a value block must end with an explicit 'yield'")
|
||||
}
|
||||
return flow, ok
|
||||
}
|
||||
|
||||
ct_exec_statements :: proc(
|
||||
state: ^Ct_State,
|
||||
statements: []ast.Stmt_Id,
|
||||
@@ -4139,7 +4535,7 @@ ct_exec_statements :: proc(
|
||||
ct_pop_bindings(state, scope_start)
|
||||
resize(&state.defers, defer_start)
|
||||
}
|
||||
for statement_id in statements {
|
||||
for statement_id, statement_index in statements {
|
||||
if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) {
|
||||
return ct_flow(.Normal), false
|
||||
}
|
||||
@@ -4152,7 +4548,9 @@ ct_exec_statements :: proc(
|
||||
#partial switch statement.kind {
|
||||
case .Declaration:
|
||||
if statement.expr == ast.INVALID_EXPR {
|
||||
value_flow, value_ok := ct_exec_statements(state, statement.body, true, depth+1)
|
||||
value_flow, value_ok := ct_exec_value_source(
|
||||
state, statement.body, statement.label, statement.value_control_flow, false, depth+1,
|
||||
)
|
||||
ok = value_ok
|
||||
if ok && value_flow.kind == .Yield {
|
||||
value := value_flow.value
|
||||
@@ -4163,6 +4561,8 @@ ct_exec_statements :: proc(
|
||||
if ok && statement.name != checker.sink_symbol {
|
||||
ct_bind_value(state, statement.name, state.values[value].type, value, !statement.immutable)
|
||||
}
|
||||
} else if ok && value_flow.kind != .Normal {
|
||||
flow = value_flow
|
||||
} else if ok {
|
||||
ok = ct_fail(state, .Not_Comptime, statement.span, "comptime value block must yield")
|
||||
}
|
||||
@@ -4189,7 +4589,9 @@ ct_exec_statements :: proc(
|
||||
if yield_returns && state.value_return_depth == 0 {
|
||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'return' is not valid in this comptime block")
|
||||
} else if statement.value_control_flow {
|
||||
value_flow, value_ok := ct_exec_statements(state, statement.body, true, depth+1)
|
||||
value_flow, value_ok := ct_exec_value_source(
|
||||
state, statement.body, symbol.INVALID, true, false, depth+1,
|
||||
)
|
||||
ok = value_ok
|
||||
if ok && value_flow.kind == .Yield {
|
||||
value := value_flow.value
|
||||
@@ -4205,42 +4607,62 @@ ct_exec_statements :: proc(
|
||||
flow = ct_flow(.Return, value)
|
||||
}
|
||||
}
|
||||
} else if ok {
|
||||
flow = value_flow
|
||||
}
|
||||
} else {
|
||||
flow, ok = ct_return_value(state, statement.expr, statement.span, depth+1)
|
||||
}
|
||||
case .Yield:
|
||||
if !yield_returns {
|
||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' is only valid in a comptime value block")
|
||||
} else if statement.value_control_flow {
|
||||
flow, ok = ct_exec_statements(state, statement.body, true, depth+1)
|
||||
if ok && flow.kind == .Yield && types.is_void(state.values[flow.value].type) {
|
||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value")
|
||||
if symbol.is_valid(statement.label) {
|
||||
if !ct_has_yield_target(state, statement.label) {
|
||||
ok = ct_failf(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"no enclosing value loop or block is labeled '%s'",
|
||||
symbol_text(checker, statement.label),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
value, expr_flow, expr_ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
||||
ok = expr_ok
|
||||
flow = expr_flow
|
||||
if ok && flow.kind == .Normal {
|
||||
if types.is_void(state.values[value].type) {
|
||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value")
|
||||
} else {
|
||||
flow = ct_flow(.Yield, value, statement.label)
|
||||
} else if !yield_returns || statement_index != len(statements)-1 {
|
||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' is only valid as the final statement of a value block")
|
||||
}
|
||||
if ok {
|
||||
if statement.value_control_flow {
|
||||
value_flow, value_ok := ct_exec_value_source(
|
||||
state, statement.body, symbol.INVALID, true, false, depth+1,
|
||||
)
|
||||
ok = value_ok
|
||||
flow = value_flow
|
||||
if ok && value_flow.kind == .Yield {
|
||||
flow.label = statement.label
|
||||
if types.is_void(state.values[flow.value].type) {
|
||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
value, expr_flow, expr_ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
||||
ok = expr_ok
|
||||
flow = expr_flow
|
||||
if ok && flow.kind == .Normal {
|
||||
if types.is_void(state.values[value].type) {
|
||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value")
|
||||
} else {
|
||||
flow = ct_flow(.Yield, value, statement.label)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case .If:
|
||||
flow, ok = ct_exec_if(state, statement, yield_returns, depth+1)
|
||||
flow, ok = ct_exec_if(state, statement, false, depth+1)
|
||||
case .While:
|
||||
flow, ok = ct_exec_while(state, statement, yield_returns, depth+1)
|
||||
flow, ok = ct_exec_while(state, statement, false, depth+1)
|
||||
case .For:
|
||||
flow, ok = ct_exec_for(state, statement, yield_returns, depth+1)
|
||||
flow, ok = ct_exec_for(state, statement, false, depth+1)
|
||||
case .Break:
|
||||
flow = ct_flow(.Break, INVALID_CT_VALUE, statement.label)
|
||||
case .Continue:
|
||||
flow = ct_flow(.Continue, INVALID_CT_VALUE, statement.label)
|
||||
case .Block:
|
||||
flow, ok = ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
||||
flow, ok = ct_exec_statements(state, statement.body, false, depth+1)
|
||||
case .Defer:
|
||||
if statement.error_only && types.kind(state.result, &checker.module.types) != .Fallible {
|
||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'errdefer' requires an enclosing fallible function")
|
||||
@@ -4254,7 +4676,7 @@ ct_exec_statements :: proc(
|
||||
})
|
||||
}
|
||||
case .Match:
|
||||
flow, ok = ct_exec_match(state, statement, yield_returns, depth+1)
|
||||
flow, ok = ct_exec_match(state, statement, false, depth+1)
|
||||
case .Match_Arm:
|
||||
ok = ct_fail(state, .Not_Comptime, statement.span, "unexpected match arm outside 'match'")
|
||||
case .Invalid:
|
||||
@@ -4327,8 +4749,13 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
|
||||
}
|
||||
if name == checker.sink_symbol {
|
||||
if statement.expr == ast.INVALID_EXPR {
|
||||
flow, ok := ct_exec_statements(state, statement.body, true, depth+1)
|
||||
return ct_flow(.Normal), ok && (flow.kind == .Yield || flow.kind == .Normal)
|
||||
flow, ok := ct_exec_value_source(
|
||||
state, statement.body, statement.label, statement.value_control_flow, false, depth+1,
|
||||
)
|
||||
if ok && flow.kind == .Yield {
|
||||
return ct_flow(.Normal), true
|
||||
}
|
||||
return flow, ok
|
||||
}
|
||||
_, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
||||
return flow, ok
|
||||
@@ -4359,7 +4786,9 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
|
||||
flow := ct_flow(.Normal)
|
||||
ok := true
|
||||
if statement.expr == ast.INVALID_EXPR {
|
||||
flow, ok = ct_exec_statements(state, statement.body, true, depth+1)
|
||||
flow, ok = ct_exec_value_source(
|
||||
state, statement.body, statement.label, statement.value_control_flow, false, depth+1,
|
||||
)
|
||||
if ok && flow.kind == .Yield {
|
||||
value = flow.value
|
||||
flow = ct_flow(.Normal)
|
||||
@@ -4411,6 +4840,12 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
|
||||
|
||||
ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) {
|
||||
checker := state.checker
|
||||
if yield_returns && statement.else_body == nil {
|
||||
return ct_flow(.Normal), ct_fail(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"an 'if' used as a value must have an 'else' so every path yields",
|
||||
)
|
||||
}
|
||||
if len(statement.captures) == 0 {
|
||||
condition, flow, ok := ct_eval_expr(state, statement.expr, types.BOOL, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
@@ -4421,7 +4856,13 @@ ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, d
|
||||
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' condition must be a bool")
|
||||
}
|
||||
body := statement.body if value else statement.else_body
|
||||
return ct_exec_statements(state, body, yield_returns, depth+1)
|
||||
if yield_returns {
|
||||
if !value && len(body) == 1 && checker.ast_module.statements[body[0]].kind == .If {
|
||||
return ct_exec_if(state, checker.ast_module.statements[body[0]], true, depth+1)
|
||||
}
|
||||
return ct_exec_value_branch(state, body, depth+1)
|
||||
}
|
||||
return ct_exec_statements(state, body, false, depth+1)
|
||||
}
|
||||
operands: [dynamic]ast.Expr_Id
|
||||
operands.allocator = checker.allocator
|
||||
@@ -4466,7 +4907,17 @@ ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, d
|
||||
matched = guard_value
|
||||
}
|
||||
body := statement.body if matched else statement.else_body
|
||||
flow, ok := ct_exec_statements(state, body, yield_returns, depth+1)
|
||||
flow := ct_flow(.Normal)
|
||||
ok := false
|
||||
if yield_returns {
|
||||
if !matched && len(body) == 1 && checker.ast_module.statements[body[0]].kind == .If {
|
||||
flow, ok = ct_exec_if(state, checker.ast_module.statements[body[0]], true, depth+1)
|
||||
} else {
|
||||
flow, ok = ct_exec_value_branch(state, body, depth+1)
|
||||
}
|
||||
} else {
|
||||
flow, ok = ct_exec_statements(state, body, false, depth+1)
|
||||
}
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return flow, ok
|
||||
}
|
||||
@@ -4918,16 +5369,13 @@ ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool
|
||||
selection.tag != INVALID_CT_VALUE {
|
||||
ct_bind_value(state, arm.captures[1], state.values[selection.tag].type, selection.tag, false)
|
||||
}
|
||||
if yield_returns && len(arm.body) == 1 && checker.ast_module.statements[arm.body[0]].kind == .Expression {
|
||||
expr_stmt := checker.ast_module.statements[arm.body[0]]
|
||||
value, expr_flow, expr_ok := ct_eval_expr(state, expr_stmt.expr, types.INVALID, depth+1)
|
||||
ct_pop_bindings(state, scope_start)
|
||||
if !expr_ok || expr_flow.kind != .Normal {
|
||||
return expr_flow, expr_ok
|
||||
}
|
||||
return ct_flow(.Yield, value), true
|
||||
arm_flow := ct_flow(.Normal)
|
||||
arm_ok := false
|
||||
if yield_returns {
|
||||
arm_flow, arm_ok = ct_exec_value_branch(state, arm.body, depth+1)
|
||||
} else {
|
||||
arm_flow, arm_ok = ct_exec_statements(state, arm.body, false, depth+1)
|
||||
}
|
||||
arm_flow, arm_ok := ct_exec_statements(state, arm.body, yield_returns, depth+1)
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return arm_flow, arm_ok
|
||||
}
|
||||
@@ -5049,7 +5497,13 @@ eval_comptime_statements :: proc(
|
||||
) -> (Constant, bool, bool) {
|
||||
state := ct_state_make(checker, pkg, file, types.INVALID, values, diagnose=false)
|
||||
defer ct_state_destroy(&state)
|
||||
flow, ok := ct_exec_statements(&state, statements, yield_returns, depth)
|
||||
flow := ct_flow(.Normal)
|
||||
ok := false
|
||||
if yield_returns {
|
||||
flow, ok = ct_exec_value_source(&state, statements, symbol.INVALID, false, false, depth)
|
||||
} else {
|
||||
flow, ok = ct_exec_statements(&state, statements, false, depth)
|
||||
}
|
||||
if !ok {
|
||||
#partial switch state.error {
|
||||
case .Overflow:
|
||||
@@ -5120,7 +5574,7 @@ infer_comptime_expr_type :: proc(
|
||||
if expr.left != ast.INVALID_EXPR {
|
||||
value, flow, ok = ct_eval_expr(&state, expr.left, types.INVALID)
|
||||
} else {
|
||||
flow, ok = ct_exec_statements(&state, expr.body, true)
|
||||
flow, ok = ct_exec_value_source(&state, expr.body, symbol.INVALID, false, false, 0)
|
||||
if ok && flow.kind == .Yield {
|
||||
value = flow.value
|
||||
flow = ct_flow(.Normal)
|
||||
@@ -5156,7 +5610,7 @@ build_comptime_expr :: proc(
|
||||
if expr.left != ast.INVALID_EXPR {
|
||||
value, flow, ok = ct_eval_expr(&state, expr.left, expected)
|
||||
} else {
|
||||
flow, ok = ct_exec_statements(&state, expr.body, true)
|
||||
flow, ok = ct_exec_value_source(&state, expr.body, symbol.INVALID, false, false, 0)
|
||||
if ok && flow.kind == .Yield {
|
||||
value = flow.value
|
||||
flow = ct_flow(.Normal)
|
||||
|
||||
+59
-1
@@ -5064,7 +5064,7 @@ main func() void {
|
||||
found_expired = found_expired || strings.contains(message, "expired storage")
|
||||
found_quota = found_quota || strings.contains(message, "comptime evaluation exceeded the step quota")
|
||||
found_missing = found_missing || strings.contains(message, "did not return a value")
|
||||
found_yield = found_yield || strings.contains(message, "comptime block must yield a value")
|
||||
found_yield = found_yield || strings.contains(message, "a value block must end with an explicit 'yield'")
|
||||
}
|
||||
testing.expect(t, found_runtime)
|
||||
testing.expect(t, runtime_only_count >= 2)
|
||||
@@ -7428,6 +7428,64 @@ yield_misuse_is_diagnosed :: proc(t: ^testing.T) {
|
||||
testing.expect(t, misplaced_yield)
|
||||
}
|
||||
|
||||
@(test)
|
||||
comptime_yield_targets_match_value_source_semantics :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-comptime-yield-targets"
|
||||
main_path := "/tmp/brolang-test-comptime-yield-targets/main.bro"
|
||||
output := "/tmp/brolang-test-comptime-yield-targets-output"
|
||||
valid_text := `selected :: ${
|
||||
via_if :: if true {
|
||||
yield 1
|
||||
} else {
|
||||
yield 2
|
||||
}
|
||||
via_label :: done: {
|
||||
if true {
|
||||
yield :done 3
|
||||
}
|
||||
yield :done 4
|
||||
}
|
||||
yield via_if + via_label
|
||||
}
|
||||
|
||||
main func() i32 { return selected - 4 }
|
||||
`
|
||||
_ = os2.remove_all(directory)
|
||||
defer _ = os2.remove_all(directory)
|
||||
defer _ = os.remove(output)
|
||||
testing.expect(t, os.make_directory(directory) == nil)
|
||||
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)valid_text))
|
||||
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
|
||||
invalid_text := `invalid :: ${
|
||||
if false {
|
||||
yield 1
|
||||
}
|
||||
yield 2
|
||||
}
|
||||
|
||||
main func() void {}
|
||||
`
|
||||
source_file := source.Source{path="invalid_comptime_yield.bro", text=invalid_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)
|
||||
found := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || strings.contains(diagnostic.message, "'yield' is only valid as the final statement of a value block")
|
||||
}
|
||||
testing.expect(t, found)
|
||||
}
|
||||
|
||||
@(test)
|
||||
yield_control_flow_is_diagnosed :: proc(t: ^testing.T) {
|
||||
// Value if/loop/block misuse: an `if` value without an `else`; a branch that neither
|
||||
|
||||
@@ -170,10 +170,8 @@ ct_errdefer_check func() i32 {
|
||||
}
|
||||
|
||||
recover func() i32 {
|
||||
return may_fail(true) catch |e| {
|
||||
match e {
|
||||
.bad: yield 5
|
||||
}
|
||||
return may_fail(true) catch |e| match e {
|
||||
.bad: 5
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,48 +35,51 @@ init func($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) {
|
||||
values[i] = entry.1
|
||||
}
|
||||
|
||||
if N == 0 {
|
||||
len_indexes [0]mut u32 = undefined
|
||||
yield StaticStringMap(V) {
|
||||
result :: done: {
|
||||
if N == 0 {
|
||||
len_indexes [0]mut u32 = undefined
|
||||
yield :done StaticStringMap(V) {
|
||||
keys = keys[..],
|
||||
values = values[..],
|
||||
len_indexes = len_indexes[..],
|
||||
min_len = 0,
|
||||
max_len = 0,
|
||||
}
|
||||
}
|
||||
|
||||
# ponytail: insertion sort is compile-time O(N²); replace if large maps affect builds.
|
||||
i usize = 1
|
||||
while i < N : i += 1 {
|
||||
key :: keys[i]
|
||||
value :: values[i]
|
||||
j usize = i
|
||||
while j > 0 and keys[j - 1].len > key.len : j -= 1 {
|
||||
keys[j] = keys[j - 1]
|
||||
values[j] = values[j - 1]
|
||||
}
|
||||
keys[j] = key
|
||||
values[j] = value
|
||||
}
|
||||
|
||||
min_len u32 :: u32(keys[0].len)
|
||||
max_len u32 :: u32(keys[N - 1].len)
|
||||
len_indexes [usize(max_len) + 1]mut u32 = undefined
|
||||
entry_index usize = 0
|
||||
length usize = 0
|
||||
while length <= usize(max_len) : length += 1 {
|
||||
while entry_index < N and keys[entry_index].len < length : entry_index += 1 {}
|
||||
len_indexes[length] = u32(entry_index)
|
||||
}
|
||||
|
||||
yield :done StaticStringMap(V) {
|
||||
keys = keys[..],
|
||||
values = values[..],
|
||||
len_indexes = len_indexes[..],
|
||||
min_len = 0,
|
||||
max_len = 0,
|
||||
min_len = min_len,
|
||||
max_len = max_len,
|
||||
}
|
||||
}
|
||||
|
||||
# ponytail: insertion sort is compile-time O(N²); replace if large maps affect builds.
|
||||
i usize = 1
|
||||
while i < N : i += 1 {
|
||||
key :: keys[i]
|
||||
value :: values[i]
|
||||
j usize = i
|
||||
while j > 0 and keys[j - 1].len > key.len : j -= 1 {
|
||||
keys[j] = keys[j - 1]
|
||||
values[j] = values[j - 1]
|
||||
}
|
||||
keys[j] = key
|
||||
values[j] = value
|
||||
}
|
||||
|
||||
min_len u32 :: u32(keys[0].len)
|
||||
max_len u32 :: u32(keys[N - 1].len)
|
||||
len_indexes [usize(max_len) + 1]mut u32 = undefined
|
||||
entry_index usize = 0
|
||||
length usize = 0
|
||||
while length <= usize(max_len) : length += 1 {
|
||||
while entry_index < N and keys[entry_index].len < length : entry_index += 1 {}
|
||||
len_indexes[length] = u32(entry_index)
|
||||
}
|
||||
|
||||
yield StaticStringMap(V) {
|
||||
keys = keys[..],
|
||||
values = values[..],
|
||||
len_indexes = len_indexes[..],
|
||||
min_len = min_len,
|
||||
max_len = max_len,
|
||||
}
|
||||
yield result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user