Index: src/BasicTypes-gen.cc
===================================================================
--- src/BasicTypes-gen.cc	(revision 3f219ebc9e49d052d8faf3c90c18635fd3edfb24)
+++ src/BasicTypes-gen.cc	(revision 0f6d28849f6757ceab6650084ee1ecdf951265fe)
@@ -443,27 +443,26 @@
 	code << str.substr( 0, start );
 
-	code << "\t\t\t// GENERATED BY " __FILE__ << endl;
 	code <<
-		"\t\t\t// NOTES ON MANGLING:\n"
-		"\t\t\t// * Itanium spec says that Float80 encodes to \"e\" (like LongDouble), but the distinct lengths cause resolution problems.\n"
-		"\t\t\t// * Float128 is supposed to encode to \"g\", but I wanted it to mangle equal to LongDouble.\n"
-		"\t\t\t// * Mangling for non-standard complex types is by best guess\n"
-		"\t\t\t// * _FloatN is supposed to encode as \"DF\"N\"_\"; modified for same reason as above.\n"
-		"\t\t\t// * unused mangling identifiers:\n"
-		"\t\t\t//   - \"z\" ellipsis\n"
-		"\t\t\t//   - \"Dd\" IEEE 754r 64-bit decimal floating point (borrowed for _Float32x)\n"
-		"\t\t\t//   - \"De\" IEEE 754r 128-bit decimal floating point\n"
-		"\t\t\t//   - \"Df\" IEEE 754r 32-bit decimal floating point\n"
-		"\t\t\t//   - \"Dh\" IEEE 754r 16-bit decimal floating point (borrowed for _Float16)\n"
-		"\t\t\t//   - \"DF\"N\"_\" ISO/IEC TS 18661 N-bit binary floating point (_FloatN)\n"
-		"\t\t\t//   - \"Di\" char32_t\n"
-		"\t\t\t//   - \"Ds\" char16_t\n";
-
-	code << "\t\t\tconst std::string basicTypes[ast::BasicType::NUMBER_OF_BASIC_TYPES] = {" << endl;
+		"// GENERATED BY " __FILE__ "\n"
+		"// NOTES ON MANGLING:\n"
+		"// * Itanium spec says that Float80 encodes to \"e\" (like LongDouble), but the distinct lengths cause resolution problems.\n"
+		"// * Float128 is supposed to encode to \"g\", but I wanted it to mangle equal to LongDouble.\n"
+		"// * Mangling for non-standard complex types is by best guess\n"
+		"// * _FloatN is supposed to encode as \"DF\"N\"_\"; modified for same reason as above.\n"
+		"// * unused mangling identifiers:\n"
+		"//   - \"z\" ellipsis\n"
+		"//   - \"Dd\" IEEE 754r 64-bit decimal floating point (borrowed for _Float32x)\n"
+		"//   - \"De\" IEEE 754r 128-bit decimal floating point\n"
+		"//   - \"Df\" IEEE 754r 32-bit decimal floating point\n"
+		"//   - \"Dh\" IEEE 754r 16-bit decimal floating point (borrowed for _Float16)\n"
+		"//   - \"DF\"N\"_\" ISO/IEC TS 18661 N-bit binary floating point (_FloatN)\n"
+		"//   - \"Di\" char32_t\n"
+		"//   - \"Ds\" char16_t\n";
+
+	code << "const std::string basicTypes[ast::BasicType::NUMBER_OF_BASIC_TYPES] = {" << endl;
 	for ( int r = 0; r < NUMBER_OF_BASIC_TYPES; r += 1 ) {
-		code << "\t\t\t\t\"" << graph[r].mangled << "\"," << setw(9 - strlen(graph[r].mangled)) << ' ' << "// " << graph[r].type << endl;
-	} // for
-	code << "\t\t\t}; // basicTypes" << endl;
-	code << "\t\t\t";									// indentation for end marker
+		code << "\t\"" << graph[r].mangled << "\"," << setw(9 - strlen(graph[r].mangled)) << ' ' << "// " << graph[r].type << endl;
+	} // for
+	code << "}; // basicTypes" << endl;
 
 	if ( (start = str.find( ENDMK, start + 1 )) == string::npos ) Abort( "end", ManglerCommon );
Index: src/SymTab/FixFunction.cc
===================================================================
--- src/SymTab/FixFunction.cc	(revision 3f219ebc9e49d052d8faf3c90c18635fd3edfb24)
+++ src/SymTab/FixFunction.cc	(revision 0f6d28849f6757ceab6650084ee1ecdf951265fe)
@@ -26,45 +26,47 @@
 
 namespace {
-	struct FixFunction final : public ast::WithShortCircuiting {
-		bool isVoid = false;
 
-		void previsit( const ast::FunctionDecl * ) { visit_children = false; }
+struct FixFunction final : public ast::WithShortCircuiting {
+	bool isVoid = false;
 
-		const ast::DeclWithType * postvisit( const ast::FunctionDecl * func ) {
-			// Cannot handle cases with asserions.
-			assert( func->assertions.empty() );
-			return new ast::ObjectDecl{
-				func->location, func->name, new ast::PointerType( func->type ), nullptr,
-				func->storage, func->linkage, nullptr, copy( func->attributes ) };
-		}
+	void previsit( const ast::FunctionDecl * ) { visit_children = false; }
 
-		void previsit( const ast::ArrayType * ) { visit_children = false; }
+	const ast::DeclWithType * postvisit( const ast::FunctionDecl * func ) {
+		// Cannot handle cases with asserions.
+		assert( func->assertions.empty() );
+		return new ast::ObjectDecl{
+			func->location, func->name, new ast::PointerType( func->type ), nullptr,
+			func->storage, func->linkage, nullptr, copy( func->attributes ) };
+	}
 
-		const ast::Type * postvisit( const ast::ArrayType * array ) {
-			return new ast::PointerType{
-				array->base, array->dimension, array->isVarLen, array->isStatic,
-				array->qualifiers };
-		}
+	void previsit( const ast::ArrayType * ) { visit_children = false; }
 
-		void previsit( const ast::FunctionType * ) { visit_children = false; }
+	const ast::Type * postvisit( const ast::ArrayType * array ) {
+		return new ast::PointerType{
+			array->base, array->dimension, array->isVarLen, array->isStatic,
+			array->qualifiers };
+	}
 
-		const ast::Type * postvisit( const ast::FunctionType * type ) {
-			return new ast::PointerType( type );
-		}
+	void previsit( const ast::FunctionType * ) { visit_children = false; }
 
-		void previsit( const ast::VoidType * ) { isVoid = true; }
+	const ast::Type * postvisit( const ast::FunctionType * type ) {
+		return new ast::PointerType( type );
+	}
 
-		void previsit( const ast::BasicType * ) { visit_children = false; }
-		void previsit( const ast::PointerType * ) { visit_children = false; }
-		void previsit( const ast::StructInstType * ) { visit_children = false; }
-		void previsit( const ast::UnionInstType * ) { visit_children = false; }
-		void previsit( const ast::EnumInstType * ) { visit_children = false; }
-		void previsit( const ast::TraitInstType * ) { visit_children = false; }
-		void previsit( const ast::TypeInstType * ) { visit_children = false; }
-		void previsit( const ast::TupleType * ) { visit_children = false; }
-		void previsit( const ast::VarArgsType * ) { visit_children = false; }
-		void previsit( const ast::ZeroType * ) { visit_children = false; }
-		void previsit( const ast::OneType * ) { visit_children = false; }
-	};
+	void previsit( const ast::VoidType * ) { isVoid = true; }
+
+	void previsit( const ast::BasicType * ) { visit_children = false; }
+	void previsit( const ast::PointerType * ) { visit_children = false; }
+	void previsit( const ast::StructInstType * ) { visit_children = false; }
+	void previsit( const ast::UnionInstType * ) { visit_children = false; }
+	void previsit( const ast::EnumInstType * ) { visit_children = false; }
+	void previsit( const ast::TraitInstType * ) { visit_children = false; }
+	void previsit( const ast::TypeInstType * ) { visit_children = false; }
+	void previsit( const ast::TupleType * ) { visit_children = false; }
+	void previsit( const ast::VarArgsType * ) { visit_children = false; }
+	void previsit( const ast::ZeroType * ) { visit_children = false; }
+	void previsit( const ast::OneType * ) { visit_children = false; }
+};
+
 } // anonymous namespace
 
Index: src/SymTab/FixFunction.h
===================================================================
--- src/SymTab/FixFunction.h	(revision 3f219ebc9e49d052d8faf3c90c18635fd3edfb24)
+++ src/SymTab/FixFunction.h	(revision 0f6d28849f6757ceab6650084ee1ecdf951265fe)
@@ -22,8 +22,10 @@
 
 namespace SymTab {
-	/// Returns declaration with function and array types replaced by equivalent pointer types.
-	/// Sets isVoid to true if type is void
-	const ast::DeclWithType * fixFunction( const ast::DeclWithType * dwt, bool & isVoid );
-	const ast::Type * fixFunction( const ast::Type * type, bool & isVoid );
+
+/// Returns declaration with function and array types replaced by equivalent pointer types.
+/// Sets isVoid to true if type is void.
+const ast::DeclWithType * fixFunction( const ast::DeclWithType * dwt, bool & isVoid );
+const ast::Type * fixFunction( const ast::Type * type, bool & isVoid );
+
 } // namespace SymTab
 
Index: src/SymTab/Mangler.cc
===================================================================
--- src/SymTab/Mangler.cc	(revision 3f219ebc9e49d052d8faf3c90c18635fd3edfb24)
+++ src/SymTab/Mangler.cc	(revision 0f6d28849f6757ceab6650084ee1ecdf951265fe)
@@ -28,329 +28,323 @@
 
 namespace Mangle {
-	namespace {
-		/// Mangles names to a unique C identifier
-		struct Mangler : public ast::WithShortCircuiting, public ast::WithVisitorRef<Mangler>, public ast::WithGuards {
-			Mangler( Mangle::Mode mode );
-			Mangler( const Mangler & ) = delete;
-
-			void previsit( const ast::Node * ) { visit_children = false; }
-
-			void postvisit( const ast::ObjectDecl * declaration );
-			void postvisit( const ast::FunctionDecl * declaration );
-			void postvisit( const ast::TypeDecl * declaration );
-
-			void postvisit( const ast::VoidType * voidType );
-			void postvisit( const ast::BasicType * basicType );
-			void postvisit( const ast::PointerType * pointerType );
-			void postvisit( const ast::ArrayType * arrayType );
-			void postvisit( const ast::ReferenceType * refType );
-			void postvisit( const ast::FunctionType * functionType );
-			void postvisit( const ast::StructInstType * aggregateUseType );
-			void postvisit( const ast::UnionInstType * aggregateUseType );
-			void postvisit( const ast::EnumInstType * aggregateUseType );
-			void postvisit( const ast::TypeInstType * aggregateUseType );
-			void postvisit( const ast::TraitInstType * inst );
-			void postvisit( const ast::TupleType * tupleType );
-			void postvisit( const ast::VarArgsType * varArgsType );
-			void postvisit( const ast::ZeroType * zeroType );
-			void postvisit( const ast::OneType * oneType );
-			void postvisit( const ast::QualifiedType * qualType );
-
-			/// The result is the current constructed mangled name.
-			std::string result() const { return mangleName; }
-		  private:
-			std::string mangleName;         ///< Mangled name being constructed
-			typedef std::map< std::string, std::pair< int, int > > VarMapType;
-			VarMapType varNums;             ///< Map of type variables to indices
-			int nextVarNum;                 ///< Next type variable index
-			bool isTopLevel;                ///< Is the Mangler at the top level
-			bool mangleOverridable;         ///< Specially mangle overridable built-in methods
-			bool typeMode;                  ///< Produce a unique mangled name for a type
-			bool mangleGenericParams;       ///< Include generic parameters in name mangling if true
-			bool inFunctionType = false;    ///< Include type qualifiers if false.
-			bool inQualifiedType = false;   ///< Add start/end delimiters around qualified type
-
-		  private:
-			Mangler( bool mangleOverridable, bool typeMode, bool mangleGenericParams,
-				int nextVarNum, const VarMapType& varNums );
-			friend class ast::Pass<Mangler>;
-
-		  private:
-			void mangleDecl( const ast::DeclWithType *declaration );
-			void mangleRef( const ast::BaseInstType *refType, const std::string & prefix );
-
-			void printQualifiers( const ast::Type *type );
-		}; // Mangler
-	} // namespace
-
-	std::string mangle( const ast::Node * decl, Mangle::Mode mode ) {
-		return ast::Pass<Mangler>::read( decl, mode );
-	}
-
-	namespace {
-		Mangler::Mangler( Mangle::Mode mode )
-			: nextVarNum( 0 ), isTopLevel( true ),
-			mangleOverridable  ( ! mode.no_overrideable   ),
-			typeMode           (   mode.type              ),
-			mangleGenericParams( ! mode.no_generic_params ) {}
-
-		Mangler::Mangler( bool mangleOverridable, bool typeMode, bool mangleGenericParams,
-			int nextVarNum, const VarMapType& varNums )
-			: varNums( varNums ), nextVarNum( nextVarNum ), isTopLevel( false ),
-			mangleOverridable( mangleOverridable ), typeMode( typeMode ),
-			mangleGenericParams( mangleGenericParams ) {}
-
-		void Mangler::mangleDecl( const ast::DeclWithType * decl ) {
-			bool wasTopLevel = isTopLevel;
-			if ( isTopLevel ) {
-				varNums.clear();
-				nextVarNum = 0;
-				isTopLevel = false;
-			} // if
-			mangleName += Encoding::manglePrefix;
-			const CodeGen::OperatorInfo * opInfo = CodeGen::operatorLookup( decl->name );
-			if ( opInfo ) {
-				mangleName += std::to_string( opInfo->outputName.size() ) + opInfo->outputName;
-			} else {
-				mangleName += std::to_string( decl->name.size() ) + decl->name;
-			} // if
-			decl->get_type()->accept( *visitor );
-			if ( mangleOverridable && decl->linkage.is_overrideable ) {
-				// want to be able to override autogenerated and intrinsic routines,
-				// so they need a different name mangling
-				if ( decl->linkage == ast::Linkage::AutoGen ) {
-					mangleName += Encoding::autogen;
-				} else if ( decl->linkage == ast::Linkage::Intrinsic ) {
-					mangleName += Encoding::intrinsic;
-				} else {
-					// if we add another kind of overridable function, this has to change
-					assert( false && "unknown overrideable linkage" );
-				} // if
+
+namespace {
+
+/// Mangles names to a unique C identifier.
+struct Mangler : public ast::WithShortCircuiting, public ast::WithVisitorRef<Mangler>, public ast::WithGuards {
+	Mangler( Mangle::Mode mode );
+	Mangler( const Mangler & ) = delete;
+
+	void previsit( const ast::Node * ) { visit_children = false; }
+
+	void postvisit( const ast::ObjectDecl * declaration );
+	void postvisit( const ast::FunctionDecl * declaration );
+	void postvisit( const ast::TypeDecl * declaration );
+
+	void postvisit( const ast::VoidType * voidType );
+	void postvisit( const ast::BasicType * basicType );
+	void postvisit( const ast::PointerType * pointerType );
+	void postvisit( const ast::ArrayType * arrayType );
+	void postvisit( const ast::ReferenceType * refType );
+	void postvisit( const ast::FunctionType * functionType );
+	void postvisit( const ast::StructInstType * aggregateUseType );
+	void postvisit( const ast::UnionInstType * aggregateUseType );
+	void postvisit( const ast::EnumInstType * aggregateUseType );
+	void postvisit( const ast::TypeInstType * aggregateUseType );
+	void postvisit( const ast::TraitInstType * inst );
+	void postvisit( const ast::TupleType * tupleType );
+	void postvisit( const ast::VarArgsType * varArgsType );
+	void postvisit( const ast::ZeroType * zeroType );
+	void postvisit( const ast::OneType * oneType );
+	void postvisit( const ast::QualifiedType * qualType );
+
+	/// The result is the current constructed mangled name.
+	std::string result() const { return mangleName; }
+private:
+	std::string mangleName;         ///< Mangled name being constructed
+	typedef std::map< std::string, std::pair< int, int > > VarMapType;
+	VarMapType varNums;             ///< Map of type variables to indices
+	int nextVarNum;                 ///< Next type variable index
+	bool isTopLevel;                ///< Is the Mangler at the top level
+	bool mangleOverridable;         ///< Specially mangle overridable built-in methods
+	bool typeMode;                  ///< Produce a unique mangled name for a type
+	bool mangleGenericParams;       ///< Include generic parameters in name mangling if true
+	bool inFunctionType = false;    ///< Include type qualifiers if false.
+	bool inQualifiedType = false;   ///< Add start/end delimiters around qualified type
+
+private:
+	Mangler( bool mangleOverridable, bool typeMode, bool mangleGenericParams,
+		int nextVarNum, const VarMapType& varNums );
+	friend class ast::Pass<Mangler>;
+
+private:
+	void mangleDecl( const ast::DeclWithType *declaration );
+	void mangleRef( const ast::BaseInstType *refType, const std::string & prefix );
+
+	void printQualifiers( const ast::Type *type );
+}; // Mangler
+
+Mangler::Mangler( Mangle::Mode mode )
+	: nextVarNum( 0 ), isTopLevel( true ),
+	mangleOverridable  ( ! mode.no_overrideable   ),
+	typeMode           (   mode.type              ),
+	mangleGenericParams( ! mode.no_generic_params ) {}
+
+Mangler::Mangler( bool mangleOverridable, bool typeMode, bool mangleGenericParams,
+	int nextVarNum, const VarMapType& varNums )
+	: varNums( varNums ), nextVarNum( nextVarNum ), isTopLevel( false ),
+	mangleOverridable( mangleOverridable ), typeMode( typeMode ),
+	mangleGenericParams( mangleGenericParams ) {}
+
+void Mangler::mangleDecl( const ast::DeclWithType * decl ) {
+	bool wasTopLevel = isTopLevel;
+	if ( isTopLevel ) {
+		varNums.clear();
+		nextVarNum = 0;
+		isTopLevel = false;
+	}
+	mangleName += Encoding::manglePrefix;
+	if ( auto opInfo = CodeGen::operatorLookup( decl->name ) ) {
+		mangleName += std::to_string( opInfo->outputName.size() ) + opInfo->outputName;
+	} else {
+		mangleName += std::to_string( decl->name.size() ) + decl->name;
+	}
+	decl->get_type()->accept( *visitor );
+	if ( mangleOverridable && decl->linkage.is_overrideable ) {
+		// want to be able to override autogenerated and intrinsic routines,
+		// so they need a different name mangling
+		if ( decl->linkage == ast::Linkage::AutoGen ) {
+			mangleName += Encoding::autogen;
+		} else if ( decl->linkage == ast::Linkage::Intrinsic ) {
+			mangleName += Encoding::intrinsic;
+		} else {
+			// if we add another kind of overridable function, this has to change
+			assert( false && "unknown overrideable linkage" );
+		}
+	}
+	isTopLevel = wasTopLevel;
+}
+
+void Mangler::postvisit( const ast::ObjectDecl * decl ) {
+	mangleDecl( decl );
+}
+
+void Mangler::postvisit( const ast::FunctionDecl * decl ) {
+	mangleDecl( decl );
+}
+
+void Mangler::postvisit( const ast::VoidType * voidType ) {
+	printQualifiers( voidType );
+	mangleName += Encoding::void_t;
+}
+
+void Mangler::postvisit( const ast::BasicType * basicType ) {
+	printQualifiers( basicType );
+	assertf( basicType->kind < ast::BasicType::NUMBER_OF_BASIC_TYPES, "Unhandled basic type: %d", basicType->kind );
+	mangleName += Encoding::basicTypes[ basicType->kind ];
+}
+
+void Mangler::postvisit( const ast::PointerType * pointerType ) {
+	printQualifiers( pointerType );
+	// Mangle void (*f)() and void f() to the same name to prevent overloading on functions and function pointers.
+	if ( !pointerType->base.as<ast::FunctionType>() ) mangleName += Encoding::pointer;
+	maybe_accept( pointerType->base.get(), *visitor );
+}
+
+void Mangler::postvisit( const ast::ArrayType * arrayType ) {
+	// TODO: encode dimension
+	printQualifiers( arrayType );
+	mangleName += Encoding::array + "0";
+	arrayType->base->accept( *visitor );
+}
+
+void Mangler::postvisit( const ast::ReferenceType * refType ) {
+	// Don't print prefix (e.g. 'R') for reference types so that references and non-references do not overload.
+	// Further, do not print the qualifiers for a reference type (but do run printQualifers because of TypeDecls, etc.),
+	// by pretending every reference type is a function parameter.
+	GuardValue( inFunctionType ) = true;
+	printQualifiers( refType );
+	refType->base->accept( *visitor );
+}
+
+void Mangler::postvisit( const ast::FunctionType * functionType ) {
+	printQualifiers( functionType );
+	mangleName += Encoding::function;
+	// Turn on inFunctionType so that printQualifiers does not print most qualifiers for function parameters,
+	// since qualifiers on outermost parameter type do not differentiate function types, e.g.,
+	// void (*)(const int) and void (*)(int) are the same type, but void (*)(const int *) and void (*)(int *) are different.
+	GuardValue( inFunctionType ) = true;
+	if (functionType->returns.empty()) mangleName += Encoding::void_t;
+	else accept_each( functionType->returns, *visitor );
+	mangleName += "_";
+	accept_each( functionType->params, *visitor );
+	mangleName += "_";
+}
+
+void Mangler::mangleRef(
+		const ast::BaseInstType * refType, const std::string & prefix ) {
+	printQualifiers( refType );
+
+	mangleName += prefix + std::to_string( refType->name.length() ) + refType->name;
+
+	if ( mangleGenericParams && ! refType->params.empty() ) {
+		mangleName += "_";
+		for ( const ast::Expr * param : refType->params ) {
+			auto paramType = dynamic_cast< const ast::TypeExpr * >( param );
+			assertf(paramType, "Aggregate parameters should be type expressions: %s", toCString(param));
+			paramType->type->accept( *visitor );
+		}
+		mangleName += "_";
+	}
+}
+
+void Mangler::postvisit( const ast::StructInstType * aggregateUseType ) {
+	mangleRef( aggregateUseType, Encoding::struct_t );
+}
+
+void Mangler::postvisit( const ast::UnionInstType * aggregateUseType ) {
+	mangleRef( aggregateUseType, Encoding::union_t );
+}
+
+void Mangler::postvisit( const ast::EnumInstType * aggregateUseType ) {
+	mangleRef( aggregateUseType, Encoding::enum_t );
+}
+
+void Mangler::postvisit( const ast::TypeInstType * typeInst ) {
+	VarMapType::iterator varNum = varNums.find( typeInst->name );
+	if ( varNum == varNums.end() ) {
+		mangleRef( typeInst, Encoding::type );
+	} else {
+		printQualifiers( typeInst );
+		// Note: Can't use name here, since type variable names do not actually disambiguate a function, e.g.
+		//   forall(dtype T) void f(T);
+		//   forall(dtype S) void f(S);
+		// are equivalent and should mangle the same way. This is accomplished by numbering the type variables when they
+		// are first found and prefixing with the appropriate encoding for the type class.
+		assertf( varNum->second.second < ast::TypeDecl::NUMBER_OF_KINDS, "Unhandled type variable kind: %d", varNum->second.second );
+		mangleName += Encoding::typeVariables[varNum->second.second] + std::to_string( varNum->second.first );
+	}
+}
+
+void Mangler::postvisit( const ast::TraitInstType * inst ) {
+	printQualifiers( inst );
+	mangleName += std::to_string( inst->name.size() ) + inst->name;
+}
+
+void Mangler::postvisit( const ast::TupleType * tupleType ) {
+	printQualifiers( tupleType );
+	mangleName += Encoding::tuple + std::to_string( tupleType->types.size() );
+	accept_each( tupleType->types, *visitor );
+}
+
+void Mangler::postvisit( const ast::VarArgsType * varArgsType ) {
+	printQualifiers( varArgsType );
+	static const std::string vargs = "__builtin_va_list";
+	mangleName += Encoding::type + std::to_string( vargs.size() ) + vargs;
+}
+
+void Mangler::postvisit( const ast::ZeroType * ) {
+	mangleName += Encoding::zero;
+}
+
+void Mangler::postvisit( const ast::OneType * ) {
+	mangleName += Encoding::one;
+}
+
+void Mangler::postvisit( const ast::QualifiedType * qualType ) {
+	bool inqual = inQualifiedType;
+	if ( !inqual ) {
+		// N marks the start of a qualified type.
+		inQualifiedType = true;
+		mangleName += Encoding::qualifiedTypeStart;
+	}
+	qualType->parent->accept( *visitor );
+	qualType->child->accept( *visitor );
+	if ( !inqual ) {
+		// E marks the end of a qualified type.
+		inQualifiedType = false;
+		mangleName += Encoding::qualifiedTypeEnd;
+	}
+}
+
+void Mangler::postvisit( const ast::TypeDecl * decl ) {
+	// TODO: is there any case where mangling a TypeDecl makes sense? If so, this code needs to be
+	// fixed to ensure that two TypeDecls mangle to the same name when they are the same type and vice versa.
+	// Note: The current scheme may already work correctly for this case, I have not thought about this deeply
+	// and the case has not yet come up in practice. Alternatively, if not then this code can be removed
+	// aside from the assert false.
+	assertf(false, "Mangler should not visit typedecl: %s", toCString(decl));
+	assertf( decl->kind < ast::TypeDecl::Kind::NUMBER_OF_KINDS, "Unhandled type variable kind: %d", decl->kind );
+	mangleName += Encoding::typeVariables[ decl->kind ] + std::to_string( decl->name.length() ) + decl->name;
+}
+
+// For debugging:
+__attribute__((unused)) void printVarMap( const std::map< std::string, std::pair< int, int > > &varMap, std::ostream &os ) {
+	for ( std::map< std::string, std::pair< int, int > >::const_iterator i = varMap.begin(); i != varMap.end(); ++i ) {
+		os << i->first << "(" << i->second.first << "/" << i->second.second << ")" << std::endl;
+	}
+}
+
+void Mangler::printQualifiers( const ast::Type * type ) {
+	// Skip if not including qualifiers:
+	if ( typeMode ) return;
+	auto funcType = dynamic_cast<const ast::FunctionType *>( type );
+	if ( funcType && !funcType->forall.empty() ) {
+		std::list< std::string > assertionNames;
+		int dcount = 0, fcount = 0, vcount = 0, acount = 0;
+		mangleName += Encoding::forall;
+		for ( auto & decl : funcType->forall ) {
+			switch ( decl->kind ) {
+			case ast::TypeDecl::Dtype:
+				dcount++;
+				break;
+			case ast::TypeDecl::Ftype:
+				fcount++;
+				break;
+			case ast::TypeDecl::Ttype:
+				vcount++;
+				break;
+			default:
+				assertf( false, "unimplemented kind for type variable %s", Encoding::typeVariables[decl->kind].c_str() );
 			}
-			isTopLevel = wasTopLevel;
-		}
-
-		void Mangler::postvisit( const ast::ObjectDecl * decl ) {
-			mangleDecl( decl );
-		}
-
-		void Mangler::postvisit( const ast::FunctionDecl * decl ) {
-			mangleDecl( decl );
-		}
-
-		void Mangler::postvisit( const ast::VoidType * voidType ) {
-			printQualifiers( voidType );
-			mangleName += Encoding::void_t;
-		}
-
-		void Mangler::postvisit( const ast::BasicType * basicType ) {
-			printQualifiers( basicType );
-			assertf( basicType->kind < ast::BasicType::NUMBER_OF_BASIC_TYPES, "Unhandled basic type: %d", basicType->kind );
-			mangleName += Encoding::basicTypes[ basicType->kind ];
-		}
-
-		void Mangler::postvisit( const ast::PointerType * pointerType ) {
-			printQualifiers( pointerType );
-			// mangle void (*f)() and void f() to the same name to prevent overloading on functions and function pointers
-			if ( ! pointerType->base.as<ast::FunctionType>() ) mangleName += Encoding::pointer;
-			maybe_accept( pointerType->base.get(), *visitor );
-		}
-
-		void Mangler::postvisit( const ast::ArrayType * arrayType ) {
-			// TODO: encode dimension
-			printQualifiers( arrayType );
-			mangleName += Encoding::array + "0";
-			arrayType->base->accept( *visitor );
-		}
-
-		void Mangler::postvisit( const ast::ReferenceType * refType ) {
-			// don't print prefix (e.g. 'R') for reference types so that references and non-references do not overload.
-			// Further, do not print the qualifiers for a reference type (but do run printQualifers because of TypeDecls, etc.),
-			// by pretending every reference type is a function parameter.
-			GuardValue( inFunctionType );
-			inFunctionType = true;
-			printQualifiers( refType );
-			refType->base->accept( *visitor );
-		}
-
-		void Mangler::postvisit( const ast::FunctionType * functionType ) {
-			printQualifiers( functionType );
-			mangleName += Encoding::function;
-			// turn on inFunctionType so that printQualifiers does not print most qualifiers for function parameters,
-			// since qualifiers on outermost parameter type do not differentiate function types, e.g.,
-			// void (*)(const int) and void (*)(int) are the same type, but void (*)(const int *) and void (*)(int *) are different
-			GuardValue( inFunctionType );
-			inFunctionType = true;
-			if (functionType->returns.empty()) mangleName += Encoding::void_t;
-			else accept_each( functionType->returns, *visitor );
-			mangleName += "_";
-			accept_each( functionType->params, *visitor );
-			mangleName += "_";
-		}
-
-		void Mangler::mangleRef(
-				const ast::BaseInstType * refType, const std::string & prefix ) {
-			printQualifiers( refType );
-
-			mangleName += prefix + std::to_string( refType->name.length() ) + refType->name;
-
-			if ( mangleGenericParams && ! refType->params.empty() ) {
-				mangleName += "_";
-				for ( const ast::Expr * param : refType->params ) {
-					auto paramType = dynamic_cast< const ast::TypeExpr * >( param );
-					assertf(paramType, "Aggregate parameters should be type expressions: %s", toCString(param));
-					paramType->type->accept( *visitor );
-				}
-				mangleName += "_";
-			}
-		}
-
-		void Mangler::postvisit( const ast::StructInstType * aggregateUseType ) {
-			mangleRef( aggregateUseType, Encoding::struct_t );
-		}
-
-		void Mangler::postvisit( const ast::UnionInstType * aggregateUseType ) {
-			mangleRef( aggregateUseType, Encoding::union_t );
-		}
-
-		void Mangler::postvisit( const ast::EnumInstType * aggregateUseType ) {
-			mangleRef( aggregateUseType, Encoding::enum_t );
-		}
-
-		void Mangler::postvisit( const ast::TypeInstType * typeInst ) {
-			VarMapType::iterator varNum = varNums.find( typeInst->name );
-			if ( varNum == varNums.end() ) {
-				mangleRef( typeInst, Encoding::type );
-			} else {
-				printQualifiers( typeInst );
-				// Note: Can't use name here, since type variable names do not actually disambiguate a function, e.g.
-				//   forall(dtype T) void f(T);
-				//   forall(dtype S) void f(S);
-				// are equivalent and should mangle the same way. This is accomplished by numbering the type variables when they
-				// are first found and prefixing with the appropriate encoding for the type class.
-				assertf( varNum->second.second < ast::TypeDecl::NUMBER_OF_KINDS, "Unhandled type variable kind: %d", varNum->second.second );
-				mangleName += Encoding::typeVariables[varNum->second.second] + std::to_string( varNum->second.first );
-			} // if
-		}
-
-		void Mangler::postvisit( const ast::TraitInstType * inst ) {
-			printQualifiers( inst );
-			mangleName += std::to_string( inst->name.size() ) + inst->name;
-		}
-
-		void Mangler::postvisit( const ast::TupleType * tupleType ) {
-			printQualifiers( tupleType );
-			mangleName += Encoding::tuple + std::to_string( tupleType->types.size() );
-			accept_each( tupleType->types, *visitor );
-		}
-
-		void Mangler::postvisit( const ast::VarArgsType * varArgsType ) {
-			printQualifiers( varArgsType );
-			static const std::string vargs = "__builtin_va_list";
-			mangleName += Encoding::type + std::to_string( vargs.size() ) + vargs;
-		}
-
-		void Mangler::postvisit( const ast::ZeroType * ) {
-			mangleName += Encoding::zero;
-		}
-
-		void Mangler::postvisit( const ast::OneType * ) {
-			mangleName += Encoding::one;
-		}
-
-		void Mangler::postvisit( const ast::QualifiedType * qualType ) {
-			bool inqual = inQualifiedType;
-			if ( !inqual ) {
-				// N marks the start of a qualified type
-				inQualifiedType = true;
-				mangleName += Encoding::qualifiedTypeStart;
-			}
-			qualType->parent->accept( *visitor );
-			qualType->child->accept( *visitor );
-			if ( !inqual ) {
-				// E marks the end of a qualified type
-				inQualifiedType = false;
-				mangleName += Encoding::qualifiedTypeEnd;
-			}
-		}
-
-		void Mangler::postvisit( const ast::TypeDecl * decl ) {
-			// TODO: is there any case where mangling a TypeDecl makes sense? If so, this code needs to be
-			// fixed to ensure that two TypeDecls mangle to the same name when they are the same type and vice versa.
-			// Note: The current scheme may already work correctly for this case, I have not thought about this deeply
-			// and the case has not yet come up in practice. Alternatively, if not then this code can be removed
-			// aside from the assert false.
-			assertf(false, "Mangler should not visit typedecl: %s", toCString(decl));
-			assertf( decl->kind < ast::TypeDecl::Kind::NUMBER_OF_KINDS, "Unhandled type variable kind: %d", decl->kind );
-			mangleName += Encoding::typeVariables[ decl->kind ] + std::to_string( decl->name.length() ) + decl->name;
-		}
-
-		// For debugging:
-		__attribute__((unused)) void printVarMap( const std::map< std::string, std::pair< int, int > > &varMap, std::ostream &os ) {
-			for ( std::map< std::string, std::pair< int, int > >::const_iterator i = varMap.begin(); i != varMap.end(); ++i ) {
-				os << i->first << "(" << i->second.first << "/" << i->second.second << ")" << std::endl;
-			} // for
-		}
-
-		void Mangler::printQualifiers( const ast::Type * type ) {
-			// skip if not including qualifiers
-			if ( typeMode ) return;
-			auto funcType = dynamic_cast<const ast::FunctionType *>( type );
-			if ( funcType && !funcType->forall.empty() ) {
-				std::list< std::string > assertionNames;
-				int dcount = 0, fcount = 0, vcount = 0, acount = 0;
-				mangleName += Encoding::forall;
-				for ( auto & decl : funcType->forall ) {
-					switch ( decl->kind ) {
-					case ast::TypeDecl::Dtype:
-						dcount++;
-						break;
-					case ast::TypeDecl::Ftype:
-						fcount++;
-						break;
-					case ast::TypeDecl::Ttype:
-						vcount++;
-						break;
-					default:
-						assertf( false, "unimplemented kind for type variable %s", SymTab::Mangler::Encoding::typeVariables[decl->kind].c_str() );
-					} // switch
-					varNums[ decl->name ] = std::make_pair( nextVarNum, (int)decl->kind );
-				} // for
-				for ( auto & assert : funcType->assertions ) {
-					assertionNames.push_back( ast::Pass<Mangler>::read(
-						assert->var.get(),
-						mangleOverridable, typeMode, mangleGenericParams, nextVarNum, varNums ) );
-					acount++;
-				} // for
-				mangleName += std::to_string( dcount ) + "_" + std::to_string( fcount ) + "_" + std::to_string( vcount ) + "_" + std::to_string( acount ) + "_";
-				for ( const auto & a : assertionNames ) mangleName += a;
-				mangleName += "_";
-			} // if
-			if ( ! inFunctionType ) {
-				// these qualifiers do not distinguish the outermost type of a function parameter
-				if ( type->is_const() ) {
-					mangleName += Encoding::qualifiers.at( ast::CV::Const );
-				} // if
-				if ( type->is_volatile() ) {
-					mangleName += Encoding::qualifiers.at( ast::CV::Volatile );
-				} // if
-				// Removed due to restrict not affecting function compatibility in GCC
-				// if ( type->get_isRestrict() ) {
-				// 	mangleName += "E";
-				// } // if
-				if ( type->is_atomic() ) {
-					mangleName += Encoding::qualifiers.at( ast::CV::Atomic );
-				} // if
-			}
-			if ( type->is_mutex() ) {
-				mangleName += Encoding::qualifiers.at( ast::CV::Mutex );
-			} // if
-			if ( inFunctionType ) {
-				// turn off inFunctionType so that types can be differentiated for nested qualifiers
-				GuardValue( inFunctionType );
-				inFunctionType = false;
-			}
-		}
-	}	// namespace
+			varNums[ decl->name ] = std::make_pair( nextVarNum, (int)decl->kind );
+		}
+		for ( auto & assert : funcType->assertions ) {
+			assertionNames.push_back( ast::Pass<Mangler>::read(
+				assert->var.get(),
+				mangleOverridable, typeMode, mangleGenericParams, nextVarNum, varNums ) );
+			acount++;
+		}
+		mangleName += std::to_string( dcount ) + "_" + std::to_string( fcount ) + "_" + std::to_string( vcount ) + "_" + std::to_string( acount ) + "_";
+		for ( const auto & a : assertionNames ) mangleName += a;
+		mangleName += "_";
+	}
+	if ( !inFunctionType ) {
+		// These qualifiers do not distinguish the outermost type of a function parameter.
+		if ( type->is_const() ) {
+			mangleName += Encoding::qualifiers.at( ast::CV::Const );
+		}
+		if ( type->is_volatile() ) {
+			mangleName += Encoding::qualifiers.at( ast::CV::Volatile );
+		}
+		if ( type->is_atomic() ) {
+			mangleName += Encoding::qualifiers.at( ast::CV::Atomic );
+		}
+	}
+	if ( type->is_mutex() ) {
+		mangleName += Encoding::qualifiers.at( ast::CV::Mutex );
+	}
+	if ( inFunctionType ) {
+		// Turn off inFunctionType so that types can be differentiated for nested qualifiers.
+		GuardValue( inFunctionType ) = false;
+	}
+}
+
+}	// namespace
+
+std::string mangle( const ast::Node * decl, Mangle::Mode mode ) {
+	return ast::Pass<Mangler>::read( decl, mode );
+}
+
 } // namespace Mangle
 
Index: src/SymTab/Mangler.h
===================================================================
--- src/SymTab/Mangler.h	(revision 3f219ebc9e49d052d8faf3c90c18635fd3edfb24)
+++ src/SymTab/Mangler.h	(revision 0f6d28849f6757ceab6650084ee1ecdf951265fe)
@@ -34,71 +34,66 @@
 }
 
-namespace SymTab {
-	namespace Mangler {
-		namespace Encoding {
-			extern const std::string manglePrefix;
-			extern const std::string basicTypes[];
-			extern const std::map<int, std::string> qualifiers;
+namespace Mangle {
 
-			extern const std::string void_t;
-			extern const std::string zero;
-			extern const std::string one;
+/// Bitflags for mangle Mode:
+enum {
+	NoOverrideable  = 1 << 0,
+	Type            = 1 << 1,
+	NoGenericParams = 1 << 2
+};
 
-			extern const std::string function;
-			extern const std::string tuple;
-			extern const std::string pointer;
-			extern const std::string array;
-			extern const std::string qualifiedTypeStart;
-			extern const std::string qualifiedTypeEnd;
-
-			extern const std::string forall;
-			extern const std::string typeVariables[];
-
-			extern const std::string struct_t;
-			extern const std::string union_t;
-			extern const std::string enum_t;
-			extern const std::string type;
-
-			extern const std::string autogen;
-			extern const std::string intrinsic;
+/// Bitflag type for mangle Mode:
+struct mangle_flags {
+	union {
+		unsigned int val;
+		struct {
+			bool no_overrideable   : 1;
+			bool type              : 1;
+			bool no_generic_params : 1;
 		};
-	} // Mangler
-} // SymTab
-
-namespace Mangle {
-	/// Bitflags for mangle modes
-	enum {
-		NoOverrideable  = 1 << 0,
-		Type            = 1 << 1,
-		NoGenericParams = 1 << 2
 	};
 
-	/// Bitflag type for mangler modes
-	struct mangle_flags {
-		union {
-			unsigned int val;
-			struct {
-				bool no_overrideable   : 1;
-				bool type              : 1;
-				bool no_generic_params : 1;
-			};
-		};
+	constexpr mangle_flags( unsigned int val ) : val(val) {}
+};
 
-		constexpr mangle_flags( unsigned int val ) : val(val) {}
-	};
+using Mode = bitfield<mangle_flags>;
 
-	using Mode = bitfield<mangle_flags>;
+/// Mangle declaration name.
+std::string mangle( const ast::Node * decl, Mode mode = {} );
 
-	/// Mangle declaration name.
-	std::string mangle( const ast::Node * decl, Mode mode = {} );
+/// Most common mangle configuration for types.
+static inline std::string mangleType( const ast::Node * type ) {
+	return mangle( type, { NoOverrideable | Type } );
+}
 
-	/// Most common mangle configuration for types.
-	static inline std::string mangleType( const ast::Node * type ) {
-		return mangle( type, { NoOverrideable | Type } );
-	}
+/// The substrings used in name mangling and demangling.
+namespace Encoding {
+	extern const std::string manglePrefix;
+	extern const std::string basicTypes[];
+	extern const std::map<int, std::string> qualifiers;
 
-	namespace Encoding {
-		using namespace SymTab::Mangler::Encoding;
-	};
+	extern const std::string void_t;
+	extern const std::string zero;
+	extern const std::string one;
+
+	extern const std::string function;
+	extern const std::string tuple;
+	extern const std::string pointer;
+	extern const std::string array;
+	extern const std::string qualifiedTypeStart;
+	extern const std::string qualifiedTypeEnd;
+
+	extern const std::string forall;
+	extern const std::string typeVariables[];
+
+	extern const std::string struct_t;
+	extern const std::string union_t;
+	extern const std::string enum_t;
+	extern const std::string type;
+
+	extern const std::string autogen;
+	extern const std::string intrinsic;
+}
+
 }
 
Index: src/SymTab/ManglerCommon.cc
===================================================================
--- src/SymTab/ManglerCommon.cc	(revision 3f219ebc9e49d052d8faf3c90c18635fd3edfb24)
+++ src/SymTab/ManglerCommon.cc	(revision 0f6d28849f6757ceab6650084ee1ecdf951265fe)
@@ -19,109 +19,111 @@
 #include "AST/Type.hpp"
 
-namespace SymTab {
-	namespace Mangler {
-		namespace Encoding {
-			const std::string manglePrefix = "_X";
+namespace Mangle {
 
-			// GENERATED START, DO NOT EDIT
-			// GENERATED BY BasicTypes-gen.cc
-			// NOTES ON MANGLING:
-			// * Itanium spec says that Float80 encodes to "e" (like LongDouble), but the distinct lengths cause resolution problems.
-			// * Float128 is supposed to encode to "g", but I wanted it to mangle equal to LongDouble.
-			// * Mangling for non-standard complex types is by best guess
-			// * _FloatN is supposed to encode as "DF"N"_"; modified for same reason as above.
-			// * unused mangling identifiers:
-			//   - "z" ellipsis
-			//   - "Dd" IEEE 754r 64-bit decimal floating point (borrowed for _Float32x)
-			//   - "De" IEEE 754r 128-bit decimal floating point
-			//   - "Df" IEEE 754r 32-bit decimal floating point
-			//   - "Dh" IEEE 754r 16-bit decimal floating point (borrowed for _Float16)
-			//   - "DF"N"_" ISO/IEC TS 18661 N-bit binary floating point (_FloatN)
-			//   - "Di" char32_t
-			//   - "Ds" char16_t
-			const std::string basicTypes[ast::BasicType::NUMBER_OF_BASIC_TYPES] = {
-				"b",        // _Bool
-				"c",        // char
-				"a",        // signed char
-				"h",        // unsigned char
-				"s",        // signed short int
-				"t",        // unsigned short int
-				"i",        // signed int
-				"j",        // unsigned int
-				"l",        // signed long int
-				"m",        // unsigned long int
-				"x",        // signed long long int
-				"y",        // unsigned long long int
-				"n",        // __int128
-				"o",        // unsigned __int128
-				"DF16_",    // _Float16
-				"CDF16_",   // _Float16 _Complex
-				"DF32_",    // _Float32
-				"CDF32_",   // _Float32 _Complex
-				"f",        // float
-				"Cf",       // float _Complex
-				"DF32x_",   // _Float32x
-				"CDF32x_",  // _Float32x _Complex
-				"DF64_",    // _Float64
-				"CDF64_",   // _Float64 _Complex
-				"d",        // double
-				"Cd",       // double _Complex
-				"DF64x_",   // _Float64x
-				"CDF64x_",  // _Float64x _Complex
-				"Dq",       // __float80
-				"DF128_",   // _Float128
-				"CDF128_",  // _Float128 _Complex
-				"g",        // __float128
-				"e",        // long double
-				"Ce",       // long double _Complex
-				"DF128x_",  // _Float128x
-				"CDF128x_", // _Float128x _Complex
-			}; // basicTypes
-			// GENERATED END
-			static_assert(
-				sizeof(basicTypes)/sizeof(basicTypes[0]) == ast::BasicType::NUMBER_OF_BASIC_TYPES,
-				"Each basic type kind should have a corresponding mangler letter"
-			);
+namespace Encoding {
 
-			const std::map<int, std::string> qualifiers = {
-				{ ast::CV::Const, "K" },
-				{ ast::CV::Volatile, "V" },
-				{ ast::CV::Atomic, "DA" }, // A is array, so need something unique for atmoic. For now, go with multiletter DA
-				{ ast::CV::Mutex, "X" },
-			};
+const std::string manglePrefix = "_X";
 
-			const std::string void_t = "v";
-			const std::string zero = "Z";
-			const std::string one = "O";
+// GENERATED START, DO NOT EDIT
+// GENERATED BY BasicTypes-gen.cc
+// NOTES ON MANGLING:
+// * Itanium spec says that Float80 encodes to "e" (like LongDouble), but the distinct lengths cause resolution problems.
+// * Float128 is supposed to encode to "g", but I wanted it to mangle equal to LongDouble.
+// * Mangling for non-standard complex types is by best guess
+// * _FloatN is supposed to encode as "DF"N"_"; modified for same reason as above.
+// * unused mangling identifiers:
+//   - "z" ellipsis
+//   - "Dd" IEEE 754r 64-bit decimal floating point (borrowed for _Float32x)
+//   - "De" IEEE 754r 128-bit decimal floating point
+//   - "Df" IEEE 754r 32-bit decimal floating point
+//   - "Dh" IEEE 754r 16-bit decimal floating point (borrowed for _Float16)
+//   - "DF"N"_" ISO/IEC TS 18661 N-bit binary floating point (_FloatN)
+//   - "Di" char32_t
+//   - "Ds" char16_t
+const std::string basicTypes[ast::BasicType::NUMBER_OF_BASIC_TYPES] = {
+	"b",        // _Bool
+	"c",        // char
+	"a",        // signed char
+	"h",        // unsigned char
+	"s",        // signed short int
+	"t",        // unsigned short int
+	"i",        // signed int
+	"j",        // unsigned int
+	"l",        // signed long int
+	"m",        // unsigned long int
+	"x",        // signed long long int
+	"y",        // unsigned long long int
+	"n",        // __int128
+	"o",        // unsigned __int128
+	"DF16_",    // _Float16
+	"CDF16_",   // _Float16 _Complex
+	"DF32_",    // _Float32
+	"CDF32_",   // _Float32 _Complex
+	"f",        // float
+	"Cf",       // float _Complex
+	"DF32x_",   // _Float32x
+	"CDF32x_",  // _Float32x _Complex
+	"DF64_",    // _Float64
+	"CDF64_",   // _Float64 _Complex
+	"d",        // double
+	"Cd",       // double _Complex
+	"DF64x_",   // _Float64x
+	"CDF64x_",  // _Float64x _Complex
+	"Dq",       // __float80
+	"DF128_",   // _Float128
+	"CDF128_",  // _Float128 _Complex
+	"g",        // __float128
+	"e",        // long double
+	"Ce",       // long double _Complex
+	"DF128x_",  // _Float128x
+	"CDF128x_", // _Float128x _Complex
+}; // basicTypes
+// GENERATED END
+static_assert(
+	sizeof(basicTypes) / sizeof(basicTypes[0]) == ast::BasicType::NUMBER_OF_BASIC_TYPES,
+	"Each basic type kind should have a corresponding mangler letter"
+);
 
-			const std::string function = "F";
-			const std::string tuple = "T";
-			const std::string pointer = "P";
-			const std::string array = "A";
-			const std::string qualifiedTypeStart = "N";
-			const std::string qualifiedTypeEnd = "E";
+const std::map<int, std::string> qualifiers = {
+	{ ast::CV::Const, "K" },
+	{ ast::CV::Volatile, "V" },
+	{ ast::CV::Atomic, "DA" }, // A is array, so need something unique for atmoic. For now, go with multiletter DA
+	{ ast::CV::Mutex, "X" },
+};
 
-			const std::string forall = "Q";
-			const std::string typeVariables[] = {
-				"BD", // dtype
-				"BDS", // dtype + sized
-				"BO", // otype
-				"BF", // ftype
-				"BT", // ttype
-				"BAL", // array length type
-			};
-			static_assert(
-				sizeof(typeVariables) / sizeof(typeVariables[0]) == ast::TypeDecl::NUMBER_OF_KINDS,
-				"Each type variable kind should have a corresponding mangler prefix"
-			);
+const std::string void_t = "v";
+const std::string zero = "Z";
+const std::string one = "O";
 
-			const std::string struct_t = "S";
-			const std::string union_t = "U";
-			const std::string enum_t = "M";
-			const std::string type = "Y";
+const std::string function = "F";
+const std::string tuple = "T";
+const std::string pointer = "P";
+const std::string array = "A";
+const std::string qualifiedTypeStart = "N";
+const std::string qualifiedTypeEnd = "E";
 
-			const std::string autogen = "autogen__";
-			const std::string intrinsic = "intrinsic__";
-		} // namespace Encoding
-	} // namespace Mangler
-} // namespace SymTab
+const std::string forall = "Q";
+const std::string typeVariables[] = {
+	"BD", // dtype
+	"BDS", // dtype + sized
+	"BO", // otype
+	"BF", // ftype
+	"BT", // ttype
+	"BAL", // array length type
+};
+static_assert(
+	sizeof(typeVariables) / sizeof(typeVariables[0]) == ast::TypeDecl::NUMBER_OF_KINDS,
+	"Each type variable kind should have a corresponding mangler prefix"
+);
+
+const std::string struct_t = "S";
+const std::string union_t = "U";
+const std::string enum_t = "M";
+const std::string type = "Y";
+
+const std::string autogen = "autogen__";
+const std::string intrinsic = "intrinsic__";
+
+} // namespace Encoding
+
+} // namespace Mangle
Index: src/Tuples/TupleAssignment.cc
===================================================================
--- src/Tuples/TupleAssignment.cc	(revision 3f219ebc9e49d052d8faf3c90c18635fd3edfb24)
+++ src/Tuples/TupleAssignment.cc	(revision 0f6d28849f6757ceab6650084ee1ecdf951265fe)
@@ -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 3f219ebc9e49d052d8faf3c90c18635fd3edfb24)
+++ src/Tuples/Tuples.cc	(revision 0f6d28849f6757ceab6650084ee1ecdf951265fe)
@@ -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 3f219ebc9e49d052d8faf3c90c18635fd3edfb24)
+++ src/Tuples/Tuples.h	(revision 0f6d28849f6757ceab6650084ee1ecdf951265fe)
@@ -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
 
