Browse Source

Add support for C# 8.0 await foreach

pull/2126/head
Siegfried Pammer 5 years ago
parent
commit
562699fc94
  1. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 6
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  3. 32
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncForeach.cs
  4. 2
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  5. 18
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  6. 12
      ICSharpCode.Decompiler/CSharp/Syntax/Statements/ForeachStatement.cs
  7. 26
      ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs

1
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -94,6 +94,7 @@ @@ -94,6 +94,7 @@
<Compile Include="TestAssemblyResolver.cs" />
<Compile Include="TestCases\Correctness\DeconstructionTests.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" />
<None Include="TestCases\Pretty\AsyncForeach.cs" />
<Compile Include="TestCases\Pretty\DeconstructionTests.cs" />
<Compile Include="TestCases\ILPretty\Issue2104.cs" />
<Compile Include="TestCases\Pretty\SwitchExpressions.cs" />

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -314,6 +314,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -314,6 +314,12 @@ namespace ICSharpCode.Decompiler.Tests
RunForLibrary(cscOptions: cscOptions);
}
[Test]
public void AsyncForeach([ValueSource(nameof(dotnetCoreOnlyOptions))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}
[Test]
public void AsyncMain([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{

32
ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncForeach.cs

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
internal class AsyncForeach
{
public async Task<int> SumIntegers(IAsyncEnumerable<int> items, CancellationToken token)
{
int sum = 0;
await foreach (int item in items.WithCancellation(token)) {
if (token.IsCancellationRequested) {
break;
}
sum += item;
}
return sum;
}
public async Task<int> MaxInteger(IAsyncEnumerable<int> items)
{
int max = int.MinValue;
await foreach (int item in items) {
if (item > max) {
max = item;
}
}
return max;
}
}
}

2
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -1556,6 +1556,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1556,6 +1556,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public virtual void VisitForeachStatement(ForeachStatement foreachStatement)
{
StartNode(foreachStatement);
if (foreachStatement.IsAsync)
WriteKeyword(ForeachStatement.AwaitRole);
WriteKeyword(ForeachStatement.ForeachKeywordRole);
Space(policy.SpaceBeforeForeachParentheses);
LPar();

18
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -403,8 +403,16 @@ namespace ICSharpCode.Decompiler.CSharp @@ -403,8 +403,16 @@ namespace ICSharpCode.Decompiler.CSharp
}
#region foreach construction
static readonly InvocationExpression getEnumeratorPattern = new InvocationExpression(new MemberReferenceExpression(new AnyNode("collection").ToExpression(), "GetEnumerator"));
static readonly InvocationExpression moveNextConditionPattern = new InvocationExpression(new MemberReferenceExpression(new NamedNode("enumerator", new IdentifierExpression(Pattern.AnyString)), "MoveNext"));
static readonly InvocationExpression getEnumeratorPattern = new InvocationExpression(
new Choice {
new MemberReferenceExpression(new AnyNode("collection").ToExpression(), "GetEnumerator"),
new MemberReferenceExpression(new AnyNode("collection").ToExpression(), "GetAsyncEnumerator")
}
);
static readonly Expression moveNextConditionPattern = new Choice {
new InvocationExpression(new MemberReferenceExpression(new NamedNode("enumerator", new IdentifierExpression(Pattern.AnyString)), "MoveNext")),
new UnaryOperatorExpression(UnaryOperatorType.Await, new InvocationExpression(new MemberReferenceExpression(new NamedNode("enumerator", new IdentifierExpression(Pattern.AnyString)), "MoveNextAsync")))
};
protected internal override TranslatedStatement VisitUsingInstruction(UsingInstruction inst)
{
@ -484,6 +492,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -484,6 +492,9 @@ namespace ICSharpCode.Decompiler.CSharp
// The using body must be a BlockContainer.
if (!(inst.Body is BlockContainer container) || !m.Success)
return null;
bool isAsync = ((MemberReferenceExpression)((InvocationExpression)resource).Target).MemberName == "GetAsyncEnumerator";
if (isAsync != inst.IsAsync)
return null;
// The using-variable is the enumerator.
var enumeratorVar = inst.Variable;
// If there's another BlockContainer nested in this container and it only has one child block, unwrap it.
@ -499,6 +510,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -499,6 +510,8 @@ namespace ICSharpCode.Decompiler.CSharp
var m2 = moveNextConditionPattern.Match(condition.Expression);
if (!m2.Success)
return null;
if (condition.Expression is UnaryOperatorExpression { Operator: UnaryOperatorType.Await } != isAsync)
return null;
// Check enumerator variable references.
var enumeratorVar2 = m2.Get<IdentifierExpression>("enumerator").Single().GetILVariable();
if (enumeratorVar2 != enumeratorVar)
@ -604,6 +617,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -604,6 +617,7 @@ namespace ICSharpCode.Decompiler.CSharp
// Construct the foreach loop.
var foreachStmt = new ForeachStatement {
IsAsync = isAsync,
VariableType = useVar ? new SimpleType("var") : exprBuilder.ConvertType(foreachVariable.Type),
VariableDesignation = designation,
InExpression = collectionExpr.Detach(),

12
ICSharpCode.Decompiler/CSharp/Syntax/Statements/ForeachStatement.cs

@ -32,9 +32,19 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -32,9 +32,19 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// </summary>
public class ForeachStatement : Statement
{
public static readonly TokenRole AwaitRole = UnaryOperatorExpression.AwaitRole;
public static readonly TokenRole ForeachKeywordRole = new TokenRole ("foreach");
public static readonly TokenRole InKeywordRole = new TokenRole ("in");
public CSharpTokenNode AwaitToken {
get { return GetChildByRole(AwaitRole); }
}
public bool IsAsync {
get { return !GetChildByRole(AwaitRole).IsNull; }
set { SetChildByRole(AwaitRole, value ? new CSharpTokenNode(TextLocation.Empty, null) : null); }
}
public CSharpTokenNode ForeachToken {
get { return GetChildByRole (ForeachKeywordRole); }
}

26
ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs

@ -293,7 +293,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -293,7 +293,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!UnwrapAwait(ref checkInst))
return false;
}
if (!(checkInst is CallVirt cv))
if (!(checkInst is CallInstruction cv))
return false;
target = cv.Arguments.FirstOrDefault();
if (target == null)
@ -345,7 +345,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -345,7 +345,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (i < 1 || !context.Settings.AsyncUsingAndForEachStatement) return false;
if (!(block.Instructions[i] is TryFinally tryFinally) || !(block.Instructions[i - 1] is StLoc storeInst))
return false;
if (!CheckAsyncResourceType(storeInst.Variable.Type))
if (!CheckAsyncResourceType(storeInst.Variable.Type, out string disposeMethodFullName))
return false;
if (storeInst.Variable.Kind != VariableKind.Local)
return false;
@ -355,7 +355,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -355,7 +355,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (storeInst.Variable.StoreInstructions.Count > 1)
return false;
if (!(tryFinally.FinallyBlock is BlockContainer container) || !MatchDisposeBlock(container, storeInst.Variable, usingNull: false, "System.IAsyncDisposable.DisposeAsync", KnownTypeCode.IAsyncDisposable))
if (!(tryFinally.FinallyBlock is BlockContainer container) || !MatchDisposeBlock(container, storeInst.Variable, usingNull: false, disposeMethodFullName, KnownTypeCode.IAsyncDisposable))
return false;
context.Step("AsyncUsingTransform", tryFinally);
storeInst.Variable.Kind = VariableKind.UsingLocal;
@ -365,10 +365,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -365,10 +365,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
bool CheckAsyncResourceType(IType type)
bool CheckAsyncResourceType(IType type, out string disposeMethodFullName)
{
if (NullableType.GetUnderlyingType(type).GetAllBaseTypes().Any(b => b.IsKnownType(KnownTypeCode.IAsyncDisposable)))
disposeMethodFullName = null;
IType t = NullableType.GetUnderlyingType(type);
if (t.GetAllBaseTypes().Any(b => b.IsKnownType(KnownTypeCode.IAsyncDisposable))) {
disposeMethodFullName = "System.IAsyncDisposable.DisposeAsync";
return true;
}
IMethod disposeMethod = t
.GetMethods(m => m.Parameters.Count == 0 && m.TypeParameters.Count == 0 && m.Name == "DisposeAsync")
.SingleOrDefault();
if (disposeMethod != null) {
disposeMethodFullName = disposeMethod.FullName;
return true;
}
return false;
}
@ -380,8 +393,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -380,8 +393,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!arg.MatchAddressOf(out awaitInstruction, out var type))
return false;
if (!type.IsKnownType(KnownTypeCode.ValueTask))
return false;
// TODO check type: does it match the structural 'Awaitable' pattern?
return true;
}
}

Loading…
Cancel
Save