Index: src/AST/Pass.hpp
===================================================================
--- src/AST/Pass.hpp	(revision 76a798d60267d1ba1ad46d084a0f9edf5f2b02fc)
+++ src/AST/Pass.hpp	(revision 9e23b446e321a87bbf5f2439c8d555a808d5e53c)
@@ -264,4 +264,7 @@
 	__pass::result1<ast::Stmt> call_accept_as_compound(const ast::Stmt *);
 
+	// requests type environment to be updated (why is it implemented like this?)
+	__pass::result1<ast::Expr> call_accept_top(const ast::Expr *);
+
 	template< template <class...> class container_t >
 	__pass::resultNstmt<container_t> call_accept( const container_t< ptr<Stmt> > & );
@@ -277,4 +280,7 @@
 	template<typename node_t, typename parent_t, typename field_t>
 	void maybe_accept_as_compound(const node_t * &, field_t parent_t::* field);
+
+	template<typename node_t, typename parent_t, typename field_t>
+	void maybe_accept_top(const node_t * &, field_t parent_t::* field);
 
 private:
Index: src/AST/Pass.impl.hpp
===================================================================
--- src/AST/Pass.impl.hpp	(revision 76a798d60267d1ba1ad46d084a0f9edf5f2b02fc)
+++ src/AST/Pass.impl.hpp	(revision 9e23b446e321a87bbf5f2439c8d555a808d5e53c)
@@ -155,9 +155,4 @@
 		__pedantic_pass_assert( expr );
 
-		const ast::TypeSubstitution ** typeSubs_ptr = __pass::typeSubs( core, 0 );
-		if ( typeSubs_ptr && expr->env ) {
-			*typeSubs_ptr = expr->env;
-		}
-
 		auto nval = expr->accept( *this );
 		return { nval != expr, nval };
@@ -171,4 +166,18 @@
 		const ast::Stmt * nval = stmt->accept( *this );
 		return { nval != stmt, nval };
+	}
+
+	template< typename core_t >
+	__pass::template result1<ast::Expr> ast::Pass< core_t >::call_accept_top( const ast::Expr * expr ) {
+		__pedantic_pass_assert( __visit_children() );
+		__pedantic_pass_assert( expr );
+
+		const ast::TypeSubstitution ** typeSubs_ptr = __pass::typeSubs( core, 0 );
+		if ( typeSubs_ptr && expr->env ) {
+			*typeSubs_ptr = expr->env;
+		}
+
+		auto nval = expr->accept( *this );
+		return { nval != expr, nval };
 	}
 
@@ -410,4 +419,28 @@
 
 		auto new_val = call_accept( old_val );
+
+		static_assert( !std::is_same<const ast::Node *, decltype(new_val)>::value /* || std::is_same<int, decltype(old_val)>::value */, "ERROR");
+
+		if( new_val.differs ) {
+			auto new_parent = __pass::mutate<core_t>(parent);
+			new_val.apply(new_parent, field);
+			parent = new_parent;
+		}
+	}
+
+	template< typename core_t >
+	template<typename node_t, typename super_t, typename field_t>
+	void ast::Pass< core_t >::maybe_accept_top(
+		const node_t * & parent,
+		field_t super_t::*field
+	) {
+		static_assert( std::is_base_of<super_t, node_t>::value, "Error deducing member object" );
+
+		if(__pass::skip(parent->*field)) return;
+		const auto & old_val = __pass::get(parent->*field, 0);
+
+		static_assert( !std::is_same<const ast::Node * &, decltype(old_val)>::value, "ERROR");
+
+		auto new_val = call_accept_top( old_val );
 
 		static_assert( !std::is_same<const ast::Node *, decltype(new_val)>::value /* || std::is_same<int, decltype(old_val)>::value */, "ERROR");
@@ -755,5 +788,5 @@
 
 	if ( __visit_children() ) {
-		maybe_accept( node, &StaticAssertDecl::cond );
+		maybe_accept_top( node, &StaticAssertDecl::cond );
 		maybe_accept( node, &StaticAssertDecl::msg  );
 	}
@@ -797,5 +830,5 @@
 
 	if ( __visit_children() ) {
-		maybe_accept( node, &ExprStmt::expr );
+		maybe_accept_top( node, &ExprStmt::expr );
 	}
 
@@ -838,5 +871,5 @@
 		guard_symtab guard { *this };
 		maybe_accept( node, &IfStmt::inits    );
-		maybe_accept( node, &IfStmt::cond     );
+		maybe_accept_top( node, &IfStmt::cond     );
 		maybe_accept_as_compound( node, &IfStmt::then );
 		maybe_accept_as_compound( node, &IfStmt::else_ );
@@ -856,5 +889,5 @@
 		guard_symtab guard { *this };
 		maybe_accept( node, &WhileDoStmt::inits );
-		maybe_accept( node, &WhileDoStmt::cond  );
+		maybe_accept_top( node, &WhileDoStmt::cond  );
 		maybe_accept_as_compound( node, &WhileDoStmt::body  );
 	}
@@ -874,6 +907,6 @@
 		// xxx - old ast does not create WithStmtsToAdd scope for loop inits. should revisit this later.
 		maybe_accept( node, &ForStmt::inits );
-		maybe_accept( node, &ForStmt::cond  );
-		maybe_accept( node, &ForStmt::inc   );
+		maybe_accept_top( node, &ForStmt::cond  );
+		maybe_accept_top( node, &ForStmt::inc   );
 		maybe_accept_as_compound( node, &ForStmt::body  );
 	}
@@ -889,5 +922,5 @@
 
 	if ( __visit_children() ) {
-		maybe_accept( node, &SwitchStmt::cond  );
+		maybe_accept_top( node, &SwitchStmt::cond  );
 		maybe_accept( node, &SwitchStmt::cases );
 	}
@@ -903,5 +936,5 @@
 
 	if ( __visit_children() ) {
-		maybe_accept( node, &CaseClause::cond  );
+		maybe_accept_top( node, &CaseClause::cond  );
 		maybe_accept( node, &CaseClause::stmts );
 	}
@@ -925,5 +958,5 @@
 
 	if ( __visit_children() ) {
-		maybe_accept( node, &ReturnStmt::expr );
+		maybe_accept_top( node, &ReturnStmt::expr );
 	}
 
@@ -970,5 +1003,5 @@
 		guard_symtab guard { *this };
 		maybe_accept( node, &CatchClause::decl );
-		maybe_accept( node, &CatchClause::cond );
+		maybe_accept_top( node, &CatchClause::cond );
 		maybe_accept_as_compound( node, &CatchClause::body );
 	}
@@ -2057,5 +2090,5 @@
 
 	if ( __visit_children() ) {
-		maybe_accept( node, &SingleInit::value );
+		maybe_accept_top( node, &SingleInit::value );
 	}
 
Index: src/AST/SymbolTable.cpp
===================================================================
--- src/AST/SymbolTable.cpp	(revision 76a798d60267d1ba1ad46d084a0f9edf5f2b02fc)
+++ src/AST/SymbolTable.cpp	(revision 9e23b446e321a87bbf5f2439c8d555a808d5e53c)
@@ -65,7 +65,20 @@
 
 Expr * SymbolTable::IdData::combine( const CodeLocation & loc, ResolvExpr::Cost & cost ) const {
-	Expr * ret = ( baseExpr ) ?
-		(Expr *)new MemberExpr{ loc, id, referenceToRvalueConversion( baseExpr, cost ) } :
-		(Expr *)new VariableExpr{ loc, id };
+	Expr * ret;
+	if ( baseExpr ) {
+		if (baseExpr->env) {
+			Expr * base = shallowCopy(baseExpr);
+			const TypeSubstitution * subs = baseExpr->env;
+			base->env = nullptr;
+			ret = new MemberExpr{loc, id, referenceToRvalueConversion( base, cost )};
+			ret->env = subs;
+		}
+		else {
+			ret = new MemberExpr{ loc, id, referenceToRvalueConversion( baseExpr, cost ) };
+		}
+	}
+	else {
+		ret = new VariableExpr{ loc, id };
+	}
 	if ( deleter ) { ret = new DeletedExpr{ loc, ret, deleter }; }
 	return ret;
@@ -772,5 +785,9 @@
 						&& ! dynamic_cast<const UnionInstType *>(rty) ) continue;
 					ResolvExpr::Cost cost = ResolvExpr::Cost::zero;
+					ast::ptr<ast::TypeSubstitution> tmp = expr->env;
+					expr = mutate_field(expr, &Expr::env, nullptr);
 					const Expr * base = ResolvExpr::referenceToRvalueConversion( expr, cost );
+					base = mutate_field(base, &Expr::env, tmp);
+
 					addMembers(
 						rty->aggr(), new MemberExpr{ base->location, dwt, base }, handleConflicts );
Index: src/AST/TypeSubstitution.cpp
===================================================================
--- src/AST/TypeSubstitution.cpp	(revision 76a798d60267d1ba1ad46d084a0f9edf5f2b02fc)
+++ src/AST/TypeSubstitution.cpp	(revision 9e23b446e321a87bbf5f2439c8d555a808d5e53c)
@@ -97,5 +97,5 @@
 		TypeSubstitution * newEnv;
 		EnvTrimmer( const TypeSubstitution * env, TypeSubstitution * newEnv ) : env( env ), newEnv( newEnv ){}
-		void previsit( FunctionType * ftype ) {
+		void previsit( const FunctionType * ftype ) {
 			// transfer known bindings for seen type variables
 			for (auto & formal : ftype->forall) {
Index: src/GenPoly/Specialize.h
===================================================================
--- src/GenPoly/Specialize.h	(revision 76a798d60267d1ba1ad46d084a0f9edf5f2b02fc)
+++ src/GenPoly/Specialize.h	(revision 9e23b446e321a87bbf5f2439c8d555a808d5e53c)
@@ -17,4 +17,5 @@
 
 #include <list>  // for list
+#include "AST/TranslationUnit.hpp"
 
 class Declaration;
@@ -23,4 +24,6 @@
 	/// generates thunks where needed
 	void convertSpecializations( std::list< Declaration* >& translationUnit );
+
+	void convertSpecializations( ast::TranslationUnit & translationUnit );
 } // namespace GenPoly
 
Index: src/GenPoly/SpecializeNew.cpp
===================================================================
--- src/GenPoly/SpecializeNew.cpp	(revision 9e23b446e321a87bbf5f2439c8d555a808d5e53c)
+++ src/GenPoly/SpecializeNew.cpp	(revision 9e23b446e321a87bbf5f2439c8d555a808d5e53c)
@@ -0,0 +1,521 @@
+//
+// 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.
+//
+// SpecializeNew.cpp --
+//
+// Author           : Andrew Beach
+// Created On       : Tue Jun  7 13:37:00 2022
+// Last Modified By : Andrew Beach
+// Last Modified On : Tue Jun  7 13:37:00 2022
+// Update Count     : 0
+//
+
+#include "Specialize.h"
+
+#include "AST/Pass.hpp"
+#include "AST/TypeEnvironment.hpp"       // for OpenVarSet, AssertionSet
+#include "Common/UniqueName.h"           // for UniqueName
+#include "GenPoly/GenPoly.h"             // for getFunctionType
+#include "InitTweak/InitTweak.h"         // for isIntrinsicCallExpr
+#include "ResolvExpr/FindOpenVars.h"     // for findOpenVars
+#include "ResolvExpr/TypeEnvironment.h"  // for FirstOpen, FirstClosed
+
+#include "AST/Print.hpp"
+
+namespace GenPoly {
+
+namespace {
+
+struct SpecializeCore final :
+		public ast::WithConstTypeSubstitution,
+		public ast::WithDeclsToAdd<>,
+		public ast::WithVisitorRef<SpecializeCore> {
+	std::string paramPrefix = "_p";
+
+	ast::ApplicationExpr * handleExplicitParams(
+		const ast::ApplicationExpr * expr );
+	const ast::Expr * createThunkFunction(
+		const CodeLocation & location,
+		const ast::FunctionType * funType,
+		const ast::Expr * actual,
+		const ast::InferredParams * inferParams );
+	const ast::Expr * doSpecialization(
+		const CodeLocation & location,
+		const ast::Type * formalType,
+		const ast::Expr * actual,
+		const ast::InferredParams * inferParams );
+
+	const ast::Expr * postvisit( const ast::ApplicationExpr * expr );
+	const ast::Expr * postvisit( const ast::CastExpr * expr );
+};
+
+const ast::InferredParams * getInferredParams( const ast::Expr * expr ) {
+	const ast::Expr::InferUnion & inferred = expr->inferred;
+	if ( inferred.hasParams() ) {
+		return &inferred.inferParams();
+	} else {
+		return nullptr;
+	}
+}
+
+// Check if both types have the same structure. The leaf (non-tuple) types
+// don't have to match but the tuples must match.
+bool isTupleStructureMatching( const ast::Type * t0, const ast::Type * t1 ) {
+	const ast::TupleType * tt0 = dynamic_cast<const ast::TupleType *>( t0 );
+	const ast::TupleType * tt1 = dynamic_cast<const ast::TupleType *>( t1 );
+	if ( tt0 && tt1 ) {
+		if ( tt0->size() != tt1->size() ) {
+			return false;
+		}
+		for ( auto types : group_iterate( tt0->types, tt1->types ) ) {
+			if ( !isTupleStructureMatching(
+					std::get<0>( types ), std::get<1>( types ) ) ) {
+				return false;
+			}
+		}
+		return true;
+	}
+	return (!tt0 && !tt1);
+}
+
+// The number of elements in a type if it is a flattened tuple.
+size_t flatTupleSize( const ast::Type * type ) {
+	if ( auto tuple = dynamic_cast<const ast::TupleType *>( type ) ) {
+		size_t sum = 0;
+		for ( auto t : *tuple ) {
+			sum += flatTupleSize( t );
+		}
+		return sum;
+	} else {
+		return 1;
+	}
+}
+
+// Find the total number of components in a parameter list.
+size_t functionParameterSize( const ast::FunctionType * type ) {
+	size_t sum = 0;
+	for ( auto param : type->params ) {
+		sum += flatTupleSize( param );
+	}
+	return sum;
+}
+
+bool needsPolySpecialization(
+		const ast::Type * formalType,
+		const ast::Type * actualType,
+		const ast::TypeSubstitution * subs ) {
+	if ( !subs ) {
+		return false;
+	}
+
+	using namespace ResolvExpr;
+	ast::OpenVarSet openVars, closedVars;
+	ast::AssertionSet need, have;
+	findOpenVars( formalType, openVars, closedVars, need, have, FirstClosed );
+	findOpenVars( actualType, openVars, closedVars, need, have, FirstOpen );
+	for ( const ast::OpenVarSet::value_type & openVar : openVars ) {
+		const ast::Type * boundType = subs->lookup( openVar.first );
+		// If the variable is not bound, move onto the next variable.
+		if ( !boundType ) continue;
+
+		// Is the variable cound to another type variable?
+		if ( auto inst = dynamic_cast<const ast::TypeInstType *>( boundType ) ) {
+			if ( closedVars.find( *inst ) == closedVars.end() ) {
+				return true;
+			}
+		// Otherwise, the variable is bound to a concrete type.
+		} else {
+			return true;
+		}
+	}
+	// None of the type variables are bound.
+	return false;
+}
+
+bool needsTupleSpecialization(
+		const ast::Type * formalType, const ast::Type * actualType ) {
+	// Needs tuple specialization if the structure of the formal type and
+	// actual type do not match.
+
+	// This is the case if the formal type has ttype polymorphism, or if the structure  of tuple types
+	// between the function do not match exactly.
+	if ( const ast::FunctionType * ftype = getFunctionType( formalType ) ) {
+		// A pack in the parameter or return type requires specialization.
+		if ( ftype->isTtype() ) {
+			return true;
+		}
+		// Conversion of 0 to a function type does not require specialization.
+		if ( dynamic_cast<const ast::ZeroType *>( actualType ) ) {
+			return false;
+		}
+		const ast::FunctionType * atype =
+			getFunctionType( actualType->stripReferences() );
+		assertf( atype,
+			"formal type is a function type, but actual type is not: %s",
+			toString( actualType ).c_str() );
+		// Can't tuple specialize if parameter sizes deeply-differ.
+		if ( functionParameterSize( ftype ) != functionParameterSize( atype ) ) {
+			return false;
+		}
+		// If tuple parameter size matches but actual parameter sizes differ
+		// then there needs to be specialization.
+		if ( ftype->params.size() != atype->params.size() ) {
+			return true;
+		}
+		// Total parameter size can be the same, while individual parameters
+		// can have different structure.
+		for ( auto pairs : group_iterate( ftype->params, atype->params ) ) {
+			if ( !isTupleStructureMatching(
+					std::get<0>( pairs ), std::get<1>( pairs ) ) ) {
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
+bool needsSpecialization(
+		const ast::Type * formalType, const ast::Type * actualType,
+		const ast::TypeSubstitution * subs ) {
+	return needsPolySpecialization( formalType, actualType, subs )
+		|| needsTupleSpecialization( formalType, actualType );
+}
+
+ast::ApplicationExpr * SpecializeCore::handleExplicitParams(
+		const ast::ApplicationExpr * expr ) {
+	assert( expr->func->result );
+	const ast::FunctionType * func = getFunctionType( expr->func->result );
+	assert( func );
+
+	ast::ApplicationExpr * mut = ast::mutate( expr );
+
+	std::vector<ast::ptr<ast::Type>>::const_iterator formal;
+	std::vector<ast::ptr<ast::Expr>>::iterator actual;
+	for ( formal = func->params.begin(), actual = mut->args.begin() ;
+			formal != func->params.end() && actual != mut->args.end() ;
+			++formal, ++actual ) {
+		*actual = doSpecialization( (*actual)->location,
+			*formal, *actual, getInferredParams( expr ) );
+	}
+	//for ( auto pair : group_iterate( formal->params, mut->args ) ) {
+	//	const ast::ptr<ast::Type> & formal = std::get<0>( pair );
+	//	ast::ptr<ast::Expr> & actual = std::get<1>( pair );
+	//	*actual = doSpecialization( (*actual)->location, *formal, *actual, getInferredParams( expr ) );
+	//}
+	return mut;
+}
+
+// Explode assuming simple cases: either type is pure tuple (but not tuple
+// expr) or type is non-tuple.
+template<typename OutputIterator>
+void explodeSimple( const CodeLocation & location,
+		const ast::Expr * expr, OutputIterator out ) {
+	// Recurse on tuple types using index expressions on each component.
+	if ( auto tuple = expr->result.as<ast::TupleType>() ) {
+		ast::ptr<ast::Expr> cleanup = expr;
+		for ( unsigned int i = 0 ; i < tuple->size() ; ++i ) {
+			explodeSimple( location,
+				new ast::TupleIndexExpr( location, expr, i ), out );
+		}
+	// For a non-tuple type, output a clone of the expression.
+	} else {
+		*out++ = expr;
+	}
+}
+
+// Restructures the arguments to match the structure of the formal parameters
+// of the actual function. [begin, end) are the exploded arguments.
+template<typename Iterator, typename OutIterator>
+void structureArg( const CodeLocation & location, const ast::Type * type,
+		Iterator & begin, Iterator end, OutIterator out ) {
+	if ( auto tuple = dynamic_cast<const ast::TupleType *>( type ) ) {
+		std::vector<ast::ptr<ast::Expr>> exprs;
+		for ( const ast::Type * t : *tuple ) {
+			structureArg( location, t, begin, end, std::back_inserter( exprs ) );
+		}
+		*out++ = new ast::TupleExpr( location, std::move( exprs ) );
+	} else {
+		assertf( begin != end, "reached the end of the arguments while structuring" );
+		*out++ = *begin++;
+	}
+}
+
+#if 0
+template<typename Iterator>
+const ast::Expr * structureArg(
+		const CodeLocation& location, const ast::ptr<ast::Type> & type,
+		Iterator & begin, const Iterator & end ) {
+	if ( auto tuple = type->as<ast::TupleType>() ) {
+		std::vector<ast::ptr<ast::Expr>> exprs;
+		for ( const ast::Type * t : *tuple ) {
+			exprs.push_back( structureArg( location, t, begin, end ) );
+		}
+		return new ast::TupleExpr( location, std::move( exprs ) );
+	} else {
+		assertf( begin != end, "reached the end of the arguments while structuring" );
+		return *begin++;
+	}
+}
+#endif
+
+namespace {
+	struct TypeInstFixer : public ast::WithShortCircuiting {
+		std::map<const ast::TypeDecl *, std::pair<int, int>> typeMap;
+
+		void previsit(const ast::TypeDecl *) { visit_children = false; }
+		const ast::TypeInstType * postvisit(const ast::TypeInstType * typeInst) {
+			if (typeMap.count(typeInst->base)) {
+				ast::TypeInstType * newInst = mutate(typeInst);
+				newInst->expr_id = typeMap[typeInst->base].first;
+				newInst->formal_usage = typeMap[typeInst->base].second;
+				return newInst;
+			}
+			return typeInst;
+		}
+	};
+}
+
+const ast::Expr * SpecializeCore::createThunkFunction(
+		const CodeLocation & location,
+		const ast::FunctionType * funType,
+		const ast::Expr * actual,
+		const ast::InferredParams * inferParams ) {
+	// One set of unique names per program.
+	static UniqueName thunkNamer("_thunk");
+
+	const ast::FunctionType * newType = ast::deepCopy( funType );
+	if ( typeSubs ) {
+		// Must replace only occurrences of type variables
+		// that occure free in the thunk's type.
+		//ast::TypeSubstitution::ApplyResult<ast::FunctionType>
+		//	typeSubs->applyFree( newType );
+		auto result = typeSubs->applyFree( newType );
+		newType = result.node.release();
+	}
+
+	using DWTVector = std::vector<ast::ptr<ast::DeclWithType>>;
+	using DeclVector = std::vector<ast::ptr<ast::TypeDecl>>;
+
+	//const std::string & thunkName = thunkNamer.newName();
+	//UniqueName paramNamer(thunkName + "Param");
+	UniqueName paramNamer( paramPrefix );
+
+	//auto toParamDecl = [&location, &paramNamer]( const ast::Type * type ) {
+	//	return new ast::ObjectDecl(
+	//		location, paramNamer.newName(), ast::deepCopy( type ) );
+	//};
+
+	// Create new thunk with same signature as formal type.
+
+	// std::map<const ast::TypeDecl *, std::pair<int, int>> typeMap;
+	ast::Pass<TypeInstFixer> fixer;
+	for (const auto & kv : newType->forall) {
+		if (fixer.core.typeMap.count(kv->base)) {
+			std::cerr << location << ' ' << kv->base->name << ' ' << kv->expr_id << '_' << kv->formal_usage << ',' 
+			<< fixer.core.typeMap[kv->base].first << '_' << fixer.core.typeMap[kv->base].second << std::endl;
+			assertf(false, "multiple formals in specialize");
+		}
+		else {
+			fixer.core.typeMap[kv->base] = std::make_pair(kv->expr_id, kv->formal_usage);
+		}
+	} 
+
+	ast::CompoundStmt * thunkBody = new ast::CompoundStmt( location );
+	ast::FunctionDecl * thunkFunc = new ast::FunctionDecl(
+		location,
+		thunkNamer.newName(),
+		map_range<DeclVector>( newType->forall, []( const ast::TypeInstType * inst ) {
+			return ast::deepCopy( inst->base );
+		} ),
+		map_range<DWTVector>( newType->assertions, []( const ast::VariableExpr * expr ) {
+			return ast::deepCopy( expr->var );
+		} ),
+		map_range<DWTVector>( newType->params, [&location, &paramNamer]( const ast::Type * type ) {
+			return new ast::ObjectDecl( location, paramNamer.newName(), ast::deepCopy( type ) );
+		} ),
+		map_range<DWTVector>( newType->returns, [&location, &paramNamer]( const ast::Type * type ) {
+			return new ast::ObjectDecl( location, paramNamer.newName(), ast::deepCopy( type ) );
+		} ),
+		thunkBody,
+		ast::Storage::Classes(),
+		ast::Linkage::C
+		);
+
+	// thunkFunc->accept(fixer);
+	thunkFunc->fixUniqueId();
+
+
+
+	// Thunks may be generated and not used, avoid them.
+	thunkFunc->attributes.push_back( new ast::Attribute( "unused" ) );
+
+	// Global thunks must be static to avoid collitions.
+	// Nested thunks must not be unique and hence, not static.
+	thunkFunc->storage.is_static = !isInFunction();
+
+	// Weave thunk parameters into call to actual function,
+	// naming thunk parameters as we go.
+	ast::ApplicationExpr * app = new ast::ApplicationExpr( location, actual );
+
+	const ast::FunctionType * actualType = ast::deepCopy( getFunctionType( actual->result ) );
+	if ( typeSubs ) {
+		// Need to apply the environment to the actual function's type,
+		// since it may itself be polymorphic.
+		auto result = typeSubs->apply( actualType );
+		actualType = result.node.release();
+	}
+
+	ast::ptr<ast::FunctionType> actualTypeManager = actualType;
+
+	std::vector<ast::ptr<ast::Expr>> args;
+	for ( ast::ptr<ast::DeclWithType> & param : thunkFunc->params ) {
+		// Name each thunk parameter and explode it.
+		// These are then threaded back into the actual function call.
+		//param->name = paramNamer.newName();
+		ast::DeclWithType * mutParam = ast::mutate( param.get() );
+		// - Should be renamed earlier. -
+		//mutParam->name = paramNamer.newName();
+		explodeSimple( location, new ast::VariableExpr( location, mutParam ),
+			std::back_inserter( args ) );
+	}
+
+	// Walk parameters to the actual function alongside the exploded thunk
+	// parameters and restructure the arguments to match the actual parameters.
+	std::vector<ast::ptr<ast::Expr>>::iterator
+		argBegin = args.begin(), argEnd = args.end();
+	for ( const auto & actualArg : actualType->params ) {
+		structureArg( location, actualArg.get(), argBegin, argEnd,
+			std::back_inserter( app->args ) );
+	}
+	assertf( argBegin == argEnd, "Did not structure all arguments." );
+
+	app->accept(fixer); // this should modify in place
+
+	app->env = ast::TypeSubstitution::newFromExpr( app, typeSubs );
+	if ( inferParams ) {
+		app->inferred.inferParams() = *inferParams;
+	}
+
+	// Handle any specializations that may still be present.
+	{
+		std::string oldParamPrefix = paramPrefix;
+		paramPrefix += "p";
+		std::list<ast::ptr<ast::Decl>> oldDecls;
+		oldDecls.splice( oldDecls.end(), declsToAddBefore );
+
+		app->accept( *visitor );
+		// Write recursive specializations into the thunk body.
+		for ( const ast::ptr<ast::Decl> & decl : declsToAddBefore ) {
+			thunkBody->push_back( new ast::DeclStmt( decl->location, decl ) );
+		}
+
+		declsToAddBefore = std::move( oldDecls );
+		paramPrefix = std::move( oldParamPrefix );
+	}
+
+	// Add return (or valueless expression) to the thunk.
+	ast::Stmt * appStmt;
+	if ( funType->returns.empty() ) {
+		appStmt = new ast::ExprStmt( app->location, app );
+	} else {
+		appStmt = new ast::ReturnStmt( app->location, app );
+	}
+	thunkBody->push_back( appStmt );
+
+	// Add the thunk definition:
+	declsToAddBefore.push_back( thunkFunc );
+
+	// Return address of thunk function as replacement expression.
+	return new ast::AddressExpr( location,
+		new ast::VariableExpr( location, thunkFunc ) );
+}
+
+const ast::Expr * SpecializeCore::doSpecialization(
+		const CodeLocation & location,
+		const ast::Type * formalType,
+		const ast::Expr * actual,
+		const ast::InferredParams * inferParams ) {
+	assertf( actual->result, "attempting to specialize an untyped expression" );
+	if ( needsSpecialization( formalType, actual->result, typeSubs ) ) {
+		if ( const ast::FunctionType * type = getFunctionType( formalType ) ) {
+			if ( const ast::ApplicationExpr * expr =
+					dynamic_cast<const ast::ApplicationExpr *>( actual ) ) {
+				return createThunkFunction( location, type, expr->func, inferParams );
+			} else if ( auto expr =
+					dynamic_cast<const ast::VariableExpr *>( actual ) ) {
+				return createThunkFunction( location, type, expr, inferParams );
+			} else {
+				// (I don't even know what that comment means.)
+				// This likely won't work, as anything that could build an ApplicationExpr probably hit one of the previous two branches
+				return createThunkFunction( location, type, actual, inferParams );
+			}
+		} else {
+			return actual;
+		}
+	} else {
+		return actual;
+	}
+}
+
+const ast::Expr * SpecializeCore::postvisit(
+		const ast::ApplicationExpr * expr ) {
+	if ( InitTweak::isIntrinsicCallExpr( expr ) ) {
+		return expr;
+	}
+
+	// Create thunks for the inferred parameters.
+	// This is not needed for intrinsic calls, because they aren't
+	// actually passed
+	//
+	// need to handle explicit params before inferred params so that
+	// explicit params do not recieve a changed set of inferParams (and
+	// change them again) alternatively, if order starts to matter then
+	// copy appExpr's inferParams and pass them to handleExplicitParams.
+	ast::ApplicationExpr * mut = handleExplicitParams( expr );
+	if ( !mut->inferred.hasParams() ) {
+		return mut;
+	}
+	ast::InferredParams & inferParams = mut->inferred.inferParams();
+	for ( ast::InferredParams::value_type & inferParam : inferParams ) {
+		inferParam.second.expr = doSpecialization(
+			inferParam.second.expr->location,
+			inferParam.second.formalType,
+			inferParam.second.expr,
+			getInferredParams( inferParam.second.expr )
+		);
+	}
+	return mut;
+}
+
+const ast::Expr * SpecializeCore::postvisit( const ast::CastExpr * expr ) {
+	if ( expr->result->isVoid() ) {
+		// No specialization if there is no return value.
+		return expr;
+	}
+	const ast::Expr * specialized = doSpecialization(
+		expr->location, expr->result, expr->arg, getInferredParams( expr ) );
+	if ( specialized != expr->arg ) {
+		// Assume that the specialization incorporates the cast.
+		std::cerr << expr <<std::endl;
+		return specialized;
+	} else {
+		return expr;
+	}
+}
+
+} // namespace
+
+void convertSpecializations( ast::TranslationUnit & translationUnit ) {
+	ast::Pass<SpecializeCore>::run( translationUnit );
+}
+
+} // namespace GenPoly
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/GenPoly/module.mk
===================================================================
--- src/GenPoly/module.mk	(revision 76a798d60267d1ba1ad46d084a0f9edf5f2b02fc)
+++ src/GenPoly/module.mk	(revision 9e23b446e321a87bbf5f2439c8d555a808d5e53c)
@@ -34,4 +34,5 @@
 	GenPoly/ScrubTyVars.h \
 	GenPoly/Specialize.cc \
+	GenPoly/SpecializeNew.cpp \
 	GenPoly/Specialize.h
 
Index: src/InitTweak/FixInitNew.cpp
===================================================================
--- src/InitTweak/FixInitNew.cpp	(revision 76a798d60267d1ba1ad46d084a0f9edf5f2b02fc)
+++ src/InitTweak/FixInitNew.cpp	(revision 9e23b446e321a87bbf5f2439c8d555a808d5e53c)
@@ -73,5 +73,5 @@
 	/// wrap function application expressions as ImplicitCopyCtorExpr nodes so that it is easy to identify which
 	/// function calls need their parameters to be copy constructed
-	struct InsertImplicitCalls : public ast::WithConstTypeSubstitution, public ast::WithShortCircuiting {
+	struct InsertImplicitCalls : public ast::WithShortCircuiting {
 		const ast::Expr * postvisit( const ast::ApplicationExpr * appExpr );
 
@@ -457,5 +457,4 @@
 		// is needed to obtain the type of temporary variables so that copy
 		// constructor calls can be resolved.
-		assert( typeSubs );
 		expr->env = tmp;
 		return expr;
Index: src/ResolvExpr/CandidateFinder.cpp
===================================================================
--- src/ResolvExpr/CandidateFinder.cpp	(revision 76a798d60267d1ba1ad46d084a0f9edf5f2b02fc)
+++ src/ResolvExpr/CandidateFinder.cpp	(revision 9e23b446e321a87bbf5f2439c8d555a808d5e53c)
@@ -1265,4 +1265,12 @@
 					newExpr, copy( tenv ), ast::OpenVarSet{}, ast::AssertionSet{}, Cost::zero,
 					cost );
+
+				if (newCand->expr->env) {
+					newCand->env.add(*newCand->expr->env);
+					auto mutExpr = newCand->expr.get_and_mutate();
+					mutExpr->env  = nullptr;
+					newCand->expr = mutExpr;
+				}
+
 				PRINT(
 					std::cerr << "decl is ";
Index: src/ResolvExpr/Resolver.cc
===================================================================
--- src/ResolvExpr/Resolver.cc	(revision 76a798d60267d1ba1ad46d084a0f9edf5f2b02fc)
+++ src/ResolvExpr/Resolver.cc	(revision 9e23b446e321a87bbf5f2439c8d555a808d5e53c)
@@ -1555,7 +1555,10 @@
 		if ( type->dimension ) {
 			ast::ptr< ast::Type > sizeType = context.global.sizeType;
+			ast::ptr< ast::Expr > dimension = findSingleExpression( type->dimension, sizeType, context );
+			assertf(dimension->env->empty(), "array dimension expr has nonempty env");
+			dimension.get_and_mutate()->env = nullptr;
 			ast::mutate_field(
 				type, &PtrType::dimension,
-				findSingleExpression( type->dimension, sizeType, context ) );
+				dimension);
 		}
 		return type;
@@ -2008,4 +2011,7 @@
 				tmp->accept( *visitor );
 			}
+			else if (expr->env && expr->env->empty()) {
+				expr = ast::mutate_field(expr.get(), &ast::Expr::env, nullptr);
+			}
 		}
 	}
Index: src/main.cc
===================================================================
--- src/main.cc	(revision 76a798d60267d1ba1ad46d084a0f9edf5f2b02fc)
+++ src/main.cc	(revision 9e23b446e321a87bbf5f2439c8d555a808d5e53c)
@@ -449,4 +449,6 @@
 			PASS( "Translate Tries", ControlStruct::translateTries( transUnit ) );
 			PASS( "Gen Waitfor", Concurrency::generateWaitFor( transUnit ) );
+			PASS( "Convert Specializations",  GenPoly::convertSpecializations( transUnit ) ); // needs to happen before tuple types are expanded
+
 
 			translationUnit = convert( move( transUnit ) );
@@ -520,7 +522,10 @@
 			PASS( "Translate Tries", ControlStruct::translateTries( translationUnit ) );
 			PASS( "Gen Waitfor", Concurrency::generateWaitFor( translationUnit ) );
+			PASS( "Convert Specializations",  GenPoly::convertSpecializations( translationUnit ) ); // needs to happen before tuple types are expanded
+
 		}
 
-		PASS( "Convert Specializations",  GenPoly::convertSpecializations( translationUnit ) ); // needs to happen before tuple types are expanded
+
+		// PASS( "Convert Specializations",  GenPoly::convertSpecializations( translationUnit ) ); // needs to happen before tuple types are expanded
 
 		PASS( "Expand Tuples", Tuples::expandTuples( translationUnit ) ); // xxx - is this the right place for this?
