File: Compiler\ClsComplianceChecker.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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.
 
#nullable disable
 
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// Traverses the symbol table checking for CLS compliance.
    /// </summary>
    internal partial class ClsComplianceChecker : CSharpSymbolVisitor
    {
        private readonly CSharpCompilation _compilation;
        private readonly SyntaxTree _filterTree; //if not null, limit analysis to types residing in this tree
        private readonly TextSpan? _filterSpanWithinTree; //if filterTree and filterSpanWithinTree is not null, limit analysis to types residing within this span in the filterTree.
        private readonly BindingDiagnosticBag _diagnostics;
        private readonly CancellationToken _cancellationToken;
 
        private readonly ConcurrentDictionary<Symbol, Compliance> _declaredOrInheritedCompliance;
 
        /// <seealso cref="MethodCompiler._compilerTasks"/>
        private readonly ConcurrentStack<Task> _compilerTasks;
 
        private ClsComplianceChecker(
            CSharpCompilation compilation,
            SyntaxTree filterTree,
            TextSpan? filterSpanWithinTree,
            BindingDiagnosticBag diagnostics,
            CancellationToken cancellationToken)
        {
            Debug.Assert(diagnostics.DependenciesBag is null || diagnostics.DependenciesBag is ConcurrentSet<AssemblySymbol>);
 
            _compilation = compilation;
            _filterTree = filterTree;
            _filterSpanWithinTree = filterSpanWithinTree;
            _diagnostics = diagnostics;
            _cancellationToken = cancellationToken;
 
            _declaredOrInheritedCompliance = new ConcurrentDictionary<Symbol, Compliance>(Symbols.SymbolEqualityComparer.ConsiderEverything);
 
            if (ConcurrentAnalysis)
            {
                _compilerTasks = new ConcurrentStack<Task>();
            }
        }
 
        /// <summary>
        /// Gets a value indicating whether <see cref="ClsComplianceChecker"/> is allowed to analyze in parallel.
        /// </summary>
        private bool ConcurrentAnalysis => _filterTree == null && _compilation.Options.ConcurrentBuild;
 
        /// <summary>
        /// Traverses the symbol table checking for CLS compliance.
        /// </summary>
        /// <param name="compilation">Compilation that owns the symbol table.</param>
        /// <param name="diagnostics">Will be supplemented with documentation comment diagnostics.</param>
        /// <param name="cancellationToken">To stop traversing the symbol table early.</param>
        /// <param name="filterTree">Only report diagnostics from this syntax tree, if non-null.</param>
        /// <param name="filterSpanWithinTree">If <paramref name="filterTree"/> and <paramref name="filterSpanWithinTree"/> is non-null, report diagnostics within this span in the <paramref name="filterTree"/>.</param>
        public static void CheckCompliance(CSharpCompilation compilation, BindingDiagnosticBag diagnostics, CancellationToken cancellationToken, SyntaxTree filterTree = null, TextSpan? filterSpanWithinTree = null)
        {
            var queue = diagnostics.AccumulatesDependencies ? BindingDiagnosticBag.GetConcurrentInstance() : BindingDiagnosticBag.GetInstance(withDiagnostics: diagnostics.AccumulatesDiagnostics, withDependencies: false);
            var checker = new ClsComplianceChecker(compilation, filterTree, filterSpanWithinTree, queue, cancellationToken);
            checker.Visit(compilation.Assembly);
            checker.WaitForWorkers();
 
            if (diagnostics.AccumulatesDiagnostics)
            {
                diagnostics.AddRange(queue.DiagnosticBag);
            }
 
            diagnostics.AddDependencies(queue);
            queue.Free();
        }
 
        public override void VisitAssembly(AssemblySymbol symbol)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            System.Diagnostics.Debug.Assert(symbol is SourceAssemblySymbol);
 
            Compliance assemblyCompliance = GetDeclaredOrInheritedCompliance(symbol);
 
            if (assemblyCompliance == Compliance.DeclaredFalse)
            {
                // Our interpretation of an assembly-level CLSCompliant attribute is as follows:
                //   1) If true, then perform all CLS checks.
                //   2) If false, then perform no CLS checks (dev11 still performs a few, mostly around
                //      meaningless attributes).  Our interpretation is that the user ultimately wants
                //      his code to be CLS-compliant, but is temporarily disabling the checks (e.g. during
                //      refactoring/prototyping).
                //   3) If absent, the perform all CLS checks.  Our interpretation is that - in the
                //      absence of an assembly-level attribute - any CLS problems within the compilation
                //      indicate that the user was trying to do something but didn't know how.  It would
                //      be nice if the most common case (i.e. this one) was the cheapest, but we don't
                //      want to confuse novice users.
                return;
            }
 
            bool assemblyComplianceValue = IsTrue(assemblyCompliance);
 
            for (int i = 0; i < symbol.Modules.Length; i++)
            {
                ModuleSymbol module = symbol.Modules[i];
                Location attributeLocation;
                bool? moduleDeclaredCompliance = GetDeclaredCompliance(module, out attributeLocation);
 
                Location warningLocation = i == 0 ? attributeLocation : module.GetFirstLocation();
                System.Diagnostics.Debug.Assert(warningLocation != null || !moduleDeclaredCompliance.HasValue || (i == 0 && _filterTree != null),
                    "Can only be null when the source location is filtered out.");
 
                if (moduleDeclaredCompliance.HasValue)
                {
                    if (warningLocation != null)
                    {
                        if (!IsDeclared(assemblyCompliance))
                        {
                            // This is not useful on non-source modules, but dev11 reports it anyway.
                            this.AddDiagnostic(ErrorCode.WRN_CLS_NotOnModules, warningLocation);
                        }
                        else if (assemblyComplianceValue != moduleDeclaredCompliance.GetValueOrDefault())
                        {
                            this.AddDiagnostic(ErrorCode.WRN_CLS_NotOnModules2, warningLocation);
                        }
                    }
                }
                else if (assemblyComplianceValue && i > 0)
                {
                    bool sawClsCompliantAttribute = false;
                    var peModule = (Symbols.Metadata.PE.PEModuleSymbol)module;
                    foreach (CSharpAttributeData assemblyLevelAttribute in peModule.GetAssemblyAttributes())
                    {
                        if (assemblyLevelAttribute.IsTargetAttribute(AttributeDescription.CLSCompliantAttribute))
                        {
                            sawClsCompliantAttribute = true;
                            break;
                        }
                    }
 
                    if (!sawClsCompliantAttribute)
                    {
                        this.AddDiagnostic(ErrorCode.WRN_CLS_ModuleMissingCLS, warningLocation);
                    }
                }
            }
 
            if (assemblyComplianceValue)
            {
                CheckForAttributeWithArrayArgument(symbol);
            }
 
            ModuleSymbol sourceModule = symbol.Modules[0];
            if (IsTrue(GetDeclaredOrInheritedCompliance(sourceModule)))
            {
                CheckForAttributeWithArrayArgument(sourceModule);
            }
 
            Visit(symbol.GlobalNamespace);
        }
 
        private void WaitForWorkers()
        {
            var tasks = _compilerTasks;
            if (tasks == null)
            {
                return;
            }
 
            while (tasks.TryPop(out Task curTask))
            {
                curTask.GetAwaiter().GetResult();
            }
        }
 
        public override void VisitNamespace(NamespaceSymbol symbol)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            if (DoNotVisit(symbol)) return;
 
            if (IsTrue(GetDeclaredOrInheritedCompliance(symbol)))
            {
                CheckName(symbol);
                CheckMemberDistinctness(symbol);
            }
 
            if (ConcurrentAnalysis)
            {
                VisitNamespaceMembersAsTasks(symbol);
            }
            else
            {
                VisitNamespaceMembers(symbol);
            }
        }
 
        private void VisitNamespaceMembersAsTasks(NamespaceSymbol symbol)
        {
            foreach (var m in symbol.GetMembersUnordered())
            {
                _compilerTasks.Push(Task.Run(UICultureUtilities.WithCurrentUICulture(() =>
                {
                    try
                    {
                        Visit(m);
                    }
                    catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e))
                    {
                        throw ExceptionUtilities.Unreachable();
                    }
                }), _cancellationToken));
            }
        }
 
        private void VisitNamespaceMembers(NamespaceSymbol symbol)
        {
            foreach (var m in symbol.GetMembersUnordered())
            {
                Visit(m);
            }
        }
 
        [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/23582", IsParallelEntry = false)]
        public override void VisitNamedType(NamedTypeSymbol symbol)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            if (DoNotVisit(symbol)) return;
 
            Debug.Assert(!symbol.IsImplicitClass);
 
            Compliance compliance = GetDeclaredOrInheritedCompliance(symbol);
 
            if (VisitTypeOrMember(symbol, compliance))
            {
                if (IsTrue(compliance))
                {
                    CheckBaseTypeCompliance(symbol);
                    CheckTypeParameterCompliance(symbol.TypeParameters, symbol);
 
                    if (symbol.TypeKind == TypeKind.Delegate)
                    {
                        CheckParameterCompliance(symbol.DelegateInvokeMethod.Parameters, symbol);
                    }
                    else if (_compilation.IsAttributeType(symbol) && !HasAcceptableAttributeConstructor(symbol))
                    {
                        this.AddDiagnostic(ErrorCode.WRN_CLS_BadAttributeType, symbol.GetFirstLocation(), symbol);
                    }
                }
            }
 
            // You may assume we could skip the members if this type is inaccessible,
            // but dev11 reports that they are inaccessible as well.
            foreach (var m in symbol.GetMembersUnordered())
            {
                Visit(m);
            }
        }
 
        private bool HasAcceptableAttributeConstructor(NamedTypeSymbol attributeType)
        {
            foreach (MethodSymbol constructor in attributeType.InstanceConstructors)
            {
                if (IsTrue(GetDeclaredOrInheritedCompliance(constructor)) && IsAccessibleIfContainerIsAccessible(constructor))
                {
                    System.Diagnostics.Debug.Assert(IsAccessibleOutsideAssembly(constructor), "Should be implied by IsAccessibleIfContainerIsAccessible");
 
                    bool hasUnacceptableParameterType = false;
 
                    foreach (var paramType in constructor.ParameterTypesWithAnnotations) // Public caller would select type out of parameters.
                    {
                        if (paramType.TypeKind == TypeKind.Array ||
                            paramType.Type.GetAttributeParameterTypedConstantKind(_compilation) == TypedConstantKind.Error)
                        {
                            hasUnacceptableParameterType = true;
                            break;
                        }
                    }
 
                    if (!hasUnacceptableParameterType)
                    {
                        return true;
                    }
                }
            }
 
            return false;
        }
 
        public override void VisitMethod(MethodSymbol symbol)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            if (DoNotVisit(symbol)) return;
 
            Compliance compliance = GetDeclaredOrInheritedCompliance(symbol);
 
            // Most CLS checks don't apply to accessors.
            if (symbol.IsAccessor())
            {
                CheckForAttributeOnAccessor(symbol);
                CheckForMeaninglessOnParameter(symbol.Parameters);
                CheckForMeaninglessOnReturn(symbol);
 
                if (IsTrue(compliance))
                {
                    CheckForAttributeWithArrayArgument(symbol);
                }
 
                return;
            }
 
            if (!VisitTypeOrMember(symbol, compliance)) return;
 
            if (IsTrue(compliance))
            {
                CheckParameterCompliance(symbol.Parameters, symbol.ContainingType);
                CheckTypeParameterCompliance(symbol.TypeParameters, symbol.ContainingType);
 
                if (symbol.IsVararg)
                {
                    this.AddDiagnostic(ErrorCode.WRN_CLS_NoVarArgs, symbol.GetFirstLocation());
                }
            }
        }
 
        private void CheckForAttributeOnAccessor(MethodSymbol symbol)
        {
            foreach (CSharpAttributeData attribute in symbol.GetAttributes())
            {
                if (attribute.IsTargetAttribute(AttributeDescription.CLSCompliantAttribute))
                {
                    Location attributeLocation;
                    if (TryGetAttributeWarningLocation(attribute, out attributeLocation))
                    {
                        AttributeUsageInfo attributeUsage = attribute.AttributeClass.GetAttributeUsageInfo();
                        this.AddDiagnostic(ErrorCode.ERR_AttributeNotOnAccessor, attributeLocation, attribute.AttributeClass.Name, attributeUsage.GetValidTargetsErrorArgument());
                        break;
                    }
                }
            }
        }
 
        public override void VisitProperty(PropertySymbol symbol)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            if (DoNotVisit(symbol)) return;
 
            Compliance compliance = GetDeclaredOrInheritedCompliance(symbol);
 
            if (!VisitTypeOrMember(symbol, compliance)) return;
 
            // Rule 28 requires that the accessors "adhere to a special naming pattern".
            // We don't actually need to do anything here, because they automatically
            // will unless they override accessors from metadata - we don't check overrides -
            // or they explicitly implement interface accessors - we don't check non-public
            // members.
 
            if (IsTrue(compliance))
            {
                CheckParameterCompliance(symbol.Parameters, symbol.ContainingType);
            }
        }
 
        public override void VisitEvent(EventSymbol symbol)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            if (DoNotVisit(symbol)) return;
 
            Compliance compliance = GetDeclaredOrInheritedCompliance(symbol);
 
            // Rule 32 specifies the shapes of the accessor signatures.  However,
            // even though WinRT events have a different shape, dev11 does not
            // report that they are non-compliant.
 
            // Rule 28 requires that the accessors "adhere to a special naming pattern".
            // We don't actually need to do anything here, because they automatically
            // will unless they override accessors from metadata - we don't check overrides -
            // or they explicitly implement interface accessors - we don't check non-public
            // members.
 
            if (!VisitTypeOrMember(symbol, compliance)) return;
        }
 
        public override void VisitField(FieldSymbol symbol)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            if (DoNotVisit(symbol)) return;
 
            Compliance compliance = GetDeclaredOrInheritedCompliance(symbol);
 
            if (!VisitTypeOrMember(symbol, compliance)) return;
 
            if (IsTrue(compliance))
            {
                if (symbol.IsVolatile)
                {
                    this.AddDiagnostic(ErrorCode.WRN_CLS_VolatileField, symbol.GetFirstLocation(), symbol);
                }
            }
        }
 
        /// <returns>False if no further checks are required (because they would be cascading).</returns>
        private bool VisitTypeOrMember(Symbol symbol, Compliance compliance)
        {
            SymbolKind symbolKind = symbol.Kind;
 
            System.Diagnostics.Debug.Assert(
                symbolKind == SymbolKind.NamedType ||
                symbolKind == SymbolKind.Field ||
                symbolKind == SymbolKind.Property ||
                symbolKind == SymbolKind.Event ||
                symbolKind == SymbolKind.Method);
            System.Diagnostics.Debug.Assert(!symbol.IsAccessor());
 
            if (!CheckForDeclarationWithoutAssemblyDeclaration(symbol, compliance))
            {
                return false; // Don't cascade from this failure.
            }
 
            bool isCompliant = IsTrue(compliance);
            bool isAccessibleOutsideAssembly = IsAccessibleOutsideAssembly(symbol);
 
            if (isAccessibleOutsideAssembly)
            {
                if (isCompliant)
                {
                    CheckName(symbol);
                    CheckForCompliantWithinNonCompliant(symbol);
                    CheckReturnTypeCompliance(symbol);
 
                    if (symbol.Kind == SymbolKind.NamedType)
                    {
                        CheckMemberDistinctness((NamedTypeSymbol)symbol);
                    }
                }
                else if (GetDeclaredOrInheritedCompliance(symbol.ContainingAssembly) == Compliance.DeclaredTrue && IsTrue(GetInheritedCompliance(symbol)))
                {
                    CheckForNonCompliantAbstractMember(symbol);
                }
            }
            else if (IsDeclared(compliance))
            {
                this.AddDiagnostic(ErrorCode.WRN_CLS_MeaninglessOnPrivateType, symbol.GetFirstLocation(), symbol);
                return false; // Don't cascade from this failure.
            }
 
            if (isCompliant)
            {
                // Independent of accessibility.
                CheckForAttributeWithArrayArgument(symbol);
            }
 
            // These checks are independent of accessibility and compliance.
            if (symbolKind == SymbolKind.NamedType)
            {
                NamedTypeSymbol type = (NamedTypeSymbol)symbol;
                if (type.TypeKind == TypeKind.Delegate)
                {
                    MethodSymbol method = type.DelegateInvokeMethod;
                    CheckForMeaninglessOnParameter(method.Parameters);
                    CheckForMeaninglessOnReturn(method);
                }
            }
            else if (symbolKind == SymbolKind.Method)
            {
                MethodSymbol method = (MethodSymbol)symbol;
                CheckForMeaninglessOnParameter(method.Parameters);
                CheckForMeaninglessOnReturn(method);
            }
            else if (symbolKind == SymbolKind.Property)
            {
                PropertySymbol property = (PropertySymbol)symbol;
                CheckForMeaninglessOnParameter(property.Parameters);
            }
 
            // All checks that apply to inaccessible symbols are performed by this method.
            return isAccessibleOutsideAssembly;
        }
 
        private void CheckForNonCompliantAbstractMember(Symbol symbol)
        {
            System.Diagnostics.Debug.Assert(!IsTrue(GetDeclaredOrInheritedCompliance(symbol)), "Only call on non-compliant symbols");
 
            NamedTypeSymbol containingType = symbol.ContainingType;
            if ((object)containingType != null && containingType.IsInterface)
            {
                this.AddDiagnostic(ErrorCode.WRN_CLS_BadInterfaceMember, symbol.GetFirstLocation(), symbol);
            }
            else if (symbol.IsAbstract && symbol.Kind != SymbolKind.NamedType)
            {
                this.AddDiagnostic(ErrorCode.WRN_CLS_NoAbstractMembers, symbol.GetFirstLocation(), symbol);
            }
        }
 
        private void CheckBaseTypeCompliance(NamedTypeSymbol symbol)
        {
            System.Diagnostics.Debug.Assert(IsTrue(GetDeclaredOrInheritedCompliance(symbol)), "Only call on compliant symbols");
 
            // NOTE: implemented interfaces do not have to be CLS-compliant (unless the type itself is an interface).
 
            if (symbol.IsInterface)
            {
                foreach (NamedTypeSymbol interfaceType in symbol.InterfacesNoUseSiteDiagnostics())
                {
                    if (!IsCompliantType(interfaceType, symbol))
                    {
                        // TODO: it would be nice to report this on the base type clause.
                        this.AddDiagnostic(ErrorCode.WRN_CLS_BadInterface, symbol.GetFirstLocation(), symbol, interfaceType);
                    }
                }
            }
            else
            {
                NamedTypeSymbol baseType = symbol.EnumUnderlyingType ?? symbol.BaseTypeNoUseSiteDiagnostics; // null for interfaces
                System.Diagnostics.Debug.Assert((object)baseType != null || symbol.SpecialType == SpecialType.System_Object, "Only object has no base.");
                if ((object)baseType != null && !IsCompliantType(baseType, symbol))
                {
                    // TODO: it would be nice to report this on the base type clause.
                    this.AddDiagnostic(ErrorCode.WRN_CLS_BadBase, symbol.GetFirstLocation(), symbol, baseType);
                }
            }
        }
 
        private void CheckForCompliantWithinNonCompliant(Symbol symbol)
        {
            System.Diagnostics.Debug.Assert(IsTrue(GetDeclaredOrInheritedCompliance(symbol)), "Only call on compliant symbols");
 
            NamedTypeSymbol containingType = symbol.ContainingType;
            System.Diagnostics.Debug.Assert((object)containingType == null || !containingType.IsImplicitClass);
            if ((object)containingType != null && !IsTrue(GetDeclaredOrInheritedCompliance(containingType)))
            {
                this.AddDiagnostic(ErrorCode.WRN_CLS_IllegalTrueInFalse, symbol.GetFirstLocation(), symbol, containingType);
            }
        }
 
        private void CheckTypeParameterCompliance(ImmutableArray<TypeParameterSymbol> typeParameters, NamedTypeSymbol context)
        {
            System.Diagnostics.Debug.Assert(typeParameters.IsEmpty || IsTrue(GetDeclaredOrInheritedCompliance(context)), "Only call on compliant symbols");
 
            foreach (TypeParameterSymbol typeParameter in typeParameters)
            {
                foreach (TypeWithAnnotations constraintType in typeParameter.ConstraintTypesNoUseSiteDiagnostics)
                {
                    if (!IsCompliantType(constraintType.Type, context))
                    {
                        // TODO: it would be nice to report this on the constraint clause.
                        // NOTE: we're improving over dev11 by reporting on the type parameter declaration,
                        // rather than on the constraint type declaration.
                        this.AddDiagnostic(ErrorCode.WRN_CLS_BadTypeVar, typeParameter.GetFirstLocation(), constraintType.Type);
                    }
                }
            }
        }
 
        private void CheckParameterCompliance(ImmutableArray<ParameterSymbol> parameters, NamedTypeSymbol context)
        {
            System.Diagnostics.Debug.Assert(parameters.IsEmpty || IsTrue(GetDeclaredOrInheritedCompliance(context)), "Only call on compliant symbols");
 
            foreach (ParameterSymbol parameter in parameters)
            {
                if (!IsCompliantType(parameter.Type, context))
                {
                    this.AddDiagnostic(ErrorCode.WRN_CLS_BadArgType, parameter.GetFirstLocation(), parameter.Type);
                }
            }
        }
 
        private void CheckForAttributeWithArrayArgument(Symbol symbol)
        {
            System.Diagnostics.Debug.Assert(IsTrue(GetDeclaredOrInheritedCompliance(symbol)), "Only call on compliant symbols");
            CheckForAttributeWithArrayArgumentInternal(symbol.GetAttributes());
            if (symbol.Kind == SymbolKind.Method)
            {
                CheckForAttributeWithArrayArgumentInternal(((MethodSymbol)symbol).GetReturnTypeAttributes());
            }
        }
 
        /// <remarks>
        /// BREAK: Dev11 reports WRN_CLS_ArrayArgumentToAttribute on all symbols, whereas roslyn reports it only
        /// on accessible symbols.
        /// </remarks>
        private void CheckForAttributeWithArrayArgumentInternal(ImmutableArray<CSharpAttributeData> attributes)
        {
            foreach (CSharpAttributeData attribute in attributes)
            {
                foreach (TypedConstant argument in attribute.ConstructorArguments)
                {
                    if (argument.TypeInternal.TypeKind == TypeKind.Array)
                    {
                        // TODO: it would be nice to report for each bad argument, but currently it's pointless since they
                        // would all have the same message and location.
                        Location warningLocation;
                        if (TryGetAttributeWarningLocation(attribute, out warningLocation))
                        {
                            this.AddDiagnostic(ErrorCode.WRN_CLS_ArrayArgumentToAttribute, warningLocation);
                            return;
                        }
                    }
                }
 
                foreach (var pair in attribute.NamedArguments)
                {
                    TypedConstant argument = pair.Value;
                    if (argument.TypeInternal.TypeKind == TypeKind.Array)
                    {
                        // TODO: it would be nice to report for each bad argument, but currently it's pointless since they
                        // would all have the same message and location.
                        Location warningLocation;
                        if (TryGetAttributeWarningLocation(attribute, out warningLocation))
                        {
                            this.AddDiagnostic(ErrorCode.WRN_CLS_ArrayArgumentToAttribute, warningLocation);
                            return;
                        }
                    }
                }
 
                // This catches things like param arrays and converted null literals.
                if ((object)attribute.AttributeConstructor != null) // Happens in error scenarios.
                {
                    foreach (var type in attribute.AttributeConstructor.ParameterTypesWithAnnotations)
                    {
                        if (type.TypeKind == TypeKind.Array)
                        {
                            // TODO: it would be nice to report for each bad argument, but currently it's pointless since they
                            // would all have the same message and location.
                            Location warningLocation;
                            if (TryGetAttributeWarningLocation(attribute, out warningLocation))
                            {
                                this.AddDiagnostic(ErrorCode.WRN_CLS_ArrayArgumentToAttribute, warningLocation);
                                return;
                            }
                        }
                    }
                }
            }
        }
 
        private bool TryGetAttributeWarningLocation(CSharpAttributeData attribute, out Location location)
        {
            SyntaxReference syntaxRef = attribute.ApplicationSyntaxReference;
            if (syntaxRef == null && _filterTree == null)
            {
                location = NoLocation.Singleton;
                return true;
            }
            else if (_filterTree == null || (syntaxRef != null && syntaxRef.SyntaxTree == _filterTree))
            {
                System.Diagnostics.Debug.Assert(syntaxRef.SyntaxTree.HasCompilationUnitRoot);
                location = new SourceLocation(syntaxRef);
                return true;
            }
 
            location = null;
            return false;
        }
 
        private void CheckForMeaninglessOnParameter(ImmutableArray<ParameterSymbol> parameters)
        {
            if (parameters.IsEmpty) return;
 
            int startPos = 0;
 
            Symbol container = parameters[0].ContainingSymbol;
            if (container.Kind == SymbolKind.Method)
            {
                Symbol associated = ((MethodSymbol)container).AssociatedSymbol;
                if ((object)associated != null && associated.Kind == SymbolKind.Property)
                {
                    // Only care about "value" parameter for accessors.
                    // NOTE: public caller would have to count parameters.
                    startPos = ((PropertySymbol)associated).ParameterCount;
                }
            }
 
            for (int i = startPos; i < parameters.Length; i++)
            {
                Location attributeLocation;
                if (TryGetClsComplianceAttributeLocation(parameters[i].GetAttributes(), out attributeLocation))
                {
                    this.AddDiagnostic(ErrorCode.WRN_CLS_MeaninglessOnParam, attributeLocation);
                }
            }
        }
 
        private void CheckForMeaninglessOnReturn(MethodSymbol method)
        {
            Location attributeLocation;
            if (TryGetClsComplianceAttributeLocation(method.GetReturnTypeAttributes(), out attributeLocation))
            {
                this.AddDiagnostic(ErrorCode.WRN_CLS_MeaninglessOnReturn, attributeLocation);
            }
        }
 
        private void CheckReturnTypeCompliance(Symbol symbol)
        {
            System.Diagnostics.Debug.Assert(IsTrue(GetDeclaredOrInheritedCompliance(symbol)), "Only call on compliant symbols");
 
            ErrorCode code;
            TypeSymbol type;
            switch (symbol.Kind)
            {
                case SymbolKind.Field:
                    code = ErrorCode.WRN_CLS_BadFieldPropType;
                    type = ((FieldSymbol)symbol).Type;
                    break;
                case SymbolKind.Property:
                    code = ErrorCode.WRN_CLS_BadFieldPropType;
                    type = ((PropertySymbol)symbol).Type;
                    break;
                case SymbolKind.Event:
                    code = ErrorCode.WRN_CLS_BadFieldPropType;
                    type = ((EventSymbol)symbol).Type;
                    break;
                case SymbolKind.Method:
                    code = ErrorCode.WRN_CLS_BadReturnType;
                    MethodSymbol method = (MethodSymbol)symbol;
                    type = method.ReturnType;
 
                    if (method.MethodKind == MethodKind.DelegateInvoke)
                    {
                        System.Diagnostics.Debug.Assert(method.ContainingType.TypeKind == TypeKind.Delegate);
                        symbol = method.ContainingType; // Refer to the delegate type in diagnostics.
                    }
 
                    // Diagnostic not interesting for accessors.
                    System.Diagnostics.Debug.Assert(!method.IsAccessor());
 
                    break;
                case SymbolKind.NamedType:
                    symbol = ((NamedTypeSymbol)symbol).DelegateInvokeMethod;
                    if ((object)symbol == null)
                    {
                        return;
                    }
                    else
                    {
                        goto case SymbolKind.Method;
                    }
                default:
                    throw ExceptionUtilities.UnexpectedValue(symbol.Kind);
            }
 
            if (!IsCompliantType(type, symbol.ContainingType))
            {
                this.AddDiagnostic(code, symbol.GetFirstLocation(), symbol);
            }
        }
 
        private bool TryGetClsComplianceAttributeLocation(ImmutableArray<CSharpAttributeData> attributes, out Location attributeLocation)
        {
            foreach (CSharpAttributeData data in attributes)
            {
                if (data.IsTargetAttribute(AttributeDescription.CLSCompliantAttribute))
                {
                    if (TryGetAttributeWarningLocation(data, out attributeLocation))
                    {
                        return true;
                    }
                }
            }
 
            attributeLocation = null;
            return false;
        }
 
        /// <returns>True if the symbol is okay (i.e. no warnings).</returns>
        private bool CheckForDeclarationWithoutAssemblyDeclaration(Symbol symbol, Compliance compliance)
        {
            if (IsDeclared(compliance))
            {
                Compliance assemblyCompliance = GetDeclaredOrInheritedCompliance(symbol.ContainingAssembly);
 
                if (!IsDeclared(assemblyCompliance))
                {
                    ErrorCode code = IsTrue(compliance)
                        ? ErrorCode.WRN_CLS_AssemblyNotCLS
                        : ErrorCode.WRN_CLS_AssemblyNotCLS2;
                    this.AddDiagnostic(code, symbol.GetFirstLocation(), symbol);
                    return false;
                }
            }
            return true;
        }
 
        private void CheckMemberDistinctness(NamespaceOrTypeSymbol symbol)
        {
            System.Diagnostics.Debug.Assert(IsAccessibleOutsideAssembly(symbol));
            System.Diagnostics.Debug.Assert(IsTrue(GetDeclaredOrInheritedCompliance(symbol)));
 
            MultiDictionary<string, Symbol> seenByName = new MultiDictionary<string, Symbol>(CaseInsensitiveComparison.Comparer);
 
            // For types, we also need to consider collisions with inherited members.
            if (symbol.Kind != SymbolKind.Namespace)
            {
                NamedTypeSymbol type = (NamedTypeSymbol)symbol;
 
                // NOTE: As in dev11 we're using Interfaces, rather than AllInterfaces.
                // This seems like a bug, but it's easier to reproduce it than to deal
                // with all the potential breaks.
                // NOTE: It's not clear why dev11 is looking in interfaces at all. Maybe
                // it was only supposed to happen for interface types?
                foreach (NamedTypeSymbol @interface in type.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.Keys) // NOTE: would be hand-rolled in a standalone component.
                {
                    if (!IsAccessibleOutsideAssembly(@interface)) continue;
 
                    foreach (Symbol member in @interface.GetMembersUnordered())
                    {
                        // NOTE: As in dev11 we filter out overriding methods and properties (but not events).
                        // NOTE: As in dev11, we ignore the CLS compliance of the interface and its members.
                        if (IsAccessibleIfContainerIsAccessible(member) &&
                            (!member.IsOverride || !(member.Kind == SymbolKind.Method || member.Kind == SymbolKind.Property)))
                        {
                            seenByName.Add(member.Name, member);
                        }
                    }
                }
 
                NamedTypeSymbol baseType = type.BaseTypeNoUseSiteDiagnostics;
                while ((object)baseType != null)
                {
                    foreach (Symbol member in baseType.GetMembersUnordered())
                    {
                        // NOTE: As in dev11 we filter out overriding methods and properties (but not events).
                        // NOTE: Unlike for interface members, we check CLS compliance of base type members.
                        if (IsAccessibleOutsideAssembly(member) &&
                            IsTrue(GetDeclaredOrInheritedCompliance(member)) &&
                            (!member.IsOverride || !(member.Kind == SymbolKind.Method || member.Kind == SymbolKind.Property)))
                        {
                            seenByName.Add(member.Name, member);
                        }
                    }
 
                    baseType = baseType.BaseTypeNoUseSiteDiagnostics;
                }
            }
 
            // NOTE: visit the members in order so that the same one is always reported as a conflict.
            foreach (Symbol member in symbol.GetMembers())
            {
                // Filter out uninteresting members:
                if (DoNotVisit(member) ||
                    !IsAccessibleIfContainerIsAccessible(member) || // We already know that the container is accessible.
                    !IsTrue(GetDeclaredOrInheritedCompliance(member)) ||
                    member.IsOverride)
                {
                    continue;
                }
 
                var name = member.Name;
                var sameNameSymbols = seenByName[name];
                if (sameNameSymbols.Count > 0)
                {
                    CheckSymbolDistinctness(member, name, sameNameSymbols);
                }
 
                seenByName.Add(name, member);
            }
        }
 
        /// <remarks>
        /// NOTE: Dev11 behavior - First, it ignores arity,
        /// which seems like a good way to disambiguate symbols (in particular,
        /// CLS Rule 43 says that the name includes backtick-arity).  Second, it
        /// does not consider two members with identical names (i.e. not differing
        /// in case) to collide.
        /// </remarks>
        private void CheckSymbolDistinctness(Symbol symbol, string symbolName, MultiDictionary<string, Symbol>.ValueSet sameNameSymbols)
        {
            Debug.Assert(sameNameSymbols.Count > 0);
            Debug.Assert(symbol.Name == symbolName);
 
            bool isMethodOrProperty = symbol.Kind == SymbolKind.Method || symbol.Kind == SymbolKind.Property;
 
            foreach (Symbol other in sameNameSymbols)
            {
                if (other.Name != symbolName && !(isMethodOrProperty && other.Kind == symbol.Kind))
                {
                    // TODO: Shouldn't we somehow reference the conflicting member?  Dev11 doesn't.
                    this.AddDiagnostic(ErrorCode.WRN_CLS_BadIdentifierCase, symbol.GetFirstLocation(), symbol);
                    return;
                }
            }
 
            if (!isMethodOrProperty)
            {
                return;
            }
 
            foreach (Symbol other in sameNameSymbols)
            {
                // Note: not checking accessor signatures, but checking accessor names.
                ErrorCode code;
                if (symbol.Kind == other.Kind &&
                    !symbol.IsAccessor() &&
                    !other.IsAccessor() &&
                    TryGetCollisionErrorCode(symbol, other, out code))
                {
                    this.AddDiagnostic(code, symbol.GetFirstLocation(), symbol);
                    return;
                }
                else if (other.Name != symbolName)
                {
                    // TODO: Shouldn't we somehow reference the conflicting member?  Dev11 doesn't.
                    this.AddDiagnostic(ErrorCode.WRN_CLS_BadIdentifierCase, symbol.GetFirstLocation(), symbol);
                    return;
                }
            }
        }
 
        private void CheckName(Symbol symbol)
        {
            System.Diagnostics.Debug.Assert(IsTrue(GetDeclaredOrInheritedCompliance(symbol)));
            System.Diagnostics.Debug.Assert(IsAccessibleOutsideAssembly(symbol));
 
            if (!symbol.CanBeReferencedByName || symbol.IsOverride) return;
 
            string name = symbol.Name;
 
            // NOTE: The CLI spec says
            //   CLS Rule 4: Assemblies shall follow Annex 7 of Technical Report 15 of the Unicode Standard 3.0 governing 
            //   the set of characters permitted to start and be included in identifiers, available on-line at 
            //   http://www.unicode.org/unicode/reports/tr15/tr15-18.html. Identifiers shall be in the canonical format defined 
            //   by Unicode Normalization Form C. For CLS purposes, two identifiers are the same if their lowercase mappings 
            //   (as specified by the Unicode locale-insensitive, one-to-one lowercase mappings) are the same. That is, for two 
            //   identifiers to be considered different under the CLS they shall differ in more than simply their case. However, 
            //   in order to override an inherited definition the CLI requires the precise encoding of the original declaration be 
            //   used.
            //
            // However, what the native compiler actually does is ignore everything about composed and decomposed characters 
            // (see comment in CompilationPass::checkCLSnaming) and then just checks if the first character is underscore
            // (0x005F or 0xFF3F).  Presumably, it assumes that the language rules have weeded out any other identifiers 
            // forbidden by the unicode spec.
 
            // NOTE: The parser won't actually accept '\uFF3F' as part of an identifier.
            System.Diagnostics.Debug.Assert(name.Length == 0 || name[0] != '\uFF3F');
 
            if (name.Length > 0 && name[0] == '\u005F')
            {
                this.AddDiagnostic(ErrorCode.WRN_CLS_BadIdentifier, symbol.GetFirstLocation(), name);
            }
        }
 
        private bool DoNotVisit(Symbol symbol)
        {
            if (symbol.Kind == SymbolKind.Namespace)
            {
                return false;
            }
 
            // TODO: There's no public equivalent of Symbol.DeclaringCompilation.
            return symbol.DeclaringCompilation != _compilation ||
                symbol.IsImplicitlyDeclared ||
                IsSyntacticallyFilteredOut(symbol);
        }
 
        private bool IsSyntacticallyFilteredOut(Symbol symbol)
        {
            // TODO: it would be nice to be more precise than this: we only want to
            // warn about the base class if it is listed in the filter tree, not if
            // any part of the type is in the filter tree.
            return _filterTree != null && !symbol.IsDefinedInSourceTree(_filterTree, _filterSpanWithinTree);
        }
 
        private bool IsCompliantType(TypeSymbol type, NamedTypeSymbol context)
        {
            switch (type.TypeKind)
            {
                case TypeKind.Array:
                    return IsCompliantType(((ArrayTypeSymbol)type).ElementType, context);
                case TypeKind.Dynamic:
                    // NOTE: It would probably be most correct to return 
                    // IsCompliantType(this.compilation.GetSpecialType(SpecialType.System_Object), context)
                    // but that's way too much work in the 99.9% case.
                    return true;
                case TypeKind.Pointer:
                case TypeKind.FunctionPointer:
                    return false;
                case TypeKind.Error:
                case TypeKind.TypeParameter:
                    // Possibly not the most accurate answer, but the gist is that we
                    // don't want to report problems with these types.
                    return true;
                case TypeKind.Class:
                case TypeKind.Struct:
                case TypeKind.Interface:
                case TypeKind.Delegate:
                case TypeKind.Enum:
                case TypeKind.Submission:
                    return IsCompliantType((NamedTypeSymbol)type, context);
                default:
                    throw ExceptionUtilities.UnexpectedValue(type.TypeKind);
            }
        }
 
        private bool IsCompliantType(NamedTypeSymbol type, NamedTypeSymbol context)
        {
            // BREAK: Other than in the cases listed below, dev11 always
            // returns true for predefined types - even if they are constructed
            // with non-compliant type arguments (e.g. System.Action<uint>).
            switch (type.SpecialType)
            {
                case SpecialType.System_TypedReference:
                case SpecialType.System_UIntPtr:
                    // Hard-coded in dev11 (LangCompiler::isCLS_Type).
                    return false;
 
                case SpecialType.System_SByte: // sic
                case SpecialType.System_UInt16:
                case SpecialType.System_UInt32:
                case SpecialType.System_UInt64:
                    // Dev11 calls these "quasi-simple" types and hard-codes false.
                    return false;
            }
 
            if (type.TypeKind == TypeKind.Error)
            {
                // Possibly not the most accurate answer, but the gist is that we
                // don't want to report problems with these types.
                return true;
            }
 
            if (!IsTrue(GetDeclaredOrInheritedCompliance(type.OriginalDefinition)))
            {
                return false;
            }
 
            foreach (TypeWithAnnotations typeArg in type.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics)
            {
                if (!IsCompliantType(typeArg.Type, context))
                {
                    return false;
                }
            }
 
            return !IsInaccessibleBecauseOfConstruction(type, context);
        }
 
        /// <remarks>
        /// This check (the only one that uses the "context" parameter is based on CLS Rule 46,
        /// as implemented by LangCompiler::IsCLSAccessible.  The idea is that C&lt;int&gt; and C&lt;char&gt;
        /// are separate types in CLS, so they can't touch each other's protected members.
        /// TODO: This should really have a separate error code - it's logically separate and requires explanation.
        /// </remarks>
        /// <param name="type">Check the accessibility of this type (probably a parameter or return type).</param>
        /// <param name="context">Context for the accessibility check (e.g. containing type of method with <paramref name="type"/> as a parameter type.</param>
        private static bool IsInaccessibleBecauseOfConstruction(NamedTypeSymbol type, NamedTypeSymbol context)
        {
            // NOTE: Dev11 (incorrectly) only checks whether "type" is protected - it ignores container accessibility.
            bool sawProtected = type.DeclaredAccessibility.HasProtected();
            bool sawGeneric = false; // Generic "type" doesn't count.
            Dictionary<NamedTypeSymbol, NamedTypeSymbol> containingTypes = null; // maps definition to constructed
            {
                NamedTypeSymbol containingType = type.ContainingType;
                while ((object)containingType != null)
                {
                    if (containingTypes == null)
                    {
                        containingTypes = new Dictionary<NamedTypeSymbol, NamedTypeSymbol>();
                    }
 
                    sawProtected = sawProtected || containingType.DeclaredAccessibility.HasProtected();
                    sawGeneric = sawGeneric || containingType.Arity > 0;
 
                    containingTypes.Add(containingType.OriginalDefinition, containingType);
 
                    containingType = containingType.ContainingType;
                }
            }
 
            if (!sawProtected || !sawGeneric || containingTypes == null)
            {
                return false;
            }
 
            while ((object)context != null)
            {
                NamedTypeSymbol contextBaseType = context;
                while ((object)contextBaseType != null)
                {
                    NamedTypeSymbol containingType;
                    if (containingTypes.TryGetValue(contextBaseType.OriginalDefinition, out containingType))
                    {
                        return !TypeSymbol.Equals(containingType, contextBaseType, TypeCompareKind.AllIgnoreOptions);
                    }
 
                    contextBaseType = contextBaseType.BaseTypeNoUseSiteDiagnostics;
                }
 
                context = context.ContainingType;
            }
 
            // NOTE: Dev11 seems to return true here.  That's reasonable, since the type is inaccessible,
            // but it seems like the inaccessibility can only be cascading from another error.
            return false;
        }
 
        private Compliance GetDeclaredOrInheritedCompliance(Symbol symbol)
        {
            System.Diagnostics.Debug.Assert(symbol.Kind == SymbolKind.NamedType || !((symbol is TypeSymbol)),
                "Type kinds without declarations are handled elsewhere.");
 
            if (symbol.Kind == SymbolKind.Namespace)
            {
                // Don't bother storing entries for namespaces - just go straight to the assembly.
                return GetDeclaredOrInheritedCompliance(symbol.ContainingAssembly);
            }
            else if (symbol.Kind == SymbolKind.Method)
            {
                MethodSymbol method = (MethodSymbol)symbol;
                Symbol associated = method.AssociatedSymbol;
                if ((object)associated != null)
                {
                    // Don't bother storing entries for accessors - just go straight to the property/event.
                    return GetDeclaredOrInheritedCompliance(associated);
                }
            }
 
            // Not meaningful
            Debug.Assert(symbol.Kind != SymbolKind.Alias);
            Debug.Assert(symbol.Kind != SymbolKind.Label);
            Debug.Assert(symbol.Kind != SymbolKind.Namespace);
            Debug.Assert(symbol.Kind != SymbolKind.Parameter);
            Debug.Assert(symbol.Kind != SymbolKind.RangeVariable);
 
            Compliance compliance;
            if (_declaredOrInheritedCompliance.TryGetValue(symbol, out compliance))
            {
                return compliance;
            }
 
            Location ignoredLocation;
            bool? declaredCompliance = GetDeclaredCompliance(symbol, out ignoredLocation);
            if (declaredCompliance.HasValue)
            {
                compliance = declaredCompliance.GetValueOrDefault() ? Compliance.DeclaredTrue : Compliance.DeclaredFalse;
            }
            else if (symbol.Kind == SymbolKind.Assembly)
            {
                // Assemblies are not compliant unless specifically declared to be so.
                compliance = Compliance.ImpliedFalse;
            }
            else
            {
                compliance = IsTrue(GetInheritedCompliance(symbol)) ? Compliance.InheritedTrue : Compliance.InheritedFalse;
            }
 
            // Don't bother caching methods, etc - they won't be reused.
            return (symbol.Kind == SymbolKind.Assembly || symbol.Kind == SymbolKind.NamedType)
                ? _declaredOrInheritedCompliance.GetOrAdd(symbol, compliance)
                : compliance;
        }
 
        private Compliance GetInheritedCompliance(Symbol symbol)
        {
            System.Diagnostics.Debug.Assert(symbol.Kind != SymbolKind.Assembly);
 
            Symbol containing = (Symbol)symbol.ContainingType ?? symbol.ContainingAssembly;
            System.Diagnostics.Debug.Assert((object)containing != null);
            return GetDeclaredOrInheritedCompliance(containing);
        }
 
        /// <remarks>
        /// As in dev11, we ignore the fact that CLSCompliantAttribute is inherited (i.e. from the base type)
        /// (see CSemanticChecker::CheckSymForCLS).  This should only affect types where the syntactic parent
        /// and the inheritance parent disagree.
        /// </remarks>
        private bool? GetDeclaredCompliance(Symbol symbol, out Location attributeLocation)
        {
            attributeLocation = null;
            foreach (CSharpAttributeData data in symbol.GetAttributes())
            {
                // Check signature before HasErrors to avoid realizing symbols for other attributes.
                if (data.IsTargetAttribute(AttributeDescription.CLSCompliantAttribute))
                {
                    NamedTypeSymbol attributeClass = data.AttributeClass;
                    if ((object)attributeClass != null)
                    {
                        if (_diagnostics.ReportUseSite(attributeClass, symbol.GetFirstLocationOrNone()))
                        {
                            continue;
                        }
                    }
 
                    if (!data.HasErrors)
                    {
                        if (!TryGetAttributeWarningLocation(data, out attributeLocation))
                        {
                            attributeLocation = null;
                        }
 
                        ImmutableArray<TypedConstant> args = data.CommonConstructorArguments;
                        System.Diagnostics.Debug.Assert(args.Length == 1, "We already checked the signature and HasErrors.");
 
                        // Duplicates are reported elsewhere - we only care about the first (error-free) occurrence.
                        return (bool)args[0].ValueInternal;
                    }
                }
            }
 
            return null;
        }
 
        private static bool IsAccessibleOutsideAssembly(Symbol symbol)
        {
            while ((object)symbol != null && !IsImplicitClass(symbol))
            {
                if (!IsAccessibleIfContainerIsAccessible(symbol))
                {
                    return false;
                }
                symbol = symbol.ContainingType;
            }
            return true;
        }
 
        private static bool IsAccessibleIfContainerIsAccessible(Symbol symbol)
        {
            switch (symbol.DeclaredAccessibility)
            {
                case Accessibility.Public:
                case Accessibility.Protected:
                case Accessibility.ProtectedOrInternal:
                    return true;
                case Accessibility.Private:
                case Accessibility.ProtectedAndInternal:
                case Accessibility.Internal:
                    return false;
                case Accessibility.NotApplicable:
                    System.Diagnostics.Debug.Assert(symbol.Kind == SymbolKind.ErrorType);
                    return false;
                default:
                    throw ExceptionUtilities.UnexpectedValue(symbol.DeclaredAccessibility);
            }
        }
 
        private void AddDiagnostic(ErrorCode code, Location location)
        {
            var info = new CSDiagnosticInfo(code);
            var diag = new CSDiagnostic(info, location);
            _diagnostics.Add(diag);
        }
 
        private void AddDiagnostic(ErrorCode code, Location location, params object[] args)
        {
            var info = new CSDiagnosticInfo(code, args);
            var diag = new CSDiagnostic(info, location);
            _diagnostics.Add(diag);
        }
 
        private static bool IsImplicitClass(Symbol symbol)
        {
            return symbol.Kind == SymbolKind.NamedType && ((NamedTypeSymbol)symbol).IsImplicitClass;
        }
 
        private static bool IsTrue(Compliance compliance)
        {
            switch (compliance)
            {
                case Compliance.DeclaredTrue:
                case Compliance.InheritedTrue:
                    return true;
                case Compliance.DeclaredFalse:
                case Compliance.InheritedFalse:
                case Compliance.ImpliedFalse:
                    return false;
                default:
                    throw ExceptionUtilities.UnexpectedValue(compliance);
            }
        }
 
        private static bool IsDeclared(Compliance compliance)
        {
            switch (compliance)
            {
                case Compliance.DeclaredTrue:
                case Compliance.DeclaredFalse:
                    return true;
                case Compliance.InheritedTrue:
                case Compliance.InheritedFalse:
                case Compliance.ImpliedFalse:
                    return false;
                default:
                    throw ExceptionUtilities.UnexpectedValue(compliance);
            }
        }
 
        private enum Compliance
        {
            DeclaredTrue,
            DeclaredFalse,
            InheritedTrue,
            InheritedFalse,
            ImpliedFalse,
        }
 
        /// <remarks>
        /// Based on CompilationPass::CLSReduceSignature.
        /// </remarks>
        private static bool TryGetCollisionErrorCode(Symbol x, Symbol y, out ErrorCode code)
        {
            System.Diagnostics.Debug.Assert((object)x != null);
            System.Diagnostics.Debug.Assert((object)y != null);
            System.Diagnostics.Debug.Assert((object)x != (object)y);
            System.Diagnostics.Debug.Assert(x.Kind == y.Kind);
 
            code = ErrorCode.Void;
 
            ImmutableArray<TypeWithAnnotations> xParameterTypes;
            ImmutableArray<TypeWithAnnotations> yParameterTypes;
            ImmutableArray<RefKind> xRefKinds;
            ImmutableArray<RefKind> yRefKinds;
            switch (x.Kind)
            {
                case SymbolKind.Method:
                    var mX = (MethodSymbol)x;
                    xParameterTypes = mX.ParameterTypesWithAnnotations;
                    xRefKinds = mX.ParameterRefKinds;
 
                    var mY = (MethodSymbol)y;
                    yParameterTypes = mY.ParameterTypesWithAnnotations;
                    yRefKinds = mY.ParameterRefKinds;
                    break;
                case SymbolKind.Property:
                    var pX = (PropertySymbol)x;
                    xParameterTypes = pX.ParameterTypesWithAnnotations;
                    xRefKinds = pX.ParameterRefKinds;
 
                    var pY = (PropertySymbol)y;
                    yParameterTypes = pY.ParameterTypesWithAnnotations;
                    yRefKinds = pY.ParameterRefKinds;
                    break;
                default:
                    throw ExceptionUtilities.UnexpectedValue(x.Kind);
            }
 
            int numParams = xParameterTypes.Length;
 
            if (yParameterTypes.Length != numParams)
            {
                return false;
            }
 
            // Compare parameters without regard for RefKind (or other modifier),
            // array rank, or unnamed array element types (e.g. int[][] == char[][]).
 
            bool sawRefKindDifference = xRefKinds.IsDefault != yRefKinds.IsDefault;
            bool sawArrayRankDifference = false;
            bool sawArrayOfArraysDifference = false;
 
            for (int i = 0; i < numParams; i++)
            {
                TypeSymbol xType = xParameterTypes[i].Type;
                TypeSymbol yType = yParameterTypes[i].Type;
 
                TypeKind typeKind = xType.TypeKind;
                if (yType.TypeKind != typeKind)
                {
                    return false;
                }
 
                if (typeKind == TypeKind.Array)
                {
                    ArrayTypeSymbol xArrayType = (ArrayTypeSymbol)xType;
                    ArrayTypeSymbol yArrayType = (ArrayTypeSymbol)yType;
 
                    sawArrayRankDifference = sawArrayRankDifference || xArrayType.Rank != yArrayType.Rank;
 
                    bool elementTypesDiffer = !TypeSymbol.Equals(xArrayType.ElementType, yArrayType.ElementType, TypeCompareKind.ConsiderEverything2);
 
                    // You might expect that only unnamed-vs-unnamed would produce a warning, but
                    // dev11 reports unnamed-vs-anything.
                    if (IsArrayOfArrays(xArrayType) || IsArrayOfArrays(yArrayType))
                    {
                        sawArrayOfArraysDifference = sawArrayOfArraysDifference || elementTypesDiffer;
                    }
                    else if (elementTypesDiffer)
                    {
                        return false;
                    }
                }
                else if (!TypeSymbol.Equals(xType, yType, TypeCompareKind.ConsiderEverything2))
                {
                    return false;
                }
 
                if (!xRefKinds.IsDefault)
                {
                    sawRefKindDifference = sawRefKindDifference || xRefKinds[i] != yRefKinds[i];
                }
            }
 
            code =
                sawArrayOfArraysDifference ? ErrorCode.WRN_CLS_OverloadUnnamed :
                sawArrayRankDifference ? ErrorCode.WRN_CLS_OverloadRefOut : // Lumping rank difference with refkind is odd, but matches dev11.
                sawRefKindDifference ? ErrorCode.WRN_CLS_OverloadRefOut :
                ErrorCode.Void;
 
            return code != ErrorCode.Void;
        }
 
        private static bool IsArrayOfArrays(ArrayTypeSymbol arrayType)
        {
            return arrayType.ElementType.Kind == SymbolKind.ArrayType;
        }
    }
}