Index: src/Common/utility.h
===================================================================
--- src/Common/utility.h	(revision d318a182b0b2491a1aa64771c523470334852f01)
+++ src/Common/utility.h	(revision eff03a94a7c9efb0da9adc3ba0df25839eeaed04)
@@ -294,4 +294,6 @@
 	aT m_after;
 
+	FuncGuard( aT after ) : m_after( after ) {}
+
 	template< typename bT >
 	FuncGuard( bT before, aT after ) : m_after( after ) {
@@ -303,4 +305,7 @@
 	}
 };
+
+template< typename aT >
+FuncGuard<aT> makeFuncGuard( aT && after ) { return FuncGuard<aT>( std::forward<aT>(after) ); }
 
 template< typename bT, typename aT >
Index: src/ResolvExpr/TypeEnvironment.cc
===================================================================
--- src/ResolvExpr/TypeEnvironment.cc	(revision d318a182b0b2491a1aa64771c523470334852f01)
+++ src/ResolvExpr/TypeEnvironment.cc	(revision eff03a94a7c9efb0da9adc3ba0df25839eeaed04)
@@ -33,5 +33,5 @@
 
 namespace ResolvExpr {
-	#if 1
+	#if 0
 	#define PRE_POST_VALIDATE auto dbg = ValidateGuard{this, __func__};
 	#define PRE_POST_VALIDATE_NOM auto dbg = ValidateGuard{this};
Index: src/SynTree/TypeSubstitution.cc
===================================================================
--- src/SynTree/TypeSubstitution.cc	(revision d318a182b0b2491a1aa64771c523470334852f01)
+++ src/SynTree/TypeSubstitution.cc	(revision eff03a94a7c9efb0da9adc3ba0df25839eeaed04)
@@ -14,7 +14,8 @@
 //
 
-#include <ostream>  // for ostream, basic_ostream, operator<<, endl
-
-#include "Type.h"   // for TypeInstType, Type, StructInstType, UnionInstType
+#include <algorithm>  // for find
+#include <ostream>    // for ostream, basic_ostream, operator<<, endl
+
+#include "Type.h"     // for TypeInstType, Type, StructInstType, UnionInstType
 #include "TypeSubstitution.h"
 
@@ -59,17 +60,22 @@
 
 	// break on not in substitution set
-	if ( i == typeEnv.end() ) return 0;
+	if ( i == typeEnv.end() ) return nullptr;
 
 	// attempt to transitively follow TypeInstType links.
+	std::vector<std::string> equivNames{ formalType };
 	while ( TypeInstType *actualType = dynamic_cast< TypeInstType* >( i->second ) ) {
 		const std::string& typeName = actualType->get_name();
 
 		// break cycles in the transitive follow
-		if ( formalType == typeName ) break;
-
-		// Look for the type this maps to, returning previous mapping if none-such
+		if ( std::find( equivNames.begin(), equivNames.end(), typeName ) != equivNames.end() ) {
+			break;
+		}
+		equivNames.emplace_back( typeName );
+
+		// Look for the type this maps to, returning previous mapping if none such
 		i = typeEnv.find( typeName );
 		if ( i == typeEnv.end() ) return actualType;
 	}
+
 
 	// return type from substitution set
@@ -132,12 +138,16 @@
 		return inst;
 	} else {
-		// cut off infinite loop for the case where a type is bound to itself.
-		// Note: this does not prevent cycles in the general case, so it may be necessary to do something more sophisticated here.
-		// TODO: investigate preventing type variables from being bound to themselves in the first place.
+		// clear equivalent variables on exit
+		auto guard = makeFuncGuard( [&]() { equivVars.clear(); } );
+
 		if ( TypeInstType * replacement = dynamic_cast< TypeInstType * >( i->second ) ) {
-			if ( inst->name == replacement->name ) {
-				return inst;
-			}
-		}
+			// cut off infinite loop if type is bound to itself (possibly transitively)
+			equivVars.push_back( inst );
+			auto equiv = std::find_if( equivVars.begin(), equivVars.end(), [&]( TypeInstType* e ) {
+				return e->name == replacement->name;
+			} );
+			if ( equiv != equivVars.end() ) return *equiv;
+		}
+
 		// std::cerr << "found " << inst->name << ", replacing with " << i->second << std::endl;
 		subCount++;
Index: src/SynTree/TypeSubstitution.h
===================================================================
--- src/SynTree/TypeSubstitution.h	(revision d318a182b0b2491a1aa64771c523470334852f01)
+++ src/SynTree/TypeSubstitution.h	(revision eff03a94a7c9efb0da9adc3ba0df25839eeaed04)
@@ -126,5 +126,6 @@
 // definitition must happen after PassVisitor is included so that WithGuards can be used
 struct TypeSubstitution::Substituter : public WithGuards, public WithVisitorRef<Substituter> {
-		Substituter( TypeSubstitution & sub, bool freeOnly ) : sub( sub ), freeOnly( freeOnly ) {}
+		Substituter( TypeSubstitution & sub, bool freeOnly )
+			: sub( sub ), freeOnly( freeOnly ), boundVars(), equivVars() {}
 
 		Type * postmutate( TypeInstType * aggregateUseType );
@@ -144,4 +145,5 @@
 		typedef std::set< std::string > BoundVarsType;
 		BoundVarsType boundVars;
+		std::vector<TypeInstType*> equivVars;  ///< equivalent names of type variables
 };
 
