whisperity updated this revision to Diff 259324.
whisperity retitled this revision from "[clang-tidy] Approximate implicit
conversion issues for the
'experimental-cppcoreguidelines-avoid-adjacent-arguments-of-the-same-type'
check" to "[clang-tidy] Approximate implicit conversion issues in
'experimental-cppcoreguidelines-avoid-adjacent-arguments-of-the-same-type'".
whisperity added a comment.
- Re-organised code, removed debug prints, rebased, the usual tidy-up.
- Bug fixes on certain crashes like incomplete types, conversion operator
templates, etc.
- Even more bug fixes against crashes, hopefully... sorry I lost the individual
commits in a `squash` I think
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D75041/new/
https://reviews.llvm.org/D75041
Files:
clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.cpp
clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.h
clang-tools-extra/docs/clang-tidy/checks/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.rst
clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.c
clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.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.c
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.
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-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,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 <typename T>
+struct TemplateConversion {
+ TemplateConversion();
+ TemplateConversion(const T &);
+ template <typename T2>
+ operator TemplateConversion<T2>() const;
+};
+typedef TemplateConversion<int> IntConvert;
+typedef TemplateConversion<float> 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-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/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,41 @@
struct T {};
void f(T *tp, const T *ctp) {}
+.. option:: ImplicitConversion
+
+ Whether to allow the approximation of some usual
+ `implicit conversions <https://en.cppreference.com/w/cpp/language/implicit_conversion>`_
+ 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 <typename T>
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<T> & Vector,
typename vector<T>::const_reference_type RightEnd,
const typename vector<T>::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 <https://en.cppreference.com/w/cpp/language/implicit_conversion>`_
+``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.
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
@@ -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
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 <string>
@@ -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<Mixup>::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<const ParenType>(LType.getTypePtr()))
return HowPossibleToMixUpAtCallSite(LType.getSingleStepDesugaredType(Ctx),
- RType, Ctx, CVRMixPossible);
+ RType, Ctx, CVRMixPossible,
+ ImplicitConversion);
if (dyn_cast<const ParenType>(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<LValueReferenceType>(),
- 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<LValueReferenceType>(),
- 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<LValueReferenceType>(),
+ RType.getTypePtr(), Ctx,
+ /* IsRefRightType =*/false, CVRMixPossible,
+ ImplicitConversion) |
+ MIXUP_RefBind;
+ if (RType->isLValueReferenceType())
+ return RefBindsToSameType(RType->getAs<LValueReferenceType>(),
+ 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<TypedefType>();
const auto *RTypedef = RType->getAs<TypedefType>();
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<TypedefType>())
+ if (const auto *TypedefTy = ReferredType->getAs<TypedefType>()) {
// 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<BuiltinType>();
+ const auto *RBT = RT->getAs<BuiltinType>();
+ 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<EnumType>();
+ const auto *RET = RT->getAs<EnumType>();
+ 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<RecordType>()) {
+ 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<RecordType>()) {
+ 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<CXXConversionDecl>(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<PackExpansionType>(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 << "<unhandled Type of " << Ty->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<FunctionDecl>("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<unsigned>(MixingRange.NumParamsChecked)
<< FunName;
- if (!HasAnyTypePrint)
+ if (!HasAnyImplicits && !HasAnyTypePrint)
Diag << MainParmTypeAsWritten;
}
@@ -568,11 +959,11 @@
llvm::SmallPtrSet<const ParmVarDecl *, 4> TypedefResolutionPrintedForParm;
llvm::SmallPtrSet<const ParmVarDecl *, 4> 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;
+ }
+ }
}
}
}
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits