From c52aa9a67745a8a366db12ac13a8d79d7b4d11a8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 31 Jul 2018 16:00:31 +0200 Subject: [PATCH 1/5] Handle transparent implicits in implicit context When adding implicit bindings to an implicit context of an inline body, we need to maintain the transparency of the implicits. The added implicit bindings are eliminated after inlining, since they are either unused or have been inlined. --- compiler/src/dotty/tools/dotc/core/Flags.scala | 3 +++ .../src/dotty/tools/dotc/typer/Inliner.scala | 15 ++++++++++----- .../tools/dotc/typer/PrepareTransparent.scala | 18 +++++++++++++++--- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index f6f949cf6e32..8d894bdfa034 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -560,6 +560,9 @@ object Flags { /** A transparent method */ final val TransparentMethod = allOf(Transparent, Method) + /** A transparent implicit method */ + final val TransparentImplicitMethod = allOf(Transparent, Implicit, Method) + /** A transparent parameter */ final val TransparentParam = allOf(Transparent, Param) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 516ded001526..ae00c153f436 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -793,6 +793,14 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } countRefs.traverse(tree) for (binding <- bindings) countRefs.traverse(binding) + + def retain(boundSym: Symbol) = { + refCount.get(boundSym) match { + case Some(x) => x > 1 || x == 1 && !boundSym.is(Method) + case none => true + } + } && !boundSym.is(TransparentImplicitMethod) + val inlineBindings = new TreeMap { override def transform(t: Tree)(implicit ctx: Context) = t match { case t: RefTree => @@ -812,11 +820,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { super.transform(t) } } - def retain(binding: MemberDef) = refCount.get(binding.symbol) match { - case Some(x) => x > 1 || x == 1 && !binding.symbol.is(Method) - case none => true - } - val retained = bindings.filterConserve(retain) + + val retained = bindings.filterConserve(binding => retain(binding.symbol)) if (retained `eq` bindings) { (bindings, tree) } diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala index 927ce6f4f053..9219853fa309 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala @@ -287,7 +287,7 @@ object PrepareTransparent { !isLocalOrParam(tree.symbol, inlineMethod) && !implicitRefTypes.contains(tree.tpe) => if (tree.existsSubTree(t => isLocal(tree.symbol, inlineMethod))) - ctx.warning("implicit reference $tree is dropped at inline site because it refers to local symbol(s)", tree.pos) + ctx.warning(i"implicit reference $tree is dropped at inline site because it refers to local symbol(s)", tree.pos) else { implicitRefTypes += tree.tpe implicitRefs += tree @@ -417,11 +417,23 @@ object PrepareTransparent { val localImplicit = iref.symbol.asTerm.copy( owner = inlineMethod, name = UniqueInlineName.fresh(iref.symbol.name.asTermName), - flags = Implicit | Method | Stable, + flags = Implicit | Method | Stable | iref.symbol.flags & (Transparent | Erased), info = iref.tpe.widen.ensureMethodic, coord = inlineMethod.pos).asTerm - polyDefDef(localImplicit, tps => vrefss => + val idef = polyDefDef(localImplicit, tps => vrefss => iref.appliedToTypes(tps).appliedToArgss(vrefss)) + if (localImplicit.is(Transparent)) { + // produce a Body annotation for inlining + def untype(tree: Tree): untpd.Tree = tree match { + case Apply(fn, args) => untpd.cpy.Apply(tree)(untype(fn), args) + case TypeApply(fn, args) => untpd.cpy.TypeApply(tree)(untype(fn), args) + case _ => untpd.TypedSplice(tree) + } + val inlineBody = tpd.UntypedSplice(untype(idef.rhs)).withType(idef.rhs.tpe) + inlining.println(i"body annot for $idef: $inlineBody") + localImplicit.addAnnotation(ConcreteBodyAnnotation(inlineBody)) + } + idef } val untpdSplice = tpd.UntypedSplice(addRefs.transform(original)).withType(typed.tpe) seq(implicitBindings, untpdSplice) From 36df150d9abae580a947e3d939954ef47001a3b9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 31 Jul 2018 16:08:51 +0200 Subject: [PATCH 2/5] Make sure to copy local symbols when reducing projections If the result of reducing a projection has local bindings, these bindings need to be copied. Test case in run/Tuple.scala --- .../src/dotty/tools/dotc/typer/Inliner.scala | 7 +++- tests/run/Tuple.scala | 38 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 tests/run/Tuple.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index ae00c153f436..d4ca15b81461 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -469,10 +469,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { val argInPlace = if (trailing.isEmpty) arg else letBindUnless(TreeInfo.Pure, arg)(seq(trailing, _)) - seq(prefix, seq(leading, argInPlace)) - .reporting(res => i"projecting $tree -> $res", inlining) + val fullArg = seq(prefix, seq(leading, argInPlace)) + new TreeTypeMap().transform(fullArg) // make sure local bindings in argument have fresh symbols + .reporting(res => i"projecting $tree -> $res") } else tree + case Block(stats, expr) if stats.forall(isPureBinding) => + cpy.Block(tree)(stats, reduceProjection(expr)) case _ => tree } } diff --git a/tests/run/Tuple.scala b/tests/run/Tuple.scala new file mode 100644 index 000000000000..34eadf2b45bc --- /dev/null +++ b/tests/run/Tuple.scala @@ -0,0 +1,38 @@ +import annotation.showAsInfix + +sealed trait Tuple + +object Tuple { + object Empty extends Tuple + + type Empty = Empty.type + + @showAsInfix + final case class *: [H, T <: Tuple](hd: H, tl: T) extends Tuple + + class HListDeco(val xs: Tuple) extends AnyVal { + transparent def *: [H] (x: H): Tuple = Tuple.*:.apply(x, xs) + + transparent def size: Int = Tuple.size(xs) + } + + transparent def size(xs: Tuple): Int = xs match { + case Empty => 0 + case _ *: xs1 => size(xs1) + 1 + } + + transparent implicit def hlistDeco(xs: Tuple): HListDeco = new HListDeco(xs) + + transparent def apply(): Tuple = Empty + transparent def apply(x1: Any): Tuple = x1 *: Empty + transparent def apply(x1: Any, x2: Any) = x1 *: x2 *: Empty + + val xs0 = Tuple() + val xs1 = Tuple(2) + val xs2 = Tuple(2, "a") + val s0 = xs0.size + val s1 = xs1.size + val s2 = xs2.size +} + +object Test extends App \ No newline at end of file From a0795e265627003d883e9939b9fcedb84eb02192 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 31 Jul 2018 16:15:59 +0200 Subject: [PATCH 3/5] Optimization: Lift bindings out of inline call --- .../src/dotty/tools/dotc/typer/Inliner.scala | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index d4ca15b81461..7060383f9ae8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -85,10 +85,49 @@ object Inliner { * @return An `Inlined` node that refers to the original call and the inlined bindings * and body that replace it. */ - def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree match { - case Block(stats, expr) => - cpy.Block(tree)(stats, inlineCall(expr, pt)) - case _ if (enclosingInlineds.length < ctx.settings.XmaxInlines.value) => + def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { + + /** Set the position of all trees logically contained in the expansion of + * inlined call `call` to the position of `call`. This transform is necessary + * when lifting bindings from the expansion to the outside of the call. + */ + def liftFromInlined(call: Tree) = new TreeMap { + override def transform(t: Tree)(implicit ctx: Context) = { + t match { + case Inlined(t, Nil, expr) if t.isEmpty => expr + case _ => super.transform(t.withPos(call.pos)) + } + } + } + + val bindings = new mutable.ListBuffer[Tree] + + /** Lift bindings in function or argument of inline call to + * the `bindings` buffer. This is done as an optimization to keep + * inline call expansions smaller. + */ + def liftBindings(tree: Tree, liftPos: Tree => Tree): Tree = tree match { + case Block(stats, expr) => + bindings ++= stats.map(liftPos) + liftBindings(expr, liftPos) + case Inlined(call, stats, expr) => + bindings ++= stats.map(liftPos) + val lifter = liftFromInlined(call) + cpy.Inlined(tree)(call, Nil, liftBindings(expr, liftFromInlined(call).transform(_))) + case Apply(fn, args) => + cpy.Apply(tree)(liftBindings(fn, liftPos), args.map(liftBindings(_, liftPos))) + case TypeApply(fn, args) => + cpy.TypeApply(tree)(liftBindings(fn, liftPos), args) + case Select(qual, name) => + cpy.Select(tree)(liftBindings(qual, liftPos), name) + case _ => + tree + } + + val tree1 = liftBindings(tree, identity) + if (bindings.nonEmpty) + cpy.Block(tree)(bindings.toList, inlineCall(tree1, pt)) + else if (enclosingInlineds.length < ctx.settings.XmaxInlines.value) { val body = bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors if (ctx.reporter.hasErrors) tree else { @@ -97,7 +136,8 @@ object Inliner { else ctx.fresh.setProperty(InlineBindings, newMutableSymbolMap[Tree]) new Inliner(tree, body)(inlinerCtx).inlined(pt) } - case _ => + } + else errorTree( tree, i"""|Maximal number of successive inlines (${ctx.settings.XmaxInlines.value}) exceeded, From f4eff107c4a907b0620800f190649724ff465226 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 31 Jul 2018 18:45:37 +0200 Subject: [PATCH 4/5] Fix optimization Can't lift bindings from arguments as this might change evaluation order and it also does not work for cbn parameters. Also, fix stray printing --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 7060383f9ae8..8731515f06cd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -102,7 +102,7 @@ object Inliner { val bindings = new mutable.ListBuffer[Tree] - /** Lift bindings in function or argument of inline call to + /** Lift bindings around inline call or in its function part to * the `bindings` buffer. This is done as an optimization to keep * inline call expansions smaller. */ @@ -115,7 +115,7 @@ object Inliner { val lifter = liftFromInlined(call) cpy.Inlined(tree)(call, Nil, liftBindings(expr, liftFromInlined(call).transform(_))) case Apply(fn, args) => - cpy.Apply(tree)(liftBindings(fn, liftPos), args.map(liftBindings(_, liftPos))) + cpy.Apply(tree)(liftBindings(fn, liftPos), args) case TypeApply(fn, args) => cpy.TypeApply(tree)(liftBindings(fn, liftPos), args) case Select(qual, name) => @@ -123,7 +123,7 @@ object Inliner { case _ => tree } - + val tree1 = liftBindings(tree, identity) if (bindings.nonEmpty) cpy.Block(tree)(bindings.toList, inlineCall(tree1, pt)) @@ -511,7 +511,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { else letBindUnless(TreeInfo.Pure, arg)(seq(trailing, _)) val fullArg = seq(prefix, seq(leading, argInPlace)) new TreeTypeMap().transform(fullArg) // make sure local bindings in argument have fresh symbols - .reporting(res => i"projecting $tree -> $res") + .reporting(res => i"projecting $tree -> $res", inlining) } else tree case Block(stats, expr) if stats.forall(isPureBinding) => From 8b941d611b7ba09b91328e3632c89e2c4d5b7864 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 2 Aug 2018 15:47:24 +0200 Subject: [PATCH 5/5] Don't print prefixes of infix types --- compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 9990d1d9ea8f..6797e57ea15d 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -165,7 +165,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val leftArg = if (isRightAssoc && isInfixType(l)) "(" ~ argText(l) ~ ")" else argText(l) val rightArg = if (!isRightAssoc && isInfixType(r)) "(" ~ argText(r) ~ ")" else argText(r) - leftArg ~ " " ~ toTextLocal(op) ~ " " ~ rightArg + leftArg ~ " " ~ simpleNameString(op.classSymbol) ~ " " ~ rightArg } homogenize(tp) match {