Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Suggest impl Trait for References to Bare Trait in Function Header #127692

Merged
merged 2 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 55 additions & 23 deletions compiler/rustc_hir_analysis/src/hir_ty_lowering/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
return;
};
let sugg = self.add_generic_param_suggestion(generics, self_ty.span, &impl_trait_name);
if sugg.is_empty() {
return;
};
diag.multipart_suggestion(
format!(
"alternatively use a blanket implementation to implement `{of_trait_name}` for \
Expand Down Expand Up @@ -157,6 +154,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
let parent_id = tcx.hir().get_parent_item(self_ty.hir_id).def_id;
// FIXME: If `type_alias_impl_trait` is enabled, also look for `Trait0<Ty = Trait1>`
// and suggest `Trait0<Ty = impl Trait1>`.
// Functions are found in three different contexts.
// 1. Independent functions
// 2. Functions inside trait blocks
// 3. Functions inside impl blocks
let (sig, generics, owner) = match tcx.hir_node_by_def_id(parent_id) {
hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, generics, _), .. }) => {
(sig, generics, None)
Expand All @@ -167,13 +168,21 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
owner_id,
..
}) => (sig, generics, Some(tcx.parent(owner_id.to_def_id()))),
hir::Node::ImplItem(hir::ImplItem {
kind: hir::ImplItemKind::Fn(sig, _),
generics,
owner_id,
..
}) => (sig, generics, Some(tcx.parent(owner_id.to_def_id()))),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't wrong, but I'm not sure why this is necessary to add for these changes. Can you explain what's being changed by adding this arm?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this for consistency so that we show the same suggestions in E0782 for function items and function inside trait blocks and impl blocks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm asking in what specific case you discovered this? It's really hard to determine what changes affect which diagnostics when it's all together like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, E0782 suggests impl Trait when Trait is found as function parameter or return type. I noticed that this suggestion is not being made for functions inside impl block and decided to add this arm for consistency. The original author probably forgot to add it.

It does not add anything to the diagnostic. It just ensures that the existing diagnostics apply to functions inside impl blocks as well.

_ => return false,
};
let Ok(trait_name) = tcx.sess.source_map().span_to_snippet(self_ty.span) else {
return false;
};
let impl_sugg = vec![(self_ty.span.shrink_to_lo(), "impl ".to_string())];
let mut is_downgradable = true;

// Check if trait object is safe for suggesting dynamic dispatch.
let is_object_safe = match self_ty.kind {
hir::TyKind::TraitObject(objects, ..) => {
objects.iter().all(|o| match o.trait_ref.path.res {
Expand All @@ -189,8 +198,15 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
}
_ => false,
};

let borrowed = matches!(
tcx.parent_hir_node(self_ty.hir_id),
hir::Node::Ty(hir::Ty { kind: hir::TyKind::Ref(..), .. })
);

// Suggestions for function return type.
if let hir::FnRetTy::Return(ty) = sig.decl.output
&& ty.hir_id == self_ty.hir_id
&& ty.peel_refs().hir_id == self_ty.hir_id
{
let pre = if !is_object_safe {
format!("`{trait_name}` is not object safe, ")
Expand All @@ -201,14 +217,26 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
"{pre}use `impl {trait_name}` to return an opaque type, as long as you return a \
single underlying type",
);

diag.multipart_suggestion_verbose(msg, impl_sugg, Applicability::MachineApplicable);

// Suggest `Box<dyn Trait>` for return type
if is_object_safe {
diag.multipart_suggestion_verbose(
"alternatively, you can return an owned trait object",
// If the return type is `&Trait`, we don't want
// the ampersand to be displayed in the `Box<dyn Trait>`
// suggestion.
let suggestion = if borrowed {
vec![(ty.span, format!("Box<dyn {trait_name}>"))]
} else {
vec![
(ty.span.shrink_to_lo(), "Box<dyn ".to_string()),
(ty.span.shrink_to_hi(), ">".to_string()),
],
]
};

diag.multipart_suggestion_verbose(
"alternatively, you can return an owned trait object",
suggestion,
Applicability::MachineApplicable,
);
} else if is_downgradable {
Expand All @@ -217,39 +245,43 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
}
return true;
}

// Suggestions for function parameters.
for ty in sig.decl.inputs {
if ty.hir_id != self_ty.hir_id {
if ty.peel_refs().hir_id != self_ty.hir_id {
continue;
}
let sugg = self.add_generic_param_suggestion(generics, self_ty.span, &trait_name);
if !sugg.is_empty() {
diag.multipart_suggestion_verbose(
format!("use a new generic type parameter, constrained by `{trait_name}`"),
sugg,
Applicability::MachineApplicable,
);
diag.multipart_suggestion_verbose(
"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",
impl_sugg,
Applicability::MachineApplicable,
);
}
diag.multipart_suggestion_verbose(
format!("use a new generic type parameter, constrained by `{trait_name}`"),
sugg,
Applicability::MachineApplicable,
);
diag.multipart_suggestion_verbose(
"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",
impl_sugg,
Applicability::MachineApplicable,
);
if !is_object_safe {
diag.note(format!("`{trait_name}` it is not object safe, so it can't be `dyn`"));
if is_downgradable {
// We'll emit the object safety error already, with a structured suggestion.
diag.downgrade_to_delayed_bug();
}
} else {
// No ampersand in suggestion if it's borrowed already
let (dyn_str, paren_dyn_str) =
if borrowed { ("dyn ", "(dyn ") } else { ("&dyn ", "&(dyn ") };

let sugg = if let hir::TyKind::TraitObject([_, _, ..], _, _) = self_ty.kind {
// There are more than one trait bound, we need surrounding parentheses.
vec![
(self_ty.span.shrink_to_lo(), "&(dyn ".to_string()),
(self_ty.span.shrink_to_lo(), paren_dyn_str.to_string()),
(self_ty.span.shrink_to_hi(), ")".to_string()),
]
} else {
vec![(self_ty.span.shrink_to_lo(), "&dyn ".to_string())]
vec![(self_ty.span.shrink_to_lo(), dyn_str.to_string())]
};
diag.multipart_suggestion_verbose(
format!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4925,24 +4925,32 @@ impl<'v> Visitor<'v> for AwaitsVisitor {
}
}

/// Suggest a new type parameter name for diagnostic purposes.
///
/// `name` is the preferred name you'd like to suggest if it's not in use already.
pub trait NextTypeParamName {
fn next_type_param_name(&self, name: Option<&str>) -> String;
}

impl NextTypeParamName for &[hir::GenericParam<'_>] {
fn next_type_param_name(&self, name: Option<&str>) -> String {
// This is the list of possible parameter names that we might suggest.
// Type names are usually single letters in uppercase. So convert the first letter of input string to uppercase.
let name = name.and_then(|n| n.chars().next()).map(|c| c.to_uppercase().to_string());
let name = name.as_deref();

// This is the list of possible parameter names that we might suggest.
let possible_names = [name.unwrap_or("T"), "T", "U", "V", "X", "Y", "Z", "A", "B", "C"];
let used_names = self

// Filter out used names based on `filter_fn`.
let used_names: Vec<Symbol> = self
.iter()
.filter_map(|p| match p.name {
.filter_map(|param| match param.name {
hir::ParamName::Plain(ident) => Some(ident.name),
_ => None,
})
.collect::<Vec<_>>();
.collect();

// Find a name from `possible_names` that is not in `used_names`.
possible_names
.iter()
.find(|n| !used_names.contains(&Symbol::intern(n)))
Expand Down
10 changes: 9 additions & 1 deletion tests/ui/dyn-keyword/dyn-2021-edition-error.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ error[E0782]: trait objects must include the `dyn` keyword
LL | fn function(x: &SomeTrait, y: Box<SomeTrait>) {
| ^^^^^^^^^
|
help: add `dyn` keyword before this trait
help: use a new generic type parameter, constrained by `SomeTrait`
|
LL | fn function<T: SomeTrait>(x: &T, y: Box<SomeTrait>) {
| ++++++++++++++ ~
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
|
LL | fn function(x: &impl SomeTrait, y: Box<SomeTrait>) {
| ++++
help: alternatively, use a trait object to accept any type that implements `SomeTrait`, accessing its methods at runtime using dynamic dispatch
|
LL | fn function(x: &dyn SomeTrait, y: Box<SomeTrait>) {
| +++
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
//@ edition:2021

trait Trait {}

struct IceCream;

impl IceCream {
fn foo(_: &Trait) {}
//~^ ERROR: trait objects must include the `dyn` keyword

fn bar(self, _: &'a Trait) {}
//~^ ERROR: trait objects must include the `dyn` keyword
//~| ERROR: use of undeclared lifetime name

fn alice<'a>(&self, _: &Trait) {}
//~^ ERROR: trait objects must include the `dyn` keyword

fn bob<'a>(_: &'a Trait) {}
//~^ ERROR: trait objects must include the `dyn` keyword

fn cat() -> &Trait {
//~^ ERROR: missing lifetime specifier
//~| ERROR: trait objects must include the `dyn` keyword
&Type
}

fn dog<'a>() -> &Trait {
//~^ ERROR: missing lifetime specifier
//~| ERROR: trait objects must include the `dyn` keyword
&Type
}

fn kitten() -> &'a Trait {
//~^ ERROR: use of undeclared lifetime name
//~| ERROR: trait objects must include the `dyn` keyword
&Type
}

fn puppy<'a>() -> &'a Trait {
//~^ ERROR: trait objects must include the `dyn` keyword
&Type
}

fn parrot() -> &mut Trait {
//~^ ERROR: missing lifetime specifier
//~| ERROR: trait objects must include the `dyn` keyword
&mut Type
//~^ ERROR: cannot return reference to temporary value
}
}

trait Sing {
fn foo(_: &Trait);
//~^ ERROR: trait objects must include the `dyn` keyword

fn bar(_: &'a Trait);
//~^ ERROR: trait objects must include the `dyn` keyword
//~| ERROR: use of undeclared lifetime name

fn alice<'a>(_: &Trait);
//~^ ERROR: trait objects must include the `dyn` keyword

fn bob<'a>(_: &'a Trait);
//~^ ERROR: trait objects must include the `dyn` keyword

fn cat() -> &Trait;
//~^ ERROR: missing lifetime specifier
//~| ERROR: trait objects must include the `dyn` keyword

fn dog<'a>() -> &Trait {
//~^ ERROR: missing lifetime specifier
//~| ERROR: trait objects must include the `dyn` keyword
&Type
}

fn kitten() -> &'a Trait {
//~^ ERROR: use of undeclared lifetime name
//~| ERROR: trait objects must include the `dyn` keyword
&Type
}

fn puppy<'a>() -> &'a Trait {
//~^ ERROR: trait objects must include the `dyn` keyword
&Type
}

fn parrot() -> &mut Trait {
//~^ ERROR: missing lifetime specifier
//~| ERROR: trait objects must include the `dyn` keyword
&mut Type
//~^ ERROR: cannot return reference to temporary value
}
}

fn foo(_: &Trait) {}
//~^ ERROR: trait objects must include the `dyn` keyword

fn bar(_: &'a Trait) {}
//~^ ERROR: trait objects must include the `dyn` keyword
//~| ERROR: use of undeclared lifetime name

fn alice<'a>(_: &Trait) {}
//~^ ERROR: trait objects must include the `dyn` keyword

fn bob<'a>(_: &'a Trait) {}
//~^ ERROR: trait objects must include the `dyn` keyword

struct Type;

impl Trait for Type {}

fn cat() -> &Trait {
//~^ ERROR: missing lifetime specifier
//~| ERROR: trait objects must include the `dyn` keyword
&Type
}

fn dog<'a>() -> &Trait {
//~^ ERROR: missing lifetime specifier
//~| ERROR: trait objects must include the `dyn` keyword
&Type
}

fn kitten() -> &'a Trait {
//~^ ERROR: use of undeclared lifetime name
//~| ERROR: trait objects must include the `dyn` keyword
&Type
}

fn puppy<'a>() -> &'a Trait {
//~^ ERROR: trait objects must include the `dyn` keyword
&Type
}

fn parrot() -> &mut Trait {
//~^ ERROR: missing lifetime specifier
//~| ERROR: trait objects must include the `dyn` keyword
&mut Type
//~^ ERROR: cannot return reference to temporary value
}

fn main() {}
Loading
Loading