From c5e1b92ac1fa95df5bc44202fb5b6528ccb020e9 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 16 Dec 2025 13:57:53 -0600 Subject: [PATCH 1/2] [generator] Fix `UnsupportedOSPlatform` for property setters when base has getter-only Context: https://github.com/dotnet/android/pull/10510#issuecomment-3325250701 When a derived class has a property setter with `ApiRemovedSince`, but the base class only has a getter (no setter), clear the setter's `ApiRemovedSince` if the base getter is not removed. Also handle standalone `setXxx` methods that correspond to base class properties. Fixes `CA1416` warning for `ListView.Adapter.set` being incorrectly marked as unsupported on `android15.0`. --- .../Unit-Tests/CodeGeneratorTests.cs | 37 +++++++++++++++++++ .../GenBase.cs | 23 ++++++++++++ 2 files changed, 60 insertions(+) diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs index bc7adf68e..bd952f6f4 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs @@ -1563,6 +1563,43 @@ public void UnsupportedOSPlatformIgnoresPropertyOverrides () StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android30.0\")]", ifaceActual, "Should not contain UnsupportedOSPlatform on interface property override!"); } + [Test] + public void UnsupportedOSPlatformIgnoresPropertySetterOverridesWhenBaseHasGetterOnly () + { + // Given: + // public class AdapterView { + // public Object getAdapter () { ... } + // } + // public class ListView : AdapterView { + // public Object getAdapter () { ... } // removed-since = 15 + // public void setAdapter (Object value) { ... } // removed-since = 15 + // } + // We should not write [UnsupportedOSPlatform] on ListView.Adapter.set because the base property (via getter) isn't "removed". + var xml = @$" + + + + + + + + + + + + + + + "; + + var gens = ParseApiDefinition (xml); + var klass = gens.Single (g => g.Name == "ListView"); + var actual = GetGeneratedTypeOutput (klass); + + // Neither the getter nor the setter should have [UnsupportedOSPlatform] because the base property (getter) isn't removed + StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android15.0\")]", actual, "Should not contain UnsupportedOSPlatform on property setter when base has getter only!"); + } + [Test] public void StringPropertyOverride ([Values ("true", "false")] string final) { diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs index 4f0ca0b8e..215ff1df0 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs @@ -366,6 +366,10 @@ public void FixupMethodOverrides (CodeGenerationOptions opt) prop.Setter.ApiRemovedSince = default; shouldBreak = true; } + } else if (prop.Setter != null && prop.Setter.ApiRemovedSince > 0 && baseProp.Setter == null && baseProp.Getter != null && baseProp.Getter.ApiRemovedSince == 0) { + // Base has getter-only property; setter in derived should not be marked removed + prop.Setter.ApiRemovedSince = default; + shouldBreak = true; } if (shouldBreak) { break; @@ -373,6 +377,21 @@ public void FixupMethodOverrides (CodeGenerationOptions opt) } } + // Process standalone setter methods (setXxx) that correspond to base class properties. + // If the base property getter isn't removed, the setter shouldn't be either. + foreach (var m in Methods.Where (m => !m.IsStatic && !m.IsInterfaceDefaultMethod && m.ApiRemovedSince > 0)) { + if (!m.JavaName.StartsWith ("set", StringComparison.Ordinal) || m.Parameters.Count != 1 || m.RetVal.JavaName != "void") + continue; + var propertyName = m.JavaName.Substring (3); + for (var bt = GetBaseGen (opt); bt != null; bt = bt.GetBaseGen (opt)) { + var baseProp = bt.Properties.FirstOrDefault (p => p.Getter?.JavaName == "get" + propertyName); + if (baseProp?.Getter != null && baseProp.Getter.ApiRemovedSince == 0) { + m.ApiRemovedSince = default; + break; + } + } + } + // Process interface inheritance for both regular and default interface methods if (this is InterfaceGen currentInterface) { // For interfaces, check all base interfaces (interfaces that this interface implements/extends) @@ -419,6 +438,10 @@ public void FixupMethodOverrides (CodeGenerationOptions opt) prop.Setter.ApiRemovedSince = default; shouldBreak = true; } + } else if (prop.Setter != null && prop.Setter.ApiRemovedSince > 0 && baseProp.Setter == null && baseProp.Getter != null && baseProp.Getter.ApiRemovedSince == 0) { + // Base has getter-only property; setter in derived should not be marked removed + prop.Setter.ApiRemovedSince = default; + shouldBreak = true; } if (shouldBreak) { break; From 25e6a9aaaf2d7acb945e08f6e0819f4da2d83c77 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 16 Dec 2025 15:59:09 -0600 Subject: [PATCH 2/2] Update CodeGeneratorTests.cs --- .../Unit-Tests/CodeGeneratorTests.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs index bd952f6f4..11f0219c5 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs @@ -1600,6 +1600,43 @@ public void UnsupportedOSPlatformIgnoresPropertySetterOverridesWhenBaseHasGetter StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android15.0\")]", actual, "Should not contain UnsupportedOSPlatform on property setter when base has getter only!"); } + [Test] + public void UnsupportedOSPlatformIgnoresStandaloneSetterMethodWhenBaseHasGetterOnly () + { + // Given: + // public class AdapterView { + // public Object getAdapter () { ... } + // } + // public class ListView : AdapterView { + // // no getAdapter override + // public void setAdapter (Object value) { ... } // removed-since = 15 + // } + // We should not write [UnsupportedOSPlatform] on ListView.SetAdapter because the base property (via getter) isn't "removed". + // The setAdapter remains a standalone method because there's no getAdapter to pair with in the derived class. + var xml = @$" + + + + + + + + + + + + + + "; + + var gens = ParseApiDefinition (xml); + var klass = gens.Single (g => g.Name == "ListView"); + var actual = GetGeneratedTypeOutput (klass); + + // The standalone setter method should not have [UnsupportedOSPlatform] because the base property (getter) isn't removed + StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android15.0\")]", actual, "Should not contain UnsupportedOSPlatform on standalone setter method when base has getter only!"); + } + [Test] public void StringPropertyOverride ([Values ("true", "false")] string final) {