https://github.com/lucasly-ba updated https://github.com/llvm/llvm-project/pull/210554
>From 960ad52a6bcac10fe0f41295202d23b676cf359b Mon Sep 17 00:00:00 2001 From: Lucas Ly Ba <[email protected]> Date: Sun, 19 Jul 2026 00:10:25 +0200 Subject: [PATCH] [clang-tidy] Add `modernize-use-as-const` check Replaces a static_cast that only adds const to an lvalue with a call to std::as_const (C++17), which states the intent more clearly and cannot accidentally change the referenced type. The fix also inserts the <utility> include. Fixes #189665 --- .../clang-tidy/modernize/CMakeLists.txt | 1 + .../modernize/ModernizeTidyModule.cpp | 3 + .../clang-tidy/modernize/UseAsConstCheck.cpp | 101 ++++++++++++++++++ .../clang-tidy/modernize/UseAsConstCheck.h | 44 ++++++++ clang-tools-extra/docs/ReleaseNotes.rst | 7 ++ .../docs/clang-tidy/checks/list.rst | 1 + .../checks/modernize/use-as-const.rst | 44 ++++++++ .../modernize/use-as-const-ignore-macros.cpp | 22 ++++ .../checkers/modernize/use-as-const.cpp | 99 +++++++++++++++++ 9 files changed, 322 insertions(+) create mode 100644 clang-tools-extra/clang-tidy/modernize/UseAsConstCheck.cpp create mode 100644 clang-tools-extra/clang-tidy/modernize/UseAsConstCheck.h create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/use-as-const.rst create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-as-const-ignore-macros.cpp create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-as-const.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt index 2c5c44db587fe..78e9c02b16255 100644 --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -31,6 +31,7 @@ add_clang_library(clangTidyModernizeModule STATIC ShrinkToFitCheck.cpp TypeTraitsCheck.cpp UnaryStaticAssertCheck.cpp + UseAsConstCheck.cpp UseAutoCheck.cpp UseBoolLiteralsCheck.cpp UseConstraintsCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp index cc13da7535bcb..abeff9e72e117 100644 --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -31,6 +31,7 @@ #include "ShrinkToFitCheck.h" #include "TypeTraitsCheck.h" #include "UnaryStaticAssertCheck.h" +#include "UseAsConstCheck.h" #include "UseAutoCheck.h" #include "UseBoolLiteralsCheck.h" #include "UseConstraintsCheck.h" @@ -88,6 +89,8 @@ class ModernizeModule : public ClangTidyModule { CheckFactories.registerCheck<MinMaxUseInitializerListCheck>( "modernize-min-max-use-initializer-list"); CheckFactories.registerCheck<PassByValueCheck>("modernize-pass-by-value"); + CheckFactories.registerCheck<UseAsConstCheck>( + "modernize-use-as-const"); CheckFactories.registerCheck<UseDesignatedInitializersCheck>( "modernize-use-designated-initializers"); CheckFactories.registerCheck<UseIntegerSignComparisonCheck>( diff --git a/clang-tools-extra/clang-tidy/modernize/UseAsConstCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseAsConstCheck.cpp new file mode 100644 index 0000000000000..61582abd87ba7 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseAsConstCheck.cpp @@ -0,0 +1,101 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "UseAsConstCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +namespace { + +AST_MATCHER(Expr, isLValueExpr) { return Node.isLValue(); } + +AST_MATCHER_P(ExplicitCastExpr, hasSourceExpressionAsWritten, + ast_matchers::internal::Matcher<Expr>, InnerMatcher) { + const Expr *Sub = Node.getSubExprAsWritten(); + return Sub != nullptr && InnerMatcher.matches(*Sub, Finder, Builder); +} + +AST_MATCHER(ExplicitCastExpr, addsOnlyConst) { + const auto *Ref = Node.getTypeAsWritten()->getAs<LValueReferenceType>(); + if (Ref == nullptr) + return false; + const Expr *Sub = Node.getSubExprAsWritten(); + return Sub != nullptr && + Finder->getASTContext().hasSameType(Ref->getPointeeType(), + Sub->getType().withConst()); +} + +} // namespace + +UseAsConstCheck::UseAsConstCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + Inserter(Options.getLocalOrGlobal("IncludeStyle", + utils::IncludeSorter::IS_LLVM), + areDiagsSelfContained()), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} + +void UseAsConstCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", Inserter.getStyle()); + Options.store(Opts, "IgnoreMacros", IgnoreMacros); +} + +void UseAsConstCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + cxxStaticCastExpr( + unless(isTypeDependent()), + hasDestinationType(qualType(hasCanonicalType( + lValueReferenceType(pointee(qualType(isConstQualified())))))), + hasSourceExpressionAsWritten( + expr(unless(isTypeDependent()), isLValueExpr(), + unless(hasType(qualType(isConstQualified())))) + .bind("sub")), + addsOnlyConst()) + .bind("cast"), + this); +} + +void UseAsConstCheck::registerPPCallbacks(const SourceManager &SM, + Preprocessor *PP, + Preprocessor *ModuleExpanderPP) { + Inserter.registerPreprocessor(PP); +} + +void UseAsConstCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Cast = Result.Nodes.getNodeAs<CXXStaticCastExpr>("cast"); + const auto *Sub = Result.Nodes.getNodeAs<Expr>("sub"); + + const bool InMacro = + Cast->getBeginLoc().isMacroID() || Cast->getEndLoc().isMacroID(); + if (InMacro && IgnoreMacros) + return; + + auto Diag = + diag(Cast->getBeginLoc(), + "use 'std::as_const' instead of 'static_cast' to add 'const'"); + + if (InMacro) + return; + + const SourceManager &SM = *Result.SourceManager; + StringRef SubText = Lexer::getSourceText( + CharSourceRange::getTokenRange(Sub->getSourceRange()), SM, getLangOpts()); + if (SubText.empty()) + return; + + Diag << FixItHint::CreateReplacement(Cast->getSourceRange(), + ("std::as_const(" + SubText + ")").str()) + << Inserter.createIncludeInsertion(SM.getFileID(Cast->getBeginLoc()), + "<utility>"); +} + +} // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/modernize/UseAsConstCheck.h b/clang-tools-extra/clang-tidy/modernize/UseAsConstCheck.h new file mode 100644 index 0000000000000..ff18936a07b84 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseAsConstCheck.h @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEASCONSTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEASCONSTCHECK_H + +#include "../ClangTidyCheck.h" +#include "../utils/IncludeInserter.h" + +namespace clang::tidy::modernize { + +/// Suggests ``std::as_const`` for a ``static_cast`` that only adds ``const`` to +/// an lvalue, e.g. ``static_cast<const T &>(x)`` becomes ``std::as_const(x)``. +/// +/// For the user-facing documentation see: +/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-as-const.html +class UseAsConstCheck : public ClangTidyCheck { +public: + UseAsConstCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus17; + } + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + utils::IncludeInserter Inserter; + const bool IgnoreMacros; +}; + +} // namespace clang::tidy::modernize + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEASCONSTCHECK_H diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 69c3bcf67b8db..63988a8d22950 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -97,6 +97,13 @@ Improvements to clang-tidy New checks ^^^^^^^^^^ +- New :doc:`modernize-use-as-const + <clang-tidy/checks/modernize/use-as-const>` check. + + Replaces a ``static_cast`` that only adds ``const`` to an lvalue with a call + to ``std::as_const`` (available since C++17), which states the intent more + clearly and cannot accidentally change the referenced type. + New check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index 2a44dc78fbc89..24613445f3205 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -312,6 +312,7 @@ Clang-Tidy Checks :doc:`modernize-shrink-to-fit <modernize/shrink-to-fit>`, "Yes" :doc:`modernize-type-traits <modernize/type-traits>`, "Yes" :doc:`modernize-unary-static-assert <modernize/unary-static-assert>`, "Yes" + :doc:`modernize-use-as-const <modernize/use-as-const>`, "Yes" :doc:`modernize-use-auto <modernize/use-auto>`, "Yes" :doc:`modernize-use-bool-literals <modernize/use-bool-literals>`, "Yes" :doc:`modernize-use-constraints <modernize/use-constraints>`, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-as-const.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-as-const.rst new file mode 100644 index 0000000000000..5ff53b766f7aa --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-as-const.rst @@ -0,0 +1,44 @@ +.. title:: clang-tidy - modernize-use-as-const + +modernize-use-as-const +====================== + +Replaces a ``static_cast`` that only adds ``const`` to an lvalue with a call to +``std::as_const`` (available since C++17), which states the intent more clearly +and cannot accidentally change the referenced type. + +.. code-block:: c++ + + void use(const std::string &); + + void f(std::string s) { + use(static_cast<const std::string &>(s)); + } + +becomes + +.. code-block:: c++ + + void use(const std::string &); + + void f(std::string s) { + use(std::as_const(s)); + } + +Casts of an already ``const`` operand, and casts that change the type rather than +only adding ``const``, are left untouched. A cast whose operand is an rvalue is +also left untouched, because ``std::as_const`` is deleted for rvalues. + +Options +------- + +.. option:: IgnoreMacros + + If set to `true`, the check will not give warnings inside macros. Default + is `true`. A cast inside a macro is reported but not fixed, since the + replacement would rewrite the macro body for every expansion. + +.. option:: IncludeStyle + + A string specifying which include-style is used, `llvm` or `google`. Default + is `llvm`. diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-as-const-ignore-macros.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-as-const-ignore-macros.cpp new file mode 100644 index 0000000000000..0337a8b35b369 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-as-const-ignore-macros.cpp @@ -0,0 +1,22 @@ +// RUN: %check_clang_tidy -std=c++17-or-later %s modernize-use-as-const %t -- \ +// RUN: -config="{CheckOptions: {modernize-use-as-const.IgnoreMacros: false}}" + +// CHECK-FIXES: #include <utility> + +struct S {}; +void use(const S &); + +#define TO_CONST(x) static_cast<const S &>(x) + +// Reported at the expansion, but not fixed. +void in_macro(S obj) { + use(TO_CONST(obj)); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use 'std::as_const' instead of 'static_cast' to add 'const' [modernize-use-as-const] + // CHECK-FIXES: use(TO_CONST(obj)); +} + +void outside_macro(S obj) { + use(static_cast<const S &>(obj)); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use 'std::as_const' instead of 'static_cast' to add 'const' [modernize-use-as-const] + // CHECK-FIXES: use(std::as_const(obj)); +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-as-const.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-as-const.cpp new file mode 100644 index 0000000000000..94f8389a08332 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-as-const.cpp @@ -0,0 +1,99 @@ +// RUN: %check_clang_tidy -std=c++17-or-later %s modernize-use-as-const %t + +// CHECK-FIXES: #include <utility> + +struct S {}; +struct Derived : S {}; +void use(const S &); +void use_cv(const volatile S &); + +typedef S SAlias; +using ConstRef = const S &; + +void basic(S obj) { + use(static_cast<const S &>(obj)); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use 'std::as_const' instead of 'static_cast' to add 'const' [modernize-use-as-const] + // CHECK-FIXES: use(std::as_const(obj)); +} + +struct Wrap { + S m; +}; + +void other_lvalues(Wrap w, S *p, S arr[1]) { + use(static_cast<const S &>(w.m)); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use 'std::as_const' instead of 'static_cast' to add 'const' [modernize-use-as-const] + // CHECK-FIXES: use(std::as_const(w.m)); + use(static_cast<const S &>(*p)); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use 'std::as_const' instead of 'static_cast' to add 'const' [modernize-use-as-const] + // CHECK-FIXES: use(std::as_const(*p)); + use(static_cast<const S &>(arr[0])); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use 'std::as_const' instead of 'static_cast' to add 'const' [modernize-use-as-const] + // CHECK-FIXES: use(std::as_const(arr[0])); +} + +void via_typedef(SAlias obj) { + use(static_cast<const S &>(obj)); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use 'std::as_const' instead of 'static_cast' to add 'const' [modernize-use-as-const] + // CHECK-FIXES: use(std::as_const(obj)); +} + +void destination_typedef(S obj) { + use(static_cast<ConstRef>(obj)); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use 'std::as_const' instead of 'static_cast' to add 'const' [modernize-use-as-const] + // CHECK-FIXES: use(std::as_const(obj)); +} + +void keeps_volatile(volatile S vobj) { + use_cv(static_cast<const volatile S &>(vobj)); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::as_const' instead of 'static_cast' to add 'const' [modernize-use-as-const] + // CHECK-FIXES: use_cv(std::as_const(vobj)); +} + +// The instantiation must not report the cast a second time. +template <typename T> +struct Holder { + const S &get(S &s) { + return static_cast<const S &>(s); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use 'std::as_const' instead of 'static_cast' to add 'const' [modernize-use-as-const] + // CHECK-FIXES: return std::as_const(s); + } +}; +template struct Holder<int>; + +S make(); + +// std::as_const is deleted for rvalues, even though binding one to 'const S &' +// is well-formed. +void rvalue_negatives(S obj) { + use(static_cast<const S &>(S{})); + use(static_cast<const S &>(make())); + use(static_cast<const S &>(static_cast<S &&>(obj))); +} + +void negatives(const S cobj, Derived d, S obj) { + use(static_cast<const S &>(cobj)); + use(static_cast<const S &>(d)); + S copy = static_cast<S>(obj); + (void)copy; + S &&r = static_cast<S &&>(obj); + (void)r; +} + +// See use-as-const-ignore-macros.cpp for IgnoreMacros: false. +#define TO_CONST(x) static_cast<const S &>(x) +void in_macro(S obj) { + use(TO_CONST(obj)); +} + +// Dependent casts are skipped: when T is a reference type 'const T &' collapses +// and adds no const, so std::as_const would not be equivalent. +template <typename T> +const T &as_const_tmpl(T &x) { + return static_cast<const T &>(x); +} +void instantiate() { + S s; + as_const_tmpl(s); + as_const_tmpl<S &>(s); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
