File: Rules\Compat\CannotRemoveGenerics.cs
Web Access
Project: src\src\Microsoft.DotNet.ApiCompat\src\Microsoft.DotNet.ApiCompat.Core\Microsoft.DotNet.ApiCompat.Core.csproj (Microsoft.DotNet.ApiCompat.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using Microsoft.Cci.Extensions;
using System.Linq;
using System.Collections.Generic;
 
namespace Microsoft.Cci.Differs.Rules
{
    // @todo: More thinking needed to see whether this is really breaking.
    //[ExportDifferenceRule]
    internal class CannotRemoveGenerics : CompatDifferenceRule
    {
        public override DifferenceType Diff(IDifferences differences, ITypeDefinition impl, ITypeDefinition contract)
        {
            if (impl == null || contract == null)
                return DifferenceType.Unknown;
 
            return DiffConstraints(differences, impl, impl.GenericParameters, contract.GenericParameters);
        }
 
        public override DifferenceType Diff(IDifferences differences, ITypeDefinitionMember impl, ITypeDefinitionMember contract)
        {
            return Diff(differences, impl as IMethodDefinition, contract as IMethodDefinition);
        }
 
        private DifferenceType Diff(IDifferences differences, IMethodDefinition implMethod, IMethodDefinition contractMethod)
        {
            if (implMethod == null || contractMethod == null)
                return DifferenceType.Unknown;
 
            return DiffConstraints(differences, implMethod, implMethod.GenericParameters, contractMethod.GenericParameters);
        }
 
        private DifferenceType DiffConstraints(IDifferences differences, IReference target, IEnumerable<IGenericParameter> implGenericParams, IEnumerable<IGenericParameter> contractGenericParams)
        {
            int beforeCount = differences.Count();
            IGenericParameter[] implParams = implGenericParams.ToArray();
            IGenericParameter[] contractParams = contractGenericParams.ToArray();
 
            // We shouldn't hit this because the types/members shouldn't be matched up if they have different generic argument lists
            if (implParams.Length != contractParams.Length)
                return DifferenceType.Changed;
 
            for (int i = 0; i < implParams.Length; i++)
            {
                IGenericParameter implParam = implParams[i];
                IGenericParameter contractParam = contractParams[i];
 
                if (contractParam.Variance != TypeParameterVariance.NonVariant &&
                    contractParam.Variance != implParam.Variance)
                {
                    differences.AddIncompatibleDifference("CannotChangeVariance",
                        $"Variance on generic parameter '{implParam.FullName()}' for '{target.FullName()}' is '{implParam.Variance}' in the {Implementation} but '{contractParam.Variance}' in the {Contract}.");
                }
 
                string implConstraints = string.Join(",", GetConstraints(implParam).OrderBy(s => s, StringComparer.OrdinalIgnoreCase));
                string contractConstraints = string.Join(",", GetConstraints(contractParam).OrderBy(s => s, StringComparer.OrdinalIgnoreCase));
 
                if (!string.Equals(implConstraints, contractConstraints))
                {
                    differences.AddIncompatibleDifference("CannotChangeGenericConstraints",
                        $"Constraints for generic parameter '{implParam.FullName()}' for '{target.FullName()}' is '{implConstraints}' in the {Implementation} but '{contractConstraints}' in the {Contract}.");
                }
            }
 
            if (differences.Count() != beforeCount)
                return DifferenceType.Changed;
 
            return DifferenceType.Unknown;
        }
 
        private IEnumerable<string> GetConstraints(IGenericParameter parameter)
        {
            if (parameter.MustBeValueType)
                yield return "struct";
            else
            {
                if (parameter.MustBeReferenceType)
                    yield return "class";
 
                if (parameter.MustHaveDefaultConstructor)
                    yield return "new()";
            }
 
            foreach (var constraint in parameter.Constraints)
            {
                // Skip valuetype because we should get it above.
                if (TypeHelper.TypesAreEquivalent(constraint, constraint.PlatformType.SystemValueType) && parameter.MustBeValueType)
                    continue;
 
                yield return constraint.FullName();
            }
        }
    }
}