Index: src/GenPoly/Specialize.cc
===================================================================
--- src/GenPoly/Specialize.cc	(revision e33f3213eb817855f65c2c1a3a7e12bdcc2831e5)
+++ src/GenPoly/Specialize.cc	(revision 626dbc100f84707022515f68526522de85c2f653)
@@ -34,10 +34,8 @@
 
 namespace GenPoly {
-	const std::list<Label> noLabels;
-
+	class Specializer;
 	class Specialize final : public PolyMutator {
+		friend class Specializer;
 	  public:
-		Specialize( std::string paramPrefix = "_p" );
-
 		using PolyMutator::mutate;
 		virtual Expression * mutate( ApplicationExpr *applicationExpr ) override;
@@ -48,24 +46,40 @@
 		// virtual Expression * mutate( CommaExpr *commaExpr );
 
-	  private:
-		Expression *createThunkFunction( FunctionType *funType, Expression *actual, InferredParams *inferParams = 0 );
-		Expression *doSpecialization( Type *formalType, Expression *actual, InferredParams *inferParams = 0 );
+		Specializer * specializer = nullptr;
 		void handleExplicitParams( ApplicationExpr *appExpr );
-
-		UniqueName thunkNamer;
-		std::string paramPrefix;
 	};
 
-	void convertSpecializations( std::list< Declaration* >& translationUnit ) {
-		Specialize specializer;
-		mutateAll( translationUnit, specializer );
-	}
-
-	Specialize::Specialize( std::string paramPrefix )
-		: thunkNamer( "_thunk" ), paramPrefix( paramPrefix ) {
-	}
+	class Specializer {
+	  public:
+		Specializer( Specialize & spec ) : spec( spec ), env( spec.env ), stmtsToAdd( spec.stmtsToAdd ) {}
+		virtual bool needsSpecialization( Type * formalType, Type * actualType, TypeSubstitution * env ) = 0;
+		virtual Expression *createThunkFunction( FunctionType *funType, Expression *actual, InferredParams *inferParams ) = 0;
+		virtual Expression *doSpecialization( Type *formalType, Expression *actual, InferredParams *inferParams = 0 );
+
+	  protected:
+		Specialize & spec;
+		std::string paramPrefix = "_p";
+		TypeSubstitution *& env;
+		std::list< Statement * > & stmtsToAdd;
+	};
+
+	// for normal polymorphic -> monomorphic function conversion
+	class PolySpecializer : public Specializer {
+	  public:
+		PolySpecializer( Specialize & spec ) : Specializer( spec ) {}
+		virtual bool needsSpecialization( Type * formalType, Type * actualType, TypeSubstitution * env ) override;
+		virtual Expression *createThunkFunction( FunctionType *funType, Expression *actual, InferredParams *inferParams ) override;
+	};
+
+	// // for tuple -> non-tuple function conversion
+	class TupleSpecializer : public Specializer {
+	  public:
+		TupleSpecializer( Specialize & spec ) : Specializer( spec ) {}
+		virtual bool needsSpecialization( Type * formalType, Type * actualType, TypeSubstitution * env ) override;
+		virtual Expression *createThunkFunction( FunctionType *funType, Expression *actual, InferredParams *inferParams ) override;
+	};
 
 	/// Looks up open variables in actual type, returning true if any of them are bound in the environment or formal type.
-	bool needsSpecialization( Type *formalType, Type *actualType, TypeSubstitution *env ) {
+	bool PolySpecializer::needsSpecialization( Type *formalType, Type *actualType, TypeSubstitution *env ) {
 		if ( env ) {
 			using namespace ResolvExpr;
@@ -92,5 +106,7 @@
 
 	/// Generates a thunk that calls `actual` with type `funType` and returns its address
-	Expression * Specialize::createThunkFunction( FunctionType *funType, Expression *actual, InferredParams *inferParams ) {
+	Expression * PolySpecializer::createThunkFunction( FunctionType *funType, Expression *actual, InferredParams *inferParams ) {
+		static UniqueName thunkNamer( "_thunk" );
+
 		FunctionType *newType = funType->clone();
 		if ( env ) {
@@ -125,5 +141,5 @@
 		std::list< Statement* > oldStmts;
 		oldStmts.splice( oldStmts.end(), stmtsToAdd );
-		handleExplicitParams( appExpr );
+		spec.handleExplicitParams( appExpr );
 		paramPrefix = oldParamPrefix;
 		// write any statements added for recursive specializations into the thunk body
@@ -147,5 +163,5 @@
 	}
 
-	Expression * Specialize::doSpecialization( Type *formalType, Expression *actual, InferredParams *inferParams ) {
+	Expression * Specializer::doSpecialization( Type *formalType, Expression *actual, InferredParams *inferParams ) {
 		assertf( actual->has_result(), "attempting to specialize an untyped expression" );
 		if ( needsSpecialization( formalType, actual->get_result(), env ) ) {
@@ -170,4 +186,100 @@
 	}
 
+	bool TupleSpecializer::needsSpecialization( Type *formalType, Type *actualType, TypeSubstitution *env ) {
+		// std::cerr << "asking if type needs tuple spec: " << formalType << std::endl;
+		if ( FunctionType * ftype = getFunctionType( formalType ) ) {
+			return ftype->isTtype();
+		}
+		return false;
+	}
+
+	template< typename Iterator >
+	void fixLastArg( std::list< Expression * > & args, Iterator begin, Iterator end ) {
+		assertf( ! args.empty(), "Somehow args to tuple function are empty" ); // xxx - it's quite possible this will trigger for the nullary case...
+		Expression * last = args.back();
+		// safe_dynamic_cast for the assertion
+		safe_dynamic_cast< TupleType * >( last->get_result() ); // xxx - it's quite possible this will trigger for the unary case...
+		args.pop_back(); // replace last argument in the call with
+		unsigned idx = 0;
+		for ( ; begin != end; ++begin ) {
+			// DeclarationWithType * formal = *begin;
+			// Type * formalType = formal->get_type();
+			args.push_back( new TupleIndexExpr( last->clone(), idx++ ) );
+		}
+		delete last;
+	}
+
+	Expression * TupleSpecializer::createThunkFunction( FunctionType *funType, Expression *actual, InferredParams *inferParams ) {
+		static UniqueName thunkNamer( "_tupleThunk" );
+		// std::cerr << "creating tuple thunk for " << funType << std::endl;
+
+		FunctionType *newType = funType->clone();
+		if ( env ) {
+			TypeSubstitution newEnv( *env );
+			// it is important to replace only occurrences of type variables that occur free in the
+			// thunk's type
+			newEnv.applyFree( newType );
+		} // if
+		// create new thunk with same signature as formal type (C linkage, empty body)
+		FunctionDecl *thunkFunc = new FunctionDecl( thunkNamer.newName(), DeclarationNode::NoStorageClass, LinkageSpec::C, newType, new CompoundStmt( noLabels ), false, false );
+		thunkFunc->fixUniqueId();
+
+		// thunks may be generated and not used - silence warning with attribute
+		thunkFunc->get_attributes().push_back( new Attribute( "unused" ) );
+
+		// thread thunk parameters into call to actual function, naming thunk parameters as we go
+		UniqueName paramNamer( paramPrefix );
+		ApplicationExpr *appExpr = new ApplicationExpr( actual );
+		// std::cerr << actual << std::endl;
+
+		FunctionType * actualType = getFunctionType( actual->get_result() );
+		std::list< DeclarationWithType * >::iterator begin = actualType->get_parameters().begin();
+		std::list< DeclarationWithType * >::iterator end = actualType->get_parameters().end();
+
+		for ( DeclarationWithType* param : thunkFunc->get_functionType()->get_parameters() ) {
+			++begin;
+			assert( begin != end );
+
+			// std::cerr << "thunk param: " << param << std::endl;
+			// last param will always be a tuple type... expand it into the actual type(?)
+			param->set_name( paramNamer.newName() );
+			appExpr->get_args().push_back( new VariableExpr( param ) );
+		} // for
+		fixLastArg( appExpr->get_args(), --begin, end );
+		appExpr->set_env( maybeClone( env ) );
+		if ( inferParams ) {
+			appExpr->get_inferParams() = *inferParams;
+		} // if
+
+		// handle any specializations that may still be present
+		std::string oldParamPrefix = paramPrefix;
+		paramPrefix += "p";
+		// save stmtsToAdd in oldStmts
+		std::list< Statement* > oldStmts;
+		oldStmts.splice( oldStmts.end(), stmtsToAdd );
+		spec.handleExplicitParams( appExpr );
+		paramPrefix = oldParamPrefix;
+		// write any statements added for recursive specializations into the thunk body
+		thunkFunc->get_statements()->get_kids().splice( thunkFunc->get_statements()->get_kids().end(), stmtsToAdd );
+		// restore oldStmts into stmtsToAdd
+		stmtsToAdd.splice( stmtsToAdd.end(), oldStmts );
+
+		// add return (or valueless expression) to the thunk
+		Statement *appStmt;
+		if ( funType->get_returnVals().empty() ) {
+			appStmt = new ExprStmt( noLabels, appExpr );
+		} else {
+			appStmt = new ReturnStmt( noLabels, appExpr );
+		} // if
+		thunkFunc->get_statements()->get_kids().push_back( appStmt );
+
+		// std::cerr << "thunkFunc is: " << thunkFunc << std::endl;
+
+		// add thunk definition to queue of statements to add
+		stmtsToAdd.push_back( new DeclStmt( noLabels, thunkFunc ) );
+		// return address of thunk function as replacement expression
+		return new AddressExpr( new VariableExpr( thunkFunc ) );
+	}
+
 	void Specialize::handleExplicitParams( ApplicationExpr *appExpr ) {
 		// create thunks for the explicit parameters
@@ -178,5 +290,5 @@
 		std::list< Expression* >::iterator actual;
 		for ( formal = function->get_parameters().begin(), actual = appExpr->get_args().begin(); formal != function->get_parameters().end() && actual != appExpr->get_args().end(); ++formal, ++actual ) {
-			*actual = doSpecialization( (*formal )->get_type(), *actual, &appExpr->get_inferParams() );
+			*actual = specializer->doSpecialization( (*formal )->get_type(), *actual, &appExpr->get_inferParams() );
 		}
 	}
@@ -190,10 +302,8 @@
 			// don't need to do this for intrinsic calls, because they aren't actually passed
 			for ( InferredParams::iterator inferParam = appExpr->get_inferParams().begin(); inferParam != appExpr->get_inferParams().end(); ++inferParam ) {
-				inferParam->second.expr = doSpecialization( inferParam->second.formalType, inferParam->second.expr, &appExpr->get_inferParams() );
+				inferParam->second.expr = specializer->doSpecialization( inferParam->second.formalType, inferParam->second.expr, &appExpr->get_inferParams() );
 			}
-
 			handleExplicitParams( appExpr );
 		}
-
 		return appExpr;
 	}
@@ -202,5 +312,5 @@
 		addrExpr->get_arg()->acceptMutator( *this );
 		assert( addrExpr->has_result() );
-		addrExpr->set_arg( doSpecialization( addrExpr->get_result(), addrExpr->get_arg() ) );
+		addrExpr->set_arg( specializer->doSpecialization( addrExpr->get_result(), addrExpr->get_arg() ) );
 		return addrExpr;
 	}
@@ -212,5 +322,5 @@
 			return castExpr;
 		}
-		Expression *specialized = doSpecialization( castExpr->get_result(), castExpr->get_arg() );
+		Expression *specialized = specializer->doSpecialization( castExpr->get_result(), castExpr->get_arg() );
 		if ( specialized != castExpr->get_arg() ) {
 			// assume here that the specialization incorporates the cast
@@ -236,4 +346,16 @@
 	// 	return commaExpr;
 	// }
+
+	void convertSpecializations( std::list< Declaration* >& translationUnit ) {
+		Specialize spec;
+
+		TupleSpecializer tupleSpec( spec );
+		spec.specializer = &tupleSpec;
+		mutateAll( translationUnit, spec );
+
+		PolySpecializer polySpec( spec );
+		spec.specializer = &polySpec;
+		mutateAll( translationUnit, spec );
+	}
 } // namespace GenPoly
 
Index: src/main.cc
===================================================================
--- src/main.cc	(revision e33f3213eb817855f65c2c1a3a7e12bdcc2831e5)
+++ src/main.cc	(revision 626dbc100f84707022515f68526522de85c2f653)
@@ -69,4 +69,5 @@
 	symtabp = false,
 	treep = false,
+	tuplep = false,
 	validp = false,
 	errorp = false,
@@ -266,6 +267,14 @@
 		OPTPRINT( "expandUniqueExpr" ); // xxx - is this the right place for this? want to expand ASAP so that subsequent passes don't need to worry about double-visiting a unique expr - needs to go after InitTweak::fix so that copy constructed return declarations are reused
 		Tuples::expandUniqueExpr( translationUnit );
+
+		OPTPRINT( "convertSpecializations" ) // needs to happen before tuple types are expanded
+		GenPoly::convertSpecializations( translationUnit );
+
 		OPTPRINT( "expandTuples" ); // xxx - is this the right place for this?
 		Tuples::expandTuples( translationUnit );
+		if ( tuplep ) {
+			dump( translationUnit );
+			return 0;
+		}
 
 		OPTPRINT("instantiateGenerics")
@@ -273,6 +282,4 @@
 		OPTPRINT( "copyParams" );
 		GenPoly::copyParams( translationUnit );
-		OPTPRINT( "convertSpecializations" )
-		GenPoly::convertSpecializations( translationUnit );
 		OPTPRINT( "convertLvalue" )
 		GenPoly::convertLvalue( translationUnit );
@@ -327,5 +334,5 @@
 
 void parse_cmdline( int argc, char * argv[], const char *& filename ) {
-	enum { Ast, Bbox, Bresolver, CtorInitFix, Expr, ExprAlt, Grammar, LibCFA, Nopreamble, Parse, Prototypes, Resolver, Symbol, Tree, Validate, };
+	enum { Ast, Bbox, Bresolver, CtorInitFix, Expr, ExprAlt, Grammar, LibCFA, Nopreamble, Parse, Prototypes, Resolver, Symbol, Tree, TupleExpansion, Validate, };
 
 	static struct option long_opts[] = {
@@ -344,4 +351,5 @@
 		{ "symbol", no_argument, 0, Symbol },
 		{ "tree", no_argument, 0, Tree },
+		{ "tuple-expansion", no_argument, 0, TupleExpansion },
 		{ "validate", no_argument, 0, Validate },
 		{ 0, 0, 0, 0 }
@@ -352,5 +360,5 @@
 
 	int c;
-	while ( (c = getopt_long( argc, argv, "abBcefglnpqrstvyzD:F:", long_opts, &long_index )) != -1 ) {
+	while ( (c = getopt_long( argc, argv, "abBcefglnpqrstTvyzD:F:", long_opts, &long_index )) != -1 ) {
 		switch ( c ) {
 		  case Ast:
@@ -362,5 +370,5 @@
 			bresolvep = true;
 			break;
-		  case 'B':										// print before resolver steps
+		  case 'B':										// print before box steps
 			bboxp = true;
 			break;
@@ -408,4 +416,8 @@
 		  case 't':										// build in tree
 			treep = true;
+			break;
+		  case TupleExpansion:
+		  case 'T':										// print after tuple expansion
+			tuplep = true;
 			break;
 		  case 'v':										// dump AST after decl validation pass
