Skip to content

Commit

Permalink
Fix async method default return statements - fixes #478
Browse files Browse the repository at this point in the history
  • Loading branch information
GrahamTheCoder committed Mar 8, 2020
1 parent 745007a commit 1a7e389
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
* Convert "{}" to "Array.Empty<T>()" [#495](https://github.com/icsharpcode/CodeConverter/issues/495)
* Convert inferred anonymous member names without duplicating name [#480](https://github.com/icsharpcode/CodeConverter/issues/480)
* Convert "!" operator to element access [#479](https://github.com/icsharpcode/CodeConverter/issues/479)
* Fix async method default return statements [#478](https://github.com/icsharpcode/CodeConverter/issues/478)

### C# -> VB

Expand Down
6 changes: 5 additions & 1 deletion CodeConverter/CSharp/CommonConversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,11 @@ public static CSSyntax.VariableDeclaratorSyntax CreateVariableDeclarator(string

public CSSyntax.IdentifierNameSyntax GetRetVariableNameOrNull(VBSyntax.MethodBlockBaseSyntax node)
{
if (!node.AllowsImplicitReturn()) return null;
if (!node.MustReturn()) return null;
if (_semanticModel.GetDeclaredSymbol(node) is IMethodSymbol ms && ms.ReturnsVoidOrAsyncTask()) {
return null;
}


bool assignsToMethodNameVariable = false;

Expand Down
6 changes: 5 additions & 1 deletion CodeConverter/CSharp/DeclarationNodeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,11 @@ public override async Task<CSharpSyntaxNode> VisitMethodBlock(VBSyntax.MethodBlo
private BlockSyntax WithImplicitReturnStatements(VBSyntax.MethodBlockBaseSyntax node, BlockSyntax convertedStatements,
IdentifierNameSyntax csReturnVariableOrNull)
{
if (!node.AllowsImplicitReturn()) return convertedStatements;
if (!node.MustReturn()) return convertedStatements;
if (_semanticModel.GetDeclaredSymbol(node) is IMethodSymbol ms && ms.ReturnsVoidOrAsyncTask()) {
return convertedStatements;
}


var preBodyStatements = new List<StatementSyntax>();
var postBodyStatements = new List<StatementSyntax>();
Expand Down
2 changes: 1 addition & 1 deletion CodeConverter/CSharp/ExpressionNodeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1011,7 +1011,7 @@ public override async Task<CSharpSyntaxNode> VisitIdentifierName(VBasic.Syntax.I
if (sym != null && sym.Kind == SymbolKind.Local) {
var vbMethodBlock = node.Ancestors().OfType<VBasic.Syntax.MethodBlockBaseSyntax>().FirstOrDefault();
if (vbMethodBlock != null &&
vbMethodBlock.AllowsImplicitReturn() &&
vbMethodBlock.MustReturn() &&
!node.Parent.IsKind(VBasic.SyntaxKind.NameOfExpression) &&
node.Identifier.ValueText.Equals(CommonConversions.GetMethodBlockBaseIdentifierForImplicitReturn(vbMethodBlock).ValueText, StringComparison.OrdinalIgnoreCase)) {
var retVar = CommonConversions.GetRetVariableNameOrNull(vbMethodBlock);
Expand Down
11 changes: 8 additions & 3 deletions CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using VBasic = Microsoft.CodeAnalysis.VisualBasic;
using VBSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax;
using Microsoft.CodeAnalysis.Text;
using ICSharpCode.CodeConverter.Util.FromRoslyn;

namespace ICSharpCode.CodeConverter.CSharp
{
Expand Down Expand Up @@ -348,12 +349,16 @@ public override async Task<SyntaxList<StatementSyntax>> VisitExitStatement(VBSyn
var enclosingMethodInfo = await typeContainer.TypeSwitch(
async (VBSyntax.LambdaExpressionSyntax e) => _semanticModel.GetSymbolInfo(e).Symbol,
async (VBSyntax.MethodBlockSyntax e) => _semanticModel.GetDeclaredSymbol(e),
async (VBSyntax.AccessorBlockSyntax e) => _semanticModel.GetDeclaredSymbol(e));
async (VBSyntax.AccessorBlockSyntax e) => _semanticModel.GetDeclaredSymbol(e)) as IMethodSymbol;

if (IsIterator) return SingleStatement(SyntaxFactory.YieldStatement(SyntaxKind.YieldBreakStatement));

ExpressionSyntax expr = HasReturnVariable ? (ExpressionSyntax)ReturnVariable : SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression);
return SingleStatement(SyntaxFactory.ReturnStatement(expr));
if (!enclosingMethodInfo.ReturnsVoidOrAsyncTask()) {
ExpressionSyntax expr = HasReturnVariable ? (ExpressionSyntax)ReturnVariable : SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression);
return SingleStatement(SyntaxFactory.ReturnStatement(expr));
}

return SingleStatement(SyntaxFactory.ReturnStatement());
default:
return SingleStatement(SyntaxFactory.BreakStatement());
}
Expand Down
24 changes: 17 additions & 7 deletions CodeConverter/CSharp/VbMethodSyntaxExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
using System.Linq;
using ICSharpCode.CodeConverter.Util;
using Microsoft.CodeAnalysis;
using VBasic = Microsoft.CodeAnalysis.VisualBasic;
using VBSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax;

namespace ICSharpCode.CodeConverter.CSharp
{
internal static class VbMethodSyntaxExtensions
{
public static bool AllowsImplicitReturn(this Microsoft.CodeAnalysis.VisualBasic.Syntax.MethodBlockBaseSyntax node)
/// <summary>
/// Use in conjunction with <see cref="IMethodSymbolExtensions.ReturnsVoidOrAsyncTask(IMethodSymbol)" />
/// </summary>
public static bool MustReturn(this VBSyntax.MethodBlockBaseSyntax node)
{
return !IsIterator(node) && node.IsKind(Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.FunctionBlock, Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.GetAccessorBlock);
return !IsIterator(node) && node.IsKind(VBasic.SyntaxKind.FunctionBlock, VBasic.SyntaxKind.GetAccessorBlock)
&& !node.IsIterator();
}

public static bool IsIterator(this Microsoft.CodeAnalysis.VisualBasic.Syntax.MethodBlockBaseSyntax node)
public static bool IsIterator(this VBSyntax.MethodBlockBaseSyntax node)
{
var modifiableNode = node.IsKind(Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.GetAccessorBlock) ? node.GetAncestor<Microsoft.CodeAnalysis.VisualBasic.Syntax.PropertyBlockSyntax>().PropertyStatement : node.BlockStatement;
return HasIteratorModifier(modifiableNode);
return GetMethodBlock(node).HasModifier(VBasic.SyntaxKind.IteratorKeyword);
}

private static bool HasIteratorModifier(this Microsoft.CodeAnalysis.VisualBasic.Syntax.MethodBaseSyntax d)
public static bool HasModifier(this VBSyntax.MethodBaseSyntax d, VBasic.SyntaxKind modifierKind)
{
return d.Modifiers.Any(m => SyntaxTokenExtensions.IsKind(m, Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.IteratorKeyword));
return d.Modifiers.Any(m => SyntaxTokenExtensions.IsKind(m, modifierKind));
}

private static VBSyntax.MethodBaseSyntax GetMethodBlock(VBSyntax.MethodBlockBaseSyntax node)
{
return node.IsKind(VBasic.SyntaxKind.GetAccessorBlock) ? node.GetAncestor<VBSyntax.PropertyBlockSyntax>().PropertyStatement : node.BlockStatement;
}
}
}
6 changes: 6 additions & 0 deletions CodeConverter/Util/IMethodSymbolExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq;
using ICSharpCode.CodeConverter.Util.FromRoslyn;
using Microsoft.CodeAnalysis;

namespace ICSharpCode.CodeConverter.Util
Expand All @@ -14,5 +15,10 @@ public static (string Name, int TypeParameterCount, string ParameterTypes) GetUn
{
return (caseSensitiveName ? methodSymbol.Name : methodSymbol.Name.ToLowerInvariant() , methodSymbol.TypeParameters.Length, GetParameterSignature(methodSymbol));
}

public static bool ReturnsVoidOrAsyncTask(this IMethodSymbol enclosingMethodInfo)
{
return enclosingMethodInfo.ReturnsVoid || enclosingMethodInfo.IsAsync && enclosingMethodInfo.ReturnType.GetArity() == 0;
}
}
}
67 changes: 67 additions & 0 deletions Tests/CSharp/MemberTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2191,6 +2191,73 @@ public async void AsyncSub()
");
}

[Fact]
public async Task TestAsyncMethodsWithNoReturn()
{
await TestConversionVisualBasicToCSharp(
@"Friend Partial Module TaskExtensions
<Extension()>
Async Function [Then](Of T)(ByVal task As Task, ByVal f As Func(Of Task(Of T))) As Task(Of T)
Await task
Return Await f()
End Function
<Extension()>
Async Function [Then](ByVal task As Task, ByVal f As Func(Of Task)) As Task
Await task
Await f()
End Function
<Extension()>
Async Function [Then](Of T, U)(ByVal task As Task(Of T), ByVal f As Func(Of T, Task(Of U))) As Task(Of U)
Return Await f(Await task)
End Function
<Extension()>
Async Function [Then](Of T)(ByVal task As Task(Of T), ByVal f As Func(Of T, Task)) As Task
Await f(Await task)
End Function
<Extension()>
Async Function [ThenExit](Of T)(ByVal task As Task(Of T), ByVal f As Func(Of T, Task)) As Task
Await f(Await task)
Exit Function
End Function
End Module", @"using System;
using System.Threading.Tasks;
internal static partial class TaskExtensions
{
public async static Task<T> Then<T>(this Task task, Func<Task<T>> f)
{
await task;
return await f();
}
public async static Task Then(this Task task, Func<Task> f)
{
await task;
await f();
}
public async static Task<U> Then<T, U>(this Task<T> task, Func<T, Task<U>> f)
{
return await f(await task);
}
public async static Task Then<T>(this Task<T> task, Func<T, Task> f)
{
await f(await task);
}
public async static Task ThenExit<T>(this Task<T> task, Func<T, Task> f)
{
await f(await task);
return;
}
}");
}

[Fact]
public async Task TestExternDllImport()
{
Expand Down

0 comments on commit 1a7e389

Please sign in to comment.