MockitoのargThatとScala 3 (original) (raw)

一見かなり謎なタイミングで NullPointerException で混乱したので、かるくメモ。

以下、再現コード。

scalaVersion := "3.5.1-RC1"

libraryDependencies += "org.mockito" % "mockito-subclass" % "5.12.0"

package example

object Main { def main(args: Array[String]): Unit = { try { println("型引数を明示しなかった場合") val b = org.mockito.ArgumentMatchers.argThat((a: String) => a.isEmpty) println(b) println("成功") } catch { case e: Throwable => println("失敗 " + e) }

println("")

try {
  println("型引数を明示した場合")
  val b = org.mockito.ArgumentMatchers.argThat[String](a => a.isEmpty)
  println(b)
  println("成功")
} catch {
  case e: Throwable =>
    println("失敗 " + e)
}

} }

Scala 3だと

ArgumentMatchers.argThat((a: String) => a.isEmpty)

ArgumentMatchers.argThat[String](a => a.isEmpty)

で、違う挙動になります。後者の方が想定した挙動になる・・・はず?

Scala 3.5.1-RC1での実行結果例。

型引数を明示しなかった場合 失敗 java.lang.NullPointerException

型引数を明示した場合 null 成功

Scala 2.13.14だとどちらで書いても同じっぽい。

Scala 3では変数にアクセスするタイミング?でエラーという謎な挙動・・・。

とりあえず再現コードは作れたが -Xprint:all したり javap して眺めてみたけど、詳細に正確には現象を理解出来てないので、理解したらもっと詳細な解説を書くかもしれません。

https://github.com/mockito/mockito/blob/5c3875125ae5600c6598765d4df3b739e2175332/src/main/java/org/mockito/ArgumentMatchers.java#L882-L916

TODO: mockito関係なく単体で再現コード作れるのか?も、あとで試す・・・

MockitoもJavaも関係なく書けますね、こう。Scalaで書くと asInstanceOf 必要だが

package example

object Main { def foo[A](f: A => Boolean): A = null.asInstanceOf[A]

def main(args: Array[String]): Unit = { val y1 = foo[String](x => x.isEmpty) println(y1)

val y2 = foo((x: String) => x.isEmpty)
println(y2)

} }

Scala 3だと型引数がどこで使われるか?によって、型推論結果が変わるようです。 ある意味ではScala 3の方が頭がいいような気がしつつ、Mockitoのようにリフレクション使うようなライブラリとは相性が良くないですね。

Welcome to Scala 3.5.1-RC1 (21.0.4, Java OpenJDK 64-Bit Server VM). Type in expressions for evaluation. Or try :help.

scala> def f1[A](x: A => String): A = ??? def f1[A](x: A => String): A

scala> def f2[A](x: A => String): A => Int = ??? def f2[A](x: A => String): A => Int

scala> def f3[A](x: A => String): String => A = ??? def f3[A](x: A => String): String => A

scala> def a1 = f1((s: String) => s) def a1: Nothing

scala> def a2 = f2((s: String) => s) def a2: String => Int

scala> def a3 = f3((s: String) => s) def a3: String => Nothing