環境
- IntelliJ IDEA Community 2018.1
- Scala 2.12.5
- sbt 1.1.3
- ScalaTest 3.0.5
build.sbt
# build.sbtの一部
scalaVersion := "2.12.5"
libraryDependencies += "org.scalactic" %% "scalactic" % "3.0.5"
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test"
やりたいこと
以下のメソッドに対応するテストコードを、ScalaTestで書きたいです。
Sample.scala
object Sample {
def toOption(name: String): Option[name.type] = {
println("toOption is called.")
Some(name)
}
def toOption2(name: String): Option[String] = {
println("toOption is called.")
Some(name)
}
}
toOption
とtoOption2
の違いは、戻り値の型です。
toOption
の戻り値の型は、「引数の型と同じ型パラメータを持つOption型」という意味を表すため、Option[name.type]
にしています。
テストコードは、以下の通りです。
SampleTest.scala
import org.scalatest.FreeSpec
import org.scalatest.Matchers._
class SampleTest extends FreeSpec {
"toOption Test" in {
println("[toOption Test]")
Sample.toOption("hoge") shouldBe Some("hoge")
Sample.toOption("hoge").get shouldBe "hoge" // toOption IS NOT CALLED
}
"toOption2 Test" in {
println("[toOption2 Test]")
Sample.toOption2("hoge") shouldBe Some("hoge")
Sample.toOption2("hoge").get shouldBe "hoge"
}
}
問題
上記のテストコードを実行すると、コンソールには以下の内容が出力されます。
[toOption Test]
toOption is called.
[toOption2 Test]
toOption2 is called.
toOption2 is called.
Sample.toOption("hoge").get shouldBe "hoge"
というコードでは、** Sample.toOption
メソッドが実行されませんでした!!**
原因調査
@xuwei_k さんからコメントをいただきました。
ScalaTestは関係ありませんでした。
Scala_Console
Welcome to Scala 2.12.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_172).
Type in expressions for evaluation. Or try :help.
scala> def toOption(name: String): Option[name.type] = {
| println("toOption is called.")
| Some(name)
| }
toOption: (name: String)Option[name.type]
scala> toOption("hoge").get
res0: String = hoge
scala> toOption("hoge")
toOption is called.
res1: Option[String("hoge")] = Some(hoge)
@xuwei_k さんのコメントから引用
javapで逆アセンブル
javapで逆アセンブルしてみます。
Sample.scala
object Sample {
//追加
def toString(name: String): name.type = {
println("toString is called.")
name
}
}
CallTest.scala
object CallTest {
def test = {
Sample.toOption("aaa")
Sample.toOption("bbb").get
Sample.toOption2("ccc").get
Sample.toString("ddd")
}
}
console
$ javap -verbose CallTest$.class > CallTest$.txt
CallTest$.txt
public java.lang.String test();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #19 // Field jp/co/scalablescala/Sample$.MODULE$:Ljp/co/scalablescala/Sample$;
3: ldc #21 // String aaa
5: invokevirtual #25 // Method jp/co/scalablescala/Sample$.toOption:(Ljava/lang/String;)Lscala/Option;
8: pop
9: ldc #27 // String bbb
11: pop
12: getstatic #19 // Field jp/co/scalablescala/Sample$.MODULE$:Ljp/co/scalablescala/Sample$;
15: ldc #29 // String ccc
17: invokevirtual #32 // Method jp/co/scalablescala/Sample$.toOption2:(Ljava/lang/String;)Lscala/Option;
20: invokevirtual #38 // Method scala/Option.get:()Ljava/lang/Object;
23: pop
24: ldc #40 // String ddd
26: areturn
LocalVariableTable:
Start Length Slot Name Signature
0 27 0 this Ljp/co/scala/invest/CallTest$;
LineNumberTable:
line 8: 0
line 9: 9
line 10: 12
line 11: 24
Sample.toOption("bbb").get
, Sample.toString("ddd")
のときは、invokevirtual
(インスタンスのメソッド呼び出し)が実行されていませんでした。
まとめ
関数の戻りの型が引数の型を参照しているとき、その関数は呼び出されない場合があります。
関数の定義 | 関数の呼び出し | 関数が呼び出されるか |
---|---|---|
def hoge(arg:String) : Option[arg.type] |
hoge("fuga") |
OK |
def hoge(arg:String) : Option[arg.type] |
hoge("fuga").get |
NG |
def hoge(arg:String) : Option[String] |
hoge("fuga").get |
OK |
def hoge(arg:String) : arg.type |
hoge("fuga") |
NG |
Scalaコンパイラのバグですかね?