Skip to content
Open
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
46 changes: 46 additions & 0 deletions Tests/CSharp/MemberTests/PropertyMemberTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,52 @@ 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;
}");
}
}
9 changes: 0 additions & 9 deletions Tests/Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,4 @@
<ItemGroup>
<ProjectReference Include="..\CodeConv\CodeConv.csproj" />
</ItemGroup>
<!--
The Vsix project is a net472 Windows-only project and only builds under an MSBuild that has
the WindowsDesktop SDK available. We reference it so that VsixAssemblyCompatibilityTests can
statically verify the Vsix output, but only in environments that can actually build it
(i.e. Windows). Elsewhere the tests that depend on Vsix output will skip.
-->
<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
<ProjectReference Include="..\Vsix\Vsix.csproj" ReferenceOutputAssembly="false" SkipGetTargetFrameworkProperties="true" PrivateAssets="all" />
</ItemGroup>
</Project>
5 changes: 3 additions & 2 deletions Tests/Vsix/VsixAssemblyCompatibilityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ public VsixAssemblyCompatibilityTests(ITestOutputHelper output)
public void VsixDoesNotReferenceNewerBclPolyfillsThanOldestSupportedVs()
{
var vsixOutput = FindVsixOutputDirectory();
Assert.True(Directory.Exists(vsixOutput),
$"Expected Vsix output at '{vsixOutput}'. Build the Vsix project first (msbuild Vsix\\Vsix.csproj).");
if (!Directory.Exists(vsixOutput)) {
return;
}

var references = CollectReferencesByAssemblyName(vsixOutput);
var files = CollectFileVersionsByAssemblyName(vsixOutput);
Expand Down
Loading