Skip to content

Commit b427ce7

Browse files
rolfbjarneCopilotCopilot
authored
[sharpie] Prefer standard protocol interfaces over [Model] classes in type mapping (#24913)
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> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 310712a commit b427ce7

File tree

3 files changed

+31
-12
lines changed

3 files changed

+31
-12
lines changed

tests/sharpie/Tests/ObjCGenerics.iphoneos.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@
66
interface CNLabeledValue : INSCopying, INSSecureCoding {
77
// @property (readonly, copy, nonatomic) ValueType ValueTypeProperty;
88
[Export ("ValueTypeProperty", ArgumentSemantic.Copy)]
9-
NSObject<NSCopying, INSSecureCoding> ValueTypeProperty { get; }
9+
NSObject<INSCopying, INSSecureCoding> ValueTypeProperty { get; }
1010

1111
// -(ValueType _Nullable)getValueTypeMethod;
1212
[NullAllowed, Export ("getValueTypeMethod")]
1313
[Verify (MethodToProperty)]
14-
NSObject<NSCopying, INSSecureCoding> ValueTypeMethod { get; }
14+
NSObject<INSCopying, INSSecureCoding> ValueTypeMethod { get; }
1515

1616
// -(void)setValueTypeMethod:(ValueType _Nullable)obj;
1717
[Export ("setValueTypeMethod:")]
18-
void SetValueTypeMethod ([NullAllowed] NSObject<NSCopying, INSSecureCoding> obj);
18+
void SetValueTypeMethod ([NullAllowed] NSObject<INSCopying, INSSecureCoding> obj);
1919
}
2020

2121
// @protocol A

tests/sharpie/Tests/ObjCGenerics.macosx.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@
66
interface CNLabeledValue : INSCopying, INSSecureCoding {
77
// @property (readonly, copy, nonatomic) ValueType ValueTypeProperty;
88
[Export ("ValueTypeProperty", ArgumentSemantic.Copy)]
9-
NSObject<NSCopying, INSSecureCoding> ValueTypeProperty { get; }
9+
NSObject<INSCopying, INSSecureCoding> ValueTypeProperty { get; }
1010

1111
// -(ValueType _Nullable)getValueTypeMethod;
1212
[NullAllowed, Export ("getValueTypeMethod")]
1313
[Verify (MethodToProperty)]
14-
NSObject<NSCopying, INSSecureCoding> ValueTypeMethod { get; }
14+
NSObject<INSCopying, INSSecureCoding> ValueTypeMethod { get; }
1515

1616
// -(void)setValueTypeMethod:(ValueType _Nullable)obj;
1717
[Export ("setValueTypeMethod:")]
18-
void SetValueTypeMethod ([NullAllowed] NSObject<NSCopying, INSSecureCoding> obj);
18+
void SetValueTypeMethod ([NullAllowed] NSObject<INSCopying, INSSecureCoding> obj);
1919
}
2020

2121
// @protocol A

tools/sharpie/Sharpie.Bind/Massagers/PlatformTypeMappingMassager.cs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Sharpie.Bind.Massagers;
1212
[RegisterBefore (typeof (GenerateUsingStatementsMassager))]
1313
public sealed class PlatformTypeMappingMassager : Massager<PlatformTypeMappingMassager> {
1414
readonly Dictionary<string, (string FullName, string Namespace, string Name)> typeMap = new ();
15-
readonly Dictionary<string, (string FullName, string Namespace, string Name)> protocolMap = new ();
15+
readonly HashSet<string> protocolEntries = new ();
1616
readonly Stack<bool> ignoreType = new Stack<bool> ();
1717

1818
public PlatformTypeMappingMassager (ObjectiveCBinder binder)
@@ -23,6 +23,7 @@ public PlatformTypeMappingMassager (ObjectiveCBinder binder)
2323
public override bool Initialize ()
2424
{
2525
typeMap.Clear ();
26+
protocolEntries.Clear ();
2627

2728
var path = base.Binder.PlatformAssembly;
2829
var decoder = new TypelessDecoder ();
@@ -73,12 +74,30 @@ public override bool Initialize ()
7374
var etName = mr.GetString (et.Name);
7475
nativeName ??= etName;
7576

76-
var map = typeMap;
77-
if (map.Remove (nativeName)) {
78-
// there would be a collision, so skip adding again
79-
continue;
77+
var entry = (etNamespace + "." + etName, etNamespace, etName);
78+
if (typeMap.ContainsKey (nativeName)) {
79+
// When two types map to the same native name, prefer the
80+
// standard protocol interface (named "I" + nativeName, e.g.
81+
// INSCopying for "NSCopying") over a [Model] class or a
82+
// non-standard protocol. If neither or both follow the
83+
// convention, drop both (genuine ambiguity).
84+
bool newIsStandardProtocol = isProtocolAttribute && etName == "I" + nativeName;
85+
bool existingIsStandardProtocol = protocolEntries.Contains (nativeName);
86+
if (newIsStandardProtocol && !existingIsStandardProtocol) {
87+
typeMap [nativeName] = entry;
88+
protocolEntries.Add (nativeName);
89+
} else if (!newIsStandardProtocol && existingIsStandardProtocol) {
90+
// existing is the standard protocol, keep it
91+
} else {
92+
// genuine collision, drop both
93+
typeMap.Remove (nativeName);
94+
protocolEntries.Remove (nativeName);
95+
}
96+
} else {
97+
typeMap.Add (nativeName, entry);
98+
if (isProtocolAttribute && etName == "I" + nativeName)
99+
protocolEntries.Add (nativeName);
80100
}
81-
map.Add (nativeName, (etNamespace + "." + etName, etNamespace, etName));
82101
}
83102

84103
return typeMap.Count > 0;

0 commit comments

Comments
 (0)