// 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.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Differencing;
using Microsoft.CodeAnalysis.EditAndContinue;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue
{
[ExportLanguageService(typeof(IEditAndContinueAnalyzer), LanguageNames.CSharp), Shared]
internal sealed class CSharpEditAndContinueAnalyzer : AbstractEditAndContinueAnalyzer
{
#region Syntax Analysis
private enum ConstructorPart
{
None = 0,
DefaultBaseConstructorCall = 1,
}
private enum BlockPart
{
None = 0,
OpenBrace = 1,
CloseBrace = 2,
}
private enum ForEachPart
{
None = 0,
ForEach = 1,
VariableDeclaration = 2,
In = 3,
Expression = 4,
}
///
/// for methods, operators, constructors, destructors and accessors.
/// for field initializers.
/// for property initializers and expression bodies.
/// for indexer expression bodies.
///
internal override SyntaxNode FindMemberDeclaration(SyntaxNode rootOpt, SyntaxNode node)
{
while (node != rootOpt)
{
switch (node.Kind())
{
case SyntaxKind.MethodDeclaration:
case SyntaxKind.ConversionOperatorDeclaration:
case SyntaxKind.OperatorDeclaration:
case SyntaxKind.SetAccessorDeclaration:
case SyntaxKind.AddAccessorDeclaration:
case SyntaxKind.RemoveAccessorDeclaration:
case SyntaxKind.GetAccessorDeclaration:
case SyntaxKind.ConstructorDeclaration:
case SyntaxKind.DestructorDeclaration:
return node;
case SyntaxKind.PropertyDeclaration:
// int P { get; } = [|initializer|];
Debug.Assert(((PropertyDeclarationSyntax)node).Initializer != null);
return node;
case SyntaxKind.FieldDeclaration:
case SyntaxKind.EventFieldDeclaration:
// Active statements encompassing modifiers or type correspond to the first initialized field.
// [|public static int F = 1|], G = 2;
return ((BaseFieldDeclarationSyntax)node).Declaration.Variables.First();
case SyntaxKind.VariableDeclarator:
// public static int F = 1, [|G = 2|];
Debug.Assert(node.Parent.IsKind(SyntaxKind.VariableDeclaration));
switch (node.Parent.Parent.Kind())
{
case SyntaxKind.FieldDeclaration:
case SyntaxKind.EventFieldDeclaration:
return node;
}
node = node.Parent;
break;
}
node = node.Parent;
}
return null;
}
///
/// Given a node representing a declaration ( = true) or a top-level match node ( = false) returns:
/// - for method-like member declarations with block bodies (methods, operators, constructors, destructors, accessors).
/// - for variable declarators of fields, properties with an initializer expression, or
/// for method-like member declarations with expression bodies (methods, properties, indexers, operators)
///
/// A null reference otherwise.
///
internal override SyntaxNode TryGetDeclarationBody(SyntaxNode node, bool isMember)
{
if (node.IsKind(SyntaxKind.VariableDeclarator))
{
return (((VariableDeclaratorSyntax)node).Initializer)?.Value;
}
return SyntaxUtilities.TryGetMethodDeclarationBody(node);
}
protected override ImmutableArray GetCapturedVariables(SemanticModel model, SyntaxNode memberBody)
{
Debug.Assert(memberBody.IsKind(SyntaxKind.Block) || memberBody is ExpressionSyntax);
return model.AnalyzeDataFlow(memberBody).Captured;
}
internal override bool HasParameterClosureScope(ISymbol member)
{
// in instance constructor parameters are lifted to a closure different from method body
return (member as IMethodSymbol)?.MethodKind == MethodKind.Constructor;
}
protected override IEnumerable GetVariableUseSites(IEnumerable roots, ISymbol localOrParameter, SemanticModel model, CancellationToken cancellationToken)
{
Debug.Assert(localOrParameter is IParameterSymbol || localOrParameter is ILocalSymbol || localOrParameter is IRangeVariableSymbol);
// not supported (it's non trivial to find all places where "this" is used):
Debug.Assert(!localOrParameter.IsThisParameter());
return from root in roots
from node in root.DescendantNodesAndSelf()
where node.IsKind(SyntaxKind.IdentifierName)
let nameSyntax = (IdentifierNameSyntax)node
where (string)nameSyntax.Identifier.Value == localOrParameter.Name &&
(model.GetSymbolInfo(nameSyntax, cancellationToken).Symbol?.Equals(localOrParameter) ?? false)
select node;
}
///
/// If is a method, accessor, operator, destructor, or constructor without an initializer,
/// tokens of its block body, or tokens of the expression body.
///
/// If is an indexer declaration the tokens of its expression body.
///
/// If is a property declaration the tokens of its expression body or initializer.
///
/// If is a constructor with an initializer,
/// tokens of the initializer concatenated with tokens of the constructor body.
///
/// If is a variable declarator of a field with an initializer,
/// tokens of the field initializer.
///
/// Null reference otherwise.
///
internal override IEnumerable TryGetActiveTokens(SyntaxNode node)
{
if (node.IsKind(SyntaxKind.VariableDeclarator))
{
// TODO: The logic is similar to BreakpointSpans.TryCreateSpanForVariableDeclaration. Can we abstract it?
var declarator = node;
var fieldDeclaration = (BaseFieldDeclarationSyntax)declarator.Parent.Parent;
var variableDeclaration = fieldDeclaration.Declaration;
if (fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
return null;
}
if (variableDeclaration.Variables.Count == 1)
{
if (variableDeclaration.Variables[0].Initializer == null)
{
return null;
}
return fieldDeclaration.Modifiers.Concat(variableDeclaration.DescendantTokens()).Concat(fieldDeclaration.SemicolonToken);
}
if (declarator == variableDeclaration.Variables[0])
{
return fieldDeclaration.Modifiers.Concat(variableDeclaration.Type.DescendantTokens()).Concat(node.DescendantTokens());
}
return declarator.DescendantTokens();
}
var bodyTokens = SyntaxUtilities.TryGetMethodDeclarationBody(node)?.DescendantTokens();
if (node.IsKind(SyntaxKind.ConstructorDeclaration))
{
var ctor = (ConstructorDeclarationSyntax)node;
if (ctor.Initializer != null)
{
bodyTokens = ctor.Initializer.DescendantTokens().Concat(bodyTokens);
}
}
return bodyTokens;
}
protected override SyntaxNode GetEncompassingAncestorImpl(SyntaxNode bodyOrMatchRoot)
{
// Constructor may contain active nodes outside of its body (constructor initializer),
// but within the body of the member declaration (the parent).
if (bodyOrMatchRoot.Parent.IsKind(SyntaxKind.ConstructorDeclaration))
{
return bodyOrMatchRoot.Parent;
}
// Field initializer match root -- an active statement may include the modifiers
// and type specification of the field declaration.
if (bodyOrMatchRoot.IsKind(SyntaxKind.EqualsValueClause) &&
bodyOrMatchRoot.Parent.IsKind(SyntaxKind.VariableDeclarator) &&
bodyOrMatchRoot.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration))
{
return bodyOrMatchRoot.Parent.Parent;
}
// Field initializer body -- an active statement may include the modifiers
// and type specification of the field declaration.
if (bodyOrMatchRoot.Parent.IsKind(SyntaxKind.EqualsValueClause) &&
bodyOrMatchRoot.Parent.Parent.IsKind(SyntaxKind.VariableDeclarator) &&
bodyOrMatchRoot.Parent.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration))
{
return bodyOrMatchRoot.Parent.Parent.Parent;
}
// otherwise all active statements are covered by the body/match root itself:
return bodyOrMatchRoot;
}
protected override SyntaxNode FindStatementAndPartner(SyntaxNode declarationBody, int position, SyntaxNode partnerDeclarationBodyOpt, out SyntaxNode partnerOpt, out int statementPart)
{
SyntaxUtilities.AssertIsBody(declarationBody, allowLambda: false);
if (position < declarationBody.SpanStart)
{
// Only constructors and the field initializers may have an [|active statement|] starting outside of the <>.
// Constructor: [|public C()|] <<{ }>>
// Constructor initializer: public C() : [|base(expr)|] <<{ }>>
// Constructor initializer with lambda: public C() : base(() => { [|...|] }) <<{ }>>
// Field initializers: [|public int a = <>|], [|b = <>|];
// No need to special case property initializers here, the active statement always spans the initializer expression.
if (declarationBody.Parent.Kind() == SyntaxKind.ConstructorDeclaration)
{
var constructor = (ConstructorDeclarationSyntax)declarationBody.Parent;
var partnerConstructor = (ConstructorDeclarationSyntax)partnerDeclarationBodyOpt?.Parent;
if (constructor.Initializer == null || position < constructor.Initializer.ColonToken.SpanStart)
{
statementPart = (int)ConstructorPart.DefaultBaseConstructorCall;
partnerOpt = partnerConstructor;
return constructor;
}
declarationBody = constructor.Initializer;
partnerDeclarationBodyOpt = partnerConstructor?.Initializer;
}
}
if (!declarationBody.FullSpan.Contains(position))
{
// invalid position, let's find a labeled node that encompasses the body:
position = declarationBody.SpanStart;
}
SyntaxNode node;
if (partnerDeclarationBodyOpt != null)
{
SyntaxUtilities.FindLeafNodeAndPartner(declarationBody, position, partnerDeclarationBodyOpt, out node, out partnerOpt);
}
else
{
node = declarationBody.FindToken(position).Parent;
partnerOpt = null;
}
while (node != declarationBody && !StatementSyntaxComparer.HasLabel(node) && !LambdaUtilities.IsLambdaBodyStatementOrExpression(node))
{
node = node.Parent;
if (partnerOpt != null)
{
partnerOpt = partnerOpt.Parent;
}
}
switch (node.Kind())
{
case SyntaxKind.Block:
statementPart = (int)GetStatementPart((BlockSyntax)node, position);
break;
case SyntaxKind.ForEachStatement:
case SyntaxKind.ForEachVariableStatement:
statementPart = (int)GetStatementPart((CommonForEachStatementSyntax)node, position);
break;
case SyntaxKind.VariableDeclaration:
// VariableDeclaration ::= TypeSyntax CommaSeparatedList(VariableDeclarator)
//
// The compiler places sequence points after each local variable initialization.
// The TypeSyntax is considered to be part of the first sequence span.
node = ((VariableDeclarationSyntax)node).Variables.First();
if (partnerOpt != null)
{
partnerOpt = ((VariableDeclarationSyntax)partnerOpt).Variables.First();
}
statementPart = 0;
break;
default:
statementPart = 0;
break;
}
return node;
}
private static BlockPart GetStatementPart(BlockSyntax node, int position)
{
return position < node.OpenBraceToken.Span.End ? BlockPart.OpenBrace : BlockPart.CloseBrace;
}
private static TextSpan GetActiveSpan(BlockSyntax node, BlockPart part)
{
switch (part)
{
case BlockPart.OpenBrace:
return node.OpenBraceToken.Span;
case BlockPart.CloseBrace:
return node.CloseBraceToken.Span;
default:
throw ExceptionUtilities.UnexpectedValue(part);
}
}
private static ForEachPart GetStatementPart(CommonForEachStatementSyntax node, int position)
{
return position < node.OpenParenToken.SpanStart ? ForEachPart.ForEach :
position < node.InKeyword.SpanStart ? ForEachPart.VariableDeclaration :
position < node.Expression.SpanStart ? ForEachPart.In :
ForEachPart.Expression;
}
private static TextSpan GetActiveSpan(ForEachStatementSyntax node, ForEachPart part)
{
switch (part)
{
case ForEachPart.ForEach:
return node.ForEachKeyword.Span;
case ForEachPart.VariableDeclaration:
return TextSpan.FromBounds(node.Type.SpanStart, node.Identifier.Span.End);
case ForEachPart.In:
return node.InKeyword.Span;
case ForEachPart.Expression:
return node.Expression.Span;
default:
throw ExceptionUtilities.UnexpectedValue(part);
}
}
private static TextSpan GetActiveSpan(ForEachVariableStatementSyntax node, ForEachPart part)
{
switch (part)
{
case ForEachPart.ForEach:
return node.ForEachKeyword.Span;
case ForEachPart.VariableDeclaration:
return TextSpan.FromBounds(node.Variable.SpanStart, node.Variable.Span.End);
case ForEachPart.In:
return node.InKeyword.Span;
case ForEachPart.Expression:
return node.Expression.Span;
default:
throw ExceptionUtilities.UnexpectedValue(part);
}
}
protected override bool AreEquivalent(SyntaxNode left, SyntaxNode right)
{
return SyntaxFactory.AreEquivalent(left, right);
}
private static bool AreEquivalentIgnoringLambdaBodies(SyntaxNode left, SyntaxNode right)
{
// usual case:
if (SyntaxFactory.AreEquivalent(left, right))
{
return true;
}
return LambdaUtilities.AreEquivalentIgnoringLambdaBodies(left, right);
}
internal override SyntaxNode FindPartner(SyntaxNode leftRoot, SyntaxNode rightRoot, SyntaxNode leftNode)
{
return SyntaxUtilities.FindPartner(leftRoot, rightRoot, leftNode);
}
internal override SyntaxNode FindPartnerInMemberInitializer(SemanticModel leftModel, INamedTypeSymbol leftType, SyntaxNode leftNode, INamedTypeSymbol rightType, CancellationToken cancellationToken)
{
var leftEqualsClause = leftNode.FirstAncestorOrSelf(
node => node.Parent.IsKind(SyntaxKind.PropertyDeclaration) || node.Parent.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration));
if (leftEqualsClause == null)
{
return null;
}
SyntaxNode rightEqualsClause;
if (leftEqualsClause.Parent.IsKind(SyntaxKind.PropertyDeclaration))
{
var leftDeclaration = (PropertyDeclarationSyntax)leftEqualsClause.Parent;
var leftSymbol = leftModel.GetDeclaredSymbol(leftDeclaration, cancellationToken);
Debug.Assert(leftSymbol != null);
var rightProperty = rightType.GetMembers(leftSymbol.Name).Single();
var rightDeclaration = (PropertyDeclarationSyntax)GetSymbolSyntax(rightProperty, cancellationToken);
rightEqualsClause = rightDeclaration.Initializer;
}
else
{
var leftDeclarator = (VariableDeclaratorSyntax)leftEqualsClause.Parent;
var leftSymbol = leftModel.GetDeclaredSymbol(leftDeclarator, cancellationToken);
Debug.Assert(leftSymbol != null);
var rightField = rightType.GetMembers(leftSymbol.Name).Single();
var rightDeclarator = (VariableDeclaratorSyntax)GetSymbolSyntax(rightField, cancellationToken);
rightEqualsClause = rightDeclarator.Initializer;
}
if (rightEqualsClause == null)
{
return null;
}
return FindPartner(leftEqualsClause, rightEqualsClause, leftNode);
}
internal override bool IsClosureScope(SyntaxNode node)
{
return LambdaUtilities.IsClosureScope(node);
}
protected override SyntaxNode FindEnclosingLambdaBody(SyntaxNode containerOpt, SyntaxNode node)
{
SyntaxNode root = GetEncompassingAncestor(containerOpt);
while (node != root && node != null)
{
if (LambdaUtilities.IsLambdaBodyStatementOrExpression(node, out var body))
{
return body;
}
node = node.Parent;
}
return null;
}
protected override IEnumerable GetLambdaBodyExpressionsAndStatements(SyntaxNode lambdaBody)
{
return SpecializedCollections.SingletonEnumerable(lambdaBody);
}
protected override SyntaxNode TryGetPartnerLambdaBody(SyntaxNode oldBody, SyntaxNode newLambda)
{
return LambdaUtilities.TryGetCorrespondingLambdaBody(oldBody, newLambda);
}
protected override Match ComputeTopLevelMatch(SyntaxNode oldCompilationUnit, SyntaxNode newCompilationUnit)
{
return TopSyntaxComparer.Instance.ComputeMatch(oldCompilationUnit, newCompilationUnit);
}
protected override Match ComputeBodyMatch(SyntaxNode oldBody, SyntaxNode newBody, IEnumerable> knownMatches)
{
SyntaxUtilities.AssertIsBody(oldBody, allowLambda: true);
SyntaxUtilities.AssertIsBody(newBody, allowLambda: true);
if (oldBody is ExpressionSyntax || newBody is ExpressionSyntax)
{
Debug.Assert(oldBody is ExpressionSyntax || oldBody is BlockSyntax);
Debug.Assert(newBody is ExpressionSyntax || newBody is BlockSyntax);
// The matching algorithm requires the roots to match each other.
// Lambda bodies, field/property initializers, and method/property/indexer/operator expression-bodies may also be lambda expressions.
// Say we have oldBody 'x => x' and newBody 'F(x => x + 1)', then
// the algorithm would match 'x => x' to 'F(x => x + 1)' instead of
// matching 'x => x' to 'x => x + 1'.
// We use the parent node as a root:
// - for field/property initializers the root is EqualsValueClause.
// - for expression-bodies the root is ArrowExpressionClauseSyntax.
// - for block bodies the root is a method/operator/accessor declaration (only happens when matching expression body with a block body)
// - for lambdas the root is a LambdaExpression.
// - for query lambdas the root is the query clause containing the lambda (e.g. where).
return new StatementSyntaxComparer(oldBody, newBody).ComputeMatch(oldBody.Parent, newBody.Parent, knownMatches);
}
if (oldBody.Parent.IsKind(SyntaxKind.ConstructorDeclaration))
{
// We need to include constructor initializer in the match, since it may contain lambdas.
// Use the constructor declaration as a root.
Debug.Assert(oldBody.IsKind(SyntaxKind.Block));
Debug.Assert(newBody.IsKind(SyntaxKind.Block));
Debug.Assert(newBody.Parent.IsKind(SyntaxKind.ConstructorDeclaration));
return StatementSyntaxComparer.Default.ComputeMatch(oldBody.Parent, newBody.Parent, knownMatches);
}
return StatementSyntaxComparer.Default.ComputeMatch(oldBody, newBody, knownMatches);
}
protected override bool TryMatchActiveStatement(
SyntaxNode oldStatement,
int statementPart,
SyntaxNode oldBody,
SyntaxNode newBody,
out SyntaxNode newStatement)
{
SyntaxUtilities.AssertIsBody(oldBody, allowLambda: true);
SyntaxUtilities.AssertIsBody(newBody, allowLambda: true);
switch (oldStatement.Kind())
{
case SyntaxKind.ThisConstructorInitializer:
case SyntaxKind.BaseConstructorInitializer:
case SyntaxKind.ConstructorDeclaration:
var newConstructor = (ConstructorDeclarationSyntax)newBody.Parent;
newStatement = (SyntaxNode)newConstructor.Initializer ?? newConstructor;
return true;
default:
// TODO: Consider mapping an expression body to an equivalent statement expression or return statement and vice versa.
// It would benefit transformations of expression bodies to block bodies of lambdas, methods, operators and properties.
// field initializer, lambda and query expressions:
if (oldStatement == oldBody && !newBody.IsKind(SyntaxKind.Block))
{
newStatement = newBody;
return true;
}
newStatement = null;
return false;
}
}
#endregion
#region Syntax and Semantic Utils
protected override IEnumerable GetSyntaxSequenceEdits(ImmutableArray oldNodes, ImmutableArray newNodes)
{
return SyntaxComparer.GetSequenceEdits(oldNodes, newNodes);
}
internal override SyntaxNode EmptyCompilationUnit
{
get
{
return SyntaxFactory.CompilationUnit();
}
}
internal override bool ExperimentalFeaturesEnabled(SyntaxTree tree)
{
// there are no experimental features at this time.
return false;
}
protected override bool StatementLabelEquals(SyntaxNode node1, SyntaxNode node2)
{
return StatementSyntaxComparer.GetLabelImpl(node1) == StatementSyntaxComparer.GetLabelImpl(node2);
}
protected override bool TryGetEnclosingBreakpointSpan(SyntaxNode root, int position, out TextSpan span)
{
return BreakpointSpans.TryGetClosestBreakpointSpan(root, position, out span);
}
protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, out TextSpan span)
{
switch (node.Kind())
{
case SyntaxKind.Block:
span = GetActiveSpan((BlockSyntax)node, (BlockPart)statementPart);
return true;
case SyntaxKind.ForEachStatement:
span = GetActiveSpan((ForEachStatementSyntax)node, (ForEachPart)statementPart);
return true;
case SyntaxKind.ForEachVariableStatement:
span = GetActiveSpan((ForEachVariableStatementSyntax)node, (ForEachPart)statementPart);
return true;
case SyntaxKind.DoStatement:
// The active statement of DoStatement node is the while condition,
// which is lexically not the closest breakpoint span (the body is).
// do { ... } [|while (condition);|]
var doStatement = (DoStatementSyntax)node;
return BreakpointSpans.TryGetClosestBreakpointSpan(node, doStatement.WhileKeyword.SpanStart, out span);
case SyntaxKind.PropertyDeclaration:
// The active span corresponding to a property declaration is the span corresponding to its initializer (if any),
// not the span corresponding to the accessor.
// int P { [|get;|] } = [||];
var propertyDeclaration = (PropertyDeclarationSyntax)node;
if (propertyDeclaration.Initializer != null &&
BreakpointSpans.TryGetClosestBreakpointSpan(node, propertyDeclaration.Initializer.SpanStart, out span))
{
return true;
}
else
{
span = default(TextSpan);
return false;
}
default:
return BreakpointSpans.TryGetClosestBreakpointSpan(node, node.SpanStart, out span);
}
}
protected override IEnumerable> EnumerateNearStatements(SyntaxNode statement)
{
int direction = +1;
SyntaxNodeOrToken nodeOrToken = statement;
var fieldOrPropertyModifiers = SyntaxUtilities.TryGetFieldOrPropertyModifiers(statement);
while (true)
{
nodeOrToken = (direction < 0) ? nodeOrToken.GetPreviousSibling() : nodeOrToken.GetNextSibling();
if (nodeOrToken.RawKind == 0)
{
var parent = statement.Parent;
if (parent == null)
{
yield break;
}
if (parent.IsKind(SyntaxKind.Block))
{
yield return KeyValuePair.Create(parent, (int)(direction > 0 ? BlockPart.CloseBrace : BlockPart.OpenBrace));
}
else if (parent.IsKind(SyntaxKind.ForEachStatement))
{
yield return KeyValuePair.Create(parent, (int)ForEachPart.ForEach);
}
if (direction > 0)
{
nodeOrToken = statement;
direction = -1;
continue;
}
if (fieldOrPropertyModifiers.HasValue)
{
// We enumerated all members and none of them has an initializer.
// We don't have any better place where to place the span than the initial field.
// Consider: in non-partial classes we could find a single constructor.
// Otherwise, it would be confusing to select one arbitrarily.
yield return KeyValuePair.Create(statement, -1);
}
nodeOrToken = statement = parent;
fieldOrPropertyModifiers = SyntaxUtilities.TryGetFieldOrPropertyModifiers(statement);
direction = +1;
yield return KeyValuePair.Create(nodeOrToken.AsNode(), 0);
}
else
{
var node = nodeOrToken.AsNode();
if (node == null)
{
continue;
}
if (fieldOrPropertyModifiers.HasValue)
{
var nodeModifiers = SyntaxUtilities.TryGetFieldOrPropertyModifiers(node);
if (!nodeModifiers.HasValue ||
nodeModifiers.Value.Any(SyntaxKind.StaticKeyword) != fieldOrPropertyModifiers.Value.Any(SyntaxKind.StaticKeyword))
{
continue;
}
}
if (node.IsKind(SyntaxKind.Block))
{
yield return KeyValuePair.Create(node, (int)(direction > 0 ? BlockPart.OpenBrace : BlockPart.CloseBrace));
}
else if (node.IsKind(SyntaxKind.ForEachStatement))
{
yield return KeyValuePair.Create(node, (int)ForEachPart.ForEach);
}
yield return KeyValuePair.Create(node, 0);
}
}
}
protected override bool AreEquivalentActiveStatements(SyntaxNode oldStatement, SyntaxNode newStatement, int statementPart)
{
if (oldStatement.Kind() != newStatement.Kind())
{
return false;
}
switch (oldStatement.Kind())
{
case SyntaxKind.Block:
Debug.Assert(statementPart != 0);
return true;
case SyntaxKind.ConstructorDeclaration:
Debug.Assert(statementPart != 0);
// The call could only change if the base type of the containing class changed.
return true;
case SyntaxKind.ForEachStatement:
case SyntaxKind.ForEachVariableStatement:
Debug.Assert(statementPart != 0);
// only check the expression, edits in the body and the variable declaration are allowed:
return AreEquivalentActiveStatements((CommonForEachStatementSyntax)oldStatement, (CommonForEachStatementSyntax)newStatement);
case SyntaxKind.IfStatement:
// only check the condition, edits in the body are allowed:
return AreEquivalentActiveStatements((IfStatementSyntax)oldStatement, (IfStatementSyntax)newStatement);
case SyntaxKind.WhileStatement:
// only check the condition, edits in the body are allowed:
return AreEquivalentActiveStatements((WhileStatementSyntax)oldStatement, (WhileStatementSyntax)newStatement);
case SyntaxKind.DoStatement:
// only check the condition, edits in the body are allowed:
return AreEquivalentActiveStatements((DoStatementSyntax)oldStatement, (DoStatementSyntax)newStatement);
case SyntaxKind.SwitchStatement:
return AreEquivalentActiveStatements((SwitchStatementSyntax)oldStatement, (SwitchStatementSyntax)newStatement);
case SyntaxKind.LockStatement:
return AreEquivalentActiveStatements((LockStatementSyntax)oldStatement, (LockStatementSyntax)newStatement);
case SyntaxKind.UsingStatement:
return AreEquivalentActiveStatements((UsingStatementSyntax)oldStatement, (UsingStatementSyntax)newStatement);
// fixed and for statements don't need special handling since the active statement is a variable declaration
default:
return AreEquivalentIgnoringLambdaBodies(oldStatement, newStatement);
}
}
private static bool AreEquivalentActiveStatements(IfStatementSyntax oldNode, IfStatementSyntax newNode)
{
// only check the condition, edits in the body are allowed:
return AreEquivalentIgnoringLambdaBodies(oldNode.Condition, newNode.Condition);
}
private static bool AreEquivalentActiveStatements(WhileStatementSyntax oldNode, WhileStatementSyntax newNode)
{
// only check the condition, edits in the body are allowed:
return AreEquivalentIgnoringLambdaBodies(oldNode.Condition, newNode.Condition);
}
private static bool AreEquivalentActiveStatements(DoStatementSyntax oldNode, DoStatementSyntax newNode)
{
// only check the condition, edits in the body are allowed:
return AreEquivalentIgnoringLambdaBodies(oldNode.Condition, newNode.Condition);
}
private static bool AreEquivalentActiveStatements(SwitchStatementSyntax oldNode, SwitchStatementSyntax newNode)
{
// only check the expression, edits in the body are allowed:
return AreEquivalentIgnoringLambdaBodies(oldNode.Expression, newNode.Expression);
}
private static bool AreEquivalentActiveStatements(LockStatementSyntax oldNode, LockStatementSyntax newNode)
{
// only check the expression, edits in the body are allowed:
return AreEquivalentIgnoringLambdaBodies(oldNode.Expression, newNode.Expression);
}
private static bool AreEquivalentActiveStatements(FixedStatementSyntax oldNode, FixedStatementSyntax newNode)
{
return AreEquivalentIgnoringLambdaBodies(oldNode.Declaration, newNode.Declaration);
}
private static bool AreEquivalentActiveStatements(UsingStatementSyntax oldNode, UsingStatementSyntax newNode)
{
// only check the expression/declaration, edits in the body are allowed:
return AreEquivalentIgnoringLambdaBodies(
(SyntaxNode)oldNode.Declaration ?? oldNode.Expression,
(SyntaxNode)newNode.Declaration ?? newNode.Expression);
}
private static bool AreEquivalentActiveStatements(CommonForEachStatementSyntax oldNode, CommonForEachStatementSyntax newNode)
{
if (oldNode.Kind() != newNode.Kind() || !AreEquivalentIgnoringLambdaBodies(oldNode.Expression, newNode.Expression))
{
return false;
}
switch (oldNode.Kind())
{
case SyntaxKind.ForEachStatement: return AreEquivalentIgnoringLambdaBodies(((ForEachStatementSyntax)oldNode).Type, ((ForEachStatementSyntax)newNode).Type);
case SyntaxKind.ForEachVariableStatement: return AreEquivalentIgnoringLambdaBodies(((ForEachVariableStatementSyntax)oldNode).Variable, ((ForEachVariableStatementSyntax)newNode).Variable);
default: throw ExceptionUtilities.UnexpectedValue(oldNode.Kind());
}
}
private static bool AreSimilarActiveStatements(CommonForEachStatementSyntax oldNode, CommonForEachStatementSyntax newNode)
{
List oldTokens = null;
List newTokens = null;
StatementSyntaxComparer.GetLocalNames(oldNode, ref oldTokens);
StatementSyntaxComparer.GetLocalNames(newNode, ref newTokens);
return DeclareSameIdentifiers(oldTokens.ToArray(), newTokens.ToArray());
}
internal override bool IsMethod(SyntaxNode declaration)
{
return SyntaxUtilities.IsMethod(declaration);
}
internal override SyntaxNode TryGetContainingTypeDeclaration(SyntaxNode memberDeclaration)
{
return memberDeclaration.Parent.FirstAncestorOrSelf();
}
internal override bool HasBackingField(SyntaxNode propertyOrIndexerDeclaration)
{
return propertyOrIndexerDeclaration.IsKind(SyntaxKind.PropertyDeclaration) &&
SyntaxUtilities.HasBackingField((PropertyDeclarationSyntax)propertyOrIndexerDeclaration);
}
internal override bool IsDeclarationWithInitializer(SyntaxNode declaration)
{
switch (declaration.Kind())
{
case SyntaxKind.VariableDeclarator:
return ((VariableDeclaratorSyntax)declaration).Initializer != null;
case SyntaxKind.PropertyDeclaration:
return ((PropertyDeclarationSyntax)declaration).Initializer != null;
default:
return false;
}
}
internal override bool IsConstructorWithMemberInitializers(SyntaxNode constructorDeclaration)
{
var ctor = constructorDeclaration as ConstructorDeclarationSyntax;
return ctor != null && (ctor.Initializer == null || ctor.Initializer.IsKind(SyntaxKind.BaseConstructorInitializer));
}
internal override bool IsPartial(INamedTypeSymbol type)
{
var syntaxRefs = type.DeclaringSyntaxReferences;
return syntaxRefs.Length > 1
|| ((TypeDeclarationSyntax)syntaxRefs.Single().GetSyntax()).Modifiers.Any(SyntaxKind.PartialKeyword);
}
protected override ISymbol GetSymbolForEdit(SemanticModel model, SyntaxNode node, EditKind editKind, Dictionary editMap, CancellationToken cancellationToken)
{
if (node.IsKind(SyntaxKind.Parameter))
{
return null;
}
if (editKind == EditKind.Update)
{
if (node.IsKind(SyntaxKind.EnumDeclaration))
{
// Enum declaration update that removes/adds a trailing comma.
return null;
}
if (node.IsKind(SyntaxKind.IndexerDeclaration) || node.IsKind(SyntaxKind.PropertyDeclaration))
{
// The only legitimate update of an indexer/property declaration is an update of its expression body.
// The expression body itself may have been updated, replaced with an explicit getter, or added to replace an explicit getter.
// In any case, the update is to the property getter symbol.
var propertyOrIndexer = model.GetDeclaredSymbol(node, cancellationToken);
return ((IPropertySymbol)propertyOrIndexer).GetMethod;
}
}
if (IsGetterToExpressionBodyTransformation(editKind, node, editMap))
{
return null;
}
return model.GetDeclaredSymbol(node, cancellationToken);
}
protected override bool TryGetDeclarationBodyEdit(Edit edit, Dictionary editMap, out SyntaxNode oldBody, out SyntaxNode newBody)
{
// Detect a transition between a property/indexer with an expression body and with an explicit getter.
// int P => old_body; <-> int P { get { new_body } }
// int this[args] => old_body; <-> int this[args] { get { new_body } }
// First, return getter or expression body for property/indexer update:
if (edit.Kind == EditKind.Update && (edit.OldNode.IsKind(SyntaxKind.PropertyDeclaration) || edit.OldNode.IsKind(SyntaxKind.IndexerDeclaration)))
{
oldBody = SyntaxUtilities.TryGetEffectiveGetterBody(edit.OldNode);
newBody = SyntaxUtilities.TryGetEffectiveGetterBody(edit.NewNode);
if (oldBody != null && newBody != null)
{
return true;
}
}
// Second, ignore deletion of a getter body:
if (IsGetterToExpressionBodyTransformation(edit.Kind, edit.OldNode ?? edit.NewNode, editMap))
{
oldBody = newBody = null;
return false;
}
return base.TryGetDeclarationBodyEdit(edit, editMap, out oldBody, out newBody);
}
private static bool IsGetterToExpressionBodyTransformation(EditKind editKind, SyntaxNode node, Dictionary editMap)
{
if ((editKind == EditKind.Insert || editKind == EditKind.Delete) && node.IsKind(SyntaxKind.GetAccessorDeclaration))
{
Debug.Assert(node.Parent.IsKind(SyntaxKind.AccessorList));
Debug.Assert(node.Parent.Parent.IsKind(SyntaxKind.PropertyDeclaration) || node.Parent.Parent.IsKind(SyntaxKind.IndexerDeclaration));
return editMap.TryGetValue(node.Parent, out var parentEdit) && parentEdit == editKind &&
editMap.TryGetValue(node.Parent.Parent, out parentEdit) && parentEdit == EditKind.Update;
}
return false;
}
internal override bool ContainsLambda(SyntaxNode declaration)
{
return declaration.DescendantNodes().Any(LambdaUtilities.IsLambda);
}
internal override bool IsLambda(SyntaxNode node)
{
return LambdaUtilities.IsLambda(node);
}
internal override bool IsLambdaExpression(SyntaxNode node)
{
return node is LambdaExpressionSyntax;
}
internal override bool TryGetLambdaBodies(SyntaxNode node, out SyntaxNode body1, out SyntaxNode body2)
{
return LambdaUtilities.TryGetLambdaBodies(node, out body1, out body2);
}
internal override SyntaxNode GetLambda(SyntaxNode lambdaBody)
{
return LambdaUtilities.GetLambda(lambdaBody);
}
internal override IMethodSymbol GetLambdaExpressionSymbol(SemanticModel model, SyntaxNode lambdaExpression, CancellationToken cancellationToken)
{
return (IMethodSymbol)model.GetEnclosingSymbol(((AnonymousFunctionExpressionSyntax)lambdaExpression).Body.SpanStart, cancellationToken);
}
internal override SyntaxNode GetContainingQueryExpression(SyntaxNode node)
{
return node.FirstAncestorOrSelf();
}
internal override bool QueryClauseLambdasTypeEquivalent(SemanticModel oldModel, SyntaxNode oldNode, SemanticModel newModel, SyntaxNode newNode, CancellationToken cancellationToken)
{
switch (oldNode.Kind())
{
case SyntaxKind.FromClause:
case SyntaxKind.LetClause:
case SyntaxKind.WhereClause:
case SyntaxKind.OrderByClause:
case SyntaxKind.JoinClause:
var oldQueryClauseInfo = oldModel.GetQueryClauseInfo((QueryClauseSyntax)oldNode, cancellationToken);
var newQueryClauseInfo = newModel.GetQueryClauseInfo((QueryClauseSyntax)newNode, cancellationToken);
return MemberSignaturesEquivalent(oldQueryClauseInfo.CastInfo.Symbol, newQueryClauseInfo.CastInfo.Symbol) &&
MemberSignaturesEquivalent(oldQueryClauseInfo.OperationInfo.Symbol, newQueryClauseInfo.OperationInfo.Symbol);
case SyntaxKind.AscendingOrdering:
case SyntaxKind.DescendingOrdering:
var oldOrderingInfo = oldModel.GetSymbolInfo(oldNode, cancellationToken);
var newOrderingInfo = newModel.GetSymbolInfo(newNode, cancellationToken);
return MemberSignaturesEquivalent(oldOrderingInfo.Symbol, newOrderingInfo.Symbol);
case SyntaxKind.SelectClause:
var oldSelectInfo = oldModel.GetSymbolInfo(oldNode, cancellationToken);
var newSelectInfo = newModel.GetSymbolInfo(newNode, cancellationToken);
// Changing reduced select clause to a non-reduced form or vice versa
// adds/removes a call to Select method, which is a supported change.
return oldSelectInfo.Symbol == null ||
newSelectInfo.Symbol == null ||
MemberSignaturesEquivalent(oldSelectInfo.Symbol, newSelectInfo.Symbol);
case SyntaxKind.GroupClause:
var oldGroupByInfo = oldModel.GetSymbolInfo(oldNode, cancellationToken);
var newGroupByInfo = newModel.GetSymbolInfo(newNode, cancellationToken);
return MemberSignaturesEquivalent(oldGroupByInfo.Symbol, newGroupByInfo.Symbol, GroupBySignatureComparer);
default:
return true;
}
}
private static bool GroupBySignatureComparer(ImmutableArray oldParameters, ITypeSymbol oldReturnType, ImmutableArray newParameters, ITypeSymbol newReturnType)
{
// C# spec paragraph 7.16.2.6 "Groupby clauses":
//
// A query expression of the form
// from x in e group v by k
// is translated into
// (e).GroupBy(x => k, x => v)
// except when v is the identifier x, the translation is
// (e).GroupBy(x => k)
//
// Possible signatures:
// C> GroupBy(Func keySelector);
// C> GroupBy(Func keySelector, Func elementSelector);
if (!s_assemblyEqualityComparer.Equals(oldReturnType, newReturnType))
{
return false;
}
Debug.Assert(oldParameters.Length == 1 || oldParameters.Length == 2);
Debug.Assert(newParameters.Length == 1 || newParameters.Length == 2);
// The types of the lambdas have to be the same if present.
// The element selector may be added/removed.
if (!s_assemblyEqualityComparer.ParameterEquivalenceComparer.Equals(oldParameters[0], newParameters[0]))
{
return false;
}
if (oldParameters.Length == newParameters.Length && newParameters.Length == 2)
{
return s_assemblyEqualityComparer.ParameterEquivalenceComparer.Equals(oldParameters[1], newParameters[1]);
}
return true;
}
#endregion
#region Diagnostic Info
protected override SymbolDisplayFormat ErrorDisplayFormat => SymbolDisplayFormat.CSharpErrorMessageFormat;
protected override TextSpan GetDiagnosticSpan(SyntaxNode node, EditKind editKind)
{
return GetDiagnosticSpanImpl(node, editKind);
}
private static TextSpan GetDiagnosticSpanImpl(SyntaxNode node, EditKind editKind)
{
return GetDiagnosticSpanImpl(node.Kind(), node, editKind);
}
// internal for testing; kind is passed explicitly for testing as well
internal static TextSpan GetDiagnosticSpanImpl(SyntaxKind kind, SyntaxNode node, EditKind editKind)
{
switch (kind)
{
case SyntaxKind.CompilationUnit:
return default(TextSpan);
case SyntaxKind.GlobalStatement:
// TODO:
return default(TextSpan);
case SyntaxKind.ExternAliasDirective:
case SyntaxKind.UsingDirective:
return node.Span;
case SyntaxKind.NamespaceDeclaration:
var ns = (NamespaceDeclarationSyntax)node;
return TextSpan.FromBounds(ns.NamespaceKeyword.SpanStart, ns.Name.Span.End);
case SyntaxKind.ClassDeclaration:
case SyntaxKind.StructDeclaration:
case SyntaxKind.InterfaceDeclaration:
var typeDeclaration = (TypeDeclarationSyntax)node;
return GetDiagnosticSpan(typeDeclaration.Modifiers, typeDeclaration.Keyword,
typeDeclaration.TypeParameterList ?? (SyntaxNodeOrToken)typeDeclaration.Identifier);
case SyntaxKind.EnumDeclaration:
var enumDeclaration = (EnumDeclarationSyntax)node;
return GetDiagnosticSpan(enumDeclaration.Modifiers, enumDeclaration.EnumKeyword, enumDeclaration.Identifier);
case SyntaxKind.DelegateDeclaration:
var delegateDeclaration = (DelegateDeclarationSyntax)node;
return GetDiagnosticSpan(delegateDeclaration.Modifiers, delegateDeclaration.DelegateKeyword, delegateDeclaration.ParameterList);
case SyntaxKind.FieldDeclaration:
var fieldDeclaration = (BaseFieldDeclarationSyntax)node;
return GetDiagnosticSpan(fieldDeclaration.Modifiers, fieldDeclaration.Declaration, fieldDeclaration.Declaration);
case SyntaxKind.EventFieldDeclaration:
var eventFieldDeclaration = (EventFieldDeclarationSyntax)node;
return GetDiagnosticSpan(eventFieldDeclaration.Modifiers, eventFieldDeclaration.EventKeyword, eventFieldDeclaration.Declaration);
case SyntaxKind.VariableDeclaration:
return GetDiagnosticSpanImpl(node.Parent, editKind);
case SyntaxKind.VariableDeclarator:
return node.Span;
case SyntaxKind.MethodDeclaration:
var methodDeclaration = (MethodDeclarationSyntax)node;
return GetDiagnosticSpan(methodDeclaration.Modifiers, methodDeclaration.ReturnType, methodDeclaration.ParameterList);
case SyntaxKind.ConversionOperatorDeclaration:
var conversionOperatorDeclaration = (ConversionOperatorDeclarationSyntax)node;
return GetDiagnosticSpan(conversionOperatorDeclaration.Modifiers, conversionOperatorDeclaration.ImplicitOrExplicitKeyword, conversionOperatorDeclaration.ParameterList);
case SyntaxKind.OperatorDeclaration:
var operatorDeclaration = (OperatorDeclarationSyntax)node;
return GetDiagnosticSpan(operatorDeclaration.Modifiers, operatorDeclaration.ReturnType, operatorDeclaration.ParameterList);
case SyntaxKind.ConstructorDeclaration:
var constructorDeclaration = (ConstructorDeclarationSyntax)node;
return GetDiagnosticSpan(constructorDeclaration.Modifiers, constructorDeclaration.Identifier, constructorDeclaration.ParameterList);
case SyntaxKind.DestructorDeclaration:
var destructorDeclaration = (DestructorDeclarationSyntax)node;
return GetDiagnosticSpan(destructorDeclaration.Modifiers, destructorDeclaration.TildeToken, destructorDeclaration.ParameterList);
case SyntaxKind.PropertyDeclaration:
var propertyDeclaration = (PropertyDeclarationSyntax)node;
return GetDiagnosticSpan(propertyDeclaration.Modifiers, propertyDeclaration.Type, propertyDeclaration.Identifier);
case SyntaxKind.IndexerDeclaration:
var indexerDeclaration = (IndexerDeclarationSyntax)node;
return GetDiagnosticSpan(indexerDeclaration.Modifiers, indexerDeclaration.Type, indexerDeclaration.ParameterList);
case SyntaxKind.EventDeclaration:
var eventDeclaration = (EventDeclarationSyntax)node;
return GetDiagnosticSpan(eventDeclaration.Modifiers, eventDeclaration.EventKeyword, eventDeclaration.Identifier);
case SyntaxKind.EnumMemberDeclaration:
return node.Span;
case SyntaxKind.GetAccessorDeclaration:
case SyntaxKind.SetAccessorDeclaration:
case SyntaxKind.AddAccessorDeclaration:
case SyntaxKind.RemoveAccessorDeclaration:
case SyntaxKind.UnknownAccessorDeclaration:
var accessorDeclaration = (AccessorDeclarationSyntax)node;
return GetDiagnosticSpan(accessorDeclaration.Modifiers, accessorDeclaration.Keyword, accessorDeclaration.Keyword);
case SyntaxKind.TypeParameterConstraintClause:
var constraint = (TypeParameterConstraintClauseSyntax)node;
return TextSpan.FromBounds(constraint.WhereKeyword.SpanStart, constraint.Constraints.Last().Span.End);
case SyntaxKind.TypeParameter:
var typeParameter = (TypeParameterSyntax)node;
return typeParameter.Identifier.Span;
case SyntaxKind.AccessorList:
case SyntaxKind.TypeParameterList:
case SyntaxKind.ParameterList:
case SyntaxKind.BracketedParameterList:
if (editKind == EditKind.Delete)
{
return GetDiagnosticSpanImpl(node.Parent, editKind);
}
else
{
return node.Span;
}
case SyntaxKind.Parameter:
// We ignore anonymous methods and lambdas,
// we only care about parameters of member declarations.
var parameter = (ParameterSyntax)node;
return GetDiagnosticSpan(parameter.Modifiers, parameter.Type, parameter);
case SyntaxKind.AttributeList:
var attributeList = (AttributeListSyntax)node;
if (editKind == EditKind.Update)
{
return (attributeList.Target != null) ? attributeList.Target.Span : attributeList.Span;
}
else
{
return attributeList.Span;
}
case SyntaxKind.Attribute:
case SyntaxKind.ArrowExpressionClause:
return node.Span;
// We only need a diagnostic span if reporting an error for a child statement.
// The following statements may have child statements.
case SyntaxKind.Block:
return ((BlockSyntax)node).OpenBraceToken.Span;
case SyntaxKind.UsingStatement:
var usingStatement = (UsingStatementSyntax)node;
return TextSpan.FromBounds(usingStatement.UsingKeyword.SpanStart, usingStatement.CloseParenToken.Span.End);
case SyntaxKind.FixedStatement:
var fixedStatement = (FixedStatementSyntax)node;
return TextSpan.FromBounds(fixedStatement.FixedKeyword.SpanStart, fixedStatement.CloseParenToken.Span.End);
case SyntaxKind.LockStatement:
var lockStatement = (LockStatementSyntax)node;
return TextSpan.FromBounds(lockStatement.LockKeyword.SpanStart, lockStatement.CloseParenToken.Span.End);
case SyntaxKind.StackAllocArrayCreationExpression:
return ((StackAllocArrayCreationExpressionSyntax)node).StackAllocKeyword.Span;
case SyntaxKind.TryStatement:
return ((TryStatementSyntax)node).TryKeyword.Span;
case SyntaxKind.CatchClause:
return ((CatchClauseSyntax)node).CatchKeyword.Span;
case SyntaxKind.CatchDeclaration:
case SyntaxKind.CatchFilterClause:
return node.Span;
case SyntaxKind.FinallyClause:
return ((FinallyClauseSyntax)node).FinallyKeyword.Span;
case SyntaxKind.IfStatement:
var ifStatement = (IfStatementSyntax)node;
return TextSpan.FromBounds(ifStatement.IfKeyword.SpanStart, ifStatement.CloseParenToken.Span.End);
case SyntaxKind.ElseClause:
return ((ElseClauseSyntax)node).ElseKeyword.Span;
case SyntaxKind.SwitchStatement:
var switchStatement = (SwitchStatementSyntax)node;
return TextSpan.FromBounds(switchStatement.SwitchKeyword.SpanStart, switchStatement.CloseParenToken.Span.End);
case SyntaxKind.SwitchSection:
return ((SwitchSectionSyntax)node).Labels.Last().Span;
case SyntaxKind.WhileStatement:
var whileStatement = (WhileStatementSyntax)node;
return TextSpan.FromBounds(whileStatement.WhileKeyword.SpanStart, whileStatement.CloseParenToken.Span.End);
case SyntaxKind.DoStatement:
return ((DoStatementSyntax)node).DoKeyword.Span;
case SyntaxKind.ForStatement:
var forStatement = (ForStatementSyntax)node;
return TextSpan.FromBounds(forStatement.ForKeyword.SpanStart, forStatement.CloseParenToken.Span.End);
case SyntaxKind.ForEachStatement:
case SyntaxKind.ForEachVariableStatement:
var commonForEachStatement = (CommonForEachStatementSyntax)node;
return TextSpan.FromBounds(commonForEachStatement.ForEachKeyword.SpanStart, commonForEachStatement.CloseParenToken.Span.End);
case SyntaxKind.LabeledStatement:
return ((LabeledStatementSyntax)node).Identifier.Span;
case SyntaxKind.CheckedStatement:
case SyntaxKind.UncheckedStatement:
return ((CheckedStatementSyntax)node).Keyword.Span;
case SyntaxKind.UnsafeStatement:
return ((UnsafeStatementSyntax)node).UnsafeKeyword.Span;
case SyntaxKind.LocalFunctionStatement:
var lfd = (LocalFunctionStatementSyntax)node;
return lfd.Identifier.Span;
case SyntaxKind.YieldBreakStatement:
case SyntaxKind.YieldReturnStatement:
case SyntaxKind.ReturnStatement:
case SyntaxKind.ThrowStatement:
case SyntaxKind.ExpressionStatement:
case SyntaxKind.LocalDeclarationStatement:
case SyntaxKind.GotoStatement:
case SyntaxKind.GotoCaseStatement:
case SyntaxKind.GotoDefaultStatement:
case SyntaxKind.BreakStatement:
case SyntaxKind.ContinueStatement:
return node.Span;
case SyntaxKind.AwaitExpression:
return ((AwaitExpressionSyntax)node).AwaitKeyword.Span;
case SyntaxKind.AnonymousObjectCreationExpression:
return ((AnonymousObjectCreationExpressionSyntax)node).NewKeyword.Span;
case SyntaxKind.ParenthesizedLambdaExpression:
return ((ParenthesizedLambdaExpressionSyntax)node).ParameterList.Span;
case SyntaxKind.SimpleLambdaExpression:
return ((SimpleLambdaExpressionSyntax)node).Parameter.Span;
case SyntaxKind.AnonymousMethodExpression:
return ((AnonymousMethodExpressionSyntax)node).DelegateKeyword.Span;
case SyntaxKind.QueryExpression:
return ((QueryExpressionSyntax)node).FromClause.FromKeyword.Span;
case SyntaxKind.QueryBody:
var queryBody = (QueryBodySyntax)node;
return GetDiagnosticSpanImpl(queryBody.Clauses.FirstOrDefault() ?? queryBody.Parent, editKind);
case SyntaxKind.QueryContinuation:
return ((QueryContinuationSyntax)node).IntoKeyword.Span;
case SyntaxKind.FromClause:
return ((FromClauseSyntax)node).FromKeyword.Span;
case SyntaxKind.JoinClause:
return ((JoinClauseSyntax)node).JoinKeyword.Span;
case SyntaxKind.JoinIntoClause:
return ((JoinIntoClauseSyntax)node).IntoKeyword.Span;
case SyntaxKind.LetClause:
return ((LetClauseSyntax)node).LetKeyword.Span;
case SyntaxKind.WhereClause:
return ((WhereClauseSyntax)node).WhereKeyword.Span;
case SyntaxKind.OrderByClause:
return ((OrderByClauseSyntax)node).OrderByKeyword.Span;
case SyntaxKind.AscendingOrdering:
case SyntaxKind.DescendingOrdering:
return node.Span;
case SyntaxKind.SelectClause:
return ((SelectClauseSyntax)node).SelectKeyword.Span;
case SyntaxKind.GroupClause:
return ((GroupClauseSyntax)node).GroupKeyword.Span;
case SyntaxKind.IsPatternExpression:
case SyntaxKind.TupleType:
case SyntaxKind.TupleExpression:
case SyntaxKind.DeclarationExpression:
case SyntaxKind.RefType:
case SyntaxKind.RefExpression:
case SyntaxKind.DeclarationPattern:
case SyntaxKind.SimpleAssignmentExpression:
case SyntaxKind.WhenClause:
case SyntaxKind.SingleVariableDesignation:
case SyntaxKind.CasePatternSwitchLabel:
return node.Span;
default:
throw ExceptionUtilities.UnexpectedValue(kind);
}
}
private static TextSpan GetDiagnosticSpan(SyntaxTokenList modifiers, SyntaxNodeOrToken start, SyntaxNodeOrToken end)
{
return TextSpan.FromBounds((modifiers.Count != 0) ? modifiers.First().SpanStart : start.SpanStart, end.Span.End);
}
internal override TextSpan GetLambdaParameterDiagnosticSpan(SyntaxNode lambda, int ordinal)
{
switch (lambda.Kind())
{
case SyntaxKind.ParenthesizedLambdaExpression:
return ((ParenthesizedLambdaExpressionSyntax)lambda).ParameterList.Parameters[ordinal].Identifier.Span;
case SyntaxKind.SimpleLambdaExpression:
Debug.Assert(ordinal == 0);
return ((SimpleLambdaExpressionSyntax)lambda).Parameter.Identifier.Span;
case SyntaxKind.AnonymousMethodExpression:
return ((AnonymousMethodExpressionSyntax)lambda).ParameterList.Parameters[ordinal].Identifier.Span;
default:
return lambda.Span;
}
}
protected override string GetTopLevelDisplayName(SyntaxNode node, EditKind editKind)
{
return GetTopLevelDisplayNameImpl(node, editKind);
}
protected override string GetStatementDisplayName(SyntaxNode node, EditKind editKind)
{
return GetStatementDisplayNameImpl(node);
}
protected override string GetLambdaDisplayName(SyntaxNode lambda)
{
return GetStatementDisplayNameImpl(lambda);
}
// internal for testing
internal static string GetTopLevelDisplayNameImpl(SyntaxNode node, EditKind editKind)
{
switch (node.Kind())
{
case SyntaxKind.GlobalStatement:
return CSharpFeaturesResources.global_statement;
case SyntaxKind.ExternAliasDirective:
return CSharpFeaturesResources.using_namespace;
case SyntaxKind.UsingDirective:
// Dev12 distinguishes using alias from using namespace and reports different errors for removing alias.
// None of these changes are allowed anyways, so let's keep it simple.
return CSharpFeaturesResources.using_directive;
case SyntaxKind.NamespaceDeclaration:
return FeaturesResources.namespace_;
case SyntaxKind.ClassDeclaration:
return FeaturesResources.class_;
case SyntaxKind.StructDeclaration:
return CSharpFeaturesResources.struct_;
case SyntaxKind.InterfaceDeclaration:
return FeaturesResources.interface_;
case SyntaxKind.EnumDeclaration:
return FeaturesResources.enum_;
case SyntaxKind.DelegateDeclaration:
return FeaturesResources.delegate_;
case SyntaxKind.FieldDeclaration:
var declaration = (FieldDeclarationSyntax)node;
return declaration.Modifiers.Any(SyntaxKind.ConstKeyword) ? FeaturesResources.const_field : FeaturesResources.field;
case SyntaxKind.EventFieldDeclaration:
return CSharpFeaturesResources.event_field;
case SyntaxKind.VariableDeclaration:
case SyntaxKind.VariableDeclarator:
return GetTopLevelDisplayNameImpl(node.Parent, editKind);
case SyntaxKind.MethodDeclaration:
return FeaturesResources.method;
case SyntaxKind.ConversionOperatorDeclaration:
return CSharpFeaturesResources.conversion_operator;
case SyntaxKind.OperatorDeclaration:
return FeaturesResources.operator_;
case SyntaxKind.ConstructorDeclaration:
return FeaturesResources.constructor;
case SyntaxKind.DestructorDeclaration:
return CSharpFeaturesResources.destructor;
case SyntaxKind.PropertyDeclaration:
return SyntaxUtilities.HasBackingField((PropertyDeclarationSyntax)node) ? FeaturesResources.auto_property : FeaturesResources.property_;
case SyntaxKind.IndexerDeclaration:
return CSharpFeaturesResources.indexer;
case SyntaxKind.EventDeclaration:
return FeaturesResources.event_;
case SyntaxKind.EnumMemberDeclaration:
return FeaturesResources.enum_value;
case SyntaxKind.GetAccessorDeclaration:
if (node.Parent.Parent.IsKind(SyntaxKind.PropertyDeclaration))
{
return CSharpFeaturesResources.property_getter;
}
else
{
Debug.Assert(node.Parent.Parent.IsKind(SyntaxKind.IndexerDeclaration));
return CSharpFeaturesResources.indexer_getter;
}
case SyntaxKind.SetAccessorDeclaration:
if (node.Parent.Parent.IsKind(SyntaxKind.PropertyDeclaration))
{
return CSharpFeaturesResources.property_setter;
}
else
{
Debug.Assert(node.Parent.Parent.IsKind(SyntaxKind.IndexerDeclaration));
return CSharpFeaturesResources.indexer_setter;
}
case SyntaxKind.AddAccessorDeclaration:
case SyntaxKind.RemoveAccessorDeclaration:
return FeaturesResources.event_accessor;
case SyntaxKind.TypeParameterConstraintClause:
return FeaturesResources.type_constraint;
case SyntaxKind.TypeParameterList:
case SyntaxKind.TypeParameter:
return FeaturesResources.type_parameter;
case SyntaxKind.Parameter:
return FeaturesResources.parameter;
case SyntaxKind.AttributeList:
return (editKind == EditKind.Update) ? CSharpFeaturesResources.attribute_target : FeaturesResources.attribute;
case SyntaxKind.Attribute:
return FeaturesResources.attribute;
default:
throw ExceptionUtilities.UnexpectedValue(node.Kind());
}
}
// internal for testing
internal static string GetStatementDisplayNameImpl(SyntaxNode node)
{
switch (node.Kind())
{
case SyntaxKind.TryStatement:
return CSharpFeaturesResources.try_block;
case SyntaxKind.CatchClause:
case SyntaxKind.CatchDeclaration:
return CSharpFeaturesResources.catch_clause;
case SyntaxKind.CatchFilterClause:
return CSharpFeaturesResources.filter_clause;
case SyntaxKind.FinallyClause:
return CSharpFeaturesResources.finally_clause;
case SyntaxKind.FixedStatement:
return CSharpFeaturesResources.fixed_statement;
case SyntaxKind.UsingStatement:
return CSharpFeaturesResources.using_statement;
case SyntaxKind.LockStatement:
return CSharpFeaturesResources.lock_statement;
case SyntaxKind.ForEachStatement:
case SyntaxKind.ForEachVariableStatement:
return CSharpFeaturesResources.foreach_statement;
case SyntaxKind.CheckedStatement:
return CSharpFeaturesResources.checked_statement;
case SyntaxKind.UncheckedStatement:
return CSharpFeaturesResources.unchecked_statement;
case SyntaxKind.YieldBreakStatement:
case SyntaxKind.YieldReturnStatement:
return CSharpFeaturesResources.yield_statement;
case SyntaxKind.AwaitExpression:
return CSharpFeaturesResources.await_expression;
case SyntaxKind.ParenthesizedLambdaExpression:
case SyntaxKind.SimpleLambdaExpression:
return CSharpFeaturesResources.lambda;
case SyntaxKind.AnonymousMethodExpression:
return CSharpFeaturesResources.anonymous_method;
case SyntaxKind.FromClause:
return CSharpFeaturesResources.from_clause;
case SyntaxKind.JoinClause:
case SyntaxKind.JoinIntoClause:
return CSharpFeaturesResources.join_clause;
case SyntaxKind.LetClause:
return CSharpFeaturesResources.let_clause;
case SyntaxKind.WhereClause:
return CSharpFeaturesResources.where_clause;
case SyntaxKind.OrderByClause:
case SyntaxKind.AscendingOrdering:
case SyntaxKind.DescendingOrdering:
return CSharpFeaturesResources.orderby_clause;
case SyntaxKind.SelectClause:
return CSharpFeaturesResources.select_clause;
case SyntaxKind.GroupClause:
return CSharpFeaturesResources.groupby_clause;
case SyntaxKind.QueryBody:
return CSharpFeaturesResources.query_body;
case SyntaxKind.QueryContinuation:
return CSharpFeaturesResources.into_clause;
case SyntaxKind.IsPatternExpression:
return CSharpFeaturesResources.is_pattern;
case SyntaxKind.SimpleAssignmentExpression:
if (((AssignmentExpressionSyntax)node).IsDeconstruction())
{
return CSharpFeaturesResources.deconstruction;
}
else
{
throw ExceptionUtilities.UnexpectedValue(node.Kind());
}
case SyntaxKind.TupleType:
case SyntaxKind.TupleExpression:
return CSharpFeaturesResources.tuple;
case SyntaxKind.LocalFunctionStatement:
return CSharpFeaturesResources.local_function;
case SyntaxKind.DeclarationExpression:
return CSharpFeaturesResources.out_var;
case SyntaxKind.RefType:
case SyntaxKind.RefExpression:
return CSharpFeaturesResources.ref_local_or_expression;
case SyntaxKind.SwitchStatement:
case SyntaxKind.DeclarationPattern:
return CSharpFeaturesResources.v7_switch;
default:
throw ExceptionUtilities.UnexpectedValue(node.Kind());
}
}
#endregion
#region Top-Level Syntactic Rude Edits
private struct EditClassifier
{
private readonly CSharpEditAndContinueAnalyzer _analyzer;
private readonly List _diagnostics;
private readonly Match _match;
private readonly SyntaxNode _oldNode;
private readonly SyntaxNode _newNode;
private readonly EditKind _kind;
private readonly TextSpan? _span;
public EditClassifier(
CSharpEditAndContinueAnalyzer analyzer,
List diagnostics,
SyntaxNode oldNode,
SyntaxNode newNode,
EditKind kind,
Match match = null,
TextSpan? span = null)
{
_analyzer = analyzer;
_diagnostics = diagnostics;
_oldNode = oldNode;
_newNode = newNode;
_kind = kind;
_span = span;
_match = match;
}
private void ReportError(RudeEditKind kind, SyntaxNode spanNode = null, SyntaxNode displayNode = null)
{
var span = (spanNode != null) ? GetDiagnosticSpanImpl(spanNode, _kind) : GetSpan();
var node = displayNode ?? _newNode ?? _oldNode;
var displayName = (displayNode != null) ? GetTopLevelDisplayNameImpl(displayNode, _kind) : GetDisplayName();
_diagnostics.Add(new RudeEditDiagnostic(kind, span, node, arguments: new[] { displayName }));
}
private string GetDisplayName()
{
return GetTopLevelDisplayNameImpl(_newNode ?? _oldNode, _kind);
}
private TextSpan GetSpan()
{
if (_span.HasValue)
{
return _span.Value;
}
if (_newNode == null)
{
return _analyzer.GetDeletedNodeDiagnosticSpan(_match.Matches, _oldNode);
}
else
{
return GetDiagnosticSpanImpl(_newNode, _kind);
}
}
public void ClassifyEdit()
{
switch (_kind)
{
case EditKind.Delete:
ClassifyDelete(_oldNode);
return;
case EditKind.Update:
ClassifyUpdate(_oldNode, _newNode);
return;
case EditKind.Move:
ClassifyMove(_oldNode, _newNode);
return;
case EditKind.Insert:
ClassifyInsert(_newNode);
return;
case EditKind.Reorder:
ClassifyReorder(_oldNode, _newNode);
return;
default:
throw ExceptionUtilities.UnexpectedValue(_kind);
}
}
#region Move and Reorder
private void ClassifyMove(SyntaxNode oldNode, SyntaxNode newNode)
{
// We could perhaps allow moving a type declaration to a different namespace syntax node
// as long as it represents semantically the same namespace as the one of the original type declaration.
ReportError(RudeEditKind.Move);
}
private void ClassifyReorder(SyntaxNode oldNode, SyntaxNode newNode)
{
switch (newNode.Kind())
{
case SyntaxKind.GlobalStatement:
// TODO:
ReportError(RudeEditKind.Move);
return;
case SyntaxKind.ExternAliasDirective:
case SyntaxKind.UsingDirective:
case SyntaxKind.NamespaceDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.StructDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.DelegateDeclaration:
case SyntaxKind.VariableDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.ConversionOperatorDeclaration:
case SyntaxKind.OperatorDeclaration:
case SyntaxKind.ConstructorDeclaration:
case SyntaxKind.DestructorDeclaration:
case SyntaxKind.IndexerDeclaration:
case SyntaxKind.EventDeclaration:
case SyntaxKind.GetAccessorDeclaration:
case SyntaxKind.SetAccessorDeclaration:
case SyntaxKind.AddAccessorDeclaration:
case SyntaxKind.RemoveAccessorDeclaration:
case SyntaxKind.TypeParameterConstraintClause:
case SyntaxKind.AttributeList:
case SyntaxKind.Attribute:
// We'll ignore these edits. A general policy is to ignore edits that are only discoverable via reflection.
return;
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.FieldDeclaration:
case SyntaxKind.EventFieldDeclaration:
case SyntaxKind.VariableDeclarator:
// Maybe we could allow changing order of field declarations unless the containing type layout is sequential.
ReportError(RudeEditKind.Move);
return;
case SyntaxKind.EnumMemberDeclaration:
// To allow this change we would need to check that values of all fields of the enum
// are preserved, or make sure we can update all method bodies that accessed those that changed.
ReportError(RudeEditKind.Move);
return;
case SyntaxKind.TypeParameter:
case SyntaxKind.Parameter:
ReportError(RudeEditKind.Move);
return;
default:
throw ExceptionUtilities.UnexpectedValue(newNode.Kind());
}
}
#endregion
#region Insert
private void ClassifyInsert(SyntaxNode node)
{
switch (node.Kind())
{
case SyntaxKind.GlobalStatement:
// TODO:
ReportError(RudeEditKind.Insert);
return;
case SyntaxKind.ExternAliasDirective:
case SyntaxKind.UsingDirective:
case SyntaxKind.NamespaceDeclaration:
case SyntaxKind.DestructorDeclaration:
ReportError(RudeEditKind.Insert);
return;
case SyntaxKind.ClassDeclaration:
case SyntaxKind.StructDeclaration:
ClassifyTypeWithPossibleExternMembersInsert((TypeDeclarationSyntax)node);
return;
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.DelegateDeclaration:
return;
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.IndexerDeclaration:
case SyntaxKind.EventDeclaration:
ClassifyModifiedMemberInsert(((BasePropertyDeclarationSyntax)node).Modifiers);
return;
case SyntaxKind.ConversionOperatorDeclaration:
case SyntaxKind.OperatorDeclaration:
ReportError(RudeEditKind.InsertOperator);
return;
case SyntaxKind.MethodDeclaration:
ClassifyMethodInsert((MethodDeclarationSyntax)node);
return;
case SyntaxKind.ConstructorDeclaration:
// Allow adding parameterless constructor.
// Semantic analysis will determine if it's an actual addition or
// just an update of an existing implicit constructor.
var modifiers = ((BaseMethodDeclarationSyntax)node).Modifiers;
if (SyntaxUtilities.IsParameterlessConstructor(node))
{
// Disallow adding an extern constructor
if (modifiers.Any(SyntaxKind.ExternKeyword))
{
ReportError(RudeEditKind.InsertExtern);
}
return;
}
ClassifyModifiedMemberInsert(modifiers);
return;
case SyntaxKind.GetAccessorDeclaration:
case SyntaxKind.SetAccessorDeclaration:
case SyntaxKind.AddAccessorDeclaration:
case SyntaxKind.RemoveAccessorDeclaration:
ClassifyAccessorInsert((AccessorDeclarationSyntax)node);
return;
case SyntaxKind.AccessorList:
// an error will be reported for each accessor
return;
case SyntaxKind.FieldDeclaration:
case SyntaxKind.EventFieldDeclaration:
// allowed: private fields in classes
ClassifyFieldInsert((BaseFieldDeclarationSyntax)node);
return;
case SyntaxKind.VariableDeclarator:
// allowed: private fields in classes
ClassifyFieldInsert((VariableDeclaratorSyntax)node);
return;
case SyntaxKind.VariableDeclaration:
// allowed: private fields in classes
ClassifyFieldInsert((VariableDeclarationSyntax)node);
return;
case SyntaxKind.EnumMemberDeclaration:
case SyntaxKind.TypeParameter:
case SyntaxKind.TypeParameterConstraintClause:
case SyntaxKind.TypeParameterList:
case SyntaxKind.Parameter:
case SyntaxKind.Attribute:
case SyntaxKind.AttributeList:
ReportError(RudeEditKind.Insert);
return;
default:
throw ExceptionUtilities.UnexpectedValue(node.Kind());
}
}
private bool ClassifyModifiedMemberInsert(SyntaxTokenList modifiers)
{
if (modifiers.Any(SyntaxKind.ExternKeyword))
{
ReportError(RudeEditKind.InsertExtern);
return false;
}
if (modifiers.Any(SyntaxKind.VirtualKeyword) || modifiers.Any(SyntaxKind.AbstractKeyword) || modifiers.Any(SyntaxKind.OverrideKeyword))
{
ReportError(RudeEditKind.InsertVirtual);
return false;
}
return true;
}
private void ClassifyTypeWithPossibleExternMembersInsert(TypeDeclarationSyntax type)
{
// extern members are not allowed, even in a new type
foreach (var member in type.Members)
{
var modifiers = default(SyntaxTokenList);
switch (member.Kind())
{
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.IndexerDeclaration:
case SyntaxKind.EventDeclaration:
modifiers = ((BasePropertyDeclarationSyntax)member).Modifiers;
break;
case SyntaxKind.ConversionOperatorDeclaration:
case SyntaxKind.OperatorDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.ConstructorDeclaration:
modifiers = ((BaseMethodDeclarationSyntax)member).Modifiers;
break;
}
if (modifiers.Any(SyntaxKind.ExternKeyword))
{
ReportError(RudeEditKind.InsertExtern, member, member);
}
}
}
private void ClassifyMethodInsert(MethodDeclarationSyntax method)
{
ClassifyModifiedMemberInsert(method.Modifiers);
if (method.Arity > 0)
{
ReportError(RudeEditKind.InsertGenericMethod);
}
}
private void ClassifyAccessorInsert(AccessorDeclarationSyntax accessor)
{
var baseProperty = (BasePropertyDeclarationSyntax)accessor.Parent.Parent;
ClassifyModifiedMemberInsert(baseProperty.Modifiers);
}
private void ClassifyFieldInsert(BaseFieldDeclarationSyntax field)
{
ClassifyModifiedMemberInsert(field.Modifiers);
}
private void ClassifyFieldInsert(VariableDeclaratorSyntax fieldVariable)
{
ClassifyFieldInsert((VariableDeclarationSyntax)fieldVariable.Parent);
}
private void ClassifyFieldInsert(VariableDeclarationSyntax fieldVariable)
{
ClassifyFieldInsert((BaseFieldDeclarationSyntax)fieldVariable.Parent);
}
#endregion
#region Delete
private void ClassifyDelete(SyntaxNode oldNode)
{
switch (oldNode.Kind())
{
case SyntaxKind.GlobalStatement:
// TODO:
ReportError(RudeEditKind.Delete);
return;
case SyntaxKind.ExternAliasDirective:
case SyntaxKind.UsingDirective:
case SyntaxKind.NamespaceDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.StructDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.DelegateDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.ConversionOperatorDeclaration:
case SyntaxKind.OperatorDeclaration:
case SyntaxKind.DestructorDeclaration:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.IndexerDeclaration:
case SyntaxKind.EventDeclaration:
case SyntaxKind.FieldDeclaration:
case SyntaxKind.EventFieldDeclaration:
case SyntaxKind.VariableDeclarator:
case SyntaxKind.VariableDeclaration:
// To allow removal of declarations we would need to update method bodies that
// were previously binding to them but now are binding to another symbol that was previously hidden.
ReportError(RudeEditKind.Delete);
return;
case SyntaxKind.ConstructorDeclaration:
// Allow deletion of a parameterless constructor.
// Semantic analysis reports an error if the parameterless ctor isn't replaced by a default ctor.
if (!SyntaxUtilities.IsParameterlessConstructor(oldNode))
{
ReportError(RudeEditKind.Delete);
}
return;
case SyntaxKind.GetAccessorDeclaration:
case SyntaxKind.SetAccessorDeclaration:
case SyntaxKind.AddAccessorDeclaration:
case SyntaxKind.RemoveAccessorDeclaration:
// An accessor can be removed. Accessors are not hiding other symbols.
// If the new compilation still uses the removed accessor a semantic error will be reported.
// For simplicity though we disallow deletion of accessors for now.
// The compiler would need to remember that the accessor has been deleted,
// so that its addition back is interpreted as an update.
// Additional issues might involve changing accessibility of the accessor.
ReportError(RudeEditKind.Delete);
return;
case SyntaxKind.AccessorList:
Debug.Assert(
oldNode.Parent.IsKind(SyntaxKind.PropertyDeclaration) ||
oldNode.Parent.IsKind(SyntaxKind.IndexerDeclaration));
var accessorList = (AccessorListSyntax)oldNode;
var setter = accessorList.Accessors.FirstOrDefault(a => a.IsKind(SyntaxKind.SetAccessorDeclaration));
if (setter != null)
{
ReportError(RudeEditKind.Delete, accessorList.Parent, setter);
}
return;
case SyntaxKind.AttributeList:
case SyntaxKind.Attribute:
// To allow removal of attributes we would need to check if the removed attribute
// is a pseudo-custom attribute that CLR allows us to change, or if it is a compiler well-know attribute
// that affects the generated IL.
ReportError(RudeEditKind.Delete);
return;
case SyntaxKind.EnumMemberDeclaration:
// We could allow removing enum member if it didn't affect the values of other enum members.
// If the updated compilation binds without errors it means that the enum value wasn't used.
ReportError(RudeEditKind.Delete);
return;
case SyntaxKind.TypeParameter:
case SyntaxKind.TypeParameterList:
case SyntaxKind.Parameter:
case SyntaxKind.ParameterList:
case SyntaxKind.TypeParameterConstraintClause:
ReportError(RudeEditKind.Delete);
return;
default:
throw ExceptionUtilities.UnexpectedValue(oldNode.Kind());
}
}
#endregion
#region Update
private void ClassifyUpdate(SyntaxNode oldNode, SyntaxNode newNode)
{
switch (newNode.Kind())
{
case SyntaxKind.GlobalStatement:
ReportError(RudeEditKind.Update);
return;
case SyntaxKind.ExternAliasDirective:
ReportError(RudeEditKind.Update);
return;
case SyntaxKind.UsingDirective:
// Dev12 distinguishes using alias from using namespace and reports different errors for removing alias.
// None of these changes are allowed anyways, so let's keep it simple.
ReportError(RudeEditKind.Update);
return;
case SyntaxKind.NamespaceDeclaration:
ClassifyUpdate((NamespaceDeclarationSyntax)oldNode, (NamespaceDeclarationSyntax)newNode);
return;
case SyntaxKind.ClassDeclaration:
case SyntaxKind.StructDeclaration:
case SyntaxKind.InterfaceDeclaration:
ClassifyUpdate((TypeDeclarationSyntax)oldNode, (TypeDeclarationSyntax)newNode);
return;
case SyntaxKind.EnumDeclaration:
ClassifyUpdate((EnumDeclarationSyntax)oldNode, (EnumDeclarationSyntax)newNode);
return;
case SyntaxKind.DelegateDeclaration:
ClassifyUpdate((DelegateDeclarationSyntax)oldNode, (DelegateDeclarationSyntax)newNode);
return;
case SyntaxKind.FieldDeclaration:
ClassifyUpdate((BaseFieldDeclarationSyntax)oldNode, (BaseFieldDeclarationSyntax)newNode);
return;
case SyntaxKind.EventFieldDeclaration:
ClassifyUpdate((BaseFieldDeclarationSyntax)oldNode, (BaseFieldDeclarationSyntax)newNode);
return;
case SyntaxKind.VariableDeclaration:
ClassifyUpdate((VariableDeclarationSyntax)oldNode, (VariableDeclarationSyntax)newNode);
return;
case SyntaxKind.VariableDeclarator:
ClassifyUpdate((VariableDeclaratorSyntax)oldNode, (VariableDeclaratorSyntax)newNode);
return;
case SyntaxKind.MethodDeclaration:
ClassifyUpdate((MethodDeclarationSyntax)oldNode, (MethodDeclarationSyntax)newNode);
return;
case SyntaxKind.ConversionOperatorDeclaration:
ClassifyUpdate((ConversionOperatorDeclarationSyntax)oldNode, (ConversionOperatorDeclarationSyntax)newNode);
return;
case SyntaxKind.OperatorDeclaration:
ClassifyUpdate((OperatorDeclarationSyntax)oldNode, (OperatorDeclarationSyntax)newNode);
return;
case SyntaxKind.ConstructorDeclaration:
ClassifyUpdate((ConstructorDeclarationSyntax)oldNode, (ConstructorDeclarationSyntax)newNode);
return;
case SyntaxKind.DestructorDeclaration:
ClassifyUpdate((DestructorDeclarationSyntax)oldNode, (DestructorDeclarationSyntax)newNode);
return;
case SyntaxKind.PropertyDeclaration:
ClassifyUpdate((PropertyDeclarationSyntax)oldNode, (PropertyDeclarationSyntax)newNode);
return;
case SyntaxKind.IndexerDeclaration:
ClassifyUpdate((IndexerDeclarationSyntax)oldNode, (IndexerDeclarationSyntax)newNode);
return;
case SyntaxKind.EventDeclaration:
return;
case SyntaxKind.EnumMemberDeclaration:
ClassifyUpdate((EnumMemberDeclarationSyntax)oldNode, (EnumMemberDeclarationSyntax)newNode);
return;
case SyntaxKind.GetAccessorDeclaration:
case SyntaxKind.SetAccessorDeclaration:
case SyntaxKind.AddAccessorDeclaration:
case SyntaxKind.RemoveAccessorDeclaration:
ClassifyUpdate((AccessorDeclarationSyntax)oldNode, (AccessorDeclarationSyntax)newNode);
return;
case SyntaxKind.TypeParameterConstraintClause:
ClassifyUpdate((TypeParameterConstraintClauseSyntax)oldNode, (TypeParameterConstraintClauseSyntax)newNode);
return;
case SyntaxKind.TypeParameter:
ClassifyUpdate((TypeParameterSyntax)oldNode, (TypeParameterSyntax)newNode);
return;
case SyntaxKind.Parameter:
ClassifyUpdate((ParameterSyntax)oldNode, (ParameterSyntax)newNode);
return;
case SyntaxKind.AttributeList:
ClassifyUpdate((AttributeListSyntax)oldNode, (AttributeListSyntax)newNode);
return;
case SyntaxKind.Attribute:
// Dev12 reports "Rename" if the attribute type name is changed.
// But such update is actually not renaming the attribute, it's changing what attribute is applied.
ReportError(RudeEditKind.Update);
return;
case SyntaxKind.TypeParameterList:
case SyntaxKind.ParameterList:
case SyntaxKind.BracketedParameterList:
case SyntaxKind.AccessorList:
return;
default:
throw ExceptionUtilities.UnexpectedValue(newNode.Kind());
}
}
private void ClassifyUpdate(NamespaceDeclarationSyntax oldNode, NamespaceDeclarationSyntax newNode)
{
Debug.Assert(!SyntaxFactory.AreEquivalent(oldNode.Name, newNode.Name));
ReportError(RudeEditKind.Renamed);
}
private void ClassifyUpdate(TypeDeclarationSyntax oldNode, TypeDeclarationSyntax newNode)
{
if (oldNode.Kind() != newNode.Kind())
{
ReportError(RudeEditKind.TypeKindUpdate);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers))
{
ReportError(RudeEditKind.ModifiersUpdate);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier))
{
ReportError(RudeEditKind.Renamed);
return;
}
Debug.Assert(!SyntaxFactory.AreEquivalent(oldNode.BaseList, newNode.BaseList));
ReportError(RudeEditKind.BaseTypeOrInterfaceUpdate);
}
private void ClassifyUpdate(EnumDeclarationSyntax oldNode, EnumDeclarationSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier))
{
ReportError(RudeEditKind.Renamed);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers))
{
ReportError(RudeEditKind.ModifiersUpdate);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.BaseList, newNode.BaseList))
{
ReportError(RudeEditKind.EnumUnderlyingTypeUpdate);
return;
}
// The list of members has been updated (separators added).
// We report a Rude Edit for each updated member.
}
private void ClassifyUpdate(DelegateDeclarationSyntax oldNode, DelegateDeclarationSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers))
{
ReportError(RudeEditKind.ModifiersUpdate);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.ReturnType, newNode.ReturnType))
{
ReportError(RudeEditKind.TypeUpdate);
return;
}
Debug.Assert(!SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier));
ReportError(RudeEditKind.Renamed);
}
private void ClassifyUpdate(BaseFieldDeclarationSyntax oldNode, BaseFieldDeclarationSyntax newNode)
{
if (oldNode.Kind() != newNode.Kind())
{
ReportError(RudeEditKind.FieldKindUpdate);
return;
}
Debug.Assert(!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers));
ReportError(RudeEditKind.ModifiersUpdate);
return;
}
private void ClassifyUpdate(VariableDeclarationSyntax oldNode, VariableDeclarationSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Type, newNode.Type))
{
ReportError(RudeEditKind.TypeUpdate);
return;
}
// separators may be added/removed:
}
private void ClassifyUpdate(VariableDeclaratorSyntax oldNode, VariableDeclaratorSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier))
{
ReportError(RudeEditKind.Renamed);
return;
}
// If the argument lists are mismatched the field must have mismatched "fixed" modifier,
// which is reported by the field declaration.
if ((oldNode.ArgumentList == null) == (newNode.ArgumentList == null))
{
if (!SyntaxFactory.AreEquivalent(oldNode.ArgumentList, newNode.ArgumentList))
{
ReportError(RudeEditKind.FixedSizeFieldUpdate);
return;
}
}
var typeDeclaration = (TypeDeclarationSyntax)oldNode.Parent.Parent.Parent;
if (typeDeclaration.Arity > 0)
{
ReportError(RudeEditKind.GenericTypeInitializerUpdate);
return;
}
// Check if a constant field is updated:
var fieldDeclaration = (FieldDeclarationSyntax)oldNode.Parent.Parent;
if (fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
ReportError(RudeEditKind.Update);
return;
}
ClassifyDeclarationBodyRudeUpdates(newNode);
}
private void ClassifyUpdate(MethodDeclarationSyntax oldNode, MethodDeclarationSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier))
{
ReportError(RudeEditKind.Renamed);
return;
}
if (!ClassifyMethodModifierUpdate(oldNode.Modifiers, newNode.Modifiers))
{
ReportError(RudeEditKind.ModifiersUpdate);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.ReturnType, newNode.ReturnType))
{
ReportError(RudeEditKind.TypeUpdate);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.ExplicitInterfaceSpecifier, newNode.ExplicitInterfaceSpecifier))
{
ReportError(RudeEditKind.Renamed);
return;
}
ClassifyMethodBodyRudeUpdate(
(SyntaxNode)oldNode.Body ?? oldNode.ExpressionBody?.Expression,
(SyntaxNode)newNode.Body ?? newNode.ExpressionBody?.Expression,
containingMethodOpt: newNode,
containingType: (TypeDeclarationSyntax)newNode.Parent);
}
private bool ClassifyMethodModifierUpdate(SyntaxTokenList oldModifiers, SyntaxTokenList newModifiers)
{
var oldAsyncIndex = oldModifiers.IndexOf(SyntaxKind.AsyncKeyword);
var newAsyncIndex = newModifiers.IndexOf(SyntaxKind.AsyncKeyword);
if (oldAsyncIndex >= 0)
{
oldModifiers = oldModifiers.RemoveAt(oldAsyncIndex);
}
if (newAsyncIndex >= 0)
{
newModifiers = newModifiers.RemoveAt(newAsyncIndex);
}
// 'async' keyword is allowed to add, but not to remove.
if (oldAsyncIndex >= 0 && newAsyncIndex < 0)
{
return false;
}
return SyntaxFactory.AreEquivalent(oldModifiers, newModifiers);
}
private void ClassifyUpdate(ConversionOperatorDeclarationSyntax oldNode, ConversionOperatorDeclarationSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers))
{
ReportError(RudeEditKind.ModifiersUpdate);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.ImplicitOrExplicitKeyword, newNode.ImplicitOrExplicitKeyword))
{
ReportError(RudeEditKind.Renamed);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.Type, newNode.Type))
{
ReportError(RudeEditKind.TypeUpdate);
return;
}
ClassifyMethodBodyRudeUpdate(
(SyntaxNode)oldNode.Body ?? oldNode.ExpressionBody?.Expression,
(SyntaxNode)newNode.Body ?? newNode.ExpressionBody?.Expression,
containingMethodOpt: null,
containingType: (TypeDeclarationSyntax)newNode.Parent);
}
private void ClassifyUpdate(OperatorDeclarationSyntax oldNode, OperatorDeclarationSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers))
{
ReportError(RudeEditKind.ModifiersUpdate);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.OperatorToken, newNode.OperatorToken))
{
ReportError(RudeEditKind.Renamed);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.ReturnType, newNode.ReturnType))
{
ReportError(RudeEditKind.TypeUpdate);
return;
}
ClassifyMethodBodyRudeUpdate(
(SyntaxNode)oldNode.Body ?? oldNode.ExpressionBody?.Expression,
(SyntaxNode)newNode.Body ?? newNode.ExpressionBody?.Expression,
containingMethodOpt: null,
containingType: (TypeDeclarationSyntax)newNode.Parent);
}
private void ClassifyUpdate(AccessorDeclarationSyntax oldNode, AccessorDeclarationSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers))
{
ReportError(RudeEditKind.ModifiersUpdate);
return;
}
if (oldNode.Kind() != newNode.Kind())
{
ReportError(RudeEditKind.AccessorKindUpdate);
return;
}
Debug.Assert(newNode.Parent is AccessorListSyntax);
Debug.Assert(newNode.Parent.Parent is BasePropertyDeclarationSyntax);
ClassifyMethodBodyRudeUpdate(
(SyntaxNode)oldNode.Body ?? oldNode.ExpressionBody?.Expression,
(SyntaxNode)newNode.Body ?? newNode.ExpressionBody?.Expression,
containingMethodOpt: null,
containingType: (TypeDeclarationSyntax)newNode.Parent.Parent.Parent);
}
private void ClassifyUpdate(EnumMemberDeclarationSyntax oldNode, EnumMemberDeclarationSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier))
{
ReportError(RudeEditKind.Renamed);
return;
}
Debug.Assert(!SyntaxFactory.AreEquivalent(oldNode.EqualsValue, newNode.EqualsValue));
ReportError(RudeEditKind.InitializerUpdate);
}
private void ClassifyUpdate(ConstructorDeclarationSyntax oldNode, ConstructorDeclarationSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers))
{
ReportError(RudeEditKind.ModifiersUpdate);
return;
}
ClassifyMethodBodyRudeUpdate(
(SyntaxNode)oldNode.Body ?? oldNode.ExpressionBody?.Expression,
(SyntaxNode)newNode.Body ?? newNode.ExpressionBody?.Expression,
containingMethodOpt: null,
containingType: (TypeDeclarationSyntax)newNode.Parent);
}
private void ClassifyUpdate(DestructorDeclarationSyntax oldNode, DestructorDeclarationSyntax newNode)
{
ClassifyMethodBodyRudeUpdate(
(SyntaxNode)oldNode.Body ?? oldNode.ExpressionBody?.Expression,
(SyntaxNode)newNode.Body ?? newNode.ExpressionBody?.Expression,
containingMethodOpt: null,
containingType: (TypeDeclarationSyntax)newNode.Parent);
}
private void ClassifyUpdate(PropertyDeclarationSyntax oldNode, PropertyDeclarationSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers))
{
ReportError(RudeEditKind.ModifiersUpdate);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.Type, newNode.Type))
{
ReportError(RudeEditKind.TypeUpdate);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier))
{
ReportError(RudeEditKind.Renamed);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.ExplicitInterfaceSpecifier, newNode.ExplicitInterfaceSpecifier))
{
ReportError(RudeEditKind.Renamed);
return;
}
var containingType = (TypeDeclarationSyntax)newNode.Parent;
// TODO: We currently don't support switching from auto-props to properties with accessors and vice versa.
// If we do we should also allow it for expression bodies.
if (!SyntaxFactory.AreEquivalent(oldNode.ExpressionBody, newNode.ExpressionBody))
{
var oldBody = SyntaxUtilities.TryGetEffectiveGetterBody(oldNode.ExpressionBody, oldNode.AccessorList);
var newBody = SyntaxUtilities.TryGetEffectiveGetterBody(newNode.ExpressionBody, newNode.AccessorList);
ClassifyMethodBodyRudeUpdate(
oldBody,
newBody,
containingMethodOpt: null,
containingType: containingType);
return;
}
Debug.Assert(!SyntaxFactory.AreEquivalent(oldNode.Initializer, newNode.Initializer));
if (containingType.Arity > 0)
{
ReportError(RudeEditKind.GenericTypeInitializerUpdate);
return;
}
if (newNode.Initializer != null)
{
ClassifyDeclarationBodyRudeUpdates(newNode.Initializer);
}
}
private void ClassifyUpdate(IndexerDeclarationSyntax oldNode, IndexerDeclarationSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers))
{
ReportError(RudeEditKind.ModifiersUpdate);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.Type, newNode.Type))
{
ReportError(RudeEditKind.TypeUpdate);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.ExplicitInterfaceSpecifier, newNode.ExplicitInterfaceSpecifier))
{
ReportError(RudeEditKind.Renamed);
return;
}
Debug.Assert(!SyntaxFactory.AreEquivalent(oldNode.ExpressionBody, newNode.ExpressionBody));
var oldBody = SyntaxUtilities.TryGetEffectiveGetterBody(oldNode.ExpressionBody, oldNode.AccessorList);
var newBody = SyntaxUtilities.TryGetEffectiveGetterBody(newNode.ExpressionBody, newNode.AccessorList);
ClassifyMethodBodyRudeUpdate(
oldBody,
newBody,
containingMethodOpt: null,
containingType: (TypeDeclarationSyntax)newNode.Parent);
}
private void ClassifyUpdate(TypeParameterSyntax oldNode, TypeParameterSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier))
{
ReportError(RudeEditKind.Renamed);
return;
}
Debug.Assert(!SyntaxFactory.AreEquivalent(oldNode.VarianceKeyword, newNode.VarianceKeyword));
ReportError(RudeEditKind.VarianceUpdate);
}
private void ClassifyUpdate(TypeParameterConstraintClauseSyntax oldNode, TypeParameterConstraintClauseSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Name, newNode.Name))
{
ReportError(RudeEditKind.Renamed);
return;
}
Debug.Assert(!SyntaxFactory.AreEquivalent(oldNode.Constraints, newNode.Constraints));
ReportError(RudeEditKind.TypeUpdate);
}
private void ClassifyUpdate(ParameterSyntax oldNode, ParameterSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier))
{
ReportError(RudeEditKind.Renamed);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers))
{
ReportError(RudeEditKind.ModifiersUpdate);
return;
}
if (!SyntaxFactory.AreEquivalent(oldNode.Type, newNode.Type))
{
ReportError(RudeEditKind.TypeUpdate);
return;
}
Debug.Assert(!SyntaxFactory.AreEquivalent(oldNode.Default, newNode.Default));
ReportError(RudeEditKind.InitializerUpdate);
}
private void ClassifyUpdate(AttributeListSyntax oldNode, AttributeListSyntax newNode)
{
if (!SyntaxFactory.AreEquivalent(oldNode.Target, newNode.Target))
{
ReportError(RudeEditKind.Update);
return;
}
// changes in attribute separators are not interesting:
}
private void ClassifyMethodBodyRudeUpdate(
SyntaxNode oldBody,
SyntaxNode newBody,
MethodDeclarationSyntax containingMethodOpt,
TypeDeclarationSyntax containingType)
{
Debug.Assert(oldBody is BlockSyntax || oldBody is ExpressionSyntax || oldBody == null);
Debug.Assert(newBody is BlockSyntax || newBody is ExpressionSyntax || newBody == null);
if ((oldBody == null) != (newBody == null))
{
if (oldBody == null)
{
ReportError(RudeEditKind.MethodBodyAdd);
return;
}
else
{
ReportError(RudeEditKind.MethodBodyDelete);
return;
}
}
ClassifyMemberBodyRudeUpdate(containingMethodOpt, containingType, isTriviaUpdate: false);
if (newBody != null)
{
ClassifyDeclarationBodyRudeUpdates(newBody);
}
}
public void ClassifyMemberBodyRudeUpdate(
MethodDeclarationSyntax containingMethodOpt,
TypeDeclarationSyntax containingTypeOpt,
bool isTriviaUpdate)
{
if (SyntaxUtilities.Any(containingMethodOpt?.TypeParameterList))
{
ReportError(isTriviaUpdate ? RudeEditKind.GenericMethodTriviaUpdate : RudeEditKind.GenericMethodUpdate);
return;
}
if (SyntaxUtilities.Any(containingTypeOpt?.TypeParameterList))
{
ReportError(isTriviaUpdate ? RudeEditKind.GenericTypeTriviaUpdate : RudeEditKind.GenericTypeUpdate);
return;
}
}
public void ClassifyDeclarationBodyRudeUpdates(SyntaxNode newDeclarationOrBody)
{
foreach (var node in newDeclarationOrBody.DescendantNodesAndSelf(ChildrenCompiledInBody))
{
switch (node.Kind())
{
case SyntaxKind.StackAllocArrayCreationExpression:
ReportError(RudeEditKind.StackAllocUpdate, node, _newNode);
return;
}
}
}
private static bool ChildrenCompiledInBody(SyntaxNode node)
{
return node.Kind() != SyntaxKind.ParenthesizedLambdaExpression
&& node.Kind() != SyntaxKind.SimpleLambdaExpression;
}
#endregion
}
internal override void ReportSyntacticRudeEdits(
List diagnostics,
Match match,
Edit edit,
Dictionary editMap)
{
if (HasParentEdit(editMap, edit))
{
return;
}
var classifier = new EditClassifier(this, diagnostics, edit.OldNode, edit.NewNode, edit.Kind, match);
classifier.ClassifyEdit();
}
internal override void ReportMemberUpdateRudeEdits(List diagnostics, SyntaxNode newMember, TextSpan? span)
{
var classifier = new EditClassifier(this, diagnostics, null, newMember, EditKind.Update, span: span);
classifier.ClassifyMemberBodyRudeUpdate(
newMember as MethodDeclarationSyntax,
newMember.FirstAncestorOrSelf(),
isTriviaUpdate: true);
classifier.ClassifyDeclarationBodyRudeUpdates(newMember);
}
#endregion
#region Semantic Rude Edits
internal override void ReportInsertedMemberSymbolRudeEdits(List diagnostics, ISymbol newSymbol)
{
// We rejected all exported methods during syntax analysis, so no additional work is needed here.
}
#endregion
#region Exception Handling Rude Edits
protected override List GetExceptionHandlingAncestors(SyntaxNode node, bool isLeaf)
{
var result = new List();
while (node != null)
{
var kind = node.Kind();
switch (kind)
{
case SyntaxKind.TryStatement:
if (!isLeaf)
{
result.Add(node);
}
break;
case SyntaxKind.CatchClause:
case SyntaxKind.FinallyClause:
result.Add(node);
// skip try:
Debug.Assert(node.Parent.Kind() == SyntaxKind.TryStatement);
node = node.Parent;
break;
// stop at type declaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.StructDeclaration:
return result;
}
// stop at lambda:
if (LambdaUtilities.IsLambda(node))
{
return result;
}
node = node.Parent;
}
return result;
}
internal override void ReportEnclosingExceptionHandlingRudeEdits(
List diagnostics,
IEnumerable> exceptionHandlingEdits,
SyntaxNode oldStatement,
TextSpan newStatementSpan)
{
foreach (var edit in exceptionHandlingEdits)
{
// try/catch/finally have distinct labels so only the nodes of the same kind may match:
Debug.Assert(edit.Kind != EditKind.Update || edit.OldNode.RawKind == edit.NewNode.RawKind);
if (edit.Kind != EditKind.Update || !AreExceptionClausesEquivalent(edit.OldNode, edit.NewNode))
{
AddRudeDiagnostic(diagnostics, edit.OldNode, edit.NewNode, newStatementSpan);
}
}
}
private static bool AreExceptionClausesEquivalent(SyntaxNode oldNode, SyntaxNode newNode)
{
switch (oldNode.Kind())
{
case SyntaxKind.TryStatement:
var oldTryStatement = (TryStatementSyntax)oldNode;
var newTryStatement = (TryStatementSyntax)newNode;
return SyntaxFactory.AreEquivalent(oldTryStatement.Finally, newTryStatement.Finally)
&& SyntaxFactory.AreEquivalent(oldTryStatement.Catches, newTryStatement.Catches);
case SyntaxKind.CatchClause:
case SyntaxKind.FinallyClause:
return SyntaxFactory.AreEquivalent(oldNode, newNode);
default:
throw ExceptionUtilities.UnexpectedValue(oldNode.Kind());
}
}
///
/// An active statement (leaf or not) inside a "catch" makes the catch block read-only.
/// An active statement (leaf or not) inside a "finally" makes the whole try/catch/finally block read-only.
/// An active statement (non leaf) inside a "try" makes the catch/finally block read-only.
///
protected override TextSpan GetExceptionHandlingRegion(SyntaxNode node, out bool coversAllChildren)
{
TryStatementSyntax tryStatement;
switch (node.Kind())
{
case SyntaxKind.TryStatement:
tryStatement = (TryStatementSyntax)node;
coversAllChildren = false;
if (tryStatement.Catches.Count == 0)
{
Debug.Assert(tryStatement.Finally != null);
return tryStatement.Finally.Span;
}
return TextSpan.FromBounds(
tryStatement.Catches.First().SpanStart,
(tryStatement.Finally != null) ?
tryStatement.Finally.Span.End :
tryStatement.Catches.Last().Span.End);
case SyntaxKind.CatchClause:
coversAllChildren = true;
return node.Span;
case SyntaxKind.FinallyClause:
coversAllChildren = true;
tryStatement = (TryStatementSyntax)node.Parent;
return tryStatement.Span;
default:
throw ExceptionUtilities.UnexpectedValue(node.Kind());
}
}
#endregion
#region State Machines
internal override bool IsStateMachineMethod(SyntaxNode declaration)
{
return SyntaxUtilities.IsAsyncMethodOrLambda(declaration) ||
SyntaxUtilities.IsIteratorMethod(declaration);
}
protected override void GetStateMachineInfo(SyntaxNode body, out ImmutableArray suspensionPoints, out StateMachineKind kind)
{
if (SyntaxUtilities.IsAsyncMethodOrLambda(body.Parent))
{
suspensionPoints = SyntaxUtilities.GetAwaitExpressions(body);
kind = StateMachineKind.Async;
}
else
{
suspensionPoints = SyntaxUtilities.GetYieldStatements(body);
kind = suspensionPoints.IsEmpty ? StateMachineKind.None : StateMachineKind.Iterator;
}
}
internal override void ReportStateMachineSuspensionPointRudeEdits(List diagnostics, SyntaxNode oldNode, SyntaxNode newNode)
{
// TODO: changes around suspension points (foreach, lock, using, etc.)
if (oldNode.RawKind != newNode.RawKind)
{
Debug.Assert(oldNode is YieldStatementSyntax && newNode is YieldStatementSyntax);
// changing yield return to yield break
diagnostics.Add(new RudeEditDiagnostic(
RudeEditKind.Update,
newNode.Span,
newNode,
new[] { GetStatementDisplayName(newNode, EditKind.Update) }));
}
else if (newNode.IsKind(SyntaxKind.AwaitExpression))
{
var oldContainingStatementPart = FindContainingStatementPart(oldNode);
var newContainingStatementPart = FindContainingStatementPart(newNode);
// If the old statement has spilled state and the new doesn't the edit is ok. We'll just not use the spilled state.
if (!SyntaxFactory.AreEquivalent(oldContainingStatementPart, newContainingStatementPart) &&
!HasNoSpilledState(newNode, newContainingStatementPart))
{
diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.AwaitStatementUpdate, newContainingStatementPart.Span));
}
}
}
private static SyntaxNode FindContainingStatementPart(SyntaxNode node)
{
while (true)
{
var statement = node as StatementSyntax;
if (statement != null)
{
return statement;
}
switch (node.Parent.Kind())
{
case SyntaxKind.ForStatement:
case SyntaxKind.ForEachStatement:
case SyntaxKind.IfStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.DoStatement:
case SyntaxKind.SwitchStatement:
case SyntaxKind.LockStatement:
case SyntaxKind.UsingStatement:
case SyntaxKind.ArrowExpressionClause:
return node;
}
if (LambdaUtilities.IsLambdaBodyStatementOrExpression(node))
{
return node;
}
node = node.Parent;
}
}
private static bool HasNoSpilledState(SyntaxNode awaitExpression, SyntaxNode containingStatementPart)
{
Debug.Assert(awaitExpression.IsKind(SyntaxKind.AwaitExpression));
// There is nothing within the statement part surrounding the await expression.
if (containingStatementPart == awaitExpression)
{
return true;
}
switch (containingStatementPart.Kind())
{
case SyntaxKind.ExpressionStatement:
case SyntaxKind.ReturnStatement:
var expression = GetExpressionFromStatementPart(containingStatementPart);
// await expr;
// return await expr;
if (expression == awaitExpression)
{
return true;
}
// identifier = await expr;
// return identifier = await expr;
return IsSimpleAwaitAssignment(expression, awaitExpression);
case SyntaxKind.VariableDeclaration:
// var idf = await expr in using, for, etc.
// EqualsValueClause -> VariableDeclarator -> VariableDeclaration
return awaitExpression.Parent.Parent.Parent == containingStatementPart;
case SyntaxKind.LocalDeclarationStatement:
// var idf = await expr;
// EqualsValueClause -> VariableDeclarator -> VariableDeclaration -> LocalDeclarationStatement
return awaitExpression.Parent.Parent.Parent.Parent == containingStatementPart;
}
return IsSimpleAwaitAssignment(containingStatementPart, awaitExpression);
}
private static ExpressionSyntax GetExpressionFromStatementPart(SyntaxNode statement)
{
switch (statement.Kind())
{
case SyntaxKind.ExpressionStatement:
return ((ExpressionStatementSyntax)statement).Expression;
case SyntaxKind.ReturnStatement:
return ((ReturnStatementSyntax)statement).Expression;
default:
throw ExceptionUtilities.UnexpectedValue(statement.Kind());
}
}
private static bool IsSimpleAwaitAssignment(SyntaxNode node, SyntaxNode awaitExpression)
{
if (node.IsKind(SyntaxKind.SimpleAssignmentExpression))
{
var assignment = (AssignmentExpressionSyntax)node;
return assignment.Left.IsKind(SyntaxKind.IdentifierName) && assignment.Right == awaitExpression;
}
return false;
}
#endregion
#region Rude Edits around Active Statement
internal override void ReportOtherRudeEditsAroundActiveStatement(
List diagnostics,
Match match,
SyntaxNode oldActiveStatement,
SyntaxNode newActiveStatement,
bool isLeaf)
{
ReportRudeEditsForUnsupportedCSharp7EnC(diagnostics, match);
ReportRudeEditsForAncestorsDeclaringInterStatementTemps(diagnostics, match, oldActiveStatement, newActiveStatement, isLeaf);
ReportRudeEditsForCheckedStatements(diagnostics, oldActiveStatement, newActiveStatement, isLeaf);
}
///
/// If either trees (after or before the edit) contain unsupported C# 7 features around the active statement, report it.
///
private void ReportRudeEditsForUnsupportedCSharp7EnC(List diagnostics, Match match)
{
SyntaxNode foundCSharp7Syntax = match.NewRoot.DescendantNodesAndSelf().FirstOrDefault(n => IsUnsupportedCSharp7EnCNode(n));
if (foundCSharp7Syntax != null)
{
AddRudeUpdateAroundActiveStatement(diagnostics, foundCSharp7Syntax);
return;
}
foundCSharp7Syntax = match.OldRoot.DescendantNodesAndSelf().FirstOrDefault(n => IsUnsupportedCSharp7EnCNode(n));
if (foundCSharp7Syntax != null)
{
AddRudeUpdateInCSharp7Method(diagnostics, foundCSharp7Syntax);
}
}
///
/// If the active method used unsupported C# 7 features before the edit, it needs to be reported.
///
private void AddRudeUpdateInCSharp7Method(List diagnostics, SyntaxNode oldCSharp7Syntax)
{
diagnostics.Add(new RudeEditDiagnostic(
RudeEditKind.UpdateAroundActiveStatement,
default(TextSpan), // no span since the offending node is in the old syntax
oldCSharp7Syntax,
new[] { GetStatementDisplayName(oldCSharp7Syntax, EditKind.Update) }));
}
private static bool IsUnsupportedCSharp7EnCNode(SyntaxNode n)
{
switch (n.Kind())
{
case SyntaxKind.LocalFunctionStatement:
return true;
default:
return false;
}
}
private void ReportRudeEditsForCheckedStatements(
List diagnostics,
SyntaxNode oldActiveStatement,
SyntaxNode newActiveStatement,
bool isLeaf)
{
// checked context can be changed around leaf active statement:
if (isLeaf)
{
return;
}
// Changing checked context around an internal active statement may change the instructions
// executed after method calls in the active statement but before the next sequence point.
// Since the debugger remaps the IP at the first sequence point following a call instruction
// allowing overflow context to be changed may lead to execution of code with old semantics.
var oldCheckedStatement = TryGetCheckedStatementAncestor(oldActiveStatement);
var newCheckedStatement = TryGetCheckedStatementAncestor(newActiveStatement);
bool isRude;
if (oldCheckedStatement == null || newCheckedStatement == null)
{
isRude = oldCheckedStatement != newCheckedStatement;
}
else
{
isRude = oldCheckedStatement.Kind() != newCheckedStatement.Kind();
}
if (isRude)
{
AddRudeDiagnostic(diagnostics, oldCheckedStatement, newCheckedStatement, newActiveStatement.Span);
}
}
private static CheckedStatementSyntax TryGetCheckedStatementAncestor(SyntaxNode node)
{
// Ignoring lambda boundaries since checked context flows through.
while (node != null)
{
switch (node.Kind())
{
case SyntaxKind.CheckedStatement:
case SyntaxKind.UncheckedStatement:
return (CheckedStatementSyntax)node;
}
node = node.Parent;
}
return null;
}
private void ReportRudeEditsForAncestorsDeclaringInterStatementTemps(
List diagnostics,
Match match,
SyntaxNode oldActiveStatement,
SyntaxNode newActiveStatement,
bool isLeaf)
{
// Rude Edits for fixed/using/lock/foreach statements that are added/updated around an active statement.
// Although such changes are technically possible, they might lead to confusion since
// the temporary variables these statements generate won't be properly initialized.
//
// We use a simple algorithm to match each new node with its old counterpart.
// If all nodes match this algorithm is linear, otherwise it's quadratic.
//
// Unlike exception regions matching where we use LCS, we allow reordering of the statements.
ReportUnmatchedStatements(diagnostics, match, new[] { (int)SyntaxKind.LockStatement }, oldActiveStatement, newActiveStatement,
areEquivalent: AreEquivalentActiveStatements,
areSimilar: null);
ReportUnmatchedStatements(diagnostics, match, new[] { (int)SyntaxKind.FixedStatement }, oldActiveStatement, newActiveStatement,
areEquivalent: AreEquivalentActiveStatements,
areSimilar: (n1, n2) => DeclareSameIdentifiers(n1.Declaration.Variables, n2.Declaration.Variables));
ReportUnmatchedStatements(diagnostics, match, new[] { (int)SyntaxKind.UsingStatement }, oldActiveStatement, newActiveStatement,
areEquivalent: AreEquivalentActiveStatements,
areSimilar: (using1, using2) =>
{
return using1.Declaration != null && using2.Declaration != null &&
DeclareSameIdentifiers(using1.Declaration.Variables, using2.Declaration.Variables);
});
ReportUnmatchedStatements(diagnostics, match, new[] { (int)SyntaxKind.ForEachStatement, (int)SyntaxKind.ForEachVariableStatement }, oldActiveStatement, newActiveStatement,
areEquivalent: AreEquivalentActiveStatements,
areSimilar: AreSimilarActiveStatements);
}
private static bool DeclareSameIdentifiers(SeparatedSyntaxList oldVariables, SeparatedSyntaxList newVariables)
{
return DeclareSameIdentifiers(oldVariables.Select(v => v.Identifier).ToArray(), newVariables.Select(v => v.Identifier).ToArray());
}
private static bool DeclareSameIdentifiers(SyntaxToken[] oldVariables, SyntaxToken[] newVariables)
{
if (oldVariables.Length != newVariables.Length)
{
return false;
}
for (int i = 0; i < oldVariables.Length; i++)
{
if (!SyntaxFactory.AreEquivalent(oldVariables[i], newVariables[i]))
{
return false;
}
}
return true;
}
#endregion
}
}