Skip to content

Commit 67e90ac

Browse files
authored
Fully qualified type name support, re: issue #27 (#29)
* Start of support for fully qualified type names * Fixing nested generic type name translation bug
1 parent 14ffecc commit 67e90ac

File tree

6 files changed

+173
-65
lines changed

6 files changed

+173
-65
lines changed

ReadableExpressions.UnitTests/TestClassBase.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,25 @@ public abstract class TestClassBase
1313
{
1414
#if NET35
1515
protected static LambdaExpression CreateLambda(LinqExp.Expression<Action> linqLambda)
16-
=> (LambdaExpression)LinqExpressionToDlrExpressionConverter.Convert(linqLambda);
16+
=> LinqExpressionToDlrExpressionConverter.Convert(linqLambda);
1717

1818
protected static LambdaExpression CreateLambda<TArg>(LinqExp.Expression<Action<TArg>> linqLambda)
19-
=> (LambdaExpression)LinqExpressionToDlrExpressionConverter.Convert(linqLambda);
19+
=> LinqExpressionToDlrExpressionConverter.Convert(linqLambda);
2020

2121
protected static LambdaExpression CreateLambda<TArg1, TArg2>(LinqExp.Expression<Action<TArg1, TArg2>> linqLambda)
22-
=> (LambdaExpression)LinqExpressionToDlrExpressionConverter.Convert(linqLambda);
22+
=> LinqExpressionToDlrExpressionConverter.Convert(linqLambda);
2323

2424
internal static LambdaExpression CreateLambda<TReturn>(LinqExp.Expression<Func<TReturn>> linqLambda)
25-
=> (LambdaExpression)LinqExpressionToDlrExpressionConverter.Convert(linqLambda);
25+
=> LinqExpressionToDlrExpressionConverter.Convert(linqLambda);
2626

2727
protected static LambdaExpression CreateLambda<TArg, TReturn>(LinqExp.Expression<Func<TArg, TReturn>> linqLambda)
28-
=> (LambdaExpression)LinqExpressionToDlrExpressionConverter.Convert(linqLambda);
28+
=> LinqExpressionToDlrExpressionConverter.Convert(linqLambda);
2929

3030
protected static LambdaExpression CreateLambda<TArg1, TArg2, TReturn>(LinqExp.Expression<Func<TArg1, TArg2, TReturn>> linqLambda)
31-
=> (LambdaExpression)LinqExpressionToDlrExpressionConverter.Convert(linqLambda);
31+
=> LinqExpressionToDlrExpressionConverter.Convert(linqLambda);
3232

3333
protected static LambdaExpression CreateLambda<TArg1, TArg2, TArg3, TReturn>(LinqExp.Expression<Func<TArg1, TArg2, TArg3, TReturn>> linqLambda)
34-
=> (LambdaExpression)LinqExpressionToDlrExpressionConverter.Convert(linqLambda);
34+
=> LinqExpressionToDlrExpressionConverter.Convert(linqLambda);
3535

3636
internal static string ToReadableString(Expression expression, Func<TranslationSettings, TranslationSettings> configuration = null)
3737
=> expression.ToReadableString(configuration);

ReadableExpressions.UnitTests/WhenTranslatingBlocks.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ public void ShouldNotTerminateMethodCallArguments()
275275
{
276276
var objectVariable = Expression.Variable(typeof(object), "o");
277277
var objectCastToInt = Expression.Convert(objectVariable, typeof(int));
278-
var intToStringMethod = typeof(int).GetPublicInstanceMethods("ToString").First();
278+
var intToStringMethod = typeof(int).GetPublicInstanceMethod("ToString", parameterCount: 0);
279279
var intToStringCall = Expression.Call(objectCastToInt, intToStringMethod);
280280
var intToStringBlock = Expression.Block(intToStringCall);
281281
var openTextFile = CreateLambda((string str) => File.OpenText(str));

ReadableExpressions.UnitTests/WhenTranslatingObjectCreations.cs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,26 @@ public void ShouldTranslateAParameterlessNewExpression()
2626
translated.ShouldBe("new Object()");
2727
}
2828

29+
[Fact]
30+
public void ShouldTranslateAParameterlessNoNamespaceaNewExpression()
31+
{
32+
var createObject = CreateLambda(() => new NoNamespace());
33+
34+
var translated = ToReadableString(createObject.Body);
35+
36+
translated.ShouldBe("new NoNamespace()");
37+
}
38+
39+
[Fact]
40+
public void ShouldTranslateAFullyQualifiedParameterlessNoNamespaceaNewExpression()
41+
{
42+
var createObject = CreateLambda(() => new NoNamespace());
43+
44+
var translated = ToReadableString(createObject.Body, s => s.UseFullyQualifiedTypeNames);
45+
46+
translated.ShouldBe("new NoNamespace()");
47+
}
48+
2949
[Fact]
3050
public void ShouldTranslateANewExpressionWithParameters()
3151
{
@@ -36,6 +56,16 @@ public void ShouldTranslateANewExpressionWithParameters()
3656
translated.ShouldBe("new DateTime(2014, 8, 23)");
3757
}
3858

59+
[Fact]
60+
public void ShouldTranslateAFullyQualifiedNewExpressionWithParameters()
61+
{
62+
var createToday = CreateLambda(() => new DateTime(2018, 11, 17));
63+
64+
var translated = ToReadableString(createToday.Body, s => s.UseFullyQualifiedTypeNames);
65+
66+
translated.ShouldBe("new System.DateTime(2018, 11, 17)");
67+
}
68+
3969
[Fact]
4070
public void ShouldTranslateAnEmptyObjectInitialisation()
4171
{
@@ -47,6 +77,16 @@ public void ShouldTranslateAnEmptyObjectInitialisation()
4777
translated.ShouldBe("new MemoryStream()");
4878
}
4979

80+
[Fact]
81+
public void ShouldTranslateANewFullQualifiedNestedGenericTypeExpression()
82+
{
83+
var createArray = CreateLambda(() => new NestedType<int>.NestedValue<DateTime>());
84+
85+
var translated = ToReadableString(createArray.Body, s => s.UseFullyQualifiedTypeNames);
86+
87+
translated.ShouldBe("new AgileObjects.ReadableExpressions.UnitTests.NestedType<int>.NestedValue<System.DateTime>()");
88+
}
89+
5090
[Fact]
5191
public void ShouldTranslateANewExpressionWithASingleInitialisation()
5292
{
@@ -188,6 +228,16 @@ public void ShouldTranslateANewGenericTypeArrayExpression()
188228
translated.ShouldBe("new List<decimal>[5]");
189229
}
190230

231+
[Fact]
232+
public void ShouldTranslateANewFullyQualifiedGenericTypeArrayExpression()
233+
{
234+
var createArray = CreateLambda(() => new List<decimal>[5]);
235+
236+
var translated = ToReadableString(createArray.Body, s => s.UseFullyQualifiedTypeNames);
237+
238+
translated.ShouldBe("new System.Collections.Generic.List<decimal>[5]");
239+
}
240+
191241
[Fact]
192242
public void ShouldTranslateAnImplicitTypeNewArrayExpressionWithAdditions()
193243
{
@@ -316,7 +366,7 @@ public void ShouldTranslateMultilineConstructorParameters()
316366
public void ShouldTranslateAnAnonymousTypeCreation()
317367
{
318368
var anonType = new { ValueString = default(string), ValueInt = default(int) }.GetType();
319-
var constructor = anonType.GetConstructor(new[] { typeof(string), typeof(int) });
369+
var constructor = anonType.GetPublicInstanceConstructor(typeof(string), typeof(int));
320370

321371
// ReSharper disable once AssignNullToNotNullAttribute
322372
var creation = Expression.New(constructor, Expression.Constant("How much?!"), Expression.Constant(100));
@@ -325,6 +375,20 @@ public void ShouldTranslateAnAnonymousTypeCreation()
325375

326376
translated.ShouldBe("new { ValueString = \"How much?!\", ValueInt = 100 }");
327377
}
378+
379+
[Fact]
380+
public void ShouldTranslateAFullyQualfiedAnonymousTypeCreation()
381+
{
382+
var anonType = new { ValueString = default(string), TimeSpanValue = default(TimeSpan) }.GetType();
383+
var constructor = anonType.GetPublicInstanceConstructor(typeof(string), typeof(TimeSpan));
384+
385+
// ReSharper disable once AssignNullToNotNullAttribute
386+
var creation = Expression.New(constructor, Expression.Constant("How much?!"), Expression.Default(typeof(TimeSpan)));
387+
388+
var translated = ToReadableString(creation, s => s.UseFullyQualifiedTypeNames);
389+
390+
translated.ShouldBe("new { ValueString = \"How much?!\", TimeSpanValue = default(System.TimeSpan) }");
391+
}
328392
}
329393

330394
#region Helper Classes
@@ -355,5 +419,17 @@ internal class ContactDetails
355419
public List<string> PhoneNumbers { get; set; }
356420
}
357421

422+
internal class NestedType<T>
423+
{
424+
internal class NestedValue<TValue>
425+
{
426+
public T Type { get; set; }
427+
428+
public TValue Value { get; set; }
429+
}
430+
}
431+
358432
#endregion
359433
}
434+
435+
public class NoNamespace { }

ReadableExpressions/Extensions/PublicTypeExtensions.cs

Lines changed: 73 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,17 @@ internal static string GetFriendlyName(this Type type, TranslationSettings trans
3636

3737
if (!type.IsGenericType())
3838
{
39-
var qualifiedTypeName = type.FullName.GetSubstitutionOrNull() ?? type.Name;
39+
var substitutedTypeName = type.FullName.GetSubstitutionOrNull();
40+
var qualifiedTypeName = substitutedTypeName ?? type.Name;
4041

4142
if (type.IsNested)
4243
{
4344
return type.DeclaringType.GetFriendlyName(translationSettings) + "." + qualifiedTypeName;
4445
}
4546

46-
return qualifiedTypeName;
47+
return (substitutedTypeName == null)
48+
? GetFinalisedTypeName(type, qualifiedTypeName, translationSettings)
49+
: qualifiedTypeName;
4750
}
4851

4952
Type underlyingNullableType;
@@ -59,67 +62,77 @@ internal static string GetFriendlyName(this Type type, TranslationSettings trans
5962
private static string GetGenericTypeName(Type genericType, TranslationSettings settings)
6063
{
6164
var typeGenericTypeArguments = genericType.GetGenericTypeArguments();
62-
var genericTypeName = GetGenericTypeName(genericType, typeGenericTypeArguments.Length, typeGenericTypeArguments, settings);
65+
var genericTypeName = GetClosedGenericTypeName(genericType, ref typeGenericTypeArguments, settings);
6366

6467
if (!genericType.IsNested)
6568
{
66-
return genericTypeName;
69+
return GetFinalisedTypeName(genericType, genericTypeName, settings);
6770
}
6871

6972
// ReSharper disable once PossibleNullReferenceException
7073
while (genericType.IsNested)
7174
{
7275
genericType = genericType.DeclaringType;
73-
var parentTypeName = genericType.Name;
76+
var parentTypeName = GetClosedGenericTypeName(genericType, ref typeGenericTypeArguments, settings);
7477

75-
var backtickIndex = parentTypeName.IndexOf("`", StringComparison.Ordinal);
78+
genericTypeName = parentTypeName + "." + genericTypeName;
79+
}
7680

77-
if (backtickIndex != -1)
78-
{
79-
var numberOfParameters = int.Parse(parentTypeName.Substring(backtickIndex + 1));
80-
81-
Type[] typeArguments;
82-
83-
if (numberOfParameters == typeGenericTypeArguments.Length)
84-
{
85-
typeArguments = typeGenericTypeArguments;
86-
}
87-
else
88-
{
89-
typeArguments = new Type[numberOfParameters];
90-
var numberOfRemainingTypeArguments = typeGenericTypeArguments.Length - numberOfParameters;
91-
var typeGenericTypeArgumentsSubset = new Type[numberOfRemainingTypeArguments];
92-
93-
Array.Copy(
94-
typeGenericTypeArguments,
95-
numberOfRemainingTypeArguments,
96-
typeArguments,
97-
0,
98-
numberOfParameters);
99-
100-
Array.Copy(
101-
typeGenericTypeArguments,
102-
0,
103-
typeGenericTypeArgumentsSubset,
104-
0,
105-
numberOfRemainingTypeArguments);
106-
107-
typeGenericTypeArguments = typeGenericTypeArgumentsSubset;
108-
}
109-
110-
parentTypeName = GetGenericTypeName(genericType, numberOfParameters, typeArguments, settings);
111-
}
81+
return GetFinalisedTypeName(genericType, genericTypeName, settings);
82+
}
11283

113-
genericTypeName = parentTypeName + "." + genericTypeName;
84+
private static string GetClosedGenericTypeName(
85+
Type genericType,
86+
ref Type[] typeGenericTypeArguments,
87+
TranslationSettings settings)
88+
{
89+
var typeName = genericType.Name;
90+
91+
var backtickIndex = typeName.IndexOf("`", StringComparison.Ordinal);
92+
93+
if (backtickIndex == -1)
94+
{
95+
return typeName;
11496
}
11597

116-
return genericTypeName;
98+
var numberOfParameters = int.Parse(typeName.Substring(backtickIndex + 1));
99+
100+
Type[] typeArguments;
101+
102+
if (numberOfParameters == typeGenericTypeArguments.Length)
103+
{
104+
typeArguments = typeGenericTypeArguments;
105+
}
106+
else
107+
{
108+
typeArguments = new Type[numberOfParameters];
109+
var numberOfRemainingTypeArguments = typeGenericTypeArguments.Length - numberOfParameters;
110+
var typeGenericTypeArgumentsSubset = new Type[numberOfRemainingTypeArguments];
111+
112+
Array.Copy(
113+
typeGenericTypeArguments,
114+
numberOfRemainingTypeArguments,
115+
typeArguments,
116+
0,
117+
numberOfParameters);
118+
119+
Array.Copy(
120+
typeGenericTypeArguments,
121+
0,
122+
typeGenericTypeArgumentsSubset,
123+
0,
124+
numberOfRemainingTypeArguments);
125+
126+
typeGenericTypeArguments = typeGenericTypeArgumentsSubset;
127+
}
128+
129+
return GetGenericTypeName(genericType, numberOfParameters, typeArguments, settings);
117130
}
118131

119132
private static string GetGenericTypeName(
120133
Type type,
121134
int numberOfParameters,
122-
IEnumerable<Type> typeArguments,
135+
IList<Type> typeArguments,
123136
TranslationSettings settings)
124137
{
125138
var anonTypeIndex = 0;
@@ -135,12 +148,12 @@ private static string GetGenericTypeName(
135148

136149
var typeName = type.Name;
137150

138-
var typeGenericTypeArgumentFriendlyNames =
139-
typeArguments.Project(t => GetFriendlyName(t, settings)).Join(", ");
151+
var typeGenericTypeArgumentNames = typeArguments
152+
.ProjectToArray(t => GetFriendlyName(t, settings));
140153

141154
typeName = typeName.Replace(
142155
"`" + numberOfParameters,
143-
"<" + typeGenericTypeArgumentFriendlyNames + ">");
156+
"<" + typeGenericTypeArgumentNames.Join(", ") + ">");
144157

145158
return isAnonType ? GetAnonymousTypeName(typeName, anonTypeIndex) : typeName;
146159
}
@@ -157,6 +170,13 @@ private static string GetAnonymousTypeName(string typeName, int anonTypeIndex)
157170
return typeName;
158171
}
159172

173+
private static string GetFinalisedTypeName(Type type, string typeName, TranslationSettings settings)
174+
{
175+
return settings.FullyQualifyTypeNames && (type.Namespace != null)
176+
? type.Namespace + "." + typeName
177+
: typeName;
178+
}
179+
160180
/// <summary>
161181
/// Retrieves a camel-case variable name for a variable of this <paramref name="type"/>.
162182
/// </summary>
@@ -297,9 +317,8 @@ public static Type GetDictionaryType(this Type type)
297317
}
298318
}
299319

300-
var interfaceType = type
301-
.GetAllInterfaces()
302-
.FirstOrDefault(t => t.IsClosedTypeOf(typeof(IDictionary<,>)));
320+
var interfaceType = InternalEnumerableExtensions.FirstOrDefault(type
321+
.GetAllInterfaces(), t => t.IsClosedTypeOf(typeof(IDictionary<,>)));
303322

304323
return interfaceType;
305324
}
@@ -326,14 +345,13 @@ public static Type GetEnumerableElementType(this Type enumerableType)
326345

327346
if (enumerableType.IsGenericType())
328347
{
329-
return enumerableType.GetGenericTypeArguments().Last();
348+
return InternalEnumerableExtensions.Last(enumerableType.GetGenericTypeArguments());
330349
}
331350

332-
var enumerableInterfaceType = enumerableType
333-
.GetAllInterfaces()
334-
.FirstOrDefault(interfaceType => interfaceType.IsClosedTypeOf(typeof(IEnumerable<>)));
351+
var enumerableInterfaceType = InternalEnumerableExtensions.FirstOrDefault(enumerableType
352+
.GetAllInterfaces(), interfaceType => interfaceType.IsClosedTypeOf(typeof(IEnumerable<>)));
335353

336-
return enumerableInterfaceType?.GetGenericTypeArguments().First() ?? typeof(object);
354+
return InternalEnumerableExtensions.First(enumerableInterfaceType?.GetGenericTypeArguments()) ?? typeof(object);
337355
}
338356

339357
/// <summary>

ReadableExpressions/TranslationSettings.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ internal TranslationSettings()
1414
UseImplicitGenericParameters = true;
1515
}
1616

17+
/// <summary>
18+
/// Fully qualify Type names with their namespace.
19+
/// </summary>
20+
public TranslationSettings UseFullyQualifiedTypeNames
21+
{
22+
get
23+
{
24+
FullyQualifyTypeNames = true;
25+
return this;
26+
}
27+
}
28+
29+
internal bool FullyQualifyTypeNames { get; private set; }
30+
1731
/// <summary>
1832
/// Always specify generic parameter arguments explicitly in &lt;pointy braces&gt;
1933
/// </summary>

ReadableExpressions/Translations/ConstantTranslation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public static ITranslation For(ConstantExpression constant, ITranslationContext
4141
return FixedValueTranslation(constant.Value, valueType);
4242
}
4343

44-
return FixedValueTranslation(valueType.GetFriendlyName(context.Settings), valueType);
44+
return context.GetTranslationFor(valueType).WithNodeType(Constant);
4545
}
4646

4747
private static ITranslation FixedValueTranslation(ConstantExpression constant) => FixedValueTranslation(constant.Value, constant.Type);

0 commit comments

Comments
 (0)