Skip to content

Commit d3e5bfc

Browse files
committed
Provide better suggestions when encountering a bare trait as a type
Add the following suggestions: ``` error[E0782]: trait objects must include the `dyn` keyword --> $DIR/not-on-bare-trait-2021.rs:11:11 | LL | fn bar(x: Foo) -> Foo { | ^^^ | help: use a generic type parameter, constrained by the trait `Foo` | LL | fn bar<T: Foo>(x: T) -> Foo { | ++++++++ ~ help: you can also use `impl Foo`, but users won't be able to specify the type paramer when calling the `fn`, having to rely exclusively on type inference | LL | fn bar(x: impl Foo) -> Foo { | ++++ help: alternatively, use a trait object to accept any type that implements `Foo`, accessing its methods at runtime using dynamic dispatch | LL | fn bar(x: &dyn Foo) -> Foo { | ++++ error[E0782]: trait objects must include the `dyn` keyword --> $DIR/not-on-bare-trait-2021.rs:11:19 | LL | fn bar(x: Foo) -> Foo { | ^^^ | help: use `impl Foo` to return an opaque type, as long as you return a single underlying type | LL | fn bar(x: Foo) -> impl Foo { | ++++ help: alternatively, you can return an owned trait object | LL | fn bar(x: Foo) -> Box<dyn Foo> { | +++++++ + ```
1 parent 92ad4b4 commit d3e5bfc

File tree

4 files changed

+197
-21
lines changed

4 files changed

+197
-21
lines changed

compiler/rustc_hir_analysis/src/astconv/lint.rs

+113-18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use rustc_ast::TraitObjectSyntax;
22
use rustc_errors::{Diagnostic, StashKey};
33
use rustc_hir as hir;
44
use rustc_lint_defs::{builtin::BARE_TRAIT_OBJECTS, Applicability};
5+
use rustc_span::Span;
56
use rustc_trait_selection::traits::error_reporting::suggestions::NextTypeParamName;
67

78
use super::AstConv;
@@ -32,32 +33,120 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
3233
}
3334
let of_trait_span = of_trait_ref.path.span;
3435
// make sure that we are not calling unwrap to abort during the compilation
35-
let Ok(impl_trait_name) = tcx.sess.source_map().span_to_snippet(self_ty.span) else {
36-
return;
37-
};
3836
let Ok(of_trait_name) = tcx.sess.source_map().span_to_snippet(of_trait_span) else {
3937
return;
4038
};
41-
// check if the trait has generics, to make a correct suggestion
42-
let param_name = generics.params.next_type_param_name(None);
4339

44-
let add_generic_sugg = if let Some(span) = generics.span_for_param_suggestion() {
45-
(span, format!(", {param_name}: {impl_trait_name}"))
46-
} else {
47-
(generics.span, format!("<{param_name}: {impl_trait_name}>"))
40+
let Ok(impl_trait_name) = self.tcx().sess.source_map().span_to_snippet(self_ty.span)
41+
else {
42+
return;
43+
};
44+
let Some(sugg) = self.generics_suggestion(generics, self_ty.span, &impl_trait_name)
45+
else {
46+
return;
4847
};
4948
diag.multipart_suggestion(
5049
format!(
51-
"alternatively use a blanket \
52-
implementation to implement `{of_trait_name}` for \
50+
"alternatively use a blanket implementation to implement `{of_trait_name}` for \
5351
all types that also implement `{impl_trait_name}`"
5452
),
55-
vec![(self_ty.span, param_name), add_generic_sugg],
53+
sugg,
5654
Applicability::MaybeIncorrect,
5755
);
5856
}
5957
}
6058

59+
fn generics_suggestion(
60+
&self,
61+
generics: &hir::Generics<'_>,
62+
self_ty_span: Span,
63+
impl_trait_name: &str,
64+
) -> Option<Vec<(Span, String)>> {
65+
// check if the trait has generics, to make a correct suggestion
66+
let param_name = generics.params.next_type_param_name(None);
67+
68+
let add_generic_sugg = if let Some(span) = generics.span_for_param_suggestion() {
69+
(span, format!(", {param_name}: {impl_trait_name}"))
70+
} else {
71+
(generics.span, format!("<{param_name}: {impl_trait_name}>"))
72+
};
73+
Some(vec![(self_ty_span, param_name), add_generic_sugg])
74+
}
75+
76+
/// Make sure that we are in the condition to suggest `impl Trait`.
77+
fn maybe_lint_impl_trait(&self, self_ty: &hir::Ty<'_>, diag: &mut Diagnostic) -> bool {
78+
let tcx = self.tcx();
79+
let parent_id = tcx.hir().get_parent_item(self_ty.hir_id).def_id;
80+
let (hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, generics, _), .. })
81+
| hir::Node::TraitItem(hir::TraitItem {
82+
kind: hir::TraitItemKind::Fn(sig, _),
83+
generics,
84+
..
85+
})) = tcx.hir_node_by_def_id(parent_id)
86+
else {
87+
return false;
88+
};
89+
let Ok(trait_name) = self.tcx().sess.source_map().span_to_snippet(self_ty.span) else {
90+
return false;
91+
};
92+
let impl_sugg = vec![(self_ty.span.shrink_to_lo(), "impl ".to_string())];
93+
if let hir::FnRetTy::Return(ty) = sig.decl.output
94+
&& ty.hir_id == self_ty.hir_id
95+
{
96+
diag.multipart_suggestion_verbose(
97+
format!("use `impl {trait_name}` to return an opaque type, as long as you return a single underlying type"),
98+
impl_sugg,
99+
Applicability::MachineApplicable,
100+
);
101+
diag.multipart_suggestion_verbose(
102+
"alternatively, you can return an owned trait object",
103+
vec![
104+
(ty.span.shrink_to_lo(), "Box<dyn ".to_string()),
105+
(ty.span.shrink_to_hi(), ">".to_string()),
106+
],
107+
Applicability::MachineApplicable,
108+
);
109+
return true;
110+
}
111+
for ty in sig.decl.inputs {
112+
if ty.hir_id == self_ty.hir_id {
113+
if let Some(sugg) = self.generics_suggestion(generics, self_ty.span, &trait_name) {
114+
diag.multipart_suggestion_verbose(
115+
format!("use a new generic type parameter, constrained by `{trait_name}`"),
116+
sugg,
117+
Applicability::MachineApplicable,
118+
);
119+
diag.multipart_suggestion_verbose(
120+
"you can also use an opaque type, but users won't be able to specify the \
121+
type parameter when calling the `fn`, having to rely exclusively on type \
122+
inference",
123+
impl_sugg,
124+
Applicability::MachineApplicable,
125+
);
126+
}
127+
let sugg = if let hir::TyKind::TraitObject([_, _, ..], _, _) = self_ty.kind {
128+
// There are more than one trait bound, we need surrounding parentheses.
129+
vec![
130+
(self_ty.span.shrink_to_lo(), "&(dyn ".to_string()),
131+
(self_ty.span.shrink_to_hi(), ")".to_string()),
132+
]
133+
} else {
134+
vec![(self_ty.span.shrink_to_lo(), "&dyn ".to_string())]
135+
};
136+
diag.multipart_suggestion_verbose(
137+
format!(
138+
"alternatively, use a trait object to accept any type that implements \
139+
`{trait_name}`, accessing its methods at runtime using dynamic dispatch",
140+
),
141+
sugg,
142+
Applicability::MachineApplicable,
143+
);
144+
return true;
145+
}
146+
}
147+
false
148+
}
149+
61150
pub(super) fn maybe_lint_bare_trait(&self, self_ty: &hir::Ty<'_>, in_path: bool) {
62151
let tcx = self.tcx();
63152
if let hir::TyKind::TraitObject([poly_trait_ref, ..], _, TraitObjectSyntax::None) =
@@ -98,7 +187,9 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
98187
let label = "add `dyn` keyword before this trait";
99188
let mut diag =
100189
rustc_errors::struct_span_err!(tcx.sess, self_ty.span, E0782, "{}", msg);
101-
if self_ty.span.can_be_used_for_suggestions() {
190+
if self_ty.span.can_be_used_for_suggestions()
191+
&& !self.maybe_lint_impl_trait(self_ty, &mut diag)
192+
{
102193
diag.multipart_suggestion_verbose(
103194
label,
104195
sugg,
@@ -116,11 +207,15 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
116207
self_ty.span,
117208
msg,
118209
|lint| {
119-
lint.multipart_suggestion_verbose(
120-
"use `dyn`",
121-
sugg,
122-
Applicability::MachineApplicable,
123-
);
210+
if self_ty.span.can_be_used_for_suggestions()
211+
&& !self.maybe_lint_impl_trait(self_ty, lint)
212+
{
213+
lint.multipart_suggestion_verbose(
214+
"use `dyn`",
215+
sugg,
216+
Applicability::MachineApplicable,
217+
);
218+
}
124219
self.maybe_lint_blanket_trait_impl(self_ty, lint);
125220
},
126221
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// edition:2021
2+
trait Foo {
3+
fn dummy(&self) {}
4+
}
5+
6+
// This should emit the less confusing error, not the more confusing one.
7+
8+
fn foo(_x: Foo + Send) {
9+
//~^ ERROR trait objects must include the `dyn` keyword
10+
}
11+
fn bar(x: Foo) -> Foo {
12+
//~^ ERROR trait objects must include the `dyn` keyword
13+
//~| ERROR trait objects must include the `dyn` keyword
14+
x
15+
}
16+
17+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
error[E0782]: trait objects must include the `dyn` keyword
2+
--> $DIR/not-on-bare-trait-2021.rs:8:12
3+
|
4+
LL | fn foo(_x: Foo + Send) {
5+
| ^^^^^^^^^^
6+
|
7+
help: use a new generic type parameter, constrained by `Foo + Send`
8+
|
9+
LL | fn foo<T: Foo + Send>(_x: T) {
10+
| +++++++++++++++ ~
11+
help: you can also use an opaque type, but users won't be able to specify the type parameter when calling the `fn`, having to rely exclusively on type inference
12+
|
13+
LL | fn foo(_x: impl Foo + Send) {
14+
| ++++
15+
help: alternatively, use a trait object to accept any type that implements `Foo + Send`, accessing its methods at runtime using dynamic dispatch
16+
|
17+
LL | fn foo(_x: &(dyn Foo + Send)) {
18+
| +++++ +
19+
20+
error[E0782]: trait objects must include the `dyn` keyword
21+
--> $DIR/not-on-bare-trait-2021.rs:11:11
22+
|
23+
LL | fn bar(x: Foo) -> Foo {
24+
| ^^^
25+
|
26+
help: use a new generic type parameter, constrained by `Foo`
27+
|
28+
LL | fn bar<T: Foo>(x: T) -> Foo {
29+
| ++++++++ ~
30+
help: you can also use an opaque type, but users won't be able to specify the type parameter when calling the `fn`, having to rely exclusively on type inference
31+
|
32+
LL | fn bar(x: impl Foo) -> Foo {
33+
| ++++
34+
help: alternatively, use a trait object to accept any type that implements `Foo`, accessing its methods at runtime using dynamic dispatch
35+
|
36+
LL | fn bar(x: &dyn Foo) -> Foo {
37+
| ++++
38+
39+
error[E0782]: trait objects must include the `dyn` keyword
40+
--> $DIR/not-on-bare-trait-2021.rs:11:19
41+
|
42+
LL | fn bar(x: Foo) -> Foo {
43+
| ^^^
44+
|
45+
help: use `impl Foo` to return an opaque type, as long as you return a single underlying type
46+
|
47+
LL | fn bar(x: Foo) -> impl Foo {
48+
| ++++
49+
help: alternatively, you can return an owned trait object
50+
|
51+
LL | fn bar(x: Foo) -> Box<dyn Foo> {
52+
| +++++++ +
53+
54+
error: aborting due to 3 previous errors
55+
56+
For more information about this error, try `rustc --explain E0782`.

tests/ui/traits/bound/not-on-bare-trait.stderr

+11-3
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,18 @@ LL | fn foo(_x: Foo + Send) {
77
= warning: this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2021!
88
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/warnings-promoted-to-error.html>
99
= note: `#[warn(bare_trait_objects)]` on by default
10-
help: use `dyn`
10+
help: use a new generic type parameter, constrained by `Foo + Send`
1111
|
12-
LL | fn foo(_x: dyn Foo + Send) {
13-
| +++
12+
LL | fn foo<T: Foo + Send>(_x: T) {
13+
| +++++++++++++++ ~
14+
help: you can also use an opaque type, but users won't be able to specify the type parameter when calling the `fn`, having to rely exclusively on type inference
15+
|
16+
LL | fn foo(_x: impl Foo + Send) {
17+
| ++++
18+
help: alternatively, use a trait object to accept any type that implements `Foo + Send`, accessing its methods at runtime using dynamic dispatch
19+
|
20+
LL | fn foo(_x: &(dyn Foo + Send)) {
21+
| +++++ +
1422

1523
error[E0277]: the size for values of type `(dyn Foo + Send + 'static)` cannot be known at compilation time
1624
--> $DIR/not-on-bare-trait.rs:7:8

0 commit comments

Comments
 (0)