diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index a94bdd0..579fd30 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-stryker": { - "version": "5.0.0", + "version": "5.1.0", "commands": [ "dotnet-stryker" ], diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3efa4a0..a662e57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,13 @@ jobs: path: artifacts/test-results if-no-files-found: ignore + # Stryker's bundled Roslyn still treats unions as a preview feature (CS8652): a union conversion or pattern in + # src/ makes every mutated build fail. Building the library as C# 14 finds them first. Runs last: it rebuilds + # the library with other settings. + - name: Check the library builds without union conversions (for Stryker) + if: runner.os == 'Linux' + run: dotnet build src/DotMaybe/DotMaybe.csproj --configuration Release --no-restore -p:LangVersion=14 + native-aot: name: Native AOT smoke test runs-on: ubuntu-latest diff --git a/src/DotMaybe/Json/MaybeJsonConverter.cs b/src/DotMaybe/Json/MaybeJsonConverter.cs index ca5a321..a1c059e 100644 --- a/src/DotMaybe/Json/MaybeJsonConverter.cs +++ b/src/DotMaybe/Json/MaybeJsonConverter.cs @@ -46,11 +46,11 @@ public override Maybe Read(ref Utf8JsonReader reader, Type typeToConvert, Jso { if (reader.TokenType == JsonTokenType.Null) { - return none; + return default; } var value = JsonSerializer.Deserialize(ref reader, ValueInfo(options)); - return value is null ? none : value; + return value is null ? default : Maybe.Of(value); } /// diff --git a/src/DotMaybe/Maybe.Async.cs b/src/DotMaybe/Maybe.Async.cs index 232a637..e503400 100644 --- a/src/DotMaybe/Maybe.Async.cs +++ b/src/DotMaybe/Maybe.Async.cs @@ -77,10 +77,10 @@ public ValueTask> MapAsync(Func> map) where TResult : notnull { ArgumentNullException.ThrowIfNull(map); - return _isSome ? MapValueAsync(_value, map) : ValueTask.FromResult>(none); + return _isSome ? MapValueAsync(_value, map) : ValueTask.FromResult>(default); static async ValueTask> MapValueAsync(T value, Func> map) => - await map(value).ConfigureAwait(false); + Maybe.Of(await map(value).ConfigureAwait(false)); } /// @@ -95,7 +95,7 @@ public ValueTask> BindAsync(Func> where TResult : notnull { ArgumentNullException.ThrowIfNull(bind); - return _isSome ? Start(bind, _value) : ValueTask.FromResult>(none); + return _isSome ? Start(bind, _value) : ValueTask.FromResult>(default); } /// @@ -108,10 +108,10 @@ public ValueTask> BindAsync(Func> public ValueTask> FilterAsync(Func> predicate) { ArgumentNullException.ThrowIfNull(predicate); - return _isSome ? FilterValueAsync(_value, predicate) : ValueTask.FromResult>(none); + return _isSome ? FilterValueAsync(_value, predicate) : ValueTask.FromResult>(default); static async ValueTask> FilterValueAsync(T value, Func> predicate) => - await predicate(value).ConfigureAwait(false) ? value : none; + await predicate(value).ConfigureAwait(false) ? Maybe.Of(value) : default; } private static ValueTask Start(Func> step, TArg arg) diff --git a/src/DotMaybe/Maybe.Collections.cs b/src/DotMaybe/Maybe.Collections.cs index dd22699..9e183a8 100644 --- a/src/DotMaybe/Maybe.Collections.cs +++ b/src/DotMaybe/Maybe.Collections.cs @@ -78,13 +78,13 @@ public static Maybe> Sequence(this IEnumerable> sou { if (!maybe.TryGetSome(out var value)) { - return none; + return default; } values.Add(value); } - return values; + return Maybe>.Of(values); } /// @@ -124,10 +124,10 @@ public static Maybe FirstOrNone(this IEnumerable source) ArgumentNullException.ThrowIfNull(source); foreach (var value in source) { - return value; + return Maybe.Of(value); } - return none; + return default; } /// The first element that satisfies , or . @@ -146,11 +146,11 @@ public static Maybe FirstOrNone(this IEnumerable source, Func { if (predicate(value)) { - return value; + return Maybe.Of(value); } } - return none; + return default; } /// @@ -171,11 +171,11 @@ public static Maybe SingleOrNone(this IEnumerable source) using var values = source.GetEnumerator(); if (!values.MoveNext()) { - return none; + return default; } var first = values.Current; - return values.MoveNext() ? none : first; + return values.MoveNext() ? default : Maybe.Of(first); } /// @@ -192,7 +192,7 @@ public static Maybe SingleOrNone(this IEnumerable source, Func { ArgumentNullException.ThrowIfNull(source); ArgumentNullException.ThrowIfNull(predicate); - Maybe candidate = none; + Maybe candidate = default; var found = false; foreach (var value in source) { @@ -203,10 +203,10 @@ public static Maybe SingleOrNone(this IEnumerable source, Func if (found) { - return none; + return default; } - candidate = value; + candidate = Maybe.Of(value); found = true; } @@ -232,6 +232,6 @@ public static Maybe GetValueOrNone(this IReadOnlyDictionar where TValue : notnull { ArgumentNullException.ThrowIfNull(dictionary); - return dictionary.TryGetValue(key, out var value) ? value : none; + return dictionary.TryGetValue(key, out var value) ? Maybe.Of(value) : default; } } diff --git a/src/DotMaybe/Maybe.Combinators.cs b/src/DotMaybe/Maybe.Combinators.cs index a22d900..b3ceea5 100644 --- a/src/DotMaybe/Maybe.Combinators.cs +++ b/src/DotMaybe/Maybe.Combinators.cs @@ -42,10 +42,10 @@ public Maybe OrElse(Func> alternative) { if (!_isSome || !other._isSome) { - return none; + return default; } - return (_value, other._value); + return Maybe<(T, TOther)>.Of((_value, other._value)); } /// @@ -68,10 +68,10 @@ public Maybe Zip(Maybe other, Func.Of(combine(_value, other._value)); } /// diff --git a/src/DotMaybe/Maybe.Interop.cs b/src/DotMaybe/Maybe.Interop.cs index d9b3962..cd7ae1d 100644 --- a/src/DotMaybe/Maybe.Interop.cs +++ b/src/DotMaybe/Maybe.Interop.cs @@ -14,7 +14,7 @@ public static partial class Maybe /// The nullable value. /// The value, or . public static Maybe FromNullable(T? value) - where T : struct => value.HasValue ? value.Value : none; + where T : struct => value.HasValue ? Maybe.Of(value.Value) : default; /// /// A maybe from a nullable reference: becomes . @@ -27,7 +27,7 @@ public static Maybe FromNullable(T? value) /// The nullable reference. /// The value, or . public static Maybe FromNullable(T? value) - where T : class => value is not null ? value : none; + where T : class => value is not null ? Maybe.Of(value) : default; /// /// Parses with T.TryParse: the parsed value, or when the text is @@ -38,7 +38,7 @@ public static Maybe FromNullable(T? value) /// The text to parse. /// The parsed value, or . public static Maybe Parse(string? text) - where T : notnull, IParsable => T.TryParse(text, null, out var value) ? value : none; + where T : notnull, IParsable => T.TryParse(text, null, out var value) ? Maybe.Of(value) : default; /// /// Parses with T.TryParse and : the parsed value, or @@ -49,5 +49,5 @@ public static Maybe Parse(string? text) /// Culture-specific formatting information, or for the current culture. /// The parsed value, or . public static Maybe Parse(string? text, IFormatProvider? provider) - where T : notnull, IParsable => T.TryParse(text, provider, out var value) ? value : none; + where T : notnull, IParsable => T.TryParse(text, provider, out var value) ? Maybe.Of(value) : default; } diff --git a/src/DotMaybe/Maybe.Static.cs b/src/DotMaybe/Maybe.Static.cs index 3fd8d83..0b5a694 100644 --- a/src/DotMaybe/Maybe.Static.cs +++ b/src/DotMaybe/Maybe.Static.cs @@ -19,5 +19,5 @@ public static partial class Maybe /// The maybe of a maybe. /// The inner maybe, or . public static Maybe Flatten(this Maybe> nested) - where T : notnull => nested.TryGetSome(out var value) ? value : none; + where T : notnull => nested.TryGetSome(out var value) ? value : default; } diff --git a/src/DotMaybe/Maybe.cs b/src/DotMaybe/Maybe.cs index 069daa1..67cacc5 100644 --- a/src/DotMaybe/Maybe.cs +++ b/src/DotMaybe/Maybe.cs @@ -161,7 +161,7 @@ public Maybe Map(Func map) where TResult : notnull { ArgumentNullException.ThrowIfNull(map); - return _isSome ? map(_value) : none; + return _isSome ? Maybe.Of(map(_value)) : default; } /// @@ -179,7 +179,7 @@ public Maybe Bind(Func> bind) where TResult : notnull { ArgumentNullException.ThrowIfNull(bind); - return _isSome ? bind(_value) : none; + return _isSome ? bind(_value) : default; } /// @@ -194,7 +194,7 @@ public Maybe Bind(Func> bind) public Maybe Filter(Func predicate) { ArgumentNullException.ThrowIfNull(predicate); - return _isSome && predicate(_value) ? this : none; + return _isSome && predicate(_value) ? this : default; } /// @@ -247,15 +247,15 @@ public Maybe SelectMany( ArgumentNullException.ThrowIfNull(resultSelector); if (!_isSome) { - return none; + return default; } if (selector(_value).TryGetSome(out var selected)) { - return resultSelector(_value, selected); + return Maybe.Of(resultSelector(_value, selected)); } - return none; + return default; } /// @@ -308,6 +308,11 @@ public bool Equals(Maybe other) => /// A text representation of this maybe. public override string ToString() => _isSome ? $"Some({_value})" : "None"; + // Library code builds maybes through Of and default (None), never through union conversions or union patterns: + // Stryker's bundled Roslyn still treats unions as a preview feature (CS8652) and cannot compile a mutated file that + // uses them. CI builds the library as C# 14 to keep it that way. + internal static Maybe Of(T value) => IUnionMembers.Create(value); + internal bool TryGetSome([MaybeNullWhen(false)] out T value) { value = _value; diff --git a/src/DotMaybe/MaybeTaskExtensions.cs b/src/DotMaybe/MaybeTaskExtensions.cs index ed69546..1e608b7 100644 --- a/src/DotMaybe/MaybeTaskExtensions.cs +++ b/src/DotMaybe/MaybeTaskExtensions.cs @@ -224,7 +224,7 @@ public static ValueTask> MapAsync(this ValueTask> MapAfter(ValueTask> source, Func map) { var maybe = await source.ConfigureAwait(false); - return maybe.TryGetSome(out var value) ? map(value) : none; + return maybe.TryGetSome(out var value) ? Maybe.Of(map(value)) : default; } } @@ -263,7 +263,7 @@ public static ValueTask> BindAsync(this ValueTask> BindAfter(ValueTask> source, Func> bind) { var maybe = await source.ConfigureAwait(false); - return maybe.TryGetSome(out var value) ? bind(value) : none; + return maybe.TryGetSome(out var value) ? bind(value) : default; } } @@ -302,7 +302,7 @@ public static ValueTask> FilterAsync(this ValueTask> source static async ValueTask> FilterAfter(ValueTask> source, Func predicate) { var maybe = await source.ConfigureAwait(false); - return maybe.TryGetSome(out var value) && predicate(value) ? value : none; + return maybe.TryGetSome(out var value) && predicate(value) ? Maybe.Of(value) : default; } } diff --git a/src/DotMaybe/MaybeTaskQueryExtensions.cs b/src/DotMaybe/MaybeTaskQueryExtensions.cs index a096720..76a55bb 100644 --- a/src/DotMaybe/MaybeTaskQueryExtensions.cs +++ b/src/DotMaybe/MaybeTaskQueryExtensions.cs @@ -102,15 +102,15 @@ public static ValueTask> SelectMany( ArgumentNullException.ThrowIfNull(resultSelector); return source.TryGetSome(out var value) ? Project(value, selector, resultSelector) - : ValueTask.FromResult>(none); + : ValueTask.FromResult>(default); static async ValueTask> Project( T value, Func>> selector, Func resultSelector) => (await selector(value).ConfigureAwait(false)).TryGetSome(out var selected) - ? resultSelector(value, selected) - : none; + ? Maybe.Of(resultSelector(value, selected)) + : default; } /// diff --git a/tests/DotMaybe.PropertyTests/JsonModels.cs b/tests/DotMaybe.PropertyTests/JsonModels.cs index 4189656..3bf2781 100644 --- a/tests/DotMaybe.PropertyTests/JsonModels.cs +++ b/tests/DotMaybe.PropertyTests/JsonModels.cs @@ -3,7 +3,7 @@ namespace DotMaybe.PropertyTests; /// A document with optional properties, as an API would receive or return it. -public sealed record Profile(string Name, Maybe Nickname, Maybe Age); +internal sealed record Profile(string Name, Maybe Nickname, Maybe Age); /// An object value inside a maybe. public sealed record Person(string Name, int Age);