From 11e2bacfc2d4dcfadf507cbf23091c57a03bb316 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 14 Jul 2026 16:07:51 -0500 Subject: [PATCH 1/2] Fix platform attributes on specialized setters Match removed property accessors against supported Java base methods, including generic setter specializations. Restore the analyzer regression cases and add GridView.Adapter coverage for #12107. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5ee5ebf9-0580-4cab-916a-a702e18c5dc3 --- .../Unit-Tests/CodeGeneratorTests.cs | 46 +++++++++++++++++++ .../GenBase.cs | 43 ++++++++--------- .../Xamarin.Android.Build.Tests/BuildTest2.cs | 12 +++++ 3 files changed, 76 insertions(+), 25 deletions(-) diff --git a/external/Java.Interop/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs b/external/Java.Interop/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs index bc7adf68e67..185088690a9 100644 --- a/external/Java.Interop/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs +++ b/external/Java.Interop/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs @@ -1563,6 +1563,52 @@ public void UnsupportedOSPlatformIgnoresPropertyOverrides () StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android30.0\")]", ifaceActual, "Should not contain UnsupportedOSPlatform on interface property override!"); } + [Test] + public void UnsupportedOSPlatformIgnoresPropertyAccessorMovedToBaseMethod () + { + var xml = @$" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "; + + var gens = ParseApiDefinition (xml); + var klass = gens.Single (g => g.Name == "GridView"); + var actual = GetGeneratedTypeOutput (klass); + + StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android15.0\")]", actual); + } + [Test] public void StringPropertyOverride ([Values ("true", "false")] string final) { diff --git a/external/Java.Interop/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs b/external/Java.Interop/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs index 4f0ca0b8e01..029fe9837dc 100644 --- a/external/Java.Interop/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs +++ b/external/Java.Interop/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs @@ -339,37 +339,30 @@ public void FixupMethodOverrides (CodeGenerationOptions opt) } } - // Process property getter/setter methods for ApiRemovedSince fixup + // A property accessor can correspond to a standalone method in a base class + // when the accessor types differ. Compare the underlying Java methods. foreach (var prop in Properties) { - for (var bt = GetBaseGen (opt); bt != null; bt = bt.GetBaseGen (opt)) { - var baseProp = bt.Properties.FirstOrDefault (p => p.Name == prop.Name && p.Type == prop.Type); - if (baseProp == null) { + foreach (var m in new [] { prop.Getter, prop.Setter }) { + if (m == null || m.ApiRemovedSince == 0) { continue; } - bool shouldBreak = false; - if (prop.Getter != null && prop.Getter.ApiRemovedSince > 0 && baseProp.Getter != null && baseProp.Getter.ApiRemovedSince == 0) { - if (baseProp.Getter.Visibility == prop.Getter.Visibility && - ParameterList.Equals (baseProp.Getter.Parameters, prop.Getter.Parameters) && - baseProp.Getter.RetVal.FullName == prop.Getter.RetVal.FullName) { - // If a "removed" property getter overrides a "not removed" getter, the method was - // likely moved to a base class, so don't mark it as removed. - prop.Getter.ApiRemovedSince = default; - shouldBreak = true; - } - } - if (prop.Setter != null && prop.Setter.ApiRemovedSince > 0 && baseProp.Setter != null && baseProp.Setter.ApiRemovedSince == 0) { - if (baseProp.Setter.Visibility == prop.Setter.Visibility && - ParameterList.Equals (baseProp.Setter.Parameters, prop.Setter.Parameters)) { - // If a "removed" property setter overrides a "not removed" setter, the method was - // likely moved to a base class, so don't mark it as removed. - prop.Setter.ApiRemovedSince = default; - shouldBreak = true; + for (var bt = GetBaseGen (opt); bt != null; bt = bt.GetBaseGen (opt)) { + var bm = bt.GetAllMethods ().FirstOrDefault (mm => + mm.ApiRemovedSince == 0 && + mm.JavaName == m.JavaName && + mm.Visibility == m.Visibility && + ((m == prop.Getter && + mm.RetVal.FullName == m.RetVal.FullName && + ParameterList.Equals (mm.Parameters, m.Parameters)) || + (m == prop.Setter && + (ParameterList.Equals (mm.Parameters, m.Parameters) || + (mm.Parameters.Count == 1 && mm.Parameters [0].IsGeneric))))); + if (bm != null) { + m.ApiRemovedSince = default; + break; } } - if (shouldBreak) { - break; - } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs index 91bbece641a..305a65d6de5 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs @@ -478,6 +478,18 @@ public void BuildHasNoWarnings (bool isRelease, bool multidex, string packageFor proj.SetProperty ("XamarinAndroidSupportSkipVerifyVersions", "True"); // Disables API 29 warning in Xamarin.Build.Download proj.SetProperty ("AndroidPackageFormat", packageFormat); proj.SetProperty ("TrimmerSingleWarn", "false"); + // Regression tests for: + // https://github.com/dotnet/android/issues/10509 + // https://github.com/dotnet/android/issues/12107 + proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_ONCREATE}", + """ + // These should not cause warnings + new FrameLayout (this).Foreground = null; + new GridView (this).Adapter = null; + new ListView (this).Adapter = null; + Console.WriteLine (Android.Provider.MediaStore.Video.IVideoColumns.DateTaken); + Console.WriteLine (Android.Provider.MediaStore.Images.IImageColumns.DateTaken); + """); using (var b = CreateApkBuilder ()) { Assert.IsTrue (b.Build (proj), "Build should have succeeded."); From c83eac4f213235cf3d51560e044ebe9534e9f88f Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 15 Jul 2026 08:38:19 -0500 Subject: [PATCH 2/2] Tighten generic setter matching Restrict specialized setter matching to generic type parameters so concrete generic overloads do not suppress valid platform attributes. Add focused positive and negative property-accessor regression tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5ee5ebf9-0580-4cab-916a-a702e18c5dc3 --- .../Unit-Tests/CodeGeneratorTests.cs | 85 +++++++++++++++++-- .../GenBase.cs | 2 +- 2 files changed, 79 insertions(+), 8 deletions(-) diff --git a/external/Java.Interop/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs b/external/Java.Interop/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs index 185088690a9..a12dc402216 100644 --- a/external/Java.Interop/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs +++ b/external/Java.Interop/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs @@ -1564,7 +1564,40 @@ public void UnsupportedOSPlatformIgnoresPropertyOverrides () } [Test] - public void UnsupportedOSPlatformIgnoresPropertyAccessorMovedToBaseMethod () + public void UnsupportedOSPlatformIgnoresPropertySetterMovedToStandaloneBaseMethod () + { + var xml = @$" + + + + + + + + + + + + + + + + + "; + + var gens = ParseApiDefinition (xml); + var klass = gens.Single (g => g.Name == "Derived"); + var property = klass.Properties.Single (p => p.Name == "Value"); + var actual = GetGeneratedTypeOutput (klass); + + Assert.IsNotNull (property.Setter, "The removed setter must be tested as a property accessor."); + StringAssert.Contains ("public virtual unsafe Java.Lang.Object Value {", actual); + StringAssert.Contains ("set {", actual); + StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android15.0\")]", actual); + } + + [Test] + public void UnsupportedOSPlatformIgnoresSpecializedGenericBasePropertySetter () { var xml = @$" @@ -1588,12 +1621,7 @@ public void UnsupportedOSPlatformIgnoresPropertyAccessorMovedToBaseMethod () - - - - - - + @@ -1604,11 +1632,54 @@ public void UnsupportedOSPlatformIgnoresPropertyAccessorMovedToBaseMethod () var gens = ParseApiDefinition (xml); var klass = gens.Single (g => g.Name == "GridView"); + var property = klass.Properties.Single (p => p.Name == "Adapter"); var actual = GetGeneratedTypeOutput (klass); + Assert.IsNotNull (property.Setter, "The specialized setter must be tested as a property accessor."); + StringAssert.Contains ("set {", actual); StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android15.0\")]", actual); } + [Test] + public void UnsupportedOSPlatformPreservesPropertySetterForDifferentGenericBaseParameter () + { + var xml = @$" + + + + + + + + + + + + + + + + + + + + + + + "; + + var gens = ParseApiDefinition (xml); + var baseSetter = gens.Single (g => g.Name == "Base").GetAllMethods ().Single (m => m.JavaName == "setValue"); + var klass = gens.Single (g => g.Name == "Derived"); + var property = klass.Properties.Single (p => p.Name == "Value"); + var actual = GetGeneratedTypeOutput (klass); + + Assert.IsTrue (baseSetter.Parameters [0].IsGeneric, $"Expected a concrete generic parameter, but found {baseSetter.Parameters [0].Symbol.GetType ().FullName}."); + Assert.IsNotNull (property.Setter, "The removed setter must be tested as a property accessor."); + StringAssert.Contains ("set {", actual); + StringAssert.Contains ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android15.0\")]", actual); + } + [Test] public void StringPropertyOverride ([Values ("true", "false")] string final) { diff --git a/external/Java.Interop/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs b/external/Java.Interop/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs index 029fe9837dc..d4a8dc8ba7c 100644 --- a/external/Java.Interop/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs +++ b/external/Java.Interop/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs @@ -357,7 +357,7 @@ public void FixupMethodOverrides (CodeGenerationOptions opt) ParameterList.Equals (mm.Parameters, m.Parameters)) || (m == prop.Setter && (ParameterList.Equals (mm.Parameters, m.Parameters) || - (mm.Parameters.Count == 1 && mm.Parameters [0].IsGeneric))))); + (mm.Parameters.Count == 1 && mm.Parameters [0].Symbol is GenericTypeParameter))))); if (bm != null) { m.ApiRemovedSince = default; break;