Skip to content

Commit e7355d7

Browse files
superbobrycopybara-github
authored andcommitted
Fixed the signature computation for a functools.partial-wrapped callable
The diff is hopefully self-explanatory, but the gist is that parameters can only be overwritten via a keyword argument. This means that * positional-only parameters cannot be overwritten; * "normal" parameters must become keyword-only in the resulting signature. PiperOrigin-RevId: 821616872
1 parent 268c04b commit e7355d7

File tree

2 files changed

+44
-3
lines changed

2 files changed

+44
-3
lines changed

pytype/overlays/functools_overlay.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,37 @@ def get_signatures(self) -> Sequence[function.Signature]:
147147
# Use the partial arguments as defaults in the signature, making them
148148
# optional but overwritable.
149149
defaults = sig.defaults.copy()
150+
kwonly_params = [*sig.kwonly_params]
151+
bound_param_names = set()
152+
pos_only_count = sig.posonly_count
150153
for name, value, _ in sig.iter_args(args):
151-
if value is not None:
152-
defaults[name] = value
153-
sigs.append(sig._replace(defaults=defaults))
154+
if value is None:
155+
continue
156+
if sig.param_names.index(name) < sig.posonly_count:
157+
# The parameter is positional-only, meaning that it cannot be
158+
# overwritten via a keyword argument. Remove it.
159+
bound_param_names.add(name)
160+
sig.posonly_count -= 1
161+
continue
162+
if name not in sig.kwonly_params:
163+
# The parameter can be overwritten via a keyword argument. Note
164+
# that we still have to remove it from ``param_names`` to make
165+
# sure it cannot be bound by position.
166+
bound_param_names.add(name)
167+
kwonly_params.append(name)
168+
169+
defaults[name] = value
170+
171+
sigs.append(
172+
sig._replace(
173+
param_names=tuple(
174+
n for n in sig.param_names if n not in bound_param_names
175+
),
176+
posonly_count=pos_only_count,
177+
kwonly_params=tuple(kwonly_params),
178+
defaults=defaults,
179+
)
180+
)
154181
return sigs
155182

156183
def call_slot(self, node: cfg.CFGNode, *args, **kwargs):

pytype/tests/test_attr2.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,20 @@ class Foo:
230230
assert_type(foo.x, str)
231231
""")
232232

233+
def test_partial_with_positional_args_as_converter(self):
234+
self.Check("""
235+
import attr
236+
import functools
237+
def f(x: str, y: int) -> int:
238+
del x
239+
return y
240+
@attr.s
241+
class Foo:
242+
x = attr.ib(converter=functools.partial(f, "foo"))
243+
foo = Foo(x=0)
244+
assert_type(foo.x, int)
245+
""")
246+
233247
def test_partial_overloaded_as_converter(self):
234248
self.Check("""
235249
import attr

0 commit comments

Comments
 (0)