// 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 // . #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 = 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 (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 (expected); try_result = coerce_unsafe_ptr (receiver, ptr, ptr->mutability ()); return !try_result.is_error (); } case TyTy::TypeKind::REF: { TyTy::ReferenceType *ptr = static_cast (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 (receiver); from_mutbl = ref->mutability (); element = ref->get_base (); } break; case TyTy::TypeKind::POINTER: { TyTy::PointerType *ref = static_cast (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 (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 (source); TyTy::ReferenceType *target_ref = static_cast (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 (source); TyTy::PointerType *target_ref = static_cast (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