| File: Mapping\TypeMapper.cs | Web Access |
| Project: src\sdk\src\Compatibility\ApiCompat\Microsoft.DotNet.ApiCompatibility\Microsoft.DotNet.ApiCompatibility.csproj (Microsoft.DotNet.ApiCompatibility) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using Microsoft.CodeAnalysis; using Microsoft.DotNet.ApiCompatibility.Rules; using Microsoft.DotNet.ApiSymbolExtensions; namespace Microsoft.DotNet.ApiCompatibility.Mapping { /// <summary> /// Object that represents a mapping between two <see cref="ITypeSymbol"/> objects. /// This also holds the nested types as a list of <see cref="ITypeMapper"/> and the members defined within the type /// as a list of <see cref="IMemberMapper"/> /// </summary> /// <param name="ruleRunner">The <see cref="IRuleRunner"/> that compares the type mapper elements.</param> /// <param name="settings">The <see cref="IMapperSettings"/> used to compare the type mapper elements.</param> /// <param name="rightSetSize">The number of elements in the right set to compare.</param> /// <param name="containingNamespace">The containing <see cref="INamespaceMapper"/>.</param> /// <param name="containingType">The containing <see cref="ITypeMapper"/>. <see langword="null" />, if the type doesn't have a containing type.</param> public class TypeMapper(IRuleRunner ruleRunner, IMapperSettings settings, int rightSetSize, INamespaceMapper containingNamespace, ITypeMapper? containingType = null) : ElementMapper<ITypeSymbol>(ruleRunner, settings, rightSetSize), ITypeMapper { private Dictionary<ITypeSymbol, ITypeMapper>? _nestedTypes; private Dictionary<ISymbol, IMemberMapper>? _members; /// <inheritdoc /> public INamespaceMapper ContainingNamespace { get; } = containingNamespace; /// <inheritdoc /> public ITypeMapper? ContainingType { get; } = containingType; internal bool ShouldDiffElement(int rightIndex) { if (ContainingType != null) { Debug.Assert(ContainingType.ShouldDiffMembers); // This should only be called at a point where containingType.ShouldDiffMembers is true // So that means that containingType.Left is not null and we don't need to check. // If containingType.Right only contains one element, we can assume it is not null. return ContainingType.Right.Length == 1 || ContainingType.Right[rightIndex] != null; } return true; } /// <inheritdoc /> public bool ShouldDiffMembers { get { if (Left == null) return false; if (Right.Length == 1 && Right[0] == null) return false; for (int i = 0; i < Right.Length; i++) { if (Right[i] != null) { return true; } } return false; } } /// <inheritdoc /> public IEnumerable<ITypeMapper> GetNestedTypes() { if (_nestedTypes == null) { _nestedTypes = new Dictionary<ITypeSymbol, ITypeMapper>(Settings.SymbolEqualityComparer); AddOrCreateMappers(Left, ElementSide.Left); for (int i = 0; i < Right.Length; i++) { AddOrCreateMappers(Right[i], ElementSide.Right, i); } void AddOrCreateMappers(ITypeSymbol? symbol, ElementSide side, int setIndex = 0) { // Silently return if the element hasn't been added yet. if (symbol == null) { return; } foreach (INamedTypeSymbol nestedType in symbol.GetTypeMembers()) { if (Settings.SymbolFilter.Include(nestedType)) { if (!_nestedTypes.TryGetValue(nestedType, out ITypeMapper? mapper)) { mapper = new TypeMapper(RuleRunner, Settings, Right.Length, ContainingNamespace, this); _nestedTypes.Add(nestedType, mapper); } mapper.AddElement(nestedType, side, setIndex); } } } } return _nestedTypes.Values; } /// <inheritdoc /> public IEnumerable<IMemberMapper> GetMembers() { if (_members == null) { _members = new Dictionary<ISymbol, IMemberMapper>(Settings.SymbolEqualityComparer); AddOrCreateMappers(Left, ElementSide.Left); for (int i = 0; i < Right.Length; i++) { AddOrCreateMappers(Right[i], ElementSide.Right, i); } void AddOrCreateMappers(ITypeSymbol? symbol, ElementSide side, int setIndex = 0) { // Silently return if the element hasn't been added yet. if (symbol == null) { return; } foreach (ISymbol member in symbol.GetMembers()) { // when running without references Roslyn doesn't filter out the special value__ field emitted // for enums. The reason why we should filter it out, is because we could have a case // where one side was loaded with references and one that was loaded without, if that is the case // we would compare __value vs null and emit some warnings. if (Settings.SymbolFilter.Include(member) && member is not ITypeSymbol && !IsSpecialEnumField(member)) { if (!_members.TryGetValue(member, out IMemberMapper? mapper)) { mapper = new MemberMapper(RuleRunner, Settings, Right.Length, this); _members.Add(member, mapper); } mapper.AddElement(member, side, setIndex); } } } } return _members.Values; } private bool IsSpecialEnumField(ISymbol member) => !Settings.WithReferences && member is IFieldSymbol && member.Name == "value__" && // When running without references, Roslyn doesn't set the type kind as enum. Compare by name instead. member.ContainingType.BaseType?.ToComparisonDisplayString() == "System.Enum"; } }