Skip to content

Erase stray typevars in functools.partial generic #18954

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

sterliakov
Copy link
Collaborator

@sterliakov sterliakov commented Apr 23, 2025

Fixes #18953. Fixes #15215. Refs #17461.

When the function passed to partial is generic and has generic params in the return type, we must erase them, otherwise they become orphan and cannot be used later. This only applies to partial[...] generic param and not to the underlying "exact" callable stored internally as the latter remains generic.

The ultimate fix would be to implement #17620 so that we stop caring about partial[...] generic param, but this should improve usability (but causes false negatives).

This comment has been minimized.

Copy link
Collaborator

@A5rocks A5rocks left a comment

Choose a reason for hiding this comment

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

The change itself makes sense to me but I'm a bit surprised by the tests


# We should not leak stray typevars that aren't in scope:
use_int_callable(partial(func_b, b=""))
use_func_callable(partial(func_b, b=""))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why does this pass? I assume the erased return type is Union[int, str] and not Never...

Also could you add some reveal_type like use_func_callable(reveal_type(partial(func_b, b="")))

Copy link
Collaborator Author

@sterliakov sterliakov Apr 24, 2025

Choose a reason for hiding this comment

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

The erased return type is Any. erase_typevars replaces them with Any (special_form), not with upper bounds.

Adding reveal_type is a good idea, let's show the inferred generics explicitly. I don't even need use_*_callable then... no, let's keep both - subtype checks may do something different

Copy link
Collaborator

@A5rocks A5rocks Apr 24, 2025

Choose a reason for hiding this comment

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

I think you should still have the use_*_callable to ensure things still work, at least in a few spots -- printing correctly doesn't necessarily mean the logic is right, e.g. maybe the plugin could mess up passing the partials to the use_*_callable functions (?).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

And Any is really what we want. Replacing tvars with their upper bounds will cause another bunch of false positives whenever smth like def fn[T: str | int](x: T) -> T (hope I didn't mess up this ugly syntax) is used with partial.

Copy link
Contributor

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

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.

Spurious error with partial and TypeVar Spurious error with partial and ParamSpec
2 participants