diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs index c02b7def7..bc7adf68e 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs @@ -1441,13 +1441,23 @@ public void UnsupportedOSPlatformConstFields () public void UnsupportedOSPlatformIgnoresMethodOverrides () { // Given: + // Class inheritance scenario: // public class TextView { // public Object doThing () { ... } // } // public class TextView2 : TextView { // public Object doThing () { ... } // removed-since = 30 // } - // We should not write [UnsupportedOSPlatform] on TextView2.doThing (), because the base method isn't "removed". + // Interface inheritance scenario: + // public interface IFoo { + // public Object doSomething () { ... } + // public static final String DATE_TAKEN = "datetaken"; + // } + // public interface IBar : IFoo { + // public Object doSomething () { ... } // removed-since = 30 + // public static final String DATE_TAKEN = "datetaken"; // removed-since = 30 + // } + // We should not write [UnsupportedOSPlatform] on overriding methods or fields, because the base methods/fields aren't "removed". var xml = @$" @@ -1460,13 +1470,97 @@ public void UnsupportedOSPlatformIgnoresMethodOverrides () + + + + + + + + + + + + "; + + var gens = ParseApiDefinition (xml); + + // Test class inheritance scenario + var klass = gens.Single (g => g.Name == "TextView2"); + var actual = GetGeneratedTypeOutput (klass); + StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android30.0\")]", actual, "Should not contain UnsupportedOSPlatform on class override!"); + + // Test interface inheritance scenario + var iface = gens.OfType ().Single (g => g.Name == "IBar"); + var ifaceActual = GetGeneratedTypeOutput (iface); + StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android30.0\")]", ifaceActual, "Should not contain UnsupportedOSPlatform on interface override!"); + } + + [Test] + public void UnsupportedOSPlatformIgnoresPropertyOverrides () + { + // Given: + // Class inheritance scenario: + // public class TextView { + // public Object getThing () { ... } + // public void setThing (Object value) { ... } + // } + // public class TextView2 : TextView { + // public Object getThing () { ... } // removed-since = 30 + // public void setThing (Object value) { ... } // removed-since = 30 + // } + // Interface inheritance scenario: + // public interface IPropertyProvider { + // public Object getSomething () { ... } + // public static final String DATE_TAKEN = "datetaken"; + // } + // public interface IExtendedProvider : IPropertyProvider { + // public Object getSomething () { ... } // removed-since = 30 + // public static final String DATE_TAKEN = "datetaken"; // removed-since = 30 + // } + // We should not write [UnsupportedOSPlatform] on overriding properties or fields, because the base methods/fields aren't "removed". + var xml = @$" + + + + + + + + + + + + + + + + + + + + + + + + + + + + "; var gens = ParseApiDefinition (xml); + + // Test class inheritance scenario var klass = gens.Single (g => g.Name == "TextView2"); var actual = GetGeneratedTypeOutput (klass); + StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android30.0\")]", actual, "Should not contain UnsupportedOSPlatform on class property override!"); - StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android30.0\")]", actual, "Should contain UnsupportedOSPlatform!"); + // Test interface inheritance scenario + var iface = gens.OfType ().Single (g => g.Name == "IExtendedProvider"); + var ifaceActual = GetGeneratedTypeOutput (iface); + StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android30.0\")]", ifaceActual, "Should not contain UnsupportedOSPlatform on interface property override!"); } [Test] diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs index 317835581..4f0ca0b8e 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs @@ -307,27 +307,137 @@ public virtual void FixupExplicitImplementation () public void FixupMethodOverrides (CodeGenerationOptions opt) { + // Process regular methods (non-static, non-interface default methods) foreach (var m in Methods.Where (m => !m.IsStatic && !m.IsInterfaceDefaultMethod)) { for (var bt = GetBaseGen (opt); bt != null; bt = bt.GetBaseGen (opt)) { - var bm = bt.Methods.FirstOrDefault (mm => mm.Name == m.Name && mm.Visibility == m.Visibility && ParameterList.Equals (mm.Parameters, m.Parameters)); - if (bm != null && bm.RetVal.FullName == m.RetVal.FullName) { // if return type is different, it could be still "new", not "override". - m.IsOverride = true; - - if (opt.FixObsoleteOverrides) { - // If method overrides a deprecated method, it also needs to be marked as deprecated - if (bm.Deprecated.HasValue () && !m.Deprecated.HasValue ()) - m.Deprecated = bm.Deprecated; - - // Fix issue when base method was deprecated before the overriding method, set both both to base method value - if (bm.DeprecatedSince.GetValueOrDefault (default) < m.DeprecatedSince.GetValueOrDefault (default)) - m.DeprecatedSince = bm.DeprecatedSince; + var bm = bt.Methods.FirstOrDefault (mm => + mm.Name == m.Name && + mm.Visibility == m.Visibility && + mm.RetVal.FullName == m.RetVal.FullName && // if return type is different, it could be still "new", not "override". + ParameterList.Equals (mm.Parameters, m.Parameters)); + if (bm == null) { + continue; + } + + m.IsOverride = true; + if (opt.FixObsoleteOverrides) { + // If method overrides a deprecated method, it also needs to be marked as deprecated + if (bm.Deprecated.HasValue () && !m.Deprecated.HasValue ()) + m.Deprecated = bm.Deprecated; + + // Fix issue when base method was deprecated before the overriding method, set both both to base method value + if (bm.DeprecatedSince.GetValueOrDefault (default) < m.DeprecatedSince.GetValueOrDefault (default)) + m.DeprecatedSince = bm.DeprecatedSince; + } + + // If a "removed" method overrides a "not removed" method, the method was + // likely moved to a base class, so don't mark it as removed. + if (m.ApiRemovedSince > 0 && bm.ApiRemovedSince == 0) { + m.ApiRemovedSince = default; + } + break; + } + } + + // Process property getter/setter methods for ApiRemovedSince fixup + 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) { + 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; + } + } + if (shouldBreak) { + break; + } + } + } - // If a "removed" method overrides a "not removed" method, the method was - // likely moved to a base class, so don't mark it as removed. - if (m.ApiRemovedSince > 0 && bm.ApiRemovedSince == 0) + // 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) + var baseInterfaces = currentInterface.GetAllDerivedInterfaces (); + + foreach (var m in Methods.Where (m => !m.IsStatic)) { + foreach (var baseIface in baseInterfaces) { + var bm = baseIface.Methods.FirstOrDefault (mm => + mm.Name == m.Name && + mm.Visibility == m.Visibility && + mm.RetVal.FullName == m.RetVal.FullName && + ParameterList.Equals (mm.Parameters, m.Parameters)); + if (bm == null) { + continue; + } + // If a "removed" interface method overrides a "not removed" method, the method was + // likely moved to a base interface, so don't mark it as removed. + if (m.ApiRemovedSince > 0 && bm.ApiRemovedSince == 0) { m.ApiRemovedSince = default; + } + break; + } + } + + // Process interface property getter/setter methods for ApiRemovedSince fixup + foreach (var prop in Properties) { + foreach (var baseIface in baseInterfaces) { + var baseProp = baseIface.Properties.FirstOrDefault (p => p.Name == prop.Name && p.Type == prop.Type); + if (baseProp == null) + 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) { + 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)) { + prop.Setter.ApiRemovedSince = default; + shouldBreak = true; + } + } + if (shouldBreak) { + break; + } + } + } + // Process interface field inheritance for ApiRemovedSince fixup + foreach (var field in Fields) { + foreach (var baseIface in baseInterfaces) { + var baseField = baseIface.Fields.FirstOrDefault (f => f.Name == field.Name && f.TypeName == field.TypeName && f.Visibility == field.Visibility); + if (baseField == null) { + continue; + } + // If a "removed" interface field overrides a "not removed" field, the field was + // likely moved to a base interface, so don't mark it as removed. + if (field.ApiRemovedSince > 0 && baseField.ApiRemovedSince == 0) { + field.ApiRemovedSince = default; + } break; } }