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 );
Index: src/Concurrency/Actors.cpp
===================================================================
--- src/Concurrency/Actors.cpp	(revision 24d6572fc571b2a894d56a9335edd57899c448c0)
+++ src/Concurrency/Actors.cpp	(revision 62d62db217dc9f917346863faa9d03148d98844f)
@@ -38,7 +38,7 @@
     bool namedDecl = false;
 
-    // finds and sets a ptr to the Allocation enum, which is needed in the next pass
+    // finds and sets a ptr to the allocation enum, which is needed in the next pass
     void previsit( const EnumDecl * decl ) {
-        if( decl->name == "Allocation" ) *allocationDecl = decl;
+        if( decl->name == "allocation" ) *allocationDecl = decl;
     }
 
@@ -227,5 +227,5 @@
                 static inline derived_actor & ?|?( derived_actor & receiver, derived_msg & msg ) {
                     request new_req;
-                    Allocation (*my_work_fn)( derived_actor &, derived_msg & ) = receive;
+                    allocation (*my_work_fn)( derived_actor &, derived_msg & ) = receive;
                     __receive_fn fn = (__receive_fn)my_work_fn;
                     new_req{ &receiver, &msg, fn };
@@ -246,5 +246,5 @@
             ));
             
-            // Function type is: Allocation (*)( derived_actor &, derived_msg & )
+            // Function type is: allocation (*)( derived_actor &, derived_msg & )
             FunctionType * derivedReceive = new FunctionType();
             derivedReceive->params.push_back( ast::deepCopy( derivedActorRef ) );
@@ -252,5 +252,5 @@
             derivedReceive->returns.push_back( new EnumInstType( *allocationDecl ) );
 
-            // Generates: Allocation (*my_work_fn)( derived_actor &, derived_msg & ) = receive;
+            // Generates: allocation (*my_work_fn)( derived_actor &, derived_msg & ) = receive;
             sendBody->push_back( new DeclStmt(
                 decl->location,
@@ -263,5 +263,5 @@
             ));
 
-            // Function type is: Allocation (*)( actor &, message & )
+            // Function type is: allocation (*)( actor &, message & )
             FunctionType * genericReceive = new FunctionType();
             genericReceive->params.push_back( new ReferenceType( new StructInstType( *actorDecl ) ) );
@@ -269,7 +269,7 @@
             genericReceive->returns.push_back( new EnumInstType( *allocationDecl ) );
 
-            // Generates: Allocation (*fn)( actor &, message & ) = (Allocation (*)( actor &, message & ))my_work_fn;
+            // Generates: allocation (*fn)( actor &, message & ) = (allocation (*)( actor &, message & ))my_work_fn;
             // More readable synonymous code: 
-            //     typedef Allocation (*__receive_fn)(actor &, message &);
+            //     typedef allocation (*__receive_fn)(actor &, message &);
             //     __receive_fn fn = (__receive_fn)my_work_fn;
             sendBody->push_back( new DeclStmt(
@@ -422,5 +422,5 @@
     const StructDecl ** msgDecl = &msgDeclPtr;
 
-    // first pass collects ptrs to Allocation enum, request type, and generic receive fn typedef
+    // first pass collects ptrs to allocation enum, request type, and generic receive fn typedef
     // also populates maps of all derived actors and messages
     Pass<CollectactorStructDecls>::run( translationUnit, actorStructDecls, messageStructDecls, requestDecl, 
Index: src/Parser/lex.ll
===================================================================
--- src/Parser/lex.ll	(revision 24d6572fc571b2a894d56a9335edd57899c448c0)
+++ src/Parser/lex.ll	(revision 62d62db217dc9f917346863faa9d03148d98844f)
@@ -10,6 +10,6 @@
  * Created On       : Sat Sep 22 08:58:10 2001
  * Last Modified By : Peter A. Buhr
- * Last Modified On : Tue May  2 08:45:21 2023
- * Update Count     : 769
+ * Last Modified On : Fri Jun  9 10:04:00 2023
+ * Update Count     : 770
  */
 
@@ -319,4 +319,5 @@
 static			{ KEYWORD_RETURN(STATIC); }
 _Static_assert	{ KEYWORD_RETURN(STATICASSERT); }		// C11
+_static_assert	{ KEYWORD_RETURN(STATICASSERT); }		// C23
 struct			{ KEYWORD_RETURN(STRUCT); }
 suspend			{ KEYWORD_RETURN(SUSPEND); }			// CFA
Index: src/Parser/parser.yy
===================================================================
--- src/Parser/parser.yy	(revision 24d6572fc571b2a894d56a9335edd57899c448c0)
+++ src/Parser/parser.yy	(revision 62d62db217dc9f917346863faa9d03148d98844f)
@@ -10,6 +10,6 @@
 // Created On       : Sat Sep  1 20:22:55 2001
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Wed Apr 26 16:45:37 2023
-// Update Count     : 6330
+// Last Modified On : Wed Jun  7 14:32:28 2023
+// Update Count     : 6341
 //
 
@@ -108,9 +108,9 @@
 	assert( declList );
 	// printf( "distAttr1 typeSpec %p\n", typeSpec ); typeSpec->print( std::cout );
-	DeclarationNode * cur = declList, * cl = (new DeclarationNode)->addType( typeSpec );
+	DeclarationNode * cl = (new DeclarationNode)->addType( typeSpec );
 	// printf( "distAttr2 cl %p\n", cl ); cl->type->print( std::cout );
 	// cl->type->aggregate.name = cl->type->aggInst.aggregate->aggregate.name;
 
-	for ( cur = dynamic_cast<DeclarationNode *>( cur->get_next() ); cur != nullptr; cur = dynamic_cast<DeclarationNode *>( cur->get_next() ) ) {
+	for ( DeclarationNode * cur = dynamic_cast<DeclarationNode *>( declList->get_next() ); cur != nullptr; cur = dynamic_cast<DeclarationNode *>( cur->get_next() ) ) {
 		cl->cloneBaseType( cur );
 	} // for
@@ -206,7 +206,7 @@
 #define NEW_ONE  new ExpressionNode( build_constantInteger( yylloc, *new string( "1" ) ) )
 #define UPDOWN( compop, left, right ) (compop == OperKinds::LThan || compop == OperKinds::LEThan ? left : right)
-#define MISSING_ANON_FIELD "Missing loop fields with an anonymous loop index is meaningless as loop index is unavailable in loop body."
-#define MISSING_LOW "Missing low value for up-to range so index is uninitialized."
-#define MISSING_HIGH "Missing high value for down-to range so index is uninitialized."
+#define MISSING_ANON_FIELD "syntax error, missing loop fields with an anonymous loop index is meaningless as loop index is unavailable in loop body."
+#define MISSING_LOW "syntax error, missing low value for up-to range so index is uninitialized."
+#define MISSING_HIGH "syntax error, missing high value for down-to range so index is uninitialized."
 
 static ForCtrl * makeForCtrl(
@@ -232,8 +232,8 @@
 ForCtrl * forCtrl( const CodeLocation & location, DeclarationNode * index, ExpressionNode * start, enum OperKinds compop, ExpressionNode * comp, ExpressionNode * inc ) {
 	if ( index->initializer ) {
-		SemanticError( yylloc, "Direct initialization disallowed. Use instead: type var; initialization ~ comparison ~ increment." );
+		SemanticError( yylloc, "syntax error, direct initialization disallowed. Use instead: type var; initialization ~ comparison ~ increment." );
 	} // if
 	if ( index->next ) {
-		SemanticError( yylloc, "Multiple loop indexes disallowed in for-loop declaration." );
+		SemanticError( yylloc, "syntax error, multiple loop indexes disallowed in for-loop declaration." );
 	} // if
 	DeclarationNode * initDecl = index->addInitializer( new InitializerNode( start ) );
@@ -260,18 +260,18 @@
 			return forCtrl( location, type, new string( identifier->name ), start, compop, comp, inc );
 		} else {
-			SemanticError( yylloc, "Expression disallowed. Only loop-index name allowed." ); return nullptr;
+			SemanticError( yylloc, "syntax error, loop-index name missing. Expression disallowed." ); return nullptr;
 		} // if
 	} else {
-		SemanticError( yylloc, "Expression disallowed. Only loop-index name allowed." ); return nullptr;
+		SemanticError( yylloc, "syntax error, loop-index name missing. Expression disallowed. ." ); return nullptr;
 	} // if
 } // forCtrl
 
 static void IdentifierBeforeIdentifier( string & identifier1, string & identifier2, const char * kind ) {
-	SemanticError( yylloc, ::toString( "Adjacent identifiers \"", identifier1, "\" and \"", identifier2, "\" are not meaningful in a", kind, ".\n"
+	SemanticError( yylloc, ::toString( "syntax error, adjacent identifiers \"", identifier1, "\" and \"", identifier2, "\" are not meaningful in a", kind, ".\n"
 				   "Possible cause is misspelled type name or missing generic parameter." ) );
 } // IdentifierBeforeIdentifier
 
 static void IdentifierBeforeType( string & identifier, const char * kind ) {
-	SemanticError( yylloc, ::toString( "Identifier \"", identifier, "\" cannot appear before a ", kind, ".\n"
+	SemanticError( yylloc, ::toString( "syntax error, identifier \"", identifier, "\" cannot appear before a ", kind, ".\n"
 				   "Possible cause is misspelled storage/CV qualifier, misspelled typename, or missing generic parameter." ) );
 } // IdentifierBeforeType
@@ -689,15 +689,15 @@
 	// | RESUME '(' comma_expression ')' compound_statement
 	//   	{ SemanticError( yylloc, "Resume expression is currently unimplemented." ); $$ = nullptr; }
-	| IDENTIFIER IDENTIFIER								// syntax error
+	| IDENTIFIER IDENTIFIER								// invalid syntax rules
 		{ IdentifierBeforeIdentifier( *$1.str, *$2.str, "n expression" ); $$ = nullptr; }
-	| IDENTIFIER type_qualifier							// syntax error
+	| IDENTIFIER type_qualifier							// invalid syntax rules
 		{ IdentifierBeforeType( *$1.str, "type qualifier" ); $$ = nullptr; }
-	| IDENTIFIER storage_class							// syntax error
+	| IDENTIFIER storage_class							// invalid syntax rules
 		{ IdentifierBeforeType( *$1.str, "storage class" ); $$ = nullptr; }
-	| IDENTIFIER basic_type_name						// syntax error
+	| IDENTIFIER basic_type_name						// invalid syntax rules
 		{ IdentifierBeforeType( *$1.str, "type" ); $$ = nullptr; }
-	| IDENTIFIER TYPEDEFname							// syntax error
+	| IDENTIFIER TYPEDEFname							// invalid syntax rules
 		{ IdentifierBeforeType( *$1.str, "type" ); $$ = nullptr; }
-	| IDENTIFIER TYPEGENname							// syntax error
+	| IDENTIFIER TYPEGENname							// invalid syntax rules
 		{ IdentifierBeforeType( *$1.str, "type" ); $$ = nullptr; }
 	;
@@ -1152,7 +1152,7 @@
 	identifier_or_type_name ':' attribute_list_opt statement
 		{ $$ = $4->add_label( yylloc, $1, $3 ); }
-	| identifier_or_type_name ':' attribute_list_opt error // syntax error
-		{
-			SemanticError( yylloc, ::toString( "Label \"", *$1.str, "\" must be associated with a statement, "
+	| identifier_or_type_name ':' attribute_list_opt error // invalid syntax rule
+		{
+			SemanticError( yylloc, ::toString( "syntx error, label \"", *$1.str, "\" must be associated with a statement, "
 											   "where a declaration, case, or default is not a statement. "
 											   "Move the label or terminate with a semi-colon." ) );
@@ -1193,6 +1193,6 @@
 	| statement_list_nodecl statement
 		{ assert( $1 ); $1->set_last( $2 ); $$ = $1; }
-	| statement_list_nodecl error						// syntax error
-		{ SemanticError( yylloc, "Declarations only allowed at the start of the switch body, i.e., after the '{'." ); $$ = nullptr; }
+	| statement_list_nodecl error						// invalid syntax rule
+		{ SemanticError( yylloc, "syntax error, declarations only allowed at the start of the switch body, i.e., after the '{'." ); $$ = nullptr; }
 	;
 
@@ -1219,6 +1219,6 @@
 			$$ = $7 ? new StatementNode( build_compound( yylloc, (StatementNode *)((new StatementNode( $7 ))->set_last( sw )) ) ) : sw;
 		}
-	| SWITCH '(' comma_expression ')' '{' error '}'		// CFA, syntax error
-		{ SemanticError( yylloc, "Only declarations can appear before the list of case clauses." ); $$ = nullptr; }
+	| SWITCH '(' comma_expression ')' '{' error '}'		// CFA, invalid syntax rule error
+		{ SemanticError( yylloc, "synatx error, declarations can only appear before the list of case clauses." ); $$ = nullptr; }
 	| CHOOSE '(' comma_expression ')' case_clause		// CFA
 		{ $$ = new StatementNode( build_switch( yylloc, false, $3, $5 ) ); }
@@ -1228,6 +1228,6 @@
 			$$ = $7 ? new StatementNode( build_compound( yylloc, (StatementNode *)((new StatementNode( $7 ))->set_last( sw )) ) ) : sw;
 		}
-	| CHOOSE '(' comma_expression ')' '{' error '}'		// CFA, syntax error
-		{ SemanticError( yylloc, "Only declarations can appear before the list of case clauses." ); $$ = nullptr; }
+	| CHOOSE '(' comma_expression ')' '{' error '}'		// CFA, invalid syntax rule
+		{ SemanticError( yylloc, "syntax error, declarations can only appear before the list of case clauses." ); $$ = nullptr; }
 	;
 
@@ -1268,13 +1268,13 @@
 
 case_label:												// CFA
-	CASE error											// syntax error
-		{ SemanticError( yylloc, "Missing case list after case." ); $$ = nullptr; }
+	CASE error											// invalid syntax rule
+		{ SemanticError( yylloc, "syntax error, case list missing after case." ); $$ = nullptr; }
 	| CASE case_value_list ':'					{ $$ = $2; }
-	| CASE case_value_list error						// syntax error
-		{ SemanticError( yylloc, "Missing colon after case list." ); $$ = nullptr; }
+	| CASE case_value_list error						// invalid syntax rule
+		{ SemanticError( yylloc, "syntax error, colon missing after case list." ); $$ = nullptr; }
 	| DEFAULT ':'								{ $$ = new ClauseNode( build_default( yylloc ) ); }
 		// A semantic check is required to ensure only one default clause per switch/choose statement.
-	| DEFAULT error										//  syntax error
-		{ SemanticError( yylloc, "Missing colon after default." ); $$ = nullptr; }
+	| DEFAULT error										//  invalid syntax rules
+		{ SemanticError( yylloc, "syntax error, colon missing after default." ); $$ = nullptr; }
 	;
 
@@ -1405,13 +1405,13 @@
 			else { SemanticError( yylloc, MISSING_HIGH ); $$ = nullptr; }
 		}
-	| comma_expression updowneq comma_expression '~' '@' // CFA, error
+	| comma_expression updowneq comma_expression '~' '@' // CFA, invalid syntax rules
 		{ SemanticError( yylloc, MISSING_ANON_FIELD ); $$ = nullptr; }
-	| '@' updowneq '@'									// CFA, error
+	| '@' updowneq '@'									// CFA, invalid syntax rules
 		{ SemanticError( yylloc, MISSING_ANON_FIELD ); $$ = nullptr; }
-	| '@' updowneq comma_expression '~' '@'				// CFA, error
+	| '@' updowneq comma_expression '~' '@'				// CFA, invalid syntax rules
 		{ SemanticError( yylloc, MISSING_ANON_FIELD ); $$ = nullptr; }
-	| comma_expression updowneq '@' '~' '@'				// CFA, error
+	| comma_expression updowneq '@' '~' '@'				// CFA, invalid syntax rules
 		{ SemanticError( yylloc, MISSING_ANON_FIELD ); $$ = nullptr; }
-	| '@' updowneq '@' '~' '@'							// CFA, error
+	| '@' updowneq '@' '~' '@'							// CFA, invalid syntax rules
 		{ SemanticError( yylloc, MISSING_ANON_FIELD ); $$ = nullptr; }
 
@@ -1431,13 +1431,13 @@
 		{
 			if ( $4 == OperKinds::GThan || $4 == OperKinds::GEThan ) { SemanticError( yylloc, MISSING_HIGH ); $$ = nullptr; }
-			else if ( $4 == OperKinds::LEThan ) { SemanticError( yylloc, "Equality with missing high value is meaningless. Use \"~\"." ); $$ = nullptr; }
+			else if ( $4 == OperKinds::LEThan ) { SemanticError( yylloc, "syntax error, equality with missing high value is meaningless. Use \"~\"." ); $$ = nullptr; }
 			else $$ = forCtrl( yylloc, $3, $1, $3->clone(), $4, nullptr, NEW_ONE );
 		}
-	| comma_expression ';' '@' updowneq '@'				// CFA, error
-		{ SemanticError( yylloc, "Missing low/high value for up/down-to range so index is uninitialized." ); $$ = nullptr; }
+	| comma_expression ';' '@' updowneq '@'				// CFA, invalid syntax rules
+		{ SemanticError( yylloc, "syntax error, missing low/high value for up/down-to range so index is uninitialized." ); $$ = nullptr; }
 
 	| comma_expression ';' comma_expression updowneq comma_expression '~' comma_expression // CFA
 		{ $$ = forCtrl( yylloc, $3, $1, UPDOWN( $4, $3->clone(), $5 ), $4, UPDOWN( $4, $5->clone(), $3->clone() ), $7 ); }
-	| comma_expression ';' '@' updowneq comma_expression '~' comma_expression // CFA, error
+	| comma_expression ';' '@' updowneq comma_expression '~' comma_expression // CFA, invalid syntax rules
 		{
 			if ( $4 == OperKinds::LThan || $4 == OperKinds::LEThan ) { SemanticError( yylloc, MISSING_LOW ); $$ = nullptr; }
@@ -1447,10 +1447,10 @@
 		{
 			if ( $4 == OperKinds::GThan || $4 == OperKinds::GEThan ) { SemanticError( yylloc, MISSING_HIGH ); $$ = nullptr; }
-			else if ( $4 == OperKinds::LEThan ) { SemanticError( yylloc, "Equality with missing high value is meaningless. Use \"~\"." ); $$ = nullptr; }
+			else if ( $4 == OperKinds::LEThan ) { SemanticError( yylloc, "syntax error, equality with missing high value is meaningless. Use \"~\"." ); $$ = nullptr; }
 			else $$ = forCtrl( yylloc, $3, $1, $3->clone(), $4, nullptr, $7 );
 		}
 	| comma_expression ';' comma_expression updowneq comma_expression '~' '@' // CFA
 		{ $$ = forCtrl( yylloc, $3, $1, UPDOWN( $4, $3->clone(), $5 ), $4, UPDOWN( $4, $5->clone(), $3->clone() ), nullptr ); }
-	| comma_expression ';' '@' updowneq comma_expression '~' '@' // CFA, error
+	| comma_expression ';' '@' updowneq comma_expression '~' '@' // CFA, invalid syntax rules
 		{
 			if ( $4 == OperKinds::LThan || $4 == OperKinds::LEThan ) { SemanticError( yylloc, MISSING_LOW ); $$ = nullptr; }
@@ -1460,9 +1460,9 @@
 		{
 			if ( $4 == OperKinds::GThan || $4 == OperKinds::GEThan ) { SemanticError( yylloc, MISSING_HIGH ); $$ = nullptr; }
-			else if ( $4 == OperKinds::LEThan ) { SemanticError( yylloc, "Equality with missing high value is meaningless. Use \"~\"." ); $$ = nullptr; }
+			else if ( $4 == OperKinds::LEThan ) { SemanticError( yylloc, "syntax error, equality with missing high value is meaningless. Use \"~\"." ); $$ = nullptr; }
 			else $$ = forCtrl( yylloc, $3, $1, $3->clone(), $4, nullptr, nullptr );
 		}
 	| comma_expression ';' '@' updowneq '@' '~' '@' // CFA
-		{ SemanticError( yylloc, "Missing low/high value for up/down-to range so index is uninitialized." ); $$ = nullptr; }
+		{ SemanticError( yylloc, "syntax error, missing low/high value for up/down-to range so index is uninitialized." ); $$ = nullptr; }
 
 	| declaration comma_expression						// CFA
@@ -1481,5 +1481,5 @@
 		{
 			if ( $3 == OperKinds::GThan || $3 == OperKinds::GEThan ) { SemanticError( yylloc, MISSING_HIGH ); $$ = nullptr; }
-			else if ( $3 == OperKinds::LEThan ) { SemanticError( yylloc, "Equality with missing high value is meaningless. Use \"~\"." ); $$ = nullptr; }
+			else if ( $3 == OperKinds::LEThan ) { SemanticError( yylloc, "syntax error, equality with missing high value is meaningless. Use \"~\"." ); $$ = nullptr; }
 			else $$ = forCtrl( yylloc, $1, $2, $3, nullptr, NEW_ONE );
 		}
@@ -1495,5 +1495,5 @@
 		{
 			if ( $3 == OperKinds::GThan || $3 == OperKinds::GEThan ) { SemanticError( yylloc, MISSING_HIGH ); $$ = nullptr; }
-			else if ( $3 == OperKinds::LEThan ) { SemanticError( yylloc, "Equality with missing high value is meaningless. Use \"~\"." ); $$ = nullptr; }
+			else if ( $3 == OperKinds::LEThan ) { SemanticError( yylloc, "syntax error, equality with missing high value is meaningless. Use \"~\"." ); $$ = nullptr; }
 			else $$ = forCtrl( yylloc, $1, $2, $3, nullptr, $6 );
 		}
@@ -1508,9 +1508,9 @@
 		{
 			if ( $3 == OperKinds::GThan || $3 == OperKinds::GEThan ) { SemanticError( yylloc, MISSING_HIGH ); $$ = nullptr; }
-			else if ( $3 == OperKinds::LEThan ) { SemanticError( yylloc, "Equality with missing high value is meaningless. Use \"~\"." ); $$ = nullptr; }
+			else if ( $3 == OperKinds::LEThan ) { SemanticError( yylloc, "syntax error, equality with missing high value is meaningless. Use \"~\"." ); $$ = nullptr; }
 			else $$ = forCtrl( yylloc, $1, $2, $3, nullptr, nullptr );
 		}
-	| declaration '@' updowneq '@' '~' '@'				// CFA, error
-		{ SemanticError( yylloc, "Missing low/high value for up/down-to range so index is uninitialized." ); $$ = nullptr; }
+	| declaration '@' updowneq '@' '~' '@'				// CFA, invalid syntax rules
+		{ SemanticError( yylloc, "syntax error, missing low/high value for up/down-to range so index is uninitialized." ); $$ = nullptr; }
 
 	| comma_expression ';' TYPEDEFname					// CFA, array type
@@ -1521,5 +1521,7 @@
 	| comma_expression ';' downupdowneq TYPEDEFname		// CFA, array type
 		{
-			if ( $3 == OperKinds::LEThan || $3 == OperKinds::GEThan ) { SemanticError( yylloc, "All enumation ranges are equal (all values). Remove \"=~\"." ); $$ = nullptr; }
+			if ( $3 == OperKinds::LEThan || $3 == OperKinds::GEThan ) {
+				SemanticError( yylloc, "syntax error, all enumeration ranges are equal (all values). Remove \"=~\"." ); $$ = nullptr;
+			}
 			SemanticError( yylloc, "Type iterator is currently unimplemented." ); $$ = nullptr;
 		}
@@ -1616,5 +1618,5 @@
 	MUTEX '(' argument_expression_list_opt ')' statement
 		{
-			if ( ! $3 ) { SemanticError( yylloc, "mutex argument list cannot be empty." ); $$ = nullptr; }
+			if ( ! $3 ) { SemanticError( yylloc, "syntax error, mutex argument list cannot be empty." ); $$ = nullptr; }
 			$$ = new StatementNode( build_mutex( yylloc, $3, $5 ) );
 		}
@@ -1664,6 +1666,6 @@
 		{ $$ = build_waitfor_timeout( yylloc, $1, $3, $4, maybe_build_compound( yylloc, $5 ) ); }
 	// "else" must be conditional after timeout or timeout is never triggered (i.e., it is meaningless)
-	| wor_waitfor_clause wor when_clause_opt timeout statement wor ELSE statement // syntax error
-		{ SemanticError( yylloc, "else clause must be conditional after timeout or timeout never triggered." ); $$ = nullptr; }
+	| wor_waitfor_clause wor when_clause_opt timeout statement wor ELSE statement // invalid syntax rules
+		{ SemanticError( yylloc, "syntax error, else clause must be conditional after timeout or timeout never triggered." ); $$ = nullptr; }
 	| wor_waitfor_clause wor when_clause_opt timeout statement wor when_clause ELSE statement
 		{ $$ = build_waitfor_else( yylloc, build_waitfor_timeout( yylloc, $1, $3, $4, maybe_build_compound( yylloc, $5 ) ), $7, maybe_build_compound( yylloc, $9 ) ); }
@@ -1709,6 +1711,6 @@
 		{ $$ = new ast::WaitUntilStmt::ClauseNode( ast::WaitUntilStmt::ClauseNode::Op::LEFT_OR, $1, build_waituntil_timeout( yylloc, $3, $4, maybe_build_compound( yylloc, $5 ) ) ); }
 	// "else" must be conditional after timeout or timeout is never triggered (i.e., it is meaningless)
-	| wor_waituntil_clause wor when_clause_opt timeout statement wor ELSE statement // syntax error
-		{ SemanticError( yylloc, "else clause must be conditional after timeout or timeout never triggered." ); $$ = nullptr; }
+	| wor_waituntil_clause wor when_clause_opt timeout statement wor ELSE statement // invalid syntax rules
+		{ SemanticError( yylloc, "syntax error, else clause must be conditional after timeout or timeout never triggered." ); $$ = nullptr; }
 	| wor_waituntil_clause wor when_clause_opt timeout statement wor when_clause ELSE statement
 		{ $$ = new ast::WaitUntilStmt::ClauseNode( ast::WaitUntilStmt::ClauseNode::Op::LEFT_OR, $1,
@@ -2065,9 +2067,9 @@
 			assert( $1->type );
 			if ( $1->type->qualifiers.any() ) {			// CV qualifiers ?
-				SemanticError( yylloc, "Useless type qualifier(s) in empty declaration." ); $$ = nullptr;
+				SemanticError( yylloc, "syntax error, useless type qualifier(s) in empty declaration." ); $$ = nullptr;
 			}
 			// enums are never empty declarations because there must have at least one enumeration.
 			if ( $1->type->kind == TypeData::AggregateInst && $1->storageClasses.any() ) { // storage class ?
-				SemanticError( yylloc, "Useless storage qualifier(s) in empty aggregate declaration." ); $$ = nullptr;
+				SemanticError( yylloc, "syntax error, useless storage qualifier(s) in empty aggregate declaration." ); $$ = nullptr;
 			}
 		}
@@ -2100,9 +2102,9 @@
 	| type_declaration_specifier
 	| sue_declaration_specifier
-	| sue_declaration_specifier invalid_types
-		{
-			SemanticError( yylloc, ::toString( "Missing ';' after end of ",
+	| sue_declaration_specifier invalid_types			// invalid syntax rule
+		{
+			SemanticError( yylloc, ::toString( "syntax error, expecting ';' at end of ",
 				$1->type->enumeration.name ? "enum" : ast::AggregateDecl::aggrString( $1->type->aggregate.kind ),
-				" declaration" ) );
+				" declaration." ) );
 			$$ = nullptr;
 		}
@@ -2584,4 +2586,9 @@
 			// } // for
 		}
+	| type_specifier field_declaring_list_opt '}'		// invalid syntax rule
+		{
+			SemanticError( yylloc, ::toString( "syntax error, expecting ';' at end of previous declaration." ) );
+			$$ = nullptr;
+		}
 	| EXTENSION type_specifier field_declaring_list_opt ';'	// GCC
 		{ $$ = fieldDecl( $2, $3 ); distExt( $$ ); }
@@ -2682,7 +2689,7 @@
 	| ENUM '(' cfa_abstract_parameter_declaration ')' attribute_list_opt '{' enumerator_list comma_opt '}'
 		{
-			if ( $3->storageClasses.val != 0 || $3->type->qualifiers.any() )
-			{ SemanticError( yylloc, "storage-class and CV qualifiers are not meaningful for enumeration constants, which are const." ); }
-
+			if ( $3->storageClasses.val != 0 || $3->type->qualifiers.any() ) {
+				SemanticError( yylloc, "syntax error, storage-class and CV qualifiers are not meaningful for enumeration constants, which are const." );
+			}
 			$$ = DeclarationNode::newEnum( nullptr, $7, true, true, $3 )->addQualifiers( $5 );
 		}
@@ -2693,5 +2700,7 @@
 	| ENUM '(' cfa_abstract_parameter_declaration ')' attribute_list_opt identifier attribute_list_opt
 		{
-			if ( $3->storageClasses.any() || $3->type->qualifiers.val != 0 ) { SemanticError( yylloc, "storage-class and CV qualifiers are not meaningful for enumeration constants, which are const." ); }
+			if ( $3->storageClasses.any() || $3->type->qualifiers.val != 0 ) {
+				SemanticError( yylloc, "syntax error, storage-class and CV qualifiers are not meaningful for enumeration constants, which are const." );
+			}
 			typedefTable.makeTypedef( *$6 );
 		}
@@ -3166,13 +3175,13 @@
 	| IDENTIFIER IDENTIFIER
 		{ IdentifierBeforeIdentifier( *$1.str, *$2.str, " declaration" ); $$ = nullptr; }
-	| IDENTIFIER type_qualifier							// syntax error
+	| IDENTIFIER type_qualifier							// invalid syntax rules
 		{ IdentifierBeforeType( *$1.str, "type qualifier" ); $$ = nullptr; }
-	| IDENTIFIER storage_class							// syntax error
+	| IDENTIFIER storage_class							// invalid syntax rules
 		{ IdentifierBeforeType( *$1.str, "storage class" ); $$ = nullptr; }
-	| IDENTIFIER basic_type_name						// syntax error
+	| IDENTIFIER basic_type_name						// invalid syntax rules
 		{ IdentifierBeforeType( *$1.str, "type" ); $$ = nullptr; }
-	| IDENTIFIER TYPEDEFname							// syntax error
+	| IDENTIFIER TYPEDEFname							// invalid syntax rules
 		{ IdentifierBeforeType( *$1.str, "type" ); $$ = nullptr; }
-	| IDENTIFIER TYPEGENname							// syntax error
+	| IDENTIFIER TYPEGENname							// invalid syntax rules
 		{ IdentifierBeforeType( *$1.str, "type" ); $$ = nullptr; }
 	| external_function_definition
@@ -3209,5 +3218,7 @@
 	| type_qualifier_list
 		{
-			if ( $1->type->qualifiers.any() ) { SemanticError( yylloc, "CV qualifiers cannot be distributed; only storage-class and forall qualifiers." ); }
+			if ( $1->type->qualifiers.any() ) {
+				SemanticError( yylloc, "syntax error, CV qualifiers cannot be distributed; only storage-class and forall qualifiers." );
+			}
 			if ( $1->type->forall ) forall = true;		// remember generic type
 		}
@@ -3220,5 +3231,7 @@
 	| declaration_qualifier_list
 		{
-			if ( $1->type && $1->type->qualifiers.any() ) { SemanticError( yylloc, "CV qualifiers cannot be distributed; only storage-class and forall qualifiers." ); }
+			if ( $1->type && $1->type->qualifiers.any() ) {
+				SemanticError( yylloc, "syntax error, CV qualifiers cannot be distributed; only storage-class and forall qualifiers." );
+			}
 			if ( $1->type && $1->type->forall ) forall = true; // remember generic type
 		}
@@ -3231,5 +3244,7 @@
 	| declaration_qualifier_list type_qualifier_list
 		{
-			if ( ($1->type && $1->type->qualifiers.any()) || ($2->type && $2->type->qualifiers.any()) ) { SemanticError( yylloc, "CV qualifiers cannot be distributed; only storage-class and forall qualifiers." ); }
+			if ( ($1->type && $1->type->qualifiers.any()) || ($2->type && $2->type->qualifiers.any()) ) {
+				SemanticError( yylloc, "syntax error, CV qualifiers cannot be distributed; only storage-class and forall qualifiers." );
+			}
 			if ( ($1->type && $1->type->forall) || ($2->type && $2->type->forall) ) forall = true; // remember generic type
 		}
@@ -3262,5 +3277,5 @@
 			$$ = $3; forall = false;
 			if ( $5 ) {
-				SemanticError( yylloc, "Attributes cannot be associated with function body. Move attribute(s) before \"with\" clause." );
+				SemanticError( yylloc, "syntax error, attributes cannot be associated with function body. Move attribute(s) before \"with\" clause." );
 				$$ = nullptr;
 			} // if
Index: src/ResolvExpr/CommonType.cc
===================================================================
--- src/ResolvExpr/CommonType.cc	(revision 24d6572fc571b2a894d56a9335edd57899c448c0)
+++ src/ResolvExpr/CommonType.cc	(revision 62d62db217dc9f917346863faa9d03148d98844f)
@@ -1035,7 +1035,7 @@
 		void postvisit( const ast::TraitInstType * ) {}
 
-		void postvisit( const ast::TypeInstType * inst ) {}
-
-		void postvisit( const ast::TupleType * tuple) {
+		void postvisit( const ast::TypeInstType * ) {}
+
+		void postvisit( const ast::TupleType * tuple ) {
 			tryResolveWithTypedEnum( tuple );
 		}
Index: src/ResolvExpr/Resolver.cc
===================================================================
--- src/ResolvExpr/Resolver.cc	(revision 24d6572fc571b2a894d56a9335edd57899c448c0)
+++ src/ResolvExpr/Resolver.cc	(revision 62d62db217dc9f917346863faa9d03148d98844f)
@@ -1107,5 +1107,5 @@
 
 		/// Removes cast to type of argument (unlike StripCasts, also handles non-generated casts)
-		void removeExtraneousCast( ast::ptr<ast::Expr> & expr, const ast::SymbolTable & symtab ) {
+		void removeExtraneousCast( ast::ptr<ast::Expr> & expr ) {
 			if ( const ast::CastExpr * castExpr = expr.as< ast::CastExpr >() ) {
 				if ( typesCompatible( castExpr->arg->result, castExpr->result ) ) {
@@ -1197,5 +1197,5 @@
 		ast::ptr< ast::Expr > castExpr = new ast::CastExpr{ untyped, type };
 		ast::ptr< ast::Expr > newExpr = findSingleExpression( castExpr, context );
-		removeExtraneousCast( newExpr, context.symtab );
+		removeExtraneousCast( newExpr );
 		return newExpr;
 	}
@@ -1262,4 +1262,5 @@
 		static size_t traceId;
 		Resolver_new( const ast::TranslationGlobal & global ) :
+			ast::WithSymbolTable(ast::SymbolTable::ErrorDetection::ValidateOnAdd),
 			context{ symtab, global } {}
 		Resolver_new( const ResolveContext & context ) :
@@ -2041,5 +2042,5 @@
 		const ast::Type * initContext = currentObject.getCurrentType();
 
-		removeExtraneousCast( newExpr, symtab );
+		removeExtraneousCast( newExpr );
 
 		// check if actual object's type is char[]
Index: src/Validate/HoistStruct.cpp
===================================================================
--- src/Validate/HoistStruct.cpp	(revision 24d6572fc571b2a894d56a9335edd57899c448c0)
+++ src/Validate/HoistStruct.cpp	(revision 62d62db217dc9f917346863faa9d03148d98844f)
@@ -18,6 +18,8 @@
 #include <sstream>
 
+#include "AST/DeclReplacer.hpp"
 #include "AST/Pass.hpp"
 #include "AST/TranslationUnit.hpp"
+#include "AST/Vector.hpp"
 
 namespace Validate {
@@ -51,4 +53,6 @@
 	template<typename AggrDecl>
 	AggrDecl const * postAggregate( AggrDecl const * );
+	template<typename InstType>
+	InstType const * preCollectionInstType( InstType const * type );
 
 	ast::AggregateDecl const * parent = nullptr;
@@ -66,4 +70,22 @@
 	qualifiedName( decl, ss );
 	return ss.str();
+}
+
+void extendParams( ast::vector<ast::TypeDecl> & dstParams,
+		ast::vector<ast::TypeDecl> const & srcParams ) {
+	if ( srcParams.empty() ) return;
+
+	ast::DeclReplacer::TypeMap newToOld;
+	ast::vector<ast::TypeDecl> params;
+	for ( ast::ptr<ast::TypeDecl> const & srcParam : srcParams ) {
+		ast::TypeDecl * dstParam = ast::deepCopy( srcParam.get() );
+		dstParam->init = nullptr;
+		newToOld.emplace( srcParam, dstParam );
+		for ( auto assertion : dstParam->assertions ) {
+			assertion = ast::DeclReplacer::replace( assertion, newToOld );
+		}
+		params.emplace_back( dstParam );
+	}
+	spliceBegin( dstParams, params );
 }
 
@@ -74,9 +96,9 @@
 		mut->parent = parent;
 		mut->name = qualifiedName( mut );
-		return mut;
-	} else {
-		GuardValue( parent ) = decl;
-		return decl;
-	}
+		extendParams( mut->params, parent->params );
+		decl = mut;
+	}
+	GuardValue( parent ) = decl;
+	return decl;
 }
 
@@ -112,4 +134,36 @@
 }
 
+ast::AggregateDecl const * commonParent(
+		ast::AggregateDecl const * lhs, ast::AggregateDecl const * rhs ) {
+	for ( auto outer = lhs ; outer ; outer = outer->parent ) {
+		for ( auto inner = rhs ; inner ; inner = inner->parent ) {
+			if ( outer == inner ) {
+				return outer;
+			}
+		}
+	}
+	return nullptr;
+}
+
+template<typename InstType>
+InstType const * HoistStructCore::preCollectionInstType( InstType const * type ) {
+    if ( !type->base->parent ) return type;
+    if ( type->base->params.empty() ) return type;
+
+    InstType * mut = ast::mutate( type );
+    ast::AggregateDecl const * parent =
+        commonParent( this->parent, mut->base->parent );
+    assert( parent );
+
+    std::vector<ast::ptr<ast::Expr>> args;
+    for ( const ast::ptr<ast::TypeDecl> & param : parent->params ) {
+        args.emplace_back( new ast::TypeExpr( param->location,
+            new ast::TypeInstType( param )
+        ) );
+    }
+    spliceBegin( mut->params, args );
+    return mut;
+}
+
 template<typename InstType>
 InstType const * preInstType( InstType const * type ) {
@@ -121,9 +175,9 @@
 
 ast::StructInstType const * HoistStructCore::previsit( ast::StructInstType const * type ) {
-	return preInstType( type );
+	return preInstType( preCollectionInstType( type ) );
 }
 
 ast::UnionInstType const * HoistStructCore::previsit( ast::UnionInstType const * type ) {
-	return preInstType( type );
+	return preInstType( preCollectionInstType( type ) );
 }
 
