Skip to content

Commit b939626

Browse files
CopilotStefH
andauthored
Support implicit operators in method argument matching (#977)
* Initial plan * Add implicit operator support in ExpressionPromoter and tests Agent-Logs-Url: https://github.com/zzzprojects/System.Linq.Dynamic.Core/sessions/82ed2f4e-0c76-407c-a330-d4043e96162f Co-authored-by: StefH <249938+StefH@users.noreply.github.com> * Remove coverage file from tracking, add to gitignore Agent-Logs-Url: https://github.com/zzzprojects/System.Linq.Dynamic.Core/sessions/82ed2f4e-0c76-407c-a330-d4043e96162f Co-authored-by: StefH <249938+StefH@users.noreply.github.com> * Optimize implicit operator lookup in ExpressionPromoter Agent-Logs-Url: https://github.com/zzzprojects/System.Linq.Dynamic.Core/sessions/82ed2f4e-0c76-407c-a330-d4043e96162f Co-authored-by: StefH <249938+StefH@users.noreply.github.com> * Refactor: move implicit operator lookup to TypeHelper.TryFindImplicitConversionOperator Agent-Logs-Url: https://github.com/zzzprojects/System.Linq.Dynamic.Core/sessions/3d221836-ae46-4e82-b79f-a67b544ee3af Co-authored-by: StefH <249938+StefH@users.noreply.github.com> * refactor code --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: StefH <249938+StefH@users.noreply.github.com> Co-authored-by: Stef Heyenrath <stef.heyenrath@gmail.com>
1 parent b734bfb commit b939626

File tree

5 files changed

+83
-12
lines changed

5 files changed

+83
-12
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,5 @@ _Pvt_Extensions
238238
/coverage.xml
239239
/dynamic-coverage-*.xml
240240
/test/**/coverage.net8.0.opencover.xml
241+
/test/**/coverage.opencover.xml
241242
.nuget/

src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -680,18 +680,7 @@ private Expression ParseComparisonOperator()
680680

681681
private static bool HasImplicitConversion(Type baseType, Type targetType)
682682
{
683-
var baseTypeHasConversion = baseType.GetMethods(BindingFlags.Public | BindingFlags.Static)
684-
.Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType)
685-
.Any(mi => mi.GetParameters().FirstOrDefault()?.ParameterType == baseType);
686-
687-
if (baseTypeHasConversion)
688-
{
689-
return true;
690-
}
691-
692-
return targetType.GetMethods(BindingFlags.Public | BindingFlags.Static)
693-
.Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType)
694-
.Any(mi => mi.GetParameters().FirstOrDefault()?.ParameterType == baseType);
683+
return TypeHelper.TryGetImplicitConversionOperatorMethod(baseType, targetType, out _);
695684
}
696685

697686
private static ConstantExpression ParseEnumToConstantExpression(int pos, Type leftType, ConstantExpression constantExpr)

src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ public ExpressionPromoter(ParsingConfig config)
135135
return sourceExpression;
136136
}
137137

138+
if (TypeHelper.TryGetImplicitConversionOperatorMethod(returnType, type, out _))
139+
{
140+
return Expression.Convert(sourceExpression, type);
141+
}
142+
138143
return null;
139144
}
140145
}

src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,4 +533,31 @@ public static bool IsDictionary(Type? type)
533533
TryFindGenericType(typeof(IReadOnlyDictionary<,>), type, out _);
534534
#endif
535535
}
536+
537+
/// <summary>
538+
/// Check for implicit conversion operators (op_Implicit) from returnType to type.
539+
/// Look for op_Implicit on the source type or the target type.
540+
/// </summary>
541+
public static bool TryGetImplicitConversionOperatorMethod(Type returnType, Type type, [NotNullWhen(true)] out MethodBase? implicitOperator)
542+
{
543+
const string methodName = "op_Implicit";
544+
545+
implicitOperator = Find(returnType) ?? Find(type);
546+
return implicitOperator != null;
547+
548+
MethodBase? Find(Type searchType)
549+
{
550+
return searchType.GetMethods(BindingFlags.Public | BindingFlags.Static)
551+
.FirstOrDefault(m =>
552+
{
553+
if (m.Name != methodName || m.ReturnType != type)
554+
{
555+
return false;
556+
}
557+
558+
var parameters = m.GetParameters();
559+
return parameters.Length == 1 && parameters[0].ParameterType == returnType;
560+
});
561+
}
562+
}
536563
}

test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq.Dynamic.Core.Config;
44
using System.Linq.Dynamic.Core.CustomTypeProviders;
55
using System.Linq.Dynamic.Core.Exceptions;
6+
using System.Linq.Dynamic.Core.Parser;
67
using System.Linq.Dynamic.Core.Tests.Helpers;
78
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
89
using System.Linq.Dynamic.Core.Tests.TestHelpers;
@@ -270,6 +271,37 @@ public override string ToString()
270271
}
271272
}
272273

274+
[DynamicLinqType]
275+
public static class MyMethodsWithImplicitOperatorSupport
276+
{
277+
public static string UsesMyStructWithImplicitOperator(MyStructWithImplicitOperator myStruct)
278+
{
279+
return myStruct.Value;
280+
}
281+
}
282+
283+
public readonly struct MyStructWithImplicitOperator
284+
{
285+
private readonly string _value;
286+
287+
public MyStructWithImplicitOperator(string value)
288+
{
289+
_value = value;
290+
}
291+
292+
public static implicit operator MyStructWithImplicitOperator(string value)
293+
{
294+
return new MyStructWithImplicitOperator(value);
295+
}
296+
297+
public static implicit operator string(MyStructWithImplicitOperator myStruct)
298+
{
299+
return myStruct._value;
300+
}
301+
302+
public string Value => _value;
303+
}
304+
273305
internal class TestClass794
274306
{
275307
public byte ByteValue { get; set; }
@@ -1517,6 +1549,23 @@ public void DynamicExpressionParser_ParseLambda_With_One_Way_Implicit_Conversion
15171549
Assert.NotNull(lambda);
15181550
}
15191551

1552+
[Fact]
1553+
public void DynamicExpressionParser_ParseLambda_With_Implicit_Operator_In_Method_Argument()
1554+
{
1555+
// Arrange - Method takes a MyStructWithImplicitOperator but we pass a string literal
1556+
var expression = $"{nameof(MyMethodsWithImplicitOperatorSupport)}.{nameof(MyMethodsWithImplicitOperatorSupport.UsesMyStructWithImplicitOperator)}(\"Foo\")";
1557+
1558+
// Act
1559+
var parser = new ExpressionParser(parameters: [], expression, values: [], ParsingConfig.Default);
1560+
var parsedExpression = parser.Parse(typeof(string));
1561+
var lambda = Expression.Lambda<Func<string>>(parsedExpression);
1562+
var method = lambda.Compile();
1563+
var result = method();
1564+
1565+
// Assert
1566+
Assert.Equal("Foo", result);
1567+
}
1568+
15201569
[Fact]
15211570
public void DynamicExpressionParser_ParseLambda_StaticClassWithStaticPropertyWithSameNameAsNormalProperty()
15221571
{

0 commit comments

Comments
 (0)