From dd29c8a58f121b33b3a03f57f697c1f6cd3095ae Mon Sep 17 00:00:00 2001 From: Koen Date: Sun, 2 Aug 2026 01:07:48 +0000 Subject: [PATCH] numeric-only zero check, unambiguous overload lookups --- .../Transformers/ConvertLoopsToLinq.cs | 30 ++++++++++++++---- .../Transformers/ConvertLoopsToLinqTests.cs | 31 +++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/ExpressiveSharp/Transformers/ConvertLoopsToLinq.cs b/src/ExpressiveSharp/Transformers/ConvertLoopsToLinq.cs index 16db26a8..c5279c25 100644 --- a/src/ExpressiveSharp/Transformers/ConvertLoopsToLinq.cs +++ b/src/ExpressiveSharp/Transformers/ConvertLoopsToLinq.cs @@ -420,7 +420,8 @@ private bool TryMatchMinMaxPattern( var method = typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static) .First(m => m.Name == linqMethodName && m.IsGenericMethodDefinition && m.GetParameters().Length == 2 - && m.GetGenericArguments().Length == 2); + && m.GetGenericArguments().Length == 2 + && m.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2); result = Expression.Call( method.MakeGenericMethod(elementType, selectorBody.Type), collection, @@ -461,7 +462,8 @@ private bool TryMatchSelectPattern( var selectMethod = typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static) .First(m => m.Name == "Select" && m.IsGenericMethodDefinition && m.GetParameters().Length == 2 - && m.GetGenericArguments().Length == 2); + && m.GetGenericArguments().Length == 2 + && m.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2); var selectCall = Expression.Call( selectMethod.MakeGenericMethod(elementType, resultElementType), collection, @@ -504,7 +506,8 @@ private bool TryMatchConditionalSelect( var selectMethod = typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static) .First(m => m.Name == "Select" && m.IsGenericMethodDefinition && m.GetParameters().Length == 2 - && m.GetGenericArguments().Length == 2); + && m.GetGenericArguments().Length == 2 + && m.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2); var selectCall = Expression.Call( selectMethod.MakeGenericMethod(elementType, resultElementType), whereCall, @@ -537,9 +540,23 @@ private static bool IsConstant(Expression expr, object? value) private static bool IsNumericZero(Expression expr) { - if (expr is ConstantExpression constant && constant.Value is not null) + if (expr is ConstantExpression { Value: not null } constant) { - return Convert.ToDouble(constant.Value) == 0.0; + return constant.Value switch + { + byte v => v == 0, + sbyte v => v == 0, + short v => v == 0, + ushort v => v == 0, + int v => v == 0, + uint v => v == 0, + long v => v == 0, + ulong v => v == 0, + float v => v == 0f, + double v => v == 0d, + decimal v => v == 0m, + _ => false, + }; } return expr is DefaultExpression; @@ -568,7 +585,8 @@ private static MethodInfo GetEnumerableMethod(string name, Type elementType, boo { // Generic overload with predicate/selector (e.g., Count(IEnumerable, Func)) var method = typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static) - .First(m => m.Name == name && m.IsGenericMethodDefinition && m.GetParameters().Length == 2); + .First(m => m.Name == name && m.IsGenericMethodDefinition && m.GetParameters().Length == 2 + && m.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2); return method.MakeGenericMethod(elementType); } diff --git a/tests/ExpressiveSharp.Tests/Transformers/ConvertLoopsToLinqTests.cs b/tests/ExpressiveSharp.Tests/Transformers/ConvertLoopsToLinqTests.cs index c0e3dc3e..df4db64a 100644 --- a/tests/ExpressiveSharp.Tests/Transformers/ConvertLoopsToLinqTests.cs +++ b/tests/ExpressiveSharp.Tests/Transformers/ConvertLoopsToLinqTests.cs @@ -296,4 +296,35 @@ public void ForEach_Min_ExecutesCorrectly() var result = compiled(new List { 5, 2, 8, 1, 9 }); Assert.AreEqual(1, result); } + + [TestMethod] + public void ForEach_StringAccumulation_ThrowsInvalidOperationException() + { + var items = Expression.Parameter(typeof(List), "items"); + var acc = Expression.Variable(typeof(string), "s"); + var concat = typeof(string).GetMethod(nameof(string.Concat), new[] { typeof(string), typeof(string) })!; + + var loopExpr = BuildForEachLoop(items, acc, Expression.Constant(""), + typeof(string), + (iter, a) => Expression.Assign(a, Expression.Add(a, iter, concat))); + + Assert.ThrowsExactly(() => _sut.Transform(loopExpr)); + } + + [TestMethod] + public void EnumerableOverloadPredicates_MatchExactlyOneMethod() + { + var whereMatches = typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static) + .Count(m => m.Name == "Where" && m.IsGenericMethodDefinition && m.GetParameters().Length == 2 + && m.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2); + + var selectMatches = typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static) + .Count(m => m.Name == "Select" && m.IsGenericMethodDefinition + && m.GetParameters().Length == 2 + && m.GetGenericArguments().Length == 2 + && m.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2); + + Assert.AreEqual(1, whereMatches); + Assert.AreEqual(1, selectMatches); + } }