Skip to content

Commit 937dcc2

Browse files
Create delegating method for implementations - fixes #443 - fixes #444
1 parent 4ea9fa0 commit 937dcc2

File tree

8 files changed

+221
-35
lines changed

8 files changed

+221
-35
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1010

1111
### VB -> C#
1212
* When converting ReDim Preserve to Array.Resize, "ref" is now added
13+
* Create delegating method for renamed implementations [#443](https://github.com/icsharpcode/CodeConverter/issues/443), [#444](https://github.com/icsharpcode/CodeConverter/issues/444)
1314

1415
### C# -> VB
1516

CodeConverter/CSharp/CommonConversions.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -205,15 +205,15 @@ public TypeSyntax GetTypeSyntax(ITypeSymbol typeSymbol, bool useImplicitType = f
205205
if (useImplicitType || typeSymbol == null) return ValidSyntaxFactory.VarType;
206206
var syntax = (TypeSyntax)CsSyntaxGenerator.TypeExpression(typeSymbol);
207207

208-
return WithDeclarationCasing(syntax, typeSymbol);
208+
return WithDeclarationNameCasing(syntax, typeSymbol);
209209
}
210210

211211
/// <summary>
212212
/// Semantic model merges the symbols, but the compiled form retains multiple namespaces, which (when referenced from C#) need to keep the correct casing.
213-
/// <seealso cref="DeclarationNodeVisitor.WithDeclarationCasingAsync(VBSyntax.NamespaceBlockSyntax, ISymbol)"/>
214-
/// <seealso cref="CommonConversions.WithDeclarationCasing(SyntaxToken, ISymbol, string)"/>
213+
/// <seealso cref="DeclarationNodeVisitor.WithDeclarationNameCasingAsync(VBSyntax.NamespaceBlockSyntax, ISymbol)"/>
214+
/// <seealso cref="CommonConversions.WithDeclarationName(SyntaxToken, ISymbol, string)"/>
215215
/// </summary>
216-
private static TypeSyntax WithDeclarationCasing(TypeSyntax syntax, ITypeSymbol typeSymbol)
216+
private static TypeSyntax WithDeclarationNameCasing(TypeSyntax syntax, ITypeSymbol typeSymbol)
217217
{
218218
var vbType = SyntaxFactory.ParseTypeName(typeSymbol.ToDisplayString());
219219
var originalNames = vbType.DescendantNodes().OfType<CSSyntax.IdentifierNameSyntax>()
@@ -269,7 +269,7 @@ public SyntaxToken ConvertIdentifier(SyntaxToken id, bool isAttribute = false, S
269269
if (id.SyntaxTree == _semanticModel.SyntaxTree) {
270270
var idSymbol = _semanticModel.GetSymbolInfo(id.Parent).Symbol ?? _semanticModel.GetDeclaredSymbol(id.Parent);
271271
if (idSymbol != null && !String.IsNullOrWhiteSpace(idSymbol.Name)) {
272-
text = WithDeclarationCasing(id, idSymbol, text);
272+
text = WithDeclarationName(id, idSymbol, text);
273273
var normalizedText = text.WithHalfWidthLatinCharacters();
274274
if (idSymbol.IsConstructor() && isAttribute) {
275275
text = idSymbol.ContainingType.Name;
@@ -299,17 +299,17 @@ public SyntaxToken ConvertIdentifier(SyntaxToken id, bool isAttribute = false, S
299299

300300
/// <summary>
301301
/// Semantic model merges the symbols, but the compiled form retains multiple namespaces, which (when referenced from C#) need to keep the correct casing.
302-
/// <seealso cref="DeclarationNodeVisitor.WithDeclarationCasingAsync(VBSyntax.NamespaceBlockSyntax, ISymbol)"/>
303-
/// <seealso cref="CommonConversions.WithDeclarationCasing(TypeSyntax, ITypeSymbol)"/>
302+
/// <seealso cref="DeclarationNodeVisitor.WithDeclarationNameCasingAsync(VBSyntax.NamespaceBlockSyntax, ISymbol)"/>
303+
/// <seealso cref="CommonConversions.WithDeclarationNameCasing(TypeSyntax, ITypeSymbol)"/>
304304
/// </summary>
305-
private static string WithDeclarationCasing(SyntaxToken id, ISymbol idSymbol, string text)
305+
private static string WithDeclarationName(SyntaxToken id, ISymbol idSymbol, string text)
306306
{
307-
//TODO: Consider what happens when the names aren't equal for overridden members (I think in VB you can have X implements Y)
308-
var baseSymbol = idSymbol.IsKind(SymbolKind.Method) || idSymbol.IsKind(SymbolKind.Property) ? idSymbol.FollowProperty(s => s.OverriddenMember()).Last() : idSymbol;
307+
//This also covers the case when the name is different (in VB you can have method X implements IFoo.Y), but doesn't resolve any resulting name clashes
308+
var baseSymbol = idSymbol.IsKind(SymbolKind.Method) || idSymbol.IsKind(SymbolKind.Property) ? idSymbol.FollowProperty(s => s.BaseMember()).Last() : idSymbol;
309309
bool isDeclaration = baseSymbol.Locations.Any(l => l.SourceSpan == id.Span);
310310
bool isPartial = baseSymbol.IsPartialClassDefinition() || baseSymbol.IsPartialMethodDefinition() ||
311311
baseSymbol.IsPartialMethodImplementation();
312-
if (isPartial || (!isDeclaration && text.WithHalfWidthLatinCharacters().Equals(baseSymbol.Name, StringComparison.OrdinalIgnoreCase)))
312+
if (isPartial || !isDeclaration)
313313
{
314314
text = baseSymbol.Name;
315315
}

CodeConverter/CSharp/DeclarationNodeVisitor.cs

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public override async Task<CSharpSyntaxNode> VisitNamespaceBlock(VBSyntax.Namesp
161161
{
162162
var members = (await node.Members.SelectAsync(ConvertMemberAsync)).Where(m => m != null);
163163
var sym = ModelExtensions.GetDeclaredSymbol(_semanticModel, node);
164-
string namespaceToDeclare = await WithDeclarationCasingAsync(node, sym);
164+
string namespaceToDeclare = await WithDeclarationNameCasingAsync(node, sym);
165165
var parentNamespaceSyntax = node.GetAncestor<VBSyntax.NamespaceBlockSyntax>();
166166
var parentNamespaceDecl = parentNamespaceSyntax != null ? ModelExtensions.GetDeclaredSymbol(_semanticModel, parentNamespaceSyntax) : null;
167167
var parentNamespaceFullName = parentNamespaceDecl?.ToDisplayString() ?? _topAncestorNamespace;
@@ -174,10 +174,10 @@ public override async Task<CSharpSyntaxNode> VisitNamespaceBlock(VBSyntax.Namesp
174174

175175
/// <summary>
176176
/// Semantic model merges the symbols, but the compiled form retains multiple namespaces, which (when referenced from C#) need to keep the correct casing.
177-
/// <seealso cref="CommonConversions.WithDeclarationCasing(TypeSyntax, ITypeSymbol)"/>
178-
/// <seealso cref="CommonConversions.WithDeclarationCasing(SyntaxToken, ISymbol, string)"/>
177+
/// <seealso cref="CommonConversions.WithDeclarationNameCasing(TypeSyntax, ITypeSymbol)"/>
178+
/// <seealso cref="CommonConversions.WithDeclarationName(SyntaxToken, ISymbol, string)"/>
179179
/// </summary>
180-
private async Task<string> WithDeclarationCasingAsync(VBSyntax.NamespaceBlockSyntax node, ISymbol sym)
180+
private async Task<string> WithDeclarationNameCasingAsync(VBSyntax.NamespaceBlockSyntax node, ISymbol sym)
181181
{
182182
var sourceName = (await node.NamespaceStatement.Name.AcceptAsync(_triviaConvertingExpressionVisitor)).ToString();
183183
var namespaceToDeclare = sym?.ToDisplayString() ?? sourceName;
@@ -1034,10 +1034,12 @@ public override async Task<CSharpSyntaxNode> VisitMethodStatement(VBSyntax.Metho
10341034
return decl.WithSemicolonToken(SemicolonToken);
10351035
} else {
10361036
var tokenContext = GetMemberContext(node);
1037-
var declaredSymbol = ModelExtensions.GetDeclaredSymbol(_semanticModel, node) as IMethodSymbol;
1038-
var extraCsModifierKinds = declaredSymbol?.IsExtern == true ? new[] { Microsoft.CodeAnalysis.CSharp.SyntaxKind.ExternKeyword} : Array.Empty<Microsoft.CodeAnalysis.CSharp.SyntaxKind>();
1037+
var declaredSymbol = (IMethodSymbol)ModelExtensions.GetDeclaredSymbol(_semanticModel, node);
1038+
var extraCsModifierKinds = declaredSymbol?.IsExtern == true ? new[] { Microsoft.CodeAnalysis.CSharp.SyntaxKind.ExternKeyword } : Array.Empty<Microsoft.CodeAnalysis.CSharp.SyntaxKind>();
10391039
var convertedModifiers = CommonConversions.ConvertModifiers(node, node.Modifiers, tokenContext, extraCsModifierKinds: extraCsModifierKinds);
1040-
1040+
var explicitInterfaceSpecifier = declaredSymbol.DeclaredAccessibility == Accessibility.Private && declaredSymbol.ExplicitInterfaceImplementations.Any() ?
1041+
SyntaxFactory.ExplicitInterfaceSpecifier(SyntaxFactory.IdentifierName(declaredSymbol.ExplicitInterfaceImplementations.First().ContainingType.Name))
1042+
: null;
10411043
bool accessedThroughMyClass = IsAccessedThroughMyClass(node, node.Identifier, declaredSymbol);
10421044

10431045
var isPartialDefinition = declaredSymbol.IsPartialMethodDefinition();
@@ -1053,50 +1055,96 @@ public override async Task<CSharpSyntaxNode> VisitMethodStatement(VBSyntax.Metho
10531055
}
10541056
var (typeParameters, constraints) = await SplitTypeParametersAsync(node.TypeParameterList);
10551057

1058+
var returnType = (TypeSyntax)(declaredSymbol != null ? CommonConversions.GetTypeSyntax(declaredSymbol.ReturnType) :
1059+
await (node.AsClause?.Type).AcceptAsync(_triviaConvertingExpressionVisitor) ?? SyntaxFactory.PredefinedType(SyntaxFactory.Token(Microsoft.CodeAnalysis.CSharp.SyntaxKind.VoidKeyword)));
1060+
1061+
var directlyConvertedCsIdentifier = CommonConversions.CsEscapedIdentifier(node.Identifier.Value as string);
10561062
var csIdentifier = CommonConversions.ConvertIdentifier(node.Identifier);
1063+
var parameterList = (ParameterListSyntax)await node.ParameterList.AcceptAsync(_triviaConvertingExpressionVisitor) ?? SyntaxFactory.ParameterList();
1064+
var additionalDeclarations = new List<MemberDeclarationSyntax>();
1065+
1066+
// If we had to rename the method to match the interface, emit a method for external references with the old name to point to
1067+
if (!StringComparer.OrdinalIgnoreCase.Equals(directlyConvertedCsIdentifier.Value, csIdentifier.Value) && declaredSymbol.GetResultantVisibility() == SymbolVisibility.Public) {
1068+
1069+
var arrowClause = SyntaxFactory.ArrowExpressionClause(SyntaxFactory.InvocationExpression(SyntaxFactory.IdentifierName(csIdentifier), CreateDelegatingArgList(parameterList)));
1070+
additionalDeclarations.Add(SyntaxFactory.MethodDeclaration(
1071+
attributes,
1072+
convertedModifiers,
1073+
returnType,
1074+
explicitInterfaceSpecifier,
1075+
directlyConvertedCsIdentifier,
1076+
typeParameters,
1077+
parameterList,
1078+
constraints,
1079+
null,
1080+
arrowClause,
1081+
SyntaxFactory.Token(Microsoft.CodeAnalysis.CSharp.SyntaxKind.SemicolonToken)
1082+
));
1083+
}
10571084
// If the method is virtual, and there is a MyClass.SomeMethod() call,
10581085
// we need to emit a non-virtual method for it to call
1059-
var returnType = (TypeSyntax) (declaredSymbol != null ? CommonConversions.GetTypeSyntax(declaredSymbol.ReturnType) :
1060-
await (node.AsClause?.Type).AcceptAsync(_triviaConvertingExpressionVisitor) ?? SyntaxFactory.PredefinedType(SyntaxFactory.Token(Microsoft.CodeAnalysis.CSharp.SyntaxKind.VoidKeyword)));
1061-
if (accessedThroughMyClass)
1062-
{
1086+
if (accessedThroughMyClass) {
10631087
var identifierName = "MyClass" + csIdentifier.ValueText;
1064-
var arrowClause = SyntaxFactory.ArrowExpressionClause(SyntaxFactory.InvocationExpression(SyntaxFactory.IdentifierName(identifierName)));
1065-
var realDecl = SyntaxFactory.MethodDeclaration(
1088+
var arrowClause = SyntaxFactory.ArrowExpressionClause(SyntaxFactory.InvocationExpression(SyntaxFactory.IdentifierName(identifierName), CreateDelegatingArgList(parameterList)));
1089+
1090+
convertedModifiers = convertedModifiers.RemoveOnly(m => m.IsKind(CSSyntaxKind.PrivateKeyword));
1091+
var originalNameDecl = SyntaxFactory.MethodDeclaration(
10661092
attributes,
10671093
convertedModifiers,
10681094
returnType,
1069-
null, CommonConversions.ConvertIdentifier(node.Identifier),
1095+
explicitInterfaceSpecifier,
1096+
csIdentifier,
10701097
typeParameters,
1071-
(ParameterListSyntax) await node.ParameterList.AcceptAsync(_triviaConvertingExpressionVisitor, SourceTriviaMapKind.None) ?? SyntaxFactory.ParameterList(),
1098+
parameterList,
10721099
constraints,
10731100
null,
10741101
arrowClause,
1075-
SyntaxFactory.Token(Microsoft.CodeAnalysis.CSharp.SyntaxKind.SemicolonToken)
1102+
SyntaxFactory.Token(CSSyntaxKind.SemicolonToken)
10761103
);
10771104

1078-
var declNode = (VBSyntax.StatementSyntax)node.Parent;
1079-
_additionalDeclarations.Add(declNode, new MemberDeclarationSyntax[] { realDecl });
1080-
convertedModifiers = convertedModifiers.Remove(convertedModifiers.Single(m => m.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.VirtualKeyword)));
1105+
additionalDeclarations.Add(originalNameDecl);
1106+
convertedModifiers = convertedModifiers.Remove(convertedModifiers.Single(m => m.IsKind(CSSyntaxKind.VirtualKeyword)));
10811107
csIdentifier = SyntaxFactory.Identifier(identifierName);
1108+
explicitInterfaceSpecifier = null;
1109+
} else if (explicitInterfaceSpecifier != null) {
1110+
convertedModifiers = convertedModifiers.Remove(convertedModifiers.Single(m => m.IsKind(CSSyntaxKind.PrivateKeyword)));
1111+
}
1112+
1113+
if (additionalDeclarations.Any()) {
1114+
var declNode = (VBSyntax.StatementSyntax)node.Parent;
1115+
_additionalDeclarations.Add(declNode, additionalDeclarations.ToArray());
10821116
}
10831117

10841118
var decl = SyntaxFactory.MethodDeclaration(
10851119
attributes,
10861120
convertedModifiers,
10871121
returnType,
1088-
null,
1122+
explicitInterfaceSpecifier,
10891123
csIdentifier,
10901124
typeParameters,
1091-
(ParameterListSyntax) await node.ParameterList.AcceptAsync(_triviaConvertingExpressionVisitor) ?? SyntaxFactory.ParameterList(),
1125+
parameterList,
10921126
constraints,
1093-
null,
1127+
null,//Body added by surrounding method block if appropriate
10941128
null
10951129
);
10961130
return hasBody && declaredSymbol.CanHaveMethodBody() ? decl : decl.WithSemicolonToken(SemicolonToken);
10971131
}
10981132
}
10991133

1134+
private static ArgumentListSyntax CreateDelegatingArgList(ParameterListSyntax parameterList)
1135+
{
1136+
var refKinds = parameterList.Parameters.Select(GetSingleModifier).ToArray();
1137+
return parameterList.Parameters.Select(p => SyntaxFactory.IdentifierName(p.Identifier)).CreateCsArgList(refKinds);
1138+
}
1139+
1140+
private static CSSyntaxKind? GetSingleModifier(ParameterSyntax p)
1141+
{
1142+
var argKinds = new CSSyntaxKind?[] { CSSyntaxKind.RefKeyword, CSSyntaxKind.OutKeyword, CSSyntaxKind.InKeyword };
1143+
return p.Modifiers.Select(Microsoft.CodeAnalysis.CSharp.CSharpExtensions.Kind)
1144+
.Select<CSSyntaxKind, CSSyntaxKind?>(k => k)
1145+
.FirstOrDefault(argKinds.Contains);
1146+
}
1147+
11001148
private TokenContext GetMemberContext(VBSyntax.StatementSyntax member)
11011149
{
11021150
var parentType = member.GetAncestorOrThis<VBSyntax.TypeBlockSyntax>();

CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1479,6 +1479,7 @@ private async Task<CSharpSyntaxNode> SubstituteVisualBasicMethodOrNullAsync(VBas
14791479

14801480
private CSharpSyntaxNode AddEmptyArgumentListIfImplicit(SyntaxNode node, ExpressionSyntax id)
14811481
{
1482+
if (_semanticModel.SyntaxTree != node.SyntaxTree) return id;
14821483
return _semanticModel.GetOperation(node) is IInvocationOperation invocation
14831484
? SyntaxFactory.InvocationExpression(id, CreateArgList(invocation.TargetMethod))
14841485
: id;

CodeConverter/Util/ExpressionSyntaxExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ public static VBSyntax.ArgumentListSyntax CreateVbArgList<T>(this IEnumerable<T>
2626
return VBasic.SyntaxFactory.ArgumentList(VBasic.SyntaxFactory.SeparatedList(argExpressions.Select(e => (VBSyntax.ArgumentSyntax) VBasic.SyntaxFactory.SimpleArgument(e))));
2727
}
2828

29-
public static ArgumentListSyntax CreateCsArgList<T>(this IEnumerable<T> argExpressions, params SyntaxKind[] refTokenKinds) where T : ExpressionSyntax
29+
public static ArgumentListSyntax CreateCsArgList<T>(this IEnumerable<T> argExpressions, params SyntaxKind?[] refTokenKinds) where T : ExpressionSyntax
3030
{
3131
return SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(argExpressions.Select((e, i) => {
3232
var arg = SyntaxFactory.Argument(e);
33-
if (i < refTokenKinds.Length) arg = arg.WithRefKindKeyword(SyntaxFactory.Token(refTokenKinds[i]));
33+
if (i < refTokenKinds.Length && refTokenKinds[i].HasValue) arg = arg.WithRefKindKeyword(SyntaxFactory.Token(refTokenKinds[i].Value));
3434
return arg;
3535
})));
3636
}

CodeConverter/Util/SymbolExtensions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ public static bool IsAnonymousType(this ISymbol symbol)
160160
return symbol is INamedTypeSymbol && ((INamedTypeSymbol)symbol).IsAnonymousType;
161161
}
162162

163+
164+
public static ISymbol BaseMember(this ISymbol symbol)
165+
{
166+
return symbol.ExplicitInterfaceImplementations().FirstOrDefault() ?? symbol.OverriddenMember();
167+
}
168+
163169
public static ISymbol OverriddenMember(this ISymbol symbol)
164170
{
165171
switch (symbol.Kind) {

CodeConverter/Util/SyntaxTokenExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,12 @@ public static SyntaxToken WithoutSourceMapping(this SyntaxToken token)
138138
{
139139
return token.WithoutAnnotations(AnnotationConstants.SourceStartLineAnnotationKind).WithoutAnnotations(AnnotationConstants.SourceEndLineAnnotationKind);
140140
}
141+
142+
public static SyntaxTokenList RemoveOnly(this SyntaxTokenList list, Func<SyntaxToken, bool> where)
143+
{
144+
var toRemove = list.OnlyOrDefault(where);
145+
if (toRemove != default) list = list.Remove(toRemove);
146+
return list;
147+
}
141148
}
142149
}

0 commit comments

Comments
 (0)