Index: src/AST/Convert.cpp
===================================================================
--- src/AST/Convert.cpp	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/AST/Convert.cpp	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -2331,5 +2331,6 @@
 				old->location,
 				GET_ACCEPT_1(arg, Expr),
-				old->isGenerated ? ast::GeneratedCast : ast::ExplicitCast
+				old->isGenerated ? ast::GeneratedCast : ast::ExplicitCast,
+				(ast::CastExpr::CastKind) old->kind
 			)
 		);
Index: src/AST/Expr.cpp
===================================================================
--- src/AST/Expr.cpp	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/AST/Expr.cpp	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -186,6 +186,6 @@
 // --- CastExpr
 
-CastExpr::CastExpr( const CodeLocation & loc, const Expr * a, GeneratedFlag g )
-: Expr( loc, new VoidType{} ), arg( a ), isGenerated( g ) {}
+CastExpr::CastExpr( const CodeLocation & loc, const Expr * a, GeneratedFlag g, CastKind kind )
+: Expr( loc, new VoidType{} ), arg( a ), isGenerated( g ), kind( kind ) {}
 
 bool CastExpr::get_lvalue() const {
Index: src/AST/Expr.hpp
===================================================================
--- src/AST/Expr.hpp	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/AST/Expr.hpp	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -55,4 +55,6 @@
 		const Expr * e )
 	: decl( id ), declptr( declptr ), actualType( actual ), formalType( formal ), expr( e ) {}
+
+	operator bool() {return declptr;}
 };
 
@@ -334,8 +336,16 @@
 	GeneratedFlag isGenerated;
 
+	enum CastKind {
+		Default, // C
+		Coerce, // reinterpret cast
+		Return  // overload selection
+	};
+
+	CastKind kind = Default;
+
 	CastExpr( const CodeLocation & loc, const Expr * a, const Type * to,
-		GeneratedFlag g = GeneratedCast ) : Expr( loc, to ), arg( a ), isGenerated( g ) {}
+		GeneratedFlag g = GeneratedCast, CastKind kind = Default ) : Expr( loc, to ), arg( a ), isGenerated( g ), kind( kind ) {}
 	/// Cast-to-void
-	CastExpr( const CodeLocation & loc, const Expr * a, GeneratedFlag g = GeneratedCast );
+	CastExpr( const CodeLocation & loc, const Expr * a, GeneratedFlag g = GeneratedCast, CastKind kind = Default );
 
 	/// Wrap a cast expression around an existing expression (always generated)
Index: src/AST/Type.hpp
===================================================================
--- src/AST/Type.hpp	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/AST/Type.hpp	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -454,5 +454,7 @@
 	bool operator==(const TypeEnvKey & other) const;
 	bool operator<(const TypeEnvKey & other) const;
-};
+	operator bool() {return base;}
+};
+
 
 /// tuple type e.g. `[int, char]`
Index: src/AST/TypeEnvironment.cpp
===================================================================
--- src/AST/TypeEnvironment.cpp	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/AST/TypeEnvironment.cpp	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -135,5 +135,5 @@
 		}
 	}
-	sub.normalize();
+	// sub.normalize();
 }
 
Index: src/GenPoly/SpecializeNew.cpp
===================================================================
--- src/GenPoly/SpecializeNew.cpp	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/GenPoly/SpecializeNew.cpp	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -112,7 +112,8 @@
 	using namespace ResolvExpr;
 	ast::OpenVarSet openVars, closedVars;
-	ast::AssertionSet need, have;
-	findOpenVars( formalType, openVars, closedVars, need, have, FirstClosed );
-	findOpenVars( actualType, openVars, closedVars, need, have, FirstOpen );
+	ast::AssertionSet need, have; // unused
+	ast::TypeEnvironment env; // unused
+	// findOpenVars( formalType, openVars, closedVars, need, have, FirstClosed );
+	findOpenVars( actualType, openVars, closedVars, need, have, env, FirstOpen );
 	for ( const ast::OpenVarSet::value_type & openVar : openVars ) {
 		const ast::Type * boundType = subs->lookup( openVar.first );
@@ -124,4 +125,7 @@
 			if ( closedVars.find( *inst ) == closedVars.end() ) {
 				return true;
+			}
+			else {
+				assertf(false, "closed: %s", inst->name.c_str());
 			}
 		// Otherwise, the variable is bound to a concrete type.
Index: src/Parser/ExpressionNode.cc
===================================================================
--- src/Parser/ExpressionNode.cc	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/Parser/ExpressionNode.cc	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -544,11 +544,11 @@
 }; // OperName
 
-Expression * build_cast( DeclarationNode * decl_node, ExpressionNode * expr_node ) {
+Expression * build_cast( DeclarationNode * decl_node, ExpressionNode * expr_node, CastExpr::CastKind kind ) {
 	Type * targetType = maybeMoveBuildType( decl_node );
 	if ( dynamic_cast< VoidType * >( targetType ) ) {
 		delete targetType;
-		return new CastExpr( maybeMoveBuild< Expression >(expr_node), false );
+		return new CastExpr( maybeMoveBuild< Expression >(expr_node), false, kind );
 	} else {
-		return new CastExpr( maybeMoveBuild< Expression >(expr_node), targetType, false );
+		return new CastExpr( maybeMoveBuild< Expression >(expr_node), targetType, false, kind );
 	} // if
 } // build_cast
Index: src/Parser/ParseNode.h
===================================================================
--- src/Parser/ParseNode.h	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/Parser/ParseNode.h	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -189,5 +189,5 @@
 DimensionExpr * build_dimensionref( const std::string * name );
 
-Expression * build_cast( DeclarationNode * decl_node, ExpressionNode * expr_node );
+Expression * build_cast( DeclarationNode * decl_node, ExpressionNode * expr_node, CastExpr::CastKind kind = CastExpr::Default );
 Expression * build_keyword_cast( AggregateDecl::Aggregate target, ExpressionNode * expr_node );
 Expression * build_virtual_cast( DeclarationNode * decl_node, ExpressionNode * expr_node );
Index: src/Parser/parser.yy
===================================================================
--- src/Parser/parser.yy	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/Parser/parser.yy	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -905,5 +905,5 @@
 		{ $$ = new ExpressionNode( new VirtualCastExpr( maybeMoveBuild<Expression>( $5 ), maybeMoveBuildType( $3 ) ) ); }
 	| '(' RETURN type_no_function ')' cast_expression	// CFA
-		{ SemanticError( yylloc, "Return cast is currently unimplemented." ); $$ = nullptr; }
+		{ $$ = new ExpressionNode( build_cast( $3, $5, CastExpr::Return ) ); }
 	| '(' COERCE type_no_function ')' cast_expression	// CFA
 		{ SemanticError( yylloc, "Coerce cast is currently unimplemented." ); $$ = nullptr; }
Index: src/ResolvExpr/Candidate.hpp
===================================================================
--- src/ResolvExpr/Candidate.hpp	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/ResolvExpr/Candidate.hpp	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -91,4 +91,5 @@
 
 /// Holdover behaviour from old `findMinCost` -- xxx -- can maybe be eliminated?
+/*
 static inline void promoteCvtCost( CandidateList & candidates ) {
 	for ( CandidateRef & r : candidates ) {
@@ -96,4 +97,5 @@
 	}
 }
+*/
 
 void print( std::ostream & os, const Candidate & cand, Indenter indent = {} );
Index: src/ResolvExpr/CandidateFinder.cpp
===================================================================
--- src/ResolvExpr/CandidateFinder.cpp	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/ResolvExpr/CandidateFinder.cpp	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -30,4 +30,5 @@
 #include "Resolver.h"
 #include "ResolveTypeof.h"
+#include "WidenMode.h"
 #include "SatisfyAssertions.hpp"
 #include "typeops.h"              // for adjustExprType, conversionCost, polyCost, specCost
@@ -700,9 +701,19 @@
 				// attempt to narrow based on expected target type
 				const ast::Type * returnType = funcType->returns.front();
-				if ( ! unify(
-					returnType, targetType, funcEnv, funcNeed, funcHave, funcOpen, symtab )
-				) {
-					// unification failed, do not pursue this candidate
-					return;
+				if ( selfFinder.strictMode ) {
+					if ( ! unifyExact(
+						returnType, targetType, funcEnv, funcNeed, funcHave, funcOpen, noWiden(), symtab ) // xxx - is no widening correct?
+					) {
+						// unification failed, do not pursue this candidate
+						return;
+					}
+				}
+				else {
+					if ( ! unify(
+						returnType, targetType, funcEnv, funcNeed, funcHave, funcOpen, symtab )
+					) {
+						// unification failed, do not pursue this candidate
+						return;
+					}
 				}
 			}
@@ -825,7 +836,7 @@
 
 			if ( auto structInst = aggrExpr->result.as< ast::StructInstType >() ) {
-				addAggMembers( structInst, aggrExpr, *cand, Cost::safe, "" );
+				addAggMembers( structInst, aggrExpr, *cand, Cost::unsafe, "" );
 			} else if ( auto unionInst = aggrExpr->result.as< ast::UnionInstType >() ) {
-				addAggMembers( unionInst, aggrExpr, *cand, Cost::safe, "" );
+				addAggMembers( unionInst, aggrExpr, *cand, Cost::unsafe, "" );
 			}
 		}
@@ -958,4 +969,5 @@
 					if ( auto pointer = dynamic_cast< const ast::PointerType * >( funcResult ) ) {
 						if ( auto function = pointer->base.as< ast::FunctionType >() ) {
+							// if (!selfFinder.allowVoid && function->returns.empty()) continue;
 							CandidateRef newFunc{ new Candidate{ *func } };
 							newFunc->expr =
@@ -1008,4 +1020,9 @@
 			if ( found.empty() && ! errors.isEmpty() ) { throw errors; }
 
+			// only keep the best matching intrinsic result to match C semantics (no unexpected narrowing/widening)
+			// TODO: keep one for each set of argument candidates?
+			Cost intrinsicCost = Cost::infinity;
+			CandidateList intrinsicResult;
+
 			// Compute conversion costs
 			for ( CandidateRef & withFunc : found ) {
@@ -1030,22 +1047,37 @@
 				if ( cvtCost != Cost::infinity ) {
 					withFunc->cvtCost = cvtCost;
-					candidates.emplace_back( std::move( withFunc ) );
-				}
-			}
+					withFunc->cost += cvtCost;	
+					auto func = withFunc->expr.strict_as<ast::ApplicationExpr>()->func.as<ast::VariableExpr>();
+					if (func && func->var->linkage == ast::Linkage::Intrinsic) {
+						if (withFunc->cost < intrinsicCost) {
+							intrinsicResult.clear();
+							intrinsicCost = withFunc->cost;
+						}
+						if (withFunc->cost == intrinsicCost) {
+							intrinsicResult.emplace_back(std::move(withFunc));
+						}
+					}	
+					else {
+						candidates.emplace_back( std::move( withFunc ) );
+					}
+				}
+			}
+			spliceBegin( candidates, intrinsicResult );
 			found = std::move( candidates );
 
 			// use a new list so that candidates are not examined by addAnonConversions twice
-			CandidateList winners = findMinCost( found );
-			promoteCvtCost( winners );
+			// CandidateList winners = findMinCost( found );
+			// promoteCvtCost( winners );
 
 			// function may return a struct/union value, in which case we need to add candidates
 			// for implicit conversions to each of the anonymous members, which must happen after
 			// `findMinCost`, since anon conversions are never the cheapest
-			for ( const CandidateRef & c : winners ) {
+			for ( const CandidateRef & c : found ) {
 				addAnonConversions( c );
 			}
-			spliceBegin( candidates, winners );
-
-			if ( candidates.empty() && targetType && ! targetType->isVoid() ) {
+			// would this be too slow when we don't check cost anymore?
+			spliceBegin( candidates, found );
+
+			if ( candidates.empty() && targetType && ! targetType->isVoid() && !selfFinder.strictMode ) {
 				// If resolution is unsuccessful with a target type, try again without, since it
 				// will sometimes succeed when it wouldn't with a target type binding.
@@ -1093,4 +1125,14 @@
 
 			CandidateFinder finder( context, tenv, toType );
+			if (toType->isVoid()) {
+				finder.allowVoid = true;
+			}
+			if ( castExpr->kind == ast::CastExpr::Return ) {
+				finder.strictMode = true;
+				finder.find( castExpr->arg, ResolvMode::withAdjustment() );
+
+				// return casts are eliminated (merely selecting an overload, no actual operation)
+				candidates = std::move(finder.candidates);
+			}
 			finder.find( castExpr->arg, ResolvMode::withAdjustment() );
 
@@ -1098,4 +1140,6 @@
 
 			CandidateList matches;
+			Cost minExprCost = Cost::infinity;
+			Cost minCastCost = Cost::infinity;
 			for ( CandidateRef & cand : finder.candidates ) {
 				ast::AssertionSet need( cand->need.begin(), cand->need.end() ), have;
@@ -1129,16 +1173,31 @@
 					// count one safe conversion for each value that is thrown away
 					thisCost.incSafe( discardedValues );
-					CandidateRef newCand = std::make_shared<Candidate>(
-						restructureCast( cand->expr, toType, castExpr->isGenerated ),
-						copy( cand->env ), std::move( open ), std::move( need ), cand->cost,
-						cand->cost + thisCost );
-					inferParameters( newCand, matches );
-				}
-			}
-
-			// select first on argument cost, then conversion cost
-			CandidateList minArgCost = findMinCost( matches );
-			promoteCvtCost( minArgCost );
-			candidates = findMinCost( minArgCost );
+					// select first on argument cost, then conversion cost
+					if (cand->cost < minExprCost || cand->cost == minExprCost && thisCost < minCastCost) {
+						minExprCost = cand->cost;
+						minCastCost = thisCost;
+						matches.clear();
+
+
+					}
+					// ambiguous case, still output candidates to print in error message
+					if (cand->cost == minExprCost && thisCost == minCastCost) {
+						CandidateRef newCand = std::make_shared<Candidate>(
+							restructureCast( cand->expr, toType, castExpr->isGenerated ),
+							copy( cand->env ), std::move( open ), std::move( need ), cand->cost + thisCost);
+						// currently assertions are always resolved immediately so this should have no effect. 
+						// if this somehow changes in the future (e.g. delayed by indeterminate return type)
+						// we may need to revisit the logic.
+						inferParameters( newCand, matches );
+					}
+					// else skip, better alternatives found
+
+				}
+			}
+			candidates = std::move(matches);
+
+			//CandidateList minArgCost = findMinCost( matches );
+			//promoteCvtCost( minArgCost );
+			//candidates = findMinCost( minArgCost );
 		}
 
@@ -1261,6 +1320,5 @@
 
 				CandidateRef newCand = std::make_shared<Candidate>(
-					newExpr, copy( tenv ), ast::OpenVarSet{}, ast::AssertionSet{}, Cost::zero,
-					cost );
+					newExpr, copy( tenv ), ast::OpenVarSet{}, ast::AssertionSet{}, cost );
 
 				if (newCand->expr->env) {
@@ -1407,4 +1465,5 @@
 			// candidates for true result
 			CandidateFinder finder2( context, tenv );
+			finder2.allowVoid = true;
 			finder2.find( conditionalExpr->arg2, ResolvMode::withAdjustment() );
 			if ( finder2.candidates.empty() ) return;
@@ -1412,4 +1471,5 @@
 			// candidates for false result
 			CandidateFinder finder3( context, tenv );
+			finder3.allowVoid = true;
 			finder3.find( conditionalExpr->arg3, ResolvMode::withAdjustment() );
 			if ( finder3.candidates.empty() ) return;
@@ -1478,4 +1538,5 @@
 		void postvisit( const ast::ConstructorExpr * ctorExpr ) {
 			CandidateFinder finder( context, tenv );
+			finder.allowVoid = true;
 			finder.find( ctorExpr->callExpr, ResolvMode::withoutPrune() );
 			for ( CandidateRef & r : finder.candidates ) {
@@ -1594,4 +1655,7 @@
 				CandidateFinder finder( context, tenv, toType );
 				finder.find( initExpr->expr, ResolvMode::withAdjustment() );
+
+				Cost minExprCost = Cost::infinity;
+				Cost minCastCost = Cost::infinity;
 				for ( CandidateRef & cand : finder.candidates ) {
 					if(reason.code == NotFound) reason.code = NoMatch;
@@ -1631,11 +1695,24 @@
 						// count one safe conversion for each value that is thrown away
 						thisCost.incSafe( discardedValues );
-						CandidateRef newCand = std::make_shared<Candidate>(
+						if (cand->cost < minExprCost || cand->cost == minExprCost && thisCost < minCastCost) {
+							minExprCost = cand->cost;
+							minCastCost = thisCost;
+							matches.clear();
+						}
+						// ambiguous case, still output candidates to print in error message
+						if (cand->cost == minExprCost && thisCost == minCastCost) {
+							CandidateRef newCand = std::make_shared<Candidate>(
 							new ast::InitExpr{
 								initExpr->location, restructureCast( cand->expr, toType ),
 								initAlt.designation },
-							std::move(env), std::move( open ), std::move( need ), cand->cost, thisCost );
-						inferParameters( newCand, matches );
-					}
+							std::move(env), std::move( open ), std::move( need ), cand->cost + thisCost );
+							// currently assertions are always resolved immediately so this should have no effect. 
+							// if this somehow changes in the future (e.g. delayed by indeterminate return type)
+							// we may need to revisit the logic.
+							inferParameters( newCand, matches );
+						}
+
+					}
+					
 				}
 
@@ -1643,7 +1720,8 @@
 
 			// select first on argument cost, then conversion cost
-			CandidateList minArgCost = findMinCost( matches );
-			promoteCvtCost( minArgCost );
-			candidates = findMinCost( minArgCost );
+			// CandidateList minArgCost = findMinCost( matches );
+			// promoteCvtCost( minArgCost );
+			// candidates = findMinCost( minArgCost );
+			candidates = std::move(matches);
 		}
 
@@ -1724,5 +1802,10 @@
 			auto found = selected.find( mangleName );
 			if ( found != selected.end() ) {
-				if ( newCand->cost < found->second.candidate->cost ) {
+				// tiebreaking by picking the lower cost on CURRENT expression
+				// NOTE: this behavior is different from C semantics.
+				// Specific remediations are performed for C operators at postvisit(UntypedExpr).
+				// Further investigations may take place.
+				if ( newCand->cost < found->second.candidate->cost
+					|| (newCand->cost == found->second.candidate->cost && newCand->cvtCost < found->second.candidate->cvtCost) ) {
 					PRINT(
 						std::cerr << "cost " << newCand->cost << " beats "
@@ -1731,5 +1814,5 @@
 
 					found->second = PruneStruct{ newCand };
-				} else if ( newCand->cost == found->second.candidate->cost ) {
+				} else if ( newCand->cost == found->second.candidate->cost && newCand->cvtCost == found->second.candidate->cvtCost) {
 					// if one of the candidates contains a deleted identifier, can pick the other,
 					// since deleted expressions should not be ambiguous if there is another option
@@ -1822,5 +1905,7 @@
 	*/
 
-	if ( mode.prune ) {
+	// if ( mode.prune ) {
+	// optimization: don't prune for NameExpr since it never has cost
+	if ( mode.prune && !dynamic_cast<const ast::NameExpr *>(expr)) {
 		// trim candidates to single best one
 		PRINT(
Index: src/ResolvExpr/CandidateFinder.hpp
===================================================================
--- src/ResolvExpr/CandidateFinder.hpp	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/ResolvExpr/CandidateFinder.hpp	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -33,4 +33,6 @@
 	const ast::TypeEnvironment & env;  ///< Substitutions performed in this resolution
 	ast::ptr< ast::Type > targetType;  ///< Target type for resolution
+	bool strictMode = false;           ///< If set to true, requires targetType to be exact match (inside return cast)
+	bool allowVoid = false;            ///< If set to true, allow void-returning function calls (only top level, cast to void and first in comma)
 	std::set< std::string > otypeKeys;  /// different type may map to same key
 
Index: src/ResolvExpr/CastCost.cc
===================================================================
--- src/ResolvExpr/CastCost.cc	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/ResolvExpr/CastCost.cc	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -229,4 +229,7 @@
 	if ( typesCompatibleIgnoreQualifiers( src, dst, symtab, env ) ) {
 		PRINT( std::cerr << "compatible!" << std::endl; )
+		if (dynamic_cast<const ast::ZeroType *>(dst) || dynamic_cast<const ast::OneType *>(dst)) {
+			return Cost::spec;
+		}
 		return Cost::zero;
 	} else if ( dynamic_cast< const ast::VoidType * >( dst ) ) {
Index: src/ResolvExpr/CommonType.cc
===================================================================
--- src/ResolvExpr/CommonType.cc	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/ResolvExpr/CommonType.cc	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -695,4 +695,6 @@
 			if ( auto basic2 = dynamic_cast< const ast::BasicType * >( type2 ) ) {
 				#warning remove casts when `commonTypes` moved to new AST
+				
+				/*
 				ast::BasicType::Kind kind = (ast::BasicType::Kind)(int)commonTypes[ (BasicType::Kind)(int)basic->kind ][ (BasicType::Kind)(int)basic2->kind ];
 				if (
@@ -704,4 +706,16 @@
 					result = new ast::BasicType{ kind, basic->qualifiers | basic2->qualifiers };
 				}
+				*/
+				ast::BasicType::Kind kind;
+				if (basic->kind != basic2->kind && !widen.first && !widen.second) return;
+				else if (!widen.first) kind = basic->kind; // widen.second
+				else if (!widen.second) kind = basic2->kind;
+				else kind = (ast::BasicType::Kind)(int)commonTypes[ (BasicType::Kind)(int)basic->kind ][ (BasicType::Kind)(int)basic2->kind ];
+				// xxx - what does qualifiers even do here??
+				if ( (basic->qualifiers >= basic2->qualifiers || widen.first)
+					&& (basic->qualifiers <= basic2->qualifiers || widen.second) ) {
+					result = new ast::BasicType{ kind, basic->qualifiers | basic2->qualifiers };
+				}
+				
 			} else if (
 				dynamic_cast< const ast::ZeroType * >( type2 )
@@ -710,11 +724,14 @@
 				#warning remove casts when `commonTypes` moved to new AST
 				ast::BasicType::Kind kind = (ast::BasicType::Kind)(int)commonTypes[ (BasicType::Kind)(int)basic->kind ][ (BasicType::Kind)(int)ast::BasicType::SignedInt ];
-				if (
-					( ( kind == basic->kind && basic->qualifiers >= type2->qualifiers )
+				/*
+				if ( // xxx - what does qualifier even do here??
+					( ( basic->qualifiers >= type2->qualifiers )
 						|| widen.first )
-					&& ( ( kind != basic->kind && basic->qualifiers <= type2->qualifiers )
+					 && ( ( /* kind != basic->kind && basic->qualifiers <= type2->qualifiers )
 						|| widen.second )
-				) {
-					result = new ast::BasicType{ kind, basic->qualifiers | type2->qualifiers };
+				) 
+				*/
+				if (widen.second) {
+					result = new ast::BasicType{ basic->kind, basic->qualifiers | type2->qualifiers };
 				}
 			} else if ( const ast::EnumInstType * enumInst = dynamic_cast< const ast::EnumInstType * >( type2 ) ) {
@@ -744,4 +761,5 @@
 				auto entry = open.find( *var );
 				if ( entry != open.end() ) {
+				// if (tenv.lookup(*var)) {
 					ast::AssertionSet need, have;
 					if ( ! tenv.bindVar(
Index: src/ResolvExpr/ConversionCost.cc
===================================================================
--- src/ResolvExpr/ConversionCost.cc	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/ResolvExpr/ConversionCost.cc	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -702,4 +702,7 @@
 
 	cost = costCalc( refType->base, dst, srcIsLvalue, symtab, env );
+
+	// xxx - should qualifiers be considered in pass-by-value?
+	/*
 	if ( refType->base->qualifiers == dst->qualifiers ) {
 		cost.incReference();
@@ -709,4 +712,6 @@
 		cost.incUnsafe();
 	}
+	*/
+	cost.incReference();
 }
 
@@ -792,8 +797,14 @@
 			cost.incSign( signMatrix[ ast::BasicType::SignedInt ][ dstAsBasic->kind ] );
 		}
+		// this has the effect of letting any expr such as x+0, x+1 to be typed
+		// the same as x, instead of at least int. are we willing to sacrifice this little
+		// bit of coherence with C?
+		// TODO: currently this does not work when no zero/one overloads exist. Find a fix for it.
+		// cost = Cost::zero;
 	} else if ( dynamic_cast< const ast::PointerType * >( dst ) ) {
 		cost = Cost::zero;
 		// +1 for zero_t ->, +1 for disambiguation
 		cost.incSafe( maxIntCost + 2 );
+		// assuming 0p is supposed to be used for pointers?
 	}
 }
@@ -804,5 +815,5 @@
 		cost = Cost::zero;
 	} else if ( const ast::BasicType * dstAsBasic =
-			dynamic_cast< const ast::BasicType * >( dst ) ) {
+			dynamic_cast< const ast::BasicType * >( dst ) ) {		
 		int tableResult = costMatrix[ ast::BasicType::SignedInt ][ dstAsBasic->kind ];
 		if ( -1 == tableResult ) {
@@ -813,4 +824,6 @@
 			cost.incSign( signMatrix[ ast::BasicType::SignedInt ][ dstAsBasic->kind ] );
 		}
+		
+		// cost = Cost::zero;
 	}
 }
Index: src/ResolvExpr/FindOpenVars.cc
===================================================================
--- src/ResolvExpr/FindOpenVars.cc	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/ResolvExpr/FindOpenVars.cc	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -21,7 +21,10 @@
 #include "AST/Pass.hpp"
 #include "AST/Type.hpp"
+#include "AST/TypeEnvironment.hpp"
 #include "Common/PassVisitor.h"
 #include "SynTree/Declaration.h"  // for TypeDecl, DeclarationWithType (ptr ...
 #include "SynTree/Type.h"         // for Type, Type::ForallList, ArrayType
+
+#include <iostream>
 
 namespace ResolvExpr {
@@ -102,14 +105,21 @@
 			ast::AssertionSet & need;
 			ast::AssertionSet & have;
+			ast::TypeEnvironment & env;
 			bool nextIsOpen;
 
 			FindOpenVars_new(
 				ast::OpenVarSet & o, ast::OpenVarSet & c, ast::AssertionSet & n,
-				ast::AssertionSet & h, FirstMode firstIsOpen )
-			: open( o ), closed( c ), need( n ), have( h ), nextIsOpen( firstIsOpen ) {}
+				ast::AssertionSet & h, ast::TypeEnvironment & env, FirstMode firstIsOpen )
+			: open( o ), closed( c ), need( n ), have( h ), env (env), nextIsOpen( firstIsOpen ) {}
 
 			void previsit( const ast::FunctionType * type ) {
 				// mark open/closed variables
 				if ( nextIsOpen ) {
+					// trying to remove this from resolver.
+					// occasionally used in other parts so not deleting right now.
+
+					// insert open variables unbound to environment.
+					env.add(type->forall);
+
 					for ( auto & decl : type->forall ) {
 						open[ *decl ] = ast::TypeData{ decl->base };
@@ -137,7 +147,15 @@
 	void findOpenVars(
 			const ast::Type * type, ast::OpenVarSet & open, ast::OpenVarSet & closed,
-			ast::AssertionSet & need, ast::AssertionSet & have, FirstMode firstIsOpen ) {
-		ast::Pass< FindOpenVars_new > finder{ open, closed, need, have, firstIsOpen };
+			ast::AssertionSet & need, ast::AssertionSet & have, ast::TypeEnvironment & env, FirstMode firstIsOpen ) {
+		ast::Pass< FindOpenVars_new > finder{ open, closed, need, have, env, firstIsOpen };
 		type->accept( finder );
+
+		if (!closed.empty()) {
+			std::cerr << "closed: ";
+			for (auto& i : closed) {
+				std::cerr << i.first.base->location << ":" << i.first.base->name << ' ';
+			}
+			std::cerr << std::endl;
+		}
 	}
 } // namespace ResolvExpr
Index: src/ResolvExpr/FindOpenVars.h
===================================================================
--- src/ResolvExpr/FindOpenVars.h	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/ResolvExpr/FindOpenVars.h	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -33,5 +33,5 @@
 	void findOpenVars( 
 		const ast::Type * type, ast::OpenVarSet & open, ast::OpenVarSet & closed, 
-		ast::AssertionSet & need, ast::AssertionSet & have, FirstMode firstIsOpen );
+		ast::AssertionSet & need, ast::AssertionSet & have, ast::TypeEnvironment & env, FirstMode firstIsOpen );
 } // namespace ResolvExpr
 
Index: src/ResolvExpr/Resolver.cc
===================================================================
--- src/ResolvExpr/Resolver.cc	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/ResolvExpr/Resolver.cc	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -1009,4 +1009,5 @@
 			ast::TypeEnvironment env;
 			CandidateFinder finder( context, env );
+			finder.allowVoid = true;
 			finder.find( untyped, recursion_level == 1 ? mode.atTopLevel() : mode );
 			--recursion_level;
@@ -1052,5 +1053,5 @@
 
 			// promote candidate.cvtCost to .cost
-			promoteCvtCost( winners );
+			// promoteCvtCost( winners );
 
 			// produce ambiguous errors, if applicable
Index: src/ResolvExpr/SatisfyAssertions.cpp
===================================================================
--- src/ResolvExpr/SatisfyAssertions.cpp	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/ResolvExpr/SatisfyAssertions.cpp	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -16,4 +16,5 @@
 #include "SatisfyAssertions.hpp"
 
+#include <iostream>
 #include <algorithm>
 #include <cassert>
@@ -42,4 +43,6 @@
 #include "SymTab/Mangler.h"
 
+
+
 namespace ResolvExpr {
 
@@ -62,5 +65,9 @@
 			ast::AssertionSet && h, ast::AssertionSet && n, ast::OpenVarSet && o, ast::UniqueId rs )
 		: cdata( c ), adjType( at ), env( std::move( e ) ), have( std::move( h ) ),
-		  need( std::move( n ) ), open( std::move( o ) ), resnSlot( rs ) {}
+		  need( std::move( n ) ), open( std::move( o ) ), resnSlot( rs ) {
+			if (!have.empty()) {
+				std::cerr << c.id->location << ':' << c.id->name << std::endl;
+			}
+		  }
 	};
 
@@ -136,10 +143,5 @@
 	};
 
-	/// Adds a captured assertion to the symbol table
-	void addToSymbolTable( const ast::AssertionSet & have, ast::SymbolTable & symtab ) {
-		for ( auto & i : have ) {
-			if ( i.second.isUsed ) { symtab.addId( i.first->var ); }
-		}
-	}
+
 
 	/// Binds a single assertion, updating satisfaction state
@@ -152,5 +154,5 @@
 			"Assertion candidate does not have a unique ID: %s", toString( candidate ).c_str() );
 
-		ast::Expr * varExpr = match.cdata.combine( cand->expr->location, cand->cvtCost );
+		ast::Expr * varExpr = match.cdata.combine( cand->expr->location, cand->cost );
 		varExpr->result = match.adjType;
 		if ( match.resnSlot ) { varExpr->inferred.resnSlots().emplace_back( match.resnSlot ); }
@@ -162,10 +164,13 @@
 
 	/// Satisfy a single assertion
-	bool satisfyAssertion( ast::AssertionList::value_type & assn, SatState & sat, bool allowConversion = false, bool skipUnbound = false) {
+	bool satisfyAssertion( ast::AssertionList::value_type & assn, SatState & sat, bool skipUnbound = false) {
 		// skip unused assertions
+		static unsigned int cnt = 0;
 		if ( ! assn.second.isUsed ) return true;
 
+		if (assn.first->var->name[1] == '|') std::cerr << ++cnt << std::endl;
+
 		// find candidates that unify with the desired type
-		AssnCandidateList matches;
+		AssnCandidateList matches, inexactMatches;
 
 		std::vector<ast::SymbolTable::IdData> candidates;
@@ -209,7 +214,24 @@
 
 			ast::OpenVarSet closed;
-			findOpenVars( toType, newOpen, closed, newNeed, have, FirstClosed );
-			findOpenVars( adjType, newOpen, closed, newNeed, have, FirstOpen );
-			if ( allowConversion ) {
+			// findOpenVars( toType, newOpen, closed, newNeed, have, FirstClosed );
+			findOpenVars( adjType, newOpen, closed, newNeed, have, newEnv, FirstOpen );
+			ast::TypeEnvironment tempNewEnv {newEnv};
+
+			if ( unifyExact( toType, adjType, tempNewEnv, newNeed, have, newOpen, WidenMode {true, true}, sat.symtab ) ) {
+				// set up binding slot for recursive assertions
+				ast::UniqueId crntResnSlot = 0;
+				if ( ! newNeed.empty() ) {
+					crntResnSlot = ++globalResnSlot;
+					for ( auto & a : newNeed ) { a.second.resnSlot = crntResnSlot; }
+				}
+
+				matches.emplace_back(
+					cdata, adjType, std::move( tempNewEnv ), std::move( have ), std::move( newNeed ),
+					std::move( newOpen ), crntResnSlot );
+			}
+			else if ( matches.empty() ) {
+				// restore invalidated env
+				// newEnv = sat.cand->env;
+				// newNeed.clear();
 				if ( auto c = commonType( toType, adjType, newEnv, newNeed, have, newOpen, WidenMode {true, true}, sat.symtab ) ) {
 					// set up binding slot for recursive assertions
@@ -220,26 +242,13 @@
 					}
 
-					matches.emplace_back(
+					inexactMatches.emplace_back(
 						cdata, adjType, std::move( newEnv ), std::move( have ), std::move( newNeed ),
 						std::move( newOpen ), crntResnSlot );
 				}
 			}
-			else {
-				if ( unifyExact( toType, adjType, newEnv, newNeed, have, newOpen, WidenMode {true, true}, sat.symtab ) ) {
-					// set up binding slot for recursive assertions
-					ast::UniqueId crntResnSlot = 0;
-					if ( ! newNeed.empty() ) {
-						crntResnSlot = ++globalResnSlot;
-						for ( auto & a : newNeed ) { a.second.resnSlot = crntResnSlot; }
-					}
-
-					matches.emplace_back(
-						cdata, adjType, std::move( newEnv ), std::move( have ), std::move( newNeed ),
-						std::move( newOpen ), crntResnSlot );
-				}
-			}
 		}
 
 		// break if no satisfying match
+		if ( matches.empty() ) matches = std::move(inexactMatches);
 		if ( matches.empty() ) return false;
 
@@ -252,5 +261,5 @@
 		// otherwise bind unique match in ongoing scope
 		AssnCandidate & match = matches.front();
-		addToSymbolTable( match.have, sat.symtab );
+		// addToSymbolTable( match.have, sat.symtab );
 		sat.newNeed.insert( match.need.begin(), match.need.end() );
 		sat.cand->env = std::move( match.env );
@@ -435,5 +444,4 @@
 		// for each current mutually-compatible set of assertions
 		for ( SatState & sat : sats ) {
-			bool allowConversion = false;
 			// stop this branch if a better option is already found
 			auto it = thresholds.find( pruneKey( *sat.cand ) );
@@ -448,5 +456,5 @@
 				for ( auto & assn : sat.need ) {
 					// fail early if any assertion is not satisfiable
-					if ( ! satisfyAssertion( assn, sat, allowConversion, !next.empty() ) ) {
+					if ( ! satisfyAssertion( assn, sat, !next.empty() ) ) {
 						next.emplace_back(assn);
 						// goto nextSat;
@@ -457,5 +465,5 @@
 				// fail if nothing resolves
 				else if (next.size() == sat.need.size()) {
-					if (allowConversion) {
+					// if (allowConversion) {
 						Indenter tabs{ 3 };
 						std::ostringstream ss;
@@ -467,12 +475,11 @@
 						errors.emplace_back( ss.str() );
 						goto nextSat;
-					}
-
-					else {
-						allowConversion = true;
-						continue;
-					}
-				}
-				allowConversion = false;
+					// }
+
+					// else {
+					//	allowConversion = true;
+					//	continue;
+					// }
+				}
 				sat.need = std::move(next);
 			}
@@ -528,5 +535,5 @@
 						sat.cand->expr, std::move( compat.env ), std::move( compat.open ),
 						ast::AssertionSet{} /* need moved into satisfaction state */,
-						sat.cand->cost, sat.cand->cvtCost );
+						sat.cand->cost );
 
 					ast::AssertionSet nextNewNeed{ sat.newNeed };
@@ -541,5 +548,5 @@
 					for ( DeferRef r : compat.assns ) {
 						AssnCandidate match = r.match;
-						addToSymbolTable( match.have, nextSymtab );
+						// addToSymbolTable( match.have, nextSymtab );
 						nextNewNeed.insert( match.need.begin(), match.need.end() );
 
Index: src/ResolvExpr/Unify.cc
===================================================================
--- src/ResolvExpr/Unify.cc	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/ResolvExpr/Unify.cc	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -134,6 +134,6 @@
 		env.apply( newSecond );
 
-		findOpenVars( newFirst, open, closed, need, have, FirstClosed );
-		findOpenVars( newSecond, open, closed, need, have, FirstOpen );
+		// findOpenVars( newFirst, open, closed, need, have, FirstClosed );
+		findOpenVars( newSecond, open, closed, need, have, newEnv, FirstOpen );
 
 		return unifyExact(newFirst, newSecond, newEnv, need, have, open, noWiden(), symtab );
@@ -1029,5 +1029,5 @@
 
 		void postvisit( const ast::TypeInstType * typeInst ) {
-			assert( open.find( *typeInst ) == open.end() );
+			// assert( open.find( *typeInst ) == open.end() );
 			handleRefType( typeInst, type2 );
 		}
@@ -1142,6 +1142,6 @@
 	) {
 		ast::OpenVarSet closed;
-		findOpenVars( type1, open, closed, need, have, FirstClosed );
-		findOpenVars( type2, open, closed, need, have, FirstOpen );
+		// findOpenVars( type1, open, closed, need, have, FirstClosed );
+		findOpenVars( type2, open, closed, need, have, env, FirstOpen );
 		return unifyInexact(
 			type1, type2, env, need, have, open, WidenMode{ true, true }, symtab, common );
@@ -1160,7 +1160,10 @@
 			entry1 = var1 ? open.find( *var1 ) : open.end(),
 			entry2 = var2 ? open.find( *var2 ) : open.end();
-		bool isopen1 = entry1 != open.end();
-		bool isopen2 = entry2 != open.end();
-
+		// bool isopen1 = entry1 != open.end();
+		// bool isopen2 = entry2 != open.end();
+		bool isopen1 = var1 && env.lookup(*var1);
+		bool isopen2 = var2 && env.lookup(*var2);
+
+		/*
 		if ( isopen1 && isopen2 ) {
 			if ( entry1->second.kind != entry2->second.kind ) return false;
@@ -1172,8 +1175,19 @@
 		} else if ( isopen2 ) {
 			return env.bindVar( var2, type1, entry2->second, need, have, open, widen, symtab );
-		} else {
+		} */
+		if ( isopen1 && isopen2 ) {
+			if ( var1->base->kind != var2->base->kind ) return false;
+			return env.bindVarToVar(
+				var1, var2, ast::TypeData{ var1->base->kind, var1->base->sized||var2->base->sized }, need, have,
+				open, widen, symtab );
+		} else if ( isopen1 ) {
+			return env.bindVar( var1, type2, ast::TypeData{var1->base}, need, have, open, widen, symtab );
+		} else if ( isopen2 ) {
+			return env.bindVar( var2, type1, ast::TypeData{var2->base}, need, have, open, widen, symtab );
+		}else {
 			return ast::Pass<Unify_new>::read(
 				type1, type2, env, need, have, open, widen, symtab );
 		}
+		
 	}
 
Index: src/SynTree/Expression.cc
===================================================================
--- src/SynTree/Expression.cc	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/SynTree/Expression.cc	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -267,13 +267,13 @@
 }
 
-CastExpr::CastExpr( Expression * arg, Type * toType, bool isGenerated ) : arg(arg), isGenerated( isGenerated ) {
+CastExpr::CastExpr( Expression * arg, Type * toType, bool isGenerated, CastKind kind ) : arg(arg), isGenerated( isGenerated ), kind( kind ) {
 	set_result(toType);
 }
 
-CastExpr::CastExpr( Expression * arg, bool isGenerated ) : arg(arg), isGenerated( isGenerated ) {
+CastExpr::CastExpr( Expression * arg, bool isGenerated, CastKind kind ) : arg(arg), isGenerated( isGenerated ), kind( kind ) {
 	set_result( new VoidType( Type::Qualifiers() ) );
 }
 
-CastExpr::CastExpr( const CastExpr & other ) : Expression( other ), arg( maybeClone( other.arg ) ), isGenerated( other.isGenerated ) {
+CastExpr::CastExpr( const CastExpr & other ) : Expression( other ), arg( maybeClone( other.arg ) ), isGenerated( other.isGenerated ), kind( other.kind ) {
 }
 
Index: src/SynTree/Expression.h
===================================================================
--- src/SynTree/Expression.h	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/SynTree/Expression.h	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -271,6 +271,14 @@
 	bool isGenerated = true;
 
-	CastExpr( Expression * arg, bool isGenerated = true );
-	CastExpr( Expression * arg, Type * toType, bool isGenerated = true );
+	enum CastKind {
+		Default, // C
+		Coerce, // reinterpret cast
+		Return  // overload selection
+	};
+
+	CastKind kind = Default;
+
+	CastExpr( Expression * arg, bool isGenerated = true, CastKind kind = Default );
+	CastExpr( Expression * arg, Type * toType, bool isGenerated = true, CastKind kind = Default );
 	CastExpr( Expression * arg, void * ) = delete; // prevent accidentally passing pointers for isGenerated in the first constructor
 	CastExpr( const CastExpr & other );
Index: src/Tuples/TupleAssignment.cc
===================================================================
--- src/Tuples/TupleAssignment.cc	(revision e1d66c84e0c0d91acc25559fd73ab14706eee525)
+++ src/Tuples/TupleAssignment.cc	(revision 46da46b804c2acd6916c888202a57186f41df8a8)
@@ -679,4 +679,5 @@
 
 				ResolvExpr::CandidateFinder finder( crntFinder.context, matcher->env );
+				finder.allowVoid = true;
 
 				try {
