diff --git a/src/DynamicExpresso.Core/Interpreter.cs b/src/DynamicExpresso.Core/Interpreter.cs index b8fdcff0..445fe777 100644 --- a/src/DynamicExpresso.Core/Interpreter.cs +++ b/src/DynamicExpresso.Core/Interpreter.cs @@ -60,6 +60,11 @@ public Interpreter(InterpreterOptions options) _settings.LambdaExpressions = true; } + if ((options & InterpreterOptions.DetectUsedParameters) == InterpreterOptions.DetectUsedParameters) + { + _settings.DetectUsedParameters = true; + } + _visitors.Add(new DisableReflectionVisitor()); } @@ -366,7 +371,7 @@ public Lambda Parse(string expressionText, params Parameter[] parameters) /// public Lambda Parse(string expressionText, Type expressionType, params Parameter[] parameters) { - return ParseAsLambda(expressionText, expressionType, parameters); + return ParseRawExpression(expressionText, expressionType, parameters).ToLambda(); } [Obsolete("Use ParseAsDelegate(string, params string[])")] @@ -385,8 +390,8 @@ public TDelegate Parse(string expressionText, params string[] paramet /// public TDelegate ParseAsDelegate(string expressionText, params string[] parametersNames) { - var lambda = ParseAs(expressionText, parametersNames); - return lambda.Compile(); + var lambda = ParseRawExpression(expressionText, parametersNames); + return lambda.Compile(); } /// @@ -399,35 +404,14 @@ public TDelegate ParseAsDelegate(string expressionText, params string /// public Expression ParseAsExpression(string expressionText, params string[] parametersNames) { - var lambda = ParseAs(expressionText, parametersNames); - return lambda.LambdaExpression(); - } - - internal LambdaExpression ParseAsExpression(Type delegateType, string expressionText, params string[] parametersNames) - { - var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames); - - // return type is object means that we have no information beforehand - // => we force it to typeof(void) so that no conversion expression is emitted by the parser - // and the actual expression type is preserved - var returnType = delegateInfo.ReturnType; - if (returnType == typeof(object)) - returnType = typeof(void); - - var lambda = ParseAsLambda(expressionText, returnType, delegateInfo.Parameters); - return lambda.LambdaExpression(delegateType); + var lambda = ParseRawExpression(expressionText, parametersNames); + return lambda.LambdaExpression(); } public Lambda ParseAs(string expressionText, params string[] parametersNames) { - return ParseAs(typeof(TDelegate), expressionText, parametersNames); - } - - internal Lambda ParseAs(Type delegateType, string expressionText, params string[] parametersNames) - { - var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames); - - return ParseAsLambda(expressionText, delegateInfo.ReturnType, delegateInfo.Parameters); + var lambda = ParseRawExpression(expressionText, parametersNames); + return lambda.ToLambda(); } #endregion @@ -478,7 +462,14 @@ public IdentifiersInfo DetectIdentifiers(string expression) #region Private methods - private Lambda ParseAsLambda(string expressionText, Type expressionType, Parameter[] parameters) + internal ParseResult ParseRawExpression(string expressionText, params string[] parametersNames) + { + var delegateInfo = ReflectionExtensions.GetDelegateInfo(typeof(TDelegate), parametersNames); + var parseResult = ParseRawExpression(expressionText, delegateInfo.ReturnType, delegateInfo.Parameters); + return new ParseResult(parseResult); + } + + internal ParseResult ParseRawExpression(string expressionText, Type expressionType, Parameter[] parameters) { var arguments = new ParserArguments( expressionText, @@ -491,7 +482,7 @@ private Lambda ParseAsLambda(string expressionText, Type expressionType, Paramet foreach (var visitor in Visitors) expression = visitor.Visit(expression); - var lambda = new Lambda(expression, arguments); + var lambda = new ParseResult(expression, arguments); #if TEST_DetectIdentifiers AssertDetectIdentifiers(lambda); @@ -501,7 +492,7 @@ private Lambda ParseAsLambda(string expressionText, Type expressionType, Paramet } #if TEST_DetectIdentifiers - private void AssertDetectIdentifiers(Lambda lambda) + private void AssertDetectIdentifiers(ParseResult lambda) { var info = DetectIdentifiers(lambda.ExpressionText); diff --git a/src/DynamicExpresso.Core/InterpreterExtensions.cs b/src/DynamicExpresso.Core/InterpreterExtensions.cs new file mode 100644 index 00000000..31ca8353 --- /dev/null +++ b/src/DynamicExpresso.Core/InterpreterExtensions.cs @@ -0,0 +1,68 @@ +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace DynamicExpresso +{ + /// + /// Interpreter extensions. + /// + public static class InterpreterExtensions + { + /// + /// Compiles lambda with used parameters. + /// + public static Delegate Compile(this ParseResult parseResult) + { + return parseResult.LambdaExpression().Compile(); + } + + /// + /// Compiles lambda with declared parameters. + /// + public static TDelegate Compile(this ParseResult parseResult) + { + return parseResult.LambdaExpression().Compile(); + } + + /// + /// Compiles lambda with declared parameters. + /// + public static TDelegate Compile(this ParseResult parseResult) + { + return Compile((ParseResult)parseResult); + } + + /// + /// Convert parse result to a lambda expression with used parameters. + /// + public static LambdaExpression LambdaExpression(this ParseResult parseResult) + { + return Expression.Lambda(parseResult.Expression, parseResult.UsedParameters.Select(_ => _.Expression).ToArray()); + } + + /// + /// Convert parse result to a lambda expression with declared parameters. + /// + public static Expression LambdaExpression(this ParseResult parseResult) + { + return Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.Select(_ => _.Expression).ToArray()); + } + + /// + /// Convert parse result to a lambda expression with declared parameters. + /// + public static Expression LambdaExpression(this ParseResult parseResult) + { + return ((ParseResult)parseResult).LambdaExpression(); + } + + /// + /// Convert parse result to a lambda expression with declared parameters. + /// + public static LambdaExpression LambdaExpression(this ParseResult parseResult, Type delegateType) + { + return Expression.Lambda(delegateType, parseResult.Expression, parseResult.DeclaredParameters.Select(_ => _.Expression).ToArray()); + } + } +} diff --git a/src/DynamicExpresso.Core/InterpreterOptions.cs b/src/DynamicExpresso.Core/InterpreterOptions.cs index 5ed789f3..bd950aaf 100644 --- a/src/DynamicExpresso.Core/InterpreterOptions.cs +++ b/src/DynamicExpresso.Core/InterpreterOptions.cs @@ -31,12 +31,16 @@ public enum InterpreterOptions /// LambdaExpressions = 32, /// - /// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + /// Detect which parameters are actually used in the expression, to minimise the compiled lambda signature. /// - Default = PrimitiveTypes | SystemKeywords | CommonTypes, + DetectUsedParameters = 64, /// - /// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + CaseInsensitive + /// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + DetectUsedParameters /// - DefaultCaseInsensitive = PrimitiveTypes | SystemKeywords | CommonTypes | CaseInsensitive, + Default = PrimitiveTypes | SystemKeywords | CommonTypes | DetectUsedParameters, + /// + /// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + DetectUsedParameters + CaseInsensitive + /// + DefaultCaseInsensitive = PrimitiveTypes | SystemKeywords | CommonTypes | DetectUsedParameters | CaseInsensitive, } } diff --git a/src/DynamicExpresso.Core/Lambda.cs b/src/DynamicExpresso.Core/Lambda.cs index e89edbb8..38e8ccd2 100644 --- a/src/DynamicExpresso.Core/Lambda.cs +++ b/src/DynamicExpresso.Core/Lambda.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -10,47 +10,20 @@ namespace DynamicExpresso /// /// Represents a lambda expression that can be invoked. This class is thread safe. /// - public class Lambda + public class Lambda : ParseResult { - private readonly Expression _expression; - private readonly ParserArguments _parserArguments; + private readonly bool _caseInsensitive; + private readonly StringComparison _keyComparison; private readonly Lazy _delegate; - internal Lambda(Expression expression, ParserArguments parserArguments) + internal Lambda(Expression expression, ParserArguments parserArguments) : base(expression, parserArguments) { - _expression = expression ?? throw new ArgumentNullException(nameof(expression)); - _parserArguments = parserArguments ?? throw new ArgumentNullException(nameof(parserArguments)); - - // Note: I always lazy compile the generic lambda. Maybe in the future this can be a setting because if I generate a typed delegate this compilation is not required. - _delegate = new Lazy(() => - Expression.Lambda(_expression, _parserArguments.UsedParameters.Select(p => p.Expression).ToArray()).Compile()); + _caseInsensitive = parserArguments.Settings.CaseInsensitive; + _keyComparison = parserArguments.Settings.KeyComparison; + _delegate = new Lazy(() => this.Compile()); } - public Expression Expression { get { return _expression; } } - public bool CaseInsensitive { get { return _parserArguments.Settings.CaseInsensitive; } } - public string ExpressionText { get { return _parserArguments.ExpressionText; } } - public Type ReturnType { get { return Expression.Type; } } - - /// - /// Gets the parameters actually used in the expression parsed. - /// - /// The used parameters. - [Obsolete("Use UsedParameters or DeclaredParameters")] - public IEnumerable Parameters { get { return _parserArguments.UsedParameters; } } - - /// - /// Gets the parameters actually used in the expression parsed. - /// - /// The used parameters. - public IEnumerable UsedParameters { get { return _parserArguments.UsedParameters; } } - /// - /// Gets the parameters declared when parsing the expression. - /// - /// The declared parameters. - public IEnumerable DeclaredParameters { get { return _parserArguments.DeclaredParameters; } } - - public IEnumerable Types { get { return _parserArguments.UsedTypes; } } - public IEnumerable Identifiers { get { return _parserArguments.UsedIdentifiers; } } + public bool CaseInsensitive => _caseInsensitive; public object Invoke() { @@ -66,7 +39,7 @@ public object Invoke(IEnumerable parameters) { var args = (from usedParameter in UsedParameters from actualParameter in parameters - where usedParameter.Name.Equals(actualParameter.Name, _parserArguments.Settings.KeyComparison) + where usedParameter.Name.Equals(actualParameter.Name, _keyComparison) select actualParameter.Value) .ToArray(); @@ -116,49 +89,5 @@ private object InvokeWithUsedParameters(object[] orderedArgs) throw; } } - - public override string ToString() - { - return ExpressionText; - } - - /// - /// Generate the given delegate by compiling the lambda expression. - /// - /// The delegate to generate. Delegate parameters must match the one defined when creating the expression, see UsedParameters. - public TDelegate Compile() - { - var lambdaExpression = LambdaExpression(); - return lambdaExpression.Compile(); - } - - [Obsolete("Use Compile()")] - public TDelegate Compile(IEnumerable parameters) - { - var lambdaExpression = Expression.Lambda(_expression, parameters.Select(p => p.Expression).ToArray()); - return lambdaExpression.Compile(); - } - - /// - /// Generate a lambda expression. - /// - /// The lambda expression. - /// The delegate to generate. Delegate parameters must match the one defined when creating the expression, see UsedParameters. - public Expression LambdaExpression() - { - return Expression.Lambda(_expression, DeclaredParameters.Select(p => p.Expression).ToArray()); - } - - internal LambdaExpression LambdaExpression(Type delegateType) - { - var types = delegateType.GetGenericArguments(); - - // return type - types[types.Length - 1] = _expression.Type; - - var genericType = delegateType.GetGenericTypeDefinition(); - var inferredDelegateType = genericType.MakeGenericType(types); - return Expression.Lambda(inferredDelegateType, _expression, DeclaredParameters.Select(p => p.Expression).ToArray()); - } } } diff --git a/src/DynamicExpresso.Core/ParseResult.cs b/src/DynamicExpresso.Core/ParseResult.cs new file mode 100644 index 00000000..818a4fb7 --- /dev/null +++ b/src/DynamicExpresso.Core/ParseResult.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace DynamicExpresso +{ + /// + /// Represents an expression parse result. + /// + public class ParseResult + { + private readonly Expression _expression; + private readonly ParserArguments _parserArguments; + + internal ParseResult(Expression expression, ParserArguments parserArguments) + { + _expression = expression ?? throw new ArgumentNullException(nameof(expression)); + _parserArguments = parserArguments ?? throw new ArgumentNullException(nameof(parserArguments)); + } + + internal ParseResult(ParseResult other) : this(other._expression, other._parserArguments) + { + } + + public string ExpressionText => _parserArguments.ExpressionText; + public virtual Type ReturnType => _expression.Type; + + /// + /// Gets the parsed expression. + /// + /// The expression. + public Expression Expression => _expression; + + /// + /// Gets the parameters actually used in the expression parsed. + /// + /// The used parameters. + [Obsolete("Use UsedParameters or DeclaredParameters")] + public IEnumerable Parameters => UsedParameters; + + /// + /// Gets the parameters actually used in the expression parsed. + /// + /// The used parameters. + public IEnumerable UsedParameters => _parserArguments.UsedParameters; + + /// + /// Gets the parameters declared when parsing the expression. + /// + /// The declared parameters. + public IEnumerable DeclaredParameters => _parserArguments.DeclaredParameters; + + /// + /// Gets the references types in parsed expression. + /// + /// The references types. + public IEnumerable Types => _parserArguments.UsedTypes; + + /// + /// Gets the identifiers in parsed expression. + /// + /// The identifiers. + public IEnumerable Identifiers => _parserArguments.UsedIdentifiers; + + internal Lambda ToLambda() + { + return new Lambda(_expression, _parserArguments); + } + + public override string ToString() + { + return ExpressionText; + } + } + + public class ParseResult : ParseResult + { + internal ParseResult(ParseResult parseResult) : base(parseResult) + { + } + } +} diff --git a/src/DynamicExpresso.Core/ParserArguments.cs b/src/DynamicExpresso.Core/ParserArguments.cs index 0f5d8649..1a1ba384 100644 --- a/src/DynamicExpresso.Core/ParserArguments.cs +++ b/src/DynamicExpresso.Core/ParserArguments.cs @@ -39,6 +39,9 @@ IEnumerable declaredParameters throw new DuplicateParameterException(pe.Name); } } + + if (!Settings.DetectUsedParameters) + _usedParameters = new HashSet(_declaredParameters.Values); } public ParserSettings Settings { get; private set;} diff --git a/src/DynamicExpresso.Core/Parsing/Parser.cs b/src/DynamicExpresso.Core/Parsing/Parser.cs index a2fffa6c..ae6bb1ea 100644 --- a/src/DynamicExpresso.Core/Parsing/Parser.cs +++ b/src/DynamicExpresso.Core/Parsing/Parser.cs @@ -8,6 +8,7 @@ using System.Runtime.CompilerServices; using System.Text; using DynamicExpresso.Exceptions; +using DynamicExpresso.Reflection; using DynamicExpresso.Resources; using Microsoft.CSharp.RuntimeBinder; @@ -3129,7 +3130,26 @@ public override Type Type internal LambdaExpression EvalAs(Type delegateType) { - var lambdaExpr = _interpreter.ParseAsExpression(delegateType, _expressionText, _parameters.Select(p => p.Name).ToArray()); + var parametersNames = _parameters.Select(p => p.Name).ToArray(); + var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames); + + // return type is object means that we have no information beforehand + // => we force it to typeof(void) so that no conversion expression is emitted by the parser + // and the actual expression type is preserved + var returnType = delegateInfo.ReturnType; + if (returnType == typeof(object)) + returnType = typeof(void); + + var lambda = _interpreter.ParseRawExpression(_expressionText, returnType, delegateInfo.Parameters); + + // change delegate return type to the actual type inferred during parsing + var types = delegateType.GetGenericArguments(); + types[types.Length - 1] = lambda.Expression.Type; + + var genericType = delegateType.GetGenericTypeDefinition(); + var inferredDelegateType = genericType.MakeGenericType(types); + var lambdaExpr = Lambda(inferredDelegateType, lambda.Expression, lambda.DeclaredParameters.Select(p => p.Expression).ToArray()); + _type = lambdaExpr.Type; return lambdaExpr; } diff --git a/src/DynamicExpresso.Core/Parsing/ParserSettings.cs b/src/DynamicExpresso.Core/Parsing/ParserSettings.cs index 01e0b0c6..709a659b 100644 --- a/src/DynamicExpresso.Core/Parsing/ParserSettings.cs +++ b/src/DynamicExpresso.Core/Parsing/ParserSettings.cs @@ -31,6 +31,8 @@ public ParserSettings(bool caseInsensitive, bool lateBindObject) DefaultNumberType = DefaultNumberType.Default; LambdaExpressions = false; + + DetectUsedParameters = false; } private ParserSettings(ParserSettings other) : this(other.CaseInsensitive, other.LateBindObject) @@ -42,6 +44,7 @@ private ParserSettings(ParserSettings other) : this(other.CaseInsensitive, other AssignmentOperators = other.AssignmentOperators; DefaultNumberType = other.DefaultNumberType; LambdaExpressions = other.LambdaExpressions; + DetectUsedParameters = other.DetectUsedParameters; } /// @@ -105,5 +108,11 @@ public bool LambdaExpressions get; set; } + + public bool DetectUsedParameters + { + get; + set; + } } } diff --git a/test/DynamicExpresso.UnitTest/GithubIssues.cs b/test/DynamicExpresso.UnitTest/GithubIssues.cs index ba7e65d1..de194441 100644 --- a/test/DynamicExpresso.UnitTest/GithubIssues.cs +++ b/test/DynamicExpresso.UnitTest/GithubIssues.cs @@ -446,6 +446,21 @@ public void GitHub_Issue_205() Assert.AreEqual(-1, interpreter.Eval("(date1 - date2)?.Days")); } + [Test] + public void GitHub_Issue_207() + { + var interpreter = new Interpreter(InterpreterOptions.Default ^ InterpreterOptions.DetectUsedParameters); + var parameter = new Parameter("x", typeof(int)); + var expression = interpreter.Parse("x + 1", parameter).Expression; + + var lambda = interpreter + .SetExpression("value", expression) + .Parse("value + 1", parameter); + + var result = lambda.Invoke(1); + Assert.AreEqual(3, result); + } + [Test] public void GitHub_Issue_217() { diff --git a/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs b/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs index ba021abb..5ae41ef6 100644 --- a/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs +++ b/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs @@ -231,6 +231,18 @@ public void Parent_scope_variable() Assert.AreEqual(new[] { 4, 5, 6 }, results); } + [Test] + public void Unused_lambda_parameter() + { + var target = new Interpreter(_options); + var list = new List { 1, 2, 3 }; + target.SetVariable("myList", list); + var results = target.Eval>("myList.Select(i => 4)"); + + Assert.AreEqual(3, results.Count()); + Assert.AreEqual(new[] { 4, 4, 4 }, results); + } + [Test] public void Lambda_with_multiple_params() { diff --git a/test/DynamicExpresso.UnitTest/NullableTest.cs b/test/DynamicExpresso.UnitTest/NullableTest.cs index 7348f827..fe885d51 100644 --- a/test/DynamicExpresso.UnitTest/NullableTest.cs +++ b/test/DynamicExpresso.UnitTest/NullableTest.cs @@ -241,45 +241,24 @@ public void NullableDateTimeOffset_DatetimeOffset() interpreter.SetVariable("c", c, typeof(DateTimeOffset)); var expectedReturnType = typeof(bool); - var expected = a < b; - var lambda = interpreter.Parse("a < b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); - - expected = a > b; - lambda = interpreter.Parse("a > b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); - - expected = a == b; - lambda = interpreter.Parse("a == b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); - - expected = a != b; - lambda = interpreter.Parse("a != b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); - - expected = b == c; - lambda = interpreter.Parse("b == b"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); - - expected = b != c; - lambda = interpreter.Parse("b != c"); - Assert.AreEqual(expected, lambda.Invoke()); - Assert.AreEqual(expectedReturnType, lambda.ReturnType); - - lambda = interpreter.Parse("a - b"); - Assert.AreEqual(a - b, lambda.Invoke()); - Assert.AreEqual(typeof(TimeSpan?), lambda.ReturnType); + Verify(interpreter, "a < b", a < b); + Verify(interpreter, "a > b", a > b); + Verify(interpreter, "a == b", a == b); + Verify(interpreter, "a != b", a != b); + Verify(interpreter, "b == b", b == b); + Verify(interpreter, "b != c", b != c); + Verify(interpreter, "a - b", a - b); b = null; interpreter.SetVariable("b", b, typeof(DateTimeOffset?)); - lambda = interpreter.Parse("a - b"); - Assert.AreEqual(a - b, lambda.Invoke()); - Assert.AreEqual(typeof(TimeSpan?), lambda.ReturnType); + Verify(interpreter, "a - b", a - b); + } + + private static void Verify(Interpreter interpreter, string expression, T expected) + { + var parsed = interpreter.Parse(expression); + Assert.AreEqual(expected, parsed.Invoke()); + Assert.AreEqual(typeof(T), parsed.Expression.Type); } } }