From 6d468fa0713e42cedb4a8ba8a13b8e5024b1deb1 Mon Sep 17 00:00:00 2001 From: Koen Date: Tue, 2 Jun 2026 15:16:05 +0000 Subject: [PATCH] fixed nested-lambda variable collission --- .../Emitter/ExpressionTreeEmitter.cs | 2 +- ...ateAsync_GeneratesInterceptor.verified.txt | 6 +-- ...ate_SetProperty_ConstantValue.verified.txt | 6 +-- ...tProperty_WithNullConditional.verified.txt | 12 ++--- ...Property_WithSwitchExpression.verified.txt | 12 ++--- .../PolyfillInterceptorGenerator/JoinTests.cs | 45 +++++++++++++++++++ 6 files changed, 64 insertions(+), 19 deletions(-) diff --git a/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs b/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs index b38533d..aa400e4 100644 --- a/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs +++ b/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs @@ -1436,7 +1436,7 @@ private string EmitNestedLambda(IAnonymousFunctionOperation lambda) foreach (var param in lambdaParams) { var paramTypeFqn = param.Type.ToDisplayString(_fqnFormat); - var varName = $"p_{SanitizeIdentifier(param.Name)}_{_varCounter++}"; + var varName = $"{_varPrefix}p_{SanitizeIdentifier(param.Name)}_{_varCounter++}"; _symbolToVar[param] = varName; paramVarNames.Add(varName); AppendLine($"var {varName} = {Expr}.Parameter(typeof({paramTypeFqn}), \"{param.Name}\");"); diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdateAsync_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdateAsync_GeneratesInterceptor.verified.txt index 4b5c603..ea22f6c 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdateAsync_GeneratesInterceptor.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdateAsync_GeneratesInterceptor.verified.txt @@ -13,9 +13,9 @@ namespace ExpressiveSharp.Generated.Interceptors { // Source: s => s.SetProperty(o => o.Tag, "updated") var i361d58c24_p_s = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.SetPropertyCalls), "s"); - var p_o_1 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); // o => o.Tag - var i361d58c24_expr_2 = global::System.Linq.Expressions.Expression.Property(p_o_1, typeof(global::TestNs.Order).GetProperty("Tag", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Tag - var i361d58c24_expr_3 = global::System.Linq.Expressions.Expression.Lambda>(i361d58c24_expr_2, p_o_1); + var i361d58c24_p_o_1 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); // o => o.Tag + var i361d58c24_expr_2 = global::System.Linq.Expressions.Expression.Property(i361d58c24_p_o_1, typeof(global::TestNs.Order).GetProperty("Tag", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Tag + var i361d58c24_expr_3 = global::System.Linq.Expressions.Expression.Lambda>(i361d58c24_expr_2, i361d58c24_p_o_1); var i361d58c24_expr_4 = global::System.Linq.Expressions.Expression.Constant("updated", typeof(string)); // "updated" var i361d58c24_expr_0 = global::System.Linq.Expressions.Expression.Call(i361d58c24_p_s, global::System.Linq.Enumerable.First(global::System.Linq.Enumerable.Where(typeof(global::TestNs.SetPropertyCalls).GetMethods(global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance), m => m.Name == "SetProperty" && m.IsGenericMethodDefinition && m.GetGenericArguments().Length == 1 && m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType.IsGenericType && !m.GetParameters()[0].ParameterType.IsGenericParameter && m.GetParameters()[1].ParameterType.IsGenericParameter && !m.GetParameters()[1].ParameterType.IsGenericType)).MakeGenericMethod(typeof(string)), new global::System.Linq.Expressions.Expression[] { i361d58c24_expr_3, i361d58c24_expr_4 }); var __lambda = global::System.Linq.Expressions.Expression.Lambda, global::TestNs.SetPropertyCalls>>(i361d58c24_expr_0, i361d58c24_p_s); diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_ConstantValue.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_ConstantValue.verified.txt index 32ed0ef..d746d59 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_ConstantValue.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_ConstantValue.verified.txt @@ -12,9 +12,9 @@ namespace ExpressiveSharp.Generated.Interceptors { // Source: s => s.SetProperty(o => o.Tag, "updated") var i361d58c18_p_s = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.SetPropertyCalls), "s"); - var p_o_1 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); // o => o.Tag - var i361d58c18_expr_2 = global::System.Linq.Expressions.Expression.Property(p_o_1, typeof(global::TestNs.Order).GetProperty("Tag", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Tag - var i361d58c18_expr_3 = global::System.Linq.Expressions.Expression.Lambda>(i361d58c18_expr_2, p_o_1); + var i361d58c18_p_o_1 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); // o => o.Tag + var i361d58c18_expr_2 = global::System.Linq.Expressions.Expression.Property(i361d58c18_p_o_1, typeof(global::TestNs.Order).GetProperty("Tag", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Tag + var i361d58c18_expr_3 = global::System.Linq.Expressions.Expression.Lambda>(i361d58c18_expr_2, i361d58c18_p_o_1); var i361d58c18_expr_4 = global::System.Linq.Expressions.Expression.Constant("updated", typeof(string)); // "updated" var i361d58c18_expr_0 = global::System.Linq.Expressions.Expression.Call(i361d58c18_p_s, global::System.Linq.Enumerable.First(global::System.Linq.Enumerable.Where(typeof(global::TestNs.SetPropertyCalls).GetMethods(global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance), m => m.Name == "SetProperty" && m.IsGenericMethodDefinition && m.GetGenericArguments().Length == 1 && m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType.IsGenericType && !m.GetParameters()[0].ParameterType.IsGenericParameter && m.GetParameters()[1].ParameterType.IsGenericParameter && !m.GetParameters()[1].ParameterType.IsGenericType)).MakeGenericMethod(typeof(string)), new global::System.Linq.Expressions.Expression[] { i361d58c18_expr_3, i361d58c18_expr_4 }); var __lambda = global::System.Linq.Expressions.Expression.Lambda, global::TestNs.SetPropertyCalls>>(i361d58c18_expr_0, i361d58c18_p_s); diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_WithNullConditional.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_WithNullConditional.verified.txt index b43492e..d1147ff 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_WithNullConditional.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_WithNullConditional.verified.txt @@ -12,11 +12,11 @@ namespace ExpressiveSharp.Generated.Interceptors { // Source: s => s.SetProperty(o => o.Tag, o => o.Customer?.Name ?? "none") var i361d63c18_p_s = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.SetPropertyCalls), "s"); - var p_o_1 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); // o => o.Tag - var i361d63c18_expr_2 = global::System.Linq.Expressions.Expression.Property(p_o_1, typeof(global::TestNs.Order).GetProperty("Tag", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Tag - var i361d63c18_expr_3 = global::System.Linq.Expressions.Expression.Lambda>(i361d63c18_expr_2, p_o_1); - var p_o_4 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); // o => o.Customer?.Name ?? "none" - var i361d63c18_expr_6 = global::System.Linq.Expressions.Expression.Property(p_o_4, typeof(global::TestNs.Order).GetProperty("Customer", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Customer + var i361d63c18_p_o_1 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); // o => o.Tag + var i361d63c18_expr_2 = global::System.Linq.Expressions.Expression.Property(i361d63c18_p_o_1, typeof(global::TestNs.Order).GetProperty("Tag", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Tag + var i361d63c18_expr_3 = global::System.Linq.Expressions.Expression.Lambda>(i361d63c18_expr_2, i361d63c18_p_o_1); + var i361d63c18_p_o_4 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); // o => o.Customer?.Name ?? "none" + var i361d63c18_expr_6 = global::System.Linq.Expressions.Expression.Property(i361d63c18_p_o_4, typeof(global::TestNs.Order).GetProperty("Customer", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Customer var i361d63c18_expr_7 = global::System.Linq.Expressions.Expression.Property(i361d63c18_expr_6, typeof(global::TestNs.Customer).GetProperty("Name", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // .Name var i361d63c18_expr_9 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::TestNs.Customer)); var i361d63c18_expr_10 = global::System.Linq.Expressions.Expression.NotEqual(i361d63c18_expr_6, i361d63c18_expr_9); @@ -24,7 +24,7 @@ namespace ExpressiveSharp.Generated.Interceptors var i361d63c18_expr_8 = global::System.Linq.Expressions.Expression.Condition(i361d63c18_expr_10, i361d63c18_expr_7, i361d63c18_expr_11, typeof(string)); var i361d63c18_expr_12 = global::System.Linq.Expressions.Expression.Constant("none", typeof(string)); // "none" var i361d63c18_expr_5 = global::System.Linq.Expressions.Expression.Coalesce(i361d63c18_expr_8, i361d63c18_expr_12); - var i361d63c18_expr_13 = global::System.Linq.Expressions.Expression.Lambda>(i361d63c18_expr_5, p_o_4); + var i361d63c18_expr_13 = global::System.Linq.Expressions.Expression.Lambda>(i361d63c18_expr_5, i361d63c18_p_o_4); var i361d63c18_expr_0 = global::System.Linq.Expressions.Expression.Call(i361d63c18_p_s, global::System.Linq.Enumerable.First(global::System.Linq.Enumerable.Where(typeof(global::TestNs.SetPropertyCalls).GetMethods(global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance), m => m.Name == "SetProperty" && m.IsGenericMethodDefinition && m.GetGenericArguments().Length == 1 && m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType.IsGenericType && !m.GetParameters()[0].ParameterType.IsGenericParameter && m.GetParameters()[1].ParameterType.IsGenericType && !m.GetParameters()[1].ParameterType.IsGenericParameter)).MakeGenericMethod(typeof(string)), new global::System.Linq.Expressions.Expression[] { i361d63c18_expr_3, i361d63c18_expr_13 }); var __lambda = global::System.Linq.Expressions.Expression.Lambda, global::TestNs.SetPropertyCalls>>(i361d63c18_expr_0, i361d63c18_p_s); return global::TestNs.MockRelationalExtensions.ExecuteUpdate( diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_WithSwitchExpression.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_WithSwitchExpression.verified.txt index a0523df..236b4be 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_WithSwitchExpression.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_WithSwitchExpression.verified.txt @@ -12,11 +12,11 @@ namespace ExpressiveSharp.Generated.Interceptors { // Source: s => s.SetProperty(o => o.Tag, o => o.Amount switch { > 100 => "high", > 50 => "medium", _ => "low" }) var i361d62c18_p_s = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.SetPropertyCalls), "s"); - var p_o_1 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); // o => o.Tag - var i361d62c18_expr_2 = global::System.Linq.Expressions.Expression.Property(p_o_1, typeof(global::TestNs.Order).GetProperty("Tag", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Tag - var i361d62c18_expr_3 = global::System.Linq.Expressions.Expression.Lambda>(i361d62c18_expr_2, p_o_1); - var p_o_4 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); // o => o.Amount switch { ... - var i361d62c18_expr_5 = global::System.Linq.Expressions.Expression.Property(p_o_4, typeof(global::TestNs.Order).GetProperty("Amount", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Amount + var i361d62c18_p_o_1 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); // o => o.Tag + var i361d62c18_expr_2 = global::System.Linq.Expressions.Expression.Property(i361d62c18_p_o_1, typeof(global::TestNs.Order).GetProperty("Tag", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Tag + var i361d62c18_expr_3 = global::System.Linq.Expressions.Expression.Lambda>(i361d62c18_expr_2, i361d62c18_p_o_1); + var i361d62c18_p_o_4 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); // o => o.Amount switch { ... + var i361d62c18_expr_5 = global::System.Linq.Expressions.Expression.Property(i361d62c18_p_o_4, typeof(global::TestNs.Order).GetProperty("Amount", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Amount var i361d62c18_expr_6 = global::System.Linq.Expressions.Expression.Constant("low", typeof(string)); // "low" var i361d62c18_expr_8 = global::System.Linq.Expressions.Expression.Constant(50, typeof(int)); // 50 var i361d62c18_expr_7 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, i361d62c18_expr_5, i361d62c18_expr_8); @@ -26,7 +26,7 @@ namespace ExpressiveSharp.Generated.Interceptors var i361d62c18_expr_11 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, i361d62c18_expr_5, i361d62c18_expr_12); var i361d62c18_expr_13 = global::System.Linq.Expressions.Expression.Constant("high", typeof(string)); // "high" var i361d62c18_expr_14 = global::System.Linq.Expressions.Expression.Condition(i361d62c18_expr_11, i361d62c18_expr_13, i361d62c18_expr_10, typeof(string)); - var i361d62c18_expr_15 = global::System.Linq.Expressions.Expression.Lambda>(i361d62c18_expr_14, p_o_4); + var i361d62c18_expr_15 = global::System.Linq.Expressions.Expression.Lambda>(i361d62c18_expr_14, i361d62c18_p_o_4); var i361d62c18_expr_0 = global::System.Linq.Expressions.Expression.Call(i361d62c18_p_s, global::System.Linq.Enumerable.First(global::System.Linq.Enumerable.Where(typeof(global::TestNs.SetPropertyCalls).GetMethods(global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance), m => m.Name == "SetProperty" && m.IsGenericMethodDefinition && m.GetGenericArguments().Length == 1 && m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType.IsGenericType && !m.GetParameters()[0].ParameterType.IsGenericParameter && m.GetParameters()[1].ParameterType.IsGenericType && !m.GetParameters()[1].ParameterType.IsGenericParameter)).MakeGenericMethod(typeof(string)), new global::System.Linq.Expressions.Expression[] { i361d62c18_expr_3, i361d62c18_expr_15 }); var __lambda = global::System.Linq.Expressions.Expression.Lambda, global::TestNs.SetPropertyCalls>>(i361d62c18_expr_0, i361d62c18_p_s); return global::TestNs.MockRelationalExtensions.ExecuteUpdate( diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/JoinTests.cs b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/JoinTests.cs index af18ac2..3b4869b 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/JoinTests.cs +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/JoinTests.cs @@ -179,4 +179,49 @@ public void Run(System.Linq.IQueryable orders, System.Collections.Generic Assert.AreEqual(0, errors.Count, string.Join("\n", errors.Select(d => d.ToString()))); } + + // Regression: two sibling key-selector lambdas in a multi-lambda interceptor each contain a + // nested lambda with the same parameter name. The nested-lambda ParameterExpression variable + // must carry the per-lambda _varPrefix; without it, both emitters declare the same local + // (p_t_) in the shared interceptor body → CS0128 duplicate-variable compile error. + [TestMethod] + public void Join_SiblingKeySelectorsWithNestedLambdas_GeneratedInterceptorCompiles() + { + var source = + """ + using ExpressiveSharp; + + namespace TestNs + { + class Order { public int CustomerId { get; set; } public System.Collections.Generic.List Tags { get; set; } } + class Customer { public int Id { get; set; } public System.Collections.Generic.List Labels { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable orders, System.Collections.Generic.IEnumerable customers) + { + orders.AsExpressive() + .Join(customers, + o => o.Tags.Count(t => t.Length > 0), + c => c.Labels.Count(t => t.Length > 0), + (o, c) => o.CustomerId) + .ToList(); + } + } + } + """; + + var compilation = CreateCompilation(source); + var subject = new global::ExpressiveSharp.Generator.PolyfillInterceptorGenerator(); + GeneratorDriver driver = CSharpGeneratorDriver + .Create(subject) + .WithUpdatedParseOptions((CSharpParseOptions)compilation.SyntaxTrees.First().Options) + .RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out _); + + var errors = outputCompilation.GetDiagnostics() + .Where(d => d.Severity == DiagnosticSeverity.Error) + .Where(d => d.Id != "CS9137") + .ToList(); + + Assert.AreEqual(0, errors.Count, string.Join("\n", errors.Select(d => d.ToString()))); + } }