Skip to content

Commit f41ad1b

Browse files
Rollup merge of rust-lang#119148 - estebank:bare-traits, r=davidtwco
Tweak suggestions for bare trait used as a type ``` 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> { | +++++++ + ``` Fix rust-lang#119525: ``` error[E0038]: the trait `Ord` cannot be made into an object --> $DIR/bare-trait-dont-suggest-dyn.rs:3:33 | LL | fn ord_prefer_dot(s: String) -> Ord { | ^^^ `Ord` cannot be made into an object | 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> --> $SRC_DIR/core/src/cmp.rs:LL:COL | = note: the trait cannot be made into an object because it uses `Self` as a type parameter ::: $SRC_DIR/core/src/cmp.rs:LL:COL | = note: the trait cannot be made into an object because it uses `Self` as a type parameter help: consider using an opaque type instead | LL | fn ord_prefer_dot(s: String) -> impl Ord { | ++++ ```
2 parents d180e91 + 698dfc3 commit f41ad1b

File tree

22 files changed

+412
-75
lines changed

22 files changed

+412
-75
lines changed

compiler/rustc_hir_analysis/src/astconv/errors.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
605605
let violations =
606606
object_safety_violations_for_assoc_item(tcx, trait_def_id, *assoc_item);
607607
if !violations.is_empty() {
608-
report_object_safety_error(tcx, *span, trait_def_id, &violations).emit();
608+
report_object_safety_error(tcx, *span, None, trait_def_id, &violations).emit();
609609
object_safety_violations = true;
610610
}
611611
}

compiler/rustc_hir_analysis/src/astconv/lint.rs

+140-18
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use rustc_ast::TraitObjectSyntax;
22
use rustc_errors::{Diagnostic, StashKey};
33
use rustc_hir as hir;
4+
use rustc_hir::def::{DefKind, Res};
45
use rustc_lint_defs::{builtin::BARE_TRAIT_OBJECTS, Applicability};
6+
use rustc_span::Span;
57
use rustc_trait_selection::traits::error_reporting::suggestions::NextTypeParamName;
68

79
use super::AstConv;
@@ -32,32 +34,146 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
3234
}
3335
let of_trait_span = of_trait_ref.path.span;
3436
// 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-
};
3837
let Ok(of_trait_name) = tcx.sess.source_map().span_to_snippet(of_trait_span) else {
3938
return;
4039
};
41-
// check if the trait has generics, to make a correct suggestion
42-
let param_name = generics.params.next_type_param_name(None);
4340

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}>"))
41+
let Ok(impl_trait_name) = self.tcx().sess.source_map().span_to_snippet(self_ty.span)
42+
else {
43+
return;
44+
};
45+
let sugg = self.add_generic_param_suggestion(generics, self_ty.span, &impl_trait_name);
46+
if sugg.is_empty() {
47+
return;
4848
};
4949
diag.multipart_suggestion(
5050
format!(
51-
"alternatively use a blanket \
52-
implementation to implement `{of_trait_name}` for \
51+
"alternatively use a blanket implementation to implement `{of_trait_name}` for \
5352
all types that also implement `{impl_trait_name}`"
5453
),
55-
vec![(self_ty.span, param_name), add_generic_sugg],
54+
sugg,
5655
Applicability::MaybeIncorrect,
5756
);
5857
}
5958
}
6059

60+
fn add_generic_param_suggestion(
61+
&self,
62+
generics: &hir::Generics<'_>,
63+
self_ty_span: Span,
64+
impl_trait_name: &str,
65+
) -> Vec<(Span, String)> {
66+
// check if the trait has generics, to make a correct suggestion
67+
let param_name = generics.params.next_type_param_name(None);
68+
69+
let add_generic_sugg = if let Some(span) = generics.span_for_param_suggestion() {
70+
(span, format!(", {param_name}: {impl_trait_name}"))
71+
} else {
72+
(generics.span, format!("<{param_name}: {impl_trait_name}>"))
73+
};
74+
vec![(self_ty_span, param_name), add_generic_sugg]
75+
}
76+
77+
/// Make sure that we are in the condition to suggest `impl Trait`.
78+
fn maybe_lint_impl_trait(&self, self_ty: &hir::Ty<'_>, diag: &mut Diagnostic) -> bool {
79+
let tcx = self.tcx();
80+
let parent_id = tcx.hir().get_parent_item(self_ty.hir_id).def_id;
81+
let (hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, generics, _), .. })
82+
| hir::Node::TraitItem(hir::TraitItem {
83+
kind: hir::TraitItemKind::Fn(sig, _),
84+
generics,
85+
..
86+
})) = tcx.hir_node_by_def_id(parent_id)
87+
else {
88+
return false;
89+
};
90+
let Ok(trait_name) = tcx.sess.source_map().span_to_snippet(self_ty.span) else {
91+
return false;
92+
};
93+
let impl_sugg = vec![(self_ty.span.shrink_to_lo(), "impl ".to_string())];
94+
let is_object_safe = match self_ty.kind {
95+
hir::TyKind::TraitObject(objects, ..) => {
96+
objects.iter().all(|o| match o.trait_ref.path.res {
97+
Res::Def(DefKind::Trait, id) => tcx.check_is_object_safe(id),
98+
_ => false,
99+
})
100+
}
101+
_ => false,
102+
};
103+
if let hir::FnRetTy::Return(ty) = sig.decl.output
104+
&& ty.hir_id == self_ty.hir_id
105+
{
106+
let pre = if !is_object_safe {
107+
format!("`{trait_name}` is not object safe, ")
108+
} else {
109+
String::new()
110+
};
111+
let msg = format!(
112+
"{pre}use `impl {trait_name}` to return an opaque type, as long as you return a \
113+
single underlying type",
114+
);
115+
diag.multipart_suggestion_verbose(msg, impl_sugg, Applicability::MachineApplicable);
116+
if is_object_safe {
117+
diag.multipart_suggestion_verbose(
118+
"alternatively, you can return an owned trait object",
119+
vec![
120+
(ty.span.shrink_to_lo(), "Box<dyn ".to_string()),
121+
(ty.span.shrink_to_hi(), ">".to_string()),
122+
],
123+
Applicability::MachineApplicable,
124+
);
125+
} else {
126+
// We'll emit the object safety error already, with a structured suggestion.
127+
diag.downgrade_to_delayed_bug();
128+
}
129+
return true;
130+
}
131+
for ty in sig.decl.inputs {
132+
if ty.hir_id != self_ty.hir_id {
133+
continue;
134+
}
135+
let sugg = self.add_generic_param_suggestion(generics, self_ty.span, &trait_name);
136+
if !sugg.is_empty() {
137+
diag.multipart_suggestion_verbose(
138+
format!("use a new generic type parameter, constrained by `{trait_name}`"),
139+
sugg,
140+
Applicability::MachineApplicable,
141+
);
142+
diag.multipart_suggestion_verbose(
143+
"you can also use an opaque type, but users won't be able to specify the type \
144+
parameter when calling the `fn`, having to rely exclusively on type inference",
145+
impl_sugg,
146+
Applicability::MachineApplicable,
147+
);
148+
}
149+
if !is_object_safe {
150+
diag.note(format!("`{trait_name}` it is not object safe, so it can't be `dyn`"));
151+
// We'll emit the object safety error already, with a structured suggestion.
152+
diag.downgrade_to_delayed_bug();
153+
} else {
154+
let sugg = if let hir::TyKind::TraitObject([_, _, ..], _, _) = self_ty.kind {
155+
// There are more than one trait bound, we need surrounding parentheses.
156+
vec![
157+
(self_ty.span.shrink_to_lo(), "&(dyn ".to_string()),
158+
(self_ty.span.shrink_to_hi(), ")".to_string()),
159+
]
160+
} else {
161+
vec![(self_ty.span.shrink_to_lo(), "&dyn ".to_string())]
162+
};
163+
diag.multipart_suggestion_verbose(
164+
format!(
165+
"alternatively, use a trait object to accept any type that implements \
166+
`{trait_name}`, accessing its methods at runtime using dynamic dispatch",
167+
),
168+
sugg,
169+
Applicability::MachineApplicable,
170+
);
171+
}
172+
return true;
173+
}
174+
false
175+
}
176+
61177
pub(super) fn maybe_lint_bare_trait(&self, self_ty: &hir::Ty<'_>, in_path: bool) {
62178
let tcx = self.tcx();
63179
if let hir::TyKind::TraitObject([poly_trait_ref, ..], _, TraitObjectSyntax::None) =
@@ -98,7 +214,9 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
98214
let label = "add `dyn` keyword before this trait";
99215
let mut diag =
100216
rustc_errors::struct_span_err!(tcx.dcx(), self_ty.span, E0782, "{}", msg);
101-
if self_ty.span.can_be_used_for_suggestions() {
217+
if self_ty.span.can_be_used_for_suggestions()
218+
&& !self.maybe_lint_impl_trait(self_ty, &mut diag)
219+
{
102220
diag.multipart_suggestion_verbose(
103221
label,
104222
sugg,
@@ -116,11 +234,15 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
116234
self_ty.span,
117235
msg,
118236
|lint| {
119-
lint.multipart_suggestion_verbose(
120-
"use `dyn`",
121-
sugg,
122-
Applicability::MachineApplicable,
123-
);
237+
if self_ty.span.can_be_used_for_suggestions()
238+
&& !self.maybe_lint_impl_trait(self_ty, lint)
239+
{
240+
lint.multipart_suggestion_verbose(
241+
"use `dyn`",
242+
sugg,
243+
Applicability::MachineApplicable,
244+
);
245+
}
124246
self.maybe_lint_blanket_trait_impl(self_ty, lint);
125247
},
126248
);

compiler/rustc_hir_analysis/src/astconv/object_safety.rs

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
140140
let reported = report_object_safety_error(
141141
tcx,
142142
span,
143+
Some(hir_id),
143144
item.trait_ref().def_id(),
144145
&object_safety_violations,
145146
)

compiler/rustc_hir_typeck/src/check.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -73,22 +73,23 @@ pub(super) fn check_fn<'a, 'tcx>(
7373
let inputs_fn = fn_sig.inputs().iter().copied();
7474
for (idx, (param_ty, param)) in inputs_fn.chain(maybe_va_list).zip(body.params).enumerate() {
7575
// Check the pattern.
76-
let ty_span = try { inputs_hir?.get(idx)?.span };
76+
let ty: Option<&hir::Ty<'_>> = try { inputs_hir?.get(idx)? };
77+
let ty_span = ty.map(|ty| ty.span);
7778
fcx.check_pat_top(param.pat, param_ty, ty_span, None, None);
7879

7980
// Check that argument is Sized.
8081
if !params_can_be_unsized {
8182
fcx.require_type_is_sized(
8283
param_ty,
8384
param.pat.span,
84-
// ty_span == binding_span iff this is a closure parameter with no type ascription,
85+
// ty.span == binding_span iff this is a closure parameter with no type ascription,
8586
// or if it's an implicit `self` parameter
8687
traits::SizedArgumentType(
8788
if ty_span == Some(param.span) && tcx.is_closure_or_coroutine(fn_def_id.into())
8889
{
8990
None
9091
} else {
91-
ty_span
92+
ty.map(|ty| ty.hir_id)
9293
},
9394
),
9495
);

compiler/rustc_hir_typeck/src/gather_locals.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub(super) struct GatherLocalsVisitor<'a, 'tcx> {
6060
// parameters are special cases of patterns, but we want to handle them as
6161
// *distinct* cases. so track when we are hitting a pattern *within* an fn
6262
// parameter.
63-
outermost_fn_param_pat: Option<Span>,
63+
outermost_fn_param_pat: Option<(Span, hir::HirId)>,
6464
}
6565

6666
impl<'a, 'tcx> GatherLocalsVisitor<'a, 'tcx> {
@@ -131,7 +131,8 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherLocalsVisitor<'a, 'tcx> {
131131
}
132132

133133
fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
134-
let old_outermost_fn_param_pat = self.outermost_fn_param_pat.replace(param.ty_span);
134+
let old_outermost_fn_param_pat =
135+
self.outermost_fn_param_pat.replace((param.ty_span, param.hir_id));
135136
intravisit::walk_param(self, param);
136137
self.outermost_fn_param_pat = old_outermost_fn_param_pat;
137138
}
@@ -141,7 +142,7 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherLocalsVisitor<'a, 'tcx> {
141142
if let PatKind::Binding(_, _, ident, _) = p.kind {
142143
let var_ty = self.assign(p.span, p.hir_id, None);
143144

144-
if let Some(ty_span) = self.outermost_fn_param_pat {
145+
if let Some((ty_span, hir_id)) = self.outermost_fn_param_pat {
145146
if !self.fcx.tcx.features().unsized_fn_params {
146147
self.fcx.require_type_is_sized(
147148
var_ty,
@@ -154,7 +155,7 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherLocalsVisitor<'a, 'tcx> {
154155
{
155156
None
156157
} else {
157-
Some(ty_span)
158+
Some(hir_id)
158159
},
159160
),
160161
);

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

+21-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ use super::ObjectSafetyViolation;
22

33
use crate::infer::InferCtxt;
44
use rustc_data_structures::fx::FxIndexSet;
5-
use rustc_errors::{struct_span_err, DiagnosticBuilder, MultiSpan};
5+
use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, MultiSpan};
66
use rustc_hir as hir;
77
use rustc_hir::def_id::{DefId, LocalDefId};
8+
use rustc_hir::intravisit::Map;
89
use rustc_middle::ty::print::with_no_trimmed_paths;
910
use rustc_middle::ty::{self, TyCtxt};
1011
use rustc_span::Span;
@@ -42,6 +43,7 @@ impl<'tcx> InferCtxt<'tcx> {
4243
pub fn report_object_safety_error<'tcx>(
4344
tcx: TyCtxt<'tcx>,
4445
span: Span,
46+
hir_id: Option<hir::HirId>,
4547
trait_def_id: DefId,
4648
violations: &[ObjectSafetyViolation],
4749
) -> DiagnosticBuilder<'tcx> {
@@ -59,6 +61,24 @@ pub fn report_object_safety_error<'tcx>(
5961
);
6062
err.span_label(span, format!("`{trait_str}` cannot be made into an object"));
6163

64+
if let Some(hir_id) = hir_id
65+
&& let Some(hir::Node::Ty(ty)) = tcx.hir().find(hir_id)
66+
&& let hir::TyKind::TraitObject([trait_ref, ..], ..) = ty.kind
67+
{
68+
let mut hir_id = hir_id;
69+
while let hir::Node::Ty(ty) = tcx.hir().get_parent(hir_id) {
70+
hir_id = ty.hir_id;
71+
}
72+
if tcx.hir().get_parent(hir_id).fn_sig().is_some() {
73+
// Do not suggest `impl Trait` when dealing with things like super-traits.
74+
err.span_suggestion_verbose(
75+
ty.span.until(trait_ref.span),
76+
"consider using an opaque type instead",
77+
"impl ",
78+
Applicability::MaybeIncorrect,
79+
);
80+
}
81+
}
6282
let mut reported_violations = FxIndexSet::default();
6383
let mut multi_span = vec![];
6484
let mut messages = vec![];

compiler/rustc_middle/src/traits/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ pub enum ObligationCauseCode<'tcx> {
289289
/// Type of each variable must be `Sized`.
290290
VariableType(hir::HirId),
291291
/// Argument type must be `Sized`.
292-
SizedArgumentType(Option<Span>),
292+
SizedArgumentType(Option<hir::HirId>),
293293
/// Return type must be `Sized`.
294294
SizedReturnType,
295295
/// Yield type must be `Sized`.

0 commit comments

Comments
 (0)