// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#ifndef CARBON_TOOLCHAIN_SEM_IR_TYPE_INFO_H_
#define CARBON_TOOLCHAIN_SEM_IR_TYPE_INFO_H_

#include "common/ostream.h"
#include "toolchain/sem_ir/function.h"
#include "toolchain/sem_ir/ids.h"

namespace Carbon::SemIR {

// The value representation to use when passing by value.
struct ValueRepr : public Printable<ValueRepr> {
  // Returns information about the value representation to use for a type.
  static auto ForType(const File& file, TypeId type_id) -> ValueRepr;

  auto Print(llvm::raw_ostream& out) const -> void;

  enum Kind : int8_t {
    // The value representation is not yet known. This is used for incomplete
    // types.
    Unknown,
    // The value representation is dependent because the type is symbolic.
    Dependent,
    // The type has no value representation. This is used for empty types, such
    // as `()`, where there is no value.
    None,
    // The value representation is a copy of the value. On call boundaries, the
    // value itself will be passed. `type` is the value type.
    Copy,
    // The value representation is a pointer to the value. When used as a
    // parameter, the argument is a reference expression. `type` is the pointee
    // type.
    Pointer,
    // The value representation has been customized, and has the same behavior
    // as the value representation of some other type.
    // TODO: This is not implemented or used yet.
    Custom,
  };

  enum AggregateKind : int8_t {
    // This type is not an aggregation of other types.
    NotAggregate,
    // This type is an aggregate that holds the value representations of its
    // elements.
    ValueAggregate,
    // This type is an aggregate that holds the object representations of its
    // elements.
    ObjectAggregate,
    // This type is an aggregate for which the value and object representation
    // of all elements are the same, so it effectively holds both.
    ValueAndObjectAggregate,
  };

  // Returns whether this is an aggregate that holds its elements by value.
  auto elements_are_values() const {
    return aggregate_kind == ValueAggregate ||
           aggregate_kind == ValueAndObjectAggregate;
  }

  // Returns whether this value representation is a copy of the object
  // representation of the type. `orig_type_id` must be the type for which this
  // is the value representation.
  auto IsCopyOfObjectRepr(const File& file, TypeId orig_type_id) const -> bool;

  // The kind of value representation used by this type.
  Kind kind = Unknown;
  // The kind of aggregate representation used by this type.
  AggregateKind aggregate_kind = AggregateKind::NotAggregate;
  // The type used to model the value representation.
  TypeId type_id = TypeId::None;
};

// Information stored about a TypeId corresponding to a complete type.
struct CompleteTypeInfo : public Printable<CompleteTypeInfo> {
  auto Print(llvm::raw_ostream& out) const -> void;

  // The value representation for this type. Will be `Unknown` if the type is
  // not complete.
  ValueRepr value_repr = ValueRepr();

  // If this type is abstract, this is id of an abstract class it uses.
  ClassId abstract_class_id = ClassId::None;
};

// The representation to use for an initializing expression of some type.
struct InitRepr : Printable<InitRepr> {
  // Returns information about the initializing representation to use for a
  // type.
  static auto ForType(const File& file, TypeId type_id) -> InitRepr;

  enum Kind : int8_t {
    // The type has no initializing representation. This is used for empty
    // types, where no initialization is necessary.
    None,
    // The initializing representation is dependent because the type is
    // symbolic.
    Dependent,
    // An initializing expression produces an object representation by value,
    // which is copied into the initialized object.
    ByCopy,
    // An initializing expression takes a location as input, which is
    // initialized as a side effect of evaluating the expression.
    InPlace,
    // No initializing expressions should exist because the type is abstract.
    Abstract,
    // No initializing expressions should exist yet, because the type is not
    // complete.
    Incomplete,
    // TODO: Consider adding a kind where the expression takes an advisory
    // location and returns a value plus an indicator of whether the location
    // was actually initialized.
  };
  // The kind of initializing representation used by this type.
  Kind kind;

  // Returns whether the type can be used as the type of an initializing
  // expression in the current context.
  auto is_valid() const -> bool {
    return kind != Incomplete && kind != Abstract;
  }

  // Returns whether the initializing representation is a copy of the object
  // representation of the type. Provided for symmetry with `ValueRepr`.
  auto IsCopyOfObjectRepr() const -> bool { return kind == ByCopy; }

  // Returns whether the initializing representation might be by-copy, and
  // therefore might require a final destination store.
  auto MightBeByCopy() const -> bool {
    return kind == ByCopy || kind == Dependent;
  }

  // Returns whether the initializing representation might be in-place, and
  // therefore might require a destination address to be provided as input.
  auto MightBeInPlace() const -> bool {
    return kind == InPlace || kind == Dependent;
  }

  auto Print(llvm::raw_ostream& out) const -> void {
    out << "{kind: ";
    switch (kind) {
      case None:
        out << "None";
        break;
      case Dependent:
        out << "Dependent";
        break;
      case ByCopy:
        out << "ByCopy";
        break;
      case InPlace:
        out << "InPlace";
        break;
      case Abstract:
        out << "Abstract";
        break;
      case Incomplete:
        out << "Incomplete";
        break;
    }
    out << "}";
  }
};

// Information about the numeric type literal that corresponds to a type.
struct NumericTypeLiteralInfo {
  // The kind of a numeric type literal, as determined by the letter that
  // prefixes the bit width.
  enum Kind : char {
    None = 0,
    Int = 'i',
    UInt = 'u',
    Float = 'f',
  };

  static const NumericTypeLiteralInfo Invalid;

  // Returns the numeric type literal that would evaluate to this class type, if
  // any.
  static auto ForType(const File& file, ClassType class_type)
      -> NumericTypeLiteralInfo;

  // Prints the numeric type literal that corresponds to this type.
  auto PrintLiteral(const File& file, llvm::raw_ostream& out) const -> void;

  // Returns whether this is a valid numeric type literal.
  auto is_valid() const -> bool { return kind != None; }

  // The kind of this type literal.
  Kind kind;
  // The bit width of this type literal.
  IntId bit_width_id;
};

// Information about a recognized type, which is either a literal type, or a C++
// builtin.
struct RecognizedTypeInfo {
  enum Kind : char {
    None,
    // A numeric type literal such as `i8`; see `numeric` field for details.
    Numeric,
    // `char` / `Core.Char`.
    Char,
    // `Core.CppCompat.Long32` which is `Cpp.long` when `long` is 32 bits.
    CppLong32,
    // `Core.CppCompat.ULong32` which is `Cpp.unsigned_long` when `unsigned
    // long` is 32 bits.
    CppULong32,
    // `Core.CppCompat.LongLong64` which is `Cpp.long_long` when `long` is 64
    // bits.
    CppLongLong64,
    // `Core.CppCompat.ULongLong64` which is `Cpp.unsigned_long_long` when
    // `unsigned long` is 64 bits.
    CppULongLong64,
    // `Cpp.nullptr_t` / `Core.CppCompat.NullptrT`.
    CppNullptrT,
    // `Cpp.void` / `Core.CppCompat.VoidBase`.
    CppVoidBase,
    // `Core.Optional(...)`.
    Optional,
    // `str` / `Core.String`.
    // TODO: Rename `Core.String` to `Core.Str`.
    Str,
  };

  // Returns the type literal that would evaluate to this class type, if any.
  static auto ForType(const File& file, ClassType class_type)
      -> RecognizedTypeInfo;

  // Prints the type literal or special type name that corresponds to this type,
  // if there is one. Returns true if the type was printed, or false if this
  // type doesn't have special syntax and should be printed directly.
  auto PrintLiteral(const File& file, llvm::raw_ostream& out) const -> bool;

  // Returns whether this is a valid type literal.
  auto is_valid() const -> bool { return kind != None; }

  // The kind of the literal.
  Kind kind;
  // If this is a numeric literal, additional information about the literal.
  NumericTypeLiteralInfo numeric = NumericTypeLiteralInfo::Invalid;
  // If this is a generic type, the arguments.
  InstBlockId args_id = InstBlockId::None;
};

inline constexpr NumericTypeLiteralInfo NumericTypeLiteralInfo::Invalid = {
    .kind = None, .bit_width_id = IntId::None};

}  // namespace Carbon::SemIR

#endif  // CARBON_TOOLCHAIN_SEM_IR_TYPE_INFO_H_
