Skip to content

Commit bdcc7b6

Browse files
Rename explicit method implementations where needed - fixes #492
1 parent e9d18fd commit bdcc7b6

4 files changed

Lines changed: 73 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1717
* Fix conversion for string implicitly converted to enum [#476](https://github.com/icsharpcode/CodeConverter/issues/476)
1818

1919
### C# -> VB
20-
20+
* Rename explicit method implementations where needed [#492](https://github.com/icsharpcode/CodeConverter/issues/492)
2121

2222
## [8.1.4] - 2020-06-26
2323

CodeConverter/VB/ClashingMemberRenamer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ public static async Task<Project> RenameClashingSymbolsAsync(Project project)
2828

2929
private static IEnumerable<(ISymbol Original, string NewName)> GetSymbolsWithNewNames(INamespaceOrTypeSymbol containerSymbol, Compilation compilation)
3030
{
31-
var members = containerSymbol.GetMembers().Where(m => m.Locations.Any(loc => loc.SourceTree != null && compilation.ContainsSyntaxTree(loc.SourceTree))).ToArray();
31+
var members = containerSymbol.GetMembers()
32+
.Where(m => m.Locations.Any(loc => loc.SourceTree != null && compilation.ContainsSyntaxTree(loc.SourceTree))).ToArray();
3233
var symbolSets = GetLocalSymbolSets(containerSymbol, compilation, members).Concat(members.AsEnumerable().Yield());
3334
return symbolSets.SelectMany(GetUniqueNamesForSymbolSet);
3435
}

CodeConverter/VB/NodesVisitor.cs

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.Immutable;
34
using System.Linq;
45
using ICSharpCode.CodeConverter.Shared;
56
using ICSharpCode.CodeConverter.Util;
@@ -50,6 +51,7 @@ internal class NodesVisitor : CS.CSharpSyntaxVisitor<VisualBasicSyntaxNode>
5051
private readonly HashSet<string> _extraImports = new HashSet<string>();
5152
private readonly CSharpHelperMethodDefinition _cSharpHelperMethodDefinition;
5253
private readonly CommonConversions _commonConversions;
54+
private readonly HashSet<string> _addedNames = new HashSet<string>();
5355

5456
private int _placeholder = 1;
5557
public CommentConvertingVisitorWrapper<VisualBasicSyntaxNode> TriviaConvertingVisitor { get; }
@@ -432,7 +434,7 @@ public override VisualBasicSyntaxNode VisitMethodDeclaration(CSS.MethodDeclarati
432434
if (needsOverloads == true) {
433435
modifiers = modifiers.Add(SyntaxFactory.Token(SyntaxKind.OverloadsKeyword));
434436
}
435-
var implementsClause = methodInfo == null ? null : CreateImplementsClauseSyntaxOrNull(methodInfo, id);
437+
var implementsClause = methodInfo == null ? null : CreateImplementsClauseSyntaxOrNull(methodInfo, ref id);
436438
if (isVoidSub) {
437439
var stmt = SyntaxFactory.SubStatement(
438440
attributes,
@@ -459,20 +461,33 @@ public override VisualBasicSyntaxNode VisitMethodDeclaration(CSS.MethodDeclarati
459461
/// <remarks>
460462
/// PERF: Computational complexity high due to starting with all members and narrowing down
461463
/// </remarks>
462-
private ImplementsClauseSyntax CreateImplementsClauseSyntaxOrNull(ISymbol memberInfo, SyntaxToken id)
464+
private ImplementsClauseSyntax CreateImplementsClauseSyntaxOrNull(ISymbol memberInfo, ref SyntaxToken id)
463465
{
466+
var originalId = id;
464467
var explicitImplementors = memberInfo.ExplicitInterfaceImplementations();
465-
if (explicitImplementors.Any())
466-
return CreateImplementsClauseSyntax(explicitImplementors, id);
467-
var containingType = memberInfo.ContainingType;
468-
var baseClassesAndInterfaces = containingType.GetAllBaseClassesAndInterfaces(true);
469-
var implementors = baseClassesAndInterfaces.Except(new[] { containingType })
470-
.SelectMany(t => t.GetMembers().Where(m => memberInfo.Name.EndsWith(m.Name)))
471-
.Where(m => containingType.FindImplementationForInterfaceMember(m)?.Equals(memberInfo) == true)
472-
.ToList();
468+
if (explicitImplementors.Any()) {
469+
//https://github.com/icsharpcode/CodeConverter/issues/492
470+
var memberNames = memberInfo.ContainingType.GetMembers().ToLookup(s => UndottedMemberName(s.Name), StringComparer.OrdinalIgnoreCase);
471+
string explicitMemberName = UndottedMemberName(memberInfo.Name);
472+
var hasDuplicateNames = memberNames[explicitMemberName].Count() > 1;
473+
if (hasDuplicateNames) id = SyntaxFactory.Identifier(NameGenerator.GenerateUniqueName(explicitMemberName, n => !memberNames.Contains(n) && _addedNames.Add(n)));
474+
} else {
475+
var containingType = memberInfo.ContainingType;
476+
var baseClassesAndInterfaces = containingType.GetAllBaseClassesAndInterfaces(true);
477+
explicitImplementors = baseClassesAndInterfaces.Except(new[] { containingType })
478+
.SelectMany(t => t.GetMembers().Where(m => memberInfo.Name.EndsWith(m.Name)))
479+
.Where(m => containingType.FindImplementationForInterfaceMember(m)?.Equals(memberInfo) == true)
480+
.ToImmutableArray();
481+
}
482+
483+
return !explicitImplementors.Any() ? null : CreateImplementsClauseSyntax(explicitImplementors, originalId);
484+
}
473485

474-
return !implementors.Any() ? null: CreateImplementsClauseSyntax(implementors, id);
486+
private static string UndottedMemberName(string n)
487+
{
488+
return n.Split('.').Last();
475489
}
490+
476491
private ImplementsClauseSyntax CreateImplementsClauseSyntax(IEnumerable<ISymbol> implementors, SyntaxToken id) {
477492
return SyntaxFactory.ImplementsClause(implementors.Select(x => {
478493
NameSyntax nameSyntax = _commonConversions.GetFullyQualifiedNameSyntax(x.ContainingSymbol as INamedTypeSymbol);
@@ -515,14 +530,15 @@ private VisualBasicSyntaxNode ConvertPropertyBlock(CSS.BasePropertyDeclarationSy
515530
IPropertySymbol declaredSymbol = _semanticModel.GetDeclaredSymbol(node) as IPropertySymbol;
516531
modifiers = modifiers.AddRange(GetAccessLimitationSyntaxKinds(declaredSymbol).Select(x => SyntaxFactory.Token(x)));
517532
Func<PropertyStatementSyntax> getStatementSyntax = () => {
533+
var implementsClauseSyntaxOrNull = declaredSymbol == null ? null : CreateImplementsClauseSyntaxOrNull(declaredSymbol, ref id);
518534
return SyntaxFactory.PropertyStatement(
519535
attributes,
520536
modifiers,
521537
id,
522538
parameterListSyntax,
523539
SyntaxFactory.SimpleAsClause(returnAttributes, (TypeSyntax)node.Type.Accept(TriviaConvertingVisitor)),
524540
initializerOrNull,
525-
declaredSymbol == null ? null : CreateImplementsClauseSyntaxOrNull(declaredSymbol, id)
541+
implementsClauseSyntaxOrNull
526542
);
527543
};
528544

@@ -591,10 +607,11 @@ public override VisualBasicSyntaxNode VisitEventDeclaration(CSS.EventDeclaration
591607
var id = _commonConversions.ConvertIdentifier(node.Identifier);
592608
var modifiers = CommonConversions.ConvertModifiers(node.Modifiers, GetMemberContext(node))
593609
.Add(SyntaxFactory.Token(SyntaxKind.CustomKeyword));
610+
var implementsClauseSyntaxOrNull = declaredSymbol == null ? null : CreateImplementsClauseSyntaxOrNull(declaredSymbol, ref id);
594611
var stmt = SyntaxFactory.EventStatement(
595612
attributes, modifiers, id, null,
596613
SyntaxFactory.SimpleAsClause(returnAttributes, (TypeSyntax)node.Type.Accept(TriviaConvertingVisitor)),
597-
declaredSymbol == null ? null : CreateImplementsClauseSyntaxOrNull(declaredSymbol, id)
614+
implementsClauseSyntaxOrNull
598615
);
599616
if (!RequiresAccessorBody(node.AccessorList))
600617
return stmt;
@@ -650,14 +667,15 @@ public override VisualBasicSyntaxNode VisitEventFieldDeclaration(CSS.EventFieldD
650667
var id = SyntaxFactory.Identifier(decl.Identifier.ValueText, SyntaxFacts.IsKeywordKind(decl.Identifier.Kind()), decl.Identifier.GetIdentifierText(), TypeCharacter.None);
651668
ConvertAndSplitAttributes(node.AttributeLists, out SyntaxList<AttributeListSyntax> attributes, out SyntaxList<AttributeListSyntax> returnAttributes);
652669
var declaredSymbol = _semanticModel.GetDeclaredSymbol(decl);
670+
var implementsClauseSyntaxOrNull = declaredSymbol == null ? null : CreateImplementsClauseSyntaxOrNull(declaredSymbol, ref id);
653671
return SyntaxFactory.EventStatement(
654672
attributes,
655673
CommonConversions.ConvertModifiers(node.Modifiers, GetMemberContext(node)),
656674
id,
657675
null,
658676
SyntaxFactory.SimpleAsClause(returnAttributes,
659677
(TypeSyntax)node.Declaration.Type.Accept(TriviaConvertingVisitor)),
660-
declaredSymbol == null ? null : CreateImplementsClauseSyntaxOrNull(declaredSymbol, id));
678+
implementsClauseSyntaxOrNull);
661679
}
662680
private TypeSyntax GetTypeSyntax(ITypeSymbol typeInfo) {
663681
return (TypeSyntax) _vbSyntaxGenerator.TypeExpression(typeInfo);

Tests/VB/SpecialConversionTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,44 @@ Public Sub Method()
660660
Dim vbLf As String = Microsoft.VisualBasic.vbLf
661661
Dim vbCrLf As String = Microsoft.VisualBasic.vbCrLf
662662
End Sub
663+
End Class", conversionOptions: EmptyNamespaceOptionStrictOff);
664+
}
665+
666+
[Fact]
667+
public async Task ExplicitImplementationsMustNotDifferOnlyByReturnTypeAsync() {
668+
await TestConversionCSharpToVisualBasicAsync(
669+
@"using System.Collections;
670+
using System.Collections.Generic;
671+
672+
public class AdditionalLocals : IEnumerable<KeyValuePair<string, int>>
673+
{
674+
private readonly Stack<Dictionary<string, int>> _additionalLocals = new Stack<Dictionary<string, int>>();
675+
676+
public IEnumerator<KeyValuePair<string, int>> GetEnumerator()
677+
{
678+
return _additionalLocals.Peek().GetEnumerator();
679+
}
680+
681+
IEnumerator IEnumerable.GetEnumerator()
682+
{
683+
return _additionalLocals.Peek().GetEnumerator();
684+
}
685+
}",
686+
@"Imports System.Collections
687+
Imports System.Collections.Generic
688+
689+
Public Class AdditionalLocals
690+
Implements IEnumerable(Of KeyValuePair(Of String, Integer))
691+
692+
Private ReadOnly _additionalLocals As Stack(Of Dictionary(Of String, Integer)) = New Stack(Of Dictionary(Of String, Integer))()
693+
694+
Public Function GetEnumerator() As IEnumerator(Of KeyValuePair(Of String, Integer)) Implements IEnumerable(Of KeyValuePair(Of String, Integer)).GetEnumerator
695+
Return _additionalLocals.Peek().GetEnumerator()
696+
End Function
697+
698+
Private Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
699+
Return _additionalLocals.Peek().GetEnumerator()
700+
End Function
663701
End Class", conversionOptions: EmptyNamespaceOptionStrictOff);
664702
}
665703
}

0 commit comments

Comments
 (0)