Skip to content

Commit fbea27b

Browse files
committed
On object safety errors, point at specific object unsafe trait in path
We use the new HIR node tracking for well-formedness obligations to point at the specific trait object in a path/type (`dyn T` instead of a whole `Box<dyn T>`, for example). ``` error[E0038]: the trait `MapLike` cannot be made into an object --> $DIR/file.rs:47:16 | LL | as Box<dyn Trait>; | ^^^^^^^^^ `Trait` cannot be made into an object ``` We also provide a structured suggestion for `<dyn Trait>::assoc`: ``` = help: when writing `<dyn Trait>::function` you are requiring `Trait` be "object safe", which it isn't help: you might have meant to access the associated function of a specific `impl` to avoid requiring "object safety" from `Trait`, either with some explicit type... | LL | </* Type */ as Trait>::function(); | ~~~~~~~~~~~~~ help: ...or rely on inference if the compiler has enough context to identify the desired type on its own... | LL - <dyn Trait>::function(); LL + Trait::function(); | help: ...which is equivalent to | LL | <_ as Trait>::function(); | ~~~~ ```
1 parent 888f8b8 commit fbea27b

30 files changed

+283
-71
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3997,6 +3997,7 @@ dependencies = [
39973997
name = "rustc_infer"
39983998
version = "0.0.0"
39993999
dependencies = [
4000+
"rustc_ast",
40004001
"rustc_ast_ir",
40014002
"rustc_data_structures",
40024003
"rustc_errors",

compiler/rustc_infer/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ doctest = false
88

99
[dependencies]
1010
# tidy-alphabetical-start
11+
rustc_ast = { path = "../rustc_ast" }
1112
rustc_ast_ir = { path = "../rustc_ast_ir" }
1213
rustc_data_structures = { path = "../rustc_data_structures" }
1314
rustc_errors = { path = "../rustc_errors" }

compiler/rustc_infer/src/traits/error_reporting/mod.rs

Lines changed: 184 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use super::ObjectSafetyViolation;
22

33
use crate::infer::InferCtxt;
4+
use rustc_ast::TraitObjectSyntax;
45
use rustc_data_structures::fx::FxIndexSet;
56
use rustc_errors::{codes::*, struct_span_code_err, Applicability, Diag, MultiSpan};
67
use rustc_hir as hir;
78
use rustc_hir::def_id::{DefId, LocalDefId};
9+
use rustc_hir::intravisit::Visitor;
810
use rustc_middle::ty::print::with_no_trimmed_paths;
911
use rustc_middle::ty::{self, TyCtxt};
1012
use rustc_span::Span;
@@ -39,6 +41,23 @@ impl<'tcx> InferCtxt<'tcx> {
3941
}
4042
}
4143

44+
struct TraitObjectFinder<'tcx> {
45+
trait_def_id: DefId,
46+
result: Vec<&'tcx hir::Ty<'tcx>>,
47+
}
48+
49+
impl<'v> Visitor<'v> for TraitObjectFinder<'v> {
50+
fn visit_ty(&mut self, ty: &'v hir::Ty<'v>) {
51+
if let hir::TyKind::TraitObject(traits, _, _) = ty.kind
52+
&& traits.iter().any(|t| t.trait_ref.trait_def_id() == Some(self.trait_def_id))
53+
{
54+
self.result.push(ty);
55+
}
56+
hir::intravisit::walk_ty(self, ty);
57+
}
58+
}
59+
60+
#[tracing::instrument(level = "debug", skip(tcx))]
4261
pub fn report_object_safety_error<'tcx>(
4362
tcx: TyCtxt<'tcx>,
4463
span: Span,
@@ -51,33 +70,79 @@ pub fn report_object_safety_error<'tcx>(
5170
hir::Node::Item(item) => Some(item.ident.span),
5271
_ => None,
5372
});
73+
let mut visitor = TraitObjectFinder { trait_def_id, result: vec![] };
74+
if let Some(hir_id) = hir_id {
75+
match tcx.hir_node(hir_id) {
76+
hir::Node::Expr(expr) => {
77+
visitor.visit_expr(&expr);
78+
if visitor.result.is_empty() {
79+
match tcx.parent_hir_node(hir_id) {
80+
hir::Node::Expr(expr) if let hir::ExprKind::Cast(_, ty) = expr.kind => {
81+
// Special case for `<expr> as <ty>`, as we're given the `expr` instead
82+
// of the whole cast expression. This will let us point at `dyn Trait`
83+
// instead of `x` in `x as Box<dyn Trait>`.
84+
visitor.visit_ty(ty);
85+
}
86+
hir::Node::LetStmt(stmt) if let Some(ty) = stmt.ty => {
87+
// Special case for `let <pat>: <ty> = <expr>;`, as we're given the
88+
// `expr` instead of the whole cast expression. This will let us point
89+
// at `dyn Trait` instead of `x` in `let y: Box<dyn Trait> = x;`.
90+
visitor.visit_ty(ty);
91+
}
92+
_ => {}
93+
}
94+
}
95+
}
96+
hir::Node::Ty(ty) => {
97+
visitor.visit_ty(&ty);
98+
}
99+
_ => {}
100+
}
101+
}
102+
let mut label_span = span;
103+
let mut dyn_trait_spans = vec![];
104+
let mut trait_spans = vec![];
105+
let spans: MultiSpan = if visitor.result.is_empty() {
106+
span.into()
107+
} else {
108+
for ty in &visitor.result {
109+
let hir::TyKind::TraitObject(traits, ..) = ty.kind else { continue };
110+
dyn_trait_spans.push(ty.span);
111+
trait_spans.extend(
112+
traits
113+
.iter()
114+
.filter(|t| t.trait_ref.trait_def_id() == Some(trait_def_id))
115+
.map(|t| t.trait_ref.path.span),
116+
);
117+
}
118+
match (&dyn_trait_spans[..], &trait_spans[..]) {
119+
([], []) => span.into(),
120+
([only], [_]) => {
121+
// There is a single `dyn Trait` for the expression or type that was stored in the
122+
// `WellFormedLoc`. We point at the whole `dyn Trait`.
123+
label_span = *only;
124+
(*only).into()
125+
}
126+
(_, [.., last]) => {
127+
// There are more than one trait in `dyn A + A` in the expression or type that was
128+
// stored in the `WellFormedLoc` that points at the relevant trait, or there are
129+
// more than one `dyn A`. We apply the primary span label to the last one of these.
130+
label_span = *last;
131+
trait_spans.into()
132+
}
133+
// Should be unreachable, as if one is empty, the other must be too.
134+
_ => span.into(),
135+
}
136+
};
54137
let mut err = struct_span_code_err!(
55138
tcx.dcx(),
56-
span,
139+
spans,
57140
E0038,
58141
"the trait `{}` cannot be made into an object",
59142
trait_str
60143
);
61-
err.span_label(span, format!("`{trait_str}` cannot be made into an object"));
144+
err.span_label(label_span, format!("`{trait_str}` cannot be made into an object"));
62145

63-
if let Some(hir_id) = hir_id
64-
&& let hir::Node::Ty(ty) = tcx.hir_node(hir_id)
65-
&& let hir::TyKind::TraitObject([trait_ref, ..], ..) = ty.kind
66-
{
67-
let mut hir_id = hir_id;
68-
while let hir::Node::Ty(ty) = tcx.parent_hir_node(hir_id) {
69-
hir_id = ty.hir_id;
70-
}
71-
if tcx.parent_hir_node(hir_id).fn_sig().is_some() {
72-
// Do not suggest `impl Trait` when dealing with things like super-traits.
73-
err.span_suggestion_verbose(
74-
ty.span.until(trait_ref.span),
75-
"consider using an opaque type instead",
76-
"impl ",
77-
Applicability::MaybeIncorrect,
78-
);
79-
}
80-
}
81146
let mut reported_violations = FxIndexSet::default();
82147
let mut multi_span = vec![];
83148
let mut messages = vec![];
@@ -160,7 +225,106 @@ pub fn report_object_safety_error<'tcx>(
160225
} else {
161226
false
162227
};
228+
let mut has_suggested = false;
229+
if let Some(hir_id) = hir_id {
230+
let node = tcx.hir_node(hir_id);
231+
if let hir::Node::Ty(ty) = node
232+
&& let hir::TyKind::TraitObject([trait_ref, ..], ..) = ty.kind
233+
{
234+
let mut hir_id = hir_id;
235+
while let hir::Node::Ty(ty) = tcx.parent_hir_node(hir_id) {
236+
hir_id = ty.hir_id;
237+
}
238+
if tcx.parent_hir_node(hir_id).fn_sig().is_some() {
239+
// Do not suggest `impl Trait` when dealing with things like super-traits.
240+
err.span_suggestion_verbose(
241+
ty.span.until(trait_ref.span),
242+
"consider using an opaque type instead",
243+
"impl ",
244+
Applicability::MaybeIncorrect,
245+
);
246+
has_suggested = true;
247+
}
248+
}
249+
if let hir::Node::Expr(expr) = node
250+
&& let hir::ExprKind::Path(hir::QPath::TypeRelative(ty, path_segment)) = expr.kind
251+
&& let hir::TyKind::TraitObject([trait_ref, ..], _, trait_object_syntax) = ty.kind
252+
{
253+
if let TraitObjectSyntax::None = trait_object_syntax
254+
&& !expr.span.edition().at_least_rust_2021()
255+
{
256+
err.span_note(
257+
trait_ref.trait_ref.path.span,
258+
format!(
259+
"`{trait_str}` is the type for the trait in editions 2015 and 2018 and is \
260+
equivalent to writing `dyn {trait_str}`",
261+
),
262+
);
263+
}
264+
let segment = path_segment.ident;
265+
err.help(format!(
266+
"when writing `<dyn {trait_str}>::{segment}` you are requiring `{trait_str}` be \
267+
\"object safe\", which it isn't",
268+
));
269+
let (only, msg, sugg, appl) = if let [only] = &impls[..] {
270+
// We know there's a single implementation for this trait, so we can be explicit on
271+
// the type they should have used.
272+
let ty = tcx.type_of(*only).instantiate_identity();
273+
(
274+
true,
275+
format!(
276+
"specify the specific `impl` for type `{ty}` to avoid requiring \"object \
277+
safety\" from `{trait_str}`",
278+
),
279+
with_no_trimmed_paths!(format!("{ty} as ")),
280+
Applicability::MachineApplicable,
281+
)
282+
} else {
283+
(
284+
false,
285+
format!(
286+
"you might have meant to access the associated function of a specific \
287+
`impl` to avoid requiring \"object safety\" from `{trait_str}`, either \
288+
with some explicit type...",
289+
),
290+
"/* Type */ as ".to_string(),
291+
Applicability::HasPlaceholders,
292+
)
293+
};
294+
// `<dyn Trait>::segment()` or `<Trait>::segment()` to `<Type as Trait>::segment()`
295+
let sp = ty.span.until(trait_ref.trait_ref.path.span);
296+
err.span_suggestion_verbose(sp, msg, sugg, appl);
297+
if !only {
298+
// `<dyn Trait>::segment()` or `<Trait>::segment()` to `Trait::segment()`
299+
err.multipart_suggestion_verbose(
300+
"...or rely on inference if the compiler has enough context to identify the \
301+
desired type on its own...",
302+
vec![
303+
(expr.span.until(trait_ref.trait_ref.path.span), String::new()),
304+
(
305+
path_segment
306+
.ident
307+
.span
308+
.shrink_to_lo()
309+
.with_lo(trait_ref.trait_ref.path.span.hi()),
310+
"::".to_string(),
311+
),
312+
],
313+
Applicability::MaybeIncorrect,
314+
);
315+
// `<dyn Trait>::segment()` or `<Trait>::segment()` to `<_ as Trait>::segment()`
316+
err.span_suggestion_verbose(
317+
ty.span.until(trait_ref.trait_ref.path.span),
318+
"...which is equivalent to",
319+
format!("_ as "),
320+
Applicability::MaybeIncorrect,
321+
);
322+
}
323+
has_suggested = true;
324+
}
325+
}
163326
match &impls[..] {
327+
_ if has_suggested => {}
164328
[] => {}
165329
_ if impls.len() > 9 => {}
166330
[only] if externally_visible => {

tests/ui/async-await/in-trait/object-safety.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error[E0038]: the trait `Foo` cannot be made into an object
2-
--> $DIR/object-safety.rs:9:12
2+
--> $DIR/object-safety.rs:9:13
33
|
44
LL | let x: &dyn Foo = todo!();
5-
| ^^^^^^^^ `Foo` cannot be made into an object
5+
| ^^^^^^^ `Foo` cannot be made into an object
66
|
77
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
88
--> $DIR/object-safety.rs:5:14

tests/ui/did_you_mean/trait-object-reference-without-parens-suggestion.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ LL | let _: &'static Copy + 'static;
1111
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try adding parentheses: `&'static (Copy + 'static)`
1212

1313
error[E0038]: the trait `Copy` cannot be made into an object
14-
--> $DIR/trait-object-reference-without-parens-suggestion.rs:4:12
14+
--> $DIR/trait-object-reference-without-parens-suggestion.rs:4:13
1515
|
1616
LL | let _: &Copy + 'static;
17-
| ^^^^^ `Copy` cannot be made into an object
17+
| ^^^^ `Copy` cannot be made into an object
1818
|
1919
= note: the trait cannot be made into an object because it requires `Self: Sized`
2020
= note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>

tests/ui/dyn-keyword/trait-dyn-in-qualified-path-for-object-safe-function-with-one-impl.stderr

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ LL | trait Trait {
1111
| ----- this trait cannot be made into an object...
1212
LL | fn function() {}
1313
| ^^^^^^^^ ...because associated function `function` has no `self` parameter
14-
= help: only type `()` implements the trait, consider using it directly instead
14+
= help: when writing `<dyn Trait>::function` you are requiring `Trait` be "object safe", which it isn't
1515
help: consider turning `function` into a method by giving it a `&self` argument
1616
|
1717
LL | fn function(&self) {}
@@ -20,6 +20,10 @@ help: alternatively, consider constraining `function` so it does not apply to tr
2020
|
2121
LL | fn function() where Self: Sized {}
2222
| +++++++++++++++++
23+
help: specify the specific `impl` for type `()` to avoid requiring "object safety" from `Trait`
24+
|
25+
LL | <() as Trait>::function();
26+
| ~~~~~
2327

2428
error: aborting due to 1 previous error
2529

tests/ui/dyn-keyword/trait-dyn-in-qualified-path-for-object-unsafe-trait.stderr

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,11 @@ LL | trait Trait: Sized {}
5757
| ----- ^^^^^ ...because it requires `Self: Sized`
5858
| |
5959
| this trait cannot be made into an object...
60-
= help: only type `()` implements the trait, consider using it directly instead
60+
= help: when writing `<dyn Trait>::function` you are requiring `Trait` be "object safe", which it isn't
61+
help: specify the specific `impl` for type `()` to avoid requiring "object safety" from `Trait`
62+
|
63+
LL | <() as Trait>::function(&());
64+
| ~~~~~
6165

6266
error: aborting due to 4 previous errors
6367

tests/ui/dyn-keyword/trait-dyn-in-qualified-path.stderr

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@ LL | trait Trait: Sized {
1111
| ----- ^^^^^ ...because it requires `Self: Sized`
1212
| |
1313
| this trait cannot be made into an object...
14+
= help: when writing `<dyn Trait>::function` you are requiring `Trait` be "object safe", which it isn't
15+
help: you might have meant to access the associated function of a specific `impl` to avoid requiring "object safety" from `Trait`, either with some explicit type...
16+
|
17+
LL | </* Type */ as Trait>::function();
18+
| ~~~~~~~~~~~~~
19+
help: ...or rely on inference if the compiler has enough context to identify the desired type on its own...
20+
|
21+
LL - <dyn Trait>::function();
22+
LL + Trait::function();
23+
|
24+
help: ...which is equivalent to
25+
|
26+
LL | <_ as Trait>::function();
27+
| ~~~~
1428

1529
error[E0277]: the size for values of type `dyn Trait` cannot be known at compilation time
1630
--> $DIR/trait-dyn-in-qualified-path.rs:5:6

tests/ui/dyn-keyword/trait-missing-dyn-in-qualified-path.edition2018.stderr

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,25 @@ LL | let x: u32 = <Default>::default();
2020
|
2121
= note: the trait cannot be made into an object because it requires `Self: Sized`
2222
= note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
23+
note: `Default` is the type for the trait in editions 2015 and 2018 and is equivalent to writing `dyn Default`
24+
--> $DIR/trait-missing-dyn-in-qualified-path.rs:5:19
25+
|
26+
LL | let x: u32 = <Default>::default();
27+
| ^^^^^^^
28+
= help: when writing `<dyn Default>::default` you are requiring `Default` be "object safe", which it isn't
29+
help: you might have meant to access the associated function of a specific `impl` to avoid requiring "object safety" from `Default`, either with some explicit type...
30+
|
31+
LL | let x: u32 = </* Type */ as Default>::default();
32+
| +++++++++++++
33+
help: ...or rely on inference if the compiler has enough context to identify the desired type on its own...
34+
|
35+
LL - let x: u32 = <Default>::default();
36+
LL + let x: u32 = Default::default();
37+
|
38+
help: ...which is equivalent to
39+
|
40+
LL | let x: u32 = <_ as Default>::default();
41+
| ++++
2342

2443
error[E0277]: the size for values of type `dyn Default` cannot be known at compilation time
2544
--> $DIR/trait-missing-dyn-in-qualified-path.rs:5:19
@@ -44,10 +63,10 @@ LL | let x: u32 = <Default>::default();
4463
= help: `u32` implements `Default` so you could change the expected type to `Box<dyn Default>`
4564

4665
error[E0038]: the trait `Default` cannot be made into an object
47-
--> $DIR/trait-missing-dyn-in-qualified-path.rs:5:18
66+
--> $DIR/trait-missing-dyn-in-qualified-path.rs:5:19
4867
|
4968
LL | let x: u32 = <Default>::default();
50-
| ^^^^^^^^^^^^^^^^^^^^ `Default` cannot be made into an object
69+
| ^^^^^^^ `Default` cannot be made into an object
5170
|
5271
= note: the trait cannot be made into an object because it requires `Self: Sized`
5372
= note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>

tests/ui/feature-gates/feature-gate-dispatch-from-dyn-missing-impl.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
error[E0038]: the trait `Trait` cannot be made into an object
2-
--> $DIR/feature-gate-dispatch-from-dyn-missing-impl.rs:32:25
2+
--> $DIR/feature-gate-dispatch-from-dyn-missing-impl.rs:32:29
33
|
44
LL | fn ptr(self: Ptr<Self>);
55
| --------- help: consider changing method `ptr`'s `self` parameter to be `&self`: `&Self`
66
...
77
LL | Ptr(Box::new(4)) as Ptr<dyn Trait>;
8-
| ^^^^^^^^^^^^^^ `Trait` cannot be made into an object
8+
| ^^^^^^^^^ `Trait` cannot be made into an object
99
|
1010
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
1111
--> $DIR/feature-gate-dispatch-from-dyn-missing-impl.rs:25:18

0 commit comments

Comments
 (0)