Skip to content

Commit 3d8778d

Browse files
committed
Auto merge of #68522 - estebank:impl-trait-sugg-2, r=oli-obk
Further improve `impl Trait`/`dyn Trait` suggestions After reading [_Returning Trait Objects_ by Bryce Fisher-Fleig](https://bryce.fisher-fleig.org/blog/returning-trait-objects/), [I noticed that](https://www.reddit.com/r/rust/comments/esueur/returning_trait_objects/ffczl4k/) #68195 had a few bugs due to not ignoring `ty::Error`. - Account for `ty::Error`. - Account for `if`/`else` and `match` blocks when pointing at return types and referencing their types. - Increase the multiline suggestion output from 6 lines to 20.
2 parents fe1e815 + 16709f0 commit 3d8778d

File tree

5 files changed

+262
-23
lines changed

5 files changed

+262
-23
lines changed

src/librustc/traits/error_reporting/suggestions.rs

+52-17
Original file line numberDiff line numberDiff line change
@@ -601,17 +601,24 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
601601

602602
// Visit to make sure there's a single `return` type to suggest `impl Trait`,
603603
// otherwise suggest using `Box<dyn Trait>` or an enum.
604-
let mut visitor = ReturnsVisitor(vec![]);
604+
let mut visitor = ReturnsVisitor::default();
605605
visitor.visit_body(&body);
606606

607607
let tables = self.in_progress_tables.map(|t| t.borrow()).unwrap();
608608

609-
let mut ret_types = visitor.0.iter().filter_map(|expr| tables.node_type_opt(expr.hir_id));
610-
let (last_ty, all_returns_have_same_type) =
611-
ret_types.clone().fold((None, true), |(last_ty, mut same), returned_ty| {
612-
same &= last_ty.map_or(true, |ty| ty == returned_ty);
613-
(Some(returned_ty), same)
614-
});
609+
let mut ret_types = visitor
610+
.returns
611+
.iter()
612+
.filter_map(|expr| tables.node_type_opt(expr.hir_id))
613+
.map(|ty| self.resolve_vars_if_possible(&ty));
614+
let (last_ty, all_returns_have_same_type) = ret_types.clone().fold(
615+
(None, true),
616+
|(last_ty, mut same): (std::option::Option<Ty<'_>>, bool), ty| {
617+
let ty = self.resolve_vars_if_possible(&ty);
618+
same &= last_ty.map_or(true, |last_ty| last_ty == ty) && ty.kind != ty::Error;
619+
(Some(ty), same)
620+
},
621+
);
615622
let all_returns_conform_to_trait =
616623
if let Some(ty_ret_ty) = tables.node_type_opt(ret_ty.hir_id) {
617624
match ty_ret_ty.kind {
@@ -626,7 +633,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
626633
})
627634
})
628635
}
629-
_ => true,
636+
_ => false,
630637
}
631638
} else {
632639
true
@@ -676,7 +683,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
676683
// Suggest `-> Box<dyn Trait>` and `Box::new(returned_value)`.
677684
// Get all the return values and collect their span and suggestion.
678685
let mut suggestions = visitor
679-
.0
686+
.returns
680687
.iter()
681688
.map(|expr| {
682689
(
@@ -736,10 +743,10 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
736743
{
737744
let body = hir.body(*body_id);
738745
// Point at all the `return`s in the function as they have failed trait bounds.
739-
let mut visitor = ReturnsVisitor(vec![]);
746+
let mut visitor = ReturnsVisitor::default();
740747
visitor.visit_body(&body);
741748
let tables = self.in_progress_tables.map(|t| t.borrow()).unwrap();
742-
for expr in &visitor.0 {
749+
for expr in &visitor.returns {
743750
if let Some(returned_ty) = tables.node_type_opt(expr.hir_id) {
744751
let ty = self.resolve_vars_if_possible(&returned_ty);
745752
err.span_label(expr.span, &format!("this returned value is of type `{}`", ty));
@@ -1716,7 +1723,11 @@ pub fn suggest_constraining_type_param(
17161723

17171724
/// Collect all the returned expressions within the input expression.
17181725
/// Used to point at the return spans when we want to suggest some change to them.
1719-
struct ReturnsVisitor<'v>(Vec<&'v hir::Expr<'v>>);
1726+
#[derive(Default)]
1727+
struct ReturnsVisitor<'v> {
1728+
returns: Vec<&'v hir::Expr<'v>>,
1729+
in_block_tail: bool,
1730+
}
17201731

17211732
impl<'v> Visitor<'v> for ReturnsVisitor<'v> {
17221733
type Map = rustc::hir::map::Map<'v>;
@@ -1726,17 +1737,41 @@ impl<'v> Visitor<'v> for ReturnsVisitor<'v> {
17261737
}
17271738

17281739
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
1729-
if let hir::ExprKind::Ret(Some(ex)) = ex.kind {
1730-
self.0.push(ex);
1740+
// Visit every expression to detect `return` paths, either through the function's tail
1741+
// expression or `return` statements. We walk all nodes to find `return` statements, but
1742+
// we only care about tail expressions when `in_block_tail` is `true`, which means that
1743+
// they're in the return path of the function body.
1744+
match ex.kind {
1745+
hir::ExprKind::Ret(Some(ex)) => {
1746+
self.returns.push(ex);
1747+
}
1748+
hir::ExprKind::Block(block, _) if self.in_block_tail => {
1749+
self.in_block_tail = false;
1750+
for stmt in block.stmts {
1751+
hir::intravisit::walk_stmt(self, stmt);
1752+
}
1753+
self.in_block_tail = true;
1754+
if let Some(expr) = block.expr {
1755+
self.visit_expr(expr);
1756+
}
1757+
}
1758+
hir::ExprKind::Match(_, arms, _) if self.in_block_tail => {
1759+
for arm in arms {
1760+
self.visit_expr(arm.body);
1761+
}
1762+
}
1763+
// We need to walk to find `return`s in the entire body.
1764+
_ if !self.in_block_tail => hir::intravisit::walk_expr(self, ex),
1765+
_ => self.returns.push(ex),
17311766
}
1732-
hir::intravisit::walk_expr(self, ex);
17331767
}
17341768

17351769
fn visit_body(&mut self, body: &'v hir::Body<'v>) {
1770+
assert!(!self.in_block_tail);
17361771
if body.generator_kind().is_none() {
17371772
if let hir::ExprKind::Block(block, None) = body.value.kind {
1738-
if let Some(expr) = block.expr {
1739-
self.0.push(expr);
1773+
if block.expr.is_some() {
1774+
self.in_block_tail = true;
17401775
}
17411776
}
17421777
}

src/librustc_errors/emitter.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -456,9 +456,14 @@ impl Emitter for SilentEmitter {
456456
fn emit_diagnostic(&mut self, _: &Diagnostic) {}
457457
}
458458

459-
/// maximum number of lines we will print for each error; arbitrary.
459+
/// Maximum number of lines we will print for each error; arbitrary.
460460
pub const MAX_HIGHLIGHT_LINES: usize = 6;
461-
/// maximum number of suggestions to be shown
461+
/// Maximum number of lines we will print for a multiline suggestion; arbitrary.
462+
///
463+
/// This should be replaced with a more involved mechanism to output multiline suggestions that
464+
/// more closely mimmics the regular diagnostic output, where irrelevant code lines are elided.
465+
pub const MAX_SUGGESTION_HIGHLIGHT_LINES: usize = 6;
466+
/// Maximum number of suggestions to be shown
462467
///
463468
/// Arbitrary, but taken from trait import suggestion limit
464469
pub const MAX_SUGGESTIONS: usize = 4;
@@ -1521,7 +1526,7 @@ impl EmitterWriter {
15211526
draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1);
15221527
let mut line_pos = 0;
15231528
let mut lines = complete.lines();
1524-
for line in lines.by_ref().take(MAX_HIGHLIGHT_LINES) {
1529+
for line in lines.by_ref().take(MAX_SUGGESTION_HIGHLIGHT_LINES) {
15251530
// Print the span column to avoid confusion
15261531
buffer.puts(
15271532
row_num,

src/test/ui/impl-trait/dyn-trait-return-should-be-impl-trait.rs

+40
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,39 @@ fn bal() -> dyn Trait { //~ ERROR E0746
2222
}
2323
42
2424
}
25+
fn bax() -> dyn Trait { //~ ERROR E0746
26+
if true {
27+
Struct
28+
} else {
29+
42
30+
}
31+
}
32+
fn bam() -> Box<dyn Trait> {
33+
if true {
34+
return Struct; //~ ERROR mismatched types
35+
}
36+
42 //~ ERROR mismatched types
37+
}
38+
fn baq() -> Box<dyn Trait> {
39+
if true {
40+
return 0; //~ ERROR mismatched types
41+
}
42+
42 //~ ERROR mismatched types
43+
}
44+
fn baz() -> Box<dyn Trait> {
45+
if true {
46+
Struct //~ ERROR mismatched types
47+
} else {
48+
42 //~ ERROR mismatched types
49+
}
50+
}
51+
fn baw() -> Box<dyn Trait> {
52+
if true {
53+
0 //~ ERROR mismatched types
54+
} else {
55+
42 //~ ERROR mismatched types
56+
}
57+
}
2558

2659
// Suggest using `impl Trait`
2760
fn bat() -> dyn Trait { //~ ERROR E0746
@@ -30,5 +63,12 @@ fn bat() -> dyn Trait { //~ ERROR E0746
3063
}
3164
42
3265
}
66+
fn bay() -> dyn Trait { //~ ERROR E0746
67+
if true {
68+
0
69+
} else {
70+
42
71+
}
72+
}
3373

3474
fn main() {}

src/test/ui/impl-trait/dyn-trait-return-should-be-impl-trait.stderr

+161-2
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,154 @@ LL | Box::new(42)
9696
|
9797

9898
error[E0746]: return type cannot have an unboxed trait object
99-
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:27:13
99+
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:25:13
100+
|
101+
LL | fn bax() -> dyn Trait {
102+
| ^^^^^^^^^ doesn't have a size known at compile-time
103+
|
104+
= 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>
105+
= note: if all the returned values were of the same type you could use `impl Trait` as the return type
106+
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
107+
= note: you can create a new `enum` with a variant for each returned type
108+
help: return a boxed trait object instead
109+
|
110+
LL | fn bax() -> Box<dyn Trait> {
111+
LL | if true {
112+
LL | Box::new(Struct)
113+
LL | } else {
114+
LL | Box::new(42)
115+
|
116+
117+
error[E0308]: mismatched types
118+
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:34:16
119+
|
120+
LL | fn bam() -> Box<dyn Trait> {
121+
| -------------- expected `std::boxed::Box<(dyn Trait + 'static)>` because of return type
122+
LL | if true {
123+
LL | return Struct;
124+
| ^^^^^^
125+
| |
126+
| expected struct `std::boxed::Box`, found struct `Struct`
127+
| help: store this in the heap by calling `Box::new`: `Box::new(Struct)`
128+
|
129+
= note: expected struct `std::boxed::Box<(dyn Trait + 'static)>`
130+
found struct `Struct`
131+
= note: for more on the distinction between the stack and the heap, read https://doc.rust-lang.org/book/ch15-01-box.html, https://doc.rust-lang.org/rust-by-example/std/box.html, and https://doc.rust-lang.org/std/boxed/index.html
132+
133+
error[E0308]: mismatched types
134+
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:36:5
135+
|
136+
LL | fn bam() -> Box<dyn Trait> {
137+
| -------------- expected `std::boxed::Box<(dyn Trait + 'static)>` because of return type
138+
...
139+
LL | 42
140+
| ^^
141+
| |
142+
| expected struct `std::boxed::Box`, found integer
143+
| help: store this in the heap by calling `Box::new`: `Box::new(42)`
144+
|
145+
= note: expected struct `std::boxed::Box<(dyn Trait + 'static)>`
146+
found type `{integer}`
147+
= note: for more on the distinction between the stack and the heap, read https://doc.rust-lang.org/book/ch15-01-box.html, https://doc.rust-lang.org/rust-by-example/std/box.html, and https://doc.rust-lang.org/std/boxed/index.html
148+
149+
error[E0308]: mismatched types
150+
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:40:16
151+
|
152+
LL | fn baq() -> Box<dyn Trait> {
153+
| -------------- expected `std::boxed::Box<(dyn Trait + 'static)>` because of return type
154+
LL | if true {
155+
LL | return 0;
156+
| ^
157+
| |
158+
| expected struct `std::boxed::Box`, found integer
159+
| help: store this in the heap by calling `Box::new`: `Box::new(0)`
160+
|
161+
= note: expected struct `std::boxed::Box<(dyn Trait + 'static)>`
162+
found type `{integer}`
163+
= note: for more on the distinction between the stack and the heap, read https://doc.rust-lang.org/book/ch15-01-box.html, https://doc.rust-lang.org/rust-by-example/std/box.html, and https://doc.rust-lang.org/std/boxed/index.html
164+
165+
error[E0308]: mismatched types
166+
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:42:5
167+
|
168+
LL | fn baq() -> Box<dyn Trait> {
169+
| -------------- expected `std::boxed::Box<(dyn Trait + 'static)>` because of return type
170+
...
171+
LL | 42
172+
| ^^
173+
| |
174+
| expected struct `std::boxed::Box`, found integer
175+
| help: store this in the heap by calling `Box::new`: `Box::new(42)`
176+
|
177+
= note: expected struct `std::boxed::Box<(dyn Trait + 'static)>`
178+
found type `{integer}`
179+
= note: for more on the distinction between the stack and the heap, read https://doc.rust-lang.org/book/ch15-01-box.html, https://doc.rust-lang.org/rust-by-example/std/box.html, and https://doc.rust-lang.org/std/boxed/index.html
180+
181+
error[E0308]: mismatched types
182+
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:46:9
183+
|
184+
LL | fn baz() -> Box<dyn Trait> {
185+
| -------------- expected `std::boxed::Box<(dyn Trait + 'static)>` because of return type
186+
LL | if true {
187+
LL | Struct
188+
| ^^^^^^
189+
| |
190+
| expected struct `std::boxed::Box`, found struct `Struct`
191+
| help: store this in the heap by calling `Box::new`: `Box::new(Struct)`
192+
|
193+
= note: expected struct `std::boxed::Box<(dyn Trait + 'static)>`
194+
found struct `Struct`
195+
= note: for more on the distinction between the stack and the heap, read https://doc.rust-lang.org/book/ch15-01-box.html, https://doc.rust-lang.org/rust-by-example/std/box.html, and https://doc.rust-lang.org/std/boxed/index.html
196+
197+
error[E0308]: mismatched types
198+
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:48:9
199+
|
200+
LL | fn baz() -> Box<dyn Trait> {
201+
| -------------- expected `std::boxed::Box<(dyn Trait + 'static)>` because of return type
202+
...
203+
LL | 42
204+
| ^^
205+
| |
206+
| expected struct `std::boxed::Box`, found integer
207+
| help: store this in the heap by calling `Box::new`: `Box::new(42)`
208+
|
209+
= note: expected struct `std::boxed::Box<(dyn Trait + 'static)>`
210+
found type `{integer}`
211+
= note: for more on the distinction between the stack and the heap, read https://doc.rust-lang.org/book/ch15-01-box.html, https://doc.rust-lang.org/rust-by-example/std/box.html, and https://doc.rust-lang.org/std/boxed/index.html
212+
213+
error[E0308]: mismatched types
214+
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:53:9
215+
|
216+
LL | fn baw() -> Box<dyn Trait> {
217+
| -------------- expected `std::boxed::Box<(dyn Trait + 'static)>` because of return type
218+
LL | if true {
219+
LL | 0
220+
| ^
221+
| |
222+
| expected struct `std::boxed::Box`, found integer
223+
| help: store this in the heap by calling `Box::new`: `Box::new(0)`
224+
|
225+
= note: expected struct `std::boxed::Box<(dyn Trait + 'static)>`
226+
found type `{integer}`
227+
= note: for more on the distinction between the stack and the heap, read https://doc.rust-lang.org/book/ch15-01-box.html, https://doc.rust-lang.org/rust-by-example/std/box.html, and https://doc.rust-lang.org/std/boxed/index.html
228+
229+
error[E0308]: mismatched types
230+
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:55:9
231+
|
232+
LL | fn baw() -> Box<dyn Trait> {
233+
| -------------- expected `std::boxed::Box<(dyn Trait + 'static)>` because of return type
234+
...
235+
LL | 42
236+
| ^^
237+
| |
238+
| expected struct `std::boxed::Box`, found integer
239+
| help: store this in the heap by calling `Box::new`: `Box::new(42)`
240+
|
241+
= note: expected struct `std::boxed::Box<(dyn Trait + 'static)>`
242+
found type `{integer}`
243+
= note: for more on the distinction between the stack and the heap, read https://doc.rust-lang.org/book/ch15-01-box.html, https://doc.rust-lang.org/rust-by-example/std/box.html, and https://doc.rust-lang.org/std/boxed/index.html
244+
245+
error[E0746]: return type cannot have an unboxed trait object
246+
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:60:13
100247
|
101248
LL | fn bat() -> dyn Trait {
102249
| ^^^^^^^^^ doesn't have a size known at compile-time
@@ -107,7 +254,19 @@ help: return `impl Trait` instead, as all return paths are of type `{integer}`,
107254
LL | fn bat() -> impl Trait {
108255
| ^^^^^^^^^^
109256

110-
error: aborting due to 9 previous errors
257+
error[E0746]: return type cannot have an unboxed trait object
258+
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:66:13
259+
|
260+
LL | fn bay() -> dyn Trait {
261+
| ^^^^^^^^^ doesn't have a size known at compile-time
262+
|
263+
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
264+
help: return `impl Trait` instead, as all return paths are of type `{integer}`, which implements `Trait`
265+
|
266+
LL | fn bay() -> impl Trait {
267+
| ^^^^^^^^^^
268+
269+
error: aborting due to 19 previous errors
111270

112271
Some errors have detailed explanations: E0277, E0308, E0746.
113272
For more information about an error, try `rustc --explain E0277`.

src/test/ui/never_type/feature-gate-never_type_fallback.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ LL | fn should_ret_unit() -> impl T {
55
| ^^^^^^ the trait `T` is not implemented for `()`
66
LL |
77
LL | panic!()
8-
| -------- this returned value is of type `()`
8+
| -------- this returned value is of type `!`
99
|
1010
= note: the return type of a function must have a statically known size
1111
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

0 commit comments

Comments
 (0)