From 41a2fe4aeae30113a1f29f24aa42b53c61c54edb Mon Sep 17 00:00:00 2001 From: Miles Sabin Date: Wed, 21 Mar 2018 14:48:41 +0000 Subject: [PATCH] Tests illustrating need to deconst literal type (Commit message slightly reworded by adriaanm. While preserving Miles's analysis & tests, my fix ended up differently.) Without proper deconsting, no output is produced by: class Box[T](t: T) { def foo: T = { println("effect") t } } object Box { def apply(x: String): Box[x.type] = new Box[x.type](x) } val bar = Box("foo").foo The cause of the problem is that the Box value is created with its type parameter T instantiated as x.type which is computed as the ConstantType(Literal("foo")). This results in the subsequent application of foo also being seen as having a constant type and so being eligible to be inlined as the constant value, eliding the object creation, method call and effect. So the definition of bar, val bar = Box("foo").foo was transformed to, val bar: String = "foo"; Note that although the earlier mention of LiteralType suggests that this is related to the literal types extension, this problem is present in compiler versions going back to 2.10.x at least. Another occurrence of this bug is seen in scala/bug#10768: object Test { type Id[T] = T def foo(x: Int): Id[x.type] = x lazy val result = foo(1) } the inferred FoldableConstantType for T is hidden in the Id type constructor and so is not deconsted. It then reemerges during Uncurry where Id[Int(1)] is dealiased to Int(1). Subsequently during Fields this constant type interferes with synthesis of the various fields and accessors associated with the lazy val. This also affects run/macro-reify-unreify. I've been able to convince myself that the previous output was incorrect due to the typer seeing Expr[String("world")](...).splice as having the constant type String("world") and hence inlining the literal String without invoking any of the splicing machinery, ie. it's an actual instance of the issue this commit fixes. --- test/files/pos/t10768.scala | 6 +++ test/files/run/macro-reify-unreify.check | 2 +- test/files/run/t10768.check | 11 +++++ test/files/run/t10768.scala | 10 +++++ test/files/run/t10788.check | 1 + test/files/run/t10788.scala | 16 +++++++ test/files/run/t10788b.check | 9 ++++ test/files/run/t10788b.scala | 55 ++++++++++++++++++++++++ 8 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 test/files/pos/t10768.scala create mode 100644 test/files/run/t10768.check create mode 100644 test/files/run/t10768.scala create mode 100644 test/files/run/t10788.check create mode 100644 test/files/run/t10788.scala create mode 100644 test/files/run/t10788b.check create mode 100644 test/files/run/t10788b.scala diff --git a/test/files/pos/t10768.scala b/test/files/pos/t10768.scala new file mode 100644 index 000000000000..e85bda4fbf68 --- /dev/null +++ b/test/files/pos/t10768.scala @@ -0,0 +1,6 @@ +object Test { + type Id[T] = T + def foo(x: Int): Id[x.type] = x + + lazy val result = foo(1) +} diff --git a/test/files/run/macro-reify-unreify.check b/test/files/run/macro-reify-unreify.check index 55d61e6068b4..926d7ce1d150 100644 --- a/test/files/run/macro-reify-unreify.check +++ b/test/files/run/macro-reify-unreify.check @@ -1 +1 @@ -hello world = Expr[String("hello world")]("hello world") +hello world = Expr[String("hello world")]("hello ".$plus("world")) diff --git a/test/files/run/t10768.check b/test/files/run/t10768.check new file mode 100644 index 000000000000..d08a78fbb704 --- /dev/null +++ b/test/files/run/t10768.check @@ -0,0 +1,11 @@ + +scala> type Id[T] = T +defined type alias Id + +scala> def foo(x: Int): Id[x.type] = x +foo: (x: Int)Id[x.type] + +scala> foo(1) +res0: Id[Int(1)] = 1 + +scala> :quit diff --git a/test/files/run/t10768.scala b/test/files/run/t10768.scala new file mode 100644 index 000000000000..2ac4bd87be83 --- /dev/null +++ b/test/files/run/t10768.scala @@ -0,0 +1,10 @@ +import scala.tools.nsc.interpreter._ +import scala.tools.partest.ReplTest + +object Test extends ReplTest { + def code = """ + |type Id[T] = T + |def foo(x: Int): Id[x.type] = x + |foo(1) + |""".stripMargin +} diff --git a/test/files/run/t10788.check b/test/files/run/t10788.check new file mode 100644 index 000000000000..a08c1d171e1b --- /dev/null +++ b/test/files/run/t10788.check @@ -0,0 +1 @@ +effect diff --git a/test/files/run/t10788.scala b/test/files/run/t10788.scala new file mode 100644 index 000000000000..1816ea8c6a9d --- /dev/null +++ b/test/files/run/t10788.scala @@ -0,0 +1,16 @@ +object Test { + class Box[T](t: T) { + def foo: T = { + println("effect") + t + } + } + + object Box { + def apply(x: String): Box[x.type] = new Box[x.type](x) + } + + def main(args: Array[String]): Unit = { + val bar = Box("foo").foo + } +} diff --git a/test/files/run/t10788b.check b/test/files/run/t10788b.check new file mode 100644 index 000000000000..37fd1b0c31a1 --- /dev/null +++ b/test/files/run/t10788b.check @@ -0,0 +1,9 @@ +effect +effect +effect +oh boy +ohai +oh boy +ohai +boo +effect42 diff --git a/test/files/run/t10788b.scala b/test/files/run/t10788b.scala new file mode 100644 index 000000000000..d690fb549eb9 --- /dev/null +++ b/test/files/run/t10788b.scala @@ -0,0 +1,55 @@ +object Test { + abstract class Box { + type T + val t: T + def foo: T = { + println("effect") + t + } + def fooEff(): T = { + println("effect") + t + } + + def fooEffPoly[U >: T <: T](): U = { + println("effect") + t + } + + } + + object Box { + def apply(x: String): Box { type T = x.type } = new Box { + type T = x.type + val t = x + } + } + + class BoxParam[T](val t: T) { + println("ohai") + def foo: T = { + println("boo") + t + } + } + + object BoxParam { + def apply(x: String): BoxParam[x.type] = { println("oh boy"); new BoxParam[x.type](x) } + } + + + class C { + println("effect42") + final val const = 42 + } + + def main(args: Array[String]): Unit = { + val bar = Box("foo").foo + Box("foo").fooEff() + Box("foo").fooEffPoly() + BoxParam("foo").t + BoxParam("foo").foo + + val x = new C().const + } +}