diff --git a/CHANGELOG.md b/CHANGELOG.md index 77873175b..a559e572b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Convert "{}" to "Array.Empty()" [#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 diff --git a/CodeConverter/CSharp/CommonConversions.cs b/CodeConverter/CSharp/CommonConversions.cs index f1959772d..7e49ba90a 100644 --- a/CodeConverter/CSharp/CommonConversions.cs +++ b/CodeConverter/CSharp/CommonConversions.cs @@ -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; diff --git a/CodeConverter/CSharp/DeclarationNodeVisitor.cs b/CodeConverter/CSharp/DeclarationNodeVisitor.cs index a3669527c..163d8deb8 100644 --- a/CodeConverter/CSharp/DeclarationNodeVisitor.cs +++ b/CodeConverter/CSharp/DeclarationNodeVisitor.cs @@ -877,7 +877,11 @@ public override async Task 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(); var postBodyStatements = new List(); diff --git a/CodeConverter/CSharp/ExpressionNodeVisitor.cs b/CodeConverter/CSharp/ExpressionNodeVisitor.cs index 2a5dcc631..60353c672 100644 --- a/CodeConverter/CSharp/ExpressionNodeVisitor.cs +++ b/CodeConverter/CSharp/ExpressionNodeVisitor.cs @@ -1011,7 +1011,7 @@ public override async Task VisitIdentifierName(VBasic.Syntax.I if (sym != null && sym.Kind == SymbolKind.Local) { var vbMethodBlock = node.Ancestors().OfType().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); diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index 2a9318f2f..da04f4ac8 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -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 { @@ -348,12 +349,16 @@ public override async Task> 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()); } diff --git a/CodeConverter/CSharp/VbMethodSyntaxExtensions.cs b/CodeConverter/CSharp/VbMethodSyntaxExtensions.cs index 90571d795..4b322ebd2 100644 --- a/CodeConverter/CSharp/VbMethodSyntaxExtensions.cs +++ b/CodeConverter/CSharp/VbMethodSyntaxExtensions.cs @@ -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) + /// + /// Use in conjunction with + /// + 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().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().PropertyStatement : node.BlockStatement; } } } diff --git a/CodeConverter/Util/IMethodSymbolExtensions.cs b/CodeConverter/Util/IMethodSymbolExtensions.cs index 76a6cef6b..ba18a6232 100644 --- a/CodeConverter/Util/IMethodSymbolExtensions.cs +++ b/CodeConverter/Util/IMethodSymbolExtensions.cs @@ -1,4 +1,5 @@ using System.Linq; +using ICSharpCode.CodeConverter.Util.FromRoslyn; using Microsoft.CodeAnalysis; namespace ICSharpCode.CodeConverter.Util @@ -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; + } } } \ No newline at end of file diff --git a/Tests/CSharp/MemberTests.cs b/Tests/CSharp/MemberTests.cs index 8f1460ad8..d0a258dd0 100644 --- a/Tests/CSharp/MemberTests.cs +++ b/Tests/CSharp/MemberTests.cs @@ -2191,6 +2191,73 @@ public async void AsyncSub() "); } + [Fact] + public async Task TestAsyncMethodsWithNoReturn() + { + await TestConversionVisualBasicToCSharp( +@"Friend Partial Module TaskExtensions + + 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 + + + Async Function [Then](ByVal task As Task, ByVal f As Func(Of Task)) As Task + Await task + Await f() + End Function + + + 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 + + + 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 + + + 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 Then(this Task task, Func> f) + { + await task; + return await f(); + } + + public async static Task Then(this Task task, Func f) + { + await task; + await f(); + } + + public async static Task Then(this Task task, Func> f) + { + return await f(await task); + } + + public async static Task Then(this Task task, Func f) + { + await f(await task); + } + + public async static Task ThenExit(this Task task, Func f) + { + await f(await task); + return; + } +}"); + } + [Fact] public async Task TestExternDllImport() {