diff --git a/Funzo.SourceGenerators/Funzo.SourceGenerators.csproj b/Funzo.SourceGenerators/Funzo.SourceGenerators.csproj index d57cc3c..2c6231a 100644 --- a/Funzo.SourceGenerators/Funzo.SourceGenerators.csproj +++ b/Funzo.SourceGenerators/Funzo.SourceGenerators.csproj @@ -18,13 +18,26 @@ LICENSE true + true - - + + + + $(GetTargetPathDependsOn);GetDependencyTargetPaths + + + + + + + + + + True diff --git a/Funzo.SourceGenerators/Generators/ResultSourceGenerator.cs b/Funzo.SourceGenerators/Generators/ResultSourceGenerator.cs index 018f1c7..81467c4 100644 --- a/Funzo.SourceGenerators/Generators/ResultSourceGenerator.cs +++ b/Funzo.SourceGenerators/Generators/ResultSourceGenerator.cs @@ -3,10 +3,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using System; +using Sorse; using System.Collections.Immutable; using System.Linq; -using System.Text; namespace Funzo.SourceGenerators.Generators; @@ -21,40 +20,16 @@ internal class ResultSourceGenerator : GeneratorBase var is1ArityResult = type.AttributeTypeArguments.Length == 1; + ResultGenerator generator = is1ArityResult ? new Result1AritySourceGenerator(type) : new Result2AritySourceGenerator(type); - var sb = new StringBuilder(); - sb.AppendLine($@"// - -namespace {type.Symbol.ContainingNamespace.ToDisplayString()} -{{"); + var sorse = WithSorse.CreateNamespaceScope(type.Symbol.ContainingNamespace.ToDisplayString(), []); - // Add all needed upper classes - foreach (var containingType in type.ContainerClasses) - { - var isStatic = type.Symbol.ContainingType!.IsStatic ? "static " : ""; - - sb.AppendLine($"{isStatic}partial class {containingType.Name} {{"); - } + sorse.AddClassWithInnerClasses(type, generator.GenerateResult); - sb.AppendLine($@" - {generator.ClassDefinition} - {{ - {generator.OkConstructor} - {generator.OkStaticHelper} - {generator.OkImplicitConverter} - {generator.ErrConstructor} - {generator.ErrStaticHelper} - {generator.ErrImplicitConverter} - }} -}}"); - - // Close all the braces opened for parent classes - sb.AppendLine(new string('}', type.ContainerClasses.Count)); - - var src = sb.ToString(); + var src = sorse.GetSource(); return src; } diff --git a/Funzo.SourceGenerators/Generators/Results/Result1AritySourceGenerator.cs b/Funzo.SourceGenerators/Generators/Results/Result1AritySourceGenerator.cs index 8c1ae78..52acd71 100644 --- a/Funzo.SourceGenerators/Generators/Results/Result1AritySourceGenerator.cs +++ b/Funzo.SourceGenerators/Generators/Results/Result1AritySourceGenerator.cs @@ -1,43 +1,38 @@ using Funzo.SourceGenerators.Helpers; using Microsoft.CodeAnalysis; -using System.Text; +using Sorse.BuilderInterfaces; namespace Funzo.SourceGenerators.Generators.Results; + internal class Result1AritySourceGenerator : ResultGenerator { internal Result1AritySourceGenerator(MarkedType symbolWithAttribute) : base(symbolWithAttribute) { } - internal override string ClassDefinition => $@"partial class {ClassName} : global::Funzo.ResultBase<{ClassName},{ErrDisplayName}>, global::Funzo.IResultBase <{ClassName},{ErrDisplayName}>"; - - internal override string OkConstructor => @$"protected {ClassName}() : base() {{}}"; - - internal override string ErrConstructor => $@"protected {ClassName}({ErrDisplayName} _) : base(_) {{}}"; - - internal override string OkStaticHelper => $@"public static {ClassName} Ok() => new();"; - - internal override string ErrStaticHelper => $@"public static {ClassName} Err({ErrDisplayName} err) => new(err);"; - - internal override string OkImplicitConverter => string.Empty; + protected override void GenerateResultInner(IClassBuilder builder) + { + builder.Inherits($"global::Funzo.ResultBase<{ClassName}, {ErrDisplayName}>") + .Implements($"global::Funzo.IResultBase<{ClassName}, {ErrDisplayName}>") + .WithConstructor(c => c.WithBaseCall([])) + .WithConstructor(c => c.WithArguments([new(new(ErrType), "x")]).WithBaseCall(["x"])) + .WithMethod(ClassName, "Ok", m => m.Static().WithBody(" => new();")) + .WithMethod(ClassName, "Err", m => m.Static().WithArguments([new(new(ErrType), "x")]).WithBody(" => new(x);")) + .WithImplicitConversionOperatorFrom(new(ErrType), " => new(x);"); + + AddConversionsForErrUnions(builder); + } - internal override string ErrImplicitConverter + private void AddConversionsForErrUnions(IClassBuilder builder) { - get + foreach (var type in GetTypesNeedingImplicitConversions(ErrType, ResultParameterType.Err)) { - var implicitConversions = new StringBuilder(); - implicitConversions.AppendLine($@"public static implicit operator {ClassName}({ErrDisplayName} _) => new {ClassName}(_);"); - - if (TryGetImplicitConvertersForUnionType(ErrType, ResultParameterType.Err, out var converters)) - { - implicitConversions.AppendLine(converters); - } - - return implicitConversions.ToString(); + builder.WithImplicitConversionOperatorFrom(new(type), $" => new(x);"); } } private string ErrDisplayName => ErrType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); private ITypeSymbol ErrType => TypeArguments[0]; + } diff --git a/Funzo.SourceGenerators/Generators/Results/Result2AritySourceGenerator.cs b/Funzo.SourceGenerators/Generators/Results/Result2AritySourceGenerator.cs index 31f294e..508c6a4 100644 --- a/Funzo.SourceGenerators/Generators/Results/Result2AritySourceGenerator.cs +++ b/Funzo.SourceGenerators/Generators/Results/Result2AritySourceGenerator.cs @@ -1,55 +1,52 @@ using Funzo.SourceGenerators.Helpers; using Microsoft.CodeAnalysis; +using Sorse; +using Sorse.BuilderInterfaces; using System.Collections.Immutable; using System.Linq; -using System.Text; namespace Funzo.SourceGenerators.Generators.Results; + internal class Result2AritySourceGenerator : ResultGenerator { internal Result2AritySourceGenerator(MarkedType symbolWithAttribute) : base(symbolWithAttribute) { } - internal override string ClassDefinition => $@"partial class {ClassName} : global::Funzo.ResultBase<{ClassName}, {OkDisplayName},{ErrDisplayName}>, global::Funzo.IResultBase<{ClassName}, {OkDisplayName},{ErrDisplayName}>"; - - internal override string OkConstructor => @$"protected {ClassName}({OkDisplayName} _) : base(_) {{}}"; - - internal override string ErrConstructor => $@"protected {ClassName}({ErrDisplayName} _) : base(_) {{}}"; - - internal override string OkStaticHelper => $@"public static {ClassName} Ok({OkDisplayName} ok) => new(ok);"; - - internal override string ErrStaticHelper => $@"public static {ClassName} Err({ErrDisplayName} err) => new(err);"; + protected override void GenerateResultInner(IClassBuilder builder) + { + builder.Inherits($"global::Funzo.ResultBase<{ClassName}, {OkDisplayName}, {ErrDisplayName}>") + .Implements($"global::Funzo.IResultBase<{ClassName}, {OkDisplayName}, {ErrDisplayName}>") + .WithConstructor(c => c.WithAccessModifier(AccessModifier.Protected) + .WithArguments([new(new(OkType), "_")]).WithBaseCall(["_"])) + .WithConstructor(c => c.WithAccessModifier(AccessModifier.Protected) + .WithArguments([new(new(ErrType), "_")]).WithBaseCall(["_"])) + .WithMethod(ClassName, "Ok", m => m.Static().WithArguments([new(new(OkType), "ok")]).WithBody(" => new(ok);")) + .WithMethod(ClassName, "Err", m => m.Static().WithArguments([new(new(ErrType), "err")]).WithBody(" => new(err);")) + .WithImplicitConversionOperatorFrom(new(OkType), " => new(x);") + .WithImplicitConversionOperatorFrom(new(ErrType), " => new(x);"); + + AddConversionsForUnions(builder); + } - internal override string OkImplicitConverter + private void AddConversionsForUnions(IClassBuilder builder) { - get + if (HasCollidingParameters()) { - var implicitConversions = new StringBuilder(); - implicitConversions.AppendLine($@"public static implicit operator {ClassName}({OkDisplayName} _) => new {ClassName}(_);"); + return; + } - if (!HasCollidingParameters() && TryGetImplicitConvertersForUnionType(OkType, ResultParameterType.Ok, out var converters)) - { - implicitConversions.AppendLine(converters); - } + var okUnions = GetTypesNeedingImplicitConversions(OkType, ResultParameterType.Ok); + var errUnions = GetTypesNeedingImplicitConversions(ErrType, ResultParameterType.Err); - return implicitConversions.ToString(); + foreach (var ok in okUnions) + { + builder.WithImplicitConversionOperatorFrom(new(ok), " => new(x);"); } - } - internal override string ErrImplicitConverter - { - get + foreach (var err in errUnions) { - var implicitConversions = new StringBuilder(); - implicitConversions.AppendLine($@"public static implicit operator {ClassName}({ErrDisplayName} _) => new {ClassName}(_);"); - - if (!HasCollidingParameters() && TryGetImplicitConvertersForUnionType(ErrType, ResultParameterType.Err, out var converters)) - { - implicitConversions.AppendLine(converters); - } - - return implicitConversions.ToString(); + builder.WithImplicitConversionOperatorFrom(new(err), " => new(x);"); } } diff --git a/Funzo.SourceGenerators/Generators/Results/ResultGenerator.cs b/Funzo.SourceGenerators/Generators/Results/ResultGenerator.cs index bcfb446..7b23b93 100644 --- a/Funzo.SourceGenerators/Generators/Results/ResultGenerator.cs +++ b/Funzo.SourceGenerators/Generators/Results/ResultGenerator.cs @@ -1,8 +1,8 @@ using Funzo.SourceGenerators.Helpers; using Microsoft.CodeAnalysis; +using Sorse.BuilderInterfaces; using System.Collections.Generic; using System.Collections.Immutable; -using System.Text; namespace Funzo.SourceGenerators.Generators.Results; @@ -17,14 +17,14 @@ protected ResultGenerator(MarkedType symbolWithAttribute) _symbolWithAttribute = symbolWithAttribute; } - internal abstract string ClassDefinition { get; } - internal abstract string OkConstructor { get; } - internal abstract string ErrConstructor { get; } - internal abstract string OkStaticHelper { get; } - internal abstract string ErrStaticHelper { get; } - internal abstract string OkImplicitConverter { get; } - internal abstract string ErrImplicitConverter { get; } + internal void GenerateResult(IClassBuilder builder) + { + builder.Partial(); + + GenerateResultInner(builder); + } + protected abstract void GenerateResultInner(IClassBuilder builder); protected string ClassName => $"{ClassSymbol.Name}"; protected bool TryGetUnionTypes(ITypeSymbol type, out IEnumerable types) @@ -43,24 +43,13 @@ protected bool TryGetUnionTypes(ITypeSymbol type, out IEnumerable t } } - protected bool TryGetImplicitConvertersForUnionType(ITypeSymbol type, ResultParameterType parameterType, out string converters) + protected IEnumerable GetTypesNeedingImplicitConversions(ITypeSymbol type, ResultParameterType parameterType) { if (!TryGetUnionTypes(type, out var unionTypes)) { - converters = string.Empty; - return false; - } - - var sb = new StringBuilder(); - - var ctor = parameterType is ResultParameterType.Ok ? "Ok" : "Err"; - - foreach (var unionType in unionTypes) - { - sb.AppendLine($@"public static implicit operator {ClassName}({unionType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} _) => {ctor}(_);"); + return []; } - converters = sb.ToString(); - return true; + return unionTypes; } } diff --git a/Funzo.SourceGenerators/Generators/Unions/UnionGenerator.cs b/Funzo.SourceGenerators/Generators/Unions/UnionGenerator.cs index 054b3e4..6564dab 100644 --- a/Funzo.SourceGenerators/Generators/Unions/UnionGenerator.cs +++ b/Funzo.SourceGenerators/Generators/Unions/UnionGenerator.cs @@ -2,10 +2,11 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Sorse; +using Sorse.BuilderInterfaces; using System; using System.Collections.Immutable; using System.Linq; -using System.Text; namespace Funzo.SourceGenerators.Generators.Unions; @@ -24,47 +25,36 @@ internal class UnionGenerator : GeneratorBase var className = $"{classSymbol.Name}"; var typeArguments = type.AttributeTypeArguments; - StringBuilder source = new($@"// + var sorse = WithSorse.CreateNamespaceScope(classSymbol.ContainingNamespace.ToDisplayString(), []); -namespace {classSymbol.ContainingNamespace.ToDisplayString()} -{{"); + sorse.AddClassWithInnerClasses(type, AddUnionClass(type)); - // Add all needed upper classes - foreach (var containingType in type.ContainerClasses) - { - var isStatic = type.Symbol.ContainingType!.IsStatic ? "static " : ""; - - source.AppendLine($"{isStatic}partial class {containingType.Name} {{"); - } - - source.AppendLine($@" partial class {className} : global::{FunzoAttributeSources.AttributeNamespace}.Union{typeArguments.OpenGenericPart()} - {{"); - - foreach (var typeArgument in typeArguments) - { - source.Append($@" - public {classSymbol.Name}({typeArgument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} _) : base(_) {{}} - public static implicit operator {className}({typeArgument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} _) => new {className}(_); -"); - } + var src = sorse.GetSource(); - foreach (var prop in commonProperties) - { - source.Append($@" - public {prop.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {prop.Name} => Match({string.Join(",", Enumerable.Range(0, typeArguments.Length).Select(_ => $"x => x.{prop.Name}"))}); -"); - } - - // Close all the braces opened for parent classes - source.AppendLine(new string('}', type.ContainerClasses.Count)); - - source.Append(@" } -}"); - - var src = source.ToString(); return src; } + private Action AddUnionClass(MarkedType type) => + builder => + { + var typeArguments = type.AttributeTypeArguments; + builder.Partial() + .Inherits($"global::{FunzoAttributeSources.AttributeNamespace}.Union{typeArguments.OpenGenericPart()}"); + + foreach (var typeArgument in typeArguments) + { + builder.WithConstructor(c => c.WithBaseCall(["_"]).WithArguments([new(new(typeArgument), "_")])); + builder.WithImplicitConversionOperatorFrom(new(typeArgument), " => new(x);"); + } + + var commonProperties = GetCommonProperties(type.AttributeTypeArguments); + + foreach (var prop in commonProperties) + { + builder.WithProperty(new(prop.Type), prop.Name, p => p.WithComputedValue($" => Match({string.Join(",", Enumerable.Range(0, typeArguments.Length).Select(_ => $"x => x.{prop.Name}"))});")); + } + }; + private bool HasErrors(SourceProductionContext context, MarkedType type) { var symbol = type.Symbol; diff --git a/Funzo.SourceGenerators/Helpers/ContainerClass.cs b/Funzo.SourceGenerators/Helpers/ContainerClass.cs index c02a2ef..e12380c 100644 --- a/Funzo.SourceGenerators/Helpers/ContainerClass.cs +++ b/Funzo.SourceGenerators/Helpers/ContainerClass.cs @@ -1,6 +1,7 @@ namespace Funzo.SourceGenerators.Helpers; -public sealed class ContainerClass(string name) +public sealed class ContainerClass(string name, bool isStatic) { public string Name { get; } = name; + public bool IsStatic { get; } = isStatic; } diff --git a/Funzo.SourceGenerators/Helpers/MarkedType.cs b/Funzo.SourceGenerators/Helpers/MarkedType.cs index 5821c6d..4ea501b 100644 --- a/Funzo.SourceGenerators/Helpers/MarkedType.cs +++ b/Funzo.SourceGenerators/Helpers/MarkedType.cs @@ -26,7 +26,7 @@ private static Stack GenerateContainerClassesHierarchy(INamedTyp while (container is not null) { - containers.Push(new(container.Name)); + containers.Push(new(container.Name, container.IsStatic)); container = container.ContainingType; } diff --git a/Funzo.SourceGenerators/Helpers/SorseExtensions.cs b/Funzo.SourceGenerators/Helpers/SorseExtensions.cs new file mode 100644 index 0000000..d08db6e --- /dev/null +++ b/Funzo.SourceGenerators/Helpers/SorseExtensions.cs @@ -0,0 +1,66 @@ +using Sorse; +using Sorse.BuilderInterfaces; +using System; +using System.Linq; + +namespace Funzo.SourceGenerators.Helpers; + +internal static class SorseExtensions +{ + internal static void AddClassWithInnerClasses(this INamespaceScope sorse, MarkedType type, Action builderAction) + { + var classSymbol = type.Symbol; + var className = $"{classSymbol.Name}"; + var typeArguments = type.AttributeTypeArguments; + + // TODO: Needs some rework. Feels off + if (!type.ContainerClasses.Any()) + { + sorse.AddClass(className, builderAction); + } + else + { + var builder = GetClassBuilderFromContainingClasses(type, sorse); + + // Add the main partial class + builder.WithInnerClass(className, builderAction); + } + } + + private static IClassBuilder GetClassBuilderFromContainingClasses(MarkedType type, INamespaceScope sorse) + { + // Create the first class in the namespace + var container = type.ContainerClasses.Pop(); + + IClassBuilder builder = null!; + + sorse.AddClass(container.Name, b => + { + builder = b; + AddContainer(b, container); + }); + + // Nest the rest + foreach (var c in type.ContainerClasses) + { + builder.WithInnerClass(c.Name, b => + { + builder = b; + AddContainer(b, c); + }); + + } + + return builder; + + static void AddContainer(IClassBuilder builder, ContainerClass c) + { + builder.Partial(); + + if (c.IsStatic) + { + builder.Static(); + } + } + } +} diff --git a/Funzo.Test/UnionTests.cs b/Funzo.Test/UnionTests.cs index 28af8b8..5a0410d 100644 --- a/Funzo.Test/UnionTests.cs +++ b/Funzo.Test/UnionTests.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; namespace Funzo.Test;