//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MATCHERS_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MATCHERS_H

#include "TypeTraits.h"
#include "clang/AST/ExprConcepts.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include <optional>

namespace clang::tidy::matchers {

AST_MATCHER(BinaryOperator, isRelationalOperator) {
  return Node.isRelationalOp();
}

AST_MATCHER(BinaryOperator, isEqualityOperator) { return Node.isEqualityOp(); }

AST_MATCHER(QualType, isExpensiveToCopy) {
  std::optional<bool> IsExpensive =
      utils::type_traits::isExpensiveToCopy(Node, Finder->getASTContext());
  return IsExpensive && *IsExpensive;
}

AST_MATCHER(RecordDecl, isTriviallyDefaultConstructible) {
  return utils::type_traits::recordIsTriviallyDefaultConstructible(
      Node, Finder->getASTContext());
}

AST_MATCHER(QualType, isTriviallyDestructible) {
  return utils::type_traits::isTriviallyDestructible(Node);
}

// Returns QualType matcher for references to const.
AST_MATCHER_FUNCTION(ast_matchers::TypeMatcher, isReferenceToConst) {
  using namespace ast_matchers;
  return referenceType(pointee(qualType(isConstQualified())));
}

// Returns QualType matcher for pointers to const.
AST_MATCHER_FUNCTION(ast_matchers::TypeMatcher, isPointerToConst) {
  using namespace ast_matchers;
  return pointerType(pointee(qualType(isConstQualified())));
}

// Returns QualType matcher for target char type only.
AST_MATCHER(QualType, isSimpleChar) {
  const auto *ActualType = Node.getTypePtr();
  return ActualType &&
         (ActualType->isSpecificBuiltinType(BuiltinType::Char_S) ||
          ActualType->isSpecificBuiltinType(BuiltinType::Char_U));
}

AST_MATCHER(Expr, hasUnevaluatedContext) {
  if (isa<CXXNoexceptExpr>(Node) || isa<RequiresExpr>(Node))
    return true;
  if (const auto *UnaryExpr = dyn_cast<UnaryExprOrTypeTraitExpr>(&Node)) {
    switch (UnaryExpr->getKind()) {
    case UETT_SizeOf:
    case UETT_AlignOf:
      return true;
    default:
      return false;
    }
  }
  if (const auto *TypeIDExpr = dyn_cast<CXXTypeidExpr>(&Node))
    return !TypeIDExpr->isPotentiallyEvaluated();
  return false;
}

// A matcher implementation that matches a list of type name regular expressions
// against a NamedDecl. If a regular expression contains the substring "::"
// matching will occur against the qualified name, otherwise only the typename.
class MatchesAnyListedRegexNameMatcher
    : public ast_matchers::internal::MatcherInterface<NamedDecl> {
public:
  explicit MatchesAnyListedRegexNameMatcher(
      llvm::ArrayRef<StringRef> NameList) {
    std::transform(
        NameList.begin(), NameList.end(), std::back_inserter(NameMatchers),
        [](const llvm::StringRef Name) { return NameMatcher(Name); });
  }

  class NameMatcher {
    llvm::Regex Regex;
    enum class MatchMode {
      // Match against the unqualified name because the regular expression
      // does not contain ":".
      MatchUnqualified,
      // Match against the qualified name because the regular expression
      // contains ":" suggesting name and namespace should be matched.
      MatchQualified,
      // Match against the fully qualified name because the regular expression
      // starts with ":".
      MatchFullyQualified,
    };
    MatchMode Mode;

  public:
    NameMatcher(const llvm::StringRef Regex)
        : Regex(Regex), Mode(determineMatchMode(Regex)) {}

    bool match(const NamedDecl &ND) const {
      switch (Mode) {
      case MatchMode::MatchQualified:
        return Regex.match(ND.getQualifiedNameAsString());
      case MatchMode::MatchFullyQualified:
        return Regex.match("::" + ND.getQualifiedNameAsString());
      default:
        if (const IdentifierInfo *II = ND.getIdentifier())
          return Regex.match(II->getName());
        return false;
      }
    }

  private:
    MatchMode determineMatchMode(llvm::StringRef Regex) {
      if (Regex.starts_with(":") || Regex.starts_with("^:"))
        return MatchMode::MatchFullyQualified;
      return Regex.contains(":") ? MatchMode::MatchQualified
                                 : MatchMode::MatchUnqualified;
    }
  };

  bool matches(
      const NamedDecl &Node, ast_matchers::internal::ASTMatchFinder *Finder,
      ast_matchers::internal::BoundNodesTreeBuilder *Builder) const override {
    return llvm::any_of(NameMatchers, [&Node](const NameMatcher &NM) {
      return NM.match(Node);
    });
  }

private:
  std::vector<NameMatcher> NameMatchers;
};

// Returns a matcher that matches NamedDecl's against a list of provided regular
// expressions. If a regular expression contains starts ':' the NamedDecl's
// qualified name will be used for matching, otherwise its name will be used.
inline ::clang::ast_matchers::internal::Matcher<NamedDecl>
matchesAnyListedRegexName(llvm::ArrayRef<StringRef> NameList) {
  return ::clang::ast_matchers::internal::Matcher(
      new MatchesAnyListedRegexNameMatcher(NameList));
}

// Predicate that verify if statement is not identical to one bound to ID node.
struct NotIdenticalStatementsPredicate {
  bool
  operator()(const clang::ast_matchers::internal::BoundNodesMap &Nodes) const;

  std::string ID;
  ::clang::DynTypedNode Node;
  ASTContext *Context;
};

// Checks if statement is identical (utils::areStatementsIdentical) to one bound
// to ID node.
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID) {
  const NotIdenticalStatementsPredicate Predicate{
      ID, ::clang::DynTypedNode::create(Node), &(Finder->getASTContext())};
  return Builder->removeBindings(Predicate);
}

// A matcher implementation that matches a list of type name regular expressions
// against a QualType.
class MatchesAnyListedTypeNameMatcher
    : public ast_matchers::internal::MatcherInterface<QualType> {
public:
  explicit MatchesAnyListedTypeNameMatcher(llvm::ArrayRef<StringRef> NameList,
                                           bool CanonicalTypes);
  ~MatchesAnyListedTypeNameMatcher() override;
  bool matches(
      const QualType &Node, ast_matchers::internal::ASTMatchFinder *Finder,
      ast_matchers::internal::BoundNodesTreeBuilder *Builder) const override;

private:
  std::vector<llvm::Regex> NameMatchers;
  bool CanonicalTypes;
};

// Returns a matcher that matches QualType against a list of provided regular.
inline ::clang::ast_matchers::internal::Matcher<QualType>
matchesAnyListedTypeName(llvm::ArrayRef<StringRef> NameList,
                         bool CanonicalTypes) {
  return ::clang::ast_matchers::internal::Matcher(
      new MatchesAnyListedTypeNameMatcher(NameList, CanonicalTypes));
}
inline ::clang::ast_matchers::internal::Matcher<QualType>
matchesAnyListedTypeName(llvm::ArrayRef<StringRef> NameList) {
  return matchesAnyListedTypeName(NameList, true);
}

} // namespace clang::tidy::matchers

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MATCHERS_H
