Skip to content

Commit f921981

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

File tree

2 files changed

+61
-6
lines changed

2 files changed

+61
-6
lines changed

Diff for: 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, Var))
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

Diff for: test-data/unit/fine-grained-suggest.test

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

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

0 commit comments

Comments
 (0)