diff --git a/NuGet/AgileObjects.ReadableExpressions.3.3.0.nupkg b/NuGet/AgileObjects.ReadableExpressions.3.3.0.nupkg new file mode 100644 index 00000000..4e000bad Binary files /dev/null and b/NuGet/AgileObjects.ReadableExpressions.3.3.0.nupkg differ diff --git a/NuGet/AgileObjects.ReadableExpressions.3.3.0.snupkg b/NuGet/AgileObjects.ReadableExpressions.3.3.0.snupkg new file mode 100644 index 00000000..40fe63cf Binary files /dev/null and b/NuGet/AgileObjects.ReadableExpressions.3.3.0.snupkg differ diff --git a/NuGetPack.bat b/NuGetPack.bat deleted file mode 100644 index 99070c4f..00000000 --- a/NuGetPack.bat +++ /dev/null @@ -1,3 +0,0 @@ -@echo off -dotnet restore -msbuild ReadableExpressions /t:Pack /p:PackageOutputPath=../NuGet /p:Configuration=Release /v:m \ No newline at end of file diff --git a/ReadableExpressions.UnitTests.Common.Vb/PublicNamedIndex.vb b/ReadableExpressions.UnitTests.Common.Vb/PublicNamedIndex.vb new file mode 100644 index 00000000..49d04c4a --- /dev/null +++ b/ReadableExpressions.UnitTests.Common.Vb/PublicNamedIndex.vb @@ -0,0 +1,11 @@ +Public Class PublicNamedIndex(Of T) + + Public ReadOnly Property Value( + Optional indexOne As Integer = 1, + Optional indexTwo As Integer? = Nothing) As T + Get + Return Nothing + End Get + End Property + +End Class \ No newline at end of file diff --git a/ReadableExpressions.UnitTests.Common.Vb/ReadableExpressions.UnitTests.Common.Vb.vbproj b/ReadableExpressions.UnitTests.Common.Vb/ReadableExpressions.UnitTests.Common.Vb.vbproj new file mode 100644 index 00000000..316a39d7 --- /dev/null +++ b/ReadableExpressions.UnitTests.Common.Vb/ReadableExpressions.UnitTests.Common.Vb.vbproj @@ -0,0 +1,9 @@ + + + + net35;net461;netstandard1.3 + AgileObjects.ReadableExpressions.UnitTests.Common.Vb + AgileObjects.ReadableExpressions.UnitTests.Common.Vb + + + diff --git a/ReadableExpressions.UnitTests.Common/ReadableExpressions.UnitTests.Common.csproj b/ReadableExpressions.UnitTests.Common/ReadableExpressions.UnitTests.Common.csproj index cd9ba904..ba8d2182 100644 --- a/ReadableExpressions.UnitTests.Common/ReadableExpressions.UnitTests.Common.csproj +++ b/ReadableExpressions.UnitTests.Common/ReadableExpressions.UnitTests.Common.csproj @@ -16,6 +16,7 @@ + diff --git a/ReadableExpressions.UnitTests.Net35/ReadableExpressions.UnitTests.Net35.csproj b/ReadableExpressions.UnitTests.Net35/ReadableExpressions.UnitTests.Net35.csproj index 7591a1b7..19cc6f1f 100644 --- a/ReadableExpressions.UnitTests.Net35/ReadableExpressions.UnitTests.Net35.csproj +++ b/ReadableExpressions.UnitTests.Net35/ReadableExpressions.UnitTests.Net35.csproj @@ -7,7 +7,6 @@ true false - {9EB2525F-48E7-4057-98E5-7FE325ECCD32} @@ -15,7 +14,7 @@ - $(DefineConstants);TRACE + $(DefineConstants);TRACE;FEATURE_PROPERTY_INDEX_DEFAULTS @@ -25,10 +24,8 @@ - - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -36,7 +33,6 @@ - diff --git a/ReadableExpressions.UnitTests.Net5/ReadableExpressions.UnitTests.Net5.csproj b/ReadableExpressions.UnitTests.Net5/ReadableExpressions.UnitTests.Net5.csproj new file mode 100644 index 00000000..ed416c14 --- /dev/null +++ b/ReadableExpressions.UnitTests.Net5/ReadableExpressions.UnitTests.Net5.csproj @@ -0,0 +1,50 @@ + + + + net5.0 + AgileObjects.ReadableExpressions.UnitTests.Net5 + AgileObjects.ReadableExpressions.UnitTests + true + + 0649;1701;1702 + true + false + false + false + false + + + + $(DefineConstants);TRACE;FEATURE_VALUE_TUPLE + + + + + Properties\ClsCompliant.cs + + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + diff --git a/ReadableExpressions.UnitTests.Net6/ReadableExpressions.UnitTests.Net6.csproj b/ReadableExpressions.UnitTests.Net6/ReadableExpressions.UnitTests.Net6.csproj new file mode 100644 index 00000000..d0a42b29 --- /dev/null +++ b/ReadableExpressions.UnitTests.Net6/ReadableExpressions.UnitTests.Net6.csproj @@ -0,0 +1,50 @@ + + + + net6.0 + AgileObjects.ReadableExpressions.UnitTests.Net6 + AgileObjects.ReadableExpressions.UnitTests + true + + 0649;1701;1702 + true + false + false + false + false + + + + $(DefineConstants);TRACE;FEATURE_VALUE_TUPLE + + + + + Properties\ClsCompliant.cs + + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + diff --git a/ReadableExpressions.UnitTests.NetCore/ReadableExpressions.UnitTests.NetCore.csproj b/ReadableExpressions.UnitTests.NetCore1/ReadableExpressions.UnitTests.NetCore1.csproj similarity index 92% rename from ReadableExpressions.UnitTests.NetCore/ReadableExpressions.UnitTests.NetCore.csproj rename to ReadableExpressions.UnitTests.NetCore1/ReadableExpressions.UnitTests.NetCore1.csproj index 2925ccce..68637d1a 100644 --- a/ReadableExpressions.UnitTests.NetCore/ReadableExpressions.UnitTests.NetCore.csproj +++ b/ReadableExpressions.UnitTests.NetCore1/ReadableExpressions.UnitTests.NetCore1.csproj @@ -2,7 +2,6 @@ netcoreapp1.0 - 1.1.13 8.0 AgileObjects.ReadableExpressions.UnitTests.NetCore AgileObjects.ReadableExpressions.UnitTests @@ -36,7 +35,7 @@ - + @@ -47,7 +46,6 @@ - diff --git a/ReadableExpressions.UnitTests.NetCore2/ReadableExpressions.UnitTests.NetCore2.csproj b/ReadableExpressions.UnitTests.NetCore2/ReadableExpressions.UnitTests.NetCore2.csproj new file mode 100644 index 00000000..2d83d3d4 --- /dev/null +++ b/ReadableExpressions.UnitTests.NetCore2/ReadableExpressions.UnitTests.NetCore2.csproj @@ -0,0 +1,50 @@ + + + + netcoreapp2.1 + AgileObjects.ReadableExpressions.UnitTests.NetCore2 + AgileObjects.ReadableExpressions.UnitTests + true + + 0649;1701;1702 + true + false + false + false + false + + + + $(DefineConstants);TRACE;FEATURE_VALUE_TUPLE + + + + + Properties\ClsCompliant.cs + + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + diff --git a/ReadableExpressions.UnitTests.NetCore3.1/ReadableExpressions.UnitTests.NetCore3.1.csproj b/ReadableExpressions.UnitTests.NetCore3/ReadableExpressions.UnitTests.NetCore3.csproj similarity index 91% rename from ReadableExpressions.UnitTests.NetCore3.1/ReadableExpressions.UnitTests.NetCore3.1.csproj rename to ReadableExpressions.UnitTests.NetCore3/ReadableExpressions.UnitTests.NetCore3.csproj index 602f8fbc..5bb5b21e 100644 --- a/ReadableExpressions.UnitTests.NetCore3.1/ReadableExpressions.UnitTests.NetCore3.1.csproj +++ b/ReadableExpressions.UnitTests.NetCore3/ReadableExpressions.UnitTests.NetCore3.csproj @@ -2,8 +2,7 @@ netcoreapp3.1 - 3.1.3 - AgileObjects.ReadableExpressions.UnitTests.NetCore3_1 + AgileObjects.ReadableExpressions.UnitTests.NetCore3 AgileObjects.ReadableExpressions.UnitTests true @@ -36,7 +35,7 @@ - + all @@ -46,7 +45,6 @@ - diff --git a/ReadableExpressions.UnitTests/ReadableExpressions.UnitTests.csproj b/ReadableExpressions.UnitTests/ReadableExpressions.UnitTests.csproj index e0e322d4..d22c1cee 100644 --- a/ReadableExpressions.UnitTests/ReadableExpressions.UnitTests.csproj +++ b/ReadableExpressions.UnitTests/ReadableExpressions.UnitTests.csproj @@ -10,13 +10,12 @@ - $(DefineConstants);TRACE;FEATURE_EF;FEATURE_PARTIAL_TRUST;FEATURE_DYNAMIC;FEATURE_VALUE_TUPLE;FEATURE_HTML + $(DefineConstants);TRACE;FEATURE_EF;FEATURE_PARTIAL_TRUST;FEATURE_DYNAMIC;FEATURE_VALUE_TUPLE;FEATURE_HTML;FEATURE_PROPERTY_INDEX_DEFAULTS - all @@ -28,7 +27,6 @@ - diff --git a/ReadableExpressions.UnitTests/Translations/WhenTranslatingMethodCalls.cs b/ReadableExpressions.UnitTests/Translations/WhenTranslatingMethodCalls.cs index b71a36d4..0f64e4b0 100644 --- a/ReadableExpressions.UnitTests/Translations/WhenTranslatingMethodCalls.cs +++ b/ReadableExpressions.UnitTests/Translations/WhenTranslatingMethodCalls.cs @@ -34,8 +34,7 @@ public void ShouldTranslateAnInstanceCallExpression() [Fact] public void ShouldTranslateAStaticCallExpression() { - // ReSharper disable once ReferenceEqualsWithValueType - var oneEqualsTwo = CreateLambda(() => ReferenceEquals(1, 2)); + var oneEqualsTwo = CreateLambda(() => ReferenceEquals("1", "2")); var referenceEqualsCall = (MethodCallExpression)oneEqualsTwo.Body; var context = new TestTranslationContext(referenceEqualsCall); var referenceEqualsMethod = new ClrMethodWrapper(referenceEqualsCall.Method, context); @@ -45,7 +44,7 @@ public void ShouldTranslateAStaticCallExpression() var translated = new TestTranslationWriter(translation).GetContent(); - translated.ShouldBe("object.ReferenceEquals(1, 2)"); + translated.ShouldBe("object.ReferenceEquals(\"1\", \"2\")"); } [Fact] diff --git a/ReadableExpressions.UnitTests/WhenFormattingCode.cs b/ReadableExpressions.UnitTests/WhenFormattingCode.cs index 28b16c7d..f5d53436 100644 --- a/ReadableExpressions.UnitTests/WhenFormattingCode.cs +++ b/ReadableExpressions.UnitTests/WhenFormattingCode.cs @@ -356,7 +356,7 @@ public void ShouldVarAssignVariablesInSiblingBlocks() } [Fact] - public void ShouldNotVarAssignAVariableAssignedInATryButUsedInACatch() + public void ShouldNotVarAssignVariablesAssignedInATryAndUsedInACatch() { var exceptionFactory = CreateLambda((int number) => new Exception(number.ToString())); var intVariable = exceptionFactory.Parameters.First(); @@ -385,7 +385,7 @@ public void ShouldNotVarAssignAVariableAssignedInATryButUsedInACatch() } [Fact] - public void ShouldVarAssignAVariableUsedInNestedConstructs() + public void ShouldVarAssignVariablesUsedInNestedConstructs() { var returnLabel = Label(typeof(long), "Return"); var streamVariable = Variable(typeof(Stream), "stream"); @@ -488,7 +488,7 @@ public void ShouldVarAssignAVariableUsedInNestedConstructs() } [Fact] - public void ShouldVarAssignAVariableReusedInASiblingBlock() + public void ShouldVarAssignVariablesReusedInSiblingConstructs() { var intVariable = Variable(typeof(int), "i"); var assignVariable1 = Assign(intVariable, Constant(1)); @@ -516,6 +516,76 @@ public void ShouldVarAssignAVariableReusedInASiblingBlock() translated.ShouldBe(EXPECTED.TrimStart()); } + [Fact] + public void ShouldNotVarAssignEquivalentVariablesUsedInSiblingExpressions() + { + var intVariable1 = Variable(typeof(int), "i"); + var assignVariable1 = Assign(intVariable1, Constant(1)); + + var intVariable2 = Variable(typeof(int), "i"); + var assignVariable2 = Assign(intVariable2, Constant(2)); + + var assignmentsBlock = Block( + new[] { intVariable1, intVariable2 }, + assignVariable1, + assignVariable2); + + var translated = assignmentsBlock.ToReadableString(); + + const string EXPECTED = @" +var i = 1; +i = 2;"; + translated.ShouldBe(EXPECTED.TrimStart()); + } + + [Fact] + public void ShouldNotVarAssignEquivalentVariablesUsedInNestedConstructs() + { + var intVariable1 = Variable(typeof(int), "i"); + var assignVariable1 = Assign(intVariable1, Constant(1)); + + var intVariable2 = Variable(typeof(int), "i"); + var assignVariable2 = Assign(intVariable2, Constant(2)); + var assignment2Block = Block(new[] { intVariable2 }, assignVariable2); + + var assignment2Construct = IfThen( + GreaterThan(intVariable1, Constant(0)), + assignment2Block); + + var assignmentsBlock = Block( + new[] { intVariable1 }, + assignVariable1, + assignment2Construct); + + var translated = assignmentsBlock.ToReadableString(); + + const string EXPECTED = @" +var i = 1; + +if (i > 0) +{ + i = 2; +}"; + translated.ShouldBe(EXPECTED.TrimStart()); + } + + [Fact] + public void ShouldNotVarAssignEquivalentLambdaParametersAssignedInTheBody() + { + var intParameter = Parameter(typeof(int), "i"); + + var intVariable = Parameter(typeof(int), "i"); + var assignVariable = Assign(intVariable, Constant(123)); + var assignmentBlock = Block(new[] { intVariable }, assignVariable); + + var lambda = Lambda>(assignmentBlock, intParameter); + + var translated = lambda.ToReadableString(); + + const string EXPECTED = "i => i = 123"; + translated.ShouldBe(EXPECTED.TrimStart()); + } + [Fact] public void ShouldNotIndentParamsArrayArguments() { @@ -787,6 +857,34 @@ public void ShouldConvertADelegateMethodArgumentToAMethodGroup() translated.ShouldBe("evaluatorInvoker.Invoke(list.Contains, i)"); } + [Fact] + public void ShouldNotEvaluateMethodGroupArgumentParameters() + { + var copy = CreateLambda((List list, ICollection items) => + list.ForEach(idx => items.Add(idx))); + + var methodCall = (MethodCallExpression)copy.Body; + var methodCallLambdaArgument = (LambdaExpression)methodCall.Arguments[0]; + var methodCallLambdaParameter = methodCallLambdaArgument.Parameters[0]; + + var parameterAssignment = Assign( + methodCallLambdaParameter, + Constant(123)); + + var block = Block( + new[] { methodCallLambdaParameter }, + methodCall, + parameterAssignment); + + var translated = block.ToReadableString(); + + const string EXPECTED = @" +list.ForEach(items.Add); +var idx = 123;"; + + translated.ShouldBe(EXPECTED.TrimStart()); + } + [Fact] public void ShouldNotConvertAModifyingArgumentToAMethodGroup() { diff --git a/ReadableExpressions.UnitTests/WhenTranslatingConstants.cs b/ReadableExpressions.UnitTests/WhenTranslatingConstants.cs index 81f340ab..dece393f 100644 --- a/ReadableExpressions.UnitTests/WhenTranslatingConstants.cs +++ b/ReadableExpressions.UnitTests/WhenTranslatingConstants.cs @@ -79,7 +79,36 @@ public void ShouldTranslateAVerbatimStringWithDoubleQuotes() 12 """"1""""2 """); + } + + [Fact] + public void ShouldTranslateAStringWithACarriageReturnNewline() + { + var stringConstant = Constant("hello\r\nthere!", typeof(string)); + var translated = stringConstant.ToReadableString(); + + translated.ShouldBe(@"@""hello +there!"""); + } + + // See https://github.com/agileobjects/ReadableExpressions/issues/107 + [Fact] + public void ShouldTranslateAStringWithJustACarriageReturnline() + { + var stringConstant = Constant("hello\rthere!", typeof(string)); + var translated = stringConstant.ToReadableString(); + + translated.ShouldBe(@"@""hello" + '\r' + @"there!"""); + } + + // See https://github.com/agileobjects/ReadableExpressions/issues/107 + [Fact] + public void ShouldTranslateAStringWithJustANewline() + { + var stringConstant = Constant("hello\nthere!", typeof(string)); + var translated = stringConstant.ToReadableString(); + translated.ShouldBe(@"@""hello" + '\n' + @"there!"""); } [Fact] diff --git a/ReadableExpressions.UnitTests/WhenTranslatingExtensions.cs b/ReadableExpressions.UnitTests/WhenTranslatingExtensions.cs index a9381e98..11f8da43 100644 --- a/ReadableExpressions.UnitTests/WhenTranslatingExtensions.cs +++ b/ReadableExpressions.UnitTests/WhenTranslatingExtensions.cs @@ -36,6 +36,15 @@ public void ShouldTranslateAnUnknownExpressionType() translated.ShouldBe(unknown.ToString()); } + [Fact] + public void ShouldTranslateANullExpressionString() + { + var nullToString = new NullToStringExpression(); + var translated = nullToString.ToReadableString(); + + translated.ShouldBeNull(); + } + [Fact] public void ShouldTranslateACustomTranslationExpression() { @@ -63,7 +72,7 @@ public void ShouldAnalyseACustomAnalysableExpression() analysis.EnterScope(variable1Block); analysis.ShouldBeDeclaredInVariableList(intVariable1).ShouldBeFalse(); analysis.ExitScope(); - + analysis.EnterScope(variable2Block); analysis.ShouldBeDeclaredInVariableList(intVariable2).ShouldBeFalse(); analysis.ExitScope(); @@ -111,6 +120,21 @@ public override string ToString() } } + internal class NullToStringExpression : Expression + { + public override ExpressionType NodeType => ExpressionType.Extension; + + public override Type Type => typeof(string); + + protected override Expression VisitChildren(ExpressionVisitor visitor) + { + // See ExtensionExpression for why this is necessary: + return this; + } + + public override string ToString() => null!; + } + internal class CustomTranslationExpression : Expression, ICustomTranslationExpression { public override ExpressionType NodeType => ExpressionType.Extension; diff --git a/ReadableExpressions.UnitTests/WhenTranslatingLambdas.cs b/ReadableExpressions.UnitTests/WhenTranslatingLambdas.cs index 9812bed5..5a44d4ce 100644 --- a/ReadableExpressions.UnitTests/WhenTranslatingLambdas.cs +++ b/ReadableExpressions.UnitTests/WhenTranslatingLambdas.cs @@ -111,7 +111,7 @@ public void ShouldTranslateQuotedLambdaWithAnAnnotation() var quotedLambda = Quote(intToDouble); - var translated = quotedLambda.ToReadableString(stgs => + var translated = quotedLambda.ToReadableString(stgs => stgs.ShowQuotedLambdaComments); const string EXPECTED = @" @@ -165,6 +165,192 @@ public void ShouldTranslateWithoutError() sourceLambda.ToReadableString(); } + // See https://github.com/agileobjects/ReadableExpressions/issues/106 + [Fact] + public void ShouldTranslateADelegateLambda() + { + var boolParameter = Parameter(typeof(bool), "regBool"); + var boolAssignment1 = Assign(boolParameter, Constant(false)); + var boolAssignment2 = Assign(boolParameter, Constant(true)); + + var delegateLambda = Lambda( + Block(boolAssignment1, boolAssignment2), + boolParameter); + + const string EXPECTED = @" +regBool => +{ + regBool = false; + regBool = true; +}"; + var translated = delegateLambda.ToReadableString(); + + translated.ShouldBe(EXPECTED.TrimStart()); + } + + // See https://github.com/agileobjects/ReadableExpressions/issues/106 + [Fact] + public void ShouldIncludeAnOutParameterKeyword() + { + var boolParameter = Parameter(typeof(bool).MakeByRefType(), "outBool"); + var boolAssignment1 = Assign(boolParameter, Constant(false)); + var boolAssignment2 = Assign(boolParameter, Constant(true)); + + var delegateLambda = Lambda( + Block(boolAssignment1, boolAssignment2), + boolParameter); + + const string EXPECTED = @" +(out bool outBool) => +{ + outBool = false; + outBool = true; +}"; + var translated = delegateLambda.ToReadableString(); + + translated.ShouldBe(EXPECTED.TrimStart()); + } + + // See https://github.com/agileobjects/ReadableExpressions/issues/106 + [Fact] + public void ShouldIncludeARefParameterKeyword() + { + var boolParameter = Parameter(typeof(bool).MakeByRefType(), "refBool"); + var boolAssignment1 = Assign(boolParameter, Constant(false)); + var boolAssignment2 = Assign(boolParameter, Constant(true)); + + var delegateLambda = Lambda( + Block(boolAssignment1, boolAssignment2), + boolParameter); + + const string EXPECTED = @" +(ref bool refBool) => +{ + refBool = false; + refBool = true; +}"; + var translated = delegateLambda.ToReadableString(); + + translated.ShouldBe(EXPECTED.TrimStart()); + } + + // See https://github.com/agileobjects/ReadableExpressions/issues/106 + [Fact] + public void ShouldTranslateAParamsParameterDelegate() + { + var boolsParameter = Parameter(typeof(bool[]), "bools"); + var zeroethBool = ArrayAccess(boolsParameter, Constant(0)); + var boolAssignment1 = Assign(zeroethBool, Constant(false)); + var boolAssignment2 = Assign(zeroethBool, Constant(true)); + + var delegateLambda = Lambda( + Block(boolAssignment1, boolAssignment2), + boolsParameter); + + const string EXPECTED = @" +bools => +{ + bools[0] = false; + bools[0] = true; +}"; + var translated = delegateLambda.ToReadableString(); + + translated.ShouldBe(EXPECTED.TrimStart()); + } + + // See https://github.com/agileobjects/ReadableExpressions/issues/106 + [Fact] + public void ShouldTranslateARegularParameterAndParamsParameterDelegate() + { + var intParameter = Parameter(typeof(int), "regInt"); + var boolsParameter = Parameter(typeof(bool[]), "bools"); + var zeroethBool = ArrayAccess(boolsParameter, Constant(0)); + var boolAssignment1 = Assign(zeroethBool, Constant(false)); + var boolAssignment2 = Assign(zeroethBool, Constant(true)); + + var delegateLambda = Lambda( + Block(boolAssignment1, boolAssignment2), + intParameter, + boolsParameter); + + const string EXPECTED = @" +(regInt, bools) => +{ + bools[0] = false; + bools[0] = true; +}"; + var translated = delegateLambda.ToReadableString(); + + translated.ShouldBe(EXPECTED.TrimStart()); + } + + // See https://github.com/agileobjects/ReadableExpressions/issues/106 + [Fact] + public void ShouldTranslateAnOutParameterAndParamsParameterDelegate() + { + var intParameter = Parameter(typeof(int).MakeByRefType(), "outInt"); + var boolsParameter = Parameter(typeof(bool[]), "bools"); + var zeroethBool = ArrayAccess(boolsParameter, Constant(0)); + var boolAssignment1 = Assign(zeroethBool, Constant(false)); + var boolAssignment2 = Assign(zeroethBool, Constant(true)); + + var delegateLambda = Lambda( + Block(boolAssignment1, boolAssignment2), + intParameter, + boolsParameter); + + const string EXPECTED = @" +(out int outInt, bool[] bools) => +{ + bools[0] = false; + bools[0] = true; +}"; + var translated = delegateLambda.ToReadableString(); + + translated.ShouldBe(EXPECTED.TrimStart()); + } + + // See https://github.com/agileobjects/ReadableExpressions/issues/106 + [Fact] + public void ShouldTranslateAMultiParameterDelegate() + { + var boolParameter = Parameter(typeof(bool), "regBool"); + var refBoolParameter = Parameter(typeof(bool).MakeByRefType(), "refBool"); + var outBoolParameter = Parameter(typeof(bool).MakeByRefType(), "outBool"); + + var outBoolAssignment1 = Assign(outBoolParameter, Constant(false)); + var outBoolAssignment2 = Assign(outBoolParameter, Constant(true)); + + var delegateLambda = Lambda( + Block(outBoolAssignment1, outBoolAssignment2), + boolParameter, + refBoolParameter, + outBoolParameter); + + const string EXPECTED = @" +(bool regBool, ref bool refBool, out bool outBool) => +{ + outBool = false; + outBool = true; +}"; + var translated = delegateLambda.ToReadableString(); + + translated.ShouldBe(EXPECTED.TrimStart()); + } + + [Fact] + public void ShouldDiscardAnUnusedParameter() + { + var linqSelect = CreateLambda((string[] ints, int index) + => ints.Select(int.Parse)); + + var translated = linqSelect.ToReadableString(stgs => stgs + .ShowLambdaParameterTypes + .DiscardUnusedParameters); + + translated.ShouldBe("(string[] ints, int _) => ints.Select(int.Parse)"); + } + #region Helper Members private static class Issue49 @@ -177,6 +363,17 @@ public class DerivedEntity : EntityBase } } + private static class Issue106 + { + public delegate void OutDelegate(out bool outBool); + public delegate void RefDelegate(ref bool refBool); + public delegate void RegularDelegate(bool regBool); + public delegate void ParamsDelegate(params bool[] boolParams); + public delegate void RegAndParamsDelegate(int regInt, params bool[] boolParams); + public delegate void OutAndParamsDelegate(out int outInt, params bool[] boolParams); + public delegate void MixedDelegate(bool regBool, ref bool refBool, out bool outBool); + } + #endregion } } diff --git a/ReadableExpressions.UnitTests/WhenTranslatingMemberAccesses.cs b/ReadableExpressions.UnitTests/WhenTranslatingMemberAccesses.cs index 89b0c90f..93d2b816 100644 --- a/ReadableExpressions.UnitTests/WhenTranslatingMemberAccesses.cs +++ b/ReadableExpressions.UnitTests/WhenTranslatingMemberAccesses.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.Linq; using Common; + using Common.Vb; using NetStandardPolyfills; #if !NET35 using System.Linq.Expressions; @@ -105,12 +106,11 @@ public void ShouldTranslateAnExtensionMethodCallWithTypedLambdaParameter() [Fact] public void ShouldTranslateAStaticCallExpression() { - // ReSharper disable once ReferenceEqualsWithValueType - var oneEqualsTwo = CreateLambda(() => ReferenceEquals(1, 2)); + var oneEqualsTwo = CreateLambda(() => ReferenceEquals("1", "2")); var translated = oneEqualsTwo.ToReadableString(); - translated.ShouldBe("() => object.ReferenceEquals(1, 2)"); + translated.ShouldBe("() => object.ReferenceEquals(\"1\", \"2\")"); } [Fact] @@ -378,6 +378,33 @@ public void ShouldTranslateAManualIndexedPropertyAccessExpression() translated.ShouldBe("p[1]"); } +#if FEATURE_PROPERTY_INDEX_DEFAULTS + [Fact] + public void ShouldTranslateANamedIndexedPropertyAccessExpression() + { + var instance = Variable(typeof(PublicNamedIndex), "instance"); + var indexProperty = instance.Type.GetPublicInstanceProperty("Value"); + var indexAccess = Property(instance, indexProperty); + + var translated = indexAccess.ToReadableString(); + + translated.ShouldBe("instance.get_Value(1, null)"); + } +#endif + [Fact] + public void ShouldTranslateANamedIndexedPropertyAccessExpressionWithArguments() + { + var instance = Variable(typeof(PublicNamedIndex), "instance"); + var indexProperty = instance.Type.GetPublicInstanceProperty("Value"); + var index1 = Constant(123); + var index2 = Constant(456, typeof(int?)); + var indexAccess = Property(instance, indexProperty, index1, index2); + + var translated = indexAccess.ToReadableString(); + + translated.ShouldBe("instance.get_Value(123, 456)"); + } + [Fact] public void ShouldTranslateAStringIndexAccessExpression() { @@ -686,7 +713,8 @@ public void ShouldIncludeAnInlineOutParameterVariable() var tryGetBlock = Block(new[] { valueVariable }, tryGetCall, valueVariable); var tryGetLambda = Lambda>(tryGetBlock, helperParameter); - var translated = tryGetLambda.ToReadableString(stgs => stgs.DeclareOutputParametersInline); + var translated = tryGetLambda.ToReadableString(stgs => stgs + .DeclareOutputParametersInline); const string EXPECTED = @" ip => @@ -753,6 +781,57 @@ public void ShouldIncludeAnExplicitlyTypedFullyQualifiedInlineOutParameterVariab translated.ShouldBe(EXPECTED.TrimStart()); } + [Fact] + public void ShouldIncludeAnInlineOutParameterDiscard() + { + var helperParameter = Parameter(typeof(IndexedProperty), "ip"); + var intParameter = Parameter(typeof(int), "i"); + var valueVariable = Variable(typeof(object), "value"); + var tryGetMethod = typeof(IndexedProperty).GetPublicInstanceMethod("TryGet"); + var tryGetCall = Call(helperParameter, tryGetMethod, intParameter, valueVariable); + var tryGetBlock = Block(new[] { valueVariable }, tryGetCall); + + var tryGetLambda = Lambda>( + tryGetBlock, + helperParameter, + intParameter); + + var translated = tryGetLambda.ToReadableString(stgs => stgs + .DiscardUnusedParameters); + + translated.ShouldBe("(ip, i) => ip.TryGet(i, out _)"); + } + + [Fact] + public void ShouldNotDiscardAPreviouslyUsedOutParameter() + { + var helperParameter = Parameter(typeof(IndexedProperty), "ip"); + var valueVariable = Variable(typeof(object), "value"); + var assignValue = Assign(valueVariable, Constant(123, typeof(object))); + + var tryGetMethod = typeof(IndexedProperty).GetPublicInstanceMethod("TryGet"); + var zero = Constant(0); + var tryGetCall = Call(helperParameter, tryGetMethod, zero, valueVariable); + var tryGetBlock = Block(new[] { valueVariable }, assignValue, tryGetCall); + + var tryGetLambda = Lambda>( + tryGetBlock, + helperParameter); + + var translated = tryGetLambda.ToReadableString(stgs => stgs + .DeclareOutputParametersInline + .DiscardUnusedParameters); + + const string EXPECTED = @" +ip => +{ + var value = 123; + + return ip.TryGet(0, out value); +}"; + translated.ShouldBe(EXPECTED.TrimStart()); + } + [Fact] public void ShouldOnlyDeclareAnOutParameterVariableInlineOnce() { diff --git a/ReadableExpressions.sln b/ReadableExpressions.sln index ed7c97bf..e59affc5 100644 --- a/ReadableExpressions.sln +++ b/ReadableExpressions.sln @@ -34,8 +34,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.Visuali EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReadableExpressions.Visualizers.Console", "ReadableExpressions.Visualizers.Console\ReadableExpressions.Visualizers.Console.csproj", "{26270B39-08A9-47ED-AE24-73A81CA0FDDE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.UnitTests.NetCore3.1", "ReadableExpressions.UnitTests.NetCore3.1\ReadableExpressions.UnitTests.NetCore3.1.csproj", "{35177AF2-C17C-4C32-A2B4-838584CA86F2}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.Visualizers.Console.NetCore", "ReadableExpressions.Visualizers.Console.NetCore\ReadableExpressions.Visualizers.Console.NetCore.csproj", "{F679DB41-C60F-48C0-8D22-50390238A5A1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.Visualizers.Dialog", "ReadableExpressions.Visualizers.Dialog\ReadableExpressions.Visualizers.Dialog.csproj", "{1DF26474-08FC-45BB-A0C3-7634BD0B6C0F}" @@ -66,12 +64,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.Visuali EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.UnitTests.Common", "ReadableExpressions.UnitTests.Common\ReadableExpressions.UnitTests.Common.csproj", "{7000A01B-B4DA-4F30-87FF-B3EA4057BBE5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.UnitTests.NetCore", "ReadableExpressions.UnitTests.NetCore\ReadableExpressions.UnitTests.NetCore.csproj", "{7CE50AB9-9A8D-4D91-80CB-4F6F94F79CF9}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.Visualizers.Vs17.ObjectSource", "ReadableExpressions.Visualizers.Vs17.ObjectSource\ReadableExpressions.Visualizers.Vs17.ObjectSource.csproj", "{D78B0491-4460-4918-9BEA-D886A80D7847}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.Visualizers.Vs17", "ReadableExpressions.Visualizers.Vs17\ReadableExpressions.Visualizers.Vs17.csproj", "{BF07061B-7B94-4DD2-BCB8-63E491CA7463}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.UnitTests.NetCore1", "ReadableExpressions.UnitTests.NetCore1\ReadableExpressions.UnitTests.NetCore1.csproj", "{78774489-9988-4CF3-93E5-FB91A2D2764B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.UnitTests.NetCore2", "ReadableExpressions.UnitTests.NetCore2\ReadableExpressions.UnitTests.NetCore2.csproj", "{B43FFC6D-9FF1-49C7-A41A-F427231B056B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.UnitTests.NetCore3", "ReadableExpressions.UnitTests.NetCore3\ReadableExpressions.UnitTests.NetCore3.csproj", "{34E4702D-8D93-4D39-8D46-063892C848FD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.UnitTests.Net5", "ReadableExpressions.UnitTests.Net5\ReadableExpressions.UnitTests.Net5.csproj", "{49F670E1-0D48-47B6-8C49-00284A19C232}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.UnitTests.Net6", "ReadableExpressions.UnitTests.Net6\ReadableExpressions.UnitTests.Net6.csproj", "{C00A01BD-4C32-4C77-975B-D3D7CE1AC8B1}" +EndProject +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "ReadableExpressions.UnitTests.Common.Vb", "ReadableExpressions.UnitTests.Common.Vb\ReadableExpressions.UnitTests.Common.Vb.vbproj", "{5AA5C674-F7FE-4FF5-98B6-03C9F9B70D1B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -167,14 +175,6 @@ Global {26270B39-08A9-47ED-AE24-73A81CA0FDDE}.Release|Any CPU.Build.0 = Release|Any CPU {26270B39-08A9-47ED-AE24-73A81CA0FDDE}.Release|x86.ActiveCfg = Release|Any CPU {26270B39-08A9-47ED-AE24-73A81CA0FDDE}.Release|x86.Build.0 = Release|Any CPU - {35177AF2-C17C-4C32-A2B4-838584CA86F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {35177AF2-C17C-4C32-A2B4-838584CA86F2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {35177AF2-C17C-4C32-A2B4-838584CA86F2}.Debug|x86.ActiveCfg = Debug|Any CPU - {35177AF2-C17C-4C32-A2B4-838584CA86F2}.Debug|x86.Build.0 = Debug|Any CPU - {35177AF2-C17C-4C32-A2B4-838584CA86F2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {35177AF2-C17C-4C32-A2B4-838584CA86F2}.Release|Any CPU.Build.0 = Release|Any CPU - {35177AF2-C17C-4C32-A2B4-838584CA86F2}.Release|x86.ActiveCfg = Release|Any CPU - {35177AF2-C17C-4C32-A2B4-838584CA86F2}.Release|x86.Build.0 = Release|Any CPU {F679DB41-C60F-48C0-8D22-50390238A5A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F679DB41-C60F-48C0-8D22-50390238A5A1}.Debug|Any CPU.Build.0 = Debug|Any CPU {F679DB41-C60F-48C0-8D22-50390238A5A1}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -257,14 +257,6 @@ Global {7000A01B-B4DA-4F30-87FF-B3EA4057BBE5}.Release|Any CPU.Build.0 = Release|Any CPU {7000A01B-B4DA-4F30-87FF-B3EA4057BBE5}.Release|x86.ActiveCfg = Release|Any CPU {7000A01B-B4DA-4F30-87FF-B3EA4057BBE5}.Release|x86.Build.0 = Release|Any CPU - {7CE50AB9-9A8D-4D91-80CB-4F6F94F79CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7CE50AB9-9A8D-4D91-80CB-4F6F94F79CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7CE50AB9-9A8D-4D91-80CB-4F6F94F79CF9}.Debug|x86.ActiveCfg = Debug|Any CPU - {7CE50AB9-9A8D-4D91-80CB-4F6F94F79CF9}.Debug|x86.Build.0 = Debug|Any CPU - {7CE50AB9-9A8D-4D91-80CB-4F6F94F79CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7CE50AB9-9A8D-4D91-80CB-4F6F94F79CF9}.Release|Any CPU.Build.0 = Release|Any CPU - {7CE50AB9-9A8D-4D91-80CB-4F6F94F79CF9}.Release|x86.ActiveCfg = Release|Any CPU - {7CE50AB9-9A8D-4D91-80CB-4F6F94F79CF9}.Release|x86.Build.0 = Release|Any CPU {D78B0491-4460-4918-9BEA-D886A80D7847}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D78B0491-4460-4918-9BEA-D886A80D7847}.Debug|x86.ActiveCfg = Debug|Any CPU {D78B0491-4460-4918-9BEA-D886A80D7847}.Debug|x86.Build.0 = Debug|Any CPU @@ -279,6 +271,54 @@ Global {BF07061B-7B94-4DD2-BCB8-63E491CA7463}.Release|Any CPU.Build.0 = Release|Any CPU {BF07061B-7B94-4DD2-BCB8-63E491CA7463}.Release|x86.ActiveCfg = Release|Any CPU {BF07061B-7B94-4DD2-BCB8-63E491CA7463}.Release|x86.Build.0 = Release|Any CPU + {78774489-9988-4CF3-93E5-FB91A2D2764B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78774489-9988-4CF3-93E5-FB91A2D2764B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78774489-9988-4CF3-93E5-FB91A2D2764B}.Debug|x86.ActiveCfg = Debug|Any CPU + {78774489-9988-4CF3-93E5-FB91A2D2764B}.Debug|x86.Build.0 = Debug|Any CPU + {78774489-9988-4CF3-93E5-FB91A2D2764B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78774489-9988-4CF3-93E5-FB91A2D2764B}.Release|Any CPU.Build.0 = Release|Any CPU + {78774489-9988-4CF3-93E5-FB91A2D2764B}.Release|x86.ActiveCfg = Release|Any CPU + {78774489-9988-4CF3-93E5-FB91A2D2764B}.Release|x86.Build.0 = Release|Any CPU + {B43FFC6D-9FF1-49C7-A41A-F427231B056B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B43FFC6D-9FF1-49C7-A41A-F427231B056B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B43FFC6D-9FF1-49C7-A41A-F427231B056B}.Debug|x86.ActiveCfg = Debug|Any CPU + {B43FFC6D-9FF1-49C7-A41A-F427231B056B}.Debug|x86.Build.0 = Debug|Any CPU + {B43FFC6D-9FF1-49C7-A41A-F427231B056B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B43FFC6D-9FF1-49C7-A41A-F427231B056B}.Release|Any CPU.Build.0 = Release|Any CPU + {B43FFC6D-9FF1-49C7-A41A-F427231B056B}.Release|x86.ActiveCfg = Release|Any CPU + {B43FFC6D-9FF1-49C7-A41A-F427231B056B}.Release|x86.Build.0 = Release|Any CPU + {34E4702D-8D93-4D39-8D46-063892C848FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34E4702D-8D93-4D39-8D46-063892C848FD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34E4702D-8D93-4D39-8D46-063892C848FD}.Debug|x86.ActiveCfg = Debug|Any CPU + {34E4702D-8D93-4D39-8D46-063892C848FD}.Debug|x86.Build.0 = Debug|Any CPU + {34E4702D-8D93-4D39-8D46-063892C848FD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34E4702D-8D93-4D39-8D46-063892C848FD}.Release|Any CPU.Build.0 = Release|Any CPU + {34E4702D-8D93-4D39-8D46-063892C848FD}.Release|x86.ActiveCfg = Release|Any CPU + {34E4702D-8D93-4D39-8D46-063892C848FD}.Release|x86.Build.0 = Release|Any CPU + {49F670E1-0D48-47B6-8C49-00284A19C232}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {49F670E1-0D48-47B6-8C49-00284A19C232}.Debug|Any CPU.Build.0 = Debug|Any CPU + {49F670E1-0D48-47B6-8C49-00284A19C232}.Debug|x86.ActiveCfg = Debug|Any CPU + {49F670E1-0D48-47B6-8C49-00284A19C232}.Debug|x86.Build.0 = Debug|Any CPU + {49F670E1-0D48-47B6-8C49-00284A19C232}.Release|Any CPU.ActiveCfg = Release|Any CPU + {49F670E1-0D48-47B6-8C49-00284A19C232}.Release|Any CPU.Build.0 = Release|Any CPU + {49F670E1-0D48-47B6-8C49-00284A19C232}.Release|x86.ActiveCfg = Release|Any CPU + {49F670E1-0D48-47B6-8C49-00284A19C232}.Release|x86.Build.0 = Release|Any CPU + {C00A01BD-4C32-4C77-975B-D3D7CE1AC8B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C00A01BD-4C32-4C77-975B-D3D7CE1AC8B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C00A01BD-4C32-4C77-975B-D3D7CE1AC8B1}.Debug|x86.ActiveCfg = Debug|Any CPU + {C00A01BD-4C32-4C77-975B-D3D7CE1AC8B1}.Debug|x86.Build.0 = Debug|Any CPU + {C00A01BD-4C32-4C77-975B-D3D7CE1AC8B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C00A01BD-4C32-4C77-975B-D3D7CE1AC8B1}.Release|Any CPU.Build.0 = Release|Any CPU + {C00A01BD-4C32-4C77-975B-D3D7CE1AC8B1}.Release|x86.ActiveCfg = Release|Any CPU + {C00A01BD-4C32-4C77-975B-D3D7CE1AC8B1}.Release|x86.Build.0 = Release|Any CPU + {5AA5C674-F7FE-4FF5-98B6-03C9F9B70D1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5AA5C674-F7FE-4FF5-98B6-03C9F9B70D1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5AA5C674-F7FE-4FF5-98B6-03C9F9B70D1B}.Debug|x86.ActiveCfg = Debug|Any CPU + {5AA5C674-F7FE-4FF5-98B6-03C9F9B70D1B}.Debug|x86.Build.0 = Debug|Any CPU + {5AA5C674-F7FE-4FF5-98B6-03C9F9B70D1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5AA5C674-F7FE-4FF5-98B6-03C9F9B70D1B}.Release|Any CPU.Build.0 = Release|Any CPU + {5AA5C674-F7FE-4FF5-98B6-03C9F9B70D1B}.Release|x86.ActiveCfg = Release|Any CPU + {5AA5C674-F7FE-4FF5-98B6-03C9F9B70D1B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -295,7 +335,6 @@ Global {9EB2525F-48E7-4057-98E5-7FE325ECCD32} = {E2401C71-C5F2-46FB-B5A3-E6EFB85106B9} {4D0C6CF1-F71F-4648-97C7-C8F38E7E464E} = {4A12EE3E-81ED-4842-A69F-9D15413DC46D} {26270B39-08A9-47ED-AE24-73A81CA0FDDE} = {E2401C71-C5F2-46FB-B5A3-E6EFB85106B9} - {35177AF2-C17C-4C32-A2B4-838584CA86F2} = {E2401C71-C5F2-46FB-B5A3-E6EFB85106B9} {F679DB41-C60F-48C0-8D22-50390238A5A1} = {E2401C71-C5F2-46FB-B5A3-E6EFB85106B9} {1DF26474-08FC-45BB-A0C3-7634BD0B6C0F} = {3D9FE09F-24C0-4AF7-880C-D2D7DB71AA99} {93181E6E-3953-4711-AFFE-99AA62E5AB29} = {3D9FE09F-24C0-4AF7-880C-D2D7DB71AA99} @@ -309,9 +348,14 @@ Global {170EE1BF-C862-4683-8CD1-C5EAFCBD4B52} = {4A12EE3E-81ED-4842-A69F-9D15413DC46D} {DA28DD2A-8D1B-4976-AD3C-69F80C1165E8} = {4A12EE3E-81ED-4842-A69F-9D15413DC46D} {7000A01B-B4DA-4F30-87FF-B3EA4057BBE5} = {E2401C71-C5F2-46FB-B5A3-E6EFB85106B9} - {7CE50AB9-9A8D-4D91-80CB-4F6F94F79CF9} = {E2401C71-C5F2-46FB-B5A3-E6EFB85106B9} {D78B0491-4460-4918-9BEA-D886A80D7847} = {4A12EE3E-81ED-4842-A69F-9D15413DC46D} {BF07061B-7B94-4DD2-BCB8-63E491CA7463} = {4A12EE3E-81ED-4842-A69F-9D15413DC46D} + {78774489-9988-4CF3-93E5-FB91A2D2764B} = {E2401C71-C5F2-46FB-B5A3-E6EFB85106B9} + {B43FFC6D-9FF1-49C7-A41A-F427231B056B} = {E2401C71-C5F2-46FB-B5A3-E6EFB85106B9} + {34E4702D-8D93-4D39-8D46-063892C848FD} = {E2401C71-C5F2-46FB-B5A3-E6EFB85106B9} + {49F670E1-0D48-47B6-8C49-00284A19C232} = {E2401C71-C5F2-46FB-B5A3-E6EFB85106B9} + {C00A01BD-4C32-4C77-975B-D3D7CE1AC8B1} = {E2401C71-C5F2-46FB-B5A3-E6EFB85106B9} + {5AA5C674-F7FE-4FF5-98B6-03C9F9B70D1B} = {E2401C71-C5F2-46FB-B5A3-E6EFB85106B9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7EFE121E-7A84-43A4-8C76-7EE70DF2736A} diff --git a/ReadableExpressions/ExpressionAnalysis.cs b/ReadableExpressions/ExpressionAnalysis.cs index ecfe26ff..89790105 100644 --- a/ReadableExpressions/ExpressionAnalysis.cs +++ b/ReadableExpressions/ExpressionAnalysis.cs @@ -22,17 +22,15 @@ public class ExpressionAnalysis { private readonly TranslationSettings _settings; - private Dictionary _constructsByAssignment; - private ICollection _joinedAssignments; private IList _assignedAssignments; private ICollection _nestedCasts; - private Scope _currentScope; - private Scope _previousScope; + private ExpressionScope _currentExpressionScope; + private ExpressionScope _previousExpressionScope; private ICollection _namedLabelTargets; private List _chainedMethodCalls; + private List _methodGroupLambdas; private ICollection _gotoReturnGotos; private Dictionary _unnamedVariablesByType; - private ICollection _declaredOutputParameters; /// /// Initializes a new instance of the class. @@ -68,9 +66,10 @@ public static ExpressionAnalysis For(Expression expression, TranslationSettings /// /// Gets the current BlockExpression, or null if none is in scope. /// - protected BlockExpression CurrentBlock => _currentScope?.GetCurrentBlockOrNull(); + protected BlockExpression CurrentBlock => _currentExpressionScope?.GetCurrentBlockOrNull(); - private Scope CurrentScope => _currentScope ??= new Scope(); + private ExpressionScope CurrentExpressionScope + => _currentExpressionScope ??= new ExpressionScope(); /// /// Analyses the given , setting @@ -111,62 +110,73 @@ protected virtual void Analyse(Expression expression) /// public virtual bool ShouldBeDeclaredInVariableList(ParameterExpression variable) { - return !CurrentScope.IsInlineOutputParameter(variable) && - !CurrentScope.HasBeenJoinAssigned(variable); + var shouldDeclare = CurrentExpressionScope + .ShouldDeclareInVariableList(variable, out var isUsed); + + return shouldDeclare && (isUsed || !_settings.DiscardUnusedParams); } /// - /// Returns a value indicating whether the given is an output - /// parameter that should be declared inline. + /// Determines if the given represents an output parameter that + /// should be declared inline. /// - /// The ParameterExpression for which to make the determination. + /// The parameter Expression for which to make the determination. /// - /// True if the given is an output parameter that should be - /// declared inline, otherwise false. + /// True if the given represents an output parameter that should + /// be declared inline, otherwise false. /// - public bool ShouldBeDeclaredInline(ParameterExpression parameter) + public bool ShouldBeDeclaredInOutputParameterUse(Expression parameter) { - var declareInline = - CurrentScope.IsInlineOutputParameter(parameter) && - _declaredOutputParameters?.Contains(parameter) != true; + if (!_settings.DeclareOutParamsInline || parameter.NodeType != Parameter) + { + return false; + } + + var typedParameter = (ParameterExpression)parameter; + return CurrentExpressionScope.ShouldDeclareInOutputParameterUse(typedParameter); + } - if (declareInline) + /// + /// Determines if the given is unused, and should be translated + /// to a parameter discard (_). + /// + /// The parameter Expression for which to make the determination. + /// True if the given is unused, otherwise false. + public bool ShouldBeDiscarded(Expression parameter) + { + if (!_settings.DiscardUnusedParams || parameter.NodeType != Parameter) { - (_declaredOutputParameters ??= new List()).Add(parameter); - return true; + return false; } - return false; + var typedParameter = (ParameterExpression)parameter; + return !CurrentExpressionScope.IsUsed(typedParameter); } /// - /// Returns a value indicating whether the given represents an - /// assignment where the assigned variable is declared as part of the assignment statement. + /// Determines if the given represents an assignment where the + /// assigned variable should be declared as part of the assignment statement. /// /// The Expression to evaluate. - /// - /// Populated with the BinaryExpression representing the assignment statement, if the given - /// represents an assignment. - /// /// - /// True if the given represents an assignment where the assigned - /// variable is declared as part of the assignment statement, otherwise false. + /// True if the given represents an assignment where the + /// assigned variable should be declared as part of the assignment statement, otherwise + /// false. /// - public bool IsJoinedAssignment(Expression expression, out BinaryExpression assignment) + public bool IsJoinedAssignment(Expression expression) { - if (expression.NodeType != Assign || _joinedAssignments == null) + if (expression.NodeType != Assign) { - assignment = null; return false; } - assignment = (BinaryExpression)expression; - return _joinedAssignments.Contains(assignment); + return CurrentExpressionScope + .IsJoinedAssignment((BinaryExpression)expression); } /// - /// Returns a value indicating whether the given is the Exception - /// variable in a Catch block. + /// Determines if the given is the Exception variable in a Catch + /// block. /// /// The Expression for which to make the determination. /// @@ -176,11 +186,11 @@ public bool IsJoinedAssignment(Expression expression, out BinaryExpression assig public bool IsCatchBlockVariable(Expression variable) { return (variable.NodeType == Parameter) && - CurrentScope.IsCatchBlockVariable((ParameterExpression)variable); + CurrentExpressionScope.IsCatchBlockVariable((ParameterExpression)variable); } /// - /// Returns a value indicating whether the given is referenced by a + /// Determines if the given is referenced by a /// . /// /// The to evaluate. @@ -192,8 +202,8 @@ public bool IsReferencedByGoto(LabelTarget labelTarget) => _namedLabelTargets?.Contains(labelTarget) == true; /// - /// Returns a value indicating whether the given is nested inside - /// another cast UnaryExpression, as part of a set of nested type casts. + /// Determines if the given is nested inside another cast + /// UnaryExpression, as part of a set of nested type casts. /// /// /// The UnaryExpression describing the cast for which to make the determination. @@ -205,8 +215,8 @@ public bool IsReferencedByGoto(LabelTarget labelTarget) public bool IsNestedCast(UnaryExpression cast) => _nestedCasts?.Contains(cast) == true; /// - /// Returns a value indicating whether the given goes to the - /// final statement in a block, and so should be rendered as a return statement. + /// Determines if the given goes to the final statement in a block, + /// and so should be rendered as a return statement. /// /// The GotoExpression for which to make the determination. /// @@ -217,8 +227,8 @@ public bool GoesToReturnLabel(GotoExpression @goto) => _gotoReturnGotos?.Contains(@goto) == true; /// - /// Returns a value indicating whether the given is part of a chain - /// of multiple method calls. + /// Determines if the given is part of a chain of multiple + /// method calls. /// /// The Expression to evaluate. /// @@ -228,6 +238,28 @@ public bool GoesToReturnLabel(GotoExpression @goto) public bool IsPartOfMethodCallChain(MethodCallExpression methodCall) => _chainedMethodCalls?.Contains(methodCall) == true; + /// + /// Determines if the given can be converted to a + /// method group, populating with the method group if so. + /// + /// The method argument for which to make the determination. + /// + /// + public bool CanBeConvertedToMethodGroup( + Expression methodCallArgument, + out MethodCallExpression groupMethodCall) + { + if (_methodGroupLambdas?.Contains(methodCallArgument) != true) + { + groupMethodCall = null; + return false; + } + + var methodGroupLambda = (LambdaExpression)methodCallArgument; + groupMethodCall = (MethodCallExpression)methodGroupLambda.Body; + return true; + } + /// /// Returns the 1-based index of the given in the set of /// unnamed, accessed variables of its Type. @@ -250,7 +282,7 @@ public bool IsPartOfMethodCallChain(MethodCallExpression methodCall) } private Dictionary UnnamedVariablesByType - => _unnamedVariablesByType ??= _currentScope?.AllVariables + => _unnamedVariablesByType ??= _currentExpressionScope?.AllVariables .Where(variable => InternalStringExtensions.IsNullOrWhiteSpace(variable.Name)) .GroupBy(variable => variable.Type) .ToDictionary(grp => grp.Key, grp => grp.ToArray()) ?? @@ -431,29 +463,13 @@ protected virtual Expression VisitAndConvert(Expression expression) /// protected virtual Expression VisitAndConvert(BinaryExpression binary) { - var isConstructAssignment = false; var isJoinedAssignment = false; if (IsJoinableVariableAssignment(binary, out var variable)) { - if (IsFirstAccess(variable)) + if (CurrentExpressionScope.TryAddFirstAccess(variable, binary)) { - var currentConstruct = CurrentScope.GetCurrentConstructObjectOrNull(); - - if (currentConstruct != null) - { - (_constructsByAssignment ??= new Dictionary()) - .Add(binary, currentConstruct); - - isConstructAssignment = true; - } - - (_joinedAssignments ??= new List()).Add(binary); - CurrentScope.AddJoinedAssignmentVariable(variable); - isJoinedAssignment = true; - - AddVariableAccess(variable); } AddAssignedAssignmentIfAppropriate(binary.Right); @@ -464,21 +480,9 @@ protected virtual Expression VisitAndConvert(BinaryExpression binary) VisitAndConvert(binary.Conversion), VisitAndConvert(binary.Right)); - if (updatedBinary == binary) + if (isJoinedAssignment && updatedBinary != binary) { - return updatedBinary; - } - - if (isConstructAssignment) - { - _constructsByAssignment.Add(updatedBinary, _constructsByAssignment[binary]); - _constructsByAssignment.Remove(binary); - } - - if (isJoinedAssignment) - { - _joinedAssignments.Add(updatedBinary); - _joinedAssignments.Remove(binary); + CurrentExpressionScope.DeclareInAssignment(variable, updatedBinary); } return updatedBinary; @@ -497,7 +501,7 @@ private bool IsJoinableVariableAssignment( } variable = (ParameterExpression)binary.Left; - return !HasBeenAssigned(variable); + return !HasBeenJoinAssigned(variable); } private bool IsAssigned(BinaryExpression binary) @@ -549,14 +553,8 @@ private static bool IsChainedAssignmentWithin( /// True if the current assignment of the given being evaluated /// can include a joined declaration of the variable, otherwise false. /// - protected virtual bool HasBeenAssigned(ParameterExpression variable) - => CurrentScope.HasBeenJoinAssigned(variable); - - private bool IsFirstAccess(ParameterExpression variable) - => !CurrentScope.HasBeenAccessed(variable); - - private void AddVariableAccess(ParameterExpression variable) - => CurrentScope.AddVariableAccess(variable); + protected virtual bool HasBeenJoinAssigned(ParameterExpression variable) + => CurrentExpressionScope.IsJoinedAssignmentVariable(variable); /// /// Visits the given , returning a replacement Expression if @@ -571,11 +569,17 @@ protected virtual Expression VisitAndConvert(BlockExpression block) { PushScope(block); + // Visit child expression first to track + // variable usage in child scopes: var expressions = VisitAndConvert(block.Expressions); - var variables = VisitAndConvert(block.Variables, EvaluateJoinedAssignment); - block = block.Update(variables, expressions); + var variables = VisitAndConvert(block.Variables, ReevaluateDeclaration); + + if (expressions != block.Expressions || variables != block.Variables) + { + block = block.Update(variables, expressions); + _currentExpressionScope.Set(block); + } - _currentScope.Set(block); ExitScope(); return block; } @@ -612,7 +616,7 @@ private Expression VisitAndConvertCast(Expression castExpression) private bool ParentConstructIsCast() { - var parent = _currentScope.Parent.ScopeObject; + var parent = _currentExpressionScope.Parent.ScopeObject; return parent is UnaryExpression { NodeType: ExpressionType.Convert or ConvertChecked }; } @@ -769,15 +773,19 @@ protected virtual LambdaExpression VisitAndConvert(LambdaExpression lambda) return null; } + PushScope(lambda); + var parameters = VisitAndConvert(lambda.Parameters); var body = VisitAndConvert(lambda.Body); - if (parameters == lambda.Parameters && body == lambda.Body) + if (parameters != lambda.Parameters || body != lambda.Body) { - return lambda; + lambda = lambda.Update(body, parameters); + _currentExpressionScope.Set(lambda); } - return lambda.Update(body, parameters); + ExitScope(); + return lambda; } /// @@ -859,30 +867,40 @@ protected virtual Expression VisitAndConvert(MethodCallExpression methodCall) } } - if (_settings.DeclareOutParamsInline) + var parameters = methodCall.Method.GetParameters(); + IList arguments = methodCall.Arguments; + var argumentCount = arguments.Count; + + for (var i = 0; i < argumentCount; ++i) { - for (int i = 0, l = methodCall.Arguments.Count; i < l; ++i) - { - var argument = methodCall.Arguments[i]; + var argument = arguments[i]; - if (argument.NodeType != Parameter) - { + switch (argument.NodeType) + { + case Parameter when parameters[i].IsOut && _settings.DeclareOutParamsInline: + var parameter = (ParameterExpression)argument; + CurrentExpressionScope.AddOutputParameter(parameter); continue; - } - var parameter = (ParameterExpression)argument; + case Lambda when argument.CanBeConvertedToMethodGroup(): + (_methodGroupLambdas ??= new()).Add(argument); - if (IsFirstAccess(parameter)) - { - CurrentScope.AddInlineOutputVariable(parameter); - } + if (arguments == methodCall.Arguments) + { + var reducedArguments = new Expression[argumentCount]; + arguments.CopyTo(reducedArguments, 0, 0, argumentCount); + arguments = reducedArguments; + } + + arguments[i] = null; + continue; } } return VisitAndConvert( methodCall, methodCall.Object, - methodCall.Arguments, + arguments, (mc, s, args) => Expression.Call(s, mc.Method, args)); } @@ -1035,44 +1053,12 @@ private ParameterExpression Visit(ParameterExpression variable) return null; } - if (IsFirstAccess(variable)) - { - AddVariableAccess(variable); - } - - return EvaluateJoinedAssignment(variable); + CurrentExpressionScope.TryAddFirstAccess(variable); + return ReevaluateDeclaration(variable); } - private ParameterExpression EvaluateJoinedAssignment(ParameterExpression variable) - { - if (!CurrentScope.HasBeenJoinAssigned(variable)) - { - return variable; - } - - var joinedAssignmentData = _constructsByAssignment? - .Filter(kvp => kvp.Key.Left == variable) - .Project(kvp => new - { - Assignment = kvp.Key, - Construct = kvp.Value - }) - .FirstOrDefault(); - - if ((joinedAssignmentData == null) || - _currentScope.Contains(joinedAssignmentData.Construct)) - { - return variable; - } - - // This variable was assigned within a construct but is being accessed - // outside of that scope, so the assignment shouldn't be joined: - _currentScope.RemoveJoinedAssignmentVariable(variable); - _joinedAssignments.Remove(joinedAssignmentData.Assignment); - _constructsByAssignment.Remove(joinedAssignmentData.Assignment); - - return variable; - } + private ParameterExpression ReevaluateDeclaration(ParameterExpression variable) + => CurrentExpressionScope.ReevaluateDeclaration(variable); /// /// Visits the given , returning a replacement Expression @@ -1168,7 +1154,7 @@ protected virtual CatchBlock VisitAndConvert(CatchBlock @catch) if (catchVariable != null) { - CurrentScope.AddCatchBlockVariable(catchVariable); + CurrentExpressionScope.AddCatchBlockVariable(catchVariable); } return VisitConstruct(@catch, c => c.Update( @@ -1295,14 +1281,14 @@ protected static IList VisitAndConvert( } /// - /// Visits the given and , and uses + /// Visits the given and , and uses /// the to create a replacement /// if appropriate. /// /// The type of Expression to visit. /// The to visit. /// The 's subject. - /// The 's expressions. + /// The 's expressions. /// /// A Func with which to create a new instance. /// @@ -1313,14 +1299,14 @@ protected static IList VisitAndConvert( protected Expression VisitAndConvert( TExpression expression, Expression subject, - IList expressions, + IList arguments, Func, Expression> expressionFactory) where TExpression : Expression { var updatedSubject = VisitAndConvert(subject); - var updatedArguments = VisitAndConvert(expressions); + var updatedArguments = VisitAndConvert(arguments); - if (updatedSubject == subject && updatedArguments == expressions) + if (updatedSubject == subject && updatedArguments == arguments) { return expression; } @@ -1335,7 +1321,7 @@ private TConstruct VisitConstruct( PushScope(expression); var updatedConstruct = visitor.Invoke(expression); - _currentScope.Set(updatedConstruct); + _currentExpressionScope.Set(updatedConstruct); ExitScope(); return updatedConstruct; @@ -1373,227 +1359,29 @@ private void AddAssignedAssignmentIfAppropriate(Expression assignedValue) /// The BlockExpression the scope for which to access. public void EnterScope(BlockExpression block) { - _previousScope = _currentScope; - _currentScope = _currentScope?.FindScopeFor(block); + if (_currentExpressionScope == null) + { + _previousExpressionScope = null; + return; + } + + _previousExpressionScope = _currentExpressionScope; + _currentExpressionScope = _currentExpressionScope.FindScopeFor(block); } /// /// Exits the current scope. /// public void ExitScope() - => _currentScope = _previousScope ?? _currentScope?.Parent; + => _currentExpressionScope = _previousExpressionScope ?? _currentExpressionScope?.Parent; + + private void PushScope(LambdaExpression lambda) + => _currentExpressionScope = new ExpressionScope(lambda, CurrentExpressionScope); private void PushScope(BlockExpression block) - => _currentScope = new Scope(block, CurrentScope); + => _currentExpressionScope = new ExpressionScope(block, CurrentExpressionScope); private void PushScope(object scopeObject) - => _currentScope = new Scope(scopeObject, CurrentScope); - - #region Helper Class - - private class Scope - { - private BlockExpression _block; - private ICollection _accessedVariables; - private IList _joinedAssignmentVariables; - private IList _inlineOutputVariables; - private ICollection _catchBlockVariables; - private List _childScopes; - - public Scope() - { - _childScopes = new List(); - } - - public Scope(BlockExpression block, Scope parent) - : this((object)block, parent) - { - _block = block; - } - - public Scope(object scopeObject, Scope parent) - { - ScopeObject = scopeObject; - Parent = parent; - (parent._childScopes ??= new List()).Add(this); - } - - public Scope Parent { get; } - - public object ScopeObject { get; private set; } - - public IEnumerable AllVariables - { - get - { - if (_accessedVariables != null) - { - foreach (var variable in _accessedVariables) - { - yield return variable; - } - } - - if (_childScopes != null) - { - var childScopeVariables = _childScopes - .SelectMany(childScope => childScope.AllVariables); - - foreach (var variable in childScopeVariables) - { - yield return variable; - } - } - } - } - - public BlockExpression GetCurrentBlockOrNull() - => _block ?? Parent?.GetCurrentBlockOrNull(); - - public void Set(BlockExpression block) => _block = block; - - public object GetCurrentConstructObjectOrNull() - { - return _block == null - ? ScopeObject - : Parent?.GetCurrentConstructObjectOrNull(); - } - - public void Set(object scopeObject) => ScopeObject = scopeObject; - - public bool Contains(object scopeObject) - { - return ScopeObject == scopeObject || - Parent?.Contains(scopeObject) == true; - } - - public Scope FindScopeFor(BlockExpression block) - { - if (_block == block) - { - return this; - } - - return _childScopes? - .Project(childScope => childScope.FindScopeFor(block)) - .Filter(scope => scope != null) - .FirstOrDefault(); - } - - public bool HasBeenAccessed(ParameterExpression variable) - { - var declaringScope = GetDeclaringScope(variable); - - return declaringScope.HasBeenAdded( - scope => scope._accessedVariables, - variable, - out _); - } - - public void AddVariableAccess(ParameterExpression variable) - => (_accessedVariables ??= new List()).Add(variable); - - public bool HasBeenJoinAssigned(ParameterExpression variable) - => HasBeenJoinAssigned(variable, out _); - - private bool HasBeenJoinAssigned( - ParameterExpression variable, - out ICollection variables) - { - var declaringScope = GetDeclaringScope(variable); - - return declaringScope.HasBeenAdded( - scope => scope._joinedAssignmentVariables, - variable, - out variables); - } - - public void AddJoinedAssignmentVariable(ParameterExpression variable) - => (_joinedAssignmentVariables ??= new List()).Add(variable); - - public void RemoveJoinedAssignmentVariable(ParameterExpression variable) - { - if (HasBeenJoinAssigned(variable, out var variables)) - { - variables.Remove(variable); - } - } - - public bool IsInlineOutputParameter(ParameterExpression variable) - { - var declaringScope = GetDeclaringScope(variable); - - return declaringScope.HasBeenAdded( - scope => scope._inlineOutputVariables, - variable, - out _); - } - - public void AddInlineOutputVariable(ParameterExpression parameter) - { - (_inlineOutputVariables ??= new List()) - .Add(parameter); - } - - public void AddCatchBlockVariable(ParameterExpression variable) - { - (_catchBlockVariables ??= new List()) - .Add(variable); - } - - public bool IsCatchBlockVariable(ParameterExpression variable) - { - var declaringScope = GetDeclaringScope(variable); - - return declaringScope.HasBeenAdded( - scope => scope._catchBlockVariables, - variable, - out _); - } - - private Scope GetDeclaringScope(ParameterExpression variable) - { - if (Parent == null || - _block?.Variables.Contains(variable) == true) - { - return this; - } - - return Parent.GetDeclaringScope(variable); - } - - private bool HasBeenAdded( - Func> variablesGetter, - ParameterExpression variable, - out ICollection variables) - { - variables = variablesGetter.Invoke(this); - - if (variables?.Contains(variable) == true) - { - return true; - } - - if (_childScopes?.Any() != true) - { - return false; - } - - foreach (var childScope in _childScopes) - { - if (childScope.HasBeenAdded( - variablesGetter, - variable, - out variables)) - { - return true; - } - } - - return false; - } - } - - #endregion + => _currentExpressionScope = new ExpressionScope(scopeObject, CurrentExpressionScope); } } \ No newline at end of file diff --git a/ReadableExpressions/ExpressionScope.cs b/ReadableExpressions/ExpressionScope.cs new file mode 100644 index 00000000..0a20c71b --- /dev/null +++ b/ReadableExpressions/ExpressionScope.cs @@ -0,0 +1,450 @@ +namespace AgileObjects.ReadableExpressions +{ + using System.Collections.Generic; + using System.Linq; +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif + using Extensions; +#if NET35 + using static Microsoft.Scripting.Ast.ExpressionType; +#else + using static System.Linq.Expressions.ExpressionType; +#endif + + internal class ExpressionScope + { + private readonly DeclarationType _defaultDeclarationType; + private Expression _scopeExpression; + private ICollection _variables; + private Dictionary _variableInfos; + private List _childScopes; + + public ExpressionScope() + { + _childScopes = new List(); + } + + public ExpressionScope(LambdaExpression lambda, ExpressionScope parent) : + this(parent) + { + _defaultDeclarationType = DeclarationType.None; + Set(lambda); + } + + public ExpressionScope(BlockExpression block, ExpressionScope parent) : + this(parent) + { + _defaultDeclarationType = DeclarationType.VariableList; + Set(block); + } + + public ExpressionScope(object scopeObject, ExpressionScope parent) : + this(parent) + { + Set(scopeObject); + } + + private ExpressionScope(ExpressionScope parent) + { + Parent = parent; + (parent._childScopes ??= new List()).Add(this); + } + + public ExpressionScope Parent { get; } + + public object ScopeObject { get; private set; } + + public IEnumerable AllVariables + => AllVariableInfos.Project(info => info.Variable); + + private IEnumerable AllVariableInfos + { + get + { + if (_variableInfos != null) + { + foreach (var variableInfo in _variableInfos.Values) + { + yield return variableInfo; + } + } + + if (_childScopes != null) + { + var childScopeVariableInfos = _childScopes + .SelectMany(childScope => childScope.AllVariableInfos); + + foreach (var variableInfo in childScopeVariableInfos) + { + yield return variableInfo; + } + } + } + } + + public BlockExpression GetCurrentBlockOrNull() + { + if (_scopeExpression?.NodeType == Block) + { + return (BlockExpression)_scopeExpression; + } + + return Parent?.GetCurrentBlockOrNull(); + } + + public void Set(LambdaExpression lambda) + { + ScopeObject = lambda; + _scopeExpression = lambda; + _variables = lambda.Parameters; + } + + public void Set(BlockExpression block) + { + ScopeObject = block; + _scopeExpression = block; + _variables = block.Variables; + } + + public void Set(object scopeObject) => ScopeObject = scopeObject; + + public object GetCurrentConstructObjectOrNull() + { + return _scopeExpression == null + ? ScopeObject + : Parent?.GetCurrentConstructObjectOrNull(); + } + + public ExpressionScope FindScopeFor(BlockExpression block) + { + if (_scopeExpression == block) + { + return this; + } + + return _childScopes? + .Project(childScope => childScope.FindScopeFor(block)) + .Filter(scope => scope != null) + .FirstOrDefault(); + } + + public bool TryAddFirstAccess( + ParameterExpression variable, + BinaryExpression assignment) + { + if (!TryAddFirstAccess(variable, out var variableInfo)) + { + return false; + } + + variableInfo.AssignmentParentConstruct = + GetCurrentConstructObjectOrNull(); + + DeclareInAssignment(variableInfo, assignment); + return true; + } + + public bool TryAddFirstAccess(ParameterExpression variable) + => TryAddFirstAccess(variable, out _); + + private bool TryAddFirstAccess( + ParameterExpression variable, + out VariableInfo variableInfo) + { + variableInfo = GetOrAddVariableInfo(variable, out var variableAdded); + + if (variableAdded) + { + return true; + } + + variableInfo.IsUsed = true; + return false; + } + + public ParameterExpression ReevaluateDeclaration(ParameterExpression variable) + { + var variableInfo = GetVariableInfoOrNull(variable); + + if (!IsJoinedAssignment(variableInfo)) + { + return variable; + } + + var parentConstruct = variableInfo.AssignmentParentConstruct; + + if (parentConstruct == null || IsForOrIsWithin(parentConstruct)) + { + return variable; + } + + // This variable was assigned within a construct but is being accessed + // outside of that scope, so the assignment shouldn't be joined: + DeclareInVariableList(variable); + return variable; + } + + private void DeclareInVariableList(ParameterExpression variable) + => GetVariableInfo(variable).DeclarationType = DeclarationType.VariableList; + + public void AddOutputParameter(ParameterExpression parameter) + { + var variableInfo = + GetOrAddVariableInfo(parameter, out var variableAdded); + + if (variableAdded) + { + variableInfo.DeclarationType = DeclarationType.InlineOutput; + } + } + + public void DeclareInAssignment( + ParameterExpression variable, + Expression assignment) + { + var variableInfo = GetVariableInfo(variable); + DeclareInAssignment(variableInfo, assignment); + } + + private static void DeclareInAssignment( + VariableInfo variableInfo, + Expression assignment) + { + variableInfo.DeclarationType = DeclarationType.JoinedAssignment; + variableInfo.Assignment = assignment; + } + + public void AddCatchBlockVariable(ParameterExpression variable) + => GetOrAddVariableInfo(variable, out _).IsCatchBlockVariable = true; + + public bool IsCatchBlockVariable(ParameterExpression variable) + => GetVariableInfo(variable).IsCatchBlockVariable; + + private bool IsForOrIsWithin(object scopeObject) + { + return ScopeObject == scopeObject || + Parent?.IsForOrIsWithin(scopeObject) == true; + } + + public bool IsJoinedAssignmentVariable(ParameterExpression variable) + => IsJoinedAssignment(GetVariableInfoOrNull(variable)); + + private static bool IsJoinedAssignment(VariableInfo variableInfo) + => variableInfo?.DeclarationType == DeclarationType.JoinedAssignment; + + public bool IsJoinedAssignment(BinaryExpression assignment) + { + if (assignment.Left.NodeType != Parameter) + { + return false; + } + + var variable = (ParameterExpression)assignment.Left; + var variableInfo = GetVariableInfoOrNull(variable); + + return + variableInfo?.Assignment == assignment && + variableInfo.DeclarationType == DeclarationType.JoinedAssignment; + } + + public bool ShouldDeclareInVariableList( + ParameterExpression variable, + out bool isUsed) + { + var info = GetVariableInfoOrNull(variable); + + if (info == null) + { + isUsed = false; + return true; + } + + isUsed = info.IsUsed; + return info.DeclarationType == DeclarationType.VariableList; + } + + public bool ShouldDeclareInOutputParameterUse(ParameterExpression variable) + { + var variableInfo = GetVariableInfo(variable); + + if (variableInfo.HasBeenDeclared || + variableInfo.DeclarationType != DeclarationType.InlineOutput) + { + return false; + } + + variableInfo.HasBeenDeclared = true; + return true; + } + + public bool IsUsed(ParameterExpression parameter) + { + var declaringScope = GetDeclaringScope(parameter); + + foreach (var variableInfo in declaringScope.AllVariableInfos) + { + if (VariableComparer.Instance.Equals(variableInfo.Variable, parameter)) + { + return variableInfo.IsUsed; + } + } + + return false; + } + + private VariableInfo GetVariableInfo(ParameterExpression variable) + => GetDeclaringScope(variable)._variableInfos[variable]; + + private VariableInfo GetVariableInfoOrNull(ParameterExpression variable) + { + var variableInfos = GetDeclaringScope(variable)._variableInfos; + + if (variableInfos != null && + variableInfos.TryGetValue(variable, out var info)) + { + return info; + } + + return null; + } + + private VariableInfo GetOrAddVariableInfo( + ParameterExpression variable, + out bool variableAdded) + { + var declaringScope = GetDeclaringScope(variable); + var variableInfos = declaringScope._variableInfos; + + VariableInfo info; + + if (variableInfos != null) + { + if (variableInfos.TryGetValue(variable, out info)) + { + variableAdded = false; + return info; + } + + goto AddNewInfo; + } + + variableInfos = declaringScope.EnsureVariableInfos(); + + AddNewInfo: + info = new() + { + Variable = variable, + DeclarationType = declaringScope._defaultDeclarationType + }; + + variableInfos.Add(variable, info); + variableAdded = true; + return info; + } + + private Dictionary EnsureVariableInfos() + { + return _variableInfos = + new Dictionary( + VariableComparer.Instance); + } + + private ExpressionScope GetDeclaringScope(ParameterExpression variable) + { + var declaringScope = default(ExpressionScope); + var searchScope = this; + + ExpressionScope rootScope; + + do + { + rootScope = searchScope; + + if (searchScope.Declares(variable)) + { + declaringScope = searchScope; + } + + searchScope = searchScope.Parent; + } + while (searchScope != null); + + return declaringScope ?? rootScope; + } + + private bool Declares(ParameterExpression variable) + { + if (_variables?.Any() != true) + { + return false; + } + + foreach (var scopeVariable in _variables) + { + if (VariableComparer.Instance.Equals(variable, scopeVariable)) + { + return true; + } + } + + return false; + } + + #region Helper Classes + + private class VariableInfo + { + public ParameterExpression Variable; + public DeclarationType DeclarationType; + public Expression Assignment; + public object AssignmentParentConstruct; + public bool IsCatchBlockVariable; + public bool HasBeenDeclared; + public bool IsUsed; + } + + private enum DeclarationType + { + None, + VariableList, + InlineOutput, + JoinedAssignment + } + + private class VariableComparer : IEqualityComparer + { + public static readonly IEqualityComparer Instance = + new VariableComparer(); + + public bool Equals(ParameterExpression x, ParameterExpression y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + return x!.Type == y!.Type && x.Name == y.Name; + } + + public int GetHashCode(ParameterExpression param) + { + var hashCode = param.Type.GetHashCode(); + + if (param.Name == null) + { + return hashCode; + } + + unchecked + { + return (hashCode * 397) ^ param.Name.GetHashCode(); + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/ReadableExpressions/Extensions/InternalExpressionExtensions.cs b/ReadableExpressions/Extensions/InternalExpressionExtensions.cs index a48daffa..f7149c44 100644 --- a/ReadableExpressions/Extensions/InternalExpressionExtensions.cs +++ b/ReadableExpressions/Extensions/InternalExpressionExtensions.cs @@ -8,6 +8,7 @@ using System.Linq.Expressions; #endif using System.Reflection; + using NetStandardPolyfills; #if NET35 using static Microsoft.Scripting.Ast.ExpressionType; #else @@ -130,5 +131,46 @@ public static bool IsCapturedValue( return true; } } + + public static bool CanBeConvertedToMethodGroup(this Expression argument) + { + if (argument.NodeType != Lambda) + { + return false; + } + + var argumentLambda = (LambdaExpression)argument; + + if ((argumentLambda.Body.NodeType != Call) || + (argumentLambda.ReturnType != argumentLambda.Body.Type)) + { + return false; + } + + if (argumentLambda.Body is not MethodCallExpression methodCall) + { + return false; + } + + IList lambdaBodyMethodCallArguments = methodCall.Arguments; + + if (methodCall.Method.IsExtensionMethod()) + { + lambdaBodyMethodCallArguments = lambdaBodyMethodCallArguments.Skip(1).ToArray(); + } + + if (lambdaBodyMethodCallArguments.Count != argumentLambda.Parameters.Count) + { + return false; + } + + var i = 0; + + var allArgumentTypesMatch = argumentLambda + .Parameters + .All(lambdaParameter => lambdaBodyMethodCallArguments[i++] == lambdaParameter); + + return allArgumentTypesMatch; + } } } \ No newline at end of file diff --git a/ReadableExpressions/ITranslationSettings.cs b/ReadableExpressions/ITranslationSettings.cs index 3f78e9dc..00342f12 100644 --- a/ReadableExpressions/ITranslationSettings.cs +++ b/ReadableExpressions/ITranslationSettings.cs @@ -29,6 +29,11 @@ public interface ITranslationSettings /// ITranslationSettings DeclareOutputParametersInline { get; } + /// + /// Use discards (_) in place of unused parameters. + /// + ITranslationSettings DiscardUnusedParameters { get; } + /// /// Show the names of implicitly-typed array types. /// diff --git a/ReadableExpressions/ReadableExpressions.csproj b/ReadableExpressions/ReadableExpressions.csproj index 2679ec01..e772257b 100644 --- a/ReadableExpressions/ReadableExpressions.csproj +++ b/ReadableExpressions/ReadableExpressions.csproj @@ -14,10 +14,10 @@ 1.6.1 $(PackageTargetFallback);dnxcore50 - 3.2.0.0 - 3.2.0.0 - 3.2.0 - 3.2.0 + 3.3.0.0 + 3.3.0.0 + 3.3.0 + 3.3.0 AgileObjects.ReadableExpressions AgileObjects.ReadableExpressions @@ -26,9 +26,13 @@ ./Icon.png ExpressionTrees Debugging DebuggerVisualizers Linq DLR https://github.com/AgileObjects/ReadableExpressions - - Support for translating ValueTuples -- Fixing MemberAccess translation regression, re: #101, #103 -- Fixing reused ParameterExpression object handling across different scopes + - Support for translating unused parameters to discards (_) +- Including ref and out parameter keywords in lambda parameter declarations, re: #106 +- Handling null custom Expression values, re: #105 +- Translating strings containing any new line characters as verbatim, re: #107 +- Support for translating VB.NET named, indexed properties +- Improving variable scope analysis + ../NuGet true diff --git a/ReadableExpressions/TranslationSettings.cs b/ReadableExpressions/TranslationSettings.cs index 4f6d699e..2bbbe642 100644 --- a/ReadableExpressions/TranslationSettings.cs +++ b/ReadableExpressions/TranslationSettings.cs @@ -84,6 +84,21 @@ ITranslationSettings ITranslationSettings.DeclareOutputParametersInline /// public bool DeclareOutParamsInline { get; private set; } + ITranslationSettings ITranslationSettings.DiscardUnusedParameters + { + get + { + DiscardUnusedParams = true; + return this; + } + } + + /// + /// Gets a value indicating whether discards (_) should be used in place of unused + /// parameters. + /// + public bool DiscardUnusedParams { get; private set; } + ITranslationSettings ITranslationSettings.ShowImplicitArrayTypes { get diff --git a/ReadableExpressions/Translations/BlockTranslation.cs b/ReadableExpressions/Translations/BlockTranslation.cs index d65c7997..73525b18 100644 --- a/ReadableExpressions/Translations/BlockTranslation.cs +++ b/ReadableExpressions/Translations/BlockTranslation.cs @@ -137,8 +137,8 @@ private IList GetBlockStatements( if (Include(expression, block, context)) { - var statementTranslation = context.Analysis.IsJoinedAssignment(expression, out var assignment) - ? new BlockAssignmentStatementTranslation(assignment, context) + var statementTranslation = context.Analysis.IsJoinedAssignment(expression) + ? new BlockAssignmentStatementTranslation((BinaryExpression)expression, context) : new BlockStatementTranslation(expression, context); translations[statementIndex] = statementTranslation; diff --git a/ReadableExpressions/Translations/ConstantTranslation.cs b/ReadableExpressions/Translations/ConstantTranslation.cs index d4e1d28a..db3e0a69 100644 --- a/ReadableExpressions/Translations/ConstantTranslation.cs +++ b/ReadableExpressions/Translations/ConstantTranslation.cs @@ -14,7 +14,6 @@ using Initialisations; using NetStandardPolyfills; using static System.Convert; - using static System.Environment; using static System.Globalization.CultureInfo; #if NET35 using static Microsoft.Scripting.Ast.ExpressionType; @@ -236,12 +235,14 @@ private static ITranslation GetFloatTranslation( return FixedValueTranslation(stringValue + "f", constant.Type, Numeric, context); } + private static readonly char[] _newlineCharacters = { '\r', '\n' }; + private static string GetStringConstant( ConstantExpression constant, out bool isVerbatim) { var stringValue = (string)constant.Value; - isVerbatim = stringValue.Contains(NewLine); + isVerbatim = stringValue.IndexOfAny(_newlineCharacters) != -1; return stringValue .Replace(@"\", @"\\") diff --git a/ReadableExpressions/Translations/ExpressionTranslation.cs b/ReadableExpressions/Translations/ExpressionTranslation.cs index 1ad1f032..d48cb8f2 100644 --- a/ReadableExpressions/Translations/ExpressionTranslation.cs +++ b/ReadableExpressions/Translations/ExpressionTranslation.cs @@ -167,7 +167,7 @@ public virtual ITranslation GetTranslationFor(Expression expression) return GotoTranslation.For((GotoExpression)expression, this); case Index: - return new IndexAccessTranslation((IndexExpression)expression, this); + return IndexAccessTranslation.For((IndexExpression)expression, this); case Invoke: return MethodCallTranslation.For((InvocationExpression)expression, this); diff --git a/ReadableExpressions/Translations/FixedValueTranslation.cs b/ReadableExpressions/Translations/FixedValueTranslation.cs index 4e073315..8f91fc2d 100644 --- a/ReadableExpressions/Translations/FixedValueTranslation.cs +++ b/ReadableExpressions/Translations/FixedValueTranslation.cs @@ -16,7 +16,10 @@ public class FixedValueTranslation : ITranslation { private readonly string _value; private readonly TokenType _tokenType; + private readonly bool _isEmptyValue; + private readonly int _translationSize; private readonly int _formattingSize; + private readonly int _lineCount; /// /// Initializes a new instance of the class. @@ -80,7 +83,16 @@ public FixedValueTranslation( Type = type; _value = value; _tokenType = tokenType; + _isEmptyValue = string.IsNullOrEmpty(value); + + if (_isEmptyValue) + { + return; + } + + _translationSize = value!.Length; _formattingSize = settings.GetFormattingSize(tokenType); + _lineCount = value.GetLineCount(); } /// @@ -93,15 +105,20 @@ public FixedValueTranslation( /// public Type Type { get; } - int ITranslatable.TranslationSize => _value.Length; + int ITranslatable.TranslationSize => _translationSize; int ITranslatable.FormattingSize => _formattingSize; int ITranslatable.GetIndentSize() => 0; - int ITranslatable.GetLineCount() => _value.GetLineCount(); + int ITranslatable.GetLineCount() => _lineCount; void ITranslatable.WriteTo(TranslationWriter writer) - => writer.WriteToTranslation(_value, _tokenType); + { + if (!_isEmptyValue) + { + writer.WriteToTranslation(_value, _tokenType); + } + } } } \ No newline at end of file diff --git a/ReadableExpressions/Translations/IParameterTranslation.cs b/ReadableExpressions/Translations/IParameterTranslation.cs index 401d3eb7..463648fc 100644 --- a/ReadableExpressions/Translations/IParameterTranslation.cs +++ b/ReadableExpressions/Translations/IParameterTranslation.cs @@ -4,8 +4,8 @@ internal interface IParameterTranslation : ITranslation { string Name { get; } - void WithTypeNames(ITranslationContext context); - + ITranslation WithTypeNames(ITranslationContext context); + void WithoutTypeNames(ITranslationContext context); } } \ No newline at end of file diff --git a/ReadableExpressions/Translations/IndexAccessTranslation.cs b/ReadableExpressions/Translations/IndexAccessTranslation.cs index 6261b817..433551a6 100644 --- a/ReadableExpressions/Translations/IndexAccessTranslation.cs +++ b/ReadableExpressions/Translations/IndexAccessTranslation.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using NetStandardPolyfills; #if NET35 using Microsoft.Scripting.Ast; #else @@ -23,7 +24,7 @@ public IndexAccessTranslation( Type = indexAccessCall.Type; } - public IndexAccessTranslation(IndexExpression indexAccess, ITranslationContext context) + private IndexAccessTranslation(IndexExpression indexAccess, ITranslationContext context) : this(indexAccess.Object, indexAccess.Arguments, context) { NodeType = ExpressionType.Index; @@ -55,6 +56,34 @@ private IndexAccessTranslation(ITranslation subject, ParameterSetTranslation par FormattingSize = subject.FormattingSize + parameters.FormattingSize; } + #region Factory Methods + + public static ITranslation For(IndexExpression indexAccess, ITranslationContext context) + { + var indexer = indexAccess.Indexer; + + if (indexer == null) + { + return new IndexAccessTranslation(indexAccess, context); + } + + var indexAccessor = indexer.GetGetter() ?? indexer.GetSetter(); + + if (indexAccessor.IsHideBySig) + { + return new IndexAccessTranslation(indexAccess, context); + } + + var indexCall = Expression.Call( + indexAccess.Object, + indexAccessor, + indexAccess.Arguments); + + return MethodCallTranslation.For(indexCall, context); + } + + #endregion + public ExpressionType NodeType { get; } public Type Type { get; } @@ -63,7 +92,7 @@ private IndexAccessTranslation(ITranslation subject, ParameterSetTranslation par public int FormattingSize { get; } - public int GetIndentSize() + public int GetIndentSize() => _subject.GetIndentSize() + _parameters.GetIndentSize(); public int GetLineCount() diff --git a/ReadableExpressions/Translations/MemberAccessTranslation.cs b/ReadableExpressions/Translations/MemberAccessTranslation.cs index f690a2b6..aaddaaa8 100644 --- a/ReadableExpressions/Translations/MemberAccessTranslation.cs +++ b/ReadableExpressions/Translations/MemberAccessTranslation.cs @@ -9,6 +9,7 @@ using System.Reflection; using Extensions; using Formatting; + using NetStandardPolyfills; internal class MemberAccessTranslation : ITranslation { @@ -52,6 +53,11 @@ public static ITranslation For(MemberExpression memberAccess, ITranslationContex translateSubject = true; } + if (IsIndexedPropertyAccess(memberAccess, context, out var translation)) + { + return translation; + } + var subjectTranslation = translateSubject ? TranslateSubject(memberAccess, context) : null; @@ -63,8 +69,49 @@ public static ITranslation For(MemberExpression memberAccess, ITranslationContex context); } + private static bool IsIndexedPropertyAccess( + MemberExpression memberAccess, + ITranslationContext context, + out ITranslation translation) + { + if (memberAccess.Member is not PropertyInfo property) + { + translation = null; + return false; + } + + var indexParameters = property.GetIndexParameters(); + + if (indexParameters.None()) + { + translation = null; + return false; + } + + var method = memberAccess.Type != typeof(void) + ? property.GetGetter() : property.GetSetter(); + + var arguments = indexParameters.ProjectToArray(p => + { + if (p.DefaultValue != null) + { + return Expression.Constant(p.DefaultValue, p.ParameterType); + } + + return (Expression)Expression.Default(p.ParameterType); + }); + + var indexedPropertyCall = Expression.Call( + memberAccess.Expression, + method, + arguments); + + translation = MethodCallTranslation.For(indexedPropertyCall, context); + return true; + } + private static ITranslation TranslateSubject( - MemberExpression memberAccess, + MemberExpression memberAccess, ITranslationContext context) { return GetSubjectTranslationOrNull( diff --git a/ReadableExpressions/Translations/MethodCallTranslation.cs b/ReadableExpressions/Translations/MethodCallTranslation.cs index 339c5b01..79c3c163 100644 --- a/ReadableExpressions/Translations/MethodCallTranslation.cs +++ b/ReadableExpressions/Translations/MethodCallTranslation.cs @@ -84,7 +84,7 @@ public static ITranslation For(MethodCallExpression methodCall, ITranslationCont return new CodeBlockTranslation(parameters[0], context).WithNodeType(Call); } - if (IsIndexedPropertyAccess(methodCall)) + if (IsDefaultIndexedPropertyAccess(methodCall)) { return new IndexAccessTranslation(methodCall, parameters, context); } @@ -131,8 +131,11 @@ public static ITranslation GetSubjectTranslation( context.GetTranslationFor(methodCall.Method.DeclaringType); } - private static bool IsIndexedPropertyAccess(MethodCallExpression methodCall) - => methodCall.Method.GetProperty()?.GetIndexParameters().Any() == true; + private static bool IsDefaultIndexedPropertyAccess(MethodCallExpression methodCall) + { + return methodCall.Method.IsHideBySig && + methodCall.Method.GetProperty()?.GetIndexParameters().Any() == true; + } /// /// Creates an for the given . diff --git a/ReadableExpressions/Translations/ParameterSetTranslation.cs b/ReadableExpressions/Translations/ParameterSetTranslation.cs index 3b2a8358..3f1f552c 100644 --- a/ReadableExpressions/Translations/ParameterSetTranslation.cs +++ b/ReadableExpressions/Translations/ParameterSetTranslation.cs @@ -9,7 +9,6 @@ using System.Linq.Expressions; #endif using Extensions; - using NetStandardPolyfills; using Reflection; #if NET35 using static Microsoft.Scripting.Ast.ExpressionType; @@ -57,7 +56,7 @@ public ParameterSetTranslation( private ParameterSetTranslation( IMethodBase method, IEnumerable parameters, - bool areLambdaParameters, + bool showParameterTypeNames, int count, ITranslationContext context) { @@ -97,16 +96,12 @@ private ParameterSetTranslation( var translationSize = 0; var formattingSize = 0; - var showParameterTypeNames = - areLambdaParameters && - context.Settings.ShowLambdaParamTypes; - _parameterTranslations = parameters .Project((p, index) => { ITranslation translation; - if (CanBeConvertedToMethodGroup(p, out var lambdaBodyMethodCall)) + if (context.Analysis.CanBeConvertedToMethodGroup(p, out var lambdaBodyMethodCall)) { translation = new MethodGroupTranslation( Lambda, @@ -130,10 +125,11 @@ private ParameterSetTranslation( // ReSharper disable once PossibleNullReferenceException translation = GetParameterTranslation(p, methodParameters[parameterIndex], context); - goto FinaliseParameterTranslation; } - - translation = context.GetTranslationFor(p); + else + { + translation = context.GetTranslationFor(p); + } if (showParameterTypeNames && (translation is IParameterTranslation parameterTranslation)) @@ -195,6 +191,13 @@ private static ITranslation GetParameterTranslation( IParameter info, ITranslationContext context) { + if (context.Analysis.ShouldBeDiscarded(parameter)) + { + return info.IsOut + ? new DiscardedOutParameterTranslation(parameter, context) + : new DiscardedParameterTranslation(parameter, context); + } + if (info.IsOut) { return new OutParameterTranslation(parameter, context); @@ -208,57 +211,6 @@ private static ITranslation GetParameterTranslation( return context.GetTranslationFor(parameter); } - private static bool CanBeConvertedToMethodGroup( - Expression argument, - out MethodCallExpression lambdaBodyMethodCall) - { - if (argument.NodeType != Lambda) - { - return CannotBeConverted(out lambdaBodyMethodCall); - } - - var argumentLambda = (LambdaExpression)argument; - - if ((argumentLambda.Body.NodeType != Call) || - (argumentLambda.ReturnType != argumentLambda.Body.Type)) - { - return CannotBeConverted(out lambdaBodyMethodCall); - } - - lambdaBodyMethodCall = argumentLambda.Body as MethodCallExpression; - - if (lambdaBodyMethodCall == null) - { - return CannotBeConverted(out lambdaBodyMethodCall); - } - - IList lambdaBodyMethodCallArguments = lambdaBodyMethodCall.Arguments; - - if (lambdaBodyMethodCall.Method.IsExtensionMethod()) - { - lambdaBodyMethodCallArguments = lambdaBodyMethodCallArguments.Skip(1).ToArray(); - } - - if (lambdaBodyMethodCallArguments.Count != argumentLambda.Parameters.Count) - { - return false; - } - - var i = 0; - - var allArgumentTypesMatch = argumentLambda - .Parameters - .All(lambdaParameter => lambdaBodyMethodCallArguments[i++] == lambdaParameter); - - return allArgumentTypesMatch; - } - - private static bool CannotBeConverted(out MethodCallExpression lambdaBodyMethodCall) - { - lambdaBodyMethodCall = null; - return false; - } - private ITranslation GetParameterTranslation( ITranslation translation, ITranslationContext context, @@ -310,10 +262,16 @@ public static ParameterSetTranslation For( LambdaExpression lambda, ITranslationContext context) { + var lambdaMethod = new LambdaExpressionMethodAdapter(lambda); + + var showParameterTypeNames = + context.Settings.ShowLambdaParamTypes || + lambdaMethod.GetParameters().Any(p => p.IsOut || p.IsRef); + return For( - method: null, + lambdaMethod, lambda.Parameters, - areLambdaParameters: true, + showParameterTypeNames, context); } @@ -364,13 +322,13 @@ public static ParameterSetTranslation For( ITranslationContext context) where TParameterExpression : Expression { - return For(method, parameters, areLambdaParameters: false, context); + return For(method, parameters, showParameterTypeNames: false, context); } private static ParameterSetTranslation For( IMethodBase method, ICollection parameters, - bool areLambdaParameters, + bool showParameterTypeNames, ITranslationContext context) where TParameterExpression : Expression { @@ -381,7 +339,7 @@ private static ParameterSetTranslation For( #else parameters, #endif - areLambdaParameters, + showParameterTypeNames, parameters.Count, context); } @@ -648,120 +606,248 @@ private enum ParenthesesMode Never } - private class OutParameterTranslation : ITranslation + private abstract class KeywordParameterTranslationBase : IParameterTranslation { - private const string _out = "out "; - private const string _var = "var "; + private readonly string _keyword; private readonly ITranslation _parameterTranslation; - private readonly ITranslation _typeNameTranslation; - private readonly bool _declareParameterInline; + private readonly IParameterTranslation _wrappedParameterTranslation; + private readonly Func _typeNameTranslationFactory; + private int _translationSize; + private int _formattingSize; + private ITranslation _typeNameTranslation; + + protected KeywordParameterTranslationBase( + string keyword, + Expression parameter, + ITranslationContext context) + { + _keyword = keyword; + _parameterTranslation = context.GetTranslationFor(parameter); + _wrappedParameterTranslation = _parameterTranslation as IParameterTranslation; + _typeNameTranslationFactory = context.GetTranslationFor; + _translationSize = _parameterTranslation.TranslationSize + keyword.Length; + _formattingSize = _parameterTranslation.FormattingSize + context.GetKeywordFormattingSize(); + } + + public ExpressionType NodeType => _parameterTranslation.NodeType; + + public virtual Type Type => _parameterTranslation.Type; + + public virtual string Name => _wrappedParameterTranslation?.Name; - public OutParameterTranslation(Expression parameter, ITranslationContext context) + public virtual int TranslationSize => _translationSize; + + public virtual int FormattingSize => _formattingSize; + + protected ITranslation TypeNameTranslation { - _parameterTranslation = context.GetTranslationFor(parameter); - TranslationSize = _parameterTranslation.TranslationSize + _out.Length; - FormattingSize = _parameterTranslation.FormattingSize + context.GetKeywordFormattingSize(); + get => _typeNameTranslation ??= _typeNameTranslationFactory.Invoke(Type); + private set => _typeNameTranslation = value; + } + + protected bool WriteTypeName { get; private set; } + + public virtual int GetIndentSize() => _parameterTranslation.GetIndentSize(); + + public virtual int GetLineCount() => _parameterTranslation.GetLineCount(); + + public void WriteTo(TranslationWriter writer) + { + writer.WriteKeywordToTranslation(_keyword); + WriteTo(writer, _parameterTranslation); + } + + protected virtual void WriteTo( + TranslationWriter writer, + ITranslation parameterTranslation) + { + } - if ((parameter.NodeType == Parameter) && - context.Settings.DeclareOutParamsInline && - context.Analysis.ShouldBeDeclaredInline((ParameterExpression)parameter)) + public ITranslation WithTypeNames(ITranslationContext context) + { + if (_wrappedParameterTranslation == null) { - _declareParameterInline = true; + return null; + } - if (context.Settings.UseImplicitTypeNames) - { - TranslationSize += _var.Length; - FormattingSize += context.GetKeywordFormattingSize(); - return; - } + WriteTypeName = true; + SubtractParameterTranslationSizes(); + + TypeNameTranslation = _wrappedParameterTranslation + .WithTypeNames(context); + + AddParameterTranslationSizes(); + return TypeNameTranslation; + } - _typeNameTranslation = context.GetTranslationFor(parameter.Type); - TranslationSize += _typeNameTranslation.TranslationSize + 1; - FormattingSize += _typeNameTranslation.FormattingSize; + public void WithoutTypeNames(ITranslationContext context) + { + if (_wrappedParameterTranslation == null) + { + return; } + + WriteTypeName = false; + SubtractParameterTranslationSizes(); + _wrappedParameterTranslation.WithoutTypeNames(context); + AddParameterTranslationSizes(); } - public ExpressionType NodeType => _parameterTranslation.NodeType; + private void SubtractParameterTranslationSizes() + { + _translationSize -= _wrappedParameterTranslation.TranslationSize; + _formattingSize -= _wrappedParameterTranslation.FormattingSize; + } - public Type Type => _parameterTranslation.Type; + private void AddParameterTranslationSizes() + { + _translationSize += _wrappedParameterTranslation.TranslationSize; + _formattingSize += _wrappedParameterTranslation.FormattingSize; + } + } - public int TranslationSize { get; } + private class DiscardedParameterTranslation : KeywordParameterTranslationBase + { + private const string _discard = "_"; - public int FormattingSize { get; } + public DiscardedParameterTranslation( + Expression parameter, + ITranslationContext context) : + this(keyword: string.Empty, parameter, context) + { + } - public int GetIndentSize() + protected DiscardedParameterTranslation( + string keyword, + Expression parameter, + ITranslationContext context) : + base(keyword, parameter, context) { - var indentSize = _parameterTranslation.GetIndentSize(); + } + + public override Type Type => typeof(void); + + public override string Name => _discard; - if (_typeNameTranslation != null) + public override int TranslationSize + => base.TranslationSize + _discard.Length; + + protected override void WriteTo( + TranslationWriter writer, + ITranslation parameterTranslation) + { + if (WriteTypeName) { - indentSize += _typeNameTranslation.GetIndentSize(); + TypeNameTranslation.WriteTo(writer); + writer.WriteSpaceToTranslation(); } - return indentSize; + writer.WriteToTranslation(_discard); + } + } + + private class DiscardedOutParameterTranslation : DiscardedParameterTranslation + { + private const string _out = "out "; + private readonly int _formattingSize; + + public DiscardedOutParameterTranslation( + Expression parameter, + ITranslationContext context) : base(_out, parameter, context) + { + _formattingSize = context.GetKeywordFormattingSize(); } - public int GetLineCount() + public override int TranslationSize + => base.TranslationSize + _out.Length; + + public override int FormattingSize + => base.FormattingSize + _formattingSize; + } + + private class OutParameterTranslation : KeywordParameterTranslationBase + { + private const string _out = "out "; + private const string _var = "var "; + private readonly bool _declareParameterInline; + private readonly bool _useImplicitTypeName; + private readonly int _translationSize; + private readonly int _formattingSize; + + public OutParameterTranslation( + Expression parameter, + ITranslationContext context) : base(_out, parameter, context) { - var parameterLineCount = _parameterTranslation.GetLineCount(); + if (DoNotDeclareInline(parameter, context)) + { + return; + } + + _declareParameterInline = true; - if (_declareParameterInline && _typeNameTranslation != null) + if (!context.Settings.UseImplicitTypeNames) { - parameterLineCount += _typeNameTranslation.GetLineCount(); + return; } - return parameterLineCount; + _useImplicitTypeName = true; + _translationSize += _var.Length; + _formattingSize += context.GetKeywordFormattingSize(); } - public void WriteTo(TranslationWriter writer) + #region Setup + + private static bool DoNotDeclareInline( + Expression parameter, + ITranslationContext context) { - writer.WriteKeywordToTranslation(_out); + return !context.Analysis + .ShouldBeDeclaredInOutputParameterUse(parameter); + } + #endregion + + public override int TranslationSize + => base.TranslationSize + _translationSize; + + public override int FormattingSize + => base.FormattingSize + _formattingSize; + + protected override void WriteTo( + TranslationWriter writer, + ITranslation parameterTranslation) + { if (_declareParameterInline) { - if (_typeNameTranslation != null) + if (_useImplicitTypeName) { - _typeNameTranslation.WriteTo(writer); - writer.WriteSpaceToTranslation(); + writer.WriteKeywordToTranslation(_var); } else { - writer.WriteKeywordToTranslation(_var); + TypeNameTranslation.WriteTo(writer); + writer.WriteSpaceToTranslation(); } } - _parameterTranslation.WriteTo(writer); + parameterTranslation.WriteTo(writer); } } - private class RefParameterTranslation : ITranslation + private class RefParameterTranslation : KeywordParameterTranslationBase { private const string _ref = "ref "; - private readonly ITranslation _parameterTranslation; - public RefParameterTranslation(Expression parameter, ITranslationContext context) + public RefParameterTranslation( + Expression parameter, + ITranslationContext context) : base(_ref, parameter, context) { - _parameterTranslation = context.GetTranslationFor(parameter); - TranslationSize = _parameterTranslation.TranslationSize + _ref.Length; - FormattingSize = _parameterTranslation.FormattingSize + context.GetKeywordFormattingSize(); } - public ExpressionType NodeType => _parameterTranslation.NodeType; - - public Type Type => _parameterTranslation.Type; - - public int TranslationSize { get; } - - public int FormattingSize { get; } - - public int GetIndentSize() => _parameterTranslation.GetIndentSize(); - - public int GetLineCount() => _parameterTranslation.GetLineCount(); - - public void WriteTo(TranslationWriter writer) + protected override void WriteTo(TranslationWriter writer, + ITranslation parameterTranslation) { - writer.WriteKeywordToTranslation(_ref); - _parameterTranslation.WriteTo(writer); + parameterTranslation.WriteTo(writer); } } } diff --git a/ReadableExpressions/Translations/ParameterTranslation.cs b/ReadableExpressions/Translations/ParameterTranslation.cs index 7a1aaaa8..c30efbd4 100644 --- a/ReadableExpressions/Translations/ParameterTranslation.cs +++ b/ReadableExpressions/Translations/ParameterTranslation.cs @@ -59,12 +59,17 @@ protected ParameterTranslationBase( public string Name { get; } - public void WithTypeNames(ITranslationContext context) + public ITranslation WithTypeNames(ITranslationContext context) { - _typeNameTranslation = context.GetTranslationFor(Type); + if (_typeNameTranslation == null) + { + _typeNameTranslation = context.GetTranslationFor(Type); + + TranslationSize += _typeNameTranslation.TranslationSize; + FormattingSize += _typeNameTranslation.FormattingSize; + } - TranslationSize += _typeNameTranslation.TranslationSize; - FormattingSize += _typeNameTranslation.FormattingSize; + return _typeNameTranslation; } public void WithoutTypeNames(ITranslationContext context) diff --git a/ReadableExpressions/Translations/Reflection/ClrMethodWrapperBase.cs b/ReadableExpressions/Translations/Reflection/ClrMethodWrapperBase.cs index b0d829e9..c1096d43 100644 --- a/ReadableExpressions/Translations/Reflection/ClrMethodWrapperBase.cs +++ b/ReadableExpressions/Translations/Reflection/ClrMethodWrapperBase.cs @@ -1,10 +1,8 @@ namespace AgileObjects.ReadableExpressions.Translations.Reflection { - using System; using System.Collections.ObjectModel; using System.Reflection; using Extensions; - using NetStandardPolyfills; /// /// Helper base class for implementations which wrap MethodBase-derived @@ -92,29 +90,5 @@ public ReadOnlyCollection GetParameters() /// public abstract IType ReturnType { get; } - - private class ClrParameterWrapper : IParameter - { - private readonly ParameterInfo _parameter; - private IType _type; - - public ClrParameterWrapper(ParameterInfo parameter) - { - _parameter = parameter; - } - - public IType Type - => _type ??= ClrTypeWrapper.For(ParameterType); - - private Type ParameterType => _parameter.ParameterType; - - public string Name => _parameter.Name; - - public bool IsOut => _parameter.IsOut; - - public bool IsRef => !IsOut && ParameterType.IsByRef; - - public bool IsParamsArray => _parameter.IsParamsArray(); - } } } \ No newline at end of file diff --git a/ReadableExpressions/Translations/Reflection/ClrParameterWrapper.cs b/ReadableExpressions/Translations/Reflection/ClrParameterWrapper.cs new file mode 100644 index 00000000..3a5a6d69 --- /dev/null +++ b/ReadableExpressions/Translations/Reflection/ClrParameterWrapper.cs @@ -0,0 +1,30 @@ +namespace AgileObjects.ReadableExpressions.Translations.Reflection +{ + using System; + using System.Reflection; + using NetStandardPolyfills; + + internal class ClrParameterWrapper : IParameter + { + private readonly ParameterInfo _parameter; + private IType _type; + + public ClrParameterWrapper(ParameterInfo parameter) + { + _parameter = parameter; + } + + public IType Type + => _type ??= ClrTypeWrapper.For(ParameterType); + + private Type ParameterType => _parameter.ParameterType; + + public string Name => _parameter.Name; + + public bool IsOut => _parameter.IsOut; + + public bool IsRef => !IsOut && ParameterType.IsByRef; + + public bool IsParamsArray => _parameter.IsParamsArray(); + } +} \ No newline at end of file diff --git a/ReadableExpressions/Translations/Reflection/ClrTypeWrapper.cs b/ReadableExpressions/Translations/Reflection/ClrTypeWrapper.cs index 131c45c0..cf089e8c 100644 --- a/ReadableExpressions/Translations/Reflection/ClrTypeWrapper.cs +++ b/ReadableExpressions/Translations/Reflection/ClrTypeWrapper.cs @@ -52,7 +52,7 @@ public class ClrTypeWrapper : IType private static readonly object _typeCacheLock = new(); private static readonly Dictionary _types = new(); #endif - private readonly Type _type; + private readonly Type _clrType; private IType _baseType; private int? _genericParameterCount; private ReadOnlyCollection _genericTypeArguments; @@ -64,9 +64,9 @@ public class ClrTypeWrapper : IType private ReadOnlyCollection _allInterfaces; private IEnumerable _allMembers; - private ClrTypeWrapper(Type type) + private ClrTypeWrapper(Type clrType) { - _type = type; + _clrType = clrType; } #region Factory Methods @@ -159,68 +159,68 @@ public static bool AreEqual(IType type, IType otherType) /// public IType BaseType - => _baseType ??= For(_type.GetBaseType()); + => _baseType ??= For(_clrType.GetBaseType()); /// public ReadOnlyCollection AllInterfaces => _allInterfaces ??= GetAllInterfaces().ToReadOnlyCollection(); private IList GetAllInterfaces() - => _type.GetAllInterfaces().ProjectToArray(For); + => _clrType.GetAllInterfaces().ProjectToArray(For); /// - public Assembly Assembly => _type.GetAssembly(); + public Assembly Assembly => _clrType.GetAssembly(); /// - public string Namespace => _type.Namespace; + public string Namespace => _clrType.Namespace; /// - public string Name => _type.Name; + public string Name => _clrType.Name; /// - public string FullName => _type.FullName; + public string FullName => _clrType.FullName; /// - public bool IsInterface => _type.IsInterface(); + public bool IsInterface => _clrType.IsInterface(); /// - public bool IsClass => _type.IsClass(); + public bool IsClass => _clrType.IsClass(); /// - public bool IsEnum => _type.IsEnum(); + public bool IsEnum => _clrType.IsEnum(); /// - public bool IsPrimitive => _type.IsPrimitive(); + public bool IsPrimitive => _clrType.IsPrimitive(); /// - public bool IsAnonymous => _type.IsAnonymous(); + public bool IsAnonymous => _clrType.IsAnonymous(); /// - public bool IsAbstract => _type.IsAbstract(); + public bool IsAbstract => _clrType.IsAbstract(); /// - public bool IsSealed => _type.IsSealed(); + public bool IsSealed => _clrType.IsSealed(); /// - public bool IsEnumerable => _type.IsEnumerable(); + public bool IsEnumerable => _clrType.IsEnumerable(); /// - public bool IsDictionary => _type.IsDictionary(); + public bool IsDictionary => _clrType.IsDictionary(); /// - public bool IsGeneric => _type.IsGenericType(); + public bool IsGeneric => _clrType.IsGenericType(); /// public bool IsGenericDefinition #if NETSTANDARD1_0 - => _type.GetTypeInfo().IsGenericTypeDefinition; + => _clrType.GetTypeInfo().IsGenericTypeDefinition; #else - => _type.IsGenericTypeDefinition; + => _clrType.IsGenericTypeDefinition; #endif /// public IType GenericDefinition => IsGenericDefinition - ? For(_type.GetGenericTypeDefinition()) + ? For(_clrType.GetGenericTypeDefinition()) : null; /// @@ -242,15 +242,15 @@ public ReadOnlyCollection GenericTypeArguments private IType[] GetGenericTypeArguments() { return IsGeneric - ? _type.GetGenericTypeArguments().ProjectToArray(For) + ? _clrType.GetGenericTypeArguments().ProjectToArray(For) : Enumerable.EmptyArray; } /// - public bool IsGenericParameter => _type.IsGenericParameter(); + public bool IsGenericParameter => _clrType.IsGenericParameter(); /// - public GenericParameterAttributes Constraints => _type.GetConstraints(); + public GenericParameterAttributes Constraints => _clrType.GetConstraints(); /// public ReadOnlyCollection ConstraintTypes @@ -264,7 +264,7 @@ public ReadOnlyCollection ConstraintTypes private IList GetUniqueConstraintTypes() { - var typeConstraints = _type.GetConstraintTypes().ProjectToArray(For); + var typeConstraints = _clrType.GetConstraintTypes().ProjectToArray(For); var constraintCount = typeConstraints.Length; switch (constraintCount) @@ -312,19 +312,19 @@ private IList GetUniqueConstraintTypes() } /// - public bool IsNested => _type.IsNested; + public bool IsNested => _clrType.IsNested; /// public IType DeclaringType => _declaringType ??= GetDeclaringType(); private IType GetDeclaringType() { - return _type.DeclaringType != null - ? For(_type.DeclaringType) : null; + return _clrType.DeclaringType != null + ? For(_clrType.DeclaringType) : null; } /// - public bool IsArray => _type.IsArray; + public bool IsArray => _clrType.IsArray; /// public IType ElementType => _elementType ??= GetElementType(); @@ -340,10 +340,10 @@ private IType GetElementType() if (IsByRef) { - return For(_type.GetElementType()); + return For(_clrType.GetElementType()); } - if (_type.TryGetElementType(out var elementType)) + if (_clrType.TryGetElementType(out var elementType)) { return For(elementType); } @@ -362,18 +362,18 @@ public IType NonNullableUnderlyingType => _underlyingNonNullableType ??= GetUnderlyingNonNullableType(); /// - public bool IsByRef => _type.IsByRef; + public bool IsByRef => _clrType.IsByRef; private IType GetUnderlyingNonNullableType() { - var nonNullableUnderlyingType = Nullable.GetUnderlyingType(_type); + var nonNullableUnderlyingType = Nullable.GetUnderlyingType(_clrType); return nonNullableUnderlyingType != null ? new ClrTypeWrapper(nonNullableUnderlyingType) : null; } /// - public IEnumerable AllMembers => _allMembers ??= _type.GetAllMembers(); + public IEnumerable AllMembers => _allMembers ??= _clrType.GetAllMembers(); /// public IEnumerable GetMembers(Action selectionConfigurator) @@ -411,14 +411,14 @@ public override bool Equals(object obj) public bool Equals(IType otherType) => AreEqual(this, otherType); /// - public Type AsType() => _type; + public Type AsType() => _clrType; /// public override int GetHashCode() { unchecked { - return _type.GetHashCode() * 397; + return _clrType.GetHashCode() * 397; } } @@ -426,7 +426,7 @@ public override int GetHashCode() /// Gets a string representation of this . /// /// A string representation of this . - public override string ToString() => _type.ToString(); + public override string ToString() => _clrType.ToString(); private class ClrBaseTypeWrapper : IType { diff --git a/ReadableExpressions/Translations/Reflection/IConstructor.cs b/ReadableExpressions/Translations/Reflection/IConstructor.cs index 448f9967..b5683ec2 100644 --- a/ReadableExpressions/Translations/Reflection/IConstructor.cs +++ b/ReadableExpressions/Translations/Reflection/IConstructor.cs @@ -3,7 +3,7 @@ namespace AgileObjects.ReadableExpressions.Translations.Reflection /// /// Implementing classes will provide metadata about a type constructor. /// - public interface IConstructor : IMethodBase + public interface IConstructor : IMethodMemberBase { } } \ No newline at end of file diff --git a/ReadableExpressions/Translations/Reflection/IMethod.cs b/ReadableExpressions/Translations/Reflection/IMethod.cs index aceb4c04..34ee0218 100644 --- a/ReadableExpressions/Translations/Reflection/IMethod.cs +++ b/ReadableExpressions/Translations/Reflection/IMethod.cs @@ -5,7 +5,7 @@ namespace AgileObjects.ReadableExpressions.Translations.Reflection /// /// Implementing classes will provide metadata about a method. /// - public interface IMethod : IMethodBase + public interface IMethod : IMethodMemberBase { /// /// Gets a value indicating whether the has generic Type parameters. diff --git a/ReadableExpressions/Translations/Reflection/IMethodBase.cs b/ReadableExpressions/Translations/Reflection/IMethodBase.cs index 54a7ae18..11c843ae 100644 --- a/ReadableExpressions/Translations/Reflection/IMethodBase.cs +++ b/ReadableExpressions/Translations/Reflection/IMethodBase.cs @@ -5,7 +5,7 @@ namespace AgileObjects.ReadableExpressions.Translations.Reflection /// /// Implementing classes will provide basic metadata about a method. /// - public interface IMethodBase : IComplexMember + public interface IMethodBase { /// /// Gets a value indicating whether the is an extension method. diff --git a/ReadableExpressions/Translations/Reflection/IMethodMemberBase.cs b/ReadableExpressions/Translations/Reflection/IMethodMemberBase.cs new file mode 100644 index 00000000..7d5f3661 --- /dev/null +++ b/ReadableExpressions/Translations/Reflection/IMethodMemberBase.cs @@ -0,0 +1,9 @@ +namespace AgileObjects.ReadableExpressions.Translations.Reflection +{ + /// + /// Implementing classes will provide basic metadata about a method member. + /// + public interface IMethodMemberBase : IComplexMember, IMethodBase + { + } +} \ No newline at end of file diff --git a/ReadableExpressions/Translations/Reflection/LambdaExpressionMethodAdapter.cs b/ReadableExpressions/Translations/Reflection/LambdaExpressionMethodAdapter.cs new file mode 100644 index 00000000..c6498c7f --- /dev/null +++ b/ReadableExpressions/Translations/Reflection/LambdaExpressionMethodAdapter.cs @@ -0,0 +1,47 @@ +namespace AgileObjects.ReadableExpressions.Translations.Reflection +{ + using System.Collections.ObjectModel; +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif + using Extensions; + using NetStandardPolyfills; + + internal class LambdaExpressionMethodAdapter : IMethodBase + { + private readonly ReadOnlyCollection _parameters; + + public LambdaExpressionMethodAdapter(LambdaExpression lambda) + { + _parameters = GetParameters(lambda); + } + + #region Setup + + private static ReadOnlyCollection GetParameters( + LambdaExpression lambda) + { + var parameters = lambda.Parameters; + + if (parameters.Count == 0) + { + return Enumerable.EmptyReadOnlyCollection; + } + + var delegateParameters = lambda.Type + .GetPublicInstanceMethod("Invoke") + .GetParameters() + .ProjectToArray(p => (IParameter)new ClrParameterWrapper(p)); + + return new ReadOnlyCollection(delegateParameters); + } + + #endregion + + public bool IsExtensionMethod => false; + + public ReadOnlyCollection GetParameters() => _parameters; + } +} diff --git a/ReadableExpressions/Translations/TranslationWriter.cs b/ReadableExpressions/Translations/TranslationWriter.cs index a2f357d5..668c2301 100644 --- a/ReadableExpressions/Translations/TranslationWriter.cs +++ b/ReadableExpressions/Translations/TranslationWriter.cs @@ -268,6 +268,11 @@ private void WriteToTranslation(char character, TokenType tokenType) /// public void WriteToTranslation(string stringValue, TokenType tokenType = Default) { + if (string.IsNullOrEmpty(stringValue)) + { + return; + } + if (stringValue.Length == 1) { WriteToTranslation(stringValue[0], tokenType); diff --git a/docs/src/configuration.md b/docs/src/configuration.md index 956585b6..827b9fd3 100644 --- a/docs/src/configuration.md +++ b/docs/src/configuration.md @@ -20,6 +20,14 @@ To declare output parameter variables inline with the method where they are firs string readable = myExpression .ToReadableString(c => c.DeclareOutputParametersInline); ``` +``` + +To discard unused output parameter variables or lambda parameters, use: + +```csharp +string readable = myExpression + .ToReadableString(c => c.DiscardUnusedParameters); +``` To maintain explicit generic arguments on method calls where they are implied, use: