Skip to content

Commit 57b79f7

Browse files
Suggest impl Trait for References to Bare Trait in Function Header
1 parent 102997a commit 57b79f7

File tree

5 files changed

+173
-42
lines changed

5 files changed

+173
-42
lines changed

compiler/rustc_hir/src/hir.rs

+14
Original file line numberDiff line numberDiff line change
@@ -2515,6 +2515,20 @@ impl<'hir> Ty<'hir> {
25152515
final_ty
25162516
}
25172517

2518+
/// Check whether type is a reference with anonymous lifetime
2519+
pub fn is_ref_with_anonymous_lifetime(&self) -> bool {
2520+
if let TyKind::Ref(lifetime, MutTy { mutbl: Mutability::Not, .. }) = self.kind {
2521+
lifetime.is_anonymous()
2522+
} else {
2523+
false
2524+
}
2525+
}
2526+
2527+
/// Check whether type is a mutable reference to some type
2528+
pub fn is_mut_ref(&self) -> bool {
2529+
matches!(self.kind, TyKind::Ref(_, MutTy { mutbl: Mutability::Mut, .. }))
2530+
}
2531+
25182532
pub fn find_self_aliases(&self) -> Vec<Span> {
25192533
use crate::intravisit::Visitor;
25202534
struct MyVisitor(Vec<Span>);

compiler/rustc_hir_analysis/src/hir_ty_lowering/lint.rs

+90-27
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ use rustc_hir as hir;
44
use rustc_hir::def::{DefKind, Res};
55
use rustc_lint_defs::{builtin::BARE_TRAIT_OBJECTS, Applicability};
66
use rustc_span::Span;
7-
use rustc_trait_selection::error_reporting::traits::suggestions::NextTypeParamName;
7+
use rustc_trait_selection::error_reporting::traits::suggestions::{
8+
NextLifetimeParamName, NextTypeParamName,
9+
};
810

911
use super::HirTyLowerer;
1012

@@ -17,6 +19,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
1719
&self,
1820
self_ty: &hir::Ty<'_>,
1921
in_path: bool,
22+
borrowed: bool,
2023
) {
2124
let tcx = self.tcx();
2225

@@ -62,7 +65,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
6265
let mut diag =
6366
rustc_errors::struct_span_code_err!(self.dcx(), self_ty.span, E0782, "{}", msg);
6467
if self_ty.span.can_be_used_for_suggestions()
65-
&& !self.maybe_suggest_impl_trait(self_ty, &mut diag)
68+
&& !self.maybe_suggest_impl_trait(self_ty, &mut diag, borrowed)
6669
{
6770
// FIXME: Only emit this suggestion if the trait is object safe.
6871
diag.multipart_suggestion_verbose(label, sugg, Applicability::MachineApplicable);
@@ -120,9 +123,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
120123
return;
121124
};
122125
let sugg = self.add_generic_param_suggestion(generics, self_ty.span, &impl_trait_name);
123-
if sugg.is_empty() {
124-
return;
125-
};
126126
diag.multipart_suggestion(
127127
format!(
128128
"alternatively use a blanket implementation to implement `{of_trait_name}` for \
@@ -152,11 +152,20 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
152152
}
153153

154154
/// Make sure that we are in the condition to suggest `impl Trait`.
155-
fn maybe_suggest_impl_trait(&self, self_ty: &hir::Ty<'_>, diag: &mut Diag<'_>) -> bool {
155+
fn maybe_suggest_impl_trait(
156+
&self,
157+
self_ty: &hir::Ty<'_>,
158+
diag: &mut Diag<'_>,
159+
borrowed: bool,
160+
) -> bool {
156161
let tcx = self.tcx();
157162
let parent_id = tcx.hir().get_parent_item(self_ty.hir_id).def_id;
158163
// FIXME: If `type_alias_impl_trait` is enabled, also look for `Trait0<Ty = Trait1>`
159164
// and suggest `Trait0<Ty = impl Trait1>`.
165+
// Functions are found in three different contexts.
166+
// 1. Independent functions
167+
// 2. Functions inside trait blocks
168+
// 3. Functions inside impl blocks
160169
let (sig, generics, owner) = match tcx.hir_node_by_def_id(parent_id) {
161170
hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, generics, _), .. }) => {
162171
(sig, generics, None)
@@ -167,13 +176,21 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
167176
owner_id,
168177
..
169178
}) => (sig, generics, Some(tcx.parent(owner_id.to_def_id()))),
179+
hir::Node::ImplItem(hir::ImplItem {
180+
kind: hir::ImplItemKind::Fn(sig, _),
181+
generics,
182+
owner_id,
183+
..
184+
}) => (sig, generics, Some(tcx.parent(owner_id.to_def_id()))),
170185
_ => return false,
171186
};
172187
let Ok(trait_name) = tcx.sess.source_map().span_to_snippet(self_ty.span) else {
173188
return false;
174189
};
175190
let impl_sugg = vec![(self_ty.span.shrink_to_lo(), "impl ".to_string())];
176191
let mut is_downgradable = true;
192+
193+
// Check if trait object is safe for suggesting dynamic dispatch.
177194
let is_object_safe = match self_ty.kind {
178195
hir::TyKind::TraitObject(objects, ..) => {
179196
objects.iter().all(|o| match o.trait_ref.path.res {
@@ -189,8 +206,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
189206
}
190207
_ => false,
191208
};
209+
210+
// Suggestions for function return type.
192211
if let hir::FnRetTy::Return(ty) = sig.decl.output
193-
&& ty.hir_id == self_ty.hir_id
212+
&& ty.peel_refs().hir_id == self_ty.hir_id
194213
{
195214
let pre = if !is_object_safe {
196215
format!("`{trait_name}` is not object safe, ")
@@ -201,14 +220,54 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
201220
"{pre}use `impl {trait_name}` to return an opaque type, as long as you return a \
202221
single underlying type",
203222
);
204-
diag.multipart_suggestion_verbose(msg, impl_sugg, Applicability::MachineApplicable);
223+
224+
// Six different cases to consider when suggesting `impl Trait` for a return type:
225+
// 1. `fn fun() -> Trait {}` => Suggest `impl Trait` without mentioning anything about lifetime
226+
// 2. `fn fun<'a>() -> &'a Trait {}` => Suggest `impl Trait` without mentioning anything about lifetime
227+
// 3. `fn fun() -> &'a Trait {}` => Suggest `impl Trait` and an other error (E0261) will suggest to declare the lifetime
228+
// 4. `fn fun() -> &Trait {}` => Suggest to declare and use a fresh lifetime
229+
// 5. `fn fun<'a>() -> &Trait {}` => Suggest to declare and use a fresh lifetime
230+
// 6. `fn fun() -> &mut Trait {}` => Suggest `impl Trait` and mention that returning a mutable reference to a bare trait is impossible
231+
let suggestion = if ty.is_mut_ref() {
232+
// case 6
233+
diag.primary_message("cannot return a mutable reference to a bare trait");
234+
vec![(ty.span, format!("impl {trait_name}"))]
235+
} else if ty.is_ref_with_anonymous_lifetime() {
236+
// cases 4 and 5
237+
let lifetime = generics.params.next_lifetime_param_name(None);
238+
239+
let lifetime_decl = if let Some(span) = generics.span_for_lifetime_suggestion() {
240+
(span, format!("{lifetime}, "))
241+
} else {
242+
(generics.span, format!("<{lifetime}>"))
243+
};
244+
245+
let impl_with_lifetime = (self_ty.span.shrink_to_lo(), format!("{lifetime} impl "));
246+
vec![lifetime_decl, impl_with_lifetime]
247+
} else {
248+
// cases 1, 2, and 3
249+
impl_sugg
250+
};
251+
252+
diag.multipart_suggestion_verbose(msg, suggestion, Applicability::MachineApplicable);
253+
254+
// Suggest `Box<dyn Trait>` for return type
205255
if is_object_safe {
206-
diag.multipart_suggestion_verbose(
207-
"alternatively, you can return an owned trait object",
256+
// If the return type is `&Trait`, we don't want
257+
// the ampersand to be displayed in the `Box<dyn Trait>`
258+
// suggestion.
259+
let suggestion = if borrowed {
260+
vec![(ty.span, format!("Box<dyn {trait_name}>"))]
261+
} else {
208262
vec![
209263
(ty.span.shrink_to_lo(), "Box<dyn ".to_string()),
210264
(ty.span.shrink_to_hi(), ">".to_string()),
211-
],
265+
]
266+
};
267+
268+
diag.multipart_suggestion_verbose(
269+
"alternatively, you can return an owned trait object",
270+
suggestion,
212271
Applicability::MachineApplicable,
213272
);
214273
} else if is_downgradable {
@@ -217,39 +276,43 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
217276
}
218277
return true;
219278
}
279+
280+
// Suggestions for function parameters.
220281
for ty in sig.decl.inputs {
221-
if ty.hir_id != self_ty.hir_id {
282+
if ty.peel_refs().hir_id != self_ty.hir_id {
222283
continue;
223284
}
224285
let sugg = self.add_generic_param_suggestion(generics, self_ty.span, &trait_name);
225-
if !sugg.is_empty() {
226-
diag.multipart_suggestion_verbose(
227-
format!("use a new generic type parameter, constrained by `{trait_name}`"),
228-
sugg,
229-
Applicability::MachineApplicable,
230-
);
231-
diag.multipart_suggestion_verbose(
232-
"you can also use an opaque type, but users won't be able to specify the type \
233-
parameter when calling the `fn`, having to rely exclusively on type inference",
234-
impl_sugg,
235-
Applicability::MachineApplicable,
236-
);
237-
}
286+
diag.multipart_suggestion_verbose(
287+
format!("use a new generic type parameter, constrained by `{trait_name}`"),
288+
sugg,
289+
Applicability::MachineApplicable,
290+
);
291+
diag.multipart_suggestion_verbose(
292+
"you can also use an opaque type, but users won't be able to specify the type \
293+
parameter when calling the `fn`, having to rely exclusively on type inference",
294+
impl_sugg,
295+
Applicability::MachineApplicable,
296+
);
238297
if !is_object_safe {
239298
diag.note(format!("`{trait_name}` it is not object safe, so it can't be `dyn`"));
240299
if is_downgradable {
241300
// We'll emit the object safety error already, with a structured suggestion.
242301
diag.downgrade_to_delayed_bug();
243302
}
244303
} else {
304+
// No ampersand in suggestion if it's borrowed already
305+
let (dyn_str, paren_dyn_str) =
306+
if borrowed { ("dyn ", "(dyn ") } else { ("&dyn ", "&(dyn ") };
307+
245308
let sugg = if let hir::TyKind::TraitObject([_, _, ..], _, _) = self_ty.kind {
246309
// There are more than one trait bound, we need surrounding parentheses.
247310
vec![
248-
(self_ty.span.shrink_to_lo(), "&(dyn ".to_string()),
311+
(self_ty.span.shrink_to_lo(), paren_dyn_str.to_string()),
249312
(self_ty.span.shrink_to_hi(), ")".to_string()),
250313
]
251314
} else {
252-
vec![(self_ty.span.shrink_to_lo(), "&dyn ".to_string())]
315+
vec![(self_ty.span.shrink_to_lo(), dyn_str.to_string())]
253316
};
254317
diag.multipart_suggestion_verbose(
255318
format!(

compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2078,7 +2078,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
20782078
)
20792079
}
20802080
hir::TyKind::TraitObject(bounds, lifetime, repr) => {
2081-
self.prohibit_or_lint_bare_trait_object_ty(hir_ty, in_path);
2081+
self.prohibit_or_lint_bare_trait_object_ty(hir_ty, in_path, borrowed);
20822082

20832083
let repr = match repr {
20842084
TraitObjectSyntax::Dyn | TraitObjectSyntax::None => ty::Dyn,

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

+67-13
Original file line numberDiff line numberDiff line change
@@ -4925,29 +4925,83 @@ impl<'v> Visitor<'v> for AwaitsVisitor {
49254925
}
49264926
}
49274927

4928+
/// Function to suggest a type or lifetime parameter.
4929+
///
4930+
/// `possible_names` is the list of names from which a name will be choosen.
4931+
/// `used_names` is the list of `hir::GenericsParam`s already in use.
4932+
/// `filter_fn` is a function to filter out the desired names from `used_names`.
4933+
/// `default_name` is the name to suggest when all `possible_names` are in `used_names`.
4934+
fn next_param_name<F: FnMut(&hir::GenericParam<'_>) -> Option<Symbol>>(
4935+
possible_names: [&str; 10],
4936+
used_names: &[hir::GenericParam<'_>],
4937+
filter_fn: F,
4938+
default_name: &str,
4939+
) -> String {
4940+
// Filter out used names based on `filter_fn`.
4941+
let used_names = used_names.iter().filter_map(filter_fn).collect::<Vec<_>>();
4942+
4943+
// Find a name from `possible_names` that is not in `used_names`.
4944+
possible_names
4945+
.iter()
4946+
.find(|n| !used_names.contains(&Symbol::intern(n)))
4947+
.unwrap_or(&default_name)
4948+
.to_string()
4949+
}
4950+
4951+
/// Suggest a new type parameter name for diagnostic purposes.
4952+
///
4953+
/// `name` is the preferred name you'd like to suggest if it's not in use already.
49284954
pub trait NextTypeParamName {
49294955
fn next_type_param_name(&self, name: Option<&str>) -> String;
49304956
}
49314957

49324958
impl NextTypeParamName for &[hir::GenericParam<'_>] {
49334959
fn next_type_param_name(&self, name: Option<&str>) -> String {
4934-
// This is the list of possible parameter names that we might suggest.
4960+
// Type names are usually single letters in uppercase. So convert the first letter of input string to uppercase.
49354961
let name = name.and_then(|n| n.chars().next()).map(|c| c.to_uppercase().to_string());
49364962
let name = name.as_deref();
4963+
4964+
// This is the list of possible parameter names that we might suggest.
49374965
let possible_names = [name.unwrap_or("T"), "T", "U", "V", "X", "Y", "Z", "A", "B", "C"];
4938-
let used_names = self
4939-
.iter()
4940-
.filter_map(|p| match p.name {
4941-
hir::ParamName::Plain(ident) => Some(ident.name),
4942-
_ => None,
4943-
})
4944-
.collect::<Vec<_>>();
49454966

4946-
possible_names
4947-
.iter()
4948-
.find(|n| !used_names.contains(&Symbol::intern(n)))
4949-
.unwrap_or(&"ParamName")
4950-
.to_string()
4967+
// Filter out the existing type parameter names.
4968+
let filter_fn = |p: &hir::GenericParam<'_>| match p.name {
4969+
hir::ParamName::Plain(ident) => Some(ident.name),
4970+
_ => None,
4971+
};
4972+
next_param_name(possible_names, self, filter_fn, "ParamName")
4973+
}
4974+
}
4975+
4976+
/// Suggest a new lifetime parameter name for diagnostic purposes.
4977+
///
4978+
/// `name` is the preferred name you'd like to suggest if it's not in use already.
4979+
/// Note: `name`, if provided, should begin with an apostrophe and followed by a lifetime name.
4980+
/// The output string will also begin with an apostrophe and follwed by a lifetime name.
4981+
pub trait NextLifetimeParamName {
4982+
fn next_lifetime_param_name(&self, name: Option<&str>) -> String;
4983+
}
4984+
4985+
impl NextLifetimeParamName for &[hir::GenericParam<'_>] {
4986+
fn next_lifetime_param_name(&self, name: Option<&str>) -> String {
4987+
// Lifetimes are usually in lowercase. So transform input string to lowercase.
4988+
let name = name.map(|n| n.to_lowercase());
4989+
let name = name.as_deref();
4990+
// This is the list of possible lifetime names that we might suggest.
4991+
let possible_names =
4992+
[name.unwrap_or("'a"), "'a", "'b", "'c", "'d", "'e", "'f", "'g", "'h", "'i"];
4993+
// Filter out the existing lifetime names
4994+
let filter_fn = |p: &hir::GenericParam<'_>| {
4995+
if matches!(p.kind, hir::GenericParamKind::Lifetime { .. }) {
4996+
match p.name {
4997+
hir::ParamName::Plain(ident) => Some(ident.name),
4998+
_ => None,
4999+
}
5000+
} else {
5001+
None
5002+
}
5003+
};
5004+
next_param_name(possible_names, self, filter_fn, "LifetimeName")
49515005
}
49525006
}
49535007

tests/ui/object-safety/reference-to-bare-trait-in-fn-inputs-and-outputs-issue-125139.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ trait Sing {
9191
//~^ ERROR: cannot return reference to temporary value
9292
}
9393
}
94-
94+
9595
fn foo(_: &Trait) {}
9696
//~^ ERROR: trait objects must include the `dyn` keyword
9797

0 commit comments

Comments
 (0)