File: PublicContract.cs
Web Access
Project: ..\..\..\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle)
// 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.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis
{
    /// <summary>
    /// Helpers used for public API argument validation.
    /// </summary>
    internal static class PublicContract
    {
        // Guidance on inlining:
        // Inline implementation of condition checking but don't inline the code that is only executed on failure.
        // This approach makes the common path efficient (both execution time and code size) 
        // while keeping the rarely executed code in a separate method.
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static IEnumerable<T> RequireNonNullItems<T>([NotNull] IEnumerable<T>? sequence, string argumentName) where T : class
        {
            if (sequence == null)
            {
                throw new ArgumentNullException(argumentName);
            }
 
            if (sequence.Contains((T)null!))
            {
                ThrowArgumentItemNullException(sequence, argumentName);
            }
 
            return sequence;
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static void RequireUniqueNonNullItems<T>([NotNull] IEnumerable<T>? sequence, string argumentName) where T : class
        {
            if (sequence == null)
            {
                throw new ArgumentNullException(argumentName);
            }
 
            if (sequence.IndexOfNullOrDuplicateItem() >= 0)
            {
                ThrowArgumentItemNullOrDuplicateException(sequence, argumentName);
            }
        }
 
        /// <summary>
        /// Use to validate public API input for properties that are exposed as <see cref="IReadOnlyList{T}"/>.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static IReadOnlyList<T> ToBoxedImmutableArrayWithNonNullItems<T>(IEnumerable<T>? sequence, string argumentName) where T : class
        {
            var list = sequence.ToBoxedImmutableArray();
 
            if (list.Contains((T)null!))
            {
                ThrowArgumentItemNullException(list, argumentName);
            }
 
            return list;
        }
 
        /// <summary>
        /// Use to validate public API input for properties that are exposed as <see cref="IReadOnlyList{T}"/> and 
        /// whose items should be unique.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static IReadOnlyList<T> ToBoxedImmutableArrayWithDistinctNonNullItems<T>(IEnumerable<T>? sequence, string argumentName) where T : class
        {
            var list = sequence.ToBoxedImmutableArray();
 
            if (list.IndexOfNullOrDuplicateItem() >= 0)
            {
                ThrowArgumentItemNullOrDuplicateException(list, argumentName);
            }
 
            return list;
        }
 
        private static int IndexOfNullOrDuplicateItem<T>(this IEnumerable<T> sequence) where T : class
            => (sequence is IReadOnlyList<T> list) ? IndexOfNullOrDuplicateItem(list) : EnumeratingIndexOfNullOrDuplicateItem(sequence);
 
        private static int EnumeratingIndexOfNullOrDuplicateItem<T>(IEnumerable<T> sequence) where T : class
        {
            using var _ = PooledHashSet<T>.GetInstance(out var set);
 
            var i = 0;
            foreach (var item in sequence)
            {
                if (item is null || !set.Add(item))
                {
                    return i;
                }
 
                i++;
            }
 
            return -1;
        }
 
        private static int IndexOfNullOrDuplicateItem<T>(this IReadOnlyList<T> list) where T : class
        {
            var length = list.Count;
 
            if (length == 0)
            {
                return -1;
            }
 
            if (length == 1)
            {
                return (list[0] is null) ? 0 : -1;
            }
 
            using var _ = PooledHashSet<T>.GetInstance(out var set);
 
            for (var i = 0; i < length; i++)
            {
                var item = list[i];
                if (item is null || !set.Add(item))
                {
                    return i;
                }
            }
 
            return -1;
        }
 
        private static string MakeIndexedArgumentName(string argumentName, int index)
            => $"{argumentName}[{index}]";
 
        [DoesNotReturn]
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static void ThrowArgumentItemNullOrDuplicateException<T>(IEnumerable<T> sequence, string argumentName) where T : class
        {
            var list = sequence.ToList();
            var index = list.IndexOfNullOrDuplicateItem();
 
            argumentName = MakeIndexedArgumentName(argumentName, index);
 
            throw (list[index] is null)
                 ? new ArgumentNullException(argumentName)
                 : new ArgumentException(CompilerExtensionsResources.Specified_sequence_has_duplicate_items, argumentName);
        }
 
        [DoesNotReturn]
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static void ThrowArgumentItemNullException<T>(IEnumerable<T> sequence, string argumentName) where T : class
            => throw new ArgumentNullException(MakeIndexedArgumentName(argumentName, sequence.IndexOf(null!)));
    }
}