From 2080c57059ec4afdc3e47438a342931743e15a14 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Sun, 16 Dec 2018 17:31:47 -0800 Subject: [PATCH 1/5] Add tests for Literal types with incremental and fine-grained mode This pull request adds a variety of tests for Literal types and incremental and fine-grained mode. Many of these tests are a bit perfunctory: literal types don't really interact with incremental/fine-grained logic in a special way, so it was a little hard for me to think of good test cases. The only code-related change I ended up needing to make was to remove the assert from the mypy.server.astmerge.TypeReplaceVisitor's `visit_raw_literal_type` method. This actually worries me a bit: we should have removed all instances of RawLiteralType after phase 2 of semantic analysis. Does the merge step happen before then? If the merge is supposed to happen after phase 2, I can spend more time trying to track down the leak and update this PR. --- mypy/server/astmerge.py | 2 +- mypy/test/testmerge.py | 1 + test-data/unit/check-incremental.test | 14 ++ test-data/unit/deps-expressions.test | 16 ++ test-data/unit/diff.test | 299 ++++++++++++++++++++++++++ test-data/unit/fine-grained.test | 77 +++++++ test-data/unit/merge.test | 48 +++++ 7 files changed, 456 insertions(+), 1 deletion(-) diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index edfdc076e7d7..a9f755ce84d2 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -393,7 +393,7 @@ def visit_typeddict_type(self, typ: TypedDictType) -> None: typ.fallback.accept(self) def visit_raw_literal_type(self, t: RawLiteralType) -> None: - assert False, "Unexpected RawLiteralType after semantic analysis phase" + pass def visit_literal_type(self, typ: LiteralType) -> None: typ.fallback.accept(self) diff --git a/mypy/test/testmerge.py b/mypy/test/testmerge.py index ad163eddfe9c..32076db8e802 100644 --- a/mypy/test/testmerge.py +++ b/mypy/test/testmerge.py @@ -38,6 +38,7 @@ 'contextlib', 'sys', 'mypy_extensions', + 'typing_extensions', 'enum', ) diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index e7f43c6f4e12..081e4256442c 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5586,3 +5586,17 @@ python_version=3.6 [out] [out2] tmp/a.py:2: error: Incompatible types in assignment (expression has type "int", variable has type "str") + +[case testLiteralIncrementalTurningIntoLiteral] +import mod +reveal_type(mod.a) +[file mod.py] +from typing_extensions import Literal +a = 1 +[file mod.py.2] +from typing_extensions import Literal +a: Literal[2] = 2 +[out] +main:2: error: Revealed type is 'builtins.int' +[out2] +main:2: error: Revealed type is 'Literal[2]' diff --git a/test-data/unit/deps-expressions.test b/test-data/unit/deps-expressions.test index 6a50b2f61425..eb87878ed6b3 100644 --- a/test-data/unit/deps-expressions.test +++ b/test-data/unit/deps-expressions.test @@ -448,3 +448,19 @@ def g() -> None: [out] -> m.g -> m.g + +[case testLiteralDepsExpr] +from typing_extensions import Literal + +Alias = Literal[1] + +a: Alias +b = a +def f(x: Alias) -> None: pass +def g() -> Literal[1]: + return b +[out] + -> m, m.f + -> m + -> m, m.g + -> m diff --git a/test-data/unit/diff.test b/test-data/unit/diff.test index ef3718522ce3..88067e28362a 100644 --- a/test-data/unit/diff.test +++ b/test-data/unit/diff.test @@ -1109,3 +1109,302 @@ plugins=/test-data/unit/plugins/dyn_class.py [out] __main__.Diff __main__.Diff.x + +[case testLiteralTriggersVar] +from typing_extensions import Literal + +x: Literal[1] = 1 +y = 1 +z: Literal[1] = 1 +same: Literal[1] = 1 +class C: + x_class: Literal[1] = 1 + y_class = 1 + z_class: Literal[1] = 1 + same_class: Literal[1] = 1 + def __init__(self) -> None: + self.x_instance: Literal[1] = 1 + self.y_instance = 1 + self.z_instance: Literal[1] = 1 + self.same_instance: Literal[1] = 1 + +[file next.py] +from typing_extensions import Literal + +x = 1 +y: Literal[1] = 1 +z: Literal[2] = 2 +same: Literal[1] = 1 +class C: + x_class = 1 + y_class: Literal[1] = 1 + z_class: Literal[2] = 2 + same_class: Literal[1] = 1 + def __init__(self) -> None: + self.x_instance = 1 + self.y_instance: Literal[1] = 1 + self.z_instance: Literal[2] = 2 + self.same_instance: Literal[1] = 1 +[out] +__main__.C.x_class +__main__.C.x_instance +__main__.C.y_class +__main__.C.y_instance +__main__.C.z_class +__main__.C.z_instance +__main__.x +__main__.y +__main__.z + +[case testLiteralTriggersFunctions] +from typing_extensions import Literal + +def function_1() -> int: pass +def function_2() -> Literal[1]: pass +def function_3() -> Literal[1]: pass + +def function_4(x: int) -> None: pass +def function_5(x: Literal[1]) -> None: pass +def function_6(x: Literal[1]) -> None: pass + +def function_same_1() -> Literal[1]: pass +def function_same_2(x: Literal[1]) -> None: pass + +class C: + def method_1(self) -> int: pass + def method_2(self) -> Literal[1]: pass + def method_3(self) -> Literal[1]: pass + + def method_4(self, x: int) -> None: pass + def method_5(self, x: Literal[1]) -> None: pass + def method_6(self, x: Literal[1]) -> None: pass + + def method_same_1(self) -> Literal[1]: pass + def method_same_2(self, x: Literal[1]) -> None: pass + + @classmethod + def classmethod_1(cls) -> int: pass + @classmethod + def classmethod_2(cls) -> Literal[1]: pass + @classmethod + def classmethod_3(cls) -> Literal[1]: pass + + @classmethod + def classmethod_4(cls, x: int) -> None: pass + @classmethod + def classmethod_5(cls, x: Literal[1]) -> None: pass + @classmethod + def classmethod_6(cls, x: Literal[1]) -> None: pass + + @classmethod + def classmethod_same_1(cls) -> Literal[1]: pass + @classmethod + def classmethod_same_2(cls, x: Literal[1]) -> None: pass + + @staticmethod + def staticmethod_1() -> int: pass + @staticmethod + def staticmethod_2() -> Literal[1]: pass + @staticmethod + def staticmethod_3() -> Literal[1]: pass + + @staticmethod + def staticmethod_4(x: int) -> None: pass + @staticmethod + def staticmethod_5(x: Literal[1]) -> None: pass + @staticmethod + def staticmethod_6(x: Literal[1]) -> None: pass + + @staticmethod + def staticmethod_same_1() -> Literal[1]: pass + @staticmethod + def staticmethod_same_2(x: Literal[1]) -> None: pass + +[file next.py] +from typing_extensions import Literal + +def function_1() -> Literal[1]: pass +def function_2() -> int: pass +def function_3() -> Literal[2]: pass + +def function_4(x: Literal[1]) -> None: pass +def function_5(x: int) -> None: pass +def function_6(x: Literal[2]) -> None: pass + +def function_same_1() -> Literal[1]: pass +def function_same_2(x: Literal[1]) -> None: pass + +class C: + def method_1(self) -> Literal[1]: pass + def method_2(self) -> int: pass + def method_3(self) -> Literal[2]: pass + + def method_4(self, x: Literal[1]) -> None: pass + def method_5(self, x: int) -> None: pass + def method_6(self, x: Literal[2]) -> None: pass + + def method_same_1(self) -> Literal[1]: pass + def method_same_2(self, x: Literal[1]) -> None: pass + + @classmethod + def classmethod_1(cls) -> Literal[1]: pass + @classmethod + def classmethod_2(cls) -> int: pass + @classmethod + def classmethod_3(cls) -> Literal[2]: pass + + @classmethod + def classmethod_4(cls, x: Literal[1]) -> None: pass + @classmethod + def classmethod_5(cls, x: int) -> None: pass + @classmethod + def classmethod_6(cls, x: Literal[2]) -> None: pass + + @classmethod + def classmethod_same_1(cls) -> Literal[1]: pass + @classmethod + def classmethod_same_2(cls, x: Literal[1]) -> None: pass + + @staticmethod + def staticmethod_1() -> Literal[1]: pass + @staticmethod + def staticmethod_2() -> int: pass + @staticmethod + def staticmethod_3() -> Literal[2]: pass + + @staticmethod + def staticmethod_4(x: Literal[1]) -> None: pass + @staticmethod + def staticmethod_5(x: int) -> None: pass + @staticmethod + def staticmethod_6(x: Literal[2]) -> None: pass + + @staticmethod + def staticmethod_same_1() -> Literal[1]: pass + @staticmethod + def staticmethod_same_2(x: Literal[1]) -> None: pass +[builtins fixtures/classmethod.pyi] +[out] +__main__.C.classmethod_1 +__main__.C.classmethod_2 +__main__.C.classmethod_3 +__main__.C.classmethod_4 +__main__.C.classmethod_5 +__main__.C.classmethod_6 +__main__.C.method_1 +__main__.C.method_2 +__main__.C.method_3 +__main__.C.method_4 +__main__.C.method_5 +__main__.C.method_6 +__main__.C.staticmethod_1 +__main__.C.staticmethod_2 +__main__.C.staticmethod_3 +__main__.C.staticmethod_4 +__main__.C.staticmethod_5 +__main__.C.staticmethod_6 +__main__.function_1 +__main__.function_2 +__main__.function_3 +__main__.function_4 +__main__.function_5 +__main__.function_6 + +[case testLiteralTriggersProperty] +from typing_extensions import Literal + +class C: + @property + def p1(self) -> Literal[1]: pass + + @property + def p2(self) -> int: pass + + @property + def same(self) -> Literal[1]: pass + +[file next.py] +from typing_extensions import Literal + +class C: + @property + def p1(self) -> int: pass + + @property + def p2(self) -> Literal[1]: pass + + @property + def same(self) -> Literal[1]: pass +[builtins fixtures/property.pyi] +[out] +__main__.C.p1 +__main__.C.p2 + +[case testLiteralsTriggersOverload] +from typing import overload +from typing_extensions import Literal + +@overload +def func(x: str) -> str: ... +@overload +def func(x: Literal[1]) -> int: ... +def func(x): + pass + +@overload +def func_same(x: str) -> str: ... +@overload +def func_same(x: Literal[1]) -> int: ... +def func_same(x): + pass + +class C: + @overload + def method(self, x: str) -> str: ... + @overload + def method(self, x: Literal[1]) -> int: ... + def method(self, x): + pass + + @overload + def method_same(self, x: str) -> str: ... + @overload + def method_same(self, x: Literal[1]) -> int: ... + def method_same(self, x): + pass + +[file next.py] +from typing import overload +from typing_extensions import Literal + +@overload +def func(x: str) -> str: ... +@overload +def func(x: Literal[2]) -> int: ... +def func(x): + pass + +@overload +def func_same(x: str) -> str: ... +@overload +def func_same(x: Literal[1]) -> int: ... +def func_same(x): + pass + +class C: + @overload + def method(self, x: str) -> str: ... + @overload + def method(self, x: Literal[2]) -> int: ... + def method(self, x): + pass + + @overload + def method_same(self, x: str) -> str: ... + @overload + def method_same(self, x: Literal[1]) -> int: ... + def method_same(self, x): + pass +[out] +__main__.C.method +__main__.func diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index cee3fa709d47..50e75a9154f0 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -8134,3 +8134,80 @@ x = 'no way' == == main:10: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testLiteralFineGrainedVarConversion] +import mod +reveal_type(mod.x) +[file mod.py] +x = 1 +[file mod.py.2] +from typing_extensions import Literal +x: Literal[1] = 1 +[file mod.py.3] +from typing_extensions import Literal +x: Literal[1] = 2 +[out] +main:2: error: Revealed type is 'builtins.int' +== +main:2: error: Revealed type is 'Literal[1]' +== +mod.py:2: error: Incompatible types in assignment (expression has type "Literal[2]", variable has type "Literal[1]") +main:2: error: Revealed type is 'Literal[1]' + +[case testLiteralFineGrainedFunctionConversion] +from mod import foo +foo(3) +[file mod.py] +def foo(x: int) -> None: pass +[file mod.py.2] +from typing_extensions import Literal +def foo(x: Literal[3]) -> None: pass +[file mod.py.3] +from typing_extensions import Literal +def foo(x: Literal[4]) -> None: pass +[out] +== +== +main:2: error: Argument 1 to "foo" has incompatible type "Literal[3]"; expected "Literal[4]" + +[case testLiteralFineGrainedAlias] +from mod import Alias +a: Alias = 1 +[file mod.py] +Alias = int +[file mod.py.2] +from typing_extensions import Literal +Alias = Literal[1] +[file mod.py.3] +from typing_extensions import Literal +Alias = Literal[2] +[out] +== +== +main:2: error: Incompatible types in assignment (expression has type "Literal[1]", variable has type "Literal[2]") + +[case testLiteralFineGrainedOverload] +from mod import foo +reveal_type(foo(4)) +[file mod.py] +from typing import overload +from typing_extensions import Literal +@overload +def foo(x: int) -> str: ... +@overload +def foo(x: Literal['bar']) -> int: ... +def foo(x): pass +[file mod.py.2] +from typing import overload +from typing_extensions import Literal +@overload +def foo(x: Literal[4]) -> Literal['foo']: ... +@overload +def foo(x: int) -> str: ... +@overload +def foo(x: Literal['bar']) -> int: ... +def foo(x): pass +[out] +main:2: error: Revealed type is 'builtins.str' +== +main:2: error: Revealed type is 'Literal['foo']' diff --git a/test-data/unit/merge.test b/test-data/unit/merge.test index 281006557083..748172efeac3 100644 --- a/test-data/unit/merge.test +++ b/test-data/unit/merge.test @@ -1451,3 +1451,51 @@ TypeInfo<0>( X<3> (builtins.int<4>) Y<6> (builtins.int<4>)) MetaclassType(enum.EnumMeta<5>)) + +[case testLiteralMerge] +import target +[file target.py] +from typing_extensions import Literal +def foo(x: Literal[3]) -> Literal['a']: pass +bar: Literal[4] = 4 +[file target.py.next] +from typing_extensions import Literal +def foo(x: Literal['3']) -> Literal['b']: pass +bar: Literal[5] = 5 +[out] +MypyFile:1<0>( + tmp/main + Import:1(target)) +MypyFile:1<1>( + tmp/target.py + ImportFrom:1(typing_extensions, [Literal]) + FuncDef:2<2>( + foo + Args( + Var(x)) + def (x: Literal[3]) -> Literal['a'] + Block:2<3>( + PassStmt:2<4>())) + AssignmentStmt:3<5>( + NameExpr(bar [target.bar<6>]) + IntExpr(4) + Literal[4])) +==> +MypyFile:1<0>( + tmp/main + Import:1(target)) +MypyFile:1<1>( + tmp/target.py + ImportFrom:1(typing_extensions, [Literal]) + FuncDef:2<2>( + foo + Args( + Var(x)) + def (x: Literal['3']) -> Literal['b'] + Block:2<7>( + PassStmt:2<8>())) + AssignmentStmt:3<9>( + NameExpr(bar [target.bar<6>]) + IntExpr(5) + Literal[5])) + From 7e7bb3eab9c98ee5e262fde6406d426b001815cc Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Sun, 16 Dec 2018 17:31:47 -0800 Subject: [PATCH 2/5] Add tests for Literal types with incremental and fine-grained mode This pull request adds a variety of tests for Literal types and incremental and fine-grained mode. Many of these tests are a bit perfunctory: literal types don't really interact with incremental/fine-grained logic in a special way, so it was a little hard for me to think of good test cases. The only code-related change I ended up needing to make was to remove the assert from the mypy.server.astmerge.TypeReplaceVisitor's `visit_raw_literal_type` method. This actually worries me a bit: we should have removed all instances of RawLiteralType after phase 2 of semantic analysis. Does the merge step happen before then? If the merge is supposed to happen after phase 2, I can spend more time trying to track down the leak and update this PR. --- mypy/server/astmerge.py | 2 +- mypy/test/testmerge.py | 1 + test-data/unit/check-incremental.test | 14 ++ test-data/unit/deps-expressions.test | 16 ++ test-data/unit/diff.test | 299 ++++++++++++++++++++++++++ test-data/unit/fine-grained.test | 77 +++++++ test-data/unit/merge.test | 48 +++++ 7 files changed, 456 insertions(+), 1 deletion(-) diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index edfdc076e7d7..a9f755ce84d2 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -393,7 +393,7 @@ def visit_typeddict_type(self, typ: TypedDictType) -> None: typ.fallback.accept(self) def visit_raw_literal_type(self, t: RawLiteralType) -> None: - assert False, "Unexpected RawLiteralType after semantic analysis phase" + pass def visit_literal_type(self, typ: LiteralType) -> None: typ.fallback.accept(self) diff --git a/mypy/test/testmerge.py b/mypy/test/testmerge.py index ad163eddfe9c..32076db8e802 100644 --- a/mypy/test/testmerge.py +++ b/mypy/test/testmerge.py @@ -38,6 +38,7 @@ 'contextlib', 'sys', 'mypy_extensions', + 'typing_extensions', 'enum', ) diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index b3e70a12f70a..e054d6a35267 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -4890,3 +4890,17 @@ python_version=3.6 [out] [out2] tmp/a.py:2: error: Incompatible types in assignment (expression has type "int", variable has type "str") + +[case testLiteralIncrementalTurningIntoLiteral] +import mod +reveal_type(mod.a) +[file mod.py] +from typing_extensions import Literal +a = 1 +[file mod.py.2] +from typing_extensions import Literal +a: Literal[2] = 2 +[out] +main:2: error: Revealed type is 'builtins.int' +[out2] +main:2: error: Revealed type is 'Literal[2]' diff --git a/test-data/unit/deps-expressions.test b/test-data/unit/deps-expressions.test index 6a50b2f61425..eb87878ed6b3 100644 --- a/test-data/unit/deps-expressions.test +++ b/test-data/unit/deps-expressions.test @@ -448,3 +448,19 @@ def g() -> None: [out] -> m.g -> m.g + +[case testLiteralDepsExpr] +from typing_extensions import Literal + +Alias = Literal[1] + +a: Alias +b = a +def f(x: Alias) -> None: pass +def g() -> Literal[1]: + return b +[out] + -> m, m.f + -> m + -> m, m.g + -> m diff --git a/test-data/unit/diff.test b/test-data/unit/diff.test index ef3718522ce3..88067e28362a 100644 --- a/test-data/unit/diff.test +++ b/test-data/unit/diff.test @@ -1109,3 +1109,302 @@ plugins=/test-data/unit/plugins/dyn_class.py [out] __main__.Diff __main__.Diff.x + +[case testLiteralTriggersVar] +from typing_extensions import Literal + +x: Literal[1] = 1 +y = 1 +z: Literal[1] = 1 +same: Literal[1] = 1 +class C: + x_class: Literal[1] = 1 + y_class = 1 + z_class: Literal[1] = 1 + same_class: Literal[1] = 1 + def __init__(self) -> None: + self.x_instance: Literal[1] = 1 + self.y_instance = 1 + self.z_instance: Literal[1] = 1 + self.same_instance: Literal[1] = 1 + +[file next.py] +from typing_extensions import Literal + +x = 1 +y: Literal[1] = 1 +z: Literal[2] = 2 +same: Literal[1] = 1 +class C: + x_class = 1 + y_class: Literal[1] = 1 + z_class: Literal[2] = 2 + same_class: Literal[1] = 1 + def __init__(self) -> None: + self.x_instance = 1 + self.y_instance: Literal[1] = 1 + self.z_instance: Literal[2] = 2 + self.same_instance: Literal[1] = 1 +[out] +__main__.C.x_class +__main__.C.x_instance +__main__.C.y_class +__main__.C.y_instance +__main__.C.z_class +__main__.C.z_instance +__main__.x +__main__.y +__main__.z + +[case testLiteralTriggersFunctions] +from typing_extensions import Literal + +def function_1() -> int: pass +def function_2() -> Literal[1]: pass +def function_3() -> Literal[1]: pass + +def function_4(x: int) -> None: pass +def function_5(x: Literal[1]) -> None: pass +def function_6(x: Literal[1]) -> None: pass + +def function_same_1() -> Literal[1]: pass +def function_same_2(x: Literal[1]) -> None: pass + +class C: + def method_1(self) -> int: pass + def method_2(self) -> Literal[1]: pass + def method_3(self) -> Literal[1]: pass + + def method_4(self, x: int) -> None: pass + def method_5(self, x: Literal[1]) -> None: pass + def method_6(self, x: Literal[1]) -> None: pass + + def method_same_1(self) -> Literal[1]: pass + def method_same_2(self, x: Literal[1]) -> None: pass + + @classmethod + def classmethod_1(cls) -> int: pass + @classmethod + def classmethod_2(cls) -> Literal[1]: pass + @classmethod + def classmethod_3(cls) -> Literal[1]: pass + + @classmethod + def classmethod_4(cls, x: int) -> None: pass + @classmethod + def classmethod_5(cls, x: Literal[1]) -> None: pass + @classmethod + def classmethod_6(cls, x: Literal[1]) -> None: pass + + @classmethod + def classmethod_same_1(cls) -> Literal[1]: pass + @classmethod + def classmethod_same_2(cls, x: Literal[1]) -> None: pass + + @staticmethod + def staticmethod_1() -> int: pass + @staticmethod + def staticmethod_2() -> Literal[1]: pass + @staticmethod + def staticmethod_3() -> Literal[1]: pass + + @staticmethod + def staticmethod_4(x: int) -> None: pass + @staticmethod + def staticmethod_5(x: Literal[1]) -> None: pass + @staticmethod + def staticmethod_6(x: Literal[1]) -> None: pass + + @staticmethod + def staticmethod_same_1() -> Literal[1]: pass + @staticmethod + def staticmethod_same_2(x: Literal[1]) -> None: pass + +[file next.py] +from typing_extensions import Literal + +def function_1() -> Literal[1]: pass +def function_2() -> int: pass +def function_3() -> Literal[2]: pass + +def function_4(x: Literal[1]) -> None: pass +def function_5(x: int) -> None: pass +def function_6(x: Literal[2]) -> None: pass + +def function_same_1() -> Literal[1]: pass +def function_same_2(x: Literal[1]) -> None: pass + +class C: + def method_1(self) -> Literal[1]: pass + def method_2(self) -> int: pass + def method_3(self) -> Literal[2]: pass + + def method_4(self, x: Literal[1]) -> None: pass + def method_5(self, x: int) -> None: pass + def method_6(self, x: Literal[2]) -> None: pass + + def method_same_1(self) -> Literal[1]: pass + def method_same_2(self, x: Literal[1]) -> None: pass + + @classmethod + def classmethod_1(cls) -> Literal[1]: pass + @classmethod + def classmethod_2(cls) -> int: pass + @classmethod + def classmethod_3(cls) -> Literal[2]: pass + + @classmethod + def classmethod_4(cls, x: Literal[1]) -> None: pass + @classmethod + def classmethod_5(cls, x: int) -> None: pass + @classmethod + def classmethod_6(cls, x: Literal[2]) -> None: pass + + @classmethod + def classmethod_same_1(cls) -> Literal[1]: pass + @classmethod + def classmethod_same_2(cls, x: Literal[1]) -> None: pass + + @staticmethod + def staticmethod_1() -> Literal[1]: pass + @staticmethod + def staticmethod_2() -> int: pass + @staticmethod + def staticmethod_3() -> Literal[2]: pass + + @staticmethod + def staticmethod_4(x: Literal[1]) -> None: pass + @staticmethod + def staticmethod_5(x: int) -> None: pass + @staticmethod + def staticmethod_6(x: Literal[2]) -> None: pass + + @staticmethod + def staticmethod_same_1() -> Literal[1]: pass + @staticmethod + def staticmethod_same_2(x: Literal[1]) -> None: pass +[builtins fixtures/classmethod.pyi] +[out] +__main__.C.classmethod_1 +__main__.C.classmethod_2 +__main__.C.classmethod_3 +__main__.C.classmethod_4 +__main__.C.classmethod_5 +__main__.C.classmethod_6 +__main__.C.method_1 +__main__.C.method_2 +__main__.C.method_3 +__main__.C.method_4 +__main__.C.method_5 +__main__.C.method_6 +__main__.C.staticmethod_1 +__main__.C.staticmethod_2 +__main__.C.staticmethod_3 +__main__.C.staticmethod_4 +__main__.C.staticmethod_5 +__main__.C.staticmethod_6 +__main__.function_1 +__main__.function_2 +__main__.function_3 +__main__.function_4 +__main__.function_5 +__main__.function_6 + +[case testLiteralTriggersProperty] +from typing_extensions import Literal + +class C: + @property + def p1(self) -> Literal[1]: pass + + @property + def p2(self) -> int: pass + + @property + def same(self) -> Literal[1]: pass + +[file next.py] +from typing_extensions import Literal + +class C: + @property + def p1(self) -> int: pass + + @property + def p2(self) -> Literal[1]: pass + + @property + def same(self) -> Literal[1]: pass +[builtins fixtures/property.pyi] +[out] +__main__.C.p1 +__main__.C.p2 + +[case testLiteralsTriggersOverload] +from typing import overload +from typing_extensions import Literal + +@overload +def func(x: str) -> str: ... +@overload +def func(x: Literal[1]) -> int: ... +def func(x): + pass + +@overload +def func_same(x: str) -> str: ... +@overload +def func_same(x: Literal[1]) -> int: ... +def func_same(x): + pass + +class C: + @overload + def method(self, x: str) -> str: ... + @overload + def method(self, x: Literal[1]) -> int: ... + def method(self, x): + pass + + @overload + def method_same(self, x: str) -> str: ... + @overload + def method_same(self, x: Literal[1]) -> int: ... + def method_same(self, x): + pass + +[file next.py] +from typing import overload +from typing_extensions import Literal + +@overload +def func(x: str) -> str: ... +@overload +def func(x: Literal[2]) -> int: ... +def func(x): + pass + +@overload +def func_same(x: str) -> str: ... +@overload +def func_same(x: Literal[1]) -> int: ... +def func_same(x): + pass + +class C: + @overload + def method(self, x: str) -> str: ... + @overload + def method(self, x: Literal[2]) -> int: ... + def method(self, x): + pass + + @overload + def method_same(self, x: str) -> str: ... + @overload + def method_same(self, x: Literal[1]) -> int: ... + def method_same(self, x): + pass +[out] +__main__.C.method +__main__.func diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 8ac461bc2d59..82aa39cf6648 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -8155,3 +8155,80 @@ x = 'no way' == == main:10: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testLiteralFineGrainedVarConversion] +import mod +reveal_type(mod.x) +[file mod.py] +x = 1 +[file mod.py.2] +from typing_extensions import Literal +x: Literal[1] = 1 +[file mod.py.3] +from typing_extensions import Literal +x: Literal[1] = 2 +[out] +main:2: error: Revealed type is 'builtins.int' +== +main:2: error: Revealed type is 'Literal[1]' +== +mod.py:2: error: Incompatible types in assignment (expression has type "Literal[2]", variable has type "Literal[1]") +main:2: error: Revealed type is 'Literal[1]' + +[case testLiteralFineGrainedFunctionConversion] +from mod import foo +foo(3) +[file mod.py] +def foo(x: int) -> None: pass +[file mod.py.2] +from typing_extensions import Literal +def foo(x: Literal[3]) -> None: pass +[file mod.py.3] +from typing_extensions import Literal +def foo(x: Literal[4]) -> None: pass +[out] +== +== +main:2: error: Argument 1 to "foo" has incompatible type "Literal[3]"; expected "Literal[4]" + +[case testLiteralFineGrainedAlias] +from mod import Alias +a: Alias = 1 +[file mod.py] +Alias = int +[file mod.py.2] +from typing_extensions import Literal +Alias = Literal[1] +[file mod.py.3] +from typing_extensions import Literal +Alias = Literal[2] +[out] +== +== +main:2: error: Incompatible types in assignment (expression has type "Literal[1]", variable has type "Literal[2]") + +[case testLiteralFineGrainedOverload] +from mod import foo +reveal_type(foo(4)) +[file mod.py] +from typing import overload +from typing_extensions import Literal +@overload +def foo(x: int) -> str: ... +@overload +def foo(x: Literal['bar']) -> int: ... +def foo(x): pass +[file mod.py.2] +from typing import overload +from typing_extensions import Literal +@overload +def foo(x: Literal[4]) -> Literal['foo']: ... +@overload +def foo(x: int) -> str: ... +@overload +def foo(x: Literal['bar']) -> int: ... +def foo(x): pass +[out] +main:2: error: Revealed type is 'builtins.str' +== +main:2: error: Revealed type is 'Literal['foo']' diff --git a/test-data/unit/merge.test b/test-data/unit/merge.test index 281006557083..748172efeac3 100644 --- a/test-data/unit/merge.test +++ b/test-data/unit/merge.test @@ -1451,3 +1451,51 @@ TypeInfo<0>( X<3> (builtins.int<4>) Y<6> (builtins.int<4>)) MetaclassType(enum.EnumMeta<5>)) + +[case testLiteralMerge] +import target +[file target.py] +from typing_extensions import Literal +def foo(x: Literal[3]) -> Literal['a']: pass +bar: Literal[4] = 4 +[file target.py.next] +from typing_extensions import Literal +def foo(x: Literal['3']) -> Literal['b']: pass +bar: Literal[5] = 5 +[out] +MypyFile:1<0>( + tmp/main + Import:1(target)) +MypyFile:1<1>( + tmp/target.py + ImportFrom:1(typing_extensions, [Literal]) + FuncDef:2<2>( + foo + Args( + Var(x)) + def (x: Literal[3]) -> Literal['a'] + Block:2<3>( + PassStmt:2<4>())) + AssignmentStmt:3<5>( + NameExpr(bar [target.bar<6>]) + IntExpr(4) + Literal[4])) +==> +MypyFile:1<0>( + tmp/main + Import:1(target)) +MypyFile:1<1>( + tmp/target.py + ImportFrom:1(typing_extensions, [Literal]) + FuncDef:2<2>( + foo + Args( + Var(x)) + def (x: Literal['3']) -> Literal['b'] + Block:2<7>( + PassStmt:2<8>())) + AssignmentStmt:3<9>( + NameExpr(bar [target.bar<6>]) + IntExpr(5) + Literal[5])) + From 299e88ef70e0c94c5b79435fad4ba8c5ef34d41a Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Sun, 30 Dec 2018 14:56:50 -0800 Subject: [PATCH 3/5] Add a few more tests --- test-data/unit/fine-grained.test | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 82aa39cf6648..5625e8d8ff6f 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -8232,3 +8232,43 @@ def foo(x): pass main:2: error: Revealed type is 'builtins.str' == main:2: error: Revealed type is 'Literal['foo']' + +[case testLiteralFineGrainedChainedDefinitions] +from mod1 import foo +reveal_type(foo) +[file mod1.py] +from mod2 import bar +foo = bar +[file mod2.py] +from mod3 import qux as bar +[file mod3.py] +from typing_extensions import Literal +qux: Literal[3] +[file mod3.py.2] +from typing_extensions import Literal +qux: Literal[4] +[out] +main:2: error: Revealed type is 'Literal[3]' +== +main:2: error: Revealed type is 'Literal[4]' + +[case testLiteralFineGrainedChainedAliases] +from mod1 import Alias1 +x: Alias1 +reveal_type(x) +[file mod1.py] +from mod2 import Alias2 +Alias1 = Alias2 +[file mod2.py] +from mod3 import Alias3 +Alias2 = Alias3 +[file mod3.py] +from typing_extensions import Literal +Alias3 = Literal[3] +[file mod3.py.2] +from typing_extensions import Literal +Alias3 = Literal[4] +[out] +main:3: error: Revealed type is 'Literal[3]' +== +main:3: error: Revealed type is 'Literal[4]' From d4a5a81be9a5ab33560a09b4fc2180fbd50bbc77 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Fri, 4 Jan 2019 00:00:39 -0800 Subject: [PATCH 4/5] Add comments to astmerge; add a few more tests --- mypy/server/astmerge.py | 7 +- test-data/unit/fine-grained.test | 106 +++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index a9f755ce84d2..e86de0a7a812 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -328,7 +328,12 @@ def replace_statements(self, nodes: List[Statement]) -> List[Statement]: class TypeReplaceVisitor(SyntheticTypeVisitor[None]): - """Similar to NodeReplaceVisitor, but for type objects.""" + """Similar to NodeReplaceVisitor, but for type objects. + + Note: this class may sometimes be used to analyze types before + semantic analysis has taken place. For example, wee + NodeReplaceVisitor.process_base_func. + """ def __init__(self, replacements: Dict[SymbolNode, SymbolNode]) -> None: self.replacements = replacements diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 1d39f753b491..888841e52285 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -8410,3 +8410,109 @@ Alias3 = Literal[4] main:3: error: Revealed type is 'Literal[3]' == main:3: error: Revealed type is 'Literal[4]' + +[case testLiteralFineGrainedChainedFunctionDefinitions] +from mod1 import func1 +reveal_type(func1()) +[file mod1.py] +from mod2 import func2 as func1 +[file mod2.py] +from mod3 import func3 +func2 = func3 +[file mod3.py] +from typing_extensions import Literal +def func3() -> Literal[3]: pass +[file mod3.py.2] +from typing_extensions import Literal +def func3() -> Literal[4]: pass +[out] +main:2: error: Revealed type is 'Literal[3]' +== +main:2: error: Revealed type is 'Literal[4]' + +[case testLiteralFineGrainedChainedTypeVarInference] +from mod1 import foo +reveal_type(foo) +[file mod1.py] +from typing import TypeVar +from mod2 import bar +T = TypeVar('T', bound=int) +def func(x: T) -> T: return x +foo = func(bar) +[file mod2.py] +bar = 3 +[file mod2.py.2] +from typing_extensions import Literal +bar: Literal[3] = 3 +[out] +main:2: error: Revealed type is 'builtins.int*' +== +main:2: error: Revealed type is 'Literal[3]' + +[case testLiteralFineGrainedStringConversionPython3] +from mod1 import foo +reveal_type(foo) +[file mod1.py] +from mod2 import bar +foo = bar() +[file mod2.py] +from typing_extensions import Literal +def bar() -> Literal["foo"]: pass +[file mod2.py.2] +from typing_extensions import Literal +def bar() -> Literal[u"foo"]: pass +[file mod2.py.3] +from typing_extensions import Literal +def bar() -> Literal[b"foo"]: pass +[out] +main:2: error: Revealed type is 'Literal['foo']' +== +main:2: error: Revealed type is 'Literal['foo']' +== +main:2: error: Revealed type is 'Literal[b'foo']' + +[case testLiteralFineGrainedStringConversionPython2] +# flags: --python-version 2.7 +from mod1 import foo +reveal_type(foo) +[file mod1.py] +from mod2 import bar +foo = bar() +[file mod2.py] +from typing_extensions import Literal +def bar(): + # type: () -> Literal["foo"] + pass +[file mod2.py.2] +from typing_extensions import Literal +def bar(): + # type: () -> Literal[b"foo"] + pass +[file mod2.py.3] +from __future__ import unicode_literals +from typing_extensions import Literal +def bar(): + # type: () -> Literal["foo"] + pass +[file mod2.py.4] +from __future__ import unicode_literals +from typing_extensions import Literal +def bar(): + # type: () -> Literal[b"foo"] + pass +[file mod2.py.5] +from typing_extensions import Literal +def bar(): + # type: () -> Literal[u"foo"] + pass +[out] +main:3: error: Revealed type is 'Literal['foo']' +== +main:3: error: Revealed type is 'Literal['foo']' +== +main:3: error: Revealed type is 'Literal[u'foo']' +== +main:3: error: Revealed type is 'Literal['foo']' +== +main:3: error: Revealed type is 'Literal[u'foo']' + From dc4e505832394dc67e4081778c22f949d0c7995d Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Fri, 4 Jan 2019 09:56:08 -0800 Subject: [PATCH 5/5] Respond to code review --- mypy/server/astmerge.py | 4 ++-- test-data/unit/fine-grained.test | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index e86de0a7a812..32dd7157ccc8 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -330,8 +330,8 @@ def replace_statements(self, nodes: List[Statement]) -> List[Statement]: class TypeReplaceVisitor(SyntheticTypeVisitor[None]): """Similar to NodeReplaceVisitor, but for type objects. - Note: this class may sometimes be used to analyze types before - semantic analysis has taken place. For example, wee + Note: this visitor may sometimes visit unanalyzed types + such as 'UnboundType' and 'RawLiteralType' For example, see NodeReplaceVisitor.process_base_func. """ diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 888841e52285..68306f579273 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -8373,7 +8373,9 @@ main:2: error: Revealed type is 'Literal['foo']' [case testLiteralFineGrainedChainedDefinitions] from mod1 import foo -reveal_type(foo) +from typing_extensions import Literal +def expect_3(x: Literal[3]) -> None: pass +expect_3(foo) [file mod1.py] from mod2 import bar foo = bar @@ -8386,14 +8388,15 @@ qux: Literal[3] from typing_extensions import Literal qux: Literal[4] [out] -main:2: error: Revealed type is 'Literal[3]' == -main:2: error: Revealed type is 'Literal[4]' +main:4: error: Argument 1 to "expect_3" has incompatible type "Literal[4]"; expected "Literal[3]" [case testLiteralFineGrainedChainedAliases] from mod1 import Alias1 +from typing_extensions import Literal x: Alias1 -reveal_type(x) +def expect_3(x: Literal[3]) -> None: pass +expect_3(x) [file mod1.py] from mod2 import Alias2 Alias1 = Alias2 @@ -8407,13 +8410,14 @@ Alias3 = Literal[3] from typing_extensions import Literal Alias3 = Literal[4] [out] -main:3: error: Revealed type is 'Literal[3]' == -main:3: error: Revealed type is 'Literal[4]' +main:5: error: Argument 1 to "expect_3" has incompatible type "Literal[4]"; expected "Literal[3]" [case testLiteralFineGrainedChainedFunctionDefinitions] from mod1 import func1 -reveal_type(func1()) +from typing_extensions import Literal +def expect_3(x: Literal[3]) -> None: pass +expect_3(func1()) [file mod1.py] from mod2 import func2 as func1 [file mod2.py] @@ -8426,9 +8430,8 @@ def func3() -> Literal[3]: pass from typing_extensions import Literal def func3() -> Literal[4]: pass [out] -main:2: error: Revealed type is 'Literal[3]' == -main:2: error: Revealed type is 'Literal[4]' +main:4: error: Argument 1 to "expect_3" has incompatible type "Literal[4]"; expected "Literal[3]" [case testLiteralFineGrainedChainedTypeVarInference] from mod1 import foo