Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 107 additions & 12 deletions compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@
//! // and are then unable to coerce `&7i32` to `&mut i32`.
//! ```

use std::ops::Deref;
use std::ops::{ControlFlow, Deref};

use rustc_errors::codes::*;
use rustc_errors::{Applicability, Diag, struct_span_code_err};
use rustc_hir as hir;
use rustc_hir::attrs::InlineAttr;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{self as hir, LangItem};
use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer;
use rustc_infer::infer::relate::RelateResult;
use rustc_infer::infer::{DefineOpaqueTypes, InferOk, InferResult, RegionVariableOrigin};
Expand All @@ -56,6 +56,8 @@ use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt};
use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Span};
use rustc_trait_selection::infer::InferCtxtExt as _;
use rustc_trait_selection::solve::inspect::{self, InferCtxtProofTreeExt, ProofTreeVisitor};
use rustc_trait_selection::solve::{Certainty, Goal, NoSolution};
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
use rustc_trait_selection::traits::{
self, ImplSource, NormalizeExt, ObligationCause, ObligationCauseCode, ObligationCtxt,
Expand Down Expand Up @@ -639,24 +641,52 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
Adjust::Pointer(PointerCoercion::Unsize),
)?;

let mut selcx = traits::SelectionContext::new(self);

// Create an obligation for `Source: CoerceUnsized<Target>`.
let cause = self.cause(self.cause.span, ObligationCauseCode::Coercion { source, target });
let pred = ty::TraitRef::new(self.tcx, coerce_unsized_did, [coerce_source, coerce_target]);
let obligation = Obligation::new(self.tcx, cause, self.fcx.param_env, pred);

if self.next_trait_solver() {
coercion.obligations.push(obligation);

if self
.infcx
.visit_proof_tree(
Goal::new(self.tcx, self.param_env, pred),
&mut CoerceVisitor { fcx: self.fcx, span: self.cause.span },
)
.is_break()
{
return Err(TypeError::Mismatch);
}
} else {
self.coerce_unsized_old_solver(
obligation,
&mut coercion,
coerce_unsized_did,
unsize_did,
)?;
}

Ok(coercion)
}

fn coerce_unsized_old_solver(
&self,
obligation: Obligation<'tcx, ty::Predicate<'tcx>>,
coercion: &mut InferOk<'tcx, (Vec<Adjustment<'tcx>>, Ty<'tcx>)>,
coerce_unsized_did: DefId,
unsize_did: DefId,
) -> Result<(), TypeError<'tcx>> {
let mut selcx = traits::SelectionContext::new(self);
// Use a FIFO queue for this custom fulfillment procedure.
//
// A Vec (or SmallVec) is not a natural choice for a queue. However,
// this code path is hot, and this queue usually has a max length of 1
// and almost never more than 3. By using a SmallVec we avoid an
// allocation, at the (very small) cost of (occasionally) having to
// shift subsequent elements down when removing the front element.
let mut queue: SmallVec<[PredicateObligation<'tcx>; 4]> = smallvec![Obligation::new(
self.tcx,
cause,
self.fcx.param_env,
ty::TraitRef::new(self.tcx, coerce_unsized_did, [coerce_source, coerce_target])
)];
let mut queue: SmallVec<[PredicateObligation<'tcx>; 4]> = smallvec![obligation];

// Keep resolving `CoerceUnsized` and `Unsize` predicates to avoid
// emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where
Expand Down Expand Up @@ -749,7 +779,6 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
// with the unsizing - the lack of a coercion should
// be silent, as it causes a type mismatch later.
}

Ok(Some(ImplSource::UserDefined(impl_source))) => {
queue.extend(impl_source.nested);
// Certain incoherent `CoerceUnsized` implementations may cause ICEs,
Expand All @@ -767,7 +796,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
}
}

Ok(coercion)
Ok(())
}

/// Applies reborrowing for `Pin`
Expand Down Expand Up @@ -2005,3 +2034,69 @@ impl AsCoercionSite for hir::Arm<'_> {
self.body
}
}

/// Recursively visit goals to decide whether an unsizing is possible.
/// `Break`s when it isn't, and an error should be raised.
/// `Continue`s when an unsizing ok based on an implementation of the `Unsize` trait / lang item.
struct CoerceVisitor<'a, 'tcx> {
fcx: &'a FnCtxt<'a, 'tcx>,
span: Span,
}

impl<'tcx> ProofTreeVisitor<'tcx> for CoerceVisitor<'_, 'tcx> {
type Result = ControlFlow<()>;

fn span(&self) -> Span {
self.span
}

fn visit_goal(&mut self, goal: &inspect::InspectGoal<'_, 'tcx>) -> Self::Result {
let Some(pred) = goal.goal().predicate.as_trait_clause() else {
return ControlFlow::Continue(());
};

// Make sure this predicate is referring to either an `Unsize` or `CoerceUnsized` trait,
// Otherwise there's nothing to do.
if !self.fcx.tcx.is_lang_item(pred.def_id(), LangItem::Unsize)
&& !self.fcx.tcx.is_lang_item(pred.def_id(), LangItem::CoerceUnsized)
{
return ControlFlow::Continue(());
}

match goal.result() {
// If we prove the `Unsize` or `CoerceUnsized` goal, continue recursing.
Ok(Certainty::Yes) => ControlFlow::Continue(()),
Err(NoSolution) => {
// Even if we find no solution, continue recursing if we find a single candidate
// for which we're shallowly certain it holds to get the right error source.
if let [only_candidate] = &goal.candidates()[..]
&& only_candidate.shallow_certainty() == Certainty::Yes
{
only_candidate.visit_nested_no_probe(self)
} else {
ControlFlow::Break(())
}
}
Ok(Certainty::Maybe { .. }) => {
// FIXME: structurally normalize?
if self.fcx.tcx.is_lang_item(pred.def_id(), LangItem::Unsize)
&& let ty::Dynamic(..) = pred.skip_binder().trait_ref.args.type_at(1).kind()
&& let ty::Infer(ty::TyVar(vid)) = *pred.self_ty().skip_binder().kind()
&& self.fcx.type_var_is_sized(vid)
{
// We get here when trying to unsize a type variable to a `dyn Trait`,
// knowing that that variable is sized. Unsizing definitely has to happen in that case.
// If the variable weren't sized, we may not need an unsizing coercion.
// In general, we don't want to add coercions too eagerly since it makes error messages much worse.
ControlFlow::Continue(())
} else if let Some(cand) = goal.unique_applicable_candidate()
&& cand.shallow_certainty() == Certainty::Yes
{
cand.visit_nested_no_probe(self)
} else {
ControlFlow::Break(())
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use rustc_middle::ty::{self, Ty, TypeVisitableExt};
use rustc_span::Span;
use rustc_trait_selection::solve::Certainty;
use rustc_trait_selection::solve::inspect::{
InspectConfig, InspectGoal, ProofTreeInferCtxtExt, ProofTreeVisitor,
InferCtxtProofTreeExt, InspectConfig, InspectGoal, ProofTreeVisitor,
};
use tracing::{debug, instrument, trace};

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_trait_selection/src/solve/fulfill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use tracing::instrument;
use self::derive_errors::*;
use super::Certainty;
use super::delegate::SolverDelegate;
use super::inspect::{self, ProofTreeInferCtxtExt};
use super::inspect::{self, InferCtxtProofTreeExt};
use crate::traits::{FulfillmentError, ScrubbedTraitError};

mod derive_errors;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use rustc_next_trait_solver::solve::{GoalEvaluation, SolverDelegateEvalExt as _}
use tracing::{instrument, trace};

use crate::solve::delegate::SolverDelegate;
use crate::solve::inspect::{self, ProofTreeInferCtxtExt, ProofTreeVisitor};
use crate::solve::inspect::{self, InferCtxtProofTreeExt, ProofTreeVisitor};
use crate::solve::{Certainty, deeply_normalize_for_diagnostics};
use crate::traits::{FulfillmentError, FulfillmentErrorCode, wf};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ pub trait ProofTreeVisitor<'tcx> {
fn visit_goal(&mut self, goal: &InspectGoal<'_, 'tcx>) -> Self::Result;
}

#[extension(pub trait ProofTreeInferCtxtExt<'tcx>)]
#[extension(pub trait InferCtxtProofTreeExt<'tcx>)]
impl<'tcx> InferCtxt<'tcx> {
fn visit_proof_tree<V: ProofTreeVisitor<'tcx>>(
&self,
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_trait_selection/src/solve/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use rustc_middle::{bug, span_bug};
use rustc_span::Span;
use thin_vec::thin_vec;

use crate::solve::inspect::{self, ProofTreeInferCtxtExt};
use crate::solve::inspect::{self, InferCtxtProofTreeExt};

#[extension(pub trait InferCtxtSelectExt<'tcx>)]
impl<'tcx> InferCtxt<'tcx> {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_trait_selection/src/traits/coherence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use tracing::{debug, instrument, warn};
use super::ObligationCtxt;
use crate::error_reporting::traits::suggest_new_overflow_limit;
use crate::infer::InferOk;
use crate::solve::inspect::{InspectGoal, ProofTreeInferCtxtExt, ProofTreeVisitor};
use crate::solve::inspect::{InferCtxtProofTreeExt, InspectGoal, ProofTreeVisitor};
use crate::solve::{SolverDelegate, deeply_normalize_for_diagnostics, inspect};
use crate::traits::query::evaluate_obligation::InferCtxtExt;
use crate::traits::select::IntercrateAmbiguityCause;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0277]: the size for values of type `T` cannot be known at compilation time
--> $DIR/dst-object-from-unsized-type.rs:8:23
--> $DIR/dst-object-from-unsized-type.rs:11:23
|
LL | fn test1<T: ?Sized + Foo>(t: &T) {
| - this type parameter needs to be `Sized`
Expand All @@ -14,7 +14,7 @@ LL + fn test1<T: Foo>(t: &T) {
|

error[E0277]: the size for values of type `T` cannot be known at compilation time
--> $DIR/dst-object-from-unsized-type.rs:13:23
--> $DIR/dst-object-from-unsized-type.rs:17:23
|
LL | fn test2<T: ?Sized + Foo>(t: &T) {
| - this type parameter needs to be `Sized`
Expand All @@ -29,7 +29,7 @@ LL + fn test2<T: Foo>(t: &T) {
|

error[E0277]: the size for values of type `str` cannot be known at compilation time
--> $DIR/dst-object-from-unsized-type.rs:18:28
--> $DIR/dst-object-from-unsized-type.rs:23:28
|
LL | let _: &[&dyn Foo] = &["hi"];
| ^^^^ doesn't have a size known at compile-time
Expand All @@ -38,7 +38,7 @@ LL | let _: &[&dyn Foo] = &["hi"];
= note: required for the cast from `&'static str` to `&dyn Foo`

error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
--> $DIR/dst-object-from-unsized-type.rs:23:23
--> $DIR/dst-object-from-unsized-type.rs:29:23
|
LL | let _: &dyn Foo = x as &dyn Foo;
| ^ doesn't have a size known at compile-time
Expand Down
57 changes: 57 additions & 0 deletions tests/ui/dst/dst-object-from-unsized-type.next.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
error[E0277]: the trait bound `&T: CoerceUnsized<&dyn Foo>` is not satisfied in `T`
--> $DIR/dst-object-from-unsized-type.rs:11:23
|
LL | fn test1<T: ?Sized + Foo>(t: &T) {
| - this type parameter needs to be `Sized`
LL | let u: &dyn Foo = t;
| ^ within `T`, the trait `Sized` is not implemented for `T`
|
= note: required because it appears within the type `T`
= note: required for `&T` to implement `CoerceUnsized<&dyn Foo>`
= note: required for the cast from `&T` to `&dyn Foo`
help: consider removing the `?Sized` bound to make the type parameter `Sized`
|
LL - fn test1<T: ?Sized + Foo>(t: &T) {
LL + fn test1<T: Foo>(t: &T) {
|

error[E0277]: the trait bound `&T: CoerceUnsized<&dyn Foo>` is not satisfied in `T`
--> $DIR/dst-object-from-unsized-type.rs:17:23
|
LL | fn test2<T: ?Sized + Foo>(t: &T) {
| - this type parameter needs to be `Sized`
LL | let v: &dyn Foo = t as &dyn Foo;
| ^ within `T`, the trait `Sized` is not implemented for `T`
|
= note: required because it appears within the type `T`
= note: required for `&T` to implement `CoerceUnsized<&dyn Foo>`
= note: required for the cast from `&T` to `&dyn Foo`
help: consider removing the `?Sized` bound to make the type parameter `Sized`
|
LL - fn test2<T: ?Sized + Foo>(t: &T) {
LL + fn test2<T: Foo>(t: &T) {
|

error[E0277]: the trait bound `&str: CoerceUnsized<&dyn Foo>` is not satisfied in `str`
--> $DIR/dst-object-from-unsized-type.rs:23:28
|
LL | let _: &[&dyn Foo] = &["hi"];
| ^^^^ within `str`, the trait `Sized` is not implemented for `str`
|
= note: `str` is considered to contain a `[u8]` slice for auto trait purposes
= note: required for `&str` to implement `CoerceUnsized<&dyn Foo>`
= note: required for the cast from `&'static str` to `&dyn Foo`

error[E0277]: the trait bound `&[u8]: CoerceUnsized<&dyn Foo>` is not satisfied in `[u8]`
--> $DIR/dst-object-from-unsized-type.rs:29:23
|
LL | let _: &dyn Foo = x as &dyn Foo;
| ^ within `[u8]`, the trait `Sized` is not implemented for `[u8]`
|
= note: required because it appears within the type `[u8]`
= note: required for `&[u8]` to implement `CoerceUnsized<&dyn Foo>`
= note: required for the cast from `&[u8]` to `&dyn Foo`

error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0277`.
15 changes: 11 additions & 4 deletions tests/ui/dst/dst-object-from-unsized-type.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
// Test that we cannot create objects from unsized types.
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver

trait Foo { fn foo(&self) {} }
impl Foo for str {}
impl Foo for [u8] {}

fn test1<T: ?Sized + Foo>(t: &T) {
let u: &dyn Foo = t;
//~^ ERROR the size for values of type
//[current]~^ ERROR the size for values of type
//[next]~^^ ERROR the trait bound `&T: CoerceUnsized<&dyn Foo>` is not satisfied in `T`
}

fn test2<T: ?Sized + Foo>(t: &T) {
let v: &dyn Foo = t as &dyn Foo;
//~^ ERROR the size for values of type
//[current]~^ ERROR the size for values of type
//[next]~^^ ERROR the trait bound `&T: CoerceUnsized<&dyn Foo>` is not satisfied in `T`
}

fn test3() {
let _: &[&dyn Foo] = &["hi"];
//~^ ERROR the size for values of type
//[current]~^ ERROR the size for values of type
//[next]~^^ ERROR the trait bound `&str: CoerceUnsized<&dyn Foo>` is not satisfied in `str`
}

fn test4(x: &[u8]) {
let _: &dyn Foo = x as &dyn Foo;
//~^ ERROR the size for values of type
//[current]~^ ERROR the size for values of type
//[next]~^^ ERROR the trait bound `&[u8]: CoerceUnsized<&dyn Foo>` is not satisfied in `[u8]`
}

fn main() { }
13 changes: 13 additions & 0 deletions tests/ui/traits/next-solver/unsize-goal-mismatch-2.next.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
error[E0283]: type annotations needed: cannot satisfy `dyn Trait<&()>: Unsize<dyn Super<&()>>`
--> $DIR/unsize-goal-mismatch-2.rs:15:5
|
LL | x
| ^
|
= note: cannot satisfy `dyn Trait<&()>: Unsize<dyn Super<&()>>`
= note: required for `Box<dyn Trait<&()>>` to implement `CoerceUnsized<Box<dyn Super<&()>>>`
= note: required for the cast from `Box<(dyn Trait<&'a ()> + 'static)>` to `Box<(dyn Super<&'a ()> + 'static)>`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0283`.
19 changes: 19 additions & 0 deletions tests/ui/traits/next-solver/unsize-goal-mismatch-2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
//@[current] check-pass
// Test from trait-system-refactor-initiative#241:
// Used to ICE in mir typeck because of ambiguity in the new solver.
// The wrong (first) trait bound was selected.
// This is fixed with new logic for unsizing coercions
// that's independent from that of the old solver, which this test verifies.

trait Super<T> {}
trait Trait<T>: Super<T> + for<'hr> Super<&'hr ()> {}

fn foo<'a>(x: Box<dyn Trait<&'a ()>>) -> Box<dyn Super<&'a ()>> {
x
//[next]~^ ERROR type annotations needed: cannot satisfy `dyn Trait<&()>: Unsize<dyn Super<&()>>`
}

fn main() {}
Loading
Loading