mirror of https://github.com/python/cpython
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:
parent
6c98fce33a
commit
87295b4068
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue