Skip to content

Commit d5f80c8

Browse files
committed
Improve the explicit_outlives_requirements lint
* Don't use Strings to compare parameters * Extend the lint to lifetime bounds * Extend the lint to enums and unions * Use the correct span for where clauses in tuple structs * Try to early-out where possible
1 parent 36960a5 commit d5f80c8

6 files changed

+3404
-514
lines changed

src/librustc_lint/builtin.rs

+210-101
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
2424
use rustc::hir::def::{Res, DefKind};
2525
use rustc::hir::def_id::{DefId, LOCAL_CRATE};
26-
use rustc::ty::{self, Ty};
26+
use rustc::ty::{self, Ty, TyCtxt};
2727
use rustc::{lint, util};
2828
use hir::Node;
2929
use util::nodemap::HirIdSet;
@@ -1494,58 +1494,107 @@ impl EarlyLintPass for KeywordIdents {
14941494
declare_lint_pass!(ExplicitOutlivesRequirements => [EXPLICIT_OUTLIVES_REQUIREMENTS]);
14951495

14961496
impl ExplicitOutlivesRequirements {
1497-
fn collect_outlives_bound_spans(
1498-
&self,
1499-
cx: &LateContext<'_, '_>,
1500-
item_def_id: DefId,
1501-
param_name: &str,
1502-
bounds: &hir::GenericBounds,
1503-
infer_static: bool
1504-
) -> Vec<(usize, Span)> {
1505-
// For lack of a more elegant strategy for comparing the `ty::Predicate`s
1506-
// returned by this query with the params/bounds grabbed from the HIR—and
1507-
// with some regrets—we're going to covert the param/lifetime names to
1508-
// strings
1509-
let inferred_outlives = cx.tcx.inferred_outlives_of(item_def_id);
1510-
1511-
let ty_lt_names = inferred_outlives.iter().filter_map(|pred| {
1512-
let binder = match pred {
1513-
ty::Predicate::TypeOutlives(binder) => binder,
1514-
_ => { return None; }
1515-
};
1516-
let ty_outlives_pred = binder.skip_binder();
1517-
let ty_name = match ty_outlives_pred.0.sty {
1518-
ty::Param(param) => param.name.to_string(),
1519-
_ => { return None; }
1520-
};
1521-
let lt_name = match ty_outlives_pred.1 {
1522-
ty::RegionKind::ReEarlyBound(region) => {
1523-
region.name.to_string()
1524-
},
1525-
_ => { return None; }
1526-
};
1527-
Some((ty_name, lt_name))
1528-
}).collect::<Vec<_>>();
1529-
1530-
let mut bound_spans = Vec::new();
1531-
for (i, bound) in bounds.iter().enumerate() {
1532-
if let hir::GenericBound::Outlives(lifetime) = bound {
1533-
let is_static = match lifetime.name {
1534-
hir::LifetimeName::Static => true,
1535-
_ => false
1536-
};
1537-
if is_static && !infer_static {
1538-
// infer-outlives for 'static is still feature-gated (tracking issue #44493)
1539-
continue;
1497+
fn lifetimes_outliving_lifetime<'tcx>(
1498+
inferred_outlives: &'tcx [ty::Predicate<'tcx>],
1499+
index: u32,
1500+
) -> Vec<ty::Region<'tcx>> {
1501+
inferred_outlives.iter().filter_map(|pred| {
1502+
match pred {
1503+
ty::Predicate::RegionOutlives(outlives) => {
1504+
let outlives = outlives.skip_binder();
1505+
match outlives.0 {
1506+
ty::ReEarlyBound(ebr) if ebr.index == index => {
1507+
Some(outlives.1)
1508+
}
1509+
_ => None,
1510+
}
15401511
}
1512+
_ => None
1513+
}
1514+
}).collect()
1515+
}
15411516

1542-
let lt_name = &lifetime.name.ident().to_string();
1543-
if ty_lt_names.contains(&(param_name.to_owned(), lt_name.to_owned())) {
1544-
bound_spans.push((i, bound.span()));
1517+
fn lifetimes_outliving_type<'tcx>(
1518+
inferred_outlives: &'tcx [ty::Predicate<'tcx>],
1519+
index: u32,
1520+
) -> Vec<ty::Region<'tcx>> {
1521+
inferred_outlives.iter().filter_map(|pred| {
1522+
match pred {
1523+
ty::Predicate::TypeOutlives(outlives) => {
1524+
let outlives = outlives.skip_binder();
1525+
if outlives.0.is_param(index) {
1526+
Some(outlives.1)
1527+
} else {
1528+
None
1529+
}
15451530
}
1531+
_ => None
1532+
}
1533+
}).collect()
1534+
}
1535+
1536+
fn collect_outlived_lifetimes<'tcx>(
1537+
&self,
1538+
param: &'tcx hir::GenericParam,
1539+
tcx: TyCtxt<'tcx>,
1540+
inferred_outlives: &'tcx [ty::Predicate<'tcx>],
1541+
ty_generics: &'tcx ty::Generics,
1542+
) -> Vec<ty::Region<'tcx>> {
1543+
let index = ty_generics.param_def_id_to_index[
1544+
&tcx.hir().local_def_id_from_hir_id(param.hir_id)];
1545+
1546+
match param.kind {
1547+
hir::GenericParamKind::Lifetime { .. } => {
1548+
Self::lifetimes_outliving_lifetime(inferred_outlives, index)
1549+
}
1550+
hir::GenericParamKind::Type { .. } => {
1551+
Self::lifetimes_outliving_type(inferred_outlives, index)
15461552
}
1553+
hir::GenericParamKind::Const { .. } => Vec::new(),
15471554
}
1548-
bound_spans
1555+
}
1556+
1557+
1558+
fn collect_outlives_bound_spans<'tcx>(
1559+
&self,
1560+
tcx: TyCtxt<'tcx>,
1561+
bounds: &hir::GenericBounds,
1562+
inferred_outlives: &[ty::Region<'tcx>],
1563+
infer_static: bool,
1564+
) -> Vec<(usize, Span)> {
1565+
use rustc::middle::resolve_lifetime::Region;
1566+
1567+
bounds
1568+
.iter()
1569+
.enumerate()
1570+
.filter_map(|(i, bound)| {
1571+
if let hir::GenericBound::Outlives(lifetime) = bound {
1572+
let is_inferred = match tcx.named_region(lifetime.hir_id) {
1573+
Some(Region::Static) if infer_static => {
1574+
inferred_outlives.iter()
1575+
.any(|r| if let ty::ReStatic = r { true } else { false })
1576+
}
1577+
Some(Region::EarlyBound(index, ..)) => inferred_outlives
1578+
.iter()
1579+
.any(|r| {
1580+
if let ty::ReEarlyBound(ebr) = r {
1581+
ebr.index == index
1582+
} else {
1583+
false
1584+
}
1585+
}),
1586+
_ => false,
1587+
};
1588+
if is_inferred {
1589+
Some((i, bound.span()))
1590+
} else {
1591+
None
1592+
}
1593+
} else {
1594+
None
1595+
}
1596+
})
1597+
.collect()
15491598
}
15501599

15511600
fn consolidate_outlives_bound_spans(
@@ -1569,7 +1618,7 @@ impl ExplicitOutlivesRequirements {
15691618
let mut from_start = true;
15701619
for (i, bound_span) in bound_spans {
15711620
match last_merged_i {
1572-
// If the first bound is inferable, our span should also eat the trailing `+`
1621+
// If the first bound is inferable, our span should also eat the leading `+`
15731622
None if i == 0 => {
15741623
merged.push(bound_span.to(bounds[1].span().shrink_to_lo()));
15751624
last_merged_i = Some(0);
@@ -1607,26 +1656,48 @@ impl ExplicitOutlivesRequirements {
16071656

16081657
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExplicitOutlivesRequirements {
16091658
fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item) {
1659+
use rustc::middle::resolve_lifetime::Region;
1660+
16101661
let infer_static = cx.tcx.features().infer_static_outlives_requirements;
16111662
let def_id = cx.tcx.hir().local_def_id_from_hir_id(item.hir_id);
1612-
if let hir::ItemKind::Struct(_, ref generics) = item.node {
1663+
if let hir::ItemKind::Struct(_, ref hir_generics)
1664+
| hir::ItemKind::Enum(_, ref hir_generics)
1665+
| hir::ItemKind::Union(_, ref hir_generics) = item.node
1666+
{
1667+
let inferred_outlives = cx.tcx.inferred_outlives_of(def_id);
1668+
if inferred_outlives.is_empty() {
1669+
return;
1670+
}
1671+
1672+
let ty_generics = cx.tcx.generics_of(def_id);
1673+
16131674
let mut bound_count = 0;
16141675
let mut lint_spans = Vec::new();
16151676

1616-
for param in &generics.params {
1617-
let param_name = match param.kind {
1618-
hir::GenericParamKind::Lifetime { .. } => continue,
1619-
hir::GenericParamKind::Type { .. } => {
1620-
match param.name {
1621-
hir::ParamName::Fresh(_) => continue,
1622-
hir::ParamName::Error => continue,
1623-
hir::ParamName::Plain(name) => name.to_string(),
1624-
}
1677+
for param in &hir_generics.params {
1678+
let has_lifetime_bounds = param.bounds.iter().any(|bound| {
1679+
if let hir::GenericBound::Outlives(_) = bound {
1680+
true
1681+
} else {
1682+
false
16251683
}
1626-
hir::GenericParamKind::Const { .. } => continue,
1627-
};
1684+
});
1685+
if !has_lifetime_bounds {
1686+
continue;
1687+
}
1688+
1689+
let relevant_lifetimes = self.collect_outlived_lifetimes(
1690+
param,
1691+
cx.tcx,
1692+
inferred_outlives,
1693+
ty_generics,
1694+
);
1695+
if relevant_lifetimes.is_empty() {
1696+
continue;
1697+
}
1698+
16281699
let bound_spans = self.collect_outlives_bound_spans(
1629-
cx, def_id, &param_name, &param.bounds, infer_static
1700+
cx.tcx, &param.bounds, &relevant_lifetimes, infer_static,
16301701
);
16311702
bound_count += bound_spans.len();
16321703
lint_spans.extend(
@@ -1638,54 +1709,92 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExplicitOutlivesRequirements {
16381709

16391710
let mut where_lint_spans = Vec::new();
16401711
let mut dropped_predicate_count = 0;
1641-
let num_predicates = generics.where_clause.predicates.len();
1642-
for (i, where_predicate) in generics.where_clause.predicates.iter().enumerate() {
1643-
if let hir::WherePredicate::BoundPredicate(predicate) = where_predicate {
1644-
let param_name = match predicate.bounded_ty.node {
1645-
hir::TyKind::Path(ref qpath) => {
1646-
if let hir::QPath::Resolved(None, ty_param_path) = qpath {
1647-
ty_param_path.segments[0].ident.to_string()
1648-
} else {
1649-
continue;
1650-
}
1651-
},
1652-
_ => { continue; }
1653-
};
1654-
let bound_spans = self.collect_outlives_bound_spans(
1655-
cx, def_id, &param_name, &predicate.bounds, infer_static
1656-
);
1657-
bound_count += bound_spans.len();
1658-
1659-
let drop_predicate = bound_spans.len() == predicate.bounds.len();
1660-
if drop_predicate {
1661-
dropped_predicate_count += 1;
1662-
}
1663-
1664-
// If all the bounds on a predicate were inferable and there are
1665-
// further predicates, we want to eat the trailing comma
1666-
if drop_predicate && i + 1 < num_predicates {
1667-
let next_predicate_span = generics.where_clause.predicates[i+1].span();
1668-
where_lint_spans.push(
1669-
predicate.span.to(next_predicate_span.shrink_to_lo())
1670-
);
1671-
} else {
1672-
where_lint_spans.extend(
1673-
self.consolidate_outlives_bound_spans(
1674-
predicate.span.shrink_to_lo(),
1712+
let num_predicates = hir_generics.where_clause.predicates.len();
1713+
for (i, where_predicate) in hir_generics.where_clause.predicates.iter().enumerate() {
1714+
let (relevant_lifetimes, bounds, span) = match where_predicate {
1715+
hir::WherePredicate::RegionPredicate(predicate) => {
1716+
if let Some(Region::EarlyBound(index, ..))
1717+
= cx.tcx.named_region(predicate.lifetime.hir_id)
1718+
{
1719+
(
1720+
Self::lifetimes_outliving_lifetime(inferred_outlives, index),
16751721
&predicate.bounds,
1676-
bound_spans
1722+
predicate.span,
16771723
)
1678-
);
1724+
} else {
1725+
continue;
1726+
}
16791727
}
1728+
hir::WherePredicate::BoundPredicate(predicate) => {
1729+
// FIXME we can also infer bounds on associated types,
1730+
// and should check for them here.
1731+
match predicate.bounded_ty.node {
1732+
hir::TyKind::Path(hir::QPath::Resolved(
1733+
None,
1734+
ref path,
1735+
)) => if let Res::Def(DefKind::TyParam, def_id) = path.res {
1736+
let index = ty_generics.param_def_id_to_index[&def_id];
1737+
(
1738+
Self::lifetimes_outliving_type(inferred_outlives, index),
1739+
&predicate.bounds,
1740+
predicate.span,
1741+
)
1742+
} else {
1743+
continue
1744+
},
1745+
_ => { continue; }
1746+
}
1747+
}
1748+
_ => continue,
1749+
};
1750+
if relevant_lifetimes.is_empty() {
1751+
continue;
1752+
}
1753+
1754+
let bound_spans = self.collect_outlives_bound_spans(
1755+
cx.tcx, bounds, &relevant_lifetimes, infer_static,
1756+
);
1757+
bound_count += bound_spans.len();
1758+
1759+
let drop_predicate = bound_spans.len() == bounds.len();
1760+
if drop_predicate {
1761+
dropped_predicate_count += 1;
1762+
}
1763+
1764+
// If all the bounds on a predicate were inferable and there are
1765+
// further predicates, we want to eat the trailing comma
1766+
if drop_predicate && i + 1 < num_predicates {
1767+
let next_predicate_span = hir_generics.where_clause.predicates[i+1].span();
1768+
where_lint_spans.push(
1769+
span.to(next_predicate_span.shrink_to_lo())
1770+
);
1771+
} else {
1772+
where_lint_spans.extend(
1773+
self.consolidate_outlives_bound_spans(
1774+
span.shrink_to_lo(),
1775+
bounds,
1776+
bound_spans
1777+
)
1778+
);
16801779
}
16811780
}
16821781

16831782
// If all predicates are inferable, drop the entire clause
16841783
// (including the `where`)
16851784
if num_predicates > 0 && dropped_predicate_count == num_predicates {
1686-
let full_where_span = generics.span.shrink_to_hi()
1687-
.to(generics.where_clause.span()
1688-
.expect("span of (nonempty) where clause should exist"));
1785+
let where_span = hir_generics.where_clause.span()
1786+
.expect("span of (nonempty) where clause should exist");
1787+
// Extend the where clause back to the closing `>` of the
1788+
// generics, except for tuple struct, which have the `where`
1789+
// after the fields of the struct.
1790+
let full_where_span = match item.node {
1791+
hir::ItemKind::Struct(hir::VariantData::Tuple(..), _) => {
1792+
where_span
1793+
}
1794+
_ => {
1795+
hir_generics.span.shrink_to_hi().to(where_span)
1796+
}
1797+
};
16891798
lint_spans.push(
16901799
full_where_span
16911800
);

0 commit comments

Comments
 (0)