From e5b16364cbd05d82b43bd361a4a879115f5b52ec Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Thu, 14 Nov 2019 10:22:55 -0800 Subject: [PATCH 1/3] Make revealed type of Final vars distinct from non-Final vars 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)`. This changes should help greatly simplify some of the tests I want to write for https://github.com/python/mypy/pull/7169 and also help make a somewhat confusing and implicit part of mypy more visible. --- docs/source/literal_types.rst | 27 +++++++-- 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 | 53 ++++++++++------- test-data/unit/check-newsemanal.test | 4 +- test-data/unit/check-optional.test | 2 +- 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 +++--- 19 files changed, 153 insertions(+), 104 deletions(-) diff --git a/docs/source/literal_types.rst b/docs/source/literal_types.rst index d85bc24a02ff..d4f6e9124494 100644 --- a/docs/source/literal_types.rst +++ b/docs/source/literal_types.rst @@ -121,8 +121,8 @@ 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 @@ -138,8 +138,27 @@ 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``. +In other words, the type of ``c`` is *context-dependent*: It could be either ``int`` +or ``Literal[19]`` depending on where it's used. For example, here is an example of +where mypy will decide to use ``int`` over ``Literal[19]``: + +..code-block:: python + + from typing_extensions import Final, Literal + + a: Final = 19 + b: Literal[19] = 19 + + list_of_ints = [] + list_of_ints.append(a) + reveal_type(list_of_ints) # Revealed type is 'List[int]' + + list_of_lits = [] + list_of_lits.append(b) + reveal_type(list_of_lits) # Revealed type is 'List[Literal[19]]' + +This is why the revealed type of ``c`` is ``Literal[19]?``: the question mark at +the end indicates the context-sensitive nature of ``c``. 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. 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..0d5448ce7092 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..386ee2261307 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,17 @@ force4(reveal_type(f.instancevar4)) # N: Revealed type is 'None' [builtins fixtures/primitives.pyi] [out] +[case testLiteralFinalErasureInMutableDatastructures] +# 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 testLiteralFinalMismatchCausesError] from typing_extensions import Final, Literal @@ -2604,12 +2615,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 +3074,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-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 -- From b4606dc2f295b867beaf81c7182a655cc396a779 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Fri, 15 Nov 2019 07:47:57 -0800 Subject: [PATCH 2/3] Respond to code review --- docs/source/literal_types.rst | 28 +++++++++++++++++----------- mypy/checkmember.py | 2 +- test-data/unit/check-literal.test | 20 +++++++++++++++++++- test-data/unit/check-python38.test | 8 ++++++-- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/docs/source/literal_types.rst b/docs/source/literal_types.rst index d4f6e9124494..58a9d613777e 100644 --- a/docs/source/literal_types.rst +++ b/docs/source/literal_types.rst @@ -125,9 +125,12 @@ you can instead change the variable to be ``Final`` (see :ref:`final_attrs`): 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,9 +141,13 @@ the above program almost as if it were written like so: reveal_type(19) expects_literal(19) -In other words, the type of ``c`` is *context-dependent*: It could be either ``int`` -or ``Literal[19]`` depending on where it's used. For example, here is an example of -where mypy will decide to use ``int`` over ``Literal[19]``: +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 @@ -149,19 +156,18 @@ where mypy will decide to use ``int`` over ``Literal[19]``: a: Final = 19 b: Literal[19] = 19 + # Mypy will chose to infer List[int] instead of List[Literal[19]?] or + # List[Literal[19]] since the former is most likely more useful. 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]]' -This is why the revealed type of ``c`` is ``Literal[19]?``: the question mark at -the end indicates the context-sensitive nature of ``c``. - -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/checkmember.py b/mypy/checkmember.py index 0d5448ce7092..bc9aa41efeab 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -693,7 +693,7 @@ def analyze_class_attribute_access(itype: Instance, enum_literal = LiteralType(name, fallback=itype) # 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 + # 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) diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index 386ee2261307..da6f5dfd25db 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -2564,7 +2564,7 @@ force4(reveal_type(f.instancevar4)) # N: Revealed type is 'None' [builtins fixtures/primitives.pyi] [out] -[case testLiteralFinalErasureInMutableDatastructures] +[case testLiteralFinalErasureInMutableDatastructures1] # flags: --strict-optional from typing_extensions import Final @@ -2575,6 +2575,24 @@ 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 diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index eeb964e990ef..4ae1fd912c24 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' From 2e1993a4c468a3bc586b3cbb53bbb14641d92a97 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Fri, 15 Nov 2019 08:21:44 -0800 Subject: [PATCH 3/3] Fix syntax error in tests, respond to second docs review --- docs/source/literal_types.rst | 5 ++--- test-data/unit/check-python38.test | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/source/literal_types.rst b/docs/source/literal_types.rst index 58a9d613777e..707574752018 100644 --- a/docs/source/literal_types.rst +++ b/docs/source/literal_types.rst @@ -149,15 +149,14 @@ The main cases where the behavior of context-sensitive vs true literal types dif 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 +.. code-block:: python from typing_extensions import Final, Literal a: Final = 19 b: Literal[19] = 19 - # Mypy will chose to infer List[int] instead of List[Literal[19]?] or - # List[Literal[19]] since the former is most likely more useful. + # 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]' diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 4ae1fd912c24..e1507d9a2ed4 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -240,8 +240,8 @@ def f(x: int = (c := 4)) -> int: if Alias := int: z3: Alias # E: Variable "Alias" is not valid as a type - if reveal_type(y9 := 3) and \ # N: Revealed type is 'Literal[3]?' - reveal_type(y9): # N: Revealed type is 'builtins.int' + 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 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy