Description
Typically mypy infers the type of the variable from the first assignment. In some cases mypy infers the type from two assignments. The first assignment creates internally a partial type, which is missing some information. The second assignment completes the type. Example:
x = [] # x gets a partial List type
x.append(1) # x gets complete type List[int]
In some cases the assignments can be in two different scopes. Example:
x = []
def f() -> None:
x.append(1)
# type of x is List[int]
This feature has apparently been supported a while, but it has some issues:
- This behavior is somewhat messy to support in fine-grained incremental mode (Refresh of partial type produces the wrong type in fine-grained incremental mode #4464).
- It can make it hard to figure out the inferred type of a variable, since the two assignments can be separated from each other by hundreds of lines and many functions.
- Moving functions around can affect the type of a module/class variable in cases where there are two potential functions that can complete a partial type.
- If the second assignment is missing or removed, and the first assignment initializes the variable with
None
, the inferred type will becomeNone
, which is usually not what the user wanted. - It seems a bit fragile with some issues that took a long time to notice (Inconsistent types for variable with partial type #4484, Inference from unannotated functions #4455, Invalid type inferred for attribute initialized to
None
in class body #4416).
There are a few alternatives which might be reasonable, instead of the current behavior:
- Require an annotation for the variable in cases such as the above, where the assignment that completes a partial type is in a different scope. This would break compatibility with previous mypy releases. Based on a quick experiment, the S internal codebase at Dropbox would require about 40 additional type annotations if we make this change.
- Ignore assignments in nested scopes and assume
Any
for any missing partial type information. For example, if the initial assignment isx = []
and the only additional assignments are in nested scopes, inferList[Any]
as the type ofx
. This way the type ofx
is defined entirely by the contents of a single scope, which solves the above problems. This also introduces an additional problem -- implicitAny
types. We might want to have a strictness flag that falls back to requiring a type annotation (as in option 1 above).
Currently my biggest concern is fine-grained incremental mode. There it may be possible to support the current behavior by introducing a new concept: "bound targets". I'll add a writeup about this to #4464.
@gvanrossum @msullivan @ilevkivskyi Thoughts?