diff options
Diffstat (limited to 'compiler/rustc_middle/src/ty/diagnostics.rs')
| -rw-r--r-- | compiler/rustc_middle/src/ty/diagnostics.rs | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/compiler/rustc_middle/src/ty/diagnostics.rs b/compiler/rustc_middle/src/ty/diagnostics.rs new file mode 100644 index 00000000000..b22727bdd75 --- /dev/null +++ b/compiler/rustc_middle/src/ty/diagnostics.rs @@ -0,0 +1,270 @@ +//! Diagnostics related methods for `TyS`. + +use crate::ty::sty::InferTy; +use crate::ty::TyKind::*; +use crate::ty::{TyCtxt, TyS}; +use rustc_errors::{Applicability, DiagnosticBuilder}; +use rustc_hir as hir; +use rustc_hir::def_id::DefId; +use rustc_hir::{QPath, TyKind, WhereBoundPredicate, WherePredicate}; + +impl<'tcx> TyS<'tcx> { + /// Similar to `TyS::is_primitive`, but also considers inferred numeric values to be primitive. + pub fn is_primitive_ty(&self) -> bool { + match self.kind { + Bool + | Char + | Str + | Int(_) + | Uint(_) + | Float(_) + | Infer( + InferTy::IntVar(_) + | InferTy::FloatVar(_) + | InferTy::FreshIntTy(_) + | InferTy::FreshFloatTy(_), + ) => true, + _ => false, + } + } + + /// Whether the type is succinctly representable as a type instead of just referred to with a + /// description in error messages. This is used in the main error message. + pub fn is_simple_ty(&self) -> bool { + match self.kind { + Bool + | Char + | Str + | Int(_) + | Uint(_) + | Float(_) + | Infer( + InferTy::IntVar(_) + | InferTy::FloatVar(_) + | InferTy::FreshIntTy(_) + | InferTy::FreshFloatTy(_), + ) => true, + Ref(_, x, _) | Array(x, _) | Slice(x) => x.peel_refs().is_simple_ty(), + Tuple(tys) if tys.is_empty() => true, + _ => false, + } + } + + /// Whether the type is succinctly representable as a type instead of just referred to with a + /// description in error messages. This is used in the primary span label. Beyond what + /// `is_simple_ty` includes, it also accepts ADTs with no type arguments and references to + /// ADTs with no type arguments. + pub fn is_simple_text(&self) -> bool { + match self.kind { + Adt(_, substs) => substs.types().next().is_none(), + Ref(_, ty, _) => ty.is_simple_text(), + _ => self.is_simple_ty(), + } + } + + /// Whether the type can be safely suggested during error recovery. + pub fn is_suggestable(&self) -> bool { + match self.kind { + Opaque(..) | FnDef(..) | FnPtr(..) | Dynamic(..) | Closure(..) | Infer(..) + | Projection(..) => false, + _ => true, + } + } +} + +/// Suggest restricting a type param with a new bound. +pub fn suggest_constraining_type_param( + tcx: TyCtxt<'_>, + generics: &hir::Generics<'_>, + err: &mut DiagnosticBuilder<'_>, + param_name: &str, + constraint: &str, + def_id: Option<DefId>, +) -> bool { + let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name); + + let param = if let Some(param) = param { + param + } else { + return false; + }; + + const MSG_RESTRICT_BOUND_FURTHER: &str = "consider further restricting this bound"; + let msg_restrict_type = format!("consider restricting type parameter `{}`", param_name); + let msg_restrict_type_further = + format!("consider further restricting type parameter `{}`", param_name); + + if def_id == tcx.lang_items().sized_trait() { + // Type parameters are already `Sized` by default. + err.span_label(param.span, &format!("this type parameter needs to be `{}`", constraint)); + return true; + } + let mut suggest_restrict = |span| { + err.span_suggestion_verbose( + span, + MSG_RESTRICT_BOUND_FURTHER, + format!(" + {}", constraint), + Applicability::MachineApplicable, + ); + }; + + if param_name.starts_with("impl ") { + // If there's an `impl Trait` used in argument position, suggest + // restricting it: + // + // fn foo(t: impl Foo) { ... } + // -------- + // | + // help: consider further restricting this bound with `+ Bar` + // + // Suggestion for tools in this case is: + // + // fn foo(t: impl Foo) { ... } + // -------- + // | + // replace with: `impl Foo + Bar` + + suggest_restrict(param.span.shrink_to_hi()); + return true; + } + + if generics.where_clause.predicates.is_empty() + // Given `trait Base<T = String>: Super<T>` where `T: Copy`, suggest restricting in the + // `where` clause instead of `trait Base<T: Copy = String>: Super<T>`. + && !matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. }) + { + if let Some(bounds_span) = param.bounds_span() { + // If user has provided some bounds, suggest restricting them: + // + // fn foo<T: Foo>(t: T) { ... } + // --- + // | + // help: consider further restricting this bound with `+ Bar` + // + // Suggestion for tools in this case is: + // + // fn foo<T: Foo>(t: T) { ... } + // -- + // | + // replace with: `T: Bar +` + suggest_restrict(bounds_span.shrink_to_hi()); + } else { + // If user hasn't provided any bounds, suggest adding a new one: + // + // fn foo<T>(t: T) { ... } + // - help: consider restricting this type parameter with `T: Foo` + err.span_suggestion_verbose( + param.span.shrink_to_hi(), + &msg_restrict_type, + format!(": {}", constraint), + Applicability::MachineApplicable, + ); + } + + true + } else { + // This part is a bit tricky, because using the `where` clause user can + // provide zero, one or many bounds for the same type parameter, so we + // have following cases to consider: + // + // 1) When the type parameter has been provided zero bounds + // + // Message: + // fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... } + // - help: consider restricting this type parameter with `where X: Bar` + // + // Suggestion: + // fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... } + // - insert: `, X: Bar` + // + // + // 2) When the type parameter has been provided one bound + // + // Message: + // fn foo<T>(t: T) where T: Foo { ... } + // ^^^^^^ + // | + // help: consider further restricting this bound with `+ Bar` + // + // Suggestion: + // fn foo<T>(t: T) where T: Foo { ... } + // ^^ + // | + // replace with: `T: Bar +` + // + // + // 3) When the type parameter has been provided many bounds + // + // Message: + // fn foo<T>(t: T) where T: Foo, T: Bar {... } + // - help: consider further restricting this type parameter with `where T: Zar` + // + // Suggestion: + // fn foo<T>(t: T) where T: Foo, T: Bar {... } + // - insert: `, T: Zar` + + let mut param_spans = Vec::new(); + + for predicate in generics.where_clause.predicates { + if let WherePredicate::BoundPredicate(WhereBoundPredicate { + span, bounded_ty, .. + }) = predicate + { + if let TyKind::Path(QPath::Resolved(_, path)) = &bounded_ty.kind { + if let Some(segment) = path.segments.first() { + if segment.ident.to_string() == param_name { + param_spans.push(span); + } + } + } + } + } + + match ¶m_spans[..] { + &[¶m_span] => suggest_restrict(param_span.shrink_to_hi()), + _ => { + err.span_suggestion_verbose( + generics.where_clause.tail_span_for_suggestion(), + &msg_restrict_type_further, + format!(", {}: {}", param_name, constraint), + Applicability::MachineApplicable, + ); + } + } + + true + } +} + +/// Collect al types that have an implicit `'static` obligation that we could suggest `'_` for. +pub struct TraitObjectVisitor<'tcx>(pub Vec<&'tcx hir::Ty<'tcx>>, pub crate::hir::map::Map<'tcx>); + +impl<'v> hir::intravisit::Visitor<'v> for TraitObjectVisitor<'v> { + type Map = rustc_hir::intravisit::ErasedMap<'v>; + + fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap<Self::Map> { + hir::intravisit::NestedVisitorMap::None + } + + fn visit_ty(&mut self, ty: &'v hir::Ty<'v>) { + match ty.kind { + hir::TyKind::TraitObject( + _, + hir::Lifetime { + name: + hir::LifetimeName::ImplicitObjectLifetimeDefault | hir::LifetimeName::Static, + .. + }, + ) => { + self.0.push(ty); + } + hir::TyKind::OpaqueDef(item_id, _) => { + self.0.push(ty); + let item = self.1.expect_item(item_id.id); + hir::intravisit::walk_item(self, item); + } + _ => {} + } + hir::intravisit::walk_ty(self, ty); + } +} |
