Skip to content

Commit 8f4c226

Browse files
committed
Auto merge of #58608 - pnkfelix:warning-period-for-detecting-nested-impl-trait, r=zoxc
Warning period for detecting nested impl trait Here is some proposed code for making a warning period for the new checking of nested impl trait. It undoes some of the corrective effects of PR #57730, by using boolean flags to track parts of the analysis that were previously skipped prior to PRs #57730 and #57981 landing. Cc #57979
2 parents 7486b9c + 0a03ca7 commit 8f4c226

11 files changed

+345
-73
lines changed

src/librustc/lint/builtin.rs

+14
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,12 @@ declare_lint! {
386386
"ambiguous associated items"
387387
}
388388

389+
declare_lint! {
390+
pub NESTED_IMPL_TRAIT,
391+
Warn,
392+
"nested occurrence of `impl Trait` type"
393+
}
394+
389395
/// Does nothing as a lint pass, but registers some `Lint`s
390396
/// that are used by other parts of the compiler.
391397
#[derive(Copy, Clone)]
@@ -457,6 +463,7 @@ impl LintPass for HardwiredLints {
457463
parser::ILL_FORMED_ATTRIBUTE_INPUT,
458464
DEPRECATED_IN_FUTURE,
459465
AMBIGUOUS_ASSOCIATED_ITEMS,
466+
NESTED_IMPL_TRAIT,
460467
)
461468
}
462469
}
@@ -474,6 +481,7 @@ pub enum BuiltinLintDiagnostics {
474481
ElidedLifetimesInPaths(usize, Span, bool, Span, String),
475482
UnknownCrateTypes(Span, String, String),
476483
UnusedImports(String, Vec<(Span, String)>),
484+
NestedImplTrait { outer_impl_trait_span: Span, inner_impl_trait_span: Span },
477485
}
478486

479487
impl BuiltinLintDiagnostics {
@@ -564,6 +572,12 @@ impl BuiltinLintDiagnostics {
564572
);
565573
}
566574
}
575+
BuiltinLintDiagnostics::NestedImplTrait {
576+
outer_impl_trait_span, inner_impl_trait_span
577+
} => {
578+
db.span_label(outer_impl_trait_span, "outer `impl Trait`");
579+
db.span_label(inner_impl_trait_span, "nested `impl Trait` here");
580+
}
567581
}
568582
}
569583
}

src/librustc_lint/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,11 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
353353
reference: "issue #57593 <https://github.com/rust-lang/rust/issues/57593>",
354354
edition: None,
355355
},
356+
FutureIncompatibleInfo {
357+
id: LintId::of(NESTED_IMPL_TRAIT),
358+
reference: "issue #59014 <https://github.com/rust-lang/rust/issues/59014>",
359+
edition: None,
360+
},
356361
]);
357362

358363
// Register renamed and removed lints.

src/librustc_passes/ast_validation.rs

+114-14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use std::mem;
1010
use syntax::print::pprust;
1111
use rustc::lint;
12+
use rustc::lint::builtin::{BuiltinLintDiagnostics, NESTED_IMPL_TRAIT};
1213
use rustc::session::Session;
1314
use rustc_data_structures::fx::FxHashMap;
1415
use syntax::ast::*;
@@ -23,6 +24,31 @@ use syntax_pos::Span;
2324
use errors::Applicability;
2425
use log::debug;
2526

27+
#[derive(Copy, Clone, Debug)]
28+
struct OuterImplTrait {
29+
span: Span,
30+
31+
/// rust-lang/rust#57979: a bug in original implementation caused
32+
/// us to fail sometimes to record an outer `impl Trait`.
33+
/// Therefore, in order to reliably issue a warning (rather than
34+
/// an error) in the *precise* places where we are newly injecting
35+
/// the diagnostic, we have to distinguish between the places
36+
/// where the outer `impl Trait` has always been recorded, versus
37+
/// the places where it has only recently started being recorded.
38+
only_recorded_since_pull_request_57730: bool,
39+
}
40+
41+
impl OuterImplTrait {
42+
/// This controls whether we should downgrade the nested impl
43+
/// trait diagnostic to a warning rather than an error, based on
44+
/// whether the outer impl trait had been improperly skipped in
45+
/// earlier implementations of the analysis on the stable
46+
/// compiler.
47+
fn should_warn_instead_of_error(&self) -> bool {
48+
self.only_recorded_since_pull_request_57730
49+
}
50+
}
51+
2652
struct AstValidator<'a> {
2753
session: &'a Session,
2854
has_proc_macro_decls: bool,
@@ -31,31 +57,83 @@ struct AstValidator<'a> {
3157
// Used to ban nested `impl Trait`, e.g., `impl Into<impl Debug>`.
3258
// Nested `impl Trait` _is_ allowed in associated type position,
3359
// e.g `impl Iterator<Item=impl Debug>`
34-
outer_impl_trait: Option<Span>,
60+
outer_impl_trait: Option<OuterImplTrait>,
3561

3662
// Used to ban `impl Trait` in path projections like `<impl Iterator>::Item`
3763
// or `Foo::Bar<impl Trait>`
3864
is_impl_trait_banned: bool,
65+
66+
// rust-lang/rust#57979: the ban of nested `impl Trait` was buggy
67+
// until PRs #57730 and #57981 landed: it would jump directly to
68+
// walk_ty rather than visit_ty (or skip recurring entirely for
69+
// impl trait in projections), and thus miss some cases. We track
70+
// whether we should downgrade to a warning for short-term via
71+
// these booleans.
72+
warning_period_57979_didnt_record_next_impl_trait: bool,
73+
warning_period_57979_impl_trait_in_proj: bool,
3974
}
4075

4176
impl<'a> AstValidator<'a> {
77+
fn with_impl_trait_in_proj_warning<T>(&mut self, v: bool, f: impl FnOnce(&mut Self) -> T) -> T {
78+
let old = mem::replace(&mut self.warning_period_57979_impl_trait_in_proj, v);
79+
let ret = f(self);
80+
self.warning_period_57979_impl_trait_in_proj = old;
81+
ret
82+
}
83+
4284
fn with_banned_impl_trait(&mut self, f: impl FnOnce(&mut Self)) {
4385
let old = mem::replace(&mut self.is_impl_trait_banned, true);
4486
f(self);
4587
self.is_impl_trait_banned = old;
4688
}
4789

48-
fn with_impl_trait(&mut self, outer_impl_trait: Option<Span>, f: impl FnOnce(&mut Self)) {
49-
let old = mem::replace(&mut self.outer_impl_trait, outer_impl_trait);
90+
fn with_impl_trait(&mut self, outer: Option<OuterImplTrait>, f: impl FnOnce(&mut Self)) {
91+
let old = mem::replace(&mut self.outer_impl_trait, outer);
5092
f(self);
5193
self.outer_impl_trait = old;
5294
}
5395

96+
fn visit_assoc_type_binding_from_generic_args(&mut self, type_binding: &'a TypeBinding) {
97+
// rust-lang/rust#57979: bug in old visit_generic_args called
98+
// walk_ty rather than visit_ty, skipping outer `impl Trait`
99+
// if it happened to occur at `type_binding.ty`
100+
if let TyKind::ImplTrait(..) = type_binding.ty.node {
101+
self.warning_period_57979_didnt_record_next_impl_trait = true;
102+
}
103+
self.visit_assoc_type_binding(type_binding);
104+
}
105+
106+
fn visit_ty_from_generic_args(&mut self, ty: &'a Ty) {
107+
// rust-lang/rust#57979: bug in old visit_generic_args called
108+
// walk_ty rather than visit_ty, skippping outer `impl Trait`
109+
// if it happened to occur at `ty`
110+
if let TyKind::ImplTrait(..) = ty.node {
111+
self.warning_period_57979_didnt_record_next_impl_trait = true;
112+
}
113+
self.visit_ty(ty);
114+
}
115+
116+
fn outer_impl_trait(&mut self, span: Span) -> OuterImplTrait {
117+
let only_recorded_since_pull_request_57730 =
118+
self.warning_period_57979_didnt_record_next_impl_trait;
119+
120+
// (this flag is designed to be set to true and then only
121+
// reach the construction point for the outer impl trait once,
122+
// so its safe and easiest to unconditionally reset it to
123+
// false)
124+
self.warning_period_57979_didnt_record_next_impl_trait = false;
125+
126+
OuterImplTrait {
127+
span, only_recorded_since_pull_request_57730,
128+
}
129+
}
130+
54131
// Mirrors visit::walk_ty, but tracks relevant state
55132
fn walk_ty(&mut self, t: &'a Ty) {
56133
match t.node {
57134
TyKind::ImplTrait(..) => {
58-
self.with_impl_trait(Some(t.span), |this| visit::walk_ty(this, t))
135+
let outer_impl_trait = self.outer_impl_trait(t.span);
136+
self.with_impl_trait(Some(outer_impl_trait), |this| visit::walk_ty(this, t))
59137
}
60138
TyKind::Path(ref qself, ref path) => {
61139
// We allow these:
@@ -406,22 +484,41 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
406484
}
407485
TyKind::ImplTrait(_, ref bounds) => {
408486
if self.is_impl_trait_banned {
409-
struct_span_err!(self.session, ty.span, E0667,
410-
"`impl Trait` is not allowed in path parameters").emit();
487+
if self.warning_period_57979_impl_trait_in_proj {
488+
self.session.buffer_lint(
489+
NESTED_IMPL_TRAIT, ty.id, ty.span,
490+
"`impl Trait` is not allowed in path parameters");
491+
} else {
492+
struct_span_err!(self.session, ty.span, E0667,
493+
"`impl Trait` is not allowed in path parameters").emit();
494+
}
411495
}
412496

413497
if let Some(outer_impl_trait) = self.outer_impl_trait {
414-
struct_span_err!(self.session, ty.span, E0666,
415-
"nested `impl Trait` is not allowed")
416-
.span_label(outer_impl_trait, "outer `impl Trait`")
417-
.span_label(ty.span, "nested `impl Trait` here")
418-
.emit();
419-
498+
if outer_impl_trait.should_warn_instead_of_error() {
499+
self.session.buffer_lint_with_diagnostic(
500+
NESTED_IMPL_TRAIT, ty.id, ty.span,
501+
"nested `impl Trait` is not allowed",
502+
BuiltinLintDiagnostics::NestedImplTrait {
503+
outer_impl_trait_span: outer_impl_trait.span,
504+
inner_impl_trait_span: ty.span,
505+
});
506+
} else {
507+
struct_span_err!(self.session, ty.span, E0666,
508+
"nested `impl Trait` is not allowed")
509+
.span_label(outer_impl_trait.span, "outer `impl Trait`")
510+
.span_label(ty.span, "nested `impl Trait` here")
511+
.emit();
512+
}
420513
}
514+
421515
if !bounds.iter()
422516
.any(|b| if let GenericBound::Trait(..) = *b { true } else { false }) {
423517
self.err_handler().span_err(ty.span, "at least one trait must be specified");
424518
}
519+
520+
self.with_impl_trait_in_proj_warning(true, |this| this.walk_ty(ty));
521+
return;
425522
}
426523
_ => {}
427524
}
@@ -606,18 +703,19 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
606703
GenericArg::Const(..) => ParamKindOrd::Const,
607704
}, arg.span(), None)
608705
}), GenericPosition::Arg, generic_args.span());
706+
609707
// Type bindings such as `Item=impl Debug` in `Iterator<Item=Debug>`
610708
// are allowed to contain nested `impl Trait`.
611709
self.with_impl_trait(None, |this| {
612-
walk_list!(this, visit_assoc_type_binding, &data.bindings);
710+
walk_list!(this, visit_assoc_type_binding_from_generic_args, &data.bindings);
613711
});
614712
}
615713
GenericArgs::Parenthesized(ref data) => {
616714
walk_list!(self, visit_ty, &data.inputs);
617715
if let Some(ref type_) = data.output {
618716
// `-> Foo` syntax is essentially an associated type binding,
619717
// so it is also allowed to contain nested `impl Trait`.
620-
self.with_impl_trait(None, |this| this.visit_ty(type_));
718+
self.with_impl_trait(None, |this| this.visit_ty_from_generic_args(type_));
621719
}
622720
}
623721
}
@@ -719,6 +817,8 @@ pub fn check_crate(session: &Session, krate: &Crate) -> (bool, bool) {
719817
has_global_allocator: false,
720818
outer_impl_trait: None,
721819
is_impl_trait_banned: false,
820+
warning_period_57979_didnt_record_next_impl_trait: false,
821+
warning_period_57979_impl_trait_in_proj: false,
722822
};
723823
visit::walk_crate(&mut validator, krate);
724824

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// rust-lang/rust#57979 : the initial support for `impl Trait` didn't
2+
// properly check syntax hidden behind an associated type projection,
3+
// but it did catch *some cases*. This is checking that we continue to
4+
// properly emit errors for those, even with the new
5+
// future-incompatibility warnings.
6+
//
7+
// issue-57979-nested-impl-trait-in-assoc-proj.rs shows the main case
8+
// that we were previously failing to catch.
9+
10+
struct Deeper<T>(T);
11+
12+
mod allowed {
13+
#![allow(nested_impl_trait)]
14+
15+
pub trait Foo<T> { }
16+
pub trait Bar { }
17+
pub trait Quux { type Assoc; }
18+
pub fn demo(_: impl Quux<Assoc=super::Deeper<impl Foo<impl Bar>>>) { }
19+
//~^ ERROR nested `impl Trait` is not allowed
20+
}
21+
22+
mod warned {
23+
#![warn(nested_impl_trait)]
24+
25+
pub trait Foo<T> { }
26+
pub trait Bar { }
27+
pub trait Quux { type Assoc; }
28+
pub fn demo(_: impl Quux<Assoc=super::Deeper<impl Foo<impl Bar>>>) { }
29+
//~^ ERROR nested `impl Trait` is not allowed
30+
}
31+
32+
mod denied {
33+
#![deny(nested_impl_trait)]
34+
35+
pub trait Foo<T> { }
36+
pub trait Bar { }
37+
pub trait Quux { type Assoc; }
38+
pub fn demo(_: impl Quux<Assoc=super::Deeper<impl Foo<impl Bar>>>) { }
39+
//~^ ERROR nested `impl Trait` is not allowed
40+
}
41+
42+
fn main() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
error[E0666]: nested `impl Trait` is not allowed
2+
--> $DIR/issue-57979-deeply-nested-impl-trait-in-assoc-proj.rs:18:59
3+
|
4+
LL | pub fn demo(_: impl Quux<Assoc=super::Deeper<impl Foo<impl Bar>>>) { }
5+
| ---------^^^^^^^^-
6+
| | |
7+
| | nested `impl Trait` here
8+
| outer `impl Trait`
9+
10+
error[E0666]: nested `impl Trait` is not allowed
11+
--> $DIR/issue-57979-deeply-nested-impl-trait-in-assoc-proj.rs:28:59
12+
|
13+
LL | pub fn demo(_: impl Quux<Assoc=super::Deeper<impl Foo<impl Bar>>>) { }
14+
| ---------^^^^^^^^-
15+
| | |
16+
| | nested `impl Trait` here
17+
| outer `impl Trait`
18+
19+
error[E0666]: nested `impl Trait` is not allowed
20+
--> $DIR/issue-57979-deeply-nested-impl-trait-in-assoc-proj.rs:38:59
21+
|
22+
LL | pub fn demo(_: impl Quux<Assoc=super::Deeper<impl Foo<impl Bar>>>) { }
23+
| ---------^^^^^^^^-
24+
| | |
25+
| | nested `impl Trait` here
26+
| outer `impl Trait`
27+
28+
error: aborting due to 3 previous errors
29+
30+
For more information about this error, try `rustc --explain E0666`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// rust-lang/rust#57979 : the initial support for `impl Trait` didn't
2+
// properly check syntax hidden behind an associated type projection.
3+
// Here we test behavior of occurrences of `impl Trait` within a path
4+
// component in that context.
5+
6+
mod allowed {
7+
#![allow(nested_impl_trait)]
8+
9+
pub trait Bar { }
10+
pub trait Quux<T> { type Assoc; }
11+
pub fn demo(_: impl Quux<(), Assoc=<() as Quux<impl Bar>>::Assoc>) { }
12+
impl<T> Quux<T> for () { type Assoc = u32; }
13+
}
14+
15+
mod warned {
16+
#![warn(nested_impl_trait)]
17+
18+
pub trait Bar { }
19+
pub trait Quux<T> { type Assoc; }
20+
pub fn demo(_: impl Quux<(), Assoc=<() as Quux<impl Bar>>::Assoc>) { }
21+
//~^ WARN `impl Trait` is not allowed in path parameters
22+
//~| WARN will become a hard error in a future release!
23+
impl<T> Quux<T> for () { type Assoc = u32; }
24+
}
25+
26+
mod denied {
27+
#![deny(nested_impl_trait)]
28+
29+
pub trait Bar { }
30+
pub trait Quux<T> { type Assoc; }
31+
pub fn demo(_: impl Quux<(), Assoc=<() as Quux<impl Bar>>::Assoc>) { }
32+
//~^ ERROR `impl Trait` is not allowed in path parameters
33+
//~| WARN will become a hard error in a future release!
34+
impl<T> Quux<T> for () { type Assoc = u32; }
35+
}
36+
37+
fn main() { }

0 commit comments

Comments
 (0)