Skip to content

Commit 02d301f

Browse files
mandel-macaqueGitHub Actions AutoformatterCopilot
authored
[RGen] Generate the strong dictionary Keys. (#23403)
Add a generator for the strong dictionary keys. This does generator does not generate the backwards compatible code, that will be performed by another generator that can be easily disabled. --------- Co-authored-by: GitHub Actions Autoformatter <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 28de6d6 commit 02d301f

File tree

15 files changed

+418
-92
lines changed

15 files changed

+418
-92
lines changed

src/rgen/Microsoft.Macios.Generator/AttributesNames.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ static class AttributesNames {
1616
public const string BindFromAttribute = "ObjCBindings.BindFromAttribute";
1717
public const string ProtocolAttribute = "ObjCBindings.BindingTypeAttribute<ObjCBindings.Protocol>";
1818
public const string StrongDictionaryAttribute = "ObjCBindings.BindingTypeAttribute<ObjCBindings.StrongDictionary>";
19+
public const string StrongDictionaryKeysAttribute = "ObjCBindings.BindingTypeAttribute<ObjCBindings.StrongDictionaryKeys>";
1920
public const string FieldAttribute = "ObjCBindings.FieldAttribute";
2021
public const string EnumFieldAttribute = "ObjCBindings.FieldAttribute<ObjCBindings.EnumValue>";
2122
public const string FieldPropertyAttribute = "ObjCBindings.FieldAttribute<ObjCBindings.Property>";
@@ -36,6 +37,7 @@ static class AttributesNames {
3637
ClassAttribute,
3738
ProtocolAttribute,
3839
StrongDictionaryAttribute,
40+
StrongDictionaryKeysAttribute,
3941
CoreImageFilterAttribute,
4042
SmartEnumAttribute,
4143
];
@@ -56,6 +58,9 @@ static class AttributesNames {
5658
if (type == typeof (ObjCBindings.StrongDictionary)) {
5759
return StrongDictionaryAttribute;
5860
}
61+
if (type == typeof (ObjCBindings.StrongDictionaryKeys)) {
62+
return StrongDictionaryKeysAttribute;
63+
}
5964
if (type == typeof (ObjCBindings.SmartEnum)) {
6065
return SmartEnumAttribute;
6166
}

src/rgen/Microsoft.Macios.Generator/DataModel/BindingInfo.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ namespace Microsoft.Macios.Generator.DataModel;
2121
[FieldOffset (8)] readonly BindingTypeData<ObjCBindings.CoreImageFilter> coreImageFilterData;
2222
[FieldOffset (8)] readonly BindingTypeData<ObjCBindings.SmartEnum> smartEnumData;
2323
[FieldOffset (8)] readonly BindingTypeData<ObjCBindings.StrongDictionary> strongDictionaryData;
24+
[FieldOffset (8)] readonly BindingTypeData<ObjCBindings.StrongDictionaryKeys> strongDictionaryKeysData;
2425

2526
/// <summary>
2627
/// The type of the binding.
@@ -112,6 +113,16 @@ public BindingInfo (BindingTypeData<ObjCBindings.StrongDictionary> data)
112113
strongDictionaryData = data;
113114
}
114115

116+
/// <summary>
117+
/// Initializes a new instance of the <see cref="BindingInfo"/> struct for a strong dictionary keys binding.
118+
/// </summary>
119+
/// <param name="data">The strong dictionary keys binding data.</param>
120+
public BindingInfo (BindingTypeData<ObjCBindings.StrongDictionaryKeys> data)
121+
{
122+
bindingType = BindingType.StrongDictionaryKeys;
123+
strongDictionaryKeysData = data;
124+
}
125+
115126
/// <summary>
116127
/// Implicitly converts a <see cref="BindingInfo"/> to a <see cref="BindingTypeData"/>.
117128
/// </summary>
@@ -190,6 +201,18 @@ public BindingInfo (BindingTypeData<ObjCBindings.StrongDictionary> data)
190201
return info.strongDictionaryData;
191202
}
192203

204+
/// <summary>
205+
/// Implicitly converts a <see cref="BindingInfo"/> to a <see cref="BindingTypeData{T}"/> for a strong dictionary keys.
206+
/// </summary>
207+
/// <param name="info">The <see cref="BindingInfo"/> to convert.</param>
208+
/// <exception cref="InvalidCastException">Thrown if the binding type is not <see cref="BindingType.StrongDictionaryKeys"/>.</exception>
209+
public static implicit operator BindingTypeData<ObjCBindings.StrongDictionaryKeys> (BindingInfo info)
210+
{
211+
if (info.BindingType != BindingType.StrongDictionaryKeys)
212+
throw new InvalidCastException ($"Invalid cast to ObjCBindings.StrongDictionaryKeys for binding type {info.BindingType}");
213+
return info.strongDictionaryKeysData;
214+
}
215+
193216
/// <summary>
194217
/// Implicitly converts a <see cref="BindingTypeData"/> to a <see cref="BindingInfo"/>.
195218
/// </summary>
@@ -225,6 +248,11 @@ public BindingInfo (BindingTypeData<ObjCBindings.StrongDictionary> data)
225248
/// </summary>
226249
/// <param name="data">The <see cref="BindingTypeData{T}"/> to convert.</param>
227250
public static implicit operator BindingInfo (BindingTypeData<ObjCBindings.StrongDictionary> data) => new (data);
251+
/// <summary>
252+
/// Implicitly converts a <see cref="BindingTypeData{T}"/> for a strong dictionary keys to a <see cref="BindingInfo"/>.
253+
/// </summary>
254+
/// <param name="data">The <see cref="BindingTypeData{T}"/> to convert.</param>
255+
public static implicit operator BindingInfo (BindingTypeData<ObjCBindings.StrongDictionaryKeys> data) => new (data);
228256

229257
/// <inheritdoc />
230258
public bool Equals (BindingInfo other)
@@ -244,6 +272,8 @@ public bool Equals (BindingInfo other)
244272
return categoryData == other.categoryData;
245273
case BindingType.StrongDictionary:
246274
return strongDictionaryData == other.strongDictionaryData;
275+
case BindingType.StrongDictionaryKeys:
276+
return strongDictionaryKeysData == other.strongDictionaryKeysData;
247277
}
248278
return false;
249279
}
@@ -261,6 +291,7 @@ public override bool Equals (object? obj)
261291
BindingType.Protocol => HashCode.Combine (bindingType, protocolData),
262292
BindingType.Category => HashCode.Combine (bindingType, categoryData),
263293
BindingType.StrongDictionary => HashCode.Combine (bindingType, strongDictionaryData),
294+
BindingType.StrongDictionaryKeys => HashCode.Combine (bindingType, strongDictionaryKeysData),
264295
_ => HashCode.Combine (bindingType, bindingTypeData)
265296
};
266297

@@ -294,6 +325,7 @@ public override bool Equals (object? obj)
294325
BindingType.CoreImageFilter => $"{{ BindingType: {bindingType}, BindingData: {coreImageFilterData} }}",
295326
BindingType.SmartEnum => $"{{ BindingType: {bindingType}, BindingData: {smartEnumData} }}",
296327
BindingType.StrongDictionary => $"{{ BindingType: {bindingType}, BindingData: {strongDictionaryData} }}",
328+
BindingType.StrongDictionaryKeys => $"{{ BindingType: {bindingType}, BindingData: {strongDictionaryKeysData} }}",
297329
_ => throw new NotImplementedException ()
298330
};
299331
}

src/rgen/Microsoft.Macios.Generator/DataModel/BindingType.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ enum BindingType : ulong {
3232
/// </summary>
3333
StrongDictionary,
3434
/// <summary>
35+
/// Binding type for strong dictionary keys.
36+
/// </summary>
37+
StrongDictionaryKeys,
38+
/// <summary>
3539
/// Binding type for a core image filter.
3640
/// </summary>
3741
CoreImageFilter,

src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs

Lines changed: 9 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ namespace Microsoft.Macios.Generator.Emitters;
2525
/// </summary>
2626
class ClassEmitter : IClassEmitter {
2727
/// <inheritdoc />
28-
public string GetSymbolName (in Binding binding) => binding.Name;
28+
public string GetSymbolName (in Binding binding)
29+
{
30+
var outerClasses = binding.OuterClasses.Select (x => x.Name);
31+
var prefix = string.Join ('.', outerClasses);
32+
return string.IsNullOrEmpty (prefix)
33+
? binding.Name
34+
: $"{prefix}.{binding.Name}";
35+
}
2936

3037
/// <inheritdoc />
3138
public IEnumerable<string> UsingStatements => [
@@ -124,85 +131,6 @@ void EmitConstructors (in BindingContext context, TabbedWriter<StringWriter> cla
124131
}
125132
}
126133

127-
/// <summary>
128-
/// Emit the code for all the field properties in the class. The code will add any necessary backing fields and
129-
/// will return all properties that are notifications.
130-
/// </summary>
131-
/// <param name="className">The current class name.</param>
132-
/// <param name="properties">All properties of the class, the method will filter those that are fields.</param>
133-
/// <param name="classBlock">Current class block.</param>
134-
/// <param name="notificationProperties">An immutable array with all the properties that are marked as notifications
135-
/// and that need a helper class to be generated.</param>
136-
void EmitFields (string className, in ImmutableArray<Property> properties, TabbedWriter<StringWriter> classBlock,
137-
out ImmutableArray<Property> notificationProperties)
138-
{
139-
var notificationsBuilder = ImmutableArray.CreateBuilder<Property> ();
140-
foreach (var property in properties.OrderBy (p => p.Name)) {
141-
if (!property.IsField)
142-
continue;
143-
144-
classBlock.WriteLine ();
145-
// a field should always have a getter, if it does not, we do not generate the property
146-
var getter = property.GetAccessor (AccessorKind.Getter);
147-
if (getter is null)
148-
continue;
149-
150-
// provide a backing variable for the property if and only if we are dealing with a reference type
151-
if (property.IsReferenceType) {
152-
classBlock.WriteLine (FieldPropertyBackingVariable (property).ToString ());
153-
}
154-
155-
classBlock.WriteLine ();
156-
classBlock.AppendMemberAvailability (property.SymbolAvailability);
157-
classBlock.AppendGeneratedCodeAttribute (optimizable: true);
158-
if (property.IsNotification) {
159-
// add it to the bucket so that we can later generate the necessary partial class for the
160-
// notifications
161-
notificationsBuilder.Add (property);
162-
classBlock.AppendNotificationAdvice (className, property.Name);
163-
}
164-
165-
using (var propertyBlock = classBlock.CreateBlock (property.ToDeclaration ().ToString (), block: true)) {
166-
// generate the accessors, we will always have a get, a set is optional depending on the type
167-
// if the symbol availability of the accessor is different of the one from the property, write it
168-
var backingField = property.BackingField;
169-
170-
// be very verbose with the availability, makes the life easier to the dotnet analyzer
171-
propertyBlock.AppendMemberAvailability (getter.Value.SymbolAvailability);
172-
using (var getterBlock = propertyBlock.CreateBlock ("get", block: true)) {
173-
// fields with a reference type have a backing fields, while value types do not
174-
if (property.IsReferenceType) {
175-
getterBlock.WriteRaw (
176-
$@"if ({backingField} is null)
177-
{backingField} = {ExpressionStatement (FieldConstantGetter (property))}
178-
return {backingField};
179-
");
180-
} else {
181-
// directly return the call from the getter
182-
getterBlock.WriteLine ($"return {ExpressionStatement (FieldConstantGetter (property))}");
183-
}
184-
}
185-
186-
var setter = property.GetAccessor (AccessorKind.Setter);
187-
if (setter is null)
188-
// we are done with the current property
189-
continue;
190-
191-
propertyBlock.WriteLine (); // add space between getter and setter since we have the attrs
192-
propertyBlock.AppendMemberAvailability (setter.Value.SymbolAvailability);
193-
using (var setterBlock = propertyBlock.CreateBlock ("set", block: true)) {
194-
if (property.IsReferenceType) {
195-
// set the backing field
196-
setterBlock.WriteLine ($"{backingField} = value;");
197-
}
198-
// call the native code
199-
setterBlock.WriteLine ($"{ExpressionStatement (FieldConstantSetter (property, "value"))}");
200-
}
201-
}
202-
}
203-
notificationProperties = notificationsBuilder.ToImmutable ();
204-
}
205-
206134
/// <summary>
207135
/// Emit the code for all the properties in the class.
208136
/// </summary>
@@ -441,7 +369,7 @@ public bool TryEmit (in BindingContext bindingContext, [NotNullWhen (false)] out
441369
EmitConstructors (bindingContext, classBlock);
442370
}
443371

444-
EmitFields (bindingContext.Changes.Name, bindingContext.Changes.Properties, classBlock,
372+
this.EmitFields (bindingContext.Changes.Name, bindingContext.Changes.Properties, classBlock,
445373
out var notificationProperties);
446374
EmitProperties (bindingContext, classBlock);
447375
this.EmitMethods (bindingContext, classBlock);

src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitterExtensions.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Collections.Immutable;
45
using System.IO;
56
using System.Linq;
67
using Microsoft.Macios.Generator.Context;
@@ -220,4 +221,83 @@ public static void EmitMethods (this IClassEmitter self, in BindingContext conte
220221
}
221222
}
222223

224+
/// <summary>
225+
/// Emit the code for all the field properties in the class. The code will add any necessary backing fields and
226+
/// will return all properties that are notifications.
227+
/// </summary>
228+
/// <param name="self"></param>
229+
/// <param name="className">The current class name.</param>
230+
/// <param name="properties">All properties of the class, the method will filter those that are fields.</param>
231+
/// <param name="classBlock">Current class block.</param>
232+
/// <param name="notificationProperties">An immutable array with all the properties that are marked as notifications
233+
/// and that need a helper class to be generated.</param>
234+
public static void EmitFields (this IClassEmitter self, string className, in ImmutableArray<Property> properties, TabbedWriter<StringWriter> classBlock,
235+
out ImmutableArray<Property> notificationProperties)
236+
{
237+
var notificationsBuilder = ImmutableArray.CreateBuilder<Property> ();
238+
foreach (var property in properties.OrderBy (p => p.Name)) {
239+
if (!property.IsField)
240+
continue;
241+
242+
classBlock.WriteLine ();
243+
// a field should always have a getter, if it does not, we do not generate the property
244+
var getter = property.GetAccessor (AccessorKind.Getter);
245+
if (getter is null)
246+
continue;
247+
248+
// provide a backing variable for the property if and only if we are dealing with a reference type
249+
if (property.IsReferenceType) {
250+
classBlock.WriteLine (FieldPropertyBackingVariable (property).ToString ());
251+
}
252+
253+
classBlock.WriteLine ();
254+
classBlock.AppendMemberAvailability (property.SymbolAvailability);
255+
classBlock.AppendGeneratedCodeAttribute (optimizable: true);
256+
if (property.IsNotification) {
257+
// add it to the bucket so that we can later generate the necessary partial class for the
258+
// notifications
259+
notificationsBuilder.Add (property);
260+
classBlock.AppendNotificationAdvice (className, property.Name);
261+
}
262+
263+
using (var propertyBlock = classBlock.CreateBlock (property.ToDeclaration ().ToString (), block: true)) {
264+
// generate the accessors, we will always have a get, a set is optional depending on the type
265+
// if the symbol availability of the accessor is different of the one from the property, write it
266+
var backingField = property.BackingField;
267+
268+
// be very verbose with the availability, makes the life easier to the dotnet analyzer
269+
propertyBlock.AppendMemberAvailability (getter.Value.SymbolAvailability);
270+
using (var getterBlock = propertyBlock.CreateBlock ("get", block: true)) {
271+
// fields with a reference type have a backing fields, while value types do not
272+
if (property.IsReferenceType) {
273+
getterBlock.WriteRaw (
274+
$@"if ({backingField} is null)
275+
{backingField} = {ExpressionStatement (FieldConstantGetter (property))}
276+
return {backingField};
277+
");
278+
} else {
279+
// directly return the call from the getter
280+
getterBlock.WriteLine ($"return {ExpressionStatement (FieldConstantGetter (property))}");
281+
}
282+
}
283+
284+
var setter = property.GetAccessor (AccessorKind.Setter);
285+
if (setter is null)
286+
// we are done with the current property
287+
continue;
288+
289+
propertyBlock.WriteLine (); // add space between getter and setter since we have the attrs
290+
propertyBlock.AppendMemberAvailability (setter.Value.SymbolAvailability);
291+
using (var setterBlock = propertyBlock.CreateBlock ("set", block: true)) {
292+
if (property.IsReferenceType) {
293+
// set the backing field
294+
setterBlock.WriteLine ($"{backingField} = value;");
295+
}
296+
// call the native code
297+
setterBlock.WriteLine ($"{ExpressionStatement (FieldConstantSetter (property, "value"))}");
298+
}
299+
}
300+
}
301+
notificationProperties = notificationsBuilder.ToImmutable ();
302+
}
223303
}

src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ static class EmitterFactory {
1616
{ BindingType.SmartEnum, new EnumEmitter () },
1717
{ BindingType.Protocol, new InterfaceEmitter () },
1818
{ BindingType.Category, new CategoryEmitter () },
19-
{ BindingType.StrongDictionary, new StrongDictionaryEmitter () }
19+
{ BindingType.StrongDictionary, new StrongDictionaryEmitter () },
20+
{ BindingType.StrongDictionaryKeys, new StrongDictionaryKeysEmitter () }
2021
};
2122
public static bool TryCreate (Binding changes, [NotNullWhen (true)] out ICodeEmitter? emitter)
2223
=> emitters.TryGetValue (changes.BindingType, out emitter);
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Collections.Generic;
5+
using System.Collections.Immutable;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Linq;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.Macios.Generator.Context;
10+
using Microsoft.Macios.Generator.DataModel;
11+
using Microsoft.Macios.Generator.IO;
12+
13+
namespace Microsoft.Macios.Generator.Emitters;
14+
15+
/// <summary>
16+
/// Emitter responsible for generating strong dictionary keys classes.
17+
/// </summary>
18+
class StrongDictionaryKeysEmitter : IClassEmitter {
19+
20+
/// <inheritdoc />
21+
public string GetSymbolName (in Binding binding)
22+
{
23+
var outerClasses = binding.OuterClasses.Select (x => x.Name);
24+
var prefix = string.Join ('.', outerClasses);
25+
return string.IsNullOrEmpty (prefix)
26+
? binding.Name
27+
: $"{prefix}.{binding.Name}";
28+
}
29+
30+
/// <inheritdoc />
31+
public IEnumerable<string> UsingStatements { get; } = [];
32+
33+
/// <inheritdoc />
34+
public bool TryEmit (in BindingContext bindingContext, [NotNullWhen (false)] out ImmutableArray<Diagnostic>? diagnostics)
35+
{
36+
diagnostics = null;
37+
if (bindingContext.Changes.BindingType != BindingType.StrongDictionaryKeys) {
38+
diagnostics = [Diagnostic.Create (
39+
Diagnostics
40+
.RBI0000, // An unexpected error occurred while processing '{0}'. Please fill a bug report at https://github.com/dotnet/macios/issues/new.
41+
null,
42+
bindingContext.Changes.FullyQualifiedSymbol)];
43+
return false;
44+
}
45+
46+
// namespace declaration
47+
this.EmitNamespace (bindingContext);
48+
49+
using (var _ = this.EmitOuterClasses (bindingContext, out var builder)) {
50+
builder.AppendMemberAvailability (bindingContext.Changes.SymbolAvailability);
51+
var modifiers = $"{string.Join (' ', bindingContext.Changes.Modifiers)} ";
52+
using (var classBlock = builder.CreateBlock (
53+
$"{(string.IsNullOrWhiteSpace (modifiers) ? string.Empty : modifiers)}class {bindingContext.Changes.Name}",
54+
true)) {
55+
// the only thing we care about is the keys, so we emit the keys
56+
this.EmitFields (bindingContext.Changes.Name, bindingContext.Changes.Properties, classBlock,
57+
out var _);
58+
}
59+
}
60+
return true;
61+
}
62+
63+
}

0 commit comments

Comments
 (0)