28
28
29
29
from mypy .state import strict_optional_set
30
30
from mypy .types import (
31
- Type , AnyType , TypeOfAny , CallableType , UnionType , NoneType , Instance , TupleType , is_optional ,
31
+ Type , AnyType , TypeOfAny , CallableType , UnionType , NoneType , Instance , TupleType ,
32
32
TypeVarType , FunctionLike ,
33
- TypeStrVisitor ,
33
+ TypeStrVisitor , TypeTranslator ,
34
+ is_optional ,
34
35
)
35
36
from mypy .build import State , Graph
36
37
from mypy .nodes import (
@@ -145,7 +146,8 @@ class SuggestionEngine:
145
146
def __init__ (self , fgmanager : FineGrainedBuildManager ,
146
147
json : bool ,
147
148
no_errors : bool = False ,
148
- no_any : bool = False ) -> None :
149
+ no_any : bool = False ,
150
+ try_text : bool = False ) -> None :
149
151
self .fgmanager = fgmanager
150
152
self .manager = fgmanager .manager
151
153
self .plugin = self .manager .plugin
@@ -154,6 +156,7 @@ def __init__(self, fgmanager: FineGrainedBuildManager,
154
156
self .give_json = json
155
157
self .no_errors = no_errors
156
158
self .no_any = no_any
159
+ self .try_text = try_text
157
160
158
161
self .max_guesses = 16
159
162
@@ -251,13 +254,20 @@ def get_default_arg_types(self, state: State, fdef: FuncDef) -> List[Optional[Ty
251
254
return [self .manager .all_types [arg .initializer ] if arg .initializer else None
252
255
for arg in fdef .arguments ]
253
256
257
+ def add_adjustments (self , typs : List [Type ]) -> List [Type ]:
258
+ if not self .try_text or self .manager .options .python_version [0 ] != 2 :
259
+ return typs
260
+ translator = StrToText (self .builtin_type )
261
+ return dedup (typs + [tp .accept (translator ) for tp in typs ])
262
+
254
263
def get_guesses (self , is_method : bool , base : CallableType , defaults : List [Optional [Type ]],
255
264
callsites : List [Callsite ]) -> List [CallableType ]:
256
265
"""Compute a list of guesses for a function's type.
257
266
258
267
This focuses just on the argument types, and doesn't change the provided return type.
259
268
"""
260
269
options = self .get_args (is_method , base , defaults , callsites )
270
+ options = [self .add_adjustments (tps ) for tps in options ]
261
271
return [base .copy_modified (arg_types = list (x )) for x in itertools .product (* options )]
262
272
263
273
def get_callsites (self , func : FuncDef ) -> Tuple [List [Callsite ], List [str ]]:
@@ -292,7 +302,7 @@ def find_best(self, func: FuncDef, guesses: List[CallableType]) -> Tuple[Callabl
292
302
raise SuggestionFailure ("No guesses that match criteria!" )
293
303
errors = {guess : self .try_type (func , guess ) for guess in guesses }
294
304
best = min (guesses ,
295
- key = lambda s : (count_errors (errors [s ]), score_callable (s )))
305
+ key = lambda s : (count_errors (errors [s ]), self . score_callable (s )))
296
306
return best , count_errors (errors [best ])
297
307
298
308
def get_suggestion (self , function : str ) -> str :
@@ -501,6 +511,25 @@ def format_callable(self,
501
511
def format_type (self , cur_module : Optional [str ], typ : Type ) -> str :
502
512
return typ .accept (TypeFormatter (cur_module , self .graph ))
503
513
514
+ def score_type (self , t : Type ) -> int :
515
+ """Generate a score for a type that we use to pick which type to use.
516
+
517
+ Lower is better, prefer non-union/non-any types. Don't penalize optionals.
518
+ """
519
+ if isinstance (t , AnyType ):
520
+ return 20
521
+ if isinstance (t , UnionType ):
522
+ if any (isinstance (x , AnyType ) for x in t .items ):
523
+ return 20
524
+ if not is_optional (t ):
525
+ return 10
526
+ if self .try_text and isinstance (t , Instance ) and t .type .fullname () == 'builtins.str' :
527
+ return 1
528
+ return 0
529
+
530
+ def score_callable (self , t : CallableType ) -> int :
531
+ return sum ([self .score_type (x ) for x in t .arg_types ])
532
+
504
533
505
534
class TypeFormatter (TypeStrVisitor ):
506
535
"""Visitor used to format types
@@ -555,6 +584,17 @@ def visit_tuple_type(self, t: TupleType) -> str:
555
584
return 'Tuple[{}]' .format (s )
556
585
557
586
587
+ class StrToText (TypeTranslator ):
588
+ def __init__ (self , builtin_type : Callable [[str ], Instance ]) -> None :
589
+ self .text_type = builtin_type ('builtins.unicode' )
590
+
591
+ def visit_instance (self , t : Instance ) -> Type :
592
+ if t .type .fullname () == 'builtins.str' :
593
+ return self .text_type
594
+ else :
595
+ return super ().visit_instance (t )
596
+
597
+
558
598
def generate_type_combinations (types : List [Type ]) -> List [Type ]:
559
599
"""Generate possible combinations of a list of types.
560
600
@@ -573,25 +613,6 @@ def count_errors(msgs: List[str]) -> int:
573
613
return len ([x for x in msgs if ' error: ' in x ])
574
614
575
615
576
- def score_type (t : Type ) -> int :
577
- """Generate a score for a type that we use to pick which type to use.
578
-
579
- Lower is better, prefer non-union/non-any types. Don't penalize optionals.
580
- """
581
- if isinstance (t , AnyType ):
582
- return 2
583
- if isinstance (t , UnionType ):
584
- if any (isinstance (x , AnyType ) for x in t .items ):
585
- return 2
586
- if not is_optional (t ):
587
- return 1
588
- return 0
589
-
590
-
591
- def score_callable (t : CallableType ) -> int :
592
- return sum ([score_type (x ) for x in t .arg_types ])
593
-
594
-
595
616
def callable_has_any (t : CallableType ) -> int :
596
617
# We count a bare None in argument position as Any, since
597
618
# pyannotate turns it into Optional[Any]
0 commit comments