statement expressions¶
a compound statement can stand where a value is expected. the value is whatever its branches evaluate last:
each branch's value is the expression it ends on — no return, no assignment
repeated per arm. the type is the union of the branch values, so direction
above is Literal[1, -1, 0]
the forms that carry a suite are if, match, for, while and try.
raise, return, break and continue are also expressions; they never
produce a value, so they type as Never
if¶
an if expression needs an else: without one the statement can finish having
evaluated nothing, and there is no value to stand in. that is reported as
non-exhaustive-statement-expression
the branches are ordinary suites, so they can do work before their value:
match¶
the same exhaustiveness rule applies, and it is the type checker's ordinary
one: the arms have to cover the subject's type. a case _: always suffices
for and while¶
a loop yields a value through break:
break <value> leaves the loop with that value. the else clause — which runs
when the loop finishes without breaking — supplies the value for that path, so
a loop expression without else is non-exhaustive
a break inside a nested loop belongs to that loop, exactly as it does in a
statement
a break may only carry a value where something reads it — in a loop that is
not a statement expression there is nowhere for the value to go, and it is
rejected
try¶
the try block's value is what it evaluates last, and each handler supplies
the value for the exception it catches:
an else clause runs once the try block has completed, so it is the value on
that path and the block's own last expression is not:
a finally clause runs on the way out of every path, including the ones that
carry an exception past the statement, so what it evaluates last is never the
value
a handler that ends on something other than an expression — or on nothing at
all — leaves a path that produces no value, which is
non-exhaustive-statement-expression. a handler that raises is fine: it never
completes, so it owes no value
the diverging forms¶
raise, return, break and continue are all Never, so each can stand in
for a value that will never be produced:
def lookup(table: dict[str, int], key: str) -> int:
return table.get(key) ?? raise KeyError(key)
def parse_port(raw: str?) -> int:
text = raw ?? return 0
return int(text)
because they are Never, the surrounding expression's type is just the other
branch's — table.get(key) ?? raise ... is int, not int | None
break and continue bring that to a loop body, where the value that went
missing is a reason to move on rather than to fail:
def total(rows: list[str]) -> int:
sum = 0
for row in rows:
amount = parse(row) ?? continue
sum += amount
return sum
nesting¶
a branch that ends on another compound statement takes its value, so the inner form does not have to be spelled as an expression:
where they can appear¶
a statement expression owns the tail of the statement it appears in. a form with a suite must be the whole value of its statement, and must be the first statement on its line — its suite continues that line, so nothing may precede it there:
a = match x: # ok
case _:
1
a = 1 + match x: # error: must be the whole value of its statement
case _:
1
the diverging forms carry no suite, so they may also appear inside the
operators that choose between operands — and, or, ??, the conditional
expression, and the walrus:
value = maybe() ?? raise Missing()
value = cached or raise Missing()
value = x if x > 0 else raise ValueError(x)
value = (found := lookup() ?? raise Missing())
value = maybe() ?? continue
anywhere else — as a call argument, inside a list, as an operand of +, in a
compound statement's header — the surrounding expression would have to be
evaluated around the statement, and is rejected. a rejected form with a suite
is dropped along with what it wrapped, so the rest of the file still checks
lowering¶
a statement expression lowers to the statement it always was, with the assignment moved below it:
match command:
case "up":
__by_stmt_expr_0__ = 1
case _:
__by_stmt_expr_0__ = 0
a = __by_stmt_expr_0__
break <value> becomes an assignment followed by a bare break. a diverging
statement expression under a choosing operator becomes the guard it implies: