提交 928237ec 编写于 作者: T TomasMatousek

We haven't calculated variable slot mapping when only whitespace/comments were...

We haven't calculated variable slot mapping when only whitespace/comments were updated in an active method. As a result the values of local variables were often lost after the update.

This changeset creates syntax map for such an edit.
Fixes RC bug 1113015. (changeset 1404723)
上级 ea718c30
......@@ -17,6 +17,86 @@ namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests
{
public class LocalSlotMappingTests : EditAndContinueTestBase
{
/// <summary>
/// If no changes were made we don't product a syntax map.
/// If we don't have syntax map and preserve variables is true we should still successfully map the locals to their previous slots.
/// </summary>
[Fact]
public void SlotMappingWithNoChanges()
{
var source0 = @"
using System;
class C
{
static void Main(string[] args)
{
var b = true;
do
{
Console.WriteLine(""hi"");
} while (b == true);
}
}
";
var compilation0 = CreateCompilationWithMscorlib(source0, options: TestOptions.DebugDll);
var compilation1 = compilation0.WithSource(source0);
var v0 = CompileAndVerify(compilation0);
var methodData0 = v0.TestData.GetMethodData("C.Main");
var method0 = compilation0.GetMember<MethodSymbol>("C.Main");
var method1 = compilation1.GetMember<MethodSymbol>("C.Main");
var generation0 = EmitBaseline.CreateInitialBaseline(ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData), methodData0.EncDebugInfoProvider());
v0.VerifyIL("C.Main", @"
{
// Code size 22 (0x16)
.maxstack 1
.locals init (bool V_0, //b
bool V_1)
-IL_0000: nop
-IL_0001: ldc.i4.1
IL_0002: stloc.0
-IL_0003: nop
-IL_0004: ldstr ""hi""
IL_0009: call ""void System.Console.WriteLine(string)""
IL_000e: nop
-IL_000f: nop
-IL_0010: ldloc.0
IL_0011: stloc.1
~IL_0012: ldloc.1
IL_0013: brtrue.s IL_0003
-IL_0015: ret
}", sequencePoints: "C.Main");
var diff1 = compilation1.EmitDifference(
generation0,
ImmutableArray.Create(new SemanticEdit(SemanticEditKind.Update, method0, method1, syntaxMap: null, preserveLocalVariables: true)));
diff1.VerifyIL("C.Main", @"
{
// Code size 22 (0x16)
.maxstack 1
.locals init (bool V_0, //b
bool V_1)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.0
IL_0003: nop
IL_0004: ldstr ""hi""
IL_0009: call ""void System.Console.WriteLine(string)""
IL_000e: nop
IL_000f: nop
IL_0010: ldloc.0
IL_0011: stloc.1
IL_0012: ldloc.1
IL_0013: brtrue.s IL_0003
IL_0015: ret
}");
}
[Fact]
public void OutOfOrderUserLocals()
{
......@@ -2421,7 +2501,7 @@ .locals init (bool V_0)
}
[Fact]
public void Do()
public void Do1()
{
var source0 = @"
class C
......
......@@ -52,8 +52,6 @@ Friend NotInheritable Class LocalVariableDeclaratorsCollector
End Sub
Public Overrides Sub VisitIdentifierName(node As IdentifierNameSyntax)
Debug.Assert(Not Me.builder.Contains(node))
Me.builder.Add(node)
End Sub
Public Overrides Sub VisitGoToStatement(node As GoToStatementSyntax)
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// 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;
using System.Collections.Generic;
......@@ -352,6 +352,12 @@ private static TextSpan GetActiveSpan(ForEachStatementSyntax node, ForEachPart p
}
}
internal override Func<SyntaxNode, SyntaxNode> CreateSyntaxMapForEquivalentNodes(SyntaxNode oldRoot, SyntaxNode newRoot)
{
Debug.Assert(SyntaxFactory.AreEquivalent(oldRoot, newRoot));
return newNode => SyntaxUtilities.FindPartner(newRoot, oldRoot, newNode);
}
protected override SyntaxNode FindEnclosingLambdaBody(SyntaxNode containerOpt, SyntaxNode node)
{
SyntaxNode root = GetEncompassingAncestor(containerOpt);
......@@ -2592,6 +2598,12 @@ protected override TextSpan GetExceptionHandlingRegion(SyntaxNode node, out bool
#region State Machines
internal override bool IsStateMachineMethod(SyntaxNode declaration)
{
return SyntaxUtilities.IsAsyncMethodOrLambda(declaration) ||
SyntaxUtilities.IsIteratorMethod(declaration);
}
protected override ImmutableArray<SyntaxNode> GetStateMachineSuspensionPoints(SyntaxNode body)
{
if (SyntaxUtilities.IsAsyncMethodOrLambda(body.Parent))
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// 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.Collections.Immutable;
using System.Diagnostics;
......@@ -185,6 +185,34 @@ public static void FindLeafNodeAndPartner(SyntaxNode leftRoot, int leftPosition,
}
}
public static SyntaxNode FindPartner(SyntaxNode leftRoot, SyntaxNode rightRoot, SyntaxNode leftNode)
{
// Finding a partner of a zero-width node is complicated and not supported atm:
Debug.Assert(leftNode.FullSpan.Length > 0);
Debug.Assert(leftNode.SyntaxTree == leftRoot.SyntaxTree);
SyntaxNode originalLeftNode = leftNode;
int leftPosition = leftNode.SpanStart;
leftNode = leftRoot;
SyntaxNode rightNode = rightRoot;
while (leftNode != originalLeftNode)
{
Debug.Assert(leftNode.RawKind == rightNode.RawKind);
int childIndex;
var leftChild = leftNode.ChildThatContainsPosition(leftPosition, out childIndex);
// Can only happen when searching for zero-width node.
Debug.Assert(!leftChild.IsToken);
rightNode = rightNode.ChildNodesAndTokens().ElementAt(childIndex).AsNode();
leftNode = leftChild.AsNode();
}
return rightNode;
}
public static bool IsNotLambda(SyntaxNode node)
{
return !IsLambda(node.Kind());
......@@ -324,29 +352,29 @@ public static bool HasBackingField(PropertyDeclarationSyntax property)
&& property.AccessorList.Accessors.Any(e => e.Body == null);
}
public static bool IsAsyncMethodOrLambda(SyntaxNode node)
public static bool IsAsyncMethodOrLambda(SyntaxNode declaration)
{
if (node.IsKind(SyntaxKind.ParenthesizedLambdaExpression))
if (declaration.IsKind(SyntaxKind.ParenthesizedLambdaExpression))
{
var lambda = (ParenthesizedLambdaExpressionSyntax)node;
var lambda = (ParenthesizedLambdaExpressionSyntax)declaration;
if (lambda.AsyncKeyword.IsKind(SyntaxKind.AsyncKeyword))
{
return true;
}
}
if (node.IsKind(SyntaxKind.SimpleLambdaExpression))
if (declaration.IsKind(SyntaxKind.SimpleLambdaExpression))
{
var lambda = (SimpleLambdaExpressionSyntax)node;
var lambda = (SimpleLambdaExpressionSyntax)declaration;
if (lambda.AsyncKeyword.IsKind(SyntaxKind.AsyncKeyword))
{
return true;
}
}
if (node.IsKind(SyntaxKind.AnonymousMethodExpression))
if (declaration.IsKind(SyntaxKind.AnonymousMethodExpression))
{
var lambda = (AnonymousMethodExpressionSyntax)node;
var lambda = (AnonymousMethodExpressionSyntax)declaration;
if (lambda.AsyncKeyword.IsKind(SyntaxKind.AsyncKeyword))
{
return true;
......@@ -354,17 +382,17 @@ public static bool IsAsyncMethodOrLambda(SyntaxNode node)
}
// expression bodied methods:
if (node.IsKind(SyntaxKind.ArrowExpressionClause))
if (declaration.IsKind(SyntaxKind.ArrowExpressionClause))
{
node = node.Parent;
declaration = declaration.Parent;
}
if (!node.IsKind(SyntaxKind.MethodDeclaration))
if (!declaration.IsKind(SyntaxKind.MethodDeclaration))
{
return false;
}
var method = (MethodDeclarationSyntax)node;
var method = (MethodDeclarationSyntax)declaration;
return method.Modifiers.Any(SyntaxKind.AsyncKeyword);
}
......@@ -386,5 +414,18 @@ public static ImmutableArray<SyntaxNode> GetYieldStatements(SyntaxNode body)
return ImmutableArray.CreateRange(body.DescendantNodes(n => !(n is ExpressionSyntax))
.Where(n => n.IsKind(SyntaxKind.YieldBreakStatement) || n.IsKind(SyntaxKind.YieldReturnStatement)));
}
public static bool IsIteratorMethod(SyntaxNode declaration)
{
// lambdas and expression-bodied methods can't be iterators:
if (!declaration.IsKind(SyntaxKind.MethodDeclaration))
{
return false;
}
// enumerate statements:
return declaration.DescendantNodes(n => !(n is ExpressionSyntax))
.Any(n => n.IsKind(SyntaxKind.YieldBreakStatement) || n.IsKind(SyntaxKind.YieldReturnStatement));
}
}
}
......@@ -129,6 +129,12 @@ private SyntaxNode FindStatement(SyntaxNode declarationBody, int position, out i
return FindStatementAndPartner(declarationBody, position, null, out partner, out statementPart);
}
/// <summary>
/// Returns a function that maps nodes of <paramref name="newRoot"/> to corresponding nodes of <paramref name="oldRoot"/>,
/// assuming that the bodies only differ in trivia.
/// </summary>
internal abstract Func<SyntaxNode, SyntaxNode> CreateSyntaxMapForEquivalentNodes(SyntaxNode oldRoot, SyntaxNode newRoot);
/// <summary>
/// Returns a node that represents a body of a lambda containing specified <paramref name="node"/>,
/// or null if the node isn't contained in a lambda. If a node is returned it must uniquely represent the lambda,
......@@ -193,7 +199,7 @@ private SyntaxNode FindStatement(SyntaxNode declarationBody, int position, out i
protected abstract List<SyntaxNode> GetExceptionHandlingAncestors(SyntaxNode node, bool isLeaf);
protected abstract ImmutableArray<SyntaxNode> GetStateMachineSuspensionPoints(SyntaxNode body);
protected abstract TextSpan GetExceptionHandlingRegion(SyntaxNode node, out bool coversAllChildren);
internal abstract void ReportSyntacticRudeEdits(List<RudeEditDiagnostic> diagnostics, Match<SyntaxNode> match, Edit<SyntaxNode> edit, Dictionary<SyntaxNode, EditKind> editMap);
internal abstract void ReportEnclosingExceptionHandlingRudeEdits(List<RudeEditDiagnostic> diagnostics, IEnumerable<Edit<SyntaxNode>> exceptionHandlingEdits, SyntaxNode oldStatement, SyntaxNode newStatement);
internal abstract void ReportOtherRudeEditsAroundActiveStatement(List<RudeEditDiagnostic> diagnostics, Match<SyntaxNode> match, SyntaxNode oldStatement, SyntaxNode newStatement, bool isLeaf);
......@@ -202,6 +208,7 @@ private SyntaxNode FindStatement(SyntaxNode declarationBody, int position, out i
internal abstract void ReportStateMachineSuspensionPointRudeEdits(List<RudeEditDiagnostic> diagnostics, SyntaxNode oldNode, SyntaxNode newNode);
internal abstract bool IsMethod(SyntaxNode declaration);
internal abstract bool IsStateMachineMethod(SyntaxNode declaration);
internal abstract SyntaxNode TryGetContainingTypeDeclaration(SyntaxNode memberDeclaration);
internal abstract bool HasBackingField(SyntaxNode propertyDeclaration);
......@@ -2043,9 +2050,15 @@ private static int CompareLineChanges(LineChange x, LineChange y)
if (IsMethod(edit.Key))
{
int start, end;
bool preserveLocalVariables = TryGetOverlappingActiveStatements(oldText, edit.Key.Span, oldActiveStatements, out start, out end);
semanticEdits.Add(new SemanticEdit(SemanticEditKind.Update, oldSymbol, newSymbol, preserveLocalVariables: preserveLocalVariables));
// TODO: also preserve local variables if the body contains lambdas
bool preserveLocalVariables =
TryGetOverlappingActiveStatements(oldText, edit.Key.Span, oldActiveStatements, out start, out end) ||
IsStateMachineMethod(edit.Key);
var syntaxMap = preserveLocalVariables ? CreateSyntaxMapForEquivalentNodes(edit.Key, edit.Value) : null;
semanticEdits.Add(new SemanticEdit(SemanticEditKind.Update, oldSymbol, newSymbol, syntaxMap, preserveLocalVariables));
newSymbolsWithEdit.Add(newSymbol);
}
else
......@@ -2337,6 +2350,7 @@ private INamedTypeSymbol TryGetPartnerType(SyntaxNode typeSyntax, Match<SyntaxNo
// of the constructor. They don't contain any local declarators that span statements (explicit or temp).
// And the constructor body haven't changed.
// TODO: preserve local variables if the constructor or the initializers contain lambdas
semanticEdits.Add(new SemanticEdit((oldCtor == null) ? SemanticEditKind.Insert : SemanticEditKind.Update, oldCtor, newCtor));
}
}
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
' 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 System.Collections.Immutable
Imports System.Composition
......@@ -204,6 +204,31 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue
End While
End Sub
Public Shared Function FindPartner(leftRoot As SyntaxNode, rightRoot As SyntaxNode, leftNode As SyntaxNode) As SyntaxNode
' Finding a partner of a zero-width node is complicated and not supported atm
Debug.Assert(leftNode.FullSpan.Length > 0)
Dim originalLeftNode = leftNode
Dim leftPosition = leftNode.SpanStart
leftNode = leftRoot
Dim rightNode = rightRoot
While leftNode IsNot originalLeftNode
Debug.Assert(leftNode.RawKind = rightNode.RawKind)
Dim childIndex = 0
Dim leftChild = leftNode.ChildThatContainsPosition(leftPosition, childIndex)
' Can only happen when searching for zero-width node.
Debug.Assert(Not leftChild.IsToken)
rightNode = rightNode.ChildNodesAndTokens().ElementAt(childIndex).AsNode()
leftNode = leftChild.AsNode()
End While
Return rightNode
End Function
Public Shared Function IsNotLambda(node As SyntaxNode) As Boolean
Return Not IsLambda(node.Kind())
End Function
......@@ -261,29 +286,41 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue
Not DirectCast(propertyDeclaration, PropertyStatementSyntax).Modifiers.Any(SyntaxKind.MustOverrideKeyword)
End Function
Public Shared Function IsAsyncMethodOrLambda(node As SyntaxNode) As Boolean
' TODO: check Lambda
Select Case node.Kind
Case SyntaxKind.SubBlock,
SyntaxKind.FunctionBlock
Return DirectCast(node, MethodBlockBaseSyntax).BlockStatement.Modifiers.Any(SyntaxKind.AsyncKeyword)
End Select
Public Shared Function IsAsyncMethodOrLambda(declaration As SyntaxNode) As Boolean
Return GetModifiers(declaration).Any(SyntaxKind.AsyncKeyword)
End Function
Return False
Public Shared Function IsIteratorMethodOrLambda(declaration As SyntaxNode) As Boolean
Return GetModifiers(declaration).Any(SyntaxKind.IteratorKeyword)
End Function
Public Shared Function GetAwaitExpressions(body As SyntaxNode) As ImmutableArray(Of SyntaxNode)
' skip lambda bodies
Return ImmutableArray.CreateRange(Of SyntaxNode)(body.DescendantNodes(Function(n) IsNotLambda(n)).
Return ImmutableArray.CreateRange(body.DescendantNodes(Function(n) IsNotLambda(n)).
Where(Function(n) n.IsKind(SyntaxKind.AwaitExpression)))
End Function
Public Shared Function GetYieldStatements(body As SyntaxNode) As ImmutableArray(Of SyntaxNode)
' enumerate statements:
Return ImmutableArray.CreateRange(Of SyntaxNode)(body.DescendantNodes(Function(n) TypeOf n IsNot ExpressionSyntax).
Return ImmutableArray.CreateRange(body.DescendantNodes(Function(n) TypeOf n IsNot ExpressionSyntax).
Where(Function(n) n.IsKind(SyntaxKind.YieldStatement)))
End Function
Public Shared Function GetModifiers(declaration As SyntaxNode) As SyntaxTokenList
Select Case declaration.Kind
Case SyntaxKind.SubBlock,
SyntaxKind.FunctionBlock
Return DirectCast(declaration, MethodBlockBaseSyntax).BlockStatement.Modifiers
Case SyntaxKind.MultiLineFunctionLambdaExpression,
SyntaxKind.SingleLineFunctionLambdaExpression,
SyntaxKind.MultiLineSubLambdaExpression,
SyntaxKind.SingleLineSubLambdaExpression
Return DirectCast(declaration, LambdaExpressionSyntax).SubOrFunctionHeader.Modifiers
End Select
Return Nothing
End Function
End Class
End Namespace
......@@ -397,6 +397,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue
Return node
End Function
Friend Overrides Function CreateSyntaxMapForEquivalentNodes(oldRoot As SyntaxNode, newRoot As SyntaxNode) As Func(Of SyntaxNode, SyntaxNode)
Debug.Assert(SyntaxFactory.AreEquivalent(oldRoot, newRoot))
Return Function(newNode) SyntaxUtilities.FindPartner(newRoot, oldRoot, newNode)
End Function
Protected Overrides Function FindEnclosingLambdaBody(containerOpt As SyntaxNode, node As SyntaxNode) As SyntaxNode
Dim root As SyntaxNode = GetEncompassingAncestor(containerOpt)
......@@ -2670,7 +2675,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue
#Region "State Machines"
Friend Overrides Function IsStateMachineMethod(declaration As SyntaxNode) As Boolean
Return SyntaxUtilities.IsAsyncMethodOrLambda(declaration) OrElse
SyntaxUtilities.IsIteratorMethodOrLambda(declaration)
End Function
Protected Overrides Function GetStateMachineSuspensionPoints(body As SyntaxNode) As ImmutableArray(Of SyntaxNode)
' In VB declaration and body are reprsented by teh same node for both lambdas and methods (unlike C#)
If SyntaxUtilities.IsAsyncMethodOrLambda(body) Then
Return SyntaxUtilities.GetAwaitExpressions(body)
Else
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册