From 90c02a69753b043d96ee28d4a34823f842f30e0d Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Wed, 18 May 2022 22:41:03 +0200 Subject: [PATCH 1/2] Introduce _typeshed.Maybe --- stdlib/_typeshed/__init__.pyi | 11 +++++++++++ stdlib/re.pyi | 6 +++--- stdlib/sqlite3/dbapi2.pyi | 5 ++--- stdlib/tkinter/__init__.pyi | 6 +++--- stdlib/tkinter/ttk.pyi | 3 ++- stdlib/types.pyi | 4 ++-- stdlib/typing.pyi | 22 ++++++++-------------- 7 files changed, 31 insertions(+), 26 deletions(-) diff --git a/stdlib/_typeshed/__init__.pyi b/stdlib/_typeshed/__init__.pyi index d5e0c691e8c0..7cc6b079f57d 100644 --- a/stdlib/_typeshed/__init__.pyi +++ b/stdlib/_typeshed/__init__.pyi @@ -33,6 +33,17 @@ Self = TypeVar("Self") # noqa: Y001 # "Incomplete | None" instead of "Any | None". Incomplete: TypeAlias = Any +# Annotation for return types that can return a value or None, but where the +# caller should not be forced to check for None. For example: +# +# if m := re.match(r"(a)(b)?"): +# m.group(1) # will never be None +# m.group(2) # can be None +# +# match's return type is annotated as "Maybe[str]". In cases, where the caller +# should check for None, use "_T | None". +Maybe: TypeAlias = _T | Any + # stable class IdentityFunction(Protocol): def __call__(self, __x: _T) -> _T: ... diff --git a/stdlib/re.pyi b/stdlib/re.pyi index 68684035593d..e15f7bf1f9f9 100644 --- a/stdlib/re.pyi +++ b/stdlib/re.pyi @@ -1,7 +1,7 @@ import enum import sre_compile import sys -from _typeshed import ReadableBuffer +from _typeshed import Maybe, ReadableBuffer from collections.abc import Callable, Iterator from sre_constants import error as error from typing import Any, AnyStr, overload @@ -181,11 +181,11 @@ def fullmatch(pattern: str | Pattern[str], string: str, flags: _FlagsType = ...) @overload def fullmatch(pattern: bytes | Pattern[bytes], string: ReadableBuffer, flags: _FlagsType = ...) -> Match[bytes] | None: ... @overload -def split(pattern: str | Pattern[str], string: str, maxsplit: int = ..., flags: _FlagsType = ...) -> list[str | Any]: ... +def split(pattern: str | Pattern[str], string: str, maxsplit: int = ..., flags: _FlagsType = ...) -> list[Maybe[str]]: ... @overload def split( pattern: bytes | Pattern[bytes], string: ReadableBuffer, maxsplit: int = ..., flags: _FlagsType = ... -) -> list[bytes | Any]: ... +) -> list[Maybe[bytes]]: ... @overload def findall(pattern: str | Pattern[str], string: str, flags: _FlagsType = ...) -> list[Any]: ... @overload diff --git a/stdlib/sqlite3/dbapi2.pyi b/stdlib/sqlite3/dbapi2.pyi index fa7d29c38bce..6d17701a59a6 100644 --- a/stdlib/sqlite3/dbapi2.pyi +++ b/stdlib/sqlite3/dbapi2.pyi @@ -1,6 +1,6 @@ import sqlite3 import sys -from _typeshed import ReadableBuffer, Self, StrOrBytesPath, SupportsLenAndGetItem +from _typeshed import Maybe, ReadableBuffer, Self, StrOrBytesPath, SupportsLenAndGetItem from collections.abc import Callable, Generator, Iterable, Iterator, Mapping from datetime import date, datetime, time from types import TracebackType @@ -374,9 +374,8 @@ class Cursor(Iterator[Any]): arraysize: int @property def connection(self) -> Connection: ... - # May be None, but using | Any instead to avoid slightly annoying false positives. @property - def description(self) -> tuple[tuple[str, None, None, None, None, None, None], ...] | Any: ... + def description(self) -> Maybe[tuple[tuple[str, None, None, None, None, None, None], ...]]: ... @property def lastrowid(self) -> int | None: ... row_factory: Callable[[Cursor, Row[Any]], object] | None diff --git a/stdlib/tkinter/__init__.pyi b/stdlib/tkinter/__init__.pyi index 582503971e15..4e5fd963e4f5 100644 --- a/stdlib/tkinter/__init__.pyi +++ b/stdlib/tkinter/__init__.pyi @@ -1,6 +1,6 @@ import _tkinter import sys -from _typeshed import StrOrBytesPath +from _typeshed import Maybe, StrOrBytesPath from collections.abc import Callable, Mapping, Sequence from enum import Enum from tkinter.constants import * @@ -518,7 +518,7 @@ class Misc: pad: _ScreenUnits = ..., uniform: str = ..., weight: int = ..., - ) -> _GridIndexInfo | Any: ... # can be None but annoying to check + ) -> Maybe[_GridIndexInfo]: ... def grid_rowconfigure( self, index: _GridIndex, @@ -528,7 +528,7 @@ class Misc: pad: _ScreenUnits = ..., uniform: str = ..., weight: int = ..., - ) -> _GridIndexInfo | Any: ... # can be None but annoying to check + ) -> Maybe[_GridIndexInfo]: ... columnconfigure = grid_columnconfigure rowconfigure = grid_rowconfigure def grid_location(self, x: _ScreenUnits, y: _ScreenUnits) -> tuple[int, int]: ... diff --git a/stdlib/tkinter/ttk.pyi b/stdlib/tkinter/ttk.pyi index 7ca8f9b800ce..321819284710 100644 --- a/stdlib/tkinter/ttk.pyi +++ b/stdlib/tkinter/ttk.pyi @@ -1,6 +1,7 @@ import _tkinter import sys import tkinter +from _typeshed import Maybe from collections.abc import Callable from tkinter.font import _FontDescription from typing import Any, overload @@ -1166,7 +1167,7 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): background: tkinter._Color = ..., font: _FontDescription = ..., image: tkinter._ImageSpec = ..., - ) -> _TreeviewTagDict | Any: ... # can be None but annoying to check + ) -> Maybe[_TreeviewTagDict]: ... @overload def tag_has(self, tagname: str, item: None = ...) -> tuple[str, ...]: ... @overload diff --git a/stdlib/types.pyi b/stdlib/types.pyi index 76d7bfcd0fde..bf5f33e241f5 100644 --- a/stdlib/types.pyi +++ b/stdlib/types.pyi @@ -1,5 +1,5 @@ import sys -from _typeshed import SupportsKeysAndGetItem +from _typeshed import Maybe, SupportsKeysAndGetItem from collections.abc import ( AsyncGenerator, Awaitable, @@ -571,7 +571,7 @@ class FrameType: # but you should probably file a bug report with CPython if you encounter it being None in the wild. # An `int | None` annotation here causes too many false-positive errors. @property - def f_lineno(self) -> int | Any: ... + def f_lineno(self) -> Maybe[int]: ... @property def f_locals(self) -> dict[str, Any]: ... f_trace: Callable[[FrameType, str, Any], Any] | None diff --git a/stdlib/typing.pyi b/stdlib/typing.pyi index 37ea55c9f2ef..5b4c0e544516 100644 --- a/stdlib/typing.pyi +++ b/stdlib/typing.pyi @@ -1,6 +1,6 @@ import collections # Needed by aliases like DefaultDict, see mypy issue 2986 import sys -from _typeshed import ReadableBuffer, Self as TypeshedSelf, SupportsKeysAndGetItem +from _typeshed import Maybe, ReadableBuffer, Self as TypeshedSelf, SupportsKeysAndGetItem from abc import ABCMeta, abstractmethod from types import BuiltinFunctionType, CodeType, FrameType, FunctionType, MethodType, ModuleType, TracebackType from typing_extensions import Literal as _Literal, ParamSpec as _ParamSpec, final as _final @@ -1084,23 +1084,18 @@ class Match(Generic[AnyStr]): def expand(self: Match[str], template: str) -> str: ... @overload def expand(self: Match[bytes], template: ReadableBuffer) -> bytes: ... - # group() returns "AnyStr" or "AnyStr | None", depending on the pattern. @overload def group(self, __group: _Literal[0] = ...) -> AnyStr: ... @overload - def group(self, __group: str | int) -> AnyStr | Any: ... + def group(self, __group: str | int) -> Maybe[AnyStr]: ... @overload - def group(self, __group1: str | int, __group2: str | int, *groups: str | int) -> tuple[AnyStr | Any, ...]: ... - # Each item of groups()'s return tuple is either "AnyStr" or - # "AnyStr | None", depending on the pattern. + def group(self, __group1: str | int, __group2: str | int, *groups: str | int) -> tuple[Maybe[AnyStr], ...]: ... @overload - def groups(self) -> tuple[AnyStr | Any, ...]: ... + def groups(self) -> tuple[Maybe[AnyStr], ...]: ... @overload def groups(self, default: _T) -> tuple[AnyStr | _T, ...]: ... - # Each value in groupdict()'s return dict is either "AnyStr" or - # "AnyStr | None", depending on the pattern. @overload - def groupdict(self) -> dict[str, AnyStr | Any]: ... + def groupdict(self) -> dict[str, Maybe[AnyStr]]: ... @overload def groupdict(self, default: _T) -> dict[str, AnyStr | _T]: ... def start(self, __group: int | str = ...) -> int: ... @@ -1108,11 +1103,10 @@ class Match(Generic[AnyStr]): def span(self, __group: int | str = ...) -> tuple[int, int]: ... @property def regs(self) -> tuple[tuple[int, int], ...]: ... # undocumented - # __getitem__() returns "AnyStr" or "AnyStr | None", depending on the pattern. @overload def __getitem__(self, __key: _Literal[0]) -> AnyStr: ... @overload - def __getitem__(self, __key: int | str) -> AnyStr | Any: ... + def __getitem__(self, __key: int | str) -> Maybe[AnyStr]: ... def __copy__(self) -> Match[AnyStr]: ... def __deepcopy__(self, __memo: Any) -> Match[AnyStr]: ... if sys.version_info >= (3, 9): @@ -1141,9 +1135,9 @@ class Pattern(Generic[AnyStr]): @overload def fullmatch(self: Pattern[bytes], string: ReadableBuffer, pos: int = ..., endpos: int = ...) -> Match[bytes] | None: ... @overload - def split(self: Pattern[str], string: str, maxsplit: int = ...) -> list[str | Any]: ... + def split(self: Pattern[str], string: str, maxsplit: int = ...) -> list[Maybe[str]]: ... @overload - def split(self: Pattern[bytes], string: ReadableBuffer, maxsplit: int = ...) -> list[bytes | Any]: ... + def split(self: Pattern[bytes], string: ReadableBuffer, maxsplit: int = ...) -> list[Maybe[bytes]]: ... # return type depends on the number of groups in the pattern @overload def findall(self: Pattern[str], string: str, pos: int = ..., endpos: int = ...) -> list[Any]: ... From 716c38514835023220198fa9aa5aed7f3593bbdb Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Wed, 18 May 2022 23:07:18 +0200 Subject: [PATCH 2/2] Fix example Co-authored-by: Akuli --- stdlib/_typeshed/__init__.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/_typeshed/__init__.pyi b/stdlib/_typeshed/__init__.pyi index 7cc6b079f57d..8ce27df83645 100644 --- a/stdlib/_typeshed/__init__.pyi +++ b/stdlib/_typeshed/__init__.pyi @@ -36,7 +36,7 @@ Incomplete: TypeAlias = Any # Annotation for return types that can return a value or None, but where the # caller should not be forced to check for None. For example: # -# if m := re.match(r"(a)(b)?"): +# if m := re.match(r"(a)(b)?", string): # m.group(1) # will never be None # m.group(2) # can be None #