Skip to content

Commit c39335a

Browse files
authored
[suggest] Fix inference from collection literals (python#7979)
This is another take at python#7810 which was reverted because it caused internal trouble. Do this by ignoring constraints with Any when the Any comes from the suggestion engine. The Any context causes assigned types to be Any overaggressively. Restricting it only to Anys from the suggestion engine avoids the issues seen last time. Doing it at a fine-grained level in constraints allows us to do better refining things like `List[Any]`.
1 parent 6347ba7 commit c39335a

File tree

4 files changed

+88
-15
lines changed

4 files changed

+88
-15
lines changed

mypy/constraints.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ def _infer_constraints(template: Type, actual: Type,
108108
template = get_proper_type(template)
109109
actual = get_proper_type(actual)
110110

111+
# Ignore Any types from the type suggestion engine to avoid them
112+
# causing us to infer Any in situations where a better job could
113+
# be done otherwise. (This can produce false positives but that
114+
# doesn't really matter because it is all heuristic anyway.)
115+
if isinstance(actual, AnyType) and actual.type_of_any == TypeOfAny.suggestion_engine:
116+
return []
117+
111118
# If the template is simply a type variable, emit a Constraint directly.
112119
# We need to handle this case before handling Unions for two reasons:
113120
# 1. "T <: Union[U1, U2]" is not equivalent to "T <: U1 or T <: U2",

mypy/suggestions.py

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@
2323
"""
2424

2525
from typing import (
26-
List, Optional, Tuple, Dict, Callable, Union, NamedTuple, TypeVar, Iterator,
26+
List, Optional, Tuple, Dict, Callable, Union, NamedTuple, TypeVar, Iterator, cast,
2727
)
2828
from typing_extensions import TypedDict
2929

3030
from mypy.state import strict_optional_set
3131
from mypy.types import (
3232
Type, AnyType, TypeOfAny, CallableType, UnionType, NoneType, Instance, TupleType,
33-
TypeVarType, FunctionLike,
33+
TypeVarType, FunctionLike, UninhabitedType,
3434
TypeStrVisitor, TypeTranslator,
3535
is_optional, remove_optional, ProperType, get_proper_type,
3636
TypedDictType, TypeAliasType
@@ -282,17 +282,19 @@ def with_export_types(self) -> Iterator[None]:
282282

283283
def get_trivial_type(self, fdef: FuncDef) -> CallableType:
284284
"""Generate a trivial callable type from a func def, with all Anys"""
285+
# The Anys are marked as being from the suggestion engine
286+
# since they need some special treatment (specifically,
287+
# constraint generation ignores them.)
285288
return CallableType(
286-
[AnyType(TypeOfAny.unannotated) for a in fdef.arg_kinds],
289+
[AnyType(TypeOfAny.suggestion_engine) for a in fdef.arg_kinds],
287290
fdef.arg_kinds,
288291
fdef.arg_names,
289-
# We call this a special form so that has_any_type doesn't consider it to be a real any
290-
AnyType(TypeOfAny.special_form),
292+
AnyType(TypeOfAny.suggestion_engine),
291293
self.builtin_type('builtins.function'))
292294

293295
def get_starting_type(self, fdef: FuncDef) -> CallableType:
294296
if isinstance(fdef.type, CallableType):
295-
return fdef.type
297+
return make_suggestion_anys(fdef.type)
296298
else:
297299
return self.get_trivial_type(fdef)
298300

@@ -304,9 +306,8 @@ def get_args(self, is_method: bool,
304306
types = [] # type: List[List[Type]]
305307
for i in range(len(base.arg_kinds)):
306308
# Make self args Any but this will get overriden somewhere in the checker
307-
# We call this a special form so that has_any_type doesn't consider it to be a real any
308309
if i == 0 and is_method:
309-
types.append([AnyType(TypeOfAny.special_form)])
310+
types.append([AnyType(TypeOfAny.suggestion_engine)])
310311
continue
311312

312313
all_arg_types = []
@@ -383,13 +384,16 @@ def get_callsites(self, func: FuncDef) -> Tuple[List[Callsite], List[str]]:
383384

384385
return collector_plugin.mystery_hits, errors
385386

386-
def filter_options(self, guesses: List[CallableType], is_method: bool) -> List[CallableType]:
387+
def filter_options(
388+
self, guesses: List[CallableType], is_method: bool, ignore_return: bool
389+
) -> List[CallableType]:
387390
"""Apply any configured filters to the possible guesses.
388391
389392
Currently the only option is filtering based on Any prevalance."""
390393
return [
391394
t for t in guesses
392-
if self.flex_any is None or any_score_callable(t, is_method) >= self.flex_any
395+
if self.flex_any is None
396+
or any_score_callable(t, is_method, ignore_return) >= self.flex_any
393397
]
394398

395399
def find_best(self, func: FuncDef, guesses: List[CallableType]) -> Tuple[CallableType, int]:
@@ -426,7 +430,7 @@ def get_suggestion(self, mod: str, node: FuncDef) -> PyAnnotateSignature:
426430
callsites,
427431
uses,
428432
)
429-
guesses = self.filter_options(guesses, is_method)
433+
guesses = self.filter_options(guesses, is_method, ignore_return=True)
430434
best, _ = self.find_best(node, guesses)
431435

432436
# Now try to find the return type!
@@ -439,7 +443,7 @@ def get_suggestion(self, mod: str, node: FuncDef) -> PyAnnotateSignature:
439443
ret_types = [NoneType()]
440444

441445
guesses = [best.copy_modified(ret_type=refine_type(best.ret_type, t)) for t in ret_types]
442-
guesses = self.filter_options(guesses, is_method)
446+
guesses = self.filter_options(guesses, is_method, ignore_return=False)
443447
best, errors = self.find_best(node, guesses)
444448

445449
if self.no_errors and errors:
@@ -710,7 +714,7 @@ def any_score_type(ut: Type, arg_pos: bool) -> float:
710714
Higher is better, 1.0 is max
711715
"""
712716
t = get_proper_type(ut)
713-
if isinstance(t, AnyType) and t.type_of_any != TypeOfAny.special_form:
717+
if isinstance(t, AnyType) and t.type_of_any != TypeOfAny.suggestion_engine:
714718
return 0
715719
if isinstance(t, NoneType) and arg_pos:
716720
return 0.5
@@ -727,14 +731,14 @@ def any_score_type(ut: Type, arg_pos: bool) -> float:
727731
return 1.0
728732

729733

730-
def any_score_callable(t: CallableType, is_method: bool) -> float:
734+
def any_score_callable(t: CallableType, is_method: bool, ignore_return: bool) -> float:
731735
# Ignore the first argument of methods
732736
scores = [any_score_type(x, arg_pos=True) for x in t.arg_types[int(is_method):]]
733737
# Return type counts twice (since it spreads type information), unless it is
734738
# None in which case it does not count at all. (Though it *does* still count
735739
# if there are no arguments.)
736740
if not isinstance(get_proper_type(t.ret_type), NoneType) or not scores:
737-
ret = any_score_type(t.ret_type, arg_pos=False)
741+
ret = 1.0 if ignore_return else any_score_type(t.ret_type, arg_pos=False)
738742
scores += [ret, ret]
739743

740744
return sum(scores) / len(scores)
@@ -796,6 +800,9 @@ def visit_tuple_type(self, t: TupleType) -> str:
796800
s = self.list_str(t.items)
797801
return 'Tuple[{}]'.format(s)
798802

803+
def visit_uninhabited_type(self, t: UninhabitedType) -> str:
804+
return "Any"
805+
799806
def visit_typeddict_type(self, t: TypedDictType) -> str:
800807
return t.fallback.accept(self)
801808

@@ -837,6 +844,26 @@ def visit_instance(self, t: Instance) -> Type:
837844
return super().visit_instance(t)
838845

839846

847+
TType = TypeVar('TType', bound=Type)
848+
849+
850+
def make_suggestion_anys(t: TType) -> TType:
851+
"""Make all anys in the type as coming from the suggestion engine.
852+
853+
This keeps those Anys from influencing constraint generation,
854+
which allows us to do better when refining types.
855+
"""
856+
return cast(TType, t.accept(MakeSuggestionAny()))
857+
858+
859+
class MakeSuggestionAny(TypeTranslator):
860+
def visit_any(self, t: AnyType) -> Type:
861+
return t.copy_modified(type_of_any=TypeOfAny.suggestion_engine)
862+
863+
def visit_type_alias_type(self, t: TypeAliasType) -> Type:
864+
return t.copy_modified(args=[a.accept(self) for a in t.args])
865+
866+
840867
def generate_type_combinations(types: List[Type]) -> List[Type]:
841868
"""Generate possible combinations of a list of types.
842869

mypy/types.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ class TypeOfAny:
103103
from_another_any = 7 # type: Final
104104
# Does this Any come from an implementation limitation/bug?
105105
implementation_artifact = 8 # type: Final
106+
# Does this Any come from use in the suggestion engine? This is
107+
# used to ignore Anys inserted by the suggestion engine when
108+
# generating constraints.
109+
suggestion_engine = 9 # type: Final
106110

107111

108112
def deserialize_type(data: Union[JsonDict, str]) -> 'Type':

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,41 @@ z = foo(f(), g())
908908
(foo.List[Any], UNKNOWN) -> Tuple[foo.List[Any], Any]
909909
==
910910

911+
[case testSuggestDict]
912+
# suggest: foo.foo
913+
# suggest: foo.bar
914+
# suggest: foo.baz
915+
# suggest: foo.quux
916+
# suggest: foo.spam
917+
[file foo.py]
918+
from typing import List, Any
919+
920+
def foo():
921+
return {'x': 5}
922+
923+
def bar():
924+
return {}
925+
926+
def baz() -> List[Any]:
927+
return [{'x': 5}]
928+
929+
def quux() -> List[Any]:
930+
return [1]
931+
932+
def spam(x):
933+
pass
934+
935+
spam({'x': 5})
936+
937+
[builtins fixtures/dict.pyi]
938+
[out]
939+
() -> typing.Dict[str, int]
940+
() -> typing.Dict[Any, Any]
941+
() -> foo:List[typing.Dict[str, int]]
942+
() -> foo.List[int]
943+
(typing.Dict[str, int]) -> None
944+
==
945+
911946
[case testSuggestWithErrors]
912947
# suggest: foo.foo
913948
[file foo.py]

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy