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

add support for generic union types to funicular switch generators #41

Merged
merged 6 commits into from
Jan 28, 2025
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 .github/workflows/dotnet-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.0.x
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore ./Source
- name: Build
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ class ExampleConsumer
}
```

Base types of unions may also be generic types with arbitrary number of type parameters. Case types with generic arguments are not yet supported.

If you like union types but don't like excessive typing in C# try the [Switchyard](https://github.com/bluehands/Switchyard) Visual Studio extension, which generates the boilerplate code for you. It plays nicely with the FunicularSwitch.Generators package.

## ExtendedEnum attribute
Expand Down
6 changes: 3 additions & 3 deletions Source/DocSamples/ReadmeSamples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,12 @@ static IEnumerable<string> CheckFruit(Fruit fruit)
}

var salad =
await ingredients
.Select(ingredient =>
await ingredients.Select(ingredient =>
stock
.Where(fruit => fruit.Name == ingredient)
.FirstOk(CheckFruit, onEmpty: () => $"No {ingredient} in stock")
)
)
.Aggregate()
.Bind(fruits => CutIntoPieces(fruits, cookSkillLevel))
.Map(Serve);

Expand Down
44 changes: 44 additions & 0 deletions Source/FunicularSwitch.Generators.Common/RoslynExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,12 @@ public static bool Implements(this INamedTypeSymbol symbol, ITypeSymbol interfac
}

static readonly SymbolDisplayFormat FullTypeWithNamespaceDisplayFormat = SymbolWrapper.FullTypeWithNamespaceDisplayFormat;
static readonly SymbolDisplayFormat FullTypeWithNamespaceAndGenericsDisplayFormat = SymbolWrapper.FullTypeWithNamespaceAndGenericsDisplayFormat;
static readonly SymbolDisplayFormat FullTypeDisplayFormat = new(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes);


public static string FullTypeNameWithNamespace(this INamedTypeSymbol namedTypeSymbol) => namedTypeSymbol.ToDisplayString(FullTypeWithNamespaceDisplayFormat);
public static string FullTypeNameWithNamespaceAndGenerics(this INamedTypeSymbol namedTypeSymbol) => namedTypeSymbol.ToDisplayString(FullTypeWithNamespaceAndGenericsDisplayFormat);
public static string FullTypeName(this INamedTypeSymbol namedTypeSymbol) => namedTypeSymbol.ToDisplayString(FullTypeDisplayFormat);

public static string FullNamespace(this INamespaceSymbol namespaceSymbol) =>
Expand All @@ -99,6 +101,48 @@ public static QualifiedTypeName QualifiedName(this BaseTypeDeclarationSyntax dec
return new(dec.Name(), typeNames);
}

public static QualifiedTypeName QualifiedNameWithGenerics(this BaseTypeDeclarationSyntax dec)
{
var current = dec.Parent as BaseTypeDeclarationSyntax;
var typeNames = new Stack<string>();
while (current != null)
{
typeNames.Push(current.Name() + FormatTypeParameters(current.GetTypeParameterList()));
current = current.Parent as BaseTypeDeclarationSyntax;
}

return new(dec.Name() + FormatTypeParameters(dec.GetTypeParameterList()), typeNames);
}

public static EquatableArray<string> GetTypeParameterList(this BaseTypeDeclarationSyntax dec)
{
if (dec is not TypeDeclarationSyntax tds)
{
return [];
}

return tds.TypeParameterList?.Parameters.Select(tps => tps.Identifier.Text).ToImmutableArray() ?? ImmutableArray<string>.Empty;
}

public static string FormatTypeParameters(EquatableArray<string> typeParameters)
{
if (typeParameters.Length == 0)
{
return string.Empty;
}

return "<" + string.Join(", ", typeParameters) + ">";
}

public static string FormatTypeParameterForFileName(EquatableArray<string> typeParameters)
{
if (typeParameters.Length == 0)
{
return string.Empty;
}

return "Of" + string.Join("_", typeParameters);
}

public static string Name(this BaseTypeDeclarationSyntax declaration) => declaration.Identifier.ToString();

Expand Down
1 change: 1 addition & 0 deletions Source/FunicularSwitch.Generators.Common/SymbolWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace FunicularSwitch.Generators.Common;
public static class SymbolWrapper
{
internal static readonly SymbolDisplayFormat FullTypeWithNamespaceDisplayFormat = new(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);
internal static readonly SymbolDisplayFormat FullTypeWithNamespaceAndGenericsDisplayFormat = new(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters);

public static SymbolWrapper<T> Create<T>(T symbol) where T : ISymbol => new(symbol);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace FunicularSwitch.Generators.FluentAssertions.Templates
{
internal static class MyAssertionExtensions_UnionType
{
public static MyAssertions_UnionType Should(this MyUnionType unionType) => new(unionType);
public static MyAssertions_UnionType Should<TTypeParameters>(this MyUnionType unionType) => new(unionType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public AndWhichConstraint<MyAssertions_UnionType, MyDerivedUnionType> BeFriendly
Execute.Assertion
.ForCondition(this.Subject is MyDerivedUnionType)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context} to be Error with MyDerivedErrorType MyErrorType{reason}, but found {0}",
.FailWith("Expected {context} to be MyDerivedUnionType{reason}, but found {0}",
this.Subject);

return new(this, (this.Subject as MyDerivedUnionType)!);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using FunicularSwitch.Generators.Common;
using System.Collections.Immutable;
using CommunityToolkit.Mvvm.SourceGenerators.Helpers;
using FunicularSwitch.Generators.Common;
using Microsoft.CodeAnalysis;

namespace FunicularSwitch.Generators.FluentAssertions.FluentAssertionMethods;
Expand All @@ -14,6 +16,7 @@ internal static class Generator
private const string TemplateResultAssertionExtensions = "MyAssertionExtensions_Result";
private const string TemplateUnionTypeAssertionsTypeName = "MyAssertions_UnionType";
private const string TemplateUnionTypeAssertionExtensions = "MyAssertionExtensions_UnionType";
private const string TemplateUnionTypeTypeParameterList = "<TTypeParameters>";
private const string TemplateFriendlyDerivedUnionTypeName = "FriendlyDerivedUnionTypeName";
private const string TemplateAdditionalUsingDirectives = "//additional using directives";

Expand Down Expand Up @@ -64,19 +67,25 @@ string Replace(string code, params string[] additionalNamespaces)
{
var unionTypeFullName = unionTypeSchema.UnionTypeBaseType.FullTypeName().Replace('.', '_');
var unionTypeFullNameWithNamespace = unionTypeSchema.UnionTypeBaseType.FullTypeNameWithNamespace();
var unionTypeFullNameWithNamespaceAndGenerics = unionTypeSchema.UnionTypeBaseType.FullTypeNameWithNamespaceAndGenerics();
EquatableArray<string> typeParameters = unionTypeSchema.UnionTypeBaseType.TypeParameters
.Select(t => t.Name).ToImmutableArray();
var unionTypeNamespace = unionTypeSchema.UnionTypeBaseType.GetFullNamespace();
var typeParametersText = RoslynExtensions.FormatTypeParameters(typeParameters);

var generateFileHint = $"{unionTypeFullNameWithNamespace}";
var generateFileHint = $"{unionTypeFullNameWithNamespace}{RoslynExtensions.FormatTypeParameterForFileName(typeParameters)}";

//var generatorRuns = RunCount.Increase(unionTypeSchema.UnionTypeBaseType.FullTypeNameWithNamespace());

string Replace(string code, params string[] additionalNamespaces)
{
code = code
.Replace($"namespace {TemplateNamespace}", $"namespace {unionTypeNamespace}")
.Replace(TemplateUnionTypeName, unionTypeFullNameWithNamespace)
.Replace(TemplateUnionTypeAssertionsTypeName, $"{unionTypeFullName}Assertions")
.Replace(TemplateUnionTypeName, unionTypeFullNameWithNamespaceAndGenerics)
.Replace($"public {TemplateUnionTypeAssertionsTypeName}(", $"public {unionTypeFullName}Assertions(")
.Replace(TemplateUnionTypeAssertionsTypeName, $"{unionTypeFullName}Assertions{typeParametersText}")
.Replace(TemplateUnionTypeAssertionExtensions, $"{unionTypeFullName}FluentAssertionExtensions")
.Replace(TemplateUnionTypeTypeParameterList, typeParametersText)
.Replace(
TemplateAdditionalUsingDirectives,
additionalNamespaces
Expand All @@ -99,7 +108,7 @@ string Replace(string code, params string[] additionalNamespaces)

foreach (var derivedType in unionTypeSchema.DerivedTypes)
{
var derivedTypeFullNameWithNamespace = derivedType.FullTypeNameWithNamespace();
var derivedTypeFullNameWithNamespace = derivedType.FullTypeNameWithNamespaceAndGenerics();
yield return (
$"{generateFileHint}_Derived_{derivedType.Name}Assertions.g.cs",
Replace(Templates.GenerateFluentAssertionsForTemplates.MyDerivedUnionTypeAssertions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,9 @@

namespace FunicularSwitch.Generators.FluentAssertions.FluentAssertionMethods;

public class ResultTypeSchema
public record ResultTypeSchema(
INamedTypeSymbol ResultType,
INamedTypeSymbol? ErrorType)
{
public INamedTypeSymbol ResultType { get; }
public INamedTypeSymbol? ErrorType { get; }

public ResultTypeSchema(INamedTypeSymbol resultType, INamedTypeSymbol? errorType)
{
ResultType = resultType;
ErrorType = errorType;
}

public override string ToString() => $"{nameof(ResultType)}: {ResultType}, {nameof(ErrorType)}: {ErrorType}";
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

namespace FunicularSwitch.Generators.FluentAssertions.FluentAssertionMethods;

public class UnionTypeSchema
public record UnionTypeSchema(
INamedTypeSymbol UnionTypeBaseType,
IEnumerable<INamedTypeSymbol> DerivedTypes)
{
public UnionTypeSchema(INamedTypeSymbol unionTypeBaseType, IEnumerable<INamedTypeSymbol> derivedTypes)
public override string ToString()
{
UnionTypeBaseType = unionTypeBaseType;
DerivedTypes = derivedTypes.ToList();
var derivedTypes = string.Join(", ", DerivedTypes.Select(d => d.ToString()));
return $"{nameof(UnionTypeBaseType)}: {UnionTypeBaseType}, {nameof(DerivedTypes)}, {derivedTypes}";
}

public INamedTypeSymbol UnionTypeBaseType { get; }
public IReadOnlyList<INamedTypeSymbol> DerivedTypes { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

<!--#region adapt versions here-->
<MajorVersion>1</MajorVersion>
<MinorAndPatchVersion>2.2</MinorAndPatchVersion>
<MinorAndPatchVersion>3.0</MinorAndPatchVersion>
<!--#endregion-->

<AssemblyVersion>$(MajorVersion).0.0</AssemblyVersion>
Expand Down
5 changes: 3 additions & 2 deletions Source/FunicularSwitch.Generators/EnumType/Generator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FunicularSwitch.Generators.Common;
using System.Collections.Immutable;
using FunicularSwitch.Generators.Common;
using FunicularSwitch.Generators.Generation;
using Microsoft.CodeAnalysis;

Expand Down Expand Up @@ -55,7 +56,7 @@ void BlankLine()
}

builder.WriteLine("#pragma warning restore 1591");
return (enumTypeSchema.FullTypeName.ToMatchExtensionFilename(), builder.ToString());
return (enumTypeSchema.FullTypeName.ToMatchExtensionFilename(ImmutableArray<string>.Empty), builder.ToString());
}

static void GenerateMatchMethod(CSharpBuilder builder, EnumTypeSchema enumTypeSchema, string t)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

<!--#region adapt versions here-->
<MajorVersion>4</MajorVersion>
<MinorAndPatchVersion>1.3</MinorAndPatchVersion>
<MinorAndPatchVersion>2.0</MinorAndPatchVersion>
<!--#endregion-->

<AssemblyVersion>$(MajorVersion).0.0</AssemblyVersion>
Expand Down
7 changes: 5 additions & 2 deletions Source/FunicularSwitch.Generators/Generation/Indent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FunicularSwitch.Generators.Common;
using CommunityToolkit.Mvvm.SourceGenerators.Helpers;
using FunicularSwitch.Generators.Common;

namespace FunicularSwitch.Generators.Generation;

Expand Down Expand Up @@ -242,5 +243,7 @@ public static string TrimBaseTypeName(this string value, string baseTypeName)
return value;
}

public static string ToMatchExtensionFilename(this string fullTypeName) => $"{fullTypeName.Replace(".", "")}MatchExtension.g.cs";
public static string ToMatchExtensionFilename(this string fullTypeName, EquatableArray<string> typeParameters) => $"{fullTypeName.Replace(".", "")}{RoslynExtensions.FormatTypeParameterForFileName(typeParameters)}MatchExtension.g.cs";


}
Loading
Loading