From 3fef8809cf98b4a161cbc6e91821b1898e8ca3fa Mon Sep 17 00:00:00 2001 From: leftibot Date: Fri, 6 Nov 2026 15:10:25 -0700 Subject: [PATCH] Fix #5: Port the no-implicit-conversions check as a clang-query check The misc-no-implicit-conversions clang-tidy check from lefticus/llvm-project only existed as a C++ clang-tidy module. Its AST matchers are ported here to a clang-query check with pass/fail cases derived from its upstream test. The slicing matcher is narrowed to base copy/move constructors so derived-to-base pointer arguments to explicit constructors are no longer reported. Co-Authored-By: Claude Opus 5.5 --- .../no-implicit-conversions.query | 34 +++++++++++++++++++ .../fail/arithmetic_conversions_in_calls.cpp | 11 ++++++ .../tests/fail/conversion_operator.cpp | 17 ++++++++++ .../fail/converting_constructor_template.cpp | 22 ++++++++++++ .../tests/fail/explicit_bool_in_condition.cpp | 20 +++++++++++ .../fail/non_trivial_object_from_pointer.cpp | 14 ++++++++ .../tests/fail/slicing.cpp | 17 ++++++++++ .../tests/fail/zero_to_pointer.cpp | 7 ++++ .../tests/pass/binding_derived_to_base.cpp | 17 ++++++++++ ...erived_pointer_to_explicit_constructor.cpp | 13 +++++++ .../tests/pass/exact_types.cpp | 23 +++++++++++++ .../tests/pass/explicit_conversions.cpp | 14 ++++++++ .../tests/pass/literals_and_switch.cpp | 14 ++++++++ 13 files changed, 223 insertions(+) create mode 100644 checks/clang-query/no-implicit-conversions/no-implicit-conversions.query create mode 100644 checks/clang-query/no-implicit-conversions/tests/fail/arithmetic_conversions_in_calls.cpp create mode 100644 checks/clang-query/no-implicit-conversions/tests/fail/conversion_operator.cpp create mode 100644 checks/clang-query/no-implicit-conversions/tests/fail/converting_constructor_template.cpp create mode 100644 checks/clang-query/no-implicit-conversions/tests/fail/explicit_bool_in_condition.cpp create mode 100644 checks/clang-query/no-implicit-conversions/tests/fail/non_trivial_object_from_pointer.cpp create mode 100644 checks/clang-query/no-implicit-conversions/tests/fail/slicing.cpp create mode 100644 checks/clang-query/no-implicit-conversions/tests/fail/zero_to_pointer.cpp create mode 100644 checks/clang-query/no-implicit-conversions/tests/pass/binding_derived_to_base.cpp create mode 100644 checks/clang-query/no-implicit-conversions/tests/pass/derived_pointer_to_explicit_constructor.cpp create mode 100644 checks/clang-query/no-implicit-conversions/tests/pass/exact_types.cpp create mode 100644 checks/clang-query/no-implicit-conversions/tests/pass/explicit_conversions.cpp create mode 100644 checks/clang-query/no-implicit-conversions/tests/pass/literals_and_switch.cpp diff --git a/checks/clang-query/no-implicit-conversions/no-implicit-conversions.query b/checks/clang-query/no-implicit-conversions/no-implicit-conversions.query new file mode 100644 index 0000000..b6165ec --- /dev/null +++ b/checks/clang-query/no-implicit-conversions/no-implicit-conversions.query @@ -0,0 +1,34 @@ +# Flags implicit conversions; spell them out with a named cast or an explicit +# construction instead. Ported from misc-no-implicit-conversions in +# https://github.com/lefticus/llvm-project (add-implicit-conversion-checks). +# +# Value category adjustments, qualification changes, function decay, binding +# derived objects to base references or pointers, string literal decay, +# integer literal narrowing, nullptr and switch/case integral promotions are +# allowed. +set traversal AsIs + +# Implicit conversions between built-in types, 0 to pointer and calls to +# conversion operators. +match implicitCastExpr(isExpansionInMainFile(), + unless(anyOf(hasCastKind("CK_LValueToRValue"), + hasCastKind("CK_FunctionToPointerDecay"), + hasCastKind("CK_NoOp"), + hasCastKind("CK_UncheckedDerivedToBase"), + hasCastKind("CK_DerivedToBase"), + allOf(hasCastKind("CK_ArrayToPointerDecay"), has(stringLiteral())), + allOf(hasCastKind("CK_IntegralCast"), has(integerLiteral())), + allOf(hasCastKind("CK_NullToPointer"), has(cxxNullPtrLiteralExpr())), + hasParent(cxxStaticCastExpr()), + allOf(hasCastKind("CK_IntegralCast"), hasParent(switchStmt())), + allOf(hasCastKind("CK_IntegralCast"), + hasParent(constantExpr(hasParent(caseStmt()))))))) + +# Temporaries with destructors created through a converting constructor. +match materializeTemporaryExpr(isExpansionInMainFile(), + has(cxxBindTemporaryExpr(has(cxxConstructExpr(has(implicitCastExpr())))))) + +# Copies of a derived object into a base object. +match cxxConstructExpr(isExpansionInMainFile(), + hasDeclaration(cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor()))), + has(implicitCastExpr(hasCastKind("CK_DerivedToBase")))) diff --git a/checks/clang-query/no-implicit-conversions/tests/fail/arithmetic_conversions_in_calls.cpp b/checks/clang-query/no-implicit-conversions/tests/fail/arithmetic_conversions_in_calls.cpp new file mode 100644 index 0000000..318f809 --- /dev/null +++ b/checks/clang-query/no-implicit-conversions/tests/fail/arithmetic_conversions_in_calls.cpp @@ -0,0 +1,11 @@ +void take_double(double); +void take_long_long(long long); + +void promote_arguments(const float single, const int integer) +{ + take_double(single);// expect-finding + take_double(integer);// expect-finding + take_long_long(integer);// expect-finding + take_double(1.3f);// expect-finding + take_double(1);// expect-finding +} diff --git a/checks/clang-query/no-implicit-conversions/tests/fail/conversion_operator.cpp b/checks/clang-query/no-implicit-conversions/tests/fail/conversion_operator.cpp new file mode 100644 index 0000000..a38c819 --- /dev/null +++ b/checks/clang-query/no-implicit-conversions/tests/fail/conversion_operator.cpp @@ -0,0 +1,17 @@ +template struct basic_string_view +{ + basic_string_view(const Char *);// NOLINT(google-explicit-constructor) +}; + +template struct basic_string +{ + basic_string(); + ~basic_string(); + operator basic_string_view() const;// NOLINT(google-explicit-constructor) +}; + +basic_string_view dangling_view() +{ + const basic_string value; + return value;// expect-finding +} diff --git a/checks/clang-query/no-implicit-conversions/tests/fail/converting_constructor_template.cpp b/checks/clang-query/no-implicit-conversions/tests/fail/converting_constructor_template.cpp new file mode 100644 index 0000000..ca9e2c8 --- /dev/null +++ b/checks/clang-query/no-implicit-conversions/tests/fail/converting_constructor_template.cpp @@ -0,0 +1,22 @@ +struct Base +{ +}; +struct Derived : Base +{ +}; + +template struct Holder +{ + template Holder(const Holder &);// NOLINT(google-explicit-constructor) + + Holder() = default; +}; + +void take_holder(const Holder &); + +void pass_converted_holders() +{ + const auto derived = Holder{}; + take_holder(derived);// expect-finding + take_holder(Holder());// expect-finding +} diff --git a/checks/clang-query/no-implicit-conversions/tests/fail/explicit_bool_in_condition.cpp b/checks/clang-query/no-implicit-conversions/tests/fail/explicit_bool_in_condition.cpp new file mode 100644 index 0000000..1dba6e1 --- /dev/null +++ b/checks/clang-query/no-implicit-conversions/tests/fail/explicit_bool_in_condition.cpp @@ -0,0 +1,20 @@ +struct string +{ +}; + +struct istream +{ + explicit operator bool() const; + ~istream(); +}; + +istream &getline(istream &input, string &output); + +void read_lines() +{ + istream input; + string output; + + while (getline(input, output)) {// expect-finding + } +} diff --git a/checks/clang-query/no-implicit-conversions/tests/fail/non_trivial_object_from_pointer.cpp b/checks/clang-query/no-implicit-conversions/tests/fail/non_trivial_object_from_pointer.cpp new file mode 100644 index 0000000..7b949f8 --- /dev/null +++ b/checks/clang-query/no-implicit-conversions/tests/fail/non_trivial_object_from_pointer.cpp @@ -0,0 +1,14 @@ +template struct basic_string +{ + basic_string(const Char *);// NOLINT(google-explicit-constructor) + basic_string(const basic_string &); + ~basic_string(); + [[nodiscard]] const Char *c_str() const; +}; + +using string = basic_string; + +string copy_through_c_str(const string &value) +{ + return value.c_str();// expect-finding +} diff --git a/checks/clang-query/no-implicit-conversions/tests/fail/slicing.cpp b/checks/clang-query/no-implicit-conversions/tests/fail/slicing.cpp new file mode 100644 index 0000000..b85de33 --- /dev/null +++ b/checks/clang-query/no-implicit-conversions/tests/fail/slicing.cpp @@ -0,0 +1,17 @@ +struct Base +{ +}; +struct Derived : Base +{ +}; + +void take_by_value(Base); + +void slice_derived() +{ + const auto derived = Derived{}; + + take_by_value(derived);// expect-finding + [[maybe_unused]] const Base sliced = derived;// expect-finding + take_by_value(Derived{});// expect-finding +} diff --git a/checks/clang-query/no-implicit-conversions/tests/fail/zero_to_pointer.cpp b/checks/clang-query/no-implicit-conversions/tests/fail/zero_to_pointer.cpp new file mode 100644 index 0000000..f240d5b --- /dev/null +++ b/checks/clang-query/no-implicit-conversions/tests/fail/zero_to_pointer.cpp @@ -0,0 +1,7 @@ +struct Base +{ +}; + +void take_pointer(const Base *); + +void pass_zero() { take_pointer(0); }// expect-finding diff --git a/checks/clang-query/no-implicit-conversions/tests/pass/binding_derived_to_base.cpp b/checks/clang-query/no-implicit-conversions/tests/pass/binding_derived_to_base.cpp new file mode 100644 index 0000000..75fc449 --- /dev/null +++ b/checks/clang-query/no-implicit-conversions/tests/pass/binding_derived_to_base.cpp @@ -0,0 +1,17 @@ +struct Base +{ +}; +struct Derived : Base +{ +}; + +void take_base(const Base &); +void take_base(const Base *); + +void bind_derived() +{ + const auto derived = Derived{}; + take_base(&derived); + take_base(Derived{}); + take_base(derived); +} diff --git a/checks/clang-query/no-implicit-conversions/tests/pass/derived_pointer_to_explicit_constructor.cpp b/checks/clang-query/no-implicit-conversions/tests/pass/derived_pointer_to_explicit_constructor.cpp new file mode 100644 index 0000000..06da993 --- /dev/null +++ b/checks/clang-query/no-implicit-conversions/tests/pass/derived_pointer_to_explicit_constructor.cpp @@ -0,0 +1,13 @@ +struct Base +{ +}; +struct Derived : Base +{ +}; + +struct Observer +{ + explicit Observer(const Base *); +}; + +void observe(const Derived &derived) { [[maybe_unused]] const Observer observer(&derived); } diff --git a/checks/clang-query/no-implicit-conversions/tests/pass/exact_types.cpp b/checks/clang-query/no-implicit-conversions/tests/pass/exact_types.cpp new file mode 100644 index 0000000..f27351f --- /dev/null +++ b/checks/clang-query/no-implicit-conversions/tests/pass/exact_types.cpp @@ -0,0 +1,23 @@ +struct Base +{ +}; + +template struct Holder +{ + template Holder(const Holder &);// NOLINT(google-explicit-constructor) + + Holder() = default; +}; + +void take_holder(const Holder &); +void take_by_value(Base); +void take_pointer(const Base *); +void take_double(double); + +void pass_exact_types(const double value) +{ + take_holder(Holder()); + take_by_value(Base{}); + take_pointer(nullptr); + take_double(value); +} diff --git a/checks/clang-query/no-implicit-conversions/tests/pass/explicit_conversions.cpp b/checks/clang-query/no-implicit-conversions/tests/pass/explicit_conversions.cpp new file mode 100644 index 0000000..74b6b15 --- /dev/null +++ b/checks/clang-query/no-implicit-conversions/tests/pass/explicit_conversions.cpp @@ -0,0 +1,14 @@ +struct istream +{ + explicit operator bool() const; + [[nodiscard]] bool good() const; +}; + +void take_double(double); + +void convert_explicitly(const istream &input, const int value) +{ + while (input.good()) {} + take_double(static_cast(value)); + [[maybe_unused]] const bool valid = static_cast(input); +} diff --git a/checks/clang-query/no-implicit-conversions/tests/pass/literals_and_switch.cpp b/checks/clang-query/no-implicit-conversions/tests/pass/literals_and_switch.cpp new file mode 100644 index 0000000..eea3625 --- /dev/null +++ b/checks/clang-query/no-implicit-conversions/tests/pass/literals_and_switch.cpp @@ -0,0 +1,14 @@ +void take_string(const char *); + +[[nodiscard]] int classify(const char value) +{ + take_string("literal"); + [[maybe_unused]] const long widened = 1; + + switch (value) { + case 'a': + return 1; + default: + return 0; + } +}