Index: src/Common/PersistentDisjointSet.h
===================================================================
--- src/Common/PersistentDisjointSet.h	(revision b21c77a0e211d9361763565a2eb5ef4330f2957b)
+++ src/Common/PersistentDisjointSet.h	(revision 184557ed94d1cdbdc6fd685e497e5375f8968a96)
@@ -128,4 +128,7 @@
 		}
 	}
+
+	/// Non-initializing constructor; should call init() before use
+	PersistentDisjointSet( Mode m ) : data(), mode(m) {}
 
 	PersistentDisjointSet( Mode m, Base&& b ) : data(), mode(m) {
@@ -291,5 +294,5 @@
 	size_type count(Elm i) const { return rerooted().count( i ); }
 
-	/// Finds root for element i, assertfs i is present
+	/// Finds root for element i, undefined behaviour if i is not present
 	Elm find(Elm i) const {
 		const Base& self = rerooted();
@@ -375,15 +378,81 @@
 	}
 
-	/// Reports all members of a class to `out`; none if i does not exist in map
-	template<typename OutputIterator>
-	void find_class(Elm i, OutputIterator&& out) const {
+private:
+	/// Removes the children of the tree rooted at `root` as `root_node`, returning a new 
+	/// uninitialized node and editing `next_edit` to point toward this new node.
+	static Self* remove_children(Elm root, Node& root_node, Base& base_map, Self* next_edit) {
+		// Invariant: root.next is *always* a pointer to a leaf of the most-recently added
+		// subtree.
+		//
+		// Proof: By induction on height:
+		// * height == 0: root.next == root, trivially true
+		// * added.height < root.height: true by ind. hyp.
+		// * added.height == root.height: no node may have a child the same height, ergo 
+		//   property holds for added, therefore root.
+		//
+		// Corollary: next of most-recently-added subtree root is previously added subtree
+
+		// remove all subtrees
+		while ( root != root_node.next ) {
+			// find added child
+			auto it = base_map.find( root_node.next );
+			while ( it->second.parent != root ) it = base_map.find( it->second.parent );
+			Elm added = it->first;
+			Node& added_node = it->second;
+
+			// unlink subtree and set up previous map as ADDTO new map
+			std::swap( root_node.next, added_node.next );
+			Self* new_edit = new Self{ BASE };
+			next_edit->init<AddTo>( new_edit, root, added, false );  // assume unchanged root height
+			next_edit->mode = ADDTO;
+
+			// remove subtree children from map
+			next_edit = remove_children( added, added_node, base_map, new_edit );
+
+			// remove subtree root from map and set up previous map as ADD to new map
+			base_map.erase( added );
+			new_edit = new Self{ BASE };
+			next_edit->init<Add>( new_edit, added );
+			next_edit->mode = ADD;
+
+			next_edit = new_edit;
+		}
+
+		return next_edit;
+	}
+
+public:
+	/// Removes all elements of a class given by its root; returns updated map.
+	Self* remove_class(Elm root) {
+		reroot();
+
+		// remove map from self
+		Base base_map = take_as<Base>();
+		reset_as_base();
+
+		// find root node and remove children
+		auto it = base_map.find( root );
+		assertf(it != base_map.end(), "root node must exist in map");
+		Self* next_edit = remove_children( root, it->second, base_map, this);
+
+		// root is alone in class, remove from map
+		base_map.erase( root );
+		Self* ret = new Self{ BASE, std::move(base_map) };
+		// reinitialize previous node as ADD to new map
+		next_edit->init<Add>( ret, root );
+		next_edit->mode = ADD;
+
+		return ret;
+	}
+
+	/// Applies `f` to all members of a class containing `i` (undefined behaviour if not present).
+	/// `f` should take members by const reference or copy
+	template<typename F>
+	void apply_to_class(Elm i, F&& f) const {
 		const Base& self = rerooted();
-
-		// skip empty classes
-		if ( ! self.count( i ) ) return;
 
 		Elm crnt = i;
 		do {
-			*out++ = crnt;
+			f( crnt );
 			auto it = self.find( crnt );
 			assertf( it != self.end(), "current node must exist in base" );
Index: src/Common/PersistentMap.h
===================================================================
--- src/Common/PersistentMap.h	(revision b21c77a0e211d9361763565a2eb5ef4330f2957b)
+++ src/Common/PersistentMap.h	(revision 184557ed94d1cdbdc6fd685e497e5375f8968a96)
@@ -114,4 +114,7 @@
 	}
 
+	/// Non-initializing constructor; should call init() before use
+	PersistentMap( Mode m ) : data(), mode(m) {}
+
 	PersistentMap( Mode m, Base&& b ) : data(), mode(m) {
 		assertf(m == BASE, "invalid mode");
@@ -420,4 +423,42 @@
 		}
 	}
+
+	/// Applies the function `f` to all elements of the map, returning a pointer to the updated map.
+	/// `f` should take two parameters, `const Key&` and `Val&`, returning option<Val> filled with 
+	/// the previous value if mutated, an empty option<Val> otherwise.
+	/// NOTE: when porting to C++17, this should work fine with std::optional
+	template<typename F>
+	Self* apply_to_all(F&& f) {
+		// reset to root and exit early if no change
+		if ( rerooted().empty() ) return this;
+
+		// remove map from self
+		Base base_map = take_as<Base>();
+		reset_as_base();
+
+		// apply all edits
+		Self* next_edit = this;
+		for ( auto& entry : base_map ) {
+			auto res = f( entry.first, entry.second );
+			if ( res ) {
+				// entry has been mutated; reset next_edit node as mutation
+				Self* new_node = new Self{ BASE };
+				next_edit->init<Ins>( new_node, entry.first, *std::move(res) );
+				next_edit->mode = UPD;
+				next_edit = new_node;
+			}
+		}
+
+		// set map into final node and return
+		next_edit->init<Base>( std::move(base_map) );
+		return next_edit;
+	}
+
+	/// Applies the function `f` to all elements of the map.
+	/// `f` should take two parameters, `const Key&` and `const Val&`.
+	template<typename F>
+	void apply_to_all(F&& f) const {
+		for ( const auto& entry : rerooted() ) { f( entry.first, entry.second ); }
+	}
 };
 
Index: src/Common/option.h
===================================================================
--- src/Common/option.h	(revision 184557ed94d1cdbdc6fd685e497e5375f8968a96)
+++ src/Common/option.h	(revision 184557ed94d1cdbdc6fd685e497e5375f8968a96)
@@ -0,0 +1,233 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// option.h --
+//
+// Author           : Aaron B. Moss
+// Created On       : Wed Jul 03 15:41:00 2018
+// Last Modified By : Aaron B. Moss
+// Last Modified On : Wed Jul 03 15:41:00 2018
+// Update Count     : 1
+//
+
+#pragma once
+
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include "debug.h"
+
+using std::move;
+
+/// A class that may or may not hold a value of type T
+/// Similar interface to the C++17 std::optional
+template<typename T>
+class option {
+    typename std::aligned_storage<sizeof(T), alignof(T)>::type storage;
+    bool filled;
+
+    /// gets the underlying value (filled must be true)
+    T& get() { return reinterpret_cast<T&>(storage); }
+    const T& get() const { return reinterpret_cast<const T&>(storage); }
+    
+    /// copy-initializes from a value
+    void init( const T& x ) {
+        new(&storage) T(x);
+        filled = true;
+    }
+
+    /// move-initializes from a value
+    void init( T&& x ) {
+        new(&storage) T( move(x) );
+        filled = true;
+    }
+
+    /// destroys the underlying value (must be filled), sets filled to false
+    void destroy() {
+        get().~T();
+        filled = false;
+    }
+
+public:
+
+    option() : filled(false) {}
+    option( const T& x ) { init( x ); }
+    option( T&& x ) { init( move(x) ); }
+    option( const option<T>& o ) : filled(o.filled) { if ( filled ) init( o.get() ); }
+    option( option<T>&& o ) : filled(o.filled) { if ( filled ) init( move(o.get()) ); }
+    option& operator= ( const T& x ) {
+        if ( filled ) { get() = x; } else { init( x ); }
+        return *this;
+    }
+    option& operator= ( T&& x ) {
+        if ( filled ) { get() = move(x); } else { init( move(x) ); }
+        return *this;
+    }
+    option& operator= ( const option<T>& o ) {
+        if ( filled ) {
+            if ( o.filled ) {
+                get() = o.get();
+            } else {
+                destroy();
+            }
+        } else if ( o.filled ) {
+            init( o.get() );
+        }
+        return *this;
+    }
+    option& operator= ( option<T>&& o ) {
+        if ( filled ) {
+            if ( o.filled ) {
+                get() = move(o.get());
+            } else {
+                destroy();
+            }
+        } else if ( o.filled ) {
+            init( move(o.get()) );
+        }
+        return *this;
+    }
+    ~option() { if ( filled ) get().~T(); }
+
+    void swap( option<T>& o ) {
+        if ( filled ) {
+            if ( o.filled ) {
+                std::swap( get(), o.get() );
+            } else {
+                o.init( move(get()) );
+                destroy();
+            }
+        } else if ( o.filled ) {
+            init( move(o.get()) );
+            o.destroy();
+        }
+    }
+
+    /// Get contained value (unchecked)
+    T* operator->() { return &get(); }
+    const T* operator->() const { return &get(); }
+    T& operator*() & { return get(); }
+    const T& operator*() const& { return get(); }
+    T&& operator*() && { return move(get()); }
+    const T&& operator*() const&& { return move(get()); }
+
+    /// Check if option is filled
+    constexpr explicit operator bool() const { return filled; }
+    constexpr bool has_value() const { return filled; }
+
+    /// Get contained value (checked)
+    T& value() & { assume(filled, "checked get failed"); return get(); }
+    const T& value() const& { assume(filled, "checked get failed"); return get(); }
+    T&& value() && { assume(filled, "checked get failed"); return move(get()); }
+    const T&& value() const&& { assume(filled, "checked get failed"); return move(get()); }
+
+    /// Get contained or default value
+    template<typename U>
+    T value_or( U&& y ) const& { return filled ? get() : static_cast<T>(std::forward<U>(y)); }
+    template<typename U>
+    T value_or( U&& y ) && { return filled ? move(get()) : static_cast<T>(std::forward<U>(y)); }
+
+    /// Unset value if set
+    void reset() { if ( filled ) destroy(); }
+
+    /// Construct value in-place (destroys existing value if needed)
+    template<class... Args>
+    void emplace( Args&&... args ) {
+        if ( filled ) { destroy(); }
+        new(&storage) T{ std::forward<Args>(args)... };
+        filled = true;
+    }
+};
+
+template<typename T>
+bool operator== ( const option<T>& a, const option<T>& b ) {
+    return a ? (b && *a == *b) : !b;
+}
+
+template<typename T>
+bool operator!= ( const option<T>& a, const option<T>& b ) { return !(a == b); }
+
+template<typename T>
+bool operator< ( const option<T>& a, const option<T>& b ) {
+    return b && (!a || *a < *b); 
+}
+
+template<typename T>
+bool operator<= ( const option<T>& a, const option<T>& b ) {
+    return !a || (b && *a <= *b);
+}
+
+template<typename T>
+bool operator> ( const option<T>& a, const option<T>& b ) { return b < a; }
+
+template<typename T>
+bool operator>= ( const option<T>& a, const option<T>& b ) { return b <= a; }
+
+template<typename T>
+bool operator== ( const option<T>& a, const T& x ) {
+    return a && *a == x;
+}
+
+template<typename T>
+bool operator!= ( const option<T>& a, const T& x ) { return !(a == x); }
+
+template<typename T>
+bool operator< ( const option<T>& a, const T& x ) {
+    return !a || *a < x;
+}
+
+template<typename T>
+bool operator<= ( const option<T>& a, const T& x ) {
+    return !a || *a <= x;
+}
+
+template<typename T>
+bool operator> ( const option<T>& a, const T& x ) {
+    return a && *a > x;
+}
+
+template<typename T>
+bool operator>= ( const option<T>& a, const T& x ) {
+    return a && *a >= x;
+}
+
+template<typename T>
+bool operator== ( const T& x, const option<T>& b ) { return b == x; }
+
+template<typename T>
+bool operator!= ( const T& x, const option<T>& b ) { return !(b == x); }
+
+template<typename T>
+bool operator< ( const T& x, const option<T>& b ) { return b > x; }
+
+template<typename T>
+bool operator<= ( const T& x, const option<T>& b ) { return b >= x; }
+
+template<typename T>
+bool operator> ( const T& x, const option<T>& b ) { return b < x; }
+
+template<typename T>
+bool operator>= ( const T& x, const option<T>& b ) { return b <= x; }
+
+template<typename T, typename... Args>
+option<T> make_option( Args&&... args ) {
+    option<T> o;
+    o.emplace( std::forward<Args>(args)... );
+    return o;
+}
+
+namespace std {
+    template<typename T>
+    void swap( option<T>& a, option<T>& b ) { a.swap(b); }
+
+    template<typename T>
+    struct hash< option<T> > {
+        size_t operator() ( const option<T>& a ) {
+            return a ? hash<T>{}( *a ) : size_t(1);
+        }
+    };
+}
Index: src/ResolvExpr/TypeEnvironment.cc
===================================================================
--- src/ResolvExpr/TypeEnvironment.cc	(revision b21c77a0e211d9361763565a2eb5ef4330f2957b)
+++ src/ResolvExpr/TypeEnvironment.cc	(revision 184557ed94d1cdbdc6fd685e497e5375f8968a96)
@@ -21,4 +21,5 @@
 #include "Common/InternedString.h"     // for interned_string
 #include "Common/PassVisitor.h"        // for PassVisitor<GcTracer>
+#include "Common/option.h"             // for option<T>
 #include "Common/utility.h"            // for maybeClone
 #include "SymTab/Indexer.h"            // for Indexer
@@ -49,74 +50,4 @@
 	}
 
-#if 0
-	void EqvClass::initialize( const EqvClass &src, EqvClass &dest ) {
-		initialize( src, dest, src.type );
-	}
-
-	void EqvClass::initialize( const EqvClass &src, EqvClass &dest, const Type *ty ) {
-		dest.vars = src.vars;
-		dest.type = maybeClone( ty );
-		dest.allowWidening = src.allowWidening;
-		dest.data = src.data;
-	}
-
-	EqvClass::EqvClass() : type( nullptr ), allowWidening( true ) {
-	}
-
-	EqvClass::EqvClass( const EqvClass &other ) {
-		initialize( other, *this );
-	}
-
-	EqvClass::EqvClass( const EqvClass &other, const Type *ty ) {
-		initialize( other, *this, ty );
-	}
-
-	EqvClass::EqvClass( EqvClass &&other ) 
-	: vars{std::move(other.vars)}, type{other.type}, 
-	  allowWidening{std::move(other.allowWidening)}, data{std::move(other.data)} {
-		  other.type = nullptr;
-	}
-
-	EqvClass &EqvClass::operator=( const EqvClass &other ) {
-		if ( this == &other ) return *this;
-		initialize( other, *this );
-		return *this;
-	}
-
-	EqvClass &EqvClass::operator=( EqvClass &&other ) {
-		if ( this == &other ) return *this;
-		
-		vars = std::move(other.vars);
-		type = other.type;
-		allowWidening = std::move(other.allowWidening);
-		data = std::move(other.data);
-
-		return *this;
-	}
-
-	void EqvClass::set_type( Type* ty ) { type = ty; }
-
-	void EqvClass::print( std::ostream &os, Indenter indent ) const {
-		os << "( ";
-		std::copy( vars.begin(), vars.end(), std::ostream_iterator< std::string >( os, " " ) );
-		os << ")";
-		if ( type ) {
-			os << " -> ";
-			type->print( os, indent+1 );
-		} // if
-		if ( ! allowWidening ) {
-			os << " (no widening)";
-		} // if
-		os << std::endl;
-	}
-
-	const EqvClass* TypeEnvironment::lookup( const std::string &var ) const {
-		for ( std::list< EqvClass >::const_iterator i = env.begin(); i != env.end(); ++i ) {
-			if ( i->vars.find( var ) != i->vars.end() ) return &*i;
-		} // for
-		return nullptr;
-	}
-#endif
-
 	std::pair<interned_string, interned_string> TypeEnvironment::mergeClasses( 
 			interned_string root1, interned_string root2 ) {
@@ -126,5 +57,5 @@
 		// determine new root
 		assertf(classes->get_mode() == Classes::REMFROM, "classes updated to REMFROM by merge");
-		auto ret = std::make_pair( classes->get_root(), classes->get_child );
+		auto ret = std::make_pair( classes->get_root(), classes->get_child() );
 		
 		// finalize classes
@@ -302,94 +233,112 @@
 	}
 
-#if !1
-	/// Removes any class from env that intersects eqvClass
-	void filterOverlappingClasses( std::list<EqvClass> &env, const EqvClass &eqvClass ) {
-		for ( auto i = env.begin(); i != env.end(); ) {
-			auto next = i;
-			++next;
-			std::set<std::string> intersection;
-			std::set_intersection( i->vars.begin(), i->vars.end(), eqvClass.vars.begin(), eqvClass.vars.end(), 
-				std::inserter( intersection, intersection.begin() ) );
-			if ( ! intersection.empty() ) { env.erase( i ); }
-			i = next;
-		}
-	}
-
-	void TypeEnvironment::add( EqvClass &&eqvClass ) {
-		filterOverlappingClasses( env, eqvClass );
-		env.push_back( std::move(eqvClass) );
-	}
-
 	void TypeEnvironment::add( const Type::ForallList &tyDecls ) {
 		for ( Type::ForallList::const_iterator i = tyDecls.begin(); i != tyDecls.end(); ++i ) {
-			EqvClass newClass;
-			newClass.vars.insert( (*i)->get_name() );
-			newClass.data = TypeDecl::Data{ (*i) };
-			env.push_back( std::move(newClass) );
+			interned_string name = (*i)->get_name();
+			classes = classes->add( name );
+			bindings = bindings->set( name, *i );
 		} // for
 	}
 
 	void TypeEnvironment::add( const TypeSubstitution & sub ) {
-		EqvClass newClass;
+		interned_string not_found{nullptr};
+
 		for ( auto p : sub ) {
-			newClass.vars.insert( p.first );
-			newClass.type = p.second->clone();
-			newClass.allowWidening = false;
+			interned_string var = p.first;
+			
+			// filter overlapping classes out of existing environment
+			// (this is a very shady assumption, but has worked for a long time...)
+			interned_string root = classes->find_or_default( v, not_found );
+			if ( root != not_found ) {
+				classes = classes->remove_class( root );
+				bindings = bindings->erase( root );
+			}
+
 			// Minimal assumptions. Not technically correct, but might be good enough, and
 			// is the best we can do at the moment since information is lost in the
 			// transition to TypeSubstitution
-			newClass.data = TypeDecl::Data{ TypeDecl::Dtype, false };
-			add( std::move(newClass) );
+			TypeDecl::Data data{ TypeDecl::Dtype, false };
+
+			// add variable to class and bindings
+			classes = classes->add( var );
+			bindings = bindings->set( var, BoundType{ p.second->clone, false, data } );
 		}
 	}
 
 	void TypeEnvironment::makeSubstitution( TypeSubstitution &sub ) const {
-		for ( std::list< EqvClass >::const_iterator theClass = env.begin(); theClass != env.end(); ++theClass ) {
-			for ( std::set< std::string >::const_iterator theVar = theClass->vars.begin(); theVar != theClass->vars.end(); ++theVar ) {
-				if ( theClass->type ) {
-					sub.add( *theVar, theClass->type );
-				} else if ( theVar != theClass->vars.begin() ) {
-					TypeInstType *newTypeInst = new TypeInstType( Type::Qualifiers(), *theClass->vars.begin(), theClass->data.kind == TypeDecl::Ftype );
-					sub.add( *theVar, newTypeInst );
-				} // if
-			} // for
-		} // for
+		bindings->apply_to_all([classes, &sub]( interned_string root, const BoundType& bound ){
+			classes->apply_to_class(root, [&]( interned_string var ) {
+				if ( bound.type ) {
+					sub.add( var, bound.type );
+				} else if ( var != root ) {
+					sub.add( var, new TypeInstType{ 
+						Type::Qualifiers{}, root, bound.data.kind == TypeDecl::Ftype } );
+				}
+			});
+		});
+
 		sub.normalize();
 	}
 
 	void TypeEnvironment::print( std::ostream &os, Indenter indent ) const {
-		for ( const EqvClass & theClass : env ) {
-			theClass.print( os, indent );
-		} // for
-	}
-
-	std::list< EqvClass >::iterator TypeEnvironment::internal_lookup( const std::string &var ) {
-		for ( std::list< EqvClass >::iterator i = env.begin(); i != env.end(); ++i ) {
-			if ( i->vars.count( var ) ) return i;
-		} // for
-		return env.end();
-	}
-
-	void TypeEnvironment::simpleCombine( const TypeEnvironment &second ) {
-		env.insert( env.end(), second.env.begin(), second.env.end() );
+		bindings->apply_to_all([classes,&]( interned_string root, const BoundType& bound ) {
+			os << "( ";
+			classes->apply_to_class( root, [&os]( interned_string var ) { os << var << " "; } );
+			os << ")";
+			if ( bound.type ) {
+				os << " -> ";
+				type->print( os, indent+1 );
+			}
+			if ( ! bound.allowWidening ) {
+				os << " (no widening)";
+			}
+			os << std::endl;
+		});
+	}
+
+	void TypeEnvironment::simpleCombine( const TypeEnvironment &o ) {
+		o.bindings->apply_to_all( [&]( interned_string root, const BoundType& bound ) {
+			// add members of new class
+			interned_string new_root{nullptr};
+			o.classes->apply_to_class( root, [this,&new_root]( interned_string var ) {
+				classes = classes->add( var );
+				new_root = new_root ? mergeClasses( new_root, var ).first : var;
+			});
+			// set binding for new class
+			bindings = bindings->set( new_root, bound );
+		});
 	}
 
 	void TypeEnvironment::extractOpenVars( OpenVarSet &openVars ) const {
-		for ( std::list< EqvClass >::const_iterator eqvClass = env.begin(); eqvClass != env.end(); ++eqvClass ) {
-			for ( std::set< std::string >::const_iterator var = eqvClass->vars.begin(); var != eqvClass->vars.end(); ++var ) {
-				openVars[ *var ] = eqvClass->data;
-			} // for
-		} // for
+		bindings->apply_to_all( [classes,&openVars]( interned_string root, const BoundType& bound ) {
+			classes->apply_to_class( root, [&openVars,&bound]( interned_string var ) {
+				openVars[ var ] = bound.data;
+			} );
+		} );
 	}
 
 	void TypeEnvironment::addActual( const TypeEnvironment& actualEnv, OpenVarSet& openVars ) {
-		for ( const EqvClass& c : actualEnv ) {
-			EqvClass c2 = c;
-			c2.allowWidening = false;
-			for ( const std::string& var : c2.vars ) {
-				openVars[ var ] = c2.data;
-			}
-			env.push_back( std::move(c2) );
-		}
+		actualEnv.bindings->apply_to_all( [&]( interned_string root, const BoundType& bound ) {
+			// add members of new class, setting openVars concurrently
+			interned_string new_root{nullptr};
+			actualEnv.classes->apply_to_class( root, [&]( interned_string var ) {
+				classes = classes->add( var );
+				new_root = new_root ? mergeClasses( new_root, var ).first : var;
+				openVars[ var ] = bound.data;
+			} );
+			// add new type binding without widening
+			bindings = bindings->set( new_root, 
+				BoundType{ maybeClone(bound.type), false, bound.data } );
+		} );
+	}
+
+	void TypeEnvironment::forbidWidening() {
+		bindings = bindings->apply_to_all([]( const interned_string& k, BoundType& c ) {
+			if ( c.allowWidening ) {
+				option<BoundType> ret = c;
+				c.allowWidening = false;
+				return ret;
+			} else return option<BoundType>{};
+		});
 	}
 
@@ -398,5 +347,4 @@
 		return out;
 	}
-#endif
 
 	PassVisitor<GcTracer> & operator<<( PassVisitor<GcTracer> & gc, const TypeEnvironment & env ) {
Index: src/ResolvExpr/TypeEnvironment.h
===================================================================
--- src/ResolvExpr/TypeEnvironment.h	(revision b21c77a0e211d9361763565a2eb5ef4330f2957b)
+++ src/ResolvExpr/TypeEnvironment.h	(revision 184557ed94d1cdbdc6fd685e497e5375f8968a96)
@@ -86,29 +86,22 @@
 		bool allowWidening;
 		TypeDecl::Data data;
-	};
-
-#if 0
-	/// An equivalence class, with its binding information
-	struct EqvClass {
-		std::set< std::string > vars;
-		Type *type;
-		bool allowWidening;
-		TypeDecl::Data data;
-
-		void initialize( const EqvClass &src, EqvClass &dest );
-		void initialize( const EqvClass &src, EqvClass &dest, const Type *ty );
-		EqvClass();
-		EqvClass( const EqvClass &other );
-		EqvClass( const EqvClass &other, const Type *ty );
-		EqvClass( EqvClass &&other );
-		EqvClass &operator=( const EqvClass &other );
-		EqvClass &operator=( EqvClass &&other );
-		void print( std::ostream &os, Indenter indent = {} ) const;
-
-		/// Takes ownership of `ty`, freeing old `type`
-		void set_type(Type* ty);
-	};
-#endif
-	
+
+		BoundType() = default;
+		BoundType( TypeDecl* td ) : type{nullptr}, allowWidening{true}, data{td} {}
+		BoundType( Type* ty, bool aw, const TypeDecl::Data& td )
+			: type{ty}, allowWidening{aw}, data{td} {}
+		BoundType( const BoundType& o )
+			: type{maybeClone(o.type)}, allowWidening{o.allowWidening}, data{o.data} {}
+		BoundType( BoundType&& o ) = default;
+		BoundType& operator= (const BoundType& o) {
+			if ( this == &o ) return *this;
+			type = maybeClone( o.type );
+			allowWidening = o.allowWidening;
+			data = o.data;
+			return *this;
+		}
+		BoundType& operator= (BoundType&& o) = default;
+	};
+
 	class TypeEnvironment;
 
@@ -137,10 +130,4 @@
 		/// Gets the bound type information of the referenced equivalence class, default if none
 		inline BoundType get_bound() const;
-
-#if 0
-		/// Gets the referenced equivalence class
-		inline EqvClass get_class() const;
-		EqvClass operator* () const { return get_class(); }
-#endif
 
 		// Check that there is a referenced typeclass
@@ -171,5 +158,5 @@
 			interned_string root1, interned_string root2 );
 
-	  public:
+	public:
 		class iterator : public std::iterator<
 				std::forward_iterator_tag, 
@@ -213,7 +200,5 @@
 			AssertionSet& need, AssertionSet& have, const OpenVarSet& openVars, 
 			WidenMode widenMode, const SymTab::Indexer& indexer );
-#if !1
-	private:
-		void add( EqvClass &&eqvClass );
+
 	public:
 		void add( const Type::ForallList &tyDecls );
@@ -222,14 +207,14 @@
 		template< typename SynTreeClass > int applyFree( SynTreeClass *&type ) const;
 		void makeSubstitution( TypeSubstitution &result ) const;
-#endif
 		bool isEmpty() const { return classes->empty(); }
-#if !1
 		void print( std::ostream &os, Indenter indent = {} ) const;
+	
+		/// Combines two environments without checking invariants.
+		/// Caller should ensure environments do not share type variables.
 		void simpleCombine( const TypeEnvironment &second );
+	
 		void extractOpenVars( OpenVarSet &openVars ) const;
-#endif
 		TypeEnvironment *clone() const { return new TypeEnvironment( *this ); }
 
-#if !1
 		/// Iteratively adds the environment of a new actual (with allowWidening = false),
 		/// and extracts open variables.
@@ -238,5 +223,4 @@
 		/// Disallows widening for all bindings in the environment
 		void forbidWidening();
-#endif
 
 		iterator begin() { return { this, bindings->begin() }; }
@@ -244,10 +228,13 @@
 	};
 
-	void ClassRef::update_root() { return root = env->classes->find( root ); }
+	interned_string ClassRef::update_root() { return root = env->classes->find( root ); }
 
 	template<typename T>
 	T ClassRef::get_vars() const {
 		T vars;
-		env->classes->find_class( root, std::inserter(vars, vars.end()) );
+		if ( ! env->classes->count( root ) ) return vars;
+		env->classes->apply_to_class( root, [&vars]( interned_string var ) {
+			vars.insert( vars.end(), var );
+		} );
 		return vars;
 	}
@@ -256,8 +243,4 @@
 		return env->bindings->get_or_default( root, BoundType{} );
 	}
-
-#if 0
-	EqvClass ClassRef::get_class() const { return { get_vars(), get_bound() }; }
-#endif
 
 	template< typename SynTreeClass >
@@ -275,7 +258,5 @@
 	}
 
-#if !1
 	std::ostream & operator<<( std::ostream & out, const TypeEnvironment & env );
-#endif
 
 	PassVisitor<GcTracer> & operator<<( PassVisitor<GcTracer> & gc, const TypeEnvironment & env );
