Skip to content

Support multiple rights vs same left scenario with a simple call to ApiComparer #17295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
/// <summary>
/// Class to wrap an Element of T with it's <see cref="MetadataInformation"/>.
/// </summary>
/// <typeparam name="T">The type of the Element that is holded</typeparam>
public class ElementContainer<T>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started with this as a Generic type even though it is now currently used with IAssemblySymbol only as this might be useful to use it to store the elements in the mappers and that way we could customize the diagnostic messages using the metadata information for each element in the mapper.

However I don't know if it is too much overhead and instead just have a setting to customize the left and right names when raising diagnostics.

{
/// <summary>
/// Instantiates a new object with the <paramref name="element"/> and <paramref name="metadataInformation"/> used.
/// </summary>
/// <param name="element">Element to store in the container.</param>
/// <param name="metadataInformation">Metadata related to the element</param>
public ElementContainer(T element, MetadataInformation metadataInformation)
{
Element = element;
MetadataInformation = metadataInformation;
}

/// <summary>
/// The element that the container is holding.
/// </summary>
public T Element { get; private set; }

/// <summary>
/// The metadata associated to the element.
/// </summary>
public MetadataInformation MetadataInformation { get; private set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ public interface IRuleRunner
/// </summary>
/// <typeparam name="T">The underlying type on the mapper.</typeparam>
/// <param name="mapper">The mapper to run the rules on.</param>
/// <returns></returns>
IEnumerable<CompatDifference> Run<T>(ElementMapper<T> mapper);
/// <returns>A list containing the list of differences for each possible combination of
/// (<see cref="ElementMapper{T}.Left"/>, <see cref="ElementMapper{T}.Right"/>).
/// One list of <see cref="CompatDifference"/> per the number of right elements that the <see cref="ElementMapper{T}"/> contains.
/// </returns>
IReadOnlyList<IEnumerable<CompatDifference>> Run<T>(ElementMapper<T> mapper);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ public class AssemblyMapper : ElementMapper<IAssemblySymbol>
/// Instantiates an object with the provided <see cref="ComparingSettings"/>.
/// </summary>
/// <param name="settings">The settings used to diff the elements in the mapper.</param>
public AssemblyMapper(ComparingSettings settings) : base(settings) { }
/// <param name="rightSetSize">The number of elements in the right set to compare.</param>
public AssemblyMapper(ComparingSettings settings, int rightSetSize = 1)
: base(settings, rightSetSize) { }

/// <summary>
/// Gets the mappers for the namespaces contained in <see cref="ElementMapper{T}.Left"/> and <see cref="ElementMapper{T}.Right"/>
Expand All @@ -30,41 +32,59 @@ public IEnumerable<NamespaceMapper> GetNamespaces()
if (_namespaces == null)
{
_namespaces = new Dictionary<INamespaceSymbol, NamespaceMapper>(Settings.EqualityComparer);
Dictionary<INamespaceSymbol, List<INamedTypeSymbol>> typeForwards;
if (Left != null)
AddOrCreateMappers(Left, ElementSide.Left);

if (Right.Length == 1)
{
typeForwards = ResolveTypeForwards(Left, Settings.EqualityComparer);
AddOrCreateMappers(Left.GlobalNamespace, 0);
AddOrCreateMappers(Right[0], ElementSide.Right);
}

if (Right != null)
else
{
typeForwards = ResolveTypeForwards(Right, Settings.EqualityComparer);
AddOrCreateMappers(Right.GlobalNamespace, 1);
for (int i = 0; i < Right.Length; i++)
{
AddOrCreateMappers(Right[i], ElementSide.Right, i);
}
}

void AddOrCreateMappers(INamespaceSymbol ns, int index)
void AddOrCreateMappers(IAssemblySymbol symbol, ElementSide side, int setIndex = 0)
{
if (symbol == null)
{
return;
}

Stack<INamespaceSymbol> stack = new();
stack.Push(ns);
stack.Push(symbol.GlobalNamespace);
while (stack.Count > 0)
{
INamespaceSymbol symbol = stack.Pop();
if (typeForwards.TryGetValue(symbol, out List<INamedTypeSymbol> forwardedTypes) || symbol.GetTypeMembers().Length > 0)
INamespaceSymbol nsSymbol = stack.Pop();
if (nsSymbol.GetTypeMembers().Length > 0)
{
if (!_namespaces.TryGetValue(symbol, out NamespaceMapper mapper))
{
mapper = new NamespaceMapper(Settings);
_namespaces.Add(symbol, mapper);
}

mapper.AddElement(symbol, index);
mapper.AddForwardedTypes(forwardedTypes ?? new List<INamedTypeSymbol>(), index);
AddMapper(nsSymbol);
}

foreach (INamespaceSymbol child in symbol.GetNamespaceMembers())
foreach (INamespaceSymbol child in nsSymbol.GetNamespaceMembers())
stack.Push(child);
}

Dictionary<INamespaceSymbol, List<INamedTypeSymbol>> typeForwards = ResolveTypeForwards(symbol, Settings.EqualityComparer);
foreach (KeyValuePair<INamespaceSymbol, List<INamedTypeSymbol>> kvp in typeForwards)
{
NamespaceMapper mapper = AddMapper(kvp.Key);
mapper.AddForwardedTypes(kvp.Value, side, setIndex);
}

NamespaceMapper AddMapper(INamespaceSymbol ns)
{
if (!_namespaces.TryGetValue(ns, out NamespaceMapper mapper))
{
mapper = new NamespaceMapper(Settings, Right.Length);
_namespaces.Add(ns, mapper);
}

mapper.AddElement(ns, side, setIndex);
return mapper;
}
}

static Dictionary<INamespaceSymbol, List<INamedTypeSymbol>> ResolveTypeForwards(IAssemblySymbol assembly, IEqualityComparer<ISymbol> comparer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ public class AssemblySetMapper : ElementMapper<IEnumerable<IAssemblySymbol>>
/// Instantiates an object with the provided <see cref="ComparingSettings"/>.
/// </summary>
/// <param name="settings">The settings used to diff the elements in the mapper.</param>
public AssemblySetMapper(ComparingSettings settings) : base(settings) { }
/// <param name="rightSetSize">The number of elements in the right set to compare.</param>
public AssemblySetMapper(ComparingSettings settings, int rightSetSize = 1)
: base(settings, rightSetSize) { }

/// <summary>
/// Gets the assembly mappers built from the provided lists of <see cref="IAssemblySymbol"/>.
Expand All @@ -27,27 +29,36 @@ public IEnumerable<AssemblyMapper> GetAssemblies()
if (_assemblies == null)
{
_assemblies = new Dictionary<IAssemblySymbol, AssemblyMapper>(Settings.EqualityComparer);
AddOrCreateMappers(Left, ElementSide.Left);

if (Left != null)
if (Right.Length == 1)
{
AddOrCreateMappers(Left, 0);
AddOrCreateMappers(Right[0], ElementSide.Right);
}

if (Right != null)
else
{
AddOrCreateMappers(Right, 1);
for (int i = 0; i < Right.Length; i++)
{
AddOrCreateMappers(Right[i], ElementSide.Right, i);
}
}

void AddOrCreateMappers(IEnumerable<IAssemblySymbol> elements, int index)
void AddOrCreateMappers(IEnumerable<IAssemblySymbol> symbols, ElementSide side, int setIndex = 0)
{
foreach (IAssemblySymbol assembly in elements)
if (symbols == null)
{
return;
}

foreach (IAssemblySymbol assembly in symbols)
{
if (!_assemblies.TryGetValue(assembly, out AssemblyMapper mapper))
{
mapper = new AssemblyMapper(Settings);
mapper = new AssemblyMapper(Settings, Right.Length);
_assemblies.Add(assembly, mapper);
}
mapper.AddElement(assembly, index);

mapper.AddElement(assembly, side, setIndex);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ namespace Microsoft.DotNet.ApiCompatibility.Abstractions
/// </summary>
public class ElementMapper<T>
{
private IEnumerable<CompatDifference> _differences;
private IReadOnlyList<IEnumerable<CompatDifference>> _differences;

/// <summary>
/// Property representing the Left hand side of the mapping.
/// </summary>
public T Left { get; private set; }

/// <summary>
/// Property representing the Right hand side of the mapping.
/// Property representing the Right hand side element(s) of the mapping.
/// </summary>
public T Right { get; private set; }
public T[] Right { get; private set; }

/// <summary>
/// The <see cref="ComparingSettings"/> used to diff <see cref="Left"/> and <see cref="Right"/>.
Expand All @@ -32,36 +32,51 @@ public class ElementMapper<T>
/// Instantiates an object with the provided <see cref="ComparingSettings"/>.
/// </summary>
/// <param name="settings">The settings used to diff the elements in the mapper.</param>
public ElementMapper(ComparingSettings settings)
/// <param name="rightSetSize">The number of elements in the right set to compare.</param>
public ElementMapper(ComparingSettings settings, int rightSetSize)
{
if (rightSetSize <= 0)
throw new ArgumentOutOfRangeException(nameof(rightSetSize), "Should be greater than 0");

Settings = settings;
Right = new T[rightSetSize];
}

/// <summary>
/// Adds an element to the mapping given the index, 0 (Left) or 1 (Right).
/// Adds an element to the given <paramref name="side"/> using the index 0 for <see cref="ElementSide.Right"/>.
/// </summary>
/// <param name="element">The element to add.</param>
/// <param name="side">Value representing the side of the mapping. </param>
public virtual void AddElement(T element, ElementSide side) => AddElement(element, side, 0);

/// <summary>
/// Adds an element to the mapping given the <paramref name="side"/> and the <paramref name="setIndex"/>.
/// </summary>
/// <param name="element">The element to add to the mapping.</param>
/// <param name="index">Index representing the side of the mapping, 0 (Left) or 1 (Right).</param>
public virtual void AddElement(T element, int index)
/// <param name="side">Value representing the side of the mapping.</param>
/// <param name="setIndex">Value representing the index the element is added. Only used when adding to <see cref="ElementSide.Right"/>.</param>
public virtual void AddElement(T element, ElementSide side, int setIndex)
{
if ((uint)index > 1)
throw new ArgumentOutOfRangeException(nameof(index), "index should either be 0 or 1");

if (index == 0)
if (side == ElementSide.Left)
{
Left = element;
}
else
{
Right = element;
if ((uint)setIndex >= Right.Length)
throw new ArgumentOutOfRangeException(nameof(setIndex), "the index should be within the right set size");

Right[setIndex] = element;
}
}

/// <summary>
/// Runs the rules found by the rule driver on the element mapper and returns a list of differences.
/// </summary>
/// <returns>The list of <see cref="CompatDifference"/>.</returns>
public IEnumerable<CompatDifference> GetDifferences()
/// <returns>A list containing the list of differences for each possible combination of
/// (<see cref="ElementMapper{T}.Left"/>, <see cref="ElementMapper{T}.Right"/>).
/// One list of <see cref="CompatDifference"/> per the number of right elements that the <see cref="ElementMapper{T}"/> contains.</returns>
public IReadOnlyList<IEnumerable<CompatDifference>> GetDifferences()
{
return _differences ??= Settings.RuleRunnerFactory.GetRuleRunner().Run(this);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
/// <summary>
/// Enum representing the side of an element in the mappers.
/// </summary>
public enum ElementSide : byte
{
Left = 0,
Right = 1
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,22 @@ namespace Microsoft.DotNet.ApiCompatibility.Abstractions
/// </summary>
public class MemberMapper : ElementMapper<ISymbol>
{
private readonly TypeMapper _containingType;

/// <summary>
/// Instantiates an object with the provided <see cref="ComparingSettings"/>.
/// </summary>
/// <param name="settings">The settings used to diff the elements in the mapper.</param>
public MemberMapper(ComparingSettings settings) : base(settings) { }
/// <param name="rightSetSize">The number of elements in the right set to compare.</param>
public MemberMapper(ComparingSettings settings, TypeMapper containingType, int rightSetSize = 1)
: base(settings, rightSetSize)
{
_containingType = containingType;
}

// If we got to this point it means that _containingType.Left is not null.
// Because of that we can only check _containingType.Right.
internal bool ShouldDiffElement(int rightIndex) =>
_containingType.Right.Length == 1 || _containingType.Right[rightIndex] != null;
}
}
Loading