Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 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.TryGetImplicitConversionOperatorMethod(baseType, targetType, out _);
}

private static ConstantExpression ParseEnumToConstantExpression(int pos, Type leftType, ConstantExpression constantExpr)
Expand Down Expand Up @@ -1525,7 +1514,7 @@
{
if (!propertyNames.Add(propName!))
{
throw ParseError(exprPos, Res.DuplicateIdentifier, propName);

Check warning on line 1517 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Windows: Build and Tests

Possible null reference argument for parameter 'args' in 'Exception ExpressionParser.ParseError(int pos, string format, params object[] args)'.

Check warning on line 1517 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Possible null reference argument for parameter 'args' in 'Exception ExpressionParser.ParseError(int pos, string format, params object[] args)'.
}

properties.Add(new DynamicProperty(propName!, expr.Type));
Expand Down
5 changes: 5 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,11 @@ public ExpressionPromoter(ParsingConfig config)
return sourceExpression;
}

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

return null;
}
}
27 changes: 27 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,31 @@ public static bool IsDictionary(Type? type)
TryFindGenericType(typeof(IReadOnlyDictionary<,>), type, out _);
#endif
}

/// <summary>
/// Check for implicit conversion operators (op_Implicit) from returnType to type.
/// Look for op_Implicit on the source type or the target type.
/// </summary>
public static bool TryGetImplicitConversionOperatorMethod(Type returnType, Type type, [NotNullWhen(true)] out MethodBase? implicitOperator)
{
const string methodName = "op_Implicit";

implicitOperator = Find(returnType) ?? Find(type);
return implicitOperator != null;

MethodBase? Find(Type searchType)
{
return searchType.GetMethods(BindingFlags.Public | BindingFlags.Static)
.FirstOrDefault(m =>
{
if (m.Name != methodName || m.ReturnType != type)
{
return false;
}

var parameters = m.GetParameters();
return parameters.Length == 1 && parameters[0].ParameterType == returnType;
});
}
}
}
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
Loading