def __init__(self, name, param_names, posonly_count, varargs_name, kwonly_params, kwargs_name, defaults, annotations, ctx): """Create a SimpleFunction. Args: name: Name of the function as a string param_names: Tuple of parameter names as strings. This DOES include positional-only parameters and does NOT include keyword-only parameters. posonly_count: Number of positional-only parameters. varargs_name: The "args" in "*args". String or None. kwonly_params: Tuple of keyword-only parameters as strings. kwargs_name: The "kwargs" in "**kwargs". String or None. defaults: Dictionary of string names to values of default arguments. annotations: Dictionary of string names to annotations (strings or types). ctx: The abstract context for this function. """ annotations = dict(annotations) # Every parameter must have an annotation. Defaults to unsolvable. for n in itertools.chain(param_names, [varargs_name, kwargs_name], kwonly_params): if n and n not in annotations: annotations[n] = ctx.convert.unsolvable if not isinstance(defaults, dict): defaults = dict(zip(param_names[-len(defaults):], defaults)) signature = function.Signature(name, param_names, posonly_count, varargs_name, kwonly_params, kwargs_name, defaults, annotations) super().__init__(signature, ctx) self.bound_class = _function_base.BoundFunction
def call(self, node, _, args, alias_map=None): sig = None if isinstance(self.func.__self__, _classes.CallableClass): sig = function.Signature.from_callable(self.func.__self__) args = args.simplify(node, self.ctx, match_signature=sig) posargs = [u.AssignToNewVariable(node) for u in args.posargs] namedargs = { k: u.AssignToNewVariable(node) for k, u in args.namedargs.items() } try: inspect.signature(self.func).bind(node, *posargs, **namedargs) except ValueError as e: # Happens for, e.g., # def f((x, y)): pass # f((42,)) raise NotImplementedError( "Wrong number of values to unpack") from e except TypeError as e: # The possible errors here are: # (1) wrong arg count # (2) duplicate keyword # (3) unexpected keyword # The way we constructed namedargs rules out (2). if "keyword" in utils.message(e): # Happens for, e.g., # def f(*args): pass # f(x=42) raise NotImplementedError("Unexpected keyword") from e # The function was passed the wrong number of arguments. The signature is # ([self, ]node, ...). The length of "..." tells us how many variables # are expected. expected_argcount = len(inspect.getfullargspec(self.func).args) - 1 if inspect.ismethod(self.func) and self.func.__self__ is not None: expected_argcount -= 1 actual_argcount = len(posargs) + len(namedargs) if (actual_argcount > expected_argcount or (not args.starargs and not args.starstarargs)): # If we have too many arguments, or starargs and starstarargs are both # empty, then we can be certain of a WrongArgCount error. argnames = tuple("_" + str(i) for i in range(expected_argcount)) sig = function.Signature(self.name, argnames, 0, None, set(), None, {}, {}, {}) raise function.WrongArgCount(sig, args, self.ctx) assert actual_argcount < expected_argcount # Assume that starargs or starstarargs fills in the missing arguments. # Instead of guessing where these arguments should go, overwrite all of # the arguments with a list of unsolvables of the correct length, which # is guaranteed to give us a correct (but imprecise) analysis. posargs = [ self.ctx.new_unsolvable(node) for _ in range(expected_argcount) ] namedargs = {} return self.func(node, *posargs, **namedargs)
def test_signature_del_nonexistent_annotation(self): # def f(): ... sig = function.Signature( name="f", param_names=(), posonly_count=0, varargs_name=None, kwonly_params=(), kwargs_name=None, defaults={}, annotations={}, ) self.assertRaises(KeyError, sig.del_annotation, "rumpelstiltskin")
def test_signature_kwonly_param_count(self): # def f(*, y=None): ... sig = function.Signature( name="f", param_names=(), posonly_count=0, varargs_name=None, kwonly_params=("y",), kwargs_name=None, defaults={"y": self._ctx.convert.none_type.to_variable(self._node)}, annotations={}, ) self.assertEqual(repr(sig), "def f(*, y = None) -> Any") self.assertEqual(sig.mandatory_param_count(), 0) self.assertEqual(sig.maximum_param_count(), 1)
def test_signature_kwargs_param_count(self): # def f(**kwargs): ... sig = function.Signature( name="f", param_names=(), posonly_count=0, varargs_name=None, kwonly_params=(), kwargs_name="kwargs", defaults={}, annotations={}, ) self.assertEqual(repr(sig), "def f(**kwargs) -> Any") self.assertEqual(sig.mandatory_param_count(), 0) self.assertIsNone(sig.maximum_param_count())
def test_signature_has_param(self): # def f(x, *args, y, **kwargs): ... sig = function.Signature( name="f", param_names=("x",), posonly_count=0, varargs_name="args", kwonly_params={"y"}, kwargs_name="kwargs", defaults={}, annotations={}, ) self.assertEqual(repr(sig), "def f(x, *args, y, **kwargs) -> Any") for param in ("x", "args", "y", "kwargs"): self.assertTrue(sig.has_param(param)) self.assertFalse(sig.has_param("rumpelstiltskin"))
def test_signature_del_return_annotation(self): # def f(x) -> int: ... sig = function.Signature( name="f", param_names=("x",), posonly_count=0, varargs_name=None, kwonly_params=(), kwargs_name=None, defaults={}, annotations={ "x": self._ctx.convert.unsolvable, "return": self._ctx.convert.unsolvable }, ) sig.del_annotation("return") self.assertCountEqual(sig.annotations.keys(), {"x"}) self.assertTrue(sig.has_param_annotations) self.assertFalse(sig.has_return_annotation)
def test_signature_annotations_existence(self): # def f(v: "X") -> "Y" sig = function.Signature( name="f", param_names=("v",), posonly_count=0, varargs_name=None, kwonly_params=(), kwargs_name=None, defaults={}, annotations={}, ) self.assertFalse(sig.has_param_annotations) self.assertFalse(sig.has_return_annotation) sig.set_annotation("v", self._ctx.convert.unsolvable) self.assertTrue(sig.has_param_annotations) self.assertFalse(sig.has_return_annotation) sig.set_annotation("return", self._ctx.convert.unsolvable) self.assertTrue(sig.has_param_annotations) self.assertTrue(sig.has_return_annotation)
def _build_signature(self, name, annotations): """Build a function.Signature object representing this function.""" vararg_name = None kwarg_name = None kwonly = set( self.code.co_varnames[self.code.co_argcount:self.nonstararg_count]) arg_pos = self.nonstararg_count if self.has_varargs(): vararg_name = self.code.co_varnames[arg_pos] arg_pos += 1 if self.has_kwargs(): kwarg_name = self.code.co_varnames[arg_pos] arg_pos += 1 defaults = dict( zip(self.get_positional_names()[-len(self.defaults):], self.defaults)) defaults.update(self.kw_defaults) return function.Signature( name, tuple(self.code.co_varnames[:self.code.co_argcount]), self.posonlyarg_count, vararg_name, tuple(kwonly), kwarg_name, defaults, annotations)
def test_signature_insert_varargs_and_kwargs(self): # def f(x, *args, y, **kwargs): ... sig = function.Signature( name="f", param_names=("x",), posonly_count=0, varargs_name="args", kwonly_params={"y"}, kwargs_name="kwargs", defaults={}, annotations={}, ) # f(1, 2, y=3, z=4) int_inst = self._ctx.convert.primitive_class_instances[int] int_binding = int_inst.to_binding(self._node) arg_dict = { "x": int_binding, "_1": int_binding, "y": int_binding, "z": int_binding} sig = sig.insert_varargs_and_kwargs(arg_dict) self.assertEqual(sig.name, "f") self.assertSequenceEqual(sig.param_names, ("x", "_1", "z")) self.assertEqual(sig.varargs_name, "args") self.assertSetEqual(sig.kwonly_params, {"y"}) self.assertEqual(sig.kwargs_name, "kwargs") self.assertFalse(sig.annotations)