Skip to content

Commit afe25a2

Browse files
committed
Auto merge of #26515 - quantheory:check_enum_recursion, r=nrc
Fixes #23302. Note that there's an odd situation regarding the following, most likely due to some inadequacy in `const_eval`: ```rust enum Y { A = 1usize, B, } ``` In this case, `Y::B as usize` might be considered a constant expression in some cases, but not others. (See #23513, for a related problem where there is only one variant, with no discriminant, and it doesn't behave nicely as a constant expression either.) Most of the complexity in this PR is basically future-proofing, to ensure that when `Y::B as usize` is fully made to be a constant expression, it can't be used to set `Y::A`, and thus indirectly itself.
2 parents 3198e1a + b952c0e commit afe25a2

File tree

4 files changed

+170
-36
lines changed

4 files changed

+170
-36
lines changed

src/librustc/diagnostics.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1211,7 +1211,6 @@ register_diagnostics! {
12111211
E0138,
12121212
E0139,
12131213
E0264, // unknown external lang item
1214-
E0266, // expected item
12151214
E0269, // not all control paths return a value
12161215
E0270, // computation may converge in a function marked as diverging
12171216
E0272, // rustc_on_unimplemented attribute refers to non-existent type parameter

src/librustc/middle/check_static_recursion.rs

+140-34
Original file line numberDiff line numberDiff line change
@@ -13,68 +13,90 @@
1313

1414
use ast_map;
1515
use session::Session;
16-
use middle::def::{DefStatic, DefConst, DefAssociatedConst, DefMap};
16+
use middle::def::{DefStatic, DefConst, DefAssociatedConst, DefVariant, DefMap};
17+
use util::nodemap::NodeMap;
1718

1819
use syntax::{ast, ast_util};
1920
use syntax::codemap::Span;
2021
use syntax::visit::Visitor;
2122
use syntax::visit;
2223

24+
use std::cell::RefCell;
25+
2326
struct CheckCrateVisitor<'a, 'ast: 'a> {
2427
sess: &'a Session,
2528
def_map: &'a DefMap,
26-
ast_map: &'a ast_map::Map<'ast>
29+
ast_map: &'a ast_map::Map<'ast>,
30+
// `discriminant_map` is a cache that associates the `NodeId`s of local
31+
// variant definitions with the discriminant expression that applies to
32+
// each one. If the variant uses the default values (starting from `0`),
33+
// then `None` is stored.
34+
discriminant_map: RefCell<NodeMap<Option<&'ast ast::Expr>>>,
2735
}
2836

29-
impl<'v, 'a, 'ast> Visitor<'v> for CheckCrateVisitor<'a, 'ast> {
30-
fn visit_item(&mut self, it: &ast::Item) {
37+
impl<'a, 'ast: 'a> Visitor<'ast> for CheckCrateVisitor<'a, 'ast> {
38+
fn visit_item(&mut self, it: &'ast ast::Item) {
3139
match it.node {
32-
ast::ItemStatic(_, _, ref expr) |
33-
ast::ItemConst(_, ref expr) => {
40+
ast::ItemStatic(..) |
41+
ast::ItemConst(..) => {
3442
let mut recursion_visitor =
3543
CheckItemRecursionVisitor::new(self, &it.span);
3644
recursion_visitor.visit_item(it);
37-
visit::walk_expr(self, &*expr)
3845
},
39-
_ => visit::walk_item(self, it)
46+
ast::ItemEnum(ref enum_def, ref generics) => {
47+
// We could process the whole enum, but handling the variants
48+
// with discriminant expressions one by one gives more specific,
49+
// less redundant output.
50+
for variant in &enum_def.variants {
51+
if let Some(_) = variant.node.disr_expr {
52+
let mut recursion_visitor =
53+
CheckItemRecursionVisitor::new(self, &variant.span);
54+
recursion_visitor.populate_enum_discriminants(enum_def);
55+
recursion_visitor.visit_variant(variant, generics);
56+
}
57+
}
58+
}
59+
_ => {}
4060
}
61+
visit::walk_item(self, it)
4162
}
4263

43-
fn visit_trait_item(&mut self, ti: &ast::TraitItem) {
64+
fn visit_trait_item(&mut self, ti: &'ast ast::TraitItem) {
4465
match ti.node {
4566
ast::ConstTraitItem(_, ref default) => {
46-
if let Some(ref expr) = *default {
67+
if let Some(_) = *default {
4768
let mut recursion_visitor =
4869
CheckItemRecursionVisitor::new(self, &ti.span);
4970
recursion_visitor.visit_trait_item(ti);
50-
visit::walk_expr(self, &*expr)
5171
}
5272
}
53-
_ => visit::walk_trait_item(self, ti)
73+
_ => {}
5474
}
75+
visit::walk_trait_item(self, ti)
5576
}
5677

57-
fn visit_impl_item(&mut self, ii: &ast::ImplItem) {
78+
fn visit_impl_item(&mut self, ii: &'ast ast::ImplItem) {
5879
match ii.node {
59-
ast::ConstImplItem(_, ref expr) => {
80+
ast::ConstImplItem(..) => {
6081
let mut recursion_visitor =
6182
CheckItemRecursionVisitor::new(self, &ii.span);
6283
recursion_visitor.visit_impl_item(ii);
63-
visit::walk_expr(self, &*expr)
6484
}
65-
_ => visit::walk_impl_item(self, ii)
85+
_ => {}
6686
}
87+
visit::walk_impl_item(self, ii)
6788
}
6889
}
6990

7091
pub fn check_crate<'ast>(sess: &Session,
71-
krate: &ast::Crate,
92+
krate: &'ast ast::Crate,
7293
def_map: &DefMap,
7394
ast_map: &ast_map::Map<'ast>) {
7495
let mut visitor = CheckCrateVisitor {
7596
sess: sess,
7697
def_map: def_map,
77-
ast_map: ast_map
98+
ast_map: ast_map,
99+
discriminant_map: RefCell::new(NodeMap()),
78100
};
79101
visit::walk_crate(&mut visitor, krate);
80102
sess.abort_if_errors();
@@ -85,53 +107,120 @@ struct CheckItemRecursionVisitor<'a, 'ast: 'a> {
85107
sess: &'a Session,
86108
ast_map: &'a ast_map::Map<'ast>,
87109
def_map: &'a DefMap,
88-
idstack: Vec<ast::NodeId>
110+
discriminant_map: &'a RefCell<NodeMap<Option<&'ast ast::Expr>>>,
111+
idstack: Vec<ast::NodeId>,
89112
}
90113

91114
impl<'a, 'ast: 'a> CheckItemRecursionVisitor<'a, 'ast> {
92-
fn new(v: &CheckCrateVisitor<'a, 'ast>, span: &'a Span)
115+
fn new(v: &'a CheckCrateVisitor<'a, 'ast>, span: &'a Span)
93116
-> CheckItemRecursionVisitor<'a, 'ast> {
94117
CheckItemRecursionVisitor {
95118
root_span: span,
96119
sess: v.sess,
97120
ast_map: v.ast_map,
98121
def_map: v.def_map,
99-
idstack: Vec::new()
122+
discriminant_map: &v.discriminant_map,
123+
idstack: Vec::new(),
100124
}
101125
}
102126
fn with_item_id_pushed<F>(&mut self, id: ast::NodeId, f: F)
103127
where F: Fn(&mut Self) {
104-
if self.idstack.iter().any(|x| x == &(id)) {
128+
if self.idstack.iter().any(|x| *x == id) {
105129
span_err!(self.sess, *self.root_span, E0265, "recursive constant");
106130
return;
107131
}
108132
self.idstack.push(id);
109133
f(self);
110134
self.idstack.pop();
111135
}
136+
// If a variant has an expression specifying its discriminant, then it needs
137+
// to be checked just like a static or constant. However, if there are more
138+
// variants with no explicitly specified discriminant, those variants will
139+
// increment the same expression to get their values.
140+
//
141+
// So for every variant, we need to track whether there is an expression
142+
// somewhere in the enum definition that controls its discriminant. We do
143+
// this by starting from the end and searching backward.
144+
fn populate_enum_discriminants(&self, enum_definition: &'ast ast::EnumDef) {
145+
// Get the map, and return if we already processed this enum or if it
146+
// has no variants.
147+
let mut discriminant_map = self.discriminant_map.borrow_mut();
148+
match enum_definition.variants.first() {
149+
None => { return; }
150+
Some(variant) if discriminant_map.contains_key(&variant.node.id) => {
151+
return;
152+
}
153+
_ => {}
154+
}
155+
156+
// Go through all the variants.
157+
let mut variant_stack: Vec<ast::NodeId> = Vec::new();
158+
for variant in enum_definition.variants.iter().rev() {
159+
variant_stack.push(variant.node.id);
160+
// When we find an expression, every variant currently on the stack
161+
// is affected by that expression.
162+
if let Some(ref expr) = variant.node.disr_expr {
163+
for id in &variant_stack {
164+
discriminant_map.insert(*id, Some(expr));
165+
}
166+
variant_stack.clear()
167+
}
168+
}
169+
// If we are at the top, that always starts at 0, so any variant on the
170+
// stack has a default value and does not need to be checked.
171+
for id in &variant_stack {
172+
discriminant_map.insert(*id, None);
173+
}
174+
}
112175
}
113176

114-
impl<'a, 'ast, 'v> Visitor<'v> for CheckItemRecursionVisitor<'a, 'ast> {
115-
fn visit_item(&mut self, it: &ast::Item) {
177+
impl<'a, 'ast: 'a> Visitor<'ast> for CheckItemRecursionVisitor<'a, 'ast> {
178+
fn visit_item(&mut self, it: &'ast ast::Item) {
116179
self.with_item_id_pushed(it.id, |v| visit::walk_item(v, it));
117180
}
118181

119-
fn visit_trait_item(&mut self, ti: &ast::TraitItem) {
182+
fn visit_enum_def(&mut self, enum_definition: &'ast ast::EnumDef,
183+
generics: &'ast ast::Generics) {
184+
self.populate_enum_discriminants(enum_definition);
185+
visit::walk_enum_def(self, enum_definition, generics);
186+
}
187+
188+
fn visit_variant(&mut self, variant: &'ast ast::Variant,
189+
_: &'ast ast::Generics) {
190+
let variant_id = variant.node.id;
191+
let maybe_expr;
192+
if let Some(get_expr) = self.discriminant_map.borrow().get(&variant_id) {
193+
// This is necessary because we need to let the `discriminant_map`
194+
// borrow fall out of scope, so that we can reborrow farther down.
195+
maybe_expr = (*get_expr).clone();
196+
} else {
197+
self.sess.span_bug(variant.span,
198+
"`check_static_recursion` attempted to visit \
199+
variant with unknown discriminant")
200+
}
201+
// If `maybe_expr` is `None`, that's because no discriminant is
202+
// specified that affects this variant. Thus, no risk of recursion.
203+
if let Some(expr) = maybe_expr {
204+
self.with_item_id_pushed(expr.id, |v| visit::walk_expr(v, expr));
205+
}
206+
}
207+
208+
fn visit_trait_item(&mut self, ti: &'ast ast::TraitItem) {
120209
self.with_item_id_pushed(ti.id, |v| visit::walk_trait_item(v, ti));
121210
}
122211

123-
fn visit_impl_item(&mut self, ii: &ast::ImplItem) {
212+
fn visit_impl_item(&mut self, ii: &'ast ast::ImplItem) {
124213
self.with_item_id_pushed(ii.id, |v| visit::walk_impl_item(v, ii));
125214
}
126215

127-
fn visit_expr(&mut self, e: &ast::Expr) {
216+
fn visit_expr(&mut self, e: &'ast ast::Expr) {
128217
match e.node {
129218
ast::ExprPath(..) => {
130219
match self.def_map.borrow().get(&e.id).map(|d| d.base_def) {
131220
Some(DefStatic(def_id, _)) |
132221
Some(DefAssociatedConst(def_id, _)) |
133-
Some(DefConst(def_id)) if
134-
ast_util::is_local(def_id) => {
222+
Some(DefConst(def_id))
223+
if ast_util::is_local(def_id) => {
135224
match self.ast_map.get(def_id.node) {
136225
ast_map::NodeItem(item) =>
137226
self.visit_item(item),
@@ -141,11 +230,28 @@ impl<'a, 'ast, 'v> Visitor<'v> for CheckItemRecursionVisitor<'a, 'ast> {
141230
self.visit_impl_item(item),
142231
ast_map::NodeForeignItem(_) => {},
143232
_ => {
144-
span_err!(self.sess, e.span, E0266,
145-
"expected item, found {}",
146-
self.ast_map.node_to_string(def_id.node));
147-
return;
148-
},
233+
self.sess.span_bug(
234+
e.span,
235+
&format!("expected item, found {}",
236+
self.ast_map.node_to_string(def_id.node)));
237+
}
238+
}
239+
}
240+
// For variants, we only want to check expressions that
241+
// affect the specific variant used, but we need to check
242+
// the whole enum definition to see what expression that
243+
// might be (if any).
244+
Some(DefVariant(enum_id, variant_id, false))
245+
if ast_util::is_local(enum_id) => {
246+
if let ast::ItemEnum(ref enum_def, ref generics) =
247+
self.ast_map.expect_item(enum_id.local_id()).node {
248+
self.populate_enum_discriminants(enum_def);
249+
let variant = self.ast_map.expect_variant(variant_id.local_id());
250+
self.visit_variant(variant, generics);
251+
} else {
252+
self.sess.span_bug(e.span,
253+
"`check_static_recursion` found \
254+
non-enum in DefVariant");
149255
}
150256
}
151257
_ => ()

src/libsyntax/visit.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ pub trait Visitor<'v> : Sized {
9090
walk_struct_def(self, s)
9191
}
9292
fn visit_struct_field(&mut self, s: &'v StructField) { walk_struct_field(self, s) }
93+
fn visit_enum_def(&mut self, enum_definition: &'v EnumDef,
94+
generics: &'v Generics) {
95+
walk_enum_def(self, enum_definition, generics)
96+
}
97+
9398
fn visit_variant(&mut self, v: &'v Variant, g: &'v Generics) { walk_variant(self, v, g) }
9499

95100
/// Visits an optional reference to a lifetime. The `span` is the span of some surrounding
@@ -268,7 +273,7 @@ pub fn walk_item<'v, V: Visitor<'v>>(visitor: &mut V, item: &'v Item) {
268273
}
269274
ItemEnum(ref enum_definition, ref type_parameters) => {
270275
visitor.visit_generics(type_parameters);
271-
walk_enum_def(visitor, enum_definition, type_parameters)
276+
visitor.visit_enum_def(enum_definition, type_parameters)
272277
}
273278
ItemDefaultImpl(_, ref trait_ref) => {
274279
visitor.visit_trait_ref(trait_ref)

src/test/compile-fail/issue-23302.rs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2015 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+
// Check that an enum with recursion in the discriminant throws
12+
// the appropriate error (rather than, say, blowing the stack).
13+
enum X {
14+
A = X::A as isize, //~ ERROR E0265
15+
}
16+
17+
// Since `Y::B` here defaults to `Y::A+1`, this is also a
18+
// recursive definition.
19+
enum Y {
20+
A = Y::B as isize, //~ ERROR E0265
21+
B,
22+
}
23+
24+
fn main() { }

0 commit comments

Comments
 (0)