Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/Godot.Bindings/Core/Attributes/BindSharedEnumAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;

namespace Godot;

/// <summary>
/// Registers the constants of the specified enum type within the annotated extension class.
/// This allows a namespace-level enum to be shared across multiple extension classes.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
public sealed class BindSharedEnumAttribute : Attribute
{
/// <summary>
/// Constructs a new <see cref="BindSharedEnumAttribute"/> with the specified enum type.
/// </summary>
/// <param name="enumType">The enum type whose constants will be registered for this class.</param>
public BindSharedEnumAttribute(Type enumType)
{
EnumType = enumType;
}

/// <summary>
/// The enum type whose constants are registered for this class.
/// </summary>
public Type EnumType { get; }

/// <summary>
/// Specifies the name that will be used to register the enum.
/// If unspecified it will use the name of the enum type.
/// </summary>
public string? Name { get; init; }
}
1 change: 1 addition & 0 deletions src/Godot.Common.CodeAnalysis/KnownTypeNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ internal static class KnownTypeNames
public const string PropertySubgroupAttribute = "Godot.PropertySubgroupAttribute";
public const string RpcAttribute = "Godot.RpcAttribute";
public const string SignalAttribute = "Godot.SignalAttribute";
public const string BindSharedEnumAttribute = "Godot.BindSharedEnumAttribute";
public const string MustBeVariantAttribute = "Godot.MustBeVariantAttribute";
public const string DisableGodotEntryPointGenerationAttribute = "Godot.DisableGodotEntryPointGenerationAttribute";
}
34 changes: 34 additions & 0 deletions src/Godot.SourceGeneration/SpecCollectors/ClassSpecCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,40 @@ internal static class ClassSpecCollector
}
}

// Collect shared enum constants from [BindSharedEnum] attributes.
foreach (var attributeData in typeSymbol.GetAttributes())
{
if (attributeData.AttributeClass?.FullQualifiedNameOmitGlobal() != KnownTypeNames.BindSharedEnumAttribute)
{
continue;
}

if (attributeData.ConstructorArguments.Length < 1)
{
continue;
}

if (attributeData.ConstructorArguments[0].Value is not INamedTypeSymbol enumTypeSymbol)
{
continue;
}

string? enumNameOverride = null;
foreach (var (key, constant) in attributeData.NamedArguments)
{
if (key == "Name")
{
enumNameOverride = constant.Value as string;
}
}

var sharedConstantSpecs = ConstantSpecCollector.CollectShared(compilation, enumTypeSymbol, enumNameOverride, cancellationToken);
foreach (var constantSpec in sharedConstantSpecs)
{
constants.Add(constantSpec);
}
}

// Collect property specs.
foreach (var symbol in members)
{
Expand Down
19 changes: 19 additions & 0 deletions src/Godot.SourceGeneration/SpecCollectors/ConstantSpecCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,25 @@ public static IEnumerable<GodotConstantSpec> Collect(Compilation compilation, IT
}
}

public static IEnumerable<GodotConstantSpec> CollectShared(Compilation compilation, ITypeSymbol enumTypeSymbol, string? enumNameOverride = null, CancellationToken cancellationToken = default)
{
if (enumTypeSymbol.TypeKind != TypeKind.Enum)
{
yield break;
}

string? effectiveEnumName = enumNameOverride ?? enumTypeSymbol.Name;

bool isFlagsEnum = enumTypeSymbol.HasAttribute(KnownTypeNames.SystemFlagsAttribute);

foreach (var fieldSymbol in enumTypeSymbol.GetMembers().OfType<IFieldSymbol>())
{
fieldSymbol.TryGetAttribute(KnownTypeNames.BindConstantAttribute, out var constantAttribute);

yield return CollectCore(compilation, fieldSymbol.Name, enumTypeSymbol.Name, effectiveEnumName, isFlagsEnum, constantAttribute, cancellationToken);
}
}

private static GodotConstantSpec CollectCore(Compilation compilation, string symbolName, string? enumSymbolName, string? enumNameOverride, bool isFlagsEnum, AttributeData? attribute, CancellationToken cancellationToken = default)
{
string? nameOverride = null;
Expand Down