From 5c9b8a9cc7477672ad854654475ab23d68acd249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Tue, 7 Jan 2025 12:47:11 +0100 Subject: [PATCH 01/13] Update to .NET 9 --- .../Funcky.Analyzers.Test/Funcky.Analyzers.Test.csproj | 2 +- Funcky.Async.Test/Funcky.Async.Test.csproj | 2 +- Funcky.Async/Funcky.Async.csproj | 2 +- .../Funcky.SourceGenerator.Test.csproj | 2 +- Funcky.Test/Funcky.Test.csproj | 2 +- Funcky.Xunit.Test/Funcky.Xunit.Test.csproj | 2 +- Funcky/Funcky.csproj | 4 ++-- global.json | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Funcky.Analyzers/Funcky.Analyzers.Test/Funcky.Analyzers.Test.csproj b/Funcky.Analyzers/Funcky.Analyzers.Test/Funcky.Analyzers.Test.csproj index 3213f1ba..34e1bd7e 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.Test/Funcky.Analyzers.Test.csproj +++ b/Funcky.Analyzers/Funcky.Analyzers.Test/Funcky.Analyzers.Test.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 true true diff --git a/Funcky.Async.Test/Funcky.Async.Test.csproj b/Funcky.Async.Test/Funcky.Async.Test.csproj index fe3b8cd9..d956a47e 100644 --- a/Funcky.Async.Test/Funcky.Async.Test.csproj +++ b/Funcky.Async.Test/Funcky.Async.Test.csproj @@ -1,6 +1,6 @@ - net8.0;net7.0 + net9.0;net8.0;net7.0 preview enable false diff --git a/Funcky.Async/Funcky.Async.csproj b/Funcky.Async/Funcky.Async.csproj index a408cdfd..30b82f4a 100644 --- a/Funcky.Async/Funcky.Async.csproj +++ b/Funcky.Async/Funcky.Async.csproj @@ -1,6 +1,6 @@ - net8.0;net5.0;netstandard2.1;netstandard2.0 + net9.0;net8.0;net5.0;netstandard2.1;netstandard2.0 preview enable Extends Funcky with support for IAsyncEnumerable and Tasks. diff --git a/Funcky.SourceGenerator.Test/Funcky.SourceGenerator.Test.csproj b/Funcky.SourceGenerator.Test/Funcky.SourceGenerator.Test.csproj index 26d891a9..b50fdf5a 100644 --- a/Funcky.SourceGenerator.Test/Funcky.SourceGenerator.Test.csproj +++ b/Funcky.SourceGenerator.Test/Funcky.SourceGenerator.Test.csproj @@ -3,7 +3,7 @@ Funcky.SourceGenerator.Test Funcky.SourceGenerator.Test - net8.0 + net9.0 enable enable preview diff --git a/Funcky.Test/Funcky.Test.csproj b/Funcky.Test/Funcky.Test.csproj index 9c8ba337..35833cc5 100644 --- a/Funcky.Test/Funcky.Test.csproj +++ b/Funcky.Test/Funcky.Test.csproj @@ -1,6 +1,6 @@ - net8.0;net7.0;net6.0;net5.0;netcoreapp3.1 + net9.0;net8.0;net7.0;net6.0;net5.0;netcoreapp3.1 $(TargetFrameworks);net4.8 preview enable diff --git a/Funcky.Xunit.Test/Funcky.Xunit.Test.csproj b/Funcky.Xunit.Test/Funcky.Xunit.Test.csproj index fca5771c..e23fb008 100644 --- a/Funcky.Xunit.Test/Funcky.Xunit.Test.csproj +++ b/Funcky.Xunit.Test/Funcky.Xunit.Test.csproj @@ -1,6 +1,6 @@ - net8.0 + net9.0 preview enable false diff --git a/Funcky/Funcky.csproj b/Funcky/Funcky.csproj index 1d7fcff8..e35de13e 100644 --- a/Funcky/Funcky.csproj +++ b/Funcky/Funcky.csproj @@ -1,6 +1,6 @@ - net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;netstandard2.0;netstandard2.1 + net9.0;net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;netstandard2.0;netstandard2.1 preview enable Funcky @@ -18,7 +18,7 @@ $(DefineConstants);CONTRACTS_FULL - net8.0 + net9.0 diff --git a/global.json b/global.json index 3f809cbf..c6fbdb46 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", + "version": "9.0.100", "rollForward": "feature" } } From f0e9e50e5e417b1619321805ce24aa3c8764cba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Tue, 7 Jan 2025 13:05:41 +0100 Subject: [PATCH 02/13] Call .ConfigureAwait and pass cancellation token --- .../Extensions/AsyncEnumerableExtensions/Merge.cs | 2 +- .../AsyncEnumerableExtensions/PowerSet.cs | 13 +++++++------ .../AsyncEnumerableExtensions/WithPrevious.cs | 10 ++++++++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Funcky.Async/Extensions/AsyncEnumerableExtensions/Merge.cs b/Funcky.Async/Extensions/AsyncEnumerableExtensions/Merge.cs index 479ab356..1460aaba 100644 --- a/Funcky.Async/Extensions/AsyncEnumerableExtensions/Merge.cs +++ b/Funcky.Async/Extensions/AsyncEnumerableExtensions/Merge.cs @@ -61,7 +61,7 @@ public static async IAsyncEnumerable Merge(this IEnumerable await HasMoreElements(f).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false)), GetMergeComparer(comparer))) + await foreach (var element in MergeEnumerators(enumerators.RemoveRange(await enumerators.ToAsyncEnumerable().WhereAwait(async f => await HasMoreElements(f).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false)), GetMergeComparer(comparer)).ConfigureAwait(false)) { yield return element; } diff --git a/Funcky.Async/Extensions/AsyncEnumerableExtensions/PowerSet.cs b/Funcky.Async/Extensions/AsyncEnumerableExtensions/PowerSet.cs index a06f1970..66233fb4 100644 --- a/Funcky.Async/Extensions/AsyncEnumerableExtensions/PowerSet.cs +++ b/Funcky.Async/Extensions/AsyncEnumerableExtensions/PowerSet.cs @@ -18,21 +18,22 @@ public static IAsyncEnumerable> PowerSet(this IAsy private static async IAsyncEnumerable> PowerSetInternal(this IAsyncEnumerable source, [EnumeratorCancellation] CancellationToken cancellationToken = default) { - var asyncEnumerator = source.GetAsyncEnumerator(cancellationToken); - await using var sourceEnumerator = asyncEnumerator.ConfigureAwait(false); +#pragma warning disable CA2007 // Configured via IAsyncEnumerable extension + await using var asyncEnumerator = source.ConfigureAwait(false).WithCancellation(cancellationToken).GetAsyncEnumerator(); +#pragma warning restore CA2007 - await foreach (var set in PowerSetEnumerator(asyncEnumerator).WithCancellation(cancellationToken)) + await foreach (var set in PowerSetEnumerator(asyncEnumerator).WithCancellation(cancellationToken).ConfigureAwait(false)) { yield return set; } } - private static async IAsyncEnumerable> PowerSetEnumerator(this IAsyncEnumerator source) + private static async IAsyncEnumerable> PowerSetEnumerator(this ConfiguredCancelableAsyncEnumerable.Enumerator source) { - if (await source.MoveNextAsync().ConfigureAwait(false)) + if (await source.MoveNextAsync()) { var temp = source.Current; - await foreach (var set in source.PowerSetEnumerator()) + await foreach (var set in source.PowerSetEnumerator().ConfigureAwait(false)) { yield return set; yield return set.Push(temp); diff --git a/Funcky.Async/Extensions/AsyncEnumerableExtensions/WithPrevious.cs b/Funcky.Async/Extensions/AsyncEnumerableExtensions/WithPrevious.cs index 01ce13d8..b174482a 100644 --- a/Funcky.Async/Extensions/AsyncEnumerableExtensions/WithPrevious.cs +++ b/Funcky.Async/Extensions/AsyncEnumerableExtensions/WithPrevious.cs @@ -1,3 +1,5 @@ +using System.Runtime.CompilerServices; + namespace Funcky.Extensions; public static partial class AsyncEnumerableExtensions @@ -5,12 +7,16 @@ public static partial class AsyncEnumerableExtensions /// Returns a sequence mapping each element together with its predecessor. /// Thrown when any value in is . [Pure] - public static async IAsyncEnumerable> WithPrevious(this IAsyncEnumerable source) + public static IAsyncEnumerable> WithPrevious(this IAsyncEnumerable source) + where TSource : notnull + => source.WithPreviousInternal(); + + private static async IAsyncEnumerable> WithPreviousInternal(this IAsyncEnumerable source, [EnumeratorCancellation] CancellationToken cancellationToken = default) where TSource : notnull { var previous = Option.None; - await foreach (var value in source) + await foreach (var value in source.ConfigureAwait(false).WithCancellation(cancellationToken)) { yield return new ValueWithPrevious(value, previous); previous = value; From 483142ed33112677c6c7732adc3a0ee2df58515c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Tue, 7 Jan 2025 13:38:32 +0100 Subject: [PATCH 03/13] Add new parse extensions --- FrameworkFeatureConstants.props | 3 +++ .../ParseExtensions.AssemblyNameInfo.cs | 9 +++++++++ .../ParseExtensions/ParseExtensions.TypeName.cs | 16 ++++++++++++++++ Funcky/PublicAPI.Unshipped.txt | 2 ++ 4 files changed, 30 insertions(+) create mode 100644 Funcky/Extensions/ParseExtensions/ParseExtensions.AssemblyNameInfo.cs create mode 100644 Funcky/Extensions/ParseExtensions/ParseExtensions.TypeName.cs diff --git a/FrameworkFeatureConstants.props b/FrameworkFeatureConstants.props index d37daa6d..b1e63c81 100644 --- a/FrameworkFeatureConstants.props +++ b/FrameworkFeatureConstants.props @@ -18,4 +18,7 @@ $(DefineConstants);RANDOM_SHUFFLE;UTF8_SPAN_PARSABLE + + $(DefineConstants);REFLECTION_ASSEMBLY_NAME_INFO;REFLECTION_TYPE_NAME + diff --git a/Funcky/Extensions/ParseExtensions/ParseExtensions.AssemblyNameInfo.cs b/Funcky/Extensions/ParseExtensions/ParseExtensions.AssemblyNameInfo.cs new file mode 100644 index 00000000..0797d43b --- /dev/null +++ b/Funcky/Extensions/ParseExtensions/ParseExtensions.AssemblyNameInfo.cs @@ -0,0 +1,9 @@ +#if REFLECTION_ASSEMBLY_NAME_INFO +using System.Reflection.Metadata; +using Funcky.Internal; + +namespace Funcky.Extensions; + +[OrNoneFromTryPattern(typeof(AssemblyNameInfo), nameof(AssemblyNameInfo.TryParse))] +public static partial class ParseExtensions; +#endif diff --git a/Funcky/Extensions/ParseExtensions/ParseExtensions.TypeName.cs b/Funcky/Extensions/ParseExtensions/ParseExtensions.TypeName.cs new file mode 100644 index 00000000..69d010b5 --- /dev/null +++ b/Funcky/Extensions/ParseExtensions/ParseExtensions.TypeName.cs @@ -0,0 +1,16 @@ +#if REFLECTION_TYPE_NAME +using System.Reflection.Metadata; + +namespace Funcky.Extensions; + +public static partial class ParseExtensions +{ + [Pure] + public static Option ParseTypeNameOrNone( + this ReadOnlySpan candidate, + TypeNameParseOptions? options = null) + => TypeName.TryParse(candidate, out var result, options) + ? result + : Option.None; +} +#endif diff --git a/Funcky/PublicAPI.Unshipped.txt b/Funcky/PublicAPI.Unshipped.txt index ad71e021..df864c0e 100644 --- a/Funcky/PublicAPI.Unshipped.txt +++ b/Funcky/PublicAPI.Unshipped.txt @@ -3,3 +3,5 @@ Funcky.DownCast static Funcky.DownCast.From(Funcky.Monads.Option option) -> Funcky.Monads.Option static Funcky.DownCast.From(Funcky.Monads.Result result) -> Funcky.Monads.Result static Funcky.DownCast.From(Funcky.Monads.Either either, System.Func! failedCast) -> Funcky.Monads.Either +static Funcky.Extensions.ParseExtensions.ParseTypeNameOrNone(this System.ReadOnlySpan candidate, System.Reflection.Metadata.TypeNameParseOptions? options = null) -> Funcky.Monads.Option +static Funcky.Extensions.ParseExtensions.ParseAssemblyNameInfoOrNone(this System.ReadOnlySpan candidate) -> Funcky.Monads.Option From c88da57ffa90b0cdd121c1019b2c2b098dcc7736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 13 Jan 2025 11:32:05 +0100 Subject: [PATCH 04/13] Update internal source generator to latest analyzer version --- Funcky.SourceGenerator/Funcky.SourceGenerator.csproj | 6 +++--- Funcky.SourceGenerator/OrNoneFromTryPatternGenerator.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Funcky.SourceGenerator/Funcky.SourceGenerator.csproj b/Funcky.SourceGenerator/Funcky.SourceGenerator.csproj index 8df35b48..b554c893 100644 --- a/Funcky.SourceGenerator/Funcky.SourceGenerator.csproj +++ b/Funcky.SourceGenerator/Funcky.SourceGenerator.csproj @@ -7,11 +7,11 @@ enable preview false + true - - - + + diff --git a/Funcky.SourceGenerator/OrNoneFromTryPatternGenerator.cs b/Funcky.SourceGenerator/OrNoneFromTryPatternGenerator.cs index b5155b8e..915c1dc1 100644 --- a/Funcky.SourceGenerator/OrNoneFromTryPatternGenerator.cs +++ b/Funcky.SourceGenerator/OrNoneFromTryPatternGenerator.cs @@ -29,7 +29,7 @@ private static SourceProductionContext CreateSourceByClass(SourceProductionConte { var syntaxTree = OrNoneFromTryPatternPartial.GetSyntaxTree(methodByClass.First().NamespaceName, methodByClass.First().ClassName, methodByClass.SelectMany(m => m.Methods)); - context.AddSource($"{Path.GetFileName(methodByClass.Key)}.g.cs", string.Join(Environment.NewLine, GeneratedFileHeadersSource) + Environment.NewLine + syntaxTree.NormalizeWhitespace().ToFullString()); + context.AddSource($"{Path.GetFileName(methodByClass.Key)}.g.cs", string.Join("\n", GeneratedFileHeadersSource) + "\n" + syntaxTree.NormalizeWhitespace().ToFullString()); return context; } From 9ff025d3f8295649fd67423438b62609aded0b42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 13 Jan 2025 11:42:14 +0100 Subject: [PATCH 05/13] Use `ForAttributeWithMetadataName` instead of filtering manually --- .../IncrementalValuesProviderExtensions.cs | 9 -------- .../OrNoneFromTryPatternGenerator.cs | 22 +++++-------------- 2 files changed, 6 insertions(+), 25 deletions(-) delete mode 100644 Funcky.SourceGenerator/Extensions/IncrementalValuesProviderExtensions.cs diff --git a/Funcky.SourceGenerator/Extensions/IncrementalValuesProviderExtensions.cs b/Funcky.SourceGenerator/Extensions/IncrementalValuesProviderExtensions.cs deleted file mode 100644 index adac36d0..00000000 --- a/Funcky.SourceGenerator/Extensions/IncrementalValuesProviderExtensions.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace Funcky.SourceGenerator.Extensions; - -internal static class IncrementalValuesProviderExtensions -{ - public static IncrementalValuesProvider WhereNotNull(this IncrementalValuesProvider source) - => source.Where(x => x is not null)!; -} diff --git a/Funcky.SourceGenerator/OrNoneFromTryPatternGenerator.cs b/Funcky.SourceGenerator/OrNoneFromTryPatternGenerator.cs index 915c1dc1..b2e970ec 100644 --- a/Funcky.SourceGenerator/OrNoneFromTryPatternGenerator.cs +++ b/Funcky.SourceGenerator/OrNoneFromTryPatternGenerator.cs @@ -1,5 +1,4 @@ using System.Collections.Immutable; -using Funcky.SourceGenerator.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -35,8 +34,7 @@ private static SourceProductionContext CreateSourceByClass(SourceProductionConte } private static IncrementalValueProvider> GetOrNonePartialMethods(IncrementalGeneratorInitializationContext context) - => context.SyntaxProvider.CreateSyntaxProvider(predicate: IsSyntaxTargetForGeneration, transform: GetSemanticTargetForGeneration) - .WhereNotNull() + => context.SyntaxProvider.ForAttributeWithMetadataName(AttributeFullName, IsSyntaxTargetForGeneration, GetSemanticTargetForGeneration) .Combine(context.CompilationProvider) .Select((state, _) => ToMethodPartial(state.Left, state.Right)) .Collect(); @@ -44,19 +42,11 @@ private static IncrementalValueProvider> GetOrNone private static bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationToken cancellationToken) => node is ClassDeclarationSyntax { AttributeLists: [_, ..] }; - private static SemanticTarget? GetSemanticTargetForGeneration(GeneratorSyntaxContext context, CancellationToken cancellationToken) - => context.Node is ClassDeclarationSyntax classDeclarationSyntax - && context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax, cancellationToken) is { } classSymbol - && classSymbol.GetAttributes() - .Where(a => a.AttributeClass?.ToDisplayString() == AttributeFullName) - .Where(AttributeBelongsToPartialPart(classDeclarationSyntax)) - .Select(ParseAttribute) - .ToImmutableArray() is [_, ..] attributes - ? new SemanticTarget(classDeclarationSyntax, attributes) - : null; - - private static Func AttributeBelongsToPartialPart(ClassDeclarationSyntax partialPart) - => attribute => attribute.ApplicationSyntaxReference?.GetSyntax().Ancestors().OfType().FirstOrDefault() == partialPart; + private static SemanticTarget GetSemanticTargetForGeneration(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) + { + var node = (ClassDeclarationSyntax)context.TargetNode; + return new SemanticTarget(node, context.Attributes.Select(ParseAttribute).ToImmutableArray()); + } private static ParsedAttribute ParseAttribute(AttributeData attribute) => attribute.ConstructorArguments is [{ Value: INamedTypeSymbol type }, { Value: string methodName }, ..] From 8f30fe3ab96a19c66e89e988ffa51aa57d7df792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 13 Jan 2025 11:55:55 +0100 Subject: [PATCH 06/13] Support default values in source gen --- ...odWithDefaultValuedArgument.00.verified.cs | 12 ++++++++ ...odWithDefaultValuedArgument.01.verified.cs | 15 ++++++++++ .../OrNoneGeneratorSnapshotTests.cs | 30 +++++++++++++++++++ .../OrNoneFromTryPatternGenerator.cs | 9 +++++- .../ParseExtensions.TypeName.cs | 13 ++------ 5 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateMethodWithDefaultValuedArgument.00.verified.cs create mode 100644 Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateMethodWithDefaultValuedArgument.01.verified.cs diff --git a/Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateMethodWithDefaultValuedArgument.00.verified.cs b/Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateMethodWithDefaultValuedArgument.00.verified.cs new file mode 100644 index 00000000..59c4638b --- /dev/null +++ b/Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateMethodWithDefaultValuedArgument.00.verified.cs @@ -0,0 +1,12 @@ +//HintName: .g.cs +// +#nullable enable + +namespace Funcky.Extensions +{ + public static partial class ParseExtensions + { + [global::System.Diagnostics.Contracts.Pure] + public static Funcky.Monads.Option ParseTargetOrNone(this string candidate, string? hasDefault = null) => global::Funcky.Extensions.Target.TryParse(candidate, out var result, hasDefault) ? result : default(Funcky.Monads.Option); + } +} diff --git a/Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateMethodWithDefaultValuedArgument.01.verified.cs b/Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateMethodWithDefaultValuedArgument.01.verified.cs new file mode 100644 index 00000000..cbe9fb73 --- /dev/null +++ b/Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateMethodWithDefaultValuedArgument.01.verified.cs @@ -0,0 +1,15 @@ +//HintName: OrNoneFromTryPatternAttribute.g.cs +namespace Funcky.Internal +{ + [global::System.Diagnostics.Conditional("COMPILE_TIME_ONLY")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true)] + internal class OrNoneFromTryPatternAttribute : global::System.Attribute + { + public OrNoneFromTryPatternAttribute(global::System.Type type, string method) + => (Type, Method) = (type, method); + + public global::System.Type Type { get; } + + public string Method { get; } + } +} diff --git a/Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.cs b/Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.cs index 364e2e6e..2a33b38a 100644 --- a/Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.cs +++ b/Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.cs @@ -46,6 +46,36 @@ public static bool TryParse(string candidate, out Target result) return TestHelper.Verify(source + Environment.NewLine + OptionSource); } + [Fact] + public Task GenerateMethodWithDefaultValuedArgument() + { + const string source = + """ + #nullable enable + + using Funcky.Internal; + + namespace Funcky.Extensions + { + [OrNoneFromTryPattern(typeof(Target), nameof(Target.TryParse))] + public static partial class ParseExtensions + { + } + + public sealed class Target + { + public static bool TryParse(string candidate, out Target result, string? hasDefault = null) + { + result = default!; + return false; + } + } + } + """; + + return TestHelper.Verify(source + Environment.NewLine + OptionSource); + } + [Fact] public Task GeneratesMethodWhenTargetIsNotNullableAnnotated() { diff --git a/Funcky.SourceGenerator/OrNoneFromTryPatternGenerator.cs b/Funcky.SourceGenerator/OrNoneFromTryPatternGenerator.cs index b2e970ec..a5ab993a 100644 --- a/Funcky.SourceGenerator/OrNoneFromTryPatternGenerator.cs +++ b/Funcky.SourceGenerator/OrNoneFromTryPatternGenerator.cs @@ -167,9 +167,16 @@ private static string GetParameterName(ISymbol parameter, int index) private static EqualsValueClauseSyntax? GetParameterDefaultValue(IParameterSymbol parameter) => parameter.HasExplicitDefaultValue - ? throw new InvalidOperationException("Default values are not supported") + ? EqualsValueClause(GetLiteralForConstantValue(parameter.ExplicitDefaultValue, parameter.Type)) : null; + private static ExpressionSyntax GetLiteralForConstantValue(object? value, ITypeSymbol type) + => value switch + { + null => LiteralExpression(SyntaxKind.NullLiteralExpression), + _ => throw new NotSupportedException($"unsupported constant: {value} ({type})"), + }; + private static void RegisterOrNoneAttribute(IncrementalGeneratorPostInitializationContext context) => context.AddSource("OrNoneFromTryPatternAttribute.g.cs", CodeSnippets.OrNoneFromTryPatternAttribute); diff --git a/Funcky/Extensions/ParseExtensions/ParseExtensions.TypeName.cs b/Funcky/Extensions/ParseExtensions/ParseExtensions.TypeName.cs index 69d010b5..f611e63f 100644 --- a/Funcky/Extensions/ParseExtensions/ParseExtensions.TypeName.cs +++ b/Funcky/Extensions/ParseExtensions/ParseExtensions.TypeName.cs @@ -1,16 +1,9 @@ #if REFLECTION_TYPE_NAME using System.Reflection.Metadata; +using Funcky.Internal; namespace Funcky.Extensions; -public static partial class ParseExtensions -{ - [Pure] - public static Option ParseTypeNameOrNone( - this ReadOnlySpan candidate, - TypeNameParseOptions? options = null) - => TypeName.TryParse(candidate, out var result, options) - ? result - : Option.None; -} +[OrNoneFromTryPattern(typeof(TypeName), nameof(TypeName.TryParse))] +public static partial class ParseExtensions; #endif From 00a50d604b0f97b56458cf934226a3da3a6d8a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 13 Jan 2025 12:00:51 +0100 Subject: [PATCH 07/13] Give the IReadOnlyDictionary overload priority --- Directory.Packages.props | 2 +- Funcky.Test/Extensions/DictionaryExtensionTest.cs | 7 +++++++ Funcky/Extensions/DictionaryExtensions.cs | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index d25dc544..2cdbc4ed 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,7 +12,7 @@ - + diff --git a/Funcky.Test/Extensions/DictionaryExtensionTest.cs b/Funcky.Test/Extensions/DictionaryExtensionTest.cs index ab5114c6..d75907f5 100644 --- a/Funcky.Test/Extensions/DictionaryExtensionTest.cs +++ b/Funcky.Test/Extensions/DictionaryExtensionTest.cs @@ -15,4 +15,11 @@ public void GivenADictionaryWhenWeLookForAnInexistentValueWithGetValueOrNoneThen var dictionary = new Dictionary { ["some"] = "value" }; FunctionalAssert.None(dictionary.GetValueOrNone(readOnlyKey: "none")); } + + [Fact] + public void CallingGetValueOrNoneOnADictionaryThatImplementsBothReadonlyAndNonReadonlyInterfacesIsNotACompileError() + { + var dictionary = new Dictionary { ["some"] = "value" }; + _ = dictionary.GetValueOrNone("some"); + } } diff --git a/Funcky/Extensions/DictionaryExtensions.cs b/Funcky/Extensions/DictionaryExtensions.cs index cab184d4..a6fd9e6d 100644 --- a/Funcky/Extensions/DictionaryExtensions.cs +++ b/Funcky/Extensions/DictionaryExtensions.cs @@ -1,3 +1,5 @@ +using System.Runtime.CompilerServices; + namespace Funcky.Extensions; public static class DictionaryExtensions @@ -15,6 +17,7 @@ public static Option GetValueOrNone(this IDictionary.None; [Pure] + [OverloadResolutionPriority(10)] public static Option GetValueOrNone(this IReadOnlyDictionary dictionary, TKey readOnlyKey) #if NETCOREAPP3_1 // TKey was constraint to notnull when nullability annotations were originally added. It was later dropped again. From 57abd915999bceafd35ee2d65c7daed11bcd659a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 13 Jan 2025 10:30:36 +0100 Subject: [PATCH 08/13] Don't test against EOL target frameworks xunit.runner.visualstudio only supports >= net6.0 and .NET Framework --- .github/workflows/build.yml | 8 -------- Funcky.Test/Funcky.Test.csproj | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 013eb12b..dd78d7e1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,14 +23,6 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4 name: Install Current .NET SDK - - uses: actions/setup-dotnet@v4 - name: 'Install .NET SDK 3.1' - with: - dotnet-version: '3.1.x' - - uses: actions/setup-dotnet@v4 - name: 'Install .NET SDK 5.0' - with: - dotnet-version: '5.0.x' - uses: actions/setup-dotnet@v4 name: 'Install .NET SDK 7.0' with: diff --git a/Funcky.Test/Funcky.Test.csproj b/Funcky.Test/Funcky.Test.csproj index 35833cc5..4a49af35 100644 --- a/Funcky.Test/Funcky.Test.csproj +++ b/Funcky.Test/Funcky.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0;net7.0;net6.0;net5.0;netcoreapp3.1 + net9.0;net8.0;net7.0;net6.0 $(TargetFrameworks);net4.8 preview enable From 25e920eff42a72533477a8b5f1269cc5351b76ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 13 Jan 2025 12:10:56 +0100 Subject: [PATCH 09/13] Install .NET 8 on CI --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dd78d7e1..137bad11 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,6 +23,10 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4 name: Install Current .NET SDK + - uses: actions/setup-dotnet@v4 + name: 'Install .NET SDK 8.0' + with: + dotnet-version: '8.0.x' - uses: actions/setup-dotnet@v4 name: 'Install .NET SDK 7.0' with: From dddab1c39bef6075a1ef90ba189886c81471ebce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 13 Jan 2025 12:36:27 +0100 Subject: [PATCH 10/13] Remove source link package Starting with .NET 8, source link is included in the SDK --- Directory.Build.props | 3 --- Directory.Packages.props | 1 - 2 files changed, 4 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 3b750436..1052c835 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -23,9 +23,6 @@ true true - - - $(MSBuildThisFileDirectory)artifacts diff --git a/Directory.Packages.props b/Directory.Packages.props index 2cdbc4ed..b7aded27 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,7 +15,6 @@ - From f3047f997968eaf5d5ab7d137a642e5a766ab531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 13 Jan 2025 12:46:03 +0100 Subject: [PATCH 11/13] Only audit direct deps --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 137bad11..4938ae4c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,6 +8,9 @@ on: env: DOTNET_NOLOGO: 1 + # This is for some reason set to 'all' on CI which means + # we get warnings for transitive dependencies (e.g. from Messerli.CodeStyle -> StyleCop) + NuGetAuditMode: direct jobs: build: From 97c1ed99394e9a88cc916a0ed51bfd276ba0b0e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 13 Jan 2025 12:58:36 +0100 Subject: [PATCH 12/13] Suppress addition of notnull constraint in .NET Core 3.1 --- Funcky/CompatibilitySuppressions.xml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Funcky/CompatibilitySuppressions.xml b/Funcky/CompatibilitySuppressions.xml index 96b033ac..ee1f1edc 100644 --- a/Funcky/CompatibilitySuppressions.xml +++ b/Funcky/CompatibilitySuppressions.xml @@ -1,5 +1,5 @@  - + CP0002 @@ -7,4 +7,16 @@ lib/net5.0/Funcky.dll lib/net6.0/Funcky.dll + + CP0021 + M:Funcky.Extensions.DictionaryExtensions.GetValueOrNone``2(System.Collections.Generic.IDictionary{``0,``1},``0)``0:notnull + lib/netstandard2.1/Funcky.dll + lib/netcoreapp3.1/Funcky.dll + + + CP0021 + M:Funcky.Extensions.DictionaryExtensions.GetValueOrNone``2(System.Collections.Generic.IReadOnlyDictionary{``0,``1},``0)``0:notnull + lib/netstandard2.1/Funcky.dll + lib/netcoreapp3.1/Funcky.dll + \ No newline at end of file From ba75ecbdbfbcfc9cbaac9763305ad21113dfa910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 13 Jan 2025 18:58:26 +0100 Subject: [PATCH 13/13] Use less arbitrary number --- Funcky/Extensions/DictionaryExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Funcky/Extensions/DictionaryExtensions.cs b/Funcky/Extensions/DictionaryExtensions.cs index a6fd9e6d..f942fecc 100644 --- a/Funcky/Extensions/DictionaryExtensions.cs +++ b/Funcky/Extensions/DictionaryExtensions.cs @@ -17,7 +17,7 @@ public static Option GetValueOrNone(this IDictionary.None; [Pure] - [OverloadResolutionPriority(10)] + [OverloadResolutionPriority(1)] public static Option GetValueOrNone(this IReadOnlyDictionary dictionary, TKey readOnlyKey) #if NETCOREAPP3_1 // TKey was constraint to notnull when nullability annotations were originally added. It was later dropped again.