From 4a9334e1166d7425d021e9e7bfab4b2449c6ee23 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 17 Mar 2026 17:02:19 +0100 Subject: [PATCH 1/2] [sharpie] Prefer standard protocol interfaces over [Model] classes in type mapping When two types in the platform assembly map to the same native name, prefer the standard protocol interface (named "I" + nativeName, e.g. INSCopying for native "NSCopying") over a [Model] class stub. Previously, collisions between a [Model] class (e.g. NSCopying with [Protocol()]) and the protocol interface (INSCopying with [Protocol(Name="NSCopying")]) would cause both mappings to be dropped entirely. Now the standard protocol interface wins, so types like NSCopying in generic constraints are correctly mapped to INSCopying. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/sharpie/Tests/ObjCGenerics.iphoneos.cs | 6 ++-- tests/sharpie/Tests/ObjCGenerics.macosx.cs | 6 ++-- .../Massagers/PlatformTypeMappingMassager.cs | 31 +++++++++++++++---- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/tests/sharpie/Tests/ObjCGenerics.iphoneos.cs b/tests/sharpie/Tests/ObjCGenerics.iphoneos.cs index 68ed305d6555..63e042105327 100644 --- a/tests/sharpie/Tests/ObjCGenerics.iphoneos.cs +++ b/tests/sharpie/Tests/ObjCGenerics.iphoneos.cs @@ -6,16 +6,16 @@ interface CNLabeledValue : INSCopying, INSSecureCoding { // @property (readonly, copy, nonatomic) ValueType ValueTypeProperty; [Export ("ValueTypeProperty", ArgumentSemantic.Copy)] - NSObject ValueTypeProperty { get; } + NSObject ValueTypeProperty { get; } // -(ValueType _Nullable)getValueTypeMethod; [NullAllowed, Export ("getValueTypeMethod")] [Verify (MethodToProperty)] - NSObject ValueTypeMethod { get; } + NSObject ValueTypeMethod { get; } // -(void)setValueTypeMethod:(ValueType _Nullable)obj; [Export ("setValueTypeMethod:")] - void SetValueTypeMethod ([NullAllowed] NSObject obj); + void SetValueTypeMethod ([NullAllowed] NSObject obj); } // @protocol A diff --git a/tests/sharpie/Tests/ObjCGenerics.macosx.cs b/tests/sharpie/Tests/ObjCGenerics.macosx.cs index 68ed305d6555..63e042105327 100644 --- a/tests/sharpie/Tests/ObjCGenerics.macosx.cs +++ b/tests/sharpie/Tests/ObjCGenerics.macosx.cs @@ -6,16 +6,16 @@ interface CNLabeledValue : INSCopying, INSSecureCoding { // @property (readonly, copy, nonatomic) ValueType ValueTypeProperty; [Export ("ValueTypeProperty", ArgumentSemantic.Copy)] - NSObject ValueTypeProperty { get; } + NSObject ValueTypeProperty { get; } // -(ValueType _Nullable)getValueTypeMethod; [NullAllowed, Export ("getValueTypeMethod")] [Verify (MethodToProperty)] - NSObject ValueTypeMethod { get; } + NSObject ValueTypeMethod { get; } // -(void)setValueTypeMethod:(ValueType _Nullable)obj; [Export ("setValueTypeMethod:")] - void SetValueTypeMethod ([NullAllowed] NSObject obj); + void SetValueTypeMethod ([NullAllowed] NSObject obj); } // @protocol A diff --git a/tools/sharpie/Sharpie.Bind/Massagers/PlatformTypeMappingMassager.cs b/tools/sharpie/Sharpie.Bind/Massagers/PlatformTypeMappingMassager.cs index 3d36915ccdc2..72fe42d56406 100644 --- a/tools/sharpie/Sharpie.Bind/Massagers/PlatformTypeMappingMassager.cs +++ b/tools/sharpie/Sharpie.Bind/Massagers/PlatformTypeMappingMassager.cs @@ -12,7 +12,7 @@ namespace Sharpie.Bind.Massagers; [RegisterBefore (typeof (GenerateUsingStatementsMassager))] public sealed class PlatformTypeMappingMassager : Massager { readonly Dictionary typeMap = new (); - readonly Dictionary protocolMap = new (); + readonly HashSet protocolEntries = new (); readonly Stack ignoreType = new Stack (); public PlatformTypeMappingMassager (ObjectiveCBinder binder) @@ -23,6 +23,7 @@ public PlatformTypeMappingMassager (ObjectiveCBinder binder) public override bool Initialize () { typeMap.Clear (); + protocolEntries.Clear (); var path = base.Binder.PlatformAssembly; var decoder = new TypelessDecoder (); @@ -73,12 +74,30 @@ public override bool Initialize () var etName = mr.GetString (et.Name); nativeName ??= etName; - var map = typeMap; - if (map.Remove (nativeName)) { - // there would be a collision, so skip adding again - continue; + var entry = (etNamespace + "." + etName, etNamespace, etName); + if (typeMap.TryGetValue (nativeName, out var existingEntry)) { + // When two types map to the same native name, prefer the + // standard protocol interface (named "I" + nativeName, e.g. + // INSCopying for "NSCopying") over a [Model] class or a + // non-standard protocol. If neither or both follow the + // convention, drop both (genuine ambiguity). + bool newIsStandardProtocol = isProtocolAttribute && etName == "I" + nativeName; + bool existingIsStandardProtocol = protocolEntries.Contains (nativeName); + if (newIsStandardProtocol && !existingIsStandardProtocol) { + typeMap [nativeName] = entry; + protocolEntries.Add (nativeName); + } else if (!newIsStandardProtocol && existingIsStandardProtocol) { + // existing is the standard protocol, keep it + } else { + // genuine collision, drop both + typeMap.Remove (nativeName); + protocolEntries.Remove (nativeName); + } + } else { + typeMap.Add (nativeName, entry); + if (isProtocolAttribute && etName == "I" + nativeName) + protocolEntries.Add (nativeName); } - map.Add (nativeName, (etNamespace + "." + etName, etNamespace, etName)); } return typeMap.Count > 0; From 3d8c14f922434c332b36573f14a40a1952a4d526 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 17 Mar 2026 20:56:07 +0100 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Sharpie.Bind/Massagers/PlatformTypeMappingMassager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sharpie/Sharpie.Bind/Massagers/PlatformTypeMappingMassager.cs b/tools/sharpie/Sharpie.Bind/Massagers/PlatformTypeMappingMassager.cs index 72fe42d56406..6137a057916b 100644 --- a/tools/sharpie/Sharpie.Bind/Massagers/PlatformTypeMappingMassager.cs +++ b/tools/sharpie/Sharpie.Bind/Massagers/PlatformTypeMappingMassager.cs @@ -75,7 +75,7 @@ public override bool Initialize () nativeName ??= etName; var entry = (etNamespace + "." + etName, etNamespace, etName); - if (typeMap.TryGetValue (nativeName, out var existingEntry)) { + if (typeMap.ContainsKey (nativeName)) { // When two types map to the same native name, prefer the // standard protocol interface (named "I" + nativeName, e.g. // INSCopying for "NSCopying") over a [Model] class or a