Index: src/AST/Attribute.hpp
===================================================================
--- src/AST/Attribute.hpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/Attribute.hpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -27,4 +27,5 @@
 class Expr;
 
+/// An entry in an attribute list: `__attribute__(( ... ))`
 class Attribute final : public Node {
 public:
Index: src/AST/Convert.cpp
===================================================================
--- src/AST/Convert.cpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/Convert.cpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -559,5 +559,5 @@
 		auto stmt = new SuspendStmt();
 		stmt->then   = get<CompoundStmt>().accept1( node->then   );
-		switch(node->type) {
+		switch (node->kind) {
 			case ast::SuspendStmt::None     : stmt->type = SuspendStmt::None     ; break;
 			case ast::SuspendStmt::Coroutine: stmt->type = SuspendStmt::Coroutine; break;
@@ -1695,5 +1695,5 @@
 			GET_ACCEPT_V(attributes, Attribute),
 			{ old->get_funcSpec().val },
-			old->type->isVarArgs
+			(old->type->isVarArgs) ? ast::VariableArgs : ast::FixedArgs
 		};
 
@@ -2001,5 +2001,5 @@
 			GET_ACCEPT_1(else_, Stmt),
 			GET_ACCEPT_V(initialization, Stmt),
-			old->isDoWhile,
+			(old->isDoWhile) ? ast::DoWhile : ast::While,
 			GET_LABELS_V(old->labels)
 		);
@@ -2143,5 +2143,5 @@
 	virtual void visit( const SuspendStmt * old ) override final {
 		if ( inCache( old ) ) return;
-		ast::SuspendStmt::Type type;
+		ast::SuspendStmt::Kind type;
 		switch (old->type) {
 			case SuspendStmt::Coroutine: type = ast::SuspendStmt::Coroutine; break;
Index: src/AST/Decl.cpp
===================================================================
--- src/AST/Decl.cpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/Decl.cpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -57,9 +57,9 @@
 	std::vector<ptr<DeclWithType>>&& params, std::vector<ptr<DeclWithType>>&& returns,
 	CompoundStmt * stmts, Storage::Classes storage, Linkage::Spec linkage,
-	std::vector<ptr<Attribute>>&& attrs, Function::Specs fs, bool isVarArgs)
+	std::vector<ptr<Attribute>>&& attrs, Function::Specs fs, ArgumentFlag isVarArgs )
 : DeclWithType( loc, name, storage, linkage, std::move(attrs), fs ),
 	type_params(std::move(forall)), assertions(),
 	params(std::move(params)), returns(std::move(returns)), stmts( stmts ) {
-	FunctionType * ftype = new FunctionType(static_cast<ArgumentFlag>(isVarArgs));
+	FunctionType * ftype = new FunctionType( isVarArgs );
 	for (auto & param : this->params) {
 		ftype->params.emplace_back(param->get_type());
@@ -81,10 +81,10 @@
 	std::vector<ptr<DeclWithType>>&& params, std::vector<ptr<DeclWithType>>&& returns,
 	CompoundStmt * stmts, Storage::Classes storage, Linkage::Spec linkage,
-	std::vector<ptr<Attribute>>&& attrs, Function::Specs fs, bool isVarArgs)
+	std::vector<ptr<Attribute>>&& attrs, Function::Specs fs, ArgumentFlag isVarArgs )
 : DeclWithType( location, name, storage, linkage, std::move(attrs), fs ),
 		type_params( std::move( forall) ), assertions( std::move( assertions ) ),
 		params( std::move(params) ), returns( std::move(returns) ),
 		type( nullptr ), stmts( stmts ) {
-	FunctionType * type = new FunctionType( (isVarArgs) ? VariableArgs : FixedArgs );
+	FunctionType * type = new FunctionType( isVarArgs );
 	for ( auto & param : this->params ) {
 		type->params.emplace_back( param->get_type() );
Index: src/AST/Decl.hpp
===================================================================
--- src/AST/Decl.hpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/Decl.hpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -10,6 +10,6 @@
 // Created On       : Thu May 9 10:00:00 2019
 // Last Modified By : Andrew Beach
-// Last Modified On : Thu Nov 24  9:44:00 2022
-// Update Count     : 34
+// Last Modified On : Wed Apr  5 10:42:00 2023
+// Update Count     : 35
 //
 
@@ -122,4 +122,7 @@
 };
 
+/// Function variable arguments flag
+enum ArgumentFlag { FixedArgs, VariableArgs };
+
 /// Object declaration `int foo()`
 class FunctionDecl : public DeclWithType {
@@ -144,5 +147,5 @@
 		std::vector<ptr<DeclWithType>>&& params, std::vector<ptr<DeclWithType>>&& returns,
 		CompoundStmt * stmts, Storage::Classes storage = {}, Linkage::Spec linkage = Linkage::Cforall,
-		std::vector<ptr<Attribute>>&& attrs = {}, Function::Specs fs = {}, bool isVarArgs = false);
+		std::vector<ptr<Attribute>>&& attrs = {}, Function::Specs fs = {}, ArgumentFlag isVarArgs = FixedArgs );
 
 	FunctionDecl( const CodeLocation & location, const std::string & name,
@@ -150,5 +153,5 @@
 		std::vector<ptr<DeclWithType>>&& params, std::vector<ptr<DeclWithType>>&& returns,
 		CompoundStmt * stmts, Storage::Classes storage = {}, Linkage::Spec linkage = Linkage::Cforall,
-		std::vector<ptr<Attribute>>&& attrs = {}, Function::Specs fs = {}, bool isVarArgs = false);
+		std::vector<ptr<Attribute>>&& attrs = {}, Function::Specs fs = {}, ArgumentFlag isVarArgs = FixedArgs );
 
 	const Type * get_type() const override;
@@ -313,5 +316,5 @@
 public:
 	bool isTyped; // isTyped indicated if the enum has a declaration like:
-	// enum (type_optional) Name {...} 
+	// enum (type_optional) Name {...}
 	ptr<Type> base; // if isTyped == true && base.get() == nullptr, it is a "void" type enum
 	enum class EnumHiding { Visible, Hide } hide;
@@ -371,4 +374,5 @@
 };
 
+/// Assembly declaration: `asm ... ( "..." : ... )`
 class AsmDecl : public Decl {
 public:
Index: src/AST/Expr.hpp
===================================================================
--- src/AST/Expr.hpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/Expr.hpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -254,4 +254,5 @@
 };
 
+/// A name qualified by a namespace or type.
 class QualifiedNameExpr final : public Expr {
 public:
@@ -259,5 +260,5 @@
 	std::string name;
 
-	QualifiedNameExpr( const CodeLocation & loc, const Decl * d, const std::string & n ) 
+	QualifiedNameExpr( const CodeLocation & loc, const Decl * d, const std::string & n )
 	: Expr( loc ), type_decl( d ), name( n ) {}
 
@@ -621,4 +622,5 @@
 };
 
+/// A name that refers to a generic dimension parameter.
 class DimensionExpr final : public Expr {
 public:
@@ -910,5 +912,4 @@
 };
 
-
 }
 
Index: src/AST/Init.hpp
===================================================================
--- src/AST/Init.hpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/Init.hpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -117,5 +117,5 @@
 	ptr<Init> init;
 
-	ConstructorInit( 
+	ConstructorInit(
 		const CodeLocation & loc, const Stmt * ctor, const Stmt * dtor, const Init * init )
 	: Init( loc, MaybeConstruct ), ctor( ctor ), dtor( dtor ), init( init ) {}
Index: src/AST/Inspect.cpp
===================================================================
--- src/AST/Inspect.cpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/Inspect.cpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -10,6 +10,6 @@
 // Created On       : Fri Jun 24 13:16:31 2022
 // Last Modified By : Andrew Beach
-// Last Modified On : Mon Oct  3 11:04:00 2022
-// Update Count     : 3
+// Last Modified On : Fri Apr 14 15:09:00 2023
+// Update Count     : 4
 //
 
@@ -168,3 +168,7 @@
 }
 
+bool isUnnamedBitfield( const ast::ObjectDecl * obj ) {
+	return obj && obj->name.empty() && obj->bitfieldWidth;
+}
+
 } // namespace ast
Index: src/AST/Inspect.hpp
===================================================================
--- src/AST/Inspect.hpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/Inspect.hpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -10,6 +10,6 @@
 // Created On       : Fri Jun 24 13:16:31 2022
 // Last Modified By : Andrew Beach
-// Last Modified On : Thr Sep 22 13:44:00 2022
-// Update Count     : 2
+// Last Modified On : Fri Apr 14 15:09:00 2023
+// Update Count     : 3
 //
 
@@ -38,3 +38,6 @@
 const ApplicationExpr * isIntrinsicCallExpr( const Expr * expr );
 
+/// Returns true if obj's name is the empty string and it has a bitfield width.
+bool isUnnamedBitfield( const ObjectDecl * obj );
+
 }
Index: src/AST/Pass.impl.hpp
===================================================================
--- src/AST/Pass.impl.hpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/Pass.impl.hpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -2075,5 +2075,4 @@
 	if ( __visit_children() ) {
 		maybe_accept( node, &TupleType::types );
-		maybe_accept( node, &TupleType::members );
 	}
 
Index: src/AST/Print.cpp
===================================================================
--- src/AST/Print.cpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/Print.cpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -766,8 +766,8 @@
 	virtual const ast::Stmt * visit( const ast::SuspendStmt * node ) override final {
 		os << "Suspend Statement";
-		switch (node->type) {
-			case ast::SuspendStmt::None     : os << " with implicit target"; break;
-			case ast::SuspendStmt::Generator: os << " for generator"; break;
-			case ast::SuspendStmt::Coroutine: os << " for coroutine"; break;
+		switch (node->kind) {
+		case ast::SuspendStmt::None     : os << " with implicit target"; break;
+		case ast::SuspendStmt::Generator: os << " for generator"; break;
+		case ast::SuspendStmt::Coroutine: os << " for coroutine"; break;
 		}
 		os << endl;
Index: src/AST/Stmt.hpp
===================================================================
--- src/AST/Stmt.hpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/Stmt.hpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -10,6 +10,6 @@
 // Created On       : Wed May  8 13:00:00 2019
 // Last Modified By : Andrew Beach
-// Last Modified On : Wed Apr 20 14:34:00 2022
-// Update Count     : 36
+// Last Modified On : Wed Apr  5 10:34:00 2023
+// Update Count     : 37
 //
 
@@ -205,4 +205,7 @@
 };
 
+// A while loop or a do-while loop:
+enum WhileDoKind { While, DoWhile };
+
 // While loop: while (...) ... else ... or do ... while (...) else ...;
 class WhileDoStmt final : public Stmt {
@@ -212,12 +215,12 @@
 	ptr<Stmt> else_;
 	std::vector<ptr<Stmt>> inits;
-	bool isDoWhile;
+	WhileDoKind isDoWhile;
 
 	WhileDoStmt( const CodeLocation & loc, const Expr * cond, const Stmt * body,
-				 const std::vector<ptr<Stmt>> && inits, bool isDoWhile = false, const std::vector<Label> && labels = {} )
+				 const std::vector<ptr<Stmt>> && inits, WhileDoKind isDoWhile = While, const std::vector<Label> && labels = {} )
 		: Stmt(loc, std::move(labels)), cond(cond), body(body), else_(nullptr), inits(std::move(inits)), isDoWhile(isDoWhile) {}
 
 	WhileDoStmt( const CodeLocation & loc, const Expr * cond, const Stmt * body, const Stmt * else_,
-				 const std::vector<ptr<Stmt>> && inits, bool isDoWhile = false, const std::vector<Label> && labels = {} )
+				 const std::vector<ptr<Stmt>> && inits, WhileDoKind isDoWhile = While, const std::vector<Label> && labels = {} )
 		: Stmt(loc, std::move(labels)), cond(cond), body(body), else_(else_), inits(std::move(inits)), isDoWhile(isDoWhile) {}
 
@@ -364,8 +367,8 @@
   public:
 	ptr<CompoundStmt> then;
-	enum Type { None, Coroutine, Generator } type = None;
-
-	SuspendStmt( const CodeLocation & loc, const CompoundStmt * then, Type type, const std::vector<Label> && labels = {} )
-		: Stmt(loc, std::move(labels)), then(then), type(type) {}
+	enum Kind { None, Coroutine, Generator } kind = None;
+
+	SuspendStmt( const CodeLocation & loc, const CompoundStmt * then, Kind kind, const std::vector<Label> && labels = {} )
+		: Stmt(loc, std::move(labels)), then(then), kind(kind) {}
 
 	const Stmt * accept( Visitor & v ) const override { return v.visit( this ); }
@@ -424,4 +427,5 @@
 };
 
+// Clause in a waitfor statement: waitfor (..., ...) ...
 class WaitForClause final : public WhenClause {
   public:
@@ -527,4 +531,5 @@
 	MUTATE_FRIEND
 };
+
 } // namespace ast
 
Index: src/AST/SymbolTable.cpp
===================================================================
--- src/AST/SymbolTable.cpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/SymbolTable.cpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -260,10 +260,10 @@
 void SymbolTable::addId( const DeclWithType * decl, const Expr * baseExpr ) {
 	// default handling of conflicts is to raise an error
-	addId( decl, OnConflict::error(), baseExpr, decl->isDeleted ? decl : nullptr );
+	addIdCommon( decl, OnConflict::error(), baseExpr, decl->isDeleted ? decl : nullptr );
 }
 
 void SymbolTable::addDeletedId( const DeclWithType * decl, const Decl * deleter ) {
 	// default handling of conflicts is to raise an error
-	addId( decl, OnConflict::error(), nullptr, deleter );
+	addIdCommon( decl, OnConflict::error(), nullptr, deleter );
 }
 
@@ -677,10 +677,10 @@
 }
 
-void SymbolTable::addId(
-		const DeclWithType * decl, SymbolTable::OnConflict handleConflicts, const Expr * baseExpr,
-		const Decl * deleter ) {
+void SymbolTable::addIdCommon(
+		const DeclWithType * decl, SymbolTable::OnConflict handleConflicts,
+		const Expr * baseExpr, const Decl * deleter ) {
 	SpecialFunctionKind kind = getSpecialFunctionKind(decl->name);
 	if (kind == NUMBER_OF_KINDS) { // not a special decl
-		addId(decl, decl->name, idTable, handleConflicts, baseExpr, deleter);
+		addIdToTable(decl, decl->name, idTable, handleConflicts, baseExpr, deleter);
 	}
 	else {
@@ -695,11 +695,12 @@
 			assertf(false, "special decl with non-function type");
 		}
-		addId(decl, key, specialFunctionTable[kind], handleConflicts, baseExpr, deleter);
-	}
-}
-
-void SymbolTable::addId(
-		const DeclWithType * decl, const std::string & lookupKey, IdTable::Ptr & table, SymbolTable::OnConflict handleConflicts, const Expr * baseExpr,
-		const Decl * deleter ) {
+		addIdToTable(decl, key, specialFunctionTable[kind], handleConflicts, baseExpr, deleter);
+	}
+}
+
+void SymbolTable::addIdToTable(
+		const DeclWithType * decl, const std::string & lookupKey,
+		IdTable::Ptr & table, SymbolTable::OnConflict handleConflicts,
+		const Expr * baseExpr, const Decl * deleter ) {
 	++*stats().add_calls;
 	const std::string &name = decl->name;
@@ -778,22 +779,22 @@
 void SymbolTable::addMembers(
 		const AggregateDecl * aggr, const Expr * expr, SymbolTable::OnConflict handleConflicts ) {
-	for ( const Decl * decl : aggr->members ) {
-		if ( auto dwt = dynamic_cast< const DeclWithType * >( decl ) ) {
-			addId( dwt, handleConflicts, expr );
-			if ( dwt->name == "" ) {
-				const Type * t = dwt->get_type()->stripReferences();
-				if ( auto rty = dynamic_cast<const BaseInstType *>( t ) ) {
-					if ( ! dynamic_cast<const StructInstType *>(rty)
-						&& ! 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 );
-				}
-			}
+	for ( const ptr<Decl> & decl : aggr->members ) {
+		auto dwt = decl.as<DeclWithType>();
+		if ( nullptr == dwt ) continue;
+		addIdCommon( dwt, handleConflicts, expr );
+		// Inline through unnamed struct/union members.
+		if ( "" != dwt->name ) continue;
+		const Type * t = dwt->get_type()->stripReferences();
+		if ( auto rty = dynamic_cast<const BaseInstType *>( t ) ) {
+			if ( ! dynamic_cast<const StructInstType *>(rty)
+				&& ! 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/SymbolTable.hpp
===================================================================
--- src/AST/SymbolTable.hpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/SymbolTable.hpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -192,13 +192,14 @@
 
 	/// common code for addId, addDeletedId, etc.
-	void addId(
-		const DeclWithType * decl, OnConflict handleConflicts, const Expr * baseExpr = nullptr,
-		const Decl * deleter = nullptr );
+	void addIdCommon(
+		const DeclWithType * decl, OnConflict handleConflicts,
+		const Expr * baseExpr = nullptr, const Decl * deleter = nullptr );
 
 	/// common code for addId when special decls are placed into separate tables
-	void addId(
-		const DeclWithType * decl, const std::string & lookupKey, IdTable::Ptr & idTable, OnConflict handleConflicts, 
+	void addIdToTable(
+		const DeclWithType * decl, const std::string & lookupKey,
+		IdTable::Ptr & idTable, OnConflict handleConflicts,
 		const Expr * baseExpr = nullptr, const Decl * deleter = nullptr);
-	
+
 	/// adds all of the members of the Aggregate (addWith helper)
 	void addMembers( const AggregateDecl * aggr, const Expr * expr, OnConflict handleConflicts );
Index: src/AST/Type.cpp
===================================================================
--- src/AST/Type.cpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/Type.cpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -10,6 +10,6 @@
 // Created On       : Mon May 13 15:00:00 2019
 // Last Modified By : Andrew Beach
-// Last Modified On : Thu Nov 24  9:49:00 2022
-// Update Count     : 6
+// Last Modified On : Thu Apr  6 15:59:00 2023
+// Update Count     : 7
 //
 
@@ -199,23 +199,5 @@
 
 TupleType::TupleType( std::vector<ptr<Type>> && ts, CV::Qualifiers q )
-: Type( q ), types( std::move(ts) ), members() {
-	// This constructor is awkward. `TupleType` needs to contain objects so that members can be
-	// named, but members without initializer nodes end up getting constructors, which breaks
-	// things. This happens because the object decls have to be visited so that their types are
-	// kept in sync with the types listed here. Ultimately, the types listed here should perhaps
-	// be eliminated and replaced with a list-view over members. The temporary solution is to
-	// make a `ListInit` with `maybeConstructed = false`, so when the object is visited it is not
-	// constructed. Potential better solutions include:
-	//   a) Separate `TupleType` from its declarations, into `TupleDecl` and `Tuple{Inst?}Type`,
-	//      similar to the aggregate types.
-	//   b) Separate initializer nodes better, e.g. add a `MaybeConstructed` node that is replaced
-	//      by `genInit`, rather than the current boolean flag.
-	members.reserve( types.size() );
-	for ( const Type * ty : types ) {
-		members.emplace_back( new ObjectDecl{
-			CodeLocation(), "", ty, new ListInit( CodeLocation(), {}, {}, NoConstruct ),
-			Storage::Classes{}, Linkage::Cforall } );
-	}
-}
+: Type( q ), types( std::move(ts) ) {}
 
 bool isUnboundType(const Type * type) {
Index: src/AST/Type.hpp
===================================================================
--- src/AST/Type.hpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/AST/Type.hpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -10,6 +10,6 @@
 // Created On       : Thu May 9 10:00:00 2019
 // Last Modified By : Andrew Beach
-// Last Modified On : Thu Nov 24  9:47:00 2022
-// Update Count     : 8
+// Last Modified On : Thu Apr  6 15:58:00 2023
+// Update Count     : 9
 //
 
@@ -265,7 +265,4 @@
 };
 
-/// Function variable arguments flag
-enum ArgumentFlag { FixedArgs, VariableArgs };
-
 /// Type of a function `[R1, R2](*)(P1, P2, P3)`
 class FunctionType final : public Type {
@@ -460,5 +457,4 @@
 public:
 	std::vector<ptr<Type>> types;
-	std::vector<ptr<Decl>> members;
 
 	TupleType( std::vector<ptr<Type>> && ts, CV::Qualifiers q = {} );
Index: src/Common/CodeLocationTools.cpp
===================================================================
--- src/Common/CodeLocationTools.cpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Common/CodeLocationTools.cpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -210,9 +210,9 @@
 
 struct LeafKindVisitor : public ast::Visitor {
-	LeafKind kind;
+	LeafKind result;
 
 #define VISIT(node_type, return_type) \
 	const ast::return_type * visit( const ast::node_type * ) final { \
-		kind = LeafKind::node_type; \
+		result = LeafKind::node_type; \
 		return nullptr; \
 	}
@@ -224,7 +224,5 @@
 
 LeafKind get_leaf_kind( ast::Node const * node ) {
-	LeafKindVisitor visitor;
-	node->accept( visitor );
-	return visitor.kind;
+	return ast::Pass<LeafKindVisitor>::read( node );
 }
 
Index: src/Common/Iterate.hpp
===================================================================
--- src/Common/Iterate.hpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Common/Iterate.hpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -20,5 +20,5 @@
 #include <iterator>
 
-// it's nice to actually be able to increment iterators by an arbitrary amount
+// Returns an iterator that is it advanced n times.
 template< class InputIt, class Distance >
 InputIt operator+( InputIt it, Distance n ) {
@@ -50,4 +50,8 @@
 
 // -----------------------------------------------------------------------------
+// Helper struct and function to support
+// for ( val_and_index : enumerate( container ) ) {}
+// which iterates through the container and tracks the index as well.
+
 template< typename T >
 struct enumerate_t {
@@ -109,4 +113,6 @@
 
 // -----------------------------------------------------------------------------
+// Helper function to transform one iterable container into another.
+
 template< typename OutType, typename Range, typename Functor >
 OutType map_range( const Range& range, Functor&& functor ) {
@@ -206,5 +212,7 @@
 // Helper struct and function to support
 // for ( val : lazy_map( container1, f ) ) {}
-// syntax to have a for each that iterates a container, mapping each element by applying f
+// syntax to have a for each that iterates a container,
+// mapping each element by applying f.
+
 template< typename T, typename Func >
 struct lambda_iterate_t {
Index: src/CompilationState.cc
===================================================================
--- src/CompilationState.cc	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/CompilationState.cc	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -9,7 +9,7 @@
 // Author           : Rob Schluntz
 // Created On       : Mon Ju1 30 10:47:01 2018
-// Last Modified By : Henry Xue
-// Last Modified On : Tue Jul 20 04:27:35 2021
-// Update Count     : 5
+// Last Modified By : Peter A. Buhr
+// Last Modified On : Mon Apr 10 19:12:50 2023
+// Update Count     : 6
 //
 
@@ -27,4 +27,5 @@
 	expraltp = false,
 	genericsp = false,
+	invariant = false,
 	libcfap = false,
 	nopreludep = false,
Index: src/CompilationState.h
===================================================================
--- src/CompilationState.h	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/CompilationState.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -9,7 +9,7 @@
 // Author           : Rob Schluntz
 // Created On       : Mon Ju1 30 10:47:01 2018
-// Last Modified By : Henry Xue
-// Last Modified On : Tue Jul 20 04:27:35 2021
-// Update Count     : 5
+// Last Modified By : Peter A. Buhr
+// Last Modified On : Mon Apr 10 19:12:53 2023
+// Update Count     : 6
 //
 
@@ -26,4 +26,5 @@
 	expraltp,
 	genericsp,
+	invariant,
 	libcfap,
 	nopreludep,
Index: src/Concurrency/KeywordsNew.cpp
===================================================================
--- src/Concurrency/KeywordsNew.cpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Concurrency/KeywordsNew.cpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -779,5 +779,5 @@
 
 const ast::Stmt * SuspendKeyword::postvisit( const ast::SuspendStmt * stmt ) {
-	switch ( stmt->type ) {
+	switch ( stmt->kind ) {
 	case ast::SuspendStmt::None:
 		// Use the context to determain the implicit target.
Index: src/InitTweak/FixInitNew.cpp
===================================================================
--- src/InitTweak/FixInitNew.cpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/InitTweak/FixInitNew.cpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -14,8 +14,14 @@
 #include <utility>                     // for pair
 
+#include "AST/DeclReplacer.hpp"
+#include "AST/Expr.hpp"
 #include "AST/Inspect.hpp"             // for getFunction, getPointerBase, g...
+#include "AST/Node.hpp"
+#include "AST/Pass.hpp"
+#include "AST/Print.hpp"
+#include "AST/SymbolTable.hpp"
+#include "AST/Type.hpp"
 #include "CodeGen/GenType.h"           // for genPrettyType
 #include "CodeGen/OperatorTable.h"
-#include "Common/CodeLocationTools.hpp"
 #include "Common/PassVisitor.h"        // for PassVisitor, WithStmtsToAdd
 #include "Common/SemanticError.h"      // for SemanticError
@@ -28,4 +34,5 @@
 #include "ResolvExpr/Unify.h"          // for typesCompatible
 #include "SymTab/Autogen.h"            // for genImplicitCall
+#include "SymTab/GenImplicitCall.hpp"  // for genImplicitCall
 #include "SymTab/Indexer.h"            // for Indexer
 #include "SymTab/Mangler.h"            // for Mangler
@@ -45,12 +52,4 @@
 #include "Validate/FindSpecialDecls.h" // for dtorStmt, dtorStructDestroy
 
-#include "AST/Expr.hpp"
-#include "AST/Node.hpp"
-#include "AST/Pass.hpp"
-#include "AST/Print.hpp"
-#include "AST/SymbolTable.hpp"
-#include "AST/Type.hpp"
-#include "AST/DeclReplacer.hpp"
-
 extern bool ctordtorp; // print all debug
 extern bool ctorp; // print ctor debug
@@ -63,4 +62,42 @@
 namespace InitTweak {
 namespace {
+
+	// Shallow copy the pointer list for return.
+	std::vector<ast::ptr<ast::TypeDecl>> getGenericParams( const ast::Type * t ) {
+		if ( auto inst = dynamic_cast<const ast::StructInstType *>( t ) ) {
+			return inst->base->params;
+		}
+		if ( auto inst = dynamic_cast<const ast::UnionInstType *>( t ) ) {
+			return inst->base->params;
+		}
+		return {};
+	}
+
+	/// Given type T, generate type of default ctor/dtor, i.e. function type void (*) (T &).
+	ast::FunctionDecl * genDefaultFunc(
+			const CodeLocation loc,
+			const std::string fname,
+			const ast::Type * paramType,
+			bool maybePolymorphic = true) {
+		std::vector<ast::ptr<ast::TypeDecl>> typeParams;
+		if ( maybePolymorphic ) typeParams = getGenericParams( paramType );
+		auto dstParam = new ast::ObjectDecl( loc,
+			"_dst",
+			new ast::ReferenceType( paramType ),
+			nullptr,
+			{},
+			ast::Linkage::Cforall
+		);
+		return new ast::FunctionDecl( loc,
+			fname,
+			std::move(typeParams),
+			{dstParam},
+			{},
+			new ast::CompoundStmt(loc),
+			{},
+			ast::Linkage::Cforall
+		);
+	}
+
 	struct SelfAssignChecker {
 		void previsit( const ast::ApplicationExpr * appExpr );
@@ -121,5 +158,5 @@
 		void previsit( const ast::FunctionDecl * ) { visit_children = false; }
 
-	  protected:
+	protected:
 		ObjectSet curVars;
 	};
@@ -202,5 +239,5 @@
 
 		SemanticErrorException errors;
-	  private:
+	private:
 		template< typename... Params >
 		void emit( CodeLocation, const Params &... params );
@@ -288,5 +325,5 @@
 		static UniqueName dtorNamer( "__cleanup_dtor" );
 		std::string name = dtorNamer.newName();
-		ast::FunctionDecl * dtorFunc = SymTab::genDefaultFunc( loc, name, objDecl->type->stripReferences(), false );
+		ast::FunctionDecl * dtorFunc = genDefaultFunc( loc, name, objDecl->type->stripReferences(), false );
 		stmtsToAdd.push_back( new ast::DeclStmt(loc, dtorFunc ) );
 
@@ -1080,12 +1117,12 @@
 	void InsertDtors::previsit( const ast::BranchStmt * stmt ) {
 		switch( stmt->kind ) {
-		  case ast::BranchStmt::Continue:
-		  case ast::BranchStmt::Break:
+		case ast::BranchStmt::Continue:
+		case ast::BranchStmt::Break:
 			// could optimize the break/continue case, because the S_L-S_G check is unnecessary (this set should
 			// always be empty), but it serves as a small sanity check.
-		  case ast::BranchStmt::Goto:
+		case ast::BranchStmt::Goto:
 			handleGoto( stmt );
 			break;
-		  default:
+		default:
 			assert( false );
 		} // switch
@@ -1312,7 +1349,5 @@
 		// xxx - functions returning ast::ptr seems wrong...
 		auto res = ResolvExpr::findVoidExpression( untypedExpr, { symtab, transUnit().global } );
-		// Fix CodeLocation (at least until resolver is fixed).
-		auto fix = localFillCodeLocations( untypedExpr->location, res.release() );
-		return strict_dynamic_cast<const ast::Expr *>( fix );
+		return res.release();
 	}
 
Index: src/InitTweak/GenInit.cc
===================================================================
--- src/InitTweak/GenInit.cc	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/InitTweak/GenInit.cc	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -39,4 +39,5 @@
 #include "ResolvExpr/Resolver.h"
 #include "SymTab/Autogen.h"            // for genImplicitCall
+#include "SymTab/GenImplicitCall.hpp"  // for genImplicitCall
 #include "SymTab/Mangler.h"            // for Mangler
 #include "SynTree/LinkageSpec.h"       // for isOverridable, C
Index: src/Parser/DeclarationNode.cc
===================================================================
--- src/Parser/DeclarationNode.cc	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Parser/DeclarationNode.cc	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -10,7 +10,9 @@
 // Created On       : Sat May 16 12:34:05 2015
 // Last Modified By : Andrew Beach
-// Last Modified On : Tue Apr  4 10:28:00 2023
-// Update Count     : 1392
+// Last Modified On : Thr Apr 20 11:46:00 2023
+// Update Count     : 1393
 //
+
+#include "DeclarationNode.h"
 
 #include <cassert>                 // for assert, assertf, strict_dynamic_cast
@@ -34,5 +36,7 @@
 #include "Common/UniqueName.h"     // for UniqueName
 #include "Common/utility.h"        // for maybeClone
-#include "Parser/ParseNode.h"      // for DeclarationNode, ExpressionNode
+#include "Parser/ExpressionNode.h" // for ExpressionNode
+#include "Parser/InitializerNode.h"// for InitializerNode
+#include "Parser/StatementNode.h"  // for StatementNode
 #include "TypeData.h"              // for TypeData, TypeData::Aggregate_t
 #include "TypedefTable.h"          // for TypedefTable
@@ -999,66 +1003,116 @@
 }
 
-void buildList( const DeclarationNode * firstNode,
+// If a typedef wraps an anonymous declaration, name the inner declaration
+// so it has a consistent name across translation units.
+static void nameTypedefedDecl(
+		DeclarationNode * innerDecl,
+		const DeclarationNode * outerDecl ) {
+	TypeData * outer = outerDecl->type;
+	assert( outer );
+	// First make sure this is a typedef:
+	if ( outer->kind != TypeData::Symbolic || !outer->symbolic.isTypedef ) {
+		return;
+	}
+	TypeData * inner = innerDecl->type;
+	assert( inner );
+	// Always clear any CVs associated with the aggregate:
+	inner->qualifiers.reset();
+	// Handle anonymous aggregates: typedef struct { int i; } foo
+	if ( inner->kind == TypeData::Aggregate && inner->aggregate.anon ) {
+		delete inner->aggregate.name;
+		inner->aggregate.name = new string( "__anonymous_" + *outerDecl->name );
+		inner->aggregate.anon = false;
+		assert( outer->base );
+		delete outer->base->aggInst.aggregate->aggregate.name;
+		outer->base->aggInst.aggregate->aggregate.name = new string( "__anonymous_" + *outerDecl->name );
+		outer->base->aggInst.aggregate->aggregate.anon = false;
+		outer->base->aggInst.aggregate->qualifiers.reset();
+	// Handle anonymous enumeration: typedef enum { A, B, C } foo
+	} else if ( inner->kind == TypeData::Enum && inner->enumeration.anon ) {
+		delete inner->enumeration.name;
+		inner->enumeration.name = new string( "__anonymous_" + *outerDecl->name );
+		inner->enumeration.anon = false;
+		assert( outer->base );
+		delete outer->base->aggInst.aggregate->enumeration.name;
+		outer->base->aggInst.aggregate->enumeration.name = new string( "__anonymous_" + *outerDecl->name );
+		outer->base->aggInst.aggregate->enumeration.anon = false;
+		// No qualifiers.reset() here.
+	}
+}
+
+// This code handles a special issue with the attribute transparent_union.
+//
+//    typedef union U { int i; } typedef_name __attribute__(( aligned(16) )) __attribute__(( transparent_union ))
+//
+// Here the attribute aligned goes with the typedef_name, so variables declared of this type are
+// aligned.  However, the attribute transparent_union must be moved from the typedef_name to
+// alias union U.  Currently, this is the only know attribute that must be moved from typedef to
+// alias.
+static void moveUnionAttribute( ast::Decl * decl, ast::UnionDecl * unionDecl ) {
+	if ( auto typedefDecl = dynamic_cast<ast::TypedefDecl *>( decl ) ) {
+		// Is the typedef alias a union aggregate?
+		if ( nullptr == unionDecl ) return;
+
+		// If typedef is an alias for a union, then its alias type was hoisted above and remembered.
+		if ( auto unionInstType = typedefDecl->base.as<ast::UnionInstType>() ) {
+			auto instType = ast::mutate( unionInstType );
+			// Remove all transparent_union attributes from typedef and move to alias union.
+			for ( auto attr = instType->attributes.begin() ; attr != instType->attributes.end() ; ) {
+				assert( *attr );
+				if ( (*attr)->name == "transparent_union" || (*attr)->name == "__transparent_union__" ) {
+					unionDecl->attributes.emplace_back( attr->release() );
+					attr = instType->attributes.erase( attr );
+				} else {
+					attr++;
+				}
+			}
+			typedefDecl->base = instType;
+		}
+	}
+}
+
+// Get the non-anonymous name of the instance type of the declaration,
+// if one exists.
+static const std::string * getInstTypeOfName( ast::Decl * decl ) {
+	if ( auto dwt = dynamic_cast<ast::DeclWithType *>( decl ) ) {
+		if ( auto aggr = dynamic_cast<ast::BaseInstType const *>( dwt->get_type() ) ) {
+			if ( aggr->name.find("anonymous") == std::string::npos ) {
+				return &aggr->name;
+			}
+		}
+	}
+	return nullptr;
+}
+
+void buildList( DeclarationNode * firstNode,
 		std::vector<ast::ptr<ast::Decl>> & outputList ) {
 	SemanticErrorException errors;
 	std::back_insert_iterator<std::vector<ast::ptr<ast::Decl>>> out( outputList );
 
-	for ( const DeclarationNode * cur = firstNode; cur; cur = dynamic_cast< DeclarationNode * >( cur->get_next() ) ) {
+	for ( const DeclarationNode * cur = firstNode ; cur ; cur = strict_next( cur ) ) {
 		try {
-			bool extracted = false, anon = false;
-			ast::AggregateDecl * unionDecl = nullptr;
+			bool extracted_named = false;
+			ast::UnionDecl * unionDecl = nullptr;
 
 			if ( DeclarationNode * extr = cur->extractAggregate() ) {
-				// Handle the case where a SUE declaration is contained within an object or type declaration.
-
 				assert( cur->type );
-				// Replace anonymous SUE name with typedef name to prevent anonymous naming problems across translation units.
-				if ( cur->type->kind == TypeData::Symbolic && cur->type->symbolic.isTypedef ) {
-					assert( extr->type );
-					// Handle anonymous aggregates: typedef struct { int i; } foo
-					extr->type->qualifiers.reset();		// clear any CVs associated with the aggregate
-					if ( extr->type->kind == TypeData::Aggregate && extr->type->aggregate.anon ) {
-						delete extr->type->aggregate.name;
-						extr->type->aggregate.name = new string( "__anonymous_" + *cur->name );
-						extr->type->aggregate.anon = false;
-						assert( cur->type->base );
-						if ( cur->type->base ) {
-							delete cur->type->base->aggInst.aggregate->aggregate.name;
-							cur->type->base->aggInst.aggregate->aggregate.name = new string( "__anonymous_" + *cur->name );
-							cur->type->base->aggInst.aggregate->aggregate.anon = false;
-							cur->type->base->aggInst.aggregate->qualifiers.reset();
-						} // if
-					} // if
-					// Handle anonymous enumeration: typedef enum { A, B, C } foo
-					if ( extr->type->kind == TypeData::Enum && extr->type->enumeration.anon ) {
-						delete extr->type->enumeration.name;
-						extr->type->enumeration.name = new string( "__anonymous_" + *cur->name );
-						extr->type->enumeration.anon = false;
-						assert( cur->type->base );
-						if ( cur->type->base ) {
-							delete cur->type->base->aggInst.aggregate->enumeration.name;
-							cur->type->base->aggInst.aggregate->enumeration.name = new string( "__anonymous_" + *cur->name );
-							cur->type->base->aggInst.aggregate->enumeration.anon = false;
-						} // if
-					} // if
-				} // if
-
-				ast::Decl * decl = extr->build();
-				if ( decl ) {
+				nameTypedefedDecl( extr, cur );
+
+				if ( ast::Decl * decl = extr->build() ) {
 					// Remember the declaration if it is a union aggregate ?
 					unionDecl = dynamic_cast<ast::UnionDecl *>( decl );
 
-					decl->location = cur->location;
 					*out++ = decl;
 
 					// need to remember the cases where a declaration contains an anonymous aggregate definition
-					extracted = true;
 					assert( extr->type );
 					if ( extr->type->kind == TypeData::Aggregate ) {
 						// typedef struct { int A } B is the only case?
-						anon = extr->type->aggregate.anon;
+						extracted_named = !extr->type->aggregate.anon;
 					} else if ( extr->type->kind == TypeData::Enum ) {
 						// typedef enum { A } B is the only case?
-						anon = extr->type->enumeration.anon;
+						extracted_named = !extr->type->enumeration.anon;
+					} else {
+						extracted_named = true;
 					}
 				} // if
@@ -1066,60 +1120,28 @@
 			} // if
 
-			ast::Decl * decl = cur->build();
-			if ( decl ) {
-				if ( auto typedefDecl = dynamic_cast<ast::TypedefDecl *>( decl ) ) {
-					if ( unionDecl ) {					// is the typedef alias a union aggregate ?
-						// This code handles a special issue with the attribute transparent_union.
-						//
-						//    typedef union U { int i; } typedef_name __attribute__(( aligned(16) )) __attribute__(( transparent_union ))
-						//
-						// Here the attribute aligned goes with the typedef_name, so variables declared of this type are
-						// aligned.  However, the attribute transparent_union must be moved from the typedef_name to
-						// alias union U.  Currently, this is the only know attribute that must be moved from typedef to
-						// alias.
-
-						// If typedef is an alias for a union, then its alias type was hoisted above and remembered.
-						if ( auto unionInstType = typedefDecl->base.as<ast::UnionInstType>() ) {
-							auto instType = ast::mutate( unionInstType );
-							// Remove all transparent_union attributes from typedef and move to alias union.
-							for ( auto attr = instType->attributes.begin() ; attr != instType->attributes.end() ; ) { // forward order
-								assert( *attr );
-								if ( (*attr)->name == "transparent_union" || (*attr)->name == "__transparent_union__" ) {
-									unionDecl->attributes.emplace_back( attr->release() );
-									attr = instType->attributes.erase( attr );
-								} else {
-									attr++;
-								} // if
-							} // for
-							typedefDecl->base = instType;
-						} // if
-					} // if
+			if ( ast::Decl * decl = cur->build() ) {
+				moveUnionAttribute( decl, unionDecl );
+
+				if ( "" == decl->name && !cur->get_inLine() ) {
+					// Don't include anonymous declaration for named aggregates,
+					// but do include them for anonymous aggregates, e.g.:
+					// struct S {
+					//   struct T { int x; }; // no anonymous member
+					//   struct { int y; };   // anonymous member
+					//   struct T;            // anonymous member
+					// };
+					if ( extracted_named ) {
+						continue;
+					}
+
+					if ( auto name = getInstTypeOfName( decl ) ) {
+						// Temporary: warn about anonymous member declarations of named types, since
+						// this conflicts with the syntax for the forward declaration of an anonymous type.
+						SemanticWarning( cur->location, Warning::AggrForwardDecl, name->c_str() );
+					}
 				} // if
-
-				// don't include anonymous declaration for named aggregates, but do include them for anonymous aggregates, e.g.:
-				// struct S {
-				//   struct T { int x; }; // no anonymous member
-				//   struct { int y; };   // anonymous member
-				//   struct T;            // anonymous member
-				// };
-				if ( ! (extracted && decl->name == "" && ! anon && ! cur->get_inLine()) ) {
-					if ( decl->name == "" ) {
-						if ( auto dwt = dynamic_cast<ast::DeclWithType *>( decl ) ) {
-							if ( auto aggr = dynamic_cast<ast::BaseInstType const *>( dwt->get_type() ) ) {
-								if ( aggr->name.find("anonymous") == std::string::npos ) {
-									if ( ! cur->get_inLine() ) {
-										// temporary: warn about anonymous member declarations of named types, since
-										// this conflicts with the syntax for the forward declaration of an anonymous type
-										SemanticWarning( cur->location, Warning::AggrForwardDecl, aggr->name.c_str() );
-									} // if
-								} // if
-							} // if
-						} // if
-					} // if
-					decl->location = cur->location;
-					*out++ = decl;
-				} // if
+				*out++ = decl;
 			} // if
-		} catch( SemanticErrorException & e ) {
+		} catch ( SemanticErrorException & e ) {
 			errors.append( e );
 		} // try
@@ -1132,12 +1154,12 @@
 
 // currently only builds assertions, function parameters, and return values
-void buildList( const DeclarationNode * firstNode, std::vector<ast::ptr<ast::DeclWithType>> & outputList ) {
+void buildList( DeclarationNode * firstNode, std::vector<ast::ptr<ast::DeclWithType>> & outputList ) {
 	SemanticErrorException errors;
 	std::back_insert_iterator<std::vector<ast::ptr<ast::DeclWithType>>> out( outputList );
 
-	for ( const DeclarationNode * cur = firstNode; cur; cur = dynamic_cast< DeclarationNode * >( cur->get_next() ) ) {
+	for ( const DeclarationNode * cur = firstNode; cur; cur = strict_next( cur ) ) {
 		try {
 			ast::Decl * decl = cur->build();
-			assert( decl );
+			assertf( decl, "buildList: build for ast::DeclWithType." );
 			if ( ast::DeclWithType * dwt = dynamic_cast<ast::DeclWithType *>( decl ) ) {
 				dwt->location = cur->location;
@@ -1168,6 +1190,8 @@
 				);
 				*out++ = obj;
+			} else {
+				assertf( false, "buildList: Could not convert to ast::DeclWithType." );
 			} // if
-		} catch( SemanticErrorException & e ) {
+		} catch ( SemanticErrorException & e ) {
 			errors.append( e );
 		} // try
@@ -1183,14 +1207,12 @@
 	SemanticErrorException errors;
 	std::back_insert_iterator<std::vector<ast::ptr<ast::Type>>> out( outputList );
-	const DeclarationNode * cur = firstNode;
-
-	while ( cur ) {
+
+	for ( const DeclarationNode * cur = firstNode ; cur ; cur = strict_next( cur ) ) {
 		try {
 			* out++ = cur->buildType();
-		} catch( SemanticErrorException & e ) {
+		} catch ( SemanticErrorException & e ) {
 			errors.append( e );
 		} // try
-		cur = dynamic_cast< DeclarationNode * >( cur->get_next() );
-	} // while
+	} // for
 
 	if ( ! errors.isEmpty() ) {
Index: src/Parser/DeclarationNode.h
===================================================================
--- src/Parser/DeclarationNode.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
+++ src/Parser/DeclarationNode.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -0,0 +1,219 @@
+//
+// 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.
+//
+// DeclarationNode.h --
+//
+// Author           : Andrew Beach
+// Created On       : Wed Apr  5 11:38:00 2023
+// Last Modified By : Andrew Beach
+// Last Modified On : Wed Apr  5 11:55:00 2023
+// Update Count     : 0
+//
+
+#pragma once
+
+#include "ParseNode.h"
+
+struct TypeData;
+class InitializerNode;
+
+struct DeclarationNode : public ParseNode {
+	// These enumerations must harmonize with their names in DeclarationNode.cc.
+	enum BasicType {
+		Void, Bool, Char, Int, Int128,
+		Float, Double, LongDouble, uuFloat80, uuFloat128,
+		uFloat16, uFloat32, uFloat32x, uFloat64, uFloat64x, uFloat128, uFloat128x,
+		NoBasicType
+	};
+	static const char * basicTypeNames[];
+	enum ComplexType { Complex, NoComplexType, Imaginary };
+	// Imaginary unsupported => parse, but make invisible and print error message
+	static const char * complexTypeNames[];
+	enum Signedness { Signed, Unsigned, NoSignedness };
+	static const char * signednessNames[];
+	enum Length { Short, Long, LongLong, NoLength };
+	static const char * lengthNames[];
+	enum BuiltinType { Valist, AutoType, Zero, One, NoBuiltinType };
+	static const char * builtinTypeNames[];
+
+	static DeclarationNode * newStorageClass( ast::Storage::Classes );
+	static DeclarationNode * newFuncSpecifier( ast::Function::Specs );
+	static DeclarationNode * newTypeQualifier( ast::CV::Qualifiers );
+	static DeclarationNode * newBasicType( BasicType );
+	static DeclarationNode * newComplexType( ComplexType );
+	static DeclarationNode * newSignedNess( Signedness );
+	static DeclarationNode * newLength( Length );
+	static DeclarationNode * newBuiltinType( BuiltinType );
+	static DeclarationNode * newForall( DeclarationNode * );
+	static DeclarationNode * newFromTypedef( const std::string * );
+	static DeclarationNode * newFromGlobalScope();
+	static DeclarationNode * newQualifiedType( DeclarationNode *, DeclarationNode * );
+	static DeclarationNode * newFunction( const std::string * name, DeclarationNode * ret, DeclarationNode * param, StatementNode * body );
+	static DeclarationNode * newAggregate( ast::AggregateDecl::Aggregate kind, const std::string * name, ExpressionNode * actuals, DeclarationNode * fields, bool body );
+	static DeclarationNode * newEnum( const std::string * name, DeclarationNode * constants, bool body, bool typed, DeclarationNode * base = nullptr, EnumHiding hiding = EnumHiding::Visible );
+	static DeclarationNode * newEnumConstant( const std::string * name, ExpressionNode * constant );
+	static DeclarationNode * newEnumValueGeneric( const std::string * name, InitializerNode * init );
+	static DeclarationNode * newEnumInLine( const std::string name );
+	static DeclarationNode * newName( const std::string * );
+	static DeclarationNode * newFromTypeGen( const std::string *, ExpressionNode * params );
+	static DeclarationNode * newTypeParam( ast::TypeDecl::Kind, const std::string * );
+	static DeclarationNode * newTrait( const std::string * name, DeclarationNode * params, DeclarationNode * asserts );
+	static DeclarationNode * newTraitUse( const std::string * name, ExpressionNode * params );
+	static DeclarationNode * newTypeDecl( const std::string * name, DeclarationNode * typeParams );
+	static DeclarationNode * newPointer( DeclarationNode * qualifiers, OperKinds kind );
+	static DeclarationNode * newArray( ExpressionNode * size, DeclarationNode * qualifiers, bool isStatic );
+	static DeclarationNode * newVarArray( DeclarationNode * qualifiers );
+	static DeclarationNode * newBitfield( ExpressionNode * size );
+	static DeclarationNode * newTuple( DeclarationNode * members );
+	static DeclarationNode * newTypeof( ExpressionNode * expr, bool basetypeof = false );
+	static DeclarationNode * newVtableType( DeclarationNode * expr );
+	static DeclarationNode * newAttribute( const std::string *, ExpressionNode * expr = nullptr ); // gcc attributes
+	static DeclarationNode * newDirectiveStmt( StatementNode * stmt ); // gcc external directive statement
+	static DeclarationNode * newAsmStmt( StatementNode * stmt ); // gcc external asm statement
+	static DeclarationNode * newStaticAssert( ExpressionNode * condition, ast::Expr * message );
+
+	DeclarationNode();
+	~DeclarationNode();
+	DeclarationNode * clone() const override;
+
+	DeclarationNode * addQualifiers( DeclarationNode * );
+	void checkQualifiers( const TypeData *, const TypeData * );
+	void checkSpecifiers( DeclarationNode * );
+	DeclarationNode * copySpecifiers( DeclarationNode * );
+	DeclarationNode * addType( DeclarationNode * );
+	DeclarationNode * addTypedef();
+	DeclarationNode * addEnumBase( DeclarationNode * );
+	DeclarationNode * addAssertions( DeclarationNode * );
+	DeclarationNode * addName( std::string * );
+	DeclarationNode * addAsmName( DeclarationNode * );
+	DeclarationNode * addBitfield( ExpressionNode * size );
+	DeclarationNode * addVarArgs();
+	DeclarationNode * addFunctionBody( StatementNode * body, ExpressionNode * with = nullptr );
+	DeclarationNode * addOldDeclList( DeclarationNode * list );
+	DeclarationNode * setBase( TypeData * newType );
+	DeclarationNode * copyAttribute( DeclarationNode * attr );
+	DeclarationNode * addPointer( DeclarationNode * qualifiers );
+	DeclarationNode * addArray( DeclarationNode * array );
+	DeclarationNode * addNewPointer( DeclarationNode * pointer );
+	DeclarationNode * addNewArray( DeclarationNode * array );
+	DeclarationNode * addParamList( DeclarationNode * list );
+	DeclarationNode * addIdList( DeclarationNode * list ); // old-style functions
+	DeclarationNode * addInitializer( InitializerNode * init );
+	DeclarationNode * addTypeInitializer( DeclarationNode * init );
+
+	DeclarationNode * cloneType( std::string * newName );
+	DeclarationNode * cloneBaseType( DeclarationNode * newdecl );
+
+	DeclarationNode * appendList( DeclarationNode * node ) {
+		return (DeclarationNode *)set_last( node );
+	}
+
+	virtual void print( __attribute__((unused)) std::ostream & os, __attribute__((unused)) int indent = 0 ) const override;
+	virtual void printList( __attribute__((unused)) std::ostream & os, __attribute__((unused)) int indent = 0 ) const override;
+
+	ast::Decl * build() const;
+	ast::Type * buildType() const;
+
+	ast::Linkage::Spec get_linkage() const { return linkage; }
+	DeclarationNode * extractAggregate() const;
+	bool has_enumeratorValue() const { return (bool)enumeratorValue; }
+	ExpressionNode * consume_enumeratorValue() const { return const_cast<DeclarationNode *>(this)->enumeratorValue.release(); }
+
+	bool get_extension() const { return extension; }
+	DeclarationNode * set_extension( bool exten ) { extension = exten; return this; }
+
+	bool get_inLine() const { return inLine; }
+	DeclarationNode * set_inLine( bool inL ) { inLine = inL; return this; }
+
+	DeclarationNode * get_last() { return (DeclarationNode *)ParseNode::get_last(); }
+
+	struct Variable_t {
+//		const std::string * name;
+		ast::TypeDecl::Kind tyClass;
+		DeclarationNode * assertions;
+		DeclarationNode * initializer;
+	};
+	Variable_t variable;
+
+	struct StaticAssert_t {
+		ExpressionNode * condition;
+		ast::Expr * message;
+	};
+	StaticAssert_t assert;
+
+	BuiltinType builtin = NoBuiltinType;
+
+	TypeData * type = nullptr;
+
+	bool inLine = false;
+	bool enumInLine = false;
+	ast::Function::Specs funcSpecs;
+	ast::Storage::Classes storageClasses;
+
+	ExpressionNode * bitfieldWidth = nullptr;
+	std::unique_ptr<ExpressionNode> enumeratorValue;
+	bool hasEllipsis = false;
+	ast::Linkage::Spec linkage;
+	ast::Expr * asmName = nullptr;
+	std::vector<ast::ptr<ast::Attribute>> attributes;
+	InitializerNode * initializer = nullptr;
+	bool extension = false;
+	std::string error;
+	StatementNode * asmStmt = nullptr;
+	StatementNode * directiveStmt = nullptr;
+
+	static UniqueName anonymous;
+}; // DeclarationNode
+
+ast::Type * buildType( TypeData * type );
+
+static inline ast::Type * maybeMoveBuildType( const DeclarationNode * orig ) {
+	ast::Type * ret = orig ? orig->buildType() : nullptr;
+	delete orig;
+	return ret;
+}
+
+template<typename NodeType>
+NodeType * strict_next( NodeType * node ) {
+	ParseNode * next = node->get_next();
+	if ( nullptr == next ) return nullptr;
+	if ( NodeType * ret = dynamic_cast<NodeType *>( next ) ) return ret;
+	SemanticError( next->location, "internal error, non-homogeneous nodes founds in buildList processing." );
+}
+
+// This generic buildList is here along side its overloads.
+template<typename AstType, typename NodeType,
+		template<typename, typename...> class Container, typename... Args>
+void buildList( NodeType * firstNode,
+		Container<ast::ptr<AstType>, Args...> & output ) {
+	SemanticErrorException errors;
+	std::back_insert_iterator<Container<ast::ptr<AstType>, Args...>> out( output );
+
+	for ( NodeType * cur = firstNode ; cur ; cur = strict_next( cur ) ) {
+		try {
+			AstType * node = dynamic_cast<AstType *>( maybeBuild( cur ) );
+			assertf( node, "buildList: Did not build node of correct type." );
+			*out++ = node;
+		} catch ( SemanticErrorException & e ) {
+			errors.append( e );
+		} // try
+	} // for
+	if ( ! errors.isEmpty() ) {
+		throw errors;
+	} // if
+}
+
+void buildList( DeclarationNode * firstNode, std::vector<ast::ptr<ast::Decl>> & outputList );
+void buildList( DeclarationNode * firstNode, std::vector<ast::ptr<ast::DeclWithType>> & outputList );
+void buildTypeList( const DeclarationNode * firstNode, std::vector<ast::ptr<ast::Type>> & outputList );
+
+template<typename AstType, typename NodeType,
+		template<typename, typename...> class Container, typename... Args>
+void buildMoveList( NodeType * firstNode,
+		Container<ast::ptr<AstType>, Args...> & output ) {
+	buildList<AstType, NodeType, Container, Args...>( firstNode, output );
+	delete firstNode;
+}
Index: src/Parser/ExpressionNode.cc
===================================================================
--- src/Parser/ExpressionNode.cc	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Parser/ExpressionNode.cc	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -13,4 +13,6 @@
 // Update Count     : 1083
 //
+
+#include "ExpressionNode.h"
 
 #include <cassert>                 // for assert
@@ -25,5 +27,6 @@
 #include "Common/SemanticError.h"  // for SemanticError
 #include "Common/utility.h"        // for maybeMoveBuild, maybeBuild, CodeLo...
-#include "ParseNode.h"             // for ExpressionNode, maybeMoveBuildType
+#include "DeclarationNode.h"       // for DeclarationNode
+#include "InitializerNode.h"       // for InitializerNode
 #include "parserutility.h"         // for notZeroExpr
 
@@ -699,11 +702,4 @@
 } // build_binary_val
 
-ast::Expr * build_binary_ptr( const CodeLocation & location,
-		OperKinds op,
-		ExpressionNode * expr_node1,
-		ExpressionNode * expr_node2 ) {
-	return build_binary_val( location, op, expr_node1, expr_node2 );
-} // build_binary_ptr
-
 ast::Expr * build_cond( const CodeLocation & location,
 		ExpressionNode * expr_node1,
Index: src/Parser/ExpressionNode.h
===================================================================
--- src/Parser/ExpressionNode.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
+++ src/Parser/ExpressionNode.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -0,0 +1,84 @@
+//
+// 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.
+//
+// ExpressionNode.h --
+//
+// Author           : Andrew Beach
+// Created On       : Wed Apr  5 11:34:00 2023
+// Last Modified By : Andrew Beach
+// Last Modified On : Wed Apr  5 11:50:00 2023
+// Update Count     : 0
+//
+
+#pragma once
+
+#include "ParseNode.h"
+
+class InitializerNode;
+
+class ExpressionNode final : public ParseNode {
+public:
+	ExpressionNode( ast::Expr * expr = nullptr ) : expr( expr ) {}
+	virtual ~ExpressionNode() {}
+	virtual ExpressionNode * clone() const override {
+		if ( nullptr == expr ) return nullptr;
+		return static_cast<ExpressionNode*>(
+			(new ExpressionNode( ast::shallowCopy( expr.get() ) ))->set_next( maybeCopy( get_next() ) ));
+	}
+
+	bool get_extension() const { return extension; }
+	ExpressionNode * set_extension( bool exten ) { extension = exten; return this; }
+
+	virtual void print( std::ostream & os, __attribute__((unused)) int indent = 0 ) const override {
+		os << expr.get();
+	}
+	void printOneLine( __attribute__((unused)) std::ostream & os, __attribute__((unused)) int indent = 0 ) const {}
+
+	template<typename T>
+	bool isExpressionType() const {  return nullptr != dynamic_cast<T>(expr.get()); }
+
+	ast::Expr * build() {
+		ast::Expr * node = expr.release();
+		node->set_extension( this->get_extension() );
+		node->location = this->location;
+		return node;
+	}
+
+	// Public because of lifetime implications (what lifetime implications?)
+	std::unique_ptr<ast::Expr> expr;
+private:
+	bool extension = false;
+}; // ExpressionNode
+
+// These 4 routines modify the string:
+ast::Expr * build_constantInteger( const CodeLocation &, std::string & );
+ast::Expr * build_constantFloat( const CodeLocation &, std::string & );
+ast::Expr * build_constantChar( const CodeLocation &, std::string & );
+ast::Expr * build_constantStr( const CodeLocation &, std::string & );
+ast::Expr * build_field_name_FLOATING_FRACTIONconstant( const CodeLocation &, const std::string & str );
+ast::Expr * build_field_name_FLOATING_DECIMALconstant( const CodeLocation &, const std::string & str );
+ast::Expr * build_field_name_FLOATINGconstant( const CodeLocation &, const std::string & str );
+ast::Expr * build_field_name_fraction_constants( const CodeLocation &, ast::Expr * fieldName, ExpressionNode * fracts );
+
+ast::NameExpr * build_varref( const CodeLocation &, const std::string * name );
+ast::QualifiedNameExpr * build_qualified_expr( const CodeLocation &, const DeclarationNode * decl_node, const ast::NameExpr * name );
+ast::QualifiedNameExpr * build_qualified_expr( const CodeLocation &, const ast::EnumDecl * decl, const ast::NameExpr * name );
+ast::DimensionExpr * build_dimensionref( const CodeLocation &, const std::string * name );
+
+ast::Expr * build_cast( const CodeLocation &, DeclarationNode * decl_node, ExpressionNode * expr_node );
+ast::Expr * build_keyword_cast( const CodeLocation &, ast::AggregateDecl::Aggregate target, ExpressionNode * expr_node );
+ast::Expr * build_virtual_cast( const CodeLocation &, DeclarationNode * decl_node, ExpressionNode * expr_node );
+ast::Expr * build_fieldSel( const CodeLocation &, ExpressionNode * expr_node, ast::Expr * member );
+ast::Expr * build_pfieldSel( const CodeLocation &, ExpressionNode * expr_node, ast::Expr * member );
+ast::Expr * build_offsetOf( const CodeLocation &, DeclarationNode * decl_node, ast::NameExpr * member );
+ast::Expr * build_and( const CodeLocation &, ExpressionNode * expr_node1, ExpressionNode * expr_node2 );
+ast::Expr * build_and_or( const CodeLocation &, ExpressionNode * expr_node1, ExpressionNode * expr_node2, ast::LogicalFlag flag );
+ast::Expr * build_unary_val( const CodeLocation &, OperKinds op, ExpressionNode * expr_node );
+ast::Expr * build_binary_val( const CodeLocation &, OperKinds op, ExpressionNode * expr_node1, ExpressionNode * expr_node2 );
+ast::Expr * build_cond( const CodeLocation &, ExpressionNode * expr_node1, ExpressionNode * expr_node2, ExpressionNode * expr_node3 );
+ast::Expr * build_tuple( const CodeLocation &, ExpressionNode * expr_node = nullptr );
+ast::Expr * build_func( const CodeLocation &, ExpressionNode * function, ExpressionNode * expr_node );
+ast::Expr * build_compoundLiteral( const CodeLocation &, DeclarationNode * decl_node, InitializerNode * kids );
Index: src/Parser/InitializerNode.cc
===================================================================
--- src/Parser/InitializerNode.cc	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Parser/InitializerNode.cc	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -14,9 +14,9 @@
 //
 
+#include "InitializerNode.h"
+
 #include <iostream>                // for operator<<, ostream, basic_ostream
 #include <list>                    // for list
 #include <string>                  // for operator<<, string
-
-using namespace std;
 
 #include "AST/Expr.hpp"            // for Expr
@@ -24,5 +24,8 @@
 #include "Common/SemanticError.h"  // for SemanticError
 #include "Common/utility.h"        // for maybeBuild
-#include "ParseNode.h"             // for InitializerNode, ExpressionNode
+#include "ExpressionNode.h"        // for ExpressionNode
+#include "DeclarationNode.h"       // for buildList
+
+using namespace std;
 
 static ast::ConstructFlag toConstructFlag( bool maybeConstructed ) {
Index: src/Parser/InitializerNode.h
===================================================================
--- src/Parser/InitializerNode.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
+++ src/Parser/InitializerNode.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -0,0 +1,51 @@
+//
+// 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.
+//
+// InitializerNode.h --
+//
+// Author           : Andrew Beach
+// Created On       : Wed Apr  5 11:31:00 2023
+// Last Modified By : Andrew Beach
+// Last Modified On : Wed Apr  5 11:48:00 2023
+// Update Count     : 0
+//
+
+#pragma once
+
+#include "ParseNode.h"
+
+class InitializerNode : public ParseNode {
+public:
+	InitializerNode( ExpressionNode *, bool aggrp = false, ExpressionNode * des = nullptr );
+	InitializerNode( InitializerNode *, bool aggrp = false, ExpressionNode * des = nullptr );
+	InitializerNode( bool isDelete );
+	~InitializerNode();
+	virtual InitializerNode * clone() const { assert( false ); return nullptr; }
+
+	ExpressionNode * get_expression() const { return expr; }
+
+	InitializerNode * set_designators( ExpressionNode * des ) { designator = des; return this; }
+	ExpressionNode * get_designators() const { return designator; }
+
+	InitializerNode * set_maybeConstructed( bool value ) { maybeConstructed = value; return this; }
+	bool get_maybeConstructed() const { return maybeConstructed; }
+
+	bool get_isDelete() const { return isDelete; }
+
+	InitializerNode * next_init() const { return kids; }
+
+	void print( std::ostream & os, int indent = 0 ) const;
+	void printOneLine( std::ostream & ) const;
+
+	virtual ast::Init * build() const;
+private:
+	ExpressionNode * expr;
+	bool aggregate;
+	ExpressionNode * designator;                        // may be list
+	InitializerNode * kids;
+	bool maybeConstructed;
+	bool isDelete;
+}; // InitializerNode
Index: src/Parser/ParseNode.h
===================================================================
--- src/Parser/ParseNode.h	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Parser/ParseNode.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -38,4 +38,5 @@
 class DeclarationWithType;
 class Initializer;
+class InitializerNode;
 class ExpressionNode;
 struct StatementNode;
@@ -80,75 +81,4 @@
 }; // ParseNode
 
-//##############################################################################
-
-class InitializerNode : public ParseNode {
-  public:
-	InitializerNode( ExpressionNode *, bool aggrp = false, ExpressionNode * des = nullptr );
-	InitializerNode( InitializerNode *, bool aggrp = false, ExpressionNode * des = nullptr );
-	InitializerNode( bool isDelete );
-	~InitializerNode();
-	virtual InitializerNode * clone() const { assert( false ); return nullptr; }
-
-	ExpressionNode * get_expression() const { return expr; }
-
-	InitializerNode * set_designators( ExpressionNode * des ) { designator = des; return this; }
-	ExpressionNode * get_designators() const { return designator; }
-
-	InitializerNode * set_maybeConstructed( bool value ) { maybeConstructed = value; return this; }
-	bool get_maybeConstructed() const { return maybeConstructed; }
-
-	bool get_isDelete() const { return isDelete; }
-
-	InitializerNode * next_init() const { return kids; }
-
-	void print( std::ostream & os, int indent = 0 ) const;
-	void printOneLine( std::ostream & ) const;
-
-	virtual ast::Init * build() const;
-  private:
-	ExpressionNode * expr;
-	bool aggregate;
-	ExpressionNode * designator;						// may be list
-	InitializerNode * kids;
-	bool maybeConstructed;
-	bool isDelete;
-}; // InitializerNode
-
-//##############################################################################
-
-class ExpressionNode final : public ParseNode {
-  public:
-	ExpressionNode( ast::Expr * expr = nullptr ) : expr( expr ) {}
-	virtual ~ExpressionNode() {}
-	virtual ExpressionNode * clone() const override {
-		if ( nullptr == expr ) return nullptr;
-		return static_cast<ExpressionNode*>(
-			(new ExpressionNode( ast::shallowCopy( expr.get() ) ))->set_next( maybeCopy( get_next() ) ));
-	}
-
-	bool get_extension() const { return extension; }
-	ExpressionNode * set_extension( bool exten ) { extension = exten; return this; }
-
-	virtual void print( std::ostream & os, __attribute__((unused)) int indent = 0 ) const override {
-		os << expr.get();
-	}
-	void printOneLine( __attribute__((unused)) std::ostream & os, __attribute__((unused)) int indent = 0 ) const {}
-
-	template<typename T>
-	bool isExpressionType() const {	return nullptr != dynamic_cast<T>(expr.get()); }
-
-	ast::Expr * build() const {
-		ast::Expr * node = const_cast<ExpressionNode *>(this)->expr.release();
-		node->set_extension( this->get_extension() );
-		node->location = this->location;
-		return node;
-	}
-
-	// Public because of lifetime implications (what lifetime implications?)
-	std::unique_ptr<ast::Expr> expr;
-  private:
-	bool extension = false;
-}; // ExpressionNode
-
 // Must harmonize with OperName.
 enum class OperKinds {
@@ -169,330 +99,4 @@
 };
 
-// These 4 routines modify the string:
-ast::Expr * build_constantInteger( const CodeLocation &, std::string & );
-ast::Expr * build_constantFloat( const CodeLocation &, std::string & );
-ast::Expr * build_constantChar( const CodeLocation &, std::string & );
-ast::Expr * build_constantStr( const CodeLocation &, std::string & );
-ast::Expr * build_field_name_FLOATING_FRACTIONconstant( const CodeLocation &, const std::string & str );
-ast::Expr * build_field_name_FLOATING_DECIMALconstant( const CodeLocation &, const std::string & str );
-ast::Expr * build_field_name_FLOATINGconstant( const CodeLocation &, const std::string & str );
-ast::Expr * build_field_name_fraction_constants( const CodeLocation &, ast::Expr * fieldName, ExpressionNode * fracts );
-
-ast::NameExpr * build_varref( const CodeLocation &, const std::string * name );
-ast::QualifiedNameExpr * build_qualified_expr( const CodeLocation &, const DeclarationNode * decl_node, const ast::NameExpr * name );
-ast::QualifiedNameExpr * build_qualified_expr( const CodeLocation &, const ast::EnumDecl * decl, const ast::NameExpr * name );
-ast::DimensionExpr * build_dimensionref( const CodeLocation &, const std::string * name );
-
-ast::Expr * build_cast( const CodeLocation &, DeclarationNode * decl_node, ExpressionNode * expr_node );
-ast::Expr * build_keyword_cast( const CodeLocation &, ast::AggregateDecl::Aggregate target, ExpressionNode * expr_node );
-ast::Expr * build_virtual_cast( const CodeLocation &, DeclarationNode * decl_node, ExpressionNode * expr_node );
-ast::Expr * build_fieldSel( const CodeLocation &, ExpressionNode * expr_node, ast::Expr * member );
-ast::Expr * build_pfieldSel( const CodeLocation &, ExpressionNode * expr_node, ast::Expr * member );
-ast::Expr * build_offsetOf( const CodeLocation &, DeclarationNode * decl_node, ast::NameExpr * member );
-ast::Expr * build_and( const CodeLocation &, ExpressionNode * expr_node1, ExpressionNode * expr_node2 );
-ast::Expr * build_and_or( const CodeLocation &, ExpressionNode * expr_node1, ExpressionNode * expr_node2, ast::LogicalFlag flag );
-ast::Expr * build_unary_val( const CodeLocation &, OperKinds op, ExpressionNode * expr_node );
-ast::Expr * build_binary_val( const CodeLocation &, OperKinds op, ExpressionNode * expr_node1, ExpressionNode * expr_node2 );
-ast::Expr * build_binary_ptr( const CodeLocation &, OperKinds op, ExpressionNode * expr_node1, ExpressionNode * expr_node2 );
-ast::Expr * build_cond( const CodeLocation &, ExpressionNode * expr_node1, ExpressionNode * expr_node2, ExpressionNode * expr_node3 );
-ast::Expr * build_tuple( const CodeLocation &, ExpressionNode * expr_node = nullptr );
-ast::Expr * build_func( const CodeLocation &, ExpressionNode * function, ExpressionNode * expr_node );
-ast::Expr * build_compoundLiteral( const CodeLocation &, DeclarationNode * decl_node, InitializerNode * kids );
-
-//##############################################################################
-
-struct TypeData;
-
-struct DeclarationNode : public ParseNode {
-	// These enumerations must harmonize with their names in DeclarationNode.cc.
-	enum BasicType {
-		Void, Bool, Char, Int, Int128,
-		Float, Double, LongDouble, uuFloat80, uuFloat128,
-		uFloat16, uFloat32, uFloat32x, uFloat64, uFloat64x, uFloat128, uFloat128x,
-		NoBasicType
-	};
-	static const char * basicTypeNames[];
-	enum ComplexType { Complex, NoComplexType, Imaginary };	// Imaginary unsupported => parse, but make invisible and print error message
-	static const char * complexTypeNames[];
-	enum Signedness { Signed, Unsigned, NoSignedness };
-	static const char * signednessNames[];
-	enum Length { Short, Long, LongLong, NoLength };
-	static const char * lengthNames[];
-	enum BuiltinType { Valist, AutoType, Zero, One, NoBuiltinType };
-	static const char * builtinTypeNames[];
-
-	static DeclarationNode * newStorageClass( ast::Storage::Classes );
-	static DeclarationNode * newFuncSpecifier( ast::Function::Specs );
-	static DeclarationNode * newTypeQualifier( ast::CV::Qualifiers );
-	static DeclarationNode * newBasicType( BasicType );
-	static DeclarationNode * newComplexType( ComplexType );
-	static DeclarationNode * newSignedNess( Signedness );
-	static DeclarationNode * newLength( Length );
-	static DeclarationNode * newBuiltinType( BuiltinType );
-	static DeclarationNode * newForall( DeclarationNode * );
-	static DeclarationNode * newFromTypedef( const std::string * );
-	static DeclarationNode * newFromGlobalScope();
-	static DeclarationNode * newQualifiedType( DeclarationNode *, DeclarationNode * );
-	static DeclarationNode * newFunction( const std::string * name, DeclarationNode * ret, DeclarationNode * param, StatementNode * body );
-	static DeclarationNode * newAggregate( ast::AggregateDecl::Aggregate kind, const std::string * name, ExpressionNode * actuals, DeclarationNode * fields, bool body );
-	static DeclarationNode * newEnum( const std::string * name, DeclarationNode * constants, bool body, bool typed, DeclarationNode * base = nullptr, EnumHiding hiding = EnumHiding::Visible );
-	static DeclarationNode * newEnumConstant( const std::string * name, ExpressionNode * constant );
-	static DeclarationNode * newEnumValueGeneric( const std::string * name, InitializerNode * init );
-	static DeclarationNode * newEnumInLine( const std::string name );
-	static DeclarationNode * newName( const std::string * );
-	static DeclarationNode * newFromTypeGen( const std::string *, ExpressionNode * params );
-	static DeclarationNode * newTypeParam( ast::TypeDecl::Kind, const std::string * );
-	static DeclarationNode * newTrait( const std::string * name, DeclarationNode * params, DeclarationNode * asserts );
-	static DeclarationNode * newTraitUse( const std::string * name, ExpressionNode * params );
-	static DeclarationNode * newTypeDecl( const std::string * name, DeclarationNode * typeParams );
-	static DeclarationNode * newPointer( DeclarationNode * qualifiers, OperKinds kind );
-	static DeclarationNode * newArray( ExpressionNode * size, DeclarationNode * qualifiers, bool isStatic );
-	static DeclarationNode * newVarArray( DeclarationNode * qualifiers );
-	static DeclarationNode * newBitfield( ExpressionNode * size );
-	static DeclarationNode * newTuple( DeclarationNode * members );
-	static DeclarationNode * newTypeof( ExpressionNode * expr, bool basetypeof = false );
-	static DeclarationNode * newVtableType( DeclarationNode * expr );
-	static DeclarationNode * newAttribute( const std::string *, ExpressionNode * expr = nullptr ); // gcc attributes
-	static DeclarationNode * newDirectiveStmt( StatementNode * stmt ); // gcc external directive statement
-	static DeclarationNode * newAsmStmt( StatementNode * stmt ); // gcc external asm statement
-	static DeclarationNode * newStaticAssert( ExpressionNode * condition, ast::Expr * message );
-
-	DeclarationNode();
-	~DeclarationNode();
-	DeclarationNode * clone() const override;
-
-	DeclarationNode * addQualifiers( DeclarationNode * );
-	void checkQualifiers( const TypeData *, const TypeData * );
-	void checkSpecifiers( DeclarationNode * );
-	DeclarationNode * copySpecifiers( DeclarationNode * );
-	DeclarationNode * addType( DeclarationNode * );
-	DeclarationNode * addTypedef();
-	DeclarationNode * addEnumBase( DeclarationNode * );
-	DeclarationNode * addAssertions( DeclarationNode * );
-	DeclarationNode * addName( std::string * );
-	DeclarationNode * addAsmName( DeclarationNode * );
-	DeclarationNode * addBitfield( ExpressionNode * size );
-	DeclarationNode * addVarArgs();
-	DeclarationNode * addFunctionBody( StatementNode * body, ExpressionNode * with = nullptr );
-	DeclarationNode * addOldDeclList( DeclarationNode * list );
-	DeclarationNode * setBase( TypeData * newType );
-	DeclarationNode * copyAttribute( DeclarationNode * attr );
-	DeclarationNode * addPointer( DeclarationNode * qualifiers );
-	DeclarationNode * addArray( DeclarationNode * array );
-	DeclarationNode * addNewPointer( DeclarationNode * pointer );
-	DeclarationNode * addNewArray( DeclarationNode * array );
-	DeclarationNode * addParamList( DeclarationNode * list );
-	DeclarationNode * addIdList( DeclarationNode * list ); // old-style functions
-	DeclarationNode * addInitializer( InitializerNode * init );
-	DeclarationNode * addTypeInitializer( DeclarationNode * init );
-
-	DeclarationNode * cloneType( std::string * newName );
-	DeclarationNode * cloneBaseType( DeclarationNode * newdecl );
-
-	DeclarationNode * appendList( DeclarationNode * node ) {
-		return (DeclarationNode *)set_last( node );
-	}
-
-	virtual void print( __attribute__((unused)) std::ostream & os, __attribute__((unused)) int indent = 0 ) const override;
-	virtual void printList( __attribute__((unused)) std::ostream & os, __attribute__((unused)) int indent = 0 ) const override;
-
-	ast::Decl * build() const;
-	ast::Type * buildType() const;
-
-	ast::Linkage::Spec get_linkage() const { return linkage; }
-	DeclarationNode * extractAggregate() const;
-	bool has_enumeratorValue() const { return (bool)enumeratorValue; }
-	ExpressionNode * consume_enumeratorValue() const { return const_cast<DeclarationNode *>(this)->enumeratorValue.release(); }
-
-	bool get_extension() const { return extension; }
-	DeclarationNode * set_extension( bool exten ) { extension = exten; return this; }
-
-	bool get_inLine() const { return inLine; }
-	DeclarationNode * set_inLine( bool inL ) { inLine = inL; return this; }
-
-	DeclarationNode * get_last() { return (DeclarationNode *)ParseNode::get_last(); }
-
-	struct Variable_t {
-//		const std::string * name;
-		ast::TypeDecl::Kind tyClass;
-		DeclarationNode * assertions;
-		DeclarationNode * initializer;
-	};
-	Variable_t variable;
-
-	struct StaticAssert_t {
-		ExpressionNode * condition;
-		ast::Expr * message;
-	};
-	StaticAssert_t assert;
-
-	BuiltinType builtin = NoBuiltinType;
-
-	TypeData * type = nullptr;
-
-	bool inLine = false;
-	bool enumInLine = false;
-	ast::Function::Specs funcSpecs;
-	ast::Storage::Classes storageClasses;
-
-	ExpressionNode * bitfieldWidth = nullptr;
-	std::unique_ptr<ExpressionNode> enumeratorValue;
-	bool hasEllipsis = false;
-	ast::Linkage::Spec linkage;
-	ast::Expr * asmName = nullptr;
-	std::vector<ast::ptr<ast::Attribute>> attributes;
-	InitializerNode * initializer = nullptr;
-	bool extension = false;
-	std::string error;
-	StatementNode * asmStmt = nullptr;
-	StatementNode * directiveStmt = nullptr;
-
-	static UniqueName anonymous;
-}; // DeclarationNode
-
-ast::Type * buildType( TypeData * type );
-
-static inline ast::Type * maybeMoveBuildType( const DeclarationNode * orig ) {
-	ast::Type * ret = orig ? orig->buildType() : nullptr;
-	delete orig;
-	return ret;
-}
-
-//##############################################################################
-
-struct StatementNode final : public ParseNode {
-	StatementNode() :
-		stmt( nullptr ), clause( nullptr ) {}
-	StatementNode( ast::Stmt * stmt ) :
-		stmt( stmt ), clause( nullptr ) {}
-	StatementNode( ast::StmtClause * clause ) :
-		stmt( nullptr ), clause( clause ) {}
-	StatementNode( DeclarationNode * decl );
-	virtual ~StatementNode() {}
-
-	virtual StatementNode * clone() const final { assert( false ); return nullptr; }
-	ast::Stmt * build() const { return const_cast<StatementNode *>(this)->stmt.release(); }
-
-	virtual StatementNode * add_label(
-			const CodeLocation & location,
-			const std::string * name,
-			DeclarationNode * attr = nullptr ) {
-		stmt->labels.emplace_back( location,
-			*name,
-			attr ? std::move( attr->attributes )
-				: std::vector<ast::ptr<ast::Attribute>>{} );
-		delete attr;
-		delete name;
-		return this;
-	}
-
-	virtual StatementNode * append_last_case( StatementNode * );
-
-	virtual void print( std::ostream & os, __attribute__((unused)) int indent = 0 ) const override {
-		os << stmt.get() << std::endl;
-	}
-
-	std::unique_ptr<ast::Stmt> stmt;
-	std::unique_ptr<ast::StmtClause> clause;
-}; // StatementNode
-
-ast::Stmt * build_expr( CodeLocation const &, ExpressionNode * ctl );
-
-struct CondCtl {
-	CondCtl( DeclarationNode * decl, ExpressionNode * condition ) :
-		init( decl ? new StatementNode( decl ) : nullptr ), condition( condition ) {}
-
-	StatementNode * init;
-	ExpressionNode * condition;
-};
-
-struct ForCtrl {
-	ForCtrl( StatementNode * stmt, ExpressionNode * condition, ExpressionNode * change ) :
-		init( stmt ), condition( condition ), change( change ) {}
-
-	StatementNode * init;
-	ExpressionNode * condition;
-	ExpressionNode * change;
-};
-
-ast::Stmt * build_if( const CodeLocation &, CondCtl * ctl, StatementNode * then, StatementNode * else_ );
-ast::Stmt * build_switch( const CodeLocation &, bool isSwitch, ExpressionNode * ctl, StatementNode * stmt );
-ast::CaseClause * build_case( ExpressionNode * ctl );
-ast::CaseClause * build_default( const CodeLocation & );
-ast::Stmt * build_while( const CodeLocation &, CondCtl * ctl, StatementNode * stmt, StatementNode * else_ = nullptr );
-ast::Stmt * build_do_while( const CodeLocation &, ExpressionNode * ctl, StatementNode * stmt, StatementNode * else_ = nullptr );
-ast::Stmt * build_for( const CodeLocation &, ForCtrl * forctl, StatementNode * stmt, StatementNode * else_ = nullptr );
-ast::Stmt * build_branch( const CodeLocation &, ast::BranchStmt::Kind kind );
-ast::Stmt * build_branch( const CodeLocation &, std::string * identifier, ast::BranchStmt::Kind kind );
-ast::Stmt * build_computedgoto( ExpressionNode * ctl );
-ast::Stmt * build_return( const CodeLocation &, ExpressionNode * ctl );
-ast::Stmt * build_throw( const CodeLocation &, ExpressionNode * ctl );
-ast::Stmt * build_resume( const CodeLocation &, ExpressionNode * ctl );
-ast::Stmt * build_resume_at( ExpressionNode * ctl , ExpressionNode * target );
-ast::Stmt * build_try( const CodeLocation &, StatementNode * try_, StatementNode * catch_, StatementNode * finally_ );
-ast::CatchClause * build_catch( const CodeLocation &, ast::ExceptionKind kind, DeclarationNode * decl, ExpressionNode * cond, StatementNode * body );
-ast::FinallyClause * build_finally( const CodeLocation &, StatementNode * stmt );
-ast::Stmt * build_compound( const CodeLocation &, StatementNode * first );
-StatementNode * maybe_build_compound( const CodeLocation &, StatementNode * first );
-ast::Stmt * build_asm( const CodeLocation &, bool voltile, ast::Expr * instruction, ExpressionNode * output = nullptr, ExpressionNode * input = nullptr, ExpressionNode * clobber = nullptr, LabelNode * gotolabels = nullptr );
-ast::Stmt * build_directive( const CodeLocation &, std::string * directive );
-ast::SuspendStmt * build_suspend( const CodeLocation &, StatementNode *, ast::SuspendStmt::Type );
-ast::WaitForStmt * build_waitfor( const CodeLocation &, ast::WaitForStmt * existing, ExpressionNode * when, ExpressionNode * targetExpr, StatementNode * stmt );
-ast::WaitForStmt * build_waitfor_else( const CodeLocation &, ast::WaitForStmt * existing, ExpressionNode * when, StatementNode * stmt );
-ast::WaitForStmt * build_waitfor_timeout( const CodeLocation &, ast::WaitForStmt * existing, ExpressionNode * when, ExpressionNode * timeout, StatementNode * stmt );
-ast::WaitUntilStmt::ClauseNode * build_waituntil_clause( const CodeLocation &, ExpressionNode * when, ExpressionNode * targetExpr, StatementNode * stmt );
-ast::WaitUntilStmt::ClauseNode * build_waituntil_else( const CodeLocation &, ExpressionNode * when, StatementNode * stmt );
-ast::WaitUntilStmt::ClauseNode * build_waituntil_timeout( const CodeLocation &, ExpressionNode * when, ExpressionNode * timeout, StatementNode * stmt );
-ast::WaitUntilStmt * build_waituntil_stmt( const CodeLocation &, ast::WaitUntilStmt::ClauseNode * root );
-ast::Stmt * build_with( const CodeLocation &, ExpressionNode * exprs, StatementNode * stmt );
-ast::Stmt * build_mutex( const CodeLocation &, ExpressionNode * exprs, StatementNode * stmt );
-
-//##############################################################################
-
-template<typename AstType, typename NodeType,
-	template<typename, typename...> class Container, typename... Args>
-void buildList( const NodeType * firstNode,
-		Container<ast::ptr<AstType>, Args...> & output ) {
-	SemanticErrorException errors;
-	std::back_insert_iterator<Container<ast::ptr<AstType>, Args...>> out( output );
-	const NodeType * cur = firstNode;
-
-	while ( cur ) {
-		try {
-			if ( auto result = dynamic_cast<AstType *>( maybeBuild( cur ) ) ) {
-				*out++ = result;
-			} else {
-				assertf(false, __PRETTY_FUNCTION__ );
-				SemanticError( cur->location, "type specifier declaration in forall clause is currently unimplemented." );
-			} // if
-		} catch( SemanticErrorException & e ) {
-			errors.append( e );
-		} // try
-		const ParseNode * temp = cur->get_next();
-		// Should not return nullptr, then it is non-homogeneous:
-		cur = dynamic_cast<const NodeType *>( temp );
-		if ( !cur && temp ) {
-			SemanticError( temp->location, "internal error, non-homogeneous nodes founds in buildList processing." );
-		} // if
-	} // while
-	if ( ! errors.isEmpty() ) {
-		throw errors;
-	} // if
-}
-
-// in DeclarationNode.cc
-void buildList( const DeclarationNode * firstNode, std::vector<ast::ptr<ast::Decl>> & outputList );
-void buildList( const DeclarationNode * firstNode, std::vector<ast::ptr<ast::DeclWithType>> & outputList );
-void buildTypeList( const DeclarationNode * firstNode, std::vector<ast::ptr<ast::Type>> & outputList );
-
-template<typename AstType, typename NodeType,
-	template<typename, typename...> class Container, typename... Args>
-void buildMoveList( const NodeType * firstNode,
-		Container<ast::ptr<AstType>, Args...> & output ) {
-	buildList<AstType, NodeType, Container, Args...>( firstNode, output );
-	delete firstNode;
-}
-
-// in ParseNode.cc
 std::ostream & operator<<( std::ostream & out, const ParseNode * node );
 
Index: src/Parser/RunParser.cpp
===================================================================
--- src/Parser/RunParser.cpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Parser/RunParser.cpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -20,5 +20,5 @@
 #include "CodeTools/TrackLoc.h"             // for fillLocations
 #include "Common/CodeLocationTools.hpp"     // for forceFillCodeLocations
-#include "Parser/ParseNode.h"               // for DeclarationNode, buildList
+#include "Parser/DeclarationNode.h"         // for DeclarationNode, buildList
 #include "Parser/TypedefTable.h"            // for TypedefTable
 
Index: src/Parser/StatementNode.cc
===================================================================
--- src/Parser/StatementNode.cc	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Parser/StatementNode.cc	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -11,7 +11,9 @@
 // Created On       : Sat May 16 14:59:41 2015
 // Last Modified By : Andrew Beach
-// Last Modified On : Tue Apr  4 11:40:00 2023
-// Update Count     : 427
+// Last Modified On : Tue Apr 11 10:16:00 2023
+// Update Count     : 428
 //
+
+#include "StatementNode.h"
 
 #include <cassert>                 // for assert, strict_dynamic_cast, assertf
@@ -23,5 +25,6 @@
 #include "Common/SemanticError.h"  // for SemanticError
 #include "Common/utility.h"        // for maybeMoveBuild, maybeBuild
-#include "ParseNode.h"             // for StatementNode, ExpressionNode, bui...
+#include "DeclarationNode.h"       // for DeclarationNode
+#include "ExpressionNode.h"        // for ExpressionNode
 #include "parserutility.h"         // for notZeroExpr
 
@@ -29,4 +32,19 @@
 
 using namespace std;
+
+// Some helpers for cases that really want a single node but check for lists.
+static const ast::Stmt * buildMoveSingle( StatementNode * node ) {
+	std::vector<ast::ptr<ast::Stmt>> list;
+	buildMoveList( node, list );
+	assertf( list.size() == 1, "CFA Internal Error: Extra/Missing Nodes" );
+	return list.front().release();
+}
+
+static const ast::Stmt * buildMoveOptional( StatementNode * node ) {
+	std::vector<ast::ptr<ast::Stmt>> list;
+	buildMoveList( node, list );
+	assertf( list.size() <= 1, "CFA Internal Error: Extra Nodes" );
+	return list.empty() ? nullptr : list.front().release();
+}
 
 StatementNode::StatementNode( DeclarationNode * decl ) {
@@ -53,15 +71,27 @@
 } // StatementNode::StatementNode
 
-StatementNode * StatementNode::append_last_case( StatementNode * stmt ) {
-	StatementNode * prev = this;
+StatementNode * StatementNode::add_label(
+		const CodeLocation & location,
+		const std::string * name,
+		DeclarationNode * attr ) {
+	stmt->labels.emplace_back( location,
+		*name,
+		attr ? std::move( attr->attributes )
+			: std::vector<ast::ptr<ast::Attribute>>{} );
+	delete attr;
+	delete name;
+	return this;
+}
+
+ClauseNode * ClauseNode::append_last_case( StatementNode * stmt ) {
+	ClauseNode * prev = this;
 	// find end of list and maintain previous pointer
-	for ( StatementNode * curr = prev; curr != nullptr; curr = (StatementNode *)curr->get_next() ) {
-		StatementNode * node = strict_dynamic_cast< StatementNode * >(curr);
-		assert( nullptr == node->stmt.get() );
+	for ( ClauseNode * curr = prev; curr != nullptr; curr = (ClauseNode *)curr->get_next() ) {
+		ClauseNode * node = strict_dynamic_cast< ClauseNode * >(curr);
 		assert( dynamic_cast<ast::CaseClause *>( node->clause.get() ) );
 		prev = curr;
 	} // for
+	ClauseNode * node = dynamic_cast< ClauseNode * >(prev);
 	// convert from StatementNode list to Statement list
-	StatementNode * node = dynamic_cast< StatementNode * >(prev);
 	std::vector<ast::ptr<ast::Stmt>> stmts;
 	buildMoveList( stmt, stmts );
@@ -73,5 +103,5 @@
 	stmts.clear();
 	return this;
-} // StatementNode::append_last_case
+} // ClauseNode::append_last_case
 
 ast::Stmt * build_expr( CodeLocation const & location, ExpressionNode * ctl ) {
@@ -97,7 +127,5 @@
 		for ( ast::ptr<ast::Stmt> & stmt : inits ) {
 			// build the && of all of the declared variables compared against 0
-			//auto declStmt = strict_dynamic_cast<ast::DeclStmt *>( stmt );
 			auto declStmt = stmt.strict_as<ast::DeclStmt>();
-			//ast::DeclWithType * dwt = strict_dynamic_cast<ast::DeclWithType *>( declStmt->decl );
 			auto dwt = declStmt->decl.strict_as<ast::DeclWithType>();
 			ast::Expr * nze = notZeroExpr( new ast::VariableExpr( dwt->location, dwt ) );
@@ -113,16 +141,6 @@
 	ast::Expr * astcond = build_if_control( ctl, astinit ); // ctl deleted, cond/init set
 
-	std::vector<ast::ptr<ast::Stmt>> aststmt;
-	buildMoveList( then, aststmt );
-	assert( aststmt.size() == 1 );
-	ast::Stmt const * astthen = aststmt.front().release();
-
-	ast::Stmt const * astelse = nullptr;
-	if ( else_ ) {
-		std::vector<ast::ptr<ast::Stmt>> aststmt;
-		buildMoveList( else_, aststmt );
-		assert( aststmt.size() == 1 );
-		astelse = aststmt.front().release();
-	} // if
+	ast::Stmt const * astthen = buildMoveSingle( then );
+	ast::Stmt const * astelse = buildMoveOptional( else_ );
 
 	return new ast::IfStmt( location, astcond, astthen, astelse,
@@ -131,42 +149,7 @@
 } // build_if
 
-// Temporary work around. Split StmtClause off from StatementNode.
-template<typename clause_t>
-static void buildMoveClauseList( StatementNode * firstNode,
-		std::vector<ast::ptr<clause_t>> & output ) {
-	SemanticErrorException errors;
-	std::back_insert_iterator<std::vector<ast::ptr<clause_t>>>
-		out( output );
-	StatementNode * cur = firstNode;
-
-	while ( cur ) {
-		try {
-			auto clause = cur->clause.release();
-			if ( auto result = dynamic_cast<clause_t *>( clause ) ) {
-				*out++ = result;
-			} else {
-				assertf(false, __PRETTY_FUNCTION__ );
-				SemanticError( cur->location, "type specifier declaration in forall clause is currently unimplemented." );
-			} // if
-		} catch( SemanticErrorException & e ) {
-			errors.append( e );
-		} // try
-		ParseNode * temp = cur->get_next();
-		// Should not return nullptr, then it is non-homogeneous:
-		cur = dynamic_cast<StatementNode *>( temp );
-		if ( !cur && temp ) {
-			SemanticError( temp->location, "internal error, non-homogeneous nodes founds in buildList processing." );
-		} // if
-	} // while
-	if ( ! errors.isEmpty() ) {
-		throw errors;
-	} // if
-	// Usually in the wrapper.
-	delete firstNode;
-}
-
-ast::Stmt * build_switch( const CodeLocation & location, bool isSwitch, ExpressionNode * ctl, StatementNode * stmt ) {
+ast::Stmt * build_switch( const CodeLocation & location, bool isSwitch, ExpressionNode * ctl, ClauseNode * stmt ) {
 	std::vector<ast::ptr<ast::CaseClause>> aststmt;
-	buildMoveClauseList( stmt, aststmt );
+	buildMoveList( stmt, aststmt );
 	// If it is not a switch it is a choose statement.
 	if ( ! isSwitch ) {
@@ -190,8 +173,8 @@
 } // build_switch
 
-ast::CaseClause * build_case( ExpressionNode * ctl ) {
+ast::CaseClause * build_case( const CodeLocation & location, ExpressionNode * ctl ) {
 	// stmt starts empty and then added to
 	auto expr = maybeMoveBuild( ctl );
-	return new ast::CaseClause( expr->location, expr, {} );
+	return new ast::CaseClause( location, expr, {} );
 } // build_case
 
@@ -205,37 +188,21 @@
 	ast::Expr * astcond = build_if_control( ctl, astinit ); // ctl deleted, cond/init set
 
-	std::vector<ast::ptr<ast::Stmt>> aststmt;						// loop body, compound created if empty
-	buildMoveList( stmt, aststmt );
-	assert( aststmt.size() == 1 );
-
-	std::vector<ast::ptr<ast::Stmt>> astelse;						// else clause, maybe empty
-	buildMoveList( else_, astelse );
-	assert( astelse.size() <= 1 );
-
 	return new ast::WhileDoStmt( location,
 		astcond,
-		aststmt.front(),
-		astelse.empty() ? nullptr : astelse.front().release(),
+		buildMoveSingle( stmt ),
+		buildMoveOptional( else_ ),
 		std::move( astinit ),
-		false
+		ast::While
 	);
 } // build_while
 
 ast::Stmt * build_do_while( const CodeLocation & location, ExpressionNode * ctl, StatementNode * stmt, StatementNode * else_ ) {
-	std::vector<ast::ptr<ast::Stmt>> aststmt;						// loop body, compound created if empty
-	buildMoveList( stmt, aststmt );
-	assert( aststmt.size() == 1 );						// compound created if empty
-
-	std::vector<ast::ptr<ast::Stmt>> astelse;						// else clause, maybe empty
-	buildMoveList( else_, astelse );
-	assert( astelse.size() <= 1 );
-
 	// do-while cannot have declarations in the contitional, so init is always empty
 	return new ast::WhileDoStmt( location,
 		notZeroExpr( maybeMoveBuild( ctl ) ),
-		aststmt.front(),
-		astelse.empty() ? nullptr : astelse.front().release(),
+		buildMoveSingle( stmt ),
+		buildMoveOptional( else_ ),
 		{},
-		true
+		ast::DoWhile
 	);
 } // build_do_while
@@ -251,12 +218,4 @@
 	astincr = maybeMoveBuild( forctl->change );
 	delete forctl;
-
-	std::vector<ast::ptr<ast::Stmt>> aststmt;						// loop body, compound created if empty
-	buildMoveList( stmt, aststmt );
-	assert( aststmt.size() == 1 );
-
-	std::vector<ast::ptr<ast::Stmt>> astelse;						// else clause, maybe empty
-	buildMoveList( else_, astelse );
-	assert( astelse.size() <= 1 );
 
 	return new ast::ForStmt( location,
@@ -264,6 +223,6 @@
 		astcond,
 		astincr,
-		aststmt.front(),
-		astelse.empty() ? nullptr : astelse.front().release()
+		buildMoveSingle( stmt ),
+		buildMoveOptional( else_ )
 	);
 } // build_for
@@ -326,7 +285,7 @@
 } // build_resume_at
 
-ast::Stmt * build_try( const CodeLocation & location, StatementNode * try_, StatementNode * catch_, StatementNode * finally_ ) {
+ast::Stmt * build_try( const CodeLocation & location, StatementNode * try_, ClauseNode * catch_, ClauseNode * finally_ ) {
 	std::vector<ast::ptr<ast::CatchClause>> aststmt;
-	buildMoveClauseList( catch_, aststmt );
+	buildMoveList( catch_, aststmt );
 	ast::CompoundStmt * tryBlock = strict_dynamic_cast<ast::CompoundStmt *>( maybeMoveBuild( try_ ) );
 	ast::FinallyClause * finallyBlock = nullptr;
@@ -342,35 +301,27 @@
 
 ast::CatchClause * build_catch( const CodeLocation & location, ast::ExceptionKind kind, DeclarationNode * decl, ExpressionNode * cond, StatementNode * body ) {
-	std::vector<ast::ptr<ast::Stmt>> aststmt;
-	buildMoveList( body, aststmt );
-	assert( aststmt.size() == 1 );
 	return new ast::CatchClause( location,
 		kind,
 		maybeMoveBuild( decl ),
 		maybeMoveBuild( cond ),
-		aststmt.front().release()
+		buildMoveSingle( body )
 	);
 } // build_catch
 
 ast::FinallyClause * build_finally( const CodeLocation & location, StatementNode * stmt ) {
-	std::vector<ast::ptr<ast::Stmt>> aststmt;
-	buildMoveList( stmt, aststmt );
-	assert( aststmt.size() == 1 );
 	return new ast::FinallyClause( location,
-		aststmt.front().strict_as<ast::CompoundStmt>()
+		strict_dynamic_cast<const ast::CompoundStmt *>(
+			buildMoveSingle( stmt )
+		)
 	);
 } // build_finally
 
-ast::SuspendStmt * build_suspend( const CodeLocation & location, StatementNode * then, ast::SuspendStmt::Type type ) {
-	std::vector<ast::ptr<ast::Stmt>> stmts;
-	buildMoveList( then, stmts );
-	ast::CompoundStmt const * then2 = nullptr;
-	if(!stmts.empty()) {
-		assert( stmts.size() == 1 );
-		then2 = stmts.front().strict_as<ast::CompoundStmt>();
-	}
-	auto node = new ast::SuspendStmt( location, then2, ast::SuspendStmt::None );
-	node->type = type;
-	return node;
+ast::SuspendStmt * build_suspend( const CodeLocation & location, StatementNode * then, ast::SuspendStmt::Kind kind ) {
+	return new ast::SuspendStmt( location,
+		strict_dynamic_cast<const ast::CompoundStmt *, nullptr>(
+			buildMoveOptional( then )
+		),
+		kind
+	);
 } // build_suspend
 
@@ -525,5 +476,5 @@
 
 // Question
-ast::Stmt * build_asm( const CodeLocation & location, bool voltile, ast::Expr * instruction, ExpressionNode * output, ExpressionNode * input, ExpressionNode * clobber, LabelNode * gotolabels ) {
+ast::Stmt * build_asm( const CodeLocation & location, bool is_volatile, ExpressionNode * instruction, ExpressionNode * output, ExpressionNode * input, ExpressionNode * clobber, LabelNode * gotolabels ) {
 	std::vector<ast::ptr<ast::Expr>> out, in;
 	std::vector<ast::ptr<ast::ConstantExpr>> clob;
@@ -533,6 +484,6 @@
 	buildMoveList( clobber, clob );
 	return new ast::AsmStmt( location,
-		voltile,
-		instruction,
+		is_volatile,
+		maybeMoveBuild( instruction ),
 		std::move( out ),
 		std::move( in ),
Index: src/Parser/StatementNode.h
===================================================================
--- src/Parser/StatementNode.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
+++ src/Parser/StatementNode.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -0,0 +1,107 @@
+//
+// 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.
+//
+// StatementNode.h --
+//
+// Author           : Andrew Beach
+// Created On       : Wed Apr  5 11:42:00 2023
+// Last Modified By : Andrew Beach
+// Last Modified On : Tue Apr 11  9:43:00 2023
+// Update Count     : 1
+//
+
+#pragma once
+
+#include "ParseNode.h"
+
+struct StatementNode final : public ParseNode {
+	StatementNode() : stmt( nullptr ) {}
+	StatementNode( ast::Stmt * stmt ) : stmt( stmt ) {}
+	StatementNode( DeclarationNode * decl );
+	virtual ~StatementNode() {}
+
+	virtual StatementNode * clone() const final { assert( false ); return nullptr; }
+	ast::Stmt * build() { return stmt.release(); }
+
+	StatementNode * add_label(
+			const CodeLocation & location,
+			const std::string * name,
+			DeclarationNode * attr = nullptr );
+
+	virtual void print( std::ostream & os, __attribute__((unused)) int indent = 0 ) const override {
+		os << stmt.get() << std::endl;
+	}
+
+	std::unique_ptr<ast::Stmt> stmt;
+}; // StatementNode
+
+struct ClauseNode final : public ParseNode {
+	ClauseNode( ast::StmtClause * clause ) : clause( clause ) {}
+	virtual ~ClauseNode() {}
+
+	ClauseNode * set_last( ParseNode * newlast ) {
+		ParseNode::set_last( newlast );
+        return this;
+    }
+
+	virtual ClauseNode * clone() const final { assert( false ); return nullptr; }
+	ast::StmtClause * build() { return clause.release(); }
+
+	virtual ClauseNode * append_last_case( StatementNode * );
+
+	std::unique_ptr<ast::StmtClause> clause;
+};
+
+ast::Stmt * build_expr( CodeLocation const &, ExpressionNode * ctl );
+
+struct CondCtl {
+	CondCtl( DeclarationNode * decl, ExpressionNode * condition ) :
+		init( decl ? new StatementNode( decl ) : nullptr ), condition( condition ) {}
+
+	StatementNode * init;
+	ExpressionNode * condition;
+};
+
+struct ForCtrl {
+	ForCtrl( StatementNode * stmt, ExpressionNode * condition, ExpressionNode * change ) :
+		init( stmt ), condition( condition ), change( change ) {}
+
+	StatementNode * init;
+	ExpressionNode * condition;
+	ExpressionNode * change;
+};
+
+ast::Stmt * build_if( const CodeLocation &, CondCtl * ctl, StatementNode * then, StatementNode * else_ );
+ast::Stmt * build_switch( const CodeLocation &, bool isSwitch, ExpressionNode * ctl, ClauseNode * stmt );
+ast::CaseClause * build_case( const CodeLocation &, ExpressionNode * ctl );
+ast::CaseClause * build_default( const CodeLocation & );
+ast::Stmt * build_while( const CodeLocation &, CondCtl * ctl, StatementNode * stmt, StatementNode * else_ = nullptr );
+ast::Stmt * build_do_while( const CodeLocation &, ExpressionNode * ctl, StatementNode * stmt, StatementNode * else_ = nullptr );
+ast::Stmt * build_for( const CodeLocation &, ForCtrl * forctl, StatementNode * stmt, StatementNode * else_ = nullptr );
+ast::Stmt * build_branch( const CodeLocation &, ast::BranchStmt::Kind kind );
+ast::Stmt * build_branch( const CodeLocation &, std::string * identifier, ast::BranchStmt::Kind kind );
+ast::Stmt * build_computedgoto( ExpressionNode * ctl );
+ast::Stmt * build_return( const CodeLocation &, ExpressionNode * ctl );
+ast::Stmt * build_throw( const CodeLocation &, ExpressionNode * ctl );
+ast::Stmt * build_resume( const CodeLocation &, ExpressionNode * ctl );
+ast::Stmt * build_resume_at( ExpressionNode * ctl , ExpressionNode * target );
+ast::Stmt * build_try( const CodeLocation &, StatementNode * try_, ClauseNode * catch_, ClauseNode * finally_ );
+ast::CatchClause * build_catch( const CodeLocation &, ast::ExceptionKind kind, DeclarationNode * decl, ExpressionNode * cond, StatementNode * body );
+ast::FinallyClause * build_finally( const CodeLocation &, StatementNode * stmt );
+ast::Stmt * build_compound( const CodeLocation &, StatementNode * first );
+StatementNode * maybe_build_compound( const CodeLocation &, StatementNode * first );
+ast::Stmt * build_asm( const CodeLocation &, bool is_volatile, ExpressionNode * instruction, ExpressionNode * output = nullptr, ExpressionNode * input = nullptr, ExpressionNode * clobber = nullptr, LabelNode * gotolabels = nullptr );
+ast::Stmt * build_directive( const CodeLocation &, std::string * directive );
+ast::SuspendStmt * build_suspend( const CodeLocation &, StatementNode *, ast::SuspendStmt::Kind );
+ast::WaitForStmt * build_waitfor( const CodeLocation &, ast::WaitForStmt * existing, ExpressionNode * when, ExpressionNode * targetExpr, StatementNode * stmt );
+ast::WaitForStmt * build_waitfor_else( const CodeLocation &, ast::WaitForStmt * existing, ExpressionNode * when, StatementNode * stmt );
+ast::WaitForStmt * build_waitfor_timeout( const CodeLocation &, ast::WaitForStmt * existing, ExpressionNode * when, ExpressionNode * timeout, StatementNode * stmt );
+ast::WaitUntilStmt::ClauseNode * build_waituntil_clause( const CodeLocation &, ExpressionNode * when, ExpressionNode * targetExpr, StatementNode * stmt );
+ast::WaitUntilStmt::ClauseNode * build_waituntil_else( const CodeLocation &, ExpressionNode * when, StatementNode * stmt );
+ast::WaitUntilStmt::ClauseNode * build_waituntil_timeout( const CodeLocation &, ExpressionNode * when, ExpressionNode * timeout, StatementNode * stmt );
+ast::WaitUntilStmt * build_waituntil_stmt( const CodeLocation &, ast::WaitUntilStmt::ClauseNode * root );
+ast::Stmt * build_with( const CodeLocation &, ExpressionNode * exprs, StatementNode * stmt );
+ast::Stmt * build_mutex( const CodeLocation &, ExpressionNode * exprs, StatementNode * stmt );
Index: src/Parser/TypeData.cc
===================================================================
--- src/Parser/TypeData.cc	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Parser/TypeData.cc	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -24,6 +24,6 @@
 #include "Common/SemanticError.h"  // for SemanticError
 #include "Common/utility.h"        // for splice, spliceBegin
-#include "Parser/parserutility.h"  // for maybeCopy, maybeBuild, maybeMoveB...
-#include "Parser/ParseNode.h"      // for DeclarationNode, ExpressionNode
+#include "Parser/ExpressionNode.h" // for ExpressionNode
+#include "Parser/StatementNode.h"  // for StatementNode
 
 class Attribute;
@@ -1397,5 +1397,5 @@
 		std::move( attributes ),
 		funcSpec,
-		isVarArgs
+		(isVarArgs) ? ast::VariableArgs : ast::FixedArgs
 	);
 	buildList( td->function.withExprs, decl->withExprs );
Index: src/Parser/TypeData.h
===================================================================
--- src/Parser/TypeData.h	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Parser/TypeData.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -16,10 +16,10 @@
 #pragma once
 
-#include <iosfwd>										// for ostream
-#include <list>											// for list
-#include <string>										// for string
+#include <iosfwd>                                   // for ostream
+#include <list>                                     // for list
+#include <string>                                   // for string
 
-#include "AST/Type.hpp"									// for Type
-#include "ParseNode.h"									// for DeclarationNode, DeclarationNode::Ag...
+#include "AST/Type.hpp"                             // for Type
+#include "DeclarationNode.h"                        // for DeclarationNode
 
 struct TypeData {
Index: src/Parser/TypedefTable.cc
===================================================================
--- src/Parser/TypedefTable.cc	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Parser/TypedefTable.cc	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -16,29 +16,34 @@
 
 #include "TypedefTable.h"
-#include <cassert>										// for assert
-#include <iostream>
+
+#include <cassert>                                // for assert
+#include <string>                                 // for string
+#include <iostream>                               // for iostream
+
+#include "ExpressionNode.h"                       // for LabelNode
+#include "ParserTypes.h"                          // for Token
+#include "StatementNode.h"                        // for CondCtl, ForCtrl
+// This (generated) header must come late as it is missing includes.
+#include "parser.hh"              // for IDENTIFIER, TYPEDEFname, TYPEGENname
+
 using namespace std;
 
 #if 0
 #define debugPrint( code ) code
+
+static const char *kindName( int kind ) {
+	switch ( kind ) {
+	case IDENTIFIER: return "identifier";
+	case TYPEDIMname: return "typedim";
+	case TYPEDEFname: return "typedef";
+	case TYPEGENname: return "typegen";
+	default:
+		cerr << "Error: cfa-cpp internal error, invalid kind of identifier" << endl;
+		abort();
+	} // switch
+} // kindName
 #else
 #define debugPrint( code )
 #endif
-
-using namespace std;									// string, iostream
-
-debugPrint(
-	static const char *kindName( int kind ) {
-		switch ( kind ) {
-		case IDENTIFIER: return "identifier";
-		case TYPEDIMname: return "typedim";
-		case TYPEDEFname: return "typedef";
-		case TYPEGENname: return "typegen";
-		default:
-			cerr << "Error: cfa-cpp internal error, invalid kind of identifier" << endl;
-			abort();
-		} // switch
-	} // kindName
-);
 
 TypedefTable::~TypedefTable() {
@@ -78,4 +83,8 @@
 		typedefTable.addToEnclosingScope( name, kind, "MTD" );
 	} // if
+} // TypedefTable::makeTypedef
+
+void TypedefTable::makeTypedef( const string & name ) {
+	return makeTypedef( name, TYPEDEFname );
 } // TypedefTable::makeTypedef
 
Index: src/Parser/TypedefTable.h
===================================================================
--- src/Parser/TypedefTable.h	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Parser/TypedefTable.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -19,11 +19,9 @@
 
 #include "Common/ScopedMap.h"							// for ScopedMap
-#include "ParserTypes.h"
-#include "parser.hh"									// for IDENTIFIER, TYPEDEFname, TYPEGENname
 
 class TypedefTable {
 	struct Note { size_t level; bool forall; };
 	typedef ScopedMap< std::string, int, Note > KindTable;
-	KindTable kindTable;	
+	KindTable kindTable;
 	unsigned int level = 0;
   public:
@@ -33,5 +31,6 @@
 	bool existsCurr( const std::string & identifier ) const;
 	int isKind( const std::string & identifier ) const;
-	void makeTypedef( const std::string & name, int kind = TYPEDEFname );
+	void makeTypedef( const std::string & name, int kind );
+	void makeTypedef( const std::string & name );
 	void addToScope( const std::string & identifier, int kind, const char * );
 	void addToEnclosingScope( const std::string & identifier, int kind, const char * );
Index: src/Parser/lex.ll
===================================================================
--- src/Parser/lex.ll	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Parser/lex.ll	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -44,6 +44,13 @@
 
 #include "config.h"										// configure info
+#include "DeclarationNode.h"                            // for DeclarationNode
+#include "ExpressionNode.h"                             // for LabelNode
+#include "InitializerNode.h"                            // for InitializerNode
 #include "ParseNode.h"
+#include "ParserTypes.h"                                // for Token
+#include "StatementNode.h"                              // for CondCtl, ForCtrl
 #include "TypedefTable.h"
+// This (generated) header must come late as it is missing includes.
+#include "parser.hh"                                    // generated info
 
 string * build_postfix_name( string * name );
Index: src/Parser/module.mk
===================================================================
--- src/Parser/module.mk	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Parser/module.mk	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -21,6 +21,9 @@
 SRC += \
        Parser/DeclarationNode.cc \
+       Parser/DeclarationNode.h \
        Parser/ExpressionNode.cc \
+       Parser/ExpressionNode.h \
        Parser/InitializerNode.cc \
+       Parser/InitializerNode.h \
        Parser/lex.ll \
        Parser/ParseNode.cc \
@@ -33,4 +36,5 @@
        Parser/RunParser.hpp \
        Parser/StatementNode.cc \
+       Parser/StatementNode.h \
        Parser/TypeData.cc \
        Parser/TypeData.h \
Index: src/Parser/parser.yy
===================================================================
--- src/Parser/parser.yy	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Parser/parser.yy	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -9,7 +9,7 @@
 // Author           : Peter A. Buhr
 // Created On       : Sat Sep  1 20:22:55 2001
-// Last Modified By : Andrew Beach
-// Last Modified On : Tue Apr  4 14:02:00 2023
-// Update Count     : 6329
+// Last Modified By : Peter A. Buhr
+// Last Modified On : Wed Apr 26 16:45:37 2023
+// Update Count     : 6330
 //
 
@@ -48,9 +48,12 @@
 using namespace std;
 
-#include "SynTree/Declaration.h"
-#include "ParseNode.h"
+#include "SynTree/Type.h"                               // for Type
+#include "DeclarationNode.h"                            // for DeclarationNode, ...
+#include "ExpressionNode.h"                             // for ExpressionNode, ...
+#include "InitializerNode.h"                            // for InitializerNode, ...
+#include "ParserTypes.h"
+#include "StatementNode.h"                              // for build_...
 #include "TypedefTable.h"
 #include "TypeData.h"
-#include "SynTree/LinkageSpec.h"
 #include "Common/SemanticError.h"						// error_str
 #include "Common/utility.h"								// for maybeMoveBuild, maybeBuild, CodeLo...
@@ -297,23 +300,21 @@
 %union {
 	Token tok;
-	ParseNode * pn;
-	ExpressionNode * en;
+	ExpressionNode * expr;
 	DeclarationNode * decl;
 	ast::AggregateDecl::Aggregate aggKey;
 	ast::TypeDecl::Kind tclass;
-	StatementNode * sn;
+	StatementNode * stmt;
+	ClauseNode * clause;
 	ast::WaitForStmt * wfs;
-    ast::WaitUntilStmt::ClauseNode * wuscn;
-	ast::Expr * constant;
+    ast::WaitUntilStmt::ClauseNode * wucn;
 	CondCtl * ifctl;
-	ForCtrl * fctl;
-	OperKinds compop;
-	LabelNode * label;
-	InitializerNode * in;
-	OperKinds op;
+	ForCtrl * forctl;
+	LabelNode * labels;
+	InitializerNode * init;
+	OperKinds oper;
 	std::string * str;
-	bool flag;
-	EnumHiding hide;
-	ast::ExceptionKind catch_kind;
+	bool is_volatile;
+	EnumHiding enum_hiding;
+	ast::ExceptionKind except_kind;
 	ast::GenericExpr * genexpr;
 }
@@ -381,51 +382,52 @@
 %type<tok> identifier					identifier_at				identifier_or_type_name		attr_name
 %type<tok> quasi_keyword
-%type<constant> string_literal
+%type<expr> string_literal
 %type<str> string_literal_list
 
-%type<hide> hide_opt					visible_hide_opt
+%type<enum_hiding> hide_opt					visible_hide_opt
 
 // expressions
-%type<en> constant
-%type<en> tuple							tuple_expression_list
-%type<op> ptrref_operator				unary_operator				assignment_operator			simple_assignment_operator	compound_assignment_operator
-%type<en> primary_expression			postfix_expression			unary_expression
-%type<en> cast_expression_list			cast_expression				exponential_expression		multiplicative_expression	additive_expression
-%type<en> shift_expression				relational_expression		equality_expression
-%type<en> AND_expression				exclusive_OR_expression		inclusive_OR_expression
-%type<en> logical_AND_expression		logical_OR_expression
-%type<en> conditional_expression		constant_expression			assignment_expression		assignment_expression_opt
-%type<en> comma_expression				comma_expression_opt
-%type<en> argument_expression_list_opt	argument_expression_list	argument_expression			default_initializer_opt
+%type<expr> constant
+%type<expr> tuple							tuple_expression_list
+%type<oper> ptrref_operator				unary_operator				assignment_operator			simple_assignment_operator	compound_assignment_operator
+%type<expr> primary_expression			postfix_expression			unary_expression
+%type<expr> cast_expression_list			cast_expression				exponential_expression		multiplicative_expression	additive_expression
+%type<expr> shift_expression				relational_expression		equality_expression
+%type<expr> AND_expression				exclusive_OR_expression		inclusive_OR_expression
+%type<expr> logical_AND_expression		logical_OR_expression
+%type<expr> conditional_expression		constant_expression			assignment_expression		assignment_expression_opt
+%type<expr> comma_expression				comma_expression_opt
+%type<expr> argument_expression_list_opt	argument_expression_list	argument_expression			default_initializer_opt
 %type<ifctl> conditional_declaration
-%type<fctl> for_control_expression		for_control_expression_list
-%type<compop> upupeq updown updowneq downupdowneq
-%type<en> subrange
+%type<forctl> for_control_expression		for_control_expression_list
+%type<oper> upupeq updown updowneq downupdowneq
+%type<expr> subrange
 %type<decl> asm_name_opt
-%type<en> asm_operands_opt				asm_operands_list			asm_operand
-%type<label> label_list
-%type<en> asm_clobbers_list_opt
-%type<flag> asm_volatile_opt
-%type<en> handler_predicate_opt
+%type<expr> asm_operands_opt				asm_operands_list			asm_operand
+%type<labels> label_list
+%type<expr> asm_clobbers_list_opt
+%type<is_volatile> asm_volatile_opt
+%type<expr> handler_predicate_opt
 %type<genexpr> generic_association		generic_assoc_list
 
 // statements
-%type<sn> statement						labeled_statement			compound_statement
-%type<sn> statement_decl				statement_decl_list			statement_list_nodecl
-%type<sn> selection_statement			if_statement
-%type<sn> switch_clause_list_opt		switch_clause_list
-%type<en> case_value
-%type<sn> case_clause					case_value_list				case_label					case_label_list
-%type<sn> iteration_statement			jump_statement
-%type<sn> expression_statement			asm_statement
-%type<sn> with_statement
-%type<en> with_clause_opt
-%type<sn> exception_statement			handler_clause				finally_clause
-%type<catch_kind> handler_key
-%type<sn> mutex_statement
-%type<en> when_clause					when_clause_opt				waitfor		waituntil		timeout
-%type<sn> waitfor_statement				waituntil_statement
+%type<stmt> statement						labeled_statement			compound_statement
+%type<stmt> statement_decl				statement_decl_list			statement_list_nodecl
+%type<stmt> selection_statement			if_statement
+%type<clause> switch_clause_list_opt		switch_clause_list
+%type<expr> case_value
+%type<clause> case_clause				case_value_list				case_label					case_label_list
+%type<stmt> iteration_statement			jump_statement
+%type<stmt> expression_statement			asm_statement
+%type<stmt> with_statement
+%type<expr> with_clause_opt
+%type<stmt> exception_statement
+%type<clause> handler_clause			finally_clause
+%type<except_kind> handler_key
+%type<stmt> mutex_statement
+%type<expr> when_clause					when_clause_opt				waitfor		waituntil		timeout
+%type<stmt> waitfor_statement				waituntil_statement
 %type<wfs> wor_waitfor_clause
-%type<wuscn> waituntil_clause			wand_waituntil_clause       wor_waituntil_clause
+%type<wucn> waituntil_clause			wand_waituntil_clause       wor_waituntil_clause
 
 // declarations
@@ -439,5 +441,5 @@
 %type<decl> assertion assertion_list assertion_list_opt
 
-%type<en> bit_subrange_size_opt bit_subrange_size
+%type<expr> bit_subrange_size_opt bit_subrange_size
 
 %type<decl> basic_declaration_specifier basic_type_name basic_type_specifier direct_type indirect_type
@@ -452,5 +454,5 @@
 
 %type<decl> enumerator_list enum_type enum_type_nobody
-%type<in> enumerator_value_opt
+%type<init> enumerator_value_opt
 
 %type<decl> external_definition external_definition_list external_definition_list_opt
@@ -459,5 +461,5 @@
 
 %type<decl> field_declaration_list_opt field_declaration field_declaring_list_opt field_declarator field_abstract_list_opt field_abstract
-%type<en> field field_name_list field_name fraction_constants_opt
+%type<expr> field field_name_list field_name fraction_constants_opt
 
 %type<decl> external_function_definition function_definition function_array function_declarator function_no_ptr function_ptr
@@ -508,5 +510,5 @@
 %type<decl> type_parameter type_parameter_list type_initializer_opt
 
-%type<en> type_parameters_opt type_list array_type_list
+%type<expr> type_parameters_opt type_list array_type_list
 
 %type<decl> type_qualifier type_qualifier_name forall type_qualifier_list_opt type_qualifier_list
@@ -519,8 +521,8 @@
 
 // initializers
-%type<in>  initializer initializer_list_opt initializer_opt
+%type<init>  initializer initializer_list_opt initializer_opt
 
 // designators
-%type<en>  designator designator_list designation
+%type<expr>  designator designator_list designation
 
 
@@ -644,5 +646,5 @@
 
 string_literal:
-	string_literal_list							{ $$ = build_constantStr( yylloc, *$1 ); }
+	string_literal_list							{ $$ = new ExpressionNode( build_constantStr( yylloc, *$1 ) ); }
 	;
 
@@ -739,5 +741,5 @@
 		{ $$ = new ExpressionNode( build_binary_val( yylloc, OperKinds::Index, $1, $3 ) ); }
 	| string_literal '[' assignment_expression ']'		// "abc"[3], 3["abc"]
-		{ $$ = new ExpressionNode( build_binary_val( yylloc, OperKinds::Index, new ExpressionNode( $1 ), $3 ) ); }
+		{ $$ = new ExpressionNode( build_binary_val( yylloc, OperKinds::Index, $1, $3 ) ); }
 	| postfix_expression '{' argument_expression_list_opt '}' // CFA, constructor call
 		{
@@ -757,5 +759,5 @@
 		{ $$ = new ExpressionNode( build_func( yylloc, new ExpressionNode( build_varref( yylloc, build_postfix_name( $3 ) ) ), $1 ) ); }
 	| string_literal '`' identifier						// CFA, postfix call
-		{ $$ = new ExpressionNode( build_func( yylloc, new ExpressionNode( build_varref( yylloc, build_postfix_name( $3 ) ) ), new ExpressionNode( $1 ) ) ); }
+		{ $$ = new ExpressionNode( build_func( yylloc, new ExpressionNode( build_varref( yylloc, build_postfix_name( $3 ) ) ), $1 ) ); }
 	| postfix_expression '.' identifier
 		{ $$ = new ExpressionNode( build_fieldSel( yylloc, $1, build_varref( yylloc, $3 ) ) ); }
@@ -857,5 +859,5 @@
 	| constant
 	| string_literal
-		{ $$ = new ExpressionNode( $1 ); }
+		{ $$ = $1; }
 	| EXTENSION cast_expression							// GCC
 		{ $$ = $2->set_extension( true ); }
@@ -1260,7 +1262,7 @@
 
 case_value_list:										// CFA
-	case_value									{ $$ = new StatementNode( build_case( $1 ) ); }
+	case_value									{ $$ = new ClauseNode( build_case( yylloc, $1 ) ); }
 		// convert case list, e.g., "case 1, 3, 5:" into "case 1: case 3: case 5"
-	| case_value_list ',' case_value			{ $$ = (StatementNode *)($1->set_last( new StatementNode( build_case( $3 ) ) ) ); }
+	| case_value_list ',' case_value			{ $$ = $1->set_last( new ClauseNode( build_case( yylloc, $3 ) ) ); }
 	;
 
@@ -1271,5 +1273,5 @@
 	| CASE case_value_list error						// syntax error
 		{ SemanticError( yylloc, "Missing colon after case list." ); $$ = nullptr; }
-	| DEFAULT ':'								{ $$ = new StatementNode( build_default( yylloc ) ); }
+	| DEFAULT ':'								{ $$ = new ClauseNode( build_default( yylloc ) ); }
 		// A semantic check is required to ensure only one default clause per switch/choose statement.
 	| DEFAULT error										//  syntax error
@@ -1279,5 +1281,5 @@
 case_label_list:										// CFA
 	case_label
-	| case_label_list case_label				{ $$ = (StatementNode *)( $1->set_last( $2 )); }
+	| case_label_list case_label				{ $$ = $1->set_last( $2 ); }
 	;
 
@@ -1296,5 +1298,5 @@
 		{ $$ = $1->append_last_case( new StatementNode( build_compound( yylloc, $2 ) ) ); }
 	| switch_clause_list case_label_list statement_list_nodecl
-		{ $$ = (StatementNode *)( $1->set_last( $2->append_last_case( new StatementNode( build_compound( yylloc, $3 ) ) ) ) ); }
+		{ $$ = $1->set_last( $2->append_last_case( new StatementNode( build_compound( yylloc, $3 ) ) ) ); }
 	;
 
@@ -1679,5 +1681,5 @@
 
 waituntil:
-	WAITUNTIL '(' cast_expression ')'
+	WAITUNTIL '(' comma_expression ')'
 		{ $$ = $3; }
 	;
@@ -1736,7 +1738,7 @@
 handler_clause:
 	handler_key '(' push exception_declaration pop handler_predicate_opt ')' compound_statement
-		{ $$ = new StatementNode( build_catch( yylloc, $1, $4, $6, $8 ) ); }
+		{ $$ = new ClauseNode( build_catch( yylloc, $1, $4, $6, $8 ) ); }
 	| handler_clause handler_key '(' push exception_declaration pop handler_predicate_opt ')' compound_statement
-		{ $$ = (StatementNode *)$1->set_last( new StatementNode( build_catch( yylloc, $2, $5, $7, $9 ) ) ); }
+		{ $$ = $1->set_last( new ClauseNode( build_catch( yylloc, $2, $5, $7, $9 ) ) ); }
 	;
 
@@ -1755,5 +1757,5 @@
 
 finally_clause:
-	FINALLY compound_statement					{ $$ = new StatementNode( build_finally( yylloc, $2 ) ); }
+	FINALLY compound_statement					{ $$ = new ClauseNode( build_finally( yylloc, $2 ) ); }
 	;
 
@@ -1813,8 +1815,8 @@
 asm_operand:											// GCC
 	string_literal '(' constant_expression ')'
-		{ $$ = new ExpressionNode( new ast::AsmExpr( yylloc, "", $1, maybeMoveBuild( $3 ) ) ); }
+		{ $$ = new ExpressionNode( new ast::AsmExpr( yylloc, "", maybeMoveBuild( $1 ), maybeMoveBuild( $3 ) ) ); }
 	| '[' IDENTIFIER ']' string_literal '(' constant_expression ')'
 		{
-			$$ = new ExpressionNode( new ast::AsmExpr( yylloc, *$2.str, $4, maybeMoveBuild( $6 ) ) );
+			$$ = new ExpressionNode( new ast::AsmExpr( yylloc, *$2.str, maybeMoveBuild( $4 ), maybeMoveBuild( $6 ) ) );
 			delete $2.str;
 		}
@@ -1825,7 +1827,7 @@
 		{ $$ = nullptr; }								// use default argument
 	| string_literal
-		{ $$ = new ExpressionNode( $1 ); }
+		{ $$ = $1; }
 	| asm_clobbers_list_opt ',' string_literal
-		{ $$ = (ExpressionNode *)($1->set_last( new ExpressionNode( $3 ) )); }
+		{ $$ = (ExpressionNode *)( $1->set_last( $3 ) ); }
 	;
 
@@ -1899,5 +1901,5 @@
 static_assert:
 	STATICASSERT '(' constant_expression ',' string_literal ')' ';' // C11
-		{ $$ = DeclarationNode::newStaticAssert( $3, $5 ); }
+		{ $$ = DeclarationNode::newStaticAssert( $3, maybeMoveBuild( $5 ) ); }
 	| STATICASSERT '(' constant_expression ')' ';'		// CFA
 		{ $$ = DeclarationNode::newStaticAssert( $3, build_constantStr( yylloc, *new string( "\"\"" ) ) ); }
@@ -3329,5 +3331,5 @@
 		{
 			DeclarationNode * name = new DeclarationNode();
-			name->asmName = $3;
+			name->asmName = maybeMoveBuild( $3 );
 			$$ = name->addQualifiers( $5 );
 		}
Index: src/Parser/parserutility.h
===================================================================
--- src/Parser/parserutility.h	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Parser/parserutility.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -24,10 +24,10 @@
 
 template< typename T >
-static inline auto maybeBuild( const T *orig ) -> decltype(orig->build()) {
+static inline auto maybeBuild( T * orig ) -> decltype(orig->build()) {
 	return (orig) ? orig->build() : nullptr;
 }
 
 template< typename T >
-static inline auto maybeMoveBuild( const T *orig ) -> decltype(orig->build()) {
+static inline auto maybeMoveBuild( T * orig ) -> decltype(orig->build()) {
 	auto ret = maybeBuild<T>(orig);
 	delete orig;
Index: src/ResolvExpr/CandidateFinder.cpp
===================================================================
--- src/ResolvExpr/CandidateFinder.cpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/ResolvExpr/CandidateFinder.cpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -55,4 +55,1882 @@
 namespace ResolvExpr {
 
+/// Unique identifier for matching expression resolutions to their requesting expression
+UniqueId globalResnSlot = 0;
+
+namespace {
+	/// First index is which argument, second is which alternative, third is which exploded element
+	using ExplodedArgs_new = std::deque< std::vector< ExplodedArg > >;
+
+	/// Returns a list of alternatives with the minimum cost in the given list
+	CandidateList findMinCost( const CandidateList & candidates ) {
+		CandidateList out;
+		Cost minCost = Cost::infinity;
+		for ( const CandidateRef & r : candidates ) {
+			if ( r->cost < minCost ) {
+				minCost = r->cost;
+				out.clear();
+				out.emplace_back( r );
+			} else if ( r->cost == minCost ) {
+				out.emplace_back( r );
+			}
+		}
+		return out;
+	}
+
+	/// Computes conversion cost for a given expression to a given type
+	const ast::Expr * computeExpressionConversionCost(
+		const ast::Expr * arg, const ast::Type * paramType, const ast::SymbolTable & symtab, const ast::TypeEnvironment & env, Cost & outCost
+	) {
+		Cost convCost = computeConversionCost(
+				arg->result, paramType, arg->get_lvalue(), symtab, env );
+		outCost += convCost;
+
+		// If there is a non-zero conversion cost, ignoring poly cost, then the expression requires
+		// conversion. Ignore poly cost for now, since this requires resolution of the cast to
+		// infer parameters and this does not currently work for the reason stated below
+		Cost tmpCost = convCost;
+		tmpCost.incPoly( -tmpCost.get_polyCost() );
+		if ( tmpCost != Cost::zero ) {
+			ast::ptr< ast::Type > newType = paramType;
+			env.apply( newType );
+			return new ast::CastExpr{ arg, newType };
+
+			// xxx - *should* be able to resolve this cast, but at the moment pointers are not
+			// castable to zero_t, but are implicitly convertible. This is clearly inconsistent,
+			// once this is fixed it should be possible to resolve the cast.
+			// xxx - this isn't working, it appears because type1 (parameter) is seen as widenable,
+			// but it shouldn't be because this makes the conversion from DT* to DT* since
+			// commontype(zero_t, DT*) is DT*, rather than nothing
+
+			// CandidateFinder finder{ symtab, env };
+			// finder.find( arg, ResolvMode::withAdjustment() );
+			// assertf( finder.candidates.size() > 0,
+			// 	"Somehow castable expression failed to find alternatives." );
+			// assertf( finder.candidates.size() == 1,
+			// 	"Somehow got multiple alternatives for known cast expression." );
+			// return finder.candidates.front()->expr;
+		}
+
+		return arg;
+	}
+
+	/// Computes conversion cost for a given candidate
+	Cost computeApplicationConversionCost(
+		CandidateRef cand, const ast::SymbolTable & symtab
+	) {
+		auto appExpr = cand->expr.strict_as< ast::ApplicationExpr >();
+		auto pointer = appExpr->func->result.strict_as< ast::PointerType >();
+		auto function = pointer->base.strict_as< ast::FunctionType >();
+
+		Cost convCost = Cost::zero;
+		const auto & params = function->params;
+		auto param = params.begin();
+		auto & args = appExpr->args;
+
+		for ( unsigned i = 0; i < args.size(); ++i ) {
+			const ast::Type * argType = args[i]->result;
+			PRINT(
+				std::cerr << "arg expression:" << std::endl;
+				ast::print( std::cerr, args[i], 2 );
+				std::cerr << "--- results are" << std::endl;
+				ast::print( std::cerr, argType, 2 );
+			)
+
+			if ( param == params.end() ) {
+				if ( function->isVarArgs ) {
+					convCost.incUnsafe();
+					PRINT( std::cerr << "end of params with varargs function: inc unsafe: "
+						<< convCost << std::endl; ; )
+					// convert reference-typed expressions into value-typed expressions
+					cand->expr = ast::mutate_field_index(
+						appExpr, &ast::ApplicationExpr::args, i,
+						referenceToRvalueConversion( args[i], convCost ) );
+					continue;
+				} else return Cost::infinity;
+			}
+
+			if ( auto def = args[i].as< ast::DefaultArgExpr >() ) {
+				// Default arguments should be free - don't include conversion cost.
+				// Unwrap them here because they are not relevant to the rest of the system
+				cand->expr = ast::mutate_field_index(
+					appExpr, &ast::ApplicationExpr::args, i, def->expr );
+				++param;
+				continue;
+			}
+
+			// mark conversion cost and also specialization cost of param type
+			// const ast::Type * paramType = (*param)->get_type();
+			cand->expr = ast::mutate_field_index(
+				appExpr, &ast::ApplicationExpr::args, i,
+				computeExpressionConversionCost(
+					args[i], *param, symtab, cand->env, convCost ) );
+			convCost.decSpec( specCost( *param ) );
+			++param;  // can't be in for-loop update because of the continue
+		}
+
+		if ( param != params.end() ) return Cost::infinity;
+
+		// specialization cost of return types can't be accounted for directly, it disables
+		// otherwise-identical calls, like this example based on auto-newline in the I/O lib:
+		//
+		//   forall(otype OS) {
+		//     void ?|?(OS&, int);  // with newline
+		//     OS&  ?|?(OS&, int);  // no newline, always chosen due to more specialization
+		//   }
+
+		// mark type variable and specialization cost of forall clause
+		convCost.incVar( function->forall.size() );
+		convCost.decSpec( function->assertions.size() );
+
+		return convCost;
+	}
+
+	void makeUnifiableVars(
+		const ast::FunctionType * type, ast::OpenVarSet & unifiableVars,
+		ast::AssertionSet & need
+	) {
+		for ( auto & tyvar : type->forall ) {
+			unifiableVars[ *tyvar ] = ast::TypeData{ tyvar->base };
+		}
+		for ( auto & assn : type->assertions ) {
+			need[ assn ].isUsed = true;
+		}
+	}
+
+	/// Gets a default value from an initializer, nullptr if not present
+	const ast::ConstantExpr * getDefaultValue( const ast::Init * init ) {
+		if ( auto si = dynamic_cast< const ast::SingleInit * >( init ) ) {
+			if ( auto ce = si->value.as< ast::CastExpr >() ) {
+				return ce->arg.as< ast::ConstantExpr >();
+			} else {
+				return si->value.as< ast::ConstantExpr >();
+			}
+		}
+		return nullptr;
+	}
+
+	/// State to iteratively build a match of parameter expressions to arguments
+	struct ArgPack {
+		std::size_t parent;          ///< Index of parent pack
+		ast::ptr< ast::Expr > expr;  ///< The argument stored here
+		Cost cost;                   ///< The cost of this argument
+		ast::TypeEnvironment env;    ///< Environment for this pack
+		ast::AssertionSet need;      ///< Assertions outstanding for this pack
+		ast::AssertionSet have;      ///< Assertions found for this pack
+		ast::OpenVarSet open;        ///< Open variables for this pack
+		unsigned nextArg;            ///< Index of next argument in arguments list
+		unsigned tupleStart;         ///< Number of tuples that start at this index
+		unsigned nextExpl;           ///< Index of next exploded element
+		unsigned explAlt;            ///< Index of alternative for nextExpl > 0
+
+		ArgPack()
+		: parent( 0 ), expr(), cost( Cost::zero ), env(), need(), have(), open(), nextArg( 0 ),
+		  tupleStart( 0 ), nextExpl( 0 ), explAlt( 0 ) {}
+
+		ArgPack(
+			const ast::TypeEnvironment & env, const ast::AssertionSet & need,
+			const ast::AssertionSet & have, const ast::OpenVarSet & open )
+		: parent( 0 ), expr(), cost( Cost::zero ), env( env ), need( need ), have( have ),
+		  open( open ), nextArg( 0 ), tupleStart( 0 ), nextExpl( 0 ), explAlt( 0 ) {}
+
+		ArgPack(
+			std::size_t parent, const ast::Expr * expr, ast::TypeEnvironment && env,
+			ast::AssertionSet && need, ast::AssertionSet && have, ast::OpenVarSet && open,
+			unsigned nextArg, unsigned tupleStart = 0, Cost cost = Cost::zero,
+			unsigned nextExpl = 0, unsigned explAlt = 0 )
+		: parent(parent), expr( expr ), cost( cost ), env( std::move( env ) ), need( std::move( need ) ),
+		  have( std::move( have ) ), open( std::move( open ) ), nextArg( nextArg ), tupleStart( tupleStart ),
+		  nextExpl( nextExpl ), explAlt( explAlt ) {}
+
+		ArgPack(
+			const ArgPack & o, ast::TypeEnvironment && env, ast::AssertionSet && need,
+			ast::AssertionSet && have, ast::OpenVarSet && open, unsigned nextArg, Cost added )
+		: parent( o.parent ), expr( o.expr ), cost( o.cost + added ), env( std::move( env ) ),
+		  need( std::move( need ) ), have( std::move( have ) ), open( std::move( open ) ), nextArg( nextArg ),
+		  tupleStart( o.tupleStart ), nextExpl( 0 ), explAlt( 0 ) {}
+
+		/// true if this pack is in the middle of an exploded argument
+		bool hasExpl() const { return nextExpl > 0; }
+
+		/// Gets the list of exploded candidates for this pack
+		const ExplodedArg & getExpl( const ExplodedArgs_new & args ) const {
+			return args[ nextArg-1 ][ explAlt ];
+		}
+
+		/// Ends a tuple expression, consolidating the appropriate args
+		void endTuple( const std::vector< ArgPack > & packs ) {
+			// add all expressions in tuple to list, summing cost
+			std::deque< const ast::Expr * > exprs;
+			const ArgPack * pack = this;
+			if ( expr ) { exprs.emplace_front( expr ); }
+			while ( pack->tupleStart == 0 ) {
+				pack = &packs[pack->parent];
+				exprs.emplace_front( pack->expr );
+				cost += pack->cost;
+			}
+			// reset pack to appropriate tuple
+			std::vector< ast::ptr< ast::Expr > > exprv( exprs.begin(), exprs.end() );
+			expr = new ast::TupleExpr{ expr->location, std::move( exprv ) };
+			tupleStart = pack->tupleStart - 1;
+			parent = pack->parent;
+		}
+	};
+
+	/// Instantiates an argument to match a parameter, returns false if no matching results left
+	bool instantiateArgument(
+		const CodeLocation & location,
+		const ast::Type * paramType, const ast::Init * init, const ExplodedArgs_new & args,
+		std::vector< ArgPack > & results, std::size_t & genStart, const ast::SymbolTable & symtab,
+		unsigned nTuples = 0
+	) {
+		if ( auto tupleType = dynamic_cast< const ast::TupleType * >( paramType ) ) {
+			// paramType is a TupleType -- group args into a TupleExpr
+			++nTuples;
+			for ( const ast::Type * type : *tupleType ) {
+				// xxx - dropping initializer changes behaviour from previous, but seems correct
+				// ^^^ need to handle the case where a tuple has a default argument
+				if ( ! instantiateArgument( location,
+					type, nullptr, args, results, genStart, symtab, nTuples ) ) return false;
+				nTuples = 0;
+			}
+			// re-constitute tuples for final generation
+			for ( auto i = genStart; i < results.size(); ++i ) {
+				results[i].endTuple( results );
+			}
+			return true;
+		} else if ( const ast::TypeInstType * ttype = Tuples::isTtype( paramType ) ) {
+			// paramType is a ttype, consumes all remaining arguments
+
+			// completed tuples; will be spliced to end of results to finish
+			std::vector< ArgPack > finalResults{};
+
+			// iterate until all results completed
+			std::size_t genEnd;
+			++nTuples;
+			do {
+				genEnd = results.size();
+
+				// add another argument to results
+				for ( std::size_t i = genStart; i < genEnd; ++i ) {
+					unsigned nextArg = results[i].nextArg;
+
+					// use next element of exploded tuple if present
+					if ( results[i].hasExpl() ) {
+						const ExplodedArg & expl = results[i].getExpl( args );
+
+						unsigned nextExpl = results[i].nextExpl + 1;
+						if ( nextExpl == expl.exprs.size() ) { nextExpl = 0; }
+
+						results.emplace_back(
+							i, expl.exprs[ results[i].nextExpl ], copy( results[i].env ),
+							copy( results[i].need ), copy( results[i].have ),
+							copy( results[i].open ), nextArg, nTuples, Cost::zero, nextExpl,
+							results[i].explAlt );
+
+						continue;
+					}
+
+					// finish result when out of arguments
+					if ( nextArg >= args.size() ) {
+						ArgPack newResult{
+							results[i].env, results[i].need, results[i].have, results[i].open };
+						newResult.nextArg = nextArg;
+						const ast::Type * argType = nullptr;
+
+						if ( nTuples > 0 || ! results[i].expr ) {
+							// first iteration or no expression to clone,
+							// push empty tuple expression
+							newResult.parent = i;
+							newResult.expr = new ast::TupleExpr( location, {} );
+							argType = newResult.expr->result;
+						} else {
+							// clone result to collect tuple
+							newResult.parent = results[i].parent;
+							newResult.cost = results[i].cost;
+							newResult.tupleStart = results[i].tupleStart;
+							newResult.expr = results[i].expr;
+							argType = newResult.expr->result;
+
+							if ( results[i].tupleStart > 0 && Tuples::isTtype( argType ) ) {
+								// the case where a ttype value is passed directly is special,
+								// e.g. for argument forwarding purposes
+								// xxx - what if passing multiple arguments, last of which is
+								//       ttype?
+								// xxx - what would happen if unify was changed so that unifying
+								//       tuple
+								// types flattened both before unifying lists? then pass in
+								// TupleType (ttype) below.
+								--newResult.tupleStart;
+							} else {
+								// collapse leftover arguments into tuple
+								newResult.endTuple( results );
+								argType = newResult.expr->result;
+							}
+						}
+
+						// check unification for ttype before adding to final
+						if (
+							unify(
+								ttype, argType, newResult.env, newResult.need, newResult.have,
+								newResult.open, symtab )
+						) {
+							finalResults.emplace_back( std::move( newResult ) );
+						}
+
+						continue;
+					}
+
+					// add each possible next argument
+					for ( std::size_t j = 0; j < args[nextArg].size(); ++j ) {
+						const ExplodedArg & expl = args[nextArg][j];
+
+						// fresh copies of parent parameters for this iteration
+						ast::TypeEnvironment env = results[i].env;
+						ast::OpenVarSet open = results[i].open;
+
+						env.addActual( expl.env, open );
+
+						// skip empty tuple arguments by (nearly) cloning parent into next gen
+						if ( expl.exprs.empty() ) {
+							results.emplace_back(
+								results[i], std::move( env ), copy( results[i].need ),
+								copy( results[i].have ), std::move( open ), nextArg + 1, expl.cost );
+
+							continue;
+						}
+
+						// add new result
+						results.emplace_back(
+							i, expl.exprs.front(), std::move( env ), copy( results[i].need ),
+							copy( results[i].have ), std::move( open ), nextArg + 1, nTuples,
+							expl.cost, expl.exprs.size() == 1 ? 0 : 1, j );
+					}
+				}
+
+				// reset for next round
+				genStart = genEnd;
+				nTuples = 0;
+			} while ( genEnd != results.size() );
+
+			// splice final results onto results
+			for ( std::size_t i = 0; i < finalResults.size(); ++i ) {
+				results.emplace_back( std::move( finalResults[i] ) );
+			}
+			return ! finalResults.empty();
+		}
+
+		// iterate each current subresult
+		std::size_t genEnd = results.size();
+		for ( std::size_t i = genStart; i < genEnd; ++i ) {
+			unsigned nextArg = results[i].nextArg;
+
+			// use remainder of exploded tuple if present
+			if ( results[i].hasExpl() ) {
+				const ExplodedArg & expl = results[i].getExpl( args );
+				const ast::Expr * expr = expl.exprs[ results[i].nextExpl ];
+
+				ast::TypeEnvironment env = results[i].env;
+				ast::AssertionSet need = results[i].need, have = results[i].have;
+				ast::OpenVarSet open = results[i].open;
+
+				const ast::Type * argType = expr->result;
+
+				PRINT(
+					std::cerr << "param type is ";
+					ast::print( std::cerr, paramType );
+					std::cerr << std::endl << "arg type is ";
+					ast::print( std::cerr, argType );
+					std::cerr << std::endl;
+				)
+
+				if ( unify( paramType, argType, env, need, have, open, symtab ) ) {
+					unsigned nextExpl = results[i].nextExpl + 1;
+					if ( nextExpl == expl.exprs.size() ) { nextExpl = 0; }
+
+					results.emplace_back(
+						i, expr, std::move( env ), std::move( need ), std::move( have ), std::move( open ), nextArg,
+						nTuples, Cost::zero, nextExpl, results[i].explAlt );
+				}
+
+				continue;
+			}
+
+			// use default initializers if out of arguments
+			if ( nextArg >= args.size() ) {
+				if ( const ast::ConstantExpr * cnst = getDefaultValue( init ) ) {
+					ast::TypeEnvironment env = results[i].env;
+					ast::AssertionSet need = results[i].need, have = results[i].have;
+					ast::OpenVarSet open = results[i].open;
+
+					if ( unify( paramType, cnst->result, env, need, have, open, symtab ) ) {
+						results.emplace_back(
+							i, new ast::DefaultArgExpr{ cnst->location, cnst }, std::move( env ),
+							std::move( need ), std::move( have ), std::move( open ), nextArg, nTuples );
+					}
+				}
+
+				continue;
+			}
+
+			// Check each possible next argument
+			for ( std::size_t j = 0; j < args[nextArg].size(); ++j ) {
+				const ExplodedArg & expl = args[nextArg][j];
+
+				// fresh copies of parent parameters for this iteration
+				ast::TypeEnvironment env = results[i].env;
+				ast::AssertionSet need = results[i].need, have = results[i].have;
+				ast::OpenVarSet open = results[i].open;
+
+				env.addActual( expl.env, open );
+
+				// skip empty tuple arguments by (nearly) cloning parent into next gen
+				if ( expl.exprs.empty() ) {
+					results.emplace_back(
+						results[i], std::move( env ), std::move( need ), std::move( have ), std::move( open ),
+						nextArg + 1, expl.cost );
+
+					continue;
+				}
+
+				// consider only first exploded arg
+				const ast::Expr * expr = expl.exprs.front();
+				const ast::Type * argType = expr->result;
+
+				PRINT(
+					std::cerr << "param type is ";
+					ast::print( std::cerr, paramType );
+					std::cerr << std::endl << "arg type is ";
+					ast::print( std::cerr, argType );
+					std::cerr << std::endl;
+				)
+
+				// attempt to unify types
+				if ( unify( paramType, argType, env, need, have, open, symtab ) ) {
+					// add new result
+					results.emplace_back(
+						i, expr, std::move( env ), std::move( need ), std::move( have ), std::move( open ),
+						nextArg + 1, nTuples, expl.cost, expl.exprs.size() == 1 ? 0 : 1, j );
+				}
+			}
+		}
+
+		// reset for next parameter
+		genStart = genEnd;
+
+		return genEnd != results.size();  // were any new results added?
+	}
+
+	/// Generate a cast expression from `arg` to `toType`
+	const ast::Expr * restructureCast(
+		ast::ptr< ast::Expr > & arg, const ast::Type * toType, ast::GeneratedFlag isGenerated = ast::GeneratedCast
+	) {
+		if (
+			arg->result->size() > 1
+			&& ! toType->isVoid()
+			&& ! dynamic_cast< const ast::ReferenceType * >( toType )
+		) {
+			// Argument is a tuple and the target type is neither void nor a reference. Cast each
+			// member of the tuple to its corresponding target type, producing the tuple of those
+			// cast expressions. If there are more components of the tuple than components in the
+			// target type, then excess components do not come out in the result expression (but
+			// UniqueExpr ensures that the side effects will still be produced)
+			if ( Tuples::maybeImpureIgnoreUnique( arg ) ) {
+				// expressions which may contain side effects require a single unique instance of
+				// the expression
+				arg = new ast::UniqueExpr{ arg->location, arg };
+			}
+			std::vector< ast::ptr< ast::Expr > > components;
+			for ( unsigned i = 0; i < toType->size(); ++i ) {
+				// cast each component
+				ast::ptr< ast::Expr > idx = new ast::TupleIndexExpr{ arg->location, arg, i };
+				components.emplace_back(
+					restructureCast( idx, toType->getComponent( i ), isGenerated ) );
+			}
+			return new ast::TupleExpr{ arg->location, std::move( components ) };
+		} else {
+			// handle normally
+			return new ast::CastExpr{ arg->location, arg, toType, isGenerated };
+		}
+	}
+
+	/// Gets the name from an untyped member expression (must be NameExpr)
+	const std::string & getMemberName( const ast::UntypedMemberExpr * memberExpr ) {
+		if ( memberExpr->member.as< ast::ConstantExpr >() ) {
+			SemanticError( memberExpr, "Indexed access to struct fields unsupported: " );
+		}
+
+		return memberExpr->member.strict_as< ast::NameExpr >()->name;
+	}
+
+	/// Actually visits expressions to find their candidate interpretations
+	class Finder final : public ast::WithShortCircuiting {
+		const ResolveContext & context;
+		const ast::SymbolTable & symtab;
+	public:
+		// static size_t traceId;
+		CandidateFinder & selfFinder;
+		CandidateList & candidates;
+		const ast::TypeEnvironment & tenv;
+		ast::ptr< ast::Type > & targetType;
+
+		enum Errors {
+			NotFound,
+			NoMatch,
+			ArgsToFew,
+			ArgsToMany,
+			RetsToFew,
+			RetsToMany,
+			NoReason
+		};
+
+		struct {
+			Errors code = NotFound;
+		} reason;
+
+		Finder( CandidateFinder & f )
+		: context( f.context ), symtab( context.symtab ), selfFinder( f ),
+		  candidates( f.candidates ), tenv( f.env ), targetType( f.targetType ) {}
+
+		void previsit( const ast::Node * ) { visit_children = false; }
+
+		/// Convenience to add candidate to list
+		template<typename... Args>
+		void addCandidate( Args &&... args ) {
+			candidates.emplace_back( new Candidate{ std::forward<Args>( args )... } );
+			reason.code = NoReason;
+		}
+
+		void postvisit( const ast::ApplicationExpr * applicationExpr ) {
+			addCandidate( applicationExpr, tenv );
+		}
+
+		/// Set up candidate assertions for inference
+		void inferParameters( CandidateRef & newCand, CandidateList & out );
+
+		/// Completes a function candidate with arguments located
+		void validateFunctionCandidate(
+			const CandidateRef & func, ArgPack & result, const std::vector< ArgPack > & results,
+			CandidateList & out );
+
+		/// Builds a list of candidates for a function, storing them in out
+		void makeFunctionCandidates(
+			const CodeLocation & location,
+			const CandidateRef & func, const ast::FunctionType * funcType,
+			const ExplodedArgs_new & args, CandidateList & out );
+
+		/// Adds implicit struct-conversions to the alternative list
+		void addAnonConversions( const CandidateRef & cand );
+
+		/// Adds aggregate member interpretations
+		void addAggMembers(
+			const ast::BaseInstType * aggrInst, const ast::Expr * expr,
+			const Candidate & cand, const Cost & addedCost, const std::string & name
+		);
+
+		/// Adds tuple member interpretations
+		void addTupleMembers(
+			const ast::TupleType * tupleType, const ast::Expr * expr, const Candidate & cand,
+			const Cost & addedCost, const ast::Expr * member
+		);
+
+		/// true if expression is an lvalue
+		static bool isLvalue( const ast::Expr * x ) {
+			return x->result && ( x->get_lvalue() || x->result.as< ast::ReferenceType >() );
+		}
+
+		void postvisit( const ast::UntypedExpr * untypedExpr );
+		void postvisit( const ast::VariableExpr * variableExpr );
+		void postvisit( const ast::ConstantExpr * constantExpr );
+		void postvisit( const ast::SizeofExpr * sizeofExpr );
+		void postvisit( const ast::AlignofExpr * alignofExpr );
+		void postvisit( const ast::AddressExpr * addressExpr );
+		void postvisit( const ast::LabelAddressExpr * labelExpr );
+		void postvisit( const ast::CastExpr * castExpr );
+		void postvisit( const ast::VirtualCastExpr * castExpr );
+		void postvisit( const ast::KeywordCastExpr * castExpr );
+		void postvisit( const ast::UntypedMemberExpr * memberExpr );
+		void postvisit( const ast::MemberExpr * memberExpr );
+		void postvisit( const ast::NameExpr * nameExpr );
+		void postvisit( const ast::UntypedOffsetofExpr * offsetofExpr );
+		void postvisit( const ast::OffsetofExpr * offsetofExpr );
+		void postvisit( const ast::OffsetPackExpr * offsetPackExpr );
+		void postvisit( const ast::LogicalExpr * logicalExpr );
+		void postvisit( const ast::ConditionalExpr * conditionalExpr );
+		void postvisit( const ast::CommaExpr * commaExpr );
+		void postvisit( const ast::ImplicitCopyCtorExpr * ctorExpr );
+		void postvisit( const ast::ConstructorExpr * ctorExpr );
+		void postvisit( const ast::RangeExpr * rangeExpr );
+		void postvisit( const ast::UntypedTupleExpr * tupleExpr );
+		void postvisit( const ast::TupleExpr * tupleExpr );
+		void postvisit( const ast::TupleIndexExpr * tupleExpr );
+		void postvisit( const ast::TupleAssignExpr * tupleExpr );
+		void postvisit( const ast::UniqueExpr * unqExpr );
+		void postvisit( const ast::StmtExpr * stmtExpr );
+		void postvisit( const ast::UntypedInitExpr * initExpr );
+
+		void postvisit( const ast::InitExpr * ) {
+			assertf( false, "CandidateFinder should never see a resolved InitExpr." );
+		}
+
+		void postvisit( const ast::DeletedExpr * ) {
+			assertf( false, "CandidateFinder should never see a DeletedExpr." );
+		}
+
+		void postvisit( const ast::GenericExpr * ) {
+			assertf( false, "_Generic is not yet supported." );
+		}
+	};
+
+	/// Set up candidate assertions for inference
+	void Finder::inferParameters( CandidateRef & newCand, CandidateList & out ) {
+		// Set need bindings for any unbound assertions
+		UniqueId crntResnSlot = 0; // matching ID for this expression's assertions
+		for ( auto & assn : newCand->need ) {
+			// skip already-matched assertions
+			if ( assn.second.resnSlot != 0 ) continue;
+			// assign slot for expression if needed
+			if ( crntResnSlot == 0 ) { crntResnSlot = ++globalResnSlot; }
+			// fix slot to assertion
+			assn.second.resnSlot = crntResnSlot;
+		}
+		// pair slot to expression
+		if ( crntResnSlot != 0 ) {
+			newCand->expr.get_and_mutate()->inferred.resnSlots().emplace_back( crntResnSlot );
+		}
+
+		// add to output list; assertion satisfaction will occur later
+		out.emplace_back( newCand );
+	}
+
+	/// Completes a function candidate with arguments located
+	void Finder::validateFunctionCandidate(
+		const CandidateRef & func, ArgPack & result, const std::vector< ArgPack > & results,
+		CandidateList & out
+	) {
+		ast::ApplicationExpr * appExpr =
+			new ast::ApplicationExpr{ func->expr->location, func->expr };
+		// sum cost and accumulate arguments
+		std::deque< const ast::Expr * > args;
+		Cost cost = func->cost;
+		const ArgPack * pack = &result;
+		while ( pack->expr ) {
+			args.emplace_front( pack->expr );
+			cost += pack->cost;
+			pack = &results[pack->parent];
+		}
+		std::vector< ast::ptr< ast::Expr > > vargs( args.begin(), args.end() );
+		appExpr->args = std::move( vargs );
+		// build and validate new candidate
+		auto newCand =
+			std::make_shared<Candidate>( appExpr, result.env, result.open, result.need, cost );
+		PRINT(
+			std::cerr << "instantiate function success: " << appExpr << std::endl;
+			std::cerr << "need assertions:" << std::endl;
+			ast::print( std::cerr, result.need, 2 );
+		)
+		inferParameters( newCand, out );
+	}
+
+	/// Builds a list of candidates for a function, storing them in out
+	void Finder::makeFunctionCandidates(
+		const CodeLocation & location,
+		const CandidateRef & func, const ast::FunctionType * funcType,
+		const ExplodedArgs_new & args, CandidateList & out
+	) {
+		ast::OpenVarSet funcOpen;
+		ast::AssertionSet funcNeed, funcHave;
+		ast::TypeEnvironment funcEnv{ func->env };
+		makeUnifiableVars( funcType, funcOpen, funcNeed );
+		// add all type variables as open variables now so that those not used in the
+		// parameter list are still considered open
+		funcEnv.add( funcType->forall );
+
+		if ( targetType && ! targetType->isVoid() && ! funcType->returns.empty() ) {
+			// attempt to narrow based on expected target type
+			const ast::Type * returnType = funcType->returns.front();
+			if ( ! unify(
+				returnType, targetType, funcEnv, funcNeed, funcHave, funcOpen, symtab )
+			) {
+				// unification failed, do not pursue this candidate
+				return;
+			}
+		}
+
+		// iteratively build matches, one parameter at a time
+		std::vector< ArgPack > results;
+		results.emplace_back( funcEnv, funcNeed, funcHave, funcOpen );
+		std::size_t genStart = 0;
+
+		// 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( location,
+						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( location,
+				param, nullptr, args, results, genStart, symtab ) ) return;
+		}
+
+		endMatch:
+		if ( funcType->isVarArgs ) {
+			// append any unused arguments to vararg pack
+			std::size_t genEnd;
+			do {
+				genEnd = results.size();
+
+				// iterate results
+				for ( std::size_t i = genStart; i < genEnd; ++i ) {
+					unsigned nextArg = results[i].nextArg;
+
+					// use remainder of exploded tuple if present
+					if ( results[i].hasExpl() ) {
+						const ExplodedArg & expl = results[i].getExpl( args );
+
+						unsigned nextExpl = results[i].nextExpl + 1;
+						if ( nextExpl == expl.exprs.size() ) { nextExpl = 0; }
+
+						results.emplace_back(
+							i, expl.exprs[ results[i].nextExpl ], copy( results[i].env ),
+							copy( results[i].need ), copy( results[i].have ),
+							copy( results[i].open ), nextArg, 0, Cost::zero, nextExpl,
+							results[i].explAlt );
+
+						continue;
+					}
+
+					// finish result when out of arguments
+					if ( nextArg >= args.size() ) {
+						validateFunctionCandidate( func, results[i], results, out );
+
+						continue;
+					}
+
+					// add each possible next argument
+					for ( std::size_t j = 0; j < args[nextArg].size(); ++j ) {
+						const ExplodedArg & expl = args[nextArg][j];
+
+						// fresh copies of parent parameters for this iteration
+						ast::TypeEnvironment env = results[i].env;
+						ast::OpenVarSet open = results[i].open;
+
+						env.addActual( expl.env, open );
+
+						// skip empty tuple arguments by (nearly) cloning parent into next gen
+						if ( expl.exprs.empty() ) {
+							results.emplace_back(
+								results[i], std::move( env ), copy( results[i].need ),
+								copy( results[i].have ), std::move( open ), nextArg + 1,
+								expl.cost );
+
+							continue;
+						}
+
+						// add new result
+						results.emplace_back(
+							i, expl.exprs.front(), std::move( env ), copy( results[i].need ),
+							copy( results[i].have ), std::move( open ), nextArg + 1, 0, expl.cost,
+							expl.exprs.size() == 1 ? 0 : 1, j );
+					}
+				}
+
+				genStart = genEnd;
+			} while( genEnd != results.size() );
+		} else {
+			// filter out the results that don't use all the arguments
+			for ( std::size_t i = genStart; i < results.size(); ++i ) {
+				ArgPack & result = results[i];
+				if ( ! result.hasExpl() && result.nextArg >= args.size() ) {
+					validateFunctionCandidate( func, result, results, out );
+				}
+			}
+		}
+	}
+
+	/// Adds implicit struct-conversions to the alternative list
+	void Finder::addAnonConversions( const CandidateRef & cand ) {
+		// adds anonymous member interpretations whenever an aggregate value type is seen.
+		// it's okay for the aggregate expression to have reference type -- cast it to the
+		// base type to treat the aggregate as the referenced value
+		ast::ptr< ast::Expr > aggrExpr( cand->expr );
+		ast::ptr< ast::Type > & aggrType = aggrExpr.get_and_mutate()->result;
+		cand->env.apply( aggrType );
+
+		if ( aggrType.as< ast::ReferenceType >() ) {
+			aggrExpr = new ast::CastExpr{ aggrExpr, aggrType->stripReferences() };
+		}
+
+		if ( auto structInst = aggrExpr->result.as< ast::StructInstType >() ) {
+			addAggMembers( structInst, aggrExpr, *cand, Cost::safe, "" );
+		} else if ( auto unionInst = aggrExpr->result.as< ast::UnionInstType >() ) {
+			addAggMembers( unionInst, aggrExpr, *cand, Cost::safe, "" );
+		}
+	}
+
+	/// Adds aggregate member interpretations
+	void Finder::addAggMembers(
+		const ast::BaseInstType * aggrInst, const ast::Expr * expr,
+		const Candidate & cand, const Cost & addedCost, const std::string & name
+	) {
+		for ( const ast::Decl * decl : aggrInst->lookup( name ) ) {
+			auto dwt = strict_dynamic_cast< const ast::DeclWithType * >( decl );
+			CandidateRef newCand = std::make_shared<Candidate>(
+				cand, new ast::MemberExpr{ expr->location, dwt, expr }, addedCost );
+			// add anonymous member interpretations whenever an aggregate value type is seen
+			// as a member expression
+			addAnonConversions( newCand );
+			candidates.emplace_back( std::move( newCand ) );
+		}
+	}
+
+	/// Adds tuple member interpretations
+	void Finder::addTupleMembers(
+		const ast::TupleType * tupleType, const ast::Expr * expr, const Candidate & cand,
+		const Cost & addedCost, const ast::Expr * member
+	) {
+		if ( auto constantExpr = dynamic_cast< const ast::ConstantExpr * >( member ) ) {
+			// get the value of the constant expression as an int, must be between 0 and the
+			// length of the tuple to have meaning
+			long long val = constantExpr->intValue();
+			if ( val >= 0 && (unsigned long long)val < tupleType->size() ) {
+				addCandidate(
+					cand, new ast::TupleIndexExpr{ expr->location, expr, (unsigned)val },
+					addedCost );
+			}
+		}
+	}
+
+	void Finder::postvisit( const ast::UntypedExpr * untypedExpr ) {
+		std::vector< CandidateFinder > argCandidates =
+			selfFinder.findSubExprs( untypedExpr->args );
+
+		// take care of possible tuple assignments
+		// if not tuple assignment, handled as normal function call
+		Tuples::handleTupleAssignment( selfFinder, untypedExpr, argCandidates );
+
+		CandidateFinder funcFinder( context, tenv );
+		if (auto nameExpr = untypedExpr->func.as<ast::NameExpr>()) {
+			auto kind = ast::SymbolTable::getSpecialFunctionKind(nameExpr->name);
+			if (kind != ast::SymbolTable::SpecialFunctionKind::NUMBER_OF_KINDS) {
+				assertf(!argCandidates.empty(), "special function call without argument");
+				for (auto & firstArgCand: argCandidates[0]) {
+					ast::ptr<ast::Type> argType = firstArgCand->expr->result;
+					firstArgCand->env.apply(argType);
+					// strip references
+					// xxx - is this correct?
+					while (argType.as<ast::ReferenceType>()) argType = argType.as<ast::ReferenceType>()->base;
+
+					// convert 1-tuple to plain type
+					if (auto tuple = argType.as<ast::TupleType>()) {
+						if (tuple->size() == 1) {
+							argType = tuple->types[0];
+						}
+					}
+
+					// if argType is an unbound type parameter, all special functions need to be searched.
+					if (isUnboundType(argType)) {
+						funcFinder.otypeKeys.clear();
+						break;
+					}
+
+					if (argType.as<ast::PointerType>()) funcFinder.otypeKeys.insert(Mangle::Encoding::pointer);						
+					// else if (const ast::EnumInstType * enumInst = argType.as<ast::EnumInstType>()) {
+					// 	const ast::EnumDecl * enumDecl = enumInst->base; // Here
+					// 	if ( const ast::Type* enumType = enumDecl->base ) {
+					// 		// instance of enum (T) is a instance of type (T)
+					// 		funcFinder.otypeKeys.insert(Mangle::mangle(enumType, Mangle::NoGenericParams | Mangle::Type));
+					// 	} else {
+					// 		// instance of an untyped enum is techically int
+					// 		funcFinder.otypeKeys.insert(Mangle::mangle(enumDecl, Mangle::NoGenericParams | Mangle::Type));
+					// 	}
+					// }
+					else funcFinder.otypeKeys.insert(Mangle::mangle(argType, Mangle::NoGenericParams | Mangle::Type));
+				}
+			}
+		}
+		// if candidates are already produced, do not fail
+		// xxx - is it possible that handleTupleAssignment and main finder both produce candidates?
+		// this means there exists ctor/assign functions with a tuple as first parameter.
+		ResolvMode mode = {
+			true, // adjust
+			!untypedExpr->func.as<ast::NameExpr>(), // prune if not calling by name
+			selfFinder.candidates.empty() // failfast if other options are not found
+		};
+		funcFinder.find( untypedExpr->func, mode );
+		// short-circuit if no candidates
+		// if ( funcFinder.candidates.empty() ) return;
+
+		reason.code = NoMatch;
+
+		// find function operators
+		ast::ptr< ast::Expr > opExpr = new ast::NameExpr{ untypedExpr->location, "?()" }; // ??? why not ?{}
+		CandidateFinder opFinder( context, tenv );
+		// okay if there aren't any function operations
+		opFinder.find( opExpr, ResolvMode::withoutFailFast() );
+		PRINT(
+			std::cerr << "known function ops:" << std::endl;
+			print( std::cerr, opFinder.candidates, 1 );
+		)
+
+		// pre-explode arguments
+		ExplodedArgs_new argExpansions;
+		for ( const CandidateFinder & args : argCandidates ) {
+			argExpansions.emplace_back();
+			auto & argE = argExpansions.back();
+			for ( const CandidateRef & arg : args ) { argE.emplace_back( *arg, symtab ); }
+		}
+
+		// Find function matches
+		CandidateList found;
+		SemanticErrorException errors;
+		for ( CandidateRef & func : funcFinder ) {
+			try {
+				PRINT(
+					std::cerr << "working on alternative:" << std::endl;
+					print( std::cerr, *func, 2 );
+				)
+
+				// check if the type is a pointer to function
+				const ast::Type * funcResult = func->expr->result->stripReferences();
+				if ( auto pointer = dynamic_cast< const ast::PointerType * >( funcResult ) ) {
+					if ( auto function = pointer->base.as< ast::FunctionType >() ) {
+						CandidateRef newFunc{ new Candidate{ *func } };
+						newFunc->expr =
+							referenceToRvalueConversion( newFunc->expr, newFunc->cost );
+						makeFunctionCandidates( untypedExpr->location,
+							newFunc, function, argExpansions, found );
+					}
+				} else if (
+					auto inst = dynamic_cast< const ast::TypeInstType * >( funcResult )
+				) {
+					if ( const ast::EqvClass * clz = func->env.lookup( *inst ) ) {
+						if ( auto function = clz->bound.as< ast::FunctionType >() ) {
+							CandidateRef newFunc{ new Candidate{ *func } };
+							newFunc->expr =
+								referenceToRvalueConversion( newFunc->expr, newFunc->cost );
+							makeFunctionCandidates( untypedExpr->location,
+								newFunc, function, argExpansions, found );
+						}
+					}
+				}
+			} catch ( SemanticErrorException & e ) { errors.append( e ); }
+		}
+
+		// Find matches on function operators `?()`
+		if ( ! opFinder.candidates.empty() ) {
+			// add exploded function alternatives to front of argument list
+			std::vector< ExplodedArg > funcE;
+			funcE.reserve( funcFinder.candidates.size() );
+			for ( const CandidateRef & func : funcFinder ) {
+				funcE.emplace_back( *func, symtab );
+			}
+			argExpansions.emplace_front( std::move( funcE ) );
+
+			for ( const CandidateRef & op : opFinder ) {
+				try {
+					// check if type is pointer-to-function
+					const ast::Type * opResult = op->expr->result->stripReferences();
+					if ( auto pointer = dynamic_cast< const ast::PointerType * >( opResult ) ) {
+						if ( auto function = pointer->base.as< ast::FunctionType >() ) {
+							CandidateRef newOp{ new Candidate{ *op} };
+							newOp->expr =
+								referenceToRvalueConversion( newOp->expr, newOp->cost );
+							makeFunctionCandidates( untypedExpr->location,
+								newOp, function, argExpansions, found );
+						}
+					}
+				} catch ( SemanticErrorException & e ) { errors.append( e ); }
+			}
+		}
+
+		// Implement SFINAE; resolution errors are only errors if there aren't any non-error
+		// candidates
+		if ( found.empty() && ! errors.isEmpty() ) { throw errors; }
+
+		// Compute conversion costs
+		for ( CandidateRef & withFunc : found ) {
+			Cost cvtCost = computeApplicationConversionCost( withFunc, symtab );
+
+			PRINT(
+				auto appExpr = withFunc->expr.strict_as< ast::ApplicationExpr >();
+				auto pointer = appExpr->func->result.strict_as< ast::PointerType >();
+				auto function = pointer->base.strict_as< ast::FunctionType >();
+
+				std::cerr << "Case +++++++++++++ " << appExpr->func << std::endl;
+				std::cerr << "parameters are:" << std::endl;
+				ast::printAll( std::cerr, function->params, 2 );
+				std::cerr << "arguments are:" << std::endl;
+				ast::printAll( std::cerr, appExpr->args, 2 );
+				std::cerr << "bindings are:" << std::endl;
+				ast::print( std::cerr, withFunc->env, 2 );
+				std::cerr << "cost is: " << withFunc->cost << std::endl;
+				std::cerr << "cost of conversion is:" << cvtCost << std::endl;
+			)
+
+			if ( cvtCost != Cost::infinity ) {
+				withFunc->cvtCost = cvtCost;
+				candidates.emplace_back( std::move( withFunc ) );
+			}
+		}
+		found = std::move( candidates );
+
+		// use a new list so that candidates are not examined by addAnonConversions twice
+		CandidateList winners = findMinCost( found );
+		promoteCvtCost( winners );
+
+		// function may return a struct/union value, in which case we need to add candidates
+		// for implicit conversions to each of the anonymous members, which must happen after
+		// `findMinCost`, since anon conversions are never the cheapest
+		for ( const CandidateRef & c : winners ) {
+			addAnonConversions( c );
+		}
+		spliceBegin( candidates, winners );
+
+		if ( candidates.empty() && targetType && ! targetType->isVoid() ) {
+			// If resolution is unsuccessful with a target type, try again without, since it
+			// will sometimes succeed when it wouldn't with a target type binding.
+			// For example:
+			//   forall( otype T ) T & ?[]( T *, ptrdiff_t );
+			//   const char * x = "hello world";
+			//   unsigned char ch = x[0];
+			// Fails with simple return type binding (xxx -- check this!) as follows:
+			// * T is bound to unsigned char
+			// * (x: const char *) is unified with unsigned char *, which fails
+			// xxx -- fix this better
+			targetType = nullptr;
+			postvisit( untypedExpr );
+		}
+	}
+
+	void Finder::postvisit( const ast::AddressExpr * addressExpr ) {
+		CandidateFinder finder( context, tenv );
+		finder.find( addressExpr->arg );
+
+		if ( finder.candidates.empty() ) return;
+
+		reason.code = NoMatch;
+
+		for ( CandidateRef & r : finder.candidates ) {
+			if ( ! isLvalue( r->expr ) ) continue;
+			addCandidate( *r, new ast::AddressExpr{ addressExpr->location, r->expr } );
+		}
+	}
+
+	void Finder::postvisit( const ast::LabelAddressExpr * labelExpr ) {
+		addCandidate( labelExpr, tenv );
+	}
+
+	void Finder::postvisit( const ast::CastExpr * castExpr ) {
+		ast::ptr< ast::Type > toType = castExpr->result;
+		assert( toType );
+		toType = resolveTypeof( toType, context );
+		toType = adjustExprType( toType, tenv, symtab );
+
+		CandidateFinder finder( context, tenv, toType );
+		finder.find( castExpr->arg, ResolvMode::withAdjustment() );
+
+		if ( !finder.candidates.empty() ) reason.code = NoMatch;
+
+		CandidateList matches;
+		for ( CandidateRef & cand : finder.candidates ) {
+			ast::AssertionSet need( cand->need.begin(), cand->need.end() ), have;
+			ast::OpenVarSet open( cand->open );
+
+			cand->env.extractOpenVars( open );
+
+			// It is possible that a cast can throw away some values in a multiply-valued
+			// expression, e.g. cast-to-void, one value to zero. Figure out the prefix of the
+			// subexpression results that are cast directly. The candidate is invalid if it
+			// has fewer results than there are types to cast to.
+			int discardedValues = cand->expr->result->size() - toType->size();
+			if ( discardedValues < 0 ) continue;
+
+			// unification run for side-effects
+			unify( toType, cand->expr->result, cand->env, need, have, open, symtab );
+			Cost thisCost =
+				(castExpr->isGenerated == ast::GeneratedFlag::GeneratedCast)
+					? conversionCost( cand->expr->result, toType, cand->expr->get_lvalue(), symtab, cand->env )
+					: castCost( cand->expr->result, toType, cand->expr->get_lvalue(), symtab, cand->env );
+
+			PRINT(
+				std::cerr << "working on cast with result: " << toType << std::endl;
+				std::cerr << "and expr type: " << cand->expr->result << std::endl;
+				std::cerr << "env: " << cand->env << std::endl;
+			)
+			if ( thisCost != Cost::infinity ) {
+				PRINT(
+					std::cerr << "has finite cost." << std::endl;
+				)
+				// count one safe conversion for each value that is thrown away
+				thisCost.incSafe( discardedValues );
+				CandidateRef newCand = std::make_shared<Candidate>(
+					restructureCast( cand->expr, toType, castExpr->isGenerated ),
+					copy( cand->env ), std::move( open ), std::move( need ), cand->cost,
+					cand->cost + thisCost );
+				inferParameters( newCand, matches );
+			}
+		}
+
+		// select first on argument cost, then conversion cost
+		CandidateList minArgCost = findMinCost( matches );
+		promoteCvtCost( minArgCost );
+		candidates = findMinCost( minArgCost );
+	}
+
+	void Finder::postvisit( const ast::VirtualCastExpr * castExpr ) {
+		assertf( castExpr->result, "Implicit virtual cast targets not yet supported." );
+		CandidateFinder finder( context, tenv );
+		// don't prune here, all alternatives guaranteed to have same type
+		finder.find( castExpr->arg, ResolvMode::withoutPrune() );
+		for ( CandidateRef & r : finder.candidates ) {
+			addCandidate(
+				*r,
+				new ast::VirtualCastExpr{ castExpr->location, r->expr, castExpr->result } );
+		}
+	}
+
+	void Finder::postvisit( const ast::KeywordCastExpr * castExpr ) {
+		const auto & loc = castExpr->location;
+		assertf( castExpr->result, "Cast target should have been set in Validate." );
+		auto ref = castExpr->result.strict_as<ast::ReferenceType>();
+		auto inst = ref->base.strict_as<ast::StructInstType>();
+		auto target = inst->base.get();
+
+		CandidateFinder finder( context, tenv );
+
+		auto pick_alternatives = [target, this](CandidateList & found, bool expect_ref) {
+			for (auto & cand : found) {
+				const ast::Type * expr = cand->expr->result.get();
+				if (expect_ref) {
+					auto res = dynamic_cast<const ast::ReferenceType*>(expr);
+					if (!res) { continue; }
+					expr = res->base.get();
+				}
+
+				if (auto insttype = dynamic_cast<const ast::TypeInstType*>(expr)) {
+					auto td = cand->env.lookup(*insttype);
+					if (!td) { continue; }
+					expr = td->bound.get();
+				}
+
+				if (auto base = dynamic_cast<const ast::StructInstType*>(expr)) {
+					if (base->base == target) {
+						candidates.push_back( std::move(cand) );
+						reason.code = NoReason;
+					}
+				}
+			}
+		};
+
+		try {
+			// Attempt 1 : turn (thread&)X into (thread$&)X.__thrd
+			// Clone is purely for memory management
+			std::unique_ptr<const ast::Expr> tech1 { new ast::UntypedMemberExpr(loc, new ast::NameExpr(loc, castExpr->concrete_target.field), castExpr->arg) };
+
+			// don't prune here, since it's guaranteed all alternatives will have the same type
+			finder.find( tech1.get(), ResolvMode::withoutPrune() );
+			pick_alternatives(finder.candidates, false);
+
+			return;
+		} catch(SemanticErrorException & ) {}
+
+		// Fallback : turn (thread&)X into (thread$&)get_thread(X)
+		std::unique_ptr<const ast::Expr> fallback { ast::UntypedExpr::createDeref(loc,  new ast::UntypedExpr(loc, new ast::NameExpr(loc, castExpr->concrete_target.getter), { castExpr->arg })) };
+		// don't prune here, since it's guaranteed all alternatives will have the same type
+		finder.find( fallback.get(), ResolvMode::withoutPrune() );
+
+		pick_alternatives(finder.candidates, true);
+
+		// Whatever happens here, we have no more fallbacks
+	}
+
+	void Finder::postvisit( const ast::UntypedMemberExpr * memberExpr ) {
+		CandidateFinder aggFinder( context, tenv );
+		aggFinder.find( memberExpr->aggregate, ResolvMode::withAdjustment() );
+		for ( CandidateRef & agg : aggFinder.candidates ) {
+			// it's okay for the aggregate expression to have reference type -- cast it to the
+			// base type to treat the aggregate as the referenced value
+			Cost addedCost = Cost::zero;
+			agg->expr = referenceToRvalueConversion( agg->expr, addedCost );
+
+			// find member of the given type
+			if ( auto structInst = agg->expr->result.as< ast::StructInstType >() ) {
+				addAggMembers(
+					structInst, agg->expr, *agg, addedCost, getMemberName( memberExpr ) );
+			} else if ( auto unionInst = agg->expr->result.as< ast::UnionInstType >() ) {
+				addAggMembers(
+					unionInst, agg->expr, *agg, addedCost, getMemberName( memberExpr ) );
+			} else if ( auto tupleType = agg->expr->result.as< ast::TupleType >() ) {
+				addTupleMembers( tupleType, agg->expr, *agg, addedCost, memberExpr->member );
+			}
+		}
+	}
+
+	void Finder::postvisit( const ast::MemberExpr * memberExpr ) {
+		addCandidate( memberExpr, tenv );
+	}
+
+	void Finder::postvisit( const ast::NameExpr * nameExpr ) {
+		std::vector< ast::SymbolTable::IdData > declList;
+		if (!selfFinder.otypeKeys.empty()) {
+			auto kind = ast::SymbolTable::getSpecialFunctionKind(nameExpr->name);
+			assertf(kind != ast::SymbolTable::SpecialFunctionKind::NUMBER_OF_KINDS, "special lookup with non-special target: %s", nameExpr->name.c_str());
+
+			for (auto & otypeKey: selfFinder.otypeKeys) {
+				auto result = symtab.specialLookupId(kind, otypeKey);
+				declList.insert(declList.end(), std::make_move_iterator(result.begin()), std::make_move_iterator(result.end()));
+			}
+		} else {
+			declList = symtab.lookupId( nameExpr->name );
+		}
+		PRINT( std::cerr << "nameExpr is " << nameExpr->name << std::endl; )
+
+		if ( declList.empty() ) return;
+
+		reason.code = NoMatch;
+
+		for ( auto & data : declList ) {
+			Cost cost = Cost::zero;
+			ast::Expr * newExpr = data.combine( nameExpr->location, cost );
+
+			CandidateRef newCand = std::make_shared<Candidate>(
+				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 ";
+				ast::print( std::cerr, data.id );
+				std::cerr << std::endl;
+				std::cerr << "newExpr is ";
+				ast::print( std::cerr, newExpr );
+				std::cerr << std::endl;
+			)
+			newCand->expr = ast::mutate_field(
+				newCand->expr.get(), &ast::Expr::result,
+				renameTyVars( newCand->expr->result ) );
+			// add anonymous member interpretations whenever an aggregate value type is seen
+			// as a name expression
+			addAnonConversions( newCand );
+			candidates.emplace_back( std::move( newCand ) );
+		}
+	}
+
+	void Finder::postvisit( const ast::VariableExpr * variableExpr ) {
+		// not sufficient to just pass `variableExpr` here, type might have changed since
+		// creation
+		addCandidate(
+			new ast::VariableExpr{ variableExpr->location, variableExpr->var }, tenv );
+	}
+
+	void Finder::postvisit( const ast::ConstantExpr * constantExpr ) {
+		addCandidate( constantExpr, tenv );
+	}
+
+	void Finder::postvisit( const ast::SizeofExpr * sizeofExpr ) {
+		if ( sizeofExpr->type ) {
+			addCandidate(
+				new ast::SizeofExpr{
+					sizeofExpr->location, resolveTypeof( sizeofExpr->type, context ) },
+				tenv );
+		} else {
+			// find all candidates for the argument to sizeof
+			CandidateFinder finder( context, tenv );
+			finder.find( sizeofExpr->expr );
+			// find the lowest-cost candidate, otherwise ambiguous
+			CandidateList winners = findMinCost( finder.candidates );
+			if ( winners.size() != 1 ) {
+				SemanticError(
+					sizeofExpr->expr.get(), "Ambiguous expression in sizeof operand: " );
+			}
+			// return the lowest-cost candidate
+			CandidateRef & choice = winners.front();
+			choice->expr = referenceToRvalueConversion( choice->expr, choice->cost );
+			choice->cost = Cost::zero;
+			addCandidate( *choice, new ast::SizeofExpr{ sizeofExpr->location, choice->expr } );
+		}
+	}
+
+	void Finder::postvisit( const ast::AlignofExpr * alignofExpr ) {
+		if ( alignofExpr->type ) {
+			addCandidate(
+				new ast::AlignofExpr{
+					alignofExpr->location, resolveTypeof( alignofExpr->type, context ) },
+				tenv );
+		} else {
+			// find all candidates for the argument to alignof
+			CandidateFinder finder( context, tenv );
+			finder.find( alignofExpr->expr );
+			// find the lowest-cost candidate, otherwise ambiguous
+			CandidateList winners = findMinCost( finder.candidates );
+			if ( winners.size() != 1 ) {
+				SemanticError(
+					alignofExpr->expr.get(), "Ambiguous expression in alignof operand: " );
+			}
+			// return the lowest-cost candidate
+			CandidateRef & choice = winners.front();
+			choice->expr = referenceToRvalueConversion( choice->expr, choice->cost );
+			choice->cost = Cost::zero;
+			addCandidate(
+				*choice, new ast::AlignofExpr{ alignofExpr->location, choice->expr } );
+		}
+	}
+
+	void Finder::postvisit( const ast::UntypedOffsetofExpr * offsetofExpr ) {
+		const ast::BaseInstType * aggInst;
+		if (( aggInst = offsetofExpr->type.as< ast::StructInstType >() )) ;
+		else if (( aggInst = offsetofExpr->type.as< ast::UnionInstType >() )) ;
+		else return;
+
+		for ( const ast::Decl * member : aggInst->lookup( offsetofExpr->member ) ) {
+			auto dwt = strict_dynamic_cast< const ast::DeclWithType * >( member );
+			addCandidate(
+				new ast::OffsetofExpr{ offsetofExpr->location, aggInst, dwt }, tenv );
+		}
+	}
+
+	void Finder::postvisit( const ast::OffsetofExpr * offsetofExpr ) {
+		addCandidate( offsetofExpr, tenv );
+	}
+
+	void Finder::postvisit( const ast::OffsetPackExpr * offsetPackExpr ) {
+		addCandidate( offsetPackExpr, tenv );
+	}
+
+	void Finder::postvisit( const ast::LogicalExpr * logicalExpr ) {
+		CandidateFinder finder1( context, tenv );
+		finder1.find( logicalExpr->arg1, ResolvMode::withAdjustment() );
+		if ( finder1.candidates.empty() ) return;
+
+		CandidateFinder finder2( context, tenv );
+		finder2.find( logicalExpr->arg2, ResolvMode::withAdjustment() );
+		if ( finder2.candidates.empty() ) return;
+
+		reason.code = NoMatch;
+
+		for ( const CandidateRef & r1 : finder1.candidates ) {
+			for ( const CandidateRef & r2 : finder2.candidates ) {
+				ast::TypeEnvironment env{ r1->env };
+				env.simpleCombine( r2->env );
+				ast::OpenVarSet open{ r1->open };
+				mergeOpenVars( open, r2->open );
+				ast::AssertionSet need;
+				mergeAssertionSet( need, r1->need );
+				mergeAssertionSet( need, r2->need );
+
+				addCandidate(
+					new ast::LogicalExpr{
+						logicalExpr->location, r1->expr, r2->expr, logicalExpr->isAnd },
+					std::move( env ), std::move( open ), std::move( need ), r1->cost + r2->cost );
+			}
+		}
+	}
+
+	void Finder::postvisit( const ast::ConditionalExpr * conditionalExpr ) {
+		// candidates for condition
+		CandidateFinder finder1( context, tenv );
+		finder1.find( conditionalExpr->arg1, ResolvMode::withAdjustment() );
+		if ( finder1.candidates.empty() ) return;
+
+		// candidates for true result
+		CandidateFinder finder2( context, tenv );
+		finder2.find( conditionalExpr->arg2, ResolvMode::withAdjustment() );
+		if ( finder2.candidates.empty() ) return;
+
+		// candidates for false result
+		CandidateFinder finder3( context, tenv );
+		finder3.find( conditionalExpr->arg3, ResolvMode::withAdjustment() );
+		if ( finder3.candidates.empty() ) return;
+
+		reason.code = NoMatch;
+
+		for ( const CandidateRef & r1 : finder1.candidates ) {
+			for ( const CandidateRef & r2 : finder2.candidates ) {
+				for ( const CandidateRef & r3 : finder3.candidates ) {
+					ast::TypeEnvironment env{ r1->env };
+					env.simpleCombine( r2->env );
+					env.simpleCombine( r3->env );
+					ast::OpenVarSet open{ r1->open };
+					mergeOpenVars( open, r2->open );
+					mergeOpenVars( open, r3->open );
+					ast::AssertionSet need;
+					mergeAssertionSet( need, r1->need );
+					mergeAssertionSet( need, r2->need );
+					mergeAssertionSet( need, r3->need );
+					ast::AssertionSet have;
+
+					// unify true and false results, then infer parameters to produce new
+					// candidates
+					ast::ptr< ast::Type > common;
+					if (
+						unify(
+							r2->expr->result, r3->expr->result, env, need, have, open, symtab,
+							common )
+					) {
+						// generate typed expression
+						ast::ConditionalExpr * newExpr = new ast::ConditionalExpr{
+							conditionalExpr->location, r1->expr, r2->expr, r3->expr };
+						newExpr->result = common ? common : r2->expr->result;
+						// convert both options to result type
+						Cost cost = r1->cost + r2->cost + r3->cost;
+						newExpr->arg2 = computeExpressionConversionCost(
+							newExpr->arg2, newExpr->result, symtab, env, cost );
+						newExpr->arg3 = computeExpressionConversionCost(
+							newExpr->arg3, newExpr->result, symtab, env, cost );
+						// output candidate
+						CandidateRef newCand = std::make_shared<Candidate>(
+							newExpr, std::move( env ), std::move( open ), std::move( need ), cost );
+						inferParameters( newCand, candidates );
+					}
+				}
+			}
+		}
+	}
+
+	void Finder::postvisit( const ast::CommaExpr * commaExpr ) {
+		ast::TypeEnvironment env{ tenv };
+		ast::ptr< ast::Expr > arg1 = resolveInVoidContext( commaExpr->arg1, context, env );
+
+		CandidateFinder finder2( context, env );
+		finder2.find( commaExpr->arg2, ResolvMode::withAdjustment() );
+
+		for ( const CandidateRef & r2 : finder2.candidates ) {
+			addCandidate( *r2, new ast::CommaExpr{ commaExpr->location, arg1, r2->expr } );
+		}
+	}
+
+	void Finder::postvisit( const ast::ImplicitCopyCtorExpr * ctorExpr ) {
+		addCandidate( ctorExpr, tenv );
+	}
+
+	void Finder::postvisit( const ast::ConstructorExpr * ctorExpr ) {
+		CandidateFinder finder( context, tenv );
+		finder.find( ctorExpr->callExpr, ResolvMode::withoutPrune() );
+		for ( CandidateRef & r : finder.candidates ) {
+			addCandidate( *r, new ast::ConstructorExpr{ ctorExpr->location, r->expr } );
+		}
+	}
+
+	void Finder::postvisit( const ast::RangeExpr * rangeExpr ) {
+		// resolve low and high, accept candidates where low and high types unify
+		CandidateFinder finder1( context, tenv );
+		finder1.find( rangeExpr->low, ResolvMode::withAdjustment() );
+		if ( finder1.candidates.empty() ) return;
+
+		CandidateFinder finder2( context, tenv );
+		finder2.find( rangeExpr->high, ResolvMode::withAdjustment() );
+		if ( finder2.candidates.empty() ) return;
+
+		reason.code = NoMatch;
+
+		for ( const CandidateRef & r1 : finder1.candidates ) {
+			for ( const CandidateRef & r2 : finder2.candidates ) {
+				ast::TypeEnvironment env{ r1->env };
+				env.simpleCombine( r2->env );
+				ast::OpenVarSet open{ r1->open };
+				mergeOpenVars( open, r2->open );
+				ast::AssertionSet need;
+				mergeAssertionSet( need, r1->need );
+				mergeAssertionSet( need, r2->need );
+				ast::AssertionSet have;
+
+				ast::ptr< ast::Type > common;
+				if (
+					unify(
+						r1->expr->result, r2->expr->result, env, need, have, open, symtab,
+						common )
+				) {
+					// generate new expression
+					ast::RangeExpr * newExpr =
+						new ast::RangeExpr{ rangeExpr->location, r1->expr, r2->expr };
+					newExpr->result = common ? common : r1->expr->result;
+					// add candidate
+					CandidateRef newCand = std::make_shared<Candidate>(
+						newExpr, std::move( env ), std::move( open ), std::move( need ),
+						r1->cost + r2->cost );
+					inferParameters( newCand, candidates );
+				}
+			}
+		}
+	}
+
+	void Finder::postvisit( const ast::UntypedTupleExpr * tupleExpr ) {
+		std::vector< CandidateFinder > subCandidates =
+			selfFinder.findSubExprs( tupleExpr->exprs );
+		std::vector< CandidateList > possibilities;
+		combos( subCandidates.begin(), subCandidates.end(), back_inserter( possibilities ) );
+
+		for ( const CandidateList & subs : possibilities ) {
+			std::vector< ast::ptr< ast::Expr > > exprs;
+			exprs.reserve( subs.size() );
+			for ( const CandidateRef & sub : subs ) { exprs.emplace_back( sub->expr ); }
+
+			ast::TypeEnvironment env;
+			ast::OpenVarSet open;
+			ast::AssertionSet need;
+			for ( const CandidateRef & sub : subs ) {
+				env.simpleCombine( sub->env );
+				mergeOpenVars( open, sub->open );
+				mergeAssertionSet( need, sub->need );
+			}
+
+			addCandidate(
+				new ast::TupleExpr{ tupleExpr->location, std::move( exprs ) },
+				std::move( env ), std::move( open ), std::move( need ), sumCost( subs ) );
+		}
+	}
+
+	void Finder::postvisit( const ast::TupleExpr * tupleExpr ) {
+		addCandidate( tupleExpr, tenv );
+	}
+
+	void Finder::postvisit( const ast::TupleIndexExpr * tupleExpr ) {
+		addCandidate( tupleExpr, tenv );
+	}
+
+	void Finder::postvisit( const ast::TupleAssignExpr * tupleExpr ) {
+		addCandidate( tupleExpr, tenv );
+	}
+
+	void Finder::postvisit( const ast::UniqueExpr * unqExpr ) {
+		CandidateFinder finder( context, tenv );
+		finder.find( unqExpr->expr, ResolvMode::withAdjustment() );
+		for ( CandidateRef & r : finder.candidates ) {
+			// ensure that the the id is passed on so that the expressions are "linked"
+			addCandidate( *r, new ast::UniqueExpr{ unqExpr->location, r->expr, unqExpr->id } );
+		}
+	}
+
+	void Finder::postvisit( const ast::StmtExpr * stmtExpr ) {
+		addCandidate( resolveStmtExpr( stmtExpr, context ), tenv );
+	}
+
+	void Finder::postvisit( const ast::UntypedInitExpr * initExpr ) {
+		// handle each option like a cast
+		CandidateList matches;
+		PRINT(
+			std::cerr << "untyped init expr: " << initExpr << std::endl;
+		)
+		// O(n^2) checks of d-types with e-types
+		for ( const ast::InitAlternative & initAlt : initExpr->initAlts ) {
+			// calculate target type
+			const ast::Type * toType = resolveTypeof( initAlt.type, context );
+			toType = adjustExprType( toType, tenv, symtab );
+			// The call to find must occur inside this loop, otherwise polymorphic return
+			// types are not bound to the initialization type, since return type variables are
+			// only open for the duration of resolving the UntypedExpr.
+			CandidateFinder finder( context, tenv, toType );
+			finder.find( initExpr->expr, ResolvMode::withAdjustment() );
+			for ( CandidateRef & cand : finder.candidates ) {
+				if (reason.code == NotFound) reason.code = NoMatch;
+
+				ast::TypeEnvironment env{ cand->env };
+				ast::AssertionSet need( cand->need.begin(), cand->need.end() ), have;
+				ast::OpenVarSet open{ cand->open };
+
+				PRINT(
+					std::cerr << "  @ " << toType << " " << initAlt.designation << std::endl;
+				)
+
+				// It is possible that a cast can throw away some values in a multiply-valued
+				// expression, e.g. cast-to-void, one value to zero. Figure out the prefix of
+				// the subexpression results that are cast directly. The candidate is invalid
+				// if it has fewer results than there are types to cast to.
+				int discardedValues = cand->expr->result->size() - toType->size();
+				if ( discardedValues < 0 ) continue;
+
+				// unification run for side-effects
+				bool canUnify = unify( toType, cand->expr->result, env, need, have, open, symtab );
+				(void) canUnify;
+				Cost thisCost = computeConversionCost( cand->expr->result, toType, cand->expr->get_lvalue(),
+					symtab, env );
+				PRINT(
+					Cost legacyCost = castCost( cand->expr->result, toType, cand->expr->get_lvalue(),
+						symtab, env );
+					std::cerr << "Considering initialization:";
+					std::cerr << std::endl << "  FROM: " << cand->expr->result << std::endl;
+					std::cerr << std::endl << "  TO: "   << toType             << std::endl;
+					std::cerr << std::endl << "  Unification " << (canUnify ? "succeeded" : "failed");
+					std::cerr << std::endl << "  Legacy cost " << legacyCost;
+					std::cerr << std::endl << "  New cost " << thisCost;
+					std::cerr << std::endl;
+				)
+				if ( thisCost != Cost::infinity ) {
+					// count one safe conversion for each value that is thrown away
+					thisCost.incSafe( discardedValues );
+					CandidateRef newCand = std::make_shared<Candidate>(
+						new ast::InitExpr{
+							initExpr->location, restructureCast( cand->expr, toType ),
+							initAlt.designation },
+						std::move(env), std::move( open ), std::move( need ), cand->cost, thisCost );
+					inferParameters( newCand, matches );
+				}
+			}
+		}
+
+		// select first on argument cost, then conversion cost
+		CandidateList minArgCost = findMinCost( matches );
+		promoteCvtCost( minArgCost );
+		candidates = findMinCost( minArgCost );
+	}
+
+	// size_t Finder::traceId = Stats::Heap::new_stacktrace_id("Finder");
+	/// Prunes a list of candidates down to those that have the minimum conversion cost for a given
+	/// return type. Skips ambiguous candidates.
+
+} // anonymous namespace
+
+bool CandidateFinder::pruneCandidates( CandidateList & candidates, CandidateList & out, std::vector<std::string> & errors ) {
+	struct PruneStruct {
+		CandidateRef candidate;
+		bool ambiguous;
+
+		PruneStruct() = default;
+		PruneStruct( const CandidateRef & c ) : candidate( c ), ambiguous( false ) {}
+	};
+
+	// find lowest-cost candidate for each type
+	std::unordered_map< std::string, PruneStruct > selected;
+	// attempt to skip satisfyAssertions on more expensive alternatives if better options have been found
+	std::sort(candidates.begin(), candidates.end(), [](const CandidateRef & x, const CandidateRef & y){return x->cost < y->cost;});
+	for ( CandidateRef & candidate : candidates ) {
+		std::string mangleName;
+		{
+			ast::ptr< ast::Type > newType = candidate->expr->result;
+			assertf(candidate->expr->result, "Result of expression %p for candidate is null", candidate->expr.get());
+			candidate->env.apply( newType );
+			mangleName = Mangle::mangle( newType );
+		}
+
+		auto found = selected.find( mangleName );
+		if (found != selected.end() && found->second.candidate->cost < candidate->cost) {
+			PRINT(
+				std::cerr << "cost " << candidate->cost << " loses to "
+					<< found->second.candidate->cost << std::endl;
+			)
+			continue;
+		}
+
+		// xxx - when do satisfyAssertions produce more than 1 result?
+		// this should only happen when initial result type contains
+		// unbound type parameters, then it should never be pruned by
+		// the previous step, since renameTyVars guarantees the mangled name
+		// is unique.
+		CandidateList satisfied;
+		bool needRecomputeKey = false;
+		if (candidate->need.empty()) {
+			satisfied.emplace_back(candidate);
+		}
+		else {
+			satisfyAssertions(candidate, context.symtab, satisfied, errors);
+			needRecomputeKey = true;
+		}
+
+		for (auto & newCand : satisfied) {
+			// recomputes type key, if satisfyAssertions changed it
+			if (needRecomputeKey)
+			{
+				ast::ptr< ast::Type > newType = newCand->expr->result;
+				assertf(newCand->expr->result, "Result of expression %p for candidate is null", newCand->expr.get());
+				newCand->env.apply( newType );
+				mangleName = Mangle::mangle( newType );
+			}
+			auto found = selected.find( mangleName );
+			if ( found != selected.end() ) {
+				if ( newCand->cost < found->second.candidate->cost ) {
+					PRINT(
+						std::cerr << "cost " << newCand->cost << " beats "
+							<< found->second.candidate->cost << std::endl;
+					)
+
+					found->second = PruneStruct{ newCand };
+				} else if ( newCand->cost == found->second.candidate->cost ) {
+					// if one of the candidates contains a deleted identifier, can pick the other,
+					// since deleted expressions should not be ambiguous if there is another option
+					// that is at least as good
+					if ( findDeletedExpr( newCand->expr ) ) {
+						// do nothing
+						PRINT( std::cerr << "candidate is deleted" << std::endl; )
+					} else if ( findDeletedExpr( found->second.candidate->expr ) ) {
+						PRINT( std::cerr << "current is deleted" << std::endl; )
+						found->second = PruneStruct{ newCand };
+					} else {
+						PRINT( std::cerr << "marking ambiguous" << std::endl; )
+						found->second.ambiguous = true;
+					}
+				} else {
+					// xxx - can satisfyAssertions increase the cost?
+					PRINT(
+						std::cerr << "cost " << newCand->cost << " loses to "
+							<< found->second.candidate->cost << std::endl;
+					)
+				}
+			} else {
+				selected.emplace_hint( found, mangleName, newCand );
+			}
+		}
+	}
+
+	// report unambiguous min-cost candidates
+	// CandidateList out;
+	for ( auto & target : selected ) {
+		if ( target.second.ambiguous ) continue;
+
+		CandidateRef cand = target.second.candidate;
+
+		ast::ptr< ast::Type > newResult = cand->expr->result;
+		cand->env.applyFree( newResult );
+		cand->expr = ast::mutate_field(
+			cand->expr.get(), &ast::Expr::result, std::move( newResult ) );
+
+		out.emplace_back( cand );
+	}
+	// if everything is lost in satisfyAssertions, report the error
+	return !selected.empty();
+}
+
+void CandidateFinder::find( const ast::Expr * expr, ResolvMode mode ) {
+	// Find alternatives for expression
+	ast::Pass<Finder> finder{ *this };
+	expr->accept( finder );
+
+	if ( mode.failFast && candidates.empty() ) {
+		switch(finder.core.reason.code) {
+		case Finder::NotFound:
+			{ SemanticError( expr, "No alternatives for expression " ); break; }
+		case Finder::NoMatch:
+			{ SemanticError( expr, "Invalid application of existing declaration(s) in expression " ); break; }
+		case Finder::ArgsToFew:
+		case Finder::ArgsToMany:
+		case Finder::RetsToFew:
+		case Finder::RetsToMany:
+		case Finder::NoReason:
+		default:
+			{ SemanticError( expr->location, "No reasonable alternatives for expression : reasons unkown" ); }
+		}
+	}
+
+	/*
+	if ( mode.satisfyAssns || mode.prune ) {
+		// trim candidates to just those where the assertions are satisfiable
+		// - necessary pre-requisite to pruning
+		CandidateList satisfied;
+		std::vector< std::string > errors;
+		for ( CandidateRef & candidate : candidates ) {
+			satisfyAssertions( candidate, localSyms, satisfied, errors );
+		}
+
+		// fail early if none such
+		if ( mode.failFast && satisfied.empty() ) {
+			std::ostringstream stream;
+			stream << "No alternatives with satisfiable assertions for " << expr << "\n";
+			for ( const auto& err : errors ) {
+				stream << err;
+			}
+			SemanticError( expr->location, stream.str() );
+		}
+
+		// reset candidates
+		candidates = move( satisfied );
+	}
+	*/
+
+	if ( mode.prune ) {
+		// trim candidates to single best one
+		PRINT(
+			std::cerr << "alternatives before prune:" << std::endl;
+			print( std::cerr, candidates );
+		)
+
+		CandidateList pruned;
+		std::vector<std::string> errors;
+		bool found = pruneCandidates( candidates, pruned, errors );
+
+		if ( mode.failFast && pruned.empty() ) {
+			std::ostringstream stream;
+			if (found) {
+				CandidateList winners = findMinCost( candidates );
+				stream << "Cannot choose between " << winners.size() << " alternatives for "
+					"expression\n";
+				ast::print( stream, expr );
+				stream << " Alternatives are:\n";
+				print( stream, winners, 1 );
+				SemanticError( expr->location, stream.str() );
+			}
+			else {
+				stream << "No alternatives with satisfiable assertions for " << expr << "\n";
+				for ( const auto& err : errors ) {
+					stream << err;
+				}
+				SemanticError( expr->location, stream.str() );
+			}
+		}
+
+		auto oldsize = candidates.size();
+		candidates = std::move( pruned );
+
+		PRINT(
+			std::cerr << "there are " << oldsize << " alternatives before elimination" << std::endl;
+		)
+		PRINT(
+			std::cerr << "there are " << candidates.size() << " alternatives after elimination"
+				<< std::endl;
+		)
+	}
+
+	// adjust types after pruning so that types substituted by pruneAlternatives are correctly
+	// adjusted
+	if ( mode.adjust ) {
+		for ( CandidateRef & r : candidates ) {
+			r->expr = ast::mutate_field(
+				r->expr.get(), &ast::Expr::result,
+				adjustExprType( r->expr->result, r->env, context.symtab ) );
+		}
+	}
+
+	// Central location to handle gcc extension keyword, etc. for all expressions
+	for ( CandidateRef & r : candidates ) {
+		if ( r->expr->extension != expr->extension ) {
+			r->expr.get_and_mutate()->extension = expr->extension;
+		}
+	}
+}
+
+std::vector< CandidateFinder > CandidateFinder::findSubExprs(
+	const std::vector< ast::ptr< ast::Expr > > & xs
+) {
+	std::vector< CandidateFinder > out;
+
+	for ( const auto & x : xs ) {
+		out.emplace_back( context, env );
+		out.back().find( x, ResolvMode::withAdjustment() );
+
+		PRINT(
+			std::cerr << "findSubExprs" << std::endl;
+			print( std::cerr, out.back().candidates );
+		)
+	}
+
+	return out;
+}
+
 const ast::Expr * referenceToRvalueConversion( const ast::Expr * expr, Cost & cost ) {
 	if ( expr->result.as< ast::ReferenceType >() ) {
@@ -64,7 +1942,4 @@
 	return expr;
 }
-
-/// Unique identifier for matching expression resolutions to their requesting expression
-UniqueId globalResnSlot = 0;
 
 Cost computeConversionCost(
@@ -93,1817 +1968,4 @@
 }
 
-namespace {
-	/// First index is which argument, second is which alternative, third is which exploded element
-	using ExplodedArgs_new = std::deque< std::vector< ExplodedArg > >;
-
-	/// Returns a list of alternatives with the minimum cost in the given list
-	CandidateList findMinCost( const CandidateList & candidates ) {
-		CandidateList out;
-		Cost minCost = Cost::infinity;
-		for ( const CandidateRef & r : candidates ) {
-			if ( r->cost < minCost ) {
-				minCost = r->cost;
-				out.clear();
-				out.emplace_back( r );
-			} else if ( r->cost == minCost ) {
-				out.emplace_back( r );
-			}
-		}
-		return out;
-	}
-
-	/// Computes conversion cost for a given expression to a given type
-	const ast::Expr * computeExpressionConversionCost(
-		const ast::Expr * arg, const ast::Type * paramType, const ast::SymbolTable & symtab, const ast::TypeEnvironment & env, Cost & outCost
-	) {
-		Cost convCost = computeConversionCost(
-				arg->result, paramType, arg->get_lvalue(), symtab, env );
-		outCost += convCost;
-
-		// If there is a non-zero conversion cost, ignoring poly cost, then the expression requires
-		// conversion. Ignore poly cost for now, since this requires resolution of the cast to
-		// infer parameters and this does not currently work for the reason stated below
-		Cost tmpCost = convCost;
-		tmpCost.incPoly( -tmpCost.get_polyCost() );
-		if ( tmpCost != Cost::zero ) {
-			ast::ptr< ast::Type > newType = paramType;
-			env.apply( newType );
-			return new ast::CastExpr{ arg, newType };
-
-			// xxx - *should* be able to resolve this cast, but at the moment pointers are not
-			// castable to zero_t, but are implicitly convertible. This is clearly inconsistent,
-			// once this is fixed it should be possible to resolve the cast.
-			// xxx - this isn't working, it appears because type1 (parameter) is seen as widenable,
-			// but it shouldn't be because this makes the conversion from DT* to DT* since
-			// commontype(zero_t, DT*) is DT*, rather than nothing
-
-			// CandidateFinder finder{ symtab, env };
-			// finder.find( arg, ResolvMode::withAdjustment() );
-			// assertf( finder.candidates.size() > 0,
-			// 	"Somehow castable expression failed to find alternatives." );
-			// assertf( finder.candidates.size() == 1,
-			// 	"Somehow got multiple alternatives for known cast expression." );
-			// return finder.candidates.front()->expr;
-		}
-
-		return arg;
-	}
-
-	/// Computes conversion cost for a given candidate
-	Cost computeApplicationConversionCost(
-		CandidateRef cand, const ast::SymbolTable & symtab
-	) {
-		auto appExpr = cand->expr.strict_as< ast::ApplicationExpr >();
-		auto pointer = appExpr->func->result.strict_as< ast::PointerType >();
-		auto function = pointer->base.strict_as< ast::FunctionType >();
-
-		Cost convCost = Cost::zero;
-		const auto & params = function->params;
-		auto param = params.begin();
-		auto & args = appExpr->args;
-
-		for ( unsigned i = 0; i < args.size(); ++i ) {
-			const ast::Type * argType = args[i]->result;
-			PRINT(
-				std::cerr << "arg expression:" << std::endl;
-				ast::print( std::cerr, args[i], 2 );
-				std::cerr << "--- results are" << std::endl;
-				ast::print( std::cerr, argType, 2 );
-			)
-
-			if ( param == params.end() ) {
-				if ( function->isVarArgs ) {
-					convCost.incUnsafe();
-					PRINT( std::cerr << "end of params with varargs function: inc unsafe: "
-						<< convCost << std::endl; ; )
-					// convert reference-typed expressions into value-typed expressions
-					cand->expr = ast::mutate_field_index(
-						appExpr, &ast::ApplicationExpr::args, i,
-						referenceToRvalueConversion( args[i], convCost ) );
-					continue;
-				} else return Cost::infinity;
-			}
-
-			if ( auto def = args[i].as< ast::DefaultArgExpr >() ) {
-				// Default arguments should be free - don't include conversion cost.
-				// Unwrap them here because they are not relevant to the rest of the system
-				cand->expr = ast::mutate_field_index(
-					appExpr, &ast::ApplicationExpr::args, i, def->expr );
-				++param;
-				continue;
-			}
-
-			// mark conversion cost and also specialization cost of param type
-			// const ast::Type * paramType = (*param)->get_type();
-			cand->expr = ast::mutate_field_index(
-				appExpr, &ast::ApplicationExpr::args, i,
-				computeExpressionConversionCost(
-					args[i], *param, symtab, cand->env, convCost ) );
-			convCost.decSpec( specCost( *param ) );
-			++param;  // can't be in for-loop update because of the continue
-		}
-
-		if ( param != params.end() ) return Cost::infinity;
-
-		// specialization cost of return types can't be accounted for directly, it disables
-		// otherwise-identical calls, like this example based on auto-newline in the I/O lib:
-		//
-		//   forall(otype OS) {
-		//     void ?|?(OS&, int);  // with newline
-		//     OS&  ?|?(OS&, int);  // no newline, always chosen due to more specialization
-		//   }
-
-		// mark type variable and specialization cost of forall clause
-		convCost.incVar( function->forall.size() );
-		convCost.decSpec( function->assertions.size() );
-
-		return convCost;
-	}
-
-	void makeUnifiableVars(
-		const ast::FunctionType * type, ast::OpenVarSet & unifiableVars,
-		ast::AssertionSet & need
-	) {
-		for ( auto & tyvar : type->forall ) {
-			unifiableVars[ *tyvar ] = ast::TypeData{ tyvar->base };
-		}
-		for ( auto & assn : type->assertions ) {
-			need[ assn ].isUsed = true;
-		}
-	}
-
-	/// Gets a default value from an initializer, nullptr if not present
-	const ast::ConstantExpr * getDefaultValue( const ast::Init * init ) {
-		if ( auto si = dynamic_cast< const ast::SingleInit * >( init ) ) {
-			if ( auto ce = si->value.as< ast::CastExpr >() ) {
-				return ce->arg.as< ast::ConstantExpr >();
-			} else {
-				return si->value.as< ast::ConstantExpr >();
-			}
-		}
-		return nullptr;
-	}
-
-	/// State to iteratively build a match of parameter expressions to arguments
-	struct ArgPack {
-		std::size_t parent;          ///< Index of parent pack
-		ast::ptr< ast::Expr > expr;  ///< The argument stored here
-		Cost cost;                   ///< The cost of this argument
-		ast::TypeEnvironment env;    ///< Environment for this pack
-		ast::AssertionSet need;      ///< Assertions outstanding for this pack
-		ast::AssertionSet have;      ///< Assertions found for this pack
-		ast::OpenVarSet open;        ///< Open variables for this pack
-		unsigned nextArg;            ///< Index of next argument in arguments list
-		unsigned tupleStart;         ///< Number of tuples that start at this index
-		unsigned nextExpl;           ///< Index of next exploded element
-		unsigned explAlt;            ///< Index of alternative for nextExpl > 0
-
-		ArgPack()
-		: parent( 0 ), expr(), cost( Cost::zero ), env(), need(), have(), open(), nextArg( 0 ),
-		  tupleStart( 0 ), nextExpl( 0 ), explAlt( 0 ) {}
-
-		ArgPack(
-			const ast::TypeEnvironment & env, const ast::AssertionSet & need,
-			const ast::AssertionSet & have, const ast::OpenVarSet & open )
-		: parent( 0 ), expr(), cost( Cost::zero ), env( env ), need( need ), have( have ),
-		  open( open ), nextArg( 0 ), tupleStart( 0 ), nextExpl( 0 ), explAlt( 0 ) {}
-
-		ArgPack(
-			std::size_t parent, const ast::Expr * expr, ast::TypeEnvironment && env,
-			ast::AssertionSet && need, ast::AssertionSet && have, ast::OpenVarSet && open,
-			unsigned nextArg, unsigned tupleStart = 0, Cost cost = Cost::zero,
-			unsigned nextExpl = 0, unsigned explAlt = 0 )
-		: parent(parent), expr( expr ), cost( cost ), env( std::move( env ) ), need( std::move( need ) ),
-		  have( std::move( have ) ), open( std::move( open ) ), nextArg( nextArg ), tupleStart( tupleStart ),
-		  nextExpl( nextExpl ), explAlt( explAlt ) {}
-
-		ArgPack(
-			const ArgPack & o, ast::TypeEnvironment && env, ast::AssertionSet && need,
-			ast::AssertionSet && have, ast::OpenVarSet && open, unsigned nextArg, Cost added )
-		: parent( o.parent ), expr( o.expr ), cost( o.cost + added ), env( std::move( env ) ),
-		  need( std::move( need ) ), have( std::move( have ) ), open( std::move( open ) ), nextArg( nextArg ),
-		  tupleStart( o.tupleStart ), nextExpl( 0 ), explAlt( 0 ) {}
-
-		/// true if this pack is in the middle of an exploded argument
-		bool hasExpl() const { return nextExpl > 0; }
-
-		/// Gets the list of exploded candidates for this pack
-		const ExplodedArg & getExpl( const ExplodedArgs_new & args ) const {
-			return args[ nextArg-1 ][ explAlt ];
-		}
-
-		/// Ends a tuple expression, consolidating the appropriate args
-		void endTuple( const std::vector< ArgPack > & packs ) {
-			// add all expressions in tuple to list, summing cost
-			std::deque< const ast::Expr * > exprs;
-			const ArgPack * pack = this;
-			if ( expr ) { exprs.emplace_front( expr ); }
-			while ( pack->tupleStart == 0 ) {
-				pack = &packs[pack->parent];
-				exprs.emplace_front( pack->expr );
-				cost += pack->cost;
-			}
-			// reset pack to appropriate tuple
-			std::vector< ast::ptr< ast::Expr > > exprv( exprs.begin(), exprs.end() );
-			expr = new ast::TupleExpr{ expr->location, std::move( exprv ) };
-			tupleStart = pack->tupleStart - 1;
-			parent = pack->parent;
-		}
-	};
-
-	/// Instantiates an argument to match a parameter, returns false if no matching results left
-	bool instantiateArgument(
-		const ast::Type * paramType, const ast::Init * init, const ExplodedArgs_new & args,
-		std::vector< ArgPack > & results, std::size_t & genStart, const ast::SymbolTable & symtab,
-		unsigned nTuples = 0
-	) {
-		if ( auto tupleType = dynamic_cast< const ast::TupleType * >( paramType ) ) {
-			// paramType is a TupleType -- group args into a TupleExpr
-			++nTuples;
-			for ( const ast::Type * type : *tupleType ) {
-				// xxx - dropping initializer changes behaviour from previous, but seems correct
-				// ^^^ need to handle the case where a tuple has a default argument
-				if ( ! instantiateArgument(
-					type, nullptr, args, results, genStart, symtab, nTuples ) ) return false;
-				nTuples = 0;
-			}
-			// re-constitute tuples for final generation
-			for ( auto i = genStart; i < results.size(); ++i ) {
-				results[i].endTuple( results );
-			}
-			return true;
-		} else if ( const ast::TypeInstType * ttype = Tuples::isTtype( paramType ) ) {
-			// paramType is a ttype, consumes all remaining arguments
-
-			// completed tuples; will be spliced to end of results to finish
-			std::vector< ArgPack > finalResults{};
-
-			// iterate until all results completed
-			std::size_t genEnd;
-			++nTuples;
-			do {
-				genEnd = results.size();
-
-				// add another argument to results
-				for ( std::size_t i = genStart; i < genEnd; ++i ) {
-					unsigned nextArg = results[i].nextArg;
-
-					// use next element of exploded tuple if present
-					if ( results[i].hasExpl() ) {
-						const ExplodedArg & expl = results[i].getExpl( args );
-
-						unsigned nextExpl = results[i].nextExpl + 1;
-						if ( nextExpl == expl.exprs.size() ) { nextExpl = 0; }
-
-						results.emplace_back(
-							i, expl.exprs[ results[i].nextExpl ], copy( results[i].env ),
-							copy( results[i].need ), copy( results[i].have ),
-							copy( results[i].open ), nextArg, nTuples, Cost::zero, nextExpl,
-							results[i].explAlt );
-
-						continue;
-					}
-
-					// finish result when out of arguments
-					if ( nextArg >= args.size() ) {
-						ArgPack newResult{
-							results[i].env, results[i].need, results[i].have, results[i].open };
-						newResult.nextArg = nextArg;
-						const ast::Type * argType = nullptr;
-
-						if ( nTuples > 0 || ! results[i].expr ) {
-							// first iteration or no expression to clone,
-							// push empty tuple expression
-							newResult.parent = i;
-							newResult.expr = new ast::TupleExpr{ CodeLocation{}, {} };
-							argType = newResult.expr->result;
-						} else {
-							// clone result to collect tuple
-							newResult.parent = results[i].parent;
-							newResult.cost = results[i].cost;
-							newResult.tupleStart = results[i].tupleStart;
-							newResult.expr = results[i].expr;
-							argType = newResult.expr->result;
-
-							if ( results[i].tupleStart > 0 && Tuples::isTtype( argType ) ) {
-								// the case where a ttype value is passed directly is special,
-								// e.g. for argument forwarding purposes
-								// xxx - what if passing multiple arguments, last of which is
-								//       ttype?
-								// xxx - what would happen if unify was changed so that unifying
-								//       tuple
-								// types flattened both before unifying lists? then pass in
-								// TupleType (ttype) below.
-								--newResult.tupleStart;
-							} else {
-								// collapse leftover arguments into tuple
-								newResult.endTuple( results );
-								argType = newResult.expr->result;
-							}
-						}
-
-						// check unification for ttype before adding to final
-						if (
-							unify(
-								ttype, argType, newResult.env, newResult.need, newResult.have,
-								newResult.open, symtab )
-						) {
-							finalResults.emplace_back( std::move( newResult ) );
-						}
-
-						continue;
-					}
-
-					// add each possible next argument
-					for ( std::size_t j = 0; j < args[nextArg].size(); ++j ) {
-						const ExplodedArg & expl = args[nextArg][j];
-
-						// fresh copies of parent parameters for this iteration
-						ast::TypeEnvironment env = results[i].env;
-						ast::OpenVarSet open = results[i].open;
-
-						env.addActual( expl.env, open );
-
-						// skip empty tuple arguments by (nearly) cloning parent into next gen
-						if ( expl.exprs.empty() ) {
-							results.emplace_back(
-								results[i], std::move( env ), copy( results[i].need ),
-								copy( results[i].have ), std::move( open ), nextArg + 1, expl.cost );
-
-							continue;
-						}
-
-						// add new result
-						results.emplace_back(
-							i, expl.exprs.front(), std::move( env ), copy( results[i].need ),
-							copy( results[i].have ), std::move( open ), nextArg + 1, nTuples,
-							expl.cost, expl.exprs.size() == 1 ? 0 : 1, j );
-					}
-				}
-
-				// reset for next round
-				genStart = genEnd;
-				nTuples = 0;
-			} while ( genEnd != results.size() );
-
-			// splice final results onto results
-			for ( std::size_t i = 0; i < finalResults.size(); ++i ) {
-				results.emplace_back( std::move( finalResults[i] ) );
-			}
-			return ! finalResults.empty();
-		}
-
-		// iterate each current subresult
-		std::size_t genEnd = results.size();
-		for ( std::size_t i = genStart; i < genEnd; ++i ) {
-			unsigned nextArg = results[i].nextArg;
-
-			// use remainder of exploded tuple if present
-			if ( results[i].hasExpl() ) {
-				const ExplodedArg & expl = results[i].getExpl( args );
-				const ast::Expr * expr = expl.exprs[ results[i].nextExpl ];
-
-				ast::TypeEnvironment env = results[i].env;
-				ast::AssertionSet need = results[i].need, have = results[i].have;
-				ast::OpenVarSet open = results[i].open;
-
-				const ast::Type * argType = expr->result;
-
-				PRINT(
-					std::cerr << "param type is ";
-					ast::print( std::cerr, paramType );
-					std::cerr << std::endl << "arg type is ";
-					ast::print( std::cerr, argType );
-					std::cerr << std::endl;
-				)
-
-				if ( unify( paramType, argType, env, need, have, open, symtab ) ) {
-					unsigned nextExpl = results[i].nextExpl + 1;
-					if ( nextExpl == expl.exprs.size() ) { nextExpl = 0; }
-
-					results.emplace_back(
-						i, expr, std::move( env ), std::move( need ), std::move( have ), std::move( open ), nextArg,
-						nTuples, Cost::zero, nextExpl, results[i].explAlt );
-				}
-
-				continue;
-			}
-
-			// use default initializers if out of arguments
-			if ( nextArg >= args.size() ) {
-				if ( const ast::ConstantExpr * cnst = getDefaultValue( init ) ) {
-					ast::TypeEnvironment env = results[i].env;
-					ast::AssertionSet need = results[i].need, have = results[i].have;
-					ast::OpenVarSet open = results[i].open;
-
-					if ( unify( paramType, cnst->result, env, need, have, open, symtab ) ) {
-						results.emplace_back(
-							i, new ast::DefaultArgExpr{ cnst->location, cnst }, std::move( env ),
-							std::move( need ), std::move( have ), std::move( open ), nextArg, nTuples );
-					}
-				}
-
-				continue;
-			}
-
-			// Check each possible next argument
-			for ( std::size_t j = 0; j < args[nextArg].size(); ++j ) {
-				const ExplodedArg & expl = args[nextArg][j];
-
-				// fresh copies of parent parameters for this iteration
-				ast::TypeEnvironment env = results[i].env;
-				ast::AssertionSet need = results[i].need, have = results[i].have;
-				ast::OpenVarSet open = results[i].open;
-
-				env.addActual( expl.env, open );
-
-				// skip empty tuple arguments by (nearly) cloning parent into next gen
-				if ( expl.exprs.empty() ) {
-					results.emplace_back(
-						results[i], std::move( env ), std::move( need ), std::move( have ), std::move( open ),
-						nextArg + 1, expl.cost );
-
-					continue;
-				}
-
-				// consider only first exploded arg
-				const ast::Expr * expr = expl.exprs.front();
-				const ast::Type * argType = expr->result;
-
-				PRINT(
-					std::cerr << "param type is ";
-					ast::print( std::cerr, paramType );
-					std::cerr << std::endl << "arg type is ";
-					ast::print( std::cerr, argType );
-					std::cerr << std::endl;
-				)
-
-				// attempt to unify types
-				if ( unify( paramType, argType, env, need, have, open, symtab ) ) {
-					// add new result
-					results.emplace_back(
-						i, expr, std::move( env ), std::move( need ), std::move( have ), std::move( open ),
-						nextArg + 1, nTuples, expl.cost, expl.exprs.size() == 1 ? 0 : 1, j );
-				}
-			}
-		}
-
-		// reset for next parameter
-		genStart = genEnd;
-
-		return genEnd != results.size();  // were any new results added?
-	}
-
-	/// Generate a cast expression from `arg` to `toType`
-	const ast::Expr * restructureCast(
-		ast::ptr< ast::Expr > & arg, const ast::Type * toType, ast::GeneratedFlag isGenerated = ast::GeneratedCast
-	) {
-		if (
-			arg->result->size() > 1
-			&& ! toType->isVoid()
-			&& ! dynamic_cast< const ast::ReferenceType * >( toType )
-		) {
-			// Argument is a tuple and the target type is neither void nor a reference. Cast each
-			// member of the tuple to its corresponding target type, producing the tuple of those
-			// cast expressions. If there are more components of the tuple than components in the
-			// target type, then excess components do not come out in the result expression (but
-			// UniqueExpr ensures that the side effects will still be produced)
-			if ( Tuples::maybeImpureIgnoreUnique( arg ) ) {
-				// expressions which may contain side effects require a single unique instance of
-				// the expression
-				arg = new ast::UniqueExpr{ arg->location, arg };
-			}
-			std::vector< ast::ptr< ast::Expr > > components;
-			for ( unsigned i = 0; i < toType->size(); ++i ) {
-				// cast each component
-				ast::ptr< ast::Expr > idx = new ast::TupleIndexExpr{ arg->location, arg, i };
-				components.emplace_back(
-					restructureCast( idx, toType->getComponent( i ), isGenerated ) );
-			}
-			return new ast::TupleExpr{ arg->location, std::move( components ) };
-		} else {
-			// handle normally
-			return new ast::CastExpr{ arg->location, arg, toType, isGenerated };
-		}
-	}
-
-	/// Gets the name from an untyped member expression (must be NameExpr)
-	const std::string & getMemberName( const ast::UntypedMemberExpr * memberExpr ) {
-		if ( memberExpr->member.as< ast::ConstantExpr >() ) {
-			SemanticError( memberExpr, "Indexed access to struct fields unsupported: " );
-		}
-
-		return memberExpr->member.strict_as< ast::NameExpr >()->name;
-	}
-
-	/// Actually visits expressions to find their candidate interpretations
-	class Finder final : public ast::WithShortCircuiting {
-		const ResolveContext & context;
-		const ast::SymbolTable & symtab;
-	public:
-		// static size_t traceId;
-		CandidateFinder & selfFinder;
-		CandidateList & candidates;
-		const ast::TypeEnvironment & tenv;
-		ast::ptr< ast::Type > & targetType;
-
-		enum Errors {
-			NotFound,
-			NoMatch,
-			ArgsToFew,
-			ArgsToMany,
-			RetsToFew,
-			RetsToMany,
-			NoReason
-		};
-
-		struct {
-			Errors code = NotFound;
-		} reason;
-
-		Finder( CandidateFinder & f )
-		: context( f.context ), symtab( context.symtab ), selfFinder( f ),
-		  candidates( f.candidates ), tenv( f.env ), targetType( f.targetType ) {}
-
-		void previsit( const ast::Node * ) { visit_children = false; }
-
-		/// Convenience to add candidate to list
-		template<typename... Args>
-		void addCandidate( Args &&... args ) {
-			candidates.emplace_back( new Candidate{ std::forward<Args>( args )... } );
-			reason.code = NoReason;
-		}
-
-		void postvisit( const ast::ApplicationExpr * applicationExpr ) {
-			addCandidate( applicationExpr, tenv );
-		}
-
-		/// Set up candidate assertions for inference
-		void inferParameters( CandidateRef & newCand, CandidateList & out ) {
-			// Set need bindings for any unbound assertions
-			UniqueId crntResnSlot = 0; // matching ID for this expression's assertions
-			for ( auto & assn : newCand->need ) {
-				// skip already-matched assertions
-				if ( assn.second.resnSlot != 0 ) continue;
-				// assign slot for expression if needed
-				if ( crntResnSlot == 0 ) { crntResnSlot = ++globalResnSlot; }
-				// fix slot to assertion
-				assn.second.resnSlot = crntResnSlot;
-			}
-			// pair slot to expression
-			if ( crntResnSlot != 0 ) {
-				newCand->expr.get_and_mutate()->inferred.resnSlots().emplace_back( crntResnSlot );
-			}
-
-			// add to output list; assertion satisfaction will occur later
-			out.emplace_back( newCand );
-		}
-
-		/// Completes a function candidate with arguments located
-		void validateFunctionCandidate(
-			const CandidateRef & func, ArgPack & result, const std::vector< ArgPack > & results,
-			CandidateList & out
-		) {
-			ast::ApplicationExpr * appExpr =
-				new ast::ApplicationExpr{ func->expr->location, func->expr };
-			// sum cost and accumulate arguments
-			std::deque< const ast::Expr * > args;
-			Cost cost = func->cost;
-			const ArgPack * pack = &result;
-			while ( pack->expr ) {
-				args.emplace_front( pack->expr );
-				cost += pack->cost;
-				pack = &results[pack->parent];
-			}
-			std::vector< ast::ptr< ast::Expr > > vargs( args.begin(), args.end() );
-			appExpr->args = std::move( vargs );
-			// build and validate new candidate
-			auto newCand =
-				std::make_shared<Candidate>( appExpr, result.env, result.open, result.need, cost );
-			PRINT(
-				std::cerr << "instantiate function success: " << appExpr << std::endl;
-				std::cerr << "need assertions:" << std::endl;
-				ast::print( std::cerr, result.need, 2 );
-			)
-			inferParameters( newCand, out );
-		}
-
-		/// Builds a list of candidates for a function, storing them in out
-		void makeFunctionCandidates(
-			const CandidateRef & func, const ast::FunctionType * funcType,
-			const ExplodedArgs_new & args, CandidateList & out
-		) {
-			ast::OpenVarSet funcOpen;
-			ast::AssertionSet funcNeed, funcHave;
-			ast::TypeEnvironment funcEnv{ func->env };
-			makeUnifiableVars( funcType, funcOpen, funcNeed );
-			// add all type variables as open variables now so that those not used in the
-			// parameter list are still considered open
-			funcEnv.add( funcType->forall );
-
-			if ( targetType && ! targetType->isVoid() && ! funcType->returns.empty() ) {
-				// attempt to narrow based on expected target type
-				const ast::Type * returnType = funcType->returns.front();
-				if ( ! unify(
-					returnType, targetType, funcEnv, funcNeed, funcHave, funcOpen, symtab )
-				) {
-					// unification failed, do not pursue this candidate
-					return;
-				}
-			}
-
-			// iteratively build matches, one parameter at a time
-			std::vector< ArgPack > results;
-			results.emplace_back( funcEnv, funcNeed, funcHave, funcOpen );
-			std::size_t genStart = 0;
-
-			// 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(
-					param, nullptr, args, results, genStart, symtab ) ) return;
-			}
-
-			endMatch:
-			if ( funcType->isVarArgs ) {
-				// append any unused arguments to vararg pack
-				std::size_t genEnd;
-				do {
-					genEnd = results.size();
-
-					// iterate results
-					for ( std::size_t i = genStart; i < genEnd; ++i ) {
-						unsigned nextArg = results[i].nextArg;
-
-						// use remainder of exploded tuple if present
-						if ( results[i].hasExpl() ) {
-							const ExplodedArg & expl = results[i].getExpl( args );
-
-							unsigned nextExpl = results[i].nextExpl + 1;
-							if ( nextExpl == expl.exprs.size() ) { nextExpl = 0; }
-
-							results.emplace_back(
-								i, expl.exprs[ results[i].nextExpl ], copy( results[i].env ),
-								copy( results[i].need ), copy( results[i].have ),
-								copy( results[i].open ), nextArg, 0, Cost::zero, nextExpl,
-								results[i].explAlt );
-
-							continue;
-						}
-
-						// finish result when out of arguments
-						if ( nextArg >= args.size() ) {
-							validateFunctionCandidate( func, results[i], results, out );
-
-							continue;
-						}
-
-						// add each possible next argument
-						for ( std::size_t j = 0; j < args[nextArg].size(); ++j ) {
-							const ExplodedArg & expl = args[nextArg][j];
-
-							// fresh copies of parent parameters for this iteration
-							ast::TypeEnvironment env = results[i].env;
-							ast::OpenVarSet open = results[i].open;
-
-							env.addActual( expl.env, open );
-
-							// skip empty tuple arguments by (nearly) cloning parent into next gen
-							if ( expl.exprs.empty() ) {
-								results.emplace_back(
-									results[i], std::move( env ), copy( results[i].need ),
-									copy( results[i].have ), std::move( open ), nextArg + 1,
-									expl.cost );
-
-								continue;
-							}
-
-							// add new result
-							results.emplace_back(
-								i, expl.exprs.front(), std::move( env ), copy( results[i].need ),
-								copy( results[i].have ), std::move( open ), nextArg + 1, 0, expl.cost,
-								expl.exprs.size() == 1 ? 0 : 1, j );
-						}
-					}
-
-					genStart = genEnd;
-				} while( genEnd != results.size() );
-			} else {
-				// filter out the results that don't use all the arguments
-				for ( std::size_t i = genStart; i < results.size(); ++i ) {
-					ArgPack & result = results[i];
-					if ( ! result.hasExpl() && result.nextArg >= args.size() ) {
-						validateFunctionCandidate( func, result, results, out );
-					}
-				}
-			}
-		}
-
-		/// Adds implicit struct-conversions to the alternative list
-		void addAnonConversions( const CandidateRef & cand ) {
-			// adds anonymous member interpretations whenever an aggregate value type is seen.
-			// it's okay for the aggregate expression to have reference type -- cast it to the
-			// base type to treat the aggregate as the referenced value
-			ast::ptr< ast::Expr > aggrExpr( cand->expr );
-			ast::ptr< ast::Type > & aggrType = aggrExpr.get_and_mutate()->result;
-			cand->env.apply( aggrType );
-
-			if ( aggrType.as< ast::ReferenceType >() ) {
-				aggrExpr = new ast::CastExpr{ aggrExpr, aggrType->stripReferences() };
-			}
-
-			if ( auto structInst = aggrExpr->result.as< ast::StructInstType >() ) {
-				addAggMembers( structInst, aggrExpr, *cand, Cost::safe, "" );
-			} else if ( auto unionInst = aggrExpr->result.as< ast::UnionInstType >() ) {
-				addAggMembers( unionInst, aggrExpr, *cand, Cost::safe, "" );
-			}
-		}
-
-		/// Adds aggregate member interpretations
-		void addAggMembers(
-			const ast::BaseInstType * aggrInst, const ast::Expr * expr,
-			const Candidate & cand, const Cost & addedCost, const std::string & name
-		) {
-			for ( const ast::Decl * decl : aggrInst->lookup( name ) ) {
-				auto dwt = strict_dynamic_cast< const ast::DeclWithType * >( decl );
-				CandidateRef newCand = std::make_shared<Candidate>(
-					cand, new ast::MemberExpr{ expr->location, dwt, expr }, addedCost );
-				// add anonymous member interpretations whenever an aggregate value type is seen
-				// as a member expression
-				addAnonConversions( newCand );
-				candidates.emplace_back( std::move( newCand ) );
-			}
-		}
-
-		/// Adds tuple member interpretations
-		void addTupleMembers(
-			const ast::TupleType * tupleType, const ast::Expr * expr, const Candidate & cand,
-			const Cost & addedCost, const ast::Expr * member
-		) {
-			if ( auto constantExpr = dynamic_cast< const ast::ConstantExpr * >( member ) ) {
-				// get the value of the constant expression as an int, must be between 0 and the
-				// length of the tuple to have meaning
-				long long val = constantExpr->intValue();
-				if ( val >= 0 && (unsigned long long)val < tupleType->size() ) {
-					addCandidate(
-						cand, new ast::TupleIndexExpr{ expr->location, expr, (unsigned)val },
-						addedCost );
-				}
-			}
-		}
-
-		void postvisit( const ast::UntypedExpr * untypedExpr ) {
-			std::vector< CandidateFinder > argCandidates =
-				selfFinder.findSubExprs( untypedExpr->args );
-
-			// take care of possible tuple assignments
-			// if not tuple assignment, handled as normal function call
-			Tuples::handleTupleAssignment( selfFinder, untypedExpr, argCandidates );
-
-			CandidateFinder funcFinder( context, tenv );
-			if (auto nameExpr = untypedExpr->func.as<ast::NameExpr>()) {
-				auto kind = ast::SymbolTable::getSpecialFunctionKind(nameExpr->name);
-				if (kind != ast::SymbolTable::SpecialFunctionKind::NUMBER_OF_KINDS) {
-					assertf(!argCandidates.empty(), "special function call without argument");
-					for (auto & firstArgCand: argCandidates[0]) {
-						ast::ptr<ast::Type> argType = firstArgCand->expr->result;
-						firstArgCand->env.apply(argType);
-						// strip references
-						// xxx - is this correct?
-						while (argType.as<ast::ReferenceType>()) argType = argType.as<ast::ReferenceType>()->base;
-
-						// convert 1-tuple to plain type
-						if (auto tuple = argType.as<ast::TupleType>()) {
-							if (tuple->size() == 1) {
-								argType = tuple->types[0];
-							}
-						}
-
-						// if argType is an unbound type parameter, all special functions need to be searched.
-						if (isUnboundType(argType)) {
-							funcFinder.otypeKeys.clear();
-							break;
-						}
-
-						if (argType.as<ast::PointerType>()) funcFinder.otypeKeys.insert(Mangle::Encoding::pointer);						
-						// else if (const ast::EnumInstType * enumInst = argType.as<ast::EnumInstType>()) {
-						// 	const ast::EnumDecl * enumDecl = enumInst->base; // Here
-						// 	if ( const ast::Type* enumType = enumDecl->base ) {
-						// 		// instance of enum (T) is a instance of type (T) 
-						// 		funcFinder.otypeKeys.insert(Mangle::mangle(enumType, Mangle::NoGenericParams | Mangle::Type));
-						// 	} else {
-						// 		// instance of an untyped enum is techically int
-						// 		funcFinder.otypeKeys.insert(Mangle::mangle(enumDecl, Mangle::NoGenericParams | Mangle::Type));
-						// 	}
-						// }
-						else funcFinder.otypeKeys.insert(Mangle::mangle(argType, Mangle::NoGenericParams | Mangle::Type));
-					}
-				}
-			}
-			// if candidates are already produced, do not fail
-			// xxx - is it possible that handleTupleAssignment and main finder both produce candidates?
-			// this means there exists ctor/assign functions with a tuple as first parameter.
-			ResolvMode mode = {
-				true, // adjust
-				!untypedExpr->func.as<ast::NameExpr>(), // prune if not calling by name
-				selfFinder.candidates.empty() // failfast if other options are not found
-			};
-			funcFinder.find( untypedExpr->func, mode );
-			// short-circuit if no candidates
-			// if ( funcFinder.candidates.empty() ) return;
-
-			reason.code = NoMatch;
-
-			// find function operators
-			ast::ptr< ast::Expr > opExpr = new ast::NameExpr{ untypedExpr->location, "?()" }; // ??? why not ?{}
-			CandidateFinder opFinder( context, tenv );
-			// okay if there aren't any function operations
-			opFinder.find( opExpr, ResolvMode::withoutFailFast() );
-			PRINT(
-				std::cerr << "known function ops:" << std::endl;
-				print( std::cerr, opFinder.candidates, 1 );
-			)
-
-			// pre-explode arguments
-			ExplodedArgs_new argExpansions;
-			for ( const CandidateFinder & args : argCandidates ) {
-				argExpansions.emplace_back();
-				auto & argE = argExpansions.back();
-				for ( const CandidateRef & arg : args ) { argE.emplace_back( *arg, symtab ); }
-			}
-
-			// Find function matches
-			CandidateList found;
-			SemanticErrorException errors;
-			for ( CandidateRef & func : funcFinder ) {
-				try {
-					PRINT(
-						std::cerr << "working on alternative:" << std::endl;
-						print( std::cerr, *func, 2 );
-					)
-
-					// check if the type is a pointer to function
-					const ast::Type * funcResult = func->expr->result->stripReferences();
-					if ( auto pointer = dynamic_cast< const ast::PointerType * >( funcResult ) ) {
-						if ( auto function = pointer->base.as< ast::FunctionType >() ) {
-							CandidateRef newFunc{ new Candidate{ *func } };
-							newFunc->expr =
-								referenceToRvalueConversion( newFunc->expr, newFunc->cost );
-							makeFunctionCandidates( newFunc, function, argExpansions, found );
-						}
-					} else if (
-						auto inst = dynamic_cast< const ast::TypeInstType * >( funcResult )
-					) {
-						if ( const ast::EqvClass * clz = func->env.lookup( *inst ) ) {
-							if ( auto function = clz->bound.as< ast::FunctionType >() ) {
-								CandidateRef newFunc{ new Candidate{ *func } };
-								newFunc->expr =
-									referenceToRvalueConversion( newFunc->expr, newFunc->cost );
-								makeFunctionCandidates( newFunc, function, argExpansions, found );
-							}
-						}
-					}
-				} catch ( SemanticErrorException & e ) { errors.append( e ); }
-			}
-
-			// Find matches on function operators `?()`
-			if ( ! opFinder.candidates.empty() ) {
-				// add exploded function alternatives to front of argument list
-				std::vector< ExplodedArg > funcE;
-				funcE.reserve( funcFinder.candidates.size() );
-				for ( const CandidateRef & func : funcFinder ) {
-					funcE.emplace_back( *func, symtab );
-				}
-				argExpansions.emplace_front( std::move( funcE ) );
-
-				for ( const CandidateRef & op : opFinder ) {
-					try {
-						// check if type is pointer-to-function
-						const ast::Type * opResult = op->expr->result->stripReferences();
-						if ( auto pointer = dynamic_cast< const ast::PointerType * >( opResult ) ) {
-							if ( auto function = pointer->base.as< ast::FunctionType >() ) {
-								CandidateRef newOp{ new Candidate{ *op} };
-								newOp->expr =
-									referenceToRvalueConversion( newOp->expr, newOp->cost );
-								makeFunctionCandidates( newOp, function, argExpansions, found );
-							}
-						}
-					} catch ( SemanticErrorException & e ) { errors.append( e ); }
-				}
-			}
-
-			// Implement SFINAE; resolution errors are only errors if there aren't any non-error
-			// candidates
-			if ( found.empty() && ! errors.isEmpty() ) { throw errors; }
-
-			// Compute conversion costs
-			for ( CandidateRef & withFunc : found ) {
-				Cost cvtCost = computeApplicationConversionCost( withFunc, symtab );
-
-				PRINT(
-					auto appExpr = withFunc->expr.strict_as< ast::ApplicationExpr >();
-					auto pointer = appExpr->func->result.strict_as< ast::PointerType >();
-					auto function = pointer->base.strict_as< ast::FunctionType >();
-
-					std::cerr << "Case +++++++++++++ " << appExpr->func << std::endl;
-					std::cerr << "parameters are:" << std::endl;
-					ast::printAll( std::cerr, function->params, 2 );
-					std::cerr << "arguments are:" << std::endl;
-					ast::printAll( std::cerr, appExpr->args, 2 );
-					std::cerr << "bindings are:" << std::endl;
-					ast::print( std::cerr, withFunc->env, 2 );
-					std::cerr << "cost is: " << withFunc->cost << std::endl;
-					std::cerr << "cost of conversion is:" << cvtCost << std::endl;
-				)
-
-				if ( cvtCost != Cost::infinity ) {
-					withFunc->cvtCost = cvtCost;
-					candidates.emplace_back( std::move( withFunc ) );
-				}
-			}
-			found = std::move( candidates );
-
-			// use a new list so that candidates are not examined by addAnonConversions twice
-			CandidateList winners = findMinCost( found );
-			promoteCvtCost( winners );
-
-			// function may return a struct/union value, in which case we need to add candidates
-			// for implicit conversions to each of the anonymous members, which must happen after
-			// `findMinCost`, since anon conversions are never the cheapest
-			for ( const CandidateRef & c : winners ) {
-				addAnonConversions( c );
-			}
-			spliceBegin( candidates, winners );
-
-			if ( candidates.empty() && targetType && ! targetType->isVoid() ) {
-				// If resolution is unsuccessful with a target type, try again without, since it
-				// will sometimes succeed when it wouldn't with a target type binding.
-				// For example:
-				//   forall( otype T ) T & ?[]( T *, ptrdiff_t );
-				//   const char * x = "hello world";
-				//   unsigned char ch = x[0];
-				// Fails with simple return type binding (xxx -- check this!) as follows:
-				// * T is bound to unsigned char
-				// * (x: const char *) is unified with unsigned char *, which fails
-				// xxx -- fix this better
-				targetType = nullptr;
-				postvisit( untypedExpr );
-			}
-		}
-
-		/// true if expression is an lvalue
-		static bool isLvalue( const ast::Expr * x ) {
-			return x->result && ( x->get_lvalue() || x->result.as< ast::ReferenceType >() );
-		}
-
-		void postvisit( const ast::AddressExpr * addressExpr ) {
-			CandidateFinder finder( context, tenv );
-			finder.find( addressExpr->arg );
-
-			if( finder.candidates.empty() ) return;
-
-			reason.code = NoMatch;
-
-			for ( CandidateRef & r : finder.candidates ) {
-				if ( ! isLvalue( r->expr ) ) continue;
-				addCandidate( *r, new ast::AddressExpr{ addressExpr->location, r->expr } );
-			}
-		}
-
-		void postvisit( const ast::LabelAddressExpr * labelExpr ) {
-			addCandidate( labelExpr, tenv );
-		}
-
-		void postvisit( const ast::CastExpr * castExpr ) {
-			ast::ptr< ast::Type > toType = castExpr->result;
-			assert( toType );
-			toType = resolveTypeof( toType, context );
-			toType = adjustExprType( toType, tenv, symtab );
-
-			CandidateFinder finder( context, tenv, toType );
-			finder.find( castExpr->arg, ResolvMode::withAdjustment() );
-
-			if( !finder.candidates.empty() ) reason.code = NoMatch;
-
-			CandidateList matches;
-			for ( CandidateRef & cand : finder.candidates ) {
-				ast::AssertionSet need( cand->need.begin(), cand->need.end() ), have;
-				ast::OpenVarSet open( cand->open );
-
-				cand->env.extractOpenVars( open );
-
-				// It is possible that a cast can throw away some values in a multiply-valued
-				// expression, e.g. cast-to-void, one value to zero. Figure out the prefix of the
-				// subexpression results that are cast directly. The candidate is invalid if it
-				// has fewer results than there are types to cast to.
-				int discardedValues = cand->expr->result->size() - toType->size();
-				if ( discardedValues < 0 ) continue;
-
-				// unification run for side-effects
-				unify( toType, cand->expr->result, cand->env, need, have, open, symtab );
-				Cost thisCost =
-					(castExpr->isGenerated == ast::GeneratedFlag::GeneratedCast)
- 	                    ? conversionCost( cand->expr->result, toType, cand->expr->get_lvalue(), symtab, cand->env )
- 	                    : castCost( cand->expr->result, toType, cand->expr->get_lvalue(), symtab, cand->env );
-
-				PRINT(
-					std::cerr << "working on cast with result: " << toType << std::endl;
-					std::cerr << "and expr type: " << cand->expr->result << std::endl;
-					std::cerr << "env: " << cand->env << std::endl;
-				)
-				if ( thisCost != Cost::infinity ) {
-					PRINT(
-						std::cerr << "has finite cost." << std::endl;
-					)
-					// count one safe conversion for each value that is thrown away
-					thisCost.incSafe( discardedValues );
-					CandidateRef newCand = std::make_shared<Candidate>(
-						restructureCast( cand->expr, toType, castExpr->isGenerated ),
-						copy( cand->env ), std::move( open ), std::move( need ), cand->cost,
-						cand->cost + thisCost );
-					inferParameters( newCand, matches );
-				}
-			}
-
-			// select first on argument cost, then conversion cost
-			CandidateList minArgCost = findMinCost( matches );
-			promoteCvtCost( minArgCost );
-			candidates = findMinCost( minArgCost );
-		}
-
-		void postvisit( const ast::VirtualCastExpr * castExpr ) {
-			assertf( castExpr->result, "Implicit virtual cast targets not yet supported." );
-			CandidateFinder finder( context, tenv );
-			// don't prune here, all alternatives guaranteed to have same type
-			finder.find( castExpr->arg, ResolvMode::withoutPrune() );
-			for ( CandidateRef & r : finder.candidates ) {
-				addCandidate(
-					*r,
-					new ast::VirtualCastExpr{ castExpr->location, r->expr, castExpr->result } );
-			}
-		}
-
-		void postvisit( const ast::KeywordCastExpr * castExpr ) {
-			const auto & loc = castExpr->location;
-			assertf( castExpr->result, "Cast target should have been set in Validate." );
-			auto ref = castExpr->result.strict_as<ast::ReferenceType>();
-			auto inst = ref->base.strict_as<ast::StructInstType>();
-			auto target = inst->base.get();
-
-			CandidateFinder finder( context, tenv );
-
-			auto pick_alternatives = [target, this](CandidateList & found, bool expect_ref) {
-				for(auto & cand : found) {
-					const ast::Type * expr = cand->expr->result.get();
-					if(expect_ref) {
-						auto res = dynamic_cast<const ast::ReferenceType*>(expr);
-						if(!res) { continue; }
-						expr = res->base.get();
-					}
-
-					if(auto insttype = dynamic_cast<const ast::TypeInstType*>(expr)) {
-						auto td = cand->env.lookup(*insttype);
-						if(!td) { continue; }
-						expr = td->bound.get();
-					}
-
-					if(auto base = dynamic_cast<const ast::StructInstType*>(expr)) {
-						if(base->base == target) {
-							candidates.push_back( std::move(cand) );
-							reason.code = NoReason;
-						}
-					}
-				}
-			};
-
-			try {
-				// Attempt 1 : turn (thread&)X into (thread$&)X.__thrd
-				// Clone is purely for memory management
-				std::unique_ptr<const ast::Expr> tech1 { new ast::UntypedMemberExpr(loc, new ast::NameExpr(loc, castExpr->concrete_target.field), castExpr->arg) };
-
-				// don't prune here, since it's guaranteed all alternatives will have the same type
-				finder.find( tech1.get(), ResolvMode::withoutPrune() );
-				pick_alternatives(finder.candidates, false);
-
-				return;
-			} catch(SemanticErrorException & ) {}
-
-			// Fallback : turn (thread&)X into (thread$&)get_thread(X)
-			std::unique_ptr<const ast::Expr> fallback { ast::UntypedExpr::createDeref(loc,  new ast::UntypedExpr(loc, new ast::NameExpr(loc, castExpr->concrete_target.getter), { castExpr->arg })) };
-			// don't prune here, since it's guaranteed all alternatives will have the same type
-			finder.find( fallback.get(), ResolvMode::withoutPrune() );
-
-			pick_alternatives(finder.candidates, true);
-
-			// Whatever happens here, we have no more fallbacks
-		}
-
-		void postvisit( const ast::UntypedMemberExpr * memberExpr ) {
-			CandidateFinder aggFinder( context, tenv );
-			aggFinder.find( memberExpr->aggregate, ResolvMode::withAdjustment() );
-			for ( CandidateRef & agg : aggFinder.candidates ) {
-				// it's okay for the aggregate expression to have reference type -- cast it to the
-				// base type to treat the aggregate as the referenced value
-				Cost addedCost = Cost::zero;
-				agg->expr = referenceToRvalueConversion( agg->expr, addedCost );
-
-				// find member of the given type
-				if ( auto structInst = agg->expr->result.as< ast::StructInstType >() ) {
-					addAggMembers(
-						structInst, agg->expr, *agg, addedCost, getMemberName( memberExpr ) );
-				} else if ( auto unionInst = agg->expr->result.as< ast::UnionInstType >() ) {
-					addAggMembers(
-						unionInst, agg->expr, *agg, addedCost, getMemberName( memberExpr ) );
-				} else if ( auto tupleType = agg->expr->result.as< ast::TupleType >() ) {
-					addTupleMembers( tupleType, agg->expr, *agg, addedCost, memberExpr->member );
-				}
-			}
-		}
-
-		void postvisit( const ast::MemberExpr * memberExpr ) {
-			addCandidate( memberExpr, tenv );
-		}
-
-		void postvisit( const ast::NameExpr * nameExpr ) {
-			std::vector< ast::SymbolTable::IdData > declList;
-			if (!selfFinder.otypeKeys.empty()) {
-				auto kind = ast::SymbolTable::getSpecialFunctionKind(nameExpr->name);
-				assertf(kind != ast::SymbolTable::SpecialFunctionKind::NUMBER_OF_KINDS, "special lookup with non-special target: %s", nameExpr->name.c_str());
-
-				for (auto & otypeKey: selfFinder.otypeKeys) {
-					auto result = symtab.specialLookupId(kind, otypeKey);
-					declList.insert(declList.end(), std::make_move_iterator(result.begin()), std::make_move_iterator(result.end()));
-				}
-			}
-			else {
-				declList = symtab.lookupId( nameExpr->name );
-			}
-			PRINT( std::cerr << "nameExpr is " << nameExpr->name << std::endl; )
-
-			if( declList.empty() ) return;
-
-			reason.code = NoMatch;
-
-			for ( auto & data : declList ) {
-				Cost cost = Cost::zero;
-				ast::Expr * newExpr = data.combine( nameExpr->location, cost );
-
-				CandidateRef newCand = std::make_shared<Candidate>(
-					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 ";
-					ast::print( std::cerr, data.id );
-					std::cerr << std::endl;
-					std::cerr << "newExpr is ";
-					ast::print( std::cerr, newExpr );
-					std::cerr << std::endl;
-				)
-				newCand->expr = ast::mutate_field(
-					newCand->expr.get(), &ast::Expr::result,
-					renameTyVars( newCand->expr->result ) );
-				// add anonymous member interpretations whenever an aggregate value type is seen
-				// as a name expression
-				addAnonConversions( newCand );
-				candidates.emplace_back( std::move( newCand ) );
-			}
-		}
-
-		void postvisit( const ast::VariableExpr * variableExpr ) {
-			// not sufficient to just pass `variableExpr` here, type might have changed since
-			// creation
-			addCandidate(
-				new ast::VariableExpr{ variableExpr->location, variableExpr->var }, tenv );
-		}
-
-		void postvisit( const ast::ConstantExpr * constantExpr ) {
-			addCandidate( constantExpr, tenv );
-		}
-
-		void postvisit( const ast::SizeofExpr * sizeofExpr ) {
-			if ( sizeofExpr->type ) {
-				addCandidate(
-					new ast::SizeofExpr{
-						sizeofExpr->location, resolveTypeof( sizeofExpr->type, context ) },
-					tenv );
-			} else {
-				// find all candidates for the argument to sizeof
-				CandidateFinder finder( context, tenv );
-				finder.find( sizeofExpr->expr );
-				// find the lowest-cost candidate, otherwise ambiguous
-				CandidateList winners = findMinCost( finder.candidates );
-				if ( winners.size() != 1 ) {
-					SemanticError(
-						sizeofExpr->expr.get(), "Ambiguous expression in sizeof operand: " );
-				}
-				// return the lowest-cost candidate
-				CandidateRef & choice = winners.front();
-				choice->expr = referenceToRvalueConversion( choice->expr, choice->cost );
-				choice->cost = Cost::zero;
-				addCandidate( *choice, new ast::SizeofExpr{ sizeofExpr->location, choice->expr } );
-			}
-		}
-
-		void postvisit( const ast::AlignofExpr * alignofExpr ) {
-			if ( alignofExpr->type ) {
-				addCandidate(
-					new ast::AlignofExpr{
-						alignofExpr->location, resolveTypeof( alignofExpr->type, context ) },
-					tenv );
-			} else {
-				// find all candidates for the argument to alignof
-				CandidateFinder finder( context, tenv );
-				finder.find( alignofExpr->expr );
-				// find the lowest-cost candidate, otherwise ambiguous
-				CandidateList winners = findMinCost( finder.candidates );
-				if ( winners.size() != 1 ) {
-					SemanticError(
-						alignofExpr->expr.get(), "Ambiguous expression in alignof operand: " );
-				}
-				// return the lowest-cost candidate
-				CandidateRef & choice = winners.front();
-				choice->expr = referenceToRvalueConversion( choice->expr, choice->cost );
-				choice->cost = Cost::zero;
-				addCandidate(
-					*choice, new ast::AlignofExpr{ alignofExpr->location, choice->expr } );
-			}
-		}
-
-		void postvisit( const ast::UntypedOffsetofExpr * offsetofExpr ) {
-			const ast::BaseInstType * aggInst;
-			if (( aggInst = offsetofExpr->type.as< ast::StructInstType >() )) ;
-			else if (( aggInst = offsetofExpr->type.as< ast::UnionInstType >() )) ;
-			else return;
-
-			for ( const ast::Decl * member : aggInst->lookup( offsetofExpr->member ) ) {
-				auto dwt = strict_dynamic_cast< const ast::DeclWithType * >( member );
-				addCandidate(
-					new ast::OffsetofExpr{ offsetofExpr->location, aggInst, dwt }, tenv );
-			}
-		}
-
-		void postvisit( const ast::OffsetofExpr * offsetofExpr ) {
-			addCandidate( offsetofExpr, tenv );
-		}
-
-		void postvisit( const ast::OffsetPackExpr * offsetPackExpr ) {
-			addCandidate( offsetPackExpr, tenv );
-		}
-
-		void postvisit( const ast::LogicalExpr * logicalExpr ) {
-			CandidateFinder finder1( context, tenv );
-			finder1.find( logicalExpr->arg1, ResolvMode::withAdjustment() );
-			if ( finder1.candidates.empty() ) return;
-
-			CandidateFinder finder2( context, tenv );
-			finder2.find( logicalExpr->arg2, ResolvMode::withAdjustment() );
-			if ( finder2.candidates.empty() ) return;
-
-			reason.code = NoMatch;
-
-			for ( const CandidateRef & r1 : finder1.candidates ) {
-				for ( const CandidateRef & r2 : finder2.candidates ) {
-					ast::TypeEnvironment env{ r1->env };
-					env.simpleCombine( r2->env );
-					ast::OpenVarSet open{ r1->open };
-					mergeOpenVars( open, r2->open );
-					ast::AssertionSet need;
-					mergeAssertionSet( need, r1->need );
-					mergeAssertionSet( need, r2->need );
-
-					addCandidate(
-						new ast::LogicalExpr{
-							logicalExpr->location, r1->expr, r2->expr, logicalExpr->isAnd },
-						std::move( env ), std::move( open ), std::move( need ), r1->cost + r2->cost );
-				}
-			}
-		}
-
-		void postvisit( const ast::ConditionalExpr * conditionalExpr ) {
-			// candidates for condition
-			CandidateFinder finder1( context, tenv );
-			finder1.find( conditionalExpr->arg1, ResolvMode::withAdjustment() );
-			if ( finder1.candidates.empty() ) return;
-
-			// candidates for true result
-			CandidateFinder finder2( context, tenv );
-			finder2.find( conditionalExpr->arg2, ResolvMode::withAdjustment() );
-			if ( finder2.candidates.empty() ) return;
-
-			// candidates for false result
-			CandidateFinder finder3( context, tenv );
-			finder3.find( conditionalExpr->arg3, ResolvMode::withAdjustment() );
-			if ( finder3.candidates.empty() ) return;
-
-			reason.code = NoMatch;
-
-			for ( const CandidateRef & r1 : finder1.candidates ) {
-				for ( const CandidateRef & r2 : finder2.candidates ) {
-					for ( const CandidateRef & r3 : finder3.candidates ) {
-						ast::TypeEnvironment env{ r1->env };
-						env.simpleCombine( r2->env );
-						env.simpleCombine( r3->env );
-						ast::OpenVarSet open{ r1->open };
-						mergeOpenVars( open, r2->open );
-						mergeOpenVars( open, r3->open );
-						ast::AssertionSet need;
-						mergeAssertionSet( need, r1->need );
-						mergeAssertionSet( need, r2->need );
-						mergeAssertionSet( need, r3->need );
-						ast::AssertionSet have;
-
-						// unify true and false results, then infer parameters to produce new
-						// candidates
-						ast::ptr< ast::Type > common;
-						if (
-							unify(
-								r2->expr->result, r3->expr->result, env, need, have, open, symtab,
-								common )
-						) {
-							// generate typed expression
-							ast::ConditionalExpr * newExpr = new ast::ConditionalExpr{
-								conditionalExpr->location, r1->expr, r2->expr, r3->expr };
-							newExpr->result = common ? common : r2->expr->result;
-							// convert both options to result type
-							Cost cost = r1->cost + r2->cost + r3->cost;
-							newExpr->arg2 = computeExpressionConversionCost(
-								newExpr->arg2, newExpr->result, symtab, env, cost );
-							newExpr->arg3 = computeExpressionConversionCost(
-								newExpr->arg3, newExpr->result, symtab, env, cost );
-							// output candidate
-							CandidateRef newCand = std::make_shared<Candidate>(
-								newExpr, std::move( env ), std::move( open ), std::move( need ), cost );
-							inferParameters( newCand, candidates );
-						}
-					}
-				}
-			}
-		}
-
-		void postvisit( const ast::CommaExpr * commaExpr ) {
-			ast::TypeEnvironment env{ tenv };
-			ast::ptr< ast::Expr > arg1 = resolveInVoidContext( commaExpr->arg1, context, env );
-
-			CandidateFinder finder2( context, env );
-			finder2.find( commaExpr->arg2, ResolvMode::withAdjustment() );
-
-			for ( const CandidateRef & r2 : finder2.candidates ) {
-				addCandidate( *r2, new ast::CommaExpr{ commaExpr->location, arg1, r2->expr } );
-			}
-		}
-
-		void postvisit( const ast::ImplicitCopyCtorExpr * ctorExpr ) {
-			addCandidate( ctorExpr, tenv );
-		}
-
-		void postvisit( const ast::ConstructorExpr * ctorExpr ) {
-			CandidateFinder finder( context, tenv );
-			finder.find( ctorExpr->callExpr, ResolvMode::withoutPrune() );
-			for ( CandidateRef & r : finder.candidates ) {
-				addCandidate( *r, new ast::ConstructorExpr{ ctorExpr->location, r->expr } );
-			}
-		}
-
-		void postvisit( const ast::RangeExpr * rangeExpr ) {
-			// resolve low and high, accept candidates where low and high types unify
-			CandidateFinder finder1( context, tenv );
-			finder1.find( rangeExpr->low, ResolvMode::withAdjustment() );
-			if ( finder1.candidates.empty() ) return;
-
-			CandidateFinder finder2( context, tenv );
-			finder2.find( rangeExpr->high, ResolvMode::withAdjustment() );
-			if ( finder2.candidates.empty() ) return;
-
-			reason.code = NoMatch;
-
-			for ( const CandidateRef & r1 : finder1.candidates ) {
-				for ( const CandidateRef & r2 : finder2.candidates ) {
-					ast::TypeEnvironment env{ r1->env };
-					env.simpleCombine( r2->env );
-					ast::OpenVarSet open{ r1->open };
-					mergeOpenVars( open, r2->open );
-					ast::AssertionSet need;
-					mergeAssertionSet( need, r1->need );
-					mergeAssertionSet( need, r2->need );
-					ast::AssertionSet have;
-
-					ast::ptr< ast::Type > common;
-					if (
-						unify(
-							r1->expr->result, r2->expr->result, env, need, have, open, symtab,
-							common )
-					) {
-						// generate new expression
-						ast::RangeExpr * newExpr =
-							new ast::RangeExpr{ rangeExpr->location, r1->expr, r2->expr };
-						newExpr->result = common ? common : r1->expr->result;
-						// add candidate
-						CandidateRef newCand = std::make_shared<Candidate>(
-							newExpr, std::move( env ), std::move( open ), std::move( need ),
-							r1->cost + r2->cost );
-						inferParameters( newCand, candidates );
-					}
-				}
-			}
-		}
-
-		void postvisit( const ast::UntypedTupleExpr * tupleExpr ) {
-			std::vector< CandidateFinder > subCandidates =
-				selfFinder.findSubExprs( tupleExpr->exprs );
-			std::vector< CandidateList > possibilities;
-			combos( subCandidates.begin(), subCandidates.end(), back_inserter( possibilities ) );
-
-			for ( const CandidateList & subs : possibilities ) {
-				std::vector< ast::ptr< ast::Expr > > exprs;
-				exprs.reserve( subs.size() );
-				for ( const CandidateRef & sub : subs ) { exprs.emplace_back( sub->expr ); }
-
-				ast::TypeEnvironment env;
-				ast::OpenVarSet open;
-				ast::AssertionSet need;
-				for ( const CandidateRef & sub : subs ) {
-					env.simpleCombine( sub->env );
-					mergeOpenVars( open, sub->open );
-					mergeAssertionSet( need, sub->need );
-				}
-
-				addCandidate(
-					new ast::TupleExpr{ tupleExpr->location, std::move( exprs ) },
-					std::move( env ), std::move( open ), std::move( need ), sumCost( subs ) );
-			}
-		}
-
-		void postvisit( const ast::TupleExpr * tupleExpr ) {
-			addCandidate( tupleExpr, tenv );
-		}
-
-		void postvisit( const ast::TupleIndexExpr * tupleExpr ) {
-			addCandidate( tupleExpr, tenv );
-		}
-
-		void postvisit( const ast::TupleAssignExpr * tupleExpr ) {
-			addCandidate( tupleExpr, tenv );
-		}
-
-		void postvisit( const ast::UniqueExpr * unqExpr ) {
-			CandidateFinder finder( context, tenv );
-			finder.find( unqExpr->expr, ResolvMode::withAdjustment() );
-			for ( CandidateRef & r : finder.candidates ) {
-				// ensure that the the id is passed on so that the expressions are "linked"
-				addCandidate( *r, new ast::UniqueExpr{ unqExpr->location, r->expr, unqExpr->id } );
-			}
-		}
-
-		void postvisit( const ast::StmtExpr * stmtExpr ) {
-			addCandidate( resolveStmtExpr( stmtExpr, context ), tenv );
-		}
-
-		void postvisit( const ast::UntypedInitExpr * initExpr ) {
-			// handle each option like a cast
-			CandidateList matches;
-			PRINT(
-				std::cerr << "untyped init expr: " << initExpr << std::endl;
-			)
-			// O(n^2) checks of d-types with e-types
-			for ( const ast::InitAlternative & initAlt : initExpr->initAlts ) {
-				// calculate target type
-				const ast::Type * toType = resolveTypeof( initAlt.type, context );
-				toType = adjustExprType( toType, tenv, symtab );
-				// The call to find must occur inside this loop, otherwise polymorphic return
-				// types are not bound to the initialization type, since return type variables are
-				// only open for the duration of resolving the UntypedExpr.
-				CandidateFinder finder( context, tenv, toType );
-				finder.find( initExpr->expr, ResolvMode::withAdjustment() );
-				for ( CandidateRef & cand : finder.candidates ) {
-					if(reason.code == NotFound) reason.code = NoMatch;
-
-					ast::TypeEnvironment env{ cand->env };
-					ast::AssertionSet need( cand->need.begin(), cand->need.end() ), have;
-					ast::OpenVarSet open{ cand->open };
-
-					PRINT(
-						std::cerr << "  @ " << toType << " " << initAlt.designation << std::endl;
-					)
-
-					// It is possible that a cast can throw away some values in a multiply-valued
-					// expression, e.g. cast-to-void, one value to zero. Figure out the prefix of
-					// the subexpression results that are cast directly. The candidate is invalid
-					// if it has fewer results than there are types to cast to.
-					int discardedValues = cand->expr->result->size() - toType->size();
-					if ( discardedValues < 0 ) continue;
-
-					// unification run for side-effects
-					bool canUnify = unify( toType, cand->expr->result, env, need, have, open, symtab );
-					(void) canUnify;
-					Cost thisCost = computeConversionCost( cand->expr->result, toType, cand->expr->get_lvalue(),
-						symtab, env );
-					PRINT(
-						Cost legacyCost = castCost( cand->expr->result, toType, cand->expr->get_lvalue(),
-							symtab, env );
-						std::cerr << "Considering initialization:";
-						std::cerr << std::endl << "  FROM: " << cand->expr->result << std::endl;
-						std::cerr << std::endl << "  TO: "   << toType             << std::endl;
-						std::cerr << std::endl << "  Unification " << (canUnify ? "succeeded" : "failed");
-						std::cerr << std::endl << "  Legacy cost " << legacyCost;
-						std::cerr << std::endl << "  New cost " << thisCost;
-						std::cerr << std::endl;
-					)
-					if ( thisCost != Cost::infinity ) {
-						// count one safe conversion for each value that is thrown away
-						thisCost.incSafe( discardedValues );
-						CandidateRef newCand = std::make_shared<Candidate>(
-							new ast::InitExpr{
-								initExpr->location, restructureCast( cand->expr, toType ),
-								initAlt.designation },
-							std::move(env), std::move( open ), std::move( need ), cand->cost, thisCost );
-						inferParameters( newCand, matches );
-					}
-				}
-
-			}
-
-			// select first on argument cost, then conversion cost
-			CandidateList minArgCost = findMinCost( matches );
-			promoteCvtCost( minArgCost );
-			candidates = findMinCost( minArgCost );
-		}
-
-		void postvisit( const ast::InitExpr * ) {
-			assertf( false, "CandidateFinder should never see a resolved InitExpr." );
-		}
-
-		void postvisit( const ast::DeletedExpr * ) {
-			assertf( false, "CandidateFinder should never see a DeletedExpr." );
-		}
-
-		void postvisit( const ast::GenericExpr * ) {
-			assertf( false, "_Generic is not yet supported." );
-		}
-	};
-
-	// size_t Finder::traceId = Stats::Heap::new_stacktrace_id("Finder");
-	/// Prunes a list of candidates down to those that have the minimum conversion cost for a given
-	/// return type. Skips ambiguous candidates.
-
-} // anonymous namespace
-
-bool CandidateFinder::pruneCandidates( CandidateList & candidates, CandidateList & out, std::vector<std::string> & errors ) {
-	struct PruneStruct {
-		CandidateRef candidate;
-		bool ambiguous;
-
-		PruneStruct() = default;
-		PruneStruct( const CandidateRef & c ) : candidate( c ), ambiguous( false ) {}
-	};
-
-	// find lowest-cost candidate for each type
-	std::unordered_map< std::string, PruneStruct > selected;
-	// attempt to skip satisfyAssertions on more expensive alternatives if better options have been found
-	std::sort(candidates.begin(), candidates.end(), [](const CandidateRef & x, const CandidateRef & y){return x->cost < y->cost;});
-	for ( CandidateRef & candidate : candidates ) {
-		std::string mangleName;
-		{
-			ast::ptr< ast::Type > newType = candidate->expr->result;
-			assertf(candidate->expr->result, "Result of expression %p for candidate is null", candidate->expr.get());
-			candidate->env.apply( newType );
-			mangleName = Mangle::mangle( newType );
-		}
-
-		auto found = selected.find( mangleName );
-		if (found != selected.end() && found->second.candidate->cost < candidate->cost) {
-			PRINT(
-				std::cerr << "cost " << candidate->cost << " loses to "
-					<< found->second.candidate->cost << std::endl;
-			)
-			continue;
-		}
-
-		// xxx - when do satisfyAssertions produce more than 1 result?
-		// this should only happen when initial result type contains
-		// unbound type parameters, then it should never be pruned by
-		// the previous step, since renameTyVars guarantees the mangled name
-		// is unique.
-		CandidateList satisfied;
-		bool needRecomputeKey = false;
-		if (candidate->need.empty()) {
-			satisfied.emplace_back(candidate);
-		}
-		else {
-			satisfyAssertions(candidate, context.symtab, satisfied, errors);
-			needRecomputeKey = true;
-		}
-
-		for (auto & newCand : satisfied) {
-			// recomputes type key, if satisfyAssertions changed it
-			if (needRecomputeKey)
-			{
-				ast::ptr< ast::Type > newType = newCand->expr->result;
-				assertf(newCand->expr->result, "Result of expression %p for candidate is null", newCand->expr.get());
-				newCand->env.apply( newType );
-				mangleName = Mangle::mangle( newType );
-			}
-			auto found = selected.find( mangleName );
-			if ( found != selected.end() ) {
-				if ( newCand->cost < found->second.candidate->cost ) {
-					PRINT(
-						std::cerr << "cost " << newCand->cost << " beats "
-							<< found->second.candidate->cost << std::endl;
-					)
-
-					found->second = PruneStruct{ newCand };
-				} else if ( newCand->cost == found->second.candidate->cost ) {
-					// if one of the candidates contains a deleted identifier, can pick the other,
-					// since deleted expressions should not be ambiguous if there is another option
-					// that is at least as good
-					if ( findDeletedExpr( newCand->expr ) ) {
-						// do nothing
-						PRINT( std::cerr << "candidate is deleted" << std::endl; )
-					} else if ( findDeletedExpr( found->second.candidate->expr ) ) {
-						PRINT( std::cerr << "current is deleted" << std::endl; )
-						found->second = PruneStruct{ newCand };
-					} else {
-						PRINT( std::cerr << "marking ambiguous" << std::endl; )
-						found->second.ambiguous = true;
-					}
-				} else {
-					// xxx - can satisfyAssertions increase the cost?
-					PRINT(
-						std::cerr << "cost " << newCand->cost << " loses to "
-							<< found->second.candidate->cost << std::endl;
-					)
-				}
-			} else {
-				selected.emplace_hint( found, mangleName, newCand );
-			}
-		}
-	}
-
-	// report unambiguous min-cost candidates
-	// CandidateList out;
-	for ( auto & target : selected ) {
-		if ( target.second.ambiguous ) continue;
-
-		CandidateRef cand = target.second.candidate;
-
-		ast::ptr< ast::Type > newResult = cand->expr->result;
-		cand->env.applyFree( newResult );
-		cand->expr = ast::mutate_field(
-			cand->expr.get(), &ast::Expr::result, std::move( newResult ) );
-
-		out.emplace_back( cand );
-	}
-	// if everything is lost in satisfyAssertions, report the error
-	return !selected.empty();
-}
-
-void CandidateFinder::find( const ast::Expr * expr, ResolvMode mode ) {
-	// Find alternatives for expression
-	ast::Pass<Finder> finder{ *this };
-	expr->accept( finder );
-
-	if ( mode.failFast && candidates.empty() ) {
-		switch(finder.core.reason.code) {
-		case Finder::NotFound:
-			{ SemanticError( expr, "No alternatives for expression " ); break; }
-		case Finder::NoMatch:
-			{ SemanticError( expr, "Invalid application of existing declaration(s) in expression " ); break; }
-		case Finder::ArgsToFew:
-		case Finder::ArgsToMany:
-		case Finder::RetsToFew:
-		case Finder::RetsToMany:
-		case Finder::NoReason:
-		default:
-			{ SemanticError( expr->location, "No reasonable alternatives for expression : reasons unkown" ); }
-		}
-	}
-
-	/*
-	if ( mode.satisfyAssns || mode.prune ) {
-		// trim candidates to just those where the assertions are satisfiable
-		// - necessary pre-requisite to pruning
-		CandidateList satisfied;
-		std::vector< std::string > errors;
-		for ( CandidateRef & candidate : candidates ) {
-			satisfyAssertions( candidate, localSyms, satisfied, errors );
-		}
-
-		// fail early if none such
-		if ( mode.failFast && satisfied.empty() ) {
-			std::ostringstream stream;
-			stream << "No alternatives with satisfiable assertions for " << expr << "\n";
-			for ( const auto& err : errors ) {
-				stream << err;
-			}
-			SemanticError( expr->location, stream.str() );
-		}
-
-		// reset candidates
-		candidates = move( satisfied );
-	}
-	*/
-
-	if ( mode.prune ) {
-		// trim candidates to single best one
-		PRINT(
-			std::cerr << "alternatives before prune:" << std::endl;
-			print( std::cerr, candidates );
-		)
-
-		CandidateList pruned;
-		std::vector<std::string> errors;
-		bool found = pruneCandidates( candidates, pruned, errors );
-
-		if ( mode.failFast && pruned.empty() ) {
-			std::ostringstream stream;
-			if (found) {
-				CandidateList winners = findMinCost( candidates );
-				stream << "Cannot choose between " << winners.size() << " alternatives for "
-					"expression\n";
-				ast::print( stream, expr );
-				stream << " Alternatives are:\n";
-				print( stream, winners, 1 );
-				SemanticError( expr->location, stream.str() );
-			}
-			else {
-				stream << "No alternatives with satisfiable assertions for " << expr << "\n";
-				for ( const auto& err : errors ) {
-					stream << err;
-				}
-				SemanticError( expr->location, stream.str() );
-			}
-		}
-
-		auto oldsize = candidates.size();
-		candidates = std::move( pruned );
-
-		PRINT(
-			std::cerr << "there are " << oldsize << " alternatives before elimination" << std::endl;
-		)
-		PRINT(
-			std::cerr << "there are " << candidates.size() << " alternatives after elimination"
-				<< std::endl;
-		)
-	}
-
-	// adjust types after pruning so that types substituted by pruneAlternatives are correctly
-	// adjusted
-	if ( mode.adjust ) {
-		for ( CandidateRef & r : candidates ) {
-			r->expr = ast::mutate_field(
-				r->expr.get(), &ast::Expr::result,
-				adjustExprType( r->expr->result, r->env, context.symtab ) );
-		}
-	}
-
-	// Central location to handle gcc extension keyword, etc. for all expressions
-	for ( CandidateRef & r : candidates ) {
-		if ( r->expr->extension != expr->extension ) {
-			r->expr.get_and_mutate()->extension = expr->extension;
-		}
-	}
-}
-
-std::vector< CandidateFinder > CandidateFinder::findSubExprs(
-	const std::vector< ast::ptr< ast::Expr > > & xs
-) {
-	std::vector< CandidateFinder > out;
-
-	for ( const auto & x : xs ) {
-		out.emplace_back( context, env );
-		out.back().find( x, ResolvMode::withAdjustment() );
-
-		PRINT(
-			std::cerr << "findSubExprs" << std::endl;
-			print( std::cerr, out.back().candidates );
-		)
-	}
-
-	return out;
-}
-
 } // namespace ResolvExpr
 
Index: src/ResolvExpr/CurrentObject.cc
===================================================================
--- src/ResolvExpr/CurrentObject.cc	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/ResolvExpr/CurrentObject.cc	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -9,7 +9,7 @@
 // Author           : Rob Schluntz
 // Created On       : Tue Jun 13 15:28:32 2017
-// Last Modified By : Peter A. Buhr
-// Last Modified On : Fri Jul  1 09:16:01 2022
-// Update Count     : 15
+// Last Modified By : Andrew Beach
+// Last Modified On : Mon Apr 10  9:40:00 2023
+// Update Count     : 18
 //
 
@@ -593,4 +593,43 @@
 
 namespace ast {
+	/// Iterates members of a type by initializer.
+	class MemberIterator {
+	public:
+		virtual ~MemberIterator() {}
+
+		/// Internal set position based on iterator ranges.
+		virtual void setPosition(
+			std::deque< ptr< Expr > >::const_iterator it,
+			std::deque< ptr< Expr > >::const_iterator end ) = 0;
+
+		/// Walks the current object using the given designators as a guide.
+		void setPosition( const std::deque< ptr< Expr > > & designators ) {
+			setPosition( designators.begin(), designators.end() );
+		}
+
+		/// Retrieve the list of possible (Type,Designation) pairs for the
+		/// current position in the current object.
+		virtual std::deque< InitAlternative > operator* () const = 0;
+
+		/// True if the iterator is not currently at the end.
+		virtual operator bool() const = 0;
+
+		/// Moves the iterator by one member in the current object.
+		virtual MemberIterator & bigStep() = 0;
+
+		/// Moves the iterator by one member in the current subobject.
+		virtual MemberIterator & smallStep() = 0;
+
+		/// The type of the current object.
+		virtual const Type * getType() = 0;
+
+		/// The type of the current subobject.
+		virtual const Type * getNext() = 0;
+
+		/// Helper for operator*; aggregates must add designator to each init
+		/// alternative, but adding designators in operator* creates duplicates.
+		virtual std::deque< InitAlternative > first() const = 0;
+	};
+
 	/// create a new MemberIterator that traverses a type correctly
 	MemberIterator * createMemberIterator( const CodeLocation & loc, const Type * type );
@@ -632,30 +671,15 @@
 	};
 
-	/// Iterates array types
-	class ArrayIterator final : public MemberIterator {
+	/// Iterates over an indexed type:
+	class IndexIterator : public MemberIterator {
+	protected:
 		CodeLocation location;
-		const ArrayType * array = nullptr;
-		const Type * base = nullptr;
 		size_t index = 0;
 		size_t size = 0;
-		std::unique_ptr< MemberIterator > memberIter;
-
-		void setSize( const Expr * expr ) {
-			auto res = eval( expr );
-			if ( ! res.second ) {
-				SemanticError( location, toString( "Array designator must be a constant expression: ", expr ) );
-			}
-			size = res.first;
-		}
-
-	public:
-		ArrayIterator( const CodeLocation & loc, const ArrayType * at ) : location( loc ), array( at ), base( at->base ) {
-			PRINT( std::cerr << "Creating array iterator: " << at << std::endl; )
-			memberIter.reset( createMemberIterator( loc, base ) );
-			if ( at->isVarLen ) {
-				SemanticError( location, at, "VLA initialization does not support @=: " );
-			}
-			setSize( at->dimension );
-		}
+		std::unique_ptr<MemberIterator> memberIter;
+	public:
+		IndexIterator( const CodeLocation & loc, size_t size ) :
+			location( loc ), size( size )
+		{}
 
 		void setPosition( const Expr * expr ) {
@@ -666,5 +690,4 @@
 			auto arg = eval( expr );
 			index = arg.first;
-			return;
 
 			// if ( auto constExpr = dynamic_cast< const ConstantExpr * >( expr ) ) {
@@ -684,6 +707,6 @@
 
 		void setPosition(
-			std::deque< ptr< Expr > >::const_iterator begin,
-			std::deque< ptr< Expr > >::const_iterator end
+			std::deque<ast::ptr<ast::Expr>>::const_iterator begin,
+			std::deque<ast::ptr<ast::Expr>>::const_iterator end
 		) override {
 			if ( begin == end ) return;
@@ -696,4 +719,29 @@
 
 		operator bool() const override { return index < size; }
+	};
+
+	/// Iterates over the members of array types:
+	class ArrayIterator final : public IndexIterator {
+		const ArrayType * array = nullptr;
+		const Type * base = nullptr;
+
+		size_t getSize( const Expr * expr ) {
+			auto res = eval( expr );
+			if ( !res.second ) {
+				SemanticError( location, toString( "Array designator must be a constant expression: ", expr ) );
+			}
+			return res.first;
+		}
+
+	public:
+		ArrayIterator( const CodeLocation & loc, const ArrayType * at ) :
+				IndexIterator( loc, getSize( at->dimension) ),
+				array( at ), base( at->base ) {
+			PRINT( std::cerr << "Creating array iterator: " << at << std::endl; )
+			memberIter.reset( createMemberIterator( loc, base ) );
+			if ( at->isVarLen ) {
+				SemanticError( location, at, "VLA initialization does not support @=: " );
+			}
+		}
 
 		ArrayIterator & bigStep() override {
@@ -834,5 +882,6 @@
 
 		const Type * getNext() final {
-			return ( memberIter && *memberIter ) ? memberIter->getType() : nullptr;
+			bool hasMember = memberIter && *memberIter;
+			return hasMember ? memberIter->getType() : nullptr;
 		}
 
@@ -898,25 +947,59 @@
 	};
 
-	class TupleIterator final : public AggregateIterator {
-	public:
-		TupleIterator( const CodeLocation & loc, const TupleType * inst )
-		: AggregateIterator(
-			loc, "TupleIterator", toString("Tuple", inst->size()), inst, inst->members
-		) {}
-
-		operator bool() const override {
-			return curMember != members.end() || (memberIter && *memberIter);
+	/// Iterates across the positions in a tuple:
+	class TupleIterator final : public IndexIterator {
+		ast::TupleType const * const tuple;
+
+		const ast::Type * typeAtIndex() const {
+			assert( index < size );
+			return tuple->types[ index ].get();
+		}
+
+	public:
+		TupleIterator( const CodeLocation & loc, const TupleType * type )
+		: IndexIterator( loc, type->size() ), tuple( type ) {
+			PRINT( std::cerr << "Creating tuple iterator: " << type << std::endl; )
+			memberIter.reset( createMemberIterator( loc, typeAtIndex() ) );
 		}
 
 		TupleIterator & bigStep() override {
-			PRINT( std::cerr << "bigStep in " << kind << std::endl; )
-			atbegin = false;
-			memberIter = nullptr;
-			curType = nullptr;
-			while ( curMember != members.end() ) {
-				++curMember;
-				if ( init() ) return *this;
-			}
+			++index;
+			memberIter.reset( index < size ?
+				createMemberIterator( location, typeAtIndex() ) : nullptr );
 			return *this;
+		}
+
+		TupleIterator & smallStep() override {
+			if ( memberIter ) {
+				PRINT( std::cerr << "has member iter: " << *memberIter << std::endl; )
+				memberIter->smallStep();
+				if ( !memberIter ) {
+					PRINT( std::cerr << "has valid member iter" << std::endl; )
+					return *this;
+				}
+			}
+			return bigStep();
+		}
+
+		const ast::Type * getType() override {
+			return tuple;
+		}
+
+		const ast::Type * getNext() override {
+			bool hasMember = memberIter && *memberIter;
+			return hasMember ? memberIter->getType() : nullptr;
+		}
+
+		std::deque< InitAlternative > first() const override {
+			PRINT( std::cerr << "first in TupleIterator (" << index << "/" << size << ")" << std::endl; )
+			if ( memberIter && *memberIter ) {
+				std::deque< InitAlternative > ret = memberIter->first();
+				for ( InitAlternative & alt : ret ) {
+					alt.designation.get_and_mutate()->designators.emplace_front(
+						ConstantExpr::from_ulong( location, index ) );
+				}
+				return ret;
+			}
+			return {};
 		}
 	};
Index: src/ResolvExpr/CurrentObject.h
===================================================================
--- src/ResolvExpr/CurrentObject.h	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/ResolvExpr/CurrentObject.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -9,7 +9,7 @@
 // Author           : Rob Schluntz
 // Created On       : Thu Jun  8 11:07:25 2017
-// Last Modified By : Peter A. Buhr
-// Last Modified On : Sat Jul 22 09:36:48 2017
-// Update Count     : 3
+// Last Modified By : Andrew Beach
+// Last Modified On : Thu Apr  6 16:14:00 2023
+// Update Count     : 4
 //
 
@@ -65,41 +65,5 @@
 
 	/// Iterates members of a type by initializer
-	class MemberIterator {
-	public:
-		virtual ~MemberIterator() {}
-
-		/// Internal set position based on iterator ranges
-		virtual void setPosition( 
-			std::deque< ptr< Expr > >::const_iterator it, 
-			std::deque< ptr< Expr > >::const_iterator end ) = 0;
-
-		/// walks the current object using the given designators as a guide
-		void setPosition( const std::deque< ptr< Expr > > & designators ) {
-			setPosition( designators.begin(), designators.end() );
-		}
-
-		/// retrieve the list of possible (Type,Designation) pairs for the current position in the 
-		/// current object
-		virtual std::deque< InitAlternative > operator* () const = 0;
-
-		/// true if the iterator is not currently at the end
-		virtual operator bool() const = 0;
-
-		/// moves the iterator by one member in the current object
-		virtual MemberIterator & bigStep() = 0;
-
-		/// moves the iterator by one member in the current subobject
-		virtual MemberIterator & smallStep() = 0;
-
-		/// the type of the current object
-		virtual const Type * getType() = 0;
-
-		/// the type of the current subobject
-		virtual const Type * getNext() = 0;
-	
-		/// helper for operator*; aggregates must add designator to each init alternative, but 
-		/// adding designators in operator* creates duplicates
-		virtual std::deque< InitAlternative > first() const = 0;
-	};
+	class MemberIterator;
 
 	/// Builds initializer lists in resolution
Index: src/ResolvExpr/ExplodedArg.hpp
===================================================================
--- src/ResolvExpr/ExplodedArg.hpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/ResolvExpr/ExplodedArg.hpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -35,5 +35,5 @@
 	ExplodedArg() : env(), cost( Cost::zero ), exprs() {}
 	ExplodedArg( const Candidate & arg, const ast::SymbolTable & symtab );
-	
+
 	ExplodedArg( ExplodedArg && ) = default;
 	ExplodedArg & operator= ( ExplodedArg && ) = default;
Index: src/SymTab/Autogen.cc
===================================================================
--- src/SymTab/Autogen.cc	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/SymTab/Autogen.cc	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -10,6 +10,6 @@
 // Created On       : Thu Mar 03 15:45:56 2016
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Fri Apr 27 14:39:06 2018
-// Update Count     : 63
+// Last Modified On : Fri Apr 14 15:03:00 2023
+// Update Count     : 64
 //
 
@@ -211,8 +211,4 @@
 	}
 
-	bool isUnnamedBitfield( const ast::ObjectDecl * obj ) {
-		return obj && obj->name.empty() && obj->bitfieldWidth;
-	}
-
 	/// inserts a forward declaration for functionDecl into declsToAdd
 	void addForwardDecl( FunctionDecl * functionDecl, std::list< Declaration * > & declsToAdd ) {
@@ -234,15 +230,4 @@
 	}
 
-	// shallow copy the pointer list for return
-	std::vector<ast::ptr<ast::TypeDecl>> getGenericParams (const ast::Type * t) {
-		if (auto structInst = dynamic_cast<const ast::StructInstType*>(t)) {
-			return structInst->base->params;
-		}
-		if (auto unionInst = dynamic_cast<const ast::UnionInstType*>(t)) {
-			return unionInst->base->params;
-		}
-		return {};
-	}
-
 	/// given type T, generate type of default ctor/dtor, i.e. function type void (*) (T *)
 	FunctionType * genDefaultType( Type * paramType, bool maybePolymorphic ) {
@@ -256,12 +241,4 @@
 		ftype->parameters.push_back( dstParam );
 		return ftype;
-	}
-
-	/// Given type T, generate type of default ctor/dtor, i.e. function type void (*) (T &).
-	ast::FunctionDecl * genDefaultFunc(const CodeLocation loc, const std::string fname, const ast::Type * paramType, bool maybePolymorphic) {
-		std::vector<ast::ptr<ast::TypeDecl>> typeParams;
-		if (maybePolymorphic) typeParams = getGenericParams(paramType);
-		auto dstParam = new ast::ObjectDecl(loc, "_dst", new ast::ReferenceType(paramType), nullptr, {}, ast::Linkage::Cforall);
-		return new ast::FunctionDecl(loc, fname, std::move(typeParams), {dstParam}, {}, new ast::CompoundStmt(loc), {}, ast::Linkage::Cforall);
 	}
 
Index: src/SymTab/Autogen.h
===================================================================
--- src/SymTab/Autogen.h	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/SymTab/Autogen.h	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -9,7 +9,7 @@
 // Author           : Rob Schluntz
 // Created On       : Sun May 17 21:53:34 2015
-// Last Modified By : Peter A. Buhr
-// Last Modified On : Fri Dec 13 16:38:06 2019
-// Update Count     : 16
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Apr 14 15:06:00 2023
+// Update Count     : 17
 //
 
@@ -45,5 +45,4 @@
 	/// returns true if obj's name is the empty string and it has a bitfield width
 	bool isUnnamedBitfield( ObjectDecl * obj );
-	bool isUnnamedBitfield( const ast::ObjectDecl * obj );
 
 	/// generate the type of an assignment function for paramType.
@@ -55,6 +54,4 @@
 	FunctionType * genDefaultType( Type * paramType, bool maybePolymorphic = true );
 
-	ast::FunctionDecl * genDefaultFunc(const CodeLocation loc, const std::string fname, const ast::Type * paramType, bool maybePolymorphic = true);
-
 	/// generate the type of a copy constructor for paramType.
 	/// maybePolymorphic is true if the resulting FunctionType is allowed to be polymorphic
@@ -67,10 +64,4 @@
 	template< typename OutputIterator >
 	Statement * genCall( InitTweak::InitExpander_old & srcParam, Expression * dstParam, const std::string & fname, OutputIterator out, Type * type, Type * addCast = nullptr, bool forward = true );
-
-	template< typename OutIter >
-	ast::ptr< ast::Stmt > genCall(
-		InitTweak::InitExpander_new & srcParam, const ast::Expr * dstParam,
-		const CodeLocation & loc, const std::string & fname, OutIter && out,
-		const ast::Type * type, const ast::Type * addCast, LoopDirection forward = LoopForward );
 
 	/// inserts into out a generated call expression to function fname with arguments dstParam and srcParam. Should only be called with non-array types.
@@ -121,61 +112,4 @@
 
 		*out++ = new ExprStmt( fExpr );
-
-		srcParam.clearArrayIndices();
-
-		return listInit;
-	}
-
-	/// inserts into out a generated call expression to function fname with arguments dstParam and
-	/// srcParam. Should only be called with non-array types.
-	/// optionally returns a statement which must be inserted prior to the containing loop, if
-	/// there is one
-	template< typename OutIter >
-	ast::ptr< ast::Stmt > genScalarCall(
-		InitTweak::InitExpander_new & srcParam, const ast::Expr * dstParam,
-		const CodeLocation & loc, std::string fname, OutIter && out, const ast::Type * type,
-		const ast::Type * addCast = nullptr
-	) {
-		bool isReferenceCtorDtor = false;
-		if ( dynamic_cast< const ast::ReferenceType * >( type ) && CodeGen::isCtorDtor( fname ) ) {
-			// reference constructors are essentially application of the rebind operator.
-			// apply & to both arguments, do not need a cast
-			fname = "?=?";
-			dstParam = new ast::AddressExpr{ dstParam };
-			addCast = nullptr;
-			isReferenceCtorDtor = true;
-		}
-
-		// want to be able to generate assignment, ctor, and dtor generically, so fname is one of
-		// "?=?", "?{}", or "^?{}"
-		ast::UntypedExpr * fExpr = new ast::UntypedExpr{ loc, new ast::NameExpr{ loc, fname } };
-
-		if ( addCast ) {
-			// cast to T& with qualifiers removed, so that qualified objects can be constructed and
-			// destructed with the same functions as non-qualified objects. Unfortunately, lvalue
-			// is considered a qualifier - for AddressExpr to resolve, its argument must have an
-			// lvalue-qualified type, so remove all qualifiers except lvalue.
-			// xxx -- old code actually removed lvalue too...
-			ast::ptr< ast::Type > guard = addCast;  // prevent castType from mutating addCast
-			ast::ptr< ast::Type > castType = addCast;
-			ast::remove_qualifiers(
-				castType,
-				ast::CV::Const | ast::CV::Volatile | ast::CV::Restrict | ast::CV::Atomic );
-			dstParam = new ast::CastExpr{ dstParam, new ast::ReferenceType{ castType } };
-		}
-		fExpr->args.emplace_back( dstParam );
-
-		ast::ptr<ast::Stmt> listInit = srcParam.buildListInit( fExpr );
-
-		// fetch next set of arguments
-		++srcParam;
-
-		// return if adding reference fails -- will happen on default ctor and dtor
-		if ( isReferenceCtorDtor && ! srcParam.addReference() ) return listInit;
-
-		std::vector< ast::ptr< ast::Expr > > args = *srcParam;
-		splice( fExpr->args, args );
-
-		*out++ = new ast::ExprStmt{ loc, fExpr };
 
 		srcParam.clearArrayIndices();
@@ -248,74 +182,4 @@
 	}
 
-	/// Store in out a loop which calls fname on each element of the array with srcParam and
-	/// dstParam as arguments. If forward is true, loop goes from 0 to N-1, else N-1 to 0
-	template< typename OutIter >
-	void genArrayCall(
-		InitTweak::InitExpander_new & srcParam, const ast::Expr * dstParam,
-		const CodeLocation & loc, const std::string & fname, OutIter && out,
-		const ast::ArrayType * array, const ast::Type * addCast = nullptr,
-		LoopDirection forward = LoopForward
-	) {
-		static UniqueName indexName( "_index" );
-
-		// for a flexible array member nothing is done -- user must define own assignment
-		if ( ! array->dimension ) return;
-
-		if ( addCast ) {
-			// peel off array layer from cast
-			addCast = strict_dynamic_cast< const ast::ArrayType * >( addCast )->base;
-		}
-
-		ast::ptr< ast::Expr > begin, end;
-		std::string cmp, update;
-
-		if ( forward ) {
-			// generate: for ( int i = 0; i < N; ++i )
-			begin = ast::ConstantExpr::from_int( loc, 0 );
-			end = array->dimension;
-			cmp = "?<?";
-			update = "++?";
-		} else {
-			// generate: for ( int i = N-1; i >= 0; --i )
-			begin = ast::UntypedExpr::createCall( loc, "?-?",
-				{ array->dimension, ast::ConstantExpr::from_int( loc, 1 ) } );
-			end = ast::ConstantExpr::from_int( loc, 0 );
-			cmp = "?>=?";
-			update = "--?";
-		}
-
-		ast::ptr< ast::DeclWithType > index = new ast::ObjectDecl{
-			loc, indexName.newName(), new ast::BasicType{ ast::BasicType::SignedInt },
-			new ast::SingleInit{ loc, begin } };
-		ast::ptr< ast::Expr > indexVar = new ast::VariableExpr{ loc, index };
-
-		ast::ptr< ast::Expr > cond = ast::UntypedExpr::createCall(
-			loc, cmp, { indexVar, end } );
-
-		ast::ptr< ast::Expr > inc = ast::UntypedExpr::createCall(
-			loc, update, { indexVar } );
-
-		ast::ptr< ast::Expr > dstIndex = ast::UntypedExpr::createCall(
-			loc, "?[?]", { dstParam, indexVar } );
-
-		// srcParam must keep track of the array indices to build the source parameter and/or
-		// array list initializer
-		srcParam.addArrayIndex( indexVar, array->dimension );
-
-		// for stmt's body, eventually containing call
-		ast::CompoundStmt * body = new ast::CompoundStmt{ loc };
-		ast::ptr< ast::Stmt > listInit = genCall(
-			srcParam, dstIndex, loc, fname, std::back_inserter( body->kids ), array->base, addCast,
-			forward );
-
-		// block containing the stmt and index variable
-		ast::CompoundStmt * block = new ast::CompoundStmt{ loc };
-		block->push_back( new ast::DeclStmt{ loc, index } );
-		if ( listInit ) { block->push_back( listInit ); }
-		block->push_back( new ast::ForStmt{ loc, {}, cond, inc, body } );
-
-		*out++ = block;
-	}
-
 	template< typename OutputIterator >
 	Statement * genCall( InitTweak::InitExpander_old & srcParam, Expression * dstParam, const std::string & fname, OutputIterator out, Type * type, Type * addCast, bool forward ) {
@@ -325,21 +189,4 @@
 		} else {
 			return genScalarCall( srcParam, dstParam, fname, out, type, addCast );
-		}
-	}
-
-	template< typename OutIter >
-	ast::ptr< ast::Stmt > genCall(
-		InitTweak::InitExpander_new & srcParam, const ast::Expr * dstParam,
-		const CodeLocation & loc, const std::string & fname, OutIter && out,
-		const ast::Type * type, const ast::Type * addCast, LoopDirection forward
-	) {
-		if ( auto at = dynamic_cast< const ast::ArrayType * >( type ) ) {
-			genArrayCall(
-				srcParam, dstParam, loc, fname, std::forward< OutIter >(out), at, addCast,
-				forward );
-			return {};
-		} else {
-			return genScalarCall(
-				srcParam, dstParam, loc, fname, std::forward< OutIter >( out ), type, addCast );
 		}
 	}
@@ -379,37 +226,4 @@
 	}
 
-	static inline ast::ptr< ast::Stmt > genImplicitCall(
-		InitTweak::InitExpander_new & srcParam, const ast::Expr * dstParam,
-		const CodeLocation & loc, const std::string & fname, const ast::ObjectDecl * obj,
-		LoopDirection forward = LoopForward
-	) {
-		// unnamed bit fields are not copied as they cannot be accessed
-		if ( isUnnamedBitfield( obj ) ) return {};
-
-		ast::ptr< ast::Type > addCast;
-		if ( (fname == "?{}" || fname == "^?{}") && ( ! obj || ( obj && ! obj->bitfieldWidth ) ) ) {
-			assert( dstParam->result );
-			addCast = dstParam->result;
-		}
-
-		std::vector< ast::ptr< ast::Stmt > > stmts;
-		genCall(
-			srcParam, dstParam, loc, fname, back_inserter( stmts ), obj->type, addCast, forward );
-
-		if ( stmts.empty() ) {
-			return {};
-		} else if ( stmts.size() == 1 ) {
-			const ast::Stmt * callStmt = stmts.front();
-			if ( addCast ) {
-				// implicitly generated ctor/dtor calls should be wrapped so that later passes are
-				// aware they were generated.
-				callStmt = new ast::ImplicitCtorDtorStmt{ callStmt->location, callStmt };
-			}
-			return callStmt;
-		} else {
-			assert( false );
-			return {};
-		}
-	}
 } // namespace SymTab
 
Index: src/SymTab/GenImplicitCall.cpp
===================================================================
--- src/SymTab/GenImplicitCall.cpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
+++ src/SymTab/GenImplicitCall.cpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -0,0 +1,217 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2018 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// GenImplicitCall.hpp --
+//
+// Author           : Andrew Beach
+// Created On       : Fri Apr 14 14:38:00 2023
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Apr 14 14:46:00 2023
+// Update Count     : 0
+//
+
+#include "GenImplicitCall.hpp"
+
+#include "AST/Inspect.hpp"               // for isUnnamedBitfield
+#include "CodeGen/OperatorTable.h"       // for isCtorDtor
+#include "Common/UniqueName.h"           // for UniqueName
+
+namespace SymTab {
+
+template< typename OutIter >
+ast::ptr< ast::Stmt > genCall(
+	InitTweak::InitExpander_new & srcParam, const ast::Expr * dstParam,
+	const CodeLocation & loc, const std::string & fname, OutIter && out,
+	const ast::Type * type, const ast::Type * addCast, LoopDirection forward = LoopForward );
+
+/// inserts into out a generated call expression to function fname with arguments dstParam and
+/// srcParam. Should only be called with non-array types.
+/// optionally returns a statement which must be inserted prior to the containing loop, if
+/// there is one
+template< typename OutIter >
+ast::ptr< ast::Stmt > genScalarCall(
+	InitTweak::InitExpander_new & srcParam, const ast::Expr * dstParam,
+	const CodeLocation & loc, std::string fname, OutIter && out, const ast::Type * type,
+	const ast::Type * addCast = nullptr
+) {
+	bool isReferenceCtorDtor = false;
+	if ( dynamic_cast< const ast::ReferenceType * >( type ) && CodeGen::isCtorDtor( fname ) ) {
+		// reference constructors are essentially application of the rebind operator.
+		// apply & to both arguments, do not need a cast
+		fname = "?=?";
+		dstParam = new ast::AddressExpr( dstParam );
+		addCast = nullptr;
+		isReferenceCtorDtor = true;
+	}
+
+	// want to be able to generate assignment, ctor, and dtor generically, so fname is one of
+	// "?=?", "?{}", or "^?{}"
+	ast::UntypedExpr * fExpr = new ast::UntypedExpr( loc, new ast::NameExpr( loc, fname ) );
+
+	if ( addCast ) {
+		// cast to T& with qualifiers removed, so that qualified objects can be constructed and
+		// destructed with the same functions as non-qualified objects. Unfortunately, lvalue
+		// is considered a qualifier - for AddressExpr to resolve, its argument must have an
+		// lvalue-qualified type, so remove all qualifiers except lvalue.
+		// xxx -- old code actually removed lvalue too...
+		ast::ptr< ast::Type > guard = addCast;  // prevent castType from mutating addCast
+		ast::ptr< ast::Type > castType = addCast;
+		ast::remove_qualifiers(
+			castType,
+			ast::CV::Const | ast::CV::Volatile | ast::CV::Restrict | ast::CV::Atomic );
+			dstParam = new ast::CastExpr{ dstParam, new ast::ReferenceType{ castType } };
+	}
+	fExpr->args.emplace_back( dstParam );
+
+	ast::ptr<ast::Stmt> listInit = srcParam.buildListInit( fExpr );
+
+	// fetch next set of arguments
+	++srcParam;
+
+	// return if adding reference fails -- will happen on default ctor and dtor
+	if ( isReferenceCtorDtor && ! srcParam.addReference() ) return listInit;
+
+	std::vector< ast::ptr< ast::Expr > > args = *srcParam;
+	splice( fExpr->args, args );
+
+	*out++ = new ast::ExprStmt{ loc, fExpr };
+
+	srcParam.clearArrayIndices();
+
+	return listInit;
+}
+
+
+/// Store in out a loop which calls fname on each element of the array with srcParam and
+/// dstParam as arguments. If forward is true, loop goes from 0 to N-1, else N-1 to 0
+template< typename OutIter >
+void genArrayCall(
+	InitTweak::InitExpander_new & srcParam, const ast::Expr * dstParam,
+	const CodeLocation & loc, const std::string & fname, OutIter && out,
+	const ast::ArrayType * array, const ast::Type * addCast = nullptr,
+	LoopDirection forward = LoopForward
+) {
+	static UniqueName indexName( "_index" );
+
+	// for a flexible array member nothing is done -- user must define own assignment
+	if ( ! array->dimension ) return;
+
+	if ( addCast ) {
+		// peel off array layer from cast
+		addCast = strict_dynamic_cast< const ast::ArrayType * >( addCast )->base;
+	}
+
+	ast::ptr< ast::Expr > begin, end;
+	std::string cmp, update;
+
+	if ( forward ) {
+		// generate: for ( int i = 0; i < N; ++i )
+		begin = ast::ConstantExpr::from_int( loc, 0 );
+		end = array->dimension;
+		cmp = "?<?";
+		update = "++?";
+	} else {
+		// generate: for ( int i = N-1; i >= 0; --i )
+		begin = ast::UntypedExpr::createCall( loc, "?-?",
+			{ array->dimension, ast::ConstantExpr::from_int( loc, 1 ) } );
+		end = ast::ConstantExpr::from_int( loc, 0 );
+		cmp = "?>=?";
+		update = "--?";
+	}
+
+	ast::ptr< ast::DeclWithType > index = new ast::ObjectDecl(
+		loc, indexName.newName(), new ast::BasicType( ast::BasicType::SignedInt ),
+		new ast::SingleInit( loc, begin ) );
+	ast::ptr< ast::Expr > indexVar = new ast::VariableExpr( loc, index );
+
+	ast::ptr< ast::Expr > cond = ast::UntypedExpr::createCall(
+		loc, cmp, { indexVar, end } );
+
+	ast::ptr< ast::Expr > inc = ast::UntypedExpr::createCall(
+		loc, update, { indexVar } );
+
+	ast::ptr< ast::Expr > dstIndex = ast::UntypedExpr::createCall(
+		loc, "?[?]", { dstParam, indexVar } );
+
+	// srcParam must keep track of the array indices to build the source parameter and/or
+	// array list initializer
+	srcParam.addArrayIndex( indexVar, array->dimension );
+
+	// for stmt's body, eventually containing call
+	ast::CompoundStmt * body = new ast::CompoundStmt( loc );
+	ast::ptr< ast::Stmt > listInit = genCall(
+		srcParam, dstIndex, loc, fname, std::back_inserter( body->kids ), array->base, addCast,
+		forward );
+
+	// block containing the stmt and index variable
+	ast::CompoundStmt * block = new ast::CompoundStmt( loc );
+	block->push_back( new ast::DeclStmt( loc, index ) );
+	if ( listInit ) { block->push_back( listInit ); }
+	block->push_back( new ast::ForStmt( loc, {}, cond, inc, body ) );
+
+	*out++ = block;
+}
+
+template< typename OutIter >
+ast::ptr< ast::Stmt > genCall(
+	InitTweak::InitExpander_new & srcParam, const ast::Expr * dstParam,
+	const CodeLocation & loc, const std::string & fname, OutIter && out,
+	const ast::Type * type, const ast::Type * addCast, LoopDirection forward
+) {
+	if ( auto at = dynamic_cast< const ast::ArrayType * >( type ) ) {
+		genArrayCall(
+			srcParam, dstParam, loc, fname, std::forward< OutIter >(out), at, addCast,
+			forward );
+		return {};
+	} else {
+		return genScalarCall(
+			srcParam, dstParam, loc, fname, std::forward< OutIter >( out ), type, addCast );
+	}
+}
+
+ast::ptr< ast::Stmt > genImplicitCall(
+	InitTweak::InitExpander_new & srcParam, const ast::Expr * dstParam,
+	const CodeLocation & loc, const std::string & fname, const ast::ObjectDecl * obj,
+	LoopDirection forward
+) {
+	// unnamed bit fields are not copied as they cannot be accessed
+	if ( isUnnamedBitfield( obj ) ) return {};
+
+	ast::ptr< ast::Type > addCast;
+	if ( (fname == "?{}" || fname == "^?{}") && ( ! obj || ( obj && ! obj->bitfieldWidth ) ) ) {
+		assert( dstParam->result );
+		addCast = dstParam->result;
+	}
+
+	std::vector< ast::ptr< ast::Stmt > > stmts;
+	genCall(
+		srcParam, dstParam, loc, fname, back_inserter( stmts ), obj->type, addCast, forward );
+
+	if ( stmts.empty() ) {
+		return {};
+	} else if ( stmts.size() == 1 ) {
+		const ast::Stmt * callStmt = stmts.front();
+		if ( addCast ) {
+			// implicitly generated ctor/dtor calls should be wrapped so that later passes are
+			// aware they were generated.
+			callStmt = new ast::ImplicitCtorDtorStmt( callStmt->location, callStmt );
+		}
+		return callStmt;
+	} else {
+		assert( false );
+		return {};
+	}
+}
+
+} // namespace SymTab
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
+
+
Index: src/SymTab/GenImplicitCall.hpp
===================================================================
--- src/SymTab/GenImplicitCall.hpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
+++ src/SymTab/GenImplicitCall.hpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -0,0 +1,36 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2018 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// GenImplicitCall.hpp --
+//
+// Author           : Andrew Beach
+// Created On       : Fri Apr 14 14:29:00 2023
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Apr 14 14:29:00 2023
+// Update Count     : 0
+//
+
+#pragma once
+
+#include "InitTweak/InitTweak.h"  // for InitExpander
+#include "SymTab/Autogen.h"       // for LoopDirection
+
+namespace SymTab {
+
+ast::ptr<ast::Stmt> genImplicitCall(
+	InitTweak::InitExpander_new & srcParam, const ast::Expr * dstParam,
+	const CodeLocation & loc, const std::string & fname, const ast::ObjectDecl * obj,
+	LoopDirection forward = LoopForward
+);
+
+} // namespace SymTab
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
+
Index: src/SymTab/module.mk
===================================================================
--- src/SymTab/module.mk	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/SymTab/module.mk	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -20,4 +20,6 @@
 	SymTab/FixFunction.cc \
 	SymTab/FixFunction.h \
+	SymTab/GenImplicitCall.cpp \
+	SymTab/GenImplicitCall.hpp \
 	SymTab/Indexer.cc \
 	SymTab/Indexer.h \
Index: src/Validate/Autogen.cpp
===================================================================
--- src/Validate/Autogen.cpp	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/Validate/Autogen.cpp	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -39,4 +39,5 @@
 #include "InitTweak/GenInit.h"     // for fixReturnStatements
 #include "InitTweak/InitTweak.h"   // for isAssignment, isCopyConstructor
+#include "SymTab/GenImplicitCall.hpp"  // for genImplicitCall
 #include "SymTab/Mangler.h"        // for Mangler
 #include "CompilationState.h"
@@ -423,5 +424,5 @@
 	for ( unsigned int index = 0 ; index < fields ; ++index ) {
 		auto member = aggr->members[index].strict_as<ast::DeclWithType>();
-		if ( SymTab::isUnnamedBitfield(
+		if ( ast::isUnnamedBitfield(
 				dynamic_cast<const ast::ObjectDecl *>( member ) ) ) {
 			if ( index == fields - 1 ) {
@@ -599,5 +600,5 @@
 		// Not sure why it could be null.
 		// Don't make a function for a parameter that is an unnamed bitfield.
-		if ( nullptr == field || SymTab::isUnnamedBitfield( field ) ) {
+		if ( nullptr == field || ast::isUnnamedBitfield( field ) ) {
 			continue;
 		// Matching Parameter: Initialize the field by copy.
Index: src/main.cc
===================================================================
--- src/main.cc	(revision c86b08de42d13ee7646e48f36c60ff65633dc0ea)
+++ src/main.cc	(revision 6e1e2d0299e4210d269f7a30ec3864a8bddceabb)
@@ -9,7 +9,7 @@
 // Author           : Peter Buhr and Rob Schluntz
 // Created On       : Fri May 15 23:12:02 2015
-// Last Modified By : Andrew Beach
-// Last Modified On : Thr Feb 16 10:08:00 2023
-// Update Count     : 680
+// Last Modified By : Peter A. Buhr
+// Last Modified On : Mon Apr 10 21:12:17 2023
+// Update Count     : 682
 //
 
@@ -32,4 +32,5 @@
 
 #include "AST/Convert.hpp"
+#include "AST/Util.hpp"                     // for checkInvariants
 #include "CompilationState.h"
 #include "../config.h"                      // for CFA_LIBDIR
@@ -102,10 +103,23 @@
 }
 
-#define PASS( name, pass )                  \
+// Helpers for checkInvariant:
+void checkInvariants( std::list< Declaration * > & ) {}
+using ast::checkInvariants;
+
+#define PASS( name, pass, unit, ... )       \
 	if ( errorp ) { cerr << name << endl; } \
 	NewPass(name);                          \
 	Stats::Time::StartBlock(name);          \
-	pass;                                   \
-	Stats::Time::StopBlock();
+	pass(unit,##__VA_ARGS__);               \
+	Stats::Time::StopBlock();               \
+	if ( invariant ) {                      \
+		checkInvariants(unit);              \
+	}
+
+#define DUMP( cond, unit )                  \
+	if ( cond ) {                           \
+		dump(unit);                         \
+		return EXIT_SUCCESS;                \
+	}
 
 static bool waiting_for_gdb = false;					// flag to set cfa-cpp to wait for gdb on start
@@ -298,8 +312,5 @@
 		transUnit = buildUnit();
 
-		if ( astp ) {
-			dump( std::move( transUnit ) );
-			return EXIT_SUCCESS;
-		} // if
+		DUMP( astp, std::move( transUnit ) );
 
 		Stats::Time::StopBlock();
@@ -310,45 +321,37 @@
 		}
 
-		PASS( "Hoist Type Decls", Validate::hoistTypeDecls( transUnit ) );
-		// Hoist Type Decls pulls some declarations out of contexts where
-		// locations are not tracked. Perhaps they should be, but for now
-		// the full fill solves it.
-		forceFillCodeLocations( transUnit );
-
-		PASS( "Translate Exception Declarations", ControlStruct::translateExcept( transUnit ) );
-		if ( exdeclp ) {
-			dump( std::move( transUnit ) );
-			return EXIT_SUCCESS;
-		}
-
-		PASS( "Verify Ctor, Dtor & Assign", Validate::verifyCtorDtorAssign( transUnit ) );
-		PASS( "Replace Typedefs", Validate::replaceTypedef( transUnit ) );
-		PASS( "Fix Return Types", Validate::fixReturnTypes( transUnit ) );
-		PASS( "Enum and Pointer Decay", Validate::decayEnumsAndPointers( transUnit ) );
-
-		PASS( "Link Reference To Types", Validate::linkReferenceToTypes( transUnit ) );
-
-		PASS( "Fix Qualified Types", Validate::fixQualifiedTypes( transUnit ) );
-		PASS( "Hoist Struct", Validate::hoistStruct( transUnit ) );
-		PASS( "Eliminate Typedef", Validate::eliminateTypedef( transUnit ) );
-		PASS( "Validate Generic Parameters", Validate::fillGenericParameters( transUnit ) );
-		PASS( "Translate Dimensions", Validate::translateDimensionParameters( transUnit ) );
-		PASS( "Check Function Returns", Validate::checkReturnStatements( transUnit ) );
-		PASS( "Fix Return Statements", InitTweak::fixReturnStatements( transUnit ) );
-		PASS( "Implement Concurrent Keywords", Concurrency::implementKeywords( transUnit ) );
-		PASS( "Forall Pointer Decay", Validate::decayForallPointers( transUnit ) );
-        PASS( "Implement Waituntil", Concurrency::generateWaitUntil( transUnit ) );
-		PASS( "Hoist Control Declarations", ControlStruct::hoistControlDecls( transUnit ) );
-
-		PASS( "Generate Autogen Routines", Validate::autogenerateRoutines( transUnit ) );
-
-		PASS( "Implement Actors", Concurrency::implementActors( transUnit ) );
-        PASS( "Implement Virtual Destructors", Virtual::implementVirtDtors(transUnit) );
-		PASS( "Implement Mutex", Concurrency::implementMutex( transUnit ) );
-		PASS( "Implement Thread Start", Concurrency::implementThreadStarter( transUnit ) );
-		PASS( "Compound Literal", Validate::handleCompoundLiterals( transUnit ) );
-		PASS( "Set Length From Initializer", Validate::setLengthFromInitializer( transUnit ) );
-		PASS( "Find Global Decls", Validate::findGlobalDecls( transUnit ) );
-		PASS( "Fix Label Address", Validate::fixLabelAddresses( transUnit ) );
+		PASS( "Hoist Type Decls", Validate::hoistTypeDecls, transUnit );
+
+		PASS( "Translate Exception Declarations", ControlStruct::translateExcept, transUnit );
+		DUMP( exdeclp, std::move( transUnit ) );
+		PASS( "Verify Ctor, Dtor & Assign", Validate::verifyCtorDtorAssign, transUnit );
+		PASS( "Replace Typedefs", Validate::replaceTypedef, transUnit );
+		PASS( "Fix Return Types", Validate::fixReturnTypes, transUnit );
+		PASS( "Enum and Pointer Decay", Validate::decayEnumsAndPointers, transUnit );
+
+		PASS( "Link Reference To Types", Validate::linkReferenceToTypes, transUnit );
+
+		PASS( "Fix Qualified Types", Validate::fixQualifiedTypes, transUnit );
+		PASS( "Hoist Struct", Validate::hoistStruct, transUnit );
+		PASS( "Eliminate Typedef", Validate::eliminateTypedef, transUnit );
+		PASS( "Validate Generic Parameters", Validate::fillGenericParameters, transUnit );
+		PASS( "Translate Dimensions", Validate::translateDimensionParameters, transUnit );
+		PASS( "Check Function Returns", Validate::checkReturnStatements, transUnit );
+		PASS( "Fix Return Statements", InitTweak::fixReturnStatements, transUnit );
+		PASS( "Implement Concurrent Keywords", Concurrency::implementKeywords, transUnit );
+		PASS( "Forall Pointer Decay", Validate::decayForallPointers, transUnit );
+        PASS( "Implement Waituntil", Concurrency::generateWaitUntil, transUnit  );
+		PASS( "Hoist Control Declarations", ControlStruct::hoistControlDecls, transUnit );
+
+		PASS( "Generate Autogen Routines", Validate::autogenerateRoutines, transUnit );
+
+		PASS( "Implement Actors", Concurrency::implementActors, transUnit );
+		PASS( "Implement Virtual Destructors", Virtual::implementVirtDtors, transUnit );
+		PASS( "Implement Mutex", Concurrency::implementMutex, transUnit );
+		PASS( "Implement Thread Start", Concurrency::implementThreadStarter, transUnit );
+		PASS( "Compound Literal", Validate::handleCompoundLiterals, transUnit );
+		PASS( "Set Length From Initializer", Validate::setLengthFromInitializer, transUnit );
+		PASS( "Find Global Decls", Validate::findGlobalDecls, transUnit );
+		PASS( "Fix Label Address", Validate::fixLabelAddresses, transUnit );
 
 		if ( symtabp ) {
@@ -361,14 +364,11 @@
 		} // if
 
-		if ( validp ) {
-			dump( std::move( transUnit ) );
-			return EXIT_SUCCESS;
-		} // if
-
-		PASS( "Translate Throws", ControlStruct::translateThrows( transUnit ) );
-		PASS( "Fix Labels", ControlStruct::fixLabels( transUnit ) );
-		PASS( "Fix Names", CodeGen::fixNames( transUnit ) );
-		PASS( "Gen Init", InitTweak::genInit( transUnit ) );
-		PASS( "Expand Member Tuples" , Tuples::expandMemberTuples( transUnit ) );
+		DUMP( validp, std::move( transUnit ) );
+
+		PASS( "Translate Throws", ControlStruct::translateThrows, transUnit );
+		PASS( "Fix Labels", ControlStruct::fixLabels, transUnit );
+		PASS( "Fix Names", CodeGen::fixNames, transUnit );
+		PASS( "Gen Init", InitTweak::genInit, transUnit );
+		PASS( "Expand Member Tuples" , Tuples::expandMemberTuples, transUnit );
 
 		if ( libcfap ) {
@@ -382,8 +382,5 @@
 		} // if
 
-		if ( bresolvep ) {
-			dump( std::move( transUnit ) );
-			return EXIT_SUCCESS;
-		} // if
+		DUMP( bresolvep, std::move( transUnit ) );
 
 		if ( resolvprotop ) {
@@ -392,63 +389,42 @@
 		} // if
 
-		PASS( "Resolve", ResolvExpr::resolve( transUnit ) );
-		if ( exprp ) {
-			dump( std::move( transUnit ) );
-			return EXIT_SUCCESS;
-		} // if
-
-		forceFillCodeLocations( transUnit );
-
-		PASS( "Fix Init", InitTweak::fix(transUnit, buildingLibrary()));
+		PASS( "Resolve", ResolvExpr::resolve, transUnit );
+		DUMP( exprp, std::move( transUnit ) );
+
+		PASS( "Fix Init", InitTweak::fix, transUnit, buildingLibrary() );
 
 		// fix ObjectDecl - replaces ConstructorInit nodes
-		if ( ctorinitp ) {
-			dump( std::move( transUnit ) );
-			return EXIT_SUCCESS;
-		} // if
+		DUMP( ctorinitp, std::move( transUnit ) );
 
 		// Currently not working due to unresolved issues with UniqueExpr
-		PASS( "Expand Unique Expr", Tuples::expandUniqueExpr( transUnit ) ); // xxx - is this the right place for this? want to expand ASAP so tha, sequent 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
-
-		PASS( "Translate Tries", ControlStruct::translateTries( transUnit ) );
-		PASS( "Gen Waitfor", Concurrency::generateWaitFor( transUnit ) );
+		PASS( "Expand Unique Expr", Tuples::expandUniqueExpr, transUnit ); // xxx - is this the right place for this? want to expand ASAP so tha, sequent 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
+
+		PASS( "Translate Tries", ControlStruct::translateTries, transUnit );
+		PASS( "Gen Waitfor", Concurrency::generateWaitFor, transUnit );
 
 		// Needs to happen before tuple types are expanded.
-		PASS( "Convert Specializations",  GenPoly::convertSpecializations( transUnit ) );
-
-		PASS( "Expand Tuples", Tuples::expandTuples( transUnit ) );
-
-		if ( tuplep ) {
-			dump( std::move( transUnit ) );
-			return EXIT_SUCCESS;
-		} // if
+		PASS( "Convert Specializations",  GenPoly::convertSpecializations, transUnit );
+
+		PASS( "Expand Tuples", Tuples::expandTuples, transUnit );
+		DUMP( tuplep, std::move( transUnit ) );
 
 		// Must come after Translate Tries.
-		PASS( "Virtual Expand Casts", Virtual::expandCasts( transUnit ) );
-
-		PASS( "Instantiate Generics", GenPoly::instantiateGeneric( transUnit ) );
-		if ( genericsp ) {
-			dump( std::move( transUnit ) );
-			return EXIT_SUCCESS;
-		} // if
-
-		PASS( "Convert L-Value", GenPoly::convertLvalue( transUnit ) );
+		PASS( "Virtual Expand Casts", Virtual::expandCasts, transUnit );
+
+		PASS( "Instantiate Generics", GenPoly::instantiateGeneric, transUnit );
+		DUMP( genericsp, std::move( transUnit ) );
+
+		PASS( "Convert L-Value", GenPoly::convertLvalue, transUnit );
 
 		translationUnit = convert( std::move( transUnit ) );
 
-		if ( bboxp ) {
-			dump( translationUnit );
-			return EXIT_SUCCESS;
-		} // if
-		PASS( "Box", GenPoly::box( translationUnit ) );
-
-		PASS( "Link-Once", CodeGen::translateLinkOnce( translationUnit ) );
+		DUMP( bboxp, translationUnit );
+		PASS( "Box", GenPoly::box, translationUnit );
+
+		PASS( "Link-Once", CodeGen::translateLinkOnce, translationUnit );
 
 		// Code has been lowered to C, now we can start generation.
 
-		if ( bcodegenp ) {
-			dump( translationUnit );
-			return EXIT_SUCCESS;
-		} // if
+		DUMP( bcodegenp, translationUnit );
 
 		if ( optind < argc ) {							// any commands after the flags and input file ? => output file name
@@ -457,5 +433,5 @@
 
 		CodeTools::fillLocations( translationUnit );
-		PASS( "Code Gen", CodeGen::generate( translationUnit, *output, ! genproto, prettycodegenp, true, linemarks ) );
+		PASS( "Code Gen", CodeGen::generate, translationUnit, *output, ! genproto, prettycodegenp, true, linemarks );
 
 		CodeGen::FixMain::fix( translationUnit, *output,
@@ -505,5 +481,5 @@
 
 
-static const char optstring[] = ":c:ghlLmNnpdP:S:twW:D:";
+static const char optstring[] = ":c:ghilLmNnpdP:S:twW:D:";
 
 enum { PreludeDir = 128 };
@@ -512,7 +488,8 @@
 	{ "gdb", no_argument, nullptr, 'g' },
 	{ "help", no_argument, nullptr, 'h' },
+	{ "invariant", no_argument, nullptr, 'i' },
 	{ "libcfa", no_argument, nullptr, 'l' },
 	{ "linemarks", no_argument, nullptr, 'L' },
-	{ "no-main", no_argument, 0, 'm' },
+	{ "no-main", no_argument, nullptr, 'm' },
 	{ "no-linemarks", no_argument, nullptr, 'N' },
 	{ "no-prelude", no_argument, nullptr, 'n' },
@@ -533,4 +510,5 @@
 	"wait for gdb to attach",							// -g
 	"print translator help message",					// -h
+	"invariant checking during AST passes",				// -i
 	"generate libcfa.c",								// -l
 	"generate line marks",								// -L
@@ -626,4 +604,7 @@
 			usage( argv );								// no return
 			break;
+		  case 'i':										// invariant checking
+			invariant = true;
+			break;
 		  case 'l':										// generate libcfa.c
 			libcfap = true;
