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

stubtest false positive with @type_check_only subtype #816

Open
jorenham opened this issue Nov 21, 2024 · 1 comment
Open

stubtest false positive with @type_check_only subtype #816

jorenham opened this issue Nov 21, 2024 · 1 comment
Labels

Comments

@jorenham
Copy link
Collaborator

jorenham commented Nov 21, 2024

Describe the problem

In scipy.special there are a lot of "ufuncs", which are callables numpy.ufunc instances that have a bunch of extra attributes, methods, and automatically convert an vectorize the input.
But the numpy.ufunc annotations are very limited, and not very helpfull when it comes to annotating the ufuncs in scipy.special.
So instead of annotating e.g. beta: np.ufunc, I created a private @type_check_only subtype of np.ufunc, and annotated it to match the type-signatures of scipy.special.beta.
But running stubtest on it, gives the following error:

error: scipy.special.beta variable differs from runtime type numpy.ufunc
Stub: in file /home/joren/Workspace/scipy-stubs/scipy-stubs/special/__init__.pyi:590
scipy.special._ufuncs._UFunc21f['beta', 0]
Runtime:
def (*args, **kwargs)

(scipy.special._ufuncs._UFunc21f is the ufunc subtype)

The error message here isn't very helpful, and I don't see why it shouldn't be allowed. Because such a subtype is basically an intersection of ufunc and the beta-specific annotations (but in a way that pyright and cringe mypy also understand).

See jorenham/scipy-stubs#189 for the gory details.

Maybe I'm missing something, but I tried this a bunch of different ways, and none of them seemed to get rid of this error, so this looks like a false positive to me 🤷🏻.

Gist to reproduce

No response

Severity

annoying but workaround is available

Your Environment

basedmypy 2.7.0 (compiled: yes)
Based on mypy 1.13.0
@jorenham jorenham added the bug Something isn't working label Nov 21, 2024
@jorenham jorenham changed the title stubgen false positive with @type_check_only subtype stubtest false positive with @type_check_only subtype Nov 25, 2024
@jorenham jorenham added stubtest and removed stubgen labels Nov 25, 2024
@jorenham
Copy link
Collaborator Author

some of the relevant code:

basedmypy/mypy/stubtest.py

Lines 1098 to 1122 in 0935784

runtime_type = get_mypy_type_of_runtime_value(runtime)
if (
runtime_type is not None
and stub.type is not None
and not is_subtype_helper(runtime_type, stub.type)
):
should_error = True
# Avoid errors when defining enums, since runtime_type is the enum itself, but we'd
# annotate it with the type of runtime.value
if isinstance(runtime, enum.Enum):
runtime_type = get_mypy_type_of_runtime_value(runtime.value)
if runtime_type is not None and is_subtype_helper(runtime_type, stub.type):
should_error = False
# We always allow setting the stub value to ...
proper_type = mypy.types.get_proper_type(stub.type)
if (
isinstance(proper_type, mypy.types.Instance)
and proper_type.type.fullname == "builtins.ellipsis"
):
should_error = False
if should_error:
yield Error(
object_path, f"variable differs from runtime type {runtime_type}", stub, runtime
)

basedmypy/mypy/stubtest.py

Lines 1564 to 1584 in 0935784

def is_subtype_helper(left: mypy.types.Type, right: mypy.types.Type) -> bool:
"""Checks whether ``left`` is a subtype of ``right``."""
left = mypy.types.get_proper_type(left)
right = mypy.types.get_proper_type(right)
if (
isinstance(left, mypy.types.LiteralType)
and isinstance(left.value, int)
and left.value in (0, 1)
and mypy.types.is_named_instance(right, "builtins.bool")
):
# Pretend Literal[0, 1] is a subtype of bool to avoid unhelpful errors.
return True
if isinstance(right, mypy.types.TypedDictType) and mypy.types.is_named_instance(
left, "builtins.dict"
):
# Special case checks against TypedDicts
return True
with mypy.state.state.strict_optional_set(True):
return mypy.subtypes.is_subtype(left, right)

Perhaps this mypy.types.get_proper_type function should return the supertype of a @type_check_only type 🤔?
Or maybe it should use the return type of the overridden __class__ property (which I did in this case as well, but that didn't do much) if present?

basedmypy/mypy/types.py

Lines 3498 to 3514 in 0935784

def get_proper_type(typ: Type | None) -> ProperType | None:
"""Get the expansion of a type alias type.
If the type is already a proper type, this is a no-op. Use this function
wherever a decision is made on a call like e.g. 'if isinstance(typ, UnionType): ...',
because 'typ' in this case may be an alias to union. Note: if after making the decision
on the isinstance() call you pass on the original type (and not one of its components)
it is recommended to *always* pass on the unexpanded alias.
"""
if typ is None:
return None
if isinstance(typ, TypeGuardedType): # type: ignore[misc]
typ = typ.type_guard
while isinstance(typ, TypeAliasType):
typ = typ._expand_once()
# TODO: store the name of original type alias on this type, so we can show it in errors.
return cast(ProperType, typ)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants