-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Bug fixes for struct+enum representability check #17815
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2823,99 +2823,151 @@ pub fn is_instantiable(cx: &ctxt, r_ty: t) -> bool { | |
/// distinguish between types that are recursive with themselves and types that | ||
/// contain a different recursive type. These cases can therefore be treated | ||
/// differently when reporting errors. | ||
#[deriving(PartialEq)] | ||
/// | ||
/// The ordering of the cases is significant. They are sorted so that cmp::max | ||
/// will keep the "more erroneous" of two values. | ||
#[deriving(PartialOrd, Ord, Eq, PartialEq, Show)] | ||
pub enum Representability { | ||
Representable, | ||
SelfRecursive, | ||
ContainsRecursive, | ||
SelfRecursive, | ||
} | ||
|
||
/// Check whether a type is representable. This means it cannot contain unboxed | ||
/// structural recursion. This check is needed for structs and enums. | ||
pub fn is_type_representable(cx: &ctxt, sp: Span, ty: t) -> Representability { | ||
|
||
// Iterate until something non-representable is found | ||
fn find_nonrepresentable<It: Iterator<t>>(cx: &ctxt, sp: Span, seen: &mut Vec<DefId>, | ||
fn find_nonrepresentable<It: Iterator<t>>(cx: &ctxt, sp: Span, seen: &mut Vec<t>, | ||
mut iter: It) -> Representability { | ||
for ty in iter { | ||
let r = type_structurally_recursive(cx, sp, seen, ty); | ||
if r != Representable { | ||
return r | ||
} | ||
} | ||
Representable | ||
iter.fold(Representable, | ||
|r, ty| cmp::max(r, is_type_structurally_recursive(cx, sp, seen, ty))) | ||
} | ||
|
||
// Does the type `ty` directly (without indirection through a pointer) | ||
// contain any types on stack `seen`? | ||
fn type_structurally_recursive(cx: &ctxt, sp: Span, seen: &mut Vec<DefId>, | ||
ty: t) -> Representability { | ||
debug!("type_structurally_recursive: {}", | ||
::util::ppaux::ty_to_string(cx, ty)); | ||
|
||
// Compare current type to previously seen types | ||
match get(ty).sty { | ||
ty_struct(did, _) | | ||
ty_enum(did, _) => { | ||
for (i, &seen_did) in seen.iter().enumerate() { | ||
if did == seen_did { | ||
return if i == 0 { SelfRecursive } | ||
else { ContainsRecursive } | ||
} | ||
} | ||
} | ||
_ => (), | ||
} | ||
|
||
// Check inner types | ||
fn are_inner_types_recursive(cx: &ctxt, sp: Span, | ||
seen: &mut Vec<t>, ty: t) -> Representability { | ||
match get(ty).sty { | ||
// Tuples | ||
ty_tup(ref ts) => { | ||
find_nonrepresentable(cx, sp, seen, ts.iter().map(|t| *t)) | ||
} | ||
// Fixed-length vectors. | ||
// FIXME(#11924) Behavior undecided for zero-length vectors. | ||
ty_vec(ty, Some(_)) => { | ||
type_structurally_recursive(cx, sp, seen, ty) | ||
is_type_structurally_recursive(cx, sp, seen, ty) | ||
} | ||
|
||
// Push struct and enum def-ids onto `seen` before recursing. | ||
ty_struct(did, ref substs) => { | ||
seen.push(did); | ||
let fields = struct_fields(cx, did, substs); | ||
let r = find_nonrepresentable(cx, sp, seen, | ||
fields.iter().map(|f| f.mt.ty)); | ||
seen.pop(); | ||
r | ||
find_nonrepresentable(cx, sp, seen, fields.iter().map(|f| f.mt.ty)) | ||
} | ||
|
||
ty_enum(did, ref substs) => { | ||
seen.push(did); | ||
let vs = enum_variants(cx, did); | ||
let iter = vs.iter() | ||
.flat_map(|variant| { variant.args.iter() }) | ||
.map(|aty| { aty.subst_spanned(cx, substs, Some(sp)) }); | ||
|
||
let mut r = Representable; | ||
for variant in vs.iter() { | ||
let iter = variant.args.iter().map(|aty| { | ||
aty.subst_spanned(cx, substs, Some(sp)) | ||
}); | ||
r = find_nonrepresentable(cx, sp, seen, iter); | ||
find_nonrepresentable(cx, sp, seen, iter) | ||
} | ||
ty_unboxed_closure(did, _) => { | ||
let upvars = unboxed_closure_upvars(cx, did); | ||
find_nonrepresentable(cx, sp, seen, upvars.iter().map(|f| f.ty)) | ||
} | ||
_ => Representable, | ||
} | ||
} | ||
|
||
if r != Representable { break } | ||
fn same_struct_or_enum_def_id(ty: t, did: DefId) -> bool { | ||
match get(ty).sty { | ||
ty_struct(ty_did, _) | ty_enum(ty_did, _) => { | ||
ty_did == did | ||
} | ||
_ => false | ||
} | ||
} | ||
|
||
fn same_type(a: t, b: t) -> bool { | ||
match (&get(a).sty, &get(b).sty) { | ||
(&ty_struct(did_a, ref substs_a), &ty_struct(did_b, ref substs_b)) | | ||
(&ty_enum(did_a, ref substs_a), &ty_enum(did_b, ref substs_b)) => { | ||
if did_a != did_b { | ||
return false; | ||
} | ||
|
||
seen.pop(); | ||
r | ||
} | ||
let types_a = substs_a.types.get_slice(subst::TypeSpace); | ||
let types_b = substs_b.types.get_slice(subst::TypeSpace); | ||
|
||
ty_unboxed_closure(did, _) => { | ||
let upvars = unboxed_closure_upvars(cx, did); | ||
find_nonrepresentable(cx, | ||
sp, | ||
seen, | ||
upvars.iter().map(|f| f.ty)) | ||
let mut pairs = types_a.iter().zip(types_b.iter()); | ||
|
||
pairs.all(|(&a, &b)| same_type(a, b)) | ||
} | ||
_ => { | ||
type_id(a) == type_id(b) | ||
} | ||
} | ||
} | ||
|
||
_ => Representable, | ||
// Does the type `ty` directly (without indirection through a pointer) | ||
// contain any types on stack `seen`? | ||
fn is_type_structurally_recursive(cx: &ctxt, sp: Span, seen: &mut Vec<t>, | ||
ty: t) -> Representability { | ||
debug!("is_type_structurally_recursive: {}", | ||
::util::ppaux::ty_to_string(cx, ty)); | ||
|
||
match get(ty).sty { | ||
ty_struct(did, _) | ty_enum(did, _) => { | ||
{ | ||
// Iterate through stack of previously seen types. | ||
let mut iter = seen.iter(); | ||
|
||
// The first item in `seen` is the type we are actually curious about. | ||
// We want to return SelfRecursive if this type contains itself. | ||
// It is important that we DON'T take generic parameters into account | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Taking into account generic parameters would... mean that, (Is the fact that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. (But the definition of Foo is irrelevant--I'll remove it) |
||
// for this check, so that Bar<T> in this example counts as SelfRecursive: | ||
// | ||
// struct Foo; | ||
// struct Bar<T> { x: Bar<Foo> } | ||
|
||
match iter.next() { | ||
Some(&seen_type) => { | ||
if same_struct_or_enum_def_id(seen_type, did) { | ||
debug!("SelfRecursive: {} contains {}", | ||
::util::ppaux::ty_to_string(cx, seen_type), | ||
::util::ppaux::ty_to_string(cx, ty)); | ||
return SelfRecursive; | ||
} | ||
} | ||
None => {} | ||
} | ||
|
||
// We also need to know whether the first item contains other types that | ||
// are structurally recursive. If we don't catch this case, we will recurse | ||
// infinitely for some inputs. | ||
// | ||
// It is important that we DO take generic parameters into account here, | ||
// so that code like this is considered SelfRecursive, not ContainsRecursive: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is considered (Just checking my understanding.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup. The problem was that we'd terminate when we saw Option for the second time. |
||
// | ||
// struct Foo { Option<Option<Foo>> } | ||
|
||
for &seen_type in iter { | ||
if same_type(ty, seen_type) { | ||
debug!("ContainsRecursive: {} contains {}", | ||
::util::ppaux::ty_to_string(cx, seen_type), | ||
::util::ppaux::ty_to_string(cx, ty)); | ||
return ContainsRecursive; | ||
} | ||
} | ||
} | ||
|
||
// For structs and enums, track all previously seen types by pushing them | ||
// onto the 'seen' stack. | ||
seen.push(ty); | ||
let out = are_inner_types_recursive(cx, sp, seen, ty); | ||
seen.pop(); | ||
out | ||
} | ||
_ => { | ||
// No need to push in other cases. | ||
are_inner_types_recursive(cx, sp, seen, ty) | ||
} | ||
} | ||
} | ||
|
||
|
@@ -2925,8 +2977,11 @@ pub fn is_type_representable(cx: &ctxt, sp: Span, ty: t) -> Representability { | |
// To avoid a stack overflow when checking an enum variant or struct that | ||
// contains a different, structurally recursive type, maintain a stack | ||
// of seen types and check recursion for each of them (issues #3008, #3779). | ||
let mut seen: Vec<DefId> = Vec::new(); | ||
type_structurally_recursive(cx, sp, &mut seen, ty) | ||
let mut seen: Vec<t> = Vec::new(); | ||
let r = is_type_structurally_recursive(cx, sp, &mut seen, ty); | ||
debug!("is_type_representable: {} is {}", | ||
::util::ppaux::ty_to_string(cx, ty), r); | ||
r | ||
} | ||
|
||
pub fn type_is_trait(ty: t) -> bool { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
struct Foo { foo: Option<Option<Foo>> } | ||
//~^ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable | ||
|
||
impl Foo { fn bar(&self) {} } | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
struct Baz { q: Option<Foo> } | ||
//~^ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable | ||
|
||
struct Foo { q: Option<Baz> } | ||
//~^ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable | ||
|
||
impl Foo { fn bar(&self) {} } | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
use std::sync::Mutex; | ||
|
||
struct Foo { foo: Mutex<Option<Foo>> } | ||
//~^ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable | ||
|
||
impl Foo { fn bar(&self) {} } | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
struct Foo<T> { foo: Option<Option<Foo<T>>> } | ||
//~^ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable | ||
|
||
impl<T> Foo<T> { fn bar(&self) {} } | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
struct Foo { foo: Bar<Foo> } | ||
struct Bar<T> { x: Bar<Foo> } | ||
//~^ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable | ||
|
||
impl Foo { fn foo(&self) {} } | ||
|
||
fn main() { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
use std::sync::Mutex; | ||
|
||
enum Foo { X(Mutex<Option<Foo>>) } | ||
//~^ ERROR illegal recursive enum type; wrap the inner value in a box to make it representable | ||
|
||
impl Foo { fn bar(self) {} } | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
enum Foo { Voo(Option<Option<Foo>>) } | ||
//~^ ERROR illegal recursive enum type; wrap the inner value in a box to make it representable | ||
|
||
impl Foo { fn bar(&self) {} } | ||
|
||
fn main() { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I take it this ordering is now important; could you add a comment describing this? (Or at least noting it.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, will do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know whether #[deriving(Ord)] is guaranteed to order the variants so the first listed is the 'least' and the last is the 'greatest'? Or is it better if I don't rely on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think we do have that guarantee.