def _match_type_against_callback_protocol( self, left, other_type, subst, node, view): """See https://www.python.org/dev/peps/pep-0544/#callback-protocols.""" _, method_var = self.vm.attribute_handler.get_attribute( node, other_type, "__call__") if not method_var or not method_var.data or any( not isinstance(v, abstract.Function) for v in method_var.data): return None new_substs = [] for expected_method in method_var.data: signatures = abstract_utils.get_signatures(expected_method) for sig in signatures: sig = sig.drop_first_parameter() # drop `self` expected_callable = ( self.vm.convert.pytd_convert.signature_to_callable(sig)) new_subst = self._match_type_against_type( left, expected_callable, subst, node, view) if new_subst is not None: # For a set of overloaded signatures, only one needs to match. new_substs.append(new_subst) break else: # Every method_var binding must have a matching signature. return None return self._merge_substs(subst, new_substs)
def value_to_pytd_type(self, node, v, seen, view): """Get a PyTD type representing this object, as seen at a node. Args: node: The node from which we want to observe this object. v: The object. seen: The set of values seen before while computing the type. view: A Variable -> binding map. Returns: A PyTD type. """ if isinstance(v, (abstract.Empty, typing_overlay.NoReturn)): return pytd.NothingType() elif isinstance(v, abstract.TypeParameterInstance): if v.instance.get_instance_type_parameter(v.full_name).bindings: # The type parameter was initialized. Set the view to None, since we # don't include v.instance in the view. return pytd_utils.JoinTypes( self.value_to_pytd_type(node, p, seen, None) for p in v.instance.get_instance_type_parameter(v.full_name).data) elif v.param.constraints: return pytd_utils.JoinTypes( self.value_instance_to_pytd_type(node, p, None, seen, view) for p in v.param.constraints) elif v.param.bound: return self.value_instance_to_pytd_type( node, v.param.bound, None, seen, view) else: return pytd.AnythingType() elif isinstance(v, typing_overlay.TypeVar): return pytd.NamedType("__builtin__.type") elif isinstance(v, abstract.FUNCTION_TYPES): try: signatures = abstract_utils.get_signatures(v) except NotImplementedError: return pytd.NamedType("typing.Callable") if len(signatures) == 1: val = self.signature_to_callable(signatures[0], self.vm) if (not isinstance(v, abstract.PYTD_FUNCTION_TYPES) or not self.vm.annotations_util.get_type_parameters(val)): # This is a workaround to make sure we don't put unexpected type # parameters in call traces. return self.value_instance_to_pytd_type(node, val, None, seen, view) return pytd.NamedType("typing.Callable") elif isinstance(v, (abstract.ClassMethod, abstract.StaticMethod)): return self.value_to_pytd_type(node, v.method, seen, view) elif isinstance(v, (special_builtins.IsInstance, special_builtins.ClassMethodCallable)): return pytd.NamedType("typing.Callable") elif isinstance(v, mixin.Class): param = self.value_instance_to_pytd_type(node, v, None, seen, view) return pytd.GenericType(base_type=pytd.NamedType("__builtin__.type"), parameters=(param,)) elif isinstance(v, abstract.Module): return pytd.NamedType("__builtin__.module") elif isinstance(v, abstract.SimpleAbstractValue): if v.cls: ret = self.value_instance_to_pytd_type( node, v.cls, v, seen=seen, view=view) ret.Visit(visitors.FillInLocalPointers( {"__builtin__": self.vm.loader.builtins})) return ret else: # We don't know this type's __class__, so return AnythingType to # indicate that we don't know anything about what this is. # This happens e.g. for locals / globals, which are returned from the # code in class declarations. log.info("Using ? for %s", v.name) return pytd.AnythingType() elif isinstance(v, abstract.Union): return pytd.UnionType(tuple(self.value_to_pytd_type(node, o, seen, view) for o in v.options)) elif isinstance(v, special_builtins.SuperInstance): return pytd.NamedType("__builtin__.super") elif isinstance(v, (abstract.Unsolvable, abstract.TypeParameter)): # Arguably, the type of a type parameter is NamedType("typing.TypeVar"), # but pytype doesn't know how to handle that, so let's just go with Any. return pytd.AnythingType() elif isinstance(v, abstract.Unknown): return pytd.NamedType(v.class_name) elif isinstance(v, abstract.BuildClass): return pytd.NamedType("typing.Callable") else: raise NotImplementedError(v.__class__.__name__)
def _match_type_against_type(self, left, other_type, subst, node, view): """Checks whether a type is compatible with a (formal) type. Args: left: A type. other_type: A formal type. E.g. mixin.Class or abstract.Union. subst: The current type parameter assignment. node: The current CFG node. view: The current mapping of Variable to Value. Returns: A new type parameter assignment if the matching succeeded, None otherwise. """ if (isinstance(left, abstract.Empty) and isinstance(other_type, abstract.Empty)): return subst elif isinstance(left, abstract.AMBIGUOUS_OR_EMPTY): params = self.vm.annotations_util.get_type_parameters(other_type) if isinstance(left, abstract.Empty): value = self.vm.convert.empty else: value = self.vm.convert.unsolvable return self._mutate_type_parameters(params, value, subst, node) elif isinstance(left, mixin.Class): if (other_type.full_name == "builtins.type" and isinstance(other_type, abstract.ParameterizedClass)): other_type = other_type.get_formal_type_parameter(abstract_utils.T) return self._instantiate_and_match(left, other_type, subst, node, view) elif (other_type.full_name == "typing.Callable" and isinstance(other_type, abstract.ParameterizedClass)): # TODO(rechen): Check left's constructor against the callable's params. other_type = other_type.get_formal_type_parameter(abstract_utils.RET) return self._instantiate_and_match(left, other_type, subst, node, view) elif other_type.full_name in [ "builtins.type", "builtins.object", "typing.Callable", "typing.Hashable"]: return subst elif _is_callback_protocol(other_type): return self._match_type_against_callback_protocol( left, other_type, subst, node, view) elif left.cls: return self._match_instance_against_type( left, other_type, subst, node, view) elif isinstance(left, abstract.Module): if other_type.full_name in [ "builtins.module", "builtins.object", "types.ModuleType", "typing.Hashable"]: return subst elif isinstance(left, abstract.FUNCTION_TYPES): if other_type.full_name == "typing.Callable": if not isinstance(other_type, abstract.ParameterizedClass): # The callable has no parameters, so any function matches it. return subst if isinstance(left, abstract.NativeFunction): # If we could get the class on which 'left' is defined (perhaps by # using bound_class?), we could get the argument and return types # from the underlying PyTDFunction, but we wouldn't get much value # out of that additional matching, since most NativeFunction objects # are magic methods like __getitem__ which aren't likely to be passed # as function arguments. return subst signatures = abstract_utils.get_signatures(left) for sig in signatures: new_subst = self._match_signature_against_callable( sig, other_type, subst, node, view) if new_subst is not None: return new_subst return None elif _is_callback_protocol(other_type): return self._match_type_against_callback_protocol( left, other_type, subst, node, view) elif left.cls: return self._match_type_against_type( abstract.Instance(left.cls, self.vm), other_type, subst, node, view) else: return None elif isinstance(left, dataclass_overlay.FieldInstance) and left.default: return self._match_all_bindings( left.default, other_type, subst, node, view) elif isinstance(left, abstract.SimpleValue): return self._match_instance_against_type( left, other_type, subst, node, view) elif isinstance(left, special_builtins.SuperInstance): return self._match_class_and_instance_against_type( left.super_cls, left.super_obj, other_type, subst, node, view) elif isinstance(left, abstract.ClassMethod): if other_type.full_name in [ "builtins.classmethod", "builtins.object"]: return subst return self._match_type_against_type( left.to_bound_function(), other_type, subst, node, view) elif isinstance(left, abstract.StaticMethod): if other_type.full_name in [ "builtins.staticmethod", "builtins.object"]: return subst return self._match_type_against_type( left.method, other_type, subst, node, view) elif isinstance(left, abstract.Union): for o in left.options: new_subst = self._match_type_against_type( o, other_type, subst, node, view) if new_subst is not None: return new_subst elif isinstance(left, abstract.TypeParameterInstance): return self._instantiate_and_match( left.param, other_type, subst, node, view) else: raise NotImplementedError("Matching not implemented for %s against %s" % (type(left), type(other_type)))
def value_to_pytd_type(self, node, v, seen, view): """Get a PyTD type representing this object, as seen at a node. Args: node: The node from which we want to observe this object. v: The object. seen: The set of values seen before while computing the type. view: A Variable -> binding map. Returns: A PyTD type. """ if isinstance(v, (abstract.Empty, typing_overlay.NoReturn)): return pytd.NothingType() elif isinstance(v, abstract.TypeParameterInstance): if v.module in self._scopes: return self._typeparam_to_def(node, v.param, v.param.name) elif v.instance.get_instance_type_parameter(v.full_name).bindings: # The type parameter was initialized. Set the view to None, since we # don't include v.instance in the view. return pytd_utils.JoinTypes( self.value_to_pytd_type(node, p, seen, None) for p in v.instance.get_instance_type_parameter(v.full_name).data) elif v.param.constraints: return pytd_utils.JoinTypes( self.value_instance_to_pytd_type(node, p, None, seen, view) for p in v.param.constraints) elif v.param.bound: return self.value_instance_to_pytd_type( node, v.param.bound, None, seen, view) else: return pytd.AnythingType() elif isinstance(v, typing_overlay.TypeVar): return pytd.NamedType("builtins.type") elif isinstance(v, dataclass_overlay.FieldInstance): if not v.default: return pytd.AnythingType() return pytd_utils.JoinTypes( self.value_to_pytd_type(node, d, seen, view) for d in v.default.data) elif isinstance(v, abstract.FUNCTION_TYPES): try: signatures = abstract_utils.get_signatures(v) except NotImplementedError: return pytd.NamedType("typing.Callable") if len(signatures) == 1: val = self.signature_to_callable(signatures[0]) if not isinstance( v, abstract.PYTD_FUNCTION_TYPES) or not val.formal: # This is a workaround to make sure we don't put unexpected type # parameters in call traces. return self.value_instance_to_pytd_type( node, val, None, seen, view) return pytd.NamedType("typing.Callable") elif isinstance(v, (abstract.ClassMethod, abstract.StaticMethod)): return self.value_to_pytd_type(node, v.method, seen, view) elif isinstance(v, (special_builtins.IsInstance, special_builtins.ClassMethodCallable)): return pytd.NamedType("typing.Callable") elif isinstance(v, class_mixin.Class): param = self.value_instance_to_pytd_type(node, v, None, seen, view) return pytd.GenericType(base_type=pytd.NamedType("builtins.type"), parameters=(param, )) elif isinstance(v, abstract.Module): return pytd.NamedType("builtins.module") elif (self._output_mode >= Converter.OutputMode.LITERAL and isinstance(v, abstract.ConcreteValue) and isinstance(v.pyval, (int, str, bytes))): # LITERAL mode is used only for pretty-printing, so we just stringify the # inner value rather than properly converting it. return pytd.Literal(repr(v.pyval)) elif isinstance(v, abstract.SimpleValue): if v.cls: ret = self.value_instance_to_pytd_type(node, v.cls, v, seen=seen, view=view) ret.Visit( visitors.FillInLocalPointers( {"builtins": self.vm.loader.builtins})) return ret else: # We don't know this type's __class__, so return AnythingType to # indicate that we don't know anything about what this is. # This happens e.g. for locals / globals, which are returned from the # code in class declarations. log.info("Using Any for %s", v.name) return pytd.AnythingType() elif isinstance(v, abstract.Union): opts = [] for o in v.options: # NOTE: Guarding printing of type parameters behind _detailed until # round-tripping is working properly. if self._detailed and isinstance(o, abstract.TypeParameter): opt = self._typeparam_to_def(node, o, o.name) else: opt = self.value_to_pytd_type(node, o, seen, view) opts.append(opt) return pytd.UnionType(tuple(opts)) elif isinstance(v, special_builtins.SuperInstance): return pytd.NamedType("builtins.super") elif isinstance(v, abstract.TypeParameter): # Arguably, the type of a type parameter is NamedType("typing.TypeVar"), # but pytype doesn't know how to handle that, so let's just go with Any # unless self._detailed is set. if self._detailed: return pytd.NamedType("typing.TypeVar") else: return pytd.AnythingType() elif isinstance(v, abstract.Unsolvable): return pytd.AnythingType() elif isinstance(v, abstract.Unknown): return pytd.NamedType(v.class_name) elif isinstance(v, abstract.BuildClass): return pytd.NamedType("typing.Callable") else: raise NotImplementedError(v.__class__.__name__)