Skip to content

Commit

Permalink
[Rgen] Implement SmartEnums code emittion.
Browse files Browse the repository at this point in the history
This change brings the following:

1. We now generate the Extension classes for the SmartEnums.
2. Bumped the roslyn version, the one we were using had a bug when using
   dir separators in the name hint for a generated file.
3. Generated code is added under the expected directory for bgen to
   allow the API diff.
  • Loading branch information
mandel-macaque committed Oct 18, 2024
1 parent dbbdf51 commit d0c8564
Show file tree
Hide file tree
Showing 46 changed files with 2,413 additions and 135 deletions.
1 change: 1 addition & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)) \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\..\..\tools\common\ApplePlatform.cs">
<Link>external\ApplePlatform.cs</Link>
</Compile>
<Compile Include="..\..\..\tools\common\SdkVersions.cs">
<Link>external\SdkVersions.cs</Link>
</Compile>
<Compile Include="..\..\..\tools\common\TargetFramework.cs">
<Link>external\TargetFramework.cs</Link>
</Compile>
<Compile Include="../../../tools/common/Frameworks.cs" >
<Link>external\Frameworks.cs</Link>
</Compile>
<Compile Include="..\..\bgen\PlatformName.cs" >
<Link>external\PlatformName.cs</Link>
</Compile>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Microsoft.Macios.Binding.Common\Microsoft.Macios.Binding.Common.csproj" OutputItemType="Analyzer" />
<ProjectReference Include="..\Microsoft.Macios.Generator\Microsoft.Macios.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\Microsoft.Macios.Bindings.Analyzer\Microsoft.Macios.Bindings.Analyzer.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>
Expand All @@ -15,12 +16,6 @@
<Compile Include="..\..\..\src\ObjCBindings\BindingTypeAttribute.cs" >
<Link>external\BindginTypeAttribute.cs</Link>
</Compile>
<Compile Include="..\..\..\src\bgen\Attributes.cs" >
<Link>external\Attributes.cs</Link>
</Compile>
<Compile Include="..\..\..\src\bgen\PlatformName.cs" >
<Link>external\PlatformName.cs</Link>
</Compile>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@
### New Rules

| Rule ID | Category | Severity | Notes |
|---------|----------|----------|------------------------------------------------------|
| RBI0001 | Usage | Error | Binding types should be declared as partial classes. |
|---------|----------|----------|------------------------------------------------------|
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
### New Rules

| Rule ID | Category | Severity | Notes |
|---------|----------|----------|-------|
| 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. |
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -12,23 +13,20 @@ namespace Microsoft.Macios.Bindings.Analyzer;
/// pattern.
/// </summary>
[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<ClassDeclarationSyntax> {

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

public override ImmutableArray<DiagnosticDescriptor> 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<DiagnosticDescriptor> SupportedDiagnostics { get; } = [RBI0001];

public override void Initialize (AnalysisContext context)
{
Expand All @@ -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<Diagnostic> 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;
}
}
}
}
17 changes: 17 additions & 0 deletions src/rgen/Microsoft.Macios.Bindings.Analyzer/DiagnosticInfo.cs
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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<T> (this IBindingTypeAnalyzer<T> 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;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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,
};

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Microsoft.Macios.Bindings.Analyzer;

/// <summary>
/// Interface to be implemented by those analyzer that will be looking at BindingTypes.
/// </summary>
public interface IBindingTypeAnalyzer<T> where T : BaseTypeDeclarationSyntax {
ImmutableArray<Diagnostic> Analyze (PlatformName platformName, T declarationNode, INamedTypeSymbol symbol);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.0"/>
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.9.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.2"/>
</ItemGroup>

<ItemGroup>
Expand All @@ -43,4 +44,23 @@
</Compile>
</ItemGroup>

<ItemGroup>
<Compile Include="../Microsoft.Macios.Generator/AttributesNames.cs" >
<Link>external\AttributesNames.cs</Link>
</Compile>
<Compile Include="../Microsoft.Macios.Generator/Attributes/FieldData.cs" >
<Link>external\FieldData.cs</Link>
</Compile>
<Compile Include="../Microsoft.Macios.Generator/Extensions/CompilationExtensions.cs" >
<Link>Extensions\CompilationExtensions.cs</Link>
</Compile>
<Compile Include="../Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.cs" >
<Link>Extensions\TypeSymbolExtensions.cs</Link>
</Compile>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Microsoft.Macios.Binding.Common\Microsoft.Macios.Binding.Common.csproj" />
</ItemGroup>

</Project>
Loading

0 comments on commit d0c8564

Please sign in to comment.