Skip to content

Commit 266a767

Browse files
[RGen] Refactor how properties are emitted. (#23465)
1 parent ba77eaa commit 266a767

File tree

2 files changed

+168
-146
lines changed

2 files changed

+168
-146
lines changed

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

Lines changed: 1 addition & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -131,149 +131,6 @@ void EmitConstructors (in BindingContext context, TabbedWriter<StringWriter> cla
131131
}
132132
}
133133

134-
/// <summary>
135-
/// Emit the code for all the properties in the class.
136-
/// </summary>
137-
/// <param name="context">The current binding context.</param>
138-
/// <param name="classBlock">Current class block.</param>
139-
void EmitProperties (in BindingContext context, TabbedWriter<StringWriter> classBlock)
140-
{
141-
142-
// use the binding context to decide if we need to insert the ui thread check
143-
var uiThreadCheck = (context.NeedsThreadChecks)
144-
? EnsureUiThread (context.RootContext.CurrentPlatform) : null;
145-
146-
foreach (var property in context.Changes.Properties.OrderBy (p => p.Name)) {
147-
if (property.IsField)
148-
// ignore fields
149-
continue;
150-
// use the factory to generate all the needed invocations for the current
151-
var invocations = GetInvocations (property);
152-
153-
// we expect to always at least have a getter
154-
var getter = property.GetAccessor (AccessorKind.Getter);
155-
if (getter.IsNullOrDefault)
156-
continue;
157-
158-
// add backing variable for the property if it is needed
159-
if (property.NeedsBackingField) {
160-
classBlock.WriteLine ();
161-
classBlock.AppendGeneratedCodeAttribute (optimizable: true);
162-
classBlock.WriteLine ($"object? {property.BackingField} = null;");
163-
}
164-
165-
classBlock.WriteLine ();
166-
classBlock.AppendMemberAvailability (property.SymbolAvailability);
167-
classBlock.AppendGeneratedCodeAttribute (optimizable: true);
168-
169-
using (var propertyBlock = classBlock.CreateBlock (property.ToDeclaration ().ToString (), block: true)) {
170-
// be very verbose with the availability, makes the life easier to the dotnet analyzer
171-
propertyBlock.AppendMemberAvailability (getter.SymbolAvailability);
172-
// if we deal with a delegate, include the attr:
173-
// [return: DelegateProxy (typeof ({staticBridge}))]
174-
if (property.ReturnType.IsDelegate)
175-
propertyBlock.AppendDelegateProxyReturn (property.ReturnType);
176-
using (var getterBlock = propertyBlock.CreateBlock ("get", block: true)) {
177-
if (uiThreadCheck is not null) {
178-
getterBlock.WriteLine (uiThreadCheck.ToString ());
179-
getterBlock.WriteLine ();
180-
}
181-
// depending on the property definition, we might need a temp variable to store
182-
// the return value
183-
var (tempVar, tempDeclaration) = GetReturnValueAuxVariable (property.ReturnType);
184-
getterBlock.WriteRaw (
185-
$@"{tempDeclaration}
186-
if (IsDirectBinding) {{
187-
{ExpressionStatement (invocations.Getter.Send)}
188-
}} else {{
189-
{ExpressionStatement (invocations.Getter.SendSuper)}
190-
}}
191-
{ExpressionStatement (KeepAlive ("this"))}
192-
");
193-
if (property.RequiresDirtyCheck || property.IsWeakDelegate) {
194-
getterBlock.WriteLine ("MarkDirty ();");
195-
}
196-
197-
if (property.NeedsBackingField) {
198-
getterBlock.WriteLine ($"{property.BackingField} = {tempVar};");
199-
}
200-
201-
getterBlock.WriteLine ($"return {tempVar};");
202-
}
203-
204-
var setter = property.GetAccessor (AccessorKind.Setter);
205-
if (setter.IsNullOrDefault || invocations.Setter is null)
206-
// we are done with the current property
207-
continue;
208-
209-
propertyBlock.WriteLine (); // add space between getter and setter since we have the attrs
210-
propertyBlock.AppendMemberAvailability (setter.SymbolAvailability);
211-
// if we deal with a delegate, include the attr:
212-
// [param: BlockProxy (typeof ({nativeInvoker}))]
213-
if (property.ReturnType.IsDelegate)
214-
propertyBlock.AppendDelegateParameter (property.ReturnType);
215-
using (var setterBlock = propertyBlock.CreateBlock ("set", block: true)) {
216-
if (uiThreadCheck is not null) {
217-
setterBlock.WriteLine (uiThreadCheck.ToString ());
218-
setterBlock.WriteLine ();
219-
}
220-
// init the needed temp variables
221-
setterBlock.Write (invocations.Setter.Value.Argument.Initializers, verifyTrivia: false);
222-
setterBlock.Write (invocations.Setter.Value.Argument.Validations, verifyTrivia: false);
223-
setterBlock.Write (invocations.Setter.Value.Argument.PreCallConversion, verifyTrivia: false);
224-
225-
// perform the invocation
226-
setterBlock.WriteRaw (
227-
$@"if (IsDirectBinding) {{
228-
{ExpressionStatement (invocations.Setter.Value.Send)}
229-
}} else {{
230-
{ExpressionStatement (invocations.Setter.Value.SendSuper)}
231-
}}
232-
{ExpressionStatement (KeepAlive ("this"))}
233-
");
234-
// perform the post delegate call conversion, this might include the GC.KeepAlive calls to keep
235-
// the native object alive
236-
setterBlock.Write (invocations.Setter.Value.Argument.PostCallConversion, verifyTrivia: false);
237-
// mark property as dirty if needed
238-
if (property.RequiresDirtyCheck || property.IsWeakDelegate) {
239-
setterBlock.WriteLine ("MarkDirty ();");
240-
}
241-
242-
if (property.NeedsBackingField) {
243-
setterBlock.WriteLine ($"{property.BackingField} = value;");
244-
}
245-
}
246-
}
247-
248-
// if the property is a weak delegate and has the strong delegate type set, we need to emit the
249-
// strong delegate property
250-
if (property is { IsProperty: true, IsWeakDelegate: true }
251-
&& !property.ExportPropertyData.StrongDelegateType.IsNullOrDefault) {
252-
classBlock.WriteLine ();
253-
var strongDelegate = property.ToStrongDelegate ();
254-
using (var propertyBlock =
255-
classBlock.CreateBlock (strongDelegate.ToDeclaration ().ToString (), block: true)) {
256-
using (var getterBlock =
257-
propertyBlock.CreateBlock ("get", block: true)) {
258-
getterBlock.WriteLine (
259-
$"return {property.Name} as {strongDelegate.ReturnType.WithNullable (isNullable: false).GetIdentifierSyntax ()};");
260-
}
261-
262-
using (var setterBlock =
263-
propertyBlock.CreateBlock ("set", block: true)) {
264-
setterBlock.WriteRaw (
265-
$@"var rvalue = value as NSObject;
266-
if (!(value is null) && rvalue is null) {{
267-
throw new ArgumentException ($""The object passed of type {{value.GetType ()}} does not derive from NSObject"");
268-
}}
269-
{property.Name} = rvalue;
270-
");
271-
}
272-
}
273-
}
274-
}
275-
}
276-
277134
/// <summary>
278135
/// Emit the code for all the notifications in the class.
279136
/// </summary>
@@ -375,7 +232,7 @@ public bool TryEmit (in BindingContext bindingContext, [NotNullWhen (false)] out
375232

376233
this.EmitFields (bindingContext.Changes.Name, bindingContext.Changes.Properties, classBlock,
377234
out var notificationProperties);
378-
EmitProperties (bindingContext, classBlock);
235+
this.EmitProperties (bindingContext, classBlock);
379236
this.EmitMethods (bindingContext, classBlock);
380237

381238
// emit the notification helper classes, leave this for the very bottom of the class

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

Lines changed: 167 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ static class ClassEmitterExtensions {
2525
/// Emit the selector fields for the current class. The method will add the fields to the binding context so that
2626
/// they can be used later.
2727
/// </summary>
28-
/// <param name="self"></param>
28+
/// <param name="self">The class emitter.</param>
2929
/// <param name="bindingContext">The current binding context.</param>
3030
/// <param name="classBlock">The current class block.</param>
3131
public static void EmitSelectorFields (this IClassEmitter self, in BindingContext bindingContext, TabbedWriter<StringWriter> classBlock)
@@ -257,7 +257,7 @@ public static void EmitMethods (this IClassEmitter self, in BindingContext conte
257257
/// Emit the code for all the field properties in the class. The code will add any necessary backing fields and
258258
/// will return all properties that are notifications.
259259
/// </summary>
260-
/// <param name="self"></param>
260+
/// <param name="self">The class emitter.</param>
261261
/// <param name="className">The current class name.</param>
262262
/// <param name="properties">All properties of the class, the method will filter those that are fields.</param>
263263
/// <param name="classBlock">Current class block.</param>
@@ -332,4 +332,169 @@ public static void EmitFields (this IClassEmitter self, string className, in Imm
332332
}
333333
notificationProperties = notificationsBuilder.ToImmutable ();
334334
}
335+
336+
/// <summary>
337+
/// Emits the code for a given property.
338+
/// </summary>
339+
/// <param name="self">The class emitter.</param>
340+
/// <param name="context">The current binding context.</param>
341+
/// <param name="property">The property to emit.</param>
342+
/// <param name="classBlock">The current class block writer.</param>
343+
/// <param name="uiThreadCheck">An optional UI thread check expression. If not provided, it will be created based on the context.</param>
344+
public static void EmitProperty (this IClassEmitter self, in BindingContext context, in Property property,
345+
TabbedWriter<StringWriter> classBlock, ExpressionStatementSyntax? uiThreadCheck = null)
346+
{
347+
348+
// if not passed as an argument, we will create the ui thread check based on the context
349+
if (uiThreadCheck is null) {
350+
uiThreadCheck = (context.NeedsThreadChecks)
351+
? EnsureUiThread (context.RootContext.CurrentPlatform)
352+
: null;
353+
}
354+
355+
if (property.IsField)
356+
// ignore fields
357+
return;
358+
// use the factory to generate all the needed invocations for the current
359+
var invocations = GetInvocations (property);
360+
361+
// we expect to always at least have a getter
362+
var getter = property.GetAccessor (AccessorKind.Getter);
363+
if (getter.IsNullOrDefault)
364+
return;
365+
366+
// add backing variable for the property if it is needed
367+
if (property.NeedsBackingField) {
368+
classBlock.WriteLine ();
369+
classBlock.AppendGeneratedCodeAttribute (optimizable: true);
370+
classBlock.WriteLine ($"object? {property.BackingField} = null;");
371+
}
372+
373+
classBlock.WriteLine ();
374+
classBlock.AppendMemberAvailability (property.SymbolAvailability);
375+
classBlock.AppendGeneratedCodeAttribute (optimizable: true);
376+
377+
using (var propertyBlock = classBlock.CreateBlock (property.ToDeclaration ().ToString (), block: true)) {
378+
// be very verbose with the availability, makes the life easier to the dotnet analyzer
379+
propertyBlock.AppendMemberAvailability (getter.SymbolAvailability);
380+
// if we deal with a delegate, include the attr:
381+
// [return: DelegateProxy (typeof ({staticBridge}))]
382+
if (property.ReturnType.IsDelegate)
383+
propertyBlock.AppendDelegateProxyReturn (property.ReturnType);
384+
using (var getterBlock = propertyBlock.CreateBlock ("get", block: true)) {
385+
if (uiThreadCheck is not null) {
386+
getterBlock.WriteLine (uiThreadCheck.ToString ());
387+
getterBlock.WriteLine ();
388+
}
389+
// depending on the property definition, we might need a temp variable to store
390+
// the return value
391+
var (tempVar, tempDeclaration) = GetReturnValueAuxVariable (property.ReturnType);
392+
getterBlock.WriteRaw (
393+
$@"{tempDeclaration}
394+
if (IsDirectBinding) {{
395+
{ExpressionStatement (invocations.Getter.Send)}
396+
}} else {{
397+
{ExpressionStatement (invocations.Getter.SendSuper)}
398+
}}
399+
{ExpressionStatement (KeepAlive ("this"))}
400+
");
401+
if (property.RequiresDirtyCheck || property.IsWeakDelegate) {
402+
getterBlock.WriteLine ("MarkDirty ();");
403+
}
404+
405+
if (property.NeedsBackingField) {
406+
getterBlock.WriteLine ($"{property.BackingField} = {tempVar};");
407+
}
408+
409+
getterBlock.WriteLine ($"return {tempVar};");
410+
}
411+
412+
var setter = property.GetAccessor (AccessorKind.Setter);
413+
if (setter.IsNullOrDefault || invocations.Setter is null)
414+
// we are done with the current property
415+
return;
416+
417+
propertyBlock.WriteLine (); // add space between getter and setter since we have the attrs
418+
propertyBlock.AppendMemberAvailability (setter.SymbolAvailability);
419+
// if we deal with a delegate, include the attr:
420+
// [param: BlockProxy (typeof ({nativeInvoker}))]
421+
if (property.ReturnType.IsDelegate)
422+
propertyBlock.AppendDelegateParameter (property.ReturnType);
423+
using (var setterBlock = propertyBlock.CreateBlock ("set", block: true)) {
424+
if (uiThreadCheck is not null) {
425+
setterBlock.WriteLine (uiThreadCheck.ToString ());
426+
setterBlock.WriteLine ();
427+
}
428+
// init the needed temp variables
429+
setterBlock.Write (invocations.Setter.Value.Argument.Initializers, verifyTrivia: false);
430+
setterBlock.Write (invocations.Setter.Value.Argument.Validations, verifyTrivia: false);
431+
setterBlock.Write (invocations.Setter.Value.Argument.PreCallConversion, verifyTrivia: false);
432+
433+
// perform the invocation
434+
setterBlock.WriteRaw (
435+
$@"if (IsDirectBinding) {{
436+
{ExpressionStatement (invocations.Setter.Value.Send)}
437+
}} else {{
438+
{ExpressionStatement (invocations.Setter.Value.SendSuper)}
439+
}}
440+
{ExpressionStatement (KeepAlive ("this"))}
441+
");
442+
// perform the post delegate call conversion, this might include the GC.KeepAlive calls to keep
443+
// the native object alive
444+
setterBlock.Write (invocations.Setter.Value.Argument.PostCallConversion, verifyTrivia: false);
445+
// mark property as dirty if needed
446+
if (property.RequiresDirtyCheck || property.IsWeakDelegate) {
447+
setterBlock.WriteLine ("MarkDirty ();");
448+
}
449+
450+
if (property.NeedsBackingField) {
451+
setterBlock.WriteLine ($"{property.BackingField} = value;");
452+
}
453+
}
454+
}
455+
456+
// if the property is a weak delegate and has the strong delegate type set, we need to emit the
457+
// strong delegate property
458+
if (property is { IsProperty: true, IsWeakDelegate: true }
459+
&& !property.ExportPropertyData.StrongDelegateType.IsNullOrDefault) {
460+
classBlock.WriteLine ();
461+
var strongDelegate = property.ToStrongDelegate ();
462+
using (var propertyBlock =
463+
classBlock.CreateBlock (strongDelegate.ToDeclaration ().ToString (), block: true)) {
464+
using (var getterBlock =
465+
propertyBlock.CreateBlock ("get", block: true)) {
466+
getterBlock.WriteLine (
467+
$"return {property.Name} as {strongDelegate.ReturnType.WithNullable (isNullable: false).GetIdentifierSyntax ()};");
468+
}
469+
470+
using (var setterBlock =
471+
propertyBlock.CreateBlock ("set", block: true)) {
472+
setterBlock.WriteRaw (
473+
$@"var rvalue = value as NSObject;
474+
if (!(value is null) && rvalue is null) {{
475+
throw new ArgumentException ($""The object passed of type {{value.GetType ()}} does not derive from NSObject"");
476+
}}
477+
{property.Name} = rvalue;
478+
");
479+
}
480+
}
481+
}
482+
}
483+
484+
/// <summary>
485+
/// Emit the code for all the properties in the class.
486+
/// </summary>
487+
/// <param name="self">The class emitter.</param>
488+
/// <param name="context">The current binding context.</param>
489+
/// <param name="classBlock">Current class block.</param>
490+
public static void EmitProperties (this IClassEmitter self, in BindingContext context, TabbedWriter<StringWriter> classBlock)
491+
{
492+
// use the binding context to decide if we need to insert the ui thread check
493+
var uiThreadCheck = (context.NeedsThreadChecks)
494+
? EnsureUiThread (context.RootContext.CurrentPlatform) : null;
495+
496+
foreach (var property in context.Changes.Properties.OrderBy (p => p.Name)) {
497+
EmitProperty (self, in context, property, classBlock, uiThreadCheck);
498+
}
499+
}
335500
}

0 commit comments

Comments
 (0)