https://github.com/grigorypas updated https://github.com/llvm/llvm-project/pull/205440
>From cc759bc43123ea733fab22dccbdcb6a5ce83208d Mon Sep 17 00:00:00 2001 From: Grigory Pastukhov <[email protected]> Date: Tue, 23 Jun 2026 14:23:58 -0700 Subject: [PATCH] modernize-return-braced-init-list: skip types with an initializer_list constructor --- .../modernize/ReturnBracedInitListCheck.cpp | 30 ++++++++++ clang-tools-extra/docs/ReleaseNotes.rst | 5 +- .../modernize/return-braced-init-list.rst | 5 ++ .../modernize/return-braced-init-list.cpp | 55 ++++++++++++++++++- 4 files changed, 93 insertions(+), 2 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/ReturnBracedInitListCheck.cpp b/clang-tools-extra/clang-tidy/modernize/ReturnBracedInitListCheck.cpp index 65da5b1a2b403..0731707d7126b 100644 --- a/clang-tools-extra/clang-tidy/modernize/ReturnBracedInitListCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ReturnBracedInitListCheck.cpp @@ -7,7 +7,9 @@ //===----------------------------------------------------------------------===// #include "ReturnBracedInitListCheck.h" +#include "../utils/TypeTraits.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/DeclTemplate.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Lex/Lexer.h" @@ -16,6 +18,27 @@ using namespace clang::ast_matchers; namespace clang::tidy::modernize { +namespace { +bool hasInitListConstructor(const CXXRecordDecl *RD) { + if (RD == nullptr || !RD->hasDefinition()) + return false; + auto IsInitListCtor = [](const CXXConstructorDecl *Ctor) { + return Ctor->hasOneParamOrDefaultArgs() && + utils::type_traits::isStdInitializerList( + Ctor->getParamDecl(0)->getType().getNonReferenceType()); + }; + return llvm::any_of(RD->decls(), [&](const Decl *D) { + if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(D)) + return IsInitListCtor(Ctor); + if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(D)) + if (const auto *Ctor = + dyn_cast<CXXConstructorDecl>(FTD->getTemplatedDecl())) + return IsInitListCtor(Ctor); + return false; + }); +} +} // namespace + void ReturnBracedInitListCheck::registerMatchers(MatchFinder *Finder) { auto SemanticallyDifferentContainer = allOf( hasDeclaration( @@ -66,6 +89,13 @@ void ReturnBracedInitListCheck::check(const MatchFinder::MatchResult &Result) { if (ReturnType != ConstructType) return; + // Rewriting `T(args)` to a braced-init-list changes overload resolution when + // `T` has a std::initializer_list constructor: list-initialization prefers + // the initializer_list overload, so the braced form may silently select a + // different constructor than the parenthesized call. + if (hasInitListConstructor(ConstructType->getAsCXXRecordDecl())) + return; + auto Diag = diag(Loc, "avoid repeating the return type from the " "declaration; use a braced initializer list instead"); diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 81e5de4e0a868..d0782e79ad04a 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -625,7 +625,10 @@ Changes in existing checks - Improved :doc:`modernize-return-braced-init-list <clang-tidy/checks/modernize/return-braced-init-list>` check to apply fix-it - when type qualifiers and/or reference modifiers are used with parameters. + when type qualifiers and/or reference modifiers are used with parameters, and + to no longer rewrite the return value when the constructed type has a + ``std::initializer_list`` constructor, as the braced form could select a + different constructor. - Improved :doc:`modernize-type-traits <clang-tidy/checks/modernize/type-traits>` check to suggest usage of diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/return-braced-init-list.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/return-braced-init-list.rst index 2631d9056e851..c2feb31830f2c 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/return-braced-init-list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/return-braced-init-list.rst @@ -20,3 +20,8 @@ function definition and the return statement. Baz baz; return {baz}; } + +The check is not applied when the constructed type has a +``std::initializer_list`` constructor, since list-initialization would prefer +that constructor and the braced form could therefore select a different +constructor than the original call. diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/return-braced-init-list.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/return-braced-init-list.cpp index 5b0e7e7fb97f3..d8e5caa6f3c87 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/return-braced-init-list.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/return-braced-init-list.cpp @@ -66,8 +66,10 @@ Foo f6() { std::vector<int> vectorWithOneParameter() { int i7 = 1; + // No warning: std::vector has a std::initializer_list constructor, so + // `std::vector<int>{i7}` would select it (a 1-element vector) instead of + // `std::vector<int>(i7)` (an i7-element vector). Rewriting is unsound. return std::vector<int>(i7); - // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: avoid repeating the return type } std::vector<int> vectorIntWithTwoParameter() { @@ -84,6 +86,57 @@ std::vector<A> vectorRecordWithTwoParameter() { } +struct WithInitList { + WithInitList(Bar) {} + WithInitList(std::initializer_list<WithInitList>) {} +}; + +// No warning: braces would select the std::initializer_list constructor +// instead of WithInitList(Bar), changing overload resolution. +WithInitList initListCtorNoRewrite() { + Bar b; + return WithInitList(b); +} + +WithInitList initListCtorNoRewriteArg(Bar b) { + return WithInitList(b); +} + +// `std::initializer_list` as the first parameter but with a non-defaulted +// trailing parameter is NOT an initializer-list constructor, so braces cannot +// select it. A warning is expected. +struct NotInitListCtor { + NotInitListCtor(std::initializer_list<int>, int); + NotInitListCtor(Bar) {} +}; + +NotInitListCtor notInitListCtorRewrite(Bar b) { + return NotInitListCtor(b); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: avoid repeating the return type + // CHECK-FIXES: return {b}; +} + +struct TemplatedInitListCtor { + TemplatedInitListCtor(Bar) {} + template <typename T> + TemplatedInitListCtor(std::initializer_list<T>) {} +}; + +TemplatedInitListCtor templatedInitListCtorNoRewrite(Bar b) { + return TemplatedInitListCtor(b); +} + +// An initializer-list constructor may take the list by (cv-qualified) +// reference; braces still prefer it, so no rewrite. +struct RefInitListCtor { + RefInitListCtor(Bar) {} + RefInitListCtor(const std::initializer_list<RefInitListCtor> &) {} +}; + +RefInitListCtor refInitListCtorNoRewrite(Bar b) { + return RefInitListCtor(b); +} + Bar f8() { return {}; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
