fixed warnings

This commit is contained in:
timerix 2022-09-26 17:12:57 +06:00
parent 5f593123de
commit ffddfa21ec
17 changed files with 1092 additions and 1379 deletions

View File

@ -1,71 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27019.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A2FCCAAC-BE90-4F7E-B95F-A72D46DDD6B3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{59CA6310-4AA5-4093-95D4-472B94DC0CD4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ben.Demystifier", "src\Ben.Demystifier\Ben.Demystifier.csproj", "{5410A056-89AB-4912-BD1E-A63616AD91D0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ben.Demystifier.Test", "test\Ben.Demystifier.Test\Ben.Demystifier.Test.csproj", "{B9E150B0-AEEB-4D98-8BE1-92C1296699A2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{455921D3-DD54-4355-85CF-F4009DF2AB70}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StackTrace", "sample\StackTrace\StackTrace.csproj", "{E161FC12-53C2-47CD-A5FC-3684B86723A9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5937ACDF-0059-488E-9604-D84689C72933}"
ProjectSection(SolutionItems) = preProject
appveyor.yml = appveyor.yml
build.ps1 = build.ps1
Directory.Build.props = Directory.Build.props
README.md = README.md
version.json = version.json
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ben.Demystifier.Benchmarks", "test\Ben.Demystifier.Benchmarks\Ben.Demystifier.Benchmarks.csproj", "{EF5557DF-C48E-4999-846C-D99A92E86373}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpStackTrace", "sample\FSharpStackTrace\FSharpStackTrace.fsproj", "{D6B779D2-A678-47CC-A2F9-A312292EA7A2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5410A056-89AB-4912-BD1E-A63616AD91D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5410A056-89AB-4912-BD1E-A63616AD91D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5410A056-89AB-4912-BD1E-A63616AD91D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5410A056-89AB-4912-BD1E-A63616AD91D0}.Release|Any CPU.Build.0 = Release|Any CPU
{B9E150B0-AEEB-4D98-8BE1-92C1296699A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9E150B0-AEEB-4D98-8BE1-92C1296699A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9E150B0-AEEB-4D98-8BE1-92C1296699A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9E150B0-AEEB-4D98-8BE1-92C1296699A2}.Release|Any CPU.Build.0 = Release|Any CPU
{E161FC12-53C2-47CD-A5FC-3684B86723A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E161FC12-53C2-47CD-A5FC-3684B86723A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E161FC12-53C2-47CD-A5FC-3684B86723A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E161FC12-53C2-47CD-A5FC-3684B86723A9}.Release|Any CPU.Build.0 = Release|Any CPU
{EF5557DF-C48E-4999-846C-D99A92E86373}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{EF5557DF-C48E-4999-846C-D99A92E86373}.Debug|Any CPU.Build.0 = Release|Any CPU
{EF5557DF-C48E-4999-846C-D99A92E86373}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF5557DF-C48E-4999-846C-D99A92E86373}.Release|Any CPU.Build.0 = Release|Any CPU
{D6B779D2-A678-47CC-A2F9-A312292EA7A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D6B779D2-A678-47CC-A2F9-A312292EA7A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6B779D2-A678-47CC-A2F9-A312292EA7A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6B779D2-A678-47CC-A2F9-A312292EA7A2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5410A056-89AB-4912-BD1E-A63616AD91D0} = {A2FCCAAC-BE90-4F7E-B95F-A72D46DDD6B3}
{B9E150B0-AEEB-4D98-8BE1-92C1296699A2} = {59CA6310-4AA5-4093-95D4-472B94DC0CD4}
{E161FC12-53C2-47CD-A5FC-3684B86723A9} = {455921D3-DD54-4355-85CF-F4009DF2AB70}
{EF5557DF-C48E-4999-846C-D99A92E86373} = {59CA6310-4AA5-4093-95D4-472B94DC0CD4}
{D6B779D2-A678-47CC-A2F9-A312292EA7A2} = {455921D3-DD54-4355-85CF-F4009DF2AB70}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {841B7D5F-E810-4F94-A529-002C7E075216}
EndGlobalSection
EndGlobal

View File

@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<DebugType>embedded</DebugType> <DebugType>embedded</DebugType>
<PublishRepositoryUrl>true</PublishRepositoryUrl> <PublishRepositoryUrl>true</PublishRepositoryUrl>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild> <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<TargetFrameworks>netstandard2.1;netstandard2.0;net45;net6.0</TargetFrameworks> <TargetFramework>net6.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Reflection.Metadata" Version="5.0.0" Condition="'$(TargetFramework)' != 'net6.0'"/> <PackageReference Include="System.Reflection.Metadata" Version="5.0.0" Condition="'$(TargetFramework)' != 'net6.0'"/>
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" Condition="'$(TargetFramework)' != 'netstandard2.1'" /> <PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" Condition="'$(TargetFramework)' != 'netstandard2.1'"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,93 +1,84 @@
// Copyright (c) Ben A Adams. All rights reserved. // Copyright (c) Ben A Adams. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection; namespace Ben.Demystifier;
namespace Ben.Demystifier public class EnhancedStackFrame : StackFrame
{ {
public class EnhancedStackFrame : StackFrame private readonly int _colNumber;
private readonly string? _fileName;
private readonly int _lineNumber;
internal EnhancedStackFrame(StackFrame stackFrameBase, ResolvedMethod methodInfo, string? fileName, int lineNumber,
int colNumber)
: base(fileName, lineNumber, colNumber)
{ {
private readonly string? _fileName; StackFrameBase = stackFrameBase;
private readonly int _lineNumber; MethodInfo = methodInfo;
private readonly int _colNumber; _fileName = fileName;
_lineNumber = lineNumber;
public StackFrame StackFrame { get; } _colNumber = colNumber;
public bool IsRecursive
{
get => MethodInfo.RecurseCount > 0;
internal set => MethodInfo.RecurseCount++;
}
public ResolvedMethod MethodInfo { get; }
internal EnhancedStackFrame(StackFrame stackFrame, ResolvedMethod methodInfo, string? fileName, int lineNumber, int colNumber)
: base(fileName, lineNumber, colNumber)
{
StackFrame = stackFrame;
MethodInfo = methodInfo;
_fileName = fileName;
_lineNumber = lineNumber;
_colNumber = colNumber;
}
internal bool IsEquivalent(ResolvedMethod methodInfo, string? fileName, int lineNumber, int colNumber)
{
return _lineNumber == lineNumber &&
_colNumber == colNumber &&
_fileName == fileName &&
MethodInfo.IsSequentialEquivalent(methodInfo);
}
/// <summary>
/// Gets the column number in the file that contains the code that is executing.
/// This information is typically extracted from the debugging symbols for the executable.
/// </summary>
/// <returns>The file column number, or 0 (zero) if the file column number cannot be determined.</returns>
public override int GetFileColumnNumber() => _colNumber;
/// <summary>
/// Gets the line number in the file that contains the code that is executing.
/// This information is typically extracted from the debugging symbols for the executable.
/// </summary>
/// <returns>The file line number, or 0 (zero) if the file line number cannot be determined.</returns>
public override int GetFileLineNumber() => _lineNumber;
/// <summary>
/// Gets the file name that contains the code that is executing.
/// This information is typically extracted from the debugging symbols for the executable.
/// </summary>
/// <returns>The file name, or null if the file name cannot be determined.</returns>
public override string? GetFileName() => _fileName;
/// <summary>
/// Gets the offset from the start of the Microsoft intermediate language (MSIL)
/// code for the method that is executing. This offset might be an approximation
/// depending on whether or not the just-in-time (JIT) compiler is generating debugging
/// code. The generation of this debugging information is controlled by the System.Diagnostics.DebuggableAttribute.
/// </summary>
/// <returns>The offset from the start of the MSIL code for the method that is executing.</returns>
public override int GetILOffset() => StackFrame.GetILOffset();
/// <summary>
/// Gets the method in which the frame is executing.
/// </summary>
/// <returns>The method in which the frame is executing.</returns>
public override MethodBase? GetMethod() => StackFrame.GetMethod();
/// <summary>
/// Gets the offset from the start of the native just-in-time (JIT)-compiled code
/// for the method that is being executed. The generation of this debugging information
/// is controlled by the System.Diagnostics.DebuggableAttribute class.
/// </summary>
/// <returns>The offset from the start of the JIT-compiled code for the method that is being executed.</returns>
public override int GetNativeOffset() => StackFrame.GetNativeOffset();
/// <summary>
/// Builds a readable representation of the stack trace.
/// </summary>
/// <returns>A readable representation of the stack trace.</returns>
public override string ToString() => MethodInfo.ToString();
} }
private StackFrame StackFrameBase { get; }
public bool IsRecursive
{
get => MethodInfo.RecurseCount > 0;
internal set => MethodInfo.RecurseCount++;
}
public ResolvedMethod MethodInfo { get; }
internal bool IsEquivalent(ResolvedMethod methodInfo, string? fileName, int lineNumber, int colNumber) =>
_lineNumber == lineNumber &&
_colNumber == colNumber &&
_fileName == fileName &&
MethodInfo.IsSequentialEquivalent(methodInfo);
/// <summary>
/// Gets the column number in the file that contains the code that is executing.
/// This information is typically extracted from the debugging symbols for the executable.
/// </summary>
/// <returns>The file column number, or 0 (zero) if the file column number cannot be determined.</returns>
public override int GetFileColumnNumber() => _colNumber;
/// <summary>
/// Gets the line number in the file that contains the code that is executing.
/// This information is typically extracted from the debugging symbols for the executable.
/// </summary>
/// <returns>The file line number, or 0 (zero) if the file line number cannot be determined.</returns>
public override int GetFileLineNumber() => _lineNumber;
/// <summary>
/// Gets the file name that contains the code that is executing.
/// This information is typically extracted from the debugging symbols for the executable.
/// </summary>
/// <returns>The file name, or null if the file name cannot be determined.</returns>
public override string? GetFileName() => _fileName;
/// <summary>
/// Gets the offset from the start of the Microsoft intermediate language (MSIL)
/// code for the method that is executing. This offset might be an approximation
/// depending on whether or not the just-in-time (JIT) compiler is generating debugging
/// code. The generation of this debugging information is controlled by the System.Diagnostics.DebuggableAttribute.
/// </summary>
/// <returns>The offset from the start of the MSIL code for the method that is executing.</returns>
public override int GetILOffset() => StackFrameBase.GetILOffset();
/// <summary>Gets the method in which the frame is executing.</summary>
/// <returns>The method in which the frame is executing.</returns>
public override MethodBase? GetMethod() => StackFrameBase.GetMethod();
/// <summary>
/// Gets the offset from the start of the native just-in-time (JIT)-compiled code
/// for the method that is being executed. The generation of this debugging information
/// is controlled by the System.Diagnostics.DebuggableAttribute class.
/// </summary>
/// <returns>The offset from the start of the JIT-compiled code for the method that is being executed.</returns>
public override int GetNativeOffset() => StackFrameBase.GetNativeOffset();
/// <summary>Builds a readable representation of the stack trace.</summary>
/// <returns>A readable representation of the stack trace.</returns>
public override string ToString() => MethodInfo.ToString();
} }

View File

@ -3,28 +3,27 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
global using System.Collections;
global using System.Collections.Generic;
using Ben.Demystifier.Enumerable;
using Ben.Demystifier.Internal;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices; using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ben.Demystifier.Enumerable;
using Ben.Demystifier.Internal;
namespace Ben.Demystifier namespace Ben.Demystifier
{ {
public partial class EnhancedStackTrace public partial class EnhancedStackTrace
{ {
private static readonly Type? StackTraceHiddenAttributeType = Type.GetType("System.Diagnostics.StackTraceHiddenAttribute", false); private static readonly Type? StackTraceHiddenAttributeType =
private static readonly Type? AsyncIteratorStateMachineAttributeType = Type.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false); Type.GetType("System.Diagnostics.StackTraceHiddenAttribute", false);
private static readonly Type? AsyncIteratorStateMachineAttributeType =
Type.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false);
static EnhancedStackTrace() static EnhancedStackTrace()
{ {
if (AsyncIteratorStateMachineAttributeType != null) return; if (AsyncIteratorStateMachineAttributeType is not null) return;
Assembly mba; Assembly mba;
try try
@ -36,32 +35,15 @@ namespace Ben.Demystifier
return; return;
} }
AsyncIteratorStateMachineAttributeType = mba.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false); AsyncIteratorStateMachineAttributeType =
} mba.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false);
private static List<EnhancedStackFrame> GetFrames(Exception exception)
{
if (exception == null)
{
return new List<EnhancedStackFrame>();
}
var needFileInfo = true;
var stackTrace = new StackTrace(exception, needFileInfo);
return GetFrames(stackTrace);
} }
public static List<EnhancedStackFrame> GetFrames(StackTrace stackTrace) public static List<EnhancedStackFrame> GetFrames(StackTrace stackTrace)
{ {
var frames = new List<EnhancedStackFrame>(); var enhancedFrames = new List<EnhancedStackFrame>();
var stackFrames = stackTrace.GetFrames(); var stackFrames = stackTrace.GetFrames();
if (stackFrames == null)
{
return frames;
}
EnhancedStackFrame? lastFrame = null; EnhancedStackFrame? lastFrame = null;
PortablePdbReader? portablePdbReader = null; PortablePdbReader? portablePdbReader = null;
try try
@ -69,27 +51,22 @@ namespace Ben.Demystifier
for (var i = 0; i < stackFrames.Length; i++) for (var i = 0; i < stackFrames.Length; i++)
{ {
var frame = stackFrames[i]; var frame = stackFrames[i];
if (frame is null)
{
continue;
}
var method = frame.GetMethod(); var method = frame.GetMethod();
// Always show last stackFrame // Always show last stackFrame
if (method != null && !ShowInStackTrace(method) && i < stackFrames.Length - 1) if (method is not null && !ShowInStackTrace(method) && i < stackFrames.Length - 1)
{
continue; continue;
}
var fileName = frame.GetFileName(); var fileName = frame.GetFileName();
var row = frame.GetFileLineNumber(); var row = frame.GetFileLineNumber();
var column = frame.GetFileColumnNumber(); var column = frame.GetFileColumnNumber();
var ilOffset = frame.GetILOffset(); var ilOffset = frame.GetILOffset();
if (method != null && string.IsNullOrEmpty(fileName) && ilOffset >= 0) if (method is not null && string.IsNullOrEmpty(fileName) && ilOffset >= 0)
{ {
// .NET Framework and older versions of mono don't support portable PDBs // .NET Framework and older versions of mono don't support portable PDBs
// so we read it manually to get file name and line information // so we read it manually to get file name and line information
(portablePdbReader ??= new PortablePdbReader()).PopulateStackFrame(frame, method, frame.GetILOffset(), out fileName, out row, out column); (portablePdbReader ??= new PortablePdbReader()).PopulateStackFrame(frame, method,
frame.GetILOffset(), out fileName, out row, out column);
} }
if (method is null) if (method is null)
@ -106,7 +83,7 @@ namespace Ben.Demystifier
else else
{ {
var stackFrame = new EnhancedStackFrame(frame, resolvedMethod, fileName, row, column); var stackFrame = new EnhancedStackFrame(frame, resolvedMethod, fileName, row, column);
frames.Add(stackFrame); enhancedFrames.Add(stackFrame);
lastFrame = stackFrame; lastFrame = stackFrame;
} }
} }
@ -116,7 +93,7 @@ namespace Ben.Demystifier
portablePdbReader?.Dispose(); portablePdbReader?.Dispose();
} }
return frames; return enhancedFrames;
} }
public static ResolvedMethod GetMethodDisplayString(MethodBase originMethod) public static ResolvedMethod GetMethodDisplayString(MethodBase originMethod)
@ -161,7 +138,8 @@ namespace Ben.Demystifier
methodDisplayInfo.Name = methodName; methodDisplayInfo.Name = methodName;
if (method.Name.IndexOf("<") >= 0) if (method.Name.IndexOf("<") >= 0)
{ {
if (TryResolveGeneratedName(ref method, out type, out methodName, out subMethodName, out var kind, out var ordinal)) if (TryResolveGeneratedName(ref method, out type, out methodName, out subMethodName, out var kind,
out var ordinal))
{ {
methodName = method.Name; methodName = method.Name;
methodDisplayInfo.MethodBase = method; methodDisplayInfo.MethodBase = method;
@ -175,7 +153,7 @@ namespace Ben.Demystifier
methodDisplayInfo.IsLambda = (kind == GeneratedNameKind.LambdaMethod); methodDisplayInfo.IsLambda = (kind == GeneratedNameKind.LambdaMethod);
if (methodDisplayInfo.IsLambda && type != null) if (methodDisplayInfo.IsLambda && type is not null)
{ {
if (methodName == ".cctor") if (methodName == ".cctor")
{ {
@ -185,7 +163,8 @@ namespace Ben.Demystifier
} }
else else
{ {
var fields = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); var fields =
type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var field in fields) foreach (var field in fields)
{ {
var value = field.GetValue(field); var value = field.GetValue(field);
@ -212,33 +191,29 @@ namespace Ben.Demystifier
} }
// ResolveStateMachineMethod may have set declaringType to null // ResolveStateMachineMethod may have set declaringType to null
if (type != null) if (type is not null)
{ {
methodDisplayInfo.DeclaringType = type; methodDisplayInfo.DeclaringType = type;
} }
if (method is MethodInfo mi) if (method is MethodInfo mi)
{ {
var returnParameter = mi.ReturnParameter; if (mi.ReturnParameter is not null)
if (returnParameter != null)
{
methodDisplayInfo.ReturnParameter = GetParameter(mi.ReturnParameter); methodDisplayInfo.ReturnParameter = GetParameter(mi.ReturnParameter);
} else if (mi.ReturnType is not null)
else if (mi.ReturnType != null)
{
methodDisplayInfo.ReturnParameter = new ResolvedParameter(mi.ReturnType) methodDisplayInfo.ReturnParameter = new ResolvedParameter(mi.ReturnType)
{ {
Prefix = "", Prefix = "",
Name = "", Name = ""
}; };
}
} }
if (method.IsGenericMethod) if (method.IsGenericMethod)
{ {
var genericArguments = method.GetGenericArguments(); var genericArguments = method.GetGenericArguments();
var genericArgumentsString = string.Join(", ", genericArguments var genericArgumentsString = string.Join(", ", genericArguments
.Select(arg => TypeNameHelper.GetTypeDisplayName(arg, fullName: false, includeGenericParameterNames: true))); .Select(arg =>
TypeNameHelper.GetTypeDisplayName(arg, fullName: false, includeGenericParameterNames: true)));
methodDisplayInfo.GenericArguments += "<" + genericArgumentsString + ">"; methodDisplayInfo.GenericArguments += "<" + genericArgumentsString + ">";
methodDisplayInfo.ResolvedGenericArguments = genericArguments; methodDisplayInfo.ResolvedGenericArguments = genericArguments;
} }
@ -260,7 +235,7 @@ namespace Ben.Demystifier
{ {
methodDisplayInfo.SubMethodBase = null; methodDisplayInfo.SubMethodBase = null;
} }
else if (methodDisplayInfo.SubMethodBase != null) else if (methodDisplayInfo.SubMethodBase is not null)
{ {
parameters = methodDisplayInfo.SubMethodBase.GetParameters(); parameters = methodDisplayInfo.SubMethodBase.GetParameters();
if (parameters.Length > 0) if (parameters.Length > 0)
@ -295,7 +270,8 @@ namespace Ben.Demystifier
return false; return false;
} }
private static bool TryResolveGeneratedName(ref MethodBase method, out Type? type, out string methodName, out string? subMethodName, out GeneratedNameKind kind, out int? ordinal) private static bool TryResolveGeneratedName(ref MethodBase method, out Type? type, out string methodName,
out string? subMethodName, out GeneratedNameKind kind, out int? ordinal)
{ {
kind = GeneratedNameKind.None; kind = GeneratedNameKind.None;
type = method.DeclaringType; type = method.DeclaringType;
@ -315,28 +291,29 @@ namespace Ben.Demystifier
switch (kind) switch (kind)
{ {
case GeneratedNameKind.LocalFunction: case GeneratedNameKind.LocalFunction:
{ {
var localNameStart = generatedName.IndexOf((char)kind, closeBracketOffset + 1); var localNameStart = generatedName.IndexOf((char)kind, closeBracketOffset + 1);
if (localNameStart < 0) break; if (localNameStart < 0) break;
localNameStart += 3; localNameStart += 3;
if (localNameStart < generatedName.Length) if (localNameStart < generatedName.Length)
{
var localNameEnd = generatedName.IndexOf("|", localNameStart);
if (localNameEnd > 0)
{ {
var localNameEnd = generatedName.IndexOf("|", localNameStart); subMethodName = generatedName.Substring(localNameStart, localNameEnd - localNameStart);
if (localNameEnd > 0)
{
subMethodName = generatedName.Substring(localNameStart, localNameEnd - localNameStart);
}
} }
break;
} }
break;
}
case GeneratedNameKind.LambdaMethod: case GeneratedNameKind.LambdaMethod:
subMethodName = ""; subMethodName = "";
break; break;
} }
var dt = method.DeclaringType; var dt = method.DeclaringType;
if (dt == null) if (dt is null)
{ {
return false; return false;
} }
@ -345,30 +322,42 @@ namespace Ben.Demystifier
var matchName = methodName; var matchName = methodName;
var candidateMethods = dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); var candidateMethods =
if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) return true; dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static |
BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName);
if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal))
return true;
var candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); var candidateConstructors =
if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) return true; dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static |
BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName);
if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal))
return true;
const int MaxResolveDepth = 10; for (var i = 0; i < 10; i++)
for (var i = 0; i < MaxResolveDepth; i++)
{ {
dt = dt.DeclaringType; dt = dt.DeclaringType;
if (dt == null) if (dt is null)
{
return false; return false;
}
candidateMethods = dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); candidateMethods =
if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) return true; dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static |
BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName);
if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal))
return true;
candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); candidateConstructors =
if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) return true; dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static |
BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(m => m.Name == matchName);
if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal))
return true;
if (methodName == ".cctor") if (methodName == ".cctor")
{ {
candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); candidateConstructors =
dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static |
BindingFlags.DeclaredOnly).Where(m => m.Name == matchName);
foreach (var cctor in candidateConstructors) foreach (var cctor in candidateConstructors)
{ {
method = cctor; method = cctor;
@ -381,7 +370,8 @@ namespace Ben.Demystifier
return false; return false;
} }
private static bool TryResolveSourceMethod(IEnumerable<MethodBase> candidateMethods, GeneratedNameKind kind, string? matchHint, ref MethodBase method, ref Type? type, out int? ordinal) private static bool TryResolveSourceMethod(IEnumerable<MethodBase> candidateMethods, GeneratedNameKind kind,
string? matchHint, ref MethodBase method, ref Type? type, out int? ordinal)
{ {
ordinal = null; ordinal = null;
foreach (var candidateMethod in candidateMethods) foreach (var candidateMethod in candidateMethods)
@ -390,6 +380,7 @@ namespace Ben.Demystifier
{ {
continue; continue;
} }
if (kind == GeneratedNameKind.LambdaMethod) if (kind == GeneratedNameKind.LambdaMethod)
{ {
foreach (var v in EnumerableIList.Create(methodBody.LocalVariables)) foreach (var v in EnumerableIList.Create(methodBody.LocalVariables))
@ -397,8 +388,8 @@ namespace Ben.Demystifier
if (v.LocalType == type) if (v.LocalType == type)
{ {
GetOrdinal(method, ref ordinal); GetOrdinal(method, ref ordinal);
} }
method = candidateMethod; method = candidateMethod;
type = method.DeclaringType; type = method.DeclaringType;
return true; return true;
@ -412,12 +403,13 @@ namespace Ben.Demystifier
{ {
continue; continue;
} }
var reader = new ILReader(rawIl); var reader = new ILReader(rawIl);
while (reader.Read(candidateMethod)) while (reader.Read(candidateMethod))
{ {
if (reader.Operand is MethodBase mb) if (reader.Operand is MethodBase mb)
{ {
if (method == mb || matchHint != null && method.Name.Contains(matchHint)) if (method == mb || matchHint is not null && method.Name.Contains(matchHint))
{ {
if (kind == GeneratedNameKind.LambdaMethod) if (kind == GeneratedNameKind.LambdaMethod)
{ {
@ -460,10 +452,12 @@ namespace Ben.Demystifier
ordinal = foundOrdinal; ordinal = foundOrdinal;
var methods = method.DeclaringType?.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly); var methods = method.DeclaringType?.GetMethods(BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance |
BindingFlags.DeclaredOnly);
var count = 0; var count = 0;
if (methods != null) if (methods is not null)
{ {
var startName = method.Name.Substring(0, lamdaStart); var startName = method.Name.Substring(0, lamdaStart);
foreach (var m in methods) foreach (var m in methods)
@ -501,6 +495,7 @@ namespace Ben.Demystifier
return methodName.Substring(start, end - start); return methodName.Substring(start, end - start);
} }
return null; return null;
} }
@ -517,13 +512,8 @@ namespace Ben.Demystifier
{ {
openBracketOffset = -1; openBracketOffset = -1;
if (name.StartsWith("CS$<", StringComparison.Ordinal)) if (name.StartsWith("CS$<", StringComparison.Ordinal))
{
openBracketOffset = 3; openBracketOffset = 3;
} else if (name.StartsWith("<", StringComparison.Ordinal)) openBracketOffset = 0;
else if (name.StartsWith("<", StringComparison.Ordinal))
{
openBracketOffset = 0;
}
if (openBracketOffset >= 0) if (openBracketOffset >= 0)
{ {
@ -549,50 +539,32 @@ namespace Ben.Demystifier
private static int IndexOfBalancedParenthesis(string str, int openingOffset, char closing) private static int IndexOfBalancedParenthesis(string str, int openingOffset, char closing)
{ {
var opening = str[openingOffset]; var opening = str[openingOffset];
var depth = 1; var depth = 1;
for (var i = openingOffset + 1; i < str.Length; i++) for (var i = openingOffset + 1; i < str.Length; i++)
{ {
var c = str[i]; var c = str[i];
if (c == opening) if (c == opening)
{
depth++; depth++;
}
else if (c == closing) else if (c == closing)
{ {
depth--; depth--;
if (depth == 0) if (depth == 0)
{
return i; return i;
}
} }
} }
return -1; return -1;
} }
private static string GetPrefix(ParameterInfo parameter) private static string GetPrefix(ParameterInfo parameter)
{ {
if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute), false)) if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute), false))
{
return "params"; return "params";
}
if (parameter.IsOut) if (parameter.IsOut)
{
return "out"; return "out";
}
if (parameter.IsIn) if (parameter.IsIn)
{
return "in"; return "in";
}
if (parameter.ParameterType.IsByRef) if (parameter.ParameterType.IsByRef)
{
return "ref"; return "ref";
}
return string.Empty; return string.Empty;
} }
@ -605,20 +577,16 @@ namespace Ben.Demystifier
{ {
var customAttribs = parameter.GetCustomAttributes(inherit: false); var customAttribs = parameter.GetCustomAttributes(inherit: false);
var tupleNameAttribute = customAttribs.OfType<Attribute>().FirstOrDefault(a => a.IsTupleElementNameAttribute()); var tupleNameAttribute = customAttribs.OfType<Attribute>()
.FirstOrDefault(a => a.IsTupleElementNameAttribute());
var tupleNames = tupleNameAttribute?.GetTransformerNames(); var tupleNames = tupleNameAttribute?.GetTransformerNames();
if (tupleNames?.Count > 0) if (tupleNames?.Count > 0)
{
return GetValueTupleParameter(tupleNames, prefix, parameter.Name, parameterType); return GetValueTupleParameter(tupleNames, prefix, parameter.Name, parameterType);
}
} }
if (parameterType.IsByRef && parameterType.GetElementType() is {} elementType) if (parameterType.IsByRef && parameterType.GetElementType() is { } elementType) parameterType = elementType;
{
parameterType = elementType;
}
return new ResolvedParameter(parameterType) return new ResolvedParameter(parameterType)
{ {
@ -628,48 +596,16 @@ namespace Ben.Demystifier
}; };
} }
private static ResolvedParameter GetValueTupleParameter(IList<string> tupleNames, string prefix, string? name, Type parameterType) private static ResolvedParameter GetValueTupleParameter(IList<string> tupleNames, string prefix, string? name,
Type parameterType)
{ {
return new ValueTupleResolvedParameter(parameterType, tupleNames) return new ValueTupleResolvedParameter(parameterType, tupleNames)
{ {
Prefix = prefix, Prefix = prefix,
Name = name, Name = name
}; };
} }
private static string GetValueTupleParameterName(IList<string> tupleNames, Type parameterType)
{
var sb = new StringBuilder();
sb.Append("(");
var args = parameterType.GetGenericArguments();
for (var i = 0; i < args.Length; i++)
{
if (i > 0)
{
sb.Append(", ");
}
sb.Append(TypeNameHelper.GetTypeDisplayName(args[i], fullName: false, includeGenericParameterNames: true));
if (i >= tupleNames.Count)
{
continue;
}
var argName = tupleNames[i];
if (argName == null)
{
continue;
}
sb.Append(" ");
sb.Append(argName);
}
sb.Append(")");
return sb.ToString();
}
private static bool ShowInStackTrace(MethodBase method) private static bool ShowInStackTrace(MethodBase method)
{ {
// Since .NET 5: // Since .NET 5:
@ -684,7 +620,7 @@ namespace Ben.Demystifier
} }
// Since .NET Core 2: // Since .NET Core 2:
if (StackTraceHiddenAttributeType != null) if (StackTraceHiddenAttributeType is not null)
{ {
// Don't show any methods marked with the StackTraceHiddenAttribute // Don't show any methods marked with the StackTraceHiddenAttribute
// https://github.com/dotnet/coreclr/pull/14652 // https://github.com/dotnet/coreclr/pull/14652
@ -696,13 +632,13 @@ namespace Ben.Demystifier
var type = method.DeclaringType; var type = method.DeclaringType;
if (type == null) if (type is null)
{ {
return true; return true;
} }
// Since .NET Core 2: // Since .NET Core 2:
if (StackTraceHiddenAttributeType != null) if (StackTraceHiddenAttributeType is not null)
{ {
// Don't show any methods marked with the StackTraceHiddenAttribute // Don't show any methods marked with the StackTraceHiddenAttribute
// https://github.com/dotnet/coreclr/pull/14652 // https://github.com/dotnet/coreclr/pull/14652
@ -716,18 +652,24 @@ namespace Ben.Demystifier
{ {
return false; return false;
} }
if (type == typeof(ValueTask<>) && method.Name == "get_Result") if (type == typeof(ValueTask<>) && method.Name == "get_Result")
{ {
return false; return false;
} }
if (method.Name.StartsWith("System.Threading.Tasks.Sources.IValueTaskSource") && method.Name.EndsWith(".GetResult"))
if (method.Name.StartsWith("System.Threading.Tasks.Sources.IValueTaskSource") &&
method.Name.EndsWith(".GetResult"))
{ {
return false; return false;
} }
if (method.Name == "GetResult" && method.DeclaringType?.FullName == "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore`1")
if (method.Name == "GetResult" && method.DeclaringType?.FullName ==
"System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore`1")
{ {
return false; return false;
} }
if (type == typeof(Task) || type.DeclaringType == typeof(Task)) if (type == typeof(Task) || type.DeclaringType == typeof(Task))
{ {
if (method.Name.Contains(".cctor")) if (method.Name.Contains(".cctor"))
@ -747,12 +689,11 @@ namespace Ben.Demystifier
return false; return false;
} }
} }
if (type == typeof(ExecutionContext)) if (type == typeof(ExecutionContext))
{ {
if (method.Name.Contains(".cctor")) if (method.Name.Contains(".cctor"))
{
return false; return false;
}
switch (method.Name) switch (method.Name)
{ {
@ -821,9 +762,7 @@ namespace Ben.Demystifier
private static bool IsStackTraceHidden(MemberInfo memberInfo) private static bool IsStackTraceHidden(MemberInfo memberInfo)
{ {
if (StackTraceHiddenAttributeType is not null && !memberInfo.Module.Assembly.ReflectionOnly) if (StackTraceHiddenAttributeType is not null && !memberInfo.Module.Assembly.ReflectionOnly)
{
return memberInfo.GetCustomAttributes(StackTraceHiddenAttributeType, false).Length != 0; return memberInfo.GetCustomAttributes(StackTraceHiddenAttributeType, false).Length != 0;
}
EnumerableIList<CustomAttributeData> attributes; EnumerableIList<CustomAttributeData> attributes;
try try
@ -835,8 +774,9 @@ namespace Ben.Demystifier
return false; return false;
} }
foreach (var attribute in attributes) for (var i = 0; i < attributes.Count; i++)
{ {
var attribute = attributes[i];
// reflection-only attribute, match on name // reflection-only attribute, match on name
if (attribute.AttributeType.FullName == StackTraceHiddenAttributeType?.FullName) if (attribute.AttributeType.FullName == StackTraceHiddenAttributeType?.FullName)
{ {
@ -855,6 +795,7 @@ namespace Ben.Demystifier
declaringType = null!; declaringType = null!;
return false; return false;
} }
declaringType = method.DeclaringType; declaringType = method.DeclaringType;
var parentType = declaringType.DeclaringType; var parentType = declaringType.DeclaringType;
@ -863,11 +804,9 @@ namespace Ben.Demystifier
return false; return false;
} }
static MethodInfo[] GetDeclaredMethods(Type type) => var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static |
type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly); BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (methods is null)
var methods = GetDeclaredMethods(parentType);
if (methods == null)
{ {
return false; return false;
} }
@ -888,8 +827,8 @@ namespace Ben.Demystifier
{ {
foundAttribute = true; foundAttribute = true;
foundIteratorAttribute |= asma is IteratorStateMachineAttribute foundIteratorAttribute |= asma is IteratorStateMachineAttribute
|| AsyncIteratorStateMachineAttributeType != null || AsyncIteratorStateMachineAttributeType is not null
&& AsyncIteratorStateMachineAttributeType.IsInstanceOfType(asma); && AsyncIteratorStateMachineAttributeType.IsInstanceOfType(asma);
} }
} }
@ -903,6 +842,7 @@ namespace Ben.Demystifier
return foundIteratorAttribute; return foundIteratorAttribute;
} }
} }
return false; return false;
} }
@ -917,7 +857,9 @@ namespace Ben.Demystifier
LambdaMethod = 'b', LambdaMethod = 'b',
LambdaDisplayClass = 'c', LambdaDisplayClass = 'c',
StateMachineType = 'd', StateMachineType = 'd',
LocalFunction = 'g', // note collision with Deprecated_InitializerLocal, however this one is only used for method names
LocalFunction =
'g', // note collision with Deprecated_InitializerLocal, however this one is only used for method names
// Used by EnC: // Used by EnC:
AwaiterField = 'u', AwaiterField = 'u',

View File

@ -1,141 +1,89 @@
// Copyright (c) Ben A Adams. All rights reserved. // Copyright (c) Ben A Adams. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections; namespace Ben.Demystifier;
using System.Collections.Generic;
using Ben.Demystifier.Enumerable;
using System.IO;
using System.Text;
namespace Ben.Demystifier public partial class EnhancedStackTrace : StackTrace, IEnumerable<EnhancedStackFrame>
{ {
public partial class EnhancedStackTrace : StackTrace, IEnumerable<EnhancedStackFrame> private readonly List<EnhancedStackFrame> _frames;
/// <summary>
/// Initializes a new instance of the System.Diagnostics.StackTrace class using the
/// provided exception object
/// </summary>
public EnhancedStackTrace(Exception e)
{ {
public static EnhancedStackTrace Current() => new EnhancedStackTrace(new StackTrace(1 /* skip this one frame */, true)); _frames = GetFrames(new StackTrace(e, true));
}
private readonly List<EnhancedStackFrame> _frames; public EnhancedStackTrace(StackTrace stackTrace)
{
_frames = GetFrames(stackTrace);
}
// Summary: /// <returns>The number of frames in the stack trace.</returns>
// Initializes a new instance of the System.Diagnostics.StackTrace class using the public override int FrameCount => _frames.Count;
// provided exception object.
// IEnumerator<EnhancedStackFrame> IEnumerable<EnhancedStackFrame>.GetEnumerator() => _frames.GetEnumerator();
// Parameters:
// e: IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator();
// The exception object from which to construct the stack trace.
// public static EnhancedStackTrace Current()
// Exceptions: => new EnhancedStackTrace(new StackTrace(1 /* skip this one frame */, true));
// T:System.ArgumentNullException:
// The parameter e is null. /// <param name="index">The index of the stack frame requested.</param>
public EnhancedStackTrace(Exception e) /// <returns>The specified stack frame.</returns>
public override StackFrame GetFrame(int index) => _frames[index];
/// <returns>a copy of all stack frames in the current stack trace. </returns>
public override StackFrame[] GetFrames() => _frames.ToArray();
/// <summary>
/// Builds a readable representation of the stack trace.
/// </summary>
public override string ToString()
{
if (_frames.Count == 0) return "";
var sb = new StringBuilder();
AppendTo(sb);
return sb.ToString();
}
public void AppendTo(StringBuilder sb)
{
var count = _frames.Count;
for (var i = 0; i < count; i++)
{ {
if (e == null) if (i > 0) sb.Append(Environment.NewLine);
var frame = _frames[i];
sb.Append(" at ");
frame.MethodInfo.AppendTo(sb);
var fileName = frame.GetFileName();
if (!string.IsNullOrEmpty(fileName))
{ {
throw new ArgumentNullException(nameof(e)); sb.Append(" in ");
sb.Append(TryGetFullPath(fileName));
} }
_frames = GetFrames(e); var lineNo = frame.GetFileLineNumber();
} if (lineNo != 0)
public EnhancedStackTrace(StackTrace stackTrace)
{
if (stackTrace == null)
{ {
throw new ArgumentNullException(nameof(stackTrace)); sb.Append(":line ");
sb.Append(lineNo);
} }
_frames = GetFrames(stackTrace);
}
/// <summary>
/// Gets the number of frames in the stack trace.
/// </summary>
/// <returns>The number of frames in the stack trace.</returns>
public override int FrameCount => _frames.Count;
/// <summary>
/// Gets the specified stack frame.
/// </summary>
/// <param name="index">The index of the stack frame requested.</param>
/// <returns>The specified stack frame.</returns>
public override StackFrame GetFrame(int index) => _frames[index];
/// <summary>
/// Returns a copy of all stack frames in the current stack trace.
/// </summary>
/// <returns>
/// An array of type System.Diagnostics.StackFrame representing the function calls
/// in the stack trace.
/// </returns>
public override StackFrame[] GetFrames() => _frames.ToArray();
/// <summary>
/// Builds a readable representation of the stack trace.
/// </summary>
/// <returns>A readable representation of the stack trace.</returns>
public override string ToString()
{
if (_frames == null || _frames.Count == 0) return "";
var sb = new StringBuilder();
Append(sb);
return sb.ToString();
}
internal void Append(StringBuilder sb)
{
var frames = _frames;
var count = frames.Count;
for (var i = 0; i < count; i++)
{
if (i > 0)
{
sb.Append(Environment.NewLine);
}
var frame = frames[i];
sb.Append(" at ");
frame.MethodInfo.Append(sb);
if (frame.GetFileName() is {} fileName
// IsNullOrEmpty alone wasn't enough to disable the null warning
&& !string.IsNullOrEmpty(fileName))
{
sb.Append(" in ");
sb.Append(TryGetFullPath(fileName));
}
var lineNo = frame.GetFileLineNumber();
if (lineNo != 0)
{
sb.Append(":line ");
sb.Append(lineNo);
}
}
}
EnumerableIList<EnhancedStackFrame> GetEnumerator() => EnumerableIList.Create(_frames);
IEnumerator<EnhancedStackFrame> IEnumerable<EnhancedStackFrame>.GetEnumerator() => _frames.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator();
/// <summary>
/// Tries to convert a given <paramref name="filePath"/> to a full path.
/// Returns original value if the conversion isn't possible or a given path is relative.
/// </summary>
public static string TryGetFullPath(string filePath)
{
if (Uri.TryCreate(filePath, UriKind.Absolute, out var uri) && uri.IsFile)
{
return Uri.UnescapeDataString(uri.AbsolutePath);
}
return filePath;
} }
} }
/// <summary>
/// Tries to convert a given <paramref name="filePath" /> to a full path.
/// Returns original value if the conversion isn't possible or a given path is relative.
/// </summary>
public static string TryGetFullPath(string filePath)
{
if (Uri.TryCreate(filePath, UriKind.Absolute, out var uri) && uri.IsFile)
return Uri.UnescapeDataString(uri.AbsolutePath);
return filePath;
}
} }

View File

@ -1,65 +1,77 @@
// Copyright (c) Ben A Adams. All rights reserved. // Copyright (c) Ben A Adams. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Ben.Demystifier.Enumerable namespace Ben.Demystifier.Enumerable;
public static class EnumerableIList
{ {
public static class EnumerableIList public static EnumerableIList<T> Create<T>(IList<T> list)
{ {
public static EnumerableIList<T> Create<T>(IList<T> list) => new EnumerableIList<T>(list); return new(list);
} }
}
public struct EnumerableIList<T> : IEnumerableIList<T>, IList<T>
{ public struct EnumerableIList<T> : IEnumerableIList<T>, IList<T>
private readonly IList<T> _list; {
private readonly IList<T> _list;
public EnumerableIList(IList<T> list) => _list = list;
public EnumerableIList(IList<T> list) => _list = list;
public EnumeratorIList<T> GetEnumerator() => new EnumeratorIList<T>(_list);
public EnumeratorIList<T> GetEnumerator() => new(_list);
public static implicit operator EnumerableIList<T>(List<T> list) => new EnumerableIList<T>(list);
public static implicit operator EnumerableIList<T>(List<T> list) => new(list);
public static implicit operator EnumerableIList<T>(T[] array) => new EnumerableIList<T>(array);
public static implicit operator EnumerableIList<T>(T[] array) => new(array);
public static EnumerableIList<T> Empty = default;
public static EnumerableIList<T> Empty = default;
// IList pass through
// IList pass through
/// <inheritdoc />
public T this[int index] { get => _list[index]; set => _list[index] = value; } /// <inheritdoc />
public T this[int index]
/// <inheritdoc /> {
public int Count => _list.Count; get => _list[index];
set => _list[index] = value;
/// <inheritdoc /> }
public bool IsReadOnly => _list.IsReadOnly;
/// <inheritdoc />
/// <inheritdoc /> public int Count => _list.Count;
public void Add(T item) => _list.Add(item);
/// <inheritdoc />
/// <inheritdoc /> public bool IsReadOnly => _list.IsReadOnly;
public void Clear() => _list.Clear();
/// <inheritdoc />
/// <inheritdoc /> public void Add(T item) => _list.Add(item);
public bool Contains(T item) => _list.Contains(item);
/// <inheritdoc />
/// <inheritdoc /> public void Clear() => _list.Clear();
public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
/// <inheritdoc />
/// <inheritdoc /> public bool Contains(T item) => _list.Contains(item);
public int IndexOf(T item) => _list.IndexOf(item);
/// <inheritdoc />
/// <inheritdoc /> public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
public void Insert(int index, T item) => _list.Insert(index, item);
/// <inheritdoc />
/// <inheritdoc /> public int IndexOf(T item) => _list.IndexOf(item);
public bool Remove(T item) => _list.Remove(item);
/// <inheritdoc />
/// <inheritdoc /> public void Insert(int index, T item) => _list.Insert(index, item);
public void RemoveAt(int index) => _list.RemoveAt(index);
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public bool Remove(T item) => _list.Remove(item);
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator(); /// <inheritdoc />
public void RemoveAt(int index) => _list.RemoveAt(index);
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return GetEnumerator();
} }
} }

View File

@ -1,30 +1,35 @@
// Copyright (c) Ben A Adams. All rights reserved. // Copyright (c) Ben A Adams. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Ben.Demystifier.Enumerable namespace Ben.Demystifier.Enumerable;
public struct EnumeratorIList<T> : IEnumerator<T>
{ {
public struct EnumeratorIList<T> : IEnumerator<T> private readonly IList<T> _list;
private int _index;
public EnumeratorIList(IList<T> list)
{ {
private readonly IList<T> _list; _index = -1;
private int _index; _list = list;
}
public EnumeratorIList(IList<T> list) public T Current => _list[_index];
{
_index = -1;
_list = list;
}
public T Current => _list[_index]; public bool MoveNext()
{
_index++;
public bool MoveNext() return _index < (_list?.Count ?? 0);
{ }
_index++;
return _index < (_list?.Count ?? 0); public void Dispose()
} { }
public void Dispose() { } object? IEnumerator.Current => Current;
object? IEnumerator.Current => Current;
public void Reset() => _index = -1; public void Reset()
{
_index = -1;
} }
} }

View File

@ -1,10 +1,9 @@
// Copyright (c) Ben A Adams. All rights reserved. // Copyright (c) Ben A Adams. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Ben.Demystifier.Enumerable namespace Ben.Demystifier.Enumerable;
internal interface IEnumerableIList<T> : IEnumerable<T>
{ {
interface IEnumerableIList<T> : IEnumerable<T> new EnumeratorIList<T> GetEnumerator();
{
new EnumeratorIList<T> GetEnumerator();
}
} }

View File

@ -1,66 +1,66 @@
// Copyright (c) Ben A Adams. All rights reserved. // Copyright (c) Ben A Adams. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
global using System.Diagnostics;
global using System; global using System;
global using System.Text;
global using System.Collections;
global using System.Collections.Generic;
global using System.Diagnostics;
global using System.Reflection;
global using System.Linq;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Collections.Generic;
using Ben.Demystifier.Enumerable; using Ben.Demystifier.Enumerable;
using System.Reflection;
using System.Text;
namespace Ben.Demystifier namespace Ben.Demystifier;
public static class ExceptionExtensions
{ {
public static class ExceptionExtensions private static readonly FieldInfo? StackTraceString =
typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
private static void SetStackTracesString(this Exception exception, string value)
{ {
private static readonly FieldInfo? stackTraceString = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic); StackTraceString?.SetValue(exception, value);
}
private static void SetStackTracesString(this Exception exception, string value) /// <summary>
=> stackTraceString?.SetValue(exception, value); /// Demystifies the given <paramref name="exception" /> and tracks the original stack traces for the whole exception
/// tree.
/// <summary> /// </summary>
/// Demystifies the given <paramref name="exception"/> and tracks the original stack traces for the whole exception tree. public static T Demystify<T>(this T exception) where T : Exception
/// </summary> {
public static T Demystify<T>(this T exception) where T : Exception try
{ {
try var stackTrace = new EnhancedStackTrace(exception);
{
var stackTrace = new EnhancedStackTrace(exception);
if (stackTrace.FrameCount > 0) if (stackTrace.FrameCount > 0) exception.SetStackTracesString(stackTrace.ToString());
{
exception.SetStackTracesString(stackTrace.ToString());
}
if (exception is AggregateException aggEx) if (exception is AggregateException aggEx)
{ foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions)) ex.Demystify();
{
ex.Demystify();
}
}
exception.InnerException?.Demystify(); exception.InnerException?.Demystify();
} }
catch catch
{ {
// Processing exceptions shouldn't throw exceptions; if it fails // Processing exceptions shouldn't throw exceptions; if it fails
}
return exception;
} }
/// <summary> return exception;
/// Gets demystified string representation of the <paramref name="exception"/>. }
/// </summary>
/// <remarks> /// <summary>
/// <see cref="Demystify{T}"/> method mutates the exception instance that can cause /// Gets demystified string representation of the <paramref name="exception" />.
/// issues if a system relies on the stack trace be in the specific form. /// </summary>
/// Unlike <see cref="Demystify{T}"/> this method is pure. It calls <see cref="Demystify{T}"/> first, /// <remarks>
/// computes a demystified string representation and then restores the original state of the exception back. /// <see cref="Demystify{T}" /> method mutates the exception instance that can cause
/// </remarks> /// issues if a system relies on the stack trace be in the specific form.
[Pure] /// Unlike <see cref="Demystify{T}" /> this method is pure. It calls <see cref="Demystify{T}" /> first,
public static string ToStringDemystified(this Exception exception) /// computes a demystified string representation and then restores the original state of the exception back.
=> new StringBuilder().AppendDemystified(exception).ToString(); /// </remarks>
[Pure]
public static string ToStringDemystified(this Exception exception)
{
return new StringBuilder().AppendDemystified(exception).ToString();
} }
} }

View File

@ -1,145 +1,145 @@
using System.Reflection;
using System.Reflection.Emit; using System.Reflection.Emit;
namespace Ben.Demystifier.Internal namespace Ben.Demystifier.Internal;
internal class ILReader
{ {
internal class ILReader private static readonly OpCode[] singleByteOpCode;
private static readonly OpCode[] doubleByteOpCode;
private readonly byte[] _cil;
private int ptr;
static ILReader()
{ {
private static OpCode[] singleByteOpCode; singleByteOpCode = new OpCode[225];
private static OpCode[] doubleByteOpCode; doubleByteOpCode = new OpCode[31];
private readonly byte[] _cil; var fields = GetOpCodeFields();
private int ptr;
for (var i = 0; i < fields.Length; i++)
public ILReader(byte[] cil) => _cil = cil;
public OpCode OpCode { get; private set; }
public int MetadataToken { get; private set; }
public MemberInfo? Operand { get; private set; }
public bool Read(MethodBase methodInfo)
{ {
if (ptr < _cil.Length) var code = (OpCode)fields[i].GetValue(null)!;
{ if (code.OpCodeType == OpCodeType.Nternal)
OpCode = ReadOpCode(); continue;
Operand = ReadOperand(OpCode, methodInfo);
return true;
}
return false;
}
OpCode ReadOpCode() if (code.Size == 1)
{ singleByteOpCode[code.Value] = code;
var instruction = ReadByte();
if (instruction < 254)
return singleByteOpCode[instruction];
else else
return doubleByteOpCode[ReadByte()]; doubleByteOpCode[code.Value & 0xff] = code;
}
}
public ILReader(byte[] cil)
{
_cil = cil;
}
public OpCode OpCode { get; private set; }
public int MetadataToken { get; private set; }
public MemberInfo? Operand { get; private set; }
public bool Read(MethodBase methodInfo)
{
if (ptr < _cil.Length)
{
OpCode = ReadOpCode();
Operand = ReadOperand(OpCode, methodInfo);
return true;
} }
MemberInfo? ReadOperand(OpCode code, MethodBase methodInfo) return false;
}
private OpCode ReadOpCode()
{
var instruction = ReadByte();
if (instruction < 254)
return singleByteOpCode[instruction];
return doubleByteOpCode[ReadByte()];
}
private MemberInfo? ReadOperand(OpCode code, MethodBase methodInfo)
{
MetadataToken = 0;
int inlineLength;
switch (code.OperandType)
{ {
MetadataToken = 0; case OperandType.InlineMethod:
int inlineLength; MetadataToken = ReadInt();
switch (code.OperandType) Type[]? methodArgs = null;
{ if (methodInfo.GetType() != typeof(ConstructorInfo) &&
case OperandType.InlineMethod: !methodInfo.GetType().IsSubclassOf(typeof(ConstructorInfo)))
MetadataToken = ReadInt(); methodArgs = methodInfo.GetGenericArguments();
Type[]? methodArgs = null; Type[]? typeArgs = null;
if (methodInfo.GetType() != typeof(ConstructorInfo) && !methodInfo.GetType().IsSubclassOf(typeof(ConstructorInfo))) if (methodInfo.DeclaringType is not null) typeArgs = methodInfo.DeclaringType.GetGenericArguments();
{ try
methodArgs = methodInfo.GetGenericArguments(); {
} return methodInfo.Module.ResolveMember(MetadataToken, typeArgs, methodArgs);
Type[]? typeArgs = null; }
if (methodInfo.DeclaringType != null) catch
{ {
typeArgs = methodInfo.DeclaringType.GetGenericArguments(); // Can return System.ArgumentException : Token xxx is not a valid MemberInfo token in the scope of module xxx.dll
}
try
{
return methodInfo.Module.ResolveMember(MetadataToken, typeArgs, methodArgs);
}
catch
{
// Can return System.ArgumentException : Token xxx is not a valid MemberInfo token in the scope of module xxx.dll
return null;
}
case OperandType.InlineNone:
inlineLength = 0;
break;
case OperandType.ShortInlineBrTarget:
case OperandType.ShortInlineVar:
case OperandType.ShortInlineI:
inlineLength = 1;
break;
case OperandType.InlineVar:
inlineLength = 2;
break;
case OperandType.InlineBrTarget:
case OperandType.InlineField:
case OperandType.InlineI:
case OperandType.InlineString:
case OperandType.InlineSig:
case OperandType.InlineSwitch:
case OperandType.InlineTok:
case OperandType.InlineType:
case OperandType.ShortInlineR:
inlineLength = 4;
break;
case OperandType.InlineI8:
case OperandType.InlineR:
inlineLength = 8;
break;
default:
return null; return null;
} }
for (var i = 0; i < inlineLength; i++) case OperandType.InlineNone:
{ inlineLength = 0;
ReadByte(); break;
}
return null; case OperandType.ShortInlineBrTarget:
case OperandType.ShortInlineVar:
case OperandType.ShortInlineI:
inlineLength = 1;
break;
case OperandType.InlineVar:
inlineLength = 2;
break;
case OperandType.InlineBrTarget:
case OperandType.InlineField:
case OperandType.InlineI:
case OperandType.InlineString:
case OperandType.InlineSig:
case OperandType.InlineSwitch:
case OperandType.InlineTok:
case OperandType.InlineType:
case OperandType.ShortInlineR:
inlineLength = 4;
break;
case OperandType.InlineI8:
case OperandType.InlineR:
inlineLength = 8;
break;
default:
return null;
} }
byte ReadByte() => _cil[ptr++]; for (var i = 0; i < inlineLength; i++) ReadByte();
int ReadInt() return null;
{ }
var b1 = ReadByte();
var b2 = ReadByte();
var b3 = ReadByte();
var b4 = ReadByte();
return b1 | b2 << 8 | b3 << 16 | b4 << 24;
}
static ILReader() private byte ReadByte()
{ {
singleByteOpCode = new OpCode[225]; return _cil[ptr++];
doubleByteOpCode = new OpCode[31]; }
var fields = GetOpCodeFields(); private int ReadInt()
{
var b1 = ReadByte();
var b2 = ReadByte();
var b3 = ReadByte();
var b4 = ReadByte();
return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
}
for (var i = 0; i < fields.Length; i++) private static FieldInfo[] GetOpCodeFields()
{ {
var code = (OpCode)fields[i].GetValue(null)!; return typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static);
if (code.OpCodeType == OpCodeType.Nternal)
continue;
if (code.Size == 1)
singleByteOpCode[code.Value] = code;
else
doubleByteOpCode[code.Value & 0xff] = code;
}
}
static FieldInfo[] GetOpCodeFields() => typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static);
} }
} }

View File

@ -1,136 +1,113 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection;
using System.Reflection.Metadata; using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335; using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
namespace Ben.Demystifier.Internal namespace Ben.Demystifier.Internal;
// Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.StackTrace.Sources/StackFrame/PortablePdbReader.cs
internal class PortablePdbReader : IDisposable
{ {
// Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.StackTrace.Sources/StackFrame/PortablePdbReader.cs private readonly Dictionary<string, MetadataReaderProvider> _cache = new(StringComparer.Ordinal);
internal class PortablePdbReader : IDisposable
public void Dispose()
{ {
private readonly Dictionary<string, MetadataReaderProvider> _cache = foreach (var entry in _cache) entry.Value.Dispose();
new Dictionary<string, MetadataReaderProvider>(StringComparer.Ordinal);
public void PopulateStackFrame(StackFrame frameInfo, MethodBase method, int IlOffset, out string fileName, out int row, out int column) _cache.Clear();
}
public void PopulateStackFrame(StackFrame frameInfo, MethodBase method, int IlOffset, out string fileName,
out int row, out int column)
{
fileName = "";
row = 0;
column = 0;
if (method.Module.Assembly.IsDynamic) return;
var metadataReader = GetMetadataReader(method.Module.Assembly.Location);
if (metadataReader is null) return;
var methodToken = MetadataTokens.Handle(method.MetadataToken);
Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition);
var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle();
if (!handle.IsNil)
{ {
fileName = ""; var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle);
row = 0; var sequencePoints = methodDebugInfo.GetSequencePoints();
column = 0; SequencePoint? bestPointSoFar = null;
if (method.Module.Assembly.IsDynamic) foreach (var point in sequencePoints)
{ {
return; if (point.Offset > IlOffset) break;
if (point.StartLine != SequencePoint.HiddenLine) bestPointSoFar = point;
} }
var metadataReader = GetMetadataReader(method.Module.Assembly.Location); if (bestPointSoFar.HasValue)
if (metadataReader == null)
{ {
return; row = bestPointSoFar.Value.StartLine;
column = bestPointSoFar.Value.StartColumn;
fileName = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name);
} }
var methodToken = MetadataTokens.Handle(method.MetadataToken);
Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition);
var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle();
if (!handle.IsNil)
{
var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle);
var sequencePoints = methodDebugInfo.GetSequencePoints();
SequencePoint? bestPointSoFar = null;
foreach (var point in sequencePoints)
{
if (point.Offset > IlOffset)
{
break;
}
if (point.StartLine != SequencePoint.HiddenLine)
{
bestPointSoFar = point;
}
}
if (bestPointSoFar.HasValue)
{
row = bestPointSoFar.Value.StartLine;
column = bestPointSoFar.Value.StartColumn;
fileName = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name);
}
}
}
private MetadataReader? GetMetadataReader(string assemblyPath)
{
if (!_cache.TryGetValue(assemblyPath, out var provider) && provider is not null)
{
var pdbPath = GetPdbPath(assemblyPath);
if (!string.IsNullOrEmpty(pdbPath) && File.Exists(pdbPath) && IsPortable(pdbPath!))
{
var pdbStream = File.OpenRead(pdbPath);
provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
}
_cache[assemblyPath] = provider;
}
return provider?.GetMetadataReader();
}
private static string? GetPdbPath(string assemblyPath)
{
if (string.IsNullOrEmpty(assemblyPath))
{
return null;
}
if (File.Exists(assemblyPath))
{
var peStream = File.OpenRead(assemblyPath);
using var peReader = new PEReader(peStream);
foreach (var entry in peReader.ReadDebugDirectory())
{
if (entry.Type == DebugDirectoryEntryType.CodeView)
{
var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
var peDirectory = Path.GetDirectoryName(assemblyPath);
return peDirectory is null
? null
: Path.Combine(peDirectory, Path.GetFileName(codeViewData.Path));
}
}
}
return null;
}
private static bool IsPortable(string pdbPath)
{
using var pdbStream = File.OpenRead(pdbPath);
return pdbStream.ReadByte() == 'B' &&
pdbStream.ReadByte() == 'S' &&
pdbStream.ReadByte() == 'J' &&
pdbStream.ReadByte() == 'B';
}
public void Dispose()
{
foreach (var entry in _cache)
{
entry.Value?.Dispose();
}
_cache.Clear();
} }
} }
private MetadataReader? GetMetadataReader(string assemblyPath)
{
if (!_cache.TryGetValue(assemblyPath, out var provider) && provider is not null)
{
var pdbPath = GetPdbPath(assemblyPath);
if (!string.IsNullOrEmpty(pdbPath) && File.Exists(pdbPath) && IsPortable(pdbPath!))
{
var pdbStream = File.OpenRead(pdbPath);
provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
}
_cache[assemblyPath] = provider;
}
return provider?.GetMetadataReader();
}
private static string? GetPdbPath(string assemblyPath)
{
if (string.IsNullOrEmpty(assemblyPath)) return null;
if (File.Exists(assemblyPath))
{
var peStream = File.OpenRead(assemblyPath);
using var peReader = new PEReader(peStream);
foreach (var entry in peReader.ReadDebugDirectory())
if (entry.Type == DebugDirectoryEntryType.CodeView)
{
var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
var peDirectory = Path.GetDirectoryName(assemblyPath);
return peDirectory is null
? null
: Path.Combine(peDirectory, Path.GetFileName(codeViewData.Path));
}
}
return null;
}
private static bool IsPortable(string pdbPath)
{
using var pdbStream = File.OpenRead(pdbPath);
return pdbStream.ReadByte() == 'B' &&
pdbStream.ReadByte() == 'S' &&
pdbStream.ReadByte() == 'J' &&
pdbStream.ReadByte() == 'B';
}
} }

View File

@ -1,62 +1,60 @@
// Copyright (c) Ben A Adams. All rights reserved. // Copyright (c) Ben A Adams. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Reflection;
using System.Threading; using System.Threading;
namespace Ben.Demystifier.Internal namespace Ben.Demystifier.Internal;
/// <summary>
/// A helper class that contains utilities methods for dealing with reflection.
/// </summary>
public static class ReflectionHelper
{ {
private static PropertyInfo? transformerNamesLazyPropertyInfo;
/// <summary> /// <summary>
/// A helper class that contains utilities methods for dealing with reflection. /// Returns true if the <paramref name="type" /> is a value tuple type.
/// </summary> /// </summary>
public static class ReflectionHelper public static bool IsValueTuple(this Type type)
{ {
private static PropertyInfo? transformerNamesLazyPropertyInfo; return type.Namespace == "System" && type.Name.Contains("ValueTuple`");
}
/// <summary> /// <summary>
/// Returns true if the <paramref name="type"/> is a value tuple type. /// Returns true if the given <paramref name="attribute" /> is of type <code>TupleElementNameAttribute</code>.
/// </summary> /// </summary>
public static bool IsValueTuple(this Type type) /// <remarks>
{ /// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection and not checks statically
return type.Namespace == "System" && type.Name.Contains("ValueTuple`"); /// that
} /// the given <paramref name="attribute" /> is <code>TupleElementNameAttribute</code>.
/// </remarks>
public static bool IsTupleElementNameAttribute(this Attribute attribute)
{
var attributeType = attribute.GetType();
return attributeType.Namespace == "System.Runtime.CompilerServices" &&
attributeType.Name == "TupleElementNamesAttribute";
}
/// <summary> /// <summary>
/// Returns true if the given <paramref name="attribute"/> is of type <code>TupleElementNameAttribute</code>. /// Returns 'TransformNames' property value from a given <paramref name="attribute" />.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection and not checks statically that /// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection
/// the given <paramref name="attribute"/> is <code>TupleElementNameAttribute</code>. /// instead of casting the attribute to a specific type.
/// </remarks> /// </remarks>
public static bool IsTupleElementNameAttribute(this Attribute attribute) public static IList<string>? GetTransformerNames(this Attribute attribute)
{ {
var attributeType = attribute.GetType(); Debug.Assert(attribute.IsTupleElementNameAttribute());
return attributeType.Namespace == "System.Runtime.CompilerServices" &&
attributeType.Name == "TupleElementNamesAttribute";
}
/// <summary> var propertyInfo = GetTransformNamesPropertyInfo(attribute.GetType());
/// Returns 'TransformNames' property value from a given <paramref name="attribute"/>. return propertyInfo.GetValue(attribute) as IList<string>;
/// </summary> }
/// <remarks>
/// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection
/// instead of casting the attribute to a specific type.
/// </remarks>
public static IList<string>? GetTransformerNames(this Attribute attribute)
{
Debug.Assert(attribute.IsTupleElementNameAttribute());
var propertyInfo = GetTransformNamesPropertyInfo(attribute.GetType()); private static PropertyInfo GetTransformNamesPropertyInfo(Type attributeType)
return propertyInfo?.GetValue(attribute) as IList<string>; {
}
private static PropertyInfo? GetTransformNamesPropertyInfo(Type attributeType)
{
#pragma warning disable 8634 #pragma warning disable 8634
return LazyInitializer.EnsureInitialized(ref transformerNamesLazyPropertyInfo, return LazyInitializer.EnsureInitialized(ref transformerNamesLazyPropertyInfo,
#pragma warning restore 8634 #pragma warning restore 8634
() => attributeType.GetProperty("TransformNames", BindingFlags.Instance | BindingFlags.Public)!); () => attributeType.GetProperty("TransformNames", BindingFlags.Instance | BindingFlags.Public)!);
}
} }
} }

View File

@ -2,171 +2,142 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Ben.Demystifier.Enumerable; using Ben.Demystifier.Enumerable;
using System.Reflection;
using System.Text;
namespace Ben.Demystifier namespace Ben.Demystifier;
public class ResolvedMethod
{ {
public class ResolvedMethod public MethodBase? MethodBase { get; set; }
public Type? DeclaringType { get; set; }
public bool IsAsync { get; set; }
public bool IsLambda { get; set; }
public ResolvedParameter? ReturnParameter { get; set; }
public string? Name { get; set; }
public int? Ordinal { get; set; }
public string? GenericArguments { get; set; }
public Type[]? ResolvedGenericArguments { get; set; }
public MethodBase? SubMethodBase { get; set; }
public string? SubMethod { get; set; }
public EnumerableIList<ResolvedParameter> Parameters { get; set; }
public EnumerableIList<ResolvedParameter> SubMethodParameters { get; set; }
public int RecurseCount { get; internal set; }
internal bool IsSequentialEquivalent(ResolvedMethod obj)
{ {
public MethodBase? MethodBase { get; set; } return
IsAsync == obj.IsAsync &&
DeclaringType == obj.DeclaringType &&
Name == obj.Name &&
IsLambda == obj.IsLambda &&
Ordinal == obj.Ordinal &&
GenericArguments == obj.GenericArguments &&
SubMethod == obj.SubMethod;
}
public Type? DeclaringType { get; set; } public override string ToString() => AppendTo(new StringBuilder()).ToString();
public bool IsAsync { get; set; } public StringBuilder AppendTo(StringBuilder builder, bool fullName = true)
{
if (IsAsync) builder.Append("async ");
public bool IsLambda { get; set; } if (ReturnParameter is not null)
public ResolvedParameter? ReturnParameter { get; set; }
public string? Name { get; set; }
public int? Ordinal { get; set; }
public string? GenericArguments { get; set; }
public Type[]? ResolvedGenericArguments { get; set; }
public MethodBase? SubMethodBase { get; set; }
public string? SubMethod { get; set; }
public EnumerableIList<ResolvedParameter> Parameters { get; set; }
public EnumerableIList<ResolvedParameter> SubMethodParameters { get; set; }
public int RecurseCount { get; internal set; }
internal bool IsSequentialEquivalent(ResolvedMethod obj)
{ {
return ReturnParameter.Append(builder);
IsAsync == obj.IsAsync && builder.Append(' ');
DeclaringType == obj.DeclaringType &&
Name == obj.Name &&
IsLambda == obj.IsLambda &&
Ordinal == obj.Ordinal &&
GenericArguments == obj.GenericArguments &&
SubMethod == obj.SubMethod;
} }
public override string ToString() => Append(new StringBuilder()).ToString(); if (DeclaringType is not null)
public StringBuilder Append(StringBuilder builder)
=> Append(builder, true);
public StringBuilder Append(StringBuilder builder, bool fullName)
{ {
if (IsAsync) if (Name == ".ctor")
{ {
builder.Append("async "); if (string.IsNullOrEmpty(SubMethod) && !IsLambda)
builder.Append("new ");
AppendDeclaringTypeName(builder, fullName);
} }
else if (Name == ".cctor")
if (ReturnParameter != null)
{ {
ReturnParameter.Append(builder); builder.Append("static ");
builder.Append(" "); AppendDeclaringTypeName(builder, fullName);
}
if (DeclaringType != null)
{
if (Name == ".ctor")
{
if (string.IsNullOrEmpty(SubMethod) && !IsLambda)
builder.Append("new ");
AppendDeclaringTypeName(builder, fullName);
}
else if (Name == ".cctor")
{
builder.Append("static ");
AppendDeclaringTypeName(builder, fullName);
}
else
{
AppendDeclaringTypeName(builder, fullName)
.Append(".")
.Append(Name);
}
} }
else else
{ {
builder.Append(Name); AppendDeclaringTypeName(builder, fullName)
.Append('.')
.Append(Name);
} }
builder.Append(GenericArguments); }
else builder.Append(Name);
builder.Append("("); builder.Append(GenericArguments);
if (MethodBase != null)
builder.Append('(');
if (MethodBase is not null)
{
var isFirst = true;
foreach (var param in Parameters)
{
if (isFirst)
isFirst = false;
else builder.Append(", ");
param.Append(builder);
}
}
else builder.Append('?');
builder.Append(')');
if (!string.IsNullOrEmpty(SubMethod) || IsLambda)
{
builder.Append('+');
builder.Append(SubMethod);
builder.Append('(');
if (SubMethodBase is not null)
{ {
var isFirst = true; var isFirst = true;
foreach(var param in Parameters) foreach (var param in SubMethodParameters)
{ {
if (isFirst) if (isFirst)
{
isFirst = false; isFirst = false;
} else builder.Append(", ");
else
{
builder.Append(", ");
}
param.Append(builder); param.Append(builder);
} }
} }
else else builder.Append('?');
{
builder.Append("?");
}
builder.Append(")");
if (!string.IsNullOrEmpty(SubMethod) || IsLambda) builder.Append(')');
if (IsLambda)
{ {
builder.Append("+"); builder.Append(" => { }");
builder.Append(SubMethod);
builder.Append("(");
if (SubMethodBase != null)
{
var isFirst = true;
foreach (var param in SubMethodParameters)
{
if (isFirst)
{
isFirst = false;
}
else
{
builder.Append(", ");
}
param.Append(builder);
}
}
else
{
builder.Append("?");
}
builder.Append(")");
if (IsLambda)
{
builder.Append(" => { }");
if (Ordinal.HasValue) if (Ordinal.HasValue)
{ {
builder.Append(" ["); builder.Append(" [");
builder.Append(Ordinal); builder.Append(Ordinal);
builder.Append("]"); builder.Append(']');
}
} }
} }
if (RecurseCount > 0)
{
builder.Append($" x {RecurseCount + 1:0}");
}
return builder;
} }
private StringBuilder AppendDeclaringTypeName(StringBuilder builder, bool fullName = true) if (RecurseCount > 0) builder.Append($" x {RecurseCount + 1:0}");
{
return DeclaringType != null ? builder.AppendTypeDisplayName(DeclaringType, fullName: fullName, includeGenericParameterNames: true) : builder; return builder;
} }
private StringBuilder AppendDeclaringTypeName(StringBuilder builder, bool fullName = true)
{
return DeclaringType is not null ? builder.AppendTypeDisplayName(DeclaringType, fullName, true) : builder;
} }
} }

View File

@ -1,59 +1,49 @@
// Copyright (c) Ben A Adams. All rights reserved. // Copyright (c) Ben A Adams. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text; namespace Ben.Demystifier;
namespace Ben.Demystifier public class ResolvedParameter
{ {
public class ResolvedParameter public ResolvedParameter(Type resolvedType)
{ {
public string? Name { get; set; } ResolvedType = resolvedType;
}
public Type ResolvedType { get; set; } public string? Name { get; set; }
public string? Prefix { get; set; } public Type ResolvedType { get; set; }
public bool IsDynamicType { get; set; }
public ResolvedParameter(Type resolvedType) => ResolvedType = resolvedType; public string? Prefix { get; set; }
public bool IsDynamicType { get; set; }
public override string ToString() => Append(new StringBuilder()).ToString(); public override string ToString()
{
public StringBuilder Append(StringBuilder sb) return Append(new StringBuilder()).ToString();
{ }
if (ResolvedType.Assembly.ManifestModule.Name == "FSharp.Core.dll" && ResolvedType.Name == "Unit")
return sb;
if (!string.IsNullOrEmpty(Prefix))
{
sb.Append(Prefix)
.Append(" ");
}
if (IsDynamicType)
{
sb.Append("dynamic");
}
else if (ResolvedType != null)
{
AppendTypeName(sb);
}
else
{
sb.Append("?");
}
if (!string.IsNullOrEmpty(Name))
{
sb.Append(" ")
.Append(Name);
}
public StringBuilder Append(StringBuilder sb)
{
if (ResolvedType.Assembly.ManifestModule.Name == "FSharp.Core.dll" && ResolvedType.Name == "Unit")
return sb; return sb;
}
protected virtual void AppendTypeName(StringBuilder sb) if (!string.IsNullOrEmpty(Prefix))
{ sb.Append(Prefix)
sb.AppendTypeDisplayName(ResolvedType, fullName: false, includeGenericParameterNames: true); .Append(' ');
}
if (IsDynamicType)
sb.Append("dynamic");
else AppendTypeName(sb);
if (!string.IsNullOrEmpty(Name))
sb.Append(' ')
.Append(Name);
return sb;
}
protected virtual void AppendTypeName(StringBuilder sb)
{
sb.AppendTypeDisplayName(ResolvedType, false, true);
} }
} }

View File

@ -2,55 +2,42 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Ben.Demystifier.Enumerable; using Ben.Demystifier.Enumerable;
using System.Text;
namespace Ben.Demystifier namespace Ben.Demystifier;
public static class StringBuilderExtentions
{ {
public static class StringBuilderExtentions public static StringBuilder AppendDemystified(this StringBuilder builder, Exception exception)
{ {
public static StringBuilder AppendDemystified(this StringBuilder builder, Exception exception) try
{ {
try var stackTrace = new EnhancedStackTrace(exception);
{
var stackTrace = new EnhancedStackTrace(exception);
builder.Append(exception.GetType()); builder.Append(exception.GetType());
if (!string.IsNullOrEmpty(exception.Message)) if (!string.IsNullOrEmpty(exception.Message)) builder.Append(": ").Append(exception.Message);
{ builder.Append(Environment.NewLine);
builder.Append(": ").Append(exception.Message);
}
builder.Append(Environment.NewLine);
if (stackTrace.FrameCount > 0) if (stackTrace.FrameCount > 0) stackTrace.AppendTo(builder);
{
stackTrace.Append(builder);
}
if (exception is AggregateException aggEx) if (exception is AggregateException aggEx)
{ foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions)) builder.AppendInnerException(ex);
{
builder.AppendInnerException(ex);
}
}
if (exception.InnerException != null) if (exception.InnerException is not null) builder.AppendInnerException(exception.InnerException);
{ }
builder.AppendInnerException(exception.InnerException); catch
} {
} // Processing exceptions shouldn't throw exceptions; if it fails
catch
{
// Processing exceptions shouldn't throw exceptions; if it fails
}
return builder;
} }
private static void AppendInnerException(this StringBuilder builder, Exception exception) return builder;
=> builder.Append(" ---> ") }
.AppendDemystified(exception)
.AppendLine() private static void AppendInnerException(this StringBuilder builder, Exception exception)
.Append(" --- End of inner exception stack trace ---"); {
builder.Append(" ---> ")
.AppendDemystified(exception)
.AppendLine()
.Append(" --- End of inner exception stack trace ---");
} }
} }

View File

@ -1,218 +1,192 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic; namespace Ben.Demystifier;
using System.Text;
namespace Ben.Demystifier // Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs
public static class TypeNameHelper
{ {
// Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs public static readonly Dictionary<Type, string> BuiltInTypeNames = new()
public static class TypeNameHelper
{ {
public static readonly Dictionary<Type, string> BuiltInTypeNames = new Dictionary<Type, string> { typeof(void), "void" },
{ typeof(bool), "bool" },
{ typeof(byte), "byte" },
{ typeof(char), "char" },
{ typeof(decimal), "decimal" },
{ typeof(double), "double" },
{ typeof(float), "float" },
{ typeof(int), "int" },
{ typeof(long), "long" },
{ typeof(object), "object" },
{ typeof(sbyte), "sbyte" },
{ typeof(short), "short" },
{ typeof(string), "string" },
{ typeof(uint), "uint" },
{ typeof(ulong), "ulong" },
{ typeof(ushort), "ushort" }
};
public static readonly Dictionary<string, string> FSharpTypeNames = new()
{
{ "Unit", "void" },
{ "FSharpOption", "Option" },
{ "FSharpAsync", "Async" },
{ "FSharpOption`1", "Option" },
{ "FSharpAsync`1", "Async" }
};
/// <summary>
/// Pretty print a type name.
/// </summary>
/// <param name="type">The <see cref="Type" />.</param>
/// <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)
{
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)
{ {
{ typeof(void), "void" }, var underlyingType = Nullable.GetUnderlyingType(type);
{ typeof(bool), "bool" }, if (underlyingType is not null)
{ typeof(byte), "byte" },
{ typeof(char), "char" },
{ typeof(decimal), "decimal" },
{ typeof(double), "double" },
{ typeof(float), "float" },
{ typeof(int), "int" },
{ typeof(long), "long" },
{ typeof(object), "object" },
{ typeof(sbyte), "sbyte" },
{ typeof(short), "short" },
{ typeof(string), "string" },
{ typeof(uint), "uint" },
{ typeof(ulong), "ulong" },
{ typeof(ushort), "ushort" }
};
public static readonly Dictionary<string, string> FSharpTypeNames = new Dictionary<string, string>
{
{ "Unit", "void" },
{ "FSharpOption", "Option" },
{ "FSharpAsync", "Async" },
{ "FSharpOption`1", "Option" },
{ "FSharpAsync`1", "Async" }
};
/// <summary>
/// Pretty print a type name.
/// </summary>
/// <param name="type">The <see cref="Type"/>.</param>
/// <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)
{
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)); ProcessType(builder, underlyingType, options);
} builder.Append('?');
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)
{
var underlyingType = Nullable.GetUnderlyingType(type);
if (underlyingType != null)
{
ProcessType(builder, underlyingType, options);
builder.Append('?');
}
else
{
var genericArguments = type.GetGenericArguments();
ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options);
}
}
else if (type.IsArray)
{
ProcessArrayType(builder, type, options);
}
else if (BuiltInTypeNames.TryGetValue(type, out var builtInName))
{
builder.Append(builtInName);
}
else if (type.Namespace == nameof(System))
{
builder.Append(type.Name);
}
else if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll"
&& FSharpTypeNames.TryGetValue(type.Name, out builtInName))
{
builder.Append(builtInName);
}
else if (type.IsGenericParameter)
{
if (options.IncludeGenericParameterNames)
{
builder.Append(type.Name);
}
} }
else else
{ {
builder.Append(options.FullName ? type.FullName ?? type.Name : type.Name); var genericArguments = type.GetGenericArguments();
ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options);
} }
} }
else if (type.IsArray)
private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options)
{ {
var innerType = type; ProcessArrayType(builder, type, options);
while (innerType.IsArray)
{
if (innerType.GetElementType() is { } inner)
{
innerType = inner;
}
}
ProcessType(builder, innerType, options);
while (type.IsArray)
{
builder.Append('[');
builder.Append(',', type.GetArrayRank() - 1);
builder.Append(']');
if (type.GetElementType() is not { } elementType)
{
break;
}
type = elementType;
}
} }
else if (BuiltInTypeNames.TryGetValue(type, out var builtInName))
private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, DisplayNameOptions options)
{ {
var offset = 0; builder.Append(builtInName);
if (type.IsNested && type.DeclaringType is not null)
{
offset = type.DeclaringType.GetGenericArguments().Length;
}
if (options.FullName)
{
if (type.IsNested && type.DeclaringType is not null)
{
ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options);
builder.Append('+');
}
else if (!string.IsNullOrEmpty(type.Namespace))
{
builder.Append(type.Namespace);
builder.Append('.');
}
}
var genericPartIndex = type.Name.IndexOf('`');
if (genericPartIndex <= 0)
{
builder.Append(type.Name);
return;
}
if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll"
&& FSharpTypeNames.TryGetValue(type.Name, out var builtInName))
{
builder.Append(builtInName);
}
else
{
builder.Append(type.Name, 0, genericPartIndex);
}
builder.Append('<');
for (var i = offset; i < length; i++)
{
ProcessType(builder, genericArguments[i], options);
if (i + 1 == length)
{
continue;
}
builder.Append(',');
if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter)
{
builder.Append(' ');
}
}
builder.Append('>');
} }
else if (type.Namespace == nameof(System))
private struct DisplayNameOptions
{ {
public DisplayNameOptions(bool fullName, bool includeGenericParameterNames) builder.Append(type.Name);
{ }
FullName = fullName; else if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll"
IncludeGenericParameterNames = includeGenericParameterNames; && FSharpTypeNames.TryGetValue(type.Name, out builtInName))
} {
builder.Append(builtInName);
public bool FullName { get; } }
else if (type.IsGenericParameter)
public bool IncludeGenericParameterNames { get; } {
if (options.IncludeGenericParameterNames) builder.Append(type.Name);
}
else
{
builder.Append(options.FullName ? type.FullName ?? type.Name : type.Name);
} }
} }
private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options)
{
var innerType = type;
while (innerType.IsArray)
if (innerType.GetElementType() is { } inner)
innerType = inner;
ProcessType(builder, innerType, options);
while (type.IsArray)
{
builder.Append('[');
builder.Append(',', type.GetArrayRank() - 1);
builder.Append(']');
if (type.GetElementType() is not { } elementType) break;
type = elementType;
}
}
private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length,
DisplayNameOptions options)
{
var offset = 0;
if (type.IsNested && type.DeclaringType is not null) offset = type.DeclaringType.GetGenericArguments().Length;
if (options.FullName)
{
if (type.IsNested && type.DeclaringType is not null)
{
ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options);
builder.Append('+');
}
else if (!string.IsNullOrEmpty(type.Namespace))
{
builder.Append(type.Namespace);
builder.Append('.');
}
}
var genericPartIndex = type.Name.IndexOf('`');
if (genericPartIndex <= 0)
{
builder.Append(type.Name);
return;
}
if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll"
&& FSharpTypeNames.TryGetValue(type.Name, out var builtInName))
builder.Append(builtInName);
else
builder.Append(type.Name, 0, genericPartIndex);
builder.Append('<');
for (var i = offset; i < length; i++)
{
ProcessType(builder, genericArguments[i], options);
if (i + 1 == length) continue;
builder.Append(',');
if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter)
builder.Append(' ');
}
builder.Append('>');
}
private struct DisplayNameOptions
{
public DisplayNameOptions(bool fullName, bool includeGenericParameterNames)
{
FullName = fullName;
IncludeGenericParameterNames = includeGenericParameterNames;
}
public bool FullName { get; }
public bool IncludeGenericParameterNames { get; }
}
} }

View File

@ -1,68 +1,58 @@
// Copyright (c) Ben A Adams. All rights reserved. // Copyright (c) Ben A Adams. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Ben.Demystifier.Internal; using Ben.Demystifier.Internal;
using System.Text;
namespace Ben.Demystifier namespace Ben.Demystifier;
public class ValueTupleResolvedParameter : ResolvedParameter
{ {
public class ValueTupleResolvedParameter : ResolvedParameter public ValueTupleResolvedParameter(Type resolvedType, IList<string> tupleNames)
: base(resolvedType)
{ {
public IList<string> TupleNames { get; } TupleNames = tupleNames;
}
public ValueTupleResolvedParameter(Type resolvedType, IList<string> tupleNames) public IList<string> TupleNames { get; }
: base(resolvedType)
=> TupleNames = tupleNames;
protected override void AppendTypeName(StringBuilder sb) protected override void AppendTypeName(StringBuilder sb)
{
if (ResolvedType is not null)
{ {
if (ResolvedType is not null) if (ResolvedType.IsValueTuple())
{ {
if (ResolvedType.IsValueTuple()) AppendValueTupleParameterName(sb, ResolvedType);
{
AppendValueTupleParameterName(sb, ResolvedType);
}
else
{
// Need to unwrap the first generic argument first.
sb.Append(TypeNameHelper.GetTypeNameForGenericType(ResolvedType));
sb.Append("<");
AppendValueTupleParameterName(sb, ResolvedType.GetGenericArguments()[0]);
sb.Append(">");
}
} }
} else
private void AppendValueTupleParameterName(StringBuilder sb, Type parameterType)
{
sb.Append("(");
var args = parameterType.GetGenericArguments();
for (var i = 0; i < args.Length; i++)
{ {
if (i > 0) // Need to unwrap the first generic argument first.
{ sb.Append(TypeNameHelper.GetTypeNameForGenericType(ResolvedType));
sb.Append(", "); sb.Append("<");
} AppendValueTupleParameterName(sb, ResolvedType.GetGenericArguments()[0]);
sb.Append(">");
sb.AppendTypeDisplayName(args[i], fullName: false, includeGenericParameterNames: true);
if (i >= TupleNames.Count)
{
continue;
}
var argName = TupleNames[i];
if (argName == null)
{
continue;
}
sb.Append(" ");
sb.Append(argName);
} }
sb.Append(")");
} }
} }
private void AppendValueTupleParameterName(StringBuilder sb, Type parameterType)
{
sb.Append('(');
var args = parameterType.GetGenericArguments();
for (var i = 0; i < args.Length; i++)
{
if (i > 0) sb.Append(", ");
sb.AppendTypeDisplayName(args[i], false, true);
if (i >= TupleNames.Count) continue;
var argName = TupleNames[i];
if (argName is null) continue;
sb.Append(' ');
sb.Append(argName);
}
sb.Append(')');
}
} }