diff --git a/Ben.Demystifier.sln b/Ben.Demystifier.sln index 5945254..b2fcbf9 100644 --- a/Ben.Demystifier.sln +++ b/Ben.Demystifier.sln @@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution 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 @@ -48,6 +50,10 @@ Global {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 @@ -57,6 +63,7 @@ Global {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} diff --git a/directory.build.props b/directory.build.props index 44c7d4e..1588fce 100644 --- a/directory.build.props +++ b/directory.build.props @@ -1,5 +1,4 @@  - 7.2 - \ No newline at end of file + diff --git a/sample/FSharpStackTrace/FSharpStackTrace.fsproj b/sample/FSharpStackTrace/FSharpStackTrace.fsproj new file mode 100644 index 0000000..0601ebd --- /dev/null +++ b/sample/FSharpStackTrace/FSharpStackTrace.fsproj @@ -0,0 +1,20 @@ + + + + Exe + netcoreapp3.0 + + + + + + + + + + + + + + + diff --git a/sample/FSharpStackTrace/Program.fs b/sample/FSharpStackTrace/Program.fs new file mode 100644 index 0000000..11659a4 --- /dev/null +++ b/sample/FSharpStackTrace/Program.fs @@ -0,0 +1,35 @@ +// Learn more about F# at http://fsharp.org + +open System +open System.Diagnostics +open System.Threading.Tasks +open FSharp.Control.Tasks.Builders + +let call i = async { + do! Async.Sleep 1 + if i = 10 then + failwith "BOOM!" + return i +} + +let run count = async { + let calls = Array.init count call + for call in calls do + let! _ = call + () + return 0 +} + +let makeTheCall () = task { + let! x = run 20 + return x +} + +[] +let main argv = + try + let results = makeTheCall().GetAwaiter().GetResult() + printfn "%A" results + with e -> + printfn "%s" <| string (e.Demystify()) + 0 // return an integer exit code diff --git a/sample/StackTrace/Program.cs b/sample/StackTrace/Program.cs index 05f9df5..b3bbd90 100644 --- a/sample/StackTrace/Program.cs +++ b/sample/StackTrace/Program.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading.Tasks; class Program diff --git a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs index bee86ba..03fd069 100644 --- a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs +++ b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs @@ -114,6 +114,13 @@ namespace System.Diagnostics methodName = method.Name; } + else if (IsFSharpAsync(method)) + { + methodDisplayInfo.IsAsync = true; + methodDisplayInfo.SubMethodBase = null; + subMethodName = null; + methodName = null; + } // Method name methodDisplayInfo.MethodBase = method; @@ -241,6 +248,20 @@ namespace System.Diagnostics return methodDisplayInfo; } + private static bool IsFSharpAsync(MethodBase method) + { + if (method is MethodInfo minfo) + { + var returnType = minfo.ReturnType; + if (returnType.Namespace == "Microsoft.FSharp.Control" && returnType.Name == "FSharpAsync`1") + { + return true; + } + } + + return false; + } + 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; @@ -699,6 +720,29 @@ namespace System.Diagnostics } } + if (type.Namespace == "Microsoft.FSharp.Control") + { + switch (type.Name) + { + case "AsyncPrimitives": + case "Trampoline": + return false; + case var typeName when type.IsGenericType: + { + if (typeName == "AsyncResult`1") return false; + else break; + } + } + } + + if (type.Namespace == "Ply") + { + if (type.DeclaringType.Name == "TplPrimitives") + { + return false; + } + } + // Fallbacks for runtime pre-StackTraceHiddenAttribute if (type == typeof(ExceptionDispatchInfo) && method.Name == "Throw") { diff --git a/src/Ben.Demystifier/ResolvedParameter.cs b/src/Ben.Demystifier/ResolvedParameter.cs index 2b4f5e5..038d1f4 100644 --- a/src/Ben.Demystifier/ResolvedParameter.cs +++ b/src/Ben.Demystifier/ResolvedParameter.cs @@ -18,6 +18,9 @@ namespace System.Diagnostics internal StringBuilder Append(StringBuilder sb) { + if (ResolvedType.Assembly.ManifestModule.Name == "FSharp.Core.dll" && ResolvedType.Name == "Unit") + return sb; + if (!string.IsNullOrEmpty(Prefix)) { sb.Append(Prefix) diff --git a/src/Ben.Demystifier/TypeNameHelper.cs b/src/Ben.Demystifier/TypeNameHelper.cs index 070276f..3450b63 100644 --- a/src/Ben.Demystifier/TypeNameHelper.cs +++ b/src/Ben.Demystifier/TypeNameHelper.cs @@ -28,6 +28,15 @@ namespace System.Diagnostics { typeof(ulong), "ulong" }, { typeof(ushort), "ushort" } }; + + public static readonly Dictionary FSharpTypeNames = new Dictionary + { + { "Unit", "void" }, + { "FSharpOption", "Option" }, + { "FSharpAsync", "Async" }, + { "FSharpOption`1", "Option" }, + { "FSharpAsync`1", "Async" } + }; /// /// Pretty print a type name. @@ -92,6 +101,11 @@ namespace System.Diagnostics { 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) @@ -153,7 +167,15 @@ namespace System.Diagnostics return; } - builder.Append(type.Name, 0, genericPartIndex); + 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++)