Skip to content

Commit

Permalink
feat: apply incremental source generator best practises
Browse files Browse the repository at this point in the history
* the attributes are now always in the Tuxedo namespace, but performance is improved 99x
  • Loading branch information
bmazzarol committed Jan 1, 2025
1 parent 7489cb3 commit 96be562
Show file tree
Hide file tree
Showing 24 changed files with 238 additions and 192 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ public override void Initialize(AnalysisContext context)

context.RegisterCompilationStartAction(compilationContext =>
{
var typeSymbol = compilationContext.Compilation.GetTypeByMetadataName(
$"{compilationContext.Compilation.Assembly.Name}.RefinedTypeAttribute"
);
if (typeSymbol == null)
if (!compilationContext.Compilation.HasRefinedTypeAttribute())
{
return;
}
Expand All @@ -54,7 +51,9 @@ public override void Initialize(AnalysisContext context)

private static void Analyze(SyntaxNodeAnalysisContext ctx)
{
var typeInfo = ctx.SemanticModel.GetTypeInfo(ctx.Node).Type;
var typeInfo = ctx
.SemanticModel.GetTypeInfo(ctx.Node, cancellationToken: ctx.CancellationToken)
.Type;
if (typeInfo is not INamedTypeSymbol symbol || !symbol.IsRefinedType())
{
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ public override void Initialize(AnalysisContext context)

context.RegisterCompilationStartAction(compilationContext =>
{
var typeSymbol = compilationContext.Compilation.GetTypeByMetadataName(
$"{compilationContext.Compilation.Assembly.Name}.RefinedTypeAttribute"
);
if (typeSymbol == null)
if (!compilationContext.Compilation.HasRefinedTypeAttribute())
{
return;
}
Expand Down
19 changes: 19 additions & 0 deletions Tuxedo.SourceGenerator/Compiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 || NETCOREAPP3_1 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48

using System.ComponentModel;

// ReSharper disable once CheckNamespace
namespace System.Runtime.CompilerServices;

/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class IsExternalInit { }

#endif
12 changes: 12 additions & 0 deletions Tuxedo.SourceGenerator/Extensions/CompilationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.CodeAnalysis;

namespace Tuxedo.SourceGenerator.Extensions;

internal static class CompilationExtensions
{
public static bool HasRefinedTypeAttribute(this Compilation compilation)
{
var typeSymbol = compilation.GetTypeByMetadataName("Tuxedo.RefinedTypeAttribute");
return typeSymbol != null;
}
}
2 changes: 1 addition & 1 deletion Tuxedo.SourceGenerator/Extensions/SymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public static bool IsRefinedType(this INamedTypeSymbol symbol)
return attrs.Any(a =>
a
.AttributeClass?.ToDisplayString()
.EndsWith(".RefinedTypeAttribute", StringComparison.Ordinal)
.Equals("Tuxedo.RefinedTypeAttribute", StringComparison.Ordinal)
is true
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,63 @@ namespace Tuxedo.SourceGenerator;

public sealed partial class RefinementSourceGenerator
{
private static string RenderRefinementAttribute(string? @namespace)
{
return $$"""
// <auto-generated/>
#nullable enable
private const string RefinementAttributeSource = """
#if !TUXEDO_EXCLUDE_ATTRIBUTES
namespace {{@namespace}};
// <auto-generated/>
#nullable enable
namespace Tuxedo;
/// <summary>
/// Marks a method as a refinement to a type
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
internal sealed class RefinementAttribute : Attribute
{
/// <summary>
/// The message to display when the refinement fails.
/// The `value` parameter is available for string interpolation.
/// </summary>
public string FailureMessage { get; }
/// <summary>
/// Indicates whether the refined type is internal, default is public
/// </summary>
public bool IsInternal { get; set; }
/// <summary>
/// Marks a method as a refinement to a type
/// Optional name of the refined type.
/// Defaults to the refinement method name + the raw type name.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
internal sealed class RefinementAttribute : Attribute
public string? Name { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="RefinementAttribute"/> class.
/// </summary>
public RefinementAttribute(string failureMessage)
{
/// <summary>
/// The message to display when the refinement fails.
/// The `value` parameter is available for string interpolation.
/// </summary>
public string FailureMessage { get; }
FailureMessage = failureMessage;
}
}
#endif
""";

/// <summary>
/// Indicates whether the refined type is internal, default is public
/// </summary>
public bool IsInternal { get; set; }
/// <summary>
/// Optional name of the refined type.
/// Defaults to the refinement method name + the raw type name.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="RefinementAttribute"/> class.
/// </summary>
public RefinementAttribute(string failureMessage)
{
FailureMessage = failureMessage;
}
}
""";
}
private const string RefinedTypeAttributeSource = """
#if !TUXEDO_EXCLUDE_ATTRIBUTES
private static string RenderRefinedTypeAttribute(string? @namespace)
{
return $$"""
// <auto-generated/>
#nullable enable
// <auto-generated/>
#nullable enable
namespace {{@namespace}};
namespace Tuxedo;
/// <summary>
/// Marks a struct as a refined type
/// </summary>
[AttributeUsage(AttributeTargets.Struct)]
internal sealed class RefinedTypeAttribute : Attribute
{
}
""";
}
/// <summary>
/// Marks a struct as a refined type
/// </summary>
[AttributeUsage(AttributeTargets.Struct)]
internal sealed class RefinedTypeAttribute : Attribute
{
}
#endif
""";
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Tuxedo.SourceGenerator;

public sealed partial class RefinementSourceGenerator
{
private static string RenderMultiRefinedType(ViewModel model)
private static string RenderMultiRefinedType(RefinedTypeDetails model)
{
return $$"""
{{RenderNamespaceParts(model)}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Tuxedo.SourceGenerator.Extensions;

namespace Tuxedo.SourceGenerator;

public sealed partial class RefinementSourceGenerator
{
private readonly record struct RefinedTypeDetails(
string? Namespace,
string? Predicate,
string? FailureMessage,
string? AccessModifier,
string? Generics,
string? GenericConstraints,
string? RawType,
string? RefinedType,
string? AlternativeType
)
{
public string? RefinedTypeXmlSafeName => (RefinedType + Generics).EscapeXml();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public RefinementAttributeParts(MethodDeclarationSyntax methodDeclaration)
.AttributeLists.SelectMany(list => list.Attributes)
.Single(attribute =>
string.Equals(attribute.Name.ToString(), "Refinement", StringComparison.Ordinal)
|| string.Equals(
attribute.Name.ToString(),
"Tuxedo.Refinement",
StringComparison.Ordinal
)
)
.ArgumentList!.Arguments;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Tuxedo.SourceGenerator;

public sealed partial class RefinementSourceGenerator
{
private static string RenderSingleRefinedType(ViewModel model)
private static string RenderSingleRefinedType(RefinedTypeDetails model)
{
return $$"""
{{RenderNamespaceParts(model)}}
Expand All @@ -31,7 +31,7 @@ private static string RenderSingleRefinedType(ViewModel model)
""";
}

private static string RenderNamespaceParts(ViewModel model)
private static string RenderNamespaceParts(RefinedTypeDetails model)
{
return $"""
// <auto-generated/>
Expand All @@ -44,15 +44,15 @@ namespace {model.Namespace};
""";
}

private static string RenderTypeNameParts(ViewModel model)
private static string RenderTypeNameParts(RefinedTypeDetails model)
{
return $"""
/// <summary>
/// A refined {model.RawType.EscapeXml()} based on the {model.Predicate.EscapeXml()} refinement predicate{model.AlternativeType.RenderIfNotNull(
x => $" which produces an alternative {x.EscapeXml()} value"
)}
/// </summary>
[RefinedType]
[Tuxedo.RefinedType]
{model.AccessModifier} readonly partial struct {model.RefinedType}{model.Generics} : IEquatable<{model.RefinedType}{model.Generics}>{model.GenericConstraints.PrependIfNotNull(
"\n\t"
)}
Expand All @@ -62,7 +62,7 @@ private static string RenderTypeNameParts(ViewModel model)
private static string RenderTypePropertyParts(
string name,
string? typeName,
ViewModel model,
RefinedTypeDetails model,
bool addImplicitOperator
)
{
Expand Down Expand Up @@ -95,7 +95,7 @@ bool addImplicitOperator
return result;
}

private static string RenderParseMethod(ViewModel model)
private static string RenderParseMethod(RefinedTypeDetails model)
{
return $$"""
/// <summary>
Expand All @@ -122,7 +122,7 @@ private static string RenderParseMethod(ViewModel model)
""";
}

private static string RenderTryParseMethod(ViewModel model)
private static string RenderTryParseMethod(RefinedTypeDetails model)
{
return $$"""
/// <summary>
Expand Down Expand Up @@ -154,7 +154,7 @@ public static bool TryParse(
""";
}

private static string RenderEqualityMembers(ViewModel model)
private static string RenderEqualityMembers(RefinedTypeDetails model)
{
var hasAltValue = model.AlternativeType is not null;
var equals = hasAltValue
Expand Down

This file was deleted.

Loading

0 comments on commit 96be562

Please sign in to comment.