From d3260adc22968cde19f246bdafec423bec64b55d Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 20 Dec 2023 16:18:42 +0100
Subject: [PATCH 1/8] bump pyright, introducing warnings for tons of missing
docstrings
---
test-requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test-requirements.txt b/test-requirements.txt
index 093beb4102..723108abff 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -106,7 +106,7 @@ pyopenssl==23.3.0
# via -r test-requirements.in
pyproject-hooks==1.0.0
# via build
-pyright==1.1.339
+pyright==1.1.342
# via -r test-requirements.in
pytest==7.4.3
# via -r test-requirements.in
From 5acf76f48a450d56e7f55bfd4467c5ace30bf7b6 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 21 Dec 2023 13:44:48 +0100
Subject: [PATCH 2/8] WIP pyright filter script. calling it from check.sh. Copy
docstrings to SocketType from stdlib
---
check.sh | 12 +-
pyproject.toml | 2 +
src/trio/_core/_run.py | 1 +
src/trio/_socket.py | 19 +++
src/trio/_tests/check_type_completeness.py | 136 +++++++++++++++++++++
5 files changed, 159 insertions(+), 11 deletions(-)
create mode 100755 src/trio/_tests/check_type_completeness.py
diff --git a/check.sh b/check.sh
index 3e07056dcd..0bc6bc8b78 100755
--- a/check.sh
+++ b/check.sh
@@ -96,18 +96,8 @@ fi
codespell || EXIT_STATUS=$?
-PYRIGHT=0
echo "::group::Pyright interface tests"
-pyright --verifytypes --ignoreexternal --pythonplatform=Linux --verifytypes=trio \
- || { echo "* Pyright --verifytypes (Linux) found errors." >> "$GITHUB_STEP_SUMMARY"; PYRIGHT=1; }
-pyright --verifytypes --ignoreexternal --pythonplatform=Darwin --verifytypes=trio \
- || { echo "* Pyright --verifytypes (Mac) found errors." >> "$GITHUB_STEP_SUMMARY"; PYRIGHT=1; }
-pyright --verifytypes --ignoreexternal --pythonplatform=Windows --verifytypes=trio \
- || { echo "* Pyright --verifytypes (Windows) found errors." >> "$GITHUB_STEP_SUMMARY"; PYRIGHT=1; }
-if [ $PYRIGHT -ne 0 ]; then
- echo "::error:: Pyright --verifytypes returned errors."
- EXIT_STATUS=1
-fi
+python src/trio/_tests/check_type_completeness.py || EXIT_STATUS=$?
pyright src/trio/_tests/type_tests || EXIT_STATUS=$?
pyright src/trio/_core/_tests/type_tests || EXIT_STATUS=$?
diff --git a/pyproject.toml b/pyproject.toml
index 48c97eccd2..641073d28c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -243,6 +243,8 @@ omit = [
"*/trio/_core/_generated_*",
# Type tests aren't intended to be run, just passed to type checkers.
"*/type_tests/*",
+ # Script used to check type completeness that isn't run in tests
+ "*/trio/_tests/check_type_completeness.py",
]
# The test suite spawns subprocesses to test some stuff, so make sure
# this doesn't corrupt the coverage files
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index 015065863e..d94d4c6329 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -162,6 +162,7 @@ def function_with_unique_name_xyzzy() -> NoReturn:
@attr.s(frozen=True, slots=True)
class SystemClock(Clock):
+ "aoeu"
# Add a large random offset to our clock to ensure that if people
# accidentally call time.perf_counter() directly or start comparing clocks
# between different runs, then they'll notice the bug quickly:
diff --git a/src/trio/_socket.py b/src/trio/_socket.py
index 1e530b14a1..008085b899 100644
--- a/src/trio/_socket.py
+++ b/src/trio/_socket.py
@@ -616,6 +616,7 @@ def proto(self) -> int:
@property
def did_shutdown_SHUT_WR(self) -> bool:
+ """Return True if the socket has been shut down with the SHUT_WR flag"""
raise NotImplementedError
def __repr__(self) -> str:
@@ -634,9 +635,11 @@ def shutdown(self, flag: int) -> None:
raise NotImplementedError
def is_readable(self) -> bool:
+ """Return True if the socket is readable. This is checked with `select.select` on Windows, otherwise `select.poll`."""
raise NotImplementedError
async def wait_writable(self) -> None:
+ """Convenience method that calls trio.lowlevel.wait_writable for the object."""
raise NotImplementedError
async def accept(self) -> tuple[SocketType, AddressFormat]:
@@ -726,6 +729,22 @@ async def sendmsg(
raise NotImplementedError
+# copy docstrings from socket.SocketType
+# this doesn't satisfy pyright for platform-specific objects though
+for name, obj in SocketType.__dict__.items():
+ # skip dunders and already defined docstrings
+ if name.startswith("__") or obj.__doc__:
+ continue
+ # try both socket.socket and socket.SocketType
+ for stdlib_type in _stdlib_socket.socket, _stdlib_socket.SocketType:
+ stdlib_obj = getattr(stdlib_type, name, None)
+ if stdlib_obj and stdlib_obj.__doc__:
+ break
+ else:
+ continue
+ obj.__doc__ = stdlib_obj.__doc__
+
+
class _SocketType(SocketType):
def __init__(self, sock: _stdlib_socket.socket):
if type(sock) is not _stdlib_socket.socket:
diff --git a/src/trio/_tests/check_type_completeness.py b/src/trio/_tests/check_type_completeness.py
new file mode 100755
index 0000000000..6faf341c5d
--- /dev/null
+++ b/src/trio/_tests/check_type_completeness.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+from __future__ import annotations
+
+# this file is not run as part of the tests, instead it's run standalone from check.sh
+import argparse
+import json
+import subprocess
+import sys
+from pathlib import Path
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
+# the result file is not marked in MANIFEST.in so it's not included in the package
+failed = False
+
+printed_diagnostics: set[str] = set()
+
+
+# TODO: consider checking manually without `--ignoreexternal`, and/or
+# removing it from the below call later on.
+def run_pyright(platform: str) -> subprocess.CompletedProcess[bytes]:
+ return subprocess.run(
+ [
+ "pyright",
+ # Specify a platform and version to keep imported modules consistent.
+ f"--pythonplatform={platform}",
+ "--pythonversion=3.8",
+ "--verifytypes=trio",
+ "--outputjson",
+ "--ignoreexternal",
+ ],
+ capture_output=True,
+ )
+
+
+def check_zero(key: str, current_dict: Mapping[str, float]) -> None:
+ global failed
+ if current_dict[key] != 0:
+ failed = True
+ print(f"ERROR: {key} is {current_dict[key]}")
+
+
+def check_type(args: argparse.Namespace, platform: str) -> int:
+ res = run_pyright(platform)
+ current_result = json.loads(res.stdout)
+
+ if res.stderr:
+ print(res.stderr)
+
+ if args.full_diagnostics_file:
+ with open(args.full_diagnostics_file, "w") as f:
+ json.dump(current_result, f, sort_keys=True, indent=4)
+
+ missingFunctionDocStringCount = current_result["typeCompleteness"][
+ "missingFunctionDocStringCount"
+ ]
+ # missingClassDocStringCount = current_result["typeCompleteness"][
+ # "missingClassDocStringCount"
+ # ]
+
+ for symbol in current_result["typeCompleteness"]["symbols"]:
+ diagnostics = symbol["diagnostics"]
+ if not diagnostics:
+ continue
+ for diagnostic in diagnostics:
+ if diagnostic["message"].startswith("No docstring found for"):
+ # check if it actually has a docstring at runtime
+ # this is rickety and incomplete - but works for the current
+ # missing docstrings
+ name_parts = symbol["name"].split(".")
+ if name_parts[1] == "_core": # noqa: SIM108
+ split_i = 2
+ else:
+ split_i = 1
+ module = sys.modules[".".join(name_parts[:split_i])]
+ obj = module
+ try:
+ for obj_name in name_parts[split_i:]:
+ obj = getattr(obj, obj_name)
+ except AttributeError as exc:
+ # asynciowrapper does funky getattr stuff
+ if "AsyncIOWrapper" in str(exc):
+ missingFunctionDocStringCount -= 1
+ continue
+ if obj.__doc__:
+ missingFunctionDocStringCount -= 1
+ continue
+
+ if diagnostic["message"] in printed_diagnostics:
+ continue
+ print(diagnostic["message"])
+ printed_diagnostics.add(diagnostic["message"])
+
+ continue
+
+ if missingFunctionDocStringCount > 0:
+ print(
+ f"ERROR: missingFunctionDocStringCount is {missingFunctionDocStringCount}"
+ )
+ failed = True
+
+ for key in "errorCount", "warningCount", "informationCount":
+ check_zero(key, current_result["summary"])
+
+ for key in (
+ # "missingFunctionDocStringCount",
+ "missingClassDocStringCount",
+ "missingDefaultParamCount",
+ # "completenessScore",
+ ):
+ check_zero(key, current_result["typeCompleteness"])
+
+ for key in ("withUnknownType", "withAmbiguousType"):
+ check_zero(key, current_result["typeCompleteness"]["exportedSymbolCounts"])
+
+ return int(failed)
+
+
+def main(args: argparse.Namespace) -> int:
+ res = 0
+ for platform in "Linux", "Windows", "Darwin":
+ print("*" * 20, f"\nChecking {platform}...")
+ res += check_type(args, platform)
+ print("*" * 20)
+ return res
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument("--overwrite-file", action="store_true", default=False)
+parser.add_argument("--full-diagnostics-file", type=Path, default=None)
+args = parser.parse_args()
+
+assert __name__ == "__main__", "This script should be run standalone"
+sys.exit(main(args))
From d9c5adae843c3ab2680c8e35ac75ab6eac8587d9 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 20 Feb 2024 15:22:15 +0100
Subject: [PATCH 3/8] wip
---
src/trio/_tests/check_type_completeness.py | 127 ++++++++++++---------
1 file changed, 70 insertions(+), 57 deletions(-)
diff --git a/src/trio/_tests/check_type_completeness.py b/src/trio/_tests/check_type_completeness.py
index 6faf341c5d..273e7db897 100755
--- a/src/trio/_tests/check_type_completeness.py
+++ b/src/trio/_tests/check_type_completeness.py
@@ -7,13 +7,9 @@
import subprocess
import sys
from pathlib import Path
-from typing import TYPE_CHECKING
-if TYPE_CHECKING:
- from collections.abc import Mapping
-
-# the result file is not marked in MANIFEST.in so it's not included in the package
-failed = False
+import trio
+import trio.testing
printed_diagnostics: set[str] = set()
@@ -35,14 +31,35 @@ def run_pyright(platform: str) -> subprocess.CompletedProcess[bytes]:
)
-def check_zero(key: str, current_dict: Mapping[str, float]) -> None:
- global failed
- if current_dict[key] != 0:
- failed = True
- print(f"ERROR: {key} is {current_dict[key]}")
+def has_docstring_at_runtime(name: str) -> bool:
+ assert trio.testing
+ # this is rickety, but works for all current symbols
+ name_parts = name.split(".")
+ split_i = 1
+ if name_parts[1] == "tests":
+ return True
+ if name_parts[1] in ("_core", "testing"): # noqa: SIM108
+ split_i = 2
+ else:
+ split_i = 1
+ module = sys.modules[".".join(name_parts[:split_i])]
+ obj = module
+ try:
+ for obj_name in name_parts[split_i:]:
+ obj = getattr(obj, obj_name)
+ except AttributeError as exc:
+ # asynciowrapper does funky getattr stuff
+ if "AsyncIOWrapper" in str(exc):
+ return True
+ # raise
+ print(exc)
+ return bool(obj.__doc__)
def check_type(args: argparse.Namespace, platform: str) -> int:
+ # convince isort we use the trio import
+ assert trio
+
res = run_pyright(platform)
current_result = json.loads(res.stdout)
@@ -53,40 +70,51 @@ def check_type(args: argparse.Namespace, platform: str) -> int:
with open(args.full_diagnostics_file, "w") as f:
json.dump(current_result, f, sort_keys=True, indent=4)
- missingFunctionDocStringCount = current_result["typeCompleteness"][
- "missingFunctionDocStringCount"
- ]
- # missingClassDocStringCount = current_result["typeCompleteness"][
- # "missingClassDocStringCount"
- # ]
+ counts = {}
+ for where, key in (
+ (("typeCompleteness",), "missingFunctionDocStringCount"),
+ (("typeCompleteness",), "missingClassDocStringCount"),
+ (("typeCompleteness",), "missingDefaultParamCount"),
+ (("summary",), "errorCount"),
+ (("summary",), "warningCount"),
+ (("summary",), "informationCount"),
+ (("typeCompleteness", "exportedSymbolCounts"), "withUnknownType"),
+ (("typeCompleteness", "exportedSymbolCounts"), "withAmbiguousType"),
+ ):
+ curr_dict = current_result
+ for subdict in where:
+ curr_dict = curr_dict[subdict]
+
+ counts[key] = curr_dict[key]
for symbol in current_result["typeCompleteness"]["symbols"]:
+ category = symbol["category"]
diagnostics = symbol["diagnostics"]
+ name = symbol["name"]
if not diagnostics:
+ if (
+ category
+ not in ("variable", "symbol", "type alias", "constant", "module")
+ and not name.endswith("__")
+ and not has_docstring_at_runtime(symbol["name"])
+ ):
+ print(
+ "not warned by pyright, but missing docstring:",
+ symbol["name"],
+ symbol["category"],
+ )
continue
for diagnostic in diagnostics:
- if diagnostic["message"].startswith("No docstring found for"):
- # check if it actually has a docstring at runtime
- # this is rickety and incomplete - but works for the current
- # missing docstrings
- name_parts = symbol["name"].split(".")
- if name_parts[1] == "_core": # noqa: SIM108
- split_i = 2
+ if diagnostic["message"].startswith(
+ "No docstring found for"
+ ) and has_docstring_at_runtime(symbol["name"]):
+ if category in ("method", "function"):
+ counts["missingFunctionDocStringCount"] -= 1
+ elif category == "class":
+ counts["missingClassDocStringCount"] -= 1
else:
- split_i = 1
- module = sys.modules[".".join(name_parts[:split_i])]
- obj = module
- try:
- for obj_name in name_parts[split_i:]:
- obj = getattr(obj, obj_name)
- except AttributeError as exc:
- # asynciowrapper does funky getattr stuff
- if "AsyncIOWrapper" in str(exc):
- missingFunctionDocStringCount -= 1
- continue
- if obj.__doc__:
- missingFunctionDocStringCount -= 1
- continue
+ raise AssertionError("This category shouldn't be possible here")
+ continue
if diagnostic["message"] in printed_diagnostics:
continue
@@ -95,25 +123,10 @@ def check_type(args: argparse.Namespace, platform: str) -> int:
continue
- if missingFunctionDocStringCount > 0:
- print(
- f"ERROR: missingFunctionDocStringCount is {missingFunctionDocStringCount}"
- )
- failed = True
-
- for key in "errorCount", "warningCount", "informationCount":
- check_zero(key, current_result["summary"])
-
- for key in (
- # "missingFunctionDocStringCount",
- "missingClassDocStringCount",
- "missingDefaultParamCount",
- # "completenessScore",
- ):
- check_zero(key, current_result["typeCompleteness"])
-
- for key in ("withUnknownType", "withAmbiguousType"):
- check_zero(key, current_result["typeCompleteness"]["exportedSymbolCounts"])
+ for name, val in counts.items():
+ if val > 0:
+ print(f"ERROR: {name} is {val}")
+ failed = True
return int(failed)
From 825e7991f9c2a5513e1ce89d58e9cc870906ede4 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 21 Feb 2024 12:00:31 +0100
Subject: [PATCH 4/8] ugly implementation
---
src/trio/_core/_run.py | 1 -
src/trio/_socket.py | 3 +-
src/trio/_tests/_check_type_completeness.json | 156 ++++++++++++++++++
src/trio/_tests/check_type_completeness.py | 156 ++++++++++++------
4 files changed, 266 insertions(+), 50 deletions(-)
create mode 100644 src/trio/_tests/_check_type_completeness.json
diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py
index 83ccf41254..c5d6b65712 100644
--- a/src/trio/_core/_run.py
+++ b/src/trio/_core/_run.py
@@ -157,7 +157,6 @@ def function_with_unique_name_xyzzy() -> NoReturn:
@attrs.frozen
class SystemClock(Clock):
- "aoeu"
# Add a large random offset to our clock to ensure that if people
# accidentally call time.perf_counter() directly or start comparing clocks
# between different runs, then they'll notice the bug quickly:
diff --git a/src/trio/_socket.py b/src/trio/_socket.py
index 65ef3f77ec..14b0dc7cb5 100644
--- a/src/trio/_socket.py
+++ b/src/trio/_socket.py
@@ -727,8 +727,7 @@ async def sendmsg(
raise NotImplementedError
-# copy docstrings from socket.SocketType
-# this doesn't satisfy pyright for platform-specific objects though
+# copy docstrings from socket.SocketType / socket.socket
for name, obj in SocketType.__dict__.items():
# skip dunders and already defined docstrings
if name.startswith("__") or obj.__doc__:
diff --git a/src/trio/_tests/_check_type_completeness.json b/src/trio/_tests/_check_type_completeness.json
new file mode 100644
index 0000000000..2e2d7b6ef7
--- /dev/null
+++ b/src/trio/_tests/_check_type_completeness.json
@@ -0,0 +1,156 @@
+{
+ "Darwin": [
+ "No docstring found for class \"trio.MemoryReceiveChannel\"",
+ "No docstring found for class \"trio._channel.MemoryReceiveChannel\"",
+ "No docstring found for function \"trio._channel.MemoryReceiveChannel.statistics\"",
+ "No docstring found for class \"trio._channel.MemoryChannelStats\"",
+ "No docstring found for function \"trio._channel.MemoryReceiveChannel.aclose\"",
+ "No docstring found for class \"trio.MemorySendChannel\"",
+ "No docstring found for class \"trio._channel.MemorySendChannel\"",
+ "No docstring found for function \"trio._channel.MemorySendChannel.statistics\"",
+ "No docstring found for function \"trio._channel.MemorySendChannel.aclose\"",
+ "No docstring found for class \"trio._core._run.Task\"",
+ "No docstring found for class \"trio._socket.SocketType\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.send_all\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.wait_send_all_might_not_block\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.send_eof\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.receive_some\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.aclose\"",
+ "No docstring found for function \"trio._path.Path.absolute\"",
+ "No docstring found for class \"trio._path.AsyncAutoWrapperType\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_forwards\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_wraps\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_magic\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_iter\"",
+ "No docstring found for function \"trio._subprocess.HasFileno.fileno\"",
+ "No docstring found for class \"trio._sync.AsyncContextManagerMixin\"",
+ "No docstring found for function \"trio._sync._HasAcquireRelease.acquire\"",
+ "No docstring found for function \"trio._sync._HasAcquireRelease.release\"",
+ "No docstring found for class \"trio._sync._LockImpl\"",
+ "No docstring found for class \"trio._core._local._NoValue\"",
+ "No docstring found for class \"trio._core._local.RunVarToken\"",
+ "No docstring found for class \"trio.lowlevel.RunVarToken\"",
+ "No docstring found for class \"trio.lowlevel.Task\"",
+ "No docstring found for class \"trio._core._ki.KIProtectionSignature\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.send_all\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.wait_send_all_might_not_block\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.receive_some\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.close\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.aclose\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.fileno\"",
+ "No docstring found for class \"trio.socket.SocketType\"",
+ "No docstring found for class \"trio.socket.gaierror\"",
+ "No docstring found for class \"trio.socket.herror\"",
+ "No docstring found for function \"trio._core._mock_clock.MockClock.start_clock\"",
+ "No docstring found for function \"trio._core._mock_clock.MockClock.current_time\"",
+ "No docstring found for function \"trio._core._mock_clock.MockClock.deadline_to_sleep_time\"",
+ "trio.testing._raises_group.RaisesGroup: Type of metaclass unknown",
+ "trio.testing._raises_group.RaisesGroup: Type of base class \"contextlib.AbstractContextManager\" is partially unknown\n\u00a0\u00a0Type argument 1 for class \"AbstractContextManager\" has partially unknown type",
+ "trio.testing._raises_group.RaisesGroup: Type of base class unknown",
+ "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.exconly\"",
+ "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.errisinstance\"",
+ "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.getrepr\"",
+ "No docstring found for function \"trio.testing._raises_group.RaisesGroup.expected_type\""
+ ],
+ "Linux": [
+ "No docstring found for class \"trio.MemoryReceiveChannel\"",
+ "No docstring found for class \"trio._channel.MemoryReceiveChannel\"",
+ "No docstring found for function \"trio._channel.MemoryReceiveChannel.statistics\"",
+ "No docstring found for class \"trio._channel.MemoryChannelStats\"",
+ "No docstring found for function \"trio._channel.MemoryReceiveChannel.aclose\"",
+ "No docstring found for class \"trio.MemorySendChannel\"",
+ "No docstring found for class \"trio._channel.MemorySendChannel\"",
+ "No docstring found for function \"trio._channel.MemorySendChannel.statistics\"",
+ "No docstring found for function \"trio._channel.MemorySendChannel.aclose\"",
+ "No docstring found for class \"trio._core._run.Task\"",
+ "No docstring found for class \"trio._socket.SocketType\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.send_all\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.wait_send_all_might_not_block\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.send_eof\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.receive_some\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.aclose\"",
+ "No docstring found for function \"trio._path.Path.absolute\"",
+ "No docstring found for class \"trio._path.AsyncAutoWrapperType\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_forwards\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_wraps\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_magic\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_iter\"",
+ "No docstring found for function \"trio._subprocess.HasFileno.fileno\"",
+ "No docstring found for class \"trio._sync.AsyncContextManagerMixin\"",
+ "No docstring found for function \"trio._sync._HasAcquireRelease.acquire\"",
+ "No docstring found for function \"trio._sync._HasAcquireRelease.release\"",
+ "No docstring found for class \"trio._sync._LockImpl\"",
+ "No docstring found for class \"trio._core._io_epoll._EpollStatistics\"",
+ "No docstring found for class \"trio._core._local._NoValue\"",
+ "No docstring found for class \"trio._core._local.RunVarToken\"",
+ "No docstring found for class \"trio.lowlevel.RunVarToken\"",
+ "No docstring found for class \"trio.lowlevel.Task\"",
+ "No docstring found for class \"trio._core._ki.KIProtectionSignature\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.send_all\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.wait_send_all_might_not_block\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.receive_some\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.close\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.aclose\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.fileno\"",
+ "No docstring found for class \"trio.socket.SocketType\"",
+ "No docstring found for class \"trio.socket.gaierror\"",
+ "No docstring found for class \"trio.socket.herror\"",
+ "No docstring found for function \"trio._core._mock_clock.MockClock.start_clock\"",
+ "No docstring found for function \"trio._core._mock_clock.MockClock.current_time\"",
+ "No docstring found for function \"trio._core._mock_clock.MockClock.deadline_to_sleep_time\"",
+ "trio.testing._raises_group.RaisesGroup: Type of metaclass unknown",
+ "trio.testing._raises_group.RaisesGroup: Type of base class \"contextlib.AbstractContextManager\" is partially unknown\n\u00a0\u00a0Type argument 1 for class \"AbstractContextManager\" has partially unknown type",
+ "trio.testing._raises_group.RaisesGroup: Type of base class unknown",
+ "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.exconly\"",
+ "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.errisinstance\"",
+ "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.getrepr\"",
+ "No docstring found for function \"trio.testing._raises_group.RaisesGroup.expected_type\""
+ ],
+ "Windows": [
+ "No docstring found for class \"trio.MemoryReceiveChannel\"",
+ "No docstring found for class \"trio._channel.MemoryReceiveChannel\"",
+ "No docstring found for function \"trio._channel.MemoryReceiveChannel.statistics\"",
+ "No docstring found for class \"trio._channel.MemoryChannelStats\"",
+ "No docstring found for function \"trio._channel.MemoryReceiveChannel.aclose\"",
+ "No docstring found for class \"trio.MemorySendChannel\"",
+ "No docstring found for class \"trio._channel.MemorySendChannel\"",
+ "No docstring found for function \"trio._channel.MemorySendChannel.statistics\"",
+ "No docstring found for function \"trio._channel.MemorySendChannel.aclose\"",
+ "No docstring found for class \"trio._core._run.Task\"",
+ "No docstring found for class \"trio._socket.SocketType\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.send_all\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.wait_send_all_might_not_block\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.send_eof\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.receive_some\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.aclose\"",
+ "No docstring found for function \"trio._path.Path.absolute\"",
+ "No docstring found for class \"trio._path.AsyncAutoWrapperType\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_forwards\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_wraps\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_magic\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_iter\"",
+ "No docstring found for function \"trio._subprocess.HasFileno.fileno\"",
+ "No docstring found for class \"trio._sync.AsyncContextManagerMixin\"",
+ "No docstring found for function \"trio._sync._HasAcquireRelease.acquire\"",
+ "No docstring found for function \"trio._sync._HasAcquireRelease.release\"",
+ "No docstring found for class \"trio._sync._LockImpl\"",
+ "No docstring found for class \"trio._core._local._NoValue\"",
+ "No docstring found for class \"trio._core._local.RunVarToken\"",
+ "No docstring found for class \"trio.lowlevel.RunVarToken\"",
+ "No docstring found for class \"trio.lowlevel.Task\"",
+ "No docstring found for class \"trio._core._ki.KIProtectionSignature\"",
+ "No docstring found for class \"trio.socket.SocketType\"",
+ "No docstring found for class \"trio.socket.gaierror\"",
+ "No docstring found for class \"trio.socket.herror\"",
+ "No docstring found for function \"trio._core._mock_clock.MockClock.start_clock\"",
+ "No docstring found for function \"trio._core._mock_clock.MockClock.current_time\"",
+ "No docstring found for function \"trio._core._mock_clock.MockClock.deadline_to_sleep_time\"",
+ "trio.testing._raises_group.RaisesGroup: Type of metaclass unknown",
+ "trio.testing._raises_group.RaisesGroup: Type of base class \"contextlib.AbstractContextManager\" is partially unknown\n\u00a0\u00a0Type argument 1 for class \"AbstractContextManager\" has partially unknown type",
+ "trio.testing._raises_group.RaisesGroup: Type of base class unknown",
+ "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.exconly\"",
+ "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.errisinstance\"",
+ "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.getrepr\"",
+ "No docstring found for function \"trio.testing._raises_group.RaisesGroup.expected_type\""
+ ]
+}
diff --git a/src/trio/_tests/check_type_completeness.py b/src/trio/_tests/check_type_completeness.py
index 273e7db897..4a45fc63a4 100755
--- a/src/trio/_tests/check_type_completeness.py
+++ b/src/trio/_tests/check_type_completeness.py
@@ -32,8 +32,14 @@ def run_pyright(platform: str) -> subprocess.CompletedProcess[bytes]:
def has_docstring_at_runtime(name: str) -> bool:
+ """Pyright gives us an object identifier of xx.yy.zz
+ This function tries to decompose that into its constituent parts, such that we
+ can resolve it, in order to check whether it has a `__doc__` at runtime and
+ verifytypes misses it because we're doing overly fancy stuff.
+ """
assert trio.testing
- # this is rickety, but works for all current symbols
+
+ # figure out what part of the name is the module, so we can "import" it
name_parts = name.split(".")
split_i = 1
if name_parts[1] == "tests":
@@ -43,49 +49,69 @@ def has_docstring_at_runtime(name: str) -> bool:
else:
split_i = 1
module = sys.modules[".".join(name_parts[:split_i])]
+
+ # traverse down the remaining identifiers with getattr
obj = module
try:
for obj_name in name_parts[split_i:]:
obj = getattr(obj, obj_name)
except AttributeError as exc:
# asynciowrapper does funky getattr stuff
- if "AsyncIOWrapper" in str(exc):
+ if "AsyncIOWrapper" in str(exc) or name in (
+ # Symbols not existing on all platforms, so we can't dynamically inspect them.
+ # Manually confirmed to have docstrings but pyright doesn't see them due to
+ # export shenanigans.
+ # darwin
+ "trio.lowlevel.current_kqueue",
+ "trio.lowlevel.monitor_kevent",
+ "trio.lowlevel.wait_kevent",
+ "trio._core._io_kqueue._KqueueStatistics",
+ # windows
+ "trio._socket.SocketType.share",
+ "trio._core._io_windows._WindowsStatistics",
+ "trio._core._windows_cffi.Handle",
+ "trio.lowlevel.current_iocp",
+ "trio.lowlevel.monitor_completion_key",
+ "trio.lowlevel.readinto_overlapped",
+ "trio.lowlevel.register_with_iocp",
+ "trio.lowlevel.wait_overlapped",
+ "trio.lowlevel.write_overlapped",
+ "trio.lowlevel.WaitForSingleObject",
+ "trio.socket.fromshare",
+ # these are erroring on all platforms
+ "trio._highlevel_generic.StapledStream.send_stream",
+ "trio._highlevel_generic.StapledStream.receive_stream",
+ "trio._ssl.SSLStream.transport_stream",
+ "trio._file_io._HasFileNo",
+ "trio._file_io._HasFileNo.fileno",
+ ):
return True
- # raise
- print(exc)
+
+ else:
+ print(
+ f"Pyright sees {name} at runtime, but unable to getattr({obj.__name__}, {obj_name})."
+ )
return bool(obj.__doc__)
-def check_type(args: argparse.Namespace, platform: str) -> int:
+def check_type(
+ platform: str, full_diagnostics_file: Path | None, expected_errors: list[object]
+) -> list[object]:
# convince isort we use the trio import
assert trio
+ # run pyright, load output into json
res = run_pyright(platform)
current_result = json.loads(res.stdout)
if res.stderr:
print(res.stderr)
- if args.full_diagnostics_file:
- with open(args.full_diagnostics_file, "w") as f:
+ if full_diagnostics_file:
+ with open(full_diagnostics_file, "a") as f:
json.dump(current_result, f, sort_keys=True, indent=4)
- counts = {}
- for where, key in (
- (("typeCompleteness",), "missingFunctionDocStringCount"),
- (("typeCompleteness",), "missingClassDocStringCount"),
- (("typeCompleteness",), "missingDefaultParamCount"),
- (("summary",), "errorCount"),
- (("summary",), "warningCount"),
- (("summary",), "informationCount"),
- (("typeCompleteness", "exportedSymbolCounts"), "withUnknownType"),
- (("typeCompleteness", "exportedSymbolCounts"), "withAmbiguousType"),
- ):
- curr_dict = current_result
- for subdict in where:
- curr_dict = curr_dict[subdict]
-
- counts[key] = curr_dict[key]
+ errors = []
for symbol in current_result["typeCompleteness"]["symbols"]:
category = symbol["category"]
@@ -105,39 +131,75 @@ def check_type(args: argparse.Namespace, platform: str) -> int:
)
continue
for diagnostic in diagnostics:
- if diagnostic["message"].startswith(
- "No docstring found for"
- ) and has_docstring_at_runtime(symbol["name"]):
- if category in ("method", "function"):
- counts["missingFunctionDocStringCount"] -= 1
- elif category == "class":
- counts["missingClassDocStringCount"] -= 1
- else:
- raise AssertionError("This category shouldn't be possible here")
- continue
-
- if diagnostic["message"] in printed_diagnostics:
- continue
- print(diagnostic["message"])
- printed_diagnostics.add(diagnostic["message"])
+ message = diagnostic["message"]
+ # ignore errors about missing docstrings if they're available at runtime
+ if message.startswith("No docstring found for"):
+ if has_docstring_at_runtime(symbol["name"]):
+ continue
+ else:
+ message = f"{name}: {message}"
+ # try:
+ # expected_errors.remove(diagnostic)
+ # # decrement count for object
+ # except ValueError:
+ if message not in expected_errors and message not in printed_diagnostics:
+ print(f"new error: {message}")
+ errors.append(message)
+ printed_diagnostics.add(message)
continue
- for name, val in counts.items():
- if val > 0:
- print(f"ERROR: {name} is {val}")
- failed = True
-
- return int(failed)
+ return errors
def main(args: argparse.Namespace) -> int:
- res = 0
+ if args.full_diagnostics_file:
+ full_diagnostics_file = Path(args.full_diagnostics_file)
+ full_diagnostics_file.write_text("")
+ else:
+ full_diagnostics_file = None
+
+ errors_by_platform_file = Path(__file__).parent / "_check_type_completeness.json"
+ if errors_by_platform_file.exists():
+ with open(errors_by_platform_file) as f:
+ errors_by_platform = json.load(f)
+ else:
+ errors_by_platform = {"Linux": [], "Windows": [], "Darwin": []}
+
+ changed = False
for platform in "Linux", "Windows", "Darwin":
print("*" * 20, f"\nChecking {platform}...")
- res += check_type(args, platform)
+ errors = check_type(
+ platform, full_diagnostics_file, errors_by_platform[platform]
+ )
+
+ new_errors = [e for e in errors if e not in errors_by_platform[platform]]
+ missing_errors = [e for e in errors_by_platform[platform] if e not in errors]
+
+ if new_errors:
+ print(
+ f"New errors introduced in `pyright --verifytypes`. Fix them, or ignore them by modifying {errors_by_platform_file}. The latter can be done by pre-commit CI bot."
+ )
+ # print(new_errors)
+ changed = True
+ if missing_errors:
+ print(
+ f"Congratulations, you have resolved existing errors! Please remove them from {errors_by_platform_file}, either manually or with the pre-commit CI bot."
+ )
+ # print(missing_errors)
+ changed = True
+
+ errors_by_platform[platform] = errors
print("*" * 20)
- return res
+
+ if changed and args.overwrite_file:
+ with open(errors_by_platform_file, "w") as f:
+ json.dump(errors_by_platform, f, indent=4, sort_keys=True)
+ # newline at end of file
+ f.write("\n")
+
+ # True -> 1 -> non-zero exit value -> error
+ return changed
parser = argparse.ArgumentParser()
From ecde0eecacb8558909f7d8c3278dfa058c9c7d1f Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 21 Feb 2024 12:41:30 +0100
Subject: [PATCH 5/8] move errors on all platforms into a separate category.
add comments and stuff. Remove logic for 'not warned by pyright but missing
docstring' cause that mostly shouldn't be a thing anymore
---
src/trio/_tests/_check_type_completeness.json | 89 +++----------------
src/trio/_tests/check_type_completeness.py | 69 +++++++-------
2 files changed, 51 insertions(+), 107 deletions(-)
diff --git a/src/trio/_tests/_check_type_completeness.json b/src/trio/_tests/_check_type_completeness.json
index 2e2d7b6ef7..8405b9bb1e 100644
--- a/src/trio/_tests/_check_type_completeness.json
+++ b/src/trio/_tests/_check_type_completeness.json
@@ -1,112 +1,47 @@
{
"Darwin": [
- "No docstring found for class \"trio.MemoryReceiveChannel\"",
- "No docstring found for class \"trio._channel.MemoryReceiveChannel\"",
- "No docstring found for function \"trio._channel.MemoryReceiveChannel.statistics\"",
- "No docstring found for class \"trio._channel.MemoryChannelStats\"",
- "No docstring found for function \"trio._channel.MemoryReceiveChannel.aclose\"",
- "No docstring found for class \"trio.MemorySendChannel\"",
- "No docstring found for class \"trio._channel.MemorySendChannel\"",
- "No docstring found for function \"trio._channel.MemorySendChannel.statistics\"",
- "No docstring found for function \"trio._channel.MemorySendChannel.aclose\"",
- "No docstring found for class \"trio._core._run.Task\"",
- "No docstring found for class \"trio._socket.SocketType\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.send_all\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.wait_send_all_might_not_block\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.send_eof\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.receive_some\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.aclose\"",
- "No docstring found for function \"trio._path.Path.absolute\"",
- "No docstring found for class \"trio._path.AsyncAutoWrapperType\"",
- "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_forwards\"",
- "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_wraps\"",
- "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_magic\"",
- "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_iter\"",
- "No docstring found for function \"trio._subprocess.HasFileno.fileno\"",
- "No docstring found for class \"trio._sync.AsyncContextManagerMixin\"",
- "No docstring found for function \"trio._sync._HasAcquireRelease.acquire\"",
- "No docstring found for function \"trio._sync._HasAcquireRelease.release\"",
- "No docstring found for class \"trio._sync._LockImpl\"",
- "No docstring found for class \"trio._core._local._NoValue\"",
- "No docstring found for class \"trio._core._local.RunVarToken\"",
- "No docstring found for class \"trio.lowlevel.RunVarToken\"",
- "No docstring found for class \"trio.lowlevel.Task\"",
- "No docstring found for class \"trio._core._ki.KIProtectionSignature\"",
"No docstring found for function \"trio._unix_pipes.FdStream.send_all\"",
"No docstring found for function \"trio._unix_pipes.FdStream.wait_send_all_might_not_block\"",
"No docstring found for function \"trio._unix_pipes.FdStream.receive_some\"",
"No docstring found for function \"trio._unix_pipes.FdStream.close\"",
"No docstring found for function \"trio._unix_pipes.FdStream.aclose\"",
- "No docstring found for function \"trio._unix_pipes.FdStream.fileno\"",
- "No docstring found for class \"trio.socket.SocketType\"",
- "No docstring found for class \"trio.socket.gaierror\"",
- "No docstring found for class \"trio.socket.herror\"",
- "No docstring found for function \"trio._core._mock_clock.MockClock.start_clock\"",
- "No docstring found for function \"trio._core._mock_clock.MockClock.current_time\"",
- "No docstring found for function \"trio._core._mock_clock.MockClock.deadline_to_sleep_time\"",
- "trio.testing._raises_group.RaisesGroup: Type of metaclass unknown",
- "trio.testing._raises_group.RaisesGroup: Type of base class \"contextlib.AbstractContextManager\" is partially unknown\n\u00a0\u00a0Type argument 1 for class \"AbstractContextManager\" has partially unknown type",
- "trio.testing._raises_group.RaisesGroup: Type of base class unknown",
- "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.exconly\"",
- "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.errisinstance\"",
- "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.getrepr\"",
- "No docstring found for function \"trio.testing._raises_group.RaisesGroup.expected_type\""
+ "No docstring found for function \"trio._unix_pipes.FdStream.fileno\""
],
"Linux": [
+ "No docstring found for class \"trio._core._io_epoll._EpollStatistics\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.send_all\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.wait_send_all_might_not_block\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.receive_some\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.close\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.aclose\"",
+ "No docstring found for function \"trio._unix_pipes.FdStream.fileno\""
+ ],
+ "Windows": [],
+ "all": [
"No docstring found for class \"trio.MemoryReceiveChannel\"",
- "No docstring found for class \"trio._channel.MemoryReceiveChannel\"",
"No docstring found for function \"trio._channel.MemoryReceiveChannel.statistics\"",
- "No docstring found for class \"trio._channel.MemoryChannelStats\"",
"No docstring found for function \"trio._channel.MemoryReceiveChannel.aclose\"",
- "No docstring found for class \"trio.MemorySendChannel\"",
"No docstring found for class \"trio._channel.MemorySendChannel\"",
- "No docstring found for function \"trio._channel.MemorySendChannel.statistics\"",
"No docstring found for function \"trio._channel.MemorySendChannel.aclose\"",
- "No docstring found for class \"trio._core._run.Task\"",
"No docstring found for class \"trio._socket.SocketType\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.send_all\"",
"No docstring found for function \"trio._highlevel_socket.SocketStream.wait_send_all_might_not_block\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.send_eof\"",
"No docstring found for function \"trio._highlevel_socket.SocketStream.receive_some\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.aclose\"",
"No docstring found for function \"trio._path.Path.absolute\"",
- "No docstring found for class \"trio._path.AsyncAutoWrapperType\"",
"No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_forwards\"",
- "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_wraps\"",
"No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_magic\"",
- "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_iter\"",
"No docstring found for function \"trio._subprocess.HasFileno.fileno\"",
- "No docstring found for class \"trio._sync.AsyncContextManagerMixin\"",
"No docstring found for function \"trio._sync._HasAcquireRelease.acquire\"",
- "No docstring found for function \"trio._sync._HasAcquireRelease.release\"",
"No docstring found for class \"trio._sync._LockImpl\"",
- "No docstring found for class \"trio._core._io_epoll._EpollStatistics\"",
"No docstring found for class \"trio._core._local._NoValue\"",
- "No docstring found for class \"trio._core._local.RunVarToken\"",
"No docstring found for class \"trio.lowlevel.RunVarToken\"",
- "No docstring found for class \"trio.lowlevel.Task\"",
"No docstring found for class \"trio._core._ki.KIProtectionSignature\"",
- "No docstring found for function \"trio._unix_pipes.FdStream.send_all\"",
- "No docstring found for function \"trio._unix_pipes.FdStream.wait_send_all_might_not_block\"",
- "No docstring found for function \"trio._unix_pipes.FdStream.receive_some\"",
- "No docstring found for function \"trio._unix_pipes.FdStream.close\"",
- "No docstring found for function \"trio._unix_pipes.FdStream.aclose\"",
- "No docstring found for function \"trio._unix_pipes.FdStream.fileno\"",
"No docstring found for class \"trio.socket.SocketType\"",
- "No docstring found for class \"trio.socket.gaierror\"",
"No docstring found for class \"trio.socket.herror\"",
- "No docstring found for function \"trio._core._mock_clock.MockClock.start_clock\"",
"No docstring found for function \"trio._core._mock_clock.MockClock.current_time\"",
- "No docstring found for function \"trio._core._mock_clock.MockClock.deadline_to_sleep_time\"",
"trio.testing._raises_group.RaisesGroup: Type of metaclass unknown",
- "trio.testing._raises_group.RaisesGroup: Type of base class \"contextlib.AbstractContextManager\" is partially unknown\n\u00a0\u00a0Type argument 1 for class \"AbstractContextManager\" has partially unknown type",
"trio.testing._raises_group.RaisesGroup: Type of base class unknown",
- "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.exconly\"",
"No docstring found for function \"trio.testing._raises_group._ExceptionInfo.errisinstance\"",
- "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.getrepr\"",
- "No docstring found for function \"trio.testing._raises_group.RaisesGroup.expected_type\""
- ],
- "Windows": [
+ "No docstring found for function \"trio.testing._raises_group.RaisesGroup.expected_type\"",
"No docstring found for class \"trio.MemoryReceiveChannel\"",
"No docstring found for class \"trio._channel.MemoryReceiveChannel\"",
"No docstring found for function \"trio._channel.MemoryReceiveChannel.statistics\"",
diff --git a/src/trio/_tests/check_type_completeness.py b/src/trio/_tests/check_type_completeness.py
index 4a45fc63a4..179f04275f 100755
--- a/src/trio/_tests/check_type_completeness.py
+++ b/src/trio/_tests/check_type_completeness.py
@@ -1,4 +1,10 @@
#!/usr/bin/env python3
+"""This is a file that wraps calls to `pyright --verifytypes`, achieving two things:
+1. give an error if docstrings are missing.
+ pyright will give a number of missing docstrings, and error messages, but not exit with a non-zero value.
+2. filter out specific errors we don't care about.
+ this is largely due to 1, but also because Trio does some very complex stuff and --verifytypes has few to no ways of ignoring specific errors.
+"""
from __future__ import annotations
# this file is not run as part of the tests, instead it's run standalone from check.sh
@@ -11,6 +17,8 @@
import trio
import trio.testing
+# not needed if everything is working, but if somebody does something to generate
+# tons of errors, we can be nice and stop them from getting 3*tons of output
printed_diagnostics: set[str] = set()
@@ -60,7 +68,7 @@ def has_docstring_at_runtime(name: str) -> bool:
if "AsyncIOWrapper" in str(exc) or name in (
# Symbols not existing on all platforms, so we can't dynamically inspect them.
# Manually confirmed to have docstrings but pyright doesn't see them due to
- # export shenanigans.
+ # export shenanigans. TODO: actually manually confirm that.
# darwin
"trio.lowlevel.current_kqueue",
"trio.lowlevel.monitor_kevent",
@@ -78,7 +86,7 @@ def has_docstring_at_runtime(name: str) -> bool:
"trio.lowlevel.write_overlapped",
"trio.lowlevel.WaitForSingleObject",
"trio.socket.fromshare",
- # these are erroring on all platforms
+ # TODO: these are erroring on all platforms, why?
"trio._highlevel_generic.StapledStream.send_stream",
"trio._highlevel_generic.StapledStream.receive_stream",
"trio._ssl.SSLStream.transport_stream",
@@ -91,6 +99,7 @@ def has_docstring_at_runtime(name: str) -> bool:
print(
f"Pyright sees {name} at runtime, but unable to getattr({obj.__name__}, {obj_name})."
)
+ return False
return bool(obj.__doc__)
@@ -114,22 +123,8 @@ def check_type(
errors = []
for symbol in current_result["typeCompleteness"]["symbols"]:
- category = symbol["category"]
diagnostics = symbol["diagnostics"]
name = symbol["name"]
- if not diagnostics:
- if (
- category
- not in ("variable", "symbol", "type alias", "constant", "module")
- and not name.endswith("__")
- and not has_docstring_at_runtime(symbol["name"])
- ):
- print(
- "not warned by pyright, but missing docstring:",
- symbol["name"],
- symbol["category"],
- )
- continue
for diagnostic in diagnostics:
message = diagnostic["message"]
# ignore errors about missing docstrings if they're available at runtime
@@ -137,11 +132,9 @@ def check_type(
if has_docstring_at_runtime(symbol["name"]):
continue
else:
+ # Missing docstring messages include the name of the object.
+ # Other errors don't, so we add it.
message = f"{name}: {message}"
- # try:
- # expected_errors.remove(diagnostic)
- # # decrement count for object
- # except ValueError:
if message not in expected_errors and message not in printed_diagnostics:
print(f"new error: {message}")
errors.append(message)
@@ -164,34 +157,40 @@ def main(args: argparse.Namespace) -> int:
with open(errors_by_platform_file) as f:
errors_by_platform = json.load(f)
else:
- errors_by_platform = {"Linux": [], "Windows": [], "Darwin": []}
+ errors_by_platform = {"Linux": [], "Windows": [], "Darwin": [], "all": []}
changed = False
for platform in "Linux", "Windows", "Darwin":
+ platform_errors = errors_by_platform[platform] + errors_by_platform["all"]
print("*" * 20, f"\nChecking {platform}...")
- errors = check_type(
- platform, full_diagnostics_file, errors_by_platform[platform]
- )
+ errors = check_type(platform, full_diagnostics_file, platform_errors)
- new_errors = [e for e in errors if e not in errors_by_platform[platform]]
- missing_errors = [e for e in errors_by_platform[platform] if e not in errors]
+ new_errors = [e for e in errors if e not in platform_errors]
+ missing_errors = [e for e in platform_errors if e not in errors]
if new_errors:
print(
f"New errors introduced in `pyright --verifytypes`. Fix them, or ignore them by modifying {errors_by_platform_file}. The latter can be done by pre-commit CI bot."
)
- # print(new_errors)
changed = True
if missing_errors:
print(
f"Congratulations, you have resolved existing errors! Please remove them from {errors_by_platform_file}, either manually or with the pre-commit CI bot."
)
- # print(missing_errors)
changed = True
+ print(missing_errors)
errors_by_platform[platform] = errors
print("*" * 20)
+ # cut down the size of the json file by a lot, and make it easier to parse for
+ # humans, by moving errors that appear on all platforms to a separate category
+ for e in errors_by_platform["Linux"].copy():
+ if e in errors_by_platform["Darwin"] and e in errors_by_platform["Windows"]:
+ for platform in "Linux", "Windows", "Darwin":
+ errors_by_platform[platform].remove(e)
+ errors_by_platform["all"].append(e)
+
if changed and args.overwrite_file:
with open(errors_by_platform_file, "w") as f:
json.dump(errors_by_platform, f, indent=4, sort_keys=True)
@@ -203,8 +202,18 @@ def main(args: argparse.Namespace) -> int:
parser = argparse.ArgumentParser()
-parser.add_argument("--overwrite-file", action="store_true", default=False)
-parser.add_argument("--full-diagnostics-file", type=Path, default=None)
+parser.add_argument(
+ "--overwrite-file",
+ action="store_true",
+ default=False,
+ help="Use this flag to overwrite the current stored results. Either in CI together with a diff check, or to avoid having to manually correct it.",
+)
+parser.add_argument(
+ "--full-diagnostics-file",
+ type=Path,
+ default=None,
+ help="Use this for debugging, it will dump the output of all three pyright runs by platform into this file.",
+)
args = parser.parse_args()
assert __name__ == "__main__", "This script should be run standalone"
From 930e1a0b2d72f991d9373aeddb47b4c3bba7b926 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 21 Feb 2024 12:57:35 +0100
Subject: [PATCH 6/8] running with typing dependencies installed is good,
actually
---
src/trio/_tests/_check_type_completeness.json | 42 +++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/src/trio/_tests/_check_type_completeness.json b/src/trio/_tests/_check_type_completeness.json
index 8405b9bb1e..638de610b6 100644
--- a/src/trio/_tests/_check_type_completeness.json
+++ b/src/trio/_tests/_check_type_completeness.json
@@ -86,6 +86,48 @@
"No docstring found for function \"trio.testing._raises_group._ExceptionInfo.exconly\"",
"No docstring found for function \"trio.testing._raises_group._ExceptionInfo.errisinstance\"",
"No docstring found for function \"trio.testing._raises_group._ExceptionInfo.getrepr\"",
+ "No docstring found for function \"trio.testing._raises_group.RaisesGroup.expected_type\"",
+ "No docstring found for class \"trio.MemoryReceiveChannel\"",
+ "No docstring found for class \"trio._channel.MemoryReceiveChannel\"",
+ "No docstring found for function \"trio._channel.MemoryReceiveChannel.statistics\"",
+ "No docstring found for class \"trio._channel.MemoryChannelStats\"",
+ "No docstring found for function \"trio._channel.MemoryReceiveChannel.aclose\"",
+ "No docstring found for class \"trio.MemorySendChannel\"",
+ "No docstring found for class \"trio._channel.MemorySendChannel\"",
+ "No docstring found for function \"trio._channel.MemorySendChannel.statistics\"",
+ "No docstring found for function \"trio._channel.MemorySendChannel.aclose\"",
+ "No docstring found for class \"trio._core._run.Task\"",
+ "No docstring found for class \"trio._socket.SocketType\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.send_all\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.wait_send_all_might_not_block\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.send_eof\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.receive_some\"",
+ "No docstring found for function \"trio._highlevel_socket.SocketStream.aclose\"",
+ "No docstring found for function \"trio._path.Path.absolute\"",
+ "No docstring found for class \"trio._path.AsyncAutoWrapperType\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_forwards\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_wraps\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_magic\"",
+ "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_iter\"",
+ "No docstring found for function \"trio._subprocess.HasFileno.fileno\"",
+ "No docstring found for class \"trio._sync.AsyncContextManagerMixin\"",
+ "No docstring found for function \"trio._sync._HasAcquireRelease.acquire\"",
+ "No docstring found for function \"trio._sync._HasAcquireRelease.release\"",
+ "No docstring found for class \"trio._sync._LockImpl\"",
+ "No docstring found for class \"trio._core._local._NoValue\"",
+ "No docstring found for class \"trio._core._local.RunVarToken\"",
+ "No docstring found for class \"trio.lowlevel.RunVarToken\"",
+ "No docstring found for class \"trio.lowlevel.Task\"",
+ "No docstring found for class \"trio._core._ki.KIProtectionSignature\"",
+ "No docstring found for class \"trio.socket.SocketType\"",
+ "No docstring found for class \"trio.socket.gaierror\"",
+ "No docstring found for class \"trio.socket.herror\"",
+ "No docstring found for function \"trio._core._mock_clock.MockClock.start_clock\"",
+ "No docstring found for function \"trio._core._mock_clock.MockClock.current_time\"",
+ "No docstring found for function \"trio._core._mock_clock.MockClock.deadline_to_sleep_time\"",
+ "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.exconly\"",
+ "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.errisinstance\"",
+ "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.getrepr\"",
"No docstring found for function \"trio.testing._raises_group.RaisesGroup.expected_type\""
]
}
From 3b48476be18793037416baf09e07efeb1de8a14a Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 21 Feb 2024 15:13:11 +0100
Subject: [PATCH 7/8] remove old errors from 'all'
---
src/trio/_tests/_check_type_completeness.json | 69 -------------------
src/trio/_tests/check_type_completeness.py | 1 +
2 files changed, 1 insertion(+), 69 deletions(-)
diff --git a/src/trio/_tests/_check_type_completeness.json b/src/trio/_tests/_check_type_completeness.json
index 638de610b6..21654d2030 100644
--- a/src/trio/_tests/_check_type_completeness.json
+++ b/src/trio/_tests/_check_type_completeness.json
@@ -18,75 +18,6 @@
],
"Windows": [],
"all": [
- "No docstring found for class \"trio.MemoryReceiveChannel\"",
- "No docstring found for function \"trio._channel.MemoryReceiveChannel.statistics\"",
- "No docstring found for function \"trio._channel.MemoryReceiveChannel.aclose\"",
- "No docstring found for class \"trio._channel.MemorySendChannel\"",
- "No docstring found for function \"trio._channel.MemorySendChannel.aclose\"",
- "No docstring found for class \"trio._socket.SocketType\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.wait_send_all_might_not_block\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.receive_some\"",
- "No docstring found for function \"trio._path.Path.absolute\"",
- "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_forwards\"",
- "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_magic\"",
- "No docstring found for function \"trio._subprocess.HasFileno.fileno\"",
- "No docstring found for function \"trio._sync._HasAcquireRelease.acquire\"",
- "No docstring found for class \"trio._sync._LockImpl\"",
- "No docstring found for class \"trio._core._local._NoValue\"",
- "No docstring found for class \"trio.lowlevel.RunVarToken\"",
- "No docstring found for class \"trio._core._ki.KIProtectionSignature\"",
- "No docstring found for class \"trio.socket.SocketType\"",
- "No docstring found for class \"trio.socket.herror\"",
- "No docstring found for function \"trio._core._mock_clock.MockClock.current_time\"",
- "trio.testing._raises_group.RaisesGroup: Type of metaclass unknown",
- "trio.testing._raises_group.RaisesGroup: Type of base class unknown",
- "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.errisinstance\"",
- "No docstring found for function \"trio.testing._raises_group.RaisesGroup.expected_type\"",
- "No docstring found for class \"trio.MemoryReceiveChannel\"",
- "No docstring found for class \"trio._channel.MemoryReceiveChannel\"",
- "No docstring found for function \"trio._channel.MemoryReceiveChannel.statistics\"",
- "No docstring found for class \"trio._channel.MemoryChannelStats\"",
- "No docstring found for function \"trio._channel.MemoryReceiveChannel.aclose\"",
- "No docstring found for class \"trio.MemorySendChannel\"",
- "No docstring found for class \"trio._channel.MemorySendChannel\"",
- "No docstring found for function \"trio._channel.MemorySendChannel.statistics\"",
- "No docstring found for function \"trio._channel.MemorySendChannel.aclose\"",
- "No docstring found for class \"trio._core._run.Task\"",
- "No docstring found for class \"trio._socket.SocketType\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.send_all\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.wait_send_all_might_not_block\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.send_eof\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.receive_some\"",
- "No docstring found for function \"trio._highlevel_socket.SocketStream.aclose\"",
- "No docstring found for function \"trio._path.Path.absolute\"",
- "No docstring found for class \"trio._path.AsyncAutoWrapperType\"",
- "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_forwards\"",
- "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_wraps\"",
- "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_magic\"",
- "No docstring found for function \"trio._path.AsyncAutoWrapperType.generate_iter\"",
- "No docstring found for function \"trio._subprocess.HasFileno.fileno\"",
- "No docstring found for class \"trio._sync.AsyncContextManagerMixin\"",
- "No docstring found for function \"trio._sync._HasAcquireRelease.acquire\"",
- "No docstring found for function \"trio._sync._HasAcquireRelease.release\"",
- "No docstring found for class \"trio._sync._LockImpl\"",
- "No docstring found for class \"trio._core._local._NoValue\"",
- "No docstring found for class \"trio._core._local.RunVarToken\"",
- "No docstring found for class \"trio.lowlevel.RunVarToken\"",
- "No docstring found for class \"trio.lowlevel.Task\"",
- "No docstring found for class \"trio._core._ki.KIProtectionSignature\"",
- "No docstring found for class \"trio.socket.SocketType\"",
- "No docstring found for class \"trio.socket.gaierror\"",
- "No docstring found for class \"trio.socket.herror\"",
- "No docstring found for function \"trio._core._mock_clock.MockClock.start_clock\"",
- "No docstring found for function \"trio._core._mock_clock.MockClock.current_time\"",
- "No docstring found for function \"trio._core._mock_clock.MockClock.deadline_to_sleep_time\"",
- "trio.testing._raises_group.RaisesGroup: Type of metaclass unknown",
- "trio.testing._raises_group.RaisesGroup: Type of base class \"contextlib.AbstractContextManager\" is partially unknown\n\u00a0\u00a0Type argument 1 for class \"AbstractContextManager\" has partially unknown type",
- "trio.testing._raises_group.RaisesGroup: Type of base class unknown",
- "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.exconly\"",
- "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.errisinstance\"",
- "No docstring found for function \"trio.testing._raises_group._ExceptionInfo.getrepr\"",
- "No docstring found for function \"trio.testing._raises_group.RaisesGroup.expected_type\"",
"No docstring found for class \"trio.MemoryReceiveChannel\"",
"No docstring found for class \"trio._channel.MemoryReceiveChannel\"",
"No docstring found for function \"trio._channel.MemoryReceiveChannel.statistics\"",
diff --git a/src/trio/_tests/check_type_completeness.py b/src/trio/_tests/check_type_completeness.py
index 179f04275f..1e18dbbd59 100755
--- a/src/trio/_tests/check_type_completeness.py
+++ b/src/trio/_tests/check_type_completeness.py
@@ -185,6 +185,7 @@ def main(args: argparse.Namespace) -> int:
# cut down the size of the json file by a lot, and make it easier to parse for
# humans, by moving errors that appear on all platforms to a separate category
+ errors_by_platform["all"] = []
for e in errors_by_platform["Linux"].copy():
if e in errors_by_platform["Darwin"] and e in errors_by_platform["Windows"]:
for platform in "Linux", "Windows", "Darwin":
From dd976ba6b21fa394718ba4ee9e50ab14a8e86b85 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Mon, 4 Mar 2024 13:47:27 +0100
Subject: [PATCH 8/8] fixes after review from a5rocks
---
src/trio/_tests/check_type_completeness.py | 35 +++++++++++++---------
1 file changed, 21 insertions(+), 14 deletions(-)
diff --git a/src/trio/_tests/check_type_completeness.py b/src/trio/_tests/check_type_completeness.py
index 1e18dbbd59..3773925644 100755
--- a/src/trio/_tests/check_type_completeness.py
+++ b/src/trio/_tests/check_type_completeness.py
@@ -45,23 +45,20 @@ def has_docstring_at_runtime(name: str) -> bool:
can resolve it, in order to check whether it has a `__doc__` at runtime and
verifytypes misses it because we're doing overly fancy stuff.
"""
+ # This assert is solely for stopping isort from removing our imports of trio & trio.testing
+ # It could also be done with isort:skip, but that'd also disable import sorting and the like.
assert trio.testing
# figure out what part of the name is the module, so we can "import" it
name_parts = name.split(".")
- split_i = 1
+ assert name_parts[0] == "trio"
if name_parts[1] == "tests":
return True
- if name_parts[1] in ("_core", "testing"): # noqa: SIM108
- split_i = 2
- else:
- split_i = 1
- module = sys.modules[".".join(name_parts[:split_i])]
# traverse down the remaining identifiers with getattr
- obj = module
+ obj = trio
try:
- for obj_name in name_parts[split_i:]:
+ for obj_name in name_parts[1:]:
obj = getattr(obj, obj_name)
except AttributeError as exc:
# asynciowrapper does funky getattr stuff
@@ -69,6 +66,9 @@ def has_docstring_at_runtime(name: str) -> bool:
# Symbols not existing on all platforms, so we can't dynamically inspect them.
# Manually confirmed to have docstrings but pyright doesn't see them due to
# export shenanigans. TODO: actually manually confirm that.
+ # In theory we could verify these at runtime, probably by running the script separately
+ # on separate platforms. It might also be a decent idea to work the other way around,
+ # a la test_static_tool_sees_class_members
# darwin
"trio.lowlevel.current_kqueue",
"trio.lowlevel.monitor_kevent",
@@ -86,6 +86,10 @@ def has_docstring_at_runtime(name: str) -> bool:
"trio.lowlevel.write_overlapped",
"trio.lowlevel.WaitForSingleObject",
"trio.socket.fromshare",
+ # linux
+ # this test will fail on linux, but I don't develop on linux. So the next
+ # person to do so is very welcome to open a pull request and populate with
+ # objects
# TODO: these are erroring on all platforms, why?
"trio._highlevel_generic.StapledStream.send_stream",
"trio._highlevel_generic.StapledStream.receive_stream",
@@ -97,7 +101,8 @@ def has_docstring_at_runtime(name: str) -> bool:
else:
print(
- f"Pyright sees {name} at runtime, but unable to getattr({obj.__name__}, {obj_name})."
+ f"Pyright sees {name} at runtime, but unable to getattr({obj.__name__}, {obj_name}).",
+ file=sys.stderr,
)
return False
return bool(obj.__doc__)
@@ -114,7 +119,7 @@ def check_type(
current_result = json.loads(res.stdout)
if res.stderr:
- print(res.stderr)
+ print(res.stderr, file=sys.stderr)
if full_diagnostics_file:
with open(full_diagnostics_file, "a") as f:
@@ -136,7 +141,7 @@ def check_type(
# Other errors don't, so we add it.
message = f"{name}: {message}"
if message not in expected_errors and message not in printed_diagnostics:
- print(f"new error: {message}")
+ print(f"new error: {message}", file=sys.stderr)
errors.append(message)
printed_diagnostics.add(message)
@@ -170,15 +175,17 @@ def main(args: argparse.Namespace) -> int:
if new_errors:
print(
- f"New errors introduced in `pyright --verifytypes`. Fix them, or ignore them by modifying {errors_by_platform_file}. The latter can be done by pre-commit CI bot."
+ f"New errors introduced in `pyright --verifytypes`. Fix them, or ignore them by modifying {errors_by_platform_file}. The latter can be done by pre-commit CI bot.",
+ file=sys.stderr,
)
changed = True
if missing_errors:
print(
- f"Congratulations, you have resolved existing errors! Please remove them from {errors_by_platform_file}, either manually or with the pre-commit CI bot."
+ f"Congratulations, you have resolved existing errors! Please remove them from {errors_by_platform_file}, either manually or with the pre-commit CI bot.",
+ file=sys.stderr,
)
changed = True
- print(missing_errors)
+ print(missing_errors, file=sys.stderr)
errors_by_platform[platform] = errors
print("*" * 20)