Skip to content

Commit f50f8f2

Browse files
Merge pull request #449 from icsharpcode/expand
Improve and move QualifyNode
2 parents b0404ca + 737f537 commit f50f8f2

14 files changed

Lines changed: 320 additions & 74 deletions

File tree

Lines changed: 141 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Linq;
43
using System.Threading;
54
using System.Threading.Tasks;
65
using ICSharpCode.CodeConverter.Util;
76
using Microsoft.CodeAnalysis;
87
using Microsoft.CodeAnalysis.CSharp;
9-
using Microsoft.CodeAnalysis.CSharp.Syntax;
10-
using Microsoft.CodeAnalysis.Options;
8+
using Microsoft.CodeAnalysis.Operations;
119
using Microsoft.CodeAnalysis.Simplification;
1210
using VBasic = Microsoft.CodeAnalysis.VisualBasic;
1311
using VBSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax;
@@ -17,32 +15,157 @@ namespace ICSharpCode.CodeConverter.CSharp
1715
{
1816
internal static class DocumentExtensions
1917
{
20-
public static async Task<Document> WithSimplifiedSyntaxRootAsync(this Document doc, SyntaxNode syntaxRoot = null)
18+
public static async Task<Document> SimplifyStatements<TUsingDirectiveSyntax, TExpressionSyntax>(this Document convertedDocument, string unresolvedTypeDiagnosticId)
19+
where TUsingDirectiveSyntax : SyntaxNode where TExpressionSyntax : SyntaxNode
20+
{
21+
var originalRoot = await convertedDocument.GetSyntaxRootAsync();
22+
var nodesWithUnresolvedTypes = (await convertedDocument.GetSemanticModelAsync()).GetDiagnostics()
23+
.Where(d => d.Id == unresolvedTypeDiagnosticId && d.Location.IsInSource)
24+
.Select(d => originalRoot.FindNode(d.Location.SourceSpan).GetAncestor<TUsingDirectiveSyntax>())
25+
.ToLookup(d => (SyntaxNode) d);
26+
27+
var toSimplify = originalRoot
28+
.DescendantNodes(n => !(n is TExpressionSyntax) && !nodesWithUnresolvedTypes.Contains(n))
29+
.Where(n => !nodesWithUnresolvedTypes.Contains(n));
30+
var newRoot = originalRoot.ReplaceNodes(toSimplify, (orig, rewritten) =>
31+
rewritten.WithAdditionalAnnotations(Simplifier.Annotation)
32+
);
33+
34+
var document = await convertedDocument.WithReducedRootAsync(newRoot.WithAdditionalAnnotations(Simplifier.Annotation));
35+
return document;
36+
}
37+
38+
public static async Task<Document> WithExpandedRootAsync(this Document document)
2139
{
22-
var root = syntaxRoot ?? await doc.GetSyntaxRootAsync();
23-
var withSyntaxRoot = doc.WithSyntaxRoot(root.WithAdditionalAnnotations(Simplifier.Annotation));
40+
var shouldExpand = document.Project.Language == LanguageNames.VisualBasic
41+
? (Func<SemanticModel, SyntaxNode, bool>)ShouldExpandVbNode
42+
: ShouldExpandCsNode;
43+
document = await WorkaroundBugsInExpandVbAsync(document, shouldExpand);
44+
45+
#if SimplifierBugsAreFixed //See https://github.com/icsharpcode/CodeConverter/pull/449 and https://github.com/icsharpcode/CodeConverter/pull/464
46+
document = await ExpandVbAsync(document, shouldExpand);
47+
document = await UndoVbExpansionsHardToReverseInCSharpSemanticModel(document);
48+
#endif
49+
return document;
50+
}
51+
52+
private static async Task<Document> WorkaroundBugsInExpandVbAsync(Document document, Func<SemanticModel, SyntaxNode, bool> shouldExpand)
53+
{
54+
var semanticModel = await document.GetSemanticModelAsync();
55+
var root = (VBasic.VisualBasicSyntaxNode)await document.GetSyntaxRootAsync();
56+
57+
try {
58+
var newRoot = root.ReplaceNodes(root.DescendantNodes(n => !shouldExpand(semanticModel, n)).Where(n => shouldExpand(semanticModel, n)),
59+
(node, rewrittenNode) => {
60+
var symbol = semanticModel.GetSymbolInfo(node).Symbol;
61+
if (rewrittenNode is VBSyntax.SimpleNameSyntax sns && IsMyBaseBug(semanticModel, root, node, symbol) && semanticModel.GetOperation(node) is IMemberReferenceOperation mro) {
62+
return VBasic.SyntaxFactory.MemberAccessExpression(VBasic.SyntaxKind.SimpleMemberAccessExpression,
63+
(VBSyntax.ExpressionSyntax) mro.Instance.Syntax,
64+
VBasic.SyntaxFactory.Token(VBasic.SyntaxKind.DotToken),
65+
sns);
66+
};
67+
return rewrittenNode;
68+
});
69+
return document.WithSyntaxRoot(newRoot);
70+
} catch (Exception) {
71+
return document.WithSyntaxRoot(root);
72+
}
73+
}
74+
75+
/// <returns>True iff calling Expand would qualify with MyBase when the symbol isn't in the base type
76+
/// See https://github.com/dotnet/roslyn/blob/97123b393c3a5a91cc798b329db0d7fc38634784/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.Expander.vb#L657</returns>
77+
private static bool IsMyBaseBug(SemanticModel semanticModel, VBasic.VisualBasicSyntaxNode root, SyntaxNode node,
78+
ISymbol symbol)
79+
{
80+
if (symbol?.IsStatic == false && (symbol.Kind == SymbolKind.Method || symbol.Kind ==
81+
SymbolKind.Field || symbol.Kind == SymbolKind.Property))
82+
{
83+
INamedTypeSymbol nodeEnclosingNamedType = GetEnclosingNamedType(semanticModel, root, node.SpanStart);
84+
if (!Equals(nodeEnclosingNamedType, symbol.ContainingType)) {
85+
return !Equals(nodeEnclosingNamedType, symbol.ContainingType?.BaseType);
86+
}
87+
}
88+
89+
return false;
90+
}
91+
92+
/// <summary>
93+
/// Pasted from AbstractGenerateFromMembersCodeRefactoringProvider
94+
/// Gets the enclosing named type for the specified position. We can't use
95+
/// <see cref="SemanticModel.GetEnclosingSymbol"/> because that doesn't return
96+
/// the type you're current on if you're on the header of a class/interface.
97+
/// </summary>
98+
private static INamedTypeSymbol GetEnclosingNamedType(
99+
SemanticModel semanticModel, SyntaxNode root, int start, CancellationToken cancellationToken = default(CancellationToken))
100+
{
101+
var token = root.FindToken(start);
102+
if (token == ((ICompilationUnitSyntax)root).EndOfFileToken) {
103+
token = token.GetPreviousToken();
104+
}
105+
106+
for (var node = token.Parent; node != null; node = node.Parent) {
107+
if (semanticModel.GetDeclaredSymbol(node) is INamedTypeSymbol declaration) {
108+
return declaration;
109+
}
110+
}
111+
112+
return null;
113+
}
114+
115+
private static async Task<Document> ExpandVbAsync(Document document, Func<SemanticModel, SyntaxNode, bool> shouldExpand)
116+
{
117+
var semanticModel = await document.GetSemanticModelAsync();
118+
var workspace = document.Project.Solution.Workspace;
119+
var root = (VBasic.VisualBasicSyntaxNode) await document.GetSyntaxRootAsync();
120+
try {
121+
var newRoot = root.ReplaceNodes(root.DescendantNodes(n => !shouldExpand(semanticModel, n)).Where(n => shouldExpand(semanticModel, n)),
122+
(node, rewrittenNode) => TryExpandNode(node, semanticModel, workspace)
123+
);
124+
return document.WithSyntaxRoot(newRoot);
125+
} catch (Exception) {
126+
return document.WithSyntaxRoot(root);
127+
}
128+
}
129+
private static async Task<Document> UndoVbExpansionsHardToReverseInCSharpSemanticModel(Document document)
130+
{
131+
var root = (VBasic.VisualBasicSyntaxNode)await document.GetSyntaxRootAsync();
132+
var toSimplify = root.DescendantNodes()
133+
.Where(n => n.IsKind(VBasic.SyntaxKind.PredefinedCastExpression, VBasic.SyntaxKind.CTypeExpression, VBasic.SyntaxKind.DirectCastExpression))
134+
.Where(n => n.HasAnnotation(Simplifier.Annotation));
135+
root = root.ReplaceNodes(toSimplify, (orig, rewritten) =>
136+
rewritten.WithAdditionalAnnotations(Simplifier.Annotation)
137+
);
138+
return await document.WithReducedRootAsync(root);
139+
}
140+
private static async Task<Document> WithReducedRootAsync(this Document doc, SyntaxNode syntaxRoot = null)
141+
{
142+
var root = syntaxRoot ?? await doc.GetSyntaxRootAsync();
143+
var withSyntaxRoot = doc.WithSyntaxRoot(root);
24144
try {
25145
return await Simplifier.ReduceAsync(withSyntaxRoot);
26146
} catch {
27147
return doc;
28148
}
29149
}
30150

31-
public static async Task<Document> SimplifyStatements<TUsingDirectiveSyntax, TExpressionSyntax>(this Document convertedDocument, string unresolvedTypeDiagnosticId)
32-
where TUsingDirectiveSyntax : SyntaxNode where TExpressionSyntax : SyntaxNode
151+
152+
private static SyntaxNode TryExpandNode(SyntaxNode node, SemanticModel semanticModel, Workspace workspace)
33153
{
34-
var root = await convertedDocument.GetSyntaxRootAsync();
35-
var nodesWithUnresolvedTypes = (await convertedDocument.GetSemanticModelAsync()).GetDiagnostics()
36-
.Where(d => d.Id == unresolvedTypeDiagnosticId && d.Location.IsInSource)
37-
.Select(d => root.FindNode(d.Location.SourceSpan).GetAncestor<TUsingDirectiveSyntax>())
38-
.ToLookup(d => (SyntaxNode) d);
154+
try {
155+
return Simplifier.Expand(node, semanticModel, workspace);
156+
} catch (Exception) {
157+
return node;
158+
}
159+
}
39160

40-
root = root.ReplaceNodes(
41-
root.DescendantNodes(n => !(n is TExpressionSyntax) && !nodesWithUnresolvedTypes.Contains(n)),
42-
(orig, rewritten) => !nodesWithUnresolvedTypes.Contains(rewritten) ? rewritten.WithAdditionalAnnotations(Simplifier.Annotation) : rewritten);
161+
private static bool ShouldExpandVbNode(SemanticModel semanticModel, SyntaxNode node)
162+
{
163+
return node is VBSyntax.NameSyntax || node is VBSyntax.InvocationExpressionSyntax && !semanticModel.GetSymbolInfo(node).Symbol.IsReducedTypeParameterMethod();
164+
}
43165

44-
var document = await convertedDocument.WithSimplifiedSyntaxRootAsync(root);
45-
return document;
166+
private static bool ShouldExpandCsNode(SemanticModel semanticModel, SyntaxNode node)
167+
{
168+
return false;
46169
}
47170
}
48171
}

ICSharpCode.CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 55 additions & 17 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 System.Linq.Expressions;
56
using System.Threading.Tasks;
@@ -11,6 +12,7 @@
1112
using Microsoft.CodeAnalysis.FindSymbols;
1213
using Microsoft.CodeAnalysis.Operations;
1314
using IOperation = Microsoft.CodeAnalysis.IOperation;
15+
using ISymbolExtensions = ICSharpCode.CodeConverter.Util.ISymbolExtensions;
1416
using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
1517
using SyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind;
1618
using VBasic = Microsoft.CodeAnalysis.VisualBasic;
@@ -191,7 +193,7 @@ public override async Task<CSharpSyntaxNode> VisitPredefinedCastExpression(VBasi
191193
SyntaxFactory.SingletonSeparatedList(
192194
SyntaxFactory.Argument(expressionSyntax)))
193195
) // Hopefully will be a compile error if it's wrong
194-
: ValidSyntaxFactory.CastExpression(SyntaxFactory.PredefinedType(node.Keyword.ConvertToken()), (ExpressionSyntax) await node.Expression.AcceptAsync(TriviaConvertingVisitor));
196+
: ValidSyntaxFactory.CastExpression(SyntaxFactory.PredefinedType(node.Keyword.ConvertToken()), expressionSyntax);
195197
}
196198

197199
public override async Task<CSharpSyntaxNode> VisitTryCastExpression(VBasic.Syntax.TryCastExpressionSyntax node)
@@ -278,7 +280,7 @@ public override async Task<CSharpSyntaxNode> VisitMemberAccessExpression(VBasic.
278280
var isDefaultProperty = nodeSymbol is IPropertySymbol p && VBasic.VisualBasicExtensions.IsDefault(p);
279281
ExpressionSyntax left = null;
280282
if (node.Expression is VBasic.Syntax.MyClassExpressionSyntax) {
281-
if (nodeSymbol.IsStatic) {
283+
if (nodeSymbol?.IsStatic != false) {
282284
var typeInfo = _semanticModel.GetTypeInfo(node.Expression);
283285
left = CommonConversions.GetTypeSyntax(typeInfo.Type);
284286
} else {
@@ -650,9 +652,9 @@ private async Task<ExpressionSyntax> ConvertMyGroupCollectionPropertyGetWithUnde
650652
return await ConvertMyGroupCollectionPropertyGetWithUnderlyingField(co.Operand.Syntax);
651653
case IPropertyReferenceOperation pro when pro.Property.IsMyGroupCollectionProperty():
652654
var associatedField = pro.Property.GetAssociatedField();
653-
var propertyAccessExpression = (VBSyntax.MemberAccessExpressionSyntax)pro.Syntax;
654-
var qualification = await propertyAccessExpression.Expression.AcceptAsync(TriviaConvertingVisitor);
655-
return SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, (ExpressionSyntax) qualification, SyntaxFactory.IdentifierName(associatedField.Name));
655+
var propertyReferenceOperation = ((IPropertyReferenceOperation) pro.Instance);
656+
var qualification = SyntaxFactory.ParseExpression(propertyReferenceOperation.Syntax.ToString());
657+
return SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, qualification, SyntaxFactory.IdentifierName(associatedField.Name));
656658
default:
657659
return null;
658660
}
@@ -664,7 +666,7 @@ public override async Task<CSharpSyntaxNode> VisitInvocationExpression(VBasic.Sy
664666
var expressionSymbol = _semanticModel.GetSymbolInfo(node.Expression).ExtractBestMatch();
665667
var expressionReturnType = expressionSymbol?.GetReturnType() ?? _semanticModel.GetTypeInfo(node.Expression).Type;
666668
var operation = _semanticModel.GetOperation(node);
667-
if (expressionSymbol?.ContainingNamespace.MetadataName == "VisualBasic" && await SubstituteVisualBasicMethodOrNull(node) is CSharpSyntaxNode csEquivalent) {
669+
if (expressionSymbol?.ContainingNamespace.MetadataName == nameof(Microsoft.VisualBasic) && await SubstituteVisualBasicMethodOrNull(node) is CSharpSyntaxNode csEquivalent) {
668670
return csEquivalent;
669671
}
670672

@@ -942,12 +944,15 @@ public override async Task<CSharpSyntaxNode> VisitQualifiedName(VBasic.Syntax.Qu
942944

943945
public override async Task<CSharpSyntaxNode> VisitGenericName(VBasic.Syntax.GenericNameSyntax node)
944946
{
945-
var genericNameSyntax = SyntaxFactory.GenericName(ConvertIdentifier(node.Identifier), (TypeArgumentListSyntax) await node.TypeArgumentList.AcceptAsync(TriviaConvertingVisitor));
947+
var symbol = GetSymbolInfoInDocument(node);
948+
var genericNameSyntax = await GenericNameAccountingForReducedParameters(node, symbol);
946949
ExpressionSyntax name = genericNameSyntax;
947950

948-
if (!node.Parent.IsKind(VBasic.SyntaxKind.SimpleMemberAccessExpression, VBasic.SyntaxKind.QualifiedName)) {
949-
var symbol = GetSymbolInfoInDocument(node);
950-
if (symbol?.ContainingSymbol != null) {
951+
952+
if (symbol?.ContainingSymbol != null) {
953+
954+
if (!node.Parent.IsKind(VBasic.SyntaxKind.SimpleMemberAccessExpression, VBasic.SyntaxKind.QualifiedName)) {
955+
951956
string lhs;
952957
if (symbol.ContainingSymbol.IsType()) {
953958
lhs = CommonConversions.GetTypeSyntax(symbol.ContainingSymbol.GetSymbolType()).ToString();
@@ -963,6 +968,37 @@ public override async Task<CSharpSyntaxNode> VisitGenericName(VBasic.Syntax.Gene
963968
return AddEmptyArgumentListIfImplicit(node, name);
964969
}
965970

971+
/// <summary>
972+
/// Adjusts for Visual Basic's omission of type arguments that can be inferred in reduced generic method invocations
973+
/// The upfront WithExpandedRootAsync pass should ensure this only happens on broken syntax trees.
974+
/// In those cases, just comment the errant information. It would only cause a compiling change in behaviour if it can be inferred, was not set to the inferred value, and was reflected upon within the method body
975+
/// </summary>
976+
private async Task<SimpleNameSyntax> GenericNameAccountingForReducedParameters(VBSyntax.GenericNameSyntax node, ISymbol symbol)
977+
{
978+
SyntaxToken convertedIdentifier = ConvertIdentifier(node.Identifier);
979+
if (symbol.IsReducedTypeParameterMethod()) {
980+
if (symbol is IMethodSymbol vbMethod && CommonConversions.GetCsOriginalSymbolOrNull(symbol) is IMethodSymbol csSymbolWithInferredTypeParametersSet) {
981+
var argSubstitutions = vbMethod.TypeParameters
982+
.Zip(vbMethod.TypeArguments, (parameter, arg) => (parameter, arg))
983+
.ToDictionary(x => x.parameter.Name, x => x.arg);
984+
var allTypeArgs = csSymbolWithInferredTypeParametersSet.GetTypeArguments()
985+
.Select(a => a.Kind == SymbolKind.TypeParameter && argSubstitutions.TryGetValue(a.Name, out var t) ? t : a).ToArray();
986+
return (SimpleNameSyntax)CommonConversions.CsSyntaxGenerator.GenericName(convertedIdentifier.Text, allTypeArgs);
987+
}
988+
var commentedText = "/* " + (await ConvertTypeArgumentList(node)).ToFullString() + " */";
989+
var error = SyntaxFactory.ParseLeadingTrivia($"#error Conversion error: Could not convert all type parameters, so they've been commented out. Inferred type may be different{Environment.NewLine}");
990+
var partialConversion = SyntaxFactory.Comment(commentedText);
991+
return SyntaxFactory.IdentifierName(convertedIdentifier).WithPrependedLeadingTrivia(error).WithTrailingTrivia(partialConversion);
992+
}
993+
994+
return SyntaxFactory.GenericName(convertedIdentifier, await ConvertTypeArgumentList(node));
995+
}
996+
997+
private async Task<TypeArgumentListSyntax> ConvertTypeArgumentList(VBSyntax.GenericNameSyntax node)
998+
{
999+
return (TypeArgumentListSyntax)await node.TypeArgumentList.AcceptAsync(TriviaConvertingVisitor);
1000+
}
1001+
9661002
public override async Task<CSharpSyntaxNode> VisitTypeArgumentList(VBasic.Syntax.TypeArgumentListSyntax node)
9671003
{
9681004
var args = await node.Arguments.SelectAsync(async a => (TypeSyntax) await a.AcceptAsync(TriviaConvertingVisitor));
@@ -1011,14 +1047,16 @@ private async Task<CSharpSyntaxNode> ConvertCastExpression(VBSyntax.CastExpressi
10111047
{
10121048
var expressionSyntax = (ExpressionSyntax) await node.Expression.AcceptAsync(TriviaConvertingVisitor);
10131049

1014-
if (convertMethodOrNull != null) {
1015-
expressionSyntax = Invoke(convertMethodOrNull, expressionSyntax);
1016-
}
1050+
if (!(_semanticModel.GetOperation(node) is IConversionOperation co) || !co.Conversion.IsIdentity) {
1051+
if (convertMethodOrNull != null) {
1052+
expressionSyntax = Invoke(convertMethodOrNull, expressionSyntax);
1053+
}
10171054

1018-
if (castToOrNull != null) {
1019-
expressionSyntax = await Cast(expressionSyntax, castToOrNull);
1020-
if (node.Parent is VBasic.Syntax.MemberAccessExpressionSyntax) {
1021-
expressionSyntax = SyntaxFactory.ParenthesizedExpression(expressionSyntax);
1055+
if (castToOrNull != null) {
1056+
expressionSyntax = await Cast(expressionSyntax, castToOrNull);
1057+
if (node.Parent is VBasic.Syntax.MemberAccessExpressionSyntax) {
1058+
expressionSyntax = SyntaxFactory.ParenthesizedExpression(expressionSyntax);
1059+
}
10221060
}
10231061
}
10241062

0 commit comments

Comments
 (0)