Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-stryker": {
"version": "5.0.0",
"version": "5.1.0",
"commands": [
"dotnet-stryker"
],
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/DotMaybe/Json/MaybeJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ public override Maybe<T> 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<T>.Of(value);
}

/// <inheritdoc/>
Expand Down
10 changes: 5 additions & 5 deletions src/DotMaybe/Maybe.Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ public ValueTask<Maybe<TResult>> MapAsync<TResult>(Func<T, Task<TResult>> map)
where TResult : notnull
{
ArgumentNullException.ThrowIfNull(map);
return _isSome ? MapValueAsync(_value, map) : ValueTask.FromResult<Maybe<TResult>>(none);
return _isSome ? MapValueAsync(_value, map) : ValueTask.FromResult<Maybe<TResult>>(default);

static async ValueTask<Maybe<TResult>> MapValueAsync(T value, Func<T, Task<TResult>> map) =>
await map(value).ConfigureAwait(false);
Maybe<TResult>.Of(await map(value).ConfigureAwait(false));
}

/// <summary>
Expand All @@ -95,7 +95,7 @@ public ValueTask<Maybe<TResult>> BindAsync<TResult>(Func<T, Task<Maybe<TResult>>
where TResult : notnull
{
ArgumentNullException.ThrowIfNull(bind);
return _isSome ? Start(bind, _value) : ValueTask.FromResult<Maybe<TResult>>(none);
return _isSome ? Start(bind, _value) : ValueTask.FromResult<Maybe<TResult>>(default);
}

/// <summary>
Expand All @@ -108,10 +108,10 @@ public ValueTask<Maybe<TResult>> BindAsync<TResult>(Func<T, Task<Maybe<TResult>>
public ValueTask<Maybe<T>> FilterAsync(Func<T, Task<bool>> predicate)
{
ArgumentNullException.ThrowIfNull(predicate);
return _isSome ? FilterValueAsync(_value, predicate) : ValueTask.FromResult<Maybe<T>>(none);
return _isSome ? FilterValueAsync(_value, predicate) : ValueTask.FromResult<Maybe<T>>(default);

static async ValueTask<Maybe<T>> FilterValueAsync(T value, Func<T, Task<bool>> predicate) =>
await predicate(value).ConfigureAwait(false) ? value : none;
await predicate(value).ConfigureAwait(false) ? Maybe<T>.Of(value) : default;
}

private static ValueTask<TResult> Start<TArg, TResult>(Func<TArg, Task<TResult>> step, TArg arg)
Expand Down
24 changes: 12 additions & 12 deletions src/DotMaybe/Maybe.Collections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,13 @@ public static Maybe<IReadOnlyList<T>> Sequence<T>(this IEnumerable<Maybe<T>> sou
{
if (!maybe.TryGetSome(out var value))
{
return none;
return default;
}

values.Add(value);
}

return values;
return Maybe<IReadOnlyList<T>>.Of(values);
}

/// <summary>
Expand Down Expand Up @@ -124,10 +124,10 @@ public static Maybe<T> FirstOrNone<T>(this IEnumerable<T> source)
ArgumentNullException.ThrowIfNull(source);
foreach (var value in source)
{
return value;
return Maybe<T>.Of(value);
}

return none;
return default;
}

/// <summary>The first element that satisfies <paramref name="predicate"/>, or <see cref="None"/>.</summary>
Expand All @@ -146,11 +146,11 @@ public static Maybe<T> FirstOrNone<T>(this IEnumerable<T> source, Func<T, bool>
{
if (predicate(value))
{
return value;
return Maybe<T>.Of(value);
}
}

return none;
return default;
}

/// <summary>
Expand All @@ -171,11 +171,11 @@ public static Maybe<T> SingleOrNone<T>(this IEnumerable<T> 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<T>.Of(first);
}

/// <summary>
Expand All @@ -192,7 +192,7 @@ public static Maybe<T> SingleOrNone<T>(this IEnumerable<T> source, Func<T, bool>
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(predicate);
Maybe<T> candidate = none;
Maybe<T> candidate = default;
var found = false;
foreach (var value in source)
{
Expand All @@ -203,10 +203,10 @@ public static Maybe<T> SingleOrNone<T>(this IEnumerable<T> source, Func<T, bool>

if (found)
{
return none;
return default;
}

candidate = value;
candidate = Maybe<T>.Of(value);
found = true;
}

Expand All @@ -232,6 +232,6 @@ public static Maybe<TValue> GetValueOrNone<TKey, TValue>(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<TValue>.Of(value) : default;
}
}
8 changes: 4 additions & 4 deletions src/DotMaybe/Maybe.Combinators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ public Maybe<T> OrElse(Func<Maybe<T>> alternative)
{
if (!_isSome || !other._isSome)
{
return none;
return default;
}

return (_value, other._value);
return Maybe<(T, TOther)>.Of((_value, other._value));
}

/// <summary>
Expand All @@ -68,10 +68,10 @@ public Maybe<TResult> Zip<TOther, TResult>(Maybe<TOther> other, Func<T, TOther,
ArgumentNullException.ThrowIfNull(combine);
if (!_isSome || !other._isSome)
{
return none;
return default;
}

return combine(_value, other._value);
return Maybe<TResult>.Of(combine(_value, other._value));
}

/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions src/DotMaybe/Maybe.Interop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static partial class Maybe
/// <param name="value">The nullable value.</param>
/// <returns>The value, or <see cref="None"/>.</returns>
public static Maybe<T> FromNullable<T>(T? value)
where T : struct => value.HasValue ? value.Value : none;
where T : struct => value.HasValue ? Maybe<T>.Of(value.Value) : default;

/// <summary>
/// A maybe from a nullable reference: <see langword="null"/> becomes <see cref="None"/>.
Expand All @@ -27,7 +27,7 @@ public static Maybe<T> FromNullable<T>(T? value)
/// <param name="value">The nullable reference.</param>
/// <returns>The value, or <see cref="None"/>.</returns>
public static Maybe<T> FromNullable<T>(T? value)
where T : class => value is not null ? value : none;
where T : class => value is not null ? Maybe<T>.Of(value) : default;

/// <summary>
/// Parses <paramref name="text"/> with <c>T.TryParse</c>: the parsed value, or <see cref="None"/> when the text is
Expand All @@ -38,7 +38,7 @@ public static Maybe<T> FromNullable<T>(T? value)
/// <param name="text">The text to parse.</param>
/// <returns>The parsed value, or <see cref="None"/>.</returns>
public static Maybe<T> Parse<T>(string? text)
where T : notnull, IParsable<T> => T.TryParse(text, null, out var value) ? value : none;
where T : notnull, IParsable<T> => T.TryParse(text, null, out var value) ? Maybe<T>.Of(value) : default;

/// <summary>
/// Parses <paramref name="text"/> with <c>T.TryParse</c> and <paramref name="provider"/>: the parsed value, or
Expand All @@ -49,5 +49,5 @@ public static Maybe<T> Parse<T>(string? text)
/// <param name="provider">Culture-specific formatting information, or <see langword="null"/> for the current culture.</param>
/// <returns>The parsed value, or <see cref="None"/>.</returns>
public static Maybe<T> Parse<T>(string? text, IFormatProvider? provider)
where T : notnull, IParsable<T> => T.TryParse(text, provider, out var value) ? value : none;
where T : notnull, IParsable<T> => T.TryParse(text, provider, out var value) ? Maybe<T>.Of(value) : default;
}
2 changes: 1 addition & 1 deletion src/DotMaybe/Maybe.Static.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ public static partial class Maybe
/// <param name="nested">The maybe of a maybe.</param>
/// <returns>The inner maybe, or <see cref="None"/>.</returns>
public static Maybe<T> Flatten<T>(this Maybe<Maybe<T>> nested)
where T : notnull => nested.TryGetSome(out var value) ? value : none;
where T : notnull => nested.TryGetSome(out var value) ? value : default;
}
17 changes: 11 additions & 6 deletions src/DotMaybe/Maybe.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public Maybe<TResult> Map<TResult>(Func<T, TResult> map)
where TResult : notnull
{
ArgumentNullException.ThrowIfNull(map);
return _isSome ? map(_value) : none;
return _isSome ? Maybe<TResult>.Of(map(_value)) : default;
}

/// <summary>
Expand All @@ -179,7 +179,7 @@ public Maybe<TResult> Bind<TResult>(Func<T, Maybe<TResult>> bind)
where TResult : notnull
{
ArgumentNullException.ThrowIfNull(bind);
return _isSome ? bind(_value) : none;
return _isSome ? bind(_value) : default;
}

/// <summary>
Expand All @@ -194,7 +194,7 @@ public Maybe<TResult> Bind<TResult>(Func<T, Maybe<TResult>> bind)
public Maybe<T> Filter(Func<T, bool> predicate)
{
ArgumentNullException.ThrowIfNull(predicate);
return _isSome && predicate(_value) ? this : none;
return _isSome && predicate(_value) ? this : default;
}

/// <summary>
Expand Down Expand Up @@ -247,15 +247,15 @@ public Maybe<TResult> SelectMany<TIntermediate, TResult>(
ArgumentNullException.ThrowIfNull(resultSelector);
if (!_isSome)
{
return none;
return default;
}

if (selector(_value).TryGetSome(out var selected))
{
return resultSelector(_value, selected);
return Maybe<TResult>.Of(resultSelector(_value, selected));
}

return none;
return default;
}

/// <summary>
Expand Down Expand Up @@ -308,6 +308,11 @@ public bool Equals(Maybe<T> other) =>
/// <returns>A text representation of this maybe.</returns>
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<T> Of(T value) => IUnionMembers.Create(value);

internal bool TryGetSome([MaybeNullWhen(false)] out T value)
{
value = _value;
Expand Down
6 changes: 3 additions & 3 deletions src/DotMaybe/MaybeTaskExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ public static ValueTask<Maybe<TResult>> MapAsync<T, TResult>(this ValueTask<Mayb
static async ValueTask<Maybe<TResult>> MapAfter(ValueTask<Maybe<T>> source, Func<T, TResult> map)
{
var maybe = await source.ConfigureAwait(false);
return maybe.TryGetSome(out var value) ? map(value) : none;
return maybe.TryGetSome(out var value) ? Maybe<TResult>.Of(map(value)) : default;
}
}

Expand Down Expand Up @@ -263,7 +263,7 @@ public static ValueTask<Maybe<TResult>> BindAsync<T, TResult>(this ValueTask<May
static async ValueTask<Maybe<TResult>> BindAfter(ValueTask<Maybe<T>> source, Func<T, Maybe<TResult>> 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;
}
}

Expand Down Expand Up @@ -302,7 +302,7 @@ public static ValueTask<Maybe<T>> FilterAsync<T>(this ValueTask<Maybe<T>> source
static async ValueTask<Maybe<T>> FilterAfter(ValueTask<Maybe<T>> source, Func<T, bool> 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<T>.Of(value) : default;
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/DotMaybe/MaybeTaskQueryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,15 @@ public static ValueTask<Maybe<TResult>> SelectMany<T, TIntermediate, TResult>(
ArgumentNullException.ThrowIfNull(resultSelector);
return source.TryGetSome(out var value)
? Project(value, selector, resultSelector)
: ValueTask.FromResult<Maybe<TResult>>(none);
: ValueTask.FromResult<Maybe<TResult>>(default);

static async ValueTask<Maybe<TResult>> Project(
T value,
Func<T, Task<Maybe<TIntermediate>>> selector,
Func<T, TIntermediate, TResult> resultSelector) =>
(await selector(value).ConfigureAwait(false)).TryGetSome(out var selected)
? resultSelector(value, selected)
: none;
? Maybe<TResult>.Of(resultSelector(value, selected))
: default;
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion tests/DotMaybe.PropertyTests/JsonModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace DotMaybe.PropertyTests;

/// <summary>A document with optional properties, as an API would receive or return it.</summary>
public sealed record Profile(string Name, Maybe<string> Nickname, Maybe<int> Age);
internal sealed record Profile(string Name, Maybe<string> Nickname, Maybe<int> Age);

/// <summary>An object value inside a maybe.</summary>
public sealed record Person(string Name, int Age);
Expand Down
Loading