diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a2cdfcc7..436c93296 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Add parentheses around ternary statement - [#565](https://github.com/icsharpcode/CodeConverter/issues/565) * When converting ForEach loop, avoid duplicate variable compilation issue [#558](https://github.com/icsharpcode/CodeConverter/issues/558) * Improvements to for loop with missing semantic info - [#482](https://github.com/icsharpcode/CodeConverter/issues/482) +* Fix logic issue when converting property passed byref - [#324](https://github.com/icsharpcode/CodeConverter/issues/324) +* Fix logic issue when converting expression passed in byref within conditional expression - [#310](https://github.com/icsharpcode/CodeConverter/issues/310) ### C# -> VB diff --git a/CodeConverter/CSharp/AdditionalAssignment.cs b/CodeConverter/CSharp/AdditionalAssignment.cs new file mode 100644 index 000000000..749e6cb62 --- /dev/null +++ b/CodeConverter/CSharp/AdditionalAssignment.cs @@ -0,0 +1,16 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ICSharpCode.CodeConverter.CSharp +{ + internal class AdditionalAssignment : IHoistedNode + { + public AdditionalAssignment(ExpressionSyntax lhs, ExpressionSyntax rhs) + { + RightHandSide = rhs ?? throw new System.ArgumentNullException(nameof(rhs)); + LeftHandSide = lhs ?? throw new System.ArgumentNullException(nameof(lhs)); + } + + public ExpressionSyntax LeftHandSide { get; set; } + public ExpressionSyntax RightHandSide { get; } + } +} \ No newline at end of file diff --git a/CodeConverter/CSharp/AdditionalLocal.cs b/CodeConverter/CSharp/AdditionalDeclaration.cs similarity index 67% rename from CodeConverter/CSharp/AdditionalLocal.cs rename to CodeConverter/CSharp/AdditionalDeclaration.cs index 45964373a..ec13e449b 100644 --- a/CodeConverter/CSharp/AdditionalLocal.cs +++ b/CodeConverter/CSharp/AdditionalDeclaration.cs @@ -1,18 +1,20 @@ using System; +using System.Collections.Generic; +using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace ICSharpCode.CodeConverter.CSharp { - public class AdditionalLocal + internal class AdditionalDeclaration : IHoistedNode { public string Prefix { get; } public string Id { get; } public ExpressionSyntax Initializer { get; } public TypeSyntax Type { get; } - public AdditionalLocal(string prefix, ExpressionSyntax initializer, TypeSyntax type) + public AdditionalDeclaration(string prefix, ExpressionSyntax initializer, TypeSyntax type) { Prefix = prefix; Id = $"ph{Guid.NewGuid().ToString("N")}"; @@ -20,6 +22,6 @@ public AdditionalLocal(string prefix, ExpressionSyntax initializer, TypeSyntax t Type = type; } - public IdentifierNameSyntax IdentifierName => SyntaxFactory.IdentifierName(Id).WithAdditionalAnnotations(AdditionalLocals.Annotation); + public IdentifierNameSyntax IdentifierName => SyntaxFactory.IdentifierName(Id).WithAdditionalAnnotations(HoistedNodeState.Annotation); } } diff --git a/CodeConverter/CSharp/AdditionalLocals.cs b/CodeConverter/CSharp/AdditionalLocals.cs deleted file mode 100644 index 26ed1ff5d..000000000 --- a/CodeConverter/CSharp/AdditionalLocals.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using Microsoft.CodeAnalysis; - -namespace ICSharpCode.CodeConverter.CSharp -{ - public class AdditionalLocals : IEnumerable> - { - public static SyntaxAnnotation Annotation = new SyntaxAnnotation("CodeconverterAdditionalLocal"); - - private readonly Stack> _additionalLocals; - - public AdditionalLocals() - { - _additionalLocals = new Stack>(); - } - - public void PushScope() - { - _additionalLocals.Push(new Dictionary()); - } - - public void PopScope() - { - _additionalLocals.Pop(); - } - - public AdditionalLocal AddAdditionalLocal(AdditionalLocal additionalLocal) - { - _additionalLocals.Peek().Add(additionalLocal.Id, additionalLocal); - return additionalLocal; - } - - public IEnumerator> GetEnumerator() - { - return _additionalLocals.Peek().GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _additionalLocals.Peek().GetEnumerator(); - } - - public AdditionalLocal this[string id] { - get { - return _additionalLocals.Peek()[id]; - } - } - } -} diff --git a/CodeConverter/CSharp/CommonConversions.cs b/CodeConverter/CSharp/CommonConversions.cs index 44907c3bb..a47076970 100644 --- a/CodeConverter/CSharp/CommonConversions.cs +++ b/CodeConverter/CSharp/CommonConversions.cs @@ -508,6 +508,11 @@ public static AttributeArgumentListSyntax CreateAttributeArgumentList(params Att return SyntaxFactory.AttributeArgumentList(SyntaxFactory.SeparatedList(attributeArgumentSyntaxs)); } + public static CSSyntax.LocalDeclarationStatementSyntax CreateLocalVariableDeclarationAndAssignment(string variableName, ExpressionSyntax initValue) + { + return SyntaxFactory.LocalDeclarationStatement(CreateVariableDeclarationAndAssignment(variableName, initValue)); + } + public static VariableDeclarationSyntax CreateVariableDeclarationAndAssignment(string variableName, ExpressionSyntax initValue, TypeSyntax explicitType = null) { diff --git a/CodeConverter/CSharp/DeclarationNodeVisitor.cs b/CodeConverter/CSharp/DeclarationNodeVisitor.cs index ad8690eb6..d8a035f7c 100644 --- a/CodeConverter/CSharp/DeclarationNodeVisitor.cs +++ b/CodeConverter/CSharp/DeclarationNodeVisitor.cs @@ -29,7 +29,6 @@ internal class DeclarationNodeVisitor : VBasic.VisualBasicSyntaxVisitor _additionalDeclarations = new Dictionary(); private readonly AdditionalInitializers _additionalInitializers; - private readonly AdditionalLocals _additionalLocals = new AdditionalLocals(); + private readonly HoistedNodeState _additionalLocals = new HoistedNodeState(); private uint _failedMemberConversionMarkerCount; private readonly HashSet _extraUsingDirectives = new HashSet(); private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison; @@ -490,7 +489,7 @@ private IEnumerable CreateMemberDeclarations(IReadOnlyC foreach (var f in fieldDecls) yield return f; } else { - if (_additionalLocals.Count() > 0) { + if (_additionalLocals.GetDeclarations().Count() > 0) { foreach (var additionalDecl in CreateAdditionalLocalMembers(convertedModifiers, attributes, decl)) { yield return additionalDecl; } @@ -547,26 +546,19 @@ private IEnumerable CreateAdditionalLocalMembers(Syntax var methodName = invocationExpressionSyntax.Expression .ChildNodes().OfType().Last(); var newMethodName = $"{methodName.Identifier.ValueText}_{v.Identifier.ValueText}"; - var localVars = _additionalLocals.Select(l => l.Value) - .Select(al => - SyntaxFactory.LocalDeclarationStatement( - CommonConversions.CreateVariableDeclarationAndAssignment(al.Prefix, al.Initializer))) - .Cast().ToList(); - var newInitializer = v.Initializer.Value.ReplaceNodes( - v.Initializer.Value.GetAnnotatedNodes(AdditionalLocals.Annotation), (an, _) => { - // This should probably use a unique name like in MethodBodyVisitor - a collision is far less likely here - var id = ((IdentifierNameSyntax)an).Identifier.ValueText; - return SyntaxFactory.IdentifierName(_additionalLocals[id].Prefix); - }); - var body = SyntaxFactory.Block( - localVars.Concat(SyntaxFactory.SingletonList(SyntaxFactory.ReturnStatement(newInitializer)))); - var methodAttrs = SyntaxFactory.List(); + var declarationInfo = _additionalLocals.GetDeclarations(); + + var localVars = declarationInfo + .Select(al => CommonConversions.CreateLocalVariableDeclarationAndAssignment(al.Prefix, al.Initializer)) + .ToArray(); + + // This should probably use a unique name like in MethodBodyVisitor - a collision is far less likely here + var newNames = declarationInfo.ToDictionary(l => l.Id, l => l.Prefix); + var newInitializer = HoistedNodeState.ReplaceNames(v.Initializer.Value, newNames); + + var body = SyntaxFactory.Block(localVars.Concat(SyntaxFactory.ReturnStatement(newInitializer).Yield())); // Method calls in initializers must be static in C# - Supporting this is #281 - var modifiers = SyntaxFactory.TokenList(SyntaxFactory.Token(Microsoft.CodeAnalysis.CSharp.SyntaxKind.StaticKeyword)); - var typeConstraints = SyntaxFactory.List(); - var parameterList = SyntaxFactory.ParameterList(); - var methodDecl = SyntaxFactory.MethodDeclaration(methodAttrs, modifiers, decl.Type, null, - SyntaxFactory.Identifier(newMethodName), null, parameterList, typeConstraints, body, null); + var methodDecl = CreateParameterlessMethod(newMethodName, decl.Type, body); yield return methodDecl; var newVar = @@ -578,6 +570,17 @@ private IEnumerable CreateAdditionalLocalMembers(Syntax yield return SyntaxFactory.FieldDeclaration(SyntaxFactory.List(attributes), convertedModifiers, newVarDecl); } + private static MethodDeclarationSyntax CreateParameterlessMethod(string newMethodName, TypeSyntax type, BlockSyntax body) + { + var modifiers = SyntaxFactory.TokenList(SyntaxFactory.Token(Microsoft.CodeAnalysis.CSharp.SyntaxKind.StaticKeyword)); + var typeConstraints = SyntaxFactory.List(); + var parameterList = SyntaxFactory.ParameterList(); + var methodAttrs = SyntaxFactory.List(); + var methodDecl = SyntaxFactory.MethodDeclaration(methodAttrs, modifiers, type, null, + SyntaxFactory.Identifier(newMethodName), null, parameterList, typeConstraints, body, null); + return methodDecl; + } + private List GetMethodWithHandles(VBSyntax.TypeBlockSyntax parentType) { if (parentType == null || !(this._semanticModel.GetDeclaredSymbol((global::Microsoft.CodeAnalysis.SyntaxNode)parentType) is ITypeSymbol containingType)) return new List(); @@ -1077,7 +1080,7 @@ public override async Task VisitEventBlock(VBSyntax.EventBlock var attributes = await block.AttributeLists.SelectManyAsync(CommonConversions.ConvertAttribute); var modifiers = CommonConversions.ConvertModifiers(block, block.Modifiers, GetMemberContext(node)); - var rawType = (TypeSyntax) await (block.AsClause?.Type).AcceptAsync(_triviaConvertingExpressionVisitor) ?? VarType; + var rawType = (TypeSyntax) await (block.AsClause?.Type).AcceptAsync(_triviaConvertingExpressionVisitor) ?? ValidSyntaxFactory.VarType; var convertedAccessors = await node.Accessors.SelectAsync(async a => await a.AcceptAsync(TriviaConvertingDeclarationVisitor)); _additionalDeclarations.Add(node, convertedAccessors.OfType().ToArray()); diff --git a/CodeConverter/CSharp/ExpressionNodeVisitor.cs b/CodeConverter/CSharp/ExpressionNodeVisitor.cs index e395dd4d1..4990fb61c 100644 --- a/CodeConverter/CSharp/ExpressionNodeVisitor.cs +++ b/CodeConverter/CSharp/ExpressionNodeVisitor.cs @@ -5,6 +5,7 @@ using ICSharpCode.CodeConverter.Shared; using ICSharpCode.CodeConverter.Util; using ICSharpCode.CodeConverter.Util.FromRoslyn; +using ICSharpCode.CodeConverter.VB; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -34,7 +35,7 @@ internal class ExpressionNodeVisitor : VBasic.VisualBasicSyntaxVisitor _withBlockLhs = new Stack(); - private readonly AdditionalLocals _additionalLocals; + private readonly HoistedNodeState _additionalLocals; private readonly MethodsWithHandles _methodsWithHandles; private readonly QueryConverter _queryConverter; private readonly Lazy> _convertMethodsLookupByReturnType; @@ -43,7 +44,7 @@ internal class ExpressionNodeVisitor : VBasic.VisualBasicSyntaxVisitor extraUsingDirectives) { @@ -406,44 +407,93 @@ public override async Task VisitSimpleArgument(VBasic.Syntax.S return await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); var symbol = GetInvocationSymbol(invocation); SyntaxToken token = default(SyntaxToken); - string argName = null; - RefKind refKind = RefKind.None; + var convertedArgExpression = ((ExpressionSyntax)await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor)).SkipParens(); + var typeConversionAnalyzer = CommonConversions.TypeConversionAnalyzer; if (symbol is IMethodSymbol methodSymbol) { var parameters = (CommonConversions.GetCsOriginalSymbolOrNull(methodSymbol.OriginalDefinition) ?? methodSymbol).GetParameters(); - var parameter = !node.IsNamed ? parameters.ElementAtOrDefault(argList.Arguments.IndexOf(node)) : parameters.FirstOrDefault(p => p.Name.Equals(node.NameColonEquals.Name.Identifier.Text, StringComparison.OrdinalIgnoreCase)); - if (parameter != null) { - refKind = parameter.RefKind; - argName = parameter.Name; - } - switch (refKind) { - case RefKind.None: - token = default(SyntaxToken); - break; - case RefKind.Ref: - token = SyntaxFactory.Token(SyntaxKind.RefKeyword); - break; - case RefKind.Out: - token = SyntaxFactory.Token(SyntaxKind.OutKeyword); - break; - default: - throw new ArgumentOutOfRangeException(); + var refType = GetRefConversionType(node, argList, parameters, out var argName, out var refKind); + token = GetRefToken(refKind); + if (refType != RefConversion.Inline) { + convertedArgExpression = HoistByRefDeclaration(node, convertedArgExpression, refType, argName, refKind); + } else { + convertedArgExpression = typeConversionAnalyzer.AddExplicitConversion(node.Expression, convertedArgExpression, defaultToCast: refKind != RefKind.None); } + } else { + convertedArgExpression = typeConversionAnalyzer.AddExplicitConversion(node.Expression, convertedArgExpression); + } + + var nameColon = node.IsNamed ? SyntaxFactory.NameColon((IdentifierNameSyntax)await node.NameColonEquals.Name.AcceptAsync(TriviaConvertingExpressionVisitor)) : null; + return SyntaxFactory.Argument(nameColon, token, convertedArgExpression); + } + + private ExpressionSyntax HoistByRefDeclaration(VBSyntax.SimpleArgumentSyntax node, ExpressionSyntax refLValue, RefConversion refType, string argName, RefKind refKind) + { + string prefix = $"arg{argName}"; + var expressionTypeInfo = _semanticModel.GetTypeInfo(node.Expression); + bool useVar = expressionTypeInfo.Type?.Equals(expressionTypeInfo.ConvertedType) == true && !CommonConversions.ShouldPreferExplicitType(node.Expression, expressionTypeInfo.ConvertedType, out var _); + var typeSyntax = CommonConversions.GetTypeSyntax(expressionTypeInfo.ConvertedType, useVar); + + if (refLValue is ElementAccessExpressionSyntax eae) { + //Hoist out the container so we can assign back to the same one after (like VB does) + var tmpContainer = _additionalLocals.Hoist(new AdditionalDeclaration("tmp", eae.Expression, ValidSyntaxFactory.VarType)); + refLValue = eae.WithExpression(tmpContainer.IdentifierName); + } + + var withCast = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, refLValue, defaultToCast: refKind != RefKind.None); + + var local = _additionalLocals.Hoist(new AdditionalDeclaration(prefix, withCast, typeSyntax)); + + if (refType == RefConversion.PreAndPostAssignment) { + var convertedLocalIdentifier = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, local.IdentifierName, forceSourceType: expressionTypeInfo.ConvertedType, forceTargetType: expressionTypeInfo.Type); + _additionalLocals.Hoist(new AdditionalAssignment(refLValue, convertedLocalIdentifier)); + } + + return local.IdentifierName; + } + + private static SyntaxToken GetRefToken(RefKind refKind) + { + SyntaxToken token; + switch (refKind) { + case RefKind.None: + token = default(SyntaxToken); + break; + case RefKind.Ref: + token = SyntaxFactory.Token(SyntaxKind.RefKeyword); + break; + case RefKind.Out: + token = SyntaxFactory.Token(SyntaxKind.OutKeyword); + break; + default: + throw new ArgumentOutOfRangeException(); } - var expression = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, (ExpressionSyntax) await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor), defaultToCast: refKind != RefKind.None); - AdditionalLocal local = null; - if (refKind != RefKind.None && NeedsVariableForArgument(node)) { - var expressionTypeInfo = _semanticModel.GetTypeInfo(node.Expression); - bool useVar = expressionTypeInfo.Type?.Equals(expressionTypeInfo.ConvertedType) == true && !CommonConversions.ShouldPreferExplicitType(node.Expression, expressionTypeInfo.ConvertedType, out var _); - var typeSyntax = CommonConversions.GetTypeSyntax(expressionTypeInfo.ConvertedType, useVar); - string prefix = $"arg{argName}"; - local = _additionalLocals.AddAdditionalLocal(new AdditionalLocal(prefix, expression, typeSyntax)); - } - var nameColon = node.IsNamed ? SyntaxFactory.NameColon((IdentifierNameSyntax) await node.NameColonEquals.Name.AcceptAsync(TriviaConvertingExpressionVisitor)) : null; - if (local == null) { - return SyntaxFactory.Argument(nameColon, token, expression); + + return token; + } + + private RefConversion GetRefConversionType(VBSyntax.ArgumentSyntax node, VBSyntax.ArgumentListSyntax argList, System.Collections.Immutable.ImmutableArray parameters, out string argName, out RefKind refKind) + { + var parameter = node.IsNamed && node is VBSyntax.SimpleArgumentSyntax sas + ? parameters.FirstOrDefault(p => p.Name.Equals(sas.NameColonEquals.Name.Identifier.Text, StringComparison.OrdinalIgnoreCase)) + : parameters.ElementAtOrDefault(argList.Arguments.IndexOf(node)); + if (parameter != null) { + refKind = parameter.RefKind; + argName = parameter.Name; } else { - return SyntaxFactory.Argument(nameColon, token, local.IdentifierName); + refKind = RefKind.None; + argName = null; } + return NeedsVariableForArgument(node, refKind); + } + + private static StatementSyntax AssignStmt(ExpressionSyntax left, IdentifierNameSyntax right) + { + return SyntaxFactory.ExpressionStatement(Assign(left, right)); + } + + private static AssignmentExpressionSyntax Assign(ExpressionSyntax left, IdentifierNameSyntax right) + { + return SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, left, right); } public override async Task VisitNameOfExpression(VBasic.Syntax.NameOfExpressionSyntax node) @@ -787,6 +837,26 @@ public override async Task VisitInvocationExpression( VBasic.Syntax.InvocationExpressionSyntax node) { var invocationSymbol = _semanticModel.GetSymbolInfo(node).ExtractBestMatch(); + var methodInvocationSymbol = invocationSymbol as IMethodSymbol; + var withinLocalFunction = methodInvocationSymbol != null && RequiresLocalFunction(node, methodInvocationSymbol); + if (withinLocalFunction) { + _additionalLocals.PushScope(); + } + try { + var convertedInvocation = await ConvertInvocation(node, invocationSymbol); + if (withinLocalFunction) { + return await HoistAndCallLocalFunction(node, methodInvocationSymbol, (ExpressionSyntax)convertedInvocation); + } + return convertedInvocation; + } finally { + if (withinLocalFunction) { + _additionalLocals.PopExpressionScope(); + } + } + } + + private async Task ConvertInvocation(VBSyntax.InvocationExpressionSyntax node, ISymbol invocationSymbol) + { var expressionSymbol = _semanticModel.GetSymbolInfo(node.Expression).ExtractBestMatch(); var expressionReturnType = expressionSymbol?.GetReturnType() ?? _semanticModel.GetTypeInfo(node.Expression).Type; @@ -811,6 +881,8 @@ public override async Task VisitInvocationExpression( return SyntaxFactory.InvocationExpression((ExpressionSyntax)expr, args); } + //TODO: Decide if the above override should be subject to the rest of this method's adjustments (probably) + // VB doesn't have a specialized node for element access because the syntax is ambiguous. Instead, it just uses an invocation expression or dictionary access expression, then figures out using the semantic model which one is most likely intended. // https://github.com/dotnet/roslyn/blob/master/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb#L768 @@ -820,7 +892,7 @@ public override async Task VisitInvocationExpression( } if (expressionSymbol != null && expressionSymbol.IsKind(SymbolKind.Property) && - invocationSymbol.GetParameters().Length == 0) { + invocationSymbol != null && invocationSymbol.GetParameters().Length == 0) { return convertedExpression; //Parameterless property access } @@ -837,8 +909,8 @@ public override async Task VisitInvocationExpression( async Task<(ExpressionSyntax, bool isElementAccess)> ConvertInvocationSubExpression() { - var isElementAccess = IsPropertyElementAccess(operation) || - IsArrayElementAccess(operation) || + var isElementAccess = operation.IsPropertyElementAccess() || + operation.IsArrayElementAccess() || ProbablyNotAMethodCall(node, expressionSymbol, expressionReturnType); var expr = await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); @@ -860,6 +932,65 @@ async Task CreateElementAccess() } } + + /// + /// The VB compiler actually just hoists the conditions within the same method, but that leads to the original logic looking very different. + /// This should be equivalent but keep closer to the look of the original source code. + /// See https://github.com/icsharpcode/CodeConverter/issues/310 and https://github.com/icsharpcode/CodeConverter/issues/324 + /// + private async Task HoistAndCallLocalFunction(VBSyntax.InvocationExpressionSyntax invocation, IMethodSymbol invocationSymbol, ExpressionSyntax csExpression) + { + const string retVariableName = "ret"; + var localFuncName = $"local{invocationSymbol.Name}"; + var generatedNames = new HashSet();//TODO: Populate from local scope + + var callAndStoreResult = CommonConversions.CreateLocalVariableDeclarationAndAssignment(retVariableName, csExpression); + + var statements = await _additionalLocals.CreateLocals(invocation, new[] { callAndStoreResult }, generatedNames, _semanticModel); + + var block = SyntaxFactory.Block( + statements.Concat(SyntaxFactory.ReturnStatement(SyntaxFactory.IdentifierName(retVariableName)).Yield()) + ); + var returnType = CommonConversions.GetTypeSyntax(invocationSymbol.ReturnType); + + var localFunc = _additionalLocals.Hoist(new HoistedLocalFunction(localFuncName, returnType, block)); + return SyntaxFactory.InvocationExpression(localFunc.TempIdentifier, SyntaxFactory.ArgumentList()); + } + + private bool RequiresLocalFunction(VBSyntax.InvocationExpressionSyntax invocation, IMethodSymbol invocationSymbol) + { + if (invocation.ArgumentList == null || IsDefinitelyExecutedInStatement(invocation)) return false; + return invocation.ArgumentList.Arguments.Any(a => RequiresLocalFunction(invocation, invocationSymbol, a)); + + bool RequiresLocalFunction(VBSyntax.InvocationExpressionSyntax invocation, IMethodSymbol invocationSymbol, VBSyntax.ArgumentSyntax a) + { + var refConversion = GetRefConversionType(a, invocation.ArgumentList, invocationSymbol.Parameters, out var argName, out var refKind); + if (RefConversion.Inline == refConversion) return false; + if (!(a is VBSyntax.SimpleArgumentSyntax sas)) return false; + var argExpression = sas.Expression.SkipParens(); + if (argExpression is VBSyntax.InstanceExpressionSyntax) return false; + return !_semanticModel.GetConstantValue(argExpression).HasValue; + } + } + + private static bool IsDefinitelyExecutedInStatement(VBSyntax.InvocationExpressionSyntax invocation) + { + SyntaxNode parentStatement = invocation; + do { + parentStatement = parentStatement.GetAncestor(); + } while (parentStatement is VBSyntax.ElseIfStatementSyntax); + return parentStatement.FollowProperty(n => GetLeftMostWithPossibleExitPoints(n)).Contains(invocation); + } + + /// + /// It'd be great to use _semanticModel.AnalyzeControlFlow(invocation).ExitPoints, but that doesn't account for the possibility of exceptions + /// + private static SyntaxNode GetLeftMostWithPossibleExitPoints(SyntaxNode n) => n switch + { + VBSyntax.VariableDeclaratorSyntax vds => vds.Initializer, + _ => n.ChildNodes().FirstOrDefault() + }; + public override async Task VisitSingleLineLambdaExpression(VBasic.Syntax.SingleLineLambdaExpressionSyntax node) { IReadOnlyCollection convertedStatements; @@ -1287,23 +1418,68 @@ private IEnumerable GetAdditionalRequiredArgs(ISymbol invocation private ArgumentSyntax CreateOptionalRefArg(IParameterSymbol p) { string prefix = $"arg{p.Name}"; - var local = _additionalLocals.AddAdditionalLocal(new AdditionalLocal(prefix, CommonConversions.Literal(p.ExplicitDefaultValue), CommonConversions.GetTypeSyntax(p.Type))); + var local = _additionalLocals.Hoist(new AdditionalDeclaration(prefix, CommonConversions.Literal(p.ExplicitDefaultValue), CommonConversions.GetTypeSyntax(p.Type))); return (ArgumentSyntax)CommonConversions.CsSyntaxGenerator.Argument(p.Name, p.RefKind, local.IdentifierName); } - private bool NeedsVariableForArgument(VBasic.Syntax.SimpleArgumentSyntax node) + private RefConversion NeedsVariableForArgument(VBasic.Syntax.ArgumentSyntax node, RefKind refKind) { - bool isIdentifier = node.Expression is VBasic.Syntax.IdentifierNameSyntax; - bool isMemberAccess = node.Expression is VBasic.Syntax.MemberAccessExpressionSyntax; + if (refKind == RefKind.None) return RefConversion.Inline; + if (!(node is VBSyntax.SimpleArgumentSyntax sas)) return RefConversion.PreAssigment; + var expression = sas.Expression.SkipParens(); + + return GetRefConversion(expression); - var symbolInfo = GetSymbolInfoInDocument(node.Expression); - bool isProperty = symbolInfo != null && symbolInfo.IsKind(SymbolKind.Property); - bool isUsing = symbolInfo?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax()?.Parent?.Parent?.IsKind(VBasic.SyntaxKind.UsingStatement) == true; + RefConversion GetRefConversion(VBSyntax.ExpressionSyntax expression) + { + var symbolInfo = GetSymbolInfoInDocument(expression); + if (symbolInfo.IsKind(SymbolKind.Property)) return RefConversion.PreAndPostAssignment; + + var typeInfo = _semanticModel.GetTypeInfo(expression); + bool isTypeMismatch = typeInfo.Type == null || !typeInfo.Type.Equals(typeInfo.ConvertedType); + + if (isTypeMismatch || DeclaredInUsing(symbolInfo)) return RefConversion.PreAssigment; - var typeInfo = _semanticModel.GetTypeInfo(node.Expression); - bool isTypeMismatch = typeInfo.Type == null || !typeInfo.Type.Equals(typeInfo.ConvertedType); + if (expression is VBasic.Syntax.IdentifierNameSyntax || expression is VBSyntax.MemberAccessExpressionSyntax || + IsRefArrayAcces(expression)) { + return RefConversion.Inline; + } + + return RefConversion.PreAssigment; + } - return (!isIdentifier && !isMemberAccess) || isProperty || isTypeMismatch || isUsing; + bool IsRefArrayAcces(VBSyntax.ExpressionSyntax expression) + { + if (!(expression is VBSyntax.InvocationExpressionSyntax ies)) return false; + return _semanticModel.GetOperation(ies).IsArrayElementAccess() && GetRefConversion(ies.Expression) == RefConversion.Inline; + } + } + + private static bool DeclaredInUsing(ISymbol symbolInfo) + { + return symbolInfo?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax()?.Parent?.Parent?.IsKind(VBasic.SyntaxKind.UsingStatement) == true; + } + + /// + /// https://github.com/icsharpcode/CodeConverter/issues/324 + /// https://github.com/icsharpcode/CodeConverter/issues/310 + /// + private enum RefConversion + { + /// + /// e.g. Normal field, parameter or local + /// + Inline, + /// + /// Needs assignment before and/or after + /// e.g. Method/Property result + /// + PreAssigment, + /// + /// Needs assignment before and/or after + /// i.e. Property + /// + PreAndPostAssignment } private ISymbol GetInvocationSymbol(SyntaxNode invocation) @@ -1339,16 +1515,6 @@ private static CSharpSyntaxNode ReplaceRightmostIdentifierText(CSharpSyntaxNode return expr.ReplaceToken(idToken, SyntaxFactory.Identifier(overrideIdentifier).WithTriviaFrom(idToken).WithAdditionalAnnotations(idToken.GetAnnotations())); } - private static bool IsPropertyElementAccess(IOperation operation) - { - return operation is IPropertyReferenceOperation pro && pro.Arguments.Any() && VBasic.VisualBasicExtensions.IsDefault(pro.Property); - } - - private static bool IsArrayElementAccess(IOperation operation) - { - return operation != null && operation.Kind == OperationKind.ArrayElementReference; - } - /// /// Chances of having an unknown delegate stored as a field/local seem lower than having an unknown non-delegate type with an indexer stored. /// So for a standalone identifier err on the side of assuming it's an indexer. diff --git a/CodeConverter/CSharp/HoistedLocalFunction.cs b/CodeConverter/CSharp/HoistedLocalFunction.cs new file mode 100644 index 000000000..fabd0fa00 --- /dev/null +++ b/CodeConverter/CSharp/HoistedLocalFunction.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ICSharpCode.CodeConverter.CSharp +{ + internal class HoistedLocalFunction : IHoistedNode + { + private readonly TypeSyntax _returnType; + private readonly BlockSyntax _block; + + public string Id { get; } + public string Prefix { get; } + + public HoistedLocalFunction(string localFuncName, TypeSyntax returnType, BlockSyntax block) + { + Id = $"hs{Guid.NewGuid().ToString("N")}"; + Prefix = localFuncName; + _returnType = returnType; + _block = block; + } + + public IdentifierNameSyntax TempIdentifier => SyntaxFactory.IdentifierName(Id).WithAdditionalAnnotations(HoistedNodeState.Annotation); + public LocalFunctionStatementSyntax LocalFunction(string functionName) => SyntaxFactory.LocalFunctionStatement(_returnType, SyntaxFactory.Identifier(functionName)).WithBody(_block); + } +} \ No newline at end of file diff --git a/CodeConverter/CSharp/HoistedNodeState.cs b/CodeConverter/CSharp/HoistedNodeState.cs new file mode 100644 index 000000000..6d946ce2d --- /dev/null +++ b/CodeConverter/CSharp/HoistedNodeState.cs @@ -0,0 +1,120 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using VBSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax; +using VBasic = Microsoft.CodeAnalysis.VisualBasic; +using CS = Microsoft.CodeAnalysis.CSharp; +using ICSharpCode.CodeConverter.Shared; +using System; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; + +namespace ICSharpCode.CodeConverter.CSharp +{ + internal class HoistedNodeState + { + public static SyntaxAnnotation Annotation = new SyntaxAnnotation("CodeconverterAdditionalLocal"); + + private readonly Stack> _hoistedNodesPerScope; + + public HoistedNodeState() + { + _hoistedNodesPerScope = new Stack>(); + } + + public void PushScope() + { + _hoistedNodesPerScope.Push(new List()); + } + + public void PopScope() + { + _hoistedNodesPerScope.Pop(); + } + + public void PopExpressionScope() + { + var statements = GetStatements(); + PopScope(); + foreach (var statement in statements) { + Hoist(statement); + } + } + + public T Hoist(T additionalLocal) where T: IHoistedNode + { + _hoistedNodesPerScope.Peek().Add(additionalLocal); + return additionalLocal; + } + + public IReadOnlyCollection GetDeclarations() + { + return _hoistedNodesPerScope.Peek().OfType().ToArray(); + } + + public IReadOnlyCollection GetPostAssignments() + { + return _hoistedNodesPerScope.Peek().OfType().ToArray(); + } + + public IReadOnlyCollection GetStatements() + { + return _hoistedNodesPerScope.Peek().OfType().ToArray(); + } + + public SyntaxList CreateStatements(VBasic.VisualBasicSyntaxNode vbNode, IEnumerable statements, HashSet generatedNames, SemanticModel semanticModel) + { + var localFunctions = GetStatements(); + var newNames = localFunctions.ToDictionary(f => f.Id, f => + NameGenerator.GetUniqueVariableNameInScope(semanticModel, generatedNames, vbNode, f.Prefix) + ); + statements = ReplaceNames(statements, newNames); + var functions = localFunctions.Select(f => f.LocalFunction(newNames[f.Id])); + return SyntaxFactory.List(functions.Concat(statements)); + } + + public async Task> CreateLocals(VBasic.VisualBasicSyntaxNode vbNode, IEnumerable csNodes, HashSet generatedNames, SemanticModel semanticModel) + { + var preDeclarations = new List(); + var postAssignments = new List(); + + var additionalDeclarationInfo = GetDeclarations(); + var newNames = additionalDeclarationInfo.ToDictionary(l => l.Id, l => + NameGenerator.GetUniqueVariableNameInScope(semanticModel, generatedNames, vbNode, l.Prefix) + ); + foreach (var additionalLocal in additionalDeclarationInfo) { + var decl = CommonConversions.CreateVariableDeclarationAndAssignment(newNames[additionalLocal.Id], + additionalLocal.Initializer, additionalLocal.Type); + preDeclarations.Add(CS.SyntaxFactory.LocalDeclarationStatement(decl)); + } + + foreach (var additionalAssignment in GetPostAssignments()) { + var assign = CS.SyntaxFactory.AssignmentExpression(CS.SyntaxKind.SimpleAssignmentExpression, additionalAssignment.LeftHandSide, additionalAssignment.RightHandSide); + postAssignments.Add(CS.SyntaxFactory.ExpressionStatement(assign)); + } + + var statementsWithUpdatedIds = ReplaceNames(preDeclarations.Concat(csNodes).Concat(postAssignments), newNames); + + return CS.SyntaxFactory.List(statementsWithUpdatedIds); + } + + public static IEnumerable ReplaceNames(IEnumerable csNodes, Dictionary newNames) + { + csNodes = csNodes.Select(csNode => ReplaceNames(csNode, newNames)).ToList(); + return csNodes; + } + + public static T ReplaceNames(T csNode, Dictionary newNames) where T : SyntaxNode + { + return csNode.ReplaceNodes(csNode.GetAnnotatedNodes(Annotation), (_, withReplaced) => { + var idns = (IdentifierNameSyntax)withReplaced; + if (newNames.TryGetValue(idns.Identifier.ValueText, out var newName)) { + return idns.WithoutAnnotations(Annotation).WithIdentifier(CS.SyntaxFactory.Identifier(newName)); + } + return idns; + }); + } + } +} diff --git a/CodeConverter/CSharp/ByRefParameterVisitor.cs b/CodeConverter/CSharp/HoistedNodeStateVisitor.cs similarity index 74% rename from CodeConverter/CSharp/ByRefParameterVisitor.cs rename to CodeConverter/CSharp/HoistedNodeStateVisitor.cs index 4f0d1e8bd..65cfe27ec 100644 --- a/CodeConverter/CSharp/ByRefParameterVisitor.cs +++ b/CodeConverter/CSharp/HoistedNodeStateVisitor.cs @@ -12,14 +12,20 @@ namespace ICSharpCode.CodeConverter.CSharp { - public class ByRefParameterVisitor : VBasic.VisualBasicSyntaxVisitor>> + /// + /// Stores state to allow adding a syntax node to the surrounding scope (by sharing an instance of AdditionalLocals) + /// e.g. Add a local variable declaration in the scope immediately before the expression currently being visited. + /// e.g. Add a member declaration in the scope immediately before the member currently being visited. + /// The current implementation uses a guid variable name, then replaces it later with a unique name by tracking the annotation added to it. + /// + internal class HoistedNodeStateVisitor : VBasic.VisualBasicSyntaxVisitor>> { private readonly VBasic.VisualBasicSyntaxVisitor>> _wrappedVisitor; - private readonly AdditionalLocals _additionalLocals; + private readonly HoistedNodeState _additionalLocals; private readonly SemanticModel _semanticModel; private readonly HashSet _generatedNames; - public ByRefParameterVisitor(VBasic.VisualBasicSyntaxVisitor>> wrappedVisitor, AdditionalLocals additionalLocals, + public HoistedNodeStateVisitor(VBasic.VisualBasicSyntaxVisitor>> wrappedVisitor, HoistedNodeState additionalLocals, SemanticModel semanticModel, HashSet generatedNames) { _wrappedVisitor = wrappedVisitor; @@ -37,44 +43,13 @@ public override Task> DefaultVisit(SyntaxNode node) private async Task> AddLocalVariables(VBasic.VisualBasicSyntaxNode node) { _additionalLocals.PushScope(); - IEnumerable csNodes; - List additionalDeclarations; try { - (csNodes, additionalDeclarations) = await CreateLocals(node); + var csNodes = await _wrappedVisitor.Visit(node); + var statements = await _additionalLocals.CreateLocals(node, csNodes, _generatedNames, _semanticModel); + return _additionalLocals.CreateStatements(node, statements, _generatedNames, _semanticModel); } finally { _additionalLocals.PopScope(); } - - return SyntaxFactory.List(additionalDeclarations.Concat(csNodes)); - } - - private async Task<(IEnumerable csNodes, List additionalDeclarations)> CreateLocals(VBasic.VisualBasicSyntaxNode node) - { - IEnumerable csNodes = await _wrappedVisitor.Visit(node); - - var additionalDeclarations = new List(); - - if (_additionalLocals.Count() > 0) - { - var newNames = new Dictionary(); - csNodes = csNodes.Select(csNode => csNode.ReplaceNodes(csNode.GetAnnotatedNodes(AdditionalLocals.Annotation), - (an, _) => - { - var id = ((IdentifierNameSyntax) an).Identifier.ValueText; - newNames[id] = NameGenerator.GetUniqueVariableNameInScope(_semanticModel, _generatedNames, node, - _additionalLocals[id].Prefix); - return SyntaxFactory.IdentifierName(newNames[id]); - })).ToList(); - - foreach (var additionalLocal in _additionalLocals) - { - var decl = CommonConversions.CreateVariableDeclarationAndAssignment(newNames[additionalLocal.Key], - additionalLocal.Value.Initializer, additionalLocal.Value.Type); - additionalDeclarations.Add(SyntaxFactory.LocalDeclarationStatement(decl)); - } - } - - return (csNodes, additionalDeclarations); } public override Task> VisitAddRemoveHandlerStatement(VBSyntax.AddRemoveHandlerStatementSyntax node) => AddLocalVariables(node); diff --git a/CodeConverter/CSharp/IHoistedNode.cs b/CodeConverter/CSharp/IHoistedNode.cs new file mode 100644 index 000000000..c560e7a56 --- /dev/null +++ b/CodeConverter/CSharp/IHoistedNode.cs @@ -0,0 +1,9 @@ +namespace ICSharpCode.CodeConverter.CSharp +{ + /// + /// Marker interface + /// + internal interface IHoistedNode + { + } +} \ No newline at end of file diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index 9586fe9de..a797b87e2 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -41,7 +41,7 @@ internal class MethodBodyExecutableStatementVisitor : VBasic.VisualBasicSyntaxVi private CommonConversions CommonConversions { get; } - public static async Task CreateAsync(VBasic.VisualBasicSyntaxNode node, SemanticModel semanticModel, CommentConvertingVisitorWrapper triviaConvertingExpressionVisitor, CommonConversions commonConversions, Stack withBlockLhs, HashSet extraUsingDirectives, AdditionalLocals additionalLocals, MethodsWithHandles methodsWithHandles, bool isIterator, IdentifierNameSyntax csReturnVariable) + public static async Task CreateAsync(VBasic.VisualBasicSyntaxNode node, SemanticModel semanticModel, CommentConvertingVisitorWrapper triviaConvertingExpressionVisitor, CommonConversions commonConversions, Stack withBlockLhs, HashSet extraUsingDirectives, HoistedNodeState additionalLocals, MethodsWithHandles methodsWithHandles, bool isIterator, IdentifierNameSyntax csReturnVariable) { var solution = commonConversions.Document.Project.Solution; var declarationsToInlineInLoop = await solution.GetDescendantsToInlineInLoopAsync(semanticModel, node); @@ -54,7 +54,7 @@ public static async Task CreateAsync(VBasi private MethodBodyExecutableStatementVisitor(VBasic.VisualBasicSyntaxNode methodNode, SemanticModel semanticModel, CommentConvertingVisitorWrapper expressionVisitor, CommonConversions commonConversions, Stack withBlockLhs, HashSet extraUsingDirectives, - AdditionalLocals additionalLocals, MethodsWithHandles methodsWithHandles, HashSet localsToInlineInLoop) + HoistedNodeState additionalLocals, MethodsWithHandles methodsWithHandles, HashSet localsToInlineInLoop) { _methodNode = methodNode; _semanticModel = semanticModel; @@ -63,7 +63,7 @@ private MethodBodyExecutableStatementVisitor(VBasic.VisualBasicSyntaxNode method _withBlockLhs = withBlockLhs; _extraUsingDirectives = extraUsingDirectives; _methodsWithHandles = methodsWithHandles; - var byRefParameterVisitor = new ByRefParameterVisitor(this, additionalLocals, semanticModel, _generatedNames); + var byRefParameterVisitor = new HoistedNodeStateVisitor(this, additionalLocals, semanticModel, _generatedNames); CommentConvertingVisitor = new CommentConvertingMethodBodyVisitor(byRefParameterVisitor); _vbBooleanTypeSymbol = _semanticModel.Compilation.GetTypeByMetadataName("System.Boolean"); _localsToInlineInLoop = localsToInlineInLoop; @@ -424,17 +424,23 @@ public override async Task> VisitMultiLineIfBlock(VB var elseClause = await ConvertElseClause(node.ElseBlock); elseClause = elseClause.WithVbSourceMappingFrom(node.ElseBlock); //Special case where explicit mapping is needed since block becomes clause so cannot be easily visited - foreach (var elseIf in node.ElseIfBlocks.Reverse()) { - var elseBlock = SyntaxFactory.Block(await ConvertStatements(elseIf.Statements)); - var elseIfCondition = (ExpressionSyntax) await elseIf.ElseIfStatement.Condition.AcceptAsync(_expressionVisitor); - elseIfCondition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(elseIf.ElseIfStatement.Condition, elseIfCondition, forceTargetType: _vbBooleanTypeSymbol); - var ifStmt = SyntaxFactory.IfStatement(elseIfCondition, elseBlock, elseClause); + var elseIfBlocks = await node.ElseIfBlocks.SelectAsync(async elseIf => await ConvertElseIf(elseIf)); + foreach (var elseIf in elseIfBlocks.Reverse()) { + var ifStmt = SyntaxFactory.IfStatement(elseIf.ElseIfCondition, elseIf.ElseBlock, elseClause); elseClause = SyntaxFactory.ElseClause(ifStmt); } return SingleStatement(SyntaxFactory.IfStatement(condition, block, elseClause)); } + private async Task<(ExpressionSyntax ElseIfCondition, BlockSyntax ElseBlock)> ConvertElseIf(VBSyntax.ElseIfBlockSyntax elseIf) + { + var elseBlock = SyntaxFactory.Block(await ConvertStatements(elseIf.Statements)); + var elseIfCondition = (ExpressionSyntax)await elseIf.ElseIfStatement.Condition.AcceptAsync(_expressionVisitor); + elseIfCondition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(elseIf.ElseIfStatement.Condition, elseIfCondition, forceTargetType: _vbBooleanTypeSymbol); + return (elseIfCondition, elseBlock); + } + private async Task ConvertElseClause(VBSyntax.ElseBlockSyntax elseBlock) { if (elseBlock == null) return null; @@ -669,7 +675,7 @@ public override async Task> VisitSelectBlock(VBSynta if (forceVariable || !await CanEvaluateMultipleTimesAsync(vbExpr)) { var contextNode = vbExpr.GetAncestor() ?? (VBasic.VisualBasicSyntaxNode) vbExpr.Parent; var varName = GetUniqueVariableNameInScope(contextNode, variableNameBase); - var stmt = CreateLocalVariableDeclarationAndAssignment(varName, expr); + var stmt = CommonConversions.CreateLocalVariableDeclarationAndAssignment(varName, expr); stmts = stmts.Add(stmt); exprWithoutSideEffects = SyntaxFactory.IdentifierName(varName); reusableExprWithoutSideEffects = exprWithoutSideEffects; @@ -732,11 +738,6 @@ public override async Task> VisitWithBlock(VBSyntax. } } - private LocalDeclarationStatementSyntax CreateLocalVariableDeclarationAndAssignment(string variableName, ExpressionSyntax initValue) - { - return SyntaxFactory.LocalDeclarationStatement(CommonConversions.CreateVariableDeclarationAndAssignment(variableName, initValue)); - } - private string GetUniqueVariableNameInScope(VBasic.VisualBasicSyntaxNode node, string variableNameBase) { return NameGenerator.GetUniqueVariableNameInScope(_semanticModel, _generatedNames, node, variableNameBase); diff --git a/CodeConverter/CSharp/OperationExtensions.cs b/CodeConverter/CSharp/OperationExtensions.cs index 4b3b2a455..3e3f231ce 100644 --- a/CodeConverter/CSharp/OperationExtensions.cs +++ b/CodeConverter/CSharp/OperationExtensions.cs @@ -1,6 +1,10 @@ -using ICSharpCode.CodeConverter.Util.FromRoslyn; +using System.Linq; +using ICSharpCode.CodeConverter.Util.FromRoslyn; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; +using VBasic = Microsoft.CodeAnalysis.VisualBasic; +using VBSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax; + namespace ICSharpCode.CodeConverter.CSharp { @@ -32,5 +36,15 @@ public static IOperation SkipParens(this IOperation operation, bool skipImplicit } } } + + public static bool IsPropertyElementAccess(this IOperation operation) + { + return operation is IPropertyReferenceOperation pro && pro.Arguments.Any() && VBasic.VisualBasicExtensions.IsDefault(pro.Property); + } + + public static bool IsArrayElementAccess(this IOperation operation) + { + return operation != null && operation.Kind == OperationKind.ArrayElementReference; + } } } \ No newline at end of file diff --git a/CodeConverter/CSharp/TypeConversionAnalyzer.cs b/CodeConverter/CSharp/TypeConversionAnalyzer.cs index d28d95e41..152198b9f 100644 --- a/CodeConverter/CSharp/TypeConversionAnalyzer.cs +++ b/CodeConverter/CSharp/TypeConversionAnalyzer.cs @@ -46,20 +46,20 @@ public TypeConversionAnalyzer(SemanticModel semanticModel, CSharpCompilation csC _expressionEvaluator = expressionEvaluator; } - public ExpressionSyntax AddExplicitConversion(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, bool addParenthesisIfNeeded = true, bool defaultToCast = false, bool isConst = false, ITypeSymbol forceTargetType = null) + 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) { if (csNode == null) return null; - var conversionKind = AnalyzeConversion(vbNode, defaultToCast, isConst, forceTargetType); + var conversionKind = AnalyzeConversion(vbNode, defaultToCast, isConst, forceSourceType, forceTargetType); csNode = addParenthesisIfNeeded && (conversionKind == TypeConversionKind.DestructiveCast || conversionKind == TypeConversionKind.NonDestructiveCast) ? VbSyntaxNodeExtensions.ParenthesizeIfPrecedenceCouldChange(vbNode, csNode) : csNode; - return AddExplicitConversion(vbNode, csNode, conversionKind, addParenthesisIfNeeded, isConst, forceTargetType: forceTargetType); + return AddExplicitConversion(vbNode, csNode, conversionKind, addParenthesisIfNeeded, isConst, forceSourceType: forceSourceType, forceTargetType: forceTargetType); } - public ExpressionSyntax AddExplicitConversion(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, TypeConversionKind conversionKind, bool addParenthesisIfNeeded = false, bool isConst = false, ITypeSymbol forceTargetType = null) + 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) { var typeInfo = ModelExtensions.GetTypeInfo(_semanticModel, vbNode); - var vbType = typeInfo.Type; + var vbType = forceSourceType ?? typeInfo.Type; var vbConvertedType = forceTargetType ?? typeInfo.ConvertedType; if (isConst && _expressionEvaluator.GetConstantOrNull(vbNode, vbConvertedType, csNode) is ExpressionSyntax constLiteral) { @@ -98,10 +98,10 @@ private ExpressionSyntax CreateCast(ExpressionSyntax csNode, ITypeSymbol vbConve return ValidSyntaxFactory.CastExpression(typeName, csNode); } - public TypeConversionKind AnalyzeConversion(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax vbNode, bool alwaysExplicit = false, bool isConst = false, ITypeSymbol forceTargetType = null) + public TypeConversionKind AnalyzeConversion(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax vbNode, bool alwaysExplicit = false, bool isConst = false, ITypeSymbol forceSourceType = null, ITypeSymbol forceTargetType = null) { var typeInfo = ModelExtensions.GetTypeInfo(_semanticModel, vbNode); - var vbType = typeInfo.Type; + var vbType = forceSourceType ?? typeInfo.Type; var vbConvertedType = forceTargetType ?? typeInfo.ConvertedType; if (vbType is null || vbConvertedType is null) { diff --git a/CodeConverter/CSharp/ValidSyntaxFactory.cs b/CodeConverter/CSharp/ValidSyntaxFactory.cs index fb8c26007..551d17cc3 100644 --- a/CodeConverter/CSharp/ValidSyntaxFactory.cs +++ b/CodeConverter/CSharp/ValidSyntaxFactory.cs @@ -13,6 +13,8 @@ namespace ICSharpCode.CodeConverter.CSharp /// public static class ValidSyntaxFactory { + public static readonly TypeSyntax VarType = SyntaxFactory.ParseTypeName("var"); + /// /// As of VS2019 Preview 3 this is required since not passing these arguments now means "I don't want any parentheses" /// https://github.com/dotnet/roslyn/issues/33685 diff --git a/CodeConverter/VB/CommonConversions.cs b/CodeConverter/VB/CommonConversions.cs index 8fb96c2c8..8c9c67834 100644 --- a/CodeConverter/VB/CommonConversions.cs +++ b/CodeConverter/VB/CommonConversions.cs @@ -322,9 +322,9 @@ public LambdaExpressionSyntax ConvertLambdaExpression(CSS.AnonymousFunctionExpre return CreateLambdaExpression(singleLineExpressionKind, multiLineExpressionKind, header, statements, endBlock); } - StatementSyntax GetStatementSyntax(VisualBasicSyntaxNode node, Func create) { - if (node is StatementSyntax) - return (StatementSyntax)node; + + private StatementSyntax GetStatementSyntax(VisualBasicSyntaxNode node, Func create) { + if (node is StatementSyntax syntax) return syntax; return create(node as ExpressionSyntax); } diff --git a/Tests/CSharp/ExpressionTests/ByRefTests.cs b/Tests/CSharp/ExpressionTests/ByRefTests.cs new file mode 100644 index 000000000..bb4031051 --- /dev/null +++ b/Tests/CSharp/ExpressionTests/ByRefTests.cs @@ -0,0 +1,523 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ICSharpCode.CodeConverter.Tests.TestRunners; +using Xunit; + +namespace ICSharpCode.CodeConverter.Tests.CSharp.ExpressionTests +{ + public class ByRefTests : ConverterTestBase + { + + [Fact] + public async Task OptionalRefDateConstsWithOmittedArgListAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Class Issue213 + Const x As Date = #1990-1-1# + + Private Sub Y(Optional ByRef opt As Date = x) + End Sub + + Private Sub CallsY() + Y + End Sub +End Class", @"using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +public partial class Issue213 +{ + private static DateTime x = DateTime.Parse(""1990-01-01""); + + private void Y([Optional, DateTimeConstant(627667488000000000/* Global.Issue213.x */)] ref DateTime opt) + { + } + + private void CallsY() + { + DateTime argopt = DateTime.Parse(""1990-01-01""); + Y(opt: ref argopt); + } +}"); + } + + [Fact] + public async Task NullInlineRefArgumentAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Class VisualBasicClass + Public Sub UseStuff() + Stuff(Nothing) + End Sub + + Public Sub Stuff(ByRef strs As String()) + End Sub +End Class", @" +public partial class VisualBasicClass +{ + public void UseStuff() + { + string[] argstrs = null; + Stuff(ref argstrs); + } + + public void Stuff(ref string[] strs) + { + } +}"); + } + + [Fact] + public async Task RefArgumentRValueAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Class Class1 + Private Property C1 As Class1 + Private _c2 As Class1 + Private _o1 As Object + + Sub Foo() + Bar(New Class1) + Bar(C1) + Bar(Me.C1) + Bar(_c2) + Bar(Me._c2) + Bar(_o1) + Bar(Me._o1) + End Sub + + Sub Bar(ByRef class1) + End Sub +End Class", @" +public partial class Class1 +{ + private Class1 C1 { get; set; } + + private Class1 _c2; + private object _o1; + + public void Foo() + { + object argclass1 = new Class1(); + Bar(ref argclass1); + object argclass11 = C1; + Bar(ref argclass11); + C1 = (Class1)argclass11; + object argclass12 = C1; + Bar(ref argclass12); + C1 = (Class1)argclass12; + object argclass13 = _c2; + Bar(ref argclass13); + object argclass14 = _c2; + Bar(ref argclass14); + Bar(ref _o1); + Bar(ref _o1); + } + + public void Bar(ref object class1) + { + } +}"); + } + + [Fact] + public async Task RefArgumentRValue2Async() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Class Class1 + Sub Foo() + Dim x = True + Bar(x = True) + End Sub + + Function Foo2() + Return Bar(True = False) + End Function + + Sub Foo3() + If Bar(True = False) Then Bar(True = False) + End Sub + + Sub Foo4() + If Bar(True = False) Then + Bar(True = False) + ElseIf Bar(True = False) Then + Bar(True = False) + Else + Bar(True = False) + End If + End Sub + + Sub Foo5() + Bar(Nothing) + End Sub + + Function Bar(ByRef b As Boolean) As Boolean + Return True + End Function + + Function Bar2(ByRef c1 As Class1) As Integer + If c1 IsNot Nothing AndAlso Len(Bar3(Me)) <> 0 Then + Return 1 + End If + Return 0 + End Function + + Function Bar3(ByRef c1 As Class1) As String + Return """" + End Function + +End Class", @"using Microsoft.VisualBasic; + +public partial class Class1 +{ + public void Foo() + { + bool x = true; + bool argb = x == true; + Bar(ref argb); + } + + public object Foo2() + { + bool argb = true == false; + return Bar(ref argb); + } + + public void Foo3() + { + bool argb1 = true == false; + if (Bar(ref argb1)) + { + bool argb = true == false; + Bar(ref argb); + } + } + + public void Foo4() + { + bool argb3 = true == false; + bool argb4 = true == false; + if (Bar(ref argb3)) + { + bool argb = true == false; + Bar(ref argb); + } + else if (Bar(ref argb4)) + { + bool argb2 = true == false; + Bar(ref argb2); + } + else + { + bool argb1 = true == false; + Bar(ref argb1); + } + } + + public void Foo5() + { + bool argb = default; + Bar(ref argb); + } + + public bool Bar(ref bool b) + { + return true; + } + + public int Bar2(ref Class1 c1) + { + var argc1 = this; + if (c1 is object && Strings.Len(Bar3(ref argc1)) != 0) + { + return 1; + } + + return 0; + } + + public string Bar3(ref Class1 c1) + { + return """"; + } +}"); + } + + [Fact] + public async Task RefArgumentUsingAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Imports System.Data.SqlClient + +Public Class Class1 + Sub Foo() + Using x = New SqlConnection + Bar(x) + End Using + End Sub + Sub Bar(ByRef x As SqlConnection) + + End Sub +End Class", @"using System.Data.SqlClient; + +public partial class Class1 +{ + public void Foo() + { + using (var x = new SqlConnection()) + { + var argx = x; + Bar(ref argx); + } + } + + public void Bar(ref SqlConnection x) + { + } +}"); + } + + [Fact] + public async Task RefOptionalArgumentAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Class OptionalRefIssue91 + Public Shared Function TestSub(Optional ByRef IsDefault As Boolean = False) As Boolean + End Function + + Public Shared Function CallingFunc() As Boolean + Return TestSub() AndAlso TestSub(True) + End Function +End Class", @"using System.Runtime.InteropServices; + +public partial class OptionalRefIssue91 +{ + public static bool TestSub([Optional, DefaultParameterValue(false)] ref bool IsDefault) + { + return default; + } + + public static bool CallingFunc() + { + bool argIsDefault = false; + bool argIsDefault1 = true; + return TestSub(IsDefault: ref argIsDefault) && TestSub(ref argIsDefault1); + } +}"); + } + + [Fact] + public async Task RefArgumentPropertyInitializerAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Class Class1 + Private _p1 As Class1 = Foo(New Class1) + Public Shared Function Foo(ByRef c1 As Class1) As Class1 + Return c1 + End Function +End Class", @" +public partial class Class1 +{ + static Class1 Foo__p1() + { + var argc1 = new Class1(); + return Foo(ref argc1); + } + + private Class1 _p1 = Foo__p1(); + + public static Class1 Foo(ref Class1 c1) + { + return c1; + } +}"); + } + + [Fact] + public async Task AssignsBackToPropertyAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Imports System + +Public Class MyTestClass + + Private Property Prop As Integer + Private Property Prop2 As Integer + + Private Function TakesRef(ByRef vrbTst As Integer) As Boolean + vrbTst = Prop + 1 + Return vrbTst > 3 + End Function + + Private Sub TakesRefVoid(ByRef vrbTst As Integer) + vrbTst = vrbTst + 1 + End Sub + + Public Sub UsesRef(someBool As Boolean, someInt As Integer) + + TakesRefVoid(someInt) ' Convert directly + TakesRefVoid(1) 'Requires variable before + TakesRefVoid(Prop2) ' Requires variable before, and to assign back after + + Dim a = TakesRef(someInt) ' Convert directly + Dim b = TakesRef(2) 'Requires variable before + Dim c = TakesRef(Prop) ' Requires variable before, and to assign back after + + If 16 > someInt OrElse TakesRef(someInt) ' Convert directly + Console.WriteLine(1) + Else If someBool AndAlso TakesRef(3 * a) 'Requires variable before (in local function) + someInt += 1 + Else If TakesRef(Prop) ' Requires variable before, and to assign back after (in local function) + someInt -=2 + End If + Console.WriteLine(someInt) + End Sub +End Class", @"using System; +using Microsoft.VisualBasic.CompilerServices; + +public partial class MyTestClass +{ + private int Prop { get; set; } + private int Prop2 { get; set; } + + private bool TakesRef(ref int vrbTst) + { + vrbTst = Prop + 1; + return vrbTst > 3; + } + + private void TakesRefVoid(ref int vrbTst) + { + vrbTst = vrbTst + 1; + } + + public void UsesRef(bool someBool, int someInt) + { + TakesRefVoid(ref someInt); // Convert directly + int argvrbTst = 1; + TakesRefVoid(ref argvrbTst); // Requires variable before + int argvrbTst1 = Prop2; + TakesRefVoid(ref argvrbTst1); + Prop2 = argvrbTst1; // Requires variable before, and to assign back after + bool a = TakesRef(ref someInt); // Convert directly + int argvrbTst2 = 2; + bool b = TakesRef(ref argvrbTst2); // Requires variable before + int argvrbTst3 = Prop; + bool c = TakesRef(ref argvrbTst3); + Prop = argvrbTst3; // Requires variable before, and to assign back after + bool localTakesRef() { int argvrbTst = 3 * Conversions.ToInteger(a); var ret = TakesRef(ref argvrbTst); return ret; } + + bool localTakesRef1() { int argvrbTst = Prop; var ret = TakesRef(ref argvrbTst); Prop = argvrbTst; return ret; } + + if (16 > someInt || TakesRef(ref someInt)) // Convert directly + { + Console.WriteLine(1); + } + else if (someBool && localTakesRef()) // Requires variable before (in local function) + { + someInt += 1; + } + else if (localTakesRef1()) // Requires variable before, and to assign back after (in local function) + { + someInt -= 2; + } + + Console.WriteLine(someInt); + } +}"); + } + + [Fact] + public async Task Issue567() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Class Issue567 + Dim arr() As String + Dim arr2(,) As String + + Sub DoSomething(ByRef str As String) + str = ""test"" + End Sub + + Sub Main() + DoSomething(arr(1)) + Debug.Assert(arr(1) = ""test"") + DoSomething(arr2(2, 2)) + Debug.Assert(arr2(2, 2) = ""test"") + End Sub + +End Class", @"using System.Diagnostics; + +public partial class Issue567 +{ + private string[] arr; + private string[,] arr2; + + public void DoSomething(ref string str) + { + str = ""test""; + } + + public void Main() + { + DoSomething(ref arr[1]); + Debug.Assert((arr[1] ?? """") == ""test""); + DoSomething(ref arr2[2, 2]); + Debug.Assert((arr2[2, 2] ?? """") == ""test""); + } +}"); + } + + [Fact] + public async Task Issue567Extended() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Class Issue567 + Sub DoSomething(ByRef str As String) + lst = New List(Of String)({4.ToString(), 5.ToString(), 6.ToString()}) + lst2 = New List(Of Object)({4.ToString(), 5.ToString(), 6.ToString()}) + str = 999.ToString() + End Sub + + Sub Main() + DoSomething(lst(1)) + Debug.Assert(lst(1) = 4.ToString()) + DoSomething(lst2(1)) + Debug.Assert(lst2(1) = 5.ToString()) + End Sub + +End Class + +Friend Module Other + Public lst As List(Of String) = New List(Of String)({ 1.ToString(), 2.ToString(), 3.ToString()}) + Public lst2 As List(Of Object) = New List(Of Object)({ 1.ToString(), 2.ToString(), 3.ToString()}) +End Module", @"using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.VisualBasic.CompilerServices; + +public partial class Issue567 +{ + public void DoSomething(ref string str) + { + Other.lst = new List(new[] { 4.ToString(), 5.ToString(), 6.ToString() }); + Other.lst2 = new List(new[] { 4.ToString(), 5.ToString(), 6.ToString() }); + str = 999.ToString(); + } + + public void Main() + { + var tmp = Other.lst; + string argstr = tmp[1]; + DoSomething(ref argstr); + tmp[1] = argstr; + Debug.Assert((Other.lst[1] ?? """") == (4.ToString() ?? """")); + var tmp1 = Other.lst2; + string argstr1 = Conversions.ToString(tmp1[1]); + DoSomething(ref argstr1); + tmp1[1] = argstr1; + Debug.Assert(Conversions.ToBoolean(Operators.ConditionalCompareObjectEqual(Other.lst2[1], 5.ToString(), false))); + } +} + +internal static partial class Other +{ + public static List lst = new List(new[] { 1.ToString(), 2.ToString(), 3.ToString() }); + public static List lst2 = new List(new[] { 1.ToString(), 2.ToString(), 3.ToString() }); +}"); + } + } +} diff --git a/Tests/CSharp/ExpressionTests/ExpressionTests.cs b/Tests/CSharp/ExpressionTests/ExpressionTests.cs index c323b0b06..75ffcf1be 100644 --- a/Tests/CSharp/ExpressionTests/ExpressionTests.cs +++ b/Tests/CSharp/ExpressionTests/ExpressionTests.cs @@ -74,323 +74,6 @@ private void Y([Optional, DateTimeConstant(627667488000000000/* Global.Issue213. }"); } - [Fact] - public async Task OptionalRefDateConstsWithOmittedArgListAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Class Issue213 - Const x As Date = #1990-1-1# - - Private Sub Y(Optional ByRef opt As Date = x) - End Sub - - Private Sub CallsY() - Y - End Sub -End Class", @"using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -public partial class Issue213 -{ - private static DateTime x = DateTime.Parse(""1990-01-01""); - - private void Y([Optional, DateTimeConstant(627667488000000000/* Global.Issue213.x */)] ref DateTime opt) - { - } - - private void CallsY() - { - DateTime argopt = DateTime.Parse(""1990-01-01""); - Y(opt: ref argopt); - } -}"); - } - - [Fact] - public async Task NullInlineRefArgumentAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Class VisualBasicClass - Public Sub UseStuff() - Stuff(Nothing) - End Sub - - Public Sub Stuff(ByRef strs As String()) - End Sub -End Class", @" -public partial class VisualBasicClass -{ - public void UseStuff() - { - string[] argstrs = null; - Stuff(ref argstrs); - } - - public void Stuff(ref string[] strs) - { - } -}"); - } - - [Fact] - public async Task RefArgumentRValueAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Class Class1 - Private Property C1 As Class1 - Private _c2 As Class1 - Private _o1 As Object - - Sub Foo() - Bar(New Class1) - Bar(C1) - Bar(Me.C1) - Bar(_c2) - Bar(Me._c2) - Bar(_o1) - Bar(Me._o1) - End Sub - - Sub Bar(ByRef class1) - End Sub -End Class", @" -public partial class Class1 -{ - private Class1 C1 { get; set; } - - private Class1 _c2; - private object _o1; - - public void Foo() - { - object argclass1 = new Class1(); - Bar(ref argclass1); - object argclass11 = C1; - Bar(ref argclass11); - object argclass12 = C1; - Bar(ref argclass12); - object argclass13 = _c2; - Bar(ref argclass13); - object argclass14 = _c2; - Bar(ref argclass14); - Bar(ref _o1); - Bar(ref _o1); - } - - public void Bar(ref object class1) - { - } -}"); - } - - [Fact] - public async Task RefArgumentRValue2Async() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Class Class1 - Sub Foo() - Dim x = True - Bar(x = True) - End Sub - - Function Foo2() - Return Bar(True = False) - End Function - - Sub Foo3() - If Bar(True = False) Then Bar(True = False) - End Sub - - Sub Foo4() - If Bar(True = False) Then - Bar(True = False) - ElseIf Bar(True = False) Then - Bar(True = False) - Else - Bar(True = False) - End If - End Sub - - Sub Foo5() - Bar(Nothing) - End Sub - - Function Bar(ByRef b As Boolean) As Boolean - Return True - End Function - - Function Bar2(ByRef c1 As Class1) As Integer - If c1 IsNot Nothing AndAlso Len(Bar3(Me)) <> 0 Then - Return 1 - End If - Return 0 - End Function - - Function Bar3(ByRef c1 As Class1) As String - Return """" - End Function - -End Class", @"using Microsoft.VisualBasic; - -public partial class Class1 -{ - public void Foo() - { - bool x = true; - bool argb = x == true; - Bar(ref argb); - } - - public object Foo2() - { - bool argb = true == false; - return Bar(ref argb); - } - - public void Foo3() - { - bool argb1 = true == false; - if (Bar(ref argb1)) - { - bool argb = true == false; - Bar(ref argb); - } - } - - public void Foo4() - { - bool argb3 = true == false; - bool argb4 = true == false; - if (Bar(ref argb3)) - { - bool argb = true == false; - Bar(ref argb); - } - else if (Bar(ref argb4)) - { - bool argb2 = true == false; - Bar(ref argb2); - } - else - { - bool argb1 = true == false; - Bar(ref argb1); - } - } - - public void Foo5() - { - bool argb = default; - Bar(ref argb); - } - - public bool Bar(ref bool b) - { - return true; - } - - public int Bar2(ref Class1 c1) - { - var argc1 = this; - if (c1 is object && Strings.Len(Bar3(ref argc1)) != 0) - { - return 1; - } - - return 0; - } - - public string Bar3(ref Class1 c1) - { - return """"; - } -}"); - } - - [Fact] - public async Task RefArgumentUsingAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Imports System.Data.SqlClient - -Public Class Class1 - Sub Foo() - Using x = New SqlConnection - Bar(x) - End Using - End Sub - Sub Bar(ByRef x As SqlConnection) - - End Sub -End Class", @"using System.Data.SqlClient; - -public partial class Class1 -{ - public void Foo() - { - using (var x = new SqlConnection()) - { - var argx = x; - Bar(ref argx); - } - } - - public void Bar(ref SqlConnection x) - { - } -}"); - } - - [Fact] - public async Task RefOptionalArgumentAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Class OptionalRefIssue91 - Public Shared Function TestSub(Optional ByRef IsDefault As Boolean = False) As Boolean - End Function - - Public Shared Function CallingFunc() As Boolean - Return TestSub() AndAlso TestSub(True) - End Function -End Class", @"using System.Runtime.InteropServices; - -public partial class OptionalRefIssue91 -{ - public static bool TestSub([Optional, DefaultParameterValue(false)] ref bool IsDefault) - { - return default; - } - - public static bool CallingFunc() - { - bool argIsDefault = false; - bool argIsDefault1 = true; - return TestSub(IsDefault: ref argIsDefault) && TestSub(ref argIsDefault1); - } -}"); - } - - [Fact] - public async Task RefArgumentPropertyInitializerAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Class Class1 - Private _p1 As Class1 = Foo(New Class1) - Public Shared Function Foo(ByRef c1 As Class1) As Class1 - Return c1 - End Function -End Class", @" -public partial class Class1 -{ - static Class1 Foo__p1() - { - var argc1 = new Class1(); - return Foo(ref argc1); - } - - private Class1 _p1 = Foo__p1(); - - public static Class1 Foo(ref Class1 c1) - { - return c1; - } -}"); - } - [Fact] public async Task MethodCallWithImplicitConversionAsync() {