Index: clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.h =================================================================== --- clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.h +++ clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.h @@ -47,6 +47,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 Index: clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.cpp =================================================================== --- clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.cpp +++ 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 @@ -21,6 +22,29 @@ #define DEBUG_TYPE "AdjacentParameters" #include "llvm/Support/Debug.h" // clang-format on +template +static inline std::string format_bytes_do(std::size_t N) { + static_assert(std::is_unsigned::value, + "size_t is not unsigned?!"); + constexpr std::size_t WidthWithPadding = Width + (Width / 4); + char C[WidthWithPadding]; + for (std::size_t CurPos = 0; CurPos < WidthWithPadding; ++CurPos) { + if (CurPos % 5 == 0) { + // Group digits by 4. + C[CurPos] = ' '; + continue; + } + + C[CurPos] = N % 2 ? '1' : '0'; + N >>= 1; + } + + return std::string{std::rbegin(C), std::rend(C)}; +} + +template static inline std::string format_bytes(T &&t) { + return format_bytes_do(t); +} using namespace clang::ast_matchers; @@ -37,60 +61,101 @@ // 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) }; +#undef FBIT 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); + } +}; + +struct MixupData { + MixupTag Flag; + ConversionSequence ConvLTR, ConvRTL; - Mixup(const ParmVarDecl *A, const ParmVarDecl *B, MixupTag Flags) - : First(A), Second(B), Flags(Flags) {} + 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) {} - Mixup operator|(MixupTag EnableFlags) const { - return {First, Second, Flags | EnableFlags}; + 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. @@ -118,19 +183,33 @@ } // 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) { LLVM_DEBUG(llvm::dbgs() << "HowPossibleToMixUpAtCallSite?\n Left:"; LType.dump(llvm::dbgs()); llvm::dbgs() << "\n\t and \n Right: "; RType.dump(llvm::dbgs()); llvm::dbgs() << '\n';); @@ -141,12 +220,18 @@ } // Remove certain sugars that don't affect mixability from the types. - if (dyn_cast(LType.getTypePtr())) + if (dyn_cast(LType.getTypePtr())) { + LLVM_DEBUG(llvm::dbgs() << "Left is some sugared Parens()\n"); return HowPossibleToMixUpAtCallSite(LType.getSingleStepDesugaredType(Ctx), - RType, Ctx, CVRMixPossible); - if (dyn_cast(RType.getTypePtr())) + RType, Ctx, CVRMixPossible, + ImplicitConversion); + } + if (dyn_cast(RType.getTypePtr())) { + LLVM_DEBUG(llvm::dbgs() << "Right is some sugared Parens()\n"); 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, @@ -156,37 +241,46 @@ if (LType->isLValueReferenceType()) { LLVM_DEBUG(llvm::dbgs() << "!!! Left is an lvalue reference type\n";); - 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; + // 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()) { LLVM_DEBUG(llvm::dbgs() << "!!! Right is an lvalue reference type.\n";); - MixupTag RefBind = RefBindsToSameType(RType->getAs(), - LType.getTypePtr(), CVRMixPossible); - return RefBind | MIXUP_RefBind; + 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. + // An argument 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 (LType.getLocalCVRQualifiers() || RType.getLocalCVRQualifiers()) { - LLVM_DEBUG(llvm::dbgs() - << "!!! Left or right is locally CVR: Left: " - << LType.getLocalQualifiers().getCVRQualifiers() << ", Right: " - << RType.getLocalQualifiers().getCVRQualifiers() << '\n'); + LLVM_DEBUG( + llvm::dbgs() + << "!!! Left or right is locally CVR: Left: " + << format_bytes( + (unsigned char)LType.getLocalQualifiers().getCVRQualifiers()) + << ", Right: " + << format_bytes( + (unsigned char)RType.getLocalQualifiers().getCVRQualifiers()) + << '\n'); if (!CVRMixPossible) return MIXUP_None; return HowPossibleToMixUpAtCallSite(LType.getUnqualifiedType(), RType.getUnqualifiedType(), Ctx, - CVRMixPossible) | + CVRMixPossible, ImplicitConversion) | MIXUP_CVR; } @@ -196,57 +290,86 @@ if (LTypedef && RTypedef) { LLVM_DEBUG(llvm::dbgs() << "!!! Both Left and Right are typedef types.\n";); - return MIXUP_Typedef | HowPossibleToMixUpAtCallSite(LTypedef->desugar(), - RTypedef->desugar(), - Ctx, CVRMixPossible); + return HowPossibleToMixUpAtCallSite(LTypedef->desugar(), + RTypedef->desugar(), Ctx, + CVRMixPossible, ImplicitConversion) | + MIXUP_Typedef; } if (LTypedef) { LLVM_DEBUG(llvm::dbgs() << "!!! Left is typedef type.\n";); - return MIXUP_Typedef | - HowPossibleToMixUpAtCallSite(LTypedef->desugar(), RType, Ctx, - CVRMixPossible); + return HowPossibleToMixUpAtCallSite(LTypedef->desugar(), RType, Ctx, + CVRMixPossible, ImplicitConversion) | + MIXUP_Typedef; } if (RTypedef) { LLVM_DEBUG(llvm::dbgs() << "!!! Right is typedef type.\n";); - return MIXUP_Typedef | - HowPossibleToMixUpAtCallSite(LType, RTypedef->desugar(), Ctx, - CVRMixPossible); + return HowPossibleToMixUpAtCallSite(LType, RTypedef->desugar(), Ctx, + CVRMixPossible, ImplicitConversion) | + MIXUP_Typedef; } } 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. LLVM_DEBUG(llvm::dbgs() << "!!! Both Left and Right are pointer types.\n";); + // 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(); + LLVM_DEBUG( + llvm::dbgs() + << "Checking mixup between pointed types. Allowing implicit modelling? " + << ShouldDiagnoseImplicitBehindPtr << "\nOriginally the user specified " + << ImplicitConversion << ".\n"); 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())) { - LLVM_DEBUG(llvm::dbgs() << "!!! Left is CV qualified."; - LType.dump(llvm::dbgs()); llvm::dbgs() << '\n';); - return MIXUP_CVR | HowPossibleToMixUpAtCallSite(LType.getUnqualifiedType(), - RType, Ctx, CVRMixPossible); - } - if (CVRMixPossible && - (RType.isLocalConstQualified() || RType.isLocalVolatileQualified())) { - LLVM_DEBUG(llvm::dbgs() << "!!! Right is CV qualified\n"; - RType.dump(llvm::dbgs()); llvm::dbgs() << '\n';); - return MIXUP_CVR | - HowPossibleToMixUpAtCallSite(LType, RType.getUnqualifiedType(), Ctx, - CVRMixPossible); - } - - LLVM_DEBUG(llvm::dbgs() << "<<< End of logic reached, types don't match.";); + if (ImplicitConversion) { + LLVM_DEBUG(llvm::dbgs() + << "Implicit conversion check has been enabled! Checking...\n"); + + const Type *LT = LType.getTypePtr(); + const Type *RT = RType.getTypePtr(); + + // Try approximating an implicit conversion sequence. + LLVM_DEBUG(llvm::dbgs() << "?????????? Check Left->Right ..............\n"); + MixupData LTR = + HowConvertible(LT, RT, Ctx.getLangOpts(), /* AllowUserDefined =*/true); + LLVM_DEBUG(llvm::dbgs() + << "Left->Right flags: " << format_bytes(LTR.Flag) << '\n'); + if (LTR.Flag != MIXUP_None) + LLVM_DEBUG(llvm::dbgs() << "Left -> Right mixup\n"); + + LLVM_DEBUG(llvm::dbgs() << "?????????? Check Right->Left ..............\n"); + MixupData RTL = + HowConvertible(RT, LT, Ctx.getLangOpts(), /* AllowUserDefined =*/true); + LLVM_DEBUG(llvm::dbgs() + << "Left<-Right flags: " << format_bytes(RTL.Flag) << '\n'); + if (RTL.Flag != MIXUP_None) + LLVM_DEBUG(llvm::dbgs() << "Left <- Right mixup\n"); + + if (LTR.ConvLTR && RTL.ConvRTL) + return {MIXUP_Implicit, LTR.ConvLTR, RTL.ConvRTL}; + } + + LLVM_DEBUG(llvm::dbgs() << "<<< End of logic reached, types don't match.\n";); return MIXUP_None; } -static MixupTag RefBindsToSameType(const LValueReferenceType *LRef, - const Type *T, bool CVRMixPossible) { - LLVM_DEBUG(llvm::dbgs() << "RefBindsToSameType?\n"; LRef->dump(llvm::dbgs()); +static MixupData RefBindsToSameType(const LValueReferenceType *LRef, + const Type *T, const ASTContext &Ctx, + const bool IsRefRightType, + const bool CVRMixPossible, + const bool ImplicitConversion) { + LLVM_DEBUG(llvm::dbgs() << "refBindsToSameType?\n"; LRef->dump(llvm::dbgs()); llvm::dbgs() << "\n\t and \n"; T->dump(llvm::dbgs()); llvm::dbgs() << '\n';); @@ -260,18 +383,379 @@ 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. LLVM_DEBUG(llvm::dbgs() << "Reference to a typedef, calling back...\n";); - 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); } LLVM_DEBUG(llvm::dbgs() << "is referred type type_ptr() and T the same? " << (ReferredType.getTypePtr() == T) << '\n';); - 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) { + LLVM_DEBUG(llvm::dbgs() << "IsNumericConvertible(" << LT << ", " << RT + << ")\n"); + 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); + LLVM_DEBUG(llvm::dbgs() << "Conv no-std (pre sanitise): " + << format_bytes(Mix.Flag) << '\n'); + Mix.sanitise(); + LLVM_DEBUG(llvm::dbgs() << "Conv no-std (post sanitise): " + << format_bytes(Mix.Flag) << '\n'); + if (Mix.Flag != MIXUP_None) { + // If simply applying the user-defined constructor works as is, this is + // the right constructor. + LLVM_DEBUG(llvm::dbgs() << "Conversion constructor matches exactly.\n"); + 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); + LLVM_DEBUG(llvm::dbgs() << "Pre-UD: " << format_bytes(Pre.Flag) + << " to type \n"; + Pre.ConvLTR.StdPre->dump(llvm::dbgs()); llvm::dbgs() << '\n'); + 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; + + LLVM_DEBUG(llvm::dbgs() + << "Conversion constructor matches after standard conversion.\n"); + + 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); + LLVM_DEBUG(llvm::dbgs() << "Conv (pre sanitise): " << format_bytes(Mix.Flag) + << '\n'); + Mix.sanitise(); + LLVM_DEBUG(llvm::dbgs() << "Conv (post sanitise): " << format_bytes(Mix.Flag) + << '\n'); + if (Mix.Flag != MIXUP_None) { + LLVM_DEBUG(llvm::dbgs() << "Conversion constructor matches!\n"); + 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); + LLVM_DEBUG(llvm::dbgs() << "Conv no-std (pre sanitise): " + << format_bytes(Mix.Flag) << '\n'); + Mix.sanitise(); + LLVM_DEBUG(llvm::dbgs() << "Conv no-std (post sanitise): " + << format_bytes(Mix.Flag) << '\n'); + 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); + LLVM_DEBUG(llvm::dbgs() << "Post-UD: " << format_bytes(Post.Flag) + << " to type \n"; + Post.ConvLTR.StdPre->dump(llvm::dbgs()); llvm::dbgs() << '\n'); + 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); + LLVM_DEBUG(llvm::dbgs() << "Conv (pre sanitise): " << format_bytes(Mix.Flag) + << '\n'); + Mix.sanitise(); + LLVM_DEBUG(llvm::dbgs() << "Conv (post sanitise): " << format_bytes(Mix.Flag) + << '\n'); + if (Mix.Flag != MIXUP_None) { + LLVM_DEBUG(llvm::dbgs() << "Conversion operator matches!\n"); + 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) { + LLVM_DEBUG(llvm::dbgs() << ">>>>> How convertible? \n"; + LT->dump(llvm::dbgs()); llvm::dbgs() << '\n'; + RT->dump(llvm::dbgs()); llvm::dbgs() << '\n'); + if (LT == RT) { + LLVM_DEBUG(llvm::dbgs() + << "Left and right is the same, returning Trivial.\n"); + 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. + LLVM_DEBUG(llvm::dbgs() << "Builtin conversion between numerics.\n"); + 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. + LLVM_DEBUG(llvm::dbgs() << "Enum -> integer/float.\n"); + 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) { + LLVM_DEBUG(llvm::dbgs() << "Numeric -/-> Enum NOT POSSIBLE (C++).\n"); + 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(); + LLVM_DEBUG(llvm::dbgs() << "Left as record: " << LCXXRec + << ", Right as record: " << RCXXRec << '\n'); + if (LCXXRec && RCXXRec && LCXXRec->isCompleteDefinition() && + RCXXRec->isCompleteDefinition() && + (LCXXRec->isDerivedFrom(RCXXRec) || + LCXXRec->isVirtuallyDerivedFrom(RCXXRec))) { + LLVM_DEBUG(llvm::dbgs() << "Left <: Right, giving standard DerivedToBase " + "cast Left->Right.\n"); + Seq.StdPre = RT; + return {MIXUP_Implicit, Seq}; + } + } + + LLVM_DEBUG(llvm::dbgs() << "Before checking user defined...\n"); + + 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."); + + LLVM_DEBUG(llvm::dbgs() << "Checking for user-defined conversions...\n"); + 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()) { + LLVM_DEBUG(llvm::dbgs() << "Right hand side is a record type.\n"); + 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; + LLVM_DEBUG(llvm::dbgs() << "Found a converting constructor? \n"; + Con->dump(llvm::dbgs()); llvm::dbgs() << '\n'); + + MixupData ConvertingResult = TryConvertingConstructor(LT, RD, Con, LOpts); + if (ConvertingResult.Flag == MIXUP_None) + continue; + + LLVM_DEBUG(llvm::dbgs() << "Came back with a positive result.\n"); + FoundConvertingCtor = true; + + if (!ConvertingResult.ConvLTR.StdPre) { + // A match without a pre-conversion was found for the converting ctor. + LLVM_DEBUG(llvm::dbgs() << "! EXACT MATCH.\n"); + 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()) { + LLVM_DEBUG(llvm::dbgs() << "Left hand side is a record type.\n"); + 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()) { + LLVM_DEBUG(llvm::dbgs() << "Found visible conv operator: \n"; + Method->dump(llvm::dbgs()); llvm::dbgs() << '\n';); + + const auto *Con = dyn_cast(Method); + if (!Con || Con->isExplicit()) + continue; + LLVM_DEBUG(llvm::dbgs() << "Found a conversion method? \n"; + Con->dump(llvm::dbgs()); llvm::dbgs() << '\n'); + + MixupData ConvertingResult = TryConversionOperator(Con, RT, LOpts); + if (ConvertingResult.Flag == MIXUP_None) + continue; + + LLVM_DEBUG(llvm::dbgs() << "Came back with a positive result.\n"); + FoundConversionOperator = true; + + if (!ConvertingResult.ConvLTR.StdPost) { + // A match without a pre-conversion was found for the converting oper. + LLVM_DEBUG(llvm::dbgs() << "! EXACT MATCH.\n"); + 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) { + LLVM_DEBUG(llvm::dbgs() << "Multiple conversion sequences found! :(\n"); + return MIXUP_None; + } + if (FoundConvertingCtor && FoundConversionOperator) { + LLVM_DEBUG(llvm::dbgs() << "Left<->Right ambiguous as both can be " + "converted to each other...\n"); + return MIXUP_None; + } + + LLVM_DEBUG(llvm::dbgs() << "Flags at the end of 'howConvertible?': " + << format_bytes(UserMixup) << '\n'); + return Seq ? MixupData{UserMixup, Seq} : MixupData{MIXUP_None}; } /// Gets the mixable range of the parameters of F starting with the param at @@ -292,8 +776,7 @@ 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); LLVM_DEBUG(llvm::dbgs() << "Check " << I << "th param:\n"; @@ -311,16 +794,17 @@ Mixup M{Jth, Ith, HowPossibleToMixUpAtCallSite(Jth->getType(), Ith->getType(), Ctx, - Checker.CVRMixPossible)}; + Checker.CVRMixPossible, + Checker.ImplicitConversion)}; LLVM_DEBUG(llvm::dbgs() << "\tMixup (before sanitise)? " - << llvm::format_bytes(M.Flags) << "\n";); - M.sanitise(); + << llvm::format_bytes(M.Data.Flag) << "\n";); + M.Data.sanitise(); LLVM_DEBUG(llvm::dbgs() << "\tMixup (after sanitise)? " - << llvm::format_bytes(M.Flags) << "\n";); - assert(M.Flags != MIXUP_Invalid && + << llvm::format_bytes(M.Data.Flag) << "\n";); + 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; } @@ -532,7 +1016,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) { @@ -542,6 +1027,7 @@ Options.store(Opts, "IgnoredTypes", utils::options::serializeStringList(IgnoredParamTypes)); Options.store(Opts, "CVRMixPossible", CVRMixPossible); + Options.store(Opts, "ImplicitConversion", ImplicitConversion); } void CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck:: @@ -571,7 +1057,13 @@ /// 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( @@ -609,6 +1101,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(); @@ -616,7 +1110,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"; @@ -628,7 +1127,7 @@ auto Diag = diag(RangeFirst->getOuterLocStart(), MainDiagnostic) << static_cast(MixingRange.NumParamsChecked) << FunName; - if (!HasAnyTypePrint) + if (!HasAnyImplicits && !HasAnyTypePrint) Diag << MainParmTypeAsWritten; } @@ -647,11 +1146,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. LLVM_DEBUG(llvm::dbgs() @@ -663,7 +1162,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)) { @@ -683,7 +1182,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'", @@ -693,6 +1192,55 @@ } } } + + 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); + } + } } } } Index: clang-tools-extra/docs/clang-tidy/checks/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.rst =================================================================== --- clang-tools-extra/docs/clang-tidy/checks/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.rst +++ clang-tools-extra/docs/clang-tidy/checks/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.rst @@ -99,6 +99,38 @@ 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) {} + Limitations ----------- @@ -133,10 +165,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 +180,34 @@ 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 vice versa, diagnostics will be made. + +In the case of the following function, the numeric parameters can be +converted between one another. +``SomeEnum`` is convertible to any numeric type. +Thus, an "adjacent parameter range" of **4** 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. Index: clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-cvr-on.cpp =================================================================== --- clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-cvr-on.cpp +++ clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-cvr-on.cpp @@ -1,5 +1,6 @@ // RUN: %check_clang_tidy %s \ // RUN: experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type %t \ +// RUN: --debug --debug-only=AdjacentParameters \ // RUN: -config='{CheckOptions: [ \ // RUN: {key: experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.CVRMixPossible, value: 1} \ // RUN: ]}' -- Index: clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.c =================================================================== --- /dev/null +++ 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. Index: clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.cpp @@ -0,0 +1,308 @@ +// 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 }; + +// NO-WARN: In C++, unscoped enums can be converted to scalars, but not the +// other way around. + +void unscopedEnumToIntegral(En e, int i) {} // NO-WARN + +void unscopedEnumToIntegral2(En e, unsigned long long ull) {} // NO-WARN + +void unscopedEnum3(char c, En e, char c2) {} // NO-WARN + +void unscopedEnumToFloating(En e, long double ld) {} // NO-WARN + +void unscopedEnumToIntOrFloat(En e, int i, float f) {} +// CHECK-MESSAGES: :[[@LINE-1]]:37: warning: 2 adjacent parameters for 'unscopedEnumToIntOrFloat' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:41: note: the first parameter in this range is 'i' +// CHECK-MESSAGES: :[[@LINE-3]]:50: note: the last parameter in this range is 'f' +// CHECK-MESSAGES: :[[@LINE-4]]: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); +}; + +// NO-WARN: User-defined conversions in just one direction. + +void userConv1(int i, FromInt fi) {} + +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) {} + +void userConv2(unsigned long long ull, FromInt fi) {} + +struct ToInt { + operator int() const; +}; + +void userConv3(int i, ToInt ti) {} + +void userConv4(double d, FromInt fi) {} + +void userConv4cr(double d, const FromInt &cfir) {} + +struct FromDouble { + FromDouble(double); +}; + +void userConv5(En e, FromDouble fd) {} + +struct Ambiguous { + Ambiguous(int); + Ambiguous(long); +}; + +void userConv6(En e, Ambiguous a) {} + +struct ToExplicitInt { + explicit operator int() const; +}; +struct FromExplicitInt { + explicit FromExplicitInt(int); +}; + +void nonConverting1(int i, FromExplicitInt fei) {} + +void nonConverting2(int i, ToExplicitInt tei) {} + +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) {} +// NO-WARN: One-way used defined conversion only. + +void record2record_2(Rec r, ToRec tr) {} +// NO-WARN: One-way used defined conversion only. + +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) {} +// NO-WARN: "Rec" and "RecBoth" are not adjacent. + +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) {} +// NO-WARN: Upcasts are unidirectional. + +void upcasting_ref(const Base &br, const Derived1 &d1r, const Derived2 &d2r) {} + +// This example is weird, because normally * or & upcasts are unidirectional, +// except when the user explicitly writes converting functions. +struct WeirdDerived; +struct WeirdBase { + operator WeirdDerived() const; +}; +struct WeirdDerived : WeirdBase {}; + +void weird_upcast(const WeirdBase &wbr, const WeirdDerived &wdr) {} +// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 2 adjacent parameters for 'weird_upcast' of convertible +// CHECK-MESSAGES: :[[@LINE-2]]:36: note: the first parameter in this range is 'wbr' +// CHECK-MESSAGES: :[[@LINE-3]]:61: note: the last parameter in this range is 'wdr' +// CHECK-MESSAGES: :[[@LINE-4]]:41: note: at a call site, 'const WeirdDerived &' might bind with same force as 'const WeirdBase &' +// CHECK-MESSAGES: :[[@LINE-5]]:41: note: 'const WeirdBase &' and 'const WeirdDerived &' can suffer implicit conversions between one another{{$}} + +// 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... Index: clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-minlen3.cpp =================================================================== --- clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-minlen3.cpp +++ 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. Index: clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.c =================================================================== --- clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.c +++ 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.