Index: src/GenPoly/GenPoly.cpp
===================================================================
--- src/GenPoly/GenPoly.cpp	(revision cef5bfcf6f5619c0a358f9d4c2fda5a52c4ca1e5)
+++ src/GenPoly/GenPoly.cpp	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
@@ -27,4 +27,5 @@
 #include "AST/Type.hpp"
 #include "AST/TypeSubstitution.hpp"
+#include "Common/Eval.hpp"                // for eval
 #include "GenPoly/ErasableScopedMap.hpp"  // for ErasableScopedMap<>::const_...
 #include "ResolvExpr/Typeops.hpp"         // for flatten
@@ -243,4 +244,72 @@
 } // namespace
 
+// This function, and its helpers following, have logic duplicated from
+// unification.  The difference in context is that unification applies where
+// the types "must" match, while this variation applies to arbitrary type
+// pairs, when an optimization could apply if they happen to match.  This
+// variation does not bind type variables.  The helper functions support
+// the case for matching ArrayType.
+bool typesPolyCompatible( ast::Type const * lhs, ast::Type const * rhs );
+
+static bool exprsPolyCompatibleByStaticValue(
+		const ast::Expr * e1, const ast::Expr * e2 ) {
+	Evaluation r1 = eval(e1);
+	Evaluation r2 = eval(e2);
+
+	if ( !r1.hasKnownValue ) return false;
+	if ( !r2.hasKnownValue ) return false;
+
+	if ( r1.knownValue != r2.knownValue ) return false;
+
+	return true;
+}
+
+static bool exprsPolyCompatible( ast::Expr const * lhs,
+		ast::Expr const * rhs ) {
+	type_index const lid = typeid(*lhs);
+	type_index const rid = typeid(*rhs);
+	if ( lid != rid ) return false;
+
+	if ( exprsPolyCompatibleByStaticValue( lhs, rhs ) ) return true;
+
+	if ( type_index(typeid(ast::CastExpr)) == lid ) {
+		ast::CastExpr const * l = as<ast::CastExpr>(lhs);
+		ast::CastExpr const * r = as<ast::CastExpr>(rhs);
+
+		// inspect casts' target types
+		if ( !typesPolyCompatible(
+			l->result, r->result ) ) return false;
+
+		// inspect casts' inner expressions
+		return exprsPolyCompatible( l->arg, r->arg );
+
+	} else if ( type_index(typeid(ast::VariableExpr)) == lid ) {
+		ast::VariableExpr const * l = as<ast::VariableExpr>(lhs);
+		ast::VariableExpr const * r = as<ast::VariableExpr>(rhs);
+
+		assert(l->var);
+		assert(r->var);
+
+		// conservative: variable exprs match if their declarations are
+		// represented by the same C++ AST object
+		return (l->var == r->var);
+
+	} else if ( type_index(typeid(ast::SizeofExpr)) == lid ) {
+		ast::SizeofExpr const * l = as<ast::SizeofExpr>(lhs);
+		ast::SizeofExpr const * r = as<ast::SizeofExpr>(rhs);
+
+		assert((l->type != nullptr) ^ (l->expr != nullptr));
+		assert((r->type != nullptr) ^ (r->expr != nullptr));
+		if ( !(l->type && r->type) ) return false;
+
+		// mutual recursion with type poly compatibility
+		return typesPolyCompatible( l->type, r->type );
+
+	} else {
+		// All other forms compare on static value only, done earlier
+		return false;
+	}
+}
+
 bool typesPolyCompatible( ast::Type const * lhs, ast::Type const * rhs ) {
 	type_index const lid = typeid(*lhs);
@@ -256,5 +325,5 @@
 
 	// So remaining types can be examined case by case.
-	// Recurse through type structure (conditions borrowed from Unify.cpp).
+	// Recurse through type structure (conditions duplicated from Unify.cpp).
 
 	if ( type_index(typeid(ast::BasicType)) == lid ) {
@@ -280,14 +349,13 @@
 		ast::ArrayType const * r = as<ast::ArrayType>(rhs);
 
-		if ( l->isVarLen ) {
-			if ( !r->isVarLen ) return false;
-		} else {
-			if ( r->isVarLen ) return false;
-
-			auto lc = l->dimension.as<ast::ConstantExpr>();
-			auto rc = r->dimension.as<ast::ConstantExpr>();
-			if ( lc && rc && lc->intValue() != rc->intValue() ) {
+		if ( l->isVarLen != r->isVarLen ) return false;
+		if ( (l->dimension != nullptr) != (r->dimension != nullptr) )
+			return false;
+
+		if ( l->dimension ) {
+			assert( r->dimension );
+			// mutual recursion with expression poly compatibility
+			if ( !exprsPolyCompatible(l->dimension, r->dimension) )
 				return false;
-			}
 		}
 
Index: src/ResolvExpr/CandidateFinder.cpp
===================================================================
--- src/ResolvExpr/CandidateFinder.cpp	(revision cef5bfcf6f5619c0a358f9d4c2fda5a52c4ca1e5)
+++ src/ResolvExpr/CandidateFinder.cpp	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
@@ -1241,4 +1241,9 @@
 		Cost minCastCost = Cost::infinity;
 		for ( CandidateRef & cand : finder.candidates ) {
+			ast::ptr< ast::Type > fromType = cand->expr->result;
+			assert( fromType );
+			fromType = resolveTypeof( fromType, context );
+			fromType = adjustExprType( fromType, tenv, symtab );
+
 			ast::AssertionSet need( cand->need.begin(), cand->need.end() ), have;
 			ast::OpenVarSet open( cand->open );
@@ -1250,16 +1255,16 @@
 			// subexpression results that are cast directly. The candidate is invalid if it
 			// has fewer results than there are types to cast to.
-			int discardedValues = cand->expr->result->size() - toType->size();
+			int discardedValues = fromType->size() - toType->size();
 			if ( discardedValues < 0 ) continue;
 
 			// unification run for side-effects
-			unify( toType, cand->expr->result, cand->env, need, have, open );
+			unify( toType, fromType, cand->env, need, have, open );
 			Cost thisCost =
 				(castExpr->isGenerated == ast::GeneratedFlag::GeneratedCast)
-					? conversionCost( cand->expr->result, toType, cand->expr->get_lvalue(), symtab, cand->env )
-					: castCost( cand->expr->result, toType, cand->expr->get_lvalue(), symtab, cand->env );
+					? conversionCost( fromType, toType, cand->expr->get_lvalue(), symtab, cand->env )
+					: castCost( fromType, toType, cand->expr->get_lvalue(), symtab, cand->env );
 			
 			// Redefine enum cast
-			auto argAsEnum = cand->expr->result.as<ast::EnumInstType>();
+			auto argAsEnum = fromType.as<ast::EnumInstType>();
 			auto toAsEnum = toType.as<ast::EnumInstType>();
 			if ( argAsEnum && toAsEnum && argAsEnum->name != toAsEnum->name ) {
@@ -1272,5 +1277,5 @@
 			PRINT(
 				std::cerr << "working on cast with result: " << toType << std::endl;
-				std::cerr << "and expr type: " << cand->expr->result << std::endl;
+				std::cerr << "and expr type: " << fromType << std::endl;
 				std::cerr << "env: " << cand->env << std::endl;
 			)
@@ -1281,4 +1286,11 @@
 				// count one safe conversion for each value that is thrown away
 				thisCost.incSafe( discardedValues );
+
+				// See Aaron Moss, page 47; this reasoning does not hold since implicit conversions
+				// can create the same resolution issue. The C intrinsic interpretations are pruned
+				// immediately for the lowest cost option regardless of result type. Related code in
+				// postvisit (UntypedExpr).
+				// Cast expression costs are updated now to use the general rules.
+				/*
 				// select first on argument cost, then conversion cost
 				if ( cand->cost < minExprCost || ( cand->cost == minExprCost && thisCost < minCastCost ) ) {
@@ -1289,7 +1301,14 @@
 				// ambigious case, still output candidates to print in error message
 				if ( cand->cost == minExprCost && thisCost == minCastCost ) {
+				*/
+				cand->cost += thisCost;
+				if (cand->cost < minExprCost) {
+					minExprCost = cand->cost;
+					matches.clear();
+				}
+				if (cand->cost == minExprCost) {
 					CandidateRef newCand = std::make_shared<Candidate>(
 						restructureCast( cand->expr, toType, castExpr->isGenerated ),
-						copy( cand->env ), std::move( open ), std::move( need ), cand->cost + thisCost);
+						copy( cand->env ), std::move( open ), std::move( need ), cand->cost);
 					// 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)
Index: tests/.expect/poly-many-arsz.txt
===================================================================
--- tests/.expect/poly-many-arsz.txt	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
+++ tests/.expect/poly-many-arsz.txt	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
@@ -0,0 +1,9 @@
+baseline
+|y| = 3
+y.second = 0
+y.second = 0
+with interference
+|x| = 2
+|y| = 3
+y.second = 0
+y.second = 0
Index: tests/array-collections/.expect/accordion.txt
===================================================================
--- tests/array-collections/.expect/accordion.txt	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
+++ tests/array-collections/.expect/accordion.txt	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
@@ -0,0 +1,4 @@
+simple
+0 1 2 3 4 5 6 7 8 9 10 11 12
+0 1 2 3 4
+0 1 2 3 4 5 6 7
Index: tests/array-collections/accordion.cfa
===================================================================
--- tests/array-collections/accordion.cfa	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
+++ tests/array-collections/accordion.cfa	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
@@ -0,0 +1,43 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2023 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// accordion.cfa -- declaring a struct with more content after an array member
+//
+// Author           : Mike Brooks
+// Created On       : Mon Aug 12 12:00:00 2024
+// Last Modified By :
+// Last Modified On :
+// Update Count     :
+//
+
+
+#include <fstream.hfa>
+#include <array.hfa>
+
+forall( T, [N], [M] )
+struct simple_t {
+    T c[N + M];
+    array( T, N ) n;
+    array( T, M ) m;
+};
+
+void simple( void ) {
+
+    sout | "simple";
+
+    simple_t(int, 5, 8) s;
+    for ( i; 5 + 8 ) s.c[i] = i;
+    for ( i; 5     ) s.n[i] = i;
+    for ( i;     8 ) s.m[i] = i;
+    for ( i; 5 + 8 ) sout | s.c[i] | nonl;  sout | nl;
+    for ( i; 5     ) sout | s.n[i] | nonl;  sout | nl;
+    for ( i;     8 ) sout | s.m[i] | nonl;  sout | nl;
+}
+
+int main() {
+    simple();
+    return 0;
+}
Index: tests/array-collections/dimexpr-match.hfa
===================================================================
--- tests/array-collections/dimexpr-match.hfa	(revision cef5bfcf6f5619c0a358f9d4c2fda5a52c4ca1e5)
+++ tests/array-collections/dimexpr-match.hfa	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
@@ -15,5 +15,5 @@
 //
 //      compiler=gcc -x c           # pick one
-//      compiler=$fa
+//      compiler=$cfa
 //
 //      test=dimexpr-match-c.cfa    # pick one
Index: tests/poly-many-arsz.cfa
===================================================================
--- tests/poly-many-arsz.cfa	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
+++ tests/poly-many-arsz.cfa	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
@@ -0,0 +1,63 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2023 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// poly-many-arsz.cfa -- using many built-in array types with concrete sizes
+//
+// Author           : Mike Brooks
+// Created On       : Tue Aug 13 12:00:00 2024
+// Last Modified By :
+// Last Modified On :
+// Update Count     :
+//
+
+// This behaviour was once broken, as trac #175.
+
+forall( T1*, T2* )
+struct mypair {
+    T1 first;
+    T2 second;
+};
+
+void baseline( void ) {
+    printf("baseline\n");
+
+    // no declaration of x
+    // facts that are true of y:
+
+    mypair(char[2], char) y @= {};
+    printf("|y| = %ld\n", sizeof(y));    // 3
+
+    y.second = 0;
+    printf("y.second = %d\n", y.second); // 0
+
+    y.first[1] = 17;
+    printf("y.second = %d\n", y.second); // 0
+}
+
+void withInterference( void ) {
+    printf("with interference\n");
+
+    // adding this declaration of x ...
+    mypair(char[1], char) x @= {};
+    printf("|x| = %ld\n", sizeof(x));    // 2
+
+    // ... must not affect the observed facts of y:
+
+    mypair(char[2], char) y @= {};
+    printf("|y| = %ld\n", sizeof(y));    // 3
+
+    y.second = 0;
+    printf("y.second = %d\n", y.second); // 0
+
+    y.first[1] = 17;
+    printf("y.second = %d\n", y.second); // 0
+}
+
+int main() {
+    baseline();
+    withInterference();
+    return 0;
+}
Index: tests/raii/.expect/typeof-member.txt
===================================================================
--- tests/raii/.expect/typeof-member.txt	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
+++ tests/raii/.expect/typeof-member.txt	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
@@ -0,0 +1,3 @@
+custom A ctor called
+5
+custom A dtor called
Index: tests/raii/typeof-member.cfa
===================================================================
--- tests/raii/typeof-member.cfa	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
+++ tests/raii/typeof-member.cfa	(revision 5b643eacb610dbfc2b4d7da3386b4a4b3c58cc79)
@@ -0,0 +1,38 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2023 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// typeof-member.cfa -- managed type declared as contained member via typeof
+//
+// Author           : Mike Brooks
+// Created On       : Tue Aug 13 12:00:00 2024
+// Last Modified By :
+// Last Modified On :
+// Update Count     :
+//
+
+// Note - array(...) expands as typeof(...).
+// So supporting member as typeof is necessary for having array as member.
+// The array type (arpk) is managed.
+
+struct A {
+    int x;
+};
+
+void  ?{}( A & ) { printf("custom A ctor called\n"); }
+void ^?{}( A & ) { printf("custom A dtor called\n"); }
+
+A foo( void );
+
+struct outer {
+    typeof( foo() ) a;
+};
+
+int main() {
+    outer b;
+    b.a.x = 5;
+    printf( "%d\n", b.a.x );
+    return 0;
+}
