Skip to content

Commit 5d065d6

Browse files
superbobrycopybara-github
authored andcommitted
Fixed a spurious type error when binding a param spec to Any/Callable[..., Any]
PiperOrigin-RevId: 831814821
1 parent 9df221e commit 5d065d6

File tree

2 files changed

+61
-3
lines changed

2 files changed

+61
-3
lines changed

pytype/output.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,17 @@ def _value_to_parameter_types(self, node, v, instance, template, seen, view):
123123
"""Get PyTD types for the parameters of an instance of an abstract value."""
124124
if isinstance(v, abstract.CallableClass):
125125
assert template == (abstract_utils.ARGS, abstract_utils.RET), template
126-
template = list(range(v.num_args)) + [template[1]]
126+
should_preserve_paramspec = (
127+
v.has_paramspec()
128+
and v.num_args == 1
129+
and instance
130+
and isinstance(
131+
instance.get_formal_type_parameter(abstract_utils.ARGS),
132+
abstract.Unsolvable,
133+
)
134+
)
135+
if not should_preserve_paramspec:
136+
template = list(range(v.num_args)) + [template[1]]
127137
if self._is_tuple(v, instance):
128138
if isinstance(v, abstract.TupleClass):
129139
new_template = range(v.tuple_length)
@@ -176,10 +186,18 @@ def _value_to_parameter_types(self, node, v, instance, template, seen, view):
176186
else:
177187
param_values = {self.ctx.convert.unsolvable: view}
178188
formal_param = v.get_formal_type_parameter(t)
189+
if (
190+
t == abstract_utils.ARGS
191+
and isinstance(v, abstract.CallableClass)
192+
and v.has_paramspec()
193+
and [*param_values] == [self.ctx.convert.unsolvable]
194+
):
195+
# This is a Callable[P, T] where P is unsolvable.
196+
arg = pytd.AnythingType()
179197
# If the instance's parameter value is unsolvable or the parameter type
180198
# is recursive, we can get a more precise type from the class. Note that
181199
# we need to be careful not to introduce unbound type parameters.
182-
if (
200+
elif (
183201
isinstance(v, abstract.ParameterizedClass)
184202
and not formal_param.formal
185203
and (
@@ -281,7 +299,16 @@ def value_instance_to_pytd_type(self, node, v, instance, seen, view):
281299
if self._is_tuple(v, instance):
282300
homogeneous = False
283301
elif v.full_name == "typing.Callable":
284-
homogeneous = not isinstance(v, abstract.CallableClass)
302+
is_callable_with_unsolvable_paramspec = (
303+
isinstance(v, abstract.CallableClass)
304+
and v.has_paramspec()
305+
and len(type_arguments) == 2
306+
and isinstance(type_arguments[0], pytd.AnythingType)
307+
)
308+
homogeneous = (
309+
not isinstance(v, abstract.CallableClass)
310+
or is_callable_with_unsolvable_paramspec
311+
)
285312
else:
286313
homogeneous = len(type_arguments) == 1
287314
return pytd_utils.MakeClassOrContainerType(

pytype/tests/test_paramspec.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""PEP 612 tests."""
22

3+
import unittest
4+
35
from pytype.tests import test_base
46
from pytype.tests import test_utils
57

@@ -205,6 +207,35 @@ def g(x: int) -> int:
205207
assert_type(a, Callable[[int], str])
206208
""")
207209

210+
def test_bound_to_any1(self):
211+
self.Check("""
212+
from typing import Any, Callable, ParamSpec
213+
214+
P = ParamSpec("P")
215+
216+
def f(x: Callable[P, int]) -> Callable[P, int]:
217+
return x
218+
219+
def test1(g: Any):
220+
assert_type(f(g), Callable[..., int])
221+
def test1(g: Callable[..., Any]):
222+
assert_type(f(g), Callable[..., int])
223+
""")
224+
225+
@unittest.expectedFailure
226+
def test_bound_to_any2(self):
227+
self.Check("""
228+
from typing import Any, Callable, Concatenate, ParamSpec
229+
230+
P = ParamSpec("P")
231+
232+
def f(x: Callable[Concatenate[int, P], int]) -> Callable[Concatenate[int, P], int]:
233+
return x
234+
235+
def test(g: Any):
236+
assert_type(f(g), Callable[Concatenate[int, ...], int])
237+
""")
238+
208239
def test_typevar(self):
209240
self.Check("""
210241
from typing import Callable, Concatenate, List, ParamSpec, TypeVar

0 commit comments

Comments
 (0)