Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Commit 15c8879

Browse files
authored
[generator] Use decl type's @deprecated-since if < member's (#1068)
Context: https://discord.com/channels/732297728826277939/732297837953679412/1052239604905934848 Imagine a type is deprecated in API-25 but its members were not explicitly marked as deprecated: @deprecated public class TabActivity extends ActivityGroup { public void setDefaultTab(String tag) { ... } } Then in API-29 the member is explicitly marked as deprecated: @deprecated public class TabActivity extends ActivityGroup { @deprecated public void setDefaultTab(String tag) { ... } } Due to the way `api-merge` calculates `@deprecated-since`, these would end up with different values, with a resulting C# API: [ObsoletedOSPlatform("android25.0")] public class TabActivity : ActivityGroup { [ObsoletedOSPlatform("android29.0")] public void SetDefaultTab (string tag) { ... } } While technically "fine", it is confusing that the method says it was obsoleted in API-29 instead of API-25. To fix this, if a member has a "higher" `@deprecated-since` value than its declaring type, we set the member's `@deprecated-since` value to the declaring type's `@deprecated-since` value.
1 parent 525a45d commit 15c8879

3 files changed

Lines changed: 40 additions & 2 deletions

File tree

‎tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ public void ObsoletedOSPlatformAttributeUnneededSupport ()
576576
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
577577
</package>
578578
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
579-
<class abstract='false' deprecated='This is a class deprecated since 19!' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;' deprecated-since='19'>
579+
<class abstract='false' deprecated='This is a class deprecated since 28!' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;' deprecated-since='28'>
580580
<field deprecated='This is a field deprecated since 0!' final='true' name='ACCEPT_HANDOVER' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;android.permission.ACCEPT_HANDOVER&quot;' visibility='public' volatile='false' deprecated-since='0'></field>
581581
<constructor deprecated='This is a constructor deprecated since empty string!' final='false' name='MyClass' jni-signature='()V' bridge='false' static='false' type='com.xamarin.android.MyClass' synthetic='false' visibility='public' deprecated-since=''></constructor>
582582
<method abstract='true' deprecated='deprecated' final='false' name='countAffectedRows' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='25'></method>
@@ -599,7 +599,6 @@ public void ObsoletedOSPlatformAttributeUnneededSupport ()
599599
generator.Context.ContextTypes.Pop();
600600

601601
// These should use [Obsolete] because they have always been obsolete in all currently supported versions (21+)
602-
Assert.True(writer.ToString().Contains("[global::System.Obsolete (@\"This is a class deprecated since 19!\")]"),writer.ToString());
603602
Assert.True(writer.ToString().Contains("[global::System.Obsolete (@\"This is a field deprecated since 0!\")]"),writer.ToString());
604603
Assert.True(writer.ToString().Contains("[global::System.Obsolete (@\"This is a constructor deprecated since empty string!\")]"),writer.ToString());
605604

‎tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ public void CreateCtor_CorrectApiSinceFromClass ()
7878
Assert.AreEqual(7,klass.Ctors[0].ApiAvailableSince);
7979
}
8080

81+
[Test]
82+
publicvoidCreateCtor_CorrectDeprecatedSinceFromClass()
83+
{
84+
varxml=XDocument.Parse("<package name='com.example.test' jni-name='com/example/test'><class name='test' deprecated-since='7'><constructor name='ctor' deprecated-since='17' /></class></package>");
85+
varklass=XmlApiImporter.CreateClass(xml.Root,xml.Root.Element("class"),opt);
86+
87+
Assert.AreEqual(7,klass.Ctors[0].DeprecatedSince);
88+
}
89+
8190
[Test]
8291
publicvoidCreateField_StudlyCaseName()
8392
{
@@ -138,6 +147,15 @@ public void CreateField_CorrectApiVersionFromClass ()
138147
Assert.AreEqual(7,field.ApiAvailableSince);
139148
}
140149

150+
[Test]
151+
publicvoidCreateField_CorrectDeprecatedSinceFromClass()
152+
{
153+
varxml=XDocument.Parse("<package name='com.example.test' jni-name='com/example/test'><class name='test' deprecated-since='7'><field name='$3' deprecated-since='17' /></class></package>");
154+
varklass=XmlApiImporter.CreateClass(xml.Root,xml.Root.Element("class"),opt);
155+
156+
Assert.AreEqual(7,klass.Fields[0].DeprecatedSince);
157+
}
158+
141159
[Test]
142160
publicvoidCreateInterface_EnsureValidName()
143161
{
@@ -212,6 +230,15 @@ public void CreateMethod_CorrectApiSinceFromClass ()
212230
Assert.AreEqual(7,klass.Methods[0].ApiAvailableSince);
213231
}
214232

233+
[Test]
234+
publicvoidCreateMethod_CorrectDeprecatedSinceFromClass()
235+
{
236+
varxml=XDocument.Parse("<package name='com.example.test' jni-name='com/example/test'><class name='test' deprecated-since='7'><method name='-3' deprecated-since='17' /></class></package>");
237+
varklass=XmlApiImporter.CreateClass(xml.Root,xml.Root.Element("class"),opt);
238+
239+
Assert.AreEqual(7,klass.Methods[0].DeprecatedSince);
240+
}
241+
215242
[Test]
216243
publicvoidCreateParameter_EnsureValidName()
217244
{

‎tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ public static Ctor CreateCtor (GenBase declaringType, XElement elem, CodeGenerat
191191

192192
ctor.Name=EnsureValidIdentifer(ctor.Name);
193193

194+
// If declaring type was deprecated earlier than member, use the type's deprecated-since
195+
if(declaringType.DeprecatedSince.HasValue&&declaringType.DeprecatedSince.Value<ctor.DeprecatedSince.GetValueOrDefault(0))
196+
ctor.DeprecatedSince=declaringType.DeprecatedSince;
197+
194198
FillApiSince(ctor,elem);
195199

196200
returnctor;
@@ -230,6 +234,10 @@ public static Field CreateField (GenBase declaringType, XElement elem, CodeGener
230234
field.Name=EnsureValidIdentifer(field.Name);
231235
}
232236

237+
// If declaring type was deprecated earlier than member, use the type's deprecated-since
238+
if(declaringType.DeprecatedSince.HasValue&&declaringType.DeprecatedSince.Value<field.DeprecatedSince.GetValueOrDefault(0))
239+
field.DeprecatedSince=declaringType.DeprecatedSince;
240+
233241
FillApiSince(field,elem);
234242
SetLineInfo(field,elem,options);
235243

@@ -398,6 +406,10 @@ public static Method CreateMethod (GenBase declaringType, XElement elem, CodeGen
398406

399407
method.FillReturnType();
400408

409+
// If declaring type was deprecated earlier than member, use the type's deprecated-since
410+
if(declaringType.DeprecatedSince.HasValue&&declaringType.DeprecatedSince.Value<method.DeprecatedSince.GetValueOrDefault(0))
411+
method.DeprecatedSince=declaringType.DeprecatedSince;
412+
401413
FillApiSince(method,elem);
402414
SetLineInfo(method,elem,options);
403415

0 commit comments

Comments
 (0)