Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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: 3 additions & 2 deletions mypy/expandtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,7 @@ def visit_erased_type(self, t: ErasedType) -> Type:

def visit_instance(self, t: Instance) -> Type:
if len(t.args) == 0:
# TODO: Why do we need to create a copy here?
return t.copy_modified()
return t

args = self.expand_type_tuple_with_unpack(t.args)

Expand Down Expand Up @@ -525,6 +524,8 @@ 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.
if len(t.args) == 0:
return t
args = self.expand_type_list_with_unpack(t.args)
# TODO: normalize if target is Tuple, and args are [*tuple[X, ...]]?
return t.copy_modified(args=args)
Expand Down
11 changes: 6 additions & 5 deletions mypy/meet.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,11 @@ def meet_types(s: Type, t: Type) -> ProperType:
def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
"""Return the declared type narrowed down to another type."""
# TODO: check infinite recursion for aliases here.
if isinstance(narrowed, TypeGuardedType): # type: ignore[misc]
# A type guard forces the new type even if it doesn't overlap the old.
if isinstance(narrowed, TypeGuardedType):
# A type guard forces the new type even if it doesn't overlap the old...
if is_proper_subtype(declared, narrowed.type_guard, ignore_promotions=True):
# ...unless it is a proper supertype of declared type.
return declared
return narrowed.type_guard

original_declared = declared
Expand Down Expand Up @@ -308,9 +311,7 @@ def is_overlapping_types(
positives), for example: None only overlaps with explicitly optional types, Any
doesn't overlap with anything except object, we don't ignore positional argument names.
"""
if isinstance(left, TypeGuardedType) or isinstance( # type: ignore[misc]
right, TypeGuardedType
):
if isinstance(left, TypeGuardedType) or isinstance(right, TypeGuardedType):
# A type guard forces the new type even if it doesn't overlap the old.
return True

Expand Down
1 change: 1 addition & 0 deletions mypy/plugins/proper_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def is_special_target(right: ProperType) -> bool:
"mypy.types.DeletedType",
"mypy.types.RequiredType",
"mypy.types.ReadOnlyType",
"mypy.types.TypeGuardedType",
):
# Special case: these are not valid targets for a type alias and thus safe.
# TODO: introduce a SyntheticType base to simplify this?
Expand Down
8 changes: 4 additions & 4 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1046,12 +1046,12 @@ def remove_unpack_kwargs(self, defn: FuncDef, typ: CallableType) -> CallableType
last_type = typ.arg_types[-1]
if not isinstance(last_type, UnpackType):
return typ
last_type = get_proper_type(last_type.type)
if not isinstance(last_type, TypedDictType):
p_last_type = get_proper_type(last_type.type)
if not isinstance(p_last_type, TypedDictType):
self.fail("Unpack item in ** argument must be a TypedDict", last_type)
new_arg_types = typ.arg_types[:-1] + [AnyType(TypeOfAny.from_error)]
return typ.copy_modified(arg_types=new_arg_types)
overlap = set(typ.arg_names) & set(last_type.items)
overlap = set(typ.arg_names) & set(p_last_type.items)
# It is OK for TypedDict to have a key named 'kwargs'.
overlap.discard(typ.arg_names[-1])
if overlap:
Expand All @@ -1060,7 +1060,7 @@ def remove_unpack_kwargs(self, defn: FuncDef, typ: CallableType) -> CallableType
new_arg_types = typ.arg_types[:-1] + [AnyType(TypeOfAny.from_error)]
return typ.copy_modified(arg_types=new_arg_types)
# OK, everything looks right now, mark the callable type as using unpack.
new_arg_types = typ.arg_types[:-1] + [last_type]
new_arg_types = typ.arg_types[:-1] + [p_last_type]
return typ.copy_modified(arg_types=new_arg_types, unpack_kwargs=True)

def prepare_method_signature(self, func: FuncDef, info: TypeInfo, has_self_type: bool) -> None:
Expand Down
51 changes: 21 additions & 30 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,11 +355,7 @@ def _expand_once(self) -> Type:
):
mapping[tvar.id] = sub

new_tp = self.alias.target.accept(InstantiateAliasVisitor(mapping))
new_tp.accept(LocationSetter(self.line, self.column))
new_tp.line = self.line
new_tp.column = self.column
return new_tp
return self.alias.target.accept(InstantiateAliasVisitor(mapping))

def _partial_expansion(self, nothing_args: bool = False) -> tuple[ProperType, bool]:
# Private method mostly for debugging and testing.
Expand Down Expand Up @@ -3214,7 +3210,8 @@ def get_proper_type(typ: Type | None) -> ProperType | None:
"""
if typ is None:
return None
if isinstance(typ, TypeGuardedType): # type: ignore[misc]
# TODO: this is an ugly hack, remove.
if isinstance(typ, TypeGuardedType):
typ = typ.type_guard
while isinstance(typ, TypeAliasType):
typ = typ._expand_once()
Expand All @@ -3238,9 +3235,7 @@ def get_proper_types(
if isinstance(types, list):
typelist = types
# Optimize for the common case so that we don't need to allocate anything
if not any(
isinstance(t, (TypeAliasType, TypeGuardedType)) for t in typelist # type: ignore[misc]
):
if not any(isinstance(t, (TypeAliasType, TypeGuardedType)) for t in typelist):
return cast("list[ProperType]", typelist)
return [get_proper_type(t) for t in typelist]
else:
Expand All @@ -3260,7 +3255,6 @@ def get_proper_types(
TypeTranslator as TypeTranslator,
TypeVisitor as TypeVisitor,
)
from mypy.typetraverser import TypeTraverserVisitor


class TypeStrVisitor(SyntheticTypeVisitor[str]):
Expand Down Expand Up @@ -3598,23 +3592,6 @@ def is_named_instance(t: Type, fullnames: str | tuple[str, ...]) -> TypeGuard[In
return isinstance(t, Instance) and t.type.fullname in fullnames


class LocationSetter(TypeTraverserVisitor):
# TODO: Should we update locations of other Type subclasses?
def __init__(self, line: int, column: int) -> None:
self.line = line
self.column = column

def visit_instance(self, typ: Instance) -> None:
typ.line = self.line
typ.column = self.column
super().visit_instance(typ)

def visit_type_alias_type(self, typ: TypeAliasType) -> None:
typ.line = self.line
typ.column = self.column
super().visit_type_alias_type(typ)


class HasTypeVars(BoolTypeQuery):
"""Visitor for querying whether a type has a type variable component."""

Expand Down Expand Up @@ -3709,8 +3686,8 @@ def flatten_nested_unions(

flat_items: list[Type] = []
for t in typelist:
if handle_type_alias_type:
if not handle_recursive and isinstance(t, TypeAliasType) and t.is_recursive:
if handle_type_alias_type and isinstance(t, TypeAliasType):
if not handle_recursive and t.is_recursive:
tp: Type = t
else:
tp = get_proper_type(t)
Expand Down Expand Up @@ -3757,7 +3734,21 @@ def flatten_nested_tuples(types: Iterable[Type]) -> list[Type]:
if not isinstance(p_type, TupleType):
res.append(typ)
continue
res.extend(flatten_nested_tuples(p_type.items))
if isinstance(typ.type, TypeAliasType):
items = []
for item in p_type.items:
if (
isinstance(item, ProperType)
and isinstance(item, Instance)
or isinstance(item, TypeAliasType)
):
if len(item.args) == 0:
item = item.copy_modified()
item.set_line(typ)
items.append(item)
else:
items = p_type.items
res.extend(flatten_nested_tuples(items))
return res


Expand Down