favor return over return _ (void return); newline/closing terminates

This commit is contained in:
2026-07-14 19:07:37 +02:00
parent eac5b32738
commit 471896b48a
19 changed files with 92266 additions and 94923 deletions
+32 -3
View File
@@ -7516,7 +7516,7 @@ build_block :: proc(
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
})
} else if !types.is_void(ctx.result) {
id := source.add(checker.diagnostics, statement.span, "'return _' is only valid in a void function")
id := source.add(checker.diagnostics, statement.span, "non-void function must return a value")
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
@@ -8118,6 +8118,16 @@ build_block :: proc(
} else {
yielded = build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, target.slot_type, ctx.pkg, ctx.file)
}
if yielded != hir.INVALID_EXPR && types.is_void(checker.module.exprs[yielded].type) {
id := source.add(checker.diagnostics, statement.span, "'yield' expression must produce a non-void value")
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
continue
}
yielded = resolve_loop_slot(ctx, target, yielded, checker.module.exprs[yielded].type if yielded != hir.INVALID_EXPR else types.INVALID, statement.span)
if target.slot == hir.INVALID_LOCAL || yielded == hir.INVALID_EXPR {
id := source.add(checker.diagnostics, statement.span,
@@ -8291,9 +8301,19 @@ build_value_block :: proc(
checker, yield_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
expected, ctx.pkg, ctx.file,
)
value_type = checker.module.exprs[value].type
}
value_type = checker.module.exprs[value].type
if is_runtime_type(checker, expected) {
if types.is_void(value_type) {
id := source.add(checker.diagnostics, yield_stmt.span, "'yield' expression must produce a non-void value")
value = invalid_hir_expr(checker, yield_stmt.span, id)
value_type = types.INVALID
ctx.problematic^ = true
} else if types.is_void(expected) {
id := source.add(checker.diagnostics, yield_stmt.span, "void value context must fall through instead of yielding")
value = invalid_hir_expr(checker, yield_stmt.span, id)
value_type = types.INVALID
ctx.problematic^ = true
} else if is_runtime_type(checker, expected) {
value = coerce_expr(checker, value, expected, yield_stmt.span)
value_type = checker.module.exprs[value].type
}
@@ -9521,6 +9541,15 @@ build_value_loop :: proc(
// The fall-through value initializes the slot before the loop (loop captures are
// out of scope here), so the loop completing leaves it as the result.
fall_value := build_expr(checker, fall_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, target.slot_type, ctx.pkg, ctx.file)
if fall_value != hir.INVALID_EXPR && types.is_void(checker.module.exprs[fall_value].type) {
for s in loop_block {
append(body, s)
}
delete(loop_block, checker.allocator)
id := source.add(checker.diagnostics, fall_stmt.span, "'yield' expression must produce a non-void value")
ctx.problematic^ = true
return invalid_hir_expr(checker, fall_stmt.span, id), types.INVALID
}
fall_value = resolve_loop_slot(ctx, &target, fall_value, checker.module.exprs[fall_value].type if fall_value != hir.INVALID_EXPR else types.INVALID, fall_stmt.span)
if target.slot == hir.INVALID_LOCAL || fall_value == hir.INVALID_EXPR {
for s in loop_block {
+8 -1
View File
@@ -2463,12 +2463,19 @@ ct_exec_statements :: proc(
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")
}
} 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 {
flow = ct_flow(.Yield, value, statement.label)
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:
+17 -10
View File
@@ -1282,14 +1282,11 @@ finish_statement :: proc(parser: ^Parser, allow_closing_brace := false) -> sourc
parse_return :: proc(parser: ^Parser) -> ast.Stmt_Id {
start := advance(parser)
skip_newlines(parser)
if current(parser).kind == .Underscore {
end := advance(parser)
if current(parser).kind == .Newline || current(parser).kind == .Right_Brace || current(parser).kind == .Eof {
id := ast.stmt_id(len(parser.module.statements))
append(&parser.module.statements, ast.Stmt{
kind=.Return,
span=span_from(start.span, end.span),
name=end.symbol,
span=start.span,
expr=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
@@ -1321,12 +1318,10 @@ parse_return :: proc(parser: ^Parser) -> ast.Stmt_Id {
return id
}
// `yield <expr>` supplies the value of the enclosing value block. The checker
// only accepts it as the final statement of a value block (a `{ ... }` on the
// right of a declaration/assignment); it is the block analogue of `return`.
// `yield <expr>` supplies a non-void value to an enclosing value construct.
// The expression must start on the same line; `break` handles valueless exits.
parse_yield :: proc(parser: ^Parser) -> ast.Stmt_Id {
start := advance(parser) // consume 'yield'
skip_newlines(parser)
// `yield :blk x` targets the loop labeled `blk`; a bare `yield x` targets
// the directly-enclosing value block / if branch. No expression starts with
// ':', so a leading colon is unambiguously a label.
@@ -1335,9 +1330,21 @@ parse_yield :: proc(parser: ^Parser) -> ast.Stmt_Id {
if name, name_ok := allow(parser, .Identifier); name_ok {
label = name.symbol
} else {
source.add(parser.diagnostics, current(parser).span, "expected a loop label after ':'")
source.add(parser.diagnostics, current(parser).span, "expected a yield target label after ':'")
}
}
if current(parser).kind == .Newline || current(parser).kind == .Right_Brace || current(parser).kind == .Eof {
expr := invalid_expr(parser, start.span, "'yield' must produce a value")
id := ast.stmt_id(len(parser.module.statements))
append(&parser.module.statements, ast.Stmt{
kind=.Yield,
span=start.span,
label=label,
expr=expr,
diagnostic=source.INVALID_DIAGNOSTIC,
})
return id
}
if cf, is_cf := parse_value_control_flow(parser); is_cf {
cf_span := parser.module.statements[cf].span
body := make([]ast.Stmt_Id, 1, parser.module.allocator)