Ten przykład mi się podoba, bo jest to prawdziwy kod, używany na produkcji w złożonym systemie, a nawet pewnie jakiś core
Chyba zawsze, niezależnie gdzie patrzę - base class libraries, kod frameworka, runtime, kompilatora czy chociażby Linuxa
A zatem co, czy ten kod piszą słabi ludzie czy co? Dlaczego clean code, wydzielanie funkcyjek nie jest tak popularne w prawdziwym sofcie, który na dodatek jest OSS, więc każdy widzi?
Kopiuj
private async Task<ImmutableArray<SemanticEditInfo>> AnalyzeSemanticsAsync(
EditScript<SyntaxNode> editScript,
IReadOnlyDictionary<SyntaxNode, EditKind> editMap,
ImmutableArray<UnmappedActiveStatement> oldActiveStatements,
ImmutableArray<LinePositionSpan> newActiveStatementSpans,
IReadOnlyList<(SyntaxNode OldNode, SyntaxNode NewNode, TextSpan DiagnosticSpan)> triviaEdits,
Project oldProject,
Document? oldDocument,
Document newDocument,
SourceText newText,
ArrayBuilder<RudeEditDiagnostic> diagnostics,
ImmutableArray<ActiveStatement>.Builder newActiveStatements,
ImmutableArray<ImmutableArray<SourceFileSpan>>.Builder newExceptionRegions,
EditAndContinueCapabilities capabilities,
bool inBreakState,
CancellationToken cancellationToken)
{
Debug.Assert(inBreakState || newActiveStatementSpans.IsEmpty);
if (editScript.Edits.Length == 0 && triviaEdits.Count == 0)
{
return ImmutableArray<SemanticEditInfo>.Empty;
}
// { new type -> constructor update }
PooledDictionary<INamedTypeSymbol, ConstructorEdit>? instanceConstructorEdits = null;
PooledDictionary<INamedTypeSymbol, ConstructorEdit>? staticConstructorEdits = null;
var oldModel = (oldDocument != null) ? await oldDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false) : null;
var newModel = await newDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var oldCompilation = oldModel?.Compilation ?? await oldProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
var newCompilation = newModel.Compilation;
using var _1 = PooledHashSet<ISymbol>.GetInstance(out var processedSymbols);
using var _2 = ArrayBuilder<SemanticEditInfo>.GetInstance(out var semanticEdits);
try
{
INamedTypeSymbol? lazyLayoutAttribute = null;
foreach (var edit in editScript.Edits)
{
cancellationToken.ThrowIfCancellationRequested();
if (edit.Kind == EditKind.Move)
{
// Move is either a Rude Edit and already reported in syntax analysis, or has no semantic effect.
// For example, in VB we allow move from field multi-declaration.
// "Dim a, b As Integer" -> "Dim a As Integer" (update) and "Dim b As Integer" (move)
continue;
}
if (edit.Kind == EditKind.Reorder)
{
// Currently we don't do any semantic checks for reordering
// and we don't need to report them to the compiler either.
// Consider: Currently symbol ordering changes are not reflected in metadata (Reflection will report original order).
// Consider: Reordering of fields is not allowed since it changes the layout of the type.
// This ordering should however not matter unless the type has explicit layout so we might want to allow it.
// We do not check changes to the order if they occur across multiple documents (the containing type is partial).
Debug.Assert(!IsDeclarationWithInitializer(edit.OldNode) && !IsDeclarationWithInitializer(edit.NewNode));
continue;
}
foreach (var symbolEdits in GetSymbolEdits(edit.Kind, edit.OldNode, edit.NewNode, oldModel, newModel, editMap, cancellationToken))
{
Func<SyntaxNode, SyntaxNode?>? syntaxMap;
SemanticEditKind editKind;
var (oldSymbol, newSymbol, syntacticEditKind) = symbolEdits;
var symbol = newSymbol ?? oldSymbol;
Contract.ThrowIfNull(symbol);
if (!processedSymbols.Add(symbol))
{
continue;
}
var symbolKey = SymbolKey.Create(symbol, cancellationToken);
// Ignore ambiguous resolution result - it may happen if there are semantic errors in the compilation.
oldSymbol ??= symbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
newSymbol ??= symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
var (oldDeclaration, newDeclaration) = GetSymbolDeclarationNodes(oldSymbol, newSymbol, edit.OldNode, edit.NewNode);
// The syntax change implies an update of the associated symbol but the old/new symbol does not actually exist.
// Treat the edit as Insert/Delete. This may happen e.g. when all C# global statements are removed, the first one is added or they are moved to another file.
if (syntacticEditKind == EditKind.Update)
{
if (oldSymbol == null || oldDeclaration != null && oldDeclaration.SyntaxTree != oldModel?.SyntaxTree)
{
syntacticEditKind = EditKind.Insert;
}
else if (newSymbol == null || newDeclaration != null && newDeclaration.SyntaxTree != newModel.SyntaxTree)
{
syntacticEditKind = EditKind.Delete;
}
}
if (!inBreakState)
{
// Delete/insert/update edit of a member of a reloadable type (including nested types) results in Replace edit of the containing type.
// If a Delete edit is part of delete-insert operation (member moved to a different partial type declaration or to a different file)
// skip producing Replace semantic edit for this Delete edit as one will be reported by the corresponding Insert edit.
var oldContainingType = oldSymbol?.ContainingType;
var newContainingType = newSymbol?.ContainingType;
var containingType = newContainingType ?? oldContainingType;
if (containingType != null && (syntacticEditKind != EditKind.Delete || newSymbol == null))
{
var containingTypeSymbolKey = SymbolKey.Create(containingType, cancellationToken);
oldContainingType ??= (INamedTypeSymbol?)containingTypeSymbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
newContainingType ??= (INamedTypeSymbol?)containingTypeSymbolKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
if (oldContainingType != null && newContainingType != null && IsReloadable(oldContainingType))
{
if (processedSymbols.Add(newContainingType))
{
if (capabilities.HasFlag(EditAndContinueCapabilities.NewTypeDefinition))
{
semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Replace, containingTypeSymbolKey, syntaxMap: null, syntaxMapTree: null,
IsPartialEdit(oldContainingType, newContainingType, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? containingTypeSymbolKey : null));
}
else
{
ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, newContainingType, newDeclaration, cancellationToken);
}
}
continue;
}
}
var oldType = oldSymbol as INamedTypeSymbol;
var newType = newSymbol as INamedTypeSymbol;
// Deleting a reloadable type is a rude edit, reported the same as for non-reloadable.
// Adding a reloadable type is a standard type addition (TODO: unless added to a reloadable type?).
// Making reloadable attribute non-reloadable results in a new version of the type that is
// not reloadable but does not update the old version in-place.
if (syntacticEditKind != EditKind.Delete && oldType != null && newType != null && IsReloadable(oldType))
{
if (symbol == newType || processedSymbols.Add(newType))
{
if (oldType.Name != newType.Name)
{
// https://github.com/dotnet/roslyn/issues/54886
ReportUpdateRudeEdit(diagnostics, RudeEditKind.Renamed, newType, newDeclaration, cancellationToken);
}
else if (oldType.Arity != newType.Arity)
{
// https://github.com/dotnet/roslyn/issues/54881
ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingTypeParameters, newType, newDeclaration, cancellationToken);
}
else if (!capabilities.HasFlag(EditAndContinueCapabilities.NewTypeDefinition))
{
ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, newType, newDeclaration, cancellationToken);
}
else
{
semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Replace, symbolKey, syntaxMap: null, syntaxMapTree: null,
IsPartialEdit(oldType, newType, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? symbolKey : null));
}
}
continue;
}
}
switch (syntacticEditKind)
{
case EditKind.Delete:
{
Contract.ThrowIfNull(oldModel);
Contract.ThrowIfNull(oldSymbol);
Contract.ThrowIfNull(oldDeclaration);
var activeStatementIndices = GetOverlappingActiveStatements(oldDeclaration, oldActiveStatements);
var hasActiveStatement = activeStatementIndices.Any();
// TODO: if the member isn't a field/property we should return empty span.
// We need to adjust the tracking span design and UpdateUneditedSpans to account for such empty spans.
if (hasActiveStatement)
{
var newSpan = IsDeclarationWithInitializer(oldDeclaration) ?
GetDeletedNodeActiveSpan(editScript.Match.Matches, oldDeclaration) :
GetDeletedNodeDiagnosticSpan(editScript.Match.Matches, oldDeclaration);
foreach (var index in activeStatementIndices)
{
Debug.Assert(newActiveStatements[index] is null);
newActiveStatements[index] = GetActiveStatementWithSpan(oldActiveStatements[index], editScript.Match.NewRoot.SyntaxTree, newSpan, diagnostics, cancellationToken);
newExceptionRegions[index] = ImmutableArray<SourceFileSpan>.Empty;
}
}
syntaxMap = null;
editKind = SemanticEditKind.Delete;
// Check if the declaration has been moved from one document to another.
if (newSymbol != null && !(newSymbol is IMethodSymbol newMethod && newMethod.IsPartialDefinition))
{
// Symbol has actually not been deleted but rather moved to another document, another partial type declaration
// or replaced with an implicitly generated one (e.g. parameterless constructor, auto-generated record methods, etc.)
// Report rude edit if the deleted code contains active statements.
// TODO (https://github.com/dotnet/roslyn/issues/51177):
// Only report rude edit when replacing member with an implicit one if it has an active statement.
// We might be able to support moving active members but we would need to
// 1) Move AnalyzeChangedMemberBody from Insert to Delete
// 2) Handle active statements that moved to a different document in ActiveStatementTrackingService
// 3) The debugger's ManagedActiveStatementUpdate might need another field indicating the source file path.
if (hasActiveStatement)
{
ReportDeletedMemberRudeEdit(diagnostics, oldSymbol, newCompilation, RudeEditKind.DeleteActiveStatement, cancellationToken);
continue;
}
if (!newSymbol.IsImplicitlyDeclared)
{
// Ignore the delete. The new symbol is explicitly declared and thus there will be an insert edit that will issue a semantic update.
// Note that this could also be the case for deleting properties of records, but they will be handled when we see
// their accessors below.
continue;
}
if (IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(oldDeclaration, newSymbol.ContainingType, out var isFirst))
{
// Defer a constructor edit to cover the property initializer changing
DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration: null, syntaxMap, oldSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
// If there was no body deleted then we are done since the compiler generated property also has no body
if (TryGetDeclarationBody(oldDeclaration) is null)
{
continue;
}
// If there was a body, then the backing field of the property will be affected so we
// need to issue edits for the synthezied members.
// We only need to do this once though.
if (isFirst)
{
AddEditsForSynthesizedRecordMembers(newCompilation, newSymbol.ContainingType, semanticEdits, cancellationToken);
}
}
// If a constructor is deleted and replaced by an implicit one the update needs to aggregate updates to all data member initializers,
// or if a property is deleted that is part of a records primary constructor, which is effectivelly moving from an explicit to implicit
// initializer.
if (IsConstructorWithMemberInitializers(oldDeclaration))
{
processedSymbols.Remove(oldSymbol);
DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration: null, syntaxMap, oldSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
continue;
}
// there is no insert edit for an implicit declaration, therefore we need to issue an update:
editKind = SemanticEditKind.Update;
}
else
{
var diagnosticSpan = GetDeletedNodeDiagnosticSpan(editScript.Match.Matches, oldDeclaration);
// If we got here for a global statement then the actual edit is a delete of the synthesized Main method
if (IsGlobalMain(oldSymbol))
{
diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.Delete, diagnosticSpan, edit.OldNode, new[] { GetDisplayName(edit.OldNode, EditKind.Delete) }));
continue;
}
// If the associated member declaration (accessor -> property/indexer/event, parameter -> method) has also been deleted skip
// the delete of the symbol as it will be deleted by the delete of the associated member.
//
// Associated member declarations must be in the same document as the symbol, so we don't need to resolve their symbol.
// In some cases the symbol even can't be resolved unambiguously. Consider e.g. resolving a method with its parameter deleted -
// we wouldn't know which overload to resolve to.
if (TryGetAssociatedMemberDeclaration(oldDeclaration, out var oldAssociatedMemberDeclaration))
{
if (HasEdit(editMap, oldAssociatedMemberDeclaration, EditKind.Delete))
{
continue;
}
}
else if (oldSymbol.ContainingType != null)
{
// Check if the symbol being deleted is a member of a type that's also being deleted.
// If so, skip the member deletion and only report the containing symbol deletion.
var containingSymbolKey = SymbolKey.Create(oldSymbol.ContainingType, cancellationToken);
var newContainingSymbol = containingSymbolKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
if (newContainingSymbol == null)
{
continue;
}
}
// deleting symbol is not allowed
diagnostics.Add(new RudeEditDiagnostic(
RudeEditKind.Delete,
diagnosticSpan,
oldDeclaration,
new[]
{
string.Format(FeaturesResources.member_kind_and_name,
GetDisplayName(oldDeclaration, EditKind.Delete),
oldSymbol.ToDisplayString(diagnosticSpan.IsEmpty ? s_fullyQualifiedMemberDisplayFormat : s_unqualifiedMemberDisplayFormat))
}));
continue;
}
}
break;
case EditKind.Insert:
{
Contract.ThrowIfNull(newModel);
Contract.ThrowIfNull(newSymbol);
Contract.ThrowIfNull(newDeclaration);
syntaxMap = null;
editKind = SemanticEditKind.Insert;
INamedTypeSymbol? oldContainingType;
var newContainingType = newSymbol.ContainingType;
// Check if the declaration has been moved from one document to another.
if (oldSymbol != null)
{
// Symbol has actually not been inserted but rather moved between documents or partial type declarations,
// or is replacing an implicitly generated one (e.g. parameterless constructor, auto-generated record methods, etc.)
oldContainingType = oldSymbol.ContainingType;
if (oldSymbol.IsImplicitlyDeclared)
{
// If a user explicitly implements a member of a record then we want to issue an update, not an insert.
if (oldSymbol.DeclaringSyntaxReferences.Length == 1)
{
Contract.ThrowIfNull(oldDeclaration);
ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldDeclaration, newDeclaration, oldSymbol, newSymbol, capabilities, cancellationToken);
if (IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(newDeclaration, newContainingType, out var isFirst))
{
// If there is no body declared we can skip it entirely because for a property accessor
// it matches what the compiler would have previously implicitly implemented.
if (TryGetDeclarationBody(newDeclaration) is null)
{
continue;
}
// If there was a body, then the backing field of the property will be affected so we
// need to issue edits for the synthezied members. Only need to do it once.
if (isFirst)
{
AddEditsForSynthesizedRecordMembers(newCompilation, newContainingType, semanticEdits, cancellationToken);
}
}
editKind = SemanticEditKind.Update;
}
}
else if (oldSymbol.DeclaringSyntaxReferences.Length == 1 && newSymbol.DeclaringSyntaxReferences.Length == 1)
{
Contract.ThrowIfNull(oldDeclaration);
// Handles partial methods and explicitly implemented properties that implement positional parameters of records
// We ignore partial method definition parts when processing edits (GetSymbolForEdit).
// The only declaration in compilation without syntax errors that can have multiple declaring references is a type declaration.
// We can therefore ignore any symbols that have more than one declaration.
ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, newDeclaration, newModel, ref lazyLayoutAttribute);
// Compare the old declaration syntax of the symbol with its new declaration and report rude edits
// if it changed in any way that's not allowed.
ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldDeclaration, newDeclaration, oldSymbol, newSymbol, capabilities, cancellationToken);
var oldBody = TryGetDeclarationBody(oldDeclaration);
if (oldBody != null)
{
// The old symbol's declaration syntax may be located in a different document than the old version of the current document.
var oldSyntaxDocument = oldProject.Solution.GetRequiredDocument(oldDeclaration.SyntaxTree);
var oldSyntaxModel = await oldSyntaxDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var oldSyntaxText = await oldSyntaxDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
var newBody = TryGetDeclarationBody(newDeclaration);
// Skip analysis of active statements. We already report rude edit for removal of code containing
// active statements in the old declaration and don't currently support moving active statements.
AnalyzeChangedMemberBody(
oldDeclaration,
newDeclaration,
oldBody,
newBody,
oldSyntaxModel,
newModel,
oldSymbol,
newSymbol,
newText,
oldActiveStatements: ImmutableArray<UnmappedActiveStatement>.Empty,
newActiveStatementSpans: ImmutableArray<LinePositionSpan>.Empty,
capabilities: capabilities,
newActiveStatements,
newExceptionRegions,
diagnostics,
out syntaxMap,
cancellationToken);
}
// 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.
var isNewConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration);
var isDeclarationWithInitializer = IsDeclarationWithInitializer(oldDeclaration) || IsDeclarationWithInitializer(newDeclaration);
var isRecordPrimaryConstructorParameter = IsRecordPrimaryConstructorParameter(oldDeclaration);
if (isNewConstructorWithMemberInitializers || isDeclarationWithInitializer || isRecordPrimaryConstructorParameter)
{
if (isNewConstructorWithMemberInitializers)
{
processedSymbols.Remove(newSymbol);
}
if (isDeclarationWithInitializer)
{
AnalyzeSymbolUpdate(oldSymbol, newSymbol, edit.NewNode, newCompilation, editScript.Match, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken);
}
DeferConstructorEdit(oldSymbol.ContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
// Don't add a separate semantic edit.
// Updates of data members with initializers and constructors that emit initializers will be aggregated and added later.
continue;
}
editKind = SemanticEditKind.Update;
}
else
{
editKind = SemanticEditKind.Update;
}
}
else if (TryGetAssociatedMemberDeclaration(newDeclaration, out var newAssociatedMemberDeclaration) &&
HasEdit(editMap, newAssociatedMemberDeclaration, EditKind.Insert))
{
// If the symbol is an accessor and the containing property/indexer/event declaration has also been inserted skip
// the insert of the accessor as it will be inserted by the property/indexer/event.
continue;
}
else if (newSymbol is IParameterSymbol or ITypeParameterSymbol)
{
diagnostics.Add(new RudeEditDiagnostic(
RudeEditKind.Insert,
GetDiagnosticSpan(newDeclaration, EditKind.Insert),
newDeclaration,
arguments: new[] { GetDisplayName(newDeclaration, EditKind.Insert) }));
continue;
}
else if (newContainingType != null && !IsGlobalMain(newSymbol))
{
// The edit actually adds a new symbol into an existing or a new type.
var containingSymbolKey = SymbolKey.Create(newContainingType, cancellationToken);
oldContainingType = containingSymbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol as INamedTypeSymbol;
if (oldContainingType != null && !CanAddNewMember(newSymbol, capabilities))
{
diagnostics.Add(new RudeEditDiagnostic(
RudeEditKind.InsertNotSupportedByRuntime,
GetDiagnosticSpan(newDeclaration, EditKind.Insert),
newDeclaration,
arguments: new[] { GetDisplayName(newDeclaration, EditKind.Insert) }));
}
// Check rude edits for each member even if it is inserted into a new type.
ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, newDeclaration, insertingIntoExistingContainingType: oldContainingType != null);
if (oldContainingType == null)
{
// Insertion of a new symbol into a new type.
// We'll produce a single insert edit for the entire type.
continue;
}
// Report rude edits for changes to data member changes of a type with an explicit layout.
// We disallow moving a data member of a partial type with explicit layout even when it actually does not change the layout.
// We could compare the exact order of the members but the scenario is unlikely to occur.
ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, newDeclaration, newModel, ref lazyLayoutAttribute);
// If a property or field is added to a record then the implicit constructors change,
// and we need to mark a number of other synthesized members as having changed.
if (newSymbol is IPropertySymbol or IFieldSymbol && newContainingType.IsRecord)
{
DeferConstructorEdit(oldContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
AddEditsForSynthesizedRecordMembers(newCompilation, newContainingType, semanticEdits, cancellationToken);
}
}
else
{
// adds a new top-level type, or a global statement where none existed before, which is
// therefore inserting the <Program>$ type
Contract.ThrowIfFalse(newSymbol is INamedTypeSymbol || IsGlobalMain(newSymbol));
if (!capabilities.HasFlag(EditAndContinueCapabilities.NewTypeDefinition))
{
diagnostics.Add(new RudeEditDiagnostic(
RudeEditKind.InsertNotSupportedByRuntime,
GetDiagnosticSpan(newDeclaration, EditKind.Insert),
newDeclaration,
arguments: new[] { GetDisplayName(newDeclaration, EditKind.Insert) }));
}
oldContainingType = null;
ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, newDeclaration, insertingIntoExistingContainingType: false);
}
var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration);
if (isConstructorWithMemberInitializers || IsDeclarationWithInitializer(newDeclaration))
{
Contract.ThrowIfNull(newContainingType);
Contract.ThrowIfNull(oldContainingType);
// TODO (bug https://github.com/dotnet/roslyn/issues/2504)
if (isConstructorWithMemberInitializers &&
editKind == SemanticEditKind.Insert &&
IsPartial(newContainingType) &&
HasMemberInitializerContainingLambda(oldContainingType, newSymbol.IsStatic, cancellationToken))
{
// rude edit: Adding a constructor to a type with a field or property initializer that contains an anonymous function
diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, GetDiagnosticSpan(newDeclaration, EditKind.Insert)));
break;
}
DeferConstructorEdit(oldContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
if (isConstructorWithMemberInitializers)
{
processedSymbols.Remove(newSymbol);
}
if (isConstructorWithMemberInitializers || editKind == SemanticEditKind.Update)
{
// Don't add a separate semantic edit.
// Edits of data members with initializers and constructors that emit initializers will be aggregated and added later.
continue;
}
// A semantic edit to create the field/property is gonna be added.
Contract.ThrowIfFalse(editKind == SemanticEditKind.Insert);
}
}
break;
case EditKind.Update:
{
Contract.ThrowIfNull(oldModel);
Contract.ThrowIfNull(newModel);
Contract.ThrowIfNull(oldSymbol);
Contract.ThrowIfNull(newSymbol);
editKind = SemanticEditKind.Update;
syntaxMap = null;
// Partial type declarations and their type parameters.
if (oldSymbol.DeclaringSyntaxReferences.Length != 1 && newSymbol.DeclaringSyntaxReferences.Length != 1)
{
break;
}
Contract.ThrowIfNull(oldDeclaration);
Contract.ThrowIfNull(newDeclaration);
var oldBody = TryGetDeclarationBody(oldDeclaration);
if (oldBody != null)
{
var newBody = TryGetDeclarationBody(newDeclaration);
AnalyzeChangedMemberBody(
oldDeclaration,
newDeclaration,
oldBody,
newBody,
oldModel,
newModel,
oldSymbol,
newSymbol,
newText,
oldActiveStatements,
newActiveStatementSpans,
capabilities,
newActiveStatements,
newExceptionRegions,
diagnostics,
out syntaxMap,
cancellationToken);
}
// 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.
var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration);
var isDeclarationWithInitializer = IsDeclarationWithInitializer(oldDeclaration) || IsDeclarationWithInitializer(newDeclaration);
if (isConstructorWithMemberInitializers || isDeclarationWithInitializer)
{
if (isConstructorWithMemberInitializers)
{
processedSymbols.Remove(newSymbol);
}
if (isDeclarationWithInitializer)
{
AnalyzeSymbolUpdate(oldSymbol, newSymbol, edit.NewNode, newCompilation, editScript.Match, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken);
}
DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
// Don't add a separate semantic edit.
// Updates of data members with initializers and constructors that emit initializers will be aggregated and added later.
continue;
}
}
break;
default:
throw ExceptionUtilities.UnexpectedValue(edit.Kind);
}
Contract.ThrowIfFalse(editKind is SemanticEditKind.Update or SemanticEditKind.Insert);
if (editKind == SemanticEditKind.Update)
{
Contract.ThrowIfNull(oldSymbol);
AnalyzeSymbolUpdate(oldSymbol, newSymbol, edit.NewNode, newCompilation, editScript.Match, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken);
if (newSymbol is INamedTypeSymbol or IFieldSymbol or IPropertySymbol or IEventSymbol or IParameterSymbol or ITypeParameterSymbol)
{
continue;
}
}
semanticEdits.Add(new SemanticEditInfo(editKind, symbolKey, syntaxMap, syntaxMapTree: null,
IsPartialEdit(oldSymbol, newSymbol, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? symbolKey : null));
}
}
foreach (var (oldEditNode, newEditNode, diagnosticSpan) in triviaEdits)
{
Contract.ThrowIfNull(oldModel);
Contract.ThrowIfNull(newModel);
foreach (var (oldSymbol, newSymbol, editKind) in GetSymbolEdits(EditKind.Update, oldEditNode, newEditNode, oldModel, newModel, editMap, cancellationToken))
{
// Trivia edits are only calculated for member bodies and each member has a symbol.
Contract.ThrowIfNull(newSymbol);
Contract.ThrowIfNull(oldSymbol);
if (!processedSymbols.Add(newSymbol))
{
// symbol already processed
continue;
}
var (oldDeclaration, newDeclaration) = GetSymbolDeclarationNodes(oldSymbol, newSymbol, oldEditNode, newEditNode);
Contract.ThrowIfNull(oldDeclaration);
Contract.ThrowIfNull(newDeclaration);
var oldContainingType = oldSymbol.ContainingType;
var newContainingType = newSymbol.ContainingType;
Contract.ThrowIfNull(oldContainingType);
Contract.ThrowIfNull(newContainingType);
if (IsReloadable(oldContainingType))
{
if (processedSymbols.Add(newContainingType))
{
if (capabilities.HasFlag(EditAndContinueCapabilities.NewTypeDefinition))
{
var containingTypeSymbolKey = SymbolKey.Create(oldContainingType, cancellationToken);
semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Replace, containingTypeSymbolKey, syntaxMap: null, syntaxMapTree: null,
IsPartialEdit(oldContainingType, newContainingType, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? containingTypeSymbolKey : null));
}
else
{
ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, newContainingType, newDeclaration, cancellationToken);
}
}
continue;
}
// We need to provide syntax map to the compiler if the member is active (see member update above):
var isActiveMember =
GetOverlappingActiveStatements(oldDeclaration, oldActiveStatements).Any() ||
IsStateMachineMethod(oldDeclaration) ||
ContainsLambda(oldDeclaration);
var syntaxMap = isActiveMember ? CreateSyntaxMapForEquivalentNodes(oldDeclaration, newDeclaration) : null;
// only trivia changed:
Contract.ThrowIfFalse(IsConstructorWithMemberInitializers(oldDeclaration) == IsConstructorWithMemberInitializers(newDeclaration));
Contract.ThrowIfFalse(IsDeclarationWithInitializer(oldDeclaration) == IsDeclarationWithInitializer(newDeclaration));
var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration);
var isDeclarationWithInitializer = IsDeclarationWithInitializer(newDeclaration);
if (isConstructorWithMemberInitializers || isDeclarationWithInitializer)
{
// TODO: only create syntax map if any field initializers are active/contain lambdas or this is a partial type
syntaxMap ??= CreateSyntaxMapForEquivalentNodes(oldDeclaration, newDeclaration);
if (isConstructorWithMemberInitializers)
{
processedSymbols.Remove(newSymbol);
}
DeferConstructorEdit(oldContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
// Don't add a separate semantic edit.
// Updates of data members with initializers and constructors that emit initializers will be aggregated and added later.
continue;
}
ReportMemberBodyUpdateRudeEdits(diagnostics, newDeclaration, diagnosticSpan);
// updating generic methods and types
if (InGenericContext(oldSymbol, out var oldIsGenericMethod))
{
var rudeEdit = oldIsGenericMethod ? RudeEditKind.GenericMethodTriviaUpdate : RudeEditKind.GenericTypeTriviaUpdate;
diagnostics.Add(new RudeEditDiagnostic(rudeEdit, diagnosticSpan, newEditNode, new[] { GetDisplayName(newEditNode) }));
continue;
}
var symbolKey = SymbolKey.Create(newSymbol, cancellationToken);
semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap, syntaxMapTree: null,
IsPartialEdit(oldSymbol, newSymbol, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? symbolKey : null));
}
}
if (instanceConstructorEdits != null)
{
AddConstructorEdits(
instanceConstructorEdits,
editScript.Match,
oldModel,
oldCompilation,
newCompilation,
processedSymbols,
capabilities,
isStatic: false,
semanticEdits,
diagnostics,
cancellationToken);
}
if (staticConstructorEdits != null)
{
AddConstructorEdits(
staticConstructorEdits,
editScript.Match,
oldModel,
oldCompilation,
newCompilation,
processedSymbols,
capabilities,
isStatic: true,
semanticEdits,
diagnostics,
cancellationToken);
}
}
finally
{
instanceConstructorEdits?.Free();
staticConstructorEdits?.Free();
}
return semanticEdits.Distinct(SemanticEditInfoComparer.Instance).ToImmutableArray();
// If the symbol has a single declaring reference use its syntax node for further analysis.
// Some syntax edits may not be directly associated with the declarations.
// For example, in VB an update to AsNew clause of a multi-variable field declaration results in update to multiple symbols associated
// with the variable declaration. But we need to analyse each symbol's modified identifier separately.
(SyntaxNode? oldDeclaration, SyntaxNode? newDeclaration) GetSymbolDeclarationNodes(ISymbol? oldSymbol, ISymbol? newSymbol, SyntaxNode? oldNode, SyntaxNode? newNode)
{
return (
(oldSymbol != null && oldSymbol.DeclaringSyntaxReferences.Length == 1) ?
GetSymbolDeclarationSyntax(oldSymbol.DeclaringSyntaxReferences.Single(), cancellationToken) : oldNode,
(newSymbol != null && newSymbol.DeclaringSyntaxReferences.Length == 1) ?
GetSymbolDeclarationSyntax(newSymbol.DeclaringSyntaxReferences.Single(), cancellationToken) : newNode);
}
}