Skip to content

Commit 69b26e5

Browse files
authored
Implement meet for literal types; add tests for 'is_same_type' (#6043)
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).
1 parent 977cfc5 commit 69b26e5

File tree

3 files changed

+153
-2
lines changed

3 files changed

+153
-2
lines changed

mypy/meet.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,8 @@ def visit_instance(self, t: Instance) -> Type:
453453
return meet_types(t, self.s)
454454
elif isinstance(self.s, TupleType):
455455
return meet_types(t, self.s)
456+
elif isinstance(self.s, LiteralType):
457+
return meet_types(t, self.s)
456458
return self.default(self.s)
457459

458460
def visit_callable_type(self, t: CallableType) -> Type:
@@ -528,7 +530,12 @@ def visit_typeddict_type(self, t: TypedDictType) -> Type:
528530
return self.default(self.s)
529531

530532
def visit_literal_type(self, t: LiteralType) -> Type:
531-
raise NotImplementedError()
533+
if isinstance(self.s, LiteralType) and self.s == t:
534+
return t
535+
elif isinstance(self.s, Instance) and is_subtype(t.fallback, self.s):
536+
return t
537+
else:
538+
return self.default(self.s)
532539

533540
def visit_partial_type(self, t: PartialType) -> Type:
534541
# We can't determine the meet of partial types. We should never get here.

mypy/test/testtypes.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
"""Test cases for mypy types and type operations."""
22

3-
from typing import List, Tuple
3+
from typing import List, Tuple, cast
44

55
from mypy.test.helpers import Suite, assert_equal, assert_true, assert_false, assert_type, skip
66
from mypy.erasetype import erase_type
77
from mypy.expandtype import expand_type
88
from mypy.join import join_types, join_simple
99
from mypy.meet import meet_types
10+
from mypy.sametypes import is_same_type
1011
from mypy.types import (
1112
UnboundType, AnyType, CallableType, TupleType, TypeVarDef, Type, Instance, NoneTyp, Overloaded,
1213
TypeType, UnionType, UninhabitedType, true_only, false_only, TypeVarId, TypeOfAny, LiteralType
1314
)
1415
from mypy.nodes import ARG_POS, ARG_OPT, ARG_STAR, ARG_STAR2, CONTRAVARIANT, INVARIANT, COVARIANT
1516
from mypy.subtypes import is_subtype, is_more_precise, is_proper_subtype
1617
from mypy.test.typefixture import TypeFixture, InterfaceTypeFixture
18+
from mypy.experiments import strict_optional_set
1719

1820

1921
class TypesSuite(Suite):
@@ -846,8 +848,31 @@ def test_type_type(self) -> None:
846848
self.assert_meet(self.fx.type_type, self.fx.type_any, self.fx.type_any)
847849
self.assert_meet(self.fx.type_b, self.fx.anyt, self.fx.type_b)
848850

851+
def test_literal_type(self) -> None:
852+
a = self.fx.a
853+
d = self.fx.d
854+
lit1 = LiteralType(1, a)
855+
lit2 = LiteralType(2, a)
856+
lit3 = LiteralType("foo", d)
857+
858+
self.assert_meet(lit1, lit1, lit1)
859+
self.assert_meet(lit1, a, lit1)
860+
self.assert_meet_uninhabited(lit1, lit3)
861+
self.assert_meet_uninhabited(lit1, lit2)
862+
self.assert_meet(UnionType([lit1, lit2]), lit1, lit1)
863+
self.assert_meet(UnionType([lit1, lit2]), UnionType([lit2, lit3]), lit2)
864+
self.assert_meet(UnionType([lit1, lit2]), UnionType([lit1, lit2]), UnionType([lit1, lit2]))
865+
self.assert_meet(lit1, self.fx.anyt, lit1)
866+
self.assert_meet(lit1, self.fx.o, lit1)
867+
849868
# FIX generic interfaces + ranges
850869

870+
def assert_meet_uninhabited(self, s: Type, t: Type) -> None:
871+
with strict_optional_set(False):
872+
self.assert_meet(s, t, self.fx.nonet)
873+
with strict_optional_set(True):
874+
self.assert_meet(s, t, self.fx.uninhabited)
875+
851876
def assert_meet(self, s: Type, t: Type, meet: Type) -> None:
852877
self.assert_simple_meet(s, t, meet)
853878
self.assert_simple_meet(t, s, meet)
@@ -874,3 +899,52 @@ def callable(self, *a: Type) -> CallableType:
874899
return CallableType(list(a[:-1]),
875900
[ARG_POS] * n, [None] * n,
876901
a[-1], self.fx.function)
902+
903+
904+
class SameTypeSuite(Suite):
905+
def setUp(self) -> None:
906+
self.fx = TypeFixture()
907+
908+
def test_literal_type(self) -> None:
909+
a = self.fx.a
910+
b = self.fx.b # Reminder: b is a subclass of a
911+
d = self.fx.d
912+
913+
# Literals are not allowed to contain floats, but we're going to
914+
# test them anyways, just to make sure the semantics are robust
915+
# against these kinds of things.
916+
lit0 = LiteralType(cast(int, 1.0), a)
917+
lit1 = LiteralType(1, b)
918+
lit2 = LiteralType(2, b)
919+
lit3 = LiteralType("foo", d)
920+
921+
self.assert_same(lit1, lit1)
922+
self.assert_same(UnionType([lit1, lit2]), UnionType([lit1, lit2]))
923+
self.assert_same(UnionType([lit1, lit2]), UnionType([lit2, lit1]))
924+
self.assert_not_same(lit1, b)
925+
self.assert_not_same(lit0, lit1)
926+
self.assert_not_same(lit1, lit2)
927+
self.assert_not_same(lit1, lit3)
928+
929+
self.assert_not_same(lit1, self.fx.anyt)
930+
self.assert_not_same(lit1, self.fx.nonet)
931+
932+
def assert_same(self, s: Type, t: Type, strict: bool = True) -> None:
933+
self.assert_simple_is_same(s, t, expected=True, strict=strict)
934+
self.assert_simple_is_same(t, s, expected=True, strict=strict)
935+
936+
def assert_not_same(self, s: Type, t: Type, strict: bool = True) -> None:
937+
self.assert_simple_is_same(s, t, False, strict=strict)
938+
self.assert_simple_is_same(t, s, False, strict=strict)
939+
940+
def assert_simple_is_same(self, s: Type, t: Type, expected: bool, strict: bool) -> None:
941+
actual = is_same_type(s, t)
942+
assert_equal(actual, expected,
943+
'is_same_type({}, {}) is {{}} ({{}} expected)'.format(s, t))
944+
945+
if strict:
946+
actual2 = (s == t)
947+
assert_equal(actual2, expected,
948+
'({} == {}) is {{}} ({{}} expected)'.format(s, t))
949+
assert_equal(hash(s) == hash(t), expected,
950+
'(hash({}) == hash({}) is {{}} ({{}} expected)'.format(s, t))

test-data/unit/check-literal.test

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,3 +1240,73 @@ indirect.Literal()
12401240
[builtins fixtures/isinstancelist.pyi]
12411241
[out]
12421242

1243+
1244+
--
1245+
-- Other misc interactions
1246+
--
1247+
1248+
[case testLiteralMeets]
1249+
from typing import TypeVar, List, Callable, Union
1250+
from typing_extensions import Literal
1251+
1252+
a: Callable[[Literal[1]], int]
1253+
b: Callable[[Literal[2]], str]
1254+
c: Callable[[int], str]
1255+
d: Callable[[object], str]
1256+
e: Callable[[Union[Literal[1], Literal[2]]], str]
1257+
1258+
arr1 = [a, a]
1259+
arr2 = [a, b]
1260+
arr3 = [a, c]
1261+
arr4 = [a, d]
1262+
arr5 = [a, e]
1263+
1264+
reveal_type(arr1) # E: Revealed type is 'builtins.list[def (Literal[1]) -> builtins.int]'
1265+
reveal_type(arr2) # E: Revealed type is 'builtins.list[builtins.function*]'
1266+
reveal_type(arr3) # E: Revealed type is 'builtins.list[def (Literal[1]) -> builtins.object]'
1267+
reveal_type(arr4) # E: Revealed type is 'builtins.list[def (Literal[1]) -> builtins.object]'
1268+
reveal_type(arr5) # E: Revealed type is 'builtins.list[def (Literal[1]) -> builtins.object]'
1269+
1270+
# Inspect just only one interesting one
1271+
lit: Literal[1]
1272+
reveal_type(arr2[0](lit)) # E: Revealed type is 'Any' \
1273+
# E: Cannot call function of unknown type
1274+
1275+
T = TypeVar('T')
1276+
def unify(func: Callable[[T, T], None]) -> T: pass
1277+
1278+
def f1(x: Literal[1], y: Literal[1]) -> None: pass
1279+
def f2(x: Literal[1], y: Literal[2]) -> None: pass
1280+
def f3(x: Literal[1], y: int) -> None: pass
1281+
def f4(x: Literal[1], y: object) -> None: pass
1282+
def f5(x: Literal[1], y: Union[Literal[1], Literal[2]]) -> None: pass
1283+
1284+
reveal_type(unify(f1)) # E: Revealed type is 'Literal[1]'
1285+
reveal_type(unify(f2)) # E: Revealed type is 'None'
1286+
reveal_type(unify(f3)) # E: Revealed type is 'Literal[1]'
1287+
reveal_type(unify(f4)) # E: Revealed type is 'Literal[1]'
1288+
reveal_type(unify(f5)) # E: Revealed type is 'Literal[1]'
1289+
[builtins fixtures/list.pyi]
1290+
[out]
1291+
1292+
[case testLiteralMeetsWithStrictOptional]
1293+
# flags: --strict-optional
1294+
from typing import TypeVar, Callable, Union
1295+
from typing_extensions import Literal
1296+
1297+
a: Callable[[Literal[1]], int]
1298+
b: Callable[[Literal[2]], str]
1299+
lit: Literal[1]
1300+
1301+
arr = [a, b]
1302+
reveal_type(arr) # E: Revealed type is 'builtins.list[builtins.function*]'
1303+
reveal_type(arr[0](lit)) # E: Revealed type is 'Any' \
1304+
# E: Cannot call function of unknown type
1305+
1306+
T = TypeVar('T')
1307+
def unify(func: Callable[[T, T], None]) -> T: pass
1308+
def func(x: Literal[1], y: Literal[2]) -> None: pass
1309+
1310+
reveal_type(unify(func)) # E: Revealed type is '<nothing>'
1311+
[builtins fixtures/list.pyi]
1312+
[out]

0 commit comments

Comments
 (0)