From 2bb8eedc0a3703864aa7cbc4ef2436ee70791192 Mon Sep 17 00:00:00 2001 From: Koen Date: Thu, 26 Mar 2026 00:39:05 +0000 Subject: [PATCH 1/2] fix: Emit string.Concat instead of Expression.Add for string + operator String concatenation via the + operator was incorrectly emitting Expression.MakeBinary(ExpressionType.Add, ...) which is only valid for numeric types. The built-in C# string + is a compiler intrinsic with no OperatorMethod, so it fell through to the plain MakeBinary path. Now emits Expression.Call(string.Concat, ...) matching the interpolation codepath, with Concat(string,string) for homogeneous operands and Concat(object,object) for mixed types. Also fixes the same latent bug in EmitCompoundAssignment for string +=. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Emitter/ExpressionTreeEmitter.cs | 74 ++++++++++++++++++ ...cingBasePropertyInDerivedBody.verified.txt | 5 +- ...ingPreviouslyAssignedProperty.verified.txt | 5 +- ..._ReferencingStaticConstMember.verified.txt | 5 +- ...nitializer_ChainedThisAndBase.verified.txt | 3 +- ...ThisInitializer_WithBodyAfter.verified.txt | 9 ++- ...ializer_WithIfElseInDelegated.verified.txt | 3 +- ...bleConstructor_WithFullObject.verified.txt | 9 ++- ...Constructor_WithLocalVariable.verified.txt | 5 +- ...or_WithMultipleLocalVariables.verified.txt | 9 ++- ...structor_WithSwitchExpression.verified.txt | 3 +- ...chExpression_AndExtraProperty.verified.txt | 8 +- ...ts.ExtensionMemberOnInterface.verified.txt | 6 +- ...tensionMemberWithMemberAccess.verified.txt | 6 +- ...catenationTests.IntPlusString.verified.txt | 30 +++++++ ...ationTests.SingleStringConcat.verified.txt | 26 +++++++ ...enationTests.StringPlusString.verified.txt | 28 +++++++ .../StringConcatenationTests.cs | 78 +++++++++++++++++++ .../Common/Tests/StringOperationTests.cs | 19 +++++ .../Scenarios/Store/Models/Order.cs | 4 + 20 files changed, 305 insertions(+), 30 deletions(-) create mode 100644 tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.IntPlusString.verified.txt create mode 100644 tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.SingleStringConcat.verified.txt create mode 100644 tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.StringPlusString.verified.txt create mode 100644 tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.cs diff --git a/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs b/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs index f9b037b..6eac0d3 100644 --- a/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs +++ b/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs @@ -731,6 +731,15 @@ private string EmitBinary(IBinaryOperation binary) return EmitUnsupported(binary); } + // String concatenation via + uses string.Concat, not Expression.Add. + // Only matches the built-in compiler intrinsic (OperatorMethod is null). + // User-defined operator+ that returns string will have OperatorMethod set + // and is correctly handled by the existing MakeBinary path below. + if (binary.OperatorKind == BinaryOperatorKind.Add + && binary.OperatorMethod is null + && binary.Type?.SpecialType == SpecialType.System_String) + return EmitStringConcatenation(binary); + // Use checked variants when in a checked context if (binary.IsChecked) { @@ -760,6 +769,26 @@ private string EmitBinary(IBinaryOperation binary) return resultVar; } + private string EmitStringConcatenation(IBinaryOperation binary) + { + var resultVar = NextVar(); + var leftVar = EmitOperation(binary.LeftOperand); + var rightVar = EmitOperation(binary.RightOperand); + + // Choose the correct Concat overload based on operand types. + // If both operands are string, use Concat(string, string). + // Otherwise (e.g. object + string from implicit boxing), use Concat(object, object). + var bothString = binary.LeftOperand.Type?.SpecialType == SpecialType.System_String + && binary.RightOperand.Type?.SpecialType == SpecialType.System_String; + + var concatMethod = bothString + ? EnsureStringConcatMethod() + : EnsureStringConcatObjectMethod(); + + AppendLine($"var {resultVar} = {Expr}.Call({concatMethod}, {leftVar}, {rightVar});"); + return resultVar; + } + private static string? MapBinaryOperatorKind(BinaryOperatorKind kind) { return kind switch @@ -2110,6 +2139,35 @@ private string EnsureStringConcatMethod() return _concatMethodField; } + private string? _concatObjectMethodField; + + private string EnsureStringConcatObjectMethod() + { + if (_concatObjectMethodField is not null) + return _concatObjectMethodField; + + var stringType = _semanticModel.Compilation.GetSpecialType(SpecialType.System_String); + var concatMethod = stringType.GetMembers("Concat") + .OfType() + .FirstOrDefault(m => m.IsStatic + && m.Parameters.Length == 2 + && m.Parameters[0].Type.SpecialType == SpecialType.System_Object + && m.Parameters[1].Type.SpecialType == SpecialType.System_Object); + + if (concatMethod is not null) + { + _concatObjectMethodField = _fieldCache.EnsureMethodInfo(concatMethod); + } + else + { + // Fallback: emit inline reflection + _concatObjectMethodField = "_stringConcatObj"; + _fieldCache.GetDeclarations(); // ensure we can add to it + } + + return _concatObjectMethodField; + } + private INamedTypeSymbol? _enumerableType; private string? ResolveEnumerableMethod(string methodName, int paramCount, ITypeSymbol elementType) @@ -2460,6 +2518,22 @@ private string EmitCompoundAssignment(ICompoundAssignmentOperation compoundAssig return EmitUnsupported(compoundAssign); } + // String += uses string.Concat, not Expression.Add + if (compoundAssign.OperatorKind == BinaryOperatorKind.Add + && compoundAssign.OperatorMethod is null + && compoundAssign.Type?.SpecialType == SpecialType.System_String) + { + var bothString = compoundAssign.Target.Type?.SpecialType == SpecialType.System_String + && compoundAssign.Value.Type?.SpecialType == SpecialType.System_String; + var concatMethod = bothString + ? EnsureStringConcatMethod() + : EnsureStringConcatObjectMethod(); + var concatVar = NextVar(); + AppendLine($"var {concatVar} = {Expr}.Call({concatMethod}, {targetVar}, {valueVar});"); + AppendLine($"var {resultVar} = {Expr}.Assign({targetVar}, {concatVar});"); + return resultVar; + } + if (compoundAssign.IsChecked) { exprType = exprType switch diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt index 152c2bd..84f90a4 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt @@ -10,6 +10,7 @@ namespace ExpressiveSharp.Generated { private static readonly global::System.Reflection.ConstructorInfo _c0 = typeof(global::Foo.Child).GetConstructor(new global::System.Type[] { }); private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.Base).GetProperty("Code"); + private static readonly global::System.Reflection.MethodInfo _m0 = 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); private static readonly global::System.Reflection.PropertyInfo _p1 = typeof(global::Foo.Child).GetProperty("Label"); // [Expressive] @@ -24,9 +25,9 @@ namespace ExpressiveSharp.Generated var expr_3 = global::System.Linq.Expressions.Expression.Constant("[", typeof(string)); // "[" var p___this = global::System.Linq.Expressions.Expression.Parameter(typeof(object), "@this"); // Code var expr_4 = global::System.Linq.Expressions.Expression.Property(p___this, _p0); - var expr_2 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_3, expr_4); + var expr_2 = global::System.Linq.Expressions.Expression.Call(_m0, expr_3, expr_4); var expr_5 = global::System.Linq.Expressions.Expression.Constant("]", typeof(string)); // "]" - var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_2, expr_5); + var expr_1 = global::System.Linq.Expressions.Expression.Call(_m0, expr_2, expr_5); var expr_6 = global::System.Linq.Expressions.Expression.Bind(_p1, expr_1); var expr_7 = global::System.Linq.Expressions.Expression.MemberInit(expr_0, expr_6); return global::System.Linq.Expressions.Expression.Lambda>(expr_7, p_code); diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt index 14c3097..4afda40 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt @@ -10,6 +10,7 @@ namespace ExpressiveSharp.Generated { private static readonly global::System.Reflection.ConstructorInfo _c0 = typeof(global::Foo.PersonDto).GetConstructor(new global::System.Type[] { }); private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.PersonDto).GetProperty("FirstName"); + private static readonly global::System.Reflection.MethodInfo _m0 = 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); private static readonly global::System.Reflection.PropertyInfo _p1 = typeof(global::Foo.PersonDto).GetProperty("LastName"); private static readonly global::System.Reflection.PropertyInfo _p2 = typeof(global::Foo.PersonDto).GetProperty("FullName"); @@ -28,9 +29,9 @@ namespace ExpressiveSharp.Generated var p___this = global::System.Linq.Expressions.Expression.Parameter(typeof(object), "@this"); // FirstName var expr_3 = global::System.Linq.Expressions.Expression.Property(p___this, _p0); var expr_4 = global::System.Linq.Expressions.Expression.Constant(" ", typeof(string)); // " " - var expr_2 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_3, expr_4); + var expr_2 = global::System.Linq.Expressions.Expression.Call(_m0, expr_3, expr_4); var expr_5 = global::System.Linq.Expressions.Expression.Property(p___this, _p1); // LastName - var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_2, expr_5); + var expr_1 = global::System.Linq.Expressions.Expression.Call(_m0, expr_2, expr_5); var expr_6 = global::System.Linq.Expressions.Expression.Bind(_p0, p_firstName); var expr_7 = global::System.Linq.Expressions.Expression.Bind(_p1, p_lastName); var expr_8 = global::System.Linq.Expressions.Expression.Bind(_p2, expr_1); diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt index d23cbea..c05d228 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt @@ -10,6 +10,7 @@ namespace ExpressiveSharp.Generated { private static readonly global::System.Reflection.ConstructorInfo _c0 = typeof(global::Foo.PersonDto).GetConstructor(new global::System.Type[] { }); private static readonly global::System.Reflection.FieldInfo _f0 = typeof(global::Foo.PersonDto).GetField("Separator", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static); + private static readonly global::System.Reflection.MethodInfo _m0 = 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); private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.PersonDto).GetProperty("FullName"); // [Expressive] @@ -23,8 +24,8 @@ namespace ExpressiveSharp.Generated var p_last = global::System.Linq.Expressions.Expression.Parameter(typeof(string), "last"); var expr_0 = global::System.Linq.Expressions.Expression.New(_c0); var expr_3 = global::System.Linq.Expressions.Expression.Field(null, _f0); // Separator - var expr_2 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, p_first, expr_3); - var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_2, p_last); + var expr_2 = global::System.Linq.Expressions.Expression.Call(_m0, p_first, expr_3); + var expr_1 = global::System.Linq.Expressions.Expression.Call(_m0, expr_2, p_last); var expr_4 = global::System.Linq.Expressions.Expression.Bind(_p0, expr_1); var expr_5 = global::System.Linq.Expressions.Expression.MemberInit(expr_0, expr_4); return global::System.Linq.Expressions.Expression.Lambda>(expr_5, p_first, p_last); diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt index 512168a..eec40be 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt @@ -10,6 +10,7 @@ namespace ExpressiveSharp.Generated { private static readonly global::System.Reflection.ConstructorInfo _c0 = typeof(global::Foo.Child).GetConstructor(new global::System.Type[] { }); private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.Child).GetProperty("Name"); + private static readonly global::System.Reflection.MethodInfo _m0 = 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); // [Expressive] // public Child(int id, string name, string suffix) : this(id, name) @@ -24,7 +25,7 @@ namespace ExpressiveSharp.Generated var expr_0 = global::System.Linq.Expressions.Expression.New(_c0); var p___this = global::System.Linq.Expressions.Expression.Parameter(typeof(object), "@this"); // Name var expr_2 = global::System.Linq.Expressions.Expression.Property(p___this, _p0); - var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_2, p_suffix); + var expr_1 = global::System.Linq.Expressions.Expression.Call(_m0, expr_2, p_suffix); var expr_3 = global::System.Linq.Expressions.Expression.Bind(_p0, expr_1); var expr_4 = global::System.Linq.Expressions.Expression.MemberInit(expr_0, expr_3); return global::System.Linq.Expressions.Expression.Lambda>(expr_4, p_id, p_name, p_suffix); diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt index 88b6103..8ce8d5b 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt @@ -11,6 +11,7 @@ namespace ExpressiveSharp.Generated private static readonly global::System.Reflection.ConstructorInfo _c0 = typeof(global::Foo.PersonDto).GetConstructor(new global::System.Type[] { }); private static readonly global::System.Reflection.MethodInfo _m0 = typeof(string).GetMethod("ToUpper", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance, null, new global::System.Type[] { }, null); private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.PersonDto).GetProperty("FirstName"); + private static readonly global::System.Reflection.MethodInfo _m1 = 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); private static readonly global::System.Reflection.PropertyInfo _p1 = typeof(global::Foo.PersonDto).GetProperty("LastName"); private static readonly global::System.Reflection.PropertyInfo _p2 = typeof(global::Foo.PersonDto).GetProperty("FullName"); @@ -28,15 +29,15 @@ namespace ExpressiveSharp.Generated var p___this = global::System.Linq.Expressions.Expression.Parameter(typeof(object), "@this"); // FirstName var expr_5 = global::System.Linq.Expressions.Expression.Property(p___this, _p0); var expr_6 = global::System.Linq.Expressions.Expression.Constant(" ", typeof(string)); // " " - var expr_4 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_5, expr_6); + var expr_4 = global::System.Linq.Expressions.Expression.Call(_m1, expr_5, expr_6); var expr_7 = global::System.Linq.Expressions.Expression.Property(p___this, _p1); // LastName - var expr_3 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_4, expr_7); + var expr_3 = global::System.Linq.Expressions.Expression.Call(_m1, expr_4, expr_7); var expr_2 = global::System.Linq.Expressions.Expression.Call(expr_3, _m0, global::System.Array.Empty()); var expr_10 = global::System.Linq.Expressions.Expression.Property(p___this, _p0); // FirstName var expr_11 = global::System.Linq.Expressions.Expression.Constant(" ", typeof(string)); // " " - var expr_9 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_10, expr_11); + var expr_9 = global::System.Linq.Expressions.Expression.Call(_m1, expr_10, expr_11); var expr_12 = global::System.Linq.Expressions.Expression.Property(p___this, _p1); // LastName - var expr_8 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_9, expr_12); + var expr_8 = global::System.Linq.Expressions.Expression.Call(_m1, expr_9, expr_12); var expr_1 = global::System.Linq.Expressions.Expression.Condition(p_upper, expr_2, expr_8, typeof(string)); var expr_13 = global::System.Linq.Expressions.Expression.Bind(_p2, expr_1); var expr_14 = global::System.Linq.Expressions.Expression.MemberInit(expr_0, expr_13); diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt index 37a63b6..b7a3f7a 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt @@ -10,6 +10,7 @@ namespace ExpressiveSharp.Generated { private static readonly global::System.Reflection.ConstructorInfo _c0 = typeof(global::Foo.PersonDto).GetConstructor(new global::System.Type[] { }); private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.PersonDto).GetProperty("Label"); + private static readonly global::System.Reflection.MethodInfo _m0 = 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); // [Expressive] // public PersonDto(int score, string prefix) : this(score) @@ -23,7 +24,7 @@ namespace ExpressiveSharp.Generated var expr_0 = global::System.Linq.Expressions.Expression.New(_c0); var p___this = global::System.Linq.Expressions.Expression.Parameter(typeof(object), "@this"); // Label var expr_2 = global::System.Linq.Expressions.Expression.Property(p___this, _p0); - var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, p_prefix, expr_2); + var expr_1 = global::System.Linq.Expressions.Expression.Call(_m0, p_prefix, expr_2); var expr_3 = global::System.Linq.Expressions.Expression.Bind(_p0, expr_1); var expr_4 = global::System.Linq.Expressions.Expression.MemberInit(expr_0, expr_3); return global::System.Linq.Expressions.Expression.Lambda>(expr_4, p_score, p_prefix); diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithFullObject.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithFullObject.verified.txt index 71a72d4..b6149fb 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithFullObject.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithFullObject.verified.txt @@ -11,9 +11,10 @@ namespace ExpressiveSharp.Generated private static readonly global::System.Reflection.ConstructorInfo _c0 = typeof(global::Foo.CustomerDto).GetConstructor(new global::System.Type[] { }); private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.Customer).GetProperty("Id"); private static readonly global::System.Reflection.PropertyInfo _p1 = typeof(global::Foo.Customer).GetProperty("FirstName"); + private static readonly global::System.Reflection.MethodInfo _m0 = 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); private static readonly global::System.Reflection.PropertyInfo _p2 = typeof(global::Foo.Customer).GetProperty("LastName"); private static readonly global::System.Reflection.PropertyInfo _p3 = typeof(global::Foo.Customer).GetProperty("IsActive"); - private static readonly global::System.Reflection.MethodInfo _m0 = global::System.Linq.Enumerable.First(global::System.Linq.Enumerable.Where(typeof(global::System.Linq.Enumerable).GetMethods(global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static), m => m.Name == "Count" && m.IsGenericMethodDefinition && m.GetGenericArguments().Length == 1 && m.GetParameters().Length == 1)).MakeGenericMethod(typeof(global::Foo.Order)); + private static readonly global::System.Reflection.MethodInfo _m1 = global::System.Linq.Enumerable.First(global::System.Linq.Enumerable.Where(typeof(global::System.Linq.Enumerable).GetMethods(global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static), m => m.Name == "Count" && m.IsGenericMethodDefinition && m.GetGenericArguments().Length == 1 && m.GetParameters().Length == 1)).MakeGenericMethod(typeof(global::Foo.Order)); private static readonly global::System.Reflection.PropertyInfo _p4 = typeof(global::Foo.Customer).GetProperty("Orders"); private static readonly global::System.Reflection.PropertyInfo _p5 = typeof(global::Foo.CustomerDto).GetProperty("Id"); private static readonly global::System.Reflection.PropertyInfo _p6 = typeof(global::Foo.CustomerDto).GetProperty("FullName"); @@ -35,13 +36,13 @@ namespace ExpressiveSharp.Generated var expr_1 = global::System.Linq.Expressions.Expression.Property(p_customer, _p0); // customer.Id var expr_4 = global::System.Linq.Expressions.Expression.Property(p_customer, _p1); // customer.FirstName var expr_5 = global::System.Linq.Expressions.Expression.Constant(" ", typeof(string)); // " " - var expr_3 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_4, expr_5); + var expr_3 = global::System.Linq.Expressions.Expression.Call(_m0, expr_4, expr_5); var expr_6 = global::System.Linq.Expressions.Expression.Property(p_customer, _p2); // customer.LastName - var expr_2 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_3, expr_6); + var expr_2 = global::System.Linq.Expressions.Expression.Call(_m0, expr_3, expr_6); var expr_7 = global::System.Linq.Expressions.Expression.Property(p_customer, _p3); // customer.IsActive var expr_10 = global::System.Linq.Expressions.Expression.Property(p_customer, _p4); // customer.Orders var expr_9 = global::System.Linq.Expressions.Expression.Convert(expr_10, typeof(global::System.Collections.Generic.IEnumerable)); - var expr_8 = global::System.Linq.Expressions.Expression.Call(_m0, new global::System.Linq.Expressions.Expression[] { expr_9 }); + var expr_8 = global::System.Linq.Expressions.Expression.Call(_m1, new global::System.Linq.Expressions.Expression[] { expr_9 }); var expr_11 = global::System.Linq.Expressions.Expression.Bind(_p5, expr_1); var expr_12 = global::System.Linq.Expressions.Expression.Bind(_p6, expr_2); var expr_13 = global::System.Linq.Expressions.Expression.Bind(_p7, expr_7); diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithLocalVariable.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithLocalVariable.verified.txt index 659a1c1..d8962f3 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithLocalVariable.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithLocalVariable.verified.txt @@ -9,6 +9,7 @@ namespace ExpressiveSharp.Generated static class Foo_PersonDto__ctor_P0_string_P1_string { private static readonly global::System.Reflection.ConstructorInfo _c0 = typeof(global::Foo.PersonDto).GetConstructor(new global::System.Type[] { }); + private static readonly global::System.Reflection.MethodInfo _m0 = 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); private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.PersonDto).GetProperty("FullName"); // [Expressive] @@ -24,8 +25,8 @@ namespace ExpressiveSharp.Generated var expr_0 = global::System.Linq.Expressions.Expression.New(_c0); var expr_1 = global::System.Linq.Expressions.Expression.Variable(typeof(string), "full"); var expr_4 = global::System.Linq.Expressions.Expression.Constant(" ", typeof(string)); // " " - var expr_3 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, p_first, expr_4); - var expr_2 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_3, p_last); + var expr_3 = global::System.Linq.Expressions.Expression.Call(_m0, p_first, expr_4); + var expr_2 = global::System.Linq.Expressions.Expression.Call(_m0, expr_3, p_last); var expr_5 = global::System.Linq.Expressions.Expression.Assign(expr_1, expr_2); var expr_6 = global::System.Linq.Expressions.Expression.Bind(_p0, expr_1); var expr_7 = global::System.Linq.Expressions.Expression.MemberInit(expr_0, expr_6); diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithMultipleLocalVariables.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithMultipleLocalVariables.verified.txt index 8545218..ad593df 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithMultipleLocalVariables.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithMultipleLocalVariables.verified.txt @@ -10,6 +10,7 @@ namespace ExpressiveSharp.Generated { private static readonly global::System.Reflection.ConstructorInfo _c0 = typeof(global::Foo.AddressDto).GetConstructor(new global::System.Type[] { }); private static readonly global::System.Reflection.MethodInfo _m0 = typeof(string).GetMethod("Trim", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance, null, new global::System.Type[] { }, null); + private static readonly global::System.Reflection.MethodInfo _m1 = 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); private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.AddressDto).GetProperty("Street"); private static readonly global::System.Reflection.PropertyInfo _p1 = typeof(global::Foo.AddressDto).GetProperty("City"); private static readonly global::System.Reflection.PropertyInfo _p2 = typeof(global::Foo.AddressDto).GetProperty("Full"); @@ -36,11 +37,11 @@ namespace ExpressiveSharp.Generated var expr_5 = global::System.Linq.Expressions.Expression.Call(p_city, _m0, global::System.Array.Empty()); // city.Trim() var expr_6 = global::System.Linq.Expressions.Expression.Assign(expr_4, expr_5); var expr_11 = global::System.Linq.Expressions.Expression.Constant(", ", typeof(string)); // ", " - var expr_10 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_1, expr_11); - var expr_9 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_10, expr_4); + var expr_10 = global::System.Linq.Expressions.Expression.Call(_m1, expr_1, expr_11); + var expr_9 = global::System.Linq.Expressions.Expression.Call(_m1, expr_10, expr_4); var expr_12 = global::System.Linq.Expressions.Expression.Constant(", ", typeof(string)); // ", " - var expr_8 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_9, expr_12); - var expr_7 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_8, p_country); + var expr_8 = global::System.Linq.Expressions.Expression.Call(_m1, expr_9, expr_12); + var expr_7 = global::System.Linq.Expressions.Expression.Call(_m1, expr_8, p_country); var expr_13 = global::System.Linq.Expressions.Expression.Bind(_p0, expr_1); var expr_14 = global::System.Linq.Expressions.Expression.Bind(_p1, expr_4); var expr_15 = global::System.Linq.Expressions.Expression.Bind(_p2, expr_7); diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithSwitchExpression.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithSwitchExpression.verified.txt index 9859aab..34f31e5 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithSwitchExpression.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithSwitchExpression.verified.txt @@ -9,6 +9,7 @@ namespace ExpressiveSharp.Generated static class Foo_SeasonDto__ctor_P0_int { private static readonly global::System.Reflection.ConstructorInfo _c0 = typeof(global::Foo.SeasonDto).GetConstructor(new global::System.Type[] { }); + private static readonly global::System.Reflection.MethodInfo _m0 = 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); private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.SeasonDto).GetProperty("Name"); private static readonly global::System.Reflection.PropertyInfo _p1 = typeof(global::Foo.SeasonDto).GetProperty("Description"); @@ -61,7 +62,7 @@ namespace ExpressiveSharp.Generated var expr_31 = global::System.Linq.Expressions.Expression.Condition(expr_22, expr_30, expr_21, typeof(string)); var expr_33 = global::System.Linq.Expressions.Expression.Constant("Month: ", typeof(string)); // "Month: " var expr_34 = global::System.Linq.Expressions.Expression.Convert(p_month, typeof(object)); // month - var expr_32 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_33, expr_34); + var expr_32 = global::System.Linq.Expressions.Expression.Call(_m0, expr_33, expr_34); var expr_35 = global::System.Linq.Expressions.Expression.Bind(_p0, expr_31); var expr_36 = global::System.Linq.Expressions.Expression.Bind(_p1, expr_32); var expr_37 = global::System.Linq.Expressions.Expression.MemberInit(expr_0, expr_35, expr_36); diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithSwitchExpression_AndExtraProperty.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithSwitchExpression_AndExtraProperty.verified.txt index 2e41256..96f4743 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithSwitchExpression_AndExtraProperty.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithSwitchExpression_AndExtraProperty.verified.txt @@ -10,6 +10,8 @@ namespace ExpressiveSharp.Generated { private static readonly global::System.Reflection.ConstructorInfo _c0 = typeof(global::Foo.ShapeDto).GetConstructor(new global::System.Type[] { }); private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.ShapeDto).GetProperty("ShapeType"); + private static readonly global::System.Reflection.MethodInfo _m0 = 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); + private static readonly global::System.Reflection.MethodInfo _m1 = 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); private static readonly global::System.Reflection.PropertyInfo _p1 = typeof(global::Foo.ShapeDto).GetProperty("Sides"); private static readonly global::System.Reflection.PropertyInfo _p2 = typeof(global::Foo.ShapeDto).GetProperty("Description"); @@ -46,11 +48,11 @@ namespace ExpressiveSharp.Generated var p___this = global::System.Linq.Expressions.Expression.Parameter(typeof(object), "@this"); // ShapeType var expr_17 = global::System.Linq.Expressions.Expression.Property(p___this, _p0); var expr_18 = global::System.Linq.Expressions.Expression.Constant(" with ", typeof(string)); // " with " - var expr_16 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_17, expr_18); + var expr_16 = global::System.Linq.Expressions.Expression.Call(_m0, expr_17, expr_18); var expr_19 = global::System.Linq.Expressions.Expression.Convert(p_sides, typeof(object)); // sides - var expr_15 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_16, expr_19); + var expr_15 = global::System.Linq.Expressions.Expression.Call(_m1, expr_16, expr_19); var expr_20 = global::System.Linq.Expressions.Expression.Constant(" sides", typeof(string)); // " sides" - var expr_14 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_15, expr_20); + var expr_14 = global::System.Linq.Expressions.Expression.Call(_m0, expr_15, expr_20); var expr_21 = global::System.Linq.Expressions.Expression.Bind(_p1, p_sides); var expr_22 = global::System.Linq.Expressions.Expression.Bind(_p0, expr_13); var expr_23 = global::System.Linq.Expressions.Expression.Bind(_p2, expr_14); diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberOnInterface.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberOnInterface.verified.txt index 038f160..c7042ca 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberOnInterface.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberOnInterface.verified.txt @@ -9,7 +9,9 @@ namespace ExpressiveSharp.Generated static class Foo_IEntityExtensions_Label { private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.IEntity).GetProperty("Id"); + private static readonly global::System.Reflection.MethodInfo _m0 = 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); private static readonly global::System.Reflection.PropertyInfo _p1 = typeof(global::Foo.IEntity).GetProperty("Name"); + private static readonly global::System.Reflection.MethodInfo _m1 = 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); // [Expressive] // public string Label => e.Id + ": " + e.Name; @@ -20,9 +22,9 @@ namespace ExpressiveSharp.Generated var expr_3 = global::System.Linq.Expressions.Expression.Property(expr_4, _p0); 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.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_2, expr_5); + var expr_1 = global::System.Linq.Expressions.Expression.Call(_m0, expr_2, expr_5); var expr_6 = global::System.Linq.Expressions.Expression.Property(expr_4, _p1); // e.Name - var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_1, expr_6); + var expr_0 = global::System.Linq.Expressions.Expression.Call(_m1, expr_1, expr_6); return global::System.Linq.Expressions.Expression.Lambda>(expr_0, 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 e71a0e0..a93de60 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithMemberAccess.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ExtensionMemberTests.ExtensionMemberWithMemberAccess.verified.txt @@ -9,7 +9,9 @@ namespace ExpressiveSharp.Generated static class Foo_EntityExtensions_IdAndName { private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.Entity).GetProperty("Id"); + private static readonly global::System.Reflection.MethodInfo _m0 = 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); private static readonly global::System.Reflection.PropertyInfo _p1 = typeof(global::Foo.Entity).GetProperty("Name"); + private static readonly global::System.Reflection.MethodInfo _m1 = 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); // [Expressive] // public string IdAndName => e.Id + ": " + e.Name; @@ -20,9 +22,9 @@ namespace ExpressiveSharp.Generated var expr_3 = global::System.Linq.Expressions.Expression.Property(expr_4, _p0); 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.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_2, expr_5); + var expr_1 = global::System.Linq.Expressions.Expression.Call(_m0, expr_2, expr_5); var expr_6 = global::System.Linq.Expressions.Expression.Property(expr_4, _p1); // e.Name - var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_1, expr_6); + var expr_0 = global::System.Linq.Expressions.Expression.Call(_m1, expr_1, expr_6); return global::System.Linq.Expressions.Expression.Lambda>(expr_0, p__this); } } diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.IntPlusString.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.IntPlusString.verified.txt new file mode 100644 index 0000000..e06f538 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.IntPlusString.verified.txt @@ -0,0 +1,30 @@ +// +#nullable disable + +using Foo; + +namespace ExpressiveSharp.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_C_Label + { + private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.C).GetProperty("Id"); + private static readonly global::System.Reflection.MethodInfo _m0 = 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); + private static readonly global::System.Reflection.PropertyInfo _p1 = typeof(global::Foo.C).GetProperty("Name"); + private static readonly global::System.Reflection.MethodInfo _m1 = 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); + + // [Expressive] + // public string Label => Id + ": " + Name; + static global::System.Linq.Expressions.Expression> Expression() + { + var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.C), "@this"); + var expr_3 = global::System.Linq.Expressions.Expression.Property(p__this, _p0); // Id + var expr_2 = global::System.Linq.Expressions.Expression.Convert(expr_3, typeof(object)); + var expr_4 = global::System.Linq.Expressions.Expression.Constant(": ", typeof(string)); // ": " + var expr_1 = global::System.Linq.Expressions.Expression.Call(_m0, expr_2, expr_4); + var expr_5 = global::System.Linq.Expressions.Expression.Property(p__this, _p1); // Name + var expr_0 = global::System.Linq.Expressions.Expression.Call(_m1, expr_1, expr_5); + return global::System.Linq.Expressions.Expression.Lambda>(expr_0, p__this); + } + } +} diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.SingleStringConcat.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.SingleStringConcat.verified.txt new file mode 100644 index 0000000..7ac3fe3 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.SingleStringConcat.verified.txt @@ -0,0 +1,26 @@ +// +#nullable disable + +using Foo; + +namespace ExpressiveSharp.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_C_Joined + { + private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.C).GetProperty("A"); + private static readonly global::System.Reflection.PropertyInfo _p1 = typeof(global::Foo.C).GetProperty("B"); + private static readonly global::System.Reflection.MethodInfo _m0 = 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); + + // [Expressive] + // public string Joined => A + B; + static global::System.Linq.Expressions.Expression> Expression() + { + var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.C), "@this"); + var expr_1 = global::System.Linq.Expressions.Expression.Property(p__this, _p0); // A + var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, _p1); // B + var expr_0 = global::System.Linq.Expressions.Expression.Call(_m0, expr_1, expr_2); + return global::System.Linq.Expressions.Expression.Lambda>(expr_0, p__this); + } + } +} diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.StringPlusString.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.StringPlusString.verified.txt new file mode 100644 index 0000000..53be3aa --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.StringPlusString.verified.txt @@ -0,0 +1,28 @@ +// +#nullable disable + +using Foo; + +namespace ExpressiveSharp.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_C_FullName + { + private static readonly global::System.Reflection.PropertyInfo _p0 = typeof(global::Foo.C).GetProperty("First"); + private static readonly global::System.Reflection.MethodInfo _m0 = 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); + private static readonly global::System.Reflection.PropertyInfo _p1 = typeof(global::Foo.C).GetProperty("Last"); + + // [Expressive] + // public string FullName => First + " " + Last; + static global::System.Linq.Expressions.Expression> Expression() + { + var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.C), "@this"); + var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, _p0); // First + var expr_3 = global::System.Linq.Expressions.Expression.Constant(" ", typeof(string)); // " " + var expr_1 = global::System.Linq.Expressions.Expression.Call(_m0, expr_2, expr_3); + var expr_4 = global::System.Linq.Expressions.Expression.Property(p__this, _p1); // Last + var expr_0 = global::System.Linq.Expressions.Expression.Call(_m0, expr_1, expr_4); + return global::System.Linq.Expressions.Expression.Lambda>(expr_0, p__this); + } + } +} diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.cs b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.cs new file mode 100644 index 0000000..a5da230 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/StringConcatenationTests.cs @@ -0,0 +1,78 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using VerifyMSTest; +using ExpressiveSharp.Generator.Tests.Infrastructure; + +namespace ExpressiveSharp.Generator.Tests.ExpressiveGenerator; + +[TestClass] +public class StringConcatenationTests : GeneratorTestBase +{ + [TestMethod] + public Task StringPlusString() + { + var compilation = CreateCompilation( + """ + namespace Foo { + class C { + public string First { get; set; } + public string Last { get; set; } + + [Expressive] + public string FullName => First + " " + Last; + } + } + """); + var result = RunExpressiveGenerator(compilation); + + Assert.AreEqual(0, result.Diagnostics.Length); + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [TestMethod] + public Task IntPlusString() + { + var compilation = CreateCompilation( + """ + namespace Foo { + class C { + public int Id { get; set; } + public string Name { get; set; } + + [Expressive] + public string Label => Id + ": " + Name; + } + } + """); + var result = RunExpressiveGenerator(compilation); + + Assert.AreEqual(0, result.Diagnostics.Length); + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [TestMethod] + public Task SingleStringConcat() + { + var compilation = CreateCompilation( + """ + namespace Foo { + class C { + public string A { get; set; } + public string B { get; set; } + + [Expressive] + public string Joined => A + B; + } + } + """); + var result = RunExpressiveGenerator(compilation); + + Assert.AreEqual(0, result.Diagnostics.Length); + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } +} diff --git a/tests/ExpressiveSharp.IntegrationTests/Scenarios/Common/Tests/StringOperationTests.cs b/tests/ExpressiveSharp.IntegrationTests/Scenarios/Common/Tests/StringOperationTests.cs index ada1ec0..3b6381e 100644 --- a/tests/ExpressiveSharp.IntegrationTests/Scenarios/Common/Tests/StringOperationTests.cs +++ b/tests/ExpressiveSharp.IntegrationTests/Scenarios/Common/Tests/StringOperationTests.cs @@ -26,4 +26,23 @@ public async Task Select_Summary_ReturnsCorrectValues() }, results); } + + [TestMethod] + public async Task Select_SummaryConcat_ReturnsCorrectValues() + { + Expression> expr = o => o.SummaryConcat; + var expanded = (Expression>)expr.ExpandExpressives(); + + var results = await Runner.SelectAsync(expanded); + + CollectionAssert.AreEquivalent( + new[] + { + "Order #1: RUSH", + "Order #2: STD", + "Order #3: N/A", + "Order #4: SPECIAL", + }, + results); + } } diff --git a/tests/ExpressiveSharp.IntegrationTests/Scenarios/Store/Models/Order.cs b/tests/ExpressiveSharp.IntegrationTests/Scenarios/Store/Models/Order.cs index 926cf31..f7ba433 100644 --- a/tests/ExpressiveSharp.IntegrationTests/Scenarios/Store/Models/Order.cs +++ b/tests/ExpressiveSharp.IntegrationTests/Scenarios/Store/Models/Order.cs @@ -58,6 +58,10 @@ public string GetCategory() [Expressive] public string Summary => $"Order #{Id}: {Tag ?? "N/A"}"; + // String concatenation via + operator (tests Expression.Call(string.Concat) emission) + [Expressive] + public string SummaryConcat => "Order #" + Id + ": " + (Tag ?? "N/A"); + // Loop-based computed members (foreach → Expression.Loop) [Expressive(AllowBlockBody = true)] public int ItemCount() From e7bda3dd4d56c111210423792e32abf3ab9990f5 Mon Sep 17 00:00:00 2001 From: Koen Date: Thu, 26 Mar 2026 00:51:33 +0000 Subject: [PATCH 2/2] fix: Replace broken fallbacks in EnsureStringConcat methods with throws The fallback paths set field names but never emitted corresponding MethodInfo declarations, which would produce uncompilable generated code. Since string.Concat(string,string) and string.Concat(object,object) exist in all .NET versions, replace with InvalidOperationException. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Emitter/ExpressionTreeEmitter.cs | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs b/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs index 6eac0d3..04b3de1 100644 --- a/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs +++ b/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs @@ -2125,17 +2125,8 @@ private string EnsureStringConcatMethod() && m.Parameters[0].Type.SpecialType == SpecialType.System_String && m.Parameters[1].Type.SpecialType == SpecialType.System_String); - if (concatMethod is not null) - { - _concatMethodField = _fieldCache.EnsureMethodInfo(concatMethod); - } - else - { - // Fallback: emit inline reflection - _concatMethodField = "_stringConcat"; - _fieldCache.GetDeclarations(); // ensure we can add to it - } - + _concatMethodField = _fieldCache.EnsureMethodInfo(concatMethod + ?? throw new InvalidOperationException("string.Concat(string, string) not found in compilation")); return _concatMethodField; } @@ -2154,17 +2145,8 @@ private string EnsureStringConcatObjectMethod() && m.Parameters[0].Type.SpecialType == SpecialType.System_Object && m.Parameters[1].Type.SpecialType == SpecialType.System_Object); - if (concatMethod is not null) - { - _concatObjectMethodField = _fieldCache.EnsureMethodInfo(concatMethod); - } - else - { - // Fallback: emit inline reflection - _concatObjectMethodField = "_stringConcatObj"; - _fieldCache.GetDeclarations(); // ensure we can add to it - } - + _concatObjectMethodField = _fieldCache.EnsureMethodInfo(concatMethod + ?? throw new InvalidOperationException("string.Concat(object, object) not found in compilation")); return _concatObjectMethodField; }