blob: bea40840fbffc028739930a9a89a6be8fd79f10f [file] [log] [blame]
// 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