Skip to content

Commit 5fdb16b

Browse files
authored
dmypy suggest: handful of bug fixes/improvements (#6997)
* Fix --no-any to not spuriously reject all methods * Support inferring types of decorated methods if the decorator doesn't change the type * Refuse to infer functions with then or more arguments to avoid spending forever calculating an exponentially large number of possibilities before deciding there are too many possibilities.
1 parent df52a85 commit 5fdb16b

File tree

2 files changed

+131
-3
lines changed

2 files changed

+131
-3
lines changed

mypy/suggestions.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@
2929
from mypy.state import strict_optional_set
3030
from mypy.types import (
3131
Type, AnyType, TypeOfAny, CallableType, UnionType, NoneType, Instance, TupleType, is_optional,
32+
TypeVarType, FunctionLike,
3233
TypeStrVisitor,
3334
)
3435
from mypy.build import State, Graph
3536
from mypy.nodes import (
3637
ARG_STAR, ARG_NAMED, ARG_STAR2, ARG_NAMED_OPT, FuncDef, MypyFile, SymbolTable,
37-
SymbolNode, TypeInfo, Expression, ReturnStmt,
38+
Decorator, RefExpr,
39+
SymbolNode, TypeInfo, Expression, ReturnStmt, CallExpr,
3840
reverse_builtin_aliases,
3941
)
4042
from mypy.server.update import FineGrainedBuildManager
@@ -220,8 +222,9 @@ def get_args(self, is_method: bool,
220222
types = [] # type: List[List[Type]]
221223
for i in range(len(base.arg_kinds)):
222224
# Make self args Any but this will get overriden somewhere in the checker
225+
# We call this a special form so that has_any_type doesn't consider it to be a real any
223226
if i == 0 and is_method:
224-
types.append([AnyType(TypeOfAny.explicit)])
227+
types.append([AnyType(TypeOfAny.special_form)])
225228
continue
226229

227230
all_arg_types = []
@@ -307,6 +310,9 @@ def get_suggestion(self, function: str) -> str:
307310
# FIXME: what about static and class methods?
308311
is_method = bool(node.info)
309312

313+
if len(node.arg_names) >= 10:
314+
raise SuggestionFailure("Too many arguments")
315+
310316
with strict_optional_set(graph[mod].options.strict_optional):
311317
guesses = self.get_guesses(
312318
is_method,
@@ -388,11 +394,38 @@ def find_node(self, key: str) -> Tuple[str, str, FuncDef]:
388394
raise SuggestionFailure("Unknown %s %s" %
389395
("method" if len(components) > 1 else "function", key))
390396
node = names[funcname].node
397+
if isinstance(node, Decorator):
398+
node = self.extract_from_decorator(node)
399+
if not node:
400+
raise SuggestionFailure("Object %s is a decorator we can't handle" % key)
401+
391402
if not isinstance(node, FuncDef):
392403
raise SuggestionFailure("Object %s is not a function" % key)
393404

394405
return (modname, tail, node)
395406

407+
def extract_from_decorator(self, node: Decorator) -> Optional[FuncDef]:
408+
for dec in node.decorators:
409+
typ = None
410+
if (isinstance(dec, RefExpr)
411+
and isinstance(dec.node, FuncDef)):
412+
typ = dec.node.type
413+
elif (isinstance(dec, CallExpr)
414+
and isinstance(dec.callee, RefExpr)
415+
and isinstance(dec.callee.node, FuncDef)
416+
and isinstance(dec.callee.node.type, CallableType)):
417+
typ = dec.callee.node.type.ret_type
418+
419+
if not isinstance(typ, FunctionLike):
420+
return None
421+
for ct in typ.items():
422+
if not (len(ct.arg_types) == 1
423+
and isinstance(ct.arg_types[0], TypeVarType)
424+
and ct.arg_types[0] == ct.ret_type):
425+
return None
426+
427+
return node.func
428+
396429
def try_type(self, func: FuncDef, typ: Type) -> List[str]:
397430
"""Recheck a function while assuming it has type typ.
398431

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

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ Foo('lol')
177177

178178
[case testSuggestInferMethod1]
179179
# flags: --strict-optional
180-
# suggest: foo.Foo.foo
180+
# suggest: --no-any foo.Foo.foo
181181
[file foo.py]
182182
class Foo:
183183
def __init__(self) -> None:
@@ -348,3 +348,98 @@ def baz() -> None:
348348
(Union[str, int, None], Union[int, None]) -> object
349349
(Union[str, int, None], Union[int, None]) -> None
350350
==
351+
352+
[case testSuggestInferFuncDecorator1]
353+
# flags: --strict-optional
354+
# suggest: foo.foo
355+
[file foo.py]
356+
from typing import TypeVar
357+
F = TypeVar('F')
358+
359+
def dec(x: F) -> F:
360+
return x
361+
362+
@dec
363+
def foo(arg):
364+
return arg
365+
[file bar.py]
366+
from foo import foo
367+
def bar() -> None:
368+
foo('abc')
369+
[builtins fixtures/isinstancelist.pyi]
370+
[out]
371+
(str) -> str
372+
==
373+
374+
[case testSuggestInferFuncDecorator2]
375+
# flags: --strict-optional
376+
# suggest: foo.foo
377+
[file foo.py]
378+
from typing import TypeVar, Callable, Any
379+
F = TypeVar('F', bound=Callable[..., Any])
380+
381+
def dec(x: F) -> F:
382+
return x
383+
384+
@dec
385+
def foo(arg):
386+
return arg
387+
[file bar.py]
388+
from foo import foo
389+
def bar() -> None:
390+
foo('abc')
391+
[builtins fixtures/isinstancelist.pyi]
392+
[out]
393+
(str) -> str
394+
==
395+
396+
[case testSuggestInferFuncDecorator3]
397+
# flags: --strict-optional
398+
# suggest: foo.foo
399+
[file foo.py]
400+
from typing import TypeVar, Callable, Any
401+
F = TypeVar('F', bound=Callable[..., Any])
402+
403+
def dec(s: str) -> Callable[[F], F]:
404+
def f(x: F) -> F:
405+
return x
406+
return f
407+
408+
@dec('lol')
409+
def foo(arg):
410+
return arg
411+
[file bar.py]
412+
from foo import foo
413+
def bar() -> None:
414+
foo('abc')
415+
[builtins fixtures/isinstancelist.pyi]
416+
[out]
417+
(str) -> str
418+
==
419+
420+
[case testSuggestInferFuncDecorator4]
421+
# flags: --strict-optional
422+
# suggest: foo.foo
423+
[file dec.py]
424+
from typing import TypeVar, Callable, Any
425+
F = TypeVar('F', bound=Callable[..., Any])
426+
427+
def dec(s: str) -> Callable[[F], F]:
428+
def f(x: F) -> F:
429+
return x
430+
return f
431+
432+
[file foo.py]
433+
import dec
434+
435+
@dec.dec('lol')
436+
def foo(arg):
437+
return arg
438+
[file bar.py]
439+
from foo import foo
440+
def bar() -> None:
441+
foo('abc')
442+
[builtins fixtures/isinstancelist.pyi]
443+
[out]
444+
(str) -> str
445+
==

0 commit comments

Comments
 (0)