Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
12 changes: 11 additions & 1 deletion src/ObjCBindings/ExportAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public class ExportAttribute<T> : Attribute where T : Enum {
/// <summary>
/// The type of the result for an async method.
/// </summary>
public TypeInfo? ResultType { get; set; } = null;
public Type? ResultType { get; set; } = null;

/// <summary>
/// The name of the generated async method.
Expand All @@ -71,6 +71,16 @@ public class ExportAttribute<T> : Attribute where T : Enum {
/// </summary>
public string? PostNonResultSnippet { get; set; } = null;

/// <summary>
/// The type of the strong delegate for a weak delegate property.
/// </summary>
public Type? StrongDelegateType { get; set; } = null;

/// <summary>
/// The name of the strong delegate for a weak delegate property.
/// </summary>
public string? StrongDelegateName { get; set; } = null;

protected ExportAttribute () { }

/// <summary>
Expand Down
30 changes: 29 additions & 1 deletion src/rgen/Microsoft.Macios.Generator/Attributes/ExportData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ namespace Microsoft.Macios.Generator.Attributes;
/// A code snippet to be executed after the async method call.
/// </summary>
public string? PostNonResultSnippet { get; init; }

/// <summary>
/// The type of the strong delegate for a weak delegate property.
/// </summary>
public TypeInfo? StrongDelegateType { get; init; }

/// <summary>
/// The name of the strong delegate for a weak delegate property.
/// </summary>
public string? StrongDelegateName{ get; init; }

public ExportData () { }

Expand Down Expand Up @@ -119,6 +129,9 @@ public static bool TryParse (AttributeData attributeData,
string? methodName = null;
string? resultTypeName = null;
string? postNonResultSnippet = null;
// weak delegate related data
TypeInfo? strongDelegateType = null;
string? strongDelegateName = null;

switch (count) {
case 1:
Expand Down Expand Up @@ -163,6 +176,7 @@ public static bool TryParse (AttributeData attributeData,

// from this point we have to check the name of the argument AND if the export method is an Async method.
var isAsync = typeof (T) == typeof (ObjCBindings.Method) && flags is not null && flags.HasFlag (ObjCBindings.Method.Async);
var isWeakDelegate = typeof (T) == typeof (ObjCBindings.Property) && flags is not null && flags.HasFlag (ObjCBindings.Property.WeakDelegate);

// loop over all the named arguments and set the data accordingly, ignore the Flags one since we already set it
foreach (var (name, value) in attrsDict) {
Expand Down Expand Up @@ -206,6 +220,17 @@ public static bool TryParse (AttributeData attributeData,
postNonResultSnippet = (string?) value;
}
break;
// weak delegate related data
case "StrongDelegateType":
if (isWeakDelegate) {
strongDelegateType = new ((INamedTypeSymbol) value!);
}
break;
case "StrongDelegateName":
if (isWeakDelegate) {
strongDelegateName = (string?) value!;
}
break;
default:
data = null;
return false;
Expand All @@ -221,7 +246,10 @@ public static bool TryParse (AttributeData attributeData,
ResultType = isAsync ? resultType : null,
MethodName = isAsync ? methodName : null,
ResultTypeName = isAsync ? resultTypeName : null,
PostNonResultSnippet = isAsync ? postNonResultSnippet : null
PostNonResultSnippet = isAsync ? postNonResultSnippet : null,
// we set the data for the weak delegate only if the flags are set
StrongDelegateType = isWeakDelegate ? strongDelegateType : null,
StrongDelegateName = isWeakDelegate ? strongDelegateName : null
};
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,19 @@ public bool Equals (Property other)
return RequiresDirtyCheck == other.RequiresDirtyCheck;
}

public Property ToStrongDelegate ()
{
// has to be a property, weak delegate and have its strong delegate type set
if (!IsProperty || !IsWeakDelegate || ExportPropertyData.Value.StrongDelegateType is null)
return this;

// update the return type, all the rest is the same
return this with {
Name = ExportPropertyData.Value.StrongDelegateName ?? Name.Remove (0, 4 /* "Weak".Length */),

Copilot AI Jun 28, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a hard-coded 4 for the "Weak" prefix length is brittle; consider using "Weak".Length as a constant or checking Name.StartsWith("Weak") before removing to avoid mis-removal if the pattern changes.

Suggested change
Name = ExportPropertyData.Value.StrongDelegateName ?? Name.Remove (0, 4 /* "Weak".Length */),
Name = ExportPropertyData.Value.StrongDelegateName ?? (Name.StartsWith("Weak") ? Name.Remove(0, "Weak".Length) : Name),

Copilot uses AI. Check for mistakes.
ReturnType = ExportPropertyData.Value.StrongDelegateType.Value.WithNullable (true),
};
}

/// <inheritdoc />
public override string ToString ()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Microsoft.Macios.Generator.DataModel;
/// <summary>
/// Name of the property.
/// </summary>
public string Name { get; } = string.Empty;
public string Name { get; init; } = string.Empty;

readonly string? backingField = null;

Expand Down
15 changes: 7 additions & 8 deletions src/rgen/Microsoft.Macios.Generator/DataModel/TypeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -556,20 +556,19 @@ public TypeInfo ToArrayElementType ()
}

/// <summary>
/// If the current <see cref="TypeInfo"/> is nullable, this method returns a new <see cref="TypeInfo"/>
/// representing the non-nullable version of the type. Otherwise, it returns the current instance.
/// Returns a new <see cref="TypeInfo"/> with the specified nullability.
/// </summary>
/// <param name="isNullable">A boolean value indicating whether the new type should be nullable.</param>
/// <returns>
/// A new <see cref="TypeInfo"/> instance with <see cref="IsNullable"/> set to false if the original <see cref="IsNullable"/> was true;
/// otherwise, returns the current <see cref="TypeInfo"/> instance.
/// A new <see cref="TypeInfo"/> instance with the specified nullability. If the current instance already has the
/// specified nullability, the current instance is returned.
/// </returns>
public TypeInfo ToNonNullable ()
public TypeInfo WithNullable (bool isNullable)
{
if (!IsNullable)
if (IsNullable == isNullable)
return this;
// copy all the elements from the current array type and set the array type to false
return this with {
IsNullable = false,
IsNullable = isNullable,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1229,12 +1229,12 @@ internal static SyntaxNode ThrowIfNull (string variableName)

// NSObject[] => CFArray.ArrayFromHandle<Foundation.NSMetadataItem> (global::ObjCRuntime.Messaging.NativeHandle_objc_msgSend (this.Handle, Selector.GetHandle ("results")))!;
{ Type.IsArray: true, Type.ArrayElementTypeIsWrapped: true }
=> GetCFArrayFromHandle (info.Type.ToArrayElementType ().ToNonNullable ().GetIdentifierSyntax (), [
=> GetCFArrayFromHandle (info.Type.ToArrayElementType ().WithNullable (isNullable: false).GetIdentifierSyntax (), [
Argument (expression)
], suppressNullableWarning: !info.Type.IsNullable),

{ Type.IsArray: true, Type.ArrayElementIsINativeObject: true }
=> GetCFArrayFromHandle (info.Type.ToArrayElementType ().ToNonNullable ().GetIdentifierSyntax (), [
=> GetCFArrayFromHandle (info.Type.ToArrayElementType ().WithNullable (isNullable: false).GetIdentifierSyntax (), [
Argument (expression)
], suppressNullableWarning: !info.Type.IsNullable),

Expand All @@ -1244,7 +1244,7 @@ internal static SyntaxNode ThrowIfNull (string variableName)
// Runtime.GetINativeObject<NSString> (auxVariable, false)!;
{ Type.IsINativeObject: true, Type.IsNSObject: false, ReleaseHandle: not null}
=> GetINativeObject (
nsObjectType: info.Type.ToNonNullable ().GetIdentifierSyntax (),
nsObjectType: info.Type.WithNullable (isNullable: false).GetIdentifierSyntax (),
args: [
Argument (expression),
BoolArgument (info.ReleaseHandle.Value)
Expand All @@ -1253,7 +1253,7 @@ internal static SyntaxNode ThrowIfNull (string variableName)

{ Type.IsINativeObject: true, Type.IsNSObject: false, ReleaseHandle: null}
=> GetINativeObject (
nsObjectType: info.Type.ToNonNullable ().GetIdentifierSyntax (),
nsObjectType: info.Type.WithNullable (isNullable: false).GetIdentifierSyntax (),
args: [
Argument (expression),
],
Expand All @@ -1263,7 +1263,7 @@ internal static SyntaxNode ThrowIfNull (string variableName)
// Runtime.GetNSObject<NSString> (auxVariable, false)!;
{ Type.IsNSObject: true, ReleaseHandle: not null}
=> GetNSObject (
nsObjectType: info.Type.ToNonNullable ().GetIdentifierSyntax (),
nsObjectType: info.Type.WithNullable (isNullable: false).GetIdentifierSyntax (),
args: [
Argument (expression),
BoolArgument (false)
Expand All @@ -1272,7 +1272,7 @@ internal static SyntaxNode ThrowIfNull (string variableName)

{ Type.IsNSObject: true, ReleaseHandle: null}
=> GetNSObject (
nsObjectType: info.Type.ToNonNullable ().GetIdentifierSyntax (),
nsObjectType: info.Type.WithNullable (isNullable: false).GetIdentifierSyntax (),
args: [
Argument (expression),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ internal static (SyntaxToken ParameterName, TypeSyntax ParameterType) GetTrampol
// parameters that are passed by reference, depend on the type that is referenced
{ IsByRef: true, Type.IsReferenceType: false, Type.IsNullable: true}
=> (parameterIdentifier,
PointerType (GetLowLevelType (parameterType.ToNonNullable ()))),
PointerType (GetLowLevelType (parameterType.WithNullable (isNullable: false)))),

{ IsByRef: true, Type.IsReferenceType: false, Type.IsNullable: false}
=> (parameterIdentifier,
Expand Down Expand Up @@ -333,7 +333,7 @@ internal static ArgumentSyntax GetTrampolineInvokeArgument (string trampolineNam

// Runtime.GetNSObject<ParameterType> (ParameterName)
{ Type.IsNSObject: true, Type.IsNullable: true} =>
GetNSObject (parameterType.ToNonNullable ().GetIdentifierSyntax (), [
GetNSObject (parameterType.WithNullable (isNullable: false).GetIdentifierSyntax (), [
Argument (parameterIdentifier)
], suppressNullableWarning: false),

Expand All @@ -345,7 +345,7 @@ internal static ArgumentSyntax GetTrampolineInvokeArgument (string trampolineNam

// Runtime.GetINativeObject<ParameterType> (ParameterName, false)!
{ Type.IsINativeObject: true, Type.IsNullable: true } =>
GetINativeObject (parameterType.ToNonNullable ().GetIdentifierSyntax (), [
GetINativeObject (parameterType.WithNullable (isNullable: false).GetIdentifierSyntax (), [
Argument (parameterIdentifier),
BoolArgument (false)
], suppressNullableWarning: false),
Expand Down
39 changes: 37 additions & 2 deletions src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ void EmitProperties (in BindingContext context, TabbedWriter<StringWriter> class
// add backing variable for the property if it is needed
if (property.NeedsBackingField) {
classBlock.WriteLine ();
classBlock.AppendGeneratedCodeAttribute (optimizable: true);
classBlock.WriteLine ($"object? {property.BackingField} = null;");
}

Expand Down Expand Up @@ -205,10 +206,14 @@ void EmitProperties (in BindingContext context, TabbedWriter<StringWriter> class
}}
{ExpressionStatement (KeepAlive ("this"))}
");
if (property.RequiresDirtyCheck) {
if (property.RequiresDirtyCheck || property.IsWeakDelegate) {
getterBlock.WriteLine ("MarkDirty ();");
}

if (property.NeedsBackingField) {
getterBlock.WriteLine ($"{property.BackingField} = {tempVar};");
}

getterBlock.WriteLine ($"return {tempVar};");
}

Expand Down Expand Up @@ -245,12 +250,42 @@ void EmitProperties (in BindingContext context, TabbedWriter<StringWriter> class
// the native object alive
setterBlock.Write (invocations.Setter.Value.Argument.PostDelegateCallConversion, verifyTrivia: false);
// mark property as dirty if needed
if (property.RequiresDirtyCheck) {
if (property.RequiresDirtyCheck || property.IsWeakDelegate) {
setterBlock.WriteLine ("MarkDirty ();");
}

if (property.NeedsBackingField) {
setterBlock.WriteLine ($"{property.BackingField} = value;");
}
}
}

// if the property is a weak delegate and has the strong delegate type set, we need to emit the
// strong delegate property
if (property is { IsProperty: true, IsWeakDelegate: true }
&& property.ExportPropertyData.Value.StrongDelegateType is not null) {
classBlock.WriteLine ();
var strongDelegate = property.ToStrongDelegate ();
using (var propertyBlock =
classBlock.CreateBlock (strongDelegate.ToDeclaration ().ToString (), block: true)) {
using (var getterBlock =
propertyBlock.CreateBlock ("get", block: true)) {
getterBlock.WriteLine (
$"return {property.Name} as {strongDelegate.ReturnType.WithNullable (isNullable: false).GetIdentifierSyntax ()};");
}

using (var setterBlock =
propertyBlock.CreateBlock ("set", block: true)) {
setterBlock.WriteRaw (
$@"var rvalue = value as NSObject;
if (!(value is null) && rvalue is null) {{
throw new ArgumentException ($""The object passed of type {{value.GetType ()}} does not derive from NSObject"");
}}
{property.Name} = rvalue;
");
}
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#pragma warning disable APL0003
using System;
using Foundation;
using ObjCBindings;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#pragma warning disable APL0003
using System;
using Foundation;
using ObjCBindings;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#pragma warning disable APL0003
using System;
using Foundation;
using ObjCBindings;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma warning disable APL0003

using System;
using System.Runtime.Versioning;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#pragma warning disable APL0003
using System;
using System.Runtime.Versioning;
using Foundation;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma warning disable APL0003

using System;
using System.Runtime.Versioning;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma warning disable APL0003

using System;
using System.Runtime.Versioning;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma warning disable APL0003

using System;
using System.Runtime.Versioning;
Expand All @@ -12,7 +13,7 @@

namespace TestNamespace;

[BindingType<Class>]
[BindingType<ObjCBindings.Class>]
public partial class PropertyTests {

// the following are a list of examples of all possible property definitions
Expand Down Expand Up @@ -62,15 +63,15 @@ public partial class PropertyTests {
[SupportedOSPlatform ("tvos")]
[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("maccatalyst13.1")]
public virtual partial string? Name { get; set; }
public virtual partial string? OtherName { get; set; }

// array of strings
[Export<Property> ("surnames")]
[SupportedOSPlatform ("ios")]
[SupportedOSPlatform ("tvos")]
[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("maccatalyst13.1")]
public virtual partial string [] Name { get; set; }
public virtual partial string [] Names { get; set; }

// simple NSObject
[SupportedOSPlatform ("ios")]
Expand All @@ -85,7 +86,10 @@ public partial class PropertyTests {
[SupportedOSPlatform ("tvos")]
[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("maccatalyst13.1")]
[Export<Property> ("delegate", ArgumentSemantic.Assign)]
[Export<Property> ("delegate",
ArgumentSemantic.Weak,
Flags = Property.WeakDelegate,
StrongDelegateType = typeof (INSUserActivityDelegate))]
public virtual partial NSObject? WeakDelegate { get; set; }

// array nsobject
Expand Down Expand Up @@ -144,16 +148,6 @@ public virtual partial bool IsLenient {
set;
}

// wrapper property example
[SupportedOSPlatform ("ios")]
[SupportedOSPlatform ("tvos")]
[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("maccatalyst13.1")]
public virtual INSMetadataQueryDelegate? Delegate {
get => WeakDelegate as INSMetadataQueryDelegate;
set => WeakDelegate = value;
}

// bindfrom
[SupportedOSPlatform ("ios")]
[SupportedOSPlatform ("tvos")]
Expand Down
Loading