Mima reports me a binary incompatibility where I didn’t expect one. Here is a minimal example.
trait Default[T] { def default: T = null.asInstanceOf[T] }
object DefaultInt extends Default[Int] {
// the change uncomments this line
// override def default: Int = 0
}
object DefaultIntTest { DefaultInt.default }
Diff of the javap
output:
public interface Default<T> {
public static java.lang.Object default$(Default);
Code:
0: aload_0
1: invokespecial #16 // InterfaceMethod default:()Ljava/lang/Object;
4: areturn
public T default();
Code:
0: aconst_null
1: areturn
public static void $init$(Default);
Code:
0: return
}
public final class DefaultInt$ implements Default<java.lang.Object> {
public static final DefaultInt$ MODULE$;
public static {};
Code:
0: new #2 // class DefaultInt$
3: dup
4: invokespecial #15 // Method "<init>":()V
7: putstatic #17 // Field MODULE$:LDefaultInt$;
10: getstatic #17 // Field MODULE$:LDefaultInt$;
13: invokestatic #21 // InterfaceMethod Default.$init$:(LDefault;)V
16: return
+ public int default();
+ Code:
+ 0: iconst_0
+ 1: ireturn
+
public java.lang.Object default();
Code:
0: aload_0
- 1: invokestatic #27 // InterfaceMethod Default.default$:(LDefault;)Ljava/lang/Object;
- 4: areturn
+ 1: invokevirtual #27 // Method default:()I
+ 4: invokestatic #33 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
+ 7: areturn
}
public final class DefaultInt {
- public static java.lang.Object default();
+ public static int default();
Code:
0: getstatic #16 // Field DefaultInt$.MODULE$:LDefaultInt$;
- 3: invokevirtual #18 // Method DefaultInt$.default:()Ljava/lang/Object;
- 6: areturn
+ 3: invokevirtual #18 // Method DefaultInt$.default:()I
+ 6: ireturn
}
public final class DefaultIntTest$ {
public static final DefaultIntTest$ MODULE$;
public static {};
Code:
0: new #2 // class DefaultIntTest$
3: dup
4: invokespecial #12 // Method "<init>":()V
7: putstatic #14 // Field MODULE$:LDefaultIntTest$;
10: getstatic #19 // Field DefaultInt$.MODULE$:LDefaultInt$;
- 13: invokevirtual #23 // Method DefaultInt$.default:()Ljava/lang/Object;
+ 13: invokevirtual #23 // Method DefaultInt$.default:()I
16: pop
17: return
}
public final class DefaultIntTest {
}
The change modifies the bytecode method signatures of the static method DefaultInt.default:()Ljava/lang/Object
into DefaultInt.default:()I
. This is reported by mima:
[error] * static method default()java.lang.Object in class DefaultInt has a different result type in current version, where it is Int rather than java.lang.Object
[error] filter with: ProblemFilters.exclude[IncompatibleResultTypeProblem]("DefaultInt.default")
Observations:
- the bytecode of
DefaultInt$
evolves in a compatible way. A specialized variant ofDefaultInt$.default:()Ljava/lang/Object
is added:DefaultInt$.default:()I
. This is not the case for the static methodDefaultInt.default:()Ljava/lang/Object
. - the generated bytecode in
DefaultIntTest$
calls methods from the objectDefaultInt$
. The static method is called nowhere.
Questions:
- Is the static method
DefaultInt.default:()...
there for java interop reasons? - Is is guaranteed that scala doesn’t call static methods that delegate to object methods? If a lib doesn’t have java users, could it discard this mima error?
- Why doesn’t the compiler generate both a generic/boxed and a specialized static variant of the method in
DefaultInt
? Could it lead to ambiguities in java? If so, wouldn’t it be better to stick to the boxed variant of the first version and avoid the binary incompatibility?
Thanks in advance for answers if any