Index: src/Tuples/Explode.h
===================================================================
--- src/Tuples/Explode.h	(revision 6c3a988fe7dbaec419f8cc1c6be3085432447a99)
+++ src/Tuples/Explode.h	(revision f8311773ddba919bb17f47a41db0f35c3fb81226)
@@ -33,19 +33,25 @@
 	/// helper function used by explode
 	template< typename OutputIterator >
-	void explodeUnique( Expression * expr, const ResolvExpr::Alternative & alt, const SymTab::Indexer & indexer, OutputIterator out ) {
+	void explodeUnique( Expression * expr, const ResolvExpr::Alternative & alt, const SymTab::Indexer & indexer, OutputIterator out, bool isTupleAssign ) {
+		if ( isTupleAssign ) {
+			// tuple assignment needs AddressExprs to be recursively exploded to easily get at all of the components
+			if ( AddressExpr * addrExpr = dynamic_cast< AddressExpr * >( expr ) ) {
+				ResolvExpr::AltList alts;
+				explodeUnique( addrExpr->get_arg(), alt, indexer, back_inserter( alts ), isTupleAssign );
+				for ( ResolvExpr::Alternative & alt : alts ) {
+					// distribute '&' over all components
+					alt.expr = distributeAddr( alt.expr );
+					*out++ = alt;
+				}
+				// in tuple assignment, still need to handle the other cases, but only if not already handled here (don't want to output too many alternatives)
+				return;
+			}
+		}
 		Type * res = expr->get_result();
-		if ( AddressExpr * addrExpr = dynamic_cast< AddressExpr * >( expr ) ) {
-			ResolvExpr::AltList alts;
-			explodeUnique( addrExpr->get_arg(), alt, indexer, back_inserter( alts ) );
-			for ( ResolvExpr::Alternative & alt : alts ) {
-				// distribute '&' over all components
-				alt.expr = distributeAddr( alt.expr );
-				*out++ = alt;
-			}
-		} else if ( TupleType * tupleType = dynamic_cast< TupleType * > ( res ) ) {
+		if ( TupleType * tupleType = dynamic_cast< TupleType * > ( res ) ) {
 			if ( TupleExpr * tupleExpr = dynamic_cast< TupleExpr * >( expr ) ) {
 				// can open tuple expr and dump its exploded components
 				for ( Expression * expr : tupleExpr->get_exprs() ) {
-					explodeUnique( expr, alt, indexer, out );
+					explodeUnique( expr, alt, indexer, out, isTupleAssign );
 				}
 			} else {
@@ -58,5 +64,5 @@
 				for ( unsigned int i = 0; i < tupleType->size(); i++ ) {
 					TupleIndexExpr * idx = new TupleIndexExpr( arg->clone(), i );
-					explodeUnique( idx, alt, indexer, out );
+					explodeUnique( idx, alt, indexer, out, isTupleAssign );
 					delete idx;
 				}
@@ -71,19 +77,19 @@
 	/// expands a tuple-valued alternative into multiple alternatives, each with a non-tuple-type
 	template< typename OutputIterator >
-	void explode( const ResolvExpr::Alternative &alt, const SymTab::Indexer & indexer, OutputIterator out ) {
-		explodeUnique( alt.expr, alt, indexer, out );
+	void explode( const ResolvExpr::Alternative &alt, const SymTab::Indexer & indexer, OutputIterator out, bool isTupleAssign = false ) {
+		explodeUnique( alt.expr, alt, indexer, out, isTupleAssign );
 	}
 
 	// explode list of alternatives
 	template< typename AltIterator, typename OutputIterator >
-	void explode( AltIterator altBegin, AltIterator altEnd, const SymTab::Indexer & indexer, OutputIterator out ) {
+	void explode( AltIterator altBegin, AltIterator altEnd, const SymTab::Indexer & indexer, OutputIterator out, bool isTupleAssign = false ) {
 		for ( ; altBegin != altEnd; ++altBegin ) {
-			explode( *altBegin, indexer, out );
+			explode( *altBegin, indexer, out, isTupleAssign );
 		}
 	}
 
 	template< typename OutputIterator >
-	void explode( const ResolvExpr::AltList & alts, const SymTab::Indexer & indexer, OutputIterator out ) {
-		explode( alts.begin(), alts.end(), indexer, out );
+	void explode( const ResolvExpr::AltList & alts, const SymTab::Indexer & indexer, OutputIterator out, bool isTupleAssign = false ) {
+		explode( alts.begin(), alts.end(), indexer, out, isTupleAssign );
 	}
 } // namespace Tuples
Index: src/Tuples/TupleAssignment.cc
===================================================================
--- src/Tuples/TupleAssignment.cc	(revision 6c3a988fe7dbaec419f8cc1c6be3085432447a99)
+++ src/Tuples/TupleAssignment.cc	(revision f8311773ddba919bb17f47a41db0f35c3fb81226)
@@ -42,5 +42,4 @@
 	  private:
 		void match();
-		void handleEmptyTuple( const ResolvExpr::AltList & alts );
 
 		struct Matcher {
@@ -74,9 +73,9 @@
 	};
 
-	/// true if expr is an expression of tuple type, i.e. a tuple expression, tuple variable, or MRV (multiple-return-value) function
+	/// true if expr is an expression of tuple type
 	bool isTuple( Expression *expr ) {
 		if ( ! expr ) return false;
 		assert( expr->has_result() );
-		return dynamic_cast<TupleExpr *>(expr) || expr->get_result()->size() > 1;
+		return dynamic_cast< TupleType * >( expr->get_result() );
 	}
 
@@ -131,13 +130,4 @@
 						}
 						match();
-					} else {
-						// handle empty case specially. It is easy to cause conflicts for tuple assignment when we consider any expression with Tuple type to be a tuple.
-						// Instead, only tuple expressions and expressions with at least 2 results are considered tuples for tuple assignment. This most obviously leaves out the
-						// nullary and unary cases. The unary case is handled nicely by the alternative finder as is. For example, an expression of type [int] will be exploded
-						// into a list of one element (combined with the RHS elements), which will easily allow for intrinsic construction. This seems like a best case scenario anyway,
-						// since intrinsic construction is much simpler from a code-gen perspective than tuple construction is.
-						// This leaves the empty case, which is not easily handled by existing alternative finder logic. Instead, it seems simple enough to hanlde here that if the left
-						// side is an empty tuple, then the right side is allowed to be either an empty tuple or an empty list. Fortunately, these cases are identical when exploded.
-						handleEmptyTuple( *ali );
 					}
 				}
@@ -152,5 +142,9 @@
 		matcher->match( new_assigns );
 
-		if ( new_assigns.empty() ) return;
+		if ( ! matcher->lhs.empty() || ! matcher->rhs.empty() ) {
+			// if both lhs and rhs are empty then this is the empty tuple case, wherein it's okay for new_assigns to be empty.
+			// if not the empty tuple case, return early so that no new alternatives are generated.
+			if ( new_assigns.empty() ) return;
+		}
 		ResolvExpr::AltList current;
 		// now resolve new assignments
@@ -196,5 +190,5 @@
 
 		// explode the lhs so that each field of the tuple-valued-expr is assigned.
-		explode( lhsAlt, spotter.currentFinder.get_indexer(), back_inserter(lhs) );
+		explode( lhsAlt, spotter.currentFinder.get_indexer(), back_inserter(lhs), true );
 
 		// and finally, re-add the cast to each lhs expr, so that qualified tuple fields can be constructed
@@ -221,5 +215,5 @@
 	TupleAssignSpotter::MultipleAssignMatcher::MultipleAssignMatcher( TupleAssignSpotter &spotter, const ResolvExpr::AltList & alts ) : Matcher( spotter, alts ) {
 		// explode the rhs so that each field of the tuple-valued-expr is assigned.
-		explode( std::next(alts.begin(), 1), alts.end(), spotter.currentFinder.get_indexer(), back_inserter(rhs) );
+		explode( std::next(alts.begin(), 1), alts.end(), spotter.currentFinder.get_indexer(), back_inserter(rhs), true );
 	}
 
@@ -258,8 +252,10 @@
 		static UniqueName lhsNamer( "__massassign_L" );
 		static UniqueName rhsNamer( "__massassign_R" );
-		assert( ! lhs.empty() && rhs.size() <= 1 );
+		// empty tuple case falls into this matcher, hence the second part of the assert
+		assert( (! lhs.empty() && rhs.size() <= 1) || (lhs.empty() && rhs.empty()) );
 
 		ObjectDecl * rtmp = rhs.size() == 1 ? newObject( rhsNamer, rhs.front().expr ) : nullptr;
 		for ( ResolvExpr::Alternative & lhsAlt : lhs ) {
+			// create a temporary object for each value in the lhs and create a call involving the rhs
 			ObjectDecl * ltmp = newObject( lhsNamer, lhsAlt.expr );
 			out.push_back( createFunc( spotter.fname, ltmp, rtmp ) );
@@ -273,6 +269,6 @@
 		static UniqueName rhsNamer( "__multassign_R" );
 
-		// xxx - need more complicated matching?
 		if ( lhs.size() == rhs.size() ) {
+			// produce a new temporary object for each value in the lhs and rhs and pairwise create the calls
 			std::list< ObjectDecl * > ltmp;
 			std::list< ObjectDecl * > rtmp;
@@ -288,24 +284,4 @@
 		}
 	}
-
-	// empty case is okay when right side is also "empty" (empty explosion handles no argument case as well as empty tuple case)
-	void TupleAssignSpotter::handleEmptyTuple( const ResolvExpr::AltList & alts ) {
-		assert( ! alts.empty() );
-		Expression * lhs = alts.front().expr;
-		if ( PointerType * ptrType = dynamic_cast< PointerType * >( lhs->get_result() ) ) {
-			if ( TupleType * tupleType = dynamic_cast< TupleType * >( ptrType->get_base() ) ) {
-				if ( tupleType->size() == 0 ) {
-					ResolvExpr::AltList rhs;
-					explode( std::next(alts.begin(), 1), alts.end(), currentFinder.get_indexer(), back_inserter(rhs) );
-					if ( rhs.empty() ) {
-						// okay, no other case is allowed
-						ResolvExpr::TypeEnvironment compositeEnv;
-						simpleCombineEnvironments( alts.begin(), alts.end(), compositeEnv );
-						currentFinder.get_alternatives().push_front( ResolvExpr::Alternative( new TupleAssignExpr( std::list< Expression * >(), std::list< ObjectDecl * >() ), compositeEnv, ResolvExpr::sumCost( alts ) ) );
-					}
-				}
-			}
-		}
-	}
 } // namespace Tuples
 
