@slsy:
https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/volume/persistentvolume/pv_controller.go#L750 Trudno mi powiedzieć, czy to dobre podejście dla tego problemu, ale na pewno jest to jakiś argument do dyskusji
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
to zawsze spotykam tego typu kod - pełno ifków, pętelek, zagnieżdżeń
nawet teraz otworzyłem pierwszy lepszy plik w repo Linuxa i mam: (niżej)
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?
struct afs_vlserver_list *afs_parse_text_addrs(struct afs_net *net,
const char *text, size_t len,
char delim,
unsigned short service,
unsigned short port)
{
struct afs_vlserver_list *vllist;
struct afs_addr_list *alist;
const char *p, *end = text + len;
const char *problem;
unsigned int nr = 0;
int ret = -ENOMEM;
_enter("%*.*s,%c", (int)len, (int)len, text, delim);
if (!len) {
_leave(" = -EDESTADDRREQ [empty]");
return ERR_PTR(-EDESTADDRREQ);
}
if (delim == ':' && (memchr(text, ',', len) || !memchr(text, '.', len)))
delim = ',';
/* Count the addresses */
p = text;
do {
if (!*p) {
problem = "nul";
goto inval;
}
if (*p == delim)
continue;
nr++;
if (*p == '[') {
p++;
if (p == end) {
problem = "brace1";
goto inval;
}
p = memchr(p, ']', end - p);
if (!p) {
problem = "brace2";
goto inval;
}
p++;
if (p >= end)
break;
}
p = memchr(p, delim, end - p);
if (!p)
break;
p++;
} while (p < end);
_debug("%u/%u addresses", nr, AFS_MAX_ADDRESSES);
vllist = afs_alloc_vlserver_list(1);
if (!vllist)
return ERR_PTR(-ENOMEM);
vllist->nr_servers = 1;
vllist->servers[0].server = afs_alloc_vlserver("<dummy>", 7, AFS_VL_PORT);
if (!vllist->servers[0].server)
goto error_vl;
alist = afs_alloc_addrlist(nr, service, AFS_VL_PORT);
if (!alist)
goto error;
/* Extract the addresses */
p = text;
do {
const char *q, *stop;
unsigned int xport = port;
__be32 x[4];
int family;
if (*p == delim) {
p++;
continue;
}
if (*p == '[') {
p++;
q = memchr(p, ']', end - p);
} else {
for (q = p; q < end; q++)
if (*q == '+' || *q == delim)
break;
}
if (in4_pton(p, q - p, (u8 *)&x[0], -1, &stop)) {
family = AF_INET;
} else if (in6_pton(p, q - p, (u8 *)x, -1, &stop)) {
family = AF_INET6;
} else {
problem = "family";
goto bad_address;
}
p = q;
if (stop != p) {
problem = "nostop";
goto bad_address;
}
if (q < end && *q == ']')
p++;
if (p < end) {
if (*p == '+') {
/* Port number specification "+1234" */
xport = 0;
p++;
if (p >= end || !isdigit(*p)) {
problem = "port";
goto bad_address;
}
do {
xport *= 10;
xport += *p - '0';
if (xport > 65535) {
problem = "pval";
goto bad_address;
}
p++;
} while (p < end && isdigit(*p));
} else if (*p == delim) {
p++;
} else {
problem = "weird";
goto bad_address;
}
}
if (family == AF_INET)
afs_merge_fs_addr4(alist, x[0], xport);
else
afs_merge_fs_addr6(alist, x, xport);
} while (p < end);
rcu_assign_pointer(vllist->servers[0].server->addresses, alist);
_leave(" = [nr %u]", alist->nr_addrs);
return vllist;
inval:
_leave(" = -EINVAL [%s %zu %*.*s]",
problem, p - text, (int)len, (int)len, text);
return ERR_PTR(-EINVAL);
bad_address:
_leave(" = -EINVAL [%s %zu %*.*s]",
problem, p - text, (int)len, (int)len, text);
ret = -EINVAL;
error:
afs_put_addrlist(alist);
error_vl:
afs_put_vlserverlist(net, vllist);
return ERR_PTR(ret);
}
Albo jakiś losowy fragment z Roslyna
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);
}
}