def visit_Call(self, node): positional_final_wildcard = False for i, n in enumerate(node.args): if astcheck.is_ast_like(n, ast.Name(id=MULTIWILDCARD_NAME)): if i + 1 == len(node.args): # Last positional argument - wildcard may extend to kwargs positional_final_wildcard = True node.args = (self._visit_list(node.args[:i]) + astcheck.listmiddle() + self._visit_list(node.args[i + 1:])) # Don't try to handle multiple multiwildcards break kwargs_are_subset = False if positional_final_wildcard and node.starargs is None: del node.starargs # Accept any (or none) *args # f(a, ??) -> wildcarded kwargs as well kwargs_are_subset = True if kwargs_are_subset or any(k.arg == MULTIWILDCARD_NAME for k in node.keywords): template_keywords = [ self.visit(k) for k in node.keywords if k.arg != MULTIWILDCARD_NAME ] def kwargs_checker(sample_keywords, path): sample_kwargs = {k.arg: k.value for k in sample_keywords} for k in template_keywords: if k.arg == MULTIWILDCARD_NAME: continue if k.arg in sample_kwargs: astcheck.assert_ast_like(sample_kwargs[k.arg], k.value, path + [k.arg]) else: raise astcheck.ASTMismatch(path, "(missing)", "keyword arg %s" % k.arg) if template_keywords: node.keywords = kwargs_checker else: # Shortcut if there are no keywords to check del node.keywords # Accepting arbitrary keywords, so don't check absence of **kwargs if node.kwargs is None: del node.kwargs # In block contexts, we want to avoid checking empty lists (for optional # nodes), but here, an empty list should mean that there are no # arguments in that group. So we need to override the behaviour in # generic_visit if node.args == []: node.args = must_not_exist_checker if getattr(node, "keywords", None) == []: node.keywords = must_not_exist_checker return self.generic_visit(node)
def visit_arguments(self, node): positional_final_wildcard = False for i, a in enumerate(node.args): if a.arg == MULTIWILDCARD_NAME: from_end = len(node.args) - (i + 1) if from_end == 0: # Last positional argument - wildcard may extend to other groups positional_final_wildcard = True args = (self._visit_list(node.args[:i]) + astcheck.listmiddle() + self._visit_list(node.args[i + 1:])) break else: if node.args: args = self._visit_list(node.args) else: args = must_not_exist_checker defaults = [ (a.arg, self.visit(d)) for a, d in zip(node.args[-len(node.defaults):], node.defaults) if a.arg not in {WILDCARD_NAME, MULTIWILDCARD_NAME} ] if node.vararg is None: if positional_final_wildcard: vararg = None else: vararg = must_not_exist_checker else: vararg = self.visit(node.vararg) kwonly_args_dflts = [(self.visit(a), (d if d is None else self.visit(d))) for a, d in zip(node.kwonlyargs, node.kw_defaults) if a.arg != MULTIWILDCARD_NAME] koa_subset = (positional_final_wildcard and vararg is None and (not node.kwonlyargs)) or any(a.arg == MULTIWILDCARD_NAME for a in node.kwonlyargs) if node.kwarg is None: if koa_subset: kwarg = None else: kwarg = must_not_exist_checker else: kwarg = self.visit(node.kwarg) return ArgsDefChecker( args=args, defaults=defaults, vararg=vararg, kwonly_args_dflts=kwonly_args_dflts, koa_subset=koa_subset, kwarg=kwarg, )
def prune_wildcard_body(self, node, attrname, must_exist=False): """Prunes a code block (e.g. function body) if it is a wildcard""" body = getattr(node, attrname, []) def _is_multiwildcard(n): return astcheck.is_ast_like( n, ast.Expr(value=ast.Name(id=MULTIWILDCARD_NAME))) if len(body) == 1 and _is_multiwildcard(body[0]): setattr(node, attrname, must_exist_checker) return # Find a ?? node within the block, and replace it with listmiddle for i, n in enumerate(body): if _is_multiwildcard(n): newbody = body[:i] + astcheck.listmiddle() + body[i + 1:] setattr(node, attrname, newbody)