Making tuples pretty too

This commit is contained in:
Phil Scott 2022-02-03 22:33:45 -05:00 committed by Patrik Svensson
parent e8eb5b85b9
commit 74a2e10ff0
5 changed files with 100 additions and 27 deletions

View File

@ -276,16 +276,85 @@ internal static class ExceptionFormatter
var prefix = GetPrefix(parameter);
var parameterType = parameter.ParameterType;
if (parameterType.IsByRef && parameterType.GetElementType() is { } elementType)
string typeName;
if (parameterType.IsGenericType && TryGetTupleName(parameter, parameterType, out var s))
{
parameterType = elementType;
typeName = s;
}
else
{
if (parameterType.IsByRef && parameterType.GetElementType() is { } elementType)
{
parameterType = elementType;
}
typeName = TypeNameHelper.GetTypeDisplayName(parameterType);
}
var typeName = TypeNameHelper.GetTypeDisplayName(parameterType, false, true);
return string.IsNullOrWhiteSpace(prefix) ? typeName : $"{prefix} {typeName}";
}
private static bool TryGetTupleName(ParameterInfo parameter, Type parameterType, [NotNullWhen(true)] out string? tupleName)
{
var customAttribs = parameter.GetCustomAttributes(inherit: false);
var tupleNameAttribute = customAttribs
.OfType<Attribute>()
.FirstOrDefault(a =>
{
var attributeType = a.GetType();
return attributeType.Namespace == "System.Runtime.CompilerServices" &&
attributeType.Name == "TupleElementNamesAttribute";
});
if (tupleNameAttribute != null)
{
var propertyInfo = tupleNameAttribute.GetType()
.GetProperty("TransformNames", BindingFlags.Instance | BindingFlags.Public)!;
var tupleNames = propertyInfo.GetValue(tupleNameAttribute) as IList<string>;
if (tupleNames?.Count > 0)
{
var args = parameterType.GetGenericArguments();
var sb = new StringBuilder();
sb.Append('(');
for (var i = 0; i < args.Length; i++)
{
if (i > 0)
{
sb.Append(", ");
}
sb.Append(TypeNameHelper.GetTypeDisplayName(args[i]));
if (i >= tupleNames.Count)
{
continue;
}
var argName = tupleNames[i];
sb.Append(' ');
sb.Append(argName);
}
sb.Append(')');
tupleName = sb.ToString();
return true;
}
}
else if (parameterType.Namespace == "System" && parameterType.Name.Contains("ValueTuple`"))
{
var args = parameterType.GetGenericArguments().Select(i => TypeNameHelper.GetTypeDisplayName(i));
tupleName = $"({string.Join(", ", args)})";
return true;
}
tupleName = null;
return false;
}
private static string GetMethodName(ref MethodBase method, out bool isAsync)
{
var declaringType = method.DeclaringType;

View File

@ -40,35 +40,13 @@ internal static class TypeNameHelper
/// <param name="fullName"><c>true</c> to print a fully qualified name.</param>
/// <param name="includeGenericParameterNames"><c>true</c> to include generic parameter names.</param>
/// <returns>The pretty printed type name.</returns>
public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false)
public static string GetTypeDisplayName(Type type, bool fullName = false, bool includeGenericParameterNames = true)
{
var builder = new StringBuilder();
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
return builder.ToString();
}
public static StringBuilder AppendTypeDisplayName(this StringBuilder builder, Type type, bool fullName = true,
bool includeGenericParameterNames = false)
{
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
return builder;
}
/// <summary>
/// Returns a name of given generic type without '`'.
/// </summary>
public static string GetTypeNameForGenericType(Type type)
{
if (!type.IsGenericType)
{
throw new ArgumentException("The given type should be generic", nameof(type));
}
var genericPartIndex = type.Name.IndexOf('`');
return (genericPartIndex >= 0) ? type.Name.Substring(0, genericPartIndex) : type.Name;
}
private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options)
{
if (type.IsGenericType)

View File

@ -35,4 +35,10 @@ public static class TestExceptions
firstFewItems = new List<T>();
throw new InvalidOperationException("Throwing!");
}
public static (string Key, List<T> Values) GetTuplesWithInnerException<T>((int First, string Second) myValue)
{
MethodThatThrows(0);
return ("key", new List<T>());
}
}

View File

@ -0,0 +1,5 @@
InvalidOperationException: Throwing!
at bool Spectre.Console.Tests.Data.TestExceptions.MethodThatThrows(int? number) in /xyz/Exceptions.cs:nn
at (string Key, List<T> Values) Spectre.Console.Tests.Data.TestExceptions.GetTuplesWithInnerException<T>((int First, string Second) myValue) in /xyz/Exceptions.cs:nn
at void Spectre.Console.Tests.Unit.ExceptionTests.<>c.<Should_Write_Exception_With_Tuple_Return>b__6_0() in /xyz/ExceptionTests.cs:nn
at Exception Spectre.Console.Tests.Unit.ExceptionTests.GetException(Action action) in /xyz/ExceptionTests.cs:nn

View File

@ -94,6 +94,21 @@ public sealed class ExceptionTests
return Verifier.Verify(result);
}
[Fact]
[Expectation("Tuple")]
public Task Should_Write_Exception_With_Tuple_Return()
{
// Given
var console = new TestConsole().Width(1024);
var dex = GetException(() => TestExceptions.GetTuplesWithInnerException<int>((0, "value")));
// When
var result = console.WriteNormalizedException(dex, ExceptionFormats.ShortenTypes);
// Then
return Verifier.Verify(result);
}
public static Exception GetException(Action action)
{
try