Skip to content
Closed
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
Prev Previous commit
Next Next commit
Get rid of Any-tuple
  • Loading branch information
A5rocks committed Aug 27, 2024
commit 06abecd6aabf9ce57861bfb163dd0203b717453b
3 changes: 1 addition & 2 deletions mypy/erasetype.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,7 @@ def visit_tuple_type(self, t: TupleType) -> Type:

def visit_type_var_tuple(self, t: TypeVarTupleType) -> Type:
if self.erase_id(t.id):
# TODO: should t.tuple_fallback become a TupleType?
return TupleType([], t.tuple_fallback, erased_typevartuple=True)
return t.tuple_fallback.copy_modified(args=[self.replacement])
return t

def visit_param_spec(self, t: ParamSpecType) -> Type:
Expand Down
45 changes: 16 additions & 29 deletions mypy/expandtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,7 @@ def expand_type_by_instance(typ: Type, instance: Instance) -> Type:
)
tvar = tvars_middle[0]
assert isinstance(tvar, TypeVarTupleType)

tvar_value: Type = TupleType(list(args_middle), tvar.tuple_fallback)
if len(args_middle) == 1:
# prevent nested Unpacks
middle_arg = get_proper_type(args_middle[0])
if isinstance(middle_arg, UnpackType):
tvar_value = middle_arg.type

variables = {tvar.id: tvar_value}
variables = {tvar.id: TupleType(list(args_middle), tvar.tuple_fallback)}
instance_args = args_prefix + args_suffix
tvars = tvars_prefix + tvars_suffix
else:
Expand Down Expand Up @@ -215,7 +207,7 @@ def visit_erased_type(self, t: ErasedType) -> Type:
return t

def visit_instance(self, t: Instance) -> Type:
args = self.expand_types_with_unpack(list(t.args))[0]
args = self.expand_types_with_unpack(list(t.args))

if isinstance(t.type, FakeInfo):
# The type checker expands function definitions and bodies
Expand Down Expand Up @@ -309,24 +301,23 @@ def visit_unpack_type(self, t: UnpackType) -> Type:
# example is non-normalized types when called from semanal.py.
return UnpackType(t.type.accept(self))

# TODO: there must be a cleaner way to not discard `erased_typevartuple`
def expand_unpack(self, t: UnpackType) -> tuple[list[Type], bool]:
def expand_unpack(self, t: UnpackType) -> list[Type]:
assert isinstance(t.type, TypeVarTupleType)
repl = get_proper_type(self.variables.get(t.type.id, t.type))
if isinstance(repl, UnpackType):
repl = get_proper_type(repl.type)
if isinstance(repl, TupleType):
return repl.items, repl.erased_typevartuple
return repl.items
elif (
isinstance(repl, Instance)
and repl.type.fullname == "builtins.tuple"
or isinstance(repl, TypeVarTupleType)
):
return [UnpackType(typ=repl)], False
return [UnpackType(typ=repl)]
elif isinstance(repl, (AnyType, UninhabitedType)):
# Replace *Ts = Any with *Ts = *tuple[Any, ...] and same for Never.
# These types may appear here as a result of user error or failed inference.
return [UnpackType(t.type.tuple_fallback.copy_modified(args=[repl]))], False
return [UnpackType(t.type.tuple_fallback.copy_modified(args=[repl]))]
else:
raise RuntimeError(f"Invalid type replacement to expand: {repl}")

Expand All @@ -348,12 +339,13 @@ def interpolate_args_for_unpack(self, t: CallableType, var_arg: UnpackType) -> l
expanded_tuple = var_arg_type.accept(self)
assert isinstance(expanded_tuple, ProperType) and isinstance(expanded_tuple, TupleType)
expanded_items = expanded_tuple.items
new_unpack = UnpackType(var_arg_type.copy_modified(items=expanded_items))
fallback = var_arg_type.partial_fallback
new_unpack = UnpackType(TupleType(expanded_items, fallback))
elif isinstance(var_arg_type, TypeVarTupleType):
# We have plain Unpack[Ts]
fallback = var_arg_type.tuple_fallback
expanded_items, etv = self.expand_unpack(var_arg)
new_unpack = UnpackType(TupleType(expanded_items, fallback, erased_typevartuple=etv))
expanded_items = self.expand_unpack(var_arg)
new_unpack = UnpackType(TupleType(expanded_items, fallback))
else:
# We have invalid type in Unpack. This can happen when expanding aliases
# to Callable[[*Invalid], Ret]
Expand Down Expand Up @@ -433,23 +425,18 @@ def visit_overloaded(self, t: Overloaded) -> Type:
items.append(new_item)
return Overloaded(items)

def expand_types_with_unpack(self, typs: Sequence[Type]) -> tuple[list[Type], bool]:
def expand_types_with_unpack(self, typs: Sequence[Type]) -> list[Type]:
"""Expands a list of types that has an unpack."""
items: list[Type] = []
met_erased_typevartuple = False # not sure this is the right behavior.
for item in typs:
if isinstance(item, UnpackType) and isinstance(item.type, TypeVarTupleType):
its, etv = self.expand_unpack(item)
met_erased_typevartuple = met_erased_typevartuple or etv
items.extend(its)
items.extend(self.expand_unpack(item))
else:
items.append(item.accept(self))

assert not met_erased_typevartuple or len(typs) == 1
return items, met_erased_typevartuple
return items

def visit_tuple_type(self, t: TupleType) -> Type:
items, etv = self.expand_types_with_unpack(t.items)
items = self.expand_types_with_unpack(t.items)
if len(items) == 1:
# Normalize Tuple[*Tuple[X, ...]] -> Tuple[X, ...]
item = items[0]
Expand All @@ -464,7 +451,7 @@ def visit_tuple_type(self, t: TupleType) -> Type:
return unpacked
fallback = t.partial_fallback.accept(self)
assert isinstance(fallback, ProperType) and isinstance(fallback, Instance)
return t.copy_modified(items=items, fallback=fallback, erased_typevartuple=etv)
return t.copy_modified(items=items, fallback=fallback)

def visit_typeddict_type(self, t: TypedDictType) -> Type:
fallback = t.fallback.accept(self)
Expand Down Expand Up @@ -503,7 +490,7 @@ def visit_type_type(self, t: TypeType) -> Type:
def visit_type_alias_type(self, t: TypeAliasType) -> Type:
# Target of the type alias cannot contain type variables (not bound by the type
# alias itself), so we just expand the arguments.
args = self.expand_types_with_unpack(t.args)[0]
args = self.expand_types_with_unpack(t.args)
# TODO: normalize if target is Tuple, and args are [*tuple[X, ...]]?
return t.copy_modified(args=args)

Expand Down
16 changes: 6 additions & 10 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,9 +462,6 @@ def visit_instance(self, left: Instance) -> bool:
if isinstance(right, TupleType) and right.partial_fallback.type.is_enum:
return self._is_subtype(left, mypy.typeops.tuple_fallback(right))
if isinstance(right, TupleType):
if right.erased_typevartuple:
return True # treat it like Any

if len(right.items) == 1:
# Non-normalized Tuple type (may be left after semantic analysis
# because semanal_typearg visitor is not a type translator).
Expand Down Expand Up @@ -787,8 +784,6 @@ def visit_tuple_type(self, left: TupleType) -> bool:
return True
return False
elif isinstance(right, TupleType):
if right.erased_typevartuple:
return True # treat it like Any
# If right has a variadic unpack this needs special handling. If there is a TypeVarTuple
# unpack, item count must coincide. If the left has variadic unpack but right
# doesn't have one, we will fall through to False down the line.
Expand Down Expand Up @@ -829,8 +824,6 @@ def variadic_tuple_subtype(self, left: TupleType, right: TupleType) -> bool:
right_unpack = right.items[right_unpack_index]
assert isinstance(right_unpack, UnpackType)
right_unpacked = get_proper_type(right_unpack.type)
if isinstance(right_unpacked, TupleType) and right_unpacked.erased_typevartuple:
return True # treat it as Any
if not isinstance(right_unpacked, Instance):
# This case should be handled by the caller.
return False
Expand Down Expand Up @@ -1609,12 +1602,15 @@ def are_parameters_compatible(
if are_trivial_parameters(right) and not is_proper_subtype:
return True
trivial_suffix = is_trivial_suffix(right) and not is_proper_subtype
# erased typevartuples, like erased paramspecs or erased typevars are trivial

# tuple[Any, ...] allows any number of arguments, not just infinite.
if right_star and isinstance(right_star.typ, UnpackType):
right_star_inner_type = get_proper_type(right_star.typ.type)
trivial_varargs = (
isinstance(right_star_inner_type, TupleType)
and right_star_inner_type.erased_typevartuple
isinstance(right_star_inner_type, Instance)
and right_star_inner_type.type.fullname == "builtins.tuple"
and len(right_star_inner_type.args) == 1
and isinstance(right_star_inner_type.args[0], AnyType)
)
else:
trivial_varargs = False
Expand Down
2 changes: 1 addition & 1 deletion mypy/test/testtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1560,7 +1560,7 @@ def make_call(*items: tuple[str, str | None]) -> CallExpr:
class TestExpandTypeLimitGetProperType(TestCase):
# WARNING: do not increase this number unless absolutely necessary,
# and you understand what you are doing.
ALLOWED_GET_PROPER_TYPES = 10
ALLOWED_GET_PROPER_TYPES = 9

@skipUnless(mypy.expandtype.__file__.endswith(".py"), "Skip for compiled mypy")
def test_count_get_proper_type(self) -> None:
Expand Down
37 changes: 6 additions & 31 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2128,8 +2128,6 @@ def with_normalized_var_args(self) -> Self:
# this should be done once in semanal_typeargs.py for user-defined types,
# and we ourselves rarely construct such type.
return self
if unpacked.erased_typevartuple:
return self
unpack_index = find_unpack_in_list(unpacked.items)
if unpack_index == 0 and len(unpacked.items) > 1:
# Already normalized.
Expand Down Expand Up @@ -2356,16 +2354,13 @@ class TupleType(ProperType):
a tuple base class. Use mypy.typeops.tuple_fallback to calculate the
precise fallback type derived from item types.
implicit: If True, derived from a tuple expression (t,....) instead of Tuple[t, ...]
erased_typevartuple: If True, this came from a (now-erased) TypeVarTuple. This
indicates that this tuple should act more like an Any.
"""

__slots__ = ("items", "partial_fallback", "implicit", "erased_typevartuple")
__slots__ = ("items", "partial_fallback", "implicit")

items: list[Type]
partial_fallback: Instance
implicit: bool
erased_typevartuple: bool

def __init__(
self,
Expand All @@ -2374,13 +2369,11 @@ def __init__(
line: int = -1,
column: int = -1,
implicit: bool = False,
erased_typevartuple: bool = False,
) -> None:
super().__init__(line, column)
self.partial_fallback = fallback
self.items = items
self.implicit = implicit
self.erased_typevartuple = erased_typevartuple

def can_be_true_default(self) -> bool:
if self.can_be_any_bool():
Expand Down Expand Up @@ -2421,24 +2414,19 @@ def accept(self, visitor: TypeVisitor[T]) -> T:
return visitor.visit_tuple_type(self)

def __hash__(self) -> int:
return hash((tuple(self.items), self.partial_fallback, self.erased_typevartuple))
return hash((tuple(self.items), self.partial_fallback))

def __eq__(self, other: object) -> bool:
if not isinstance(other, TupleType):
return NotImplemented
return (
self.items == other.items
and self.partial_fallback == other.partial_fallback
and self.erased_typevartuple == other.erased_typevartuple
)
return self.items == other.items and self.partial_fallback == other.partial_fallback

def serialize(self) -> JsonDict:
return {
".class": "TupleType",
"items": [t.serialize() for t in self.items],
"partial_fallback": self.partial_fallback.serialize(),
"implicit": self.implicit,
"erased_typevartuple": self.erased_typevartuple,
}

@classmethod
Expand All @@ -2448,25 +2436,16 @@ def deserialize(cls, data: JsonDict) -> TupleType:
[deserialize_type(t) for t in data["items"]],
Instance.deserialize(data["partial_fallback"]),
implicit=data["implicit"],
erased_typevartuple=data["erased_typevartuple"],
)

def copy_modified(
self,
*,
fallback: Instance | None = None,
items: list[Type] | None = None,
erased_typevartuple: bool | None = None,
self, *, fallback: Instance | None = None, items: list[Type] | None = None
) -> TupleType:
if fallback is None:
fallback = self.partial_fallback
if items is None:
items = self.items
if erased_typevartuple is None:
erased_typevartuple = self.erased_typevartuple
return TupleType(
items, fallback, self.line, self.column, erased_typevartuple=erased_typevartuple
)
return TupleType(items, fallback, self.line, self.column)

def slice(
self, begin: int | None, end: int | None, stride: int | None, *, fallback: Instance | None
Expand Down Expand Up @@ -2519,9 +2498,7 @@ def slice(
return None
else:
slice_items = self.items[begin:end:stride]
return TupleType(
slice_items, fallback, self.line, self.column, self.implicit, self.erased_typevartuple
)
return TupleType(slice_items, fallback, self.line, self.column, self.implicit)


class TypedDictType(ProperType):
Expand Down Expand Up @@ -3400,8 +3377,6 @@ def visit_overloaded(self, t: Overloaded) -> str:
return f"Overload({', '.join(a)})"

def visit_tuple_type(self, t: TupleType) -> str:
if t.erased_typevartuple:
return "tuple[...]"
s = self.list_str(t.items) or "()"
tuple_name = "tuple" if self.options.use_lowercase_names() else "Tuple"
if t.partial_fallback and t.partial_fallback.type:
Expand Down