//===------------------------- MemberPointer.h ------------------*- 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
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_AST_INTERP_MEMBER_POINTER_H
#define LLVM_CLANG_AST_INTERP_MEMBER_POINTER_H

#include "Pointer.h"
#include <optional>

namespace clang {
class ASTContext;
namespace interp {

class Context;
class FunctionPointer;

class MemberPointer final {
private:
  Pointer Base;
  const ValueDecl *Dcl = nullptr;
  int32_t PtrOffset = 0;

  MemberPointer(Pointer Base, const ValueDecl *Dcl, int32_t PtrOffset)
      : Base(Base), Dcl(Dcl), PtrOffset(PtrOffset) {}

public:
  MemberPointer() = default;
  MemberPointer(Pointer Base, const ValueDecl *Dcl) : Base(Base), Dcl(Dcl) {}
  MemberPointer(uint32_t Address, const Descriptor *D) {
    // We only reach this for Address == 0, when creating a null member pointer.
    assert(Address == 0);
  }

  MemberPointer(const ValueDecl *D) : Dcl(D) {
    assert((isa<FieldDecl, IndirectFieldDecl, CXXMethodDecl>(D)));
  }

  uint64_t getIntegerRepresentation() const {
    assert(
        false &&
        "getIntegerRepresentation() shouldn't be reachable for MemberPointers");
    return 17;
  }

  std::optional<Pointer> toPointer(const Context &Ctx) const;

  FunctionPointer toFunctionPointer(const Context &Ctx) const;

  Pointer getBase() const {
    if (PtrOffset < 0)
      return Base.atField(-PtrOffset);
    return Base.atFieldSub(PtrOffset);
  }
  bool isMemberFunctionPointer() const {
    return isa_and_nonnull<CXXMethodDecl>(Dcl);
  }
  const CXXMethodDecl *getMemberFunction() const {
    return dyn_cast_if_present<CXXMethodDecl>(Dcl);
  }
  const FieldDecl *getField() const {
    return dyn_cast_if_present<FieldDecl>(Dcl);
  }

  bool hasDecl() const { return Dcl; }
  const ValueDecl *getDecl() const { return Dcl; }

  MemberPointer atInstanceBase(unsigned Offset) const {
    if (Base.isZero())
      return MemberPointer(Base, Dcl, Offset);
    return MemberPointer(this->Base, Dcl, Offset + PtrOffset);
  }

  MemberPointer takeInstance(Pointer Instance) const {
    assert(this->Base.isZero());
    return MemberPointer(Instance, this->Dcl, this->PtrOffset);
  }

  APValue toAPValue(const ASTContext &) const;

  bool isZero() const { return Base.isZero() && !Dcl; }
  bool hasBase() const { return !Base.isZero(); }
  bool isWeak() const {
    if (const auto *MF = getMemberFunction())
      return MF->isWeak();
    return false;
  }

  void print(llvm::raw_ostream &OS) const {
    OS << "MemberPtr(" << Base << " " << (const void *)Dcl << " + " << PtrOffset
       << ")";
  }

  std::string toDiagnosticString(const ASTContext &Ctx) const {
    return toAPValue(Ctx).getAsString(Ctx, Dcl->getType());
  }

  ComparisonCategoryResult compare(const MemberPointer &RHS) const {
    if (this->Dcl == RHS.Dcl)
      return ComparisonCategoryResult::Equal;
    return ComparisonCategoryResult::Unordered;
  }
};

inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, MemberPointer FP) {
  FP.print(OS);
  return OS;
}

} // namespace interp
} // namespace clang

#endif
