From 0f3e0a1078187396527acd8faf74f0bba2f184a7 Mon Sep 17 00:00:00 2001 From: Moaid Hathot Date: Sat, 2 Dec 2023 05:15:40 +0200 Subject: [PATCH] 1. Fixing a bug where when a type that implements IEnumerable> 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> and it didn't return `KeyValuePair<,>` as it was expected, necessarily. 2. Adding the type-name in Circular References rendering --- src/Dumpify.Playground/Program.cs | 29 +++++++------ .../SpectreConsoleRenderedObject.cs | 4 +- .../SpectreConsoleRendererBase.cs | 2 +- .../DictionaryTypeRenderer.cs | 42 +++++++++++++++++-- 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/Dumpify.Playground/Program.cs b/src/Dumpify.Playground/Program.cs index 47fba6c..1088777 100644 --- a/src/Dumpify.Playground/Program.cs +++ b/src/Dumpify.Playground/Program.cs @@ -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; @@ -27,6 +28,8 @@ #pragma warning disable CS0168 void TestSpecific() { + //new Dictionary() { ["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")); @@ -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 }); @@ -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 // { diff --git a/src/Dumpify/Renderers/Spectre.Console/SpectreConsoleRenderedObject.cs b/src/Dumpify/Renderers/Spectre.Console/SpectreConsoleRenderedObject.cs index 1f12639..290b1e7 100644 --- a/src/Dumpify/Renderers/Spectre.Console/SpectreConsoleRenderedObject.cs +++ b/src/Dumpify/Renderers/Spectre.Console/SpectreConsoleRenderedObject.cs @@ -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 { @@ -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 { diff --git a/src/Dumpify/Renderers/Spectre.Console/SpectreConsoleRendererBase.cs b/src/Dumpify/Renderers/Spectre.Console/SpectreConsoleRendererBase.cs index 6491808..ddd5293 100644 --- a/src/Dumpify/Renderers/Spectre.Console/SpectreConsoleRendererBase.cs +++ b/src/Dumpify/Renderers/Spectre.Console/SpectreConsoleRendererBase.cs @@ -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 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 context) => RenderSingleValue($"[null descriptor] {obj}", context, context.State.Colors.MetadataErrorColor); diff --git a/src/Dumpify/Renderers/Spectre.Console/TableRenderer/CustomTypeRenderers/DictionaryTypeRenderer.cs b/src/Dumpify/Renderers/Spectre.Console/TableRenderer/CustomTypeRenderers/DictionaryTypeRenderer.cs index a40e667..9611c7d 100644 --- a/src/Dumpify/Renderers/Spectre.Console/TableRenderer/CustomTypeRenderers/DictionaryTypeRenderer.cs +++ b/src/Dumpify/Renderers/Spectre.Console/TableRenderer/CustomTypeRenderers/DictionaryTypeRenderer.cs @@ -77,7 +77,10 @@ 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]; @@ -85,15 +88,22 @@ public IRenderable Render(IDescriptor descriptor, object obj, RenderContext base { 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); @@ -111,4 +121,28 @@ public IRenderable Render(IDescriptor descriptor, object obj, RenderContext base return (false, null); } + + private static (bool canHandle, Func? 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>.Current"; + currentProperty = enumerator.GetType().GetProperty(currentPropertyExternalImplementationName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); + + return (currentProperty is not null, enumerableSource => currentProperty!.GetValue(enumerableSource)!); + } } \ No newline at end of file