block scoping¶
a variable declared with let or var inside a block belongs to that block. after the
block ends, the name is gone:
python has no block scopes — a name bound anywhere in a function is a local of the whole function — and the python this lowers to keeps it that way. so this is a rule the checker enforces, not a change to what the emitted code does. the transpiled output is the same either way:
a block is a clause, not a statement¶
each clause of a compound statement is its own block: an if body and its else, a
loop body and its else, every except and finally, every match case
that holds even when every branch declares the name — two declarations of a are two
variables, and neither outlives its own branch:
def f(flag: bool) -> int:
if flag:
let a = 1
else:
let a = 2
return a # error: `a` is not in scope here
to carry a value out of a branch, declare it where it is used:
a scope's own body is not a block, so a declaration at the top of a function or a module is visible for the rest of it, and a class body declares members as it always did
only the binding keyword scopes a name¶
a plain assignment binds for the whole enclosing function or module, exactly as it does in python:
leaving a block early leaves it¶
a break, a continue or a raised exception leaves the block from the middle, and
takes its declarations with it:
a loop body declares again on each iteration¶
the body is a block, so what it declares is gone before the next iteration starts. a
let assigned once in the body is assigned once per declaration, which is what a
read-only variable allows:
a declaration written outside the loop is a single one, so the same assignment reaches
itself on the next iteration and is reported as invalid-assignment:
def f(lines: list[str]):
let width
for line in lines:
width = len(line) # error: read-only symbol `width` cannot be reassigned
turning it off¶
block scoping is on by default. analysis.block-scoped-declarations turns it off, and a
declaration then binds for the whole enclosing scope, as a plain assignment does:
requiring a declaration¶
the implicit-declaration rule asks that every variable a scope binds be declared once —
let for a binding that never changes, var for one that does. it is off by default:
declaring it once answers both:
a class body is left alone: x: int there declares a field, which is how a
data class or a protocol is written. so is an assignment to anything but
a plain name — an attribute, a subscript, an item of an unpacking — and so is a .byi
stub, which says what a module has rather than what it does
see modifiers and visibility for what let and var declare