Skip to content

Commit

Permalink
Rollup merge of rust-lang#119148 - estebank:bare-traits, r=davidtwco
Browse files Browse the repository at this point in the history
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 {
   |                                 ++++
```
  • Loading branch information
compiler-errors committed Jan 5, 2024
2 parents d180e91 + 698dfc3 commit f41ad1b
Show file tree
Hide file tree
Showing 22 changed files with 412 additions and 75 deletions.
2 changes: 1 addition & 1 deletion compiler/rustc_hir_analysis/src/astconv/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
let violations =
object_safety_violations_for_assoc_item(tcx, trait_def_id, *assoc_item);
if !violations.is_empty() {
report_object_safety_error(tcx, *span, trait_def_id, &violations).emit();
report_object_safety_error(tcx, *span, None, trait_def_id, &violations).emit();
object_safety_violations = true;
}
}
Expand Down
158 changes: 140 additions & 18 deletions compiler/rustc_hir_analysis/src/astconv/lint.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use rustc_ast::TraitObjectSyntax;
use rustc_errors::{Diagnostic, StashKey};
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_lint_defs::{builtin::BARE_TRAIT_OBJECTS, Applicability};
use rustc_span::Span;
use rustc_trait_selection::traits::error_reporting::suggestions::NextTypeParamName;

use super::AstConv;
Expand Down Expand Up @@ -32,32 +34,146 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
}
let of_trait_span = of_trait_ref.path.span;
// make sure that we are not calling unwrap to abort during the compilation
let Ok(impl_trait_name) = tcx.sess.source_map().span_to_snippet(self_ty.span) else {
return;
};
let Ok(of_trait_name) = tcx.sess.source_map().span_to_snippet(of_trait_span) else {
return;
};
// check if the trait has generics, to make a correct suggestion
let param_name = generics.params.next_type_param_name(None);

let add_generic_sugg = if let Some(span) = generics.span_for_param_suggestion() {
(span, format!(", {param_name}: {impl_trait_name}"))
} else {
(generics.span, format!("<{param_name}: {impl_trait_name}>"))
let Ok(impl_trait_name) = self.tcx().sess.source_map().span_to_snippet(self_ty.span)
else {
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 \
"alternatively use a blanket implementation to implement `{of_trait_name}` for \
all types that also implement `{impl_trait_name}`"
),
vec![(self_ty.span, param_name), add_generic_sugg],
sugg,
Applicability::MaybeIncorrect,
);
}
}

fn add_generic_param_suggestion(
&self,
generics: &hir::Generics<'_>,
self_ty_span: Span,
impl_trait_name: &str,
) -> Vec<(Span, String)> {
// check if the trait has generics, to make a correct suggestion
let param_name = generics.params.next_type_param_name(None);

let add_generic_sugg = if let Some(span) = generics.span_for_param_suggestion() {
(span, format!(", {param_name}: {impl_trait_name}"))
} else {
(generics.span, format!("<{param_name}: {impl_trait_name}>"))
};
vec![(self_ty_span, param_name), add_generic_sugg]
}

/// Make sure that we are in the condition to suggest `impl Trait`.
fn maybe_lint_impl_trait(&self, self_ty: &hir::Ty<'_>, diag: &mut Diagnostic) -> bool {
let tcx = self.tcx();
let parent_id = tcx.hir().get_parent_item(self_ty.hir_id).def_id;
let (hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, generics, _), .. })
| hir::Node::TraitItem(hir::TraitItem {
kind: hir::TraitItemKind::Fn(sig, _),
generics,
..
})) = tcx.hir_node_by_def_id(parent_id)
else {
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 is_object_safe = match self_ty.kind {
hir::TyKind::TraitObject(objects, ..) => {
objects.iter().all(|o| match o.trait_ref.path.res {
Res::Def(DefKind::Trait, id) => tcx.check_is_object_safe(id),
_ => false,
})
}
_ => false,
};
if let hir::FnRetTy::Return(ty) = sig.decl.output
&& ty.hir_id == self_ty.hir_id
{
let pre = if !is_object_safe {
format!("`{trait_name}` is not object safe, ")
} else {
String::new()
};
let msg = format!(
"{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);
if is_object_safe {
diag.multipart_suggestion_verbose(
"alternatively, you can return an owned trait object",
vec![
(ty.span.shrink_to_lo(), "Box<dyn ".to_string()),
(ty.span.shrink_to_hi(), ">".to_string()),
],
Applicability::MachineApplicable,
);
} else {
// We'll emit the object safety error already, with a structured suggestion.
diag.downgrade_to_delayed_bug();
}
return true;
}
for ty in sig.decl.inputs {
if ty.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,
);
}
if !is_object_safe {
diag.note(format!("`{trait_name}` it is not object safe, so it can't be `dyn`"));
// We'll emit the object safety error already, with a structured suggestion.
diag.downgrade_to_delayed_bug();
} else {
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_hi(), ")".to_string()),
]
} else {
vec![(self_ty.span.shrink_to_lo(), "&dyn ".to_string())]
};
diag.multipart_suggestion_verbose(
format!(
"alternatively, use a trait object to accept any type that implements \
`{trait_name}`, accessing its methods at runtime using dynamic dispatch",
),
sugg,
Applicability::MachineApplicable,
);
}
return true;
}
false
}

pub(super) fn maybe_lint_bare_trait(&self, self_ty: &hir::Ty<'_>, in_path: bool) {
let tcx = self.tcx();
if let hir::TyKind::TraitObject([poly_trait_ref, ..], _, TraitObjectSyntax::None) =
Expand Down Expand Up @@ -98,7 +214,9 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
let label = "add `dyn` keyword before this trait";
let mut diag =
rustc_errors::struct_span_err!(tcx.dcx(), self_ty.span, E0782, "{}", msg);
if self_ty.span.can_be_used_for_suggestions() {
if self_ty.span.can_be_used_for_suggestions()
&& !self.maybe_lint_impl_trait(self_ty, &mut diag)
{
diag.multipart_suggestion_verbose(
label,
sugg,
Expand All @@ -116,11 +234,15 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
self_ty.span,
msg,
|lint| {
lint.multipart_suggestion_verbose(
"use `dyn`",
sugg,
Applicability::MachineApplicable,
);
if self_ty.span.can_be_used_for_suggestions()
&& !self.maybe_lint_impl_trait(self_ty, lint)
{
lint.multipart_suggestion_verbose(
"use `dyn`",
sugg,
Applicability::MachineApplicable,
);
}
self.maybe_lint_blanket_trait_impl(self_ty, lint);
},
);
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir_analysis/src/astconv/object_safety.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
let reported = report_object_safety_error(
tcx,
span,
Some(hir_id),
item.trait_ref().def_id(),
&object_safety_violations,
)
Expand Down
7 changes: 4 additions & 3 deletions compiler/rustc_hir_typeck/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,22 +73,23 @@ pub(super) fn check_fn<'a, 'tcx>(
let inputs_fn = fn_sig.inputs().iter().copied();
for (idx, (param_ty, param)) in inputs_fn.chain(maybe_va_list).zip(body.params).enumerate() {
// Check the pattern.
let ty_span = try { inputs_hir?.get(idx)?.span };
let ty: Option<&hir::Ty<'_>> = try { inputs_hir?.get(idx)? };
let ty_span = ty.map(|ty| ty.span);
fcx.check_pat_top(param.pat, param_ty, ty_span, None, None);

// Check that argument is Sized.
if !params_can_be_unsized {
fcx.require_type_is_sized(
param_ty,
param.pat.span,
// ty_span == binding_span iff this is a closure parameter with no type ascription,
// ty.span == binding_span iff this is a closure parameter with no type ascription,
// or if it's an implicit `self` parameter
traits::SizedArgumentType(
if ty_span == Some(param.span) && tcx.is_closure_or_coroutine(fn_def_id.into())
{
None
} else {
ty_span
ty.map(|ty| ty.hir_id)
},
),
);
Expand Down
9 changes: 5 additions & 4 deletions compiler/rustc_hir_typeck/src/gather_locals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub(super) struct GatherLocalsVisitor<'a, 'tcx> {
// parameters are special cases of patterns, but we want to handle them as
// *distinct* cases. so track when we are hitting a pattern *within* an fn
// parameter.
outermost_fn_param_pat: Option<Span>,
outermost_fn_param_pat: Option<(Span, hir::HirId)>,
}

impl<'a, 'tcx> GatherLocalsVisitor<'a, 'tcx> {
Expand Down Expand Up @@ -131,7 +131,8 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherLocalsVisitor<'a, 'tcx> {
}

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

if let Some(ty_span) = self.outermost_fn_param_pat {
if let Some((ty_span, hir_id)) = self.outermost_fn_param_pat {
if !self.fcx.tcx.features().unsized_fn_params {
self.fcx.require_type_is_sized(
var_ty,
Expand All @@ -154,7 +155,7 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherLocalsVisitor<'a, 'tcx> {
{
None
} else {
Some(ty_span)
Some(hir_id)
},
),
);
Expand Down
22 changes: 21 additions & 1 deletion compiler/rustc_infer/src/traits/error_reporting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ use super::ObjectSafetyViolation;

use crate::infer::InferCtxt;
use rustc_data_structures::fx::FxIndexSet;
use rustc_errors::{struct_span_err, DiagnosticBuilder, MultiSpan};
use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, MultiSpan};
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit::Map;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::Span;
Expand Down Expand Up @@ -42,6 +43,7 @@ impl<'tcx> InferCtxt<'tcx> {
pub fn report_object_safety_error<'tcx>(
tcx: TyCtxt<'tcx>,
span: Span,
hir_id: Option<hir::HirId>,
trait_def_id: DefId,
violations: &[ObjectSafetyViolation],
) -> DiagnosticBuilder<'tcx> {
Expand All @@ -59,6 +61,24 @@ pub fn report_object_safety_error<'tcx>(
);
err.span_label(span, format!("`{trait_str}` cannot be made into an object"));

if let Some(hir_id) = hir_id
&& let Some(hir::Node::Ty(ty)) = tcx.hir().find(hir_id)
&& let hir::TyKind::TraitObject([trait_ref, ..], ..) = ty.kind
{
let mut hir_id = hir_id;
while let hir::Node::Ty(ty) = tcx.hir().get_parent(hir_id) {
hir_id = ty.hir_id;
}
if tcx.hir().get_parent(hir_id).fn_sig().is_some() {
// Do not suggest `impl Trait` when dealing with things like super-traits.
err.span_suggestion_verbose(
ty.span.until(trait_ref.span),
"consider using an opaque type instead",
"impl ",
Applicability::MaybeIncorrect,
);
}
}
let mut reported_violations = FxIndexSet::default();
let mut multi_span = vec![];
let mut messages = vec![];
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ pub enum ObligationCauseCode<'tcx> {
/// Type of each variable must be `Sized`.
VariableType(hir::HirId),
/// Argument type must be `Sized`.
SizedArgumentType(Option<Span>),
SizedArgumentType(Option<hir::HirId>),
/// Return type must be `Sized`.
SizedReturnType,
/// Yield type must be `Sized`.
Expand Down
Loading

0 comments on commit f41ad1b

Please sign in to comment.