fix(lint): constexpr-constant asks clang's evaluator, not the initialiser tokens

The first cut scanned the initialiser's tokens and required every one to be a
literal or an operator. That is exactly the kind of approximation this work has
been removing, and it was wrong in both directions:

  const int A = sizeof(Big);   missed — `sizeof` is a keyword
  const int B = Base + 1;      missed — `Base` is an identifier
  const auto C = 5_notConstexpr;  would have been reported, wrongly

clang_Cursor_Evaluate answers the question directly. Evaluating a variable
declaration evaluates its initialiser, so anything that folds is recognised and
a call result still is not. Costs nothing measurable — lint stays at ~15s.

Found one real case the token version could not see: an EShMessages fold over
three glslang enum constants in Crafter.Build-Shader.cpp. Promoting it to
constexpr then made `naming` ask for constant naming, since a constexpr
variable is a compile-time constant — so it is `Messages` now. Two rules
agreeing on the same declaration is the intended behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-07-31 02:05:37 +02:00
commit 649d64ae12
5 changed files with 52 additions and 31 deletions

View file

@ -478,20 +478,29 @@ int main() {
Check(!HasFinding(r.summary, "'each'"), "const-local: a range-for binding is not reported");
}
// constexpr-constant only promotes a constant whose initialiser is made of
// literals and operators, so a call result is left alone.
// constexpr-constant asks clang's constant evaluator, not the tokens of the
// initialiser. sizeof and a fold over other constants are constant
// expressions that no token scan can recognise; a call result is not.
{
RuleRun r = RunRule("int Compute();\n"
"constexpr int Base = 4;\n"
"struct Big { int a, b; };\n"
"void F() {\n"
" const int literal = 4;\n"
" const int folded = 1 << 4;\n"
" const int fromSizeof = sizeof(Big);\n"
" const int fromOtherConstant = Base + 1;\n"
" const int fromCall = Compute();\n"
" constexpr int already = 8;\n"
" (void)literal; (void)folded; (void)fromCall; (void)already;\n"
" (void)literal; (void)folded; (void)fromSizeof;\n"
" (void)fromOtherConstant; (void)fromCall; (void)already;\n"
"}\n",
"constexpr-constant", LintMode::Report);
Check(HasFinding(r.summary, "'literal'"), "constexpr: a literal constant is reported");
Check(HasFinding(r.summary, "'folded'"), "constexpr: an operator fold over literals is reported");
// Neither of these is reachable from a token scan of the initialiser.
Check(HasFinding(r.summary, "'fromSizeof'"), "constexpr: sizeof folds");
Check(HasFinding(r.summary, "'fromOtherConstant'"), "constexpr: a fold over another constant folds");
Check(!HasFinding(r.summary, "'fromCall'"), "constexpr: a call result is not a constant expression");
Check(!HasFinding(r.summary, "'already'"), "constexpr: an existing constexpr is not re-reported");
}