def simplify(self, node, vm, match_signature=None): """Try to insert part of *args, **kwargs into posargs / namedargs.""" # TODO(rechen): When we have type information about *args/**kwargs, # we need to check it before doing this simplification. posargs = self.posargs namedargs = self.namedargs starargs = self.starargs starstarargs = self.starstarargs # Unpack starstarargs into namedargs. We need to do this first so we can see # what posargs are still required. starstarargs_as_dict = self.starstarargs_as_dict() if starstarargs_as_dict is not None: # Unlike varargs below, we do not adjust starstarargs into namedargs when # the function signature has matching param_names because we have not # found a benefit in doing so. if self.namedargs is None: namedargs = starstarargs_as_dict else: namedargs.update(node, starstarargs_as_dict) starstarargs = None starargs_as_tuple = self.starargs_as_tuple(node, vm) if starargs_as_tuple is not None: if match_signature: posargs, starargs = self._unpack_and_match_args( node, vm, match_signature, starargs_as_tuple) elif (starargs_as_tuple and abstract_utils.is_var_splat(starargs_as_tuple[-1])): # If the last arg is an indefinite iterable keep it in starargs. Convert # any other splats to Any. # TODO(mdemello): If there are multiple splats should we just fall # through to the next case (setting them all to Any), and only hit this # case for a *single* splat in terminal position? posargs = self.posargs + _splats_to_any( starargs_as_tuple[:-1], vm) starargs = abstract_utils.unwrap_splat(starargs_as_tuple[-1]) else: # Don't try to unpack iterables in any other position since we don't # have a signature to match. Just set all splats to Any. posargs = self.posargs + _splats_to_any(starargs_as_tuple, vm) starargs = None return Args(posargs, namedargs, starargs, starstarargs)
def _splats_to_any(seq, vm): return tuple( vm.new_unsolvable(vm.root_node) if abstract_utils.is_var_splat(v) else v for v in seq)
def _unpack_and_match_args(self, node, vm, match_signature, starargs_tuple): """Match args against a signature with unpacking.""" posargs = self.posargs namedargs = self.namedargs # As we have the function signature we will attempt to adjust the # starargs into the missing posargs. pre = [] post = [] stars = collections.deque(starargs_tuple) while stars and not abstract_utils.is_var_splat(stars[0]): pre.append(stars.popleft()) while stars and not abstract_utils.is_var_splat(stars[-1]): post.append(stars.pop()) post.reverse() n_matched = len(posargs) + len(pre) + len(post) required_posargs = 0 for p in match_signature.param_names: if p in namedargs or p in match_signature.defaults: break required_posargs += 1 posarg_delta = required_posargs - n_matched if stars and not post: star = stars[-1] if match_signature.varargs_name: # If the invocation ends with `*args`, return it to match against *args # in the function signature. For f(<k args>, *xs, ..., *ys), transform # to f(<k args>, *ys) since ys is an indefinite tuple anyway and will # match against all remaining posargs. return posargs + tuple(pre), abstract_utils.unwrap_splat(star) else: # If we do not have a `*args` in match_signature, just expand the # terminal splat to as many args as needed and then drop it. mid = self._expand_typed_star(vm, node, star, posarg_delta) return posargs + tuple(pre + mid), None elif posarg_delta <= len(stars): # We have too many args; don't do *xs expansion. Go back to matching from # the start and treat every entry in starargs_tuple as length 1. n_params = len(match_signature.param_names) all_args = posargs + starargs_tuple if not match_signature.varargs_name: # If the function sig has no *args, return everything in posargs pos = _splats_to_any(all_args, vm) return pos, None # Don't unwrap splats here because f(*xs, y) is not the same as f(xs, y). # TODO(mdemello): Ideally, since we are matching call f(*xs, y) against # sig f(x, y) we should raise an error here. pos = _splats_to_any(all_args[:n_params], vm) star = [] for var in all_args[n_params:]: if abstract_utils.is_var_splat(var): star.append( abstract_utils.merged_type_parameter(node, var, abstract_utils.T)) else: star.append(var) if star: return pos, vm.convert.tuple_to_value(star).to_variable(node) else: return pos, None elif stars: if len(stars) == 1: # Special case (<pre>, *xs) and (*xs, <post>) to fill in the type of xs # in every remaining arg. mid = self._expand_typed_star(vm, node, stars[0], posarg_delta) else: # If we have (*xs, <k args>, *ys) remaining, and more than k+2 params to # match, don't try to match the intermediate params to any range, just # match all k+2 to Any mid = [vm.new_unsolvable(node) for _ in range(posarg_delta)] return posargs + tuple(pre + mid + post), None else: # We have **kwargs but no *args in the invocation return posargs + tuple(pre), None
def _unpack_and_match_args(self, node, vm, match_signature, starargs_tuple): """Match args against a signature with unpacking.""" posargs = self.posargs # As we have the function signature we will attempt to adjust the # starargs into the missing posargs. pre = [] post = [] stars = collections.deque(starargs_tuple) while stars and not abstract_utils.is_var_splat(stars[0]): pre.append(stars.popleft()) while stars and not abstract_utils.is_var_splat(stars[-1]): post.append(stars.pop()) post.reverse() n_matched = len(posargs) + len(pre) + len(post) posarg_delta = len(match_signature.param_names) - n_matched if stars and not post: # If the invocation ends with `*args`, return it to match against *args in # the function signature. For f(<k args>, *xs, ..., *ys), transform to # f(<k args>, *ys) since ys is an indefinite tuple anyway and will match # against all remaining posargs. return posargs + tuple(pre), abstract_utils.unwrap_splat(stars[-1]) elif posarg_delta <= len(stars): # We have too many args; don't do *xs expansion. Go back to matching from # the start and treat every entry in starargs_tuple as length 1. n_params = len(match_signature.param_names) all_args = posargs + starargs_tuple # Don't unwrap splats here because f(*xs, y) is not the same as f(xs, y). # TODO(mdemello): Ideally, since we are matching call f(*xs, y) against # sig f(x, y) we should raise an error here. pos = _splats_to_any(all_args[:n_params], vm) star = [] for var in all_args[n_params:]: if abstract_utils.is_var_splat(var): star.append( abstract_utils.merged_type_parameter(node, var, abstract_utils.T)) else: star.append(var) if star: return pos, vm.convert.tuple_to_value(star).to_variable(node) else: return pos, None elif stars: if len(stars) == 1: # Special case (*xs, <post>) to fill in the type of xs in every arg p = abstract_utils.merged_type_parameter( node, stars[0], abstract_utils.T) if not p.bindings: # TODO(b/159052609): This shouldn't happen. For some reason, # namedtuple instances don't have any bindings in T; see # tests/py3/test_unpack:TestUnpack.test_unpack_namedtuple. p.AddBinding(vm.convert.unsolvable) mid = [p.AssignToNewVariable(node) for _ in range(posarg_delta)] else: # If we have (*xs, <k args>, *ys) remaining, and more than k+2 params to # match, don't try to match the intermediate params to any range, just # match all k+2 to Any mid = [vm.new_unsolvable(node) for _ in range(posarg_delta)] return posargs + tuple(pre + mid + post), None else: # We have **kwargs but no *args in the invocation return posargs + tuple(pre), None