Skip to content

Commit 84ac27d

Browse files
erlend-aaslandaisk
authored andcommitted
pythongh-113317: Rework Argument Clinic cpp.py error handling (python#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.
1 parent 97a0367 commit 84ac27d

File tree

5 files changed

+44
-36
lines changed

5 files changed

+44
-36
lines changed

Lib/test/test_clinic.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323

2424
def _make_clinic(*, filename='clinic_tests'):
25-
clang = clinic.CLanguage(None)
25+
clang = clinic.CLanguage(filename)
2626
c = clinic.Clinic(clang, filename=filename, limited_capi=False)
2727
c.block_parser = clinic.BlockParser('', clang)
2828
return c
@@ -3920,7 +3920,7 @@ def test_Function_and_Parameter_reprs(self):
39203920
self.assertEqual(repr(parameter), "<clinic.Parameter 'bar'>")
39213921

39223922
def test_Monitor_repr(self):
3923-
monitor = clinic.cpp.Monitor()
3923+
monitor = clinic.cpp.Monitor("test.c")
39243924
self.assertRegex(repr(monitor), r"<clinic.Monitor \d+ line=0 condition=''>")
39253925

39263926
monitor.line_number = 42

Tools/clinic/clinic.py

+1-22
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353

5454
# Local imports.
5555
import libclinic
56+
from libclinic import ClinicError
5657

5758

5859
# TODO:
@@ -94,27 +95,6 @@ def __repr__(self) -> str:
9495
TemplateDict = dict[str, str]
9596

9697

97-
@dc.dataclass
98-
class ClinicError(Exception):
99-
message: str
100-
_: dc.KW_ONLY
101-
lineno: int | None = None
102-
filename: str | None = None
103-
104-
def __post_init__(self) -> None:
105-
super().__init__(self.message)
106-
107-
def report(self, *, warn_only: bool = False) -> str:
108-
msg = "Warning" if warn_only else "Error"
109-
if self.filename is not None:
110-
msg += f" in file {self.filename!r}"
111-
if self.lineno is not None:
112-
msg += f" on line {self.lineno}"
113-
msg += ":\n"
114-
msg += f"{self.message}\n"
115-
return msg
116-
117-
11898
@overload
11999
def warn_or_fail(
120100
*args: object,
@@ -669,7 +649,6 @@ class CLanguage(Language):
669649
def __init__(self, filename: str) -> None:
670650
super().__init__(filename)
671651
self.cpp = cpp.Monitor(filename)
672-
self.cpp.fail = fail # type: ignore[method-assign]
673652

674653
def parse_line(self, line: str) -> None:
675654
self.cpp.writeline(line)

Tools/clinic/cpp.py

+9-12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import sys
44
from typing import NoReturn
55

6+
from libclinic.errors import ParseError
7+
68

79
TokenAndCondition = tuple[str, str]
810
TokenStack = list[TokenAndCondition]
@@ -32,7 +34,7 @@ class Monitor:
3234
3335
Anyway this implementation seems to work well enough for the CPython sources.
3436
"""
35-
filename: str | None = None
37+
filename: str
3638
_: dc.KW_ONLY
3739
verbose: bool = False
3840

@@ -59,22 +61,16 @@ def condition(self) -> str:
5961
"""
6062
return " && ".join(condition for token, condition in self.stack)
6163

62-
def fail(self, *a: object) -> NoReturn:
63-
if self.filename:
64-
filename = " " + self.filename
65-
else:
66-
filename = ''
67-
print("Error at" + filename, "line", self.line_number, ":")
68-
print(" ", ' '.join(str(x) for x in a))
69-
sys.exit(-1)
64+
def fail(self, msg: str) -> NoReturn:
65+
raise ParseError(msg, filename=self.filename, lineno=self.line_number)
7066

7167
def writeline(self, line: str) -> None:
7268
self.line_number += 1
7369
line = line.strip()
7470

7571
def pop_stack() -> TokenAndCondition:
7672
if not self.stack:
77-
self.fail("#" + token + " without matching #if / #ifdef / #ifndef!")
73+
self.fail(f"#{token} without matching #if / #ifdef / #ifndef!")
7874
return self.stack.pop()
7975

8076
if self.continuation:
@@ -145,7 +141,7 @@ def pop_stack() -> TokenAndCondition:
145141

146142
if token in {'if', 'ifdef', 'ifndef', 'elif'}:
147143
if not condition:
148-
self.fail("Invalid format for #" + token + " line: no argument!")
144+
self.fail(f"Invalid format for #{token} line: no argument!")
149145
if token in {'if', 'elif'}:
150146
if not is_a_simple_defined(condition):
151147
condition = "(" + condition + ")"
@@ -155,7 +151,8 @@ def pop_stack() -> TokenAndCondition:
155151
else:
156152
fields = condition.split()
157153
if len(fields) != 1:
158-
self.fail("Invalid format for #" + token + " line: should be exactly one argument!")
154+
self.fail(f"Invalid format for #{token} line: "
155+
"should be exactly one argument!")
159156
symbol = fields[0]
160157
condition = 'defined(' + symbol + ')'
161158
if token == 'ifndef':

Tools/clinic/libclinic/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from typing import Final
22

3+
from .errors import (
4+
ClinicError,
5+
)
36
from .formatting import (
47
SIG_END_MARKER,
58
c_repr,
@@ -15,6 +18,9 @@
1518

1619

1720
__all__ = [
21+
# Error handling
22+
"ClinicError",
23+
1824
# Formatting helpers
1925
"SIG_END_MARKER",
2026
"c_repr",

Tools/clinic/libclinic/errors.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import dataclasses as dc
2+
3+
4+
@dc.dataclass
5+
class ClinicError(Exception):
6+
message: str
7+
_: dc.KW_ONLY
8+
lineno: int | None = None
9+
filename: str | None = None
10+
11+
def __post_init__(self) -> None:
12+
super().__init__(self.message)
13+
14+
def report(self, *, warn_only: bool = False) -> str:
15+
msg = "Warning" if warn_only else "Error"
16+
if self.filename is not None:
17+
msg += f" in file {self.filename!r}"
18+
if self.lineno is not None:
19+
msg += f" on line {self.lineno}"
20+
msg += ":\n"
21+
msg += f"{self.message}\n"
22+
return msg
23+
24+
25+
class ParseError(ClinicError):
26+
pass

0 commit comments

Comments
 (0)