Skip to content

Fix daemon crashes on generic methods #13551

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 1 commit into from
Aug 29, 2022
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
26 changes: 24 additions & 2 deletions mypy/server/astdiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method'

from __future__ import annotations

from typing import Sequence, Tuple
from typing import Sequence, Tuple, cast
from typing_extensions import TypeAlias as _TypeAlias

from mypy.expandtype import expand_type
from mypy.nodes import (
UNBOUND_IMPORTED,
Decorator,
Expand Down Expand Up @@ -88,6 +89,8 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method'
TypeAliasType,
TypedDictType,
TypeType,
TypeVarId,
TypeVarLikeType,
TypeVarTupleType,
TypeVarType,
TypeVisitor,
Expand Down Expand Up @@ -388,7 +391,8 @@ def visit_parameters(self, typ: Parameters) -> SnapshotItem:
)

def visit_callable_type(self, typ: CallableType) -> SnapshotItem:
# FIX generics
if typ.is_generic():
typ = self.normalize_callable_variables(typ)
return (
"CallableType",
snapshot_types(typ.arg_types),
Expand All @@ -397,8 +401,26 @@ def visit_callable_type(self, typ: CallableType) -> SnapshotItem:
tuple(typ.arg_kinds),
typ.is_type_obj(),
typ.is_ellipsis_args,
snapshot_types(typ.variables),
)

def normalize_callable_variables(self, typ: CallableType) -> CallableType:
"""Normalize all type variable ids to run from -1 to -len(variables)."""
tvs = []
tvmap: dict[TypeVarId, Type] = {}
for i, v in enumerate(typ.variables):
tid = TypeVarId(-1 - i)
if isinstance(v, TypeVarType):
tv: TypeVarLikeType = v.copy_modified(id=tid)
elif isinstance(v, TypeVarTupleType):
tv = v.copy_modified(id=tid)
else:
assert isinstance(v, ParamSpecType)
tv = v.copy_modified(id=tid)
tvs.append(tv)
tvmap[v.id] = tv
return cast(CallableType, expand_type(typ, tvmap)).copy_modified(variables=tvs)

def visit_tuple_type(self, typ: TupleType) -> SnapshotItem:
return ("TupleType", snapshot_types(typ.items))

Expand Down
45 changes: 26 additions & 19 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,15 +517,23 @@ def __init__(
@staticmethod
def new_unification_variable(old: TypeVarType) -> TypeVarType:
new_id = TypeVarId.new(meta_level=1)
return old.copy_modified(id=new_id)

def copy_modified(
self,
values: Bogus[list[Type]] = _dummy,
upper_bound: Bogus[Type] = _dummy,
id: Bogus[TypeVarId | int] = _dummy,
) -> TypeVarType:
return TypeVarType(
old.name,
old.fullname,
new_id,
old.values,
old.upper_bound,
old.variance,
old.line,
old.column,
self.name,
self.fullname,
self.id if id is _dummy else id,
self.values if values is _dummy else values,
self.upper_bound if upper_bound is _dummy else upper_bound,
self.variance,
self.line,
self.column,
)

def accept(self, visitor: TypeVisitor[T]) -> T:
Expand Down Expand Up @@ -616,16 +624,7 @@ def __init__(
@staticmethod
def new_unification_variable(old: ParamSpecType) -> ParamSpecType:
new_id = TypeVarId.new(meta_level=1)
return ParamSpecType(
old.name,
old.fullname,
new_id,
old.flavor,
old.upper_bound,
line=old.line,
column=old.column,
prefix=old.prefix,
)
return old.copy_modified(id=new_id)

def with_flavor(self, flavor: int) -> ParamSpecType:
return ParamSpecType(
Expand Down Expand Up @@ -737,8 +736,16 @@ def __eq__(self, other: object) -> bool:
@staticmethod
def new_unification_variable(old: TypeVarTupleType) -> TypeVarTupleType:
new_id = TypeVarId.new(meta_level=1)
return old.copy_modified(id=new_id)

def copy_modified(self, id: Bogus[TypeVarId | int] = _dummy) -> TypeVarTupleType:
return TypeVarTupleType(
old.name, old.fullname, new_id, old.upper_bound, line=old.line, column=old.column
self.name,
self.fullname,
self.id if id is _dummy else id,
self.upper_bound,
line=self.line,
column=self.column,
)


Expand Down
62 changes: 62 additions & 0 deletions test-data/unit/fine-grained.test
Original file line number Diff line number Diff line change
Expand Up @@ -9969,3 +9969,65 @@ m.py:9: note: Expected:
m.py:9: note: def update() -> bool
m.py:9: note: Got:
m.py:9: note: def update() -> str

[case testBoundGenericMethodFine]
import main
[file main.py]
import lib
[file main.py.3]
import lib
reveal_type(lib.foo(42))
[file lib/__init__.pyi]
from lib import context
foo = context.test.foo
[file lib/context.pyi]
from typing import TypeVar
import lib.other

T = TypeVar("T")
class Test:
def foo(self, x: T, n: lib.other.C = ...) -> T: ...
test: Test

[file lib/other.pyi]
class C: ...
[file lib/other.pyi.2]
class B: ...
class C(B): ...
[out]
==
==
main.py:2: note: Revealed type is "builtins.int"

[case testBoundGenericMethodParamSpecFine]
import main
[file main.py]
import lib
[file main.py.3]
from typing import Callable
import lib
f: Callable[[], int]
reveal_type(lib.foo(f))
[file lib/__init__.pyi]
from lib import context
foo = context.test.foo
[file lib/context.pyi]
from typing_extensions import ParamSpec
from typing import Callable
import lib.other

P = ParamSpec("P")
class Test:
def foo(self, x: Callable[P, int], n: lib.other.C = ...) -> Callable[P, str]: ...
test: Test

[file lib/other.pyi]
class C: ...
[file lib/other.pyi.2]
class B: ...
class C(B): ...
[builtins fixtures/dict.pyi]
[out]
==
==
main.py:4: note: Revealed type is "def () -> builtins.str"