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'):
|
def _make_clinic(*, filename='clinic_tests'):
|
||||||
clang = clinic.CLanguage(None)
|
clang = clinic.CLanguage(filename)
|
||||||
c = clinic.Clinic(clang, filename=filename, limited_capi=False)
|
c = clinic.Clinic(clang, filename=filename, limited_capi=False)
|
||||||
c.block_parser = clinic.BlockParser('', clang)
|
c.block_parser = clinic.BlockParser('', clang)
|
||||||
return c
|
return c
|
||||||
|
@ -3920,7 +3920,7 @@ class ClinicReprTests(unittest.TestCase):
|
||||||
self.assertEqual(repr(parameter), "<clinic.Parameter 'bar'>")
|
self.assertEqual(repr(parameter), "<clinic.Parameter 'bar'>")
|
||||||
|
|
||||||
def test_Monitor_repr(self):
|
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=''>")
|
self.assertRegex(repr(monitor), r"<clinic.Monitor \d+ line=0 condition=''>")
|
||||||
|
|
||||||
monitor.line_number = 42
|
monitor.line_number = 42
|
||||||
|
|
|
@ -53,6 +53,7 @@ from typing import (
|
||||||
|
|
||||||
# Local imports.
|
# Local imports.
|
||||||
import libclinic
|
import libclinic
|
||||||
|
from libclinic import ClinicError
|
||||||
|
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
|
@ -94,27 +95,6 @@ NULL = Null()
|
||||||
TemplateDict = dict[str, str]
|
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
|
@overload
|
||||||
def warn_or_fail(
|
def warn_or_fail(
|
||||||
*args: object,
|
*args: object,
|
||||||
|
@ -669,7 +649,6 @@ class CLanguage(Language):
|
||||||
def __init__(self, filename: str) -> None:
|
def __init__(self, filename: str) -> None:
|
||||||
super().__init__(filename)
|
super().__init__(filename)
|
||||||
self.cpp = cpp.Monitor(filename)
|
self.cpp = cpp.Monitor(filename)
|
||||||
self.cpp.fail = fail # type: ignore[method-assign]
|
|
||||||
|
|
||||||
def parse_line(self, line: str) -> None:
|
def parse_line(self, line: str) -> None:
|
||||||
self.cpp.writeline(line)
|
self.cpp.writeline(line)
|
||||||
|
|
|
@ -3,6 +3,8 @@ import re
|
||||||
import sys
|
import sys
|
||||||
from typing import NoReturn
|
from typing import NoReturn
|
||||||
|
|
||||||
|
from libclinic.errors import ParseError
|
||||||
|
|
||||||
|
|
||||||
TokenAndCondition = tuple[str, str]
|
TokenAndCondition = tuple[str, str]
|
||||||
TokenStack = list[TokenAndCondition]
|
TokenStack = list[TokenAndCondition]
|
||||||
|
@ -32,7 +34,7 @@ class Monitor:
|
||||||
|
|
||||||
Anyway this implementation seems to work well enough for the CPython sources.
|
Anyway this implementation seems to work well enough for the CPython sources.
|
||||||
"""
|
"""
|
||||||
filename: str | None = None
|
filename: str
|
||||||
_: dc.KW_ONLY
|
_: dc.KW_ONLY
|
||||||
verbose: bool = False
|
verbose: bool = False
|
||||||
|
|
||||||
|
@ -59,14 +61,8 @@ class Monitor:
|
||||||
"""
|
"""
|
||||||
return " && ".join(condition for token, condition in self.stack)
|
return " && ".join(condition for token, condition in self.stack)
|
||||||
|
|
||||||
def fail(self, *a: object) -> NoReturn:
|
def fail(self, msg: str) -> NoReturn:
|
||||||
if self.filename:
|
raise ParseError(msg, filename=self.filename, lineno=self.line_number)
|
||||||
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 writeline(self, line: str) -> None:
|
def writeline(self, line: str) -> None:
|
||||||
self.line_number += 1
|
self.line_number += 1
|
||||||
|
@ -74,7 +70,7 @@ class Monitor:
|
||||||
|
|
||||||
def pop_stack() -> TokenAndCondition:
|
def pop_stack() -> TokenAndCondition:
|
||||||
if not self.stack:
|
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()
|
return self.stack.pop()
|
||||||
|
|
||||||
if self.continuation:
|
if self.continuation:
|
||||||
|
@ -145,7 +141,7 @@ class Monitor:
|
||||||
|
|
||||||
if token in {'if', 'ifdef', 'ifndef', 'elif'}:
|
if token in {'if', 'ifdef', 'ifndef', 'elif'}:
|
||||||
if not condition:
|
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 token in {'if', 'elif'}:
|
||||||
if not is_a_simple_defined(condition):
|
if not is_a_simple_defined(condition):
|
||||||
condition = "(" + condition + ")"
|
condition = "(" + condition + ")"
|
||||||
|
@ -155,7 +151,8 @@ class Monitor:
|
||||||
else:
|
else:
|
||||||
fields = condition.split()
|
fields = condition.split()
|
||||||
if len(fields) != 1:
|
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]
|
symbol = fields[0]
|
||||||
condition = 'defined(' + symbol + ')'
|
condition = 'defined(' + symbol + ')'
|
||||||
if token == 'ifndef':
|
if token == 'ifndef':
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
|
from .errors import (
|
||||||
|
ClinicError,
|
||||||
|
)
|
||||||
from .formatting import (
|
from .formatting import (
|
||||||
SIG_END_MARKER,
|
SIG_END_MARKER,
|
||||||
c_repr,
|
c_repr,
|
||||||
|
@ -15,6 +18,9 @@ from .formatting import (
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
# Error handling
|
||||||
|
"ClinicError",
|
||||||
|
|
||||||
# Formatting helpers
|
# Formatting helpers
|
||||||
"SIG_END_MARKER",
|
"SIG_END_MARKER",
|
||||||
"c_repr",
|
"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