Skip to content

Commit 0db2c46

Browse files
committed
dmypy suggest can now suggest through contextmanager-based decorators
1 parent 99e2688 commit 0db2c46

File tree

2 files changed

+51
-6
lines changed

2 files changed

+51
-6
lines changed

mypy/suggestions.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,15 @@
5252
SymbolNode,
5353
SymbolTable,
5454
TypeInfo,
55+
Var,
5556
reverse_builtin_aliases,
5657
)
5758
from mypy.options import Options
5859
from mypy.plugin import FunctionContext, MethodContext, Plugin
5960
from mypy.server.update import FineGrainedBuildManager
6061
from mypy.state import state
6162
from mypy.traverser import TraverserVisitor
62-
from mypy.typeops import make_simplified_union
63+
from mypy.typeops import bind_self, make_simplified_union
6364
from mypy.types import (
6465
AnyType,
6566
CallableType,
@@ -638,15 +639,20 @@ def find_node_by_file_and_line(self, file: str, line: int) -> tuple[str, SymbolN
638639
def extract_from_decorator(self, node: Decorator) -> FuncDef | None:
639640
for dec in node.decorators:
640641
typ = None
641-
if isinstance(dec, RefExpr) and isinstance(dec.node, FuncDef):
642-
typ = dec.node.type
642+
if isinstance(dec, RefExpr) and isinstance(dec.node, (Var, FuncDef)):
643+
typ = get_proper_type(dec.node.type)
643644
elif (
644645
isinstance(dec, CallExpr)
645646
and isinstance(dec.callee, RefExpr)
646-
and isinstance(dec.callee.node, FuncDef)
647-
and isinstance(dec.callee.node.type, CallableType)
647+
and isinstance(dec.callee.node, (Decorator, FuncDef))
648+
and isinstance((call_tp := get_proper_type(dec.callee.node.type)), CallableType)
648649
):
649-
typ = get_proper_type(dec.callee.node.type.ret_type)
650+
typ = get_proper_type(call_tp.ret_type)
651+
652+
if isinstance(typ, Instance):
653+
call_method = typ.type.get_method("__call__")
654+
if isinstance(call_method, FuncDef) and isinstance(call_method.type, FunctionLike):
655+
typ = bind_self(call_method.type, None)
650656

651657
if not isinstance(typ, FunctionLike):
652658
return None

test-data/unit/fine-grained-suggest.test

+39
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,45 @@ def bar() -> None:
602602
(str) -> str
603603
==
604604

605+
[case testSuggestInferFuncDecorator5]
606+
# suggest: foo.foo1
607+
# suggest: foo.foo2
608+
[file foo.py]
609+
from __future__ import annotations
610+
611+
from typing import TypeVar, Generator, Callable
612+
613+
F = TypeVar('F')
614+
615+
# simplified `@contextmanager
616+
class _impl:
617+
def __call__(self, f: F) -> F: return f
618+
def contextmanager(gen: Callable[[], Generator[None, None, None]]) -> Callable[[], _impl]: return _impl
619+
620+
@contextmanager
621+
def gen() -> Generator[None, None, None]:
622+
yield
623+
624+
@gen()
625+
def foo1(x):
626+
return x
627+
628+
foo1('hi')
629+
630+
inst = gen()
631+
632+
@inst
633+
def foo2(x):
634+
return x
635+
636+
foo2('hello')
637+
638+
[builtins fixtures/isinstancelist.pyi]
639+
[out]
640+
(str) -> str
641+
(str) -> str
642+
==
643+
605644
[case testSuggestFlexAny1]
606645
# suggest: --flex-any=0.4 m.foo
607646
# suggest: --flex-any=0.7 m.foo

0 commit comments

Comments
 (0)