Index: src/AST/Convert.cpp
===================================================================
--- src/AST/Convert.cpp	(revision ba662b9ecdfbb986713208b7e06dcddbc83950e4)
+++ src/AST/Convert.cpp	(revision 07d867b95a42cbafd020825e1507f8c956e32d21)
@@ -588,17 +588,18 @@
 		assert( tgtResnSlots.empty() );
 
-		if ( srcInferred.mode == ast::Expr::InferUnion::Params ) {
+		if ( srcInferred.data.inferParams ) {
 			const ast::InferredParams &srcParams = srcInferred.inferParams();
 			for (auto & srcParam : srcParams) {
 				auto res = tgtInferParams.emplace(srcParam.first, ParamEntry(
 					srcParam.second.decl,
-					get<Declaration>().accept1(srcParam.second.declptr)->clone(),
-					get<Type>().accept1(srcParam.second.actualType),
-					get<Type>().accept1(srcParam.second.formalType),
-					get<Expression>().accept1(srcParam.second.expr)
+					get<Declaration>().accept1(srcParam.second.declptr),
+					get<Type>().accept1(srcParam.second.actualType)->clone(),
+					get<Type>().accept1(srcParam.second.formalType)->clone(),
+					get<Expression>().accept1(srcParam.second.expr)->clone()
 				));
 				assert(res.second);
 			}
-		} else if ( srcInferred.mode == ast::Expr::InferUnion::Slots  ) {
+		}
+		if ( srcInferred.data.resnSlots ) {
 			const ast::ResnSlots &srcSlots = srcInferred.resnSlots();
 			for (auto srcSlot : srcSlots) {
@@ -2003,5 +2004,5 @@
 
 		assert( oldInferParams.empty() || oldResnSlots.empty() );
-		assert( newInferred.mode == ast::Expr::InferUnion::Empty );
+		// assert( newInferred.mode == ast::Expr::InferUnion::Empty );
 
 		if ( !oldInferParams.empty() ) {
Index: src/AST/Expr.hpp
===================================================================
--- src/AST/Expr.hpp	(revision ba662b9ecdfbb986713208b7e06dcddbc83950e4)
+++ src/AST/Expr.hpp	(revision 07d867b95a42cbafd020825e1507f8c956e32d21)
@@ -45,5 +45,5 @@
 struct ParamEntry {
 	UniqueId decl;
-	ptr<Decl> declptr;
+	readonly<Decl> declptr;
 	ptr<Type> actualType;
 	ptr<Type> formalType;
@@ -65,22 +65,33 @@
 class Expr : public ParseNode {
 public:
-	/// Saves space (~16 bytes) by combining ResnSlots and InferredParams
+	/*
+	 * NOTE: the union approach is incorrect until the case of
+	 * partial resolution in InferMatcher is eliminated.
+	 * it is reverted to allow unresolved and resolved parameters
+	 * to coexist in an expression node.
+	 */
 	struct InferUnion {
+		// mode is now unused
 		enum { Empty, Slots, Params } mode;
-		union data_t {
-			char def;
-			ResnSlots resnSlots;
-			InferredParams inferParams;
-
-			data_t() : def('\0') {}
-			~data_t() {}
+		struct data_t {
+			// char def;
+			ResnSlots * resnSlots;
+			InferredParams * inferParams;
+
+			data_t(): resnSlots(nullptr), inferParams(nullptr) {}
+			data_t(const data_t &other) = delete;
+			~data_t() {
+				delete resnSlots;
+				delete inferParams;
+			}
 		} data;
 
 		/// initializes from other InferUnion
 		void init_from( const InferUnion& o ) {
-			switch ( o.mode ) {
-			case Empty:  return;
-			case Slots:  new(&data.resnSlots) ResnSlots{ o.data.resnSlots }; return;
-			case Params: new(&data.inferParams) InferredParams{ o.data.inferParams }; return;
+			if (o.data.resnSlots) {
+				data.resnSlots = new ResnSlots(*o.data.resnSlots);
+			}
+			if (o.data.inferParams) {
+				data.inferParams = new InferredParams(*o.data.inferParams);
 			}
 		}
@@ -88,19 +99,8 @@
 		/// initializes from other InferUnion (move semantics)
 		void init_from( InferUnion&& o ) {
-			switch ( o.mode ) {
-			case Empty:  return;
-			case Slots:  new(&data.resnSlots) ResnSlots{ std::move(o.data.resnSlots) }; return;
-			case Params:
-				new(&data.inferParams) InferredParams{ std::move(o.data.inferParams) }; return;
-			}
-		}
-
-		/// clears variant fields
-		void reset() {
-			switch( mode ) {
-			case Empty:  return;
-			case Slots:  data.resnSlots.~ResnSlots(); return;
-			case Params: data.inferParams.~InferredParams(); return;
-			}
+			data.resnSlots = o.data.resnSlots;
+			data.inferParams = o.data.inferParams;
+			o.data.resnSlots = nullptr;
+			o.data.inferParams = nullptr;
 		}
 
@@ -110,18 +110,17 @@
 		InferUnion& operator= ( const InferUnion& ) = delete;
 		InferUnion& operator= ( InferUnion&& ) = delete;
-		~InferUnion() { reset(); }
+
+		bool hasSlots() const { return data.resnSlots; }
 
 		ResnSlots& resnSlots() {
-			switch (mode) {
-			case Empty: new(&data.resnSlots) ResnSlots{}; mode = Slots; [[fallthrough]];
-			case Slots: return data.resnSlots;
-			case Params: assertf(false, "Cannot return to resnSlots from Params"); abort();
+			if (!data.resnSlots) {
+				data.resnSlots = new ResnSlots();
 			}
-			assertf(false, "unreachable");
+			return *data.resnSlots;
 		}
 
 		const ResnSlots& resnSlots() const {
-			if (mode == Slots) {
-				return data.resnSlots;
+			if (data.resnSlots) {
+				return *data.resnSlots;
 			}
 			assertf(false, "Mode was not already resnSlots");
@@ -130,15 +129,13 @@
 
 		InferredParams& inferParams() {
-			switch (mode) {
-			case Slots: data.resnSlots.~ResnSlots(); [[fallthrough]];
-			case Empty: new(&data.inferParams) InferredParams{}; mode = Params; [[fallthrough]];
-			case Params: return data.inferParams;
+			if (!data.inferParams) {
+				data.inferParams = new InferredParams();
 			}
-			assertf(false, "unreachable");
+			return *data.inferParams;
 		}
 
 		const InferredParams& inferParams() const {
-			if (mode == Params) {
-				return data.inferParams;
+			if (data.inferParams) {
+				return *data.inferParams;
 			}
 			assertf(false, "Mode was not already Params");
@@ -146,17 +143,9 @@
 		}
 
-		void set_inferParams( InferredParams && ps ) {
-			switch(mode) {
-			case Slots:
-				data.resnSlots.~ResnSlots();
-				[[fallthrough]];
-			case Empty:
-				new(&data.inferParams) InferredParams{ std::move( ps ) };
-				mode = Params;
-				break;
-			case Params:
-				data.inferParams = std::move( ps );
-				break;
-			}
+		void set_inferParams( InferredParams * ps ) {
+			delete data.resnSlots;
+			data.resnSlots = nullptr;
+			delete data.inferParams;
+			data.inferParams = ps;
 		}
 
@@ -164,16 +153,28 @@
 		/// and the other is in `Params`.
 		void splice( InferUnion && o ) {
-			if ( o.mode == Empty ) return;
-			if ( mode == Empty ) { init_from( o ); return; }
-			assert( mode == o.mode && "attempt to splice incompatible InferUnion" );
-
-			if ( mode == Slots ){
-				data.resnSlots.insert(
-					data.resnSlots.end(), o.data.resnSlots.begin(), o.data.resnSlots.end() );
-			} else if ( mode == Params ) {
-				for ( const auto & p : o.data.inferParams ) {
-					data.inferParams[p.first] = std::move(p.second);
+			if (o.data.resnSlots) {
+				if (data.resnSlots) {
+					data.resnSlots->insert(
+						data.resnSlots->end(), o.data.resnSlots->begin(), o.data.resnSlots->end() );
+					delete o.data.resnSlots;
 				}
-			} else assertf(false, "invalid mode");
+				else {
+					data.resnSlots = o.data.resnSlots;
+				}
+				o.data.resnSlots = nullptr;
+			}
+
+			if (o.data.inferParams) {
+				if (data.inferParams) {
+					for ( const auto & p : *o.data.inferParams ) {
+						(*data.inferParams)[p.first] = std::move(p.second);
+					}
+					delete o.data.inferParams;
+				}
+				else {
+					data.inferParams = o.data.inferParams;
+				}
+				o.data.inferParams = nullptr;
+			}
 		}
 	};
Index: src/ResolvExpr/SatisfyAssertions.cpp
===================================================================
--- src/ResolvExpr/SatisfyAssertions.cpp	(revision ba662b9ecdfbb986713208b7e06dcddbc83950e4)
+++ src/ResolvExpr/SatisfyAssertions.cpp	(revision 07d867b95a42cbafd020825e1507f8c956e32d21)
@@ -188,5 +188,5 @@
 
 				matches.emplace_back( 
-					cdata, adjType, std::move( newEnv ), std::move( newNeed ), std::move( have ), 
+					cdata, adjType, std::move( newEnv ), std::move( have ), std::move( newNeed ),
 					std::move( newOpen ), crntResnSlot );
 			}
@@ -231,8 +231,9 @@
 		const ast::Expr * postvisit( const ast::Expr * expr ) {
 			// Skip if no slots to find
-			if ( expr->inferred.mode != ast::Expr::InferUnion::Slots ) return expr;
-
+			if ( !expr->inferred.hasSlots() ) return expr;
+			// if ( expr->inferred.mode != ast::Expr::InferUnion::Slots ) return expr;
+			std::vector<UniqueId> missingSlots;
 			// find inferred parameters for resolution slots
-			ast::InferredParams newInferred;
+			ast::InferredParams * newInferred = new ast::InferredParams();
 			for ( UniqueId slot : expr->inferred.resnSlots() ) {
 				// fail if no matching assertions found
@@ -240,4 +241,5 @@
 				if ( it == inferred.end() ) {
 					std::cerr << "missing assertion " << slot << std::endl;
+					missingSlots.push_back(slot);
 					continue;
 				}
@@ -247,5 +249,5 @@
 					// recurse on inferParams of resolved expressions
 					entry.second.expr = postvisit( entry.second.expr );
-					auto res = newInferred.emplace( entry );
+					auto res = newInferred->emplace( entry );
 					assert( res.second && "all assertions newly placed" );
 				}
@@ -253,5 +255,6 @@
 
 			ast::Expr * ret = mutate( expr );
-			ret->inferred.set_inferParams( std::move( newInferred ) );
+			ret->inferred.set_inferParams( newInferred );
+			if (!missingSlots.empty()) ret->inferred.resnSlots() = missingSlots;
 			return ret;
 		}
Index: src/SynTree/ApplicationExpr.cc
===================================================================
--- src/SynTree/ApplicationExpr.cc	(revision ba662b9ecdfbb986713208b7e06dcddbc83950e4)
+++ src/SynTree/ApplicationExpr.cc	(revision 07d867b95a42cbafd020825e1507f8c956e32d21)
@@ -34,9 +34,9 @@
 
 ParamEntry::ParamEntry( const ParamEntry &other ) :
-		decl( other.decl ), declptr( maybeClone( other.declptr ) ), actualType( maybeClone( other.actualType ) ), formalType( maybeClone( other.formalType ) ), expr( maybeClone( other.expr ) ) {
+		decl( other.decl ), declptr( other.declptr ), actualType( maybeClone( other.actualType ) ), formalType( maybeClone( other.formalType ) ), expr( maybeClone( other.expr ) ) {
 }
 
 ParamEntry::~ParamEntry() {
-	delete declptr;
+	// delete declptr;
 	delete actualType;
 	delete formalType;
