gh-113317: Rework Argument Clinic cpp.py error handling (#113525)

Rework error handling in the C preprocessor helper. Instead of monkey-
patching the cpp.Monitor.fail() method from within clinic.py, rewrite
cpp.py to use a subclass of the ClinicError exception. As a side-effect,
ClinicError is moved into Tools/clinic/libclinic/errors.py.

Yak-shaving in preparation for putting cpp.py into libclinic.
This commit is contained in:
Erlend E. Aasland 2023-12-27 22:43:19 +01:00 committed by GitHub
parent 6c98fce33a
commit 87295b4068
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 44 additions and 36 deletions

View File

@ -22,7 +22,7 @@ with test_tools.imports_under_tool('clinic'):
def _make_clinic(*, filename='clinic_tests'):
clang = clinic.CLanguage(None)
clang = clinic.CLanguage(filename)
c = clinic.Clinic(clang, filename=filename, limited_capi=False)
c.block_parser = clinic.BlockParser('', clang)
return c
@ -3920,7 +3920,7 @@ class ClinicReprTests(unittest.TestCase):
self.assertEqual(repr(parameter), "<clinic.Parameter 'bar'>")
def test_Monitor_repr(self):
monitor = clinic.cpp.Monitor()
monitor = clinic.cpp.Monitor("test.c")
self.assertRegex(repr(monitor), r"<clinic.Monitor \d+ line=0 condition=''>")
monitor.line_number = 42

View File

@ -53,6 +53,7 @@ from typing import (
# Local imports.
import libclinic
from libclinic import ClinicError
# TODO:
@ -94,27 +95,6 @@ NULL = Null()
TemplateDict = dict[str, str]
@dc.dataclass
class ClinicError(Exception):
message: str
_: dc.KW_ONLY
lineno: int | None = None
filename: str | None = None
def __post_init__(self) -> None:
super().__init__(self.message)
def report(self, *, warn_only: bool = False) -> str:
msg = "Warning" if warn_only else "Error"
if self.filename is not None:
msg += f" in file {self.filename!r}"
if self.lineno is not None:
msg += f" on line {self.lineno}"
msg += ":\n"
msg += f"{self.message}\n"
return msg
@overload
def warn_or_fail(
*args: object,
@ -669,7 +649,6 @@ class CLanguage(Language):
def __init__(self, filename: str) -> None:
super().__init__(filename)
self.cpp = cpp.Monitor(filename)
self.cpp.fail = fail # type: ignore[method-assign]
def parse_line(self, line: str) -> None:
self.cpp.writeline(line)

View File

@ -3,6 +3,8 @@ import re
import sys
from typing import NoReturn
from libclinic.errors import ParseError
TokenAndCondition = tuple[str, str]
TokenStack = list[TokenAndCondition]
@ -32,7 +34,7 @@ class Monitor:
Anyway this implementation seems to work well enough for the CPython sources.
"""
filename: str | None = None
filename: str
_: dc.KW_ONLY
verbose: bool = False
@ -59,14 +61,8 @@ class Monitor:
"""
return " && ".join(condition for token, condition in self.stack)
def fail(self, *a: object) -> NoReturn:
if self.filename:
filename = " " + self.filename
else:
filename = ''
print("Error at" + filename, "line", self.line_number, ":")
print(" ", ' '.join(str(x) for x in a))
sys.exit(-1)
def fail(self, msg: str) -> NoReturn:
raise ParseError(msg, filename=self.filename, lineno=self.line_number)
def writeline(self, line: str) -> None:
self.line_number += 1
@ -74,7 +70,7 @@ class Monitor:
def pop_stack() -> TokenAndCondition:
if not self.stack:
self.fail("#" + token + " without matching #if / #ifdef / #ifndef!")
self.fail(f"#{token} without matching #if / #ifdef / #ifndef!")
return self.stack.pop()
if self.continuation:
@ -145,7 +141,7 @@ class Monitor:
if token in {'if', 'ifdef', 'ifndef', 'elif'}:
if not condition:
self.fail("Invalid format for #" + token + " line: no argument!")
self.fail(f"Invalid format for #{token} line: no argument!")
if token in {'if', 'elif'}:
if not is_a_simple_defined(condition):
condition = "(" + condition + ")"
@ -155,7 +151,8 @@ class Monitor:
else:
fields = condition.split()
if len(fields) != 1:
self.fail("Invalid format for #" + token + " line: should be exactly one argument!")
self.fail(f"Invalid format for #{token} line: "
"should be exactly one argument!")
symbol = fields[0]
condition = 'defined(' + symbol + ')'
if token == 'ifndef':

View File

@ -1,5 +1,8 @@
from typing import Final
from .errors import (
ClinicError,
)
from .formatting import (
SIG_END_MARKER,
c_repr,
@ -15,6 +18,9 @@ from .formatting import (
__all__ = [
# Error handling
"ClinicError",
# Formatting helpers
"SIG_END_MARKER",
"c_repr",

View File

@ -0,0 +1,26 @@
import dataclasses as dc
@dc.dataclass
class ClinicError(Exception):
message: str
_: dc.KW_ONLY
lineno: int | None = None
filename: str | None = None
def __post_init__(self) -> None:
super().__init__(self.message)
def report(self, *, warn_only: bool = False) -> str:
msg = "Warning" if warn_only else "Error"
if self.filename is not None:
msg += f" in file {self.filename!r}"
if self.lineno is not None:
msg += f" on line {self.lineno}"
msg += ":\n"
msg += f"{self.message}\n"
return msg
class ParseError(ClinicError):
pass