diff --git a/Funzo.SourceGenerators.Test/ResultGeneratorTests.cs b/Funzo.SourceGenerators.Test/ResultGeneratorTests.cs index 5d0c7ed..a6d741d 100644 --- a/Funzo.SourceGenerators.Test/ResultGeneratorTests.cs +++ b/Funzo.SourceGenerators.Test/ResultGeneratorTests.cs @@ -66,7 +66,7 @@ static TwoUnionResult process(int value) [Fact] public void Generates_Nested_Results() { - InnerClass.InnerResult result = "test"; + InnerClass.ReallyInnerClass.InnerResult result = "test"; Assert.True(result.IsErr(out _)); } @@ -92,9 +92,11 @@ public partial class ClonedOk; [Result] public partial class CheapClone; - public static partial class InnerClass { - [Result] - public partial class InnerResult; + public partial class ReallyInnerClass + { + [Result] + public partial class InnerResult; + } } \ No newline at end of file diff --git a/Funzo.SourceGenerators.Test/UnionGeneratorTests.cs b/Funzo.SourceGenerators.Test/UnionGeneratorTests.cs index 41c83c0..e1f71de 100644 --- a/Funzo.SourceGenerators.Test/UnionGeneratorTests.cs +++ b/Funzo.SourceGenerators.Test/UnionGeneratorTests.cs @@ -47,7 +47,7 @@ public void Generates_Shared_Property() [Fact] public void Generates_Inner_Union() { - TestUnionInPartialClass.InnerUnion u = new TestUnionInPartialClass.A(); + TestUnionInPartialClass.InnerClass.InnerUnion u = new TestUnionInPartialClass.A(); u.Switch(a => { }, b => throw new InvalidOperationException()); } @@ -84,6 +84,9 @@ public static partial class TestUnionInPartialClass public record A; public record B; - [Union] - public partial class InnerUnion; + public partial class InnerClass + { + [Union] + public partial class InnerUnion; + } } \ No newline at end of file diff --git a/Funzo.SourceGenerators/Funzo.SourceGenerators.csproj b/Funzo.SourceGenerators/Funzo.SourceGenerators.csproj index 6750b0a..d57cc3c 100644 --- a/Funzo.SourceGenerators/Funzo.SourceGenerators.csproj +++ b/Funzo.SourceGenerators/Funzo.SourceGenerators.csproj @@ -6,7 +6,6 @@ Funzo.Generators Source generators for result and union types functional programming;option types;monad;union types;discriminated unions;result - True preview enable true diff --git a/Funzo.SourceGenerators/Generators/SourceGeneratorBase.cs b/Funzo.SourceGenerators/Generators/GeneratorBase.cs similarity index 76% rename from Funzo.SourceGenerators/Generators/SourceGeneratorBase.cs rename to Funzo.SourceGenerators/Generators/GeneratorBase.cs index be77bdf..bd15bc4 100644 --- a/Funzo.SourceGenerators/Generators/SourceGeneratorBase.cs +++ b/Funzo.SourceGenerators/Generators/GeneratorBase.cs @@ -2,10 +2,11 @@ using Microsoft.CodeAnalysis; using System.Linq; -namespace Funzo.SourceGenerators; -internal abstract class SourceGeneratorBase +namespace Funzo.SourceGenerators.Generators; + +internal abstract class GeneratorBase { - protected SourceGeneratorBase() + protected GeneratorBase() { } @@ -16,5 +17,5 @@ protected void CreateDiagnosticError(SourceProductionContext context, Diagnostic DiagnosticSeverity.Error)); } - internal abstract string? GetSource(SourceProductionContext context, SymbolWithAttribute symbolWithAttribute); + internal abstract string? GetSource(SourceProductionContext context, MarkedType type); } diff --git a/Funzo.SourceGenerators/Generators/ResultSourceGenerator.cs b/Funzo.SourceGenerators/Generators/ResultSourceGenerator.cs index a8bb4cd..018f1c7 100644 --- a/Funzo.SourceGenerators/Generators/ResultSourceGenerator.cs +++ b/Funzo.SourceGenerators/Generators/ResultSourceGenerator.cs @@ -4,37 +4,39 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; +using System.Collections.Immutable; using System.Linq; using System.Text; namespace Funzo.SourceGenerators.Generators; -internal class ResultSourceGenerator : SourceGeneratorBase +internal class ResultSourceGenerator : GeneratorBase { - internal override string? GetSource(SourceProductionContext context, SymbolWithAttribute symbolWithAttribute) + internal override string? GetSource(SourceProductionContext context, MarkedType type) { - if (HasErrors(context, symbolWithAttribute)) + if (HasErrors(context, type)) { return null; } - var isSimpleResult = symbolWithAttribute.TypeArguments.Length == 1; + var is1ArityResult = type.AttributeTypeArguments.Length == 1; - Results.ResultGenerator generator = isSimpleResult - ? new Result1AritySourceGenerator(symbolWithAttribute) - : new Result2AritySourceGenerator(symbolWithAttribute); + ResultGenerator generator = is1ArityResult + ? new Result1AritySourceGenerator(type) + : new Result2AritySourceGenerator(type); - var containingType = symbolWithAttribute.Symbol.ContainingType?.Name; + var sb = new StringBuilder(); + sb.AppendLine($@"// - var sb = new StringBuilder($@"// - -namespace {symbolWithAttribute.Symbol.ContainingNamespace.ToDisplayString()} +namespace {type.Symbol.ContainingNamespace.ToDisplayString()} {{"); - if (containingType is not null) + // Add all needed upper classes + foreach (var containingType in type.ContainerClasses) { - var isStatic = symbolWithAttribute.Symbol.ContainingType!.IsStatic ? "static " : ""; - sb.AppendLine($"{isStatic}partial class {containingType} {{"); + var isStatic = type.Symbol.ContainingType!.IsStatic ? "static " : ""; + + sb.AppendLine($"{isStatic}partial class {containingType.Name} {{"); } sb.AppendLine($@" @@ -49,44 +51,58 @@ namespace {symbolWithAttribute.Symbol.ContainingNamespace.ToDisplayString()} }} }}"); - if (containingType is not null) - { - sb.AppendLine("}"); - } + // Close all the braces opened for parent classes + sb.AppendLine(new string('}', type.ContainerClasses.Count)); var src = sb.ToString(); return src; } - private bool HasErrors(SourceProductionContext context, SymbolWithAttribute symbolWithAttribute) + private bool HasErrors(SourceProductionContext context, MarkedType type) { - var (ClassSymbol, _) = symbolWithAttribute; + var symbol = type.Symbol; + var typeArguments = type.AttributeTypeArguments; - if (!ClassSymbol.ContainingSymbol.Equals(ClassSymbol.ContainingNamespace, SymbolEqualityComparer.Default) && ClassSymbol.ContainingType is { } containerType - && containerType.DeclaringSyntaxReferences.Any(syntax => - syntax.GetSyntax() is BaseTypeDeclarationSyntax declaration - && !declaration.Modifiers.Any(mod => mod.IsKind(SyntaxKind.PartialKeyword)))) + if (!IsSymbolTopLevelOrInsidePartialClasses(symbol)) { - CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Result.TopLevelError, ClassSymbol); + CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Result.TopLevelError, symbol); return true; } - if (ClassSymbol.BaseType is not null && ClassSymbol.BaseType.Name != "Object") + if (IsSymbolInheritingFromOtherClasses(symbol)) { - CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Result.WrongBaseType, ClassSymbol); + CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Result.WrongBaseType, symbol); return true; } - foreach (var typeSymbol in symbolWithAttribute.TypeArguments) + if (IsAnyTypeArgumentObject(typeArguments)) { - if (typeSymbol.Name == nameof(Object)) - { - CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Result.ObjectNotValidType, ClassSymbol); - return true; - } + CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Result.ObjectNotValidType, symbol); + return true; + } + + if (AreTypeArgumentsTheSame(typeArguments)) + { + CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Result.RepeatedTypeSymbols, symbol); + return true; } return false; } + + private static bool IsSymbolTopLevelOrInsidePartialClasses(INamedTypeSymbol symbol) + => !(!symbol.ContainingSymbol.Equals(symbol.ContainingNamespace, SymbolEqualityComparer.Default) && symbol.ContainingType is { } containerType + && containerType.DeclaringSyntaxReferences.Any(syntax => + syntax.GetSyntax() is BaseTypeDeclarationSyntax declaration + && !declaration.Modifiers.Any(mod => mod.IsKind(SyntaxKind.PartialKeyword)))); + + private static bool IsSymbolInheritingFromOtherClasses(INamedTypeSymbol symbol) + => symbol.BaseType is not null && symbol.BaseType.Name != "Object"; + + private static bool IsAnyTypeArgumentObject(ImmutableArray typeArguments) + => typeArguments.Any(a => a.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "object"); + + private static bool AreTypeArgumentsTheSame(ImmutableArray typeArguments) + => typeArguments.Count() == 2 && SymbolEqualityComparer.Default.Equals(typeArguments.First(), typeArguments.Last()); } diff --git a/Funzo.SourceGenerators/Generators/Results/Result1AritySourceGenerator.cs b/Funzo.SourceGenerators/Generators/Results/Result1AritySourceGenerator.cs index e6ca760..8c1ae78 100644 --- a/Funzo.SourceGenerators/Generators/Results/Result1AritySourceGenerator.cs +++ b/Funzo.SourceGenerators/Generators/Results/Result1AritySourceGenerator.cs @@ -5,7 +5,7 @@ namespace Funzo.SourceGenerators.Generators.Results; internal class Result1AritySourceGenerator : ResultGenerator { - internal Result1AritySourceGenerator(SymbolWithAttribute symbolWithAttribute) : base(symbolWithAttribute) + internal Result1AritySourceGenerator(MarkedType symbolWithAttribute) : base(symbolWithAttribute) { } diff --git a/Funzo.SourceGenerators/Generators/Results/Result2AritySourceGenerator.cs b/Funzo.SourceGenerators/Generators/Results/Result2AritySourceGenerator.cs index 8fd7780..31f294e 100644 --- a/Funzo.SourceGenerators/Generators/Results/Result2AritySourceGenerator.cs +++ b/Funzo.SourceGenerators/Generators/Results/Result2AritySourceGenerator.cs @@ -7,7 +7,7 @@ namespace Funzo.SourceGenerators.Generators.Results; internal class Result2AritySourceGenerator : ResultGenerator { - internal Result2AritySourceGenerator(SymbolWithAttribute symbolWithAttribute) : base(symbolWithAttribute) + internal Result2AritySourceGenerator(MarkedType symbolWithAttribute) : base(symbolWithAttribute) { } diff --git a/Funzo.SourceGenerators/Generators/Results/ResultGenerator.cs b/Funzo.SourceGenerators/Generators/Results/ResultGenerator.cs index e9a9169..bcfb446 100644 --- a/Funzo.SourceGenerators/Generators/Results/ResultGenerator.cs +++ b/Funzo.SourceGenerators/Generators/Results/ResultGenerator.cs @@ -5,13 +5,14 @@ using System.Text; namespace Funzo.SourceGenerators.Generators.Results; + internal abstract class ResultGenerator { - private readonly SymbolWithAttribute _symbolWithAttribute; + private readonly MarkedType _symbolWithAttribute; protected INamedTypeSymbol ClassSymbol => _symbolWithAttribute.Symbol; - protected ImmutableArray TypeArguments => _symbolWithAttribute.AttributeData.AttributeClass!.TypeArguments; + protected ImmutableArray TypeArguments => _symbolWithAttribute.AttributeTypeArguments; - protected ResultGenerator(SymbolWithAttribute symbolWithAttribute) + protected ResultGenerator(MarkedType symbolWithAttribute) { _symbolWithAttribute = symbolWithAttribute; } diff --git a/Funzo.SourceGenerators/Generators/Unions/UnionGenerator.cs b/Funzo.SourceGenerators/Generators/Unions/UnionGenerator.cs new file mode 100644 index 0000000..054b3e4 --- /dev/null +++ b/Funzo.SourceGenerators/Generators/Unions/UnionGenerator.cs @@ -0,0 +1,134 @@ +using Funzo.SourceGenerators.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Text; + +namespace Funzo.SourceGenerators.Generators.Unions; + +internal class UnionGenerator : GeneratorBase +{ + internal override string? GetSource(SourceProductionContext context, MarkedType type) + { + if (HasErrors(context, type)) + { + return null; + } + + var commonProperties = GetCommonProperties(type.AttributeTypeArguments); + + var classSymbol = type.Symbol; + var className = $"{classSymbol.Name}"; + var typeArguments = type.AttributeTypeArguments; + + StringBuilder source = new($@"// + +namespace {classSymbol.ContainingNamespace.ToDisplayString()} +{{"); + + // 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}(_); +"); + } + + 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 bool HasErrors(SourceProductionContext context, MarkedType type) + { + var symbol = type.Symbol; + + if (!IsSymbolTopLevelOrInsidePartialClasses(symbol)) + { + CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Union.TopLevelError, symbol); + return true; + } + + if (IsSymbolInheritingFromOtherClasses(symbol)) + { + CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Union.WrongBaseType, symbol); + return true; + } + + + if (IsAnyTypeArgumentObject(type.AttributeTypeArguments)) + { + CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Union.ObjectNotValidType, symbol); + return true; + } + + if (IsAnyTypeArgumentAnInterface(type.AttributeTypeArguments)) + { + CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Union.InterfaceNotValidType, symbol); + return true; + } + + if (IsAnyTypeRepeatedInUnion(type.AttributeTypeArguments)) + { + CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Union.RepeatedTypeSymbols, symbol); + return true; + } + + return false; + } + + private static ImmutableArray GetCommonProperties(ImmutableArray typeArguments) + { + var typeProperties = typeArguments.SelectMany(t => t.GetPublicProperties()).ToLookup(x => x.Name); + + var commonProperties = typeProperties.Where(g => g.Count() == typeArguments.Length && g.All(s => s.Type.Equals(g.First().Type, SymbolEqualityComparer.IncludeNullability))) + .Select(x => x.First()) + .ToImmutableArray(); + + return commonProperties; + } + + private static bool IsSymbolTopLevelOrInsidePartialClasses(INamedTypeSymbol type) + => !(type.ContainingSymbol.Equals(type.ContainingNamespace, SymbolEqualityComparer.Default) && type.ContainingType is { } containerType + && containerType.DeclaringSyntaxReferences.Any(syntax => + syntax.GetSyntax() is BaseTypeDeclarationSyntax declaration + && !declaration.Modifiers.Any(mod => mod.IsKind(SyntaxKind.PartialKeyword)))); + + private static bool IsSymbolInheritingFromOtherClasses(INamedTypeSymbol symbol) + => symbol.BaseType is not null && symbol.BaseType.Name != "Object"; + + private static bool IsAnyTypeArgumentObject(ImmutableArray typeArguments) + => typeArguments.Any(a => a.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "object"); + + private static bool IsAnyTypeArgumentAnInterface(ImmutableArray typeArguments) + => typeArguments.Any(t => t.TypeKind == TypeKind.Interface); + + private static bool IsAnyTypeRepeatedInUnion(ImmutableArray typeArguments) + => typeArguments.ToLookup(x => x, SymbolEqualityComparer.Default).Any(g => g.Count() > 1); +} \ No newline at end of file diff --git a/Funzo.SourceGenerators/Generators/Unions/UnionSourceGenerator.cs b/Funzo.SourceGenerators/Generators/Unions/UnionSourceGenerator.cs deleted file mode 100644 index dc1b09c..0000000 --- a/Funzo.SourceGenerators/Generators/Unions/UnionSourceGenerator.cs +++ /dev/null @@ -1,128 +0,0 @@ -using Funzo.SourceGenerators.Helpers; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System; -using System.Collections.Immutable; -using System.Linq; -using System.Text; - -namespace Funzo.SourceGenerators.Generators.Unions; - -internal class UnionSourceGenerator : SourceGeneratorBase -{ - internal override string? GetSource(SourceProductionContext context, SymbolWithAttribute symbolWithAttribute) - { - if (HasErrors(context, symbolWithAttribute)) - { - return null; - } - - var commonProperties = GetCommonProperties(symbolWithAttribute.TypeArguments); - - var (classSymbol, _) = symbolWithAttribute; - var className = $"{classSymbol.Name}"; - var typeArguments = symbolWithAttribute.TypeArguments; - - var containingType = classSymbol.ContainingType?.Name; - - StringBuilder source = new($@"// - -namespace {classSymbol.ContainingNamespace.ToDisplayString()} -{{"); - - if (containingType is not null) - { - var isStatic = classSymbol.IsStatic ? "static " : ""; - source.AppendLine($"{isStatic}partial class {containingType} {{"); - } - - source.AppendLine($@" partial class {className} : global::Funzo.Union{typeArguments.OpenGenericPart()} - {{"); - - foreach (var type in typeArguments) - { - source.Append($@" - public {classSymbol.Name}({type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} _) : base(_) {{}} - public static implicit operator {className}({type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} _) => new {className}(_); -"); - } - - 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}"))}); -"); - } - - source.Append(@" } -}"); - if (containingType is not null) - { - source.AppendLine("}"); - } - - var src = source.ToString(); - return src; - } - - private bool HasErrors(SourceProductionContext context, SymbolWithAttribute symbol) - { - var (classSymbol, attributeData) = symbol; - var attributeLocation = classSymbol.Locations.FirstOrDefault() ?? Location.None; - - if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default) && classSymbol.ContainingType is { } containerType - && containerType.DeclaringSyntaxReferences.Any(syntax => - syntax.GetSyntax() is BaseTypeDeclarationSyntax declaration - && !declaration.Modifiers.Any(mod => mod.IsKind(SyntaxKind.PartialKeyword)))) - { - System.Diagnostics.Debugger.Launch(); - CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Union.TopLevelError, classSymbol); - return true; - } - - if (classSymbol.BaseType is not null && classSymbol.BaseType.Name != "Object") - { - CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Union.WrongBaseType, classSymbol); - return true; - } - - var typeArguments = attributeData.AttributeClass!.TypeArguments; - - foreach (var typeSymbol in typeArguments) - { - if (typeSymbol.Name == nameof(Object)) - { - CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Union.ObjectNotValidType, classSymbol); - return true; - } - - if (typeSymbol.TypeKind == TypeKind.Interface) - { - CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Union.InterfaceNotValidType, classSymbol); - return true; - } - } - - var typeArgLookup = typeArguments.ToLookup(x => x, SymbolEqualityComparer.Default); - - if (typeArgLookup.Any(a => a.Count() > 1)) - { - CreateDiagnosticError(context, FunzoDiagnosticDescriptors.Union.RepeatedTypeSymbols, classSymbol); - return true; - } - - return false; - } - - private static ImmutableArray GetCommonProperties(ImmutableArray typeArguments) - { - var typeProperties = typeArguments.SelectMany(t => t.GetPublicProperties()).ToLookup(x => x.Name); - - var commonProperties = typeProperties.Where(g => g.Count() == typeArguments.Length && g.All(s => s.Type.Equals(g.First().Type, SymbolEqualityComparer.IncludeNullability))) - .Select(x => x.First()) - .ToImmutableArray(); - - return commonProperties; - } -} \ No newline at end of file diff --git a/Funzo.SourceGenerators/Helpers/ContainerClass.cs b/Funzo.SourceGenerators/Helpers/ContainerClass.cs new file mode 100644 index 0000000..c02a2ef --- /dev/null +++ b/Funzo.SourceGenerators/Helpers/ContainerClass.cs @@ -0,0 +1,6 @@ +namespace Funzo.SourceGenerators.Helpers; + +public sealed class ContainerClass(string name) +{ + public string Name { get; } = name; +} diff --git a/Funzo.SourceGenerators/Helpers/FunzoDiagnosticDescriptors.cs b/Funzo.SourceGenerators/Helpers/FunzoDiagnosticDescriptors.cs index 1b224e9..9c331b0 100644 --- a/Funzo.SourceGenerators/Helpers/FunzoDiagnosticDescriptors.cs +++ b/Funzo.SourceGenerators/Helpers/FunzoDiagnosticDescriptors.cs @@ -7,65 +7,62 @@ internal static class FunzoDiagnosticDescriptors public static class Result { public static DiagnosticDescriptor TopLevelError - => new("FNZ0001", + => GenerateResultDescriptor("FNZ0001", "Class must be top level", - "Class '{0}' using ResultGenerator must be top level or inside a partial class", - "ResultGenerator", - DiagnosticSeverity.Error, - true); + "Class '{0}' using ResultGenerator must be top level or inside a partial class"); public static DiagnosticDescriptor WrongBaseType - => new("FNZ0002", "Result should not have a base class", - "Class '{0}' should not have any base class", - "ResultGenerator", - DiagnosticSeverity.Error, - true); + => GenerateResultDescriptor("FNZ0002", + "Result should not have a base class", + "Class '{0}' should not have any base class"); public static DiagnosticDescriptor ObjectNotValidType - => new("FNZ0003", "Object is not a valid type parameter", - "Defined conversions to or from a base type are not allowed for class '{0}'", - "ResultGenerator", - DiagnosticSeverity.Error, - true); + => GenerateResultDescriptor("FNZ0003", + "Object is not a valid type parameter", + "Defined conversions to or from a base type are not allowed for class '{0}'"); + + public static DiagnosticDescriptor RepeatedTypeSymbols + => GenerateResultDescriptor("FNZ0004", + "Types in Result cannot be the same", + "The types in a source generated result cannot be the same" + ); + + private static DiagnosticDescriptor GenerateResultDescriptor(string id, string title, string message, string? description = null) + => GenerateDescriptor(id, title, message, "ResultGenerator", description); } public static class Union { public static DiagnosticDescriptor TopLevelError - => new("FNZ0004", + => GenerateUnionDescriptor("FNZ1000", "Class must be top level", - "Class '{0}' using UnionGenerator must be top level or inside a partial class", - "UnionGenerator", - DiagnosticSeverity.Error, - true); + "Class '{0}' using UnionGenerator must be top level or inside a partial class"); public static DiagnosticDescriptor WrongBaseType - => new("FNZ0005", "Unions should not have a base class", - "Class '{0}' should not have a base class", - "UnionGenerator", - DiagnosticSeverity.Error, - true); + => GenerateUnionDescriptor("FNZ1001", + "Unions should not have a base class", + "Class '{0}' should not have a base class"); public static DiagnosticDescriptor ObjectNotValidType - => new("FNZ0006", "Object is not a valid type parameter", - "Defined conversions to or from a base type are not allowed for class '{0}'", - "UnionGenerator", - DiagnosticSeverity.Error, - true); + => GenerateUnionDescriptor("FNZ1002", + "Object is not a valid type parameter", + "Defined conversions to or from a base type are not allowed for class '{0}'"); public static DiagnosticDescriptor InterfaceNotValidType - => new("FNZ0007", "User-defined conversions to or from an interface are not allowed", + => GenerateUnionDescriptor("FNZ1003", "User-defined conversions to or from an interface are not allowed", - "UnionGenerator", - DiagnosticSeverity.Error, - true); + "User-defined conversions to or from an interface are not allowed"); public static DiagnosticDescriptor RepeatedTypeSymbols - => new("FNZ0008", "Cannot use the same type twice in a union", + => GenerateUnionDescriptor("FNZ1004", + "Cannot use the same type twice in a union", "Cannot use the same type twice in a union", - "UnionGenerator", - DiagnosticSeverity.Error, - true, "Type is used to manage unions. If you need to use the same type twice, use a wrapper type around what you need"); + + private static DiagnosticDescriptor GenerateUnionDescriptor(string id, string title, string message, string? description = null) + => GenerateDescriptor(id, title, message, "UnionGenerator", description); } + + private static DiagnosticDescriptor GenerateDescriptor(string id, string title, string message, string category, string? description = null) + => new(id, title, message, category, DiagnosticSeverity.Error, true, description); } diff --git a/Funzo.SourceGenerators/Helpers/MarkedType.cs b/Funzo.SourceGenerators/Helpers/MarkedType.cs new file mode 100644 index 0000000..5821c6d --- /dev/null +++ b/Funzo.SourceGenerators/Helpers/MarkedType.cs @@ -0,0 +1,35 @@ +using Microsoft.CodeAnalysis; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Funzo.SourceGenerators.Helpers; + +internal class MarkedType +{ + internal INamedTypeSymbol Symbol { get; } + internal ImmutableArray AttributeTypeArguments { get; } + internal Stack ContainerClasses { get; } = []; + + public MarkedType(INamedTypeSymbol symbol, AttributeData attributeData) + { + Symbol = symbol; + AttributeTypeArguments = attributeData.AttributeClass!.TypeArguments; + + ContainerClasses = GenerateContainerClassesHierarchy(symbol); + } + + private static Stack GenerateContainerClassesHierarchy(INamedTypeSymbol symbol) + { + var container = symbol.ContainingType; + + var containers = new Stack(); + + while (container is not null) + { + containers.Push(new(container.Name)); + container = container.ContainingType; + } + + return containers; + } +} diff --git a/Funzo.SourceGenerators/Helpers/PropertyWithType.cs b/Funzo.SourceGenerators/Helpers/PropertyWithType.cs deleted file mode 100644 index cae17d0..0000000 --- a/Funzo.SourceGenerators/Helpers/PropertyWithType.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Funzo.SourceGenerators.Helpers; -internal class PropertyWithType -{ -} diff --git a/Funzo.SourceGenerators/Helpers/SymbolWithAttribute.cs b/Funzo.SourceGenerators/Helpers/SymbolWithAttribute.cs deleted file mode 100644 index 4ae8735..0000000 --- a/Funzo.SourceGenerators/Helpers/SymbolWithAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.CodeAnalysis; -using System.Collections.Immutable; - -namespace Funzo.SourceGenerators.Helpers; - -internal class SymbolWithAttribute -{ - internal INamedTypeSymbol Symbol { get; } - internal AttributeData AttributeData { get; } - internal ImmutableArray TypeArguments => AttributeData.AttributeClass!.TypeArguments; - - public SymbolWithAttribute(INamedTypeSymbol symbol, AttributeData resultAttribute) - { - Symbol = symbol; - AttributeData = resultAttribute; - } - - internal void Deconstruct(out INamedTypeSymbol symbol, out AttributeData resultAttribute) - { - symbol = Symbol; - resultAttribute = AttributeData; - } -} diff --git a/Funzo.SourceGenerators/ResultGenerator.cs b/Funzo.SourceGenerators/ResultSourceGenerator.cs similarity index 76% rename from Funzo.SourceGenerators/ResultGenerator.cs rename to Funzo.SourceGenerators/ResultSourceGenerator.cs index 374b881..fd92278 100644 --- a/Funzo.SourceGenerators/ResultGenerator.cs +++ b/Funzo.SourceGenerators/ResultSourceGenerator.cs @@ -11,24 +11,24 @@ namespace Funzo.SourceGenerators { [Generator] - public class ResultGenerator : IIncrementalGenerator + public class ResultSourceGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { context.RegisterPostInitializationOutput(ctx => ctx.AddSource($"{FunzoAttributeSources.ResultAttributeName}.g.cs", FunzoAttributeSources.ResultAttributeContent)); - IncrementalValueProvider> resultClasses = context.SyntaxProvider + IncrementalValueProvider> resultClasses = context.SyntaxProvider .CreateSyntaxProvider(IsSyntaxTargetForGeneration, GetSemanticTargetForGeneration(FunzoAttributeSources.ResultAttributeFullNames)) .Where(static m => m is not null) .Collect()!; - context.RegisterSourceOutput(resultClasses, (spc, symbols) => Execute(spc, symbols, new ResultSourceGenerator())); + context.RegisterSourceOutput(resultClasses, (spc, symbols) => Execute(spc, symbols, new Generators.ResultSourceGenerator())); static bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationToken _) => node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword); - static Func GetSemanticTargetForGeneration(string[] attributeNames) + static Func GetSemanticTargetForGeneration(string[] attributeNames) => (context, cancellationToken) => { var node = context.Node; @@ -46,18 +46,20 @@ static bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationToken _) }; } - private static void Execute(SourceProductionContext context, ImmutableArray symbols, SourceGeneratorBase sourceGenerator) + private static void Execute(SourceProductionContext context, ImmutableArray markedTypes, GeneratorBase sourceGenerator) { - foreach (var symbol in symbols) + foreach (var type in markedTypes) { - var source = sourceGenerator.GetSource(context, symbol); + var source = sourceGenerator.GetSource(context, type); if (source is null) { continue; } - context.AddSource($"{symbol.Symbol!.ContainingNamespace}_{symbol.Symbol.Name}.g.cs", source); + var containingNamespace = type.Symbol.ContainingNamespace + string.Join("_", type.ContainerClasses); + + context.AddSource($"{containingNamespace}_{type.Symbol.Name}.g.cs", source); } } } diff --git a/Funzo.SourceGenerators/UnionGenerator.cs b/Funzo.SourceGenerators/UnionSourceGenerator.cs similarity index 75% rename from Funzo.SourceGenerators/UnionGenerator.cs rename to Funzo.SourceGenerators/UnionSourceGenerator.cs index b6f2df3..7807b10 100644 --- a/Funzo.SourceGenerators/UnionGenerator.cs +++ b/Funzo.SourceGenerators/UnionSourceGenerator.cs @@ -1,4 +1,5 @@ -using Funzo.SourceGenerators.Generators.Unions; +using Funzo.SourceGenerators.Generators; +using Funzo.SourceGenerators.Generators.Unions; using Funzo.SourceGenerators.Helpers; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -17,18 +18,18 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { context.RegisterPostInitializationOutput(ctx => ctx.AddSource($"{FunzoAttributeSources.UnionAttributeName}.g.cs", FunzoAttributeSources.UnionAttributeContent)); - IncrementalValueProvider> unionClasses = context.SyntaxProvider + IncrementalValueProvider> unionClasses = context.SyntaxProvider .CreateSyntaxProvider(IsSyntaxTargetForGeneration, GetSemanticTargetForGeneration(FunzoAttributeSources.UnionAttributeFullNames)) .Where(static m => m is not null) .Collect()!; - context.RegisterSourceOutput(unionClasses, (spc, symbols) => Execute(spc, symbols, new UnionSourceGenerator())); + context.RegisterSourceOutput(unionClasses, (spc, symbols) => Execute(spc, symbols, new UnionGenerator())); static bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationToken _) => node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword); - static Func GetSemanticTargetForGeneration(string[] attributeNames) + static Func GetSemanticTargetForGeneration(string[] attributeNames) => (context, cancellationToken) => { var node = context.Node; @@ -46,18 +47,20 @@ static bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationToken _) }; } - private static void Execute(SourceProductionContext context, ImmutableArray symbols, SourceGeneratorBase sourceGenerator) + private static void Execute(SourceProductionContext context, ImmutableArray markedTypes, GeneratorBase sourceGenerator) { - foreach (var symbol in symbols) + foreach (var type in markedTypes) { - var source = sourceGenerator.GetSource(context, symbol); + var source = sourceGenerator.GetSource(context, type); if (source is null) { continue; } - context.AddSource($"{symbol.Symbol!.ContainingNamespace}_{symbol.Symbol.Name}.g.cs", source); + var containingNamespace = type.Symbol.ContainingNamespace + string.Join("_", type.ContainerClasses); + + context.AddSource($"{containingNamespace}_{type.Symbol.Name}.g.cs", source); } } } diff --git a/Funzo.sln b/Funzo.sln deleted file mode 100644 index c30db36..0000000 --- a/Funzo.sln +++ /dev/null @@ -1,70 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.33209.295 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Funzo", "Funzo\Funzo.csproj", "{0F8708B6-C99C-40F4-8260-1A17749784C4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Funzo.Test", "Funzo.Test\Funzo.Test.csproj", "{06F30F25-CADB-4374-AAB4-1FCA1DD05324}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Funzo.Generator", "Funzo.Generator\Funzo.Generator.csproj", "{F9F9C1C6-C622-4FAA-930B-3360625D1E63}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Funzo.Serialization", "Funzo.Serialization\Funzo.Serialization.csproj", "{EAF123F6-BBE1-4236-BE85-AA15E6546FCE}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}" - ProjectSection(SolutionItems) = preProject - .github\workflows\build-ci.yml = .github\workflows\build-ci.yml - CHANGELOG.md = CHANGELOG.md - LICENSE = LICENSE - README.md = README.md - .github\workflows\release.yml = .github\workflows\release.yml - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Funzo.Example", "Funzo.Example\Funzo.Example.csproj", "{B239C265-7416-4756-9D4D-35B0361DA3A9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Funzo.SourceGenerators", "Funzo.SourceGenerators\Funzo.SourceGenerators.csproj", "{827235C4-9711-4D73-91A3-52B5BA56320B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Funzo.SourceGenerators.Test", "Funzo.SourceGenerators.Test\Funzo.SourceGenerators.Test.csproj", "{6CF5F9D7-CF12-4606-BE90-4EBE538A220A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0F8708B6-C99C-40F4-8260-1A17749784C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0F8708B6-C99C-40F4-8260-1A17749784C4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0F8708B6-C99C-40F4-8260-1A17749784C4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0F8708B6-C99C-40F4-8260-1A17749784C4}.Release|Any CPU.Build.0 = Release|Any CPU - {06F30F25-CADB-4374-AAB4-1FCA1DD05324}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {06F30F25-CADB-4374-AAB4-1FCA1DD05324}.Debug|Any CPU.Build.0 = Debug|Any CPU - {06F30F25-CADB-4374-AAB4-1FCA1DD05324}.Release|Any CPU.ActiveCfg = Release|Any CPU - {06F30F25-CADB-4374-AAB4-1FCA1DD05324}.Release|Any CPU.Build.0 = Release|Any CPU - {F9F9C1C6-C622-4FAA-930B-3360625D1E63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F9F9C1C6-C622-4FAA-930B-3360625D1E63}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F9F9C1C6-C622-4FAA-930B-3360625D1E63}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F9F9C1C6-C622-4FAA-930B-3360625D1E63}.Release|Any CPU.Build.0 = Release|Any CPU - {EAF123F6-BBE1-4236-BE85-AA15E6546FCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EAF123F6-BBE1-4236-BE85-AA15E6546FCE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EAF123F6-BBE1-4236-BE85-AA15E6546FCE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EAF123F6-BBE1-4236-BE85-AA15E6546FCE}.Release|Any CPU.Build.0 = Release|Any CPU - {B239C265-7416-4756-9D4D-35B0361DA3A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B239C265-7416-4756-9D4D-35B0361DA3A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B239C265-7416-4756-9D4D-35B0361DA3A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B239C265-7416-4756-9D4D-35B0361DA3A9}.Release|Any CPU.Build.0 = Release|Any CPU - {827235C4-9711-4D73-91A3-52B5BA56320B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {827235C4-9711-4D73-91A3-52B5BA56320B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {827235C4-9711-4D73-91A3-52B5BA56320B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {827235C4-9711-4D73-91A3-52B5BA56320B}.Release|Any CPU.Build.0 = Release|Any CPU - {6CF5F9D7-CF12-4606-BE90-4EBE538A220A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6CF5F9D7-CF12-4606-BE90-4EBE538A220A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6CF5F9D7-CF12-4606-BE90-4EBE538A220A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6CF5F9D7-CF12-4606-BE90-4EBE538A220A}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {63C4A791-6E2B-4293-91AA-79AD40F77B11} - EndGlobalSection -EndGlobal diff --git a/Funzo.slnx b/Funzo.slnx new file mode 100644 index 0000000..fa12e4d --- /dev/null +++ b/Funzo.slnx @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + +