diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 07ca3aaa7bc1..8a0df916f5a9 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -224,8 +224,7 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: # Something that refers to a type alias appears in runtime context. # Note that we suppress bogus errors for alias redefinitions, # they are already reported in semanal.py. - result = self.alias_type_in_runtime_context(node.target, node.alias_tvars, - node.no_args, e, + result = self.alias_type_in_runtime_context(node, node.no_args, e, alias_definition=e.is_alias_rvalue or lvalue) else: @@ -2917,9 +2916,7 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: """ if isinstance(tapp.expr, RefExpr) and isinstance(tapp.expr.node, TypeAlias): # Subscription of a (generic) alias in runtime context, expand the alias. - target = tapp.expr.node.target - all_vars = tapp.expr.node.alias_tvars - item = expand_type_alias(target, all_vars, tapp.types, self.chk.fail, + item = expand_type_alias(tapp.expr.node, tapp.types, self.chk.fail, tapp.expr.node.no_args, tapp) item = get_proper_type(item) if isinstance(item, Instance): @@ -2951,10 +2948,10 @@ def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: both `reveal_type` instances will reveal the same type `def (...) -> builtins.list[Any]`. Note that type variables are implicitly substituted with `Any`. """ - return self.alias_type_in_runtime_context(alias.type, alias.tvars, alias.no_args, + return self.alias_type_in_runtime_context(alias.node, alias.no_args, alias, alias_definition=True) - def alias_type_in_runtime_context(self, target: Type, alias_tvars: List[str], + def alias_type_in_runtime_context(self, alias: TypeAlias, no_args: bool, ctx: Context, *, alias_definition: bool = False) -> Type: @@ -2971,14 +2968,14 @@ class LongName(Generic[T]): ... x = A() y = cast(A, ...) """ - if isinstance(target, Instance) and target.invalid: # type: ignore + if isinstance(alias.target, Instance) and alias.target.invalid: # type: ignore # An invalid alias, error already has been reported return AnyType(TypeOfAny.from_error) # If this is a generic alias, we set all variables to `Any`. # For example: # A = List[Tuple[T, T]] # x = A() <- same as List[Tuple[Any, Any]], see PEP 484. - item = get_proper_type(set_any_tvars(target, alias_tvars, ctx.line, ctx.column)) + item = get_proper_type(set_any_tvars(alias, ctx.line, ctx.column)) if isinstance(item, Instance): # Normally we get a callable type (or overloaded) with .is_type_obj() true # representing the class's constructor diff --git a/mypy/checkmember.py b/mypy/checkmember.py index a3a086402ad5..86ea5051e17e 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -502,8 +502,8 @@ def instance_alias_type(alias: TypeAlias, target = get_proper_type(alias.target) # type: Type assert isinstance(get_proper_type(target), Instance), "Must be called only with aliases to classes" - target = set_any_tvars(target, alias.alias_tvars, alias.line, alias.column) - assert isinstance(target, Instance) # type: ignore[misc] + target = get_proper_type(set_any_tvars(alias, alias.line, alias.column)) + assert isinstance(target, Instance) tp = type_object_type(target.type, builtin_type) return expand_type_by_instance(tp, target) diff --git a/mypy/constraints.py b/mypy/constraints.py index 1f380f0ebf2e..81067506f945 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -92,8 +92,7 @@ def infer_constraints(template: Type, actual: Type, """ if any(get_proper_type(template) == get_proper_type(t) for t in TypeState._inferring): return [] - if (isinstance(template, TypeAliasType) and isinstance(actual, TypeAliasType) and - template.is_recursive and actual.is_recursive): + if isinstance(template, TypeAliasType) and template.is_recursive: # This case requires special care because it may cause infinite recursion. TypeState._inferring.append(template) res = _infer_constraints(template, actual, direction) @@ -105,6 +104,7 @@ def infer_constraints(template: Type, actual: Type, def _infer_constraints(template: Type, actual: Type, direction: int) -> List[Constraint]: + orig_template = template template = get_proper_type(template) actual = get_proper_type(actual) @@ -129,7 +129,7 @@ def _infer_constraints(template: Type, actual: Type, if direction == SUPERTYPE_OF and isinstance(actual, UnionType): res = [] for a_item in actual.items: - res.extend(infer_constraints(template, a_item, direction)) + res.extend(infer_constraints(orig_template, a_item, direction)) return res # Now the potential subtype is known not to be a Union or a type diff --git a/mypy/nodes.py b/mypy/nodes.py index 58709590492f..d10bfafa484c 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2116,11 +2116,12 @@ class TypeAliasExpr(Expression): # A = List[Any] no_args = False # type: bool - def __init__(self, type: 'mypy.types.Type', tvars: List[str], no_args: bool) -> None: + def __init__(self, node: 'TypeAlias') -> None: super().__init__() - self.type = type - self.tvars = tvars - self.no_args = no_args + self.type = node.target + self.tvars = node.alias_tvars + self.no_args = node.no_args + self.node = node def accept(self, visitor: ExpressionVisitor[T]) -> T: return visitor.visit_type_alias_expr(self) diff --git a/mypy/semanal.py b/mypy/semanal.py index 6dd9038231d7..e864367ee14e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2495,18 +2495,20 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: context=s) # When this type alias gets "inlined", the Any is not explicit anymore, # so we need to replace it with non-explicit Anys. - res = make_any_non_explicit(res) + if not has_placeholder(res): + res = make_any_non_explicit(res) no_args = isinstance(res, Instance) and not res.args # type: ignore fix_instance_types(res, self.fail, self.note) + alias_node = TypeAlias(res, self.qualified_name(lvalue.name), s.line, s.column, + alias_tvars=alias_tvars, no_args=no_args) if isinstance(s.rvalue, (IndexExpr, CallExpr)): # CallExpr is for `void = type(None)` - s.rvalue.analyzed = TypeAliasExpr(res, alias_tvars, no_args) + s.rvalue.analyzed = TypeAliasExpr(alias_node) s.rvalue.analyzed.line = s.line # we use the column from resulting target, to get better location for errors s.rvalue.analyzed.column = res.column elif isinstance(s.rvalue, RefExpr): s.rvalue.is_alias_rvalue = True - alias_node = TypeAlias(res, self.qualified_name(lvalue.name), s.line, s.column, - alias_tvars=alias_tvars, no_args=no_args) + if existing: # An alias gets updated. updated = False diff --git a/mypy/semanal_typeargs.py b/mypy/semanal_typeargs.py index 5ea77f4025d6..38a13c12b468 100644 --- a/mypy/semanal_typeargs.py +++ b/mypy/semanal_typeargs.py @@ -5,10 +5,12 @@ operations, including subtype checks. """ -from typing import List, Optional +from typing import List, Optional, Set from mypy.nodes import TypeInfo, Context, MypyFile, FuncItem, ClassDef, Block -from mypy.types import Type, Instance, TypeVarType, AnyType, get_proper_types +from mypy.types import ( + Type, Instance, TypeVarType, AnyType, get_proper_types, TypeAliasType, get_proper_type +) from mypy.mixedtraverser import MixedTraverserVisitor from mypy.subtypes import is_subtype from mypy.sametypes import is_same_type @@ -27,6 +29,9 @@ def __init__(self, errors: Errors, options: Options, is_typeshed_file: bool) -> self.scope = Scope() # Should we also analyze function definitions, or only module top-levels? self.recurse_into_functions = True + # Keep track of the type aliases already visited. This is needed to avoid + # infinite recursion on types like A = Union[int, List[A]]. + self.seen_aliases = set() # type: Set[TypeAliasType] def visit_mypy_file(self, o: MypyFile) -> None: self.errors.set_file(o.path, o.fullname, scope=self.scope) @@ -48,6 +53,16 @@ def visit_block(self, o: Block) -> None: if not o.is_unreachable: super().visit_block(o) + def visit_type_alias_type(self, t: TypeAliasType) -> None: + super().visit_type_alias_type(t) + if t in self.seen_aliases: + # Avoid infinite recursion on recursive type aliases. + # Note: it is fine to skip the aliases we have already seen in non-recursive types, + # since errors there have already already reported. + return + self.seen_aliases.add(t) + get_proper_type(t).accept(self) + def visit_instance(self, t: Instance) -> None: # Type argument counts were checked in the main semantic analyzer pass. We assume # that the counts are correct here. diff --git a/mypy/server/deps.py b/mypy/server/deps.py index 9e1f7bd2c6d2..cf2d9b51246a 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -884,6 +884,10 @@ def visit_type_alias_type(self, typ: TypeAliasType) -> List[str]: triggers = [trigger] for arg in typ.args: triggers.extend(self.get_type_triggers(arg)) + # TODO: Add guard for infinite recursion here. Moreover, now that type aliases + # are its own kind of types we can simplify the logic to rely on intermediate + # dependencies (like for instance types). + triggers.extend(self.get_type_triggers(typ.alias.target)) return triggers def visit_any(self, typ: AnyType) -> List[str]: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index e14cdb451d87..e8fa69bf8510 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -7,7 +7,7 @@ Type, AnyType, UnboundType, TypeVisitor, FormalArgument, NoneType, Instance, TypeVarType, CallableType, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType, DeletedType, UninhabitedType, TypeType, is_named_instance, - FunctionLike, TypeOfAny, LiteralType, ProperType, get_proper_type, TypeAliasType + FunctionLike, TypeOfAny, LiteralType, get_proper_type, TypeAliasType ) import mypy.applytype import mypy.constraints @@ -103,6 +103,8 @@ def _is_subtype(left: Type, right: Type, ignore_pos_arg_names: bool = False, ignore_declared_variance: bool = False, ignore_promotions: bool = False) -> bool: + orig_right = right + orig_left = left left = get_proper_type(left) right = get_proper_type(right) @@ -113,7 +115,7 @@ def _is_subtype(left: Type, right: Type, # Normally, when 'left' is not itself a union, the only way # 'left' can be a subtype of the union 'right' is if it is a # subtype of one of the items making up the union. - is_subtype_of_item = any(is_subtype(left, item, + is_subtype_of_item = any(is_subtype(orig_left, item, ignore_type_params=ignore_type_params, ignore_pos_arg_names=ignore_pos_arg_names, ignore_declared_variance=ignore_declared_variance, @@ -130,7 +132,7 @@ def _is_subtype(left: Type, right: Type, elif is_subtype_of_item: return True # otherwise, fall through - return left.accept(SubtypeVisitor(right, + return left.accept(SubtypeVisitor(orig_right, ignore_type_params=ignore_type_params, ignore_pos_arg_names=ignore_pos_arg_names, ignore_declared_variance=ignore_declared_variance, @@ -155,13 +157,14 @@ def is_equivalent(a: Type, b: Type, class SubtypeVisitor(TypeVisitor[bool]): - def __init__(self, right: ProperType, + def __init__(self, right: Type, *, ignore_type_params: bool, ignore_pos_arg_names: bool = False, ignore_declared_variance: bool = False, ignore_promotions: bool = False) -> None: - self.right = right + self.right = get_proper_type(right) + self.orig_right = right self.ignore_type_params = ignore_type_params self.ignore_pos_arg_names = ignore_pos_arg_names self.ignore_declared_variance = ignore_declared_variance @@ -449,7 +452,7 @@ def visit_overloaded(self, left: Overloaded) -> bool: return False def visit_union_type(self, left: UnionType) -> bool: - return all(self._is_subtype(item, self.right) for item in left.items) + return all(self._is_subtype(item, self.orig_right) for item in left.items) def visit_partial_type(self, left: PartialType) -> bool: # This is indeterminate as we don't really know the complete type yet. @@ -1083,7 +1086,8 @@ def restrict_subtype_away(t: Type, s: Type, *, ignore_promotions: bool = False) s = get_proper_type(s) if isinstance(t, UnionType): - new_items = [item for item in t.relevant_items() + new_items = [restrict_subtype_away(item, s, ignore_promotions=ignore_promotions) + for item in t.relevant_items() if (isinstance(get_proper_type(item), AnyType) or not covers_at_runtime(item, s, ignore_promotions))] return UnionType.make_union(new_items) @@ -1139,22 +1143,26 @@ def is_proper_subtype(left: Type, right: Type, *, ignore_promotions: bool = Fals def _is_proper_subtype(left: Type, right: Type, *, ignore_promotions: bool = False, erase_instances: bool = False) -> bool: + orig_left = left + orig_right = right left = get_proper_type(left) right = get_proper_type(right) if isinstance(right, UnionType) and not isinstance(left, UnionType): - return any([is_proper_subtype(left, item, ignore_promotions=ignore_promotions, + return any([is_proper_subtype(orig_left, item, ignore_promotions=ignore_promotions, erase_instances=erase_instances) for item in right.items]) - return left.accept(ProperSubtypeVisitor(right, ignore_promotions=ignore_promotions, + return left.accept(ProperSubtypeVisitor(orig_right, + ignore_promotions=ignore_promotions, erase_instances=erase_instances)) class ProperSubtypeVisitor(TypeVisitor[bool]): - def __init__(self, right: ProperType, *, + def __init__(self, right: Type, *, ignore_promotions: bool = False, erase_instances: bool = False) -> None: - self.right = right + self.right = get_proper_type(right) + self.orig_right = right self.ignore_promotions = ignore_promotions self.erase_instances = erase_instances self._subtype_kind = ProperSubtypeVisitor.build_subtype_kind( @@ -1313,7 +1321,7 @@ def visit_overloaded(self, left: Overloaded) -> bool: return False def visit_union_type(self, left: UnionType) -> bool: - return all([self._is_proper_subtype(item, self.right) for item in left.items]) + return all([self._is_proper_subtype(item, self.orig_right) for item in left.items]) def visit_partial_type(self, left: PartialType) -> bool: # TODO: What's the right thing to do here? diff --git a/mypy/treetransform.py b/mypy/treetransform.py index 09d1edd4220d..9bcc8175e046 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -497,7 +497,7 @@ def visit_type_var_expr(self, node: TypeVarExpr) -> TypeVarExpr: self.type(node.upper_bound), variance=node.variance) def visit_type_alias_expr(self, node: TypeAliasExpr) -> TypeAliasExpr: - return TypeAliasExpr(node.type, node.tvars, node.no_args) + return TypeAliasExpr(node.node) def visit_newtype_expr(self, node: NewTypeExpr) -> NewTypeExpr: res = NewTypeExpr(node.name, node.old_type, line=node.line, column=node.column) diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index e2812364f25e..da630e1f176c 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -13,7 +13,7 @@ from abc import abstractmethod from collections import OrderedDict -from typing import Generic, TypeVar, cast, Any, List, Callable, Iterable, Optional +from typing import Generic, TypeVar, cast, Any, List, Callable, Iterable, Optional, Set from mypy_extensions import trait T = TypeVar('T') @@ -246,14 +246,21 @@ def visit_type_alias_type(self, t: TypeAliasType) -> Type: class TypeQuery(SyntheticTypeVisitor[T]): """Visitor for performing queries of types. - strategy is used to combine results for a series of types + strategy is used to combine results for a series of types, + common use cases involve a boolean query using `any` or `all`. - Common use cases involve a boolean query using `any` or `all` + Note: this visitor keeps an internal state (tracks type aliases to avoid + recursion), so it should *never* be re-used for querying different types, + create a new visitor instance instead. + + # TODO: check that we don't have existing violations of this rule. """ def __init__(self, strategy: Callable[[Iterable[T]], T]) -> None: self.strategy = strategy - self.seen = [] # type: List[Type] + # Keep track of the type aliases already visited. This is needed to avoid + # infinite recursion on types like A = Union[int, List[A]]. + self.seen_aliases = set() # type: Set[TypeAliasType] def visit_unbound_type(self, t: UnboundType) -> T: return self.query_types(t.args) @@ -329,14 +336,16 @@ def query_types(self, types: Iterable[Type]) -> T: """Perform a query for a list of types. Use the strategy to combine the results. - Skip types already visited types to avoid infinite recursion. - Note: types can be recursive until they are fully analyzed and "unentangled" - in patches after the semantic analysis. + Skip type aliases already visited types to avoid infinite recursion. """ res = [] # type: List[T] for t in types: - if any(t is s for s in self.seen): - continue - self.seen.append(t) + if isinstance(t, TypeAliasType): + # Avoid infinite recursion for recursive type aliases. + # TODO: Ideally we should fire subvisitors here (or use caching) if we care + # about duplicates. + if t in self.seen_aliases: + continue + self.seen_aliases.add(t) res.append(t.accept(self)) return self.strategy(res) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index ce6b06119a70..82b4585cfafb 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -14,7 +14,7 @@ from mypy.types import ( Type, UnboundType, TypeVarType, TupleType, TypedDictType, UnionType, Instance, AnyType, CallableType, NoneType, ErasedType, DeletedType, TypeList, TypeVarDef, SyntheticTypeVisitor, - StarType, PartialType, EllipsisType, UninhabitedType, TypeType, replace_alias_tvars, + StarType, PartialType, EllipsisType, UninhabitedType, TypeType, CallableArgument, TypeQuery, union_items, TypeOfAny, LiteralType, RawExpressionType, PlaceholderType, Overloaded, get_proper_type, TypeAliasType ) @@ -212,19 +212,15 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) return special if isinstance(node, TypeAlias): self.aliases_used.add(fullname) - all_vars = node.alias_tvars - target = node.target an_args = self.anal_array(t.args) disallow_any = self.options.disallow_any_generics and not self.is_typeshed_stub - res = expand_type_alias(target, all_vars, an_args, self.fail, node.no_args, t, + res = expand_type_alias(node, an_args, self.fail, node.no_args, t, unexpanded_type=t, disallow_any=disallow_any) # The only case where expand_type_alias() can return an incorrect instance is # when it is top-level instance, so no need to recurse. - # TODO: this is not really needed, since with the new logic we will not expand - # aliases immediately. - res = get_proper_type(res) - if (isinstance(res, Instance) and len(res.args) != len(res.type.type_vars) and + if (isinstance(res, Instance) and # type: ignore[misc] + len(res.args) != len(res.type.type_vars) and not self.defining_alias): fix_instance( res, @@ -483,6 +479,7 @@ def visit_instance(self, t: Instance) -> Type: return t def visit_type_alias_type(self, t: TypeAliasType) -> Type: + # TODO: should we do something here? return t def visit_type_var(self, t: TypeVarType) -> Type: @@ -973,7 +970,7 @@ def fix_instance(t: Instance, fail: MsgCallback, note: MsgCallback, t.invalid = True -def expand_type_alias(target: Type, alias_tvars: List[str], args: List[Type], +def expand_type_alias(node: TypeAlias, args: List[Type], fail: MsgCallback, no_args: bool, ctx: Context, *, unexpanded_type: Optional[Type] = None, disallow_any: bool = False) -> Type: @@ -987,38 +984,43 @@ def expand_type_alias(target: Type, alias_tvars: List[str], args: List[Type], no_args: whether original definition used a bare generic `A = List` ctx: context where expansion happens """ - exp_len = len(alias_tvars) + exp_len = len(node.alias_tvars) act_len = len(args) if exp_len > 0 and act_len == 0: # Interpret bare Alias same as normal generic, i.e., Alias[Any, Any, ...] - assert alias_tvars is not None - return set_any_tvars(target, alias_tvars, ctx.line, ctx.column, + return set_any_tvars(node, ctx.line, ctx.column, disallow_any=disallow_any, fail=fail, unexpanded_type=unexpanded_type) if exp_len == 0 and act_len == 0: if no_args: - assert isinstance(target, Instance) # type: ignore - return Instance(target.type, [], line=ctx.line, column=ctx.column) - return target - if exp_len == 0 and act_len > 0 and isinstance(target, Instance) and no_args: # type: ignore - tp = Instance(target.type, args) + assert isinstance(node.target, Instance) # type: ignore[misc] + # Note: this is the only case where we use an eager expansion. See more info about + # no_args aliases like L = List in the docstring for TypeAlias class. + return Instance(node.target.type, [], line=ctx.line, column=ctx.column) + return TypeAliasType(node, [], line=ctx.line, column=ctx.column) + if (exp_len == 0 and act_len > 0 + and isinstance(node.target, Instance) # type: ignore[misc] + and no_args): + tp = Instance(node.target.type, args) tp.line = ctx.line tp.column = ctx.column return tp if act_len != exp_len: fail('Bad number of arguments for type alias, expected: %s, given: %s' % (exp_len, act_len), ctx) - return set_any_tvars(target, alias_tvars or [], - ctx.line, ctx.column, from_error=True) - typ = replace_alias_tvars(target, alias_tvars, args, ctx.line, ctx.column) # type: Type + return set_any_tvars(node, ctx.line, ctx.column, from_error=True) + typ = TypeAliasType(node, args, ctx.line, ctx.column) + assert typ.alias is not None # HACK: Implement FlexibleAlias[T, typ] by expanding it to typ here. - if (isinstance(typ, Instance) # type: ignore - and typ.type.fullname == 'mypy_extensions.FlexibleAlias'): - typ = typ.args[-1] + if (isinstance(typ.alias.target, Instance) # type: ignore + and typ.alias.target.type.fullname == 'mypy_extensions.FlexibleAlias'): + exp = get_proper_type(typ) + assert isinstance(exp, Instance) + return exp.args[-1] return typ -def set_any_tvars(tp: Type, vars: List[str], +def set_any_tvars(node: TypeAlias, newline: int, newcolumn: int, *, from_error: bool = False, disallow_any: bool = False, @@ -1030,13 +1032,13 @@ def set_any_tvars(tp: Type, vars: List[str], type_of_any = TypeOfAny.from_omitted_generics if disallow_any: assert fail is not None - otype = unexpanded_type or tp + otype = unexpanded_type or node.target type_str = otype.name if isinstance(otype, UnboundType) else format_type_bare(otype) fail(message_registry.BARE_GENERIC.format(quote_type_string(type_str)), Context(newline, newcolumn), code=codes.TYPE_ARG) any_type = AnyType(type_of_any, line=newline, column=newcolumn) - return replace_alias_tvars(tp, vars, [any_type] * len(vars), newline, newcolumn) + return TypeAliasType(node, [any_type] * len(node.alias_tvars), newline, newcolumn) def remove_dups(tvars: Iterable[T]) -> List[T]: diff --git a/mypy/types.py b/mypy/types.py index b426b5721566..368cfe2f4b02 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -180,6 +180,11 @@ def _expand_once(self) -> Type: its public wrapper mypy.types.get_proper_type() is preferred. """ assert self.alias is not None + if self.alias.no_args: + # We know that no_args=True aliases like L = List must have an instance + # as their target. + assert isinstance(self.alias.target, Instance) # type: ignore[misc] + return self.alias.target.copy_modified(args=self.args) return replace_alias_tvars(self.alias.target, self.alias.alias_tvars, self.args, self.line, self.column) @@ -1936,6 +1941,7 @@ class TypeStrVisitor(SyntheticTypeVisitor[str]): def __init__(self, id_mapper: Optional[IdMapper] = None) -> None: self.id_mapper = id_mapper + self.any_as_dots = False def visit_unbound_type(self, t: UnboundType) -> str: s = t.name + '?' @@ -1954,6 +1960,8 @@ def visit_callable_argument(self, t: CallableArgument) -> str: return "{}({}, {})".format(t.constructor, typ, t.name) def visit_any(self, t: AnyType) -> str: + if self.any_as_dots and t.type_of_any == TypeOfAny.special_form: + return '...' return 'Any' def visit_none_type(self, t: NoneType) -> str: @@ -2093,7 +2101,11 @@ def visit_placeholder_type(self, t: PlaceholderType) -> str: def visit_type_alias_type(self, t: TypeAliasType) -> str: if t.alias is not None: - return ''.format(t.alias.fullname) + unrolled, recursed = t._partial_expansion() + self.any_as_dots = recursed + type_str = unrolled.accept(self) + self.any_as_dots = False + return type_str return '' def list_str(self, a: Iterable[Type]) -> str: diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 7f68c3afeff1..5a811dc2fd10 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -2472,7 +2472,7 @@ A = Dict[str, int] [builtins fixtures/dict.pyi] [out] --- Some crazy selef-referential named tuples, types dicts, and aliases +-- Some crazy self-referential named tuples, types dicts, and aliases -- to be sure that everything can be _serialized_ (i.e. ForwardRef's are removed). -- For this reason errors are silenced (tests with # type: ignore have equivalents in other files) diff --git a/test-data/unit/deps-expressions.test b/test-data/unit/deps-expressions.test index ec75ff0bff89..4c4d8af6d308 100644 --- a/test-data/unit/deps-expressions.test +++ b/test-data/unit/deps-expressions.test @@ -461,6 +461,6 @@ def f(x: Alias) -> None: pass def g() -> Literal[1]: return b [out] - -> m, m.f + -> , m, m.f -> m -> m, m.g diff --git a/test-data/unit/deps-types.test b/test-data/unit/deps-types.test index 869824f936c3..36cffe2ec306 100644 --- a/test-data/unit/deps-types.test +++ b/test-data/unit/deps-types.test @@ -444,7 +444,7 @@ class D(Generic[T, U]): pass class I: pass class S: pass [out] - -> m.f + -> , m.f -> m -> m -> , m, m.f @@ -467,7 +467,7 @@ class D(Generic[T, U]): pass class I: pass class S: pass [out] - -> m.f + -> , m.f -> m -> , m.f -> , m.f @@ -621,7 +621,7 @@ class D(Generic[T, U]): pass class I: pass class S: pass [out] - -> m + -> , m -> m -> m -> m @@ -650,7 +650,7 @@ class S: pass [out] -> m -> m - -> m + -> , m -> m -> m, a -> m, a @@ -675,8 +675,8 @@ class D(Generic[T, U]): pass class I: pass class S: pass [out] - -> m - -> m.f + -> , m, m.f + -> , m.f -> m -> m -> , m, m.f @@ -702,8 +702,8 @@ class D(Generic[T, U]): pass class I: pass class S: pass [out] - -> m.f - -> m + -> , m.f + -> , m, m.f -> m -> m, a -> m, a @@ -728,7 +728,7 @@ class D(Generic[T, U]): pass class I: pass class S: pass [out] - -> m.f + -> , m.f -> m.f -> m -> m @@ -753,7 +753,7 @@ class D(Generic[T, U]): pass class I: pass class S: pass [out] - -> m + -> , m -> m -> m.C -> m