https://github.com/vbvictor updated https://github.com/llvm/llvm-project/pull/184009
>From d3930322800d4c9bad5e75a6df20d36dbc30bd40 Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Mon, 11 May 2026 12:31:44 +0300 Subject: [PATCH] ~ --- .../CppCoreGuidelinesTidyModule.cpp | 3 + .../clang-tidy/misc/CMakeLists.txt | 1 + .../clang-tidy/misc/MiscTidyModule.cpp | 3 + .../misc/UseBracedInitializationCheck.cpp | 431 ++++++++ .../misc/UseBracedInitializationCheck.h | 37 + .../clang-tidy/utils/CMakeLists.txt | 1 + .../clang-tidy/utils/LexerUtils.h | 7 + .../clang-tidy/utils/NarrowingConversions.cpp | 107 ++ .../clang-tidy/utils/NarrowingConversions.h | 23 + clang-tools-extra/docs/ReleaseNotes.rst | 11 + .../use-braced-initialization.rst | 15 + .../docs/clang-tidy/checks/list.rst | 2 + .../checks/misc/use-braced-initialization.rst | 81 ++ .../checkers/Inputs/Headers/std/string | 4 + .../misc/use-braced-initialization-cxx17.cpp | 41 + .../misc/use-braced-initialization-cxx20.cpp | 284 +++++ .../misc/use-braced-initialization-cxx23.cpp | 15 + .../misc/use-braced-initialization.cpp | 977 ++++++++++++++++++ 18 files changed, 2043 insertions(+) create mode 100644 clang-tools-extra/clang-tidy/misc/UseBracedInitializationCheck.cpp create mode 100644 clang-tools-extra/clang-tidy/misc/UseBracedInitializationCheck.h create mode 100644 clang-tools-extra/clang-tidy/utils/NarrowingConversions.cpp create mode 100644 clang-tools-extra/clang-tidy/utils/NarrowingConversions.h create mode 100644 clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/use-braced-initialization.rst create mode 100644 clang-tools-extra/docs/clang-tidy/checks/misc/use-braced-initialization.rst create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization-cxx17.cpp create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization-cxx20.cpp create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization-cxx23.cpp create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization.cpp diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp index 402579adfb5d3..d1a21a10d740c 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp @@ -12,6 +12,7 @@ #include "../misc/ExplicitConstructorCheck.h" #include "../misc/NonPrivateMemberVariablesInClassesCheck.h" #include "../misc/UnconventionalAssignOperatorCheck.h" +#include "../misc/UseBracedInitializationCheck.h" #include "../modernize/AvoidCArraysCheck.h" #include "../modernize/MacroToEnumCheck.h" #include "../modernize/UseDefaultMemberInitCheck.h" @@ -136,6 +137,8 @@ class CppCoreGuidelinesModule : public ClangTidyModule { CheckFactories.registerCheck<SpecialMemberFunctionsCheck>( "cppcoreguidelines-special-member-functions"); CheckFactories.registerCheck<SlicingCheck>("cppcoreguidelines-slicing"); + CheckFactories.registerCheck<misc::UseBracedInitializationCheck>( + "cppcoreguidelines-use-braced-initialization"); CheckFactories.registerCheck<modernize::UseDefaultMemberInitCheck>( "cppcoreguidelines-use-default-member-init"); CheckFactories.registerCheck<UseEnumClassCheck>( diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt index 83a23b65f86db..8d65c551e42c9 100644 --- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt @@ -46,6 +46,7 @@ add_clang_library(clangTidyMiscModule STATIC UnusedParametersCheck.cpp UnusedUsingDeclsCheck.cpp UseAnonymousNamespaceCheck.cpp + UseBracedInitializationCheck.cpp UseInternalLinkageCheck.cpp LINK_LIBS diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp index 5a716606495db..e99104fd39669 100644 --- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp @@ -35,6 +35,7 @@ #include "UnusedParametersCheck.h" #include "UnusedUsingDeclsCheck.h" #include "UseAnonymousNamespaceCheck.h" +#include "UseBracedInitializationCheck.h" #include "UseInternalLinkageCheck.h" namespace clang::tidy { @@ -93,6 +94,8 @@ class MiscModule : public ClangTidyModule { "misc-unused-using-decls"); CheckFactories.registerCheck<UseAnonymousNamespaceCheck>( "misc-use-anonymous-namespace"); + CheckFactories.registerCheck<UseBracedInitializationCheck>( + "misc-use-braced-initialization"); CheckFactories.registerCheck<UseInternalLinkageCheck>( "misc-use-internal-linkage"); } diff --git a/clang-tools-extra/clang-tidy/misc/UseBracedInitializationCheck.cpp b/clang-tools-extra/clang-tidy/misc/UseBracedInitializationCheck.cpp new file mode 100644 index 0000000000000..3e8f72e7e7983 --- /dev/null +++ b/clang-tools-extra/clang-tidy/misc/UseBracedInitializationCheck.cpp @@ -0,0 +1,431 @@ +//===----------------------------------------------------------------------===// +// +// 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 "UseBracedInitializationCheck.h" +#include "../utils/LexerUtils.h" +#include "../utils/NarrowingConversions.h" +#include "clang/AST/ASTContext.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::misc { + +/// Returns \c true if \p From may be implicitly converted to \p To. +static bool mayConvertImplicitly(QualType From, QualType To) { + From = From.getNonReferenceType().getCanonicalType(); + To = To.getNonReferenceType().getCanonicalType(); + if (From == To) + return true; + + if ((From->isPointerType() || From->isArrayType()) && + To->isArithmeticType() && !To->isBooleanType()) + return false; + + if (const auto *FromEnum = From->getAs<EnumType>()) + if (FromEnum->getDecl()->isScoped()) + return false; + + if (const auto *FromRecord = From->getAsCXXRecordDecl(); + FromRecord && !To->isRecordType()) { + if (!FromRecord->hasDefinition()) + return true; + return !FromRecord->getVisibleConversionFunctions().empty(); + } + + return true; +} + +static SmallVector<const Expr *> +collectExplicitArgs(const CXXConstructExpr &Ctor) { + SmallVector<const Expr *> ExplicitArgs; + for (unsigned I = 0; I < Ctor.getNumArgs(); ++I) + if (!isa<CXXDefaultArgExpr>(Ctor.getArg(I))) + ExplicitArgs.push_back(Ctor.getArg(I)); + return ExplicitArgs; +} + +static bool hasInitListCtor(const CXXRecordDecl *RD, + ArrayRef<const Expr *> ExplicitArgs) { + if (!RD || !RD->hasDefinition()) + return false; + + for (const CXXConstructorDecl *CD : RD->ctors()) { + if (CD->getNumParams() == 0) + continue; + const QualType FirstParam = + CD->getParamDecl(0)->getType().getNonReferenceType(); + const auto *Init = FirstParam->getAsCXXRecordDecl(); + if (!Init || !Init->getDeclName().isIdentifier() || + Init->getName() != "initializer_list" || !Init->isInStdNamespace()) + continue; + // [dcl.init.list] p2: all other params must have defaults. + bool OthersDefaulted = true; + for (unsigned I = 1; I < CD->getNumParams(); ++I) + if (!CD->getParamDecl(I)->hasDefaultArg()) { + OthersDefaulted = false; + break; + } + if (!OthersDefaulted) + continue; + const auto *InitSpec = dyn_cast<ClassTemplateSpecializationDecl>(Init); + if (!InitSpec || InitSpec->getTemplateArgs().size() < 1) + return true; + const QualType InitType = InitSpec->getTemplateArgs()[0].getAsType(); + + if (llvm::all_of(ExplicitArgs, [&InitType](const Expr *Arg) { + return mayConvertImplicitly(Arg->getType(), InitType); + })) + return true; + } + + return false; +} + +namespace { + +AST_MATCHER_P(VarDecl, hasInitStyle, VarDecl::InitializationStyle, Style) { + return Node.getInitStyle() == Style; +} + +AST_MATCHER(Type, isDependentType) { return Node.isDependentType(); } + +AST_MATCHER(CXXConstructExpr, noMacroParens) { + const SourceRange Range = Node.getParenOrBraceRange(); + return Range.isValid() && !Range.getBegin().isMacroID() && + !Range.getEnd().isMacroID(); +} + +AST_MATCHER_P(CXXFunctionalCastExpr, hasSubExpr, + ast_matchers::internal::Matcher<Expr>, InnerMatcher) { + return InnerMatcher.matches(*Node.getSubExpr(), Finder, Builder); +} + +const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt, + CXXParenListInitExpr> + CxxParenListInitExpr; + +/// Matches 'CXXConstructExpr' whose target class has any constructor taking +/// 'std::initializer_list<Type>' where all arguments of the current call could +/// be converted to 'Type'. +AST_MATCHER(CXXConstructExpr, canOverlapWithInitListCtor) { + const CXXRecordDecl *RD = Node.getConstructor()->getParent(); + assert(RD && "CXXConstructExpr must have a parent CXXRecordDecl"); + const SmallVector<const Expr *> ExplicitArgs = collectExplicitArgs(Node); + return hasInitListCtor(RD, ExplicitArgs); +} + +AST_POLYMORPHIC_MATCHER(isListInit, + AST_POLYMORPHIC_SUPPORTED_TYPES(CXXFunctionalCastExpr, + CXXConstructExpr)) { + return Node.isListInitialization(); +} + +AST_MATCHER(CXXNewExpr, isParenInit) { + return Node.getInitializationStyle() == CXXNewInitializationStyle::Parens; +} + +AST_MATCHER(CXXNewExpr, allocatesAutoType) { + return Node.getAllocatedTypeSourceInfo()->getType()->getContainedAutoType() != + nullptr; +} + +AST_MATCHER(CXXNewExpr, allocatesRecordType) { + return Node.getType()->getPointeeType()->isRecordType(); +} + +struct ParenRange { + SourceLocation DiagLoc; + SourceLocation LParen; + SourceLocation RParen; +}; + +struct NarrowingInfo { + SourceLocation Loc; + QualType From; + QualType To; +}; + +} // namespace + +static std::optional<NarrowingInfo> +checkNarrowing(const Expr *Init, QualType TargetType, const ASTContext &Ctx) { + const Expr *OrigInit = Init->IgnoreImpCasts(); + const QualType From = OrigInit->getType(); + const QualType To = TargetType.getNonReferenceType(); + if (utils::isNarrowingConversion(From, To, OrigInit, Ctx)) + return NarrowingInfo{OrigInit->getBeginLoc(), From, To}; + return std::nullopt; +} + +/// Check whether a scalar initialization expression is narrowing. +static std::optional<NarrowingInfo> isScalarNarrowing(const Expr *Init, + QualType TargetType, + const ASTContext &Ctx) { + return checkNarrowing(Init, TargetType, Ctx); +} + +/// Returns a NarrowingInfo for every argument of \p Ctor that would narrow +/// under braced initialization. Empty if no argument narrows. +static SmallVector<NarrowingInfo> isCtorNarrowing(const CXXConstructExpr *Ctor, + const ASTContext &Ctx) { + SmallVector<NarrowingInfo> Result; + const CXXConstructorDecl *CD = Ctor->getConstructor(); + for (unsigned I = 0; I < Ctor->getNumArgs(); ++I) { + const Expr *Arg = Ctor->getArg(I); + if (isa<CXXDefaultArgExpr>(Arg)) + continue; + if (I >= CD->getNumParams()) + break; + if (auto Info = checkNarrowing(Arg, CD->getParamDecl(I)->getType(), Ctx)) + Result.push_back(*Info); + } + return Result; +} + +/// Returns a NarrowingInfo for every user-specified initializer in \p PLE +/// that would narrow. Empty if none narrow. +static SmallVector<NarrowingInfo> +isPLENarrowing(const CXXParenListInitExpr *PLE, const ASTContext &Ctx) { + SmallVector<NarrowingInfo> Result; + for (const Expr *Init : PLE->getUserSpecifiedInitExprs()) + if (auto Info = checkNarrowing(Init, Init->getType(), Ctx)) + Result.push_back(*Info); + return Result; +} + +static std::optional<ParenRange> +handleMemInit(const CXXCtorInitializer *MemInit, const CXXConstructExpr *Ctor, + const SourceManager &SM, const LangOptions &LangOpts) { + if (!Ctor) { + // CXXCtorInitializer stores both '(' and '{' locations in the same + // fields. Only transform parenthesized initialization. + const SourceLocation LParen = MemInit->getLParenLoc(); + if (!LParen.isValid() || !MemInit->getRParenLoc().isValid()) + return std::nullopt; + Token Tok; + if (Lexer::getRawToken(LParen, Tok, SM, LangOpts) || + Tok.isNot(tok::l_paren)) + return std::nullopt; + } + return ParenRange{MemInit->getSourceLocation(), MemInit->getLParenLoc(), + MemInit->getRParenLoc()}; +} + +static std::optional<ParenRange> handleScalarVar(const VarDecl *Var, + const SourceManager &SM, + const LangOptions &LangOpts) { + const Expr *Init = Var->getInit(); + SourceLocation InitBegin = Init->getBeginLoc(); + SourceLocation InitEnd = Init->getEndLoc(); + if (isa<DecompositionDecl>(Var)) + if (const auto *Ctor = dyn_cast<CXXConstructExpr>(Init); + Ctor && Ctor->getNumArgs() == 1 && InitBegin == Var->getLocation()) { + const Expr *Arg = Ctor->getArg(0); + InitBegin = Arg->getBeginLoc(); + InitEnd = Arg->getEndLoc(); + } + const std::optional<Token> LTok = + utils::lexer::findPreviousTokenSkippingComments(InitBegin, SM, LangOpts); + if (!LTok || LTok->isNot(tok::l_paren) || LTok->getLocation().isMacroID()) + return std::nullopt; + const std::optional<Token> RTok = + utils::lexer::findNextTokenSkippingComments(InitEnd, SM, LangOpts); + if (!RTok || RTok->isNot(tok::r_paren) || RTok->getLocation().isMacroID()) + return std::nullopt; + return ParenRange{Var->getLocation(), LTok->getLocation(), + RTok->getLocation()}; +} + +static std::optional<ParenRange> +handleScalarCast(const CXXFunctionalCastExpr *Cast) { + const SourceLocation LParen = Cast->getLParenLoc(); + const SourceLocation RParen = Cast->getRParenLoc(); + if (!LParen.isValid() || !RParen.isValid()) + return std::nullopt; + return ParenRange{Cast->getBeginLoc(), LParen, RParen}; +} + +static std::optional<ParenRange> handleScalarNew(const CXXNewExpr *New) { + const SourceRange InitRange = New->getDirectInitRange(); + if (!InitRange.isValid()) + return std::nullopt; + return ParenRange{New->getBeginLoc(), InitRange.getBegin(), + InitRange.getEnd()}; +} + +static std::optional<ParenRange> +handlePLE(const CXXParenListInitExpr *PLE, + const MatchFinder::MatchResult &Result) { + SourceLocation DiagLoc; + if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var_ple")) + DiagLoc = Var->getLocation(); + else if (const auto *Cast = + Result.Nodes.getNodeAs<CXXFunctionalCastExpr>("cast_ple")) + DiagLoc = Cast->getBeginLoc(); + else if (const auto *New = Result.Nodes.getNodeAs<CXXNewExpr>("new_ple")) + DiagLoc = New->getBeginLoc(); + else + llvm_unreachable("No context for CXXParenListInitExpr"); + return ParenRange{DiagLoc, PLE->getBeginLoc(), PLE->getEndLoc()}; +} + +void UseBracedInitializationCheck::registerMatchers(MatchFinder *Finder) { + const auto GoodCtor = + allOf(noMacroParens(), unless(canOverlapWithInitListCtor()), + unless(isListInitialization())); + const auto GoodCtorExpr = cxxConstructExpr(GoodCtor).bind("ctor"); + const auto GoodVar = + allOf(unless(hasType(isDependentType())), unless(hasType(autoType()))); + const auto HasGoodCtorOrIsScalar = + anyOf(hasInitializer(ignoringImplicit(GoodCtorExpr)), + unless(hasInitializer(ignoringImplicit(cxxConstructExpr())))); + + Finder->addMatcher(varDecl(unless(decompositionDecl()), + hasInitStyle(VarDecl::CallInit), GoodVar, + HasGoodCtorOrIsScalar) + .bind("var"), + this); + Finder->addMatcher(decompositionDecl(hasInitStyle(VarDecl::CallInit), + unless(hasType(isDependentType()))) + .bind("var"), + this); + Finder->addMatcher(cxxTemporaryObjectExpr(GoodCtor).bind("ctor"), this); + Finder->addMatcher( + traverse( + TK_AsIs, + cxxFunctionalCastExpr( + unless(hasType(autoType())), unless(hasType(isDependentType())), + unless(isInTemplateInstantiation()), unless(isListInit()), + anyOf(allOf(hasType(hasUnqualifiedDesugaredType(recordType())), + hasSubExpr(ignoringImplicit(cxxConstructExpr( + unless(isListInit()), + unless(canOverlapWithInitListCtor()))))), + unless(hasType(hasUnqualifiedDesugaredType(recordType()))))) + .bind("func_cast")), + this); + Finder->addMatcher( + cxxNewExpr(unless(hasType(isDependentType())), + anyOf(has(ignoringImplicit(GoodCtorExpr)), + allOf(unless(allocatesRecordType()), isParenInit(), + unless(allocatesAutoType())))) + .bind("new_expr"), + this); + Finder->addMatcher( + cxxCtorInitializer( + isWritten(), + anyOf(withInitializer(ignoringImplicit(GoodCtorExpr)), + unless(withInitializer(ignoringImplicit(cxxConstructExpr()))))) + .bind("ctor_init"), + this); + + if (getLangOpts().CPlusPlus20) { + auto GoodPLE = CxxParenListInitExpr().bind("ple"); + Finder->addMatcher(varDecl(hasInitStyle(VarDecl::ParenListInit), + hasInitializer(GoodPLE), GoodVar) + .bind("var_ple"), + this); + Finder->addMatcher(cxxFunctionalCastExpr(has(GoodPLE)).bind("cast_ple"), + this); + Finder->addMatcher(cxxNewExpr(has(GoodPLE)).bind("new_ple"), this); + } +} + +namespace { +struct MatchAnalysis { + std::optional<ParenRange> Range; + SmallVector<NarrowingInfo> Narrowings; +}; +} // namespace + +static MatchAnalysis analyzeMatch(const MatchFinder::MatchResult &Result, + const ASTContext &Ctx, + const SourceManager &SM, + const LangOptions &LangOpts) { + const auto ScalarNarrowing = + [&Ctx](const Expr *Init, QualType Target) -> SmallVector<NarrowingInfo> { + if (!Init) + return {}; + if (auto Info = isScalarNarrowing(Init, Target, Ctx)) + return {*Info}; + return {}; + }; + + MatchAnalysis Res; + if (const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor")) { + // A 'ctor' binding can be standalone or nested inside Var/New/MemInit; + // it always carries the parens and arguments we need. + const auto *CtorInit = + Result.Nodes.getNodeAs<CXXCtorInitializer>("ctor_init"); + Res.Range = + CtorInit + ? handleMemInit(CtorInit, Ctor, SM, LangOpts) + : std::optional<ParenRange>(ParenRange{ + Ctor->getBeginLoc(), Ctor->getParenOrBraceRange().getBegin(), + Ctor->getParenOrBraceRange().getEnd()}); + Res.Narrowings = isCtorNarrowing(Ctor, Ctx); + } else if (const auto *PLE = + Result.Nodes.getNodeAs<CXXParenListInitExpr>("ple")) { + Res.Range = handlePLE(PLE, Result); + Res.Narrowings = isPLENarrowing(PLE, Ctx); + } else if (const auto *CtorInit = + Result.Nodes.getNodeAs<CXXCtorInitializer>("ctor_init")) { + Res.Range = handleMemInit(CtorInit, /*Ctor=*/nullptr, SM, LangOpts); + if (CtorInit->isMemberInitializer()) + Res.Narrowings = ScalarNarrowing(CtorInit->getInit(), + CtorInit->getMember()->getType()); + } else if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var")) { + Res.Range = handleScalarVar(Var, SM, LangOpts); + Res.Narrowings = ScalarNarrowing(Var->getInit(), Var->getType()); + } else if (const auto *Cast = + Result.Nodes.getNodeAs<CXXFunctionalCastExpr>("func_cast")) { + Res.Range = handleScalarCast(Cast); + Res.Narrowings = ScalarNarrowing(Cast->getSubExpr(), Cast->getType()); + } else if (const auto *New = Result.Nodes.getNodeAs<CXXNewExpr>("new_expr")) { + Res.Range = handleScalarNew(New); + Res.Narrowings = + ScalarNarrowing(New->getInitializer(), New->getAllocatedType()); + } else { + llvm_unreachable("No matches found"); + } + return Res; +} + +void UseBracedInitializationCheck::check( + const MatchFinder::MatchResult &Result) { + const SourceManager &SM = *Result.SourceManager; + const LangOptions &LangOpts = Result.Context->getLangOpts(); + const ASTContext &Ctx = *Result.Context; + + const auto [Range, Narrowings] = analyzeMatch(Result, Ctx, SM, LangOpts); + + if (!Range || Range->LParen.isMacroID() || Range->RParen.isMacroID()) + return; + + if (Narrowings.empty()) { + diag(Range->DiagLoc, "use braced initialization instead of parenthesized " + "initialization") + << FixItHint::CreateReplacement(Range->LParen, "{") + << FixItHint::CreateReplacement(Range->RParen, "}"); + return; + } + + diag(Range->DiagLoc, "use braced initialization instead of parenthesized " + "initialization"); + for (const auto &[I, N] : llvm::enumerate(Narrowings)) { + auto Note = diag(N.Loc, + "narrowing conversion from %0 to %1 will be ill-formed in " + "braced initialization", + DiagnosticIDs::Note); + Note << N.From << N.To; + if (I == 0) + Note << FixItHint::CreateReplacement(Range->LParen, "{") + << FixItHint::CreateReplacement(Range->RParen, "}"); + } +} + +} // namespace clang::tidy::misc diff --git a/clang-tools-extra/clang-tidy/misc/UseBracedInitializationCheck.h b/clang-tools-extra/clang-tidy/misc/UseBracedInitializationCheck.h new file mode 100644 index 0000000000000..143658df58edc --- /dev/null +++ b/clang-tools-extra/clang-tidy/misc/UseBracedInitializationCheck.h @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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_MISC_USEBRACEDINITIALIZATIONCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_USEBRACEDINITIALIZATIONCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::misc { + +/// Suggests replacing parenthesized initialization with braced +/// initialization. +/// +/// For the user-facing documentation see: +/// https://clang.llvm.org/extra/clang-tidy/checks/misc/use-braced-initialization.html +class UseBracedInitializationCheck : public ClangTidyCheck { +public: + UseBracedInitializationCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus11; + } +}; + +} // namespace clang::tidy::misc + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_USEBRACEDINITIALIZATIONCHECK_H diff --git a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt index b83a1e9a77182..02201b6eb83b0 100644 --- a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt @@ -21,6 +21,7 @@ add_clang_library(clangTidyUtils STATIC LexerUtils.cpp Matchers.cpp NamespaceAliaser.cpp + NarrowingConversions.cpp OptionsUtils.cpp RenamerClangTidyCheck.cpp TransformerClangTidyCheck.cpp diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.h b/clang-tools-extra/clang-tidy/utils/LexerUtils.h index 9b2daf74965e4..98858f105c191 100644 --- a/clang-tools-extra/clang-tidy/utils/LexerUtils.h +++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.h @@ -94,6 +94,13 @@ SourceLocation findNextAnyTokenKind(SourceLocation Start, } } +/// Finds previous token that's not a comment. +inline std::optional<Token> +findPreviousTokenSkippingComments(SourceLocation Start, const SourceManager &SM, + const LangOptions &LangOpts) { + return Lexer::findPreviousToken(Start, SM, LangOpts, false); +} + // Finds next token, possibly a comment. inline std::optional<Token> findNextTokenIncludingComments(SourceLocation Start, const SourceManager &SM, diff --git a/clang-tools-extra/clang-tidy/utils/NarrowingConversions.cpp b/clang-tools-extra/clang-tidy/utils/NarrowingConversions.cpp new file mode 100644 index 0000000000000..a47c2c1d745fa --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/NarrowingConversions.cpp @@ -0,0 +1,107 @@ +//===----------------------------------------------------------------------===// +// +// 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 "NarrowingConversions.h" + +namespace clang::tidy::utils { + +// [dcl.init.list]/p7.2: long double -> double or float, or double -> float; +// unless the source is a constant expression whose value fits in the target +// representation range. +static bool isFloatToFloatNarrowing(QualType From, QualType To, + const Expr *Init, const ASTContext &Ctx) { + if (Ctx.getFloatingTypeOrder(From, To) <= 0) + return false; + if (Init->isValueDependent()) + return true; + Expr::EvalResult EvalResult; + if (!Init->EvaluateAsRValue(EvalResult, Ctx) || !EvalResult.Val.isFloat()) + return true; + llvm::APFloat Value = EvalResult.Val.getFloat(); + bool LosesInfo = false; + const llvm::APFloat::opStatus Status = + Value.convert(Ctx.getFloatTypeSemantics(To), + llvm::APFloat::rmNearestTiesToEven, &LosesInfo); + return (Status & llvm::APFloat::opOverflow) != 0; +} + +// [dcl.init.list]/p7.3: integer or unscoped enum to floating-point; unless +// the source is a constant expression that fits and round-trips exactly. +static bool isIntToFloatNarrowing(const Expr *Init, QualType To, + const ASTContext &Ctx) { + if (Init->isValueDependent()) + return true; + std::optional<llvm::APSInt> OptVal = Init->getIntegerConstantExpr(Ctx); + if (!OptVal) + return true; + llvm::APFloat FPVal(Ctx.getFloatTypeSemantics(To)); + const llvm::APFloat::opStatus Status = FPVal.convertFromAPInt( + *OptVal, OptVal->isSigned(), llvm::APFloat::rmNearestTiesToEven); + if (Status != llvm::APFloat::opOK) + return true; + llvm::APSInt RoundTrip(OptVal->getBitWidth(), !OptVal->isSigned()); + bool IsExact = false; + FPVal.convertToInteger(RoundTrip, llvm::APFloat::rmTowardZero, &IsExact); + return !IsExact || RoundTrip != *OptVal; +} + +// [dcl.init.list]/p7.4: integer or unscoped enum to integer that cannot +// represent all values; unless source is a constant expression that fits. +static bool isIntToIntNarrowing(QualType From, QualType To, const Expr *Init, + const ASTContext &Ctx) { + const bool FromSigned = From->isSignedIntegerOrEnumerationType(); + const unsigned FromWidth = Ctx.getIntWidth(From); + const bool ToSigned = To->isSignedIntegerOrEnumerationType(); + const unsigned ToWidth = Ctx.getIntWidth(To); + + if ((FromWidth < ToWidth + (FromSigned == ToSigned)) && + !(FromSigned && !ToSigned)) + return false; + + if (Init->isValueDependent()) + return true; + std::optional<llvm::APSInt> OptVal = Init->getIntegerConstantExpr(Ctx); + if (!OptVal) + return true; + const llvm::APSInt Val = OptVal->extend(OptVal->getBitWidth() + 1); + llvm::APSInt Converted = Val; + Converted = Converted.trunc(ToWidth); + Converted.setIsSigned(ToSigned); + Converted = Converted.extend(Val.getBitWidth()); + Converted.setIsSigned(Val.isSigned()); + return Converted != Val; +} + +bool isNarrowingConversion(QualType From, QualType To, const Expr *Init, + const ASTContext &Ctx) { + From = From.getCanonicalType().getUnqualifiedType(); + To = To.getCanonicalType().getUnqualifiedType(); + if (From == To) + return false; + + if (To->isBooleanType() && + (From->isPointerType() || From->isMemberPointerType())) + return true; + + if (From->isRealFloatingType() && To->isIntegralOrEnumerationType()) + return true; + + if (From->isRealFloatingType() && To->isRealFloatingType()) + return isFloatToFloatNarrowing(From, To, Init, Ctx); + + if (From->isIntegralOrUnscopedEnumerationType() && To->isRealFloatingType()) + return isIntToFloatNarrowing(Init, To, Ctx); + + if (From->isIntegralOrUnscopedEnumerationType() && + To->isIntegralOrUnscopedEnumerationType()) + return isIntToIntNarrowing(From, To, Init, Ctx); + + return false; +} + +} // namespace clang::tidy::utils diff --git a/clang-tools-extra/clang-tidy/utils/NarrowingConversions.h b/clang-tools-extra/clang-tidy/utils/NarrowingConversions.h new file mode 100644 index 0000000000000..6fac569da4398 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/NarrowingConversions.h @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// 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_UTILS_NARROWINGCONVERSIONS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_NARROWINGCONVERSIONS_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/ExprCXX.h" + +namespace clang::tidy::utils { + +/// Returns \c true if converting \p Init from \p From to \p To is narrowing +bool isNarrowingConversion(QualType From, QualType To, const Expr *Init, + const ASTContext &Ctx); + +} // namespace clang::tidy::utils + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_NARROWINGCONVERSIONS_H diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index c5a6857c7077f..eb0278f8f1e48 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -181,6 +181,12 @@ New checks ``llvm::to_vector(llvm::make_filter_range(...))`` that can be replaced with ``llvm::map_to_vector`` and ``llvm::filter_to_vector``. +- New :doc:`misc-use-braced-initialization + <clang-tidy/checks/misc/use-braced-initialization>` check. + + Suggests replacing parenthesized initialization with braced + initialization. + - New :doc:`modernize-use-std-bit <clang-tidy/checks/modernize/use-std-bit>` check. @@ -241,6 +247,11 @@ New check aliases <clang-tidy/checks/misc/explicit-constructor>`. The `cppcoreguidelines-explicit-constructor` name is kept as an alias. +- New :doc:`cppcoreguidelines-use-braced-initialization + <clang-tidy/checks/cppcoreguidelines/use-braced-initialization>` check + alias for :doc:`misc-use-braced-initialization + <clang-tidy/checks/misc/use-braced-initialization>`. + - Renamed :doc:`google-explicit-constructor <clang-tidy/checks/google/explicit-constructor>` to :doc:`misc-explicit-constructor diff --git a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/use-braced-initialization.rst b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/use-braced-initialization.rst new file mode 100644 index 0000000000000..bc7b9c3084557 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/use-braced-initialization.rst @@ -0,0 +1,15 @@ +.. title:: clang-tidy - cppcoreguidelines-use-braced-initialization +.. meta:: + :http-equiv=refresh: 5;URL=../misc/use-braced-initialization.html + +cppcoreguidelines-use-braced-initialization +=========================================== + +This check implements `ES.23 +<https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es23-prefer-the--initializer-syntax>`_ +from the C++ Core Guidelines. + +The `cppcoreguidelines-use-braced-initialization` check is an alias, +please see +:doc:`misc-use-braced-initialization <../misc/use-braced-initialization>` +for more information. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index f193c0920ec1b..15c61f903a8e5 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -286,6 +286,7 @@ Clang-Tidy Checks :doc:`misc-unused-parameters <misc/unused-parameters>`, "Yes" :doc:`misc-unused-using-decls <misc/unused-using-decls>`, "Yes" :doc:`misc-use-anonymous-namespace <misc/use-anonymous-namespace>`, + :doc:`misc-use-braced-initialization <misc/use-braced-initialization>`, "Yes" :doc:`misc-use-internal-linkage <misc/use-internal-linkage>`, "Yes" :doc:`modernize-avoid-bind <modernize/avoid-bind>`, "Yes" :doc:`modernize-avoid-c-arrays <modernize/avoid-c-arrays>`, @@ -594,6 +595,7 @@ Check aliases :doc:`cppcoreguidelines-noexcept-move-operations <cppcoreguidelines/noexcept-move-operations>`, :doc:`performance-noexcept-move-constructor <performance/noexcept-move-constructor>`, "Yes" :doc:`cppcoreguidelines-noexcept-swap <cppcoreguidelines/noexcept-swap>`, :doc:`performance-noexcept-swap <performance/noexcept-swap>`, "Yes" :doc:`cppcoreguidelines-non-private-member-variables-in-classes <cppcoreguidelines/non-private-member-variables-in-classes>`, :doc:`misc-non-private-member-variables-in-classes <misc/non-private-member-variables-in-classes>`, + :doc:`cppcoreguidelines-use-braced-initialization <cppcoreguidelines/use-braced-initialization>`, :doc:`misc-use-braced-initialization <misc/use-braced-initialization>`, "Yes" :doc:`cppcoreguidelines-use-default-member-init <cppcoreguidelines/use-default-member-init>`, :doc:`modernize-use-default-member-init <modernize/use-default-member-init>`, "Yes" :doc:`fuchsia-header-anon-namespaces <fuchsia/header-anon-namespaces>`, :doc:`misc-anonymous-namespace-in-header <misc/anonymous-namespace-in-header>`, :doc:`fuchsia-multiple-inheritance <fuchsia/multiple-inheritance>`, :doc:`misc-multiple-inheritance <misc/multiple-inheritance>`, diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc/use-braced-initialization.rst b/clang-tools-extra/docs/clang-tidy/checks/misc/use-braced-initialization.rst new file mode 100644 index 0000000000000..86102f915cd4b --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/misc/use-braced-initialization.rst @@ -0,0 +1,81 @@ +.. title:: clang-tidy - misc-use-braced-initialization + +misc-use-braced-initialization +============================== + +Suggests replacing parenthesized initialization with braced initialization. + +Braced initialization has several advantages: + +- **Catches narrowing conversions.** ``int x{3.14}`` is a compile-time error, + while ``int x(3.14)`` silently truncates. +- **Uniform syntax.** Braces work consistently for aggregates, containers, and + constructors, giving a single initialization style across all types. + +For example: + +.. code-block:: c++ + + struct Matrix { + Matrix(int rows, int cols); + }; + + // Variable declarations: + Matrix m(3, 4); // -> Matrix m{3, 4}; + int n(42); // -> int n{42}; + + // Copy initialization: + Matrix m = Matrix(3, 4); // -> Matrix m = Matrix{3, 4}; + + // Temporary objects: + use(Matrix(3, 4)); // -> use(Matrix{3, 4}); + + // New expressions: + auto *p = new Matrix(3, 4); // -> auto *p = new Matrix{3, 4}; + + // Member initializer lists: + struct Widget : Matrix { + int value; + Widget() : Matrix(3, 4), value(0) {} + // -> Widget() : Matrix{3, 4}, value{0} {} + }; + +The check skips cases where changing from ``()`` to ``{}`` would alter program +semantics: + +- Constructor calls where a ``std::initializer_list`` overload could be + preferred under braced initialization and all arguments are convertible + to the list's element type, which would silently change semantics. + For example, ``std::vector<int> v(5, 1)`` is skipped because + ``std::vector<int> v{5, 1}`` would create a two-element vector instead + of five ones, but ``std::string s("hello")`` is diagnosed because + ``const char*`` cannot implicitly convert to ``char``. +- Direct-initialized ``auto`` variables, where deduction rules may differ + between C++ standards. +- Expressions in macro expansions. + +Limitations +----------- + +Braced initialization prohibits implicit narrowing conversions. When +the check detects that changing ``()`` to ``{}`` would introduce a +narrowing conversion, it emits the warning with a note and attaches +the brace fix-it to the note rather than the warning. The fix is not +applied by default, but can be opted into with ``--fix-notes``. + +.. code-block:: c++ + + struct Foo { + Foo(short n); + }; + + int n = 10; + Foo f(n); // warning + note: narrowing from 'int' to 'short' + // fix-it on the note, applied only with --fix-notes + +References +---------- + +This check corresponds to the C++ Core Guidelines rule +`ES.23 +<https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es23-prefer-the--initializer-syntax>`_. diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/string b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/string index dbebeaaa46514..399fd0116f318 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/string +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/string @@ -4,6 +4,7 @@ // For size_t #include "string.h" #include "memory" +#include "initializer_list" typedef unsigned __INT16_TYPE__ char16; typedef unsigned __INT32_TYPE__ char32; @@ -26,6 +27,7 @@ struct basic_string { basic_string(const C *p, size_type count); basic_string(const C *b, const C *e); basic_string(size_t, C); + basic_string(std::initializer_list<C>); operator basic_string_view<C, T>() const; ~basic_string(); @@ -202,6 +204,7 @@ bool operator!=(const char*, const std::string_view&); size_t strlen(const char* str); +#if __cplusplus >= 201402L inline namespace literals { inline namespace string_literals { string operator""s(const char *, size_t); @@ -210,6 +213,7 @@ inline namespace string_view_literals { string_view operator""sv(const char *, size_t); } } +#endif // __cplusplus >= 201402L } #endif // _STRING_ diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization-cxx17.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization-cxx17.cpp new file mode 100644 index 0000000000000..6ba61b416a527 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization-cxx17.cpp @@ -0,0 +1,41 @@ +// RUN: %check_clang_tidy -std=c++17-or-later %s misc-use-braced-initialization %t -- --fix-notes + +struct Agg { + int a, b; +}; + +void structured_binding_paren() { + auto [a, b](Agg{1, 2}); + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use braced initialization + // CHECK-FIXES: auto [a, b]{Agg{1, 2}}; +} + +void structured_binding_braced() { + auto [a, b] = Agg{1, 2}; +} + +void structured_binding_paren_call() { + Agg make_agg(); + auto [a, b](make_agg()); + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use braced initialization + // CHECK-FIXES: auto [a, b]{make_agg()}; +} + +void structured_binding_paren_copy(const Agg &g) { + auto [a, b](g); + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use braced initialization + // CHECK-FIXES: auto [a, b]{g}; +} + +void structured_binding_paren_ref(Agg &g) { + auto &[a, b](g); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-FIXES: auto &[a, b]{g}; +} + +void structured_binding_paren_array() { + int arr[2] = {1, 2}; + auto [a, b](arr); + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use braced initialization + // CHECK-FIXES: auto [a, b]{arr}; +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization-cxx20.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization-cxx20.cpp new file mode 100644 index 0000000000000..897996085ef87 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization-cxx20.cpp @@ -0,0 +1,284 @@ +// RUN: %check_clang_tidy -std=c++20-or-later %s misc-use-braced-initialization %t -- --fix-notes + +#include <vector> + +struct Agg { + int a, b; +}; + +struct AggDefault { + int a = 0; + int b; +}; + +struct Nested { + Agg x; + int y; +}; + +struct Takes { + Takes(Agg); +}; + +struct Simple { + Simple(int); +}; + +void basic_aggregate() { + Agg d(1, 2); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use braced initialization instead of parenthesized initialization [misc-use-braced-initialization] + // CHECK-FIXES: Agg d{1, 2}; +} + +void aggregate_default_member() { + AggDefault ad(1, 2); + // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: use braced initialization + // CHECK-FIXES: AggDefault ad{1, 2}; +} + +void nested_aggregate_braced_inner() { + Nested n(Agg{1, 2}, 3); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use braced initialization + // CHECK-FIXES: Nested n{Agg{1, 2}, 3}; +} + +void nested_aggregate_paren_inner() { + Nested n(Agg(1, 2), 3); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:12: warning: use braced initialization + // CHECK-FIXES: Nested n{Agg{1, 2}, 3}; +} + +void aggregate_multi_decl() { + Agg a(1, 2), b(3, 4); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:16: warning: use braced initialization + // CHECK-FIXES: Agg a{1, 2}, b{3, 4}; +} + +void aggregate_temporary() { + Agg(1, 2); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use braced initialization + // CHECK-FIXES: Agg{1, 2}; +} + +void aggregate_temporary_cast_to_void() { + (void)Agg(1, 2); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-FIXES: (void)Agg{1, 2}; +} + +void aggregate_auto() { + auto d = Agg(1, 2); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use braced initialization + // CHECK-FIXES: auto d = Agg{1, 2}; +} + +Agg return_aggregate() { + return Agg(1, 2); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use braced initialization + // CHECK-FIXES: return Agg{1, 2}; +} + +void func_arg(Agg); +void aggregate_as_argument() { + func_arg(Agg(1, 2)); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use braced initialization + // CHECK-FIXES: func_arg(Agg{1, 2}); +} + +void designated_as_arg() { + Takes t({.a = 1, .b = 2}); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-FIXES: Takes t{{[{][{]}}.a = 1, .b = 2{{[}][}]}}; +} + +void aggregate_new() { + Agg *p = new Agg(1, 2); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use braced initialization + // CHECK-FIXES: Agg *p = new Agg{1, 2}; + (void)p; +} + +struct L1 { + int a, b; +}; + +struct L2 { + L1 x; + int y; +}; + +struct L3 { + L2 m; + int z; +}; + +struct L4 { + L3 n; + int w; +}; + +void nested_agg_two_levels() { + L2 v(L1(1, 2), 3); + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:8: warning: use braced initialization + // CHECK-FIXES: L2 v{L1{1, 2}, 3}; +} + +void nested_agg_three_levels() { + L3 v(L2(L1(1, 2), 3), 4); + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:8: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-3]]:11: warning: use braced initialization + // CHECK-FIXES: L3 v{L2{L1{1, 2}, 3}, 4}; +} + +void nested_agg_four_levels() { + L4 v(L3(L2(L1(1, 2), 3), 4), 5); + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:8: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-3]]:11: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-4]]:14: warning: use braced initialization + // CHECK-FIXES: L4 v{L3{L2{L1{1, 2}, 3}, 4}, 5}; +} + +void nested_agg_temporary() { + (void)L3(L2(L1(1, 2), 3), 4); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:12: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-3]]:15: warning: use braced initialization + // CHECK-FIXES: (void)L3{L2{L1{1, 2}, 3}, 4}; +} + +void nested_agg_new() { + L3 *p = new L3(L2(L1(1, 2), 3), 4); + // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:18: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-3]]:21: warning: use braced initialization + // CHECK-FIXES: L3 *p = new L3{L2{L1{1, 2}, 3}, 4}; + (void)p; +} + +// Mixed: some levels already braced, only paren levels get fixed. +void nested_agg_mixed() { + L3 v(L2{L1(1, 2), 3}, 4); + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:11: warning: use braced initialization + // CHECK-FIXES: L3 v{L2{L1{1, 2}, 3}, 4}; +} + +void nested_agg_mixed_inner_braced() { + L3 v(L2(L1{1, 2}, 3), 4); + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:8: warning: use braced initialization + // CHECK-FIXES: L3 v{L2{L1{1, 2}, 3}, 4}; +} + +void already_braced() { + Agg d{1, 2}; +} + +void already_braced_temporary() { + Agg{1, 2}; +} + +void new_already_braced() { + Agg *p = new Agg{1, 2}; + (void)p; +} + +void copy_init() { + Agg d = {1, 2}; +} + +void designated_already_braced() { + Agg d{.a = 1, .b = 2}; +} + +void designated_copy_init() { + Agg d = {.a = 1, .b = 2}; +} + +struct MemberAgg { + Agg a; + MemberAgg() : a(1, 2) + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use braced initialization + // CHECK-FIXES: MemberAgg() : a{1, 2} + {} +}; + +struct MemberAggBraced { + Agg a; + MemberAggBraced() : a{1, 2} {} +}; + +// Narrowing conversions in aggregate paren init. +struct AggFloat { + int x; + int y; +}; + +void narrowing_aggregate() { + AggFloat af(1, 3.14); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:18: note: narrowing conversion from 'double' to 'int' + // CHECK-FIXES: AggFloat af{1, 3.14}; +} + +void narrowing_aggregate_functional_cast() { + (void)AggFloat(1, 3.14); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:21: note: narrowing conversion from 'double' to 'int' + // CHECK-FIXES: (void)AggFloat{1, 3.14}; +} + +void narrowing_aggregate_multiple() { + AggFloat af(2.5, 3.14); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:15: note: narrowing conversion from 'double' to 'int' + // CHECK-MESSAGES: :[[@LINE-3]]:20: note: narrowing conversion from 'double' to 'int' + // CHECK-FIXES: AggFloat af{2.5, 3.14}; +} + +void no_narrowing_aggregate() { + Agg a(1, 2); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use braced initialization + // CHECK-FIXES: Agg a{1, 2}; +} + +void lambda_capture_init() { + auto f = [s = Simple(1)](){}; + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use braced initialization + // CHECK-FIXES: auto f = [s = Simple{1}](){}; +} + +void lambda_capture_aggregate() { + auto f = [a = Agg(1, 2)](){}; + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use braced initialization + // CHECK-FIXES: auto f = [a = Agg{1, 2}](){}; +} + +void array_paren_init() { + int a[3](1, 2, 3); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use braced initialization + // CHECK-FIXES: int a[3]{1, 2, 3}; +} + +void array_paren_init_unsized() { + int a[](1, 2, 3); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use braced initialization + // CHECK-FIXES: int a[]{1, 2, 3}; +} + +void range_for_with_init() { + int arr[] = {1, 2, 3}; + for (Simple s(1); auto x : arr) { + // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use braced initialization + // CHECK-FIXES: for (Simple s{1}; auto x : arr) { + } +} + +void ctad_no_warn() { + std::vector v(5, 1); +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization-cxx23.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization-cxx23.cpp new file mode 100644 index 0000000000000..18b42e7010b31 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization-cxx23.cpp @@ -0,0 +1,15 @@ +// RUN: %check_clang_tidy -std=c++23-or-later %s misc-use-braced-initialization %t + +struct Simple { + Simple(int); +}; + +void auto_functional_cast() { + auto x = auto(1); +} + +void auto_functional_cast_class() { + auto x = auto(Simple(1)); + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use braced initialization instead of parenthesized initialization [misc-use-braced-initialization] + // CHECK-FIXES: auto x = auto(Simple{1}); +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization.cpp new file mode 100644 index 0000000000000..114696dff1df7 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/use-braced-initialization.cpp @@ -0,0 +1,977 @@ +// RUN: %check_clang_tidy -std=c++11-or-later %s misc-use-braced-initialization %t -- --fix-notes + +#include <string> +#include <vector> + +struct Simple { + Simple(int); + Simple(int, double); + Simple(const Simple &); +}; + +struct Explicit { + explicit Explicit(int); +}; + +struct Aggregate { + int a, b; +}; + +struct Takes { + Takes(Aggregate); +}; + +struct TwoAggregates { + TwoAggregates(Aggregate, Aggregate); +}; + +struct WithDefault { + WithDefault(int, int = 0); +}; + +struct HasDefault { + HasDefault(); +}; + +struct Outer { + struct Inner { + Inner(int); + }; +}; + +namespace ns { +struct Ns { + Ns(int); +}; +} // namespace ns + +#define MAKE_SIMPLE(x) Simple w(x) +#define WRAP_PARENS(x) (x) +#define TYPE_ALIAS Simple + +void basic_single_arg() { + Simple w(1); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use braced initialization instead of parenthesized initialization [misc-use-braced-initialization] + // CHECK-FIXES: Simple w{1}; +} + +void explicit_ctor() { + Explicit e(42); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use braced initialization + // CHECK-FIXES: Explicit e{42}; +} + +void copy_construction() { + Simple w1(1); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use braced initialization + // CHECK-FIXES: Simple w1{1}; + Simple w2(w1); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use braced initialization + // CHECK-FIXES: Simple w2{w1}; +} + +void static_local() { + static Simple sw(1); + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use braced initialization + // CHECK-FIXES: static Simple sw{1}; +} + +void const_variable() { + const Simple cw(1); + // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use braced initialization + // CHECK-FIXES: const Simple cw{1}; +} + +void default_args_ctor() { + WithDefault m(1); + // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use braced initialization + // CHECK-FIXES: WithDefault m{1}; +} + +void nested_type() { + Outer::Inner oi(1); + // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use braced initialization + // CHECK-FIXES: Outer::Inner oi{1}; +} + +void namespaced_type() { + ns::Ns g(1); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use braced initialization + // CHECK-FIXES: ns::Ns g{1}; +} + +void for_loop_init() { + for (Simple fw(1);;) { + // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use braced initialization + // CHECK-FIXES: for (Simple fw{1};;) { + } +} + +void if_init_statement() { + if (Simple s(1); true) { + // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: use braced initialization + // CHECK-FIXES: if (Simple s{1}; true) { + } +} + +void switch_init_statement(int x) { + switch (Simple s(x); x) { + // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: use braced initialization + // CHECK-FIXES: switch (Simple s{x}; x) { + } +} + +void ternary_arg(bool c) { + Simple s(c ? 1 : 2); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use braced initialization + // CHECK-FIXES: Simple s{c ? 1 : 2}; +} + +void multi_decl_class() { + Simple a(1), b(2); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:16: warning: use braced initialization + // CHECK-FIXES: Simple a{1}, b{2}; +} + +Simple global_simple(1); +// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use braced initialization +// CHECK-FIXES: Simple global_simple{1}; + +namespace ns_scope { +Simple ns_var(2); +// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use braced initialization +// CHECK-FIXES: Simple ns_var{2}; +} // namespace ns_scope + +// Macro wraps only the type name; parens are in user code, safe to fix. +void macro_type_only() { + TYPE_ALIAS w(1); + // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: use braced initialization + // CHECK-FIXES: TYPE_ALIAS w{1}; +} + +void scalar_int() { + int x(42); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use braced initialization + // CHECK-FIXES: int x{42}; +} + +void scalar_double() { + double d(3.14); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use braced initialization + // CHECK-FIXES: double d{3.14}; +} + +void scalar_expression(int a) { + int y(a + 1); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use braced initialization + // CHECK-FIXES: int y{a + 1}; +} + +void scalar_pointer() { + int *p(nullptr); + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use braced initialization + // CHECK-FIXES: int *p{nullptr}; +} + +void multi_decl_scalar() { + int a(1), b(2); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:13: warning: use braced initialization + // CHECK-FIXES: int a{1}, b{2}; +} + +void temporary_single_arg() { + Simple(1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use braced initialization + // CHECK-FIXES: Simple{1}; +} + +void temporary_multi_arg() { + Simple(1, 2.0); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use braced initialization + // CHECK-FIXES: Simple{1, 2.0}; +} + +void copy_init_rhs() { + Simple w = Simple(1); + // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: use braced initialization + // CHECK-FIXES: Simple w = Simple{1}; +} + +void auto_copy_init() { + auto w = Simple(1); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use braced initialization + // CHECK-FIXES: auto w = Simple{1}; +} + +Simple return_simple() { + return Simple(1); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use braced initialization + // CHECK-FIXES: return Simple{1}; +} + +void func_arg(Simple); +void simple_as_argument() { + func_arg(Simple(1)); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use braced initialization + // CHECK-FIXES: func_arg(Simple{1}); +} + +void new_multi_arg() { + Simple *p = new Simple(1, 2.0); + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use braced initialization + // CHECK-FIXES: Simple *p = new Simple{1, 2.0}; +} + +void braced_arg() { + Takes tp({1, 2}); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-FIXES: Takes tp{{[{][{]}}1, 2{{[}][}]}}; +} + +void braced_constructed_arg() { + Takes tp(Aggregate{1, 2}); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-FIXES: Takes tp{Aggregate{1, 2}}; +} + +void multiple_braced_args() { + TwoAggregates t({1, 2}, {3, 4}); + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use braced initialization + // CHECK-FIXES: TwoAggregates t{{[{][{]}}1, 2}, {3, 4{{[}][}]}}; +} + +void temporary_braced_arg() { + (void)Takes({1, 2}); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-FIXES: (void)Takes{{[{][{]}}1, 2{{[}][}]}}; +} + +void new_braced_arg() { + Takes *p = new Takes({1, 2}); + // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: use braced initialization + // CHECK-FIXES: Takes *p = new Takes{{[{][{]}}1, 2{{[}][}]}}; +} + +void class_comment_inside_parens() { + Simple w(/*comment*/ 1); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use braced initialization + // CHECK-FIXES: Simple w{/*comment*/ 1}; +} + +void scalar_comment_before_parens() { + int x /*comment*/ (42); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use braced initialization + // CHECK-FIXES: int x /*comment*/ {42}; +} + +void scalar_comment_inside_parens() { + int x(/*comment*/ 42); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use braced initialization + // CHECK-FIXES: int x{/*comment*/ 42}; +} + +void scalar_comment_after_init() { + int x(42 /*comment*/); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use braced initialization + // CHECK-FIXES: int x{42 /*comment*/}; +} + +struct L1 { + int a, b; + L1(int, int); +}; + +struct L2 { + L1 x; + int y; + L2(L1, int); +}; + +struct L3 { + L2 m; + int z; + L3(L2, int); +}; + +struct L4 { + L3 n; + int w; + L4(L3, int); +}; + +void nested_ctors_two_levels() { + L2 v(L1(1, 2), 3); + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:8: warning: use braced initialization + // CHECK-FIXES: L2 v{L1{1, 2}, 3}; +} + +void nested_ctors_three_levels() { + L3 v(L2(L1(1, 2), 3), 4); + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:8: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-3]]:11: warning: use braced initialization + // CHECK-FIXES: L3 v{L2{L1{1, 2}, 3}, 4}; +} + +void nested_ctors_four_levels() { + L4 v(L3(L2(L1(1, 2), 3), 4), 5); + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:8: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-3]]:11: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-4]]:14: warning: use braced initialization + // CHECK-FIXES: L4 v{L3{L2{L1{1, 2}, 3}, 4}, 5}; +} + +void nested_ctors_temporary() { + (void)L3(L2(L1(1, 2), 3), 4); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:12: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-3]]:15: warning: use braced initialization + // CHECK-FIXES: (void)L3{L2{L1{1, 2}, 3}, 4}; +} + +void nested_ctors_new() { + L3 *p = new L3(L2(L1(1, 2), 3), 4); + // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:18: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-3]]:21: warning: use braced initialization + // CHECK-FIXES: L3 *p = new L3{L2{L1{1, 2}, 3}, 4}; +} + +// Mixed: some levels already braced, only paren levels get fixed. +void nested_ctors_mixed() { + L3 v(L2{L1(1, 2), 3}, 4); + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:11: warning: use braced initialization + // CHECK-FIXES: L3 v{L2{L1{1, 2}, 3}, 4}; +} + +void nested_ctors_mixed_inner_braced() { + L3 v(L2(L1{1, 2}, 3), 4); + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:8: warning: use braced initialization + // CHECK-FIXES: L3 v{L2{L1{1, 2}, 3}, 4}; +} + +void already_braced() { + Simple w{1}; +} + +void already_braced_temporary() { + Simple{1}; +} + +void new_already_braced() { + Simple *p = new Simple{1}; + (void)p; +} + +void scalar_already_braced() { + int x{42}; +} + +void direct_auto() { + auto w(1); +} + +void scalar_auto() { + auto x(42); +} + +void scalar_copy_init() { + int x = 42; +} + +void default_construction() { + HasDefault d; +} + +void macro_full_decl() { + MAKE_SIMPLE(1); +} + +void macro_wraps_parens() { + Simple w WRAP_PARENS(1); +} + +template <typename T> +void template_dependent() { + T t(1); +} + +template <typename T> +void template_instantiated(int x) { + T t(x); +} + +template <typename T> +void template_instantiated2(T x) { + auto t(x); +} + +template <typename T> +void template_temporary_single() { + (void)T(1); +} + +template <typename T> +void template_temporary_multi() { + (void)T(1, 2.0); +} + +template <typename T> +T template_return() { + return T(1); +} + +template <typename T> +T *template_new_expr() { + return new T(1); +} + +template <typename T, typename... Args> +void template_variadic(Args... args) { + T t(args...); +} + +void force_instantiation() { + template_instantiated<Simple>(1); + template_instantiated2<Simple>(1); + template_temporary_single<Simple>(); + template_temporary_multi<Simple>(); + (void)template_return<Simple>(); + delete template_new_expr<Simple>(); + template_variadic<Simple>(1); +} + +struct InitListByValue { + InitListByValue(std::initializer_list<int>); + InitListByValue(int); + InitListByValue(int, int); +}; + +void il_by_value() { + InitListByValue x(1); +} + +void il_by_value_multi() { + InitListByValue x(1, 2); +} + +struct InitListConstRef { + InitListConstRef(const std::initializer_list<int> &); + InitListConstRef(int); +}; + +void il_const_ref() { + InitListConstRef x(1); +} + +struct InitListConstVolatileRef { + InitListConstVolatileRef(const volatile std::initializer_list<int> &); + InitListConstVolatileRef(int); +}; + +void il_const_volatile_ref() { + InitListConstVolatileRef x(1); +} + +struct InitListDefaults { + InitListDefaults(std::initializer_list<int>, int = 0, double = 1.0); + InitListDefaults(int); +}; + +void il_other_params_defaulted() { + InitListDefaults x(1); +} + +void il_std_string() { + std::string s("hello"); + // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use braced initialization + // CHECK-FIXES: std::string s{"hello"}; +} + +void il_std_string_count_char() { + std::string s(3, 'a'); +} + +void il_std_vector() { + std::vector<int> v(5); +} + +void il_std_vector_count_value() { + std::vector<int> v(5, 1); +} + +void il_std_vector_temporary() { + std::vector<int>(5); +} + +void il_std_vector_already_braced() { + std::vector<int> v{1, 2, 3}; +} + +void il_new_std_vector() { + std::vector<int> *p = new std::vector<int>(5); + (void)p; +} + +void il_braced_arg() { + InitListByValue x({1, 2, 3}); + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use braced initialization + // CHECK-FIXES: InitListByValue x{{[{][{]}}1, 2, 3{{[}][}]}}; +} + +struct InitListSecondParam { + InitListSecondParam(int, std::initializer_list<int>); + InitListSecondParam(int, int); +}; + +void il_not_first_param() { + InitListSecondParam x(1, 2); + // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use braced initialization + // CHECK-FIXES: InitListSecondParam x{1, 2}; +} + +struct InitListPointer { + InitListPointer(std::initializer_list<int> *); + InitListPointer(int); +}; + +void il_pointer() { + InitListPointer x(1); + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use braced initialization + // CHECK-FIXES: InitListPointer x{1}; +} + +struct InitListNoDefaults { + InitListNoDefaults(std::initializer_list<int>, int); + InitListNoDefaults(int); +}; + +void il_other_params_no_defaults() { + InitListNoDefaults x(1); + // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: use braced initialization + // CHECK-FIXES: InitListNoDefaults x{1}; +} + +struct TemplateCtor { + TemplateCtor(int); + template <class T> TemplateCtor(T); +}; + +void il_template_ctor() { + TemplateCtor x(1); + // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use braced initialization + // CHECK-FIXES: TemplateCtor x{1}; +} + +namespace custom { +template <typename T> class initializer_list {}; +} // namespace custom + +struct HasCustomInitList { + HasCustomInitList(custom::initializer_list<int>); + HasCustomInitList(int); +}; + +void il_custom_namespace_not_std() { + // custom::initializer_list is not in std, so it doesn't block conversion. + HasCustomInitList x(42); + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use braced initialization + // CHECK-FIXES: HasCustomInitList x{42}; +} + +struct RecordArg {}; + +struct InitListRecordToNonRecord { + InitListRecordToNonRecord(std::initializer_list<int>); + InitListRecordToNonRecord(RecordArg); +}; + +void il_record_arg_non_record_element() { + RecordArg r; + InitListRecordToNonRecord x(r); + // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: use braced initialization + // CHECK-FIXES: InitListRecordToNonRecord x{r}; +} + +struct InitListPointerArgArithmetic { + InitListPointerArgArithmetic(std::initializer_list<int>); + InitListPointerArgArithmetic(int *); +}; + +void il_pointer_arg_arithmetic_element() { + int v = 0; + InitListPointerArgArithmetic x(&v); + // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: use braced initialization + // CHECK-FIXES: InitListPointerArgArithmetic x{&v}; +} + +struct InitListPointerArgBool { + InitListPointerArgBool(std::initializer_list<bool>); + InitListPointerArgBool(int *); +}; + +void il_pointer_arg_bool_element() { + int v = 0; + InitListPointerArgBool x(&v); +} + +struct InitListMixedArgs { + InitListMixedArgs(std::initializer_list<int>); + InitListMixedArgs(int, RecordArg); +}; + +void il_mixed_args_second_no_convert() { + RecordArg r; + InitListMixedArgs x(1, r); + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use braced initialization + // CHECK-FIXES: InitListMixedArgs x{1, r}; +} + +enum class ScopedKey { A }; +struct InitListVsScopedEnum { + InitListVsScopedEnum(std::initializer_list<int>); + InitListVsScopedEnum(ScopedKey); +}; +void il_scoped_enum_arg() { + InitListVsScopedEnum x(ScopedKey::A); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: use braced initialization + // CHECK-FIXES: InitListVsScopedEnum x{ScopedKey::A}; +} + +struct NoConvOps {}; +struct InitListVsNoConvOps { + InitListVsNoConvOps(std::initializer_list<int>); + InitListVsNoConvOps(NoConvOps); +}; +void il_noconv_class_arg() { + NoConvOps r; + InitListVsNoConvOps x(r); + // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use braced initialization + // CHECK-FIXES: InitListVsNoConvOps x{r}; +} + +struct ConvertsToInt { + operator int() const; +}; +struct InitListVsConvClass { + InitListVsConvClass(std::initializer_list<int>); + InitListVsConvClass(ConvertsToInt); +}; +void il_conv_class_arg() { + ConvertsToInt c; + InitListVsConvClass x(c); +} + +struct OwnerOfSimple { + Simple s; + OwnerOfSimple() : s(1) + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use braced initialization + // CHECK-FIXES: OwnerOfSimple() : s{1} + {} +}; + +struct OwnerOfScalar { + int n; + OwnerOfScalar() : n(42) + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use braced initialization + // CHECK-FIXES: OwnerOfScalar() : n{42} + {} +}; + +struct OwnerAlreadyBraced { + Simple s; + OwnerAlreadyBraced() : s{1} {} +}; + +void scalar_functional_cast() { + (void)int(42); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-FIXES: (void)int{42}; +} + +int scalar_functional_cast_return(int x) { + return int(x + 1); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use braced initialization + // CHECK-FIXES: return int{x + 1}; +} + +void new_scalar() { + int *p = new int(42); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use braced initialization + // CHECK-FIXES: int *p = new int{42}; +} + +void lambda_body_init() { + auto f = [](int x) { + Simple s(x); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use braced initialization + // CHECK-FIXES: Simple s{x}; + }; +} + +// Type aliases. +typedef Simple SimpleTypedef; +using SimpleUsing = Simple; + +void type_alias_typedef() { + SimpleTypedef s(1); + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use braced initialization + // CHECK-FIXES: SimpleTypedef s{1}; +} + +void type_alias_using() { + SimpleUsing s(1); + // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use braced initialization + // CHECK-FIXES: SimpleUsing s{1}; +} + +// Constexpr variables. +void constexpr_init() { + constexpr int x(42); + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use braced initialization + // CHECK-FIXES: constexpr int x{42}; +} + +// Narrowing conversions: warning emitted but no fix-it, with note. +struct NarrowingTarget { + NarrowingTarget(short); +}; + +void narrowing_float_to_int() { + int x(3.14); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:9: note: narrowing conversion from 'double' to 'int' + // CHECK-FIXES: int x{3.14}; +} + +void narrowing_int_to_short(int n) { + short s(n); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:11: note: narrowing conversion from 'int' to 'short' + // CHECK-FIXES: short s{n}; +} + +void narrowing_int_to_short_constant() { + short s(1); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-FIXES: short s{1}; +} + +void narrowing_double_to_float(double d) { + float f(d); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:11: note: narrowing conversion from 'double' to 'float' + // CHECK-FIXES: float f{d}; +} + +void narrowing_double_to_float_constant() { + float f(1.0); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-FIXES: float f{1.0}; +} + +void narrowing_double_to_float_constant_overflow() { + float f(1e300); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:11: note: narrowing conversion from 'double' to 'float' + // CHECK-FIXES: float f{1e300}; +} + +void narrowing_int_to_float(int n) { + float f(n); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:11: note: narrowing conversion from 'int' to 'float' + // CHECK-FIXES: float f{n}; +} + +void narrowing_int_to_float_constant() { + float f(1); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-FIXES: float f{1}; +} + +void narrowing_int_to_float_constant_inexact() { + float f(16777217); // not exactly representable in float + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:11: note: narrowing conversion from 'int' to 'float' + // CHECK-FIXES: float f{16777217}; // not exactly representable in float +} + +void narrowing_ctor_arg(int n) { + NarrowingTarget t(n); + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:21: note: narrowing conversion from 'int' to 'short' + // CHECK-FIXES: NarrowingTarget t{n}; +} + +void narrowing_ctor_arg_constant() { + NarrowingTarget t(1); + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use braced initialization + // CHECK-FIXES: NarrowingTarget t{1}; +} + +void narrowing_functional_cast() { + (void)int(3.14); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:13: note: narrowing conversion from 'double' to 'int' + // CHECK-FIXES: (void)int{3.14}; +} + +void narrowing_new_scalar() { + int *p = new int(3.14); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:20: note: narrowing conversion from 'double' to 'int' + // CHECK-FIXES: int *p = new int{3.14}; +} + +struct NarrowingMember { + short n; + NarrowingMember(int x) : n(x) {} + // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:30: note: narrowing conversion from 'int' to 'short' + // CHECK-FIXES: NarrowingMember(int x) : n{x} {} +}; + +void narrowing_ptr_to_bool() { + int *p = nullptr; + bool b(p); + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:10: note: narrowing conversion + // CHECK-FIXES: bool b{p}; +} + +struct BoolCtor { + BoolCtor(bool); +}; +void narrowing_ptr_to_bool_ctor() { + int *p = nullptr; + BoolCtor bc(p); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:15: note: narrowing conversion + // CHECK-FIXES: BoolCtor bc{p}; +} + +void narrowing_signed_neg_to_unsigned() { + unsigned u(-1); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:14: note: narrowing conversion from 'int' to 'unsigned int' + // CHECK-FIXES: unsigned u{-1}; +} + +void no_narrowing_int_to_bool() { + bool b(0); + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use braced initialization + // CHECK-FIXES: bool b{0}; +} + +struct TwoNarrowing { + TwoNarrowing(short, short); +}; +void narrowing_multiple_args(int a, int b) { + TwoNarrowing t(a, b); + // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:18: note: narrowing conversion from 'int' to 'short' + // CHECK-MESSAGES: :[[@LINE-3]]:21: note: narrowing conversion from 'int' to 'short' + // CHECK-FIXES: TwoNarrowing t{a, b}; +} + +void reference_init() { + int a = 1; + int &r(a); + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use braced initialization + // CHECK-FIXES: int &r{a}; +} + +void volatile_variable() { + volatile int x(42); + // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use braced initialization + // CHECK-FIXES: volatile int x{42}; +} + +struct BaseClass { + BaseClass(int); +}; + +struct DerivedFromBase : BaseClass { + DerivedFromBase() : BaseClass(1) + // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use braced initialization + // CHECK-FIXES: DerivedFromBase() : BaseClass{1} + {} +}; + +struct DelegatingCtor { + DelegatingCtor(int); + DelegatingCtor() : DelegatingCtor(1) + // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: use braced initialization + // CHECK-FIXES: DelegatingCtor() : DelegatingCtor{1} + {} +}; + +struct DelegatingNarrowing { + DelegatingNarrowing(short); + DelegatingNarrowing(int x, int) : DelegatingNarrowing(x) + // CHECK-MESSAGES: :[[@LINE-1]]:37: warning: use braced initialization + // CHECK-MESSAGES: :[[@LINE-2]]:57: note: narrowing conversion from 'int' to 'short' + // CHECK-FIXES: DelegatingNarrowing(int x, int) : DelegatingNarrowing{x} + {} +}; + +enum Color { Red, Green, Blue }; +void enum_functional_cast() { + (void)Color(0); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-FIXES: (void)Color{0}; +} + +enum class ScopedColor { R, G, B }; +void scoped_enum_cast() { + (void)ScopedColor(0); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-FIXES: (void)ScopedColor{0}; +} + +struct WithStatic { + static Simple member; +}; +Simple WithStatic::member(1); +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use braced initialization +// CHECK-FIXES: Simple WithStatic::member{1}; + +void zero_arg_temporary() { + (void)Simple(1); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use braced initialization + // CHECK-FIXES: (void)Simple{1}; +} + +struct BaseWithIL { + BaseWithIL(std::initializer_list<int>); + BaseWithIL(int, int); +}; +struct DerivedNoUsing : BaseWithIL { + DerivedNoUsing(int a, int b) : BaseWithIL(a, b) {} +}; + +struct BaseNoIL { + BaseNoIL(int, int); +}; +struct DerivedFromNoIL : BaseNoIL { + DerivedFromNoIL(int a, int b) : BaseNoIL(a, b) {} + // CHECK-MESSAGES: :[[@LINE-1]]:35: warning: use braced initialization + // CHECK-FIXES: DerivedFromNoIL(int a, int b) : BaseNoIL{a, b} {} +}; + +struct BitfieldStruct { + int x : 3; + BitfieldStruct(int n) : x(n) + // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: use braced initialization + // CHECK-FIXES: BitfieldStruct(int n) : x{n} + {} +}; + _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
