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:
Родитель
408ba51523
Коммит
b4cf4f5d84
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче