Skip to content

Commit 6fd5641

Browse files
committed
Specific error for unsized dyn Trait return type
Suggest `impl Trait` when possible, and `Box<dyn Trait>` otherwise.
1 parent 9fe05e9 commit 6fd5641

File tree

7 files changed

+512
-2
lines changed

7 files changed

+512
-2
lines changed

src/librustc/traits/error_reporting.rs

Lines changed: 199 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// ignore-tidy-filelength
12
use super::{
23
ConstEvalFailure, EvaluationResult, FulfillmentError, FulfillmentErrorCode,
34
MismatchedProjectionTypes, ObjectSafetyViolation, Obligation, ObligationCause,
@@ -22,9 +23,12 @@ use crate::ty::TypeckTables;
2223
use crate::ty::{self, AdtKind, DefIdTree, ToPolyTraitRef, ToPredicate, Ty, TyCtxt, TypeFoldable};
2324

2425
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
25-
use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder, Style};
26+
use rustc_errors::{
27+
error_code, pluralize, struct_span_err, Applicability, DiagnosticBuilder, Style,
28+
};
2629
use rustc_hir as hir;
2730
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
31+
use rustc_hir::intravisit::Visitor;
2832
use rustc_hir::Node;
2933
use rustc_span::source_map::SourceMap;
3034
use rustc_span::symbol::{kw, sym};
@@ -758,7 +762,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
758762
)),
759763
Some(
760764
"the question mark operation (`?`) implicitly performs a \
761-
conversion on the error value using the `From` trait"
765+
conversion on the error value using the `From` trait"
762766
.to_owned(),
763767
),
764768
)
@@ -835,6 +839,10 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
835839
self.suggest_remove_reference(&obligation, &mut err, &trait_ref);
836840
self.suggest_semicolon_removal(&obligation, &mut err, span, &trait_ref);
837841
self.note_version_mismatch(&mut err, &trait_ref);
842+
if self.suggest_impl_trait(&mut err, span, &obligation, &trait_ref) {
843+
err.emit();
844+
return;
845+
}
838846

839847
// Try to report a help message
840848
if !trait_ref.has_infer_types()
@@ -1696,6 +1704,195 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
16961704
}
16971705
}
16981706

1707+
fn suggest_impl_trait(
1708+
&self,
1709+
err: &mut DiagnosticBuilder<'tcx>,
1710+
span: Span,
1711+
obligation: &PredicateObligation<'tcx>,
1712+
trait_ref: &ty::Binder<ty::TraitRef<'tcx>>,
1713+
) -> bool {
1714+
if let ObligationCauseCode::SizedReturnType = obligation.cause.code.peel_derives() {
1715+
} else {
1716+
return false;
1717+
}
1718+
1719+
let hir = self.tcx.hir();
1720+
let parent_node = hir.get_parent_node(obligation.cause.body_id);
1721+
let node = hir.find(parent_node);
1722+
if let Some(hir::Node::Item(hir::Item {
1723+
kind: hir::ItemKind::Fn(sig, _, body_id), ..
1724+
})) = node
1725+
{
1726+
let body = hir.body(*body_id);
1727+
let trait_ref = self.resolve_vars_if_possible(trait_ref);
1728+
let ty = trait_ref.skip_binder().self_ty();
1729+
if let ty::Dynamic(..) = ty.kind {
1730+
} else {
1731+
// We only want to suggest `impl Trait` to `dyn Trait`s.
1732+
// For example, `fn foo() -> str` needs to be filtered out.
1733+
return false;
1734+
}
1735+
// Use `TypeVisitor` instead of the output type directly to find the span of `ty` for
1736+
// cases like `fn foo() -> (dyn Trait, i32) {}`.
1737+
// Recursively look for `TraitObject` types and if there's only one, use that span to
1738+
// suggest `impl Trait`.
1739+
1740+
struct ReturnsVisitor<'v>(Vec<&'v hir::Expr<'v>>);
1741+
1742+
impl<'v> Visitor<'v> for ReturnsVisitor<'v> {
1743+
type Map = rustc::hir::map::Map<'v>;
1744+
1745+
fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap<'_, Self::Map> {
1746+
hir::intravisit::NestedVisitorMap::None
1747+
}
1748+
1749+
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
1750+
match ex.kind {
1751+
hir::ExprKind::Ret(Some(ex)) => self.0.push(ex),
1752+
_ => {}
1753+
}
1754+
hir::intravisit::walk_expr(self, ex);
1755+
}
1756+
1757+
fn visit_body(&mut self, body: &'v hir::Body<'v>) {
1758+
if body.generator_kind().is_none() {
1759+
if let hir::ExprKind::Block(block, None) = body.value.kind {
1760+
if let Some(expr) = block.expr {
1761+
self.0.push(expr);
1762+
}
1763+
}
1764+
}
1765+
hir::intravisit::walk_body(self, body);
1766+
}
1767+
}
1768+
1769+
// Visit to make sure there's a single `return` type to suggest `impl Trait`,
1770+
// otherwise suggest using `Box<dyn Trait>` or an enum.
1771+
let mut visitor = ReturnsVisitor(vec![]);
1772+
visitor.visit_body(&body);
1773+
1774+
let tables = self.in_progress_tables.map(|t| t.borrow()).unwrap();
1775+
1776+
if let hir::FunctionRetTy::Return(ret_ty) = sig.decl.output {
1777+
let mut all_returns_conform_to_trait = true;
1778+
let mut all_returns_have_same_type = true;
1779+
let mut last_ty = None;
1780+
if let Some(ty_ret_ty) = tables.node_type_opt(ret_ty.hir_id) {
1781+
let cause = ObligationCause::misc(ret_ty.span, ret_ty.hir_id);
1782+
if let ty::Dynamic(predicates, _) = &ty_ret_ty.kind {
1783+
for predicate in predicates.iter() {
1784+
for expr in &visitor.0 {
1785+
if let Some(returned_ty) = tables.node_type_opt(expr.hir_id) {
1786+
if let Some(ty) = last_ty {
1787+
all_returns_have_same_type &= ty == returned_ty;
1788+
}
1789+
last_ty = Some(returned_ty);
1790+
1791+
let param_env = ty::ParamEnv::empty();
1792+
let pred = predicate.with_self_ty(self.tcx, returned_ty);
1793+
let obligation =
1794+
Obligation::new(cause.clone(), param_env, pred);
1795+
all_returns_conform_to_trait &=
1796+
self.predicate_may_hold(&obligation);
1797+
}
1798+
}
1799+
}
1800+
}
1801+
} else {
1802+
// We still want to verify whether all the return types conform to each other.
1803+
for expr in &visitor.0 {
1804+
if let Some(returned_ty) = tables.node_type_opt(expr.hir_id) {
1805+
if let Some(ty) = last_ty {
1806+
all_returns_have_same_type &= ty == returned_ty;
1807+
}
1808+
last_ty = Some(returned_ty);
1809+
}
1810+
}
1811+
}
1812+
1813+
if let (true, hir::TyKind::TraitObject(..), Ok(snippet), true, Some(last_ty)) = (
1814+
ret_ty.span.overlaps(span),
1815+
&ret_ty.kind,
1816+
self.tcx.sess.source_map().span_to_snippet(ret_ty.span),
1817+
all_returns_conform_to_trait,
1818+
last_ty,
1819+
) {
1820+
err.code = Some(error_code!(E0746));
1821+
err.set_primary_message(
1822+
"return type cannot have a bare trait because it must be `Sized`",
1823+
);
1824+
err.children.clear();
1825+
let impl_trait_msg = "for information on `impl Trait`, see \
1826+
<https://doc.rust-lang.org/book/ch10-02-traits.html\
1827+
#returning-types-that-implement-traits>";
1828+
let trait_obj_msg = "for information on trait objects, see \
1829+
<https://doc.rust-lang.org/book/ch17-02-trait-objects.html\
1830+
#using-trait-objects-that-allow-for-values-of-different-types>";
1831+
let has_dyn = snippet.split_whitespace().next().map_or(false, |s| s == "dyn");
1832+
let trait_obj = if has_dyn { &snippet[4..] } else { &snippet[..] };
1833+
if all_returns_have_same_type {
1834+
err.span_suggestion(
1835+
ret_ty.span,
1836+
&format!(
1837+
"you can use the `impl Trait` feature \
1838+
in the return type because all the return paths are of type \
1839+
`{}`, which implements `dyn {}`",
1840+
last_ty, trait_obj,
1841+
),
1842+
format!("impl {}", trait_obj),
1843+
Applicability::MaybeIncorrect,
1844+
);
1845+
err.note(impl_trait_msg);
1846+
} else {
1847+
let mut suggestions = visitor
1848+
.0
1849+
.iter()
1850+
.map(|expr| {
1851+
(
1852+
expr.span,
1853+
format!(
1854+
"Box::new({})",
1855+
self.tcx
1856+
.sess
1857+
.source_map()
1858+
.span_to_snippet(expr.span)
1859+
.unwrap()
1860+
),
1861+
)
1862+
})
1863+
.collect::<Vec<_>>();
1864+
suggestions.push((
1865+
ret_ty.span,
1866+
format!("Box<{}{}>", if has_dyn { "" } else { "dyn " }, snippet),
1867+
));
1868+
err.multipart_suggestion(
1869+
"if the performance implications are acceptable, you can return a \
1870+
trait object",
1871+
suggestions,
1872+
Applicability::MaybeIncorrect,
1873+
);
1874+
err.span_help(
1875+
visitor.0.iter().map(|expr| expr.span).collect::<Vec<_>>(),
1876+
&format!(
1877+
"if all the returned values were of the same type you could use \
1878+
`impl {}` as the return type",
1879+
trait_obj,
1880+
),
1881+
);
1882+
err.help(
1883+
"alternatively, you can always create a new `enum` with a variant \
1884+
for each returned type",
1885+
);
1886+
err.note(impl_trait_msg);
1887+
err.note(trait_obj_msg);
1888+
}
1889+
return true;
1890+
}
1891+
}
1892+
}
1893+
false
1894+
}
1895+
16991896
/// Given some node representing a fn-like thing in the HIR map,
17001897
/// returns a span and `ArgKind` information that describes the
17011898
/// arguments it expects. This can be supplied to

src/librustc/traits/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,17 @@ impl<'tcx> ObligationCause<'tcx> {
11711171
}
11721172
}
11731173

1174+
impl<'tcx> ObligationCauseCode<'tcx> {
1175+
pub fn peel_derives(&self) -> &Self {
1176+
match self {
1177+
BuiltinDerivedObligation(cause) | ImplDerivedObligation(cause) => {
1178+
cause.parent_code.peel_derives()
1179+
}
1180+
_ => self,
1181+
}
1182+
}
1183+
}
1184+
11741185
impl<'tcx, N> Vtable<'tcx, N> {
11751186
pub fn nested_obligations(self) -> Vec<N> {
11761187
match self {

src/librustc_error_codes/error_codes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,4 +608,5 @@ E0745: include_str!("./error_codes/E0745.md"),
608608
E0726, // non-explicit (not `'_`) elided lifetime in unsupported position
609609
E0727, // `async` generators are not yet supported
610610
E0739, // invalid track_caller application/syntax
611+
E0746, // `dyn Trait` return type
611612
}

src/test/ui/error-codes/E0746.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
struct Struct;
2+
trait Trait {}
3+
impl Trait for Struct {}
4+
impl Trait for u32 {}
5+
6+
fn foo() -> dyn Trait { Struct }
7+
//~^ ERROR E0746
8+
//~| ERROR E0308
9+
10+
fn bar() -> dyn Trait { //~ ERROR E0746
11+
if true {
12+
return 0; //~ ERROR E0308
13+
}
14+
42 //~ ERROR E0308
15+
}
16+
17+
fn main() {}

src/test/ui/error-codes/E0746.stderr

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/E0746.rs:6:25
3+
|
4+
LL | fn foo() -> dyn Trait { Struct }
5+
| --------- ^^^^^^ expected trait `Trait`, found struct `Struct`
6+
| |
7+
| expected `(dyn Trait + 'static)` because of return type
8+
|
9+
= note: expected trait object `(dyn Trait + 'static)`
10+
found struct `Struct`
11+
12+
error[E0746]: return type cannot have a bare trait because it must be `Sized`
13+
--> $DIR/E0746.rs:6:13
14+
|
15+
LL | fn foo() -> dyn Trait { Struct }
16+
| ^^^^^^^^^ doesn't have a size known at compile-time
17+
|
18+
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
19+
help: you can use the `impl Trait` feature in the return type because all the return paths are of type `Struct`, which implements `dyn Trait`
20+
|
21+
LL | fn foo() -> impl Trait { Struct }
22+
| ^^^^^^^^^^
23+
24+
error[E0746]: return type cannot have a bare trait because it must be `Sized`
25+
--> $DIR/E0746.rs:10:13
26+
|
27+
LL | fn bar() -> dyn Trait {
28+
| ^^^^^^^^^ doesn't have a size known at compile-time
29+
|
30+
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
31+
help: you can use the `impl Trait` feature in the return type because all the return paths are of type `{integer}`, which implements `dyn Trait`
32+
|
33+
LL | fn bar() -> impl Trait {
34+
| ^^^^^^^^^^
35+
36+
error[E0308]: mismatched types
37+
--> $DIR/E0746.rs:12:16
38+
|
39+
LL | fn bar() -> dyn Trait {
40+
| --------- expected `(dyn Trait + 'static)` because of return type
41+
LL | if true {
42+
LL | return 0;
43+
| ^ expected trait `Trait`, found integer
44+
|
45+
= note: expected trait object `(dyn Trait + 'static)`
46+
found type `{integer}`
47+
48+
error[E0308]: mismatched types
49+
--> $DIR/E0746.rs:14:5
50+
|
51+
LL | fn bar() -> dyn Trait {
52+
| --------- expected `(dyn Trait + 'static)` because of return type
53+
...
54+
LL | 42
55+
| ^^ expected trait `Trait`, found integer
56+
|
57+
= note: expected trait object `(dyn Trait + 'static)`
58+
found type `{integer}`
59+
60+
error: aborting due to 5 previous errors
61+
62+
For more information about this error, try `rustc --explain E0308`.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#![allow(bare_trait_objects)]
2+
struct Struct;
3+
trait Trait {}
4+
impl Trait for Struct {}
5+
impl Trait for u32 {}
6+
7+
fn fuz() -> (usize, Trait) { (42, Struct) }
8+
//~^ ERROR E0277
9+
//~| ERROR E0308
10+
fn bar() -> (usize, dyn Trait) { (42, Struct) }
11+
//~^ ERROR E0277
12+
//~| ERROR E0308
13+
fn bap() -> Trait { Struct }
14+
//~^ ERROR E0746
15+
//~| ERROR E0308
16+
fn ban() -> dyn Trait { Struct }
17+
//~^ ERROR E0746
18+
//~| ERROR E0308
19+
fn bak() -> dyn Trait { unimplemented!() } //~ ERROR E0277
20+
// Suggest using `Box<dyn Trait>`
21+
fn bal() -> dyn Trait { //~ ERROR E0746
22+
if true {
23+
return Struct; //~ ERROR E0308
24+
}
25+
42 //~ ERROR E0308
26+
}
27+
28+
// Suggest using `impl Trait`
29+
fn bat() -> dyn Trait { //~ ERROR E0746
30+
if true {
31+
return 0; //~ ERROR E0308
32+
}
33+
42 //~ ERROR E0308
34+
}
35+
36+
fn main() {}

0 commit comments

Comments
 (0)