diff --git a/src/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.BodyProcessors.cs b/src/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.BodyProcessors.cs index 144915ff..b2bec7c8 100644 --- a/src/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.BodyProcessors.cs +++ b/src/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.BodyProcessors.cs @@ -272,7 +272,8 @@ private static EmitResult EmitExpressionTreeForProperty( { var thisParam = descriptor.ParametersList.Parameters[0]; var thisTypeFqn = thisParam.Type?.ToString() ?? "object"; - emitterParams.Add(new EmitterParameter("@this", thisTypeFqn, isThis: true)); + emitterParams.Add(new EmitterParameter( + "@this", thisTypeFqn, symbol: descriptor.ExtensionParameterSymbol, isThis: true)); } var allTypeArgs = emitterParams.Select(p => p.TypeFqn).ToList(); @@ -295,7 +296,8 @@ private static List BuildEmitterParameters( { var thisParam = descriptor.ParametersList.Parameters[0]; var thisTypeFqn = thisParam.Type?.ToString() ?? "object"; - result.Add(new EmitterParameter("@this", thisTypeFqn, isThis: true)); + result.Add(new EmitterParameter( + "@this", thisTypeFqn, symbol: descriptor.ExtensionParameterSymbol, isThis: true)); } foreach (var param in methodSymbol.Parameters) diff --git a/src/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.cs b/src/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.cs index 41e3d496..11ae88bd 100644 --- a/src/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.cs +++ b/src/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.cs @@ -34,6 +34,7 @@ static internal partial class ExpressiveInterpreter var descriptor = BuildBaseDescriptor( member, memberSymbol, methodSymbol, isExtensionMember, extensionParameter, extensionReceiverType); + descriptor.ExtensionParameterSymbol = extensionParameter; foreach (var typeName in expressiveAttribute.TransformerTypeNames) descriptor.DeclaredTransformerTypeNames.Add(typeName); @@ -94,6 +95,12 @@ private static ExpressiveDescriptor BuildBaseDescriptor( ParametersList = SyntaxFactory.ParameterList() }; + // A `static` member inside an `extension(T) { }` block has no implicit receiver — + // treat it like an ordinary static method on the containing static class. + var isInstanceExtensionMember = isExtensionMember + && extensionReceiverType is not null + && !member.Modifiers.Any(SyntaxKind.StaticKeyword); + if (methodSymbol is not null) { var parameterTypeNames = methodSymbol.Parameters @@ -102,10 +109,10 @@ private static ExpressiveDescriptor BuildBaseDescriptor( // Prepend the extension receiver type to match how the runtime sees the method // (receiver is the first implicit parameter). - if (isExtensionMember && extensionReceiverType is not null) + if (isInstanceExtensionMember) { parameterTypeNames.Insert(0, - extensionReceiverType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + extensionReceiverType!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); } descriptor.ParameterTypeNames = parameterTypeNames; @@ -116,12 +123,12 @@ private static ExpressiveDescriptor BuildBaseDescriptor( SetupGenericTypeParameters(descriptor, classForNaming); } - if (isExtensionMember && extensionReceiverType is not null) + if (isInstanceExtensionMember) { descriptor.ParametersList = descriptor.ParametersList.AddParameters( SyntaxFactory.Parameter(SyntaxFactory.Identifier("@this")) .WithType(SyntaxFactory.ParseTypeName( - extensionReceiverType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)))); + extensionReceiverType!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)))); } else if (!member.Modifiers.Any(SyntaxKind.StaticKeyword) && member is not ConstructorDeclarationSyntax) { @@ -132,9 +139,9 @@ private static ExpressiveDescriptor BuildBaseDescriptor( } // Target class info is used by the registry to associate the projection. - if (isExtensionMember && extensionReceiverType is not null) + if (isInstanceExtensionMember) { - descriptor.TargetClassNamespace = extensionReceiverType.ContainingNamespace.IsGlobalNamespace + descriptor.TargetClassNamespace = extensionReceiverType!.ContainingNamespace.IsGlobalNamespace ? null : extensionReceiverType.ContainingNamespace.ToDisplayString(); descriptor.TargetNestedInClassNames = GetNestedInClassPath(extensionReceiverType); diff --git a/src/ExpressiveSharp.Generator/Models/ExpressiveDescriptor.cs b/src/ExpressiveSharp.Generator/Models/ExpressiveDescriptor.cs index 568dc6a7..33521c34 100644 --- a/src/ExpressiveSharp.Generator/Models/ExpressiveDescriptor.cs +++ b/src/ExpressiveSharp.Generator/Models/ExpressiveDescriptor.cs @@ -29,6 +29,8 @@ internal class ExpressiveDescriptor public ParameterListSyntax? ParametersList { get; set; } public IEnumerable? ParameterTypeNames { get; set; } + + public IParameterSymbol? ExtensionParameterSymbol { get; set; } public TypeParameterListSyntax? TypeParameterList { get; set; } diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberMethod.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberMethod.verified.txt index 5270c0cf..af4aafd9 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberMethod.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberMethod.verified.txt @@ -12,10 +12,9 @@ namespace ExpressiveSharp.Generated static global::System.Linq.Expressions.Expression> TripleId_P0_Foo_Entity_Expression() { var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "@this"); - var expr_2 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "e"); // e - var expr_1 = global::System.Linq.Expressions.Expression.Property(expr_2, typeof(global::Foo.Entity).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); - var expr_3 = global::System.Linq.Expressions.Expression.Constant(3, typeof(int)); // 3 - var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, expr_1, expr_3); + var expr_1 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // e.Id + var expr_2 = global::System.Linq.Expressions.Expression.Constant(3, typeof(int)); // 3 + var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, expr_1, expr_2); return global::System.Linq.Expressions.Expression.Lambda>(expr_0, p__this); } } diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberMethodWithParameters.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberMethodWithParameters.verified.txt index fb545c84..78a18dfb 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberMethodWithParameters.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberMethodWithParameters.verified.txt @@ -13,8 +13,7 @@ namespace ExpressiveSharp.Generated { var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "@this"); var p_factor = global::System.Linq.Expressions.Expression.Parameter(typeof(int), "factor"); - var expr_2 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "e"); // e - var expr_1 = global::System.Linq.Expressions.Expression.Property(expr_2, typeof(global::Foo.Entity).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); + var expr_1 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // e.Id var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, expr_1, p_factor); return global::System.Linq.Expressions.Expression.Lambda>(expr_0, p__this, p_factor); } diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberOnInterface.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberOnInterface.verified.txt index 294f1027..b6998d90 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberOnInterface.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberOnInterface.verified.txt @@ -12,13 +12,12 @@ namespace ExpressiveSharp.Generated static global::System.Linq.Expressions.Expression> Label_Expression() { var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.IEntity), "@this"); - var expr_4 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.IEntity), "e"); // e - var expr_3 = global::System.Linq.Expressions.Expression.Property(expr_4, typeof(global::Foo.IEntity).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); + var expr_3 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.IEntity).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // e.Id var expr_2 = global::System.Linq.Expressions.Expression.Convert(expr_3, typeof(object)); - var expr_5 = global::System.Linq.Expressions.Expression.Constant(": ", typeof(string)); // ": " - var expr_1 = global::System.Linq.Expressions.Expression.Call(typeof(string).GetMethod("Concat", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(object), typeof(object) }, null), expr_2, expr_5); - var expr_6 = global::System.Linq.Expressions.Expression.Property(expr_4, typeof(global::Foo.IEntity).GetProperty("Name", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // e.Name - var expr_0 = global::System.Linq.Expressions.Expression.Call(typeof(string).GetMethod("Concat", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(string), typeof(string) }, null), expr_1, expr_6); + var expr_4 = global::System.Linq.Expressions.Expression.Constant(": ", typeof(string)); // ": " + var expr_1 = global::System.Linq.Expressions.Expression.Call(typeof(string).GetMethod("Concat", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(object), typeof(object) }, null), expr_2, expr_4); + var expr_5 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.IEntity).GetProperty("Name", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // e.Name + var expr_0 = global::System.Linq.Expressions.Expression.Call(typeof(string).GetMethod("Concat", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(string), typeof(string) }, null), expr_1, expr_5); return global::System.Linq.Expressions.Expression.Lambda>(expr_0, p__this); } } diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberOnPrimitive.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberOnPrimitive.verified.txt index 3a38daf8..2ac5fdb7 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberOnPrimitive.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberOnPrimitive.verified.txt @@ -11,8 +11,7 @@ namespace ExpressiveSharp.Generated static global::System.Linq.Expressions.Expression> Squared_Expression() { var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(int), "@this"); - var expr_1 = global::System.Linq.Expressions.Expression.Parameter(typeof(int), "i"); // i - var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, expr_1, expr_1); + var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, p__this, p__this); // i * i return global::System.Linq.Expressions.Expression.Lambda>(expr_0, p__this); } } diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberProperty.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberProperty.verified.txt index 08e48a97..a7e2f497 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberProperty.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberProperty.verified.txt @@ -12,10 +12,9 @@ namespace ExpressiveSharp.Generated static global::System.Linq.Expressions.Expression> DoubleId_Expression() { var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "@this"); - var expr_2 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "e"); // e - var expr_1 = global::System.Linq.Expressions.Expression.Property(expr_2, typeof(global::Foo.Entity).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); - var expr_3 = global::System.Linq.Expressions.Expression.Constant(2, typeof(int)); // 2 - var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, expr_1, expr_3); + var expr_1 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // e.Id + var expr_2 = global::System.Linq.Expressions.Expression.Constant(2, typeof(int)); // 2 + var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, expr_1, expr_2); return global::System.Linq.Expressions.Expression.Lambda>(expr_0, p__this); } } diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithBlockBody.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithBlockBody.verified.txt index 14f1f961..26a4da68 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithBlockBody.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithBlockBody.verified.txt @@ -20,15 +20,14 @@ namespace ExpressiveSharp.Generated static global::System.Linq.Expressions.Expression> GetStatus_P0_Foo_Entity_Expression() { var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "@this"); - var expr_3 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "e"); // e - var expr_2 = global::System.Linq.Expressions.Expression.Property(expr_3, typeof(global::Foo.Entity).GetProperty("IsActive", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); - var expr_5 = global::System.Linq.Expressions.Expression.Property(expr_3, typeof(global::Foo.Entity).GetProperty("Value", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // e.Value - var expr_6 = global::System.Linq.Expressions.Expression.Constant(0, typeof(int)); // 0 - var expr_4 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_5, expr_6); - var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.AndAlso, expr_2, expr_4); - var expr_7 = global::System.Linq.Expressions.Expression.Constant("Active", typeof(string)); // "Active" - var expr_8 = global::System.Linq.Expressions.Expression.Constant("Inactive", typeof(string)); // "Inactive" - var expr_0 = global::System.Linq.Expressions.Expression.Condition(expr_1, expr_7, expr_8, typeof(string)); + var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("IsActive", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // e.IsActive + var expr_4 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Value", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // e.Value + var expr_5 = global::System.Linq.Expressions.Expression.Constant(0, typeof(int)); // 0 + var expr_3 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_4, expr_5); + var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.AndAlso, expr_2, expr_3); + var expr_6 = global::System.Linq.Expressions.Expression.Constant("Active", typeof(string)); // "Active" + var expr_7 = global::System.Linq.Expressions.Expression.Constant("Inactive", typeof(string)); // "Inactive" + var expr_0 = global::System.Linq.Expressions.Expression.Condition(expr_1, expr_6, expr_7, typeof(string)); return global::System.Linq.Expressions.Expression.Lambda>(expr_0, p__this); } } diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithIsPatternExpression.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithIsPatternExpression.verified.txt index 61a584cc..bed6c13b 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithIsPatternExpression.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithIsPatternExpression.verified.txt @@ -12,11 +12,10 @@ namespace ExpressiveSharp.Generated static global::System.Linq.Expressions.Expression> IsHighValue_Expression() { var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "@this"); - var expr_1 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "e"); // e - var expr_0 = global::System.Linq.Expressions.Expression.Property(expr_1, typeof(global::Foo.Entity).GetProperty("Value", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); - var expr_3 = global::System.Linq.Expressions.Expression.Constant(100, typeof(int)); // 100 - var expr_2 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_0, expr_3); - return global::System.Linq.Expressions.Expression.Lambda>(expr_2, p__this); + var expr_0 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Value", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // e.Value + var expr_2 = global::System.Linq.Expressions.Expression.Constant(100, typeof(int)); // 100 + var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_0, expr_2); + return global::System.Linq.Expressions.Expression.Lambda>(expr_1, p__this); } } } diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithMemberAccess.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithMemberAccess.verified.txt index 8f40fd86..29b9a68a 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithMemberAccess.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithMemberAccess.verified.txt @@ -12,13 +12,12 @@ namespace ExpressiveSharp.Generated static global::System.Linq.Expressions.Expression> IdAndName_Expression() { var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "@this"); - var expr_4 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "e"); // e - var expr_3 = global::System.Linq.Expressions.Expression.Property(expr_4, typeof(global::Foo.Entity).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); + var expr_3 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // e.Id var expr_2 = global::System.Linq.Expressions.Expression.Convert(expr_3, typeof(object)); - var expr_5 = global::System.Linq.Expressions.Expression.Constant(": ", typeof(string)); // ": " - var expr_1 = global::System.Linq.Expressions.Expression.Call(typeof(string).GetMethod("Concat", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(object), typeof(object) }, null), expr_2, expr_5); - var expr_6 = global::System.Linq.Expressions.Expression.Property(expr_4, typeof(global::Foo.Entity).GetProperty("Name", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // e.Name - var expr_0 = global::System.Linq.Expressions.Expression.Call(typeof(string).GetMethod("Concat", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(string), typeof(string) }, null), expr_1, expr_6); + var expr_4 = global::System.Linq.Expressions.Expression.Constant(": ", typeof(string)); // ": " + var expr_1 = global::System.Linq.Expressions.Expression.Call(typeof(string).GetMethod("Concat", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(object), typeof(object) }, null), expr_2, expr_4); + var expr_5 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Name", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // e.Name + var expr_0 = global::System.Linq.Expressions.Expression.Call(typeof(string).GetMethod("Concat", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(string), typeof(string) }, null), expr_1, expr_5); return global::System.Linq.Expressions.Expression.Lambda>(expr_0, p__this); } } diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithSwitchExpression.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithSwitchExpression.verified.txt index af80e795..c87727d0 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithSwitchExpression.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithSwitchExpression.verified.txt @@ -18,22 +18,21 @@ namespace ExpressiveSharp.Generated static global::System.Linq.Expressions.Expression> GetGrade_P0_Foo_Entity_Expression() { var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "@this"); - var expr_1 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "e"); // e - var expr_0 = global::System.Linq.Expressions.Expression.Property(expr_1, typeof(global::Foo.Entity).GetProperty("Score", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); - var expr_2 = global::System.Linq.Expressions.Expression.Constant("F", typeof(string)); // "F" - var expr_4 = global::System.Linq.Expressions.Expression.Constant(70, typeof(int)); // 70 - var expr_3 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThanOrEqual, expr_0, expr_4); - var expr_5 = global::System.Linq.Expressions.Expression.Constant("C", typeof(string)); // "C" - var expr_6 = global::System.Linq.Expressions.Expression.Condition(expr_3, expr_5, expr_2, typeof(string)); - var expr_8 = global::System.Linq.Expressions.Expression.Constant(80, typeof(int)); // 80 - var expr_7 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThanOrEqual, expr_0, expr_8); - var expr_9 = global::System.Linq.Expressions.Expression.Constant("B", typeof(string)); // "B" - var expr_10 = global::System.Linq.Expressions.Expression.Condition(expr_7, expr_9, expr_6, typeof(string)); - var expr_12 = global::System.Linq.Expressions.Expression.Constant(90, typeof(int)); // 90 - var expr_11 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThanOrEqual, expr_0, expr_12); - var expr_13 = global::System.Linq.Expressions.Expression.Constant("A", typeof(string)); // "A" - var expr_14 = global::System.Linq.Expressions.Expression.Condition(expr_11, expr_13, expr_10, typeof(string)); - return global::System.Linq.Expressions.Expression.Lambda>(expr_14, p__this); + var expr_0 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Score", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // e.Score + var expr_1 = global::System.Linq.Expressions.Expression.Constant("F", typeof(string)); // "F" + var expr_3 = global::System.Linq.Expressions.Expression.Constant(70, typeof(int)); // 70 + var expr_2 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThanOrEqual, expr_0, expr_3); + var expr_4 = global::System.Linq.Expressions.Expression.Constant("C", typeof(string)); // "C" + var expr_5 = global::System.Linq.Expressions.Expression.Condition(expr_2, expr_4, expr_1, typeof(string)); + var expr_7 = global::System.Linq.Expressions.Expression.Constant(80, typeof(int)); // 80 + var expr_6 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThanOrEqual, expr_0, expr_7); + var expr_8 = global::System.Linq.Expressions.Expression.Constant("B", typeof(string)); // "B" + var expr_9 = global::System.Linq.Expressions.Expression.Condition(expr_6, expr_8, expr_5, typeof(string)); + var expr_11 = global::System.Linq.Expressions.Expression.Constant(90, typeof(int)); // 90 + var expr_10 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThanOrEqual, expr_0, expr_11); + var expr_12 = global::System.Linq.Expressions.Expression.Constant("A", typeof(string)); // "A" + var expr_13 = global::System.Linq.Expressions.Expression.Condition(expr_10, expr_12, expr_9, typeof(string)); + return global::System.Linq.Expressions.Expression.Lambda>(expr_13, p__this); } } } diff --git a/tests/ExpressiveSharp.IntegrationTests/ExpressiveSharp.IntegrationTests.csproj b/tests/ExpressiveSharp.IntegrationTests/ExpressiveSharp.IntegrationTests.csproj index af01910a..78b68eba 100644 --- a/tests/ExpressiveSharp.IntegrationTests/ExpressiveSharp.IntegrationTests.csproj +++ b/tests/ExpressiveSharp.IntegrationTests/ExpressiveSharp.IntegrationTests.csproj @@ -11,4 +11,9 @@ OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + + + + + diff --git a/tests/ExpressiveSharp.IntegrationTests/Tests/ExtensionMemberTests.cs b/tests/ExpressiveSharp.IntegrationTests/Tests/ExtensionMemberTests.cs new file mode 100644 index 00000000..b27d05fc --- /dev/null +++ b/tests/ExpressiveSharp.IntegrationTests/Tests/ExtensionMemberTests.cs @@ -0,0 +1,175 @@ +using System.Linq.Expressions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ExpressiveSharp.IntegrationTests.Tests; + +/// +/// Issue #60: C# 14 extension(T) { ... } members must produce well-formed lambdas +/// — the body's reference to the extension parameter must be the same +/// the lambda declares. +/// +[TestClass] +public class ExtensionMemberTests +{ + [TestMethod] + public void ExtensionMethod_NoArgs_Compiles_AndEvaluates() + { + var entity = new ExtEntity { Id = 4 }; + Expression> expr = e => e.TripleId(); + var expanded = (Expression>)expr.ExpandExpressives(); + + var result = expanded.Compile()(entity); + + Assert.AreEqual(12, result); + } + + [TestMethod] + public void ExtensionMethod_WithExtraParameters_Compiles_AndEvaluates() + { + var entity = new ExtEntity { Id = 5 }; + Expression> expr = (e, f) => e.Multiply(f); + var expanded = (Expression>)expr.ExpandExpressives(); + + var result = expanded.Compile()(entity, 6); + + Assert.AreEqual(30, result); + } + + [TestMethod] + public void ExtensionMethod_OnPrimitive_Compiles_AndEvaluates() + { + Expression> expr = i => i.Squared(); + var expanded = (Expression>)expr.ExpandExpressives(); + + var result = expanded.Compile()(9); + + Assert.AreEqual(81, result); + } + + [TestMethod] + public void ExtensionMethod_WithSwitchExpression_Compiles_AndEvaluates() + { + var entity = new ExtEntity { Id = 85 }; + Expression> expr = e => e.GetGrade(); + var expanded = (Expression>)expr.ExpandExpressives(); + + var result = expanded.Compile()(entity); + + Assert.AreEqual("B", result); + } + + [TestMethod] + public void ExtensionMethod_WithBlockBody_Compiles_AndEvaluates() + { + var entity = new ExtEntity { Id = 5, IsActive = true }; + Expression> expr = e => e.GetStatus(); + var expanded = (Expression>)expr.ExpandExpressives(); + + var result = expanded.Compile()(entity); + + Assert.AreEqual("Active", result); + } + + [TestMethod] + public void ExtensionMethod_WithMultipleReceiverReferences_Compiles_AndEvaluates() + { + var entity = new ExtEntity { Id = 1, Name = "foo" }; + Expression> expr = e => e.IdAndName(); + var expanded = (Expression>)expr.ExpandExpressives(); + + var result = expanded.Compile()(entity); + + Assert.AreEqual("1: foo", result); + } + + [TestMethod] + public void ExtensionMethod_OnInterface_Compiles_AndEvaluates() + { + IExtEntity entity = new ExtEntity { Id = 3, Name = "abc" }; + Expression> expr = e => e.GetLabel(); + var expanded = (Expression>)expr.ExpandExpressives(); + + var result = expanded.Compile()(entity); + + Assert.AreEqual("3: abc", result); + } + + [TestMethod] + public void StaticExtensionMethod_Compiles_AndEvaluates() + { + Expression> expr = x => ExtEntity.DoubleOf(x); + var expanded = (Expression>)expr.ExpandExpressives(); + + var result = expanded.Compile()(11); + + Assert.AreEqual(22, result); + } +} + +public interface IExtEntity +{ + int Id { get; } + string? Name { get; } +} + +public class ExtEntity : IExtEntity +{ + public int Id { get; set; } + public string? Name { get; set; } + public bool IsActive { get; set; } +} + +public static class ExtEntityExtensions +{ + extension(ExtEntity e) + { + [Expressive] + public int DoubleId => e.Id * 2; + + [Expressive] + public int TripleId() => e.Id * 3; + + [Expressive] + public int Multiply(int factor) => e.Id * factor; + + [Expressive] + public string IdAndName() => e.Id + ": " + e.Name; + + [Expressive] + public string GetGrade() => e.Id switch + { + >= 90 => "A", + >= 80 => "B", + >= 70 => "C", + _ => "F", + }; + + [Expressive(AllowBlockBody = true)] + public string GetStatus() + { + if (e.IsActive && e.Id > 0) + { + return "Active"; + } + return "Inactive"; + } + } + + extension(IExtEntity e) + { + [Expressive] + public string GetLabel() => e.Id + ": " + e.Name; + } + + extension(ExtEntity) + { + [Expressive] + public static int DoubleOf(int x) => x * 2; + } + + extension(int i) + { + [Expressive] + public int Squared() => i * i; + } +}