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

More detailed checking of type objects in stubtest #18251

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

Conversation

tungol
Copy link
Contributor

@tungol tungol commented Dec 5, 2024

This uses checkmember.type_object_type and context to produce better types of type objects.

I did this work while doing a deep dive on argparse.ArgumentParser.__init__, which is on the allowlist in typeshed with the note:

stubtest doesn't recognise the runtime default (a class) as being compatible with a callback protocol (the stub annotation)

Mypy proper has no problem with this, and after investigation the primary difference seemed to be that stubtest looked up the type of the class and got type while Mypy proper used checkmember.type_object_type to convert the class into a callable type. This worked to clear argparse.ArgumentParser.__init__, but generated all kinds of new errors at first. After some cleanup, I still had about as many new errors as errors fixed. Several of these were an instance of #3737, so I special-cased them to skip the new logic.

At this point, this MR clears five allowlist entries from typeshed standard library:

note: unused allowlist entry http.client.HTTPConnection.response_class
note: unused allowlist entry argparse.ArgumentParser.__init__
note: unused allowlist entry inspect.Parameter.empty
note: unused allowlist entry inspect.Signature.empty
note: unused allowlist entry multiprocessing.reduction.AbstractReducer.ForkingPickler

It introduces four new errors:

error: ctypes.CDLL._func_restype_ variable differs from runtime type def (value: builtins.int =) -> ctypes.c_int
Stub: in file /Users/stephen/Developer/clones/typeshed/stdlib/ctypes/__init__.pyi:50
Union[_ctypes._SimpleCData[Any], _ctypes._Pointer[Any], _ctypes.CFuncPtr, _ctypes.Union, _ctypes.Structure, _ctypes.Array[Any]]
Runtime: in file /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/ctypes/__init__.py:187
<class 'ctypes.c_int'>

error: ctypes.c_time_t variable differs from runtime type def (value: builtins.int =) -> ctypes.c_long
Stub: in file /Users/stephen/Developer/clones/typeshed/stdlib/ctypes/__init__.pyi:205
Union[type[ctypes.c_int32], type[ctypes.c_int64]]
Runtime: in file /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/ctypes/__init__.py:174
<class 'ctypes.c_long'>

error: unittest.TextTestRunner.resultclass variable differs from runtime type def [_StreamT <: unittest.runner._TextTestStream = unittest.runner._WritelnDecorator] (stream: _StreamT`1 = unittest.runner._WritelnDecorator, descriptions: builtins.bool, verbosity: builtins.int, *, durations: Union[builtins.list[tuple[builtins.str, builtins.float]], None] =) -> unittest.runner.TextTestResult[_StreamT`1 = unittest.runner._WritelnDecorator]
Stub: in file /Users/stephen/Developer/clones/typeshed/stdlib/unittest/__init__.pyi:53
def (unittest.runner._TextTestStream, builtins.bool, builtins.int) -> unittest.runner.TextTestResult[unittest.runner._WritelnDecorator]
Runtime: in file /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/unittest/runner.py:30
def (stream, descriptions, verbosity, *, durations=None)

error: unittest.runner.TextTestRunner.resultclass variable differs from runtime type def [_StreamT <: unittest.runner._TextTestStream = unittest.runner._WritelnDecorator] (stream: _StreamT`1 = unittest.runner._WritelnDecorator, descriptions: builtins.bool, verbosity: builtins.int, *, durations: Union[builtins.list[tuple[builtins.str, builtins.float]], None] =) -> unittest.runner.TextTestResult[_StreamT`1 = unittest.runner._WritelnDecorator]
Stub: in file /Users/stephen/Developer/clones/typeshed/stdlib/unittest/runner.pyi:53
def (unittest.runner._TextTestStream, builtins.bool, builtins.int) -> unittest.runner.TextTestResult[unittest.runner._WritelnDecorator]
Runtime: in file /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/unittest/runner.py:30
def (stream, descriptions, verbosity, *, durations=None)

Of these, I think that:

ctypes.CDLL._func_restype_ is a genuine error in the stubs (we have _func_restype_: ClassVar[_CDataType] and it should be _func_restype_: ClassVar[type[_CDataType]]). `

ctypes.c_time_t looks like a genuine error. We have c_time_t: type[c_int32 | c_int64] which is fine, but the issue is that in the stubs c_int32 and c_int64 are both unique classes and at runtime they're each an alias for one of c_short, c_int, c_long, or c_longlong. We can clear it by expanding the union to include those additional types, but I'm 100% certain if that's better or not.

unittest.TextTestRunner.resultclass and unittest.runner.TextTestRunner.resultclass are the same error. I looked at it for a while and I can't figure out what the issue is. We have resultclass: _ResultClassType where that's the type alias:

_ResultClassType: TypeAlias = Callable[[_TextTestStream, bool, int], TextTestResult]

class _SupportsWriteAndFlush(SupportsWrite[str], SupportsFlush, Protocol): ...

# All methods used by unittest.runner.TextTestResult's stream
class _TextTestStream(_SupportsWriteAndFlush, Protocol):
    def writeln(self, arg: str | None = None, /) -> str: ...

and the runtime default is TextTestResult, which has

_StreamT = TypeVar("_StreamT", bound=_TextTestStream, default=_WritelnDecorator)

class TextTestResult(unittest.result.TestResult, Generic[_StreamT]):
    def __init__(
        self, stream: _StreamT, descriptions: bool, verbosity: int, *, durations: unittest.result._DurationsType | None = None
    ) -> None: ...

It's a complicated set of type aliases and type variables, and I think it's probably likely that there's something real here, but I can't spot it.

This uses checkmember.type_object_type and context to produce
better types of type objects.
@tungol tungol marked this pull request as draft December 5, 2024 23:53
@tungol tungol marked this pull request as ready for review December 6, 2024 00:15
@tungol
Copy link
Contributor Author

tungol commented Dec 6, 2024

I figured out a fix for unittest.TextTestRunner.resultclass: if _ResultClassType is chaged to _ResultClassType: TypeAlias = Callable[[_TextTestStream, bool, int], TextTestResult[Any]], then it passes. The default on _StreamT hid that the unmarked TextTestResult in there wasn't Any. That seems like a correct change as well.

@tungol
Copy link
Contributor Author

tungol commented Dec 8, 2024

I added a test case which I confirmed fails without this MR in place.

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

Successfully merging this pull request may close these issues.

2 participants