From 53ddf2e57d59387443a062c4f4348801dea0c9dd Mon Sep 17 00:00:00 2001
From: Johannes Muenzel <jmuenzel@gmail.com>
Date: Mon, 6 Oct 2014 02:20:25 -0400
Subject: [PATCH] Fix several issues with the struct and enum
 representability-checking logic.

---
 src/librustc/middle/ty.rs              | 181 ++++++++++++++++---------
 src/test/compile-fail/issue-17431-1.rs |  16 +++
 src/test/compile-fail/issue-17431-2.rs |  19 +++
 src/test/compile-fail/issue-17431-3.rs |  18 +++
 src/test/compile-fail/issue-17431-4.rs |  16 +++
 src/test/compile-fail/issue-17431-5.rs |  18 +++
 src/test/compile-fail/issue-17431-6.rs |  18 +++
 src/test/compile-fail/issue-17431-7.rs |  16 +++
 src/test/compile-fail/issue-3008-3.rs  |   2 +
 9 files changed, 241 insertions(+), 63 deletions(-)
 create mode 100644 src/test/compile-fail/issue-17431-1.rs
 create mode 100644 src/test/compile-fail/issue-17431-2.rs
 create mode 100644 src/test/compile-fail/issue-17431-3.rs
 create mode 100644 src/test/compile-fail/issue-17431-4.rs
 create mode 100644 src/test/compile-fail/issue-17431-5.rs
 create mode 100644 src/test/compile-fail/issue-17431-6.rs
 create mode 100644 src/test/compile-fail/issue-17431-7.rs

diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs
index 10bc1da3acb..800a77160db 100644
--- a/src/librustc/middle/ty.rs
+++ b/src/librustc/middle/ty.rs
@@ -2823,11 +2823,14 @@ 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
@@ -2835,98 +2838,150 @@ pub enum Representability {
 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
+    fn are_inner_types_recursive(cx: &ctxt, sp: Span,
+                                 seen: &mut Vec<t>, ty: t) -> Representability {
         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
-        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);
-
-                    if r != Representable { break }
-                }
-
-                seen.pop();
-                r
+                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))
+                find_nonrepresentable(cx, sp, seen, upvars.iter().map(|f| f.ty))
             }
-
             _ => Representable,
         }
     }
 
+    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;
+                }
+
+                let types_a = substs_a.types.get_slice(subst::TypeSpace);
+                let types_b = substs_b.types.get_slice(subst::TypeSpace);
+
+                let mut pairs = types_a.iter().zip(types_b.iter());
+
+                pairs.all(|(&a, &b)| same_type(a, b))
+            }
+            _ => {
+                type_id(a) == type_id(b)
+            }
+        }
+    }
+
+    // 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
+                    // 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:
+                    //
+                    // 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)
+            }
+        }
+    }
+
     debug!("is_type_representable: {}",
            ::util::ppaux::ty_to_string(cx, ty));
 
     // 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 {
diff --git a/src/test/compile-fail/issue-17431-1.rs b/src/test/compile-fail/issue-17431-1.rs
new file mode 100644
index 00000000000..896a9c06873
--- /dev/null
+++ b/src/test/compile-fail/issue-17431-1.rs
@@ -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() {}
diff --git a/src/test/compile-fail/issue-17431-2.rs b/src/test/compile-fail/issue-17431-2.rs
new file mode 100644
index 00000000000..886fe8d771a
--- /dev/null
+++ b/src/test/compile-fail/issue-17431-2.rs
@@ -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() {}
diff --git a/src/test/compile-fail/issue-17431-3.rs b/src/test/compile-fail/issue-17431-3.rs
new file mode 100644
index 00000000000..c1c450935f6
--- /dev/null
+++ b/src/test/compile-fail/issue-17431-3.rs
@@ -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() {}
diff --git a/src/test/compile-fail/issue-17431-4.rs b/src/test/compile-fail/issue-17431-4.rs
new file mode 100644
index 00000000000..1e27f025564
--- /dev/null
+++ b/src/test/compile-fail/issue-17431-4.rs
@@ -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() {}
diff --git a/src/test/compile-fail/issue-17431-5.rs b/src/test/compile-fail/issue-17431-5.rs
new file mode 100644
index 00000000000..d22d79ecaa5
--- /dev/null
+++ b/src/test/compile-fail/issue-17431-5.rs
@@ -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() {
+}
diff --git a/src/test/compile-fail/issue-17431-6.rs b/src/test/compile-fail/issue-17431-6.rs
new file mode 100644
index 00000000000..8eac295353d
--- /dev/null
+++ b/src/test/compile-fail/issue-17431-6.rs
@@ -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() {}
diff --git a/src/test/compile-fail/issue-17431-7.rs b/src/test/compile-fail/issue-17431-7.rs
new file mode 100644
index 00000000000..c64c040aa44
--- /dev/null
+++ b/src/test/compile-fail/issue-17431-7.rs
@@ -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() { }
diff --git a/src/test/compile-fail/issue-3008-3.rs b/src/test/compile-fail/issue-3008-3.rs
index b8ef57e2dd3..a338a01690d 100644
--- a/src/test/compile-fail/issue-3008-3.rs
+++ b/src/test/compile-fail/issue-3008-3.rs
@@ -12,5 +12,7 @@ enum E1 { V1(E2<E1>), }
 enum E2<T> { V2(E2<E1>), }
 //~^ ERROR illegal recursive enum type; wrap the inner value in a box to make it representable
 
+impl E1 { fn foo(&self) {} }
+
 fn main() {
 }