Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CodeConverter/CSharp/CommonConversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,12 @@ public SyntaxToken ConvertIdentifier(SyntaxToken id, bool isAttribute = false, S
// AND the first explicitly declared parameter is this symbol, we need to replace it with value.
text = "value";
} else if (normalizedText.StartsWith("_", StringComparison.OrdinalIgnoreCase) && idSymbol is IFieldSymbol propertyFieldSymbol && propertyFieldSymbol.AssociatedSymbol?.IsKind(SymbolKind.Property) == true) {
text = propertyFieldSymbol.AssociatedSymbol.Name;
// For virtual auto-properties, VB backing field _Prop maps to the C# MyClassProp backing property (bypasses virtual dispatch).
// Exception: when accessed as MyClass._Prop, NameExpressionNodeVisitor adds the "MyClass" prefix itself, so we just return the property name.
var isAccessedViaMyClass = id.Parent?.Parent is VBSyntax.MemberAccessExpressionSyntax { Expression: VBSyntax.MyClassExpressionSyntax };
text = !isAccessedViaMyClass && propertyFieldSymbol.IsImplicitlyDeclared && propertyFieldSymbol.AssociatedSymbol is IPropertySymbol { IsVirtual: true, IsAbstract: false } vProp
? "MyClass" + vProp.Name
: propertyFieldSymbol.AssociatedSymbol.Name;
} else if (normalizedText.EndsWith("Event", StringComparison.OrdinalIgnoreCase) && idSymbol is IFieldSymbol eventFieldSymbol && eventFieldSymbol.AssociatedSymbol?.IsKind(SymbolKind.Event) == true) {
text = eventFieldSymbol.AssociatedSymbol.Name;
} else if (WinformsConversions.MayNeedToInlinePropertyAccess(id.Parent, idSymbol) && _typeContext.HandledEventsAnalysis.ShouldGeneratePropertyFor(idSymbol.Name)) {
Expand Down
15 changes: 14 additions & 1 deletion CodeConverter/CSharp/DeclarationNodeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -689,12 +689,25 @@ private static async Task<BlockSyntax> ConvertStatementsAsync(SyntaxList<VBSynta
return CS.SyntaxFactory.Block(await statements.SelectManyAsync(async s => (IEnumerable<StatementSyntax>) await s.Accept(methodBodyVisitor)));
}

private static HashSet<string> GetMyClassAccessedNames(VBSyntax.ClassBlockSyntax classBlock)
private HashSet<string> GetMyClassAccessedNames(VBSyntax.ClassBlockSyntax classBlock)
{
var memberAccesses = classBlock.DescendantNodes().OfType<VBSyntax.MemberAccessExpressionSyntax>();
var accessedTextNames = new HashSet<string>(memberAccesses
.Where(mae => mae.Expression is VBSyntax.MyClassExpressionSyntax)
.Select(mae => mae.Name.Identifier.Text), StringComparer.OrdinalIgnoreCase);

// Also treat direct backing field access (_Prop) as MyClass access for virtual auto-properties.
// In VB, writing _Prop directly accesses the backing field, bypassing virtual dispatch -
// the same semantics as MyClass.Prop. In C#, these virtual properties get a MyClassProp
// backing property, so _Prop must map to MyClassProp.
var backingFieldIdentifiers = classBlock.DescendantNodes().OfType<VBSyntax.IdentifierNameSyntax>()
.Where(id => id.Identifier.ValueText.StartsWith("_", StringComparison.OrdinalIgnoreCase));
foreach (var id in backingFieldIdentifiers) {
if (_semanticModel.GetSymbolInfo(id).Symbol is IFieldSymbol { IsImplicitlyDeclared: true, AssociatedSymbol: IPropertySymbol { IsVirtual: true, IsAbstract: false } associatedProp }) {
accessedTextNames.Add(associatedProp.Name);
}
}

return accessedTextNames;
}

Expand Down
45 changes: 45 additions & 0 deletions Tests/CSharp/MemberTests/PropertyMemberTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,51 @@ public static IEnumerable<object[]> SomeObjects
yield return new object[3];
}
}
}");
}

/// <summary>Issue #827: VB auto-property backing field access (_Prop) should map to MyClassProp for overridable properties</summary>
[Fact]
public async Task TestOverridableAutoPropertyBackingFieldAccessAsync()
{
await TestConversionVisualBasicToCSharpAsync(@"Class Foo
Overridable Property Prop As Integer = 5

Sub Test()
_Prop = 10
Dim isCorrect = MyClass.Prop = 10
End Sub
End Class
Class Child
Inherits Foo
Overrides Property Prop As Integer = 20
End Class", @"
internal partial class Foo
{
public int MyClassProp { get; set; } = 5;

public virtual int Prop
{
get
{
return MyClassProp;
}

set
{
MyClassProp = value;
}
}

public void Test()
{
MyClassProp = 10;
bool isCorrect = MyClassProp == 10;
}
}
internal partial class Child : Foo
{
public override int Prop { get; set; } = 20;
}");
}
}
2 changes: 1 addition & 1 deletion Tests/TestRunners/TestFileRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ private static Dictionary<string, string> GetTestFileContents()
private static string GetTestSourceDirectoryPath()
{
string assemblyDir = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().Location).AbsolutePath);
string testSourceDirectoryPath = Path.Combine(assemblyDir, @"..\..\");
string testSourceDirectoryPath = Path.Combine(assemblyDir, "..", "..", "..");
return testSourceDirectoryPath;
}

Expand Down
Loading