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: