Index: src/AST/Convert.cpp
===================================================================
--- src/AST/Convert.cpp	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/AST/Convert.cpp	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -10,6 +10,6 @@
 // Created On       : Thu May 09 15::37::05 2019
 // Last Modified By : Andrew Beach
-// Last Modified On : Man Jun 10 11:51:00 2019
-// Update Count     : 11
+// Last Modified On : Mon Jun 17 16:44:00 2019
+// Update Count     : 12
 //
 
@@ -949,5 +949,5 @@
 	const ast::Expr * visit( const ast::TupleExpr * node ) override final {
 		auto expr = visitBaseExpr( node,
-			new UntypedTupleExpr(
+			new TupleExpr(
 				get<Expression>().acceptL(node->exprs)
 			)
@@ -1470,4 +1470,5 @@
 		decl->mangleName = old->mangleName;
 		decl->isDeleted  = old->isDeleted;
+		decl->asmName    = GET_ACCEPT_1(asmName, Expr);
 		decl->uniqueId   = old->uniqueId;
 		decl->extension  = old->extension;
@@ -1494,4 +1495,5 @@
 		decl->mangleName = old->mangleName;
 		decl->isDeleted  = old->isDeleted;
+		decl->asmName    = GET_ACCEPT_1(asmName, Expr);
 		decl->uniqueId   = old->uniqueId;
 		decl->extension  = old->extension;
Index: src/AST/TypeEnvironment.hpp
===================================================================
--- src/AST/TypeEnvironment.hpp	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/AST/TypeEnvironment.hpp	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -215,5 +215,5 @@
 std::ostream & operator<<( std::ostream & out, const TypeEnvironment & env );
 
-}
+} // namespace ast
 
 // Local Variables: //
Index: src/AST/porting.md
===================================================================
--- src/AST/porting.md	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/AST/porting.md	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -302,4 +302,7 @@
 * `ExplodedActual.h` => `ExplodedArg.hpp`
 
+`polyCost`
+* switched order of `env`, `symtab` parameters for better consistency
+
 [1] https://gcc.gnu.org/onlinedocs/gcc-9.1.0/gcc/Type-Attributes.html#Type-Attributes
 
Index: src/Makefile.in
===================================================================
--- src/Makefile.in	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/Makefile.in	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -239,5 +239,5 @@
 	$(am__objects_7) Tuples/TupleAssignment.$(OBJEXT) \
 	Tuples/TupleExpansion.$(OBJEXT) Tuples/Explode.$(OBJEXT) \
-	Validate/HandleAttributes.$(OBJEXT) \
+	Tuples/Tuples.$(OBJEXT) Validate/HandleAttributes.$(OBJEXT) \
 	Validate/FindSpecialDecls.$(OBJEXT)
 am_libdemangle_a_OBJECTS = $(am__objects_8)
@@ -270,5 +270,5 @@
 	Tuples/TupleAssignment.$(OBJEXT) \
 	Tuples/TupleExpansion.$(OBJEXT) Tuples/Explode.$(OBJEXT) \
-	Validate/HandleAttributes.$(OBJEXT) \
+	Tuples/Tuples.$(OBJEXT) Validate/HandleAttributes.$(OBJEXT) \
 	Validate/FindSpecialDecls.$(OBJEXT) \
 	Virtual/ExpandCasts.$(OBJEXT)
@@ -561,5 +561,5 @@
 	$(SRC_RESOLVEXPR) ResolvExpr/AlternativePrinter.cc \
 	$(SRC_SYMTAB) $(SRC_SYNTREE) Tuples/TupleAssignment.cc \
-	Tuples/TupleExpansion.cc Tuples/Explode.cc \
+	Tuples/TupleExpansion.cc Tuples/Explode.cc Tuples/Tuples.cc \
 	Validate/HandleAttributes.cc Validate/FindSpecialDecls.cc \
 	Virtual/ExpandCasts.cc
@@ -570,6 +570,6 @@
 	$(SRC_SYMTAB) SymTab/Demangle.cc $(SRC_SYNTREE) \
 	Tuples/TupleAssignment.cc Tuples/TupleExpansion.cc \
-	Tuples/Explode.cc Validate/HandleAttributes.cc \
-	Validate/FindSpecialDecls.cc
+	Tuples/Explode.cc Tuples/Tuples.cc \
+	Validate/HandleAttributes.cc Validate/FindSpecialDecls.cc
 MAINTAINERCLEANFILES = ${libdir}/${notdir ${cfa_cpplib_PROGRAMS}}
 MOSTLYCLEANFILES = Parser/lex.cc Parser/parser.cc Parser/parser.hh \
@@ -1030,4 +1030,6 @@
 	Tuples/$(DEPDIR)/$(am__dirstamp)
 Tuples/Explode.$(OBJEXT): Tuples/$(am__dirstamp) \
+	Tuples/$(DEPDIR)/$(am__dirstamp)
+Tuples/Tuples.$(OBJEXT): Tuples/$(am__dirstamp) \
 	Tuples/$(DEPDIR)/$(am__dirstamp)
 Validate/$(am__dirstamp):
@@ -1340,4 +1342,5 @@
 @AMDEP_TRUE@@am__include@ @am__quote@Tuples/$(DEPDIR)/TupleAssignment.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@Tuples/$(DEPDIR)/TupleExpansion.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@Tuples/$(DEPDIR)/Tuples.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@Validate/$(DEPDIR)/FindSpecialDecls.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@Validate/$(DEPDIR)/HandleAttributes.Po@am__quote@
Index: src/ResolvExpr/AlternativeFinder.cc
===================================================================
--- src/ResolvExpr/AlternativeFinder.cc	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/ResolvExpr/AlternativeFinder.cc	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -227,14 +227,4 @@
 	}
 
-	const ast::Expr * referenceToRvalueConversion( const ast::Expr * expr, Cost & cost ) {
-		if ( expr->result.as< ast::ReferenceType >() ) {
-			// cast away reference from expr
-			cost.incReference();
-			return new ast::CastExpr{ expr->location, expr, expr->result->stripReferences() };
-		}
-		
-		return expr;
-	}
-
 	template< typename InputIterator, typename OutputIterator >
 	void AlternativeFinder::findSubExprs( InputIterator begin, InputIterator end, OutputIterator out ) {
@@ -518,6 +508,6 @@
 	}
 
-	/// Unique identifier for matching expression resolutions to their requesting expression
-	UniqueId globalResnSlot = 0;
+	/// Unique identifier for matching expression resolutions to their requesting expression (located in CandidateFinder.cpp)
+	extern UniqueId globalResnSlot;
 
 	template< typename OutputIterator >
Index: src/ResolvExpr/Candidate.hpp
===================================================================
--- src/ResolvExpr/Candidate.hpp	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/ResolvExpr/Candidate.hpp	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -58,4 +58,9 @@
 
 	Candidate(
+		const ast::Expr * x, const ast::TypeEnvironment & e, const ast::OpenVarSet & o, 
+		const ast::AssertionSet & n, const Cost & c, const Cost & cvt = Cost::zero )
+	: expr( x ), cost( c ), cvtCost( cvt ), env( e ), open( o ), need( n.begin(), n.end() ) {}
+
+	Candidate(
 		const ast::Expr * x, ast::TypeEnvironment && e, ast::OpenVarSet && o,
 		ast::AssertionSet && n, const Cost & c, const Cost & cvt = Cost::zero )
Index: src/ResolvExpr/CandidateFinder.cpp
===================================================================
--- src/ResolvExpr/CandidateFinder.cpp	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/ResolvExpr/CandidateFinder.cpp	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -29,5 +29,5 @@
 #include "Resolver.h"
 #include "SatisfyAssertions.hpp"
-#include "typeops.h"              // for adjustExprType
+#include "typeops.h"              // for adjustExprType, conversionCost, polyCost, specCost
 #include "Unify.h"
 #include "AST/Expr.hpp"
@@ -44,6 +44,24 @@
 namespace ResolvExpr {
 
+using std::move;
+
+/// partner to move that copies any copyable type
+template<typename T>
+T copy( const T & x ) { return x; }
+
+const ast::Expr * referenceToRvalueConversion( const ast::Expr * expr, Cost & cost ) {
+	if ( expr->result.as< ast::ReferenceType >() ) {
+		// cast away reference from expr
+		cost.incReference();
+		return new ast::CastExpr{ expr->location, expr, expr->result->stripReferences() };
+	}
+	
+	return expr;
+}
+
+/// Unique identifier for matching expression resolutions to their requesting expression
+UniqueId globalResnSlot = 0;
+
 namespace {
-
 	/// First index is which argument, second is which alternative, third is which exploded element
 	using ExplodedArgs_new = std::deque< std::vector< ExplodedArg > >;
@@ -65,12 +83,473 @@
 	}
 
+	/// Computes conversion cost between two types
+	Cost computeConversionCost( 
+		const ast::Type * argType, const ast::Type * paramType, const ast::SymbolTable & symtab, 
+		const ast::TypeEnvironment & env 
+	) {
+		PRINT(
+			std::cerr << std::endl << "converting ";
+			ast::print( std::cerr, argType, 2 );
+			std::cerr << std::endl << " to ";
+			ast::print( std::cerr, paramType, 2 );
+			std::cerr << std::endl << "environment is: ";
+			ast::print( std::cerr, env, 2 );
+			std::cerr << std::endl;
+		)
+		Cost convCost = conversionCost( argType, paramType, symtab, env );
+		PRINT(
+			std::cerr << std::endl << "cost is " << convCost << std::endl;
+		)
+		if ( convCost == Cost::infinity ) return convCost;
+		convCost.incPoly( polyCost( paramType, symtab, env ) + polyCost( argType, symtab, env ) );
+		PRINT(
+			std::cerr << "cost with polycost is " << convCost << std::endl;
+		)
+		return convCost;
+	}
+
+	/// Computes conversion cost for a given expression to a given type
+	const ast::Expr * computeExpressionConversionCost( 
+		const ast::Expr * arg, const ast::Type * paramType, const ast::SymbolTable & symtab, const ast::TypeEnvironment & env, Cost & outCost 
+	) {
+		Cost convCost = computeConversionCost( arg->result, paramType, symtab, env );
+		outCost += convCost;
+
+		// If there is a non-zero conversion cost, ignoring poly cost, then the expression requires 
+		// conversion. Ignore poly cost for now, since this requires resolution of the cast to 
+		// infer parameters and this does not currently work for the reason stated below
+		Cost tmpCost = convCost;
+		tmpCost.incPoly( -tmpCost.get_polyCost() );
+		if ( tmpCost != Cost::zero ) {
+			ast::ptr< ast::Type > newType = paramType;
+			env.apply( newType );
+			return new ast::CastExpr{ arg->location, arg, newType };
+
+			// xxx - *should* be able to resolve this cast, but at the moment pointers are not 
+			// castable to zero_t, but are implicitly convertible. This is clearly inconsistent, 
+			// once this is fixed it should be possible to resolve the cast.
+			// xxx - this isn't working, it appears because type1 (parameter) is seen as widenable, 
+			// but it shouldn't be because this makes the conversion from DT* to DT* since 
+			// commontype(zero_t, DT*) is DT*, rather than nothing
+
+			// CandidateFinder finder{ symtab, env };
+			// finder.find( arg, ResolvMode::withAdjustment() );
+			// assertf( finder.candidates.size() > 0, 
+			// 	"Somehow castable expression failed to find alternatives." );
+			// assertf( finder.candidates.size() == 1, 
+			// 	"Somehow got multiple alternatives for known cast expression." );
+			// return finder.candidates.front()->expr;
+		}
+
+		return arg;
+	}
+
 	/// Computes conversion cost for a given candidate
 	Cost computeApplicationConversionCost( 
-		const CandidateRef & cand, const ast::SymbolTable & symtab 
+		CandidateRef cand, const ast::SymbolTable & symtab 
 	) {
-		#warning unimplemented
-		(void)cand; (void)symtab;
-		assert(false);
-		return Cost::infinity;
+		auto appExpr = cand->expr.strict_as< ast::ApplicationExpr >();
+		auto pointer = appExpr->func->result.strict_as< ast::PointerType >();
+		auto function = pointer->base.strict_as< ast::FunctionType >();
+
+		Cost convCost = Cost::zero;
+		const auto & params = function->params;
+		auto param = params.begin();
+		auto & args = appExpr->args;
+
+		for ( unsigned i = 0; i < args.size(); ++i ) {
+			const ast::Type * argType = args[i]->result;
+			PRINT(
+				std::cerr << "arg expression:" << std::endl;
+				ast::print( std::cerr, args[i], 2 );
+				std::cerr << "--- results are" << std::endl;
+				ast::print( std::cerr, argType, 2 );
+			)
+
+			if ( param == params.end() ) {
+				if ( function->isVarArgs ) {
+					convCost.incUnsafe();
+					PRINT( std::cerr << "end of params with varargs function: inc unsafe: " 
+						<< convCost << std::endl; ; )
+					// convert reference-typed expressions into value-typed expressions
+					cand->expr = ast::mutate_field_index( 
+						appExpr, &ast::ApplicationExpr::args, i, 
+						referenceToRvalueConversion( args[i], convCost ) );
+					continue;
+				} else return Cost::infinity;
+			}
+
+			if ( auto def = args[i].as< ast::DefaultArgExpr >() ) {
+				// Default arguments should be free - don't include conversion cost.
+				// Unwrap them here because they are not relevant to the rest of the system
+				cand->expr = ast::mutate_field_index( 
+					appExpr, &ast::ApplicationExpr::args, i, def->expr );
+				++param;
+				continue;
+			}
+
+			// mark conversion cost and also specialization cost of param type
+			const ast::Type * paramType = (*param)->get_type();
+			cand->expr = ast::mutate_field_index( 
+				appExpr, &ast::ApplicationExpr::args, i, 
+				computeExpressionConversionCost( 
+					args[i], paramType, symtab, cand->env, convCost ) );
+			convCost.decSpec( specCost( paramType ) );
+			++param;  // can't be in for-loop update because of the continue
+		}
+
+		if ( param != params.end() ) return Cost::infinity;
+
+		// specialization cost of return types can't be accounted for directly, it disables 
+		// otherwise-identical calls, like this example based on auto-newline in the I/O lib:
+		//
+		//   forall(otype OS) {
+		//     void ?|?(OS&, int);  // with newline
+		//     OS&  ?|?(OS&, int);  // no newline, always chosen due to more specialization
+		//   }
+
+		// mark type variable and specialization cost of forall clause
+		convCost.incVar( function->forall.size() );
+		for ( const ast::TypeDecl * td : function->forall ) {
+			convCost.decSpec( td->assertions.size() );
+		}
+
+		return convCost;
+	}
+
+	void makeUnifiableVars( 
+		const ast::ParameterizedType * type, ast::OpenVarSet & unifiableVars, 
+		ast::AssertionSet & need 
+	) {
+		for ( const ast::TypeDecl * tyvar : type->forall ) {
+			unifiableVars[ tyvar->name ] = ast::TypeDecl::Data{ tyvar };
+			for ( const ast::DeclWithType * assn : tyvar->assertions ) {
+				need[ assn ].isUsed = true;
+			}
+		}
+	}
+
+	/// Gets a default value from an initializer, nullptr if not present
+	const ast::ConstantExpr * getDefaultValue( const ast::Init * init ) {
+		if ( auto si = dynamic_cast< const ast::SingleInit * >( init ) ) {
+			if ( auto ce = si->value.as< ast::CastExpr >() ) {
+				return ce->arg.as< ast::ConstantExpr >();
+			} else {
+				return si->value.as< ast::ConstantExpr >();
+			}
+		}
+		return nullptr;
+	}
+
+	/// State to iteratively build a match of parameter expressions to arguments
+	struct ArgPack {
+		std::size_t parent;          ///< Index of parent pack
+		ast::ptr< ast::Expr > expr;  ///< The argument stored here
+		Cost cost;                   ///< The cost of this argument
+		ast::TypeEnvironment env;    ///< Environment for this pack
+		ast::AssertionSet need;      ///< Assertions outstanding for this pack
+		ast::AssertionSet have;      ///< Assertions found for this pack
+		ast::OpenVarSet open;        ///< Open variables for this pack
+		unsigned nextArg;            ///< Index of next argument in arguments list
+		unsigned tupleStart;         ///< Number of tuples that start at this index
+		unsigned nextExpl;           ///< Index of next exploded element
+		unsigned explAlt;            ///< Index of alternative for nextExpl > 0
+
+		ArgPack()
+		: parent( 0 ), expr(), cost( Cost::zero ), env(), need(), have(), open(), nextArg( 0 ), 
+		  tupleStart( 0 ), nextExpl( 0 ), explAlt( 0 ) {}
+		
+		ArgPack( 
+			const ast::TypeEnvironment & env, const ast::AssertionSet & need, 
+			const ast::AssertionSet & have, const ast::OpenVarSet & open )
+		: parent( 0 ), expr(), cost( Cost::zero ), env( env ), need( need ), have( have ), 
+		  open( open ), nextArg( 0 ), tupleStart( 0 ), nextExpl( 0 ), explAlt( 0 ) {}
+		
+		ArgPack(
+			std::size_t parent, const ast::Expr * expr, ast::TypeEnvironment && env, 
+			ast::AssertionSet && need, ast::AssertionSet && have, ast::OpenVarSet && open, 
+			unsigned nextArg, unsigned tupleStart = 0, Cost cost = Cost::zero, 
+			unsigned nextExpl = 0, unsigned explAlt = 0 )
+		: parent(parent), expr( expr ), cost( cost ), env( move( env ) ), need( move( need ) ),
+		  have( move( have ) ), open( move( open ) ), nextArg( nextArg ), tupleStart( tupleStart ),
+		  nextExpl( nextExpl ), explAlt( explAlt ) {}
+		
+		ArgPack(
+			const ArgPack & o, ast::TypeEnvironment && env, ast::AssertionSet && need, 
+			ast::AssertionSet && have, ast::OpenVarSet && open, unsigned nextArg, Cost added )
+		: parent( o.parent ), expr( o.expr ), cost( o.cost + added ), env( move( env ) ), 
+		  need( move( need ) ), have( move( have ) ), open( move( open ) ), nextArg( nextArg ), 
+		  tupleStart( o.tupleStart ), nextExpl( 0 ), explAlt( 0 ) {}
+		
+		/// true if this pack is in the middle of an exploded argument
+		bool hasExpl() const { return nextExpl > 0; }
+
+		/// Gets the list of exploded candidates for this pack
+		const ExplodedArg & getExpl( const ExplodedArgs_new & args ) const {
+			return args[ nextArg-1 ][ explAlt ];
+		}
+		
+		/// Ends a tuple expression, consolidating the appropriate args
+		void endTuple( const std::vector< ArgPack > & packs ) {
+			// add all expressions in tuple to list, summing cost
+			std::deque< const ast::Expr * > exprs;
+			const ArgPack * pack = this;
+			if ( expr ) { exprs.emplace_front( expr ); }
+			while ( pack->tupleStart == 0 ) {
+				pack = &packs[pack->parent];
+				exprs.emplace_front( pack->expr );
+				cost += pack->cost;
+			}
+			// reset pack to appropriate tuple
+			std::vector< ast::ptr< ast::Expr > > exprv( exprs.begin(), exprs.end() );
+			expr = new ast::TupleExpr{ expr->location, move( exprv ) };
+			tupleStart = pack->tupleStart - 1;
+			parent = pack->parent;
+		}
+	};
+
+	/// Instantiates an argument to match a parameter, returns false if no matching results left
+	bool instantiateArgument( 
+		const ast::Type * paramType, const ast::Init * init, const ExplodedArgs_new & args, 
+		std::vector< ArgPack > & results, std::size_t & genStart, const ast::SymbolTable & symtab, 
+		unsigned nTuples = 0 
+	) {
+		if ( auto tupleType = dynamic_cast< const ast::TupleType * >( paramType ) ) {
+			// paramType is a TupleType -- group args into a TupleExpr
+			++nTuples;
+			for ( const ast::Type * type : *tupleType ) {
+				// xxx - dropping initializer changes behaviour from previous, but seems correct
+				// ^^^ need to handle the case where a tuple has a default argument
+				if ( ! instantiateArgument( 
+					type, nullptr, args, results, genStart, symtab, nTuples ) ) return false;
+				nTuples = 0;
+			}
+			// re-constitute tuples for final generation
+			for ( auto i = genStart; i < results.size(); ++i ) {
+				results[i].endTuple( results );
+			}
+			return true;
+		} else if ( const ast::TypeInstType * ttype = Tuples::isTtype( paramType ) ) {
+			// paramType is a ttype, consumes all remaining arguments
+			
+			// completed tuples; will be spliced to end of results to finish
+			std::vector< ArgPack > finalResults{};
+
+			// iterate until all results completed
+			std::size_t genEnd;
+			++nTuples;
+			do {
+				genEnd = results.size();
+
+				// add another argument to results
+				for ( std::size_t i = genStart; i < genEnd; ++i ) {
+					unsigned nextArg = results[i].nextArg;
+					
+					// use next element of exploded tuple if present
+					if ( results[i].hasExpl() ) {
+						const ExplodedArg & expl = results[i].getExpl( args );
+
+						unsigned nextExpl = results[i].nextExpl + 1;
+						if ( nextExpl == expl.exprs.size() ) { nextExpl = 0; }
+
+						results.emplace_back(
+							i, expl.exprs[ results[i].nextExpl ], copy( results[i].env ),
+							copy( results[i].need ), copy( results[i].have ), 
+							copy( results[i].open ), nextArg, nTuples, Cost::zero, nextExpl,
+							results[i].explAlt );
+
+						continue;
+					}
+
+					// finish result when out of arguments
+					if ( nextArg >= args.size() ) {
+						ArgPack newResult{
+							results[i].env, results[i].need, results[i].have, results[i].open };
+						newResult.nextArg = nextArg;
+						const ast::Type * argType = nullptr;
+
+						if ( nTuples > 0 || ! results[i].expr ) {
+							// first iteration or no expression to clone,
+							// push empty tuple expression
+							newResult.parent = i;
+							std::vector< ast::ptr< ast::Expr > > emptyList;
+							newResult.expr = 
+								new ast::TupleExpr{ CodeLocation{}, move( emptyList ) };
+							argType = newResult.expr->result;
+						} else {
+							// clone result to collect tuple
+							newResult.parent = results[i].parent;
+							newResult.cost = results[i].cost;
+							newResult.tupleStart = results[i].tupleStart;
+							newResult.expr = results[i].expr;
+							argType = newResult.expr->result;
+
+							if ( results[i].tupleStart > 0 && Tuples::isTtype( argType ) ) {
+								// the case where a ttype value is passed directly is special,
+								// e.g. for argument forwarding purposes
+								// xxx - what if passing multiple arguments, last of which is
+								//       ttype?
+								// xxx - what would happen if unify was changed so that unifying
+								//       tuple
+								// types flattened both before unifying lists? then pass in
+								// TupleType (ttype) below.
+								--newResult.tupleStart;
+							} else {
+								// collapse leftover arguments into tuple
+								newResult.endTuple( results );
+								argType = newResult.expr->result;
+							}
+						}
+
+						// check unification for ttype before adding to final
+						if ( 
+							unify( 
+								ttype, argType, newResult.env, newResult.need, newResult.have,
+								newResult.open, symtab ) 
+						) {
+							finalResults.emplace_back( move( newResult ) );
+						}
+
+						continue;
+					}
+
+					// add each possible next argument
+					for ( std::size_t j = 0; j < args[nextArg].size(); ++j ) {
+						const ExplodedArg & expl = args[nextArg][j];
+
+						// fresh copies of parent parameters for this iteration
+						ast::TypeEnvironment env = results[i].env;
+						ast::OpenVarSet open = results[i].open;
+
+						env.addActual( expl.env, open );
+
+						// skip empty tuple arguments by (nearly) cloning parent into next gen
+						if ( expl.exprs.empty() ) {
+							results.emplace_back(
+								results[i], move( env ), copy( results[i].need ), 
+								copy( results[i].have ), move( open ), nextArg + 1, expl.cost );
+							
+							continue;
+						}
+
+						// add new result
+						results.emplace_back(
+							i, expl.exprs.front(), move( env ), copy( results[i].need ), 
+							copy( results[i].have ), move( open ), nextArg + 1, nTuples, 
+							expl.cost, expl.exprs.size() == 1 ? 0 : 1, j );
+					}
+				}
+
+				// reset for next round
+				genStart = genEnd;
+				nTuples = 0;
+			} while ( genEnd != results.size() );
+
+			// splice final results onto results
+			for ( std::size_t i = 0; i < finalResults.size(); ++i ) {
+				results.emplace_back( move( finalResults[i] ) );
+			}
+			return ! finalResults.empty();
+		}
+
+		// iterate each current subresult
+		std::size_t genEnd = results.size();
+		for ( std::size_t i = genStart; i < genEnd; ++i ) {
+			unsigned nextArg = results[i].nextArg;
+
+			// use remainder of exploded tuple if present
+			if ( results[i].hasExpl() ) {
+				const ExplodedArg & expl = results[i].getExpl( args );
+				const ast::Expr * expr = expl.exprs[ results[i].nextExpl ];
+
+				ast::TypeEnvironment env = results[i].env;
+				ast::AssertionSet need = results[i].need, have = results[i].have;
+				ast::OpenVarSet open = results[i].open;
+
+				const ast::Type * argType = expr->result;
+
+				PRINT(
+					std::cerr << "param type is ";
+					ast::print( std::cerr, paramType );
+					std::cerr << std::endl << "arg type is ";
+					ast::print( std::cerr, argType );
+					std::cerr << std::endl;
+				)
+
+				if ( unify( paramType, argType, env, need, have, open, symtab ) ) {
+					unsigned nextExpl = results[i].nextExpl + 1;
+					if ( nextExpl == expl.exprs.size() ) { nextExpl = 0; }
+
+					results.emplace_back(
+						i, expr, move( env ), move( need ), move( have ), move( open ), nextArg, 
+						nTuples, Cost::zero, nextExpl, results[i].explAlt );
+				}
+
+				continue;
+			}
+
+			// use default initializers if out of arguments
+			if ( nextArg >= args.size() ) {
+				if ( const ast::ConstantExpr * cnst = getDefaultValue( init ) ) {
+					ast::TypeEnvironment env = results[i].env;
+					ast::AssertionSet need = results[i].need, have = results[i].have;
+					ast::OpenVarSet open = results[i].open;
+
+					if ( unify( paramType, cnst->result, env, need, have, open, symtab ) ) {
+						results.emplace_back(
+							i, new ast::DefaultArgExpr{ cnst->location, cnst }, move( env ), 
+							move( need ), move( have ), move( open ), nextArg, nTuples );
+					}
+				}
+
+				continue;
+			}
+
+			// Check each possible next argument
+			for ( std::size_t j = 0; j < args[nextArg].size(); ++j ) {
+				const ExplodedArg & expl = args[nextArg][j];
+
+				// fresh copies of parent parameters for this iteration
+				ast::TypeEnvironment env = results[i].env;
+				ast::AssertionSet need = results[i].need, have = results[i].have;
+				ast::OpenVarSet open = results[i].open;
+
+				env.addActual( expl.env, open );
+
+				// skip empty tuple arguments by (nearly) cloning parent into next gen
+				if ( expl.exprs.empty() ) {
+					results.emplace_back(
+						results[i], move( env ), move( need ), move( have ), move( open ), 
+						nextArg + 1, expl.cost );
+					
+					continue;
+				}
+
+				// consider only first exploded arg
+				const ast::Expr * expr = expl.exprs.front();
+				const ast::Type * argType = expr->result;
+
+				PRINT(
+					std::cerr << "param type is ";
+					ast::print( std::cerr, paramType );
+					std::cerr << std::endl << "arg type is ";
+					ast::print( std::cerr, argType );
+					std::cerr << std::endl;
+				)
+
+				// attempt to unify types
+				if ( unify( paramType, argType, env, need, have, open, symtab ) ) {
+					// add new result
+					results.emplace_back(
+						i, expr, move( env ), move( need ), move( have ), move( open ), 
+						nextArg + 1, nTuples, expl.cost, expl.exprs.size() == 1 ? 0 : 1, j );
+				}
+			}
+		}
+
+		// reset for next parameter
+		genStart = genEnd;
+
+		return genEnd != results.size();
 	}
 
@@ -99,4 +578,54 @@
 		}
 
+		/// Set up candidate assertions for inference
+		void inferParameters( CandidateRef & newCand, CandidateList & out ) {
+			// Set need bindings for any unbound assertions
+			UniqueId crntResnSlot = 0; // matching ID for this expression's assertions
+			for ( auto & assn : newCand->need ) {
+				// skip already-matched assertions
+				if ( assn.second.resnSlot != 0 ) continue;
+				// assign slot for expression if needed
+				if ( crntResnSlot == 0 ) { crntResnSlot = ++globalResnSlot; }
+				// fix slot to assertion
+				assn.second.resnSlot = crntResnSlot;
+			}
+			// pair slot to expression
+			if ( crntResnSlot != 0 ) {
+				newCand->expr.get_and_mutate()->inferred.resnSlots().emplace_back( crntResnSlot );
+			}
+
+			// add to output list; assertion satisfaction will occur later
+			out.emplace_back( newCand );
+		}
+
+		/// Completes a function candidate with arguments located
+		void validateFunctionCandidate( 
+			const CandidateRef & func, ArgPack & result, const std::vector< ArgPack > & results, 
+			CandidateList & out 
+		) {
+			ast::ApplicationExpr * appExpr = 
+				new ast::ApplicationExpr{ func->expr->location, func->expr };
+			// sum cost and accumulate arguments
+			std::deque< const ast::Expr * > args;
+			Cost cost = func->cost;
+			const ArgPack * pack = &result;
+			while ( pack->expr ) {
+				args.emplace_front( pack->expr );
+				cost += pack->cost;
+				pack = &results[pack->parent];
+			}
+			std::vector< ast::ptr< ast::Expr > > vargs( args.begin(), args.end() );
+			appExpr->args = move( vargs );
+			// build and validate new candidate
+			auto newCand = 
+				std::make_shared<Candidate>( appExpr, result.env, result.open, result.need, cost );
+			PRINT(
+				std::cerr << "instantiate function success: " << appExpr << std::endl;
+				std::cerr << "need assertions:" << std::endl;
+				ast::print( std::cerr, result.need, 2 );
+			)
+			inferParameters( newCand, out );
+		}
+
 		/// Builds a list of candidates for a function, storing them in out
 		void makeFunctionCandidates(
@@ -104,7 +633,108 @@
 			const ExplodedArgs_new & args, CandidateList & out
 		) {
-			#warning unimplemented
-			(void)func; (void)funcType; (void)args; (void)out;
-			assert(false);
+			ast::OpenVarSet funcOpen;
+			ast::AssertionSet funcNeed, funcHave;
+			ast::TypeEnvironment funcEnv{ func->env };
+			makeUnifiableVars( funcType, funcOpen, funcNeed );
+			// add all type variables as open variables now so that those not used in the parameter 
+			// list are still considered open
+			funcEnv.add( funcType->forall );
+
+			if ( targetType && ! targetType->isVoid() && ! funcType->returns.empty() ) {
+				// attempt to narrow based on expected target type
+				const ast::Type * returnType = funcType->returns.front()->get_type();
+				if ( ! unify( 
+					returnType, targetType, funcEnv, funcNeed, funcHave, funcOpen, symtab ) 
+				) {
+					// unification failed, do not pursue this candidate
+					return;
+				}
+			}
+
+			// iteratively build matches, one parameter at a time
+			std::vector< ArgPack > results;
+			results.emplace_back( funcEnv, funcNeed, funcHave, funcOpen );
+			std::size_t genStart = 0;
+
+			for ( const ast::DeclWithType * param : funcType->params ) {
+				auto obj = strict_dynamic_cast< const ast::ObjectDecl * >( param );
+				// Try adding the arguments corresponding to the current parameter to the existing 
+				// matches
+				if ( ! instantiateArgument( 
+					obj->type, obj->init, args, results, genStart, symtab ) ) return;
+			}
+
+			if ( funcType->isVarArgs ) {
+				// append any unused arguments to vararg pack
+				std::size_t genEnd;
+				do {
+					genEnd = results.size();
+
+					// iterate results
+					for ( std::size_t i = genStart; i < genEnd; ++i ) {
+						unsigned nextArg = results[i].nextArg;
+
+						// use remainder of exploded tuple if present
+						if ( results[i].hasExpl() ) {
+							const ExplodedArg & expl = results[i].getExpl( args );
+
+							unsigned nextExpl = results[i].nextExpl + 1;
+							if ( nextExpl == expl.exprs.size() ) { nextExpl = 0; }
+
+							results.emplace_back(
+								i, expl.exprs[ results[i].nextExpl ], copy( results[i].env ),
+								copy( results[i].need ), copy( results[i].have ),
+								copy( results[i].open ), nextArg, 0, Cost::zero, nextExpl,
+								results[i].explAlt );
+
+							continue;
+						}
+
+						// finish result when out of arguments
+						if ( nextArg >= args.size() ) {
+							validateFunctionCandidate( func, results[i], results, out );
+
+							continue;
+						}
+
+						// add each possible next argument
+						for ( std::size_t j = 0; j < args[nextArg].size(); ++j ) {
+							const ExplodedArg & expl = args[nextArg][j];
+
+							// fresh copies of parent parameters for this iteration
+							ast::TypeEnvironment env = results[i].env;
+							ast::OpenVarSet open = results[i].open;
+
+							env.addActual( expl.env, open );
+
+							// skip empty tuple arguments by (nearly) cloning parent into next gen
+							if ( expl.exprs.empty() ) {
+								results.emplace_back(
+									results[i], move( env ), copy( results[i].need ), 
+									copy( results[i].have ), move( open ), nextArg + 1, 
+									expl.cost );
+
+								continue;
+							}
+
+							// add new result
+							results.emplace_back(
+								i, expl.exprs.front(), move( env ), copy( results[i].need ),
+								copy( results[i].have ), move( open ), nextArg + 1, 0, expl.cost, 
+								expl.exprs.size() == 1 ? 0 : 1, j );
+						}
+					}
+
+					genStart = genEnd;
+				} while( genEnd != results.size() );
+			} else {
+				// filter out the results that don't use all the arguments
+				for ( std::size_t i = genStart; i < results.size(); ++i ) {
+					ArgPack & result = results[i];
+					if ( ! result.hasExpl() && result.nextArg >= args.size() ) {
+						validateFunctionCandidate( func, result, results, out );
+					}
+				}
+			}
 		}
 
@@ -189,5 +819,5 @@
 					funcE.emplace_back( *func, symtab );
 				}
-				argExpansions.emplace_front( std::move( funcE ) );
+				argExpansions.emplace_front( move( funcE ) );
 
 				for ( const CandidateRef & op : opFinder ) {
@@ -233,8 +863,8 @@
 				if ( cvtCost != Cost::infinity ) {
 					withFunc->cvtCost = cvtCost;
-					candidates.emplace_back( std::move( withFunc ) );
-				}
-			}
-			found = std::move( candidates );
+					candidates.emplace_back( move( withFunc ) );
+				}
+			}
+			found = move( candidates );
 
 			// use a new list so that candidates are not examined by addAnonConversions twice
@@ -376,6 +1006,5 @@
 						new ast::LogicalExpr{ 
 							logicalExpr->location, r1->expr, r2->expr, logicalExpr->isAnd },
-						std::move( env ), std::move( open ), std::move( need ), 
-						r1->cost + r2->cost );
+						move( env ), move( open ), move( need ), r1->cost + r2->cost );
 				}
 			}
@@ -512,6 +1141,6 @@
 
 				addCandidate(
-					new ast::TupleExpr{ tupleExpr->location, std::move( exprs ) }, 
-					std::move( env ), std::move( open ), std::move( need ), sumCost( subs ) );
+					new ast::TupleExpr{ tupleExpr->location, move( exprs ) }, 
+					move( env ), move( open ), move( need ), sumCost( subs ) );
 			}
 		}
@@ -628,5 +1257,5 @@
 			cand->env.applyFree( newResult );
 			cand->expr = ast::mutate_field(
-				cand->expr.get(), &ast::Expr::result, std::move( newResult ) );
+				cand->expr.get(), &ast::Expr::result, move( newResult ) );
 			
 			out.emplace_back( cand );
@@ -666,5 +1295,5 @@
 
 		// reset candidates
-		candidates = std::move( satisfied );
+		candidates = move( satisfied );
 	}
 
@@ -690,5 +1319,5 @@
 
 		auto oldsize = candidates.size();
-		candidates = std::move( pruned );
+		candidates = move( pruned );
 
 		PRINT(
Index: src/ResolvExpr/ConversionCost.cc
===================================================================
--- src/ResolvExpr/ConversionCost.cc	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/ResolvExpr/ConversionCost.cc	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -85,5 +85,8 @@
 			});
 		} else {
-			PassVisitor<ConversionCost> converter( dest, indexer, env, conversionCost );
+			PassVisitor<ConversionCost> converter( 
+				dest, indexer, env, 
+				(Cost (*)(Type*, Type*, const SymTab::Indexer&, const TypeEnvironment&))
+					conversionCost );
 			src->accept( converter );
 			if ( converter.pass.get_cost() == Cost::infinity ) {
@@ -134,5 +137,8 @@
 			} else {
 				PRINT( std::cerr << "reference to rvalue conversion" << std::endl; )
-				PassVisitor<ConversionCost> converter( dest, indexer, env, conversionCost );
+				PassVisitor<ConversionCost> converter( 
+					dest, indexer, env, 
+					(Cost (*)(Type*, Type*, const SymTab::Indexer&, const TypeEnvironment&))
+						conversionCost );
 				src->accept( converter );
 				return converter.pass.get_cost();
@@ -482,4 +488,14 @@
 		} // if
 	}
+
+	Cost conversionCost( 
+		const ast::Type * src, const ast::Type * dst, const ast::SymbolTable & symtab, 
+		const ast::TypeEnvironment & env
+	) {
+		#warning unimplemented
+		(void)src; (void)dst; (void)symtab; (void)env;
+		assert(false);
+		return Cost::zero;
+	}
 } // namespace ResolvExpr
 
Index: src/ResolvExpr/PolyCost.cc
===================================================================
--- src/ResolvExpr/PolyCost.cc	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/ResolvExpr/PolyCost.cc	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -14,4 +14,7 @@
 //
 
+#include "AST/SymbolTable.hpp"
+#include "AST/Type.hpp"
+#include "AST/TypeEnvironment.hpp"
 #include "Common/PassVisitor.h"
 #include "SymTab/Indexer.h"   // for Indexer
@@ -54,4 +57,13 @@
 	}
 
+	int polyCost( 
+		const ast::Type * type, const ast::SymbolTable & symtab, const ast::TypeEnvironment & env 
+	) {
+		#warning unimplemented
+		(void)type; (void)symtab; (void)env;
+		assert(false);
+		return 0;
+	}
+
 } // namespace ResolvExpr
 
Index: src/ResolvExpr/SpecCost.cc
===================================================================
--- src/ResolvExpr/SpecCost.cc	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/ResolvExpr/SpecCost.cc	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -17,4 +17,5 @@
 #include <list>
 
+#include "AST/Type.hpp"
 #include "Common/PassVisitor.h"
 #include "SynTree/Declaration.h"
@@ -111,4 +112,11 @@
 		return counter.pass.get_count();
 	}
+
+	int specCost( const ast::Type * ty ) {
+		#warning unimplmented
+		(void)ty;
+		assert(false);
+		return 0;
+	}
 } // namespace ResolvExpr
 
Index: src/ResolvExpr/typeops.h
===================================================================
--- src/ResolvExpr/typeops.h	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/ResolvExpr/typeops.h	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -81,4 +81,7 @@
 	// in ConversionCost.cc
 	Cost conversionCost( Type *src, Type *dest, const SymTab::Indexer &indexer, const TypeEnvironment &env );
+	Cost conversionCost( 
+		const ast::Type * src, const ast::Type * dst, const ast::SymbolTable & symtab, 
+		const ast::TypeEnvironment & env );
 
 	// in AlternativeFinder.cc
@@ -127,7 +130,10 @@
 	// in PolyCost.cc
 	int polyCost( Type *type, const TypeEnvironment &env, const SymTab::Indexer &indexer );
+	int polyCost( 
+		const ast::Type * type, const ast::SymbolTable & symtab, const ast::TypeEnvironment & env );
 
 	// in SpecCost.cc
 	int specCost( Type *type );
+	int specCost( const ast::Type * type );
 
 	// in Occurs.cc
@@ -146,4 +152,5 @@
 	// in AlternativeFinder.cc
 	void referenceToRvalueConversion( Expression *& expr, Cost & cost );
+	// in CandidateFinder.cpp
 	const ast::Expr * referenceToRvalueConversion( const ast::Expr * expr, Cost & cost );
 
Index: src/Tuples/Explode.cc
===================================================================
--- src/Tuples/Explode.cc	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/Tuples/Explode.cc	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -9,7 +9,7 @@
 // Author           : Rob Schluntz
 // Created On       : Wed Nov 9 13:12:24 2016
-// Last Modified By : Rob Schluntz
-// Last Modified On : Wed Nov 9 13:20:24 2016
-// Update Count     : 2
+// Last Modified By : Andrew Beach
+// Last Modified On : Wed Jun 12 16:40:00 2016
+// Update Count     : 3
 //
 
@@ -106,4 +106,87 @@
 		return expr;
 	}
+
+namespace {
+
+// Remove one level of reference from a reference type.
+const ast::Type * getReferenceBase( const ast::Type * t ) {
+	if ( const ast::ReferenceType * ref = dynamic_cast< const ast::ReferenceType * >( t ) ) {
+		return ref->base;
+	} else {
+		assertf( false, "getReferenceBase for non-ref: %s", toString( t ).c_str() );
+		return nullptr;
+	}
+}
+
+struct CastExploderCore {
+	bool castAdded = false;
+	bool foundUniqueExpr = false;
+	const ast::Expr * applyCast( const ast::Expr * expr, bool first = true ) {
+		// On tuple push the cast down.
+		if ( const ast::TupleExpr * tupleExpr = dynamic_cast< const ast::TupleExpr * >( expr ) ) {
+			foundUniqueExpr = true;
+			std::vector< ast::ptr< ast::Expr > > exprs;
+			for ( const ast::Expr * expr : tupleExpr->exprs ) {
+				exprs.emplace_back( applyCast( expr, false ) );
+				//exprs.emplace_back( ast::ptr< ast::Expr >( applyCast( expr, false ) ) );
+			}
+			if ( first ) {
+				castAdded = true;
+				const ast::Expr * tuple = new ast::TupleExpr(
+					tupleExpr->location, std::move( exprs ) );
+				return new ast::CastExpr( tuple->location,
+					tuple, new ast::ReferenceType( tuple->result.get(), ast::CV::Qualifiers() ) );
+			} else {
+				return new ast::TupleExpr( tupleExpr->location, std::move( exprs ) );
+			}
+		}
+		if ( dynamic_cast< const ast::ReferenceType * >( expr->result.get() ) ) {
+			return expr;
+		} else {
+			castAdded = true;
+			return new ast::CastExpr( expr->location, expr,
+				new ast::ReferenceType( expr->result, ast::CV::Qualifiers() ) );
+		}
+	}
+
+	const ast::Expr * postmutate( const ast::UniqueExpr * node ) {
+		// move cast into unique expr so that the unique expr has type T& rather than
+		// type T. In particular, this transformation helps with generating the
+		// correct code for reference-cast member tuple expressions, since the result
+		// should now be a tuple of references rather than a reference to a tuple.
+		// Still, this code is a bit awkward, and could use some improvement.
+		const ast::UniqueExpr * newNode = new ast::UniqueExpr( node->location,
+				applyCast( node->expr ), node->id );
+		if ( castAdded ) {
+			// if a cast was added by applyCast, then unique expr now has one more layer of reference
+			// than it had coming into this function. To ensure types still match correctly, need to cast
+			//  to reference base so that outer expressions are still correct.
+			castAdded = false;
+			const ast::Type * newType = getReferenceBase( newNode->result );
+			return new ast::CastExpr( newNode->location, node, newType );
+		}
+		return newNode;
+	}
+
+	const ast::Expr * postmutate( const ast::TupleIndexExpr * tupleExpr ) {
+		// tuple index expr needs to be rebuilt to ensure that the type of the
+		// field is consistent with the type of the tuple expr, since the field
+		// may have changed from type T to T&.
+		return new ast::TupleIndexExpr( tupleExpr->location, tupleExpr->tuple, tupleExpr->index );
+	}
+};
+
+} // namespace
+
+const ast::Expr * distributeReference( const ast::Expr * expr ) {
+	ast::Pass<CastExploderCore> exploder;
+	expr = expr->accept( exploder );
+	if ( ! exploder.pass.foundUniqueExpr ) {
+		expr = new ast::CastExpr( expr->location, expr,
+			new ast::ReferenceType( expr->result, ast::CV::Qualifiers() ) );
+	}
+	return expr;
+}
+
 } // namespace Tuples
 
Index: src/Tuples/Explode.h
===================================================================
--- src/Tuples/Explode.h	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/Tuples/Explode.h	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -9,7 +9,7 @@
 // Author           : Rob Schluntz
 // Created On       : Wed Nov 9 13:12:24 2016
-// Last Modified By : Peter A. Buhr
-// Last Modified On : Sat Jul 22 09:55:16 2017
-// Update Count     : 3
+// Last Modified By : Andrew Beach
+// Last Modified On : Mon Jun 17 14:36:00 2019
+// Update Count     : 4
 //
 
@@ -138,19 +138,99 @@
 	}
 
+const ast::Expr * distributeReference( const ast::Expr * );
+
+/// Append candidate to an OutputIterator of Candidates.
+template<typename OutputIterator>
+void append( OutputIterator out, const ast::Expr * expr, const ast::TypeEnvironment & env,
+		const ast::OpenVarSet & open, const ast::AssertionList & need,
+		const ResolvExpr::Cost & cost, const ResolvExpr::Cost & cvtCost ) {
+	ast::TypeEnvironment copyEnv = env;
+	ast::OpenVarSet copyOpen = open;
+	ast::AssertionSet set;
+	mergeAssertionSet( set, need );
+	*out++ = std::make_shared<ResolvExpr::Candidate>( expr, std::move( copyEnv ),
+		std::move( copyOpen ), std::move( set ), cost, cvtCost );
+}
+
+/// Append candidate to an ExplodedArg.
+static inline void append( ResolvExpr::ExplodedArg& ea, const ast::Expr * expr,
+		const ast::TypeEnvironment&, const ast::OpenVarSet&,
+		const ast::AssertionList&, const ResolvExpr::Cost&, const ResolvExpr::Cost& ) {
+	// I'm not sure why most of the arguments are unused. But they were in the old version.
+	ea.exprs.emplace_back( expr );
+}
+
+/// Check if the expression is a cast to a reference type, return it if it is.
+static inline const ast::CastExpr * isReferenceCast( const ast::Expr * expr ) {
+	if ( const ast::CastExpr * cast = dynamic_cast< const ast::CastExpr * >( expr ) ) {
+		if ( dynamic_cast< const ast::ReferenceType * >( cast->result.get() ) ) {
+			return cast;
+		}
+	}
+	return nullptr;
+}
+
+/// helper function (indirectely) used by explode
+template< typename Output >
+void explodeRecursive(
+	const ast::CastExpr * expr, const ResolvExpr::Candidate & arg,
+	const ast::SymbolTable & symtab, Output && out
+) {
+}
+
 /// helper function used by explode
 template< typename Output >
-void explodeUnique( 
-	const ast::Expr * expr, const ResolvExpr::Candidate & arg, const ast::SymbolTable & symtab, 
-	Output && out, bool isTupleAssign
+void explodeUnique(
+	const ast::ptr< ast::Expr > & expr, const ResolvExpr::Candidate & arg,
+	const ast::SymbolTable & symtab, Output && out, bool isTupleAssign
 ) {
-	#warning unimplemented
-	(void)expr; (void)arg; (void)symtab; (void)out; (void)isTupleAssign;
-	assert(false);
+	// Tuple assignment can use a faster method if it is cast. Uses recursive exploding.
+	if ( isTupleAssign ) if ( const ast::CastExpr * castExpr = isReferenceCast( expr ) ) {
+		ResolvExpr::CandidateList candidates;
+		explodeUnique( castExpr->arg, arg, symtab, back_inserter( candidates ), true );
+		for ( ResolvExpr::CandidateRef & cand : candidates ) {
+			// Distribute the reference cast over all components of the candidate.
+			append( std::forward<Output>(out), distributeReference( cand->expr ), cand->env,
+				cand->open, cand->need, cand->cost, cand->cvtCost );
+		}
+		return;
+	}
+	const ast::Type * res = expr->result->stripReferences();
+	if ( const ast::TupleType * tupleType = dynamic_cast< const ast::TupleType * >( res ) ) {
+		if ( const ast::ptr< ast::TupleExpr > & tupleExpr = expr.as< ast::TupleExpr >() ) {
+			// Open the tuple expr and continue on its components.
+			for ( const ast::Expr * expr : tupleExpr->exprs ) {
+				explodeUnique( expr, arg, symtab, std::forward<Output>(out), isTupleAssign );
+			}
+		} else {
+			ast::ptr< ast::Expr > local = expr;
+			// Expressions which may have side effects require a single unique instance.
+			if ( Tuples::maybeImpureIgnoreUnique( local ) ) {
+				local = new ast::UniqueExpr( local->location, local );
+			}
+			// Cast a reference away to a value-type to allow further explosion.
+			if ( dynamic_cast< const ast::ReferenceType *>( local->result.get() ) ) {
+				local = new ast::CastExpr( local->location, local, tupleType );
+			}
+			// Now we have to go across the tuple via indexing.
+			for ( unsigned int i = 0 ; i < tupleType->size() ; ++i ) {
+				ast::TupleIndexExpr * idx = new ast::TupleIndexExpr( local->location, local, i );
+				explodeUnique( idx, arg, symtab, std::forward<Output>(out), isTupleAssign );
+				// TODO: We need more input to figure out the exact lifetimes of these types.
+				// delete idx;
+			}
+			// delete local;
+		}
+	} else {
+		// For atomic/non-tuple types, no explosion is used.
+		append( std::forward<Output>(out), expr, arg.env, arg.open, arg.need, arg.cost,
+			arg.cvtCost );
+	}
 }
 
 /// expands a tuple-valued candidate into multiple candidates, each with a non-tuple type
 template< typename Output >
-void explode( 
-	const ResolvExpr::Candidate & arg, const ast::SymbolTable & symtab, Output && out, 
+void explode(
+	const ResolvExpr::Candidate & arg, const ast::SymbolTable & symtab, Output && out,
 	bool isTupleAssign = false
 ) {
Index: src/Tuples/Tuples.cc
===================================================================
--- src/Tuples/Tuples.cc	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
+++ src/Tuples/Tuples.cc	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -0,0 +1,71 @@
+//
+// 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.
+//
+// Tuples.h --
+//
+// Author           : Andrew Beach
+// Created On       : Mon Jun 17 14:41:00 2019
+// Last Modified By : Andrew Beach
+// Last Modified On : Wed Jun 12 15:43:00 2019
+// Update Count     : 0
+//
+
+#include "Tuples.h"
+
+#include "AST/Pass.hpp"
+#include "AST/LinkageSpec.hpp"
+#include "InitTweak/InitTweak.h"
+
+namespace Tuples {
+
+namespace {
+	/// Determines if impurity (read: side-effects) may exist in a piece of code. Currently gives
+	/// a very crude approximation, wherein any function call expression means the code may be
+	/// impure.
+    struct ImpurityDetector : public ast::WithShortCircuiting {
+		ImpurityDetector( bool ignoreUnique ) : ignoreUnique( ignoreUnique ) {}
+		bool maybeImpure = false;
+		bool ignoreUnique;
+
+		void previsit( ast::ApplicationExpr const * appExpr ) {
+			visit_children = false;
+			if ( ast::DeclWithType const * function = InitTweak::getFunction( appExpr ) ) {
+				if ( function->linkage == ast::Linkage::Intrinsic
+						&& ( function->name == "*?" || function->name == "?[?]" ) ) {
+					visit_children = true;
+					return;
+				}
+			}
+			maybeImpure = true;
+		}
+		void previsit( ast::UntypedExpr const * ) {
+			maybeImpure = true; visit_children = false;
+		}
+		void previsit( ast::UniqueExpr const * ) {
+			if ( ignoreUnique ) {
+				visit_children = false;
+			}
+		}
+	};
+
+	bool detectImpurity( const ast::Expr * expr, bool ignoreUnique ) {
+		ast::Pass<ImpurityDetector> detector( ignoreUnique );
+		expr->accept( detector );
+		return detector.pass.maybeImpure;
+	}
+} // namespace
+
+bool maybeImpureIgnoreUnique( const ast::Expr * expr ) {
+	return detectImpurity( expr, true );
+}
+
+} // namespace Tuples
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/Tuples/Tuples.h
===================================================================
--- src/Tuples/Tuples.h	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/Tuples/Tuples.h	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -9,7 +9,7 @@
 // Author           : Rodolfo G. Esteves
 // Created On       : Mon May 18 07:44:20 2015
-// Last Modified By : Peter A. Buhr
-// Last Modified On : Sat Jul 22 09:55:00 2017
-// Update Count     : 16
+// Last Modified By : Andrew Beach
+// Last Modified On : Wed Jun 12 10:39:00 2017
+// Update Count     : 17
 //
 
@@ -57,6 +57,8 @@
 	bool maybeImpure( Expression * expr );
 
-	/// returns true if the expression may contain side-effect, ignoring the presence of unique expressions.
+	/// Returns true if the expression may contain side-effect,
+	/// ignoring the presence of unique expressions.
 	bool maybeImpureIgnoreUnique( Expression * expr );
+	bool maybeImpureIgnoreUnique( const ast::Expr * expr );
 } // namespace Tuples
 
Index: src/Tuples/module.mk
===================================================================
--- src/Tuples/module.mk	(revision 8220e50baa9ed90210df57d5737893230b168a01)
+++ src/Tuples/module.mk	(revision 07ca4dd665b38bcebea6e05f0f80c7ba4934b76b)
@@ -15,4 +15,6 @@
 ###############################################################################
 
-SRC += Tuples/TupleAssignment.cc Tuples/TupleExpansion.cc Tuples/Explode.cc
-SRCDEMANGLE += Tuples/TupleAssignment.cc Tuples/TupleExpansion.cc Tuples/Explode.cc
+SRC += Tuples/TupleAssignment.cc Tuples/TupleExpansion.cc Tuples/Explode.cc \
+	Tuples/Tuples.cc
+SRCDEMANGLE += Tuples/TupleAssignment.cc Tuples/TupleExpansion.cc Tuples/Explode.cc \
+	Tuples/Tuples.cc
