Blocks and Statements¶
Accepted
Accepted for V1 block values, divergence, and statement separation.
Blocks¶
Blocks can appear anywhere an expression can appear.
In expression position, a block's value comes from its last statement:
var x = {
compute()
}
For expression statements, that means the block value is the expression value. A block with no statements evaluates to void:
var x = {}
If a block should produce a local binding's value, write that binding as the final expression:
var x = {
const computed = compute()
computed
}
In V1, a block ending in a local declaration completes as void:
var x = {
const computed = compute()
void
}
void is the empty value/result. return is syntactic sugar for return void.
More precisely, statements have completion types:
- expression statements complete with the expression's type;
- local declaration statements complete as
voidin V1; return exprcompletes asnever;- a non-empty block completes as its last statement;
- an empty block completes as
void.
Source syntax still distinguishes statements from expressions. Declarations are statements, not expressions.
Discarding Values¶
Non-final statement values may be discarded by sequencing. This is legal, but producing a meaningful value only to discard it is lintable.
Function calls are the common case:
fn run() void {
checksum(bytes)
void
}
Use _ = expr when discarding a produced value is intentional. This is an explicit discard expression: it evaluates the right-hand side, discards the produced value, and completes as void.
fn run() void {
_ = checksum(bytes)
}
Function body completion is not sequencing discard. The final statement of a function body is checked against the function's return type:
fn run() void {
checksum(bytes) // error: final completion is not void
}
Use an explicit discard expression when a value-returning call is the final function body statement and is used only for its effects:
fn run() void {
_ = checksum(bytes)
}
The compiler lint model owns exact severities and suppression behavior for discarded meaningful values in non-final statement positions.
Divergence¶
return expr is a diverging expression. It has type never and is compatible with any expected branch type because control does not continue locally.
return is equivalent to:
return void
break and continue are diverging expressions inside loops. break exits the nearest enclosing loop. continue starts the next iteration of the nearest enclosing loop. Both trigger scope-exit cleanup, including pending defer statements and compiler-owned loop temporaries.
V1 does not have labeled break or continue, break values, or value-producing loops.
Statement Separation¶
The lexer preserves newlines, but it does not classify braces or synthesize statement separators. Newline significance is parser-contextual.
In a statement-list context, newlines and ; are statement separators. A newline separates statements only at grammar points where the parser can accept the end of the current statement. Otherwise, newline is ordinary whitespace inside the construct being parsed.
var a = first_value
var b = second_value
; is only for separating two statements on one line:
do_a(); do_b()
Lists that use commas still require commas. Newlines are not comma substitutes in parameter lists, argument lists, array literals, aggregate literals, enum cases, contract operation lists, or similar list-like constructs.
fn f(
x: i32,
y: i32,
) void {
}
fn f(
x: i32
y: i32
) void {
}
Statements may continue across a newline only when the grammar is incomplete at that point:
const x = first +
second
If a newline appears where the parser can accept the end of the statement, the statement ends:
const x = first
+ second
V1 uses a same-line rule for trailing continuation keywords such as else and catch when the preceding expression or block could otherwise end at the newline:
const x = if ready {
1
} else {
2
}
const x = if ready {
1
}
else {
2
}
Likewise, catch must remain on the same logical statement as the expression it handles:
var bytes = read(path) catch {
empty_bytes
}
var bytes = read(path)
catch {
empty_bytes
}
Allowing trailing continuation keywords across a statement-separating newline is deferred to CEP-0060: Newline-Permissive Trailing Continuations.
Function and operation bodies attach only when the body opener stays on the same logical line as the signature. A newline that can end the signature also ends the declaration header, so a following { ... } or => expr does not attach as the body. Wrapped signatures may break inside incomplete syntax such as a parameter list or return type, but the body opener itself belongs with the completed signature line.
contract {
fn len(self: *const Self) usize // bodyless required operation
fn is_empty(self: *const Self) bool {
return self.len() == 0
}
}
A signature is bodyless when no body opener is attached on the same logical line. Bodyless fn is valid only for contract required operations and deferred @extern; a bodyless fn where a body is required is an error suggesting a { ... } or => expr body. This declaration-position rule matches the same-line rule for statement-position else and catch.
Trailing empty statements are trimmed. This means:
var x = {
compute();
}
still produces the value of compute(), but should produce a lint warning because the semicolon is unnecessary or misleading.
To explicitly produce void, end with void or use an empty block:
var x = {
compute();
void
}