diff --git a/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp --- a/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp @@ -11,6 +11,7 @@ #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallSet.h" #define DEBUG_TYPE "EasilySwappableParametersCheck" #include "llvm/Support/Debug.h" @@ -112,27 +113,61 @@ #define FB(Name, K) MIX_##Name = (1ull << (K##ull - 1ull)) - FB(None, 1), //< Mix between the two parameters is not possible. - FB(Trivial, 2), //< The two mix trivially, and are the exact same type. - FB(Canonical, 3), //< The two mix because the types refer to the same - // CanonicalType, but we do not elaborate as to how. + FB(None, 1), //< Mix between the two parameters is not possible. + FB(Trivial, 2), //< The two mix trivially, and are the exact same type. + FB(Canonical, 3), //< The two mix because the types refer to the same + // CanonicalType, but we do not elaborate as to how. + FB(TypeAlias, 4), //< The path from one type to the other involves + // desugaring type aliases. + FB(ReferenceBind, 5), //< The mix involves the binding power of "const &". #undef FB - LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue =*/MIX_Canonical) + LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue =*/MIX_ReferenceBind) }; LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(); /// Contains the metadata for the mixability result between two types, /// independently of which parameters they were calculated from. struct MixData { + /// The flag bits of the mix indicating what language features allow for it. MixFlags Flags; + /// A potentially calculated common underlying type after desugaring, that + /// both sides of the mix can originate from. + QualType CommonType; + MixData(MixFlags Flags) : Flags(Flags) {} + MixData(MixFlags Flags, QualType CommonType) + : Flags(Flags), CommonType(CommonType) {} void sanitize() { assert(Flags != MIX_Invalid && "sanitize() called on invalid bitvec"); - // TODO: There will be statements here in further extensions of the check. + + if (Flags & MIX_None) { + // If anywhere down the recursion a potential mix "path" is deemed + // impossible, throw away all the other bits because the mix is not + // possible. + Flags = MIX_None; + return; + } + + if (Flags == MIX_Trivial) + return; + + if (Flags ^ MIX_Trivial) + // If the mix involves somewhere trivial equivalence but down the + // recursion other bit(s) were set, remove the trivial bit, as it is not + // trivial. + Flags &= ~MIX_Trivial; + } + + MixData operator|(MixFlags EnableFlags) const { + return {Flags | EnableFlags, CommonType}; + } + MixData &operator|=(MixFlags EnableFlags) { + Flags |= EnableFlags; + return *this; } }; @@ -147,6 +182,7 @@ void sanitize() { Data.sanitize(); } MixFlags flags() const { return Data.Flags; } + QualType commonUnderlyingType() const { return Data.CommonType; } }; // NOLINTNEXTLINE(misc-redundant-expression): Seems to be a bogus warning. @@ -183,6 +219,11 @@ } }; +static MixData isLRefEquallyBindingToType(const TheCheck &Check, + const LValueReferenceType *LRef, + QualType Ty, const ASTContext &Ctx, + bool IsRefRHS); + /// Approximate the way how LType and RType might refer to "essentially the /// same" type, in a sense that at a particular call site, an expression of /// type LType and RType might be successfully passed to a variable (in our @@ -201,24 +242,94 @@ if (LType == RType) { LLVM_DEBUG(llvm::dbgs() << "<<< calculateMixability. Trivial equality.\n"); - return {MIX_Trivial}; + return {MIX_Trivial, LType}; + } + + // Dissolve certain type sugars that do not affect the mixability of one type + // with the other, and also do not require any sort of elaboration for the + // user to understand. + if (isa(LType.getTypePtr())) { + LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. LHS is ParenType.\n"); + return calculateMixability(Check, LType.getSingleStepDesugaredType(Ctx), + RType, Ctx); + } + if (isa(RType.getTypePtr())) { + LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. RHS is ParenType.\n"); + return calculateMixability(Check, LType, + RType.getSingleStepDesugaredType(Ctx), Ctx); + } + + // Dissolve typedefs. + if (const auto *LTypedef = LType->getAs()) { + LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. LHS is typedef.\n"); + return calculateMixability(Check, LTypedef->desugar(), RType, Ctx) | + MIX_TypeAlias; + } + if (const auto *RTypedef = RType->getAs()) { + LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. RHS is typedef.\n"); + return calculateMixability(Check, LType, RTypedef->desugar(), Ctx) | + MIX_TypeAlias; } - // TODO: Implement more elaborate logic, such as typedef, implicit - // conversions, etc. + // At a particular call site, what could be passed to a 'T' or 'const T' might + // also be passed to a 'const T &' without the call site putting a direct + // side effect on the passed expressions. + if (const auto *LRef = LType->getAs()) { + LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. LHS is &.\n"); + return isLRefEquallyBindingToType(Check, LRef, RType, Ctx, false) | + MIX_ReferenceBind; + } + if (const auto *RRef = RType->getAs()) { + LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. RHS is &.\n"); + return isLRefEquallyBindingToType(Check, RRef, LType, Ctx, true) | + MIX_ReferenceBind; + } // If none of the previous logic found a match, try if Clang otherwise // believes the types to be the same. if (LType.getCanonicalType() == RType.getCanonicalType()) { LLVM_DEBUG(llvm::dbgs() << "<<< calculateMixability. Same CanonicalType.\n"); - return {MIX_Canonical}; + return {MIX_Canonical, LType.getCanonicalType()}; } LLVM_DEBUG(llvm::dbgs() << "<<< calculateMixability. No match found.\n"); return {MIX_None}; } +/// Calculates if the reference binds an expression of the given type. This is +/// true iff 'LRef' is some 'const T &' type, and the 'Ty' is 'T' or 'const T'. +static MixData isLRefEquallyBindingToType(const TheCheck &Check, + const LValueReferenceType *LRef, + QualType Ty, const ASTContext &Ctx, + bool IsRefRHS) { + LLVM_DEBUG(llvm::dbgs() << ">>> isLRefEquallyBindingToType for LRef:\n"; + LRef->dump(llvm::dbgs(), Ctx); llvm::dbgs() << "\nand Type:\n"; + Ty.dump(llvm::dbgs(), Ctx); llvm::dbgs() << '\n';); + + QualType ReferredType = LRef->getPointeeType(); + if (!ReferredType.isLocalConstQualified()) { + LLVM_DEBUG(llvm::dbgs() + << "<<< isLRefEquallyBindingToType. Not const ref.\n"); + return {MIX_None}; + }; + + QualType NonConstReferredType = ReferredType; + NonConstReferredType.removeLocalConst(); + if (ReferredType == Ty || NonConstReferredType == Ty) { + LLVM_DEBUG( + llvm::dbgs() + << "<<< isLRefEquallyBindingToType. Type of referred matches.\n"); + return {MIX_Trivial, ReferredType}; + } + + LLVM_DEBUG( + llvm::dbgs() + << "--- isLRefEquallyBindingToType. Checking mix for underlying type.\n"); + return IsRefRHS ? calculateMixability(Check, Ty, NonConstReferredType, Ctx) + : calculateMixability(Check, NonConstReferredType, Ty, Ctx); +} + static MixableParameterRange modelMixingRange(const TheCheck &Check, const FunctionDecl *FD, std::size_t StartIndex) { @@ -388,6 +499,84 @@ return Name; } +/// Returns whether a particular Mix between two parameters should have the +/// types involved diagnosed to the user. This is only a flag check. +static inline bool needsToPrintTypeInDiagnostic(const model::Mix &M) { + return M.flags() & (model::MIX_TypeAlias | model::MIX_ReferenceBind); +} + +namespace { + +/// Retains the elements called with and returns whether the call is done with +/// a new element. +template class InsertOnce { + llvm::SmallSet CalledWith; + +public: + bool operator()(E El) { return CalledWith.insert(std::move(El)).second; } + + bool calledWith(const E &El) const { return CalledWith.contains(El); } +}; + +struct SwappedEqualQualTypePair { + QualType LHSType, RHSType; + + bool operator==(const SwappedEqualQualTypePair &Other) const { + return (LHSType == Other.LHSType && RHSType == Other.RHSType) || + (LHSType == Other.RHSType && RHSType == Other.LHSType); + } + + bool operator<(const SwappedEqualQualTypePair &Other) const { + return LHSType < Other.LHSType && RHSType < Other.RHSType; + } +}; + +struct TypeAliasDiagnosticTuple { + QualType LHSType, RHSType, CommonType; + + bool operator==(const TypeAliasDiagnosticTuple &Other) const { + return CommonType == Other.CommonType && + ((LHSType == Other.LHSType && RHSType == Other.RHSType) || + (LHSType == Other.RHSType && RHSType == Other.LHSType)); + } + + bool operator<(const TypeAliasDiagnosticTuple &Other) const { + return CommonType < Other.CommonType && LHSType < Other.LHSType && + RHSType < Other.RHSType; + } +}; + +/// Helper class to only emit a diagnostic related to MIX_TypeAlias once. +class UniqueTypeAliasDiagnosticHelper + : public InsertOnce { + using Base = InsertOnce; + +public: + /// Returns whether the diagnostic for LHSType and RHSType which are both + /// referring to CommonType being the same has not been emitted already. + bool operator()(QualType LHSType, QualType RHSType, QualType CommonType) { + if (CommonType.isNull() || CommonType == LHSType || CommonType == RHSType) + return Base::operator()({LHSType, RHSType, {}}); + + TypeAliasDiagnosticTuple ThreeTuple{LHSType, RHSType, CommonType}; + if (!Base::operator()(ThreeTuple)) + return false; + + bool AlreadySaidLHSAndCommonIsSame = calledWith({LHSType, CommonType, {}}); + bool AlreadySaidRHSAndCommonIsSame = calledWith({RHSType, CommonType, {}}); + if (AlreadySaidLHSAndCommonIsSame && AlreadySaidRHSAndCommonIsSame) { + // "SomeInt == int" && "SomeOtherInt == int" => "Common(SomeInt, + // SomeOtherInt) == int", no need to diagnose it. Save the 3-tuple only + // for shortcut if it ever appears again. + return false; + } + + return true; + } +}; + +} // namespace + EasilySwappableParametersCheck::EasilySwappableParametersCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), @@ -456,16 +645,23 @@ continue; } + bool NeedsAnyTypeNote = llvm::any_of(R.Mixes, needsToPrintTypeInDiagnostic); const ParmVarDecl *First = R.getFirstParam(), *Last = R.getLastParam(); std::string FirstParamTypeAsWritten = First->getType().getAsString(PP); { - StringRef DiagText = "%0 adjacent parameters of %1 of similar type " - "('%2') are easily swapped by mistake"; - // TODO: This logic will get extended here with future flags. + StringRef DiagText; + + if (NeedsAnyTypeNote) + DiagText = "%0 adjacent parameters of %1 of similar type are easily " + "swapped by mistake"; + else + DiagText = "%0 adjacent parameters of %1 of similar type ('%2') are " + "easily swapped by mistake"; auto Diag = diag(First->getOuterLocStart(), DiagText) - << static_cast(R.NumParamsChecked) << FD - << FirstParamTypeAsWritten; + << static_cast(R.NumParamsChecked) << FD; + if (!NeedsAnyTypeNote) + Diag << FirstParamTypeAsWritten; CharSourceRange HighlightRange = CharSourceRange::getTokenRange( First->getBeginLoc(), Last->getEndLoc()); @@ -481,6 +677,58 @@ diag(Last->getLocation(), "the last parameter in the range is '%0'", DiagnosticIDs::Note) << getNameOrUnnamed(Last) << Last->getSourceRange(); + + // Helper classes to silence elaborative diagnostic notes that would be + // too verbose. + UniqueTypeAliasDiagnosticHelper UniqueTypeAlias; + InsertOnce UniqueBindPower; + + for (const model::Mix &M : R.Mixes) { + assert(M.flags() >= model::MIX_Trivial && + "Sentinel or false mix in result."); + + if (needsToPrintTypeInDiagnostic(M)) { + // Typedefs might result in the type of the variable needing to be + // emitted to a note diagnostic, so prepare it. + const ParmVarDecl *LVar = M.First; + const ParmVarDecl *RVar = M.Second; + QualType LType = LVar->getType(); + QualType RType = RVar->getType(); + QualType CommonType = M.commonUnderlyingType(); + std::string LTypeAsWritten = LType.getAsString(PP); + std::string RTypeAsWritten = RType.getAsString(PP); + std::string CommonTypeStr = CommonType.getAsString(PP); + + if (M.flags() & model::MIX_TypeAlias && + UniqueTypeAlias(LType, RType, CommonType)) { + StringRef DiagText; + bool ExplicitlyPrintCommonType = false; + if (LTypeAsWritten == CommonTypeStr || + RTypeAsWritten == CommonTypeStr) + DiagText = + "after resolving type aliases, '%0' and '%1' are the same"; + else { + DiagText = "after resolving type aliases, the common type of '%0' " + "and '%1' is '%2'"; + ExplicitlyPrintCommonType = true; + } + + auto Diag = + diag(LVar->getOuterLocStart(), DiagText, DiagnosticIDs::Note) + << LTypeAsWritten << RTypeAsWritten; + if (ExplicitlyPrintCommonType) + Diag << CommonTypeStr; + } + + if (M.flags() & model::MIX_ReferenceBind && + UniqueBindPower({LType, RType})) { + StringRef DiagText = "'%0' and '%1' parameters accept and bind the " + "same kind of values"; + diag(RVar->getOuterLocStart(), DiagText, DiagnosticIDs::Note) + << LTypeAsWritten << RTypeAsWritten; + } + } + } } } diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone-easily-swappable-parameters.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone-easily-swappable-parameters.rst --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone-easily-swappable-parameters.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone-easily-swappable-parameters.rst @@ -111,3 +111,28 @@ add(1, 2); // Instantiates 'add', but that's not a user-defined function. } + +Due to the limitation above, parameters which type are further dependent upon +template instantiations to *prove* that they mix with another parameter's is +not diagnosed. + +.. code-block:: c++ + + template + struct Vector { + typedef T element_type; + }; + + // Diagnosed: Explicit instantiation was done by the user, we can prove it + // is the same type. + void Explicit(int A, Vector::element_type B) { /* ... */ } + + // Diagnosed: The two parameter types are exactly the same. + template + void Exact(typename Vector::element_type A, + typename Vector::element_type B) { /* ... */ } + + // Skipped: The two parameters are both 'T' but we can not prove this + // without actually instantiating. + template + void FalseNegative(T A, typename Vector::element_type B) { /* ... */ } diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len2.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len2.cpp --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len2.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len2.cpp @@ -111,20 +111,38 @@ // CHECK-MESSAGES: :[[@LINE-2]]:32: note: the first parameter in the range is 'I1' // CHECK-MESSAGES: :[[@LINE-3]]:43: note: the last parameter in the range is 'I2' -void throughTypedef(int I, MyInt1 J) {} -// CHECK-MESSAGES: :[[@LINE-1]]:21: warning: 2 adjacent parameters of 'throughTypedef' of similar type ('int') -// CHECK-MESSAGES: :[[@LINE-2]]:25: note: the first parameter in the range is 'I' -// CHECK-MESSAGES: :[[@LINE-3]]:35: note: the last parameter in the range is 'J' +void typedefMultiple(MyInt1 I1, MyInt2 I2x, MyInt2 I2y) {} +// CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 3 adjacent parameters of 'typedefMultiple' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:29: note: the first parameter in the range is 'I1' +// CHECK-MESSAGES: :[[@LINE-3]]:52: note: the last parameter in the range is 'I2y' +// CHECK-MESSAGES: :[[@LINE-4]]:22: note: after resolving type aliases, the common type of 'MyInt1' and 'MyInt2' is 'int' + +void throughTypedef1(int I, MyInt1 J) {} +// CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 2 adjacent parameters of 'throughTypedef1' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:26: note: the first parameter in the range is 'I' +// CHECK-MESSAGES: :[[@LINE-3]]:36: note: the last parameter in the range is 'J' +// CHECK-MESSAGES: :[[@LINE-4]]:22: note: after resolving type aliases, 'int' and 'MyInt1' are the same + +void betweenTypedef2(MyInt1 I, MyInt2 J) {} +// CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 2 adjacent parameters of 'betweenTypedef2' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:29: note: the first parameter in the range is 'I' +// CHECK-MESSAGES: :[[@LINE-3]]:39: note: the last parameter in the range is 'J' +// CHECK-MESSAGES: :[[@LINE-4]]:22: note: after resolving type aliases, the common type of 'MyInt1' and 'MyInt2' is 'int' + +typedef MyInt2 MyInt2b; -void betweenTypedef(MyInt1 I, MyInt2 J) {} -// CHECK-MESSAGES: :[[@LINE-1]]:21: warning: 2 adjacent parameters of 'betweenTypedef' of similar type ('MyInt1') -// CHECK-MESSAGES: :[[@LINE-2]]:28: note: the first parameter in the range is 'I' -// CHECK-MESSAGES: :[[@LINE-3]]:38: note: the last parameter in the range is 'J' +void typedefChain(int I, MyInt1 MI1, MyInt2 MI2, MyInt2b MI2b) {} +// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 4 adjacent parameters of 'typedefChain' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:23: note: the first parameter in the range is 'I' +// CHECK-MESSAGES: :[[@LINE-3]]:58: note: the last parameter in the range is 'MI2b' +// CHECK-MESSAGES: :[[@LINE-4]]:19: note: after resolving type aliases, 'int' and 'MyInt1' are the same +// CHECK-MESSAGES: :[[@LINE-5]]:19: note: after resolving type aliases, 'int' and 'MyInt2' are the same +// CHECK-MESSAGES: :[[@LINE-6]]:19: note: after resolving type aliases, 'int' and 'MyInt2b' are the same typedef long MyLong1; using MyLong2 = long; -void throughTypedefToOtherType(MyInt1 I, MyLong1 J) {} // NO-WARN: Not the same type. +void throughTypedefToOtherType(MyInt1 I, MyLong1 J) {} // NO-WARN: int and long. void qualified1(int I, const int CI) {} // NO-WARN: Not the same type. @@ -138,18 +156,73 @@ void qualifiedThroughTypedef1(int I, CInt CI) {} // NO-WARN: Not the same type. -void qualifiedThroughTypedef2(CInt CI1, const int CI2) {} // NO-WARN: Not the same type. -// CHECK-MESSAGES: :[[@LINE-1]]:31: warning: 2 adjacent parameters of 'qualifiedThroughTypedef2' of similar type ('CInt') +void qualifiedThroughTypedef2(CInt CI1, const int CI2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:31: warning: 2 adjacent parameters of 'qualifiedThroughTypedef2' of similar type are // CHECK-MESSAGES: :[[@LINE-2]]:36: note: the first parameter in the range is 'CI1' // CHECK-MESSAGES: :[[@LINE-3]]:51: note: the last parameter in the range is 'CI2' - -void reference1(int I, int &IR) {} // NO-WARN: Not the same type. - -void reference2(int I, const int &CIR) {} // NO-WARN: Not the same type. - -void reference3(int I, int &&IRR) {} // NO-WARN: Not the same type. - -void reference4(int I, const int &&CIRR) {} // NO-WARN: Not the same type. +// CHECK-MESSAGES: :[[@LINE-4]]:31: note: after resolving type aliases, 'CInt' and 'const int' are the same + +void qualifiedThroughTypedef3(CInt CI1, const MyInt1 CI2, const int CI3) {} // NO-WARN: Not the same type. + +void qualifiedThroughTypedef4(CInt CI1, const MyInt1 CI2, const MyInt2 CI3) {} +// CHECK-MESSAGES: :[[@LINE-1]]:41: warning: 2 adjacent parameters of 'qualifiedThroughTypedef4' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:54: note: the first parameter in the range is 'CI2' +// CHECK-MESSAGES: :[[@LINE-3]]:72: note: the last parameter in the range is 'CI3' +// CHECK-MESSAGES: :[[@LINE-4]]:41: note: after resolving type aliases, the common type of 'const MyInt1' and 'const MyInt2' is 'int' + +void reference1(int I, int &IR) {} // NO-WARN: Distinct semantics when called. + +void reference2(int I, const int &CIR) {} +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 2 adjacent parameters of 'reference2' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:21: note: the first parameter in the range is 'I' +// CHECK-MESSAGES: :[[@LINE-3]]:35: note: the last parameter in the range is 'CIR' +// CHECK-MESSAGES: :[[@LINE-4]]:24: note: 'int' and 'const int &' parameters accept and bind the same kind of values + +void reference3(int I, int &&IRR) {} // NO-WARN: Distinct semantics when called. + +void reference4(int I, const int &&CIRR) {} // NO-WARN: Distinct semantics when called. + +void reference5(const int CI, const int &CIR) {} +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 2 adjacent parameters of 'reference5' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:27: note: the first parameter in the range is 'CI' +// CHECK-MESSAGES: :[[@LINE-3]]:42: note: the last parameter in the range is 'CIR' +// CHECK-MESSAGES: :[[@LINE-4]]:31: note: 'const int' and 'const int &' parameters accept and bind the same kind of values + +void reference6(int I, const int &CIR, int J, const int &CJR) {} +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 4 adjacent parameters of 'reference6' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:21: note: the first parameter in the range is 'I' +// CHECK-MESSAGES: :[[@LINE-3]]:58: note: the last parameter in the range is 'CJR' +// CHECK-MESSAGES: :[[@LINE-4]]:24: note: 'int' and 'const int &' parameters accept and bind the same kind of values + +using ICRTy = const int &; +using MyIntCRTy = const MyInt1 &; + +void referenceThroughTypedef(int I, ICRTy Builtin, MyIntCRTy MyInt) {} +// CHECK-MESSAGES: :[[@LINE-1]]:30: warning: 3 adjacent parameters of 'referenceThroughTypedef' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:34: note: the first parameter in the range is 'I' +// CHECK-MESSAGES: :[[@LINE-3]]:62: note: the last parameter in the range is 'MyInt' +// CHECK-MESSAGES: :[[@LINE-4]]:30: note: after resolving type aliases, the common type of 'int' and 'ICRTy' is 'const int' +// CHECK-MESSAGES: :[[@LINE-5]]:37: note: 'int' and 'ICRTy' parameters accept and bind the same kind of values +// CHECK-MESSAGES: :[[@LINE-6]]:30: note: after resolving type aliases, 'int' and 'MyIntCRTy' are the same +// CHECK-MESSAGES: :[[@LINE-7]]:52: note: 'int' and 'MyIntCRTy' parameters accept and bind the same kind of values +// CHECK-MESSAGES: :[[@LINE-8]]:37: note: after resolving type aliases, the common type of 'ICRTy' and 'MyIntCRTy' is 'int' +// CHECK-MESSAGES: :[[@LINE-9]]:52: note: 'ICRTy' and 'MyIntCRTy' parameters accept and bind the same kind of values + +short const typedef int unsigned Eldritch; +typedef const unsigned short Holy; + +void collapse(Eldritch Cursed, Holy Blessed) {} +// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: 2 adjacent parameters of 'collapse' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:24: note: the first parameter in the range is 'Cursed' +// CHECK-MESSAGES: :[[@LINE-3]]:37: note: the last parameter in the range is 'Blessed' +// CHECK-MESSAGES: :[[@LINE-4]]:15: note: after resolving type aliases, the common type of 'Eldritch' and 'Holy' is 'const unsigned short' + +void collapseAndTypedef(Eldritch Cursed, const Holy &Blessed) {} +// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 2 adjacent parameters of 'collapseAndTypedef' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:34: note: the first parameter in the range is 'Cursed' +// CHECK-MESSAGES: :[[@LINE-3]]:54: note: the last parameter in the range is 'Blessed' +// CHECK-MESSAGES: :[[@LINE-4]]:25: note: after resolving type aliases, the common type of 'Eldritch' and 'const Holy &' is 'const unsigned short' +// CHECK-MESSAGES: :[[@LINE-5]]:42: note: 'Eldritch' and 'const Holy &' parameters accept and bind the same kind of values template struct Pair {}; @@ -182,3 +255,54 @@ // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: 3 adjacent parameters of 'templateVariadic2' of similar type ('int') // CHECK-MESSAGES: :[[@LINE-2]]:28: note: the first parameter in the range is 'TVar' // CHECK-MESSAGES: :[[@LINE-3]]:50: note: the last parameter in the range is 'UVars2' + +template +using TwoOf = Pair; + +void templateAndAliasTemplate(Pair P, TwoOf I) {} +// CHECK-MESSAGES: :[[@LINE-1]]:31: warning: 2 adjacent parameters of 'templateAndAliasTemplate' of similar type ('Pair') +// CHECK-MESSAGES: :[[@LINE-2]]:46: note: the first parameter in the range is 'P' +// CHECK-MESSAGES: :[[@LINE-3]]:60: note: the last parameter in the range is 'I' + +template +struct Vector { + typedef T element_type; + typedef T &reference_type; + typedef const T const_element_type; + typedef const T &const_reference_type; +}; + +void memberTypedef(int I, Vector::element_type E) {} +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: 2 adjacent parameters of 'memberTypedef' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:24: note: the first parameter in the range is 'I' +// CHECK-MESSAGES: :[[@LINE-3]]:53: note: the last parameter in the range is 'E' +// CHECK-MESSAGES: :[[@LINE-4]]:20: note: after resolving type aliases, 'int' and 'Vector::element_type' are the same + +template +void memberTypedefDependent1(T T1, typename Vector::element_type T2) {} // NO-WARN: Dependent name is not instantiated and resolved against other type. + +template +void memberTypedefDependent2(typename Vector::element_type E1, + typename Vector::element_type E2) {} +// CHECK-MESSAGES: :[[@LINE-2]]:30: warning: 2 adjacent parameters of 'memberTypedefDependent2' of similar type ('typename Vector::element_type') +// CHECK-MESSAGES: :[[@LINE-3]]:63: note: the first parameter in the range is 'E1' +// CHECK-MESSAGES: :[[@LINE-3]]:63: note: the last parameter in the range is 'E2' + +template +void memberTypedefDependentReference1( + typename Vector::element_type E, + typename Vector::const_element_type &R) {} // NO-WARN: Not instantiated. + +template +void memberTypedefDependentReference2( + typename Vector::element_type E, + typename Vector::const_reference_type R) {} // NO-WARN: Not instantiated. + +template +void memberTypedefDependentReference3( + typename Vector::element_type E, + const typename Vector::element_type &R) {} +// CHECK-MESSAGES: :[[@LINE-2]]:5: warning: 2 adjacent parameters of 'memberTypedefDependentReference3' of similar type are +// CHECK-MESSAGES: :[[@LINE-3]]:38: note: the first parameter in the range is 'E' +// CHECK-MESSAGES: :[[@LINE-3]]:45: note: the last parameter in the range is 'R' +// CHECK-MESSAGES: :[[@LINE-4]]:5: note: 'typename Vector::element_type' and 'const typename Vector::element_type &' parameters accept and bind the same kind of values