diff --git a/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs b/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs index f9b037b..04b3de1 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 @@ -2096,20 +2125,31 @@ 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; } + 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); + + _concatObjectMethodField = _fieldCache.EnsureMethodInfo(concatMethod + ?? throw new InvalidOperationException("string.Concat(object, object) not found in compilation")); + return _concatObjectMethodField; + } + private INamedTypeSymbol? _enumerableType; private string? ResolveEnumerableMethod(string methodName, int paramCount, ITypeSymbol elementType) @@ -2460,6 +2500,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()