Index: src/ResolvExpr/CandidateFinder.cpp
===================================================================
--- src/ResolvExpr/CandidateFinder.cpp	(revision 57e0289d894ddff6323ba0a0125d5ba3ef575462)
+++ src/ResolvExpr/CandidateFinder.cpp	(revision 1c1395d2efc85a2d060e6cc92d06033044b1144d)
@@ -188,10 +188,10 @@
 
 			// mark conversion cost and also specialization cost of param type
-			const ast::Type * paramType = (*param)->get_type();
+			// const ast::Type * paramType = (*param)->get_type();
 			cand->expr = ast::mutate_field_index(
 				appExpr, &ast::ApplicationExpr::args, i,
 				computeExpressionConversionCost(
-					args[i], paramType, symtab, cand->env, convCost ) );
-			convCost.decSpec( specCost( paramType ) );
+					args[i], *param, symtab, cand->env, convCost ) );
+			convCost.decSpec( specCost( *param ) );
 			++param;  // can't be in for-loop update because of the continue
 		}
@@ -698,5 +698,5 @@
 			if ( targetType && ! targetType->isVoid() && ! funcType->returns.empty() ) {
 				// attempt to narrow based on expected target type
-				const ast::Type * returnType = funcType->returns.front()->get_type();
+				const ast::Type * returnType = funcType->returns.front();
 				if ( ! unify(
 					returnType, targetType, funcEnv, funcNeed, funcHave, funcOpen, symtab )
@@ -712,12 +712,28 @@
 			std::size_t genStart = 0;
 
-			for ( const ast::DeclWithType * param : funcType->params ) {
-				auto obj = strict_dynamic_cast< const ast::ObjectDecl * >( param );
+			// xxx - how to handle default arg after change to ftype representation?
+			if (const ast::VariableExpr * varExpr = func->expr.as<ast::VariableExpr>()) {
+				if (const ast::FunctionDecl * funcDecl = varExpr->var.as<ast::FunctionDecl>()) {
+					// function may have default args only if directly calling by name
+					// must use types on candidate however, due to RenameVars substitution
+					auto nParams = funcType->params.size();
+
+					for (size_t i=0; i<nParams; ++i) {
+						auto obj = funcDecl->params[i].strict_as<ast::ObjectDecl>();
+						if (!instantiateArgument(
+							funcType->params[i], obj->init, args, results, genStart, symtab)) return;
+					}
+					goto endMatch;
+				}
+			}
+			for ( const auto & param : funcType->params ) {
 				// Try adding the arguments corresponding to the current parameter to the existing
 				// matches
+				// no default args for indirect calls
 				if ( ! instantiateArgument(
-					obj->type, obj->init, args, results, genStart, symtab ) ) return;
-			}
-
+					param, nullptr, args, results, genStart, symtab ) ) return;
+			}
+
+			endMatch:
 			if ( funcType->isVarArgs ) {
 				// append any unused arguments to vararg pack
Index: src/ResolvExpr/CurrentObject.cc
===================================================================
--- src/ResolvExpr/CurrentObject.cc	(revision 57e0289d894ddff6323ba0a0125d5ba3ef575462)
+++ src/ResolvExpr/CurrentObject.cc	(revision 1c1395d2efc85a2d060e6cc92d06033044b1144d)
@@ -594,5 +594,5 @@
 	class SimpleIterator final : public MemberIterator {
 		CodeLocation location;
-		readonly< Type > type = nullptr;
+		const Type * type = nullptr;
 	public:
 		SimpleIterator( const CodeLocation & loc, const Type * t ) : location( loc ), type( t ) {}
@@ -630,6 +630,6 @@
 	class ArrayIterator final : public MemberIterator {
 		CodeLocation location;
-		readonly< ArrayType > array = nullptr;
-		readonly< Type > base = nullptr;
+		const ArrayType * array = nullptr;
+		const Type * base = nullptr;
 		size_t index = 0;
 		size_t size = 0;
Index: src/ResolvExpr/Resolver.cc
===================================================================
--- src/ResolvExpr/Resolver.cc	(revision 57e0289d894ddff6323ba0a0125d5ba3ef575462)
+++ src/ResolvExpr/Resolver.cc	(revision 1c1395d2efc85a2d060e6cc92d06033044b1144d)
@@ -1223,5 +1223,5 @@
 		template<typename Iter>
 		inline bool nextMutex( Iter & it, const Iter & end ) {
-			while ( it != end && ! (*it)->get_type()->is_mutex() ) { ++it; }
+			while ( it != end && ! (*it)->is_mutex() ) { ++it; }
 			return it != end;
 		}
@@ -1638,8 +1638,8 @@
 								// Check if the argument matches the parameter type in the current
 								// scope
-								ast::ptr< ast::Type > paramType = (*param)->get_type();
+								// ast::ptr< ast::Type > paramType = (*param)->get_type();
 								if (
 									! unify(
-										arg->expr->result, paramType, resultEnv, need, have, open,
+										arg->expr->result, *param, resultEnv, need, have, open,
 										symtab )
 								) {
@@ -1648,5 +1648,5 @@
 									ss << "candidate function not viable: no known conversion "
 										"from '";
-									ast::print( ss, (*param)->get_type() );
+									ast::print( ss, *param );
 									ss << "' to '";
 									ast::print( ss, arg->expr->result );
Index: src/ResolvExpr/SatisfyAssertions.cpp
===================================================================
--- src/ResolvExpr/SatisfyAssertions.cpp	(revision 57e0289d894ddff6323ba0a0125d5ba3ef575462)
+++ src/ResolvExpr/SatisfyAssertions.cpp	(revision 1c1395d2efc85a2d060e6cc92d06033044b1144d)
@@ -318,6 +318,6 @@
 					if ( ! func ) continue;
 
-					for ( const ast::DeclWithType * param : func->params ) {
-						cost.decSpec( specCost( param->get_type() ) );
+					for ( const auto & param : func->params ) {
+						cost.decSpec( specCost( param ) );
 					}
 
Index: src/ResolvExpr/SpecCost.cc
===================================================================
--- src/ResolvExpr/SpecCost.cc	(revision 57e0289d894ddff6323ba0a0125d5ba3ef575462)
+++ src/ResolvExpr/SpecCost.cc	(revision 1c1395d2efc85a2d060e6cc92d06033044b1144d)
@@ -178,6 +178,6 @@
 		void previsit( const ast::FunctionType * fty ) {
 			int minCount = std::numeric_limits<int>::max();
-			updateMinimumPresent( minCount, fty->params, decl_type );
-			updateMinimumPresent( minCount, fty->returns, decl_type );
+			updateMinimumPresent( minCount, fty->params, type_deref );
+			updateMinimumPresent( minCount, fty->returns, type_deref );
 			// Add another level to minCount if set.
 			count = toNoneOrInc( minCount );
Index: src/ResolvExpr/Unify.cc
===================================================================
--- src/ResolvExpr/Unify.cc	(revision 57e0289d894ddff6323ba0a0125d5ba3ef575462)
+++ src/ResolvExpr/Unify.cc	(revision 1c1395d2efc85a2d060e6cc92d06033044b1144d)
@@ -395,5 +395,5 @@
 
 	template< typename Iterator1, typename Iterator2 >
-	bool unifyDeclList( Iterator1 list1Begin, Iterator1 list1End, Iterator2 list2Begin, Iterator2 list2End, TypeEnvironment &env, AssertionSet &needAssertions, AssertionSet &haveAssertions, const OpenVarSet &openVars, const SymTab::Indexer &indexer ) {
+	bool unifyTypeList( Iterator1 list1Begin, Iterator1 list1End, Iterator2 list2Begin, Iterator2 list2End, TypeEnvironment &env, AssertionSet &needAssertions, AssertionSet &haveAssertions, const OpenVarSet &openVars, const SymTab::Indexer &indexer ) {
 		auto get_type = [](DeclarationWithType * dwt){ return dwt->get_type(); };
 		for ( ; list1Begin != list1End && list2Begin != list2End; ++list1Begin, ++list2Begin ) {
@@ -489,6 +489,6 @@
 					|| flatOther->isTtype()
 			) {
-				if ( unifyDeclList( flatFunc->parameters.begin(), flatFunc->parameters.end(), flatOther->parameters.begin(), flatOther->parameters.end(), env, needAssertions, haveAssertions, openVars, indexer ) ) {
-					if ( unifyDeclList( flatFunc->returnVals.begin(), flatFunc->returnVals.end(), flatOther->returnVals.begin(), flatOther->returnVals.end(), env, needAssertions, haveAssertions, openVars, indexer ) ) {
+				if ( unifyTypeList( flatFunc->parameters.begin(), flatFunc->parameters.end(), flatOther->parameters.begin(), flatOther->parameters.end(), env, needAssertions, haveAssertions, openVars, indexer ) ) {
+					if ( unifyTypeList( flatFunc->returnVals.begin(), flatFunc->returnVals.end(), flatOther->returnVals.begin(), flatOther->returnVals.end(), env, needAssertions, haveAssertions, openVars, indexer ) ) {
 
 						// the original types must be used in mark assertions, since pointer comparisons are used
@@ -784,15 +784,15 @@
 
 		/// returns flattened version of `src`
-		static std::vector< ast::ptr< ast::DeclWithType > > flattenList(
-			const std::vector< ast::ptr< ast::DeclWithType > > & src, ast::TypeEnvironment & env
+		static std::vector< ast::ptr< ast::Type > > flattenList(
+			const std::vector< ast::ptr< ast::Type > > & src, ast::TypeEnvironment & env
 		) {
-			std::vector< ast::ptr< ast::DeclWithType > > dst;
+			std::vector< ast::ptr< ast::Type > > dst;
 			dst.reserve( src.size() );
-			for ( const ast::DeclWithType * d : src ) {
+			for ( const auto & d : src ) {
 				ast::Pass<TtypeExpander_new> expander{ env };
 				// TtypeExpander pass is impure (may mutate nodes in place)
 				// need to make nodes shared to prevent accidental mutation
-				ast::ptr<ast::DeclWithType> dc = d->accept(expander);
-				auto types = flatten( dc->get_type() );
+				ast::ptr<ast::Type> dc = d->accept(expander);
+				auto types = flatten( dc );
 				for ( ast::ptr< ast::Type > & t : types ) {
 					// outermost const, volatile, _Atomic qualifiers in parameters should not play
@@ -803,5 +803,5 @@
 					// requirements than a non-mutex function
 					remove_qualifiers( t, ast::CV::Const | ast::CV::Volatile | ast::CV::Atomic );
-					dst.emplace_back( new ast::ObjectDecl{ dc->location, "", t } );
+					dst.emplace_back( t );
 				}
 			}
@@ -811,10 +811,10 @@
 		/// Creates a tuple type based on a list of DeclWithType
 		template< typename Iter >
-		static ast::ptr< ast::Type > tupleFromDecls( Iter crnt, Iter end ) {
+		static ast::ptr< ast::Type > tupleFromTypes( Iter crnt, Iter end ) {
 			std::vector< ast::ptr< ast::Type > > types;
 			while ( crnt != end ) {
 				// it is guaranteed that a ttype variable will be bound to a flat tuple, so ensure
 				// that this results in a flat tuple
-				flatten( (*crnt)->get_type(), types );
+				flatten( *crnt, types );
 
 				++crnt;
@@ -825,5 +825,5 @@
 
 		template< typename Iter >
-		static bool unifyDeclList(
+		static bool unifyTypeList(
 			Iter crnt1, Iter end1, Iter crnt2, Iter end2, ast::TypeEnvironment & env,
 			ast::AssertionSet & need, ast::AssertionSet & have, const ast::OpenVarSet & open,
@@ -831,6 +831,6 @@
 		) {
 			while ( crnt1 != end1 && crnt2 != end2 ) {
-				const ast::Type * t1 = (*crnt1)->get_type();
-				const ast::Type * t2 = (*crnt2)->get_type();
+				const ast::Type * t1 = *crnt1;
+				const ast::Type * t2 = *crnt2;
 				bool isTuple1 = Tuples::isTtype( t1 );
 				bool isTuple2 = Tuples::isTtype( t2 );
@@ -840,10 +840,10 @@
 					// combine remainder of list2, then unify
 					return unifyExact(
-						t1, tupleFromDecls( crnt2, end2 ), env, need, have, open,
+						t1, tupleFromTypes( crnt2, end2 ), env, need, have, open,
 						noWiden(), symtab );
 				} else if ( ! isTuple1 && isTuple2 ) {
 					// combine remainder of list1, then unify
 					return unifyExact(
-						tupleFromDecls( crnt1, end1 ), t2, env, need, have, open,
+						tupleFromTypes( crnt1, end1 ), t2, env, need, have, open,
 						noWiden(), symtab );
 				}
@@ -860,15 +860,15 @@
 			if ( crnt1 != end1 ) {
 				// try unifying empty tuple with ttype
-				const ast::Type * t1 = (*crnt1)->get_type();
+				const ast::Type * t1 = *crnt1;
 				if ( ! Tuples::isTtype( t1 ) ) return false;
 				return unifyExact(
-					t1, tupleFromDecls( crnt2, end2 ), env, need, have, open,
+					t1, tupleFromTypes( crnt2, end2 ), env, need, have, open,
 					noWiden(), symtab );
 			} else if ( crnt2 != end2 ) {
 				// try unifying empty tuple with ttype
-				const ast::Type * t2 = (*crnt2)->get_type();
+				const ast::Type * t2 = *crnt2;
 				if ( ! Tuples::isTtype( t2 ) ) return false;
 				return unifyExact(
-					tupleFromDecls( crnt1, end1 ), t2, env, need, have, open,
+					tupleFromTypes( crnt1, end1 ), t2, env, need, have, open,
 					noWiden(), symtab );
 			}
@@ -877,11 +877,11 @@
 		}
 
-		static bool unifyDeclList(
-			const std::vector< ast::ptr< ast::DeclWithType > > & list1,
-			const std::vector< ast::ptr< ast::DeclWithType > > & list2,
+		static bool unifyTypeList(
+			const std::vector< ast::ptr< ast::Type > > & list1,
+			const std::vector< ast::ptr< ast::Type > > & list2,
 			ast::TypeEnvironment & env, ast::AssertionSet & need, ast::AssertionSet & have,
 			const ast::OpenVarSet & open, const ast::SymbolTable & symtab
 		) {
-			return unifyDeclList(
+			return unifyTypeList(
 				list1.begin(), list1.end(), list2.begin(), list2.end(), env, need, have, open,
 				symtab );
@@ -928,6 +928,6 @@
 			) return;
 
-			if ( ! unifyDeclList( params, params2, tenv, need, have, open, symtab ) ) return;
-			if ( ! unifyDeclList(
+			if ( ! unifyTypeList( params, params2, tenv, need, have, open, symtab ) ) return;
+			if ( ! unifyTypeList(
 				func->returns, func2->returns, tenv, need, have, open, symtab ) ) return;
 
@@ -1232,9 +1232,9 @@
 	ast::ptr<ast::Type> extractResultType( const ast::FunctionType * func ) {
 		if ( func->returns.empty() ) return new ast::VoidType{};
-		if ( func->returns.size() == 1 ) return func->returns[0]->get_type();
+		if ( func->returns.size() == 1 ) return func->returns[0];
 
 		std::vector<ast::ptr<ast::Type>> tys;
-		for ( const ast::DeclWithType * decl : func->returns ) {
-			tys.emplace_back( decl->get_type() );
+		for ( const auto & decl : func->returns ) {
+			tys.emplace_back( decl );
 		}
 		return new ast::TupleType{ std::move(tys) };
