From 5e23cc49601776e97aed012fdbe268206df11c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 23 Sep 2020 23:01:17 -0700 Subject: [PATCH] Given `::A: Ty` suggest `T: Trait` Fix #75829 --- compiler/rustc_resolve/src/late.rs | 11 ++ .../rustc_resolve/src/late/diagnostics.rs | 118 ++++++++++++++++++ .../ui/traits/assoc_type_bound_with_struct.rs | 9 ++ .../assoc_type_bound_with_struct.stderr | 23 ++++ 4 files changed, 161 insertions(+) create mode 100644 src/test/ui/traits/assoc_type_bound_with_struct.rs create mode 100644 src/test/ui/traits/assoc_type_bound_with_struct.stderr diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index cfaf59e8feb0..a1538818df3f 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -386,6 +386,9 @@ struct DiagnosticMetadata<'ast> { in_if_condition: Option<&'ast Expr>, 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> { @@ -667,6 +670,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 7bd54447c7dc..b983656c423b 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -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; @@ -497,6 +498,9 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> { } } } + + 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 { @@ -521,6 +525,120 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> { (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.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, args: None, .. }], + [ast::GenericBound::Trait(poly_trait_ref, ast::TraitBoundModifier::None)], + ) = (&type_param_path.segments[..], &bounds[..]) + { + if let [ast::PathSegment { ident: bound_ident, args: None, .. }] = + &poly_trait_ref.trait_ref.path.segments[..] + { + if bound_ident.span == span { + err.span_suggestion_verbose( + *where_span, + &format!("constrain the associated type to `{}`", bound_ident), + format!( + "{}: {}<{} = {}>", + ident, + 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("::"), + bound_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 000000000000..729996c8130f --- /dev/null +++ b/src/test/ui/traits/assoc_type_bound_with_struct.rs @@ -0,0 +1,9 @@ +trait Bar { + type Baz; +} + +struct Foo where T: Bar, ::Baz: String { //~ ERROR expected trait, found struct + t: T, +} + +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 000000000000..f0c23aecf734 --- /dev/null +++ b/src/test/ui/traits/assoc_type_bound_with_struct.stderr @@ -0,0 +1,23 @@ +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: aborting due to previous error + +For more information about this error, try `rustc --explain E0404`.