Skip to content

Commit

Permalink
1. Fixing a bug where when a type that implements IEnumerable<KeyValu…
Browse files Browse the repository at this point in the history
…ePair<,>> is rendered as a dictionary and its IEnumerator has multiple `Current` method implementation (explicit interface implementation + implicit), the selected `Current` property wasn't always the one of the `IEnumerable<KeyValuePair<,>> and it didn't return `KeyValuePair<,>` as it was expected, necessarily.

2. Adding the type-name in Circular References rendering
  • Loading branch information
MoaidHathot committed Dec 2, 2023
1 parent 21e326b commit 0f3e0a1
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 19 deletions.
29 changes: 17 additions & 12 deletions src/Dumpify.Playground/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Drawing;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using Color = System.Drawing.Color;

//DumpConfig.Default.Renderer = Renderers.Text;
Expand All @@ -27,6 +28,8 @@
#pragma warning disable CS0168
void TestSpecific()
{
//new Dictionary<string, int>() { ["1"] = 2, ["2"] = 2, ["3"] = 3 }.Dump();
Regex.Match("abc", "[a-z]").Dump();
//try
//{
// throw new Exception("Bla bla", new ArgumentNullException("paramName", "inner bla fla"));
Expand All @@ -37,21 +40,21 @@ void TestSpecific()
//}
//

new { Description = "You can manually specify labels to objects" }.Dump("Manual label");
// new { Description = "You can manually specify labels to objects" }.Dump("Manual label");

//Set auto-label globally for all dumps if a custom label wasn't provided
DumpConfig.Default.UseAutoLabels = true;
new { Description = "Or set labels automatically with auto-labels" }.Dump();
// DumpConfig.Default.UseAutoLabels = true;
// new { Description = "Or set labels automatically with auto-labels" }.Dump();

new { fa = "Hello", bla = "Word!" }.Dump("without separators");
new { fa = "Hello", bla = "Word!" }.Dump("with separators", tableConfig: new TableConfig { ShowRowSeparators = true });
DumpConfig.Default.UseAutoLabels = true;
DumpConfig.Default.TableConfig.ShowMemberTypes = true;
DumpConfig.Default.TableConfig.ShowRowSeparators = true;
var str2 = new { fa = "Hello", bla = "World!" }.Dump();
// new { fa = "Hello", bla = "Word!" }.Dump("without separators");
// new { fa = "Hello", bla = "Word!" }.Dump("with separators", tableConfig: new TableConfig { ShowRowSeparators = true });
// DumpConfig.Default.UseAutoLabels = true;
// DumpConfig.Default.TableConfig.ShowMemberTypes = true;
// DumpConfig.Default.TableConfig.ShowRowSeparators = true;
// var str2 = new { fa = "Hello", bla = "World!" }.Dump();


new { Name = "Dumpify", Description = "Dump any object to Console" }.Dump(tableConfig: new TableConfig { ShowRowSeparators = true, ShowMemberTypes = true });
// new { Name = "Dumpify", Description = "Dump any object to Console" }.Dump(tableConfig: new TableConfig { ShowRowSeparators = true, ShowMemberTypes = true });



Expand Down Expand Up @@ -103,8 +106,10 @@ void TestSpecific()
moaid.Spouse = haneeni;
haneeni.Spouse = moaid;

moaid.Dump();
moaid.Dump(tableConfig: new TableConfig { ShowTableHeaders = false, ShowRowSeparators = true, ShowMemberTypes = true }, typeNames: new TypeNamingConfig { ShowTypeNames = false });
// moaid.Dump("sdf");
// DumpConfig.Default.TableConfig.ShowTableHeaders = false;
// moaid.Dump("1112");
// moaid.Dump(tableConfig: new TableConfig { ShowTableHeaders = true, ShowRowSeparators = true, ShowMemberTypes = true }, typeNames: new TypeNamingConfig { ShowTypeNames = false });
//moaid.Dump();
// var family = new Family
// {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private static (bool isStdOut, bool isStdErr) GetSystemStdSettings(IDumpOutput o

try
{
isStdOut = output.TextWriter == System.Console.Out;
isStdOut = output.TextWriter == Console.Out;
}
catch
{
Expand All @@ -67,7 +67,7 @@ private static (bool isStdOut, bool isStdErr) GetSystemStdSettings(IDumpOutput o
try
{

isStdErr = output.TextWriter == System.Console.Error;
isStdErr = output.TextWriter == Console.Error;
}
catch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public override IRenderable RenderExceededDepth(object obj, IDescriptor? descrip
=> RenderSingleValue($"[Exceeded max depth {context.Config.MaxDepth}]", context, context.State.Colors.MetadataInfoColor);

protected override IRenderable RenderCircularDependency(object @object, IDescriptor? descriptor, RenderContext<SpectreRendererState> context)
=> RenderSingleValue($"[Circular Reference]", context, context.State.Colors.MetadataInfoColor);
=> RenderSingleValue($"[Circular Reference #{context.Config.TypeNameProvider.GetTypeName(@object.GetType())}]", context, context.State.Colors.MetadataInfoColor);

protected override IRenderable RenderNullDescriptor(object obj, RenderContext<SpectreRendererState> context)
=> RenderSingleValue($"[null descriptor] {obj}", context, context.State.Colors.MetadataErrorColor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,23 +77,33 @@ public IRenderable Render(IDescriptor descriptor, object obj, RenderContext base
return (true, list);
}

foreach (var i in descriptor.Type.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
var nameProvider = new TypeNameProvider(false, true, false, false);

var enumerableInterfaces = descriptor.Type.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
foreach (var i in enumerableInterfaces)
{
var genericArgument = i.GetGenericArguments()[0];

if (genericArgument.IsGenericType)
{
if (genericArgument.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
{
var method = i.GetMethod("GetEnumerator", BindingFlags.Instance | BindingFlags.Public)!;
var method = i.GetMethod("GetEnumerator", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
var enumerator = (IEnumerator)method.Invoke(obj, null)!;

var (canHandle, propertyProvider) = GetItemProvider(obj, genericArgument, i, enumerator);

if (canHandle is not true)
{
return (false, default);
}

var list = new List<(object? key, object? value)>();

while (enumerator.MoveNext())
{
var item = enumerator.Current;
var itemType = item.GetType();
var item = propertyProvider!.Invoke(enumerator);
var itemType = item!.GetType();

var keyProperty = itemType.GetProperty("Key", BindingFlags.Instance | BindingFlags.Public)!;
var key = keyProperty.GetValue(item);
Expand All @@ -111,4 +121,28 @@ public IRenderable Render(IDescriptor descriptor, object obj, RenderContext base

return (false, null);
}

private static (bool canHandle, Func<object, object>? currentValueProvider) GetItemProvider(object? obj, Type genericArgument, Type enumerableInterface, IEnumerator enumerator)
{
var currentProperty = enumerator.GetType().GetProperty("Current", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

//Notice how the if also checks for `currentProperty` nullability
if (currentProperty?.PropertyType.IsGenericType is true && currentProperty.PropertyType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
{
return (true, enumeratorSource => currentProperty.GetValue(enumeratorSource)!);
}

//Should we "fail" gracefully without special rendering and never knowing an issue has happened, or should we really fail and indicate via a rendering failure?
if (2 != genericArgument.GenericTypeArguments.Length)
{
return (false, default);
}

var nameProvider = new TypeNameProvider(false, true, false, false);

var currentPropertyExternalImplementationName = $"System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<{nameProvider.GetTypeName(genericArgument.GenericTypeArguments[0])},{genericArgument.GenericTypeArguments[1]}>>.Current";
currentProperty = enumerator.GetType().GetProperty(currentPropertyExternalImplementationName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);

return (currentProperty is not null, enumerableSource => currentProperty!.GetValue(enumerableSource)!);
}
}

0 comments on commit 0f3e0a1

Please sign in to comment.