Binary compatibility, primitive types and java static methods

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 of DefaultInt$.default:()Ljava/lang/Object is added: DefaultInt$.default:()I. This is not the case for the static method DefaultInt.default:()Ljava/lang/Object.
  • the generated bytecode in DefaultIntTest$ calls methods from the object DefaultInt$. 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 :slight_smile:

I hope you’re on Scala 2.13.8? If you’re using some older version, I’d suggest you begin by checking if anything is different in 2.13.8.

  • Is the static method DefaultInt.default:()... there for java interop reasons?

Yes

Is is guaranteed that scala doesn’t call static methods that delegate to object methods?

Yes

If a lib doesn’t have java users, could it discard this mima error?

As far as I can see yes, but I hesitate to answer yes with complete confidence. See below.

Why doesn’t the compiler […]

I started digging into this a bit and my journey took me past tickets including but not limited to:

at which point I threw up my hands and gave up on the idea of writing a comprehensive answer to your question — I think doing so would take some real study.

3 Likes

Thanks Seth for the answers and the issue trackers search.
The example above was compiled with 2.13.8. The mima error appeared in this pityka/saddle PR. We ended up ignoring the mima errors for this case.