//===- ExprConcepts.h - C++2a Concepts expressions --------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
/// \file
/// Defines Expressions and AST nodes for C++2a concepts.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_AST_EXPRCONCEPTS_H
#define LLVM_CLANG_AST_EXPRCONCEPTS_H

#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTConcept.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclarationName.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Expr.h"
#include "clang/AST/NestedNameSpecifier.h"
#include "clang/AST/TemplateBase.h"
#include "clang/AST/Type.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/TrailingObjects.h"
#include <utility>
#include <string>

namespace clang {
class ASTStmtReader;
class ASTStmtWriter;

/// \brief Represents the specialization of a concept - evaluates to a prvalue
/// of type bool.
///
/// According to C++2a [expr.prim.id]p3 an id-expression that denotes the
/// specialization of a concept results in a prvalue of type bool.
class ConceptSpecializationExpr final : public Expr, public ConceptReference {
  friend class ASTReader;
  friend class ASTStmtReader;

public:
  using SubstitutionDiagnostic = std::pair<SourceLocation, std::string>;

protected:
  /// \brief The Implicit Concept Specialization Decl, which holds the template
  /// arguments for this specialization.
  ImplicitConceptSpecializationDecl *SpecDecl;

  /// \brief Information about the satisfaction of the named concept with the
  /// given arguments. If this expression is value dependent, this is to be
  /// ignored.
  ASTConstraintSatisfaction *Satisfaction;

  ConceptSpecializationExpr(const ASTContext &C, NestedNameSpecifierLoc NNS,
                            SourceLocation TemplateKWLoc,
                            DeclarationNameInfo ConceptNameInfo,
                            NamedDecl *FoundDecl, ConceptDecl *NamedConcept,
                            const ASTTemplateArgumentListInfo *ArgsAsWritten,
                            ImplicitConceptSpecializationDecl *SpecDecl,
                            const ConstraintSatisfaction *Satisfaction);

  ConceptSpecializationExpr(const ASTContext &C, ConceptDecl *NamedConcept,
                            const ASTTemplateArgumentListInfo *ArgsAsWritten,
                            ImplicitConceptSpecializationDecl *SpecDecl,
                            const ConstraintSatisfaction *Satisfaction,
                            bool Dependent,
                            bool ContainsUnexpandedParameterPack);
  ConceptSpecializationExpr(EmptyShell Empty);

public:
  static ConceptSpecializationExpr *
  Create(const ASTContext &C, NestedNameSpecifierLoc NNS,
         SourceLocation TemplateKWLoc, DeclarationNameInfo ConceptNameInfo,
         NamedDecl *FoundDecl, ConceptDecl *NamedConcept,
         const ASTTemplateArgumentListInfo *ArgsAsWritten,
         ImplicitConceptSpecializationDecl *SpecDecl,
         const ConstraintSatisfaction *Satisfaction);

  static ConceptSpecializationExpr *
  Create(const ASTContext &C, ConceptDecl *NamedConcept,
         ImplicitConceptSpecializationDecl *SpecDecl,
         const ConstraintSatisfaction *Satisfaction, bool Dependent,
         bool ContainsUnexpandedParameterPack);

  static ConceptSpecializationExpr *
  Create(const ASTContext &C, ConceptDecl *NamedConcept,
         const ASTTemplateArgumentListInfo *ArgsAsWritten,
         ImplicitConceptSpecializationDecl *SpecDecl,
         const ConstraintSatisfaction *Satisfaction, bool Dependent,
         bool ContainsUnexpandedParameterPack);

  ArrayRef<TemplateArgument> getTemplateArguments() const {
    return SpecDecl->getTemplateArguments();
  }

  const ImplicitConceptSpecializationDecl *getSpecializationDecl() const {
    assert(SpecDecl && "Template Argument Decl not initialized");
    return SpecDecl;
  }

  /// \brief Whether or not the concept with the given arguments was satisfied
  /// when the expression was created.
  /// The expression must not be dependent.
  bool isSatisfied() const {
    assert(!isValueDependent() &&
           "isSatisfied called on a dependent ConceptSpecializationExpr");
    return Satisfaction->IsSatisfied;
  }

  /// \brief Get elaborated satisfaction info about the template arguments'
  /// satisfaction of the named concept.
  /// The expression must not be dependent.
  const ASTConstraintSatisfaction &getSatisfaction() const {
    assert(!isValueDependent() &&
           "getSatisfaction called on dependent ConceptSpecializationExpr");
    return *Satisfaction;
  }

  static bool classof(const Stmt *T) {
    return T->getStmtClass() == ConceptSpecializationExprClass;
  }

  SourceLocation getBeginLoc() const LLVM_READONLY {
    if (auto QualifierLoc = getNestedNameSpecifierLoc())
      return QualifierLoc.getBeginLoc();
    return ConceptName.getBeginLoc();
  }

  SourceLocation getEndLoc() const LLVM_READONLY {
    // If the ConceptSpecializationExpr is the ImmediatelyDeclaredConstraint
    // of a TypeConstraint written syntactically as a constrained-parameter,
    // there may not be a template argument list.
    return ArgsAsWritten->RAngleLoc.isValid() ? ArgsAsWritten->RAngleLoc
                                              : ConceptName.getEndLoc();
  }

  // Iterators
  child_range children() {
    return child_range(child_iterator(), child_iterator());
  }
  const_child_range children() const {
    return const_child_range(const_child_iterator(), const_child_iterator());
  }
};

namespace concepts {

/// \brief A static requirement that can be used in a requires-expression to
/// check properties of types and expression.
class Requirement {
public:
  // Note - simple and compound requirements are both represented by the same
  // class (ExprRequirement).
  enum RequirementKind { RK_Type, RK_Simple, RK_Compound, RK_Nested };
private:
  const RequirementKind Kind;
  // FIXME: use RequirementDependence to model dependence?
  bool Dependent : 1;
  bool ContainsUnexpandedParameterPack : 1;
  bool Satisfied : 1;
public:
  struct SubstitutionDiagnostic {
    StringRef SubstitutedEntity;
    // FIXME: Store diagnostics semantically and not as prerendered strings.
    //  Fixing this probably requires serialization of PartialDiagnostic
    //  objects.
    SourceLocation DiagLoc;
    StringRef DiagMessage;
  };

  Requirement(RequirementKind Kind, bool IsDependent,
              bool ContainsUnexpandedParameterPack, bool IsSatisfied = true) :
      Kind(Kind), Dependent(IsDependent),
      ContainsUnexpandedParameterPack(ContainsUnexpandedParameterPack),
      Satisfied(IsSatisfied) {}

  RequirementKind getKind() const { return Kind; }

  bool isSatisfied() const {
    assert(!Dependent &&
           "isSatisfied can only be called on non-dependent requirements.");
    return Satisfied;
  }

  void setSatisfied(bool IsSatisfied) {
    assert(!Dependent &&
           "setSatisfied can only be called on non-dependent requirements.");
    Satisfied = IsSatisfied;
  }

  void setDependent(bool IsDependent) { Dependent = IsDependent; }
  bool isDependent() const { return Dependent; }

  void setContainsUnexpandedParameterPack(bool Contains) {
    ContainsUnexpandedParameterPack = Contains;
  }
  bool containsUnexpandedParameterPack() const {
    return ContainsUnexpandedParameterPack;
  }
};

/// \brief A requires-expression requirement which queries the existence of a
/// type name or type template specialization ('type' requirements).
class TypeRequirement : public Requirement {
public:
  enum SatisfactionStatus {
      SS_Dependent,
      SS_SubstitutionFailure,
      SS_Satisfied
  };
private:
  llvm::PointerUnion<SubstitutionDiagnostic *, TypeSourceInfo *> Value;
  SatisfactionStatus Status;
public:
  friend ASTStmtReader;
  friend ASTStmtWriter;

  /// \brief Construct a type requirement from a type. If the given type is not
  /// dependent, this indicates that the type exists and the requirement will be
  /// satisfied. Otherwise, the SubstitutionDiagnostic constructor is to be
  /// used.
  TypeRequirement(TypeSourceInfo *T);

  /// \brief Construct a type requirement when the nested name specifier is
  /// invalid due to a bad substitution. The requirement is unsatisfied.
  TypeRequirement(SubstitutionDiagnostic *Diagnostic) :
      Requirement(RK_Type, false, false, false), Value(Diagnostic),
      Status(SS_SubstitutionFailure) {}

  SatisfactionStatus getSatisfactionStatus() const { return Status; }
  void setSatisfactionStatus(SatisfactionStatus Status) {
    this->Status = Status;
  }

  bool isSubstitutionFailure() const {
    return Status == SS_SubstitutionFailure;
  }

  SubstitutionDiagnostic *getSubstitutionDiagnostic() const {
    assert(Status == SS_SubstitutionFailure &&
           "Attempted to get substitution diagnostic when there has been no "
           "substitution failure.");
    return Value.get<SubstitutionDiagnostic *>();
  }

  TypeSourceInfo *getType() const {
    assert(!isSubstitutionFailure() &&
           "Attempted to get type when there has been a substitution failure.");
    return Value.get<TypeSourceInfo *>();
  }

  static bool classof(const Requirement *R) {
    return R->getKind() == RK_Type;
  }
};

/// \brief A requires-expression requirement which queries the validity and
/// properties of an expression ('simple' and 'compound' requirements).
class ExprRequirement : public Requirement {
public:
  enum SatisfactionStatus {
      SS_Dependent,
      SS_ExprSubstitutionFailure,
      SS_NoexceptNotMet,
      SS_TypeRequirementSubstitutionFailure,
      SS_ConstraintsNotSatisfied,
      SS_Satisfied
  };
  class ReturnTypeRequirement {
      llvm::PointerIntPair<
          llvm::PointerUnion<TemplateParameterList *, SubstitutionDiagnostic *>,
          1, bool>
          TypeConstraintInfo;
  public:
      friend ASTStmtReader;
      friend ASTStmtWriter;

      /// \brief No return type requirement was specified.
      ReturnTypeRequirement() : TypeConstraintInfo(nullptr, false) {}

      /// \brief A return type requirement was specified but it was a
      /// substitution failure.
      ReturnTypeRequirement(SubstitutionDiagnostic *SubstDiag) :
          TypeConstraintInfo(SubstDiag, false) {}

      /// \brief A 'type constraint' style return type requirement.
      /// \param TPL an invented template parameter list containing a single
      /// type parameter with a type-constraint.
      // TODO: Can we maybe not save the whole template parameter list and just
      //  the type constraint? Saving the whole TPL makes it easier to handle in
      //  serialization but is less elegant.
      ReturnTypeRequirement(TemplateParameterList *TPL);

      bool isDependent() const {
        return TypeConstraintInfo.getInt();
      }

      bool containsUnexpandedParameterPack() const {
        if (!isTypeConstraint())
          return false;
        return getTypeConstraintTemplateParameterList()
                ->containsUnexpandedParameterPack();
      }

      bool isEmpty() const {
        return TypeConstraintInfo.getPointer().isNull();
      }

      bool isSubstitutionFailure() const {
        return !isEmpty() &&
            TypeConstraintInfo.getPointer().is<SubstitutionDiagnostic *>();
      }

      bool isTypeConstraint() const {
        return !isEmpty() &&
            TypeConstraintInfo.getPointer().is<TemplateParameterList *>();
      }

      SubstitutionDiagnostic *getSubstitutionDiagnostic() const {
        assert(isSubstitutionFailure());
        return TypeConstraintInfo.getPointer().get<SubstitutionDiagnostic *>();
      }

      const TypeConstraint *getTypeConstraint() const;

      TemplateParameterList *getTypeConstraintTemplateParameterList() const {
        assert(isTypeConstraint());
        return TypeConstraintInfo.getPointer().get<TemplateParameterList *>();
      }
  };
private:
  llvm::PointerUnion<Expr *, SubstitutionDiagnostic *> Value;
  SourceLocation NoexceptLoc; // May be empty if noexcept wasn't specified.
  ReturnTypeRequirement TypeReq;
  ConceptSpecializationExpr *SubstitutedConstraintExpr;
  SatisfactionStatus Status;
public:
  friend ASTStmtReader;
  friend ASTStmtWriter;

  /// \brief Construct a compound requirement.
  /// \param E the expression which is checked by this requirement.
  /// \param IsSimple whether this was a simple requirement in source.
  /// \param NoexceptLoc the location of the noexcept keyword, if it was
  /// specified, otherwise an empty location.
  /// \param Req the requirement for the type of the checked expression.
  /// \param Status the satisfaction status of this requirement.
  ExprRequirement(
      Expr *E, bool IsSimple, SourceLocation NoexceptLoc,
      ReturnTypeRequirement Req, SatisfactionStatus Status,
      ConceptSpecializationExpr *SubstitutedConstraintExpr = nullptr);

  /// \brief Construct a compound requirement whose expression was a
  /// substitution failure. The requirement is not satisfied.
  /// \param E the diagnostic emitted while instantiating the original
  /// expression.
  /// \param IsSimple whether this was a simple requirement in source.
  /// \param NoexceptLoc the location of the noexcept keyword, if it was
  /// specified, otherwise an empty location.
  /// \param Req the requirement for the type of the checked expression (omit
  /// if no requirement was specified).
  ExprRequirement(SubstitutionDiagnostic *E, bool IsSimple,
                  SourceLocation NoexceptLoc, ReturnTypeRequirement Req = {});

  bool isSimple() const { return getKind() == RK_Simple; }
  bool isCompound() const { return getKind() == RK_Compound; }

  bool hasNoexceptRequirement() const { return NoexceptLoc.isValid(); }
  SourceLocation getNoexceptLoc() const { return NoexceptLoc; }

  SatisfactionStatus getSatisfactionStatus() const { return Status; }

  bool isExprSubstitutionFailure() const {
    return Status == SS_ExprSubstitutionFailure;
  }

  const ReturnTypeRequirement &getReturnTypeRequirement() const {
    return TypeReq;
  }

  ConceptSpecializationExpr *
  getReturnTypeRequirementSubstitutedConstraintExpr() const {
    assert(Status >= SS_TypeRequirementSubstitutionFailure);
    return SubstitutedConstraintExpr;
  }

  SubstitutionDiagnostic *getExprSubstitutionDiagnostic() const {
    assert(isExprSubstitutionFailure() &&
           "Attempted to get expression substitution diagnostic when there has "
           "been no expression substitution failure");
    return Value.get<SubstitutionDiagnostic *>();
  }

  Expr *getExpr() const {
    assert(!isExprSubstitutionFailure() &&
           "ExprRequirement has no expression because there has been a "
           "substitution failure.");
    return Value.get<Expr *>();
  }

  static bool classof(const Requirement *R) {
    return R->getKind() == RK_Compound || R->getKind() == RK_Simple;
  }
};

/// \brief A requires-expression requirement which is satisfied when a general
/// constraint expression is satisfied ('nested' requirements).
class NestedRequirement : public Requirement {
  Expr *Constraint = nullptr;
  const ASTConstraintSatisfaction *Satisfaction = nullptr;
  bool HasInvalidConstraint = false;
  StringRef InvalidConstraintEntity;

public:
  friend ASTStmtReader;
  friend ASTStmtWriter;

  NestedRequirement(Expr *Constraint)
      : Requirement(RK_Nested, /*IsDependent=*/true,
                    Constraint->containsUnexpandedParameterPack()),
        Constraint(Constraint) {
    assert(Constraint->isInstantiationDependent() &&
           "Nested requirement with non-dependent constraint must be "
           "constructed with a ConstraintSatisfaction object");
  }

  NestedRequirement(ASTContext &C, Expr *Constraint,
                    const ConstraintSatisfaction &Satisfaction)
      : Requirement(RK_Nested, Constraint->isInstantiationDependent(),
                    Constraint->containsUnexpandedParameterPack(),
                    Satisfaction.IsSatisfied),
        Constraint(Constraint),
        Satisfaction(ASTConstraintSatisfaction::Create(C, Satisfaction)) {}

  NestedRequirement(StringRef InvalidConstraintEntity,
                    const ASTConstraintSatisfaction *Satisfaction)
      : Requirement(RK_Nested,
                    /*IsDependent=*/false,
                    /*ContainsUnexpandedParameterPack*/ false,
                    Satisfaction->IsSatisfied),
        Satisfaction(Satisfaction), HasInvalidConstraint(true),
        InvalidConstraintEntity(InvalidConstraintEntity) {}

  NestedRequirement(ASTContext &C, StringRef InvalidConstraintEntity,
                    const ConstraintSatisfaction &Satisfaction)
      : NestedRequirement(InvalidConstraintEntity,
                          ASTConstraintSatisfaction::Create(C, Satisfaction)) {}

  bool hasInvalidConstraint() const { return HasInvalidConstraint; }

  StringRef getInvalidConstraintEntity() {
    assert(hasInvalidConstraint());
    return InvalidConstraintEntity;
  }

  Expr *getConstraintExpr() const {
    assert(!hasInvalidConstraint() &&
           "getConstraintExpr() may not be called "
           "on nested requirements with invalid constraint.");
    return Constraint;
  }

  const ASTConstraintSatisfaction &getConstraintSatisfaction() const {
    return *Satisfaction;
  }

  static bool classof(const Requirement *R) {
    return R->getKind() == RK_Nested;
  }
};

} // namespace concepts

/// C++2a [expr.prim.req]:
///     A requires-expression provides a concise way to express requirements on
///     template arguments. A requirement is one that can be checked by name
///     lookup (6.4) or by checking properties of types and expressions.
///     [...]
///     A requires-expression is a prvalue of type bool [...]
class RequiresExpr final : public Expr,
    llvm::TrailingObjects<RequiresExpr, ParmVarDecl *,
                          concepts::Requirement *> {
  friend TrailingObjects;
  friend class ASTStmtReader;

  unsigned NumLocalParameters;
  unsigned NumRequirements;
  RequiresExprBodyDecl *Body;
  SourceLocation RBraceLoc;

  unsigned numTrailingObjects(OverloadToken<ParmVarDecl *>) const {
    return NumLocalParameters;
  }

  unsigned numTrailingObjects(OverloadToken<concepts::Requirement *>) const {
    return NumRequirements;
  }

  RequiresExpr(ASTContext &C, SourceLocation RequiresKWLoc,
               RequiresExprBodyDecl *Body,
               ArrayRef<ParmVarDecl *> LocalParameters,
               ArrayRef<concepts::Requirement *> Requirements,
               SourceLocation RBraceLoc);
  RequiresExpr(ASTContext &C, EmptyShell Empty, unsigned NumLocalParameters,
               unsigned NumRequirements);

public:
  static RequiresExpr *
  Create(ASTContext &C, SourceLocation RequiresKWLoc,
         RequiresExprBodyDecl *Body, ArrayRef<ParmVarDecl *> LocalParameters,
         ArrayRef<concepts::Requirement *> Requirements,
         SourceLocation RBraceLoc);
  static RequiresExpr *
  Create(ASTContext &C, EmptyShell Empty, unsigned NumLocalParameters,
         unsigned NumRequirements);

  ArrayRef<ParmVarDecl *> getLocalParameters() const {
    return {getTrailingObjects<ParmVarDecl *>(), NumLocalParameters};
  }

  RequiresExprBodyDecl *getBody() const { return Body; }

  ArrayRef<concepts::Requirement *> getRequirements() const {
    return {getTrailingObjects<concepts::Requirement *>(), NumRequirements};
  }

  /// \brief Whether or not the requires clause is satisfied.
  /// The expression must not be dependent.
  bool isSatisfied() const {
    assert(!isValueDependent()
           && "isSatisfied called on a dependent RequiresExpr");
    return RequiresExprBits.IsSatisfied;
  }

  void setSatisfied(bool IsSatisfied) {
    assert(!isValueDependent() &&
           "setSatisfied called on a dependent RequiresExpr");
    RequiresExprBits.IsSatisfied = IsSatisfied;
  }

  SourceLocation getRequiresKWLoc() const {
    return RequiresExprBits.RequiresKWLoc;
  }

  SourceLocation getRBraceLoc() const { return RBraceLoc; }

  static bool classof(const Stmt *T) {
    return T->getStmtClass() == RequiresExprClass;
  }

  SourceLocation getBeginLoc() const LLVM_READONLY {
    return RequiresExprBits.RequiresKWLoc;
  }
  SourceLocation getEndLoc() const LLVM_READONLY {
    return RBraceLoc;
  }

  // Iterators
  child_range children() {
    return child_range(child_iterator(), child_iterator());
  }
  const_child_range children() const {
    return const_child_range(const_child_iterator(), const_child_iterator());
  }
};

} // namespace clang

#endif // LLVM_CLANG_AST_EXPRCONCEPTS_H
