Currently, we have a general strategy for name lookups in enclosing scopes: they use the type as seen from end of the scope where it's defined. For example:
x = 1
def f():
# This is correct for any call to `f` that would happen after this module has
# fully executed. It could be wrong if `f` is called during execution of the
# module body.
reveal_type(x) # revealed: Literal["foo"]
x = "foo"
For nested functions, this is a pretty reasonable default behavior. Another option would be to consider all definitions of x (or at least all definitions of x visible from the point where f is defined, forwards). This would result in revealing Literal[1] | Literal["foo"] for x in the inner scope of f, instead. This would be safer, but also more annoying for the more common case where f is not called from the module body. It would also mean we would always consider x possibly-undefined inside f, if f is defined before x, which could be really annoying.
We could try to be smarter by detecting, for example, that the function f is (or might be) called from module level, and only consider earlier definitions if we observe this possibility. But this will always be best-effort, since reliably detecting all possible paths (other functions, even possibly in other modules) through which f could be called from module level isn't feasible.
But there are cases where we can be quite sure that the nested scope will run before the end of the outer scope. For example, class definitions:
x = 1
class C:
y = x
x = "foo"
# This is currently what we reveal, but it's clearly wrong: should be `Literal[1]`
reveal_type(C.y) # revealed: Literal["foo"]
The class body always runs immediately, so it should resolve types in the enclosing scope from the class definition's point in control flow, not from end-of-scope.
Currently, we have a general strategy for name lookups in enclosing scopes: they use the type as seen from end of the scope where it's defined. For example:
For nested functions, this is a pretty reasonable default behavior. Another option would be to consider all definitions of
x(or at least all definitions ofxvisible from the point wherefis defined, forwards). This would result in revealingLiteral[1] | Literal["foo"]forxin the inner scope off, instead. This would be safer, but also more annoying for the more common case wherefis not called from the module body. It would also mean we would always considerxpossibly-undefined insidef, iffis defined beforex, which could be really annoying.We could try to be smarter by detecting, for example, that the function
fis (or might be) called from module level, and only consider earlier definitions if we observe this possibility. But this will always be best-effort, since reliably detecting all possible paths (other functions, even possibly in other modules) through whichfcould be called from module level isn't feasible.But there are cases where we can be quite sure that the nested scope will run before the end of the outer scope. For example, class definitions:
The class body always runs immediately, so it should resolve types in the enclosing scope from the class definition's point in control flow, not from end-of-scope.