From 4f1a46c98afeeb57bd79d339c2be3280cdb41273 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 12 Oct 2013 23:40:15 +0200 Subject: SI-7899 Allow by-name inference under -Yinfer-by-name As usual, the hole has been exploited in the wild. While you can't abstract over by-name-ness without running into the ClassCastException or an un-applied Function0, there are cases like the enclosed test where you can tiptoe around those cases. I've proposed a small change to Scalaz to avoid tripping over this problem: https://github.com/scalaz/scalaz/pull/562/files But this commit I've also added a transitional flag, -Yinfer-by-name, that they could use to back-publish older versions without code changes. It is also an insurance policy for other projects that might be exploiting the same hole. --- .../scala/tools/nsc/settings/ScalaSettings.scala | 6 ++++-- .../scala/tools/nsc/typechecker/Infer.scala | 6 +++++- test/files/run/t7899-regression.check | 1 + test/files/run/t7899-regression.flags | 1 + test/files/run/t7899-regression.scala | 24 ++++++++++++++++++++++ 5 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 test/files/run/t7899-regression.check create mode 100644 test/files/run/t7899-regression.flags create mode 100644 test/files/run/t7899-regression.scala diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 1f80aa4b6f..01d5791f60 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -176,8 +176,8 @@ trait ScalaSettings extends AbsScalaSettings val Yreplsync = BooleanSetting ("-Yrepl-sync", "Do not use asynchronous code for repl startup") val Yreploutdir = StringSetting ("-Yrepl-outdir", "path", "Write repl-generated classfiles to given output directory (use \"\" to generate a temporary dir)" , "") val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overriden methods.") - val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition."). - withDeprecationMessage("This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug.") + val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition.").withDeprecationMessage(removalIn212) + val inferByName = BooleanSetting ("-Yinfer-by-name", "Allow inference of by-name types. This is a temporary option to ease transition. See SI-7899.").withDeprecationMessage(removalIn212) val Yinvalidate = StringSetting ("-Yinvalidate", "classpath-entry", "Invalidate classpath entry before run", "") val YvirtClasses = false // too embryonic to even expose as a -Y //BooleanSetting ("-Yvirtual-classes", "Support virtual classes") val YdisableUnreachablePrevention = BooleanSetting("-Ydisable-unreachable-prevention", "Disable the prevention of unreachable blocks in code generation.") @@ -185,6 +185,8 @@ trait ScalaSettings extends AbsScalaSettings val exposeEmptyPackage = BooleanSetting("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly() + private def removalIn212 = "This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug." + /** Area-specific debug output. */ val Ydocdebug = BooleanSetting("-Ydoc-debug", "Trace all scaladoc activity.") diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index ccf4a5e46f..dd0923a696 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -913,7 +913,11 @@ trait Infer extends Checkable { val targs = exprTypeArgs(tvars, tparams, treeTp, pt, useWeaklyCompatible) def infer_s = map3(tparams, tvars, targs)((tparam, tvar, targ) => s"$tparam=$tvar/$targ") mkString "," printTyping(tree, s"infer expr instance from pt=$pt, $infer_s") - def targsStrict = if (targs eq null) null else targs mapConserve dropByName + + // SI-7899 infering by-name types is unsound. The correct behaviour is conditional because the hole is + // exploited in Scalaz (Free.scala), as seen in: run/t7899-regression. + def dropByNameIfStrict(tp: Type): Type = if (settings.inferByName) tp else dropByName(tp) + def targsStrict = if (targs eq null) null else targs mapConserve dropByNameIfStrict if (keepNothings || (targs eq null)) { //@M: adjustTypeArgs fails if targs==null, neg/t0226 substExpr(tree, tparams, targsStrict, pt) diff --git a/test/files/run/t7899-regression.check b/test/files/run/t7899-regression.check new file mode 100644 index 0000000000..602b03a1d1 --- /dev/null +++ b/test/files/run/t7899-regression.check @@ -0,0 +1 @@ +warning: -Yinfer-by-name is deprecated: This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug. diff --git a/test/files/run/t7899-regression.flags b/test/files/run/t7899-regression.flags new file mode 100644 index 0000000000..553a27eafd --- /dev/null +++ b/test/files/run/t7899-regression.flags @@ -0,0 +1 @@ +-Yinfer-by-name -deprecation diff --git a/test/files/run/t7899-regression.scala b/test/files/run/t7899-regression.scala new file mode 100644 index 0000000000..67d38cdd1d --- /dev/null +++ b/test/files/run/t7899-regression.scala @@ -0,0 +1,24 @@ +import language.higherKinds + +object Test { + trait Monad[M[_]] { + def foo[A](ma: M[A])(f: M[A] => Any) = f(ma) + } + implicit def function1Covariant[T]: Monad[({type l[a] = (T => a)})#l] = + new Monad[({type l[a] = (T => a)})#l] {} + + def main(args: Array[String]) { + // inference of T = (=> Any) here was outlawed by SI-7899 / 8ed7099 + // but this pattern is used in Scalaz in just a few places and caused + // a regression. + // + // Inference of a by-name type doesn't *always* lead to a ClassCastException, + // it only gets there if a method in generic code accepts a parameter of + // that type. + // + // We need to introduce the stricter inference rules gradually, probably + // with a warning. + val m = implicitly[Monad[({type f[+x] = (=> Any) => x})#f]] + assert(m.foo[Int]((x => 0))(f => f(???)) == 0) + } +} -- cgit v1.2.3