diff --git a/clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.h b/clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.h --- a/clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.h +++ b/clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.h @@ -48,6 +48,10 @@ /// Whether to consider 'T' and 'const T'/'volatile T'/etc. arguments to be /// possible mixup. const bool CVRMixPossible; + + /// Whether to consider implicit conversion possibilities as a potential match + /// for adjacency. + const bool ImplicitConversion; }; } // namespace experimental diff --git a/clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.cpp b/clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.cpp --- a/clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.cpp +++ b/clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.cpp @@ -11,6 +11,7 @@ #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "llvm/ADT/BitmaskEnum.h" +#include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" #include @@ -31,60 +32,100 @@ // Set the bit at index N to 1 as the enum constant. N = 0 is invalid. #define BIT(Name, N) MIXUP_##Name = (1ull << (N##ull - 1ull)) - BIT(None, 1), //< Mixup is not possible. - BIT(Trivial, 2), //< No extra information needed. - BIT(Typedef, 3), //< Parameter of a typedef which resolves to an effective - //< desugared type same as the other arg. - BIT(RefBind, 4), //< Parameter mixes with another due to reference binding. - BIT(CVR, 5), //< Parameter mixes with another through implicit - //< qualification. + BIT(None, 1), //< Mixup is not possible. + BIT(Trivial, 2), //< No extra information needed. + BIT(Typedef, 3), //< Parameter of a typedef which resolves to an effective + //< desugared type same as the other arg. + BIT(RefBind, 4), //< Parameter mixes with another due to reference binding. + BIT(CVR, 5), //< Parameter mixes with another through implicit + //< qualification. + BIT(Implicit, 6), //< An implicit conversion may happen. #undef BIT - LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue = */ MIXUP_CVR) + LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue = */ MIXUP_Implicit) }; LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(); -/// A named tuple that contains which parameter with which other parameter -/// can be mixed up in what fashion. -struct Mixup { - const ParmVarDecl *First, *Second; - MixupTag Flags; +/// Implicit conversion sequence steps resulting in types referred here. +struct ConversionSequence { + /// Type of the intermediate value after the first standard conversion. + const Type *StdPre; + /// Type of the intermediate value after executing the user-defined conversion + /// which, in case of constructors, the user type, in case of conversion + /// operators, the result of the operator. + const Type *UserType; + /// Type of the intermediate value after the second standard conversion. + const Type *StdPost; + + ConversionSequence() : StdPre(nullptr), UserType(nullptr), StdPost(nullptr) {} + + explicit operator bool() const { return StdPre || UserType || StdPost; } + + /// Whether the conversion sequence is single-step only. + bool single() const { + return ((bool)StdPre ^ (bool)UserType ^ (bool)StdPost) && + !(StdPre && UserType && StdPost); + } +}; - Mixup(const ParmVarDecl *A, const ParmVarDecl *B, MixupTag Flags) - : First(A), Second(B), Flags(Flags) {} +struct MixupData { + MixupTag Flag; + ConversionSequence ConvLTR, ConvRTL; - Mixup operator|(MixupTag EnableFlags) const { - return {First, Second, Flags | EnableFlags}; + MixupData(MixupTag Flags) : Flag(Flags) {} + MixupData(MixupTag Flags, const ConversionSequence &Seq) + : Flag(Flags), ConvLTR(Seq), ConvRTL(Seq) {} + MixupData(MixupTag Flags, const ConversionSequence <R, + const ConversionSequence &RTL) + : Flag(Flags), ConvLTR(LTR), ConvRTL(RTL) {} + + MixupData operator|(MixupTag EnableFlag) const { + return {Flag | EnableFlag, ConvLTR, ConvRTL}; } - Mixup &operator|=(MixupTag EnableFlags) { - Flags |= EnableFlags; + MixupData &operator|=(MixupTag EnableFlag) { + Flag |= EnableFlag; return *this; } /// Sanitises the Mixup's flags so it doesn't contain contradictory bits. void sanitise() { - assert(Flags != MIXUP_Invalid && + assert(Flag != MIXUP_Invalid && "Mixup tag had full zero bit pattern value!"); - if (Flags & MIXUP_None) { + if (Flag & MIXUP_None) { // If at any point the checks mark the mixup impossible, it is just simply // impossible. - Flags = MIXUP_None; + Flag = MIXUP_None; return; } - if (Flags == MIXUP_Trivial) + if (Flag == MIXUP_Trivial) return; - if (Flags ^ MIXUP_Trivial) + if (Flag ^ MIXUP_Trivial) // If any other bits than Trivial is set, unset Trivial, so only the // annotation bits warranting extra diagnostic are set. - Flags &= ~MIXUP_Trivial; + Flag &= ~MIXUP_Trivial; + + // Set LTR and RTL implicity according to the members being set. + if (ConvLTR || ConvRTL) + Flag |= MIXUP_Implicit; + else + Flag &= ~MIXUP_Implicit; } }; +/// Named tuple that contains that the types of the arguments From and To +/// are mixable with the given flags in a particular fashion. +struct Mixup { + const ParmVarDecl *First, *Second; + MixupData Data; + + Mixup(const ParmVarDecl *A, const ParmVarDecl *B, MixupData Data) + : First(A), Second(B), Data(Data) {} +}; static_assert(std::is_trivially_copyable::value, - "keep Mixup trivially copyable!"); + "keep Mixup and components trivially copyable!"); /// Represents a (closed) range of adjacent parameters that can be mixed up at /// a call site. @@ -112,29 +153,45 @@ } // namespace -/// Returns whether an lvalue reference refers to the same type as T. -static MixupTag RefBindsToSameType(const LValueReferenceType *LRef, - const Type *T, bool CVRMixPossible); - -/// Returns whether LType and RType refer to the same type in a sense that at a -/// call site it is possible to mix the types up if the actual arguments are -/// specified in opposite order. -/// \returns MixupTag indicating how a mixup between the arguments happens. -/// The final output of this potentially recursive function must be sanitised. -static MixupTag HowPossibleToMixUpAtCallSite(const QualType LType, - const QualType RType, - const ASTContext &Ctx, - const bool CVRMixPossible) { +/// Returns how an lvalue reference refers to the same type as T. +static MixupData RefBindsToSameType(const LValueReferenceType *LRef, + const Type *T, const ASTContext &Ctx, + bool IsRefRightType, bool CVRMixPossible, + bool ImplicitConversion); + +/// Returns whether the left side type is convertible to the right side type, +/// by attempting to approximate implicit conversion sequences. +/// \param AllowUserDefined If false, only standard conversions will be +/// approximated. +/// \note The result of this operation is not symmetric! +static MixupData HowConvertible(const Type *LT, const Type *RT, + const LangOptions &LOpts, + bool AllowUserDefined = true); + +/// Returns how LType and RType may essentially refer to the same type - in a +/// sense that at a call site it is possible to mix the arguments up if +/// specified in the opposite order. +/// \returns MixupData indicating how a mixup between the arguments happens. +/// \note The final output of this potentially recursive function must be +/// sanitised by 'sanitiseMixup' before it could be used, to ensure only the +/// proper bits are set! +static MixupData HowPossibleToMixUpAtCallSite(const QualType LType, + const QualType RType, + const ASTContext &Ctx, + const bool CVRMixPossible, + const bool ImplicitConversion) { if (LType == RType) return MIXUP_Trivial; // Remove certain sugars that don't affect mixability from the types. if (dyn_cast(LType.getTypePtr())) return HowPossibleToMixUpAtCallSite(LType.getSingleStepDesugaredType(Ctx), - RType, Ctx, CVRMixPossible); + RType, Ctx, CVRMixPossible, + ImplicitConversion); if (dyn_cast(RType.getTypePtr())) return HowPossibleToMixUpAtCallSite( - LType, RType.getSingleStepDesugaredType(Ctx), Ctx, CVRMixPossible); + LType, RType.getSingleStepDesugaredType(Ctx), Ctx, CVRMixPossible, + ImplicitConversion); // An argument of type 'T' and 'const T &' may bind with the same power. // (Note this is a different case, as 'const T &' is a '&' on the top level, @@ -142,21 +199,23 @@ if (LType->isLValueReferenceType() || RType->isLValueReferenceType()) { // (If both is the same reference type, earlier a return happened.) - if (LType->isLValueReferenceType()) { - MixupTag RefBind = RefBindsToSameType(LType->getAs(), - RType.getTypePtr(), CVRMixPossible); - // RefBind may or may not have given us a tag (e.g. reference was to a - // typedef) via a recursive chain back to this function. Apply the - // "bind power" tag here to indicate a reference binding happened. - // (If RefBind was MIXUP_None, a later sanitise step will undo every bit - // except for None.) - return RefBind | MIXUP_RefBind; - } - if (RType->isLValueReferenceType()) { - MixupTag RefBind = RefBindsToSameType(RType->getAs(), - LType.getTypePtr(), CVRMixPossible); - return RefBind | MIXUP_RefBind; - } + if (LType->isLValueReferenceType()) + // Return value of function call may or may not have given us a tag (e.g. + // reference was to a typedef) via a recursive chain back to this + // function. Apply the "bind power" tag here to indicate a reference + // binding happened. (If RefBind was MIXUP_None, a later sanitise step + // will undo every bit except for None.) + return RefBindsToSameType(LType->getAs(), + RType.getTypePtr(), Ctx, + /* IsRefRightType =*/false, CVRMixPossible, + ImplicitConversion) | + MIXUP_RefBind; + if (RType->isLValueReferenceType()) + return RefBindsToSameType(RType->getAs(), + LType.getTypePtr(), Ctx, + /* IsRefRightType =*/true, CVRMixPossible, + ImplicitConversion) | + MIXUP_RefBind; } // A parameter of type 'T' and 'const T' may bind with the same power. @@ -168,7 +227,7 @@ return HowPossibleToMixUpAtCallSite(LType.getUnqualifiedType(), RType.getUnqualifiedType(), Ctx, - CVRMixPossible) | + CVRMixPossible, ImplicitConversion) | MIXUP_CVR; } @@ -176,56 +235,371 @@ const auto *LTypedef = LType->getAs(); const auto *RTypedef = RType->getAs(); if (LTypedef && RTypedef) - return MIXUP_Typedef | HowPossibleToMixUpAtCallSite(LTypedef->desugar(), - RTypedef->desugar(), - Ctx, CVRMixPossible); + return HowPossibleToMixUpAtCallSite(LTypedef->desugar(), + RTypedef->desugar(), Ctx, + CVRMixPossible, ImplicitConversion) | + MIXUP_Typedef; if (LTypedef) - return MIXUP_Typedef | - HowPossibleToMixUpAtCallSite(LTypedef->desugar(), RType, Ctx, - CVRMixPossible); + return HowPossibleToMixUpAtCallSite(LTypedef->desugar(), RType, Ctx, + CVRMixPossible, ImplicitConversion) | + MIXUP_Typedef; if (RTypedef) - return MIXUP_Typedef | - HowPossibleToMixUpAtCallSite(LType, RTypedef->desugar(), Ctx, - CVRMixPossible); + return HowPossibleToMixUpAtCallSite(LType, RTypedef->desugar(), Ctx, + CVRMixPossible, ImplicitConversion) | + MIXUP_Typedef; } - if (LType->isPointerType() && RType->isPointerType()) + if (LType->isPointerType() && RType->isPointerType()) { // (Both types being the exact same pointer is handled by LType == RType.) + // The implicit conversion between any T* and U* possible in C is ignored, + // as virtually all compilers emit a warning if the conversion is done at a + // call site. + // Implicit conversions of two pointers should be diagnosed if they point to + // C++ records to find Derived-To-Base implicit casts. + // For non-user-types, do not check - giving an unrelated, e.g. "long *" in + // place of an "int *" is diagnosed anyways by warnings or errors. + bool ShouldDiagnoseImplicitBehindPtr = ImplicitConversion && + LType->getPointeeCXXRecordDecl() && + RType->getPointeeCXXRecordDecl(); return HowPossibleToMixUpAtCallSite( - LType->getPointeeType(), RType->getPointeeType(), Ctx, CVRMixPossible); + LType->getPointeeType(), RType->getPointeeType(), Ctx, CVRMixPossible, + ShouldDiagnoseImplicitBehindPtr); + } - // A parameter of type 'T' and 'const T' may bind with the same power. - // Case for both types being const qualified (for the same type) is handled - // by LType == RType. - if (CVRMixPossible && - (LType.isLocalConstQualified() || LType.isLocalVolatileQualified())) - return MIXUP_CVR | HowPossibleToMixUpAtCallSite(LType.getUnqualifiedType(), - RType, Ctx, CVRMixPossible); - if (CVRMixPossible && - (RType.isLocalConstQualified() || RType.isLocalVolatileQualified())) - return MIXUP_CVR | - HowPossibleToMixUpAtCallSite(LType, RType.getUnqualifiedType(), Ctx, - CVRMixPossible); + if (ImplicitConversion) { + const Type *LT = LType.getTypePtr(); + const Type *RT = RType.getTypePtr(); + + // Try approximating an implicit conversion sequence. + MixupData LTR = + HowConvertible(LT, RT, Ctx.getLangOpts(), /* AllowUserDefined =*/true); + MixupData RTL = + HowConvertible(RT, LT, Ctx.getLangOpts(), /* AllowUserDefined =*/true); + + if (LTR.ConvLTR || RTL.ConvRTL) + return {MIXUP_Implicit, LTR.ConvLTR, RTL.ConvRTL}; + } return MIXUP_None; } -static MixupTag RefBindsToSameType(const LValueReferenceType *LRef, - const Type *T, bool CVRMixPossible) { +static MixupData RefBindsToSameType(const LValueReferenceType *LRef, + const Type *T, const ASTContext &Ctx, + const bool IsRefRightType, + const bool CVRMixPossible, + const bool ImplicitConversion) { const QualType ReferredType = LRef->getPointeeType(); if (!ReferredType.isLocalConstQualified()) // A non-const reference doesn't bind with the same power as a "normal" // by-value parameter. return MIXUP_None; - if (const auto *TypedefTy = ReferredType.getTypePtr()->getAs()) + if (const auto *TypedefTy = ReferredType->getAs()) { // If the referred type is a typedef, try checking the mixup-chance on the // desugared type. - return HowPossibleToMixUpAtCallSite(TypedefTy->desugar(), QualType{T, 0}, - TypedefTy->getDecl()->getASTContext(), - CVRMixPossible); + if (!IsRefRightType) + return HowPossibleToMixUpAtCallSite(TypedefTy->desugar(), QualType{T, 0}, + Ctx, CVRMixPossible, + ImplicitConversion); + return HowPossibleToMixUpAtCallSite(QualType{T, 0}, TypedefTy->desugar(), + Ctx, CVRMixPossible, + ImplicitConversion); + } - return ReferredType.getTypePtr() == T ? MIXUP_Trivial : MIXUP_None; + if (ReferredType.getTypePtr() == T) + return MIXUP_Trivial; + + if (ImplicitConversion) { + // Try to see if the reference can be bound through an implicit conversion. + if (!IsRefRightType) + return HowPossibleToMixUpAtCallSite(ReferredType.getUnqualifiedType(), + QualType{T, 0}, Ctx, CVRMixPossible, + ImplicitConversion); + return HowPossibleToMixUpAtCallSite(QualType{T, 0}, + ReferredType.getUnqualifiedType(), Ctx, + CVRMixPossible, ImplicitConversion); + } + return MIXUP_None; +} + +static inline bool IsNumericConvertible(const Type *LT, const Type *RT) { + if (!LT || !RT) + return false; + + bool LI = LT->isIntegerType(); + bool LF = LT->isFloatingType(); + bool RI = RT->isIntegerType(); + bool RF = RT->isFloatingType(); + + // Through promotions and conversions, there is free passage between + // "integer types" and "floating types". + // Promotions don't change value, conversions may result in value or precision + // loss, but for the sake of easy understanding, we put the two under + // one umbrella. + return (LI && RI) || (LF && RF) || (LI && RF) || (LF && RI); +} + +/// Returns a Mixup indicating if the specified converting constructor can be +/// called with LT or after applying a standard conversion sequence on LT. +static MixupData TryConvertingConstructor(const Type *LT, + const CXXRecordDecl *RD, + const CXXConstructorDecl *Conv, + const LangOptions &LOpts) { + const ParmVarDecl *ConArg = Conv->getParamDecl(0); + MixupData Mix = HowPossibleToMixUpAtCallSite( + QualType{LT, 0}, ConArg->getType(), Conv->getASTContext(), + // Model qualifier mixing generously. + /* CVRMixPossible =*/true, + // Do not allow any further implicit conversions. + /* ImplicitConversion =*/false); + Mix.sanitise(); + if (Mix.Flag != MIXUP_None) { + // If simply applying the user-defined constructor works as is, this is + // the right constructor. + ConversionSequence Seq; + Seq.UserType = RD->getTypeForDecl(); + Mix.ConvLTR = Seq; + return Mix; + } + + // Otherwise, see if LT and the converting constructor's argument can be + // matched via a standard conversion before. This will take care of converting + // an int to another type. + ConversionSequence Seq; + MixupData Pre = HowConvertible(LT, ConArg->getType().getTypePtr(), LOpts, + /* AllowUserDefined =*/false); + if (Pre.Flag == MIXUP_None || Pre.Flag == MIXUP_Trivial) + // If not possible to mix, don't check. If trivial, previous case should + // have returned already. + return MIXUP_None; + Seq.StdPre = Pre.ConvLTR.StdPre; + + Mix = HowPossibleToMixUpAtCallSite( + QualType{Seq.StdPre, 0}, ConArg->getType(), Conv->getASTContext(), + // Model qualifier mixing generously. + /* CVRMixPossible =*/true, + // Do not allow any further implicit conversions. + /* ImplicitConversion =*/false); + Mix.sanitise(); + if (Mix.Flag != MIXUP_None) { + Seq.UserType = RD->getTypeForDecl(); + Mix.ConvLTR = Seq; + return Mix; + } + + return MIXUP_None; +} + +static MixupData TryConversionOperator(const CXXConversionDecl *Conv, + const Type *RT, + const LangOptions &LOpts) { + MixupData Mix = HowPossibleToMixUpAtCallSite( + Conv->getConversionType(), QualType{RT, 0}, Conv->getASTContext(), + // Model qualifier mixing generously. + /* CVRMixPossible =*/true, + // Do not allow any further implicit conversions. + /* ImplicitConversion =*/false); + Mix.sanitise(); + if (Mix.Flag != MIXUP_None) { + // If simply applying the user-defined operator works as is, this is + // the right conversion operator. + ConversionSequence Seq; + Seq.UserType = RT; + Mix.ConvLTR = Seq; + return Mix; + } + + // Otherwise, see if the converting operator's result and RT can be + // matched via a standard conversion after. This will take care of converting + // a result of float to another type. + ConversionSequence Seq; + MixupData Post = HowConvertible(Conv->getConversionType().getTypePtr(), RT, + LOpts, /* AllowUserDefined =*/false); + if (Post.Flag == MIXUP_None || Post.Flag == MIXUP_Trivial) + // If not possible to mix, don't check. If trivial, previous case should + // have returned already. + return MIXUP_None; + // Save the result type of the user-defined conversion operator. + Seq.UserType = Conv->getConversionType().getTypePtr(); + + // The unqualified version of the conversion operator's return type can be + // converted to the "needed" Right-Type (output type wanted by the caller + // of this method). Now see if between we lost any qualifiers. + Mix = HowPossibleToMixUpAtCallSite( + Conv->getConversionType(), + QualType{Conv->getConversionType().getTypePtr(), 0}, + Conv->getASTContext(), + // Model qualifier mixing generously. + /* CVRMixPossible =*/true, + // Do not allow any further implicit conversions. + /* ImplicitConversion =*/false); + Mix.sanitise(); + if (Mix.Flag != MIXUP_None) { + Seq.StdPost = RT; + Mix.ConvLTR = Seq; + return Mix; + } + + return MIXUP_None; +} + +static MixupData HowConvertible(const Type *LT, const Type *RT, + const LangOptions &LOpts, + bool AllowUserDefined) { + if (LT == RT) + return MIXUP_Trivial; + + ConversionSequence Seq; + + const auto *LBT = LT->getAs(); + const auto *RBT = RT->getAs(); + if (LBT && RBT && LBT == RBT) + return {MIXUP_Trivial}; + if (IsNumericConvertible(LBT, RBT)) { + // Builtin numerical types are back-and-forth convertible. + Seq.StdPre = RBT; + return {MIXUP_Implicit, Seq}; + } + + const auto *LET = LT->getAs(); + const auto *RET = RT->getAs(); + if (LET && !LET->isScopedEnumeralType() && RBT && + (RBT->isIntegerType() || RBT->isFloatingType())) { + // Enum can convert to underlying integer type. + Seq.StdPre = RBT; + return {MIXUP_Implicit, Seq}; + } + if (LBT && (LBT->isFloatingType() || LBT->isIntegerType()) && RET) { + // Enum cannot be constructed from any builtin type (neither int, nor + // float). + if (LOpts.CPlusPlus) + return MIXUP_None; + + // In C, you can go back and forth between numeric types. + Seq.StdPre = RET; + return {MIXUP_Implicit, Seq}; + } + + if (LOpts.CPlusPlus) { + // We are checking Left -> Right mixup, in which case Left <: Right should + // hold for DerivedToBase implicit cast. This is a standard implicit + // conversion. + const auto *LCXXRec = LT->getAsCXXRecordDecl(); + const auto *RCXXRec = RT->getAsCXXRecordDecl(); + if (LCXXRec && RCXXRec && LCXXRec->isCompleteDefinition() && + RCXXRec->isCompleteDefinition() && + (LCXXRec->isDerivedFrom(RCXXRec) || + LCXXRec->isVirtuallyDerivedFrom(RCXXRec))) { + Seq.StdPre = RT; + return {MIXUP_Implicit, Seq}; + } + } + + if (!LOpts.CPlusPlus || !AllowUserDefined) + // User-defined conversions are only sensible in C++ mode. + return MIXUP_None; + assert(!Seq && "Non-user defined conversion check should've returned."); + + MixupTag UserMixup = MIXUP_Invalid; + bool FoundConvertingCtor = false, FoundConversionOperator = false; + bool FoundMultipleMatches = false; + // An implicit conversion sequence may only contain at most *one* user-defined + // conversion. + if (const auto *RRT = RT->getAs()) { + const auto *RD = RRT->getAsCXXRecordDecl(); + if (!RD) + // Initialisation of C-style record types from an unrelated type is not + // possible. + return MIXUP_None; + if (!RD->isCompleteDefinition()) + // Incomplete definition means we don't know anything about members. + return MIXUP_None; + + for (const CXXConstructorDecl *Con : RD->ctors()) { + if (Con->isCopyOrMoveConstructor() || + !Con->isConvertingConstructor(/* AllowExplicit =*/false)) + continue; + + MixupData ConvertingResult = TryConvertingConstructor(LT, RD, Con, LOpts); + if (ConvertingResult.Flag == MIXUP_None) + continue; + + FoundConvertingCtor = true; + + if (!ConvertingResult.ConvLTR.StdPre) { + // A match without a pre-conversion was found for the converting ctor. + UserMixup |= ConvertingResult.Flag; + Seq = ConvertingResult.ConvLTR; + FoundMultipleMatches = false; + break; + } + + if (!FoundMultipleMatches && Seq) { + // If the loop executes multiple times, it's possible to find multiple + // converting constructors with different, ambiguous conversion + // sequences leading up to them. This is an error, but we need to check + // all constructors first for a possible exact match. + FoundMultipleMatches = true; + continue; + } + + // Register the match. + UserMixup |= ConvertingResult.Flag; + Seq = ConvertingResult.ConvLTR; + } + } + + if (const auto *LRT = LT->getAs()) { + const auto *RD = LRT->getAsCXXRecordDecl(); + if (!RD) + // Initialisation of things from an unrelated C-style record type is not + // possible. + return MIXUP_None; + if (!RD->isCompleteDefinition()) + // Against getVisibleConversionFunctions() assert. + return MIXUP_None; + + for (const NamedDecl *Method : RD->getVisibleConversionFunctions()) { + const auto *Con = dyn_cast(Method); + if (!Con || Con->isExplicit()) + continue; + + MixupData ConvertingResult = TryConversionOperator(Con, RT, LOpts); + if (ConvertingResult.Flag == MIXUP_None) + continue; + + FoundConversionOperator = true; + + if (!ConvertingResult.ConvLTR.StdPost) { + // A match without a pre-conversion was found for the converting oper. + UserMixup |= ConvertingResult.Flag; + Seq = ConvertingResult.ConvLTR; + FoundMultipleMatches = false; + break; + } + + if (!FoundMultipleMatches && Seq) { + // If the loop executes multiple times, it's possible to find multiple + // conversion operators with different, ambiguous conversion + // sequences coming from them. This is an error, but we need to check + // all constructors first for a possible exact match. + FoundMultipleMatches = true; + continue; + } + + // Register the match. + UserMixup |= ConvertingResult.Flag; + Seq = ConvertingResult.ConvLTR; + } + } + + if (FoundMultipleMatches) + return MIXUP_None; + + if (FoundConvertingCtor && FoundConversionOperator) + return MIXUP_None; + + return Seq ? MixupData{UserMixup, Seq} : MixupData{MIXUP_None}; } /// Gets the mixable range of the parameters of F starting with the param at @@ -237,16 +611,17 @@ const unsigned int ParamCount = F->getNumParams(); assert(StartIdx < ParamCount && "invalid start index given!"); const ASTContext &Ctx = F->getASTContext(); + const ParmVarDecl *First = F->getParamDecl(StartIdx); // A parameter (the one at StartIdx) was checked. MixRange.NumParamsChecked = 1; // Try checking parameters of the function from StartIdx until the range - // breaks. The range contains parameters that are mutually mixable with each - // other. + // breaks. for (unsigned int I = StartIdx + 1; I < ParamCount; ++I) { const ParmVarDecl *Ith = F->getParamDecl(I); + if (Checker.isIgnored(Ith)) // If the next parameter in the range is ignored, break the range. break; @@ -254,14 +629,16 @@ bool AnyMixupStored = false; for (unsigned int J = StartIdx; J < I; ++J) { const ParmVarDecl *Jth = F->getParamDecl(J); + Mixup M{Jth, Ith, HowPossibleToMixUpAtCallSite(Jth->getType(), Ith->getType(), Ctx, - Checker.CVRMixPossible)}; - M.sanitise(); - assert(M.Flags != MIXUP_Invalid && + Checker.CVRMixPossible, + Checker.ImplicitConversion)}; + M.Data.sanitise(); + assert(M.Data.Flag != MIXUP_Invalid && "Bits fell off, result is sentinel value."); - if (M.Flags != MIXUP_None) { + if (M.Data.Flag != MIXUP_None) { MixRange.Mixups.emplace_back(M); AnyMixupStored = true; } @@ -367,10 +744,11 @@ else if (const auto *PackTy = dyn_cast(Ty)) { PutTypeName(PackTy->getPattern(), OS, PP); OS << "..."; - } else + } else { // There are things like "GCC Vector type" and such that who knows how // to print properly? OS << "getTypeClassName() << '>'; + } } static std::string TypeNameAsString(const QualType QT, @@ -456,7 +834,8 @@ Options.get("IgnoredNames", DefaultIgnoredParamNames))), IgnoredParamTypes(utils::options::parseStringList( Options.get("IgnoredTypes", DefaultIgnoredParamTypes))), - CVRMixPossible(Options.get("CVRMixPossible", false)) {} + CVRMixPossible(Options.get("CVRMixPossible", false)), + ImplicitConversion(Options.get("ImplicitConversion", false)) {} void CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { @@ -466,6 +845,7 @@ Options.store(Opts, "IgnoredTypes", utils::options::serializeStringList(IgnoredParamTypes)); Options.store(Opts, "CVRMixPossible", CVRMixPossible); + Options.store(Opts, "ImplicitConversion", ImplicitConversion); } void CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck:: @@ -495,16 +875,20 @@ /// Returns whether the given Mixup, when diagnosed, should elaborate the type /// of the arguments involved. static bool NeedsToPrintType(const Mixup &M) { - return M.Flags & (MIXUP_Typedef | MIXUP_RefBind | MIXUP_CVR); + return M.Data.Flag & (MIXUP_Typedef | MIXUP_RefBind | MIXUP_CVR); +} + +/// Returns whether the given Mixup, when diagnosed, should elaborate on +/// implicit conversions. +static bool NeedsToElaborateImplicitConversion(const Mixup &M) { + return M.Data.Flag & MIXUP_Implicit; } void CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck::check( const MatchFinder::MatchResult &Result) { const auto *Fun = Result.Nodes.getNodeAs("fun"); - unsigned int ParamMixRangeStartIdx = 0; const unsigned int NumArgs = Fun->getNumParams(); - while (ParamMixRangeStartIdx < NumArgs) { if (isIgnored(Fun->getParamDecl(ParamMixRangeStartIdx))) { // If the current parameters's name or type name is ignored, don't try @@ -530,6 +914,8 @@ MixingRange.getFirstParm()->getType().getAsString(PP); bool HasAnyTypePrint = llvm::any_of(MixingRange.Mixups, NeedsToPrintType); + bool HasAnyImplicits = + llvm::any_of(MixingRange.Mixups, NeedsToElaborateImplicitConversion); { const ParmVarDecl *RangeFirst = MixingRange.getFirstParm(); @@ -537,7 +923,12 @@ { StringRef MainDiagnostic; - if (HasAnyTypePrint) + if (HasAnyImplicits) + MainDiagnostic = + "%0 adjacent parameters for '%1' of convertible types " + "may be easily swapped " + "by mistake"; + else if (HasAnyTypePrint) MainDiagnostic = "%0 adjacent parameters for '%1' of similar type " "are easily swapped " "by mistake"; @@ -549,7 +940,7 @@ auto Diag = diag(RangeFirst->getOuterLocStart(), MainDiagnostic) << static_cast(MixingRange.NumParamsChecked) << FunName; - if (!HasAnyTypePrint) + if (!HasAnyImplicits && !HasAnyTypePrint) Diag << MainParmTypeAsWritten; } @@ -568,11 +959,11 @@ llvm::SmallPtrSet TypedefResolutionPrintedForParm; llvm::SmallPtrSet CVRNotePrintedForParm; for (const Mixup &M : MixingRange.Mixups) { - assert(M.Flags >= MIXUP_Trivial && "Too low bits in mixup type."); + assert(M.Data.Flag >= MIXUP_Trivial && "Too low bits in mixup type."); // For MIXUP_Trivial no extra diagnostics required. std::string FirstParmType, SecondParmType; - if (NeedsToPrintType(M)) { + if (NeedsToPrintType(M) || NeedsToElaborateImplicitConversion(M)) { // Typedefs, and reference binds might result in the type of a variable // printed in the diagnostic, so we have to prepare it. FirstParmType = TypeNameAsString(M.First->getType(), PP); @@ -580,7 +971,7 @@ } if (NeedsToPrintType(M)) { - if (M.Flags & MIXUP_Typedef) { + if (M.Data.Flag & MIXUP_Typedef) { // FIXME: Don't emit the typedef note for the parameter that isn't // actually a typedef. if (!TypedefResolutionPrintedForParm.count(M.First)) { @@ -600,7 +991,7 @@ } } - if (M.Flags & (MIXUP_RefBind | MIXUP_CVR)) { + if (M.Data.Flag & (MIXUP_RefBind | MIXUP_CVR)) { if (!CVRNotePrintedForParm.count(M.Second)) { diag(M.Second->getOuterLocStart(), "at a call site, '%0' might bind with same force as '%1'", @@ -610,6 +1001,80 @@ } } } + + if (NeedsToElaborateImplicitConversion(M)) { + // FIXME: This seems to be VERY VERBOSE in some cases... + const ConversionSequence <R = M.Data.ConvLTR, &RTL = M.Data.ConvRTL; + const auto DiagnoseSequence = + [&M, &PP](const std::string &StartType, + const ConversionSequence &Seq) -> std::string { + assert((&Seq == &M.Data.ConvLTR || &Seq == &M.Data.ConvRTL) && + "sequence should be a sequence from the capture."); + llvm::SmallString<256> SS; + llvm::raw_svector_ostream OS{SS}; + + OS << '\'' << StartType << '\''; + if (Seq.StdPre) + OS << " -> '" << TypeNameAsString(QualType{Seq.StdPre, 0}, PP) + << '\''; + if (Seq.UserType) + OS << " -> '" << TypeNameAsString(QualType{Seq.UserType, 0}, PP) + << '\''; + if (Seq.StdPost) + OS << " -> '" << TypeNameAsString(QualType{Seq.StdPost, 0}, PP) + << '\''; + + return OS.str().str(); + }; + + if (LTR && RTL) { + std::string ConvMsg = + "'%0' and '%1' can suffer implicit conversions between one " + "another"; + if (!LTR.single() && !RTL.single()) + ConvMsg.append(": %2 and %3"); + else if (!LTR.single()) + ConvMsg.append(": %2 and trivially"); + else if (!RTL.single()) + ConvMsg.append(": trivially and %2"); + + auto Diag = + diag(M.Second->getOuterLocStart(), ConvMsg, DiagnosticIDs::Note) + << FirstParmType << SecondParmType; + if (!LTR.single() && !RTL.single()) + Diag << DiagnoseSequence(FirstParmType, LTR) + << DiagnoseSequence(SecondParmType, RTL); + else if (!LTR.single()) + Diag << DiagnoseSequence(FirstParmType, LTR); + else if (!RTL.single()) + Diag << DiagnoseSequence(SecondParmType, RTL); + } else { + std::string SeqDetails; + StringRef ConvMsg; + if (LTR) { + if (LTR.single()) + ConvMsg = "'%0' can be implicitly converted to '%1'"; + else { + ConvMsg = "'%0' can be implicitly converted to '%1': %2"; + SeqDetails = DiagnoseSequence(FirstParmType, LTR); + } + } else if (RTL) { + if (RTL.single()) + ConvMsg = "'%0' can be implicitly converted from '%1'"; + else { + ConvMsg = "'%0' can be implicitly converted from '%1': %2"; + SeqDetails = DiagnoseSequence(SecondParmType, RTL); + } + } + assert(!ConvMsg.empty() && "implicit bit set but we arrived here?"); + + auto Diag = + diag(M.Second->getOuterLocStart(), ConvMsg, DiagnosticIDs::Note) + << FirstParmType << SecondParmType; + if (!SeqDetails.empty()) + Diag << SeqDetails; + } + } } } } diff --git a/clang-tools-extra/docs/clang-tidy/checks/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.rst b/clang-tools-extra/docs/clang-tidy/checks/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.rst --- a/clang-tools-extra/docs/clang-tidy/checks/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.rst @@ -99,6 +99,41 @@ struct T {}; void f(T *tp, const T *ctp) {} +.. option:: ImplicitConversion + + Whether to allow the approximation of some usual + `implicit conversions `_ + when considering which type might be potentially mixed up with another. + If `0` (default value), the check does not consider **any** implicit + conversions. + A non-zero value turns this **on**. + A non-zero value will in almost all cases produce a **significantly broader** + set of results. + + .. warning:: + Turning the modelling of implicit conversion sequences on + relaxes the constraints for "type convertibility" significantly, + however, it also applies a generous performance hit on the check's cost. + The check will have to explore a **polynomially more** possibilities: + O(n\ :sup:`2`\ ) instead of O(n) for each function's ``n`` parameters. + The emitted diagnostics will also be more verbose, which might take more + time to stringify. + + It is advised to normally leave this option *off*. + + For details on what is matched by this option, see + `Implicit conversion modelling`_. + + The following examples will not produce a diagnostic unless + *ImplicitConversion* is set to a non-zero value. + + .. code-block:: c++ + + void f(char c, int i, double d) {} + + enum E { Ea, Eb }; + void f(E e, int i, double d) {} + Limitations ----------- @@ -133,10 +168,10 @@ template struct vector { - typedef T element_type; - typedef const T const_element_type; - typedef T & reference_type; - typedef const T & const_reference_type; + typename T element_type; + typename const T const_element_type; + typename T & reference_type; + typename const T & const_reference_type; }; // Finds the longest occurrence's length between elements "RightEnd" @@ -148,3 +183,36 @@ const vector & Vector, typename vector::const_reference_type RightEnd, const typename vector::element_type & LeftBegin) { /* ... */ } + + +Implicit conversion modelling +----------------------------- + +Given a function ``void f(T1 t, T2 u)``, unless ``T1`` and ``T2`` are the same, +the check won't warn in *"default"* mode. If ``ImplicitConversion`` is turned on +(see Options_), and either ``T1`` +`can be converted to `_ +``T2``, or vica versa, diagnostics will be made. +The "adjacent parameter mixup" is considered even if the conversion is asymmetric, +which is natural in most cases. + +In the case of the following function, the numeric parameters can be +converted between one another, and the ``SomeEnum`` is convertible to any +numeric type. +Thus, an "adjacent parameter range" of **5** is diagnosed here. + +.. code-blocK:: c++ + + enum SomeEnum { /* ... */ }; + void SomeFunction(int, float, double, unsigned long, SomeEnum) {} + +Currently, the following implicit conversions are modelled: + + - *standard conversion* sequences: + - numeric promotion and conversion between integer (including ``enum``) and + floating types, considered essentially the same under the umbrella term + "conversion" + - For **C** projects, the numeric conversion rules are relaxed to conform + to C rules. + - *user defined conversions*: non-``explicit`` converting constructors and + conversion operators. diff --git a/clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.c b/clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.c new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.c @@ -0,0 +1,102 @@ +// RUN: %check_clang_tidy %s \ +// RUN: experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type %t \ +// RUN: -config='{CheckOptions: [ \ +// RUN: {key: experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.ImplicitConversion, value: 1} \ +// RUN: ]}' -- + +void compare(int a, int b, int c) {} +// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: 3 adjacent parameters for 'compare' of similar type ('int') are easily swapped by mistake [experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type] +// CHECK-MESSAGES: :[[@LINE-2]]:18: note: the first parameter in this range is 'a' +// CHECK-MESSAGES: :[[@LINE-3]]:32: note: the last parameter in this range is 'c' + +void b(_Bool b1, _Bool b2, int i) {} +// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: 3 adjacent parameters for 'b' of convertible types may be easily swapped by mistake [experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type] +// CHECK-MESSAGES: :[[@LINE-2]]:14: note: the first parameter in this range is 'b1' +// CHECK-MESSAGES: :[[@LINE-3]]:32: note: the last parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-4]]:28: note: '_Bool' and 'int' can suffer implicit conversions between one another{{$}} + +void integral1(signed char sc, int si) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'integral1' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:28: note: the first parameter in this range is 'sc' +// CHECK-MESSAGES: :[[@LINE-3]]:36: note: the last parameter in this range is 'si' +// CHECK-MESSAGES: :[[@LINE-4]]:32: note: 'signed char' and 'int' can suffer implicit conversions between one another{{$}} + +void integral2(long l, int i) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'integral2' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:21: note: the first parameter in this range is 'l' +// CHECK-MESSAGES: :[[@LINE-3]]:28: note: the last parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-4]]:24: note: 'long' and 'int' can suffer implicit conversions between one another{{$}} + +void integral3(int i, long l) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'integral3' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:20: note: the first parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-3]]:28: note: the last parameter in this range is 'l' +// CHECK-MESSAGES: :[[@LINE-4]]:23: note: 'int' and 'long' can suffer implicit conversions between one another{{$}} + +void integral4(char c, int i, long l) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 3 adjacent parameters for 'integral4' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:21: note: the first parameter in this range is 'c' +// CHECK-MESSAGES: :[[@LINE-3]]:36: note: the last parameter in this range is 'l' +// CHECK-MESSAGES: :[[@LINE-4]]:24: note: 'char' and 'int' can suffer implicit conversions between one another{{$}} +// CHECK-MESSAGES: :[[@LINE-5]]:31: note: 'char' and 'long' can suffer implicit conversions between one another{{$}} + +void floating1(float f, double d) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'floating1' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:22: note: the first parameter in this range is 'f' +// CHECK-MESSAGES: :[[@LINE-3]]:32: note: the last parameter in this range is 'd' +// CHECK-MESSAGES: :[[@LINE-4]]:25: note: 'float' and 'double' can suffer implicit conversions between one another{{$}} + +typedef double D; +void floating2(float f, D d) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'floating2' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:22: note: the first parameter in this range is 'f' +// CHECK-MESSAGES: :[[@LINE-3]]:27: note: the last parameter in this range is 'd' +// CHECK-MESSAGES: :[[@LINE-4]]:16: note: after resolving type aliases, type of parameter 'f' is 'float' +// CHECK-MESSAGES: :[[@LINE-5]]:25: note: after resolving type aliases, type of parameter 'd' is 'double' +// CHECK-MESSAGES: :[[@LINE-6]]:25: note: 'float' and 'double' can suffer implicit conversions between one another{{$}} + +void floatToInt(float f, int i) {} +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 2 adjacent parameters for 'floatToInt' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:23: note: the first parameter in this range is 'f' +// CHECK-MESSAGES: :[[@LINE-3]]:30: note: the last parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-4]]:26: note: 'float' and 'int' can suffer implicit conversions between one another{{$}} + +enum En { A, + B }; + +void unscopedEnumToIntegral(enum En e, int i) {} +// CHECK-MESSAGES: :[[@LINE-1]]:29: warning: 2 adjacent parameters for 'unscopedEnumToIntegral' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:37: note: the first parameter in this range is 'e' +// CHECK-MESSAGES: :[[@LINE-3]]:44: note: the last parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-4]]:40: note: 'enum En' and 'int' can suffer implicit conversions between one another{{$}} + +void unscopedEnumToIntegral2(enum En e, unsigned long long ull) {} +// CHECK-MESSAGES: :[[@LINE-1]]:30: warning: 2 adjacent parameters for 'unscopedEnumToIntegral2' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:38: note: the first parameter in this range is 'e' +// CHECK-MESSAGES: :[[@LINE-3]]:60: note: the last parameter in this range is 'ull' +// CHECK-MESSAGES: :[[@LINE-4]]:41: note: 'enum En' and 'unsigned long long' can suffer implicit conversions between one another{{$}} + +void unscopedEnum3(char c, enum En e, char c2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: 3 adjacent parameters for 'unscopedEnum3' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:25: note: the first parameter in this range is 'c' +// CHECK-MESSAGES: :[[@LINE-3]]:44: note: the last parameter in this range is 'c2' +// CHECK-MESSAGES: :[[@LINE-4]]:28: note: 'char' and 'enum En' can suffer implicit conversions between one another{{$}} +// CHECK-MESSAGES: :[[@LINE-5]]:39: note: 'enum En' and 'char' can suffer implicit conversions between one another{{$}} + +void unscopedEnumToFloating(enum En e, long double ld) {} +// CHECK-MESSAGES: :[[@LINE-1]]:29: warning: 2 adjacent parameters for 'unscopedEnumToFloating' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:37: note: the first parameter in this range is 'e' +// CHECK-MESSAGES: :[[@LINE-3]]:52: note: the last parameter in this range is 'ld' +// CHECK-MESSAGES: :[[@LINE-4]]:40: note: 'enum En' and 'long double' can suffer implicit conversions between one another{{$}} + +void unscopedEnumToIntOrFloat(enum En e, int i, float f) {} +// CHECK-MESSAGES: :[[@LINE-1]]:31: warning: 3 adjacent parameters for 'unscopedEnumToIntOrFloat' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:39: note: the first parameter in this range is 'e' +// CHECK-MESSAGES: :[[@LINE-3]]:55: note: the last parameter in this range is 'f' +// CHECK-MESSAGES: :[[@LINE-4]]:42: note: 'enum En' and 'int' can suffer implicit conversions between one another{{$}} +// CHECK-MESSAGES: :[[@LINE-5]]:49: note: 'enum En' and 'float' can suffer implicit conversions between one another{{$}} +// CHECK-MESSAGES: :[[@LINE-6]]:49: note: 'int' and 'float' can suffer implicit conversions between one another{{$}} + +void ptr(int *i, long *l) {} +// NO-WARN: Though 'int' and 'long' can be converted between one-another +// implicitly, mixing such pointers is diagnosed by compiler warnings. diff --git a/clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.cpp b/clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.cpp @@ -0,0 +1,362 @@ +// RUN: %check_clang_tidy %s \ +// RUN: experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type %t \ +// RUN: -config='{CheckOptions: [ \ +// RUN: {key: experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.ImplicitConversion, value: 1} \ +// RUN: ]}' -- + +void compare(int a, int b, int c) {} +// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: 3 adjacent parameters for 'compare' of similar type ('int') are easily swapped by mistake [experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type] +// CHECK-MESSAGES: :[[@LINE-2]]:18: note: the first parameter in this range is 'a' +// CHECK-MESSAGES: :[[@LINE-3]]:32: note: the last parameter in this range is 'c' + +void b(bool b1, bool b2, int i) {} +// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: 3 adjacent parameters for 'b' of convertible types may be easily swapped by mistake [experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type] +// CHECK-MESSAGES: :[[@LINE-2]]:13: note: the first parameter in this range is 'b1' +// CHECK-MESSAGES: :[[@LINE-3]]:30: note: the last parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-4]]:26: note: 'bool' and 'int' can suffer implicit conversions between one another{{$}} + +void integral1(signed char sc, int si) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'integral1' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:28: note: the first parameter in this range is 'sc' +// CHECK-MESSAGES: :[[@LINE-3]]:36: note: the last parameter in this range is 'si' +// CHECK-MESSAGES: :[[@LINE-4]]:32: note: 'signed char' and 'int' can suffer implicit conversions between one another{{$}} + +void integral2(long l, int i) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'integral2' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:21: note: the first parameter in this range is 'l' +// CHECK-MESSAGES: :[[@LINE-3]]:28: note: the last parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-4]]:24: note: 'long' and 'int' can suffer implicit conversions between one another{{$}} + +void integral3(int i, long l) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'integral3' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:20: note: the first parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-3]]:28: note: the last parameter in this range is 'l' +// CHECK-MESSAGES: :[[@LINE-4]]:23: note: 'int' and 'long' can suffer implicit conversions between one another{{$}} + +void integral4(char c, int i, long l) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 3 adjacent parameters for 'integral4' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:21: note: the first parameter in this range is 'c' +// CHECK-MESSAGES: :[[@LINE-3]]:36: note: the last parameter in this range is 'l' +// CHECK-MESSAGES: :[[@LINE-4]]:24: note: 'char' and 'int' can suffer implicit conversions between one another{{$}} +// CHECK-MESSAGES: :[[@LINE-5]]:31: note: 'char' and 'long' can suffer implicit conversions between one another{{$}} + +void floating1(float f, double d) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'floating1' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:22: note: the first parameter in this range is 'f' +// CHECK-MESSAGES: :[[@LINE-3]]:32: note: the last parameter in this range is 'd' +// CHECK-MESSAGES: :[[@LINE-4]]:25: note: 'float' and 'double' can suffer implicit conversions between one another{{$}} + +typedef double D; +void floating2(float f, D d) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'floating2' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:22: note: the first parameter in this range is 'f' +// CHECK-MESSAGES: :[[@LINE-3]]:27: note: the last parameter in this range is 'd' +// CHECK-MESSAGES: :[[@LINE-4]]:16: note: after resolving type aliases, type of parameter 'f' is 'float' +// CHECK-MESSAGES: :[[@LINE-5]]:25: note: after resolving type aliases, type of parameter 'd' is 'double' +// CHECK-MESSAGES: :[[@LINE-6]]:25: note: 'float' and 'double' can suffer implicit conversions between one another{{$}} + +void floatToInt(float f, int i) {} +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 2 adjacent parameters for 'floatToInt' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:23: note: the first parameter in this range is 'f' +// CHECK-MESSAGES: :[[@LINE-3]]:30: note: the last parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-4]]:26: note: 'float' and 'int' can suffer implicit conversions between one another{{$}} + +enum En { A, + B }; + +void unscopedEnumToIntegral(En e, int i) {} +// CHECK-MESSAGES: :[[@LINE-1]]:29: warning: 2 adjacent parameters for 'unscopedEnumToIntegral' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:32: note: the first parameter in this range is 'e' +// CHECK-MESSAGES: :[[@LINE-3]]:39: note: the last parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-4]]:35: note: 'En' can be implicitly converted to 'int' + +void unscopedEnumToIntegral2(En e, unsigned long long ull) {} +// CHECK-MESSAGES: :[[@LINE-1]]:30: warning: 2 adjacent parameters for 'unscopedEnumToIntegral2' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:33: note: the first parameter in this range is 'e' +// CHECK-MESSAGES: :[[@LINE-3]]:55: note: the last parameter in this range is 'ull' +// CHECK-MESSAGES: :[[@LINE-4]]:36: note: 'En' can be implicitly converted to 'unsigned long long' + +void unscopedEnum3(char c, En e, char c2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: 3 adjacent parameters for 'unscopedEnum3' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:25: note: the first parameter in this range is 'c' +// CHECK-MESSAGES: :[[@LINE-3]]:39: note: the last parameter in this range is 'c2' +// CHECK-MESSAGES: :[[@LINE-4]]:28: note: 'char' can be implicitly converted from 'En' +// CHECK-MESSAGES: :[[@LINE-5]]:34: note: 'En' can be implicitly converted to 'char' + +void unscopedEnumToFloating(En e, long double ld) {} +// CHECK-MESSAGES: :[[@LINE-1]]:29: warning: 2 adjacent parameters for 'unscopedEnumToFloating' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:32: note: the first parameter in this range is 'e' +// CHECK-MESSAGES: :[[@LINE-3]]:47: note: the last parameter in this range is 'ld' +// CHECK-MESSAGES: :[[@LINE-4]]:35: note: 'En' can be implicitly converted to 'long double' + +void unscopedEnumToIntOrFloat(En e, int i, float f) {} +// CHECK-MESSAGES: :[[@LINE-1]]:31: warning: 3 adjacent parameters for 'unscopedEnumToIntOrFloat' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:34: note: the first parameter in this range is 'e' +// CHECK-MESSAGES: :[[@LINE-3]]:50: note: the last parameter in this range is 'f' +// CHECK-MESSAGES: :[[@LINE-4]]:37: note: 'En' can be implicitly converted to 'int' +// CHECK-MESSAGES: :[[@LINE-5]]:44: note: 'En' can be implicitly converted to 'float' +// CHECK-MESSAGES: :[[@LINE-6]]:44: note: 'int' and 'float' can suffer implicit conversions between one another{{$}} + +enum class SEn { C, + D }; +void scopedEnumToIntegral(SEn e, int i) {} +// NO-WARN: Scoped enumerations mustn't be promoted. + +struct FromInt { + FromInt(int); +}; + +void userConv1(int i, FromInt fi) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'userConv1' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:20: note: the first parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-3]]:31: note: the last parameter in this range is 'fi' +// CHECK-MESSAGES: :[[@LINE-4]]:23: note: 'int' can be implicitly converted to 'FromInt' + +void userConv1c(int i, const FromInt cfi) {} +// NO-WARN: CVRMixPossible is set to 0, so an 'int' and a 'const "int"' does not mix. + +void userConv1cr(int i, const FromInt &cfir) {} +// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: 2 adjacent parameters for 'userConv1cr' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:22: note: the first parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-3]]:40: note: the last parameter in this range is 'cfir' +// CHECK-MESSAGES: :[[@LINE-4]]:25: note: at a call site, 'const FromInt &' might bind with same force as 'int' +// CHECK-MESSAGES: :[[@LINE-5]]:25: note: 'int' can be implicitly converted to 'const FromInt &' + +void userConv2(unsigned long long ull, FromInt fi) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'userConv2' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:35: note: the first parameter in this range is 'ull' +// CHECK-MESSAGES: :[[@LINE-3]]:48: note: the last parameter in this range is 'fi' +// CHECK-MESSAGES: :[[@LINE-4]]:40: note: 'unsigned long long' can be implicitly converted to 'FromInt': 'unsigned long long' -> 'int' -> 'FromInt' + +struct ToInt { + operator int() const; +}; + +void userConv3(int i, ToInt ti) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'userConv3' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:20: note: the first parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-3]]:29: note: the last parameter in this range is 'ti' +// CHECK-MESSAGES: :[[@LINE-4]]:23: note: 'int' can be implicitly converted from 'ToInt' + +void userConv4(double d, FromInt fi) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'userConv4' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:23: note: the first parameter in this range is 'd' +// CHECK-MESSAGES: :[[@LINE-3]]:34: note: the last parameter in this range is 'fi' +// CHECK-MESSAGES: :[[@LINE-4]]:26: note: 'double' can be implicitly converted to 'FromInt': 'double' -> 'int' -> 'FromInt' + +void userConv4cr(double d, const FromInt &cfir) {} +// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: 2 adjacent parameters for 'userConv4cr' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:25: note: the first parameter in this range is 'd' +// CHECK-MESSAGES: :[[@LINE-3]]:43: note: the last parameter in this range is 'cfir' +// CHECK-MESSAGES: :[[@LINE-4]]:28: note: at a call site, 'const FromInt &' might bind with same force as 'double' +// CHECK-MESSAGES: :[[@LINE-5]]:28: note: 'double' can be implicitly converted to 'const FromInt &': 'double' -> 'int' -> 'FromInt' + +struct FromDouble { + FromDouble(double); +}; + +void userConv5(En e, FromDouble fd) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'userConv5' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:19: note: the first parameter in this range is 'e' +// CHECK-MESSAGES: :[[@LINE-3]]:33: note: the last parameter in this range is 'fd' +// CHECK-MESSAGES: :[[@LINE-4]]:22: note: 'En' can be implicitly converted to 'FromDouble': 'En' -> 'double' -> 'FromDouble' + +struct Ambiguous { + Ambiguous(int); + Ambiguous(long); +}; + +void userConv6(En e, Ambiguous a) {} +// NO-WARN: En -> int -> Ambiguous vs. En -> long -> Ambiguous. + +struct ToExplicitInt { + explicit operator int() const; +}; +struct FromExplicitInt { + explicit FromExplicitInt(int); +}; + +void nonConverting1(int i, FromExplicitInt fei) {} +// NO-WARN: There is no chance of mix-up as 'fei' has only explicit constructor. + +void nonConverting2(int i, ToExplicitInt tei) {} +// NO-WARN: 'tei' converts to 'int' only in explicit context. + +struct Both { + Both(int); + operator int() const; +}; + +typedef int I; +typedef double D; + +void both1(int i, Both b) {} +// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: 2 adjacent parameters for 'both1' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:16: note: the first parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-3]]:24: note: the last parameter in this range is 'b' +// CHECK-MESSAGES: :[[@LINE-4]]:19: note: 'int' and 'Both' can suffer implicit conversions between one another{{$}} + +void both2(double d, Both b) {} +// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: 2 adjacent parameters for 'both2' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:19: note: the first parameter in this range is 'd' +// CHECK-MESSAGES: :[[@LINE-3]]:27: note: the last parameter in this range is 'b' +// CHECK-MESSAGES: :[[@LINE-4]]:22: note: 'double' and 'Both' can suffer implicit conversions between one another: 'double' -> 'int' -> 'Both' and 'Both' -> 'int' -> 'double' + +void both2typedef(D d, Both b) {} +// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 2 adjacent parameters for 'both2typedef' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:21: note: the first parameter in this range is 'd' +// CHECK-MESSAGES: :[[@LINE-3]]:29: note: the last parameter in this range is 'b' +// CHECK-MESSAGES: :[[@LINE-4]]:19: note: after resolving type aliases, type of parameter 'd' is 'double' +// CHECK-MESSAGES: :[[@LINE-5]]:24: note: after resolving type aliases, type of parameter 'b' is 'Both' +// CHECK-MESSAGES: :[[@LINE-6]]:24: note: 'double' and 'Both' can suffer implicit conversions between one another: 'double' -> 'int' -> 'Both' and 'Both' -> 'int' -> 'double' + +struct BoxedInt { + BoxedInt(); + BoxedInt(const BoxedInt &); + BoxedInt(BoxedInt &&); + BoxedInt(const Both &B); +}; + +void both3(int i, BoxedInt biv) {} +// NO-WARN: Two converting constructor calls (int->both->BoxedInt) is not +// possible implicitly. + +struct IntAndDouble { + IntAndDouble(const int); + IntAndDouble(const double); + + operator int() const; + operator double() const; +}; + +void twoTypes1(int i, IntAndDouble iad) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'twoTypes1' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:20: note: the first parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-3]]:36: note: the last parameter in this range is 'iad' +// CHECK-MESSAGES: :[[@LINE-4]]:23: note: 'int' and 'IntAndDouble' can suffer implicit conversions between one another{{$}} + +void twoTypes2(double d, IntAndDouble iad) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'twoTypes2' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:23: note: the first parameter in this range is 'd' +// CHECK-MESSAGES: :[[@LINE-3]]:39: note: the last parameter in this range is 'iad' +// CHECK-MESSAGES: :[[@LINE-4]]:26: note: 'double' and 'IntAndDouble' can suffer implicit conversions between one another{{$}} + +void twoTypes3(unsigned long ul, IntAndDouble iad) {} +// NO-WARN: Ambiguous constructor call and conversion call, should we take +// 'int' or 'double' route from 'unsigned long'? + +struct IADTypedef { + IADTypedef(I); + IADTypedef(D); + operator I() const; + operator D() const; +}; + +void twoTypedefs1(int i, IADTypedef iadt) {} +// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 2 adjacent parameters for 'twoTypedefs1' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:23: note: the first parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-3]]:37: note: the last parameter in this range is 'iadt' +// CHECK-MESSAGES: :[[@LINE-4]]:26: note: 'int' and 'IADTypedef' can suffer implicit conversions between one another{{$}} + +void twoTypedefs2(double d, IADTypedef iadt) {} +// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 2 adjacent parameters for 'twoTypedefs2' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:26: note: the first parameter in this range is 'd' +// CHECK-MESSAGES: :[[@LINE-3]]:40: note: the last parameter in this range is 'iadt' +// CHECK-MESSAGES: :[[@LINE-4]]:29: note: 'double' and 'IADTypedef' can suffer implicit conversions between one another{{$}} + +void twoTypedefs3(unsigned long ul, IADTypedef iadt) {} +// NO-WARN: Ambiguous constructor call and conversion call, should we take +// 'int' or 'double' route from 'unsigned long'? + +struct Fwd1; +struct Fwd2; +void forwards(Fwd1 *f1, Fwd2 *f2) {} +// NO-WARN and NO-CRASH: Don't try to compare incomplete types. + +struct Rec {}; +struct FromRec { + FromRec(const Rec &); +}; +struct ToRec { + operator Rec() const; +}; +struct RecBoth { + RecBoth(Rec); + operator Rec() const; +}; + +void record2record_1(Rec r, FromRec fr) {} +// CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 2 adjacent parameters for 'record2record_1' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:26: note: the first parameter in this range is 'r' +// CHECK-MESSAGES: :[[@LINE-3]]:37: note: the last parameter in this range is 'fr' +// CHECK-MESSAGES: :[[@LINE-4]]:29: note: 'Rec' can be implicitly converted to 'FromRec' + +void record2record_2(Rec r, ToRec tr) {} +// CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 2 adjacent parameters for 'record2record_2' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:26: note: the first parameter in this range is 'r' +// CHECK-MESSAGES: :[[@LINE-3]]:35: note: the last parameter in this range is 'tr' +// CHECK-MESSAGES: :[[@LINE-4]]:29: note: 'Rec' can be implicitly converted from 'ToRec' + +void record2record_3(Rec r, RecBoth rb) {} +// CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 2 adjacent parameters for 'record2record_3' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:26: note: the first parameter in this range is 'r' +// CHECK-MESSAGES: :[[@LINE-3]]:37: note: the last parameter in this range is 'rb' +// CHECK-MESSAGES: :[[@LINE-4]]:29: note: 'Rec' and 'RecBoth' can suffer implicit conversions between one another{{$}} + +void record2record_4(Rec r, FromRec fr, ToRec tr, RecBoth rb) {} +// CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 4 adjacent parameters for 'record2record_4' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:26: note: the first parameter in this range is 'r' +// CHECK-MESSAGES: :[[@LINE-3]]:59: note: the last parameter in this range is 'rb' +// CHECK-MESSAGES: :[[@LINE-4]]:29: note: 'Rec' can be implicitly converted to 'FromRec' +// CHECK-MESSAGES: :[[@LINE-5]]:41: note: 'Rec' can be implicitly converted from 'ToRec' +// CHECK-MESSAGES: :[[@LINE-6]]:51: note: 'Rec' and 'RecBoth' can suffer implicit conversions between one another{{$}} + +struct X; +struct Y; +struct X { + X(); + X(Y); + operator Y(); +}; +struct Y { + Y(); + Y(X); + operator X(); +}; +void ambiguous_records(X x, Y y) {} +// NO-WARN: Ambiguous conversion if the arguments are swapped, which results in +// compiler error: f3(Y{}, X{}) + +struct Base {}; +struct Derived1 : Base {}; +struct Derived2 : Base {}; +void upcasting(Base *bp, Derived1 *d1p, Derived2 *d2p) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 3 adjacent parameters for 'upcasting' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:22: note: the first parameter in this range is 'bp' +// CHECK-MESSAGES: :[[@LINE-3]]:51: note: the last parameter in this range is 'd2p' +// CHECK-MESSAGES: :[[@LINE-4]]:26: note: 'Base *' can be implicitly converted from 'Derived1 *' +// CHECK-MESSAGES: :[[@LINE-5]]:41: note: 'Base *' can be implicitly converted from 'Derived2 *' + +void upcasting_ref(const Base &br, const Derived1 &d1r, const Derived2 &d2r) {} +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: 3 adjacent parameters for 'upcasting_ref' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:32: note: the first parameter in this range is 'br' +// CHECK-MESSAGES: :[[@LINE-3]]:73: note: the last parameter in this range is 'd2r' +// CHECK-MESSAGES: :[[@LINE-4]]:36: note: at a call site, 'const Derived1 &' might bind with same force as 'const Base &' +// CHECK-MESSAGES: :[[@LINE-5]]:36: note: 'const Base &' can be implicitly converted from 'const Derived1 &' +// CHECK-MESSAGES: :[[@LINE-6]]:57: note: at a call site, 'const Derived2 &' might bind with same force as 'const Base &' +// CHECK-MESSAGES: :[[@LINE-7]]:57: note: 'const Base &' can be implicitly converted from 'const Derived2 &' + +// Reduced case from live crash on OpenCV +// http://github.com/opencv/opencv/blob/1996ae4a42d7c7cd338fbdd4abbd83b41b448328/modules/core/include/opencv2/core/types.hpp#L173 +template +struct TemplateConversion { + TemplateConversion(); + TemplateConversion(const T &); + template + operator TemplateConversion() const; +}; +typedef TemplateConversion IntConvert; +typedef TemplateConversion FloatConvert; +void templated_conversion_to_other_specialisation(FloatConvert fc, IntConvert ic) {} +// NO-WARN: Template conversion operators are not resolved, we have approximated +// much of Sema already... diff --git a/clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-minlen3.cpp b/clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-minlen3.cpp --- a/clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-minlen3.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-minlen3.cpp @@ -203,3 +203,20 @@ // CHECK-MESSAGES: :[[@LINE-6]]:25: note: after resolving type aliases, type of parameter 'Background' is 'OldSchoolTermColour' // CHECK-MESSAGES: :[[@LINE-6]]:25: note: after resolving type aliases, type of parameter 'Foreground' is 'OldSchoolTermColour' // CHECK-MESSAGES: :[[@LINE-6]]:25: note: after resolving type aliases, type of parameter 'Border' is 'OldSchoolTermColour' + +// NO-WARN: Implicit conversions should not warn if the check option is turned off. + +void integral1(signed char sc, int si) {} + +struct FromInt { + FromInt(int); +}; +void userConv1(int i, FromInt fi) {} + +struct Base {}; +struct Derived1 : Base {}; +struct Derived2 : Base {}; +void upcasting(Base *bp, Derived1 *d1p, Derived2 *d2p) {} +void upcasting_ref(const Base &br, const Derived1 &d1r, const Derived2 &d2r) {} + +// END of NO-WARN. diff --git a/clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.c b/clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.c --- a/clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.c +++ b/clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.c @@ -32,3 +32,6 @@ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: 2 adjacent parameters for 'equals2' of similar type ('S') are // CHECK-MESSAGES: :[[@LINE-2]]:15: note: the first parameter in this range is 'l' // CHECK-MESSAGES: :[[@LINE-3]]:20: note: the last parameter in this range is 'r' + +void ptrs(int *i, long *l) {} +// NO-WARN: Mixing fundamentals' pointers is diagnosed by compiler warnings.