Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-44801: Check arguments in substitution of ParamSpec in Callable #27585

Merged
merged 3 commits into from
Aug 4, 2021

Conversation

serhiy-storchaka
Copy link
Member

@serhiy-storchaka serhiy-storchaka commented Aug 3, 2021

Copy link
Member

@Fidget-Spinner Fidget-Spinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phew, that was harder than I expected to review 😉 .

Thanks for fixing this Serhiy, it looks good to me (and also seems like it follows the PEP more closely now :).

We might not be able to backport this until 3.10.1. I don't think such a huge change can land in rc2.

@@ -492,7 +487,13 @@ def __getitem__(self, item):
new_args = []
for arg in self.__args__:
if _is_typevarlike(arg):
arg = subst[arg]
if _is_param_expr(arg):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking for something like _is_paramspec would be clearer IMO, but we don't have that function and it's kind of a waste to make a function just for this one-time use.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code will gone after moving the logic in ParamSpec.__getitem__ in #27511.

I am planning first to fix most bugs and to cover more cases by tests, and only then apply #27511, so we may not need to backport it.

C1 = Callable[P, T]
# substitution
self.assertEqual(C1[int, str], Callable[[int], str])
self.assertEqual(C1[[int], str], Callable[[int], str])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I feel a little strange about this. I feel that the new form is correct, but I also feel the old form should work. There's not much ambiguity in using the old form.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PEP explicitly claim that only substituting ParamSpec with the parameters expression should be accepted. It will also simplify the future code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok I see it now:

def f(x: X[int, int]) -> str: ...                    # Rejected

Thanks for the reminder.

self.assertEqual(C1[[int, str], str], Callable[[int, str], str])
self.assertEqual(C1[[], str], Callable[[], str])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good test! 👍

@@ -4656,6 +4672,15 @@ class Z(Generic[P]):
self.assertEqual(G5.__parameters__, G6.__parameters__)
self.assertEqual(G5, G6)

G7 = Z[int]
self.assertEqual(G7.__args__, ((int,),))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a critique of your PR, but a minor regret of mine: Looking at G5, G6, G7, I realized their __args__ should be (int, str, bool) or (int, ), etc.; not nested tuple with types. Nested tuple makes repr weird, and also nested tuple means any nested TypeVar or ParamSpec won't appear in __parameters__ and so can't be substituted.

I don't remember what made me code it like that back then, but it could be me not wanting to make the _GenericAlias.__getitem__ code complicated just for ParamSpec.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not work for user generics because they can have more than one ParamSpec. For example:

class X(Generic[P1, P2]):
    f: Callable[P1, int]
    g: Callable[P2, str]
G1 = X[[int, str], [bytes]]
G2 = X[int, [str, bytes]]
assert G1.__args__ == ((int, str), (bytes,))
assert G2.__args__ == ((int,) (str, bytes))

If flatten arguments, __args__ of G1 and G2 would be the same ((int, str, bytes)). But they are clearly different types.

I'll add a special test for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, thanks for pointing that out! I forgot there was ambiguity with multiple ParamSpec. Hopefully some future PEP makes this clearer.

Copy link
Member

@Fidget-Spinner Fidget-Spinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, LGTM. Thanks for patiently addressing my questions Serhiy :).

@ambv ambv merged commit 3875a69 into python:main Aug 4, 2021
@miss-islington
Copy link
Contributor

Thanks @serhiy-storchaka for the PR, and @ambv for merging it 🌮🎉.. I'm working now to backport this PR to: 3.10.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Aug 4, 2021
…ythonGH-27585)

(cherry picked from commit 3875a69)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
@bedevere-bot bedevere-bot removed the needs backport to 3.10 only security fixes label Aug 4, 2021
@bedevere-bot
Copy link

GH-27598 is a backport of this pull request to the 3.10 branch.

miss-islington added a commit that referenced this pull request Aug 4, 2021
…H-27585)

(cherry picked from commit 3875a69)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants