From e258b8675d0d7e1ae3ea28a045717129dccee0ba Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 12 Apr 2017 14:02:35 +0200 Subject: [PATCH 1/4] Fix problems related to implicit function types. 1. Propagate expected type when typing an implicit closure. This is necessary to recursively apply eta expansion in the case of nested implicit functions. 2. Generalize match pattern in shortcutImplicits. This is necessary to avoid a crash. --- .../src/dotty/tools/dotc/core/NameKinds.scala | 2 +- .../dotc/transform/ShortcutImplicits.scala | 18 +++++++++--------- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 0f08e470103e..0c3fffe87887 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -283,7 +283,7 @@ object NameKinds { val ProtectedAccessorName = new PrefixNameKind(PROTECTEDACCESSOR, "protected$") val ProtectedSetterName = new PrefixNameKind(PROTECTEDSETTER, "protected$set") // dubious encoding, kept for Scala2 compatibility val AvoidClashName = new SuffixNameKind(AVOIDCLASH, "$_avoid_name_clash_$") - val DirectName = new SuffixNameKind(DIRECT, "$direct") + val DirectMethodName = new SuffixNameKind(DIRECT, "$direct") { override def definesNewName = true } val FieldName = new SuffixNameKind(FIELD, "$$local") val ExtMethName = new SuffixNameKind(EXTMETH, "$extension") val ModuleVarName = new SuffixNameKind(OBJECTVAR, "$module") diff --git a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala index 2fea1984730f..91a09f63bb58 100644 --- a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala @@ -11,7 +11,7 @@ import core.Decorators._ import core.StdNames.nme import core.Names._ import core.NameOps._ -import core.NameKinds.DirectName +import core.NameKinds.DirectMethodName import ast.Trees._ import ast.tpd import collection.mutable @@ -77,6 +77,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr private def shouldBeSpecialized(sym: Symbol)(implicit ctx: Context) = sym.is(Method, butNot = Accessor) && defn.isImplicitFunctionType(sym.info.finalResultType) && + !sym.isAnonymousFunction && (specializeMonoTargets || !sym.isEffectivelyFinal || sym.allOverriddenSymbols.nonEmpty) /** @pre The type's final result type is an implicit function type `implicit Ts => R`. @@ -92,7 +93,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr /** A new `m$direct` method to accompany the given method `m` */ private def newDirectMethod(sym: Symbol)(implicit ctx: Context): Symbol = { val direct = sym.copy( - name = DirectName(sym.name.asTermName).asInstanceOf[sym.ThisName], + name = DirectMethodName(sym.name.asTermName).asInstanceOf[sym.ThisName], flags = sym.flags | Synthetic, info = directInfo(sym.info)) if (direct.allOverriddenSymbols.isEmpty) direct.resetFlag(Override) @@ -104,14 +105,13 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr */ private def directMethod(sym: Symbol)(implicit ctx: Context): Symbol = if (sym.owner.isClass) { - val direct = sym.owner.info.member(DirectName(sym.name.asTermName)) + val direct = sym.owner.info.member(DirectMethodName(sym.name.asTermName)) .suchThat(_.info matches directInfo(sym.info)).symbol if (direct.maybeOwner == sym.owner) direct else newDirectMethod(sym).enteredAfter(thisTransform) } else directMeth.getOrElseUpdate(sym, newDirectMethod(sym)) - /** Transform `qual.apply` occurrences according to rewrite rule (2) above */ override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = if (tree.name == nme.apply && @@ -122,7 +122,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr case TypeApply(fn, args) => cpy.TypeApply(tree)(directQual(fn), args) case Block(stats, expr) => cpy.Block(tree)(stats, directQual(expr)) case tree: RefTree => - cpy.Ref(tree)(DirectName(tree.name.asTermName)) + cpy.Ref(tree)(DirectMethodName(tree.name.asTermName)) .withType(directMethod(tree.symbol).termRef) } directQual(tree.qualifier) @@ -136,7 +136,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr def splitClosure(tree: Tree): (List[Type] => List[List[Tree]] => Tree, Tree) = tree match { case Block(Nil, expr) => splitClosure(expr) - case Block((meth @ DefDef(nme.ANON_FUN, Nil, clparams :: Nil, _, _)) :: Nil, cl: Closure) => + case Block((meth @ DefDef(nme.ANON_FUN, Nil, clparams :: Nil, _, _)) :: rest, cl: Closure) => val tparamSyms = mdef.tparams.map(_.symbol) val vparamSymss = mdef.vparamss.map(_.map(_.symbol)) val clparamSyms = clparams.map(_.symbol) @@ -149,7 +149,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr val forwarder = ref(direct) .appliedToTypeTrees(tparamSyms.map(ref(_))) .appliedToArgss(vparamSymss.map(_.map(ref(_))) :+ clparamSyms.map(ref(_))) - val fwdClosure = cpy.Block(tree)(cpy.DefDef(meth)(rhs = forwarder) :: Nil, cl) + val fwdClosure = cpy.Block(tree)(cpy.DefDef(meth)(rhs = forwarder) :: rest, cl) (remappedCore, fwdClosure) case EmptyTree => (_ => _ => EmptyTree, EmptyTree) @@ -157,8 +157,8 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr val (remappedCore, fwdClosure) = splitClosure(mdef.rhs) val originalDef = cpy.DefDef(mdef)(rhs = fwdClosure) - val directDef = polyDefDef(direct.asTerm, remappedCore) - Thicket(originalDef, directDef) + val directDef = transformDefDef(polyDefDef(direct.asTerm, remappedCore)) + flatTree(List(originalDef, directDef)) } else mdef } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4bf938fd4344..a5e6b238e98a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1582,7 +1582,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val paramTypes = formals.map(fullyDefinedType(_, "implicit function parameter", tree.pos)) val ifun = desugar.makeImplicitFunction(paramTypes, tree) typr.println(i"make implicit function $tree / $pt ---> $ifun") - typedUnadapted(ifun) + typed(ifun, pt) } def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = /*>|>*/ ctx.traceIndented (i"typing $tree", typr, show = true) /*<|<*/ { From 7d3e86b82caba9a52e035d920d965fcf9d57f58d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 12 Apr 2017 14:37:08 +0200 Subject: [PATCH 2/4] Make normalize handle implicit function types This is necessary so that we can typecheck the last 4 statements of i2146.scala. --- .../dotty/tools/dotc/typer/ProtoTypes.scala | 16 ++++++++----- tests/pos/i2146.check | 6 +++++ tests/pos/i2146.scala | 24 +++++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 tests/pos/i2146.check create mode 100644 tests/pos/i2146.scala diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 398a7a17e69e..e5a9ab8ffafc 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -422,8 +422,8 @@ object ProtoTypes { /** The normalized form of a type * - unwraps polymorphic types, tracking their parameters in the current constraint - * - skips implicit parameters; if result type depends on implicit parameter, - * replace with Wildcard. + * - skips implicit parameters of methods and functions; + * if result type depends on implicit parameter, replace with fresh type dependent parameter. * - converts non-dependent method types to the corresponding function types * - dereferences parameterless method types * - dereferences nullary method types provided the corresponding function type @@ -437,9 +437,10 @@ object ProtoTypes { */ def normalize(tp: Type, pt: Type)(implicit ctx: Context): Type = Stats.track("normalize") { tp.widenSingleton match { - case poly: PolyType => normalize(constrained(poly).resultType, pt) + case poly: PolyType => + normalize(constrained(poly).resultType, pt) case mt: MethodType => - if (mt.isImplicit) resultTypeApprox(mt) + if (mt.isImplicit) normalize(resultTypeApprox(mt), pt) else if (mt.isDependent) tp else { val rt = normalize(mt.resultType, pt) @@ -451,8 +452,11 @@ object ProtoTypes { if (mt.paramInfos.nonEmpty || ft <:< pt) ft else rt } } - case et: ExprType => et.resultType - case _ => tp + case et: ExprType => + normalize(et.resultType, pt) + case wtp => + if (defn.isImplicitFunctionType(wtp)) normalize(wtp.dealias.argInfos.last, pt) + else tp } } diff --git a/tests/pos/i2146.check b/tests/pos/i2146.check new file mode 100644 index 000000000000..c095da9ead5b --- /dev/null +++ b/tests/pos/i2146.check @@ -0,0 +1,6 @@ +(A(),B()) +(A(),B()) +(A(),B()) +A() +(A(),B()) +(A(),B()) diff --git a/tests/pos/i2146.scala b/tests/pos/i2146.scala new file mode 100644 index 000000000000..f85e46e135d3 --- /dev/null +++ b/tests/pos/i2146.scala @@ -0,0 +1,24 @@ +object Test { + case class A() + case class B() + + def simple[A]: implicit A => A = implicitly[A] + + def foo[A, B]: implicit A => implicit B => (A, B) = + (implicitly[A], implicitly[B]) + + implicit val a: A = A() + implicit val b: B = B() + + def main(args: Array[String]) = { + println(foo[A, B]) + println(foo[A, B](a)) + println(foo(a)(b)) + val s: implicit A => A = simple[A] + println(s) + val x0: implicit A => implicit B => (A, B) = foo[A, B] + println(x0) + val x1: implicit B => (A, B) = foo[A, B] + println(x1) + } +} From 3fb9d9ae883a861912544d92c82005ad5f923be9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 12 Apr 2017 14:49:26 +0200 Subject: [PATCH 3/4] Tighten match pattern in shortcutImplicits again It was not necessary to widen it, after all. --- .../src/dotty/tools/dotc/transform/ShortcutImplicits.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala index 91a09f63bb58..ec82b5d33b74 100644 --- a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala @@ -136,7 +136,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr def splitClosure(tree: Tree): (List[Type] => List[List[Tree]] => Tree, Tree) = tree match { case Block(Nil, expr) => splitClosure(expr) - case Block((meth @ DefDef(nme.ANON_FUN, Nil, clparams :: Nil, _, _)) :: rest, cl: Closure) => + case Block((meth @ DefDef(nme.ANON_FUN, Nil, clparams :: Nil, _, _)) :: Nil, cl: Closure) => val tparamSyms = mdef.tparams.map(_.symbol) val vparamSymss = mdef.vparamss.map(_.map(_.symbol)) val clparamSyms = clparams.map(_.symbol) @@ -149,7 +149,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr val forwarder = ref(direct) .appliedToTypeTrees(tparamSyms.map(ref(_))) .appliedToArgss(vparamSymss.map(_.map(ref(_))) :+ clparamSyms.map(ref(_))) - val fwdClosure = cpy.Block(tree)(cpy.DefDef(meth)(rhs = forwarder) :: rest, cl) + val fwdClosure = cpy.Block(tree)(cpy.DefDef(meth)(rhs = forwarder) :: Nil, cl) (remappedCore, fwdClosure) case EmptyTree => (_ => _ => EmptyTree, EmptyTree) From c1483014848970d0561f2108836bd63db6fa169a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Brachtha=CC=88user?= Date: Fri, 14 Apr 2017 14:21:46 +0200 Subject: [PATCH 4/4] Add examples to test partial eta-expansion --- tests/neg/i2146.scala | 8 ++++++++ tests/pos/i2146.check | 3 +++ tests/pos/i2146.scala | 8 ++++++++ 3 files changed, 19 insertions(+) create mode 100644 tests/neg/i2146.scala diff --git a/tests/neg/i2146.scala b/tests/neg/i2146.scala new file mode 100644 index 000000000000..13ecf1e7b680 --- /dev/null +++ b/tests/neg/i2146.scala @@ -0,0 +1,8 @@ +object Test { + case class A() + case class B() + + def foo[A, B]: implicit A => implicit B => Int = { implicit b: B => + 42 // error: found Int, required: implicit A => implicit B => Int + } +} diff --git a/tests/pos/i2146.check b/tests/pos/i2146.check index c095da9ead5b..b9303dd2be3c 100644 --- a/tests/pos/i2146.check +++ b/tests/pos/i2146.check @@ -4,3 +4,6 @@ A() (A(),B()) (A(),B()) +(A(),B()) +(A(),B()) +(A(),B()) \ No newline at end of file diff --git a/tests/pos/i2146.scala b/tests/pos/i2146.scala index f85e46e135d3..8bb08de86e58 100644 --- a/tests/pos/i2146.scala +++ b/tests/pos/i2146.scala @@ -7,6 +7,10 @@ object Test { def foo[A, B]: implicit A => implicit B => (A, B) = (implicitly[A], implicitly[B]) + def bar[A, B]: implicit A => implicit B => (A, B) = { implicit a: A => + (implicitly[A], implicitly[B]) + } + implicit val a: A = A() implicit val b: B = B() @@ -20,5 +24,9 @@ object Test { println(x0) val x1: implicit B => (A, B) = foo[A, B] println(x1) + + println(bar[A, B]) + println(bar[A, B](a)) + println(bar(a)(b)) } }