diff --git a/CodeConverter/CSharp/AccessorDeclarationNodeConverter.cs b/CodeConverter/CSharp/AccessorDeclarationNodeConverter.cs new file mode 100644 index 00000000..84607e25 --- /dev/null +++ b/CodeConverter/CSharp/AccessorDeclarationNodeConverter.cs @@ -0,0 +1,542 @@ +using ICSharpCode.CodeConverter.Util.FromRoslyn; +using Microsoft.CodeAnalysis.VisualBasic; + +namespace ICSharpCode.CodeConverter.CSharp; + +internal class AccessorDeclarationNodeConverter +{ + private static readonly SyntaxToken SemicolonToken = CS.SyntaxFactory.Token(CS.SyntaxKind.SemicolonToken); + private readonly SemanticModel _semanticModel; + private readonly Func, bool, CSSyntax.IdentifierNameSyntax, Task>> _convertMethodBodyStatementsAsync; + private readonly Dictionary _additionalDeclarations; + + private CommonConversions CommonConversions { get; } + private CommentConvertingVisitorWrapper TriviaConvertingDeclarationVisitor { get; } + public HashSet AccessedThroughMyClass { get; set; } + + + public AccessorDeclarationNodeConverter(SemanticModel semanticModel, CommonConversions commonConversions, CommentConvertingVisitorWrapper triviaConvertingDeclarationVisitor, + Dictionary additionalDeclarations, + Func, bool, CSSyntax.IdentifierNameSyntax, Task>> + convertMethodBodyStatementsAsync) + { + _semanticModel = semanticModel; + _additionalDeclarations = additionalDeclarations; + _convertMethodBodyStatementsAsync = convertMethodBodyStatementsAsync; + CommonConversions = commonConversions; + TriviaConvertingDeclarationVisitor = triviaConvertingDeclarationVisitor; + } + + public async Task ConvertPropertyStatementAsync(VBSyntax.PropertyStatementSyntax node) + { + var attributes = CS.SyntaxFactory.List(await node.AttributeLists.SelectManyAsync(CommonConversions.ConvertAttributeAsync)); + var isReadonly = node.Modifiers.Any(m => m.IsKind(VBasic.SyntaxKind.ReadOnlyKeyword)); + var isWriteOnly = node.Modifiers.Any(m => m.IsKind(VBasic.SyntaxKind.WriteOnlyKeyword)); + var convertibleModifiers = node.Modifiers.Where(m => !m.IsKind(VBasic.SyntaxKind.ReadOnlyKeyword, VBasic.SyntaxKind.WriteOnlyKeyword, VBasic.SyntaxKind.DefaultKeyword)); + var modifiers = CommonConversions.ConvertModifiers(node, convertibleModifiers.ToList(), node.GetMemberContext()); + var isIndexer = CommonConversions.IsDefaultIndexer(node); + var propSymbol = ModelExtensions.GetDeclaredSymbol(_semanticModel, node) as IPropertySymbol; + var accessedThroughMyClass = IsAccessedThroughMyClass(node, node.Identifier, propSymbol); + + var directlyConvertedCsIdentifier = CommonConversions.CsEscapedIdentifier(node.Identifier.Value as string); + var additionalDeclarations = new List(); + + var hasExplicitInterfaceImplementation = propSymbol.IsNonPublicInterfaceImplementation() || propSymbol.IsRenamedInterfaceMember(directlyConvertedCsIdentifier, propSymbol.ExplicitInterfaceImplementations); + var additionalInterfaceImplements = propSymbol.ExplicitInterfaceImplementations; + directlyConvertedCsIdentifier = hasExplicitInterfaceImplementation ? directlyConvertedCsIdentifier : CommonConversions.ConvertIdentifier(node.Identifier); + + var explicitInterfaceModifiers = modifiers.RemoveWhere(m => m.IsCsMemberVisibility() || m.IsKind(CS.SyntaxKind.VirtualKeyword, CS.SyntaxKind.AbstractKeyword) || m.IsKind(CS.SyntaxKind.OverrideKeyword, CS.SyntaxKind.NewKeyword)); + var shouldConvertToMethods = ShouldConvertAsParameterizedProperty(node); + var (initializer, vbType) = await GetVbReturnTypeAsync(node); + + var rawType = await vbType.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor) + ?? CS.SyntaxFactory.PredefinedType(CS.SyntaxFactory.Token(CS.SyntaxKind.ObjectKeyword)); + + CSSyntax.AccessorListSyntax accessors; + if (node.Parent is VBSyntax.PropertyBlockSyntax propertyBlock) { + if (shouldConvertToMethods) { + if (accessedThroughMyClass) { + // Would need to create a delegating implementation to implement this + throw new NotImplementedException("MyClass indexing not implemented"); + } + var methodDeclarationSyntaxs = await propertyBlock.Accessors.SelectAsync(async a => + await a.AcceptAsync(TriviaConvertingDeclarationVisitor, SourceTriviaMapKind.All)); + var accessorMethods = methodDeclarationSyntaxs.Select(WithMergedModifiers).ToArray(); + + if (hasExplicitInterfaceImplementation) { + accessorMethods + .Zip(propertyBlock.Accessors, Tuple.Create) + .Do(x => { + var (method, accessor) = x; + AddRemainingInterfaceDeclarations(method, attributes, explicitInterfaceModifiers, additionalInterfaceImplements, additionalDeclarations, accessor.Kind()); + }); + } + + _additionalDeclarations.Add(propertyBlock, accessorMethods.Skip(1).Concat(additionalDeclarations).ToArray()); + + return accessorMethods[0]; + } + + var convertedAccessors = await propertyBlock.Accessors.SelectAsync(async a => + await a.AcceptAsync(TriviaConvertingDeclarationVisitor)); + accessors = CS.SyntaxFactory.AccessorList(CS.SyntaxFactory.List(convertedAccessors)); + + } else if (shouldConvertToMethods && propSymbol.ContainingType.IsInterfaceType()) { + var methodDeclarationSyntaxs = new List(); + + if (propSymbol.GetMethod != null) { + methodDeclarationSyntaxs.Add(await CreateMethodDeclarationSyntaxAsync(node.ParameterList, GetMethodId(node.Identifier.Text), false)); + } + + if (propSymbol.SetMethod != null) { + var setMethod = await CreateMethodDeclarationSyntaxAsync(node.ParameterList, SetMethodId(node.Identifier.Text), true); + setMethod = AddValueSetParameter(propSymbol, setMethod, rawType, hasExplicitInterfaceImplementation); + methodDeclarationSyntaxs.Add(setMethod); + } + + _additionalDeclarations.Add(node, methodDeclarationSyntaxs.Skip(1).ToArray()); + + return methodDeclarationSyntaxs[0]; + } else { + bool allowPrivateAccessorForDirectAccess = node.Modifiers.All(m => !m.IsKind(VBasic.SyntaxKind.MustOverrideKeyword, VBasic.SyntaxKind.OverridesKeyword)) && + node.GetAncestor() == null; + accessors = ConvertSimpleAccessors(isWriteOnly, isReadonly, allowPrivateAccessorForDirectAccess, propSymbol.DeclaredAccessibility); + } + + if (isIndexer) { + if (accessedThroughMyClass) { + // Not sure if this is possible + throw new NotImplementedException("MyClass indexing not implemented"); + } + + var parameters = await node.ParameterList.Parameters.SelectAsync(async p => await p.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor)); + var parameterList = CS.SyntaxFactory.BracketedParameterList(CS.SyntaxFactory.SeparatedList(parameters)); + return CS.SyntaxFactory.IndexerDeclaration( + CS.SyntaxFactory.List(attributes), + modifiers, + rawType, + null, + parameterList, + accessors + ); + } + + if (hasExplicitInterfaceImplementation) { + + var delegatingAccessorList = GetDelegatingAccessorList(directlyConvertedCsIdentifier, accessors); + foreach (var additionalInterface in additionalInterfaceImplements) { + var explicitInterfaceAccessors = new SyntaxList(); + if (additionalInterface.IsReadOnly) + explicitInterfaceAccessors = explicitInterfaceAccessors.Add(delegatingAccessorList.Single(t => t.IsKind(CS.SyntaxKind.GetAccessorDeclaration))); + else if (additionalInterface.IsWriteOnly) + explicitInterfaceAccessors = explicitInterfaceAccessors.Add(delegatingAccessorList.Single(t => t.IsKind(CS.SyntaxKind.SetAccessorDeclaration))); + else + explicitInterfaceAccessors = delegatingAccessorList; + + var interfaceDeclParams = new PropertyDeclarationParameters(attributes, explicitInterfaceModifiers, rawType, CS.SyntaxFactory.AccessorList(explicitInterfaceAccessors)); + AddInterfaceMemberDeclarations(additionalInterface, additionalDeclarations, interfaceDeclParams); + } + } + + if (accessedThroughMyClass) { + + var realModifiers = modifiers.RemoveWhere(m => m.IsKind(CS.SyntaxKind.PrivateKeyword)); + string csIdentifierName = AddRealPropertyDelegatingToMyClassVersion(additionalDeclarations, directlyConvertedCsIdentifier, attributes, realModifiers, rawType, isReadonly, isWriteOnly); + modifiers = modifiers.Remove(modifiers.Single(m => m.IsKind(CS.SyntaxKind.VirtualKeyword))); + directlyConvertedCsIdentifier = CS.SyntaxFactory.Identifier(csIdentifierName); + } + + if (additionalDeclarations.Any()) { + var declNode = (VBSyntax.StatementSyntax)node.FirstAncestorOrSelf() ?? node; + _additionalDeclarations.Add(declNode, additionalDeclarations.ToArray()); + } + + var semicolonToken = CS.SyntaxFactory.Token(initializer == null ? CS.SyntaxKind.None : CS.SyntaxKind.SemicolonToken); + return CS.SyntaxFactory.PropertyDeclaration( + attributes, + modifiers, + rawType, + explicitInterfaceSpecifier: null, + directlyConvertedCsIdentifier, + accessors, + null, + initializer, + semicolonToken); + + CSSyntax.MethodDeclarationSyntax WithMergedModifiers(CSSyntax.MethodDeclarationSyntax member) + { + SyntaxTokenList originalModifiers = member.GetModifiers(); + var hasVisibility = originalModifiers.Any(m => m.IsCsMemberVisibility()); + var modifiersToAdd = hasVisibility ? modifiers.Where(m => !m.IsCsMemberVisibility()) : modifiers; + var newModifiers = CS.SyntaxFactory.TokenList(originalModifiers.Concat(modifiersToAdd)); + return member.WithModifiers(newModifiers); + } + + async Task CreateMethodDeclarationSyntaxAsync(VBSyntax.ParameterListSyntax containingPropParameterList, string methodId, bool voidReturn) + { + var parameterListSyntax = await containingPropParameterList.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor); + var methodModifiers = CS.SyntaxFactory.TokenList(modifiers.Where(m => !m.IsCsVisibility(false, false))); + CSSyntax.MethodDeclarationSyntax methodDeclarationSyntax = CS.SyntaxFactory.MethodDeclaration(attributes, methodModifiers, + voidReturn ? CS.SyntaxFactory.PredefinedType(CS.SyntaxFactory.Token(CS.SyntaxKind.VoidKeyword)) : rawType, + null, + CS.SyntaxFactory.Identifier(methodId), null, + parameterListSyntax, CS.SyntaxFactory.List(), null, null) + .WithSemicolonToken(CS.SyntaxFactory.Token(CS.SyntaxKind.SemicolonToken)); + return methodDeclarationSyntax; + } + } + + public async Task ConvertPropertyBlockAsync(VBSyntax.PropertyBlockSyntax node) + { + var converted = await node.PropertyStatement.AcceptAsync(TriviaConvertingDeclarationVisitor, SourceTriviaMapKind.SubNodesOnly); + + if (converted is CSSyntax.MethodDeclarationSyntax) { + var first = (CSSyntax.MethodDeclarationSyntax)converted; + + var firstCsConvertedToken = first.GetFirstToken(); + var firstVbSourceToken = node.GetFirstToken(); + first = first.ReplaceToken(firstCsConvertedToken, firstCsConvertedToken.WithSourceMappingFrom(firstVbSourceToken)); + + var members = _additionalDeclarations[node]; + var last = members.OfType().LastOrDefault() ?? first; + var lastIx = members.ToList().IndexOf(last); + var lastIsFirst = lastIx < 0; + var lastCsConvertedToken = last.GetLastToken(); + var lastVbSourceToken = node.GetLastToken(); + last = last.ReplaceToken(lastCsConvertedToken, lastCsConvertedToken.WithSourceMappingFrom(lastVbSourceToken)); + + converted = lastIsFirst ? last : first; + if (!lastIsFirst) { + members[lastIx] = last; + } + } + + return converted; + } + + public async Task VisitAccessorBlockAsync(VBSyntax.AccessorBlockSyntax node) + { + CS.SyntaxKind blockKind; + bool isIterator = node.IsIterator(); + var ancestoryPropertyBlock = node.GetAncestor(); + var containingPropertyStmt = ancestoryPropertyBlock?.PropertyStatement; + var csReturnVariableOrNull = CommonConversions.GetRetVariableNameOrNull(node); + var convertedStatements = CS.SyntaxFactory.Block(await _convertMethodBodyStatementsAsync(node, node.Statements, isIterator, csReturnVariableOrNull)); + var body = WithImplicitReturnStatements(node, convertedStatements, csReturnVariableOrNull); + var attributes = await CommonConversions.ConvertAttributesAsync(node.AccessorStatement.AttributeLists); + var modifiers = CommonConversions.ConvertModifiers(node, node.AccessorStatement.Modifiers, TokenContext.Local); + var declaredPropSymbol = containingPropertyStmt != null ? _semanticModel.GetDeclaredSymbol(containingPropertyStmt) : null; + + string potentialMethodId; + var sourceMap = ancestoryPropertyBlock?.Accessors.FirstOrDefault() == node ? SourceTriviaMapKind.All : SourceTriviaMapKind.None; + var returnType = containingPropertyStmt?.AsClause is VBSyntax.SimpleAsClauseSyntax asClause ? + await asClause.Type.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor, sourceMap) : + CS.SyntaxFactory.PredefinedType(CS.SyntaxFactory.Token(CS.SyntaxKind.VoidKeyword)); + + switch (node.Kind()) { + case VBasic.SyntaxKind.GetAccessorBlock: + blockKind = CS.SyntaxKind.GetAccessorDeclaration; + potentialMethodId = GetMethodId(containingPropertyStmt.Identifier.Text); + + if (ShouldConvertAsParameterizedProperty(containingPropertyStmt)) { + var method = await CreateMethodDeclarationSyntax(containingPropertyStmt.ParameterList, false); + return method; + } + break; + case VBasic.SyntaxKind.SetAccessorBlock: + blockKind = CS.SyntaxKind.SetAccessorDeclaration; + potentialMethodId = SetMethodId(containingPropertyStmt.Identifier.Text); + + if (ShouldConvertAsParameterizedProperty(containingPropertyStmt)) { + var setMethod = await CreateMethodDeclarationSyntax(containingPropertyStmt.ParameterList, true); + return AddValueSetParameter(declaredPropSymbol, setMethod, returnType, false); + } + break; + case VBasic.SyntaxKind.AddHandlerAccessorBlock: + blockKind = CS.SyntaxKind.AddAccessorDeclaration; + break; + case VBasic.SyntaxKind.RemoveHandlerAccessorBlock: + blockKind = CS.SyntaxKind.RemoveAccessorDeclaration; + break; + case VBasic.SyntaxKind.RaiseEventAccessorBlock: + var eventStatement = ((VBSyntax.EventBlockSyntax)node.Parent).EventStatement; + var eventName = CommonConversions.ConvertIdentifier(eventStatement.Identifier).ValueText; + potentialMethodId = $"On{eventName}"; + return await CreateMethodDeclarationSyntax(node.AccessorStatement.ParameterList, true); + default: + throw new NotSupportedException(node.Kind().ToString()); + } + + return CS.SyntaxFactory.AccessorDeclaration(blockKind, attributes, modifiers, body); + + async Task CreateMethodDeclarationSyntax(VBSyntax.ParameterListSyntax containingPropParameterList, bool voidReturn) + { + var parameterListSyntax = await containingPropParameterList.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor, sourceMap); + + CSSyntax.MethodDeclarationSyntax methodDeclarationSyntax = CS.SyntaxFactory.MethodDeclaration(attributes, modifiers, + voidReturn ? CS.SyntaxFactory.PredefinedType(CS.SyntaxFactory.Token(CS.SyntaxKind.VoidKeyword)) : returnType, + explicitInterfaceSpecifier: null, + CS.SyntaxFactory.Identifier(potentialMethodId), null, + parameterListSyntax, CS.SyntaxFactory.List(), body, null); + return methodDeclarationSyntax; + } + } + + public async Task VisitAccessorStatementAsync(VBSyntax.AccessorStatementSyntax node) + { + return CS.SyntaxFactory.AccessorDeclaration(node.Kind().ConvertToken(), null); + } + + private void AddRemainingInterfaceDeclarations(CSSyntax.MethodDeclarationSyntax method, SyntaxList attributes, + SyntaxTokenList filteredModifiers, IEnumerable additionalInterfaceImplements, + ICollection additionalDeclarations, VBasic.SyntaxKind accessorKind) + { + var clause = ExpressionSyntaxExtensions.GetDelegatingClause(method.ParameterList, method.Identifier, false); + + additionalInterfaceImplements.Do(interfaceImplement => { + var isGetterMethodForParametrizedProperty = accessorKind == VBasic.SyntaxKind.GetAccessorBlock; + + if (interfaceImplement.IsReadOnly && !isGetterMethodForParametrizedProperty) + return; + if (interfaceImplement.IsWriteOnly && isGetterMethodForParametrizedProperty) + return; + + var identifier = CS.SyntaxFactory.Identifier(isGetterMethodForParametrizedProperty ? + GetMethodId(interfaceImplement.Name) : + SetMethodId(interfaceImplement.Name)); + var interfaceMethodDeclParams = new MethodDeclarationParameters(attributes, filteredModifiers, + method.ReturnType, method.TypeParameterList, method.ParameterList, method.ConstraintClauses, clause, identifier); + + AddInterfaceMemberDeclarations(interfaceImplement, additionalDeclarations, interfaceMethodDeclParams); + }); + } + + private async Task<(CSSyntax.EqualsValueClauseSyntax Initializer, VBSyntax.TypeSyntax VbType)> GetVbReturnTypeAsync(VBSyntax.PropertyStatementSyntax node) + { + var initializer = await node.Initializer.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor); + VBSyntax.TypeSyntax vbType; + switch (node.AsClause) + { + case VBSyntax.SimpleAsClauseSyntax c: + vbType = c.Type; + break; + case VBSyntax.AsNewClauseSyntax c: + initializer = CS.SyntaxFactory.EqualsValueClause( + await c.NewExpression.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor)); + vbType = VBasic.SyntaxExtensions.Type(c.NewExpression); + break; + case null: + vbType = null; + break; + default: + throw new NotImplementedException($"{node.AsClause.GetType().FullName} not implemented!"); + } + + return (initializer, vbType); + } + + private static SyntaxList GetDelegatingAccessorList(SyntaxToken csIdentifier, CSSyntax.AccessorListSyntax accessors) + { + var getArrowClause = ExpressionSyntaxExtensions.GetDelegatingClause(null, csIdentifier, false); + var setArrowClause = ExpressionSyntaxExtensions.GetDelegatingClause(null, csIdentifier, true); + + var getSetDict = new Dictionary { + {CS.SyntaxKind.GetAccessorDeclaration, getArrowClause}, + {CS.SyntaxKind.SetAccessorDeclaration, setArrowClause} + }; + + var delegatingAccessors = accessors.Accessors.Select(a => { + var attributes = a.AttributeLists; + var modifiers = a.Modifiers; + + var delegatingAccessor = CS.SyntaxFactory.AccessorDeclaration(a.Kind(), + attributes, modifiers, getSetDict[a.Kind()]).WithSemicolonToken(SemicolonToken); + + return delegatingAccessor; + }); + + return new SyntaxList(delegatingAccessors); + } + + private static string AddRealPropertyDelegatingToMyClassVersion(List additionalDeclarations, SyntaxToken csIdentifier, + SyntaxList attributes, SyntaxTokenList modifiers, CSSyntax.TypeSyntax rawType, bool readOnly, bool writeOnly) + { + var csIdentifierName = "MyClass" + csIdentifier.ValueText; + CSSyntax.ExpressionSyntax thisDotIdentifier = CS.SyntaxFactory.Identifier(csIdentifierName).GetSimpleMemberAccess(); + + var accessors = CS.SyntaxFactory.List(Array.Empty()); + if (readOnly || !writeOnly) { + var getReturn = CS.SyntaxFactory.Block(CS.SyntaxFactory.ReturnStatement(thisDotIdentifier)); + var getAccessor = CS.SyntaxFactory.AccessorDeclaration(CS.SyntaxKind.GetAccessorDeclaration, getReturn); + accessors = accessors.Add(getAccessor); + } + + if (writeOnly || !readOnly) { + var setValue = CS.SyntaxFactory.Block(CS.SyntaxFactory.ExpressionStatement( + CS.SyntaxFactory.AssignmentExpression(CS.SyntaxKind.SimpleAssignmentExpression, thisDotIdentifier, + ValidSyntaxFactory.IdentifierName(("value"))))); + var setAccessor = CS.SyntaxFactory.AccessorDeclaration(CS.SyntaxKind.SetAccessorDeclaration, setValue); + accessors = accessors.Add(setAccessor); + } + + var realAccessors = CS.SyntaxFactory.AccessorList(accessors); + var realDecl = CS.SyntaxFactory.PropertyDeclaration( + attributes, + modifiers, + rawType, + null, + csIdentifier, realAccessors, + null, + null, + CS.SyntaxFactory.Token(CS.SyntaxKind.None)); + + additionalDeclarations.Add(realDecl); + return csIdentifierName; + } + + private static CSSyntax.AccessorListSyntax ConvertSimpleAccessors(bool isWriteOnly, bool isReadonly, + bool allowPrivateAccessorForDirectAccess, Accessibility declaredAccessibility) + { + var getAccessor = CS.SyntaxFactory.AccessorDeclaration(CS.SyntaxKind.GetAccessorDeclaration) + .WithSemicolonToken(SemicolonToken); + var setAccessor = CS.SyntaxFactory.AccessorDeclaration(CS.SyntaxKind.SetAccessorDeclaration) + .WithSemicolonToken(SemicolonToken); + + if (isWriteOnly && declaredAccessibility != Accessibility.Private) { + getAccessor = getAccessor.AddModifiers(CS.SyntaxFactory.Token(CS.SyntaxKind.PrivateKeyword)); + } + + if (isReadonly && declaredAccessibility != Accessibility.Private) { + setAccessor = setAccessor.AddModifiers(CS.SyntaxFactory.Token(CS.SyntaxKind.PrivateKeyword)); + } + + // this could be improved by looking if there is actually a direct access somewhere + // if not we could skip generating private property accessor + var isReadOnlyInterface = !allowPrivateAccessorForDirectAccess && isReadonly; + var isWriteOnlyInterface = !allowPrivateAccessorForDirectAccess && isWriteOnly; + + if (isReadOnlyInterface) + return CS.SyntaxFactory.AccessorList(CS.SyntaxFactory.List(new[] { getAccessor })); + if (isWriteOnlyInterface) + return CS.SyntaxFactory.AccessorList(CS.SyntaxFactory.List(new[] { setAccessor })); + + return CS.SyntaxFactory.AccessorList(CS.SyntaxFactory.List(new[] { getAccessor, setAccessor })); + } + + private static CSSyntax.MethodDeclarationSyntax AddValueSetParameter(IPropertySymbol declaredPropSymbol, + CSSyntax.MethodDeclarationSyntax setMethod, CSSyntax.TypeSyntax returnType, bool hasExplicitInterfaceImplementation) + { + var valueParam = CS.SyntaxFactory.Parameter(CommonConversions.CsEscapedIdentifier("value")).WithType(returnType); + if ((declaredPropSymbol?.Parameters.Any(p => p.IsOptional) ?? false) && !hasExplicitInterfaceImplementation) valueParam = valueParam.WithDefault(CS.SyntaxFactory.EqualsValueClause(ValidSyntaxFactory.DefaultExpression)); + return setMethod.AddParameterListParameters(valueParam); + } + + private static string SetMethodId(string methodName) => $"set_{methodName}"; + private static string GetMethodId(string methodName) => $"get_{methodName}"; + + public static bool ShouldConvertAsParameterizedProperty(VBSyntax.PropertyStatementSyntax propStmt) + { + return propStmt.ParameterList?.Parameters.Any() == true + && !CommonConversions.IsDefaultIndexer(propStmt); + } + + public CSSyntax.BlockSyntax WithImplicitReturnStatements(VBSyntax.MethodBlockBaseSyntax node, CSSyntax.BlockSyntax convertedStatements, + CSSyntax.IdentifierNameSyntax csReturnVariableOrNull) + { + if (!node.MustReturn()) return convertedStatements; + if (_semanticModel.GetDeclaredSymbol(node) is { } ms && ms.ReturnsVoidOrAsyncTask()) { + return convertedStatements; + } + + + var preBodyStatements = new List(); + var postBodyStatements = new List(); + + var functionSym = ModelExtensions.GetDeclaredSymbol(_semanticModel, node); + if (functionSym != null) { + var returnType = CommonConversions.GetTypeSyntax(functionSym.GetReturnType()); + + if (csReturnVariableOrNull != null) { + var retDeclaration = CommonConversions.CreateVariableDeclarationAndAssignment( + csReturnVariableOrNull.Identifier.ValueText, CS.SyntaxFactory.DefaultExpression(returnType), + returnType); + preBodyStatements.Add(CS.SyntaxFactory.LocalDeclarationStatement(retDeclaration)); + } + + ControlFlowAnalysis controlFlowAnalysis = null; + if (!node.Statements.IsEmpty()) + controlFlowAnalysis = + ModelExtensions.AnalyzeControlFlow(_semanticModel, node.Statements.First(), node.Statements.Last()); + + bool mayNeedReturn = controlFlowAnalysis?.EndPointIsReachable != false; + if (mayNeedReturn) { + var csReturnExpression = csReturnVariableOrNull ?? + (CSSyntax.ExpressionSyntax)CS.SyntaxFactory.DefaultExpression(returnType); + postBodyStatements.Add(CS.SyntaxFactory.ReturnStatement(csReturnExpression)); + } + } + + var statements = preBodyStatements + .Concat(convertedStatements.Statements) + .Concat(postBodyStatements); + + return CS.SyntaxFactory.Block(statements); + } + + public bool IsAccessedThroughMyClass(SyntaxNode node, SyntaxToken identifier, ISymbol symbolOrNull) + { + bool accessedThroughMyClass = false; + if (symbolOrNull != null && symbolOrNull.IsVirtual && !symbolOrNull.IsAbstract) { + var classBlock = node.Ancestors().OfType().FirstOrDefault(); + if (classBlock != null) { + accessedThroughMyClass = AccessedThroughMyClass.Contains(identifier.Text); + } + } + + return accessedThroughMyClass; + } + + private void AddInterfaceMemberDeclarations(ISymbol interfaceImplement, + ICollection additionalDeclarations, + DeclarationParameters declParams) + { + var semicolonToken = CS.SyntaxFactory.Token(CS.SyntaxKind.SemicolonToken); + Func + declDelegate = declParams switch { + MethodDeclarationParameters methodParams => (explintfspec, identifier) + => CS.SyntaxFactory.MethodDeclaration(methodParams.Attributes, methodParams.Modifiers, + methodParams.ReturnType, explintfspec, identifier + , methodParams.TypeParameters, methodParams.ParameterList, methodParams.Constraints, null, + methodParams.ArrowClause, semicolonToken).WithoutSourceMapping(), + + PropertyDeclarationParameters propertyParams => (explintfspec, identifier) + => CS.SyntaxFactory.PropertyDeclaration(propertyParams.Attributes, propertyParams.Modifiers, + propertyParams.ReturnType, explintfspec, identifier, propertyParams.Accessors, + null, null).NormalizeWhitespace(), + + _ => throw new ArgumentOutOfRangeException(nameof(declParams), declParams, null) + }; + + AddMemberDeclaration(additionalDeclarations, interfaceImplement, declParams.Identifier, declDelegate); + } + + public void AddInterfaceMemberDeclarations(IEnumerable additionalInterfaceImplements, + ICollection additionalDeclarations, + DeclarationParameters declParams) + { + additionalInterfaceImplements.Do(interfaceImplement => AddInterfaceMemberDeclarations(interfaceImplement, additionalDeclarations, declParams)); + } + + private void AddMemberDeclaration(ICollection additionalDeclarations, + ISymbol interfaceImplement, SyntaxToken identifier, Func declDelegate) + { + var explicitInterfaceName = CommonConversions.GetFullyQualifiedNameSyntax(interfaceImplement.ContainingType); + var newExplicitInterfaceSpecifier = CS.SyntaxFactory.ExplicitInterfaceSpecifier(explicitInterfaceName); + var interfaceImplIdentifier = identifier == default + ? CS.SyntaxFactory.Identifier(interfaceImplement.Name) + : identifier; + + var declaration = declDelegate.Invoke(newExplicitInterfaceSpecifier, interfaceImplIdentifier); + additionalDeclarations.Add(declaration); + } +} \ No newline at end of file diff --git a/CodeConverter/CSharp/ArgumentConverter.cs b/CodeConverter/CSharp/ArgumentConverter.cs new file mode 100644 index 00000000..ac9e8f0e --- /dev/null +++ b/CodeConverter/CSharp/ArgumentConverter.cs @@ -0,0 +1,273 @@ +using Microsoft.VisualBasic; + +namespace ICSharpCode.CodeConverter.CSharp; + +internal class ArgumentConverter +{ + public CommonConversions CommonConversions { get; } + private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison; + private readonly ITypeContext _typeContext; + private readonly SemanticModel _semanticModel; + private CommentConvertingVisitorWrapper TriviaConvertingExpressionVisitor { get; } + + public ArgumentConverter(VisualBasicEqualityComparison visualBasicEqualityComparison, ITypeContext typeContext, SemanticModel semanticModel, CommonConversions commonConversions) + { + CommonConversions = commonConversions; + _visualBasicEqualityComparison = visualBasicEqualityComparison; + _typeContext = typeContext; + _semanticModel = semanticModel; + TriviaConvertingExpressionVisitor = commonConversions.TriviaConvertingExpressionVisitor; + } + + public async Task ConvertSimpleArgumentAsync(VBSyntax.SimpleArgumentSyntax node) + { + var argList = (VBasic.Syntax.ArgumentListSyntax)node.Parent; + var invocation = argList.Parent; + if (invocation is VBasic.Syntax.ArrayCreationExpressionSyntax) + return await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); + var symbol = GetInvocationSymbol(invocation); + SyntaxToken token = default(SyntaxToken); + var convertedArgExpression = (await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor)).SkipIntoParens(); + var typeConversionAnalyzer = CommonConversions.TypeConversionAnalyzer; + var baseSymbol = symbol?.OriginalDefinition.GetBaseSymbol(); + var possibleParameters = (CommonConversions.GetCsOriginalSymbolOrNull(baseSymbol) ?? symbol)?.GetParameters(); + if (possibleParameters.HasValue) { + var refType = _semanticModel.GetRefConversionType(node, argList, possibleParameters.Value, out var argName, out var refKind); + token = CommonConversions.GetRefToken(refKind); + if (refType != SemanticModelExtensions.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 ? CS.SyntaxFactory.NameColon(await node.NameColonEquals.Name.AcceptAsync(TriviaConvertingExpressionVisitor)) : null; + return CS.SyntaxFactory.Argument(nameColon, token, convertedArgExpression); + } + + public async Task> ConvertArgumentsAsync(VBasic.Syntax.ArgumentListSyntax node) + { + ISymbol invocationSymbol = GetInvocationSymbol(node.Parent); + var forceNamedParameters = false; + var invocationHasOverloads = invocationSymbol.HasOverloads(); + + var processedParameters = new HashSet(StringComparer.OrdinalIgnoreCase); + var argumentSyntaxs = (await node.Arguments.SelectAsync(ConvertArg)).Where(a => a != null); + return Enumerable.Concat(argumentSyntaxs, GetAdditionalRequiredArgs(node.Arguments, processedParameters, invocationSymbol, invocationHasOverloads)); + + async Task ConvertArg(VBSyntax.ArgumentSyntax arg, int argIndex) + { + var argName = arg is VBSyntax.SimpleArgumentSyntax { IsNamed: true } namedArg ? namedArg.NameColonEquals.Name.Identifier.Text : null; + var parameterSymbol = invocationSymbol?.GetParameters().GetArgument(argName, argIndex); + var convertedArg = await ConvertArgForParameter(arg, parameterSymbol); + + if (convertedArg is not null && parameterSymbol is not null) { + processedParameters.Add(parameterSymbol.Name); + } + + return convertedArg; + } + + async Task ConvertArgForParameter(VBSyntax.ArgumentSyntax arg, IParameterSymbol parameterSymbol) + { + if (arg.IsOmitted) { + if (invocationSymbol != null && !invocationHasOverloads) { + forceNamedParameters = true; + return null; //Prefer to skip omitted and use named parameters when the symbol has only one overload + } + return ConvertOmittedArgument(parameterSymbol); + } + + var argSyntax = await arg.AcceptAsync(TriviaConvertingExpressionVisitor); + if (forceNamedParameters && !arg.IsNamed && parameterSymbol != null) { + return argSyntax.WithNameColon(CS.SyntaxFactory.NameColon(CS.SyntaxFactory.IdentifierName(CommonConversions.CsEscapedIdentifier(parameterSymbol.Name)))); + } + + return argSyntax; + } + + CSSyntax.ArgumentSyntax ConvertOmittedArgument(IParameterSymbol parameter) + { + if (parameter == null) { + return CS.SyntaxFactory.Argument(CS.SyntaxFactory.LiteralExpression(CS.SyntaxKind.DefaultLiteralExpression)); + } + + var csRefKind = CommonConversions.GetCsRefKind(parameter); + return csRefKind != RefKind.None + ? CreateOptionalRefArg(parameter, csRefKind) + : CS.SyntaxFactory.Argument(CommonConversions.Literal(parameter.ExplicitDefaultValue)); + } + } + + public async Task ToAttributeArgumentAsync(VBasic.Syntax.ArgumentSyntax arg) + { + if (!(arg is VBasic.Syntax.SimpleArgumentSyntax)) + throw new NotSupportedException(); + var a = (VBasic.Syntax.SimpleArgumentSyntax)arg; + var attr = CS.SyntaxFactory.AttributeArgument(await a.Expression.AcceptAsync(TriviaConvertingExpressionVisitor)); + if (a.IsNamed) { + attr = attr.WithNameEquals(CS.SyntaxFactory.NameEquals(await a.NameColonEquals.Name.AcceptAsync(TriviaConvertingExpressionVisitor))); + } + return attr; + } + + public async Task ConvertArgumentListOrEmptyAsync(SyntaxNode node, VBSyntax.ArgumentListSyntax argumentList) + { + return await argumentList.AcceptAsync(TriviaConvertingExpressionVisitor) ?? CreateArgList(_semanticModel.GetSymbolInfo(node).Symbol); + } + + + private CSSyntax.ExpressionSyntax HoistByRefDeclaration(VBSyntax.SimpleArgumentSyntax node, CSSyntax.ExpressionSyntax refLValue, SemanticModelExtensions.RefConversion refType, string argName, RefKind refKind) + { + string prefix = $"arg{argName}"; + var expressionTypeInfo = _semanticModel.GetTypeInfo(node.Expression); + bool useVar = expressionTypeInfo.Type?.Equals(expressionTypeInfo.ConvertedType, SymbolEqualityComparer.IncludeNullability) == true && !CommonConversions.ShouldPreferExplicitType(node.Expression, expressionTypeInfo.ConvertedType, out var _); + var typeSyntax = CommonConversions.GetTypeSyntax(expressionTypeInfo.ConvertedType, useVar); + + if (refLValue is CSSyntax.ElementAccessExpressionSyntax eae) { + //Hoist out the container so we can assign back to the same one after (like VB does) + var tmpContainer = _typeContext.PerScopeState.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 = _typeContext.PerScopeState.Hoist(new AdditionalDeclaration(prefix, withCast, typeSyntax)); + + if (refType == SemanticModelExtensions.RefConversion.PreAndPostAssignment) { + var convertedLocalIdentifier = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, local.IdentifierName, forceSourceType: expressionTypeInfo.ConvertedType, forceTargetType: expressionTypeInfo.Type); + _typeContext.PerScopeState.Hoist(new AdditionalAssignment(refLValue, convertedLocalIdentifier)); + } + + return local.IdentifierName; + } + + private ISymbol GetInvocationSymbol(SyntaxNode invocation) + { + var symbol = invocation.TypeSwitch( + (VBSyntax.InvocationExpressionSyntax e) => _semanticModel.GetSymbolInfo(e).ExtractBestMatch(), + (VBSyntax.ObjectCreationExpressionSyntax e) => _semanticModel.GetSymbolInfo(e).ExtractBestMatch(), + (VBSyntax.RaiseEventStatementSyntax e) => _semanticModel.GetSymbolInfo(e.Name).ExtractBestMatch(), + (VBSyntax.MidExpressionSyntax _) => CommonConversions.KnownTypes.VbCompilerStringType?.GetMembers("MidStmtStr").FirstOrDefault(), + _ => throw new NotSupportedException()); + return symbol; + } + + private IEnumerable GetAdditionalRequiredArgs( + IEnumerable arguments, + ISymbol invocationSymbol) + { + var invocationHasOverloads = invocationSymbol.HasOverloads(); + return GetAdditionalRequiredArgs(arguments, processedParametersNames: null, invocationSymbol, invocationHasOverloads); + } + + private IEnumerable GetAdditionalRequiredArgs( + IEnumerable arguments, + ICollection processedParametersNames, + ISymbol invocationSymbol, + bool invocationHasOverloads) + { + if (invocationSymbol is null) { + yield break; + } + + var invocationHasOmittedArgs = arguments.Any(t => t.IsOmitted); + var expandOptionalArgs = invocationHasOmittedArgs && invocationHasOverloads; + var missingArgs = invocationSymbol.GetParameters().Where(t => processedParametersNames is null || !processedParametersNames.Contains(t.Name)); + var requiresCompareMethod = _visualBasicEqualityComparison.OptionCompareTextCaseInsensitive && RequiresStringCompareMethodToBeAppended(invocationSymbol); + + foreach (var parameterSymbol in missingArgs) { + var extraArg = CreateExtraArgOrNull(parameterSymbol, requiresCompareMethod, expandOptionalArgs); + if (extraArg != null) { + yield return extraArg; + } + } + } + + + private static bool RequiresStringCompareMethodToBeAppended(ISymbol symbol) => + symbol?.ContainingType.Name == nameof(Strings) && + symbol.ContainingType.ContainingNamespace.Name == nameof(Microsoft.VisualBasic) && + symbol.ContainingType.ContainingNamespace.ContainingNamespace.Name == nameof(Microsoft) && + symbol.Name is "InStr" or "InStrRev" or "Replace" or "Split" or "StrComp"; + + private CSSyntax.ArgumentSyntax CreateExtraArgOrNull(IParameterSymbol p, bool requiresCompareMethod, bool expandOptionalArgs) + { + var csRefKind = CommonConversions.GetCsRefKind(p); + if (csRefKind != RefKind.None) { + return CreateOptionalRefArg(p, csRefKind); + } + + if (requiresCompareMethod && p.Type.GetFullMetadataName() == "Microsoft.VisualBasic.CompareMethod") { + return (CSSyntax.ArgumentSyntax)CommonConversions.CsSyntaxGenerator.Argument(p.Name, RefKind.None, _visualBasicEqualityComparison.CompareMethodExpression); + } + + if (expandOptionalArgs && p.HasExplicitDefaultValue) { + return (CSSyntax.ArgumentSyntax)CommonConversions.CsSyntaxGenerator.Argument(p.Name, RefKind.None, CommonConversions.Literal(p.ExplicitDefaultValue)); + } + + return null; + } + + private CSSyntax.ArgumentSyntax CreateOptionalRefArg(IParameterSymbol p, RefKind refKind) + { + string prefix = $"arg{p.Name}"; + var type = CommonConversions.GetTypeSyntax(p.Type); + CSSyntax.ExpressionSyntax initializer; + if (p.HasExplicitDefaultValue) { + initializer = CommonConversions.Literal(p.ExplicitDefaultValue); + } else if (HasOptionalAttribute(p)) { + if (TryGetDefaultParameterValueAttributeValue(p, out var defaultValue)) { + initializer = CommonConversions.Literal(defaultValue); + } else { + initializer = CS.SyntaxFactory.DefaultExpression(type); + } + } else { + //invalid VB.NET code + return null; + } + var local = _typeContext.PerScopeState.Hoist(new AdditionalDeclaration(prefix, initializer, type)); + return (CSSyntax.ArgumentSyntax)CommonConversions.CsSyntaxGenerator.Argument(p.Name, refKind, local.IdentifierName); + + bool HasOptionalAttribute(IParameterSymbol p) + { + var optionalAttribute = CommonConversions.KnownTypes.OptionalAttribute; + if (optionalAttribute == null) { + return false; + } + + return p.GetAttributes().Any(a => SymbolEqualityComparer.IncludeNullability.Equals(a.AttributeClass, optionalAttribute)); + } + + bool TryGetDefaultParameterValueAttributeValue(IParameterSymbol p, out object defaultValue) + { + defaultValue = null; + + var defaultParameterValueAttribute = CommonConversions.KnownTypes.DefaultParameterValueAttribute; + if (defaultParameterValueAttribute == null) { + return false; + } + + var attributeData = p.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.IncludeNullability.Equals(a.AttributeClass, defaultParameterValueAttribute)); + if (attributeData == null) { + return false; + } + + if (attributeData.ConstructorArguments.Length == 0) { + return false; + } + + defaultValue = attributeData.ConstructorArguments.First().Value; + return true; + } + } + + public CSSyntax.ArgumentListSyntax CreateArgList(ISymbol invocationSymbol) + { + return CS.SyntaxFactory.ArgumentList(CS.SyntaxFactory.SeparatedList( + GetAdditionalRequiredArgs(Array.Empty(), invocationSymbol)) + ); + } +} \ No newline at end of file diff --git a/CodeConverter/CSharp/BinaryExpressionConverter.cs b/CodeConverter/CSharp/BinaryExpressionConverter.cs new file mode 100644 index 00000000..f02c085f --- /dev/null +++ b/CodeConverter/CSharp/BinaryExpressionConverter.cs @@ -0,0 +1,114 @@ +using ICSharpCode.CodeConverter.Util.FromRoslyn; + +namespace ICSharpCode.CodeConverter.CSharp; + +internal class BinaryExpressionConverter +{ + private readonly SemanticModel _semanticModel; + private readonly IOperatorConverter _operatorConverter; + private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison; + private readonly VisualBasicNullableExpressionsConverter _visualBasicNullableTypesConverter; + public CommonConversions CommonConversions { get; } + + public CommentConvertingVisitorWrapper TriviaConvertingExpressionVisitor { get; } + + public BinaryExpressionConverter(SemanticModel semanticModel, IOperatorConverter operatorConverter, VisualBasicEqualityComparison visualBasicEqualityComparison, + VisualBasicNullableExpressionsConverter visualBasicNullableTypesConverter, CommonConversions commonConversions) + { + CommonConversions = commonConversions; + _semanticModel = semanticModel; + _operatorConverter = operatorConverter; + _visualBasicEqualityComparison = visualBasicEqualityComparison; + _visualBasicNullableTypesConverter = visualBasicNullableTypesConverter; + TriviaConvertingExpressionVisitor = commonConversions.TriviaConvertingExpressionVisitor; + } + + public async Task ConvertBinaryExpressionAsync(VBSyntax.BinaryExpressionSyntax entryNode) + { + // Walk down the syntax tree for deeply nested binary expressions to avoid stack overflow + // e.g. 3 + 4 + 5 + ... + // Test "DeeplyNestedBinaryExpressionShouldNotStackOverflowAsync()" skipped because it's too slow + + CSSyntax.ExpressionSyntax csLhs = null; + int levelsToConvert = 0; + VBSyntax.BinaryExpressionSyntax currentNode = entryNode; + + // Walk down the nested levels to count them + for (var nextNode = entryNode; nextNode != null; currentNode = nextNode, nextNode = currentNode.Left as VBSyntax.BinaryExpressionSyntax, levelsToConvert++) { + // Don't go beyond a rewritten operator because that code has many paths that can call VisitBinaryExpression. Passing csLhs through all of that would harm the code quality more than it's worth to help that edge case. + if (await RewriteBinaryOperatorOrNullAsync(nextNode) is { } operatorNode) { + csLhs = operatorNode; + break; + } + } + + // Walk back up the same levels converting as we go. + for (; levelsToConvert > 0; currentNode = currentNode!.Parent as VBSyntax.BinaryExpressionSyntax, levelsToConvert--) { + csLhs = (CSSyntax.ExpressionSyntax)await ConvertBinaryExpressionAsync(currentNode, csLhs); + } + + return csLhs; + } + + private async Task ConvertBinaryExpressionAsync(VBasic.Syntax.BinaryExpressionSyntax node, CSSyntax.ExpressionSyntax lhs, CSSyntax.ExpressionSyntax rhs = null) + { + lhs ??= await node.Left.AcceptAsync(TriviaConvertingExpressionVisitor); + rhs ??= await node.Right.AcceptAsync(TriviaConvertingExpressionVisitor); + + var lhsTypeInfo = _semanticModel.GetTypeInfo(node.Left); + var rhsTypeInfo = _semanticModel.GetTypeInfo(node.Right); + + ITypeSymbol forceLhsTargetType = null; + bool omitRightConversion = false; + bool omitConversion = false; + if (lhsTypeInfo.Type != null && rhsTypeInfo.Type != null) + { + if (node.IsKind(VBasic.SyntaxKind.ConcatenateExpression) && + !lhsTypeInfo.Type.IsEnumType() && !rhsTypeInfo.Type.IsEnumType() && + !lhsTypeInfo.Type.IsDateType() && !rhsTypeInfo.Type.IsDateType()) + { + omitRightConversion = true; + omitConversion = lhsTypeInfo.Type.SpecialType == SpecialType.System_String || + rhsTypeInfo.Type.SpecialType == SpecialType.System_String; + if (lhsTypeInfo.ConvertedType.SpecialType != SpecialType.System_String) { + forceLhsTargetType = CommonConversions.KnownTypes.String; + } + } + } + + var objectEqualityType = _visualBasicEqualityComparison.GetObjectEqualityType(node, lhsTypeInfo, rhsTypeInfo); + + switch (objectEqualityType) { + case VisualBasicEqualityComparison.RequiredType.StringOnly: + if (lhsTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String && + rhsTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String && + _visualBasicEqualityComparison.TryConvertToNullOrEmptyCheck(node, lhs, rhs, out CSharpSyntaxNode visitBinaryExpression)) { + return visitBinaryExpression; + } + (lhs, rhs) = _visualBasicEqualityComparison.AdjustForVbStringComparison(node.Left, lhs, lhsTypeInfo, false, node.Right, rhs, rhsTypeInfo, false); + omitConversion = true; // Already handled within for the appropriate types (rhs can become int in comparison) + break; + case VisualBasicEqualityComparison.RequiredType.Object: + return _visualBasicEqualityComparison.GetFullExpressionForVbObjectComparison(lhs, rhs, VisualBasicEqualityComparison.ComparisonKind.Equals, node.IsKind(VBasic.SyntaxKind.NotEqualsExpression)); + } + + var lhsTypeIgnoringNullable = lhsTypeInfo.Type.GetNullableUnderlyingType() ?? lhsTypeInfo.Type; + var rhsTypeIgnoringNullable = rhsTypeInfo.Type.GetNullableUnderlyingType() ?? rhsTypeInfo.Type; + omitConversion |= lhsTypeIgnoringNullable != null && rhsTypeIgnoringNullable != null && + lhsTypeIgnoringNullable.IsEnumType() && SymbolEqualityComparer.Default.Equals(lhsTypeIgnoringNullable, rhsTypeIgnoringNullable) + && !node.IsKind(VBasic.SyntaxKind.AddExpression, VBasic.SyntaxKind.SubtractExpression, VBasic.SyntaxKind.MultiplyExpression, VBasic.SyntaxKind.DivideExpression, VBasic.SyntaxKind.IntegerDivideExpression, VBasic.SyntaxKind.ModuloExpression) + && forceLhsTargetType == null; + lhs = omitConversion ? lhs : CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Left, lhs, forceTargetType: forceLhsTargetType); + rhs = omitConversion || omitRightConversion ? rhs : CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, rhs); + + var kind = VBasic.VisualBasicExtensions.Kind(node).ConvertToken(); + var op = CS.SyntaxFactory.Token(CSharpUtil.GetExpressionOperatorTokenKind(kind)); + + var csBinExp = CS.SyntaxFactory.BinaryExpression(kind, lhs, op, rhs); + var exp = _visualBasicNullableTypesConverter.WithBinaryExpressionLogicForNullableTypes(node, lhsTypeInfo, rhsTypeInfo, csBinExp, lhs, rhs); + return node.Parent.IsKind(VBasic.SyntaxKind.SimpleArgument) ? exp : exp.AddParens(); + } + + private async Task RewriteBinaryOperatorOrNullAsync(VBSyntax.BinaryExpressionSyntax node) => + await _operatorConverter.ConvertRewrittenBinaryOperatorOrNullAsync(node, TriviaConvertingExpressionVisitor.IsWithinQuery); +} \ No newline at end of file diff --git a/CodeConverter/CSharp/CommonConversions.cs b/CodeConverter/CSharp/CommonConversions.cs index 8636907f..28af3713 100644 --- a/CodeConverter/CSharp/CommonConversions.cs +++ b/CodeConverter/CSharp/CommonConversions.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.Simplification; using ArgumentListSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax.ArgumentListSyntax; using ArrayRankSpecifierSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.ArrayRankSpecifierSyntax; using ArrayTypeSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.ArrayTypeSyntax; @@ -760,4 +761,39 @@ public bool IsLinqDelegateExpression(VisualBasicSyntaxNode node) } private bool IsLinqDelegateExpression(ITypeSymbol convertedType) =>KnownTypes.System_Linq_Expressions_Expression_T?.Equals(convertedType?.OriginalDefinition, SymbolEqualityComparer.Default) == true; + + public static SyntaxToken GetRefToken(RefKind refKind) + { + SyntaxToken token; + switch (refKind) { + case RefKind.None: + token = default(SyntaxToken); + break; + case RefKind.Ref: + token = SyntaxFactory.Token(CSSyntaxKind.RefKeyword); + break; + case RefKind.Out: + token = SyntaxFactory.Token(CSSyntaxKind.OutKeyword); + break; + default: + throw new ArgumentOutOfRangeException(nameof(refKind), refKind, null); + } + + return token; + } + + public async Task WithRemovedRedundantConversionOrNullAsync(VBSyntax.ExpressionSyntax conversionNode, VBSyntax.ExpressionSyntax conversionArg) + { + var csharpArg = await conversionArg.AcceptAsync(TriviaConvertingExpressionVisitor); + var typeInfo = SemanticModel.GetTypeInfo(conversionNode); + + // If written by the user (i.e. not generated during expand phase), maintain intended semantics which could throw sometimes e.g. object o = (int) (object) long.MaxValue; + var writtenByUser = !conversionNode.HasAnnotation(Simplifier.Annotation); + var forceTargetType = typeInfo.ConvertedType; + // TypeConversionAnalyzer can't figure out which type is required for operator/method overloads, inferred func returns or inferred variable declarations + // (currently overapproximates for numeric and gets it wrong in non-numeric cases). + // Future: Avoid more redundant conversions by still calling AddExplicitConversion when writtenByUser avoiding the above and forcing typeInfo.Type + return writtenByUser ? null : this.TypeConversionAnalyzer.AddExplicitConversion(conversionArg, csharpArg, + forceTargetType: forceTargetType, defaultToCast: true); + } } \ No newline at end of file diff --git a/CodeConverter/CSharp/DeclarationNodeVisitor.cs b/CodeConverter/CSharp/DeclarationNodeVisitor.cs index a719080d..1863a4e0 100644 --- a/CodeConverter/CSharp/DeclarationNodeVisitor.cs +++ b/CodeConverter/CSharp/DeclarationNodeVisitor.cs @@ -7,6 +7,7 @@ using CSSyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind; using Microsoft.CodeAnalysis.VisualBasic; using ICSharpCode.CodeConverter.Util.FromRoslyn; +using ISymbolExtensions = ICSharpCode.CodeConverter.Util.ISymbolExtensions; namespace ICSharpCode.CodeConverter.CSharp; @@ -31,10 +32,10 @@ internal class DeclarationNodeVisitor : VBasic.VisualBasicSyntaxVisitor _extraUsingDirectives = new(); private readonly XmlImportContext _xmlImportContext; private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison; - private HashSet _accessedThroughMyClass; public CommentConvertingVisitorWrapper TriviaConvertingDeclarationVisitor { get; } private readonly CommentConvertingVisitorWrapper _triviaConvertingExpressionVisitor; private string _topAncestorNamespace; + private readonly AccessorDeclarationNodeConverter _accessorDeclarationNodeConverter; private CommonConversions CommonConversions { get; } private Func, bool, IdentifierNameSyntax, Task>> _convertMethodBodyStatementsAsync { get; } @@ -45,10 +46,10 @@ public DeclarationNodeVisitor(Document document, Compilation compilation, Semant CSharpCompilation csCompilation, SyntaxGenerator csSyntaxGenerator, ILookup typeToInheritors) { _vbCompilation = compilation; - _semanticModel = semanticModel; _csSyntaxGenerator = csSyntaxGenerator; _typeToInheritors = typeToInheritors; _xmlImportContext = new XmlImportContext(document); + _semanticModel = semanticModel; _visualBasicEqualityComparison = new VisualBasicEqualityComparison(_semanticModel, _extraUsingDirectives); TriviaConvertingDeclarationVisitor = new CommentConvertingVisitorWrapper(this, _semanticModel.SyntaxTree); var expressionEvaluator = new ExpressionEvaluator(semanticModel, _visualBasicEqualityComparison); @@ -56,9 +57,9 @@ public DeclarationNodeVisitor(Document document, Compilation compilation, Semant var typeConversionAnalyzer = new TypeConversionAnalyzer(semanticModel, csCompilation, _extraUsingDirectives, _csSyntaxGenerator, expressionEvaluator, nullableExpressionsConverter); CommonConversions = new CommonConversions(document, semanticModel, typeConversionAnalyzer, csSyntaxGenerator, compilation, csCompilation, _typeContext, _visualBasicEqualityComparison); var expressionNodeVisitor = new ExpressionNodeVisitor(semanticModel, _visualBasicEqualityComparison, _typeContext, CommonConversions, _extraUsingDirectives, _xmlImportContext, nullableExpressionsConverter); - _triviaConvertingExpressionVisitor = expressionNodeVisitor.TriviaConvertingExpressionVisitor; + _accessorDeclarationNodeConverter = new AccessorDeclarationNodeConverter(semanticModel, CommonConversions, TriviaConvertingDeclarationVisitor, _additionalDeclarations, expressionNodeVisitor.ConvertMethodBodyStatementsAsync); + _triviaConvertingExpressionVisitor = CommonConversions.TriviaConvertingExpressionVisitor; _convertMethodBodyStatementsAsync = expressionNodeVisitor.ConvertMethodBodyStatementsAsync; - CommonConversions.TriviaConvertingExpressionVisitor = _triviaConvertingExpressionVisitor; nullableExpressionsConverter.QueryTracker = _triviaConvertingExpressionVisitor; _visualBasicEqualityComparison.QueryTracker = _triviaConvertingExpressionVisitor; } @@ -75,6 +76,11 @@ public override async Task DefaultVisit(SyntaxNode node) .WithNodeInformation(node); } + public override Task VisitPropertyStatement(VBSyntax.PropertyStatementSyntax node) => _accessorDeclarationNodeConverter.ConvertPropertyStatementAsync(node); + public override Task VisitPropertyBlock(VBSyntax.PropertyBlockSyntax node) => _accessorDeclarationNodeConverter.ConvertPropertyBlockAsync(node); + public override Task VisitAccessorBlock(VBSyntax.AccessorBlockSyntax node) => _accessorDeclarationNodeConverter.VisitAccessorBlockAsync(node); + public override Task VisitAccessorStatement(VBSyntax.AccessorStatementSyntax node) => _accessorDeclarationNodeConverter.VisitAccessorStatementAsync(node); + public override async Task VisitCompilationUnit(VBSyntax.CompilationUnitSyntax node) { var options = (VBasic.VisualBasicCompilationOptions)_semanticModel.Compilation.Options; @@ -305,7 +311,7 @@ private MemberDeclarationSyntax[] GetAdditionalDeclarations(VBSyntax.StatementSy private async Task ConvertMemberAsync(VBSyntax.StatementSyntax member) { try { - var sourceTriviaMapKind = member is VBSyntax.PropertyBlockSyntax propBlock && ShouldConvertAsParameterizedProperty(propBlock.PropertyStatement) + var sourceTriviaMapKind = member is VBSyntax.PropertyBlockSyntax propBlock && AccessorDeclarationNodeConverter.ShouldConvertAsParameterizedProperty(propBlock.PropertyStatement) ? SourceTriviaMapKind.SubNodesOnly : SourceTriviaMapKind.All; return await member.AcceptAsync(TriviaConvertingDeclarationVisitor, sourceTriviaMapKind); @@ -323,7 +329,7 @@ var dummyClass public override async Task VisitClassBlock(VBSyntax.ClassBlockSyntax node) { - _accessedThroughMyClass = GetMyClassAccessedNames(node); + _accessorDeclarationNodeConverter.AccessedThroughMyClass = GetMyClassAccessedNames(node); var classStatement = node.ClassStatement; var attributes = await CommonConversions.ConvertAttributesAsync(classStatement.AttributeLists); var (parameters, constraints) = await SplitTypeParametersAsync(classStatement.TypeParameterList); @@ -507,7 +513,7 @@ private async Task> GetMemberDeclarationsAsync(VBS node.Modifiers.Where(m => !m.IsKind(VBasic.SyntaxKind.WithEventsKeyword)); var isWithEvents = node.Modifiers.Any(m => m.IsKind(VBasic.SyntaxKind.WithEventsKeyword)); var convertedModifiers = - CommonConversions.ConvertModifiers(node.Declarators[0].Names[0], convertableModifiers.ToList(), GetMemberContext(node)); + CommonConversions.ConvertModifiers(node.Declarators[0].Names[0], convertableModifiers.ToList(), node.GetMemberContext()); var declarations = new List(node.Declarators.Count); foreach (var declarator in node.Declarators) @@ -644,420 +650,6 @@ private async Task GetMethodWithHandlesAsync(VBSyntax.Typ return await HandledEventsAnalyzer.AnalyzeAsync(CommonConversions, containingType, designerGeneratedInitializeComponentOrNull, _typeToInheritors); } - public override async Task VisitPropertyStatement(VBSyntax.PropertyStatementSyntax node) - { - var attributes = SyntaxFactory.List(await node.AttributeLists.SelectManyAsync(CommonConversions.ConvertAttributeAsync)); - var isReadonly = node.Modifiers.Any(m => m.IsKind(VBasic.SyntaxKind.ReadOnlyKeyword)); - var isWriteOnly = node.Modifiers.Any(m => m.IsKind(VBasic.SyntaxKind.WriteOnlyKeyword)); - var convertibleModifiers = node.Modifiers.Where(m => !m.IsKind(VBasic.SyntaxKind.ReadOnlyKeyword, VBasic.SyntaxKind.WriteOnlyKeyword, VBasic.SyntaxKind.DefaultKeyword)); - var modifiers = CommonConversions.ConvertModifiers(node, convertibleModifiers.ToList(), GetMemberContext(node)); - var isIndexer = CommonConversions.IsDefaultIndexer(node); - var propSymbol = ModelExtensions.GetDeclaredSymbol(_semanticModel, node) as IPropertySymbol; - var accessedThroughMyClass = IsAccessedThroughMyClass(node, node.Identifier, propSymbol); - - var directlyConvertedCsIdentifier = CommonConversions.CsEscapedIdentifier(node.Identifier.Value as string); - var additionalDeclarations = new List(); - - var hasExplicitInterfaceImplementation = IsNonPublicInterfaceImplementation(propSymbol) || IsRenamedInterfaceMember(propSymbol, directlyConvertedCsIdentifier, propSymbol.ExplicitInterfaceImplementations); - var additionalInterfaceImplements = propSymbol.ExplicitInterfaceImplementations; - directlyConvertedCsIdentifier = hasExplicitInterfaceImplementation ? directlyConvertedCsIdentifier : CommonConversions.ConvertIdentifier(node.Identifier); - - var explicitInterfaceModifiers = modifiers.RemoveWhere(m => m.IsCsMemberVisibility() || m.IsKind(CSSyntaxKind.VirtualKeyword, CSSyntaxKind.AbstractKeyword) || m.IsKind(CSSyntaxKind.OverrideKeyword, CSSyntaxKind.NewKeyword)); - var shouldConvertToMethods = ShouldConvertAsParameterizedProperty(node); - var (initializer, vbType) = await GetVbReturnTypeAsync(node); - - var rawType = await vbType.AcceptAsync(_triviaConvertingExpressionVisitor) - ?? SyntaxFactory.PredefinedType(SyntaxFactory.Token(CSSyntaxKind.ObjectKeyword)); - - AccessorListSyntax accessors; - if (node.Parent is VBSyntax.PropertyBlockSyntax propertyBlock) { - if (shouldConvertToMethods) { - if (accessedThroughMyClass) { - // Would need to create a delegating implementation to implement this - throw new NotImplementedException("MyClass indexing not implemented"); - } - var methodDeclarationSyntaxs = await propertyBlock.Accessors.SelectAsync(async a => - await a.AcceptAsync(TriviaConvertingDeclarationVisitor, SourceTriviaMapKind.All)); - var accessorMethods = methodDeclarationSyntaxs.Select(WithMergedModifiers).ToArray(); - - if (hasExplicitInterfaceImplementation) { - accessorMethods - .Zip(propertyBlock.Accessors, Tuple.Create) - .Do(x => { - var (method, accessor) = x; - AddRemainingInterfaceDeclarations(method, attributes, explicitInterfaceModifiers, additionalInterfaceImplements, additionalDeclarations, accessor.Kind()); - }); - } - - _additionalDeclarations.Add(propertyBlock, accessorMethods.Skip(1).Concat(additionalDeclarations).ToArray()); - - return accessorMethods[0]; - } - - var convertedAccessors = await propertyBlock.Accessors.SelectAsync(async a => - await a.AcceptAsync(TriviaConvertingDeclarationVisitor)); - accessors = SyntaxFactory.AccessorList(SyntaxFactory.List(convertedAccessors)); - - } else if (shouldConvertToMethods && propSymbol.ContainingType.IsInterfaceType()) { - var methodDeclarationSyntaxs = new List(); - - if (propSymbol.GetMethod != null) { - methodDeclarationSyntaxs.Add(await CreateMethodDeclarationSyntaxAsync(node.ParameterList, GetMethodId(node.Identifier.Text), false)); - } - - if (propSymbol.SetMethod != null) { - var setMethod = await CreateMethodDeclarationSyntaxAsync(node.ParameterList, SetMethodId(node.Identifier.Text), true); - setMethod = AddValueSetParameter(propSymbol, setMethod, rawType, hasExplicitInterfaceImplementation); - methodDeclarationSyntaxs.Add(setMethod); - } - - _additionalDeclarations.Add(node, methodDeclarationSyntaxs.Skip(1).ToArray()); - - return methodDeclarationSyntaxs[0]; - } else { - bool allowPrivateAccessorForDirectAccess = node.Modifiers.All(m => !m.IsKind(VBasic.SyntaxKind.MustOverrideKeyword, VBasic.SyntaxKind.OverridesKeyword)) && - node.GetAncestor() == null; - accessors = ConvertSimpleAccessors(isWriteOnly, isReadonly, allowPrivateAccessorForDirectAccess, propSymbol.DeclaredAccessibility); - } - - if (isIndexer) { - if (accessedThroughMyClass) { - // Not sure if this is possible - throw new NotImplementedException("MyClass indexing not implemented"); - } - - var parameters = await node.ParameterList.Parameters.SelectAsync(async p => await p.AcceptAsync(_triviaConvertingExpressionVisitor)); - var parameterList = SyntaxFactory.BracketedParameterList(SyntaxFactory.SeparatedList(parameters)); - return SyntaxFactory.IndexerDeclaration( - SyntaxFactory.List(attributes), - modifiers, - rawType, - null, - parameterList, - accessors - ); - } - - if (hasExplicitInterfaceImplementation) { - - var delegatingAccessorList = GetDelegatingAccessorList(directlyConvertedCsIdentifier, accessors); - foreach (var additionalInterface in additionalInterfaceImplements) { - var explicitInterfaceAccessors = new SyntaxList(); - if (additionalInterface.IsReadOnly) - explicitInterfaceAccessors = explicitInterfaceAccessors.Add(delegatingAccessorList.Single(t => t.IsKind(CSSyntaxKind.GetAccessorDeclaration))); - else if (additionalInterface.IsWriteOnly) - explicitInterfaceAccessors = explicitInterfaceAccessors.Add(delegatingAccessorList.Single(t => t.IsKind(CSSyntaxKind.SetAccessorDeclaration))); - else - explicitInterfaceAccessors = delegatingAccessorList; - - var interfaceDeclParams = new PropertyDeclarationParameters(attributes, explicitInterfaceModifiers, rawType, SyntaxFactory.AccessorList(explicitInterfaceAccessors)); - AddInterfaceMemberDeclarations(additionalInterface, additionalDeclarations, interfaceDeclParams); - } - } - - if (accessedThroughMyClass) { - - var realModifiers = modifiers.RemoveWhere(m => m.IsKind(CSSyntaxKind.PrivateKeyword)); - string csIdentifierName = AddRealPropertyDelegatingToMyClassVersion(additionalDeclarations, directlyConvertedCsIdentifier, attributes, realModifiers, rawType, isReadonly, isWriteOnly); - modifiers = modifiers.Remove(modifiers.Single(m => m.IsKind(CSSyntaxKind.VirtualKeyword))); - directlyConvertedCsIdentifier = SyntaxFactory.Identifier(csIdentifierName); - } - - if (additionalDeclarations.Any()) { - var declNode = (VBSyntax.StatementSyntax)node.FirstAncestorOrSelf() ?? node; - _additionalDeclarations.Add(declNode, additionalDeclarations.ToArray()); - } - - var semicolonToken = SyntaxFactory.Token(initializer == null ? CSSyntaxKind.None : CSSyntaxKind.SemicolonToken); - return SyntaxFactory.PropertyDeclaration( - attributes, - modifiers, - rawType, - explicitInterfaceSpecifier: null, - directlyConvertedCsIdentifier, - accessors, - null, - initializer, - semicolonToken); - - MethodDeclarationSyntax WithMergedModifiers(MethodDeclarationSyntax member) - { - SyntaxTokenList originalModifiers = member.GetModifiers(); - var hasVisibility = originalModifiers.Any(m => m.IsCsMemberVisibility()); - var modifiersToAdd = hasVisibility ? modifiers.Where(m => !m.IsCsMemberVisibility()) : modifiers; - var newModifiers = SyntaxFactory.TokenList(originalModifiers.Concat(modifiersToAdd)); - return member.WithModifiers(newModifiers); - } - - async Task CreateMethodDeclarationSyntaxAsync(VBSyntax.ParameterListSyntax containingPropParameterList, string methodId, bool voidReturn) - { - var parameterListSyntax = await containingPropParameterList.AcceptAsync(_triviaConvertingExpressionVisitor); - var methodModifiers = SyntaxFactory.TokenList(modifiers.Where(m => !m.IsCsVisibility(false, false))); - MethodDeclarationSyntax methodDeclarationSyntax = SyntaxFactory.MethodDeclaration(attributes, methodModifiers, - voidReturn ? SyntaxFactory.PredefinedType(SyntaxFactory.Token(CSSyntaxKind.VoidKeyword)) : rawType, - null, - SyntaxFactory.Identifier(methodId), null, - parameterListSyntax, SyntaxFactory.List(), null, null) - .WithSemicolonToken(SyntaxFactory.Token(CSSyntaxKind.SemicolonToken)); - return methodDeclarationSyntax; - } - } - - private void AddRemainingInterfaceDeclarations(MethodDeclarationSyntax method, SyntaxList attributes, - SyntaxTokenList filteredModifiers, IEnumerable additionalInterfaceImplements, - ICollection additionalDeclarations, VBasic.SyntaxKind accessorKind) - { - var clause = GetDelegatingClause(method.Identifier, method.ParameterList, false); - - additionalInterfaceImplements.Do(interfaceImplement => { - var isGetterMethodForParametrizedProperty = accessorKind == VBasic.SyntaxKind.GetAccessorBlock; - - if (interfaceImplement.IsReadOnly && !isGetterMethodForParametrizedProperty) - return; - if (interfaceImplement.IsWriteOnly && isGetterMethodForParametrizedProperty) - return; - - var identifier = SyntaxFactory.Identifier(isGetterMethodForParametrizedProperty ? - GetMethodId(interfaceImplement.Name) : - SetMethodId(interfaceImplement.Name)); - var interfaceMethodDeclParams = new MethodDeclarationParameters(attributes, filteredModifiers, - method.ReturnType, method.TypeParameterList, method.ParameterList, method.ConstraintClauses, clause, identifier); - - AddInterfaceMemberDeclarations(interfaceImplement, additionalDeclarations, interfaceMethodDeclParams); - }); - } - - private async Task<(EqualsValueClauseSyntax Initializer, VBSyntax.TypeSyntax VbType)> GetVbReturnTypeAsync(VBSyntax.PropertyStatementSyntax node) - { - var initializer = await node.Initializer.AcceptAsync(_triviaConvertingExpressionVisitor); - VBSyntax.TypeSyntax vbType; - switch (node.AsClause) - { - case VBSyntax.SimpleAsClauseSyntax c: - vbType = c.Type; - break; - case VBSyntax.AsNewClauseSyntax c: - initializer = SyntaxFactory.EqualsValueClause( - await c.NewExpression.AcceptAsync(_triviaConvertingExpressionVisitor)); - vbType = VBasic.SyntaxExtensions.Type(c.NewExpression); - break; - case null: - vbType = null; - break; - default: - throw new NotImplementedException($"{node.AsClause.GetType().FullName} not implemented!"); - } - - return (initializer, vbType); - } - - private static SyntaxList GetDelegatingAccessorList(SyntaxToken csIdentifier, AccessorListSyntax accessors) - { - var getArrowClause = GetDelegatingClause(csIdentifier, null, false); - var setArrowClause = GetDelegatingClause(csIdentifier, null, true); - - var getSetDict = new Dictionary { - {CSSyntaxKind.GetAccessorDeclaration, getArrowClause}, - {CSSyntaxKind.SetAccessorDeclaration, setArrowClause} - }; - - var delegatingAccessors = accessors.Accessors.Select(a => { - var attributes = a.AttributeLists; - var modifiers = a.Modifiers; - - var delegatingAccessor = SyntaxFactory.AccessorDeclaration(a.Kind(), - attributes, modifiers, getSetDict[a.Kind()]).WithSemicolonToken(SemicolonToken); - - return delegatingAccessor; - }); - - return new SyntaxList(delegatingAccessors); - } - - private static string AddRealPropertyDelegatingToMyClassVersion(List additionalDeclarations, SyntaxToken csIdentifier, - SyntaxList attributes, SyntaxTokenList modifiers, TypeSyntax rawType, bool readOnly, bool writeOnly) - { - var csIdentifierName = "MyClass" + csIdentifier.ValueText; - ExpressionSyntax thisDotIdentifier = GetSimpleMemberAccess(SyntaxFactory.Identifier(csIdentifierName)); - - var accessors = SyntaxFactory.List(Array.Empty()); - if (readOnly || !writeOnly) { - var getReturn = SyntaxFactory.Block(SyntaxFactory.ReturnStatement(thisDotIdentifier)); - var getAccessor = SyntaxFactory.AccessorDeclaration(CSSyntaxKind.GetAccessorDeclaration, getReturn); - accessors = accessors.Add(getAccessor); - } - - if (writeOnly || !readOnly) { - var setValue = SyntaxFactory.Block(SyntaxFactory.ExpressionStatement( - SyntaxFactory.AssignmentExpression(CSSyntaxKind.SimpleAssignmentExpression, thisDotIdentifier, - ValidSyntaxFactory.IdentifierName(("value"))))); - var setAccessor = SyntaxFactory.AccessorDeclaration(CSSyntaxKind.SetAccessorDeclaration, setValue); - accessors = accessors.Add(setAccessor); - } - - var realAccessors = SyntaxFactory.AccessorList(accessors); - var realDecl = SyntaxFactory.PropertyDeclaration( - attributes, - modifiers, - rawType, - null, - csIdentifier, realAccessors, - null, - null, - SyntaxFactory.Token(CSSyntaxKind.None)); - - additionalDeclarations.Add(realDecl); - return csIdentifierName; - } - - private static AccessorListSyntax ConvertSimpleAccessors(bool isWriteOnly, bool isReadonly, - bool allowPrivateAccessorForDirectAccess, Accessibility declaredAccessibility) - { - var getAccessor = SyntaxFactory.AccessorDeclaration(CSSyntaxKind.GetAccessorDeclaration) - .WithSemicolonToken(SemicolonToken); - var setAccessor = SyntaxFactory.AccessorDeclaration(CSSyntaxKind.SetAccessorDeclaration) - .WithSemicolonToken(SemicolonToken); - - if (isWriteOnly && declaredAccessibility != Accessibility.Private) { - getAccessor = getAccessor.AddModifiers(SyntaxFactory.Token(CSSyntaxKind.PrivateKeyword)); - } - - if (isReadonly && declaredAccessibility != Accessibility.Private) { - setAccessor = setAccessor.AddModifiers(SyntaxFactory.Token(CSSyntaxKind.PrivateKeyword)); - } - - // this could be improved by looking if there is actually a direct access somewhere - // if not we could skip generating private property accessor - var isReadOnlyInterface = !allowPrivateAccessorForDirectAccess && isReadonly; - var isWriteOnlyInterface = !allowPrivateAccessorForDirectAccess && isWriteOnly; - - if (isReadOnlyInterface) - return SyntaxFactory.AccessorList(SyntaxFactory.List(new[] { getAccessor })); - if (isWriteOnlyInterface) - return SyntaxFactory.AccessorList(SyntaxFactory.List(new[] { setAccessor })); - - return SyntaxFactory.AccessorList(SyntaxFactory.List(new[] { getAccessor, setAccessor })); - } - - public override async Task VisitPropertyBlock(VBSyntax.PropertyBlockSyntax node) - { - var converted = await node.PropertyStatement.AcceptAsync(TriviaConvertingDeclarationVisitor, SourceTriviaMapKind.SubNodesOnly); - - if (converted is MethodDeclarationSyntax) { - var first = (MethodDeclarationSyntax)converted; - - var firstCsConvertedToken = first.GetFirstToken(); - var firstVbSourceToken = node.GetFirstToken(); - first = first.ReplaceToken(firstCsConvertedToken, firstCsConvertedToken.WithSourceMappingFrom(firstVbSourceToken)); - - var members = _additionalDeclarations[node]; - var last = members.OfType().LastOrDefault() ?? first; - var lastIx = members.ToList().IndexOf(last); - var lastIsFirst = lastIx < 0; - var lastCsConvertedToken = last.GetLastToken(); - var lastVbSourceToken = node.GetLastToken(); - last = last.ReplaceToken(lastCsConvertedToken, lastCsConvertedToken.WithSourceMappingFrom(lastVbSourceToken)); - - converted = lastIsFirst ? last : first; - if (!lastIsFirst) { - members[lastIx] = last; - } - } - - return converted; - } - - public override async Task VisitAccessorBlock(VBSyntax.AccessorBlockSyntax node) - { - CSSyntaxKind blockKind; - bool isIterator = node.IsIterator(); - var ancestoryPropertyBlock = node.GetAncestor(); - var containingPropertyStmt = ancestoryPropertyBlock?.PropertyStatement; - var csReturnVariableOrNull = CommonConversions.GetRetVariableNameOrNull(node); - var convertedStatements = SyntaxFactory.Block(await ConvertMethodBodyStatementsAsync(node, node.Statements, isIterator, csReturnVariableOrNull)); - var body = WithImplicitReturnStatements(node, convertedStatements, csReturnVariableOrNull); - var attributes = await CommonConversions.ConvertAttributesAsync(node.AccessorStatement.AttributeLists); - var modifiers = CommonConversions.ConvertModifiers(node, node.AccessorStatement.Modifiers, TokenContext.Local); - var declaredPropSymbol = containingPropertyStmt != null ? _semanticModel.GetDeclaredSymbol(containingPropertyStmt) : null; - - string potentialMethodId; - var sourceMap = ancestoryPropertyBlock?.Accessors.FirstOrDefault() == node ? SourceTriviaMapKind.All : SourceTriviaMapKind.None; - var returnType = containingPropertyStmt?.AsClause is VBSyntax.SimpleAsClauseSyntax asClause ? - await asClause.Type.AcceptAsync(_triviaConvertingExpressionVisitor, sourceMap) : - SyntaxFactory.PredefinedType(SyntaxFactory.Token(CSSyntaxKind.VoidKeyword)); - - switch (node.Kind()) { - case VBasic.SyntaxKind.GetAccessorBlock: - blockKind = CSSyntaxKind.GetAccessorDeclaration; - potentialMethodId = GetMethodId(containingPropertyStmt.Identifier.Text); - - if (ShouldConvertAsParameterizedProperty(containingPropertyStmt)) { - var method = await CreateMethodDeclarationSyntax(containingPropertyStmt.ParameterList, false); - return method; - } - break; - case VBasic.SyntaxKind.SetAccessorBlock: - blockKind = CSSyntaxKind.SetAccessorDeclaration; - potentialMethodId = SetMethodId(containingPropertyStmt.Identifier.Text); - - if (ShouldConvertAsParameterizedProperty(containingPropertyStmt)) { - var setMethod = await CreateMethodDeclarationSyntax(containingPropertyStmt.ParameterList, true); - return AddValueSetParameter(declaredPropSymbol, setMethod, returnType, false); - } - break; - case VBasic.SyntaxKind.AddHandlerAccessorBlock: - blockKind = CSSyntaxKind.AddAccessorDeclaration; - break; - case VBasic.SyntaxKind.RemoveHandlerAccessorBlock: - blockKind = CSSyntaxKind.RemoveAccessorDeclaration; - break; - case VBasic.SyntaxKind.RaiseEventAccessorBlock: - var eventStatement = ((VBSyntax.EventBlockSyntax)node.Parent).EventStatement; - var eventName = CommonConversions.ConvertIdentifier(eventStatement.Identifier).ValueText; - potentialMethodId = $"On{eventName}"; - return await CreateMethodDeclarationSyntax(node.AccessorStatement.ParameterList, true); - default: - throw new NotSupportedException(node.Kind().ToString()); - } - - return SyntaxFactory.AccessorDeclaration(blockKind, attributes, modifiers, body); - - async Task CreateMethodDeclarationSyntax(VBSyntax.ParameterListSyntax containingPropParameterList, bool voidReturn) - { - var parameterListSyntax = await containingPropParameterList.AcceptAsync(_triviaConvertingExpressionVisitor, sourceMap); - - MethodDeclarationSyntax methodDeclarationSyntax = SyntaxFactory.MethodDeclaration(attributes, modifiers, - voidReturn ? SyntaxFactory.PredefinedType(SyntaxFactory.Token(CSSyntaxKind.VoidKeyword)) : returnType, - explicitInterfaceSpecifier: null, - SyntaxFactory.Identifier(potentialMethodId), null, - parameterListSyntax, SyntaxFactory.List(), body, null); - return methodDeclarationSyntax; - } - } - - private static MethodDeclarationSyntax AddValueSetParameter(IPropertySymbol declaredPropSymbol, - MethodDeclarationSyntax setMethod, TypeSyntax returnType, bool hasExplicitInterfaceImplementation) - { - var valueParam = SyntaxFactory.Parameter(CommonConversions.CsEscapedIdentifier("value")).WithType(returnType); - if ((declaredPropSymbol?.Parameters.Any(p => p.IsOptional) ?? false) && !hasExplicitInterfaceImplementation) valueParam = valueParam.WithDefault(SyntaxFactory.EqualsValueClause(ValidSyntaxFactory.DefaultExpression)); - return setMethod.AddParameterListParameters(valueParam); - } - - private static string SetMethodId(string methodName) => $"set_{methodName}"; - - private static string GetMethodId(string methodName) => $"get_{methodName}"; - - private static bool ShouldConvertAsParameterizedProperty(VBSyntax.PropertyStatementSyntax propStmt) - { - return propStmt.ParameterList?.Parameters.Any() == true - && !CommonConversions.IsDefaultIndexer(propStmt); - } - - public override async Task VisitAccessorStatement(VBSyntax.AccessorStatementSyntax node) - { - return SyntaxFactory.AccessorDeclaration(node.Kind().ConvertToken(), null); - } - public override async Task VisitMethodBlock(VBSyntax.MethodBlockSyntax node) { var methodBlock = await node.SubOrFunctionStatement.AcceptAsync(TriviaConvertingDeclarationVisitor, SourceTriviaMapKind.SubNodesOnly); @@ -1075,7 +667,7 @@ public override async Task VisitMethodBlock(VBSyntax.MethodBlo convertedStatements = convertedStatements.InsertNodesBefore(firstResumeLayout, _typeContext.HandledEventsAnalysis.GetInitializeComponentClassEventHandlers()); } - var body = WithImplicitReturnStatements(node, convertedStatements, csReturnVariableOrNull); + var body = _accessorDeclarationNodeConverter.WithImplicitReturnStatements(node, convertedStatements, csReturnVariableOrNull); return methodBlock.WithBody(body); } @@ -1085,67 +677,11 @@ private static bool IsThisResumeLayoutInvocation(StatementSyntax s) return s is ExpressionStatementSyntax ess && ess.Expression is InvocationExpressionSyntax ies && ies.Expression.ToString().Equals("this.ResumeLayout", StringComparison.Ordinal); } - private BlockSyntax WithImplicitReturnStatements(VBSyntax.MethodBlockBaseSyntax node, BlockSyntax convertedStatements, - IdentifierNameSyntax csReturnVariableOrNull) - { - if (!node.MustReturn()) return convertedStatements; - if (_semanticModel.GetDeclaredSymbol(node) is { } ms && ms.ReturnsVoidOrAsyncTask()) { - return convertedStatements; - } - - - var preBodyStatements = new List(); - var postBodyStatements = new List(); - - var functionSym = ModelExtensions.GetDeclaredSymbol(_semanticModel, node); - if (functionSym != null) { - var returnType = CommonConversions.GetTypeSyntax(functionSym.GetReturnType()); - - if (csReturnVariableOrNull != null) { - var retDeclaration = CommonConversions.CreateVariableDeclarationAndAssignment( - csReturnVariableOrNull.Identifier.ValueText, SyntaxFactory.DefaultExpression(returnType), - returnType); - preBodyStatements.Add(SyntaxFactory.LocalDeclarationStatement(retDeclaration)); - } - - ControlFlowAnalysis controlFlowAnalysis = null; - if (!node.Statements.IsEmpty()) - controlFlowAnalysis = - ModelExtensions.AnalyzeControlFlow(_semanticModel, node.Statements.First(), node.Statements.Last()); - - bool mayNeedReturn = controlFlowAnalysis?.EndPointIsReachable != false; - if (mayNeedReturn) { - var csReturnExpression = csReturnVariableOrNull ?? - (ExpressionSyntax)SyntaxFactory.DefaultExpression(returnType); - postBodyStatements.Add(SyntaxFactory.ReturnStatement(csReturnExpression)); - } - } - - var statements = preBodyStatements - .Concat(convertedStatements.Statements) - .Concat(postBodyStatements); - - return SyntaxFactory.Block(statements); - } - private static async Task ConvertStatementsAsync(SyntaxList statements, VBasic.VisualBasicSyntaxVisitor>> methodBodyVisitor) { return SyntaxFactory.Block(await statements.SelectManyAsync(async s => (IEnumerable) await s.Accept(methodBodyVisitor))); } - private bool IsAccessedThroughMyClass(SyntaxNode node, SyntaxToken identifier, ISymbol symbolOrNull) - { - bool accessedThroughMyClass = false; - if (symbolOrNull != null && symbolOrNull.IsVirtual && !symbolOrNull.IsAbstract) { - var classBlock = node.Ancestors().OfType().FirstOrDefault(); - if (classBlock != null) { - accessedThroughMyClass = _accessedThroughMyClass.Contains(identifier.Text); - } - } - - return accessedThroughMyClass; - } - private static HashSet GetMyClassAccessedNames(VBSyntax.ClassBlockSyntax classBlock) { var memberAccesses = classBlock.DescendantNodes().OfType(); @@ -1169,12 +705,12 @@ public override async Task VisitMethodStatement(VBSyntax.Metho return hasBody ? declaration : declaration.WithSemicolonToken(SemicolonToken); } - var tokenContext = GetMemberContext(node); + var tokenContext = node.GetMemberContext(); var declaredSymbol = (IMethodSymbol)ModelExtensions.GetDeclaredSymbol(_semanticModel, node); var extraCsModifierKinds = declaredSymbol?.IsExtern == true ? new[] { CSSyntaxKind.ExternKeyword } : Array.Empty(); var convertedModifiers = CommonConversions.ConvertModifiers(node, node.Modifiers, tokenContext, extraCsModifierKinds: extraCsModifierKinds); - bool accessedThroughMyClass = IsAccessedThroughMyClass(node, node.Identifier, declaredSymbol); + bool accessedThroughMyClass = _accessorDeclarationNodeConverter.IsAccessedThroughMyClass(node, node.Identifier, declaredSymbol); if (declaredSymbol.IsPartialMethodImplementation() || declaredSymbol.IsPartialMethodDefinition()) { @@ -1195,22 +731,22 @@ public override async Task VisitMethodStatement(VBSyntax.Metho var parameterList = await node.ParameterList.AcceptAsync(_triviaConvertingExpressionVisitor) ?? SyntaxFactory.ParameterList(); var additionalDeclarations = new List(); - var hasExplicitInterfaceImplementation = IsNonPublicInterfaceImplementation(declaredSymbol) || IsRenamedInterfaceMember(declaredSymbol, directlyConvertedCsIdentifier, declaredSymbol.ExplicitInterfaceImplementations); + var hasExplicitInterfaceImplementation = declaredSymbol.IsNonPublicInterfaceImplementation() || declaredSymbol.IsRenamedInterfaceMember(directlyConvertedCsIdentifier, declaredSymbol.ExplicitInterfaceImplementations); directlyConvertedCsIdentifier = hasExplicitInterfaceImplementation ? directlyConvertedCsIdentifier : CommonConversions.ConvertIdentifier(node.Identifier); if (hasExplicitInterfaceImplementation) { - var delegatingClause = GetDelegatingClause(directlyConvertedCsIdentifier, parameterList, false); + var delegatingClause = ExpressionSyntaxExtensions.GetDelegatingClause(parameterList, directlyConvertedCsIdentifier, false); var explicitInterfaceModifiers = convertedModifiers.RemoveWhere(m => m.IsCsMemberVisibility() || m.IsKind(CSSyntaxKind.VirtualKeyword, CSSyntaxKind.AbstractKeyword) || m.IsKind(CSSyntaxKind.OverrideKeyword, CSSyntaxKind.NewKeyword)); var interfaceDeclParams = new MethodDeclarationParameters(attributes, explicitInterfaceModifiers, returnType, typeParameters, parameterList, constraints, delegatingClause); - AddInterfaceMemberDeclarations(declaredSymbol.ExplicitInterfaceImplementations, additionalDeclarations, interfaceDeclParams); + _accessorDeclarationNodeConverter.AddInterfaceMemberDeclarations(declaredSymbol.ExplicitInterfaceImplementations, additionalDeclarations, interfaceDeclParams); } // If the method is virtual, and there is a MyClass.SomeMethod() call, // we need to emit a non-virtual method for it to call if (accessedThroughMyClass) { var identifierName = "MyClass" + directlyConvertedCsIdentifier.ValueText; - var arrowClause = SyntaxFactory.ArrowExpressionClause(SyntaxFactory.InvocationExpression(ValidSyntaxFactory.IdentifierName(identifierName), CreateDelegatingArgList(parameterList))); + var arrowClause = SyntaxFactory.ArrowExpressionClause(SyntaxFactory.InvocationExpression(ValidSyntaxFactory.IdentifierName(identifierName), parameterList.CreateDelegatingArgList())); var declModifiers = convertedModifiers; var originalNameDecl = SyntaxFactory.MethodDeclaration( @@ -1253,143 +789,11 @@ public override async Task VisitMethodStatement(VBSyntax.Metho return hasBody && declaredSymbol.CanHaveMethodBody() ? decl : decl.WithSemicolonToken(SemicolonToken); } - private void AddInterfaceMemberDeclarations(ISymbol interfaceImplement, - ICollection additionalDeclarations, - DeclarationParameters declParams) - { - var semicolonToken = SyntaxFactory.Token(CSSyntaxKind.SemicolonToken); - Func - declDelegate = declParams switch { - MethodDeclarationParameters methodParams => (explintfspec, identifier) - => SyntaxFactory.MethodDeclaration(methodParams.Attributes, methodParams.Modifiers, - methodParams.ReturnType, explintfspec, identifier - , methodParams.TypeParameters, methodParams.ParameterList, methodParams.Constraints, null, - methodParams.ArrowClause, semicolonToken).WithoutSourceMapping(), - - PropertyDeclarationParameters propertyParams => (explintfspec, identifier) - => SyntaxFactory.PropertyDeclaration(propertyParams.Attributes, propertyParams.Modifiers, - propertyParams.ReturnType, explintfspec, identifier, propertyParams.Accessors, - null, null).NormalizeWhitespace(), - - _ => throw new ArgumentOutOfRangeException(nameof(declParams), declParams, null) - }; - - AddMemberDeclaration(additionalDeclarations, interfaceImplement, declParams.Identifier, declDelegate); - } - - private void AddInterfaceMemberDeclarations(IEnumerable additionalInterfaceImplements, - ICollection additionalDeclarations, - DeclarationParameters declParams) - { - additionalInterfaceImplements.Do(interfaceImplement => AddInterfaceMemberDeclarations(interfaceImplement, additionalDeclarations, declParams)); - } - - private void AddMemberDeclaration(ICollection additionalDeclarations, - ISymbol interfaceImplement, SyntaxToken identifier, Func declDelegate) - { - var explicitInterfaceName = CommonConversions.GetFullyQualifiedNameSyntax(interfaceImplement.ContainingType); - var newExplicitInterfaceSpecifier = SyntaxFactory.ExplicitInterfaceSpecifier(explicitInterfaceName); - var interfaceImplIdentifier = identifier == default - ? SyntaxFactory.Identifier(interfaceImplement.Name) - : identifier; - - var declaration = declDelegate.Invoke(newExplicitInterfaceSpecifier, interfaceImplIdentifier); - additionalDeclarations.Add(declaration); - } - - private static ArrowExpressionClauseSyntax GetDelegatingClause(SyntaxToken csIdentifier, - ParameterListSyntax parameterList, bool isSetAccessor) - { - if (parameterList != null && isSetAccessor) - throw new InvalidOperationException("Parameterized setters shouldn't have a delegating clause. " + - $"\r\nInvalid arguments: {nameof(isSetAccessor)} = {true}," + - $" {nameof(parameterList)} has {parameterList.Parameters.Count} parameters"); - - var simpleMemberAccess = GetSimpleMemberAccess(csIdentifier); - - var expression = parameterList != null - ? (ExpressionSyntax)SyntaxFactory.InvocationExpression(simpleMemberAccess, CreateDelegatingArgList(parameterList)) - : simpleMemberAccess; - - var arrowClauseExpression = isSetAccessor - ? SyntaxFactory.AssignmentExpression(CSSyntaxKind.SimpleAssignmentExpression, simpleMemberAccess, - ValidSyntaxFactory.IdentifierName("value")) - : expression; - - var arrowClause = SyntaxFactory.ArrowExpressionClause(arrowClauseExpression); - return arrowClause; - } - - private static MemberAccessExpressionSyntax GetSimpleMemberAccess(SyntaxToken csIdentifier) - { - var simpleMemberAccess = SyntaxFactory.MemberAccessExpression( - CSSyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.ThisExpression(), - SyntaxFactory.Token(CSSyntaxKind.DotToken), ValidSyntaxFactory.IdentifierName(csIdentifier)); - - return simpleMemberAccess; - } - - private static bool IsNonPublicInterfaceImplementation(ISymbol declaredSymbol) - { - return declaredSymbol switch { - IMethodSymbol methodSymbol => methodSymbol.DeclaredAccessibility != Accessibility.Public && - methodSymbol.ExplicitInterfaceImplementations.Any(), - IPropertySymbol propertySymbol => propertySymbol.DeclaredAccessibility != Accessibility.Public && - propertySymbol.ExplicitInterfaceImplementations.Any(), - _ => throw new ArgumentOutOfRangeException(nameof(declaredSymbol)) - }; - } - - private static bool IsRenamedInterfaceMember(ISymbol declaredSymbol, - SyntaxToken directlyConvertedCsIdentifier, IEnumerable explicitInterfaceImplementations) - { - bool IsRenamed(ISymbol csIdentifier) => - declaredSymbol switch { - IMethodSymbol methodSymbol => !StringComparer.OrdinalIgnoreCase.Equals(directlyConvertedCsIdentifier.Value, csIdentifier.Name) && methodSymbol.ExplicitInterfaceImplementations.Any(), - IPropertySymbol propertySymbol => !StringComparer.OrdinalIgnoreCase.Equals(directlyConvertedCsIdentifier.Value, csIdentifier.Name) && propertySymbol.ExplicitInterfaceImplementations.Any(), - _ => throw new ArgumentOutOfRangeException(nameof(declaredSymbol)) - }; - - return explicitInterfaceImplementations.Any(IsRenamed); - } - - private static ArgumentListSyntax CreateDelegatingArgList(ParameterListSyntax parameterList) - { - var refKinds = parameterList.Parameters.Select(GetSingleModifier).ToArray(); - return parameterList.Parameters.Select(p => ValidSyntaxFactory.IdentifierName(p.Identifier)).CreateCsArgList(refKinds); - } - - private static CSSyntaxKind? GetSingleModifier(ParameterSyntax p) - { - var argKinds = new CSSyntaxKind?[] { CSSyntaxKind.RefKeyword, CSSyntaxKind.OutKeyword, CSSyntaxKind.InKeyword }; - return p.Modifiers.Select(Microsoft.CodeAnalysis.CSharp.CSharpExtensions.Kind) - .Select(k => k) - .FirstOrDefault(argKinds.Contains); - } - - private static TokenContext GetMemberContext(VBSyntax.StatementSyntax member) - { - var parentType = member.GetAncestorOrThis(); - var parentTypeKind = parentType?.Kind(); - switch (parentTypeKind) { - case VBasic.SyntaxKind.ModuleBlock: - return TokenContext.MemberInModule; - case VBasic.SyntaxKind.ClassBlock: - return TokenContext.MemberInClass; - case VBasic.SyntaxKind.InterfaceBlock: - return TokenContext.MemberInInterface; - case VBasic.SyntaxKind.StructureBlock: - return TokenContext.MemberInStruct; - default: - throw new ArgumentOutOfRangeException(nameof(member)); - } - } - public override async Task VisitEventBlock(VBSyntax.EventBlockSyntax node) { var block = node.EventStatement; var attributes = await block.AttributeLists.SelectManyAsync(CommonConversions.ConvertAttributeAsync); - var modifiers = CommonConversions.ConvertModifiers(block, block.Modifiers, GetMemberContext(node)); + var modifiers = CommonConversions.ConvertModifiers(block, block.Modifiers, node.GetMemberContext()); var rawType = await (block.AsClause?.Type).AcceptAsync(_triviaConvertingExpressionVisitor) ?? ValidSyntaxFactory.VarType; @@ -1407,7 +811,7 @@ public override async Task VisitEventBlock(VBSyntax.EventBlock public override async Task VisitEventStatement(VBSyntax.EventStatementSyntax node) { var attributes = await node.AttributeLists.SelectManyAsync(CommonConversions.ConvertAttributeAsync); - var modifiers = CommonConversions.ConvertModifiers(node, node.Modifiers, GetMemberContext(node)); + var modifiers = CommonConversions.ConvertModifiers(node, node.Modifiers, node.GetMemberContext()); var id = CommonConversions.ConvertIdentifier(node.Identifier); var symbol = _semanticModel.GetDeclaredSymbol(node); @@ -1458,7 +862,7 @@ public override async Task VisitOperatorStatement(VBSyntax.Ope var parameterList = await node.ParameterList.AcceptAsync(_triviaConvertingExpressionVisitor); var methodBodyVisitor = await ConvertMethodBodyStatementsAsync(node, containingBlock.Statements); var body = SyntaxFactory.Block(methodBodyVisitor); - var modifiers = CommonConversions.ConvertModifiers(node, node.Modifiers, GetMemberContext(node)); + var modifiers = CommonConversions.ConvertModifiers(node, node.Modifiers, node.GetMemberContext()); var conversionModifiers = modifiers.Where(CommonConversions.IsConversionOperator).ToList(); var nonConversionModifiers = SyntaxFactory.TokenList(modifiers.Except(conversionModifiers)); @@ -1475,7 +879,7 @@ public override async Task VisitConstructorBlock(VBSyntax.Cons { var block = node.BlockStatement; var attributes = await block.AttributeLists.SelectManyAsync(CommonConversions.ConvertAttributeAsync); - var modifiers = CommonConversions.ConvertModifiers(block, block.Modifiers, GetMemberContext(node)); + var modifiers = CommonConversions.ConvertModifiers(block, block.Modifiers, node.GetMemberContext()); var ctor = (node.Statements.FirstOrDefault() as VBSyntax.ExpressionStatementSyntax)?.Expression as VBSyntax.InvocationExpressionSyntax; var ctorExpression = ctor?.Expression as VBSyntax.MemberAccessExpressionSyntax; @@ -1530,7 +934,7 @@ public override async Task VisitDeclareStatement(VBSyntax.Decl var attributeLists = (await CommonConversions.ConvertAttributesAsync(node.AttributeLists)).Add(dllImportAttributeList); - var tokenContext = GetMemberContext(node); + var tokenContext = node.GetMemberContext(); var modifiers = CommonConversions.ConvertModifiers(node, node.Modifiers, tokenContext); if (!modifiers.Any(m => m.IsKind(CSSyntaxKind.StaticKeyword))) { modifiers = modifiers.Add(SyntaxFactory.Token(CSSyntaxKind.StaticKeyword)); diff --git a/CodeConverter/CSharp/ExpressionNodeVisitor.cs b/CodeConverter/CSharp/ExpressionNodeVisitor.cs index dc1eb31e..0516e35d 100644 --- a/CodeConverter/CSharp/ExpressionNodeVisitor.cs +++ b/CodeConverter/CSharp/ExpressionNodeVisitor.cs @@ -1,18 +1,8 @@ -using System.Collections.Immutable; -using System.Data; -using System.Globalization; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Xml.Linq; -using ICSharpCode.CodeConverter.CSharp.Replacements; -using ICSharpCode.CodeConverter.Util.FromRoslyn; +using ICSharpCode.CodeConverter.Util.FromRoslyn; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Operations; -using Microsoft.CodeAnalysis.Simplification; -using Microsoft.VisualBasic; using Microsoft.VisualBasic.CompilerServices; -using ComparisonKind = ICSharpCode.CodeConverter.CSharp.VisualBasicEqualityComparison.ComparisonKind; namespace ICSharpCode.CodeConverter.CSharp; @@ -22,70 +12,42 @@ namespace ICSharpCode.CodeConverter.CSharp; /// http://source.roslyn.codeplex.com/#Microsoft.CodeAnalysis.CSharp/Binder/Binder_Expressions.cs,365 /// http://source.roslyn.codeplex.com/#Microsoft.CodeAnalysis.VisualBasic/Binding/Binder_Expressions.vb,43 /// -internal class ExpressionNodeVisitor : VBasic.VisualBasicSyntaxVisitor> +internal partial class ExpressionNodeVisitor : VBasic.VisualBasicSyntaxVisitor> { - private static readonly Type ConvertType = typeof(Conversions); - public CommentConvertingVisitorWrapper TriviaConvertingExpressionVisitor { get; } + public CommentConvertingVisitorWrapper TriviaConvertingExpressionVisitor => CommonConversions.TriviaConvertingExpressionVisitor; private readonly SemanticModel _semanticModel; private readonly HashSet _extraUsingDirectives; - private readonly XmlImportContext _xmlImportContext; private readonly IOperatorConverter _operatorConverter; - private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison; private readonly Stack _withBlockLhs = new(); private readonly ITypeContext _typeContext; private readonly QueryConverter _queryConverter; - private readonly Lazy> _convertMethodsLookupByReturnType; private readonly LambdaConverter _lambdaConverter; - private readonly VisualBasicNullableExpressionsConverter _visualBasicNullableTypesConverter; private readonly Dictionary> _tempNameForAnonymousScope = new(); private readonly HashSet _generatedNames = new(StringComparer.OrdinalIgnoreCase); + private readonly XmlExpressionConverter _xmlExpressionConverter; + private readonly NameExpressionNodeVisitor _nameExpressionNodeVisitor; + private readonly ArgumentConverter _argumentConverter; + private readonly BinaryExpressionConverter _binaryExpressionConverter; + private readonly InitializerConverter _initializerConverter; public ExpressionNodeVisitor(SemanticModel semanticModel, VisualBasicEqualityComparison visualBasicEqualityComparison, ITypeContext typeContext, CommonConversions commonConversions, HashSet extraUsingDirectives, XmlImportContext xmlImportContext, VisualBasicNullableExpressionsConverter visualBasicNullableTypesConverter) { - CommonConversions = commonConversions; _semanticModel = semanticModel; - _lambdaConverter = new LambdaConverter(commonConversions, semanticModel); - _visualBasicEqualityComparison = visualBasicEqualityComparison; - TriviaConvertingExpressionVisitor = new CommentConvertingVisitorWrapper(this, _semanticModel.SyntaxTree); + CommonConversions = commonConversions; + commonConversions.TriviaConvertingExpressionVisitor = new CommentConvertingVisitorWrapper(this, _semanticModel.SyntaxTree); + _initializerConverter = new InitializerConverter(semanticModel, commonConversions, _generatedNames, _tempNameForAnonymousScope); + _lambdaConverter = new LambdaConverter(commonConversions, semanticModel, _withBlockLhs, extraUsingDirectives, typeContext); _queryConverter = new QueryConverter(commonConversions, _semanticModel, TriviaConvertingExpressionVisitor); _typeContext = typeContext; _extraUsingDirectives = extraUsingDirectives; - _xmlImportContext = xmlImportContext; - _visualBasicNullableTypesConverter = visualBasicNullableTypesConverter; + _argumentConverter = new ArgumentConverter(visualBasicEqualityComparison, typeContext, semanticModel, commonConversions); + _xmlExpressionConverter = new XmlExpressionConverter(xmlImportContext, extraUsingDirectives, TriviaConvertingExpressionVisitor); + _nameExpressionNodeVisitor = new NameExpressionNodeVisitor(semanticModel, _generatedNames, typeContext, extraUsingDirectives, _tempNameForAnonymousScope, _withBlockLhs, commonConversions, _argumentConverter, TriviaConvertingExpressionVisitor); _operatorConverter = VbOperatorConversion.Create(TriviaConvertingExpressionVisitor, semanticModel, visualBasicEqualityComparison, commonConversions.TypeConversionAnalyzer); - // If this isn't needed, the assembly with Conversions may not be referenced, so this must be done lazily - _convertMethodsLookupByReturnType = - new Lazy>(() => CreateConvertMethodsLookupByReturnType(semanticModel)); - } - - private static IReadOnlyDictionary CreateConvertMethodsLookupByReturnType( - SemanticModel semanticModel) - { - // In some projects there's a source declaration as well as the referenced one, which causes the first of these methods to fail - var symbolsWithName = semanticModel.Compilation - .GetSymbolsWithName(n => n.Equals(ConvertType.Name, StringComparison.Ordinal), SymbolFilter.Type).ToList(); - - var convertType = - semanticModel.Compilation.GetTypeByMetadataName(ConvertType.FullName) ?? - (ITypeSymbol)symbolsWithName.FirstOrDefault(s => - s.ContainingNamespace.ToDisplayString().Equals(ConvertType.Namespace, StringComparison.Ordinal)); - - if (convertType is null) return ImmutableDictionary.Empty; - - var convertMethods = convertType.GetMembers().Where(m => - m.Name.StartsWith("To", StringComparison.Ordinal) && m.GetParameters().Length == 1); - -#pragma warning disable RS1024 // Compare symbols correctly - GroupBy and ToDictionary use the same logic to dedupe as to lookup, so it doesn't matter which equality is used - var methodsByType = convertMethods - .GroupBy(m => new { ReturnType = m.GetReturnType(), Name = $"{ConvertType.FullName}.{m.Name}" }) - .ToDictionary(m => m.Key.ReturnType, m => m.Key.Name); -#pragma warning restore RS1024 // Compare symbols correctly - - return methodsByType; + _binaryExpressionConverter = new BinaryExpressionConverter(semanticModel, _operatorConverter, visualBasicEqualityComparison, visualBasicNullableTypesConverter, commonConversions); } - public CommonConversions CommonConversions { get; } public override async Task DefaultVisit(SyntaxNode node) @@ -95,169 +57,42 @@ public override async Task DefaultVisit(SyntaxNode node) .WithNodeInformation(node); } - public override async Task VisitXmlEmbeddedExpression(VBSyntax.XmlEmbeddedExpressionSyntax node) => - await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); - - public override async Task VisitXmlDocument(VBasic.Syntax.XmlDocumentSyntax node) - { - _extraUsingDirectives.Add("System.Xml.Linq"); - var arguments = SyntaxFactory.SeparatedList( - (await node.PrecedingMisc.SelectAsync(async misc => SyntaxFactory.Argument(await misc.AcceptAsync(TriviaConvertingExpressionVisitor)))) - .Concat(SyntaxFactory.Argument(await node.Root.AcceptAsync(TriviaConvertingExpressionVisitor)).Yield()) - .Concat(await node.FollowingMisc.SelectAsync(async misc => SyntaxFactory.Argument(await misc.AcceptAsync(TriviaConvertingExpressionVisitor)))) - ); - return ApplyXmlImportsIfNecessary(node, SyntaxFactory.ObjectCreationExpression(ValidSyntaxFactory.IdentifierName("XDocument")).WithArgumentList(SyntaxFactory.ArgumentList(arguments))); - } - - public override async Task VisitXmlElement(VBasic.Syntax.XmlElementSyntax node) - { - _extraUsingDirectives.Add("System.Xml.Linq"); - var arguments = SyntaxFactory.SeparatedList( - SyntaxFactory.Argument(await node.StartTag.Name.AcceptAsync(TriviaConvertingExpressionVisitor)).Yield() - .Concat(await node.StartTag.Attributes.SelectAsync(async attribute => SyntaxFactory.Argument(await attribute.AcceptAsync(TriviaConvertingExpressionVisitor)))) - .Concat(await node.Content.SelectAsync(async content => SyntaxFactory.Argument(await content.AcceptAsync(TriviaConvertingExpressionVisitor)))) - ); - return ApplyXmlImportsIfNecessary(node, SyntaxFactory.ObjectCreationExpression(ValidSyntaxFactory.IdentifierName("XElement")).WithArgumentList(SyntaxFactory.ArgumentList(arguments))); - } - - public override async Task VisitXmlEmptyElement(VBSyntax.XmlEmptyElementSyntax node) - { - _extraUsingDirectives.Add("System.Xml.Linq"); - var arguments = SyntaxFactory.SeparatedList( - SyntaxFactory.Argument(await node.Name.AcceptAsync(TriviaConvertingExpressionVisitor)).Yield() - .Concat(await node.Attributes.SelectAsync(async attribute => SyntaxFactory.Argument(await attribute.AcceptAsync(TriviaConvertingExpressionVisitor)))) - ); - return ApplyXmlImportsIfNecessary(node, SyntaxFactory.ObjectCreationExpression(ValidSyntaxFactory.IdentifierName("XElement")).WithArgumentList(SyntaxFactory.ArgumentList(arguments))); - } - - private CSharpSyntaxNode ApplyXmlImportsIfNecessary(VBSyntax.XmlNodeSyntax vbNode, ObjectCreationExpressionSyntax creation) - { - if (!_xmlImportContext.HasImports || vbNode.Parent is VBSyntax.XmlNodeSyntax) return creation; - return SyntaxFactory.InvocationExpression( - SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, XmlImportContext.HelperClassShortIdentifierName, ValidSyntaxFactory.IdentifierName("Apply")), - SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(creation)))); - } - - public override async Task VisitXmlAttribute(VBasic.Syntax.XmlAttributeSyntax node) - { - var arguments = SyntaxFactory.SeparatedList( - SyntaxFactory.Argument(await node.Name.AcceptAsync(TriviaConvertingExpressionVisitor)).Yield() - .Concat(SyntaxFactory.Argument(await node.Value.AcceptAsync(TriviaConvertingExpressionVisitor)).Yield()) - ); - return SyntaxFactory.ObjectCreationExpression(ValidSyntaxFactory.IdentifierName("XAttribute")).WithArgumentList(SyntaxFactory.ArgumentList(arguments)); - } - - public override async Task VisitXmlString(VBasic.Syntax.XmlStringSyntax node) => - CommonConversions.Literal(string.Join("", node.TextTokens.Select(b => b.Text))); - public override async Task VisitXmlText(VBSyntax.XmlTextSyntax node) => - CommonConversions.Literal(string.Join("", node.TextTokens.Select(b => b.Text))); - - public override async Task VisitXmlCDataSection(VBSyntax.XmlCDataSectionSyntax node) - { - var xcDataTypeSyntax = SyntaxFactory.ParseTypeName(nameof(XCData)); - var argumentListSyntax = CommonConversions.Literal(string.Join("", node.TextTokens.Select( b=> b.Text))).Yield().CreateCsArgList(); - return SyntaxFactory.ObjectCreationExpression(xcDataTypeSyntax).WithArgumentList(argumentListSyntax); - } - - /// - /// https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/xml/accessing-xml - /// - public override async Task VisitXmlMemberAccessExpression( - VBasic.Syntax.XmlMemberAccessExpressionSyntax node) - { - _extraUsingDirectives.Add("System.Xml.Linq"); - - var xElementMethodName = GetXElementMethodName(node); - - ExpressionSyntax elements = node.Base != null ? SyntaxFactory.MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - await node.Base.AcceptAsync(TriviaConvertingExpressionVisitor), - ValidSyntaxFactory.IdentifierName(xElementMethodName) - ) : SyntaxFactory.MemberBindingExpression( - ValidSyntaxFactory.IdentifierName(xElementMethodName) - ); - - return SyntaxFactory.InvocationExpression(elements, - ExpressionSyntaxExtensions.CreateArgList( - await node.Name.AcceptAsync(TriviaConvertingExpressionVisitor)) - ); - } - - private static string GetXElementMethodName(VBSyntax.XmlMemberAccessExpressionSyntax node) - { - if (node.Token2 == default(SyntaxToken)) { - return "Elements"; - } - - if (node.Token2.Text == "@") { - return "Attributes"; - } - - if (node.Token2.Text == ".") { - return "Descendants"; - } - throw new NotImplementedException($"Xml member access operator: '{node.Token1}{node.Token2}{node.Token3}'"); - } - - public override Task VisitXmlBracketedName(VBSyntax.XmlBracketedNameSyntax node) - { - return node.Name.AcceptAsync(TriviaConvertingExpressionVisitor); - } - - public override async Task VisitXmlName(VBSyntax.XmlNameSyntax node) - { - if (node.Prefix != null) { - switch (node.Prefix.Name.ValueText) { - case "xml": - case "xmlns": - return SyntaxFactory.BinaryExpression( - SyntaxKind.AddExpression, - SyntaxFactory.MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - ValidSyntaxFactory.IdentifierName("XNamespace"), - ValidSyntaxFactory.IdentifierName(node.Prefix.Name.ValueText.ToPascalCase()) - ), - SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(node.LocalName.Text)) - ); - default: - return SyntaxFactory.BinaryExpression( - SyntaxKind.AddExpression, - SyntaxFactory.MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - XmlImportContext.HelperClassShortIdentifierName, - ValidSyntaxFactory.IdentifierName(node.Prefix.Name.ValueText) - ), - SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(node.LocalName.Text)) - ); - } - } - - if (_xmlImportContext.HasDefaultImport && node.Parent is not VBSyntax.XmlAttributeSyntax) { - return SyntaxFactory.BinaryExpression( - SyntaxKind.AddExpression, - SyntaxFactory.MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - XmlImportContext.HelperClassShortIdentifierName, - XmlImportContext.DefaultIdentifierName - ), - SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(node.LocalName.Text)) - ); - } - - return SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(node.LocalName.Text)); - } + public override Task VisitMemberAccessExpression(VBSyntax.MemberAccessExpressionSyntax node) => _nameExpressionNodeVisitor.ConvertMemberAccessExpressionAsync(node); + public override Task VisitGlobalName(VBSyntax.GlobalNameSyntax node) => _nameExpressionNodeVisitor.ConvertGlobalNameAsync(node); + public override Task VisitMeExpression(VBSyntax.MeExpressionSyntax node) => _nameExpressionNodeVisitor.ConvertMeExpressionAsync(node); + public override Task VisitMyBaseExpression(VBSyntax.MyBaseExpressionSyntax node) => _nameExpressionNodeVisitor.ConvertMyBaseExpressionAsync(node); + public override Task VisitGenericName(VBSyntax.GenericNameSyntax node) => _nameExpressionNodeVisitor.ConvertGenericNameAsync(node); + public override Task VisitQualifiedName(VBSyntax.QualifiedNameSyntax node) => _nameExpressionNodeVisitor.ConvertQualifiedNameAsync(node); + public override Task VisitIdentifierName(VBSyntax.IdentifierNameSyntax node) => _nameExpressionNodeVisitor.ConvertIdentifierNameAsync(node); + public override Task VisitInvocationExpression(VBSyntax.InvocationExpressionSyntax node) => _nameExpressionNodeVisitor.ConvertInvocationExpressionAsync(node); + public override Task VisitXmlEmbeddedExpression(VBSyntax.XmlEmbeddedExpressionSyntax node) => _xmlExpressionConverter.ConvertXmlEmbeddedExpressionAsync(node); + public override Task VisitXmlDocument(VBasic.Syntax.XmlDocumentSyntax node) => _xmlExpressionConverter.ConvertXmlDocumentAsync(node); + public override Task VisitXmlElement(VBasic.Syntax.XmlElementSyntax node) => _xmlExpressionConverter.ConvertXmlElementAsync(node); + public override Task VisitXmlEmptyElement(VBSyntax.XmlEmptyElementSyntax node) => _xmlExpressionConverter.ConvertXmlEmptyElementAsync(node); + public override Task VisitXmlAttribute(VBSyntax.XmlAttributeSyntax node) => _xmlExpressionConverter.ConvertXmlAttributeAsync(node); + public override Task VisitXmlString(VBSyntax.XmlStringSyntax node) => _xmlExpressionConverter.ConvertXmlStringAsync(node); + public override Task VisitXmlText(VBSyntax.XmlTextSyntax node) => _xmlExpressionConverter.ConvertXmlTextAsync(node); + public override Task VisitXmlCDataSection(VBSyntax.XmlCDataSectionSyntax node) => _xmlExpressionConverter.ConvertXmlCDataSectionAsync(node); + public override Task VisitXmlMemberAccessExpression(VBSyntax.XmlMemberAccessExpressionSyntax node) => _xmlExpressionConverter.ConvertXmlMemberAccessExpressionAsync(node); + public override Task VisitXmlBracketedName(VBSyntax.XmlBracketedNameSyntax node) => _xmlExpressionConverter.ConvertXmlBracketedNameAsync(node); + public override Task VisitXmlName(VBSyntax.XmlNameSyntax node) => _xmlExpressionConverter.ConvertXmlNameAsync(node); + public override async Task VisitSimpleArgument(VBasic.Syntax.SimpleArgumentSyntax node) => await _argumentConverter.ConvertSimpleArgumentAsync(node); + public override async Task VisitBinaryExpression(VBasic.Syntax.BinaryExpressionSyntax entryNode) => await _binaryExpressionConverter.ConvertBinaryExpressionAsync(entryNode); + public override Task VisitSingleLineLambdaExpression(VBasic.Syntax.SingleLineLambdaExpressionSyntax node) => _lambdaConverter.ConvertSingleLineLambdaAsync(node); + public override Task VisitMultiLineLambdaExpression(VBasic.Syntax.MultiLineLambdaExpressionSyntax node) => _lambdaConverter.ConvertMultiLineLambdaAsync(node); + public override Task VisitInferredFieldInitializer(VBasic.Syntax.InferredFieldInitializerSyntax node) => _initializerConverter.ConvertInferredFieldInitializerAsync(node); + /// Collection initialization has many variants in both VB and C#. Please add especially many test cases when touching this. + public override Task VisitCollectionInitializer(VBasic.Syntax.CollectionInitializerSyntax node) => _initializerConverter.ConvertCollectionInitializerAsync(node); + public override Task VisitObjectMemberInitializer(VBasic.Syntax.ObjectMemberInitializerSyntax node) => _initializerConverter.ConvertObjectMemberInitializerAsync(node); + public override Task VisitNamedFieldInitializer(VBasic.Syntax.NamedFieldInitializerSyntax node) => _initializerConverter.ConvertNamedFieldInitializerAsync(node); + public override Task VisitObjectCollectionInitializer(VBasic.Syntax.ObjectCollectionInitializerSyntax node) => _initializerConverter.ConvertObjectCollectionInitializerAsync(node); public override async Task VisitGetTypeExpression(VBasic.Syntax.GetTypeExpressionSyntax node) { return SyntaxFactory.TypeOfExpression(await node.Type.AcceptAsync(TriviaConvertingExpressionVisitor)); } - public override async Task VisitGlobalName(VBasic.Syntax.GlobalNameSyntax node) - { - return ValidSyntaxFactory.IdentifierName(SyntaxFactory.Token(SyntaxKind.GlobalKeyword)); - } - public override async Task VisitAwaitExpression(VBasic.Syntax.AwaitExpressionSyntax node) { return SyntaxFactory.AwaitExpression(await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor)); @@ -278,7 +113,7 @@ public override async Task VisitDirectCastExpression(VBasic.Sy public override async Task VisitPredefinedCastExpression(VBasic.Syntax.PredefinedCastExpressionSyntax node) { - var simplifiedOrNull = await WithRemovedRedundantConversionOrNullAsync(node, node.Expression); + var simplifiedOrNull = await CommonConversions.WithRemovedRedundantConversionOrNullAsync(node, node.Expression); if (simplifiedOrNull != null) return simplifiedOrNull; var expressionSyntax = await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); @@ -366,16 +201,6 @@ public override async Task VisitInterpolationFormatClause(VBas return SyntaxFactory.InterpolationFormatClause(SyntaxFactory.Token(SyntaxKind.ColonToken), formatStringToken); } - public override async Task VisitMeExpression(VBasic.Syntax.MeExpressionSyntax node) - { - return SyntaxFactory.ThisExpression(); - } - - public override async Task VisitMyBaseExpression(VBasic.Syntax.MyBaseExpressionSyntax node) - { - return SyntaxFactory.BaseExpression(); - } - public override async Task VisitParenthesizedExpression(VBasic.Syntax.ParenthesizedExpressionSyntax node) { var cSharpSyntaxNode = await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); @@ -383,77 +208,6 @@ public override async Task VisitParenthesizedExpression(VBasic return cSharpSyntaxNode is ExpressionSyntax expr ? SyntaxFactory.ParenthesizedExpression(expr) : cSharpSyntaxNode; } - public override async Task VisitMemberAccessExpression(VBasic.Syntax.MemberAccessExpressionSyntax node) - { - var nodeSymbol = GetSymbolInfoInDocument(node.Name); - - if (!node.IsParentKind(VBasic.SyntaxKind.InvocationExpression) && - SimpleMethodReplacement.TryGet(nodeSymbol, out var methodReplacement) && - methodReplacement.ReplaceIfMatches(nodeSymbol, Array.Empty(), node.IsParentKind(VBasic.SyntaxKind.AddressOfExpression)) is {} replacement) { - return replacement; - } - - var simpleNameSyntax = await node.Name.AcceptAsync(TriviaConvertingExpressionVisitor); - - var isDefaultProperty = nodeSymbol is IPropertySymbol p && VBasic.VisualBasicExtensions.IsDefault(p); - ExpressionSyntax left = null; - if (node.Expression is VBasic.Syntax.MyClassExpressionSyntax && nodeSymbol != null) { - if (nodeSymbol.IsStatic) { - var typeInfo = _semanticModel.GetTypeInfo(node.Expression); - left = CommonConversions.GetTypeSyntax(typeInfo.Type); - } else { - left = SyntaxFactory.ThisExpression(); - if (nodeSymbol.IsVirtual && !nodeSymbol.IsAbstract || - nodeSymbol.IsImplicitlyDeclared && nodeSymbol is IFieldSymbol { AssociatedSymbol: IPropertySymbol { IsVirtual: true, IsAbstract: false } }) { - simpleNameSyntax = - ValidSyntaxFactory.IdentifierName( - $"MyClass{ConvertIdentifier(node.Name.Identifier).ValueText}"); - } - } - } - if (left == null && nodeSymbol?.IsStatic == true) { - var type = nodeSymbol.ContainingType; - if (type != null) { - left = CommonConversions.GetTypeSyntax(type); - } - } - if (left == null) { - left = await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); - if (left != null && _semanticModel.GetSymbolInfo(node) is {CandidateReason: CandidateReason.LateBound, CandidateSymbols.Length: 0} - && _semanticModel.GetSymbolInfo(node.Expression).Symbol is {Kind: var expressionSymbolKind} - && expressionSymbolKind != SymbolKind.ErrorType - && _semanticModel.GetOperation(node) is IDynamicMemberReferenceOperation) { - left = SyntaxFactory.ParenthesizedExpression(SyntaxFactory.CastExpression(SyntaxFactory.ParseTypeName("dynamic"), left)); - } - } - if (left == null) { - if (IsSubPartOfConditionalAccess(node)) { - return isDefaultProperty ? SyntaxFactory.ElementBindingExpression() - : await AdjustForImplicitInvocationAsync(node, SyntaxFactory.MemberBindingExpression(simpleNameSyntax)); - } else if (node.IsParentKind(Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.NamedFieldInitializer)) { - return ValidSyntaxFactory.IdentifierName(_tempNameForAnonymousScope[node.Name.Identifier.Text].Peek().TempName); - } - left = _withBlockLhs.Peek(); - } - - if (node.IsKind(VBasic.SyntaxKind.DictionaryAccessExpression)) { - var args = SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(CommonConversions.Literal(node.Name.Identifier.ValueText))); - var bracketedArgumentListSyntax = SyntaxFactory.BracketedArgumentList(args); - return SyntaxFactory.ElementAccessExpression(left, bracketedArgumentListSyntax); - } - - if (node.Expression.IsKind(VBasic.SyntaxKind.GlobalName)) { - return SyntaxFactory.AliasQualifiedName((IdentifierNameSyntax)left, simpleNameSyntax); - } - - if (isDefaultProperty && left != null) { - return left; - } - - var memberAccessExpressionSyntax = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, left, simpleNameSyntax); - return await AdjustForImplicitInvocationAsync(node, memberAccessExpressionSyntax); - } - public override async Task VisitConditionalAccessExpression(VBasic.Syntax.ConditionalAccessExpressionSyntax node) { var leftExpression = await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor) ?? _withBlockLhs.Peek(); @@ -463,100 +217,12 @@ public override async Task VisitConditionalAccessExpression(VB public override async Task VisitArgumentList(VBasic.Syntax.ArgumentListSyntax node) { if (node.Parent.IsKind(VBasic.SyntaxKind.Attribute)) { - return CommonConversions.CreateAttributeArgumentList(await node.Arguments.SelectAsync(ToAttributeArgumentAsync)); + return CommonConversions.CreateAttributeArgumentList(await node.Arguments.SelectAsync(_argumentConverter.ToAttributeArgumentAsync)); } - var argumentSyntaxes = await ConvertArgumentsAsync(node); + var argumentSyntaxes = await _argumentConverter.ConvertArgumentsAsync(node); return SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(argumentSyntaxes)); } - public override async Task VisitSimpleArgument(VBasic.Syntax.SimpleArgumentSyntax node) - { - var argList = (VBasic.Syntax.ArgumentListSyntax)node.Parent; - var invocation = argList.Parent; - if (invocation is VBasic.Syntax.ArrayCreationExpressionSyntax) - return await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); - var symbol = GetInvocationSymbol(invocation); - SyntaxToken token = default(SyntaxToken); - var convertedArgExpression = (await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor)).SkipIntoParens(); - var typeConversionAnalyzer = CommonConversions.TypeConversionAnalyzer; - var baseSymbol = symbol?.OriginalDefinition.GetBaseSymbol(); - var possibleParameters = (CommonConversions.GetCsOriginalSymbolOrNull(baseSymbol) ?? symbol)?.GetParameters(); - if (possibleParameters.HasValue) { - var refType = GetRefConversionType(node, argList, possibleParameters.Value, 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(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, SymbolEqualityComparer.IncludeNullability) == 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 = _typeContext.PerScopeState.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 = _typeContext.PerScopeState.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); - _typeContext.PerScopeState.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(nameof(refKind), refKind, null); - } - - return token; - } - - private RefConversion GetRefConversionType(VBSyntax.ArgumentSyntax node, VBSyntax.ArgumentListSyntax argList, 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 { - refKind = RefKind.None; - argName = null; - } - return NeedsVariableForArgument(node, refKind); - } - public override async Task VisitNameOfExpression(VBasic.Syntax.NameOfExpressionSyntax node) { return SyntaxFactory.InvocationExpression(ValidSyntaxFactory.NameOf(), SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(await node.Argument.AcceptAsync(TriviaConvertingExpressionVisitor))))); @@ -583,18 +249,13 @@ public override async Task VisitAnonymousObjectCreationExpress } - public override async Task VisitInferredFieldInitializer(VBasic.Syntax.InferredFieldInitializerSyntax node) - { - return SyntaxFactory.AnonymousObjectMemberDeclarator(await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor)); - } - public override async Task VisitObjectCreationExpression(VBasic.Syntax.ObjectCreationExpressionSyntax node) { var objectCreationExpressionSyntax = SyntaxFactory.ObjectCreationExpression( await node.Type.AcceptAsync(TriviaConvertingExpressionVisitor), // VB can omit empty arg lists: - await ConvertArgumentListOrEmptyAsync(node, node.ArgumentList), + await _argumentConverter.ConvertArgumentListOrEmptyAsync(node, node.ArgumentList), null ); async Task ConvertInitializer() => await node.Initializer.AcceptAsync(TriviaConvertingExpressionVisitor); @@ -634,52 +295,6 @@ await initializerToConvert.AcceptAsync(TriviaConver ); } - /// Collection initialization has many variants in both VB and C#. Please add especially many test cases when touching this. - public override async Task VisitCollectionInitializer(VBasic.Syntax.CollectionInitializerSyntax node) - { - var isExplicitCollectionInitializer = node.Parent is VBasic.Syntax.ObjectCollectionInitializerSyntax - || node.Parent is VBasic.Syntax.CollectionInitializerSyntax - || node.Parent is VBasic.Syntax.ArrayCreationExpressionSyntax; - var initializerKind = node.IsParentKind(VBasic.SyntaxKind.ObjectCollectionInitializer) || node.IsParentKind(VBasic.SyntaxKind.ObjectCreationExpression) ? - SyntaxKind.CollectionInitializerExpression : - node.IsParentKind(VBasic.SyntaxKind.CollectionInitializer) && IsComplexInitializer(node) ? SyntaxKind.ComplexElementInitializerExpression : - SyntaxKind.ArrayInitializerExpression; - var initializers = (await node.Initializers.SelectAsync(async i => { - var convertedInitializer = await i.AcceptAsync(TriviaConvertingExpressionVisitor); - return CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(i, convertedInitializer, false); - })); - var initializer = SyntaxFactory.InitializerExpression(initializerKind, SyntaxFactory.SeparatedList(initializers)); - if (isExplicitCollectionInitializer) return initializer; - - var convertedType = _semanticModel.GetTypeInfo(node).ConvertedType; - var dimensions = convertedType is IArrayTypeSymbol ats ? ats.Rank : 1; // For multidimensional array [,] note these are different from nested arrays [][] - if (!(convertedType.GetEnumerableElementTypeOrDefault() is {} elementType)) return SyntaxFactory.ImplicitArrayCreationExpression(initializer); - - if (!initializers.Any() && dimensions == 1) { - var arrayTypeArgs = SyntaxFactory.TypeArgumentList(SyntaxFactory.SingletonSeparatedList(CommonConversions.GetTypeSyntax(elementType))); - var arrayEmpty = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - ValidSyntaxFactory.IdentifierName(nameof(Array)), SyntaxFactory.GenericName(nameof(Array.Empty)).WithTypeArgumentList(arrayTypeArgs)); - return SyntaxFactory.InvocationExpression(arrayEmpty); - } - - bool hasExpressionToInferTypeFrom = node.Initializers.SelectMany(n => n.DescendantNodesAndSelf()).Any(n => n is not VBasic.Syntax.CollectionInitializerSyntax); - if (hasExpressionToInferTypeFrom) { - var commas = Enumerable.Repeat(SyntaxFactory.Token(SyntaxKind.CommaToken), dimensions - 1); - return SyntaxFactory.ImplicitArrayCreationExpression(SyntaxFactory.TokenList(commas), initializer); - } - - var arrayType = (ArrayTypeSyntax)CommonConversions.CsSyntaxGenerator.ArrayTypeExpression(CommonConversions.GetTypeSyntax(elementType)); - var sizes = Enumerable.Repeat(SyntaxFactory.OmittedArraySizeExpression(), dimensions); - var arrayRankSpecifierSyntax = SyntaxFactory.SingletonList(SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SeparatedList(sizes))); - arrayType = arrayType.WithRankSpecifiers(arrayRankSpecifierSyntax); - return SyntaxFactory.ArrayCreationExpression(arrayType, initializer); - } - - private bool IsComplexInitializer(VBSyntax.CollectionInitializerSyntax node) - { - return _semanticModel.GetOperation(node.Parent.Parent) is IObjectOrCollectionInitializerOperation initializer && - initializer.Initializers.OfType().Any(); - } public override async Task VisitQueryExpression(VBasic.Syntax.QueryExpressionSyntax node) { @@ -693,69 +308,9 @@ public override async Task VisitOrdering(VBasic.Syntax.Orderin var ascendingOrDescendingKeyword = node.AscendingOrDescendingKeyword.ConvertToken(); return SyntaxFactory.Ordering(convertToken, expressionSyntax, ascendingOrDescendingKeyword); } - - public override async Task VisitObjectMemberInitializer(VBasic.Syntax.ObjectMemberInitializerSyntax node) - { - var initializers = await node.Initializers.AcceptSeparatedListAsync(TriviaConvertingExpressionVisitor); - return SyntaxFactory.InitializerExpression(SyntaxKind.ObjectInitializerExpression, initializers); - } - - public override async Task VisitNamedFieldInitializer(VBasic.Syntax.NamedFieldInitializerSyntax node) - { - var csExpressionSyntax = await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); - csExpressionSyntax = - CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, csExpressionSyntax); - if (node.Parent?.Parent is VBasic.Syntax.AnonymousObjectCreationExpressionSyntax {Initializer: {Initializers: var initializers}} anonymousObjectCreationExpression) { - string nameIdentifierText = node.Name.Identifier.Text; - var isAnonymouslyReused = initializers.OfType() - .Select(i => i.Expression).OfType() - .Any(maes => maes.Expression is null && maes.Name.Identifier.Text.Equals(nameIdentifierText, StringComparison.OrdinalIgnoreCase)); - if (isAnonymouslyReused) { - string tempNameForAnonymousSelfReference = GenerateUniqueVariableName(node.Name, "temp" + ((VBSyntax.SimpleNameSyntax) node.Name).Identifier.Text.UppercaseFirstLetter()); - csExpressionSyntax = DeclareVariableInline(csExpressionSyntax, tempNameForAnonymousSelfReference); - if (!_tempNameForAnonymousScope.TryGetValue(nameIdentifierText, out var stack)) { - stack = _tempNameForAnonymousScope[nameIdentifierText] = new Stack<(SyntaxNode Scope, string TempName)>(); - } - stack.Push((anonymousObjectCreationExpression, tempNameForAnonymousSelfReference)); - } - - var anonymousObjectMemberDeclaratorSyntax = SyntaxFactory.AnonymousObjectMemberDeclarator( - SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName(ConvertIdentifier(node.Name.Identifier))), - csExpressionSyntax); - return anonymousObjectMemberDeclaratorSyntax; - } - - return SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, - await node.Name.AcceptAsync(TriviaConvertingExpressionVisitor), - csExpressionSyntax - ); - } - - private string GenerateUniqueVariableName(VisualBasicSyntaxNode existingNode, string varNameBase) => NameGenerator.CS.GetUniqueVariableNameInScope(_semanticModel, _generatedNames, existingNode, varNameBase); - - private static ExpressionSyntax DeclareVariableInline(ExpressionSyntax csExpressionSyntax, string temporaryName) - { - var temporaryNameId = SyntaxFactory.Identifier(temporaryName); - var temporaryNameExpression = ValidSyntaxFactory.IdentifierName(temporaryNameId); - csExpressionSyntax = SyntaxFactory.ConditionalExpression( - SyntaxFactory.IsPatternExpression( - csExpressionSyntax, - SyntaxFactory.VarPattern( - SyntaxFactory.SingleVariableDesignation(temporaryNameId))), - temporaryNameExpression, - SyntaxFactory.LiteralExpression( - SyntaxKind.DefaultLiteralExpression, - SyntaxFactory.Token(SyntaxKind.DefaultKeyword))); - return csExpressionSyntax; - } - public override async Task VisitVariableNameEquals(VBSyntax.VariableNameEqualsSyntax node) => SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName(ConvertIdentifier(node.Identifier.Identifier))); - public override async Task VisitObjectCollectionInitializer(VBasic.Syntax.ObjectCollectionInitializerSyntax node) - { - return await node.Initializer.AcceptAsync(TriviaConvertingExpressionVisitor); //Dictionary initializer comes through here despite the FROM keyword not being in the source code - } public override async Task VisitBinaryConditionalExpression(VBasic.Syntax.BinaryConditionalExpressionSyntax node) { @@ -851,551 +406,6 @@ private CSharpSyntaxNode ConvertAddressOf(VBSyntax.UnaryExpressionSyntax node, E return expr; } - public override async Task VisitBinaryExpression(VBasic.Syntax.BinaryExpressionSyntax entryNode) - { - // Walk down the syntax tree for deeply nested binary expressions to avoid stack overflow - // e.g. 3 + 4 + 5 + ... - // Test "DeeplyNestedBinaryExpressionShouldNotStackOverflowAsync()" skipped because it's too slow - - ExpressionSyntax csLhs = null; - int levelsToConvert = 0; - VBSyntax.BinaryExpressionSyntax currentNode = entryNode; - - // Walk down the nested levels to count them - for (var nextNode = entryNode; nextNode != null; currentNode = nextNode, nextNode = currentNode.Left as VBSyntax.BinaryExpressionSyntax, levelsToConvert++) { - // Don't go beyond a rewritten operator because that code has many paths that can call VisitBinaryExpression. Passing csLhs through all of that would harm the code quality more than it's worth to help that edge case. - if (await RewriteBinaryOperatorOrNullAsync(nextNode) is { } operatorNode) { - csLhs = operatorNode; - break; - } - } - - // Walk back up the same levels converting as we go. - for (; levelsToConvert > 0; currentNode = currentNode!.Parent as VBSyntax.BinaryExpressionSyntax, levelsToConvert--) { - csLhs = (ExpressionSyntax)await ConvertBinaryExpressionAsync(currentNode, csLhs); - } - - return csLhs; - } - - private async Task ConvertBinaryExpressionAsync(VBasic.Syntax.BinaryExpressionSyntax node, ExpressionSyntax lhs = null, ExpressionSyntax rhs = null) - { - lhs ??= await node.Left.AcceptAsync(TriviaConvertingExpressionVisitor); - rhs ??= await node.Right.AcceptAsync(TriviaConvertingExpressionVisitor); - - var lhsTypeInfo = _semanticModel.GetTypeInfo(node.Left); - var rhsTypeInfo = _semanticModel.GetTypeInfo(node.Right); - - ITypeSymbol forceLhsTargetType = null; - bool omitRightConversion = false; - bool omitConversion = false; - if (lhsTypeInfo.Type != null && rhsTypeInfo.Type != null) - { - if (node.IsKind(VBasic.SyntaxKind.ConcatenateExpression) && - !lhsTypeInfo.Type.IsEnumType() && !rhsTypeInfo.Type.IsEnumType() && - !lhsTypeInfo.Type.IsDateType() && !rhsTypeInfo.Type.IsDateType()) - { - omitRightConversion = true; - omitConversion = lhsTypeInfo.Type.SpecialType == SpecialType.System_String || - rhsTypeInfo.Type.SpecialType == SpecialType.System_String; - if (lhsTypeInfo.ConvertedType.SpecialType != SpecialType.System_String) { - forceLhsTargetType = CommonConversions.KnownTypes.String; - } - } - } - - var objectEqualityType = _visualBasicEqualityComparison.GetObjectEqualityType(node, lhsTypeInfo, rhsTypeInfo); - - switch (objectEqualityType) { - case VisualBasicEqualityComparison.RequiredType.StringOnly: - if (lhsTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String && - rhsTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String && - _visualBasicEqualityComparison.TryConvertToNullOrEmptyCheck(node, lhs, rhs, out CSharpSyntaxNode visitBinaryExpression)) { - return visitBinaryExpression; - } - (lhs, rhs) = _visualBasicEqualityComparison.AdjustForVbStringComparison(node.Left, lhs, lhsTypeInfo, false, node.Right, rhs, rhsTypeInfo, false); - omitConversion = true; // Already handled within for the appropriate types (rhs can become int in comparison) - break; - case VisualBasicEqualityComparison.RequiredType.Object: - return _visualBasicEqualityComparison.GetFullExpressionForVbObjectComparison(lhs, rhs, ComparisonKind.Equals, node.IsKind(VBasic.SyntaxKind.NotEqualsExpression)); - } - - var lhsTypeIgnoringNullable = lhsTypeInfo.Type.GetNullableUnderlyingType() ?? lhsTypeInfo.Type; - var rhsTypeIgnoringNullable = rhsTypeInfo.Type.GetNullableUnderlyingType() ?? rhsTypeInfo.Type; - omitConversion |= lhsTypeIgnoringNullable != null && rhsTypeIgnoringNullable != null && - lhsTypeIgnoringNullable.IsEnumType() && SymbolEqualityComparer.Default.Equals(lhsTypeIgnoringNullable, rhsTypeIgnoringNullable) - && !node.IsKind(VBasic.SyntaxKind.AddExpression, VBasic.SyntaxKind.SubtractExpression, VBasic.SyntaxKind.MultiplyExpression, VBasic.SyntaxKind.DivideExpression, VBasic.SyntaxKind.IntegerDivideExpression, VBasic.SyntaxKind.ModuloExpression) - && forceLhsTargetType == null; - lhs = omitConversion ? lhs : CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Left, lhs, forceTargetType: forceLhsTargetType); - rhs = omitConversion || omitRightConversion ? rhs : CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, rhs); - - var kind = VBasic.VisualBasicExtensions.Kind(node).ConvertToken(); - var op = SyntaxFactory.Token(CSharpUtil.GetExpressionOperatorTokenKind(kind)); - - var csBinExp = SyntaxFactory.BinaryExpression(kind, lhs, op, rhs); - var exp = _visualBasicNullableTypesConverter.WithBinaryExpressionLogicForNullableTypes(node, lhsTypeInfo, rhsTypeInfo, csBinExp, lhs, rhs); - return node.Parent.IsKind(VBasic.SyntaxKind.SimpleArgument) ? exp : exp.AddParens(); - } - - - - private async Task RewriteBinaryOperatorOrNullAsync(VBSyntax.BinaryExpressionSyntax node) => - await _operatorConverter.ConvertRewrittenBinaryOperatorOrNullAsync(node, TriviaConvertingExpressionVisitor.IsWithinQuery); - - private async Task WithRemovedRedundantConversionOrNullAsync(VBSyntax.InvocationExpressionSyntax conversionNode, ISymbol invocationSymbol) - { - if (invocationSymbol?.ContainingNamespace.MetadataName != nameof(Microsoft.VisualBasic) || - invocationSymbol.ContainingType.Name != nameof(Conversions) || - !invocationSymbol.Name.StartsWith("To", StringComparison.InvariantCulture) || - conversionNode.ArgumentList.Arguments.Count != 1) { - return null; - } - - var conversionArg = conversionNode.ArgumentList.Arguments.First().GetExpression(); - VBSyntax.ExpressionSyntax coercedConversionNode = conversionNode; - return await WithRemovedRedundantConversionOrNullAsync(coercedConversionNode, conversionArg); - } - - private async Task WithRemovedRedundantConversionOrNullAsync(VBSyntax.ExpressionSyntax conversionNode, VBSyntax.ExpressionSyntax conversionArg) - { - var csharpArg = await conversionArg.AcceptAsync(TriviaConvertingExpressionVisitor); - var typeInfo = _semanticModel.GetTypeInfo(conversionNode); - - // If written by the user (i.e. not generated during expand phase), maintain intended semantics which could throw sometimes e.g. object o = (int) (object) long.MaxValue; - var writtenByUser = !conversionNode.HasAnnotation(Simplifier.Annotation); - var forceTargetType = typeInfo.ConvertedType; - // TypeConversionAnalyzer can't figure out which type is required for operator/method overloads, inferred func returns or inferred variable declarations - // (currently overapproximates for numeric and gets it wrong in non-numeric cases). - // Future: Avoid more redundant conversions by still calling AddExplicitConversion when writtenByUser avoiding the above and forcing typeInfo.Type - return writtenByUser ? null : CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(conversionArg, csharpArg, - forceTargetType: forceTargetType, defaultToCast: true); - } - - - 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) { - _typeContext.PerScopeState.PushScope(); - } - try { - - if (node.Expression is null) { - var convertArgumentListOrEmptyAsync = await ConvertArgumentsAsync(node.ArgumentList); - return SyntaxFactory.ElementBindingExpression(SyntaxFactory.BracketedArgumentList(SyntaxFactory.SeparatedList(convertArgumentListOrEmptyAsync))); - } - - var convertedInvocation = await ConvertOrReplaceInvocationAsync(node, invocationSymbol); - if (withinLocalFunction) { - return await HoistAndCallLocalFunctionAsync(node, methodInvocationSymbol, (ExpressionSyntax)convertedInvocation); - } - return convertedInvocation; - } finally { - if (withinLocalFunction) { - _typeContext.PerScopeState.PopExpressionScope(); - } - } - } - - private async Task ConvertOrReplaceInvocationAsync(VBSyntax.InvocationExpressionSyntax node, ISymbol invocationSymbol) - { - var expressionSymbol = _semanticModel.GetSymbolInfo(node.Expression).ExtractBestMatch(); - if ((await SubstituteVisualBasicMethodOrNullAsync(node, expressionSymbol) ?? - await WithRemovedRedundantConversionOrNullAsync(node, expressionSymbol)) is { } csEquivalent) { - return csEquivalent; - } - - if (invocationSymbol?.Name is "op_Implicit" or "op_Explicit") { - var vbExpr = node.ArgumentList.Arguments.Single().GetExpression(); - var csExpr = await vbExpr.AcceptAsync(TriviaConvertingExpressionVisitor); - return CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbExpr, csExpr, true, true, false, forceTargetType: invocationSymbol.GetReturnType()); - } - - return await ConvertInvocationAsync(node, invocationSymbol, expressionSymbol); - } - - private async Task ConvertInvocationAsync(VBSyntax.InvocationExpressionSyntax node, ISymbol invocationSymbol, ISymbol expressionSymbol) - { - var expressionType = _semanticModel.GetTypeInfo(node.Expression).Type; - var expressionReturnType = expressionSymbol?.GetReturnType() ?? expressionType; - var operation = _semanticModel.GetOperation(node); - - var expr = await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); - if (await TryConvertParameterizedPropertyAsync(operation, node, expr, node.ArgumentList) is { } invocation) - { - return invocation; - } - //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 - (var convertedExpression, bool shouldBeElementAccess) = await ConvertInvocationSubExpressionAsync(node, operation, expressionSymbol, expressionReturnType, expr); - if (shouldBeElementAccess) - { - return await CreateElementAccessAsync(node, convertedExpression); - } - - if (expressionSymbol != null && expressionSymbol.IsKind(SymbolKind.Property) && - invocationSymbol != null && invocationSymbol.GetParameters().Length == 0 && node.ArgumentList.Arguments.Count == 0) - { - return convertedExpression; //Parameterless property access - } - - var convertedArgumentList = await ConvertArgumentListOrEmptyAsync(node, node.ArgumentList); - - if (IsElementAtOrDefaultInvocation(invocationSymbol, expressionSymbol)) - { - convertedExpression = GetElementAtOrDefaultExpression(expressionType, convertedExpression); - } - - if (invocationSymbol.IsReducedExtension() && invocationSymbol is IMethodSymbol {ReducedFrom: {Parameters: var parameters}} && - !parameters.FirstOrDefault().ValidCSharpExtensionMethodParameter() && - node.Expression is VBSyntax.MemberAccessExpressionSyntax maes) - { - var thisArgExpression = await maes.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); - var thisArg = SyntaxFactory.Argument(thisArgExpression).WithRefKindKeyword(GetRefToken(RefKind.Ref)); - convertedArgumentList = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(convertedArgumentList.Arguments.Prepend(thisArg))); - var containingType = (ExpressionSyntax) CommonConversions.CsSyntaxGenerator.TypeExpression(invocationSymbol.ContainingType); - convertedExpression = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, containingType, - ValidSyntaxFactory.IdentifierName((invocationSymbol.Name))); - } - - if (invocationSymbol is IMethodSymbol m && convertedExpression is LambdaExpressionSyntax) { - convertedExpression = SyntaxFactory.ObjectCreationExpression(CommonConversions.GetFuncTypeSyntax(expressionType, m), ExpressionSyntaxExtensions.CreateArgList(convertedExpression), null); - } - return SyntaxFactory.InvocationExpression(convertedExpression, convertedArgumentList); - } - - private async Task<(ExpressionSyntax, bool isElementAccess)> ConvertInvocationSubExpressionAsync(VBSyntax.InvocationExpressionSyntax node, - IOperation operation, ISymbol expressionSymbol, ITypeSymbol expressionReturnType, CSharpSyntaxNode expr) - { - var isElementAccess = operation.IsPropertyElementAccess() - || operation.IsArrayElementAccess() - || ProbablyNotAMethodCall(node, expressionSymbol, expressionReturnType); - - var expressionSyntax = (ExpressionSyntax)expr; - - return (expressionSyntax, isElementAccess); - } - - private async Task CreateElementAccessAsync(VBSyntax.InvocationExpressionSyntax node, ExpressionSyntax expression) - { - var args = - await node.ArgumentList.Arguments.AcceptSeparatedListAsync(TriviaConvertingExpressionVisitor); - var bracketedArgumentListSyntax = SyntaxFactory.BracketedArgumentList(args); - if (expression is ElementBindingExpressionSyntax binding && - !binding.ArgumentList.Arguments.Any()) { - // Special case where structure changes due to conditional access (See VisitMemberAccessExpression) - return binding.WithArgumentList(bracketedArgumentListSyntax); - } - - return SyntaxFactory.ElementAccessExpression(expression, bracketedArgumentListSyntax); - } - - private static bool IsElementAtOrDefaultInvocation(ISymbol invocationSymbol, ISymbol expressionSymbol) - { - return (expressionSymbol != null - && (invocationSymbol?.Name == nameof(Enumerable.ElementAtOrDefault) - && !expressionSymbol.Equals(invocationSymbol, SymbolEqualityComparer.IncludeNullability))); - } - - private ExpressionSyntax GetElementAtOrDefaultExpression(ISymbol expressionType, - ExpressionSyntax expression) - { - _extraUsingDirectives.Add(nameof(System) + "." + nameof(System.Linq)); - - // The Vb compiler interprets Datatable indexing as a AsEnumerable().ElementAtOrDefault() operation. - if (expressionType.Name == nameof(DataTable)) - { - _extraUsingDirectives.Add(nameof(System) + "." + nameof(System.Data)); - - expression = SyntaxFactory.InvocationExpression(SyntaxFactory.MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, expression, - ValidSyntaxFactory.IdentifierName(nameof(DataTableExtensions.AsEnumerable)))); - } - - var newExpression = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - expression, ValidSyntaxFactory.IdentifierName(nameof(Enumerable.ElementAtOrDefault))); - - return newExpression; - } - - private async Task TryConvertParameterizedPropertyAsync(IOperation operation, - SyntaxNode node, CSharpSyntaxNode identifier, - VBSyntax.ArgumentListSyntax optionalArgumentList = null) - { - var (overrideIdentifier, extraArg) = - await CommonConversions.GetParameterizedPropertyAccessMethodAsync(operation); - if (overrideIdentifier != null) - { - var expr = identifier; - var idToken = expr.DescendantTokens().Last(t => t.IsKind(SyntaxKind.IdentifierToken)); - expr = ReplaceRightmostIdentifierText(expr, idToken, overrideIdentifier); - - var args = await ConvertArgumentListOrEmptyAsync(node, optionalArgumentList); - if (extraArg != null) { - var extraArgSyntax = SyntaxFactory.Argument(extraArg); - var propertySymbol = ((IPropertyReferenceOperation)operation).Property; - var forceNamedExtraArg = args.Arguments.Count != propertySymbol.GetParameters().Length || - args.Arguments.Any(t => t.NameColon != null); - - if (forceNamedExtraArg) { - extraArgSyntax = extraArgSyntax.WithNameColon(SyntaxFactory.NameColon("value")); - } - - args = args.WithArguments(args.Arguments.Add(extraArgSyntax)); - } - - return SyntaxFactory.InvocationExpression((ExpressionSyntax)expr, args); - } - - return null; - } - - - /// - /// 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 HoistAndCallLocalFunctionAsync(VBSyntax.InvocationExpressionSyntax invocation, IMethodSymbol invocationSymbol, ExpressionSyntax csExpression) - { - const string retVariableName = "ret"; - var localFuncName = $"local{invocationSymbol.Name}"; - - var callAndStoreResult = CommonConversions.CreateLocalVariableDeclarationAndAssignment(retVariableName, csExpression); - - var statements = await _typeContext.PerScopeState.CreateLocalsAsync(invocation, new[] { callAndStoreResult }, _generatedNames, _semanticModel); - - var block = SyntaxFactory.Block( - statements.Concat(SyntaxFactory.ReturnStatement(ValidSyntaxFactory.IdentifierName(retVariableName)).Yield()) - ); - var returnType = CommonConversions.GetTypeSyntax(invocationSymbol.ReturnType); - - //any argument that's a ByRef parameter of the parent method needs to be passed as a ref parameter to the local function (to avoid error CS1628) - var refParametersOfParent = GetRefParameters(invocation.ArgumentList); - var (args, @params) = CreateArgumentsAndParametersLists(refParametersOfParent); - - var localFunc = _typeContext.PerScopeState.Hoist(new HoistedFunction(localFuncName, returnType, block, SyntaxFactory.ParameterList(@params))); - return SyntaxFactory.InvocationExpression(localFunc.TempIdentifier, SyntaxFactory.ArgumentList(args)); - - List GetRefParameters(VBSyntax.ArgumentListSyntax argumentList) - { - var result = new List(); - if (argumentList is null) return result; - - foreach (var arg in argumentList.Arguments) { - if (_semanticModel.GetSymbolInfo(arg.GetExpression()).Symbol is not IParameterSymbol p) continue; - if (p.RefKind != RefKind.None) { - result.Add(p); - } - } - - return result; - } - - (SeparatedSyntaxList, SeparatedSyntaxList) CreateArgumentsAndParametersLists(List parameterSymbols) - { - var arguments = new List(); - var parameters = new List(); - foreach (var p in parameterSymbols) { - var arg = (ArgumentSyntax)CommonConversions.CsSyntaxGenerator.Argument(p.RefKind, SyntaxFactory.IdentifierName(p.Name)); - arguments.Add(arg); - var par = (ParameterSyntax)CommonConversions.CsSyntaxGenerator.ParameterDeclaration(p); - parameters.Add(par); - } - return (SyntaxFactory.SeparatedList(arguments), SyntaxFactory.SeparatedList(parameters)); - } - } - - private bool RequiresLocalFunction(VBSyntax.InvocationExpressionSyntax invocation, IMethodSymbol invocationSymbol) - { - if (invocation.ArgumentList == null) return false; - var definitelyExecutedAfterPrevious = DefinitelyExecutedAfterPreviousStatement(invocation); - var nextStatementDefinitelyExecuted = NextStatementDefinitelyExecutedAfter(invocation); - if (definitelyExecutedAfterPrevious && nextStatementDefinitelyExecuted) return false; - var possibleInline = definitelyExecutedAfterPrevious ? RefConversion.PreAssigment : RefConversion.Inline; - return invocation.ArgumentList.Arguments.Any(a => RequiresLocalFunction(possibleInline, invocation, invocationSymbol, a)); - - bool RequiresLocalFunction(RefConversion possibleInline, VBSyntax.InvocationExpressionSyntax invocation, IMethodSymbol invocationSymbol, VBSyntax.ArgumentSyntax a) - { - var refConversion = GetRefConversionType(a, invocation.ArgumentList, invocationSymbol.Parameters, out string _, out _); - if (RefConversion.Inline == refConversion || possibleInline == refConversion) return false; - if (!(a is VBSyntax.SimpleArgumentSyntax sas)) return false; - var argExpression = sas.Expression.SkipIntoParens(); - if (argExpression is VBSyntax.InstanceExpressionSyntax) return false; - return !_semanticModel.GetConstantValue(argExpression).HasValue; - } - } - - /// - /// Conservative version of _semanticModel.AnalyzeControlFlow(invocation).ExitPoints to account for exceptions - /// - private bool DefinitelyExecutedAfterPreviousStatement(VBSyntax.InvocationExpressionSyntax invocation) - { - SyntaxNode parent = invocation; - while (true) { - parent = parent.Parent; - switch (parent) - { - case VBSyntax.ParenthesizedExpressionSyntax _: - continue; - case VBSyntax.BinaryExpressionSyntax binaryExpression: - if (binaryExpression.Left == invocation) continue; - else return false; - case VBSyntax.ArgumentSyntax argumentSyntax: - // Being the leftmost invocation of an unqualified method call ensures no other code is executed. Could add other cases here, such as a method call on a local variable name, or "this.". A method call on a property is not acceptable. - if (argumentSyntax.Parent.Parent is VBSyntax.InvocationExpressionSyntax parentInvocation && parentInvocation.ArgumentList.Arguments.First() == argumentSyntax && FirstArgDefinitelyEvaluated(parentInvocation)) continue; - else return false; - case VBSyntax.ElseIfStatementSyntax _: - case VBSyntax.ExpressionSyntax _: - return false; - case VBSyntax.StatementSyntax _: - return true; - } - } - } - - private bool FirstArgDefinitelyEvaluated(VBSyntax.InvocationExpressionSyntax parentInvocation) => - parentInvocation.Expression.SkipIntoParens() switch { - VBSyntax.IdentifierNameSyntax _ => true, - VBSyntax.MemberAccessExpressionSyntax maes => maes.Expression is {} exp && !MayThrow(exp), - _ => true - }; - - /// - /// Safe overapproximation of whether an expression may throw. - /// - private bool MayThrow(VBSyntax.ExpressionSyntax expression) - { - expression = expression.SkipIntoParens(); - if (expression is VBSyntax.InstanceExpressionSyntax) return false; - var symbol = _semanticModel.GetSymbolInfo(expression).Symbol; - return !symbol.IsKind(SymbolKind.Local) && !symbol.IsKind(SymbolKind.Field); - } - - /// - /// Conservative version of _semanticModel.AnalyzeControlFlow(invocation).ExitPoints to account for exceptions - /// - private static bool NextStatementDefinitelyExecutedAfter(VBSyntax.InvocationExpressionSyntax invocation) - { - SyntaxNode parent = invocation; - while (true) { - parent = parent.Parent; - switch (parent) - { - case VBSyntax.ParenthesizedExpressionSyntax _: - continue; - case VBSyntax.BinaryExpressionSyntax binaryExpression: - if (binaryExpression.Right == invocation) continue; - else return false; - case VBSyntax.IfStatementSyntax _: - case VBSyntax.ElseIfStatementSyntax _: - case VBSyntax.SingleLineIfStatementSyntax _: - return false; - case VBSyntax.ExpressionSyntax _: - case VBSyntax.StatementSyntax _: - return true; - } - } - } - - public override async Task VisitSingleLineLambdaExpression(VBasic.Syntax.SingleLineLambdaExpressionSyntax node) - { - var originalIsWithinQuery = TriviaConvertingExpressionVisitor.IsWithinQuery; - TriviaConvertingExpressionVisitor.IsWithinQuery = CommonConversions.IsLinqDelegateExpression(node); - try { - return await ConvertInnerAsync(); - } finally { - TriviaConvertingExpressionVisitor.IsWithinQuery = originalIsWithinQuery; - } - - async Task ConvertInnerAsync() - { - IReadOnlyCollection convertedStatements; - if (node.Body is VBasic.Syntax.StatementSyntax statement) - { - convertedStatements = await ConvertMethodBodyStatementsAsync(statement, statement.Yield().ToArray()); - } - else - { - var csNode = await node.Body.AcceptAsync(TriviaConvertingExpressionVisitor); - convertedStatements = new[] {SyntaxFactory.ExpressionStatement(csNode)}; - } - - var param = await node.SubOrFunctionHeader.ParameterList.AcceptAsync(TriviaConvertingExpressionVisitor); - return await _lambdaConverter.ConvertAsync(node, param, convertedStatements); - } - } - - public override async Task VisitMultiLineLambdaExpression(VBasic.Syntax.MultiLineLambdaExpressionSyntax node) - { - var originalIsWithinQuery = TriviaConvertingExpressionVisitor.IsWithinQuery; - TriviaConvertingExpressionVisitor.IsWithinQuery = CommonConversions.IsLinqDelegateExpression(node); - try { - return await ConvertInnerAsync(); - } finally { - TriviaConvertingExpressionVisitor.IsWithinQuery = originalIsWithinQuery; - } - - async Task ConvertInnerAsync() - { - var body = await ConvertMethodBodyStatementsAsync(node, node.Statements); - var param = await node.SubOrFunctionHeader.ParameterList.AcceptAsync(TriviaConvertingExpressionVisitor); - return await _lambdaConverter.ConvertAsync(node, param, body.ToList()); - } - } - - public async Task> ConvertMethodBodyStatementsAsync(VBasic.VisualBasicSyntaxNode node, IReadOnlyCollection statements, bool isIterator = false, IdentifierNameSyntax csReturnVariable = null) - { - - var innerMethodBodyVisitor = await MethodBodyExecutableStatementVisitor.CreateAsync(node, _semanticModel, TriviaConvertingExpressionVisitor, CommonConversions, _visualBasicEqualityComparison, _withBlockLhs, _extraUsingDirectives, _typeContext, isIterator, csReturnVariable); - return await GetWithConvertedGotosOrNull(statements) ?? await ConvertStatements(statements); - - async Task> ConvertStatements(IEnumerable readOnlyCollection) - { - return (await readOnlyCollection.SelectManyAsync(async s => (IEnumerable)await s.Accept(innerMethodBodyVisitor.CommentConvertingVisitor))).ToList(); - } - - async Task> GetWithConvertedGotosOrNull(IReadOnlyCollection statements) - { - var onlyIdentifierLabel = statements.OnlyOrDefault(s => s.IsKind(VBasic.SyntaxKind.LabelStatement)); - var onlyOnErrorGotoStatement = statements.OnlyOrDefault(s => s.IsKind(VBasic.SyntaxKind.OnErrorGoToLabelStatement)); - - // See https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/on-error-statement - if (onlyIdentifierLabel != null && onlyOnErrorGotoStatement != null) { - var statementsList = statements.ToList(); - var onlyIdentifierLabelIndex = statementsList.IndexOf(onlyIdentifierLabel); - var onlyOnErrorGotoStatementIndex = statementsList.IndexOf(onlyOnErrorGotoStatement); - - // Even this very simple case can generate compile errors if the error handling uses statements declared in the scope of the try block - // For now, the user will have to fix these manually, in future it'd be possible to hoist any used declarations out of the try block - if (onlyOnErrorGotoStatementIndex < onlyIdentifierLabelIndex) { - var beforeStatements = await ConvertStatements(statements.Take(onlyOnErrorGotoStatementIndex)); - var tryBlockStatements = await ConvertStatements(statements.Take(onlyIdentifierLabelIndex).Skip(onlyOnErrorGotoStatementIndex + 1)); - var tryBlock = SyntaxFactory.Block(tryBlockStatements); - var afterStatements = await ConvertStatements(statements.Skip(onlyIdentifierLabelIndex + 1)); - - var catchClauseSyntax = SyntaxFactory.CatchClause(); - - // Default to putting the statements after the catch block in case logic falls through, but if the last statement is a return, put them inside the catch block for neatness. - if (tryBlockStatements.LastOrDefault().IsKind(SyntaxKind.ReturnStatement)) { - catchClauseSyntax = catchClauseSyntax.WithBlock(SyntaxFactory.Block(afterStatements)); - afterStatements = new List(); - } - - var tryStatement = SyntaxFactory.TryStatement(SyntaxFactory.SingletonList(catchClauseSyntax)).WithBlock(tryBlock); - return beforeStatements.Append(tryStatement).Concat(afterStatements).ToList(); - } - } - - return null; - } - } public override async Task VisitParameterList(VBSyntax.ParameterListSyntax node) { @@ -1564,127 +574,6 @@ public override async Task VisitArrayRankSpecifier(VBasic.Synt return SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SeparatedList(Enumerable.Repeat(SyntaxFactory.OmittedArraySizeExpression(), node.Rank))); } - /// PERF: This is a hot code path, try to avoid using things like GetOperation except where needed. - public override async Task VisitIdentifierName(VBasic.Syntax.IdentifierNameSyntax node) - { - var identifier = SyntaxFactory.IdentifierName(ConvertIdentifier(node.Identifier, node.GetAncestor() != null)); - - bool requiresQualification = !node.Parent.IsKind(VBasic.SyntaxKind.SimpleMemberAccessExpression, VBasic.SyntaxKind.QualifiedName, VBasic.SyntaxKind.NameColonEquals, VBasic.SyntaxKind.ImportsStatement, VBasic.SyntaxKind.NamespaceStatement, VBasic.SyntaxKind.NamedFieldInitializer) || - node.Parent is VBSyntax.NamedFieldInitializerSyntax nfs && nfs.Expression == node || - node.Parent is VBasic.Syntax.MemberAccessExpressionSyntax maes && maes.Expression == node; - var qualifiedIdentifier = requiresQualification - ? QualifyNode(node, identifier) : identifier; - - var sym = GetSymbolInfoInDocument(node); - if (sym is ILocalSymbol) { - if (sym.IsStatic && sym.ContainingSymbol is IMethodSymbol m && m.AssociatedSymbol is IPropertySymbol) { - qualifiedIdentifier = qualifiedIdentifier.WithParentPropertyAccessorKind(m.MethodKind); - } - - var vbMethodBlock = node.Ancestors().OfType().FirstOrDefault(); - if (vbMethodBlock != null && - vbMethodBlock.MustReturn() && - !node.Parent.IsKind(VBasic.SyntaxKind.NameOfExpression) && - node.Identifier.ValueText.Equals(CommonConversions.GetMethodBlockBaseIdentifierForImplicitReturn(vbMethodBlock).ValueText, StringComparison.OrdinalIgnoreCase)) { - var retVar = CommonConversions.GetRetVariableNameOrNull(vbMethodBlock); - if (retVar != null) { - return retVar; - } - } - } - - return await AdjustForImplicitInvocationAsync(node, qualifiedIdentifier); - } - - private async Task AdjustForImplicitInvocationAsync(SyntaxNode node, ExpressionSyntax qualifiedIdentifier) - { - //PERF: Avoid calling expensive GetOperation when it's easy - bool nonExecutableNode = node.IsParentKind(VBasic.SyntaxKind.QualifiedName); - if (nonExecutableNode || _semanticModel.SyntaxTree != node.SyntaxTree) return qualifiedIdentifier; - - if (await TryConvertParameterizedPropertyAsync(_semanticModel.GetOperation(node), node, qualifiedIdentifier) is {} - invocation) - { - return invocation; - } - - return AddEmptyArgumentListIfImplicit(node, qualifiedIdentifier); - } - - public override async Task VisitQualifiedName(VBasic.Syntax.QualifiedNameSyntax node) - { - var symbol = GetSymbolInfoInDocument(node); - if (symbol != null) { - return CommonConversions.GetTypeSyntax(symbol.GetSymbolType()); - } - var lhsSyntax = await node.Left.AcceptAsync(TriviaConvertingExpressionVisitor); - var rhsSyntax = await node.Right.AcceptAsync(TriviaConvertingExpressionVisitor); - - VBasic.Syntax.NameSyntax topLevelName = node; - while (topLevelName.Parent is VBasic.Syntax.NameSyntax parentName) { - topLevelName = parentName; - } - var partOfNamespaceDeclaration = topLevelName.Parent.IsKind(VBasic.SyntaxKind.NamespaceStatement); - var leftIsGlobal = node.Left.IsKind(VBasic.SyntaxKind.GlobalName); - ExpressionSyntax qualifiedName; - if (partOfNamespaceDeclaration || !(lhsSyntax is SimpleNameSyntax sns)) { - if (leftIsGlobal) return rhsSyntax; - qualifiedName = lhsSyntax; - } else { - qualifiedName = QualifyNode(node.Left, sns); - } - - return leftIsGlobal ? SyntaxFactory.AliasQualifiedName((IdentifierNameSyntax)lhsSyntax, rhsSyntax) : - SyntaxFactory.QualifiedName((NameSyntax)qualifiedName, rhsSyntax); - } - - public override async Task VisitGenericName(VBasic.Syntax.GenericNameSyntax node) - { - var symbol = GetSymbolInfoInDocument(node); - var genericNameSyntax = await GenericNameAccountingForReducedParametersAsync(node, symbol); - return await AdjustForImplicitInvocationAsync(node, genericNameSyntax); - } - - /// - /// Adjusts for Visual Basic's omission of type arguments that can be inferred in reduced generic method invocations - /// The upfront WithExpandedRootAsync pass should ensure this only happens on broken syntax trees. - /// In those cases, just comment the errant information. It would only cause a compiling change in behaviour if it can be inferred, was not set to the inferred value, and was reflected upon within the method body - /// - private async Task GenericNameAccountingForReducedParametersAsync(VBSyntax.GenericNameSyntax node, ISymbol symbol) - { - SyntaxToken convertedIdentifier = ConvertIdentifier(node.Identifier); - if (symbol is IMethodSymbol vbMethod && vbMethod.IsReducedTypeParameterMethod()) { - var allTypeArgs = GetOrNullAllTypeArgsIncludingInferred(vbMethod); - if (allTypeArgs != null) { - return (SimpleNameSyntax)CommonConversions.CsSyntaxGenerator.GenericName(convertedIdentifier.Text, allTypeArgs); - } - var commentedText = "/* " + (await ConvertTypeArgumentListAsync(node)).ToFullString() + " */"; - var error = SyntaxFactory.ParseLeadingTrivia($"#error Conversion error: Could not convert all type parameters, so they've been commented out. Inferred type may be different{Environment.NewLine}"); - var partialConversion = SyntaxFactory.Comment(commentedText); - return ValidSyntaxFactory.IdentifierName(convertedIdentifier).WithPrependedLeadingTrivia(error).WithTrailingTrivia(partialConversion); - } - - return SyntaxFactory.GenericName(convertedIdentifier, await ConvertTypeArgumentListAsync(node)); - } - - /// TODO: Would be more robust to use - private ITypeSymbol[] GetOrNullAllTypeArgsIncludingInferred(IMethodSymbol vbMethod) - { - if (!(CommonConversions.GetCsOriginalSymbolOrNull(vbMethod) is IMethodSymbol - csSymbolWithInferredTypeParametersSet)) return null; - var argSubstitutions = vbMethod.TypeParameters - .Zip(vbMethod.TypeArguments, (parameter, arg) => (parameter, arg)) - .ToDictionary(x => x.parameter.Name, x => x.arg); - var allTypeArgs = csSymbolWithInferredTypeParametersSet.GetTypeArguments() - .Select(a => a.Kind == SymbolKind.TypeParameter && argSubstitutions.TryGetValue(a.Name, out var t) ? t : a) - .ToArray(); - return allTypeArgs; - } - - private async Task ConvertTypeArgumentListAsync(VBSyntax.GenericNameSyntax node) - { - return await node.TypeArgumentList.AcceptAsync(TriviaConvertingExpressionVisitor); - } public override async Task VisitTypeArgumentList(VBasic.Syntax.TypeArgumentListSyntax node) { @@ -1695,7 +584,7 @@ public override async Task VisitTypeArgumentList(VBasic.Syntax private async Task ConvertCastExpressionAsync(VBSyntax.CastExpressionSyntax node, ExpressionSyntax convertMethodOrNull = null, VBSyntax.TypeSyntax castToOrNull = null) { - var simplifiedOrNull = await WithRemovedRedundantConversionOrNullAsync(node, node.Expression); + var simplifiedOrNull = await CommonConversions.WithRemovedRedundantConversionOrNullAsync(node, node.Expression); if (simplifiedOrNull != null) return simplifiedOrNull; var expressionSyntax = await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); if (_semanticModel.GetOperation(node) is not IConversionOperation { Conversion.IsIdentity: true }) { @@ -1727,437 +616,9 @@ private static InvocationExpressionSyntax Invoke(ExpressionSyntax toInvoke, Expr ); } - private ExpressionSyntax GetConvertMethodForKeywordOrNull(SyntaxNode type) - { - var targetType = _semanticModel.GetTypeInfo(type).Type; - return GetConvertMethodForKeywordOrNull(targetType); - } - - private ExpressionSyntax GetConvertMethodForKeywordOrNull(ITypeSymbol targetType) - { - _extraUsingDirectives.Add(ConvertType.Namespace); - return targetType != null && - _convertMethodsLookupByReturnType.Value.TryGetValue(targetType, out var convertMethodName) - ? SyntaxFactory.ParseExpression(convertMethodName) - : null; - } - - private static bool IsSubPartOfConditionalAccess(VBasic.Syntax.MemberAccessExpressionSyntax node) - { - var firstPossiblyConditionalAncestor = node.Parent; - while (firstPossiblyConditionalAncestor != null && - firstPossiblyConditionalAncestor.IsKind(VBasic.SyntaxKind.InvocationExpression, - VBasic.SyntaxKind.SimpleMemberAccessExpression)) { - firstPossiblyConditionalAncestor = firstPossiblyConditionalAncestor.Parent; - } - - return firstPossiblyConditionalAncestor?.IsKind(VBasic.SyntaxKind.ConditionalAccessExpression) == true; - } - - private async Task> ConvertArgumentsAsync(VBasic.Syntax.ArgumentListSyntax node) - { - ISymbol invocationSymbol = GetInvocationSymbol(node.Parent); - var forceNamedParameters = false; - var invocationHasOverloads = invocationSymbol.HasOverloads(); - - var processedParameters = new HashSet(StringComparer.OrdinalIgnoreCase); - var argumentSyntaxs = (await node.Arguments.SelectAsync(ConvertArg)).Where(a => a != null); - return argumentSyntaxs.Concat(GetAdditionalRequiredArgs(node.Arguments, processedParameters, invocationSymbol, invocationHasOverloads)); - - async Task ConvertArg(VBSyntax.ArgumentSyntax arg, int argIndex) - { - var argName = arg is VBSyntax.SimpleArgumentSyntax { IsNamed: true } namedArg ? namedArg.NameColonEquals.Name.Identifier.Text : null; - var parameterSymbol = invocationSymbol?.GetParameters().GetArgument(argName, argIndex); - var convertedArg = await ConvertArgForParameter(arg, parameterSymbol); - - if (convertedArg is not null && parameterSymbol is not null) { - processedParameters.Add(parameterSymbol.Name); - } - - return convertedArg; - } - - async Task ConvertArgForParameter(VBSyntax.ArgumentSyntax arg, IParameterSymbol parameterSymbol) - { - if (arg.IsOmitted) { - if (invocationSymbol != null && !invocationHasOverloads) { - forceNamedParameters = true; - return null; //Prefer to skip omitted and use named parameters when the symbol has only one overload - } - return ConvertOmittedArgument(parameterSymbol); - } - - var argSyntax = await arg.AcceptAsync(TriviaConvertingExpressionVisitor); - if (forceNamedParameters && !arg.IsNamed && parameterSymbol != null) { - return argSyntax.WithNameColon(SyntaxFactory.NameColon(SyntaxFactory.IdentifierName(CommonConversions.CsEscapedIdentifier(parameterSymbol.Name)))); - } - - return argSyntax; - } - - ArgumentSyntax ConvertOmittedArgument(IParameterSymbol parameter) - { - if (parameter == null) { - return SyntaxFactory.Argument(SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression)); - } - - var csRefKind = CommonConversions.GetCsRefKind(parameter); - return csRefKind != RefKind.None - ? CreateOptionalRefArg(parameter, csRefKind) - : SyntaxFactory.Argument(CommonConversions.Literal(parameter.ExplicitDefaultValue)); - } - } - - private IEnumerable GetAdditionalRequiredArgs( - IEnumerable arguments, - ISymbol invocationSymbol) - { - var invocationHasOverloads = invocationSymbol.HasOverloads(); - return GetAdditionalRequiredArgs(arguments, processedParametersNames: null, invocationSymbol, invocationHasOverloads); - } - - private IEnumerable GetAdditionalRequiredArgs( - IEnumerable arguments, - ICollection processedParametersNames, - ISymbol invocationSymbol, - bool invocationHasOverloads) - { - if (invocationSymbol is null) { - yield break; - } - - var invocationHasOmittedArgs = arguments.Any(t => t.IsOmitted); - var expandOptionalArgs = invocationHasOmittedArgs && invocationHasOverloads; - var missingArgs = invocationSymbol.GetParameters().Where(t => processedParametersNames is null || !processedParametersNames.Contains(t.Name)); - var requiresCompareMethod = _visualBasicEqualityComparison.OptionCompareTextCaseInsensitive && RequiresStringCompareMethodToBeAppended(invocationSymbol); - - foreach (var parameterSymbol in missingArgs) { - var extraArg = CreateExtraArgOrNull(parameterSymbol, requiresCompareMethod, expandOptionalArgs); - if (extraArg != null) { - yield return extraArg; - } - } - } - - private ArgumentSyntax CreateExtraArgOrNull(IParameterSymbol p, bool requiresCompareMethod, bool expandOptionalArgs) - { - var csRefKind = CommonConversions.GetCsRefKind(p); - if (csRefKind != RefKind.None) { - return CreateOptionalRefArg(p, csRefKind); - } - - if (requiresCompareMethod && p.Type.GetFullMetadataName() == "Microsoft.VisualBasic.CompareMethod") { - return (ArgumentSyntax)CommonConversions.CsSyntaxGenerator.Argument(p.Name, RefKind.None, _visualBasicEqualityComparison.CompareMethodExpression); - } - - if (expandOptionalArgs && p.HasExplicitDefaultValue) { - return (ArgumentSyntax)CommonConversions.CsSyntaxGenerator.Argument(p.Name, RefKind.None, CommonConversions.Literal(p.ExplicitDefaultValue)); - } - - return null; - } - - private ArgumentSyntax CreateOptionalRefArg(IParameterSymbol p, RefKind refKind) - { - string prefix = $"arg{p.Name}"; - var type = CommonConversions.GetTypeSyntax(p.Type); - ExpressionSyntax initializer; - if (p.HasExplicitDefaultValue) { - initializer = CommonConversions.Literal(p.ExplicitDefaultValue); - } else if (HasOptionalAttribute(p)) { - if (TryGetDefaultParameterValueAttributeValue(p, out var defaultValue)){ - initializer = CommonConversions.Literal(defaultValue); - } else { - initializer = SyntaxFactory.DefaultExpression(type); - } - } else { - //invalid VB.NET code - return null; - } - var local = _typeContext.PerScopeState.Hoist(new AdditionalDeclaration(prefix, initializer, type)); - return (ArgumentSyntax)CommonConversions.CsSyntaxGenerator.Argument(p.Name, refKind, local.IdentifierName); - - bool HasOptionalAttribute(IParameterSymbol p) - { - var optionalAttribute = CommonConversions.KnownTypes.OptionalAttribute; - if (optionalAttribute == null) { - return false; - } - - return p.GetAttributes().Any(a => SymbolEqualityComparer.IncludeNullability.Equals(a.AttributeClass, optionalAttribute)); - } - - bool TryGetDefaultParameterValueAttributeValue(IParameterSymbol p, out object defaultValue) - { - defaultValue = null; - - var defaultParameterValueAttribute = CommonConversions.KnownTypes.DefaultParameterValueAttribute; - if (defaultParameterValueAttribute == null) { - return false; - } - - var attributeData = p.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.IncludeNullability.Equals(a.AttributeClass, defaultParameterValueAttribute)); - if (attributeData == null) { - return false; - } - - if (attributeData.ConstructorArguments.Length == 0) { - return false; - } - - defaultValue = attributeData.ConstructorArguments.First().Value; - return true; - } - } - - private RefConversion NeedsVariableForArgument(VBasic.Syntax.ArgumentSyntax node, RefKind refKind) - { - if (refKind == RefKind.None) return RefConversion.Inline; - if (!(node is VBSyntax.SimpleArgumentSyntax sas) || sas is { Expression: VBSyntax.ParenthesizedExpressionSyntax }) return RefConversion.PreAssigment; - var expression = sas.Expression; - - return GetRefConversion(expression); - - RefConversion GetRefConversion(VBSyntax.ExpressionSyntax expression) - { - var symbolInfo = GetSymbolInfoInDocument(expression); - if (symbolInfo is IPropertySymbol { ReturnsByRef: false, ReturnsByRefReadonly: false } propertySymbol) { - // a property in VB.NET code can be ReturnsByRef if it's defined in a C# assembly the VB.NET code references - return propertySymbol.IsReadOnly ? RefConversion.PreAssigment : RefConversion.PreAndPostAssignment; - } - else if (symbolInfo is IFieldSymbol { IsConst: true } or ILocalSymbol { IsConst: true }) { - return RefConversion.PreAssigment; - } else if (symbolInfo is IMethodSymbol { ReturnsByRef: false, ReturnsByRefReadonly: false }) { - // a method in VB.NET code can be ReturnsByRef if it's defined in a C# assembly the VB.NET code references - return RefConversion.PreAssigment; - } - - if (DeclaredInUsing(symbolInfo)) return RefConversion.PreAssigment; - - if (expression is VBasic.Syntax.IdentifierNameSyntax || expression is VBSyntax.MemberAccessExpressionSyntax || - IsRefArrayAcces(expression)) { - - var typeInfo = _semanticModel.GetTypeInfo(expression); - bool isTypeMismatch = typeInfo.Type == null || !typeInfo.Type.Equals(typeInfo.ConvertedType, SymbolEqualityComparer.IncludeNullability); - - if (isTypeMismatch) { - return RefConversion.PreAndPostAssignment; - } - - return RefConversion.Inline; - } - - return RefConversion.PreAssigment; - } - - bool IsRefArrayAcces(VBSyntax.ExpressionSyntax expression) - { - if (!(expression is VBSyntax.InvocationExpressionSyntax ies)) return false; - var op = _semanticModel.GetOperation(ies); - return (op.IsArrayElementAccess() || IsReturnsByRefPropertyElementAccess(op)) - && GetRefConversion(ies.Expression) == RefConversion.Inline; - - static bool IsReturnsByRefPropertyElementAccess(IOperation op) - { - return op.IsPropertyElementAccess() - && op is IPropertyReferenceOperation { Property: { } prop } - && (prop.ReturnsByRef || prop.ReturnsByRefReadonly); - } - } - } - - 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) - { - var symbol = invocation.TypeSwitch( - (VBSyntax.InvocationExpressionSyntax e) => _semanticModel.GetSymbolInfo(e).ExtractBestMatch(), - (VBSyntax.ObjectCreationExpressionSyntax e) => _semanticModel.GetSymbolInfo(e).ExtractBestMatch(), - (VBSyntax.RaiseEventStatementSyntax e) => _semanticModel.GetSymbolInfo(e.Name).ExtractBestMatch(), - (VBSyntax.MidExpressionSyntax _) => CommonConversions.KnownTypes.VbCompilerStringType?.GetMembers("MidStmtStr").FirstOrDefault(), - _ => throw new NotSupportedException()); - return symbol; - } - - private async Task ToAttributeArgumentAsync(VBasic.Syntax.ArgumentSyntax arg) - { - if (!(arg is VBasic.Syntax.SimpleArgumentSyntax)) - throw new NotSupportedException(); - var a = (VBasic.Syntax.SimpleArgumentSyntax)arg; - var attr = SyntaxFactory.AttributeArgument(await a.Expression.AcceptAsync(TriviaConvertingExpressionVisitor)); - if (a.IsNamed) { - attr = attr.WithNameEquals(SyntaxFactory.NameEquals(await a.NameColonEquals.Name.AcceptAsync(TriviaConvertingExpressionVisitor))); - } - return attr; - } - - private SyntaxToken ConvertIdentifier(SyntaxToken identifierIdentifier, bool isAttribute = false) - { - return CommonConversions.ConvertIdentifier(identifierIdentifier, isAttribute); - } - - private static CSharpSyntaxNode ReplaceRightmostIdentifierText(CSharpSyntaxNode expr, SyntaxToken idToken, string overrideIdentifier) - { - return expr.ReplaceToken(idToken, SyntaxFactory.Identifier(overrideIdentifier).WithTriviaFrom(idToken).WithAdditionalAnnotations(idToken.GetAnnotations())); - } - - /// - /// If there's a single numeric arg, let's assume it's an indexer (probably an array). - /// Otherwise, err on the side of a method call. - /// - private bool ProbablyNotAMethodCall(VBasic.Syntax.InvocationExpressionSyntax node, ISymbol symbol, ITypeSymbol symbolReturnType) - { - return !node.IsParentKind(VBasic.SyntaxKind.CallStatement) && !(symbol is IMethodSymbol) && - symbolReturnType.IsErrorType() && node.Expression is VBasic.Syntax.IdentifierNameSyntax && - node.ArgumentList?.Arguments.OnlyOrDefault()?.GetExpression() is {} arg && - _semanticModel.GetTypeInfo(arg).Type.IsNumericType(); - } - - private async Task ConvertArgumentListOrEmptyAsync(SyntaxNode node, VBSyntax.ArgumentListSyntax argumentList) - { - return await argumentList.AcceptAsync(TriviaConvertingExpressionVisitor) ?? CreateArgList(_semanticModel.GetSymbolInfo(node).Symbol); - } - - private ArgumentListSyntax CreateArgList(ISymbol invocationSymbol) - { - return SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList( - GetAdditionalRequiredArgs(Array.Empty(), invocationSymbol)) - ); - } - - private async Task SubstituteVisualBasicMethodOrNullAsync(VBSyntax.InvocationExpressionSyntax node, ISymbol symbol) - { - ExpressionSyntax cSharpSyntaxNode = null; - if (IsVisualBasicChrMethod(symbol)) { - var vbArg = node.ArgumentList.Arguments.Single().GetExpression(); - var constValue = _semanticModel.GetConstantValue(vbArg); - if (IsCultureInvariant(constValue)) { - var csArg = await vbArg.AcceptAsync(TriviaConvertingExpressionVisitor); - cSharpSyntaxNode = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node, csArg, true, true, true, forceTargetType: _semanticModel.GetTypeInfo(node).Type); - } - } - - if (SimpleMethodReplacement.TryGet(symbol, out var methodReplacement) && - methodReplacement.ReplaceIfMatches(symbol, await ConvertArgumentsAsync(node.ArgumentList), false) is {} csExpression) { - cSharpSyntaxNode = csExpression; - } - - return cSharpSyntaxNode; - } - - - private static bool RequiresStringCompareMethodToBeAppended(ISymbol symbol) => - symbol?.ContainingType.Name == nameof(Strings) && - symbol.ContainingType.ContainingNamespace.Name == nameof(Microsoft.VisualBasic) && - symbol.ContainingType.ContainingNamespace.ContainingNamespace.Name == nameof(Microsoft) && - symbol.Name is "InStr" or "InStrRev" or "Replace" or "Split" or "StrComp"; - - private static bool IsVisualBasicChrMethod(ISymbol symbol) => - symbol is not null - && symbol.ContainingNamespace.MetadataName == nameof(Microsoft.VisualBasic) - && (symbol.Name == "ChrW" || symbol.Name == "Chr"); - - /// - /// https://github.com/icsharpcode/CodeConverter/issues/745 - /// - private static bool IsCultureInvariant(Optional constValue) => - constValue.HasValue && Convert.ToUInt64(constValue.Value, CultureInfo.InvariantCulture) <= 127; - - private CSharpSyntaxNode AddEmptyArgumentListIfImplicit(SyntaxNode node, ExpressionSyntax id) - { - if (_semanticModel.SyntaxTree != node.SyntaxTree) return id; - return _semanticModel.GetOperation(node) switch { - IInvocationOperation invocation => SyntaxFactory.InvocationExpression(id, CreateArgList(invocation.TargetMethod)), - IPropertyReferenceOperation propReference when propReference.Property.Parameters.Any() => SyntaxFactory.InvocationExpression(id, CreateArgList(propReference.Property)), - _ => id - }; - } - - /// - /// The pre-expansion phase should handle this for compiling nodes. - /// This is mainly targeted at dealing with missing semantic info. - /// - /// - private ExpressionSyntax QualifyNode(SyntaxNode node, SimpleNameSyntax left) - { - var nodeSymbolInfo = GetSymbolInfoInDocument(node); - if (left != null && - nodeSymbolInfo != null && - nodeSymbolInfo.MatchesKind(SymbolKind.TypeParameter) == false && - nodeSymbolInfo.ContainingSymbol is INamespaceOrTypeSymbol containingSymbol && - !ContextImplicitlyQualfiesSymbol(node, containingSymbol)) { - - if (containingSymbol is ITypeSymbol containingTypeSymbol && - !nodeSymbolInfo.IsConstructor() /* Constructors are implicitly qualified with their type */) { - // Qualify with a type to handle VB's type promotion https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/declared-elements/type-promotion - var qualification = - CommonConversions.GetTypeSyntax(containingTypeSymbol); - return Qualify(qualification.ToString(), left); - } + private SyntaxToken ConvertIdentifier(SyntaxToken identifierIdentifier, bool isAttribute = false) => + CommonConversions.ConvertIdentifier(identifierIdentifier, isAttribute); - if (nodeSymbolInfo.IsNamespace()) { - // Turn partial namespace qualification into full namespace qualification - var qualification = - containingSymbol.ToCSharpDisplayString(); - return Qualify(qualification, left); - } - } - - return left; - } - - private bool ContextImplicitlyQualfiesSymbol(SyntaxNode syntaxNodeContext, INamespaceOrTypeSymbol symbolToCheck) - { - return symbolToCheck is INamespaceSymbol ns && ns.IsGlobalNamespace || - EnclosingTypeImplicitlyQualifiesSymbol(syntaxNodeContext, symbolToCheck); - } - - private bool EnclosingTypeImplicitlyQualifiesSymbol(SyntaxNode syntaxNodeContext, INamespaceOrTypeSymbol symbolToCheck) - { - ISymbol typeContext = syntaxNodeContext.GetEnclosingDeclaredTypeSymbol(_semanticModel); - var implicitCsQualifications = ((ITypeSymbol)typeContext).GetBaseTypesAndThis() - .Concat(typeContext.FollowProperty(n => n.ContainingSymbol)) - .ToList(); - - return implicitCsQualifications.Contains(symbolToCheck); - } - - private static QualifiedNameSyntax Qualify(string qualification, ExpressionSyntax toBeQualified) - { - return SyntaxFactory.QualifiedName( - SyntaxFactory.ParseName(qualification), - (SimpleNameSyntax)toBeQualified); - } - - /// The ISymbol if available in this document, otherwise null - /// It's possible to use _semanticModel.GetSpeculativeSymbolInfo(...) if you know (or can approximate) the position where the symbol would have been in the original document. - private TSymbol GetSymbolInfoInDocument(SyntaxNode node) where TSymbol: class, ISymbol - { - return _semanticModel.SyntaxTree == node.SyntaxTree ? _semanticModel.GetSymbolInfo(node).ExtractBestMatch(): null; - } + public Task> ConvertMethodBodyStatementsAsync(VBasic.VisualBasicSyntaxNode node, IReadOnlyCollection statements, bool isIterator = false, IdentifierNameSyntax csReturnVariable = null) => + _lambdaConverter.ConvertMethodBodyStatementsAsync(node, statements, isIterator, csReturnVariable); } \ No newline at end of file diff --git a/CodeConverter/CSharp/InitializerConverter.cs b/CodeConverter/CSharp/InitializerConverter.cs new file mode 100644 index 00000000..4d94aac7 --- /dev/null +++ b/CodeConverter/CSharp/InitializerConverter.cs @@ -0,0 +1,131 @@ +using Microsoft.CodeAnalysis.Operations; + +namespace ICSharpCode.CodeConverter.CSharp; + +internal class InitializerConverter +{ + private readonly SemanticModel _semanticModel; + private readonly Dictionary> _tempNameForAnonymousScope; + private readonly HashSet _generatedNames; + public CommonConversions CommonConversions { get; } + + public InitializerConverter(SemanticModel semanticModel, CommonConversions commonConversions, HashSet generatedNames, + Dictionary> tempNameForAnonymousScope) + { + CommonConversions = commonConversions; + _semanticModel = semanticModel; + _generatedNames = generatedNames; + _tempNameForAnonymousScope = tempNameForAnonymousScope; + } + + public async Task ConvertInferredFieldInitializerAsync(VBasic.Syntax.InferredFieldInitializerSyntax node) + { + return CS.SyntaxFactory.AnonymousObjectMemberDeclarator(await node.Expression.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor)); + } + + /// Collection initialization has many variants in both VB and C#. Please add especially many test cases when touching this. + public async Task ConvertCollectionInitializerAsync(VBasic.Syntax.CollectionInitializerSyntax node) + { + var isExplicitCollectionInitializer = node.Parent is VBasic.Syntax.ObjectCollectionInitializerSyntax + || node.Parent is VBasic.Syntax.CollectionInitializerSyntax + || node.Parent is VBasic.Syntax.ArrayCreationExpressionSyntax; + var initializerKind = node.IsParentKind(VBasic.SyntaxKind.ObjectCollectionInitializer) || node.IsParentKind(VBasic.SyntaxKind.ObjectCreationExpression) ? + CS.SyntaxKind.CollectionInitializerExpression : + node.IsParentKind(VBasic.SyntaxKind.CollectionInitializer) && IsComplexInitializer(node) ? CS.SyntaxKind.ComplexElementInitializerExpression : + CS.SyntaxKind.ArrayInitializerExpression; + var initializers = (await node.Initializers.SelectAsync(async i => { + var convertedInitializer = await i.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor); + return CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(i, convertedInitializer, false); + })); + var initializer = CS.SyntaxFactory.InitializerExpression(initializerKind, CS.SyntaxFactory.SeparatedList(initializers)); + if (isExplicitCollectionInitializer) return initializer; + + var convertedType = _semanticModel.GetTypeInfo(node).ConvertedType; + var dimensions = convertedType is IArrayTypeSymbol ats ? ats.Rank : 1; // For multidimensional array [,] note these are different from nested arrays [][] + if (!(convertedType.GetEnumerableElementTypeOrDefault() is {} elementType)) return CS.SyntaxFactory.ImplicitArrayCreationExpression(initializer); + + if (!initializers.Any() && dimensions == 1) { + var arrayTypeArgs = CS.SyntaxFactory.TypeArgumentList(CS.SyntaxFactory.SingletonSeparatedList(CommonConversions.GetTypeSyntax(elementType))); + var arrayEmpty = CS.SyntaxFactory.MemberAccessExpression(CS.SyntaxKind.SimpleMemberAccessExpression, + ValidSyntaxFactory.IdentifierName(nameof(Array)), CS.SyntaxFactory.GenericName(nameof(Array.Empty)).WithTypeArgumentList(arrayTypeArgs)); + return CS.SyntaxFactory.InvocationExpression(arrayEmpty); + } + + bool hasExpressionToInferTypeFrom = node.Initializers.SelectMany(n => n.DescendantNodesAndSelf()).Any(n => n is not VBasic.Syntax.CollectionInitializerSyntax); + if (hasExpressionToInferTypeFrom) { + var commas = Enumerable.Repeat(CS.SyntaxFactory.Token(CS.SyntaxKind.CommaToken), dimensions - 1); + return CS.SyntaxFactory.ImplicitArrayCreationExpression(CS.SyntaxFactory.TokenList(commas), initializer); + } + + var arrayType = (CSSyntax.ArrayTypeSyntax)CommonConversions.CsSyntaxGenerator.ArrayTypeExpression(CommonConversions.GetTypeSyntax(elementType)); + var sizes = Enumerable.Repeat(CS.SyntaxFactory.OmittedArraySizeExpression(), dimensions); + var arrayRankSpecifierSyntax = CS.SyntaxFactory.SingletonList(CS.SyntaxFactory.ArrayRankSpecifier(CS.SyntaxFactory.SeparatedList(sizes))); + arrayType = arrayType.WithRankSpecifiers(arrayRankSpecifierSyntax); + return CS.SyntaxFactory.ArrayCreationExpression(arrayType, initializer); + } + + public async Task ConvertObjectMemberInitializerAsync(VBasic.Syntax.ObjectMemberInitializerSyntax node) + { + var initializers = await node.Initializers.AcceptSeparatedListAsync(CommonConversions.TriviaConvertingExpressionVisitor); + return CS.SyntaxFactory.InitializerExpression(CS.SyntaxKind.ObjectInitializerExpression, initializers); + } + + public async Task ConvertNamedFieldInitializerAsync(VBasic.Syntax.NamedFieldInitializerSyntax node) + { + var csExpressionSyntax = await node.Expression.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor); + csExpressionSyntax = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, csExpressionSyntax); + if (node.Parent?.Parent is VBasic.Syntax.AnonymousObjectCreationExpressionSyntax {Initializer: {Initializers: var initializers}} anonymousObjectCreationExpression) { + string nameIdentifierText = node.Name.Identifier.Text; + var isAnonymouslyReused = initializers.OfType() + .Select(i => i.Expression).OfType() + .Any(maes => maes.Expression is null && maes.Name.Identifier.Text.Equals(nameIdentifierText, StringComparison.OrdinalIgnoreCase)); + if (isAnonymouslyReused) { + string tempNameForAnonymousSelfReference = GenerateUniqueVariableName(node.Name, "temp" + ((VBSyntax.SimpleNameSyntax) node.Name).Identifier.Text.UppercaseFirstLetter()); + csExpressionSyntax = DeclareVariableInline(csExpressionSyntax, tempNameForAnonymousSelfReference); + if (!_tempNameForAnonymousScope.TryGetValue(nameIdentifierText, out var stack)) { + stack = _tempNameForAnonymousScope[nameIdentifierText] = new Stack<(SyntaxNode Scope, string TempName)>(); + } + stack.Push((anonymousObjectCreationExpression, tempNameForAnonymousSelfReference)); + } + + var anonymousObjectMemberDeclaratorSyntax = CS.SyntaxFactory.AnonymousObjectMemberDeclarator( + CS.SyntaxFactory.NameEquals(CS.SyntaxFactory.IdentifierName(CommonConversions.ConvertIdentifier(node.Name.Identifier))), + csExpressionSyntax); + return anonymousObjectMemberDeclaratorSyntax; + } + + return CS.SyntaxFactory.AssignmentExpression(CS.SyntaxKind.SimpleAssignmentExpression, + await node.Name.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor), + csExpressionSyntax + ); + } + + public async Task ConvertObjectCollectionInitializerAsync(VBasic.Syntax.ObjectCollectionInitializerSyntax node) + { + return await node.Initializer.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor); //Dictionary initializer comes through here despite the FROM keyword not being in the source code + } + + private bool IsComplexInitializer(VBSyntax.CollectionInitializerSyntax node) + { + return _semanticModel.GetOperation(node.Parent.Parent) is IObjectOrCollectionInitializerOperation initializer && + initializer.Initializers.OfType().Any(); + } + + private string GenerateUniqueVariableName(VisualBasicSyntaxNode existingNode, string varNameBase) => NameGenerator.CS.GetUniqueVariableNameInScope(_semanticModel, _generatedNames, existingNode, varNameBase); + + private static CSSyntax.ExpressionSyntax DeclareVariableInline(CSSyntax.ExpressionSyntax csExpressionSyntax, string temporaryName) + { + var temporaryNameId = CS.SyntaxFactory.Identifier(temporaryName); + var temporaryNameExpression = ValidSyntaxFactory.IdentifierName(temporaryNameId); + csExpressionSyntax = CS.SyntaxFactory.ConditionalExpression( + CS.SyntaxFactory.IsPatternExpression( + csExpressionSyntax, + CS.SyntaxFactory.VarPattern( + CS.SyntaxFactory.SingleVariableDesignation(temporaryNameId))), + temporaryNameExpression, + CS.SyntaxFactory.LiteralExpression( + CS.SyntaxKind.DefaultLiteralExpression, + CS.SyntaxFactory.Token(CS.SyntaxKind.DefaultKeyword))); + return csExpressionSyntax; + } +} \ No newline at end of file diff --git a/CodeConverter/CSharp/LambdaConverter.cs b/CodeConverter/CSharp/LambdaConverter.cs index ad81097c..f1306f5b 100644 --- a/CodeConverter/CSharp/LambdaConverter.cs +++ b/CodeConverter/CSharp/LambdaConverter.cs @@ -7,17 +7,68 @@ namespace ICSharpCode.CodeConverter.CSharp; internal class LambdaConverter { private readonly SemanticModel _semanticModel; + private readonly Stack _withBlockLhs; + private readonly HashSet _extraUsingDirectives; + private readonly ITypeContext _typeContext; private readonly Solution _solution; - public LambdaConverter(CommonConversions commonConversions, SemanticModel semanticModel) + public LambdaConverter(CommonConversions commonConversions, SemanticModel semanticModel, Stack withBlockLhs, HashSet extraUsingDirectives, ITypeContext typeContext) { CommonConversions = commonConversions; _semanticModel = semanticModel; + _withBlockLhs = withBlockLhs; + _extraUsingDirectives = extraUsingDirectives; + _typeContext = typeContext; _solution = CommonConversions.Document.Project.Solution; } public CommonConversions CommonConversions { get; } + + + public async Task ConvertMultiLineLambdaAsync(VBSyntax.MultiLineLambdaExpressionSyntax node) + { + var originalIsWithinQuery = CommonConversions.TriviaConvertingExpressionVisitor.IsWithinQuery; + CommonConversions.TriviaConvertingExpressionVisitor.IsWithinQuery = CommonConversions.IsLinqDelegateExpression(node); + try { + return await ConvertInnerAsync(); + } finally { + CommonConversions.TriviaConvertingExpressionVisitor.IsWithinQuery = originalIsWithinQuery; + } + + async Task ConvertInnerAsync() + { + var body = await ConvertMethodBodyStatementsAsync(node, node.Statements); + var param = await node.SubOrFunctionHeader.ParameterList.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor); + return await ConvertAsync(node, param, body.ToList()); + } + } + + public async Task ConvertSingleLineLambdaAsync(VBSyntax.SingleLineLambdaExpressionSyntax node) + { + var originalIsWithinQuery = CommonConversions.TriviaConvertingExpressionVisitor.IsWithinQuery; + CommonConversions.TriviaConvertingExpressionVisitor.IsWithinQuery = CommonConversions.IsLinqDelegateExpression(node); + try { + return await ConvertInnerAsync(); + } finally { + CommonConversions.TriviaConvertingExpressionVisitor.IsWithinQuery = originalIsWithinQuery; + } + + async Task ConvertInnerAsync() + { + IReadOnlyCollection convertedStatements; + if (node.Body is VBasic.Syntax.StatementSyntax statement) { + convertedStatements = await ConvertMethodBodyStatementsAsync(statement, statement.Yield().ToArray()); + } else { + var csNode = await node.Body.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor); + convertedStatements = new[] { SyntaxFactory.ExpressionStatement(csNode) }; + } + + var param = await node.SubOrFunctionHeader.ParameterList.AcceptAsync(CommonConversions.TriviaConvertingExpressionVisitor); + return await ConvertAsync(node, param, convertedStatements); + } + } + public async Task ConvertAsync(VBSyntax.LambdaExpressionSyntax vbNode, ParameterListSyntax param, IReadOnlyCollection convertedStatements) { @@ -132,4 +183,51 @@ private LocalFunctionStatementSyntax CreateLocalFunction(IAnonymousFunctionOpera return localFunc; } + + public async Task> ConvertMethodBodyStatementsAsync(VBasic.VisualBasicSyntaxNode node, IReadOnlyCollection statements, bool isIterator = false, IdentifierNameSyntax csReturnVariable = null) + { + + var innerMethodBodyVisitor = await MethodBodyExecutableStatementVisitor.CreateAsync(node, _semanticModel, CommonConversions.TriviaConvertingExpressionVisitor, CommonConversions, CommonConversions.VisualBasicEqualityComparison, _withBlockLhs, _extraUsingDirectives, _typeContext, isIterator, csReturnVariable); + return await GetWithConvertedGotosOrNull(statements) ?? await ConvertStatements(statements); + + async Task> ConvertStatements(IEnumerable readOnlyCollection) + { + return (await readOnlyCollection.SelectManyAsync(async s => (IEnumerable)await s.Accept(innerMethodBodyVisitor.CommentConvertingVisitor))).ToList(); + } + + async Task> GetWithConvertedGotosOrNull(IReadOnlyCollection statements) + { + var onlyIdentifierLabel = statements.OnlyOrDefault(s => s.IsKind(VBasic.SyntaxKind.LabelStatement)); + var onlyOnErrorGotoStatement = statements.OnlyOrDefault(s => s.IsKind(VBasic.SyntaxKind.OnErrorGoToLabelStatement)); + + // See https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/on-error-statement + if (onlyIdentifierLabel != null && onlyOnErrorGotoStatement != null) { + var statementsList = statements.ToList(); + var onlyIdentifierLabelIndex = statementsList.IndexOf(onlyIdentifierLabel); + var onlyOnErrorGotoStatementIndex = statementsList.IndexOf(onlyOnErrorGotoStatement); + + // Even this very simple case can generate compile errors if the error handling uses statements declared in the scope of the try block + // For now, the user will have to fix these manually, in future it'd be possible to hoist any used declarations out of the try block + if (onlyOnErrorGotoStatementIndex < onlyIdentifierLabelIndex) { + var beforeStatements = await ConvertStatements(statements.Take(onlyOnErrorGotoStatementIndex)); + var tryBlockStatements = await ConvertStatements(statements.Take(onlyIdentifierLabelIndex).Skip(onlyOnErrorGotoStatementIndex + 1)); + var tryBlock = SyntaxFactory.Block(tryBlockStatements); + var afterStatements = await ConvertStatements(statements.Skip(onlyIdentifierLabelIndex + 1)); + + var catchClauseSyntax = SyntaxFactory.CatchClause(); + + // Default to putting the statements after the catch block in case logic falls through, but if the last statement is a return, put them inside the catch block for neatness. + if (tryBlockStatements.LastOrDefault().IsKind(SyntaxKind.ReturnStatement)) { + catchClauseSyntax = catchClauseSyntax.WithBlock(SyntaxFactory.Block(afterStatements)); + afterStatements = new List(); + } + + var tryStatement = SyntaxFactory.TryStatement(SyntaxFactory.SingletonList(catchClauseSyntax)).WithBlock(tryBlock); + return beforeStatements.Append(tryStatement).Concat(afterStatements).ToList(); + } + } + + return null; + } + } } \ No newline at end of file diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index c5978cb7..cd67d465 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -210,7 +210,7 @@ public override async Task> VisitAssignmentStatement var lhs = await node.Left.AcceptAsync(_expressionVisitor); var lOperation = _semanticModel.GetOperation(node.Left); - //Already dealt with by call to the same method in VisitInvocationExpression + //Already dealt with by call to the same method in ConvertInvocationExpression var (parameterizedPropertyAccessMethod, _) = await CommonConversions.GetParameterizedPropertyAccessMethodAsync(lOperation); if (parameterizedPropertyAccessMethod != null) return SingleStatement(lhs); var rhs = await node.Right.AcceptAsync(_expressionVisitor); diff --git a/CodeConverter/CSharp/NameExpressionNodeVisitor.cs b/CodeConverter/CSharp/NameExpressionNodeVisitor.cs new file mode 100644 index 00000000..b32a62cc --- /dev/null +++ b/CodeConverter/CSharp/NameExpressionNodeVisitor.cs @@ -0,0 +1,719 @@ +using System.Data; +using System.Globalization; +using ICSharpCode.CodeConverter.CSharp.Replacements; +using ICSharpCode.CodeConverter.Util.FromRoslyn; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.VisualBasic.CompilerServices; +using static ICSharpCode.CodeConverter.CSharp.SemanticModelExtensions; + +namespace ICSharpCode.CodeConverter.CSharp; + + +internal class NameExpressionNodeVisitor +{ + private readonly SemanticModel _semanticModel; + private readonly HashSet _generatedNames; + private readonly ITypeContext _typeContext; + private readonly HashSet _extraUsingDirectives; + private readonly Dictionary> _tempNameForAnonymousScope; + private readonly Stack _withBlockLhs; + private readonly ArgumentConverter _argumentConverter; + + public CommonConversions CommonConversions { get; } + public CommentConvertingVisitorWrapper TriviaConvertingExpressionVisitor { get; } + + public NameExpressionNodeVisitor(SemanticModel semanticModel, HashSet generatedNames, ITypeContext typeContext, HashSet extraUsingDirectives, + Dictionary> tempNameForAnonymousScope, Stack withBlockLhs, CommonConversions commonConversions, + ArgumentConverter argumentConverter, + CommentConvertingVisitorWrapper triviaConvertingExpressionVisitor) + { + _semanticModel = semanticModel; + _generatedNames = generatedNames; + _typeContext = typeContext; + _extraUsingDirectives = extraUsingDirectives; + _tempNameForAnonymousScope = tempNameForAnonymousScope; + _withBlockLhs = withBlockLhs; + _argumentConverter = argumentConverter; + CommonConversions = commonConversions; + TriviaConvertingExpressionVisitor = triviaConvertingExpressionVisitor; + } + + public async Task ConvertMemberAccessExpressionAsync(VBasic.Syntax.MemberAccessExpressionSyntax node) + { + var nodeSymbol = _semanticModel.GetSymbolInfoInDocument(node.Name); + + if (!node.IsParentKind(VBasic.SyntaxKind.InvocationExpression) && + SimpleMethodReplacement.TryGet(nodeSymbol, out var methodReplacement) && + methodReplacement.ReplaceIfMatches(nodeSymbol, Array.Empty(), node.IsParentKind(VBasic.SyntaxKind.AddressOfExpression)) is { } replacement) { + return replacement; + } + + var simpleNameSyntax = await node.Name.AcceptAsync(TriviaConvertingExpressionVisitor); + + var isDefaultProperty = nodeSymbol is IPropertySymbol p && VBasic.VisualBasicExtensions.IsDefault(p); + ExpressionSyntax left = null; + if (node.Expression is VBasic.Syntax.MyClassExpressionSyntax && nodeSymbol != null) { + if (nodeSymbol.IsStatic) { + var typeInfo = _semanticModel.GetTypeInfo(node.Expression); + left = CommonConversions.GetTypeSyntax(typeInfo.Type); + } else { + left = SyntaxFactory.ThisExpression(); + if (nodeSymbol.IsVirtual && !nodeSymbol.IsAbstract || + nodeSymbol.IsImplicitlyDeclared && nodeSymbol is IFieldSymbol { AssociatedSymbol: IPropertySymbol { IsVirtual: true, IsAbstract: false } }) { + simpleNameSyntax = + ValidSyntaxFactory.IdentifierName( + $"MyClass{CommonConversions.ConvertIdentifier(node.Name.Identifier).ValueText}"); + } + } + } + if (left == null && nodeSymbol?.IsStatic == true) { + var type = nodeSymbol.ContainingType; + if (type != null) { + left = CommonConversions.GetTypeSyntax(type); + } + } + if (left == null) { + left = await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); + if (left != null && _semanticModel.GetSymbolInfo(node) is { CandidateReason: CandidateReason.LateBound, CandidateSymbols.Length: 0 } + && _semanticModel.GetSymbolInfo(node.Expression).Symbol is { Kind: var expressionSymbolKind } + && expressionSymbolKind != SymbolKind.ErrorType + && _semanticModel.GetOperation(node) is IDynamicMemberReferenceOperation) { + left = SyntaxFactory.ParenthesizedExpression(SyntaxFactory.CastExpression(SyntaxFactory.ParseTypeName("dynamic"), left)); + } + } + if (left == null) { + if (IsSubPartOfConditionalAccess(node)) { + return isDefaultProperty ? SyntaxFactory.ElementBindingExpression() + : await AdjustForImplicitInvocationAsync(node, SyntaxFactory.MemberBindingExpression(simpleNameSyntax)); + } else if (node.IsParentKind(Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.NamedFieldInitializer)) { + return ValidSyntaxFactory.IdentifierName(_tempNameForAnonymousScope[node.Name.Identifier.Text].Peek().TempName); + } + left = _withBlockLhs.Peek(); + } + + if (node.IsKind(VBasic.SyntaxKind.DictionaryAccessExpression)) { + var args = SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(CommonConversions.Literal(node.Name.Identifier.ValueText))); + var bracketedArgumentListSyntax = SyntaxFactory.BracketedArgumentList(args); + return SyntaxFactory.ElementAccessExpression(left, bracketedArgumentListSyntax); + } + + if (node.Expression.IsKind(VBasic.SyntaxKind.GlobalName)) { + return SyntaxFactory.AliasQualifiedName((IdentifierNameSyntax)left, simpleNameSyntax); + } + + if (isDefaultProperty && left != null) { + return left; + } + + var memberAccessExpressionSyntax = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, left, simpleNameSyntax); + return await AdjustForImplicitInvocationAsync(node, memberAccessExpressionSyntax); + } + + public async Task ConvertGlobalNameAsync(VBasic.Syntax.GlobalNameSyntax node) + { + return ValidSyntaxFactory.IdentifierName(SyntaxFactory.Token(SyntaxKind.GlobalKeyword)); + } + + public async Task ConvertMeExpressionAsync(VBasic.Syntax.MeExpressionSyntax node) + { + return SyntaxFactory.ThisExpression(); + } + + public async Task ConvertMyBaseExpressionAsync(VBasic.Syntax.MyBaseExpressionSyntax node) + { + return SyntaxFactory.BaseExpression(); + } + + public async Task ConvertGenericNameAsync(VBasic.Syntax.GenericNameSyntax node) + { + var symbol = _semanticModel.GetSymbolInfoInDocument(node); + var genericNameSyntax = await GenericNameAccountingForReducedParametersAsync(node, symbol); + return await AdjustForImplicitInvocationAsync(node, genericNameSyntax); + } + public async Task ConvertQualifiedNameAsync(VBasic.Syntax.QualifiedNameSyntax node) + { + var symbol = _semanticModel.GetSymbolInfoInDocument(node); + if (symbol != null) { + return CommonConversions.GetTypeSyntax(symbol.GetSymbolType()); + } + var lhsSyntax = await node.Left.AcceptAsync(TriviaConvertingExpressionVisitor); + var rhsSyntax = await node.Right.AcceptAsync(TriviaConvertingExpressionVisitor); + + VBasic.Syntax.NameSyntax topLevelName = node; + while (topLevelName.Parent is VBasic.Syntax.NameSyntax parentName) { + topLevelName = parentName; + } + var partOfNamespaceDeclaration = topLevelName.Parent.IsKind(VBasic.SyntaxKind.NamespaceStatement); + var leftIsGlobal = node.Left.IsKind(VBasic.SyntaxKind.GlobalName); + ExpressionSyntax qualifiedName; + if (partOfNamespaceDeclaration || !(lhsSyntax is SimpleNameSyntax sns)) { + if (leftIsGlobal) return rhsSyntax; + qualifiedName = lhsSyntax; + } else { + qualifiedName = QualifyNode(node.Left, sns); + } + + return leftIsGlobal ? SyntaxFactory.AliasQualifiedName((IdentifierNameSyntax)lhsSyntax, rhsSyntax) : + SyntaxFactory.QualifiedName((NameSyntax)qualifiedName, rhsSyntax); + } + + /// PERF: This is a hot code path, try to avoid using things like GetOperation except where needed. + public async Task ConvertIdentifierNameAsync(VBasic.Syntax.IdentifierNameSyntax node) + { + var identifier = SyntaxFactory.IdentifierName(CommonConversions.ConvertIdentifier(node.Identifier, node.GetAncestor() != null)); + + bool requiresQualification = !node.Parent.IsKind(VBasic.SyntaxKind.SimpleMemberAccessExpression, VBasic.SyntaxKind.QualifiedName, VBasic.SyntaxKind.NameColonEquals, VBasic.SyntaxKind.ImportsStatement, VBasic.SyntaxKind.NamespaceStatement, VBasic.SyntaxKind.NamedFieldInitializer) || + node.Parent is VBSyntax.NamedFieldInitializerSyntax nfs && nfs.Expression == node || + node.Parent is VBasic.Syntax.MemberAccessExpressionSyntax maes && maes.Expression == node; + var qualifiedIdentifier = requiresQualification + ? QualifyNode(node, identifier) : identifier; + + var sym = _semanticModel.GetSymbolInfoInDocument(node); + if (sym is ILocalSymbol) { + if (sym.IsStatic && sym.ContainingSymbol is IMethodSymbol m && m.AssociatedSymbol is IPropertySymbol) { + qualifiedIdentifier = qualifiedIdentifier.WithParentPropertyAccessorKind(m.MethodKind); + } + + var vbMethodBlock = node.Ancestors().OfType().FirstOrDefault(); + if (vbMethodBlock != null && + vbMethodBlock.MustReturn() && + !node.Parent.IsKind(VBasic.SyntaxKind.NameOfExpression) && + node.Identifier.ValueText.Equals(CommonConversions.GetMethodBlockBaseIdentifierForImplicitReturn(vbMethodBlock).ValueText, StringComparison.OrdinalIgnoreCase)) { + var retVar = CommonConversions.GetRetVariableNameOrNull(vbMethodBlock); + if (retVar != null) { + return retVar; + } + } + } + + return await AdjustForImplicitInvocationAsync(node, qualifiedIdentifier); + } + + + + + public async Task ConvertInvocationExpressionAsync( + VBasic.Syntax.InvocationExpressionSyntax node) + { + var invocationSymbol = _semanticModel.GetSymbolInfo(node).ExtractBestMatch(); + var methodInvocationSymbol = invocationSymbol as IMethodSymbol; + var withinLocalFunction = methodInvocationSymbol != null && RequiresLocalFunction(node, methodInvocationSymbol); + if (withinLocalFunction) { + _typeContext.PerScopeState.PushScope(); + } + try { + + if (node.Expression is null) { + var convertArgumentListOrEmptyAsync = await _argumentConverter.ConvertArgumentsAsync(node.ArgumentList); + return SyntaxFactory.ElementBindingExpression(SyntaxFactory.BracketedArgumentList(SyntaxFactory.SeparatedList(convertArgumentListOrEmptyAsync))); + } + + var convertedInvocation = await ConvertOrReplaceInvocationAsync(node, invocationSymbol); + if (withinLocalFunction) { + return await HoistAndCallLocalFunctionAsync(node, methodInvocationSymbol, (ExpressionSyntax)convertedInvocation); + } + return convertedInvocation; + } finally { + if (withinLocalFunction) { + _typeContext.PerScopeState.PopExpressionScope(); + } + } + } + + private async Task ConvertOrReplaceInvocationAsync(VBSyntax.InvocationExpressionSyntax node, ISymbol invocationSymbol) + { + var expressionSymbol = _semanticModel.GetSymbolInfo(node.Expression).ExtractBestMatch(); + if ((await SubstituteVisualBasicMethodOrNullAsync(node, expressionSymbol) ?? + await WithRemovedRedundantConversionOrNullAsync(node, expressionSymbol)) is { } csEquivalent) { + return csEquivalent; + } + + if (invocationSymbol?.Name is "op_Implicit" or "op_Explicit") { + var vbExpr = node.ArgumentList.Arguments.Single().GetExpression(); + var csExpr = await vbExpr.AcceptAsync(TriviaConvertingExpressionVisitor); + return CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbExpr, csExpr, true, true, false, forceTargetType: invocationSymbol.GetReturnType()); + } + + return await ConvertInvocationAsync(node, invocationSymbol, expressionSymbol); + } + + private async Task WithRemovedRedundantConversionOrNullAsync(VBSyntax.InvocationExpressionSyntax conversionNode, ISymbol invocationSymbol) + { + if (invocationSymbol?.ContainingNamespace.MetadataName != nameof(Microsoft.VisualBasic) || + invocationSymbol.ContainingType.Name != nameof(Conversions) || + !invocationSymbol.Name.StartsWith("To", StringComparison.InvariantCulture) || + conversionNode.ArgumentList.Arguments.Count != 1) { + return null; + } + + var conversionArg = conversionNode.ArgumentList.Arguments.First().GetExpression(); + VBSyntax.ExpressionSyntax coercedConversionNode = conversionNode; + return await CommonConversions.WithRemovedRedundantConversionOrNullAsync(coercedConversionNode, conversionArg); + } + + private async Task ConvertInvocationAsync(VBSyntax.InvocationExpressionSyntax node, ISymbol invocationSymbol, ISymbol expressionSymbol) + { + var expressionType = _semanticModel.GetTypeInfo(node.Expression).Type; + var expressionReturnType = expressionSymbol?.GetReturnType() ?? expressionType; + var operation = _semanticModel.GetOperation(node); + + var expr = await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); + if (await TryConvertParameterizedPropertyAsync(operation, node, expr, node.ArgumentList) is { } invocation) { + return invocation; + } + //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 + (var convertedExpression, bool shouldBeElementAccess) = await ConvertInvocationSubExpressionAsync(node, operation, expressionSymbol, expressionReturnType, expr); + if (shouldBeElementAccess) { + return await CreateElementAccessAsync(node, convertedExpression); + } + + if (expressionSymbol != null && expressionSymbol.IsKind(SymbolKind.Property) && + invocationSymbol != null && invocationSymbol.GetParameters().Length == 0 && node.ArgumentList.Arguments.Count == 0) { + return convertedExpression; //Parameterless property access + } + + var convertedArgumentList = await _argumentConverter.ConvertArgumentListOrEmptyAsync(node, node.ArgumentList); + + if (IsElementAtOrDefaultInvocation(invocationSymbol, expressionSymbol)) { + convertedExpression = GetElementAtOrDefaultExpression(expressionType, convertedExpression); + } + + if (invocationSymbol.IsReducedExtension() && invocationSymbol is IMethodSymbol { ReducedFrom: { Parameters: var parameters } } && + !parameters.FirstOrDefault().ValidCSharpExtensionMethodParameter() && + node.Expression is VBSyntax.MemberAccessExpressionSyntax maes) { + var thisArgExpression = await maes.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); + var thisArg = SyntaxFactory.Argument(thisArgExpression).WithRefKindKeyword(CommonConversions.GetRefToken(RefKind.Ref)); + convertedArgumentList = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(convertedArgumentList.Arguments.Prepend(thisArg))); + var containingType = (ExpressionSyntax)CommonConversions.CsSyntaxGenerator.TypeExpression(invocationSymbol.ContainingType); + convertedExpression = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, containingType, + ValidSyntaxFactory.IdentifierName((invocationSymbol.Name))); + } + + if (invocationSymbol is IMethodSymbol m && convertedExpression is LambdaExpressionSyntax) { + convertedExpression = SyntaxFactory.ObjectCreationExpression(CommonConversions.GetFuncTypeSyntax(expressionType, m), ExpressionSyntaxExtensions.CreateArgList(convertedExpression), null); + } + return SyntaxFactory.InvocationExpression(convertedExpression, convertedArgumentList); + } + + private async Task<(ExpressionSyntax, bool isElementAccess)> ConvertInvocationSubExpressionAsync(VBSyntax.InvocationExpressionSyntax node, + IOperation operation, ISymbol expressionSymbol, ITypeSymbol expressionReturnType, CSharpSyntaxNode expr) + { + var isElementAccess = operation.IsPropertyElementAccess() + || operation.IsArrayElementAccess() + || ProbablyNotAMethodCall(node, expressionSymbol, expressionReturnType); + + var expressionSyntax = (ExpressionSyntax)expr; + + return (expressionSyntax, isElementAccess); + } + + private async Task CreateElementAccessAsync(VBSyntax.InvocationExpressionSyntax node, ExpressionSyntax expression) + { + var args = + await node.ArgumentList.Arguments.AcceptSeparatedListAsync(TriviaConvertingExpressionVisitor); + var bracketedArgumentListSyntax = SyntaxFactory.BracketedArgumentList(args); + if (expression is ElementBindingExpressionSyntax binding && + !binding.ArgumentList.Arguments.Any()) { + // Special case where structure changes due to conditional access (See ConvertMemberAccessExpression) + return binding.WithArgumentList(bracketedArgumentListSyntax); + } + + return SyntaxFactory.ElementAccessExpression(expression, bracketedArgumentListSyntax); + } + + private static bool IsElementAtOrDefaultInvocation(ISymbol invocationSymbol, ISymbol expressionSymbol) + { + return (expressionSymbol != null + && (invocationSymbol?.Name == nameof(Enumerable.ElementAtOrDefault) + && !expressionSymbol.Equals(invocationSymbol, SymbolEqualityComparer.IncludeNullability))); + } + + private ExpressionSyntax GetElementAtOrDefaultExpression(ISymbol expressionType, + ExpressionSyntax expression) + { + _extraUsingDirectives.Add(nameof(System) + "." + nameof(System.Linq)); + + // The Vb compiler interprets Datatable indexing as a AsEnumerable().ElementAtOrDefault() operation. + if (expressionType.Name == nameof(DataTable)) { + _extraUsingDirectives.Add(nameof(System) + "." + nameof(System.Data)); + + expression = SyntaxFactory.InvocationExpression(SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, expression, + ValidSyntaxFactory.IdentifierName(nameof(DataTableExtensions.AsEnumerable)))); + } + + var newExpression = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + expression, ValidSyntaxFactory.IdentifierName(nameof(Enumerable.ElementAtOrDefault))); + + return newExpression; + } + + private async Task TryConvertParameterizedPropertyAsync(IOperation operation, + SyntaxNode node, CSharpSyntaxNode identifier, + VBSyntax.ArgumentListSyntax optionalArgumentList = null) + { + var (overrideIdentifier, extraArg) = + await CommonConversions.GetParameterizedPropertyAccessMethodAsync(operation); + if (overrideIdentifier != null) { + var expr = identifier; + var idToken = expr.DescendantTokens().Last(t => t.IsKind(SyntaxKind.IdentifierToken)); + expr = ReplaceRightmostIdentifierText(expr, idToken, overrideIdentifier); + + var args = await _argumentConverter.ConvertArgumentListOrEmptyAsync(node, optionalArgumentList); + if (extraArg != null) { + var extraArgSyntax = SyntaxFactory.Argument(extraArg); + var propertySymbol = ((IPropertyReferenceOperation)operation).Property; + var forceNamedExtraArg = args.Arguments.Count != propertySymbol.GetParameters().Length || + args.Arguments.Any(t => t.NameColon != null); + + if (forceNamedExtraArg) { + extraArgSyntax = extraArgSyntax.WithNameColon(SyntaxFactory.NameColon("value")); + } + + args = args.WithArguments(args.Arguments.Add(extraArgSyntax)); + } + + return SyntaxFactory.InvocationExpression((ExpressionSyntax)expr, args); + } + + return null; + } + + + /// + /// 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 HoistAndCallLocalFunctionAsync(VBSyntax.InvocationExpressionSyntax invocation, IMethodSymbol invocationSymbol, ExpressionSyntax csExpression) + { + const string retVariableName = "ret"; + var localFuncName = $"local{invocationSymbol.Name}"; + + var callAndStoreResult = CommonConversions.CreateLocalVariableDeclarationAndAssignment(retVariableName, csExpression); + + var statements = await _typeContext.PerScopeState.CreateLocalsAsync(invocation, new[] { callAndStoreResult }, _generatedNames, _semanticModel); + + var block = SyntaxFactory.Block( + statements.Concat(SyntaxFactory.ReturnStatement(ValidSyntaxFactory.IdentifierName(retVariableName)).Yield()) + ); + var returnType = CommonConversions.GetTypeSyntax(invocationSymbol.ReturnType); + + //any argument that's a ByRef parameter of the parent method needs to be passed as a ref parameter to the local function (to avoid error CS1628) + var refParametersOfParent = GetRefParameters(invocation.ArgumentList); + var (args, @params) = CreateArgumentsAndParametersLists(refParametersOfParent); + + var localFunc = _typeContext.PerScopeState.Hoist(new HoistedFunction(localFuncName, returnType, block, SyntaxFactory.ParameterList(@params))); + return SyntaxFactory.InvocationExpression(localFunc.TempIdentifier, SyntaxFactory.ArgumentList(args)); + + List GetRefParameters(VBSyntax.ArgumentListSyntax argumentList) + { + var result = new List(); + if (argumentList is null) return result; + + foreach (var arg in argumentList.Arguments) { + if (_semanticModel.GetSymbolInfo(arg.GetExpression()).Symbol is not IParameterSymbol p) continue; + if (p.RefKind != RefKind.None) { + result.Add(p); + } + } + + return result; + } + + (SeparatedSyntaxList, SeparatedSyntaxList) CreateArgumentsAndParametersLists(List parameterSymbols) + { + var arguments = new List(); + var parameters = new List(); + foreach (var p in parameterSymbols) { + var arg = (ArgumentSyntax)CommonConversions.CsSyntaxGenerator.Argument(p.RefKind, SyntaxFactory.IdentifierName(p.Name)); + arguments.Add(arg); + var par = (ParameterSyntax)CommonConversions.CsSyntaxGenerator.ParameterDeclaration(p); + parameters.Add(par); + } + return (SyntaxFactory.SeparatedList(arguments), SyntaxFactory.SeparatedList(parameters)); + } + } + + private bool RequiresLocalFunction(VBSyntax.InvocationExpressionSyntax invocation, IMethodSymbol invocationSymbol) + { + if (invocation.ArgumentList == null) return false; + var definitelyExecutedAfterPrevious = DefinitelyExecutedAfterPreviousStatement(invocation); + var nextStatementDefinitelyExecuted = NextStatementDefinitelyExecutedAfter(invocation); + if (definitelyExecutedAfterPrevious && nextStatementDefinitelyExecuted) return false; + var possibleInline = definitelyExecutedAfterPrevious ? RefConversion.PreAssigment : RefConversion.Inline; + return invocation.ArgumentList.Arguments.Any(a => RequiresLocalFunction(possibleInline, invocation, invocationSymbol, a)); + + bool RequiresLocalFunction(RefConversion possibleInline, VBSyntax.InvocationExpressionSyntax invocation, IMethodSymbol invocationSymbol, VBSyntax.ArgumentSyntax a) + { + var refConversion = _semanticModel.GetRefConversionType(a, invocation.ArgumentList, invocationSymbol.Parameters, out string _, out _); + if (RefConversion.Inline == refConversion || possibleInline == refConversion) return false; + if (!(a is VBSyntax.SimpleArgumentSyntax sas)) return false; + var argExpression = sas.Expression.SkipIntoParens(); + if (argExpression is VBSyntax.InstanceExpressionSyntax) return false; + return !_semanticModel.GetConstantValue(argExpression).HasValue; + } + } + + /// + /// Conservative version of _semanticModel.AnalyzeControlFlow(invocation).ExitPoints to account for exceptions + /// + private bool DefinitelyExecutedAfterPreviousStatement(VBSyntax.InvocationExpressionSyntax invocation) + { + SyntaxNode parent = invocation; + while (true) { + parent = parent.Parent; + switch (parent) { + case VBSyntax.ParenthesizedExpressionSyntax _: + continue; + case VBSyntax.BinaryExpressionSyntax binaryExpression: + if (binaryExpression.Left == invocation) continue; + else return false; + case VBSyntax.ArgumentSyntax argumentSyntax: + // Being the leftmost invocation of an unqualified method call ensures no other code is executed. Could add other cases here, such as a method call on a local variable name, or "this.". A method call on a property is not acceptable. + if (argumentSyntax.Parent.Parent is VBSyntax.InvocationExpressionSyntax parentInvocation && parentInvocation.ArgumentList.Arguments.First() == argumentSyntax && FirstArgDefinitelyEvaluated(parentInvocation)) continue; + else return false; + case VBSyntax.ElseIfStatementSyntax _: + case VBSyntax.ExpressionSyntax _: + return false; + case VBSyntax.StatementSyntax _: + return true; + } + } + } + + private bool FirstArgDefinitelyEvaluated(VBSyntax.InvocationExpressionSyntax parentInvocation) => + parentInvocation.Expression.SkipIntoParens() switch { + VBSyntax.IdentifierNameSyntax _ => true, + VBSyntax.MemberAccessExpressionSyntax maes => maes.Expression is { } exp && !MayThrow(exp), + _ => true + }; + + /// + /// Safe overapproximation of whether an expression may throw. + /// + private bool MayThrow(VBSyntax.ExpressionSyntax expression) + { + expression = expression.SkipIntoParens(); + if (expression is VBSyntax.InstanceExpressionSyntax) return false; + var symbol = _semanticModel.GetSymbolInfo(expression).Symbol; + return !symbol.IsKind(SymbolKind.Local) && !symbol.IsKind(SymbolKind.Field); + } + + /// + /// Conservative version of _semanticModel.AnalyzeControlFlow(invocation).ExitPoints to account for exceptions + /// + private static bool NextStatementDefinitelyExecutedAfter(VBSyntax.InvocationExpressionSyntax invocation) + { + SyntaxNode parent = invocation; + while (true) { + parent = parent.Parent; + switch (parent) { + case VBSyntax.ParenthesizedExpressionSyntax _: + continue; + case VBSyntax.BinaryExpressionSyntax binaryExpression: + if (binaryExpression.Right == invocation) continue; + else return false; + case VBSyntax.IfStatementSyntax _: + case VBSyntax.ElseIfStatementSyntax _: + case VBSyntax.SingleLineIfStatementSyntax _: + return false; + case VBSyntax.ExpressionSyntax _: + case VBSyntax.StatementSyntax _: + return true; + } + } + } + + + /// + /// The pre-expansion phase should handle this for compiling nodes. + /// This is mainly targeted at dealing with missing semantic info. + /// + /// + private ExpressionSyntax QualifyNode(SyntaxNode node, SimpleNameSyntax left) + { + var nodeSymbolInfo = _semanticModel.GetSymbolInfoInDocument(node); + if (left != null && + nodeSymbolInfo != null && + nodeSymbolInfo.MatchesKind(SymbolKind.TypeParameter) == false && + nodeSymbolInfo.ContainingSymbol is INamespaceOrTypeSymbol containingSymbol && + !ContextImplicitlyQualfiesSymbol(node, containingSymbol)) { + + if (containingSymbol is ITypeSymbol containingTypeSymbol && + !nodeSymbolInfo.IsConstructor() /* Constructors are implicitly qualified with their type */) { + // Qualify with a type to handle VB's type promotion https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/declared-elements/type-promotion + var qualification = + CommonConversions.GetTypeSyntax(containingTypeSymbol); + return Qualify(qualification.ToString(), left); + } + + if (nodeSymbolInfo.IsNamespace()) { + // Turn partial namespace qualification into full namespace qualification + var qualification = + containingSymbol.ToCSharpDisplayString(); + return Qualify(qualification, left); + } + } + + return left; + } + + private async Task AdjustForImplicitInvocationAsync(SyntaxNode node, ExpressionSyntax qualifiedIdentifier) + { + //PERF: Avoid calling expensive GetOperation when it's easy + bool nonExecutableNode = node.IsParentKind(VBasic.SyntaxKind.QualifiedName); + if (nonExecutableNode || _semanticModel.SyntaxTree != node.SyntaxTree) return qualifiedIdentifier; + + if (await TryConvertParameterizedPropertyAsync(_semanticModel.GetOperation(node), node, qualifiedIdentifier) is { } + invocation) { + return invocation; + } + + return AddEmptyArgumentListIfImplicit(node, qualifiedIdentifier); + } + + + /// + /// Adjusts for Visual Basic's omission of type arguments that can be inferred in reduced generic method invocations + /// The upfront WithExpandedRootAsync pass should ensure this only happens on broken syntax trees. + /// In those cases, just comment the errant information. It would only cause a compiling change in behaviour if it can be inferred, was not set to the inferred value, and was reflected upon within the method body + /// + private async Task GenericNameAccountingForReducedParametersAsync(VBSyntax.GenericNameSyntax node, ISymbol symbol) + { + SyntaxToken convertedIdentifier = CommonConversions.ConvertIdentifier(node.Identifier); + if (symbol is IMethodSymbol vbMethod && vbMethod.IsReducedTypeParameterMethod()) { + var allTypeArgs = GetOrNullAllTypeArgsIncludingInferred(vbMethod); + if (allTypeArgs != null) { + return (SimpleNameSyntax)CommonConversions.CsSyntaxGenerator.GenericName(convertedIdentifier.Text, allTypeArgs); + } + var commentedText = "/* " + (await ConvertTypeArgumentListAsync(node)).ToFullString() + " */"; + var error = SyntaxFactory.ParseLeadingTrivia($"#error Conversion error: Could not convert all type parameters, so they've been commented out. Inferred type may be different{Environment.NewLine}"); + var partialConversion = SyntaxFactory.Comment(commentedText); + return ValidSyntaxFactory.IdentifierName(convertedIdentifier).WithPrependedLeadingTrivia(error).WithTrailingTrivia(partialConversion); + } + + return SyntaxFactory.GenericName(convertedIdentifier, await ConvertTypeArgumentListAsync(node)); + } + + /// TODO: Would be more robust to use + private ITypeSymbol[] GetOrNullAllTypeArgsIncludingInferred(IMethodSymbol vbMethod) + { + if (!(CommonConversions.GetCsOriginalSymbolOrNull(vbMethod) is IMethodSymbol + csSymbolWithInferredTypeParametersSet)) return null; + var argSubstitutions = vbMethod.TypeParameters + .Zip(vbMethod.TypeArguments, (parameter, arg) => (parameter, arg)) + .ToDictionary(x => x.parameter.Name, x => x.arg); + var allTypeArgs = csSymbolWithInferredTypeParametersSet.GetTypeArguments() + .Select(a => a.Kind == SymbolKind.TypeParameter && argSubstitutions.TryGetValue(a.Name, out var t) ? t : a) + .ToArray(); + return allTypeArgs; + } + + private async Task ConvertTypeArgumentListAsync(VBSyntax.GenericNameSyntax node) + { + return await node.TypeArgumentList.AcceptAsync(TriviaConvertingExpressionVisitor); + } + + private CSharpSyntaxNode AddEmptyArgumentListIfImplicit(SyntaxNode node, ExpressionSyntax id) + { + if (_semanticModel.SyntaxTree != node.SyntaxTree) return id; + return _semanticModel.GetOperation(node) switch { + IInvocationOperation invocation => SyntaxFactory.InvocationExpression(id, _argumentConverter.CreateArgList(invocation.TargetMethod)), + IPropertyReferenceOperation propReference when propReference.Property.Parameters.Any() => SyntaxFactory.InvocationExpression(id, _argumentConverter.CreateArgList(propReference.Property)), + _ => id + }; + } + + private bool ContextImplicitlyQualfiesSymbol(SyntaxNode syntaxNodeContext, INamespaceOrTypeSymbol symbolToCheck) + { + return symbolToCheck is INamespaceSymbol ns && ns.IsGlobalNamespace || + EnclosingTypeImplicitlyQualifiesSymbol(syntaxNodeContext, symbolToCheck); + } + + private async Task SubstituteVisualBasicMethodOrNullAsync(VBSyntax.InvocationExpressionSyntax node, ISymbol symbol) + { + ExpressionSyntax cSharpSyntaxNode = null; + if (IsVisualBasicChrMethod(symbol)) { + var vbArg = node.ArgumentList.Arguments.Single().GetExpression(); + var constValue = _semanticModel.GetConstantValue(vbArg); + if (IsCultureInvariant(constValue)) { + var csArg = await vbArg.AcceptAsync(TriviaConvertingExpressionVisitor); + cSharpSyntaxNode = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node, csArg, true, true, true, forceTargetType: _semanticModel.GetTypeInfo(node).Type); + } + } + + if (SimpleMethodReplacement.TryGet(symbol, out var methodReplacement) && + methodReplacement.ReplaceIfMatches(symbol, await _argumentConverter.ConvertArgumentsAsync(node.ArgumentList), false) is { } csExpression) { + cSharpSyntaxNode = csExpression; + } + + return cSharpSyntaxNode; + } + + + private static bool IsVisualBasicChrMethod(ISymbol symbol) => + symbol is not null + && symbol.ContainingNamespace.MetadataName == nameof(Microsoft.VisualBasic) + && (symbol.Name == "ChrW" || symbol.Name == "Chr"); + + /// + /// https://github.com/icsharpcode/CodeConverter/issues/745 + /// + private static bool IsCultureInvariant(Optional constValue) => + constValue.HasValue && Convert.ToUInt64(constValue.Value, CultureInfo.InvariantCulture) <= 127; + + private bool EnclosingTypeImplicitlyQualifiesSymbol(SyntaxNode syntaxNodeContext, INamespaceOrTypeSymbol symbolToCheck) + { + ISymbol typeContext = syntaxNodeContext.GetEnclosingDeclaredTypeSymbol(_semanticModel); + var implicitCsQualifications = ((ITypeSymbol)typeContext).GetBaseTypesAndThis() + .Concat(typeContext.FollowProperty(n => n.ContainingSymbol)) + .ToList(); + + return implicitCsQualifications.Contains(symbolToCheck); + } + + private static QualifiedNameSyntax Qualify(string qualification, ExpressionSyntax toBeQualified) + { + return SyntaxFactory.QualifiedName( + SyntaxFactory.ParseName(qualification), + (SimpleNameSyntax)toBeQualified); + } + + private static bool IsSubPartOfConditionalAccess(VBasic.Syntax.MemberAccessExpressionSyntax node) + { + var firstPossiblyConditionalAncestor = node.Parent; + while (firstPossiblyConditionalAncestor != null && + firstPossiblyConditionalAncestor.IsKind(VBasic.SyntaxKind.InvocationExpression, + VBasic.SyntaxKind.SimpleMemberAccessExpression)) { + firstPossiblyConditionalAncestor = firstPossiblyConditionalAncestor.Parent; + } + + return firstPossiblyConditionalAncestor?.IsKind(VBasic.SyntaxKind.ConditionalAccessExpression) == true; + } + + private static CSharpSyntaxNode ReplaceRightmostIdentifierText(CSharpSyntaxNode expr, SyntaxToken idToken, string overrideIdentifier) + { + return expr.ReplaceToken(idToken, SyntaxFactory.Identifier(overrideIdentifier).WithTriviaFrom(idToken).WithAdditionalAnnotations(idToken.GetAnnotations())); + } + + + /// + /// If there's a single numeric arg, let's assume it's an indexer (probably an array). + /// Otherwise, err on the side of a method call. + /// + private bool ProbablyNotAMethodCall(VBasic.Syntax.InvocationExpressionSyntax node, ISymbol symbol, ITypeSymbol symbolReturnType) + { + return !node.IsParentKind(VBasic.SyntaxKind.CallStatement) && !(symbol is IMethodSymbol) && + symbolReturnType.IsErrorType() && node.Expression is VBasic.Syntax.IdentifierNameSyntax && + node.ArgumentList?.Arguments.OnlyOrDefault()?.GetExpression() is { } arg && + _semanticModel.GetTypeInfo(arg).Type.IsNumericType(); + } + +} diff --git a/CodeConverter/CSharp/SemanticModelExtensions.cs b/CodeConverter/CSharp/SemanticModelExtensions.cs index e15b580c..b51f8e9f 100644 --- a/CodeConverter/CSharp/SemanticModelExtensions.cs +++ b/CodeConverter/CSharp/SemanticModelExtensions.cs @@ -1,4 +1,6 @@ -using Microsoft.CodeAnalysis.Operations; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Operations; namespace ICSharpCode.CodeConverter.CSharp; @@ -54,4 +56,108 @@ private static bool IsDefinitelyStatic(this SemanticModel semanticModel, VBSynta var instanceReferenceOperations = semanticModel.GetOperation(e).DescendantsAndSelf().OfType().ToArray(); return !instanceReferenceOperations.Any(x => x.ReferenceKind == InstanceReferenceKind.ContainingTypeInstance); } + + /// The ISymbol if available in this document, otherwise null + /// It's possible to use semanticModel.GetSpeculativeSymbolInfo(...) if you know (or can approximate) the position where the symbol would have been in the original document. + public static TSymbol GetSymbolInfoInDocument(this SemanticModel semanticModel, SyntaxNode node) where TSymbol : class, ISymbol + { + return semanticModel.SyntaxTree == node.SyntaxTree ? semanticModel.GetSymbolInfo(node).ExtractBestMatch() : null; + } + + public static RefConversion GetRefConversionType(this SemanticModel semanticModel, VBSyntax.ArgumentSyntax node, VBSyntax.ArgumentListSyntax argList, 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 { + refKind = RefKind.None; + argName = null; + } + return semanticModel.NeedsVariableForArgument(node, refKind); + } + + public static RefConversion NeedsVariableForArgument(this SemanticModel semanticModel, VBasic.Syntax.ArgumentSyntax node, RefKind refKind) + { + if (refKind == RefKind.None) return RefConversion.Inline; + if (!(node is VBSyntax.SimpleArgumentSyntax sas) || sas is { Expression: VBSyntax.ParenthesizedExpressionSyntax }) return RefConversion.PreAssigment; + var expression = sas.Expression; + + return GetRefConversion(expression); + + RefConversion GetRefConversion(VBSyntax.ExpressionSyntax expression) + { + var symbolInfo = semanticModel.GetSymbolInfoInDocument(expression); + if (symbolInfo is IPropertySymbol { ReturnsByRef: false, ReturnsByRefReadonly: false } propertySymbol) { + // a property in VB.NET code can be ReturnsByRef if it's defined in a C# assembly the VB.NET code references + return propertySymbol.IsReadOnly ? RefConversion.PreAssigment : RefConversion.PreAndPostAssignment; + } else if (symbolInfo is IFieldSymbol { IsConst: true } or ILocalSymbol { IsConst: true }) { + return RefConversion.PreAssigment; + } else if (symbolInfo is IMethodSymbol { ReturnsByRef: false, ReturnsByRefReadonly: false }) { + // a method in VB.NET code can be ReturnsByRef if it's defined in a C# assembly the VB.NET code references + return RefConversion.PreAssigment; + } + + if (DeclaredInUsing(symbolInfo)) return RefConversion.PreAssigment; + + if (expression is VBasic.Syntax.IdentifierNameSyntax || expression is VBSyntax.MemberAccessExpressionSyntax || + IsRefArrayAcces(expression)) { + + var typeInfo = semanticModel.GetTypeInfo(expression); + bool isTypeMismatch = typeInfo.Type == null || !typeInfo.Type.Equals(typeInfo.ConvertedType, SymbolEqualityComparer.IncludeNullability); + + if (isTypeMismatch) { + return RefConversion.PreAndPostAssignment; + } + + return RefConversion.Inline; + } + + return RefConversion.PreAssigment; + } + + bool IsRefArrayAcces(VBSyntax.ExpressionSyntax expression) + { + if (!(expression is VBSyntax.InvocationExpressionSyntax ies)) return false; + var op = semanticModel.GetOperation(ies); + return (op.IsArrayElementAccess() || IsReturnsByRefPropertyElementAccess(op)) + && GetRefConversion(ies.Expression) == RefConversion.Inline; + + static bool IsReturnsByRefPropertyElementAccess(IOperation op) + { + return op.IsPropertyElementAccess() + && op is IPropertyReferenceOperation { Property: { } prop } + && (prop.ReturnsByRef || prop.ReturnsByRefReadonly); + } + } + } + + 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 + /// + public 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 + } } \ No newline at end of file diff --git a/CodeConverter/CSharp/VbSyntaxNodeExtensions.cs b/CodeConverter/CSharp/VbSyntaxNodeExtensions.cs index a5017f5e..c4649984 100644 --- a/CodeConverter/CSharp/VbSyntaxNodeExtensions.cs +++ b/CodeConverter/CSharp/VbSyntaxNodeExtensions.cs @@ -30,4 +30,22 @@ public static bool AlwaysHasBooleanTypeInCSharp(this VBSyntax.ExpressionSyntax v parent is VBSyntax.TernaryConditionalExpressionSyntax ternary && ternary.Condition == vbNode || parent is VBSyntax.WhereClauseSyntax; } + + public static TokenContext GetMemberContext(this VBSyntax.StatementSyntax member) + { + var parentType = member.GetAncestorOrThis(); + var parentTypeKind = parentType?.Kind(); + switch (parentTypeKind) { + case VBasic.SyntaxKind.ModuleBlock: + return TokenContext.MemberInModule; + case VBasic.SyntaxKind.ClassBlock: + return TokenContext.MemberInClass; + case VBasic.SyntaxKind.InterfaceBlock: + return TokenContext.MemberInInterface; + case VBasic.SyntaxKind.StructureBlock: + return TokenContext.MemberInStruct; + default: + throw new ArgumentOutOfRangeException(nameof(member)); + } + } } \ No newline at end of file diff --git a/CodeConverter/CSharp/XmlExpressionConverter.cs b/CodeConverter/CSharp/XmlExpressionConverter.cs new file mode 100644 index 00000000..a906d46b --- /dev/null +++ b/CodeConverter/CSharp/XmlExpressionConverter.cs @@ -0,0 +1,179 @@ +using System.Xml.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ICSharpCode.CodeConverter.CSharp; + + +internal class XmlExpressionConverter +{ + private readonly XmlImportContext _xmlImportContext; + private readonly HashSet _extraUsingDirectives; + + public XmlExpressionConverter(XmlImportContext xmlImportContext, HashSet extraUsingDirectives, CommentConvertingVisitorWrapper triviaConvertingExpressionVisitor) + { + _xmlImportContext = xmlImportContext; + _extraUsingDirectives = extraUsingDirectives; + TriviaConvertingExpressionVisitor = triviaConvertingExpressionVisitor; + } + public async Task ConvertXmlEmbeddedExpressionAsync(VBSyntax.XmlEmbeddedExpressionSyntax node) => + await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor); + + public CommentConvertingVisitorWrapper TriviaConvertingExpressionVisitor { get; } + + public async Task ConvertXmlDocumentAsync(VBasic.Syntax.XmlDocumentSyntax node) + { + _extraUsingDirectives.Add("System.Xml.Linq"); + var arguments = SyntaxFactory.SeparatedList( + (await node.PrecedingMisc.SelectAsync(async misc => SyntaxFactory.Argument(await misc.AcceptAsync(TriviaConvertingExpressionVisitor)))) + .Concat(SyntaxFactory.Argument(await node.Root.AcceptAsync(TriviaConvertingExpressionVisitor)).Yield()) + .Concat(await node.FollowingMisc.SelectAsync(async misc => SyntaxFactory.Argument(await misc.AcceptAsync(TriviaConvertingExpressionVisitor)))) + ); + return ApplyXmlImportsIfNecessary(node, SyntaxFactory.ObjectCreationExpression(ValidSyntaxFactory.IdentifierName("XDocument")).WithArgumentList(SyntaxFactory.ArgumentList(arguments))); + } + + public async Task ConvertXmlElementAsync(VBasic.Syntax.XmlElementSyntax node) + { + _extraUsingDirectives.Add("System.Xml.Linq"); + var arguments = SyntaxFactory.SeparatedList( + SyntaxFactory.Argument(await node.StartTag.Name.AcceptAsync(TriviaConvertingExpressionVisitor)).Yield() + .Concat(await node.StartTag.Attributes.SelectAsync(async attribute => SyntaxFactory.Argument(await attribute.AcceptAsync(TriviaConvertingExpressionVisitor)))) + .Concat(await node.Content.SelectAsync(async content => SyntaxFactory.Argument(await content.AcceptAsync(TriviaConvertingExpressionVisitor)))) + ); + return ApplyXmlImportsIfNecessary(node, SyntaxFactory.ObjectCreationExpression(ValidSyntaxFactory.IdentifierName("XElement")).WithArgumentList(SyntaxFactory.ArgumentList(arguments))); + } + + public async Task ConvertXmlEmptyElementAsync(VBSyntax.XmlEmptyElementSyntax node) + { + _extraUsingDirectives.Add("System.Xml.Linq"); + var arguments = SyntaxFactory.SeparatedList( + SyntaxFactory.Argument(await node.Name.AcceptAsync(TriviaConvertingExpressionVisitor)).Yield() + .Concat(await node.Attributes.SelectAsync(async attribute => SyntaxFactory.Argument(await attribute.AcceptAsync(TriviaConvertingExpressionVisitor)))) + ); + return ApplyXmlImportsIfNecessary(node, SyntaxFactory.ObjectCreationExpression(ValidSyntaxFactory.IdentifierName("XElement")).WithArgumentList(SyntaxFactory.ArgumentList(arguments))); + } + + + public async Task ConvertXmlAttributeAsync(VBasic.Syntax.XmlAttributeSyntax node) + { + var arguments = SyntaxFactory.SeparatedList( + SyntaxFactory.Argument(await node.Name.AcceptAsync(TriviaConvertingExpressionVisitor)).Yield() + .Concat(SyntaxFactory.Argument(await node.Value.AcceptAsync(TriviaConvertingExpressionVisitor)).Yield()) + ); + return SyntaxFactory.ObjectCreationExpression(ValidSyntaxFactory.IdentifierName("XAttribute")).WithArgumentList(SyntaxFactory.ArgumentList(arguments)); + } + + public async Task ConvertXmlStringAsync(VBasic.Syntax.XmlStringSyntax node) => + CommonConversions.Literal(string.Join("", node.TextTokens.Select(b => b.Text))); + + public async Task ConvertXmlTextAsync(VBSyntax.XmlTextSyntax node) => + CommonConversions.Literal(string.Join("", node.TextTokens.Select(b => b.Text))); + + public async Task ConvertXmlCDataSectionAsync(VBSyntax.XmlCDataSectionSyntax node) + { + var xcDataTypeSyntax = SyntaxFactory.ParseTypeName(nameof(XCData)); + var argumentListSyntax = CommonConversions.Literal(string.Join("", node.TextTokens.Select(b => b.Text))).Yield().CreateCsArgList(); + return SyntaxFactory.ObjectCreationExpression(xcDataTypeSyntax).WithArgumentList(argumentListSyntax); + } + + /// + /// https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/xml/accessing-xml + /// + public async Task ConvertXmlMemberAccessExpressionAsync( + VBasic.Syntax.XmlMemberAccessExpressionSyntax node) + { + _extraUsingDirectives.Add("System.Xml.Linq"); + + var xElementMethodName = GetXElementMethodName(node); + + ExpressionSyntax elements = node.Base != null + ? SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + await node.Base.AcceptAsync(TriviaConvertingExpressionVisitor), + ValidSyntaxFactory.IdentifierName(xElementMethodName) + ) + : SyntaxFactory.MemberBindingExpression( + ValidSyntaxFactory.IdentifierName(xElementMethodName) + ); + + return SyntaxFactory.InvocationExpression(elements, + ExpressionSyntaxExtensions.CreateArgList( + await node.Name.AcceptAsync(TriviaConvertingExpressionVisitor)) + ); + } + public Task ConvertXmlBracketedNameAsync(VBSyntax.XmlBracketedNameSyntax node) + { + return node.Name.AcceptAsync(TriviaConvertingExpressionVisitor); + } + + public async Task ConvertXmlNameAsync(VBSyntax.XmlNameSyntax node) + { + if (node.Prefix != null) { + switch (node.Prefix.Name.ValueText) { + case "xml": + case "xmlns": + return SyntaxFactory.BinaryExpression( + SyntaxKind.AddExpression, + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ValidSyntaxFactory.IdentifierName("XNamespace"), + ValidSyntaxFactory.IdentifierName(node.Prefix.Name.ValueText.ToPascalCase()) + ), + SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(node.LocalName.Text)) + ); + default: + return SyntaxFactory.BinaryExpression( + SyntaxKind.AddExpression, + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + XmlImportContext.HelperClassShortIdentifierName, + ValidSyntaxFactory.IdentifierName(node.Prefix.Name.ValueText) + ), + SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(node.LocalName.Text)) + ); + } + } + + if (_xmlImportContext.HasDefaultImport && node.Parent is not VBSyntax.XmlAttributeSyntax) { + return SyntaxFactory.BinaryExpression( + SyntaxKind.AddExpression, + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + XmlImportContext.HelperClassShortIdentifierName, + XmlImportContext.DefaultIdentifierName + ), + SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(node.LocalName.Text)) + ); + } + + return SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(node.LocalName.Text)); + } + + + private CSharpSyntaxNode ApplyXmlImportsIfNecessary(VBSyntax.XmlNodeSyntax vbNode, ObjectCreationExpressionSyntax creation) + { + if (!_xmlImportContext.HasImports || vbNode.Parent is VBSyntax.XmlNodeSyntax) return creation; + return SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, XmlImportContext.HelperClassShortIdentifierName, ValidSyntaxFactory.IdentifierName("Apply")), + SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(creation)))); + } + + + private static string GetXElementMethodName(VBSyntax.XmlMemberAccessExpressionSyntax node) + { + if (node.Token2 == default(SyntaxToken)) { + return "Elements"; + } + + if (node.Token2.Text == "@") { + return "Attributes"; + } + + if (node.Token2.Text == ".") { + return "Descendants"; + } + + throw new NotImplementedException($"Xml member access operator: '{node.Token1}{node.Token2}{node.Token3}'"); + } + +} diff --git a/CodeConverter/Util/ExpressionSyntaxExtensions.cs b/CodeConverter/Util/ExpressionSyntaxExtensions.cs index a1fe8a88..745b9811 100644 --- a/CodeConverter/Util/ExpressionSyntaxExtensions.cs +++ b/CodeConverter/Util/ExpressionSyntaxExtensions.cs @@ -1,3 +1,4 @@ +using ICSharpCode.CodeConverter.CSharp; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -131,4 +132,50 @@ public static ArgumentListSyntax CreateCsArgList(this IEnumerable argExpre return null; } } + + public static CSSyntax.ArgumentListSyntax CreateDelegatingArgList(this CSSyntax.ParameterListSyntax parameterList) + { + var refKinds = parameterList.Parameters.Select(GetSingleModifier).ToArray(); + return parameterList.Parameters.Select(p => ValidSyntaxFactory.IdentifierName(p.Identifier)).CreateCsArgList(refKinds); + } + + private static CS.SyntaxKind? GetSingleModifier(CSSyntax.ParameterSyntax p) + { + var argKinds = new CS.SyntaxKind?[] { CS.SyntaxKind.RefKeyword, CS.SyntaxKind.OutKeyword, CS.SyntaxKind.InKeyword }; + return p.Modifiers.Select(Microsoft.CodeAnalysis.CSharp.CSharpExtensions.Kind) + .Select(k => k) + .FirstOrDefault(argKinds.Contains); + } + + public static CSSyntax.ArrowExpressionClauseSyntax GetDelegatingClause(CSSyntax.ParameterListSyntax parameterList, SyntaxToken csIdentifier, + bool isSetAccessor) + { + if (parameterList != null && isSetAccessor) + throw new InvalidOperationException("Parameterized setters shouldn't have a delegating clause. " + + $"\r\nInvalid arguments: {nameof(isSetAccessor)} = {true}," + + $" {nameof(parameterList)} has {parameterList.Parameters.Count} parameters"); + + var simpleMemberAccess = GetSimpleMemberAccess(csIdentifier); + + var expression = parameterList != null + ? (CSSyntax.ExpressionSyntax)CS.SyntaxFactory.InvocationExpression(simpleMemberAccess, parameterList.CreateDelegatingArgList()) + : simpleMemberAccess; + + var arrowClauseExpression = isSetAccessor + ? CS.SyntaxFactory.AssignmentExpression(CS.SyntaxKind.SimpleAssignmentExpression, simpleMemberAccess, + ValidSyntaxFactory.IdentifierName("value")) + : expression; + + var arrowClause = CS.SyntaxFactory.ArrowExpressionClause(arrowClauseExpression); + return arrowClause; + } + + public static CSSyntax.MemberAccessExpressionSyntax GetSimpleMemberAccess(this SyntaxToken csIdentifier) + { + var simpleMemberAccess = CS.SyntaxFactory.MemberAccessExpression( + CS.SyntaxKind.SimpleMemberAccessExpression, CS.SyntaxFactory.ThisExpression(), + CS.SyntaxFactory.Token(CS.SyntaxKind.DotToken), ValidSyntaxFactory.IdentifierName(csIdentifier)); + + return simpleMemberAccess; + } } \ No newline at end of file diff --git a/CodeConverter/Util/SymbolExtensions.cs b/CodeConverter/Util/SymbolExtensions.cs index c35082ef..dbbf1b97 100644 --- a/CodeConverter/Util/SymbolExtensions.cs +++ b/CodeConverter/Util/SymbolExtensions.cs @@ -376,6 +376,29 @@ public static bool IsGenericMethod(this ISymbol symbol) { return symbol is IMethodSymbol ms && (ms.IsGenericMethod || ms.IsReducedTypeParameterMethod()); } + public static bool IsNonPublicInterfaceImplementation(this ISymbol declaredSymbol) + { + return declaredSymbol switch { + IMethodSymbol methodSymbol => methodSymbol.DeclaredAccessibility != Accessibility.Public && + methodSymbol.ExplicitInterfaceImplementations.Any(), + IPropertySymbol propertySymbol => propertySymbol.DeclaredAccessibility != Accessibility.Public && + propertySymbol.ExplicitInterfaceImplementations.Any(), + _ => throw new ArgumentOutOfRangeException(nameof(declaredSymbol)) + }; + } + + public static bool IsRenamedInterfaceMember(this ISymbol declaredSymbol, + SyntaxToken directlyConvertedCsIdentifier, IEnumerable explicitInterfaceImplementations) + { + bool IsRenamed(ISymbol csIdentifier) => + declaredSymbol switch { + IMethodSymbol methodSymbol => !StringComparer.OrdinalIgnoreCase.Equals(directlyConvertedCsIdentifier.Value, csIdentifier.Name) && methodSymbol.ExplicitInterfaceImplementations.Any(), + IPropertySymbol propertySymbol => !StringComparer.OrdinalIgnoreCase.Equals(directlyConvertedCsIdentifier.Value, csIdentifier.Name) && propertySymbol.ExplicitInterfaceImplementations.Any(), + _ => throw new ArgumentOutOfRangeException(nameof(declaredSymbol)) + }; + + return explicitInterfaceImplementations.Any(IsRenamed); + } } public enum SymbolVisibility diff --git a/CommandLine/CodeConv/CodeConv.csproj b/CommandLine/CodeConv/CodeConv.csproj index 3e9c1cc5..0aee5326 100644 --- a/CommandLine/CodeConv/CodeConv.csproj +++ b/CommandLine/CodeConv/CodeConv.csproj @@ -2,7 +2,7 @@ Exe - net6.0;net8.0 + net8.0 true true true diff --git a/Tests/CSharp/MemberTests/ConstructorTests.cs b/Tests/CSharp/MemberTests/ConstructorTests.cs new file mode 100644 index 00000000..3d2c1361 --- /dev/null +++ b/Tests/CSharp/MemberTests/ConstructorTests.cs @@ -0,0 +1,208 @@ +using System.Threading.Tasks; +using ICSharpCode.CodeConverter.Tests.TestRunners; +using Xunit; + +namespace ICSharpCode.CodeConverter.Tests.CSharp.MemberTests; + +public class ConstructorTests : ConverterTestBase +{ + [Fact] + public async Task TestConstructorVisibilityAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Class Class1 + Sub New(x As Boolean) + End Sub +End Class", @" +internal partial class Class1 +{ + public Class1(bool x) + { + } +}"); + } + + [Fact] + public async Task TestModuleConstructorAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Module Module1 + Sub New() + Dim someValue As Integer = 0 + End Sub +End Module", @" +internal static partial class Module1 +{ + static Module1() + { + int someValue = 0; + } +}"); + } + + [Fact] + public async Task TestHoistedOutParameterAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Class ClassWithProperties + Public Property Property1 As String +End Class + +Public Class VisualBasicClass + Public Sub New() + Dim x As New Dictionary(Of String, String)() + Dim y As New ClassWithProperties() + + If (x.TryGetValue(""x"", y.Property1)) Then + Debug.Print(y.Property1) + End If + End Sub +End Class", @"using System.Collections.Generic; +using System.Diagnostics; + +public partial class ClassWithProperties +{ + public string Property1 { get; set; } +} + +public partial class VisualBasicClass +{ + public VisualBasicClass() + { + var x = new Dictionary(); + var y = new ClassWithProperties(); + + bool localTryGetValue() { string argvalue = y.Property1; var ret = x.TryGetValue(""x"", out argvalue); y.Property1 = argvalue; return ret; } + + if (localTryGetValue()) + { + Debug.Print(y.Property1); + } + } +}"); + } + + [Fact] + public async Task TestHoistedOutParameterLambdaUsingByRefParameterAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Class SomeClass + Sub S(Optional ByRef x As Integer = -1) + Dim i As Integer = 0 + If F1(x, i) Then + ElseIf F2(x, i) Then + ElseIf F3(x, i) Then + End If + End Sub + + Function F1(x As Integer, ByRef o As Object) As Boolean : End Function + Function F2(ByRef x As Integer, ByRef o As Object) As Boolean : End Function + Function F3(ByRef x As Object, ByRef o As Object) As Boolean : End Function +End Class", @"using System.Runtime.InteropServices; +using Microsoft.VisualBasic.CompilerServices; // Install-Package Microsoft.VisualBasic + +public partial class SomeClass +{ + public void S([Optional, DefaultParameterValue(-1)] ref int x) + { + int i = 0; + bool localF1(ref int x) { object argo = i; var ret = F1(x, ref argo); i = Conversions.ToInteger(argo); return ret; } + bool localF2(ref int x) { object argo1 = i; var ret = F2(ref x, ref argo1); i = Conversions.ToInteger(argo1); return ret; } + bool localF3(ref int x) { object argx = x; object argo2 = i; var ret = F3(ref argx, ref argo2); x = Conversions.ToInteger(argx); i = Conversions.ToInteger(argo2); return ret; } + + if (localF1(ref x)) + { + } + else if (localF2(ref x)) + { + } + else if (localF3(ref x)) + { + } + } + + public bool F1(int x, ref object o) + { + return default; + } + public bool F2(ref int x, ref object o) + { + return default; + } + public bool F3(ref object x, ref object o) + { + return default; + } +}"); + } + + [Fact] + public async Task TestConstructorAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Class TestClass(Of T As {Class, New}, T2 As Structure, T3) + Public Sub New( ByRef argument As T, ByRef argument2 As T2, ByVal argument3 As T3) + End Sub +End Class", @" +internal partial class TestClass + where T : class, new() + where T2 : struct +{ + public TestClass(out T argument, ref T2 argument2, T3 argument3) + { + } +} +1 target compilation errors: +CS0177: The out parameter 'argument' must be assigned to before control leaves the current method"); + } + + [Fact] + public async Task TestConstructorWithImplicitPublicAccessibilityAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Sub New() +End Sub", @"public SurroundingClass() +{ +}"); + } + + [Fact] + public async Task TestStaticConstructorAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Shared Sub New() +End Sub", @"static SurroundingClass() +{ +}"); + } + + [Fact] + public async Task TestConstructorStaticLocalConvertedToFieldAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Class StaticLocalConvertedToField + Sub New(x As Boolean) + Static sPrevPosition As Integer = 7 ' Comment moves with declaration + Console.WriteLine(sPrevPosition) + End Sub + Sub New(x As Integer) + Static sPrevPosition As Integer + Console.WriteLine(sPrevPosition) + End Sub +End Class", @"using System; + +internal partial class StaticLocalConvertedToField +{ + private int _sPrevPosition = 7; // Comment moves with declaration + public StaticLocalConvertedToField(bool x) + { + Console.WriteLine(_sPrevPosition); + } + + private int _sPrevPosition1 = default; + public StaticLocalConvertedToField(int x) + { + Console.WriteLine(_sPrevPosition1); + } +}"); + } +} \ No newline at end of file diff --git a/Tests/CSharp/MemberTests/ExtensionMethodTests.cs b/Tests/CSharp/MemberTests/ExtensionMethodTests.cs new file mode 100644 index 00000000..56ea17f0 --- /dev/null +++ b/Tests/CSharp/MemberTests/ExtensionMethodTests.cs @@ -0,0 +1,234 @@ +using System.Threading.Tasks; +using ICSharpCode.CodeConverter.Tests.TestRunners; +using Xunit; + +namespace ICSharpCode.CodeConverter.Tests.CSharp.MemberTests; + +public class ExtensionMethodTests : ConverterTestBase +{ + [Fact] + public async Task TestExtensionMethodAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Module TestClass + + Sub TestMethod(ByVal str As String) + End Sub + + + Sub TestMethod2Parameters(ByVal str As String, other As String) + End Sub +End Module", @" +internal static partial class TestClass +{ + public static void TestMethod(this string str) + { + } + + public static void TestMethod2Parameters(this string str, string other) + { + } +}"); + } + + [Fact] + public async Task TestExtensionMethodWithExistingImportAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Imports System.Runtime.CompilerServices ' Removed by simplifier + +Module TestClass + + Sub TestMethod(ByVal str As String) + End Sub +End Module", @" +internal static partial class TestClass +{ + public static void TestMethod(this string str) + { + } +}"); + } + + [Fact] + public async Task TestRefExtensionMethodAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Imports System +Imports System.Runtime.CompilerServices ' Removed since the extension attribute is removed + +Public Module MyExtensions + + Public Sub Add(Of T)(ByRef arr As T(), item As T) + Array.Resize(arr, arr.Length + 1) + arr(arr.Length - 1) = item + End Sub +End Module + +Public Module UsagePoint + Public Sub Main() + Dim arr = New Integer() {1, 2, 3} + arr.Add(4) + System.Console.WriteLine(arr(3)) + End Sub +End Module", @"using System; + +public static partial class MyExtensions +{ + public static void Add(ref T[] arr, T item) + { + Array.Resize(ref arr, arr.Length + 1); + arr[arr.Length - 1] = item; + } +} + +public static partial class UsagePoint +{ + public static void Main() + { + int[] arr = new int[] { 1, 2, 3 }; + MyExtensions.Add(ref arr, 4); + Console.WriteLine(arr[3]); + } +}"); + } + + [Fact] + public async Task TestExtensionWithinExtendedTypeAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Module Extensions + + Sub TestExtension(extendedClass As ExtendedClass) + End Sub +End Module + +Class ExtendedClass + Sub TestExtensionConsumer() + TestExtension() + End Sub +End Class", @" +internal static partial class Extensions +{ + public static void TestExtension(this ExtendedClass extendedClass) + { + } +} + +internal partial class ExtendedClass +{ + public void TestExtensionConsumer() + { + this.TestExtension(); + } +}"); + } + + [Fact] + public async Task TestExtensionWithinTypeDerivedFromExtendedTypeAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Module Extensions + + Sub TestExtension(extendedClass As ExtendedClass) + End Sub +End Module + +Class ExtendedClass +End Class + +Class DerivedClass + Inherits ExtendedClass + + Sub TestExtensionConsumer() + TestExtension() + End Sub +End Class", @" +internal static partial class Extensions +{ + public static void TestExtension(this ExtendedClass extendedClass) + { + } +} + +internal partial class ExtendedClass +{ +} + +internal partial class DerivedClass : ExtendedClass +{ + + public void TestExtensionConsumer() + { + this.TestExtension(); + } +}"); + } + + [Fact] + public async Task TestExtensionWithinNestedExtendedTypeAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Module Extensions + + Sub TestExtension(extendedClass As NestingClass.ExtendedClass) + End Sub +End Module + +Class NestingClass + Class ExtendedClass + Sub TestExtensionConsumer() + TestExtension() + End Sub + End Class +End Class", @" +internal static partial class Extensions +{ + public static void TestExtension(this NestingClass.ExtendedClass extendedClass) + { + } +} + +internal partial class NestingClass +{ + public partial class ExtendedClass + { + public void TestExtensionConsumer() + { + this.TestExtension(); + } + } +}"); + } + + [Fact] + public async Task TestExtensionWithMeWithinExtendedTypeAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Module Extensions + + Sub TestExtension(extendedClass As ExtendedClass) + End Sub +End Module + +Class ExtendedClass + Sub TestExtensionConsumer() + Me.TestExtension() + End Sub +End Class", @" +internal static partial class Extensions +{ + public static void TestExtension(this ExtendedClass extendedClass) + { + } +} + +internal partial class ExtendedClass +{ + public void TestExtensionConsumer() + { + this.TestExtension(); + } +}"); + } +} \ No newline at end of file diff --git a/Tests/CSharp/MemberTests/InterfaceTests.cs b/Tests/CSharp/MemberTests/InterfaceTests.cs new file mode 100644 index 00000000..da91d2c1 --- /dev/null +++ b/Tests/CSharp/MemberTests/InterfaceTests.cs @@ -0,0 +1,2336 @@ +using System.Threading.Tasks; +using ICSharpCode.CodeConverter.Tests.TestRunners; +using Xunit; + +namespace ICSharpCode.CodeConverter.Tests.CSharp.MemberTests; + +public class InterfaceTests : ConverterTestBase +{ + + [Fact] + public async Task Issue443_FixCaseForInterfaceMembersAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + Function FooDifferentCase( ByRef str2 As String) As Integer +End Interface + +Public Class Foo + Implements IFoo + Function fooDifferentCase( ByRef str2 As String) As Integer Implements IFoo.FOODIFFERENTCASE + str2 = 2.ToString() + Return 3 + End Function +End Class", @" +public partial interface IFoo +{ + int FooDifferentCase(out string str2); +} + +public partial class Foo : IFoo +{ + public int FooDifferentCase(out string str2) + { + str2 = 2.ToString(); + return 3; + } +} +"); + } + + [Fact] + public async Task Issue444_FixNameForRenamedInterfaceMembersAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + Function FooDifferentName(ByRef str As String, i As Integer) As Integer +End Interface + +Public Class Foo + Implements IFoo + + Function BarDifferentName(ByRef str As String, i As Integer) As Integer Implements IFoo.FooDifferentName + Return 4 + End Function +End Class", @" +public partial interface IFoo +{ + int FooDifferentName(ref string str, int i); +} + +public partial class Foo : IFoo +{ + + public int BarDifferentName(ref string str, int i) + { + return 4; + } + + int IFoo.FooDifferentName(ref string str, int i) => BarDifferentName(ref str, i); +} +"); + } + + [Fact] + public async Task IdenticalInterfaceMethodsWithRenamedInterfaceMembersAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + Function DoFooBar(ByRef str As String, i As Integer) As Integer +End Interface + +Public Interface IBar + Function DoFooBar(ByRef str As String, i As Integer) As Integer +End Interface + +Public Class FooBar + Implements IFoo, IBar + + Function Foo(ByRef str As String, i As Integer) As Integer Implements IFoo.DoFooBar + Return 4 + End Function + + Function Bar(ByRef str As String, i As Integer) As Integer Implements IBar.DoFooBar + Return 2 + End Function + +End Class", @" +public partial interface IFoo +{ + int DoFooBar(ref string str, int i); +} + +public partial interface IBar +{ + int DoFooBar(ref string str, int i); +} + +public partial class FooBar : IFoo, IBar +{ + + public int Foo(ref string str, int i) + { + return 4; + } + + int IFoo.DoFooBar(ref string str, int i) => Foo(ref str, i); + + public int Bar(ref string str, int i) + { + return 2; + } + + int IBar.DoFooBar(ref string str, int i) => Bar(ref str, i); + +} +"); + } + + [Fact] + public async Task RenamedInterfaceCasingOnlyDifferenceConsumerAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @" +Public Interface IFoo + Function DoFoo() As Integer + Property Prop As Integer +End Interface + +Public Class Foo + Implements IFoo + + Private Function doFoo() As Integer Implements IFoo.DoFoo + Return 4 + End Function + + Private Property prop As Integer Implements IFoo.Prop + + Private Function Consumer() As Integer + Dim foo As New Foo() + Dim interfaceInstance As IFoo = foo + Return foo.doFoo() + foo.DoFoo() + + interfaceInstance.doFoo() + interfaceInstance.DoFoo() + + foo.prop + foo.Prop + + interfaceInstance.prop + interfaceInstance.Prop + End Function + +End Class", @" +public partial interface IFoo +{ + int DoFoo(); + int Prop { get; set; } +} + +public partial class Foo : IFoo +{ + + private int doFoo() + { + return 4; + } + + int IFoo.DoFoo() => doFoo(); + + private int prop { get; set; } + int IFoo.Prop { get => prop; set => prop = value; } + + private int Consumer() + { + var foo = new Foo(); + IFoo interfaceInstance = foo; + return foo.doFoo() + foo.doFoo() + interfaceInstance.DoFoo() + interfaceInstance.DoFoo() + foo.prop + foo.prop + interfaceInstance.Prop + interfaceInstance.Prop; + } + +} +"); + } + + [Fact] + public async Task RenamedInterfaceCasingOnlyDifferenceForVirtualMemberConsumerAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @" +Public Interface IFoo + Function DoFoo() As Integer + Property Prop As Integer +End Interface + +Public MustInherit Class BaseFoo + Implements IFoo + + Protected Friend Overridable Function doFoo() As Integer Implements IFoo.DoFoo + Return 4 + End Function + + Protected Friend Overridable Property prop As Integer Implements IFoo.Prop + +End Class + +Public Class Foo + Inherits BaseFoo + + Protected Friend Overrides Function DoFoo() As Integer + Return 5 + End Function + + Protected Friend Overrides Property Prop As Integer + + Private Function Consumer() As Integer + Dim foo As New Foo() + Dim interfaceInstance As IFoo = foo + Dim baseClass As BaseFoo = foo + Return foo.doFoo() + foo.DoFoo() + + interfaceInstance.doFoo() + interfaceInstance.DoFoo() + + baseClass.doFoo() + baseClass.DoFoo() + + foo.prop + foo.Prop + + interfaceInstance.prop + interfaceInstance.Prop + + baseClass.prop + baseClass.Prop + End Function +End Class", @" +public partial interface IFoo +{ + int DoFoo(); + int Prop { get; set; } +} + +public abstract partial class BaseFoo : IFoo +{ + + protected internal virtual int doFoo() + { + return 4; + } + + int IFoo.DoFoo() => doFoo(); + + protected internal virtual int prop { get; set; } + int IFoo.Prop { get => prop; set => prop = value; } + +} + +public partial class Foo : BaseFoo +{ + + protected internal override int doFoo() + { + return 5; + } + + protected internal override int prop { get; set; } + + private int Consumer() + { + var foo = new Foo(); + IFoo interfaceInstance = foo; + BaseFoo baseClass = foo; + return foo.doFoo() + foo.doFoo() + interfaceInstance.DoFoo() + interfaceInstance.DoFoo() + baseClass.doFoo() + baseClass.doFoo() + foo.prop + foo.prop + interfaceInstance.Prop + interfaceInstance.Prop + baseClass.prop + baseClass.prop; + } +} +"); + } + + [Fact] + public async Task RenamedInterfaceCasingOnlyDifferenceWithOverloadedPropertyConsumerAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @" +Public Interface IUserContext + ReadOnly Property GroupID As String +End Interface + +Public Interface IFoo + ReadOnly Property ConnectedGroupId As String +End Interface + +Public MustInherit Class BaseFoo + Implements IUserContext + + Protected Friend ReadOnly Property ConnectedGroupID() As String Implements IUserContext.GroupID + +End Class + +Public Class Foo + Inherits BaseFoo + Implements IFoo + + Protected Friend Overloads ReadOnly Property ConnectedGroupID As String Implements IFoo.ConnectedGroupId ' Comment moves because this line gets split + Get + Return If("""", MyBase.ConnectedGroupID()) + End Get + End Property + + Private Function Consumer() As String + Dim foo As New Foo() + Dim ifoo As IFoo = foo + Dim baseFoo As BaseFoo = foo + Dim iUserContext As IUserContext = foo + Return foo.ConnectedGroupID & foo.ConnectedGroupId & + iFoo.ConnectedGroupID & iFoo.ConnectedGroupId & + baseFoo.ConnectedGroupID & baseFoo.ConnectedGroupId & + iUserContext.GroupId & iUserContext.GroupID + End Function + +End Class", @" +public partial interface IUserContext +{ + string GroupID { get; } +} + +public partial interface IFoo +{ + string ConnectedGroupId { get; } +} + +public abstract partial class BaseFoo : IUserContext +{ + + protected internal string ConnectedGroupID { get; private set; } + string IUserContext.GroupID { get => ConnectedGroupID; } + +} + +public partial class Foo : BaseFoo, IFoo +{ + + protected internal new string ConnectedGroupID + { + get + { + return """" ?? base.ConnectedGroupID; + } + } + + string IFoo.ConnectedGroupId { get => ConnectedGroupID; } // Comment moves because this line gets split + + private string Consumer() + { + var foo = new Foo(); + IFoo ifoo = foo; + BaseFoo baseFoo = foo; + IUserContext iUserContext = foo; + return foo.ConnectedGroupID + foo.ConnectedGroupID + ifoo.ConnectedGroupId + ifoo.ConnectedGroupId + baseFoo.ConnectedGroupID + baseFoo.ConnectedGroupID + iUserContext.GroupID + iUserContext.GroupID; + } + +} +"); + } + + [Fact] + public async Task RenamedMethodImplementsMultipleInterfacesAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + Function DoFooBar(ByRef str As String, i As Integer) As Integer +End Interface + +Public Interface IBar + Function DoFooBar(ByRef str As String, i As Integer) As Integer +End Interface + +Public Class FooBar + Implements IFoo, IBar + + Function Foo(ByRef str As String, i As Integer) As Integer Implements IFoo.DoFooBar, IBar.DoFooBar + Return 4 + End Function + +End Class", @" +public partial interface IFoo +{ + int DoFooBar(ref string str, int i); +} + +public partial interface IBar +{ + int DoFooBar(ref string str, int i); +} + +public partial class FooBar : IFoo, IBar +{ + + public int Foo(ref string str, int i) + { + return 4; + } + + int IFoo.DoFooBar(ref string str, int i) => Foo(ref str, i); + int IBar.DoFooBar(ref string str, int i) => Foo(ref str, i); + +}"); + } + + [Fact] + public async Task IdenticalInterfacePropertiesWithRenamedInterfaceMembersAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + Property FooBarProp As Integer + End Interface + +Public Interface IBar + Property FooBarProp As Integer +End Interface + +Public Class FooBar + Implements IFoo, IBar + + Property Foo As Integer Implements IFoo.FooBarProp + + Property Bar As Integer Implements IBar.FooBarProp + +End Class", @" +public partial interface IFoo +{ + int FooBarProp { get; set; } +} + +public partial interface IBar +{ + int FooBarProp { get; set; } +} + +public partial class FooBar : IFoo, IBar +{ + + public int Foo { get; set; } + int IFoo.FooBarProp { get => Foo; set => Foo = value; } + + public int Bar { get; set; } + int IBar.FooBarProp { get => Bar; set => Bar = value; } + +}"); + } + + [Fact] + public async Task ExplicitInterfaceImplementationRequiredMethodParameters_749_Async() + { + await TestConversionVisualBasicToCSharpAsync( + @" +Public Interface IFoo + Function DoFooBar(ByRef str As String, i As Integer) As Integer +End Interface + +Public Interface IBar + Function DoFooBar(ByRef str As String, i As Integer) As Integer +End Interface + +Public Class FooBar + Implements IFoo, IBar + + Function Foo(ByRef str As String, i As Integer) As Integer Implements IFoo.DoFooBar + Return 4 + End Function + + Function Bar(ByRef str As String, i As Integer) As Integer Implements IBar.DoFooBar + Return 2 + End Function + +End Class", @" +public partial interface IFoo +{ + int DoFooBar(ref string str, int i); +} + +public partial interface IBar +{ + int DoFooBar(ref string str, int i); +} + +public partial class FooBar : IFoo, IBar +{ + + public int Foo(ref string str, int i) + { + return 4; + } + + int IFoo.DoFooBar(ref string str, int i) => Foo(ref str, i); + + public int Bar(ref string str, int i) + { + return 2; + } + + int IBar.DoFooBar(ref string str, int i) => Bar(ref str, i); + +} +"); + } + + [Fact] + public async Task ExplicitInterfaceImplementationOptionalParameters_1062_Async() + { + await TestConversionVisualBasicToCSharpAsync( + @" +Public Interface InterfaceWithOptionalParameters + Sub S(Optional i As Integer = 0) +End Interface + +Public Class ImplInterfaceWithOptionalParameters : Implements InterfaceWithOptionalParameters + Public Sub InterfaceWithOptionalParameters_S(Optional i As Integer = 0) Implements InterfaceWithOptionalParameters.S + End Sub +End Class", @" +public partial interface InterfaceWithOptionalParameters +{ + void S(int i = 0); +} + +public partial class ImplInterfaceWithOptionalParameters : InterfaceWithOptionalParameters +{ + public void InterfaceWithOptionalParameters_S(int i = 0) + { + } + + void InterfaceWithOptionalParameters.S(int i = 0) => InterfaceWithOptionalParameters_S(i); +} +"); + } + + [Fact] + public async Task OptionalParameterWithReservedName_1092_Async() + { + await TestConversionVisualBasicToCSharpAsync( + @" +Public Class WithOptionalParameters + Sub S1(Optional a As Object = Nothing, Optional [default] As String = """") + End Sub + + Sub S() + S1(, ""a"") + End Sub +End Class", @" +public partial class WithOptionalParameters +{ + public void S1(object a = null, string @default = """") + { + } + + public void S() + { + S1(@default: ""a""); + } +} +"); + } + + + [Fact] + public async Task ExplicitInterfaceImplementationOptionalParametersAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + Property ExplicitProp(Optional str As String = """") As Integer + Function ExplicitFunc(Optional str2 As String = """", Optional i2 As Integer = 1) As Integer +End Interface + +Public Class Foo + Implements IFoo + + Private Function ExplicitFunc(Optional str As String = """", Optional i2 As Integer = 1) As Integer Implements IFoo.ExplicitFunc + Return 5 + End Function + + Private Property ExplicitProp(Optional str As String = """") As Integer Implements IFoo.ExplicitProp + Get + Return 5 + End Get + Set(value As Integer) + End Set + End Property +End Class", @" +public partial interface IFoo +{ + int get_ExplicitProp(string str = """"); + void set_ExplicitProp(string str = """", int value = default); + int ExplicitFunc(string str2 = """", int i2 = 1); +} + +public partial class Foo : IFoo +{ + + private int ExplicitFunc(string str = """", int i2 = 1) + { + return 5; + } + + int IFoo.ExplicitFunc(string str = """", int i2 = 1) => ExplicitFunc(str, i2); + + private int get_ExplicitProp(string str = """") + { + return 5; + } + private void set_ExplicitProp(string str = """", int value = default) + { + } + + int IFoo.get_ExplicitProp(string str = """") => get_ExplicitProp(str); + void IFoo.set_ExplicitProp(string str = """", int value = default) => set_ExplicitProp(str, value); +} +"); + } + + + [Fact] + public async Task ExplicitInterfaceImplementationOptionalMethodParameters_749_Async() + { + await TestConversionVisualBasicToCSharpAsync( + @" +Public Interface IFoo + Function DoFooBar(ByRef str As String, Optional i As Integer = 4) As Integer +End Interface + +Public Interface IBar + Function DoFooBar(ByRef str As String, Optional i As Integer = 8) As Integer +End Interface + +Public Class FooBar + Implements IFoo, IBar + + Function Foo(ByRef str As String, Optional i As Integer = 4) As Integer Implements IFoo.DoFooBar + Return 4 + End Function + + Function Bar(ByRef str As String, Optional i As Integer = 8) As Integer Implements IBar.DoFooBar + Return 2 + End Function + +End Class", @" +public partial interface IFoo +{ + int DoFooBar(ref string str, int i = 4); +} + +public partial interface IBar +{ + int DoFooBar(ref string str, int i = 8); +} + +public partial class FooBar : IFoo, IBar +{ + + public int Foo(ref string str, int i = 4) + { + return 4; + } + + int IFoo.DoFooBar(ref string str, int i = 4) => Foo(ref str, i); + + public int Bar(ref string str, int i = 8) + { + return 2; + } + + int IBar.DoFooBar(ref string str, int i = 8) => Bar(ref str, i); + +} +"); + } + + [Fact] + public async Task RenamedInterfaceMethodFullyQualifiedAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Namespace TestNamespace + Public Interface IFoo + Function DoFoo(ByRef str As String, i As Integer) As Integer + End Interface +End Namespace + +Public Class Foo + Implements TestNamespace.IFoo + + Function DoFooRenamed(ByRef str As String, i As Integer) As Integer Implements TestNamespace.IFoo.DoFoo + Return 4 + End Function +End Class", @" +namespace TestNamespace +{ + public partial interface IFoo + { + int DoFoo(ref string str, int i); + } +} + +public partial class Foo : TestNamespace.IFoo +{ + + public int DoFooRenamed(ref string str, int i) + { + return 4; + } + + int TestNamespace.IFoo.DoFoo(ref string str, int i) => DoFooRenamed(ref str, i); +}"); + } + + [Fact] + public async Task RenamedInterfacePropertyFullyQualifiedAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Namespace TestNamespace + Public Interface IFoo + Property FooProp As Integer + End Interface +End Namespace + +Public Class Foo + Implements TestNamespace.IFoo + + Property FooPropRenamed As Integer Implements TestNamespace.IFoo.FooProp + +End Class", @" +namespace TestNamespace +{ + public partial interface IFoo + { + int FooProp { get; set; } + } +} + +public partial class Foo : TestNamespace.IFoo +{ + + public int FooPropRenamed { get; set; } + int TestNamespace.IFoo.FooProp { get => FooPropRenamed; set => FooPropRenamed = value; } + +}"); + } + + [Fact] + public async Task RenamedInterfaceMethodConsumerCasingRenamedAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo + Function DoFoo(ByRef str As String, i As Integer) As Integer + End Interface + +Public Class Foo + Implements IFoo + + Function DoFooRenamed(ByRef str As String, i As Integer) As Integer Implements IFoo.DoFoo + Return 4 + End Function +End Class + +Public Class FooConsumer + Function DoFooRenamedConsumer(ByRef str As String, i As Integer) As Integer + Dim foo As New Foo + Dim bar As IFoo = foo + Return foo.DOFOORENAMED(str, i) + bar.DoFoo(str, i) + End Function +End Class", @" +public partial interface IFoo +{ + int DoFoo(ref string str, int i); +} + +public partial class Foo : IFoo +{ + + public int DoFooRenamed(ref string str, int i) + { + return 4; + } + + int IFoo.DoFoo(ref string str, int i) => DoFooRenamed(ref str, i); +} + +public partial class FooConsumer +{ + public int DoFooRenamedConsumer(ref string str, int i) + { + var foo = new Foo(); + IFoo bar = foo; + return foo.DoFooRenamed(ref str, i) + bar.DoFoo(ref str, i); + } +}"); + } + + [Fact] + public async Task RenamedInterfacePropertyConsumerCasingRenamedAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo + Property FooProp As Integer + End Interface + +Public Class Foo + Implements IFoo + + Property FooPropRenamed As Integer Implements IFoo.FooProp + +End Class + +Public Class FooConsumer + Function GetFooRenamed() As Integer + Dim foo As New Foo + Dim bar As IFoo = foo + Return foo.FOOPROPRENAMED + bar.FooProp + End Function +End Class", @" +public partial interface IFoo +{ + int FooProp { get; set; } +} + +public partial class Foo : IFoo +{ + + public int FooPropRenamed { get; set; } + int IFoo.FooProp { get => FooPropRenamed; set => FooPropRenamed = value; } + +} + +public partial class FooConsumer +{ + public int GetFooRenamed() + { + var foo = new Foo(); + IFoo bar = foo; + return foo.FooPropRenamed + bar.FooProp; + } +}"); + } + + [Fact] + public async Task InterfaceMethodCasingRenamedConsumerAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo + Function DoFoo(str As String, i As Integer) As Integer + End Interface + +Public Class Foo + Implements IFoo + + Function dofoo(str As String, i As Integer) As Integer Implements IFoo.DoFoo + Return 4 + End Function +End Class + +Public Class FooConsumer + Function DoFooRenamedConsumer(str As String, i As Integer) As Integer + Dim foo As New Foo + Dim bar As IFoo = foo + Return foo.dofoo(str, i) + bar.DoFoo(str, i) + End Function +End Class", @" +public partial interface IFoo +{ + int DoFoo(string str, int i); +} + +public partial class Foo : IFoo +{ + + public int DoFoo(string str, int i) + { + return 4; + } +} + +public partial class FooConsumer +{ + public int DoFooRenamedConsumer(string str, int i) + { + var foo = new Foo(); + IFoo bar = foo; + return foo.DoFoo(str, i) + bar.DoFoo(str, i); + } +}"); + } + + [Fact] + public async Task InterfacePropertyCasingRenamedConsumerAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo + Property FooProp As Integer + End Interface + +Public Class Foo + Implements IFoo + + Property fooprop As Integer Implements IFoo.FooProp + +End Class + +Public Class FooConsumer + Function GetFooRenamed() As Integer + Dim foo As New Foo + Dim bar As IFoo = foo + Return foo.fooprop + bar.FooProp + End Function +End Class", @" +public partial interface IFoo +{ + int FooProp { get; set; } +} + +public partial class Foo : IFoo +{ + + public int FooProp { get; set; } + +} + +public partial class FooConsumer +{ + public int GetFooRenamed() + { + var foo = new Foo(); + IFoo bar = foo; + return foo.FooProp + bar.FooProp; + } +}"); + } + + [Fact] + public async Task InterfaceRenamedMethodConsumerAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo + Function DoFoo(ByRef str As String, i As Integer) As Integer + End Interface + +Public Class Foo + Implements IFoo + + Function DoFooRenamed(ByRef str As String, i As Integer) As Integer Implements IFoo.DoFoo + Return 4 + End Function +End Class + +Public Class FooConsumer + Function DoFooRenamedConsumer(ByRef str As String, i As Integer) As Integer + Dim foo As New Foo + Dim bar As IFoo = foo + Return foo.DoFooRenamed(str, i) + bar.DoFoo(str, i) + End Function +End Class", @" +public partial interface IFoo +{ + int DoFoo(ref string str, int i); +} + +public partial class Foo : IFoo +{ + + public int DoFooRenamed(ref string str, int i) + { + return 4; + } + + int IFoo.DoFoo(ref string str, int i) => DoFooRenamed(ref str, i); +} + +public partial class FooConsumer +{ + public int DoFooRenamedConsumer(ref string str, int i) + { + var foo = new Foo(); + IFoo bar = foo; + return foo.DoFooRenamed(ref str, i) + bar.DoFoo(ref str, i); + } +}"); + } + + [Fact] + public async Task InterfaceRenamedPropertyConsumerAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo + Property FooProp As Integer + End Interface + +Public Class Foo + Implements IFoo + + Property FooPropRenamed As Integer Implements IFoo.FooProp + +End Class + +Public Class FooConsumer + Function GetFooRenamed() As Integer + Dim foo As New Foo + Dim bar As IFoo = foo + Return foo.FooPropRenamed + bar.FooProp + End Function +End Class", @" +public partial interface IFoo +{ + int FooProp { get; set; } +} + +public partial class Foo : IFoo +{ + + public int FooPropRenamed { get; set; } + int IFoo.FooProp { get => FooPropRenamed; set => FooPropRenamed = value; } + +} + +public partial class FooConsumer +{ + public int GetFooRenamed() + { + var foo = new Foo(); + IFoo bar = foo; + return foo.FooPropRenamed + bar.FooProp; + } +}"); + } + + [Fact] + public async Task PartialInterfaceRenamedMethodConsumerAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Partial Interface IFoo + Function DoFoo(ByRef str As String, i As Integer) As Integer + End Interface + +Public Class Foo + Implements IFoo + + Function DoFooRenamed(ByRef str As String, i As Integer) As Integer Implements IFoo.DoFoo + Return 4 + End Function +End Class + +Public Class FooConsumer + Function DoFooRenamedConsumer(ByRef str As String, i As Integer) As Integer + Dim foo As New Foo + Dim bar As IFoo = foo + Return foo.DoFooRenamed(str, i) + bar.DoFoo(str, i) + End Function +End Class", @" +public partial interface IFoo +{ + int DoFoo(ref string str, int i); +} + +public partial class Foo : IFoo +{ + + public int DoFooRenamed(ref string str, int i) + { + return 4; + } + + int IFoo.DoFoo(ref string str, int i) => DoFooRenamed(ref str, i); +} + +public partial class FooConsumer +{ + public int DoFooRenamedConsumer(ref string str, int i) + { + var foo = new Foo(); + IFoo bar = foo; + return foo.DoFooRenamed(ref str, i) + bar.DoFoo(ref str, i); + } +}"); + } + + [Fact] + public async Task PartialInterfaceRenamedPropertyConsumerAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Partial Interface IFoo + Property FooProp As Integer + End Interface + +Public Class Foo + Implements IFoo + + Property FooPropRenamed As Integer Implements IFoo.FooProp + +End Class + +Public Class FooConsumer + Function GetFooRenamed() As Integer + Dim foo As New Foo + Dim bar As IFoo = foo + Return foo.FooPropRenamed + bar.FooProp + End Function +End Class", @" +public partial interface IFoo +{ + int FooProp { get; set; } +} + +public partial class Foo : IFoo +{ + + public int FooPropRenamed { get; set; } + int IFoo.FooProp { get => FooPropRenamed; set => FooPropRenamed = value; } + +} + +public partial class FooConsumer +{ + public int GetFooRenamed() + { + var foo = new Foo(); + IFoo bar = foo; + return foo.FooPropRenamed + bar.FooProp; + } +}"); + } + + [Fact] + public async Task RenamedInterfaceMethodMyClassConsumerAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo + Function DoFoo(ByRef str As String, i As Integer) As Integer + End Interface + +Public Class Foo + Implements IFoo + + Overridable Function DoFooRenamed(ByRef str As String, i As Integer) As Integer Implements IFoo.DoFoo ' Comment ends up out of order, but attached to correct method + Return 4 + End Function + + Function DoFooRenamedConsumer(ByRef str As String, i As Integer) As Integer + Return MyClass.DoFooRenamed(str, i) + End Function +End Class", @" +public partial interface IFoo +{ + int DoFoo(ref string str, int i); +} + +public partial class Foo : IFoo +{ + + public int MyClassDoFooRenamed(ref string str, int i) + { + return 4; + } + + int IFoo.DoFoo(ref string str, int i) => DoFooRenamed(ref str, i); + public virtual int DoFooRenamed(ref string str, int i) => MyClassDoFooRenamed(ref str, i); // Comment ends up out of order, but attached to correct method + + public int DoFooRenamedConsumer(ref string str, int i) + { + return MyClassDoFooRenamed(ref str, i); + } +}"); + } + + [Fact] + public async Task RenamedInterfacePropertyMyClassConsumerAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo + ReadOnly Property DoFoo As Integer + WriteOnly Property DoBar As Integer + End Interface + +Public Class Foo + Implements IFoo + + Overridable ReadOnly Property DoFooRenamed As Integer Implements IFoo.DoFoo ' Comment ends up out of order, but attached to correct method + Get + Return 4 + End Get + End Property + + Overridable WriteOnly Property DoBarRenamed As Integer Implements IFoo.DoBar ' Comment ends up out of order, but attached to correct method + Set + Throw New Exception() + End Set + End Property + + Sub DoFooRenamedConsumer() + MyClass.DoBarRenamed = MyClass.DoFooRenamed + End Sub +End Class", @"using System; + +public partial interface IFoo +{ + int DoFoo { get; } + int DoBar { set; } +} + +public partial class Foo : IFoo +{ + + public int MyClassDoFooRenamed + { + get + { + return 4; + } + } + + int IFoo.DoFoo { get => DoFooRenamed; } + + public virtual int DoFooRenamed // Comment ends up out of order, but attached to correct method + { + get + { + return MyClassDoFooRenamed; + } + } + + public int MyClassDoBarRenamed + { + set + { + throw new Exception(); + } + } + + int IFoo.DoBar { set => DoBarRenamed = value; } + + public virtual int DoBarRenamed // Comment ends up out of order, but attached to correct method + { + set + { + MyClassDoBarRenamed = value; + } + } + + public void DoFooRenamedConsumer() + { + MyClassDoBarRenamed = MyClassDoFooRenamed; + } +}"); + } + + [Fact] + public async Task ExplicitInterfaceImplementationAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + Property ExplicitProp(str As String) As Integer + Function ExplicitFunc(ByRef str2 As String, i2 As Integer) As Integer +End Interface + +Public Class Foo + Implements IFoo + + Private Function ExplicitFunc(ByRef str As String, i As Integer) As Integer Implements IFoo.ExplicitFunc + Return 5 + End Function + + Private Property ExplicitProp(str As String) As Integer Implements IFoo.ExplicitProp + Get + Return 5 + End Get + Set(value As Integer) + End Set + End Property +End Class", @" +public partial interface IFoo +{ + int get_ExplicitProp(string str); + void set_ExplicitProp(string str, int value); + int ExplicitFunc(ref string str2, int i2); +} + +public partial class Foo : IFoo +{ + + private int ExplicitFunc(ref string str, int i) + { + return 5; + } + + int IFoo.ExplicitFunc(ref string str, int i) => ExplicitFunc(ref str, i); + + private int get_ExplicitProp(string str) + { + return 5; + } + private void set_ExplicitProp(string str, int value) + { + } + + int IFoo.get_ExplicitProp(string str) => get_ExplicitProp(str); + void IFoo.set_ExplicitProp(string str, int value) => set_ExplicitProp(str, value); +} +"); + } + + [Fact] + public async Task PropertyInterfaceImplementationKeepsVirtualModifierAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + Property PropParams(str As String) As Integer + Property Prop() As Integer +End Interface + +Public Class Foo + Implements IFoo + + Public Overridable Property PropParams(str As String) As Integer Implements IFoo.PropParams + Get + Return 5 + End Get + Set(value As Integer) + End Set + End Property + + Public Overridable Property Prop As Integer Implements IFoo.Prop + Get + Return 5 + End Get + Set(value As Integer) + End Set + End Property +End Class", @" +public partial interface IFoo +{ + int get_PropParams(string str); + void set_PropParams(string str, int value); + int Prop { get; set; } +} + +public partial class Foo : IFoo +{ + + public virtual int get_PropParams(string str) + { + return 5; + } + public virtual void set_PropParams(string str, int value) + { + } + + public virtual int Prop + { + get + { + return 5; + } + set + { + } + } +} +"); + } + + [Fact] + public async Task PrivateAutoPropertyImplementsMultipleInterfacesAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + Property ExplicitProp As Integer +End Interface + +Public Interface IBar + Property ExplicitProp As Integer +End Interface + +Public Class Foo + Implements IFoo, IBar + + Private Property ExplicitProp As Integer Implements IFoo.ExplicitProp, IBar.ExplicitProp +End Class", @" +public partial interface IFoo +{ + int ExplicitProp { get; set; } +} + +public partial interface IBar +{ + int ExplicitProp { get; set; } +} + +public partial class Foo : IFoo, IBar +{ + + private int ExplicitProp { get; set; } + int IFoo.ExplicitProp { get => ExplicitProp; set => ExplicitProp = value; } + int IBar.ExplicitProp { get => ExplicitProp; set => ExplicitProp = value; } +}"); + } + + + [Fact] + public async Task ImplementMultipleRenamedPropertiesFromInterfaceAsAbstractAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @" +Public Interface IFoo + Property ExplicitProp As Integer +End Interface +Public Interface IBar + Property ExplicitProp As Integer +End Interface +Public MustInherit Class Foo + Implements IFoo, IBar + + Protected MustOverride Property ExplicitPropRenamed1 As Integer Implements IFoo.ExplicitProp + Protected MustOverride Property ExplicitPropRenamed2 As Integer Implements IBar.ExplicitProp +End Class", @" +public partial interface IFoo +{ + int ExplicitProp { get; set; } +} + +public partial interface IBar +{ + int ExplicitProp { get; set; } +} +public abstract partial class Foo : IFoo, IBar +{ + + protected abstract int ExplicitPropRenamed1 { get; set; } + int IFoo.ExplicitProp { get => ExplicitPropRenamed1; set => ExplicitPropRenamed1 = value; } + protected abstract int ExplicitPropRenamed2 { get; set; } + int IBar.ExplicitProp { get => ExplicitPropRenamed2; set => ExplicitPropRenamed2 = value; } +}"); + } + + [Fact] + public async Task ExplicitInterfaceImplementationForVirtualMemberFromAnotherClassAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @" +Public Interface IFoo + Sub Save() + Property Prop As Integer +End Interface + +Public MustInherit Class BaseFoo + Protected Overridable Sub OnSave() + End Sub + + Protected Overridable Property MyProp As Integer = 5 +End Class + +Public Class Foo + Inherits BaseFoo + Implements IFoo + + Protected Overrides Sub OnSave() Implements IFoo.Save + End Sub + + Protected Overrides Property MyProp As Integer = 6 Implements IFoo.Prop + +End Class", @" +public partial interface IFoo +{ + void Save(); + int Prop { get; set; } +} + +public abstract partial class BaseFoo +{ + protected virtual void OnSave() + { + } + + protected virtual int MyProp { get; set; } = 5; +} + +public partial class Foo : BaseFoo, IFoo +{ + + protected override void OnSave() + { + } + + void IFoo.Save() => OnSave(); + + protected override int MyProp { get; set; } = 6; + int IFoo.Prop { get => MyProp; set => MyProp = value; } + +}"); + } + + [Fact] + public async Task ExplicitInterfaceImplementationWhereOnlyOneInterfaceMemberIsRenamedAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @" +Public Interface IFoo + Sub Save() + Property A As Integer +End Interface + +Public Interface IBar + Sub OnSave() + Property B As Integer +End Interface + +Public Class Foo + Implements IFoo, IBar + + Public Overridable Sub Save() Implements IFoo.Save, IBar.OnSave + End Sub + + Public Overridable Property A As Integer Implements IFoo.A, IBar.B + +End Class", @" +public partial interface IFoo +{ + void Save(); + int A { get; set; } +} + +public partial interface IBar +{ + void OnSave(); + int B { get; set; } +} + +public partial class Foo : IFoo, IBar +{ + + public virtual void Save() + { + } + + void IFoo.Save() => Save(); + void IBar.OnSave() => Save(); + + public virtual int A { get; set; } + int IFoo.A { get => A; set => A = value; } + int IBar.B { get => A; set => A = value; } + +}"); + } + + [Fact] + public async Task ExplicitInterfaceImplementationWhereMemberShadowsBaseAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @" +Public Interface IFoo + Sub Save() + Property Prop As Integer +End Interface + +Public MustInherit Class BaseFoo + Public Overridable Sub OnSave() + End Sub + + Public Overridable Property MyProp As Integer = 5 +End Class + +Public Class Foo + Inherits BaseFoo + Implements IFoo + + Public Shadows Sub OnSave() Implements IFoo.Save + End Sub + + Public Shadows Property MyProp As Integer = 6 Implements IFoo.Prop + +End Class", @" +public partial interface IFoo +{ + void Save(); + int Prop { get; set; } +} + +public abstract partial class BaseFoo +{ + public virtual void OnSave() + { + } + + public virtual int MyProp { get; set; } = 5; +} + +public partial class Foo : BaseFoo, IFoo +{ + + public new void OnSave() + { + } + + void IFoo.Save() => OnSave(); + + public new int MyProp { get; set; } = 6; + int IFoo.Prop { get => MyProp; set => MyProp = value; } + +}"); + } + + [Fact] + public async Task PrivatePropertyAccessorBlocksImplementsMultipleInterfacesAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + Property ExplicitProp As Integer +End Interface + +Public Interface IBar + Property ExplicitProp As Integer +End Interface + +Public Class Foo + Implements IFoo, IBar + + Private Property ExplicitProp As Integer Implements IFoo.ExplicitProp, IBar.ExplicitProp ' Comment moves because this line gets split + Get + Return 5 + End Get + Set + End Set + End Property +End Class", @" +public partial interface IFoo +{ + int ExplicitProp { get; set; } +} + +public partial interface IBar +{ + int ExplicitProp { get; set; } +} + +public partial class Foo : IFoo, IBar +{ + + private int ExplicitProp + { + get + { + return 5; + } + set + { + } + } + + int IFoo.ExplicitProp { get => ExplicitProp; set => ExplicitProp = value; } + int IBar.ExplicitProp { get => ExplicitProp; set => ExplicitProp = value; } // Comment moves because this line gets split +}"); + } + + [Fact] + public async Task NonPublicImplementsInterfacesAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + Property FriendProp As Integer + Sub ProtectedSub() + Function PrivateFunc() As Integer + Sub ProtectedInternalSub() + Sub AbstractSub() +End Interface + +Public Interface IBar + Property FriendProp As Integer + Sub ProtectedSub() + Function PrivateFunc() As Integer + Sub ProtectedInternalSub() + Sub AbstractSub() +End Interface + +Public MustInherit Class BaseFoo + Implements IFoo, IBar + + Friend Overridable Property FriendProp As Integer Implements IFoo.FriendProp, IBar.FriendProp ' Comment moves because this line gets split + Get + Return 5 + End Get + Set + End Set + End Property + + Protected Sub ProtectedSub() Implements IFoo.ProtectedSub, IBar.ProtectedSub + End Sub + + Private Function PrivateFunc() As Integer Implements IFoo.PrivateFunc, IBar.PrivateFunc + End Function + + Protected Friend Overridable Sub ProtectedInternalSub() Implements IFoo.ProtectedInternalSub, IBar.ProtectedInternalSub + End Sub + + Protected MustOverride Sub AbstractSubRenamed() Implements IFoo.AbstractSub, IBar.AbstractSub +End Class + +Public Class Foo + Inherits BaseFoo + + Protected Friend Overrides Sub ProtectedInternalSub() + End Sub + + Protected Overrides Sub AbstractSubRenamed() + End Sub +End Class +", @" +public partial interface IFoo +{ + int FriendProp { get; set; } + void ProtectedSub(); + int PrivateFunc(); + void ProtectedInternalSub(); + void AbstractSub(); +} + +public partial interface IBar +{ + int FriendProp { get; set; } + void ProtectedSub(); + int PrivateFunc(); + void ProtectedInternalSub(); + void AbstractSub(); +} + +public abstract partial class BaseFoo : IFoo, IBar +{ + + internal virtual int FriendProp + { + get + { + return 5; + } + set + { + } + } + + int IFoo.FriendProp { get => FriendProp; set => FriendProp = value; } + int IBar.FriendProp { get => FriendProp; set => FriendProp = value; } // Comment moves because this line gets split + + protected void ProtectedSub() + { + } + + void IFoo.ProtectedSub() => ProtectedSub(); + void IBar.ProtectedSub() => ProtectedSub(); + + private int PrivateFunc() + { + return default; + } + + int IFoo.PrivateFunc() => PrivateFunc(); + int IBar.PrivateFunc() => PrivateFunc(); + + protected internal virtual void ProtectedInternalSub() + { + } + + void IFoo.ProtectedInternalSub() => ProtectedInternalSub(); + void IBar.ProtectedInternalSub() => ProtectedInternalSub(); + + protected abstract void AbstractSubRenamed(); + void IFoo.AbstractSub() => AbstractSubRenamed(); + void IBar.AbstractSub() => AbstractSubRenamed(); +} + +public partial class Foo : BaseFoo +{ + + protected internal override void ProtectedInternalSub() + { + } + + protected override void AbstractSubRenamed() + { + } +}"); + } + + [Fact] + public async Task ExplicitPropertyImplementationWithDirectAccessAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @" +Public Interface IFoo + Property ExplicitProp As Integer + ReadOnly Property ExplicitReadOnlyProp As Integer +End Interface + +Public Class Foo + Implements IFoo + + Property ExplicitPropRenamed As Integer Implements IFoo.ExplicitProp + ReadOnly Property ExplicitRenamedReadOnlyProp As Integer Implements IFoo.ExplicitReadOnlyProp + + Private Sub Consumer() + _ExplicitPropRenamed = 5 + _ExplicitRenamedReadOnlyProp = 10 + End Sub + +End Class", @" +public partial interface IFoo +{ + int ExplicitProp { get; set; } + int ExplicitReadOnlyProp { get; } +} + +public partial class Foo : IFoo +{ + + public int ExplicitPropRenamed { get; set; } + int IFoo.ExplicitProp { get => ExplicitPropRenamed; set => ExplicitPropRenamed = value; } + public int ExplicitRenamedReadOnlyProp { get; private set; } + int IFoo.ExplicitReadOnlyProp { get => ExplicitRenamedReadOnlyProp; } + + private void Consumer() + { + ExplicitPropRenamed = 5; + ExplicitRenamedReadOnlyProp = 10; + } + +}"); + } + + [Fact] + public async Task ReadonlyRenamedPropertyImplementsMultipleInterfacesAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + ReadOnly Property ExplicitProp As Integer +End Interface + +Public Interface IBar + ReadOnly Property ExplicitProp As Integer +End Interface + +Public Class Foo + Implements IFoo, IBar + + ReadOnly Property ExplicitPropRenamed As Integer Implements IFoo.ExplicitProp, IBar.ExplicitProp +End Class", @" +public partial interface IFoo +{ + int ExplicitProp { get; } +} + +public partial interface IBar +{ + int ExplicitProp { get; } +} + +public partial class Foo : IFoo, IBar +{ + + public int ExplicitPropRenamed { get; private set; } + int IFoo.ExplicitProp { get => ExplicitPropRenamed; } + int IBar.ExplicitProp { get => ExplicitPropRenamed; } +}"); + } + + [Fact] + public async Task WriteonlyPropertyImplementsMultipleInterfacesAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + WriteOnly Property ExplicitProp As Integer +End Interface + +Public Interface IBar + WriteOnly Property ExplicitProp As Integer +End Interface + +Public Class Foo + Implements IFoo, IBar + + WriteOnly Property ExplicitPropRenamed As Integer Implements IFoo.ExplicitProp, IBar.ExplicitProp ' Comment moves because this line gets split + Set + End Set + End Property +End Class", @" +public partial interface IFoo +{ + int ExplicitProp { set; } +} + +public partial interface IBar +{ + int ExplicitProp { set; } +} + +public partial class Foo : IFoo, IBar +{ + + public int ExplicitPropRenamed + { + set + { + } + } + + int IFoo.ExplicitProp { set => ExplicitPropRenamed = value; } + int IBar.ExplicitProp { set => ExplicitPropRenamed = value; } // Comment moves because this line gets split +}"); + } + + [Fact] + public async Task PrivateMethodAndParameterizedPropertyImplementsMultipleInterfacesAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + Property ExplicitProp(str As String) As Integer + Function ExplicitFunc(ByRef str2 As String, i2 As Integer) As Integer +End Interface + +Public Interface IBar + Property ExplicitProp(str As String) As Integer + Function ExplicitFunc(ByRef str2 As String, i2 As Integer) As Integer +End Interface + +Public Class Foo + Implements IFoo, IBar + + Private Function ExplicitFunc(ByRef str As String, i As Integer) As Integer Implements IFoo.ExplicitFunc, IBar.ExplicitFunc + Return 5 + End Function + + Private Property ExplicitProp(str As String) As Integer Implements IFoo.ExplicitProp, IBar.ExplicitProp + Get + Return 5 + End Get + Set(value As Integer) + End Set + End Property +End Class", @" +public partial interface IFoo +{ + int get_ExplicitProp(string str); + void set_ExplicitProp(string str, int value); + int ExplicitFunc(ref string str2, int i2); +} + +public partial interface IBar +{ + int get_ExplicitProp(string str); + void set_ExplicitProp(string str, int value); + int ExplicitFunc(ref string str2, int i2); +} + +public partial class Foo : IFoo, IBar +{ + + private int ExplicitFunc(ref string str, int i) + { + return 5; + } + + int IFoo.ExplicitFunc(ref string str, int i) => ExplicitFunc(ref str, i); + int IBar.ExplicitFunc(ref string str, int i) => ExplicitFunc(ref str, i); + + private int get_ExplicitProp(string str) + { + return 5; + } + private void set_ExplicitProp(string str, int value) + { + } + + int IFoo.get_ExplicitProp(string str) => get_ExplicitProp(str); + int IBar.get_ExplicitProp(string str) => get_ExplicitProp(str); + void IFoo.set_ExplicitProp(string str, int value) => set_ExplicitProp(str, value); + void IBar.set_ExplicitProp(string str, int value) => set_ExplicitProp(str, value); +}"); + } + + /// + /// + /// + /// + [Fact] + public async Task Issue444_InternalMemberDelegatingMethodAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Interface IFoo + Function FooDifferentName(ByRef str As String, i As Integer) As Integer +End Interface + +Friend Class Foo + Implements IFoo + + Function BarDifferentName(ByRef str As String, i As Integer) As Integer Implements IFoo.FooDifferentName + Return 4 + End Function +End Class", @" +public partial interface IFoo +{ + int FooDifferentName(ref string str, int i); +} + +internal partial class Foo : IFoo +{ + + public int BarDifferentName(ref string str, int i) + { + return 4; + } + + int IFoo.FooDifferentName(ref string str, int i) => BarDifferentName(ref str, i); +} +"); + } + + + + [Fact] + public async Task TestReadOnlyOrWriteOnlyPropertyImplementedByNormalPropertyAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @" +Interface IClass + ReadOnly Property ReadOnlyPropParam(i as Integer) As Integer + ReadOnly Property ReadOnlyProp As Integer + + WriteOnly Property WriteOnlyPropParam(i as Integer) As Integer + WriteOnly Property WriteOnlyProp As Integer +End Interface + +Class ChildClass + Implements IClass + + Public Overridable Property RenamedPropertyParam(i As Integer) As Integer Implements IClass.ReadOnlyPropParam + Get + Return 1 + End Get + Set + End Set + End Property + + Public Overridable Property RenamedReadOnlyProperty As Integer Implements IClass.ReadOnlyProp ' Comment moves because this line gets split + Get + Return 2 + End Get + Set + End Set + End Property + + Public Overridable Property RenamedWriteOnlyPropParam(i As Integer) As Integer Implements IClass.WriteOnlyPropParam + Get + Return 1 + End Get + Set + End Set + End Property + + Public Overridable Property RenamedWriteOnlyProperty As Integer Implements IClass.WriteOnlyProp ' Comment moves because this line gets split + Get + Return 2 + End Get + Set + End Set + End Property +End Class +", @" +internal partial interface IClass +{ + int get_ReadOnlyPropParam(int i); + int ReadOnlyProp { get; } + + void set_WriteOnlyPropParam(int i, int value); + int WriteOnlyProp { set; } +} + +internal partial class ChildClass : IClass +{ + + public virtual int get_RenamedPropertyParam(int i) + { + return 1; + } + public virtual void set_RenamedPropertyParam(int i, int value) + { + } + int IClass.get_ReadOnlyPropParam(int i) => get_RenamedPropertyParam(i); + + public virtual int RenamedReadOnlyProperty + { + get + { + return 2; + } + set + { + } + } + + int IClass.ReadOnlyProp { get => RenamedReadOnlyProperty; } // Comment moves because this line gets split + + public virtual int get_RenamedWriteOnlyPropParam(int i) + { + return 1; + } + public virtual void set_RenamedWriteOnlyPropParam(int i, int value) + { + } + void IClass.set_WriteOnlyPropParam(int i, int value) => set_RenamedWriteOnlyPropParam(i, value); + + public virtual int RenamedWriteOnlyProperty + { + get + { + return 2; + } + set + { + } + } + + int IClass.WriteOnlyProp { set => RenamedWriteOnlyProperty = value; } // Comment moves because this line gets split +}"); + } + + [Fact] + public async Task TestReadOnlyAndWriteOnlyParametrizedPropertyAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Interface IClass + ReadOnly Property ReadOnlyProp(i as Integer) As String + WriteOnly Property WriteOnlyProp(i as Integer) As String +End Interface + +Class ChildClass + Implements IClass + + Public Overridable ReadOnly Property ReadOnlyProp(i As Integer) As String Implements IClass.ReadOnlyProp + Get + Throw New NotImplementedException + End Get + End Property + + Public Overridable WriteOnly Property WriteOnlyProp(i As Integer) As String Implements IClass.WriteOnlyProp + Set + Throw New NotImplementedException + End Set + End Property +End Class +", @"using System; + +internal partial interface IClass +{ + string get_ReadOnlyProp(int i); + void set_WriteOnlyProp(int i, string value); +} + +internal partial class ChildClass : IClass +{ + + public virtual string get_ReadOnlyProp(int i) + { + throw new NotImplementedException(); + } + + public virtual void set_WriteOnlyProp(int i, string value) + { + throw new NotImplementedException(); + } +}"); + } + + [Fact] + public async Task TestExplicitInterfaceOfParametrizedPropertyAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Interface IClass + ReadOnly Property ReadOnlyPropToRename(i as Integer) As String + WriteOnly Property WriteOnlyPropToRename(i as Integer) As String + Property PropToRename(i as Integer) As String + + ReadOnly Property ReadOnlyPropNonPublic(i as Integer) As String + WriteOnly Property WriteOnlyPropNonPublic(i as Integer) As String + Property PropNonPublic(i as Integer) As String + + ReadOnly Property ReadOnlyPropToRenameNonPublic(i as Integer) As String + WriteOnly Property WriteOnlyPropToRenameNonPublic(i as Integer) As String + Property PropToRenameNonPublic(i as Integer) As String + +End Interface + +Class ChildClass + Implements IClass + + Public ReadOnly Property ReadOnlyPropRenamed(i As Integer) As String Implements IClass.ReadOnlyPropToRename + Get + Throw New NotImplementedException + End Get + End Property + + Public Overridable WriteOnly Property WriteOnlyPropRenamed(i As Integer) As String Implements IClass.WriteOnlyPropToRename + Set + Throw New NotImplementedException + End Set + End Property + + Public Overridable Property PropRenamed(i As Integer) As String Implements IClass.PropToRename + Get + Throw New NotImplementedException + End Get + Set + Throw New NotImplementedException + End Set + End Property + + Private ReadOnly Property ReadOnlyPropNonPublic(i As Integer) As String Implements IClass.ReadOnlyPropNonPublic + Get + Throw New NotImplementedException + End Get + End Property + + Protected Friend Overridable WriteOnly Property WriteOnlyPropNonPublic(i As Integer) As String Implements IClass.WriteOnlyPropNonPublic + Set + Throw New NotImplementedException + End Set + End Property + + Friend Overridable Property PropNonPublic(i As Integer) As String Implements IClass.PropNonPublic + Get + Throw New NotImplementedException + End Get + Set + Throw New NotImplementedException + End Set + End Property + + Protected Friend Overridable ReadOnly Property ReadOnlyPropRenamedNonPublic(i As Integer) As String Implements IClass.ReadOnlyPropToRenameNonPublic + Get + Throw New NotImplementedException + End Get + End Property + + Private WriteOnly Property WriteOnlyPropRenamedNonPublic(i As Integer) As String Implements IClass.WriteOnlyPropToRenameNonPublic + Set + Throw New NotImplementedException + End Set + End Property + + Friend Overridable Property PropToRenameNonPublic(i As Integer) As String Implements IClass.PropToRenameNonPublic + Get + Throw New NotImplementedException + End Get + Set + Throw New NotImplementedException + End Set + End Property +End Class +", @"using System; + +internal partial interface IClass +{ + string get_ReadOnlyPropToRename(int i); + void set_WriteOnlyPropToRename(int i, string value); + string get_PropToRename(int i); + void set_PropToRename(int i, string value); + + string get_ReadOnlyPropNonPublic(int i); + void set_WriteOnlyPropNonPublic(int i, string value); + string get_PropNonPublic(int i); + void set_PropNonPublic(int i, string value); + + string get_ReadOnlyPropToRenameNonPublic(int i); + void set_WriteOnlyPropToRenameNonPublic(int i, string value); + string get_PropToRenameNonPublic(int i); + void set_PropToRenameNonPublic(int i, string value); + +} + +internal partial class ChildClass : IClass +{ + + public string get_ReadOnlyPropRenamed(int i) + { + throw new NotImplementedException(); + } + string IClass.get_ReadOnlyPropToRename(int i) => get_ReadOnlyPropRenamed(i); + + public virtual void set_WriteOnlyPropRenamed(int i, string value) + { + throw new NotImplementedException(); + } + void IClass.set_WriteOnlyPropToRename(int i, string value) => set_WriteOnlyPropRenamed(i, value); + + public virtual string get_PropRenamed(int i) + { + throw new NotImplementedException(); + } + public virtual void set_PropRenamed(int i, string value) + { + throw new NotImplementedException(); + } + + string IClass.get_PropToRename(int i) => get_PropRenamed(i); + void IClass.set_PropToRename(int i, string value) => set_PropRenamed(i, value); + + private string get_ReadOnlyPropNonPublic(int i) + { + throw new NotImplementedException(); + } + string IClass.get_ReadOnlyPropNonPublic(int i) => get_ReadOnlyPropNonPublic(i); + + protected internal virtual void set_WriteOnlyPropNonPublic(int i, string value) + { + throw new NotImplementedException(); + } + void IClass.set_WriteOnlyPropNonPublic(int i, string value) => set_WriteOnlyPropNonPublic(i, value); + + internal virtual string get_PropNonPublic(int i) + { + throw new NotImplementedException(); + } + internal virtual void set_PropNonPublic(int i, string value) + { + throw new NotImplementedException(); + } + + string IClass.get_PropNonPublic(int i) => get_PropNonPublic(i); + void IClass.set_PropNonPublic(int i, string value) => set_PropNonPublic(i, value); + + protected internal virtual string get_ReadOnlyPropRenamedNonPublic(int i) + { + throw new NotImplementedException(); + } + string IClass.get_ReadOnlyPropToRenameNonPublic(int i) => get_ReadOnlyPropRenamedNonPublic(i); + + private void set_WriteOnlyPropRenamedNonPublic(int i, string value) + { + throw new NotImplementedException(); + } + void IClass.set_WriteOnlyPropToRenameNonPublic(int i, string value) => set_WriteOnlyPropRenamedNonPublic(i, value); + + internal virtual string get_PropToRenameNonPublic(int i) + { + throw new NotImplementedException(); + } + internal virtual void set_PropToRenameNonPublic(int i, string value) + { + throw new NotImplementedException(); + } + + string IClass.get_PropToRenameNonPublic(int i) => get_PropToRenameNonPublic(i); + void IClass.set_PropToRenameNonPublic(int i, string value) => set_PropToRenameNonPublic(i, value); +}"); + } +} \ No newline at end of file diff --git a/Tests/CSharp/MemberTests/MemberTests.cs b/Tests/CSharp/MemberTests/MemberTests.cs index 0ac91a1a..8b7c94ce 100644 --- a/Tests/CSharp/MemberTests/MemberTests.cs +++ b/Tests/CSharp/MemberTests/MemberTests.cs @@ -6,6 +6,7 @@ namespace ICSharpCode.CodeConverter.Tests.CSharp.MemberTests; public class MemberTests : ConverterTestBase { + [Fact] public async Task TestFieldAsync() { @@ -51,39 +52,6 @@ internal static partial class TestModule }"); } - [Fact] - public async Task TestConstructorVisibilityAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Class Class1 - Sub New(x As Boolean) - End Sub -End Class", @" -internal partial class Class1 -{ - public Class1(bool x) - { - } -}"); - } - - [Fact] - public async Task TestModuleConstructorAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Module Module1 - Sub New() - Dim someValue As Integer = 0 - End Sub -End Module", @" -internal static partial class Module1 -{ - static Module1() - { - int someValue = 0; - } -}"); - } - [Fact] public async Task TestDeclareMethodVisibilityInModuleAsync() { @@ -344,102 +312,6 @@ public bool TryGet(out List strs) }"); } - [Fact] - public async Task TestHoistedOutParameterAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Class ClassWithProperties - Public Property Property1 As String -End Class - -Public Class VisualBasicClass - Public Sub New() - Dim x As New Dictionary(Of String, String)() - Dim y As New ClassWithProperties() - - If (x.TryGetValue(""x"", y.Property1)) Then - Debug.Print(y.Property1) - End If - End Sub -End Class", @"using System.Collections.Generic; -using System.Diagnostics; - -public partial class ClassWithProperties -{ - public string Property1 { get; set; } -} - -public partial class VisualBasicClass -{ - public VisualBasicClass() - { - var x = new Dictionary(); - var y = new ClassWithProperties(); - - bool localTryGetValue() { string argvalue = y.Property1; var ret = x.TryGetValue(""x"", out argvalue); y.Property1 = argvalue; return ret; } - - if (localTryGetValue()) - { - Debug.Print(y.Property1); - } - } -}"); - } - - [Fact] - public async Task TestHoistedOutParameterLambdaUsingByRefParameterAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Class SomeClass - Sub S(Optional ByRef x As Integer = -1) - Dim i As Integer = 0 - If F1(x, i) Then - ElseIf F2(x, i) Then - ElseIf F3(x, i) Then - End If - End Sub - - Function F1(x As Integer, ByRef o As Object) As Boolean : End Function - Function F2(ByRef x As Integer, ByRef o As Object) As Boolean : End Function - Function F3(ByRef x As Object, ByRef o As Object) As Boolean : End Function -End Class", @"using System.Runtime.InteropServices; -using Microsoft.VisualBasic.CompilerServices; // Install-Package Microsoft.VisualBasic - -public partial class SomeClass -{ - public void S([Optional, DefaultParameterValue(-1)] ref int x) - { - int i = 0; - bool localF1(ref int x) { object argo = i; var ret = F1(x, ref argo); i = Conversions.ToInteger(argo); return ret; } - bool localF2(ref int x) { object argo1 = i; var ret = F2(ref x, ref argo1); i = Conversions.ToInteger(argo1); return ret; } - bool localF3(ref int x) { object argx = x; object argo2 = i; var ret = F3(ref argx, ref argo2); x = Conversions.ToInteger(argx); i = Conversions.ToInteger(argo2); return ret; } - - if (localF1(ref x)) - { - } - else if (localF2(ref x)) - { - } - else if (localF3(ref x)) - { - } - } - - public bool F1(int x, ref object o) - { - return default; - } - public bool F2(ref int x, ref object o) - { - return default; - } - public bool F3(ref object x, ref object o) - { - return default; - } -}"); - } - [Fact] public async Task TestMethodWithReturnTypeAsync() { @@ -579,113 +451,6 @@ public override string WriteOnlyProp }"); } - [Fact] - public async Task TestReadOnlyOrWriteOnlyPropertyImplementedByNormalPropertyAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @" -Interface IClass - ReadOnly Property ReadOnlyPropParam(i as Integer) As Integer - ReadOnly Property ReadOnlyProp As Integer - - WriteOnly Property WriteOnlyPropParam(i as Integer) As Integer - WriteOnly Property WriteOnlyProp As Integer -End Interface - -Class ChildClass - Implements IClass - - Public Overridable Property RenamedPropertyParam(i As Integer) As Integer Implements IClass.ReadOnlyPropParam - Get - Return 1 - End Get - Set - End Set - End Property - - Public Overridable Property RenamedReadOnlyProperty As Integer Implements IClass.ReadOnlyProp ' Comment moves because this line gets split - Get - Return 2 - End Get - Set - End Set - End Property - - Public Overridable Property RenamedWriteOnlyPropParam(i As Integer) As Integer Implements IClass.WriteOnlyPropParam - Get - Return 1 - End Get - Set - End Set - End Property - - Public Overridable Property RenamedWriteOnlyProperty As Integer Implements IClass.WriteOnlyProp ' Comment moves because this line gets split - Get - Return 2 - End Get - Set - End Set - End Property -End Class -", @" -internal partial interface IClass -{ - int get_ReadOnlyPropParam(int i); - int ReadOnlyProp { get; } - - void set_WriteOnlyPropParam(int i, int value); - int WriteOnlyProp { set; } -} - -internal partial class ChildClass : IClass -{ - - public virtual int get_RenamedPropertyParam(int i) - { - return 1; - } - public virtual void set_RenamedPropertyParam(int i, int value) - { - } - int IClass.get_ReadOnlyPropParam(int i) => get_RenamedPropertyParam(i); - - public virtual int RenamedReadOnlyProperty - { - get - { - return 2; - } - set - { - } - } - - int IClass.ReadOnlyProp { get => RenamedReadOnlyProperty; } // Comment moves because this line gets split - - public virtual int get_RenamedWriteOnlyPropParam(int i) - { - return 1; - } - public virtual void set_RenamedWriteOnlyPropParam(int i, int value) - { - } - void IClass.set_WriteOnlyPropParam(int i, int value) => set_RenamedWriteOnlyPropParam(i, value); - - public virtual int RenamedWriteOnlyProperty - { - get - { - return 2; - } - set - { - } - } - - int IClass.WriteOnlyProp { set => RenamedWriteOnlyProperty = value; } // Comment moves because this line gets split -}"); - } - [Fact] public async Task SetterProperty1053Async() { @@ -762,3115 +527,632 @@ public string Prop } [Fact] - public async Task TestReadOnlyAndWriteOnlyParametrizedPropertyAsync() + public async Task TestSealedMethodAsync() { await TestConversionVisualBasicToCSharpAsync( - @"Interface IClass - ReadOnly Property ReadOnlyProp(i as Integer) As String - WriteOnly Property WriteOnlyProp(i as Integer) As String -End Interface - -Class ChildClass - Implements IClass + @"Class TestClass + Public NotOverridable Sub TestMethod(Of T As {Class, New}, T2 As Structure, T3)( ByRef argument As T, ByRef argument2 As T2, ByVal argument3 As T3) + argument = Nothing + argument2 = Nothing + argument3 = Nothing + End Sub +End Class", @" +internal partial class TestClass +{ + public sealed void TestMethod(out T argument, ref T2 argument2, T3 argument3) + where T : class, new() + where T2 : struct + { + argument = null; + argument2 = default; + argument3 = default; + } +} +1 source compilation errors: +BC31088: 'NotOverridable' cannot be specified for methods that do not override another method. +1 target compilation errors: +CS0238: 'TestClass.TestMethod(out T, ref T2, T3)' cannot be sealed because it is not an override"); + } - Public Overridable ReadOnly Property ReadOnlyProp(i As Integer) As String Implements IClass.ReadOnlyProp - Get - Throw New NotImplementedException - End Get - End Property + [Fact] + public async Task TestShadowedMethodAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Class TestClass + Public Sub TestMethod() + End Sub - Public Overridable WriteOnly Property WriteOnlyProp(i As Integer) As String Implements IClass.WriteOnlyProp - Set - Throw New NotImplementedException - End Set - End Property + Public Sub TestMethod(i as Integer) + End Sub End Class -", @"using System; -internal partial interface IClass +Class TestSubclass + Inherits TestClass + + Public Shadows Sub TestMethod() + ' Not possible: TestMethod(3) + System.Console.WriteLine(""New implementation"") + End Sub +End Class", @"using System; + +internal partial class TestClass { - string get_ReadOnlyProp(int i); - void set_WriteOnlyProp(int i, string value); + public void TestMethod() + { + } + + public void TestMethod(int i) + { + } } -internal partial class ChildClass : IClass +internal partial class TestSubclass : TestClass { - public virtual string get_ReadOnlyProp(int i) + public new void TestMethod() { - throw new NotImplementedException(); + // Not possible: TestMethod(3) + Console.WriteLine(""New implementation""); + } +}"); } - public virtual void set_WriteOnlyProp(int i, string value) + + [Fact] + public async Task TestDestructorAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Class TestClass + Protected Overrides Sub Finalize() + End Sub +End Class", @" +internal partial class TestClass +{ + ~TestClass() { - throw new NotImplementedException(); } }"); } [Fact] - public async Task TestExplicitInterfaceOfParametrizedPropertyAsync() + public async Task Issue681_OverloadsOverridesPropertyAsync() { await TestConversionVisualBasicToCSharpAsync( - @"Interface IClass - ReadOnly Property ReadOnlyPropToRename(i as Integer) As String - WriteOnly Property WriteOnlyPropToRename(i as Integer) As String - Property PropToRename(i as Integer) As String - - ReadOnly Property ReadOnlyPropNonPublic(i as Integer) As String - WriteOnly Property WriteOnlyPropNonPublic(i as Integer) As String - Property PropNonPublic(i as Integer) As String - - ReadOnly Property ReadOnlyPropToRenameNonPublic(i as Integer) As String - WriteOnly Property WriteOnlyPropToRenameNonPublic(i as Integer) As String - Property PropToRenameNonPublic(i as Integer) As String - -End Interface - -Class ChildClass - Implements IClass + @"Public Class C + Inherits B - Public ReadOnly Property ReadOnlyPropRenamed(i As Integer) As String Implements IClass.ReadOnlyPropToRename + Public ReadOnly Overloads Overrides Property X() Get - Throw New NotImplementedException + Return Nothing End Get End Property +End Class - Public Overridable WriteOnly Property WriteOnlyPropRenamed(i As Integer) As String Implements IClass.WriteOnlyPropToRename - Set - Throw New NotImplementedException - End Set - End Property - - Public Overridable Property PropRenamed(i As Integer) As String Implements IClass.PropToRename +Public Class B + Public ReadOnly Overridable Property X() Get - Throw New NotImplementedException + Return Nothing End Get - Set - Throw New NotImplementedException - End Set End Property +End Class", @"public partial class C : B +{ - Private ReadOnly Property ReadOnlyPropNonPublic(i As Integer) As String Implements IClass.ReadOnlyPropNonPublic - Get - Throw New NotImplementedException - End Get - End Property + public override object X + { + get + { + return null; + } + } +} - Protected Friend Overridable WriteOnly Property WriteOnlyPropNonPublic(i As Integer) As String Implements IClass.WriteOnlyPropNonPublic - Set - Throw New NotImplementedException - End Set - End Property - - Friend Overridable Property PropNonPublic(i As Integer) As String Implements IClass.PropNonPublic - Get - Throw New NotImplementedException - End Get - Set - Throw New NotImplementedException - End Set - End Property +public partial class B +{ + public virtual object X + { + get + { + return null; + } + } +}"); + } - Protected Friend Overridable ReadOnly Property ReadOnlyPropRenamedNonPublic(i As Integer) As String Implements IClass.ReadOnlyPropToRenameNonPublic - Get - Throw New NotImplementedException - End Get - End Property + [Fact] + public async Task PartialFriendClassWithOverloadsAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Partial Friend MustInherit Class TestClass1 + Public Shared Sub CreateStatic() + End Sub - Private WriteOnly Property WriteOnlyPropRenamedNonPublic(i As Integer) As String Implements IClass.WriteOnlyPropToRenameNonPublic - Set - Throw New NotImplementedException - End Set - End Property + Public Overloads Sub CreateInstance() + End Sub - Friend Overridable Property PropToRenameNonPublic(i As Integer) As String Implements IClass.PropToRenameNonPublic - Get - Throw New NotImplementedException - End Get - Set - Throw New NotImplementedException - End Set - End Property -End Class -", @"using System; + Public Overloads Sub CreateInstance(o As Object) + End Sub -internal partial interface IClass -{ - string get_ReadOnlyPropToRename(int i); - void set_WriteOnlyPropToRename(int i, string value); - string get_PropToRename(int i); - void set_PropToRename(int i, string value); + Public MustOverride Sub CreateAbstractInstance() - string get_ReadOnlyPropNonPublic(int i); - void set_WriteOnlyPropNonPublic(int i, string value); - string get_PropNonPublic(int i); - void set_PropNonPublic(int i, string value); + Public Overridable Sub CreateVirtualInstance() + End Sub +End Class - string get_ReadOnlyPropToRenameNonPublic(int i); - void set_WriteOnlyPropToRenameNonPublic(int i, string value); - string get_PropToRenameNonPublic(int i); - void set_PropToRenameNonPublic(int i, string value); +Friend Class TestClass2 + Inherits TestClass1 + Public Overloads Shared Sub CreateStatic() + End Sub -} + Public Overloads Sub CreateInstance() + End Sub -internal partial class ChildClass : IClass + Public Overrides Sub CreateAbstractInstance() + End Sub + + Public Overloads Sub CreateVirtualInstance(o As Object) + End Sub +End Class", + @" +internal abstract partial class TestClass1 { - - public string get_ReadOnlyPropRenamed(int i) + public static void CreateStatic() { - throw new NotImplementedException(); } - string IClass.get_ReadOnlyPropToRename(int i) => get_ReadOnlyPropRenamed(i); - public virtual void set_WriteOnlyPropRenamed(int i, string value) + public void CreateInstance() { - throw new NotImplementedException(); } - void IClass.set_WriteOnlyPropToRename(int i, string value) => set_WriteOnlyPropRenamed(i, value); - public virtual string get_PropRenamed(int i) - { - throw new NotImplementedException(); - } - public virtual void set_PropRenamed(int i, string value) + public void CreateInstance(object o) { - throw new NotImplementedException(); } - string IClass.get_PropToRename(int i) => get_PropRenamed(i); - void IClass.set_PropToRename(int i, string value) => set_PropRenamed(i, value); + public abstract void CreateAbstractInstance(); - private string get_ReadOnlyPropNonPublic(int i) + public virtual void CreateVirtualInstance() { - throw new NotImplementedException(); } - string IClass.get_ReadOnlyPropNonPublic(int i) => get_ReadOnlyPropNonPublic(i); +} - protected internal virtual void set_WriteOnlyPropNonPublic(int i, string value) +internal partial class TestClass2 : TestClass1 +{ + public new static void CreateStatic() { - throw new NotImplementedException(); } - void IClass.set_WriteOnlyPropNonPublic(int i, string value) => set_WriteOnlyPropNonPublic(i, value); - internal virtual string get_PropNonPublic(int i) - { - throw new NotImplementedException(); - } - internal virtual void set_PropNonPublic(int i, string value) + public new void CreateInstance() { - throw new NotImplementedException(); } - string IClass.get_PropNonPublic(int i) => get_PropNonPublic(i); - void IClass.set_PropNonPublic(int i, string value) => set_PropNonPublic(i, value); - - protected internal virtual string get_ReadOnlyPropRenamedNonPublic(int i) + public override void CreateAbstractInstance() { - throw new NotImplementedException(); } - string IClass.get_ReadOnlyPropToRenameNonPublic(int i) => get_ReadOnlyPropRenamedNonPublic(i); - private void set_WriteOnlyPropRenamedNonPublic(int i, string value) + public void CreateVirtualInstance(object o) { - throw new NotImplementedException(); } - void IClass.set_WriteOnlyPropToRenameNonPublic(int i, string value) => set_WriteOnlyPropRenamedNonPublic(i, value); +}"); + } - internal virtual string get_PropToRenameNonPublic(int i) + [Fact] + public async Task ClassWithGloballyQualifiedAttributeAsync() { - throw new NotImplementedException(); + await TestConversionVisualBasicToCSharpAsync(@" +Class TestClass +End Class", @"using System.Diagnostics; + +[DebuggerDisplay(""Hello World"")] +internal partial class TestClass +{ +}"); } - internal virtual void set_PropToRenameNonPublic(int i, string value) + + [Fact] + public async Task FieldWithAttributeAsync() { - throw new NotImplementedException(); - } + await TestConversionVisualBasicToCSharpAsync(@"Class TestClass + + Private Shared First As Integer +End Class", @"using System; - string IClass.get_PropToRenameNonPublic(int i) => get_PropToRenameNonPublic(i); - void IClass.set_PropToRenameNonPublic(int i, string value) => set_PropToRenameNonPublic(i, value); +internal partial class TestClass +{ + [ThreadStatic] + private static int First; }"); } [Fact] - public async Task TestSealedMethodAsync() + public async Task FieldWithNonStaticInitializerAsync() { - await TestConversionVisualBasicToCSharpAsync( - @"Class TestClass - Public NotOverridable Sub TestMethod(Of T As {Class, New}, T2 As Structure, T3)( ByRef argument As T, ByRef argument2 As T2, ByVal argument3 As T3) - argument = Nothing - argument2 = Nothing - argument3 = Nothing - End Sub + await TestConversionVisualBasicToCSharpAsync(@"Public Class A + Private x As Integer = 2 + Private y(x) As Integer End Class", @" -internal partial class TestClass +public partial class A { - public sealed void TestMethod(out T argument, ref T2 argument2, T3 argument3) - where T : class, new() - where T2 : struct + private int x = 2; + private int[] y; + + public A() { - argument = null; - argument2 = default; - argument3 = default; + y = new int[x + 1]; } -} -1 source compilation errors: -BC31088: 'NotOverridable' cannot be specified for methods that do not override another method. -1 target compilation errors: -CS0238: 'TestClass.TestMethod(out T, ref T2, T3)' cannot be sealed because it is not an override"); +}"); } [Fact] - public async Task TestShadowedMethodAsync() + public async Task FieldWithInstanceOperationOfDifferingTypeAsync() { - await TestConversionVisualBasicToCSharpAsync( - @"Class TestClass - Public Sub TestMethod() - End Sub + await TestConversionVisualBasicToCSharpAsync(@"Public Class DoesNotNeedConstructor + Private ReadOnly ClassVariable1 As New ParallelOptions With {.MaxDegreeOfParallelism = 5} +End Class", @"using System.Threading.Tasks; - Public Sub TestMethod(i as Integer) - End Sub -End Class +public partial class DoesNotNeedConstructor +{ + private readonly ParallelOptions ClassVariable1 = new ParallelOptions() { MaxDegreeOfParallelism = 5 }; +}"); + } -Class TestSubclass - Inherits TestClass + [Fact] + public async Task Issue281FieldWithNonStaticLambdaInitializerAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Imports System.IO - Public Shadows Sub TestMethod() - ' Not possible: TestMethod(3) - System.Console.WriteLine(""New implementation"") +Public Class Issue281 + Private lambda As System.Delegate = New ErrorEventHandler(Sub(a, b) Len(0)) + Private nonShared As System.Delegate = New ErrorEventHandler(AddressOf OnError) + + Sub OnError(s As Object, e As ErrorEventArgs) End Sub End Class", @"using System; +using System.IO; +using Microsoft.VisualBasic; // Install-Package Microsoft.VisualBasic -internal partial class TestClass +public partial class Issue281 { - public void TestMethod() - { - } + private Delegate lambda = new ErrorEventHandler((a, b) => Strings.Len(0)); + private Delegate nonShared; - public void TestMethod(int i) + public Issue281() { + nonShared = new ErrorEventHandler(OnError); } -} -internal partial class TestSubclass : TestClass -{ - - public new void TestMethod() + public void OnError(object s, ErrorEventArgs e) { - // Not possible: TestMethod(3) - Console.WriteLine(""New implementation""); } }"); } [Fact] - public async Task TestExtensionMethodAsync() + public async Task ParamArrayAsync() { - await TestConversionVisualBasicToCSharpAsync( - @"Module TestClass - - Sub TestMethod(ByVal str As String) - End Sub - - - Sub TestMethod2Parameters(ByVal str As String, other As String) + await TestConversionVisualBasicToCSharpAsync(@"Class TestClass + Private Sub SomeBools(ParamArray anyName As Boolean()) End Sub -End Module", @" -internal static partial class TestClass +End Class", @" +internal partial class TestClass { - public static void TestMethod(this string str) - { - } - - public static void TestMethod2Parameters(this string str, string other) + private void SomeBools(params bool[] anyName) { } }"); } [Fact] - public async Task TestExtensionMethodWithExistingImportAsync() + public async Task ParamNamedBoolAsync() { - await TestConversionVisualBasicToCSharpAsync( - @"Imports System.Runtime.CompilerServices ' Removed by simplifier - -Module TestClass - - Sub TestMethod(ByVal str As String) + await TestConversionVisualBasicToCSharpAsync(@"Class TestClass + Private Sub SomeBools(ParamArray bool As Boolean()) End Sub -End Module", @" -internal static partial class TestClass +End Class", @" +internal partial class TestClass { - public static void TestMethod(this string str) + private void SomeBools(params bool[] @bool) { } }"); } [Fact] - public async Task TestRefExtensionMethodAsync() + public async Task MethodWithNameArrayParameterAsync() { await TestConversionVisualBasicToCSharpAsync( - @"Imports System -Imports System.Runtime.CompilerServices ' Removed since the extension attribute is removed - -Public Module MyExtensions - - Public Sub Add(Of T)(ByRef arr As T(), item As T) - Array.Resize(arr, arr.Length + 1) - arr(arr.Length - 1) = item - End Sub -End Module - -Public Module UsagePoint - Public Sub Main() - Dim arr = New Integer() {1, 2, 3} - arr.Add(4) - System.Console.WriteLine(arr(3)) + @"Class TestClass + Public Sub DoNothing(ByVal strs() As String) + Dim moreStrs() As String End Sub -End Module", @"using System; - -public static partial class MyExtensions -{ - public static void Add(ref T[] arr, T item) - { - Array.Resize(ref arr, arr.Length + 1); - arr[arr.Length - 1] = item; - } -} - -public static partial class UsagePoint +End Class", + @" +internal partial class TestClass { - public static void Main() + public void DoNothing(string[] strs) { - int[] arr = new int[] { 1, 2, 3 }; - MyExtensions.Add(ref arr, 4); - Console.WriteLine(arr[3]); + string[] moreStrs; } }"); } [Fact] - public async Task TestExtensionWithinExtendedTypeAsync() + public async Task UntypedParametersAsync() { await TestConversionVisualBasicToCSharpAsync( - @"Module Extensions - - Sub TestExtension(extendedClass As ExtendedClass) + @"Class TestClass + Public Sub DoNothing(obj, objs()) End Sub -End Module - -Class ExtendedClass - Sub TestExtensionConsumer() - TestExtension() - End Sub -End Class", @" -internal static partial class Extensions -{ - public static void TestExtension(this ExtendedClass extendedClass) - { - } -} - -internal partial class ExtendedClass +End Class", + @" +internal partial class TestClass { - public void TestExtensionConsumer() + public void DoNothing(object obj, object[] objs) { - this.TestExtension(); } }"); } [Fact] - public async Task TestExtensionWithinTypeDerivedFromExtendedTypeAsync() + public async Task PartialClassAsync() { await TestConversionVisualBasicToCSharpAsync( - @"Module Extensions - - Sub TestExtension(extendedClass As ExtendedClass) + @"Public Partial Class TestClass + Private Sub DoNothing() + Console.WriteLine(""Hello"") End Sub -End Module - -Class ExtendedClass End Class -Class DerivedClass - Inherits ExtendedClass - - Sub TestExtensionConsumer() - TestExtension() - End Sub -End Class", @" -internal static partial class Extensions -{ - public static void TestExtension(this ExtendedClass extendedClass) - { - } -} - -internal partial class ExtendedClass -{ -} - -internal partial class DerivedClass : ExtendedClass -{ - - public void TestExtensionConsumer() - { - this.TestExtension(); - } -}"); - } - - [Fact] - public async Task TestExtensionWithinNestedExtendedTypeAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Module Extensions - - Sub TestExtension(extendedClass As NestingClass.ExtendedClass) - End Sub -End Module - -Class NestingClass - Class ExtendedClass - Sub TestExtensionConsumer() - TestExtension() - End Sub - End Class -End Class", @" -internal static partial class Extensions -{ - public static void TestExtension(this NestingClass.ExtendedClass extendedClass) - { - } -} - -internal partial class NestingClass -{ - public partial class ExtendedClass - { - public void TestExtensionConsumer() - { - this.TestExtension(); - } - } -}"); - } - - [Fact] - public async Task TestExtensionWithMeWithinExtendedTypeAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Module Extensions - - Sub TestExtension(extendedClass As ExtendedClass) - End Sub -End Module - -Class ExtendedClass - Sub TestExtensionConsumer() - Me.TestExtension() - End Sub -End Class", @" -internal static partial class Extensions -{ - public static void TestExtension(this ExtendedClass extendedClass) - { - } -} - -internal partial class ExtendedClass -{ - public void TestExtensionConsumer() - { - this.TestExtension(); - } -}"); - } - - [Fact] - public async Task TestConstructorAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Class TestClass(Of T As {Class, New}, T2 As Structure, T3) - Public Sub New( ByRef argument As T, ByRef argument2 As T2, ByVal argument3 As T3) - End Sub -End Class", @" -internal partial class TestClass - where T : class, new() - where T2 : struct -{ - public TestClass(out T argument, ref T2 argument2, T3 argument3) - { - } -} -1 target compilation errors: -CS0177: The out parameter 'argument' must be assigned to before control leaves the current method"); - } - - [Fact] - public async Task TestConstructorWithImplicitPublicAccessibilityAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Sub New() -End Sub", @"public SurroundingClass() -{ -}"); - } - - [Fact] - public async Task TestStaticConstructorAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Shared Sub New() -End Sub", @"static SurroundingClass() -{ -}"); - } - - [Fact] - public async Task TestDestructorAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Class TestClass - Protected Overrides Sub Finalize() - End Sub -End Class", @" -internal partial class TestClass -{ - ~TestClass() - { - } -}"); - } - - [Fact] - public async Task Issue681_OverloadsOverridesPropertyAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Class C - Inherits B - - Public ReadOnly Overloads Overrides Property X() - Get - Return Nothing - End Get - End Property -End Class - -Public Class B - Public ReadOnly Overridable Property X() - Get - Return Nothing - End Get - End Property -End Class", @"public partial class C : B -{ - - public override object X - { - get - { - return null; - } - } -} - -public partial class B -{ - public virtual object X - { - get - { - return null; - } - } -}"); - } - - [Fact] - public async Task PartialFriendClassWithOverloadsAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Partial Friend MustInherit Class TestClass1 - Public Shared Sub CreateStatic() - End Sub - - Public Overloads Sub CreateInstance() - End Sub - - Public Overloads Sub CreateInstance(o As Object) - End Sub - - Public MustOverride Sub CreateAbstractInstance() - - Public Overridable Sub CreateVirtualInstance() - End Sub -End Class - -Friend Class TestClass2 - Inherits TestClass1 - Public Overloads Shared Sub CreateStatic() - End Sub - - Public Overloads Sub CreateInstance() - End Sub - - Public Overrides Sub CreateAbstractInstance() - End Sub - - Public Overloads Sub CreateVirtualInstance(o As Object) +Class TestClass ' VB doesn't require partial here (when just a single class omits it) + Partial Private Sub DoNothing() End Sub End Class", - @" -internal abstract partial class TestClass1 -{ - public static void CreateStatic() - { - } - - public void CreateInstance() - { - } - - public void CreateInstance(object o) - { - } - - public abstract void CreateAbstractInstance(); - - public virtual void CreateVirtualInstance() - { - } -} - -internal partial class TestClass2 : TestClass1 -{ - public new static void CreateStatic() - { - } - - public new void CreateInstance() - { - } - - public override void CreateAbstractInstance() - { - } - - public void CreateVirtualInstance(object o) - { - } -}"); - } - - [Fact] - public async Task ClassWithGloballyQualifiedAttributeAsync() - { - await TestConversionVisualBasicToCSharpAsync(@" -Class TestClass -End Class", @"using System.Diagnostics; - -[DebuggerDisplay(""Hello World"")] -internal partial class TestClass -{ -}"); - } - - [Fact] - public async Task FieldWithAttributeAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Class TestClass - - Private Shared First As Integer -End Class", @"using System; - -internal partial class TestClass -{ - [ThreadStatic] - private static int First; -}"); - } - - [Fact] - public async Task FieldWithNonStaticInitializerAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Class A - Private x As Integer = 2 - Private y(x) As Integer -End Class", @" -public partial class A -{ - private int x = 2; - private int[] y; - - public A() - { - y = new int[x + 1]; - } -}"); - } - - [Fact] - public async Task FieldWithInstanceOperationOfDifferingTypeAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Class DoesNotNeedConstructor - Private ReadOnly ClassVariable1 As New ParallelOptions With {.MaxDegreeOfParallelism = 5} -End Class", @"using System.Threading.Tasks; - -public partial class DoesNotNeedConstructor -{ - private readonly ParallelOptions ClassVariable1 = new ParallelOptions() { MaxDegreeOfParallelism = 5 }; -}"); - } - - [Fact] - public async Task Issue281FieldWithNonStaticLambdaInitializerAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Imports System.IO - -Public Class Issue281 - Private lambda As System.Delegate = New ErrorEventHandler(Sub(a, b) Len(0)) - Private nonShared As System.Delegate = New ErrorEventHandler(AddressOf OnError) - - Sub OnError(s As Object, e As ErrorEventArgs) - End Sub -End Class", @"using System; -using System.IO; -using Microsoft.VisualBasic; // Install-Package Microsoft.VisualBasic - -public partial class Issue281 -{ - private Delegate lambda = new ErrorEventHandler((a, b) => Strings.Len(0)); - private Delegate nonShared; - - public Issue281() - { - nonShared = new ErrorEventHandler(OnError); - } - - public void OnError(object s, ErrorEventArgs e) - { - } -}"); - } - - [Fact] - public async Task ParamArrayAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Class TestClass - Private Sub SomeBools(ParamArray anyName As Boolean()) - End Sub -End Class", @" -internal partial class TestClass -{ - private void SomeBools(params bool[] anyName) - { - } -}"); - } - - [Fact] - public async Task ParamNamedBoolAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Class TestClass - Private Sub SomeBools(ParamArray bool As Boolean()) - End Sub -End Class", @" -internal partial class TestClass -{ - private void SomeBools(params bool[] @bool) - { - } -}"); - } - - [Fact] - public async Task MethodWithNameArrayParameterAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Class TestClass - Public Sub DoNothing(ByVal strs() As String) - Dim moreStrs() As String - End Sub -End Class", - @" -internal partial class TestClass -{ - public void DoNothing(string[] strs) - { - string[] moreStrs; - } -}"); - } - - [Fact] - public async Task UntypedParametersAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Class TestClass - Public Sub DoNothing(obj, objs()) - End Sub -End Class", - @" -internal partial class TestClass -{ - public void DoNothing(object obj, object[] objs) - { - } -}"); - } - - [Fact] - public async Task PartialClassAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Partial Class TestClass - Private Sub DoNothing() - Console.WriteLine(""Hello"") - End Sub -End Class - -Class TestClass ' VB doesn't require partial here (when just a single class omits it) - Partial Private Sub DoNothing() - End Sub -End Class", - @"using System; - -public partial class TestClass -{ - partial void DoNothing() - { - Console.WriteLine(""Hello""); - } -} - -public partial class TestClass // VB doesn't require partial here (when just a single class omits it) -{ - partial void DoNothing(); -}"); - } - - [Fact] - public async Task NestedClassAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Class ClA - Public Shared Sub MA() - ClA.ClassB.MB() - MyClassC.MC() - End Sub - - Public Class ClassB - Public Shared Function MB() as ClassB - ClA.MA() - MyClassC.MC() - Return ClA.ClassB.MB() - End Function - End Class -End Class - -Class MyClassC - Public Shared Sub MC() - ClA.MA() - ClA.ClassB.MB() - End Sub -End Class", @" -internal partial class ClA -{ - public static void MA() - { - ClassB.MB(); - MyClassC.MC(); - } - - public partial class ClassB - { - public static ClassB MB() - { - MA(); - MyClassC.MC(); - return MB(); - } - } -} - -internal partial class MyClassC -{ - public static void MC() - { - ClA.MA(); - ClA.ClassB.MB(); - } -}"); - } - - [Fact] - public async Task LessQualifiedNestedClassAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Class ClA - Public Shared Sub MA() - ClassB.MB() - MyClassC.MC() - End Sub - - Public Class ClassB - Public Shared Function MB() as ClassB - MA() - MyClassC.MC() - Return MB() - End Function - End Class -End Class - -Class MyClassC - Public Shared Sub MC() - ClA.MA() - ClA.ClassB.MB() - End Sub -End Class", @" -internal partial class ClA -{ - public static void MA() - { - ClassB.MB(); - MyClassC.MC(); - } - - public partial class ClassB - { - public static ClassB MB() - { - MA(); - MyClassC.MC(); - return MB(); - } - } -} - -internal partial class MyClassC -{ - public static void MC() - { - ClA.MA(); - ClA.ClassB.MB(); - } -}"); - } - - [Fact] - public async Task TestAsyncMethodsAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @" Class AsyncCode - Public Sub NotAsync() - Dim a1 = Async Function() 3 - Dim a2 = Async Function() - Return Await Task (Of Integer).FromResult(3) - End Function - Dim a3 = Async Sub() Await Task.CompletedTask - Dim a4 = Async Sub() - Await Task.CompletedTask - End Sub - End Sub - - Public Async Function AsyncFunc() As Task(Of Integer) - Return Await Task (Of Integer).FromResult(3) - End Function - Public Async Sub AsyncSub() - Await Task.CompletedTask - End Sub - End Class", @"using System.Threading.Tasks; - -internal partial class AsyncCode -{ - public void NotAsync() - { - async Task a1() => 3; - async Task a2() => await Task.FromResult(3); - async void a3() => await Task.CompletedTask; - async void a4() => await Task.CompletedTask; - } - - public async Task AsyncFunc() - { - return await Task.FromResult(3); - } - public async void AsyncSub() - { - await Task.CompletedTask; - } -} -"); - } - - [Fact] - public async Task TestAsyncMethodsWithNoReturnAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"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 TestExternDllImportAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @" -Private Shared Function OpenProcess(ByVal dwDesiredAccess As AccessMask, ByVal bInheritHandle As Boolean, ByVal dwProcessId As UInteger) As IntPtr -End Function", @"[DllImport(""kernel32.dll"", SetLastError = true)] -private static extern IntPtr OpenProcess(AccessMask dwDesiredAccess, bool bInheritHandle, uint dwProcessId); - -1 source compilation errors: -BC30002: Type 'AccessMask' is not defined. -1 target compilation errors: -CS0246: The type or namespace name 'AccessMask' could not be found (are you missing a using directive or an assembly reference?)"); - } - - [Fact] - public async Task Issue443_FixCaseForInterfaceMembersAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - Function FooDifferentCase( ByRef str2 As String) As Integer -End Interface - -Public Class Foo - Implements IFoo - Function fooDifferentCase( ByRef str2 As String) As Integer Implements IFoo.FOODIFFERENTCASE - str2 = 2.ToString() - Return 3 - End Function -End Class", @" -public partial interface IFoo -{ - int FooDifferentCase(out string str2); -} - -public partial class Foo : IFoo -{ - public int FooDifferentCase(out string str2) - { - str2 = 2.ToString(); - return 3; - } -} -"); - } - - [Fact] - public async Task Issue444_FixNameForRenamedInterfaceMembersAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - Function FooDifferentName(ByRef str As String, i As Integer) As Integer -End Interface - -Public Class Foo - Implements IFoo - - Function BarDifferentName(ByRef str As String, i As Integer) As Integer Implements IFoo.FooDifferentName - Return 4 - End Function -End Class", @" -public partial interface IFoo -{ - int FooDifferentName(ref string str, int i); -} - -public partial class Foo : IFoo -{ - - public int BarDifferentName(ref string str, int i) - { - return 4; - } - - int IFoo.FooDifferentName(ref string str, int i) => BarDifferentName(ref str, i); -} -"); - } - - [Fact] - public async Task IdenticalInterfaceMethodsWithRenamedInterfaceMembersAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - Function DoFooBar(ByRef str As String, i As Integer) As Integer -End Interface - -Public Interface IBar - Function DoFooBar(ByRef str As String, i As Integer) As Integer -End Interface - -Public Class FooBar - Implements IFoo, IBar - - Function Foo(ByRef str As String, i As Integer) As Integer Implements IFoo.DoFooBar - Return 4 - End Function - - Function Bar(ByRef str As String, i As Integer) As Integer Implements IBar.DoFooBar - Return 2 - End Function - -End Class", @" -public partial interface IFoo -{ - int DoFooBar(ref string str, int i); -} - -public partial interface IBar -{ - int DoFooBar(ref string str, int i); -} - -public partial class FooBar : IFoo, IBar -{ - - public int Foo(ref string str, int i) - { - return 4; - } - - int IFoo.DoFooBar(ref string str, int i) => Foo(ref str, i); - - public int Bar(ref string str, int i) - { - return 2; - } - - int IBar.DoFooBar(ref string str, int i) => Bar(ref str, i); - -} -"); - } - - [Fact] - public async Task RenamedInterfaceCasingOnlyDifferenceConsumerAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @" -Public Interface IFoo - Function DoFoo() As Integer - Property Prop As Integer -End Interface - -Public Class Foo - Implements IFoo - - Private Function doFoo() As Integer Implements IFoo.DoFoo - Return 4 - End Function - - Private Property prop As Integer Implements IFoo.Prop - - Private Function Consumer() As Integer - Dim foo As New Foo() - Dim interfaceInstance As IFoo = foo - Return foo.doFoo() + foo.DoFoo() + - interfaceInstance.doFoo() + interfaceInstance.DoFoo() + - foo.prop + foo.Prop + - interfaceInstance.prop + interfaceInstance.Prop - End Function - -End Class", @" -public partial interface IFoo -{ - int DoFoo(); - int Prop { get; set; } -} - -public partial class Foo : IFoo -{ - - private int doFoo() - { - return 4; - } - - int IFoo.DoFoo() => doFoo(); - - private int prop { get; set; } - int IFoo.Prop { get => prop; set => prop = value; } - - private int Consumer() - { - var foo = new Foo(); - IFoo interfaceInstance = foo; - return foo.doFoo() + foo.doFoo() + interfaceInstance.DoFoo() + interfaceInstance.DoFoo() + foo.prop + foo.prop + interfaceInstance.Prop + interfaceInstance.Prop; - } - -} -"); - } - - [Fact] - public async Task RenamedInterfaceCasingOnlyDifferenceForVirtualMemberConsumerAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @" -Public Interface IFoo - Function DoFoo() As Integer - Property Prop As Integer -End Interface - -Public MustInherit Class BaseFoo - Implements IFoo - - Protected Friend Overridable Function doFoo() As Integer Implements IFoo.DoFoo - Return 4 - End Function - - Protected Friend Overridable Property prop As Integer Implements IFoo.Prop - -End Class - -Public Class Foo - Inherits BaseFoo - - Protected Friend Overrides Function DoFoo() As Integer - Return 5 - End Function - - Protected Friend Overrides Property Prop As Integer - - Private Function Consumer() As Integer - Dim foo As New Foo() - Dim interfaceInstance As IFoo = foo - Dim baseClass As BaseFoo = foo - Return foo.doFoo() + foo.DoFoo() + - interfaceInstance.doFoo() + interfaceInstance.DoFoo() + - baseClass.doFoo() + baseClass.DoFoo() + - foo.prop + foo.Prop + - interfaceInstance.prop + interfaceInstance.Prop + - baseClass.prop + baseClass.Prop - End Function -End Class", @" -public partial interface IFoo -{ - int DoFoo(); - int Prop { get; set; } -} - -public abstract partial class BaseFoo : IFoo -{ - - protected internal virtual int doFoo() - { - return 4; - } - - int IFoo.DoFoo() => doFoo(); - - protected internal virtual int prop { get; set; } - int IFoo.Prop { get => prop; set => prop = value; } - -} - -public partial class Foo : BaseFoo -{ - - protected internal override int doFoo() - { - return 5; - } - - protected internal override int prop { get; set; } - - private int Consumer() - { - var foo = new Foo(); - IFoo interfaceInstance = foo; - BaseFoo baseClass = foo; - return foo.doFoo() + foo.doFoo() + interfaceInstance.DoFoo() + interfaceInstance.DoFoo() + baseClass.doFoo() + baseClass.doFoo() + foo.prop + foo.prop + interfaceInstance.Prop + interfaceInstance.Prop + baseClass.prop + baseClass.prop; - } -} -"); - } - - [Fact] - public async Task RenamedInterfaceCasingOnlyDifferenceWithOverloadedPropertyConsumerAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @" -Public Interface IUserContext - ReadOnly Property GroupID As String -End Interface - -Public Interface IFoo - ReadOnly Property ConnectedGroupId As String -End Interface - -Public MustInherit Class BaseFoo - Implements IUserContext - - Protected Friend ReadOnly Property ConnectedGroupID() As String Implements IUserContext.GroupID - -End Class - -Public Class Foo - Inherits BaseFoo - Implements IFoo - - Protected Friend Overloads ReadOnly Property ConnectedGroupID As String Implements IFoo.ConnectedGroupId ' Comment moves because this line gets split - Get - Return If("""", MyBase.ConnectedGroupID()) - End Get - End Property - - Private Function Consumer() As String - Dim foo As New Foo() - Dim ifoo As IFoo = foo - Dim baseFoo As BaseFoo = foo - Dim iUserContext As IUserContext = foo - Return foo.ConnectedGroupID & foo.ConnectedGroupId & - iFoo.ConnectedGroupID & iFoo.ConnectedGroupId & - baseFoo.ConnectedGroupID & baseFoo.ConnectedGroupId & - iUserContext.GroupId & iUserContext.GroupID - End Function - -End Class", @" -public partial interface IUserContext -{ - string GroupID { get; } -} - -public partial interface IFoo -{ - string ConnectedGroupId { get; } -} - -public abstract partial class BaseFoo : IUserContext -{ - - protected internal string ConnectedGroupID { get; private set; } - string IUserContext.GroupID { get => ConnectedGroupID; } - -} - -public partial class Foo : BaseFoo, IFoo -{ - - protected internal new string ConnectedGroupID - { - get - { - return """" ?? base.ConnectedGroupID; - } - } - - string IFoo.ConnectedGroupId { get => ConnectedGroupID; } // Comment moves because this line gets split - - private string Consumer() - { - var foo = new Foo(); - IFoo ifoo = foo; - BaseFoo baseFoo = foo; - IUserContext iUserContext = foo; - return foo.ConnectedGroupID + foo.ConnectedGroupID + ifoo.ConnectedGroupId + ifoo.ConnectedGroupId + baseFoo.ConnectedGroupID + baseFoo.ConnectedGroupID + iUserContext.GroupID + iUserContext.GroupID; - } - -} -"); - } - - [Fact] - public async Task RenamedMethodImplementsMultipleInterfacesAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - Function DoFooBar(ByRef str As String, i As Integer) As Integer -End Interface - -Public Interface IBar - Function DoFooBar(ByRef str As String, i As Integer) As Integer -End Interface - -Public Class FooBar - Implements IFoo, IBar - - Function Foo(ByRef str As String, i As Integer) As Integer Implements IFoo.DoFooBar, IBar.DoFooBar - Return 4 - End Function - -End Class", @" -public partial interface IFoo -{ - int DoFooBar(ref string str, int i); -} - -public partial interface IBar -{ - int DoFooBar(ref string str, int i); -} - -public partial class FooBar : IFoo, IBar -{ - - public int Foo(ref string str, int i) - { - return 4; - } - - int IFoo.DoFooBar(ref string str, int i) => Foo(ref str, i); - int IBar.DoFooBar(ref string str, int i) => Foo(ref str, i); - -}"); - } - - [Fact] - public async Task IdenticalInterfacePropertiesWithRenamedInterfaceMembersAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - Property FooBarProp As Integer - End Interface - -Public Interface IBar - Property FooBarProp As Integer -End Interface - -Public Class FooBar - Implements IFoo, IBar - - Property Foo As Integer Implements IFoo.FooBarProp - - Property Bar As Integer Implements IBar.FooBarProp - -End Class", @" -public partial interface IFoo -{ - int FooBarProp { get; set; } -} - -public partial interface IBar -{ - int FooBarProp { get; set; } -} - -public partial class FooBar : IFoo, IBar -{ - - public int Foo { get; set; } - int IFoo.FooBarProp { get => Foo; set => Foo = value; } - - public int Bar { get; set; } - int IBar.FooBarProp { get => Bar; set => Bar = value; } - -}"); - } - - [Fact] - public async Task ExplicitInterfaceImplementationRequiredMethodParameters_749_Async() - { - await TestConversionVisualBasicToCSharpAsync( - @" -Public Interface IFoo - Function DoFooBar(ByRef str As String, i As Integer) As Integer -End Interface - -Public Interface IBar - Function DoFooBar(ByRef str As String, i As Integer) As Integer -End Interface - -Public Class FooBar - Implements IFoo, IBar - - Function Foo(ByRef str As String, i As Integer) As Integer Implements IFoo.DoFooBar - Return 4 - End Function - - Function Bar(ByRef str As String, i As Integer) As Integer Implements IBar.DoFooBar - Return 2 - End Function - -End Class", @" -public partial interface IFoo -{ - int DoFooBar(ref string str, int i); -} - -public partial interface IBar -{ - int DoFooBar(ref string str, int i); -} - -public partial class FooBar : IFoo, IBar -{ - - public int Foo(ref string str, int i) - { - return 4; - } - - int IFoo.DoFooBar(ref string str, int i) => Foo(ref str, i); - - public int Bar(ref string str, int i) - { - return 2; - } - - int IBar.DoFooBar(ref string str, int i) => Bar(ref str, i); - -} -"); - } - - [Fact] - public async Task ExplicitInterfaceImplementationOptionalParameters_1062_Async() - { - await TestConversionVisualBasicToCSharpAsync( - @" -Public Interface InterfaceWithOptionalParameters - Sub S(Optional i As Integer = 0) -End Interface - -Public Class ImplInterfaceWithOptionalParameters : Implements InterfaceWithOptionalParameters - Public Sub InterfaceWithOptionalParameters_S(Optional i As Integer = 0) Implements InterfaceWithOptionalParameters.S - End Sub -End Class", @" -public partial interface InterfaceWithOptionalParameters -{ - void S(int i = 0); -} - -public partial class ImplInterfaceWithOptionalParameters : InterfaceWithOptionalParameters -{ - public void InterfaceWithOptionalParameters_S(int i = 0) - { - } - - void InterfaceWithOptionalParameters.S(int i = 0) => InterfaceWithOptionalParameters_S(i); -} -"); - } - - [Fact] - public async Task OptionalParameterWithReservedName_1092_Async() - { - await TestConversionVisualBasicToCSharpAsync( - @" -Public Class WithOptionalParameters - Sub S1(Optional a As Object = Nothing, Optional [default] As String = """") - End Sub - - Sub S() - S1(, ""a"") - End Sub -End Class", @" -public partial class WithOptionalParameters -{ - public void S1(object a = null, string @default = """") - { - } - - public void S() - { - S1(@default: ""a""); - } -} -"); - } - - - [Fact] - public async Task ExplicitInterfaceImplementationOptionalParametersAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - Property ExplicitProp(Optional str As String = """") As Integer - Function ExplicitFunc(Optional str2 As String = """", Optional i2 As Integer = 1) As Integer -End Interface - -Public Class Foo - Implements IFoo - - Private Function ExplicitFunc(Optional str As String = """", Optional i2 As Integer = 1) As Integer Implements IFoo.ExplicitFunc - Return 5 - End Function - - Private Property ExplicitProp(Optional str As String = """") As Integer Implements IFoo.ExplicitProp - Get - Return 5 - End Get - Set(value As Integer) - End Set - End Property -End Class", @" -public partial interface IFoo -{ - int get_ExplicitProp(string str = """"); - void set_ExplicitProp(string str = """", int value = default); - int ExplicitFunc(string str2 = """", int i2 = 1); -} - -public partial class Foo : IFoo -{ - - private int ExplicitFunc(string str = """", int i2 = 1) - { - return 5; - } - - int IFoo.ExplicitFunc(string str = """", int i2 = 1) => ExplicitFunc(str, i2); - - private int get_ExplicitProp(string str = """") - { - return 5; - } - private void set_ExplicitProp(string str = """", int value = default) - { - } - - int IFoo.get_ExplicitProp(string str = """") => get_ExplicitProp(str); - void IFoo.set_ExplicitProp(string str = """", int value = default) => set_ExplicitProp(str, value); -} -"); - } - - - [Fact] - public async Task ExplicitInterfaceImplementationOptionalMethodParameters_749_Async() - { - await TestConversionVisualBasicToCSharpAsync( - @" -Public Interface IFoo - Function DoFooBar(ByRef str As String, Optional i As Integer = 4) As Integer -End Interface - -Public Interface IBar - Function DoFooBar(ByRef str As String, Optional i As Integer = 8) As Integer -End Interface - -Public Class FooBar - Implements IFoo, IBar - - Function Foo(ByRef str As String, Optional i As Integer = 4) As Integer Implements IFoo.DoFooBar - Return 4 - End Function - - Function Bar(ByRef str As String, Optional i As Integer = 8) As Integer Implements IBar.DoFooBar - Return 2 - End Function - -End Class", @" -public partial interface IFoo -{ - int DoFooBar(ref string str, int i = 4); -} - -public partial interface IBar -{ - int DoFooBar(ref string str, int i = 8); -} - -public partial class FooBar : IFoo, IBar -{ - - public int Foo(ref string str, int i = 4) - { - return 4; - } - - int IFoo.DoFooBar(ref string str, int i = 4) => Foo(ref str, i); - - public int Bar(ref string str, int i = 8) - { - return 2; - } - - int IBar.DoFooBar(ref string str, int i = 8) => Bar(ref str, i); - -} -"); - } - - [Fact] - public async Task RenamedInterfaceMethodFullyQualifiedAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Namespace TestNamespace - Public Interface IFoo - Function DoFoo(ByRef str As String, i As Integer) As Integer - End Interface -End Namespace - -Public Class Foo - Implements TestNamespace.IFoo - - Function DoFooRenamed(ByRef str As String, i As Integer) As Integer Implements TestNamespace.IFoo.DoFoo - Return 4 - End Function -End Class", @" -namespace TestNamespace -{ - public partial interface IFoo - { - int DoFoo(ref string str, int i); - } -} - -public partial class Foo : TestNamespace.IFoo -{ - - public int DoFooRenamed(ref string str, int i) - { - return 4; - } - - int TestNamespace.IFoo.DoFoo(ref string str, int i) => DoFooRenamed(ref str, i); -}"); - } - - [Fact] - public async Task RenamedInterfacePropertyFullyQualifiedAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Namespace TestNamespace - Public Interface IFoo - Property FooProp As Integer - End Interface -End Namespace - -Public Class Foo - Implements TestNamespace.IFoo - - Property FooPropRenamed As Integer Implements TestNamespace.IFoo.FooProp - -End Class", @" -namespace TestNamespace -{ - public partial interface IFoo - { - int FooProp { get; set; } - } -} - -public partial class Foo : TestNamespace.IFoo -{ - - public int FooPropRenamed { get; set; } - int TestNamespace.IFoo.FooProp { get => FooPropRenamed; set => FooPropRenamed = value; } - -}"); - } - - [Fact] - public async Task RenamedInterfaceMethodConsumerCasingRenamedAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo - Function DoFoo(ByRef str As String, i As Integer) As Integer - End Interface - -Public Class Foo - Implements IFoo - - Function DoFooRenamed(ByRef str As String, i As Integer) As Integer Implements IFoo.DoFoo - Return 4 - End Function -End Class - -Public Class FooConsumer - Function DoFooRenamedConsumer(ByRef str As String, i As Integer) As Integer - Dim foo As New Foo - Dim bar As IFoo = foo - Return foo.DOFOORENAMED(str, i) + bar.DoFoo(str, i) - End Function -End Class", @" -public partial interface IFoo -{ - int DoFoo(ref string str, int i); -} - -public partial class Foo : IFoo -{ - - public int DoFooRenamed(ref string str, int i) - { - return 4; - } - - int IFoo.DoFoo(ref string str, int i) => DoFooRenamed(ref str, i); -} - -public partial class FooConsumer -{ - public int DoFooRenamedConsumer(ref string str, int i) - { - var foo = new Foo(); - IFoo bar = foo; - return foo.DoFooRenamed(ref str, i) + bar.DoFoo(ref str, i); - } -}"); - } - - [Fact] - public async Task RenamedInterfacePropertyConsumerCasingRenamedAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo - Property FooProp As Integer - End Interface - -Public Class Foo - Implements IFoo - - Property FooPropRenamed As Integer Implements IFoo.FooProp - -End Class - -Public Class FooConsumer - Function GetFooRenamed() As Integer - Dim foo As New Foo - Dim bar As IFoo = foo - Return foo.FOOPROPRENAMED + bar.FooProp - End Function -End Class", @" -public partial interface IFoo -{ - int FooProp { get; set; } -} - -public partial class Foo : IFoo -{ - - public int FooPropRenamed { get; set; } - int IFoo.FooProp { get => FooPropRenamed; set => FooPropRenamed = value; } - -} - -public partial class FooConsumer -{ - public int GetFooRenamed() - { - var foo = new Foo(); - IFoo bar = foo; - return foo.FooPropRenamed + bar.FooProp; - } -}"); - } - - [Fact] - public async Task InterfaceMethodCasingRenamedConsumerAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo - Function DoFoo(str As String, i As Integer) As Integer - End Interface - -Public Class Foo - Implements IFoo - - Function dofoo(str As String, i As Integer) As Integer Implements IFoo.DoFoo - Return 4 - End Function -End Class - -Public Class FooConsumer - Function DoFooRenamedConsumer(str As String, i As Integer) As Integer - Dim foo As New Foo - Dim bar As IFoo = foo - Return foo.dofoo(str, i) + bar.DoFoo(str, i) - End Function -End Class", @" -public partial interface IFoo -{ - int DoFoo(string str, int i); -} - -public partial class Foo : IFoo -{ - - public int DoFoo(string str, int i) - { - return 4; - } -} - -public partial class FooConsumer -{ - public int DoFooRenamedConsumer(string str, int i) - { - var foo = new Foo(); - IFoo bar = foo; - return foo.DoFoo(str, i) + bar.DoFoo(str, i); - } -}"); - } - - [Fact] - public async Task InterfacePropertyCasingRenamedConsumerAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo - Property FooProp As Integer - End Interface - -Public Class Foo - Implements IFoo - - Property fooprop As Integer Implements IFoo.FooProp - -End Class - -Public Class FooConsumer - Function GetFooRenamed() As Integer - Dim foo As New Foo - Dim bar As IFoo = foo - Return foo.fooprop + bar.FooProp - End Function -End Class", @" -public partial interface IFoo -{ - int FooProp { get; set; } -} - -public partial class Foo : IFoo -{ - - public int FooProp { get; set; } - -} - -public partial class FooConsumer -{ - public int GetFooRenamed() - { - var foo = new Foo(); - IFoo bar = foo; - return foo.FooProp + bar.FooProp; - } -}"); - } - - [Fact] - public async Task InterfaceRenamedMethodConsumerAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo - Function DoFoo(ByRef str As String, i As Integer) As Integer - End Interface - -Public Class Foo - Implements IFoo - - Function DoFooRenamed(ByRef str As String, i As Integer) As Integer Implements IFoo.DoFoo - Return 4 - End Function -End Class - -Public Class FooConsumer - Function DoFooRenamedConsumer(ByRef str As String, i As Integer) As Integer - Dim foo As New Foo - Dim bar As IFoo = foo - Return foo.DoFooRenamed(str, i) + bar.DoFoo(str, i) - End Function -End Class", @" -public partial interface IFoo -{ - int DoFoo(ref string str, int i); -} - -public partial class Foo : IFoo -{ - - public int DoFooRenamed(ref string str, int i) - { - return 4; - } - - int IFoo.DoFoo(ref string str, int i) => DoFooRenamed(ref str, i); -} - -public partial class FooConsumer -{ - public int DoFooRenamedConsumer(ref string str, int i) - { - var foo = new Foo(); - IFoo bar = foo; - return foo.DoFooRenamed(ref str, i) + bar.DoFoo(ref str, i); - } -}"); - } - - [Fact] - public async Task InterfaceRenamedPropertyConsumerAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo - Property FooProp As Integer - End Interface - -Public Class Foo - Implements IFoo - - Property FooPropRenamed As Integer Implements IFoo.FooProp - -End Class - -Public Class FooConsumer - Function GetFooRenamed() As Integer - Dim foo As New Foo - Dim bar As IFoo = foo - Return foo.FooPropRenamed + bar.FooProp - End Function -End Class", @" -public partial interface IFoo -{ - int FooProp { get; set; } -} - -public partial class Foo : IFoo -{ - - public int FooPropRenamed { get; set; } - int IFoo.FooProp { get => FooPropRenamed; set => FooPropRenamed = value; } - -} - -public partial class FooConsumer -{ - public int GetFooRenamed() - { - var foo = new Foo(); - IFoo bar = foo; - return foo.FooPropRenamed + bar.FooProp; - } -}"); - } - - [Fact] - public async Task PartialInterfaceRenamedMethodConsumerAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Partial Interface IFoo - Function DoFoo(ByRef str As String, i As Integer) As Integer - End Interface - -Public Class Foo - Implements IFoo - - Function DoFooRenamed(ByRef str As String, i As Integer) As Integer Implements IFoo.DoFoo - Return 4 - End Function -End Class - -Public Class FooConsumer - Function DoFooRenamedConsumer(ByRef str As String, i As Integer) As Integer - Dim foo As New Foo - Dim bar As IFoo = foo - Return foo.DoFooRenamed(str, i) + bar.DoFoo(str, i) - End Function -End Class", @" -public partial interface IFoo -{ - int DoFoo(ref string str, int i); -} - -public partial class Foo : IFoo -{ - - public int DoFooRenamed(ref string str, int i) - { - return 4; - } - - int IFoo.DoFoo(ref string str, int i) => DoFooRenamed(ref str, i); -} - -public partial class FooConsumer -{ - public int DoFooRenamedConsumer(ref string str, int i) - { - var foo = new Foo(); - IFoo bar = foo; - return foo.DoFooRenamed(ref str, i) + bar.DoFoo(ref str, i); - } -}"); - } - - [Fact] - public async Task PartialInterfaceRenamedPropertyConsumerAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Partial Interface IFoo - Property FooProp As Integer - End Interface - -Public Class Foo - Implements IFoo - - Property FooPropRenamed As Integer Implements IFoo.FooProp - -End Class - -Public Class FooConsumer - Function GetFooRenamed() As Integer - Dim foo As New Foo - Dim bar As IFoo = foo - Return foo.FooPropRenamed + bar.FooProp - End Function -End Class", @" -public partial interface IFoo -{ - int FooProp { get; set; } -} - -public partial class Foo : IFoo -{ - - public int FooPropRenamed { get; set; } - int IFoo.FooProp { get => FooPropRenamed; set => FooPropRenamed = value; } - -} - -public partial class FooConsumer -{ - public int GetFooRenamed() - { - var foo = new Foo(); - IFoo bar = foo; - return foo.FooPropRenamed + bar.FooProp; - } -}"); - } - - [Fact] - public async Task RenamedInterfaceMethodMyClassConsumerAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo - Function DoFoo(ByRef str As String, i As Integer) As Integer - End Interface - -Public Class Foo - Implements IFoo - - Overridable Function DoFooRenamed(ByRef str As String, i As Integer) As Integer Implements IFoo.DoFoo ' Comment ends up out of order, but attached to correct method - Return 4 - End Function - - Function DoFooRenamedConsumer(ByRef str As String, i As Integer) As Integer - Return MyClass.DoFooRenamed(str, i) - End Function -End Class", @" -public partial interface IFoo -{ - int DoFoo(ref string str, int i); -} - -public partial class Foo : IFoo -{ - - public int MyClassDoFooRenamed(ref string str, int i) - { - return 4; - } - - int IFoo.DoFoo(ref string str, int i) => DoFooRenamed(ref str, i); - public virtual int DoFooRenamed(ref string str, int i) => MyClassDoFooRenamed(ref str, i); // Comment ends up out of order, but attached to correct method - - public int DoFooRenamedConsumer(ref string str, int i) - { - return MyClassDoFooRenamed(ref str, i); - } -}"); - } - - [Fact] - public async Task RenamedInterfacePropertyMyClassConsumerAsync() - { - await TestConversionVisualBasicToCSharpAsync(@"Public Interface IFoo - ReadOnly Property DoFoo As Integer - WriteOnly Property DoBar As Integer - End Interface - -Public Class Foo - Implements IFoo - - Overridable ReadOnly Property DoFooRenamed As Integer Implements IFoo.DoFoo ' Comment ends up out of order, but attached to correct method - Get - Return 4 - End Get - End Property - - Overridable WriteOnly Property DoBarRenamed As Integer Implements IFoo.DoBar ' Comment ends up out of order, but attached to correct method - Set - Throw New Exception() - End Set - End Property - - Sub DoFooRenamedConsumer() - MyClass.DoBarRenamed = MyClass.DoFooRenamed - End Sub -End Class", @"using System; - -public partial interface IFoo -{ - int DoFoo { get; } - int DoBar { set; } -} - -public partial class Foo : IFoo -{ - - public int MyClassDoFooRenamed - { - get - { - return 4; - } - } - - int IFoo.DoFoo { get => DoFooRenamed; } - - public virtual int DoFooRenamed // Comment ends up out of order, but attached to correct method - { - get - { - return MyClassDoFooRenamed; - } - } - - public int MyClassDoBarRenamed - { - set - { - throw new Exception(); - } - } - - int IFoo.DoBar { set => DoBarRenamed = value; } - - public virtual int DoBarRenamed // Comment ends up out of order, but attached to correct method - { - set - { - MyClassDoBarRenamed = value; - } - } - - public void DoFooRenamedConsumer() - { - MyClassDoBarRenamed = MyClassDoFooRenamed; - } -}"); - } - - [Fact] - public async Task ExplicitInterfaceImplementationAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - Property ExplicitProp(str As String) As Integer - Function ExplicitFunc(ByRef str2 As String, i2 As Integer) As Integer -End Interface - -Public Class Foo - Implements IFoo - - Private Function ExplicitFunc(ByRef str As String, i As Integer) As Integer Implements IFoo.ExplicitFunc - Return 5 - End Function - - Private Property ExplicitProp(str As String) As Integer Implements IFoo.ExplicitProp - Get - Return 5 - End Get - Set(value As Integer) - End Set - End Property -End Class", @" -public partial interface IFoo -{ - int get_ExplicitProp(string str); - void set_ExplicitProp(string str, int value); - int ExplicitFunc(ref string str2, int i2); -} - -public partial class Foo : IFoo -{ - - private int ExplicitFunc(ref string str, int i) - { - return 5; - } - - int IFoo.ExplicitFunc(ref string str, int i) => ExplicitFunc(ref str, i); - - private int get_ExplicitProp(string str) - { - return 5; - } - private void set_ExplicitProp(string str, int value) - { - } - - int IFoo.get_ExplicitProp(string str) => get_ExplicitProp(str); - void IFoo.set_ExplicitProp(string str, int value) => set_ExplicitProp(str, value); -} -"); - } - - [Fact] - public async Task PropertyInterfaceImplementationKeepsVirtualModifierAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - Property PropParams(str As String) As Integer - Property Prop() As Integer -End Interface - -Public Class Foo - Implements IFoo - - Public Overridable Property PropParams(str As String) As Integer Implements IFoo.PropParams - Get - Return 5 - End Get - Set(value As Integer) - End Set - End Property - - Public Overridable Property Prop As Integer Implements IFoo.Prop - Get - Return 5 - End Get - Set(value As Integer) - End Set - End Property -End Class", @" -public partial interface IFoo -{ - int get_PropParams(string str); - void set_PropParams(string str, int value); - int Prop { get; set; } -} - -public partial class Foo : IFoo -{ - - public virtual int get_PropParams(string str) - { - return 5; - } - public virtual void set_PropParams(string str, int value) - { - } - - public virtual int Prop - { - get - { - return 5; - } - set - { - } - } -} -"); - } - - [Fact] - public async Task PrivateAutoPropertyImplementsMultipleInterfacesAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - Property ExplicitProp As Integer -End Interface - -Public Interface IBar - Property ExplicitProp As Integer -End Interface - -Public Class Foo - Implements IFoo, IBar - - Private Property ExplicitProp As Integer Implements IFoo.ExplicitProp, IBar.ExplicitProp -End Class", @" -public partial interface IFoo -{ - int ExplicitProp { get; set; } -} - -public partial interface IBar -{ - int ExplicitProp { get; set; } -} - -public partial class Foo : IFoo, IBar -{ - - private int ExplicitProp { get; set; } - int IFoo.ExplicitProp { get => ExplicitProp; set => ExplicitProp = value; } - int IBar.ExplicitProp { get => ExplicitProp; set => ExplicitProp = value; } -}"); - } - - - [Fact] - public async Task ImplementMultipleRenamedPropertiesFromInterfaceAsAbstractAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @" -Public Interface IFoo - Property ExplicitProp As Integer -End Interface -Public Interface IBar - Property ExplicitProp As Integer -End Interface -Public MustInherit Class Foo - Implements IFoo, IBar - - Protected MustOverride Property ExplicitPropRenamed1 As Integer Implements IFoo.ExplicitProp - Protected MustOverride Property ExplicitPropRenamed2 As Integer Implements IBar.ExplicitProp -End Class", @" -public partial interface IFoo -{ - int ExplicitProp { get; set; } -} - -public partial interface IBar -{ - int ExplicitProp { get; set; } -} -public abstract partial class Foo : IFoo, IBar -{ - - protected abstract int ExplicitPropRenamed1 { get; set; } - int IFoo.ExplicitProp { get => ExplicitPropRenamed1; set => ExplicitPropRenamed1 = value; } - protected abstract int ExplicitPropRenamed2 { get; set; } - int IBar.ExplicitProp { get => ExplicitPropRenamed2; set => ExplicitPropRenamed2 = value; } -}"); - } - - [Fact] - public async Task ExplicitInterfaceImplementationForVirtualMemberFromAnotherClassAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @" -Public Interface IFoo - Sub Save() - Property Prop As Integer -End Interface - -Public MustInherit Class BaseFoo - Protected Overridable Sub OnSave() - End Sub - - Protected Overridable Property MyProp As Integer = 5 -End Class - -Public Class Foo - Inherits BaseFoo - Implements IFoo - - Protected Overrides Sub OnSave() Implements IFoo.Save - End Sub - - Protected Overrides Property MyProp As Integer = 6 Implements IFoo.Prop - -End Class", @" -public partial interface IFoo -{ - void Save(); - int Prop { get; set; } -} - -public abstract partial class BaseFoo -{ - protected virtual void OnSave() - { - } - - protected virtual int MyProp { get; set; } = 5; -} - -public partial class Foo : BaseFoo, IFoo -{ - - protected override void OnSave() - { - } - - void IFoo.Save() => OnSave(); - - protected override int MyProp { get; set; } = 6; - int IFoo.Prop { get => MyProp; set => MyProp = value; } - -}"); - } - - [Fact] - public async Task ExplicitInterfaceImplementationWhereOnlyOneInterfaceMemberIsRenamedAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @" -Public Interface IFoo - Sub Save() - Property A As Integer -End Interface - -Public Interface IBar - Sub OnSave() - Property B As Integer -End Interface - -Public Class Foo - Implements IFoo, IBar - - Public Overridable Sub Save() Implements IFoo.Save, IBar.OnSave - End Sub - - Public Overridable Property A As Integer Implements IFoo.A, IBar.B - -End Class", @" -public partial interface IFoo -{ - void Save(); - int A { get; set; } -} - -public partial interface IBar -{ - void OnSave(); - int B { get; set; } -} - -public partial class Foo : IFoo, IBar -{ - - public virtual void Save() - { - } - - void IFoo.Save() => Save(); - void IBar.OnSave() => Save(); - - public virtual int A { get; set; } - int IFoo.A { get => A; set => A = value; } - int IBar.B { get => A; set => A = value; } - -}"); - } - - [Fact] - public async Task ExplicitInterfaceImplementationWhereMemberShadowsBaseAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @" -Public Interface IFoo - Sub Save() - Property Prop As Integer -End Interface - -Public MustInherit Class BaseFoo - Public Overridable Sub OnSave() - End Sub - - Public Overridable Property MyProp As Integer = 5 -End Class - -Public Class Foo - Inherits BaseFoo - Implements IFoo - - Public Shadows Sub OnSave() Implements IFoo.Save - End Sub - - Public Shadows Property MyProp As Integer = 6 Implements IFoo.Prop - -End Class", @" -public partial interface IFoo -{ - void Save(); - int Prop { get; set; } -} - -public abstract partial class BaseFoo -{ - public virtual void OnSave() - { - } - - public virtual int MyProp { get; set; } = 5; -} - -public partial class Foo : BaseFoo, IFoo -{ - - public new void OnSave() - { - } - - void IFoo.Save() => OnSave(); - - public new int MyProp { get; set; } = 6; - int IFoo.Prop { get => MyProp; set => MyProp = value; } - -}"); - } - - [Fact] - public async Task PrivatePropertyAccessorBlocksImplementsMultipleInterfacesAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - Property ExplicitProp As Integer -End Interface - -Public Interface IBar - Property ExplicitProp As Integer -End Interface - -Public Class Foo - Implements IFoo, IBar - - Private Property ExplicitProp As Integer Implements IFoo.ExplicitProp, IBar.ExplicitProp ' Comment moves because this line gets split - Get - Return 5 - End Get - Set - End Set - End Property -End Class", @" -public partial interface IFoo -{ - int ExplicitProp { get; set; } -} - -public partial interface IBar -{ - int ExplicitProp { get; set; } -} - -public partial class Foo : IFoo, IBar -{ - - private int ExplicitProp - { - get - { - return 5; - } - set - { - } - } - - int IFoo.ExplicitProp { get => ExplicitProp; set => ExplicitProp = value; } - int IBar.ExplicitProp { get => ExplicitProp; set => ExplicitProp = value; } // Comment moves because this line gets split -}"); - } - - [Fact] - public async Task NonPublicImplementsInterfacesAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - Property FriendProp As Integer - Sub ProtectedSub() - Function PrivateFunc() As Integer - Sub ProtectedInternalSub() - Sub AbstractSub() -End Interface - -Public Interface IBar - Property FriendProp As Integer - Sub ProtectedSub() - Function PrivateFunc() As Integer - Sub ProtectedInternalSub() - Sub AbstractSub() -End Interface - -Public MustInherit Class BaseFoo - Implements IFoo, IBar - - Friend Overridable Property FriendProp As Integer Implements IFoo.FriendProp, IBar.FriendProp ' Comment moves because this line gets split - Get - Return 5 - End Get - Set - End Set - End Property - - Protected Sub ProtectedSub() Implements IFoo.ProtectedSub, IBar.ProtectedSub - End Sub - - Private Function PrivateFunc() As Integer Implements IFoo.PrivateFunc, IBar.PrivateFunc - End Function - - Protected Friend Overridable Sub ProtectedInternalSub() Implements IFoo.ProtectedInternalSub, IBar.ProtectedInternalSub - End Sub - - Protected MustOverride Sub AbstractSubRenamed() Implements IFoo.AbstractSub, IBar.AbstractSub -End Class - -Public Class Foo - Inherits BaseFoo - - Protected Friend Overrides Sub ProtectedInternalSub() - End Sub - - Protected Overrides Sub AbstractSubRenamed() - End Sub -End Class -", @" -public partial interface IFoo -{ - int FriendProp { get; set; } - void ProtectedSub(); - int PrivateFunc(); - void ProtectedInternalSub(); - void AbstractSub(); -} - -public partial interface IBar -{ - int FriendProp { get; set; } - void ProtectedSub(); - int PrivateFunc(); - void ProtectedInternalSub(); - void AbstractSub(); -} - -public abstract partial class BaseFoo : IFoo, IBar -{ - - internal virtual int FriendProp - { - get - { - return 5; - } - set - { - } - } - - int IFoo.FriendProp { get => FriendProp; set => FriendProp = value; } - int IBar.FriendProp { get => FriendProp; set => FriendProp = value; } // Comment moves because this line gets split - - protected void ProtectedSub() - { - } - - void IFoo.ProtectedSub() => ProtectedSub(); - void IBar.ProtectedSub() => ProtectedSub(); - - private int PrivateFunc() - { - return default; - } - - int IFoo.PrivateFunc() => PrivateFunc(); - int IBar.PrivateFunc() => PrivateFunc(); + @"using System; - protected internal virtual void ProtectedInternalSub() +public partial class TestClass +{ + partial void DoNothing() { + Console.WriteLine(""Hello""); } - - void IFoo.ProtectedInternalSub() => ProtectedInternalSub(); - void IBar.ProtectedInternalSub() => ProtectedInternalSub(); - - protected abstract void AbstractSubRenamed(); - void IFoo.AbstractSub() => AbstractSubRenamed(); - void IBar.AbstractSub() => AbstractSubRenamed(); } -public partial class Foo : BaseFoo +public partial class TestClass // VB doesn't require partial here (when just a single class omits it) { - - protected internal override void ProtectedInternalSub() - { - } - - protected override void AbstractSubRenamed() - { - } + partial void DoNothing(); }"); } [Fact] - public async Task ExplicitPropertyImplementationWithDirectAccessAsync() + public async Task NestedClassAsync() { - await TestConversionVisualBasicToCSharpAsync( - @" -Public Interface IFoo - Property ExplicitProp As Integer - ReadOnly Property ExplicitReadOnlyProp As Integer -End Interface + await TestConversionVisualBasicToCSharpAsync(@"Class ClA + Public Shared Sub MA() + ClA.ClassB.MB() + MyClassC.MC() + End Sub -Public Class Foo - Implements IFoo - - Property ExplicitPropRenamed As Integer Implements IFoo.ExplicitProp - ReadOnly Property ExplicitRenamedReadOnlyProp As Integer Implements IFoo.ExplicitReadOnlyProp + Public Class ClassB + Public Shared Function MB() as ClassB + ClA.MA() + MyClassC.MC() + Return ClA.ClassB.MB() + End Function + End Class +End Class - Private Sub Consumer() - _ExplicitPropRenamed = 5 - _ExplicitRenamedReadOnlyProp = 10 +Class MyClassC + Public Shared Sub MC() + ClA.MA() + ClA.ClassB.MB() End Sub - End Class", @" -public partial interface IFoo +internal partial class ClA { - int ExplicitProp { get; set; } - int ExplicitReadOnlyProp { get; } + public static void MA() + { + ClassB.MB(); + MyClassC.MC(); + } + + public partial class ClassB + { + public static ClassB MB() + { + MA(); + MyClassC.MC(); + return MB(); + } + } } -public partial class Foo : IFoo +internal partial class MyClassC { - - public int ExplicitPropRenamed { get; set; } - int IFoo.ExplicitProp { get => ExplicitPropRenamed; set => ExplicitPropRenamed = value; } - public int ExplicitRenamedReadOnlyProp { get; private set; } - int IFoo.ExplicitReadOnlyProp { get => ExplicitRenamedReadOnlyProp; } - - private void Consumer() + public static void MC() { - ExplicitPropRenamed = 5; - ExplicitRenamedReadOnlyProp = 10; + ClA.MA(); + ClA.ClassB.MB(); } - }"); } - + [Fact] - public async Task ReadonlyRenamedPropertyImplementsMultipleInterfacesAsync() + public async Task LessQualifiedNestedClassAsync() { - await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - ReadOnly Property ExplicitProp As Integer -End Interface + await TestConversionVisualBasicToCSharpAsync(@"Class ClA + Public Shared Sub MA() + ClassB.MB() + MyClassC.MC() + End Sub -Public Interface IBar - ReadOnly Property ExplicitProp As Integer -End Interface + Public Class ClassB + Public Shared Function MB() as ClassB + MA() + MyClassC.MC() + Return MB() + End Function + End Class +End Class -Public Class Foo - Implements IFoo, IBar - - ReadOnly Property ExplicitPropRenamed As Integer Implements IFoo.ExplicitProp, IBar.ExplicitProp +Class MyClassC + Public Shared Sub MC() + ClA.MA() + ClA.ClassB.MB() + End Sub End Class", @" -public partial interface IFoo +internal partial class ClA { - int ExplicitProp { get; } -} + public static void MA() + { + ClassB.MB(); + MyClassC.MC(); + } -public partial interface IBar -{ - int ExplicitProp { get; } + public partial class ClassB + { + public static ClassB MB() + { + MA(); + MyClassC.MC(); + return MB(); + } + } } -public partial class Foo : IFoo, IBar +internal partial class MyClassC { - - public int ExplicitPropRenamed { get; private set; } - int IFoo.ExplicitProp { get => ExplicitPropRenamed; } - int IBar.ExplicitProp { get => ExplicitPropRenamed; } + public static void MC() + { + ClA.MA(); + ClA.ClassB.MB(); + } }"); } [Fact] - public async Task WriteonlyPropertyImplementsMultipleInterfacesAsync() + public async Task TestAsyncMethodsAsync() { await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - WriteOnly Property ExplicitProp As Integer -End Interface - -Public Interface IBar - WriteOnly Property ExplicitProp As Integer -End Interface - -Public Class Foo - Implements IFoo, IBar - - WriteOnly Property ExplicitPropRenamed As Integer Implements IFoo.ExplicitProp, IBar.ExplicitProp ' Comment moves because this line gets split - Set - End Set - End Property -End Class", @" -public partial interface IFoo -{ - int ExplicitProp { set; } -} + @" Class AsyncCode + Public Sub NotAsync() + Dim a1 = Async Function() 3 + Dim a2 = Async Function() + Return Await Task (Of Integer).FromResult(3) + End Function + Dim a3 = Async Sub() Await Task.CompletedTask + Dim a4 = Async Sub() + Await Task.CompletedTask + End Sub + End Sub -public partial interface IBar -{ - int ExplicitProp { set; } -} + Public Async Function AsyncFunc() As Task(Of Integer) + Return Await Task (Of Integer).FromResult(3) + End Function + Public Async Sub AsyncSub() + Await Task.CompletedTask + End Sub + End Class", @"using System.Threading.Tasks; -public partial class Foo : IFoo, IBar +internal partial class AsyncCode { - - public int ExplicitPropRenamed + public void NotAsync() { - set - { - } + async Task a1() => 3; + async Task a2() => await Task.FromResult(3); + async void a3() => await Task.CompletedTask; + async void a4() => await Task.CompletedTask; } - int IFoo.ExplicitProp { set => ExplicitPropRenamed = value; } - int IBar.ExplicitProp { set => ExplicitPropRenamed = value; } // Comment moves because this line gets split -}"); + public async Task AsyncFunc() + { + return await Task.FromResult(3); + } + public async void AsyncSub() + { + await Task.CompletedTask; + } +} +"); } [Fact] - public async Task PrivateMethodAndParameterizedPropertyImplementsMultipleInterfacesAsync() + public async Task TestAsyncMethodsWithNoReturnAsync() { await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - Property ExplicitProp(str As String) As Integer - Function ExplicitFunc(ByRef str2 As String, i2 As Integer) As Integer -End Interface + @"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 -Public Interface IBar - Property ExplicitProp(str As String) As Integer - Function ExplicitFunc(ByRef str2 As String, i2 As Integer) As Integer -End Interface + + Async Function [Then](ByVal task As Task, ByVal f As Func(Of Task)) As Task + Await task + Await f() + End Function -Public Class Foo - Implements IFoo, IBar + + 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 - Private Function ExplicitFunc(ByRef str As String, i As Integer) As Integer Implements IFoo.ExplicitFunc, IBar.ExplicitFunc - Return 5 + + 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 - - Private Property ExplicitProp(str As String) As Integer Implements IFoo.ExplicitProp, IBar.ExplicitProp - Get - Return 5 - End Get - Set(value As Integer) - End Set - End Property -End Class", @" -public partial interface IFoo -{ - int get_ExplicitProp(string str); - void set_ExplicitProp(string str, int value); - int ExplicitFunc(ref string str2, int i2); -} -public partial interface IBar -{ - int get_ExplicitProp(string str); - void set_ExplicitProp(string str, int value); - int ExplicitFunc(ref string str2, int i2); -} + + 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; -public partial class Foo : IFoo, IBar +internal static partial class TaskExtensions { - - private int ExplicitFunc(ref string str, int i) + public async static Task Then(this Task task, Func> f) { - return 5; + await task; + return await f(); } - int IFoo.ExplicitFunc(ref string str, int i) => ExplicitFunc(ref str, i); - int IBar.ExplicitFunc(ref string str, int i) => ExplicitFunc(ref str, i); + public async static Task Then(this Task task, Func f) + { + await task; + await f(); + } - private int get_ExplicitProp(string str) + public async static Task Then(this Task task, Func> f) { - return 5; + return await f(await task); } - private void set_ExplicitProp(string str, int value) + + public async static Task Then(this Task task, Func f) { + await f(await task); } - int IFoo.get_ExplicitProp(string str) => get_ExplicitProp(str); - int IBar.get_ExplicitProp(string str) => get_ExplicitProp(str); - void IFoo.set_ExplicitProp(string str, int value) => set_ExplicitProp(str, value); - void IBar.set_ExplicitProp(string str, int value) => set_ExplicitProp(str, value); + public async static Task ThenExit(this Task task, Func f) + { + await f(await task); + return; + } }"); } - /// - /// - /// - /// [Fact] - public async Task Issue444_InternalMemberDelegatingMethodAsync() + public async Task TestExternDllImportAsync() { await TestConversionVisualBasicToCSharpAsync( - @"Public Interface IFoo - Function FooDifferentName(ByRef str As String, i As Integer) As Integer -End Interface - -Friend Class Foo - Implements IFoo - - Function BarDifferentName(ByRef str As String, i As Integer) As Integer Implements IFoo.FooDifferentName - Return 4 - End Function -End Class", @" -public partial interface IFoo -{ - int FooDifferentName(ref string str, int i); -} - -internal partial class Foo : IFoo -{ - - public int BarDifferentName(ref string str, int i) - { - return 4; - } + @" +Private Shared Function OpenProcess(ByVal dwDesiredAccess As AccessMask, ByVal bInheritHandle As Boolean, ByVal dwProcessId As UInteger) As IntPtr +End Function", @"[DllImport(""kernel32.dll"", SetLastError = true)] +private static extern IntPtr OpenProcess(AccessMask dwDesiredAccess, bool bInheritHandle, uint dwProcessId); - int IFoo.FooDifferentName(ref string str, int i) => BarDifferentName(ref str, i); -} -"); +1 source compilation errors: +BC30002: Type 'AccessMask' is not defined. +1 target compilation errors: +CS0246: The type or namespace name 'AccessMask' could not be found (are you missing a using directive or an assembly reference?)"); } [Fact] @@ -3922,37 +1204,6 @@ public enum MyEnum "); } - [Fact] - public async Task TestConstructorStaticLocalConvertedToFieldAsync() - { - await TestConversionVisualBasicToCSharpAsync( - @"Class StaticLocalConvertedToField - Sub New(x As Boolean) - Static sPrevPosition As Integer = 7 ' Comment moves with declaration - Console.WriteLine(sPrevPosition) - End Sub - Sub New(x As Integer) - Static sPrevPosition As Integer - Console.WriteLine(sPrevPosition) - End Sub -End Class", @"using System; - -internal partial class StaticLocalConvertedToField -{ - private int _sPrevPosition = 7; // Comment moves with declaration - public StaticLocalConvertedToField(bool x) - { - Console.WriteLine(_sPrevPosition); - } - - private int _sPrevPosition1 = default; - public StaticLocalConvertedToField(int x) - { - Console.WriteLine(_sPrevPosition1); - } -}"); - } - [Fact] public async Task TestPropertyStaticLocalConvertedToFieldAsync() {