From 0e082d18a838ca68648fe0009fa512eea0c6b703 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Sun, 9 Dec 2018 12:58:08 -0800 Subject: [PATCH 1/3] Implement meet for literal types; add tests for 'is_same_type' This pull request implements meets for literal types and adds some corresponding tests. It also adds a test suite for the `is_same_type` method while we're at it. This test suite also lets you test each type's inherent `__eq__` and `__hash__`, since we do also use those throughout the code (especially for literal types). --- mypy/meet.py | 9 +++- mypy/test/testtypes.py | 74 +++++++++++++++++++++++++++++ test-data/unit/check-literal.test | 78 +++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 1 deletion(-) diff --git a/mypy/meet.py b/mypy/meet.py index ccf75eab98e9..b7eae354a5a5 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -453,6 +453,8 @@ def visit_instance(self, t: Instance) -> Type: return meet_types(t, self.s) elif isinstance(self.s, TupleType): return meet_types(t, self.s) + elif isinstance(self.s, LiteralType): + return meet_types(t, self.s) return self.default(self.s) def visit_callable_type(self, t: CallableType) -> Type: @@ -528,7 +530,12 @@ def visit_typeddict_type(self, t: TypedDictType) -> Type: return self.default(self.s) def visit_literal_type(self, t: LiteralType) -> Type: - raise NotImplementedError() + if isinstance(self.s, LiteralType) and self.s == t: + return t + elif isinstance(self.s, Instance) and is_subtype(t.fallback, self.s): + return t + else: + return self.default(self.s) def visit_partial_type(self, t: PartialType) -> Type: # We can't determine the meet of partial types. We should never get here. diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index f6b670126e94..054d0d94b41d 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -7,6 +7,7 @@ from mypy.expandtype import expand_type from mypy.join import join_types, join_simple from mypy.meet import meet_types +from mypy.sametypes import is_same_type from mypy.types import ( UnboundType, AnyType, CallableType, TupleType, TypeVarDef, Type, Instance, NoneTyp, Overloaded, TypeType, UnionType, UninhabitedType, true_only, false_only, TypeVarId, TypeOfAny, LiteralType @@ -14,6 +15,7 @@ from mypy.nodes import ARG_POS, ARG_OPT, ARG_STAR, ARG_STAR2, CONTRAVARIANT, INVARIANT, COVARIANT from mypy.subtypes import is_subtype, is_more_precise, is_proper_subtype from mypy.test.typefixture import TypeFixture, InterfaceTypeFixture +from mypy.experiments import strict_optional_set class TypesSuite(Suite): @@ -846,8 +848,31 @@ def test_type_type(self) -> None: self.assert_meet(self.fx.type_type, self.fx.type_any, self.fx.type_any) self.assert_meet(self.fx.type_b, self.fx.anyt, self.fx.type_b) + def test_literal_type(self) -> None: + a = self.fx.a + d = self.fx.d + lit1 = LiteralType(1, a) + lit2 = LiteralType(2, a) + lit3 = LiteralType("foo", d) + + self.assert_meet(lit1, lit1, lit1) + self.assert_meet(lit1, a, lit1) + self.assert_meet_uninhabited(lit1, lit3) + self.assert_meet_uninhabited(lit1, lit2) + self.assert_meet(UnionType([lit1, lit2]), lit1, lit1) + self.assert_meet(UnionType([lit1, lit2]), UnionType([lit2, lit3]), lit2) + self.assert_meet(UnionType([lit1, lit2]), UnionType([lit1, lit2]), UnionType([lit1, lit2])) + self.assert_meet(lit1, self.fx.anyt, lit1) + self.assert_meet(lit1, self.fx.o, lit1) + # FIX generic interfaces + ranges + def assert_meet_uninhabited(self, s: Type, t: Type) -> None: + with strict_optional_set(False): + self.assert_meet(s, t, self.fx.nonet) + with strict_optional_set(True): + self.assert_meet(s, t, self.fx.uninhabited) + def assert_meet(self, s: Type, t: Type, meet: Type) -> None: self.assert_simple_meet(s, t, meet) self.assert_simple_meet(t, s, meet) @@ -874,3 +899,52 @@ def callable(self, *a: Type) -> CallableType: return CallableType(list(a[:-1]), [ARG_POS] * n, [None] * n, a[-1], self.fx.function) + + +class SameTypeSuite(Suite): + def setUp(self) -> None: + self.fx = TypeFixture() + + def test_literal_type(self) -> None: + a = self.fx.a + b = self.fx.b # Reminder: b is a subclass of a + d = self.fx.d + + # Literals are not allowed to contain floats, but we're going to + # test them anyways, just to make sure the semantics are robust + # against these kinds of things. + lit0 = LiteralType(1.0, a) + lit1 = LiteralType(1, b) + lit2 = LiteralType(2, b) + lit3 = LiteralType("foo", d) + + self.assert_same(lit1, lit1) + self.assert_same(UnionType([lit1, lit2]), UnionType([lit1, lit2])) + self.assert_same(UnionType([lit1, lit2]), UnionType([lit2, lit1])) + self.assert_not_same(lit1, b) + self.assert_not_same(lit0, lit1) + self.assert_not_same(lit1, lit2) + self.assert_not_same(lit1, lit3) + + self.assert_not_same(lit1, self.fx.anyt) + self.assert_not_same(lit1, self.fx.nonet) + + def assert_same(self, s: Type, t: Type, strict=True) -> None: + self.assert_simple_is_same(s, t, expected=True, strict=strict) + self.assert_simple_is_same(t, s, expected=True, strict=strict) + + def assert_not_same(self, s: Type, t: Type, strict=True) -> None: + self.assert_simple_is_same(s, t, False, strict=strict) + self.assert_simple_is_same(t, s, False, strict=strict) + + def assert_simple_is_same(self, s: Type, t: Type, expected: bool, strict: bool) -> None: + actual = is_same_type(s, t) + assert_equal(actual, expected, + 'is_same_type({}, {}) is {{}} ({{}} expected)'.format(s, t)) + + if strict: + actual2 = (s == t) + assert_equal(actual2, expected, + '({} == {}) is {{}} ({{}} expected)'.format(s, t)) + assert_equal(hash(s) == hash(t), expected, + '(hash({}) == hash({}) is {{}} ({{}} expected)'.format(s, t)) diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index c7f35c94ec5e..6690c21a1d98 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -1180,3 +1180,81 @@ b = b * a c = c.strip() # E: Incompatible types in assignment (expression has type "str", variable has type "Literal['foo']") [builtins fixtures/ops.pyi] [out] + +-- +-- Other misc interactions +-- + +[case testLiteralMeetsWithCallablesInLists] +from typing import List, Callable, Union +from typing_extensions import Literal + +a: Callable[[Literal[1]], int] +b: Callable[[Literal[2]], str] +c: Callable[[int], str] +d: Callable[[object], str] +e: Callable[[Union[Literal[1], Literal[2]]], str] + +arr1 = [a, a] +arr2 = [a, b] +arr3 = [a, c] +arr4 = [a, d] +arr5 = [a, e] + +reveal_type(arr1) # E: Revealed type is 'builtins.list[def (Literal[1]) -> builtins.int]' +reveal_type(arr2) # E: Revealed type is 'builtins.list[builtins.function*]' +reveal_type(arr3) # E: Revealed type is 'builtins.list[def (Literal[1]) -> builtins.object]' +reveal_type(arr4) # E: Revealed type is 'builtins.list[def (Literal[1]) -> builtins.object]' +reveal_type(arr5) # E: Revealed type is 'builtins.list[def (Literal[1]) -> builtins.object]' + +lit: Literal[1] +reveal_type(arr1[0](lit)) # E: Revealed type is 'builtins.int' +reveal_type(arr2[0](lit)) # E: Revealed type is 'Any' \ + # E: Cannot call function of unknown type +reveal_type(arr3[0](lit)) # E: Revealed type is 'builtins.object' +reveal_type(arr4[0](lit)) # E: Revealed type is 'builtins.object' +reveal_type(arr5[0](lit)) # E: Revealed type is 'builtins.object' +[builtins fixtures/list.pyi] +[out] + +[case testLiteralMeetsWithTypeVars] +from typing import TypeVar, Callable, Union +from typing_extensions import Literal + +T = TypeVar('T') +def unify(func: Callable[[T, T], None]) -> T: pass + +def f1(x: Literal[1], y: Literal[1]) -> None: pass +def f2(x: Literal[1], y: Literal[2]) -> None: pass +def f3(x: Literal[1], y: int) -> None: pass +def f4(x: Literal[1], y: object) -> None: pass +def f5(x: Literal[1], y: Union[Literal[1], Literal[2]]) -> None: pass + +reveal_type(unify(f1)) # E: Revealed type is 'Literal[1]' +reveal_type(unify(f2)) # E: Revealed type is 'None' +reveal_type(unify(f3)) # E: Revealed type is 'Literal[1]' +reveal_type(unify(f4)) # E: Revealed type is 'Literal[1]' +reveal_type(unify(f5)) # E: Revealed type is 'Literal[1]' +[out] + +[case testLiteralMeetsWithStrictOptional] +# flags: --strict-optional +from typing import TypeVar, Callable, Union +from typing_extensions import Literal + +a: Callable[[Literal[1]], int] +b: Callable[[Literal[2]], str] +lit: Literal[1] + +arr = [a, b] +reveal_type(arr) # E: Revealed type is 'builtins.list[builtins.function*]' +reveal_type(arr[0](lit)) # E: Revealed type is 'Any' \ + # E: Cannot call function of unknown type + +T = TypeVar('T') +def unify(func: Callable[[T, T], None]) -> T: pass +def func(x: Literal[1], y: Literal[2]) -> None: pass + +reveal_type(unify(func)) # E: Revealed type is '' +[builtins fixtures/list.pyi] +[out] From 8c096a9379d8f27ed441ae1e48ecd67d07ff1402 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Sun, 9 Dec 2018 13:42:47 -0800 Subject: [PATCH 2/3] Fix self test --- mypy/test/testtypes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 054d0d94b41d..3ebf3c5d1961 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -1,6 +1,6 @@ """Test cases for mypy types and type operations.""" -from typing import List, Tuple +from typing import List, Tuple, cast from mypy.test.helpers import Suite, assert_equal, assert_true, assert_false, assert_type, skip from mypy.erasetype import erase_type @@ -913,7 +913,7 @@ def test_literal_type(self) -> None: # Literals are not allowed to contain floats, but we're going to # test them anyways, just to make sure the semantics are robust # against these kinds of things. - lit0 = LiteralType(1.0, a) + lit0 = LiteralType(cast(int, 1.0), a) lit1 = LiteralType(1, b) lit2 = LiteralType(2, b) lit3 = LiteralType("foo", d) @@ -929,11 +929,11 @@ def test_literal_type(self) -> None: self.assert_not_same(lit1, self.fx.anyt) self.assert_not_same(lit1, self.fx.nonet) - def assert_same(self, s: Type, t: Type, strict=True) -> None: + def assert_same(self, s: Type, t: Type, strict: bool = True) -> None: self.assert_simple_is_same(s, t, expected=True, strict=strict) self.assert_simple_is_same(t, s, expected=True, strict=strict) - def assert_not_same(self, s: Type, t: Type, strict=True) -> None: + def assert_not_same(self, s: Type, t: Type, strict: bool = True) -> None: self.assert_simple_is_same(s, t, False, strict=strict) self.assert_simple_is_same(t, s, False, strict=strict) From 70d2176038bc63588318f629e8cace33d043f4c4 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Tue, 11 Dec 2018 09:02:41 -0800 Subject: [PATCH 3/3] Respond to code review --- test-data/unit/check-literal.test | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index ca35d2055b12..611062d30b4f 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -1245,8 +1245,8 @@ indirect.Literal() -- Other misc interactions -- -[case testLiteralMeetsWithCallablesInLists] -from typing import List, Callable, Union +[case testLiteralMeets] +from typing import TypeVar, List, Callable, Union from typing_extensions import Literal a: Callable[[Literal[1]], int] @@ -1267,19 +1267,10 @@ reveal_type(arr3) # E: Revealed type is 'builtins.list[def (Literal[1]) -> buil reveal_type(arr4) # E: Revealed type is 'builtins.list[def (Literal[1]) -> builtins.object]' reveal_type(arr5) # E: Revealed type is 'builtins.list[def (Literal[1]) -> builtins.object]' +# Inspect just only one interesting one lit: Literal[1] -reveal_type(arr1[0](lit)) # E: Revealed type is 'builtins.int' reveal_type(arr2[0](lit)) # E: Revealed type is 'Any' \ # E: Cannot call function of unknown type -reveal_type(arr3[0](lit)) # E: Revealed type is 'builtins.object' -reveal_type(arr4[0](lit)) # E: Revealed type is 'builtins.object' -reveal_type(arr5[0](lit)) # E: Revealed type is 'builtins.object' -[builtins fixtures/list.pyi] -[out] - -[case testLiteralMeetsWithTypeVars] -from typing import TypeVar, Callable, Union -from typing_extensions import Literal T = TypeVar('T') def unify(func: Callable[[T, T], None]) -> T: pass @@ -1295,6 +1286,7 @@ reveal_type(unify(f2)) # E: Revealed type is 'None' reveal_type(unify(f3)) # E: Revealed type is 'Literal[1]' reveal_type(unify(f4)) # E: Revealed type is 'Literal[1]' reveal_type(unify(f5)) # E: Revealed type is 'Literal[1]' +[builtins fixtures/list.pyi] [out] [case testLiteralMeetsWithStrictOptional]