bug icon indicating copy to clipboard operation
bug copied to clipboard

Type mismatch calling interface with method override

Open tpetillot opened this issue 2 years ago • 9 comments

Reproduction steps

Scala version: 2.13.12

Java interfaces:

interface Parent {
  default Object bug() {
    return "Hello world";
  }
}

interface Child extends Parent {
  @Override
  String bug();
}

Problem

Do compile in Java:

public class MainJava {
  public static void main(String[] args) {
      Child child = null;
      String value = child.bug();
  }
}

Do not compile in Scala:

object MainScala {
  def main(args: Array[String]): Unit = {
    val child: Child = null
    val value: String = child.bug()
  }
}

Result:

[error] type mismatch;
[error] found   : Object
[error] required: String
[error]     val value: String = child.bug()

Java and Scala implementation should have the same behavior.

tpetillot avatar Oct 16 '23 16:10 tpetillot

In Scala 2, concrete definitions override abstract declarations, even when defined in Java.

That changed in Scala 3, so this compiles under dotty.

Not sure whether Scala 2 should complain about the types, but possibly it's just a limitation.

som-snytt avatar Oct 16 '23 18:10 som-snytt

@som-snytt thanks for your response.

As background, this limitation disrupts compatibility with the builder mechanism in aws-sdk-java-v2

SdkPresigner.java#L81C9-L81C9

S3Presigner.java#L589

Since version 2.21.0 released 5 days ago.

tpetillot avatar Oct 17 '23 08:10 tpetillot

+1 this is a bug report for the S3Presigner.java mentioned by @tpetillot. Here is a small reproducible test case that breaks with Scala 2.

Using the following Java interfaces and class,

public interface InterfaceA {

    InterfaceA anotherMethod();

    default InterfaceA defaultedMethod() {
        return this;
    }
}

public interface InterfaceB extends InterfaceA {

    @Override
    InterfaceB anotherMethod();

    @Override
    InterfaceB defaultedMethod();
}

public class InterfaceBImpl implements InterfaceB {

    @Override
    public InterfaceB anotherMethod() {
        return this;
    }

    @Override
    public InterfaceB defaultedMethod() {
        return this;
    }

    public InterfaceB doSomething() {
        return this;
    }

    public static InterfaceBImpl instance() {
        return new InterfaceBImpl();
    }
}

This method compiles and works fine in Java.

public static void main(String[] args) {
    InterfaceBImpl.instance()
            .doSomething()
            .anotherMethod()
            .defaultedMethod()
            .anotherMethod();       
}

Whereas using Scala:

  def getInterfaceB(): InterfaceB =
    InterfaceBImpl
      .instance()
      .doSomething()
      .anotherMethod()
      .defaultedMethod()
      .anotherMethod()

I get an error similar to

/home/sugmanue/ScalaBug.scala:80:21: type mismatch;
 found   : test.InterfaceA
 required: test.InterfaceB
      .anotherMethod()?
test.InterfaceA <: test.InterfaceB?
false
 one error found

sugmanue avatar Oct 18 '23 00:10 sugmanue

In Scala 2, concrete definitions override abstract declarations, even when defined in Java.

That changed in Scala 3, so this compiles under dotty.

Did it change in Scala 3?

scala> trait T { def m: T = this }
trai// defined trait T

scala> trait U extends T { override def m: U }
-- [E164] Declaration Error: ---------------------------------------------------
1 |trait U extends T { override def m: U }
  |      ^
  | error overriding method m in trait U of type => U;
  |   method m in trait T of type => T has incompatible type
  | (Note that method m in trait U of type => U is abstract,
  | and is therefore overridden by concrete method m in trait T of type => T)
  |
  | longer explanation available when compiling with `-explain`
1 error found

You're right that the test works in Scala 3, so probably it just supports Java better?

lrytz avatar Oct 18 '23 14:10 lrytz

@lrytz no it actually changed in Dotty.

som-snytt avatar Oct 18 '23 15:10 som-snytt

OK I must be confusing some things.

  • Is this change observable within Scala only, without Java declarations?
  • Do you have any history of the change at hand?

The 3.4 spec still says

First, a concrete definition always overrides an abstract definition

lrytz avatar Oct 18 '23 17:10 lrytz

We know the spec is a big fat lie!

There is an early ticket where Allan Renucci (spelling) notes the discrepancy, and then it was adopted "because that is how Java does it". At some point, I noted that it was not like "sipped", but absorbed by osmosis, but I think it was intended all along to remedy the defect in Scala 2, where it was a limitation.

(I understand the limitation is about tracking bounds in overrides or something, but I never went back as I intended to do, to understand exactly what.)

(As LeVar Burton says, "Don't take my word for it." I think I'm awake right now?)

som-snytt avatar Oct 18 '23 17:10 som-snytt

After looking at this a bunch more I find it confusing how things work on Scala 3.

Here it tells me B.f overrides A.f:

scala> trait A { def f: String = "" }
scala> trait B extends A { def f: Object }
-- [E164] Declaration Error: ---------------------------------------------------
1 |trait B extends A { def f: Object }
  |                        ^
  |                   error overriding method f in trait A of type => String;
  |                     method f of type => Object has incompatible type

But then it tells me A.f overrides B.f:

scala> trait A { def f: Object = "" }
scala> trait B extends A { override def f: String }
-- [E164] Declaration Error: ---------------------------------------------------
1 |trait B extends A { override def f: String }
  |      ^
  |error overriding method f in trait B of type => String;
  |  method f in trait A of type => Object has incompatible type
  |(Note that method f in trait B of type => String is abstract,
  |and is therefore overridden by concrete method f in trait A of type => Object)

Anyway.. like @dwijnand I don't see the problem with Scala 2's approach, except for supporting Java (this ticket). Maybe we can change the lookup rules for Java?

The override checking should be unaffected:

interface A { default A m() { return this; } }
interface B extends A { @Override B m(); }

new B {} in Scala gives

  |error overriding method m in trait B of type (): B;
  |  method m in trait A of type (): A has incompatible type
  |(Note that method m in trait B of type (): B is abstract,
  |and is therefore overridden by concrete method m in trait A of type (): A)

lrytz avatar Oct 19 '23 11:10 lrytz

🤔

  • https://github.com/scala/bug/issues/12224

xuwei-k avatar Oct 27 '23 06:10 xuwei-k