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

Commit ef82a07

Browse files
[generator] FixupMethodOverrides() checks properties (#1366)
Context: #1313Fixes: dotnet/android#10509 In .NET 10 RC 1, `View.Foreground` is defined in C# as: [SupportedOSPlatform("android23.0")] public virtual Drawable? Foreground { get; set; } But `FrameLayout.Foreground` is defined as: [UnsupportedOSPlatform("android23.0")] public override Drawable? Foreground { get; set; } I think this is the exact same bug that was addressed in #1313 for methods, but we still have the issue for properties. I was able to duplicate the test and make a `getThing()` and `setThing()` methods in Java to see the issue. I updated the `FixupMethodOverrides()` to iterate over properties and do the same fixup for property getters and setters. This also needed to be done for: * Interfaces * Fields (think of `static final int` values)
1 parent 19be370 commit ef82a07

2 files changed

Lines changed: 221 additions & 17 deletions

File tree

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

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,13 +1441,23 @@ public void UnsupportedOSPlatformConstFields ()
14411441
publicvoidUnsupportedOSPlatformIgnoresMethodOverrides()
14421442
{
14431443
// Given:
1444+
// Class inheritance scenario:
14441445
// public class TextView {
14451446
// public Object doThing () { ... }
14461447
// }
14471448
// public class TextView2 : TextView {
14481449
// public Object doThing () { ... } // removed-since = 30
14491450
// }
1450-
// We should not write [UnsupportedOSPlatform] on TextView2.doThing (), because the base method isn't "removed".
1451+
// Interface inheritance scenario:
1452+
// public interface IFoo {
1453+
// public Object doSomething () { ... }
1454+
// public static final String DATE_TAKEN = "datetaken";
1455+
// }
1456+
// public interface IBar : IFoo {
1457+
// public Object doSomething () { ... } // removed-since = 30
1458+
// public static final String DATE_TAKEN = "datetaken"; // removed-since = 30
1459+
// }
1460+
// We should not write [UnsupportedOSPlatform] on overriding methods or fields, because the base methods/fields aren't "removed".
14511461
varxml=@$"<api>
14521462
<package name='java.lang' jni-name='java/lang'>
14531463
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
@@ -1460,13 +1470,97 @@ public void UnsupportedOSPlatformIgnoresMethodOverrides ()
14601470
<method abstract='false' deprecated='not deprecated' final='false' name='doThing' bridge='false' native='false' return='java.lang.Object' static='false' synchronized='false' synthetic='false' visibility='public' removed-since='30' />
14611471
</class>
14621472
</package>
1473+
<package name='com.example' jni-name='com/example'>
1474+
<interface abstract='true' deprecated='not deprecated' final='false' name='Foo' static='false' visibility='public' jni-signature='Lcom/example/Foo;'>
1475+
<method abstract='true' deprecated='not deprecated' final='false' name='doSomething' bridge='false' native='false' return='java.lang.Object' static='false' synchronized='false' synthetic='false' visibility='public' />
1476+
<field deprecated='not deprecated' final='true' name='DATE_TAKEN' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;datetaken&quot;' visibility='public' volatile='false'></field>
1477+
</interface>
1478+
<interface abstract='true' deprecated='not deprecated' final='false' name='Bar' static='false' visibility='public' jni-signature='Lcom/example/Bar;'>
1479+
<implements name='com.example.Foo' name-generic-aware='com.example.Foo' jni-type='Lcom/example/Foo;'></implements>
1480+
<method abstract='true' deprecated='not deprecated' final='false' name='doSomething' bridge='false' native='false' return='java.lang.Object' static='false' synchronized='false' synthetic='false' visibility='public' removed-since='30' />
1481+
<field deprecated='not deprecated' final='true' name='DATE_TAKEN' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;datetaken&quot;' visibility='public' volatile='false' removed-since='30'></field>
1482+
</interface>
1483+
</package>
1484+
</api>";
1485+
1486+
vargens=ParseApiDefinition(xml);
1487+
1488+
// Test class inheritance scenario
1489+
varklass=gens.Single(g =>g.Name=="TextView2");
1490+
varactual=GetGeneratedTypeOutput(klass);
1491+
StringAssert.DoesNotContain("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android30.0\")]",actual,"Should not contain UnsupportedOSPlatform on class override!");
1492+
1493+
// Test interface inheritance scenario
1494+
variface=gens.OfType<InterfaceGen>().Single(g =>g.Name=="IBar");
1495+
varifaceActual=GetGeneratedTypeOutput(iface);
1496+
StringAssert.DoesNotContain("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android30.0\")]",ifaceActual,"Should not contain UnsupportedOSPlatform on interface override!");
1497+
}
1498+
1499+
[Test]
1500+
publicvoidUnsupportedOSPlatformIgnoresPropertyOverrides()
1501+
{
1502+
// Given:
1503+
// Class inheritance scenario:
1504+
// public class TextView {
1505+
// public Object getThing () { ... }
1506+
// public void setThing (Object value) { ... }
1507+
// }
1508+
// public class TextView2 : TextView {
1509+
// public Object getThing () { ... } // removed-since = 30
1510+
// public void setThing (Object value) { ... } // removed-since = 30
1511+
// }
1512+
// Interface inheritance scenario:
1513+
// public interface IPropertyProvider {
1514+
// public Object getSomething () { ... }
1515+
// public static final String DATE_TAKEN = "datetaken";
1516+
// }
1517+
// public interface IExtendedProvider : IPropertyProvider {
1518+
// public Object getSomething () { ... } // removed-since = 30
1519+
// public static final String DATE_TAKEN = "datetaken"; // removed-since = 30
1520+
// }
1521+
// We should not write [UnsupportedOSPlatform] on overriding properties or fields, because the base methods/fields aren't "removed".
1522+
varxml=@$"<api>
1523+
<package name='java.lang' jni-name='java/lang'>
1524+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
1525+
</package>
1526+
<package name='android.widget' jni-name='android/widget'>
1527+
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' final='false' name='TextView' static='false' visibility='public'>
1528+
<method abstract='false' deprecated='not deprecated' final='false' name='getThing' bridge='false' native='false' return='java.lang.Object' static='false' synchronized='false' synthetic='false' visibility='public' />
1529+
<method abstract='false' deprecated='not deprecated' final='false' name='setThing' bridge='false' native='false' return='void' static='false' synchronized='false' synthetic='false' visibility='public'>
1530+
<parameter name='value' type='java.lang.Object' />
1531+
</method>
1532+
</class>
1533+
<class abstract='false' deprecated='not deprecated' extends='android.widget.TextView' extends-generic-aware='java.lang.Object' final='false' name='TextView2' static='false' visibility='public'>
1534+
<method abstract='false' deprecated='not deprecated' final='false' name='getThing' bridge='false' native='false' return='java.lang.Object' static='false' synchronized='false' synthetic='false' visibility='public' removed-since='30' />
1535+
<method abstract='false' deprecated='not deprecated' final='false' name='setThing' bridge='false' native='false' return='void' static='false' synchronized='false' synthetic='false' visibility='public' removed-since='30'>
1536+
<parameter name='value' type='java.lang.Object' />
1537+
</method>
1538+
</class>
1539+
</package>
1540+
<package name='com.example' jni-name='com/example'>
1541+
<interface abstract='true' deprecated='not deprecated' final='false' name='PropertyProvider' static='false' visibility='public' jni-signature='Lcom/example/PropertyProvider;'>
1542+
<method abstract='true' deprecated='not deprecated' final='false' name='getSomething' bridge='false' native='false' return='java.lang.Object' static='false' synchronized='false' synthetic='false' visibility='public' />
1543+
<field deprecated='not deprecated' final='true' name='DATE_TAKEN' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;datetaken&quot;' visibility='public' volatile='false'></field>
1544+
</interface>
1545+
<interface abstract='true' deprecated='not deprecated' final='false' name='ExtendedProvider' static='false' visibility='public' jni-signature='Lcom/example/ExtendedProvider;'>
1546+
<implements name='com.example.PropertyProvider' name-generic-aware='com.example.PropertyProvider' jni-type='Lcom/example/PropertyProvider;'></implements>
1547+
<method abstract='true' deprecated='not deprecated' final='false' name='getSomething' bridge='false' native='false' return='java.lang.Object' static='false' synchronized='false' synthetic='false' visibility='public' removed-since='30' />
1548+
<field deprecated='not deprecated' final='true' name='DATE_TAKEN' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;datetaken&quot;' visibility='public' volatile='false' removed-since='30'></field>
1549+
</interface>
1550+
</package>
14631551
</api>";
14641552

14651553
vargens=ParseApiDefinition(xml);
1554+
1555+
// Test class inheritance scenario
14661556
varklass=gens.Single(g =>g.Name=="TextView2");
14671557
varactual=GetGeneratedTypeOutput(klass);
1558+
StringAssert.DoesNotContain("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android30.0\")]",actual,"Should not contain UnsupportedOSPlatform on class property override!");
14681559

1469-
StringAssert.DoesNotContain("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android30.0\")]",actual,"Should contain UnsupportedOSPlatform!");
1560+
// Test interface inheritance scenario
1561+
variface=gens.OfType<InterfaceGen>().Single(g =>g.Name=="IExtendedProvider");
1562+
varifaceActual=GetGeneratedTypeOutput(iface);
1563+
StringAssert.DoesNotContain("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android30.0\")]",ifaceActual,"Should not contain UnsupportedOSPlatform on interface property override!");
14701564
}
14711565

14721566
[Test]

‎tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs‎

Lines changed: 125 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -307,27 +307,137 @@ public virtual void FixupExplicitImplementation ()
307307

308308
publicvoidFixupMethodOverrides(CodeGenerationOptionsopt)
309309
{
310+
// Process regular methods (non-static, non-interface default methods)
310311
foreach(varminMethods.Where(m =>!m.IsStatic&&!m.IsInterfaceDefaultMethod)){
311312
for(varbt=GetBaseGen(opt);bt!=null;bt=bt.GetBaseGen(opt)){
312-
varbm=bt.Methods.FirstOrDefault(mm =>mm.Name==m.Name&&mm.Visibility==m.Visibility&&ParameterList.Equals(mm.Parameters,m.Parameters));
313-
if(bm!=null&&bm.RetVal.FullName==m.RetVal.FullName){// if return type is different, it could be still "new", not "override".
314-
m.IsOverride=true;
315-
316-
if(opt.FixObsoleteOverrides){
317-
// If method overrides a deprecated method, it also needs to be marked as deprecated
318-
if(bm.Deprecated.HasValue()&&!m.Deprecated.HasValue())
319-
m.Deprecated=bm.Deprecated;
320-
321-
// Fix issue when base method was deprecated before the overriding method, set both both to base method value
322-
if(bm.DeprecatedSince.GetValueOrDefault(default)<m.DeprecatedSince.GetValueOrDefault(default))
323-
m.DeprecatedSince=bm.DeprecatedSince;
313+
varbm=bt.Methods.FirstOrDefault(mm =>
314+
mm.Name==m.Name&&
315+
mm.Visibility==m.Visibility&&
316+
mm.RetVal.FullName==m.RetVal.FullName&&// if return type is different, it could be still "new", not "override".
317+
ParameterList.Equals(mm.Parameters,m.Parameters));
318+
if(bm==null){
319+
continue;
320+
}
321+
322+
m.IsOverride=true;
323+
if(opt.FixObsoleteOverrides){
324+
// If method overrides a deprecated method, it also needs to be marked as deprecated
325+
if(bm.Deprecated.HasValue()&&!m.Deprecated.HasValue())
326+
m.Deprecated=bm.Deprecated;
327+
328+
// Fix issue when base method was deprecated before the overriding method, set both both to base method value
329+
if(bm.DeprecatedSince.GetValueOrDefault(default)<m.DeprecatedSince.GetValueOrDefault(default))
330+
m.DeprecatedSince=bm.DeprecatedSince;
331+
}
332+
333+
// If a "removed" method overrides a "not removed" method, the method was
334+
// likely moved to a base class, so don't mark it as removed.
335+
if(m.ApiRemovedSince>0&&bm.ApiRemovedSince==0){
336+
m.ApiRemovedSince=default;
337+
}
338+
break;
339+
}
340+
}
341+
342+
// Process property getter/setter methods for ApiRemovedSince fixup
343+
foreach(varpropinProperties){
344+
for(varbt=GetBaseGen(opt);bt!=null;bt=bt.GetBaseGen(opt)){
345+
varbaseProp=bt.Properties.FirstOrDefault(p =>p.Name==prop.Name&&p.Type==prop.Type);
346+
if(baseProp==null){
347+
continue;
348+
}
349+
350+
boolshouldBreak=false;
351+
if(prop.Getter!=null&&prop.Getter.ApiRemovedSince>0&&baseProp.Getter!=null&&baseProp.Getter.ApiRemovedSince==0){
352+
if(baseProp.Getter.Visibility==prop.Getter.Visibility&&
353+
ParameterList.Equals(baseProp.Getter.Parameters,prop.Getter.Parameters)&&
354+
baseProp.Getter.RetVal.FullName==prop.Getter.RetVal.FullName){
355+
// If a "removed" property getter overrides a "not removed" getter, the method was
356+
// likely moved to a base class, so don't mark it as removed.
357+
prop.Getter.ApiRemovedSince=default;
358+
shouldBreak=true;
324359
}
360+
}
361+
if(prop.Setter!=null&&prop.Setter.ApiRemovedSince>0&&baseProp.Setter!=null&&baseProp.Setter.ApiRemovedSince==0){
362+
if(baseProp.Setter.Visibility==prop.Setter.Visibility&&
363+
ParameterList.Equals(baseProp.Setter.Parameters,prop.Setter.Parameters)){
364+
// If a "removed" property setter overrides a "not removed" setter, the method was
365+
// likely moved to a base class, so don't mark it as removed.
366+
prop.Setter.ApiRemovedSince=default;
367+
shouldBreak=true;
368+
}
369+
}
370+
if(shouldBreak){
371+
break;
372+
}
373+
}
374+
}
325375

326-
// If a "removed" method overrides a "not removed" method, the method was
327-
// likely moved to a base class, so don't mark it as removed.
328-
if(m.ApiRemovedSince>0&&bm.ApiRemovedSince==0)
376+
// Process interface inheritance for both regular and default interface methods
377+
if(thisisInterfaceGencurrentInterface){
378+
// For interfaces, check all base interfaces (interfaces that this interface implements/extends)
379+
varbaseInterfaces=currentInterface.GetAllDerivedInterfaces();
380+
381+
foreach(varminMethods.Where(m =>!m.IsStatic)){
382+
foreach(varbaseIfaceinbaseInterfaces){
383+
varbm=baseIface.Methods.FirstOrDefault(mm =>
384+
mm.Name==m.Name&&
385+
mm.Visibility==m.Visibility&&
386+
mm.RetVal.FullName==m.RetVal.FullName&&
387+
ParameterList.Equals(mm.Parameters,m.Parameters));
388+
if(bm==null){
389+
continue;
390+
}
391+
// If a "removed" interface method overrides a "not removed" method, the method was
392+
// likely moved to a base interface, so don't mark it as removed.
393+
if(m.ApiRemovedSince>0&&bm.ApiRemovedSince==0){
329394
m.ApiRemovedSince=default;
395+
}
396+
break;
397+
}
398+
}
399+
400+
// Process interface property getter/setter methods for ApiRemovedSince fixup
401+
foreach(varpropinProperties){
402+
foreach(varbaseIfaceinbaseInterfaces){
403+
varbaseProp=baseIface.Properties.FirstOrDefault(p =>p.Name==prop.Name&&p.Type==prop.Type);
404+
if(baseProp==null)
405+
continue;
406+
407+
boolshouldBreak=false;
408+
if(prop.Getter!=null&&prop.Getter.ApiRemovedSince>0&&baseProp.Getter!=null&&baseProp.Getter.ApiRemovedSince==0){
409+
if(baseProp.Getter.Visibility==prop.Getter.Visibility&&
410+
ParameterList.Equals(baseProp.Getter.Parameters,prop.Getter.Parameters)&&
411+
baseProp.Getter.RetVal.FullName==prop.Getter.RetVal.FullName){
412+
prop.Getter.ApiRemovedSince=default;
413+
shouldBreak=true;
414+
}
415+
}
416+
if(prop.Setter!=null&&prop.Setter.ApiRemovedSince>0&&baseProp.Setter!=null&&baseProp.Setter.ApiRemovedSince==0){
417+
if(baseProp.Setter.Visibility==prop.Setter.Visibility&&
418+
ParameterList.Equals(baseProp.Setter.Parameters,prop.Setter.Parameters)){
419+
prop.Setter.ApiRemovedSince=default;
420+
shouldBreak=true;
421+
}
422+
}
423+
if(shouldBreak){
424+
break;
425+
}
426+
}
427+
}
330428

429+
// Process interface field inheritance for ApiRemovedSince fixup
430+
foreach(varfieldinFields){
431+
foreach(varbaseIfaceinbaseInterfaces){
432+
varbaseField=baseIface.Fields.FirstOrDefault(f =>f.Name==field.Name&&f.TypeName==field.TypeName&&f.Visibility==field.Visibility);
433+
if(baseField==null){
434+
continue;
435+
}
436+
// If a "removed" interface field overrides a "not removed" field, the field was
437+
// likely moved to a base interface, so don't mark it as removed.
438+
if(field.ApiRemovedSince>0&&baseField.ApiRemovedSince==0){
439+
field.ApiRemovedSince=default;
440+
}
331441
break;
332442
}
333443
}

0 commit comments

Comments
 (0)