Skip to content

Commit 26dc64e

Browse files
Workaround another Roslyn MyBase issue - fixes #600
1 parent 8326ee1 commit 26dc64e

4 files changed

Lines changed: 123 additions & 83 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1111
### VB -> C#
1212
* Convert parameterized properties with optional parameters [#597](https://github.com/icsharpcode/CodeConverter/issues/597)
1313
* Convert bitwise negation [#599](https://github.com/icsharpcode/CodeConverter/issues/599)
14+
* No longer adds incorrect "base" qualification for virtual method calls [#600](https://github.com/icsharpcode/CodeConverter/issues/600)
1415

1516
### C# -> VB
1617

Lines changed: 47 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Threading;
22
using ICSharpCode.CodeConverter.Shared;
33
using ICSharpCode.CodeConverter.Util;
4+
using ICSharpCode.CodeConverter.Util.FromRoslyn;
45
using Microsoft.CodeAnalysis;
56
using Microsoft.CodeAnalysis.Operations;
67
using Microsoft.CodeAnalysis.Simplification;
@@ -14,40 +15,38 @@ internal class VbNameExpander : ISyntaxExpander
1415
private static readonly SyntaxToken _dotToken = Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory.Token(Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.DotToken);
1516
public static ISyntaxExpander Instance { get; } = new VbNameExpander();
1617

17-
public bool ShouldExpandWithinNode(SyntaxNode node, SyntaxNode root, SemanticModel semanticModel)
18-
{
19-
return !ShouldExpandNode(node, root, semanticModel) &&
20-
!IsRoslynInstanceExpressionBug(node as MemberAccessExpressionSyntax); ;
21-
}
18+
public bool ShouldExpandWithinNode(SyntaxNode node, SyntaxNode root, SemanticModel semanticModel) =>
19+
!ShouldExpandNode(node, root, semanticModel) && !IsRoslynInstanceExpressionBug(node as MemberAccessExpressionSyntax);
2220

23-
public bool ShouldExpandNode(SyntaxNode node, SyntaxNode root, SemanticModel semanticModel)
24-
{
25-
return ShouldExpandName(node) ||
26-
ShouldExpandMemberAccess(node, root, semanticModel);
27-
}
21+
public bool ShouldExpandNode(SyntaxNode node, SyntaxNode root, SemanticModel semanticModel) =>
22+
ShouldExpandName(node) || ShouldExpandMemberAccess(node, root, semanticModel);
2823

2924
private static bool ShouldExpandMemberAccess(SyntaxNode node, SyntaxNode root, SemanticModel semanticModel)
3025
{
3126
return node is MemberAccessExpressionSyntax maes && !IsRoslynInstanceExpressionBug(maes) &&
32-
ShouldBeQualified(node, semanticModel.GetSymbolInfo(node).Symbol, semanticModel, root);
27+
ShouldBeQualified(node, semanticModel.GetSymbolInfo(node).Symbol, semanticModel);
3328
}
3429

35-
private static bool ShouldExpandName(SyntaxNode node)
36-
{
37-
return node is NameSyntax && NameCanBeExpanded(node);
38-
}
30+
private static bool ShouldExpandName(SyntaxNode node) =>
31+
node is NameSyntax && NameCanBeExpanded(node);
3932

4033
public SyntaxNode ExpandNode(SyntaxNode node, SyntaxNode root, SemanticModel semanticModel,
4134
Workspace workspace)
4235
{
4336
var symbol = semanticModel.GetSymbolInfo(node).Symbol;
44-
if (node is SimpleNameSyntax sns && IsMyBaseBug(node, symbol, root, semanticModel) && semanticModel.GetOperation(node) is IMemberReferenceOperation mro && mro.Instance != null) {
45-
var expressionSyntax = (ExpressionSyntax)mro.Instance.Syntax;
46-
return MemberAccess(expressionSyntax, sns);
37+
if (node is SimpleNameSyntax sns && GetDefaultImplicitInstance(sns, symbol, semanticModel) is {} defaultImplicitInstance) {
38+
if (defaultImplicitInstance.InheritsFromOrEquals(symbol.ContainingType)) {
39+
return MemberAccess(SyntaxFactory.MeExpression(), sns);
40+
}
41+
42+
if (semanticModel.GetOperation(node) is IMemberReferenceOperation { Instance: { Syntax: ExpressionSyntax implicitInstance } }) {
43+
return MemberAccess(implicitInstance, sns);
44+
}
4745
}
48-
if (node is MemberAccessExpressionSyntax maes && IsTypePromotion(node, symbol, root, semanticModel) && semanticModel.GetOperation(node) is IMemberReferenceOperation mro2 && mro2.Instance != null) {
49-
var expressionSyntax = (ExpressionSyntax)mro2.Instance.Syntax;
50-
return MemberAccess(expressionSyntax, SyntaxFactory.IdentifierName(mro2.Member.Name));
46+
47+
if (node is MemberAccessExpressionSyntax && IsTypePromotion(node, symbol, semanticModel) &&
48+
semanticModel.GetOperation(node) is IMemberReferenceOperation { Instance: { Syntax: ExpressionSyntax promotedInstance }, Member: {} member }) {
49+
return MemberAccess(promotedInstance, SyntaxFactory.IdentifierName(member.Name));
5150
}
5251
return IsOriginalSymbolGenericMethod(semanticModel, node) ? node : Simplifier.Expand(node, semanticModel, workspace);
5352
}
@@ -59,12 +58,23 @@ public SyntaxNode ExpandNode(SyntaxNode node, SyntaxNode root, SemanticModel sem
5958
/// This leaves the possibility of not qualifying some instance references which didn't contain
6059
/// </summary>
6160
private static bool ShouldBeQualified(SyntaxNode node,
62-
ISymbol symbol, SemanticModel semanticModel, SyntaxNode root)
61+
ISymbol symbol, SemanticModel semanticModel)
6362
{
64-
65-
return symbol?.IsStatic == true || IsMyBaseBug(node, symbol, root, semanticModel) || IsTypePromotion(node, symbol, root, semanticModel);
63+
return symbol?.IsStatic == true || CouldBeMyBaseBug(node, symbol, semanticModel) || IsTypePromotion(node, symbol, semanticModel);
6664
}
6765

66+
/// <summary>Need to workaround roslyn bug that accidentally uses MyBase instead of Me, or implicit instances like Forms</summary>
67+
/// See https://github.com/dotnet/roslyn/blob/97123b393c3a5a91cc798b329db0d7fc38634784/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.Expander.vb#L657</returns>
68+
private static bool CouldBeMyBaseBug(SyntaxNode node, ISymbol symbol, SemanticModel semanticModel) =>
69+
node is SimpleNameSyntax sns && IsLeftMostQualifier(sns) && GetDefaultImplicitInstance(sns, symbol, semanticModel) is {};
70+
71+
private static bool IsLeftMostQualifier(SimpleNameSyntax node) =>
72+
node?.Parent switch {
73+
MemberAccessExpressionSyntax maes => maes.Expression == node,
74+
ConditionalAccessExpressionSyntax caes => caes.Expression == node,
75+
_ => true
76+
};
77+
6878
private static bool NameCanBeExpanded(SyntaxNode node)
6979
{
7080
// Workaround roslyn bug where it will try to expand something even when the parent node cannot contain the type of the expanded node
@@ -74,22 +84,12 @@ private static bool NameCanBeExpanded(SyntaxNode node)
7484
return true;
7585
}
7686

77-
/// <returns>True iff calling Expand would qualify with MyBase when the symbol isn't in the base type
78-
/// See https://github.com/dotnet/roslyn/blob/97123b393c3a5a91cc798b329db0d7fc38634784/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.Expander.vb#L657</returns>
79-
private static bool IsMyBaseBug(SyntaxNode node, ISymbol symbol, SyntaxNode root, SemanticModel semanticModel)
80-
{
81-
if (IsInstanceReference(symbol) && node is NameSyntax) {
82-
return GetEnclosingNamedType(semanticModel, root, node.SpanStart) is ITypeSymbol
83-
implicitQualifyingSymbol &&
84-
!implicitQualifyingSymbol.ContainsMember(symbol);
85-
}
86-
87-
return false;
88-
}
87+
private static INamedTypeSymbol GetDefaultImplicitInstance(SimpleNameSyntax node, ISymbol symbol, SemanticModel semanticModel, CancellationToken cancellationToken = default) =>
88+
IsQualifiableInstanceReference(symbol) && IsLeftMostQualifier(node) ? semanticModel.GetEnclosingSymbol<INamedTypeSymbol>(node.SpanStart, cancellationToken) : null;
8989

90-
private static bool IsTypePromotion(SyntaxNode node, ISymbol symbol, SyntaxNode root, SemanticModel semanticModel)
90+
private static bool IsTypePromotion(SyntaxNode node, ISymbol symbol, SemanticModel semanticModel)
9191
{
92-
if (IsInstanceReference(symbol) && node is MemberAccessExpressionSyntax maes && maes.Expression != null) {
92+
if (IsQualifiableInstanceReference(symbol) && node is MemberAccessExpressionSyntax maes && maes.Expression != null) {
9393
var qualifyingType = semanticModel.GetTypeInfo(maes.Expression).Type;
9494
return qualifyingType == null || !qualifyingType.ContainsMember(symbol);
9595
}
@@ -100,56 +100,21 @@ private static bool IsTypePromotion(SyntaxNode node, ISymbol symbol, SyntaxNode
100100
/// <summary>
101101
/// Roslyn bug - accidentally expands "New" into an identifier causing compile error
102102
/// </summary>
103-
public static bool IsRoslynInstanceExpressionBug(MemberAccessExpressionSyntax node)
104-
{
105-
return node?.Expression is InstanceExpressionSyntax;
106-
}
103+
public static bool IsRoslynInstanceExpressionBug(MemberAccessExpressionSyntax node) =>
104+
node?.Expression is InstanceExpressionSyntax;
107105

108106
/// <summary>
109107
/// Roslyn bug - accidentally expands anonymous types to just "Global."
110108
/// Since the C# reducer also doesn't seem to reduce generic extension methods, it's best to avoid those too, so let's just avoid all generic methods
111109
/// </summary>
112-
private static bool IsOriginalSymbolGenericMethod(SemanticModel semanticModel, SyntaxNode node)
113-
{
114-
return semanticModel.GetSymbolInfo(node).Symbol.IsGenericMethod();
115-
}
110+
private static bool IsOriginalSymbolGenericMethod(SemanticModel semanticModel, SyntaxNode node) =>
111+
semanticModel.GetSymbolInfo(node).Symbol.IsGenericMethod();
116112

117-
private static bool IsInstanceReference(ISymbol symbol)
118-
{
119-
return symbol?.IsStatic == false && (symbol.Kind == SymbolKind.Method || symbol.Kind ==
120-
SymbolKind.Field || symbol.Kind == SymbolKind.Property);
121-
}
122-
123-
private static MemberAccessExpressionSyntax MemberAccess(ExpressionSyntax expressionSyntax, SimpleNameSyntax sns)
124-
{
125-
return SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
126-
expressionSyntax,
127-
_dotToken,
128-
sns);
129-
}
113+
private static bool IsQualifiableInstanceReference(ISymbol symbol) =>
114+
symbol?.IsStatic == false && (symbol.IsKind(SymbolKind.Method) || symbol.IsKind(SymbolKind.Field) ||
115+
symbol.IsKind(SymbolKind.Property));
130116

131-
/// <summary>
132-
/// Pasted from AbstractGenerateFromMembersCodeRefactoringProvider
133-
/// Gets the enclosing named type for the specified position. We can't use
134-
/// <see cref="SemanticModel.GetEnclosingSymbol"/> because that doesn't return
135-
/// the type you're current on if you're on the header of a class/interface.
136-
/// </summary>
137-
private static INamedTypeSymbol GetEnclosingNamedType(
138-
SemanticModel semanticModel, SyntaxNode root, int start,
139-
CancellationToken cancellationToken = default(CancellationToken))
140-
{
141-
var token = root.FindToken(start);
142-
if (token == ((ICompilationUnitSyntax)root).EndOfFileToken) {
143-
token = token.GetPreviousToken();
144-
}
145-
146-
for (var node = token.Parent; node != null; node = node.Parent) {
147-
if (semanticModel.GetDeclaredSymbol(node) is INamedTypeSymbol declaration) {
148-
return declaration;
149-
}
150-
}
151-
152-
return null;
153-
}
117+
private static MemberAccessExpressionSyntax MemberAccess(ExpressionSyntax expressionSyntax, SimpleNameSyntax sns) =>
118+
SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expressionSyntax, _dotToken, sns);
154119
}
155120
}

CodeConverter/Shared/DocumentExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ private static async Task<Document> ExpandAsync(Document document, ISyntaxExpand
8282
);
8383
return document.WithSyntaxRoot(newRoot);
8484
} catch (Exception ex) {
85-
var warningText = "Conversion warning: Qualified name reduction failed for this file. " + ex;
85+
var warningText = "Conversion warning: Name qualification failed for this file. " + ex;
8686
return document.WithSyntaxRoot(WithWarningAnnotation(root, warningText));
8787
}
8888
}

Tests/CSharp/NamespaceLevelTests.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,5 +982,79 @@ public void TestMethod()
982982
1 source compilation errors:
983983
BC30614: 'MustOverride' method 'Public MustOverride Property P2 As Integer' cannot be called with 'MyClass'.");
984984
}
985+
986+
[Fact]
987+
public async Task OverridenMemberCallAsync()
988+
{
989+
await TestConversionVisualBasicToCSharpAsync(@"
990+
Module Module1
991+
Public Class BaseImpl
992+
Protected Overridable Function GetImplName() As String
993+
Return NameOf(BaseImpl)
994+
End Function
995+
End Class
996+
997+
''' <summary>
998+
''' The fact that this class doesn't contain a definition for GetImplName is crucial to the repro
999+
''' </summary>
1000+
Public Class ErrorSite
1001+
Inherits BaseImpl
1002+
Public Function PublicGetImplName()
1003+
' This must not be qualified with MyBase since the method is overridable
1004+
Return GetImplName()
1005+
End Function
1006+
End Class
1007+
1008+
Public Class OverrideImpl
1009+
Inherits ErrorSite
1010+
Protected Overrides Function GetImplName() As String
1011+
Return NameOf(OverrideImpl)
1012+
End Function
1013+
End Class
1014+
1015+
Sub Main()
1016+
Dim c As New OverrideImpl
1017+
Console.WriteLine(c.PublicGetImplName())
1018+
End Sub
1019+
End Module",
1020+
@"using System;
1021+
1022+
internal static partial class Module1
1023+
{
1024+
public partial class BaseImpl
1025+
{
1026+
protected virtual string GetImplName()
1027+
{
1028+
return nameof(BaseImpl);
1029+
}
1030+
}
1031+
1032+
/// <summary>
1033+
/// The fact that this class doesn't contain a definition for GetImplName is crucial to the repro
1034+
/// </summary>
1035+
public partial class ErrorSite : BaseImpl
1036+
{
1037+
public object PublicGetImplName()
1038+
{
1039+
// This must not be qualified with MyBase since the method is overridable
1040+
return GetImplName();
1041+
}
1042+
}
1043+
1044+
public partial class OverrideImpl : ErrorSite
1045+
{
1046+
protected override string GetImplName()
1047+
{
1048+
return nameof(OverrideImpl);
1049+
}
1050+
}
1051+
1052+
public static void Main()
1053+
{
1054+
var c = new OverrideImpl();
1055+
Console.WriteLine(c.PublicGetImplName());
1056+
}
1057+
}");
1058+
}
9851059
}
9861060
}

0 commit comments

Comments
 (0)