Rollup merge of #106064 - lukas-code:outlives-macro, r=cjgillot

Partially fix `explicit_outlives_requirements` lint in macros

Show the suggestion if and only if the bounds are from the same source context.

fixes https://github.com/rust-lang/rust/issues/106044
fixes https://github.com/rust-lang/rust/issues/106063
This commit is contained in:
Michael Goulet 2022-12-27 12:33:35 -08:00 committed by GitHub
commit 18bf19c3a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 458 additions and 88 deletions

View File

@ -1316,6 +1316,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
param.id,
&param.kind,
&param.bounds,
param.colon_span,
generics.span,
itctx,
PredicateOrigin::GenericParam,
)
@ -1365,6 +1367,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
id: NodeId,
kind: &GenericParamKind,
bounds: &[GenericBound],
colon_span: Option<Span>,
parent_span: Span,
itctx: &ImplTraitContext,
origin: PredicateOrigin,
) -> Option<hir::WherePredicate<'hir>> {
@ -1377,21 +1381,17 @@ impl<'hir> LoweringContext<'_, 'hir> {
let ident = self.lower_ident(ident);
let param_span = ident.span;
let span = bounds
.iter()
.fold(Some(param_span.shrink_to_hi()), |span: Option<Span>, bound| {
let bound_span = bound.span();
// We include bounds that come from a `#[derive(_)]` but point at the user's code,
// as we use this method to get a span appropriate for suggestions.
if !bound_span.can_be_used_for_suggestions() {
None
} else if let Some(span) = span {
Some(span.to(bound_span))
} else {
Some(bound_span)
}
})
.unwrap_or(param_span.shrink_to_hi());
// Reconstruct the span of the entire predicate from the individual generic bounds.
let span_start = colon_span.unwrap_or_else(|| param_span.shrink_to_hi());
let span = bounds.iter().fold(span_start, |span_accum, bound| {
match bound.span().find_ancestor_inside(parent_span) {
Some(bound_span) => span_accum.to(bound_span),
None => span_accum,
}
});
let span = self.lower_span(span);
match kind {
GenericParamKind::Const { .. } => None,
GenericParamKind::Type { .. } => {

View File

@ -2245,6 +2245,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
) -> (hir::GenericParam<'hir>, Option<hir::WherePredicate<'hir>>, hir::TyKind<'hir>) {
// Add a definition for the in-band `Param`.
let def_id = self.local_def_id(node_id);
let span = self.lower_span(span);
// Set the name to `impl Bound1 + Bound2`.
let param = hir::GenericParam {
@ -2252,7 +2253,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
def_id,
name: ParamName::Plain(self.lower_ident(ident)),
pure_wrt_drop: false,
span: self.lower_span(span),
span,
kind: hir::GenericParamKind::Type { default: None, synthetic: true },
colon_span: None,
};
@ -2262,6 +2263,8 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
node_id,
&GenericParamKind::Type { default: None },
bounds,
/* colon_span */ None,
span,
&ImplTraitContext::Universal,
hir::PredicateOrigin::ImplTrait,
);
@ -2271,7 +2274,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
let ty = hir::TyKind::Path(hir::QPath::Resolved(
None,
self.arena.alloc(hir::Path {
span: self.lower_span(span),
span,
res,
segments:
arena_vec![self; hir::PathSegment::new(self.lower_ident(ident), hir_id, res)],

View File

@ -2184,6 +2184,7 @@ impl ExplicitOutlivesRequirements {
tcx: TyCtxt<'tcx>,
bounds: &hir::GenericBounds<'_>,
inferred_outlives: &[ty::Region<'tcx>],
predicate_span: Span,
) -> Vec<(usize, Span)> {
use rustc_middle::middle::resolve_lifetime::Region;
@ -2191,23 +2192,28 @@ impl ExplicitOutlivesRequirements {
.iter()
.enumerate()
.filter_map(|(i, bound)| {
if let hir::GenericBound::Outlives(lifetime) = bound {
let is_inferred = match tcx.named_region(lifetime.hir_id) {
Some(Region::EarlyBound(def_id)) => inferred_outlives.iter().any(|r| {
if let ty::ReEarlyBound(ebr) = **r {
ebr.def_id == def_id
} else {
false
}
}),
_ => false,
};
is_inferred.then_some((i, bound.span()))
} else {
None
let hir::GenericBound::Outlives(lifetime) = bound else {
return None;
};
let is_inferred = match tcx.named_region(lifetime.hir_id) {
Some(Region::EarlyBound(def_id)) => inferred_outlives
.iter()
.any(|r| matches!(**r, ty::ReEarlyBound(ebr) if { ebr.def_id == def_id })),
_ => false,
};
if !is_inferred {
return None;
}
let span = bound.span().find_ancestor_inside(predicate_span)?;
if in_external_macro(tcx.sess, span) {
return None;
}
Some((i, span))
})
.filter(|(_, span)| !in_external_macro(tcx.sess, *span))
.collect()
}
@ -2273,9 +2279,9 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements {
use rustc_middle::middle::resolve_lifetime::Region;
let def_id = item.owner_id.def_id;
if let hir::ItemKind::Struct(_, ref hir_generics)
| hir::ItemKind::Enum(_, ref hir_generics)
| hir::ItemKind::Union(_, ref hir_generics) = item.kind
if let hir::ItemKind::Struct(_, hir_generics)
| hir::ItemKind::Enum(_, hir_generics)
| hir::ItemKind::Union(_, hir_generics) = item.kind
{
let inferred_outlives = cx.tcx.inferred_outlives_of(def_id);
if inferred_outlives.is_empty() {
@ -2290,53 +2296,58 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements {
let mut dropped_predicate_count = 0;
let num_predicates = hir_generics.predicates.len();
for (i, where_predicate) in hir_generics.predicates.iter().enumerate() {
let (relevant_lifetimes, bounds, span, in_where_clause) = match where_predicate {
hir::WherePredicate::RegionPredicate(predicate) => {
if let Some(Region::EarlyBound(region_def_id)) =
cx.tcx.named_region(predicate.lifetime.hir_id)
{
(
Self::lifetimes_outliving_lifetime(
inferred_outlives,
region_def_id,
),
&predicate.bounds,
predicate.span,
predicate.in_where_clause,
)
} else {
continue;
}
}
hir::WherePredicate::BoundPredicate(predicate) => {
// FIXME we can also infer bounds on associated types,
// and should check for them here.
match predicate.bounded_ty.kind {
hir::TyKind::Path(hir::QPath::Resolved(None, ref path)) => {
let Res::Def(DefKind::TyParam, def_id) = path.res else {
continue
};
let index = ty_generics.param_def_id_to_index[&def_id];
let (relevant_lifetimes, bounds, predicate_span, in_where_clause) =
match where_predicate {
hir::WherePredicate::RegionPredicate(predicate) => {
if let Some(Region::EarlyBound(region_def_id)) =
cx.tcx.named_region(predicate.lifetime.hir_id)
{
(
Self::lifetimes_outliving_type(inferred_outlives, index),
Self::lifetimes_outliving_lifetime(
inferred_outlives,
region_def_id,
),
&predicate.bounds,
predicate.span,
predicate.origin == PredicateOrigin::WhereClause,
predicate.in_where_clause,
)
}
_ => {
} else {
continue;
}
}
}
_ => continue,
};
hir::WherePredicate::BoundPredicate(predicate) => {
// FIXME we can also infer bounds on associated types,
// and should check for them here.
match predicate.bounded_ty.kind {
hir::TyKind::Path(hir::QPath::Resolved(None, path)) => {
let Res::Def(DefKind::TyParam, def_id) = path.res else {
continue;
};
let index = ty_generics.param_def_id_to_index[&def_id];
(
Self::lifetimes_outliving_type(inferred_outlives, index),
&predicate.bounds,
predicate.span,
predicate.origin == PredicateOrigin::WhereClause,
)
}
_ => {
continue;
}
}
}
_ => continue,
};
if relevant_lifetimes.is_empty() {
continue;
}
let bound_spans =
self.collect_outlives_bound_spans(cx.tcx, bounds, &relevant_lifetimes);
let bound_spans = self.collect_outlives_bound_spans(
cx.tcx,
bounds,
&relevant_lifetimes,
predicate_span,
);
bound_count += bound_spans.len();
let drop_predicate = bound_spans.len() == bounds.len();
@ -2345,15 +2356,15 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements {
}
if drop_predicate && !in_where_clause {
lint_spans.push(span);
lint_spans.push(predicate_span);
} else if drop_predicate && i + 1 < num_predicates {
// If all the bounds on a predicate were inferable and there are
// further predicates, we want to eat the trailing comma.
let next_predicate_span = hir_generics.predicates[i + 1].span();
where_lint_spans.push(span.to(next_predicate_span.shrink_to_lo()));
where_lint_spans.push(predicate_span.to(next_predicate_span.shrink_to_lo()));
} else {
where_lint_spans.extend(self.consolidate_outlives_bound_spans(
span.shrink_to_lo(),
predicate_span.shrink_to_lo(),
bounds,
bound_spans,
));
@ -2374,12 +2385,26 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements {
} else {
hir_generics.span.shrink_to_hi().to(where_span)
};
lint_spans.push(full_where_span);
// Due to macro expansions, the `full_where_span` might not actually contain all predicates.
if where_lint_spans.iter().all(|&sp| full_where_span.contains(sp)) {
lint_spans.push(full_where_span);
} else {
lint_spans.extend(where_lint_spans);
}
} else {
lint_spans.extend(where_lint_spans);
}
if !lint_spans.is_empty() {
// Do not automatically delete outlives requirements from macros.
let applicability = if lint_spans.iter().all(|sp| sp.can_be_used_for_suggestions())
{
Applicability::MachineApplicable
} else {
Applicability::MaybeIncorrect
};
cx.struct_span_lint(
EXPLICIT_OUTLIVES_REQUIREMENTS,
lint_spans.clone(),
@ -2387,11 +2412,8 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements {
|lint| {
lint.set_arg("count", bound_count).multipart_suggestion(
fluent::suggestion,
lint_spans
.into_iter()
.map(|span| (span, String::new()))
.collect::<Vec<_>>(),
Applicability::MachineApplicable,
lint_spans.into_iter().map(|span| (span, String::new())).collect(),
applicability,
)
},
);

View File

@ -796,6 +796,9 @@ impl Span {
/// Returns a `Span` that would enclose both `self` and `end`.
///
/// Note that this can also be used to extend the span "backwards":
/// `start.to(end)` and `end.to(start)` return the same `Span`.
///
/// ```text
/// ____ ___
/// self lorem ipsum end

View File

@ -0,0 +1,137 @@
// edition:2018
// aux-build:edition-lint-infer-outlives-macro.rs
// run-rustfix
#![deny(explicit_outlives_requirements)]
#![allow(dead_code)]
#[macro_use]
extern crate edition_lint_infer_outlives_macro;
// Test that the lint does not fire if the predicate is from the local crate,
// but all the bounds are from an external macro.
macro_rules! make_foo {
($a:tt) => {
struct Foo<$a, 'b: $a> {
foo: &$a &'b (),
}
struct FooWhere<$a, 'b> where 'b: $a {
foo: &$a &'b (),
}
}
}
gimme_a! {make_foo!}
struct Bar<'a, 'b> {
//~^ ERROR: outlives requirements can be inferred
bar: &'a &'b (),
}
struct BarWhere<'a, 'b> {
//~^ ERROR: outlives requirements can be inferred
bar: &'a &'b (),
}
// Test that the lint *does* fire if the predicate is contained in a local macro.
mod everything_inside {
macro_rules! m {
('b: 'a) => {
struct Foo<'a, 'b>(&'a &'b ());
//~^ ERROR: outlives requirements can be inferred
struct Bar<'a, 'b>(&'a &'b ()) ;
//~^ ERROR: outlives requirements can be inferred
struct Baz<'a, 'b>(&'a &'b ()) where (): Sized, ;
//~^ ERROR: outlives requirements can be inferred
};
}
m!('b: 'a);
}
mod inner_lifetime_outside_colon_inside {
macro_rules! m {
($b:lifetime: 'a) => {
struct Foo<'a, $b>(&'a &$b ());
//~^ ERROR: outlives requirements can be inferred
struct Bar<'a, $b>(&'a &$b ()) ;
//~^ ERROR: outlives requirements can be inferred
struct Baz<'a, $b>(&'a &$b ()) where (): Sized, ;
//~^ ERROR: outlives requirements can be inferred
}
}
m!('b: 'a);
}
mod outer_lifetime_outside_colon_inside {
macro_rules! m {
('b: $a:lifetime) => {
struct Foo<$a, 'b: $a>(&$a &'b ());
struct Bar<$a, 'b>(&$a &'b ()) where 'b: $a;
struct Baz<$a, 'b>(&$a &'b ()) where (): Sized, 'b: $a;
}
}
m!('b: 'a);
}
mod both_lifetimes_outside_colon_inside {
macro_rules! m {
($b:lifetime: $a:lifetime) => {
struct Foo<$a, $b: $a>(&$a &$b ());
struct Bar<$a, $b>(&$a &$b ()) where $b: $a;
struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b: $a;
}
}
m!('b: 'a);
}
mod everything_outside {
macro_rules! m {
($b:lifetime $colon:tt $a:lifetime) => {
struct Foo<$a, $b $colon $a>(&$a &$b ());
struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a;
struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a;
}
}
m!('b: 'a);
}
mod everything_outside_with_tt_inner {
macro_rules! m {
($b:tt $colon:tt $a:lifetime) => {
struct Foo<$a, $b $colon $a>(&$a &$b ());
struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a;
struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a;
}
}
m!('b: 'a);
}
// FIXME: These should be consistent.
mod everything_outside_with_tt_outer {
macro_rules! m {
($b:lifetime $colon:tt $a:tt) => {
struct Foo<$a, $b >(&$a &$b ());
//~^ ERROR: outlives requirements can be inferred
struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a;
struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a;
}
}
m!('b: 'a);
}
mod everything_outside_with_tt_both {
macro_rules! m {
($b:tt $colon:tt $a:tt) => {
struct Foo<$a, $b >(&$a &$b ());
//~^ ERROR: outlives requirements can be inferred
struct Bar<$a, $b>(&$a &$b ()) where ;
//~^ ERROR: outlives requirements can be inferred
struct Baz<$a, $b>(&$a &$b ()) where (): Sized, ;
//~^ ERROR: outlives requirements can be inferred
}
}
m!('b: 'a);
}
fn main() {}

View File

@ -1,18 +1,22 @@
// edition:2018
// aux-build:edition-lint-infer-outlives-macro.rs
// Test that the lint does not fire if the where predicate
// is from the local crate, but all the bounds are from an
// external macro.
// run-rustfix
#![deny(explicit_outlives_requirements)]
#![allow(dead_code)]
#[macro_use]
extern crate edition_lint_infer_outlives_macro;
// Test that the lint does not fire if the predicate is from the local crate,
// but all the bounds are from an external macro.
macro_rules! make_foo {
($a:tt) => {
struct Foo<$a, 'b> where 'b: $a {
struct Foo<$a, 'b: $a> {
foo: &$a &'b (),
}
struct FooWhere<$a, 'b> where 'b: $a {
foo: &$a &'b (),
}
}
@ -25,4 +29,109 @@ struct Bar<'a, 'b: 'a> {
bar: &'a &'b (),
}
struct BarWhere<'a, 'b> where 'b: 'a {
//~^ ERROR: outlives requirements can be inferred
bar: &'a &'b (),
}
// Test that the lint *does* fire if the predicate is contained in a local macro.
mod everything_inside {
macro_rules! m {
('b: 'a) => {
struct Foo<'a, 'b: 'a>(&'a &'b ());
//~^ ERROR: outlives requirements can be inferred
struct Bar<'a, 'b>(&'a &'b ()) where 'b: 'a;
//~^ ERROR: outlives requirements can be inferred
struct Baz<'a, 'b>(&'a &'b ()) where (): Sized, 'b: 'a;
//~^ ERROR: outlives requirements can be inferred
};
}
m!('b: 'a);
}
mod inner_lifetime_outside_colon_inside {
macro_rules! m {
($b:lifetime: 'a) => {
struct Foo<'a, $b: 'a>(&'a &$b ());
//~^ ERROR: outlives requirements can be inferred
struct Bar<'a, $b>(&'a &$b ()) where $b: 'a;
//~^ ERROR: outlives requirements can be inferred
struct Baz<'a, $b>(&'a &$b ()) where (): Sized, $b: 'a;
//~^ ERROR: outlives requirements can be inferred
}
}
m!('b: 'a);
}
mod outer_lifetime_outside_colon_inside {
macro_rules! m {
('b: $a:lifetime) => {
struct Foo<$a, 'b: $a>(&$a &'b ());
struct Bar<$a, 'b>(&$a &'b ()) where 'b: $a;
struct Baz<$a, 'b>(&$a &'b ()) where (): Sized, 'b: $a;
}
}
m!('b: 'a);
}
mod both_lifetimes_outside_colon_inside {
macro_rules! m {
($b:lifetime: $a:lifetime) => {
struct Foo<$a, $b: $a>(&$a &$b ());
struct Bar<$a, $b>(&$a &$b ()) where $b: $a;
struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b: $a;
}
}
m!('b: 'a);
}
mod everything_outside {
macro_rules! m {
($b:lifetime $colon:tt $a:lifetime) => {
struct Foo<$a, $b $colon $a>(&$a &$b ());
struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a;
struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a;
}
}
m!('b: 'a);
}
mod everything_outside_with_tt_inner {
macro_rules! m {
($b:tt $colon:tt $a:lifetime) => {
struct Foo<$a, $b $colon $a>(&$a &$b ());
struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a;
struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a;
}
}
m!('b: 'a);
}
// FIXME: These should be consistent.
mod everything_outside_with_tt_outer {
macro_rules! m {
($b:lifetime $colon:tt $a:tt) => {
struct Foo<$a, $b $colon $a>(&$a &$b ());
//~^ ERROR: outlives requirements can be inferred
struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a;
struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a;
}
}
m!('b: 'a);
}
mod everything_outside_with_tt_both {
macro_rules! m {
($b:tt $colon:tt $a:tt) => {
struct Foo<$a, $b $colon $a>(&$a &$b ());
//~^ ERROR: outlives requirements can be inferred
struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a;
//~^ ERROR: outlives requirements can be inferred
struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a;
//~^ ERROR: outlives requirements can be inferred
}
}
m!('b: 'a);
}
fn main() {}

View File

@ -1,14 +1,110 @@
error: outlives requirements can be inferred
--> $DIR/edition-lint-infer-outlives-macro.rs:23:18
--> $DIR/edition-lint-infer-outlives-macro.rs:27:18
|
LL | struct Bar<'a, 'b: 'a> {
| ^^^^ help: remove this bound
|
note: the lint level is defined here
--> $DIR/edition-lint-infer-outlives-macro.rs:8:9
--> $DIR/edition-lint-infer-outlives-macro.rs:5:9
|
LL | #![deny(explicit_outlives_requirements)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error
error: outlives requirements can be inferred
--> $DIR/edition-lint-infer-outlives-macro.rs:32:24
|
LL | struct BarWhere<'a, 'b> where 'b: 'a {
| ^^^^^^^^^^^^^ help: remove this bound
error: outlives requirements can be inferred
--> $DIR/edition-lint-infer-outlives-macro.rs:41:30
|
LL | struct Foo<'a, 'b: 'a>(&'a &'b ());
| ^^^^ help: remove this bound
...
LL | m!('b: 'a);
| ---------- in this macro invocation
|
= note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info)
error: outlives requirements can be inferred
--> $DIR/edition-lint-infer-outlives-macro.rs:43:44
|
LL | struct Bar<'a, 'b>(&'a &'b ()) where 'b: 'a;
| ^^^^^^^^^^^^ help: remove this bound
...
LL | m!('b: 'a);
| ---------- in this macro invocation
|
= note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info)
error: outlives requirements can be inferred
--> $DIR/edition-lint-infer-outlives-macro.rs:45:61
|
LL | struct Baz<'a, 'b>(&'a &'b ()) where (): Sized, 'b: 'a;
| ^^^^^^ help: remove this bound
...
LL | m!('b: 'a);
| ---------- in this macro invocation
|
= note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info)
error: outlives requirements can be inferred
--> $DIR/edition-lint-infer-outlives-macro.rs:55:30
|
LL | struct Foo<'a, $b: 'a>(&'a &$b ());
| ^^^^ help: remove this bound
...
LL | m!('b: 'a);
| ---------- in this macro invocation
|
= note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info)
error: outlives requirements can be inferred
--> $DIR/edition-lint-infer-outlives-macro.rs:57:44
|
LL | struct Bar<'a, $b>(&'a &$b ()) where $b: 'a;
| ^^^^^^^^^^^^ help: remove this bound
...
LL | m!('b: 'a);
| ---------- in this macro invocation
|
= note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info)
error: outlives requirements can be inferred
--> $DIR/edition-lint-infer-outlives-macro.rs:59:61
|
LL | struct Baz<'a, $b>(&'a &$b ()) where (): Sized, $b: 'a;
| ^^^^^^ help: remove this bound
...
LL | m!('b: 'a);
| ---------- in this macro invocation
|
= note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info)
error: outlives requirements can be inferred
--> $DIR/edition-lint-infer-outlives-macro.rs:114:31
|
LL | struct Foo<$a, $b $colon $a>(&$a &$b ());
| ^^^^^^^^^ help: remove this bound
error: outlives requirements can be inferred
--> $DIR/edition-lint-infer-outlives-macro.rs:126:31
|
LL | struct Foo<$a, $b $colon $a>(&$a &$b ());
| ^^^^^^^^^ help: remove this bound
error: outlives requirements can be inferred
--> $DIR/edition-lint-infer-outlives-macro.rs:128:50
|
LL | struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a;
| ^^^^^^^^^^^^ help: remove this bound
error: outlives requirements can be inferred
--> $DIR/edition-lint-infer-outlives-macro.rs:130:61
|
LL | struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a;
| ^^^^^^^^^^^^ help: remove this bound
error: aborting due to 12 previous errors