Index: src/ResolvExpr/CommonType.cc
===================================================================
--- src/ResolvExpr/CommonType.cc	(revision f474e917671c80fd81b2ffce8b4dac26709b333e)
+++ src/ResolvExpr/CommonType.cc	(revision ee574a219a52c0b47523b1a790d363cc61f813d3)
@@ -18,4 +18,6 @@
 #include <utility>                       // for pair
 
+#include "AST/Decl.hpp"
+#include "AST/Type.hpp"
 #include "Common/PassVisitor.h"
 #include "ResolvExpr/TypeEnvironment.h"  // for OpenVarSet, AssertionSet
@@ -35,6 +37,6 @@
 
 namespace ResolvExpr {
-	struct CommonType : public WithShortCircuiting {
-		CommonType( Type *type2, bool widenFirst, bool widenSecond, const SymTab::Indexer &indexer, TypeEnvironment &env, const OpenVarSet &openVars );
+	struct CommonType_old : public WithShortCircuiting {
+		CommonType_old( Type *type2, bool widenFirst, bool widenSecond, const SymTab::Indexer &indexer, TypeEnvironment &env, const OpenVarSet &openVars );
 		Type *get_result() const { return result; }
 
@@ -94,5 +96,5 @@
 
 	Type *commonType( Type *type1, Type *type2, bool widenFirst, bool widenSecond, const SymTab::Indexer &indexer, TypeEnvironment &env, const OpenVarSet &openVars ) {
-		PassVisitor<CommonType> visitor( type2, widenFirst, widenSecond, indexer, env, openVars );
+		PassVisitor<CommonType_old> visitor( type2, widenFirst, widenSecond, indexer, env, openVars );
 
 		int depth1 = type1->referenceDepth();
@@ -176,14 +178,4 @@
 	}
 
-	const ast::Type * commonType(
-			const ast::Type * type1, const ast::Type * type2, WidenMode widen, 
-			const ast::SymbolTable & symtab, ast::TypeEnvironment & env, 
-			const ast::OpenVarSet & open ) {
-		#warning unimplemented
-		(void)type1; (void)type2; (void)widen; (void)symtab; (void)env; (void)open;
-		assert(false);
-		return nullptr;
-	}
-
 	// GENERATED START, DO NOT EDIT
 	// GENERATED BY BasicTypes-gen.cc
@@ -493,11 +485,11 @@
 	);
 
-	CommonType::CommonType( Type *type2, bool widenFirst, bool widenSecond, const SymTab::Indexer &indexer, TypeEnvironment &env, const OpenVarSet &openVars )
+	CommonType_old::CommonType_old( Type *type2, bool widenFirst, bool widenSecond, const SymTab::Indexer &indexer, TypeEnvironment &env, const OpenVarSet &openVars )
 		: result( 0 ), type2( type2 ), widenFirst( widenFirst ), widenSecond( widenSecond ), indexer( indexer ), env( env ), openVars( openVars ) {
 	}
 
-	void CommonType::postvisit( VoidType * ) {}
-
-	void CommonType::postvisit( BasicType *basicType ) {
+	void CommonType_old::postvisit( VoidType * ) {}
+
+	void CommonType_old::postvisit( BasicType *basicType ) {
 		if ( BasicType *otherBasic = dynamic_cast< BasicType* >( type2 ) ) {
 			BasicType::Kind newType = commonTypes[ basicType->get_kind() ][ otherBasic->get_kind() ];
@@ -515,5 +507,5 @@
 
 	template< typename Pointer >
-	void CommonType::getCommonWithVoidPointer( Pointer* voidPointer, Pointer* otherPointer ) {
+	void CommonType_old::getCommonWithVoidPointer( Pointer* voidPointer, Pointer* otherPointer ) {
 		if ( TypeInstType* var = dynamic_cast< TypeInstType* >( otherPointer->get_base() ) ) {
 			OpenVarSet::const_iterator entry = openVars.find( var->get_name() );
@@ -528,5 +520,5 @@
 	}
 
-	void CommonType::postvisit( PointerType *pointerType ) {
+	void CommonType_old::postvisit( PointerType *pointerType ) {
 		if ( PointerType *otherPointer = dynamic_cast< PointerType* >( type2 ) ) {
 			// std::cerr << "commonType: two pointers: " << pointerType << " / " << otherPointer << std::endl;
@@ -563,7 +555,7 @@
 	}
 
-	void CommonType::postvisit( ArrayType * ) {}
-
-	void CommonType::postvisit( ReferenceType *refType ) {
+	void CommonType_old::postvisit( ArrayType * ) {}
+
+	void CommonType_old::postvisit( ReferenceType *refType ) {
 		if ( ReferenceType *otherRef = dynamic_cast< ReferenceType* >( type2 ) ) {
 			// std::cerr << "commonType: both references: " << refType << " / " << otherRef << std::endl;
@@ -600,9 +592,9 @@
 	}
 
-	void CommonType::postvisit( FunctionType * ) {}
-	void CommonType::postvisit( StructInstType * ) {}
-	void CommonType::postvisit( UnionInstType * ) {}
-
-	void CommonType::postvisit( EnumInstType *enumInstType ) {
+	void CommonType_old::postvisit( FunctionType * ) {}
+	void CommonType_old::postvisit( StructInstType * ) {}
+	void CommonType_old::postvisit( UnionInstType * ) {}
+
+	void CommonType_old::postvisit( EnumInstType *enumInstType ) {
 		if ( dynamic_cast< BasicType * >( type2 ) || dynamic_cast< ZeroType* >( type2 ) || dynamic_cast< OneType* >( type2 ) ) {
 			// reuse BasicType, EnumInstType code by swapping type2 with enumInstType
@@ -611,8 +603,8 @@
 	}
 
-	void CommonType::postvisit( TraitInstType * ) {
-	}
-
-	void CommonType::postvisit( TypeInstType *inst ) {
+	void CommonType_old::postvisit( TraitInstType * ) {
+	}
+
+	void CommonType_old::postvisit( TypeInstType *inst ) {
 		if ( widenFirst ) {
 			NamedTypeDecl *nt = indexer.lookupType( inst->get_name() );
@@ -636,8 +628,8 @@
 	}
 
-	void CommonType::postvisit( TupleType * ) {}
-	void CommonType::postvisit( VarArgsType * ) {}
-
-	void CommonType::postvisit( ZeroType *zeroType ) {
+	void CommonType_old::postvisit( TupleType * ) {}
+	void CommonType_old::postvisit( VarArgsType * ) {}
+
+	void CommonType_old::postvisit( ZeroType *zeroType ) {
 		if ( widenFirst ) {
 			if ( dynamic_cast< BasicType* >( type2 ) || dynamic_cast< PointerType* >( type2 ) || dynamic_cast< EnumInstType* >( type2 ) ) {
@@ -653,5 +645,5 @@
 	}
 
-	void CommonType::postvisit( OneType *oneType ) {
+	void CommonType_old::postvisit( OneType *oneType ) {
 		if ( widenFirst ) {
 			if ( dynamic_cast< BasicType* >( type2 ) || dynamic_cast< EnumInstType* >( type2 ) ) {
@@ -666,4 +658,343 @@
 		}
 	}
+
+	class CommonType_new final : public ast::WithShortCircuiting {
+		const ast::Type * type2;
+		WidenMode widen;
+		const ast::SymbolTable & symtab;
+		ast::TypeEnvironment & tenv;
+		const ast::OpenVarSet & open;
+	public:
+		ast::ptr< ast::Type > result;
+
+		CommonType_new( 
+			const ast::Type * t2, WidenMode w, const ast::SymbolTable & st, 
+			ast::TypeEnvironment & env, const ast::OpenVarSet & o )
+		: type2( t2 ), widen( w ), symtab( st ), tenv( env ), open( o ), result() {}
+
+		void previsit( const ast::Node * ) { visit_children = false; }
+
+		void postvisit( const ast::VoidType * ) {}
+
+		void postvisit( const ast::BasicType * basic ) {
+			if ( auto basic2 = dynamic_cast< const ast::BasicType * >( type2 ) ) {
+				#warning remove casts when `commonTypes` moved to new AST
+				ast::BasicType::Kind kind = (ast::BasicType::Kind)(int)commonTypes[ (BasicType::Kind)(int)basic->kind ][ (BasicType::Kind)(int)basic2->kind ];
+				if ( 
+					( ( kind == basic->kind && basic->qualifiers >= basic2->qualifiers ) 
+						|| widen.first ) 
+					&& ( ( kind == basic2->kind && basic->qualifiers <= basic2->qualifiers ) 
+						|| widen.second ) 
+				) {
+					result = new ast::BasicType{ kind, basic->qualifiers | basic2->qualifiers };
+				}
+			} else if ( 
+				dynamic_cast< const ast::EnumInstType * >( type2 ) 
+				|| dynamic_cast< const ast::ZeroType * >( type2 )
+				|| dynamic_cast< const ast::OneType * >( type2 )
+			) {
+				#warning remove casts when `commonTypes` moved to new AST
+				ast::BasicType::Kind kind = (ast::BasicType::Kind)(int)commonTypes[ (BasicType::Kind)(int)basic->kind ][ (BasicType::Kind)(int)ast::BasicType::SignedInt ];
+				if ( 
+					( ( kind == basic->kind && basic->qualifiers >= type2->qualifiers ) 
+						|| widen.first ) 
+					&& ( ( kind != basic->kind && basic->qualifiers <= type2->qualifiers ) 
+						|| widen.second ) 
+				) {
+					result = new ast::BasicType{ kind, basic->qualifiers | type2->qualifiers };
+				}
+			}
+		}
+
+	private:
+		template< typename Pointer >
+		void getCommonWithVoidPointer( const Pointer * voidPtr, const Pointer * oPtr ) {
+			const ast::Type * base = oPtr->base;
+			if ( auto var = dynamic_cast< const ast::TypeInstType * >( base ) ) {
+				auto entry = open.find( var->name );
+				if ( entry != open.end() ) {
+					ast::AssertionSet need, have;
+					if ( ! tenv.bindVar( 
+						var, voidPtr->base, entry->second, need, have, open, widen, symtab ) 
+					) return;
+				}
+			}
+			result = voidPtr;
+			add_qualifiers( result, oPtr->qualifiers );
+		}
+
+	public:
+		void postvisit( const ast::PointerType * pointer ) {
+			if ( auto pointer2 = dynamic_cast< const ast::PointerType * >( type2 ) ) {
+				if ( 
+					widen.first 
+					&& pointer2->base.as< ast::VoidType >() 
+					&& ! ast::isFtype( pointer->base ) 
+				) {
+					getCommonWithVoidPointer( pointer2, pointer );
+				} else if ( 
+					widen.second 
+					&& pointer->base.as< ast::VoidType >() 
+					&& ! ast::isFtype( pointer2->base ) 
+				) {
+					getCommonWithVoidPointer( pointer, pointer2 );
+				} else if (
+					( pointer->base->qualifiers >= pointer2->base->qualifiers || widen.first )
+					&& ( pointer->base->qualifiers <= pointer2->base->qualifiers || widen.second )
+				) {
+					ast::CV::Qualifiers q1 = pointer->base->qualifiers;
+					ast::CV::Qualifiers q2 = pointer2->base->qualifiers;
+
+					// force t{1,2} to be cloned if their qualifiers must be stripped, so that 
+					// pointer{,2}->base are unchanged
+					ast::ptr< ast::Type > t1{ pointer->base }, t2{ pointer2->base };
+					reset_qualifiers( t1 );
+					reset_qualifiers( t2 );
+					
+					ast::AssertionSet have, need;
+					ast::OpenVarSet newOpen{ open };
+					if ( unifyExact( t1, t2, tenv, have, need, newOpen, noWiden(), symtab ) ) {
+						result = pointer;
+						if ( q1.val != q2.val ) {
+							// reset result->base->qualifiers to be union of two base qualifiers
+							strict_dynamic_cast< ast::PointerType * >( 
+								result.get_and_mutate() 
+							)->base.get_and_mutate()->qualifiers = q1 | q2;
+						}
+					}
+				}
+			} else if ( widen.second && dynamic_cast< const ast::ZeroType * >( type2 ) ) {
+				result = pointer;
+				add_qualifiers( result, type2->qualifiers );
+			}
+		}
+
+		void postvisit( const ast::ArrayType * ) {}
+
+		void postvisit( const ast::ReferenceType * ref ) {
+			if ( auto ref2 = dynamic_cast< const ast::ReferenceType * >( type2 ) ) {
+				if (
+					widen.first && ref2->base.as< ast::VoidType >() && ! ast::isFtype( ref->base ) 
+				) {
+					getCommonWithVoidPointer( ref2, ref );
+				} else if ( 
+					widen.second && ref->base.as< ast::VoidType>() && ! ast::isFtype( ref2->base ) 
+				) {
+					getCommonWithVoidPointer( ref, ref2 );
+				} else if (
+					( ref->base->qualifiers >= ref2->base->qualifiers || widen.first )
+					&& ( ref->base->qualifiers <= ref2->base->qualifiers || widen.second )
+				) {
+					ast::CV::Qualifiers q1 = ref->base->qualifiers, q2 = ref2->base->qualifiers;
+
+					// force t{1,2} to be cloned if their qualifiers must be stripped, so that 
+					// ref{,2}->base are unchanged
+					ast::ptr< ast::Type > t1{ ref->base }, t2{ ref2->base };
+					reset_qualifiers( t1 );
+					reset_qualifiers( t2 );
+
+					ast::AssertionSet have, need;
+					ast::OpenVarSet newOpen{ open };
+					if ( unifyExact( t1, t2, tenv, have, need, newOpen, noWiden(), symtab ) ) {
+						result = ref;
+						if ( q1.val != q2.val ) {
+							// reset result->base->qualifiers to be union of two base qualifiers
+							strict_dynamic_cast< ast::ReferenceType * >( 
+								result.get_and_mutate() 
+							)->base.get_and_mutate()->qualifiers = q1 | q2;
+						}
+					}
+				}
+			} else if ( widen.second && dynamic_cast< const ast::ZeroType * >( type2 ) ) {
+				result = ref;
+				add_qualifiers( result, type2->qualifiers );
+			}
+		}
+
+		void postvisit( const ast::FunctionType * ) {}
+
+		void postvisit( const ast::StructInstType * ) {}
+
+		void postvisit( const ast::UnionInstType * ) {}
+
+		void postvisit( const ast::EnumInstType * enumInst ) {
+			if ( 
+				dynamic_cast< const ast::BasicType * >( type2 ) 
+				|| dynamic_cast< const ast::ZeroType * >( type2 )
+				|| dynamic_cast< const ast::OneType * >( type2 )
+			) {
+				// reuse BasicType/EnumInstType common type by swapping
+				result = commonType( type2, enumInst, widen, symtab, tenv, open );
+			}
+		}
+
+		void postvisit( const ast::TraitInstType * ) {}
+
+		void postvisit( const ast::TypeInstType * inst ) {
+			if ( ! widen.first ) return;
+			if ( const ast::NamedTypeDecl * nt = symtab.lookupType( inst->name ) ) {
+				if ( const ast::Type * base = 
+						strict_dynamic_cast< const ast::TypeDecl * >( nt )->base 
+				) {
+					ast::CV::Qualifiers q1 = inst->qualifiers, q2 = type2->qualifiers;
+
+					// force t{1,2} to be cloned if their qualifiers must be mutated
+					ast::ptr< ast::Type > t1{ base }, t2{ type2 };
+					reset_qualifiers( t1, q1 );
+					reset_qualifiers( t2 );
+
+					ast::AssertionSet have, need;
+					ast::OpenVarSet newOpen{ open };
+					if ( unifyExact( t1, t2, tenv, have, need, newOpen, noWiden(), symtab ) ) {
+						result = type2;
+						reset_qualifiers( result, q1 | q2 );
+					}
+				}
+			}
+		}
+
+		void postvisit( const ast::TupleType * ) {}
+
+		void postvisit( const ast::VarArgsType * ) {}
+
+		void postvisit( const ast::ZeroType * zero ) {
+			if ( ! widen.first ) return;
+			if ( 
+				dynamic_cast< const ast::BasicType * >( type2 )
+				|| dynamic_cast< const ast::PointerType * >( type2 )
+				|| dynamic_cast< const ast::EnumInstType * >( type2 )
+			) {
+				if ( widen.second || zero->qualifiers <= type2->qualifiers ) {
+					result = type2;
+					add_qualifiers( result, zero->qualifiers );
+				}
+			} else if ( widen.second && dynamic_cast< const ast::OneType * >( type2 ) ) {
+				result = new ast::BasicType{ 
+					ast::BasicType::SignedInt, zero->qualifiers | type2->qualifiers };
+			}
+		}
+
+		void postvisit( const ast::OneType * one ) {
+			if ( ! widen.first ) return;
+			if ( 
+				dynamic_cast< const ast::BasicType * >( type2 )
+				|| dynamic_cast< const ast::EnumInstType * >( type2 )
+			) {
+				if ( widen.second || one->qualifiers <= type2->qualifiers ) {
+					result = type2;
+					add_qualifiers( result, one->qualifiers );
+				}
+			} else if ( widen.second && dynamic_cast< const ast::ZeroType * >( type2 ) ) {
+				result = new ast::BasicType{ 
+					ast::BasicType::SignedInt, one->qualifiers | type2->qualifiers };
+			}
+		}
+
+	};
+
+	namespace {
+		ast::ptr< ast::Type > handleReference( 
+			const ast::ptr< ast::Type > & t1, const ast::ptr< ast::Type > & t2, WidenMode widen, 
+			const ast::SymbolTable & symtab, ast::TypeEnvironment & env, 
+			const ast::OpenVarSet & open 
+		) {
+			ast::ptr<ast::Type> common;
+			ast::AssertionSet have, need;
+			ast::OpenVarSet newOpen{ open };
+
+			// need unify to bind type variables
+			if ( unify( t1, t2, env, have, need, newOpen, symtab, common ) ) {
+				ast::CV::Qualifiers q1 = t1->qualifiers, q2 = t2->qualifiers;
+				PRINT(
+					std::cerr << "unify success: " << widenFirst << " " << widenSecond << std::endl;
+				)
+				if ( ( widen.first || q2 <= q1 ) && ( widen.second || q1 <= q2 ) ) {
+					PRINT(
+						std::cerr << "widen okay" << std::endl;
+					)
+					add_qualifiers( common, q1 | q2 );
+					return common;
+				}
+			}
+
+			PRINT(
+				std::cerr << "exact unify failed: " << t1 << " " << t2 << std::endl;
+			)
+			return { nullptr };
+		}
+	}
+
+	ast::ptr< ast::Type > commonType(
+			const ast::ptr< ast::Type > & type1, const ast::ptr< ast::Type > & type2, 
+			WidenMode widen, const ast::SymbolTable & symtab, ast::TypeEnvironment & env, 
+			const ast::OpenVarSet & open 
+	) {
+		unsigned depth1 = type1->referenceDepth();
+		unsigned depth2 = type2->referenceDepth();
+
+		if ( depth1 != depth2 ) {  // implies depth1 > 0 || depth2 > 0
+			PRINT(
+				std::cerr << "reference depth diff: " << (depth1-depth2) << std::endl;
+			)
+			ast::ptr< ast::Type > result;
+			const ast::ReferenceType * ref1 = type1.as< ast::ReferenceType >();
+			const ast::ReferenceType * ref2 = type1.as< ast::ReferenceType >();
+			
+			if ( depth1 > depth2 ) {
+				assert( ref1 );
+				result = handleReference( ref1->base, type2, widen, symtab, env, open );
+			} else {  // implies depth1 < depth2
+				assert( ref2 );
+				result = handleReference( type1, ref2->base, widen, symtab, env, open );
+			}
+
+			if ( result && ref1 ) {
+				// formal is reference, so result should be reference
+				PRINT(
+					std::cerr << "formal is reference; result should be reference" << std::endl;
+				)
+				result = new ast::ReferenceType{ result, ref1->qualifiers };
+			}
+
+			PRINT(
+				std::cerr << "common type of reference [" << type1 << "] and [" << type2 << "] is "
+				"[" << result << "]" << std::endl;
+			)
+			return result;
+		}
+		// otherwise both are reference types of the same depth and this is handled by the visitor
+		ast::Pass<CommonType_new> visitor{ type2, widen, symtab, env, open };
+		type1->accept( visitor );
+		ast::ptr< ast::Type > result = visitor.pass.result;
+
+		// handling for opaque type declarations (?)
+		if ( ! result && widen.second ) {
+			if ( const ast::TypeInstType * inst = type2.as< ast::TypeInstType >() ) {
+				if ( const ast::NamedTypeDecl * nt = symtab.lookupType( inst->name ) ) {
+					auto type = strict_dynamic_cast< const ast::TypeDecl * >( nt );
+					if ( type->base ) {
+						ast::CV::Qualifiers q1 = type1->qualifiers, q2 = type2->qualifiers;
+						ast::AssertionSet have, need;
+						ast::OpenVarSet newOpen{ open };
+
+						// force t{1,2} to be cloned if its qualifiers must be stripped, so that 
+						// type1 and type->base are left unchanged; calling convention forces 
+						// {type1,type->base}->strong_ref >= 1
+						ast::ptr<ast::Type> t1{ type1 }, t2{ type->base };
+						reset_qualifiers( t1 );
+						reset_qualifiers( t2, q1 );
+						
+						if ( unifyExact( t1, t2, env, have, need, newOpen, noWiden(), symtab ) ) {
+							result = t1;
+							reset_qualifiers( result, q1 | q2 );
+						}
+					}
+				}
+			}
+		}
+
+		return result;
+	}
+
 } // namespace ResolvExpr
 
Index: src/ResolvExpr/Unify.cc
===================================================================
--- src/ResolvExpr/Unify.cc	(revision f474e917671c80fd81b2ffce8b4dac26709b333e)
+++ src/ResolvExpr/Unify.cc	(revision ee574a219a52c0b47523b1a790d363cc61f813d3)
@@ -136,5 +136,5 @@
 
 		return unifyExact( 
-			newFirst, newSecond, newEnv, need, have, open, WidenMode{ false, false }, symtab );
+			newFirst, newSecond, newEnv, need, have, open, noWiden(), symtab );
 	}
 
@@ -173,9 +173,9 @@
 		env.apply( newFirst );
 		env.apply( newSecond );
-		clear_qualifiers( newFirst );
-		clear_qualifiers( newSecond );
+		reset_qualifiers( newFirst );
+		reset_qualifiers( newSecond );
 
 		return unifyExact( 
-			newFirst, newSecond, newEnv, need, have, open, WidenMode{ false, false }, symtab );
+			newFirst, newSecond, newEnv, need, have, open, noWiden(), symtab );
 	}
 
@@ -700,5 +700,5 @@
 	}
 
-	class Unify_new : public ast::WithShortCircuiting {
+	class Unify_new final : public ast::WithShortCircuiting {
 		const ast::Type * type2;
 		ast::TypeEnvironment & tenv;
@@ -720,11 +720,9 @@
 		void previsit( const ast::Node * ) { visit_children = false; }
 		
-		void previsit( const ast::VoidType * ) {
-			visit_children = false;
+		void postvisit( const ast::VoidType * ) {
 			result = dynamic_cast< const ast::VoidType * >( type2 );
 		}
 
-		void previsit( const ast::BasicType * basic ) {
-			visit_children = false;
+		void postvisit( const ast::BasicType * basic ) {
 			if ( auto basic2 = dynamic_cast< const ast::BasicType * >( type2 ) ) {
 				result = basic->kind == basic2->kind;
@@ -732,15 +730,13 @@
 		}
 
-		void previsit( const ast::PointerType * pointer ) {
-			visit_children = false;
+		void postvisit( const ast::PointerType * pointer ) {
 			if ( auto pointer2 = dynamic_cast< const ast::PointerType * >( type2 ) ) {
 				result = unifyExact( 
 					pointer->base, pointer2->base, tenv, need, have, open, 
-					WidenMode{ false, false }, symtab );
-			}
-		}
-
-		void previsit( const ast::ArrayType * array ) {
-			visit_children = false;
+					noWiden(), symtab );
+			}
+		}
+
+		void postvisit( const ast::ArrayType * array ) {
 			auto array2 = dynamic_cast< const ast::ArrayType * >( type2 );
 			if ( ! array2 ) return;
@@ -761,13 +757,12 @@
 
 			result = unifyExact( 
-				array->base, array2->base, tenv, need, have, open, WidenMode{ false, false }, 
+				array->base, array2->base, tenv, need, have, open, noWiden(), 
 				symtab );
 		}
 
-		void previsit( const ast::ReferenceType * ref ) {
-			visit_children = false;
+		void postvisit( const ast::ReferenceType * ref ) {
 			if ( auto ref2 = dynamic_cast< const ast::ReferenceType * >( type2 ) ) {
 				result = unifyExact( 
-					ref->base, ref2->base, tenv, need, have, open, WidenMode{ false, false }, 
+					ref->base, ref2->base, tenv, need, have, open, noWiden(), 
 					symtab );
 			}
@@ -783,5 +778,5 @@
 			TtypeExpander_new( ast::TypeEnvironment & env ) : tenv( env ) {}
 
-			const ast::Type * postmutate( const ast::TypeInstType * typeInst ) {
+			const ast::Type * postvisit( const ast::TypeInstType * typeInst ) {
 				if ( const ast::EqvClass * clz = tenv.lookup( typeInst->name ) ) {
 					// expand ttype parameter into its actual type
@@ -811,6 +806,5 @@
 					// overloaded on outermost mutex and a mutex function has different 
 					// requirements than a non-mutex function
-					t.get_and_mutate()->qualifiers 
-						-= ast::CV::Const | ast::CV::Volatile | ast::CV::Atomic;
+					remove_qualifiers( t, ast::CV::Const | ast::CV::Volatile | ast::CV::Atomic );
 					dst.emplace_back( new ast::ObjectDecl{ d->location, "", t } );
 				}
@@ -851,14 +845,14 @@
 					return unifyExact( 
 						t1, tupleFromDecls( crnt2, end2 ), env, need, have, open, 
-						WidenMode{ false, false }, symtab );
+						noWiden(), symtab );
 				} else if ( ! isTuple1 && isTuple2 ) {
 					// combine remainder of list1, then unify
 					return unifyExact( 
 						tupleFromDecls( crnt1, end1 ), t2, env, need, have, open, 
-						WidenMode{ false, false }, symtab );
+						noWiden(), symtab );
 				}
 
 				if ( ! unifyExact( 
-					t1, t2, env, need, have, open, WidenMode{ false, false }, symtab ) 
+					t1, t2, env, need, have, open, noWiden(), symtab ) 
 				) return false;
 
@@ -874,5 +868,5 @@
 				return unifyExact( 
 					t1, tupleFromDecls( crnt2, end2 ), env, need, have, open, 
-					WidenMode{ false, false }, symtab );
+					noWiden(), symtab );
 			} else if ( crnt2 != end2 ) {
 				// try unifying empty tuple with ttype
@@ -881,5 +875,5 @@
 				return unifyExact( 
 					tupleFromDecls( crnt1, end1 ), t2, env, need, have, open, 
-					WidenMode{ false, false }, symtab );
+					noWiden(), symtab );
 			}
 
@@ -919,6 +913,5 @@
 
 	public:
-		void previsit( const ast::FunctionType * func ) {
-			visit_children = false;
+		void postvisit( const ast::FunctionType * func ) {
 			auto func2 = dynamic_cast< const ast::FunctionType * >( type2 );
 			if ( ! func2 ) return;
@@ -952,5 +945,4 @@
 		template< typename RefType >
 		const RefType * handleRefType( const RefType * inst, const ast::Type * other ) {
-			visit_children = false;
 			// check that the other type is compatible and named the same
 			auto otherInst = dynamic_cast< const RefType * >( other );
@@ -1011,5 +1003,5 @@
 
 				if ( ! unifyExact( 
-						pty, pty2, tenv, need, have, open, WidenMode{ false, false }, symtab ) ) {
+						pty, pty2, tenv, need, have, open, noWiden(), symtab ) ) {
 					result = false;
 					return;
@@ -1023,21 +1015,21 @@
 
 	public:
-		void previsit( const ast::StructInstType * aggrType ) {
+		void postvisit( const ast::StructInstType * aggrType ) {
 			handleGenericRefType( aggrType, type2 );
 		}
 
-		void previsit( const ast::UnionInstType * aggrType ) {
+		void postvisit( const ast::UnionInstType * aggrType ) {
 			handleGenericRefType( aggrType, type2 );
 		}
 
-		void previsit( const ast::EnumInstType * aggrType ) {
+		void postvisit( const ast::EnumInstType * aggrType ) {
 			handleRefType( aggrType, type2 );
 		}
 
-		void previsit( const ast::TraitInstType * aggrType ) {
+		void postvisit( const ast::TraitInstType * aggrType ) {
 			handleRefType( aggrType, type2 );
 		}
 
-		void previsit( const ast::TypeInstType * typeInst ) {
+		void postvisit( const ast::TypeInstType * typeInst ) {
 			assert( open.find( typeInst->name ) == open.end() );
 			handleRefType( typeInst, type2 );
@@ -1078,14 +1070,14 @@
 					return unifyExact( 
 						t1, tupleFromTypes( list2 ), env, need, have, open, 
-						WidenMode{ false, false }, symtab );
+						noWiden(), symtab );
 				} else if ( ! isTuple1 && isTuple2 ) {
 					// combine entirety of list1, then unify
 					return unifyExact(
 						tupleFromTypes( list1 ), t2, env, need, have, open, 
-						WidenMode{ false, false }, symtab );
+						noWiden(), symtab );
 				}
 
 				if ( ! unifyExact( 
-					t1, t2, env, need, have, open, WidenMode{ false, false }, symtab ) 
+					t1, t2, env, need, have, open, noWiden(), symtab ) 
 				) return false;
 
@@ -1101,5 +1093,5 @@
 				return unifyExact( 
 						t1, tupleFromTypes( list2 ), env, need, have, open, 
-						WidenMode{ false, false }, symtab );
+						noWiden(), symtab );
 			} else if ( crnt2 != list2.end() ) {
 				// try unifying empty tuple with ttype
@@ -1110,5 +1102,5 @@
 				return unifyExact(
 						tupleFromTypes( list1 ), t2, env, need, have, open, 
-						WidenMode{ false, false }, symtab );
+						noWiden(), symtab );
 			}
 
@@ -1117,6 +1109,5 @@
 
 	public:
-		void previsit( const ast::TupleType * tuple ) {
-			visit_children = false;
+		void postvisit( const ast::TupleType * tuple ) {
 			auto tuple2 = dynamic_cast< const ast::TupleType * >( type2 );
 			if ( ! tuple2 ) return;
@@ -1132,16 +1123,13 @@
 		}
 
-		void previsit( const ast::VarArgsType * ) {
-			visit_children = false;
+		void postvisit( const ast::VarArgsType * ) {
 			result = dynamic_cast< const ast::VarArgsType * >( type2 );
 		}
 
-		void previsit( const ast::ZeroType * ) {
-			visit_children = false;
+		void postvisit( const ast::ZeroType * ) {
 			result = dynamic_cast< const ast::ZeroType * >( type2 );
 		}
 
-		void previsit( const ast::OneType * ) {
-			visit_children = false;
+		void postvisit( const ast::OneType * ) {
 			result = dynamic_cast< const ast::OneType * >( type2 );
 		}	
@@ -1151,4 +1139,16 @@
 		template< typename RefType > void handleGenericRefType( RefType *inst, Type *other );
 	};
+
+	bool unify( 
+			const ast::ptr<ast::Type> & type1, const ast::ptr<ast::Type> & type2, 
+			ast::TypeEnvironment & env, ast::AssertionSet & need, ast::AssertionSet & have, 
+			ast::OpenVarSet & open, const ast::SymbolTable & symtab, ast::ptr<ast::Type> & common 
+	) {
+		ast::OpenVarSet closed;
+		findOpenVars( type1, open, closed, need, have, FirstClosed );
+		findOpenVars( type2, open, closed, need, have, FirstOpen );
+		return unifyInexact( 
+			type1, type2, env, need, have, open, WidenMode{ true, true }, symtab, common );
+	}
 
 	bool unifyExact( 
@@ -1184,7 +1184,8 @@
 
 	bool unifyInexact( 
-			ast::ptr<ast::Type> & type1, ast::ptr<ast::Type> & type2, ast::TypeEnvironment & env, 
-			ast::AssertionSet & need, ast::AssertionSet & have, const ast::OpenVarSet & open, 
-			WidenMode widen, const ast::SymbolTable & symtab, ast::ptr<ast::Type> & common 
+			const ast::ptr<ast::Type> & type1, const ast::ptr<ast::Type> & type2, 
+			ast::TypeEnvironment & env, ast::AssertionSet & need, ast::AssertionSet & have, 
+			const ast::OpenVarSet & open, WidenMode widen, const ast::SymbolTable & symtab, 
+			ast::ptr<ast::Type> & common 
 	) {
 		ast::CV::Qualifiers q1 = type1->qualifiers, q2 = type2->qualifiers;
@@ -1193,6 +1194,6 @@
 		// type2 are left unchanged; calling convention forces type{1,2}->strong_ref >= 1
 		ast::ptr<ast::Type> t1{ type1 }, t2{ type2 };
-		clear_qualifiers( t1 );
-		clear_qualifiers( t2 );
+		reset_qualifiers( t1 );
+		reset_qualifiers( t2 );
 		
 		if ( unifyExact( t1, t2, env, need, have, open, widen, symtab ) ) {
@@ -1201,5 +1202,6 @@
 			// if exact unification on unqualified types, try to merge qualifiers
 			if ( q1 == q2 || ( ( q1 > q2 || widen.first ) && ( q2 > q1 || widen.second ) ) ) {
-				common.set_and_mutate( type1 )->qualifiers = q1 | q2;
+				common = type1;
+				reset_qualifiers( common, q1 | q2 );
 				return true;
 			} else {
@@ -1211,5 +1213,5 @@
 
 			// no exact unification, but common type
-			common.get_and_mutate()->qualifiers = q1 | q2;
+			reset_qualifiers( common, q1 | q2 );
 			return true;
 		} else {
Index: src/ResolvExpr/Unify.h
===================================================================
--- src/ResolvExpr/Unify.h	(revision f474e917671c80fd81b2ffce8b4dac26709b333e)
+++ src/ResolvExpr/Unify.h	(revision ee574a219a52c0b47523b1a790d363cc61f813d3)
@@ -69,13 +69,19 @@
 	}
 
+	bool unify( 
+		const ast::ptr<ast::Type> & type1, const ast::ptr<ast::Type> & type2, 
+		ast::TypeEnvironment & env, ast::AssertionSet & need, ast::AssertionSet & have, 
+		ast::OpenVarSet & open, const ast::SymbolTable & symtab, ast::ptr<ast::Type> & common );
+
 	bool unifyExact( 
 		const ast::Type * type1, const ast::Type * type2, ast::TypeEnvironment & env, 
-		ast::AssertionSet & need, ast::AssertionSet & have, ast::OpenVarSet & open, 
-		const ast::SymbolTable & symtab );
+		ast::AssertionSet & need, ast::AssertionSet & have, const ast::OpenVarSet & open, 
+		WidenMode widen, const ast::SymbolTable & symtab );
 
 	bool unifyInexact( 
-		ast::ptr<ast::Type> & type1, ast::ptr<ast::Type> & type2, ast::TypeEnvironment & env, 
-		ast::AssertionSet & need, ast::AssertionSet & have, const ast::OpenVarSet & open, 
-		WidenMode widen, const ast::SymbolTable & symtab, ast::ptr<ast::Type> & common );
+		const ast::ptr<ast::Type> & type1, const ast::ptr<ast::Type> & type2, 
+		ast::TypeEnvironment & env, ast::AssertionSet & need, ast::AssertionSet & have, 
+		const ast::OpenVarSet & open, WidenMode widen, const ast::SymbolTable & symtab, 
+		ast::ptr<ast::Type> & common );
 
 } // namespace ResolvExpr
Index: src/ResolvExpr/WidenMode.h
===================================================================
--- src/ResolvExpr/WidenMode.h	(revision f474e917671c80fd81b2ffce8b4dac26709b333e)
+++ src/ResolvExpr/WidenMode.h	(revision ee574a219a52c0b47523b1a790d363cc61f813d3)
@@ -40,4 +40,6 @@
 		bool first : 1, second : 1;
 	};
+
+	static inline WidenMode noWiden() { return { false, false }; }
 } // namespace ResolvExpr
 
Index: src/ResolvExpr/typeops.h
===================================================================
--- src/ResolvExpr/typeops.h	(revision f474e917671c80fd81b2ffce8b4dac26709b333e)
+++ src/ResolvExpr/typeops.h	(revision ee574a219a52c0b47523b1a790d363cc61f813d3)
@@ -89,5 +89,4 @@
 
 	// in Unify.cc
-	bool isFtype( Type *type );
 	bool typesCompatible( Type *, Type *, const SymTab::Indexer &indexer, const TypeEnvironment &env );
 	bool typesCompatibleIgnoreQualifiers( Type *, Type *, const SymTab::Indexer &indexer, const TypeEnvironment &env );
@@ -118,6 +117,6 @@
 	// in CommonType.cc
 	Type * commonType( Type *type1, Type *type2, bool widenFirst, bool widenSecond, const SymTab::Indexer &indexer, TypeEnvironment &env, const OpenVarSet &openVars );
-	const ast::Type * commonType(
-		const ast::Type * type1, const ast::Type * type2, WidenMode widen, 
+	ast::ptr< ast::Type > commonType(
+		const ast::ptr< ast::Type > & type1, const ast::ptr< ast::Type > & type2, WidenMode widen, 
 		const ast::SymbolTable & symtab, ast::TypeEnvironment & env, const ast::OpenVarSet & open );
 
@@ -177,5 +176,13 @@
 		return out;
 	}
+
+	// in TypeEnvironment.cc
+	bool isFtype( Type *type );
 } // namespace ResolvExpr
+
+namespace ast {
+	// in TypeEnvironment.cpp
+	bool isFtype( const ast::Type * type );
+} // namespace ast
 
 // Local Variables: //
