Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extensibility point for LINQ expression tree compilation #647

Merged
merged 3 commits into from
Jul 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1

## Unreleased

#### Added

* `ExpressionCompiler`: An extensibility point for setting up alternate LINQ expression tree compilation strategies (@stakx, #647)

#### Fixed

* More precise `out` parameter detection for mocking COM interfaces with `[in,out]` parameters (@koutinho, #645)
Expand Down
2 changes: 1 addition & 1 deletion src/Moq/CaptureMatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private static Predicate<T> CreatePredicate(Action<T> captureCallback)

private static Predicate<T> CreatePredicate(Action<T> captureCallback, Expression<Func<T, bool>> predicate)
{
var predicateDelegate = predicate.Compile();
var predicateDelegate = predicate.CompileUsingExpressionCompiler();
return value =>
{
var matches = predicateDelegate.Invoke(value);
Expand Down
64 changes: 64 additions & 0 deletions src/Moq/DefaultExpressionCompiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//Copyright (c) 2007. Clarius Consulting, Manas Technology Solutions, InSTEDD
//https://github.com/moq/moq4
//All rights reserved.
//
//Redistribution and use in source and binary forms,
//with or without modification, are permitted provided
//that the following conditions are met:
//
// * Redistributions of source code must retain the
// above copyright notice, this list of conditions and
// the following disclaimer.
//
// * Redistributions in binary form must reproduce
// the above copyright notice, this list of conditions
// and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Clarius Consulting, Manas Technology Solutions or InSTEDD nor the
// names of its contributors may be used to endorse
// or promote products derived from this software
// without specific prior written permission.
//
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
//CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
//INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
//MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
//SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
//WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
//OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
//SUCH DAMAGE.
//
//[This is the BSD license, see
// http://www.opensource.org/licenses/bsd-license.php]

using System;
using System.Linq.Expressions;

namespace Moq
{
internal sealed class DefaultExpressionCompiler : ExpressionCompiler
{
new public static readonly DefaultExpressionCompiler Instance = new DefaultExpressionCompiler();

private DefaultExpressionCompiler()
{
}

public override Delegate Compile(LambdaExpression expression)
{
return expression.Compile();
}

public override TDelegate Compile<TDelegate>(Expression<TDelegate> expression)
{
return expression.Compile();
}
}
}
2 changes: 1 addition & 1 deletion src/Moq/Evaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ private static Expression Evaluate(Expression e)
return e;
}
LambdaExpression lambda = Expression.Lambda(e);
Delegate fn = lambda.Compile();
Delegate fn = lambda.CompileUsingExpressionCompiler();
return Expression.Constant(fn.DynamicInvoke(null), e.Type);
}
}
Expand Down
91 changes: 91 additions & 0 deletions src/Moq/ExpressionCompiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//Copyright (c) 2007. Clarius Consulting, Manas Technology Solutions, InSTEDD
//https://github.com/moq/moq4
//All rights reserved.
//
//Redistribution and use in source and binary forms,
//with or without modification, are permitted provided
//that the following conditions are met:
//
// * Redistributions of source code must retain the
// above copyright notice, this list of conditions and
// the following disclaimer.
//
// * Redistributions in binary form must reproduce
// the above copyright notice, this list of conditions
// and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Clarius Consulting, Manas Technology Solutions or InSTEDD nor the
// names of its contributors may be used to endorse
// or promote products derived from this software
// without specific prior written permission.
//
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
//CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
//INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
//MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
//SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
//WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
//OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
//SUCH DAMAGE.
//
//[This is the BSD license, see
// http://www.opensource.org/licenses/bsd-license.php]

using System;
using System.ComponentModel;
using System.Linq.Expressions;

namespace Moq
{
/// <summary>
/// An <see cref="ExpressionCompiler"/> compiles LINQ expression trees (<see cref="Expression"/>) to delegates.
/// Whenever Moq needs to compile an expression tree, it uses the instance set up by <see cref="ExpressionCompiler.Instance"/>.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public abstract class ExpressionCompiler
{
private static ExpressionCompiler instance = DefaultExpressionCompiler.Instance;

/// <summary>
/// The default <see cref="ExpressionCompiler"/> instance, which simply delegates to the framework's <see cref="LambdaExpression.Compile"/>.
/// </summary>
public static ExpressionCompiler Default => DefaultExpressionCompiler.Instance;

/// <summary>
/// Gets or sets the <see cref="ExpressionCompiler"/> instance that Moq uses to compile <see cref="Expression"/> (LINQ expression trees).
/// Defaults to <see cref="Default"/>.
/// </summary>
public static ExpressionCompiler Instance
{
get => instance;
set => instance = value ?? throw new ArgumentNullException(nameof(value));
}

/// <summary>
/// Initializes a new instance of the <see cref="ExpressionCompiler"/> class.
/// </summary>
protected ExpressionCompiler()
{
}

/// <summary>
/// Compiles the specified LINQ expression tree.
/// </summary>
/// <param name="expression">The LINQ expression tree that should be compiled.</param>
public abstract Delegate Compile(LambdaExpression expression);

/// <summary>
/// Compiles the specified LINQ expression tree.
/// </summary>
/// <typeparam name="TDelegate">The type of delegate to which the expression will be compiled.</typeparam>
/// <param name="expression">The LINQ expression tree that should be compiled.</param>
public abstract TDelegate Compile<TDelegate>(Expression<TDelegate> expression) where TDelegate : Delegate;
}
}
16 changes: 15 additions & 1 deletion src/Moq/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ namespace Moq
{
internal static class ExpressionExtensions
{
internal static Delegate CompileUsingExpressionCompiler(this LambdaExpression expression)
{
// Expression trees are not compiled directly.
// The indirection via an ExpressionCompiler allows users to plug a different expression compiler.
return ExpressionCompiler.Instance.Compile(expression);
}

internal static TDelegate CompileUsingExpressionCompiler<TDelegate>(this Expression<TDelegate> expression) where TDelegate : Delegate
{
// Expression trees are not compiled directly.
// The indirection via an ExpressionCompiler allows users to plug a different expression compiler.
return ExpressionCompiler.Instance.Compile(expression);
}

/// <summary>
/// Converts the body of the lambda expression into the <see cref="PropertyInfo"/> referenced by it.
/// </summary>
Expand Down Expand Up @@ -141,7 +155,7 @@ private static bool PartialMatcherAwareEval_ShouldEvaluate(Expression expression
// Evaluate everything but matchers:
using (var context = new FluentMockContext())
{
Expression.Lambda<Action>(expression).Compile().Invoke();
Expression.Lambda<Action>(expression).CompileUsingExpressionCompiler().Invoke();
return context.LastMatch == null;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Moq/It.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public static TValue IsNotNull<TValue>()
public static TValue Is<TValue>(Expression<Func<TValue, bool>> match)
{
return Match<TValue>.Create(
value => match.Compile().Invoke(value),
value => match.CompileUsingExpressionCompiler().Invoke(value),
() => It.Is<TValue>(match));
}

Expand Down
2 changes: 1 addition & 1 deletion src/Moq/Linq/MockQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public TResult Execute<TResult>(Expression expression)
var replaced = new MockSetupsBuilder(this.underlyingCreateMocks).Visit(expression);

var lambda = Expression.Lambda<Func<TResult>>(replaced);
return lambda.Compile().Invoke();
return lambda.CompileUsingExpressionCompiler().Invoke();
}

public IEnumerator<T> GetEnumerator()
Expand Down
4 changes: 2 additions & 2 deletions src/Moq/MatcherFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public static IMatcher CreateMatcher(Expression expression, bool isParams)
// Try to determine if invocation is to a matcher.
using (var context = new FluentMockContext())
{
Expression.Lambda<Action>(call).Compile().Invoke();
Expression.Lambda<Action>(call).CompileUsingExpressionCompiler().Invoke();

if (context.LastMatch != null)
{
Expand All @@ -110,7 +110,7 @@ public static IMatcher CreateMatcher(Expression expression, bool isParams)
// Try to determine if invocation is to a matcher.
using (var context = new FluentMockContext())
{
Expression.Lambda<Action>((MemberExpression)expression).Compile().Invoke();
Expression.Lambda<Action>((MemberExpression)expression).CompileUsingExpressionCompiler().Invoke();
if (context.LastMatch != null)
{
return context.LastMatch;
Expand Down
2 changes: 1 addition & 1 deletion src/Moq/Mock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,7 @@ private static Mock GetTargetMock(Expression fluentExpression, Mock mock)
var targetExpression = FluentMockVisitor.Accept(fluentExpression, mock);
var targetLambda = Expression.Lambda<Func<Mock>>(Expression.Convert(targetExpression, typeof(Mock)));

var targetObject = targetLambda.Compile()();
var targetObject = targetLambda.CompileUsingExpressionCompiler()();
return targetObject;
}

Expand Down
1 change: 1 addition & 0 deletions src/Moq/Moq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<SignAssembly>true</SignAssembly>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel>
<LangVersion>7.3</LangVersion>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'net45' ">
Expand Down
2 changes: 1 addition & 1 deletion src/Moq/Protected/ProtectedMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ private static Action<T> GetSetterExpression(PropertyInfo property, Expression v

return Expression.Lambda<Action<T>>(
Expression.Call(param, property.GetSetMethod(true), value),
param).Compile();
param).CompileUsingExpressionCompiler();
}

private static void ThrowIfMemberMissing(string memberName, MemberInfo member)
Expand Down