def instantiate_return(self, node, subst, sources): return_type = self.pytd_sig.return_type # Type parameter values, which are instantiated by the matcher, will end up # in the return value. Since the matcher does not call __init__, we need to # do that now. The one exception is that Type[X] does not instantiate X, so # we do not call X.__init__. if return_type.name != "builtins.type": for param in pytd_utils.GetTypeParameters(return_type): if param.full_name in subst: node = self.vm.call_init(node, subst[param.full_name]) try: ret = self.vm.convert.constant_to_var( abstract_utils.AsReturnValue(return_type), subst, node, source_sets=[sources]) except self.vm.convert.TypeParameterError: # The return type contains a type parameter without a substitution. subst = subst.copy() for t in pytd_utils.GetTypeParameters(return_type): if t.full_name not in subst: subst[t.full_name] = self.vm.convert.empty.to_variable(node) return node, self.vm.convert.constant_to_var( abstract_utils.AsReturnValue(return_type), subst, node, source_sets=[sources]) if not ret.bindings and isinstance(return_type, pytd.TypeParameter): ret.AddBinding(self.vm.convert.empty, [], node) return node, ret
def instantiate_return(self, node, subst, sources): return_type = self.pytd_sig.return_type for param in pytd_utils.GetTypeParameters(return_type): if param.full_name in subst: # This value, which was instantiated by the matcher, will end up in the # return value. Since the matcher does not call __init__, we need to do # that now. node = self.vm.call_init(node, subst[param.full_name]) try: ret = self.vm.convert.constant_to_var( abstract_utils.AsReturnValue(return_type), subst, node, source_sets=[sources]) except self.vm.convert.TypeParameterError: # The return type contains a type parameter without a substitution. subst = subst.copy() visitor = visitors.CollectTypeParameters() return_type.Visit(visitor) for t in visitor.params: if t.full_name not in subst: subst[t.full_name] = self.vm.convert.empty.to_variable( node) return node, self.vm.convert.constant_to_var( abstract_utils.AsReturnValue(return_type), subst, node, source_sets=[sources]) if not ret.bindings and isinstance(return_type, pytd.TypeParameter): ret.AddBinding(self.vm.convert.empty, [], node) return node, ret
def _function_to_return_types(self, node, fvar): """Convert a function variable to a list of PyTD return types.""" options = fvar.FilteredData(self.vm.exitpoint, strict=False) if not all(isinstance(o, abstract.Function) for o in options): return [pytd.AnythingType()] types = [] for val in options: if isinstance(val, abstract.InterpreterFunction): combinations = val.get_call_combinations(node) for node_after, _, return_value in combinations: types.append( self._function_call_to_return_type( node_after, val, return_value, len(combinations))) elif isinstance(val, abstract.PyTDFunction): types.extend(sig.pytd_sig.return_type for sig in val.signatures) else: types.append(pytd.AnythingType()) safe_types = [] # types without type parameters for t in types: params = pytd_utils.GetTypeParameters(t) t = t.Visit( visitors.ReplaceTypeParameters( {p: p.upper_value for p in params})) safe_types.append(t) return safe_types
def _combine_multiple_returns(self, signatures): """Combines multiple return types. Args: signatures: The candidate signatures. Returns: The combined return type. """ options = [] for sig, _, _ in signatures: t = sig.pytd_sig.return_type params = pytd_utils.GetTypeParameters(t) if params: replacement = {} for param_type in params: replacement[param_type] = pytd.AnythingType() replace_visitor = visitors.ReplaceTypeParameters(replacement) t = t.Visit(replace_visitor) options.append(t) if len(set(options)) == 1: return options[0] # Optimizing and then removing unions allows us to preserve as much # precision as possible while avoiding false positives. ret_type = optimize.Optimize(pytd_utils.JoinTypes(options)) return ret_type.Visit(visitors.ReplaceUnionsWithAny())
def _get_mutation(self, node, arg_dict, subst): """Mutation for changing the type parameters of mutable arguments. This will adjust the type parameters as needed for pytd functions like: def append_float(x: list[int]): x = list[int or float] This is called after all the signature matching has succeeded, and we know we're actually calling this function. Args: node: The current CFG node. arg_dict: A map of strings to pytd.Bindings instances. subst: Current type parameters. Returns: A list of Mutation instances. Raises: ValueError: If the pytd contains invalid information for mutated params. """ # Handle mutable parameters using the information type parameters mutations = [] # It's possible that the signature contains type parameters that are used # in mutations but are not filled in by the arguments, e.g. when starargs # and starstarargs have type parameters but are not in the args. Check that # subst has an entry for every type parameter, adding any that are missing. if any(f.mutated_type for f in self.pytd_sig.params): subst = subst.copy() for t in pytd_utils.GetTypeParameters(self.pytd_sig): if t.full_name not in subst: subst[t.full_name] = self.vm.convert.empty.to_variable( node) for formal in self.pytd_sig.params: actual = arg_dict[formal.name] arg = actual.data if (formal.mutated_type is not None and arg.isinstance_SimpleValue()): try: all_names_actuals = self._collect_mutated_parameters( formal.type, formal.mutated_type) except ValueError as e: log.error("Old: %s", pytd_utils.Print(formal.type)) log.error("New: %s", pytd_utils.Print(formal.mutated_type)) log.error("Actual: %r", actual) raise ValueError( "Mutable parameters setting a type to a " "different base type is not allowed.") from e for names_actuals in all_names_actuals: for tparam, type_actual in names_actuals: log.info("Mutating %s to %s", tparam.name, pytd_utils.Print(type_actual)) type_actual_val = self.vm.convert.constant_to_var( abstract_utils.AsInstance(type_actual), subst, node, discard_concrete_values=True) mutations.append( Mutation(arg, tparam.full_name, type_actual_val)) return mutations
def call_with_args(self, node, func, arg_dict, subst, ret_map, alias_map=None): """Call this signature. Used by PyTDFunction.""" return_type = self.pytd_sig.return_type t = (return_type, subst) sources = [func] + list(arg_dict.values()) if t not in ret_map: for param in pytd_utils.GetTypeParameters(return_type): if param.full_name in subst: # This value, which was instantiated by the matcher, will end up in # the return value. Since the matcher does not call __init__, we need # to do that now. node = self.vm.call_init(node, subst[param.full_name]) try: ret_map[t] = self.vm.convert.constant_to_var( abstract_utils.AsReturnValue(return_type), subst, node, source_sets=[sources]) except self.vm.convert.TypeParameterError: # The return type contains a type parameter without a substitution. subst = subst.copy() visitor = visitors.CollectTypeParameters() return_type.Visit(visitor) for t in visitor.params: if t.full_name not in subst: subst[t.full_name] = self.vm.convert.empty.to_variable( node) ret_map[t] = self.vm.convert.constant_to_var( abstract_utils.AsReturnValue(return_type), subst, node, source_sets=[sources]) else: if (not ret_map[t].bindings and isinstance(return_type, pytd.TypeParameter)): ret_map[t].AddBinding(self.vm.convert.empty, [], node) else: # add the new sources for data in ret_map[t].data: ret_map[t].AddBinding(data, sources, node) mutations = self._get_mutation(node, arg_dict, subst) self.vm.trace_call( node, func, (self, ), tuple(arg_dict[p.name] for p in self.pytd_sig.params), {}, ret_map[t]) return node, ret_map[t], mutations
def add_attributes_from(instance): for name, member in instance.members.items(): if name in CLASS_LEVEL_IGNORE or name in ignore: continue for value in member.FilteredData(self.vm.exitpoint, strict=False): typ = value.to_type(node) if pytd_utils.GetTypeParameters(typ): # This attribute's type comes from an annotation that contains a # type parameter; we do not want to merge in substituted values of # the type parameter. canonical_attributes.add(name) constants[name].add_type(typ)
def add_attributes_from(instance): for name, member in instance.members.items(): if name in abstract_utils.CLASS_LEVEL_IGNORE or name in ignore: continue for value in member.FilteredData(self.ctx.exitpoint, strict=False): typ = value.to_type(node) if pytd_utils.GetTypeParameters(typ): # This attribute's type comes from an annotation that contains a # type parameter; we do not want to merge in substituted values of # the type parameter. canonical_attributes.add(name) if v.is_enum: # If the containing class (v) is an enum, then output the instance # attributes as properties. # https://typing.readthedocs.io/en/latest/stubs.html#enums typ = pytd.Annotated(typ, ("'property'", )) constants[name].add_type(typ)
def _GetTypeParameters(self, node): params = pytd_utils.GetTypeParameters(node) return {x.name for x in params}