Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ public static ImmutableArray<AttributeCodeChange> From (SyntaxList<AttributeList
var bucket = ImmutableArray.CreateBuilder<AttributeCodeChange> ();
foreach (AttributeListSyntax attributeListSyntax in attributes) {
foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) {
var x = semanticModel.GetSymbolInfo (attributeSyntax);
if (semanticModel.GetSymbolInfo (attributeSyntax).Symbol is not IMethodSymbol attributeSymbol)
continue; // if we can't get the symbol, ignore it
var name = attributeSymbol.ContainingType.ToDisplayString ();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,12 +283,14 @@ internal Binding (BindingInfo bindingInfo, string name, ImmutableArray<string> @
context.SemanticModel.GetSymbolData (
declaration: enumDeclaration,
bindingType: BindingType.SmartEnum,
context: context,
name: out name,
typeInfo: out typeInfo,
baseClass: out baseClass,
interfaces: out interfaces,
outerClasses: out outerClasses,
namespaces: out namespaces,
protocolConstructors: out _, // no constructors in enums
symbolAvailability: out availability,
bindingInfo: out bindingInfo);
FullyQualifiedSymbol = enumDeclaration.GetFullyQualifiedIdentifier (context.SemanticModel);
Expand Down Expand Up @@ -349,12 +351,14 @@ internal Binding (BindingInfo bindingInfo, string name, ImmutableArray<string> @
context.SemanticModel.GetSymbolData (
declaration: classDeclaration,
bindingType: classDeclaration.GetBindingType (context.SemanticModel),
context: context,
name: out name,
baseClass: out baseClass,
typeInfo: out typeInfo,
interfaces: out interfaces,
outerClasses: out outerClasses,
namespaces: out namespaces,
protocolConstructors: out protocolConstructors,
symbolAvailability: out availability,
bindingInfo: out bindingInfo);
FullyQualifiedSymbol = classDeclaration.GetFullyQualifiedIdentifier (context.SemanticModel);
Expand Down Expand Up @@ -393,12 +397,14 @@ internal Binding (BindingInfo bindingInfo, string name, ImmutableArray<string> @
context.SemanticModel.GetSymbolData (
declaration: interfaceDeclaration,
bindingType: BindingType.Protocol,
context: context,
name: out name,
typeInfo: out typeInfo,
baseClass: out baseClass,
interfaces: out interfaces,
outerClasses: out outerClasses,
namespaces: out namespaces,
protocolConstructors: out _, // ingored in interfaces
symbolAvailability: out availability,
bindingInfo: out bindingInfo);
FullyQualifiedSymbol = interfaceDeclaration.GetFullyQualifiedIdentifier (context.SemanticModel);
Expand Down Expand Up @@ -480,6 +486,8 @@ public override string ToString ()
sb.AppendJoin (", ", EnumMembers);
sb.Append ("], Constructors: [");
sb.AppendJoin (", ", Constructors);
sb.Append ("], ProtocolConstructors: [");
sb.AppendJoin (", ", ProtocolConstructors);
sb.Append ("], Properties: [");
sb.AppendJoin (", ", Properties);
sb.Append ("], ParentProtocolProperties: [");
Expand Down
25 changes: 25 additions & 0 deletions src/rgen/Microsoft.Macios.Generator/DataModel/Binding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,31 @@ public ImmutableArray<Constructor> Constructors {
/// </summary>
public ImmutableArray<string> ConstructorSelectors => [.. constructorIndex.Keys];

readonly Dictionary<string, int> protocolConstructorIndex = new ();
readonly ImmutableArray<Constructor> protocolConstructors = [];

/// <summary>
/// Gets the constructors inherited from parent protocols.
/// </summary>
public ImmutableArray<Constructor> ProtocolConstructors {
get => protocolConstructors;
init {
protocolConstructors = value;
// populate the constructor index for fast lookup using the symbol name
for (var index = 0; index < protocolConstructors.Length; index++) {
var constructor = protocolConstructors [index];
if (constructor.Selector is null)
continue;
protocolConstructorIndex [constructor.Selector] = index;
}
}
}

/// <summary>
/// Returns all the selectors for the constructors.
/// </summary>
public ImmutableArray<string> ProtocolConstructorSelectors => [.. protocolConstructorIndex.Keys];

readonly Dictionary<string, int> eventsIndex = new ();
readonly ImmutableArray<Event> events = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,15 @@ readonly partial struct Constructor {
public Location? Location { get; init; }

/// <summary>
/// True if the cosntructor was marked to skip its registration.
/// True if the constructor was marked to skip its registration.
/// </summary>
public bool SkipRegistration => ExportMethodData.Flags.HasFlag (ObjCBindings.Constructor.SkipRegistration);

/// <summary>
/// True if the constructor is thread safe.
/// </summary>
public bool IsThreadSafe => ExportMethodData.Flags.HasFlag (ObjCBindings.Constructor.IsThreadSafe);

public Constructor (string type,
SymbolAvailability symbolAvailability,
ExportData<ObjCBindings.Constructor> exportData,
Expand Down
5 changes: 5 additions & 0 deletions src/rgen/Microsoft.Macios.Generator/DataModel/Constructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ namespace Microsoft.Macios.Generator.DataModel;
/// </summary>
public bool IsNullOrDefault => State == StructState.Default;

/// <summary>
/// Gets or sets a value indicating whether the constructor comes from a protocol factory method.
/// </summary>
public bool IsProtocolConstructor { get; init; }

/// <summary>
/// Type name that owns the constructor.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ public static bool TryCreate (MethodDeclarationSyntax declaration, RootContext c
change = new (
type: method.ContainingSymbol.ToDisplayString ().Trim (), // we want the full name
name: method.Name,
returnType: new TypeInfo (method.ReturnType, context),
returnType: new TypeInfo (method.ReturnType, context, includeEvents: false),
symbolAvailability: method.GetSupportedPlatforms (),
exportMethodData: exportData,
attributes: attributes,
Expand Down Expand Up @@ -315,7 +315,7 @@ .. Modifiers.Where (m =>
/// A new <see cref="Constructor"/> instance if the method is a factory method;
/// otherwise, an uninitialized <see cref="Constructor"/> instance.
/// </returns>
public Constructor ToConstructor (TypeInfo targetClass)
public Constructor ToConstructor (string targetClass)
{
// if the method is not a factory, we cannot convert it to a constructor so we will return the default value
// which is an uninitialized instance
Expand All @@ -325,13 +325,17 @@ public Constructor ToConstructor (TypeInfo targetClass)
// we need to create a constructor with the same modifiers, parameters and the availability of the method
// since there is no guarantee that the target class has the same availability as the method
return new (
type: targetClass.Name,
exportData: new (ExportMethodData.Selector),
type: targetClass,
exportData: IsThreadSafe
? new (ExportMethodData.Selector) { Flags = ObjCBindings.Constructor.IsThreadSafe }
: new (ExportMethodData.Selector),
symbolAvailability: SymbolAvailability,
attributes: [], // we do not really care about the attributes on the constructor that is going to be inlined
modifiers: modifiers,
parameters: Parameters
);
) {
IsProtocolConstructor = true
};
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,16 @@ static ImmutableArray<EventInfo> GetInterfaceEvents (string typeNamespace, IType
return parentMethodsBucket.ToImmutable ();
}

internal TypeInfo (ITypeSymbol symbol, RootContext context) : this (symbol)
/// <summary>
/// Initializes a new instance of the <see cref="TypeInfo"/> struct from a type symbol, with additional generator-specific information.
/// </summary>
/// <param name="symbol">The type symbol to create the <see cref="TypeInfo"/> from.</param>
/// <param name="context">The root context for compilation information.</param>
/// <param name="includeEvents">A flag to indicate whether to include event information for protocols.</param>
internal TypeInfo (ITypeSymbol symbol, RootContext context, bool includeEvents = true) : this (symbol)
{
NeedsStret = symbol.NeedsStret (context.Compilation);
if (symbol.IsProtocol ()) {
if (symbol.IsProtocol () && includeEvents) {
Events = GetInterfaceEvents (string.Join ('.', Namespace), symbol, context);
}
}
Expand Down
62 changes: 56 additions & 6 deletions src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
Expand All @@ -18,9 +19,31 @@
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using Constructor = ObjCBindings.Constructor;
using Property = Microsoft.Macios.Generator.DataModel.Property;
using TypeInfo = Microsoft.Macios.Generator.DataModel.TypeInfo;

namespace Microsoft.Macios.Generator.Emitters;

file class ConstructorParameterComparer : IEqualityComparer<ImmutableArray<TypeInfo>> {
/// <summary>
/// Determines equality by requiring the same method name and identical ordered parameter type sequence.
/// </summary>
public bool Equals (ImmutableArray<TypeInfo> x, ImmutableArray<TypeInfo> y)
{
return x.SequenceEqual (y);
}

/// <summary>
/// Computes a hash code combining the method name and ordered parameter types.
/// </summary>
public int GetHashCode (ImmutableArray<TypeInfo> obj)
{
var hash = new HashCode ();
foreach (var t in obj)
hash.Add (t);
return hash.ToHashCode ();
}
}

/// <summary>
/// Emitter for Objective-C classes.
/// </summary>
Expand Down Expand Up @@ -54,9 +77,30 @@ public string GetSymbolName (in Binding binding)
/// <param name="classBlock">Current class block.</param>
void EmitConstructors (in BindingContext context, TabbedWriter<StringWriter> classBlock)
{
// merge the constructors and the protocol constructors for the current class, use a dict to avoid duplicates
var allConstructors = new Dictionary<ImmutableArray<TypeInfo>, DataModel.Constructor> (new ConstructorParameterComparer ());
foreach (var constructor in context.Changes.Constructors) {
if (constructor.Selector is null)
continue;
var key = constructor.Parameters.Select (x => x.Type).ToImmutableArray ();
allConstructors.TryAdd (key, constructor);
}

foreach (var constructor in context.Changes.ProtocolConstructors) {
if (constructor.Selector is null)
continue;
var key = constructor.Parameters.Select (x => x.Type).ToImmutableArray ();
allConstructors.TryAdd (key, constructor);
}

// create the ui thread check to be used in the constructors that come from a protocol factory method
var uiThreadCheck = (context.NeedsThreadChecks)
? EnsureUiThread (context.RootContext.CurrentPlatform)
: null;

// When dealing with constructors we cannot sort them by name because the name is always the same as the class
// instead we will sort them by the selector name so that we will always generate the constructors in the same order
foreach (var constructor in context.Changes.Constructors.OrderBy (c => c.ExportMethodData.Selector)) {
foreach (var constructor in allConstructors.Values.OrderBy (c => c.ExportMethodData.Selector)) {
classBlock.AppendMemberAvailability (constructor.SymbolAvailability);
classBlock.AppendGeneratedCodeAttribute (optimizable: true);
if (constructor.ExportMethodData.Flags.HasFlag (Constructor.DesignatedInitializer)) {
Expand All @@ -68,6 +112,12 @@ void EmitConstructors (in BindingContext context, TabbedWriter<StringWriter> cla
}

using (var constructorBlock = classBlock.CreateBlock (constructor.ToDeclaration (withBaseNSFlag: true).ToString (), block: true)) {
if (uiThreadCheck is not null && constructor is { IsProtocolConstructor: true, IsThreadSafe: false }) {
// if we are dealing with a protocol constructor, we need to ensure we are on the UI thread, this
// happens for example with NSCoding in ui elements.
constructorBlock.Write (uiThreadCheck.ToString ());
constructorBlock.WriteLine ();
}
// retrieve the method invocation via the factory, this will generate the necessary arguments
// transformations and the invocation
var invocations = GetInvocations (constructor);
Expand Down Expand Up @@ -122,12 +172,12 @@ void EmitNotifications (in ImmutableArray<Property> properties, TabbedWriter<Str
: notification.ExportFieldData.FieldData.Type;
// use the raw writer which makes it easier to read in this case
notificationClass.WriteRaw (
@$"public static {NSObject} {name} ({EventHandler}<{eventType}> handler)
@$"public static {NSObject} {name} ({BindingSyntaxFactory.EventHandler}<{eventType}> handler)
{{
return {notificationCenter}.AddObserver ({notification.Name}, notification => handler (null, new {eventType} (notification)));
}}

public static NSObject {name} ({NSObject} objectToObserve, {EventHandler}<{eventType}> handler)
public static NSObject {name} ({NSObject} objectToObserve, {BindingSyntaxFactory.EventHandler}<{eventType}> handler)
{{
return {notificationCenter}.AddObserver ({notification.Name}, notification => handler (null, new {eventType} (notification)), objectToObserve);
}}
Expand Down Expand Up @@ -193,8 +243,8 @@ void EmitEvents (in BindingContext bindingContext, in ImmutableArray<Property> d
foreach (var eventInfo in property.ExportPropertyData.StrongDelegateType.Events) {
// create the event args type name
var eventHandler = eventInfo.EventArgsType is null
? EventHandler.ToString ()
: $"{EventHandler}<{eventInfo.EventArgsType}>";
? BindingSyntaxFactory.EventHandler.ToString ()
: $"{BindingSyntaxFactory.EventHandler}<{eventInfo.EventArgsType}>";
using (var eventBlock =
classBlock.CreateBlock ($"public event {eventHandler} {eventInfo.Name}", true)) {
eventBlock.WriteLine ($"add {{ {ensureMethod} ()!.{eventInfo.Name.Decapitalize ()} += value; }}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,64 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Macios.Generator.Availability;
using Microsoft.Macios.Generator.Context;
using Microsoft.Macios.Generator.DataModel;
using TypeInfo = Microsoft.Macios.Generator.DataModel.TypeInfo;

namespace Microsoft.Macios.Generator.Extensions;

static partial class SemanticModelExtensions {

/// <summary>
/// Retrieves all the data from a symbol needed for a binding/transformation.
/// </summary>
/// <param name="symbol">The symbol being queried.</param>
/// <param name="name">Symbol name.</param>
/// <param name="baseClass">Symbol base class.</param>
/// <param name="interfaces">List of the interfaces implemented by the symbol.</param>
/// <param name="outerClasses">List of the outer classes of the symbol.</param>
/// <param name="namespaces">Collection with the namespaces of the symbol.</param>
/// <param name="protocolConstructors">Collection with the protocol constructors to inline.</param>
/// <param name="symbolAvailability">The symbols availability.</param>
public static void GetSymbolData (ISymbol? symbol,
RootContext context,
out string name,
out string? baseClass,
out ImmutableArray<string> interfaces,
out ImmutableArray<OuterClass> outerClasses,
out ImmutableArray<string> namespaces,
out ImmutableArray<Constructor> protocolConstructors,
out SymbolAvailability symbolAvailability)
{
name = symbol?.Name ?? string.Empty;
baseClass = null;
var interfacesBucket = ImmutableArray.CreateBuilder<string> ();
var protocolConstructorsBucket = ImmutableArray.CreateBuilder<Constructor> ();
if (symbol is INamedTypeSymbol namedTypeSymbol) {
baseClass = namedTypeSymbol.BaseType?.ToDisplayString ().Trim ();
foreach (var symbolInterface in namedTypeSymbol.Interfaces) {
interfacesBucket.Add (symbolInterface.ToDisplayString ().Trim ());
if (symbolInterface.IsProtocol ()) {
// get the constructors for the protocol binding info to get its methods and from those
// the inline constructors
foreach (var member in symbolInterface.GetMembers ()) {
if (member is IMethodSymbol methodSymbol &&
Method.TryCreate (methodSymbol, context, out var method) &&
method.Value.IsFactory) {
var constructor = method.Value.ToConstructor (name);
if (!constructor.IsNullOrDefault)
protocolConstructorsBucket.Add (constructor);
}
}
}
}
}
symbolAvailability = symbol?.GetSupportedPlatforms () ?? new SymbolAvailability ();
interfaces = interfacesBucket.ToImmutable ();
protocolConstructors = protocolConstructorsBucket.ToImmutable ();
(namespaces, outerClasses) = GetNamespaceArrayAndOuterClasses (symbol);
}

/// <summary>
/// Extracts symbol data from a type declaration including binding information, type details, and availability.
/// </summary>
Expand All @@ -24,23 +75,26 @@ static partial class SemanticModelExtensions {
/// <param name="interfaces">When this method returns, contains the implemented interface names.</param>
/// <param name="outerClasses">When this method returns, contains the outer class hierarchy.</param>
/// <param name="namespaces">When this method returns, contains the namespace hierarchy.</param>
/// <param name="protocolConstructors"></param>
/// <param name="symbolAvailability">When this method returns, contains the platform availability information.</param>
/// <param name="bindingInfo">When this method returns, contains the binding-specific information based on the binding type.</param>
public static void GetSymbolData (this SemanticModel self, BaseTypeDeclarationSyntax declaration,
BindingType bindingType,
RootContext context,
out string name,
out string? baseClass,
out TypeInfo typeInfo,
out ImmutableArray<string> interfaces,
out ImmutableArray<OuterClass> outerClasses,
out ImmutableArray<string> namespaces,
out ImmutableArray<Constructor> protocolConstructors,
out SymbolAvailability symbolAvailability,
out BindingInfo bindingInfo)
{
var symbol = self.GetDeclaredSymbol (declaration);
// only named types have type info
typeInfo = (symbol is INamedTypeSymbol namedTypeSymbol) ? new (namedTypeSymbol) : TypeInfo.Default;
GetSymbolData (symbol, out name, out baseClass, out interfaces, out outerClasses, out namespaces, out symbolAvailability);
GetSymbolData (symbol, context, out name, out baseClass, out interfaces, out outerClasses, out namespaces, out protocolConstructors, out symbolAvailability);
if (symbol is null)
bindingInfo = default;
else {
Expand Down
Loading
Loading