def _fill_in_default_arguments(func: Callable, call: ast.Call) -> Tuple[ast.Call, Type]: """Given a call and the function definition: * Defaults are filled in * A keyword argument that is positional is moved to the end * A follower is left to the original to help with recovering modifications to nested expressions. # TODO: Use python's signature bind to do this: # https://docs.python.org/3/library/inspect.html#inspect.Signature.bind) Args: func (Callable): The function definition call (ast.Call): The ast call site to be modified Raises: ValueError: Missing arguments, etc. Returns: Tuple[ast.Call, Type]: The modified call site and return type. """ sig = inspect.signature(func) i_arg = 0 arg_array = list(call.args) keywords = list(call.keywords) for param in sig.parameters.values(): if param.name != "self": if len(arg_array) <= i_arg: # See if they specified it as a keyword a, keywords = _find_keyword(keywords, param.name) if a is not None: arg_array.append(a) # type: ignore elif param.default is not param.empty: a = as_literal(param.default) arg_array.append(a) else: raise ValueError(f"Argument {param.name} is required") # If we are making a change to the call, put in a reference back to the # original call. if len(arg_array) != len(call.args): old_call_ast = call call = copy.copy(call) call.args = arg_array call.keywords = keywords call._old_ast = old_call_ast # type: ignore # Mark the return type - especially if it is missing t_info = get_type_hints(func) return_type = Any if "return" in t_info: return_type = t_info["return"] else: logging.getLogger(__name__).warning( f"Missing return annotation for {func.__name__}" " - assuming Any") return_type = Any return call, return_type
def visit_Call(self, node: ast.Call) -> ast.Call: self.found = False self.generic_visit(node) if self.found: node.args = [self.do_placeholder(x) for x in node.args] node.keywords = [(arg, self.do_placeholder(value)) for arg, value in node.keywords] return node
def visit_Call(self, node: ast.Call): orig_node_id = id(node) with self.attrsub_context(node): if isinstance(node.func, ast.Attribute): node.func = self.visit_Attribute(node.func, call_context=True) elif isinstance(node.func, ast.Subscript): node.func = self.visit_Subscript(node.func, call_context=True) else: node.func = self.visit(node.func) # TODO: need a way to rewrite ast of subscript args, # and to process these separately from outer rewrite node.args = self._get_replacement_args(node.args, False) node.keywords = self._get_replacement_args(node.keywords, True) # in order to ensure that the args are processed with appropriate active scope, # we need to make sure not to use the active namespace scope on args (in the case # of a function call on an ast.Attribute). # # We do so by emitting an "enter argument list", whose handler pushes the current active # scope while we process each argument. The "end argument list" event will then restore # the active scope. # # This effectively rewrites function calls as follows: # f(a, b, ..., c) -> trace(f, 'enter argument list')(a, b, ..., c) with fast.location_of(node.func): node.func = fast.Call( func=self._emitter_ast(), args=[TraceEvent.before_call.to_ast(), self._get_copy_id_ast(orig_node_id)], keywords=fast.kwargs( ret=node.func, call_node_id=self._get_copy_id_ast(orig_node_id), ), ) # f(a, b, ..., c) -> trace(f(a, b, ..., c), 'exit argument list') with fast.location_of(node): node = fast.Call( func=self._emitter_ast(), args=[TraceEvent.after_call.to_ast(), self._get_copy_id_ast(orig_node_id)], keywords=fast.kwargs( ret=node, call_node_id=self._get_copy_id_ast(orig_node_id), ), ) return self._maybe_wrap_symbol_in_before_after_tracing(node, call_context=True, orig_node_id=orig_node_id)
def visit_Call(self, node: ast.Call): is_attrsub = False if isinstance(node.func, (ast.Attribute, ast.Subscript)): is_attrsub = True with self.attrsub_load_context(): node.func = self.visit_Attribute_or_Subscript( node.func, call_context=True) # TODO: need a way to rewrite ast of attribute and subscript args, # and to process these separately from outer rewrite node.args = self._get_replacement_args(node.args, is_attrsub, False) node.keywords = self._get_replacement_args(node.keywords, is_attrsub, True) # in order to ensure that the args are processed with appropriate active scope, # we need to push current active scope before processing the args and pop after # (pop happens on function return as opposed to in tracer) node.func = ast.Call( func=ast.Name(self.scope_pusher, ast.Load()), args=[node.func], keywords=[], ) node = ast.Call(func=ast.Name(self.scope_popper, ast.Load()), args=[node, ast.NameConstant(is_attrsub)], keywords=[]) if self.inside_attrsub_load_chain or not is_attrsub: return node replacement_node = ast.Call(func=ast.Name(self.end_tracer, ast.Load()), args=[node, ast.NameConstant(True)], keywords=[]) ast.copy_location(replacement_node, node) return replacement_node
def visit_Call(self, node: ast.Call) -> None: self.generic_visit(node) if (getattr(node.func, "id", "") == "setup" or getattr(node.func, "attr", "") == "setup"): node.keywords = [n for n in node.keywords if not self.store(n)] self.setup_function = node