Skip to content

Commit

Permalink
Account for object safety when suggesting Box<dyn Trait>
Browse files Browse the repository at this point in the history
  • Loading branch information
estebank committed Jan 16, 2020
1 parent d7a6212 commit 00e2626
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 19 deletions.
4 changes: 3 additions & 1 deletion src/librustc/traits/error_reporting/suggestions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,11 +696,13 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
format!("Box<{}{}>", if has_dyn { "" } else { "dyn " }, snippet),
));
err.multipart_suggestion(
"return a trait object instead",
"return a boxed trait object instead",
suggestions,
Applicability::MaybeIncorrect,
);
} else {
// This is currently not possible to trigger because E0038 takes precedence, but
// leave it in for completeness in case anything changes in an earlier stage.
err.note(&format!(
"if trait `{}` was object safe, you could return a trait object",
trait_obj,
Expand Down
7 changes: 7 additions & 0 deletions src/librustc_hir/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,13 @@ pub enum GenericBound<'hir> {
}

impl GenericBound<'_> {
pub fn trait_def_id(&self) -> Option<DefId> {
match self {
GenericBound::Trait(data, _) => Some(data.trait_ref.trait_def_id()),
_ => None,
}
}

pub fn span(&self) -> Span {
match self {
&GenericBound::Trait(ref t, ..) => t.span,
Expand Down
53 changes: 43 additions & 10 deletions src/librustc_typeck/check/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ use crate::check::{FnCtxt, Needs};
use rustc::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc::infer::{Coercion, InferOk, InferResult};
use rustc::session::parse::feature_err;
use rustc::traits::object_safety_violations;
use rustc::traits::{self, ObligationCause, ObligationCauseCode};
use rustc::ty::adjustment::{
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, PointerCast,
Expand All @@ -68,8 +69,8 @@ use rustc_error_codes::*;
use rustc_errors::{struct_span_err, DiagnosticBuilder};
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_span::{self, Span};
use rustc_span::symbol::sym;
use rustc_span::{self, Span};
use rustc_target::spec::abi::Abi;
use smallvec::{smallvec, SmallVec};
use std::ops::Deref;
Expand Down Expand Up @@ -1311,7 +1312,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
let mut err = fcx.report_mismatched_types(cause, expected, found, ty_err);

let mut pointing_at_return_type = false;
let mut return_sp = None;
let mut fn_output = None;

// Verify that this is a tail expression of a function, otherwise the
// label pointing out the cause for the type coercion will be wrong
Expand Down Expand Up @@ -1348,11 +1349,11 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
);
}
if !pointing_at_return_type {
return_sp = Some(fn_decl.output.span()); // `impl Trait` return type
fn_output = Some(&fn_decl.output); // `impl Trait` return type
}
}
if let (Some(sp), Some(return_sp)) = (fcx.ret_coercion_span.borrow().as_ref(), return_sp) {
self.add_impl_trait_explanation(&mut err, fcx, expected, *sp, return_sp);
if let (Some(sp), Some(fn_output)) = (fcx.ret_coercion_span.borrow().as_ref(), fn_output) {
self.add_impl_trait_explanation(&mut err, fcx, expected, *sp, fn_output);
}
err
}
Expand All @@ -1363,8 +1364,9 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
fcx: &FnCtxt<'a, 'tcx>,
expected: Ty<'tcx>,
sp: Span,
return_sp: Span,
fn_output: &hir::FunctionRetTy<'_>,
) {
let return_sp = fn_output.span();
err.span_label(return_sp, "expected because this return type...");
err.span_label(
sp,
Expand All @@ -1386,11 +1388,42 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
.unwrap_or_else(|_| "dyn Trait".to_string());
let mut snippet_iter = snippet.split_whitespace();
let has_impl = snippet_iter.next().map_or(false, |s| s == "impl");
// Only suggest `Box<dyn Trait>` if `Trait` in `impl Trait` is object safe.
let mut is_object_safe = false;
if let hir::FunctionRetTy::Return(ty) = fn_output {
// Get the return type.
if let hir::TyKind::Def(..) = ty.kind {
let ty = AstConv::ast_ty_to_ty(fcx, ty);
// Get the `impl Trait`'s `DefId`.
if let ty::Opaque(def_id, _) = ty.kind {
let hir_id = fcx.tcx.hir().as_local_hir_id(def_id).unwrap();
// Get the `impl Trait`'s `Item` so that we can get its trait bounds and
// get the `Trait`'s `DefId`.
if let hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds, .. }) =
fcx.tcx.hir().expect_item(hir_id).kind
{
// Are of this `impl Trait`'s traits object safe?
is_object_safe = bounds.iter().all(|bound| {
bound.trait_def_id().map_or(false, |def_id| {
object_safety_violations(fcx.tcx, def_id).is_empty()
})
})
}
}
}
};
if has_impl {
err.help(&format!(
"you can instead return a trait object using `Box<dyn {}>`",
&snippet[5..]
));
if is_object_safe {
err.help(&format!(
"you can instead return a boxed trait object using `Box<dyn {}>`",
&snippet[5..]
));
} else {
err.help(&format!(
"if the trait `{}` were object safe, you could return a boxed trait object",
&snippet[5..]
));
}
err.note(trait_obj_msg);
}
err.help("alternatively, create a new `enum` with a variant for each returned type");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ LL | fn bal() -> dyn Trait {
= note: if all the returned values were of the same type you could use `impl Trait` as the return type
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
= note: you can create a new `enum` with a variant for each returned type
help: return a trait object instead
help: return a boxed trait object instead
|
LL | fn bal() -> Box<dyn Trait> {
LL | if true {
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/impl-trait/equality.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ LL | 0_u32
|
= note: to return `impl Trait`, all returned values must be of the same type
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
= help: you can instead return a trait object using `Box<dyn Foo>`
= help: if the trait `Foo` were object safe, you could return a boxed trait object
= note: for information on trait objects, see <https://doc.rust-lang.org/book/ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types>
= help: alternatively, create a new `enum` with a variant for each returned type

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#![allow(bare_trait_objects)]
trait NotObjectSafe {
fn foo() -> Self;
}

struct A;
struct B;

impl NotObjectSafe for A {
fn foo() -> Self {
A
}
}

impl NotObjectSafe for B {
fn foo() -> Self {
B
}
}

fn car() -> dyn NotObjectSafe { //~ ERROR the trait `NotObjectSafe` cannot be made into an object
if true {
return A;
}
B
}

fn cat() -> Box<dyn NotObjectSafe> { //~ ERROR the trait `NotObjectSafe` cannot be made into an
if true {
return Box::new(A);
}
Box::new(B)
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
error[E0038]: the trait `NotObjectSafe` cannot be made into an object
--> $DIR/object-unsafe-trait-in-return-position-dyn-trait.rs:21:1
|
LL | fn foo() -> Self;
| --- associated function `foo` has no `self` parameter
...
LL | fn car() -> dyn NotObjectSafe {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `NotObjectSafe` cannot be made into an object

error[E0038]: the trait `NotObjectSafe` cannot be made into an object
--> $DIR/object-unsafe-trait-in-return-position-dyn-trait.rs:28:1
|
LL | fn foo() -> Self;
| --- associated function `foo` has no `self` parameter
...
LL | fn cat() -> Box<dyn NotObjectSafe> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `NotObjectSafe` cannot be made into an object

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0038`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
trait NotObjectSafe {
fn foo() -> Self;
}

trait ObjectSafe {
fn bar(&self);
}

struct A;
struct B;

impl NotObjectSafe for A {
fn foo() -> Self {
A
}
}

impl NotObjectSafe for B {
fn foo() -> Self {
B
}
}

impl ObjectSafe for A {
fn bar(&self) {}
}

impl ObjectSafe for B {
fn bar(&self) {}
}

fn can() -> impl NotObjectSafe {
if true {
return A;
}
B //~ ERROR mismatched types
}

fn cat() -> impl ObjectSafe {
if true {
return A;
}
B //~ ERROR mismatched types
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
error[E0308]: mismatched types
--> $DIR/object-unsafe-trait-in-return-position-impl-trait.rs:36:5
|
LL | fn can() -> impl NotObjectSafe {
| ------------------ expected because this return type...
LL | if true {
LL | return A;
| - ...is found to be `A` here
LL | }
LL | B
| ^ expected struct `A`, found struct `B`
|
= note: to return `impl Trait`, all returned values must be of the same type
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
= help: if the trait `NotObjectSafe` were object safe, you could return a boxed trait object
= note: for information on trait objects, see <https://doc.rust-lang.org/book/ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types>
= help: alternatively, create a new `enum` with a variant for each returned type

error[E0308]: mismatched types
--> $DIR/object-unsafe-trait-in-return-position-impl-trait.rs:43:5
|
LL | fn cat() -> impl ObjectSafe {
| --------------- expected because this return type...
LL | if true {
LL | return A;
| - ...is found to be `A` here
LL | }
LL | B
| ^ expected struct `A`, found struct `B`
|
= note: to return `impl Trait`, all returned values must be of the same type
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
= help: you can instead return a boxed trait object using `Box<dyn ObjectSafe>`
= note: for information on trait objects, see <https://doc.rust-lang.org/book/ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types>
= help: alternatively, create a new `enum` with a variant for each returned type

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0308`.
12 changes: 6 additions & 6 deletions src/test/ui/point-to-type-err-cause-on-impl-trait-return.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ LL | 1u32
|
= note: to return `impl Trait`, all returned values must be of the same type
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
= help: you can instead return a trait object using `Box<dyn std::fmt::Display>`
= help: you can instead return a boxed trait object using `Box<dyn std::fmt::Display>`
= note: for information on trait objects, see <https://doc.rust-lang.org/book/ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types>
= help: alternatively, create a new `enum` with a variant for each returned type

Expand All @@ -30,7 +30,7 @@ LL | return 1u32;
|
= note: to return `impl Trait`, all returned values must be of the same type
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
= help: you can instead return a trait object using `Box<dyn std::fmt::Display>`
= help: you can instead return a boxed trait object using `Box<dyn std::fmt::Display>`
= note: for information on trait objects, see <https://doc.rust-lang.org/book/ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types>
= help: alternatively, create a new `enum` with a variant for each returned type

Expand All @@ -48,7 +48,7 @@ LL | 1u32
|
= note: to return `impl Trait`, all returned values must be of the same type
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
= help: you can instead return a trait object using `Box<dyn std::fmt::Display>`
= help: you can instead return a boxed trait object using `Box<dyn std::fmt::Display>`
= note: for information on trait objects, see <https://doc.rust-lang.org/book/ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types>
= help: alternatively, create a new `enum` with a variant for each returned type

Expand Down Expand Up @@ -78,7 +78,7 @@ LL | _ => 1u32,
|
= note: to return `impl Trait`, all returned values must be of the same type
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
= help: you can instead return a trait object using `Box<dyn std::fmt::Display>`
= help: you can instead return a boxed trait object using `Box<dyn std::fmt::Display>`
= note: for information on trait objects, see <https://doc.rust-lang.org/book/ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types>
= help: alternatively, create a new `enum` with a variant for each returned type

Expand All @@ -98,7 +98,7 @@ LL | | }
|
= note: to return `impl Trait`, all returned values must be of the same type
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
= help: you can instead return a trait object using `Box<dyn std::fmt::Display>`
= help: you can instead return a boxed trait object using `Box<dyn std::fmt::Display>`
= note: for information on trait objects, see <https://doc.rust-lang.org/book/ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types>
= help: alternatively, create a new `enum` with a variant for each returned type

Expand All @@ -116,7 +116,7 @@ LL | 1u32
|
= note: to return `impl Trait`, all returned values must be of the same type
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
= help: you can instead return a trait object using `Box<dyn std::fmt::Display>`
= help: you can instead return a boxed trait object using `Box<dyn std::fmt::Display>`
= note: for information on trait objects, see <https://doc.rust-lang.org/book/ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types>
= help: alternatively, create a new `enum` with a variant for each returned type

Expand Down

0 comments on commit 00e2626

Please sign in to comment.