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

Fix crash in daemon mode on new import cycle #14508

Merged
merged 1 commit into from
Jan 23, 2023
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
28 changes: 21 additions & 7 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1049,7 +1049,12 @@ def setup_self_type(self) -> None:
if info.self_type is not None:
if has_placeholder(info.self_type.upper_bound):
# Similar to regular (user defined) type variables.
self.defer(force_progress=True)
self.process_placeholder(
None,
"Self upper bound",
info,
force_progress=info.self_type.upper_bound != fill_typevars(info),
)
else:
return
info.self_type = TypeVarType("Self", f"{info.fullname}.Self", 0, [], fill_typevars(info))
Expand Down Expand Up @@ -2132,7 +2137,9 @@ def configure_tuple_base_class(self, defn: ClassDef, base: TupleType) -> Instanc
self.fail("Class has two incompatible bases derived from tuple", defn)
defn.has_incompatible_baseclass = True
if info.special_alias and has_placeholder(info.special_alias.target):
self.defer(force_progress=True)
self.process_placeholder(
None, "tuple base", defn, force_progress=base != info.tuple_type
)
info.update_tuple_type(base)
self.setup_alias_type_vars(defn)

Expand Down Expand Up @@ -3913,12 +3920,16 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool:
type_var = TypeVarExpr(name, self.qualified_name(name), values, upper_bound, variance)
type_var.line = call.line
call.analyzed = type_var
updated = True
else:
assert isinstance(call.analyzed, TypeVarExpr)
updated = values != call.analyzed.values or upper_bound != call.analyzed.upper_bound
call.analyzed.upper_bound = upper_bound
call.analyzed.values = values
if any(has_placeholder(v) for v in values) or has_placeholder(upper_bound):
self.defer(force_progress=True)
self.process_placeholder(
None, f"TypeVar {'values' if values else 'upper bound'}", s, force_progress=updated
)

self.add_symbol(name, call.analyzed, s)
return True
Expand Down Expand Up @@ -5931,7 +5942,9 @@ def is_incomplete_namespace(self, fullname: str) -> bool:
"""
return fullname in self.incomplete_namespaces

def process_placeholder(self, name: str, kind: str, ctx: Context) -> None:
def process_placeholder(
self, name: str | None, kind: str, ctx: Context, force_progress: bool = False
) -> None:
"""Process a reference targeting placeholder node.

If this is not a final iteration, defer current node,
Expand All @@ -5943,10 +5956,11 @@ def process_placeholder(self, name: str, kind: str, ctx: Context) -> None:
if self.final_iteration:
self.cannot_resolve_name(name, kind, ctx)
else:
self.defer(ctx)
self.defer(ctx, force_progress=force_progress)

def cannot_resolve_name(self, name: str, kind: str, ctx: Context) -> None:
self.fail(f'Cannot resolve {kind} "{name}" (possible cyclic definition)', ctx)
def cannot_resolve_name(self, name: str | None, kind: str, ctx: Context) -> None:
name_format = f' "{name}"' if name else ""
self.fail(f"Cannot resolve {kind}{name_format} (possible cyclic definition)", ctx)
if not self.options.disable_recursive_aliases and self.is_func_scope():
self.note("Recursive types are not allowed at function scope", ctx)

Expand Down
4 changes: 3 additions & 1 deletion mypy/semanal_namedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,9 @@ def build_namedtuple_typeinfo(
info.is_named_tuple = True
tuple_base = TupleType(types, fallback)
if info.special_alias and has_placeholder(info.special_alias.target):
self.api.defer(force_progress=True)
self.api.process_placeholder(
None, "NamedTuple item", info, force_progress=tuple_base != info.tuple_type
)
info.update_tuple_type(tuple_base)
info.line = line
# For use by mypyc.
Expand Down
10 changes: 8 additions & 2 deletions mypy/semanal_newtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,16 @@ def build_newtype_typeinfo(
init_func = FuncDef("__init__", args, Block([]), typ=signature)
init_func.info = info
init_func._fullname = info.fullname + ".__init__"
if not existing_info:
updated = True
else:
previous_sym = info.names["__init__"].node
assert isinstance(previous_sym, FuncDef)
updated = old_type != previous_sym.arguments[1].variable.type
info.names["__init__"] = SymbolTableNode(MDEF, init_func)

if has_placeholder(old_type) or info.tuple_type and has_placeholder(info.tuple_type):
self.api.defer(force_progress=True)
if has_placeholder(old_type):
self.api.process_placeholder(None, "NewType base", info, force_progress=updated)
return info

# Helpers
Expand Down
6 changes: 6 additions & 0 deletions mypy/semanal_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,12 @@ def qualified_name(self, n: str) -> str:
def is_typeshed_stub_file(self) -> bool:
raise NotImplementedError

@abstractmethod
def process_placeholder(
self, name: str | None, kind: str, ctx: Context, force_progress: bool = False
) -> None:
raise NotImplementedError


def set_callable_name(sig: Type, fdef: FuncDef) -> ProperType:
sig = get_proper_type(sig)
Expand Down
4 changes: 3 additions & 1 deletion mypy/semanal_typeddict.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,9 @@ def build_typeddict_typeinfo(
info = existing_info or self.api.basic_new_typeinfo(name, fallback, line)
typeddict_type = TypedDictType(dict(zip(items, types)), required_keys, fallback)
if info.special_alias and has_placeholder(info.special_alias.target):
self.api.defer(force_progress=True)
self.api.process_placeholder(
None, "TypedDict item", info, force_progress=typeddict_type != info.typeddict_type
)
info.update_typeddict_type(typeddict_type)
return info

Expand Down
8 changes: 8 additions & 0 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2857,6 +2857,14 @@ def accept(self, visitor: TypeVisitor[T]) -> T:
assert isinstance(visitor, SyntheticTypeVisitor)
return cast(T, visitor.visit_placeholder_type(self))

def __hash__(self) -> int:
return hash((self.fullname, tuple(self.args)))

def __eq__(self, other: object) -> bool:
if not isinstance(other, PlaceholderType):
return NotImplemented
return self.fullname == other.fullname and self.args == other.args

def serialize(self) -> str:
# We should never get here since all placeholders should be replaced
# during semantic analysis.
Expand Down
77 changes: 77 additions & 0 deletions test-data/unit/fine-grained-follow-imports.test
Original file line number Diff line number Diff line change
Expand Up @@ -769,3 +769,80 @@ from . import mod3
==
main.py:1: error: Cannot find implementation or library stub for module named "pkg"
main.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports

[case testNewImportCycleTypeVarBound]
# flags: --follow-imports=normal
# cmd: mypy main.py
# cmd2: mypy other.py

[file main.py]
# empty

[file other.py.2]
import trio

[file trio/__init__.py.2]
from typing import TypeVar
import trio
from . import abc as abc

T = TypeVar("T", bound=trio.abc.A)

[file trio/abc.py.2]
import trio
class A: ...
[out]
==

[case testNewImportCycleTupleBase]
# flags: --follow-imports=normal
# cmd: mypy main.py
# cmd2: mypy other.py

[file main.py]
# empty

[file other.py.2]
import trio

[file trio/__init__.py.2]
from typing import TypeVar, Tuple
import trio
from . import abc as abc

class C(Tuple[trio.abc.A, trio.abc.A]): ...

[file trio/abc.py.2]
import trio
class A: ...
[builtins fixtures/tuple.pyi]
[out]
==

[case testNewImportCycleTypedDict]
# flags: --follow-imports=normal
# cmd: mypy main.py
# cmd2: mypy other.py

[file main.py]
# empty

[file other.py.2]
import trio

[file trio/__init__.py.2]
from typing import TypeVar
from typing_extensions import TypedDict
import trio
from . import abc as abc

class C(TypedDict):
x: trio.abc.A
y: trio.abc.A

[file trio/abc.py.2]
import trio
class A: ...
[builtins fixtures/dict.pyi]
[out]
==