diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 8d9d4123c79..245353c2e07 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1884,6 +1884,16 @@ impl Clone for Ty { } } +impl Ty { + pub fn peel_refs(&self) -> &Self { + let mut final_ty = self; + while let TyKind::Rptr(_, MutTy { ty, .. }) = &final_ty.kind { + final_ty = &ty; + } + final_ty + } +} + #[derive(Clone, Encodable, Decodable, Debug)] pub struct BareFnTy { pub unsafety: Unsafe, diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 2c01934b490..219517b4ab2 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -384,6 +384,13 @@ struct DiagnosticMetadata<'ast> { /// Used to detect possible `if let` written without `let` and to provide structured suggestion. in_if_condition: Option<&'ast Expr>, + + /// If we are currently in a trait object definition. Used to point at the bounds when + /// encountering a struct or enum. + current_trait_object: Option<&'ast [ast::GenericBound]>, + + /// Given `where ::Baz: String`, suggest `where T: Bar`. + current_where_predicate: Option<&'ast WherePredicate>, } struct LateResolutionVisitor<'a, 'b, 'ast> { @@ -453,6 +460,7 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> { self.diagnostic_metadata.current_let_binding = original; } fn visit_ty(&mut self, ty: &'ast Ty) { + let prev = self.diagnostic_metadata.current_trait_object; match ty.kind { TyKind::Path(ref qself, ref path) => { self.smart_resolve_path(ty.id, qself.as_ref(), path, PathSource::Type); @@ -464,9 +472,13 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> { .map_or(Res::Err, |d| d.res()); self.r.record_partial_res(ty.id, PartialRes::new(res)); } + TyKind::TraitObject(ref bounds, ..) => { + self.diagnostic_metadata.current_trait_object = Some(&bounds[..]); + } _ => (), } visit::walk_ty(self, ty); + self.diagnostic_metadata.current_trait_object = prev; } fn visit_poly_trait_ref(&mut self, tref: &'ast PolyTraitRef, m: &'ast TraitBoundModifier) { self.smart_resolve_path( @@ -660,6 +672,14 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> { } self.diagnostic_metadata.currently_processing_generics = prev; } + + fn visit_where_predicate(&mut self, p: &'ast WherePredicate) { + debug!("visit_where_predicate {:?}", p); + let previous_value = + replace(&mut self.diagnostic_metadata.current_where_predicate, Some(p)); + visit::walk_where_predicate(self, p); + self.diagnostic_metadata.current_where_predicate = previous_value; + } } impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 15e72228870..bee05e77382 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -1,6 +1,6 @@ use crate::diagnostics::{ImportSuggestion, LabelSuggestion, TypoSuggestion}; use crate::late::lifetimes::{ElisionFailureInfo, LifetimeContext}; -use crate::late::{LateResolutionVisitor, RibKind}; +use crate::late::{AliasPossibility, LateResolutionVisitor, RibKind}; use crate::path_names_to_string; use crate::{CrateLint, Module, ModuleKind, ModuleOrUniformRoot}; use crate::{PathResult, PathSource, Segment}; @@ -8,6 +8,7 @@ use crate::{PathResult, PathSource, Segment}; use rustc_ast::util::lev_distance::find_best_match_for_name; use rustc_ast::visit::FnKind; use rustc_ast::{self as ast, Expr, ExprKind, Item, ItemKind, NodeId, Path, Ty, TyKind}; +use rustc_ast_pretty::pprust::path_segment_to_string; use rustc_data_structures::fx::FxHashSet; use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder}; use rustc_hir as hir; @@ -19,7 +20,7 @@ use rustc_session::config::nightly_options; use rustc_session::parse::feature_err; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{kw, sym, Ident, Symbol}; -use rustc_span::{BytePos, Span, DUMMY_SP}; +use rustc_span::{BytePos, MultiSpan, Span, DUMMY_SP}; use tracing::debug; @@ -439,27 +440,213 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> { } } - if !self.type_ascription_suggestion(&mut err, base_span) - && !self.r.add_typo_suggestion(&mut err, typo_sugg, ident_span) - { - // Fallback label. - err.span_label(base_span, fallback_label); + if !self.type_ascription_suggestion(&mut err, base_span) { + let mut fallback = false; + if let ( + PathSource::Trait(AliasPossibility::Maybe), + Some(Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, _)), + ) = (source, res) + { + if let Some(bounds @ [_, .., _]) = self.diagnostic_metadata.current_trait_object { + fallback = true; + let spans: Vec = bounds + .iter() + .map(|bound| bound.span()) + .filter(|&sp| sp != base_span) + .collect(); - match self.diagnostic_metadata.current_let_binding { - Some((pat_sp, Some(ty_sp), None)) if ty_sp.contains(base_span) && could_be_expr => { - err.span_suggestion_short( - pat_sp.between(ty_sp), - "use `=` if you meant to assign", - " = ".to_string(), - Applicability::MaybeIncorrect, + let start_span = bounds.iter().map(|bound| bound.span()).next().unwrap(); + // `end_span` is the end of the poly trait ref (Foo + 'baz + Bar><) + let end_span = bounds.iter().map(|bound| bound.span()).last().unwrap(); + // `last_bound_span` is the last bound of the poly trait ref (Foo + >'baz< + Bar) + let last_bound_span = spans.last().cloned().unwrap(); + let mut multi_span: MultiSpan = spans.clone().into(); + for sp in spans { + let msg = if sp == last_bound_span { + format!( + "...because of {} bound{}", + if bounds.len() <= 2 { "this" } else { "these" }, + if bounds.len() <= 2 { "" } else { "s" }, + ) + } else { + String::new() + }; + multi_span.push_span_label(sp, msg); + } + multi_span.push_span_label( + base_span, + "expected this type to be a trait...".to_string(), ); + err.span_help( + multi_span, + "`+` is used to constrain a \"trait object\" type with lifetimes or \ + auto-traits; structs and enums can't be bound in that way", + ); + if bounds.iter().all(|bound| match bound { + ast::GenericBound::Outlives(_) => true, + ast::GenericBound::Trait(tr, _) => tr.span == base_span, + }) { + let mut sugg = vec![]; + if base_span != start_span { + sugg.push((start_span.until(base_span), String::new())); + } + if base_span != end_span { + sugg.push((base_span.shrink_to_hi().to(end_span), String::new())); + } + + err.multipart_suggestion( + "if you meant to use a type and not a trait here, remove the bounds", + sugg, + Applicability::MaybeIncorrect, + ); + } } - _ => {} + } + + fallback |= self.restrict_assoc_type_in_where_clause(span, &mut err); + + if !self.r.add_typo_suggestion(&mut err, typo_sugg, ident_span) { + fallback = true; + match self.diagnostic_metadata.current_let_binding { + Some((pat_sp, Some(ty_sp), None)) + if ty_sp.contains(base_span) && could_be_expr => + { + err.span_suggestion_short( + pat_sp.between(ty_sp), + "use `=` if you meant to assign", + " = ".to_string(), + Applicability::MaybeIncorrect, + ); + } + _ => {} + } + } + if fallback { + // Fallback label. + err.span_label(base_span, fallback_label); } } (err, candidates) } + /// Given `where ::Baz: String`, suggest `where T: Bar`. + fn restrict_assoc_type_in_where_clause( + &mut self, + span: Span, + err: &mut DiagnosticBuilder<'_>, + ) -> bool { + // Detect that we are actually in a `where` predicate. + let (bounded_ty, bounds, where_span) = + if let Some(ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate { + bounded_ty, + bound_generic_params, + bounds, + span, + })) = self.diagnostic_metadata.current_where_predicate + { + if !bound_generic_params.is_empty() { + return false; + } + (bounded_ty, bounds, span) + } else { + return false; + }; + + // Confirm that the target is an associated type. + let (ty, position, path) = if let ast::TyKind::Path( + Some(ast::QSelf { ty, position, .. }), + path, + ) = &bounded_ty.kind + { + // use this to verify that ident is a type param. + let partial_res = if let Ok(Some(partial_res)) = self.resolve_qpath_anywhere( + bounded_ty.id, + None, + &Segment::from_path(path), + Namespace::TypeNS, + span, + true, + CrateLint::No, + ) { + partial_res + } else { + return false; + }; + if !(matches!( + partial_res.base_res(), + hir::def::Res::Def(hir::def::DefKind::AssocTy, _) + ) && partial_res.unresolved_segments() == 0) + { + return false; + } + (ty, position, path) + } else { + return false; + }; + + if let ast::TyKind::Path(None, type_param_path) = &ty.peel_refs().kind { + // Confirm that the `SelfTy` is a type parameter. + let partial_res = if let Ok(Some(partial_res)) = self.resolve_qpath_anywhere( + bounded_ty.id, + None, + &Segment::from_path(type_param_path), + Namespace::TypeNS, + span, + true, + CrateLint::No, + ) { + partial_res + } else { + return false; + }; + if !(matches!( + partial_res.base_res(), + hir::def::Res::Def(hir::def::DefKind::TyParam, _) + ) && partial_res.unresolved_segments() == 0) + { + return false; + } + if let ( + [ast::PathSegment { ident: constrain_ident, args: None, .. }], + [ast::GenericBound::Trait(poly_trait_ref, ast::TraitBoundModifier::None)], + ) = (&type_param_path.segments[..], &bounds[..]) + { + if let [ast::PathSegment { ident, args: None, .. }] = + &poly_trait_ref.trait_ref.path.segments[..] + { + if ident.span == span { + err.span_suggestion_verbose( + *where_span, + &format!("constrain the associated type to `{}`", ident), + format!( + "{}: {}<{} = {}>", + self.r + .session + .source_map() + .span_to_snippet(ty.span) // Account for `<&'a T as Foo>::Bar`. + .unwrap_or_else(|_| constrain_ident.to_string()), + path.segments[..*position] + .iter() + .map(|segment| path_segment_to_string(segment)) + .collect::>() + .join("::"), + path.segments[*position..] + .iter() + .map(|segment| path_segment_to_string(segment)) + .collect::>() + .join("::"), + ident, + ), + Applicability::MaybeIncorrect, + ); + } + return true; + } + } + } + false + } + /// Check if the source is call expression and the first argument is `self`. If true, /// return the span of whole call and the span for all arguments expect the first one (`self`). fn call_has_self_arg(&self, source: PathSource<'_>) -> Option<(Span, Option)> { diff --git a/src/test/ui/traits/assoc_type_bound_with_struct.rs b/src/test/ui/traits/assoc_type_bound_with_struct.rs new file mode 100644 index 00000000000..c66009fe24c --- /dev/null +++ b/src/test/ui/traits/assoc_type_bound_with_struct.rs @@ -0,0 +1,19 @@ +trait Bar { + type Baz; +} + +struct Foo where T: Bar, ::Baz: String { //~ ERROR expected trait, found struct + t: T, +} + +struct Qux<'a, T> where T: Bar, <&'a T as Bar>::Baz: String { //~ ERROR expected trait, found struct + t: &'a T, +} + +fn foo(_: T) where ::Baz: String { //~ ERROR expected trait, found struct +} + +fn qux<'a, T: Bar>(_: &'a T) where <&'a T as Bar>::Baz: String { //~ ERROR expected trait, found +} + +fn main() {} diff --git a/src/test/ui/traits/assoc_type_bound_with_struct.stderr b/src/test/ui/traits/assoc_type_bound_with_struct.stderr new file mode 100644 index 00000000000..7cf872eb6ac --- /dev/null +++ b/src/test/ui/traits/assoc_type_bound_with_struct.stderr @@ -0,0 +1,83 @@ +error[E0404]: expected trait, found struct `String` + --> $DIR/assoc_type_bound_with_struct.rs:5:46 + | +LL | struct Foo where T: Bar, ::Baz: String { + | ^^^^^^ not a trait + | + ::: $SRC_DIR/alloc/src/string.rs:LL:COL + | +LL | pub trait ToString { + | ------------------ similarly named trait `ToString` defined here + | +help: constrain the associated type to `String` + | +LL | struct Foo where T: Bar, T: Bar { + | ^^^^^^^^^^^^^^^^^^^^ +help: a trait with a similar name exists + | +LL | struct Foo where T: Bar, ::Baz: ToString { + | ^^^^^^^^ + +error[E0404]: expected trait, found struct `String` + --> $DIR/assoc_type_bound_with_struct.rs:9:54 + | +LL | struct Qux<'a, T> where T: Bar, <&'a T as Bar>::Baz: String { + | ^^^^^^ not a trait + | + ::: $SRC_DIR/alloc/src/string.rs:LL:COL + | +LL | pub trait ToString { + | ------------------ similarly named trait `ToString` defined here + | +help: constrain the associated type to `String` + | +LL | struct Qux<'a, T> where T: Bar, &'a T: Bar { + | ^^^^^^^^^^^^^^^^^^^^^^^^ +help: a trait with a similar name exists + | +LL | struct Qux<'a, T> where T: Bar, <&'a T as Bar>::Baz: ToString { + | ^^^^^^^^ + +error[E0404]: expected trait, found struct `String` + --> $DIR/assoc_type_bound_with_struct.rs:13:45 + | +LL | fn foo(_: T) where ::Baz: String { + | ^^^^^^ not a trait + | + ::: $SRC_DIR/alloc/src/string.rs:LL:COL + | +LL | pub trait ToString { + | ------------------ similarly named trait `ToString` defined here + | +help: constrain the associated type to `String` + | +LL | fn foo(_: T) where T: Bar { + | ^^^^^^^^^^^^^^^^^^^^ +help: a trait with a similar name exists + | +LL | fn foo(_: T) where ::Baz: ToString { + | ^^^^^^^^ + +error[E0404]: expected trait, found struct `String` + --> $DIR/assoc_type_bound_with_struct.rs:16:57 + | +LL | fn qux<'a, T: Bar>(_: &'a T) where <&'a T as Bar>::Baz: String { + | ^^^^^^ not a trait + | + ::: $SRC_DIR/alloc/src/string.rs:LL:COL + | +LL | pub trait ToString { + | ------------------ similarly named trait `ToString` defined here + | +help: constrain the associated type to `String` + | +LL | fn qux<'a, T: Bar>(_: &'a T) where &'a T: Bar { + | ^^^^^^^^^^^^^^^^^^^^^^^^ +help: a trait with a similar name exists + | +LL | fn qux<'a, T: Bar>(_: &'a T) where <&'a T as Bar>::Baz: ToString { + | ^^^^^^^^ + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0404`. diff --git a/src/test/ui/traits/trait-bounds-not-on-struct.rs b/src/test/ui/traits/trait-bounds-not-on-struct.rs index c6e93e75525..8633e9d7a4c 100644 --- a/src/test/ui/traits/trait-bounds-not-on-struct.rs +++ b/src/test/ui/traits/trait-bounds-not-on-struct.rs @@ -1,9 +1,38 @@ +// We don't need those errors. Ideally we would silence them, but to do so we need to move the +// lint from being an early-lint during parsing to a late-lint, because it needs to be aware of +// the types involved. #![allow(bare_trait_objects)] struct Foo; fn foo(_x: Box) { } //~ ERROR expected trait, found struct `Foo` -type A = Box>; //~ ERROR expected trait, found struct `Vec` +type TypeAlias = Box>; //~ ERROR expected trait, found struct `Vec` -fn main() { } +struct A; +fn a() -> A + 'static { //~ ERROR expected trait, found + A +} +fn b<'a,T,E>(iter: Iterator + 'a>) { //~ ERROR expected trait, found + panic!() +} +fn c() -> 'static + A { //~ ERROR expected trait, found + A +} +fn d<'a,T,E>(iter: Iterator>) { //~ ERROR expected trait, found + panic!() +} +fn e() -> 'static + A + 'static { //~ ERROR expected trait, found +//~^ ERROR only a single explicit lifetime bound is permitted + A +} +fn f<'a,T,E>(iter: Iterator + 'a>) { //~ ERROR expected trait, found +//~^ ERROR only a single explicit lifetime bound is permitted + panic!() +} +struct Traitor; +trait Trait {} +fn g() -> Traitor + 'static { //~ ERROR expected trait, found struct `Traitor` + A +} +fn main() {} diff --git a/src/test/ui/traits/trait-bounds-not-on-struct.stderr b/src/test/ui/traits/trait-bounds-not-on-struct.stderr index a649a4eee55..0f97e3bdf18 100644 --- a/src/test/ui/traits/trait-bounds-not-on-struct.stderr +++ b/src/test/ui/traits/trait-bounds-not-on-struct.stderr @@ -1,15 +1,168 @@ +error[E0226]: only a single explicit lifetime bound is permitted + --> $DIR/trait-bounds-not-on-struct.rs:25:25 + | +LL | fn e() -> 'static + A + 'static { + | ^^^^^^^ + +error[E0226]: only a single explicit lifetime bound is permitted + --> $DIR/trait-bounds-not-on-struct.rs:29:53 + | +LL | fn f<'a,T,E>(iter: Iterator + 'a>) { + | ^^ + error[E0404]: expected trait, found struct `Foo` - --> $DIR/trait-bounds-not-on-struct.rs:5:16 + --> $DIR/trait-bounds-not-on-struct.rs:8:16 | LL | fn foo(_x: Box) { } | ^^^ not a trait + | +help: `+` is used to constrain a "trait object" type with lifetimes or auto-traits; structs and enums can't be bound in that way + --> $DIR/trait-bounds-not-on-struct.rs:8:22 + | +LL | fn foo(_x: Box) { } + | --- ^^^^ ...because of this bound + | | + | expected this type to be a trait... error[E0404]: expected trait, found struct `Vec` - --> $DIR/trait-bounds-not-on-struct.rs:7:21 + --> $DIR/trait-bounds-not-on-struct.rs:10:29 | -LL | type A = Box>; - | ^^^^^^ not a trait +LL | type TypeAlias = Box>; + | ^^^^^^ not a trait -error: aborting due to 2 previous errors +error[E0404]: expected trait, found struct `A` + --> $DIR/trait-bounds-not-on-struct.rs:13:11 + | +LL | fn a() -> A + 'static { + | ^ not a trait + | +help: `+` is used to constrain a "trait object" type with lifetimes or auto-traits; structs and enums can't be bound in that way + --> $DIR/trait-bounds-not-on-struct.rs:13:15 + | +LL | fn a() -> A + 'static { + | - ^^^^^^^ ...because of this bound + | | + | expected this type to be a trait... +help: if you meant to use a type and not a trait here, remove the bounds + | +LL | fn a() -> A { + | -- -For more information about this error, try `rustc --explain E0404`. +error[E0404]: expected trait, found enum `Result` + --> $DIR/trait-bounds-not-on-struct.rs:16:34 + | +LL | fn b<'a,T,E>(iter: Iterator + 'a>) { + | ^^^^^^^^^^^ not a trait + | +help: `+` is used to constrain a "trait object" type with lifetimes or auto-traits; structs and enums can't be bound in that way + --> $DIR/trait-bounds-not-on-struct.rs:16:48 + | +LL | fn b<'a,T,E>(iter: Iterator + 'a>) { + | ----------- ^^ ...because of this bound + | | + | expected this type to be a trait... +help: if you meant to use a type and not a trait here, remove the bounds + | +LL | fn b<'a,T,E>(iter: Iterator>) { + | -- + +error[E0404]: expected trait, found struct `A` + --> $DIR/trait-bounds-not-on-struct.rs:19:21 + | +LL | fn c() -> 'static + A { + | ^ not a trait + | +help: `+` is used to constrain a "trait object" type with lifetimes or auto-traits; structs and enums can't be bound in that way + --> $DIR/trait-bounds-not-on-struct.rs:19:11 + | +LL | fn c() -> 'static + A { + | ^^^^^^^ - expected this type to be a trait... + | | + | ...because of this bound +help: if you meant to use a type and not a trait here, remove the bounds + | +LL | fn c() -> A { + | -- + +error[E0404]: expected trait, found enum `Result` + --> $DIR/trait-bounds-not-on-struct.rs:22:39 + | +LL | fn d<'a,T,E>(iter: Iterator>) { + | ^^^^^^^^^^^ not a trait + | +help: `+` is used to constrain a "trait object" type with lifetimes or auto-traits; structs and enums can't be bound in that way + --> $DIR/trait-bounds-not-on-struct.rs:22:34 + | +LL | fn d<'a,T,E>(iter: Iterator>) { + | ^^ ----------- expected this type to be a trait... + | | + | ...because of this bound +help: if you meant to use a type and not a trait here, remove the bounds + | +LL | fn d<'a,T,E>(iter: Iterator>) { + | -- + +error[E0404]: expected trait, found struct `A` + --> $DIR/trait-bounds-not-on-struct.rs:25:21 + | +LL | fn e() -> 'static + A + 'static { + | ^ not a trait + | +help: `+` is used to constrain a "trait object" type with lifetimes or auto-traits; structs and enums can't be bound in that way + --> $DIR/trait-bounds-not-on-struct.rs:25:11 + | +LL | fn e() -> 'static + A + 'static { + | ^^^^^^^ - ^^^^^^^ ...because of these bounds + | | + | expected this type to be a trait... +help: if you meant to use a type and not a trait here, remove the bounds + | +LL | fn e() -> A { + | --- + +error[E0404]: expected trait, found enum `Result` + --> $DIR/trait-bounds-not-on-struct.rs:29:39 + | +LL | fn f<'a,T,E>(iter: Iterator + 'a>) { + | ^^^^^^^^^^^ not a trait + | +help: `+` is used to constrain a "trait object" type with lifetimes or auto-traits; structs and enums can't be bound in that way + --> $DIR/trait-bounds-not-on-struct.rs:29:34 + | +LL | fn f<'a,T,E>(iter: Iterator + 'a>) { + | ^^ ----------- ^^ ...because of these bounds + | | + | expected this type to be a trait... +help: if you meant to use a type and not a trait here, remove the bounds + | +LL | fn f<'a,T,E>(iter: Iterator>) { + | -- -- + +error[E0404]: expected trait, found struct `Traitor` + --> $DIR/trait-bounds-not-on-struct.rs:35:11 + | +LL | trait Trait {} + | ----------- similarly named trait `Trait` defined here +LL | fn g() -> Traitor + 'static { + | ^^^^^^^ not a trait + | +help: `+` is used to constrain a "trait object" type with lifetimes or auto-traits; structs and enums can't be bound in that way + --> $DIR/trait-bounds-not-on-struct.rs:35:21 + | +LL | fn g() -> Traitor + 'static { + | ------- ^^^^^^^ ...because of this bound + | | + | expected this type to be a trait... +help: if you meant to use a type and not a trait here, remove the bounds + | +LL | fn g() -> Traitor { + | -- +help: a trait with a similar name exists + | +LL | fn g() -> Trait + 'static { + | ^^^^^ + +error: aborting due to 11 previous errors + +Some errors have detailed explanations: E0226, E0404. +For more information about an error, try `rustc --explain E0226`.