Skip to content

fix: replace silent back-reference skip with explicit reference-equality enforcement in ObjectPropertiesEqualsConstraint#11

Draft
jeme with Copilot wants to merge 2 commits into
masterfrom
copilot/fix-object-properties-equals-constraint
Draft

fix: replace silent back-reference skip with explicit reference-equality enforcement in ObjectPropertiesEqualsConstraint#11
jeme with Copilot wants to merge 2 commits into
masterfrom
copilot/fix-object-properties-equals-constraint

Conversation

Copilot AI commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

When comparing object graphs containing shared or circular references, the constraint silently skipped (continue) any property whose expected value had been seen before. This meant structural identity — e.g. two properties on the expected object pointing to the same instance — was never verified against the actual graph.

Changes

ComparisonContext (new)

  • Construction-time registry mapping expected object identity → ref-id (via ReferenceComparer)
  • Match-time binding of ref-id → actual object
  • EnterMatch/ExitMatch depth tracking to reset bindings between ApplyTo calls

BackReferenceConstraint (new, inner class)

  • Replaces the continue skip when a previously-registered expected object is encountered again
  • Enforces ReferenceEquals(actual, previouslyBoundActual) — the same expected reference must correspond to the same actual reference

ObjectPropertiesEqualsConstraint

  • InitializeProperties: on back-reference, creates BackReferenceConstraint instead of skipping; IEnumerable types are still delegated to EnumerableEqualsConstraint (no ref-tracking needed)
  • Matches: calls EnterMatch/ExitMatch around traversal; binds each forward-reference node's actual value via BindActual
  • ComparisonContext flows through nested constraints (replacing the old HashSet<object>)

ReferenceComparer

  • Fixed GetHashCode to use RuntimeHelpers.GetHashCode (identity hash) instead of obj.GetHashCode(), which could be overridden

Tests

// Same expected instance referenced from two properties — actual uses different (but equal) instances → must fail
var sharedAddress = new Address { Street = "Main St" };
var expected = new PersonWithTwoAddresses { Home = sharedAddress, Work = sharedAddress };
var actual   = new PersonWithTwoAddresses { Home = new Address { Street = "Main St" },
                                            Work = new Address { Street = "Main St" } };
Assert.That(actual, Does.Not.Match(expected).Properties());
  • Shared-ref with same actual instance → passes
  • Two independent equal instances (not back-references) → passes
  • Self-referencing expected vs. non-cyclic actual → fails

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants