Index: src/AST/Print.hpp
===================================================================
--- src/AST/Print.hpp	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/AST/Print.hpp	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -16,6 +16,6 @@
 #pragma once
 
-#include <iosfwd>
-#include <utility> // for forward
+#include <iostream>
+#include <utility>   // for forward
 
 #include "AST/Node.hpp"
@@ -32,6 +32,14 @@
 void printShort( std::ostream & os, const ast::Decl * node, Indenter indent = {} );
 
-inline void printShort( std::ostream & os, const ast::Decl * node, unsigned int indent ) {
-    printShort( os, node, Indenter{ indent } );
+/// Print a collection of items
+template< typename Coll >
+void printAll( std::ostream & os, const Coll & c, Indenter indent = {} ) {
+    for ( const auto & i : c ) {
+        if ( ! i ) continue;
+        
+        os << indent;
+        print( os, i, indent );
+        os << std::endl;
+    }
 }
 
Index: src/AST/porting.md
===================================================================
--- src/AST/porting.md	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/AST/porting.md	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -299,4 +299,7 @@
 * `openVars` => `open`
 
+`ExplodedActual` => `ExplodedArg`
+* `ExplodedActual.h` => `ExplodedArg.hpp`
+
 [1] https://gcc.gnu.org/onlinedocs/gcc-9.1.0/gcc/Type-Attributes.html#Type-Attributes
 
Index: src/Makefile.in
===================================================================
--- src/Makefile.in	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/Makefile.in	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -195,4 +195,5 @@
 	ResolvExpr/CurrentObject.$(OBJEXT) \
 	ResolvExpr/ExplodedActual.$(OBJEXT) \
+	ResolvExpr/ExplodedArg.$(OBJEXT) \
 	ResolvExpr/FindOpenVars.$(OBJEXT) ResolvExpr/Occurs.$(OBJEXT) \
 	ResolvExpr/PolyCost.$(OBJEXT) \
@@ -633,4 +634,5 @@
       ResolvExpr/CurrentObject.cc \
       ResolvExpr/ExplodedActual.cc \
+      ResolvExpr/ExplodedArg.cpp \
       ResolvExpr/FindOpenVars.cc \
       ResolvExpr/Occurs.cc \
@@ -895,4 +897,6 @@
 ResolvExpr/ExplodedActual.$(OBJEXT): ResolvExpr/$(am__dirstamp) \
 	ResolvExpr/$(DEPDIR)/$(am__dirstamp)
+ResolvExpr/ExplodedArg.$(OBJEXT): ResolvExpr/$(am__dirstamp) \
+	ResolvExpr/$(DEPDIR)/$(am__dirstamp)
 ResolvExpr/FindOpenVars.$(OBJEXT): ResolvExpr/$(am__dirstamp) \
 	ResolvExpr/$(DEPDIR)/$(am__dirstamp)
@@ -1277,4 +1281,5 @@
 @AMDEP_TRUE@@am__include@ @am__quote@ResolvExpr/$(DEPDIR)/CurrentObject.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@ResolvExpr/$(DEPDIR)/ExplodedActual.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@ResolvExpr/$(DEPDIR)/ExplodedArg.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@ResolvExpr/$(DEPDIR)/FindOpenVars.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@ResolvExpr/$(DEPDIR)/Occurs.Po@am__quote@
Index: src/ResolvExpr/AdjustExprType.cc
===================================================================
--- src/ResolvExpr/AdjustExprType.cc	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/ResolvExpr/AdjustExprType.cc	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -5,5 +5,5 @@
 // file "LICENCE" distributed with Cforall.
 //
-// AdjustExprType.cc --
+// AdjustExprType_old.cc --
 //
 // Author           : Richard C. Bilson
@@ -14,4 +14,9 @@
 //
 
+#include "AST/Node.hpp"
+#include "AST/Pass.hpp"
+#include "AST/SymbolTable.hpp"
+#include "AST/Type.hpp"
+#include "AST/TypeEnvironment.hpp"
 #include "Common/PassVisitor.h"
 #include "SymTab/Indexer.h"       // for Indexer
@@ -22,7 +27,9 @@
 
 namespace ResolvExpr {
-	class AdjustExprType : public WithShortCircuiting {
-	  public:
-		AdjustExprType( const TypeEnvironment & env, const SymTab::Indexer & indexer );
+
+namespace {
+	class AdjustExprType_old final : public WithShortCircuiting {
+		public:
+		AdjustExprType_old( const TypeEnvironment & env, const SymTab::Indexer & indexer );
 		void premutate( VoidType * ) { visit_children = false; }
 		void premutate( BasicType * ) { visit_children = false; }
@@ -44,26 +51,14 @@
 		Type * postmutate( TypeInstType *aggregateUseType );
 
-	  private:
+		private:
 		const TypeEnvironment & env;
 		const SymTab::Indexer & indexer;
 	};
 
-	void adjustExprType( Type *&type, const TypeEnvironment &env, const SymTab::Indexer &indexer ) {
-		PassVisitor<AdjustExprType> adjuster( env, indexer );
-		Type *newType = type->acceptMutator( adjuster );
-		type = newType;
-	}
-
-	void adjustExprType( Type *& type ) {
-		TypeEnvironment env;
-		SymTab::Indexer indexer;
-		adjustExprType( type, env, indexer );
-	}
-
-	AdjustExprType::AdjustExprType( const TypeEnvironment &env, const SymTab::Indexer &indexer )
+	AdjustExprType_old::AdjustExprType_old( const TypeEnvironment &env, const SymTab::Indexer &indexer )
 		: env( env ), indexer( indexer ) {
 	}
 
-	Type * AdjustExprType::postmutate( ArrayType * arrayType ) {
+	Type * AdjustExprType_old::postmutate( ArrayType * arrayType ) {
 		PointerType *pointerType = new PointerType{ arrayType->get_qualifiers(), arrayType->base };
 		arrayType->base = nullptr;
@@ -72,9 +67,9 @@
 	}
 
-	Type * AdjustExprType::postmutate( FunctionType * functionType ) {
+	Type * AdjustExprType_old::postmutate( FunctionType * functionType ) {
 		return new PointerType{ Type::Qualifiers(), functionType };
 	}
 
-	Type * AdjustExprType::postmutate( TypeInstType * typeInst ) {
+	Type * AdjustExprType_old::postmutate( TypeInstType * typeInst ) {
 		if ( const EqvClass* eqvClass = env.lookup( typeInst->get_name() ) ) {
 			if ( eqvClass->data.kind == TypeDecl::Ftype ) {
@@ -90,4 +85,74 @@
 		return typeInst;
 	}
+} // anonymous namespace
+
+void adjustExprType( Type *&type, const TypeEnvironment &env, const SymTab::Indexer &indexer ) {
+	PassVisitor<AdjustExprType_old> adjuster( env, indexer );
+	Type *newType = type->acceptMutator( adjuster );
+	type = newType;
+}
+
+void adjustExprType( Type *& type ) {
+	TypeEnvironment env;
+	SymTab::Indexer indexer;
+	adjustExprType( type, env, indexer );
+}
+
+namespace {
+	struct AdjustExprType_new final : public ast::WithShortCircuiting {
+		const ast::TypeEnvironment & tenv;
+		const ast::SymbolTable & symtab;
+
+		AdjustExprType_new( const ast::TypeEnvironment & e, const ast::SymbolTable & syms )
+		: tenv( e ), symtab( syms ) {}
+
+		void premutate( const ast::VoidType * ) { visit_children = false; }
+		void premutate( const ast::BasicType * ) { visit_children = false; }
+		void premutate( const ast::PointerType * ) { visit_children = false; }
+		void premutate( const ast::ArrayType * ) { visit_children = false; }
+		void premutate( const ast::FunctionType * ) { visit_children = false; }
+		void premutate( const ast::StructInstType * ) { visit_children = false; }
+		void premutate( const ast::UnionInstType * ) { visit_children = false; }
+		void premutate( const ast::EnumInstType * ) { visit_children = false; }
+		void premutate( const ast::TraitInstType * ) { visit_children = false; }
+		void premutate( const ast::TypeInstType * ) { visit_children = false; }
+		void premutate( const ast::TupleType * ) { visit_children = false; }
+		void premutate( const ast::VarArgsType * ) { visit_children = false; }
+		void premutate( const ast::ZeroType * ) { visit_children = false; }
+		void premutate( const ast::OneType * ) { visit_children = false; }
+
+		const ast::Type * postmutate( const ast::ArrayType * at ) {
+			return new ast::PointerType{ at->base, at->qualifiers };
+		}
+
+		const ast::Type * postmutate( const ast::FunctionType * ft ) {
+			return new ast::PointerType{ ft };
+		}
+
+		const ast::Type * postmutate( const ast::TypeInstType * inst ) {
+			// replace known function-type-variables with pointer-to-function
+			if ( const ast::EqvClass * eqvClass = tenv.lookup( inst->name ) ) {
+				if ( eqvClass->data.kind == ast::TypeVar::Ftype ) {
+					return new ast::PointerType{ inst };
+				}
+			} else if ( const ast::NamedTypeDecl * ntDecl = symtab.lookupType( inst->name ) ) {
+				if ( auto tyDecl = dynamic_cast< const ast::TypeDecl * >( ntDecl ) ) {
+					if ( tyDecl->kind == ast::TypeVar::Ftype ) {
+						return new ast::PointerType{ inst };
+					}
+				}
+			}
+			return inst;
+		}
+	};
+} // anonymous namespace
+
+const ast::Type * adjustExprType( 
+	const ast::Type * type, const ast::TypeEnvironment & env, const ast::SymbolTable & symtab 
+) {
+	ast::Pass<AdjustExprType_new> adjuster{ env, symtab };
+	return type->accept( adjuster );
+}
+
 } // namespace ResolvExpr
 
Index: src/ResolvExpr/AlternativeFinder.cc
===================================================================
--- src/ResolvExpr/AlternativeFinder.cc	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/ResolvExpr/AlternativeFinder.cc	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -29,4 +29,5 @@
 #include "AlternativeFinder.h"
 #include "AST/Expr.hpp"
+#include "AST/SymbolTable.hpp"
 #include "AST/Type.hpp"
 #include "Common/SemanticError.h"  // for SemanticError
@@ -115,5 +116,5 @@
 		/// Finds matching alternatives for a function, given a set of arguments
 		template<typename OutputIterator>
-		void makeFunctionAlternatives( const Alternative &func, FunctionType *funcType, const ExplodedArgs& args, OutputIterator out );
+		void makeFunctionAlternatives( const Alternative &func, FunctionType *funcType, const ExplodedArgs_old& args, OutputIterator out );
 		/// Sets up parameter inference for an output alternative
 		template< typename OutputIterator >
@@ -592,5 +593,5 @@
 
 		/// Gets the list of exploded alternatives for this pack
-		const ExplodedActual& getExpl( const ExplodedArgs& args ) const {
+		const ExplodedActual& getExpl( const ExplodedArgs_old& args ) const {
 			return args[nextArg-1][explAlt];
 		}
@@ -616,5 +617,5 @@
 	/// Instantiates an argument to match a formal, returns false if no results left
 	bool instantiateArgument( Type* formalType, Initializer* initializer,
-			const ExplodedArgs& args, std::vector<ArgPack>& results, std::size_t& genStart,
+			const ExplodedArgs_old& args, std::vector<ArgPack>& results, std::size_t& genStart,
 			const SymTab::Indexer& indexer, unsigned nTuples = 0 ) {
 		if ( TupleType * tupleType = dynamic_cast<TupleType*>( formalType ) ) {
@@ -888,5 +889,5 @@
 	template<typename OutputIterator>
 	void AlternativeFinder::Finder::makeFunctionAlternatives( const Alternative &func,
-			FunctionType *funcType, const ExplodedArgs &args, OutputIterator out ) {
+			FunctionType *funcType, const ExplodedArgs_old &args, OutputIterator out ) {
 		OpenVarSet funcOpenVars;
 		AssertionSet funcNeed, funcHave;
@@ -1020,5 +1021,5 @@
 
 		// pre-explode arguments
-		ExplodedArgs argExpansions;
+		ExplodedArgs_old argExpansions;
 		argExpansions.reserve( argAlternatives.size() );
 
Index: src/ResolvExpr/AlternativeFinder.h
===================================================================
--- src/ResolvExpr/AlternativeFinder.h	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/ResolvExpr/AlternativeFinder.h	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -37,5 +37,5 @@
 	/// First index is which argument, second index is which alternative for that argument,
 	/// third index is which exploded element of that alternative
-	using ExplodedArgs = std::vector< std::vector< ExplodedActual > >;
+	using ExplodedArgs_old = std::vector< std::vector< ExplodedActual > >;
 
 	class AlternativeFinder {
Index: src/ResolvExpr/Candidate.hpp
===================================================================
--- src/ResolvExpr/Candidate.hpp	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/ResolvExpr/Candidate.hpp	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -30,4 +30,9 @@
 	/// A list of unresolved assertions
 	using AssertionList = std::vector<AssertionSet::value_type>;
+
+	/// Convenience to merge AssertionList into AssertionSet
+	static inline void mergeAssertionSet( AssertionSet & dst, const AssertionList & src ) {
+		for ( const auto & s : src ) { dst.emplace( s ); }
+	}
 }
 
@@ -42,4 +47,19 @@
 	ast::OpenVarSet open;      ///< Open variables for environment
 	ast::AssertionList need;   ///< Assertions which need to be resolved
+
+	Candidate() : expr(), cost( Cost::zero ), cvtCost( Cost::zero ), env(), open(), need() {}
+	
+	Candidate( const ast::Expr * x, const ast::TypeEnvironment & e )
+	: expr( x ), cost( Cost::zero ), cvtCost( Cost::zero ), env( e ), open(), need() {}
+
+	Candidate( const Candidate & o, const ast::Expr * x )
+	: expr( x ), cost( o.cost ), cvtCost( Cost::zero ), env( o.env ), open( o.open ), 
+	  need( o.need ) {}
+	
+	Candidate( 
+		const ast::Expr * x, ast::TypeEnvironment && e, ast::OpenVarSet && o, 
+		ast::AssertionSet && n, const Cost & c )
+	: expr( x ), cost( c ), cvtCost( Cost::zero ), env( std::move( e ) ), open( std::move( o ) ), 
+	  need( n.begin(), n.end() ) {}
 };
 
@@ -49,4 +69,31 @@
 /// List of candidates
 using CandidateList = std::vector< CandidateRef >;
+
+/// Splice src after dst, clearing src
+static inline void splice( CandidateList & dst, CandidateList & src ) {
+	dst.reserve( dst.size() + src.size() );
+	for ( CandidateRef & r : src ) { dst.emplace_back( std::move( r ) ); }
+	src.clear();
+}
+
+/// Splice src before dst
+static inline void spliceBegin( CandidateList & dst, CandidateList & src ) {
+	splice( src, dst );
+	dst.swap( src );
+}
+
+/// Sum the cost of a list of candidates
+static inline Cost sumCost( const CandidateList & candidates ) {
+	Cost total = Cost::zero;
+	for ( const CandidateRef & r : candidates ) { total += r->cost; }
+	return total;
+}
+
+/// Holdover behaviour from old `findMinCost` -- xxx -- can maybe be eliminated?
+static inline void promoteCvtCost( CandidateList & candidates ) {
+	for ( CandidateRef & r : candidates ) {
+		r->cost = r->cvtCost;
+	}
+}
 
 void print( std::ostream & os, const Candidate & cand, Indenter indent = {} );
Index: src/ResolvExpr/CandidateFinder.cpp
===================================================================
--- src/ResolvExpr/CandidateFinder.cpp	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/ResolvExpr/CandidateFinder.cpp	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -16,12 +16,27 @@
 #include "CandidateFinder.hpp"
 
+#include <deque>
+#include <iterator>               // for back_inserter
 #include <sstream>
+#include <string>
+#include <unordered_map>
+#include <vector>
 
 #include "Candidate.hpp"
 #include "CompilationState.h"
+#include "Cost.h"
+#include "ExplodedArg.hpp"
+#include "Resolver.h"
 #include "SatisfyAssertions.hpp"
+#include "typeops.h"              // for adjustExprType
+#include "Unify.h"
 #include "AST/Expr.hpp"
 #include "AST/Node.hpp"
 #include "AST/Pass.hpp"
+#include "AST/Print.hpp"
+#include "AST/SymbolTable.hpp"
+#include "AST/Type.hpp"
+#include "SymTab/Mangler.h"
+#include "Tuples/Tuples.h"        // for handleTupleAssignment
 
 #define PRINT( text ) if ( resolvep ) { text }
@@ -31,7 +46,36 @@
 namespace {
 
+	/// First index is which argument, second is which alternative, third is which exploded element
+	using ExplodedArgs_new = std::deque< std::vector< ExplodedArg > >;
+
+	/// Returns a list of alternatives with the minimum cost in the given list
+	CandidateList findMinCost( const CandidateList & candidates ) {
+		CandidateList out;
+		Cost minCost = Cost::infinity;
+		for ( const CandidateRef & r : candidates ) {
+			if ( r->cost < minCost ) {
+				minCost = r->cost;
+				out.clear();
+				out.emplace_back( r );
+			} else if ( r->cost == minCost ) {
+				out.emplace_back( r );
+			}
+		}
+		return out;
+	}
+
+	/// Computes conversion cost for a given candidate
+	Cost computeApplicationConversionCost( 
+		const CandidateRef & cand, const ast::SymbolTable & symtab 
+	) {
+		#warning unimplemented
+		(void)cand; (void)symtab;
+		assert(false);
+		return Cost::infinity;
+	}
+
 	/// Actually visits expressions to find their candidate interpretations
-	struct Finder {
-		CandidateFinder & candFinder;
+	struct Finder final : public ast::WithShortCircuiting {
+		CandidateFinder & selfFinder;
 		const ast::SymbolTable & symtab;
 		CandidateList & candidates;
@@ -40,8 +84,481 @@
 
 		Finder( CandidateFinder & f )
-		: candFinder( f ), symtab( f.symtab ), candidates( f.candidates ), tenv( f.env ), 
+		: selfFinder( f ), symtab( f.symtab ), candidates( f.candidates ), tenv( f.env ), 
 		  targetType( f.targetType ) {}
 		
-		#warning unimplemented
+		void previsit( const ast::Node * ) { visit_children = false; }
+
+		/// Convenience to add candidate to list
+		template<typename... Args>
+		void addCandidate( Args &&... args ) {
+			candidates.emplace_back( new Candidate{ std::forward<Args>( args )... } );
+		}
+
+		void postvisit( const ast::ApplicationExpr * applicationExpr ) {
+			addCandidate( applicationExpr, tenv );
+		}
+
+		/// Builds a list of candidates for a function, storing them in out
+		void makeFunctionCandidates(
+			const CandidateRef & func, const ast::FunctionType * funcType, 
+			const ExplodedArgs_new & args, CandidateList & out
+		) {
+			#warning unimplemented
+			(void)func; (void)funcType; (void)args; (void)out;
+			assert(false);
+		}
+
+		/// Adds implicit struct-conversions to the alternative list
+		void addAnonConversions( const CandidateRef & cand ) {
+			#warning unimplemented
+			(void)cand;
+			assert(false);
+		}
+
+		void postvisit( const ast::UntypedExpr * untypedExpr ) {
+			CandidateFinder funcFinder{ symtab, tenv };
+			funcFinder.find( untypedExpr->func, ResolvMode::withAdjustment() );
+			// short-circuit if no candidates
+			if ( funcFinder.candidates.empty() ) return;
+
+			std::vector< CandidateFinder > argCandidates = 
+				selfFinder.findSubExprs( untypedExpr->args );
+			
+			// take care of possible tuple assignments
+			// if not tuple assignment, handled as normal function call
+			Tuples::handleTupleAssignment( selfFinder, untypedExpr, argCandidates );
+
+			// find function operators
+			ast::ptr< ast::Expr > opExpr = new ast::NameExpr{ untypedExpr->location, "?()" };
+			CandidateFinder opFinder{ symtab, tenv };
+			// okay if there aren't any function operations
+			opFinder.find( opExpr, ResolvMode::withoutFailFast() );
+			PRINT(
+				std::cerr << "known function ops:" << std::endl;
+				print( std::cerr, opFinder.candidates, 1 );
+			)
+
+			// pre-explode arguments
+			ExplodedArgs_new argExpansions;
+			for ( const CandidateFinder & args : argCandidates ) {
+				argExpansions.emplace_back();
+				auto & argE = argExpansions.back();
+				for ( const CandidateRef & arg : args ) { argE.emplace_back( *arg, symtab ); }
+			}
+
+			// Find function matches
+			CandidateList found;
+			SemanticErrorException errors;
+			for ( CandidateRef & func : funcFinder ) {
+				try {
+					PRINT(
+						std::cerr << "working on alternative:" << std::endl;
+						print( std::cerr, *func, 2 );
+					)
+
+					// check if the type is a pointer to function
+					const ast::Type * funcResult = func->expr->result->stripReferences();
+					if ( auto pointer = dynamic_cast< const ast::PointerType * >( funcResult ) ) {
+						if ( auto function = pointer->base.as< ast::FunctionType >() ) {
+							CandidateRef newFunc{ new Candidate{ *func } };
+							newFunc->expr = 
+								referenceToRvalueConversion( newFunc->expr, newFunc->cost );
+							makeFunctionCandidates( newFunc, function, argExpansions, found );
+						}
+					} else if ( 
+						auto inst = dynamic_cast< const ast::TypeInstType * >( funcResult ) 
+					) {
+						if ( const ast::EqvClass * clz = func->env.lookup( inst->name ) ) {
+							if ( auto function = clz->bound.as< ast::FunctionType >() ) {
+								CandidateRef newFunc{ new Candidate{ *func } };
+								newFunc->expr = 
+									referenceToRvalueConversion( newFunc->expr, newFunc->cost );
+								makeFunctionCandidates( newFunc, function, argExpansions, found );
+							}
+						}
+					}
+				} catch ( SemanticErrorException & e ) { errors.append( e ); }
+			}
+
+			// Find matches on function operators `?()`
+			if ( ! opFinder.candidates.empty() ) {
+				// add exploded function alternatives to front of argument list
+				std::vector< ExplodedArg > funcE;
+				funcE.reserve( funcFinder.candidates.size() );
+				for ( const CandidateRef & func : funcFinder ) { 
+					funcE.emplace_back( *func, symtab );
+				}
+				argExpansions.emplace_front( std::move( funcE ) );
+
+				for ( const CandidateRef & op : opFinder ) {
+					try {
+						// check if type is pointer-to-function
+						const ast::Type * opResult = op->expr->result->stripReferences();
+						if ( auto pointer = dynamic_cast< const ast::PointerType * >( opResult ) ) {
+							if ( auto function = pointer->base.as< ast::FunctionType >() ) {
+								CandidateRef newOp{ new Candidate{ *op} };
+								newOp->expr = 
+									referenceToRvalueConversion( newOp->expr, newOp->cost );
+								makeFunctionCandidates( newOp, function, argExpansions, found );
+							}
+						}
+					} catch ( SemanticErrorException & e ) { errors.append( e ); }
+				}
+			}
+
+			// Implement SFINAE; resolution errors are only errors if there aren't any non-error 
+			// candidates
+			if ( found.empty() && ! errors.isEmpty() ) { throw errors; }
+
+			// Compute conversion costs
+			for ( CandidateRef & withFunc : found ) {
+				Cost cvtCost = computeApplicationConversionCost( withFunc, symtab );
+
+				PRINT(
+					auto appExpr = withFunc->expr.strict_as< ast::ApplicationExpr >();
+					auto pointer = appExpr->func->result.strict_as< ast::PointerType >();
+					auto function = pointer->base.strict_as< ast::FunctionType >();
+					
+					std::cerr << "Case +++++++++++++ " << appExpr->func << std::endl;
+					std::cerr << "parameters are:" << std::endl;
+					ast::printAll( std::cerr, function->params, 2 );
+					std::cerr << "arguments are:" << std::endl;
+					ast::printAll( std::cerr, appExpr->args, 2 );
+					std::cerr << "bindings are:" << std::endl;
+					ast::print( std::cerr, withFunc->env, 2 );
+					std::cerr << "cost is: " << withFunc->cost << std::endl;
+					std::cerr << "cost of conversion is:" << cvtCost << std::endl;
+				)
+
+				if ( cvtCost != Cost::infinity ) {
+					withFunc->cvtCost = cvtCost;
+					candidates.emplace_back( std::move( withFunc ) );
+				}
+			}
+			found = std::move( candidates );
+
+			// use a new list so that candidates are not examined by addAnonConversions twice
+			CandidateList winners = findMinCost( found );
+			promoteCvtCost( winners );
+
+			// function may return a struct/union value, in which case we need to add candidates 
+			// for implicit conversions to each of the anonymous members, which must happen after 
+			// `findMinCost`, since anon conversions are never the cheapest
+			for ( const CandidateRef & c : winners ) {
+				addAnonConversions( c );
+			}
+			spliceBegin( candidates, winners );
+
+			if ( candidates.empty() && targetType && ! targetType->isVoid() ) {
+				// If resolution is unsuccessful with a target type, try again without, since it 
+				// will sometimes succeed when it wouldn't with a target type binding.
+				// For example:
+				//   forall( otype T ) T & ?[]( T *, ptrdiff_t );
+				//   const char * x = "hello world";
+				//   unsigned char ch = x[0];
+				// Fails with simple return type binding (xxx -- check this!) as follows:
+				// * T is bound to unsigned char
+				// * (x: const char *) is unified with unsigned char *, which fails
+				// xxx -- fix this better
+				targetType = nullptr;
+				postvisit( untypedExpr );
+			}
+		}
+
+		/// true if expression is an lvalue
+		static bool isLvalue( const ast::Expr * x ) {
+			return x->result && ( x->result->is_lvalue() || x->result.as< ast::ReferenceType >() );
+		}
+
+		void postvisit( const ast::AddressExpr * addressExpr ) {
+			CandidateFinder finder{ symtab, tenv };
+			finder.find( addressExpr->arg );
+			for ( CandidateRef & r : finder.candidates ) {
+				if ( ! isLvalue( r->expr ) ) continue;
+				addCandidate( *r, new ast::AddressExpr{ addressExpr->location, r->expr } );
+			}
+		}
+
+		void postvisit( const ast::LabelAddressExpr * labelExpr ) {
+			addCandidate( labelExpr, tenv );
+		}
+
+		void postvisit( const ast::CastExpr * castExpr ) {
+			#warning unimplemented
+			(void)castExpr;
+			assert(false);
+		}
+
+		void postvisit( const ast::VirtualCastExpr * castExpr ) {
+			assertf( castExpr->result, "Implicit virtual cast targets not yet supported." );
+			CandidateFinder finder{ symtab, tenv };
+			// don't prune here, all alternatives guaranteed to have same type
+			finder.find( castExpr->arg, ResolvMode::withoutPrune() );
+			for ( CandidateRef & r : finder.candidates ) {
+				addCandidate( 
+					*r, new ast::VirtualCastExpr{ castExpr->location, r->expr, castExpr->result } );
+			}
+		}
+
+		void postvisit( const ast::UntypedMemberExpr * memberExpr ) {
+			#warning unimplemented
+			(void)memberExpr;
+			assert(false);
+		}
+
+		void postvisit( const ast::MemberExpr * memberExpr ) {
+			addCandidate( memberExpr, tenv );
+		}
+
+		void postvisit( const ast::NameExpr * variableExpr ) {
+			#warning unimplemented
+			(void)variableExpr;
+			assert(false);
+		}
+
+		void postvisit( const ast::VariableExpr * variableExpr ) {
+			// not sufficient to just pass `variableExpr` here, type might have changed since
+			// creation
+			addCandidate( 
+				new ast::VariableExpr{ variableExpr->location, variableExpr->var }, tenv );
+		}
+
+		void postvisit( const ast::ConstantExpr * constantExpr ) {
+			addCandidate( constantExpr, tenv );
+		}
+
+		void postvisit( const ast::SizeofExpr * sizeofExpr ) {
+			#warning unimplemented
+			(void)sizeofExpr;
+			assert(false);
+		}
+
+		void postvisit( const ast::AlignofExpr * alignofExpr ) {
+			#warning unimplemented
+			(void)alignofExpr;
+			assert(false);
+		}
+
+		void postvisit( const ast::UntypedOffsetofExpr * offsetofExpr ) {
+			#warning unimplemented
+			(void)offsetofExpr;
+			assert(false);
+		}
+
+		void postvisit( const ast::OffsetofExpr * offsetofExpr ) {
+			addCandidate( offsetofExpr, tenv );
+		}
+
+		void postvisit( const ast::OffsetPackExpr * offsetPackExpr ) {
+			addCandidate( offsetPackExpr, tenv );
+		}
+
+		void postvisit( const ast::LogicalExpr * logicalExpr ) {
+			CandidateFinder finder1{ symtab, tenv };
+			finder1.find( logicalExpr->arg1, ResolvMode::withAdjustment() );
+			if ( finder1.candidates.empty() ) return;
+
+			CandidateFinder finder2{ symtab, tenv };
+			finder2.find( logicalExpr->arg2, ResolvMode::withAdjustment() );
+			if ( finder2.candidates.empty() ) return;
+
+			for ( const CandidateRef & r1 : finder1.candidates ) {
+				for ( const CandidateRef & r2 : finder2.candidates ) {
+					ast::TypeEnvironment env{ r1->env };
+					env.simpleCombine( r2->env );
+					ast::OpenVarSet open{ r1->open };
+					mergeOpenVars( open, r2->open );
+					ast::AssertionSet need;
+					mergeAssertionSet( need, r1->need );
+					mergeAssertionSet( need, r2->need );
+
+					addCandidate(
+						new ast::LogicalExpr{ 
+							logicalExpr->location, r1->expr, r2->expr, logicalExpr->isAnd },
+						std::move( env ), std::move( open ), std::move( need ), 
+						r1->cost + r2->cost );
+				}
+			}
+		}
+
+		void postvisit( const ast::ConditionalExpr * conditionalExpr ) {
+			// candidates for condition
+			CandidateFinder finder1{ symtab, tenv };
+			finder1.find( conditionalExpr->arg1, ResolvMode::withAdjustment() );
+			if ( finder1.candidates.empty() ) return;
+
+			// candidates for true result
+			CandidateFinder finder2{ symtab, tenv };
+			finder2.find( conditionalExpr->arg2, ResolvMode::withAdjustment() );
+			if ( finder2.candidates.empty() ) return;
+
+			// candidates for false result
+			CandidateFinder finder3{ symtab, tenv };
+			finder3.find( conditionalExpr->arg3, ResolvMode::withAdjustment() );
+			if ( finder3.candidates.empty() ) return;
+
+			for ( const CandidateRef & r1 : finder1.candidates ) {
+				for ( const CandidateRef & r2 : finder2.candidates ) {
+					for ( const CandidateRef & r3 : finder3.candidates ) {
+						ast::TypeEnvironment env{ r1->env };
+						env.simpleCombine( r2->env );
+						env.simpleCombine( r3->env );
+						ast::OpenVarSet open{ r1->open };
+						mergeOpenVars( open, r2->open );
+						mergeOpenVars( open, r3->open );
+						ast::AssertionSet need;
+						mergeAssertionSet( need, r1->need );
+						mergeAssertionSet( need, r2->need );
+						mergeAssertionSet( need, r3->need );
+						ast::AssertionSet have;
+
+						// unify true and false results, then infer parameters to produce new 
+						// candidates
+						ast::ptr< ast::Type > common;
+						if ( 
+							unify( 
+								r2->expr->result, r3->expr->result, env, need, have, open, symtab, 
+								common ) 
+						) {
+							#warning unimplemented
+							assert(false);
+						}
+					}
+				}
+			}
+		}
+
+		void postvisit( const ast::CommaExpr * commaExpr ) {
+			ast::TypeEnvironment env{ tenv };
+			ast::ptr< ast::Expr > arg1 = resolveInVoidContext( commaExpr->arg1, symtab, env );
+			
+			CandidateFinder finder2{ symtab, env };
+			finder2.find( commaExpr->arg2, ResolvMode::withAdjustment() );
+
+			for ( const CandidateRef & r2 : finder2.candidates ) {
+				addCandidate( *r2, new ast::CommaExpr{ commaExpr->location, arg1, r2->expr } );
+			}
+		}
+
+		void postvisit( const ast::ImplicitCopyCtorExpr * ctorExpr ) {
+			addCandidate( ctorExpr, tenv );
+		}
+
+		void postvisit( const ast::ConstructorExpr * ctorExpr ) {
+			CandidateFinder finder{ symtab, tenv };
+			finder.find( ctorExpr->callExpr, ResolvMode::withoutPrune() );
+			for ( CandidateRef & r : finder.candidates ) {
+				addCandidate( *r, new ast::ConstructorExpr{ ctorExpr->location, r->expr } );
+			}
+		}
+
+		void postvisit( const ast::RangeExpr * rangeExpr ) {
+			// resolve low and high, accept candidates where low and high types unify
+			CandidateFinder finder1{ symtab, tenv };
+			finder1.find( rangeExpr->low, ResolvMode::withAdjustment() );
+			if ( finder1.candidates.empty() ) return;
+
+			CandidateFinder finder2{ symtab, tenv };
+			finder2.find( rangeExpr->high, ResolvMode::withAdjustment() );
+			if ( finder2.candidates.empty() ) return;
+
+			for ( const CandidateRef & r1 : finder1.candidates ) {
+				for ( const CandidateRef & r2 : finder2.candidates ) {
+					ast::TypeEnvironment env{ r1->env };
+					env.simpleCombine( r2->env );
+					ast::OpenVarSet open{ r1->open };
+					mergeOpenVars( open, r2->open );
+					ast::AssertionSet need;
+					mergeAssertionSet( need, r1->need );
+					mergeAssertionSet( need, r2->need );
+					ast::AssertionSet have;
+
+					ast::ptr< ast::Type > common;
+					if ( 
+						unify( 
+							r1->expr->result, r2->expr->result, env, need, have, open, symtab, 
+							common ) 
+					) {
+						ast::RangeExpr * newExpr = 
+							new ast::RangeExpr{ rangeExpr->location, r1->expr, r2->expr };
+						newExpr->result = common ? common : r1->expr->result;
+						
+						#warning unimplemented
+						assert(false);
+					}
+				}
+			}
+		}
+
+		void postvisit( const ast::UntypedTupleExpr * tupleExpr ) {
+			std::vector< CandidateFinder > subCandidates = 
+				selfFinder.findSubExprs( tupleExpr->exprs );
+			std::vector< CandidateList > possibilities;
+			combos( subCandidates.begin(), subCandidates.end(), back_inserter( possibilities ) );
+
+			for ( const CandidateList & subs : possibilities ) {
+				std::vector< ast::ptr< ast::Expr > > exprs;
+				exprs.reserve( subs.size() );
+				for ( const CandidateRef & sub : subs ) { exprs.emplace_back( sub->expr ); }
+
+				ast::TypeEnvironment env;
+				ast::OpenVarSet open;
+				ast::AssertionSet need;
+				for ( const CandidateRef & sub : subs ) {
+					env.simpleCombine( sub->env );
+					mergeOpenVars( open, sub->open );
+					mergeAssertionSet( need, sub->need );
+				}
+
+				addCandidate(
+					new ast::TupleExpr{ tupleExpr->location, std::move( exprs ) }, 
+					std::move( env ), std::move( open ), std::move( need ), sumCost( subs ) );
+			}
+		}
+
+		void postvisit( const ast::TupleExpr * tupleExpr ) {
+			addCandidate( tupleExpr, tenv );
+		}
+
+		void postvisit( const ast::TupleIndexExpr * tupleExpr ) {
+			addCandidate( tupleExpr, tenv );
+		}
+
+		void postvisit( const ast::TupleAssignExpr * tupleExpr ) {
+			addCandidate( tupleExpr, tenv );
+		}
+
+		void postvisit( const ast::UniqueExpr * unqExpr ) {
+			CandidateFinder finder{ symtab, tenv };
+			finder.find( unqExpr->expr, ResolvMode::withAdjustment() );
+			for ( CandidateRef & r : finder.candidates ) {
+				// ensure that the the id is passed on so that the expressions are "linked"
+				addCandidate( *r, new ast::UniqueExpr{ unqExpr->location, r->expr, unqExpr->id } );
+			}
+		}
+
+		void postvisit( const ast::StmtExpr * stmtExpr ) {
+			#warning unimplemented
+			(void)stmtExpr;
+			assert(false);
+		}
+
+		void postvisit( const ast::UntypedInitExpr * initExpr ) {
+			#warning unimplemented
+			(void)initExpr;
+			assert(false);
+		}
+
+		void postvisit( const ast::InitExpr * ) {
+			assertf( false, "CandidateFinder should never see a resolved InitExpr." );
+		}
+
+		void postvisit( const ast::DeletedExpr * ) {
+			assertf( false, "CandidateFinder should never see a DeletedExpr." );
+		}
+
+		void postvisit( const ast::GenericExpr * ) {
+			assertf( false, "_Generic is not yet supported." );
+		}
 	};
 
@@ -49,8 +566,71 @@
 	/// return type. Skips ambiguous candidates.
 	CandidateList pruneCandidates( CandidateList & candidates ) {
-		#warning unimplemented
-		(void)candidates;
-		assert(false);
-		return {};
+		struct PruneStruct {
+			CandidateRef candidate;
+			bool ambiguous;
+
+			PruneStruct() = default;
+			PruneStruct( const CandidateRef & c ) : candidate( c ), ambiguous( false ) {}
+		};
+
+		// find lowest-cost candidate for each type
+		std::unordered_map< std::string, PruneStruct > selected;
+		for ( CandidateRef & candidate : candidates ) {
+			std::string mangleName;
+			{
+				ast::ptr< ast::Type > newType = candidate->expr->result;
+				candidate->env.apply( newType );
+				mangleName = Mangle::mangle( newType );
+			}
+
+			auto found = selected.find( mangleName );
+			if ( found != selected.end() ) {
+				if ( candidate->cost < found->second.candidate->cost ) {
+					PRINT(
+						std::cerr << "cost " << candidate->cost << " beats " 
+							<< found->second.candidate->cost << std::endl;
+					)
+
+					found->second = PruneStruct{ candidate };
+				} else if ( candidate->cost == found->second.candidate->cost ) {
+					// if one of the candidates contains a deleted identifier, can pick the other, 
+					// since deleted expressions should not be ambiguous if there is another option 
+					// that is at least as good
+					if ( findDeletedExpr( candidate->expr ) ) {
+						// do nothing
+						PRINT( std::cerr << "candidate is deleted" << std::endl; )
+					} else if ( findDeletedExpr( found->second.candidate->expr ) ) {
+						PRINT( std::cerr << "current is deleted" << std::endl; )
+						found->second = PruneStruct{ candidate };
+					} else {
+						PRINT( std::cerr << "marking ambiguous" << std::endl; )
+						found->second.ambiguous = true;
+					}
+				} else {
+					PRINT(
+						std::cerr << "cost " << candidate->cost << " loses to " 
+							<< found->second.candidate->cost << std::endl;
+					)
+				}
+			} else {
+				selected.emplace_hint( found, mangleName, candidate );
+			}
+		}
+
+		// report unambiguous min-cost candidates
+		CandidateList out;
+		for ( auto & target : selected ) {
+			if ( target.second.ambiguous ) continue;
+
+			CandidateRef cand = target.second.candidate;
+			
+			ast::ptr< ast::Type > newResult = cand->expr->result;
+			cand->env.applyFree( newResult );
+			cand->expr = ast::mutate_field(
+				cand->expr.get(), &ast::Expr::result, std::move( newResult ) );
+			
+			out.emplace_back( cand );
+		}
+		return out;
 	}
 
@@ -91,5 +671,4 @@
 	if ( mode.prune ) {
 		// trim candidates to single best one
-		auto oldsize = candidates.size();
 		PRINT(
 			std::cerr << "alternatives before prune:" << std::endl;
@@ -98,15 +677,44 @@
 
 		CandidateList pruned = pruneCandidates( candidates );
+		
 		if ( mode.failFast && pruned.empty() ) {
 			std::ostringstream stream;
-			CandidateList winners;
-			
-			#warning unimplemented
-			assert(false);
-		}
-	}
-
-	#warning unimplemented
-	assert(false);
+			CandidateList winners = findMinCost( candidates );
+			stream << "Cannot choose between " << winners.size() << " alternatives for "
+				"expression\n";
+			ast::print( stream, expr );
+			stream << " Alternatives are:\n";
+			print( stream, winners, 1 );
+			SemanticError( expr->location, stream.str() );
+		}
+
+		auto oldsize = candidates.size();
+		candidates = std::move( pruned );
+
+		PRINT(
+			std::cerr << "there are " << oldsize << " alternatives before elimination" << std::endl;
+		)
+		PRINT(
+			std::cerr << "there are " << candidates.size() << " alternatives after elimination" 
+				<< std::endl;
+		)
+	}
+
+	// adjust types after pruning so that types substituted by pruneAlternatives are correctly 
+	// adjusted
+	if ( mode.adjust ) {
+		for ( CandidateRef & r : candidates ) {
+			r->expr = ast::mutate_field( 
+				r->expr.get(), &ast::Expr::result, 
+				adjustExprType( r->expr->result, r->env, symtab ) );
+		}
+	}
+
+	// Central location to handle gcc extension keyword, etc. for all expressions
+	for ( CandidateRef & r : candidates ) {
+		if ( r->expr->extension != expr->extension ) {
+			r->expr.get_and_mutate()->extension = expr->extension;
+		}
+	}
 }
 
Index: src/ResolvExpr/ExplodedActual.cc
===================================================================
--- src/ResolvExpr/ExplodedActual.cc	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/ResolvExpr/ExplodedActual.cc	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -5,5 +5,5 @@
 // file "LICENCE" distributed with Cforall.
 //
-// Alternative.h --
+// ExplodedActual.cc --
 //
 // Author           : Aaron B. Moss
@@ -24,2 +24,8 @@
 	}
 }
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/ResolvExpr/ExplodedActual.h
===================================================================
--- src/ResolvExpr/ExplodedActual.h	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/ResolvExpr/ExplodedActual.h	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -5,5 +5,5 @@
 // file "LICENCE" distributed with Cforall.
 //
-// Alternative.h --
+// ExplodedActual.h --
 //
 // Author           : Aaron B. Moss
@@ -37,2 +37,8 @@
 	};
 }
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/ResolvExpr/ExplodedArg.cpp
===================================================================
--- src/ResolvExpr/ExplodedArg.cpp	(revision 662572780e20a16122cc52e797041997d3dc3120)
+++ src/ResolvExpr/ExplodedArg.cpp	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -0,0 +1,31 @@
+//
+// 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.
+//
+// ExplodedArg.cpp --
+//
+// Author           : Aaron B. Moss
+// Created On       : Tue Jun 11 16:18:00 2019
+// Last Modified By : Aaron B. Moss
+// Last Modified On : Tue Jun 11 16:18:00 2019
+// Update Count     : 1
+//
+
+#include "ExplodedArg.hpp"
+
+#include "Tuples/Explode.h"   // for Tuples::explode
+
+namespace ResolvExpr {
+
+ExplodedArg::ExplodedArg( const Candidate & arg, const ast::SymbolTable & symtab )
+: env( arg.env ), cost( arg.cost ), exprs() { Tuples::explode( arg, symtab, *this ); }
+
+}
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/ResolvExpr/ExplodedArg.hpp
===================================================================
--- src/ResolvExpr/ExplodedArg.hpp	(revision 662572780e20a16122cc52e797041997d3dc3120)
+++ src/ResolvExpr/ExplodedArg.hpp	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -0,0 +1,48 @@
+//
+// 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.
+//
+// ExplodedArg.hpp --
+//
+// Author           : Aaron B. Moss
+// Created On       : Tue Jun 11 16:18:00 2019
+// Last Modified By : Aaron B. Moss
+// Last Modified On : Tue Jun 11 16:18:00 2019
+// Update Count     : 1
+//
+
+#pragma once
+
+#include <vector>
+
+#include "Candidate.hpp"            // for Candidate, CandidateList
+#include "Cost.h"                   // for Cost
+#include "AST/Expr.hpp"
+#include "AST/Node.hpp"             // for ptr
+#include "AST/TypeEnvironment.hpp"  // for TypeEnvironment
+#include "AST/SymbolTable.hpp"      // for SymbolTable
+
+namespace ResolvExpr {
+
+/// Pre-exploded argument
+struct ExplodedArg {
+	ast::TypeEnvironment env;
+	Cost cost;
+	std::vector< ast::ptr<ast::Expr> > exprs;
+
+	ExplodedArg() : env(), cost( Cost::zero ), exprs() {}
+	ExplodedArg( const Candidate & arg, const ast::SymbolTable & symtab );
+	
+	ExplodedArg( ExplodedArg && ) = default;
+	ExplodedArg & operator= ( ExplodedArg && ) = default;
+};
+
+}
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/ResolvExpr/Resolver.cc
===================================================================
--- src/ResolvExpr/Resolver.cc	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/ResolvExpr/Resolver.cc	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -957,12 +957,14 @@
 			}
 		};
-
-		/// Check if this expression is or includes a deleted expression
-		const ast::DeletedExpr * findDeletedExpr( const ast::Expr * expr ) {
-			ast::Pass<DeleteFinder_new> finder;
-			expr->accept( finder );
-			return finder.pass.delExpr;
-		}
-
+	} // anonymous namespace
+
+	/// Check if this expression is or includes a deleted expression
+	const ast::DeletedExpr * findDeletedExpr( const ast::Expr * expr ) {
+		ast::Pass<DeleteFinder_new> finder;
+		expr->accept( finder );
+		return finder.pass.delExpr;
+	}
+
+	namespace {
 		/// always-accept candidate filter
 		bool anyCandidate( const Candidate & ) { return true; }
@@ -1024,7 +1026,5 @@
 
 			// promote candidate.cvtCost to .cost
-			for ( CandidateRef & cand : winners ) {
-				cand->cost = cand->cvtCost;
-			}
+			promoteCvtCost( winners );
 
 			// produce ambiguous errors, if applicable
@@ -1100,25 +1100,26 @@
 			StripCasts_new::strip( expr );
 		}
-
-		/// Find the expression candidate that is the unique best match for `untyped` in a `void`
-		/// context.
-		ast::ptr< ast::Expr > resolveInVoidContext(
-			const ast::Expr * expr, const ast::SymbolTable & symtab, ast::TypeEnvironment & env
-		) {
-			assertf( expr, "expected a non-null expression" );
-			
-			// set up and resolve expression cast to void
-			ast::CastExpr * untyped = new ast::CastExpr{ expr->location, expr };
-			CandidateRef choice = findUnfinishedKindExpression( 
-				untyped, symtab, "", anyCandidate, ResolvMode::withAdjustment() );
-			
-			// a cast expression has either 0 or 1 interpretations (by language rules);
-			// if 0, an exception has already been thrown, and this code will not run
-			const ast::CastExpr * castExpr = choice->expr.strict_as< ast::CastExpr >();
-			env = std::move( choice->env );
-
-			return castExpr->arg;
-		}
-
+	} // anonymous namespace
+
+		
+	ast::ptr< ast::Expr > resolveInVoidContext(
+		const ast::Expr * expr, const ast::SymbolTable & symtab, ast::TypeEnvironment & env
+	) {
+		assertf( expr, "expected a non-null expression" );
+		
+		// set up and resolve expression cast to void
+		ast::CastExpr * untyped = new ast::CastExpr{ expr->location, expr };
+		CandidateRef choice = findUnfinishedKindExpression( 
+			untyped, symtab, "", anyCandidate, ResolvMode::withAdjustment() );
+		
+		// a cast expression has either 0 or 1 interpretations (by language rules);
+		// if 0, an exception has already been thrown, and this code will not run
+		const ast::CastExpr * castExpr = choice->expr.strict_as< ast::CastExpr >();
+		env = std::move( choice->env );
+
+		return castExpr->arg;
+	}
+
+	namespace {
 		/// Resolve `untyped` to the expression whose candidate is the best match for a `void` 
 		/// context.
Index: src/ResolvExpr/Resolver.h
===================================================================
--- src/ResolvExpr/Resolver.h	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/ResolvExpr/Resolver.h	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -17,5 +17,6 @@
 
 #include <list>          // for list
-#include <AST/Node.hpp>  // for ptr
+
+#include "AST/Node.hpp"  // for ptr
 
 class ConstructorInit;
@@ -29,4 +30,7 @@
 namespace ast {
 	class Decl;
+	class DeletedExpr;
+	class SymbolTable;
+	class TypeEnvironment;
 } // namespace ast
 
@@ -48,4 +52,10 @@
 	/// Checks types and binds syntactic constructs to typed representations
 	void resolve( std::list< ast::ptr<ast::Decl> >& translationUnit );
+	/// Searches expr and returns the first DeletedExpr found, otherwise nullptr
+	const ast::DeletedExpr * findDeletedExpr( const ast::Expr * expr );
+	/// Find the expression candidate that is the unique best match for `untyped` in a `void`
+	/// context.
+	ast::ptr< ast::Expr > resolveInVoidContext(
+		const ast::Expr * expr, const ast::SymbolTable & symtab, ast::TypeEnvironment & env );
 } // namespace ResolvExpr
 
Index: src/ResolvExpr/module.mk
===================================================================
--- src/ResolvExpr/module.mk	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/ResolvExpr/module.mk	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -26,4 +26,5 @@
       ResolvExpr/CurrentObject.cc \
       ResolvExpr/ExplodedActual.cc \
+      ResolvExpr/ExplodedArg.cpp \
       ResolvExpr/FindOpenVars.cc \
       ResolvExpr/Occurs.cc \
Index: src/ResolvExpr/typeops.h
===================================================================
--- src/ResolvExpr/typeops.h	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/ResolvExpr/typeops.h	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -71,4 +71,8 @@
 		} // while
 	}
+
+	/// Replaces array types with equivalent pointer, and function types with a pointer-to-function
+	const ast::Type * adjustExprType( 
+		const ast::Type * type, const ast::TypeEnvironment & env, const ast::SymbolTable & symtab );
 
 	// in CastCost.cc
Index: src/Tuples/Explode.h
===================================================================
--- src/Tuples/Explode.h	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/Tuples/Explode.h	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -19,9 +19,16 @@
 #include <utility>                      // for forward
 
+#include "AST/Expr.hpp"
 #include "ResolvExpr/Alternative.h"     // for Alternative, AltList
+#include "ResolvExpr/Candidate.hpp"     // for Candidate, CandidateList
 #include "ResolvExpr/ExplodedActual.h"  // for ExplodedActual
+#include "ResolvExpr/ExplodedArg.hpp"   // for ExplodedArg
 #include "SynTree/Expression.h"         // for Expression, UniqueExpr, AddressExpr
 #include "SynTree/Type.h"               // for TupleType, Type
 #include "Tuples.h"                     // for maybeImpure
+
+namespace ast {
+	class SymbolTable;
+}
 
 namespace SymTab {
@@ -130,4 +137,25 @@
 		explode( alts.begin(), alts.end(), indexer, std::forward<Output>(out), isTupleAssign );
 	}
+
+/// 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
+) {
+	#warning unimplemented
+	(void)expr; (void)arg; (void)symtab; (void)out; (void)isTupleAssign;
+	assert(false);
+}
+
+/// 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, 
+	bool isTupleAssign = false
+) {
+	explodeUnique( arg.expr, arg, symtab, std::forward< Output >( out ), isTupleAssign );
+}
+
 } // namespace Tuples
 
Index: src/Tuples/TupleAssignment.cc
===================================================================
--- src/Tuples/TupleAssignment.cc	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/Tuples/TupleAssignment.cc	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -377,4 +377,13 @@
 		}
 	}
+
+	void handleTupleAssignment( 
+		ResolvExpr::CandidateFinder & finder, const ast::UntypedExpr * assign, 
+		std::vector< ResolvExpr::CandidateFinder > & args
+	) {
+		#warning unimplmented
+		(void)finder; (void)assign; (void)args;
+		assert(false);
+	}
 } // namespace Tuples
 
Index: src/Tuples/Tuples.h
===================================================================
--- src/Tuples/Tuples.h	(revision 04396aa233f11ad73e89935a71f6586a413d41cb)
+++ src/Tuples/Tuples.h	(revision 662572780e20a16122cc52e797041997d3dc3120)
@@ -26,4 +26,5 @@
 
 #include "ResolvExpr/AlternativeFinder.h"
+#include "ResolvExpr/CandidateFinder.hpp"
 
 namespace Tuples {
@@ -31,4 +32,7 @@
 	void handleTupleAssignment( ResolvExpr::AlternativeFinder & currentFinder, UntypedExpr * assign,
 		std::vector< ResolvExpr::AlternativeFinder >& args );
+	void handleTupleAssignment( 
+		ResolvExpr::CandidateFinder & finder, const ast::UntypedExpr * assign, 
+		std::vector< ResolvExpr::CandidateFinder > & args );
 
 	// TupleExpansion.cc
