Skip to content

Commit

Permalink
Merge pull request #647 from stakx/expression-compiler
Browse files Browse the repository at this point in the history
Add extensibility point for LINQ expression tree compilation
  • Loading branch information
stakx authored Jul 26, 2018
2 parents 9ca1644 + 90dd72a commit 20221fe
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 9 deletions.
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

0 comments on commit 20221fe

Please sign in to comment.