Description
Feature
Determining the actual value of a variable (or an expression) if it can be determined to be constant.
Pitch
mypy tries to determine the type of an expression already, and if it involves typed functions or known operators using values of known type, then it can infer the type of this function or operation. And so on.
There are some cases in which it would be useful for mypy to determine the actual value, not just the type, when the value can be determined statically.
def foo(x: Literal[7]): pass
var: Final = 3 + 4
foo(var)
should pass type checking. Better yet, the inferred type of var
could be Literal[7]
instead of int
.
Even if var
is not Final, mypy would recognize that its value is 7, until possibly reassigned later in the same scope.
I think that mypy could evaluate an arbitrary expression safely by compiling it and executing the compiled code, under limited conditions. All names in the expression must have a known value, or be builtin functions. Thingsl ike n += 1
inside a loop can't be evaluated. No user-defined functions or classes. Any value returned from the compiled code (if no exception is raised) would be deemed to be the fixed value.
The known value could include tuples, lists, etc. You might ask why these would be useful, since they cannot be used in a Literal type. However, such a collection might be subscripted, as in ['a', 3][1]
having a constant value of 3 which can match a Literal[3] type.
More important is the value of __all__
when it is not an actual list or tuple expression. The user could have some value like "a b".split()
, or list1 + list2
; or have a statement like __all__.append("c")
. Generally, if __all__
has a value at the end of the module code that is some fixed Iterable[str]
then mypy should use this to determine the exported names from the module. This would be a solution for #12582, and I won't have to convert my split()
s to lists in order to pass type checking.
As a speedup, the analysis of fixed value of a variable could be postponed until the variable is actually used where a Literal type is expected, or if the module is used in import *
in a different module. In the latter case, the names mentioned in __all__
are in the globals already, but the public/private status of each global name only matters for purposes of import *
.