From 11e01dd9ec88a9070b6845bc171aa3e18616f14c Mon Sep 17 00:00:00 2001 From: Israel Lot Date: Thu, 9 Jun 2022 23:44:36 -0300 Subject: [PATCH 1/2] Allow default values for generic parameters generalize default expressions for generic methods --- src/DynamicExpresso.Core/Parsing/Parser.cs | 38 ++++++++++++++++++- .../MemberInvocationTest.cs | 28 ++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/DynamicExpresso.Core/Parsing/Parser.cs b/src/DynamicExpresso.Core/Parsing/Parser.cs index 5c12a1d8..fa3a1c30 100644 --- a/src/DynamicExpresso.Core/Parsing/Parser.cs +++ b/src/DynamicExpresso.Core/Parsing/Parser.cs @@ -2004,6 +2004,37 @@ private static MethodData[] FindBestMethod(IEnumerable methods, Expr return applicable; } + private static Type GetConcreteTypeForGenericMethod(Type type, List promotedArgs, MethodData method) + { + if (type.IsGenericType) + { + //Generic type + var genericArguments = type.GetGenericArguments(); + var concreteTypeParameters = new Type[genericArguments.Length]; + + for (var i = 0; i < genericArguments.Length; i++) + { + concreteTypeParameters[i] = GetConcreteTypeForGenericMethod(genericArguments[i], promotedArgs,method); + } + + return type.GetGenericTypeDefinition().MakeGenericType(concreteTypeParameters); + } + else if (type.ContainsGenericParameters) + { + //T case + //try finding an actual parameter for the generic + for (var i = 0; i < promotedArgs.Count; i++) + { + if (method.Parameters[i].ParameterType == type) + { + return promotedArgs[i].Type; + } + } + } + + return type;//already a concrete type + } + private static bool CheckIfMethodIsApplicableAndPrepareIt(MethodData method, Expression[] args) { if (method.Parameters.Count(y => !y.HasDefaultValue && !HasParamsArrayType(y)) > args.Length) @@ -2111,7 +2142,12 @@ private static bool CheckIfMethodIsApplicableAndPrepareIt(MethodData method, Exp promotedArgs.AddRange(method.Parameters.Skip(promotedArgs.Count).Select(x => { if (x.HasDefaultValue) - return Expression.Constant(x.DefaultValue, x.ParameterType); + { + var parameterType = GetConcreteTypeForGenericMethod(x.ParameterType, promotedArgs, method); + + return Expression.Constant(x.DefaultValue, parameterType); + } + if (HasParamsArrayType(x)) { diff --git a/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs b/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs index feb4d021..6558c226 100644 --- a/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs +++ b/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs @@ -322,6 +322,14 @@ public void Method_with_generic_param() Assert.AreEqual(x.MethodWithGenericParam(y, y), target.Eval("x.MethodWithGenericParam(y, y)", parameters)); Assert.AreEqual(x.MethodWithGenericParam(y, z), target.Eval("x.MethodWithGenericParam(y, z)", parameters)); Assert.AreEqual(x.MethodWithGenericParam(y, w), target.Eval("x.MethodWithGenericParam(y, w)", parameters)); + + Assert.AreEqual(x.MethodWithGenericParamAndDefault(y,y), target.Eval("x.MethodWithGenericParamAndDefault(y,y)", parameters)); + Assert.AreEqual(x.MethodWithGenericParamAndDefault(y), target.Eval("x.MethodWithGenericParamAndDefault(y)", parameters)); + Assert.AreEqual(x.MethodWithGenericParamAndDefault1Levels(y), target.Eval("x.MethodWithGenericParamAndDefault1Levels(y)", parameters)); + Assert.AreEqual(x.MethodWithGenericParamAndDefault2Levels(y), target.Eval("x.MethodWithGenericParamAndDefault2Levels(y)", parameters)); + Assert.AreEqual(x.MethodWithGenericParamAndDefault2Levels(y, w), target.Eval("x.MethodWithGenericParamAndDefault2Levels(y, w)", parameters)); + + } [Test] @@ -559,6 +567,26 @@ public string MethodWithGenericParam(string a, T p) return string.Format("{0} {1}", a, p); } + public T MethodWithGenericParamAndDefault(T a, T b = default) + { + return a; + } + + public T MethodWithGenericParamAndDefault1Levels(T a, List b = default) + { + return a; + } + + public T MethodWithGenericParamAndDefault2Levels(T a, List> b = default) + { + return a; + } + + public T MethodWithGenericParamAndDefault2Levels(T a, T2 b, List c = default, List> d = default) + { + return a; + } + public string MethodWithOptionalParam(string param1, string param2 = "2", string param3 = "3") { return string.Format("{0} {1} {2}", param1, param2, param3); From baee57bf2d492677605191a448a1784e1ed6d75b Mon Sep 17 00:00:00 2001 From: Israel Lot Date: Wed, 3 Jun 2026 16:12:39 -0300 Subject: [PATCH 2/2] Fix string-left relational operator binding --- src/DynamicExpresso.Core/Parsing/Parser.cs | 70 ++++++- .../DynamicExpresso.UnitTest/OperatorsTest.cs | 196 ++++++++++++++++++ 2 files changed, 262 insertions(+), 4 deletions(-) diff --git a/src/DynamicExpresso.Core/Parsing/Parser.cs b/src/DynamicExpresso.Core/Parsing/Parser.cs index faa04455..6ef2aecb 100644 --- a/src/DynamicExpresso.Core/Parsing/Parser.cs +++ b/src/DynamicExpresso.Core/Parsing/Parser.cs @@ -1996,7 +1996,7 @@ private Expression GenerateNotEqual(Expression left, Expression right) private Expression GenerateGreaterThan(Expression left, Expression right) { - if (left.Type == typeof(string)) + if (left.Type == typeof(string) && right.Type == typeof(string)) { return Expression.GreaterThan( GenerateStaticMethodCall("Compare", left, right), @@ -2008,7 +2008,7 @@ private Expression GenerateGreaterThan(Expression left, Expression right) private Expression GenerateGreaterThanEqual(Expression left, Expression right) { - if (left.Type == typeof(string)) + if (left.Type == typeof(string) && right.Type == typeof(string)) { return Expression.GreaterThanOrEqual( GenerateStaticMethodCall("Compare", left, right), @@ -2020,7 +2020,7 @@ private Expression GenerateGreaterThanEqual(Expression left, Expression right) private Expression GenerateLessThan(Expression left, Expression right) { - if (left.Type == typeof(string)) + if (left.Type == typeof(string) && right.Type == typeof(string)) { return Expression.LessThan( GenerateStaticMethodCall("Compare", left, right), @@ -2033,7 +2033,7 @@ private Expression GenerateLessThan(Expression left, Expression right) private Expression GenerateLessThanEqual(Expression left, Expression right) { - if (left.Type == typeof(string)) + if (left.Type == typeof(string) && right.Type == typeof(string)) { return Expression.LessThanOrEqual( GenerateStaticMethodCall("Compare", left, right), @@ -2081,6 +2081,10 @@ private Expression GenerateBinary(ExpressionType binaryType, Expression left, Ex } var applicableMethod = FindBinaryOperator(opName, left, right); + if (applicableMethod == null) + { + applicableMethod = FindBinaryOperatorWithImplicitConversion(opName, ref left, ref right); + } MethodInfo operatorMethod = null; if (applicableMethod != null) @@ -2175,6 +2179,64 @@ private MethodData FindBinaryOperator(string operatorName, Expression left, Expr return userDefinedOperator; } + private MethodData FindBinaryOperatorWithImplicitConversion(string operatorName, ref Expression left, ref Expression right) + { + if (operatorName == null) + return null; + + var leftType = left.Type; + var rightType = right.Type; + var convertedLeft = TryGenerateImplicitConversion(left, rightType); + var leftConvertedOperator = convertedLeft != null + ? FindBinaryOperator(operatorName, convertedLeft, right) + : null; + + var convertedRight = TryGenerateImplicitConversion(right, leftType); + var rightConvertedOperator = convertedRight != null + ? FindBinaryOperator(operatorName, left, convertedRight) + : null; + + if (leftConvertedOperator != null && rightConvertedOperator != null && !ReferenceEquals(leftConvertedOperator.MethodBase, rightConvertedOperator.MethodBase)) + { + throw ParseException.Create(_token.pos, ErrorMessages.AmbiguousBinaryOperatorInvocation, operatorName, TypeUtils.GetTypeName(leftType), TypeUtils.GetTypeName(rightType)); + } + + if (leftConvertedOperator != null) + { + left = convertedLeft; + return leftConvertedOperator; + } + + if (rightConvertedOperator != null) + { + right = convertedRight; + return rightConvertedOperator; + } + + return null; + } + + private static Expression TryGenerateImplicitConversion(Expression expression, Type type) + { + if (!HasImplicitConversion(expression.Type, type)) + return null; + + return Expression.Convert(expression, type); + } + + private static bool HasImplicitConversion(Type sourceType, Type targetType) + { + return HasImplicitConversionOperator(sourceType, sourceType, targetType) || + HasImplicitConversionOperator(targetType, sourceType, targetType); + } + + private static bool HasImplicitConversionOperator(Type declaringType, Type sourceType, Type targetType) + { + return declaringType.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy) + .Any(method => method.Name == "op_Implicit" && method.ReturnType == targetType && + method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == sourceType); + } + private static Expression GenerateStringConcat(Expression left, Expression right) { var leftObj = GenerateStringConcatOperand(left); diff --git a/test/DynamicExpresso.UnitTest/OperatorsTest.cs b/test/DynamicExpresso.UnitTest/OperatorsTest.cs index 69893203..418ee7c6 100644 --- a/test/DynamicExpresso.UnitTest/OperatorsTest.cs +++ b/test/DynamicExpresso.UnitTest/OperatorsTest.cs @@ -858,6 +858,202 @@ public void Can_mix_overloaded_operators() Assert.That(target.Eval("(x + z) == \"13\"", new Parameter("z", z)), Is.True); } + [TestCase("m < \"60\"", true)] + [TestCase("m <= \"50\"", true)] + [TestCase("m > \"40\"", true)] + [TestCase("m >= \"50\"", true)] + [TestCase("\"40\" < m", true)] + [TestCase("\"50\" <= m", true)] + [TestCase("\"60\" > m", true)] + [TestCase("\"50\" >= m", true)] + public void Can_use_string_relational_operators_on_custom_type(string expression, bool expected) + { + var target = new Interpreter() + .SetVariable("m", new MoneyWithStringRelationalOperators(50)); + + Assert.That(target.Eval(expression), Is.EqualTo(expected)); + } + + [TestCase("m < \"60\"", true)] + [TestCase("m <= \"50\"", true)] + [TestCase("m > \"40\"", true)] + [TestCase("m >= \"50\"", true)] + [TestCase("\"40\" < m", true)] + [TestCase("\"50\" <= m", true)] + [TestCase("\"60\" > m", true)] + [TestCase("\"50\" >= m", true)] + public void Can_use_implicit_conversion_fallback_for_string_relational_operators_on_custom_type(string expression, bool expected) + { + var target = new Interpreter() + .SetVariable("m", new MoneyWithImplicitConversionOnlyRelationalOperators(50)); + + Assert.That(target.Eval(expression), Is.EqualTo(expected)); + } + + [TestCase("m == \"50\"", true)] + [TestCase("m != \"10\"", true)] + [TestCase("\"50\" == m", true)] + [TestCase("\"10\" != m", true)] + public void Can_use_string_equality_operators_on_custom_type(string expression, bool expected) + { + var target = new Interpreter() + .SetVariable("m", new MoneyWithStringRelationalOperators(50)); + + Assert.That(target.Eval(expression), Is.EqualTo(expected)); + } + + private sealed class MoneyWithStringRelationalOperators + { + private readonly decimal _amount; + + public MoneyWithStringRelationalOperators(decimal amount) + { + _amount = amount; + } + + public static implicit operator MoneyWithStringRelationalOperators(string value) + { + return new MoneyWithStringRelationalOperators(decimal.Parse(value)); + } + + public static bool operator <(MoneyWithStringRelationalOperators left, string right) + { + return left._amount < decimal.Parse(right); + } + + public static bool operator >(MoneyWithStringRelationalOperators left, string right) + { + return left._amount > decimal.Parse(right); + } + + public static bool operator <=(MoneyWithStringRelationalOperators left, string right) + { + return left._amount <= decimal.Parse(right); + } + + public static bool operator >=(MoneyWithStringRelationalOperators left, string right) + { + return left._amount >= decimal.Parse(right); + } + + public static bool operator <(string left, MoneyWithStringRelationalOperators right) + { + return decimal.Parse(left) < right._amount; + } + + public static bool operator >(string left, MoneyWithStringRelationalOperators right) + { + return decimal.Parse(left) > right._amount; + } + + public static bool operator <=(string left, MoneyWithStringRelationalOperators right) + { + return decimal.Parse(left) <= right._amount; + } + + public static bool operator >=(string left, MoneyWithStringRelationalOperators right) + { + return decimal.Parse(left) >= right._amount; + } + + public static bool operator ==(MoneyWithStringRelationalOperators left, string right) + { + return !ReferenceEquals(left, null) && left._amount == decimal.Parse(right); + } + + public static bool operator !=(MoneyWithStringRelationalOperators left, string right) + { + return !(left == right); + } + + public static bool operator ==(string left, MoneyWithStringRelationalOperators right) + { + return !ReferenceEquals(right, null) && decimal.Parse(left) == right._amount; + } + + public static bool operator !=(string left, MoneyWithStringRelationalOperators right) + { + return !(left == right); + } + + public override bool Equals(object obj) + { + var money = obj as MoneyWithStringRelationalOperators; + return money != null && _amount == money._amount; + } + + public override int GetHashCode() + { + return _amount.GetHashCode(); + } + } + + private sealed class MoneyWithImplicitConversionOnlyRelationalOperators + { + private readonly decimal _amount; + + public MoneyWithImplicitConversionOnlyRelationalOperators(decimal amount) + { + _amount = amount; + } + + public static implicit operator MoneyWithImplicitConversionOnlyRelationalOperators(string value) + { + return new MoneyWithImplicitConversionOnlyRelationalOperators(decimal.Parse(value)); + } + + public static bool operator <(MoneyWithImplicitConversionOnlyRelationalOperators left, string right) + { + return left._amount < decimal.Parse(right); + } + + public static bool operator >(MoneyWithImplicitConversionOnlyRelationalOperators left, string right) + { + return left._amount > decimal.Parse(right); + } + + public static bool operator <=(MoneyWithImplicitConversionOnlyRelationalOperators left, string right) + { + return left._amount <= decimal.Parse(right); + } + + public static bool operator >=(MoneyWithImplicitConversionOnlyRelationalOperators left, string right) + { + return left._amount >= decimal.Parse(right); + } + + public static bool operator <(MoneyWithImplicitConversionOnlyRelationalOperators left, MoneyWithImplicitConversionOnlyRelationalOperators right) + { + return left._amount < right._amount; + } + + public static bool operator >(MoneyWithImplicitConversionOnlyRelationalOperators left, MoneyWithImplicitConversionOnlyRelationalOperators right) + { + return left._amount > right._amount; + } + + public static bool operator <=(MoneyWithImplicitConversionOnlyRelationalOperators left, MoneyWithImplicitConversionOnlyRelationalOperators right) + { + return left._amount <= right._amount; + } + + public static bool operator >=(MoneyWithImplicitConversionOnlyRelationalOperators left, MoneyWithImplicitConversionOnlyRelationalOperators right) + { + return left._amount >= right._amount; + } + + public override bool Equals(object obj) + { + var money = obj as MoneyWithImplicitConversionOnlyRelationalOperators; + return money != null && _amount == money._amount; + } + + public override int GetHashCode() + { + return _amount.GetHashCode(); + } + } + [Test] public void Throw_an_exception_if_a_custom_type_doesnt_define_equal_operator()