Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to .NET 9 #798

Merged
merged 13 commits into from
Jan 13, 2025
11 changes: 5 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -24,13 +27,9 @@ jobs:
- uses: actions/setup-dotnet@v4
name: Install Current .NET SDK
- uses: actions/setup-dotnet@v4
name: 'Install .NET SDK 3.1'
name: 'Install .NET SDK 8.0'
with:
dotnet-version: '3.1.x'
- uses: actions/setup-dotnet@v4
name: 'Install .NET SDK 5.0'
with:
dotnet-version: '5.0.x'
dotnet-version: '8.0.x'
- uses: actions/setup-dotnet@v4
name: 'Install .NET SDK 7.0'
with:
Expand Down
3 changes: 0 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">true</ContinuousIntegrationBuild>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
FreeApophis marked this conversation as resolved.
Show resolved Hide resolved
<ItemGroup Label="Deterministic Builds and Source Link">
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All"/>
</ItemGroup>
<PropertyGroup>
<ArtifactsPath>$(MSBuildThisFileDirectory)artifacts</ArtifactsPath>
</PropertyGroup>
Expand Down
3 changes: 1 addition & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@
<PackageVersion Include="System.Linq.Async" Version="[5.0.0, 7)" />
</ItemGroup>
<ItemGroup Label="Build Dependencies">
<PackageVersion Include="PolySharp" Version="1.14.0" />
<PackageVersion Include="PolySharp" Version="1.15.0" />
<PackageVersion Include="Messerli.CodeStyle" Version="2.3.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.3" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
</ItemGroup>
<ItemGroup Label="Test Dependencies">
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
Expand Down
3 changes: 3 additions & 0 deletions FrameworkFeatureConstants.props
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<DefineConstants>$(DefineConstants);RANDOM_SHUFFLE;UTF8_SPAN_PARSABLE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net9.0'))">
<DefineConstants>$(DefineConstants);REFLECTION_ASSEMBLY_NAME_INFO;REFLECTION_TYPE_NAME</DefineConstants>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>

<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
Expand Down
2 changes: 1 addition & 1 deletion Funcky.Async.Test/Funcky.Async.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net7.0</TargetFrameworks>
<TargetFrameworks>net9.0;net8.0;net7.0</TargetFrameworks>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
Expand Down
2 changes: 1 addition & 1 deletion Funcky.Async/Extensions/AsyncEnumerableExtensions/Merge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public static async IAsyncEnumerable<TSource> Merge<TSource>(this IEnumerable<IA

try
{
await foreach (var element in MergeEnumerators(enumerators.RemoveRange(await enumerators.ToAsyncEnumerable().WhereAwait(async f => 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;
}
Expand Down
13 changes: 7 additions & 6 deletions Funcky.Async/Extensions/AsyncEnumerableExtensions/PowerSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,22 @@ public static IAsyncEnumerable<IEnumerable<TSource>> PowerSet<TSource>(this IAsy

private static async IAsyncEnumerable<IEnumerable<TSource>> PowerSetInternal<TSource>(this IAsyncEnumerable<TSource> source, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var asyncEnumerator = source.GetAsyncEnumerator(cancellationToken);
await using var sourceEnumerator = asyncEnumerator.ConfigureAwait(false);
#pragma warning disable CA2007 // Configured via IAsyncEnumerable<T> 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))
FreeApophis marked this conversation as resolved.
Show resolved Hide resolved
{
yield return set;
}
}

private static async IAsyncEnumerable<ImmutableStack<TSource>> PowerSetEnumerator<TSource>(this IAsyncEnumerator<TSource> source)
private static async IAsyncEnumerable<ImmutableStack<TSource>> PowerSetEnumerator<TSource>(this ConfiguredCancelableAsyncEnumerable<TSource>.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);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
using System.Runtime.CompilerServices;

namespace Funcky.Extensions;

public static partial class AsyncEnumerableExtensions
{
/// <summary>Returns a sequence mapping each element together with its predecessor.</summary>
/// <exception cref="ArgumentNullException">Thrown when any value in <paramref name="source"/> is <see langword="null"/>.</exception>
[Pure]
public static async IAsyncEnumerable<ValueWithPrevious<TSource>> WithPrevious<TSource>(this IAsyncEnumerable<TSource> source)
public static IAsyncEnumerable<ValueWithPrevious<TSource>> WithPrevious<TSource>(this IAsyncEnumerable<TSource> source)
where TSource : notnull
=> source.WithPreviousInternal();

private static async IAsyncEnumerable<ValueWithPrevious<TSource>> WithPreviousInternal<TSource>(this IAsyncEnumerable<TSource> source, [EnumeratorCancellation] CancellationToken cancellationToken = default)
where TSource : notnull
{
var previous = Option<TSource>.None;

await foreach (var value in source)
await foreach (var value in source.ConfigureAwait(false).WithCancellation(cancellationToken))
{
yield return new ValueWithPrevious<TSource>(value, previous);
previous = value;
Expand Down
2 changes: 1 addition & 1 deletion Funcky.Async/Funcky.Async.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net5.0;netstandard2.1;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net9.0;net8.0;net5.0;netstandard2.1;netstandard2.0</TargetFrameworks>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<Description>Extends Funcky with support for IAsyncEnumerable and Tasks.</Description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<AssemblyName>Funcky.SourceGenerator.Test</AssemblyName>
<RootNamespace>Funcky.SourceGenerator.Test</RootNamespace>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//HintName: .g.cs
// <auto-generated/>
#nullable enable

namespace Funcky.Extensions
{
public static partial class ParseExtensions
{
[global::System.Diagnostics.Contracts.Pure]
public static Funcky.Monads.Option<global::Funcky.Extensions.Target> ParseTargetOrNone(this string candidate, string? hasDefault = null) => global::Funcky.Extensions.Target.TryParse(candidate, out var result, hasDefault) ? result : default(Funcky.Monads.Option<global::Funcky.Extensions.Target>);
}
}
Original file line number Diff line number Diff line change
@@ -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; }
}
}
30 changes: 30 additions & 0 deletions Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down

This file was deleted.

6 changes: 3 additions & 3 deletions Funcky.SourceGenerator/Funcky.SourceGenerator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<IsPackable>false</IsPackable>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" VersionOverride="3.11.0" />
FreeApophis marked this conversation as resolved.
Show resolved Hide resolved
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" VersionOverride="4.12.0" />
<PackageReference Include="PolySharp" PrivateAssets="all" />
</ItemGroup>
</Project>
33 changes: 15 additions & 18 deletions Funcky.SourceGenerator/OrNoneFromTryPatternGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Immutable;
using Funcky.SourceGenerator.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -29,34 +28,25 @@ 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());
FreeApophis marked this conversation as resolved.
Show resolved Hide resolved

return context;
}

private static IncrementalValueProvider<ImmutableArray<MethodPartial>> 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();

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<AttributeData, bool> AttributeBelongsToPartialPart(ClassDeclarationSyntax partialPart)
=> attribute => attribute.ApplicationSyntaxReference?.GetSyntax().Ancestors().OfType<ClassDeclarationSyntax>().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 }, ..]
Expand Down Expand Up @@ -177,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);

Expand Down
7 changes: 7 additions & 0 deletions Funcky.Test/Extensions/DictionaryExtensionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,11 @@ public void GivenADictionaryWhenWeLookForAnInexistentValueWithGetValueOrNoneThen
var dictionary = new Dictionary<string, string> { ["some"] = "value" };
FunctionalAssert.None(dictionary.GetValueOrNone(readOnlyKey: "none"));
}

[Fact]
public void CallingGetValueOrNoneOnADictionaryThatImplementsBothReadonlyAndNonReadonlyInterfacesIsNotACompileError()
{
var dictionary = new Dictionary<string, string> { ["some"] = "value" };
_ = dictionary.GetValueOrNone("some");
}
}
2 changes: 1 addition & 1 deletion Funcky.Test/Funcky.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net7.0;net6.0;net5.0;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks>net9.0;net8.0;net7.0;net6.0</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' == 'Windows_NT'">$(TargetFrameworks);net4.8</TargetFrameworks>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
Expand Down
2 changes: 1 addition & 1 deletion Funcky.Xunit.Test/Funcky.Xunit.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
Expand Down
14 changes: 13 additions & 1 deletion Funcky/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids -->
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Funcky.Extensions.EnumerableExtensions.Chunk``1(System.Collections.Generic.IEnumerable{``0},System.Int32)</Target>
<Left>lib/net5.0/Funcky.dll</Left>
<Right>lib/net6.0/Funcky.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0021</DiagnosticId>
<Target>M:Funcky.Extensions.DictionaryExtensions.GetValueOrNone``2(System.Collections.Generic.IDictionary{``0,``1},``0)``0:notnull</Target>
<Left>lib/netstandard2.1/Funcky.dll</Left>
<Right>lib/netcoreapp3.1/Funcky.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0021</DiagnosticId>
<Target>M:Funcky.Extensions.DictionaryExtensions.GetValueOrNone``2(System.Collections.Generic.IReadOnlyDictionary{``0,``1},``0)``0:notnull</Target>
<Left>lib/netstandard2.1/Funcky.dll</Left>
<Right>lib/netcoreapp3.1/Funcky.dll</Right>
</Suppression>
</Suppressions>
3 changes: 3 additions & 0 deletions Funcky/Extensions/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Runtime.CompilerServices;

namespace Funcky.Extensions;

public static class DictionaryExtensions
Expand All @@ -15,6 +17,7 @@ public static Option<TValue> GetValueOrNone<TKey, TValue>(this IDictionary<TKey,
: Option<TValue>.None;

[Pure]
[OverloadResolutionPriority(10)]
FreeApophis marked this conversation as resolved.
Show resolved Hide resolved
public static Option<TValue> GetValueOrNone<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey readOnlyKey)
#if NETCOREAPP3_1
// TKey was constraint to notnull when nullability annotations were originally added. It was later dropped again.
Expand Down
Original file line number Diff line number Diff line change
@@ -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))]
bash marked this conversation as resolved.
Show resolved Hide resolved
public static partial class ParseExtensions;
#endif
9 changes: 9 additions & 0 deletions Funcky/Extensions/ParseExtensions/ParseExtensions.TypeName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#if REFLECTION_TYPE_NAME
using System.Reflection.Metadata;
using Funcky.Internal;

namespace Funcky.Extensions;

[OrNoneFromTryPattern(typeof(TypeName), nameof(TypeName.TryParse))]
bash marked this conversation as resolved.
Show resolved Hide resolved
public static partial class ParseExtensions;
#endif
Loading
Loading