Skip to content

Commit

Permalink
- no constructor analysis if static factories are not generated
Browse files Browse the repository at this point in the history
- fix case where parameters of derived type come from different syntax tree
- valid code generated for uniontype without cases
  • Loading branch information
ax0l0tl committed Nov 29, 2023
1 parent dd3e261 commit ffe50bf
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 29 deletions.
8 changes: 4 additions & 4 deletions Source/FunicularSwitch.Generators.Common/RoslynExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,19 +166,19 @@ public static IEnumerable<INamedTypeSymbol> GetAllTypes(this INamespaceOrTypeSym
}
}

public static MemberInfo ToMemberInfo(this BaseMethodDeclarationSyntax member, string name, SemanticModel semanticModel) =>
public static MemberInfo ToMemberInfo(this BaseMethodDeclarationSyntax member, string name, Compilation compilation) =>
new(name,
member.Modifiers,
member.ParameterList.Parameters
.Select(p =>
ToParameterInfo(p, semanticModel))
ToParameterInfo(p, compilation))
.ToImmutableList());

public static ParameterInfo ToParameterInfo(this ParameterSyntax p, SemanticModel semanticModel) =>
public static ParameterInfo ToParameterInfo(this ParameterSyntax p, Compilation compilation) =>
new(
p.Identifier.Text,
p.Modifiers,
semanticModel.GetTypeInfo(p.Type!).Type!,
compilation.GetSemanticModel(p.SyntaxTree).GetTypeInfo(p.Type!).Type!,
p.Default);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

<!--#region adapt versions here-->
<MajorVersion>3</MajorVersion>
<MinorAndPatchVersion>1.0</MinorAndPatchVersion>
<MinorAndPatchVersion>1.1</MinorAndPatchVersion>
<!--#endregion-->

<AssemblyVersion>$(MajorVersion).0.0</AssemblyVersion>
Expand Down
47 changes: 25 additions & 22 deletions Source/FunicularSwitch.Generators/UnionType/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,36 +41,36 @@ public static IEnumerable<UnionTypeSchema> GetUnionTypes(Compilation compilation
var derivedTypes = compilation.SyntaxTrees.SelectMany(t =>
{
var root = t.GetRoot(cancellationToken);
var treeSemanticModel =
t != unionTypeClass.SyntaxTree ? compilation.GetSemanticModel(t) : semanticModel;
var treeSemanticModel = t != unionTypeClass.SyntaxTree ? compilation.GetSemanticModel(t) : semanticModel;
return FindConcreteDerivedTypesWalker.Get(root, unionTypeSymbol, treeSemanticModel);
});
var isPartial = unionTypeClass.Modifiers.HasModifier(SyntaxKind.PartialKeyword);
var generateFactoryMethods = isPartial && unionTypeClass is not InterfaceDeclarationSyntax &&
staticFactoryMethods;
return new UnionTypeSchema(
Namespace: fullNamespace,
TypeName: unionTypeSymbol.Name,
FullTypeName: fullTypeName,
Cases: ToOrderedCases(caseOrder, derivedTypes, reportDiagnostic, semanticModel)
Cases: ToOrderedCases(caseOrder, derivedTypes, reportDiagnostic, compilation, generateFactoryMethods)
.ToImmutableArray(),
IsInternal: acc is Accessibility.NotApplicable or Accessibility.Internal,
IsPartial: isPartial,
IsRecord: unionTypeClass is RecordDeclarationSyntax,
StaticFactoryInfo: isPartial && unionTypeClass is not InterfaceDeclarationSyntax &&
staticFactoryMethods
? BuildFactoryInfo(unionTypeClass, semanticModel)
StaticFactoryInfo: generateFactoryMethods
? BuildFactoryInfo(unionTypeClass, compilation)
: null
);
})
.Where(unionTypeClass => unionTypeClass != null);
.Where(unionTypeClass => unionTypeClass is { Cases.Count: > 0 });

static StaticFactoryMethodsInfo BuildFactoryInfo(BaseTypeDeclarationSyntax unionTypeClass, SemanticModel semanticModel)
static StaticFactoryMethodsInfo BuildFactoryInfo(BaseTypeDeclarationSyntax unionTypeClass, Compilation compilation)
{
var staticMethods = unionTypeClass.ChildNodes()
.OfType<MethodDeclarationSyntax>()
.Where(m => m.Modifiers.HasModifier(SyntaxKind.StaticKeyword))
.Select(m => m.ToMemberInfo(m.Name(), semanticModel))
.Select(m => m.ToMemberInfo(m.Name(), compilation))
.ToImmutableList();

var staticFields = unionTypeClass.ChildNodes()
Expand Down Expand Up @@ -114,7 +114,7 @@ PropertyDeclarationSyntax p when p.Modifiers.HasModifier(SyntaxKind.StaticKeywor
return (caseOrder, staticFactoryMethods);
}

static IEnumerable<DerivedType> ToOrderedCases(CaseOrder caseOrder, IEnumerable<(INamedTypeSymbol symbol, BaseTypeDeclarationSyntax node, int? caseIndex, int numberOfConctreteBaseTypes)> derivedTypes, Action<Diagnostic> reportDiagnostic, SemanticModel semanticModel)
static IEnumerable<DerivedType> ToOrderedCases(CaseOrder caseOrder, IEnumerable<(INamedTypeSymbol symbol, BaseTypeDeclarationSyntax node, int? caseIndex, int numberOfConctreteBaseTypes)> derivedTypes, Action<Diagnostic> reportDiagnostic, Compilation compilation, bool getConstructors)
{
var ordered = derivedTypes.OrderByDescending(d => d.numberOfConctreteBaseTypes);
ordered = caseOrder switch
Expand Down Expand Up @@ -161,22 +161,25 @@ static IEnumerable<DerivedType> ToOrderedCases(CaseOrder caseOrder, IEnumerable<
{
var qualifiedTypeName = d.node.QualifiedName();
var fullNamespace = d.symbol.GetFullNamespace();
var constructors = d.node.ChildNodes()
.OfType<ConstructorDeclarationSyntax>()
.Select(c => c.ToMemberInfo(c.Identifier.Text, semanticModel));
if (d.node is RecordDeclarationSyntax { ParameterList: not null } recordDeclaration)
constructors = constructors.Concat(new[]
{
new MemberInfo(d.node.Name(), d.node.Modifiers, recordDeclaration.ParameterList.Parameters
.Select(p => p.ToParameterInfo(semanticModel)).ToImmutableList())
});
IEnumerable<MemberInfo>? constructors = null;
if (getConstructors)
{
constructors = d.node.ChildNodes()
.OfType<ConstructorDeclarationSyntax>()
.Select(c => c.ToMemberInfo(c.Identifier.Text, compilation));
if (d.node is RecordDeclarationSyntax { ParameterList: not null } recordDeclaration)
constructors = constructors.Concat(new[]
{
new MemberInfo(d.node.Name(), d.node.Modifiers, recordDeclaration.ParameterList.Parameters
.Select(p => p.ToParameterInfo(compilation)).ToImmutableList())
});
}
return new DerivedType(
fullTypeName: $"{(fullNamespace != null ? $"{fullNamespace}." : "")}{qualifiedTypeName}",
typeName: qualifiedTypeName.Name,
constructors: constructors.ToImmutableList());
constructors: constructors?.ToImmutableList());
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="FunicularSwitch" Version="4.0.0" />
<PackageReference Include="FunicularSwitch.Generators" Version="2.1.0" />
<PackageReference Include="FunicularSwitch" Version="5.0.0" />
<PackageReference Include="FunicularSwitch.Generators" Version="3.1.0" />
<PackageReference Include="FunicularSwitch.Generators.FluentAssertions" Version="1.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//HintName: Attributes.g.cs
using System;

// ReSharper disable once CheckNamespace
namespace FunicularSwitch.Generators
{
/// <summary>
/// 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.
/// </summary>
[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; }
}

/// <summary>
/// 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.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
sealed class MergeErrorAttribute : Attribute
{
}

/// <summary>
/// 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.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
sealed class ExceptionToError : Attribute
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//HintName: Attributes.g.cs
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; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//HintName: Attributes.g.cs
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
}

/// <summary>
/// Generate match methods for all enums defined in assembly that contains AssemblySpecifier.
/// </summary>
[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;
}
}

/// <summary>
/// Generate match methods for Type. Must be enum.
/// </summary>
[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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -323,4 +323,21 @@ public record Two : IBase {}";

return Verify(code);
}

[TestMethod]
public Task For_union_type_without_derived_types()
{
var code = @"
using FunicularSwitch.Generators;
namespace FunicularSwitch.Test;
[UnionType(CaseOrder = CaseOrder.Explicit)]
public abstract partial record NodeMessage(string NodeInstanceId)
{
public string Node { get; } = NodeInstanceId.Substring(0, NodeInstanceId.IndexOf(':'));
}";

return Verify(code);
}
}

0 comments on commit ffe50bf

Please sign in to comment.