in src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs [2084:2506]
internal void AnalyzeSemantics(
EditScript<SyntaxNode> editScript,
Dictionary<SyntaxNode, EditKind> editMap,
SourceText oldText,
ImmutableArray<ActiveStatementSpan> oldActiveStatements,
List<KeyValuePair<SyntaxNode, SyntaxNode>> triviaEdits,
List<UpdatedMemberInfo> updatedMembers,
SemanticModel oldModel,
SemanticModel newModel,
[Out]List<SemanticEdit> semanticEdits,
[Out]List<RudeEditDiagnostic> diagnostics,
out Diagnostic firstDeclarationErrorOpt,
CancellationToken cancellationToken)
{
// { new type -> constructor update }
Dictionary<INamedTypeSymbol, ConstructorEdit> instanceConstructorEdits = null;
Dictionary<INamedTypeSymbol, ConstructorEdit> staticConstructorEdits = null;
INamedTypeSymbol layoutAttribute = null;
var newSymbolsWithEdit = new HashSet<ISymbol>();
int updatedMemberIndex = 0;
firstDeclarationErrorOpt = null;
for (int i = 0; i < editScript.Edits.Length; i++)
{
cancellationToken.ThrowIfCancellationRequested();
var edit = editScript.Edits[i];
ISymbol oldSymbol, newSymbol;
Func<SyntaxNode, SyntaxNode> syntaxMapOpt;
SemanticEditKind editKind;
switch (edit.Kind)
{
case EditKind.Move:
// Move is always a Rude Edit.
throw ExceptionUtilities.UnexpectedValue(edit.Kind);
case EditKind.Delete:
{
editKind = SemanticEditKind.Delete;
if (HasParentEdit(editMap, edit))
{
continue;
}
oldSymbol = GetSymbolForEdit(oldModel, edit.OldNode, edit.Kind, editMap, cancellationToken);
if (oldSymbol == null)
{
continue;
}
// The only member that is allowed to be deleted is a parameterless constructor.
// For any other member a rude edit is reported earlier during syntax edit classification.
// Deleting a parameterless constructor needs special handling.
// If the new type has a parameterless ctor of the same accessibility then UPDATE.
// Error otherwise.
Debug.Assert(AsParameterlessConstructor(oldSymbol) != null);
SyntaxNode oldTypeSyntax = TryGetContainingTypeDeclaration(edit.OldNode);
Debug.Assert(oldTypeSyntax != null);
var newType = TryGetPartnerType(oldTypeSyntax, editScript.Match, newModel, cancellationToken);
newSymbol = TryGetParameterlessConstructor(newType, oldSymbol.IsStatic);
if (newSymbol == null || newSymbol.DeclaredAccessibility != oldSymbol.DeclaredAccessibility)
{
diagnostics.Add(new RudeEditDiagnostic(
RudeEditKind.Delete,
GetDeletedNodeDiagnosticSpan(editScript.Match.Matches, edit.OldNode),
edit.OldNode,
new[] { GetTopLevelDisplayName(edit.OldNode, EditKind.Delete) }));
continue;
}
editKind = SemanticEditKind.Update;
syntaxMapOpt = null;
}
break;
case EditKind.Reorder:
// Currently we don't do any semantic checks for reordering
// and we don't need to report them to the compiler either.
// Reordering of fields is not allowed since it changes the layout of the type.
Debug.Assert(!IsDeclarationWithInitializer(edit.OldNode) && !IsDeclarationWithInitializer(edit.NewNode));
continue;
case EditKind.Insert:
{
editKind = SemanticEditKind.Insert;
SyntaxNode newTypeSyntax = TryGetContainingTypeDeclaration(edit.NewNode);
if (newTypeSyntax != null && HasEdit(editMap, newTypeSyntax, EditKind.Insert))
{
// inserting into a new type
continue;
}
syntaxMapOpt = null;
oldSymbol = null;
newSymbol = GetSymbolForEdit(newModel, edit.NewNode, edit.Kind, editMap, cancellationToken);
if (newSymbol == null)
{
// node doesn't represent a symbol
continue;
}
// TODO: scripting
// inserting a top-level member/type
if (newTypeSyntax == null)
{
break;
}
var newType = (INamedTypeSymbol)newModel.GetDeclaredSymbol(newTypeSyntax, cancellationToken);
var oldType = TryGetPartnerType(newTypeSyntax, editScript.Match, oldModel, cancellationToken);
// There has to be a matching old type syntax since the containing type hasn't been inserted.
Debug.Assert(oldType != null);
Debug.Assert(newType != null);
// Validate that the type declarations are correct. If not we can't reason about their members.
// Declaration diagnostics are cached on compilation, so we don't need to cache them here.
firstDeclarationErrorOpt =
GetFirstDeclarationError(oldModel, oldType, cancellationToken) ??
GetFirstDeclarationError(newModel, newType, cancellationToken);
if (firstDeclarationErrorOpt != null)
{
continue;
}
// Inserting a parameterless constructor needs special handling:
// 1) static ctor
// a) old type has an implicit static ctor
// UPDATE of the implicit static ctor
// b) otherwise
// INSERT of a static parameterless ctor
//
// 2) public instance ctor
// a) old type has an implicit instance ctor
// UPDATE of the implicit instance ctor
// b) otherwise
// ERROR: adding a non-private member
// 3) non-public instance ctor
// a) old type has an implicit instance ctor
// ERROR: changing visibility of the ctor
// b) otherwise
// INSERT of an instance parameterless ctor
IMethodSymbol newCtor = AsParameterlessConstructor(newSymbol);
if (newCtor != null)
{
oldSymbol = TryGetParameterlessConstructor(oldType, newSymbol.IsStatic);
if (newCtor.IsStatic)
{
if (oldSymbol != null)
{
editKind = SemanticEditKind.Update;
}
}
else if (oldSymbol != null)
{
if (oldSymbol.DeclaredAccessibility != newCtor.DeclaredAccessibility)
{
// changing visibility of a member
diagnostics.Add(new RudeEditDiagnostic(
RudeEditKind.ChangingConstructorVisibility,
GetDiagnosticSpan(edit.NewNode, EditKind.Insert)));
}
else
{
editKind = SemanticEditKind.Update;
}
}
}
if (editKind == SemanticEditKind.Insert)
{
ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol);
ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, edit.NewNode, newModel, ref layoutAttribute);
}
bool isConstructorWithMemberInitializers;
if ((isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(edit.NewNode)) ||
IsDeclarationWithInitializer(edit.NewNode))
{
if (DeferConstructorEdit(
oldType,
newType,
editKind,
edit.NewNode,
newSymbol,
newModel,
isConstructorWithMemberInitializers,
ref syntaxMapOpt,
ref instanceConstructorEdits,
ref staticConstructorEdits,
diagnostics,
cancellationToken))
{
if (newSymbol.Kind == SymbolKind.Method)
{
// Don't add a separate semantic edit for a field/property with an initializer.
// All edits of initializers will be aggregated to edits of constructors where these initializers are emitted.
continue;
}
else
{
// A semantic edit to create the field/property is gonna be added.
Debug.Assert(editKind == SemanticEditKind.Insert);
}
}
}
}
break;
case EditKind.Update:
{
editKind = SemanticEditKind.Update;
newSymbol = GetSymbolForEdit(newModel, edit.NewNode, edit.Kind, editMap, cancellationToken);
if (newSymbol == null)
{
// node doesn't represent a symbol
continue;
}
oldSymbol = GetSymbolForEdit(oldModel, edit.OldNode, edit.Kind, editMap, cancellationToken);
Debug.Assert((newSymbol == null) == (oldSymbol == null));
var oldContainingType = oldSymbol.ContainingType;
var newContainingType = newSymbol.ContainingType;
// Validate that the type declarations are correct to avoid issues with invalid partial declarations, etc.
// Declaration diagnostics are cached on compilation, so we don't need to cache them here.
firstDeclarationErrorOpt =
GetFirstDeclarationError(oldModel, oldContainingType, cancellationToken) ??
GetFirstDeclarationError(newModel, newContainingType, cancellationToken);
if (firstDeclarationErrorOpt != null)
{
continue;
}
if (updatedMemberIndex < updatedMembers.Count && updatedMembers[updatedMemberIndex].EditOrdinal == i)
{
var updatedMember = updatedMembers[updatedMemberIndex];
ReportStateMachineRudeEdits(oldModel.Compilation, updatedMember, oldSymbol, diagnostics);
ReportLambdaAndClosureRudeEdits(
oldModel,
updatedMember.OldBody,
oldSymbol,
newModel,
updatedMember.NewBody,
newSymbol,
updatedMember.ActiveOrMatchedLambdasOpt,
updatedMember.Map,
diagnostics,
out var newBodyHasLambdas,
cancellationToken);
// We need to provide syntax map to the compiler if
// 1) The new member has a active statement
// The values of local variables declared or synthesized in the method have to be preserved.
// 2) The new member generates a state machine
// In case the state machine is suspended we need to preserve variables.
// 3) The new member contains lambdas
// We need to map new lambdas in the method to the matching old ones.
// If the old method has lambdas but the new one doesn't there is nothing to preserve.
if (updatedMember.HasActiveStatement || updatedMember.NewHasStateMachineSuspensionPoint || newBodyHasLambdas)
{
syntaxMapOpt = CreateSyntaxMap(updatedMember.Map.Reverse);
}
else
{
syntaxMapOpt = null;
}
updatedMemberIndex++;
}
else
{
syntaxMapOpt = null;
}
// If a constructor changes from including initializers to not including initializers
// we don't need to aggregate syntax map from all initializers for the constructor update semantic edit.
bool isConstructorWithMemberInitializers;
if ((isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(edit.NewNode)) ||
IsDeclarationWithInitializer(edit.OldNode) ||
IsDeclarationWithInitializer(edit.NewNode))
{
if (DeferConstructorEdit(
oldContainingType,
newContainingType,
editKind,
edit.NewNode,
newSymbol,
newModel,
isConstructorWithMemberInitializers,
ref syntaxMapOpt,
ref instanceConstructorEdits,
ref staticConstructorEdits,
diagnostics,
cancellationToken))
{
// Don't add a separate semantic edit for a field/property with an initializer.
// All edits of initializers will be aggregated to edits of constructors where these initializers are emitted.
continue;
}
}
}
break;
default:
throw ExceptionUtilities.UnexpectedValue(edit.Kind);
}
semanticEdits.Add(new SemanticEdit(editKind, oldSymbol, newSymbol, syntaxMapOpt, preserveLocalVariables: syntaxMapOpt != null));
newSymbolsWithEdit.Add(newSymbol);
}
foreach (var edit in triviaEdits)
{
var oldSymbol = GetSymbolForEdit(oldModel, edit.Key, EditKind.Update, editMap, cancellationToken);
var newSymbol = GetSymbolForEdit(newModel, edit.Value, EditKind.Update, editMap, cancellationToken);
var oldContainingType = oldSymbol.ContainingType;
var newContainingType = newSymbol.ContainingType;
// Validate that the type declarations are correct to avoid issues with invalid partial declarations, etc.
// Declaration diagnostics are cached on compilation, so we don't need to cache them here.
firstDeclarationErrorOpt =
GetFirstDeclarationError(oldModel, oldContainingType, cancellationToken) ??
GetFirstDeclarationError(newModel, newContainingType, cancellationToken);
if (firstDeclarationErrorOpt != null)
{
continue;
}
// We need to provide syntax map to the compiler if the member is active (see member update above):
bool isActiveMember =
TryGetOverlappingActiveStatements(oldText, edit.Key.Span, oldActiveStatements, out var start, out var end) ||
IsStateMachineMethod(edit.Key) ||
ContainsLambda(edit.Key);
var syntaxMap = isActiveMember ? CreateSyntaxMapForEquivalentNodes(edit.Key, edit.Value) : null;
// only trivia changed:
Debug.Assert(IsConstructorWithMemberInitializers(edit.Key) == IsConstructorWithMemberInitializers(edit.Value));
Debug.Assert(IsDeclarationWithInitializer(edit.Key) == IsDeclarationWithInitializer(edit.Value));
bool isConstructorWithMemberInitializers;
if ((isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(edit.Value)) ||
IsDeclarationWithInitializer(edit.Value))
{
if (DeferConstructorEdit(
oldContainingType,
newContainingType,
SemanticEditKind.Update,
edit.Value,
newSymbol,
newModel,
isConstructorWithMemberInitializers,
ref syntaxMap,
ref instanceConstructorEdits,
ref staticConstructorEdits,
diagnostics,
cancellationToken))
{
// Don't add a separate semantic edit for a field/property with an initializer.
// All edits of initializers will be aggregated to edits of constructors where these initializers are emitted.
continue;
}
}
semanticEdits.Add(new SemanticEdit(SemanticEditKind.Update, oldSymbol, newSymbol, syntaxMap, isActiveMember));
newSymbolsWithEdit.Add(newSymbol);
}
if (instanceConstructorEdits != null)
{
AddConstructorEdits(
instanceConstructorEdits,
editScript.Match,
oldText,
oldModel,
oldActiveStatements,
newSymbolsWithEdit,
isStatic: false,
semanticEdits: semanticEdits,
diagnostics: diagnostics,
cancellationToken: cancellationToken);
}
if (staticConstructorEdits != null)
{
AddConstructorEdits(
staticConstructorEdits,
editScript.Match,
oldText,
oldModel,
oldActiveStatements,
newSymbolsWithEdit,
isStatic: true,
semanticEdits: semanticEdits,
diagnostics: diagnostics,
cancellationToken: cancellationToken);
}
}