Index: src/Tuples/TupleAssignment.cc
===================================================================
--- src/Tuples/TupleAssignment.cc	(revision 0bd3faf870391e5ed803287ec0b3c696cfde22b2)
+++ src/Tuples/TupleAssignment.cc	(revision e580aa5743b01d3bef64004c1525a55b36048d7b)
@@ -46,329 +46,331 @@
 
 namespace {
-	/// true if `expr` is of tuple type
-	bool isTuple( const ast::Expr * expr ) {
-		if ( ! expr ) return false;
-		assert( expr->result );
-		return dynamic_cast< const ast::TupleType * >( expr->result->stripReferences() );
+
+/// Checks if `expr` is of tuple type.
+bool isTuple( const ast::Expr * expr ) {
+	if ( !expr ) return false;
+	assert( expr->result );
+	return dynamic_cast< const ast::TupleType * >( expr->result->stripReferences() );
+}
+
+/// Checks if `expr` is of tuple type or a cast to one.
+bool refToTuple( const ast::Expr * expr ) {
+	assert( expr->result );
+	// Check for function returning tuple of reference types.
+	if ( auto castExpr = dynamic_cast< const ast::CastExpr * >( expr ) ) {
+		return refToTuple( castExpr->arg );
+	} else {
+		return isTuple( expr );
 	}
-
-	/// true if `expr` is of tuple type or a reference to one
-	bool refToTuple( const ast::Expr * expr ) {
-		assert( expr->result );
-		// check for function returning tuple of reference types
-		if ( auto castExpr = dynamic_cast< const ast::CastExpr * >( expr ) ) {
-			return refToTuple( castExpr->arg );
-		} else {
-			return isTuple( expr );
-		}
-	}
-
-	/// Dispatcher for tuple (multiple and mass) assignment operations
-	class TupleAssignSpotter final {
-		/// Actually finds tuple assignment operations, by subclass
-		struct Matcher {
-			ResolvExpr::CandidateList lhs, rhs;
-			TupleAssignSpotter & spotter;
-			CodeLocation location;
-			ResolvExpr::Cost baseCost;
-			std::vector< ast::ptr< ast::ObjectDecl > > tmpDecls;
-			ast::TypeEnvironment env;
-			ast::OpenVarSet open;
-			ast::AssertionSet need;
-
-			void combineState( const ResolvExpr::Candidate & cand ) {
-				env.simpleCombine( cand.env );
-				ast::mergeOpenVars( open, cand.open );
-				need.insert( cand.need.begin(), cand.need.end() );
-			}
-
-			Matcher(
-				TupleAssignSpotter & s, const CodeLocation & loc,
-				const ResolvExpr::CandidateList & l, const ResolvExpr::CandidateList & r )
-			: lhs( l ), rhs( r ), spotter( s ), location( loc ),
-			  baseCost( ResolvExpr::sumCost( lhs ) + ResolvExpr::sumCost( rhs ) ), tmpDecls(),
-			  env(), open(), need() {
-				for ( auto & cand : lhs ) combineState( *cand );
-				for ( auto & cand : rhs ) combineState( *cand );
-			}
-			virtual ~Matcher() = default;
-
-			virtual std::vector< ast::ptr< ast::Expr > > match() = 0;
-
-			/// removes environments from subexpressions within statement expressions, which could
-			/// throw off later passes like those in Box which rely on PolyMutator, and adds the
-			/// bindings to the env
-			struct EnvRemover {
-				/// environment to hoist ExprStmt environments to
-				ast::TypeEnvironment & tenv;
-
-				EnvRemover( ast::TypeEnvironment & e ) : tenv( e ) {}
-
-				const ast::ExprStmt * previsit( const ast::ExprStmt * stmt ) {
-					if ( stmt->expr->env ) {
-						tenv.add( *stmt->expr->env );
-						ast::ExprStmt * mut = mutate( stmt );
-						mut->expr.get_and_mutate()->env = nullptr;
-						return mut;
-					}
-					return stmt;
+}
+
+/// Dispatcher for tuple (multiple and mass) assignment operations.
+class TupleAssignSpotter final {
+	/// Actually finds tuple assignment operations, by subclass.
+	struct Matcher {
+		ResolvExpr::CandidateList lhs, rhs;
+		TupleAssignSpotter & spotter;
+		CodeLocation location;
+		ResolvExpr::Cost baseCost;
+		std::vector< ast::ptr< ast::ObjectDecl > > tmpDecls;
+		ast::TypeEnvironment env;
+		ast::OpenVarSet open;
+		ast::AssertionSet need;
+
+		void combineState( const ResolvExpr::Candidate & cand ) {
+			env.simpleCombine( cand.env );
+			ast::mergeOpenVars( open, cand.open );
+			need.insert( cand.need.begin(), cand.need.end() );
+		}
+
+		Matcher(
+			TupleAssignSpotter & s, const CodeLocation & loc,
+			const ResolvExpr::CandidateList & l, const ResolvExpr::CandidateList & r )
+		: lhs( l ), rhs( r ), spotter( s ), location( loc ),
+		  baseCost( ResolvExpr::sumCost( lhs ) + ResolvExpr::sumCost( rhs ) ), tmpDecls(),
+		  env(), open(), need() {
+			for ( auto & cand : lhs ) combineState( *cand );
+			for ( auto & cand : rhs ) combineState( *cand );
+		}
+		virtual ~Matcher() = default;
+
+		virtual std::vector< ast::ptr< ast::Expr > > match() = 0;
+
+		/// Removes environments from subexpressions within statement expressions, which could
+		/// throw off later passes like those in Box which rely on PolyMutator, and adds the
+		/// bindings to the env.
+		struct EnvRemover {
+			/// Environment to hoist ExprStmt environments to.
+			ast::TypeEnvironment & tenv;
+
+			EnvRemover( ast::TypeEnvironment & e ) : tenv( e ) {}
+
+			const ast::ExprStmt * previsit( const ast::ExprStmt * stmt ) {
+				if ( stmt->expr->env ) {
+					tenv.add( *stmt->expr->env );
+					ast::ExprStmt * mut = mutate( stmt );
+					mut->expr.get_and_mutate()->env = nullptr;
+					return mut;
 				}
-			};
-
-			ast::ObjectDecl * newObject( UniqueName & namer, const ast::Expr * expr ) {
-				assert( expr->result && ! expr->result->isVoid() );
-
-				ast::ObjectDecl * ret = new ast::ObjectDecl{
-					location, namer.newName(), expr->result, new ast::SingleInit{ location, expr },
-					ast::Storage::Classes{}, ast::Linkage::Cforall };
-
-				// if expression type is a reference, just need an initializer, otherwise construct
-				if ( ! expr->result.as< ast::ReferenceType >() ) {
-					// resolve ctor/dtor for the new object
-					ast::ptr< ast::Init > ctorInit = ResolvExpr::resolveCtorInit(
-							InitTweak::genCtorInit( location, ret ), spotter.crntFinder.context );
-					// remove environments from subexpressions of stmtExpr
-					ast::Pass< EnvRemover > rm{ env };
-					ret->init = ctorInit->accept( rm );
+				return stmt;
+			}
+		};
+
+		ast::ObjectDecl * newObject( UniqueName & namer, const ast::Expr * expr ) {
+			assert( expr->result && !expr->result->isVoid() );
+
+			ast::ObjectDecl * ret = new ast::ObjectDecl(
+				location, namer.newName(), expr->result, new ast::SingleInit( location, expr ),
+				ast::Storage::Classes{}, ast::Linkage::Cforall );
+
+			// If expression type is a reference, just need an initializer, otherwise construct.
+			if ( ! expr->result.as< ast::ReferenceType >() ) {
+				// Resolve ctor/dtor for the new object.
+				ast::ptr< ast::Init > ctorInit = ResolvExpr::resolveCtorInit(
+						InitTweak::genCtorInit( location, ret ), spotter.crntFinder.context );
+				// Remove environments from subexpressions of stmtExpr.
+				ast::Pass< EnvRemover > rm( env );
+				ret->init = ctorInit->accept( rm );
+			}
+
+			PRINT( std::cerr << "new object: " << ret << std::endl; )
+			return ret;
+		}
+
+		ast::UntypedExpr * createFunc(
+			const std::string & fname, const ast::ObjectDecl * left,
+			const ast::ObjectDecl * right
+		) {
+			assert( left );
+			std::vector< ast::ptr< ast::Expr > > args;
+			args.emplace_back( new ast::VariableExpr( location, left ) );
+			if ( right ) { args.emplace_back( new ast::VariableExpr( location, right ) ); }
+
+			if ( left->type->referenceDepth() > 1 && CodeGen::isConstructor( fname ) ) {
+				args.front() = new ast::AddressExpr( location, args.front() );
+				if ( right ) { args.back() = new ast::AddressExpr( location, args.back() ); }
+				return new ast::UntypedExpr(
+					location, new ast::NameExpr( location, "?=?" ), std::move( args ) );
+			} else {
+				return new ast::UntypedExpr(
+					location, new ast::NameExpr( location, fname ), std::move( args ) );
+			}
+		}
+	};
+
+	/// Finds mass-assignment operations.
+	struct MassAssignMatcher final : public Matcher {
+		MassAssignMatcher(
+			TupleAssignSpotter & s, const CodeLocation & loc,
+			const ResolvExpr::CandidateList & l, const ResolvExpr::CandidateList & r )
+		: Matcher( s, loc, l, r ) {}
+
+		std::vector< ast::ptr< ast::Expr > > match() override {
+			static UniqueName lhsNamer( "__massassign_L" );
+			static UniqueName rhsNamer( "__massassign_R" );
+			// Empty tuple case falls into this matcher.
+			assert( lhs.empty() ? rhs.empty() : rhs.size() <= 1 );
+
+			ast::ptr< ast::ObjectDecl > rtmp =
+				1 == rhs.size() ? newObject( rhsNamer, rhs.front()->expr ) : nullptr;
+
+			std::vector< ast::ptr< ast::Expr > > out;
+			for ( ResolvExpr::CandidateRef & lhsCand : lhs ) {
+				// Create a temporary object for each value in
+				// the LHS and create a call involving the RHS.
+				ast::ptr< ast::ObjectDecl > ltmp = newObject( lhsNamer, lhsCand->expr );
+				out.emplace_back( createFunc( spotter.fname, ltmp, rtmp ) );
+				tmpDecls.emplace_back( std::move( ltmp ) );
+			}
+			if ( rtmp ) tmpDecls.emplace_back( std::move( rtmp ) );
+
+			return out;
+		}
+	};
+
+	/// Finds multiple-assignment operations.
+	struct MultipleAssignMatcher final : public Matcher {
+		MultipleAssignMatcher(
+			TupleAssignSpotter & s, const CodeLocation & loc,
+			const ResolvExpr::CandidateList & l, const ResolvExpr::CandidateList & r )
+		: Matcher( s, loc, l, r ) {}
+
+		std::vector< ast::ptr< ast::Expr > > match() override {
+			static UniqueName lhsNamer( "__multassign_L" );
+			static UniqueName rhsNamer( "__multassign_R" );
+
+			if ( lhs.size() != rhs.size() ) return {};
+
+			// Produce a new temporary object for each value in
+			// the LHS and RHS and pairwise create the calls.
+			std::vector< ast::ptr< ast::ObjectDecl > > ltmp, rtmp;
+
+			std::vector< ast::ptr< ast::Expr > > out;
+			for ( unsigned i = 0; i < lhs.size(); ++i ) {
+				ResolvExpr::CandidateRef & lhsCand = lhs[i];
+				ResolvExpr::CandidateRef & rhsCand = rhs[i];
+
+				// Convert RHS to LHS type minus one reference --
+				// important for case where LHS is && and RHS is lvalue.
+				auto lhsType = lhsCand->expr->result.strict_as< ast::ReferenceType >();
+				rhsCand->expr = new ast::CastExpr( rhsCand->expr, lhsType->base );
+				ast::ptr< ast::ObjectDecl > lobj = newObject( lhsNamer, lhsCand->expr );
+				ast::ptr< ast::ObjectDecl > robj = newObject( rhsNamer, rhsCand->expr );
+				out.emplace_back( createFunc( spotter.fname, lobj, robj ) );
+				ltmp.emplace_back( std::move( lobj ) );
+				rtmp.emplace_back( std::move( robj ) );
+
+				// Resolve the cast expression so that rhsCand return type is bound
+				// by the cast type as needed, and transfer the resulting environment.
+				ResolvExpr::CandidateFinder finder( spotter.crntFinder.context, env );
+				finder.find( rhsCand->expr, ResolvExpr::ResolvMode::withAdjustment() );
+				assert( 1 == finder.candidates.size() );
+				env = std::move( finder.candidates.front()->env );
+			}
+
+			splice( tmpDecls, ltmp );
+			splice( tmpDecls, rtmp );
+
+			return out;
+		}
+	};
+
+	ResolvExpr::CandidateFinder & crntFinder;
+	std::string fname;
+	std::unique_ptr< Matcher > matcher;
+
+public:
+	TupleAssignSpotter( ResolvExpr::CandidateFinder & f )
+	: crntFinder( f ), fname(), matcher() {}
+
+	// Find left- and right-hand-sides for mass or multiple assignment.
+	void spot(
+		const ast::UntypedExpr * expr, std::vector< ResolvExpr::CandidateFinder > & args
+	) {
+		if ( auto op = expr->func.as< ast::NameExpr >() ) {
+			// Skip non-assignment functions.
+			if ( !CodeGen::isCtorDtorAssign( op->name ) ) return;
+			fname = op->name;
+
+			// Handled by CandidateFinder if applicable (both odd cases).
+			if ( args.empty() || ( 1 == args.size() && CodeGen::isAssignment( fname ) ) ) {
+				return;
+			}
+
+			// Look over all possible left-hand-side.
+			for ( ResolvExpr::CandidateRef & lhsCand : args[0] ) {
+				// Skip non-tuple LHS.
+				if ( !refToTuple( lhsCand->expr ) ) continue;
+
+				// Explode is aware of casts - ensure every LHS
+				// is sent into explode with a reference cast.
+				if ( !lhsCand->expr.as< ast::CastExpr >() ) {
+					lhsCand->expr = new ast::CastExpr(
+						lhsCand->expr, new ast::ReferenceType( lhsCand->expr->result ) );
 				}
 
-				PRINT( std::cerr << "new object: " << ret << std::endl; )
-				return ret;
-			}
-
-			ast::UntypedExpr * createFunc(
-				const std::string & fname, const ast::ObjectDecl * left,
-				const ast::ObjectDecl * right
-			) {
-				assert( left );
-				std::vector< ast::ptr< ast::Expr > > args;
-				args.emplace_back( new ast::VariableExpr{ location, left } );
-				if ( right ) { args.emplace_back( new ast::VariableExpr{ location, right } ); }
-
-				if ( left->type->referenceDepth() > 1 && CodeGen::isConstructor( fname ) ) {
-					args.front() = new ast::AddressExpr{ location, args.front() };
-					if ( right ) { args.back() = new ast::AddressExpr{ location, args.back() }; }
-					return new ast::UntypedExpr{
-						location, new ast::NameExpr{ location, "?=?" }, std::move(args) };
-				} else {
-					return new ast::UntypedExpr{
-						location, new ast::NameExpr{ location, fname }, std::move(args) };
-				}
-			}
-		};
-
-		/// Finds mass-assignment operations
-		struct MassAssignMatcher final : public Matcher {
-			MassAssignMatcher(
-				TupleAssignSpotter & s, const CodeLocation & loc,
-				const ResolvExpr::CandidateList & l, const ResolvExpr::CandidateList & r )
-			: Matcher( s, loc, l, r ) {}
-
-			std::vector< ast::ptr< ast::Expr > > match() override {
-				static UniqueName lhsNamer( "__massassign_L" );
-				static UniqueName rhsNamer( "__massassign_R" );
-				// empty tuple case falls into this matcher
-				assert( lhs.empty() ? rhs.empty() : rhs.size() <= 1 );
-
-				ast::ptr< ast::ObjectDecl > rtmp =
-					rhs.size() == 1 ? newObject( rhsNamer, rhs.front()->expr ) : nullptr;
-
-				std::vector< ast::ptr< ast::Expr > > out;
-				for ( ResolvExpr::CandidateRef & lhsCand : lhs ) {
-					// create a temporary object for each value in the LHS and create a call
-					// involving the RHS
-					ast::ptr< ast::ObjectDecl > ltmp = newObject( lhsNamer, lhsCand->expr );
-					out.emplace_back( createFunc( spotter.fname, ltmp, rtmp ) );
-					tmpDecls.emplace_back( std::move( ltmp ) );
-				}
-				if ( rtmp ) tmpDecls.emplace_back( std::move( rtmp ) );
-
-				return out;
-			}
-		};
-
-		/// Finds multiple-assignment operations
-		struct MultipleAssignMatcher final : public Matcher {
-			MultipleAssignMatcher(
-				TupleAssignSpotter & s, const CodeLocation & loc,
-				const ResolvExpr::CandidateList & l, const ResolvExpr::CandidateList & r )
-			: Matcher( s, loc, l, r ) {}
-
-			std::vector< ast::ptr< ast::Expr > > match() override {
-				static UniqueName lhsNamer( "__multassign_L" );
-				static UniqueName rhsNamer( "__multassign_R" );
-
-				if ( lhs.size() != rhs.size() ) return {};
-
-				// produce a new temporary object for each value in the LHS and RHS and pairwise
-				// create the calls
-				std::vector< ast::ptr< ast::ObjectDecl > > ltmp, rtmp;
-
-				std::vector< ast::ptr< ast::Expr > > out;
-				for ( unsigned i = 0; i < lhs.size(); ++i ) {
-					ResolvExpr::CandidateRef & lhsCand = lhs[i];
-					ResolvExpr::CandidateRef & rhsCand = rhs[i];
-
-					// convert RHS to LHS type minus one reference -- important for case where LHS
-					// is && and RHS is lvalue
-					auto lhsType = lhsCand->expr->result.strict_as< ast::ReferenceType >();
-					rhsCand->expr = new ast::CastExpr{ rhsCand->expr, lhsType->base };
-					ast::ptr< ast::ObjectDecl > lobj = newObject( lhsNamer, lhsCand->expr );
-					ast::ptr< ast::ObjectDecl > robj = newObject( rhsNamer, rhsCand->expr );
-					out.emplace_back( createFunc( spotter.fname, lobj, robj ) );
-					ltmp.emplace_back( std::move( lobj ) );
-					rtmp.emplace_back( std::move( robj ) );
-
-					// resolve the cast expression so that rhsCand return type is bound by the cast
-					// type as needed, and transfer the resulting environment
-					ResolvExpr::CandidateFinder finder( spotter.crntFinder.context, env );
-					finder.find( rhsCand->expr, ResolvExpr::ResolvMode::withAdjustment() );
-					assert( finder.candidates.size() == 1 );
-					env = std::move( finder.candidates.front()->env );
-				}
-
-				splice( tmpDecls, ltmp );
-				splice( tmpDecls, rtmp );
-
-				return out;
-			}
-		};
-
-		ResolvExpr::CandidateFinder & crntFinder;
-		std::string fname;
-		std::unique_ptr< Matcher > matcher;
-
-	public:
-		TupleAssignSpotter( ResolvExpr::CandidateFinder & f )
-		: crntFinder( f ), fname(), matcher() {}
-
-		// find left- and right-hand-sides for mass or multiple assignment
-		void spot(
-			const ast::UntypedExpr * expr, std::vector< ResolvExpr::CandidateFinder > & args
-		) {
-			if ( auto op = expr->func.as< ast::NameExpr >() ) {
-				// skip non-assignment functions
-				if ( ! CodeGen::isCtorDtorAssign( op->name ) ) return;
-				fname = op->name;
-
-				// handled by CandidateFinder if applicable (both odd cases)
-				if ( args.empty() || ( args.size() == 1 && CodeGen::isAssignment( fname ) ) ) {
-					return;
-				}
-
-				// look over all possible left-hand-side
-				for ( ResolvExpr::CandidateRef & lhsCand : args[0] ) {
-					// skip non-tuple LHS
-					if ( ! refToTuple( lhsCand->expr ) ) continue;
-
-					// explode is aware of casts - ensure every LHS is sent into explode with a
-					// reference cast
-					if ( ! lhsCand->expr.as< ast::CastExpr >() ) {
-						lhsCand->expr = new ast::CastExpr{
-							lhsCand->expr, new ast::ReferenceType{ lhsCand->expr->result } };
-					}
-
-					// explode the LHS so that each field of a tuple-valued expr is assigned
-					ResolvExpr::CandidateList lhs;
-					explode( *lhsCand, crntFinder.context.symtab, back_inserter(lhs), true );
-					for ( ResolvExpr::CandidateRef & cand : lhs ) {
-						// each LHS value must be a reference - some come in with a cast, if not
-						// just cast to reference here
-						if ( ! cand->expr->result.as< ast::ReferenceType >() ) {
-							cand->expr = new ast::CastExpr{
-								cand->expr, new ast::ReferenceType{ cand->expr->result } };
-						}
-					}
-
-					if ( args.size() == 1 ) {
-						// mass default-initialization/destruction
-						ResolvExpr::CandidateList rhs{};
-						matcher.reset( new MassAssignMatcher{ *this, expr->location, lhs, rhs } );
-						match();
-					} else if ( args.size() == 2 ) {
-						for ( const ResolvExpr::CandidateRef & rhsCand : args[1] ) {
-							ResolvExpr::CandidateList rhs;
-							if ( isTuple( rhsCand->expr ) ) {
-								// multiple assignment
-								explode( *rhsCand, crntFinder.context.symtab, back_inserter(rhs), true );
-								matcher.reset(
-									new MultipleAssignMatcher{ *this, expr->location, lhs, rhs } );
-							} else {
-								// mass assignment
-								rhs.emplace_back( rhsCand );
-								matcher.reset(
-									new MassAssignMatcher{ *this, expr->location, lhs, rhs } );
-							}
-							match();
-						}
-					} else {
-						// expand all possible RHS possibilities
-						std::vector< ResolvExpr::CandidateList > rhsCands;
-						combos(
-							std::next( args.begin(), 1 ), args.end(), back_inserter( rhsCands ) );
-						for ( const ResolvExpr::CandidateList & rhsCand : rhsCands ) {
-							// multiple assignment
-							ResolvExpr::CandidateList rhs;
-							explode( rhsCand, crntFinder.context.symtab, back_inserter(rhs), true );
-							matcher.reset(
-								new MultipleAssignMatcher{ *this, expr->location, lhs, rhs } );
-							match();
-						}
+				// Explode the LHS so that each field of a tuple-valued expr is assigned.
+				ResolvExpr::CandidateList lhs;
+				explode( *lhsCand, crntFinder.context.symtab, back_inserter(lhs), true );
+				for ( ResolvExpr::CandidateRef & cand : lhs ) {
+					// Each LHS value must be a reference - some come in
+					// with a cast, if not just cast to reference here.
+					if ( !cand->expr->result.as< ast::ReferenceType >() ) {
+						cand->expr = new ast::CastExpr(
+							cand->expr, new ast::ReferenceType( cand->expr->result ) );
 					}
 				}
-			}
-		}
-
-		void match() {
-			assert( matcher );
-
-			std::vector< ast::ptr< ast::Expr > > newAssigns = matcher->match();
-
-			if ( ! ( matcher->lhs.empty() && matcher->rhs.empty() ) ) {
-				// if both LHS and RHS are empty than this is the empty tuple case, wherein it's
-				// okay for newAssigns to be empty. Otherwise, return early so that no new
-				// candidates are generated
-				if ( newAssigns.empty() ) return;
-			}
-
-			ResolvExpr::CandidateList crnt;
-			// now resolve new assignments
-			for ( const ast::Expr * expr : newAssigns ) {
-				PRINT(
-					std::cerr << "== resolving tuple assign ==" << std::endl;
-					std::cerr << expr << std::endl;
-				)
-
-				ResolvExpr::CandidateFinder finder( crntFinder.context, matcher->env );
-				finder.allowVoid = true;
-
-				try {
-					finder.find( expr, ResolvExpr::ResolvMode::withAdjustment() );
-				} catch (...) {
-					// no match is not failure, just that this tuple assignment is invalid
-					return;
+
+				if ( 1 == args.size() ) {
+					// Mass default-initialization/destruction.
+					ResolvExpr::CandidateList rhs{};
+					matcher.reset( new MassAssignMatcher( *this, expr->location, lhs, rhs ) );
+					match();
+				} else if ( 2 == args.size() ) {
+					for ( const ResolvExpr::CandidateRef & rhsCand : args[1] ) {
+						ResolvExpr::CandidateList rhs;
+						if ( isTuple( rhsCand->expr ) ) {
+							// Multiple assignment:
+							explode( *rhsCand, crntFinder.context.symtab, back_inserter( rhs ), true );
+							matcher.reset(
+								new MultipleAssignMatcher( *this, expr->location, lhs, rhs ) );
+						} else {
+							// Mass assignment:
+							rhs.emplace_back( rhsCand );
+							matcher.reset(
+								new MassAssignMatcher( *this, expr->location, lhs, rhs ) );
+						}
+						match();
+					}
+				} else {
+					// Expand all possible RHS possibilities.
+					std::vector< ResolvExpr::CandidateList > rhsCands;
+					combos(
+						std::next( args.begin(), 1 ), args.end(), back_inserter( rhsCands ) );
+					for ( const ResolvExpr::CandidateList & rhsCand : rhsCands ) {
+						// Multiple assignment:
+						ResolvExpr::CandidateList rhs;
+						explode( rhsCand, crntFinder.context.symtab, back_inserter( rhs ), true );
+						matcher.reset(
+							new MultipleAssignMatcher( *this, expr->location, lhs, rhs ) );
+						match();
+					}
 				}
-
-				ResolvExpr::CandidateList & cands = finder.candidates;
-				assert( cands.size() == 1 );
-				assert( cands.front()->expr );
-				crnt.emplace_back( std::move( cands.front() ) );
-			}
-
-			// extract expressions from the assignment candidates to produce a list of assignments
-			// that together form a sigle candidate
-			std::vector< ast::ptr< ast::Expr > > solved;
-			for ( ResolvExpr::CandidateRef & cand : crnt ) {
-				solved.emplace_back( cand->expr );
-				matcher->combineState( *cand );
-			}
-
-			crntFinder.candidates.emplace_back( std::make_shared< ResolvExpr::Candidate >(
-				new ast::TupleAssignExpr{
-					matcher->location, std::move( solved ), std::move( matcher->tmpDecls ) },
-				std::move( matcher->env ), std::move( matcher->open ), std::move( matcher->need ),
-				ResolvExpr::sumCost( crnt ) + matcher->baseCost ) );
-		}
-	};
+			}
+		}
+	}
+
+	void match() {
+		assert( matcher );
+
+		std::vector< ast::ptr< ast::Expr > > newAssigns = matcher->match();
+
+		if ( !( matcher->lhs.empty() && matcher->rhs.empty() ) ) {
+			// If both LHS and RHS are empty than this is the empty tuple
+			// case, wherein it's okay for newAssigns to be empty. Otherwise,
+			// return early so that no new candidates are generated.
+			if ( newAssigns.empty() ) return;
+		}
+
+		ResolvExpr::CandidateList crnt;
+		// Now resolve new assignments.
+		for ( const ast::Expr * expr : newAssigns ) {
+			PRINT(
+				std::cerr << "== resolving tuple assign ==" << std::endl;
+				std::cerr << expr << std::endl;
+			)
+
+			ResolvExpr::CandidateFinder finder( crntFinder.context, matcher->env );
+			finder.allowVoid = true;
+
+			try {
+				finder.find( expr, ResolvExpr::ResolvMode::withAdjustment() );
+			} catch (...) {
+				// No match is not failure, just that this tuple assignment is invalid.
+				return;
+			}
+
+			ResolvExpr::CandidateList & cands = finder.candidates;
+			assert( 1 == cands.size() );
+			assert( cands.front()->expr );
+			crnt.emplace_back( std::move( cands.front() ) );
+		}
+
+		// extract expressions from the assignment candidates to produce a list of assignments
+		// that together form a sigle candidate
+		std::vector< ast::ptr< ast::Expr > > solved;
+		for ( ResolvExpr::CandidateRef & cand : crnt ) {
+			solved.emplace_back( cand->expr );
+			matcher->combineState( *cand );
+		}
+
+		crntFinder.candidates.emplace_back( std::make_shared< ResolvExpr::Candidate >(
+			new ast::TupleAssignExpr(
+				matcher->location, std::move( solved ), std::move( matcher->tmpDecls ) ),
+			std::move( matcher->env ), std::move( matcher->open ), std::move( matcher->need ),
+			ResolvExpr::sumCost( crnt ) + matcher->baseCost ) );
+	}
+};
+
 } // anonymous namespace
 
@@ -377,5 +379,5 @@
 	std::vector< ResolvExpr::CandidateFinder > & args
 ) {
-	TupleAssignSpotter spotter{ finder };
+	TupleAssignSpotter spotter( finder );
 	spotter.spot( assign, args );
 }
Index: src/Tuples/Tuples.cc
===================================================================
--- src/Tuples/Tuples.cc	(revision 0bd3faf870391e5ed803287ec0b3c696cfde22b2)
+++ src/Tuples/Tuples.cc	(revision e580aa5743b01d3bef64004c1525a55b36048d7b)
@@ -25,30 +25,31 @@
 namespace {
 
-	/// Determines if impurity (read: side-effects) may exist in a piece of code. Currently gives
-	/// a very crude approximation, wherein any function call expression means the code may be
-	/// impure.
-    struct ImpurityDetector : public ast::WithShortCircuiting {
-		bool result = false;
+/// Determines if impurity (read: side-effects) may exist in a piece of code.
+/// Currently gives a very crude approximation, wherein almost any function
+/// call expression means the code may be impure.
+struct ImpurityDetector : public ast::WithShortCircuiting {
+	bool result = false;
 
-		void previsit( ast::ApplicationExpr const * appExpr ) {
-			if ( ast::DeclWithType const * function = ast::getFunction( appExpr ) ) {
-				if ( function->linkage == ast::Linkage::Intrinsic
-						&& ( function->name == "*?" || function->name == "?[?]" ) ) {
-					return;
-				}
+	void previsit( ast::ApplicationExpr const * appExpr ) {
+		if ( ast::DeclWithType const * function = ast::getFunction( appExpr ) ) {
+			if ( function->linkage == ast::Linkage::Intrinsic
+					&& ( function->name == "*?" || function->name == "?[?]" ) ) {
+				return;
 			}
-			result = true; visit_children = false;
 		}
-		void previsit( ast::UntypedExpr const * ) {
-			result = true; visit_children = false;
-		}
-	};
+		result = true; visit_children = false;
+	}
+	void previsit( ast::UntypedExpr const * ) {
+		result = true; visit_children = false;
+	}
+};
 
-	struct ImpurityDetectorIgnoreUnique : public ImpurityDetector {
-		using ImpurityDetector::previsit;
-		void previsit( ast::UniqueExpr const * ) {
-			visit_children = false;
-		}
-	};
+struct ImpurityDetectorIgnoreUnique : public ImpurityDetector {
+	using ImpurityDetector::previsit;
+	void previsit( ast::UniqueExpr const * ) {
+		visit_children = false;
+	}
+};
+
 } // namespace
 
Index: src/Tuples/Tuples.h
===================================================================
--- src/Tuples/Tuples.h	(revision 0bd3faf870391e5ed803287ec0b3c696cfde22b2)
+++ src/Tuples/Tuples.h	(revision e580aa5743b01d3bef64004c1525a55b36048d7b)
@@ -24,31 +24,33 @@
 
 namespace Tuples {
-	// TupleAssignment.cc
-	void handleTupleAssignment(
-		ResolvExpr::CandidateFinder & finder, const ast::UntypedExpr * assign,
-		std::vector< ResolvExpr::CandidateFinder > & args );
 
-	// TupleExpansion.cc
-	/// expands z.[a, b.[x, y], c] into [z.a, z.b.x, z.b.y, z.c], inserting UniqueExprs as appropriate
-	void expandMemberTuples( ast::TranslationUnit & translationUnit );
+// TupleAssignment.cc
+void handleTupleAssignment(
+	ResolvExpr::CandidateFinder & finder, const ast::UntypedExpr * assign,
+	std::vector< ResolvExpr::CandidateFinder > & args );
 
-	/// replaces tuple-related elements, such as TupleType, TupleExpr, TupleAssignExpr, etc.
-	void expandTuples( ast::TranslationUnit & translaionUnit );
+// TupleExpansion.cc
+/// Expands z.[a, b.[x, y], c] into [z.a, z.b.x, z.b.y, z.c], inserting UniqueExprs as appropriate.
+void expandMemberTuples( ast::TranslationUnit & translationUnit );
 
-	/// replaces UniqueExprs with a temporary variable and one call
-	void expandUniqueExpr( ast::TranslationUnit & translationUnit );
+/// Replaces tuple-related elements, such as TupleType, TupleExpr, TupleAssignExpr, etc.
+void expandTuples( ast::TranslationUnit & translaionUnit );
 
-	/// returns VoidType if any of the expressions have Voidtype, otherwise TupleType of the Expression result types
-	const ast::Type * makeTupleType( const std::vector<ast::ptr<ast::Expr>> & exprs );
+/// Replaces UniqueExprs with a temporary variable and one call.
+void expandUniqueExpr( ast::TranslationUnit & translationUnit );
 
-	/// returns a TypeInstType if `type` is a ttype, nullptr otherwise
-	const ast::TypeInstType * isTtype( const ast::Type * type );
+/// Returns VoidType if any of the expressions have Voidtype, otherwise TupleType of the Expression result types.
+const ast::Type * makeTupleType( const std::vector<ast::ptr<ast::Expr>> & exprs );
 
-	/// returns true if the expression may contain side-effects.
-	bool maybeImpure( const ast::Expr * expr );
+/// Returns a TypeInstType if `type` is a ttype, nullptr otherwise
+const ast::TypeInstType * isTtype( const ast::Type * type );
 
-	/// Returns true if the expression may contain side-effect,
-	/// ignoring the presence of unique expressions.
-	bool maybeImpureIgnoreUnique( const ast::Expr * expr );
+/// Returns true if the expression may contain side-effects.
+bool maybeImpure( const ast::Expr * expr );
+
+/// Returns true if the expression may contain side-effect,
+/// ignoring the presence of unique expressions.
+bool maybeImpureIgnoreUnique( const ast::Expr * expr );
+
 } // namespace Tuples
 
