diff --git a/Source/FunicularSwitch.Generators.Common/RoslynExtensions.cs b/Source/FunicularSwitch.Generators.Common/RoslynExtensions.cs index 2e394ca8..4e5892a1 100644 --- a/Source/FunicularSwitch.Generators.Common/RoslynExtensions.cs +++ b/Source/FunicularSwitch.Generators.Common/RoslynExtensions.cs @@ -41,13 +41,24 @@ public static bool IsAnyKeyWord(this string identifier) => || SyntaxFacts.GetReservedKeywordKinds().Contains(SyntaxFactory.ParseToken(identifier).Kind()); - public static bool InheritsFrom(this INamedTypeSymbol symbol, ITypeSymbol type) + public static bool InheritsFrom(this INamedTypeSymbol symbol, INamedTypeSymbol type) { var baseType = symbol.BaseType; while (baseType != null) { if (type.Equals(baseType, SymbolEqualityComparer.Default)) + { + return true; + } + + // If the derived type is not declared as a nested type and the base type is generic, then the generic s will not be equal to each other. + // That is why we compare to the fully qualified display string without generics and then assert the number of type arguments is the same, which also prevents issues when the type arguments are named differently in the deriving class + if (type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGenericsOptions(SymbolDisplayGenericsOptions.None)) == + baseType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGenericsOptions(SymbolDisplayGenericsOptions.None)) + && type.TypeParameters.Length == baseType.TypeParameters.Length) + { return true; + } baseType = baseType.BaseType; } @@ -101,7 +112,7 @@ public static QualifiedTypeName QualifiedName(this BaseTypeDeclarationSyntax dec return new(dec.Name(), typeNames); } - public static QualifiedTypeName QualifiedNameWithGenerics(this BaseTypeDeclarationSyntax dec) + public static QualifiedTypeName QualifiedNameWithGenerics(this BaseTypeDeclarationSyntax dec, INamedTypeSymbol typeSymbol, INamedTypeSymbol baseType) { var current = dec.Parent as BaseTypeDeclarationSyntax; var typeNames = new Stack(); @@ -110,8 +121,25 @@ public static QualifiedTypeName QualifiedNameWithGenerics(this BaseTypeDeclarati typeNames.Push(current.Name() + FormatTypeParameters(current.GetTypeParameterList())); current = current.Parent as BaseTypeDeclarationSyntax; } + + // If the base type has type parameters with different names then the generics are not resolved properly e.g. in the match method + // If though they are the same number of parameters and are passed in the same order as they are declared, we can replace the type argument list with the one from the base type and it should still work + var typeParameters = typeSymbol.TypeParameters; + var argumentsOnBaseType = typeSymbol.BaseType?.TypeArguments ?? []; + var baseTypeTypeParameters = baseType.TypeParameters; + EquatableArray typeParametersForFormatting; + if (typeParameters.Length == argumentsOnBaseType.Length + && typeParameters.Zip(argumentsOnBaseType, ValueTuple.Create).All(x => x.Item1.Equals(x.Item2, SymbolEqualityComparer.Default)) + && typeParameters.Length == baseTypeTypeParameters.Length) + { + typeParametersForFormatting = baseTypeTypeParameters.Select(tp => tp.Name).ToImmutableArray(); + } + else + { + typeParametersForFormatting = typeParameters.Select(tp => tp.Name).ToImmutableArray(); + } - return new(dec.Name() + FormatTypeParameters(dec.GetTypeParameterList()), typeNames); + return new(dec.Name() + FormatTypeParameters(typeParametersForFormatting), typeNames); } public static EquatableArray GetTypeParameterList(this BaseTypeDeclarationSyntax dec) diff --git a/Source/FunicularSwitch.Generators/UnionType/Parser.cs b/Source/FunicularSwitch.Generators/UnionType/Parser.cs index 1018618a..e29aa0d6 100644 --- a/Source/FunicularSwitch.Generators/UnionType/Parser.cs +++ b/Source/FunicularSwitch.Generators/UnionType/Parser.cs @@ -42,15 +42,27 @@ public static GenerationResult GetUnionTypeSchema( var treeSemanticModel = syntaxTree != unionTypeClass.SyntaxTree ? compilation.GetSemanticModel(syntaxTree) : semanticModel; return FindConcreteDerivedTypesWalker.Get(root, unionTypeSymbol, treeSemanticModel); - }); + }) + .ToList(); var isPartial = unionTypeClass.Modifiers.HasModifier(SyntaxKind.PartialKeyword) && unionTypeSymbol.ContainingType == null; //for now do not generate factory methods for nested types, we could support that if all containing types are partial - var generateFactoryMethods = isPartial && staticFactoryMethods; + var anyDerivedTypeInheritsFromBaseWithResolvedGenericArgument = derivedTypes.Any(tuple => + tuple.symbol.BaseType?.TypeArguments.Any(ta => ta is not ITypeParameterSymbol) ?? false); + var anyDerivedTypeHasDifferentParameterNames = derivedTypes.Any(tuple => + { + if (unionTypeSymbol.Equals(tuple.symbol.ContainingType, SymbolEqualityComparer.Default)) + { + return false; + } + return !tuple.symbol.TypeParameters.Select(tp => tp.Name) + .SequenceEqual(unionTypeSymbol.TypeParameters.Select(tp => tp.Name)); + }); + var generateFactoryMethods = isPartial && staticFactoryMethods && !anyDerivedTypeInheritsFromBaseWithResolvedGenericArgument && !anyDerivedTypeHasDifferentParameterNames; return - ToOrderedCases(caseOrder, derivedTypes, compilation, generateFactoryMethods, unionTypeSymbol.Name) + ToOrderedCases(caseOrder, derivedTypes, compilation, generateFactoryMethods, unionTypeSymbol) .Map(cases => new UnionTypeSchema( Namespace: fullNamespace, TypeName: unionTypeSymbol.Name, @@ -127,12 +139,13 @@ PropertyDeclarationSyntax p when p.Modifiers.HasModifier(SyntaxKind.StaticKeywor static GenerationResult> ToOrderedCases(CaseOrder caseOrder, IEnumerable<(INamedTypeSymbol symbol, BaseTypeDeclarationSyntax node, int? caseIndex, int - numberOfConctreteBaseTypes)> derivedTypes, Compilation compilation, bool getConstructors, string baseTypeName) + numberOfConctreteBaseTypes)> derivedTypes, Compilation compilation, bool getConstructors, INamedTypeSymbol baseType) { + var baseTypeName = baseType.Name; var ordered = derivedTypes.OrderByDescending(d => d.numberOfConctreteBaseTypes); ordered = caseOrder switch { - CaseOrder.Alphabetic => ordered.ThenBy(d => d.node.QualifiedNameWithGenerics().Name), + CaseOrder.Alphabetic => ordered.ThenBy(d => d.node.QualifiedNameWithGenerics(d.symbol, baseType).Name), CaseOrder.AsDeclared => ordered.ThenBy(d => d.node.SyntaxTree.FilePath) .ThenBy(d => d.node.Span.Start), CaseOrder.Explicit => ordered.ThenBy(d => d.caseIndex), @@ -175,7 +188,8 @@ static GenerationResult> ToOrderedCases(CaseOrder ca var derived = result.Select(d => { - var qualifiedTypeName = d.node.QualifiedNameWithGenerics(); + var qualifiedNameWithGenerics = d.node.QualifiedNameWithGenerics(d.symbol, baseType); + var qualifiedName = d.node.QualifiedName(); var fullNamespace = d.symbol.GetFullNamespace(); var constructors = ImmutableArray.Empty; var requiredMembers = ImmutableArray.Empty; @@ -205,10 +219,10 @@ static GenerationResult> ToOrderedCases(CaseOrder ca } var (parameterName, staticMethodName) = - DeriveParameterAndStaticMethodName(qualifiedTypeName.Name, baseTypeName); + DeriveParameterAndStaticMethodName(qualifiedName.Name, baseTypeName); return new DerivedType( - fullTypeName: $"{(fullNamespace != null ? $"{fullNamespace}." : "")}{qualifiedTypeName}", + fullTypeName: $"{(fullNamespace != null ? $"{fullNamespace}." : "")}{qualifiedNameWithGenerics}", constructors: constructors, requiredMembers: requiredMembers, parameterName: parameterName, @@ -230,15 +244,15 @@ class FindConcreteDerivedTypesWalker : CSharpSyntaxWalker { readonly List<(INamedTypeSymbol symbol, BaseTypeDeclarationSyntax node, int? caseIndex)> m_DerivedClasses = new(); readonly SemanticModel m_SemanticModel; - readonly ITypeSymbol m_BaseClass; + readonly INamedTypeSymbol m_BaseClass; - FindConcreteDerivedTypesWalker(SemanticModel semanticModel, ITypeSymbol baseClass) + FindConcreteDerivedTypesWalker(SemanticModel semanticModel, INamedTypeSymbol baseClass) { m_SemanticModel = semanticModel; m_BaseClass = baseClass; } - public static IEnumerable<(INamedTypeSymbol symbol, BaseTypeDeclarationSyntax node, int? caseIndex, int numberOfConctreteBaseTypes)> Get(SyntaxNode node, ITypeSymbol baseClass, SemanticModel semanticModel) + public static IEnumerable<(INamedTypeSymbol symbol, BaseTypeDeclarationSyntax node, int? caseIndex, int numberOfConctreteBaseTypes)> Get(SyntaxNode node, INamedTypeSymbol baseClass, SemanticModel semanticModel) { var me = new FindConcreteDerivedTypesWalker(semanticModel, baseClass); me.Visit(node); diff --git a/Source/Tests/FunicularSwitch.Generators.Test/ResultTypeGeneratorSpecs.cs b/Source/Tests/FunicularSwitch.Generators.Test/ResultTypeGeneratorSpecs.cs index af441720..8f86f6ca 100644 --- a/Source/Tests/FunicularSwitch.Generators.Test/ResultTypeGeneratorSpecs.cs +++ b/Source/Tests/FunicularSwitch.Generators.Test/ResultTypeGeneratorSpecs.cs @@ -46,7 +46,7 @@ public static class MyErrorExtension public static string MergeErrors(this string error, string other) => $""{error}{System.Environment.NewLine}{other}""; } "; - return Verify(code); + return Verify(code, numberOfGeneratedFiles: 4); } [TestMethod] @@ -82,7 +82,7 @@ public static class MyErrorExtension } } "; - return Verify(code); + return Verify(code, numberOfGeneratedFiles: 2); } [TestMethod] @@ -103,7 +103,7 @@ public enum MyError Unauthorized } "; - return Verify(code); + return Verify(code, numberOfGeneratedFiles: 0); } @@ -231,6 +231,6 @@ public override bool Equals(object obj) public override int GetHashCode() => (int)UnionCase; } "; - return Verify(code); + return Verify(code, numberOfGeneratedFiles: 2); } } \ No newline at end of file diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested#Attributes.g.00.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested#Attributes.g.00.verified.cs new file mode 100644 index 00000000..3336ef53 --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested#Attributes.g.00.verified.cs @@ -0,0 +1,41 @@ +//HintName: Attributes.g.cs +#nullable enable +using System; + +// ReSharper disable once CheckNamespace +namespace FunicularSwitch.Generators +{ + /// + /// Mark an abstract partial type with a single generic argument with the ResultType attribute. + /// This type from now on has Ok | Error semantics with map and bind operations. + /// + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + sealed class ResultTypeAttribute : Attribute + { + public ResultTypeAttribute() => ErrorType = typeof(string); + public ResultTypeAttribute(Type errorType) => ErrorType = errorType; + + public Type ErrorType { get; set; } + } + + /// + /// Mark a static method or a member method or you error type with the MergeErrorAttribute attribute. + /// Static signature: TError -> TError -> TError. Member signature: TError -> TError + /// We are now able to collect errors and methods like Validate, Aggregate, FirstOk that are useful to combine results are generated. + /// + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + sealed class MergeErrorAttribute : Attribute + { + } + + /// + /// Mark a static method with the ExceptionToError attribute. + /// Signature: Exception -> TError + /// This method is always called, when an exception happens in a bind operation. + /// So a call like result.Map(i => i/0) will return an Error produced by the factory method instead of throwing the DivisionByZero exception. + /// + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + sealed class ExceptionToError : Attribute + { + } +} \ No newline at end of file diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested#Attributes.g.01.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested#Attributes.g.01.verified.cs new file mode 100644 index 00000000..3d6aae40 --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested#Attributes.g.01.verified.cs @@ -0,0 +1,29 @@ +//HintName: Attributes.g.cs +#nullable enable +using System; + +// ReSharper disable once CheckNamespace +namespace FunicularSwitch.Generators +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false)] + sealed class UnionTypeAttribute : Attribute + { + public CaseOrder CaseOrder { get; set; } = CaseOrder.Alphabetic; + public bool StaticFactoryMethods { get; set; } = true; + } + + enum CaseOrder + { + Alphabetic, + AsDeclared, + Explicit + } + + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + sealed class UnionCaseAttribute : Attribute + { + public UnionCaseAttribute(int index) => Index = index; + + public int Index { get; } + } +} \ No newline at end of file diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested#Attributes.g.02.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested#Attributes.g.02.verified.cs new file mode 100644 index 00000000..ad0ffe0d --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested#Attributes.g.02.verified.cs @@ -0,0 +1,62 @@ +//HintName: Attributes.g.cs +#nullable enable +using System; + +// ReSharper disable once CheckNamespace +namespace FunicularSwitch.Generators +{ + [AttributeUsage(AttributeTargets.Enum)] + sealed class ExtendedEnumAttribute : Attribute + { + public EnumCaseOrder CaseOrder { get; set; } = EnumCaseOrder.AsDeclared; + public ExtensionAccessibility Accessibility { get; set; } = ExtensionAccessibility.Public; + } + + enum EnumCaseOrder + { + Alphabetic, + AsDeclared + } + + /// + /// Generate match methods for all enums defined in assembly that contains AssemblySpecifier. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + class ExtendEnumsAttribute : Attribute + { + public Type AssemblySpecifier { get; } + public EnumCaseOrder CaseOrder { get; set; } = EnumCaseOrder.AsDeclared; + public ExtensionAccessibility Accessibility { get; set; } = ExtensionAccessibility.Public; + + public ExtendEnumsAttribute() => AssemblySpecifier = typeof(ExtendEnumsAttribute); + + public ExtendEnumsAttribute(Type assemblySpecifier) + { + AssemblySpecifier = assemblySpecifier; + } + } + + /// + /// Generate match methods for Type. Must be enum. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + class ExtendEnumAttribute : Attribute + { + public Type Type { get; } + + public EnumCaseOrder CaseOrder { get; set; } = EnumCaseOrder.AsDeclared; + + public ExtensionAccessibility Accessibility { get; set; } = ExtensionAccessibility.Public; + + public ExtendEnumAttribute(Type type) + { + Type = type; + } + } + + enum ExtensionAccessibility + { + Internal, + Public + } +} \ No newline at end of file diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested#FunicularSwitchTestBaseTypeOfTMatchExtension.g.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested#FunicularSwitchTestBaseTypeOfTMatchExtension.g.verified.cs new file mode 100644 index 00000000..92cdc2c8 --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested#FunicularSwitchTestBaseTypeOfTMatchExtension.g.verified.cs @@ -0,0 +1,73 @@ +//HintName: FunicularSwitchTestBaseTypeOfTMatchExtension.g.cs +#pragma warning disable 1591 +#nullable enable +namespace FunicularSwitch.Test +{ + public static partial class BaseTypeMatchExtension + { + public static TMatchResult Match(this global::FunicularSwitch.Test.BaseType baseType, global::System.Func, TMatchResult> deriving, global::System.Func, TMatchResult> deriving2) => + baseType switch + { + FunicularSwitch.Test.Deriving deriving1 => deriving(deriving1), + FunicularSwitch.Test.Deriving2 deriving22 => deriving2(deriving22), + _ => throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}") + }; + + public static global::System.Threading.Tasks.Task Match(this global::FunicularSwitch.Test.BaseType baseType, global::System.Func, global::System.Threading.Tasks.Task> deriving, global::System.Func, global::System.Threading.Tasks.Task> deriving2) => + baseType switch + { + FunicularSwitch.Test.Deriving deriving1 => deriving(deriving1), + FunicularSwitch.Test.Deriving2 deriving22 => deriving2(deriving22), + _ => throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}") + }; + + public static async global::System.Threading.Tasks.Task Match(this global::System.Threading.Tasks.Task> baseType, global::System.Func, TMatchResult> deriving, global::System.Func, TMatchResult> deriving2) => + (await baseType.ConfigureAwait(false)).Match(deriving, deriving2); + + public static async global::System.Threading.Tasks.Task Match(this global::System.Threading.Tasks.Task> baseType, global::System.Func, global::System.Threading.Tasks.Task> deriving, global::System.Func, global::System.Threading.Tasks.Task> deriving2) => + await (await baseType.ConfigureAwait(false)).Match(deriving, deriving2).ConfigureAwait(false); + + public static void Switch(this global::FunicularSwitch.Test.BaseType baseType, global::System.Action> deriving, global::System.Action> deriving2) + { + switch (baseType) + { + case FunicularSwitch.Test.Deriving deriving1: + deriving(deriving1); + break; + case FunicularSwitch.Test.Deriving2 deriving22: + deriving2(deriving22); + break; + default: + throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}"); + } + } + + public static async global::System.Threading.Tasks.Task Switch(this global::FunicularSwitch.Test.BaseType baseType, global::System.Func, global::System.Threading.Tasks.Task> deriving, global::System.Func, global::System.Threading.Tasks.Task> deriving2) + { + switch (baseType) + { + case FunicularSwitch.Test.Deriving deriving1: + await deriving(deriving1).ConfigureAwait(false); + break; + case FunicularSwitch.Test.Deriving2 deriving22: + await deriving2(deriving22).ConfigureAwait(false); + break; + default: + throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}"); + } + } + + public static async global::System.Threading.Tasks.Task Switch(this global::System.Threading.Tasks.Task> baseType, global::System.Action> deriving, global::System.Action> deriving2) => + (await baseType.ConfigureAwait(false)).Switch(deriving, deriving2); + + public static async global::System.Threading.Tasks.Task Switch(this global::System.Threading.Tasks.Task> baseType, global::System.Func, global::System.Threading.Tasks.Task> deriving, global::System.Func, global::System.Threading.Tasks.Task> deriving2) => + await (await baseType.ConfigureAwait(false)).Switch(deriving, deriving2).ConfigureAwait(false); + } + + public abstract partial record BaseType + { + public static FunicularSwitch.Test.BaseType Deriving(string Value, T Other) => new FunicularSwitch.Test.Deriving(Value, Other); + public static FunicularSwitch.Test.BaseType Deriving2(string Value) => new FunicularSwitch.Test.Deriving2(Value); + } +} +#pragma warning restore 1591 diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_are_not_generic#Attributes.g.00.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_are_not_generic#Attributes.g.00.verified.cs new file mode 100644 index 00000000..3336ef53 --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_are_not_generic#Attributes.g.00.verified.cs @@ -0,0 +1,41 @@ +//HintName: Attributes.g.cs +#nullable enable +using System; + +// ReSharper disable once CheckNamespace +namespace FunicularSwitch.Generators +{ + /// + /// Mark an abstract partial type with a single generic argument with the ResultType attribute. + /// This type from now on has Ok | Error semantics with map and bind operations. + /// + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + sealed class ResultTypeAttribute : Attribute + { + public ResultTypeAttribute() => ErrorType = typeof(string); + public ResultTypeAttribute(Type errorType) => ErrorType = errorType; + + public Type ErrorType { get; set; } + } + + /// + /// Mark a static method or a member method or you error type with the MergeErrorAttribute attribute. + /// Static signature: TError -> TError -> TError. Member signature: TError -> TError + /// We are now able to collect errors and methods like Validate, Aggregate, FirstOk that are useful to combine results are generated. + /// + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + sealed class MergeErrorAttribute : Attribute + { + } + + /// + /// Mark a static method with the ExceptionToError attribute. + /// Signature: Exception -> TError + /// This method is always called, when an exception happens in a bind operation. + /// So a call like result.Map(i => i/0) will return an Error produced by the factory method instead of throwing the DivisionByZero exception. + /// + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + sealed class ExceptionToError : Attribute + { + } +} \ No newline at end of file diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_are_not_generic#Attributes.g.01.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_are_not_generic#Attributes.g.01.verified.cs new file mode 100644 index 00000000..3d6aae40 --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_are_not_generic#Attributes.g.01.verified.cs @@ -0,0 +1,29 @@ +//HintName: Attributes.g.cs +#nullable enable +using System; + +// ReSharper disable once CheckNamespace +namespace FunicularSwitch.Generators +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false)] + sealed class UnionTypeAttribute : Attribute + { + public CaseOrder CaseOrder { get; set; } = CaseOrder.Alphabetic; + public bool StaticFactoryMethods { get; set; } = true; + } + + enum CaseOrder + { + Alphabetic, + AsDeclared, + Explicit + } + + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + sealed class UnionCaseAttribute : Attribute + { + public UnionCaseAttribute(int index) => Index = index; + + public int Index { get; } + } +} \ No newline at end of file diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_are_not_generic#Attributes.g.02.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_are_not_generic#Attributes.g.02.verified.cs new file mode 100644 index 00000000..ad0ffe0d --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_are_not_generic#Attributes.g.02.verified.cs @@ -0,0 +1,62 @@ +//HintName: Attributes.g.cs +#nullable enable +using System; + +// ReSharper disable once CheckNamespace +namespace FunicularSwitch.Generators +{ + [AttributeUsage(AttributeTargets.Enum)] + sealed class ExtendedEnumAttribute : Attribute + { + public EnumCaseOrder CaseOrder { get; set; } = EnumCaseOrder.AsDeclared; + public ExtensionAccessibility Accessibility { get; set; } = ExtensionAccessibility.Public; + } + + enum EnumCaseOrder + { + Alphabetic, + AsDeclared + } + + /// + /// Generate match methods for all enums defined in assembly that contains AssemblySpecifier. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + class ExtendEnumsAttribute : Attribute + { + public Type AssemblySpecifier { get; } + public EnumCaseOrder CaseOrder { get; set; } = EnumCaseOrder.AsDeclared; + public ExtensionAccessibility Accessibility { get; set; } = ExtensionAccessibility.Public; + + public ExtendEnumsAttribute() => AssemblySpecifier = typeof(ExtendEnumsAttribute); + + public ExtendEnumsAttribute(Type assemblySpecifier) + { + AssemblySpecifier = assemblySpecifier; + } + } + + /// + /// Generate match methods for Type. Must be enum. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + class ExtendEnumAttribute : Attribute + { + public Type Type { get; } + + public EnumCaseOrder CaseOrder { get; set; } = EnumCaseOrder.AsDeclared; + + public ExtensionAccessibility Accessibility { get; set; } = ExtensionAccessibility.Public; + + public ExtendEnumAttribute(Type type) + { + Type = type; + } + } + + enum ExtensionAccessibility + { + Internal, + Public + } +} \ No newline at end of file diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_are_not_generic#FunicularSwitchTestBaseTypeOfTMatchExtension.g.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_are_not_generic#FunicularSwitchTestBaseTypeOfTMatchExtension.g.verified.cs new file mode 100644 index 00000000..aad8d663 --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_are_not_generic#FunicularSwitchTestBaseTypeOfTMatchExtension.g.verified.cs @@ -0,0 +1,59 @@ +//HintName: FunicularSwitchTestBaseTypeOfTMatchExtension.g.cs +#pragma warning disable 1591 +#nullable enable +namespace FunicularSwitch.Test +{ + public static partial class BaseTypeMatchExtension + { + public static TMatchResult Match(this global::FunicularSwitch.Test.BaseType baseType, global::System.Func deriving) => + baseType switch + { + FunicularSwitch.Test.Deriving deriving1 => deriving(deriving1), + _ => throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}") + }; + + public static global::System.Threading.Tasks.Task Match(this global::FunicularSwitch.Test.BaseType baseType, global::System.Func> deriving) => + baseType switch + { + FunicularSwitch.Test.Deriving deriving1 => deriving(deriving1), + _ => throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}") + }; + + public static async global::System.Threading.Tasks.Task Match(this global::System.Threading.Tasks.Task> baseType, global::System.Func deriving) => + (await baseType.ConfigureAwait(false)).Match(deriving); + + public static async global::System.Threading.Tasks.Task Match(this global::System.Threading.Tasks.Task> baseType, global::System.Func> deriving) => + await (await baseType.ConfigureAwait(false)).Match(deriving).ConfigureAwait(false); + + public static void Switch(this global::FunicularSwitch.Test.BaseType baseType, global::System.Action deriving) + { + switch (baseType) + { + case FunicularSwitch.Test.Deriving deriving1: + deriving(deriving1); + break; + default: + throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}"); + } + } + + public static async global::System.Threading.Tasks.Task Switch(this global::FunicularSwitch.Test.BaseType baseType, global::System.Func deriving) + { + switch (baseType) + { + case FunicularSwitch.Test.Deriving deriving1: + await deriving(deriving1).ConfigureAwait(false); + break; + default: + throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}"); + } + } + + public static async global::System.Threading.Tasks.Task Switch(this global::System.Threading.Tasks.Task> baseType, global::System.Action deriving) => + (await baseType.ConfigureAwait(false)).Switch(deriving); + + public static async global::System.Threading.Tasks.Task Switch(this global::System.Threading.Tasks.Task> baseType, global::System.Func deriving) => + await (await baseType.ConfigureAwait(false)).Switch(deriving).ConfigureAwait(false); + } +} +#pragma warning restore 1591 diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_have_different_generic_names#Attributes.g.00.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_have_different_generic_names#Attributes.g.00.verified.cs new file mode 100644 index 00000000..3336ef53 --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_have_different_generic_names#Attributes.g.00.verified.cs @@ -0,0 +1,41 @@ +//HintName: Attributes.g.cs +#nullable enable +using System; + +// ReSharper disable once CheckNamespace +namespace FunicularSwitch.Generators +{ + /// + /// Mark an abstract partial type with a single generic argument with the ResultType attribute. + /// This type from now on has Ok | Error semantics with map and bind operations. + /// + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + sealed class ResultTypeAttribute : Attribute + { + public ResultTypeAttribute() => ErrorType = typeof(string); + public ResultTypeAttribute(Type errorType) => ErrorType = errorType; + + public Type ErrorType { get; set; } + } + + /// + /// Mark a static method or a member method or you error type with the MergeErrorAttribute attribute. + /// Static signature: TError -> TError -> TError. Member signature: TError -> TError + /// We are now able to collect errors and methods like Validate, Aggregate, FirstOk that are useful to combine results are generated. + /// + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + sealed class MergeErrorAttribute : Attribute + { + } + + /// + /// Mark a static method with the ExceptionToError attribute. + /// Signature: Exception -> TError + /// This method is always called, when an exception happens in a bind operation. + /// So a call like result.Map(i => i/0) will return an Error produced by the factory method instead of throwing the DivisionByZero exception. + /// + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + sealed class ExceptionToError : Attribute + { + } +} \ No newline at end of file diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_have_different_generic_names#Attributes.g.01.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_have_different_generic_names#Attributes.g.01.verified.cs new file mode 100644 index 00000000..3d6aae40 --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_have_different_generic_names#Attributes.g.01.verified.cs @@ -0,0 +1,29 @@ +//HintName: Attributes.g.cs +#nullable enable +using System; + +// ReSharper disable once CheckNamespace +namespace FunicularSwitch.Generators +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false)] + sealed class UnionTypeAttribute : Attribute + { + public CaseOrder CaseOrder { get; set; } = CaseOrder.Alphabetic; + public bool StaticFactoryMethods { get; set; } = true; + } + + enum CaseOrder + { + Alphabetic, + AsDeclared, + Explicit + } + + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + sealed class UnionCaseAttribute : Attribute + { + public UnionCaseAttribute(int index) => Index = index; + + public int Index { get; } + } +} \ No newline at end of file diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_have_different_generic_names#Attributes.g.02.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_have_different_generic_names#Attributes.g.02.verified.cs new file mode 100644 index 00000000..ad0ffe0d --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_have_different_generic_names#Attributes.g.02.verified.cs @@ -0,0 +1,62 @@ +//HintName: Attributes.g.cs +#nullable enable +using System; + +// ReSharper disable once CheckNamespace +namespace FunicularSwitch.Generators +{ + [AttributeUsage(AttributeTargets.Enum)] + sealed class ExtendedEnumAttribute : Attribute + { + public EnumCaseOrder CaseOrder { get; set; } = EnumCaseOrder.AsDeclared; + public ExtensionAccessibility Accessibility { get; set; } = ExtensionAccessibility.Public; + } + + enum EnumCaseOrder + { + Alphabetic, + AsDeclared + } + + /// + /// Generate match methods for all enums defined in assembly that contains AssemblySpecifier. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + class ExtendEnumsAttribute : Attribute + { + public Type AssemblySpecifier { get; } + public EnumCaseOrder CaseOrder { get; set; } = EnumCaseOrder.AsDeclared; + public ExtensionAccessibility Accessibility { get; set; } = ExtensionAccessibility.Public; + + public ExtendEnumsAttribute() => AssemblySpecifier = typeof(ExtendEnumsAttribute); + + public ExtendEnumsAttribute(Type assemblySpecifier) + { + AssemblySpecifier = assemblySpecifier; + } + } + + /// + /// Generate match methods for Type. Must be enum. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + class ExtendEnumAttribute : Attribute + { + public Type Type { get; } + + public EnumCaseOrder CaseOrder { get; set; } = EnumCaseOrder.AsDeclared; + + public ExtensionAccessibility Accessibility { get; set; } = ExtensionAccessibility.Public; + + public ExtendEnumAttribute(Type type) + { + Type = type; + } + } + + enum ExtensionAccessibility + { + Internal, + Public + } +} \ No newline at end of file diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_have_different_generic_names#FunicularSwitchTestBaseTypeOfTMatchExtension.g.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_have_different_generic_names#FunicularSwitchTestBaseTypeOfTMatchExtension.g.verified.cs new file mode 100644 index 00000000..2d654a6f --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_have_different_generic_names#FunicularSwitchTestBaseTypeOfTMatchExtension.g.verified.cs @@ -0,0 +1,59 @@ +//HintName: FunicularSwitchTestBaseTypeOfTMatchExtension.g.cs +#pragma warning disable 1591 +#nullable enable +namespace FunicularSwitch.Test +{ + public static partial class BaseTypeMatchExtension + { + public static TMatchResult Match(this global::FunicularSwitch.Test.BaseType baseType, global::System.Func, TMatchResult> deriving) => + baseType switch + { + FunicularSwitch.Test.Deriving deriving1 => deriving(deriving1), + _ => throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}") + }; + + public static global::System.Threading.Tasks.Task Match(this global::FunicularSwitch.Test.BaseType baseType, global::System.Func, global::System.Threading.Tasks.Task> deriving) => + baseType switch + { + FunicularSwitch.Test.Deriving deriving1 => deriving(deriving1), + _ => throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}") + }; + + public static async global::System.Threading.Tasks.Task Match(this global::System.Threading.Tasks.Task> baseType, global::System.Func, TMatchResult> deriving) => + (await baseType.ConfigureAwait(false)).Match(deriving); + + public static async global::System.Threading.Tasks.Task Match(this global::System.Threading.Tasks.Task> baseType, global::System.Func, global::System.Threading.Tasks.Task> deriving) => + await (await baseType.ConfigureAwait(false)).Match(deriving).ConfigureAwait(false); + + public static void Switch(this global::FunicularSwitch.Test.BaseType baseType, global::System.Action> deriving) + { + switch (baseType) + { + case FunicularSwitch.Test.Deriving deriving1: + deriving(deriving1); + break; + default: + throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}"); + } + } + + public static async global::System.Threading.Tasks.Task Switch(this global::FunicularSwitch.Test.BaseType baseType, global::System.Func, global::System.Threading.Tasks.Task> deriving) + { + switch (baseType) + { + case FunicularSwitch.Test.Deriving deriving1: + await deriving(deriving1).ConfigureAwait(false); + break; + default: + throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}"); + } + } + + public static async global::System.Threading.Tasks.Task Switch(this global::System.Threading.Tasks.Task> baseType, global::System.Action> deriving) => + (await baseType.ConfigureAwait(false)).Switch(deriving); + + public static async global::System.Threading.Tasks.Task Switch(this global::System.Threading.Tasks.Task> baseType, global::System.Func, global::System.Threading.Tasks.Task> deriving) => + await (await baseType.ConfigureAwait(false)).Switch(deriving).ConfigureAwait(false); + } +} +#pragma warning restore 1591 diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_type_constraints_and_derived_definitions_that_are_not_nested#Attributes.g.00.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_type_constraints_and_derived_definitions_that_are_not_nested#Attributes.g.00.verified.cs new file mode 100644 index 00000000..3336ef53 --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_type_constraints_and_derived_definitions_that_are_not_nested#Attributes.g.00.verified.cs @@ -0,0 +1,41 @@ +//HintName: Attributes.g.cs +#nullable enable +using System; + +// ReSharper disable once CheckNamespace +namespace FunicularSwitch.Generators +{ + /// + /// Mark an abstract partial type with a single generic argument with the ResultType attribute. + /// This type from now on has Ok | Error semantics with map and bind operations. + /// + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + sealed class ResultTypeAttribute : Attribute + { + public ResultTypeAttribute() => ErrorType = typeof(string); + public ResultTypeAttribute(Type errorType) => ErrorType = errorType; + + public Type ErrorType { get; set; } + } + + /// + /// Mark a static method or a member method or you error type with the MergeErrorAttribute attribute. + /// Static signature: TError -> TError -> TError. Member signature: TError -> TError + /// We are now able to collect errors and methods like Validate, Aggregate, FirstOk that are useful to combine results are generated. + /// + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + sealed class MergeErrorAttribute : Attribute + { + } + + /// + /// Mark a static method with the ExceptionToError attribute. + /// Signature: Exception -> TError + /// This method is always called, when an exception happens in a bind operation. + /// So a call like result.Map(i => i/0) will return an Error produced by the factory method instead of throwing the DivisionByZero exception. + /// + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + sealed class ExceptionToError : Attribute + { + } +} \ No newline at end of file diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_type_constraints_and_derived_definitions_that_are_not_nested#Attributes.g.01.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_type_constraints_and_derived_definitions_that_are_not_nested#Attributes.g.01.verified.cs new file mode 100644 index 00000000..3d6aae40 --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_type_constraints_and_derived_definitions_that_are_not_nested#Attributes.g.01.verified.cs @@ -0,0 +1,29 @@ +//HintName: Attributes.g.cs +#nullable enable +using System; + +// ReSharper disable once CheckNamespace +namespace FunicularSwitch.Generators +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false)] + sealed class UnionTypeAttribute : Attribute + { + public CaseOrder CaseOrder { get; set; } = CaseOrder.Alphabetic; + public bool StaticFactoryMethods { get; set; } = true; + } + + enum CaseOrder + { + Alphabetic, + AsDeclared, + Explicit + } + + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + sealed class UnionCaseAttribute : Attribute + { + public UnionCaseAttribute(int index) => Index = index; + + public int Index { get; } + } +} \ No newline at end of file diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_type_constraints_and_derived_definitions_that_are_not_nested#Attributes.g.02.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_type_constraints_and_derived_definitions_that_are_not_nested#Attributes.g.02.verified.cs new file mode 100644 index 00000000..ad0ffe0d --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_type_constraints_and_derived_definitions_that_are_not_nested#Attributes.g.02.verified.cs @@ -0,0 +1,62 @@ +//HintName: Attributes.g.cs +#nullable enable +using System; + +// ReSharper disable once CheckNamespace +namespace FunicularSwitch.Generators +{ + [AttributeUsage(AttributeTargets.Enum)] + sealed class ExtendedEnumAttribute : Attribute + { + public EnumCaseOrder CaseOrder { get; set; } = EnumCaseOrder.AsDeclared; + public ExtensionAccessibility Accessibility { get; set; } = ExtensionAccessibility.Public; + } + + enum EnumCaseOrder + { + Alphabetic, + AsDeclared + } + + /// + /// Generate match methods for all enums defined in assembly that contains AssemblySpecifier. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + class ExtendEnumsAttribute : Attribute + { + public Type AssemblySpecifier { get; } + public EnumCaseOrder CaseOrder { get; set; } = EnumCaseOrder.AsDeclared; + public ExtensionAccessibility Accessibility { get; set; } = ExtensionAccessibility.Public; + + public ExtendEnumsAttribute() => AssemblySpecifier = typeof(ExtendEnumsAttribute); + + public ExtendEnumsAttribute(Type assemblySpecifier) + { + AssemblySpecifier = assemblySpecifier; + } + } + + /// + /// Generate match methods for Type. Must be enum. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + class ExtendEnumAttribute : Attribute + { + public Type Type { get; } + + public EnumCaseOrder CaseOrder { get; set; } = EnumCaseOrder.AsDeclared; + + public ExtensionAccessibility Accessibility { get; set; } = ExtensionAccessibility.Public; + + public ExtendEnumAttribute(Type type) + { + Type = type; + } + } + + enum ExtensionAccessibility + { + Internal, + Public + } +} \ No newline at end of file diff --git a/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_type_constraints_and_derived_definitions_that_are_not_nested#FunicularSwitchTestBaseTypeOfT1_T2MatchExtension.g.verified.cs b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_type_constraints_and_derived_definitions_that_are_not_nested#FunicularSwitchTestBaseTypeOfT1_T2MatchExtension.g.verified.cs new file mode 100644 index 00000000..edd80573 --- /dev/null +++ b/Source/Tests/FunicularSwitch.Generators.Test/Snapshots/Run_union_type_generator.For_union_type_with_generic_base_class_and_type_constraints_and_derived_definitions_that_are_not_nested#FunicularSwitchTestBaseTypeOfT1_T2MatchExtension.g.verified.cs @@ -0,0 +1,84 @@ +//HintName: FunicularSwitchTestBaseTypeOfT1_T2MatchExtension.g.cs +#pragma warning disable 1591 +#nullable enable +namespace FunicularSwitch.Test +{ + public static partial class BaseTypeMatchExtension + { + public static T Match(this global::FunicularSwitch.Test.BaseType baseType, global::System.Func, T> deriving, global::System.Func, T> deriving2) where T1 : notnull, new() + where T2 : class, global::System.Collections.Generic.IEnumerable => + baseType switch + { + FunicularSwitch.Test.Deriving deriving1 => deriving(deriving1), + FunicularSwitch.Test.Deriving2 deriving22 => deriving2(deriving22), + _ => throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}") + }; + + public static global::System.Threading.Tasks.Task Match(this global::FunicularSwitch.Test.BaseType baseType, global::System.Func, global::System.Threading.Tasks.Task> deriving, global::System.Func, global::System.Threading.Tasks.Task> deriving2) where T1 : notnull, new() + where T2 : class, global::System.Collections.Generic.IEnumerable => + baseType switch + { + FunicularSwitch.Test.Deriving deriving1 => deriving(deriving1), + FunicularSwitch.Test.Deriving2 deriving22 => deriving2(deriving22), + _ => throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}") + }; + + public static async global::System.Threading.Tasks.Task Match(this global::System.Threading.Tasks.Task> baseType, global::System.Func, T> deriving, global::System.Func, T> deriving2) where T1 : notnull, new() + where T2 : class, global::System.Collections.Generic.IEnumerable => + (await baseType.ConfigureAwait(false)).Match(deriving, deriving2); + + public static async global::System.Threading.Tasks.Task Match(this global::System.Threading.Tasks.Task> baseType, global::System.Func, global::System.Threading.Tasks.Task> deriving, global::System.Func, global::System.Threading.Tasks.Task> deriving2) where T1 : notnull, new() + where T2 : class, global::System.Collections.Generic.IEnumerable => + await (await baseType.ConfigureAwait(false)).Match(deriving, deriving2).ConfigureAwait(false); + + public static void Switch(this global::FunicularSwitch.Test.BaseType baseType, global::System.Action> deriving, global::System.Action> deriving2) where T1 : notnull, new() + where T2 : class, global::System.Collections.Generic.IEnumerable + { + switch (baseType) + { + case FunicularSwitch.Test.Deriving deriving1: + deriving(deriving1); + break; + case FunicularSwitch.Test.Deriving2 deriving22: + deriving2(deriving22); + break; + default: + throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}"); + } + } + + public static async global::System.Threading.Tasks.Task Switch(this global::FunicularSwitch.Test.BaseType baseType, global::System.Func, global::System.Threading.Tasks.Task> deriving, global::System.Func, global::System.Threading.Tasks.Task> deriving2) where T1 : notnull, new() + where T2 : class, global::System.Collections.Generic.IEnumerable + { + switch (baseType) + { + case FunicularSwitch.Test.Deriving deriving1: + await deriving(deriving1).ConfigureAwait(false); + break; + case FunicularSwitch.Test.Deriving2 deriving22: + await deriving2(deriving22).ConfigureAwait(false); + break; + default: + throw new global::System.ArgumentException($"Unknown type derived from FunicularSwitch.Test.BaseType: {baseType.GetType().Name}"); + } + } + + public static async global::System.Threading.Tasks.Task Switch(this global::System.Threading.Tasks.Task> baseType, global::System.Action> deriving, global::System.Action> deriving2) where T1 : notnull, new() + where T2 : class, global::System.Collections.Generic.IEnumerable => + (await baseType.ConfigureAwait(false)).Switch(deriving, deriving2); + + public static async global::System.Threading.Tasks.Task Switch(this global::System.Threading.Tasks.Task> baseType, global::System.Func, global::System.Threading.Tasks.Task> deriving, global::System.Func, global::System.Threading.Tasks.Task> deriving2) where T1 : notnull, new() + where T2 : class, global::System.Collections.Generic.IEnumerable => + await (await baseType.ConfigureAwait(false)).Switch(deriving, deriving2).ConfigureAwait(false); + } + + public abstract partial record BaseType + where T1 : notnull, new() + + where T2 : class, global::System.Collections.Generic.IEnumerable + { + public static FunicularSwitch.Test.BaseType Deriving(string Value, T1 Other, T2 List) => new FunicularSwitch.Test.Deriving(Value, Other, List); + public static FunicularSwitch.Test.BaseType Deriving2(string Value) => new FunicularSwitch.Test.Deriving2(Value); + } +} +#pragma warning restore 1591 diff --git a/Source/Tests/FunicularSwitch.Generators.Test/UnionTypeGeneratorSpecs.cs b/Source/Tests/FunicularSwitch.Generators.Test/UnionTypeGeneratorSpecs.cs index 54b957d6..ac82a2ec 100644 --- a/Source/Tests/FunicularSwitch.Generators.Test/UnionTypeGeneratorSpecs.cs +++ b/Source/Tests/FunicularSwitch.Generators.Test/UnionTypeGeneratorSpecs.cs @@ -192,7 +192,7 @@ record Two : Base; } "; - return Verify(code); + return Verify(code, numberOfGeneratedFiles: 0); } [TestMethod] @@ -334,7 +334,7 @@ class Consumer { } "; - return Verify(code); + return Verify(code, numberOfGeneratedFiles: 2); } [TestMethod] @@ -481,7 +481,7 @@ public abstract partial record NodeMessage(string NodeInstanceId) public string Node { get; } = NodeInstanceId.Substring(0, NodeInstanceId.IndexOf(':')); }"; - return Verify(code); + return Verify(code, numberOfGeneratedFiles: 0); } [TestMethod] @@ -504,6 +504,65 @@ public sealed record Deriving2_(string Value) : BaseType(Value); return Verify(code); } + [TestMethod] + public Task For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested() + { + var code = """ + using FunicularSwitch.Generators; + + namespace FunicularSwitch.Test; + + [UnionType(CaseOrder = CaseOrder.AsDeclared)] + public abstract partial record BaseType(string Value) + { + } + + public sealed record Deriving(string Value, T Other) : BaseType(Value); + + public sealed record Deriving2(string Value) : BaseType(Value); + """; + + return Verify(code); + } + + [TestMethod] + public Task For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_have_different_generic_names() + { + var code = """ + using FunicularSwitch.Generators; + + namespace FunicularSwitch.Test; + + [UnionType(CaseOrder = CaseOrder.AsDeclared)] + public abstract partial record BaseType(string Value) + { + } + + public sealed record Deriving(string Value, TCustom Other) : BaseType(Value); + """; + + return Verify(code); + } + + [TestMethod] + public Task For_union_type_with_generic_base_class_and_derived_definitions_that_are_not_nested_and_are_not_generic() + { + var code = """ + using FunicularSwitch.Generators; + + namespace FunicularSwitch.Test; + + [UnionType(CaseOrder = CaseOrder.AsDeclared)] + public abstract partial record BaseType(string Value) + { + } + + public sealed record Deriving(string Value) : BaseType(Value); + """; + + return Verify(code); + } + [TestMethod] public Task For_union_type_with_generic_base_class_and_type_constraints() { @@ -527,6 +586,34 @@ public sealed record Deriving2_(string Value) : BaseType(Value); return Verify(code); } + [TestMethod] + public Task For_union_type_with_generic_base_class_and_type_constraints_and_derived_definitions_that_are_not_nested() + { + var code = """ + using System.Collections.Generic; + using FunicularSwitch.Generators; + + namespace FunicularSwitch.Test; + + [UnionType(CaseOrder = CaseOrder.AsDeclared)] + public abstract partial record BaseType(string Value) + where T1 : notnull, new() + where T2 : class, IEnumerable + { + } + + public sealed record Deriving(string Value, T1 Other, T2 List) : BaseType(Value) + where T1 : notnull, new() + where T2 : class, IEnumerable; + + public sealed record Deriving2(string Value) : BaseType(Value) + where T1 : notnull, new() + where T2 : class, IEnumerable; + """; + + return Verify(code); + } + [TestMethod] public Task ForUnionType_WithNullableParameters_StaticFactoriesHaveNullableParametersToo() { diff --git a/Source/Tests/FunicularSwitch.Generators.Test/VerifySourceGenerator.cs b/Source/Tests/FunicularSwitch.Generators.Test/VerifySourceGenerator.cs index 8a5133b0..001f7fc2 100644 --- a/Source/Tests/FunicularSwitch.Generators.Test/VerifySourceGenerator.cs +++ b/Source/Tests/FunicularSwitch.Generators.Test/VerifySourceGenerator.cs @@ -9,7 +9,7 @@ namespace FunicularSwitch.Generators.Test; public abstract class VerifySourceGenerator : VerifyBase { - protected Task Verify(string source, IReadOnlyList additionalAssemblies, Action>? verifyCompilation) + protected Task Verify(string source, IReadOnlyList additionalAssemblies, Action>? verifyCompilation) { var syntaxTree = CSharpSyntaxTree.ParseText(source); @@ -40,15 +40,17 @@ protected Task Verify(string source, IReadOnlyList additionalAssemblie driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var updatedCompilation, out var diagnostics); - verifyCompilation?.Invoke(updatedCompilation, diagnostics); + verifyCompilation?.Invoke(driver, updatedCompilation, diagnostics); return Verify(driver) .UseDirectory("Snapshots"); } - protected Task Verify(string source, bool referencePolyType = false) => - Verify(source, additionalAssemblies: referencePolyType ? [typeof(DerivedTypeShapeAttribute).Assembly] : [], (compilation, _) => + protected Task Verify(string source, int numberOfGeneratedFiles = 1, bool referencePolyType = false) => + Verify(source, additionalAssemblies: referencePolyType ? [typeof(DerivedTypeShapeAttribute).Assembly] : [], (driver, compilation, _) => { + const int numberOfAttributeFiles = 3; + driver.GetRunResult().GeneratedTrees.Should().HaveCount(numberOfAttributeFiles + numberOfGeneratedFiles); var diagnostics = compilation.GetDiagnostics(); var errors = string.Join(Environment.NewLine, diagnostics .Where(d => d.Severity == DiagnosticSeverity.Error));