Fix out parameters in external assemblies

The VB model doesn't know about out parameters since it turns them all into ByRef, so after the first pass, walk the CSharp tree and turn ref into out where appropriate
Adding in this intermediate ConvertMultiple since that's the right level to fix it so that it's possible to get things right for dictionaries that are fields of another type in the project, though that functionality isn't taken advantage of by the single document fixer at this point
This commit is contained in:
GrahamTheCoder 2017-12-17 17:44:43 +00:00
Родитель 408ba51523
Коммит b4cf4f5d84
5 изменённых файлов: 110 добавлений и 8 удалений

Просмотреть файл

@ -0,0 +1,71 @@
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace ICSharpCode.CodeConverter.CSharp
{
internal class CompilationErrorFixer
{
private readonly CSharpSyntaxTree syntaxTree;
private readonly SemanticModel semanticModel;
public CompilationErrorFixer(CSharpCompilation compilation, CSharpSyntaxTree syntaxTree)
{
this.syntaxTree = syntaxTree;
this.semanticModel = compilation.GetSemanticModel(syntaxTree, true);
}
public CSharpSyntaxNode Fix()
{
var syntaxNode = syntaxTree.GetRoot();
return syntaxNode.ReplaceNodes(syntaxNode.DescendantNodes(), ComputeReplacementNode);
}
private SyntaxNode ComputeReplacementNode(SyntaxNode originalNode, SyntaxNode potentiallyRewrittenNode)
{
if (!(potentiallyRewrittenNode is ArgumentListSyntax nodeToReturn)) return potentiallyRewrittenNode;
var invocationExpression = nodeToReturn.FirstAncestorOrSelf<InvocationExpressionSyntax>();
if (invocationExpression == null) return potentiallyRewrittenNode;
var methodSymbol = semanticModel.GetSymbolInfo(invocationExpression).CandidateSymbols.OfType<IMethodSymbol>()
.FirstOrDefault(s => invocationExpression.ArgumentList.Arguments.Count == s.Parameters.Length);
if (methodSymbol != null) {
//Won't work for named parameters
for (var index = 0; index < Math.Min(nodeToReturn.Arguments.Count, methodSymbol.Parameters.Length); index++) {
var argument = nodeToReturn.Arguments[index];
var refOrOutKeyword = GetRefKeyword(methodSymbol.Parameters[index]);
var currentSyntaxKind = nodeToReturn.Arguments[index].Kind();
if (!refOrOutKeyword.IsKind(currentSyntaxKind)) {
nodeToReturn = nodeToReturn.ReplaceNode(argument,
SyntaxFactory.Argument(argument.NameColon, refOrOutKeyword, argument.Expression)
.WithLeadingTrivia(argument.GetLeadingTrivia())
.WithTrailingTrivia(argument.GetTrailingTrivia()));
}
}
}
return nodeToReturn;
}
private static SyntaxToken GetRefKeyword(IParameterSymbol formalParameter)
{
SyntaxToken token;
switch (formalParameter.RefKind) {
case RefKind.None:
token = default(SyntaxToken);
break;
case RefKind.Ref:
token = SyntaxFactory.Token(SyntaxKind.RefKeyword);
break;
case RefKind.Out:
token = SyntaxFactory.Token(SyntaxKind.OutKeyword);
break;
default:
throw new ArgumentOutOfRangeException();
}
return token.WithTrailingTrivia(SyntaxFactory.Whitespace(" "));
}
}
}

Просмотреть файл

@ -38,16 +38,14 @@ namespace ICSharpCode.CodeConverter.CSharp
class NodesVisitor : VBasic.VisualBasicSyntaxVisitor<CSharpSyntaxNode>
{
private SemanticModel semanticModel;
private Document targetDocument;
private readonly Dictionary<ITypeSymbol, string> createConvertMethodsLookupByReturnType;
private readonly Dictionary<MemberDeclarationSyntax, MemberDeclarationSyntax[]> additionalDeclarations = new Dictionary<MemberDeclarationSyntax, MemberDeclarationSyntax[]>();
private readonly Stack<string> withBlockTempVariableNames = new Stack<string>();
readonly IDictionary<string, string> importedNamespaces;
public NodesVisitor(SemanticModel semanticModel, Document targetDocument)
public NodesVisitor(SemanticModel semanticModel)
{
this.semanticModel = semanticModel;
this.targetDocument = targetDocument;
importedNamespaces = new Dictionary<string, string> {{VBasic.VisualBasicExtensions.RootNamespace(semanticModel.Compilation).ToString(), ""}};
this.createConvertMethodsLookupByReturnType = CreateConvertMethodsLookupByReturnType(semanticModel);
}

Просмотреть файл

@ -34,9 +34,17 @@ namespace ICSharpCode.CodeConverter.CSharp
MemberInInterface
}
public static CSharpSyntaxNode Convert(VBasic.VisualBasicSyntaxNode input, SemanticModel semanticModel, Document targetDocument)
public static CSharpSyntaxNode ConvertSingle(VBasic.VisualBasicCompilation compilation, VBasic.VisualBasicSyntaxTree syntaxTree)
{
return input.Accept(new NodesVisitor(semanticModel, targetDocument));
return ConvertMultiple(compilation, new[] {syntaxTree}).Values.Single();
}
public static Dictionary<string, CSharpSyntaxNode> ConvertMultiple(VBasic.VisualBasicCompilation compilation, IEnumerable<VBasic.VisualBasicSyntaxTree> syntaxTrees)
{
var cSharpFirstPass = syntaxTrees.ToDictionary(tree => tree.FilePath ?? "unknown",
tree => (CSharpSyntaxTree) SyntaxFactory.SyntaxTree(tree.GetRoot().Accept(new NodesVisitor(compilation.GetSemanticModel(tree, true)))));
var cSharpCompilation = CSharpCompilation.Create("Conversion", cSharpFirstPass.Values, compilation.References);
return cSharpFirstPass.ToDictionary(cs => cs.Key, cs => new CompilationErrorFixer(cSharpCompilation, cs.Value).Fix());
}
public static ConversionResult ConvertText(string text, MetadataReference[] references)
@ -45,10 +53,10 @@ namespace ICSharpCode.CodeConverter.CSharp
throw new ArgumentNullException(nameof(text));
if (references == null)
throw new ArgumentNullException(nameof(references));
var tree = VBasic.SyntaxFactory.ParseSyntaxTree(SourceText.From(text));
var tree = (VBasic.VisualBasicSyntaxTree) VBasic.SyntaxFactory.ParseSyntaxTree(SourceText.From(text));
var compilation = VBasic.VisualBasicCompilation.Create("Conversion", new[] { tree }, references);
try {
return new ConversionResult(Convert((VBasic.VisualBasicSyntaxNode)tree.GetRoot(), compilation.GetSemanticModel(tree, true), null).NormalizeWhitespace().ToFullString());
return new ConversionResult(ConvertSingle(compilation, tree).NormalizeWhitespace().ToFullString());
} catch (Exception ex) {
return new ConversionResult(ex);
}

Просмотреть файл

@ -222,6 +222,31 @@ class TestClass
}
[Fact]
public void ExternalReferenceToOutParameter()
{
TestConversionVisualBasicToCSharp(@"Class TestClass
Private Sub TestMethod(ByVal str As String)
Dim d = New Dictionary(Of string, string)
Dim s As String
d.TryGetValue(""a"", s)
End Sub
End Class", @"using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualBasic;
class TestClass
{
private void TestMethod(string str)
{
var d = new Dictionary<string, string>();
string s;
d.TryGetValue(""a"", out s);
}
}");
}
[Fact]
public void ElvisOperatorExpression()
{
TestConversionVisualBasicToCSharp(@"Class TestClass

Просмотреть файл

@ -263,7 +263,7 @@ namespace CodeConverter.Tests
CSharpSyntaxNode Convert(VisualBasicSyntaxNode input, SemanticModel semanticModel, Document targetDocument)
{
return VisualBasicConverter.Convert(input, semanticModel, targetDocument);
return VisualBasicConverter.ConvertSingle((VisualBasicCompilation) semanticModel.Compilation, (VisualBasicSyntaxTree) input.SyntaxTree);
}
}
}