Skip to content

Start using TypeAliasType in the semantic analyzer #7923

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

Merged
merged 16 commits into from
Nov 14, 2019
Merged
Show file tree
Hide file tree
Changes from 10 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
4 changes: 2 additions & 2 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(alias, 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)

Expand Down
6 changes: 3 additions & 3 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)

Expand All @@ -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
Expand Down
17 changes: 10 additions & 7 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2478,7 +2478,9 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
self.analyze_alias(rvalue, allow_placeholder=True)
if not res:
return False
if self.found_incomplete_ref(tag) or isinstance(res, PlaceholderType):
# TODO: Maybe we only need to reject top-level placeholders, similar
# to base classes.
if self.found_incomplete_ref(tag) or has_placeholder(res):
# Since we have got here, we know this must be a type alias (incomplete refs
# may appear in nested positions), therefore use becomes_typeinfo=True.
self.mark_incomplete(lvalue.name, rvalue, becomes_typeinfo=True)
Expand Down Expand Up @@ -2510,9 +2512,6 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:

if existing:
# An alias gets updated.
if self.final_iteration:
self.cannot_resolve_name(lvalue.name, 'iiiname', s)
return True
updated = False
if isinstance(existing.node, TypeAlias):
if existing.node.target != res:
Expand All @@ -2527,9 +2526,13 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
existing.node = alias_node
updated = True
if updated:
self.progress = True
# We need to defer so that this change can get propagated to base classes.
self.defer(s)
if self.final_iteration:
self.cannot_resolve_name(lvalue.name, 'name', s)
return True
else:
self.progress = True
# We need to defer so that this change can get propagated to base classes.
self.defer(s)
else:
self.add_symbol(lvalue.name, alias_node, s)
if isinstance(rvalue, RefExpr) and isinstance(rvalue.node, TypeAlias):
Expand Down
14 changes: 12 additions & 2 deletions mypy/semanal_typeargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -27,6 +29,7 @@ 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
self.seen_aliases = set() # type: Set[TypeAliasType]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add comment (also explain why we need this).


def visit_mypy_file(self, o: MypyFile) -> None:
self.errors.set_file(o.path, o.fullname(), scope=self.scope)
Expand All @@ -48,6 +51,13 @@ 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:
return
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add comment about this special case.

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.
Expand Down
6 changes: 5 additions & 1 deletion mypy/server/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -880,9 +880,13 @@ def visit_instance(self, typ: Instance) -> List[str]:

def visit_type_alias_type(self, typ: TypeAliasType) -> List[str]:
assert typ.alias is not None
triggers = []
trigger = make_trigger(typ.alias.fullname())
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

Expand Down
32 changes: 20 additions & 12 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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?
Expand Down
6 changes: 3 additions & 3 deletions mypy/type_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,14 +329,14 @@ 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 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)
Expand Down
22 changes: 14 additions & 8 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -219,7 +219,8 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
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.
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,
Expand Down Expand Up @@ -992,10 +993,12 @@ def expand_type_alias(node: TypeAlias, args: List[Type],
unexpanded_type=unexpanded_type)
if exp_len == 0 and act_len == 0:
if no_args:
assert isinstance(node.target, Instance) # type: ignore
assert isinstance(node.target, Instance) # type: ignore[misc]
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) and no_args: # type: ignore
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
Expand All @@ -1004,11 +1007,14 @@ def expand_type_alias(node: TypeAlias, args: List[Type],
fail('Bad number of arguments for type alias, expected: %s, given: %s'
% (exp_len, act_len), ctx)
return set_any_tvars(node, ctx.line, ctx.column, from_error=True)
typ = TypeAliasType(node, args, ctx.line, ctx.column) # type: Type
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


Expand Down
5 changes: 1 addition & 4 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2164,7 +2164,7 @@ def copy_type(t: TP) -> TP:
return copy.copy(t)


class InstantiateAliasVisitor(TypeTranslator, SyntheticTypeVisitor[Type]):
class InstantiateAliasVisitor(TypeTranslator):
def __init__(self, vars: List[str], subs: List[Type]) -> None:
self.replacements = {v: s for (v, s) in zip(vars, subs)}

Expand All @@ -2185,9 +2185,6 @@ def visit_type_var(self, typ: TypeVarType) -> Type:
return self.replacements[typ.name]
return typ

def visit_placeholder_type(self, typ: PlaceholderType) -> Type:
return typ


def replace_alias_tvars(tp: Type, vars: List[str], subs: List[Type],
newline: int, newcolumn: int) -> Type:
Expand Down
9 changes: 3 additions & 6 deletions test-data/unit/check-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -658,19 +658,16 @@ reveal_type(n) # N: Revealed type is 'Tuple[builtins.str, builtins.tuple[Any], f

[case testSelfRefNT3]

from typing import NamedTuple, Tuple, Union
from typing import NamedTuple, Tuple

class _B(NamedTuple):
class B(NamedTuple):
x: Tuple[A, int] # E: Cannot resolve name "A" (possible cyclic definition)
y: int
B = Union[_B]

_A = NamedTuple('_A', [
A = NamedTuple('A', [
('x', str),
('y', 'B'),
])
A = Union[_A]

n: B
m: A
reveal_type(n.x) # N: Revealed type is 'Tuple[Any, builtins.int]'
Expand Down
26 changes: 2 additions & 24 deletions test-data/unit/check-type-aliases.test
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,9 @@ Alias = Tuple[int, T]
# Recursive aliases are not supported yet.
from typing import Type, Callable, Union

# A = Union[A, int] # E: Cannot resolve name "A" (possible cyclic definition)
A = Union[A, int] # E: Cannot resolve name "A" (possible cyclic definition)
B = Callable[[B], int] # E: Cannot resolve name "B" (possible cyclic definition)
# C = Type[C] # E: Cannot resolve name "C" (possible cyclic definition)

x: B
reveal_type(x)
C = Type[C] # E: Cannot resolve name "C" (possible cyclic definition)

[case testRecursiveAliasesErrors2]

Expand Down Expand Up @@ -635,22 +632,3 @@ except E as e:
reveal_type(e) # N: Revealed type is '__main__.E'
[builtins fixtures/exception.pyi]
[out]

[case testTryFancyAliases]
from typing import Sequence, TypeVar, List, Union, Tuple
dummy: C
T = TypeVar('T')

Nested = Union[int, List[Nested]]

x: Nested = [1, [2, [3]]]
reveal_type(x)

B = Tuple[int, B]
y: B
reveal_type(y)
class C:
x: D

class D: ...
[builtins fixtures/list.pyi]
2 changes: 1 addition & 1 deletion test-data/unit/deps-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,6 @@ def f(x: Alias) -> None: pass
def g() -> Literal[1]:
return b
[out]
<m.Alias> -> m, m.f
<m.Alias> -> <m.f>, m, m.f
<m.a> -> m
<m.b> -> m, m.g
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy