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

type Distribution.get_command_obj to not return None with create=True #307

Merged
merged 1 commit into from
Dec 27, 2024

Conversation

Avasam
Copy link
Contributor

@Avasam Avasam commented Oct 25, 2024

This mirrors the typeshed change python/typeshed#11950

This is only visible for distutils itself and setuptools in pypa/setuptools#4704

distutils/dist.py Outdated Show resolved Hide resolved
Comment on lines 840 to 841
@overload
def get_command_obj(self, command: str, create: Literal[False]) -> Command | None: ...
Copy link
Member

Choose a reason for hiding this comment

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

Is this overload needed? I don't see how it adds any useful information.

Copy link
Contributor Author

@Avasam Avasam Dec 26, 2024

Choose a reason for hiding this comment

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

Without overloads, false-positives (before):

distribution: Distribution = ...
some_command = distribution.get_command_obj("some_command")
# Mypy: Revealed type is "distutils.cmd.Command | None"
# Pyright: Type of "some_command" is "Command | None"
reveal_type(some_command)
# Mypy: Item "None" of "Command | None" has no attribute "some_command_create_true" (union-attr)
# Pyright: "some_command_create_true" is not a known attribute of "None" (reportOptionalMemberAccess)
some_command_create_true.some_command_create_true

With overloads (this PR):

distribution: Distribution = ...
some_command = distribution.get_command_obj("some_command")
# Mypy: Revealed type is "distutils.cmd.Command"
# Pyright: Type of "some_command" is "Command"
reveal_type(some_command)
some_command.sub_commands  # OK

In both cases, create=False doesn't change

# True-positive
some_command = distribution.get_command_obj("some_command", False)
# Mypy: Revealed type is "distutils.cmd.Command | None"
# Pyright: Type of "some_command" is "Command | None"
reveal_type(some_command)
# Mypy: Item "None" of "Command | None" has no attribute "sub_commands  " (union-attr)
# Pyright: "sub_commands " is not a known attribute of "None" (reportOptionalMemberAccess)
some_command.sub_commands  

Copy link
Member

Choose a reason for hiding this comment

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

I don't think that answered my question. So I fumbled around until I was able to somewhat replicate your examples above. I created foo.py:

import distutils.dist

distribution: distutils.dist.Distribution = ...
some_command = distribution.get_command_obj("some_command", False)
# Mypy: Revealed type is "distutils.cmd.Command"
# Pyright: Type of "some_command" is "Command"
reveal_type(some_command)
some_command.sub_commands  # OK

And I ran mypy foo.py with this copy of distutils, and indeed I got:

foo.py:7: note: Revealed type is "Union[distutils.cmd.Command, None]"

Then I removed this overload and ran it again, and got a different result:

foo.py:4: error: Argument 2 to "get_command_obj" of "Distribution" has incompatible type "Literal[False]"; expected "Literal[True]"  [arg-type]
foo.py:7: note: Revealed type is "distutils.cmd.Command"

That's interesting. I would have expected the third definition, the one with create: bool = True to fulfill the Literal[False] case, instead of having to overload it with exactly the same signature. Alas, it seems our type system isn't so elegant.

Any idea if that's an issue being tracked by the type checking ecosystem?

Copy link
Contributor Author

@Avasam Avasam Dec 27, 2024

Choose a reason for hiding this comment

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

I would have expected the third definition

There's only two overload definitions.
The "third" is the implementation. That is (and should) be completely ignored by type-checkers when using that method. Annotating the implementation (generally as a combination of all possible overloads) is only useful to type-check the content of the function.

@Avasam Avasam force-pushed the get_command_obj-overload branch 3 times, most recently from f5b6dc2 to cef37dd Compare December 27, 2024 02:02
@Avasam Avasam force-pushed the get_command_obj-overload branch from cef37dd to deb1d5a Compare December 27, 2024 02:02
@jaraco jaraco merged commit cfc8c4a into pypa:main Dec 27, 2024
22 checks passed
@Avasam Avasam deleted the get_command_obj-overload branch December 27, 2024 02:39
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.

2 participants