From 236f72453aa70393e8e19e4204f828472175ee8e Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Tue, 23 Jul 2024 18:01:49 +0200 Subject: [PATCH 1/4] add a test --- Tests/CSharp/MemberTests/MemberTests.cs | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/Tests/CSharp/MemberTests/MemberTests.cs b/Tests/CSharp/MemberTests/MemberTests.cs index 1aae7ee1..319b7aae 100644 --- a/Tests/CSharp/MemberTests/MemberTests.cs +++ b/Tests/CSharp/MemberTests/MemberTests.cs @@ -672,6 +672,45 @@ public void set_Prop(int i, string value) }", incompatibleWithAutomatedCommentTesting: true);// Known bug: Additional declarations don't get comments correctly converted } + [Fact] + public async Task StaticLocalsInPropertyGetterAndSetterAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @" +Public Property Prop As String + Get + Static b As Boolean + b = True + End Get + + Set(ByVal s As String) + Static b As Boolean + b = False + End Set +End Property +", @" +internal partial class SurroundingClass +{ + private bool _Prop_b; + private bool _Prop_b1; + + public string Prop + { + get + { + _Prop_b = true; + return default; + } + + set + { + _Prop_b1 = false; + } + } + +}"); + } + [Fact] public async Task TestReadOnlyAndWriteOnlyParametrizedPropertyAsync() { From 2208e30823e3fea6f38d7b4a4784602ccb089a04 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Tue, 23 Jul 2024 18:16:52 +0200 Subject: [PATCH 2/4] differentiate between statics in setters and getters --- CodeConverter/CSharp/ExpressionNodeVisitor.cs | 4 +++ .../HoistedFieldFromVbStaticVariable.cs | 5 +++- .../MethodBodyExecutableStatementVisitor.cs | 4 ++- CodeConverter/CSharp/PerScopeState.cs | 26 +++++++++++++++---- CodeConverter/Common/AnnotationConstants.cs | 6 +++++ CodeConverter/Util/SyntaxNodeExtensions.cs | 5 ++++ 6 files changed, 43 insertions(+), 7 deletions(-) diff --git a/CodeConverter/CSharp/ExpressionNodeVisitor.cs b/CodeConverter/CSharp/ExpressionNodeVisitor.cs index 813f839b..2ebd978a 100644 --- a/CodeConverter/CSharp/ExpressionNodeVisitor.cs +++ b/CodeConverter/CSharp/ExpressionNodeVisitor.cs @@ -1544,6 +1544,10 @@ public override async Task VisitIdentifierName(VBasic.Syntax.I 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.ToString()); + } + var vbMethodBlock = node.Ancestors().OfType().FirstOrDefault(); if (vbMethodBlock != null && vbMethodBlock.MustReturn() && diff --git a/CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs b/CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs index b7e61a40..dbbfffc2 100644 --- a/CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs +++ b/CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs @@ -6,18 +6,21 @@ internal class HoistedFieldFromVbStaticVariable : IHoistedNode { public string OriginalMethodName { get; } public string OriginalVariableName { get; } + public string OriginalParentAccessorKind { get; } public ExpressionSyntax Initializer { get; } public TypeSyntax Type { get; } public bool IsStatic { get; } - public HoistedFieldFromVbStaticVariable(string originalMethodName, string originalVariableName, ExpressionSyntax initializer, TypeSyntax type, bool isStatic) + public HoistedFieldFromVbStaticVariable(string originalMethodName, string originalVariableName, string originalParentAccessorKind, ExpressionSyntax initializer, TypeSyntax type, bool isStatic) { OriginalMethodName = originalMethodName; OriginalVariableName = originalVariableName; + OriginalParentAccessorKind = originalParentAccessorKind; Initializer = initializer; Type = type; IsStatic = isStatic; } public string FieldName => OriginalMethodName != null ? $"_{OriginalMethodName}_{OriginalVariableName}" : $"_{OriginalVariableName}"; + public string PrefixedOriginalVariableName => PerScopeState.GetPrefixedName(OriginalParentAccessorKind, OriginalVariableName); } \ No newline at end of file diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index 9c23f6c6..672b5f97 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -110,6 +110,7 @@ public override async Task> VisitLocalDeclarationSta string methodName; SyntaxTokenList methodModifiers; + string parentAccessorKind = null; if (_methodNode is VBSyntax.MethodBlockSyntax methodBlock) { var methodStatement = methodBlock.BlockStatement as VBSyntax.MethodStatementSyntax; methodModifiers = methodStatement.Modifiers; @@ -120,13 +121,14 @@ public override async Task> VisitLocalDeclarationSta } else if (_methodNode is VBSyntax.AccessorBlockSyntax accessorBlock) { var propertyBlock = accessorBlock.Parent as VBSyntax.PropertyBlockSyntax; methodName = propertyBlock.PropertyStatement.Identifier.Text; + parentAccessorKind = accessorBlock.IsKind(VBasic.SyntaxKind.GetAccessorBlock) ? "PropertyGet" : "PropertySet"; methodModifiers = propertyBlock.PropertyStatement.Modifiers; } else { throw new NotImplementedException(_methodNode.GetType() + " not implemented!"); } var isVbShared = methodModifiers.Any(a => a.IsKind(VBasic.SyntaxKind.SharedKeyword)); - _perScopeState.HoistToTopLevel(new HoistedFieldFromVbStaticVariable(methodName, variable.Identifier.Text, initializeValue, decl.Declaration.Type, isVbShared)); + _perScopeState.HoistToTopLevel(new HoistedFieldFromVbStaticVariable(methodName, variable.Identifier.Text, parentAccessorKind, initializeValue, decl.Declaration.Type, isVbShared)); } } else { var shouldPullVariablesBeforeLoop = _perScopeState.IsInsideLoop() && declarator.Initializer is null && declarator.AsClause is not VBSyntax.AsNewClauseSyntax; diff --git a/CodeConverter/CSharp/PerScopeState.cs b/CodeConverter/CSharp/PerScopeState.cs index 9a467a4e..65866da2 100644 --- a/CodeConverter/CSharp/PerScopeState.cs +++ b/CodeConverter/CSharp/PerScopeState.cs @@ -157,18 +157,22 @@ public async Task> CreateVbStaticFieldsAsync var declarations = new List(); var fieldInfo = GetFields(); - var newNames = fieldInfo.ToDictionary(f => f.OriginalVariableName, f => + var newNames = fieldInfo.ToDictionary(f => f.PrefixedOriginalVariableName, f => NameGenerator.CS.GetUniqueVariableNameInScope(semanticModel, generatedNames, typeNode, f.FieldName) ); foreach (var field in fieldInfo) { var decl = (field.Initializer != null) - ? CommonConversions.CreateVariableDeclarationAndAssignment(newNames[field.OriginalVariableName], field.Initializer, field.Type) - : SyntaxFactory.VariableDeclaration(field.Type, SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(newNames[field.OriginalVariableName]))); + ? CommonConversions.CreateVariableDeclarationAndAssignment(newNames[field.PrefixedOriginalVariableName], field.Initializer, field.Type) + : SyntaxFactory.VariableDeclaration(field.Type, SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(newNames[field.PrefixedOriginalVariableName]))); var modifiers = new List { CS.SyntaxFactory.Token(SyntaxKind.PrivateKeyword) }; if (field.IsStatic || namedTypeSymbol.IsModuleType()) { modifiers.Add(CS.SyntaxFactory.Token(SyntaxKind.StaticKeyword)); } - declarations.Add(CS.SyntaxFactory.FieldDeclaration(CS.SyntaxFactory.List(), CS.SyntaxFactory.TokenList(modifiers), decl)); + var fieldDecl = CS.SyntaxFactory.FieldDeclaration(CS.SyntaxFactory.List(), CS.SyntaxFactory.TokenList(modifiers), decl); + if (!string.IsNullOrEmpty(field.OriginalParentAccessorKind)) { + fieldDecl = fieldDecl.WithParentPropertyAccessorKind(field.OriginalParentAccessorKind); + } + declarations.Add(fieldDecl); } var statementsWithUpdatedIds = ReplaceNames(declarations.Concat(csNodes), newNames); @@ -185,11 +189,23 @@ public static IEnumerable ReplaceNames(IEnumerable csNodes, Dictionary< public static T ReplaceNames(T csNode, Dictionary newNames) where T : SyntaxNode { return csNode.ReplaceNodes(csNode.DescendantNodesAndSelf().OfType(), (_, idns) => { - if (newNames.TryGetValue(idns.Identifier.ValueText, out var newName)) { + if (TryGetValue(newNames, idns, out var newName)) { return idns.WithoutAnnotations(AdditionalLocalAnnotation).WithIdentifier(CS.SyntaxFactory.Identifier(newName)); } return idns; }); + + static bool TryGetValue(Dictionary newNames, IdentifierNameSyntax idns, out string newName) + { + var ann = idns.GetAnnotations(AnnotationConstants.ParentPropertyAccessorKindAnnotation).FirstOrDefault(); + var key = GetPrefixedName(ann?.Data, idns.Identifier.ValueText); + return newNames.TryGetValue(key, out newName); + } + } + + internal static string GetPrefixedName(string prefix, string name) + { + return string.IsNullOrEmpty(prefix) ? name : $"<{prefix}>.{name}"; } public IEnumerable ConvertExit(VBasic.SyntaxKind vbBlockKeywordKind) diff --git a/CodeConverter/Common/AnnotationConstants.cs b/CodeConverter/Common/AnnotationConstants.cs index d6b16a61..125021a0 100644 --- a/CodeConverter/Common/AnnotationConstants.cs +++ b/CodeConverter/Common/AnnotationConstants.cs @@ -12,6 +12,7 @@ internal static class AnnotationConstants public const string SourceEndLineAnnotationKind = "CodeConverter.SourceEndLine"; public const string LeadingTriviaAlreadyMappedAnnotation = nameof(CodeConverter) + "." + nameof(LeadingTriviaAlreadyMappedAnnotation); public const string TrailingTriviaAlreadyMappedAnnotation = nameof(CodeConverter) + "." + nameof(TrailingTriviaAlreadyMappedAnnotation); + public const string ParentPropertyAccessorKindAnnotation = nameof(CodeConverter) + "." + nameof(ParentPropertyAccessorKindAnnotation); private static string AsString(LinePosition position) { @@ -40,4 +41,9 @@ public static SyntaxAnnotation SourceEndLine(FileLinePositionSpan origLinespan) { return new SyntaxAnnotation(SourceEndLineAnnotationKind, origLinespan.EndLinePosition.Line.ToString(CultureInfo.InvariantCulture)); } + + public static SyntaxAnnotation ParentPropertyAccessorKind(string accessorKind) + { + return new SyntaxAnnotation(ParentPropertyAccessorKindAnnotation, accessorKind); + } } \ No newline at end of file diff --git a/CodeConverter/Util/SyntaxNodeExtensions.cs b/CodeConverter/Util/SyntaxNodeExtensions.cs index ac4ba8f2..0072c282 100644 --- a/CodeConverter/Util/SyntaxNodeExtensions.cs +++ b/CodeConverter/Util/SyntaxNodeExtensions.cs @@ -173,6 +173,11 @@ private static T WithoutSourceMappingNonRecursive(this T node) where T : Synt return node.WithoutAnnotations(AnnotationConstants.SourceStartLineAnnotationKind).WithoutAnnotations(AnnotationConstants.SourceEndLineAnnotationKind); } + public static T WithParentPropertyAccessorKind(this T node, string accessorKind) where T : SyntaxNode + { + return node.WithAdditionalAnnotations(AnnotationConstants.ParentPropertyAccessorKind(accessorKind)); + } + private static bool IsBlockParent(SyntaxNode converted, SyntaxToken lastCsConvertedToken) { return lastCsConvertedToken.Parent == converted || lastCsConvertedToken.Parent is BlockSyntax b && b.Parent == converted; From c51479a67a36d7dc6e50b792e9c48af56b57ad04 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Fri, 26 Jul 2024 11:25:03 +0200 Subject: [PATCH 3/4] change the type of OriginalParentAccessorKind from string to MethodKind (enum) --- CodeConverter/CSharp/ExpressionNodeVisitor.cs | 2 +- CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs | 6 +++--- .../CSharp/MethodBodyExecutableStatementVisitor.cs | 4 ++-- CodeConverter/CSharp/PerScopeState.cs | 2 +- CodeConverter/Util/SyntaxNodeExtensions.cs | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CodeConverter/CSharp/ExpressionNodeVisitor.cs b/CodeConverter/CSharp/ExpressionNodeVisitor.cs index 2ebd978a..a0e69a0d 100644 --- a/CodeConverter/CSharp/ExpressionNodeVisitor.cs +++ b/CodeConverter/CSharp/ExpressionNodeVisitor.cs @@ -1545,7 +1545,7 @@ public override async Task VisitIdentifierName(VBasic.Syntax.I 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.ToString()); + qualifiedIdentifier = qualifiedIdentifier.WithParentPropertyAccessorKind(m.MethodKind); } var vbMethodBlock = node.Ancestors().OfType().FirstOrDefault(); diff --git a/CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs b/CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs index dbbfffc2..62813e64 100644 --- a/CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs +++ b/CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs @@ -6,12 +6,12 @@ internal class HoistedFieldFromVbStaticVariable : IHoistedNode { public string OriginalMethodName { get; } public string OriginalVariableName { get; } - public string OriginalParentAccessorKind { get; } + public MethodKind OriginalParentAccessorKind { get; } public ExpressionSyntax Initializer { get; } public TypeSyntax Type { get; } public bool IsStatic { get; } - public HoistedFieldFromVbStaticVariable(string originalMethodName, string originalVariableName, string originalParentAccessorKind, ExpressionSyntax initializer, TypeSyntax type, bool isStatic) + public HoistedFieldFromVbStaticVariable(string originalMethodName, string originalVariableName, MethodKind originalParentAccessorKind, ExpressionSyntax initializer, TypeSyntax type, bool isStatic) { OriginalMethodName = originalMethodName; OriginalVariableName = originalVariableName; @@ -22,5 +22,5 @@ public HoistedFieldFromVbStaticVariable(string originalMethodName, string origin } public string FieldName => OriginalMethodName != null ? $"_{OriginalMethodName}_{OriginalVariableName}" : $"_{OriginalVariableName}"; - public string PrefixedOriginalVariableName => PerScopeState.GetPrefixedName(OriginalParentAccessorKind, OriginalVariableName); + public string PrefixedOriginalVariableName => PerScopeState.GetPrefixedName(OriginalParentAccessorKind.ToString(), OriginalVariableName); } \ No newline at end of file diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index 672b5f97..2b041e16 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -110,7 +110,7 @@ public override async Task> VisitLocalDeclarationSta string methodName; SyntaxTokenList methodModifiers; - string parentAccessorKind = null; + MethodKind parentAccessorKind = MethodKind.Ordinary; if (_methodNode is VBSyntax.MethodBlockSyntax methodBlock) { var methodStatement = methodBlock.BlockStatement as VBSyntax.MethodStatementSyntax; methodModifiers = methodStatement.Modifiers; @@ -121,7 +121,7 @@ public override async Task> VisitLocalDeclarationSta } else if (_methodNode is VBSyntax.AccessorBlockSyntax accessorBlock) { var propertyBlock = accessorBlock.Parent as VBSyntax.PropertyBlockSyntax; methodName = propertyBlock.PropertyStatement.Identifier.Text; - parentAccessorKind = accessorBlock.IsKind(VBasic.SyntaxKind.GetAccessorBlock) ? "PropertyGet" : "PropertySet"; + parentAccessorKind = accessorBlock.IsKind(VBasic.SyntaxKind.GetAccessorBlock) ? MethodKind.PropertyGet : MethodKind.PropertySet; methodModifiers = propertyBlock.PropertyStatement.Modifiers; } else { throw new NotImplementedException(_methodNode.GetType() + " not implemented!"); diff --git a/CodeConverter/CSharp/PerScopeState.cs b/CodeConverter/CSharp/PerScopeState.cs index 65866da2..c53645b3 100644 --- a/CodeConverter/CSharp/PerScopeState.cs +++ b/CodeConverter/CSharp/PerScopeState.cs @@ -169,7 +169,7 @@ public async Task> CreateVbStaticFieldsAsync modifiers.Add(CS.SyntaxFactory.Token(SyntaxKind.StaticKeyword)); } var fieldDecl = CS.SyntaxFactory.FieldDeclaration(CS.SyntaxFactory.List(), CS.SyntaxFactory.TokenList(modifiers), decl); - if (!string.IsNullOrEmpty(field.OriginalParentAccessorKind)) { + if (field.OriginalParentAccessorKind != MethodKind.Ordinary) { fieldDecl = fieldDecl.WithParentPropertyAccessorKind(field.OriginalParentAccessorKind); } declarations.Add(fieldDecl); diff --git a/CodeConverter/Util/SyntaxNodeExtensions.cs b/CodeConverter/Util/SyntaxNodeExtensions.cs index 0072c282..f5f74ea6 100644 --- a/CodeConverter/Util/SyntaxNodeExtensions.cs +++ b/CodeConverter/Util/SyntaxNodeExtensions.cs @@ -173,9 +173,9 @@ private static T WithoutSourceMappingNonRecursive(this T node) where T : Synt return node.WithoutAnnotations(AnnotationConstants.SourceStartLineAnnotationKind).WithoutAnnotations(AnnotationConstants.SourceEndLineAnnotationKind); } - public static T WithParentPropertyAccessorKind(this T node, string accessorKind) where T : SyntaxNode + public static T WithParentPropertyAccessorKind(this T node, MethodKind accessorKind) where T : SyntaxNode { - return node.WithAdditionalAnnotations(AnnotationConstants.ParentPropertyAccessorKind(accessorKind)); + return node.WithAdditionalAnnotations(AnnotationConstants.ParentPropertyAccessorKind(accessorKind.ToString())); } private static bool IsBlockParent(SyntaxNode converted, SyntaxToken lastCsConvertedToken) From 430d49bd4d4674b517be22679e1de84c56247880 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Fri, 26 Jul 2024 11:49:08 +0200 Subject: [PATCH 4/4] fix a bug --- CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs b/CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs index 62813e64..959c7c51 100644 --- a/CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs +++ b/CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs @@ -22,5 +22,5 @@ public HoistedFieldFromVbStaticVariable(string originalMethodName, string origin } public string FieldName => OriginalMethodName != null ? $"_{OriginalMethodName}_{OriginalVariableName}" : $"_{OriginalVariableName}"; - public string PrefixedOriginalVariableName => PerScopeState.GetPrefixedName(OriginalParentAccessorKind.ToString(), OriginalVariableName); + public string PrefixedOriginalVariableName => PerScopeState.GetPrefixedName(OriginalParentAccessorKind == MethodKind.Ordinary ? "" : OriginalParentAccessorKind.ToString(), OriginalVariableName); } \ No newline at end of file