diff --git a/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp b/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp --- a/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp @@ -13,6 +13,7 @@ #include "../utils/OptionsUtils.h" #include "../utils/TypeTraits.h" #include "clang/Analysis/Analyses/ExprMutationAnalyzer.h" +#include "clang/Basic/Diagnostic.h" using namespace clang::ast_matchers; @@ -77,8 +78,11 @@ "the loop variable's type is not a reference type; this creates a " "copy in each iteration; consider making this a reference") << utils::fixit::changeVarDeclToReference(LoopVar, Context); - if (!LoopVar.getType().isConstQualified()) - Diagnostic << utils::fixit::changeVarDeclToConst(LoopVar); + if (!LoopVar.getType().isConstQualified()) { + if (llvm::Optional Fix = utils::fixit::addQualifierToVarDecl( + LoopVar, Context, DeclSpec::TQ::TQ_const)) + Diagnostic << *Fix; + } return true; } @@ -101,11 +105,15 @@ !utils::decl_ref_expr::allDeclRefExprs(LoopVar, *ForRange.getBody(), Context) .empty()) { - diag(LoopVar.getLocation(), - "loop variable is copied but only used as const reference; consider " - "making it a const reference") - << utils::fixit::changeVarDeclToConst(LoopVar) - << utils::fixit::changeVarDeclToReference(LoopVar, Context); + auto Diag = diag( + LoopVar.getLocation(), + "loop variable is copied but only used as const reference; consider " + "making it a const reference"); + + if (llvm::Optional Fix = utils::fixit::addQualifierToVarDecl( + LoopVar, Context, DeclSpec::TQ::TQ_const)) + Diag << *Fix << utils::fixit::changeVarDeclToReference(LoopVar, Context); + return true; } return false; diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp --- a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp @@ -12,6 +12,7 @@ #include "../utils/FixItHintUtils.h" #include "../utils/Matchers.h" #include "../utils/OptionsUtils.h" +#include "clang/Basic/Diagnostic.h" namespace clang { namespace tidy { @@ -21,8 +22,11 @@ void recordFixes(const VarDecl &Var, ASTContext &Context, DiagnosticBuilder &Diagnostic) { Diagnostic << utils::fixit::changeVarDeclToReference(Var, Context); - if (!Var.getType().isLocalConstQualified()) - Diagnostic << utils::fixit::changeVarDeclToConst(Var); + if (!Var.getType().isLocalConstQualified()) { + if (llvm::Optional Fix = utils::fixit::addQualifierToVarDecl( + Var, Context, DeclSpec::TQ::TQ_const)) + Diagnostic << *Fix; + } } } // namespace diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp --- a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp @@ -162,15 +162,18 @@ // The parameter of each declaration needs to be checked individually as to // whether it is const or not as constness can differ between definition and // declaration. - if (!CurrentParam.getType().getCanonicalType().isConstQualified()) - Diag << utils::fixit::changeVarDeclToConst(CurrentParam); + if (!CurrentParam.getType().getCanonicalType().isConstQualified()) { + if (llvm::Optional Fix = utils::fixit::addQualifierToVarDecl( + CurrentParam, *Result.Context, DeclSpec::TQ::TQ_const)) + Diag << *Fix; + } } } void UnnecessaryValueParamCheck::registerPPCallbacks( const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { - Inserter = std::make_unique(SM, getLangOpts(), - IncludeStyle); + Inserter = + std::make_unique(SM, getLangOpts(), IncludeStyle); PP->addPPCallbacks(Inserter->CreatePPCallbacks()); } diff --git a/clang-tools-extra/clang-tidy/utils/FixItHintUtils.h b/clang-tools-extra/clang-tidy/utils/FixItHintUtils.h --- a/clang-tools-extra/clang-tidy/utils/FixItHintUtils.h +++ b/clang-tools-extra/clang-tidy/utils/FixItHintUtils.h @@ -11,6 +11,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" +#include "clang/Sema/DeclSpec.h" namespace clang { namespace tidy { @@ -23,6 +24,31 @@ /// Creates fix to make ``VarDecl`` const qualified. FixItHint changeVarDeclToConst(const VarDecl &Var); +/// This enum defines where the qualifier shall be preferably added. +enum class QualifierPolicy { + Left, // Add the qualifier always to the left side, if that is possible. + Right, // Add the qualifier always to the right side. +}; + +/// This enum defines which entity is the target for adding the qualifier. This +/// makes only a difference for pointer-types. Other types behave identical +/// for either value of \c ConstTarget. +enum class QualifierTarget { + Pointee, /// Transforming a pointer attaches for the pointee and not the + /// pointer itself. For references and normal values this option has + /// no effect. `int * p = &i;` -> `const int * p = &i` or `int const + /// * p = &i`. + Value, /// Transforming pointers will consider the pointer itself. + /// `int * p = &i;` -> `int * const = &i` +}; + +/// \brief Creates fix to qualify ``VarDecl`` with the specified \c Qualifier. +/// Requires that `Var` is isolated in written code like in `int foo = 42;`. +Optional +addQualifierToVarDecl(const VarDecl &Var, const ASTContext &Context, + DeclSpec::TQ Qualifier, + QualifierTarget CT = QualifierTarget::Pointee, + QualifierPolicy CP = QualifierPolicy::Left); } // namespace fixit } // namespace utils } // namespace tidy diff --git a/clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp b/clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp --- a/clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp +++ b/clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp @@ -9,6 +9,7 @@ #include "FixItHintUtils.h" #include "LexerUtils.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/Type.h" namespace clang { namespace tidy { @@ -26,10 +27,201 @@ return FixItHint::CreateInsertion(AmpLocation, "&"); } -FixItHint changeVarDeclToConst(const VarDecl &Var) { - return FixItHint::CreateInsertion(Var.getTypeSpecStartLoc(), "const "); +static bool isValueType(const Type *T) { + return !(isa(T) || isa(T) || isa(T) || + isa(T) || isa(T)); } +static bool isValueType(QualType QT) { return isValueType(QT.getTypePtr()); } +static bool isMemberOrFunctionPointer(QualType QT) { + return (QT->isPointerType() && QT->isFunctionPointerType()) || + isa(QT.getTypePtr()); +} + +static bool locDangerous(SourceLocation S) { + return S.isInvalid() || S.isMacroID(); +} + +static Optional +skipLParensBackwards(SourceLocation Start, const ASTContext &Context) { + if (locDangerous(Start)) + return None; + + auto PreviousTokenLParen = [&Start, &Context]() { + Token T; + T = lexer::getPreviousToken(Start, Context.getSourceManager(), + Context.getLangOpts()); + return T.is(tok::l_paren); + }; + + while (Start.isValid() && PreviousTokenLParen()) + Start = lexer::findPreviousTokenStart(Start, Context.getSourceManager(), + Context.getLangOpts()); + + if (locDangerous(Start)) + return None; + return Start; +} + +static Optional fixIfNotDangerous(SourceLocation Loc, + StringRef Text) { + if (locDangerous(Loc)) + return None; + return FixItHint::CreateInsertion(Loc, Text); +} + +// Build a string that can be emitted as FixIt with either a space in before +// or after the qualifier, either ' const' or 'const '. +static std::string buildQualifier(DeclSpec::TQ Qualifier, + bool WhitespaceBefore = false) { + if (WhitespaceBefore) + return (llvm::Twine(' ') + DeclSpec::getSpecifierName(Qualifier)).str(); + return (llvm::Twine(DeclSpec::getSpecifierName(Qualifier)) + " ").str(); +} + +static Optional changeValue(const VarDecl &Var, + DeclSpec::TQ Qualifier, + QualifierTarget QualTarget, + QualifierPolicy QualPolicy, + const ASTContext &Context) { + switch (QualPolicy) { + case QualifierPolicy::Left: + return fixIfNotDangerous(Var.getTypeSpecStartLoc(), + buildQualifier(Qualifier)); + case QualifierPolicy::Right: + Optional IgnoredParens = + skipLParensBackwards(Var.getLocation(), Context); + + if (IgnoredParens) + return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier)); + return None; + } +} + +static Optional changePointerItself(const VarDecl &Var, + DeclSpec::TQ Qualifier, + const ASTContext &Context) { + if (locDangerous(Var.getLocation())) + return None; + + Optional IgnoredParens = + skipLParensBackwards(Var.getLocation(), Context); + if (IgnoredParens) + return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier)); + return None; +} + +static Optional +changePointer(const VarDecl &Var, DeclSpec::TQ Qualifier, const Type *Pointee, + QualifierTarget QualTarget, QualifierPolicy QualPolicy, + const ASTContext &Context) { + // The pointer itself shall be marked as `const`. This is always to the right + // of the '*' or in front of the identifier. + if (QualTarget == QualifierTarget::Value) + return changePointerItself(Var, Qualifier, Context); + + // Mark the pointee `const` that is a normal value (`int* p = nullptr;`). + if (QualTarget == QualifierTarget::Pointee && isValueType(Pointee)) { + // Adding the `const` on the left side is just the beginning of the type + // specification. (`const int* p = nullptr;`) + if (QualPolicy == QualifierPolicy::Left) + return fixIfNotDangerous(Var.getTypeSpecStartLoc(), + buildQualifier(Qualifier)); + // Adding the `const` on the right side of the value type requires finding + // the `*` token and placing the `const` left of it. + // (`int const* p = nullptr;`) + if (QualPolicy == QualifierPolicy::Right) { + SourceLocation BeforeStar = lexer::findPreviousTokenKind( + Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(), + tok::star); + if (locDangerous(BeforeStar)) + return None; + + Optional IgnoredParens = + skipLParensBackwards(BeforeStar, Context); + + if (IgnoredParens) + return fixIfNotDangerous(*IgnoredParens, + buildQualifier(Qualifier, true)); + return None; + } + } + + if (QualTarget == QualifierTarget::Pointee && Pointee->isPointerType()) { + // Adding the `const` to the pointee if the pointee is a pointer + // is the same as 'QualPolicy == Right && isValueType(Pointee)'. + // The `const` must be left of the last `*` token. + // (`int * const* p = nullptr;`) + SourceLocation BeforeStar = lexer::findPreviousTokenKind( + Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(), + tok::star); + return fixIfNotDangerous(BeforeStar, buildQualifier(Qualifier, true)); + } + + return None; +} + +static Optional +changeReferencee(const VarDecl &Var, DeclSpec::TQ Qualifier, QualType Pointee, + QualifierTarget QualTarget, QualifierPolicy QualPolicy, + const ASTContext &Context) { + if (QualPolicy == QualifierPolicy::Left && isValueType(Pointee)) + return fixIfNotDangerous(Var.getTypeSpecStartLoc(), + buildQualifier(Qualifier)); + + SourceLocation BeforeRef = lexer::findPreviousAnyTokenKind( + Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(), + tok::amp, tok::ampamp); + Optional IgnoredParens = + skipLParensBackwards(BeforeRef, Context); + if (IgnoredParens) + return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier, true)); + + return None; +} + +Optional addQualifierToVarDecl(const VarDecl &Var, + const ASTContext &Context, + DeclSpec::TQ Qualifier, + QualifierTarget QualTarget, + QualifierPolicy QualPolicy) { + assert((QualPolicy == QualifierPolicy::Left || + QualPolicy == QualifierPolicy::Right) && + "Unexpected Insertion Policy"); + assert((QualTarget == QualifierTarget::Pointee || + QualTarget == QualifierTarget::Value) && + "Unexpected Target"); + + QualType ParenStrippedType = Var.getType().IgnoreParens(); + if (isValueType(ParenStrippedType)) + return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context); + + if (ParenStrippedType->isReferenceType()) + return changeReferencee(Var, Qualifier, Var.getType()->getPointeeType(), + QualTarget, QualPolicy, Context); + + if (isMemberOrFunctionPointer(ParenStrippedType)) + return changePointerItself(Var, Qualifier, Context); + + if (ParenStrippedType->isPointerType()) + return changePointer(Var, Qualifier, + ParenStrippedType->getPointeeType().getTypePtr(), + QualTarget, QualPolicy, Context); + + if (ParenStrippedType->isArrayType()) { + const Type *AT = ParenStrippedType->getBaseElementTypeUnsafe(); + assert(AT && "Did not retrieve array element type for an array."); + + if (isValueType(AT)) + return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context); + + if (AT->isPointerType()) + return changePointer(Var, Qualifier, AT->getPointeeType().getTypePtr(), + QualTarget, QualPolicy, Context); + } + + return None; +} } // namespace fixit } // namespace utils } // namespace tidy diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.h b/clang-tools-extra/clang-tidy/utils/LexerUtils.h --- a/clang-tools-extra/clang-tidy/utils/LexerUtils.h +++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.h @@ -39,6 +39,8 @@ const SourceManager &SM, const LangOptions &LangOpts, TokenKind TK, TokenKinds... TKs) { + if (Start.isInvalid() || Start.isMacroID()) + return SourceLocation(); while (true) { SourceLocation L = findPreviousTokenStart(Start, SM, LangOpts); if (L.isInvalid() || L.isMacroID()) @@ -46,7 +48,7 @@ Token T; // Returning 'true' is used to signal failure to retrieve the token. - if (Lexer::getRawToken(L, T, SM, LangOpts)) + if (Lexer::getRawToken(L, T, SM, LangOpts, /*IgnoreWhiteSpace=*/true)) return SourceLocation(); if (T.isOneOf(TK, TKs...)) diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp --- a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp +++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp @@ -17,7 +17,11 @@ const LangOptions &LangOpts, bool SkipComments) { Token Token; Token.setKind(tok::unknown); + Location = Location.getLocWithOffset(-1); + if (Location.isInvalid()) + return Token; + auto StartOfFile = SM.getLocForStartOfFile(SM.getFileID(Location)); while (Location != StartOfFile) { Location = Lexer::GetBeginningOfToken(Location, SM, LangOpts); @@ -47,6 +51,9 @@ const SourceManager &SM, const LangOptions &LangOpts, tok::TokenKind TK) { + if (Start.isInvalid() || Start.isMacroID()) + return SourceLocation(); + while (true) { SourceLocation L = findPreviousTokenStart(Start, SM, LangOpts); if (L.isInvalid() || L.isMacroID()) diff --git a/clang-tools-extra/unittests/clang-tidy/AddConstTest.cpp b/clang-tools-extra/unittests/clang-tidy/AddConstTest.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/unittests/clang-tidy/AddConstTest.cpp @@ -0,0 +1,1009 @@ +#include "../clang-tidy/utils/FixItHintUtils.h" +#include "ClangTidyTest.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +namespace clang { +namespace tidy { + +namespace { +using namespace clang::ast_matchers; +using namespace utils::fixit; + +template +class ConstTransform : public ClangTidyCheck { +public: + ConstTransform(StringRef CheckName, ClangTidyContext *Context) + : ClangTidyCheck(CheckName, Context) {} + + void registerMatchers(MatchFinder *Finder) override { + Finder->addMatcher(varDecl(hasName("target")).bind("var"), this); + } + + void check(const MatchFinder::MatchResult &Result) override { + const auto *D = Result.Nodes.getNodeAs("var"); + using utils::fixit::addQualifierToVarDecl; + Optional Fix = addQualifierToVarDecl( + *D, *Result.Context, DeclSpec::TQ::TQ_const, CT, CP); + auto Diag = diag(D->getBeginLoc(), "doing const transformation"); + if (Fix) + Diag << *Fix; + } +}; +} // namespace + +namespace test { +using PointeeLTransform = + ConstTransform; +using PointeeRTransform = + ConstTransform; + +using ValueLTransform = + ConstTransform; +using ValueRTransform = + ConstTransform; + +// ---------------------------------------------------------------------------- +// Test Value-like types. Everything with indirection is done later. +// ---------------------------------------------------------------------------- + +TEST(Values, Builtin) { + StringRef Snippet = "int target = 0;"; + + EXPECT_EQ("const int target = 0;", runCheckOnCode(Snippet)); + EXPECT_EQ("const int target = 0;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int const target = 0;", runCheckOnCode(Snippet)); + EXPECT_EQ("int const target = 0;", + runCheckOnCode(Snippet)); +} +TEST(Values, TypedefBuiltin) { + StringRef T = "typedef int MyInt;"; + StringRef S = "MyInt target = 0;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const MyInt target = 0;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const MyInt target = 0;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("MyInt const target = 0;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("MyInt const target = 0;"), + runCheckOnCode(Cat(S))); +} +TEST(Values, TypedefBuiltinPointer) { + StringRef T = "typedef int* MyInt;"; + StringRef S = "MyInt target = nullptr;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const MyInt target = nullptr;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const MyInt target = nullptr;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("MyInt const target = nullptr;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("MyInt const target = nullptr;"), + runCheckOnCode(Cat(S))); +} +TEST(Values, UsingBuiltin) { + StringRef T = "using MyInt = int;"; + StringRef S = "MyInt target = 0;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const MyInt target = 0;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const MyInt target = 0;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("MyInt const target = 0;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("MyInt const target = 0;"), + runCheckOnCode(Cat(S))); +} +TEST(Values, UsingBuiltinPointer) { + StringRef T = "using MyInt = int*;"; + StringRef S = "MyInt target = nullptr;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const MyInt target = nullptr;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const MyInt target = nullptr;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("MyInt const target = nullptr;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("MyInt const target = nullptr;"), + runCheckOnCode(Cat(S))); +} +TEST(Values, AutoValue) { + StringRef T = "int f() { return 42; }\n"; + StringRef S = "auto target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const auto target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const auto target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("auto const target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto const target = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Values, AutoPointer) { + StringRef T = "int* f() { return nullptr; }\n"; + StringRef S = "auto target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const auto target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const auto target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("auto const target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto const target = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Values, AutoReference) { + StringRef T = "static int global = 42; int& f() { return global; }\n"; + StringRef S = "auto target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const auto target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const auto target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("auto const target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto const target = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Values, DeclTypeValue) { + StringRef T = "int f() { return 42; }\n"; + StringRef S = "decltype(f()) target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const decltype(f()) target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const decltype(f()) target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("decltype(f()) const target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("decltype(f()) const target = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Values, DeclTypePointer) { + // The pointer itself will be changed to 'const'. There is no + // way to make the pointee 'const' with this syntax. + StringRef T = "int* f() { return nullptr; }\n"; + StringRef S = "decltype(f()) target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const decltype(f()) target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const decltype(f()) target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("decltype(f()) const target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("decltype(f()) const target = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Values, DeclTypeReference) { + // Same as pointer, but the reference itself will be marked 'const'. + // This has no effect and will result in a warning afterwards. The + // transformation itself is still correct. + StringRef T = "static int global = 42; int& f() { return global; }\n"; + StringRef S = "decltype(f()) target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const decltype(f()) target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const decltype(f()) target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("decltype(f()) const target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("decltype(f()) const target = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Values, Parens) { + StringRef Snippet = "int ((target)) = 0;"; + + EXPECT_EQ("const int ((target)) = 0;", + runCheckOnCode(Snippet)); + EXPECT_EQ("const int ((target)) = 0;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int const ((target)) = 0;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int const ((target)) = 0;", + runCheckOnCode(Snippet)); +} + +// ---------------------------------------------------------------------------- +// Test builtin-arrays +// ---------------------------------------------------------------------------- + +TEST(Arrays, Builtin) { + StringRef Snippet = "int target[][1] = {{1}, {2}, {3}};"; + + EXPECT_EQ("const int target[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); + EXPECT_EQ("const int target[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int const target[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int const target[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); +} +TEST(Arrays, BuiltinParens) { + StringRef Snippet = "int ((target))[][1] = {{1}, {2}, {3}};"; + + EXPECT_EQ("const int ((target))[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); + EXPECT_EQ("const int ((target))[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int const ((target))[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int const ((target))[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); +} +TEST(Arrays, Pointers) { + StringRef Snippet = "int x; int* target[] = {&x, &x, &x};"; + + EXPECT_EQ("int x; const int* target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x; int const* target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int x; int* const target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x; int* const target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); +} +TEST(Arrays, PointerPointers) { + StringRef Snippet = "int* x = nullptr; int** target[] = {&x, &x, &x};"; + + EXPECT_EQ("int* x = nullptr; int* const* target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int* x = nullptr; int** const target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int* x = nullptr; int* const* target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int* x = nullptr; int** const target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); +} +TEST(Arrays, PointersParens) { + StringRef Snippet = "int x; int* (target)[] = {&x, &x, &x};"; + + EXPECT_EQ("int x; const int* (target)[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x; int const* (target)[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int x; int* const (target)[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x; int* const (target)[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); +} + +// ---------------------------------------------------------------------------- +// Test reference types. This does not include pointers and arrays. +// ---------------------------------------------------------------------------- + +TEST(Reference, LValueBuiltin) { + StringRef Snippet = "int x = 42; int& target = x;"; + + EXPECT_EQ("int x = 42; const int& target = x;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x = 42; const int& target = x;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int x = 42; int const& target = x;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x = 42; int const& target = x;", + runCheckOnCode(Snippet)); +} +TEST(Reference, RValueBuiltin) { + StringRef Snippet = "int&& target = 42;"; + EXPECT_EQ("const int&& target = 42;", + runCheckOnCode(Snippet)); + EXPECT_EQ("const int&& target = 42;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int const&& target = 42;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int const&& target = 42;", + runCheckOnCode(Snippet)); +} +TEST(Reference, LValueToPointer) { + StringRef Snippet = "int* p; int *& target = p;"; + EXPECT_EQ("int* p; int * const& target = p;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int* p; int * const& target = p;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int* p; int * const& target = p;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int* p; int * const& target = p;", + runCheckOnCode(Snippet)); +} +TEST(Reference, LValueParens) { + StringRef Snippet = "int x = 42; int ((& target)) = x;"; + + EXPECT_EQ("int x = 42; const int ((& target)) = x;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x = 42; const int ((& target)) = x;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int x = 42; int const((& target)) = x;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x = 42; int const((& target)) = x;", + runCheckOnCode(Snippet)); +} +TEST(Reference, ToArray) { + StringRef ArraySnippet = "int a[4] = {1, 2, 3, 4};"; + StringRef Snippet = "int (&target)[4] = a;"; + auto Cat = [&ArraySnippet](StringRef S) { return (ArraySnippet + S).str(); }; + + EXPECT_EQ(Cat("const int (&target)[4] = a;"), + runCheckOnCode(Cat(Snippet))); + EXPECT_EQ(Cat("const int (&target)[4] = a;"), + runCheckOnCode(Cat(Snippet))); + + EXPECT_EQ(Cat("int const(&target)[4] = a;"), + runCheckOnCode(Cat(Snippet))); + EXPECT_EQ(Cat("int const(&target)[4] = a;"), + runCheckOnCode(Cat(Snippet))); +} +TEST(Reference, Auto) { + StringRef T = "static int global = 42; int& f() { return global; }\n"; + StringRef S = "auto& target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const auto& target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto const& target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("const auto& target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto const& target = f();"), + runCheckOnCode(Cat(S))); +} + +// ---------------------------------------------------------------------------- +// Test pointers types. +// ---------------------------------------------------------------------------- + +TEST(Pointers, SingleBuiltin) { + StringRef Snippet = "int* target = nullptr;"; + + EXPECT_EQ("int* const target = nullptr;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int* const target = nullptr;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("const int* target = nullptr;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int const* target = nullptr;", + runCheckOnCode(Snippet)); +} +TEST(Pointers, MultiBuiltin) { + StringRef Snippet = "int** target = nullptr;"; + + EXPECT_EQ("int** const target = nullptr;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int** const target = nullptr;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int* const* target = nullptr;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int* const* target = nullptr;", + runCheckOnCode(Snippet)); +} +TEST(Pointers, ToArray) { + StringRef ArraySnippet = "int a[4] = {1, 2, 3, 4};"; + StringRef Snippet = "int (*target)[4] = &a;"; + auto Cat = [&ArraySnippet](StringRef S) { return (ArraySnippet + S).str(); }; + + EXPECT_EQ(Cat("int (*const target)[4] = &a;"), + runCheckOnCode(Cat(Snippet))); + EXPECT_EQ(Cat("const int (*target)[4] = &a;"), + runCheckOnCode(Cat(Snippet))); + + EXPECT_EQ(Cat("int (*const target)[4] = &a;"), + runCheckOnCode(Cat(Snippet))); + EXPECT_EQ(Cat("int const(*target)[4] = &a;"), + runCheckOnCode(Cat(Snippet))); +} +TEST(Pointers, Parens) { + StringRef Snippet = "int ((**target)) = nullptr;"; + + EXPECT_EQ("int ((**const target)) = nullptr;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int ((**const target)) = nullptr;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int ((* const*target)) = nullptr;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int ((* const*target)) = nullptr;", + runCheckOnCode(Snippet)); +} +TEST(Pointers, Auto) { + StringRef T = "int* f() { return nullptr; }\n"; + StringRef S = "auto* target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("auto* const target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto* const target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("const auto* target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto const* target = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Pointers, AutoParens) { + StringRef T = "int* f() { return nullptr; }\n"; + StringRef S = "auto (((* target))) = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("auto (((* const target))) = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto (((* const target))) = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("const auto (((* target))) = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto const(((* target))) = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Pointers, FunctionPointer) { + StringRef S = "int (*target)(float, int, double) = nullptr;"; + + EXPECT_EQ("int (*const target)(float, int, double) = nullptr;", + runCheckOnCode(S)); + EXPECT_EQ("int (*const target)(float, int, double) = nullptr;", + runCheckOnCode(S)); + + EXPECT_EQ("int (*const target)(float, int, double) = nullptr;", + runCheckOnCode(S)); + EXPECT_EQ("int (*const target)(float, int, double) = nullptr;", + runCheckOnCode(S)); + + S = "int (((*target)))(float, int, double) = nullptr;"; + EXPECT_EQ("int (((*const target)))(float, int, double) = nullptr;", + runCheckOnCode(S)); +} +TEST(Pointers, MemberFunctionPointer) { + StringRef T = "struct A { int f() { return 1; } };"; + StringRef S = "int (A::*target)() = &A::f;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("int (A::*const target)() = &A::f;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("int (A::*const target)() = &A::f;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("int (A::*const target)() = &A::f;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("int (A::*const target)() = &A::f;"), + runCheckOnCode(Cat(S))); + + S = "int (A::*((target)))() = &A::f;"; + EXPECT_EQ(Cat("int (A::*const ((target)))() = &A::f;"), + runCheckOnCode(Cat(S))); +} +TEST(Pointers, MemberDataPointer) { + StringRef T = "struct A { int member = 0; };"; + StringRef S = "int A::*target = &A::member;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("int A::*const target = &A::member;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("int A::*const target = &A::member;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("int A::*const target = &A::member;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("int A::*const target = &A::member;"), + runCheckOnCode(Cat(S))); + + S = "int A::*((target)) = &A::member;"; + EXPECT_EQ(Cat("int A::*const ((target)) = &A::member;"), + runCheckOnCode(Cat(S))); +} + +// ---------------------------------------------------------------------------- +// Test TagTypes (struct, class, unions, enums) +// ---------------------------------------------------------------------------- + +TEST(TagTypes, Struct) { + StringRef T = "struct Foo { int data; int method(); };\n"; + StringRef S = "struct Foo target{0};"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const struct Foo target{0};"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const struct Foo target{0};"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("struct Foo const target{0};"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("struct Foo const target{0};"), + runCheckOnCode(Cat(S))); + + S = "Foo target{0};"; + EXPECT_EQ(Cat("const Foo target{0};"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo target{0};"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const target{0};"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const target{0};"), + runCheckOnCode(Cat(S))); + + S = "Foo (target){0};"; + EXPECT_EQ(Cat("const Foo (target){0};"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo (target){0};"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const (target){0};"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const (target){0};"), + runCheckOnCode(Cat(S))); +} +TEST(TagTypes, Class) { + StringRef T = "class Foo { int data; int method(); };\n"; + StringRef S = "class Foo target;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const class Foo target;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const class Foo target;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("class Foo const target;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("class Foo const target;"), + runCheckOnCode(Cat(S))); + + S = "Foo target;"; + EXPECT_EQ(Cat("const Foo target;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo target;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const target;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const target;"), + runCheckOnCode(Cat(S))); + + S = "Foo (target);"; + EXPECT_EQ(Cat("const Foo (target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo (target);"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const (target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const (target);"), + runCheckOnCode(Cat(S))); +} +TEST(TagTypes, Enum) { + StringRef T = "enum Foo { N_ONE, N_TWO, N_THREE };\n"; + StringRef S = "enum Foo target;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const enum Foo target;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const enum Foo target;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("enum Foo const target;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("enum Foo const target;"), + runCheckOnCode(Cat(S))); + + S = "Foo target;"; + EXPECT_EQ(Cat("const Foo target;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo target;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const target;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const target;"), + runCheckOnCode(Cat(S))); + + S = "Foo (target);"; + EXPECT_EQ(Cat("const Foo (target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo (target);"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const (target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const (target);"), + runCheckOnCode(Cat(S))); +} +TEST(TagTypes, Union) { + StringRef T = "union Foo { int yay; float nej; };\n"; + StringRef S = "union Foo target;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const union Foo target;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const union Foo target;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("union Foo const target;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("union Foo const target;"), + runCheckOnCode(Cat(S))); + + S = "Foo target;"; + EXPECT_EQ(Cat("const Foo target;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo target;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const target;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const target;"), + runCheckOnCode(Cat(S))); + + S = "Foo (target);"; + EXPECT_EQ(Cat("const Foo (target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo (target);"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const (target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const (target);"), + runCheckOnCode(Cat(S))); +} + +// ---------------------------------------------------------------------------- +// Test Macro expansions. +// ---------------------------------------------------------------------------- + +TEST(Macro, AllInMacro) { + StringRef T = "#define DEFINE_VARIABLE int target = 42\n"; + StringRef S = "DEFINE_VARIABLE;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("DEFINE_VARIABLE;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("DEFINE_VARIABLE;"), runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("DEFINE_VARIABLE;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("DEFINE_VARIABLE;"), runCheckOnCode(Cat(S))); +} +TEST(Macro, MacroParameter) { + StringRef T = "#define DEFINE_VARIABLE(X) int X = 42\n"; + StringRef S = "DEFINE_VARIABLE(target);"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("DEFINE_VARIABLE(target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("DEFINE_VARIABLE(target);"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("DEFINE_VARIABLE(target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("DEFINE_VARIABLE(target);"), + runCheckOnCode(Cat(S))); +} +TEST(Macro, MacroTypeValue) { + StringRef T = "#define BAD_TYPEDEF int\n"; + StringRef S = "BAD_TYPEDEF target = 42;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("BAD_TYPEDEF target = 42;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("BAD_TYPEDEF target = 42;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("BAD_TYPEDEF const target = 42;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("BAD_TYPEDEF const target = 42;"), + runCheckOnCode(Cat(S))); +} +TEST(Macro, MacroTypePointer) { + StringRef T = "#define BAD_TYPEDEF int *\n"; + StringRef S = "BAD_TYPEDEF target = nullptr;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("BAD_TYPEDEF const target = nullptr;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("BAD_TYPEDEF const target = nullptr;"), + runCheckOnCode(Cat(S))); + + // FIXME: Failing even all parts seem to bail-out in for isMacroID() + // The macro itself is changed here and below which is not intended. + EXPECT_NE(Cat("BAD_TYPEDEF target = nullptr;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("BAD_TYPEDEF target = nullptr;"), + runCheckOnCode(Cat(S))); +} +TEST(Macro, MacroTypeReference) { + StringRef T = "static int g = 42;\n#define BAD_TYPEDEF int&\n"; + StringRef S = "BAD_TYPEDEF target = g;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("BAD_TYPEDEF target = g;"), + runCheckOnCode(Cat(S))); + // FIXME: Failing even all parts seem to bail-out in for isMacroID() + EXPECT_NE(Cat("BAD_TYPEDEF target = g;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("BAD_TYPEDEF target = g;"), + runCheckOnCode(Cat(S))); + // FIXME: Failing even all parts seem to bail-out in for isMacroID() + EXPECT_NE(Cat("BAD_TYPEDEF target = g;"), + runCheckOnCode(Cat(S))); +} +// This failed in LLVM. +TEST(Macro, Variable) { + StringRef M = "#define DEBUG(X) do { if (1) { X; } } while (0)\n"; + StringRef F = "void foo() "; + StringRef V = "{ DEBUG(int target = 42;); }"; + + auto Cat = [&](StringRef S) { return (M + F + V).str(); }; + + EXPECT_EQ(Cat("{ DEBUG(const int target = 42;); }"), + runCheckOnCode(Cat(V))); + EXPECT_EQ(Cat("{ DEBUG(int const target = 42;); }"), + runCheckOnCode(Cat(V))); +} +TEST(Macro, RangeLoop) { + StringRef M = "#define DEBUG(X) do { if (1) { X; }} while (false)\n"; + StringRef F = "void foo() { char array[] = {'a', 'b', 'c'}; "; + StringRef V = "DEBUG( for(auto& target: array) 10 + target; );"; + StringRef E = "}"; + + auto Cat = [&](StringRef S) { return (M + F + V + E).str(); }; + + EXPECT_EQ(Cat("DEBUG( for(const auto& target: array); );"), + runCheckOnCode(Cat(V))); + EXPECT_EQ(Cat("DEBUG( for(auto const& target: array); );"), + runCheckOnCode(Cat(V))); +} + +// ---------------------------------------------------------------------------- +// Test template code. +// ---------------------------------------------------------------------------- + +TEST(Template, TemplateVariable) { + StringRef T = "template T target = 3.1415;"; + + EXPECT_EQ("template const T target = 3.1415;", + runCheckOnCode(T)); + EXPECT_EQ("template T const target = 3.1415;", + runCheckOnCode(T)); + + EXPECT_EQ("template const T target = 3.1415;", + runCheckOnCode(T)); + EXPECT_EQ("template T const target = 3.1415;", + runCheckOnCode(T)); +} +TEST(Template, FunctionValue) { + StringRef T = "template void f(T v) \n"; + StringRef S = "{ T target = v; }"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("{ const T target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const target = v; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const T target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const target = v; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, FunctionPointer) { + StringRef T = "template void f(T* v) \n"; + StringRef S = "{ T* target = v; }"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("{ T* const target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T* const target = v; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const T* target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const* target = v; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, FunctionReference) { + StringRef T = "template void f(T& v) \n"; + StringRef S = "{ T& target = v; }"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("{ const T& target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const& target = v; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const T& target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const& target = v; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, MultiInstantiationsFunction) { + StringRef T = "template void f(T v) \n"; + StringRef S = "{ T target = v; }"; + StringRef InstantStart = "void calls() {\n"; + StringRef InstValue = "f(42);\n"; + StringRef InstConstValue = "f(42);\n"; + StringRef InstPointer = "f(nullptr);\n"; + StringRef InstPointerConst = "f(nullptr);\n"; + StringRef InstConstPointer = "f(nullptr);\n"; + StringRef InstConstPointerConst = "f(nullptr);\n"; + StringRef InstRef = "int i = 42;\nf(i);\n"; + StringRef InstConstRef = "f(i);\n"; + StringRef InstantEnd = "}"; + auto Cat = [&](StringRef Target) { + return (T + Target + InstantStart + InstValue + InstConstValue + + InstPointer + InstPointerConst + InstConstPointer + + InstConstPointerConst + InstRef + InstConstRef + InstantEnd) + .str(); + }; + + EXPECT_EQ(Cat("{ const T target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const target = v; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const T target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const target = v; }"), + runCheckOnCode(Cat(S))); +} + +TEST(Template, StructValue) { + StringRef T = "template struct S { void f(T& v) \n"; + StringRef S = "{ T target = v; }"; + StringRef End = "\n};"; + auto Cat = [&T, &End](StringRef S) { return (T + S + End).str(); }; + + EXPECT_EQ(Cat("{ const T target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const target = v; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const T target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const target = v; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, StructPointer) { + StringRef T = "template struct S { void f(T* v) \n"; + StringRef S = "{ T* target = v; }"; + StringRef End = "\n};"; + auto Cat = [&T, &End](StringRef S) { return (T + S + End).str(); }; + + EXPECT_EQ(Cat("{ T* const target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T* const target = v; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const T* target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const* target = v; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, StructReference) { + StringRef T = "template struct S { void f(T& v) \n"; + StringRef S = "{ T& target = v; }"; + StringRef End = "\n};"; + auto Cat = [&T, &End](StringRef S) { return (T + S + End).str(); }; + + EXPECT_EQ(Cat("{ const T& target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const& target = v; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const T& target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const& target = v; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, DependentReturnFunction) { + StringRef TS = "template struct TS { using value_type = T; };"; + StringRef T = "template void foo() "; + StringRef S = "{ typename T::value_type target; }"; + auto Cat = [&TS, &T](StringRef S) { return (TS + T + S).str(); }; + + EXPECT_EQ(Cat("{ const typename T::value_type target; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ typename T::value_type const target; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const typename T::value_type target; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ typename T::value_type const target; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, DependentReturnPointerFunction) { + StringRef TS = "template struct TS { using value_type = T; };"; + StringRef T = "template void foo() "; + StringRef S = "{ typename T::value_type *target; }"; + auto Cat = [&TS, &T](StringRef S) { return (TS + T + S).str(); }; + + EXPECT_EQ(Cat("{ typename T::value_type *const target; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ typename T::value_type *const target; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const typename T::value_type *target; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ typename T::value_type const*target; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, DependentReturnReferenceFunction) { + StringRef TS = "template struct TS { using value_type = T; };"; + StringRef T = "template void foo(T& f) "; + StringRef S = "{ typename T::value_type &target = f; }"; + auto Cat = [&TS, &T](StringRef S) { return (TS + T + S).str(); }; + + EXPECT_EQ(Cat("{ const typename T::value_type &target = f; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ typename T::value_type const&target = f; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const typename T::value_type &target = f; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ typename T::value_type const&target = f; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, VectorLikeType) { + StringRef TS = "template struct TS { TS(const T&) {} }; "; + StringRef T = "void foo() "; + StringRef S = "{ TS target(42); }"; + auto Cat = [&TS, &T](StringRef S) { return (TS + T + S).str(); }; + + EXPECT_EQ(Cat("{ const TS target(42); }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ TS const target(42); }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const TS target(42); }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ TS const target(42); }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, SpecializedTemplate) { + StringRef TS = "template struct TS { TS(const T&) {} }; "; + StringRef TS2 = "template <> struct TS { TS(const double&) {} }; "; + StringRef T = "void foo() "; + StringRef S = "{ TS target(42.42); }"; + auto Cat = [&](StringRef S) { return (TS + TS2 + T + S).str(); }; + + EXPECT_EQ(Cat("{ const TS target(42.42); }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ TS const target(42.42); }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const TS target(42.42); }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ TS const target(42.42); }"), + runCheckOnCode(Cat(S))); +} + +} // namespace test +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt --- a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt +++ b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt @@ -7,6 +7,7 @@ include_directories(${CLANG_LINT_SOURCE_DIR}) add_extra_unittest(ClangTidyTests + AddConstTest.cpp ClangTidyDiagnosticConsumerTest.cpp ClangTidyOptionsTest.cpp IncludeInserterTest.cpp