Skip to content

Commit c2949e9

Browse files
authored
Fix daemon crashes on generic methods (#13551)
Fix #11795 The fix is straightforward (but ideally generic callables should be normalized in the first place, e.g. by better use of namespaces).
1 parent 92118a9 commit c2949e9

File tree

3 files changed

+112
-21
lines changed

3 files changed

+112
-21
lines changed

mypy/server/astdiff.py

+24-2
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,10 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method'
5252

5353
from __future__ import annotations
5454

55-
from typing import Sequence, Tuple
55+
from typing import Sequence, Tuple, cast
5656
from typing_extensions import TypeAlias as _TypeAlias
5757

58+
from mypy.expandtype import expand_type
5859
from mypy.nodes import (
5960
UNBOUND_IMPORTED,
6061
Decorator,
@@ -88,6 +89,8 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method'
8889
TypeAliasType,
8990
TypedDictType,
9091
TypeType,
92+
TypeVarId,
93+
TypeVarLikeType,
9194
TypeVarTupleType,
9295
TypeVarType,
9396
TypeVisitor,
@@ -388,7 +391,8 @@ def visit_parameters(self, typ: Parameters) -> SnapshotItem:
388391
)
389392

390393
def visit_callable_type(self, typ: CallableType) -> SnapshotItem:
391-
# FIX generics
394+
if typ.is_generic():
395+
typ = self.normalize_callable_variables(typ)
392396
return (
393397
"CallableType",
394398
snapshot_types(typ.arg_types),
@@ -397,8 +401,26 @@ def visit_callable_type(self, typ: CallableType) -> SnapshotItem:
397401
tuple(typ.arg_kinds),
398402
typ.is_type_obj(),
399403
typ.is_ellipsis_args,
404+
snapshot_types(typ.variables),
400405
)
401406

407+
def normalize_callable_variables(self, typ: CallableType) -> CallableType:
408+
"""Normalize all type variable ids to run from -1 to -len(variables)."""
409+
tvs = []
410+
tvmap: dict[TypeVarId, Type] = {}
411+
for i, v in enumerate(typ.variables):
412+
tid = TypeVarId(-1 - i)
413+
if isinstance(v, TypeVarType):
414+
tv: TypeVarLikeType = v.copy_modified(id=tid)
415+
elif isinstance(v, TypeVarTupleType):
416+
tv = v.copy_modified(id=tid)
417+
else:
418+
assert isinstance(v, ParamSpecType)
419+
tv = v.copy_modified(id=tid)
420+
tvs.append(tv)
421+
tvmap[v.id] = tv
422+
return cast(CallableType, expand_type(typ, tvmap)).copy_modified(variables=tvs)
423+
402424
def visit_tuple_type(self, typ: TupleType) -> SnapshotItem:
403425
return ("TupleType", snapshot_types(typ.items))
404426

mypy/types.py

+26-19
Original file line numberDiff line numberDiff line change
@@ -517,15 +517,23 @@ def __init__(
517517
@staticmethod
518518
def new_unification_variable(old: TypeVarType) -> TypeVarType:
519519
new_id = TypeVarId.new(meta_level=1)
520+
return old.copy_modified(id=new_id)
521+
522+
def copy_modified(
523+
self,
524+
values: Bogus[list[Type]] = _dummy,
525+
upper_bound: Bogus[Type] = _dummy,
526+
id: Bogus[TypeVarId | int] = _dummy,
527+
) -> TypeVarType:
520528
return TypeVarType(
521-
old.name,
522-
old.fullname,
523-
new_id,
524-
old.values,
525-
old.upper_bound,
526-
old.variance,
527-
old.line,
528-
old.column,
529+
self.name,
530+
self.fullname,
531+
self.id if id is _dummy else id,
532+
self.values if values is _dummy else values,
533+
self.upper_bound if upper_bound is _dummy else upper_bound,
534+
self.variance,
535+
self.line,
536+
self.column,
529537
)
530538

531539
def accept(self, visitor: TypeVisitor[T]) -> T:
@@ -616,16 +624,7 @@ def __init__(
616624
@staticmethod
617625
def new_unification_variable(old: ParamSpecType) -> ParamSpecType:
618626
new_id = TypeVarId.new(meta_level=1)
619-
return ParamSpecType(
620-
old.name,
621-
old.fullname,
622-
new_id,
623-
old.flavor,
624-
old.upper_bound,
625-
line=old.line,
626-
column=old.column,
627-
prefix=old.prefix,
628-
)
627+
return old.copy_modified(id=new_id)
629628

630629
def with_flavor(self, flavor: int) -> ParamSpecType:
631630
return ParamSpecType(
@@ -737,8 +736,16 @@ def __eq__(self, other: object) -> bool:
737736
@staticmethod
738737
def new_unification_variable(old: TypeVarTupleType) -> TypeVarTupleType:
739738
new_id = TypeVarId.new(meta_level=1)
739+
return old.copy_modified(id=new_id)
740+
741+
def copy_modified(self, id: Bogus[TypeVarId | int] = _dummy) -> TypeVarTupleType:
740742
return TypeVarTupleType(
741-
old.name, old.fullname, new_id, old.upper_bound, line=old.line, column=old.column
743+
self.name,
744+
self.fullname,
745+
self.id if id is _dummy else id,
746+
self.upper_bound,
747+
line=self.line,
748+
column=self.column,
742749
)
743750

744751

test-data/unit/fine-grained.test

+62
Original file line numberDiff line numberDiff line change
@@ -9969,3 +9969,65 @@ m.py:9: note: Expected:
99699969
m.py:9: note: def update() -> bool
99709970
m.py:9: note: Got:
99719971
m.py:9: note: def update() -> str
9972+
9973+
[case testBoundGenericMethodFine]
9974+
import main
9975+
[file main.py]
9976+
import lib
9977+
[file main.py.3]
9978+
import lib
9979+
reveal_type(lib.foo(42))
9980+
[file lib/__init__.pyi]
9981+
from lib import context
9982+
foo = context.test.foo
9983+
[file lib/context.pyi]
9984+
from typing import TypeVar
9985+
import lib.other
9986+
9987+
T = TypeVar("T")
9988+
class Test:
9989+
def foo(self, x: T, n: lib.other.C = ...) -> T: ...
9990+
test: Test
9991+
9992+
[file lib/other.pyi]
9993+
class C: ...
9994+
[file lib/other.pyi.2]
9995+
class B: ...
9996+
class C(B): ...
9997+
[out]
9998+
==
9999+
==
10000+
main.py:2: note: Revealed type is "builtins.int"
10001+
10002+
[case testBoundGenericMethodParamSpecFine]
10003+
import main
10004+
[file main.py]
10005+
import lib
10006+
[file main.py.3]
10007+
from typing import Callable
10008+
import lib
10009+
f: Callable[[], int]
10010+
reveal_type(lib.foo(f))
10011+
[file lib/__init__.pyi]
10012+
from lib import context
10013+
foo = context.test.foo
10014+
[file lib/context.pyi]
10015+
from typing_extensions import ParamSpec
10016+
from typing import Callable
10017+
import lib.other
10018+
10019+
P = ParamSpec("P")
10020+
class Test:
10021+
def foo(self, x: Callable[P, int], n: lib.other.C = ...) -> Callable[P, str]: ...
10022+
test: Test
10023+
10024+
[file lib/other.pyi]
10025+
class C: ...
10026+
[file lib/other.pyi.2]
10027+
class B: ...
10028+
class C(B): ...
10029+
[builtins fixtures/dict.pyi]
10030+
[out]
10031+
==
10032+
==
10033+
main.py:4: note: Revealed type is "def () -> builtins.str"

0 commit comments

Comments
 (0)