Index: src/AST/DeclReplacer.hpp
===================================================================
--- src/AST/DeclReplacer.hpp	(revision 24d6572fc571b2a894d56a9335edd57899c448c0)
+++ src/AST/DeclReplacer.hpp	(revision 62d62db217dc9f917346863faa9d03148d98844f)
@@ -18,21 +18,26 @@
 #include <unordered_map>
 
-#include "Node.hpp"
+namespace ast {
+	class DeclWithType;
+	class Expr;
+	class Node;
+	class TypeDecl;
+}
 
 namespace ast {
-	class DeclWithType;
-	class TypeDecl;
-	class Expr;
 
-	namespace DeclReplacer {
-		using DeclMap = std::unordered_map< const DeclWithType *, const DeclWithType * >;
-		using TypeMap = std::unordered_map< const TypeDecl *, const TypeDecl * >;
-		using ExprMap = std::unordered_map< const DeclWithType *, const Expr * >;
+namespace DeclReplacer {
 
-		const Node * replace( const Node * node, const DeclMap & declMap, bool debug = false );
-		const Node * replace( const Node * node, const TypeMap & typeMap, bool debug = false );
-		const Node * replace( const Node * node, const DeclMap & declMap, const TypeMap & typeMap, bool debug = false );
-		const Node * replace( const Node * node, const ExprMap & exprMap);
-	}
+using DeclMap = std::unordered_map< const DeclWithType *, const DeclWithType * >;
+using TypeMap = std::unordered_map< const TypeDecl *, const TypeDecl * >;
+using ExprMap = std::unordered_map< const DeclWithType *, const Expr * >;
+
+const Node * replace( const Node * node, const DeclMap & declMap, bool debug = false );
+const Node * replace( const Node * node, const TypeMap & typeMap, bool debug = false );
+const Node * replace( const Node * node, const DeclMap & declMap, const TypeMap & typeMap, bool debug = false );
+const Node * replace( const Node * node, const ExprMap & exprMap);
+
+}
+
 }
 
Index: src/AST/Pass.hpp
===================================================================
--- src/AST/Pass.hpp	(revision 24d6572fc571b2a894d56a9335edd57899c448c0)
+++ src/AST/Pass.hpp	(revision 62d62db217dc9f917346863faa9d03148d98844f)
@@ -414,7 +414,22 @@
 };
 
-/// Use when the templated visitor should update the symbol table
+/// Use when the templated visitor should update the symbol table,
+/// that is, when your pass core needs to query the symbol table.
+/// Expected setups:
+/// - For master passes that kick off at the compilation unit
+///   - before resolver: extend WithSymbolTableX<IgnoreErrors>
+///   - after resolver: extend WithSymbolTable and use defaults
+///   - (FYI, for completeness, the resolver's main pass uses ValidateOnAdd when it kicks off)
+/// - For helper passes that kick off at arbitrary points in the AST:
+///   - take an existing symbol table as a parameter, extend WithSymbolTable,
+///     and construct with WithSymbolTable(const SymbolTable &)
 struct WithSymbolTable {
-	SymbolTable symtab;
+	WithSymbolTable(const ast::SymbolTable & from) : symtab(from) {}
+	WithSymbolTable(ast::SymbolTable::ErrorDetection errorMode = ast::SymbolTable::ErrorDetection::AssertClean) : symtab(errorMode) {}
+	ast::SymbolTable symtab;
+};
+template <ast::SymbolTable::ErrorDetection errorMode>
+struct WithSymbolTableX : WithSymbolTable {
+	WithSymbolTableX() : WithSymbolTable(errorMode) {}
 };
 
Index: src/AST/Pass.impl.hpp
===================================================================
--- src/AST/Pass.impl.hpp	(revision 24d6572fc571b2a894d56a9335edd57899c448c0)
+++ src/AST/Pass.impl.hpp	(revision 62d62db217dc9f917346863faa9d03148d98844f)
@@ -72,5 +72,5 @@
 		template<typename it_t, template <class...> class container_t>
 		static inline void take_all( it_t it, container_t<ast::ptr<ast::Decl>> * decls, bool * mutated = nullptr ) {
-			if(empty(decls)) return;
+			if ( empty( decls ) ) return;
 
 			std::transform(decls->begin(), decls->end(), it, [](const ast::Decl * decl) -> auto {
@@ -78,14 +78,14 @@
 				});
 			decls->clear();
-			if(mutated) *mutated = true;
+			if ( mutated ) *mutated = true;
 		}
 
 		template<typename it_t, template <class...> class container_t>
 		static inline void take_all( it_t it, container_t<ast::ptr<ast::Stmt>> * stmts, bool * mutated = nullptr ) {
-			if(empty(stmts)) return;
+			if ( empty( stmts ) ) return;
 
 			std::move(stmts->begin(), stmts->end(), it);
 			stmts->clear();
-			if(mutated) *mutated = true;
+			if ( mutated ) *mutated = true;
 		}
 
@@ -93,5 +93,5 @@
 		/// Check if should be skipped, different for pointers and containers
 		template<typename node_t>
-		bool skip( const ast::ptr<node_t> & val) {
+		bool skip( const ast::ptr<node_t> & val ) {
 			return !val;
 		}
@@ -110,5 +110,5 @@
 
 		template<typename node_t>
-		const node_t & get( const node_t & val, long) {
+		const node_t & get( const node_t & val, long ) {
 			return val;
 		}
@@ -126,286 +126,282 @@
 		}
 	}
-
-	template< typename core_t >
-	template< typename node_t >
-	auto ast::Pass< core_t >::call_accept( const node_t * node )
-		-> typename ast::Pass< core_t >::template generic_call_accept_result<node_t>::type
-	{
-		__pedantic_pass_assert( __visit_children() );
-		__pedantic_pass_assert( node );
-
-		static_assert( !std::is_base_of<ast::Expr, node_t>::value, "ERROR");
-		static_assert( !std::is_base_of<ast::Stmt, node_t>::value, "ERROR");
-
-		auto nval = node->accept( *this );
-		__pass::result1<
-			typename std::remove_pointer< decltype( node->accept(*this) ) >::type
-		> res;
-		res.differs = nval != node;
-		res.value = nval;
-		return res;
-	}
-
-	template< typename core_t >
-	__pass::template result1<ast::Expr> ast::Pass< core_t >::call_accept( const ast::Expr * expr ) {
-		__pedantic_pass_assert( __visit_children() );
-		__pedantic_pass_assert( expr );
-
-		auto nval = expr->accept( *this );
-		return { nval != expr, nval };
-	}
-
-	template< typename core_t >
-	__pass::template result1<ast::Stmt> ast::Pass< core_t >::call_accept( const ast::Stmt * stmt ) {
-		__pedantic_pass_assert( __visit_children() );
-		__pedantic_pass_assert( stmt );
-
-		const ast::Stmt * nval = stmt->accept( *this );
-		return { nval != stmt, nval };
-	}
-
-	template< typename core_t >
-	__pass::template result1<ast::Expr> ast::Pass< core_t >::call_accept_top( const ast::Expr * expr ) {
-		__pedantic_pass_assert( __visit_children() );
-		__pedantic_pass_assert( expr );
-
-		const ast::TypeSubstitution ** typeSubs_ptr = __pass::typeSubs( core, 0 );
-		if ( typeSubs_ptr && expr->env ) {
-			*typeSubs_ptr = expr->env;
-		}
-
-		auto nval = expr->accept( *this );
-		return { nval != expr, nval };
-	}
-
-	template< typename core_t >
-	__pass::template result1<ast::Stmt> ast::Pass< core_t >::call_accept_as_compound( const ast::Stmt * stmt ) {
-		__pedantic_pass_assert( __visit_children() );
-		__pedantic_pass_assert( stmt );
-
-		// add a few useful symbols to the scope
-		using __pass::empty;
-
-		// get the stmts/decls that will need to be spliced in
-		auto stmts_before = __pass::stmtsToAddBefore( core, 0 );
-		auto stmts_after  = __pass::stmtsToAddAfter ( core, 0 );
-		auto decls_before = __pass::declsToAddBefore( core, 0 );
-		auto decls_after  = __pass::declsToAddAfter ( core, 0 );
-
-		// These may be modified by subnode but most be restored once we exit this statemnet.
-		ValueGuardPtr< const ast::TypeSubstitution * > __old_env         ( __pass::typeSubs( core, 0 ) );
-		ValueGuardPtr< typename std::remove_pointer< decltype(stmts_before) >::type > __old_decls_before( stmts_before );
-		ValueGuardPtr< typename std::remove_pointer< decltype(stmts_after ) >::type > __old_decls_after ( stmts_after  );
-		ValueGuardPtr< typename std::remove_pointer< decltype(decls_before) >::type > __old_stmts_before( decls_before );
-		ValueGuardPtr< typename std::remove_pointer< decltype(decls_after ) >::type > __old_stmts_after ( decls_after  );
-
-		// Now is the time to actually visit the node
-		const ast::Stmt * nstmt = stmt->accept( *this );
-
-		// If the pass doesn't want to add anything then we are done
-		if( empty(stmts_before) && empty(stmts_after) && empty(decls_before) && empty(decls_after) ) {
-			return { nstmt != stmt, nstmt };
-		}
-
-		// Make sure that it is either adding statements or declartions but not both
-		// this is because otherwise the order would be awkward to predict
-		assert(( empty( stmts_before ) && empty( stmts_after ))
-		    || ( empty( decls_before ) && empty( decls_after )) );
-
-		// Create a new Compound Statement to hold the new decls/stmts
-		ast::CompoundStmt * compound = new ast::CompoundStmt( stmt->location );
-
-		// Take all the declarations that go before
-		__pass::take_all( std::back_inserter( compound->kids ), decls_before );
-		__pass::take_all( std::back_inserter( compound->kids ), stmts_before );
-
-		// Insert the original declaration
-		compound->kids.emplace_back( nstmt );
-
-		// Insert all the declarations that go before
-		__pass::take_all( std::back_inserter( compound->kids ), decls_after );
-		__pass::take_all( std::back_inserter( compound->kids ), stmts_after );
-
-		return {true, compound};
-	}
-
-	template< typename core_t >
-	template< template <class...> class container_t >
-	__pass::template resultNstmt<container_t> ast::Pass< core_t >::call_accept( const container_t< ptr<Stmt> > & statements ) {
-		__pedantic_pass_assert( __visit_children() );
-		if( statements.empty() ) return {};
-
-		// We are going to aggregate errors for all these statements
-		SemanticErrorException errors;
-
-		// add a few useful symbols to the scope
-		using __pass::empty;
-
-		// get the stmts/decls that will need to be spliced in
-		auto stmts_before = __pass::stmtsToAddBefore( core, 0 );
-		auto stmts_after  = __pass::stmtsToAddAfter ( core, 0 );
-		auto decls_before = __pass::declsToAddBefore( core, 0 );
-		auto decls_after  = __pass::declsToAddAfter ( core, 0 );
-
-		// These may be modified by subnode but most be restored once we exit this statemnet.
-		ValueGuardPtr< typename std::remove_pointer< decltype(stmts_before) >::type > __old_decls_before( stmts_before );
-		ValueGuardPtr< typename std::remove_pointer< decltype(stmts_after ) >::type > __old_decls_after ( stmts_after  );
-		ValueGuardPtr< typename std::remove_pointer< decltype(decls_before) >::type > __old_stmts_before( decls_before );
-		ValueGuardPtr< typename std::remove_pointer< decltype(decls_after ) >::type > __old_stmts_after ( decls_after  );
-
-		// update pass statitistics
-		pass_visitor_stats.depth++;
-		pass_visitor_stats.max->push(pass_visitor_stats.depth);
-		pass_visitor_stats.avg->push(pass_visitor_stats.depth);
-
-		__pass::resultNstmt<container_t> new_kids;
-		for( auto value : enumerate( statements ) ) {
-			try {
-				size_t i = value.idx;
-				const Stmt * stmt = value.val;
-				__pedantic_pass_assert( stmt );
-				const ast::Stmt * new_stmt = stmt->accept( *this );
-				assert( new_stmt );
-				if(new_stmt != stmt ) { new_kids.differs = true; }
-
-				// Make sure that it is either adding statements or declartions but not both
-				// this is because otherwise the order would be awkward to predict
-				assert(( empty( stmts_before ) && empty( stmts_after ))
-				    || ( empty( decls_before ) && empty( decls_after )) );
-
-				// Take all the statements which should have gone after, N/A for first iteration
-				new_kids.take_all( decls_before );
-				new_kids.take_all( stmts_before );
-
-				// Now add the statement if there is one
-				if(new_stmt != stmt) {
-					new_kids.values.emplace_back( new_stmt, i, false );
-				} else {
-					new_kids.values.emplace_back( nullptr, i, true );
-				}
-
-				// Take all the declarations that go before
-				new_kids.take_all( decls_after );
-				new_kids.take_all( stmts_after );
+}
+
+template< typename core_t >
+template< typename node_t >
+auto ast::Pass< core_t >::call_accept( const node_t * node ) ->
+	typename ast::Pass< core_t >::template generic_call_accept_result<node_t>::type
+{
+	__pedantic_pass_assert( __visit_children() );
+	__pedantic_pass_assert( node );
+
+	static_assert( !std::is_base_of<ast::Expr, node_t>::value, "ERROR" );
+	static_assert( !std::is_base_of<ast::Stmt, node_t>::value, "ERROR" );
+
+	auto nval = node->accept( *this );
+	__pass::result1<
+		typename std::remove_pointer< decltype( node->accept(*this) ) >::type
+	> res;
+	res.differs = nval != node;
+	res.value = nval;
+	return res;
+}
+
+template< typename core_t >
+ast::__pass::template result1<ast::Expr> ast::Pass< core_t >::call_accept( const ast::Expr * expr ) {
+	__pedantic_pass_assert( __visit_children() );
+	__pedantic_pass_assert( expr );
+
+	auto nval = expr->accept( *this );
+	return { nval != expr, nval };
+}
+
+template< typename core_t >
+ast::__pass::template result1<ast::Stmt> ast::Pass< core_t >::call_accept( const ast::Stmt * stmt ) {
+	__pedantic_pass_assert( __visit_children() );
+	__pedantic_pass_assert( stmt );
+
+	const ast::Stmt * nval = stmt->accept( *this );
+	return { nval != stmt, nval };
+}
+
+template< typename core_t >
+ast::__pass::template result1<ast::Expr> ast::Pass< core_t >::call_accept_top( const ast::Expr * expr ) {
+	__pedantic_pass_assert( __visit_children() );
+	__pedantic_pass_assert( expr );
+
+	const ast::TypeSubstitution ** typeSubs_ptr = __pass::typeSubs( core, 0 );
+	if ( typeSubs_ptr && expr->env ) {
+		*typeSubs_ptr = expr->env;
+	}
+
+	auto nval = expr->accept( *this );
+	return { nval != expr, nval };
+}
+
+template< typename core_t >
+ast::__pass::template result1<ast::Stmt> ast::Pass< core_t >::call_accept_as_compound( const ast::Stmt * stmt ) {
+	__pedantic_pass_assert( __visit_children() );
+	__pedantic_pass_assert( stmt );
+
+	// add a few useful symbols to the scope
+	using __pass::empty;
+
+	// get the stmts/decls that will need to be spliced in
+	auto stmts_before = __pass::stmtsToAddBefore( core, 0 );
+	auto stmts_after  = __pass::stmtsToAddAfter ( core, 0 );
+	auto decls_before = __pass::declsToAddBefore( core, 0 );
+	auto decls_after  = __pass::declsToAddAfter ( core, 0 );
+
+	// These may be modified by subnode but most be restored once we exit this statemnet.
+	ValueGuardPtr< const ast::TypeSubstitution * > __old_env         ( __pass::typeSubs( core, 0 ) );
+	ValueGuardPtr< typename std::remove_pointer< decltype(stmts_before) >::type > __old_decls_before( stmts_before );
+	ValueGuardPtr< typename std::remove_pointer< decltype(stmts_after ) >::type > __old_decls_after ( stmts_after  );
+	ValueGuardPtr< typename std::remove_pointer< decltype(decls_before) >::type > __old_stmts_before( decls_before );
+	ValueGuardPtr< typename std::remove_pointer< decltype(decls_after ) >::type > __old_stmts_after ( decls_after  );
+
+	// Now is the time to actually visit the node
+	const ast::Stmt * nstmt = stmt->accept( *this );
+
+	// If the pass doesn't want to add anything then we are done
+	if ( empty(stmts_before) && empty(stmts_after) && empty(decls_before) && empty(decls_after) ) {
+		return { nstmt != stmt, nstmt };
+	}
+
+	// Make sure that it is either adding statements or declartions but not both
+	// this is because otherwise the order would be awkward to predict
+	assert(( empty( stmts_before ) && empty( stmts_after ))
+	    || ( empty( decls_before ) && empty( decls_after )) );
+
+	// Create a new Compound Statement to hold the new decls/stmts
+	ast::CompoundStmt * compound = new ast::CompoundStmt( stmt->location );
+
+	// Take all the declarations that go before
+	__pass::take_all( std::back_inserter( compound->kids ), decls_before );
+	__pass::take_all( std::back_inserter( compound->kids ), stmts_before );
+
+	// Insert the original declaration
+	compound->kids.emplace_back( nstmt );
+
+	// Insert all the declarations that go before
+	__pass::take_all( std::back_inserter( compound->kids ), decls_after );
+	__pass::take_all( std::back_inserter( compound->kids ), stmts_after );
+
+	return { true, compound };
+}
+
+template< typename core_t >
+template< template <class...> class container_t >
+ast::__pass::template resultNstmt<container_t> ast::Pass< core_t >::call_accept( const container_t< ptr<Stmt> > & statements ) {
+	__pedantic_pass_assert( __visit_children() );
+	if ( statements.empty() ) return {};
+
+	// We are going to aggregate errors for all these statements
+	SemanticErrorException errors;
+
+	// add a few useful symbols to the scope
+	using __pass::empty;
+
+	// get the stmts/decls that will need to be spliced in
+	auto stmts_before = __pass::stmtsToAddBefore( core, 0 );
+	auto stmts_after  = __pass::stmtsToAddAfter ( core, 0 );
+	auto decls_before = __pass::declsToAddBefore( core, 0 );
+	auto decls_after  = __pass::declsToAddAfter ( core, 0 );
+
+	// These may be modified by subnode but most be restored once we exit this statemnet.
+	ValueGuardPtr< typename std::remove_pointer< decltype(stmts_before) >::type > __old_decls_before( stmts_before );
+	ValueGuardPtr< typename std::remove_pointer< decltype(stmts_after ) >::type > __old_decls_after ( stmts_after  );
+	ValueGuardPtr< typename std::remove_pointer< decltype(decls_before) >::type > __old_stmts_before( decls_before );
+	ValueGuardPtr< typename std::remove_pointer< decltype(decls_after ) >::type > __old_stmts_after ( decls_after  );
+
+	// update pass statitistics
+	pass_visitor_stats.depth++;
+	pass_visitor_stats.max->push(pass_visitor_stats.depth);
+	pass_visitor_stats.avg->push(pass_visitor_stats.depth);
+
+	__pass::resultNstmt<container_t> new_kids;
+	for ( auto value : enumerate( statements ) ) {
+		try {
+			size_t i = value.idx;
+			const Stmt * stmt = value.val;
+			__pedantic_pass_assert( stmt );
+			const ast::Stmt * new_stmt = stmt->accept( *this );
+			assert( new_stmt );
+			if ( new_stmt != stmt ) { new_kids.differs = true; }
+
+			// Make sure that it is either adding statements or declartions but not both
+			// this is because otherwise the order would be awkward to predict
+			assert(( empty( stmts_before ) && empty( stmts_after ))
+			    || ( empty( decls_before ) && empty( decls_after )) );
+
+			// Take all the statements which should have gone after, N/A for first iteration
+			new_kids.take_all( decls_before );
+			new_kids.take_all( stmts_before );
+
+			// Now add the statement if there is one
+			if ( new_stmt != stmt ) {
+				new_kids.values.emplace_back( new_stmt, i, false );
+			} else {
+				new_kids.values.emplace_back( nullptr, i, true );
 			}
-			catch ( SemanticErrorException &e ) {
-				errors.append( e );
+
+			// Take all the declarations that go before
+			new_kids.take_all( decls_after );
+			new_kids.take_all( stmts_after );
+		} catch ( SemanticErrorException &e ) {
+			errors.append( e );
+		}
+	}
+	pass_visitor_stats.depth--;
+	if ( !errors.isEmpty() ) { throw errors; }
+
+	return new_kids;
+}
+
+template< typename core_t >
+template< template <class...> class container_t, typename node_t >
+ast::__pass::template resultN<container_t, node_t> ast::Pass< core_t >::call_accept( const container_t< ast::ptr<node_t> > & container ) {
+	__pedantic_pass_assert( __visit_children() );
+	if ( container.empty() ) return {};
+	SemanticErrorException errors;
+
+	pass_visitor_stats.depth++;
+	pass_visitor_stats.max->push(pass_visitor_stats.depth);
+	pass_visitor_stats.avg->push(pass_visitor_stats.depth);
+
+	bool mutated = false;
+	container_t<ptr<node_t>> new_kids;
+	for ( const node_t * node : container ) {
+		try {
+			__pedantic_pass_assert( node );
+			const node_t * new_stmt = strict_dynamic_cast< const node_t * >( node->accept( *this ) );
+			if ( new_stmt != node ) {
+				mutated = true;
+				new_kids.emplace_back( new_stmt );
+			} else {
+				new_kids.emplace_back( nullptr );
 			}
-		}
-		pass_visitor_stats.depth--;
-		if ( !errors.isEmpty() ) { throw errors; }
-
-		return new_kids;
-	}
-
-	template< typename core_t >
-	template< template <class...> class container_t, typename node_t >
-	__pass::template resultN<container_t, node_t> ast::Pass< core_t >::call_accept( const container_t< ast::ptr<node_t> > & container ) {
-		__pedantic_pass_assert( __visit_children() );
-		if( container.empty() ) return {};
-		SemanticErrorException errors;
-
-		pass_visitor_stats.depth++;
-		pass_visitor_stats.max->push(pass_visitor_stats.depth);
-		pass_visitor_stats.avg->push(pass_visitor_stats.depth);
-
-		bool mutated = false;
-		container_t<ptr<node_t>> new_kids;
-		for ( const node_t * node : container ) {
-			try {
-				__pedantic_pass_assert( node );
-				const node_t * new_stmt = strict_dynamic_cast< const node_t * >( node->accept( *this ) );
-				if(new_stmt != node ) {
-					mutated = true;
-					new_kids.emplace_back( new_stmt );
-				} else {
-					new_kids.emplace_back( nullptr );
-				}
-
-			}
-			catch( SemanticErrorException &e ) {
-				errors.append( e );
-			}
-		}
-
-		__pedantic_pass_assert( new_kids.size() == container.size() );
-		pass_visitor_stats.depth--;
-		if ( ! errors.isEmpty() ) { throw errors; }
-
-		return ast::__pass::resultN<container_t, node_t>{ mutated, new_kids };
-	}
-
-	template< typename core_t >
-	template<typename node_t, typename super_t, typename field_t>
-	void ast::Pass< core_t >::maybe_accept(
-		const node_t * & parent,
-		field_t super_t::*field
-	) {
-		static_assert( std::is_base_of<super_t, node_t>::value, "Error deducing member object" );
-
-		if(__pass::skip(parent->*field)) return;
-		const auto & old_val = __pass::get(parent->*field, 0);
-
-		static_assert( !std::is_same<const ast::Node * &, decltype(old_val)>::value, "ERROR");
-
-		auto new_val = call_accept( old_val );
-
-		static_assert( !std::is_same<const ast::Node *, decltype(new_val)>::value /* || std::is_same<int, decltype(old_val)>::value */, "ERROR");
-
-		if( new_val.differs ) {
-			auto new_parent = __pass::mutate<core_t>(parent);
-			new_val.apply(new_parent, field);
-			parent = new_parent;
-		}
-	}
-
-	template< typename core_t >
-	template<typename node_t, typename super_t, typename field_t>
-	void ast::Pass< core_t >::maybe_accept_top(
-		const node_t * & parent,
-		field_t super_t::*field
-	) {
-		static_assert( std::is_base_of<super_t, node_t>::value, "Error deducing member object" );
-
-		if(__pass::skip(parent->*field)) return;
-		const auto & old_val = __pass::get(parent->*field, 0);
-
-		static_assert( !std::is_same<const ast::Node * &, decltype(old_val)>::value, "ERROR");
-
-		auto new_val = call_accept_top( old_val );
-
-		static_assert( !std::is_same<const ast::Node *, decltype(new_val)>::value /* || std::is_same<int, decltype(old_val)>::value */, "ERROR");
-
-		if( new_val.differs ) {
-			auto new_parent = __pass::mutate<core_t>(parent);
-			new_val.apply(new_parent, field);
-			parent = new_parent;
-		}
-	}
-
-	template< typename core_t >
-	template<typename node_t, typename super_t, typename field_t>
-	void ast::Pass< core_t >::maybe_accept_as_compound(
-		const node_t * & parent,
-		field_t super_t::*child
-	) {
-		static_assert( std::is_base_of<super_t, node_t>::value, "Error deducing member object" );
-
-		if(__pass::skip(parent->*child)) return;
-		const auto & old_val = __pass::get(parent->*child, 0);
-
-		static_assert( !std::is_same<const ast::Node * &, decltype(old_val)>::value, "ERROR");
-
-		auto new_val = call_accept_as_compound( old_val );
-
-		static_assert( !std::is_same<const ast::Node *, decltype(new_val)>::value || std::is_same<int, decltype(old_val)>::value, "ERROR");
-
-		if( new_val.differs ) {
-			auto new_parent = __pass::mutate<core_t>(parent);
-			new_val.apply( new_parent, child );
-			parent = new_parent;
-		}
-	}
-
+		} catch ( SemanticErrorException &e ) {
+			errors.append( e );
+		}
+	}
+
+	__pedantic_pass_assert( new_kids.size() == container.size() );
+	pass_visitor_stats.depth--;
+	if ( !errors.isEmpty() ) { throw errors; }
+
+	return ast::__pass::resultN<container_t, node_t>{ mutated, new_kids };
+}
+
+template< typename core_t >
+template<typename node_t, typename super_t, typename field_t>
+void ast::Pass< core_t >::maybe_accept(
+	const node_t * & parent,
+	field_t super_t::*field
+) {
+	static_assert( std::is_base_of<super_t, node_t>::value, "Error deducing member object" );
+
+	if ( __pass::skip( parent->*field ) ) return;
+	const auto & old_val = __pass::get(parent->*field, 0);
+
+	static_assert( !std::is_same<const ast::Node * &, decltype(old_val)>::value, "ERROR" );
+
+	auto new_val = call_accept( old_val );
+
+	static_assert( !std::is_same<const ast::Node *, decltype(new_val)>::value /* || std::is_same<int, decltype(old_val)>::value */, "ERROR" );
+
+	if ( new_val.differs ) {
+		auto new_parent = __pass::mutate<core_t>(parent);
+		new_val.apply(new_parent, field);
+		parent = new_parent;
+	}
+}
+
+template< typename core_t >
+template<typename node_t, typename super_t, typename field_t>
+void ast::Pass< core_t >::maybe_accept_top(
+	const node_t * & parent,
+	field_t super_t::*field
+) {
+	static_assert( std::is_base_of<super_t, node_t>::value, "Error deducing member object" );
+
+	if ( __pass::skip( parent->*field ) ) return;
+	const auto & old_val = __pass::get(parent->*field, 0);
+
+	static_assert( !std::is_same<const ast::Node * &, decltype(old_val)>::value, "ERROR" );
+
+	auto new_val = call_accept_top( old_val );
+
+	static_assert( !std::is_same<const ast::Node *, decltype(new_val)>::value /* || std::is_same<int, decltype(old_val)>::value */, "ERROR" );
+
+	if ( new_val.differs ) {
+		auto new_parent = __pass::mutate<core_t>(parent);
+		new_val.apply(new_parent, field);
+		parent = new_parent;
+	}
+}
+
+template< typename core_t >
+template<typename node_t, typename super_t, typename field_t>
+void ast::Pass< core_t >::maybe_accept_as_compound(
+	const node_t * & parent,
+	field_t super_t::*child
+) {
+	static_assert( std::is_base_of<super_t, node_t>::value, "Error deducing member object" );
+
+	if ( __pass::skip( parent->*child ) ) return;
+	const auto & old_val = __pass::get(parent->*child, 0);
+
+	static_assert( !std::is_same<const ast::Node * &, decltype(old_val)>::value, "ERROR" );
+
+	auto new_val = call_accept_as_compound( old_val );
+
+	static_assert( !std::is_same<const ast::Node *, decltype(new_val)>::value || std::is_same<int, decltype(old_val)>::value, "ERROR" );
+
+	if ( new_val.differs ) {
+		auto new_parent = __pass::mutate<core_t>(parent);
+		new_val.apply( new_parent, child );
+		parent = new_parent;
+	}
 }
 
@@ -761,18 +757,16 @@
 
 	if ( __visit_children() ) {
-		// Do not enter (or leave) a new scope if atFunctionTop. Remember to save the result.
-		auto guard1 = makeFuncGuard( [this, enterScope = !this->atFunctionTop]() {
-			if ( enterScope ) {
-				__pass::symtab::enter(core, 0);
-			}
-		}, [this, leaveScope = !this->atFunctionTop]() {
-			if ( leaveScope ) {
-				__pass::symtab::leave(core, 0);
-			}
-		});
-		ValueGuard< bool > guard2( atFunctionTop );
-		atFunctionTop = false;
-		guard_scope guard3 { *this };
-		maybe_accept( node, &CompoundStmt::kids );
+		// Do not enter (or leave) a new symbol table scope if atFunctionTop.
+		// But always enter (and leave) a new general scope.
+		if ( atFunctionTop ) {
+			ValueGuard< bool > guard1( atFunctionTop );
+			atFunctionTop = false;
+			guard_scope guard2( *this );
+			maybe_accept( node, &CompoundStmt::kids );
+		} else {
+			guard_symtab guard1( *this );
+			guard_scope guard2( *this );
+			maybe_accept( node, &CompoundStmt::kids );
+		}
 	}
 
Index: src/AST/SymbolTable.cpp
===================================================================
--- src/AST/SymbolTable.cpp	(revision 24d6572fc571b2a894d56a9335edd57899c448c0)
+++ src/AST/SymbolTable.cpp	(revision 62d62db217dc9f917346863faa9d03148d98844f)
@@ -91,9 +91,17 @@
 }
 
-SymbolTable::SymbolTable()
+SymbolTable::SymbolTable( ErrorDetection errorMode )
 : idTable(), typeTable(), structTable(), enumTable(), unionTable(), traitTable(),
-  prevScope(), scope( 0 ), repScope( 0 ) { ++*stats().count; }
+  prevScope(), scope( 0 ), repScope( 0 ), errorMode(errorMode) { ++*stats().count; }
 
 SymbolTable::~SymbolTable() { stats().size->push( idTable ? idTable->size() : 0 ); }
+
+void SymbolTable::OnFindError( CodeLocation location, std::string error ) const {
+	assertf( errorMode != AssertClean, "Name collision/redefinition, found during a compilation phase where none should be possible.  Detail: %s", error.c_str() );
+	if (errorMode == ValidateOnAdd) {
+		SemanticError(location, error);
+	}
+	assertf( errorMode == IgnoreErrors, "Unrecognized symbol-table error mode %d", errorMode );
+}
 
 void SymbolTable::enterScope() {
@@ -274,31 +282,29 @@
 }
 
-namespace {
-	/// true if redeclaration conflict between two types
-	bool addedTypeConflicts( const NamedTypeDecl * existing, const NamedTypeDecl * added ) {
-		if ( existing->base == nullptr ) {
-			return false;
-		} else if ( added->base == nullptr ) {
-			return true;
-		} else {
-			// typedef redeclarations are errors only if types are different
-			if ( ! ResolvExpr::typesCompatible( existing->base, added->base ) ) {
-				SemanticError( added->location, "redeclaration of " + added->name );
-			}
-		}
-		// does not need to be added to the table if both existing and added have a base that are
-		// the same
+bool SymbolTable::addedTypeConflicts(
+		const NamedTypeDecl * existing, const NamedTypeDecl * added ) const {
+	if ( existing->base == nullptr ) {
+		return false;
+	} else if ( added->base == nullptr ) {
 		return true;
-	}
-
-	/// true if redeclaration conflict between two aggregate declarations
-	bool addedDeclConflicts( const AggregateDecl * existing, const AggregateDecl * added ) {
-		if ( ! existing->body ) {
-			return false;
-		} else if ( added->body ) {
-			SemanticError( added, "redeclaration of " );
-		}
-		return true;
-	}
+	} else {
+		// typedef redeclarations are errors only if types are different
+		if ( ! ResolvExpr::typesCompatible( existing->base, added->base ) ) {
+			OnFindError( added->location, "redeclaration of " + added->name );
+		}
+	}
+	// does not need to be added to the table if both existing and added have a base that are
+	// the same
+	return true;
+}
+
+bool SymbolTable::addedDeclConflicts( 
+		const AggregateDecl * existing, const AggregateDecl * added ) const {
+	if ( ! existing->body ) {
+		return false;
+	} else if ( added->body ) {
+		OnFindError( added, "redeclaration of " );
+	}
+	return true;
 }
 
@@ -653,10 +659,10 @@
 		if ( deleter && ! existing.deleter ) {
 			if ( handleConflicts.mode == OnConflict::Error ) {
-				SemanticError( added, "deletion of defined identifier " );
+				OnFindError( added, "deletion of defined identifier " );
 			}
 			return true;
 		} else if ( ! deleter && existing.deleter ) {
 			if ( handleConflicts.mode == OnConflict::Error ) {
-				SemanticError( added, "definition of deleted identifier " );
+				OnFindError( added, "definition of deleted identifier " );
 			}
 			return true;
@@ -666,5 +672,5 @@
 		if ( isDefinition( added ) && isDefinition( existing.id ) ) {
 			if ( handleConflicts.mode == OnConflict::Error ) {
-				SemanticError( added,
+				OnFindError( added,
 					isFunction( added ) ?
 						"duplicate function definition for " :
@@ -675,5 +681,5 @@
 	} else {
 		if ( handleConflicts.mode == OnConflict::Error ) {
-			SemanticError( added, "duplicate definition for " );
+			OnFindError( added, "duplicate definition for " );
 		}
 		return true;
@@ -727,5 +733,5 @@
 		// Check that a Cforall declaration doesn't override any C declaration
 		if ( hasCompatibleCDecl( name, mangleName ) ) {
-			SemanticError( decl, "Cforall declaration hides C function " );
+			OnFindError( decl, "Cforall declaration hides C function " );
 		}
 	} else {
@@ -733,5 +739,5 @@
 		// type-compatibility, which it may not be.
 		if ( hasIncompatibleCDecl( name, mangleName ) ) {
-			SemanticError( decl, "conflicting overload of C function " );
+			OnFindError( decl, "conflicting overload of C function " );
 		}
 	}
Index: src/AST/SymbolTable.hpp
===================================================================
--- src/AST/SymbolTable.hpp	(revision 24d6572fc571b2a894d56a9335edd57899c448c0)
+++ src/AST/SymbolTable.hpp	(revision 62d62db217dc9f917346863faa9d03148d98844f)
@@ -93,6 +93,23 @@
 
 public:
-	SymbolTable();
+
+	/// Mode to control when (during which pass) user-caused name-declaration errors get reported.
+	/// The default setting `AssertClean` supports, "I expect all user-caused errors to have been
+	/// reported by now," or, "I wouldn't know what to do with an error; are there even any here?"
+	enum ErrorDetection {
+		AssertClean,               ///< invalid user decls => assert fails during addFoo (default)
+		ValidateOnAdd,             ///< invalid user decls => calls SemanticError during addFoo
+		IgnoreErrors               ///< acts as if unspecified decls were removed, forcing validity
+	};
+
+	explicit SymbolTable(
+		ErrorDetection             ///< mode for the lifetime of the symbol table (whole pass)
+	);
+	SymbolTable() : SymbolTable(AssertClean) {}
 	~SymbolTable();
+
+	ErrorDetection getErrorMode() const {
+		return errorMode;
+	}
 
 	// when using an indexer manually (e.g., within a mutator traversal), it is necessary to
@@ -158,4 +175,16 @@
 
 private:
+	void OnFindError( CodeLocation location, std::string error ) const;
+
+	template< typename T >
+	void OnFindError( const T * obj, const std::string & error ) const {
+		OnFindError( obj->location, toString( error, obj ) );
+	}
+
+	template< typename T >
+	void OnFindError( CodeLocation location, const T * obj, const std::string & error ) const {
+		OnFindError( location, toString( error, obj ) );
+	}
+
 	/// Ensures that a proper backtracking scope exists before a mutation
 	void lazyInitScope();
@@ -168,8 +197,17 @@
 	bool removeSpecialOverrides( IdData & decl, MangleTable::Ptr & mangleTable );
 
-	/// Options for handling identifier conflicts
+	/// Error detection mode given at construction (pass-specific).
+	/// Logically const, except that the symbol table's push-pop is achieved by autogenerated
+	/// assignment onto self.  The feield is left motuable to keep this code-gen simple.
+	/// Conceptual constness is preserved by all SymbolTable in a stack sharing the same mode.
+	ErrorDetection errorMode;
+
+	/// Options for handling identifier conflicts.
+	/// Varies according to AST location during traversal: captures semantics of the construct
+	/// being visited as "would shadow" vs "must not collide."
+	/// At a given AST location, is the same for every pass.
 	struct OnConflict {
 		enum {
-			Error,  ///< Throw a semantic error
+			Error,  ///< Follow the current pass's ErrorDetection mode (may throw a semantic error)
 			Delete  ///< Delete the earlier version with the delete statement
 		} mode;
@@ -191,4 +229,10 @@
 		const Decl * deleter );
 
+	/// true if redeclaration conflict between two types
+	bool addedTypeConflicts( const NamedTypeDecl * existing, const NamedTypeDecl * added ) const;
+
+	/// true if redeclaration conflict between two aggregate declarations
+	bool addedDeclConflicts( const AggregateDecl * existing, const AggregateDecl * added ) const;
+
 	/// common code for addId, addDeletedId, etc.
 	void addIdCommon(
@@ -213,4 +257,5 @@
 }
 
+
 // Local Variables: //
 // tab-width: 4 //
Index: src/AST/Util.cpp
===================================================================
--- src/AST/Util.cpp	(revision 24d6572fc571b2a894d56a9335edd57899c448c0)
+++ src/AST/Util.cpp	(revision 62d62db217dc9f917346863faa9d03148d98844f)
@@ -83,4 +83,23 @@
 }
 
+/// Check that the MemberExpr has an aggregate type and matching member.
+void memberMatchesAggregate( const MemberExpr * expr ) {
+	const Type * aggrType = expr->aggregate->result->stripReferences();
+	const AggregateDecl * decl = nullptr;
+	if ( auto inst = dynamic_cast<const StructInstType *>( aggrType ) ) {
+		decl = inst->base;
+	} else if ( auto inst = dynamic_cast<const UnionInstType *>( aggrType ) ) {
+		decl = inst->base;
+	}
+	assertf( decl, "Aggregate of member not correct type." );
+
+	for ( auto aggrMember : decl->members ) {
+		if ( expr->member == aggrMember ) {
+			return;
+		}
+	}
+	assertf( false, "Member not found." );
+}
+
 struct InvariantCore {
 	// To save on the number of visits: this is a kind of composed core.
@@ -108,4 +127,9 @@
 	}
 
+	void previsit( const MemberExpr * node ) {
+		previsit( (const ParseNode *)node );
+		memberMatchesAggregate( node );
+	}
+
 	void postvisit( const Node * node ) {
 		no_strong_cycles.postvisit( node );
