Skip to content

Commit

Permalink
Merge branch 'main' into fix-badges
Browse files Browse the repository at this point in the history
  • Loading branch information
bash authored Jan 13, 2025
2 parents 19e85a7 + 76bcf69 commit 9b1e81e
Show file tree
Hide file tree
Showing 27 changed files with 152 additions and 60 deletions.
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>
<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))
{
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" />
<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());

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(1)]
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))]
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))]
public static partial class ParseExtensions;
#endif
Loading

0 comments on commit 9b1e81e

Please sign in to comment.