4"""Remove redundant integer casts from TEST_ASSERT_EQ arguments.
6TEST_ASSERT_EQ internally widens both arguments to int64_t, so any outer
7cast like (int), (int32_t), (uint32_t), etc. is redundant and misleading:
8a (int) cast applied to a uint32_t enum truncates the value to 32-bit
9signed before the macro widens it again.
11This script strips those casts from the two argument positions only -- it
12does not touch casts inside other function calls.
14Limitation: a cast that TRUNCATES an out-of-range value is load-bearing, not
15redundant -- e.g. ``(uint16_t)~x``, where the bare ``~x`` promotes to a negative
16int. This script cannot tell those apart, so verify the full host suite after a
17run and hoist any load-bearing cast into a typed local before the assertion.
22from pathlib
import Path
24_TYPES =
r"u?int(?:8|16|32|64)?_t|int|size_t|ssize_t"
25_CAST_RE = re.compile(
r"^(\s*)\((?:" + _TYPES +
r")\)(.*)", re.DOTALL)
27MACRO =
"TEST_ASSERT_EQ("
30def _strip_leading_cast(text: str) -> str:
31 """Remove one leading integer cast from an argument, if present.
33 Only the outermost, leading cast: a cast deeper in the expression is
34 usually load-bearing and is left alone.
36 m = _CAST_RE.match(text)
37 return (m.group(1) + m.group(2))
if m
else text
40def _skip_token(text: str, i: int) -> int:
41 """Index just past a literal or comment at ``i``; ``i`` itself for plain code.
43 Escapes are handled, so an embedded quote does not end the literal early.
45 Bracket/comma scanning must never count a ``(``, ``)`` or ``,`` that lives
46 inside a ``"..."`` / ``'...'`` literal or a ``/* */`` / ``//`` comment, or the
47 depth accounting drifts and the scan runs off the end of the file.
50 nxt = text[i + 1]
if i + 1 < len(text)
else ""
61 if c ==
"/" and nxt ==
"/":
62 j = text.find(
"\n", i)
63 return len(text)
if j == -1
else j
64 if c ==
"/" and nxt ==
"*":
65 j = text.find(
"*/", i + 2)
66 return len(text)
if j == -1
else j + 2
70def _find_close_paren(text: str, start: int) -> int:
71 """Offset of the ``)`` closing the paren already opened before ``start``.
73 Skips literals and comments via ``_skip_token``, so a parenthesis inside a
74 string cannot unbalance the depth count.
78 while i < len(text)
and depth:
79 j = _skip_token(text, i)
92def process(content: str) -> str:
93 """Strip redundant leading casts from every TEST_ASSERT_EQ in one pass.
95 A SINGLE pass: removing a cast can expose another one beneath it, so this
96 is not guaranteed to reach a fixed point on its own -- callers should use
97 ``process_to_convergence``.
101 macro_len = len(MACRO)
103 idx = content.find(MACRO, pos)
105 out.append(content[pos:])
108 out.append(content[pos : idx + macro_len])
109 inner_start = idx + macro_len
110 close = _find_close_paren(content, inner_start)
111 inner = content[inner_start:close]
116 while k < len(inner):
117 j = _skip_token(inner, k)
126 elif ch ==
"," and depth == 0:
135 arg1 = _strip_leading_cast(inner[:split])
136 arg2 = _strip_leading_cast(inner[split + 1 :])
147def process_to_convergence(content: str) -> str:
148 """Re-run ``process`` until the text stops changing.
150 Bounded to ten iterations rather than looping until stable: a bug that
151 made the transform oscillate between two forms would otherwise hang the
152 pre-commit hook. In practice one pass fixes everything and a second
156 next_pass = process(content)
157 if next_pass == content:
164 """Rewrite the files named on argv in place, reporting how many changed.
166 Always writes -- there is no dry-run mode here, because check_assert_casts
167 is the read-only half of this pair and CI runs that one.
169 Returns 1 on the empty-argv usage error, 0 otherwise.
171 paths = [Path(p)
for p
in sys.argv[1:]]
173 print(
"usage: strip_assert_casts.py <file> [...]", file=sys.stderr)
177 original = path.read_text(encoding=
"ascii")
178 fixed = process_to_convergence(original)
179 if fixed != original:
180 path.write_text(fixed, encoding=
"ascii")
182 print(f
"fixed: {path}")
183 print(f
"{changed}/{len(paths)} file(s) modified")
187if __name__ ==
"__main__":
void main(void)
The application entry point Reset_Handler hands control to.