// 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_CHECK_CONVERT_H_
#define CARBON_TOOLCHAIN_CHECK_CONVERT_H_

#include "toolchain/check/context.h"
#include "toolchain/check/pending_block.h"
#include "toolchain/sem_ir/entity_with_params_base.h"
#include "toolchain/sem_ir/ids.h"

namespace Carbon::Check {

// Description of the target of a conversion.
struct ConversionTarget {
  enum Kind : int8_t {
    // Convert to a value of type `type_id`.
    Value,
    // Convert to either a value or a reference of type `type_id`.
    ValueOrRef,
    // Convert to a durable reference of type `type_id`.
    DurableRef,
    // Convert to a reference of type `type_id`, for use as the argument to a
    // C++ thunk.
    CppThunkRef,
    // Convert for an explicit `as` cast. This allows any expression category
    // as the result, and uses the `As` interface instead of the `ImplicitAs`
    // interface.
    ExplicitAs,
    // Convert for an explicit `unsafe as` cast. This allows any expression
    // category as the result, and uses the `UnsafeAs` interface instead of the
    // `As` or `ImplicitAs` interface.
    ExplicitUnsafeAs,
    // The result of the conversion is discarded. It can't be an initializing
    // expression, but can be anything else.
    Discarded,
    // Convert to an initializer for the object denoted by `init_id`.
    Initializer,
    // Convert to an initializer for the object denoted by `init_id`,
    // including a final destination store if needed.
    FullInitializer,
    Last = FullInitializer
  };
  // The kind of the target for this conversion.
  Kind kind;
  // The target type for the conversion.
  SemIR::TypeId type_id;
  // For an initializer, the object being initialized.
  SemIR::InstId init_id = SemIR::InstId::None;
  // For an initializer, a block of pending instructions that are needed to
  // form the value of `init_id`, and that can be discarded if no
  // initialization is needed.
  PendingBlock* init_block = nullptr;
  // Whether failure of conversion is an error and is diagnosed to the user.
  // When looking for a possible conversion but with graceful fallback, diagnose
  // should be false.
  bool diagnose = true;

  // Are we converting this value into an initializer for an object?
  auto is_initializer() const -> bool {
    return kind == Initializer || kind == FullInitializer;
  }
  // Is this some kind of explicit `as` conversion?
  auto is_explicit_as() const -> bool {
    return kind == ExplicitAs || kind == ExplicitUnsafeAs;
  }
};

// Convert a value to another type and expression category.
// TODO: The `vtable_id` parameter is too much of a special case here, and
// should be removed - once partial classes are implemented, the vtable pointer
// initialization will be done not in this conversion, but during initialization
// of the object of non-partial class type from the object of partial class
// type.
auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
             ConversionTarget target,
             SemIR::ClassType* vtable_class_type = nullptr) -> SemIR::InstId;

// Performs initialization of `target_id` from `value_id`. Returns the
// possibly-converted initializing expression, which should be assigned to the
// target using a suitable node for the kind of initialization.
auto Initialize(Context& context, SemIR::LocId loc_id, SemIR::InstId target_id,
                SemIR::InstId value_id) -> SemIR::InstId;

// Convert the given expression to a value expression of the same type.
auto ConvertToValueExpr(Context& context, SemIR::InstId expr_id)
    -> SemIR::InstId;

// Convert the given expression to a value or reference expression of the same
// type.
auto ConvertToValueOrRefExpr(Context& context, SemIR::InstId expr_id)
    -> SemIR::InstId;

// Converts `expr_id` to a value expression of type `type_id`.
auto ConvertToValueOfType(Context& context, SemIR::LocId loc_id,
                          SemIR::InstId expr_id, SemIR::TypeId type_id)
    -> SemIR::InstId;

// Convert the given expression to a value or reference expression of the given
// type.
auto ConvertToValueOrRefOfType(Context& context, SemIR::LocId loc_id,
                               SemIR::InstId expr_id, SemIR::TypeId type_id)
    -> SemIR::InstId;

// Attempted to convert `expr_id` to a value expression of type `type_id`, with
// graceful failure, which does not result in diagnostics. An ErrorInst
// instruction is still returned on failure.
auto TryConvertToValueOfType(Context& context, SemIR::LocId loc_id,
                             SemIR::InstId expr_id, SemIR::TypeId type_id)
    -> SemIR::InstId;

// Converts `value_id` to a value expression of type `bool`.
auto ConvertToBoolValue(Context& context, SemIR::LocId loc_id,
                        SemIR::InstId value_id) -> SemIR::InstId;

// Converts `value_id` to type `type_id` for an `as` expression.
auto ConvertForExplicitAs(Context& context, Parse::NodeId as_node,
                          SemIR::InstId value_id, SemIR::TypeId type_id,
                          bool unsafe) -> SemIR::InstId;

// Implicitly converts a set of arguments to match the parameter types in a
// function call. Returns a block containing the converted implicit and explicit
// argument values for runtime parameters.
auto ConvertCallArgs(Context& context, SemIR::LocId call_loc_id,
                     SemIR::InstId self_id,
                     llvm::ArrayRef<SemIR::InstId> arg_refs,
                     SemIR::InstId return_slot_arg_id,
                     const SemIR::Function& callee,
                     SemIR::SpecificId callee_specific_id)
    -> SemIR::InstBlockId;

// A type that has been converted for use as a type expression.
struct TypeExpr {
  static const TypeExpr None;

  // The converted expression of type `type`, or `ErrorInst::InstId`.
  SemIR::TypeInstId inst_id;
  // The corresponding type, or `ErrorInst::TypeId`.
  SemIR::TypeId type_id;
};

constexpr inline TypeExpr TypeExpr::None = {.inst_id = SemIR::TypeInstId::None,
                                            .type_id = SemIR::TypeId::None};

// Converts an expression for use as a type.
//
// If `diagnose` is true, errors are diagnosed to the user. Set it to false when
// looking to see if a conversion is possible but with graceful fallback.
//
// TODO: Most of the callers of this function discard the `inst_id` and lose
// track of the conversion. In most cases we should be retaining that as the
// operand of some downstream instruction.
auto ExprAsType(Context& context, SemIR::LocId loc_id, SemIR::InstId value_id,
                bool diagnose = true) -> TypeExpr;

// Handles an expression whose result value is unused.
auto DiscardExpr(Context& context, SemIR::InstId expr_id) -> void;

}  // namespace Carbon::Check

#endif  // CARBON_TOOLCHAIN_CHECK_CONVERT_H_
