Skip to content
Permalink
Browse files

Merge pull request #1730 from icsharpcode/async-streams

C# 8.0 Async streams
  • Loading branch information...
dgrunwald committed Nov 4, 2019
2 parents 6e4db66 + c88bd59 commit cb301bb0e2fd6c78ab529cbcab13e01d7a5ba46d
Showing with 1,019 additions and 146 deletions.
  1. +1 −1 BuildTools/update-assemblyinfo.ps1
  2. +1 −1 ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs
  3. +39 −10 ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
  4. +2 −0 ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  5. +19 −1 ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  6. +9 −0 ICSharpCode.Decompiler.Tests/TestCases/Correctness/TrickyTypes.cs
  7. +0 −2 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Debug.cs
  8. +0 −2 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Release.cs
  9. +0 −2 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.cs
  10. +37 −0 ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncStreams.cs
  11. +60 −0 ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs
  12. +0 −3 ICSharpCode.Decompiler.Tests/TestCases/Pretty/InterfaceTests.cs
  13. +7 −1 ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  14. +9 −0 ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  15. +4 −0 ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  16. +3 −2 ICSharpCode.Decompiler/CSharp/Resolver/LambdaResolveResult.cs
  17. +20 −10 ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs
  18. +19 −3 ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  19. +35 −25 ICSharpCode.Decompiler/CSharp/Syntax/Statements/UsingStatement.cs
  20. +1 −1 ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
  21. +36 −2 ICSharpCode.Decompiler/DecompilerSettings.cs
  22. +446 −31 ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
  23. +2 −0 ICSharpCode.Decompiler/IL/ControlFlow/AwaitInCatchTransform.cs
  24. +4 −2 ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs
  25. +16 −0 ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
  26. +21 −0 ICSharpCode.Decompiler/IL/Instructions/Block.cs
  27. +7 −1 ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs
  28. +94 −13 ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
  29. +26 −4 ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs
  30. +17 −17 ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs
  31. +1 −1 ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs
  32. +2 −0 ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs
  33. +1 −1 ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeParameter.cs
  34. +2 −1 ICSharpCode.Decompiler/TypeSystem/Implementation/NullabilityAnnotatedType.cs
  35. +19 −4 ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs
  36. +2 −1 ICSharpCode.Decompiler/TypeSystem/TupleType.cs
  37. +16 −1 ICSharpCode.Decompiler/TypeSystem/TypeParameterSubstitution.cs
  38. +5 −0 ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
  39. +27 −0 ILSpy/Properties/Resources.Designer.cs
  40. +9 −3 ILSpy/Properties/Resources.resx
@@ -53,7 +53,7 @@ function gitCommitHash() {
}

function gitBranch() {
if (-not ((Test-Dir ".git") -and (Find-Git))) {
if (-not ((Test-Dir ".git" -or Test-File ".git") -and (Find-Git))) {
return "no-branch";
}

@@ -13,7 +13,7 @@ public override void VisitAttribute(CSharp.Syntax.Attribute attribute)
var section = (AttributeSection)attribute.Parent;
SimpleType type = attribute.Type as SimpleType;
if (section.AttributeTarget == "assembly" &&
(type.Identifier == "CompilationRelaxations" || type.Identifier == "RuntimeCompatibility" || type.Identifier == "SecurityPermission" || type.Identifier == "PermissionSet" || type.Identifier == "AssemblyVersion" || type.Identifier == "Debuggable"))
(type.Identifier == "CompilationRelaxations" || type.Identifier == "RuntimeCompatibility" || type.Identifier == "SecurityPermission" || type.Identifier == "PermissionSet" || type.Identifier == "AssemblyVersion" || type.Identifier == "Debuggable" || type.Identifier == "TargetFramework"))
{
attribute.Remove();
if (section.Attributes.Count == 0)
@@ -55,6 +55,7 @@ public enum CompilerOptions
UseRoslyn = 0x10,
UseMcs = 0x20,
ReferenceVisualBasic = 0x40,
ReferenceCore = 0x80,
}

[Flags]
@@ -89,12 +90,12 @@ public static string AssembleIL(string sourceFileName, AssemblerOptions options
outputFile += ".exe";
otherOptions += "/exe ";
}


if (options.HasFlag(AssemblerOptions.UseDebug)) {
otherOptions += "/debug ";
}

ProcessStartInfo info = new ProcessStartInfo(ilasmPath);
info.Arguments = $"/nologo {otherOptions}/output=\"{outputFile}\" \"{sourceFileName}\"";
info.RedirectStandardError = true;
@@ -115,7 +116,7 @@ public static string AssembleIL(string sourceFileName, AssemblerOptions options

return outputFile;
}

public static string Disassemble(string sourceFileName, string outputFile, AssemblerOptions asmOptions)
{
if (asmOptions.HasFlag(AssemblerOptions.UseOwnDisassembler)) {
@@ -141,7 +142,7 @@ public static string Disassemble(string sourceFileName, string outputFile, Assem
}

string ildasmPath = SdkUtility.GetSdkPath("ildasm.exe");

ProcessStartInfo info = new ProcessStartInfo(ildasmPath);
info.Arguments = $"/nobar /utf8 /out=\"{outputFile}\" \"{sourceFileName}\"";
info.RedirectStandardError = true;
@@ -182,8 +183,10 @@ private static string ReplacePrivImplDetails(string il)
return Regex.Replace(il, @"'<PrivateImplementationDetails>\{[0-9A-F-]+\}'", "'<PrivateImplementationDetails>'");
}

static readonly string coreRefAsmPath = new DotNetCorePathFinder(new Version(3, 0)).GetReferenceAssemblyPath(".NETCoreApp, Version = v3.0");

static readonly string refAsmPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
@"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2");
@"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2");
static readonly string thisAsmPath = Path.GetDirectoryName(typeof(Tester).Assembly.Location);

static readonly Lazy<IEnumerable<MetadataReference>> defaultReferences = new Lazy<IEnumerable<MetadataReference>>(delegate {
@@ -202,6 +205,21 @@ private static string ReplacePrivImplDetails(string il)
};
});

static readonly Lazy<IEnumerable<MetadataReference>> coreDefaultReferences = new Lazy<IEnumerable<MetadataReference>>(GetDefaultReferences);

const string targetFrameworkAttributeSnippet = @"
[assembly: System.Runtime.Versioning.TargetFramework("".NETCoreApp, Version = v3.0"", FrameworkDisplayName = """")]
";

static IEnumerable<MetadataReference> GetDefaultReferences()
{
foreach (var reference in Directory.EnumerateFiles(coreRefAsmPath, "*.dll")) {
yield return MetadataReference.CreateFromFile(reference);
}
}

static readonly Lazy<IEnumerable<MetadataReference>> visualBasic = new Lazy<IEnumerable<MetadataReference>>(delegate {
return new[] {
MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "Microsoft.VisualBasic.dll"))
@@ -217,6 +235,9 @@ public static List<string> GetPreprocessorSymbols(CompilerOptions flags)
if (flags.HasFlag(CompilerOptions.Optimize)) {
preprocessorSymbols.Add("OPT");
}
if (flags.HasFlag(CompilerOptions.ReferenceCore)) {
preprocessorSymbols.Add("NETCORE");
}
if (flags.HasFlag(CompilerOptions.UseRoslyn)) {
preprocessorSymbols.Add("ROSLYN");
preprocessorSymbols.Add("CS60");
@@ -252,7 +273,15 @@ public static CompilerResults CompileCSharp(string sourceFileName, CompilerOptio
languageVersion: Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8
);
var syntaxTrees = sourceFileNames.Select(f => SyntaxFactory.ParseSyntaxTree(File.ReadAllText(f), parseOptions, path: f));
var references = defaultReferences.Value;
if (flags.HasFlag(CompilerOptions.ReferenceCore)) {
syntaxTrees = syntaxTrees.Concat(new[] { SyntaxFactory.ParseSyntaxTree(targetFrameworkAttributeSnippet) });
}
IEnumerable<MetadataReference> references;
if (flags.HasFlag(CompilerOptions.ReferenceCore)) {
references = coreDefaultReferences.Value;
} else {
references = defaultReferences.Value;
}
if (flags.HasFlag(CompilerOptions.ReferenceVisualBasic)) {
references = references.Concat(visualBasic.Value);
}
@@ -466,16 +495,16 @@ public static string DecompileCSharp(string assemblyFileName, DecompilerSettings
return fileName;
}
}

public static void RunAndCompareOutput(string testFileName, string outputFile, string decompiledOutputFile, string decompiledCodeFile = null)
{
string output1, output2, error1, error2;
int result1 = Tester.Run(outputFile, out output1, out error1);
int result2 = Tester.Run(decompiledOutputFile, out output2, out error2);

Assert.AreEqual(0, result1, "Exit code != 0; did the test case crash?" + Environment.NewLine + error1);
Assert.AreEqual(0, result2, "Exit code != 0; did the decompiled code crash?" + Environment.NewLine + error2);

if (output1 != output2 || error1 != error2) {
StringBuilder b = new StringBuilder();
b.AppendLine($"Test {testFileName} failed: output does not match.");
@@ -81,6 +81,8 @@
<Compile Include="DisassemblerPrettyTestRunner.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" />
<Compile Include="TestCases\ILPretty\ConstantBlobs.cs" />
<None Include="TestCases\Pretty\AsyncStreams.cs" />
<None Include="TestCases\Pretty\AsyncUsing.cs" />
<Compile Include="TestCases\Pretty\OutVariables.cs" />
<Compile Include="TestCases\Pretty\CustomTaskType.cs" />
<None Include="TestCases\Ugly\NoForEachStatement.Expected.cs" />
@@ -59,6 +59,12 @@ public void AllFilesHaveTests()
CompilerOptions.Optimize | CompilerOptions.UseRoslyn
};

static readonly CompilerOptions[] dotnetCoreOnlyOptions =
{
CompilerOptions.UseRoslyn | CompilerOptions.ReferenceCore,
CompilerOptions.Optimize | CompilerOptions.UseRoslyn | CompilerOptions.ReferenceCore
};

static readonly CompilerOptions[] defaultOptions =
{
CompilerOptions.None,
@@ -301,6 +307,18 @@ public void AsyncMain([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions c
Run(cscOptions: cscOptions);
}

[Test]
public void AsyncStreams([ValueSource(nameof(dotnetCoreOnlyOptions))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}

[Test]
public void AsyncUsing([ValueSource(nameof(dotnetCoreOnlyOptions))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}

[Test]
public void CustomTaskType([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{
@@ -436,7 +454,7 @@ public void EnumTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscO
[Test]
public void InterfaceTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
RunForLibrary(cscOptions: cscOptions | CompilerOptions.ReferenceCore);
}

[Test]
@@ -17,6 +17,7 @@
// DEALINGS IN THE SOFTWARE.

using System;
using System.Linq;

namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
{
@@ -27,6 +28,8 @@ static void Main()
InterestingConstants();
TruncatedComp();
StringConcat();
LinqNullableMin();
LinqNullableMin(1, 2, 3);
}

static void Print<T>(T val)
@@ -101,5 +104,11 @@ static void StringConcat()
Print(string.Concat(1, 2));
Print(string.Concat(1, 2, "str"));
}

static void LinqNullableMin(params int[] arr)
{
Print(string.Format("LinqNullableMin {0}:", arr.Length));
Print(arr.Min(v => (int?)v));
}
}
}
@@ -14,14 +14,12 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

[assembly: FSharpInterfaceDataVersion(2, 0, 0)]
[assembly: AssemblyTitle("ConsoleApplication1")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ConsoleApplication1")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.6.1", FrameworkDisplayName = ".NET Framework 4.6.1")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCopyright("Copyright © 2017")]
@@ -15,14 +15,12 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

[assembly: FSharpInterfaceDataVersion(2, 0, 0)]
[assembly: AssemblyTitle("ConsoleApplication1")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ConsoleApplication1")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.6.1", FrameworkDisplayName = ".NET Framework 4.6.1")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCopyright("Copyright © 2017")]
@@ -6,10 +6,8 @@
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
[assembly: Embedded]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: TargetFramework(".NETCoreApp,Version=v2.1", FrameworkDisplayName = "")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]

@@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public class AsyncStreams
{
public static async IAsyncEnumerable<int> CountTo(int until)
{
for (int i = 0; i < until; i++) {
yield return i;
await Task.Delay(10);
}
}

public static async IAsyncEnumerable<int> AlwaysThrow()
{
throw null;
yield break;
}

public static async IAsyncEnumerator<int> InfiniteLoop()
{
while (true) {
}
yield break;
}

public static async IAsyncEnumerable<int> InfiniteLoopWithAwait()
{
while (true) {
await Task.Delay(10);
}
yield break;
}
}
}
@@ -0,0 +1,60 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
internal class AsyncUsing
{
internal class AsyncDisposableClass : IAsyncDisposable
{
public ValueTask DisposeAsync()
{
throw new NotImplementedException();
}
}

[StructLayout(LayoutKind.Sequential, Size = 1)]
internal struct AsyncDisposableStruct : IAsyncDisposable
{
public ValueTask DisposeAsync()
{
throw new NotImplementedException();
}
}

public static async void TestAsyncUsing(IAsyncDisposable disposable)
{
await using (disposable) {
Console.WriteLine("Hello");
}
}

public static async void TestAsyncUsingClass()
{
await using (AsyncDisposableClass test = new AsyncDisposableClass()) {
Use(test);
}
}

public static async void TestAsyncUsingStruct()
{
await using (AsyncDisposableStruct asyncDisposableStruct = default(AsyncDisposableStruct)) {
Use(asyncDisposableStruct);
}
}

public static async void TestAsyncUsingNullableStruct()
{
await using (AsyncDisposableStruct? asyncDisposableStruct = new AsyncDisposableStruct?(default(AsyncDisposableStruct))) {
Use(asyncDisposableStruct);
}
}

private static void Use(IAsyncDisposable test)
{
throw new NotImplementedException();
}

}
}
@@ -16,9 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

// We can't test this because "error CS8701: Target runtime doesn't support default interface implementation."
#undef CS80

using System;

namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
@@ -305,6 +305,8 @@ public static bool MemberIsHidden(Metadata.PEFile module, EntityHandle member, D
return true;
if (settings.AsyncAwait && AsyncAwaitDecompiler.IsCompilerGeneratedStateMachine(typeHandle, metadata))
return true;
if (settings.AsyncEnumerator && AsyncAwaitDecompiler.IsCompilerGeneratorAsyncEnumerator(typeHandle, metadata))
return true;
if (settings.FixedBuffers && name.StartsWith("<", StringComparison.Ordinal) && name.Contains("__FixedBuffer"))
return true;
} else if (type.IsCompilerGenerated(metadata)) {
@@ -1343,7 +1345,11 @@ void DecompileBody(IMethod method, EntityDeclaration entityDecl, DecompileRun de
if (localSettings.DecompileMemberBodies && !body.Descendants.Any(d => d is YieldReturnStatement || d is YieldBreakStatement)) {
body.Add(new YieldBreakStatement());
}
RemoveAttribute(entityDecl, KnownAttribute.IteratorStateMachine);
if (function.IsAsync) {
RemoveAttribute(entityDecl, KnownAttribute.AsyncIteratorStateMachine);
} else {
RemoveAttribute(entityDecl, KnownAttribute.IteratorStateMachine);
}
if (function.StateMachineCompiledWithMono) {
RemoveAttribute(entityDecl, KnownAttribute.DebuggerHidden);
}

0 comments on commit cb301bb

Please sign in to comment.
You can’t perform that action at this time.