Skip to content

Commit

Permalink
Implement meet for literal types; add tests for 'is_same_type' (#6043)
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
Michael0x2a authored Dec 11, 2018
1 parent 977cfc5 commit 69b26e5
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 2 deletions.
9 changes: 8 additions & 1 deletion mypy/meet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down
76 changes: 75 additions & 1 deletion mypy/test/testtypes.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
"""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
)
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):
Expand Down Expand Up @@ -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)
Expand All @@ -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))
70 changes: 70 additions & 0 deletions test-data/unit/check-literal.test
Original file line number Diff line number Diff line change
Expand Up @@ -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 '<nothing>'
[builtins fixtures/list.pyi]
[out]

0 comments on commit 69b26e5

Please sign in to comment.