Demystify truncating internals of F# async/task expressions

This commit is contained in:
Bartosz Sypytkowski 2020-01-05 17:02:03 +01:00
parent 1ca8f79a36
commit af40cc3bfa
8 changed files with 134 additions and 3 deletions

View File

@ -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}

View File

@ -1,5 +1,4 @@
<Project>
<PropertyGroup>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
</Project>
</Project>

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Ben.Demystifier\Ben.Demystifier.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Ply" Version="0.1.7" />
</ItemGroup>
</Project>

View File

@ -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
}
[<EntryPoint>]
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

View File

@ -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

View File

@ -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;
@ -662,6 +683,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;
}
}
if (StackTraceHiddenAttributeType != null)
{
// Don't show any types marked with the StackTraceHiddenAttribute

View File

@ -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)

View File

@ -28,6 +28,15 @@ namespace System.Diagnostics
{ 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.
@ -93,6 +102,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)
@ -154,7 +168,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++)