-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Provide suggestions for type parameters missing bounds for associated types #70908
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
75f066d
1473a66
3453db7
d8d02f8
c93c660
5d64e91
b13f234
b17b20c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,12 @@ | |
|
||
use crate::ty::sty::InferTy; | ||
use crate::ty::TyKind::*; | ||
use crate::ty::TyS; | ||
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}; | ||
use rustc_span::{BytePos, Span}; | ||
|
||
impl<'tcx> TyS<'tcx> { | ||
/// Similar to `TyS::is_primitive`, but also considers inferred numeric values to be primitive. | ||
|
@@ -67,3 +72,180 @@ impl<'tcx> TyS<'tcx> { | |
} | ||
} | ||
} | ||
|
||
/// Suggest restricting a type param with a new bound. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'd be nice to add a brief example. |
||
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 ") { | ||
nikomatsakis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// 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>`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this comment accurate? It seems like this covers the case where we don't suggest a where clause, at least some of the time |
||
&& !matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
what have we wrought There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clearly this should be |
||
{ | ||
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); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
let where_clause_span = generics.where_clause.span_for_predicates_or_empty_place(); | ||
// Account for `fn foo<T>(t: T) where T: Foo,` so we don't suggest two trailing commas. | ||
let mut trailing_comma = false; | ||
if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(where_clause_span) { | ||
trailing_comma = snippet.ends_with(','); | ||
} | ||
let where_clause_span = if trailing_comma { | ||
let hi = where_clause_span.hi(); | ||
Span::new(hi - BytePos(1), hi, where_clause_span.ctxt()) | ||
} else { | ||
where_clause_span.shrink_to_hi() | ||
}; | ||
|
||
match ¶m_spans[..] { | ||
&[¶m_span] => suggest_restrict(param_span.shrink_to_hi()), | ||
_ => { | ||
err.span_suggestion_verbose( | ||
where_clause_span, | ||
&msg_restrict_type_further, | ||
format!(", {}: {}", param_name, constraint), | ||
Applicability::MachineApplicable, | ||
); | ||
} | ||
} | ||
|
||
true | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.