diff --git a/CodeConverter/CSharp/ExpressionNodeVisitor.cs b/CodeConverter/CSharp/ExpressionNodeVisitor.cs index 813f839b1..a0e69a0d8 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); + } + var vbMethodBlock = node.Ancestors().OfType().FirstOrDefault(); if (vbMethodBlock != null && vbMethodBlock.MustReturn() && diff --git a/CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs b/CodeConverter/CSharp/HoistedFieldFromVbStaticVariable.cs index b7e61a402..959c7c514 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 MethodKind 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, MethodKind 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 == MethodKind.Ordinary ? "" : OriginalParentAccessorKind.ToString(), OriginalVariableName); } \ No newline at end of file diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index 9c23f6c6a..2b041e164 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -110,6 +110,7 @@ public override async Task> VisitLocalDeclarationSta string methodName; SyntaxTokenList methodModifiers; + MethodKind parentAccessorKind = MethodKind.Ordinary; 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) ? MethodKind.PropertyGet : MethodKind.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 9a467a4e5..c53645b3d 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 (field.OriginalParentAccessorKind != MethodKind.Ordinary) { + 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 d6b16a61d..125021a02 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 ac4ba8f29..f5f74ea62 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, MethodKind accessorKind) where T : SyntaxNode + { + return node.WithAdditionalAnnotations(AnnotationConstants.ParentPropertyAccessorKind(accessorKind.ToString())); + } + private static bool IsBlockParent(SyntaxNode converted, SyntaxToken lastCsConvertedToken) { return lastCsConvertedToken.Parent == converted || lastCsConvertedToken.Parent is BlockSyntax b && b.Parent == converted; diff --git a/Tests/CSharp/MemberTests/MemberTests.cs b/Tests/CSharp/MemberTests/MemberTests.cs index 1a44e1b61..9abcbb254 100644 --- a/Tests/CSharp/MemberTests/MemberTests.cs +++ b/Tests/CSharp/MemberTests/MemberTests.cs @@ -665,6 +665,45 @@ public void set_Prop(int i, string value) _Prop_bSet = false; } +}"); + } + + [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; + } + } + }"); }