Skip to content

Commit f721d73

Browse files
Handle type mismatches, constants, instance expressions
1 parent 8ffe7c5 commit f721d73

File tree

5 files changed

+56
-46
lines changed

5 files changed

+56
-46
lines changed

CodeConverter/CSharp/AdditionalAssignment.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ namespace ICSharpCode.CodeConverter.CSharp
44
{
55
internal class AdditionalAssignment : IHoistedNode
66
{
7-
public AdditionalAssignment(IdentifierNameSyntax identifierName, ExpressionSyntax expression)
7+
public AdditionalAssignment(ExpressionSyntax lhs, ExpressionSyntax rhs)
88
{
9-
IdentifierName = identifierName ?? throw new System.ArgumentNullException(nameof(identifierName));
10-
Expression = expression ?? throw new System.ArgumentNullException(nameof(expression));
9+
RightHandSide = rhs ?? throw new System.ArgumentNullException(nameof(rhs));
10+
LeftHandSide = lhs ?? throw new System.ArgumentNullException(nameof(lhs));
1111
}
1212

13-
public ExpressionSyntax Expression { get; set; }
14-
public IdentifierNameSyntax IdentifierName { get; }
13+
public ExpressionSyntax LeftHandSide { get; set; }
14+
public ExpressionSyntax RightHandSide { get; }
1515
}
1616
}

CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -407,26 +407,38 @@ public override async Task<CSharpSyntaxNode> VisitSimpleArgument(VBasic.Syntax.S
407407
return await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor);
408408
var symbol = GetInvocationSymbol(invocation);
409409
SyntaxToken token = default(SyntaxToken);
410-
string argName = null;
411-
RefKind refKind = RefKind.None;
412-
AdditionalDeclaration local = null;
410+
var convertedExpression = (ExpressionSyntax)await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor);
413411
if (symbol is IMethodSymbol methodSymbol) {
414412
var parameters = (CommonConversions.GetCsOriginalSymbolOrNull(methodSymbol.OriginalDefinition) ?? methodSymbol).GetParameters();
415-
var refType = GetRefType(node, argList, parameters, out argName, out refKind);
416-
var expression = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, (ExpressionSyntax)await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor), defaultToCast: refKind != RefKind.None);
413+
var refType = GetRefConversionType(node, argList, parameters, out var argName, out var refKind);
414+
var convertedExpressionWithoutCast = convertedExpression;
415+
convertedExpression = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, convertedExpression, defaultToCast: refKind != RefKind.None);
417416

418-
string prefix = $"arg{argName}";
419417
if (refType != RefConversion.Inline) {
418+
string prefix = $"arg{argName}";
420419
var expressionTypeInfo = _semanticModel.GetTypeInfo(node.Expression);
421420
bool useVar = expressionTypeInfo.Type?.Equals(expressionTypeInfo.ConvertedType) == true && !CommonConversions.ShouldPreferExplicitType(node.Expression, expressionTypeInfo.ConvertedType, out var _);
422421
var typeSyntax = CommonConversions.GetTypeSyntax(expressionTypeInfo.ConvertedType, useVar);
423-
local = _additionalLocals.Hoist(new AdditionalDeclaration(prefix, expression, typeSyntax));
424-
}
425-
if (refType == RefConversion.PreAndPostAssignment) {
426-
_additionalLocals.Hoist(new AdditionalAssignment(local.IdentifierName, expression));
422+
var local = _additionalLocals.Hoist(new AdditionalDeclaration(prefix, convertedExpression, typeSyntax));
423+
424+
if (refType == RefConversion.PreAndPostAssignment) {
425+
var convertedLocalIdentifier = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, local.IdentifierName, forceSourceType: expressionTypeInfo.ConvertedType, forceTargetType: expressionTypeInfo.Type);
426+
_additionalLocals.Hoist(new AdditionalAssignment(convertedExpressionWithoutCast, convertedLocalIdentifier));
427+
}
428+
convertedExpression = local.IdentifierName;
427429
}
430+
token = GetRefToken(refKind);
431+
} else {
432+
convertedExpression = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, convertedExpression);
428433
}
429434

435+
var nameColon = node.IsNamed ? SyntaxFactory.NameColon((IdentifierNameSyntax)await node.NameColonEquals.Name.AcceptAsync(TriviaConvertingExpressionVisitor)) : null;
436+
return SyntaxFactory.Argument(nameColon, token, convertedExpression);
437+
}
438+
439+
private static SyntaxToken GetRefToken(RefKind refKind)
440+
{
441+
SyntaxToken token;
430442
switch (refKind) {
431443
case RefKind.None:
432444
token = default(SyntaxToken);
@@ -440,22 +452,11 @@ public override async Task<CSharpSyntaxNode> VisitSimpleArgument(VBasic.Syntax.S
440452
default:
441453
throw new ArgumentOutOfRangeException();
442454
}
443-
var simpleExpression = await ConvertArgExpression(node, refKind);
444455

445-
var nameColon = node.IsNamed ? SyntaxFactory.NameColon((IdentifierNameSyntax)await node.NameColonEquals.Name.AcceptAsync(TriviaConvertingExpressionVisitor)) : null;
446-
if (local == null) {
447-
return SyntaxFactory.Argument(nameColon, token, simpleExpression);
448-
} else {
449-
return SyntaxFactory.Argument(nameColon, token, local.IdentifierName);
450-
}
456+
return token;
451457
}
452458

453-
private async Task<ExpressionSyntax> ConvertArgExpression(VBSyntax.SimpleArgumentSyntax node, RefKind refKind)
454-
{
455-
return CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, (ExpressionSyntax)await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor), defaultToCast: refKind != RefKind.None);
456-
}
457-
458-
private RefConversion GetRefType(VBSyntax.ArgumentSyntax node, VBSyntax.ArgumentListSyntax argList, System.Collections.Immutable.ImmutableArray<IParameterSymbol> parameters, out string argName, out RefKind refKind)
459+
private RefConversion GetRefConversionType(VBSyntax.ArgumentSyntax node, VBSyntax.ArgumentListSyntax argList, System.Collections.Immutable.ImmutableArray<IParameterSymbol> parameters, out string argName, out RefKind refKind)
459460
{
460461
var parameter = node.IsNamed && node is VBSyntax.SimpleArgumentSyntax sas
461462
? parameters.FirstOrDefault(p => p.Name.Equals(sas.NameColonEquals.Name.Identifier.Text, StringComparison.OrdinalIgnoreCase))
@@ -918,11 +919,10 @@ async Task<CSharpSyntaxNode> CreateElementAccess()
918919

919920

920921
/// <summary>
921-
/// If we need a local function,
922+
/// The VB compiler actually just hoists the conditions within the same method, but that leads to the original logic looking very different.
923+
/// This should be equivalent but keep closer to the look of the original source code.
924+
/// See https://github.com/icsharpcode/CodeConverter/issues/310 and https://github.com/icsharpcode/CodeConverter/issues/324
922925
/// </summary>
923-
/// <param name="invocation"></param>
924-
/// <param name="invocationSymbol"></param>
925-
/// <returns></returns>
926926
private async Task<InvocationExpressionSyntax> HoistAndCallLocalFunction(VBSyntax.InvocationExpressionSyntax invocation, IMethodSymbol invocationSymbol, ExpressionSyntax csExpression)
927927
{
928928
const string retVariableName = "ret";
@@ -945,8 +945,17 @@ private async Task<InvocationExpressionSyntax> HoistAndCallLocalFunction(VBSynta
945945
private bool RequiresLocalFunction(VBSyntax.InvocationExpressionSyntax invocation, IMethodSymbol invocationSymbol)
946946
{
947947
if (invocation.ArgumentList == null || IsDefinitelyExecutedInStatement(invocation)) return false;
948-
return invocation.ArgumentList.Arguments
949-
.Any(a => RefConversion.Inline != GetRefType(a, invocation.ArgumentList, invocationSymbol.Parameters, out var argName, out var refKind));
948+
return invocation.ArgumentList.Arguments.Any(a => RequiresLocalFunction(invocation, invocationSymbol, a));
949+
950+
bool RequiresLocalFunction(VBSyntax.InvocationExpressionSyntax invocation, IMethodSymbol invocationSymbol, VBSyntax.ArgumentSyntax a)
951+
{
952+
var refConversion = GetRefConversionType(a, invocation.ArgumentList, invocationSymbol.Parameters, out var argName, out var refKind);
953+
if (RefConversion.Inline == refConversion) return false;
954+
if (!(a is VBSyntax.SimpleArgumentSyntax sas)) return false;
955+
var argExpression = sas.Expression.SkipParens();
956+
if (argExpression is VBSyntax.InstanceExpressionSyntax) return false;
957+
return !_semanticModel.GetConstantValue(argExpression).HasValue;
958+
}
950959
}
951960

952961
private static bool IsDefinitelyExecutedInStatement(VBSyntax.InvocationExpressionSyntax invocation)
@@ -961,8 +970,6 @@ private static bool IsDefinitelyExecutedInStatement(VBSyntax.InvocationExpressio
961970
/// <summary>
962971
/// It'd be great to use _semanticModel.AnalyzeControlFlow(invocation).ExitPoints, but that doesn't account for the possibility of exceptions
963972
/// </summary>
964-
/// <param name="n"></param>
965-
/// <returns></returns>
966973
private static SyntaxNode GetLeftMostWithPossibleExitPoints(SyntaxNode n) => n switch
967974
{
968975
VBSyntax.VariableDeclaratorSyntax vds => vds.Initializer,

CodeConverter/CSharp/HoistedNodeState.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public async Task<SyntaxList<StatementSyntax>> CreateLocals(VBasic.VisualBasicSy
9191
}
9292

9393
foreach (var additionalAssignment in GetPostAssignments()) {
94-
var assign = CS.SyntaxFactory.AssignmentExpression(CS.SyntaxKind.SimpleAssignmentExpression, additionalAssignment.Expression, additionalAssignment.IdentifierName);
94+
var assign = CS.SyntaxFactory.AssignmentExpression(CS.SyntaxKind.SimpleAssignmentExpression, additionalAssignment.LeftHandSide, additionalAssignment.RightHandSide);
9595
postAssignments.Add(CS.SyntaxFactory.ExpressionStatement(assign));
9696
}
9797

CodeConverter/CSharp/TypeConversionAnalyzer.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,20 @@ public TypeConversionAnalyzer(SemanticModel semanticModel, CSharpCompilation csC
4646
_expressionEvaluator = expressionEvaluator;
4747
}
4848

49-
public ExpressionSyntax AddExplicitConversion(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, bool addParenthesisIfNeeded = true, bool defaultToCast = false, bool isConst = false, ITypeSymbol forceTargetType = null)
49+
public ExpressionSyntax AddExplicitConversion(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, bool addParenthesisIfNeeded = true, bool defaultToCast = false, bool isConst = false, ITypeSymbol forceSourceType = null, ITypeSymbol forceTargetType = null)
5050
{
5151
if (csNode == null) return null;
52-
var conversionKind = AnalyzeConversion(vbNode, defaultToCast, isConst, forceTargetType);
52+
var conversionKind = AnalyzeConversion(vbNode, defaultToCast, isConst, forceSourceType, forceTargetType);
5353
csNode = addParenthesisIfNeeded && (conversionKind == TypeConversionKind.DestructiveCast || conversionKind == TypeConversionKind.NonDestructiveCast)
5454
? VbSyntaxNodeExtensions.ParenthesizeIfPrecedenceCouldChange(vbNode, csNode)
5555
: csNode;
56-
return AddExplicitConversion(vbNode, csNode, conversionKind, addParenthesisIfNeeded, isConst, forceTargetType: forceTargetType);
56+
return AddExplicitConversion(vbNode, csNode, conversionKind, addParenthesisIfNeeded, isConst, forceSourceType: forceSourceType, forceTargetType: forceTargetType);
5757
}
5858

59-
public ExpressionSyntax AddExplicitConversion(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, TypeConversionKind conversionKind, bool addParenthesisIfNeeded = false, bool isConst = false, ITypeSymbol forceTargetType = null)
59+
public ExpressionSyntax AddExplicitConversion(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, TypeConversionKind conversionKind, bool addParenthesisIfNeeded = false, bool isConst = false, ITypeSymbol forceSourceType = null, ITypeSymbol forceTargetType = null)
6060
{
6161
var typeInfo = ModelExtensions.GetTypeInfo(_semanticModel, vbNode);
62-
var vbType = typeInfo.Type;
62+
var vbType = forceSourceType ?? typeInfo.Type;
6363
var vbConvertedType = forceTargetType ?? typeInfo.ConvertedType;
6464

6565
if (isConst && _expressionEvaluator.GetConstantOrNull(vbNode, vbConvertedType, csNode) is ExpressionSyntax constLiteral) {
@@ -98,10 +98,10 @@ private ExpressionSyntax CreateCast(ExpressionSyntax csNode, ITypeSymbol vbConve
9898
return ValidSyntaxFactory.CastExpression(typeName, csNode);
9999
}
100100

101-
public TypeConversionKind AnalyzeConversion(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax vbNode, bool alwaysExplicit = false, bool isConst = false, ITypeSymbol forceTargetType = null)
101+
public TypeConversionKind AnalyzeConversion(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax vbNode, bool alwaysExplicit = false, bool isConst = false, ITypeSymbol forceSourceType = null, ITypeSymbol forceTargetType = null)
102102
{
103103
var typeInfo = ModelExtensions.GetTypeInfo(_semanticModel, vbNode);
104-
var vbType = typeInfo.Type;
104+
var vbType = forceSourceType ?? typeInfo.Type;
105105
var vbConvertedType = forceTargetType ?? typeInfo.ConvertedType;
106106
if (vbType is null || vbConvertedType is null)
107107
{

Tests/CSharp/ExpressionTests/ByRefTests.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,10 @@ public void Foo()
102102
Bar(ref argclass1);
103103
object argclass11 = C1;
104104
Bar(ref argclass11);
105+
C1 = (Class1)argclass11;
105106
object argclass12 = C1;
106107
Bar(ref argclass12);
108+
C1 = (Class1)argclass12;
107109
object argclass13 = _c2;
108110
Bar(ref argclass13);
109111
object argclass14 = _c2;
@@ -359,14 +361,15 @@ Public Sub UsesRef(someBool As Boolean, someInt As Integer)
359361
360362
If 16 > someInt OrElse TakesRef(someInt) ' Convert directly
361363
Console.WriteLine(1)
362-
Else If someBool AndAlso TakesRef(3) 'Requires variable before (in local function)
364+
Else If someBool AndAlso TakesRef(3 * a) 'Requires variable before (in local function)
363365
someInt += 1
364366
Else If TakesRef(Prop) ' Requires variable before, and to assign back after (in local function)
365367
someInt -=2
366368
End If
367369
Console.WriteLine(someInt)
368370
End Sub
369371
End Class", @"using System;
372+
using Microsoft.VisualBasic.CompilerServices;
370373
371374
public partial class MyTestClass
372375
{
@@ -398,7 +401,7 @@ public void UsesRef(bool someBool, int someInt)
398401
int argvrbTst3 = Prop;
399402
bool c = TakesRef(ref argvrbTst3);
400403
Prop = argvrbTst3; // Requires variable before, and to assign back after
401-
bool localTakesRef() { int argvrbTst = 3; var ret = TakesRef(ref argvrbTst); return ret; }
404+
bool localTakesRef() { int argvrbTst = 3 * Conversions.ToInteger(a); var ret = TakesRef(ref argvrbTst); return ret; }
402405
403406
bool localTakesRef1() { int argvrbTst = Prop; var ret = TakesRef(ref argvrbTst); Prop = argvrbTst; return ret; }
404407

0 commit comments

Comments
 (0)