From 8020dbda7ee09a2c3e376bdd7a9ac826a236f5c9 Mon Sep 17 00:00:00 2001
From: sobolevn <mail@sobolevn.me>
Date: Tue, 16 May 2023 14:45:31 +0300
Subject: [PATCH 1/3] gh-104050: Add more annotations to `Tools/clinic.py`

---
 Tools/clinic/clinic.py | 74 +++++++++++++++++++++++++++---------------
 Tools/clinic/cpp.py    |  5 +--
 2 files changed, 50 insertions(+), 29 deletions(-)

diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py
index 13fd66b0406f26..0a56d7d806b536 100755
--- a/Tools/clinic/clinic.py
+++ b/Tools/clinic/clinic.py
@@ -29,7 +29,7 @@
 
 from collections.abc import Callable
 from types import *
-from typing import Any, NamedTuple
+from typing import Any, NamedTuple, NoReturn, Literal, overload
 
 # TODO:
 #
@@ -60,21 +60,21 @@
 }
 
 class Unspecified:
-    def __repr__(self):
+    def __repr__(self) -> str:
         return '<Unspecified>'
 
 unspecified = Unspecified()
 
 
 class Null:
-    def __repr__(self):
+    def __repr__(self) -> str:
         return '<Null>'
 
 NULL = Null()
 
 
 class Unknown:
-    def __repr__(self):
+    def __repr__(self) -> str:
         return '<Unknown>'
 
 unknown = Unknown()
@@ -82,15 +82,15 @@ def __repr__(self):
 sig_end_marker = '--'
 
 Appender = Callable[[str], None]
-Outputter = Callable[[None], str]
+Outputter = Callable[[], str]
 
 class _TextAccumulator(NamedTuple):
     text: list[str]
     append: Appender
     output: Outputter
 
-def _text_accumulator():
-    text = []
+def _text_accumulator() -> _TextAccumulator:
+    text: list[str] = []
     def output():
         s = ''.join(text)
         text.clear()
@@ -99,10 +99,10 @@ def output():
 
 
 class TextAccumulator(NamedTuple):
-    text: list[str]
     append: Appender
+    output: Outputter
 
-def text_accumulator():
+def text_accumulator() -> TextAccumulator:
     """
     Creates a simple text accumulator / joiner.
 
@@ -116,8 +116,28 @@ def text_accumulator():
     text, append, output = _text_accumulator()
     return TextAccumulator(append, output)
 
-
-def warn_or_fail(fail=False, *args, filename=None, line_number=None):
+@overload
+def warn_or_fail(
+    *args,
+    fail: Literal[True],
+    filename: str | None = None,
+    line_number: int | None = None,
+) -> NoReturn: ...
+
+@overload
+def warn_or_fail(
+    *args,
+    fail: Literal[False] = False,
+    filename: str | None = None,
+    line_number: int | None = None,
+) -> None: ...
+
+def warn_or_fail(
+    *args,
+    fail: bool = False,
+    filename: str | None = None,
+    line_number: int | None = None,
+) -> None:
     joined = " ".join([str(a) for a in args])
     add, output = text_accumulator()
     if fail:
@@ -140,14 +160,14 @@ def warn_or_fail(fail=False, *args, filename=None, line_number=None):
         sys.exit(-1)
 
 
-def warn(*args, filename=None, line_number=None):
-    return warn_or_fail(False, *args, filename=filename, line_number=line_number)
+def warn(*args, filename: str | None = None, line_number: int | None = None) -> None:
+    return warn_or_fail(*args, filename=filename, line_number=line_number, fail=False)
 
-def fail(*args, filename=None, line_number=None):
-    return warn_or_fail(True, *args, filename=filename, line_number=line_number)
+def fail(*args, filename: str | None = None, line_number: int | None = None) -> NoReturn:
+    warn_or_fail(*args, filename=filename, line_number=line_number, fail=True)
 
 
-def quoted_for_c_string(s):
+def quoted_for_c_string(s: str) -> str:
     for old, new in (
         ('\\', '\\\\'), # must be first!
         ('"', '\\"'),
@@ -156,13 +176,13 @@ def quoted_for_c_string(s):
         s = s.replace(old, new)
     return s
 
-def c_repr(s):
+def c_repr(s: str) -> str:
     return '"' + s + '"'
 
 
 is_legal_c_identifier = re.compile('^[A-Za-z_][A-Za-z0-9_]*$').match
 
-def is_legal_py_identifier(s):
+def is_legal_py_identifier(s: str) -> bool:
     return all(is_legal_c_identifier(field) for field in s.split('.'))
 
 # identifiers that are okay in Python but aren't a good idea in C.
@@ -175,7 +195,7 @@ def is_legal_py_identifier(s):
 typedef typeof union unsigned void volatile while
 """.strip().split())
 
-def ensure_legal_c_identifier(s):
+def ensure_legal_c_identifier(s: str) -> str:
     # for now, just complain if what we're given isn't legal
     if not is_legal_c_identifier(s):
         fail("Illegal C identifier: {}".format(s))
@@ -184,7 +204,7 @@ def ensure_legal_c_identifier(s):
         return s + "_value"
     return s
 
-def rstrip_lines(s):
+def rstrip_lines(s: str) -> str:
     text, add, output = _text_accumulator()
     for line in s.split('\n'):
         add(line.rstrip())
@@ -192,14 +212,14 @@ def rstrip_lines(s):
     text.pop()
     return output()
 
-def format_escape(s):
+def format_escape(s: str) -> str:
     # double up curly-braces, this string will be used
     # as part of a format_map() template later
     s = s.replace('{', '{{')
     s = s.replace('}', '}}')
     return s
 
-def linear_format(s, **kwargs):
+def linear_format(s: str, **kwargs: str) -> str:
     """
     Perform str.format-like substitution, except:
       * The strings substituted must be on lines by
@@ -243,7 +263,7 @@ def linear_format(s, **kwargs):
 
     return output()[:-1]
 
-def indent_all_lines(s, prefix):
+def indent_all_lines(s: str, prefix: str) -> str:
     """
     Returns 's', with 'prefix' prepended to all lines.
 
@@ -264,7 +284,7 @@ def indent_all_lines(s, prefix):
         final.append(last)
     return ''.join(final)
 
-def suffix_all_lines(s, suffix):
+def suffix_all_lines(s: str, suffix: str) -> str:
     """
     Returns 's', with 'suffix' appended to all lines.
 
@@ -284,7 +304,7 @@ def suffix_all_lines(s, suffix):
     return ''.join(final)
 
 
-def version_splitter(s):
+def version_splitter(s: str) -> tuple[int, ...]:
     """Splits a version string into a tuple of integers.
 
     The following ASCII characters are allowed, and employ
@@ -295,7 +315,7 @@ def version_splitter(s):
     (This permits Python-style version strings such as "1.4b3".)
     """
     version = []
-    accumulator = []
+    accumulator: list[str] = []
     def flush():
         if not accumulator:
             raise ValueError('Unsupported version string: ' + repr(s))
@@ -315,7 +335,7 @@ def flush():
     flush()
     return tuple(version)
 
-def version_comparitor(version1, version2):
+def version_comparitor(version1: str, version2: str) -> Literal[-1, 0, 1]:
     iterator = itertools.zip_longest(version_splitter(version1), version_splitter(version2), fillvalue=0)
     for i, (a, b) in enumerate(iterator):
         if a < b:
diff --git a/Tools/clinic/cpp.py b/Tools/clinic/cpp.py
index bc2cc713aac394..2dfa6e18c002ab 100644
--- a/Tools/clinic/cpp.py
+++ b/Tools/clinic/cpp.py
@@ -1,6 +1,7 @@
 import re
 import sys
 from collections.abc import Callable
+from typing import NoReturn
 
 
 TokenAndCondition = tuple[str, str]
@@ -30,7 +31,7 @@ class Monitor:
     is_a_simple_defined: Callable[[str], re.Match[str] | None]
     is_a_simple_defined = re.compile(r'^defined\s*\(\s*[A-Za-z0-9_]+\s*\)$').match
 
-    def __init__(self, filename=None, *, verbose: bool = False):
+    def __init__(self, filename: str | None = None, *, verbose: bool = False) -> None:
         self.stack: TokenStack = []
         self.in_comment = False
         self.continuation: str | None = None
@@ -55,7 +56,7 @@ def condition(self) -> str:
         """
         return " && ".join(condition for token, condition in self.stack)
 
-    def fail(self, *a):
+    def fail(self, *a: object) -> NoReturn:
         if self.filename:
             filename = " " + self.filename
         else:

From b23fd0b843ef567bb033d3cfb451b5dcf06de951 Mon Sep 17 00:00:00 2001
From: sobolevn <mail@sobolevn.me>
Date: Tue, 16 May 2023 15:01:57 +0300
Subject: [PATCH 2/3] Minor issues

---
 Tools/clinic/clinic.py | 18 +++++++++++++-----
 Tools/clinic/cpp.py    |  2 +-
 2 files changed, 14 insertions(+), 6 deletions(-)

diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py
index ef1db78a29e7f3..bb2c2cbe615d00 100755
--- a/Tools/clinic/clinic.py
+++ b/Tools/clinic/clinic.py
@@ -118,7 +118,7 @@ def text_accumulator() -> TextAccumulator:
 
 @overload
 def warn_or_fail(
-    *args,
+    *args: object,
     fail: Literal[True],
     filename: str | None = None,
     line_number: int | None = None,
@@ -126,14 +126,14 @@ def warn_or_fail(
 
 @overload
 def warn_or_fail(
-    *args,
+    *args: object,
     fail: Literal[False] = False,
     filename: str | None = None,
     line_number: int | None = None,
 ) -> None: ...
 
 def warn_or_fail(
-    *args,
+    *args: object,
     fail: bool = False,
     filename: str | None = None,
     line_number: int | None = None,
@@ -160,10 +160,18 @@ def warn_or_fail(
         sys.exit(-1)
 
 
-def warn(*args, filename: str | None = None, line_number: int | None = None) -> None:
+def warn(
+    *args: object,
+    filename: str | None = None,
+    line_number: int | None = None,
+) -> None:
     return warn_or_fail(*args, filename=filename, line_number=line_number, fail=False)
 
-def fail(*args, filename: str | None = None, line_number: int | None = None) -> NoReturn:
+def fail(
+    *args: object,
+    filename: str | None = None,
+    line_number: int | None = None,
+) -> NoReturn:
     warn_or_fail(*args, filename=filename, line_number=line_number, fail=True)
 
 
diff --git a/Tools/clinic/cpp.py b/Tools/clinic/cpp.py
index 2dfa6e18c002ab..a3546f570c5aca 100644
--- a/Tools/clinic/cpp.py
+++ b/Tools/clinic/cpp.py
@@ -65,7 +65,7 @@ def fail(self, *a: object) -> NoReturn:
         print("   ", ' '.join(str(x) for x in a))
         sys.exit(-1)
 
-    def close(self):
+    def close(self) -> None:
         if self.stack:
             self.fail("Ended file while still in a preprocessor conditional block!")
 

From 7eb4f4e31355207ce33a0f86b88d799e4c825502 Mon Sep 17 00:00:00 2001
From: sobolevn <mail@sobolevn.me>
Date: Tue, 16 May 2023 19:50:55 +0300
Subject: [PATCH 3/3] Address review

---
 Tools/clinic/mypy.ini | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Tools/clinic/mypy.ini b/Tools/clinic/mypy.ini
index 3c5643e789be37..672911dc19abda 100644
--- a/Tools/clinic/mypy.ini
+++ b/Tools/clinic/mypy.ini
@@ -8,4 +8,5 @@ strict_concatenate = True
 warn_redundant_casts = True
 warn_unused_ignores = True
 warn_unused_configs = True
+warn_unreachable = True
 files = Tools/clinic/