diff --git a/pytype/abstract/_function_base.py b/pytype/abstract/_function_base.py index 756c1ebb1..5494c3810 100644 --- a/pytype/abstract/_function_base.py +++ b/pytype/abstract/_function_base.py @@ -192,6 +192,7 @@ def call( self.func.__self__ # pytype: disable=attribute-error ) args = args.simplify(node, self.ctx, match_signature=sig) + del sig posargs = [u.AssignToNewVariable(node) for u in args.posargs] namedargs = { k: u.AssignToNewVariable(node) for k, u in args.namedargs.items() diff --git a/pytype/overlays/functools_overlay.py b/pytype/overlays/functools_overlay.py index 930836be1..bddb67709 100644 --- a/pytype/overlays/functools_overlay.py +++ b/pytype/overlays/functools_overlay.py @@ -115,6 +115,21 @@ def call( ) -> tuple[cfg.CFGNode, cfg.Variable]: # ``NativeFunction.call`` does not forward *args and **kwargs to the # underlying function, so we do it here to avoid changing core pytype APIs. + # + # The simplification below ensures that the *args/**kwargs cannot in fact + # be split into individual arguments. This logic follow the implementation + # in the base class. + sig = None + if isinstance( + self.func.__self__, # pytype: disable=attribute-error + abstract.CallableClass, + ): + sig = function.Signature.from_callable( + self.func.__self__ # pytype: disable=attribute-error + ) + args = args.simplify(node, self.ctx, match_signature=sig) + del sig + starargs = args.starargs starstarargs = args.starstarargs if starargs is not None: diff --git a/pytype/tests/test_functions1.py b/pytype/tests/test_functions1.py index e2fa18a75..3824e4d24 100644 --- a/pytype/tests/test_functions1.py +++ b/pytype/tests/test_functions1.py @@ -1079,6 +1079,17 @@ def f(a, b=None): partial_f(0) """) + def test_functools_partial_with_starstar(self): + self.Check(""" + import functools + def f(a: str, b: int, c: list): + pass + partial_f = functools.partial(f, "foo") + + def test(**kwargs): + partial_f(42, **kwargs) + """) + def test_functools_partial_overloaded(self): self.Check(""" import functools