Skip to content

gh-112903: Handle non-types in _BaseGenericAlias.__mro_entries__() #115191

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

Merged
merged 5 commits into from
Feb 9, 2024

Conversation

carljm
Copy link
Member

@carljm carljm commented Feb 9, 2024

The tuple of bases passed to an __mro_entries__() method is the original set of bases. This means that it can validly include non-type objects which define an __mro_entries__() method themselves. So it is not safe for an __mro_entries__() implementation to assume that all the bases passed to it are types. But _BaseGenericAlias currently wrongly assumes this (it calls issubclass() on them.)

In this PR, we avoid calling issubclass on anything that is not a type.

We also have to maintain the intended invariant that we only add typing.Generic to the MRO if nothing else in the MRO will be a subclass of Generic (otherwise we will run into "can't form consistent MRO" errors.) In order to do this reliably, we must ourselves call __mro_entries__() on non-types in bases, to see if the replacement MRO entries include any Generic subclasses.

Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

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

There are several too long lines, otherwise LGTM.

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

Thanks!

So if I understand correctly, for every instance of _BaseGenericAlias in __orig_bases__, we iterate over all the following items in __orig_bases__ to see if any of them might like to insert Generic into the resulting __bases__ tuple. If any of them would, we decline to insert Generic into the __bases__ tuple now; we try to ensure Generic appears at most once in the final __bases__ tuple, and we try to ensure it appears as late in the __bases__ tuple as possible.

Is that correct? If so, would you mind expanding a little bit in your comment on line 1139? :)

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

Looks great overall.

A few more nitpicks to do with the tests. I also agree with all of Serhiy's remarks ;)

You could also test @JelleZijlstra's example from #112926 (review) and assert that Generic does not appear in the __bases__ tuple. With this new PR, there's no reason why it would, but it seems like a useful invariant to assert regardless

@carljm
Copy link
Member Author

carljm commented Feb 9, 2024

Is that correct? If so, would you mind expanding a little bit in your comment on line 1139?

Yes, and done.

You could also test @JelleZijlstra's example from #112926 (review) and assert that Generic does not appear in the bases tuple.

Done, though note the correct behavior we are testing for is that Generic should appear in the bases tuple.

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

A few more nitpicks regarding comments, but this LGTM now. Thanks!

@carljm
Copy link
Member Author

carljm commented Feb 9, 2024

Any opinions on whether this should be backported? Technically it fixes a bug that was present in both 3.11 and 3.12 (with arbitrary non-types in bases), but that bug has not been encountered in the wild (that we know of). The issue that was encountered (with specifically types.GenericAlias) is only present in 3.13, since that's where types.GenericAlias was changed to no longer act like a type in issubclass.

I'm inclined to backport it all the way to 3.11, since it does fix a known bug, but open to suggestions otherwise.

@AlexWaygood
Copy link
Member

Any opinions on whether this should be backported? Technically it fixes a bug that was present in both 3.11 and 3.12 (with arbitrary non-types in bases), but that bug has not been encountered in the wild (that we know of). The issue that was encountered (with specifically types.GenericAlias) is only present in 3.13, since that's where types.GenericAlias was changed to no longer act like a type in issubclass.

I'm inclined to backport it all the way to 3.11, since it does fix a known bug, but open to suggestions otherwise.

I was wondering about this. I agree that there's been a latent bug here for a while, so it's definitely eligible for backporting.

I lean towards not backporting, though. The fact that this hasn't come up until now suggests very few people are defining __mro_entries__ outside of the standard library, and even fewer are using multiple inheritance with their custom-__mro_entries__ objects and instances of typing._BaseGenericAlias. Also, this feels like quite a delicate area of typing.py. While this PR looks good to me, it would be pretty unfortunate if we accidentally changed people's mros on stable versions of Python.

@carljm
Copy link
Member Author

carljm commented Feb 9, 2024

That makes sense to me. I will not backport for now.

@carljm carljm merged commit a225520 into python:main Feb 9, 2024
@carljm carljm deleted the genmro branch February 9, 2024 19:19
@serhiy-storchaka
Copy link
Member

I am for backporting. We haven't had any reports of this bug, possibly because people trying to do something clever with __mro_entries__() thought the bug was in their code. But when two years pass, and 3.13 will be the main version for a new code, we may receive bug reports. We confirm that it is a bug, but too late -- 3.11 and 3.12 only accept security fixes.

@carljm
Copy link
Member Author

carljm commented Feb 9, 2024

There are risks both ways. @JelleZijlstra, as another typing maintainer, any opinion on backporting this?

@JelleZijlstra
Copy link
Member

I'd lean towards no. There is always a risk that changes to typing.py break some existing use case, and it feels unlikely for people to run into this issue on their own.

@carljm
Copy link
Member Author

carljm commented Feb 9, 2024

@serhiy-storchaka I will go with the preference of the typing maintainers. If we get bug reports about this against 3.11 when it is too late to backport the fix, you will retain "told you so" rights ;)

@itamaro
Copy link
Contributor

itamaro commented Feb 11, 2024

Another potential consideration in favor of backporting is user expectations and reliance on incorrect / buggy behavior.
I did not look at the details of this issue closely, so I don't know if that applies here, but if users rely on the incorrect behavior in 3.11 and 3.12 (explicitly or not), then the correct 3.13 behavior would be a bug for them. Sure, maybe this is already the case, and someone relying on this in 3.12.2 would have their code broken in 3.12.3 (if we backport), but arguably fixing it sooner would be better.

@carljm
Copy link
Member Author

carljm commented Feb 12, 2024

@itamaro The buggy behavior is an exception when there should not be an exception, so it's not a behavior users will rely on. (On the other hand, this also means the fix itself should not break anyone's code; the concerns about backporting are about unintended side effects of the fix that we haven't realized.)

Also, the bug has been present for quite a long time, probably since Python 3.7.

fsc-eriker pushed a commit to fsc-eriker/cpython that referenced this pull request Feb 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants