Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error with public class exposing inherited protected class methods with covariant return types #1116

Open
jpobst opened this issue May 23, 2023 · 3 comments
Labels
bug Component does not function as intended generator Issues binding a Java library (generator, class-parse, etc.)

Comments

@jpobst
Copy link
Contributor

jpobst commented May 23, 2023

Imagine we have the following Java:

public abstract class ClassA {
  protected abstract class ChildClass {
    public abstract ClassA createThing ();
  }
}

public class ClassB extends ClassA {
  public class ChildClass extends ClassA.ChildClass {
    @Override
    public ClassB createThing () { ... }
  }
}

There are 2 interesting things to "fix" when translating this to C#:

  1. public class ClassB.ChildClass inherits protected class ClassA.ChildClass, which C# doesn't allow
  2. ClassB.ChildClass.createThing () implements the abstract method ClassA.ChildClass.createThing (), but it has a covariant return type

The way we fix (1) is:

  • Make ClassB.ChildClass inherit from Object instead of ClassA.ChildClass
  • Copy members from ClassA.ChildClass to ClassB.ChildClass

If we didn't have the covariant return type (2), we would detect that ClassB.ChildClass.createThing () implements ClassA.ChildClass.createThing () and we would generate a virtual method.

However, because of the covariant return type, we cannot make the match and end up generating both a virtual and an abstract createThing ():

public abstract partial class ClassA : Java.Lang.Object {
  protected internal abstract partial class ChildClass : Java.Lang.Object {
    [Register ("createThing", "()Lexample/ClassA;", "GetCreateThingHandler")]
    public abstract ClassA CreateThing ();
  }
}

public partial class ClassB : ClassA {
  public new partial class ChildClass : Java.Lang.Object {
    [Register ("createThing", "()Lexample/ClassB;", "GetCreateThingHandler")]
    public virtual unsafe ClassB CreateThing () { ... }

    [Register ("createThing", "()Lexample/ClassA;", "GetCreateThingHandler")]
    public abstract ClassA CreateThing ();
  }
}

This fails with:

CS0513 'ClassB.ChildClass.CreateThing()' is abstract but it is contained in non-abstract type 

as well as several duplicate member errors.

@jpobst jpobst added bug Component does not function as intended generator Issues binding a Java library (generator, class-parse, etc.) labels May 23, 2023
@jpobst
Copy link
Contributor Author

jpobst commented May 25, 2023

Additional context:

This was found while enabling DIM for 4 packages in xamarin/GooglePlayServicesComponents#779. An interesting aspect to investigate is that these instances do not appear to have any relation to interfaces or default methods.

@jonpryor
Copy link
Member

Paraphrasing my replies from Discord…

Conceptual issue: the original sample code is inconsistent. The Java code has getThing() methods, while the binding code instead has DoThing() methods. These should be made consistent, and should probably just become createThing()/CreateThing(), as getThing() would become a property when bound, which just complicates discussion.

Discussion:

I think the "problem" is:

  • Copy members from ClassA.ChildClass to ClassB.ChildClass

While this needs to be done, we can't "just copy" all members from the base into the derived type. We need to copy only non-overridden/"hidden" members.

See also:

So we are attempting to filter, but the filter is not quite right.

Here might be the crux of the problem:

Method.Matches() doesn't take covariant return types into consideration! It only supports exact matches. Consequently, Method.Matches() doesn't believe that ClassB.ChildClass.createThing() overrides ClassA.ChildClass.createThing(), because the return types don't match. (ClassB != ClassA.)

Finally, note that as per 4ec5d4e, the binding of ClassB.ChildClass cannot inherit from ClassA.ChildClass, as ClassA.ChildClass isn't public. As such:

  • ClassB.ChildClass should inherit Java.Lang.Object
  • ClassB.ChildClass.CreateThing() should be virtual, not abstract

@jpobst
Copy link
Contributor Author

jpobst commented May 30, 2023

The Java code has getThing() methods, while the binding code instead has DoThing() methods

Updated issue description to standardize on createThing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Component does not function as intended generator Issues binding a Java library (generator, class-parse, etc.)
Projects
None yet
Development

No branches or pull requests

2 participants