Skip to content

Commit f0d557f

Browse files
Merge pull request #1252 from GrahamTheCoder/claude/fix-issue-827-myclass-backing-field
Claude/fix issue 827 myclass backing field
2 parents 1315673 + ef6d9b9 commit f0d557f

File tree

4 files changed

+66
-3
lines changed

4 files changed

+66
-3
lines changed

CodeConverter/CSharp/CommonConversions.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,12 @@ public SyntaxToken ConvertIdentifier(SyntaxToken id, bool isAttribute = false, S
293293
// AND the first explicitly declared parameter is this symbol, we need to replace it with value.
294294
text = "value";
295295
} else if (normalizedText.StartsWith("_", StringComparison.OrdinalIgnoreCase) && idSymbol is IFieldSymbol propertyFieldSymbol && propertyFieldSymbol.AssociatedSymbol?.IsKind(SymbolKind.Property) == true) {
296-
text = propertyFieldSymbol.AssociatedSymbol.Name;
296+
// For virtual auto-properties, VB backing field _Prop maps to the C# MyClassProp backing property (bypasses virtual dispatch).
297+
// Exception: when accessed as MyClass._Prop, NameExpressionNodeVisitor adds the "MyClass" prefix itself, so we just return the property name.
298+
var isAccessedViaMyClass = id.Parent?.Parent is VBSyntax.MemberAccessExpressionSyntax { Expression: VBSyntax.MyClassExpressionSyntax };
299+
text = !isAccessedViaMyClass && propertyFieldSymbol.IsImplicitlyDeclared && propertyFieldSymbol.AssociatedSymbol is IPropertySymbol { IsVirtual: true, IsAbstract: false } vProp
300+
? "MyClass" + vProp.Name
301+
: propertyFieldSymbol.AssociatedSymbol.Name;
297302
} else if (normalizedText.EndsWith("Event", StringComparison.OrdinalIgnoreCase) && idSymbol is IFieldSymbol eventFieldSymbol && eventFieldSymbol.AssociatedSymbol?.IsKind(SymbolKind.Event) == true) {
298303
text = eventFieldSymbol.AssociatedSymbol.Name;
299304
} else if (WinformsConversions.MayNeedToInlinePropertyAccess(id.Parent, idSymbol) && _typeContext.HandledEventsAnalysis.ShouldGeneratePropertyFor(idSymbol.Name)) {

CodeConverter/CSharp/DeclarationNodeVisitor.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,12 +689,25 @@ 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 HashSet<string> GetMyClassAccessedNames(VBSyntax.ClassBlockSyntax classBlock)
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+
// Also treat direct backing field access (_Prop) as MyClass access for virtual auto-properties.
700+
// In VB, writing _Prop directly accesses the backing field, bypassing virtual dispatch -
701+
// the same semantics as MyClass.Prop. In C#, these virtual properties get a MyClassProp
702+
// backing property, so _Prop must map to MyClassProp.
703+
var backingFieldIdentifiers = classBlock.DescendantNodes().OfType<VBSyntax.IdentifierNameSyntax>()
704+
.Where(id => id.Identifier.ValueText.StartsWith("_", StringComparison.OrdinalIgnoreCase));
705+
foreach (var id in backingFieldIdentifiers) {
706+
if (_semanticModel.GetSymbolInfo(id).Symbol is IFieldSymbol { IsImplicitlyDeclared: true, AssociatedSymbol: IPropertySymbol { IsVirtual: true, IsAbstract: false } associatedProp }) {
707+
accessedTextNames.Add(associatedProp.Name);
708+
}
709+
}
710+
698711
return accessedTextNames;
699712
}
700713

Tests/CSharp/MemberTests/PropertyMemberTests.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,51 @@ public static IEnumerable<object[]> SomeObjects
872872
yield return new object[3];
873873
}
874874
}
875+
}");
876+
}
877+
878+
/// <summary>Issue #827: VB auto-property backing field access (_Prop) should map to MyClassProp for overridable properties</summary>
879+
[Fact]
880+
public async Task TestOverridableAutoPropertyBackingFieldAccessAsync()
881+
{
882+
await TestConversionVisualBasicToCSharpAsync(@"Class Foo
883+
Overridable Property Prop As Integer = 5
884+
885+
Sub Test()
886+
_Prop = 10
887+
Dim isCorrect = MyClass.Prop = 10
888+
End Sub
889+
End Class
890+
Class Child
891+
Inherits Foo
892+
Overrides Property Prop As Integer = 20
893+
End Class", @"
894+
internal partial class Foo
895+
{
896+
public int MyClassProp { get; set; } = 5;
897+
898+
public virtual int Prop
899+
{
900+
get
901+
{
902+
return MyClassProp;
903+
}
904+
905+
set
906+
{
907+
MyClassProp = value;
908+
}
909+
}
910+
911+
public void Test()
912+
{
913+
MyClassProp = 10;
914+
bool isCorrect = MyClassProp == 10;
915+
}
916+
}
917+
internal partial class Child : Foo
918+
{
919+
public override int Prop { get; set; } = 20;
875920
}");
876921
}
877922
}

Tests/TestRunners/TestFileRewriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ private static Dictionary<string, string> GetTestFileContents()
2020
private static string GetTestSourceDirectoryPath()
2121
{
2222
string assemblyDir = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().Location).AbsolutePath);
23-
string testSourceDirectoryPath = Path.Combine(assemblyDir, @"..\..\");
23+
string testSourceDirectoryPath = Path.Combine(assemblyDir, "..", "..", "..");
2424
return testSourceDirectoryPath;
2525
}
2626

0 commit comments

Comments
 (0)