File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\Utilities\PublicContract.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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!)));
}