From 384f32cef8866b452390cccdf2a2f1f9431c5ee3 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Fri, 15 Nov 2019 09:02:59 -0800 Subject: [PATCH] Make revealed type of Final vars distinct from non-Final vars (#7955) This diff changes how we format Instances with a last known value when displaying them with `reveal_type`. Previously, we would always ignore the `last_known_value` field: ```python x: Final = 3 reveal_type(x) # N: Revealed type is 'builtins.int' ``` Now, we format it like `Literal[3]?`. Note that we use the question mark suffix as a way of distinguishing the type from true Literal types. ```python x: Final = 3 y: Literal[3] = 3 reveal_type(x) # N: Revealed type is 'Literal[3]?' reveal_type(y) # N: Revealed type is 'Literal[3]' ``` While making this change and auditing our tests, I also discovered we were accidentally copying over the `last_known_value` in a few places by accident. For example: ```python from typing_extensions import Final a = [] a.append(1) a.append(2) # Got no error here? reveal_type(a) # Incorrect revealed type: got builtins.list[Literal[1]?] b = [0, None] b.append(1) # Got no error here? reveal_type(b) # Incorrect revealed type: got builtins.list[Union[Literal[0]?, None]] ``` The other code changes I made were largely cosmetic. Similarly, most of the remaining test changes were just due to places where we were doing something like `reveal_type(0)` or `reveal_type(SomeEnum.BLAH)`. The main motivation behind this diff is that once this lands, it should become much simpler for me to write some tests I'll need while revamping https://github.com/python/mypy/pull/7169. It also helps make a somewhat confusing and implicit part of mypy internals more visible. --- docs/source/literal_types.rst | 42 +++++++++--- mypy/checker.py | 1 + mypy/checkexpr.py | 15 +++-- mypy/checkmember.py | 7 +- mypy/erasetype.py | 9 ++- mypy/types.py | 11 +++- test-data/unit/check-columns.test | 2 +- test-data/unit/check-enum.test | 66 +++++++++---------- test-data/unit/check-errorcodes.test | 2 +- test-data/unit/check-expressions.test | 6 +- test-data/unit/check-inference-context.test | 6 +- test-data/unit/check-literal.test | 71 +++++++++++++++------ test-data/unit/check-newsemanal.test | 4 +- test-data/unit/check-optional.test | 2 +- test-data/unit/check-python38.test | 8 ++- test-data/unit/check-typeddict.test | 4 +- test-data/unit/check-unreachable-code.test | 12 ++-- test-data/unit/check-varargs.test | 4 +- test-data/unit/merge.test | 8 +-- test-data/unit/typexport-basic.test | 18 +++--- 20 files changed, 187 insertions(+), 111 deletions(-) diff --git a/docs/source/literal_types.rst b/docs/source/literal_types.rst index d85bc24a02ff..707574752018 100644 --- a/docs/source/literal_types.rst +++ b/docs/source/literal_types.rst @@ -121,13 +121,16 @@ you can instead change the variable to be ``Final`` (see :ref:`final_attrs`): c: Final = 19 - reveal_type(c) # Revealed type is 'int' - expects_literal(c) # ...but this type checks! + reveal_type(c) # Revealed type is 'Literal[19]?' + expects_literal(c) # ...and this type checks! If you do not provide an explicit type in the ``Final``, the type of ``c`` becomes -context-sensitive: mypy will basically try "substituting" the original assigned -value whenever it's used before performing type checking. So, mypy will type-check -the above program almost as if it were written like so: +*context-sensitive*: mypy will basically try "substituting" the original assigned +value whenever it's used before performing type checking. This is why the revealed +type of ``c`` is ``Literal[19]?``: the question mark at the end reflects this +context-sensitive nature. + +For example, mypy will type check the above program almost as if it were written like so: .. code-block:: python @@ -138,11 +141,32 @@ the above program almost as if it were written like so: reveal_type(19) expects_literal(19) -This is why ``expects_literal(19)`` type-checks despite the fact that ``reveal_type(c)`` -reports ``int``. +This means that while changing a variable to be ``Final`` is not quite the same thing +as adding an explicit ``Literal[...]`` annotation, it often leads to the same effect +in practice. + +The main cases where the behavior of context-sensitive vs true literal types differ are +when you try using those types in places that are not explicitly expecting a ``Literal[...]``. +For example, compare and contrast what happens when you try appending these types to a list: + +.. code-block:: python + + from typing_extensions import Final, Literal + + a: Final = 19 + b: Literal[19] = 19 + + # Mypy will chose to infer List[int] here. + list_of_ints = [] + list_of_ints.append(a) + reveal_type(list_of_ints) # Revealed type is 'List[int]' + + # But if the variable you're appending is an explicit Literal, mypy + # will infer List[Literal[19]]. + list_of_lits = [] + list_of_lits.append(b) + reveal_type(list_of_lits) # Revealed type is 'List[Literal[19]]' -So while changing a variable to be ``Final`` is not quite the same thing as adding -an explicit ``Literal[...]`` annotation, it often leads to the same effect in practice. Limitations *********** diff --git a/mypy/checker.py b/mypy/checker.py index 7c7913fbeeb9..0387ffce8549 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4099,6 +4099,7 @@ def named_generic_type(self, name: str, args: List[Type]) -> Instance: the name refers to a compatible generic type. """ info = self.lookup_typeinfo(name) + args = [remove_instance_last_known_values(arg) for arg in args] # TODO: assert len(args) == len(info.defn.type_vars) return Instance(info, args) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 8a0df916f5a9..72da7663ce4a 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -39,7 +39,7 @@ import mypy.checker from mypy import types from mypy.sametypes import is_same_type -from mypy.erasetype import replace_meta_vars, erase_type +from mypy.erasetype import replace_meta_vars, erase_type, remove_instance_last_known_values from mypy.maptype import map_instance_to_supertype from mypy.messages import MessageBuilder from mypy import message_registry @@ -3045,12 +3045,13 @@ def check_lst_expr(self, items: List[Expression], fullname: str, self.named_type('builtins.function'), name=tag, variables=[tvdef]) - return self.check_call(constructor, - [(i.expr if isinstance(i, StarExpr) else i) - for i in items], - [(nodes.ARG_STAR if isinstance(i, StarExpr) else nodes.ARG_POS) - for i in items], - context)[0] + out = self.check_call(constructor, + [(i.expr if isinstance(i, StarExpr) else i) + for i in items], + [(nodes.ARG_STAR if isinstance(i, StarExpr) else nodes.ARG_POS) + for i in items], + context)[0] + return remove_instance_last_known_values(out) def visit_tuple_expr(self, e: TupleExpr) -> Type: """Type check a tuple expression.""" diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 86ea5051e17e..bc9aa41efeab 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -691,7 +691,12 @@ def analyze_class_attribute_access(itype: Instance, if info.is_enum and not (mx.is_lvalue or is_decorated or is_method): enum_literal = LiteralType(name, fallback=itype) - return itype.copy_modified(last_known_value=enum_literal) + # When we analyze enums, the corresponding Instance is always considered to be erased + # due to how the signature of Enum.__new__ is `(cls: Type[_T], value: object) -> _T` + # in typeshed. However, this is really more of an implementation detail of how Enums + # are typed, and we really don't want to treat every single Enum value as if it were + # from type variable substitution. So we reset the 'erased' field here. + return itype.copy_modified(erased=False, last_known_value=enum_literal) t = node.type if t: diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 55cc58798c1c..12c10648525d 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -140,9 +140,12 @@ class LastKnownValueEraser(TypeTranslator): Instance types.""" def visit_instance(self, t: Instance) -> Type: - if t.last_known_value: - return t.copy_modified(last_known_value=None) - return t + if not t.last_known_value and not t.args: + return t + return t.copy_modified( + args=[a.accept(self) for a in t.args], + last_known_value=None, + ) def visit_type_alias_type(self, t: TypeAliasType) -> Type: # Type aliases can't contain literal values, because they are diff --git a/mypy/types.py b/mypy/types.py index 3eef81a4035f..aea0a02198f7 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -830,13 +830,14 @@ def deserialize(cls, data: Union[JsonDict, str]) -> 'Instance': def copy_modified(self, *, args: Bogus[List[Type]] = _dummy, + erased: Bogus[bool] = _dummy, last_known_value: Bogus[Optional['LiteralType']] = _dummy) -> 'Instance': return Instance( self.type, args if args is not _dummy else self.args, self.line, self.column, - self.erased, + erased if erased is not _dummy else self.erased, last_known_value if last_known_value is not _dummy else self.last_known_value, ) @@ -1988,7 +1989,13 @@ def visit_deleted_type(self, t: DeletedType) -> str: return "".format(t.source) def visit_instance(self, t: Instance) -> str: - s = t.type.fullname or t.type.name or '' + if t.last_known_value and not t.args: + # Instances with a literal fallback should never be generic. If they are, + # something went wrong so we fall back to showing the full Instance repr. + s = '{}?'.format(t.last_known_value) + else: + s = t.type.fullname or t.type.name or '' + if t.erased: s += '*' if t.args != []: diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test index c77a27311638..554c4da58565 100644 --- a/test-data/unit/check-columns.test +++ b/test-data/unit/check-columns.test @@ -308,7 +308,7 @@ if int(): [case testColumnRevealedType] if int(): - reveal_type(1) # N:17: Revealed type is 'builtins.int' + reveal_type(1) # N:17: Revealed type is 'Literal[1]?' [case testColumnNonOverlappingEqualityCheck] # flags: --strict-equality diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 43355392098c..6be7a046e823 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -6,7 +6,7 @@ class Medal(Enum): gold = 1 silver = 2 bronze = 3 -reveal_type(Medal.bronze) # N: Revealed type is '__main__.Medal*' +reveal_type(Medal.bronze) # N: Revealed type is 'Literal[__main__.Medal.bronze]?' m = Medal.gold if int(): m = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Medal") @@ -20,7 +20,7 @@ class Medal(metaclass=EnumMeta): # Without __init__ the definition fails at runtime, but we want to verify that mypy # uses `enum.EnumMeta` and not `enum.Enum` as the definition of what is enum. def __init__(self, *args): pass -reveal_type(Medal.bronze) # N: Revealed type is '__main__.Medal' +reveal_type(Medal.bronze) # N: Revealed type is 'Literal[__main__.Medal.bronze]?' m = Medal.gold if int(): m = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Medal") @@ -34,7 +34,7 @@ class Medal(Achievement): bronze = None # See comment in testEnumFromEnumMetaBasics def __init__(self, *args): pass -reveal_type(Medal.bronze) # N: Revealed type is '__main__.Medal' +reveal_type(Medal.bronze) # N: Revealed type is 'Literal[__main__.Medal.bronze]?' m = Medal.gold if int(): m = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Medal") @@ -53,7 +53,7 @@ class Truth(Enum): false = False x = '' x = Truth.true.name -reveal_type(Truth.true.name) # N: Revealed type is 'builtins.str' +reveal_type(Truth.true.name) # N: Revealed type is 'Literal['true']?' reveal_type(Truth.false.value) # N: Revealed type is 'builtins.bool' [builtins fixtures/bool.pyi] @@ -246,7 +246,7 @@ class A: a = A() reveal_type(a.x) [out] -main:8: note: Revealed type is '__main__.E@4*' +main:8: note: Revealed type is '__main__.E@4' [case testEnumInClassBody] from enum import Enum @@ -270,9 +270,9 @@ reveal_type(E.bar.value) reveal_type(I.bar) reveal_type(I.baz.value) [out] -main:4: note: Revealed type is '__main__.E*' +main:4: note: Revealed type is 'Literal[__main__.E.foo]?' main:5: note: Revealed type is 'Any' -main:6: note: Revealed type is '__main__.I*' +main:6: note: Revealed type is 'Literal[__main__.I.bar]?' main:7: note: Revealed type is 'builtins.int' [case testFunctionalEnumListOfStrings] @@ -282,8 +282,8 @@ F = IntEnum('F', ['bar', 'baz']) reveal_type(E.foo) reveal_type(F.baz) [out] -main:4: note: Revealed type is '__main__.E*' -main:5: note: Revealed type is '__main__.F*' +main:4: note: Revealed type is 'Literal[__main__.E.foo]?' +main:5: note: Revealed type is 'Literal[__main__.F.baz]?' [case testFunctionalEnumListOfPairs] from enum import Enum, IntEnum @@ -294,10 +294,10 @@ reveal_type(F.baz) reveal_type(E.foo.value) reveal_type(F.bar.name) [out] -main:4: note: Revealed type is '__main__.E*' -main:5: note: Revealed type is '__main__.F*' -main:6: note: Revealed type is 'builtins.int' -main:7: note: Revealed type is 'builtins.str' +main:4: note: Revealed type is 'Literal[__main__.E.foo]?' +main:5: note: Revealed type is 'Literal[__main__.F.baz]?' +main:6: note: Revealed type is 'Literal[1]?' +main:7: note: Revealed type is 'Literal['bar']?' [case testFunctionalEnumDict] from enum import Enum, IntEnum @@ -308,10 +308,10 @@ reveal_type(F.baz) reveal_type(E.foo.value) reveal_type(F.bar.name) [out] -main:4: note: Revealed type is '__main__.E*' -main:5: note: Revealed type is '__main__.F*' -main:6: note: Revealed type is 'builtins.int' -main:7: note: Revealed type is 'builtins.str' +main:4: note: Revealed type is 'Literal[__main__.E.foo]?' +main:5: note: Revealed type is 'Literal[__main__.F.baz]?' +main:6: note: Revealed type is 'Literal[1]?' +main:7: note: Revealed type is 'Literal['bar']?' [case testFunctionalEnumErrors] from enum import Enum, IntEnum @@ -363,10 +363,10 @@ main:22: error: "Type[W]" has no attribute "c" from enum import Flag, IntFlag A = Flag('A', 'x y') B = IntFlag('B', 'a b') -reveal_type(A.x) # N: Revealed type is '__main__.A*' -reveal_type(B.a) # N: Revealed type is '__main__.B*' -reveal_type(A.x.name) # N: Revealed type is 'builtins.str' -reveal_type(B.a.name) # N: Revealed type is 'builtins.str' +reveal_type(A.x) # N: Revealed type is 'Literal[__main__.A.x]?' +reveal_type(B.a) # N: Revealed type is 'Literal[__main__.B.a]?' +reveal_type(A.x.name) # N: Revealed type is 'Literal['x']?' +reveal_type(B.a.name) # N: Revealed type is 'Literal['a']?' # TODO: The revealed type should be 'int' here reveal_type(A.x.value) # N: Revealed type is 'Any' @@ -381,7 +381,7 @@ class A: a = A() reveal_type(a.x) [out] -main:7: note: Revealed type is '__main__.A.E@4*' +main:7: note: Revealed type is '__main__.A.E@4' [case testFunctionalEnumInClassBody] from enum import Enum @@ -451,11 +451,11 @@ F = Enum('F', 'a b') [rechecked] [stale] [out1] -main:2: note: Revealed type is 'm.E*' -main:3: note: Revealed type is 'm.F*' +main:2: note: Revealed type is 'Literal[m.E.a]?' +main:3: note: Revealed type is 'Literal[m.F.b]?' [out2] -main:2: note: Revealed type is 'm.E*' -main:3: note: Revealed type is 'm.F*' +main:2: note: Revealed type is 'Literal[m.E.a]?' +main:3: note: Revealed type is 'Literal[m.F.b]?' [case testEnumAuto] from enum import Enum, auto @@ -463,7 +463,7 @@ class Test(Enum): a = auto() b = auto() -reveal_type(Test.a) # N: Revealed type is '__main__.Test*' +reveal_type(Test.a) # N: Revealed type is 'Literal[__main__.Test.a]?' [builtins fixtures/primitives.pyi] [case testEnumAttributeAccessMatrix] @@ -689,31 +689,31 @@ else: if x is z: reveal_type(x) # N: Revealed type is 'Literal[__main__.Foo.A]' - reveal_type(z) # N: Revealed type is '__main__.Foo*' + reveal_type(z) # N: Revealed type is 'Literal[__main__.Foo.A]?' accepts_foo_a(z) else: reveal_type(x) # N: Revealed type is 'Union[Literal[__main__.Foo.B], Literal[__main__.Foo.C]]' - reveal_type(z) # N: Revealed type is '__main__.Foo*' + reveal_type(z) # N: Revealed type is 'Literal[__main__.Foo.A]?' accepts_foo_a(z) if z is x: reveal_type(x) # N: Revealed type is 'Literal[__main__.Foo.A]' - reveal_type(z) # N: Revealed type is '__main__.Foo*' + reveal_type(z) # N: Revealed type is 'Literal[__main__.Foo.A]?' accepts_foo_a(z) else: reveal_type(x) # N: Revealed type is 'Union[Literal[__main__.Foo.B], Literal[__main__.Foo.C]]' - reveal_type(z) # N: Revealed type is '__main__.Foo*' + reveal_type(z) # N: Revealed type is 'Literal[__main__.Foo.A]?' accepts_foo_a(z) if y is z: reveal_type(y) # N: Revealed type is 'Literal[__main__.Foo.A]' - reveal_type(z) # N: Revealed type is '__main__.Foo*' + reveal_type(z) # N: Revealed type is 'Literal[__main__.Foo.A]?' accepts_foo_a(z) else: reveal_type(y) # No output: this branch is unreachable reveal_type(z) # No output: this branch is unreachable if z is y: reveal_type(y) # N: Revealed type is 'Literal[__main__.Foo.A]' - reveal_type(z) # N: Revealed type is '__main__.Foo*' + reveal_type(z) # N: Revealed type is 'Literal[__main__.Foo.A]?' accepts_foo_a(z) else: reveal_type(y) # No output: this branch is unreachable diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index a77721973945..28322eea2001 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -28,7 +28,7 @@ class A: pass [case testErrorCodeNoteHasNoCode] -reveal_type(1) # N: Revealed type is 'builtins.int' +reveal_type(1) # N: Revealed type is 'Literal[1]?' [case testErrorCodeSyntaxError] 1 '' # E: invalid syntax [syntax] diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index ba4120dea75a..969cf026a467 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -1911,7 +1911,7 @@ from typing import Union reveal_type(1 if bool() else 2) # N: Revealed type is 'builtins.int' reveal_type(1 if bool() else '') # N: Revealed type is 'builtins.object' x: Union[int, str] = reveal_type(1 if bool() else '') \ - # N: Revealed type is 'Union[builtins.int, builtins.str]' + # N: Revealed type is 'Union[Literal[1]?, Literal['']?]' class A: pass class B(A): @@ -1934,7 +1934,7 @@ reveal_type(d if bool() else b) # N: Revealed type is '__main__.A' [case testConditionalExpressionUnionWithAny] from typing import Union, Any a: Any -x: Union[int, str] = reveal_type(a if int() else 1) # N: Revealed type is 'Union[Any, builtins.int]' +x: Union[int, str] = reveal_type(a if int() else 1) # N: Revealed type is 'Union[Any, Literal[1]?]' reveal_type(a if int() else 1) # N: Revealed type is 'Any' @@ -2207,7 +2207,7 @@ d() # E: "D[str, int]" not callable [builtins fixtures/dict.pyi] [case testRevealType] -reveal_type(1) # N: Revealed type is 'builtins.int' +reveal_type(1) # N: Revealed type is 'Literal[1]?' [case testRevealLocals] x = 1 diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test index 216fb34d63be..dfb56e79b056 100644 --- a/test-data/unit/check-inference-context.test +++ b/test-data/unit/check-inference-context.test @@ -620,8 +620,8 @@ x : str = (lambda x: x + 1)(1) # E: Incompatible types in assignment (expressio reveal_type((lambda x, y: x + y)(1, 2)) # N: Revealed type is 'builtins.int' (lambda x, y: x + y)(1, "") # E: Unsupported operand types for + ("int" and "str") (lambda *, x, y: x + y)(x=1, y="") # E: Unsupported operand types for + ("int" and "str") -reveal_type((lambda s, i: s)(i=0, s='x')) # N: Revealed type is 'builtins.str' -reveal_type((lambda s, i: i)(i=0, s='x')) # N: Revealed type is 'builtins.int' +reveal_type((lambda s, i: s)(i=0, s='x')) # N: Revealed type is 'Literal['x']?' +reveal_type((lambda s, i: i)(i=0, s='x')) # N: Revealed type is 'Literal[0]?' reveal_type((lambda x, s, i: x)(1.0, i=0, s='x')) # N: Revealed type is 'builtins.float' (lambda x, s, i: x)() # E: Too few arguments (lambda: 0)(1) # E: Too many arguments @@ -643,7 +643,7 @@ f(list_a, lambda a: a.x) [case testLambdaWithoutContext] reveal_type(lambda x: x) # N: Revealed type is 'def (x: Any) -> Any' -reveal_type(lambda x: 1) # N: Revealed type is 'def (x: Any) -> builtins.int' +reveal_type(lambda x: 1) # N: Revealed type is 'def (x: Any) -> Literal[1]?' [case testLambdaContextVararg] from typing import Callable diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index 89a6cd2501e9..da6f5dfd25db 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -2285,9 +2285,9 @@ bad_keys: Literal["a", "bad"] reveal_type(test[good_keys]) # N: Revealed type is 'Union[__main__.A, __main__.B]' reveal_type(test.get(good_keys)) # N: Revealed type is 'Union[__main__.A, __main__.B]' -reveal_type(test.get(good_keys, 3)) # N: Revealed type is 'Union[__main__.A, builtins.int, __main__.B]' +reveal_type(test.get(good_keys, 3)) # N: Revealed type is 'Union[__main__.A, Literal[3]?, __main__.B]' reveal_type(test.pop(optional_keys)) # N: Revealed type is 'Union[__main__.D, __main__.E]' -reveal_type(test.pop(optional_keys, 3)) # N: Revealed type is 'Union[__main__.D, __main__.E, builtins.int]' +reveal_type(test.pop(optional_keys, 3)) # N: Revealed type is 'Union[__main__.D, __main__.E, Literal[3]?]' reveal_type(test.setdefault(good_keys, AAndB())) # N: Revealed type is 'Union[__main__.A, __main__.B]' del test[optional_keys] @@ -2390,7 +2390,7 @@ x.get(bad_keys, 3) # E: TypedDict "D1" has no key 'd' \ reveal_type(x[good_keys]) # N: Revealed type is 'Union[__main__.B, __main__.C]' reveal_type(x.get(good_keys)) # N: Revealed type is 'Union[__main__.B, __main__.C]' -reveal_type(x.get(good_keys, 3)) # N: Revealed type is 'Union[__main__.B, builtins.int, __main__.C]' +reveal_type(x.get(good_keys, 3)) # N: Revealed type is 'Union[__main__.B, Literal[3]?, __main__.C]' [builtins fixtures/dict.pyi] [typing fixtures/typing-full.pyi] @@ -2424,18 +2424,18 @@ def force2(x: Literal["foo"]) -> None: pass def force3(x: Literal[True]) -> None: pass def force4(x: Literal[None]) -> None: pass -reveal_type(var1) # N: Revealed type is 'builtins.int' -reveal_type(var2) # N: Revealed type is 'builtins.str' -reveal_type(var3) # N: Revealed type is 'builtins.bool' +reveal_type(var1) # N: Revealed type is 'Literal[1]?' +reveal_type(var2) # N: Revealed type is 'Literal['foo']?' +reveal_type(var3) # N: Revealed type is 'Literal[True]?' reveal_type(var4) # N: Revealed type is 'None' force1(reveal_type(var1)) # N: Revealed type is 'Literal[1]' force2(reveal_type(var2)) # N: Revealed type is 'Literal['foo']' force3(reveal_type(var3)) # N: Revealed type is 'Literal[True]' force4(reveal_type(var4)) # N: Revealed type is 'None' -reveal_type(Foo.classvar1) # N: Revealed type is 'builtins.int' -reveal_type(Foo.classvar2) # N: Revealed type is 'builtins.str' -reveal_type(Foo.classvar3) # N: Revealed type is 'builtins.bool' +reveal_type(Foo.classvar1) # N: Revealed type is 'Literal[1]?' +reveal_type(Foo.classvar2) # N: Revealed type is 'Literal['foo']?' +reveal_type(Foo.classvar3) # N: Revealed type is 'Literal[True]?' reveal_type(Foo.classvar4) # N: Revealed type is 'None' force1(reveal_type(Foo.classvar1)) # N: Revealed type is 'Literal[1]' force2(reveal_type(Foo.classvar2)) # N: Revealed type is 'Literal['foo']' @@ -2443,9 +2443,9 @@ force3(reveal_type(Foo.classvar3)) # N: Revealed type is 'Literal[True]' force4(reveal_type(Foo.classvar4)) # N: Revealed type is 'None' f = Foo() -reveal_type(f.instancevar1) # N: Revealed type is 'builtins.int' -reveal_type(f.instancevar2) # N: Revealed type is 'builtins.str' -reveal_type(f.instancevar3) # N: Revealed type is 'builtins.bool' +reveal_type(f.instancevar1) # N: Revealed type is 'Literal[1]?' +reveal_type(f.instancevar2) # N: Revealed type is 'Literal['foo']?' +reveal_type(f.instancevar3) # N: Revealed type is 'Literal[True]?' reveal_type(f.instancevar4) # N: Revealed type is 'None' force1(reveal_type(f.instancevar1)) # N: Revealed type is 'Literal[1]' force2(reveal_type(f.instancevar2)) # N: Revealed type is 'Literal['foo']' @@ -2564,6 +2564,35 @@ force4(reveal_type(f.instancevar4)) # N: Revealed type is 'None' [builtins fixtures/primitives.pyi] [out] +[case testLiteralFinalErasureInMutableDatastructures1] +# flags: --strict-optional +from typing_extensions import Final + +var1: Final = [0, None] +var2: Final = (0, None) + +reveal_type(var1) # N: Revealed type is 'builtins.list[Union[builtins.int, None]]' +reveal_type(var2) # N: Revealed type is 'Tuple[Literal[0]?, None]' +[builtins fixtures/tuple.pyi] + +[case testLiteralFinalErasureInMutableDatastructures2] +from typing_extensions import Final, Literal + +var1: Final = [] +var1.append(0) +reveal_type(var1) # N: Revealed type is 'builtins.list[builtins.int]' + +var2 = [] +var2.append(0) +reveal_type(var2) # N: Revealed type is 'builtins.list[builtins.int]' + +x: Literal[0] = 0 +var3 = [] +var3.append(x) +reveal_type(var3) # N: Revealed type is 'builtins.list[Literal[0]]' + +[builtins fixtures/list.pyi] + [case testLiteralFinalMismatchCausesError] from typing_extensions import Final, Literal @@ -2604,12 +2633,14 @@ b: Final = (1, 2) def force1(x: Literal[1]) -> None: pass def force2(x: Tuple[Literal[1], Literal[2]]) -> None: pass -reveal_type(a) # N: Revealed type is 'builtins.int' -reveal_type(b) # N: Revealed type is 'Tuple[builtins.int, builtins.int]' +reveal_type(a) # N: Revealed type is 'Literal[1]?' +reveal_type(b) # N: Revealed type is 'Tuple[Literal[1]?, Literal[2]?]' +# TODO: This test seems somewhat broken and might need a rewrite (and a fix somewhere in mypy). +# See https://github.com/python/mypy/issues/7399#issuecomment-554188073 for more context. force1(reveal_type(a)) # N: Revealed type is 'Literal[1]' force2(reveal_type(b)) # E: Argument 1 to "force2" has incompatible type "Tuple[int, int]"; expected "Tuple[Literal[1], Literal[2]]" \ - # N: Revealed type is 'Tuple[builtins.int, builtins.int]' + # N: Revealed type is 'Tuple[Literal[1]?, Literal[2]?]' [builtins fixtures/tuple.pyi] [out] @@ -3061,11 +3092,11 @@ expects_foo(Test3.BAR.name) # E: Argument 1 to "expects_foo" has incompatible t expects_foo(Test4.BAR.name) # E: Argument 1 to "expects_foo" has incompatible type "Literal['BAR']"; expected "Literal['FOO']" expects_foo(Test5.BAR.name) # E: Argument 1 to "expects_foo" has incompatible type "Literal['BAR']"; expected "Literal['FOO']" -reveal_type(Test1.FOO.name) # N: Revealed type is 'builtins.str' -reveal_type(Test2.FOO.name) # N: Revealed type is 'builtins.str' -reveal_type(Test3.FOO.name) # N: Revealed type is 'builtins.str' -reveal_type(Test4.FOO.name) # N: Revealed type is 'builtins.str' -reveal_type(Test5.FOO.name) # N: Revealed type is 'builtins.str' +reveal_type(Test1.FOO.name) # N: Revealed type is 'Literal['FOO']?' +reveal_type(Test2.FOO.name) # N: Revealed type is 'Literal['FOO']?' +reveal_type(Test3.FOO.name) # N: Revealed type is 'Literal['FOO']?' +reveal_type(Test4.FOO.name) # N: Revealed type is 'Literal['FOO']?' +reveal_type(Test5.FOO.name) # N: Revealed type is 'Literal['FOO']?' [out] [case testLiteralBinderLastValueErased] diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 664288c9ff20..1d7723deed21 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -150,8 +150,8 @@ def b1() -> str: pass reveal_type(b3()) # N: Revealed type is 'builtins.str' [case testNewAnalyzerBool] -reveal_type(True) # N: Revealed type is 'builtins.bool' -reveal_type(False) # N: Revealed type is 'builtins.bool' +reveal_type(True) # N: Revealed type is 'Literal[True]?' +reveal_type(False) # N: Revealed type is 'Literal[False]?' [case testNewAnalyzerNewTypeMultiplePasses] import b diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 36d6da2f2905..d1993fdc4ae6 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -388,7 +388,7 @@ def lookup_field(name, obj): attr = None [case testTernaryWithNone] -reveal_type(None if bool() else 0) # N: Revealed type is 'Union[builtins.int, None]' +reveal_type(None if bool() else 0) # N: Revealed type is 'Union[Literal[0]?, None]' [builtins fixtures/bool.pyi] [case testListWithNone] diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index eeb964e990ef..e1507d9a2ed4 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -226,7 +226,7 @@ def f(x: int = (c := 4)) -> int: f(x=(y7 := 3)) reveal_type(y7) # N: Revealed type is 'builtins.int' - reveal_type((lambda: (y8 := 3) and y8)()) # N: Revealed type is 'builtins.int' + reveal_type((lambda: (y8 := 3) and y8)()) # N: Revealed type is 'Literal[3]?' y8 # E: Name 'y8' is not defined y7 = 1.0 # E: Incompatible types in assignment (expression has type "float", variable has type "int") @@ -240,7 +240,11 @@ def f(x: int = (c := 4)) -> int: if Alias := int: z3: Alias # E: Variable "Alias" is not valid as a type - return (y9 := 3) + y9 + if (reveal_type(y9 := 3) and # N: Revealed type is 'Literal[3]?' + reveal_type(y9)): # N: Revealed type is 'builtins.int' + reveal_type(y9) # N: Revealed type is 'builtins.int' + + return (y10 := 3) + y10 reveal_type(c) # N: Revealed type is 'builtins.int' diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 1668998f4dcc..70d99fc60240 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -1583,8 +1583,8 @@ b: B reveal_type(a.pop('x')) # N: Revealed type is 'builtins.int' reveal_type(a.pop('y', [])) # N: Revealed type is 'builtins.list[builtins.int]' -reveal_type(a.pop('x', '')) # N: Revealed type is 'Union[builtins.int, builtins.str]' -reveal_type(a.pop('x', (1, 2))) # N: Revealed type is 'Union[builtins.int, Tuple[builtins.int, builtins.int]]' +reveal_type(a.pop('x', '')) # N: Revealed type is 'Union[builtins.int, Literal['']?]' +reveal_type(a.pop('x', (1, 2))) # N: Revealed type is 'Union[builtins.int, Tuple[Literal[1]?, Literal[2]?]]' a.pop('invalid', '') # E: TypedDict "A" has no key 'invalid' b.pop('x') # E: Key 'x' of TypedDict "B" cannot be deleted x = '' diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index fa77c09bfd93..8bc11e3fe5d3 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -662,20 +662,20 @@ class Child(Parent): [case testUnreachableAfterToplevelAssert] import sys -reveal_type(0) # N: Revealed type is 'builtins.int' +reveal_type(0) # N: Revealed type is 'Literal[0]?' assert sys.platform == 'lol' reveal_type('') # No error here :-) [builtins fixtures/ops.pyi] [case testUnreachableAfterToplevelAssert2] import sys -reveal_type(0) # N: Revealed type is 'builtins.int' +reveal_type(0) # N: Revealed type is 'Literal[0]?' assert sys.version_info[0] == 1 reveal_type('') # No error here :-) [builtins fixtures/ops.pyi] [case testUnreachableAfterToplevelAssert3] -reveal_type(0) # N: Revealed type is 'builtins.int' +reveal_type(0) # N: Revealed type is 'Literal[0]?' MYPY = False assert not MYPY reveal_type('') # No error here :-) @@ -683,7 +683,7 @@ reveal_type('') # No error here :-) [case testUnreachableAfterToplevelAssert4] # flags: --always-false NOPE -reveal_type(0) # N: Revealed type is 'builtins.int' +reveal_type(0) # N: Revealed type is 'Literal[0]?' NOPE = False assert NOPE reveal_type('') # No error here :-) @@ -712,8 +712,8 @@ def bar() -> None: pass import sys if sys.version_info[0] >= 2: assert sys.platform == 'lol' - reveal_type('') # N: Revealed type is 'builtins.str' -reveal_type('') # N: Revealed type is 'builtins.str' + reveal_type('') # N: Revealed type is 'Literal['']?' +reveal_type('') # N: Revealed type is 'Literal['']?' [builtins fixtures/ops.pyi] [case testUnreachableFlagWithBadControlFlow] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 78de84173ae4..3ec4028a842e 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -639,8 +639,8 @@ from typing import TypeVar T = TypeVar('T') def f(*args: T) -> T: ... -reveal_type(f(*(1, None))) # N: Revealed type is 'Union[builtins.int, None]' -reveal_type(f(1, *(None, 1))) # N: Revealed type is 'Union[builtins.int, None]' +reveal_type(f(*(1, None))) # N: Revealed type is 'Union[Literal[1]?, None]' +reveal_type(f(1, *(None, 1))) # N: Revealed type is 'Union[Literal[1]?, None]' reveal_type(f(1, *(1, None))) # N: Revealed type is 'Union[builtins.int, None]' [builtins fixtures/tuple.pyi] diff --git a/test-data/unit/merge.test b/test-data/unit/merge.test index f54b6bf6d472..279d566ceea7 100644 --- a/test-data/unit/merge.test +++ b/test-data/unit/merge.test @@ -422,12 +422,12 @@ def f(a: A) -> None: 1 [out] ## target -IntExpr:3: builtins.int<0> +IntExpr:3: Literal[1]?<0> NameExpr:4: target.A<1> ==> ## target NameExpr:3: target.A<1> -IntExpr:4: builtins.int<0> +IntExpr:4: Literal[1]?<0> [case testClassAttribute_types] import target @@ -453,14 +453,14 @@ NameExpr:3: def () -> target.A<0> NameExpr:3: target.A<0> MemberExpr:4: target.A<0> NameExpr:4: target.A<0> -IntExpr:5: builtins.int<1> +IntExpr:5: Literal[1]?<1> MemberExpr:5: builtins.int<1> NameExpr:5: target.A<0> MemberExpr:6: builtins.int<1> NameExpr:6: target.A<0> ==> ## target -IntExpr:3: builtins.int<1> +IntExpr:3: Literal[1]?<1> MemberExpr:3: builtins.int<1> NameExpr:3: target.A<0> MemberExpr:4: builtins.int<1> diff --git a/test-data/unit/typexport-basic.test b/test-data/unit/typexport-basic.test index cfd91c26307f..7f6a6f0bda2a 100644 --- a/test-data/unit/typexport-basic.test +++ b/test-data/unit/typexport-basic.test @@ -38,9 +38,9 @@ import typing 'foo' [builtins fixtures/primitives.pyi] [out] -IntExpr(2) : builtins.int +IntExpr(2) : Literal[5]? FloatExpr(3) : builtins.float -StrExpr(4) : builtins.str +StrExpr(4) : Literal['foo']? [case testNameExpression] @@ -246,10 +246,10 @@ elif not a: [builtins fixtures/bool.pyi] [out] NameExpr(3) : builtins.bool -IntExpr(4) : builtins.int +IntExpr(4) : Literal[1]? NameExpr(5) : builtins.bool UnaryExpr(5) : builtins.bool -IntExpr(6) : builtins.int +IntExpr(6) : Literal[1]? [case testWhile] @@ -715,7 +715,7 @@ NameExpr(2) : B import typing f = lambda: 1 [out] -LambdaExpr(3) : def () -> builtins.int +LambdaExpr(3) : def () -> Literal[1]? NameExpr(3) : def () -> builtins.int [case testLambdaWithInferredType2] @@ -1117,7 +1117,7 @@ m(fun, nums) [builtins fixtures/list.pyi] [out] -IntExpr(13) : builtins.int +IntExpr(13) : Literal[1]? ListExpr(13) : builtins.list[builtins.int] CallExpr(14) : None NameExpr(14) : def (s: builtins.int) -> builtins.int @@ -1146,7 +1146,7 @@ from typing import List a = [None] * 3 # type: List[str] [builtins fixtures/list.pyi] [out] -IntExpr(3) : builtins.int +IntExpr(3) : Literal[3]? ListExpr(3) : builtins.list[builtins.str] OpExpr(3) : builtins.list[builtins.str] @@ -1155,9 +1155,9 @@ OpExpr(3) : builtins.list[builtins.str] '%d' % 1 [builtins fixtures/primitives.pyi] [out] -IntExpr(2) : builtins.int +IntExpr(2) : Literal[1]? OpExpr(2) : builtins.str -StrExpr(2) : builtins.str +StrExpr(2) : Literal['%d']? -- TODO --