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

Discrepancy between pyright and mypy in type evaluation of abnormal virtual super class #4331

Closed
ringohoffman opened this issue Dec 14, 2022 · 3 comments
Labels
as designed Not a bug, working as intended

Comments

@ringohoffman
Copy link

Describe the bug
I am using cloudpathlib (the latest release, 0.10.0) in one of the repos I work in. It does some interesting stuff in regard to typing. It seems to be resulting in some undefined (?) behavior.

They have an an abstract base class called cloudpathlib.AnyPath, which they register as a virtual super class of cloudpathlib.CloudPath and pathlib.Path, except that they redefine AnyPath's __new__ to return Union[CloudPath, Path].

mypy seems to think that the returned instance is an AnyPath, while pylance seems to think the returned instance is Union[CloudPath, Path].

To Reproduce

from abc import ABC
from pathlib import Path
from typing import Union

# from cloudpathlib import AnyPath, CloudPath

# a minimally reproducible example ##############################


class CloudPath:
    def __truediv__(self, other: str) -> "CloudPath":
        ...


class AnyPath(ABC):
    def __new__(self, *args, **kwargs) -> Union[CloudPath, Path]:
        ...


AnyPath.register(Path)
AnyPath.register(CloudPath)

#################################################################


def upload_file(path: AnyPath) -> None:
    ...


cloud_path = AnyPath("s3://mybucket/myfile")

reveal_type(cloud_path)  # Type of "cloud_path" is "CloudPath | Path" Pylance ; Revealed type is "a.AnyPath" mypy(note)

full_path = cloud_path / ".txt"  # Unsupported left operand type for / ("AnyPath")  [operator] mypy(error)

upload_file(cloud_path)  # Argument of type "CloudPath | Path" cannot be assigned to parameter "path" of type "AnyPath" in function "upload_file" Type "CloudPath | Path" cannot be assigned to type "AnyPath" ; "CloudPath" is incompatible with "AnyPath" Pylance[reportGeneralTypeIssues]

Expected behavior

Even though the current implementation is kind of strange, I don't really expect pylance's message that Cloudpath is incompatible with AnyPath since AnyPath is technically a superclass of Path and CloudPath... and I DO expect mypy's error message since typically virtual super classes define the methods that its virtual subclasses implement (which AnyPath does not).

VS Code extension or command-line

mypy 0.991 (compiled: yes)
pylance extension v2022.12.21
@ringohoffman ringohoffman changed the title Discrepancy between pyright and mypy Discrepancy between pyright and mypy in type evaluation of abnormal virtual super class Dec 14, 2022
@erictraut
Copy link
Collaborator

Static type checkers do not support the dynamic (runtime) registration of ABCs.

AnyPath is not a superclass of Path or CloudPath. The runtime may fake this through dynamic registration, but from a static type checking perspective, the classes have no relationship.

To make this work with pyright, you'll need to use some technique other than dynamic ABC registration. A few options:

  1. Change CloudPath and Path to actually use AnyPath as a base class. This requires that you are defining these classes, which is probably not true in this case.
  2. Use a union type CloudPath | Path.
  3. Use a constrained TypeVar that is constrained to either CloudPath or Path.
  4. Use a Protocol class that describes the interface that is common to CloudPath and Path.

@erictraut erictraut added the as designed Not a bug, working as intended label Dec 14, 2022
@ringohoffman
Copy link
Author

Can you explain why static type checkers will accept a list[str] as an argument to a parameter typed as accepting a typing.Sequence[str]? From what you explained it seems like this shouldn't work either unless something else is going on. I am wondering why I am not capable of recreating this behavior with my own custom virtual superclass.

@hmc-cs-mdrissi
Copy link

This is a side effect of type stubs in typeshed where list is defined as a subclass of sequence. If you wanted to do same you have would need to have a stub file where you have a fake parent class hierarchy or use if TYPE_CHECKING. For pathlib Path this would require overriding typeshed stub for Path which is probably not advisable. 2/4 is solution I’d usually pick. 3 works although constrained type variables are strange to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
as designed Not a bug, working as intended
Projects
None yet
Development

No branches or pull requests

3 participants