Skip to content

Commit

Permalink
[Rgen] Implement the generation of the smart enums. (#21562)
Browse files Browse the repository at this point in the history
Add the code generation for smart enumerators. This means the following
changes:

1. Refactor the way we register the diff pipelines.
2. Add code that generates the Extension class for the enums.
3. Add code that will generate the Library.g.cs partial class.

---------

Co-authored-by: Rolf Bjarne Kvinge <[email protected]>
  • Loading branch information
mandel-macaque and rolfbjarne authored Nov 19, 2024
1 parent fa9294b commit 6bfecec
Show file tree
Hide file tree
Showing 74 changed files with 5,435 additions and 301 deletions.
2 changes: 2 additions & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,8 @@ $($(2)_DOTNET_BUILD_DIR)/$(4)/Microsoft.$(1)%dll $($(2)_DOTNET_BUILD_DIR)/$(4)/M
$(DOTNET_FLAGS) \
/analyzer:$(ROSLYN_GENERATOR_COMMON) \
/analyzer:$(ROSLYN_GENERATOR) \
/generatedfilesout:$($(2)_DOTNET_BUILD_DIR)/generated-sources \
/reportanalyzer:$(ROSLYN_GENERATOR) \
-unsafe \
-optimize \
$$(ARGS_$(1)) \
Expand Down
45 changes: 39 additions & 6 deletions src/ObjCBindings/FieldAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,22 @@ namespace ObjCBindings {
/// </summary>
[Experimental ("APL0003")]
[AttributeUsage (AttributeTargets.Property | AttributeTargets.Field)]
public sealed class FieldAttribute<T> : Attribute where T : FieldTag {
public class FieldAttribute<T> : Attribute where T : Enum {

/// <summary>
/// Extra configuration flags for the field.
/// </summary>
public T? Flags { get; set; } = default;

/// <summary>
/// Get/Set the native symbol represented by the attribute.
/// </summary>
public string SymbolName { get; set; }

/// <summary>
/// Get/Set the library that contains the symbol.
/// </summary>
public string? LibraryName { get; set; } = default;

/// <summary>
/// Create a new FieldAttribute for the given symbol and using the namespace as its containing library.
Expand All @@ -19,6 +34,8 @@ public sealed class FieldAttribute<T> : Attribute where T : FieldTag {
public FieldAttribute (string symbolName)
{
SymbolName = symbolName;
Flags = default (T);
LibraryName = null;
}

/// <summary>
Expand All @@ -30,17 +47,33 @@ public FieldAttribute (string symbolName, string libraryName)
{
SymbolName = symbolName;
LibraryName = libraryName;
Flags = default (T);
}


/// <summary>
/// Get/Set the symbol represented by the attribute.
/// Create a new FieldAttribute for the given symbol and customizing the flags.
/// <param name="symbolName">The name of the symbol.</param>
/// <param name="flags">The flags to customize the field.</param>
/// </summary>
public string SymbolName { get; set; }
public FieldAttribute (string symbolName, T? flags)
{
SymbolName = symbolName;
Flags = flags;
LibraryName = null;
}

/// <summary>
/// Get/Set the library that contains the symbol..
/// Create a new FieldAttribute for the given symbol in the provided library and customizing the flags.
/// <param name="symbolName">The name of the symbol.</param>
/// <param name="libraryName">The name of the library that contains the symbol.</param>
/// <param name="flags">The flags to customize the field.</param>
/// </summary>
public string? LibraryName { get; set; }
public FieldAttribute (string symbolName, string libraryName, T? flags)
{
SymbolName = symbolName;
LibraryName = libraryName;
Flags = flags;
}

}
}
19 changes: 15 additions & 4 deletions src/ObjCBindings/FieldTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,26 @@
namespace ObjCBindings {

/// <summary>
/// Base class use to flag a FieldAttribute usage. Each FieldAttribute must have a flag attached to it so that the
/// binding generator analyzer can verify the binding definition.
/// The exported constant/field is a class/interface property field.
/// </summary>
[Flags]
[Experimental ("APL0003")]
public abstract class FieldTag { }
public enum Field {
/// <summary>
/// Use the default values.
/// </summary>
None = 0,
}

/// <summary>
/// Field flag that states that the field is used as a Enum value.
/// </summary>
[Flags]
[Experimental ("APL0003")]
public sealed class EnumValue : FieldTag { }
public enum EnumValue {
/// <summary>
/// Use the default values.
/// </summary>
None = 0,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
<Compile Include="..\..\bgen\PlatformName.cs" >
<Link>external\PlatformName.cs</Link>
</Compile>
<Compile Include="..\..\ObjCBindings\FieldTag.cs">
<Link>external\FieldTag.cs</Link>
</Compile>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class BindingTypeSemanticAnalyzer : DiagnosticAnalyzer {
typeof (Resources));
const string Category = "Usage";

static readonly DiagnosticDescriptor RBI0001 = new (DiagnosticId, Title, MessageFormat, Category,
internal static readonly DiagnosticDescriptor RBI0001 = new (DiagnosticId, Title, MessageFormat, Category,
DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)</TargetFramework>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-ios</TargetFramework>
<Nullable>enable</Nullable>
<RootNamespace>Microsoft.Macios.Generator.Sample</RootNamespace>
<NoWarn>APL0003</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

<ItemGroup>
Expand Down
51 changes: 41 additions & 10 deletions src/rgen/Microsoft.Macios.Generator/Attributes/FieldData.cs
Original file line number Diff line number Diff line change
@@ -1,51 +1,82 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.Macios.Generator.Extensions;

namespace Microsoft.Macios.Generator.Attributes;

record FieldData {
readonly struct FieldData<T> where T : Enum {
public string SymbolName { get; }
public string? LibraryName { get; private set; }
public string? LibraryName { get; }

FieldData (string symbolName, string? libraryName = null)
public T? Flags { get; } = default;

FieldData (string symbolName, string? libraryName, T? flags)
{
SymbolName = symbolName;
LibraryName = libraryName;
Flags = flags;
}

public static bool TryParse (SyntaxNode attributeSyntax, AttributeData attributeData,
[NotNullWhen (true)] out FieldData? data)
public static bool TryParse (AttributeData attributeData,
[NotNullWhen (true)] out FieldData<T>? data)
{
data = default;

var count = attributeData.ConstructorArguments.Length;
string? symbolName;
string? libraryName = null;
T? flags = default;
switch (count) {
case 1:
data = new ((string) attributeData.ConstructorArguments [0].Value!);
if (!attributeData.ConstructorArguments [0].TryGetIdentifier (out symbolName)) {
return false;
}
break;
case 2:
data = new ((string) attributeData.ConstructorArguments [0].Value!,
(string) attributeData.ConstructorArguments [1].Value!);
if (!attributeData.ConstructorArguments [0].TryGetIdentifier (out symbolName)) {
return false;
}
switch (attributeData.ConstructorArguments [1].Value) {
// there are two possible cases here:
// 1. The second argument is a string
// 2. The second argument is an enum
case T enumValue:
flags = enumValue;
break;
case string lib:
libraryName = lib;
break;
default:
// unexpected value :/
return false;
}
break;
default:
// 0 should not be an option..
return false;
}

if (attributeData.NamedArguments.Length == 0)
if (attributeData.NamedArguments.Length == 0) {
data = new (symbolName, libraryName, flags);
return true;
}

// LibraryName can be a param value
foreach (var (name, value) in attributeData.NamedArguments) {
switch (name) {
case "LibraryName":
data.LibraryName = (string?) value.Value!;
libraryName = (string?) value.Value!;
break;
case "Flags":
flags = (T) value.Value!;
break;
default:
data = null;
return false;
}
}
data = new (symbolName, libraryName, flags);
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.Macios.Generator.Extensions;
using Xamarin.Utils;

namespace Microsoft.Macios.Generator.Attributes;

/// <summary>
/// Represents the data found in a ObsoletedOSPlatformAttribute.
/// </summary>
readonly struct ObsoletedOSPlatformData {
/// <summary>
/// Obsoleted platform.
/// </summary>
public ApplePlatform Platform { get; }

/// <summary>
/// Version in which the symbol was obsoleted. The default new Version () value will be
/// used when the symbol has been obsoleted in all version.
/// </summary>
public Version Version { get; }

/// <summary>
/// Optional obsoleted message to report to the user.
/// </summary>
public string? Message { get; } = default;

/// <summary>
/// Optional url that points to the documentation.
/// </summary>
public string? Url { get; }

internal ObsoletedOSPlatformData (string platformName)
{
(Platform, Version) = platformName.GetPlatformAndVersion ();
}

internal ObsoletedOSPlatformData (string platformName, string? message = null, string? url = null) : this (platformName)
{
Message = message;
Url = url;
}

/// <summary>
/// Try to parse the attribute data to retrieve the information of an ObsoletedOSPlatformAttribute.
/// </summary>
/// <param name="attributeData">The attribute data to be parsed.</param>
/// <param name="data">The parsed data. Null if we could not parse the attribute data.</param>
/// <returns>True if the data was parsed.</returns>
public static bool TryParse (AttributeData attributeData,
[NotNullWhen (true)] out ObsoletedOSPlatformData? data)
{
data = default;
string platformName;
string? message = null;
string? url = null;

var count = attributeData.ConstructorArguments.Length;
switch (count) {
case 1:
platformName = (string) attributeData.ConstructorArguments [0].Value!;
break;
case 2:
platformName = (string) attributeData.ConstructorArguments [0].Value!;
message = (string) attributeData.ConstructorArguments [1].Value!;
break;
default: // there is not 3 args constructor with a url
return false;
}

if (attributeData.NamedArguments.Length == 0) {
data = new (platformName, message, url);
return true;
}

foreach (var (name, value) in attributeData.NamedArguments) {
switch (name) {
case "Url":
url = (string?) value.Value!;
break;
case "TypeId": // ignore the type id
break;
default:
data = null;
return false;
}
}

data = new (platformName, message, url);
return true;
}
}
Loading

6 comments on commit 6bfecec

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

Please sign in to comment.