Skip to content

Fixes to transparent functions #4881

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
70 changes: 59 additions & 11 deletions compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 around inline call or in its function part 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)
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 {
Expand All @@ -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,
Expand Down Expand Up @@ -469,10 +509,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))
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", inlining)
}
else tree
case Block(stats, expr) if stats.forall(isPureBinding) =>
cpy.Block(tree)(stats, reduceProjection(expr))
case _ => tree
}
}
Expand Down Expand Up @@ -793,6 +836,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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be clearer and more performant if the conditions are inverted

!boundSym.is(TransparentImplicitMethod) &&
refCount.get(boundSym) match {
  ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt the "more performant part" since the test isboundSym.is(TransparentImplicitMethod) is ~99% likely to succeed. So it's better to put the test first that prunes more cases.


val inlineBindings = new TreeMap {
override def transform(t: Tree)(implicit ctx: Context) = t match {
case t: RefTree =>
Expand All @@ -812,11 +863,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)
}
Expand Down
18 changes: 15 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
38 changes: 38 additions & 0 deletions tests/run/Tuple.scala
Original file line number Diff line number Diff line change
@@ -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