Skip to content

Commit 054d9b0

Browse files
Fix property backing field conversion for virtual properties
This commit fixes issue #827 where accessing the auto-generated backing field (e.g. `_Prop`) of an overridable (virtual) property in VB.NET was incorrectly converted to the virtual property access (`Prop`) in C#. It now correctly maps these accesses to the explicitly generated non-virtual backing property `MyClassProp` and ensures that the MyClassProp property is correctly generated when such an access occurs. A test case has been added to MemberTests.cs to verify the correct translation of such field access. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent c289dcd commit 054d9b0

File tree

3 files changed

+62
-3
lines changed

3 files changed

+62
-3
lines changed

CodeConverter/CSharp/CommonConversions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,9 @@ public SyntaxToken ConvertIdentifier(SyntaxToken id, bool isAttribute = false, S
294294
text = "value";
295295
} else if (normalizedText.StartsWith("_", StringComparison.OrdinalIgnoreCase) && idSymbol is IFieldSymbol propertyFieldSymbol && propertyFieldSymbol.AssociatedSymbol?.IsKind(SymbolKind.Property) == true) {
296296
text = propertyFieldSymbol.AssociatedSymbol.Name;
297+
if (propertyFieldSymbol.AssociatedSymbol.IsVirtual && !propertyFieldSymbol.AssociatedSymbol.IsAbstract) {
298+
text = "MyClass" + text;
299+
}
297300
} else if (normalizedText.EndsWith("Event", StringComparison.OrdinalIgnoreCase) && idSymbol is IFieldSymbol eventFieldSymbol && eventFieldSymbol.AssociatedSymbol?.IsKind(SymbolKind.Event) == true) {
298301
text = eventFieldSymbol.AssociatedSymbol.Name;
299302
} else if (WinformsConversions.MayNeedToInlinePropertyAccess(id.Parent, idSymbol) && _typeContext.HandledEventsAnalysis.ShouldGeneratePropertyFor(idSymbol.Name)) {

CodeConverter/CSharp/DeclarationNodeVisitor.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ var dummyClass
336336

337337
public override async Task<CSharpSyntaxNode> VisitClassBlock(VBSyntax.ClassBlockSyntax node)
338338
{
339-
_accessorDeclarationNodeConverter.AccessedThroughMyClass = GetMyClassAccessedNames(node);
339+
_accessorDeclarationNodeConverter.AccessedThroughMyClass = GetMyClassAccessedNames(node, _semanticModel);
340340
var classStatement = node.ClassStatement;
341341
var attributes = await CommonConversions.ConvertAttributesAsync(classStatement.AttributeLists);
342342
var (parameters, constraints) = await SplitTypeParametersAsync(classStatement.TypeParameterList);
@@ -689,12 +689,23 @@ private static async Task<BlockSyntax> ConvertStatementsAsync(SyntaxList<VBSynta
689689
return CS.SyntaxFactory.Block(await statements.SelectManyAsync(async s => (IEnumerable<StatementSyntax>) await s.Accept(methodBodyVisitor)));
690690
}
691691

692-
private static HashSet<string> GetMyClassAccessedNames(VBSyntax.ClassBlockSyntax classBlock)
692+
private static HashSet<string> GetMyClassAccessedNames(VBSyntax.ClassBlockSyntax classBlock, SemanticModel semanticModel)
693693
{
694694
var memberAccesses = classBlock.DescendantNodes().OfType<VBSyntax.MemberAccessExpressionSyntax>();
695695
var accessedTextNames = new HashSet<string>(memberAccesses
696696
.Where(mae => mae.Expression is VBSyntax.MyClassExpressionSyntax)
697697
.Select(mae => mae.Name.Identifier.Text), StringComparer.OrdinalIgnoreCase);
698+
699+
var identifierNames = classBlock.DescendantNodes().OfType<VBSyntax.IdentifierNameSyntax>().Where(id => id.Identifier.Text.StartsWith("_", StringComparison.OrdinalIgnoreCase));
700+
foreach (var id in identifierNames)
701+
{
702+
var symbolInfo = semanticModel.GetSymbolInfo(id);
703+
if (symbolInfo.Symbol is IFieldSymbol fieldSymbol && fieldSymbol.AssociatedSymbol != null && fieldSymbol.AssociatedSymbol.IsVirtual && !fieldSymbol.AssociatedSymbol.IsAbstract)
704+
{
705+
accessedTextNames.Add(fieldSymbol.AssociatedSymbol.Name);
706+
}
707+
}
708+
698709
return accessedTextNames;
699710
}
700711

Tests/VB/MemberTests.cs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1495,4 +1495,49 @@ End Sub
14951495
End Class");
14961496
}
14971497

1498-
}
1498+
1499+
[Fact]
1500+
public async Task TestMyClassPropertyAccess()
1501+
{
1502+
await TestConversionVisualBasicToCSharpAsync(@"
1503+
Class Foo
1504+
Overridable Property Prop As Integer = 5
1505+
1506+
Sub Test()
1507+
_Prop = 10 ' This should convert to MyClassProp = 10 not to Prop = 10
1508+
Dim isCorrect = MyClass.Prop = 10 ' After conversion this will return 5instead of 10 because we wrote to Child.Prop
1509+
End Sub
1510+
End Class
1511+
Class Child
1512+
Inherits Foo
1513+
Overrides Property Prop As Integer = 20
1514+
End Class", @"
1515+
internal partial class Foo
1516+
{
1517+
public int MyClassProp { get; set; } = 5;
1518+
1519+
public virtual int Prop
1520+
{
1521+
get
1522+
{
1523+
return MyClassProp;
1524+
}
1525+
1526+
set
1527+
{
1528+
MyClassProp = value;
1529+
}
1530+
}
1531+
1532+
public void Test()
1533+
{
1534+
MyClassProp = 10; // This should convert to MyClassProp = 10 not to Prop = 10
1535+
bool isCorrect = MyClassProp == 10; // After conversion this will return 5instead of 10 because we wrote to Child.Prop
1536+
}
1537+
}
1538+
internal partial class Child : Foo
1539+
{
1540+
public override int Prop { get; set; } = 20;
1541+
}");
1542+
}
1543+
}

0 commit comments

Comments
 (0)