diff --git a/src/Makefile b/src/Makefile index bb088101958..4c5b8157cad 100644 --- a/src/Makefile +++ b/src/Makefile @@ -456,6 +456,7 @@ $($(2)_DOTNET_BUILD_DIR)/$(4)/Microsoft.$(1)%dll $($(2)_DOTNET_BUILD_DIR)/$(4)/M $(DOTNET_CSC) \ $(DOTNET_FLAGS) \ /analyzer:$(ROSLYN_GENERATOR) \ + /generatedfilesout:$($(2)_DOTNET_BUILD_DIR)/generated-sources \ -unsafe \ -optimize \ $$(ARGS_$(1)) \ diff --git a/src/rgen/Microsoft.Macios.Binding.Common/Microsoft.Macios.Binding.Common.csproj b/src/rgen/Microsoft.Macios.Binding.Common/Microsoft.Macios.Binding.Common.csproj new file mode 100644 index 00000000000..1698ed678a9 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Binding.Common/Microsoft.Macios.Binding.Common.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + + + + external\ApplePlatform.cs + + + external\SdkVersions.cs + + + external\TargetFramework.cs + + + external\Frameworks.cs + + + external\PlatformName.cs + + + + diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Microsoft.Macios.Bindings.Analyzer.Sample.csproj b/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Microsoft.Macios.Bindings.Analyzer.Sample.csproj index a02eefcf4af..9a9e7f03974 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Microsoft.Macios.Bindings.Analyzer.Sample.csproj +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Microsoft.Macios.Bindings.Analyzer.Sample.csproj @@ -7,6 +7,7 @@ + @@ -15,12 +16,6 @@ external\BindginTypeAttribute.cs - - external\Attributes.cs - - - external\PlatformName.cs - diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/AnalyzerReleases.Shipped.md b/src/rgen/Microsoft.Macios.Bindings.Analyzer/AnalyzerReleases.Shipped.md index 5a165075fbc..0b2cd20c485 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/AnalyzerReleases.Shipped.md +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/AnalyzerReleases.Shipped.md @@ -3,5 +3,4 @@ ### New Rules | Rule ID | Category | Severity | Notes | -|---------|----------|----------|------------------------------------------------------| -| RBI0001 | Usage | Error | Binding types should be declared as partial classes. | \ No newline at end of file +|---------|----------|----------|------------------------------------------------------| \ No newline at end of file diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/AnalyzerReleases.Unshipped.md b/src/rgen/Microsoft.Macios.Bindings.Analyzer/AnalyzerReleases.Unshipped.md index 44f7c8f4ef7..20f6010e33e 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/AnalyzerReleases.Unshipped.md +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/AnalyzerReleases.Unshipped.md @@ -1,4 +1,9 @@ ### New Rules -| Rule ID | Category | Severity | Notes | -|---------|----------|----------|-------| \ No newline at end of file +| Rule ID | Category | Severity | Notes | +|---------|----------|----------|------------------------------------------------------------| +| RBI0001 | Usage | Error | Binding types should be declared as partial classes. | +| RBI0002 | Usage | Error | Smart enum values must be tagged with an FieldAttribute. | +| RBI0003 | Usage | Error | Smart enum backing field cannot be an empty string. | +| RBI0004 | Usage | Error | Non Apple framework bindings must provide a library name. | +| RBI0005 | Usage | Warning | Do not provide the LibraryName for known Apple frameworks. | diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeCodeFixProvider.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeCodeFixProvider.cs index 17b8a298360..036b08041c1 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeCodeFixProvider.cs +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeCodeFixProvider.cs @@ -20,7 +20,7 @@ namespace Microsoft.Macios.Bindings.Analyzer; public class BindingTypeCodeFixProvider : CodeFixProvider { // Specify the diagnostic IDs of analyzers that are expected to be linked. public sealed override ImmutableArray FixableDiagnosticIds { get; } = - ImmutableArray.Create (BindingTypeSemanticAnalyzer.DiagnosticId); + ImmutableArray.Create (BindingTypeSemanticAnalyzer.RBI0001.Id); // If you don't need the 'fix all' behaviour, return null. public override FixAllProvider? GetFixAllProvider () => null; diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeSemanticAnalyzer.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeSemanticAnalyzer.cs index fd5df71a0e8..2e535572d9f 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeSemanticAnalyzer.cs +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/BindingTypeSemanticAnalyzer.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.Macios.Bindings.Analyzer.Extensions; namespace Microsoft.Macios.Bindings.Analyzer; @@ -12,23 +13,20 @@ namespace Microsoft.Macios.Bindings.Analyzer; /// pattern. /// [DiagnosticAnalyzer (LanguageNames.CSharp)] -public class BindingTypeSemanticAnalyzer : DiagnosticAnalyzer { - internal const string DiagnosticId = "RBI0001"; - static readonly LocalizableString Title = new LocalizableResourceString (nameof (Resources.RBI0001Title), - Resources.ResourceManager, typeof (Resources)); - static readonly LocalizableString MessageFormat = - new LocalizableResourceString (nameof (Resources.RBI0001MessageFormat), Resources.ResourceManager, - typeof (Resources)); - static readonly LocalizableString Description = - new LocalizableResourceString (nameof (Resources.RBI0001Description), Resources.ResourceManager, - typeof (Resources)); - const string Category = "Usage"; +public class BindingTypeSemanticAnalyzer : DiagnosticAnalyzer, IBindingTypeAnalyzer { - static readonly DiagnosticDescriptor RBI0001 = new (DiagnosticId, Title, MessageFormat, Category, - DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); - public override ImmutableArray SupportedDiagnostics { get; } = - ImmutableArray.Create (RBI0001); + internal static readonly DiagnosticDescriptor RBI0001 = new ( + "RBI0001", + new LocalizableResourceString (nameof (Resources.RBI0001Title), Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.RBI0001MessageFormat), Resources.ResourceManager, typeof (Resources)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString (nameof (Resources.RBI0001Description), Resources.ResourceManager, typeof (Resources)) + ); + + public override ImmutableArray SupportedDiagnostics { get; } = [RBI0001]; public override void Initialize (AnalysisContext context) { @@ -38,35 +36,17 @@ public override void Initialize (AnalysisContext context) } void AnalysisContext (SyntaxNodeAnalysisContext context) - { - // only care about classes - if (context.Node is not ClassDeclarationSyntax classDeclarationNode) - return; + => this.AnalyzeBindingType (context); - var classSymbol = context.SemanticModel.GetDeclaredSymbol (classDeclarationNode); - if (classSymbol is null) - return; + public ImmutableArray Analyze (PlatformName _, ClassDeclarationSyntax declarationNode, INamedTypeSymbol symbol) + { + if (declarationNode.Modifiers.Any (x => x.IsKind (SyntaxKind.PartialKeyword))) + return []; - var boundAttributes = classSymbol.GetAttributes (); - if (boundAttributes.Length == 0) { - return; - } + var diagnostic = Diagnostic.Create (RBI0001, + declarationNode.Identifier.GetLocation (), // point to where the 'class' keyword is used + symbol.ToDisplayString ()); + return [diagnostic]; - // the c# syntax is a a list of lists of attributes. That is why we need to iterate through the list of lists - foreach (var attributeData in boundAttributes) { - // based on the type use the correct parser to retrieve the data - var attributeType = attributeData.AttributeClass?.ToDisplayString (); - switch (attributeType) { - case "ObjCBindings.BindingTypeAttribute": - // validate that the class is partial, else we need to report an error - if (!classDeclarationNode.Modifiers.Any (x => x.IsKind (SyntaxKind.PartialKeyword))) { - var diagnostic = Diagnostic.Create (RBI0001, - classDeclarationNode.Identifier.GetLocation (), // point to where the 'class' keyword is used - classSymbol.ToDisplayString ()); - context.ReportDiagnostic (diagnostic); - } - break; - } - } } } diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/DiagnosticInfo.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/DiagnosticInfo.cs new file mode 100644 index 00000000000..95e9f8c3ab6 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/DiagnosticInfo.cs @@ -0,0 +1,17 @@ +using Microsoft.CodeAnalysis; + +namespace Microsoft.Macios.Bindings.Analyzer; + +public struct DiagnosticInfo ( + string id, + LocalizableResourceString title, + LocalizableResourceString messageFormat, + LocalizableResourceString description, + string category) { + + public string Id { get; } = id; + public LocalizableResourceString Title { get; } = title; + public LocalizableResourceString MessageFormat { get; } = messageFormat; + public LocalizableResourceString Description { get; } = description; + public string Category { get; } = category; +} diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Extensions/IBindingTypeAnalyzerExtensions.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Extensions/IBindingTypeAnalyzerExtensions.cs new file mode 100644 index 00000000000..a08eb31c692 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Extensions/IBindingTypeAnalyzerExtensions.cs @@ -0,0 +1,42 @@ +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.Macios.Generator; +using Microsoft.Macios.Generator.Extensions; +using Diagnostic = Microsoft.CodeAnalysis.Diagnostic; + +namespace Microsoft.Macios.Bindings.Analyzer.Extensions; + +public static class BindingTypeAnalyzerExtensions { + public static void AnalyzeBindingType (this IBindingTypeAnalyzer self, SyntaxNodeAnalysisContext context) where T : BaseTypeDeclarationSyntax + { + // calculate the current compilation platform name + if (context.Node is not T declarationNode) + return; + + var declaredSymbol = context.SemanticModel.GetDeclaredSymbol (declarationNode); + if (declaredSymbol is null) + return; + + var boundAttributes = declaredSymbol.GetAttributes (); + if (boundAttributes.Length == 0) { + // do nothing since our generator only cares about declared types with the BindingType attribute + return; + } + + // the c# syntax is a a list of lists of attributes. That is why we need to iterate through the list of lists + foreach (var attributeData in boundAttributes) { + // based on the type use the correct parser to retrieve the data + var attributeType = attributeData.AttributeClass?.ToDisplayString (); + switch (attributeType) { + case AttributesNames.BindingAttribute: + // validate that the class is partial, else we need to report an error + var diagnostics= self.Analyze (context.Compilation.GetCurrentPlatform (), + declarationNode, declaredSymbol); + foreach (var diagnostic in diagnostics) + context.ReportDiagnostic (diagnostic); + break; + } + } + } +} diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Extensions/PlatformNameExtensions.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Extensions/PlatformNameExtensions.cs new file mode 100644 index 00000000000..2564975dfbe --- /dev/null +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Extensions/PlatformNameExtensions.cs @@ -0,0 +1,16 @@ +using Xamarin.Utils; + +namespace Microsoft.Macios.Bindings.Analyzer.Extensions; + +public static class PlatformNameExtensions { + + public static ApplePlatform ToApplePlatform (this PlatformName platformName) + => platformName switch { + PlatformName.iOS => ApplePlatform.iOS, + PlatformName.MacCatalyst => ApplePlatform.MacCatalyst, + PlatformName.MacOSX => ApplePlatform.MacOSX, + PlatformName.TvOS => ApplePlatform.TVOS, + _ => ApplePlatform.None, + }; + +} diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/IBindingTypeAnalyzer.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/IBindingTypeAnalyzer.cs new file mode 100644 index 00000000000..42e7759bf5f --- /dev/null +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/IBindingTypeAnalyzer.cs @@ -0,0 +1,12 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.Macios.Bindings.Analyzer; + +/// +/// Interface to be implemented by those analyzer that will be looking at BindingTypes. +/// +public interface IBindingTypeAnalyzer where T : BaseTypeDeclarationSyntax { + ImmutableArray Analyze (PlatformName platformName, T declarationNode, INamedTypeSymbol symbol); +} diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Microsoft.Macios.Bindings.Analyzer.csproj b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Microsoft.Macios.Bindings.Analyzer.csproj index 6f9e0902e43..3c9ecfb6775 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Microsoft.Macios.Bindings.Analyzer.csproj +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Microsoft.Macios.Bindings.Analyzer.csproj @@ -14,12 +14,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + @@ -43,4 +44,23 @@ + + + external\AttributesNames.cs + + + external\FieldData.cs + + + Extensions\CompilationExtensions.cs + + + Extensions\TypeSymbolExtensions.cs + + + + + + + diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs index bfe0d3152b0..157059a7c12 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs @@ -45,51 +45,99 @@ internal static System.Globalization.CultureInfo Culture { } } - internal static string AB0001Description { + internal static string RBI0001CodeFixTitle { get { - return ResourceManager.GetString("AB0001Description", resourceCulture); + return ResourceManager.GetString("RBI0001CodeFixTitle", resourceCulture); } } - internal static string AB0001MessageFormat { + internal static string RBI0001Description { get { - return ResourceManager.GetString("AB0001MessageFormat", resourceCulture); + return ResourceManager.GetString("RBI0001Description", resourceCulture); } } - internal static string AB0001Title { + internal static string RBI0001MessageFormat { get { - return ResourceManager.GetString("AB0001Title", resourceCulture); + return ResourceManager.GetString("RBI0001MessageFormat", resourceCulture); } } - internal static string RBI0001CodeFixTitle { + internal static string RBI0001Title { get { - return ResourceManager.GetString("RBI0001CodeFixTitle", resourceCulture); + return ResourceManager.GetString("RBI0001Title", resourceCulture); } } - internal static string RBI0001Description { + internal static string RBI0002Description { get { - return ResourceManager.GetString("RBI0001Description", resourceCulture); + return ResourceManager.GetString("RBI0002Description", resourceCulture); } } - internal static string RBI0001MessageFormat { + internal static string RBI0002MessageFormat { get { - return ResourceManager.GetString("RBI0001MessageFormat", resourceCulture); + return ResourceManager.GetString("RBI0002MessageFormat", resourceCulture); } } - internal static string RBI0001Title { + internal static string RBI0002Title { get { - return ResourceManager.GetString("RBI0001Title", resourceCulture); + return ResourceManager.GetString("RBI0002Title", resourceCulture); + } + } + + internal static string RBI0003Description { + get { + return ResourceManager.GetString("RBI0003Description", resourceCulture); + } + } + + internal static string RBI0003MessageFormat { + get { + return ResourceManager.GetString("RBI0003MessageFormat", resourceCulture); + } + } + + internal static string RBI0003Title { + get { + return ResourceManager.GetString("RBI0003Title", resourceCulture); + } + } + + internal static string RBI0004Description { + get { + return ResourceManager.GetString("RBI0004Description", resourceCulture); + } + } + + internal static string RBI0004MessageFormat { + get { + return ResourceManager.GetString("RBI0004MessageFormat", resourceCulture); + } + } + + internal static string RBI0004Title { + get { + return ResourceManager.GetString("RBI0004Title", resourceCulture); + } + } + + internal static string RBI0005Description { + get { + return ResourceManager.GetString("RBI0005Description", resourceCulture); + } + } + + internal static string RBI0005MessageFormat { + get { + return ResourceManager.GetString("RBI0005MessageFormat", resourceCulture); } } - internal static string AB0002Description { + internal static string RBI0005Title { get { - return ResourceManager.GetString("AB0002Description", resourceCulture); + return ResourceManager.GetString("RBI0005Title", resourceCulture); } } } diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx index 2f228a310c3..6321521e9df 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx @@ -34,4 +34,56 @@ Binding type declaration must be partial The title of the diagnostic. + + + In order for the code to be generated a smart enum value has to have a backing field. + An optional longer localizable description of the diagnostic. + + + The enum value '{0}' must be tagged with a FieldAttribute. + The format-able message the diagnostic displays. + + + Smart enum values must be tagged with an FieldAttribute + The title of the diagnostic. + + + + In order for the code to be generated the backing filed of a smart enum value cannot be an empty string. + An optional longer localizable description of the diagnostic. + + + The enum value '{0}' backing field is an empty string. + The format-able message the diagnostic displays. + + + Smart enum backing field cannot be an empty string. + The title of the diagnostic. + + + + Smart enum backing field for a non Apple framework must provide a library name. + An optional longer localizable description of the diagnostic. + + + The enum value '{0}' backing field must provide a library name. + The format-able message the diagnostic displays. + + + Non Apple framework bindings must provide a library name. + The title of the diagnostic. + + + + Fields of known Apple frameworks should not provide a LibraryName. + An optional longer localizable description of the diagnostic. + + + The backing Field of '{0}' should not provide a LibraryName. + The format-able message the diagnostic displays. + + + Do not provide the LibraryName for known Apple frameworks. + The title of the diagnostic. + diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/SmartEnumSemanticAnalyzer.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/SmartEnumSemanticAnalyzer.cs new file mode 100644 index 00000000000..7899c23ce2b --- /dev/null +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/SmartEnumSemanticAnalyzer.cs @@ -0,0 +1,142 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.Macios.Bindings.Analyzer.Extensions; +using Microsoft.Macios.Generator; +using Microsoft.Macios.Generator.Attributes; +using Microsoft.Macios.Generator.Extensions; + +namespace Microsoft.Macios.Bindings.Analyzer; + + +/// +/// Analyzer to ensure that all enum values in an SmartEnum contains a Field attribute. +/// +[DiagnosticAnalyzer (LanguageNames.CSharp)] +public class SmartEnumSemanticAnalyzer : DiagnosticAnalyzer, IBindingTypeAnalyzer { + // All enum values must have a Field attribute + internal static readonly DiagnosticDescriptor RBI0002 = new ( + "RBI0002", + new LocalizableResourceString (nameof (Resources.RBI0002Title), Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.RBI0002MessageFormat), Resources.ResourceManager, typeof (Resources)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString (nameof (Resources.RBI0002Description), Resources.ResourceManager, typeof (Resources)) + ); + + // All Field symbols cannot be empty or white space + internal static readonly DiagnosticDescriptor RBI0003 = new ( + "RBI0003", + new LocalizableResourceString (nameof (Resources.RBI0003Title), Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.RBI0003MessageFormat), Resources.ResourceManager, typeof (Resources)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString (nameof (Resources.RBI0003Description), Resources.ResourceManager, typeof (Resources)) + ); + + // If not an apple framework, we should provide the library path + internal static readonly DiagnosticDescriptor RBI0004 = new ( + "RBI0004", + new LocalizableResourceString (nameof (Resources.RBI0004Title), Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.RBI0004MessageFormat), Resources.ResourceManager, typeof (Resources)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString (nameof (Resources.RBI0004Description), Resources.ResourceManager, typeof (Resources)) + ); + + // if apple framework, the library path should be empty + internal static readonly DiagnosticDescriptor RBI0005 = new ( + "RBI0005", + new LocalizableResourceString (nameof (Resources.RBI0005Title), Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.RBI0005MessageFormat), Resources.ResourceManager, typeof (Resources)), + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: new LocalizableResourceString (nameof (Resources.RBI0005Description), Resources.ResourceManager, typeof (Resources)) + ); + + public override ImmutableArray SupportedDiagnostics { get; } = [RBI0002, RBI0003, RBI0004, RBI0005]; + + public override void Initialize (AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis (GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution (); + context.RegisterSyntaxNodeAction (AnalysisContext, SyntaxKind.EnumDeclaration); + } + + void AnalysisContext (SyntaxNodeAnalysisContext context) + => this.AnalyzeBindingType (context); + + public ImmutableArray Analyze (PlatformName platformName, EnumDeclarationSyntax declarationNode, INamedTypeSymbol symbol) + { + // we want to ensure several things: + // 1. All enum values are marked with a Field attribute + // 2. All Field attributes have a symbol name + // 3. If the Field attribute is not from a known apple library, the library name is set + // 4. If the Field attribute is from a known apple library the lib should be null + + // based on the platform decide if we are dealing with a known apple framework, we want all, not just the + // ones that are part of the simulator + var appleFrameworks = Frameworks.GetFrameworks (platformName.ToApplePlatform (), false); + var isAppleFramework = appleFrameworks.Find (symbol.ContainingNamespace.Name) is not null; + + // bucket with all the diagnostics we have found + var bucket = ImmutableArray.CreateBuilder (); + + var members = symbol.GetMembers ().OfType ().ToArray (); + foreach (var fieldSymbol in members) { + var attributes = fieldSymbol.GetAttributeData (); + if (attributes.Count == 0) { + // 1. All enum values are marked with a Field attribute, therefore add a diagnostic + bucket.Add (Diagnostic.Create (RBI0002, + fieldSymbol.Locations.First (), + fieldSymbol.ToDisplayString ())); + continue; + } + + // Get all the FieldAttribute, parse it and add the data to the result + if (attributes.TryGetValue (AttributesNames.FieldAttribute, out var fieldAttrData)) { + var fieldSyntax = fieldAttrData.ApplicationSyntaxReference?.GetSyntax (); + if (fieldSyntax is null) { + // if we cant get the syntax reference, we have a bug + continue; + } + + if (FieldData.TryParse (fieldSyntax, fieldAttrData, out var fieldData)) { + // only provide diagnostics if we managed to parse the FieldData, else we have a bug in the + // analyzer + if (string.IsNullOrWhiteSpace (fieldData.SymbolName)) { + // 2. All Field attributes have a symbol name, therefore add a diagnostic + bucket.Add (Diagnostic.Create (RBI0003, + fieldSyntax.GetLocation (), + fieldSymbol.ToDisplayString ())); + } + + if (!isAppleFramework) { + if (string.IsNullOrWhiteSpace (fieldData.LibraryName)) { + bucket.Add (Diagnostic.Create (RBI0004, + fieldSyntax.GetLocation (), + fieldSymbol.ToDisplayString ())); + } + } else { + if (fieldData.LibraryName is not null) { + bucket.Add (Diagnostic.Create (RBI0005, + fieldSyntax.GetLocation (), + fieldSymbol.ToDisplayString ())); + } + } + } else { + // report but msg + } + } + } + + return bucket.ToImmutable (); + } +} diff --git a/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj b/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj index e10b90a1b8f..02fe20736ef 100644 --- a/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj +++ b/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj @@ -8,6 +8,7 @@ + @@ -18,9 +19,6 @@ external\Attributes.cs - - external\PlatformName.cs - diff --git a/src/rgen/Microsoft.Macios.Generator/Attributes/FieldData.cs b/src/rgen/Microsoft.Macios.Generator/Attributes/FieldData.cs new file mode 100644 index 00000000000..933cc0f082c --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Attributes/FieldData.cs @@ -0,0 +1,51 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Macios.Generator.Attributes; + +record FieldData { + public string SymbolName { get; } + public string? LibraryName { get; private set; } + + FieldData (string symbolName, string? libraryName = null) + { + SymbolName = symbolName; + LibraryName = libraryName; + } + + public static bool TryParse (SyntaxNode attributeSyntax, AttributeData attributeData, + [NotNullWhen (true)] out FieldData? data) + { + data = default; + + var count = attributeData.ConstructorArguments.Length; + switch (count) { + case 1: + data = new((string) attributeData.ConstructorArguments [0].Value!); + break; + case 2: + data = new((string) attributeData.ConstructorArguments [0].Value!, + (string) attributeData.ConstructorArguments [1].Value!); + break; + default: + // 0 should not be an option.. + return false; + } + + if (attributeData.NamedArguments.Length == 0) + return true; + + // LibraryName can be a param value + foreach (var (name, value) in attributeData.NamedArguments) { + switch (name) { + case "LibraryName": + data.LibraryName = (string) value.Value!; + break; + default: + data = null; + return false; + } + } + return true; + } +} diff --git a/src/rgen/Microsoft.Macios.Generator/AttributesNames.cs b/src/rgen/Microsoft.Macios.Generator/AttributesNames.cs index 62ac3b632b0..ca2766bb79e 100644 --- a/src/rgen/Microsoft.Macios.Generator/AttributesNames.cs +++ b/src/rgen/Microsoft.Macios.Generator/AttributesNames.cs @@ -5,5 +5,6 @@ namespace Microsoft.Macios.Generator; /// public static class AttributesNames { - public static readonly string BindingAttribute = "ObjCBindings.BindingTypeAttribute"; + public const string BindingAttribute = "ObjCBindings.BindingTypeAttribute"; + public const string FieldAttribute = "Foundation.FieldAttribute"; } diff --git a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs index 8b7d54e8533..834202e71a3 100644 --- a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs +++ b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -92,7 +93,9 @@ static void AddPipeline (IncrementalGeneratorInitializationContext context) w /// /// Root syntax tree of the base type declaration. /// String builder that will be used for the generated code. - static void CollectUsingStatements (SyntaxTree tree, TabbedStringBuilder sb) + /// The context of the code being generated. Used to keep track of the current added statemnts + static void CollectUsingStatements (SyntaxTree tree, TabbedStringBuilder sb, ICodeEmitter emitter) + where T : BaseTypeDeclarationSyntax { // collect all using from the syntax tree, add them to a hash to make sure that we don't have duplicates // and add those usings that we do know we need for bindings. @@ -103,7 +106,14 @@ static void CollectUsingStatements (SyntaxTree tree, TabbedStringBuilder sb) var usingDirectivesToKeep = new HashSet (usingDirectives) { // add the using statements that we know we need and print them to the sb }; - foreach (var ns in usingDirectivesToKeep) { + + // add those using statements needed by the emitter + foreach (var ns in emitter.UsingStatements) { + usingDirectivesToKeep.Add (ns); + } + + // add them sorted so that we have testeable generated code + foreach (var ns in usingDirectivesToKeep.OrderBy (s => s)) { if (string.IsNullOrEmpty (ns)) continue; sb.AppendLine ($"using {ns};"); @@ -138,17 +148,19 @@ static void GenerateCode (SourceProductionContext context, Compilation compil sb.AppendLine ("#nullable enable"); sb.AppendLine (); - CollectUsingStatements (baseTypeDeclarationSyntax.SyntaxTree, sb); - // delegate semantic model and syntax tree analysis to the emitter who will generate the code and knows // best if (ContextFactory.TryCreate (rootContext, semanticModel, namedTypeSymbol, baseTypeDeclarationSyntax, out var symbolBindingContext) && EmitterFactory.TryCreate (symbolBindingContext, sb, out var emitter)) { - if (emitter.TryEmit (out var diagnostics)) { - // only add file when we do generate code - var code = sb.ToString (); - context.AddSource ($"{emitter.SymbolName}.g.cs", SourceText.From (code, Encoding.UTF8)); + + CollectUsingStatements (baseTypeDeclarationSyntax.SyntaxTree, sb, emitter); + + if (emitter.TryEmit(out var diagnostics)) { + // only add file when we do generate code + var code = sb.ToString (); + context.AddSource ($"{symbolBindingContext.Namespace}/{emitter.SymbolName}.g.cs", + SourceText.From (code, Encoding.UTF8)); } else { // add to the diagnostics and continue to the next possible candidate foreach (Diagnostic diagnostic in diagnostics) { diff --git a/src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs index a9bc1f3c242..bcfb1b0bafa 100644 --- a/src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs +++ b/src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs @@ -12,8 +12,8 @@ interface ISymbolBindingContext where T : BaseTypeDeclarationSyntax { T DeclarationSyntax { get; } string Namespace { get; } string SymbolName { get; } - RootBindingContext RootBindingContext { get; init; } - SemanticModel SemanticModel { get; init; } - INamedTypeSymbol Symbol { get; init; } + RootBindingContext RootBindingContext { get; } + SemanticModel SemanticModel { get; } + INamedTypeSymbol Symbol { get; } bool IsStatic { get; } } diff --git a/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs index 64159cb0657..35df7c2036f 100644 --- a/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs +++ b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using Microsoft.CodeAnalysis; +using Microsoft.Macios.Generator.Extensions; namespace Microsoft.Macios.Generator.Context; @@ -22,26 +23,58 @@ class RootBindingContext { public RootBindingContext (Compilation compilation) { Compilation = compilation; - CurrentPlatform = PlatformName.None; - // use the reference assembly to determine what platform we are binding - foreach (var referencedAssemblyName in compilation.ReferencedAssemblyNames) { - switch (referencedAssemblyName.Name) { - case "Microsoft.iOS": - CurrentPlatform = PlatformName.iOS; - break; - case "Microsoft.MacCatalyst": - CurrentPlatform = PlatformName.MacCatalyst; - break; - case "Microsoft.macOS": - CurrentPlatform = PlatformName.MacOSX; - break; - case "Microsoft.tvOS": - CurrentPlatform = PlatformName.TvOS; - break; - default: - CurrentPlatform = PlatformName.None; - break; + CurrentPlatform = compilation.GetCurrentPlatform (); + } + + // TODO: clean code coming from the old generator + public bool TryComputeLibraryName (string? attributeLibraryName, string typeNamespace, + [NotNullWhen (true)] out string? libraryName, + out string? libraryPath) + { + libraryPath = null; + + if (!string.IsNullOrEmpty (attributeLibraryName)) { + // Remapped + libraryName = attributeLibraryName; + if (libraryName [0] == '+') { + switch (libraryName) { + case "+CoreImage": + CurrentPlatform.TryGetCoreImageMap (out libraryName); + break; + case "+CoreServices": + CurrentPlatform.TryGetCoreServicesMap (out libraryName); + break; + case "+PDFKit": + libraryName = "PdfKit"; + CurrentPlatform.TryGetPDFKitMap (out libraryPath); + break; + } + } else { + // we get something in LibraryName from FieldAttribute so we assume + // it is a path to a library, so we save the path and change library name + // to a valid identifier if needed + libraryPath = libraryName; + // without extension makes more sense, but we can't change it since it breaks compat + if (BindThirdPartyLibrary) { + libraryName = Path.GetFileName (libraryName); + } else { + libraryName = Path.GetFileNameWithoutExtension (libraryName); + } + + if (libraryName.Contains ('.')) + libraryName = libraryName.Replace (".", string.Empty); } + } else if (BindThirdPartyLibrary) { + // User should provide a LibraryName + libraryName = null; + return false; + } else { + libraryName = typeNamespace; } + + if (libraryName is not null && !_libraries.ContainsKey (libraryName)) + _libraries.Add (libraryName, libraryPath); + + return true; } } diff --git a/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs index b3d0c24cfbb..79e03a7d730 100644 --- a/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs +++ b/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs index 0d56a01a1c1..bef0cc6e4a5 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; @@ -8,6 +9,8 @@ namespace Microsoft.Macios.Generator.Emitters; class ClassEmitter (ClassBindingContext context, TabbedStringBuilder builder) : ICodeEmitter { public string SymbolName => context.SymbolName; + public IEnumerable UsingStatements => []; + public bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics) { diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs index b946986eac9..91a91047d78 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs @@ -1,8 +1,11 @@ +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Macios.Generator.Attributes; using Microsoft.Macios.Generator.Context; +using Microsoft.Macios.Generator.Extensions; namespace Microsoft.Macios.Generator.Emitters; @@ -10,10 +13,132 @@ class EnumEmitter (ISymbolBindingContext context, TabbedS : ICodeEmitter { public string SymbolName => $"{context.SymbolName}Extensions"; + public IEnumerable UsingStatements => ["System"]; - public bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics) + void Emit (TabbedStringBuilder classBlock, (IFieldSymbol Symbol, FieldData FieldData) enumField, int index) { - diagnostics = null; - return true; + var typeNamespace = enumField.Symbol.ContainingType.ContainingNamespace.Name; + if (!context.RootBindingContext.TryComputeLibraryName (enumField.FieldData.LibraryName, typeNamespace, + out string? libraryName, out string? libraryPath)) { + return; + } + + classBlock.AppendLine ($"[Field (\"{enumField.FieldData.SymbolName}\", \"{libraryPath ?? libraryName}\")]"); + + using (var propertyBlock = classBlock.CreateBlock ($"internal unsafe static IntPtr {enumField.FieldData.SymbolName}", true)) + using (var getterBlock = propertyBlock.CreateBlock ("get", true)) { + getterBlock.AppendLine ($"fixed (IntPtr *storage = &values [{index}])"); + var lib = (libraryPath is null) ? $"Libraries.{libraryName}.Handle" : $"\"{libraryPath}\""; + getterBlock.AppendLine ( + $"\treturn Dlfcn.CachePointer ({lib}, \"{enumField.FieldData.SymbolName}\", storage);"); + } + } + + void Emit (TabbedStringBuilder classBlock, ImmutableArray<(IFieldSymbol Symbol, FieldData FieldData)> fields) + { + for (var index = 0; index < fields.Length; index++) { + var field = fields [index]; + classBlock.AppendLine (); + Emit (classBlock, field, index); + } + } + + void Emit (TabbedStringBuilder classBlock, INamedTypeSymbol enumSymbol, + ImmutableArray<(IFieldSymbol Symbol, FieldData FieldData)>? members) + { + if (members is null) + return; + + // smart enum require 4 diff methods to be able to retrieve the values + + // Get constant + using (var getConstantBlock = classBlock.CreateBlock ($"public static NSString? GetConstant (this {enumSymbol.Name} self)", true)) { + getConstantBlock.AppendLine ("IntPtr ptr = IntPtr.Zero;"); + using (var switchBlock = getConstantBlock.CreateBlock ("switch ((int) self)", true)) { + for (var index = 0; index < members.Value.Length; index++) { + var (_, fieldData) = members.Value [index]; + switchBlock.AppendLine ($"case {index}: // {fieldData.SymbolName}"); + switchBlock.AppendLine ($"\tptr = {fieldData.SymbolName};"); + switchBlock.AppendLine ("\tbreak;"); + } + } + + getConstantBlock.AppendLine ("return (NSString?) Runtime.GetNSObject (ptr);"); + } + + classBlock.AppendLine (); + // Get value + using (var getValueBlock = classBlock.CreateBlock ($"public static {enumSymbol.Name} GetValue (NSString constant)", true)) { + getValueBlock.AppendLine ("if (constant is null)"); + getValueBlock.AppendLine ("\tthrow new ArgumentNullException (nameof (constant));"); + foreach ((IFieldSymbol? fieldSymbol, FieldData? fieldData) in members) { + getValueBlock.AppendLine ($"if (constant.IsEqualTo ({fieldData.SymbolName}))"); + getValueBlock.AppendLine ($"\treturn {enumSymbol.Name}.{fieldSymbol.Name};"); + } + + getValueBlock.AppendLine ( + "throw new NotSupportedException ($\"{constant} has no associated enum value on this platform.\");"); + } + + classBlock.AppendLine (); + // To ConstantArray + classBlock.AppendRaw ( +@$"internal static NSString?[]? ToConstantArray (this {enumSymbol.Name}[]? values) +{{ + if (values is null) + return null; + var rv = new global::System.Collections.Generic.List (); + for (var i = 0; i < values.Length; i++) {{ + var value = values [i]; + rv.Add (value.GetConstant ()); + }} + return rv.ToArray (); +}}"); + classBlock.AppendLine (); + classBlock.AppendLine (); + // ToEnumArray + classBlock.AppendRaw ( +@$"internal static {enumSymbol.Name}[]? ToEnumArray (this NSString[]? values) +{{ + if (values is null) + return null; + var rv = new global::System.Collections.Generic.List<{enumSymbol.Name}> (); + for (var i = 0; i < values.Length; i++) {{ + var value = values [i]; + rv.Add (GetValue (value)); + }} + return rv.ToArray (); +}}"); } + + public bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics) + { + diagnostics = null; + if (!context.Symbol.TryGetEnumFields (out var members, + out diagnostics) || members.Value.Length == 0) { + diagnostics = new ImmutableArray (); + return false; + } + // in the old generator we had to copy over the enum, in this new approach the only code + // we need to create is the extension class for the enum that is backed by fields + builder.AppendLine (); + builder.AppendLine ($"namespace {context.Namespace};"); + builder.AppendLine (); + + builder.AppendGeneratedCodeAttribute (); + using (var classBlock = builder.CreateBlock ($"static public partial class {SymbolName}", true)) { + classBlock.AppendLine (); + classBlock.AppendLine ($"static IntPtr[] values = new IntPtr [{members.Value.Length}];"); + // foreach member in the enum we need to create a field that holds the value, the property emitter + // will take care of generating the property. Do not order by name to keep the order of the enum + Emit (classBlock, members.Value); + classBlock.AppendLine (); + + // emit the extension methods that will be used to get the values from the enum + Emit (classBlock, context.Symbol, members); + classBlock.AppendLine (); + } + + return true; + } } diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs index d78584e82bb..6ae7b9b10f0 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; @@ -10,4 +11,5 @@ namespace Microsoft.Macios.Generator.Emitters; interface ICodeEmitter { public string SymbolName { get; } bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics); + IEnumerable UsingStatements { get; } } diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs index 23f77d0c4d7..65329736ad2 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; @@ -8,6 +9,7 @@ namespace Microsoft.Macios.Generator.Emitters; class InterfaceEmitter (ISymbolBindingContext context, TabbedStringBuilder builder) : ICodeEmitter { public string SymbolName { get; } = string.Empty; + public IEnumerable UsingStatements => []; public bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics) { diagnostics = null; diff --git a/src/rgen/Microsoft.Macios.Generator/Extensions/CompilationExtensions.cs b/src/rgen/Microsoft.Macios.Generator/Extensions/CompilationExtensions.cs new file mode 100644 index 00000000000..bcf6edeb23a --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Extensions/CompilationExtensions.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Macios.Generator.Extensions; + +public static class CompilationExtensions { + + public static PlatformName GetCurrentPlatform (this Compilation self) + { + // use the reference assembly to determine what platform we are binding + foreach (var referenceAssembly in self.ReferencedAssemblyNames) { + switch (referenceAssembly.Name) { + case "Microsoft.iOS": + return PlatformName.iOS; + case "Microsoft.MacCatalyst": + return PlatformName.MacCatalyst; + case "Microsoft.macOS": + return PlatformName.MacOSX; + case "Microsoft.tvOS": + return PlatformName.TvOS; + } + } + + return PlatformName.None; + } +} diff --git a/src/rgen/Microsoft.Macios.Generator/Extensions/NamedTypeSymbolExtensions.cs b/src/rgen/Microsoft.Macios.Generator/Extensions/NamedTypeSymbolExtensions.cs new file mode 100644 index 00000000000..e24f022b59f --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Extensions/NamedTypeSymbolExtensions.cs @@ -0,0 +1,47 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.Macios.Generator.Attributes; + +namespace Microsoft.Macios.Generator.Extensions; + +static class NamedTypeSymbolExtensions { + public static bool TryGetEnumFields (this INamedTypeSymbol enumSymbol, + [NotNullWhen (true)] + out ImmutableArray<(IFieldSymbol Symbol, FieldData FieldData)>? fields, + [NotNullWhen (false)] out ImmutableArray? diagnostics) + { + fields = null; + diagnostics = null; + + // because we are dealing with an enum, we need to get all the fields from the symbol but we need to + // keep the order in which they are defined in the source code. + + var fieldBucket = + ImmutableArray.CreateBuilder<(IFieldSymbol Symbol, FieldData FieldData)> (); + + var members = enumSymbol.GetMembers ().OfType ().ToArray (); + foreach (var fieldSymbol in members) { + var attributes = fieldSymbol.GetAttributeData (); + if (attributes.Count == 0) + continue; + + // Get all the FieldAttribute, parse it and add the data to the result + if (attributes.TryGetValue (AttributesNames.FieldAttribute, out var fieldAttrData)) { + var fieldSyntax = fieldAttrData.ApplicationSyntaxReference?.GetSyntax (); + if (fieldSyntax is null) + continue; + + if (FieldData.TryParse (fieldSyntax, fieldAttrData, out var fieldData)) { + fieldBucket.Add ((Symbol: fieldSymbol, FieldData: fieldData)); + } else { + // TODO: diagnostics + } + } + } + + fields = fieldBucket.ToImmutable (); + return true; + } +} diff --git a/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.cs b/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.cs new file mode 100644 index 00000000000..24c048e4d4d --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Macios.Generator.Extensions; + +static class TypeSymbolExtensions { + public static bool IsSmartEnum (this ITypeSymbol symbol) + { + var boundAttributes = symbol.GetAttributes (); + if (boundAttributes.Length == 0) { + return false; + } + + // do not use LINQ here, we need to check if the attribute is present + foreach (var attributeData in boundAttributes) { + if (attributeData.AttributeClass?.ToDisplayString () == AttributesNames.BindingAttribute) + return true; + } + return false; + } + + public static string GetSmartEnumType (this ITypeSymbol symbol) + { + // TODO: look into the backing type of the smart enum + return "NSString"; + } + + public static Dictionary GetAttributeData (this ISymbol symbol) + { + var boundAttributes = symbol.GetAttributes (); + if (boundAttributes.Length == 0) { + // return an empty dictionary if there are no attributes + return new(); + } + + var attributes = new Dictionary (); + foreach (var attributeData in boundAttributes) { + var attrName = attributeData.AttributeClass?.ToDisplayString (); + if (string.IsNullOrEmpty (attrName)) + continue; + if (!attributes.TryAdd (attrName, attributeData)) { + // TODO: diagnostics + } + } + + return attributes; + } +} diff --git a/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj b/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj index 7da28f54684..d6dc11ca906 100644 --- a/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj +++ b/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj @@ -14,12 +14,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + @@ -27,15 +27,15 @@ <_Parameter1>Microsoft.Macios.Generator.Tests - - - external\PlatformName.cs - external\PlatformNameExtensions.cs + + + + diff --git a/src/rgen/rgen.sln b/src/rgen/rgen.sln index 68499203954..701dc87108a 100644 --- a/src/rgen/rgen.sln +++ b/src/rgen/rgen.sln @@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Macios.Generator. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Macios.Bindings.Analyzer.Tests", "..\..\tests\rgen\Microsoft.Macios.Bindings.Analyzer.Tests\Microsoft.Macios.Bindings.Analyzer.Tests.csproj", "{1AC4A248-CC98-4392-8690-4E2CAF6E194B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Macios.Binding.Common", "Microsoft.Macios.Binding.Common\Microsoft.Macios.Binding.Common.csproj", "{536758BC-2A88-4B79-ABB1-6B39494A5FE6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,5 +44,9 @@ Global {1AC4A248-CC98-4392-8690-4E2CAF6E194B}.Debug|Any CPU.Build.0 = Debug|Any CPU {1AC4A248-CC98-4392-8690-4E2CAF6E194B}.Release|Any CPU.ActiveCfg = Release|Any CPU {1AC4A248-CC98-4392-8690-4E2CAF6E194B}.Release|Any CPU.Build.0 = Release|Any CPU + {536758BC-2A88-4B79-ABB1-6B39494A5FE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {536758BC-2A88-4B79-ABB1-6B39494A5FE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {536758BC-2A88-4B79-ABB1-6B39494A5FE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {536758BC-2A88-4B79-ABB1-6B39494A5FE6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs index 67c71504329..2800e2632da 100644 --- a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs +++ b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs @@ -29,10 +29,10 @@ public class Examples { var compilation = CreateCompilation (nameof (CompareGeneratedCode), platform, inputText); var diagnostics = await RunAnalyzer (new BindingTypeSemanticAnalyzer (), compilation); var analyzerDiagnotics = diagnostics - .Where (d => d.Id == BindingTypeSemanticAnalyzer.DiagnosticId).ToArray (); + .Where (d => d.Id == BindingTypeSemanticAnalyzer.RBI0001.Id).ToArray (); Assert.Single (analyzerDiagnotics); // verify the diagnostic message - VerifyDiagnosticMessage (analyzerDiagnotics [0], BindingTypeSemanticAnalyzer.DiagnosticId, + VerifyDiagnosticMessage (analyzerDiagnotics [0], BindingTypeSemanticAnalyzer.RBI0001.Id, DiagnosticSeverity.Error, "The binding type 'Test.Examples' must declared as a partial class"); } } diff --git a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/SmartEnumSemanticAnalyzerTests.cs b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/SmartEnumSemanticAnalyzerTests.cs new file mode 100644 index 00000000000..d234d8e4c12 --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/SmartEnumSemanticAnalyzerTests.cs @@ -0,0 +1,255 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Xamarin.Tests; +using Xamarin.Utils; +using Xunit; + +namespace Microsoft.Macios.Bindings.Analyzer.Tests; + +public class SmartEnumSemanticAnalyzerTests : BaseGeneratorWithAnalyzerTestClass { + + [Theory] + [PlatformInlineData (ApplePlatform.iOS)] + [PlatformInlineData (ApplePlatform.TVOS)] + [PlatformInlineData (ApplePlatform.MacOSX)] + [PlatformInlineData (ApplePlatform.MacCatalyst)] + public async Task SmartEnumMustHaveFieldAttribute (ApplePlatform platform) + { + const string inputText = @" +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace AVFoundation; + +[BindingType] +public enum AVCaptureSystemPressureExampleLevel { + [Field (""AVCaptureSystemPressureLevelNominal"")] + Nominal, + + [Field (""AVCaptureSystemPressureLevelFair"")] + Fair, + + [Field (""AVCaptureSystemPressureLevelSerious"")] + Serious, + + [Field (""AVCaptureSystemPressureLevelCritical"")] + Critical, + + // missing field attribute, should be an error + Shutdown, +} +"; + + var compilation = CreateCompilation (nameof (SmartEnumSemanticAnalyzerTests), platform, inputText); + var diagnostics = await RunAnalyzer (new SmartEnumSemanticAnalyzer (), compilation); + var analyzerDiagnotics = diagnostics + .Where (d => d.Id == SmartEnumSemanticAnalyzer.RBI0002.Id).ToArray (); + Assert.Single (analyzerDiagnotics); + // verify the diagnostic message + VerifyDiagnosticMessage (analyzerDiagnotics [0], SmartEnumSemanticAnalyzer.RBI0002.Id, + DiagnosticSeverity.Error, "The enum value 'AVFoundation.AVCaptureSystemPressureExampleLevel.Shutdown' must be tagged with a FieldAttribute."); + } + + [Theory] + [PlatformInlineData (ApplePlatform.iOS)] + [PlatformInlineData (ApplePlatform.TVOS)] + [PlatformInlineData (ApplePlatform.MacOSX)] + [PlatformInlineData (ApplePlatform.MacCatalyst)] + public async Task SmartEnumSymbolMustBeCorrect (ApplePlatform platform) + { + const string inputText = @" +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace AVFoundation; + +[BindingType] +public enum AVCaptureSystemPressureExampleLevel { + [Field (""AVCaptureSystemPressureLevelNominal"")] + Nominal, + + [Field (""AVCaptureSystemPressureLevelFair"")] + Fair, + + [Field (""AVCaptureSystemPressureLevelSerious"")] + Serious, + + [Field (""AVCaptureSystemPressureLevelCritical"")] + Critical, + + // empty field, this should be an error + [Field ("" "")] + Shutdown, +}"; + + var compilation = CreateCompilation (nameof (SmartEnumSemanticAnalyzerTests), platform, inputText); + var diagnostics = await RunAnalyzer (new SmartEnumSemanticAnalyzer (), compilation); + var analyzerDiagnotics = diagnostics + .Where (d => d.Id == SmartEnumSemanticAnalyzer.RBI0003.Id).ToArray (); + Assert.Single (analyzerDiagnotics); + VerifyDiagnosticMessage (analyzerDiagnotics [0], SmartEnumSemanticAnalyzer.RBI0003.Id, + DiagnosticSeverity.Error, "The enum value 'AVFoundation.AVCaptureSystemPressureExampleLevel.Shutdown' backing field is an empty string."); + } + + const string AppleFrameworkLib = @" +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace AVFoundation; + +[BindingType] +public enum AVCaptureSystemPressureExampleLevel { + [Field (""AVCaptureSystemPressureLevelNominal"")] + Nominal, + + [Field (""AVCaptureSystemPressureLevelFair"")] + Fair, + + [Field (""AVCaptureSystemPressureLevelSerious"")] + Serious, + + [Field (""AVCaptureSystemPressureLevelCritical"")] + Critical, + + // do not do this with apple frameworks + [Field (""AVCaptureSystemPressureLevelShutdown"", ""/path/to/not/needed/lib"")] + Shutdown, +}"; + + const string AppleFrameworkLibNamedParameter = @" +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace AVFoundation; + +[BindingType] +public enum AVCaptureSystemPressureExampleLevel { + [Field (""AVCaptureSystemPressureLevelNominal"")] + Nominal, + + [Field (""AVCaptureSystemPressureLevelFair"")] + Fair, + + [Field (""AVCaptureSystemPressureLevelSerious"")] + Serious, + + [Field (""AVCaptureSystemPressureLevelCritical"")] + Critical, + + // do not do this with apple frameworks + [Field (""AVCaptureSystemPressureLevelShutdown"", LibraryName = ""/path/to/not/needed/lib"")] + Shutdown, +}"; + + [Theory] + [PlatformInlineData (ApplePlatform.iOS, AppleFrameworkLib)] + [PlatformInlineData (ApplePlatform.iOS, AppleFrameworkLibNamedParameter)] + [PlatformInlineData (ApplePlatform.TVOS, AppleFrameworkLib)] + [PlatformInlineData (ApplePlatform.TVOS, AppleFrameworkLibNamedParameter)] + [PlatformInlineData (ApplePlatform.MacOSX, AppleFrameworkLib)] + [PlatformInlineData (ApplePlatform.MacOSX, AppleFrameworkLibNamedParameter)] + [PlatformInlineData (ApplePlatform.MacCatalyst, AppleFrameworkLib)] + [PlatformInlineData (ApplePlatform.MacCatalyst, AppleFrameworkLibNamedParameter)] + public async Task SmartEnumAppleFrameworkNotLibrary (ApplePlatform platform, string inputText) + { + var compilation = CreateCompilation (nameof (SmartEnumSemanticAnalyzerTests), platform, inputText); + var diagnostics = await RunAnalyzer (new SmartEnumSemanticAnalyzer(), compilation); + + var analyzerDiagnotics = diagnostics + .Where (d => d.Id == SmartEnumSemanticAnalyzer.RBI0005.Id).ToArray (); + Assert.Single (analyzerDiagnotics); + VerifyDiagnosticMessage (analyzerDiagnotics [0], SmartEnumSemanticAnalyzer.RBI0005.Id, + DiagnosticSeverity.Warning, "The backing Field of 'AVFoundation.AVCaptureSystemPressureExampleLevel.Shutdown' should not provide a LibraryName."); + } + + const string CustomLibraryMissingLibraryName = @" +using System; +using System.Runtime.Versioning; +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace CustomLibrary; + +[BindingType] +public enum CustomLibraryEnum { + [Field (""None"", ""/path/to/customlibrary.framework"")] + None, + [Field (""Medium"", ""/path/to/customlibrary.framework"")] + Medium, + // missing lib, this is an error + [Field (""High"")] + High, +} +"; + const string CustomLibraryEmptyLibraryName = @" +using System; +using System.Runtime.Versioning; +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace CustomLibrary; + +[BindingType] +public enum CustomLibraryEnum { + [Field (""None"", ""/path/to/customlibrary.framework"")] + None, + [Field (""Medium"", ""/path/to/customlibrary.framework"")] + Medium, + // empty lib, this is an error + [Field (""High"", "" "")] + High, +} +"; + + const string CustomLibraryEmptyLibraryNameParameter = @" +using System; +using System.Runtime.Versioning; +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace CustomLibrary; + +[BindingType] +public enum CustomLibraryEnum { + [Field (""None"", ""/path/to/customlibrary.framework"")] + None, + [Field (""Medium"", ""/path/to/customlibrary.framework"")] + Medium, + // empty lib, this is an error + [Field (""High"", LibraryName = "" "")] + High, +} +"; + + [Theory] + [PlatformInlineData (ApplePlatform.iOS, CustomLibraryMissingLibraryName)] + [PlatformInlineData (ApplePlatform.iOS, CustomLibraryEmptyLibraryName)] + [PlatformInlineData (ApplePlatform.iOS, CustomLibraryEmptyLibraryNameParameter)] + [PlatformInlineData (ApplePlatform.TVOS, CustomLibraryMissingLibraryName)] + [PlatformInlineData (ApplePlatform.TVOS, CustomLibraryEmptyLibraryName)] + [PlatformInlineData (ApplePlatform.TVOS, CustomLibraryEmptyLibraryNameParameter)] + [PlatformInlineData (ApplePlatform.MacOSX, CustomLibraryMissingLibraryName)] + [PlatformInlineData (ApplePlatform.MacOSX, CustomLibraryEmptyLibraryName)] + [PlatformInlineData (ApplePlatform.MacOSX, CustomLibraryEmptyLibraryNameParameter)] + [PlatformInlineData (ApplePlatform.MacCatalyst, CustomLibraryMissingLibraryName)] + [PlatformInlineData (ApplePlatform.MacCatalyst, CustomLibraryEmptyLibraryName)] + [PlatformInlineData (ApplePlatform.MacCatalyst, CustomLibraryEmptyLibraryNameParameter)] + public async Task SmartEnumThirdPartyLibrary (ApplePlatform platform, string inputText) + { + var compilation = CreateCompilation (nameof (SmartEnumSemanticAnalyzerTests), platform, inputText); + var diagnostics = await RunAnalyzer (new SmartEnumSemanticAnalyzer(), compilation); + var analyzerDiagnotics = diagnostics + .Where (d => d.Id == SmartEnumSemanticAnalyzer.RBI0004.Id).ToArray (); + Assert.Single (analyzerDiagnotics); + VerifyDiagnosticMessage (analyzerDiagnotics [0], SmartEnumSemanticAnalyzer.RBI0004.Id, + DiagnosticSeverity.Error, "The enum value 'CustomLibrary.CustomLibraryEnum.High' backing field must provide a library name."); + } +} diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/BaseGeneratorTestClass.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/BaseGeneratorTestClass.cs index 1ed459e25cc..a183d4206fc 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/BaseGeneratorTestClass.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/BaseGeneratorTestClass.cs @@ -16,22 +16,31 @@ namespace Microsoft.Macios.Generator.Tests; /// public class BaseGeneratorTestClass { protected BindingSourceGeneratorGenerator GeneratorGenerator; - protected CSharpGeneratorDriver _driver; + protected CSharpGeneratorDriver Driver; + + // list of the defines for each platform, this is passed to the parser to ensure that + // we are testing the platforms as if they were being compiled. + readonly Dictionary platformDefines = new() { + { ApplePlatform.iOS, new [] { "__IOS__" } }, + { ApplePlatform.TVOS, new [] { "__TVOS__" } }, + { ApplePlatform.MacOSX, new [] { "__MACOS__" } }, + { ApplePlatform.MacCatalyst, new [] { "__MACCATALYST__" } }, + }; public BaseGeneratorTestClass () { GeneratorGenerator = new BindingSourceGeneratorGenerator (); - _driver = CSharpGeneratorDriver.Create (GeneratorGenerator); + Driver = CSharpGeneratorDriver.Create (GeneratorGenerator); } protected Compilation RunGeneratorsAndUpdateCompilation (Compilation compilation, out ImmutableArray diagnostics) { - _driver.RunGeneratorsAndUpdateCompilation (compilation, out var updatedCompilation, out diagnostics); + Driver.RunGeneratorsAndUpdateCompilation (compilation, out var updatedCompilation, out diagnostics); return updatedCompilation; } protected GeneratorDriverRunResult RunGenerators (Compilation compilation) - => _driver.RunGenerators (compilation).GetRunResult (); + => Driver.RunGenerators (compilation).GetRunResult (); protected Compilation CreateCompilation (string name, ApplePlatform platform, params string [] sources) { @@ -46,8 +55,13 @@ protected Compilation CreateCompilation (string name, ApplePlatform platform, pa } else { throw new InvalidOperationException ($"Could not find platform dll for {platform}"); } - var trees = sources.Select (s => CSharpSyntaxTree.ParseText (s)); - var options = new CSharpCompilationOptions (OutputKind.NetModule); + + var parseOptions = new CSharpParseOptions (LanguageVersion.Latest, DocumentationMode.None, preprocessorSymbols: platformDefines[platform]);; + var trees = sources.Select (s => CSharpSyntaxTree.ParseText (s, parseOptions)); + + var options = new CSharpCompilationOptions (OutputKind.NetModule) + .WithAllowUnsafe (true); + return CSharpCompilation.Create (name, trees, references, options); } diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs index fd8738e7d3e..99279577131 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs @@ -23,9 +23,9 @@ public partial class AVAudioPcmBuffer : AVAudioBuffer { #nullable enable -using System; using Foundation; using ObjCBindings; +using System; namespace TestNamespace { @@ -48,7 +48,7 @@ public void CorrectUsingImports (ApplePlatform platform, string input, string ex platform, usingImportInput); // Run generators and retrieve all results. - var runResult = _driver.RunGenerators (compilation).GetRunResult (); + var runResult = Driver.RunGenerators (compilation).GetRunResult (); Assert.Empty (runResult.Diagnostics); // ensure that we do have all the needed attributes present diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/Microsoft.Macios.Generator.Tests.csproj b/tests/rgen/Microsoft.Macios.Generator.Tests/Microsoft.Macios.Generator.Tests.csproj index 417b2c63d19..066aa4b25bd 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/Microsoft.Macios.Generator.Tests.csproj +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/Microsoft.Macios.Generator.Tests.csproj @@ -20,6 +20,7 @@ + @@ -36,24 +37,20 @@ external\ExecutionHelper.cs - - external\ApplePlatform.cs - - - external\TargetFramework.cs - external\StringUtils.cs external\Execution.cs - - external\SdkVersions.cs - external\Cache.cs + + + + + diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/AVCaptureDeviceTypeEnum.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/AVCaptureDeviceTypeEnum.cs new file mode 100644 index 00000000000..ba947b2b9f4 --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/AVCaptureDeviceTypeEnum.cs @@ -0,0 +1,43 @@ +using System; +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace AVFoundation; + +[BindingType] +public enum AVCaptureDeviceType { + + [Field ("AVCaptureDeviceTypeBuiltInMicrophone")] + BuiltInMicrophone, + + [Field ("AVCaptureDeviceTypeBuiltInWideAngleCamera")] + BuiltInWideAngleCamera, + + [Field ("AVCaptureDeviceTypeBuiltInTelephotoCamera")] + BuiltInTelephotoCamera, + + [Field ("AVCaptureDeviceTypeBuiltInDuoCamera")] + BuiltInDuoCamera, + + [Field ("AVCaptureDeviceTypeBuiltInDualCamera")] + BuiltInDualCamera, + + [Field ("AVCaptureDeviceTypeBuiltInTrueDepthCamera")] + BuiltInTrueDepthCamera, + + [Field ("AVCaptureDeviceTypeBuiltInUltraWideCamera")] + BuiltInUltraWideCamera, + + [Field ("AVCaptureDeviceTypeBuiltInTripleCamera")] + BuiltInTripleCamera, + + [Field ("AVCaptureDeviceTypeBuiltInDualWideCamera")] + BuiltInDualWideCamera, + + [Field ("AVCaptureDeviceTypeExternalUnknown")] + ExternalUnknown, + + [Field ("AVCaptureDeviceTypeBuiltInLiDARDepthCamera")] + BuiltInLiDarDepthCamera, +} diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/AVCaptureSystemPressureLevel.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/AVCaptureSystemPressureLevel.cs new file mode 100644 index 00000000000..e16bf4334c5 --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/AVCaptureSystemPressureLevel.cs @@ -0,0 +1,23 @@ +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace AVFoundation; + +[BindingType] +public enum AVCaptureSystemPressureLevel { + [Field ("AVCaptureSystemPressureLevelNominal")] + Nominal, + + [Field ("AVCaptureSystemPressureLevelFair")] + Fair, + + [Field ("AVCaptureSystemPressureLevelSerious")] + Serious, + + [Field ("AVCaptureSystemPressureLevelCritical")] + Critical, + + [Field ("AVCaptureSystemPressureLevelShutdown")] + Shutdown, +} diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/AVMediaCharacteristics.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/AVMediaCharacteristics.cs new file mode 100644 index 00000000000..82fe0c10b16 --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/AVMediaCharacteristics.cs @@ -0,0 +1,74 @@ +using System; +using System.Runtime.Versioning; +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace AVFoundation; + +[BindingType] +[SupportedOSPlatform ("maccatalyst13.1")] +enum AVMediaCharacteristics { + [Field ("AVMediaCharacteristicVisual")] + Visual = 0, + + [Field ("AVMediaCharacteristicAudible")] + Audible = 1, + + [Field ("AVMediaCharacteristicLegible")] + Legible = 2, + + [Field ("AVMediaCharacteristicFrameBased")] + FrameBased = 3, + + [Field ("AVMediaCharacteristicUsesWideGamutColorSpace")] + UsesWideGamutColorSpace = 4, + + [Field ("AVMediaCharacteristicIsMainProgramContent")] + IsMainProgramContent = 5, + + [Field ("AVMediaCharacteristicIsAuxiliaryContent")] + IsAuxiliaryContent = 6, + + [Field ("AVMediaCharacteristicContainsOnlyForcedSubtitles")] + ContainsOnlyForcedSubtitles = 7, + + [Field ("AVMediaCharacteristicTranscribesSpokenDialogForAccessibility")] + TranscribesSpokenDialogForAccessibility = 8, + + [Field ("AVMediaCharacteristicDescribesMusicAndSoundForAccessibility")] + DescribesMusicAndSoundForAccessibility = 9, + + [Field ("AVMediaCharacteristicDescribesVideoForAccessibility")] + DescribesVideoForAccessibility = 10, + +#if !__MACOS__ + [Field ("AVMediaCharacteristicEasyToRead")] + EasyToRead = 11, +#endif + + [Field ("AVMediaCharacteristicLanguageTranslation")] + LanguageTranslation = 12, + + [Field ("AVMediaCharacteristicDubbedTranslation")] + DubbedTranslation = 13, + + [Field ("AVMediaCharacteristicVoiceOverTranslation")] + VoiceOverTranslation = 14, + + [SupportedOSPlatform ("ios13.0")] + [SupportedOSPlatform ("tvos13.0")] + [Field ("AVMediaCharacteristicIsOriginalContent")] + IsOriginalContent = 15, + + [SupportedOSPlatform ("ios14.0")] + [SupportedOSPlatform ("tvos14.0")] + [SupportedOSPlatform ("maccatalyst14.0")] + [Field ("AVMediaCharacteristicContainsHDRVideo")] + ContainsHdrVideo = 16, + + [SupportedOSPlatform ("ios13.0")] + [SupportedOSPlatform ("tvos13.0")] + [Field ("AVMediaCharacteristicContainsAlphaChannel")] + ContainsAlphaChannel = 17, +} diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/CustomLibraryEnum.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/CustomLibraryEnum.cs new file mode 100644 index 00000000000..7bbcf15fff3 --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/CustomLibraryEnum.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.Versioning; +using Foundation; +using ObjCRuntime; +using ObjCBindings; + +namespace CustomLibrary; + +[BindingType] +public enum CustomLibraryEnum { + [Field ("None", "/path/to/customlibrary.framework")] + None, + [Field ("Medium", "/path/to/customlibrary.framework")] + Medium, + [Field ("High", "/path/to/customlibrary.framework")] + High, +} diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureDeviceTypeEnum.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureDeviceTypeEnum.cs new file mode 100644 index 00000000000..c748e4e3671 --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureDeviceTypeEnum.cs @@ -0,0 +1,222 @@ +// + +#nullable enable + +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using System; + +namespace AVFoundation; + +[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)] +static public partial class AVCaptureDeviceTypeExtensions +{ + + static IntPtr[] values = new IntPtr [11]; + + [Field ("AVCaptureDeviceTypeBuiltInMicrophone", "AVFoundation")] + internal unsafe static IntPtr AVCaptureDeviceTypeBuiltInMicrophone + { + get + { + fixed (IntPtr *storage = &values [0]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureDeviceTypeBuiltInMicrophone", storage); + } + } + + [Field ("AVCaptureDeviceTypeBuiltInWideAngleCamera", "AVFoundation")] + internal unsafe static IntPtr AVCaptureDeviceTypeBuiltInWideAngleCamera + { + get + { + fixed (IntPtr *storage = &values [1]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureDeviceTypeBuiltInWideAngleCamera", storage); + } + } + + [Field ("AVCaptureDeviceTypeBuiltInTelephotoCamera", "AVFoundation")] + internal unsafe static IntPtr AVCaptureDeviceTypeBuiltInTelephotoCamera + { + get + { + fixed (IntPtr *storage = &values [2]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureDeviceTypeBuiltInTelephotoCamera", storage); + } + } + + [Field ("AVCaptureDeviceTypeBuiltInDuoCamera", "AVFoundation")] + internal unsafe static IntPtr AVCaptureDeviceTypeBuiltInDuoCamera + { + get + { + fixed (IntPtr *storage = &values [3]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureDeviceTypeBuiltInDuoCamera", storage); + } + } + + [Field ("AVCaptureDeviceTypeBuiltInDualCamera", "AVFoundation")] + internal unsafe static IntPtr AVCaptureDeviceTypeBuiltInDualCamera + { + get + { + fixed (IntPtr *storage = &values [4]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureDeviceTypeBuiltInDualCamera", storage); + } + } + + [Field ("AVCaptureDeviceTypeBuiltInTrueDepthCamera", "AVFoundation")] + internal unsafe static IntPtr AVCaptureDeviceTypeBuiltInTrueDepthCamera + { + get + { + fixed (IntPtr *storage = &values [5]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureDeviceTypeBuiltInTrueDepthCamera", storage); + } + } + + [Field ("AVCaptureDeviceTypeBuiltInUltraWideCamera", "AVFoundation")] + internal unsafe static IntPtr AVCaptureDeviceTypeBuiltInUltraWideCamera + { + get + { + fixed (IntPtr *storage = &values [6]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureDeviceTypeBuiltInUltraWideCamera", storage); + } + } + + [Field ("AVCaptureDeviceTypeBuiltInTripleCamera", "AVFoundation")] + internal unsafe static IntPtr AVCaptureDeviceTypeBuiltInTripleCamera + { + get + { + fixed (IntPtr *storage = &values [7]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureDeviceTypeBuiltInTripleCamera", storage); + } + } + + [Field ("AVCaptureDeviceTypeBuiltInDualWideCamera", "AVFoundation")] + internal unsafe static IntPtr AVCaptureDeviceTypeBuiltInDualWideCamera + { + get + { + fixed (IntPtr *storage = &values [8]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureDeviceTypeBuiltInDualWideCamera", storage); + } + } + + [Field ("AVCaptureDeviceTypeExternalUnknown", "AVFoundation")] + internal unsafe static IntPtr AVCaptureDeviceTypeExternalUnknown + { + get + { + fixed (IntPtr *storage = &values [9]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureDeviceTypeExternalUnknown", storage); + } + } + + [Field ("AVCaptureDeviceTypeBuiltInLiDARDepthCamera", "AVFoundation")] + internal unsafe static IntPtr AVCaptureDeviceTypeBuiltInLiDARDepthCamera + { + get + { + fixed (IntPtr *storage = &values [10]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureDeviceTypeBuiltInLiDARDepthCamera", storage); + } + } + + public static NSString? GetConstant (this AVCaptureDeviceType self) + { + IntPtr ptr = IntPtr.Zero; + switch ((int) self) + { + case 0: // AVCaptureDeviceTypeBuiltInMicrophone + ptr = AVCaptureDeviceTypeBuiltInMicrophone; + break; + case 1: // AVCaptureDeviceTypeBuiltInWideAngleCamera + ptr = AVCaptureDeviceTypeBuiltInWideAngleCamera; + break; + case 2: // AVCaptureDeviceTypeBuiltInTelephotoCamera + ptr = AVCaptureDeviceTypeBuiltInTelephotoCamera; + break; + case 3: // AVCaptureDeviceTypeBuiltInDuoCamera + ptr = AVCaptureDeviceTypeBuiltInDuoCamera; + break; + case 4: // AVCaptureDeviceTypeBuiltInDualCamera + ptr = AVCaptureDeviceTypeBuiltInDualCamera; + break; + case 5: // AVCaptureDeviceTypeBuiltInTrueDepthCamera + ptr = AVCaptureDeviceTypeBuiltInTrueDepthCamera; + break; + case 6: // AVCaptureDeviceTypeBuiltInUltraWideCamera + ptr = AVCaptureDeviceTypeBuiltInUltraWideCamera; + break; + case 7: // AVCaptureDeviceTypeBuiltInTripleCamera + ptr = AVCaptureDeviceTypeBuiltInTripleCamera; + break; + case 8: // AVCaptureDeviceTypeBuiltInDualWideCamera + ptr = AVCaptureDeviceTypeBuiltInDualWideCamera; + break; + case 9: // AVCaptureDeviceTypeExternalUnknown + ptr = AVCaptureDeviceTypeExternalUnknown; + break; + case 10: // AVCaptureDeviceTypeBuiltInLiDARDepthCamera + ptr = AVCaptureDeviceTypeBuiltInLiDARDepthCamera; + break; + } + return (NSString?) Runtime.GetNSObject (ptr); + } + + public static AVCaptureDeviceType GetValue (NSString constant) + { + if (constant is null) + throw new ArgumentNullException (nameof (constant)); + if (constant.IsEqualTo (AVCaptureDeviceTypeBuiltInMicrophone)) + return AVCaptureDeviceType.BuiltInMicrophone; + if (constant.IsEqualTo (AVCaptureDeviceTypeBuiltInWideAngleCamera)) + return AVCaptureDeviceType.BuiltInWideAngleCamera; + if (constant.IsEqualTo (AVCaptureDeviceTypeBuiltInTelephotoCamera)) + return AVCaptureDeviceType.BuiltInTelephotoCamera; + if (constant.IsEqualTo (AVCaptureDeviceTypeBuiltInDuoCamera)) + return AVCaptureDeviceType.BuiltInDuoCamera; + if (constant.IsEqualTo (AVCaptureDeviceTypeBuiltInDualCamera)) + return AVCaptureDeviceType.BuiltInDualCamera; + if (constant.IsEqualTo (AVCaptureDeviceTypeBuiltInTrueDepthCamera)) + return AVCaptureDeviceType.BuiltInTrueDepthCamera; + if (constant.IsEqualTo (AVCaptureDeviceTypeBuiltInUltraWideCamera)) + return AVCaptureDeviceType.BuiltInUltraWideCamera; + if (constant.IsEqualTo (AVCaptureDeviceTypeBuiltInTripleCamera)) + return AVCaptureDeviceType.BuiltInTripleCamera; + if (constant.IsEqualTo (AVCaptureDeviceTypeBuiltInDualWideCamera)) + return AVCaptureDeviceType.BuiltInDualWideCamera; + if (constant.IsEqualTo (AVCaptureDeviceTypeExternalUnknown)) + return AVCaptureDeviceType.ExternalUnknown; + if (constant.IsEqualTo (AVCaptureDeviceTypeBuiltInLiDARDepthCamera)) + return AVCaptureDeviceType.BuiltInLiDarDepthCamera; + throw new NotSupportedException ($"{constant} has no associated enum value on this platform."); + } + + internal static NSString?[]? ToConstantArray (this AVCaptureDeviceType[]? values) + { + if (values is null) + return null; + var rv = new global::System.Collections.Generic.List (); + for (var i = 0; i < values.Length; i++) { + var value = values [i]; + rv.Add (value.GetConstant ()); + } + return rv.ToArray (); + } + + internal static AVCaptureDeviceType[]? ToEnumArray (this NSString[]? values) + { + if (values is null) + return null; + var rv = new global::System.Collections.Generic.List (); + for (var i = 0; i < values.Length; i++) { + var value = values [i]; + rv.Add (GetValue (value)); + } + return rv.ToArray (); + } +} diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureSystemPressureLevel.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureSystemPressureLevel.cs new file mode 100644 index 00000000000..83444b481be --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureSystemPressureLevel.cs @@ -0,0 +1,132 @@ +// + +#nullable enable + +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using System; + +namespace AVFoundation; + +[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)] +static public partial class AVCaptureSystemPressureLevelExtensions +{ + + static IntPtr[] values = new IntPtr [5]; + + [Field ("AVCaptureSystemPressureLevelNominal", "AVFoundation")] + internal unsafe static IntPtr AVCaptureSystemPressureLevelNominal + { + get + { + fixed (IntPtr *storage = &values [0]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureSystemPressureLevelNominal", storage); + } + } + + [Field ("AVCaptureSystemPressureLevelFair", "AVFoundation")] + internal unsafe static IntPtr AVCaptureSystemPressureLevelFair + { + get + { + fixed (IntPtr *storage = &values [1]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureSystemPressureLevelFair", storage); + } + } + + [Field ("AVCaptureSystemPressureLevelSerious", "AVFoundation")] + internal unsafe static IntPtr AVCaptureSystemPressureLevelSerious + { + get + { + fixed (IntPtr *storage = &values [2]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureSystemPressureLevelSerious", storage); + } + } + + [Field ("AVCaptureSystemPressureLevelCritical", "AVFoundation")] + internal unsafe static IntPtr AVCaptureSystemPressureLevelCritical + { + get + { + fixed (IntPtr *storage = &values [3]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureSystemPressureLevelCritical", storage); + } + } + + [Field ("AVCaptureSystemPressureLevelShutdown", "AVFoundation")] + internal unsafe static IntPtr AVCaptureSystemPressureLevelShutdown + { + get + { + fixed (IntPtr *storage = &values [4]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVCaptureSystemPressureLevelShutdown", storage); + } + } + + public static NSString? GetConstant (this AVCaptureSystemPressureLevel self) + { + IntPtr ptr = IntPtr.Zero; + switch ((int) self) + { + case 0: // AVCaptureSystemPressureLevelNominal + ptr = AVCaptureSystemPressureLevelNominal; + break; + case 1: // AVCaptureSystemPressureLevelFair + ptr = AVCaptureSystemPressureLevelFair; + break; + case 2: // AVCaptureSystemPressureLevelSerious + ptr = AVCaptureSystemPressureLevelSerious; + break; + case 3: // AVCaptureSystemPressureLevelCritical + ptr = AVCaptureSystemPressureLevelCritical; + break; + case 4: // AVCaptureSystemPressureLevelShutdown + ptr = AVCaptureSystemPressureLevelShutdown; + break; + } + return (NSString?) Runtime.GetNSObject (ptr); + } + + public static AVCaptureSystemPressureLevel GetValue (NSString constant) + { + if (constant is null) + throw new ArgumentNullException (nameof (constant)); + if (constant.IsEqualTo (AVCaptureSystemPressureLevelNominal)) + return AVCaptureSystemPressureLevel.Nominal; + if (constant.IsEqualTo (AVCaptureSystemPressureLevelFair)) + return AVCaptureSystemPressureLevel.Fair; + if (constant.IsEqualTo (AVCaptureSystemPressureLevelSerious)) + return AVCaptureSystemPressureLevel.Serious; + if (constant.IsEqualTo (AVCaptureSystemPressureLevelCritical)) + return AVCaptureSystemPressureLevel.Critical; + if (constant.IsEqualTo (AVCaptureSystemPressureLevelShutdown)) + return AVCaptureSystemPressureLevel.Shutdown; + throw new NotSupportedException ($"{constant} has no associated enum value on this platform."); + } + + internal static NSString?[]? ToConstantArray (this AVCaptureSystemPressureLevel[]? values) + { + if (values is null) + return null; + var rv = new global::System.Collections.Generic.List (); + for (var i = 0; i < values.Length; i++) { + var value = values [i]; + rv.Add (value.GetConstant ()); + } + return rv.ToArray (); + } + + internal static AVCaptureSystemPressureLevel[]? ToEnumArray (this NSString[]? values) + { + if (values is null) + return null; + var rv = new global::System.Collections.Generic.List (); + for (var i = 0; i < values.Length; i++) { + var value = values [i]; + rv.Add (GetValue (value)); + } + return rv.ToArray (); + } +} diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedCustomLibraryEnum.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedCustomLibraryEnum.cs new file mode 100644 index 00000000000..1dc530f0b0c --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedCustomLibraryEnum.cs @@ -0,0 +1,103 @@ +// + +#nullable enable + +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using System; +using System.Runtime.Versioning; + +namespace CustomLibrary; + +[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)] +static public partial class CustomLibraryEnumExtensions +{ + + static IntPtr[] values = new IntPtr [3]; + + [Field ("None", "/path/to/customlibrary.framework")] + internal unsafe static IntPtr None + { + get + { + fixed (IntPtr *storage = &values [0]) + return Dlfcn.CachePointer ("/path/to/customlibrary.framework", "None", storage); + } + } + + [Field ("Medium", "/path/to/customlibrary.framework")] + internal unsafe static IntPtr Medium + { + get + { + fixed (IntPtr *storage = &values [1]) + return Dlfcn.CachePointer ("/path/to/customlibrary.framework", "Medium", storage); + } + } + + [Field ("High", "/path/to/customlibrary.framework")] + internal unsafe static IntPtr High + { + get + { + fixed (IntPtr *storage = &values [2]) + return Dlfcn.CachePointer ("/path/to/customlibrary.framework", "High", storage); + } + } + + public static NSString? GetConstant (this CustomLibraryEnum self) + { + IntPtr ptr = IntPtr.Zero; + switch ((int) self) + { + case 0: // None + ptr = None; + break; + case 1: // Medium + ptr = Medium; + break; + case 2: // High + ptr = High; + break; + } + return (NSString?) Runtime.GetNSObject (ptr); + } + + public static CustomLibraryEnum GetValue (NSString constant) + { + if (constant is null) + throw new ArgumentNullException (nameof (constant)); + if (constant.IsEqualTo (None)) + return CustomLibraryEnum.None; + if (constant.IsEqualTo (Medium)) + return CustomLibraryEnum.Medium; + if (constant.IsEqualTo (High)) + return CustomLibraryEnum.High; + throw new NotSupportedException ($"{constant} has no associated enum value on this platform."); + } + + internal static NSString?[]? ToConstantArray (this CustomLibraryEnum[]? values) + { + if (values is null) + return null; + var rv = new global::System.Collections.Generic.List (); + for (var i = 0; i < values.Length; i++) { + var value = values [i]; + rv.Add (value.GetConstant ()); + } + return rv.ToArray (); + } + + internal static CustomLibraryEnum[]? ToEnumArray (this NSString[]? values) + { + if (values is null) + return null; + var rv = new global::System.Collections.Generic.List (); + for (var i = 0; i < values.Length; i++) { + var value = values [i]; + rv.Add (GetValue (value)); + } + return rv.ToArray (); + } +} diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedMacOSAVMediaCharacteristics.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedMacOSAVMediaCharacteristics.cs new file mode 100644 index 00000000000..3ea93bd507d --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedMacOSAVMediaCharacteristics.cs @@ -0,0 +1,313 @@ +// + +#nullable enable + +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using System; +using System.Runtime.Versioning; + +namespace AVFoundation; + +[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)] +static public partial class AVMediaCharacteristicsExtensions +{ + + static IntPtr[] values = new IntPtr [17]; + + [Field ("AVMediaCharacteristicVisual", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicVisual + { + get + { + fixed (IntPtr *storage = &values [0]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicVisual", storage); + } + } + + [Field ("AVMediaCharacteristicAudible", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicAudible + { + get + { + fixed (IntPtr *storage = &values [1]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicAudible", storage); + } + } + + [Field ("AVMediaCharacteristicLegible", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicLegible + { + get + { + fixed (IntPtr *storage = &values [2]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicLegible", storage); + } + } + + [Field ("AVMediaCharacteristicFrameBased", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicFrameBased + { + get + { + fixed (IntPtr *storage = &values [3]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicFrameBased", storage); + } + } + + [Field ("AVMediaCharacteristicUsesWideGamutColorSpace", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicUsesWideGamutColorSpace + { + get + { + fixed (IntPtr *storage = &values [4]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicUsesWideGamutColorSpace", storage); + } + } + + [Field ("AVMediaCharacteristicIsMainProgramContent", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicIsMainProgramContent + { + get + { + fixed (IntPtr *storage = &values [5]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicIsMainProgramContent", storage); + } + } + + [Field ("AVMediaCharacteristicIsAuxiliaryContent", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicIsAuxiliaryContent + { + get + { + fixed (IntPtr *storage = &values [6]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicIsAuxiliaryContent", storage); + } + } + + [Field ("AVMediaCharacteristicContainsOnlyForcedSubtitles", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicContainsOnlyForcedSubtitles + { + get + { + fixed (IntPtr *storage = &values [7]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicContainsOnlyForcedSubtitles", storage); + } + } + + [Field ("AVMediaCharacteristicTranscribesSpokenDialogForAccessibility", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicTranscribesSpokenDialogForAccessibility + { + get + { + fixed (IntPtr *storage = &values [8]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicTranscribesSpokenDialogForAccessibility", storage); + } + } + + [Field ("AVMediaCharacteristicDescribesMusicAndSoundForAccessibility", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicDescribesMusicAndSoundForAccessibility + { + get + { + fixed (IntPtr *storage = &values [9]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicDescribesMusicAndSoundForAccessibility", storage); + } + } + + [Field ("AVMediaCharacteristicDescribesVideoForAccessibility", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicDescribesVideoForAccessibility + { + get + { + fixed (IntPtr *storage = &values [10]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicDescribesVideoForAccessibility", storage); + } + } + + [Field ("AVMediaCharacteristicLanguageTranslation", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicLanguageTranslation + { + get + { + fixed (IntPtr *storage = &values [11]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicLanguageTranslation", storage); + } + } + + [Field ("AVMediaCharacteristicDubbedTranslation", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicDubbedTranslation + { + get + { + fixed (IntPtr *storage = &values [12]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicDubbedTranslation", storage); + } + } + + [Field ("AVMediaCharacteristicVoiceOverTranslation", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicVoiceOverTranslation + { + get + { + fixed (IntPtr *storage = &values [13]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicVoiceOverTranslation", storage); + } + } + + [Field ("AVMediaCharacteristicIsOriginalContent", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicIsOriginalContent + { + get + { + fixed (IntPtr *storage = &values [14]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicIsOriginalContent", storage); + } + } + + [Field ("AVMediaCharacteristicContainsHDRVideo", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicContainsHDRVideo + { + get + { + fixed (IntPtr *storage = &values [15]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicContainsHDRVideo", storage); + } + } + + [Field ("AVMediaCharacteristicContainsAlphaChannel", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicContainsAlphaChannel + { + get + { + fixed (IntPtr *storage = &values [16]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicContainsAlphaChannel", storage); + } + } + + public static NSString? GetConstant (this AVMediaCharacteristics self) + { + IntPtr ptr = IntPtr.Zero; + switch ((int) self) + { + case 0: // AVMediaCharacteristicVisual + ptr = AVMediaCharacteristicVisual; + break; + case 1: // AVMediaCharacteristicAudible + ptr = AVMediaCharacteristicAudible; + break; + case 2: // AVMediaCharacteristicLegible + ptr = AVMediaCharacteristicLegible; + break; + case 3: // AVMediaCharacteristicFrameBased + ptr = AVMediaCharacteristicFrameBased; + break; + case 4: // AVMediaCharacteristicUsesWideGamutColorSpace + ptr = AVMediaCharacteristicUsesWideGamutColorSpace; + break; + case 5: // AVMediaCharacteristicIsMainProgramContent + ptr = AVMediaCharacteristicIsMainProgramContent; + break; + case 6: // AVMediaCharacteristicIsAuxiliaryContent + ptr = AVMediaCharacteristicIsAuxiliaryContent; + break; + case 7: // AVMediaCharacteristicContainsOnlyForcedSubtitles + ptr = AVMediaCharacteristicContainsOnlyForcedSubtitles; + break; + case 8: // AVMediaCharacteristicTranscribesSpokenDialogForAccessibility + ptr = AVMediaCharacteristicTranscribesSpokenDialogForAccessibility; + break; + case 9: // AVMediaCharacteristicDescribesMusicAndSoundForAccessibility + ptr = AVMediaCharacteristicDescribesMusicAndSoundForAccessibility; + break; + case 10: // AVMediaCharacteristicDescribesVideoForAccessibility + ptr = AVMediaCharacteristicDescribesVideoForAccessibility; + break; + case 11: // AVMediaCharacteristicLanguageTranslation + ptr = AVMediaCharacteristicLanguageTranslation; + break; + case 12: // AVMediaCharacteristicDubbedTranslation + ptr = AVMediaCharacteristicDubbedTranslation; + break; + case 13: // AVMediaCharacteristicVoiceOverTranslation + ptr = AVMediaCharacteristicVoiceOverTranslation; + break; + case 14: // AVMediaCharacteristicIsOriginalContent + ptr = AVMediaCharacteristicIsOriginalContent; + break; + case 15: // AVMediaCharacteristicContainsHDRVideo + ptr = AVMediaCharacteristicContainsHDRVideo; + break; + case 16: // AVMediaCharacteristicContainsAlphaChannel + ptr = AVMediaCharacteristicContainsAlphaChannel; + break; + } + return (NSString?) Runtime.GetNSObject (ptr); + } + + public static AVMediaCharacteristics GetValue (NSString constant) + { + if (constant is null) + throw new ArgumentNullException (nameof (constant)); + if (constant.IsEqualTo (AVMediaCharacteristicVisual)) + return AVMediaCharacteristics.Visual; + if (constant.IsEqualTo (AVMediaCharacteristicAudible)) + return AVMediaCharacteristics.Audible; + if (constant.IsEqualTo (AVMediaCharacteristicLegible)) + return AVMediaCharacteristics.Legible; + if (constant.IsEqualTo (AVMediaCharacteristicFrameBased)) + return AVMediaCharacteristics.FrameBased; + if (constant.IsEqualTo (AVMediaCharacteristicUsesWideGamutColorSpace)) + return AVMediaCharacteristics.UsesWideGamutColorSpace; + if (constant.IsEqualTo (AVMediaCharacteristicIsMainProgramContent)) + return AVMediaCharacteristics.IsMainProgramContent; + if (constant.IsEqualTo (AVMediaCharacteristicIsAuxiliaryContent)) + return AVMediaCharacteristics.IsAuxiliaryContent; + if (constant.IsEqualTo (AVMediaCharacteristicContainsOnlyForcedSubtitles)) + return AVMediaCharacteristics.ContainsOnlyForcedSubtitles; + if (constant.IsEqualTo (AVMediaCharacteristicTranscribesSpokenDialogForAccessibility)) + return AVMediaCharacteristics.TranscribesSpokenDialogForAccessibility; + if (constant.IsEqualTo (AVMediaCharacteristicDescribesMusicAndSoundForAccessibility)) + return AVMediaCharacteristics.DescribesMusicAndSoundForAccessibility; + if (constant.IsEqualTo (AVMediaCharacteristicDescribesVideoForAccessibility)) + return AVMediaCharacteristics.DescribesVideoForAccessibility; + if (constant.IsEqualTo (AVMediaCharacteristicLanguageTranslation)) + return AVMediaCharacteristics.LanguageTranslation; + if (constant.IsEqualTo (AVMediaCharacteristicDubbedTranslation)) + return AVMediaCharacteristics.DubbedTranslation; + if (constant.IsEqualTo (AVMediaCharacteristicVoiceOverTranslation)) + return AVMediaCharacteristics.VoiceOverTranslation; + if (constant.IsEqualTo (AVMediaCharacteristicIsOriginalContent)) + return AVMediaCharacteristics.IsOriginalContent; + if (constant.IsEqualTo (AVMediaCharacteristicContainsHDRVideo)) + return AVMediaCharacteristics.ContainsHdrVideo; + if (constant.IsEqualTo (AVMediaCharacteristicContainsAlphaChannel)) + return AVMediaCharacteristics.ContainsAlphaChannel; + throw new NotSupportedException ($"{constant} has no associated enum value on this platform."); + } + + internal static NSString?[]? ToConstantArray (this AVMediaCharacteristics[]? values) + { + if (values is null) + return null; + var rv = new global::System.Collections.Generic.List (); + for (var i = 0; i < values.Length; i++) { + var value = values [i]; + rv.Add (value.GetConstant ()); + } + return rv.ToArray (); + } + + internal static AVMediaCharacteristics[]? ToEnumArray (this NSString[]? values) + { + if (values is null) + return null; + var rv = new global::System.Collections.Generic.List (); + for (var i = 0; i < values.Length; i++) { + var value = values [i]; + rv.Add (GetValue (value)); + } + return rv.ToArray (); + } +} diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectediOSAVMediaCharacteristics.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectediOSAVMediaCharacteristics.cs new file mode 100644 index 00000000000..0d121407e33 --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectediOSAVMediaCharacteristics.cs @@ -0,0 +1,328 @@ +// + +#nullable enable + +using Foundation; +using ObjCBindings; +using ObjCRuntime; +using System; +using System.Runtime.Versioning; + +namespace AVFoundation; + +[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)] +static public partial class AVMediaCharacteristicsExtensions +{ + + static IntPtr[] values = new IntPtr [18]; + + [Field ("AVMediaCharacteristicVisual", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicVisual + { + get + { + fixed (IntPtr *storage = &values [0]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicVisual", storage); + } + } + + [Field ("AVMediaCharacteristicAudible", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicAudible + { + get + { + fixed (IntPtr *storage = &values [1]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicAudible", storage); + } + } + + [Field ("AVMediaCharacteristicLegible", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicLegible + { + get + { + fixed (IntPtr *storage = &values [2]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicLegible", storage); + } + } + + [Field ("AVMediaCharacteristicFrameBased", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicFrameBased + { + get + { + fixed (IntPtr *storage = &values [3]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicFrameBased", storage); + } + } + + [Field ("AVMediaCharacteristicUsesWideGamutColorSpace", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicUsesWideGamutColorSpace + { + get + { + fixed (IntPtr *storage = &values [4]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicUsesWideGamutColorSpace", storage); + } + } + + [Field ("AVMediaCharacteristicIsMainProgramContent", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicIsMainProgramContent + { + get + { + fixed (IntPtr *storage = &values [5]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicIsMainProgramContent", storage); + } + } + + [Field ("AVMediaCharacteristicIsAuxiliaryContent", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicIsAuxiliaryContent + { + get + { + fixed (IntPtr *storage = &values [6]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicIsAuxiliaryContent", storage); + } + } + + [Field ("AVMediaCharacteristicContainsOnlyForcedSubtitles", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicContainsOnlyForcedSubtitles + { + get + { + fixed (IntPtr *storage = &values [7]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicContainsOnlyForcedSubtitles", storage); + } + } + + [Field ("AVMediaCharacteristicTranscribesSpokenDialogForAccessibility", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicTranscribesSpokenDialogForAccessibility + { + get + { + fixed (IntPtr *storage = &values [8]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicTranscribesSpokenDialogForAccessibility", storage); + } + } + + [Field ("AVMediaCharacteristicDescribesMusicAndSoundForAccessibility", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicDescribesMusicAndSoundForAccessibility + { + get + { + fixed (IntPtr *storage = &values [9]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicDescribesMusicAndSoundForAccessibility", storage); + } + } + + [Field ("AVMediaCharacteristicDescribesVideoForAccessibility", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicDescribesVideoForAccessibility + { + get + { + fixed (IntPtr *storage = &values [10]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicDescribesVideoForAccessibility", storage); + } + } + + [Field ("AVMediaCharacteristicEasyToRead", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicEasyToRead + { + get + { + fixed (IntPtr *storage = &values [11]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicEasyToRead", storage); + } + } + + [Field ("AVMediaCharacteristicLanguageTranslation", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicLanguageTranslation + { + get + { + fixed (IntPtr *storage = &values [12]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicLanguageTranslation", storage); + } + } + + [Field ("AVMediaCharacteristicDubbedTranslation", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicDubbedTranslation + { + get + { + fixed (IntPtr *storage = &values [13]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicDubbedTranslation", storage); + } + } + + [Field ("AVMediaCharacteristicVoiceOverTranslation", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicVoiceOverTranslation + { + get + { + fixed (IntPtr *storage = &values [14]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicVoiceOverTranslation", storage); + } + } + + [Field ("AVMediaCharacteristicIsOriginalContent", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicIsOriginalContent + { + get + { + fixed (IntPtr *storage = &values [15]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicIsOriginalContent", storage); + } + } + + [Field ("AVMediaCharacteristicContainsHDRVideo", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicContainsHDRVideo + { + get + { + fixed (IntPtr *storage = &values [16]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicContainsHDRVideo", storage); + } + } + + [Field ("AVMediaCharacteristicContainsAlphaChannel", "AVFoundation")] + internal unsafe static IntPtr AVMediaCharacteristicContainsAlphaChannel + { + get + { + fixed (IntPtr *storage = &values [17]) + return Dlfcn.CachePointer (Libraries.AVFoundation.Handle, "AVMediaCharacteristicContainsAlphaChannel", storage); + } + } + + public static NSString? GetConstant (this AVMediaCharacteristics self) + { + IntPtr ptr = IntPtr.Zero; + switch ((int) self) + { + case 0: // AVMediaCharacteristicVisual + ptr = AVMediaCharacteristicVisual; + break; + case 1: // AVMediaCharacteristicAudible + ptr = AVMediaCharacteristicAudible; + break; + case 2: // AVMediaCharacteristicLegible + ptr = AVMediaCharacteristicLegible; + break; + case 3: // AVMediaCharacteristicFrameBased + ptr = AVMediaCharacteristicFrameBased; + break; + case 4: // AVMediaCharacteristicUsesWideGamutColorSpace + ptr = AVMediaCharacteristicUsesWideGamutColorSpace; + break; + case 5: // AVMediaCharacteristicIsMainProgramContent + ptr = AVMediaCharacteristicIsMainProgramContent; + break; + case 6: // AVMediaCharacteristicIsAuxiliaryContent + ptr = AVMediaCharacteristicIsAuxiliaryContent; + break; + case 7: // AVMediaCharacteristicContainsOnlyForcedSubtitles + ptr = AVMediaCharacteristicContainsOnlyForcedSubtitles; + break; + case 8: // AVMediaCharacteristicTranscribesSpokenDialogForAccessibility + ptr = AVMediaCharacteristicTranscribesSpokenDialogForAccessibility; + break; + case 9: // AVMediaCharacteristicDescribesMusicAndSoundForAccessibility + ptr = AVMediaCharacteristicDescribesMusicAndSoundForAccessibility; + break; + case 10: // AVMediaCharacteristicDescribesVideoForAccessibility + ptr = AVMediaCharacteristicDescribesVideoForAccessibility; + break; + case 11: // AVMediaCharacteristicEasyToRead + ptr = AVMediaCharacteristicEasyToRead; + break; + case 12: // AVMediaCharacteristicLanguageTranslation + ptr = AVMediaCharacteristicLanguageTranslation; + break; + case 13: // AVMediaCharacteristicDubbedTranslation + ptr = AVMediaCharacteristicDubbedTranslation; + break; + case 14: // AVMediaCharacteristicVoiceOverTranslation + ptr = AVMediaCharacteristicVoiceOverTranslation; + break; + case 15: // AVMediaCharacteristicIsOriginalContent + ptr = AVMediaCharacteristicIsOriginalContent; + break; + case 16: // AVMediaCharacteristicContainsHDRVideo + ptr = AVMediaCharacteristicContainsHDRVideo; + break; + case 17: // AVMediaCharacteristicContainsAlphaChannel + ptr = AVMediaCharacteristicContainsAlphaChannel; + break; + } + return (NSString?) Runtime.GetNSObject (ptr); + } + + public static AVMediaCharacteristics GetValue (NSString constant) + { + if (constant is null) + throw new ArgumentNullException (nameof (constant)); + if (constant.IsEqualTo (AVMediaCharacteristicVisual)) + return AVMediaCharacteristics.Visual; + if (constant.IsEqualTo (AVMediaCharacteristicAudible)) + return AVMediaCharacteristics.Audible; + if (constant.IsEqualTo (AVMediaCharacteristicLegible)) + return AVMediaCharacteristics.Legible; + if (constant.IsEqualTo (AVMediaCharacteristicFrameBased)) + return AVMediaCharacteristics.FrameBased; + if (constant.IsEqualTo (AVMediaCharacteristicUsesWideGamutColorSpace)) + return AVMediaCharacteristics.UsesWideGamutColorSpace; + if (constant.IsEqualTo (AVMediaCharacteristicIsMainProgramContent)) + return AVMediaCharacteristics.IsMainProgramContent; + if (constant.IsEqualTo (AVMediaCharacteristicIsAuxiliaryContent)) + return AVMediaCharacteristics.IsAuxiliaryContent; + if (constant.IsEqualTo (AVMediaCharacteristicContainsOnlyForcedSubtitles)) + return AVMediaCharacteristics.ContainsOnlyForcedSubtitles; + if (constant.IsEqualTo (AVMediaCharacteristicTranscribesSpokenDialogForAccessibility)) + return AVMediaCharacteristics.TranscribesSpokenDialogForAccessibility; + if (constant.IsEqualTo (AVMediaCharacteristicDescribesMusicAndSoundForAccessibility)) + return AVMediaCharacteristics.DescribesMusicAndSoundForAccessibility; + if (constant.IsEqualTo (AVMediaCharacteristicDescribesVideoForAccessibility)) + return AVMediaCharacteristics.DescribesVideoForAccessibility; + if (constant.IsEqualTo (AVMediaCharacteristicEasyToRead)) + return AVMediaCharacteristics.EasyToRead; + if (constant.IsEqualTo (AVMediaCharacteristicLanguageTranslation)) + return AVMediaCharacteristics.LanguageTranslation; + if (constant.IsEqualTo (AVMediaCharacteristicDubbedTranslation)) + return AVMediaCharacteristics.DubbedTranslation; + if (constant.IsEqualTo (AVMediaCharacteristicVoiceOverTranslation)) + return AVMediaCharacteristics.VoiceOverTranslation; + if (constant.IsEqualTo (AVMediaCharacteristicIsOriginalContent)) + return AVMediaCharacteristics.IsOriginalContent; + if (constant.IsEqualTo (AVMediaCharacteristicContainsHDRVideo)) + return AVMediaCharacteristics.ContainsHdrVideo; + if (constant.IsEqualTo (AVMediaCharacteristicContainsAlphaChannel)) + return AVMediaCharacteristics.ContainsAlphaChannel; + throw new NotSupportedException ($"{constant} has no associated enum value on this platform."); + } + + internal static NSString?[]? ToConstantArray (this AVMediaCharacteristics[]? values) + { + if (values is null) + return null; + var rv = new global::System.Collections.Generic.List (); + for (var i = 0; i < values.Length; i++) { + var value = values [i]; + rv.Add (value.GetConstant ()); + } + return rv.ToArray (); + } + + internal static AVMediaCharacteristics[]? ToEnumArray (this NSString[]? values) + { + if (values is null) + return null; + var rv = new global::System.Collections.Generic.List (); + for (var i = 0; i < values.Length; i++) { + var value = values [i]; + rv.Add (GetValue (value)); + } + return rv.ToArray (); + } +} diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/SmartEnumTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/SmartEnumTests.cs new file mode 100644 index 00000000000..25320b39e49 --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/SmartEnumTests.cs @@ -0,0 +1,46 @@ +using System.Collections; +using System.Collections.Generic; +using Xunit; +using Xamarin.Utils; + +namespace Microsoft.Macios.Generator.Tests.SmartEnum; + + +/// +/// Test all the field generation code. +/// +public class SmartEnumTests : BaseGeneratorTestClass +{ + public class TestDataGenerator : BaseTestDataGenerator, IEnumerable + { + readonly List<(ApplePlatform Platform, string ClassName, string BindingFile, string OutputFile)> _data = new() + { + (ApplePlatform.iOS, "AVCaptureDeviceTypeExtensions", "AVCaptureDeviceTypeEnum.cs", "ExpectedAVCaptureDeviceTypeEnum.cs" ), + (ApplePlatform.iOS, "AVCaptureSystemPressureLevelExtensions", "AVCaptureSystemPressureLevel.cs", "ExpectedAVCaptureSystemPressureLevel.cs" ), + (ApplePlatform.iOS, "AVMediaCharacteristicsExtensions", "AVMediaCharacteristics.cs", "ExpectediOSAVMediaCharacteristics.cs" ), + (ApplePlatform.MacOSX, "AVMediaCharacteristicsExtensions", "AVMediaCharacteristics.cs", "ExpectedMacOSAVMediaCharacteristics.cs" ), + (ApplePlatform.MacOSX, "CustomLibraryEnumExtensions", "CustomLibraryEnum.cs", "ExpectedCustomLibraryEnum.cs" ), + }; + + public IEnumerator GetEnumerator() + { + foreach (var testData in _data) + { + yield return [ + testData.Platform, + testData.ClassName, + testData.BindingFile, + ReadFileAsString(testData.BindingFile), + testData.OutputFile, + ReadFileAsString(testData.OutputFile)]; + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + [Theory] + [ClassData(typeof(TestDataGenerator))] + public void ExtensionGenerationTests (ApplePlatform platform, string className, string inputFileName, string inputText, string outputFileName, string expectedOutputText) + => CompareGeneratedCode(platform, className, inputFileName, inputText, outputFileName, expectedOutputText); +}