Index: src/GenPoly/Box.cc
===================================================================
--- src/GenPoly/Box.cc	(revision 626dbc100f84707022515f68526522de85c2f653)
+++ src/GenPoly/Box.cc	(revision b940dc71b9b64989729859feb28dab18705ea7e3)
@@ -1127,7 +1127,10 @@
 			makeTyVarMap( function, exprTyVars ); // xxx - should this take into account the variables already bound in scopeTyVars (i.e. remove them from exprTyVars?)
 			ReferenceToType *dynRetType = isDynRet( function, exprTyVars );
-			Type *concRetType = appExpr->get_result()->isVoid() ? nullptr : appExpr->get_result();// ?: dynRetType; // xxx - is concRetType a good name?
-
+
+			// NOTE: addDynRetParam needs to know the actual (generated) return type so it can make a temp variable, so pass the result type from the appExpr
+			// passTypeVars needs to know the program-text return type (i.e. the distinction between _conc_T30 and T3(int))
+			// concRetType may not be a good name in one or both of these places. A more appropriate name change is welcome.
 			if ( dynRetType ) {
+				Type *concRetType = appExpr->get_result()->isVoid() ? nullptr : appExpr->get_result();
 				ret = addDynRetParam( appExpr, function, concRetType, arg ); // xxx - used to use dynRetType instead of concRetType
 			} else if ( needsAdapter( function, scopeTyVars ) && ! needsAdapter( function, exprTyVars) ) { // xxx - exprTyVars is used above...?
@@ -1142,4 +1145,5 @@
 			arg = appExpr->get_args().begin();
 
+			Type *concRetType = replaceWithConcrete( appExpr, dynRetType );
 			passTypeVars( appExpr, concRetType, arg, exprTyVars ); // xxx - used to use dynRetType instead of concRetType; this changed so that the correct type paramaters are passed for return types (it should be the concrete type's parameters, not the formal type's)
 			addInferredParams( appExpr, function, arg, exprTyVars );
Index: src/GenPoly/InstantiateGeneric.cc
===================================================================
--- src/GenPoly/InstantiateGeneric.cc	(revision 626dbc100f84707022515f68526522de85c2f653)
+++ src/GenPoly/InstantiateGeneric.cc	(revision b940dc71b9b64989729859feb28dab18705ea7e3)
@@ -18,4 +18,5 @@
 #include <utility>
 #include <vector>
+#include <unordered_map>
 
 #include "InstantiateGeneric.h"
@@ -24,4 +25,5 @@
 #include "GenPoly.h"
 #include "ScopedSet.h"
+#include "PolyMutator.h"
 
 #include "ResolvExpr/typeops.h"
@@ -146,4 +148,20 @@
 	}
 
+	// collect the environments of each TypeInstType so that type variables can be replaced
+	// xxx - possibly temporary solution. Access to type environments is required in GenericInstantiator, but it needs to be a DeclMutator which does not provide easy access to the type environments.
+	class EnvFinder final : public GenPoly::PolyMutator {
+	public:
+		virtual Type * mutate( TypeInstType * inst ) override {
+			if ( env ) envMap[inst] = env;
+			return inst;
+		}
+
+		// don't want to associate an environment with TypeInstTypes that occur in function types - this may actually only apply to function types belonging to DeclarationWithTypes (or even just FunctionDecl)?
+		virtual Type * mutate( FunctionType * ftype ) override {
+			return ftype;
+		}
+		std::unordered_map< ReferenceToType *, TypeSubstitution * > envMap;
+	};
+
 	/// Mutator pass that replaces concrete instantiations of generic types with actual struct declarations, scoped appropriately
 	class GenericInstantiator final : public DeclMutator {
@@ -154,7 +172,8 @@
 		/// Namer for concrete types
 		UniqueName typeNamer;
-
+		/// Reference to mapping of environments
+		const std::unordered_map< ReferenceToType *, TypeSubstitution * > & envMap;
 	public:
-		GenericInstantiator() : DeclMutator(), instantiations(), dtypeStatics(), typeNamer("_conc_") {}
+		GenericInstantiator( const std::unordered_map< ReferenceToType *, TypeSubstitution * > & envMap ) : DeclMutator(), instantiations(), dtypeStatics(), typeNamer("_conc_"), envMap( envMap ) {}
 
 		using DeclMutator::mutate;
@@ -174,4 +193,7 @@
 		void insert( UnionInstType *inst, const std::list< TypeExpr* > &typeSubs, UnionDecl *decl ) { instantiations.insert( inst->get_baseUnion(), typeSubs, decl ); }
 
+		void replaceParametersWithConcrete( std::list< Expression* >& params );
+		Type *replaceWithConcrete( Type *type, bool doClone );
+
 		/// Strips a dtype-static aggregate decl of its type parameters, marks it as stripped
 		void stripDtypeParams( AggregateDecl *base, std::list< TypeDecl* >& baseParams, const std::list< TypeExpr* >& typeSubs );
@@ -179,5 +201,7 @@
 
 	void instantiateGeneric( std::list< Declaration* > &translationUnit ) {
-		GenericInstantiator instantiator;
+		EnvFinder finder;
+		mutateAll( translationUnit, finder );
+		GenericInstantiator instantiator( finder.envMap );
 		instantiator.mutateDeclarationList( translationUnit );
 	}
@@ -209,4 +233,7 @@
 				// can pretend that any ftype is `void (*)(void)`
 				out.push_back( new TypeExpr( new FunctionType( Type::Qualifiers(), false ) ) );
+				break;
+			case TypeDecl::Ttype:
+				assertf( false, "Ttype parameters are not currently allowed as parameters to generic types." );
 				break;
 			}
@@ -253,4 +280,40 @@
 	}
 
+	/// xxx - more or less copied from box -- these should be merged with those somehow...
+	void GenericInstantiator::replaceParametersWithConcrete( std::list< Expression* >& params ) {
+		for ( std::list< Expression* >::iterator param = params.begin(); param != params.end(); ++param ) {
+			TypeExpr *paramType = dynamic_cast< TypeExpr* >( *param );
+			assertf(paramType, "Aggregate parameters should be type expressions");
+			paramType->set_type( replaceWithConcrete( paramType->get_type(), false ) );
+		}
+	}
+
+	Type *GenericInstantiator::replaceWithConcrete( Type *type, bool doClone ) {
+		if ( TypeInstType *typeInst = dynamic_cast< TypeInstType * >( type ) ) {
+			if ( envMap.count( typeInst ) ) {
+				TypeSubstitution * env = envMap.at( typeInst );
+				Type *concrete = env->lookup( typeInst->get_name() );
+				if ( concrete ) {
+					return concrete->clone();
+				}
+				else return typeInst->clone();
+			}
+		} else if ( StructInstType *structType = dynamic_cast< StructInstType* >( type ) ) {
+			if ( doClone ) {
+				structType = structType->clone();
+			}
+			replaceParametersWithConcrete( structType->get_parameters() );
+			return structType;
+		} else if ( UnionInstType *unionType = dynamic_cast< UnionInstType* >( type ) ) {
+			if ( doClone ) {
+				unionType = unionType->clone();
+			}
+			replaceParametersWithConcrete( unionType->get_parameters() );
+			return unionType;
+		}
+		return type;
+	}
+
+
 	Type* GenericInstantiator::mutate( StructInstType *inst ) {
 		// mutate subtypes
@@ -262,4 +325,7 @@
 		if ( inst->get_parameters().empty() ) return inst;
 
+		// need to replace type variables to ensure that generic types are instantiated for the return values of polymorphic functions (in particular, for thunks, because they are not [currently] copy constructed).
+		replaceWithConcrete( inst, false );
+
 		// check for an already-instantiatiated dtype-static type
 		if ( dtypeStatics.find( inst->get_baseStruct() ) != dtypeStatics.end() ) {
@@ -269,5 +335,5 @@
 
 		// check if type can be concretely instantiated; put substitutions into typeSubs
-		assert( inst->get_baseParameters() && "Base struct has parameters" );
+		assertf( inst->get_baseParameters(), "Base struct has parameters" );
 		std::list< TypeExpr* > typeSubs;
 		genericType gt = makeSubstitutions( *inst->get_baseParameters(), inst->get_parameters(), typeSubs );
