Index: libcfa/src/concurrency/monitor.hfa
===================================================================
--- libcfa/src/concurrency/monitor.hfa	(revision 8f1e03582aaeca10f66354d884763209e5fe44bc)
+++ libcfa/src/concurrency/monitor.hfa	(revision ef1da0e2df5a222ca4e50f7e06a2b61f8ba6efcd)
@@ -60,4 +60,5 @@
 void ^?{}( monitor_dtor_guard_t & this );
 
+/*
 static inline forall( T & | sized(T) | { void ^?{}( T & mutex ); } )
 void delete( T * th ) {
@@ -65,4 +66,5 @@
 	free( th );
 }
+*/
 
 static inline forall( T & | sized(T) | { void ^?{}( T & mutex ); } )
Index: src/AST/Print.cpp
===================================================================
--- src/AST/Print.cpp	(revision 8f1e03582aaeca10f66354d884763209e5fe44bc)
+++ src/AST/Print.cpp	(revision ef1da0e2df5a222ca4e50f7e06a2b61f8ba6efcd)
@@ -90,5 +90,5 @@
 
 		static constexpr auto Qualifiers = make_array<const char*>(
-			"const", "restrict", "volatile", "lvalue", "mutex", "_Atomic"
+			"const", "restrict", "volatile", "mutex", "_Atomic"
 		);
 	};
@@ -1624,4 +1624,4 @@
 constexpr array<const char*, 3> Printer::Names::FuncSpecifiers;
 constexpr array<const char*, 6> Printer::Names::StorageClasses;
-constexpr array<const char*, 6> Printer::Names::Qualifiers;
+constexpr array<const char*, 5> Printer::Names::Qualifiers;
 }
Index: src/AST/TypeEnvironment.hpp
===================================================================
--- src/AST/TypeEnvironment.hpp	(revision 8f1e03582aaeca10f66354d884763209e5fe44bc)
+++ src/AST/TypeEnvironment.hpp	(revision ef1da0e2df5a222ca4e50f7e06a2b61f8ba6efcd)
@@ -56,4 +56,10 @@
 struct AssertCompare {
 	bool operator()( const VariableExpr * d1, const VariableExpr * d2 ) const {
+		auto kind1 = ast::SymbolTable::getSpecialFunctionKind(d1->var->name);
+		auto kind2 = ast::SymbolTable::getSpecialFunctionKind(d2->var->name);
+		// heuristics optimization: force special functions to go last
+		if (kind1 > kind2) return true;
+		else if (kind1 < kind2) return false;
+
 		int cmp = d1->var->name.compare( d2->var->name );
 		return cmp < 0 || ( cmp == 0 && d1->result < d2->result );
Index: src/ResolvExpr/CommonType.cc
===================================================================
--- src/ResolvExpr/CommonType.cc	(revision 8f1e03582aaeca10f66354d884763209e5fe44bc)
+++ src/ResolvExpr/CommonType.cc	(revision ef1da0e2df5a222ca4e50f7e06a2b61f8ba6efcd)
@@ -28,4 +28,5 @@
 #include "Unify.h"                       // for unifyExact, WidenMode
 #include "typeops.h"                     // for isFtype
+#include "Tuples/Tuples.h"
 
 // #define DEBUG
@@ -675,4 +676,6 @@
 		ast::TypeEnvironment & tenv;
 		const ast::OpenVarSet & open;
+		ast::AssertionSet & need;
+		ast::AssertionSet & have;
 	public:
 		static size_t traceId;
@@ -681,6 +684,7 @@
 		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() {}
+			ast::TypeEnvironment & env, const ast::OpenVarSet & o,
+			ast::AssertionSet & need, ast::AssertionSet & have )
+		: type2( t2 ), widen( w ), symtab( st ), tenv( env ), open( o ), need (need), have (have) ,result() {}
 
 		void previsit( const ast::Node * ) { visit_children = false; }
@@ -753,5 +757,4 @@
 		bool tryResolveWithTypedEnum( const ast::Type * type1 ) {
 			if (auto enumInst = dynamic_cast<const ast::EnumInstType *> (type2) ) {
-				ast::AssertionSet have, need; // unused
 				ast::OpenVarSet newOpen{ open };
 				if (enumInst->base->base 
@@ -792,5 +795,4 @@
 					reset_qualifiers( t2 );
 
-					ast::AssertionSet have, need;
 					ast::OpenVarSet newOpen{ open };
 					if ( unifyExact( t1, t2, tenv, have, need, newOpen, noWiden(), symtab ) ) {
@@ -803,4 +805,139 @@
 						}
 					}
+					else if ( isFtype (t1) && isFtype (t2) ) {
+						auto f1 = t1.as<ast::FunctionType>();
+						if (!f1) return;
+						auto f2 = t2.strict_as<ast::FunctionType>();
+
+						assertf(f1->returns.size() <= 1, "Function return should not be a list");
+						assertf(f2->returns.size() <= 1, "Function return should not be a list");
+
+						if (
+							( f1->params.size() != f2->params.size() || f1->returns.size() != f2->returns.size() )
+							&& ! f1->isTtype()
+							&& ! f2->isTtype()
+						) return;
+
+						auto params1 = flattenList( f1->params, tenv );
+						auto params2 = flattenList( f2->params, tenv );
+
+						auto crnt1 = params1.begin();
+						auto crnt2 = params2.begin();
+						auto end1 = params1.end();
+						auto end2 = params2.end();
+
+						while (crnt1 != end1 && crnt2 != end2 ) {
+							const ast::Type * arg1 = *crnt1;
+							const ast::Type * arg2 = *crnt2;
+
+							bool isTuple1 = Tuples::isTtype( t1 );
+							bool isTuple2 = Tuples::isTtype( t2 );
+
+							// assumes here that ttype *must* be last parameter
+							if ( isTuple1 && ! isTuple2 ) {
+								// combine remainder of list2, then unify
+								if (unifyExact(
+									arg1, tupleFromTypes( crnt2, end2 ), tenv, need, have, open,
+									noWiden(), symtab )) {
+										break;
+
+								}
+								else return;
+							} else if ( ! isTuple1 && isTuple2 ) {
+								// combine remainder of list1, then unify
+								if (unifyExact(
+									tupleFromTypes( crnt1, end1 ), arg2, tenv, need, have, open,
+									noWiden(), symtab )) {
+										break;
+
+								}
+								else return;
+							}
+
+							// allow qualifiers of pointer and reference base to become more specific
+							if (auto ref1 = dynamic_cast<const ast::ReferenceType *> (arg1)) {
+								if (auto ref2 = dynamic_cast<const ast::ReferenceType *> (arg2)) {
+									ast::ptr<ast::Type> base1 = ref1->base;
+									ast::ptr<ast::Type> base2 = ref2->base;
+
+									// xxx - assume LHS is always the target type
+
+									if ( ! ((widen.second && ref2->qualifiers.is_mutex) 
+									|| (ref1->qualifiers.is_mutex == ref2->qualifiers.is_mutex ))) return;
+
+									if ( (widen.second && base1->qualifiers <= base2->qualifiers ) || (base2->qualifiers == base1->qualifiers) ) {
+
+										reset_qualifiers(base1);
+										reset_qualifiers(base2);
+
+										if ( ! unifyExact(
+											base1, base2, tenv, need, have, open, noWiden(), symtab )
+										) return;
+									}	
+								}
+								else return;
+							}
+							else if (auto ptr1 = dynamic_cast<const ast::PointerType *> (arg1)) {
+								if (auto ptr2 = dynamic_cast<const ast::PointerType *> (arg2)) {
+									ast::ptr<ast::Type> base1 = ptr1->base;
+									ast::ptr<ast::Type> base2 = ptr2->base;
+
+									// xxx - assume LHS is always the target type
+									// a function accepting const can always be called by non-const arg
+
+									if ( (widen.second && base1->qualifiers <= base2->qualifiers ) || (base2->qualifiers == base1->qualifiers) ) {
+
+										reset_qualifiers(base1);
+										reset_qualifiers(base2);
+
+										if ( ! unifyExact(
+											base1, base2, tenv, need, have, open, noWiden(), symtab )
+										) return;
+									}	
+								}
+								else return;
+
+							}
+							else if (! unifyExact(
+								arg1, arg2, tenv, need, have, open, noWiden(), symtab )) return;
+
+							++crnt1; ++crnt2;
+						}
+						if ( crnt1 != end1 ) {
+							// try unifying empty tuple with ttype
+							const ast::Type * t1 = *crnt1;
+							if (! Tuples::isTtype( t1 ) ) return;
+							if (! unifyExact(
+								t1, tupleFromTypes( crnt2, end2 ), tenv, need, have, open,
+								noWiden(), symtab )) return;
+						} else if ( crnt2 != end2 ) {
+							// try unifying empty tuple with ttype
+							const ast::Type * t2 = *crnt2;
+							if (! Tuples::isTtype( t2 ) ) return;
+							if (! unifyExact(
+								tupleFromTypes( crnt1, end1 ), t2, tenv, need, have, open,
+								noWiden(), symtab )) return;
+						}
+						if ((f1->returns.size() == 0 && f2->returns.size() == 0)
+						  || (f1->returns.size() == 1 && f2->returns.size() == 1 && unifyExact(f1->returns[0], f2->returns[0], tenv, need, have, open, noWiden(), symtab))) {
+							result = pointer;
+
+							for (auto & assn : f1->assertions) {
+								auto i = need.find(assn);
+								if (i != need.end()) i->second.isUsed = true;
+								auto j = have.find(assn);
+								if (j != have.end()) j->second.isUsed = true;
+							}
+
+							for (auto & assn : f2->assertions) {
+								auto i = need.find(assn);
+								if (i != need.end()) i->second.isUsed = true;
+								auto j = have.find(assn);
+								if (j != have.end()) j->second.isUsed = true;
+							}
+
+						}
+					} // if ftype
+					
 				}
 			} else if ( widen.second && dynamic_cast< const ast::ZeroType * >( type2 ) ) {
@@ -839,5 +976,4 @@
 					reset_qualifiers( t2 );
 
-					ast::AssertionSet have, need;
 					ast::OpenVarSet newOpen{ open };
 					if ( unifyExact( t1, t2, tenv, have, need, newOpen, noWiden(), symtab ) ) {
@@ -857,5 +993,5 @@
 				// xxx - does unifying a ref with typed enumInst makes sense?
 				if (!dynamic_cast<const ast::EnumInstType *>(type2))
-					result = commonType( type2, ref, widen, symtab, tenv, open );
+					result = commonType( type2, ref, tenv, need, have, open, widen, symtab );
 			}
 		}
@@ -877,5 +1013,5 @@
 			// xxx - is this already handled by unify?
 			if (!dynamic_cast<const ast::EnumInstType *>(type2))
-				result = commonType( type2, enumInst, widen, symtab, tenv, open );
+				result = commonType( type2, enumInst, tenv, need, have, open, widen, symtab);
 		}
 
@@ -895,5 +1031,4 @@
 					reset_qualifiers( t2 );
 
-					ast::AssertionSet have, need;
 					ast::OpenVarSet newOpen{ open };
 					if ( unifyExact( t1, t2, tenv, have, need, newOpen, noWiden(), symtab ) ) {
@@ -999,6 +1134,6 @@
 	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
+			ast::TypeEnvironment & env, ast::AssertionSet & need, ast::AssertionSet & have,
+			const ast::OpenVarSet & open, WidenMode widen, const ast::SymbolTable & symtab
 	) {
 		unsigned depth1 = type1->referenceDepth();
@@ -1036,5 +1171,5 @@
 		}
 		// 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 };
+		ast::Pass<CommonType_new> visitor{ type2, widen, symtab, env, open, need, have };
 		type1->accept( visitor );
 		ast::ptr< ast::Type > result = visitor.core.result;
@@ -1047,5 +1182,4 @@
 					if ( type->base ) {
 						ast::CV::Qualifiers q1 = type1->qualifiers, q2 = type2->qualifiers;
-						ast::AssertionSet have, need;
 						ast::OpenVarSet newOpen{ open };
 
Index: src/ResolvExpr/ConversionCost.cc
===================================================================
--- src/ResolvExpr/ConversionCost.cc	(revision 8f1e03582aaeca10f66354d884763209e5fe44bc)
+++ src/ResolvExpr/ConversionCost.cc	(revision ef1da0e2df5a222ca4e50f7e06a2b61f8ba6efcd)
@@ -22,8 +22,10 @@
 #include "ResolvExpr/Cost.h"             // for Cost
 #include "ResolvExpr/TypeEnvironment.h"  // for EqvClass, TypeEnvironment
+#include "ResolvExpr/Unify.h"
 #include "SymTab/Indexer.h"              // for Indexer
 #include "SynTree/Declaration.h"         // for TypeDecl, NamedTypeDecl
 #include "SynTree/Type.h"                // for Type, BasicType, TypeInstType
 #include "typeops.h"                     // for typesCompatibleIgnoreQualifiers
+
 
 namespace ResolvExpr {
@@ -655,5 +657,25 @@
 				cost = Cost::safe;
 			}
-		} else {
+		}
+		/*
+		else if ( const ast::FunctionType * dstFunc = dstAsPtr->base.as<ast::FunctionType>()) {
+			if (const ast::FunctionType * srcFunc = pointerType->base.as<ast::FunctionType>()) {
+				if (dstFunc->params.empty() && dstFunc->isVarArgs ) {
+					cost = Cost::unsafe; // assign any function to variadic fptr
+				}
+			}
+			else {
+				ast::AssertionSet need, have; // unused
+				ast::OpenVarSet open;
+				env.extractOpenVars(open);
+				ast::TypeEnvironment tenv = env;
+				if ( unify(dstAsPtr->base, pointerType->base, tenv, need, have, open, symtab) ) {
+					cost = Cost::safe;
+				}
+			}
+			// else infinity
+		}
+		*/
+		else {
 			int assignResult = ptrsAssignable( pointerType->base, dstAsPtr->base, env );
 			if ( 0 < assignResult && tq1 <= tq2 ) {
Index: src/ResolvExpr/SatisfyAssertions.cpp
===================================================================
--- src/ResolvExpr/SatisfyAssertions.cpp	(revision 8f1e03582aaeca10f66354d884763209e5fe44bc)
+++ src/ResolvExpr/SatisfyAssertions.cpp	(revision ef1da0e2df5a222ca4e50f7e06a2b61f8ba6efcd)
@@ -36,4 +36,5 @@
 #include "AST/SymbolTable.hpp"
 #include "AST/TypeEnvironment.hpp"
+#include "FindOpenVars.h"
 #include "Common/FilterCombos.h"
 #include "Common/Indenter.h"
@@ -161,5 +162,5 @@
 
 	/// Satisfy a single assertion
-	bool satisfyAssertion( ast::AssertionList::value_type & assn, SatState & sat ) {
+	bool satisfyAssertion( ast::AssertionList::value_type & assn, SatState & sat, bool allowConversion = false, bool skipUnbound = false) {
 		// skip unused assertions
 		if ( ! assn.second.isUsed ) return true;
@@ -180,4 +181,5 @@
 			if (thisArgType.as<ast::PointerType>()) otypeKey = Mangle::Encoding::pointer;
 			else if (!isUnboundType(thisArgType)) otypeKey = Mangle::mangle(thisArgType, Mangle::Type | Mangle::NoGenericParams);
+			else if (skipUnbound) return false;
 
 			candidates = sat.symtab.specialLookupId(kind, otypeKey);
@@ -205,15 +207,35 @@
 
 			// only keep candidates which unify
-			if ( unify( toType, adjType, newEnv, newNeed, have, newOpen, sat.symtab ) ) {
-				// set up binding slot for recursive assertions
-				ast::UniqueId crntResnSlot = 0;
-				if ( ! newNeed.empty() ) {
-					crntResnSlot = ++globalResnSlot;
-					for ( auto & a : newNeed ) { a.second.resnSlot = crntResnSlot; }
-				}
-
-				matches.emplace_back(
-					cdata, adjType, std::move( newEnv ), std::move( have ), std::move( newNeed ),
-					std::move( newOpen ), crntResnSlot );
+
+			ast::OpenVarSet closed;
+			findOpenVars( toType, newOpen, closed, newNeed, have, FirstClosed );
+			findOpenVars( adjType, newOpen, closed, newNeed, have, FirstOpen );
+			if ( allowConversion ) {
+				if ( auto c = commonType( toType, adjType, newEnv, newNeed, have, newOpen, WidenMode {true, true}, sat.symtab ) ) {
+					// set up binding slot for recursive assertions
+					ast::UniqueId crntResnSlot = 0;
+					if ( ! newNeed.empty() ) {
+						crntResnSlot = ++globalResnSlot;
+						for ( auto & a : newNeed ) { a.second.resnSlot = crntResnSlot; }
+					}
+
+					matches.emplace_back(
+						cdata, adjType, std::move( newEnv ), std::move( have ), std::move( newNeed ),
+						std::move( newOpen ), crntResnSlot );
+				}
+			}
+			else {
+				if ( unifyExact( toType, adjType, newEnv, newNeed, have, newOpen, WidenMode {true, true}, sat.symtab ) ) {
+					// set up binding slot for recursive assertions
+					ast::UniqueId crntResnSlot = 0;
+					if ( ! newNeed.empty() ) {
+						crntResnSlot = ++globalResnSlot;
+						for ( auto & a : newNeed ) { a.second.resnSlot = crntResnSlot; }
+					}
+
+					matches.emplace_back(
+						cdata, adjType, std::move( newEnv ), std::move( have ), std::move( newNeed ),
+						std::move( newOpen ), crntResnSlot );
+				}
 			}
 		}
@@ -413,4 +435,5 @@
 		// for each current mutually-compatible set of assertions
 		for ( SatState & sat : sats ) {
+			bool allowConversion = false;
 			// stop this branch if a better option is already found
 			auto it = thresholds.find( pruneKey( *sat.cand ) );
@@ -418,4 +441,5 @@
 
 			// should a limit be imposed? worst case here is O(n^2) but very unlikely to happen.
+
 			for (unsigned resetCount = 0; ; ++resetCount) {
 				ast::AssertionList next;
@@ -424,5 +448,5 @@
 				for ( auto & assn : sat.need ) {
 					// fail early if any assertion is not satisfiable
-					if ( ! satisfyAssertion( assn, sat ) ) {
+					if ( ! satisfyAssertion( assn, sat, allowConversion, !next.empty() ) ) {
 						next.emplace_back(assn);
 						// goto nextSat;
@@ -433,14 +457,22 @@
 				// fail if nothing resolves
 				else if (next.size() == sat.need.size()) {
-					Indenter tabs{ 3 };
-					std::ostringstream ss;
-					ss << tabs << "Unsatisfiable alternative:\n";
-					print( ss, *sat.cand, ++tabs );
-					ss << (tabs-1) << "Could not satisfy assertion:\n";
-					ast::print( ss, next[0].first, tabs );
-
-					errors.emplace_back( ss.str() );
-					goto nextSat;
-				}
+					if (allowConversion) {
+						Indenter tabs{ 3 };
+						std::ostringstream ss;
+						ss << tabs << "Unsatisfiable alternative:\n";
+						print( ss, *sat.cand, ++tabs );
+						ss << (tabs-1) << "Could not satisfy assertion:\n";
+						ast::print( ss, next[0].first, tabs );
+
+						errors.emplace_back( ss.str() );
+						goto nextSat;
+					}
+
+					else {
+						allowConversion = true;
+						continue;
+					}
+				}
+				allowConversion = false;
 				sat.need = std::move(next);
 			}
Index: src/ResolvExpr/Unify.cc
===================================================================
--- src/ResolvExpr/Unify.cc	(revision 8f1e03582aaeca10f66354d884763209e5fe44bc)
+++ src/ResolvExpr/Unify.cc	(revision ef1da0e2df5a222ca4e50f7e06a2b61f8ba6efcd)
@@ -693,4 +693,50 @@
 	}
 
+	namespace {
+				/// Replaces ttype variables with their bound types.
+		/// If this isn't done when satifying ttype assertions, then argument lists can have
+		/// different size and structure when they should be compatible.
+		struct TtypeExpander_new : public ast::WithShortCircuiting, public ast::PureVisitor {
+			ast::TypeEnvironment & tenv;
+
+			TtypeExpander_new( ast::TypeEnvironment & env ) : tenv( env ) {}
+
+			const ast::Type * postvisit( const ast::TypeInstType * typeInst ) {
+				if ( const ast::EqvClass * clz = tenv.lookup( *typeInst ) ) {
+					// expand ttype parameter into its actual type
+					if ( clz->data.kind == ast::TypeDecl::Ttype && clz->bound ) {
+						return clz->bound;
+					}
+				}
+				return typeInst;
+			}
+		};
+	}
+	
+	std::vector< ast::ptr< ast::Type > > flattenList(
+		const std::vector< ast::ptr< ast::Type > > & src, ast::TypeEnvironment & env
+	) {
+		std::vector< ast::ptr< ast::Type > > dst;
+		dst.reserve( src.size() );
+		for ( const auto & d : src ) {
+			ast::Pass<TtypeExpander_new> expander{ env };
+			// TtypeExpander pass is impure (may mutate nodes in place)
+			// need to make nodes shared to prevent accidental mutation
+			ast::ptr<ast::Type> dc = d->accept(expander);
+			auto types = flatten( dc );
+			for ( ast::ptr< ast::Type > & t : types ) {
+				// outermost const, volatile, _Atomic qualifiers in parameters should not play
+				// a role in the unification of function types, since they do not determine
+				// whether a function is callable.
+				// NOTE: **must** consider at least mutex qualifier, since functions can be
+				// overloaded on outermost mutex and a mutex function has different
+				// requirements than a non-mutex function
+				remove_qualifiers( t, ast::CV::Const | ast::CV::Volatile | ast::CV::Atomic );
+				dst.emplace_back( t );
+			}
+		}
+		return dst;
+	}
+
 	class Unify_new final : public ast::WithShortCircuiting {
 		const ast::Type * type2;
@@ -764,63 +810,4 @@
 
 	private:
-		/// Replaces ttype variables with their bound types.
-		/// If this isn't done when satifying ttype assertions, then argument lists can have
-		/// different size and structure when they should be compatible.
-		struct TtypeExpander_new : public ast::WithShortCircuiting, public ast::PureVisitor {
-			ast::TypeEnvironment & tenv;
-
-			TtypeExpander_new( ast::TypeEnvironment & env ) : tenv( env ) {}
-
-			const ast::Type * postvisit( const ast::TypeInstType * typeInst ) {
-				if ( const ast::EqvClass * clz = tenv.lookup( *typeInst ) ) {
-					// expand ttype parameter into its actual type
-					if ( clz->data.kind == ast::TypeDecl::Ttype && clz->bound ) {
-						return clz->bound;
-					}
-				}
-				return typeInst;
-			}
-		};
-
-		/// returns flattened version of `src`
-		static std::vector< ast::ptr< ast::Type > > flattenList(
-			const std::vector< ast::ptr< ast::Type > > & src, ast::TypeEnvironment & env
-		) {
-			std::vector< ast::ptr< ast::Type > > dst;
-			dst.reserve( src.size() );
-			for ( const auto & d : src ) {
-				ast::Pass<TtypeExpander_new> expander{ env };
-				// TtypeExpander pass is impure (may mutate nodes in place)
-				// need to make nodes shared to prevent accidental mutation
-				ast::ptr<ast::Type> dc = d->accept(expander);
-				auto types = flatten( dc );
-				for ( ast::ptr< ast::Type > & t : types ) {
-					// outermost const, volatile, _Atomic qualifiers in parameters should not play
-					// a role in the unification of function types, since they do not determine
-					// whether a function is callable.
-					// NOTE: **must** consider at least mutex qualifier, since functions can be
-					// overloaded on outermost mutex and a mutex function has different
-					// requirements than a non-mutex function
-					remove_qualifiers( t, ast::CV::Const | ast::CV::Volatile | ast::CV::Atomic );
-					dst.emplace_back( t );
-				}
-			}
-			return dst;
-		}
-
-		/// Creates a tuple type based on a list of DeclWithType
-		template< typename Iter >
-		static const ast::Type * tupleFromTypes( Iter crnt, Iter end ) {
-			std::vector< ast::ptr< ast::Type > > types;
-			while ( crnt != end ) {
-				// it is guaranteed that a ttype variable will be bound to a flat tuple, so ensure
-				// that this results in a flat tuple
-				flatten( *crnt, types );
-
-				++crnt;
-			}
-
-			return new ast::TupleType{ std::move(types) };
-		}
 
 		template< typename Iter >
@@ -1034,16 +1021,5 @@
 	private:
 		/// Creates a tuple type based on a list of Type
-		static const ast::Type * tupleFromTypes(
-			const std::vector< ast::ptr< ast::Type > > & tys
-		) {
-			std::vector< ast::ptr< ast::Type > > out;
-			for ( const ast::Type * ty : tys ) {
-				// it is guaranteed that a ttype variable will be bound to a flat tuple, so ensure
-				// that this results in a flat tuple
-				flatten( ty, out );
-			}
-
-			return new ast::TupleType{ std::move(out) };
-		}
+		
 
 		static bool unifyList(
@@ -1217,5 +1193,5 @@
 			}
 
-		} else if (( common = commonType( t1, t2, widen, symtab, env, open ) )) {
+		} else if ( common = commonType( t1, t2, env, need, have, open, widen, symtab )) {
 			// no exact unification, but common type
 			auto c = shallowCopy(common.get());
Index: src/ResolvExpr/typeops.h
===================================================================
--- src/ResolvExpr/typeops.h	(revision 8f1e03582aaeca10f66354d884763209e5fe44bc)
+++ src/ResolvExpr/typeops.h	(revision ef1da0e2df5a222ca4e50f7e06a2b61f8ba6efcd)
@@ -138,6 +138,12 @@
 	Type * commonType( Type * type1, Type * type2, bool widenFirst, bool widenSecond, const SymTab::Indexer & indexer, TypeEnvironment & env, const OpenVarSet & openVars );
 	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 );
+		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
+	);
+	// in Unify.cc
+	std::vector< ast::ptr< ast::Type > > flattenList(
+		const std::vector< ast::ptr< ast::Type > > & src, ast::TypeEnvironment & env
+	);
 
 	// in PolyCost.cc
@@ -181,5 +187,5 @@
 
 	/// flatten tuple type into existing list of types
-	static inline void flatten(
+	inline void flatten(
 		const ast::Type * type, std::vector< ast::ptr< ast::Type > > & out
 	) {
@@ -194,5 +200,5 @@
 
 	/// flatten tuple type into list of types
-	static inline std::vector< ast::ptr< ast::Type > > flatten( const ast::Type * type ) {
+	inline std::vector< ast::ptr< ast::Type > > flatten( const ast::Type * type ) {
 		std::vector< ast::ptr< ast::Type > > out;
 		out.reserve( type->size() );
@@ -200,4 +206,27 @@
 		return out;
 	}
+
+	template< typename Iter >
+	const ast::Type * tupleFromTypes( Iter crnt, Iter end ) {
+		std::vector< ast::ptr< ast::Type > > types;
+		while ( crnt != end ) {
+			// it is guaranteed that a ttype variable will be bound to a flat tuple, so ensure
+			// that this results in a flat tuple
+			flatten( *crnt, types );
+
+			++crnt;
+		}
+
+
+		return new ast::TupleType{ std::move(types) };
+	}
+
+	inline const ast::Type * tupleFromTypes(
+		const std::vector< ast::ptr< ast::Type > > & tys
+	) {
+		return tupleFromTypes( tys.begin(), tys.end() );
+	}
+
+	
 
 	// in TypeEnvironment.cc
Index: tests/concurrent/.expect/ctor-check.txt
===================================================================
--- tests/concurrent/.expect/ctor-check.txt	(revision 8f1e03582aaeca10f66354d884763209e5fe44bc)
+++ tests/concurrent/.expect/ctor-check.txt	(revision ef1da0e2df5a222ca4e50f7e06a2b61f8ba6efcd)
@@ -2,5 +2,5 @@
 ?{}: function
 ... with parameters
-  this: lvalue reference to instance of struct Empty with body
+  this: mutex reference to instance of struct Empty with body
 ... returning nothing
  with body
