Index: src/GenPoly/Specialize.cc
===================================================================
--- src/GenPoly/Specialize.cc	(revision 6f950007fd85cf2f04928840823d690ed9c9d98b)
+++ src/GenPoly/Specialize.cc	(revision 395fc378ab2a1e951811ea084320ee6ea6f95015)
@@ -77,7 +77,34 @@
 	}
 
+	/// True if both types have the same structure, but not necessarily the same types.
+	/// That is, either both types are tuple types with the same size (recursively), or
+	/// both are not tuple types.
+	bool matchingTupleStructure( Type * t1, Type * t2 ) {
+		TupleType * tuple1 = dynamic_cast< TupleType * >( t1 );
+		TupleType * tuple2 = dynamic_cast< TupleType * >( t2 );
+		if ( tuple1 && tuple2 ) {
+			if ( tuple1->size() != tuple2->size() ) return false;
+			for ( auto types : group_iterate( tuple1->get_types(), tuple2->get_types() ) ) {
+				if ( ! matchingTupleStructure( std::get<0>( types ), std::get<1>( types ) ) ) return false;
+			}
+			return true;
+		} else if ( ! tuple1 && ! tuple2 ) return true;
+		return false;
+	}
+
 	bool needsTupleSpecialization( Type *formalType, Type *actualType, TypeSubstitution *env ) {
-		if ( FunctionType * ftype = getFunctionType( formalType ) ) {
-			return ftype->isTtype();
+		// 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 ( FunctionType * fftype = getFunctionType( formalType ) ) {
+			if ( fftype->isTtype() ) return true;
+			FunctionType * aftype = getFunctionType( actualType );
+			assertf( aftype, "formal type is a function type, but actual type is not." );
+			if ( fftype->get_parameters().size() != aftype->get_parameters().size() ) return true;
+			for ( auto params : group_iterate( fftype->get_parameters(), aftype->get_parameters() ) ) {
+				DeclarationWithType * formal = std::get<0>(params);
+				DeclarationWithType * actual = std::get<1>(params);
+				if ( ! matchingTupleStructure( formal->get_type(), actual->get_type() ) ) return true;
+			}
 		}
 		return false;
@@ -110,35 +137,32 @@
 	}
 
-	/// restructures arg to match the structure of a single formal parameter. Assumes that atomic types are compatible (as the Resolver should have ensured this)
-	template< typename OutIterator >
-	void matchOneFormal( Expression * arg, unsigned & idx, Type * formal, OutIterator out ) {
-		if ( TupleType * tupleType = dynamic_cast< TupleType * >( formal ) ) {
+	/// 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( Type * type, Iterator & begin, Iterator end, OutIterator out ) {
+		if ( TupleType * tuple = dynamic_cast< TupleType * >( type ) ) {
 			std::list< Expression * > exprs;
-			for ( Type * t : *tupleType ) {
-				matchOneFormal( arg, idx, t, back_inserter( exprs ) );
+			for ( Type * t : *tuple ) {
+				structureArg( t, begin, end, back_inserter( exprs ) );
 			}
 			*out++ = new TupleExpr( exprs );
 		} else {
-			*out++ = new TupleIndexExpr( arg->clone(), idx++ );
-		}
-	}
-
-	/// restructures the ttype argument to match the structure of the formal parameters of the actual function.
-	/// [begin, end) are the formal parameters.
-	/// args is the list of arguments currently given to the actual function, the last of which needs to be restructured.
-	template< typename Iterator, typename OutIterator >
-	void fixLastArg( Expression * last, Iterator begin, Iterator end, OutIterator out ) {
-		if ( Tuples::isTtype( last->get_result() ) ) {
-			*out++ = last;
-		} else {
-			// safe_dynamic_cast for the assertion
-			safe_dynamic_cast< TupleType * >( last->get_result() );
-			unsigned idx = 0;
-			for ( ; begin != end; ++begin ) {
-				DeclarationWithType * formal = *begin;
-				Type * formalType = formal->get_type();
-				matchOneFormal( last, idx, formalType, out );
-			}
-			delete last;
+			assertf( begin != end, "reached the end of the arguments while structuring" );
+			*out++ = *begin++;
+		}
+	}
+
+	/// explode assuming simple cases: either type is pure tuple (but not tuple expr) or type is non-tuple.
+	template< typename OutputIterator >
+	void explodeSimple( Expression * expr, OutputIterator out ) {
+		if ( TupleType * tupleType = dynamic_cast< TupleType * > ( expr->get_result() ) ) {
+			// tuple type, recursively index into its components
+			for ( unsigned int i = 0; i < tupleType->size(); i++ ) {
+				explodeSimple( new TupleIndexExpr( expr->clone(), i ), out );
+			}
+			delete expr;
+		} else {
+			// non-tuple type - output a clone of the expression
+			*out++ = expr;
 		}
 	}
@@ -173,21 +197,18 @@
 		std::list< DeclarationWithType * >::iterator actualBegin = actualType->get_parameters().begin();
 		std::list< DeclarationWithType * >::iterator actualEnd = actualType->get_parameters().end();
-		std::list< DeclarationWithType * >::iterator formalBegin = funType->get_parameters().begin();
-		std::list< DeclarationWithType * >::iterator formalEnd = funType->get_parameters().end();
-
+
+		std::list< Expression * > args;
 		for ( DeclarationWithType* param : thunkFunc->get_functionType()->get_parameters() ) {
-			// walk the parameters to the actual function alongside the parameters to the thunk to find the location where the ttype parameter begins to satisfy parameters in the actual function.
+			// name each thunk parameter and explode it - these are then threaded back into the actual function call.
 			param->set_name( paramNamer.newName() );
-			assertf( formalBegin != formalEnd, "Reached end of formal parameters before finding ttype parameter" );
-			if ( Tuples::isTtype((*formalBegin)->get_type()) ) {
-				fixLastArg( new VariableExpr( param ), actualBegin, actualEnd, back_inserter( appExpr->get_args() ) );
-				break;
-			}
-			assertf( actualBegin != actualEnd, "reached end of actual function's arguments before finding ttype parameter" );
-			++actualBegin;
-			++formalBegin;
-
-			appExpr->get_args().push_back( new VariableExpr( param ) );
-		} // for
+			explodeSimple( new VariableExpr( param ), back_inserter( args ) );
+		}
+
+		// walk parameters to the actual function alongside the exploded thunk parameters and restructure the arguments to match the actual parameters.
+		std::list< Expression * >::iterator argBegin = args.begin(), argEnd = args.end();
+		for ( ; actualBegin != actualEnd; ++actualBegin ) {
+			structureArg( (*actualBegin)->get_type(), argBegin, argEnd, back_inserter( appExpr->get_args() ) );
+		}
+
 		appExpr->set_env( maybeClone( env ) );
 		if ( inferParams ) {
Index: src/InitTweak/GenInit.cc
===================================================================
--- src/InitTweak/GenInit.cc	(revision 6f950007fd85cf2f04928840823d690ed9c9d98b)
+++ src/InitTweak/GenInit.cc	(revision 395fc378ab2a1e951811ea084320ee6ea6f95015)
@@ -218,4 +218,7 @@
 
 	bool CtorDtor::isManaged( Type * type ) const {
+		// need to clear and reset qualifiers when determining if a type is managed
+		ValueGuard< Type::Qualifiers > qualifiers( type->get_qualifiers() );
+		type->get_qualifiers() = Type::Qualifiers();
 		if ( TupleType * tupleType = dynamic_cast< TupleType * > ( type ) ) {
 			// tuple is also managed if any of its components are managed
Index: src/Parser/parser.yy
===================================================================
--- src/Parser/parser.yy	(revision 6f950007fd85cf2f04928840823d690ed9c9d98b)
+++ src/Parser/parser.yy	(revision 395fc378ab2a1e951811ea084320ee6ea6f95015)
@@ -412,7 +412,11 @@
 		{ $$ = new ExpressionNode( build_fieldSel( $1, build_field_name_REALFRACTIONconstant( *$2 ) ) ); }
 	| postfix_expression ARROW no_attr_identifier
-		{ $$ = new ExpressionNode( build_pfieldSel( $1, build_varref( $3 ) ) ); }
+		{
+			$$ = new ExpressionNode( build_pfieldSel( $1, *$3 == "0" || *$3 == "1" ? build_constantInteger( *$3 ) : build_varref( $3 ) ) );
+		}
 	| postfix_expression ARROW '[' push field_list pop ']' // CFA, tuple field selector
 			{ $$ = new ExpressionNode( build_pfieldSel( $1, build_tuple( $5 ) ) ); }
+	| postfix_expression ARROW INTEGERconstant			// CFA, tuple index
+		{ $$ = new ExpressionNode( build_pfieldSel( $1, build_constantInteger( *$3 ) ) ); }
 	| postfix_expression ICR
 	  	{ $$ = new ExpressionNode( build_unary_ptr( OperKinds::IncrPost, $1 ) ); }
Index: src/ResolvExpr/Resolver.cc
===================================================================
--- src/ResolvExpr/Resolver.cc	(revision 6f950007fd85cf2f04928840823d690ed9c9d98b)
+++ src/ResolvExpr/Resolver.cc	(revision 395fc378ab2a1e951811ea084320ee6ea6f95015)
@@ -240,4 +240,16 @@
 		functionReturn = ResolvExpr::extractResultType( functionDecl->get_functionType() );
 		Parent::visit( functionDecl );
+
+		// default value expressions have an environment which shouldn't be there and trips up later passes.
+		// xxx - it might be necessary to somehow keep the information from this environment, but I can't currently
+		// see how it's useful.
+		for ( Declaration * d : functionDecl->get_functionType()->get_parameters() ) {
+			if ( ObjectDecl * obj = dynamic_cast< ObjectDecl * >( d ) ) {
+				if ( SingleInit * init = dynamic_cast< SingleInit * >( obj->get_init() ) ) {
+					delete init->get_value()->get_env();
+					init->get_value()->set_env( nullptr );
+				}
+			}
+		}
 	}
 
Index: src/SymTab/Autogen.cc
===================================================================
--- src/SymTab/Autogen.cc	(revision 6f950007fd85cf2f04928840823d690ed9c9d98b)
+++ src/SymTab/Autogen.cc	(revision 395fc378ab2a1e951811ea084320ee6ea6f95015)
@@ -151,5 +151,5 @@
 	bool hasDynamicLayout( AggrDecl * aggregateDecl ) {
 		for ( TypeDecl * param : aggregateDecl->get_parameters() ) {
-			if ( param->get_kind() == TypeDecl::Any ) return true;
+			if ( param->isComplete() ) return true;
 		}
 		return false;
Index: src/SymTab/Indexer.cc
===================================================================
--- src/SymTab/Indexer.cc	(revision 6f950007fd85cf2f04928840823d690ed9c9d98b)
+++ src/SymTab/Indexer.cc	(revision 395fc378ab2a1e951811ea084320ee6ea6f95015)
@@ -119,11 +119,13 @@
 				FunctionDecl * decl;
 				bool isUserDefinedFunc; // properties for this particular decl
-				bool isDefaultFunc;
+				bool isDefaultCtor;
+				bool isDtor;
 				bool isCopyFunc;
 			};
 			// properties for this type
-			bool userDefinedFunc = false; // any user defined function found
-			bool userDefinedDefaultFunc = false; // user defined default ctor found
-			bool userDefinedCopyFunc = false; // user defined copy ctor found
+			bool userDefinedFunc = false; // any user-defined function found
+			bool userDefinedCtor = false; // any user-defined constructor found
+			bool userDefinedDtor = false; // any user-defined destructor found
+			bool userDefinedCopyFunc = false; // user-defined copy ctor found
 			std::list< DeclBall > decls;
 
@@ -132,9 +134,11 @@
 			ValueType & operator+=( FunctionDecl * function ) {
 				bool isUserDefinedFunc = ! LinkageSpec::isOverridable( function->get_linkage() );
-				bool isDefaultFunc = function->get_functionType()->get_parameters().size() == 1;
+				bool isDefaultCtor = InitTweak::isDefaultConstructor( function );
+				bool isDtor = InitTweak::isDestructor( function );
 				bool isCopyFunc = InitTweak::isCopyFunction( function, function->get_name() );
-				decls.push_back( DeclBall{ function, isUserDefinedFunc, isDefaultFunc, isCopyFunc } );
+				decls.push_back( DeclBall{ function, isUserDefinedFunc, isDefaultCtor, isDtor, isCopyFunc } );
 				userDefinedFunc = userDefinedFunc || isUserDefinedFunc;
-				userDefinedDefaultFunc = userDefinedDefaultFunc || (isUserDefinedFunc && isDefaultFunc);
+				userDefinedCtor = userDefinedCtor || (isUserDefinedFunc && InitTweak::isConstructor( function->get_name() ) );
+				userDefinedDtor = userDefinedDtor || (isUserDefinedFunc && isDtor);
 				userDefinedCopyFunc = userDefinedCopyFunc || (isUserDefinedFunc && isCopyFunc);
 				return *this;
@@ -163,8 +167,9 @@
 		// a default ctor, then the generated default ctor should never be seen, likewise for copy ctor
 		// and dtor. If the user defines any ctor/dtor, then no generated field ctors should be seen.
+		// If the user defines any ctor then the generated default ctor should not be seen.
 		for ( std::pair< const std::string, ValueType > & pair : funcMap ) {
 			ValueType & val = pair.second;
 			for ( ValueType::DeclBall ball : val.decls ) {
-				if ( ! val.userDefinedFunc || ball.isUserDefinedFunc || (! val.userDefinedDefaultFunc && ball.isDefaultFunc) || (! val.userDefinedCopyFunc && ball.isCopyFunc) ) {
+				if ( ! val.userDefinedFunc || ball.isUserDefinedFunc || (! val.userDefinedCtor && ball.isDefaultCtor) || (! val.userDefinedCopyFunc && ball.isCopyFunc) || (! val.userDefinedDtor && ball.isDtor) ) {
 					// decl conforms to the rules described above, so it should be seen by the requester
 					out.push_back( ball.decl );
Index: src/SymTab/Validate.cc
===================================================================
--- src/SymTab/Validate.cc	(revision 6f950007fd85cf2f04928840823d690ed9c9d98b)
+++ src/SymTab/Validate.cc	(revision 395fc378ab2a1e951811ea084320ee6ea6f95015)
@@ -224,7 +224,7 @@
 		HoistStruct::hoistStruct( translationUnit );
 		ReturnTypeFixer::fix( translationUnit ); // must happen before autogen
+		acceptAll( translationUnit, lrt ); // must happen before autogen, because sized flag needs to propagate to generated functions
 		autogenerateRoutines( translationUnit ); // moved up, used to be below compoundLiteral - currently needs EnumAndPointerDecayPass
 		acceptAll( translationUnit, epc );
-		acceptAll( translationUnit, lrt );
 		ReturnChecker::checkFunctionReturns( translationUnit );
 		compoundliteral.mutateDeclarationList( translationUnit );
@@ -840,6 +840,6 @@
 		assertf( retVals.size() == 0 || retVals.size() == 1, "Function %s has too many return values: %d", functionDecl->get_name().c_str(), retVals.size() );
 		if ( retVals.size() == 1 ) {
-			// ensure all function return values have a name - use the name of the function to disambiguate (this also provides a nice bit of help for debugging)
-			// ensure other return values have a name
+			// ensure all function return values have a name - use the name of the function to disambiguate (this also provides a nice bit of help for debugging).
+			// ensure other return values have a name.
 			DeclarationWithType * ret = retVals.front();
 			if ( ret->get_name() == "" ) {
Index: src/SynTree/TypeDecl.cc
===================================================================
--- src/SynTree/TypeDecl.cc	(revision 6f950007fd85cf2f04928840823d690ed9c9d98b)
+++ src/SynTree/TypeDecl.cc	(revision 395fc378ab2a1e951811ea084320ee6ea6f95015)
@@ -25,6 +25,6 @@
 
 std::string TypeDecl::typeString() const {
-	static const char *kindNames[] = { "type", "incomplete type", "function type", "tuple type" };
-	return kindNames[ kind ];
+	static const std::string kindNames[] = { "type", "incomplete type", "function type", "tuple type" };
+	return (kind != Any && isComplete() ? "sized " : "") + kindNames[ kind ];
 }
 
Index: src/tests/.expect/tuplePolymorphism.txt
===================================================================
--- src/tests/.expect/tuplePolymorphism.txt	(revision 6f950007fd85cf2f04928840823d690ed9c9d98b)
+++ src/tests/.expect/tuplePolymorphism.txt	(revision 395fc378ab2a1e951811ea084320ee6ea6f95015)
@@ -3,2 +3,3 @@
 123 999.123 456
 246 1998.25 912
+1.21 x 10.21 1111 v 54385938 1111 v 54385938
Index: src/tests/memberCtors.c
===================================================================
--- src/tests/memberCtors.c	(revision 6f950007fd85cf2f04928840823d690ed9c9d98b)
+++ src/tests/memberCtors.c	(revision 395fc378ab2a1e951811ea084320ee6ea6f95015)
@@ -30,4 +30,8 @@
   WrappedInt x, y, z;
 };
+
+void ?{}(A * a) {
+  // currently must define default ctor, since there's no "= default" syntax
+}
 
 void ?{}(A * a, int x) {
Index: src/tests/tuplePolymorphism.c
===================================================================
--- src/tests/tuplePolymorphism.c	(revision 6f950007fd85cf2f04928840823d690ed9c9d98b)
+++ src/tests/tuplePolymorphism.c	(revision 395fc378ab2a1e951811ea084320ee6ea6f95015)
@@ -14,4 +14,23 @@
 //
 
+// packed is needed so that structs are not passed with the same alignment as function arguments
+__attribute__((packed)) struct A {
+  double x;
+  char y;
+  double z;
+};
+
+__attribute__((packed)) struct B {
+  long long x;
+  char y;
+  long long z;
+};
+
+// ensure that f is a viable candidate for g, even though its parameter structure does not exactly match
+[A] f([A, B] x, B y) { printf("%g %c %g %lld %c %lld %lld %c %lld\n", x.0.[x,y,z], x.1.[x,y,z], y.[x,y,z]); return x.0; }
+forall(otype T, otype U | { T f(T, U, U); })
+void g(T x, U y) { f(x, y, y); }
+
+// add two triples
 forall(otype T | { T ?+?(T, T); })
 [T, T, T] ?+?([T, T, T] x, [T, T, T] y) {
@@ -40,4 +59,7 @@
   [x1, x2, x3] = zzz+zzz;
   printf("%d %g %d\n", x1, x2, x3);
+
+  // ensure non-matching assertions are specialized correctly
+  g((A){ 1.21, 'x', 10.21}, (B){ 1111LL, 'v', 54385938LL });
 }
 
