Skip to content

Commit c969604

Browse files
authored
Merge pull request #1262 from stakx/bugfix/lazy-setup-expr-evaluation
Leave quoted (nested) expressions unchanged when evaluating captured variables
2 parents 6c7e7a1 + dee0746 commit c969604

File tree

6 files changed

+56
-6
lines changed

6 files changed

+56
-6
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
99

1010
#### Fixed
1111

12+
* Regression with lazy evaluation of `It.Is` predicates in setup expressions after updating from 4.13.1 to 4.16.1 (@b3go, #1217)
1213
* Regression with `SetupProperty` where Moq fails to match a property accessor implementation against its definition in an interface (@Naxemar, #1248)
1314
* Difference in behavior when mocking async method using `.Result` vs without (@cyungmann, #1253)
1415

src/Moq/Evaluator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ internal HashSet<Expression> Nominate(Expression expression)
102102

103103
public override Expression Visit(Expression expression)
104104
{
105-
if (expression != null)
105+
if (expression != null && expression.NodeType != ExpressionType.Quote)
106106
{
107107
bool saveCannotBeEvaluated = this.cannotBeEvaluated;
108108
this.cannotBeEvaluated = false;

src/Moq/ExpressionComparer.cs

+19-5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ internal sealed class ExpressionComparer : IEqualityComparer<Expression>
1414
{
1515
public static readonly ExpressionComparer Default = new ExpressionComparer();
1616

17+
[ThreadStatic]
18+
private static int quoteDepth = 0;
19+
1720
private ExpressionComparer()
1821
{
1922
}
@@ -30,15 +33,16 @@ public bool Equals(Expression x, Expression y)
3033
return false;
3134
}
3235

33-
// Before actually comparing two nodes, make sure that captures variables have been
34-
// evaluated to their current values (as we don't want to compare their identities):
36+
// Before actually comparing two nodes, make sure that captured variables have been
37+
// evaluated to their current values (as we don't want to compare their identities).
38+
// But do so only for the main expression; leave quoted (nested) expressions unchanged.
3539

36-
if (x is MemberExpression)
40+
if (x is MemberExpression && ExpressionComparer.quoteDepth == 0)
3741
{
3842
x = x.Apply(EvaluateCaptures.Rewriter);
3943
}
4044

41-
if (y is MemberExpression)
45+
if (y is MemberExpression && ExpressionComparer.quoteDepth == 0)
4246
{
4347
y = y.Apply(EvaluateCaptures.Rewriter);
4448
}
@@ -47,13 +51,23 @@ public bool Equals(Expression x, Expression y)
4751
{
4852
switch (x.NodeType)
4953
{
54+
case ExpressionType.Quote:
55+
ExpressionComparer.quoteDepth++;
56+
try
57+
{
58+
return this.EqualsUnary((UnaryExpression)x, (UnaryExpression)y);
59+
}
60+
finally
61+
{
62+
ExpressionComparer.quoteDepth--;
63+
}
64+
5065
case ExpressionType.Negate:
5166
case ExpressionType.NegateChecked:
5267
case ExpressionType.Not:
5368
case ExpressionType.Convert:
5469
case ExpressionType.ConvertChecked:
5570
case ExpressionType.ArrayLength:
56-
case ExpressionType.Quote:
5771
case ExpressionType.TypeAs:
5872
case ExpressionType.UnaryPlus:
5973
return this.EqualsUnary((UnaryExpression)x, (UnaryExpression)y);

src/Moq/ExpressionExtensions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ private static bool PartialMatcherAwareEval_ShouldEvaluate(Expression expression
448448
#pragma warning disable 618
449449
return expression.NodeType switch
450450
{
451+
ExpressionType.Quote => false,
451452
ExpressionType.Parameter => false,
452453
ExpressionType.Extension => !(expression is MatchExpression),
453454
ExpressionType.Call => !((MethodCallExpression)expression).Method.IsDefined(typeof(MatcherAttribute), true)

src/Moq/Expressions/Visitors/EvaluateCaptures.cs

+5
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,10 @@ protected override Expression VisitMember(MemberExpression node)
3131
return base.VisitMember(node);
3232
}
3333
}
34+
35+
protected override Expression VisitUnary(UnaryExpression node)
36+
{
37+
return node.NodeType == ExpressionType.Quote ? node : base.VisitUnary(node);
38+
}
3439
}
3540
}

tests/Moq.Tests/Regressions/IssueReportsFixture.cs

+29
Original file line numberDiff line numberDiff line change
@@ -3688,6 +3688,35 @@ public virtual void SecondCall()
36883688

36893689
#endregion
36903690

3691+
#region 1217
3692+
3693+
public class Issue1217
3694+
{
3695+
[Fact]
3696+
public void It_Is_predicates_are_evaluated_lazily()
3697+
{
3698+
var patternKey = "";
3699+
var exeKey = "";
3700+
3701+
var mock = new Mock<ISettingsService>();
3702+
mock.Setup(x => x.GetSetting(It.Is<string>(y => y == patternKey))).Returns(() => patternKey);
3703+
mock.Setup(x => x.GetSetting(It.Is<string>(y => y == exeKey))).Returns(() => exeKey);
3704+
3705+
patternKey = "foo";
3706+
exeKey = "bar";
3707+
3708+
Assert.Equal("foo", mock.Object.GetSetting(patternKey));
3709+
Assert.Equal("bar", mock.Object.GetSetting(exeKey));
3710+
}
3711+
3712+
public interface ISettingsService
3713+
{
3714+
string GetSetting(string key);
3715+
}
3716+
}
3717+
3718+
#endregion
3719+
36913720
#region 1225
36923721

36933722
public class Issue1225

0 commit comments

Comments
 (0)