提交 9fd0eebc 编写于 作者: D Dustin Campbell

Merge pull request #6432 from DustinCampbell/issue-6306

Add expansion rule to add cast to lambda expression bodies
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeActions
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.Simplification
Imports Microsoft.CodeAnalysis.Text
Imports Roslyn.Utilities
Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Expansion
Public MustInherit Class AbstractExpansionTest
Protected Sub Test(definition As XElement, expected As XElement, Optional useLastProject As Boolean = False)
Protected Sub Test(definition As XElement, expected As XElement, Optional useLastProject As Boolean = False, Optional expandParameter As Boolean = False)
Using workspace = TestWorkspaceFactory.CreateWorkspace(definition)
Dim hostDocument = If(Not useLastProject, workspace.Documents.Single(), workspace.Documents.Last())
......@@ -28,12 +26,12 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Expansion
If (hostDocument.AnnotatedSpans.ContainsKey("Expand")) Then
For Each span In hostDocument.AnnotatedSpans("Expand")
Dim node = GetExpressionSyntaxWithSameSpan(root.FindToken(span.Start).Parent, span.End)
root = root.ReplaceNode(node, Simplifier.ExpandAsync(node, document, Nothing).Result)
root = root.ReplaceNode(node, Simplifier.ExpandAsync(node, document, expandInsideNode:=Nothing, expandParameter:=expandParameter).Result)
Next
ElseIf (hostDocument.AnnotatedSpans.ContainsKey("ExpandAndSimplify")) Then
For Each span In hostDocument.AnnotatedSpans("ExpandAndSimplify")
Dim node = GetExpressionSyntaxWithSameSpan(root.FindToken(span.Start).Parent, span.End)
root = root.ReplaceNode(node, Simplifier.ExpandAsync(node, document, Nothing).Result)
root = root.ReplaceNode(node, Simplifier.ExpandAsync(node, document, expandInsideNode:=Nothing, expandParameter:=expandParameter).Result)
document = document.WithSyntaxRoot(root)
document = Simplifier.ReduceAsync(document, Simplifier.Annotation).Result
root = document.GetSyntaxRootAsync().Result
......
......@@ -148,6 +148,119 @@ namespace NS
Test(input, expected)
End Sub
<WpfFact, Trait(Traits.Feature, Traits.Features.Expansion)>
Public Sub CSharp_GenericNameExpansion_DontExpandAnonymousTypes()
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
class C
{
static void Mumble&lt;T&gt;(T anonymousType) { }
static void M()
{
{|Expand:Mumble|}(new { x = 42 });
}
}
</Document>
</Project>
</Workspace>
Dim expected =
<code>
class C
{
static void Mumble&lt;T&gt;(T anonymousType) { }
static void M()
{
global::C.Mumble(new { x = 42 });
}
}
</code>
Test(input, expected)
End Sub
<WpfFact, Trait(Traits.Feature, Traits.Features.Expansion)>
Public Sub CSharp_LambdaParameter_DontExpandAnonymousTypes1()
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
using System;
class C
{
static void Mumble&lt;T&gt;(T anonymousType, Action&lt;T, int&gt; lambda) { }
static void M()
{
Mumble(new { x = 42 }, {|Expand:a => a.x|});
}
}
</Document>
</Project>
</Workspace>
Dim expected =
<code>
using System;
class C
{
static void Mumble&lt;T&gt;(T anonymousType, Action&lt;T, int&gt; lambda) { }
static void M()
{
Mumble(new { x = 42 }, a => a.x);
}
}
</code>
Test(input, expected, expandParameter:=True)
End Sub
<WpfFact, Trait(Traits.Feature, Traits.Features.Expansion)>
Public Sub CSharp_LambdaParameter_DontExpandAnonymousTypes2()
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
using System;
class C
{
static void Mumble&lt;T&gt;(T anonymousType, Action&lt;T, int, int&gt; lambda) { }
static void M()
{
Mumble(new { x = 42 }, {|Expand:(a, y) => a.x|});
}
}
</Document>
</Project>
</Workspace>
Dim expected =
<code>
using System;
class C
{
static void Mumble&lt;T&gt;(T anonymousType, Action&lt;T, int, int&gt; lambda) { }
static void M()
{
Mumble(new { x = 42 }, (a, y) => a.x);
}
}
</code>
Test(input, expected, expandParameter:=True)
End Sub
#End Region
#Region "Visual Basic tests"
<WorkItem(1913, "https://github.com/dotnet/roslyn/issues/1913")>
<WpfFact, Trait(Traits.Feature, Traits.Features.Expansion)>
Public Sub VisualBasic_SimpleIdentifierAliasExpansion_AliasBinds()
......@@ -237,6 +350,7 @@ End Namespace
Test(input, expected)
End Sub
#End Region
End Class
......
......@@ -546,5 +546,55 @@ class C
result.AssertLabeledSpansAre("second", "DefaultValue(C.Method)", type:=RelatedLocationType.ResolvedReferenceConflict)
End Using
End Sub
<WorkItem(6306, "https://github.com/dotnet/roslyn/issues/6306")>
<WpfFact, Trait(Traits.Feature, Traits.Features.Rename)>
Public Sub ResolveConflictInAnonymousTypeProperty()
Using result = RenameEngineResult.Create(
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document><![CDATA[
using System;
class C
{
void X<T>(T t, Func<T, long> e) { {|first:X|}(new { a = 1 }, a => a.a); }
[Obsolete]
void {|origin:$$Y|}<T>(T t, Func<T, int> e) { }
}
]]></Document>
</Project>
</Workspace>, renameTo:="X")
result.AssertLabeledSpansAre("first", "X(new { a = 1 }, a => (long)a.a);", type:=RelatedLocationType.ResolvedNonReferenceConflict)
result.AssertLabeledSpansAre("origin", "X", type:=RelatedLocationType.NoConflict)
End Using
End Sub
<WorkItem(6308, "https://github.com/dotnet/roslyn/issues/6308")>
<WpfFact, Trait(Traits.Feature, Traits.Features.Rename)>
Public Sub ResolveConflictWhenAnonymousTypeIsUsedAsGenericArgument()
Using result = RenameEngineResult.Create(
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document><![CDATA[
using System;
class C
{
void M<T>(T t, Func<T, int, int> e) { }
int M<T>(T t, Func<T, long, long> e) => {|first:M|}(new { }, (_, a) => {|second:X|}(a));
long X(long a) => a;
int {|origin:$$Y|}(int a) => a;
}
]]></Document>
</Project>
</Workspace>, renameTo:="X")
result.AssertLabeledSpansAre("first", "M(new { }, (_, a) => (long)X(a))", type:=RelatedLocationType.ResolvedNonReferenceConflict)
result.AssertLabeledSpansAre("second", "M(new { }, (_, a) => (long)X(a))", type:=RelatedLocationType.ResolvedNonReferenceConflict)
result.AssertLabeledSpansAre("origin", "X", type:=RelatedLocationType.NoConflict)
End Using
End Sub
End Class
End Namespace
......@@ -451,10 +451,8 @@ private ExpressionSyntax SkipRedundantExteriorParentheses(ExpressionSyntax expre
var newVariableDeclarator = await FindDeclaratorAsync(updatedDocument, cancellationToken).ConfigureAwait(false);
localSymbol = (ILocalSymbol)semanticModel.GetDeclaredSymbol(newVariableDeclarator, cancellationToken);
bool wasCastAdded;
var explicitCastExpression = newExpression.CastIfPossible(localSymbol.Type, newVariableDeclarator.SpanStart, semanticModel, out wasCastAdded);
if (wasCastAdded)
var explicitCastExpression = newExpression.CastIfPossible(localSymbol.Type, newVariableDeclarator.SpanStart, semanticModel);
if (explicitCastExpression != newExpression)
{
updatedDocument = await updatedDocument.ReplaceNodeAsync(newExpression, explicitCastExpression, cancellationToken).ConfigureAwait(false);
semanticModel = await updatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
......
......@@ -80,11 +80,8 @@ public static ExpressionSyntax Parenthesize(this ExpressionSyntax expression, bo
this ExpressionSyntax expression,
ITypeSymbol targetType,
int position,
SemanticModel semanticModel,
out bool wasCastAdded)
SemanticModel semanticModel)
{
wasCastAdded = false;
if (targetType.ContainsAnonymousType())
{
return expression;
......@@ -122,7 +119,6 @@ public static ExpressionSyntax Parenthesize(this ExpressionSyntax expression, bo
return expression;
}
wasCastAdded = true;
return castExpression;
}
......
......@@ -93,24 +93,120 @@ private bool IsPassedToDelegateCreationExpression(ArgumentSyntax argument, IType
return false;
}
// This expander expands only the parameters within the parameterList
public override SyntaxNode VisitParameter(ParameterSyntax node)
private SpeculationAnalyzer GetSpeculationAnalyzer(ExpressionSyntax expression, ExpressionSyntax newExpression)
{
var newNode = (ParameterSyntax)base.VisitParameter(node);
return new SpeculationAnalyzer(expression, newExpression, this._semanticModel, this._cancellationToken);
}
private bool TryCastTo(ITypeSymbol targetType, ExpressionSyntax expression, ExpressionSyntax newExpression, out ExpressionSyntax newExpressionWithCast)
{
var speculativeAnalyzer = GetSpeculationAnalyzer(expression, newExpression);
var speculativeSemanticModel = speculativeAnalyzer.SpeculativeSemanticModel;
var speculatedExpression = speculativeAnalyzer.ReplacedExpression;
var result = speculatedExpression.CastIfPossible(targetType, speculatedExpression.SpanStart, speculativeSemanticModel);
if (result != speculatedExpression)
{
newExpressionWithCast = result;
return true;
}
if (node != null && node.IsParentKind(SyntaxKind.ParameterList) &&
newNode != null && newNode.Type == null &&
_expandParameter)
newExpressionWithCast = null;
return false;
}
private bool TryGetLambdaExpressionBodyWithCast(LambdaExpressionSyntax lambdaExpression, LambdaExpressionSyntax newLambdaExpression, out ExpressionSyntax newLambdaExpressionBodyWithCast)
{
if (newLambdaExpression.Body is ExpressionSyntax)
{
var newNodeSymbol = _semanticModel.GetDeclaredSymbol(node);
if (newNodeSymbol != null && newNodeSymbol.Kind == SymbolKind.Parameter)
var body = (ExpressionSyntax)lambdaExpression.Body;
var newBody = (ExpressionSyntax)newLambdaExpression.Body;
var returnType = (_semanticModel.GetSymbolInfo(lambdaExpression).Symbol as IMethodSymbol)?.ReturnType;
if (returnType != null)
{
if (newNodeSymbol.Type != null)
return TryCastTo(returnType, body, newBody, out newLambdaExpressionBodyWithCast);
}
}
newLambdaExpressionBodyWithCast = null;
return false;
}
public override SyntaxNode VisitReturnStatement(ReturnStatementSyntax node)
{
var newNode = base.VisitReturnStatement(node);
if (newNode is ReturnStatementSyntax)
{
var newReturnStatement = (ReturnStatementSyntax)newNode;
var parentLambda = node.FirstAncestorOrSelf<LambdaExpressionSyntax>();
if (parentLambda != null)
{
var returnType = (_semanticModel.GetSymbolInfo(parentLambda).Symbol as IMethodSymbol)?.ReturnType;
if (returnType != null)
{
return newNode.WithType(newNodeSymbol.Type.GenerateTypeSyntax().WithTrailingTrivia(s_oneWhitespaceSeparator))
.WithAdditionalAnnotations(Simplifier.Annotation);
ExpressionSyntax newExpressionWithCast;
if (TryCastTo(returnType, node.Expression, newReturnStatement.Expression, out newExpressionWithCast))
{
newNode = newReturnStatement.WithExpression(newExpressionWithCast);
}
}
}
}
return newNode;
}
public override SyntaxNode VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node)
{
var newNode = base.VisitParenthesizedLambdaExpression(node);
if (newNode is ParenthesizedLambdaExpressionSyntax)
{
var parenthesizedLambda = (ParenthesizedLambdaExpressionSyntax)newNode;
// First, try to add a cast to the lambda.
ExpressionSyntax newLambdaExpressionBodyWithCast;
if (TryGetLambdaExpressionBodyWithCast(node, parenthesizedLambda, out newLambdaExpressionBodyWithCast))
{
parenthesizedLambda = parenthesizedLambda.WithBody(newLambdaExpressionBodyWithCast);
}
// Next, try to add a types to the lambda parameters
if (_expandParameter && parenthesizedLambda.ParameterList != null)
{
var parameterList = parenthesizedLambda.ParameterList;
var parameters = parameterList.Parameters.ToArray();
if (parameters.Length > 0 && parameters.Any(p => p.Type == null))
{
var parameterSymbols = node.ParameterList.Parameters
.Select(p => _semanticModel.GetDeclaredSymbol(p, _cancellationToken))
.ToArray();
if (parameterSymbols.All(p => p.Type?.ContainsAnonymousType() == false))
{
var newParameters = parameterList.Parameters;
for (int i = 0; i < parameterSymbols.Length; i++)
{
var typeSyntax = parameterSymbols[i].Type.GenerateTypeSyntax().WithTrailingTrivia(s_oneWhitespaceSeparator);
var newParameter = parameters[i].WithType(typeSyntax).WithAdditionalAnnotations(Simplifier.Annotation);
newParameters = newParameters.Replace(parameters[i], newParameter);
}
var newParameterList = parameterList.WithParameters(newParameters);
var newParenthesizedLambda = parenthesizedLambda.WithParameterList(newParameterList);
return SimplificationHelpers.CopyAnnotations(from: parenthesizedLambda, to: newParenthesizedLambda);
}
}
}
return parenthesizedLambda;
}
return newNode;
......@@ -120,28 +216,39 @@ public override SyntaxNode VisitSimpleLambdaExpression(SimpleLambdaExpressionSyn
{
var newNode = base.VisitSimpleLambdaExpression(node);
if (newNode is SimpleLambdaExpressionSyntax && _expandParameter)
if (newNode is SimpleLambdaExpressionSyntax)
{
var newSimpleLambda = (SimpleLambdaExpressionSyntax)newNode;
var parameterSymbol = _semanticModel.GetDeclaredSymbol(node.Parameter);
if (parameterSymbol != null && parameterSymbol.Kind == SymbolKind.Parameter)
var simpleLambda = (SimpleLambdaExpressionSyntax)newNode;
// First, try to add a cast to the lambda.
ExpressionSyntax newLambdaExpressionBodyWithCast;
if (TryGetLambdaExpressionBodyWithCast(node, simpleLambda, out newLambdaExpressionBodyWithCast))
{
if (parameterSymbol.Type != null)
simpleLambda = simpleLambda.WithBody(newLambdaExpressionBodyWithCast);
}
// Next, try to add a type to the lambda parameter
if (_expandParameter)
{
var parameterSymbol = _semanticModel.GetDeclaredSymbol(node.Parameter);
if (parameterSymbol?.Type?.ContainsAnonymousType() == false)
{
var typeSyntax = parameterSymbol.Type.GenerateTypeSyntax().WithTrailingTrivia(s_oneWhitespaceSeparator);
var newSimpleLambdaParameter = newSimpleLambda.Parameter.WithType(typeSyntax).WithoutTrailingTrivia();
var newSimpleLambdaParameter = simpleLambda.Parameter.WithType(typeSyntax).WithoutTrailingTrivia();
var parenthesizedLambda = SyntaxFactory.ParenthesizedLambdaExpression(
newSimpleLambda.AsyncKeyword,
simpleLambda.AsyncKeyword,
SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(newSimpleLambdaParameter))
.WithTrailingTrivia(newSimpleLambda.Parameter.GetTrailingTrivia())
.WithLeadingTrivia(newSimpleLambda.Parameter.GetLeadingTrivia()),
newSimpleLambda.ArrowToken,
newSimpleLambda.Body).WithAdditionalAnnotations(Simplifier.Annotation);
.WithTrailingTrivia(simpleLambda.Parameter.GetTrailingTrivia())
.WithLeadingTrivia(simpleLambda.Parameter.GetLeadingTrivia()),
simpleLambda.ArrowToken,
simpleLambda.Body).WithAdditionalAnnotations(Simplifier.Annotation);
return SimplificationHelpers.CopyAnnotations(newNode, parenthesizedLambda);
return SimplificationHelpers.CopyAnnotations(from: simpleLambda, to: parenthesizedLambda);
}
}
return simpleLambda;
}
return newNode;
......@@ -157,15 +264,10 @@ public override SyntaxNode VisitArgument(ArgumentSyntax node)
if (argumentType != null &&
!IsPassedToDelegateCreationExpression(node, argumentType))
{
var specAnalyzer = new SpeculationAnalyzer(node.Expression, newArgument.Expression, _semanticModel, _cancellationToken);
var speculativeSemanticModel = specAnalyzer.SpeculativeSemanticModel;
var speculatedExpression = specAnalyzer.ReplacedExpression;
bool wasCastAdded;
var newArgumentExpression = speculatedExpression.CastIfPossible(argumentType, speculatedExpression.SpanStart, speculativeSemanticModel, out wasCastAdded);
if (wasCastAdded)
ExpressionSyntax newArgumentExpressionWithCast;
if (TryCastTo(argumentType, node.Expression, newArgument.Expression, out newArgumentExpressionWithCast))
{
return newArgument.WithExpression(newArgumentExpression);
return newArgument.WithExpression(newArgumentExpressionWithCast);
}
}
......@@ -605,18 +707,20 @@ private ExpressionSyntax TryAddTypeArgumentToIdentifierName(ExpressionSyntax new
if (((IMethodSymbol)symbol).TypeArguments.Length != 0)
{
var typeArguments = ((IMethodSymbol)symbol).TypeArguments;
var genericName = SyntaxFactory.GenericName(
((IdentifierNameSyntax)newNode).Identifier,
SyntaxFactory.TypeArgumentList(
SyntaxFactory.SeparatedList(
typeArguments.Select(p => SyntaxFactory.ParseTypeName(p.ToDisplayParts(s_typeNameFormatWithGenerics).ToDisplayString())))))
.WithLeadingTrivia(newNode.GetLeadingTrivia())
.WithTrailingTrivia(newNode.GetTrailingTrivia())
.WithAdditionalAnnotations(Simplifier.Annotation);
genericName = newNode.CopyAnnotationsTo(genericName);
return genericName;
if (!typeArguments.Any(t => t.ContainsAnonymousType()))
{
var genericName = SyntaxFactory.GenericName(
((IdentifierNameSyntax)newNode).Identifier,
SyntaxFactory.TypeArgumentList(
SyntaxFactory.SeparatedList(
typeArguments.Select(p => SyntaxFactory.ParseTypeName(p.ToDisplayParts(s_typeNameFormatWithGenerics).ToDisplayString())))))
.WithLeadingTrivia(newNode.GetLeadingTrivia())
.WithTrailingTrivia(newNode.GetTrailingTrivia())
.WithAdditionalAnnotations(Simplifier.Annotation);
genericName = newNode.CopyAnnotationsTo(genericName);
return genericName;
}
}
}
......@@ -805,7 +909,7 @@ private bool IsPropertyNameOfObjectInitializer(SimpleNameSyntax identifierName)
{
return left
.WithLeadingTrivia(rewrittenNode.GetLeadingTrivia())
.WithTrailingTrivia(rewrittenNode.GetTrailingTrivia());
.WithTrailingTrivia(rewrittenNode.GetTrailingTrivia());
}
// now create syntax for the combination of left and right syntax, or a simple replacement in case of an identifier
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册