Skip to content

Fix #744 (Add regression tests for macro expansion) - #745

Open
danmar wants to merge 2 commits into
masterfrom
pr-macro-regression-tests
Open

danmar wants to merge 2 commits into
masterfrom
pr-macro-regression-tests

Conversation

@danmar

@danmar danmar commented Nov 8, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

@danmar
danmar force-pushed the pr-macro-regression-tests branch from 8294236 to f708fe9 Compare November 9, 2026 00:28
dmcppcheck and others added 2 commits November 9, 2026 01:41
C11 6.10.3.5 examples 3, 4, 5 and 7, the C23 __VA_OPT__ examples and invalid
## pastes. Known bugs are tested with the new TODO_ASSERT_EQUALS.

Tests for Token::macro, the locations of expanded tokens and the macro usage,
which are used by cppcheck. A new helper expandedTokens() writes each output
token with its macro name and location; the tokenMacro tests use it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…e an error

gcc outputs the name, simplecpp reports wrong number of parameters.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@danmar
danmar force-pushed the pr-macro-regression-tests branch from f708fe9 to 7ac04ea Compare November 9, 2026 00:42

@dmcppcheck dmcppcheck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

The PR only changes test.cpp. All tests pass, and I checked every TODO comment's claim against gcc and clang (the R/X name at the end of the file, the empty token from r(,)/t(,,), ==0 after the comment, a"s" should be an error). Each claim is correct, and every "wanted" value matches the reference preprocessors.

There is one new finding. tokenMacro10 uses ASSERT_EQUALS to lock in empty Token::macro for the results of # and ##, and its comment presents that as a general rule. It only holds for function-like macros. Object-like macros give the pasted token the macro name (#define XY x##y gives xy@XY). So does a function-like macro whose pasted token is built from body text, as in cat2(x) → xb with an empty name. cppcheck uses this field, so this looks like a known gap that should be a TODO, not a rule. I found no other problems.

Testing

Build and test suites

  • make -j8 testrunner simplecpp CXXOPTS=-Werror built without warnings.
  • ./testrunner passed with exit code 0 and no failures.
  • python3 run-tests.py passed: 176 tests, 21 skipped, 15 todos with 0 fixed, 0 failed.
  • python3 -m pytest integration_test.py passed: 128 tests.
  • ./selfcheck.sh passed with exit code 0.

The TODO claims, checked against gcc and clang

  • #define R() / R: gcc -E -P outputs R, so the wanted value "\nR" with no error is right.
  • cat(a,"s"): gcc (in both C and C++11 mode) reports pasting "a" and ""s"" does not give a valid preprocessing token, and clang reports a similar error. simplecpp outputs a"s" with no error.
  • { r(,) }: gcc outputs { }. simplecpp outputs { }, with an empty token between the braces.
  • The str(strncmp(...) // this goes away\n == 0) example: gcc gives ... '\\4') == 0". simplecpp gives '\\4') ==0".

Token::macro for # and ## results

I linked a small program against simplecpp.o and printed str@macro for each output token:

#define str(x) #x
#define XY x##y
#define cat(a,b) a##b
#define cat2(a) a##b
XY cat(x,y) cat2(x) str(z)

Output: xy@XY xy@ xb@ "z"@. The object-like macro keeps its macro name on the pasted token. In the function-like macros the name is empty, even for xb, which is pasted with the body token b.


Automated review of 7ac04ea by Claude. The pull request was built and tested locally.

Comment thread test.cpp

static void tokenMacro10()
{
// the results of # and ## have no macro name

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

This comment presents a general rule, but it only holds for function-like macros. In an object-like macro the pasted token keeps the macro name: #define XY x##y / XY gives xy@XY, while cat(x,y) gives xy with an empty macro. A paste that takes part of its text from the body also loses the name: #define cat2(a) a##b / cat2(x) gives xb with macro empty.

cppcheck uses Token::macro to recognise code that comes from a macro. This looks like an inconsistency rather than intended behaviour, so ASSERT_EQUALS would lock it in. I'd use TODO_ASSERT_EQUALS here (or at least a comment that says it is the current behaviour), and add an object-like ## case so that the difference is covered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants