diff --git a/CHANGELOG.md b/CHANGELOG.md index c7d58247a..1815cf156 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/src/Moq/CaptureMatch.cs b/src/Moq/CaptureMatch.cs index 30870866c..b9db92a20 100644 --- a/src/Moq/CaptureMatch.cs +++ b/src/Moq/CaptureMatch.cs @@ -79,7 +79,7 @@ private static Predicate CreatePredicate(Action captureCallback) private static Predicate CreatePredicate(Action captureCallback, Expression> predicate) { - var predicateDelegate = predicate.Compile(); + var predicateDelegate = predicate.CompileUsingExpressionCompiler(); return value => { var matches = predicateDelegate.Invoke(value); diff --git a/src/Moq/DefaultExpressionCompiler.cs b/src/Moq/DefaultExpressionCompiler.cs new file mode 100644 index 000000000..5f7a3b230 --- /dev/null +++ b/src/Moq/DefaultExpressionCompiler.cs @@ -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(Expression expression) + { + return expression.Compile(); + } + } +} diff --git a/src/Moq/Evaluator.cs b/src/Moq/Evaluator.cs index 37febedbe..7b4347d74 100644 --- a/src/Moq/Evaluator.cs +++ b/src/Moq/Evaluator.cs @@ -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); } } diff --git a/src/Moq/ExpressionCompiler.cs b/src/Moq/ExpressionCompiler.cs new file mode 100644 index 000000000..2fb2148a0 --- /dev/null +++ b/src/Moq/ExpressionCompiler.cs @@ -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 +{ + /// + /// An compiles LINQ expression trees () to delegates. + /// Whenever Moq needs to compile an expression tree, it uses the instance set up by . + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public abstract class ExpressionCompiler + { + private static ExpressionCompiler instance = DefaultExpressionCompiler.Instance; + + /// + /// The default instance, which simply delegates to the framework's . + /// + public static ExpressionCompiler Default => DefaultExpressionCompiler.Instance; + + /// + /// Gets or sets the instance that Moq uses to compile (LINQ expression trees). + /// Defaults to . + /// + public static ExpressionCompiler Instance + { + get => instance; + set => instance = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// Initializes a new instance of the class. + /// + protected ExpressionCompiler() + { + } + + /// + /// Compiles the specified LINQ expression tree. + /// + /// The LINQ expression tree that should be compiled. + public abstract Delegate Compile(LambdaExpression expression); + + /// + /// Compiles the specified LINQ expression tree. + /// + /// The type of delegate to which the expression will be compiled. + /// The LINQ expression tree that should be compiled. + public abstract TDelegate Compile(Expression expression) where TDelegate : Delegate; + } +} diff --git a/src/Moq/ExpressionExtensions.cs b/src/Moq/ExpressionExtensions.cs index abb87a02f..4097a7726 100644 --- a/src/Moq/ExpressionExtensions.cs +++ b/src/Moq/ExpressionExtensions.cs @@ -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(this Expression 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); + } + /// /// Converts the body of the lambda expression into the referenced by it. /// @@ -141,7 +155,7 @@ private static bool PartialMatcherAwareEval_ShouldEvaluate(Expression expression // Evaluate everything but matchers: using (var context = new FluentMockContext()) { - Expression.Lambda(expression).Compile().Invoke(); + Expression.Lambda(expression).CompileUsingExpressionCompiler().Invoke(); return context.LastMatch == null; } diff --git a/src/Moq/It.cs b/src/Moq/It.cs index 094b77707..9147f9a82 100644 --- a/src/Moq/It.cs +++ b/src/Moq/It.cs @@ -98,7 +98,7 @@ public static TValue IsNotNull() public static TValue Is(Expression> match) { return Match.Create( - value => match.Compile().Invoke(value), + value => match.CompileUsingExpressionCompiler().Invoke(value), () => It.Is(match)); } diff --git a/src/Moq/Linq/MockQuery.cs b/src/Moq/Linq/MockQuery.cs index 6c7df0595..0c9fda373 100644 --- a/src/Moq/Linq/MockQuery.cs +++ b/src/Moq/Linq/MockQuery.cs @@ -109,7 +109,7 @@ public TResult Execute(Expression expression) var replaced = new MockSetupsBuilder(this.underlyingCreateMocks).Visit(expression); var lambda = Expression.Lambda>(replaced); - return lambda.Compile().Invoke(); + return lambda.CompileUsingExpressionCompiler().Invoke(); } public IEnumerator GetEnumerator() diff --git a/src/Moq/MatcherFactory.cs b/src/Moq/MatcherFactory.cs index 16406a110..8d051bc41 100644 --- a/src/Moq/MatcherFactory.cs +++ b/src/Moq/MatcherFactory.cs @@ -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(call).Compile().Invoke(); + Expression.Lambda(call).CompileUsingExpressionCompiler().Invoke(); if (context.LastMatch != null) { @@ -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((MemberExpression)expression).Compile().Invoke(); + Expression.Lambda((MemberExpression)expression).CompileUsingExpressionCompiler().Invoke(); if (context.LastMatch != null) { return context.LastMatch; diff --git a/src/Moq/Mock.cs b/src/Moq/Mock.cs index c14876fee..b72587e6b 100644 --- a/src/Moq/Mock.cs +++ b/src/Moq/Mock.cs @@ -852,7 +852,7 @@ private static Mock GetTargetMock(Expression fluentExpression, Mock mock) var targetExpression = FluentMockVisitor.Accept(fluentExpression, mock); var targetLambda = Expression.Lambda>(Expression.Convert(targetExpression, typeof(Mock))); - var targetObject = targetLambda.Compile()(); + var targetObject = targetLambda.CompileUsingExpressionCompiler()(); return targetObject; } diff --git a/src/Moq/Moq.csproj b/src/Moq/Moq.csproj index 16a8c7dd4..d6de425c8 100644 --- a/src/Moq/Moq.csproj +++ b/src/Moq/Moq.csproj @@ -17,6 +17,7 @@ true true 4 + 7.3 diff --git a/src/Moq/Protected/ProtectedMock.cs b/src/Moq/Protected/ProtectedMock.cs index 53435f91c..1213fda26 100644 --- a/src/Moq/Protected/ProtectedMock.cs +++ b/src/Moq/Protected/ProtectedMock.cs @@ -282,7 +282,7 @@ private static Action GetSetterExpression(PropertyInfo property, Expression v return Expression.Lambda>( Expression.Call(param, property.GetSetMethod(true), value), - param).Compile(); + param).CompileUsingExpressionCompiler(); } private static void ThrowIfMemberMissing(string memberName, MemberInfo member)