Repository: llvm/llvm-project
File: clang-tools-extra/clang-tidy/bugprone/FoldInitTypeCheck.cpp (C++)

Code before:
        //     outside the iterator class.
        recordType(
            hasDeclaration(cxxRecordDecl(isSameOrDerivedFrom(has(functionDecl(
                hasOverloadedOperatorName("*"),
                returns(qualType(hasCanonicalType(anyOf(
                    // `value_type& operator*();`
                    references(BuiltinTypeWithId(ID)),
                    // `value_type operator*();`
                    BuiltinTypeWithId(ID),
                    // `auto operator*();`, `decltype(auto) operator*();`
                    autoType(hasDeducedType(BuiltinTypeWithId(ID)))
                    //
                    )))))))))));
  };

  const auto IteratorParam = parmVarDecl(
      hasType(hasCanonicalType(IteratorWithValueType("IterValueType"))));
  const auto Iterator2Param = parmVarDecl(
      hasType(hasCanonicalType(IteratorWithValueType("Iter2ValueType"))));
  const auto InitParam = parmVarDecl(hasType(BuiltinTypeWithId("InitType")));

  // Transparent standard functors that preserve arithmetic conversion semantics
  const auto TransparentFunctor = expr(hasType(
      hasCanonicalType(recordType(hasDeclaration(cxxRecordDecl(hasAnyName(
          "::std::plus", "::std::minus", "::std::multiplies", "::std::divides",
          "::std::bit_and", "::std::bit_or", "::std::bit_xor")))))));

  // std::accumulate, std::reduce.
  Finder->addMatcher(
      callExpr(
          callee(functionDecl(hasAnyName("::std::accumulate", "::std::reduce"),
                              hasParameter(0, IteratorParam),
                              hasParameter(2, InitParam))),
          anyOf(argumentCountIs(3),
                allOf(argumentCountIs(4), hasArgument(3, TransparentFunctor))))
          .bind("Call"),
      this);
  // std::inner_product.
  Finder->addMatcher(
      callExpr(
          callee(functionDecl(
              hasName("::std::inner_product"), hasParameter(0, IteratorParam),
              hasParameter(2, Iterator2Param), hasParameter(3, InitParam))),
          anyOf(argumentCountIs(4),
                allOf(argumentCountIs(6), hasArgument(4, TransparentFunctor),
                      hasArgument(5, TransparentFunctor))))
          .bind("Call"),
      this);
  // std::reduce with a policy.
  Finder->addMatcher(
      callExpr(

Review comment (bug):
Here is my rationale of introducing a hard-coded list:

- People use custom lambdas for intentional and safe type conversions. So we will see tricky cases like `[](int acc, float val) { return acc + (val > 0.5f ? 1 : 0); }`, IMO they would be hard to analyze in AST. (This is also an limitation of the original implementation, so fully supporting them should be a different PR.)
- If we ignore the problem and blindly match any callable, we would trigger *a lot of* False Positives, which could possibly make users to disable this check entirely. IMHO the current way is an acceptable trade-off, we limit the scope of bug detection to catch common bugs without degrading the check's reliability.

Code after:
        //     outside the iterator class.
        recordType(
            hasDeclaration(cxxRecordDecl(isSameOrDerivedFrom(has(functionDecl(
                hasOverloadedOperatorName("*"),
                returns(qualType(hasCanonicalType(anyOf(
                    // `value_type& operator*();`
                    references(BuiltinTypeWithId(ID)),
                    // `value_type operator*();`
                    BuiltinTypeWithId(ID),
                    // `auto operator*();`, `decltype(auto) operator*();`
                    autoType(hasDeducedType(BuiltinTypeWithId(ID)))
                    //
                    )))))))))));
  };

  const auto IteratorParam = parmVarDecl(
      hasType(hasCanonicalType(IteratorWithValueType("IterValueType"))));
  const auto Iterator2Param = parmVarDecl(
      hasType(hasCanonicalType(IteratorWithValueType("Iter2ValueType"))));
  const auto InitParam = parmVarDecl(hasType(BuiltinTypeWithId("InitType")));

  // Transparent standard functors that preserve arithmetic conversion
  // semantics.
  const auto TransparentFunctor = expr(hasType(
      hasCanonicalType(recordType(hasDeclaration(cxxRecordDecl(hasAnyName(
          "::std::plus", "::std::minus", "::std::multiplies", "::std::divides",
          "::std::bit_and", "::std::bit_or", "::std::bit_xor")))))));

  // std::accumulate, std::reduce.
  Finder->addMatcher(
      callExpr(
          callee(functionDecl(hasAnyName("::std::accumulate", "::std::reduce"),
                              hasParameter(0, IteratorParam),
                              hasParameter(2, InitParam))),
          anyOf(argumentCountIs(3),
                allOf(argumentCountIs(4), hasArgument(3, TransparentFunctor))))
          .bind("Call"),
      this);
  // std::inner_product.
  Finder->addMatcher(
      callExpr(
          callee(functionDecl(
              hasName("::std::inner_product"), hasParameter(0, IteratorParam),
              hasParameter(2, Iterator2Param), hasParameter(3, InitParam))),
          anyOf(argumentCountIs(4),
                allOf(argumentCountIs(6), hasArgument(4, TransparentFunctor),
                      hasArgument(5, TransparentFunctor))))
          .bind("Call"),
      this);
  // std::reduce with a policy.
  Finder->addMatcher(