Index: src/AST/Convert.cpp
===================================================================
--- src/AST/Convert.cpp	(revision 2890212a2b66d8b4d2e668cc354f6205f1c666bd)
+++ src/AST/Convert.cpp	(revision ba417e2174cff9c72bd1cdf6de9e9ade37ab35c3)
@@ -2124,5 +2124,6 @@
 				old->location,
 				GET_ACCEPT_1(member, DeclWithType),
-				GET_ACCEPT_1(aggregate, Expr)
+				GET_ACCEPT_1(aggregate, Expr),
+				ast::MemberExpr::NoOpConstructionChosen
 			)
 		);
Index: src/AST/Expr.cpp
===================================================================
--- src/AST/Expr.cpp	(revision 2890212a2b66d8b4d2e668cc354f6205f1c666bd)
+++ src/AST/Expr.cpp	(revision ba417e2174cff9c72bd1cdf6de9e9ade37ab35c3)
@@ -158,10 +158,54 @@
 	assert( aggregate->result );
 
-	// take ownership of member type
-	result = mem->get_type();
+	// Deep copy on result type avoids mutation on transitively multiply referenced object.
+	//
+	// Example, adapted from parts of builtins and bootloader:
+	//
+	// forall(dtype T)
+	// struct __Destructor {
+	//   T * object;
+	//   void (*dtor)(T *);
+	// };
+	//
+	// forall(dtype S)
+	// void foo(__Destructor(S) &d) {
+	//   if (d.dtor) {  // here
+	//   }
+	// }
+	//
+	// Let e be the "d.dtor" guard espression, which is MemberExpr after resolve.  Let d be the
+	// declaration of member __Destructor.dtor (an ObjectDecl), as accessed via the top-level
+	// declaration of __Destructor.  Consider the types e.result and d.type.  In the old AST, one
+	// is a clone of the other.  Ordinary new-AST use would set them up as a multiply-referenced
+	// object.
+	//
+	// e.result: PointerType
+	// .base: FunctionType
+	// .params.front(): ObjectDecl, the anonymous parameter of type T*
+	// .type: PointerType
+	// .base: TypeInstType
+	// let x = that
+	// let y = similar, except start from d.type
+	//
+	// Consider two code lines down, genericSubstitution(...).apply(result).
+	//
+	// Applying this chosen-candidate's type substitution means modifying x, substituting
+	// S for T.  This mutation should affect x and not y.
+
+	result = deepCopy(mem->get_type());
+
 	// substitute aggregate generic parameters into member type
 	genericSubstitution( aggregate->result ).apply( result );
 	// ensure lvalue and appropriate restrictions from aggregate type
 	add_qualifiers( result, aggregate->result->qualifiers | CV::Lvalue );
+}
+
+MemberExpr::MemberExpr( const CodeLocation & loc, const DeclWithType * mem, const Expr * agg,
+    MemberExpr::NoOpConstruction overloadSelector )
+: Expr( loc ), member( mem ), aggregate( agg ) {
+	assert( member );
+	assert( aggregate );
+	assert( aggregate->result );
+	(void) overloadSelector;
 }
 
Index: src/AST/Expr.hpp
===================================================================
--- src/AST/Expr.hpp	(revision 2890212a2b66d8b4d2e668cc354f6205f1c666bd)
+++ src/AST/Expr.hpp	(revision ba417e2174cff9c72bd1cdf6de9e9ade37ab35c3)
@@ -358,4 +358,11 @@
 	MemberExpr * clone() const override { return new MemberExpr{ *this }; }
 	MUTATE_FRIEND
+
+	// Custructor overload meant only for AST conversion
+	enum NoOpConstruction { NoOpConstructionChosen };
+	MemberExpr( const CodeLocation & loc, const DeclWithType * mem, const Expr * agg,
+	    NoOpConstruction overloadSelector );
+	friend class ::ConverterOldToNew;
+	friend class ::ConverterNewToOld;
 };
 
