From 23ac9bff1913cec5bb507adc442b8b2bd380a3f0 Mon Sep 17 00:00:00 2001 From: Avasam Date: Wed, 28 Dec 2022 05:44:29 -0500 Subject: [PATCH] Check for unused `pyright: ignore` and differentiate from mypy ignores (#9397) --- CONTRIBUTING.md | 5 +++++ pyrightconfig.json | 6 ++++++ pyrightconfig.stricter.json | 6 ++++++ pyrightconfig.testcases.json | 3 +++ stdlib/asyncio/tasks.pyi | 2 +- stdlib/builtins.pyi | 2 +- stdlib/collections/__init__.pyi | 7 ++++--- stdlib/ctypes/__init__.pyi | 4 ++-- stdlib/subprocess.pyi | 2 +- stubs/SQLAlchemy/sqlalchemy/orm/path_registry.pyi | 3 ++- stubs/SQLAlchemy/sqlalchemy/util/_collections.pyi | 8 ++++---- stubs/invoke/invoke/tasks.pyi | 4 ++-- stubs/prettytable/prettytable/colortable.pyi | 2 +- stubs/pywin32/win32comext/axdebug/documents.pyi | 3 ++- stubs/redis/redis/client.pyi | 2 -- stubs/redis/redis/commands/json/__init__.pyi | 3 ++- stubs/redis/redis/commands/timeseries/__init__.pyi | 3 ++- 17 files changed, 44 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index baf4f10ca..24d9fa8f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -467,6 +467,11 @@ Some further tips for good type hints: platform-dependent APIs; * use mypy error codes for mypy-specific `# type: ignore` annotations, e.g. `# type: ignore[override]` for Liskov Substitution Principle violations. +* use pyright error codes for pyright-specific suppressions, + e.g. `# pyright: ignore[reportGeneralTypeIssues]`. + - pyright is configured to discard `# type: ignore` annotations. + If you need both on the same line, mypy's annotation needs to go first, + e.g. `# type: ignore[override] # pyright: ignore[reportGeneralTypeIssues]`. Imports in stubs are considered private (not part of the exported API) unless: diff --git a/pyrightconfig.json b/pyrightconfig.json index 50229b5aa..49eee0038 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -35,6 +35,12 @@ "reportInconsistentConstructor": "error", "reportTypeCommentUsage": "error", "reportUnnecessaryComparison": "error", + "reportUnnecessaryTypeIgnoreComment": "error", + // Leave "type: ignore" comments to mypy + "enableTypeIgnoreComments": false, + // Stubs are allowed to use private variables + "reportPrivateUsage": "none", + // Stubs don't need the actual modules to be installed "reportMissingModuleSource": "none", // Incompatible overrides and property type mismatches are out of typeshed's control // as they are inherited from the implementation. diff --git a/pyrightconfig.stricter.json b/pyrightconfig.stricter.json index 06d11f1a3..5b61c069b 100644 --- a/pyrightconfig.stricter.json +++ b/pyrightconfig.stricter.json @@ -73,8 +73,14 @@ "stubs/vobject", ], "typeCheckingMode": "strict", + "reportUnnecessaryTypeIgnoreComment": "error", + // Leave "type: ignore" comments to mypy + "enableTypeIgnoreComments": false, + // Stubs are allowed to use private variables "reportPrivateUsage": "none", + // TODO: Complete incomplete stubs "reportIncompleteStub": "none", + // Stubs don't need the actual modules to be installed "reportMissingModuleSource": "none", // Incompatible overrides and property type mismatches are out of typeshed's control // as they are inherited from the implementation. diff --git a/pyrightconfig.testcases.json b/pyrightconfig.testcases.json index e0c54fb3a..25c51569d 100644 --- a/pyrightconfig.testcases.json +++ b/pyrightconfig.testcases.json @@ -5,6 +5,9 @@ "test_cases", ], "typeCheckingMode": "strict", + // Using unspecific "type ignore" comments in test_cases. + // See https://github.com/python/typeshed/pull/8083 + "enableTypeIgnoreComments": true, "reportPropertyTypeMismatch": "error", "reportUnnecessaryTypeIgnoreComment": "error", "reportMissingModuleSource": "none", diff --git a/stdlib/asyncio/tasks.pyi b/stdlib/asyncio/tasks.pyi index 67581eb6a..43dd020fa 100644 --- a/stdlib/asyncio/tasks.pyi +++ b/stdlib/asyncio/tasks.pyi @@ -270,7 +270,7 @@ else: # While this is true in general, here it's sort-of okay to have a covariant subclass, # since the only reason why `asyncio.Future` is invariant is the `set_result()` method, # and `asyncio.Task.set_result()` always raises. -class Task(Future[_T_co], Generic[_T_co]): # type: ignore[type-var] +class Task(Future[_T_co], Generic[_T_co]): # type: ignore[type-var] # pyright: ignore[reportGeneralTypeIssues] if sys.version_info >= (3, 8): def __init__( self, diff --git a/stdlib/builtins.pyi b/stdlib/builtins.pyi index df74a00a4..4296acf60 100644 --- a/stdlib/builtins.pyi +++ b/stdlib/builtins.pyi @@ -1188,7 +1188,7 @@ class property: class _NotImplementedType(Any): # type: ignore[misc] # A little weird, but typing the __call__ as NotImplemented makes the error message # for NotImplemented() much better - __call__: NotImplemented # type: ignore[valid-type] + __call__: NotImplemented # type: ignore[valid-type] # pyright: ignore[reportGeneralTypeIssues] NotImplemented: _NotImplementedType diff --git a/stdlib/collections/__init__.pyi b/stdlib/collections/__init__.pyi index 37505c256..2955aa3b3 100644 --- a/stdlib/collections/__init__.pyi +++ b/stdlib/collections/__init__.pyi @@ -327,16 +327,17 @@ class _OrderedDictValuesView(ValuesView[_VT_co], Reversible[_VT_co]): # The C implementations of the "views" classes # (At runtime, these are called `odict_keys`, `odict_items` and `odict_values`, # but they are not exposed anywhere) +# pyright doesn't have a specific error code for subclassing error! @final -class _odict_keys(dict_keys[_KT_co, _VT_co], Reversible[_KT_co]): # type: ignore[misc] +class _odict_keys(dict_keys[_KT_co, _VT_co], Reversible[_KT_co]): # type: ignore[misc] # pyright: ignore def __reversed__(self) -> Iterator[_KT_co]: ... @final -class _odict_items(dict_items[_KT_co, _VT_co], Reversible[tuple[_KT_co, _VT_co]]): # type: ignore[misc] +class _odict_items(dict_items[_KT_co, _VT_co], Reversible[tuple[_KT_co, _VT_co]]): # type: ignore[misc] # pyright: ignore def __reversed__(self) -> Iterator[tuple[_KT_co, _VT_co]]: ... @final -class _odict_values(dict_values[_KT_co, _VT_co], Reversible[_VT_co], Generic[_KT_co, _VT_co]): # type: ignore[misc] +class _odict_values(dict_values[_KT_co, _VT_co], Reversible[_VT_co], Generic[_KT_co, _VT_co]): # type: ignore[misc] # pyright: ignore def __reversed__(self) -> Iterator[_VT_co]: ... class OrderedDict(dict[_KT, _VT], Reversible[_KT], Generic[_KT, _VT]): diff --git a/stdlib/ctypes/__init__.pyi b/stdlib/ctypes/__init__.pyi index 1851d3481..21d92dc59 100644 --- a/stdlib/ctypes/__init__.pyi +++ b/stdlib/ctypes/__init__.pyi @@ -64,8 +64,8 @@ class _CDataMeta(type): # By default mypy complains about the following two methods, because strictly speaking cls # might not be a Type[_CT]. However this can never actually happen, because the only class that # uses _CDataMeta as its metaclass is _CData. So it's safe to ignore the errors here. - def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] - def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] + def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] + def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] class _CData(metaclass=_CDataMeta): _b_base: int diff --git a/stdlib/subprocess.pyi b/stdlib/subprocess.pyi index 98aea46cc..450eb8cd2 100644 --- a/stdlib/subprocess.pyi +++ b/stdlib/subprocess.pyi @@ -97,7 +97,7 @@ class CompletedProcess(Generic[_T]): args: _CMD, returncode: int, stdout: _T | None = ..., # pyright: ignore[reportInvalidTypeVarUse] - stderr: _T | None = ..., # pyright: ignore[reportInvalidTypeVarUse] + stderr: _T | None = ..., ) -> None: ... def check_returncode(self) -> None: ... if sys.version_info >= (3, 9): diff --git a/stubs/SQLAlchemy/sqlalchemy/orm/path_registry.pyi b/stubs/SQLAlchemy/sqlalchemy/orm/path_registry.pyi index 2750e6d97..5fbc2783c 100644 --- a/stubs/SQLAlchemy/sqlalchemy/orm/path_registry.pyi +++ b/stubs/SQLAlchemy/sqlalchemy/orm/path_registry.pyi @@ -1,3 +1,4 @@ +from _typeshed import Incomplete from typing import Any, ClassVar from ..sql.traversals import HasCacheKey @@ -100,7 +101,7 @@ class AbstractEntityRegistry(PathRegistry): class SlotsEntityRegistry(AbstractEntityRegistry): inherit_cache: bool -class CachingEntityRegistry(AbstractEntityRegistry, dict): # type: ignore[misc] +class CachingEntityRegistry(AbstractEntityRegistry, dict[Incomplete, Incomplete]): # type: ignore[misc] inherit_cache: bool def __getitem__(self, entity): ... def __missing__(self, key): ... diff --git a/stubs/SQLAlchemy/sqlalchemy/util/_collections.pyi b/stubs/SQLAlchemy/sqlalchemy/util/_collections.pyi index a91d70350..3029c8ad7 100644 --- a/stubs/SQLAlchemy/sqlalchemy/util/_collections.pyi +++ b/stubs/SQLAlchemy/sqlalchemy/util/_collections.pyi @@ -85,13 +85,13 @@ class OrderedSet(set[_T], Generic[_T]): def update(self: Self, iterable: Iterable[_T]) -> Self: ... # type: ignore[override] __ior__ = update # type: ignore[assignment] def union(self, other: Iterable[_S]) -> OrderedSet[_S | _T]: ... # type: ignore[override] - __or__ = union # type: ignore[assignment] + __or__ = union # type: ignore[assignment] # pyright: ignore[reportGeneralTypeIssues] def intersection(self: Self, other: Iterable[Any]) -> Self: ... # type: ignore[override] - __and__ = intersection # type: ignore[assignment] + __and__ = intersection # type: ignore[assignment] # pyright: ignore[reportGeneralTypeIssues] def symmetric_difference(self, other: Iterable[_S]) -> OrderedSet[_S | _T]: ... - __xor__ = symmetric_difference # type: ignore[assignment] + __xor__ = symmetric_difference # type: ignore[assignment] # pyright: ignore[reportGeneralTypeIssues] def difference(self: Self, other: Iterable[Any]) -> Self: ... # type: ignore[override] - __sub__ = difference # type: ignore[assignment] + __sub__ = difference # type: ignore[assignment] # pyright: ignore[reportGeneralTypeIssues] def intersection_update(self: Self, other: Iterable[Any]) -> Self: ... # type: ignore[override] __iand__ = intersection_update # type: ignore[assignment] def symmetric_difference_update(self: Self, other: Iterable[_T]) -> Self: ... # type: ignore[override] diff --git a/stubs/invoke/invoke/tasks.pyi b/stubs/invoke/invoke/tasks.pyi index 3fdccff62..d43bce4fb 100644 --- a/stubs/invoke/invoke/tasks.pyi +++ b/stubs/invoke/invoke/tasks.pyi @@ -1,4 +1,4 @@ -from _typeshed import Self +from _typeshed import Incomplete, Self from collections.abc import Callable, Iterable from typing import Any, Generic, TypeVar, overload from typing_extensions import ParamSpec @@ -48,7 +48,7 @@ class Task(Generic[_P, _R_co]): ) -> None: ... @property def name(self): ... - def __eq__(self, other: Task) -> bool: ... # type: ignore[override] + def __eq__(self, other: Task[Incomplete, Incomplete]) -> bool: ... # type: ignore[override] def __hash__(self) -> int: ... def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R_co: ... @property diff --git a/stubs/prettytable/prettytable/colortable.pyi b/stubs/prettytable/prettytable/colortable.pyi index 8d9bfbae0..39a60f592 100644 --- a/stubs/prettytable/prettytable/colortable.pyi +++ b/stubs/prettytable/prettytable/colortable.pyi @@ -27,7 +27,7 @@ class Theme: junction_color: str = ..., ) -> None: ... # The following method is broken in upstream code. - def format_code(s: str) -> str: ... # type: ignore[misc] + def format_code(s: str) -> str: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] class Themes: DEFAULT: ClassVar[Theme] diff --git a/stubs/pywin32/win32comext/axdebug/documents.pyi b/stubs/pywin32/win32comext/axdebug/documents.pyi index 4fe834a8e..7c1516b6a 100644 --- a/stubs/pywin32/win32comext/axdebug/documents.pyi +++ b/stubs/pywin32/win32comext/axdebug/documents.pyi @@ -14,7 +14,8 @@ class DebugDocumentProvider(gateways.DebugDocumentProvider): def GetDocument(self): ... # error: Cannot determine consistent method resolution order (MRO) for "DebugDocumentText" -class DebugDocumentText(gateways.DebugDocumentInfo, gateways.DebugDocumentText, gateways.DebugDocument): # type: ignore[misc] +# pyright doesn't have a specific error code for MRO error! +class DebugDocumentText(gateways.DebugDocumentInfo, gateways.DebugDocumentText, gateways.DebugDocument): # type: ignore[misc] # pyright: ignore codeContainer: Incomplete def __init__(self, codeContainer) -> None: ... def GetName(self, dnt): ... diff --git a/stubs/redis/redis/client.pyi b/stubs/redis/redis/client.pyi index bfc06d530..ecab3eed4 100644 --- a/stubs/redis/redis/client.pyi +++ b/stubs/redis/redis/client.pyi @@ -23,7 +23,6 @@ _StrType = TypeVar("_StrType", bound=str | bytes) _VT = TypeVar("_VT") _T = TypeVar("_T") -_ScoreCastFuncReturn = TypeVar("_ScoreCastFuncReturn") # Keyword arguments that are passed to Redis.parse_response(). _ParseResponseOptions: TypeAlias = Any @@ -568,7 +567,6 @@ class Pipeline(Redis[_StrType], Generic[_StrType]): def sscan_iter(self, name: _Key, match: _Key | None = ..., count: int | None = ...) -> Iterator[Any]: ... def hscan(self, name: _Key, cursor: int = ..., match: _Key | None = ..., count: int | None = ...) -> Pipeline[_StrType]: ... # type: ignore[override] def hscan_iter(self, name, match: _Key | None = ..., count: int | None = ...) -> Iterator[Any]: ... - def zscan(self, name: _Key, cursor: int = ..., match: _Key | None = ..., count: int | None = ..., score_cast_func: Callable[[_StrType], _ScoreCastFuncReturn] = ...) -> Pipeline[_StrType]: ... # type: ignore[override] def zscan_iter( self, name: _Key, match: _Key | None = ..., count: int | None = ..., score_cast_func: Callable[[_StrType], Any] = ... ) -> Iterator[Any]: ... diff --git a/stubs/redis/redis/commands/json/__init__.pyi b/stubs/redis/redis/commands/json/__init__.pyi index e67c7bfef..60feec52f 100644 --- a/stubs/redis/redis/commands/json/__init__.pyi +++ b/stubs/redis/redis/commands/json/__init__.pyi @@ -1,3 +1,4 @@ +from _typeshed import Incomplete from typing import Any from ...client import Pipeline as ClientPipeline @@ -11,4 +12,4 @@ class JSON(JSONCommands): def __init__(self, client, version: Any | None = ..., decoder=..., encoder=...) -> None: ... def pipeline(self, transaction: bool = ..., shard_hint: Any | None = ...) -> Pipeline: ... -class Pipeline(JSONCommands, ClientPipeline): ... # type: ignore[misc] +class Pipeline(JSONCommands, ClientPipeline[Incomplete]): ... # type: ignore[misc] diff --git a/stubs/redis/redis/commands/timeseries/__init__.pyi b/stubs/redis/redis/commands/timeseries/__init__.pyi index fae4f849b..849388bd4 100644 --- a/stubs/redis/redis/commands/timeseries/__init__.pyi +++ b/stubs/redis/redis/commands/timeseries/__init__.pyi @@ -1,3 +1,4 @@ +from _typeshed import Incomplete from typing import Any from ...client import Pipeline as ClientPipeline @@ -10,4 +11,4 @@ class TimeSeries(TimeSeriesCommands): def __init__(self, client: Any | None = ..., **kwargs) -> None: ... def pipeline(self, transaction: bool = ..., shard_hint: Any | None = ...) -> Pipeline: ... -class Pipeline(TimeSeriesCommands, ClientPipeline): ... # type: ignore[misc] +class Pipeline(TimeSeriesCommands, ClientPipeline[Incomplete]): ... # type: ignore[misc]