Skip to content

Commit a0a6e43

Browse files
committed
Auto merge of #53013 - zackmdavis:infer_outlints, r=nikomatsakis
in which inferable outlives-requirements are linted RFC 2093 (tracking issue #44493) lets us leave off these commonsensically inferable `T: 'a` outlives requirements. (A separate feature-gate was split off for the case of 'static lifetimes, for which questions still remain.) Detecting these was requested as an idioms-2018 lint. Resolves #52042, an item under the fabulous metaïssue #52047. It's plausible that this shouldn't land until after `infer_outlives_requirements` has been stabilized ([final comment period started](#44493 (comment)) 4 days ago), but I think there's also a strong case to not-wait in order to maximize the time that [Edition Preview 2](https://internals.rust-lang.org/t/rust-2018-release-schedule-and-extended-beta/8076) users have to kick at it. (It's allow by default, so there's no impact unless you explicitly turn it or the rust-2018-idioms group up to `warn` or higher.) Questions— * Is `explicit-outlives-requirements` a good name? (I chose it as an [RFC 344](https://github.com/rust-lang/rfcs/blob/master/text/0344-conventions-galore.md#lints)-compliant "inversion" of the feature-gate name, `infer_outlives_requirements`, but I could imagine someone arguing that the word `struct` should be part of the name somewhere, for specificity.) * Are there any false-positives or false-negatives? @nikomatsakis [said that](#52042 (comment)) getting this right would be "fairly hard", which makes me nervous that I'm missing something. The UI test in the initial submission of this pull request just exercises the examples [given in the Edition Guide](https://rust-lang-nursery.github.io/edition-guide/2018/transitioning/ownership-and-lifetimes/struct-inference.html). ![infer_outlints](https://user-images.githubusercontent.com/1076988/43625740-6bf43dca-96a3-11e8-9dcf-793ac83d424d.png) r? @alexcrichton
2 parents 63d51e8 + 032d97f commit a0a6e43

9 files changed

+1095
-1
lines changed

src/librustc/lint/builtin.rs

+6
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,12 @@ declare_lint! {
338338
cannot be referred to by absolute paths"
339339
}
340340

341+
declare_lint! {
342+
pub EXPLICIT_OUTLIVES_REQUIREMENTS,
343+
Allow,
344+
"outlives requirements can be inferred"
345+
}
346+
341347
/// Some lints that are buffered from `libsyntax`. See `syntax::early_buffered_lints`.
342348
pub mod parser {
343349
declare_lint! {

src/librustc_lint/builtin.rs

+230
Original file line numberDiff line numberDiff line change
@@ -1999,3 +1999,233 @@ impl EarlyLintPass for KeywordIdents {
19991999
lint.emit()
20002000
}
20012001
}
2002+
2003+
2004+
pub struct ExplicitOutlivesRequirements;
2005+
2006+
impl LintPass for ExplicitOutlivesRequirements {
2007+
fn get_lints(&self) -> LintArray {
2008+
lint_array![EXPLICIT_OUTLIVES_REQUIREMENTS]
2009+
}
2010+
}
2011+
2012+
impl ExplicitOutlivesRequirements {
2013+
fn collect_outlives_bound_spans(
2014+
&self,
2015+
cx: &LateContext,
2016+
item_def_id: DefId,
2017+
param_name: &str,
2018+
bounds: &hir::GenericBounds,
2019+
infer_static: bool
2020+
) -> Vec<(usize, Span)> {
2021+
// For lack of a more elegant strategy for comparing the `ty::Predicate`s
2022+
// returned by this query with the params/bounds grabbed from the HIR—and
2023+
// with some regrets—we're going to covert the param/lifetime names to
2024+
// strings
2025+
let inferred_outlives = cx.tcx.inferred_outlives_of(item_def_id);
2026+
2027+
let ty_lt_names = inferred_outlives.iter().filter_map(|pred| {
2028+
let binder = match pred {
2029+
ty::Predicate::TypeOutlives(binder) => binder,
2030+
_ => { return None; }
2031+
};
2032+
let ty_outlives_pred = binder.skip_binder();
2033+
let ty_name = match ty_outlives_pred.0.sty {
2034+
ty::Param(param) => param.name.to_string(),
2035+
_ => { return None; }
2036+
};
2037+
let lt_name = match ty_outlives_pred.1 {
2038+
ty::RegionKind::ReEarlyBound(region) => {
2039+
region.name.to_string()
2040+
},
2041+
_ => { return None; }
2042+
};
2043+
Some((ty_name, lt_name))
2044+
}).collect::<Vec<_>>();
2045+
2046+
let mut bound_spans = Vec::new();
2047+
for (i, bound) in bounds.iter().enumerate() {
2048+
if let hir::GenericBound::Outlives(lifetime) = bound {
2049+
let is_static = match lifetime.name {
2050+
hir::LifetimeName::Static => true,
2051+
_ => false
2052+
};
2053+
if is_static && !infer_static {
2054+
// infer-outlives for 'static is still feature-gated (tracking issue #44493)
2055+
continue;
2056+
}
2057+
2058+
let lt_name = &lifetime.name.ident().to_string();
2059+
if ty_lt_names.contains(&(param_name.to_owned(), lt_name.to_owned())) {
2060+
bound_spans.push((i, bound.span()));
2061+
}
2062+
}
2063+
}
2064+
bound_spans
2065+
}
2066+
2067+
fn consolidate_outlives_bound_spans(
2068+
&self,
2069+
lo: Span,
2070+
bounds: &hir::GenericBounds,
2071+
bound_spans: Vec<(usize, Span)>
2072+
) -> Vec<Span> {
2073+
if bounds.is_empty() {
2074+
return Vec::new();
2075+
}
2076+
if bound_spans.len() == bounds.len() {
2077+
let (_, last_bound_span) = bound_spans[bound_spans.len()-1];
2078+
// If all bounds are inferable, we want to delete the colon, so
2079+
// start from just after the parameter (span passed as argument)
2080+
vec![lo.to(last_bound_span)]
2081+
} else {
2082+
let mut merged = Vec::new();
2083+
let mut last_merged_i = None;
2084+
2085+
let mut from_start = true;
2086+
for (i, bound_span) in bound_spans {
2087+
match last_merged_i {
2088+
// If the first bound is inferable, our span should also eat the trailing `+`
2089+
None if i == 0 => {
2090+
merged.push(bound_span.to(bounds[1].span().shrink_to_lo()));
2091+
last_merged_i = Some(0);
2092+
},
2093+
// If consecutive bounds are inferable, merge their spans
2094+
Some(h) if i == h+1 => {
2095+
if let Some(tail) = merged.last_mut() {
2096+
// Also eat the trailing `+` if the first
2097+
// more-than-one bound is inferable
2098+
let to_span = if from_start && i < bounds.len() {
2099+
bounds[i+1].span().shrink_to_lo()
2100+
} else {
2101+
bound_span
2102+
};
2103+
*tail = tail.to(to_span);
2104+
last_merged_i = Some(i);
2105+
} else {
2106+
bug!("another bound-span visited earlier");
2107+
}
2108+
},
2109+
_ => {
2110+
// When we find a non-inferable bound, subsequent inferable bounds
2111+
// won't be consecutive from the start (and we'll eat the leading
2112+
// `+` rather than the trailing one)
2113+
from_start = false;
2114+
merged.push(bounds[i-1].span().shrink_to_hi().to(bound_span));
2115+
last_merged_i = Some(i);
2116+
}
2117+
}
2118+
}
2119+
merged
2120+
}
2121+
}
2122+
}
2123+
2124+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExplicitOutlivesRequirements {
2125+
fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item) {
2126+
let infer_static = cx.tcx.features().infer_static_outlives_requirements;
2127+
let def_id = cx.tcx.hir.local_def_id(item.id);
2128+
if let hir::ItemKind::Struct(_, ref generics) = item.node {
2129+
let mut bound_count = 0;
2130+
let mut lint_spans = Vec::new();
2131+
2132+
for param in &generics.params {
2133+
let param_name = match param.kind {
2134+
hir::GenericParamKind::Lifetime { .. } => { continue; },
2135+
hir::GenericParamKind::Type { .. } => {
2136+
match param.name {
2137+
hir::ParamName::Fresh(_) => { continue; },
2138+
hir::ParamName::Plain(name) => name.to_string()
2139+
}
2140+
}
2141+
};
2142+
let bound_spans = self.collect_outlives_bound_spans(
2143+
cx, def_id, &param_name, &param.bounds, infer_static
2144+
);
2145+
bound_count += bound_spans.len();
2146+
lint_spans.extend(
2147+
self.consolidate_outlives_bound_spans(
2148+
param.span.shrink_to_hi(), &param.bounds, bound_spans
2149+
)
2150+
);
2151+
}
2152+
2153+
let mut where_lint_spans = Vec::new();
2154+
let mut dropped_predicate_count = 0;
2155+
let num_predicates = generics.where_clause.predicates.len();
2156+
for (i, where_predicate) in generics.where_clause.predicates.iter().enumerate() {
2157+
if let hir::WherePredicate::BoundPredicate(predicate) = where_predicate {
2158+
let param_name = match predicate.bounded_ty.node {
2159+
hir::TyKind::Path(ref qpath) => {
2160+
if let hir::QPath::Resolved(None, ty_param_path) = qpath {
2161+
ty_param_path.segments[0].ident.to_string()
2162+
} else {
2163+
continue;
2164+
}
2165+
},
2166+
_ => { continue; }
2167+
};
2168+
let bound_spans = self.collect_outlives_bound_spans(
2169+
cx, def_id, &param_name, &predicate.bounds, infer_static
2170+
);
2171+
bound_count += bound_spans.len();
2172+
2173+
let drop_predicate = bound_spans.len() == predicate.bounds.len();
2174+
if drop_predicate {
2175+
dropped_predicate_count += 1;
2176+
}
2177+
2178+
// If all the bounds on a predicate were inferable and there are
2179+
// further predicates, we want to eat the trailing comma
2180+
if drop_predicate && i + 1 < num_predicates {
2181+
let next_predicate_span = generics.where_clause.predicates[i+1].span();
2182+
where_lint_spans.push(
2183+
predicate.span.to(next_predicate_span.shrink_to_lo())
2184+
);
2185+
} else {
2186+
where_lint_spans.extend(
2187+
self.consolidate_outlives_bound_spans(
2188+
predicate.span.shrink_to_lo(),
2189+
&predicate.bounds,
2190+
bound_spans
2191+
)
2192+
);
2193+
}
2194+
}
2195+
}
2196+
2197+
// If all predicates are inferable, drop the entire clause
2198+
// (including the `where`)
2199+
if num_predicates > 0 && dropped_predicate_count == num_predicates {
2200+
let full_where_span = generics.span.shrink_to_hi()
2201+
.to(generics.where_clause.span()
2202+
.expect("span of (nonempty) where clause should exist"));
2203+
lint_spans.push(
2204+
full_where_span
2205+
);
2206+
} else {
2207+
lint_spans.extend(where_lint_spans);
2208+
}
2209+
2210+
if !lint_spans.is_empty() {
2211+
let mut err = cx.struct_span_lint(
2212+
EXPLICIT_OUTLIVES_REQUIREMENTS,
2213+
lint_spans.clone(),
2214+
"outlives requirements can be inferred"
2215+
);
2216+
err.multipart_suggestion_with_applicability(
2217+
if bound_count == 1 {
2218+
"remove this bound"
2219+
} else {
2220+
"remove these bounds"
2221+
},
2222+
lint_spans.into_iter().map(|span| (span, "".to_owned())).collect::<Vec<_>>(),
2223+
Applicability::MachineApplicable
2224+
);
2225+
err.emit();
2226+
}
2227+
2228+
}
2229+
}
2230+
2231+
}

src/librustc_lint/lib.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ use rustc::lint::builtin::{
4848
BARE_TRAIT_OBJECTS,
4949
ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE,
5050
ELIDED_LIFETIMES_IN_PATHS,
51+
EXPLICIT_OUTLIVES_REQUIREMENTS,
5152
parser::QUESTION_MARK_MACRO_SEP
5253
};
5354
use rustc::session;
@@ -157,6 +158,7 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
157158
TypeLimits: TypeLimits::new(),
158159
MissingDoc: MissingDoc::new(),
159160
MissingDebugImplementations: MissingDebugImplementations::new(),
161+
ExplicitOutlivesRequirements: ExplicitOutlivesRequirements,
160162
]], ['tcx]);
161163

162164
store.register_late_pass(sess, false, box BuiltinCombinedLateLintPass::new());
@@ -199,7 +201,8 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
199201
BARE_TRAIT_OBJECTS,
200202
UNUSED_EXTERN_CRATES,
201203
ELLIPSIS_INCLUSIVE_RANGE_PATTERNS,
202-
ELIDED_LIFETIMES_IN_PATHS
204+
ELIDED_LIFETIMES_IN_PATHS,
205+
EXPLICIT_OUTLIVES_REQUIREMENTS
203206

204207
// FIXME(#52665, #47816) not always applicable and not all
205208
// macros are ready for this yet.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![allow(unused)]
12+
#![deny(explicit_outlives_requirements)]
13+
14+
use std::fmt::{Debug, Display};
15+
16+
// These examples should live in edition-lint-infer-outlives.rs, but are split
17+
// into this separate file because they can't be `rustfix`'d (and thus, can't
18+
// be part of a `run-rustfix` test file) until rust-lang-nursery/rustfix#141
19+
// is solved
20+
21+
struct TeeOutlivesAyIsDebugBee<'a, 'b, T: 'a + Debug + 'b> {
22+
//~^ ERROR outlives requirements can be inferred
23+
tee: &'a &'b T
24+
}
25+
26+
struct TeeWhereOutlivesAyIsDebugBee<'a, 'b, T> where T: 'a + Debug + 'b {
27+
//~^ ERROR outlives requirements can be inferred
28+
tee: &'a &'b T
29+
}
30+
31+
struct TeeYooOutlivesAyIsDebugBee<'a, 'b, T, U: 'a + Debug + 'b> {
32+
//~^ ERROR outlives requirements can be inferred
33+
tee: T,
34+
yoo: &'a &'b U
35+
}
36+
37+
struct TeeOutlivesAyYooBeeIsDebug<'a, 'b, T: 'a, U: 'b + Debug> {
38+
//~^ ERROR outlives requirements can be inferred
39+
tee: &'a T,
40+
yoo: &'b U
41+
}
42+
43+
struct TeeOutlivesAyYooIsDebugBee<'a, 'b, T: 'a, U: Debug + 'b> {
44+
//~^ ERROR outlives requirements can be inferred
45+
tee: &'a T,
46+
yoo: &'b U
47+
}
48+
49+
struct TeeOutlivesAyYooWhereBee<'a, 'b, T: 'a, U> where U: 'b {
50+
//~^ ERROR outlives requirements can be inferred
51+
tee: &'a T,
52+
yoo: &'b U
53+
}
54+
55+
struct TeeYooWhereOutlivesAyIsDebugBee<'a, 'b, T, U> where U: 'a + Debug + 'b {
56+
//~^ ERROR outlives requirements can be inferred
57+
tee: T,
58+
yoo: &'a &'b U
59+
}
60+
61+
struct TeeOutlivesAyYooWhereBeeIsDebug<'a, 'b, T: 'a, U> where U: 'b + Debug {
62+
//~^ ERROR outlives requirements can be inferred
63+
tee: &'a T,
64+
yoo: &'b U
65+
}
66+
67+
struct TeeOutlivesAyYooWhereIsDebugBee<'a, 'b, T: 'a, U> where U: Debug + 'b {
68+
//~^ ERROR outlives requirements can be inferred
69+
tee: &'a T,
70+
yoo: &'b U
71+
}
72+
73+
struct TeeWhereOutlivesAyYooWhereBeeIsDebug<'a, 'b, T, U> where T: 'a, U: 'b + Debug {
74+
//~^ ERROR outlives requirements can be inferred
75+
tee: &'a T,
76+
yoo: &'b U
77+
}
78+
79+
struct TeeWhereOutlivesAyYooWhereIsDebugBee<'a, 'b, T, U> where T: 'a, U: Debug + 'b {
80+
//~^ ERROR outlives requirements can be inferred
81+
tee: &'a T,
82+
yoo: &'b U
83+
}
84+
85+
fn main() {}

0 commit comments

Comments
 (0)