Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -238,4 +238,5 @@ _Pvt_Extensions
/coverage.xml
/dynamic-coverage-*.xml
/test/**/coverage.net8.0.opencover.xml
/test/**/coverage.opencover.xml
.nuget/
13 changes: 1 addition & 12 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -680,18 +680,7 @@ private Expression ParseComparisonOperator()

private static bool HasImplicitConversion(Type baseType, Type targetType)
{
var baseTypeHasConversion = baseType.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType)
.Any(mi => mi.GetParameters().FirstOrDefault()?.ParameterType == baseType);

if (baseTypeHasConversion)
{
return true;
}

return targetType.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType)
.Any(mi => mi.GetParameters().FirstOrDefault()?.ParameterType == baseType);
return TypeHelper.TryFindImplicitConversionOperator(baseType, targetType, out _);
}

private static ConstantExpression ParseEnumToConstantExpression(int pos, Type leftType, ConstantExpression constantExpr)
Expand Down
6 changes: 6 additions & 0 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ public ExpressionPromoter(ParsingConfig config)
return sourceExpression;
}

// Check for implicit conversion operators (op_Implicit) from returnType to type.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d0cc881. Added TryFindImplicitConversionOperator(Type sourceType, Type targetType, out MethodInfo? implicitOperator) to TypeHelper.cs, updated ExpressionPromoter.Promote and ExpressionParser.HasImplicitConversion to use it.

if (TypeHelper.TryFindImplicitConversionOperator(returnType, type, out _))
{
return Expression.Convert(sourceExpression, type);
}

return null;
}
}
28 changes: 28 additions & 0 deletions src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -533,4 +533,32 @@ public static bool IsDictionary(Type? type)
TryFindGenericType(typeof(IReadOnlyDictionary<,>), type, out _);
#endif
}

public static bool TryFindImplicitConversionOperator(Type sourceType, Type targetType, [NotNullWhen(true)] out MethodInfo? implicitOperator)
{
// Look for op_Implicit on the source type that converts sourceType -> targetType
implicitOperator = sourceType.GetMethods(BindingFlags.Public | BindingFlags.Static)
.FirstOrDefault(m =>
{
if (m.Name != "op_Implicit" || m.ReturnType != targetType) return false;
var parameters = m.GetParameters();
return parameters.Length == 1 && parameters[0].ParameterType == sourceType;
});

if (implicitOperator != null)
{
return true;
}

// Look for op_Implicit on the target type that converts sourceType -> targetType
implicitOperator = targetType.GetMethods(BindingFlags.Public | BindingFlags.Static)
.FirstOrDefault(m =>
{
if (m.Name != "op_Implicit" || m.ReturnType != targetType) return false;
var parameters = m.GetParameters();
return parameters.Length == 1 && parameters[0].ParameterType == sourceType;
});

return implicitOperator != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq.Dynamic.Core.Config;
using System.Linq.Dynamic.Core.CustomTypeProviders;
using System.Linq.Dynamic.Core.Exceptions;
using System.Linq.Dynamic.Core.Parser;
using System.Linq.Dynamic.Core.Tests.Helpers;
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
using System.Linq.Dynamic.Core.Tests.TestHelpers;
Expand Down Expand Up @@ -270,6 +271,37 @@ public override string ToString()
}
}

[DynamicLinqType]
public static class MyMethodsWithImplicitOperatorSupport
{
public static string UsesMyStructWithImplicitOperator(MyStructWithImplicitOperator myStruct)
{
return myStruct.Value;
}
}

public readonly struct MyStructWithImplicitOperator
{
private readonly string _value;

public MyStructWithImplicitOperator(string value)
{
_value = value;
}

public static implicit operator MyStructWithImplicitOperator(string value)
{
return new MyStructWithImplicitOperator(value);
}

public static implicit operator string(MyStructWithImplicitOperator myStruct)
{
return myStruct._value;
}

public string Value => _value;
}

internal class TestClass794
{
public byte ByteValue { get; set; }
Expand Down Expand Up @@ -1517,6 +1549,23 @@ public void DynamicExpressionParser_ParseLambda_With_One_Way_Implicit_Conversion
Assert.NotNull(lambda);
}

[Fact]
public void DynamicExpressionParser_ParseLambda_With_Implicit_Operator_In_Method_Argument()
{
// Arrange - Method takes a MyStructWithImplicitOperator but we pass a string literal
var expression = $"{nameof(MyMethodsWithImplicitOperatorSupport)}.{nameof(MyMethodsWithImplicitOperatorSupport.UsesMyStructWithImplicitOperator)}(\"Foo\")";

// Act
var parser = new ExpressionParser(parameters: [], expression, values: [], ParsingConfig.Default);
var parsedExpression = parser.Parse(typeof(string));
var lambda = Expression.Lambda<Func<string>>(parsedExpression);
var method = lambda.Compile();
var result = method();

// Assert
Assert.Equal("Foo", result);
}

[Fact]
public void DynamicExpressionParser_ParseLambda_StaticClassWithStaticPropertyWithSameNameAsNormalProperty()
{
Expand Down