Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into remove-watchos-part-1
Browse files Browse the repository at this point in the history
  • Loading branch information
rolfbjarne committed Oct 16, 2024
2 parents 12045db + 0f35909 commit 33ac70d
Show file tree
Hide file tree
Showing 50 changed files with 1,167 additions and 155 deletions.
7 changes: 7 additions & 0 deletions docs/preview-apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,10 @@ We've tentatively set .NET 11 as the release when we'll stop marking FSKit as pr
The diagnostic id for FSKit is APL0002.

[1]: https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.experimentalattribute?view=net-8.0

## Rgen (APL0003)

Rgen is the new Roslyn codegenerator based binding tool. The tool is underdevelopment and its API is open to change until
a stable release is announced.

The diagnostic id for Rgen is APL0003.
6 changes: 4 additions & 2 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ DOTNET_WARNINGS_TO_FIX = -nowarn:$(CSC_WARNINGS_TO_FIX)
DOTNET_CORE_WARNINGS_TO_FIX = -nowarn:$(CSC_WARNINGS_TO_FIX),$(BGEN_WARNINGS_TO_FIX)

include ./Makefile.generator
include ./Makefile.rgenerator
include ./generator-diff.mk

SHARED_RESX = $(TOP)/tools/mtouch/Errors.resx
Expand Down Expand Up @@ -355,7 +356,7 @@ $($(2)_DOTNET_BUILD_DIR)/core-$(3).dll: $($(2)_DOTNET_CORE_SOURCES) frameworks.s
$($(2)_DOTNET_BUILD_DIR)/$(3)-generated-sources: $(DOTNET_GENERATOR) $($(2)_DOTNET_APIS) $($(2)_DOTNET_BUILD_DIR)/core-$(3).dll $(DOTNET_BINDING_ATTRIBUTES) $($(2)_DOTNET_BUILD_DIR)/$(3).rsp | $($(2)_DOTNET_BUILD_DIR)/generated-sources
$$(Q_DOTNET_GEN) $$< @$($(2)_DOTNET_BUILD_DIR)/$(3).rsp

$($(2)_DOTNET_BUILD_DIR)/$(3).rsp: Makefile Makefile.generator frameworks.sources $(DOTNET_COMPILER) | $($(2)_DOTNET_BUILD_DIR)
$($(2)_DOTNET_BUILD_DIR)/$(3).rsp: Makefile Makefile.generator Makefile.rgenerator frameworks.sources $(ROSLYN_GENERATOR) $(DOTNET_COMPILER) | $($(2)_DOTNET_BUILD_DIR)
$(Q) echo \
$($(2)_GENERATOR_FLAGS) \
$(DOTNET_GENERATOR_FLAGS) \
Expand Down Expand Up @@ -450,10 +451,11 @@ $(2)_DOTNET_PLATFORM_ASSEMBLY_DIR_DEPENDENCIES = \
$($(2)_DOTNET_BUILD_DIR)/$(4) \
$($(2)_DOTNET_BUILD_DIR)/ref \

$($(2)_DOTNET_BUILD_DIR)/$(4)/Microsoft.$(1)%dll $($(2)_DOTNET_BUILD_DIR)/$(4)/Microsoft.$(1)%pdb $$($(2)_$(4)_REF_TARGET) $$($(2)_$(4)_DOC_TARGET): $$($(2)_DOTNET_PLATFORM_ASSEMBLY_DEPENDENCIES) | $$($(2)_DOTNET_PLATFORM_ASSEMBLY_DIR_DEPENDENCIES)
$($(2)_DOTNET_BUILD_DIR)/$(4)/Microsoft.$(1)%dll $($(2)_DOTNET_BUILD_DIR)/$(4)/Microsoft.$(1)%pdb $$($(2)_$(4)_REF_TARGET) $$($(2)_$(4)_DOC_TARGET): $$($(2)_DOTNET_PLATFORM_ASSEMBLY_DEPENDENCIES) $$(ROSLYN_GENERATOR) | $$($(2)_DOTNET_PLATFORM_ASSEMBLY_DIR_DEPENDENCIES)
$$(call Q_PROF_CSC,dotnet/$(4)-bit) \
$(DOTNET_CSC) \
$(DOTNET_FLAGS) \
/analyzer:$(ROSLYN_GENERATOR) \
-unsafe \
-optimize \
$$(ARGS_$(1)) \
Expand Down
8 changes: 8 additions & 0 deletions src/Makefile.rgenerator
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Roslyn code generator
ROSLYN_GENERATOR=$(DOTNET_BUILD_DIR)/common/rgen/Microsoft.Macios.Generator.dll
ROSLYN_GENERATOR_FILES := $(wildcard rgen/Microsoft.Macios.Generator/*.cs)

$(ROSLYN_GENERATOR): Makefile.rgenerator $(ROSLYN_GENERATOR_FILES)
$(Q_DOTNET_BUILD) $(DOTNET) publish rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj $(DOTNET_BUILD_VERBOSITY) /p:Configuration=Debug /p:IntermediateOutputPath=$(abspath $(DOTNET_BUILD_DIR)/IDE/obj/common/rgen)/ /p:OutputPath=$(abspath $(DOTNET_BUILD_DIR)/IDE/bin/common/rgen/)/
@mkdir -p $(dir $@)
$(Q) $(CP) -r $(DOTNET_BUILD_DIR)/IDE/bin/common/rgen/publish/* $(dir $@)
25 changes: 25 additions & 0 deletions src/ObjCBindings/BindingTypeAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;

namespace ObjCBindings {

/// <summary>
/// Attribute that indicates that a class or enum is a binding type. This attribute is used by the binding generator
/// to generate all the necessary code for the binding. The attribute can be used in a class or enum and it is required.
///
/// If the attribute is used in a class, the class must be partial otherwise the generator will fail.
/// </summary>
[Experimental ("APL0003")]
[AttributeUsage (AttributeTargets.Class | System.AttributeTargets.Enum, AllowMultiple = false)]
public class BindingTypeAttribute : Attribute {

/// <summary>
/// Indicates the name of the binding type. This is the name that will be used by the registrar to make the
/// class available in the ObjC runtime. The default value is string.Empty, in that case the generator
/// will use the name of the C# class.
/// </summary>
public string Name { get; set; } = string.Empty;
}

}
1 change: 1 addition & 0 deletions src/frameworks.sources
Original file line number Diff line number Diff line change
Expand Up @@ -2012,6 +2012,7 @@ SHARED_CORE_SOURCES = \
DotNetGlobals.cs \
MinimumVersions.cs \
MonoPInvokeCallbackAttribute.cs \
ObjCBindings/BindingTypeAttribute.cs \
ObjCRuntime/ArgumentSemantic.cs \
ObjCRuntime/BindAsAttribute.cs \
ObjCRuntime/Blocks.cs \
Expand Down
12 changes: 12 additions & 0 deletions src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Examples.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Microsoft.Macios.Bindings.Analyzer.Sample;

// If you don't see warnings, build the Analyzers Project.

[BindingType]
public class Examples {
}

[BindingType]
public class Foo {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<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>

<ItemGroup>
<Compile Include="..\..\..\src\bgen\Attributes.cs" >
<Link>external\Attributes.cs</Link>
</Compile>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Release 1.0

### 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
@@ -0,0 +1,4 @@
### New Rules

| Rule ID | Category | Severity | Notes |
|---------|----------|----------|-------|
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;

using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using SyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind;

namespace Microsoft.Macios.Bindings.Analyzer;

/// <summary>
/// Code fix provider that adds the 'partial' modifier to the class decorated with BindingTypeAttribute.
/// </summary>
[ExportCodeFixProvider (LanguageNames.CSharp, Name = nameof (BindingTypeCodeFixProvider)), Shared]
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);

// If you don't need the 'fix all' behaviour, return null.
public override FixAllProvider? GetFixAllProvider () => null;

public sealed override async Task RegisterCodeFixesAsync (CodeFixContext context)
{
var diagnostic = context.Diagnostics.Single ();
var diagnosticSpan = diagnostic.Location.SourceSpan;
var root = await context.Document.GetSyntaxRootAsync (context.CancellationToken).ConfigureAwait (false);
var diagnosticNode = root?.FindNode (diagnosticSpan);

// To get the required metadata, we should match the Node to the specific type: 'ClassDeclarationSyntax'.
if (diagnosticNode is not ClassDeclarationSyntax declaration)
return;

// Register a code action that will invoke the fix.
context.RegisterCodeFix (
CodeAction.Create (
title: Resources.RBI0001CodeFixTitle,
createChangedDocument: c => MakePartialClassAsync (context.Document, declaration, c),
equivalenceKey: nameof (Resources.RBI0001CodeFixTitle)),
diagnostic);
}

async Task<Document> MakePartialClassAsync (Document document,
ClassDeclarationSyntax classDeclarationSyntax, CancellationToken cancellationToken)
{
var partialClass = classDeclarationSyntax.AddModifiers (SyntaxFactory.Token (SyntaxKind.PartialKeyword));
var oldRoot = await document.GetSyntaxRootAsync (cancellationToken).ConfigureAwait (false);
if (oldRoot is null)
return document;

var newRoot = oldRoot.ReplaceNode (classDeclarationSyntax, partialClass);
return document.WithSyntaxRoot (newRoot);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Microsoft.Macios.Bindings.Analyzer;

/// <summary>
/// Analyzer that ensures that the types that have been declared as binding types are partial and follow the correct
/// 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";

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

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
ImmutableArray.Create (RBI0001);

public override void Initialize (AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis (GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution ();
context.RegisterSyntaxNodeAction (AnalysisContext, SyntaxKind.ClassDeclaration);
}

void AnalysisContext (SyntaxNodeAnalysisContext context)
{
// only care about classes
if (context.Node is not ClassDeclarationSyntax classDeclarationNode)
return;

var classSymbol = context.SemanticModel.GetDeclaredSymbol (classDeclarationNode);
if (classSymbol is null)
return;

var boundAttributes = classSymbol.GetAttributes ();
if (boundAttributes.Length == 0) {
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 "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;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>

<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsRoslynComponent>true</IsRoslynComponent>

<RootNamespace>Microsoft.Macios.Bindings.Analyzer</RootNamespace>
<AssemblyName>Microsoft.Macios.Bindings.Analyzer</AssemblyName>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
<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"/>
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>Microsoft.Macios.Bindings.Analyzer.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

<ItemGroup>
<EmbeddedResource Update="Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>

<ItemGroup>
<Compile Update="Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"DebugRoslynAnalyzers": {
"commandName": "DebugRoslynComponent",
"targetProject": "../Microsoft.Macios.Bindings.Analyzer.Sample/Microsoft.Macios.Bindings.Analyzer.Sample.csproj"
}
}
}
26 changes: 26 additions & 0 deletions src/rgen/Microsoft.Macios.Bindings.Analyzer/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Roslyn Analyzers Sample

Roslyn analyzer to be installed along side the Roslyn Conde generator that will help developers work on Microsoft.Macios bindings.

## Content
### Microsoft.Macios.Bindings.Analyzer

A .NET Standard project with implementations of sample analyzers and code fix providers.
**You must build this project to see the results (warnings) in the IDE.**

### Microsoft.Macios.Bindings.Analyzer.Sample
A project that references the sample analyzers. Note the parameters of `ProjectReference` in [Microsoft.Macios.Bindings.Analyzer.Sample.csproj](../Microsoft.Macios.Bindings.Analyzer.Sample/Microsoft.Macios.Bindings.Analyzer.Sample.csproj), they make sure that the project is referenced as a set of analyzers.

### Microsoft.Macios.Bindings.Analyzer.Tests
Unit tests for the sample analyzers and code fix provider. The easiest way to develop language-related features is to start with unit tests.

## How To?
### How to debug?
- Use the [launchSettings.json](Properties/launchSettings.json) profile.
- Debug tests (in VSCode).

### How can I determine which syntax nodes I should expect?
Consider installing the Roslyn syntax tree viewer plugin [Rossynt](https://plugins.jetbrains.com/plugin/16902-rossynt/).

### Learn more about wiring analyzers
The complete set of information is available at [roslyn github repo wiki](https://github.com/dotnet/roslyn/blob/main/docs/wiki/README.md).
Loading

0 comments on commit 33ac70d

Please sign in to comment.