Skip to content

Commit 92e53f5

Browse files
authoredApr 4, 2022
Rollup merge of #95603 - compiler-errors:dyn-return, r=oli-obk
Fix late-bound ICE in `dyn` return type suggestion This fixes the root-cause of the attached issues -- the root problem is that we're using the return type from a signature with late-bound instead of early-bound regions. The change on line 1087 (`let Some(liberated_sig) = typeck_results.liberated_fn_sigs().get(fn_hir_id) else { return false; };`) makes sure we're grabbing the _right_ return type for this suggestion to check the `dyn` predicates with. Fixes #91801 Fixes #91803 This fix also includes some drive-by changes, specifically: 1. Don't suggest boxing when we have `-> dyn Trait` and are already returning `Box<T>` where `T: Trait` (before we always boxed the value). 2. Suggestion applies even when the return type is a type alias (e.g. `type Foo = dyn Trait`). This does cause the suggestion to expand to the aliased type, but I think it's still beneficial. 3. Split up the multipart suggestion because there's a 6-line max in the printed output... I am open to splitting out the above changes, if we just want to fix the ICE first. cc: ```@terrarier2111``` and #92289
2 parents 3bf33b9 + b899251 commit 92e53f5

10 files changed

+242
-84
lines changed
 

‎compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs

+79-55
Original file line numberDiff line numberDiff line change
@@ -1121,8 +1121,8 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
11211121
}
11221122

11231123
let hir = self.tcx.hir();
1124-
let parent_node = hir.get_parent_node(obligation.cause.body_id);
1125-
let node = hir.find(parent_node);
1124+
let fn_hir_id = hir.get_parent_node(obligation.cause.body_id);
1125+
let node = hir.find(fn_hir_id);
11261126
let Some(hir::Node::Item(hir::Item {
11271127
kind: hir::ItemKind::Fn(sig, _, body_id),
11281128
..
@@ -1160,16 +1160,17 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
11601160
visitor.visit_body(&body);
11611161

11621162
let typeck_results = self.in_progress_typeck_results.map(|t| t.borrow()).unwrap();
1163+
let Some(liberated_sig) = typeck_results.liberated_fn_sigs().get(fn_hir_id) else { return false; };
11631164

1164-
let mut ret_types = visitor
1165+
let ret_types = visitor
11651166
.returns
11661167
.iter()
1167-
.filter_map(|expr| typeck_results.node_type_opt(expr.hir_id))
1168-
.map(|ty| self.resolve_vars_if_possible(ty));
1168+
.filter_map(|expr| Some((expr.span, typeck_results.node_type_opt(expr.hir_id)?)))
1169+
.map(|(expr_span, ty)| (expr_span, self.resolve_vars_if_possible(ty)));
11691170
let (last_ty, all_returns_have_same_type, only_never_return) = ret_types.clone().fold(
11701171
(None, true, true),
11711172
|(last_ty, mut same, only_never_return): (std::option::Option<Ty<'_>>, bool, bool),
1172-
ty| {
1173+
(_, ty)| {
11731174
let ty = self.resolve_vars_if_possible(ty);
11741175
same &=
11751176
!matches!(ty.kind(), ty::Error(_))
@@ -1190,39 +1191,60 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
11901191
(Some(ty), same, only_never_return && matches!(ty.kind(), ty::Never))
11911192
},
11921193
);
1193-
let all_returns_conform_to_trait =
1194-
if let Some(ty_ret_ty) = typeck_results.node_type_opt(ret_ty.hir_id) {
1195-
match ty_ret_ty.kind() {
1196-
ty::Dynamic(predicates, _) => {
1197-
let cause = ObligationCause::misc(ret_ty.span, ret_ty.hir_id);
1198-
let param_env = ty::ParamEnv::empty();
1199-
only_never_return
1200-
|| ret_types.all(|returned_ty| {
1201-
predicates.iter().all(|predicate| {
1202-
let pred = predicate.with_self_ty(self.tcx, returned_ty);
1203-
let obl = Obligation::new(cause.clone(), param_env, pred);
1204-
self.predicate_may_hold(&obl)
1205-
})
1194+
let mut spans_and_needs_box = vec![];
1195+
1196+
match liberated_sig.output().kind() {
1197+
ty::Dynamic(predicates, _) => {
1198+
let cause = ObligationCause::misc(ret_ty.span, fn_hir_id);
1199+
let param_env = ty::ParamEnv::empty();
1200+
1201+
if !only_never_return {
1202+
for (expr_span, return_ty) in ret_types {
1203+
let self_ty_satisfies_dyn_predicates = |self_ty| {
1204+
predicates.iter().all(|predicate| {
1205+
let pred = predicate.with_self_ty(self.tcx, self_ty);
1206+
let obl = Obligation::new(cause.clone(), param_env, pred);
1207+
self.predicate_may_hold(&obl)
12061208
})
1209+
};
1210+
1211+
if let ty::Adt(def, substs) = return_ty.kind()
1212+
&& def.is_box()
1213+
&& self_ty_satisfies_dyn_predicates(substs.type_at(0))
1214+
{
1215+
spans_and_needs_box.push((expr_span, false));
1216+
} else if self_ty_satisfies_dyn_predicates(return_ty) {
1217+
spans_and_needs_box.push((expr_span, true));
1218+
} else {
1219+
return false;
1220+
}
12071221
}
1208-
_ => false,
12091222
}
1210-
} else {
1211-
true
1212-
};
1223+
}
1224+
_ => return false,
1225+
};
12131226

12141227
let sm = self.tcx.sess.source_map();
1215-
let (true, hir::TyKind::TraitObject(..), Ok(snippet), true) = (
1216-
// Verify that we're dealing with a return `dyn Trait`
1217-
ret_ty.span.overlaps(span),
1218-
&ret_ty.kind,
1219-
sm.span_to_snippet(ret_ty.span),
1220-
// If any of the return types does not conform to the trait, then we can't
1221-
// suggest `impl Trait` nor trait objects: it is a type mismatch error.
1222-
all_returns_conform_to_trait,
1223-
) else {
1228+
if !ret_ty.span.overlaps(span) {
12241229
return false;
1230+
}
1231+
let snippet = if let hir::TyKind::TraitObject(..) = ret_ty.kind {
1232+
if let Ok(snippet) = sm.span_to_snippet(ret_ty.span) {
1233+
snippet
1234+
} else {
1235+
return false;
1236+
}
1237+
} else {
1238+
// Substitute the type, so we can print a fixup given `type Alias = dyn Trait`
1239+
let name = liberated_sig.output().to_string();
1240+
let name =
1241+
name.strip_prefix('(').and_then(|name| name.strip_suffix(')')).unwrap_or(&name);
1242+
if !name.starts_with("dyn ") {
1243+
return false;
1244+
}
1245+
name.to_owned()
12251246
};
1247+
12261248
err.code(error_code!(E0746));
12271249
err.set_primary_message("return type cannot have an unboxed trait object");
12281250
err.children.clear();
@@ -1232,6 +1254,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
12321254
let trait_obj_msg = "for information on trait objects, see \
12331255
<https://doc.rust-lang.org/book/ch17-02-trait-objects.html\
12341256
#using-trait-objects-that-allow-for-values-of-different-types>";
1257+
12351258
let has_dyn = snippet.split_whitespace().next().map_or(false, |s| s == "dyn");
12361259
let trait_obj = if has_dyn { &snippet[4..] } else { &snippet };
12371260
if only_never_return {
@@ -1259,26 +1282,25 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
12591282
} else {
12601283
if is_object_safe {
12611284
// Suggest `-> Box<dyn Trait>` and `Box::new(returned_value)`.
1262-
// Get all the return values and collect their span and suggestion.
1263-
let mut suggestions: Vec<_> = visitor
1264-
.returns
1265-
.iter()
1266-
.flat_map(|expr| {
1267-
[
1268-
(expr.span.shrink_to_lo(), "Box::new(".to_string()),
1269-
(expr.span.shrink_to_hi(), ")".to_string()),
1270-
]
1271-
.into_iter()
1272-
})
1273-
.collect();
1274-
if !suggestions.is_empty() {
1275-
// Add the suggestion for the return type.
1276-
suggestions.push((ret_ty.span, format!("Box<dyn {}>", trait_obj)));
1277-
err.multipart_suggestion(
1278-
"return a boxed trait object instead",
1279-
suggestions,
1280-
Applicability::MaybeIncorrect,
1281-
);
1285+
err.multipart_suggestion(
1286+
"return a boxed trait object instead",
1287+
vec![
1288+
(ret_ty.span.shrink_to_lo(), "Box<".to_string()),
1289+
(span.shrink_to_hi(), ">".to_string()),
1290+
],
1291+
Applicability::MaybeIncorrect,
1292+
);
1293+
for (span, needs_box) in spans_and_needs_box {
1294+
if needs_box {
1295+
err.multipart_suggestion(
1296+
"... and box this value",
1297+
vec![
1298+
(span.shrink_to_lo(), "Box::new(".to_string()),
1299+
(span.shrink_to_hi(), ")".to_string()),
1300+
],
1301+
Applicability::MaybeIncorrect,
1302+
);
1303+
}
12821304
}
12831305
} else {
12841306
// This is currently not possible to trigger because E0038 takes precedence, but
@@ -2753,13 +2775,15 @@ fn suggest_trait_object_return_type_alternatives(
27532775
Applicability::MaybeIncorrect,
27542776
);
27552777
if is_object_safe {
2756-
err.span_suggestion(
2757-
ret_ty,
2778+
err.multipart_suggestion(
27582779
&format!(
27592780
"use a boxed trait object if all return paths implement trait `{}`",
27602781
trait_obj,
27612782
),
2762-
format!("Box<dyn {}>", trait_obj),
2783+
vec![
2784+
(ret_ty.shrink_to_lo(), "Box<".to_string()),
2785+
(ret_ty.shrink_to_hi(), ">".to_string()),
2786+
],
27632787
Applicability::MaybeIncorrect,
27642788
);
27652789
}

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

+19-11
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ LL | fn bak() -> impl Trait { unimplemented!() }
8181
help: use a boxed trait object if all return paths implement trait `Trait`
8282
|
8383
LL | fn bak() -> Box<dyn Trait> { unimplemented!() }
84-
| ~~~~~~~~~~~~~~
84+
| ++++ +
8585

8686
error[E0746]: return type cannot have an unboxed trait object
8787
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:19:13
@@ -95,12 +95,16 @@ LL | fn bal() -> dyn Trait {
9595
= note: you can create a new `enum` with a variant for each returned type
9696
help: return a boxed trait object instead
9797
|
98-
LL ~ fn bal() -> Box<dyn Trait> {
99-
LL | if true {
100-
LL ~ return Box::new(Struct);
101-
LL | }
102-
LL ~ Box::new(42)
98+
LL | fn bal() -> Box<dyn Trait> {
99+
| ++++ +
100+
help: ... and box this value
101+
|
102+
LL | return Box::new(Struct);
103+
| +++++++++ +
104+
help: ... and box this value
103105
|
106+
LL | Box::new(42)
107+
| +++++++++ +
104108

105109
error[E0308]: `if` and `else` have incompatible types
106110
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:29:9
@@ -126,12 +130,16 @@ LL | fn bax() -> dyn Trait {
126130
= note: you can create a new `enum` with a variant for each returned type
127131
help: return a boxed trait object instead
128132
|
129-
LL ~ fn bax() -> Box<dyn Trait> {
130-
LL | if true {
131-
LL ~ Box::new(Struct)
132-
LL | } else {
133-
LL ~ Box::new(42)
133+
LL | fn bax() -> Box<dyn Trait> {
134+
| ++++ +
135+
help: ... and box this value
136+
|
137+
LL | Box::new(Struct)
138+
| +++++++++ +
139+
help: ... and box this value
134140
|
141+
LL | Box::new(42)
142+
| +++++++++ +
135143

136144
error[E0308]: mismatched types
137145
--> $DIR/dyn-trait-return-should-be-impl-trait.rs:34:16

‎src/test/ui/impl-trait/point-to-type-err-cause-on-impl-trait-return.stderr

+32-17
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,16 @@ LL | fn hat() -> dyn std::fmt::Display {
103103
= note: you can create a new `enum` with a variant for each returned type
104104
help: return a boxed trait object instead
105105
|
106-
LL ~ fn hat() -> Box<dyn std::fmt::Display> {
107-
LL | match 13 {
108-
LL | 0 => {
109-
LL ~ return Box::new(0i32);
110-
LL | }
111-
LL | _ => {
112-
...
106+
LL | fn hat() -> Box<dyn std::fmt::Display> {
107+
| ++++ +
108+
help: ... and box this value
109+
|
110+
LL | return Box::new(0i32);
111+
| +++++++++ +
112+
help: ... and box this value
113+
|
114+
LL | Box::new(1u32)
115+
| +++++++++ +
113116

114117
error[E0308]: `match` arms have incompatible types
115118
--> $DIR/point-to-type-err-cause-on-impl-trait-return.rs:80:14
@@ -135,12 +138,20 @@ LL | fn pug() -> dyn std::fmt::Display {
135138
= note: you can create a new `enum` with a variant for each returned type
136139
help: return a boxed trait object instead
137140
|
138-
LL ~ fn pug() -> Box<dyn std::fmt::Display> {
139-
LL | match 13 {
140-
LL ~ 0 => Box::new(0i32),
141-
LL ~ 1 => Box::new(1u32),
142-
LL ~ _ => Box::new(2u32),
141+
LL | fn pug() -> Box<dyn std::fmt::Display> {
142+
| ++++ +
143+
help: ... and box this value
144+
|
145+
LL | 0 => Box::new(0i32),
146+
| +++++++++ +
147+
help: ... and box this value
143148
|
149+
LL | 1 => Box::new(1u32),
150+
| +++++++++ +
151+
help: ... and box this value
152+
|
153+
LL | _ => Box::new(2u32),
154+
| +++++++++ +
144155

145156
error[E0308]: `if` and `else` have incompatible types
146157
--> $DIR/point-to-type-err-cause-on-impl-trait-return.rs:89:9
@@ -166,12 +177,16 @@ LL | fn man() -> dyn std::fmt::Display {
166177
= note: you can create a new `enum` with a variant for each returned type
167178
help: return a boxed trait object instead
168179
|
169-
LL ~ fn man() -> Box<dyn std::fmt::Display> {
170-
LL | if false {
171-
LL ~ Box::new(0i32)
172-
LL | } else {
173-
LL ~ Box::new(1u32)
180+
LL | fn man() -> Box<dyn std::fmt::Display> {
181+
| ++++ +
182+
help: ... and box this value
183+
|
184+
LL | Box::new(0i32)
185+
| +++++++++ +
186+
help: ... and box this value
174187
|
188+
LL | Box::new(1u32)
189+
| +++++++++ +
175190

176191
error: aborting due to 14 previous errors
177192

‎src/test/ui/issues/issue-18107.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ LL | impl AbstractRenderer
1515
help: use a boxed trait object if all return paths implement trait `AbstractRenderer`
1616
|
1717
LL | Box<dyn AbstractRenderer>
18-
|
18+
| ++++ +
1919

2020
error: aborting due to previous error
2121

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use std::fmt::Debug;
2+
3+
// Test to suggest boxing the return type, and the closure branch of the `if`
4+
5+
fn print_on_or_the_other<'a>(a: i32, b: &'a String) -> dyn Fn() + 'a {
6+
//~^ ERROR return type cannot have an unboxed trait object
7+
if a % 2 == 0 {
8+
move || println!("{a}")
9+
} else {
10+
Box::new(move || println!("{}", b))
11+
//~^ ERROR `if` and `else` have incompatible types
12+
}
13+
}
14+
15+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
error[E0308]: `if` and `else` have incompatible types
2+
--> $DIR/box-instead-of-dyn-fn.rs:10:9
3+
|
4+
LL | / if a % 2 == 0 {
5+
LL | | move || println!("{a}")
6+
| | ----------------------- expected because of this
7+
LL | | } else {
8+
LL | | Box::new(move || println!("{}", b))
9+
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected closure, found struct `Box`
10+
LL | |
11+
LL | | }
12+
| |_____- `if` and `else` have incompatible types
13+
|
14+
= note: expected type `[closure@$DIR/box-instead-of-dyn-fn.rs:8:9: 8:32]`
15+
found struct `Box<[closure@$DIR/box-instead-of-dyn-fn.rs:10:18: 10:43]>`
16+
17+
error[E0746]: return type cannot have an unboxed trait object
18+
--> $DIR/box-instead-of-dyn-fn.rs:5:56
19+
|
20+
LL | fn print_on_or_the_other<'a>(a: i32, b: &'a String) -> dyn Fn() + 'a {
21+
| ^^^^^^^^^^^^^ doesn't have a size known at compile-time
22+
|
23+
= 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>
24+
= note: if all the returned values were of the same type you could use `impl Fn() + 'a` as the return type
25+
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
26+
= note: you can create a new `enum` with a variant for each returned type
27+
help: return a boxed trait object instead
28+
|
29+
LL | fn print_on_or_the_other<'a>(a: i32, b: &'a String) -> Box<dyn Fn() + 'a> {
30+
| ++++ +
31+
help: ... and box this value
32+
|
33+
LL | Box::new(move || println!("{a}"))
34+
| +++++++++ +
35+
36+
error: aborting due to 2 previous errors
37+
38+
Some errors have detailed explanations: E0308, E0746.
39+
For more information about an error, try `rustc --explain E0308`.

‎src/test/ui/unsized/issue-91801.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
pub struct Something;
2+
3+
type Validator<'a> = dyn 'a + Send + Sync + Fn(&'a Something) -> Result<(), ()>;
4+
5+
pub static ALL_VALIDATORS: &[(&'static str, &'static Validator)] =
6+
&[("validate that credits and debits balance", &validate_something)];
7+
8+
fn or<'a>(first: &'static Validator<'a>, second: &'static Validator<'a>) -> Validator<'a> {
9+
//~^ ERROR return type cannot have an unboxed trait object
10+
return Box::new(move |something: &'_ Something| -> Result<(), ()> {
11+
first(something).or_else(|_| second(something))
12+
});
13+
}
14+
15+
fn validate_something(_: &Something) -> Result<(), ()> {
16+
Ok(())
17+
}
18+
19+
fn main() {}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error[E0746]: return type cannot have an unboxed trait object
2+
--> $DIR/issue-91801.rs:8:77
3+
|
4+
LL | fn or<'a>(first: &'static Validator<'a>, second: &'static Validator<'a>) -> Validator<'a> {
5+
| ^^^^^^^^^^^^^ doesn't have a size known at compile-time
6+
|
7+
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
8+
help: use `impl Fn(&'a Something) -> Result<(), ()> + Send + Sync + 'a` as the return type, as all return paths are of type `Box<[closure@$DIR/issue-91801.rs:10:21: 12:6]>`, which implements `Fn(&'a Something) -> Result<(), ()> + Send + Sync + 'a`
9+
|
10+
LL | fn or<'a>(first: &'static Validator<'a>, second: &'static Validator<'a>) -> impl Fn(&'a Something) -> Result<(), ()> + Send + Sync + 'a {
11+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
12+
13+
error: aborting due to previous error
14+
15+
For more information about this error, try `rustc --explain E0746`.

‎src/test/ui/unsized/issue-91803.rs

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
trait Foo<'a> {}
2+
3+
fn or<'a>(first: &'static dyn Foo<'a>) -> dyn Foo<'a> {
4+
//~^ ERROR return type cannot have an unboxed trait object
5+
return Box::new(panic!());
6+
}
7+
8+
fn main() {}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error[E0746]: return type cannot have an unboxed trait object
2+
--> $DIR/issue-91803.rs:3:43
3+
|
4+
LL | fn or<'a>(first: &'static dyn Foo<'a>) -> dyn Foo<'a> {
5+
| ^^^^^^^^^^^ doesn't have a size known at compile-time
6+
|
7+
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
8+
help: use `impl Foo<'a>` as the return type, as all return paths are of type `Box<_>`, which implements `Foo<'a>`
9+
|
10+
LL | fn or<'a>(first: &'static dyn Foo<'a>) -> impl Foo<'a> {
11+
| ~~~~~~~~~~~~
12+
13+
error: aborting due to previous error
14+
15+
For more information about this error, try `rustc --explain E0746`.

0 commit comments

Comments
 (0)
Please sign in to comment.