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

Align erasure of Array[Nothing] and Array[Null] with Scala 2 #22517

Merged
merged 3 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ object TypeErasure {
val sym = t.symbol
// Only a few classes have both primitives and references as subclasses.
if (sym eq defn.AnyClass) || (sym eq defn.AnyValClass) || (sym eq defn.MatchableClass) || (sym eq defn.SingletonClass)
|| isScala2 && !(t.derivesFrom(defn.ObjectClass) || t.isNullType) then
|| isScala2 && !(t.derivesFrom(defn.ObjectClass) || t.isNullType | t.isNothingType) then
NoSymbol
// We only need to check for primitives because derived value classes in arrays are always boxed.
else if sym.isPrimitiveValueClass then
Expand Down Expand Up @@ -596,8 +596,8 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
* will be returned.
*
* In all other situations, |T| will be computed as follow:
* - For a refined type scala.Array+[T]:
* - if T is Nothing or Null, []Object
* - For a refined type scala.Array[T]:
* - {Scala 2} if T is Nothing or Null, []Object
* - otherwise, if T <: Object, []|T|
* - otherwise, if T is a type parameter coming from Java, []Object
* - otherwise, Object
Expand Down Expand Up @@ -781,12 +781,14 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst

private def eraseArray(tp: Type)(using Context) = {
val defn.ArrayOf(elemtp) = tp: @unchecked
if (isGenericArrayElement(elemtp, isScala2 = sourceLanguage.isScala2)) defn.ObjectType
else
try
val eElem = erasureFn(sourceLanguage, semiEraseVCs = false, isConstructor, isSymbol, inSigName)(elemtp)
if eElem.isInstanceOf[WildcardType] then WildcardType
else JavaArrayType(eElem)
if isGenericArrayElement(elemtp, isScala2 = sourceLanguage.isScala2) then
defn.ObjectType
else if sourceLanguage.isScala2 && (elemtp.hiBound.isNullType || elemtp.hiBound.isNothingType) then
JavaArrayType(defn.ObjectType)
else
try erasureFn(sourceLanguage, semiEraseVCs = false, isConstructor, isSymbol, inSigName)(elemtp) match
case _: WildcardType => WildcardType
case elem => JavaArrayType(elem)
catch case ex: Throwable =>
handleRecursive("erase array type", tp.show, ex)
}
Expand Down
2 changes: 2 additions & 0 deletions sbt-test/scala2-compat/erasure/build.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
ThisBuild / fork := true
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Workaround until #22524 is resolved


lazy val scala2Lib = project.in(file("scala2Lib"))
.settings(
scalaVersion := sys.props("plugin.scala2Version")
Expand Down
6 changes: 6 additions & 0 deletions sbt-test/scala2-compat/erasure/dottyApp/Api.scala
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,10 @@ class Z {
def objectARRAY_88(x: Array[Any]): Unit = {}
def objectARRAY_89(x: Array[AnyRef]): Unit = {}
def objectARRAY_90(x: Array[AnyVal]): Unit = {}

def nothing$ARRAY_91(x: Array[Nothing]): Unit = {}
def null$ARRAY_92(x: Array[Null]): Unit = {}
def nothing$ARRAY_93(x: Array[_ <: Nothing]): Unit = {}
def null$ARRAY_94(x: Array[_ <: Null]): Unit = {}

}
8 changes: 6 additions & 2 deletions sbt-test/scala2-compat/erasure/dottyApp/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ object Main {
z.c_40(dummy)
z.c_41(dummy)
z.c_42(dummy)
z.b_43(dummy)
//z.b_43(dummy)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Inspect why both these methods' erasures are wrong, or why were they considered correct.

Copy link
Member Author

@hamzaremmal hamzaremmal Feb 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've extracted this to its issue (#22525). I would like to merge this one so I can make progress in #22480 @smarter @sjrd, is that okay with you?

z.c_44(dummy)
z.c_45(dummy)
z.b_46(dummy)
//z.b_46(dummy)
z.c_47(dummy)
// z.a_48(dummy)
// z.c_49(dummy)
Expand Down Expand Up @@ -101,6 +101,10 @@ object Main {
z.objectARRAY_88(dummy)
z.objectARRAY_89(dummy)
z.objectARRAY_90(dummy)
z.objectARRAY_91(dummy)
z.objectARRAY_92(dummy)
z.objectARRAY_93(dummy)
z.objectARRAY_94(dummy)

val methods = classOf[scala2Lib.Z].getDeclaredMethods.toList ++ classOf[dottyApp.Z].getDeclaredMethods.toList
methods.foreach { m =>
Expand Down
6 changes: 6 additions & 0 deletions sbt-test/scala2-compat/erasure/scala2Lib/Api.scala
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,10 @@ class Z {
def objectARRAY_88(x: Array[Any]): Unit = {}
def objectARRAY_89(x: Array[AnyRef]): Unit = {}
def objectARRAY_90(x: Array[AnyVal]): Unit = {}

def objectARRAY_91(x: Array[Nothing]): Unit = {}
def objectARRAY_92(x: Array[Null]): Unit = {}
def objectARRAY_93(x: Array[_ <: Nothing]): Unit = {}
def objectARRAY_94(x: Array[_ <: Null]): Unit = {}
Comment on lines +192 to +193
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should actually be:

Suggested change
def objectARRAY_93(x: Array[_ <: Nothing]): Unit = {}
def objectARRAY_94(x: Array[_ <: Null]): Unit = {}
def nothing$ARRAY_93(x: Array[_ <: Nothing]): Unit = {}
def null$ARRAY_94(x: Array[_ <: Null]): Unit = {}

but for some reason, it passes the test locally, I will have to test it better, but my machine keeps crashing... so checking with the CI :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is weird, the CI passes while Array[_ <: Nothing] erasure is wrong. I've tested with the following:

// In Scala 2
class Foo {
    def a: Array[Nothing] = null
    def b: Array[Null] = null
    def c: Array[_ <: Nothing] = null
    def s: Array[_ <: Null] = null
}
// In Scala 3
@main def Test =
    (new Foo).a
    (new Foo).b
    (new Foo).c
    (new Foo).s

The generated bytecode from Scala 2 is the following:

Compiled from "s2.scala"
public class Foo {
  public java.lang.Object[] a();
  public java.lang.Object[] b();
  public scala.runtime.Nothing$[] c();
  public scala.runtime.Null$[] s();
  public Foo();
}

We can clearly see Scala 3 (this PR) emitting a call to the wrong method for Foo.c:

public final class s3$package$ implements java.io.Serializable {
  public static final s3$package$ MODULE$;

  public static {};
    Code:
       0: new           #2                  // class s3$package$
       3: dup
       4: invokespecial #16                 // Method "<init>":()V
       7: putstatic     #18                 // Field MODULE$:Ls3$package$;
      10: return

  public scala.runtime.Null$[] Test();
    Code:
       0: new           #29                 // class Foo
       3: dup
       4: invokespecial #30                 // Method Foo."<init>":()V
       7: invokevirtual #34                 // Method Foo.a:()[Ljava/lang/Object;
      10: pop
      11: new           #29                 // class Foo
      14: dup
      15: invokespecial #30                 // Method Foo."<init>":()V
      18: invokevirtual #37                 // Method Foo.b:()[Ljava/lang/Object;
      21: pop
      22: new           #29                 // class Foo
      25: dup
      26: invokespecial #30                 // Method Foo."<init>":()V
      29: invokevirtual #40                 // Method Foo.c:()Ljava/lang/Object;
      32: pop
      33: new           #29                 // class Foo
      36: dup
      37: invokespecial #30                 // Method Foo."<init>":()V
      40: invokevirtual #43                 // Method Foo.s:()[Lscala/runtime/Null$;
      43: areturn
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've checked a bit and throwing a LinkageError will not be considered a failure... but the problem is that there is a linkage error before the code I've added (I still need to find which one).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already an issue opened on sbt (sbt/sbt#6852). The proposed workaround (fork := true) worked locally


}
Loading