def infer_annotation(type_comments): # type: (List[str]) -> Tuple[List[Argument], AbstractType] """Given some type comments, return a single inferred signature. Args: type_comments: Strings of form '(arg1, ... argN) -> ret' Returns: Tuple of (argument types and kinds, return type). """ assert type_comments args = {} # type: Dict[int, Set[Argument]] returns = set() for comment in type_comments: arg_types, return_type = parse_type_comment(comment) for i, arg_type in enumerate(arg_types): args.setdefault(i, set()).add(arg_type) returns.add(return_type) combined_args = [] for i in sorted(args): arg_infos = list(args[i]) kind = argument_kind(arg_infos) if kind is None: raise InferError('Ambiguous argument kinds:\n' + '\n'.join(type_comments)) types = [arg.type for arg in arg_infos] combined = combine_types(types) if str(combined) == 'None': # It's very rare for an argument to actually be typed `None`, more likely than # not we simply don't have any data points for this argument. combined = UnionType([ClassType('None'), AnyType()]) if kind != ARG_POS and (len(str(combined)) > 120 or isinstance(combined, UnionType)): # Avoid some noise. combined = AnyType() combined_args.append(Argument(combined, kind)) combined_return = combine_types(returns) return combined_args, combined_return
def test_union(self): # type: () -> None self.assert_type_comment('(Union[int, str]) -> Any', ([ Argument(UnionType([ClassType('int'), ClassType('str')]), ARG_POS) ], AnyType())) self.assert_type_comment('(Union[int]) -> Any', ([class_arg('int')], AnyType()))
def test_tuple(self): # type: () -> None self.assert_type_comment('(Tuple[]) -> Any', ([tuple_arg([])], AnyType())) self.assert_type_comment('(Tuple[int]) -> Any', ([tuple_arg([ClassType('int')])], AnyType())) self.assert_type_comment( '(Tuple[int, str]) -> Any', ([tuple_arg([ClassType('int'), ClassType('str')])], AnyType()))
def test_star_args(self): # type: () -> None self.assert_type_comment( '(*str) -> Any', ([Argument(ClassType('str'), ARG_STAR)], AnyType())) self.assert_type_comment( '(int, *str) -> Any', ([class_arg('int'), Argument(ClassType('str'), ARG_STAR)], AnyType()))
def parse_type(self): # type: () -> AbstractType t = self.next() if not isinstance(t, DottedName): self.fail() if t.text == 'Any': return AnyType() elif t.text == 'Tuple': self.expect('[') args = self.parse_type_list() self.expect(']') return TupleType(args) elif t.text == 'Union': self.expect('[') items = self.parse_type_list() self.expect(']') if len(items) == 1: return items[0] elif len(items) == 0: self.fail() else: return UnionType(items) else: if self.lookup() == '[': self.expect('[') args = self.parse_type_list() self.expect(']') if t.text == 'Optional' and len(args) == 1: return UnionType([args[0], ClassType('None')]) return ClassType(t.text, args) else: return ClassType(t.text)
def filter_ignored_items(items): # type: (List[AbstractType]) -> List[AbstractType] result = [ item for item in items if not isinstance(item, ClassType) or item.name not in IGNORED_ITEMS ] return result or [AnyType()]
def is_redundant_union_item(first, other): # type: (AbstractType, AbstractType) -> bool """If union has both items, is the first one redundant? For example, if first is 'str' and the other is 'Text', return True. If items are equal, return False. """ if isinstance(first, ClassType) and isinstance(other, ClassType): if first.name == 'str' and other.name == 'Text': return True elif first.name == 'bool' and other.name == 'int': return True elif first.name == 'int' and other.name == 'float': return True elif (first.name in ('List', 'Dict', 'Set') and other.name == first.name): if not first.args and other.args: return True elif len(first.args) == len(other.args) and first.args: result = all( first_arg == other_arg or other_arg == AnyType() for first_arg, other_arg in zip(first.args, other.args)) return result return False
def test_remove_redundant_dict_item_when_simplified(self): # type: () -> None self.assert_infer([ '(Dict[str, Any]) -> None', '(Dict[str, Union[str, List, Dict, int]]) -> None' ], ([(ClassType('Dict', [ClassType('str'), AnyType()]), ARG_POS) ], ClassType('None')))
def simplify_types(types): # type: (Iterable[AbstractType]) -> List[AbstractType] """Given some types, give simplified types representing the union of types.""" flattened = flatten_types(types) items = remove_redundant_items(flattened) items = [simplify_recursive(item) for item in items] items = merge_items(items) items = dedupe_types(items) if len(items) > 3: return [AnyType()] else: return items
def simplify_types(types): # type: (Iterable[AbstractType]) -> List[AbstractType] """Given some types, give simplified types representing the union of types.""" flattened = flatten_types(types) items = filter_ignored_items(flattened) items = [simplify_recursive(item) for item in items] items = merge_items(items) items = dedupe_types(items) # We have to remove reundant items after everything has been simplified and # merged as this simplification may be what makes items redundant. items = remove_redundant_items(items) if len(items) > 3: return [AnyType()] else: return items
def simplify_recursive(typ): # type: (AbstractType) -> AbstractType """Simplify all components of a type.""" if isinstance(typ, UnionType): return combine_types(typ.items) elif isinstance(typ, ClassType): simplified = ClassType(typ.name, [simplify_recursive(arg) for arg in typ.args]) args = simplified.args if (simplified.name == 'Dict' and len(args) == 2 and isinstance(args[0], ClassType) and args[0].name in ('str', 'Text') and isinstance(args[1], UnionType) and not is_optional(args[1])): # Looks like a potential case for TypedDict, which we don't properly support yet. return ClassType('Dict', [args[0], AnyType()]) return simplified elif isinstance(typ, TupleType): return TupleType([simplify_recursive(item) for item in typ.items]) return typ
def test_simplify_potential_typed_dict(self): # type: () -> None # Fall back to Dict[x, Any] in case of a complex Dict type. self.assert_infer(['(Dict[str, Union[int, str]]) -> Any'], ([(ClassType('Dict', [ClassType('str'), AnyType()]), ARG_POS)], AnyType())) self.assert_infer(['(Dict[Text, Union[int, str]]) -> Any'], ([(ClassType('Dict', [ClassType('Text'), AnyType()]), ARG_POS)], AnyType())) # Not a potential TypedDict so ordinary simplification applies. self.assert_infer(['(Dict[str, Union[str, Text]]) -> Any'], ([(ClassType('Dict', [ClassType('str'), ClassType('Text')]), ARG_POS)], AnyType())) self.assert_infer(['(Dict[str, Union[int, None]]) -> Any'], ([(ClassType('Dict', [ClassType('str'), UnionType([ClassType('int'), ClassType('None')])]), ARG_POS)], AnyType()))
def test_unicode(self): # type: () -> None self.assert_type_comment('(unicode) -> Any', ([class_arg('Text')], AnyType()))
def test_any_type_str(self): # type: () -> None assert str(AnyType()) == 'Any'
def test_remove_redundant_dict_item(self): # type: () -> None self.assert_infer(['(Dict[str, Any]) -> None', '(Dict[str, str]) -> None'], ([(ClassType('Dict', [ClassType('str'), AnyType()]), ARG_POS)], ClassType('None')))
def test_infer_ignore_mock_fallback_to_any(self): # type: () -> None self.assert_infer(['(mock.mock.Mock) -> str', '(mock.mock.Mock) -> int'], ([(AnyType(), ARG_POS)], UnionType([ClassType('str'), ClassType('int')])))
def test_any_and_unknown(self): # type: () -> None self.assert_type_comment( '(Any) -> pyannotate_runtime.collect_types.UnknownType', ([any_arg()], AnyType()))
def test_optional(self): # type: () -> None self.assert_type_comment('(Optional[int]) -> Any', ([ Argument(UnionType([ClassType('int'), ClassType('None')]), ARG_POS) ], AnyType()))
def any_arg(): # type: () -> Argument return Argument(AnyType(), ARG_POS)
def test_infer_none_argument(self): # type: () -> None self.assert_infer(['(None) -> None'], ([(UnionType([ClassType('None'), AnyType()]), ARG_POS)], ClassType('None')))
def test_function(self): # type: () -> None self.assert_type_comment('(function) -> Any', ([class_arg('Callable')], AnyType()))