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:
parent
651720e494
commit
649d64ae12
5 changed files with 52 additions and 31 deletions
37
lint-rules.h
37
lint-rules.h
|
|
@ -312,39 +312,26 @@ inline void AddProjectLintRules(Crafter::Configuration& cfg) {
|
|||
});
|
||||
|
||||
// A constant whose value is already a constant expression can be constexpr,
|
||||
// which puts it in the type system rather than leaving it to the optimiser.
|
||||
// Only fires when every token of the initialiser is a literal or an
|
||||
// operator, so `const int A = 1 << 4;` qualifies and
|
||||
// `const int B = Compute();` does not.
|
||||
// which moves it into the type system instead of leaving it to the
|
||||
// optimiser. Constant-ness of the initialiser is decided by clang's own
|
||||
// evaluator, so `sizeof(T)`, a fold over other constants and anything else
|
||||
// that folds all qualify, while `Compute()` does not.
|
||||
//
|
||||
// Restricted to scalars on purpose: for a class type, constexpr may be
|
||||
// unavailable even when the initialiser folds (a std::string constant
|
||||
// cannot be constexpr at namespace scope), and the evaluator answering
|
||||
// "this folds" is not the same question as "constexpr is legal here".
|
||||
cfg.AddAstLintRule("constexpr-constant", [](LintContext& ctx) {
|
||||
if (!IsCppFile(ctx)) return;
|
||||
std::span<const Crafter::LintToken> tokens = ctx.Tokens();
|
||||
for (const Crafter::LintDecl& decl : ctx.Decls()) {
|
||||
if (decl.kind != Crafter::LintDeclKind::Variable && decl.kind != Crafter::LintDeclKind::Field) continue;
|
||||
if (!decl.isConst || decl.isConstexpr || !decl.isScalar) continue;
|
||||
// A pointer's value is an address, which is rarely a constant
|
||||
// expression and never an interesting one to promote.
|
||||
if (decl.type.contains('*')) continue;
|
||||
|
||||
// Walk the declaration's own tokens, starting after the '='.
|
||||
bool sawAssign = false;
|
||||
bool allConstant = true;
|
||||
bool sawLiteral = false;
|
||||
for (const Crafter::LintToken& token : tokens) {
|
||||
if (token.offset < decl.nameOffset) continue;
|
||||
if (token.offset >= decl.end) break;
|
||||
std::string_view text = ctx.TokenText(token);
|
||||
if (!sawAssign) {
|
||||
if (text == "=") sawAssign = true;
|
||||
continue;
|
||||
}
|
||||
if (token.kind == Crafter::LintTokenKind::Literal) { sawLiteral = true; continue; }
|
||||
if (token.kind == Crafter::LintTokenKind::Punctuation) continue;
|
||||
allConstant = false; // an identifier or keyword: not a literal fold
|
||||
break;
|
||||
}
|
||||
if (!sawAssign || !sawLiteral || !allConstant) continue;
|
||||
ctx.Report(decl.line, std::format("'{}' is a literal constant — declare it constexpr", decl.name));
|
||||
if (!decl.isConstantInitialised) continue;
|
||||
if (decl.name.empty()) continue;
|
||||
ctx.Report(decl.line, std::format("'{}' has a constant initialiser — declare it constexpr", decl.name));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue