Browse Source

Add support for C# 8.0 await foreach

pull/2126/head
Siegfried Pammer 6 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 @@
<Compile Include="TestAssemblyResolver.cs" /> <Compile Include="TestAssemblyResolver.cs" />
<Compile Include="TestCases\Correctness\DeconstructionTests.cs" /> <Compile Include="TestCases\Correctness\DeconstructionTests.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" /> <Compile Include="TestCases\Correctness\StringConcat.cs" />
<None Include="TestCases\Pretty\AsyncForeach.cs" />
<Compile Include="TestCases\Pretty\DeconstructionTests.cs" /> <Compile Include="TestCases\Pretty\DeconstructionTests.cs" />
<Compile Include="TestCases\ILPretty\Issue2104.cs" /> <Compile Include="TestCases\ILPretty\Issue2104.cs" />
<Compile Include="TestCases\Pretty\SwitchExpressions.cs" /> <Compile Include="TestCases\Pretty\SwitchExpressions.cs" />

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

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

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

@ -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
public virtual void VisitForeachStatement(ForeachStatement foreachStatement) public virtual void VisitForeachStatement(ForeachStatement foreachStatement)
{ {
StartNode(foreachStatement); StartNode(foreachStatement);
if (foreachStatement.IsAsync)
WriteKeyword(ForeachStatement.AwaitRole);
WriteKeyword(ForeachStatement.ForeachKeywordRole); WriteKeyword(ForeachStatement.ForeachKeywordRole);
Space(policy.SpaceBeforeForeachParentheses); Space(policy.SpaceBeforeForeachParentheses);
LPar(); LPar();

18
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

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

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

@ -32,9 +32,19 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// </summary> /// </summary>
public class ForeachStatement : Statement public class ForeachStatement : Statement
{ {
public static readonly TokenRole AwaitRole = UnaryOperatorExpression.AwaitRole;
public static readonly TokenRole ForeachKeywordRole = new TokenRole ("foreach"); public static readonly TokenRole ForeachKeywordRole = new TokenRole ("foreach");
public static readonly TokenRole InKeywordRole = new TokenRole ("in"); 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 { public CSharpTokenNode ForeachToken {
get { return GetChildByRole (ForeachKeywordRole); } get { return GetChildByRole (ForeachKeywordRole); }
} }

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

@ -293,7 +293,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!UnwrapAwait(ref checkInst)) if (!UnwrapAwait(ref checkInst))
return false; return false;
} }
if (!(checkInst is CallVirt cv)) if (!(checkInst is CallInstruction cv))
return false; return false;
target = cv.Arguments.FirstOrDefault(); target = cv.Arguments.FirstOrDefault();
if (target == null) if (target == null)
@ -345,7 +345,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (i < 1 || !context.Settings.AsyncUsingAndForEachStatement) return false; if (i < 1 || !context.Settings.AsyncUsingAndForEachStatement) return false;
if (!(block.Instructions[i] is TryFinally tryFinally) || !(block.Instructions[i - 1] is StLoc storeInst)) if (!(block.Instructions[i] is TryFinally tryFinally) || !(block.Instructions[i - 1] is StLoc storeInst))
return false; return false;
if (!CheckAsyncResourceType(storeInst.Variable.Type)) if (!CheckAsyncResourceType(storeInst.Variable.Type, out string disposeMethodFullName))
return false; return false;
if (storeInst.Variable.Kind != VariableKind.Local) if (storeInst.Variable.Kind != VariableKind.Local)
return false; return false;
@ -355,7 +355,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (storeInst.Variable.StoreInstructions.Count > 1) if (storeInst.Variable.StoreInstructions.Count > 1)
return false; 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; return false;
context.Step("AsyncUsingTransform", tryFinally); context.Step("AsyncUsingTransform", tryFinally);
storeInst.Variable.Kind = VariableKind.UsingLocal; storeInst.Variable.Kind = VariableKind.UsingLocal;
@ -365,10 +365,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; 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; 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; return false;
} }
@ -380,8 +393,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (!arg.MatchAddressOf(out awaitInstruction, out var type)) if (!arg.MatchAddressOf(out awaitInstruction, out var type))
return false; return false;
if (!type.IsKnownType(KnownTypeCode.ValueTask)) // TODO check type: does it match the structural 'Awaitable' pattern?
return false;
return true; return true;
} }
} }

Loading…
Cancel
Save