diff --git a/src/Moq/Evaluator.cs b/src/Moq/Evaluator.cs index 68fbbcb9c..3af867e37 100644 --- a/src/Moq/Evaluator.cs +++ b/src/Moq/Evaluator.cs @@ -102,7 +102,7 @@ internal HashSet Nominate(Expression expression) public override Expression Visit(Expression expression) { - if (expression != null) + if (expression != null && expression.NodeType != ExpressionType.Quote) { bool saveCannotBeEvaluated = this.cannotBeEvaluated; this.cannotBeEvaluated = false; diff --git a/src/Moq/ExpressionComparer.cs b/src/Moq/ExpressionComparer.cs index 07176d660..40250c58c 100644 --- a/src/Moq/ExpressionComparer.cs +++ b/src/Moq/ExpressionComparer.cs @@ -18,6 +18,11 @@ private ExpressionComparer() { } + public bool EqualsAfterCapturesEvaluated(Expression x, Expression y) + { + return this.Equals(x?.Apply(EvaluateCaptures.Rewriter), y?.Apply(EvaluateCaptures.Rewriter)); + } + public bool Equals(Expression x, Expression y) { if (object.ReferenceEquals(x, y)) @@ -30,19 +35,6 @@ public bool Equals(Expression x, Expression y) return false; } - // Before actually comparing two nodes, make sure that captures variables have been - // evaluated to their current values (as we don't want to compare their identities): - - if (x is MemberExpression) - { - x = x.Apply(EvaluateCaptures.Rewriter); - } - - if (y is MemberExpression) - { - y = y.Apply(EvaluateCaptures.Rewriter); - } - if (x.NodeType == y.NodeType) { switch (x.NodeType) diff --git a/src/Moq/ExpressionExtensions.cs b/src/Moq/ExpressionExtensions.cs index aad628707..22f13cc10 100644 --- a/src/Moq/ExpressionExtensions.cs +++ b/src/Moq/ExpressionExtensions.cs @@ -448,6 +448,7 @@ private static bool PartialMatcherAwareEval_ShouldEvaluate(Expression expression #pragma warning disable 618 return expression.NodeType switch { + ExpressionType.Quote => false, ExpressionType.Parameter => false, ExpressionType.Extension => !(expression is MatchExpression), ExpressionType.Call => !((MethodCallExpression)expression).Method.IsDefined(typeof(MatcherAttribute), true) diff --git a/src/Moq/Expressions/Visitors/EvaluateCaptures.cs b/src/Moq/Expressions/Visitors/EvaluateCaptures.cs index 67df48da2..26c6d13e8 100644 --- a/src/Moq/Expressions/Visitors/EvaluateCaptures.cs +++ b/src/Moq/Expressions/Visitors/EvaluateCaptures.cs @@ -31,5 +31,10 @@ protected override Expression VisitMember(MemberExpression node) return base.VisitMember(node); } } + + protected override Expression VisitUnary(UnaryExpression node) + { + return node.NodeType == ExpressionType.Quote ? node : base.VisitUnary(node); + } } } diff --git a/src/Moq/Match.cs b/src/Moq/Match.cs index 479ffa248..5776e36db 100644 --- a/src/Moq/Match.cs +++ b/src/Moq/Match.cs @@ -217,7 +217,7 @@ public bool Equals(Match other) } else if (!(this.RenderExpression is MethodCallExpression ce && ce.Method.DeclaringType == typeof(Match))) { - return ExpressionComparer.Default.Equals(this.RenderExpression, other.RenderExpression); + return ExpressionComparer.Default.EqualsAfterCapturesEvaluated(this.RenderExpression, other.RenderExpression); } else { diff --git a/src/Moq/Matchers/ExpressionMatcher.cs b/src/Moq/Matchers/ExpressionMatcher.cs index feefc2181..ecfed098b 100644 --- a/src/Moq/Matchers/ExpressionMatcher.cs +++ b/src/Moq/Matchers/ExpressionMatcher.cs @@ -19,7 +19,7 @@ public ExpressionMatcher(Expression expression) public bool Matches(object argument, Type parameterType) { return argument is Expression valueExpression - && ExpressionComparer.Default.Equals(this.expression, valueExpression); + && ExpressionComparer.Default.EqualsAfterCapturesEvaluated(this.expression, valueExpression); } public void SetupEvaluatedSuccessfully(object argument, Type parameterType) diff --git a/src/Moq/MethodExpectation.cs b/src/Moq/MethodExpectation.cs index 7473c2135..a89a73ae4 100644 --- a/src/Moq/MethodExpectation.cs +++ b/src/Moq/MethodExpectation.cs @@ -240,7 +240,7 @@ public override bool Equals(Expectation obj) { for (int j = 0, nj = e1.Expressions.Count; j < nj; ++j) { - if (!ExpressionComparer.Default.Equals(e1.Expressions[j], e2.Expressions[j])) + if (!ExpressionComparer.Default.EqualsAfterCapturesEvaluated(e1.Expressions[j], e2.Expressions[j])) { return false; } @@ -250,7 +250,7 @@ public override bool Equals(Expectation obj) } } - if (!ExpressionComparer.Default.Equals(this.partiallyEvaluatedArguments[i], other.partiallyEvaluatedArguments[i])) + if (!ExpressionComparer.Default.EqualsAfterCapturesEvaluated(this.partiallyEvaluatedArguments[i], other.partiallyEvaluatedArguments[i])) { return false; } diff --git a/src/Moq/StubbedPropertiesSetup.cs b/src/Moq/StubbedPropertiesSetup.cs index c44cdd3a7..949d20443 100644 --- a/src/Moq/StubbedPropertiesSetup.cs +++ b/src/Moq/StubbedPropertiesSetup.cs @@ -90,7 +90,7 @@ public PropertyAccessorExpectation(Mock mock) public override bool Equals(Expectation other) { - return other is PropertyAccessorExpectation pae && ExpressionComparer.Default.Equals(this.expression, pae.expression); + return other is PropertyAccessorExpectation pae && ExpressionComparer.Default.EqualsAfterCapturesEvaluated(this.expression, pae.expression); } public override int GetHashCode()