(root)/
gcc-13.2.0/
gcc/
rust/
typecheck/
rust-coercion.cc
// Copyright (C) 2020-2023 Free Software Foundation, Inc.

// This file is part of GCC.

// GCC is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3, or (at your option) any later
// version.

// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
// for more details.

// You should have received a copy of the GNU General Public License
// along with GCC; see the file COPYING3.  If not see
// <http://www.gnu.org/licenses/>.

#include "rust-hir-type-check-base.h"
#include "rust-coercion.h"
#include "rust-unify.h"

namespace Rust {
namespace Resolver {

TypeCoercionRules::CoercionResult
TypeCoercionRules::Coerce (TyTy::BaseType *receiver, TyTy::BaseType *expected,
			   Location locus)
{
  TypeCoercionRules resolver (expected, locus, true);
  bool ok = resolver.do_coercion (receiver);
  return ok ? resolver.try_result : CoercionResult::get_error ();
}

TypeCoercionRules::CoercionResult
TypeCoercionRules::TryCoerce (TyTy::BaseType *receiver,
			      TyTy::BaseType *expected, Location locus)
{
  TypeCoercionRules resolver (expected, locus, false);
  bool ok = resolver.do_coercion (receiver);
  return ok ? resolver.try_result : CoercionResult::get_error ();
}

TypeCoercionRules::TypeCoercionRules (TyTy::BaseType *expected, Location locus,
				      bool emit_errors)
  : AutoderefCycle (false), mappings (Analysis::Mappings::get ()),
    context (TypeCheckContext::get ()), expected (expected), locus (locus),
    try_result (CoercionResult::get_error ()), emit_errors (emit_errors)
{}

bool
TypeCoercionRules::do_coercion (TyTy::BaseType *receiver)
{
  // FIXME this is not finished and might be super simplified
  // see:
  // https://github.com/rust-lang/rust/blob/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/compiler/rustc_typeck/src/check/coercion.rs

  // handle never
  // https://github.com/rust-lang/rust/blob/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/compiler/rustc_typeck/src/check/coercion.rs#L155
  if (receiver->get_kind () == TyTy::TypeKind::NEVER)
    {
      // Subtle: If we are coercing from `!` to `?T`, where `?T` is an unbound
      // type variable, we want `?T` to fallback to `!` if not
      // otherwise constrained. An example where this arises:
      //
      //     let _: Option<?T> = Some({ return; });
      //
      // here, we would coerce from `!` to `?T`.
      if (expected->has_subsititions_defined () && !expected->is_concrete ())
	{
	  Location locus = mappings->lookup_location (receiver->get_ref ());
	  TyTy::TyVar implicit_var
	    = TyTy::TyVar::get_implicit_infer_var (locus);
	  try_result = CoercionResult{{}, implicit_var.get_tyty ()};
	  return true;
	}
      else
	{
	  bool expected_is_infer_var
	    = expected->get_kind () == TyTy::TypeKind::INFER;
	  bool expected_is_general_infer_var
	    = expected_is_infer_var
	      && (static_cast<TyTy::InferType *> (expected)->get_infer_kind ()
		  == TyTy::InferType::InferTypeKind::GENERAL);

	  // FIXME this 'expected_is_general_infer_var' case needs to eventually
	  // should go away see: compile/never_type_err1.rs
	  //
	  // I think we need inference obligations to say that yes we have a
	  // general inference variable but we add the oligation to the expected
	  // type that it could default to '!'
	  if (expected_is_general_infer_var)
	    try_result = CoercionResult{{}, receiver};
	  else
	    try_result = CoercionResult{{}, expected->clone ()};

	  return true;
	}
    }

  // unsize
  bool unsafe_error = false;
  CoercionResult unsize_coercion
    = coerce_unsized (receiver, expected, unsafe_error);
  bool valid_unsize_coercion = !unsize_coercion.is_error ();
  if (valid_unsize_coercion)
    {
      try_result = unsize_coercion;
      return true;
    }
  else if (unsafe_error)
    {
      // Location lhs = mappings->lookup_location (receiver->get_ref ());
      // Location rhs = mappings->lookup_location (expected->get_ref ());
      // object_unsafe_error (locus, lhs, rhs);
      return false;
    }

  // pointers
  switch (expected->get_kind ())
    {
      case TyTy::TypeKind::POINTER: {
	TyTy::PointerType *ptr = static_cast<TyTy::PointerType *> (expected);
	try_result = coerce_unsafe_ptr (receiver, ptr, ptr->mutability ());
	return !try_result.is_error ();
      }

      case TyTy::TypeKind::REF: {
	TyTy::ReferenceType *ptr
	  = static_cast<TyTy::ReferenceType *> (expected);
	try_result
	  = coerce_borrowed_pointer (receiver, ptr, ptr->mutability ());
	return !try_result.is_error ();
      }
      break;

    default:
      break;
    }

  return !try_result.is_error ();
}

TypeCoercionRules::CoercionResult
TypeCoercionRules::coerce_unsafe_ptr (TyTy::BaseType *receiver,
				      TyTy::PointerType *expected,
				      Mutability to_mutbl)
{
  rust_debug ("coerce_unsafe_ptr(a={%s}, b={%s})",
	      receiver->debug_str ().c_str (), expected->debug_str ().c_str ());

  Mutability from_mutbl = Mutability::Imm;
  TyTy::BaseType *element = nullptr;
  switch (receiver->get_kind ())
    {
      case TyTy::TypeKind::REF: {
	TyTy::ReferenceType *ref
	  = static_cast<TyTy::ReferenceType *> (receiver);
	from_mutbl = ref->mutability ();
	element = ref->get_base ();
      }
      break;

      case TyTy::TypeKind::POINTER: {
	TyTy::PointerType *ref = static_cast<TyTy::PointerType *> (receiver);
	from_mutbl = ref->mutability ();
	element = ref->get_base ();
      }
      break;

      default: {
	if (receiver->can_eq (expected, false))
	  return CoercionResult{{}, expected->clone ()};

	return CoercionResult::get_error ();
      }
    }

  if (!coerceable_mutability (from_mutbl, to_mutbl))
    {
      Location lhs = mappings->lookup_location (receiver->get_ref ());
      Location rhs = mappings->lookup_location (expected->get_ref ());
      mismatched_mutability_error (locus, lhs, rhs);
      return TypeCoercionRules::CoercionResult::get_error ();
    }

  TyTy::PointerType *result
    = new TyTy::PointerType (receiver->get_ref (),
			     TyTy::TyVar (element->get_ref ()), to_mutbl);
  if (!result->can_eq (expected, false))
    return CoercionResult::get_error ();

  return CoercionResult{{}, result};
}

/// Reborrows `&mut A` to `&mut B` and `&(mut) A` to `&B`.
/// To match `A` with `B`, autoderef will be performed,
/// calling `deref`/`deref_mut` where necessary.
TypeCoercionRules::CoercionResult
TypeCoercionRules::coerce_borrowed_pointer (TyTy::BaseType *receiver,
					    TyTy::ReferenceType *expected,
					    Mutability to_mutbl)
{
  rust_debug ("coerce_borrowed_pointer(a={%s}, b={%s})",
	      receiver->debug_str ().c_str (), expected->debug_str ().c_str ());

  Mutability from_mutbl = Mutability::Imm;
  switch (receiver->get_kind ())
    {
      case TyTy::TypeKind::REF: {
	TyTy::ReferenceType *from
	  = static_cast<TyTy::ReferenceType *> (receiver);
	from_mutbl = from->mutability ();
      }
      break;

      default: {
	// FIXME
	// we might be able to replace this with a can_eq because we default
	// back to a final unity anyway
	rust_debug ("coerce_borrowed_pointer -- unify");
	TyTy::BaseType *result
	  = UnifyRules::Resolve (TyTy::TyWithLocation (receiver),
				 TyTy::TyWithLocation (expected), locus,
				 true /* commit */, true /* emit_errors */);
	return CoercionResult{{}, result};
      }
    }

  if (!coerceable_mutability (from_mutbl, to_mutbl))
    {
      Location lhs = mappings->lookup_location (receiver->get_ref ());
      Location rhs = mappings->lookup_location (expected->get_ref ());
      mismatched_mutability_error (locus, lhs, rhs);
      return TypeCoercionRules::CoercionResult::get_error ();
    }

  rust_debug ("coerce_borrowed_pointer -- autoderef cycle");
  AutoderefCycle::cycle (receiver);
  rust_debug ("coerce_borrowed_pointer -- result: [%s] with adjustments: [%zu]",
	      try_result.is_error () ? "failed" : "matched",
	      try_result.adjustments.size ());

  return try_result;
}

// &[T; n] or &mut [T; n] -> &[T]
// or &mut [T; n] -> &mut [T]
// or &Concrete -> &Trait, etc.
TypeCoercionRules::CoercionResult
TypeCoercionRules::coerce_unsized (TyTy::BaseType *source,
				   TyTy::BaseType *target, bool &unsafe_error)
{
  rust_debug ("coerce_unsized(source={%s}, target={%s})",
	      source->debug_str ().c_str (), target->debug_str ().c_str ());

  bool source_is_ref = source->get_kind () == TyTy::TypeKind::REF;
  bool target_is_ref = target->get_kind () == TyTy::TypeKind::REF;
  bool target_is_ptr = target->get_kind () == TyTy::TypeKind::POINTER;

  bool needs_reborrow = false;
  TyTy::BaseType *ty_a = source;
  TyTy::BaseType *ty_b = target;
  Mutability expected_mutability = Mutability::Imm;
  if (source_is_ref && target_is_ref)
    {
      TyTy::ReferenceType *source_ref
	= static_cast<TyTy::ReferenceType *> (source);
      TyTy::ReferenceType *target_ref
	= static_cast<TyTy::ReferenceType *> (target);

      Mutability from_mutbl = source_ref->mutability ();
      Mutability to_mutbl = target_ref->mutability ();
      if (!coerceable_mutability (from_mutbl, to_mutbl))
	{
	  unsafe_error = true;
	  Location lhs = mappings->lookup_location (source->get_ref ());
	  Location rhs = mappings->lookup_location (target->get_ref ());
	  mismatched_mutability_error (locus, lhs, rhs);
	  return TypeCoercionRules::CoercionResult::get_error ();
	}

      ty_a = source_ref->get_base ();
      ty_b = target_ref->get_base ();
      needs_reborrow = true;
      expected_mutability = to_mutbl;

      adjustments.push_back (
	Adjustment (Adjustment::AdjustmentType::INDIRECTION, source_ref, ty_a));
    }
  else if (source_is_ref && target_is_ptr)
    {
      TyTy::ReferenceType *source_ref
	= static_cast<TyTy::ReferenceType *> (source);
      TyTy::PointerType *target_ref = static_cast<TyTy::PointerType *> (target);

      Mutability from_mutbl = source_ref->mutability ();
      Mutability to_mutbl = target_ref->mutability ();
      if (!coerceable_mutability (from_mutbl, to_mutbl))
	{
	  unsafe_error = true;
	  Location lhs = mappings->lookup_location (source->get_ref ());
	  Location rhs = mappings->lookup_location (target->get_ref ());
	  mismatched_mutability_error (locus, lhs, rhs);
	  return TypeCoercionRules::CoercionResult::get_error ();
	}

      ty_a = source_ref->get_base ();
      ty_b = target_ref->get_base ();
      needs_reborrow = true;
      expected_mutability = to_mutbl;

      adjustments.push_back (
	Adjustment (Adjustment::AdjustmentType::INDIRECTION, source_ref, ty_a));
    }

  // FIXME
  // there is a bunch of code to ensure something is coerce able to a dyn trait
  // we need to support but we need to support a few more lang items for that
  // see:
  // https://github.com/rust-lang/rust/blob/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/compiler/rustc_typeck/src/check/coercion.rs#L582

  const auto a = ty_a;
  const auto b = ty_b;

  bool expect_dyn = b->get_kind () == TyTy::TypeKind::DYNAMIC;
  bool need_unsize = a->get_kind () != TyTy::TypeKind::DYNAMIC;

  if (expect_dyn && need_unsize)
    {
      bool bounds_compatible = b->bounds_compatible (*a, locus, true);
      if (!bounds_compatible)
	{
	  unsafe_error = true;
	  return TypeCoercionRules::CoercionResult::get_error ();
	}

      // return the unsize coercion
      TyTy::BaseType *result = b->clone ();
      // result->set_ref (a->get_ref ());

      // append a dyn coercion adjustment
      adjustments.push_back (Adjustment (Adjustment::UNSIZE, a, result));

      // reborrow if needed
      if (needs_reborrow)
	{
	  TyTy::ReferenceType *reborrow
	    = new TyTy::ReferenceType (source->get_ref (),
				       TyTy::TyVar (result->get_ref ()),
				       expected_mutability);

	  Adjustment::AdjustmentType borrow_type
	    = expected_mutability == Mutability::Imm ? Adjustment::IMM_REF
						     : Adjustment::MUT_REF;
	  adjustments.push_back (Adjustment (borrow_type, result, reborrow));
	  result = reborrow;
	}

      return CoercionResult{adjustments, result};
    }

  adjustments.clear ();
  return TypeCoercionRules::CoercionResult::get_error ();
}

bool
TypeCoercionRules::select (const TyTy::BaseType &autoderefed)
{
  rust_debug (
    "autoderef type-coercion select autoderefed={%s} can_eq expected={%s}",
    autoderefed.debug_str ().c_str (), expected->debug_str ().c_str ());
  if (expected->can_eq (&autoderefed, false))
    {
      try_result = CoercionResult{adjustments, autoderefed.clone ()};
      return true;
    }
  return false;
}

/// Coercing a mutable reference to an immutable works, while
/// coercing `&T` to `&mut T` should be forbidden.
bool
TypeCoercionRules::coerceable_mutability (Mutability from_mutbl,
					  Mutability to_mutbl)
{
  return to_mutbl == Mutability::Imm || (from_mutbl == to_mutbl);
}

void
TypeCoercionRules::mismatched_mutability_error (Location expr_locus,
						Location lhs, Location rhs)
{
  if (!emit_errors)
    return;

  RichLocation r (expr_locus);
  r.add_range (lhs);
  r.add_range (rhs);
  rust_error_at (r, "mismatched mutability");
}

void
TypeCoercionRules::object_unsafe_error (Location expr_locus, Location lhs,
					Location rhs)
{
  if (!emit_errors)
    return;

  RichLocation r (expr_locus);
  r.add_range (lhs);
  r.add_range (rhs);
  rust_error_at (r, "unsafe unsize coercion");
}

} // namespace Resolver
} // namespace Rust