Skip to content

Commit bd434a6

Browse files
committed
Dual purpose inliner
The new inliner inlines both inline methods with fully-typed expansions and transparent methods with partially untyped extensions.
1 parent 47f5940 commit bd434a6

File tree

3 files changed

+196
-43
lines changed

3 files changed

+196
-43
lines changed

compiler/src/dotty/tools/dotc/typer/Inliner.scala

Lines changed: 189 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -231,13 +231,125 @@ object Inliner {
231231
val inlineCtx = ctx
232232
inlined.updateAnnotation(LazyBodyAnnotation { _ =>
233233
implicit val ctx = inlineCtx
234-
val body = treeExpr(ctx)
235-
if (ctx.reporter.hasErrors) body else ctx.compilationUnit.inlineAccessors.makeInlineable(body)
234+
val rawBody = treeExpr(ctx)
235+
val typedBody =
236+
if (ctx.reporter.hasErrors) rawBody
237+
else ctx.compilationUnit.inlineAccessors.makeInlineable(rawBody)
238+
val inlineableBody =
239+
if (inlined.isInlinedMethod) typedBody
240+
else addReferences(inlined, originalBody, typedBody)
241+
inlining.println(i"Body to inline for $inlined: $inlineableBody")
242+
inlineableBody
236243
})
237244
}
238245
}
239246
}
240247

248+
/** Tweak untyped tree `original` so that all external references are typed
249+
* and it reflects the changes in the corresponding typed tree `typed` that
250+
* make `typed` inlineable. Concretely:
251+
*
252+
* - all external references via identifiers or this-references are converted
253+
* to typed splices,
254+
* - if X gets an inline accessor in `typed`, references to X in `original`
255+
* are converted to the inline accessor name.
256+
*/
257+
def addReferences(inlineMethod: Symbol,
258+
original: untpd.Tree, typed: tpd.Tree)(implicit ctx: Context): tpd.Tree = {
259+
260+
def isExternal(sym: Symbol) = sym.exists && !isLocal(sym, inlineMethod)
261+
262+
// Maps from positions to external reference types and inline selector names.
263+
object referenced extends TreeTraverser {
264+
val typeAtPos = mutable.Map[Position, Type]()
265+
val nameAtPos = mutable.Map[Position, Name]()
266+
val implicitSyms = mutable.Set[Symbol]()
267+
val implicitRefs = new mutable.ListBuffer[Tree]
268+
def registerIfContextualImplicit(tree: Tree) = tree match {
269+
case tree: RefTree
270+
if tree.removeAttachment(ContextualImplicit).isDefined &&
271+
isExternal(tree.symbol) &&
272+
!implicitSyms.contains(tree.symbol) =>
273+
if (tree.existsSubTree(t => isLocal(tree.symbol, inlineMethod)))
274+
ctx.warning("implicit reference $tree is dropped at inline site because it refers to local symbol(s)", tree.pos)
275+
else {
276+
implicitSyms += tree.symbol
277+
implicitRefs += tree
278+
}
279+
case _ =>
280+
}
281+
def traverse(tree: Tree)(implicit ctx: Context): Unit = {
282+
tree match {
283+
case _: Ident | _: This =>
284+
//println(i"leaf: $tree at ${tree.pos}")
285+
if (isExternal(tree.symbol)) {
286+
inlining.println(i"type at pos ${tree.pos.toSynthetic} = ${tree.tpe}")
287+
typeAtPos(tree.pos.toSynthetic) = tree.tpe
288+
}
289+
case _: Select if tree.symbol.name.is(InlineAccessorName) =>
290+
inlining.println(i"accessor: $tree at ${tree.pos}")
291+
nameAtPos(tree.pos.toSynthetic) = tree.symbol.name
292+
case _ =>
293+
}
294+
registerIfContextualImplicit(tree)
295+
traverseChildren(tree)
296+
}
297+
}
298+
referenced.traverse(typed)
299+
300+
// The untyped tree transform that applies the tweaks
301+
object addRefs extends untpd.UntypedTreeMap {
302+
override def transform(tree: untpd.Tree)(implicit ctx: Context): untpd.Tree = {
303+
def adjustLeaf(tree: untpd.Tree): untpd.Tree = referenced.typeAtPos.get(tree.pos.toSynthetic) match {
304+
case Some(tpe) => untpd.TypedSplice(tree.withType(tpe))
305+
case none => tree
306+
}
307+
def adjustName(name: Name) = referenced.nameAtPos.get(tree.pos.toSynthetic) match {
308+
case Some(n) => n
309+
case none => name
310+
}
311+
def adjustQualifier(tree: untpd.Tree): untpd.Tree = tree match {
312+
case tree @ Ident(name1) =>
313+
referenced.typeAtPos.get(tree.pos.startPos) match {
314+
case Some(tp: ThisType) =>
315+
val qual = untpd.TypedSplice(This(tp.cls).withPos(tree.pos.startPos))
316+
cpy.Select(tree)(qual, name1)
317+
case none =>
318+
tree
319+
}
320+
case tree => tree
321+
}
322+
val tree1 = super.transform(tree)
323+
tree1 match {
324+
case This(_) =>
325+
adjustLeaf(tree1)
326+
case Ident(name) =>
327+
adjustQualifier(adjustLeaf(cpy.Ident(tree1)(adjustName(name))))
328+
case Select(pre, name) =>
329+
cpy.Select(tree1)(pre, adjustName(name))
330+
case tree: untpd.DerivedTypeTree =>
331+
inlining.println(i"inlining derived $tree --> ${ctx.typer.typed(tree)}")
332+
untpd.TypedSplice(ctx.typer.typed(tree))
333+
case _ =>
334+
tree1
335+
}
336+
}
337+
}
338+
val implicitBindings =
339+
for (iref <- referenced.implicitRefs.toList) yield {
340+
val localImplicit = iref.symbol.asTerm.copy(
341+
owner = inlineMethod,
342+
name = UniqueName.fresh(iref.symbol.name.asTermName),
343+
flags = Implicit | Method | Stable,
344+
info = iref.symbol.info.ensureMethodic,
345+
coord = inlineMethod.pos).asTerm
346+
polyDefDef(localImplicit, tps => vrefss =>
347+
iref.appliedToTypes(tps).appliedToArgss(vrefss))
348+
}
349+
val untpdSplice = tpd.UntypedSplice(addRefs.transform(original)).withType(typed.tpe)
350+
seq(implicitBindings, untpdSplice)
351+
}
352+
241353
/** `sym` has an inline method with a known body to inline (note: definitions coming
242354
* from Scala2x class files might be `@inline`, but still lack that body.
243355
*/
@@ -499,17 +611,38 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
499611
case _ => tree
500612
}}
501613

502-
val inlineCtx = inlineContext(call)
614+
val inlineTyper = if (meth.isTransparentMethod) new InlineTyper else new InlineReTyper
615+
val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope
616+
503617
// The complete translation maps references to `this` and parameters to
504618
// corresponding arguments or proxies on the type and term level. It also changes
505619
// the owner from the inlined method to the current owner.
506620
val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil)(inlineCtx)
507621

508-
val expansion = inliner(rhsToInline.withPos(call.pos))
622+
val expansion = inliner.transform(rhsToInline.withPos(call.pos)) match {
623+
case Block(implicits, tpd.UntypedSplice(expansion)) =>
624+
val prevOwners = implicits.map(_.symbol.owner).distinct
625+
val localizer = new TreeTypeMap(oldOwners = prevOwners, newOwners = prevOwners.map(_ => ctx.owner))
626+
val (_, implicits1) = localizer.transformDefs(implicits)
627+
for (idef <- implicits1) {
628+
bindingsBuf += idef.withType(idef.symbol.typeRef).asInstanceOf[ValOrDefDef]
629+
// Note: Substituting new symbols does not automatically lead to good prefixes
630+
// if the previous symbol was owned by a class. That's why we need to set the type
631+
// of `idef` explicitly. It would be nice if substituters were smarter, but
632+
// it seems non-trivial to come up with rules that work in
633+
inlineCtx.enter(idef.symbol)
634+
}
635+
expansion
636+
case tpd.UntypedSplice(expansion) =>
637+
expansion
638+
case expansion =>
639+
expansion
640+
}
641+
509642
trace(i"inlining $call\n, BINDINGS =\n${bindingsBuf.toList}%\n%\nEXPANSION =\n$expansion", inlining, show = true) {
510643

511644
// The final expansion runs a typing pass over the inlined tree. See InlineTyper for details.
512-
val expansion1 = InlineTyper.typed(expansion, pt)(inlineCtx)
645+
val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx)
513646

514647
/** All bindings in `bindingsBuf` except bindings of inlineable closures */
515648
val bindings = bindingsBuf.toList.map(_.withPos(call.pos))
@@ -529,8 +662,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
529662
*/
530663
private object InlineableArg {
531664
lazy val paramProxies = paramProxy.values.toSet
532-
def unapply(tree: Ident)(implicit ctx: Context): Option[Tree] =
533-
if (paramProxies.contains(tree.tpe))
665+
def unapply(tree: Trees.Ident[_])(implicit ctx: Context): Option[Tree] =
666+
if (paramProxies.contains(tree.typeOpt))
534667
bindingsBuf.find(_.name == tree.name) match {
535668
case Some(vdef: ValDef) if vdef.symbol.is(Inline) =>
536669
Some(vdef.rhs.changeOwner(vdef.symbol, ctx.owner))
@@ -541,46 +674,24 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
541674
else None
542675
}
543676

544-
/** A typer for inlined code. Its purpose is:
677+
/** A base trait of InlineTyper and InlineReTyper containing operations that
678+
* work the same way for both. Beyond typing or retyping, an inline typer performs
679+
* the following functions:
680+
*
545681
* 1. Implement constant folding over inlined code
546682
* 2. Selectively expand ifs with constant conditions
547683
* 3. Inline arguments that are by-name closures
548684
* 4. Make sure inlined code is type-correct.
549685
* 5. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed)
550686
*/
551-
private object InlineTyper extends ReTyper {
552-
553-
override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = {
554-
tpe match {
555-
case tpe @ TypeRef(pre, _) if !tpe.symbol.isAccessibleFrom(pre, superAccess) =>
556-
tpe.info match {
557-
case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos)
558-
case _ =>
559-
}
560-
case _ =>
561-
}
562-
super.ensureAccessible(tpe, superAccess, pos)
563-
}
564-
565-
override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context) =
566-
tree.asInstanceOf[tpd.Tree] match {
567-
case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs
568-
case _ => super.typedIdent(tree, pt)
569-
}
570-
571-
override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = {
572-
assert(tree.hasType, tree)
573-
val qual1 = typed(tree.qualifier, selectionProto(tree.name, pt, this))
574-
val res = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt)
575-
ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.pos)
576-
res
577-
}
687+
trait InlineTyping extends Typer {
578688

579689
override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = {
580690
val cond1 = typed(tree.cond, defn.BooleanType)
581691
cond1.tpe.widenTermRefExpr match {
582692
case ConstantType(Constant(condVal: Boolean)) =>
583-
val selected = typed(if (condVal) tree.thenp else tree.elsep, pt)
693+
var selected = typed(if (condVal) tree.thenp else tree.elsep, pt)
694+
if (selected.isEmpty) selected = tpd.Literal(Constant(()))
584695
if (isIdempotentExpr(cond1)) selected
585696
else Block(cond1 :: Nil, selected)
586697
case _ =>
@@ -628,6 +739,48 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
628739
}
629740
}
630741

742+
/** A full typer used for transparent methods */
743+
private class InlineTyper extends Typer with InlineTyping {
744+
745+
override def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree =
746+
tree.splice match {
747+
case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs
748+
case _ => super.typedTypedSplice(tree)
749+
}
750+
}
751+
752+
/** A re-typer used for inlined methods */
753+
private class InlineReTyper extends ReTyper with InlineTyping {
754+
755+
override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = {
756+
tpe match {
757+
case tpe @ TypeRef(pre, _) if !tpe.symbol.isAccessibleFrom(pre, superAccess) =>
758+
tpe.info match {
759+
case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos)
760+
case _ =>
761+
}
762+
case _ =>
763+
}
764+
super.ensureAccessible(tpe, superAccess, pos)
765+
}
766+
767+
override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context) =
768+
tree.asInstanceOf[tpd.Tree] match {
769+
case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs
770+
case _ => super.typedIdent(tree, pt)
771+
}
772+
773+
override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = {
774+
assert(tree.hasType, tree)
775+
val qual1 = typed(tree.qualifier, selectionProto(tree.name, pt, this))
776+
val res = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt)
777+
ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.pos)
778+
res
779+
}
780+
781+
override def newLikeThis: Typer = new InlineReTyper
782+
}
783+
631784
/** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings.
632785
* Inline def bindings that are used only once.
633786
*/

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1910,7 +1910,7 @@ class Typer extends Namer
19101910
// from separately compiled files - the original BodyAnnotation is not kept.
19111911
}
19121912
else {
1913-
assert(mdef.symbol.isTransparentMethod)
1913+
assert(mdef1.symbol.isTransparentMethod, mdef.symbol)
19141914
Inliner.bodyToInline(mdef1.symbol) // just make sure accessors are computed,
19151915
buf += mdef1 // but keep original definition, since inline-expanded code
19161916
// is pickled in this case.

tests/run/typelevel.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ object Test extends App {
1717
}
1818

1919
val x0 = ToNat(0)
20-
//val y0: Z = x0.value
21-
//val z0: x0.Result = y0
20+
val y0: Z = x0.value
21+
val z0: x0.Result = y0
2222
val x1 = ToNat(1)
23-
//val y1: S[Z] = x1.value
24-
//val z1: x1.Result = y1
23+
val y1: S[Z] = x1.value
24+
val z1: x1.Result = y1
2525
val x2 = ToNat(2)
26-
//val y2: S[S[Z]] = x2.value
27-
//val z2: x2.Result = y2
26+
val y2: S[S[Z]] = x2.value
27+
val z2: x2.Result = y2
2828
println(x0)
2929
println(x1)
3030
println(x2)

0 commit comments

Comments
 (0)