ty icon indicating copy to clipboard operation
ty copied to clipboard

`__package__` in the global namespace should have type `str`, not `str | None`

Open spaceone opened this issue 1 week ago • 3 comments

Question

I am unsure about the following, but mypy doesn't detect it:

$ ty check t.py 
t.py:4:23: error[invalid-argument-type] Argument to function `version` is incorrect: Expected `str`, found `str | None`
$ cat t.py
from importlib.metadata import version


__version__ = version(__package__)

Version

0.0.3

spaceone avatar Dec 18 '25 11:12 spaceone

fixing it will cause a mypy issue:

__version__ = version(cast('str', __package__)) # E: Redundant cast to "str" [redundant-cast]

spaceone avatar Dec 18 '25 12:12 spaceone

fixing it will cause a mypy issue:

__version__ = version(cast('str', __package__)) # E: Redundant cast to "str" [redundant-cast]

A solution to keep mypy etc happy would be to narrow out the | None for ty instead of cast

from importlib.metadata import version


__version__ = version(__package__ or "unset")

# or

if __package__:
    __version__ = version(__package__)
else:
    __version__ = version("unset")

https://play.ty.dev/edb7161a-2b3d-4373-87ed-c4f60edf5bfc

I think this is similar to: https://github.com/astral-sh/ty/issues/860 where based on typeshed alone __package__ can be None https://github.com/search?q=repo%3Apython%2Ftypeshed%20__package__&type=code but typecheckers can probably infer that at runtime it will only be str

sinon avatar Dec 18 '25 12:12 sinon

Thanks! This issue is similar to https://github.com/astral-sh/ty/issues/860.

We get our type here from typeshed's types.ModuleType stub: https://github.com/python/typeshed/blob/8d96801533918957fb194e101cb321bfe1f836f8/stdlib/types.pyi#L362-L368. The reason why typeshed has the type str | None for this attribute is this line in the Python data model:

defaults to None for modules created dynamically using the types.ModuleType constructor; use importlib.util.module_from_spec() instead to ensure the attribute is set to a str.

But when we encounter __package__ in the global namespace like this, it's extremely likely that the module is going to be implicitly created by the Python interpreter rather than created using an explicit types.ModuleType instantiation. So I think we could safely override typeshed's type here and infer str for __package__ in the global namespace.

AlexWaygood avatar Dec 18 '25 12:12 AlexWaygood