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/