Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RGen] Add code needed to detect changes in interface. #21704

Merged
merged 2 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace Microsoft.Macios.Generator;
/// </summary>
[Generator]
public class BindingSourceGeneratorGenerator : IIncrementalGenerator {
static readonly CodeChangesEqualityComparer equalityComparer = new ();
static readonly DeclarationCodeChangesEqualityComparer equalityComparer = new ();

/// <inheritdoc cref="IIncrementalGenerator"/>
public void Initialize (IncrementalGeneratorInitializationContext context)
Expand All @@ -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 ()),
Expand All @@ -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<BaseTypeDeclarationSyntax> (context.Node);
Expand All @@ -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<CodeChanges> 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;

Expand All @@ -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));
Expand All @@ -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 ()));
}
}
Expand All @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,18 @@ 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;

public bool IsStatic => Symbol.IsStatic;
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 ();
}
}
164 changes: 101 additions & 63 deletions src/rgen/Microsoft.Macios.Generator/DataModel/CodeChanges.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -22,40 +24,60 @@ readonly struct CodeChanges {
/// </summary>
public string FullyQualifiedSymbol { get; }

/// <summary>
/// Base symbol declaration that triggered the code changes.
/// </summary>
public BaseTypeDeclarationSyntax SymbolDeclaration { get; }

/// <summary>
/// Changes to the attributes of the symbol.
/// </summary>
public ImmutableArray<AttributeCodeChange> Attributes { get; init; } = [];

readonly ImmutableArray<EnumMember> enumMembers = [];

/// <summary>
/// Changes to the enum members of the symbol.
/// </summary>
public ImmutableArray<EnumMember> EnumMembers { get; init; } = [];
public ImmutableArray<EnumMember> EnumMembers {
get => enumMembers;
init => enumMembers = value;
}

readonly ImmutableArray<Property> properties = [];

/// <summary>
/// Changes to the properties of the symbol.
/// </summary>
public ImmutableArray<Property> Properties { get; init; } = [];
public ImmutableArray<Property> Properties {
get => properties;
init => properties = value;
}

readonly ImmutableArray<Constructor> constructors = [];

/// <summary>
/// Changes to the constructors of the symbol.
/// </summary>
public ImmutableArray<Constructor> Constructors { get; init; } = [];
public ImmutableArray<Constructor> Constructors {
get => constructors;
init => constructors = value;
}

readonly ImmutableArray<Event> events = [];

/// <summary>
/// Changes to the events of the symbol.
/// </summary>
public ImmutableArray<Event> Events { get; init; } = [];
public ImmutableArray<Event> Events {
get => events;
init => events = value;
}

readonly ImmutableArray<Method> methods = [];

/// <summary>
/// Changes to the methods of a symbol.
/// </summary>
public ImmutableArray<Method> Methods { get; init; } = [];
public ImmutableArray<Method> Methods {
get => methods;
init => methods = value;
}

/// <summary>
/// Decide if an enum value should be ignored as a change.
Expand Down Expand Up @@ -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<in T> (T declarationSyntax, SemanticModel semanticModel);

delegate bool TryCreateDelegate<in T, TR> (T declaration, SemanticModel semanticModel,
[NotNullWhen (true)] out TR? change)
where T : MemberDeclarationSyntax
where TR : struct;

static void GetMembers<T, TR> (TypeDeclarationSyntax baseDeclarationSyntax, SemanticModel semanticModel,
SkipDelegate<T> skip, TryCreateDelegate<T, TR> tryCreate, out ImmutableArray<TR> members)
where T : MemberDeclarationSyntax
where TR : struct
{
var bucket = ImmutableArray.CreateBuilder<TR> ();
var declarations = baseDeclarationSyntax.Members.OfType<T> ();
foreach (var declaration in declarations) {
if (skip (declaration, semanticModel))
continue;
if (tryCreate (declaration, semanticModel, out var change))
bucket.Add (change.Value);
}

members = bucket.ToImmutable ();
}

/// <summary>
/// Internal constructor added for testing purposes.
/// </summary>
/// <param name="bindingType">The type of binding for the given code changes.</param>
/// <param name="fullyQualifiedSymbol">The fully qualified name of the symbol.</param>
/// <param name="declaration">The symbol declaration syntax.</param>
internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol, BaseTypeDeclarationSyntax declaration)
internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol)
{
BindingType = bindingType;
FullyQualifiedSymbol = fullyQualifiedSymbol;
SymbolDeclaration = declaration;
}

/// <summary>
Expand All @@ -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<EnumMember> ();
// loop over the fields and add those that contain a FieldAttribute
Expand All @@ -158,53 +202,19 @@ internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol, Base
/// <param name="semanticModel">The semantic model of the compilation.</param>
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<Property> ();
var propertyDeclarations = classDeclaration.Members.OfType<PropertyDeclarationSyntax> ();
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<Constructor> ();
var constructorDeclarations = classDeclaration.Members.OfType<ConstructorDeclarationSyntax> ();
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<Event> ();
var eventDeclarations = classDeclaration.Members.OfType<EventDeclarationSyntax> ();
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<Method> ();
var methodDeclarations = classDeclaration.Members.OfType<MethodDeclarationSyntax> ();
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<ConstructorDeclarationSyntax, Constructor> (classDeclaration, semanticModel, Skip,
Constructor.TryCreate, out constructors);
GetMembers<PropertyDeclarationSyntax, Property> (classDeclaration, semanticModel, Skip, Property.TryCreate,
out properties);
GetMembers<EventDeclarationSyntax, Event> (classDeclaration, semanticModel, Skip, Event.TryCreate, out events);
GetMembers<MethodDeclarationSyntax, Method> (classDeclaration, semanticModel, Skip, Method.TryCreate,
out methods);
}

/// <summary>
Expand All @@ -214,11 +224,17 @@ internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol, Base
/// <param name="semanticModel">The semantic model of the compilation.</param>
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<PropertyDeclarationSyntax, Property> (interfaceDeclaration, semanticModel, Skip, Property.TryCreate,
out properties);
GetMembers<EventDeclarationSyntax, Event> (interfaceDeclaration, semanticModel, Skip, Event.TryCreate,
out events);
GetMembers<MethodDeclarationSyntax, Method> (interfaceDeclaration, semanticModel, Skip, Method.TryCreate,
out methods);
}

/// <summary>
Expand All @@ -237,4 +253,26 @@ internal CodeChanges (BindingType bindingType, string fullyQualifiedSymbol, Base
ClassDeclarationSyntax classDeclarationSyntax => new CodeChanges (classDeclarationSyntax, semanticModel),
_ => null
};

/// <inheritdoc/>
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 ();
}
}
Loading