Skip to content

Commit c993033

Browse files
committed
Revised warning-downgrade strategy for nested impl trait.
Instead of a sticky-boolean flag that would downgrade errors to warnings during further recursion into the type (which is overly broad because we were not missing errors at arbitrarily deep levels), this instead tracks state closer to what the original bug actually was. In particular, the actual original bug was that we were failing to record the existence of an outer `impl Trait` solely when it occurred as an *immediate child* during the walk of the child types in `visit_generic_args`. Therefore, the correct way to precisely model when that bug would manifest itself (and thus downgrade the error-to-warning accordingly) is to track when those outer `impl Trait` cases were previously unrecorded. That's what this code does, by storing a flag with the recorded outer `impl Trait` indicating at which point in the compiler's control flow it had been stored. I will note that this commit passes the current test suite. A follow-up commit will also include tests illustrating the cases that this commit gets right (and were handled incorrectly by the previous sticky boolean).
1 parent d6cee67 commit c993033

File tree

1 file changed

+77
-27
lines changed

1 file changed

+77
-27
lines changed

src/librustc_passes/ast_validation.rs

+77-27
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,31 @@ use syntax_pos::Span;
2424
use errors::Applicability;
2525
use log::debug;
2626

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+
2752
struct AstValidator<'a> {
2853
session: &'a Session,
2954
has_proc_macro_decls: bool,
@@ -32,7 +57,7 @@ struct AstValidator<'a> {
3257
// Used to ban nested `impl Trait`, e.g., `impl Into<impl Debug>`.
3358
// Nested `impl Trait` _is_ allowed in associated type position,
3459
// e.g `impl Iterator<Item=impl Debug>`
35-
outer_impl_trait: Option<Span>,
60+
outer_impl_trait: Option<OuterImplTrait>,
3661

3762
// Used to ban `impl Trait` in path projections like `<impl Iterator>::Item`
3863
// or `Foo::Bar<impl Trait>`
@@ -44,18 +69,11 @@ struct AstValidator<'a> {
4469
// impl trait in projections), and thus miss some cases. We track
4570
// whether we should downgrade to a warning for short-term via
4671
// these booleans.
47-
warning_period_57979_nested_impl_trait: bool,
72+
warning_period_57979_didnt_record_next_impl_trait: bool,
4873
warning_period_57979_impl_trait_in_proj: bool,
4974
}
5075

5176
impl<'a> AstValidator<'a> {
52-
fn with_nested_impl_trait_warning<T>(&mut self, v: bool, f: impl FnOnce(&mut Self) -> T) -> T {
53-
let old = mem::replace(&mut self.warning_period_57979_nested_impl_trait, v);
54-
let ret = f(self);
55-
self.warning_period_57979_nested_impl_trait = old;
56-
ret
57-
}
58-
5977
fn with_impl_trait_in_proj_warning<T>(&mut self, v: bool, f: impl FnOnce(&mut Self) -> T) -> T {
6078
let old = mem::replace(&mut self.warning_period_57979_impl_trait_in_proj, v);
6179
let ret = f(self);
@@ -69,17 +87,53 @@ impl<'a> AstValidator<'a> {
6987
self.is_impl_trait_banned = old;
7088
}
7189

72-
fn with_impl_trait(&mut self, outer_impl_trait: Option<Span>, f: impl FnOnce(&mut Self)) {
73-
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);
7492
f(self);
7593
self.outer_impl_trait = old;
7694
}
7795

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+
78131
// Mirrors visit::walk_ty, but tracks relevant state
79132
fn walk_ty(&mut self, t: &'a Ty) {
80133
match t.node {
81134
TyKind::ImplTrait(..) => {
82-
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))
83137
}
84138
TyKind::Path(ref qself, ref path) => {
85139
// We allow these:
@@ -441,18 +495,18 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
441495
}
442496

443497
if let Some(outer_impl_trait) = self.outer_impl_trait {
444-
if self.warning_period_57979_nested_impl_trait {
498+
if outer_impl_trait.should_warn_instead_of_error() {
445499
self.session.buffer_lint_with_diagnostic(
446500
NESTED_IMPL_TRAIT, ty.id, ty.span,
447501
"nested `impl Trait` is not allowed",
448502
BuiltinLintDiagnostics::NestedImplTrait {
449-
outer_impl_trait_span: outer_impl_trait,
503+
outer_impl_trait_span: outer_impl_trait.span,
450504
inner_impl_trait_span: ty.span,
451505
});
452506
} else {
453507
struct_span_err!(self.session, ty.span, E0666,
454508
"nested `impl Trait` is not allowed")
455-
.span_label(outer_impl_trait, "outer `impl Trait`")
509+
.span_label(outer_impl_trait.span, "outer `impl Trait`")
456510
.span_label(ty.span, "nested `impl Trait` here")
457511
.emit();
458512
}
@@ -650,22 +704,18 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
650704
}, arg.span(), None)
651705
}), GenericPosition::Arg, generic_args.span());
652706

653-
self.with_nested_impl_trait_warning(true, |this| {
654-
// Type bindings such as `Item=impl Debug` in `Iterator<Item=Debug>`
655-
// are allowed to contain nested `impl Trait`.
656-
this.with_impl_trait(None, |this| {
657-
walk_list!(this, visit_assoc_type_binding, &data.bindings);
658-
});
707+
// Type bindings such as `Item=impl Debug` in `Iterator<Item=Debug>`
708+
// are allowed to contain nested `impl Trait`.
709+
self.with_impl_trait(None, |this| {
710+
walk_list!(this, visit_assoc_type_binding_from_generic_args, &data.bindings);
659711
});
660712
}
661713
GenericArgs::Parenthesized(ref data) => {
662714
walk_list!(self, visit_ty, &data.inputs);
663715
if let Some(ref type_) = data.output {
664-
self.with_nested_impl_trait_warning(true, |this| {
665-
// `-> Foo` syntax is essentially an associated type binding,
666-
// so it is also allowed to contain nested `impl Trait`.
667-
this.with_impl_trait(None, |this| this.visit_ty(type_));
668-
});
716+
// `-> Foo` syntax is essentially an associated type binding,
717+
// so it is also allowed to contain nested `impl Trait`.
718+
self.with_impl_trait(None, |this| this.visit_ty_from_generic_args(type_));
669719
}
670720
}
671721
}
@@ -767,7 +817,7 @@ pub fn check_crate(session: &Session, krate: &Crate) -> (bool, bool) {
767817
has_global_allocator: false,
768818
outer_impl_trait: None,
769819
is_impl_trait_banned: false,
770-
warning_period_57979_nested_impl_trait: false,
820+
warning_period_57979_didnt_record_next_impl_trait: false,
771821
warning_period_57979_impl_trait_in_proj: false,
772822
};
773823
visit::walk_crate(&mut validator, krate);

0 commit comments

Comments
 (0)