Skip to content

Commit

Permalink
Type convert lambdas to a concrete delegate type if needed - fixes #611
Browse files Browse the repository at this point in the history
  • Loading branch information
GrahamTheCoder committed Aug 23, 2020
1 parent 04d3409 commit 8cc96f2
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 6 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)


### VB -> C#

* Type convert within compound assignments [#612](https://github.com/icsharpcode/CodeConverter/issues/612)
* Type convert lambdas to a concrete delegate type if needed [#611](https://github.com/icsharpcode/CodeConverter/issues/611)

### C# -> VB

Expand All @@ -29,7 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
* Add type conversion where needed for externally declared loop control variable [#609](https://github.com/icsharpcode/CodeConverter/issues/609)
* Convert string operators in common cases [#608](https://github.com/icsharpcode/CodeConverter/issues/608)
* Type convert parameterized property in assignment [#610](https://github.com/icsharpcode/CodeConverter/issues/610)
* Type convert within compound assignments [#612](https://github.com/icsharpcode/CodeConverter/issues/612)

### C# -> VB

## [8.1.6] - 2020-07-12
Expand Down
46 changes: 42 additions & 4 deletions CodeConverter/CSharp/TypeConversionAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@
using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using SyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind;
using TypeSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Linq.Expressions;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
using CSS = Microsoft.CodeAnalysis.CSharp.Syntax;

namespace ICSharpCode.CodeConverter.CSharp
{
Expand Down Expand Up @@ -105,6 +103,8 @@ private ExpressionSyntax AddTypeConversion(VBSyntax.ExpressionSyntax vbNode, Exp
var memberAccessExpressionSyntax = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, csNode, SyntaxFactory.IdentifierName(nameof(string.ToCharArray)));
return SyntaxFactory.InvocationExpression(memberAccessExpressionSyntax,
SyntaxFactory.ArgumentList());
case TypeConversionKind.DelegateConstructor:
return Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ObjectCreationExpression(GetCommonDelegateTypeOrNull(vbNode, vbConvertedType)).WithArgumentList(new[]{csNode}.CreateCsArgList());
default:
throw new ArgumentOutOfRangeException();
}
Expand All @@ -125,11 +125,16 @@ public TypeConversionKind AnalyzeConversion(Microsoft.CodeAnalysis.VisualBasic.S
var typeInfo = ModelExtensions.GetTypeInfo(_semanticModel, vbNode);
var vbType = forceSourceType ?? typeInfo.Type;
var vbConvertedType = forceTargetType ?? typeInfo.ConvertedType;
if (vbType is null || vbConvertedType is null)

if (vbConvertedType is null)
{
return TypeConversionKind.Unknown;
}

if (vbType is null) {
return GetCommonDelegateTypeOrNull(vbNode, vbConvertedType) is {} ? TypeConversionKind.DelegateConstructor : TypeConversionKind.Unknown;
}

if (vbType.IsEnumType()) {
if (vbConvertedType.IsNumericType()) {
return TypeConversionKind.NonDestructiveCast;
Expand Down Expand Up @@ -157,6 +162,38 @@ public TypeConversionKind AnalyzeConversion(Microsoft.CodeAnalysis.VisualBasic.S
return AnalyzeVbConversion(alwaysExplicit, vbType, vbConvertedType, vbConversion);
}

private CSS.NameSyntax GetCommonDelegateTypeOrNull(VBSyntax.ExpressionSyntax vbNode, ITypeSymbol vbConvertedType)
{
if (vbNode.SkipIntoParens() is VBSyntax.LambdaExpressionSyntax vbLambda &&
vbConvertedType.Name == nameof(Delegate) &&
_semanticModel.GetSymbolInfo(vbLambda).Symbol is IMethodSymbol lambdaSymbol)
{
return CreateCommonDelegateTypeSyntax(lambdaSymbol);
}

return null;
}

private CSS.NameSyntax CreateCommonDelegateTypeSyntax(IMethodSymbol vbLambda)
{
var parameters = vbLambda.Parameters
.Select(p => _csSyntaxGenerator.TypeExpression(p.Type));

if (vbLambda.ReturnType.IsSystemVoid()) {
return CreateType("Action", parameters);
}

var typeExpression = _csSyntaxGenerator.TypeExpression(vbLambda.ReturnType);
return CreateType("Func", parameters.Concat(typeExpression));
}

private static CSS.NameSyntax CreateType(string baseTypeName, IEnumerable<SyntaxNode> parameters)
{
var parameterList = SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(parameters));
if (!parameterList.Arguments.Any()) return SyntaxFactory.IdentifierName(baseTypeName);
return SyntaxFactory.GenericName(SyntaxFactory.Identifier(baseTypeName), parameterList);
}

private ITypeSymbol GetCSType(ITypeSymbol vbType, VBSyntax.ExpressionSyntax vbNode = null)
{
// C# does not have literals for short/ushort, so the actual type here is integer
Expand Down Expand Up @@ -323,6 +360,7 @@ public enum TypeConversionKind
EnumCastThenConversion,
NullableBool,
StringToCharArray,
DelegateConstructor
}

public static bool ConvertStringToCharLiteral(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax node,
Expand Down
47 changes: 47 additions & 0 deletions Tests/CSharp/ExpressionTests/ExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,53 @@ private void TestMethod()
}");
}

[Fact]
public async Task AnonymousLambdaTypeConversionAsync()
{
await TestConversionVisualBasicToCSharpAsync(@"Public Class AnonymousLambdaTypeConversionTest
Public Sub CallThing(thingToCall As [Delegate])
End Sub
Public Sub SomeMethod()
End Sub
Public Sub Foo()
CallThing(Sub()
SomeMethod()
End Sub)
CallThing(Sub(a) SomeMethod())
CallThing(Function()
SomeMethod()
Return False
End Function)
CallThing(Function(a) False)
End Sub
End Class", @"using System;
public partial class AnonymousLambdaTypeConversionTest
{
public void CallThing(Delegate thingToCall)
{
}
public void SomeMethod()
{
}
public void Foo()
{
CallThing(new Action(() => SomeMethod()));
CallThing(new Action<object>(a => SomeMethod()));
CallThing(new Func<bool>(() =>
{
SomeMethod();
return false;
}));
CallThing(new Func<object, bool>(a => false));
}
}");
}

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

0 comments on commit 8cc96f2

Please sign in to comment.