//===--- HLSLBuiltinTypeDeclBuilder.cpp - HLSL Builtin Type Decl Builder --===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Helper classes for creating HLSL builtin class types. Used by external HLSL
// sema source.
//
//===----------------------------------------------------------------------===//

#include "HLSLBuiltinTypeDeclBuilder.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/Type.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Sema/Lookup.h"
#include "clang/Sema/Sema.h"
#include "clang/Sema/SemaHLSL.h"
#include "llvm/ADT/SmallVector.h"

using namespace llvm::hlsl;

namespace clang {

namespace hlsl {

namespace {

static FunctionDecl *lookupBuiltinFunction(Sema &S, StringRef Name) {
  IdentifierInfo &II =
      S.getASTContext().Idents.get(Name, tok::TokenKind::identifier);
  DeclarationNameInfo NameInfo =
      DeclarationNameInfo(DeclarationName(&II), SourceLocation());
  LookupResult R(S, NameInfo, Sema::LookupOrdinaryName);
  // AllowBuiltinCreation is false but LookupDirect will create
  // the builtin when searching the global scope anyways...
  S.LookupName(R, S.getCurScope());
  // FIXME: If the builtin function was user-declared in global scope,
  // this assert *will* fail. Should this call LookupBuiltin instead?
  assert(R.isSingleResult() &&
         "Since this is a builtin it should always resolve!");
  return cast<FunctionDecl>(R.getFoundDecl());
}
} // namespace

// Builder for template arguments of builtin types. Used internally
// by BuiltinTypeDeclBuilder.
struct TemplateParameterListBuilder {
  BuiltinTypeDeclBuilder &Builder;
  llvm::SmallVector<NamedDecl *> Params;

  TemplateParameterListBuilder(BuiltinTypeDeclBuilder &RB) : Builder(RB) {}
  ~TemplateParameterListBuilder();

  TemplateParameterListBuilder &
  addTypeParameter(StringRef Name, QualType DefaultValue = QualType());

  ConceptSpecializationExpr *
  constructConceptSpecializationExpr(Sema &S, ConceptDecl *CD);

  BuiltinTypeDeclBuilder &finalizeTemplateArgs(ConceptDecl *CD = nullptr);
};

// Builder for methods or constructors of builtin types. Allows creating methods
// or constructors of builtin types using the builder pattern like this:
//
//   BuiltinTypeMethodBuilder(RecordBuilder, "MethodName", ReturnType)
//       .addParam("param_name", Type, InOutModifier)
//       .callBuiltin("builtin_name", BuiltinParams...)
//       .finalize();
//
// The builder needs to have all of the parameters before it can create
// a CXXMethodDecl or CXXConstructorDecl. It collects them in addParam calls and
// when a first method that builds the body is called or when access to 'this`
// is needed it creates the CXXMethodDecl/CXXConstructorDecl and ParmVarDecls
// instances. These can then be referenced from the body building methods.
// Destructor or an explicit call to finalize() will complete the method
// definition.
//
// The callBuiltin helper method accepts constants via `Expr *` or placeholder
// value arguments to indicate which function arguments to forward to the
// builtin.
//
// If the method that is being built has a non-void return type the
// finalize() will create a return statement with the value of the last
// statement (unless the last statement is already a ReturnStmt or the return
// value is void).
struct BuiltinTypeMethodBuilder {
private:
  struct Param {
    const IdentifierInfo &NameII;
    QualType Ty;
    HLSLParamModifierAttr::Spelling Modifier;
    Param(const IdentifierInfo &NameII, QualType Ty,
          HLSLParamModifierAttr::Spelling Modifier)
        : NameII(NameII), Ty(Ty), Modifier(Modifier) {}
  };

  BuiltinTypeDeclBuilder &DeclBuilder;
  DeclarationName Name;
  QualType ReturnTy;
  // method or constructor declaration
  // (CXXConstructorDecl derives from CXXMethodDecl)
  CXXMethodDecl *Method;
  bool IsConst;
  bool IsCtor;
  llvm::SmallVector<Param> Params;
  llvm::SmallVector<Stmt *> StmtsList;

  // Argument placeholders, inspired by std::placeholder. These are the indices
  // of arguments to forward to `callBuiltin` and other method builder methods.
  // Additional special values are:
  //   Handle   - refers to the resource handle.
  //   LastStmt - refers to the last statement in the method body; referencing
  //              LastStmt will remove the statement from the method body since
  //              it will be linked from the new expression being constructed.
  enum class PlaceHolder { _0, _1, _2, _3, Handle = 128, LastStmt };

  Expr *convertPlaceholder(PlaceHolder PH);
  Expr *convertPlaceholder(Expr *E) { return E; }

public:
  friend BuiltinTypeDeclBuilder;

  BuiltinTypeMethodBuilder(BuiltinTypeDeclBuilder &DB, DeclarationName &Name,
                           QualType ReturnTy, bool IsConst = false,
                           bool IsCtor = false)
      : DeclBuilder(DB), Name(Name), ReturnTy(ReturnTy), Method(nullptr),
        IsConst(IsConst), IsCtor(IsCtor) {}

  BuiltinTypeMethodBuilder(BuiltinTypeDeclBuilder &DB, StringRef NameStr,
                           QualType ReturnTy, bool IsConst = false,
                           bool IsCtor = false);
  BuiltinTypeMethodBuilder(const BuiltinTypeMethodBuilder &Other) = delete;

  ~BuiltinTypeMethodBuilder() { finalize(); }

  BuiltinTypeMethodBuilder &
  operator=(const BuiltinTypeMethodBuilder &Other) = delete;

  BuiltinTypeMethodBuilder &addParam(StringRef Name, QualType Ty,
                                     HLSLParamModifierAttr::Spelling Modifier =
                                         HLSLParamModifierAttr::Keyword_in);
  template <typename... Ts>
  BuiltinTypeMethodBuilder &callBuiltin(StringRef BuiltinName,
                                        QualType ReturnType, Ts... ArgSpecs);
  template <typename TLHS, typename TRHS>
  BuiltinTypeMethodBuilder &assign(TLHS LHS, TRHS RHS);
  template <typename T> BuiltinTypeMethodBuilder &dereference(T Ptr);
  BuiltinTypeDeclBuilder &finalize();
  Expr *getResourceHandleExpr();

private:
  void createDecl();

  // Makes sure the declaration is created; should be called before any
  // statement added to the body or when access to 'this' is needed.
  void ensureCompleteDecl() {
    if (!Method)
      createDecl();
  }
};

TemplateParameterListBuilder::~TemplateParameterListBuilder() {
  finalizeTemplateArgs();
}

TemplateParameterListBuilder &
TemplateParameterListBuilder::addTypeParameter(StringRef Name,
                                               QualType DefaultValue) {
  assert(!Builder.Record->isCompleteDefinition() &&
         "record is already complete");
  ASTContext &AST = Builder.SemaRef.getASTContext();
  unsigned Position = static_cast<unsigned>(Params.size());
  auto *Decl = TemplateTypeParmDecl::Create(
      AST, Builder.Record->getDeclContext(), SourceLocation(), SourceLocation(),
      /* TemplateDepth */ 0, Position,
      &AST.Idents.get(Name, tok::TokenKind::identifier),
      /* Typename */ true,
      /* ParameterPack */ false,
      /* HasTypeConstraint*/ false);
  if (!DefaultValue.isNull())
    Decl->setDefaultArgument(AST,
                             Builder.SemaRef.getTrivialTemplateArgumentLoc(
                                 DefaultValue, QualType(), SourceLocation()));

  Params.emplace_back(Decl);
  return *this;
}

// The concept specialization expression (CSE) constructed in
// constructConceptSpecializationExpr is constructed so that it
// matches the CSE that is constructed when parsing the below C++ code:
//
// template<typename T>
// concept is_typed_resource_element_compatible =
// __builtin_hlsl_typed_resource_element_compatible<T>
//
// template<typename element_type> requires
// is_typed_resource_element_compatible<element_type>
// struct RWBuffer {
//     element_type Val;
// };
//
// int fn() {
//     RWBuffer<int> Buf;
// }
//
// When dumping the AST and filtering for "RWBuffer", the resulting AST
// structure is what we're trying to construct below, specifically the
// CSE portion.
ConceptSpecializationExpr *
TemplateParameterListBuilder::constructConceptSpecializationExpr(
    Sema &S, ConceptDecl *CD) {
  ASTContext &Context = S.getASTContext();
  SourceLocation Loc = Builder.Record->getBeginLoc();
  DeclarationNameInfo DNI(CD->getDeclName(), Loc);
  NestedNameSpecifierLoc NNSLoc;
  DeclContext *DC = Builder.Record->getDeclContext();
  TemplateArgumentListInfo TALI(Loc, Loc);

  // Assume that the concept decl has just one template parameter
  // This parameter should have been added when CD was constructed
  // in getTypedBufferConceptDecl
  assert(CD->getTemplateParameters()->size() == 1 &&
         "unexpected concept decl parameter count");
  TemplateTypeParmDecl *ConceptTTPD =
      dyn_cast<TemplateTypeParmDecl>(CD->getTemplateParameters()->getParam(0));

  // this TemplateTypeParmDecl is the template for the resource, and is
  // used to construct a template argumentthat will be used
  // to construct the ImplicitConceptSpecializationDecl
  TemplateTypeParmDecl *T = TemplateTypeParmDecl::Create(
      Context,                          // AST context
      Builder.Record->getDeclContext(), // DeclContext
      SourceLocation(), SourceLocation(),
      /*D=*/0,                    // Depth in the template parameter list
      /*P=*/0,                    // Position in the template parameter list
      /*Id=*/nullptr,             // Identifier for 'T'
      /*Typename=*/true,          // Indicates this is a 'typename' or 'class'
      /*ParameterPack=*/false,    // Not a parameter pack
      /*HasTypeConstraint=*/false // Has no type constraint
  );

  T->setDeclContext(DC);

  QualType ConceptTType = Context.getTypeDeclType(ConceptTTPD);

  // this is the 2nd template argument node, on which
  // the concept constraint is actually being applied: 'element_type'
  TemplateArgument ConceptTA = TemplateArgument(ConceptTType);

  QualType CSETType = Context.getTypeDeclType(T);

  // this is the 1st template argument node, which represents
  // the abstract type that a concept would refer to: 'T'
  TemplateArgument CSETA = TemplateArgument(CSETType);

  ImplicitConceptSpecializationDecl *ImplicitCSEDecl =
      ImplicitConceptSpecializationDecl::Create(
          Context, Builder.Record->getDeclContext(), Loc, {CSETA});

  // Constraint satisfaction is used to construct the
  // ConceptSpecailizationExpr, and represents the 2nd Template Argument,
  // located at the bottom of the sample AST above.
  const ConstraintSatisfaction CS(CD, {ConceptTA});
  TemplateArgumentLoc TAL =
      S.getTrivialTemplateArgumentLoc(ConceptTA, QualType(), SourceLocation());

  TALI.addArgument(TAL);
  const ASTTemplateArgumentListInfo *ATALI =
      ASTTemplateArgumentListInfo::Create(Context, TALI);

  // In the concept reference, ATALI is what adds the extra
  // TemplateArgument node underneath CSE
  ConceptReference *CR =
      ConceptReference::Create(Context, NNSLoc, Loc, DNI, CD, CD, ATALI);

  ConceptSpecializationExpr *CSE =
      ConceptSpecializationExpr::Create(Context, CR, ImplicitCSEDecl, &CS);

  return CSE;
}

BuiltinTypeDeclBuilder &
TemplateParameterListBuilder::finalizeTemplateArgs(ConceptDecl *CD) {
  if (Params.empty())
    return Builder;

  ASTContext &AST = Builder.SemaRef.Context;
  ConceptSpecializationExpr *CSE =
      CD ? constructConceptSpecializationExpr(Builder.SemaRef, CD) : nullptr;
  auto *ParamList = TemplateParameterList::Create(
      AST, SourceLocation(), SourceLocation(), Params, SourceLocation(), CSE);
  Builder.Template = ClassTemplateDecl::Create(
      AST, Builder.Record->getDeclContext(), SourceLocation(),
      DeclarationName(Builder.Record->getIdentifier()), ParamList,
      Builder.Record);

  Builder.Record->setDescribedClassTemplate(Builder.Template);
  Builder.Template->setImplicit(true);
  Builder.Template->setLexicalDeclContext(Builder.Record->getDeclContext());

  // NOTE: setPreviousDecl before addDecl so new decl replace old decl when
  // make visible.
  Builder.Template->setPreviousDecl(Builder.PrevTemplate);
  Builder.Record->getDeclContext()->addDecl(Builder.Template);
  Params.clear();

  QualType T = Builder.Template->getInjectedClassNameSpecialization();
  T = AST.getInjectedClassNameType(Builder.Record, T);

  return Builder;
}

Expr *BuiltinTypeMethodBuilder::convertPlaceholder(PlaceHolder PH) {
  if (PH == PlaceHolder::Handle)
    return getResourceHandleExpr();

  if (PH == PlaceHolder::LastStmt) {
    assert(!StmtsList.empty() && "no statements in the list");
    Stmt *LastStmt = StmtsList.pop_back_val();
    assert(isa<ValueStmt>(LastStmt) && "last statement does not have a value");
    return cast<ValueStmt>(LastStmt)->getExprStmt();
  }

  ASTContext &AST = DeclBuilder.SemaRef.getASTContext();
  ParmVarDecl *ParamDecl = Method->getParamDecl(static_cast<unsigned>(PH));
  return DeclRefExpr::Create(
      AST, NestedNameSpecifierLoc(), SourceLocation(), ParamDecl, false,
      DeclarationNameInfo(ParamDecl->getDeclName(), SourceLocation()),
      ParamDecl->getType(), VK_PRValue);
}

BuiltinTypeMethodBuilder::BuiltinTypeMethodBuilder(BuiltinTypeDeclBuilder &DB,
                                                   StringRef NameStr,
                                                   QualType ReturnTy,
                                                   bool IsConst, bool IsCtor)
    : DeclBuilder(DB), ReturnTy(ReturnTy), Method(nullptr), IsConst(IsConst),
      IsCtor(IsCtor) {

  assert((!NameStr.empty() || IsCtor) && "method needs a name");
  assert(((IsCtor && !IsConst) || !IsCtor) && "constructor cannot be const");

  ASTContext &AST = DB.SemaRef.getASTContext();
  if (IsCtor) {
    Name = AST.DeclarationNames.getCXXConstructorName(
        DB.Record->getTypeForDecl()->getCanonicalTypeUnqualified());
  } else {
    const IdentifierInfo &II =
        AST.Idents.get(NameStr, tok::TokenKind::identifier);
    Name = DeclarationName(&II);
  }
}

BuiltinTypeMethodBuilder &
BuiltinTypeMethodBuilder::addParam(StringRef Name, QualType Ty,
                                   HLSLParamModifierAttr::Spelling Modifier) {
  assert(Method == nullptr && "Cannot add param, method already created");
  const IdentifierInfo &II = DeclBuilder.SemaRef.getASTContext().Idents.get(
      Name, tok::TokenKind::identifier);
  Params.emplace_back(II, Ty, Modifier);
  return *this;
}

void BuiltinTypeMethodBuilder::createDecl() {
  assert(Method == nullptr && "Method or constructor is already created");

  // create method or constructor type
  ASTContext &AST = DeclBuilder.SemaRef.getASTContext();
  SmallVector<QualType> ParamTypes;
  for (Param &MP : Params)
    ParamTypes.emplace_back(MP.Ty);

  FunctionProtoType::ExtProtoInfo ExtInfo;
  if (IsConst)
    ExtInfo.TypeQuals.addConst();

  QualType FuncTy = AST.getFunctionType(ReturnTy, ParamTypes, ExtInfo);

  // create method or constructor decl
  auto *TSInfo = AST.getTrivialTypeSourceInfo(FuncTy, SourceLocation());
  DeclarationNameInfo NameInfo = DeclarationNameInfo(Name, SourceLocation());
  if (IsCtor)
    Method = CXXConstructorDecl::Create(
        AST, DeclBuilder.Record, SourceLocation(), NameInfo, FuncTy, TSInfo,
        ExplicitSpecifier(), false, true, false,
        ConstexprSpecKind::Unspecified);
  else
    Method =
        CXXMethodDecl::Create(AST, DeclBuilder.Record, SourceLocation(),
                              NameInfo, FuncTy, TSInfo, SC_None, false, false,
                              ConstexprSpecKind::Unspecified, SourceLocation());

  // create params & set them to the function prototype
  SmallVector<ParmVarDecl *> ParmDecls;
  auto FnProtoLoc =
      Method->getTypeSourceInfo()->getTypeLoc().getAs<FunctionProtoTypeLoc>();
  for (int I = 0, E = Params.size(); I != E; I++) {
    Param &MP = Params[I];
    ParmVarDecl *Parm = ParmVarDecl::Create(
        AST, Method->getDeclContext(), SourceLocation(), SourceLocation(),
        &MP.NameII, MP.Ty,
        AST.getTrivialTypeSourceInfo(MP.Ty, SourceLocation()), SC_None,
        nullptr);
    if (MP.Modifier != HLSLParamModifierAttr::Keyword_in) {
      auto *Mod =
          HLSLParamModifierAttr::Create(AST, SourceRange(), MP.Modifier);
      Parm->addAttr(Mod);
    }
    ParmDecls.push_back(Parm);
    FnProtoLoc.setParam(I, Parm);
  }
  Method->setParams({ParmDecls});
}

Expr *BuiltinTypeMethodBuilder::getResourceHandleExpr() {
  ensureCompleteDecl();

  ASTContext &AST = DeclBuilder.SemaRef.getASTContext();
  CXXThisExpr *This = CXXThisExpr::Create(
      AST, SourceLocation(), Method->getFunctionObjectParameterType(), true);
  FieldDecl *HandleField = DeclBuilder.getResourceHandleField();
  return MemberExpr::CreateImplicit(AST, This, false, HandleField,
                                    HandleField->getType(), VK_LValue,
                                    OK_Ordinary);
}

template <typename... Ts>
BuiltinTypeMethodBuilder &
BuiltinTypeMethodBuilder::callBuiltin(StringRef BuiltinName,
                                      QualType ReturnType, Ts... ArgSpecs) {
  std::array<Expr *, sizeof...(ArgSpecs)> Args{
      convertPlaceholder(std::forward<Ts>(ArgSpecs))...};

  ensureCompleteDecl();

  ASTContext &AST = DeclBuilder.SemaRef.getASTContext();
  FunctionDecl *FD = lookupBuiltinFunction(DeclBuilder.SemaRef, BuiltinName);
  DeclRefExpr *DRE = DeclRefExpr::Create(
      AST, NestedNameSpecifierLoc(), SourceLocation(), FD, false,
      FD->getNameInfo(), AST.BuiltinFnTy, VK_PRValue);

  if (ReturnType.isNull())
    ReturnType = FD->getReturnType();

  Expr *Call = CallExpr::Create(AST, DRE, Args, ReturnType, VK_PRValue,
                                SourceLocation(), FPOptionsOverride());
  StmtsList.push_back(Call);
  return *this;
}

template <typename TLHS, typename TRHS>
BuiltinTypeMethodBuilder &BuiltinTypeMethodBuilder::assign(TLHS LHS, TRHS RHS) {
  Expr *LHSExpr = convertPlaceholder(LHS);
  Expr *RHSExpr = convertPlaceholder(RHS);
  Stmt *AssignStmt = BinaryOperator::Create(
      DeclBuilder.SemaRef.getASTContext(), LHSExpr, RHSExpr, BO_Assign,
      LHSExpr->getType(), ExprValueKind::VK_PRValue,
      ExprObjectKind::OK_Ordinary, SourceLocation(), FPOptionsOverride());
  StmtsList.push_back(AssignStmt);
  return *this;
}

template <typename T>
BuiltinTypeMethodBuilder &BuiltinTypeMethodBuilder::dereference(T Ptr) {
  Expr *PtrExpr = convertPlaceholder(Ptr);
  Expr *Deref =
      UnaryOperator::Create(DeclBuilder.SemaRef.getASTContext(), PtrExpr,
                            UO_Deref, PtrExpr->getType()->getPointeeType(),
                            VK_PRValue, OK_Ordinary, SourceLocation(),
                            /*CanOverflow=*/false, FPOptionsOverride());
  StmtsList.push_back(Deref);
  return *this;
}

BuiltinTypeDeclBuilder &BuiltinTypeMethodBuilder::finalize() {
  assert(!DeclBuilder.Record->isCompleteDefinition() &&
         "record is already complete");

  ensureCompleteDecl();

  if (!Method->hasBody()) {
    ASTContext &AST = DeclBuilder.SemaRef.getASTContext();
    assert((ReturnTy == AST.VoidTy || !StmtsList.empty()) &&
           "nothing to return from non-void method");
    if (ReturnTy != AST.VoidTy) {
      if (Expr *LastExpr = dyn_cast<Expr>(StmtsList.back())) {
        assert(AST.hasSameUnqualifiedType(LastExpr->getType(),
                                          ReturnTy.getNonReferenceType()) &&
               "Return type of the last statement must match the return type "
               "of the method");
        if (!isa<ReturnStmt>(LastExpr)) {
          StmtsList.pop_back();
          StmtsList.push_back(
              ReturnStmt::Create(AST, SourceLocation(), LastExpr, nullptr));
        }
      }
    }

    Method->setBody(CompoundStmt::Create(AST, StmtsList, FPOptionsOverride(),
                                         SourceLocation(), SourceLocation()));
    Method->setLexicalDeclContext(DeclBuilder.Record);
    Method->setAccess(AccessSpecifier::AS_public);
    Method->addAttr(AlwaysInlineAttr::CreateImplicit(
        AST, SourceRange(), AlwaysInlineAttr::CXX11_clang_always_inline));
    DeclBuilder.Record->addDecl(Method);
  }
  return DeclBuilder;
}

BuiltinTypeDeclBuilder::BuiltinTypeDeclBuilder(Sema &SemaRef, CXXRecordDecl *R)
    : SemaRef(SemaRef), Record(R) {
  Record->startDefinition();
  Template = Record->getDescribedClassTemplate();
}

BuiltinTypeDeclBuilder::BuiltinTypeDeclBuilder(Sema &SemaRef,
                                               NamespaceDecl *Namespace,
                                               StringRef Name)
    : SemaRef(SemaRef), HLSLNamespace(Namespace) {
  ASTContext &AST = SemaRef.getASTContext();
  IdentifierInfo &II = AST.Idents.get(Name, tok::TokenKind::identifier);

  LookupResult Result(SemaRef, &II, SourceLocation(), Sema::LookupTagName);
  CXXRecordDecl *PrevDecl = nullptr;
  if (SemaRef.LookupQualifiedName(Result, HLSLNamespace)) {
    // Declaration already exists (from precompiled headers)
    NamedDecl *Found = Result.getFoundDecl();
    if (auto *TD = dyn_cast<ClassTemplateDecl>(Found)) {
      PrevDecl = TD->getTemplatedDecl();
      PrevTemplate = TD;
    } else
      PrevDecl = dyn_cast<CXXRecordDecl>(Found);
    assert(PrevDecl && "Unexpected lookup result type.");
  }

  if (PrevDecl && PrevDecl->isCompleteDefinition()) {
    Record = PrevDecl;
    Template = PrevTemplate;
    return;
  }

  Record = CXXRecordDecl::Create(AST, TagDecl::TagKind::Class, HLSLNamespace,
                                 SourceLocation(), SourceLocation(), &II,
                                 PrevDecl, true);
  Record->setImplicit(true);
  Record->setLexicalDeclContext(HLSLNamespace);
  Record->setHasExternalLexicalStorage();

  // Don't let anyone derive from built-in types.
  Record->addAttr(
      FinalAttr::CreateImplicit(AST, SourceRange(), FinalAttr::Keyword_final));
}

BuiltinTypeDeclBuilder::~BuiltinTypeDeclBuilder() {
  if (HLSLNamespace && !Template && Record->getDeclContext() == HLSLNamespace)
    HLSLNamespace->addDecl(Record);
}

CXXRecordDecl *BuiltinTypeDeclBuilder::finalizeForwardDeclaration() {
  // Force the QualType to be generated for the record declaration. In most
  // cases this will happen naturally when something uses the type the
  // QualType gets lazily created. Unfortunately, with our injected types if a
  // type isn't used in a translation unit the QualType may not get
  // automatically generated before a PCH is generated. To resolve this we
  // just force that the QualType is generated after we create a forward
  // declaration.
  (void)Record->getASTContext().getRecordType(Record);
  return Record;
}

BuiltinTypeDeclBuilder &
BuiltinTypeDeclBuilder::addMemberVariable(StringRef Name, QualType Type,
                                          llvm::ArrayRef<Attr *> Attrs,
                                          AccessSpecifier Access) {
  assert(!Record->isCompleteDefinition() && "record is already complete");
  assert(Record->isBeingDefined() &&
         "Definition must be started before adding members!");
  ASTContext &AST = Record->getASTContext();

  IdentifierInfo &II = AST.Idents.get(Name, tok::TokenKind::identifier);
  TypeSourceInfo *MemTySource =
      AST.getTrivialTypeSourceInfo(Type, SourceLocation());
  auto *Field = FieldDecl::Create(
      AST, Record, SourceLocation(), SourceLocation(), &II, Type, MemTySource,
      nullptr, false, InClassInitStyle::ICIS_NoInit);
  Field->setAccess(Access);
  Field->setImplicit(true);
  for (Attr *A : Attrs) {
    if (A)
      Field->addAttr(A);
  }

  Record->addDecl(Field);
  Fields[Name] = Field;
  return *this;
}

BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addHandleMember(
    ResourceClass RC, bool IsROV, bool RawBuffer, AccessSpecifier Access) {
  assert(!Record->isCompleteDefinition() && "record is already complete");

  ASTContext &Ctx = SemaRef.getASTContext();
  TypeSourceInfo *ElementTypeInfo =
      Ctx.getTrivialTypeSourceInfo(getHandleElementType(), SourceLocation());

  // add handle member with resource type attributes
  QualType AttributedResTy = QualType();
  SmallVector<const Attr *> Attrs = {
      HLSLResourceClassAttr::CreateImplicit(Ctx, RC),
      IsROV ? HLSLROVAttr::CreateImplicit(Ctx) : nullptr,
      RawBuffer ? HLSLRawBufferAttr::CreateImplicit(Ctx) : nullptr,
      ElementTypeInfo
          ? HLSLContainedTypeAttr::CreateImplicit(Ctx, ElementTypeInfo)
          : nullptr};
  if (CreateHLSLAttributedResourceType(SemaRef, Ctx.HLSLResourceTy, Attrs,
                                       AttributedResTy))
    addMemberVariable("__handle", AttributedResTy, {}, Access);
  return *this;
}

// Adds default constructor to the resource class:
// Resource::Resource()
BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addDefaultHandleConstructor() {
  if (Record->isCompleteDefinition())
    return *this;

  // FIXME: initialize handle to poison value; this can be added after
  // resource constructor from binding is implemented, otherwise the handle
  // value will get overwritten.
  return BuiltinTypeMethodBuilder(*this, "", SemaRef.getASTContext().VoidTy,
                                  false, true)
      .finalize();
}

BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addArraySubscriptOperators() {
  ASTContext &AST = Record->getASTContext();
  DeclarationName Subscript =
      AST.DeclarationNames.getCXXOperatorName(OO_Subscript);

  addHandleAccessFunction(Subscript, /*IsConst=*/true, /*IsRef=*/true);
  addHandleAccessFunction(Subscript, /*IsConst=*/false, /*IsRef=*/true);
  return *this;
}

BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addLoadMethods() {
  if (Record->isCompleteDefinition())
    return *this;

  ASTContext &AST = Record->getASTContext();
  IdentifierInfo &II = AST.Idents.get("Load", tok::TokenKind::identifier);
  DeclarationName Load(&II);
  // TODO: We also need versions with status for CheckAccessFullyMapped.
  addHandleAccessFunction(Load, /*IsConst=*/false, /*IsRef=*/false);

  return *this;
}

FieldDecl *BuiltinTypeDeclBuilder::getResourceHandleField() {
  auto I = Fields.find("__handle");
  assert(I != Fields.end() &&
         I->second->getType()->isHLSLAttributedResourceType() &&
         "record does not have resource handle field");
  return I->second;
}

QualType BuiltinTypeDeclBuilder::getFirstTemplateTypeParam() {
  assert(Template && "record it not a template");
  if (const auto *TTD = dyn_cast<TemplateTypeParmDecl>(
          Template->getTemplateParameters()->getParam(0))) {
    return QualType(TTD->getTypeForDecl(), 0);
  }
  return QualType();
}

QualType BuiltinTypeDeclBuilder::getHandleElementType() {
  if (Template)
    return getFirstTemplateTypeParam();
  // TODO: Should we default to VoidTy? Using `i8` is arguably ambiguous.
  return SemaRef.getASTContext().Char8Ty;
}

// BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::startDefinition() {
//   assert(!Record->isCompleteDefinition() && "record is already complete");
//   Record->startDefinition();
//   return *this;
// }

BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::completeDefinition() {
  assert(!Record->isCompleteDefinition() && "record is already complete");
  assert(Record->isBeingDefined() &&
         "Definition must be started before completing it.");

  Record->completeDefinition();
  return *this;
}

Expr *BuiltinTypeDeclBuilder::getConstantIntExpr(int value) {
  ASTContext &AST = SemaRef.getASTContext();
  return IntegerLiteral::Create(
      AST, llvm::APInt(AST.getTypeSize(AST.IntTy), value, true), AST.IntTy,
      SourceLocation());
}

BuiltinTypeDeclBuilder &
BuiltinTypeDeclBuilder::addSimpleTemplateParams(ArrayRef<StringRef> Names,
                                                ConceptDecl *CD = nullptr) {
  if (Record->isCompleteDefinition()) {
    assert(Template && "existing record it not a template");
    assert(Template->getTemplateParameters()->size() == Names.size() &&
           "template param count mismatch");
    return *this;
  }

  TemplateParameterListBuilder Builder = TemplateParameterListBuilder(*this);
  for (StringRef Name : Names)
    Builder.addTypeParameter(Name);
  return Builder.finalizeTemplateArgs(CD);
}

BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addIncrementCounterMethod() {
  using PH = BuiltinTypeMethodBuilder::PlaceHolder;
  return BuiltinTypeMethodBuilder(*this, "IncrementCounter",
                                  SemaRef.getASTContext().UnsignedIntTy)
      .callBuiltin("__builtin_hlsl_buffer_update_counter", QualType(),
                   PH::Handle, getConstantIntExpr(1))
      .finalize();
}

BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addDecrementCounterMethod() {
  using PH = BuiltinTypeMethodBuilder::PlaceHolder;
  return BuiltinTypeMethodBuilder(*this, "DecrementCounter",
                                  SemaRef.getASTContext().UnsignedIntTy)
      .callBuiltin("__builtin_hlsl_buffer_update_counter", QualType(),
                   PH::Handle, getConstantIntExpr(-1))
      .finalize();
}

BuiltinTypeDeclBuilder &
BuiltinTypeDeclBuilder::addHandleAccessFunction(DeclarationName &Name,
                                                bool IsConst, bool IsRef) {
  assert(!Record->isCompleteDefinition() && "record is already complete");
  ASTContext &AST = SemaRef.getASTContext();
  using PH = BuiltinTypeMethodBuilder::PlaceHolder;

  QualType ElemTy = getHandleElementType();
  // TODO: Map to an hlsl_device address space.
  QualType ElemPtrTy = AST.getPointerType(ElemTy);
  QualType ReturnTy = ElemTy;
  if (IsConst)
    ReturnTy.addConst();
  if (IsRef)
    ReturnTy = AST.getLValueReferenceType(ReturnTy);

  return BuiltinTypeMethodBuilder(*this, Name, ReturnTy, IsConst)
      .addParam("Index", AST.UnsignedIntTy)
      .callBuiltin("__builtin_hlsl_resource_getpointer", ElemPtrTy, PH::Handle,
                   PH::_0)
      .dereference(PH::LastStmt)
      .finalize();
}

BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addAppendMethod() {
  using PH = BuiltinTypeMethodBuilder::PlaceHolder;
  ASTContext &AST = SemaRef.getASTContext();
  QualType ElemTy = getHandleElementType();
  return BuiltinTypeMethodBuilder(*this, "Append", AST.VoidTy)
      .addParam("value", ElemTy)
      .callBuiltin("__builtin_hlsl_buffer_update_counter", AST.UnsignedIntTy,
                   PH::Handle, getConstantIntExpr(1))
      .callBuiltin("__builtin_hlsl_resource_getpointer",
                   AST.getPointerType(ElemTy), PH::Handle, PH::LastStmt)
      .dereference(PH::LastStmt)
      .assign(PH::LastStmt, PH::_0)
      .finalize();
}

BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addConsumeMethod() {
  using PH = BuiltinTypeMethodBuilder::PlaceHolder;
  ASTContext &AST = SemaRef.getASTContext();
  QualType ElemTy = getHandleElementType();
  return BuiltinTypeMethodBuilder(*this, "Consume", ElemTy)
      .callBuiltin("__builtin_hlsl_buffer_update_counter", AST.UnsignedIntTy,
                   PH::Handle, getConstantIntExpr(-1))
      .callBuiltin("__builtin_hlsl_resource_getpointer",
                   AST.getPointerType(ElemTy), PH::Handle, PH::LastStmt)
      .dereference(PH::LastStmt)
      .finalize();
}

} // namespace hlsl
} // namespace clang
