Rollup merge of #122120 - fmease:sugg-assoc-ty-bound-on-eq-bound, r=compiler-errors

Suggest associated type bounds on problematic associated equality bounds

Fixes #105056. TL;DR: Suggest `Trait<Ty: Bound>` on `Trait<Ty = Bound>` in Rust >=2021.

~~Blocked on #122055 (stabilization of `associated_type_bounds`), I'd say.~~ (merged)
This commit is contained in:
Matthias Krüger 2024-03-26 21:23:47 +01:00 committed by GitHub
commit ff8cdc9e14
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 287 additions and 99 deletions

View file

@ -2289,21 +2289,15 @@ pub enum ImplItemKind<'hir> {
Type(&'hir Ty<'hir>), Type(&'hir Ty<'hir>),
} }
/// Bind a type to an associated type (i.e., `A = Foo`). /// An associated item binding.
/// ///
/// Bindings like `A: Debug` are represented as a special type `A = /// ### Examples
/// $::Debug` that is understood by the HIR ty lowering code.
/// ///
/// FIXME(alexreg): why have a separate type for the binding case, /// * `Trait<A = Ty, B = Ty>`
/// wouldn't it be better to make the `ty` field an enum like the /// * `Trait<G<Ty> = Ty>`
/// following? /// * `Trait<A: Bound>`
/// /// * `Trait<C = { Ct }>` (under feature `associated_const_equality`)
/// ```ignore (pseudo-rust) /// * `Trait<f(): Bound>` (under feature `return_type_notation`)
/// enum TypeBindingKind {
/// Equals(...),
/// Binding(...),
/// }
/// ```
#[derive(Debug, Clone, Copy, HashStable_Generic)] #[derive(Debug, Clone, Copy, HashStable_Generic)]
pub struct TypeBinding<'hir> { pub struct TypeBinding<'hir> {
pub hir_id: HirId, pub hir_id: HirId,
@ -2336,7 +2330,7 @@ impl<'hir> From<AnonConst> for Term<'hir> {
pub enum TypeBindingKind<'hir> { pub enum TypeBindingKind<'hir> {
/// E.g., `Foo<Bar: Send>`. /// E.g., `Foo<Bar: Send>`.
Constraint { bounds: &'hir [GenericBound<'hir>] }, Constraint { bounds: &'hir [GenericBound<'hir>] },
/// E.g., `Foo<Bar = ()>`, `Foo<Bar = ()>` /// E.g., `Foo<Bar = ()>`.
Equality { term: Term<'hir> }, Equality { term: Term<'hir> },
} }

View file

@ -9,8 +9,85 @@ use rustc_trait_selection::traits::error_reporting::suggestions::NextTypeParamNa
use super::HirTyLowerer; use super::HirTyLowerer;
impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
/// Prohibit or lint against *bare* trait object types depending on the edition.
///
/// *Bare* trait object types are ones that aren't preceeded by the keyword `dyn`.
/// In edition 2021 and onward we emit a hard error for them.
pub(super) fn prohibit_or_lint_bare_trait_object_ty(
&self,
self_ty: &hir::Ty<'_>,
in_path: bool,
) {
let tcx = self.tcx();
let hir::TyKind::TraitObject([poly_trait_ref, ..], _, TraitObjectSyntax::None) =
self_ty.kind
else {
return;
};
let needs_bracket = in_path
&& !tcx
.sess
.source_map()
.span_to_prev_source(self_ty.span)
.ok()
.is_some_and(|s| s.trim_end().ends_with('<'));
let is_global = poly_trait_ref.trait_ref.path.is_global();
let mut sugg = vec![(
self_ty.span.shrink_to_lo(),
format!(
"{}dyn {}",
if needs_bracket { "<" } else { "" },
if is_global { "(" } else { "" },
),
)];
if is_global || needs_bracket {
sugg.push((
self_ty.span.shrink_to_hi(),
format!(
"{}{}",
if is_global { ")" } else { "" },
if needs_bracket { ">" } else { "" },
),
));
}
if self_ty.span.edition().at_least_rust_2021() {
let msg = "trait objects must include the `dyn` keyword";
let label = "add `dyn` keyword before this trait";
let mut diag =
rustc_errors::struct_span_code_err!(tcx.dcx(), self_ty.span, E0782, "{}", msg);
if self_ty.span.can_be_used_for_suggestions()
&& !self.maybe_suggest_impl_trait(self_ty, &mut diag)
{
// FIXME: Only emit this suggestion if the trait is object safe.
diag.multipart_suggestion_verbose(label, sugg, Applicability::MachineApplicable);
}
// Check if the impl trait that we are considering is an impl of a local trait.
self.maybe_suggest_blanket_trait_impl(self_ty, &mut diag);
self.maybe_suggest_assoc_ty_bound(self_ty, &mut diag);
diag.stash(self_ty.span, StashKey::TraitMissingMethod);
} else {
let msg = "trait objects without an explicit `dyn` are deprecated";
tcx.node_span_lint(BARE_TRAIT_OBJECTS, self_ty.hir_id, self_ty.span, msg, |lint| {
if self_ty.span.can_be_used_for_suggestions() {
lint.multipart_suggestion_verbose(
"if this is an object-safe trait, use `dyn`",
sugg,
Applicability::MachineApplicable,
);
}
self.maybe_suggest_blanket_trait_impl(self_ty, lint);
});
}
}
/// Make sure that we are in the condition to suggest the blanket implementation. /// Make sure that we are in the condition to suggest the blanket implementation.
pub(super) fn maybe_lint_blanket_trait_impl<G: EmissionGuarantee>( fn maybe_suggest_blanket_trait_impl<G: EmissionGuarantee>(
&self, &self,
self_ty: &hir::Ty<'_>, self_ty: &hir::Ty<'_>,
diag: &mut Diag<'_, G>, diag: &mut Diag<'_, G>,
@ -75,9 +152,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
} }
/// Make sure that we are in the condition to suggest `impl Trait`. /// Make sure that we are in the condition to suggest `impl Trait`.
fn maybe_lint_impl_trait(&self, self_ty: &hir::Ty<'_>, diag: &mut Diag<'_>) -> bool { fn maybe_suggest_impl_trait(&self, self_ty: &hir::Ty<'_>, diag: &mut Diag<'_>) -> bool {
let tcx = self.tcx(); let tcx = self.tcx();
let parent_id = tcx.hir().get_parent_item(self_ty.hir_id).def_id; 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>`.
let (sig, generics, owner) = match tcx.hir_node_by_def_id(parent_id) { let (sig, generics, owner) = match tcx.hir_node_by_def_id(parent_id) {
hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, generics, _), .. }) => { hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, generics, _), .. }) => {
(sig, generics, None) (sig, generics, None)
@ -186,71 +265,37 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
false false
} }
pub(super) fn maybe_lint_bare_trait(&self, self_ty: &hir::Ty<'_>, in_path: bool) { fn maybe_suggest_assoc_ty_bound(&self, self_ty: &hir::Ty<'_>, diag: &mut Diag<'_>) {
let tcx = self.tcx(); let mut parents = self.tcx().hir().parent_iter(self_ty.hir_id);
if let hir::TyKind::TraitObject([poly_trait_ref, ..], _, TraitObjectSyntax::None) =
self_ty.kind if let Some((_, hir::Node::TypeBinding(binding))) = parents.next()
&& let hir::TypeBindingKind::Equality { term: hir::Term::Ty(obj_ty) } = binding.kind
{ {
let needs_bracket = in_path if let Some((_, hir::Node::TraitRef(..))) = parents.next()
&& !tcx && let Some((_, hir::Node::Ty(ty))) = parents.next()
.sess && let hir::TyKind::TraitObject(..) = ty.kind
.source_map() {
.span_to_prev_source(self_ty.span) // Assoc ty bounds aren't permitted inside trait object types.
.ok() return;
.is_some_and(|s| s.trim_end().ends_with('<'));
let is_global = poly_trait_ref.trait_ref.path.is_global();
let mut sugg = Vec::from_iter([(
self_ty.span.shrink_to_lo(),
format!(
"{}dyn {}",
if needs_bracket { "<" } else { "" },
if is_global { "(" } else { "" },
),
)]);
if is_global || needs_bracket {
sugg.push((
self_ty.span.shrink_to_hi(),
format!(
"{}{}",
if is_global { ")" } else { "" },
if needs_bracket { ">" } else { "" },
),
));
} }
if self_ty.span.edition().at_least_rust_2021() { let lo = if binding.gen_args.span_ext.is_dummy() {
let msg = "trait objects must include the `dyn` keyword"; binding.ident.span
let label = "add `dyn` keyword before this trait";
let mut diag =
rustc_errors::struct_span_code_err!(tcx.dcx(), self_ty.span, E0782, "{}", msg);
if self_ty.span.can_be_used_for_suggestions()
&& !self.maybe_lint_impl_trait(self_ty, &mut diag)
{
diag.multipart_suggestion_verbose(
label,
sugg,
Applicability::MachineApplicable,
);
}
// check if the impl trait that we are considering is a impl of a local trait
self.maybe_lint_blanket_trait_impl(self_ty, &mut diag);
diag.stash(self_ty.span, StashKey::TraitMissingMethod);
} else { } else {
let msg = "trait objects without an explicit `dyn` are deprecated"; binding.gen_args.span_ext
tcx.node_span_lint(BARE_TRAIT_OBJECTS, self_ty.hir_id, self_ty.span, msg, |lint| { };
if self_ty.span.can_be_used_for_suggestions() { let hi = obj_ty.span;
lint.multipart_suggestion_verbose(
"if this is an object-safe trait, use `dyn`", if !lo.eq_ctxt(hi) {
sugg, return;
Applicability::MachineApplicable,
);
}
self.maybe_lint_blanket_trait_impl(self_ty, lint);
});
} }
diag.span_suggestion_verbose(
lo.between(hi),
"you might have meant to write a bound here",
": ",
Applicability::MaybeIncorrect,
);
} }
} }
} }

View file

@ -2339,12 +2339,12 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
) )
} }
hir::TyKind::TraitObject(bounds, lifetime, repr) => { hir::TyKind::TraitObject(bounds, lifetime, repr) => {
self.maybe_lint_bare_trait(hir_ty, in_path); self.prohibit_or_lint_bare_trait_object_ty(hir_ty, in_path);
let repr = match repr { let repr = match repr {
TraitObjectSyntax::Dyn | TraitObjectSyntax::None => ty::Dyn, TraitObjectSyntax::Dyn | TraitObjectSyntax::None => ty::Dyn,
TraitObjectSyntax::DynStar => ty::DynStar, TraitObjectSyntax::DynStar => ty::DynStar,
}; };
self.lower_trait_object_ty( self.lower_trait_object_ty(
hir_ty.span, hir_ty.span,
hir_ty.hir_id, hir_ty.hir_id,

View file

@ -14,10 +14,6 @@ parse_array_index_offset_of = array indexing not supported in offset_of
parse_assignment_else_not_allowed = <assignment> ... else {"{"} ... {"}"} is not allowed parse_assignment_else_not_allowed = <assignment> ... else {"{"} ... {"}"} is not allowed
parse_assoc_lifetime = associated lifetimes are not supported
.label = the lifetime is given here
.help = if you meant to specify a trait object, write `dyn Trait + 'lifetime`
parse_associated_static_item_not_allowed = associated `static` items are not allowed parse_associated_static_item_not_allowed = associated `static` items are not allowed
parse_async_block_in_2015 = `async` blocks are only allowed in Rust 2018 or later parse_async_block_in_2015 = `async` blocks are only allowed in Rust 2018 or later
@ -445,6 +441,12 @@ parse_lifetime_in_borrow_expression = borrow expressions cannot be annotated wit
.suggestion = remove the lifetime annotation .suggestion = remove the lifetime annotation
.label = annotated with lifetime here .label = annotated with lifetime here
parse_lifetime_in_eq_constraint = lifetimes are not permitted in this context
.label = lifetime is not allowed here
.context_label = this introduces an associated item binding
.help = if you meant to specify a trait object, write `dyn /* Trait */ + {$lifetime}`
.colon_sugg = you might have meant to write a bound here
parse_lone_slash = invalid trailing slash in literal parse_lone_slash = invalid trailing slash in literal
.label = {parse_lone_slash} .label = {parse_lone_slash}

View file

@ -2631,13 +2631,22 @@ pub(crate) struct GenericsInPath {
} }
#[derive(Diagnostic)] #[derive(Diagnostic)]
#[diag(parse_assoc_lifetime)] #[diag(parse_lifetime_in_eq_constraint)]
#[help] #[help]
pub(crate) struct AssocLifetime { pub(crate) struct LifetimeInEqConstraint {
#[primary_span] #[primary_span]
pub span: Span,
#[label] #[label]
pub lifetime: Span, pub span: Span,
pub lifetime: Ident,
#[label(parse_context_label)]
pub binding_label: Span,
#[suggestion(
parse_colon_sugg,
style = "verbose",
applicability = "maybe-incorrect",
code = ": "
)]
pub colon_sugg: Span,
} }
#[derive(Diagnostic)] #[derive(Diagnostic)]

View file

@ -718,7 +718,11 @@ impl<'a> Parser<'a> {
let bounds = self.parse_generic_bounds()?; let bounds = self.parse_generic_bounds()?;
AssocConstraintKind::Bound { bounds } AssocConstraintKind::Bound { bounds }
} else if self.eat(&token::Eq) { } else if self.eat(&token::Eq) {
self.parse_assoc_equality_term(ident, self.prev_token.span)? self.parse_assoc_equality_term(
ident,
gen_args.as_ref(),
self.prev_token.span,
)?
} else { } else {
unreachable!(); unreachable!();
}; };
@ -753,11 +757,13 @@ impl<'a> Parser<'a> {
} }
/// Parse the term to the right of an associated item equality constraint. /// Parse the term to the right of an associated item equality constraint.
/// That is, parse `<term>` in `Item = <term>`. ///
/// Right now, this only admits types in `<term>`. /// That is, parse `$term` in `Item = $term` where `$term` is a type or
/// a const expression (wrapped in curly braces if complex).
fn parse_assoc_equality_term( fn parse_assoc_equality_term(
&mut self, &mut self,
ident: Ident, ident: Ident,
gen_args: Option<&GenericArgs>,
eq: Span, eq: Span,
) -> PResult<'a, AssocConstraintKind> { ) -> PResult<'a, AssocConstraintKind> {
let arg = self.parse_generic_arg(None)?; let arg = self.parse_generic_arg(None)?;
@ -769,9 +775,15 @@ impl<'a> Parser<'a> {
c.into() c.into()
} }
Some(GenericArg::Lifetime(lt)) => { Some(GenericArg::Lifetime(lt)) => {
let guar = let guar = self.dcx().emit_err(errors::LifetimeInEqConstraint {
self.dcx().emit_err(errors::AssocLifetime { span, lifetime: lt.ident.span }); span: lt.ident.span,
self.mk_ty(span, ast::TyKind::Err(guar)).into() lifetime: lt.ident,
binding_label: span,
colon_sugg: gen_args
.map_or(ident.span, |args| args.span())
.between(lt.ident.span),
});
self.mk_ty(lt.ident.span, ast::TyKind::Err(guar)).into()
} }
None => { None => {
let after_eq = eq.shrink_to_hi(); let after_eq = eq.shrink_to_hi();

View file

@ -0,0 +1,30 @@
// Regression test for issue #105056.
//@ edition: 2021
fn f(_: impl Trait<T = Copy>) {}
//~^ ERROR trait objects must include the `dyn` keyword
//~| HELP add `dyn` keyword before this trait
//~| HELP you might have meant to write a bound here
//~| ERROR the trait `Copy` cannot be made into an object
fn g(_: impl Trait<T = std::fmt::Debug + Eq>) {}
//~^ ERROR trait objects must include the `dyn` keyword
//~| HELP add `dyn` keyword before this trait
//~| HELP you might have meant to write a bound here
//~| ERROR only auto traits can be used as additional traits in a trait object
//~| HELP consider creating a new trait
//~| ERROR the trait `Eq` cannot be made into an object
fn h(_: impl Trait<T<> = 'static + for<'a> Fn(&'a ())>) {}
//~^ ERROR trait objects must include the `dyn` keyword
//~| HELP add `dyn` keyword before this trait
//~| HELP you might have meant to write a bound here
// Don't suggest assoc ty bound in trait object types, that's not valid:
type Obj = dyn Trait<T = Clone>;
//~^ ERROR trait objects must include the `dyn` keyword
//~| HELP add `dyn` keyword before this trait
trait Trait { type T; }
fn main() {}

View file

@ -0,0 +1,91 @@
error[E0038]: the trait `Copy` cannot be made into an object
--> $DIR/suggest-assoc-ty-bound-on-eq-bound.rs:4:20
|
LL | fn f(_: impl Trait<T = Copy>) {}
| ^^^^^^^^ `Copy` cannot be made into an object
|
= note: the trait cannot be made into an object because it requires `Self: Sized`
= 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>
error[E0225]: only auto traits can be used as additional traits in a trait object
--> $DIR/suggest-assoc-ty-bound-on-eq-bound.rs:10:42
|
LL | fn g(_: impl Trait<T = std::fmt::Debug + Eq>) {}
| --------------- ^^ additional non-auto trait
| |
| first non-auto trait
|
= help: consider creating a new trait with all of these as supertraits and using that trait here instead: `trait NewTrait: Debug + Eq {}`
= note: auto-traits like `Send` and `Sync` are traits that have special properties; for more information on them, visit <https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits>
error[E0038]: the trait `Eq` cannot be made into an object
--> $DIR/suggest-assoc-ty-bound-on-eq-bound.rs:10:24
|
LL | fn g(_: impl Trait<T = std::fmt::Debug + Eq>) {}
| ^^^^^^^^^^^^^^^^^^^^ `Eq` 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
error[E0782]: trait objects must include the `dyn` keyword
--> $DIR/suggest-assoc-ty-bound-on-eq-bound.rs:4:24
|
LL | fn f(_: impl Trait<T = Copy>) {}
| ^^^^
|
help: add `dyn` keyword before this trait
|
LL | fn f(_: impl Trait<T = dyn Copy>) {}
| +++
help: you might have meant to write a bound here
|
LL | fn f(_: impl Trait<T: Copy>) {}
| ~
error[E0782]: trait objects must include the `dyn` keyword
--> $DIR/suggest-assoc-ty-bound-on-eq-bound.rs:10:24
|
LL | fn g(_: impl Trait<T = std::fmt::Debug + Eq>) {}
| ^^^^^^^^^^^^^^^^^^^^
|
help: add `dyn` keyword before this trait
|
LL | fn g(_: impl Trait<T = dyn std::fmt::Debug + Eq>) {}
| +++
help: you might have meant to write a bound here
|
LL | fn g(_: impl Trait<T: std::fmt::Debug + Eq>) {}
| ~
error[E0782]: trait objects must include the `dyn` keyword
--> $DIR/suggest-assoc-ty-bound-on-eq-bound.rs:18:26
|
LL | fn h(_: impl Trait<T<> = 'static + for<'a> Fn(&'a ())>) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: add `dyn` keyword before this trait
|
LL | fn h(_: impl Trait<T<> = dyn 'static + for<'a> Fn(&'a ())>) {}
| +++
help: you might have meant to write a bound here
|
LL | fn h(_: impl Trait<T<>: 'static + for<'a> Fn(&'a ())>) {}
| ~
error[E0782]: trait objects must include the `dyn` keyword
--> $DIR/suggest-assoc-ty-bound-on-eq-bound.rs:24:26
|
LL | type Obj = dyn Trait<T = Clone>;
| ^^^^^
|
help: add `dyn` keyword before this trait
|
LL | type Obj = dyn Trait<T = dyn Clone>;
| +++
error: aborting due to 7 previous errors
Some errors have detailed explanations: E0038, E0225, E0782.
For more information about an error, try `rustc --explain E0038`.

View file

@ -1,6 +1,6 @@
#[cfg(FALSE)] #[cfg(FALSE)]
fn syntax() { fn syntax() {
bar::<Item = 'a>(); //~ ERROR associated lifetimes are not supported bar::<Item = 'a>(); //~ ERROR lifetimes are not permitted in this context
} }
fn main() {} fn main() {}

View file

@ -1,12 +1,17 @@
error: associated lifetimes are not supported error: lifetimes are not permitted in this context
--> $DIR/recover-assoc-lifetime-constraint.rs:3:11 --> $DIR/recover-assoc-lifetime-constraint.rs:3:18
| |
LL | bar::<Item = 'a>(); LL | bar::<Item = 'a>();
| ^^^^^^^-- | -------^^
| | | | |
| the lifetime is given here | | lifetime is not allowed here
| this introduces an associated item binding
| |
= help: if you meant to specify a trait object, write `dyn Trait + 'lifetime` = help: if you meant to specify a trait object, write `dyn /* Trait */ + 'a`
help: you might have meant to write a bound here
|
LL | bar::<Item: 'a>();
| ~
error: aborting due to 1 previous error error: aborting due to 1 previous error