|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.Cci;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Symbols;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Emit
{
internal abstract class SymbolChanges
{
/// <summary>
/// Maps definitions being emitted to the corresponding definitions defined in the previous generation (metadata or source).
/// </summary>
private readonly DefinitionMap _definitionMap;
/// <summary>
/// Contains all symbols explicitly updated/added to the source and
/// their containing types and namespaces.
/// </summary>
private readonly IReadOnlyDictionary<ISymbol, SymbolChange> _changes;
/// <summary>
/// A set of symbols whose name emitted to metadata must include a "#{generation}" suffix to avoid naming collisions with existing types.
/// Populated based on semantic edits with <see cref="SemanticEditKind.Replace"/>.
/// </summary>
private readonly ISet<ISymbol> _replacedSymbols;
/// <summary>
/// A set of symbols, from the old compilation, that have been deleted from the new compilation
/// keyed by the containing type from the new compilation.
/// Populated based on semantic edits with <see cref="SemanticEditKind.Delete"/>.
/// </summary>
private readonly IReadOnlyDictionary<ISymbol, ISet<ISymbol>> _deletedMembers;
private readonly Func<ISymbol, bool> _isAddedSymbol;
protected SymbolChanges(DefinitionMap definitionMap, IEnumerable<SemanticEdit> edits, Func<ISymbol, bool> isAddedSymbol)
{
_definitionMap = definitionMap;
_isAddedSymbol = isAddedSymbol;
CalculateChanges(edits, out _changes, out _replacedSymbols, out _deletedMembers);
}
public DefinitionMap DefinitionMap => _definitionMap;
public ImmutableDictionary<ISymbolInternal, ImmutableArray<ISymbolInternal>> GetAllDeletedMembers()
{
var builder = ImmutableDictionary.CreateBuilder<ISymbolInternal, ImmutableArray<ISymbolInternal>>();
foreach (var (type, deletedMembers) in _deletedMembers)
{
if (GetISymbolInternalOrNull(type) is not { } typeSymbol)
{
continue;
}
var internalSymbols = GetDeletedMemberInternalSymbols(deletedMembers, includeMethods: true, includeProperties: true, includeEvents: true);
builder.Add(typeSymbol, internalSymbols);
}
return builder.ToImmutable();
}
private ImmutableArray<ISymbolInternal> GetDeletedMemberInternalSymbols(IDefinition containingType, bool includeMethods, bool includeProperties, bool includeEvents)
{
var containingSymbol = containingType.GetInternalSymbol()?.GetISymbol();
if (containingSymbol is null)
{
return ImmutableArray<ISymbolInternal>.Empty;
}
if (!_deletedMembers.TryGetValue(containingSymbol, out var deleted))
{
return ImmutableArray<ISymbolInternal>.Empty;
}
return GetDeletedMemberInternalSymbols(deleted, includeMethods, includeProperties, includeEvents);
}
private ImmutableArray<ISymbolInternal> GetDeletedMemberInternalSymbols(ISet<ISymbol> deletedMembers, bool includeMethods, bool includeProperties, bool includeEvents)
{
var internalSymbols = ArrayBuilder<ISymbolInternal>.GetInstance();
foreach (var symbol in deletedMembers)
{
if (GetISymbolInternalOrNull(symbol) is { } internalSymbol)
{
if (includeProperties &&
symbol is IMethodSymbol { AssociatedSymbol: IPropertySymbol propertySymbol } &&
(propertySymbol.GetMethod is null || propertySymbol.GetMethod == symbol))
{
var internalPropertySymbol = GetISymbolInternalOrNull(propertySymbol);
if (internalPropertySymbol is not null)
{
internalSymbols.Add(internalPropertySymbol);
}
}
if (includeEvents &&
symbol is IMethodSymbol { AssociatedSymbol: IEventSymbol eventSymbol } &&
eventSymbol.AddMethod == symbol)
{
var internalEventSymbol = GetISymbolInternalOrNull(eventSymbol);
if (internalEventSymbol is not null)
{
internalSymbols.Add(internalEventSymbol);
}
}
if (includeMethods &&
symbol is IMethodSymbol)
{
internalSymbols.Add(internalSymbol);
}
}
}
return internalSymbols.ToImmutableAndFree();
}
public ImmutableArray<ISymbolInternal> GetDeletedMethods(IDefinition containingType)
=> GetDeletedMemberInternalSymbols(containingType, includeMethods: true, includeProperties: false, includeEvents: false);
public ImmutableArray<ISymbolInternal> GetDeletedProperties(IDefinition containingType)
=> GetDeletedMemberInternalSymbols(containingType, includeMethods: false, includeProperties: true, includeEvents: false);
public ImmutableArray<ISymbolInternal> GetDeletedEvents(IDefinition containingType)
=> GetDeletedMemberInternalSymbols(containingType, includeMethods: false, includeProperties: false, includeEvents: true);
public bool IsReplaced(IDefinition definition, bool checkEnclosingTypes = false)
=> definition.GetInternalSymbol() is { } internalSymbol && IsReplaced(internalSymbol.GetISymbol(), checkEnclosingTypes);
public bool IsReplaced(ISymbol symbol, bool checkEnclosingTypes = false)
{
ISymbol? currentSymbol = symbol;
while (currentSymbol != null)
{
if (_replacedSymbols.Contains(currentSymbol))
{
return true;
}
if (!checkEnclosingTypes)
{
return false;
}
currentSymbol = currentSymbol.ContainingType;
}
return false;
}
/// <summary>
/// True if the symbol is a source symbol added during EnC session.
/// The symbol may be declared in any source compilation in the current solution.
/// </summary>
public bool IsAdded(ISymbol symbol)
{
return _isAddedSymbol(symbol);
}
/// <summary>
/// Returns true if the symbol or some child symbol has changed and needs to be compiled.
/// </summary>
public bool RequiresCompilation(ISymbol symbol)
{
return this.GetChange(symbol) != SymbolChange.None;
}
private bool DefinitionExistsInPreviousGeneration(ISymbolInternal symbol)
{
var definition = (IDefinition)symbol.GetCciAdapter();
if (!_definitionMap.DefinitionExists(definition))
{
return false;
}
// Definition map does not consider types that are being replaced,
// hence we need to check - type that is being replaced is not considered
// existing in the previous generation.
var current = symbol.GetISymbol();
do
{
if (_replacedSymbols.Contains(current))
{
return false;
}
current = current.ContainingType;
}
while (current is not null);
return true;
}
public SymbolChange GetChange(IDefinition def)
{
var symbol = def.GetInternalSymbol();
if (symbol is ISynthesizedMethodBodyImplementationSymbol synthesizedSymbol)
{
RoslynDebug.Assert(synthesizedSymbol.Method != null);
var generatorChange = GetChange((IDefinition)synthesizedSymbol.Method.GetCciAdapter());
switch (generatorChange)
{
case SymbolChange.Updated:
// The generator has been updated. Some synthesized members should be reused, others updated or added.
// The container of the synthesized symbol doesn't exist, we need to add the symbol.
// This may happen e.g. for members of a state machine type when a non-iterator method is changed to an iterator.
if (!DefinitionExistsInPreviousGeneration(synthesizedSymbol.ContainingType))
{
return SymbolChange.Added;
}
if (!DefinitionExistsInPreviousGeneration(synthesizedSymbol))
{
// A method was changed to a method containing a lambda, to an iterator, or to an async method.
// The state machine or closure class has been added.
return SymbolChange.Added;
}
// The existing symbol should be reused when the generator is updated,
// not updated since it's form doesn't depend on the content of the generator.
// For example, when an iterator method changes all methods that implement IEnumerable
// but MoveNext can be reused as they are.
if (!synthesizedSymbol.HasMethodBodyDependency)
{
return SymbolChange.None;
}
// If the type produced from the method body existed before then its members are updated.
if (synthesizedSymbol.Kind == SymbolKind.NamedType)
{
return SymbolChange.ContainsChanges;
}
if (synthesizedSymbol.Kind == SymbolKind.Method)
{
// The method body might have been updated.
return SymbolChange.Updated;
}
return SymbolChange.None;
case SymbolChange.Added:
// The method has been added - add the synthesized member as well, unless it already exists.
if (!DefinitionExistsInPreviousGeneration(synthesizedSymbol))
{
return SymbolChange.Added;
}
// If the existing member is a type we need to add new members into it.
// An example is a shared static display class - an added method with static lambda will contribute
// the lambda and cache fields into the shared display class.
if (synthesizedSymbol.Kind == SymbolKind.NamedType)
{
return SymbolChange.ContainsChanges;
}
// Update method.
// An example is a constructor a shared display class - an added method with lambda will contribute
// cache field initialization code into the constructor.
if (synthesizedSymbol.Kind == SymbolKind.Method)
{
return SymbolChange.Updated;
}
// Otherwise, there is nothing to do.
// For example, a static lambda display class cache field.
return SymbolChange.None;
default:
// The method had to change, otherwise the synthesized symbol wouldn't be generated
throw ExceptionUtilities.UnexpectedValue(generatorChange);
}
}
if (symbol is not null)
{
return GetChange(symbol.GetISymbol());
}
// If the def that has no associated internal symbol existed in the previous generation, the def is unchanged
// (although it may contain changed defs); otherwise, it was added.
if (_definitionMap.DefinitionExists(def))
{
return (def is ITypeDefinition) ? SymbolChange.ContainsChanges : SymbolChange.None;
}
return SymbolChange.Added;
}
private SymbolChange GetChange(ISymbol symbol)
{
// In CalculateChanges we always store definitions for partial methods, so we have to
// make sure we do the same thing here when we try to retrieve a change, as the compiler
// associates synthesized methods with the implementation of the method that caused it
// to be generated.
if (symbol is IMethodSymbol method)
{
symbol = method.PartialDefinitionPart ?? symbol;
}
if (_changes.TryGetValue(symbol, out var change))
{
return change;
}
// Calculate change based on change to container.
var container = GetContainingSymbol(symbol);
if (container == null)
{
return SymbolChange.None;
}
var containerChange = GetChange(container);
switch (containerChange)
{
case SymbolChange.Added:
// If container is added then all its members have been added.
return SymbolChange.Added;
case SymbolChange.None:
// If container has no changes then none of its members have any changes.
return SymbolChange.None;
case SymbolChange.Updated:
case SymbolChange.ContainsChanges:
var internalSymbol = GetISymbolInternalOrNull(symbol);
if (internalSymbol is null)
{
return SymbolChange.None;
}
if (internalSymbol.Kind == SymbolKind.Namespace)
{
// If the namespace did not exist in the previous generation, it was added.
// Otherwise the namespace may contain changes.
return _definitionMap.NamespaceExists((INamespace)internalSymbol.GetCciAdapter()) ? SymbolChange.ContainsChanges : SymbolChange.Added;
}
// If the definition did not exist in the previous generation, it was added.
return DefinitionExistsInPreviousGeneration(internalSymbol) ? SymbolChange.None : SymbolChange.Added;
default:
throw ExceptionUtilities.UnexpectedValue(containerChange);
}
}
public SymbolChange GetChangeForPossibleReAddedMember(ITypeDefinitionMember item, Func<ITypeDefinitionMember, bool> definitionExistsInAnyPreviousGeneration)
{
var change = GetChange(item);
return fixChangeIfMemberIsReAdded(item, change, definitionExistsInAnyPreviousGeneration);
SymbolChange fixChangeIfMemberIsReAdded(ITypeDefinitionMember item, SymbolChange change, Func<ITypeDefinitionMember, bool> definitionExistsInAnyPreviousGeneration)
{
// If this is a field that is being added, but it's part of a property or event that has been deleted
// and is now being re-added, we don't want to add the field twice, so we ignore the change.
// Unlike properties and methods, since we can't replace a field with a MissingMethodException
// we don't need to update it at all.
// This also makes sure to check that the field itself is being re-added, because it could be
// a property that is being re-added as an auto-prop, when it wasn't one before, for example.
if (item is IFieldDefinition fieldDefinition &&
GetContainingDefinitionForBackingField(fieldDefinition) is ITypeDefinitionMember containingDef &&
GetChange(containingDef) == SymbolChange.Added &&
definitionExistsInAnyPreviousGeneration(item) &&
fixChangeIfMemberIsReAdded(containingDef, SymbolChange.Added, definitionExistsInAnyPreviousGeneration) == SymbolChange.Updated)
{
return SymbolChange.None;
}
// Otherwise if the item was added, and not replaced, but we can find an existing row id, then treat it
// as an update. This supercedes the other checks for edit types etc. because a method could be
// deleted in a generation, and then "added" in a subsequent one, but that is an update
// even if the previous generation doesn't know about it.
if (change == SymbolChange.Added &&
!IsReplaced(item.ContainingTypeDefinition, checkEnclosingTypes: true) &&
definitionExistsInAnyPreviousGeneration(item))
{
return SymbolChange.Updated;
}
return change;
}
}
protected abstract ISymbolInternal? GetISymbolInternalOrNull(ISymbol symbol);
public IEnumerable<INamespaceTypeDefinition> GetTopLevelSourceTypeDefinitions(EmitContext context)
{
foreach (var symbol in _changes.Keys)
{
var namespaceTypeDef = (GetISymbolInternalOrNull(symbol)?.GetCciAdapter() as ITypeDefinition)?.AsNamespaceTypeDefinition(context);
if (namespaceTypeDef != null)
{
yield return namespaceTypeDef;
}
}
}
/// <summary>
/// Calculate the set of changes up to top-level types. The result
/// will be used as a filter when traversing the module.
///
/// Note that these changes only include user-defined source symbols, not synthesized symbols since those will be
/// generated during lowering of the changed user-defined symbols.
/// </summary>
private static void CalculateChanges(IEnumerable<SemanticEdit> edits, out IReadOnlyDictionary<ISymbol, SymbolChange> changes, out ISet<ISymbol> replaceSymbols, out IReadOnlyDictionary<ISymbol, ISet<ISymbol>> deletedMembers)
{
var changesBuilder = new Dictionary<ISymbol, SymbolChange>();
HashSet<ISymbol>? lazyReplaceSymbolsBuilder = null;
Dictionary<ISymbol, ISet<ISymbol>>? lazyDeletedMembersBuilder = null;
foreach (var edit in edits)
{
SymbolChange change;
switch (edit.Kind)
{
case SemanticEditKind.Update:
change = SymbolChange.Updated;
break;
case SemanticEditKind.Insert:
change = SymbolChange.Added;
break;
case SemanticEditKind.Replace:
Debug.Assert(edit.NewSymbol != null);
(lazyReplaceSymbolsBuilder ??= new HashSet<ISymbol>()).Add(edit.NewSymbol);
change = SymbolChange.Added;
break;
case SemanticEditKind.Delete:
// We allow method deletions only at the moment.
// For deletions NewSymbol is actually containing symbol
if (edit.OldSymbol is IMethodSymbol && edit.NewSymbol is { } newContainingSymbol)
{
Debug.Assert(edit.OldSymbol != null);
lazyDeletedMembersBuilder ??= new();
if (!lazyDeletedMembersBuilder.TryGetValue(newContainingSymbol, out var set))
{
set = new HashSet<ISymbol>();
lazyDeletedMembersBuilder.Add(newContainingSymbol, set);
}
set.Add(edit.OldSymbol);
// We need to make sure we track the containing type of the member being
// deleted, from the new compilation, in case the deletion is the only change.
if (!changesBuilder.ContainsKey(newContainingSymbol))
{
changesBuilder.Add(newContainingSymbol, SymbolChange.ContainsChanges);
AddContainingTypesAndNamespaces(changesBuilder, newContainingSymbol);
}
}
continue;
default:
throw ExceptionUtilities.UnexpectedValue(edit.Kind);
}
var member = edit.NewSymbol;
RoslynDebug.AssertNotNull(member);
// Partial methods are supplied as implementations but recorded
// internally as definitions since definitions are used in emit.
if (member.Kind == SymbolKind.Method)
{
var method = (IMethodSymbol)member;
// Partial methods should be implementations, not definitions.
Debug.Assert(method.PartialImplementationPart == null);
Debug.Assert((edit.OldSymbol == null) || (((IMethodSymbol)edit.OldSymbol).PartialImplementationPart == null));
var definitionPart = method.PartialDefinitionPart;
if (definitionPart != null)
{
member = definitionPart;
}
}
AddContainingTypesAndNamespaces(changesBuilder, member);
changesBuilder.Add(member, change);
}
changes = changesBuilder;
replaceSymbols = lazyReplaceSymbolsBuilder ?? SpecializedCollections.EmptySet<ISymbol>();
deletedMembers = lazyDeletedMembersBuilder ?? SpecializedCollections.EmptyReadOnlyDictionary<ISymbol, ISet<ISymbol>>();
}
private static void AddContainingTypesAndNamespaces(Dictionary<ISymbol, SymbolChange> changes, ISymbol symbol)
{
while (true)
{
var containingSymbol = GetContainingSymbol(symbol);
if (containingSymbol == null || changes.ContainsKey(containingSymbol))
{
return;
}
var change = containingSymbol.Kind is SymbolKind.Property or SymbolKind.Event ?
SymbolChange.Updated : SymbolChange.ContainsChanges;
changes.Add(containingSymbol, change);
symbol = containingSymbol;
}
}
/// <summary>
/// Return the symbol that contains this symbol as far
/// as changes are concerned. For instance, an auto property
/// is considered the containing symbol for the backing
/// field and the accessor methods. By default, the containing
/// symbol is simply Symbol.ContainingSymbol.
/// </summary>
private static ISymbol? GetContainingSymbol(ISymbol symbol)
{
// This approach of walking up the symbol hierarchy towards the
// root, rather than walking down to the leaf symbols, seems
// unreliable. It may be better to walk down using the usual
// emit traversal, but prune the traversal to those types and
// members that are known to contain changes.
var associated = GetAssociatedSymbol(symbol);
if (associated is not null)
{
return associated;
}
symbol = symbol.ContainingSymbol;
if (symbol != null)
{
switch (symbol.Kind)
{
case SymbolKind.NetModule:
case SymbolKind.Assembly:
// These symbols are never part of the changes collection.
return null;
}
}
return symbol;
}
private static ISymbol? GetAssociatedSymbol(ISymbol symbol)
{
switch (symbol.Kind)
{
case SymbolKind.Field:
{
var associated = ((IFieldSymbol)symbol).AssociatedSymbol;
if (associated != null)
{
return associated;
}
}
break;
case SymbolKind.Method:
{
var associated = ((IMethodSymbol)symbol).AssociatedSymbol;
if (associated != null)
{
return associated;
}
}
break;
}
return null;
}
internal IDefinition? GetContainingDefinitionForBackingField(IFieldDefinition fieldDefinition)
{
var field = fieldDefinition.GetInternalSymbol()?.GetISymbol();
if (field is null)
{
return null;
}
var associatedSymbol = GetAssociatedSymbol(field);
if (associatedSymbol is not null)
{
return GetISymbolInternalOrNull(associatedSymbol)?.GetCciAdapter() as IDefinition;
}
return null;
}
}
}
|