|
// 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!)));
}
|