diff --git a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs
index 7903ace5ef4a..911024e53715 100644
--- a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs
+++ b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs
@@ -22,7 +22,7 @@ namespace Microsoft.Macios.Generator;
///
[Generator]
public class BindingSourceGeneratorGenerator : IIncrementalGenerator {
- static readonly CodeChangesEqualityComparer equalityComparer = new ();
+ static readonly DeclarationCodeChangesEqualityComparer equalityComparer = new ();
///
public void Initialize (IncrementalGeneratorInitializationContext context)
@@ -42,7 +42,7 @@ public void Initialize (IncrementalGeneratorInitializationContext context)
.CreateSyntaxProvider (static (node, _) => IsValidNode (node),
static (ctx, _) => GetChangesForSourceGen (ctx))
.Where (tuple => tuple.BindingAttributeFound)
- .Select (static (tuple, _) => tuple.Changes)
+ .Select (static (tuple, _) => (tuple.Declaration, tuple.Changes))
.WithComparer (equalityComparer);
context.RegisterSourceOutput (context.CompilationProvider.Combine (provider.Collect ()),
@@ -59,7 +59,8 @@ public void Initialize (IncrementalGeneratorInitializationContext context)
_ => false,
};
- static (CodeChanges Changes, bool BindingAttributeFound) GetChangesForSourceGen (GeneratorSyntaxContext context)
+ static (BaseTypeDeclarationSyntax Declaration, CodeChanges Changes, bool BindingAttributeFound)
+ GetChangesForSourceGen (GeneratorSyntaxContext context)
{
// we do know that the context node has to be one of the base type declarations
var declarationSyntax = Unsafe.As (context.Node);
@@ -68,30 +69,32 @@ public void Initialize (IncrementalGeneratorInitializationContext context)
bool isBindingType = declarationSyntax.HasAttribute (context.SemanticModel, AttributesNames.BindingAttribute);
if (!isBindingType) {
- // return an empty data + false
- return (default, false);
+ // return empty data + false
+ return (declarationSyntax, default, false);
}
var codeChanges = CodeChanges.FromDeclaration (declarationSyntax, context.SemanticModel);
// if code changes are null, return the default value and a false to later ignore the change
- return codeChanges is not null ? (codeChanges.Value, isBindingType) : (default, false);
+ return codeChanges is not null
+ ? (declarationSyntax, codeChanges.Value, isBindingType)
+ : (declarationSyntax, default, false);
}
static void GenerateCode (SourceProductionContext context, Compilation compilation,
- ImmutableArray changesList)
+ ImmutableArray<(BaseTypeDeclarationSyntax Declaration, CodeChanges Changes)> changesList)
{
- // the process is as follows, get all the changes we have received from the incremental generator,
- // loop over them and based on the CodeChange.BindingType we are going to build the symbol context
+ // The process is as follows, get all the changes we have received from the incremental generator,
+ // loop over them, and based on the CodeChange.BindingType we are going to build the symbol context
// and emitter. Those are later used to generate the code.
//
// Once all the enums, classes and interfaces have been processed, we will use the data collected
// in the RootBindingContext to generate the library and trampoline code.
var rootContext = new RootBindingContext (compilation);
- foreach (var change in changesList) {
- var semanticModel = compilation.GetSemanticModel (change.SymbolDeclaration.SyntaxTree);
+ foreach (var (declaration, change) in changesList) {
+ var semanticModel = compilation.GetSemanticModel (declaration.SyntaxTree);
// This is a bug in the roslyn analyzer for roslyn generator https://github.com/dotnet/roslyn-analyzers/issues/7436
#pragma warning disable RS1039
- if (semanticModel.GetDeclaredSymbol (change.SymbolDeclaration) is not INamedTypeSymbol namedTypeSymbol)
+ if (semanticModel.GetDeclaredSymbol (declaration) is not INamedTypeSymbol namedTypeSymbol)
#pragma warning restore RS1039
continue;
@@ -100,10 +103,10 @@ static void GenerateCode (SourceProductionContext context, Compilation compilati
sb.WriteHeader ();
if (EmitterFactory.TryCreate (change, rootContext, semanticModel, namedTypeSymbol, sb, out var emitter)) {
// write the using statements
- CollectUsingStatements (change.SymbolDeclaration.SyntaxTree, sb, emitter);
+ CollectUsingStatements (declaration.SyntaxTree, sb, emitter);
if (emitter.TryEmit (out var diagnostics)) {
- // only add file when we do generate code
+ // only add a file when we do generate code
var code = sb.ToString ();
context.AddSource ($"{Path.Combine (emitter.SymbolNamespace, emitter.SymbolName)}.g.cs",
SourceText.From (code, Encoding.UTF8));
@@ -112,10 +115,12 @@ static void GenerateCode (SourceProductionContext context, Compilation compilati
context.ReportDiagnostics (diagnostics);
}
} else {
- // we don't have a emitter for this type, so we can't generate the code, add a diagnostic letting the
+ // we don't have an emitter for this type, so we can't generate the code, add a diagnostic letting the
// user we do not support what they are trying to do
- context.ReportDiagnostic (Diagnostic.Create (Diagnostics.RBI0000, // An unexpected error ocurred while processing '{0}'. Please fill a bug report at https://github.com/xamarin/xamarin-macios/issues/new.
- change.SymbolDeclaration.GetLocation (),
+ context.ReportDiagnostic (Diagnostic.Create (
+ Diagnostics
+ .RBI0000, // An unexpected error ocurred while processing '{0}'. Please fill a bug report at https://github.com/xamarin/xamarin-macios/issues/new.
+ declaration.GetLocation (),
namedTypeSymbol.ToDisplayString ().Trim ()));
}
}
@@ -138,7 +143,7 @@ static void GenerateLibraryCode (SourceProductionContext context, RootBindingCon
var emitter = new LibraryEmitter (rootContext, sb);
if (emitter.TryEmit (out var diagnostics)) {
- // only add file when we do generate code
+ // only add a file when we do generate code
var code = sb.ToString ();
context.AddSource ($"{Path.Combine (emitter.SymbolNamespace, emitter.SymbolName)}.g.cs",
SourceText.From (code, Encoding.UTF8));
diff --git a/src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs
index 4a24c1f4a601..e283d9b7e619 100644
--- a/src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs
+++ b/src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs
@@ -7,8 +7,8 @@ class ClassBindingContext : SymbolBindingContext {
public string RegisterName { get; init; }
public ClassBindingContext (RootBindingContext context, SemanticModel semanticModel,
- INamedTypeSymbol symbol, ClassDeclarationSyntax declarationSyntax)
- : base (context, semanticModel, symbol, declarationSyntax)
+ INamedTypeSymbol symbol)
+ : base (context, semanticModel, symbol)
{
RegisterName =
symbol.Name; //TODO: placeholder -> should this be extracted from the BindingTypeAttribute
diff --git a/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs
index 365281bc640c..e39caf9a7449 100644
--- a/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs
+++ b/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs
@@ -12,7 +12,6 @@ class SymbolBindingContext {
public SemanticModel SemanticModel { get; }
public INamedTypeSymbol Symbol { get; }
- public BaseTypeDeclarationSyntax DeclarationSyntax { get; }
public string Namespace => Symbol.ContainingNamespace.ToDisplayString ();
public string SymbolName => Symbol.Name;
@@ -20,12 +19,11 @@ class SymbolBindingContext {
public SymbolAvailability SymbolAvailability { get; }
public SymbolBindingContext (RootBindingContext rootBindingContext,
- SemanticModel semanticModel, INamedTypeSymbol symbol, BaseTypeDeclarationSyntax declarationSyntax)
+ SemanticModel semanticModel, INamedTypeSymbol symbol)
{
RootBindingContext = rootBindingContext;
SemanticModel = semanticModel;
Symbol = symbol;
- DeclarationSyntax = declarationSyntax;
SymbolAvailability = symbol.GetSupportedPlatforms ();
}
}
diff --git a/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChanges.cs b/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChanges.cs
index dc16fb990803..55d85fc0a9c8 100644
--- a/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChanges.cs
+++ b/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChanges.cs
@@ -1,5 +1,7 @@
using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -22,40 +24,60 @@ readonly struct CodeChanges {
///
public string FullyQualifiedSymbol { get; }
- ///
- /// Base symbol declaration that triggered the code changes.
- ///
- public BaseTypeDeclarationSyntax SymbolDeclaration { get; }
-
///
/// Changes to the attributes of the symbol.
///
public ImmutableArray Attributes { get; init; } = [];
+ readonly ImmutableArray enumMembers = [];
+
///
/// Changes to the enum members of the symbol.
///
- public ImmutableArray EnumMembers { get; init; } = [];
+ public ImmutableArray EnumMembers {
+ get => enumMembers;
+ init => enumMembers = value;
+ }
+
+ readonly ImmutableArray properties = [];
///
/// Changes to the properties of the symbol.
///
- public ImmutableArray Properties { get; init; } = [];
+ public ImmutableArray Properties {
+ get => properties;
+ init => properties = value;
+ }
+
+ readonly ImmutableArray constructors = [];
///
/// Changes to the constructors of the symbol.
///
- public ImmutableArray Constructors { get; init; } = [];
+ public ImmutableArray Constructors {
+ get => constructors;
+ init => constructors = value;
+ }
+
+ readonly ImmutableArray events = [];
///
/// Changes to the events of the symbol.
///
- public ImmutableArray Events { get; init; } = [];
+ public ImmutableArray Events {
+ get => events;
+ init => events = value;
+ }
+
+ readonly ImmutableArray methods = [];
///
/// Changes to the methods of a symbol.
///
- public ImmutableArray Methods { get; init; } = [];
+ public ImmutableArray Methods {
+ get => methods;
+ init => methods = value;
+ }
///
/// Decide if an enum value should be ignored as a change.
@@ -110,20 +132,43 @@ internal static bool Skip (MethodDeclarationSyntax methodDeclarationSyntax, Sema
if (methodDeclarationSyntax.Modifiers.Any (SyntaxKind.PartialKeyword)) {
return !methodDeclarationSyntax.HasAttribute (semanticModel, AttributesNames.ExportMethodAttribute);
}
+
return true;
}
+ delegate bool SkipDelegate (T declarationSyntax, SemanticModel semanticModel);
+
+ delegate bool TryCreateDelegate (T declaration, SemanticModel semanticModel,
+ [NotNullWhen (true)] out TR? change)
+ where T : MemberDeclarationSyntax
+ where TR : struct;
+
+ static void GetMembers (TypeDeclarationSyntax baseDeclarationSyntax, SemanticModel semanticModel,
+ SkipDelegate skip, TryCreateDelegate tryCreate, out ImmutableArray
members)
+ where T : MemberDeclarationSyntax
+ where TR : struct
+ {
+ var bucket = ImmutableArray.CreateBuilder
();
+ var declarations = baseDeclarationSyntax.Members.OfType ();
+ foreach (var declaration in declarations) {
+ if (skip (declaration, semanticModel))
+ continue;
+ if (tryCreate (declaration, semanticModel, out var change))
+ bucket.Add (change.Value);
+ }
+
+ members = bucket.ToImmutable ();
+ }
+
///
/// Internal constructor added for testing purposes.
///
/// The type of binding for the given code changes.
/// The fully qualified name of the symbol.
- /// The symbol declaration syntax.
- internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol, BaseTypeDeclarationSyntax declaration)
+ internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol)
{
BindingType = bindingType;
FullyQualifiedSymbol = fullyQualifiedSymbol;
- SymbolDeclaration = declaration;
}
///
@@ -135,7 +180,6 @@ internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol, Base
{
BindingType = BindingType.SmartEnum;
FullyQualifiedSymbol = enumDeclaration.GetFullyQualifiedIdentifier ();
- SymbolDeclaration = enumDeclaration;
Attributes = enumDeclaration.GetAttributeCodeChanges (semanticModel);
var bucket = ImmutableArray.CreateBuilder ();
// loop over the fields and add those that contain a FieldAttribute
@@ -158,53 +202,19 @@ internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol, Base
/// The semantic model of the compilation.
CodeChanges (ClassDeclarationSyntax classDeclaration, SemanticModel semanticModel)
{
+ BindingType = BindingType.Class;
FullyQualifiedSymbol = classDeclaration.GetFullyQualifiedIdentifier ();
- SymbolDeclaration = classDeclaration;
- EnumMembers = []; // always empty since classes dot not have enum values
- Attributes = [];
-
- var properties = ImmutableArray.CreateBuilder ();
- var propertyDeclarations = classDeclaration.Members.OfType ();
- foreach (var declaration in propertyDeclarations) {
- if (Skip (declaration, semanticModel))
- continue;
- if (Property.TryCreate (declaration, semanticModel, out var change))
- properties.Add (change.Value);
- }
-
- Properties = properties.ToImmutable ();
+ Attributes = classDeclaration.GetAttributeCodeChanges (semanticModel);
- var constructors = ImmutableArray.CreateBuilder ();
- var constructorDeclarations = classDeclaration.Members.OfType ();
- foreach (var declaration in constructorDeclarations) {
- if (Skip (declaration, semanticModel))
- continue;
- if (Constructor.TryCreate (declaration, semanticModel, out var change))
- constructors.Add (change.Value);
- }
-
- Constructors = constructors.ToImmutable ();
-
- var events = ImmutableArray.CreateBuilder ();
- var eventDeclarations = classDeclaration.Members.OfType ();
- foreach (var declaration in eventDeclarations) {
- if (Skip (declaration, semanticModel))
- continue;
- if (Event.TryCreate (declaration, semanticModel, out var change))
- events.Add (change.Value);
- }
-
- Events = events.ToImmutable ();
-
- var methods = ImmutableArray.CreateBuilder ();
- var methodDeclarations = classDeclaration.Members.OfType ();
- foreach (MethodDeclarationSyntax declaration in methodDeclarations) {
- if (Skip (declaration, semanticModel))
- continue;
- if (Method.TryCreate (declaration, semanticModel, out var change))
- methods.Add (change.Value);
- }
- Methods = methods.ToImmutable ();
+ // use the generic method to get the members, we are using an out param to try an minimize the number of times
+ // the value types are copied
+ GetMembers (classDeclaration, semanticModel, Skip,
+ Constructor.TryCreate, out constructors);
+ GetMembers (classDeclaration, semanticModel, Skip, Property.TryCreate,
+ out properties);
+ GetMembers (classDeclaration, semanticModel, Skip, Event.TryCreate, out events);
+ GetMembers (classDeclaration, semanticModel, Skip, Method.TryCreate,
+ out methods);
}
///
@@ -214,11 +224,17 @@ internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol, Base
/// The semantic model of the compilation.
CodeChanges (InterfaceDeclarationSyntax interfaceDeclaration, SemanticModel semanticModel)
{
+ BindingType = BindingType.Protocol;
FullyQualifiedSymbol = interfaceDeclaration.GetFullyQualifiedIdentifier ();
- SymbolDeclaration = interfaceDeclaration;
- // TODO: to be implemented once we add protocol support
- EnumMembers = [];
- Attributes = [];
+ Attributes = interfaceDeclaration.GetAttributeCodeChanges (semanticModel);
+ // we do not init the constructors, we use the default empty array
+
+ GetMembers (interfaceDeclaration, semanticModel, Skip, Property.TryCreate,
+ out properties);
+ GetMembers (interfaceDeclaration, semanticModel, Skip, Event.TryCreate,
+ out events);
+ GetMembers (interfaceDeclaration, semanticModel, Skip, Method.TryCreate,
+ out methods);
}
///
@@ -237,4 +253,26 @@ internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol, Base
ClassDeclarationSyntax classDeclarationSyntax => new CodeChanges (classDeclarationSyntax, semanticModel),
_ => null
};
+
+ ///
+ public override string ToString ()
+ {
+ var sb = new StringBuilder ("Changes: {");
+ sb.Append ($"BindingType: {BindingType}, ");
+ sb.Append ($"FullyQualifiedSymbol: {FullyQualifiedSymbol}, ");
+ sb.Append ("Attributes: [");
+ sb.AppendJoin (", ", Attributes);
+ sb.Append ("], EnumMembers: [");
+ sb.AppendJoin (", ", EnumMembers);
+ sb.Append ("], Constructors: [");
+ sb.AppendJoin (", ", Constructors);
+ sb.Append ("], Properties: [");
+ sb.AppendJoin (", ", Properties);
+ sb.Append ("], Methods: [");
+ sb.AppendJoin (", ", Methods);
+ sb.Append ("], Events: [");
+ sb.AppendJoin (", ", Events);
+ sb.Append ('}');
+ return sb.ToString ();
+ }
}
diff --git a/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChangesEqualityComparer.cs b/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChangesEqualityComparer.cs
index 489b2fd0509a..e4dd817fec43 100644
--- a/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChangesEqualityComparer.cs
+++ b/src/rgen/Microsoft.Macios.Generator/DataModel/CodeChangesEqualityComparer.cs
@@ -1,9 +1,23 @@
using System;
using System.Collections.Generic;
-using System.Linq;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Microsoft.Macios.Generator.DataModel;
+class DeclarationCodeChangesEqualityComparer : IEqualityComparer<(BaseTypeDeclarationSyntax Declaration, CodeChanges
+ Changes)> {
+ readonly CodeChangesEqualityComparer comparer = new ();
+
+ public bool Equals ((BaseTypeDeclarationSyntax Declaration, CodeChanges Changes) x,
+ (BaseTypeDeclarationSyntax Declaration, CodeChanges Changes) y)
+ {
+ return comparer.Equals (x.Changes, y.Changes);
+ }
+
+ public int GetHashCode ((BaseTypeDeclarationSyntax Declaration, CodeChanges Changes) obj)
+ => comparer.GetHashCode (obj.Changes);
+}
+
///
/// Custom code changes comparer used for the Roslyn code generation to invalidate caching.
///
@@ -23,8 +37,6 @@ public bool Equals (CodeChanges x, CodeChanges y)
return false;
if (x.BindingType != y.BindingType)
return false;
- if (x.SymbolDeclaration.GetType () != y.SymbolDeclaration.GetType ())
- return false;
if (x.Attributes.Length != y.Attributes.Length)
return false;
if (x.EnumMembers.Length != y.EnumMembers.Length)
diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs
index c6c01368ffed..6b2d0780a0c7 100644
--- a/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs
+++ b/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs
@@ -14,19 +14,19 @@ public static bool TryCreate (CodeChanges changes, RootBindingContext context, S
INamedTypeSymbol symbol, TabbedStringBuilder builder,
[NotNullWhen (true)] out ICodeEmitter? emitter)
{
- switch (changes.SymbolDeclaration) {
- case ClassDeclarationSyntax classDeclarationSyntax: {
- var ctx = new ClassBindingContext (context, semanticModel, symbol, classDeclarationSyntax);
+ switch (changes.BindingType) {
+ case BindingType.Class: {
+ var ctx = new ClassBindingContext (context, semanticModel, symbol);
emitter = new ClassEmitter (ctx, builder);
break;
}
- case EnumDeclarationSyntax enumDeclarationSyntax: {
- var ctx = new SymbolBindingContext (context, semanticModel, symbol, enumDeclarationSyntax);
+ case BindingType.SmartEnum: {
+ var ctx = new SymbolBindingContext (context, semanticModel, symbol);
emitter = new EnumEmitter (ctx, builder);
break;
}
- case InterfaceDeclarationSyntax interfaceDeclarationSyntax: {
- var ctx = new SymbolBindingContext (context, semanticModel, symbol, interfaceDeclarationSyntax);
+ case BindingType.Protocol: {
+ var ctx = new SymbolBindingContext (context, semanticModel, symbol);
emitter = new InterfaceEmitter (ctx, builder);
break;
}
diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/ClassCodeChangesTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/ClassCodeChangesTests.cs
new file mode 100644
index 000000000000..befd098227bd
--- /dev/null
+++ b/tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/ClassCodeChangesTests.cs
@@ -0,0 +1,530 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.Macios.Generator.DataModel;
+using Xamarin.Tests;
+using Xamarin.Utils;
+using Xunit;
+
+namespace Microsoft.Macios.Generator.Tests.DataModel;
+
+public class ClassCodeChangesTests : BaseGeneratorTestClass {
+ readonly CodeChangesEqualityComparer comparer = new ();
+
+ class TestDataCodeChangesFromClassDeclaration : IEnumerable