Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LiteralType and stub methods to type visitors #5934

Merged
merged 2 commits into from
Nov 22, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from mypy.types import (
CallableType, Type, TypeVisitor, UnboundType, AnyType, NoneTyp, TypeVarType, Instance,
TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType, DeletedType,
UninhabitedType, TypeType, TypeVarId, TypeQuery, is_named_instance, TypeOfAny
UninhabitedType, TypeType, TypeVarId, TypeQuery, is_named_instance, TypeOfAny, LiteralType,
)
from mypy.maptype import map_instance_to_supertype
from mypy import nodes
Expand Down Expand Up @@ -506,6 +506,9 @@ def visit_typeddict_type(self, template: TypedDictType) -> List[Constraint]:
else:
return []

def visit_literal_type(self, template: LiteralType) -> List[Constraint]:
raise NotImplementedError()

def visit_union_type(self, template: UnionType) -> List[Constraint]:
assert False, ("Unexpected UnionType in ConstraintBuilderVisitor"
" (should have been handled in infer_constraints)")
Expand Down
6 changes: 5 additions & 1 deletion mypy/erasetype.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from mypy.types import (
Type, TypeVisitor, UnboundType, AnyType, NoneTyp, TypeVarId, Instance, TypeVarType,
CallableType, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType,
DeletedType, TypeTranslator, UninhabitedType, TypeType, TypeOfAny
DeletedType, TypeTranslator, UninhabitedType, TypeType, TypeOfAny, LiteralType,
)
from mypy.nodes import ARG_STAR, ARG_STAR2

Expand Down Expand Up @@ -78,6 +78,10 @@ def visit_tuple_type(self, t: TupleType) -> Type:
def visit_typeddict_type(self, t: TypedDictType) -> Type:
return t.fallback.accept(self)

def visit_literal_type(self, t: LiteralType) -> Type:
# TODO: Verify this implementation is correct
return t.fallback.accept(self)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe return t unmodified instead.


def visit_union_type(self, t: UnionType) -> Type:
erased_items = [erase_type(item) for item in t.items]
return UnionType.make_simplified_union(erased_items)
Expand Down
6 changes: 5 additions & 1 deletion mypy/expandtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Type, Instance, CallableType, TypeVisitor, UnboundType, AnyType,
NoneTyp, TypeVarType, Overloaded, TupleType, TypedDictType, UnionType,
ErasedType, PartialType, DeletedType, UninhabitedType, TypeType, TypeVarId,
FunctionLike, TypeVarDef
FunctionLike, TypeVarDef, LiteralType,
)


Expand Down Expand Up @@ -111,6 +111,10 @@ def visit_tuple_type(self, t: TupleType) -> Type:
def visit_typeddict_type(self, t: TypedDictType) -> Type:
return t.copy_modified(item_types=self.expand_types(t.items.values()))

def visit_literal_type(self, t: LiteralType) -> Type:
# TODO: Verify this implementation is correct
return t

def visit_union_type(self, t: UnionType) -> Type:
# After substituting for type variables in t.items,
# some of the resulting types might be subtypes of others.
Expand Down
5 changes: 4 additions & 1 deletion mypy/fixup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
)
from mypy.types import (
CallableType, Instance, Overloaded, TupleType, TypedDictType,
TypeVarType, UnboundType, UnionType, TypeVisitor,
TypeVarType, UnboundType, UnionType, TypeVisitor, LiteralType,
TypeType, NOT_READY
)
from mypy.visitor import NodeVisitor
Expand Down Expand Up @@ -206,6 +206,9 @@ def visit_typeddict_type(self, tdt: TypedDictType) -> None:
if tdt.fallback is not None:
tdt.fallback.accept(self)

def visit_literal_type(self, lt: LiteralType) -> None:
lt.fallback.accept(self)

def visit_type_var(self, tvt: TypeVarType) -> None:
if tvt.values:
for vt in tvt.values:
Expand Down
3 changes: 3 additions & 0 deletions mypy/indirection.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ def visit_tuple_type(self, t: types.TupleType) -> Set[str]:
def visit_typeddict_type(self, t: types.TypedDictType) -> Set[str]:
return self._visit(t.items.values()) | self._visit(t.fallback)

def visit_literal_type(self, t: types.LiteralType) -> Set[str]:
return self._visit(t.fallback)

def visit_star_type(self, t: types.StarType) -> Set[str]:
return set()

Expand Down
7 changes: 5 additions & 2 deletions mypy/join.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

from mypy.types import (
Type, AnyType, NoneTyp, TypeVisitor, Instance, UnboundType, TypeVarType, CallableType,
TupleType, TypedDictType, ErasedType, UnionType, FunctionLike, Overloaded,
PartialType, DeletedType, UninhabitedType, TypeType, true_or_false, TypeOfAny
TupleType, TypedDictType, ErasedType, UnionType, FunctionLike, Overloaded, LiteralType,
PartialType, DeletedType, UninhabitedType, TypeType, true_or_false, TypeOfAny,
)
from mypy.maptype import map_instance_to_supertype
from mypy.subtypes import (
Expand Down Expand Up @@ -267,6 +267,9 @@ def visit_typeddict_type(self, t: TypedDictType) -> Type:
else:
return self.default(self.s)

def visit_literal_type(self, t: LiteralType) -> Type:
raise NotImplementedError()

def visit_partial_type(self, t: PartialType) -> Type:
# We only have partial information so we can't decide the join result. We should
# never get here.
Expand Down
5 changes: 4 additions & 1 deletion mypy/meet.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from mypy.types import (
Type, AnyType, TypeVisitor, UnboundType, NoneTyp, TypeVarType, Instance, CallableType,
TupleType, TypedDictType, ErasedType, UnionType, PartialType, DeletedType,
UninhabitedType, TypeType, TypeOfAny, Overloaded, FunctionLike,
UninhabitedType, TypeType, TypeOfAny, Overloaded, FunctionLike, LiteralType,
)
from mypy.subtypes import (
is_equivalent, is_subtype, is_protocol_implementation, is_callable_compatible,
Expand Down Expand Up @@ -520,6 +520,9 @@ def visit_typeddict_type(self, t: TypedDictType) -> Type:
else:
return self.default(self.s)

def visit_literal_type(self, t: LiteralType) -> Type:
raise NotImplementedError()

def visit_partial_type(self, t: PartialType) -> Type:
# We can't determine the meet of partial types. We should never get here.
assert False, 'Internal error'
Expand Down
10 changes: 9 additions & 1 deletion mypy/sametypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from mypy.types import (
Type, UnboundType, AnyType, NoneTyp, TupleType, TypedDictType,
UnionType, CallableType, TypeVarType, Instance, TypeVisitor, ErasedType,
Overloaded, PartialType, DeletedType, UninhabitedType, TypeType
Overloaded, PartialType, DeletedType, UninhabitedType, TypeType, LiteralType,
)


Expand Down Expand Up @@ -114,6 +114,14 @@ def visit_typeddict_type(self, left: TypedDictType) -> bool:
else:
return False

def visit_literal_type(self, left: LiteralType) -> bool:
if isinstance(self.right, LiteralType):
if left.value != self.right.value:
return False
return is_same_type(left.fallback, self.right.fallback)
else:
return False

def visit_union_type(self, left: UnionType) -> bool:
if isinstance(self.right, UnionType):
# Check that everything in left is in right
Expand Down
5 changes: 4 additions & 1 deletion mypy/server/astdiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method'
from mypy.types import (
Type, TypeVisitor, UnboundType, AnyType, NoneTyp, UninhabitedType,
ErasedType, DeletedType, Instance, TypeVarType, CallableType, TupleType, TypedDictType,
UnionType, Overloaded, PartialType, TypeType
UnionType, Overloaded, PartialType, TypeType, LiteralType,
)
from mypy.util import get_prefix

Expand Down Expand Up @@ -315,6 +315,9 @@ def visit_typeddict_type(self, typ: TypedDictType) -> SnapshotItem:
required = tuple(sorted(typ.required_keys))
return ('TypedDictType', items, required)

def visit_literal_type(self, typ: LiteralType) -> SnapshotItem:
return ('LiteralType', typ.value, snapshot_type(typ.fallback))

def visit_union_type(self, typ: UnionType) -> SnapshotItem:
# Sort and remove duplicates so that we can use equality to test for
# equivalent union type snapshots.
Expand Down
5 changes: 4 additions & 1 deletion mypy/server/astmerge.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
from mypy.types import (
Type, SyntheticTypeVisitor, Instance, AnyType, NoneTyp, CallableType, DeletedType, PartialType,
TupleType, TypeType, TypeVarType, TypedDictType, UnboundType, UninhabitedType, UnionType,
Overloaded, TypeVarDef, TypeList, CallableArgument, EllipsisType, StarType
Overloaded, TypeVarDef, TypeList, CallableArgument, EllipsisType, StarType, LiteralType,
)
from mypy.util import get_prefix, replace_object_state
from mypy.typestate import TypeState
Expand Down Expand Up @@ -391,6 +391,9 @@ def visit_typeddict_type(self, typ: TypedDictType) -> None:
value_type.accept(self)
typ.fallback.accept(self)

def visit_literal_type(self, typ: LiteralType) -> None:
typ.fallback.accept(self)

def visit_unbound_type(self, typ: UnboundType) -> None:
for arg in typ.args:
arg.accept(self)
Expand Down
5 changes: 4 additions & 1 deletion mypy/server/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class 'mod.Cls'. This can also refer to an attribute inherited from a
from mypy.types import (
Type, Instance, AnyType, NoneTyp, TypeVisitor, CallableType, DeletedType, PartialType,
TupleType, TypeType, TypeVarType, TypedDictType, UnboundType, UninhabitedType, UnionType,
FunctionLike, ForwardRef, Overloaded, TypeOfAny
FunctionLike, ForwardRef, Overloaded, TypeOfAny, LiteralType,
)
from mypy.server.trigger import make_trigger, make_wildcard_trigger
from mypy.util import correct_relative_import
Expand Down Expand Up @@ -949,6 +949,9 @@ def visit_typeddict_type(self, typ: TypedDictType) -> List[str]:
triggers.extend(self.get_type_triggers(typ.fallback))
return triggers

def visit_literal_type(self, typ: LiteralType) -> List[str]:
return self.get_type_triggers(typ.fallback)

def visit_unbound_type(self, typ: UnboundType) -> List[str]:
return []

Expand Down
8 changes: 7 additions & 1 deletion mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Type, AnyType, UnboundType, TypeVisitor, FormalArgument, NoneTyp, function_type,
Instance, TypeVarType, CallableType, TupleType, TypedDictType, UnionType, Overloaded,
ErasedType, PartialType, DeletedType, UninhabitedType, TypeType, is_named_instance,
FunctionLike, TypeOfAny
FunctionLike, TypeOfAny, LiteralType,
)
import mypy.applytype
import mypy.constraints
Expand Down Expand Up @@ -327,6 +327,9 @@ def visit_typeddict_type(self, left: TypedDictType) -> bool:
else:
return False

def visit_literal_type(self, t: LiteralType) -> bool:
raise NotImplementedError()

def visit_overloaded(self, left: Overloaded) -> bool:
right = self.right
if isinstance(right, Instance):
Expand Down Expand Up @@ -1168,6 +1171,9 @@ def visit_typeddict_type(self, left: TypedDictType) -> bool:
return True
return self._is_proper_subtype(left.fallback, right)

def visit_literal_type(self, left: LiteralType) -> bool:
raise NotImplementedError()

def visit_overloaded(self, left: Overloaded) -> bool:
# TODO: What's the right thing to do here?
return False
Expand Down
19 changes: 18 additions & 1 deletion mypy/type_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
T = TypeVar('T')

from mypy.types import (
Type, AnyType, CallableType, FunctionLike, Overloaded, TupleType, TypedDictType,
Type, AnyType, CallableType, FunctionLike, Overloaded, TupleType, TypedDictType, LiteralType,
Instance, NoneTyp, TypeType, TypeOfAny,
UnionType, TypeVarId, TypeVarType, PartialType, DeletedType, UninhabitedType, TypeVarDef,
UnboundType, ErasedType, ForwardRef, StarType, EllipsisType, TypeList, CallableArgument,
Expand Down Expand Up @@ -85,6 +85,10 @@ def visit_tuple_type(self, t: TupleType) -> T:
def visit_typeddict_type(self, t: TypedDictType) -> T:
pass

@abstractmethod
def visit_literal_type(self, t: LiteralType) -> T:
pass

@abstractmethod
def visit_union_type(self, t: UnionType) -> T:
pass
Expand Down Expand Up @@ -181,6 +185,16 @@ def visit_typeddict_type(self, t: TypedDictType) -> Type:
cast(Any, t.fallback.accept(self)),
t.line, t.column)

def visit_literal_type(self, t: LiteralType) -> Type:
fallback = t.fallback.accept(self)
assert isinstance(fallback, Instance)
return LiteralType(
value=t.value,
fallback=fallback,
line=t.line,
column=t.column,
)

def visit_union_type(self, t: UnionType) -> Type:
return UnionType(self.translate_types(t.items), t.line, t.column)

Expand Down Expand Up @@ -264,6 +278,9 @@ def visit_tuple_type(self, t: TupleType) -> T:
def visit_typeddict_type(self, t: TypedDictType) -> T:
return self.query_types(t.items.values())

def visit_literal_type(self, t: LiteralType) -> T:
return self.strategy([])

def visit_star_type(self, t: StarType) -> T:
return t.type.accept(self)

Expand Down
9 changes: 8 additions & 1 deletion mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
Type, UnboundType, TypeVarType, TupleType, TypedDictType, UnionType, Instance, AnyType,
CallableType, NoneTyp, DeletedType, TypeList, TypeVarDef, TypeVisitor, SyntheticTypeVisitor,
StarType, PartialType, EllipsisType, UninhabitedType, TypeType, get_typ_args, set_typ_args,
CallableArgument, get_type_vars, TypeQuery, union_items, TypeOfAny, ForwardRef, Overloaded
CallableArgument, get_type_vars, TypeQuery, union_items, TypeOfAny, ForwardRef, Overloaded,
LiteralType,
)

from mypy.nodes import (
Expand Down Expand Up @@ -459,6 +460,9 @@ def visit_typeddict_type(self, t: TypedDictType) -> Type:
])
return TypedDictType(items, set(t.required_keys), t.fallback)

def visit_literal_type(self, t: LiteralType) -> Type:
raise NotImplementedError()

def visit_star_type(self, t: StarType) -> Type:
return StarType(self.anal_type(t.type), t.line)

Expand Down Expand Up @@ -754,6 +758,9 @@ def visit_typeddict_type(self, t: TypedDictType) -> None:
for item_type in t.items.values():
item_type.accept(self)

def visit_literal_type(self, t: LiteralType) -> None:
raise NotImplementedError()

def visit_union_type(self, t: UnionType) -> None:
for item in t.items:
item.accept(self)
Expand Down
53 changes: 53 additions & 0 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@

JsonDict = Dict[str, Any]

# The set of all valid expressions that can currently be contained
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add TODO comments here about some things? At least enum support is probably not going to happen immediately, so maybe it should be as a TODO comment?

# inside of a Literal[...].
#
# Literals can contain enum-values: we special-case those and
# store the value as a string.
#
# Note: this type also happens to correspond to types that can be
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is very useful, thanks!

# directly converted into JSON. The serialize/deserialize methods
# of 'LiteralType' rely on this, as well 'server.astdiff.SnapshotTypeVisitor'
# and 'types.TypeStrVisitor'. If we end up adding any non-JSON-serializable
# types to this list, we should make sure to edit those methods to match.
LiteralInnerExpr = Union[int, str, bool, None]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we need to be able to distinguish between unicode and str literals in Python 2? For example, the open() mode argument could well be a unicode literal if using from __future__ import unicode_literals.

A name like LiteralValue would perhaps be a bit more descriptive. The current name implies to me that this is an Expression object, which is not the case.


# If we only import type_visitor in the middle of the file, mypy
# breaks, and if we do it at the top, it breaks at runtime because of
# import cycle issues, so we do it at the top while typechecking and
Expand Down Expand Up @@ -1240,6 +1253,43 @@ def zipall(self, right: 'TypedDictType') \
yield (item_name, None, right_item_type)


class LiteralType(Type):
__slots__ = ('value', 'fallback')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add docstring or at least a TODO comment about documenting this.


def __init__(self, value: LiteralInnerExpr, fallback: Instance,
line: int = -1, column: int = -1) -> None:
super().__init__(line, column)
self.value = value
self.fallback = fallback

def accept(self, visitor: 'TypeVisitor[T]') -> T:
return visitor.visit_literal_type(self)

def __hash__(self) -> int:
return hash((self.value, self.fallback))

def __eq__(self, other: object) -> bool:
if isinstance(other, LiteralType):
return self.fallback == other.fallback and self.value == other.value
else:
return NotImplemented

def serialize(self) -> Union[JsonDict, str]:
return {
'.class': 'LiteralType',
'value': self.value,
'fallback': self.fallback.serialize(),
}

@classmethod
def deserialize(cls, data: JsonDict) -> 'LiteralType':
assert data['.class'] == 'LiteralType'
return LiteralType(
value=data['value'],
fallback=Instance.deserialize(data['fallback']),
)


class StarType(Type):
"""The star type *type_parameter.

Expand Down Expand Up @@ -1693,6 +1743,9 @@ def item_str(name: str, typ: str) -> str:
suffix = ', fallback={}'.format(t.fallback.accept(self))
return 'TypedDict({}{}{})'.format(prefix, s, suffix)

def visit_literal_type(self, t: LiteralType) -> str:
return 'Literal[{}]'.format(t.value)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use repr() here so that string literals that contain unusual characters won't cause trouble. It would also match the source syntax.


def visit_star_type(self, t: StarType) -> str:
s = t.type.accept(self)
return '*{}'.format(s)
Expand Down