Index: src/AST/TypeEnvironment.hpp
===================================================================
--- src/AST/TypeEnvironment.hpp	(revision aba20d252ff0657f8a54ae4f5b2ff291161cfb08)
+++ src/AST/TypeEnvironment.hpp	(revision 9d5089e3f418f8ae4795697294bf00b26fc226a7)
@@ -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 aba20d252ff0657f8a54ae4f5b2ff291161cfb08)
+++ src/AST/porting.md	(revision 9d5089e3f418f8ae4795697294bf00b26fc226a7)
@@ -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/ResolvExpr/AlternativeFinder.cc
===================================================================
--- src/ResolvExpr/AlternativeFinder.cc	(revision aba20d252ff0657f8a54ae4f5b2ff291161cfb08)
+++ src/ResolvExpr/AlternativeFinder.cc	(revision 9d5089e3f418f8ae4795697294bf00b26fc226a7)
@@ -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 aba20d252ff0657f8a54ae4f5b2ff291161cfb08)
+++ src/ResolvExpr/Candidate.hpp	(revision 9d5089e3f418f8ae4795697294bf00b26fc226a7)
@@ -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 aba20d252ff0657f8a54ae4f5b2ff291161cfb08)
+++ src/ResolvExpr/CandidateFinder.cpp	(revision 9d5089e3f418f8ae4795697294bf00b26fc226a7)
@@ -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 aba20d252ff0657f8a54ae4f5b2ff291161cfb08)
+++ src/ResolvExpr/ConversionCost.cc	(revision 9d5089e3f418f8ae4795697294bf00b26fc226a7)
@@ -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 aba20d252ff0657f8a54ae4f5b2ff291161cfb08)
+++ src/ResolvExpr/PolyCost.cc	(revision 9d5089e3f418f8ae4795697294bf00b26fc226a7)
@@ -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 aba20d252ff0657f8a54ae4f5b2ff291161cfb08)
+++ src/ResolvExpr/SpecCost.cc	(revision 9d5089e3f418f8ae4795697294bf00b26fc226a7)
@@ -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 aba20d252ff0657f8a54ae4f5b2ff291161cfb08)
+++ src/ResolvExpr/typeops.h	(revision 9d5089e3f418f8ae4795697294bf00b26fc226a7)
@@ -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 );
 
