Skip to content

Commit 9e44d6f

Browse files
authored
Releases/next (#141)
* Fixing nullable enum translation, re: #134 * Extending ShowCapturedValues capabilities * Adding .NET 8 test project * Fixing parameterless Value Tuple translation, re: #135 * Tidy * Fixing non-equality enum comparisons, re #136 * v4.1.2 * Release notes --------- Co-authored-by: Steve Wilkes <[email protected]>
1 parent 6b9aa2c commit 9e44d6f

30 files changed

+634
-183
lines changed

Directory.Build.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<Company>AgileObjects Ltd</Company>
66
<Product>AgileObjects.ReadableExpressions</Product>
77
<Authors>Steve Wilkes</Authors>
8-
<Copyright>Copyright © AgileObjects Ltd 2023</Copyright>
8+
<Copyright>Copyright © AgileObjects Ltd 2024</Copyright>
99
<NeutralResourcesLanguage>en</NeutralResourcesLanguage>
1010
<RepositoryType>git</RepositoryType>
1111
<RepositoryUrl>https://github.com/AgileObjects/ReadableExpressions</RepositoryUrl>
Binary file not shown.
Binary file not shown.

src/ReadableExpressions.sln

+11
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.Visuali
9191
EndProject
9292
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.Visualizers.Vs17.6.ObjectSource", "Visualizers\ReadableExpressions.Visualizers.Vs17.6.ObjectSource\ReadableExpressions.Visualizers.Vs17.6.ObjectSource.csproj", "{EFF649F7-ABD2-49E6-BA1A-07BC34F89AA2}"
9393
EndProject
94+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadableExpressions.UnitTests.Net8", "Tests\ReadableExpressions.UnitTests.Net8\ReadableExpressions.UnitTests.Net8.csproj", "{E40E25FE-C7FA-4455-87A3-15D41784706E}"
95+
EndProject
9496
Global
9597
GlobalSection(SolutionConfigurationPlatforms) = preSolution
9698
Debug|Any CPU = Debug|Any CPU
@@ -354,6 +356,14 @@ Global
354356
{EFF649F7-ABD2-49E6-BA1A-07BC34F89AA2}.Release|Any CPU.Build.0 = Release|Any CPU
355357
{EFF649F7-ABD2-49E6-BA1A-07BC34F89AA2}.Release|x86.ActiveCfg = Release|Any CPU
356358
{EFF649F7-ABD2-49E6-BA1A-07BC34F89AA2}.Release|x86.Build.0 = Release|Any CPU
359+
{E40E25FE-C7FA-4455-87A3-15D41784706E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
360+
{E40E25FE-C7FA-4455-87A3-15D41784706E}.Debug|Any CPU.Build.0 = Debug|Any CPU
361+
{E40E25FE-C7FA-4455-87A3-15D41784706E}.Debug|x86.ActiveCfg = Debug|Any CPU
362+
{E40E25FE-C7FA-4455-87A3-15D41784706E}.Debug|x86.Build.0 = Debug|Any CPU
363+
{E40E25FE-C7FA-4455-87A3-15D41784706E}.Release|Any CPU.ActiveCfg = Release|Any CPU
364+
{E40E25FE-C7FA-4455-87A3-15D41784706E}.Release|Any CPU.Build.0 = Release|Any CPU
365+
{E40E25FE-C7FA-4455-87A3-15D41784706E}.Release|x86.ActiveCfg = Release|Any CPU
366+
{E40E25FE-C7FA-4455-87A3-15D41784706E}.Release|x86.Build.0 = Release|Any CPU
357367
EndGlobalSection
358368
GlobalSection(SolutionProperties) = preSolution
359369
HideSolutionNode = FALSE
@@ -394,6 +404,7 @@ Global
394404
{53A69151-1721-4ED5-BB65-6A750D46B133} = {E2401C71-C5F2-46FB-B5A3-E6EFB85106B9}
395405
{CAC4A53F-D4B9-4AD5-864A-F1141ECE91DA} = {4A12EE3E-81ED-4842-A69F-9D15413DC46D}
396406
{EFF649F7-ABD2-49E6-BA1A-07BC34F89AA2} = {4A12EE3E-81ED-4842-A69F-9D15413DC46D}
407+
{E40E25FE-C7FA-4455-87A3-15D41784706E} = {E2401C71-C5F2-46FB-B5A3-E6EFB85106B9}
397408
EndGlobalSection
398409
GlobalSection(ExtensibilityGlobals) = postSolution
399410
SolutionGuid = {7EFE121E-7A84-43A4-8C76-7EE70DF2736A}

src/ReadableExpressions.sln.DotSettings

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=LocalConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;</s:String>
55
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
66
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
7+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
8+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=236f7aa5_002D7b06_002D43ca_002Dbf2a_002D9b31bfcff09a/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="CONSTANT_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
9+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=669e5282_002Dfb4b_002D4e90_002D91e7_002D07d269d04b60/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="CONSTANT_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
10+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a4f433b8_002Dabcd_002D4e55_002Da08f_002D82e78cef0f0c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"&gt;&lt;ElementKinds&gt;&lt;Kind Name="LOCAL_CONSTANT" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;&lt;/Policy&gt;</s:String>
711
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
812
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FFUNCTION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
913
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FVARIABLE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
@@ -45,4 +49,5 @@
4549
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=NAMESPACE_005FALIAS/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
4650
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
4751
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FRESOURCE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
48-
<s:String x:Key="/Default/Environment/PerformanceGuide/SwitchBehaviour/=Antivirus/@EntryIndexedValue">DO_NOTHING</s:String></wpf:ResourceDictionary>
52+
<s:String x:Key="/Default/Environment/PerformanceGuide/SwitchBehaviour/=Antivirus/@EntryIndexedValue">DO_NOTHING</s:String>
53+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

src/ReadableExpressions/Extensions/InternalEnumerableExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static ReadOnlyCollection<T> ToReadOnlyCollection<T>(
3535
this IList<T> items)
3636
{
3737
return items.Count != 0
38-
? new ReadOnlyCollection<T>(items)
38+
? new(items)
3939
: Enumerable<T>.EmptyReadOnlyCollection;
4040
}
4141

src/ReadableExpressions/Extensions/InternalExpressionExtensions.cs

+10-10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#endif
1010
using System.Reflection;
1111
using NetStandardPolyfills;
12+
using Translations;
1213
#if NET35
1314
using static Microsoft.Scripting.Ast.ExpressionType;
1415
#else
@@ -69,13 +70,10 @@ public static bool IsReturnable(this Expression expression)
6970
public static bool IsReturnable(this BlockExpression block)
7071
=> block.HasReturnType() && block.Result.IsReturnable();
7172

72-
public static bool IsCapturedValue(
73-
this Expression expression,
74-
out object capturedValue,
75-
out bool isStatic)
73+
public static bool IsCapture(this Expression expression, out Capture capture)
7674
{
77-
capturedValue = null;
78-
isStatic = false;
75+
capture = new();
76+
7977
var capturedMemberAccesses = new List<MemberInfo>();
8078

8179
while (true)
@@ -102,22 +100,23 @@ public static bool IsCapturedValue(
102100
return false;
103101
}
104102

105-
var declaringType = capturedMemberAccesses.LastOrDefault()?.DeclaringType;
103+
var declaringType = capturedMemberAccesses
104+
.LastOrDefault()?.DeclaringType;
106105

107106
if (captureConstant.Type != declaringType)
108107
{
109108
return false;
110109
}
111110

112-
capturedValue = captureConstant.Value;
111+
capture.Object = captureConstant.Value;
113112
break;
114113

115114
case Convert:
116115
expression = expression.GetUnaryOperand();
117116
continue;
118117

119118
case null:
120-
isStatic = true;
119+
capture.IsStatic = true;
121120
break;
122121

123122
default:
@@ -131,9 +130,10 @@ public static bool IsCapturedValue(
131130

132131
for (var i = capturedMemberAccesses.Count - 1; i >= 0; --i)
133132
{
134-
capturedValue = capturedMemberAccesses[i].GetValue(capturedValue);
133+
capture.Object = capturedMemberAccesses[i].GetValue(capture.Object);
135134
}
136135

136+
capture.Type = capturedMemberAccesses[0].GetMemberInfoType();
137137
return true;
138138
}
139139
}

src/ReadableExpressions/Extensions/InternalReflectionExtensions.cs

+93-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
namespace AgileObjects.ReadableExpressions.Extensions
22
{
3-
#if FEATURE_VALUE_TUPLE
43
using System;
5-
#endif
64
using System.Collections.Generic;
5+
using System.Linq;
76
using System.Reflection;
87
using NetStandardPolyfills;
98
using Translations.Reflection;
@@ -49,18 +48,105 @@ public static string GetKeywordOrNull(this IType type)
4948
}
5049

5150
public static object GetValue(this MemberInfo member, object subject)
52-
{
53-
var hasSubject = subject != null;
51+
=> member.TryGetValue(subject, out var value) ? value : null;
5452

53+
public static Type GetMemberInfoType(this MemberInfo member)
54+
{
5555
return member switch
5656
{
57-
FieldInfo field when hasSubject || field.IsStatic => field.GetValue(subject),
58-
PropertyInfo property when hasSubject || property.IsStatic() => property.GetValue(subject,
59-
Enumerable<object>.EmptyArray),
57+
FieldInfo field => field.FieldType,
58+
PropertyInfo property => property.PropertyType,
59+
MethodInfo method => method.ReturnType,
6060
_ => null
6161
};
6262
}
6363

64+
public static bool TryGetValue(
65+
this MemberInfo member,
66+
object subject,
67+
out object value)
68+
{
69+
var hasSubject = subject != null;
70+
71+
switch (member)
72+
{
73+
case FieldInfo field when hasSubject || field.IsStatic:
74+
value = field.GetValue(subject);
75+
break;
76+
77+
case PropertyInfo property when hasSubject || property.IsStatic():
78+
value = property.GetValue(subject, Enumerable<object>.EmptyArray);
79+
break;
80+
81+
case MethodInfo method when method.IsCallable(subject, out var parameters):
82+
value = method.Invoke(subject, parameters);
83+
break;
84+
85+
default:
86+
value = null;
87+
return false;
88+
}
89+
90+
return true;
91+
}
92+
93+
private static bool IsCallable(
94+
this MethodInfo method,
95+
object subject,
96+
out object[] parameters)
97+
{
98+
if (!method.IsPure())
99+
{
100+
parameters = null;
101+
return false;
102+
}
103+
104+
var parameterCount = method.GetParameters().Length;
105+
var isParameterless = parameterCount == 0;
106+
107+
if (!method.IsStatic)
108+
{
109+
parameters = Enumerable<object>.EmptyArray;
110+
return isParameterless && subject != null;
111+
}
112+
113+
if (isParameterless)
114+
{
115+
parameters = Enumerable<object>.EmptyArray;
116+
return true;
117+
}
118+
119+
if (parameterCount == 1 && subject != null &&
120+
method.IsExtensionMethod())
121+
{
122+
parameters = new[] { subject };
123+
return true;
124+
}
125+
126+
parameters = null;
127+
return false;
128+
}
129+
130+
private static bool IsPure(this MethodInfo method)
131+
{
132+
if (method.DeclaringType == typeof(Enumerable))
133+
{
134+
return method.Name switch
135+
{
136+
nameof(Enumerable.Any) => true,
137+
nameof(Enumerable.First) => true,
138+
nameof(Enumerable.FirstOrDefault) => true,
139+
nameof(Enumerable.Last) => true,
140+
nameof(Enumerable.LastOrDefault) => true,
141+
_ => false
142+
};
143+
}
144+
145+
return method
146+
.GetCustomAttributes(inherit: false)
147+
.Any(attr => attr.GetType().Name == "PureAttribute");
148+
}
149+
64150
#if FEATURE_VALUE_TUPLE
65151
public static bool IsValueTuple(this Type type)
66152
{

src/ReadableExpressions/ReadableExpressions.csproj

+8-6
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.0' ">1.6.1</NetStandardImplicitPackageVersion>
1515
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.0' ">$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
1616

17-
<AssemblyVersion>4.1.1.0</AssemblyVersion>
18-
<FileVersion>4.1.1.0</FileVersion>
19-
<VersionPrefix>4.1.1</VersionPrefix>
20-
<Version>4.1.1</Version>
17+
<AssemblyVersion>4.1.2.0</AssemblyVersion>
18+
<FileVersion>4.1.2.0</FileVersion>
19+
<VersionPrefix>4.1.2</VersionPrefix>
20+
<Version>4.1.2</Version>
2121

2222
<PackageId>AgileObjects.ReadableExpressions</PackageId>
2323
<Title>AgileObjects.ReadableExpressions</Title>
@@ -26,8 +26,10 @@
2626
<PackageIcon>./Icon.png</PackageIcon>
2727
<PackageTags>ExpressionTrees Debugging DebuggerVisualizers Linq DLR</PackageTags>
2828
<PackageProjectUrl>https://github.com/AgileObjects/ReadableExpressions</PackageProjectUrl>
29-
<PackageReleaseNotes>- Fixing static method access when showing captured values re: #129
30-
- Translating enum comparisons as enum constants
29+
<PackageReleaseNotes>- Fixing nullable enum translation, re: #134
30+
- Improving ShowCapturedValues capabilities to include captured Linq calls, re: #133
31+
- Fixing parameterless Value Tuple translation, re: #135
32+
- Fixing non-equality enum comparisons, re: #136
3133
</PackageReleaseNotes>
3234
<PackageReadmeFile>README.md</PackageReadmeFile>
3335
<PackageOutputPath>../../NuGet</PackageOutputPath>

src/ReadableExpressions/Translations/BinaryTranslation.cs

+48-23
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,25 @@ internal class BinaryTranslation :
2525
private readonly INodeTranslation _rightOperandTranslation;
2626
private bool _suppressParentheses;
2727

28-
private BinaryTranslation(BinaryExpression binary, ITranslationContext context) :
29-
base(IsChecked(binary.NodeType), "(", ")")
28+
private BinaryTranslation(
29+
BinaryExpression binary,
30+
ITranslationContext context) :
31+
this(binary.Left, binary.NodeType, binary.Right, context)
32+
{
33+
}
34+
35+
private BinaryTranslation(
36+
Expression leftOperand,
37+
ExpressionType nodeType,
38+
Expression rightOperand,
39+
ITranslationContext context) :
40+
base(IsChecked(nodeType), "(", ")")
3041
{
3142
_context = context;
32-
NodeType = binary.NodeType;
33-
_leftOperandTranslation = context.GetTranslationFor(binary.Left);
43+
NodeType = nodeType;
44+
_leftOperandTranslation = context.GetTranslationFor(leftOperand);
3445
_operator = GetOperator(NodeType);
35-
_rightOperandTranslation = context.GetTranslationFor(binary.Right);
46+
_rightOperandTranslation = context.GetTranslationFor(rightOperand);
3647

3748
if (_leftOperandTranslation is BinaryTranslation leftNestedBinary &&
3849
HasComplimentaryOperator(leftNestedBinary))
@@ -138,32 +149,45 @@ public static INodeTranslation For(
138149
goto default;
139150

140151
default:
141-
TryGetEnumComparisonExpression(ref binary);
152+
var isEnumComparison = TryGetEnumComparisonExpression(
153+
binary,
154+
out var leftOperand,
155+
out var rightOperand);
156+
157+
if (isEnumComparison)
158+
{
159+
return new BinaryTranslation(
160+
leftOperand,
161+
binary.NodeType,
162+
rightOperand,
163+
context);
164+
}
165+
142166
break;
143167
}
144168

145169
return new BinaryTranslation(binary, context);
146170
}
147171

148-
public static void TryGetEnumComparisonExpression(
149-
ref BinaryExpression comparison)
172+
private static bool TryGetEnumComparisonExpression(
173+
BinaryExpression comparison,
174+
out Expression leftOperand,
175+
out Expression rightOperand)
150176
{
151-
var leftOperandIsEnum =
152-
IsEnumType(comparison.Left, out var leftExpression);
153-
154-
var rightOperandIsEnum =
155-
IsEnumType(comparison.Right, out var rightExpression);
177+
var leftOperandIsEnum = IsEnumType(comparison.Left, out leftOperand);
178+
var rightOperandIsEnum = IsEnumType(comparison.Right, out rightOperand);
156179

157-
if (leftOperandIsEnum || rightOperandIsEnum)
180+
if (!(leftOperandIsEnum || rightOperandIsEnum))
158181
{
159-
var enumType = leftOperandIsEnum
160-
? leftExpression.Type : rightExpression.Type;
161-
162-
comparison = comparison.Update(
163-
GetEnumValue(leftExpression, enumType),
164-
comparison.Conversion,
165-
GetEnumValue(rightExpression, enumType));
182+
return false;
166183
}
184+
185+
var enumType = leftOperandIsEnum
186+
? leftOperand.Type : rightOperand.Type;
187+
188+
leftOperand = GetEnumValue(leftOperand, enumType);
189+
rightOperand = GetEnumValue(rightOperand, enumType);
190+
return true;
167191
}
168192

169193
private static bool IsEnumType(
@@ -187,16 +211,17 @@ private static bool IsEnumType(
187211

188212
private static Expression GetEnumValue(
189213
Expression expression,
190-
Type enumType)
214+
Type enumValueType)
191215
{
192216
if (expression.NodeType != Constant)
193217
{
194218
return expression;
195219
}
196220

197221
var value = ((ConstantExpression)expression).Value;
222+
var enumType = enumValueType.GetNonNullableType();
198223
var enumValue = Enum.Parse(enumType, value.ToString());
199-
return Expression.Constant(enumValue, enumType);
224+
return Expression.Constant(enumValue, enumValueType);
200225
}
201226

202227
#endregion

0 commit comments

Comments
 (0)