Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<Project>
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>13.0</LangVersion>
<!-- Use C# 14 -->
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

Expand Down
1 change: 1 addition & 0 deletions PolySharp.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<Folder Name="/Tests/">
<File Path="tests/PolySharp.NuGet/PolySharp.NuGet.csproj" />
<Project Path="tests/PolySharp.MinimumCSharpVersion.Tests/PolySharp.MinimumCSharpVersion.Tests.csproj" />
<Project Path="tests/PolySharp.OldCSharpVersion.Tests/PolySharp.OldCSharpVersion.Tests.csproj" />
<Project Path="tests/PolySharp.PolySharpUseTypeAliasForUnmanagedCallersOnlyAttribute.Tests/PolySharp.PolySharpUseTypeAliasForUnmanagedCallersOnlyAttribute.Tests.csproj" />
<Project Path="tests/PolySharp.Tests/PolySharp.Tests.csproj" />
<Project Path="tests/PolySharp.TypeForwards.Tests/PolySharp.TypeForwards.Tests.csproj" />
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ Here's an example of some of the new features that **PolySharp** can enable down
- `[OverloadResolutionPriority]` (needed for [overload resolution priority](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13#overload-resolution-priority))
- `[ParamsCollection]` (needed for [params collection](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13#params-collections))
- `[ConstantExpected]` (see [proposal](https://github.com/dotnet/runtime/issues/33771))
- Throw helper methods (only for C# 14 and above, because these polyfills require [Extension members](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14#extension-members))
- `ArgumentException.ThrowIfNullOrEmpty(string? argument, string? paramName = default)` (see [docs](https://learn.microsoft.com/dotnet/api/system.argumentexception.throwifnullorempty))
- `ArgumentException.ThrowIfNullOrWhiteSpace(string? argument, string? paramName = default)` (see [docs](https://learn.microsoft.com/dotnet/api/system.argumentexception.throwifnullorwhitespace))
- `ArgumentNullException.ThrowIfNull(object? argument, string? paramName = default)` (see [docs](https://learn.microsoft.com/dotnet/api/system.argumentnullexception.throwifnull))
- `ArgumentOutOfRangeException.ThrowIfEqual<T>(T value, T other, string? paramName = default)` (see [docs](https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception.throwifequal))
- `ArgumentOutOfRangeException.ThrowIfNotEqual<T>(T value, T other, string? paramName = default)` (see [docs](https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception.throwifnotequal))
- `ArgumentOutOfRangeException.ThrowIfGreaterThan<T>(T value, T other, string? paramName = default)` (see [docs](https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception.throwifgreaterthan))
- `ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<T>(T value, T other, string? paramName = default)` (see [docs](https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception.throwifgreaterthanorequal))
- `ArgumentOutOfRangeException.ThrowIfLessThan<T>(T value, T other, string? paramName = default)` (see [docs](https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception.throwiflessthan))
- `ArgumentOutOfRangeException.ThrowIfLessThanOrEqual<T>(T value, T other, string? paramName = default)` (see [docs](https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception.throwiflessthanorequal))
- `ObjectDisposedException.ThrowIf(bool condition, object instance)` (see [docs](https://learn.microsoft.com/dotnet/api/system.objectdisposedexception.throwif#system-objectdisposedexception-throwif(system-boolean-system-object)))
- `ObjectDisposedException.ThrowIf(bool condition, Type type)` (see [docs](https://learn.microsoft.com/dotnet/api/system.objectdisposedexception.throwif#system-objectdisposedexception-throwif(system-boolean-system-type)))

To leverage them, make sure to bump your C# language version. You can do this by setting the `<LangVersion>` MSBuild property in your project. For instance, by adding `<LangVersion>13.0</LangVersion>` (or your desired C# version) to the first `<PropertyGroup>` of your .csproj file. For more info on this, [see here](https://sergiopedri.medium.com/enabling-and-using-c-9-features-on-older-and-unsupported-runtimes-ce384d8debb), but remember that you don't need to manually copy polyfills anymore: simply adding a reference to **PolySharp** will do this for you automatically.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// <auto-generated/>
#pragma warning disable
#nullable enable annotations

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System
{
/// <summary>Provides downlevel polyfills for static methods on Exception-derived types.</summary>
internal static class ExceptionPolyfills
{
extension(global::System.ArgumentException)
{
/// <summary>Throws an exception if <paramref name="argument"/> is null or empty.</summary>
/// <param name="argument">The string argument to validate as non-null and non-empty.</param>
/// <param name="paramName">The name of the parameter with which <paramref name="argument"/> corresponds.</param>
/// <exception cref="global::System.ArgumentNullException"><paramref name="argument"/> is null.</exception>
/// <exception cref="global::System.ArgumentException"><paramref name="argument"/> is empty.</exception>
public static void ThrowIfNullOrEmpty(
[global::System.Diagnostics.CodeAnalysis.NotNull] string? argument,
[global::System.Runtime.CompilerServices.CallerArgumentExpression(nameof(argument))] string? paramName = null
)
{
if (string.IsNullOrEmpty(argument))
ThrowArgumentException(argument, paramName, "The value cannot be an empty string.");
}

/// <summary>Throws an exception if <paramref name="argument"/> is null, empty, or consists only of white-space characters.</summary>
/// <param name="argument">The string argument to validate.</param>
/// <param name="paramName">The name of the parameter with which <paramref name="argument"/> corresponds.</param>
/// <exception cref="global::System.ArgumentNullException"><paramref name="argument"/> is null.</exception>
/// <exception cref="global::System.ArgumentException"><paramref name="argument"/> is empty or consists only of white-space characters.</exception>
public static void ThrowIfNullOrWhiteSpace(
[global::System.Diagnostics.CodeAnalysis.NotNull] string? argument,
[global::System.Runtime.CompilerServices.CallerArgumentExpression(nameof(argument))] string? paramName = null
)
{
if (string.IsNullOrWhiteSpace(argument))
ThrowArgumentException(argument, paramName, "The value cannot be an empty string or composed entirely of whitespace.");
}
}

[global::System.Diagnostics.CodeAnalysis.DoesNotReturn]
private static void ThrowArgumentException(string? argument, string? paramName, string message)
{
ExceptionPolyfills.ThrowIfNull(argument, paramName);
throw new global::System.ArgumentException(message, paramName);
}

extension(global::System.ArgumentNullException)
{
/// <summary>Throws an <see cref="global::System.ArgumentNullException"/> if <paramref name="argument"/> is null.</summary>
/// <param name="argument">The reference type argument to validate as non-null.</param>
/// <param name="paramName">The name of the parameter with which <paramref name="argument"/> corresponds.</param>
/// <exception cref="global::System.ArgumentNullException"><paramref name="argument"/> is null.</exception>
public static void ThrowIfNull(
[global::System.Diagnostics.CodeAnalysis.NotNull] object? argument,
[global::System.Runtime.CompilerServices.CallerArgumentExpression(nameof(argument))] string? paramName = null
)
{
if (argument is null)
ThrowArgumentNullException(paramName);
}
}

[global::System.Diagnostics.CodeAnalysis.DoesNotReturn]
private static void ThrowArgumentNullException(string? paramName) =>
throw new global::System.ArgumentNullException(paramName);

extension(global::System.ArgumentOutOfRangeException)
{
/// <summary>Throws an <see cref="global::System.ArgumentOutOfRangeException"/> if <paramref name="value"/> is equal to <paramref name="other"/>.</summary>
/// <param name="value">The argument to validate as not equal to <paramref name="other"/>.</param>
/// <param name="other">The value to compare with <paramref name="value"/>.</param>
/// <param name="paramName">The name of the parameter with which <paramref name="value"/> corresponds.</param>
public static void ThrowIfEqual<T>(T value, T other, [global::System.Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null)
{
if (global::System.Collections.Generic.EqualityComparer<T>.Default.Equals(value, other))
ThrowArgumentOutOfRangeException(paramName, $"{paramName} ('{(object?)value ?? "null"}') must not be equal to '{(object?)other ?? "null"}'.");
}

/// <summary>Throws an <see cref="global::System.ArgumentOutOfRangeException"/> if <paramref name="value"/> is not equal to <paramref name="other"/>.</summary>
/// <param name="value">The argument to validate as equal to <paramref name="other"/>.</param>
/// <param name="other">The value to compare with <paramref name="value"/>.</param>
/// <param name="paramName">The name of the parameter with which <paramref name="value"/> corresponds.</param>
public static void ThrowIfNotEqual<T>(T value, T other, [global::System.Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null)
{
if (!global::System.Collections.Generic.EqualityComparer<T>.Default.Equals(value, other))
ThrowArgumentOutOfRangeException(paramName, $"{paramName} ('{(object?)value ?? "null"}') must be equal to '{(object?)other ?? "null"}'.");
}

/// <summary>Throws an <see cref="global::System.ArgumentOutOfRangeException"/> if <paramref name="value"/> is greater than <paramref name="other"/>.</summary>
/// <param name="value">The argument to validate as less or equal than <paramref name="other"/>.</param>
/// <param name="other">The value to compare with <paramref name="value"/>.</param>
/// <param name="paramName">The name of the parameter with which <paramref name="value"/> corresponds.</param>
public static void ThrowIfGreaterThan<T>(T value, T other, [global::System.Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null)
where T : global::System.IComparable<T>
{
if (value.CompareTo(other) > 0)
ThrowArgumentOutOfRangeException(paramName, $"{paramName} ('{value}') must be less than or equal to '{other}'.");
}

/// <summary>Throws an <see cref="global::System.ArgumentOutOfRangeException"/> if <paramref name="value"/> is greater than or equal <paramref name="other"/>.</summary>
/// <param name="value">The argument to validate as less than <paramref name="other"/>.</param>
/// <param name="other">The value to compare with <paramref name="value"/>.</param>
/// <param name="paramName">The name of the parameter with which <paramref name="value"/> corresponds.</param>
public static void ThrowIfGreaterThanOrEqual<T>(T value, T other, [global::System.Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null)
where T : global::System.IComparable<T>
{
if (value.CompareTo(other) >= 0)
ThrowArgumentOutOfRangeException(paramName, $"{paramName} ('{value}') must be less than '{other}'.");
}

/// <summary>Throws an <see cref="global::System.ArgumentOutOfRangeException"/> if <paramref name="value"/> is less than <paramref name="other"/>.</summary>
/// <param name="value">The argument to validate as greater than or equal than <paramref name="other"/>.</param>
/// <param name="other">The value to compare with <paramref name="value"/>.</param>
/// <param name="paramName">The name of the parameter with which <paramref name="value"/> corresponds.</param>
public static void ThrowIfLessThan<T>(T value, T other, [global::System.Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null)
where T : global::System.IComparable<T>
{
if (value.CompareTo(other) < 0)
ThrowArgumentOutOfRangeException(paramName, $"{paramName} ('{value}') must be greater than or equal to '{other}'.");
}

/// <summary>Throws an <see cref="global::System.ArgumentOutOfRangeException"/> if <paramref name="value"/> is less than or equal <paramref name="other"/>.</summary>
/// <param name="value">The argument to validate as greater than than <paramref name="other"/>.</param>
/// <param name="other">The value to compare with <paramref name="value"/>.</param>
/// <param name="paramName">The name of the parameter with which <paramref name="value"/> corresponds.</param>
public static void ThrowIfLessThanOrEqual<T>(T value, T other, [global::System.Runtime.CompilerServices.CallerArgumentExpression(nameof(value))] string? paramName = null)
where T : global::System.IComparable<T>
{
if (value.CompareTo(other) <= 0)
ThrowArgumentOutOfRangeException(paramName, $"{paramName} ('{value}') must be greater than '{other}'.");
}
}

[global::System.Diagnostics.CodeAnalysis.DoesNotReturn]
private static void ThrowArgumentOutOfRangeException(string? paramName, string message) =>
throw new global::System.ArgumentOutOfRangeException(paramName, message);

extension(global::System.ObjectDisposedException)
{
/// <summary>Throws an <see cref="global::System.ObjectDisposedException"/> if the specified <paramref name="condition"/> is <see langword="true"/>.</summary>
/// <param name="condition">The condition to evaluate.</param>
/// <param name="instance">The object whose type's full name should be included in any resulting <see cref="ObjectDisposedException"/>.</param>
/// <exception cref="global::System.ObjectDisposedException">The <paramref name="condition"/> is <see langword="true"/>.</exception>
public static void ThrowIf([global::System.Diagnostics.CodeAnalysis.DoesNotReturnIf(true)] bool condition, object instance)
{
if (condition)
ThrowObjectDisposedException(instance?.GetType());
}

/// <summary>Throws an <see cref="global::System.ObjectDisposedException"/> if the specified <paramref name="condition"/> is <see langword="true"/>.</summary>
/// <param name="condition">The condition to evaluate.</param>
/// <param name="type">The type whose full name should be included in any resulting <see cref="ObjectDisposedException"/>.</param>
/// <exception cref="global::System.ObjectDisposedException">The <paramref name="condition"/> is <see langword="true"/>.</exception>
public static void ThrowIf([global::System.Diagnostics.CodeAnalysis.DoesNotReturnIf(true)] bool condition, global::System.Type type)
{
if (condition)
ThrowObjectDisposedException(type);
}
}

[global::System.Diagnostics.CodeAnalysis.DoesNotReturn]
private static void ThrowObjectDisposedException(global::System.Type? type) =>
throw new global::System.ObjectDisposedException(type?.FullName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ static bool IsTypeAvailable(Compilation compilation, string name, CancellationTo
return compilation.GetTypeByMetadataName("System.ValueTuple`2") is not null;
}

// Extension members only available starting with C# 14.0
if (name is "System.ExceptionPolyfills")
{
return compilation.HasLanguageVersionAtLeastEqualTo((LanguageVersion)1400);
}

return true;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PolySharpIncludeRuntimeSupportedAttributes>true</PolySharpIncludeRuntimeSupportedAttributes>
<LangVersion>13.0</LangVersion>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\PolySharp.SourceGenerators\PolySharp.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" PrivateAssets="contentfiles;build" />
</ItemGroup>

</Project>
45 changes: 45 additions & 0 deletions tests/PolySharp.Tests/LanguageFeatures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,48 @@ public static void AnotherCpuIntrinsic([ConstantExpected(Min = 0, Max = 8)] int
{
}
}

internal class ExceptionPolyfillsTests : IDisposable
{
private bool disposedValue = false;
private readonly IDisposable disposable;

public ExceptionPolyfillsTests(IDisposable disposable)
{
ArgumentNullException.ThrowIfNull(disposable);
this.disposable = disposable;
}

protected virtual void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (disposing)
{
this.disposable.Dispose();
}

this.disposedValue = true;
}
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

public void Connect()
{
ObjectDisposedException.ThrowIf(this.disposedValue, this);

ArgumentException.ThrowIfNullOrEmpty("foo");
ArgumentException.ThrowIfNullOrWhiteSpace("foo");
ArgumentOutOfRangeException.ThrowIfEqual(1, 0);
ArgumentOutOfRangeException.ThrowIfNotEqual(1, 1);
ArgumentOutOfRangeException.ThrowIfGreaterThan(0, 1);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(0, 1);
ArgumentOutOfRangeException.ThrowIfLessThan(1, 0);
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(1, 0);
}
}