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..3ebf3c5d1961 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -1,12 +1,13 @@ """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 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(cast(int, 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: 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: bool = 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 fbb50cad0d7c..611062d30b4f 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -1240,3 +1240,73 @@ indirect.Literal() [builtins fixtures/isinstancelist.pyi] [out] + +-- +-- Other misc interactions +-- + +[case testLiteralMeets] +from typing import TypeVar, 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]' + +# Inspect just only one interesting one +lit: Literal[1] +reveal_type(arr2[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 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]' +[builtins fixtures/list.pyi] +[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]