Skip to content

Commit e4080ba

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 f25d48d commit e4080ba

File tree

7 files changed

+268
-64
lines changed

7 files changed

+268
-64
lines changed

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

Lines changed: 201 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import StdNames.nme
1515
import Contexts.Context
1616
import Names.{Name, TermName, EmptyTermName}
1717
import NameOps._
18-
import NameKinds.{ClassifiedNameKind, InlineAccessorName}
18+
import NameKinds.{ClassifiedNameKind, InlineAccessorName, UniqueName}
1919
import ProtoTypes.selectionProto
2020
import SymDenotations.SymDenotation
2121
import Annotations._
@@ -63,23 +63,13 @@ object Inliner {
6363
// This is quite tricky, as such types can appear anywhere, including as parts
6464
// of types of other things. For the moment we do nothing and complain
6565
// at the implicit expansion site if there's a reference to an inaccessible type.
66-
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
67-
case tree: Assign =>
68-
transform(tree.lhs) match {
69-
case lhs1: RefTree =>
70-
lhs1.name match {
71-
case InlineAccessorName(name) =>
72-
cpy.Apply(tree)(useSetter(lhs1), transform(tree.rhs) :: Nil)
73-
case _ =>
74-
super.transform(tree)
75-
}
76-
case _ =>
77-
super.transform(tree)
78-
}
79-
case _ =>
80-
super.transform(accessorIfNeeded(tree))
81-
}
82-
66+
override def transform(tree: Tree)(implicit ctx: Context): Tree =
67+
super.transform(accessorIfNeeded(tree)) match {
68+
case tree1 @ Assign(lhs: RefTree, rhs) if lhs.symbol.name.is(InlineAccessorName) =>
69+
cpy.Apply(tree1)(useSetter(lhs), rhs :: Nil)
70+
case tree1 =>
71+
tree1
72+
}
8373
}
8474

8575
/** Adds accessors for all non-public term members accessed
@@ -114,22 +104,134 @@ object Inliner {
114104
* to have the inlined method as owner.
115105
*/
116106
def registerInlineInfo(
117-
sym: SymDenotation, originalBody: untpd.Tree, treeExpr: Context => Tree)(implicit ctx: Context): Unit = {
118-
sym.unforcedAnnotation(defn.BodyAnnot) match {
107+
inlined: Symbol, originalBody: untpd.Tree, treeExpr: Context => Tree)(implicit ctx: Context): Unit = {
108+
inlined.unforcedAnnotation(defn.BodyAnnot) match {
119109
case Some(ann: ConcreteBodyAnnotation) =>
120110
case Some(ann: LazyBodyAnnotation) if ann.isEvaluated =>
121111
case _ =>
122112
if (!ctx.isAfterTyper) {
123113
val inlineCtx = ctx
124-
sym.updateAnnotation(LazyBodyAnnotation { _ =>
114+
inlined.updateAnnotation(LazyBodyAnnotation { _ =>
125115
implicit val ctx = inlineCtx
126-
val body = treeExpr(ctx)
127-
if (ctx.reporter.hasErrors) body else ctx.compilationUnit.inlineAccessors.makeInlineable(body)
116+
val rawBody = treeExpr(ctx)
117+
val typedBody =
118+
if (ctx.reporter.hasErrors) rawBody
119+
else ctx.compilationUnit.inlineAccessors.makeInlineable(rawBody)
120+
val inlineableBody =
121+
if (inlined.isInlinedMethod) typedBody
122+
else addReferences(inlined, originalBody, typedBody)
123+
inlining.println(i"Body to inline for $inlined: $inlineableBody")
124+
inlineableBody
128125
})
129126
}
130127
}
131128
}
132129

130+
/** Tweak untyped tree `original` so that all external references are typed
131+
* and it reflects the changes in the corresponding typed tree `typed` that
132+
* make `typed` inlineable. Concretely:
133+
*
134+
* - all external references via identifiers or this-references are converted
135+
* to typed splices,
136+
* - if X gets an inline accessor in `typed`, references to X in `original`
137+
* are converted to the inline accessor name.
138+
*/
139+
def addReferences(inlineMethod: Symbol,
140+
original: untpd.Tree, typed: tpd.Tree)(implicit ctx: Context): tpd.Tree = {
141+
142+
def isExternal(sym: Symbol) = sym.exists && !isLocal(sym, inlineMethod)
143+
144+
// Maps from positions to external reference types and inline selector names.
145+
object referenced extends TreeTraverser {
146+
val typeAtPos = mutable.Map[Position, Type]()
147+
val nameAtPos = mutable.Map[Position, Name]()
148+
val implicitSyms = mutable.Set[Symbol]()
149+
val implicitRefs = new mutable.ListBuffer[Tree]
150+
def registerIfContextualImplicit(tree: Tree) = tree match {
151+
case tree: RefTree
152+
if tree.removeAttachment(ContextualImplicit).isDefined &&
153+
isExternal(tree.symbol) &&
154+
!implicitSyms.contains(tree.symbol) =>
155+
if (tree.existsSubTree(t => isLocal(tree.symbol, inlineMethod)))
156+
ctx.warning("implicit reference $tree is dropped at inline site because it refers to local symbol(s)", tree.pos)
157+
else {
158+
implicitSyms += tree.symbol
159+
implicitRefs += tree
160+
}
161+
case _ =>
162+
}
163+
def traverse(tree: Tree)(implicit ctx: Context): Unit = {
164+
tree match {
165+
case _: Ident | _: This =>
166+
//println(i"leaf: $tree at ${tree.pos}")
167+
if (isExternal(tree.symbol)) {
168+
inlining.println(i"type at pos ${tree.pos.toSynthetic} = ${tree.tpe}")
169+
typeAtPos(tree.pos.toSynthetic) = tree.tpe
170+
}
171+
case _: Select if tree.symbol.name.is(InlineAccessorName) =>
172+
inlining.println(i"accessor: $tree at ${tree.pos}")
173+
nameAtPos(tree.pos.toSynthetic) = tree.symbol.name
174+
case _ =>
175+
}
176+
registerIfContextualImplicit(tree)
177+
traverseChildren(tree)
178+
}
179+
}
180+
referenced.traverse(typed)
181+
182+
// The untyped tree transform that applies the tweaks
183+
object addRefs extends untpd.UntypedTreeMap {
184+
override def transform(tree: untpd.Tree)(implicit ctx: Context): untpd.Tree = {
185+
def adjustLeaf(tree: untpd.Tree): untpd.Tree = referenced.typeAtPos.get(tree.pos.toSynthetic) match {
186+
case Some(tpe) => untpd.TypedSplice(tree.withType(tpe))
187+
case none => tree
188+
}
189+
def adjustName(name: Name) = referenced.nameAtPos.get(tree.pos.toSynthetic) match {
190+
case Some(n) => n
191+
case none => name
192+
}
193+
def adjustQualifier(tree: untpd.Tree): untpd.Tree = tree match {
194+
case tree @ Ident(name1) =>
195+
referenced.typeAtPos.get(tree.pos.startPos) match {
196+
case Some(tp: ThisType) =>
197+
val qual = untpd.TypedSplice(This(tp.cls).withPos(tree.pos.startPos))
198+
cpy.Select(tree)(qual, name1)
199+
case none =>
200+
tree
201+
}
202+
case tree => tree
203+
}
204+
val tree1 = super.transform(tree)
205+
tree1 match {
206+
case This(_) =>
207+
adjustLeaf(tree1)
208+
case Ident(name) =>
209+
adjustQualifier(adjustLeaf(cpy.Ident(tree1)(adjustName(name))))
210+
case Select(pre, name) =>
211+
cpy.Select(tree1)(pre, adjustName(name))
212+
case tree: untpd.DerivedTypeTree =>
213+
inlining.println(i"inlining derived $tree --> ${ctx.typer.typed(tree)}")
214+
untpd.TypedSplice(ctx.typer.typed(tree))
215+
case _ =>
216+
tree1
217+
}
218+
}
219+
}
220+
val implicitBindings =
221+
for (iref <- referenced.implicitRefs.toList) yield {
222+
val localImplicit = iref.symbol.asTerm.copy(
223+
owner = inlineMethod,
224+
name = UniqueName.fresh(iref.symbol.name.asTermName),
225+
flags = Implicit | Method | Stable,
226+
info = iref.symbol.info.ensureMethodic,
227+
coord = inlineMethod.pos).asTerm
228+
polyDefDef(localImplicit, tps => vrefss =>
229+
iref.appliedToTypes(tps).appliedToArgss(vrefss))
230+
}
231+
val untpdSplice = tpd.UntypedSplice(addRefs.transform(original)).withType(typed.tpe)
232+
seq(implicitBindings, untpdSplice)
233+
}
234+
133235
/** `sym` has an inline method with a known body to inline (note: definitions coming
134236
* from Scala2x class files might be `@inline`, but still lack that body.
135237
*/
@@ -391,17 +493,38 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
391493
case _ => tree
392494
}}
393495

394-
val inlineCtx = inlineContext(call)
496+
val inlineTyper = if (meth.isTransparentMethod) new InlineTyper else new InlineReTyper
497+
val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope
498+
395499
// The complete translation maps references to `this` and parameters to
396500
// corresponding arguments or proxies on the type and term level. It also changes
397501
// the owner from the inlined method to the current owner.
398502
val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil)(inlineCtx)
399503

400-
val expansion = inliner(rhsToInline.withPos(call.pos))
504+
val expansion = inliner.transform(rhsToInline.withPos(call.pos)) match {
505+
case Block(implicits, tpd.UntypedSplice(expansion)) =>
506+
val prevOwners = implicits.map(_.symbol.owner).distinct
507+
val localizer = new TreeTypeMap(oldOwners = prevOwners, newOwners = prevOwners.map(_ => ctx.owner))
508+
val (_, implicits1) = localizer.transformDefs(implicits)
509+
for (idef <- implicits1) {
510+
bindingsBuf += idef.withType(idef.symbol.typeRef).asInstanceOf[ValOrDefDef]
511+
// Note: Substituting new symbols does not automatically lead to good prefixes
512+
// if the previous symbol was owned by a class. That's why we need to set the type
513+
// of `idef` explicitly. It would be nice if substituters were smarter, but
514+
// it seems non-trivial to come up with rules that work in
515+
inlineCtx.enter(idef.symbol)
516+
}
517+
expansion
518+
case tpd.UntypedSplice(expansion) =>
519+
expansion
520+
case expansion =>
521+
expansion
522+
}
523+
401524
trace(i"inlining $call\n, BINDINGS =\n${bindingsBuf.toList}%\n%\nEXPANSION =\n$expansion", inlining, show = true) {
402525

403526
// The final expansion runs a typing pass over the inlined tree. See InlineTyper for details.
404-
val expansion1 = InlineTyper.typed(expansion, pt)(inlineCtx)
527+
val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx)
405528

406529
/** All bindings in `bindingsBuf` except bindings of inlineable closures */
407530
val bindings = bindingsBuf.toList.map(_.withPos(call.pos))
@@ -421,8 +544,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
421544
*/
422545
private object InlineableArg {
423546
lazy val paramProxies = paramProxy.values.toSet
424-
def unapply(tree: Ident)(implicit ctx: Context): Option[Tree] =
425-
if (paramProxies.contains(tree.tpe))
547+
def unapply(tree: Trees.Ident[_])(implicit ctx: Context): Option[Tree] =
548+
if (paramProxies.contains(tree.typeOpt))
426549
bindingsBuf.find(_.name == tree.name) match {
427550
case Some(vdef: ValDef) if vdef.symbol.is(Inline) =>
428551
Some(vdef.rhs.changeOwner(vdef.symbol, ctx.owner))
@@ -433,46 +556,24 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
433556
else None
434557
}
435558

436-
/** A typer for inlined code. Its purpose is:
559+
/** A base trait of InlineTyper and InlineReTyper containing operations that
560+
* work the same way for both. Beyond typing or retyping, an inline typer performs
561+
* the following functions:
562+
*
437563
* 1. Implement constant folding over inlined code
438564
* 2. Selectively expand ifs with constant conditions
439-
* 3. Inline arguments that are inlineable closures
565+
* 3. Inline arguments that are by-name closures
440566
* 4. Make sure inlined code is type-correct.
441567
* 5. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed)
442568
*/
443-
private object InlineTyper extends ReTyper {
444-
445-
override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = {
446-
tpe match {
447-
case tpe @ TypeRef(pre, _) if !tpe.symbol.isAccessibleFrom(pre, superAccess) =>
448-
tpe.info match {
449-
case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos)
450-
case _ =>
451-
}
452-
case _ =>
453-
}
454-
super.ensureAccessible(tpe, superAccess, pos)
455-
}
456-
457-
override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context) =
458-
tree.asInstanceOf[tpd.Tree] match {
459-
case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs
460-
case _ => super.typedIdent(tree, pt)
461-
}
462-
463-
override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = {
464-
assert(tree.hasType, tree)
465-
val qual1 = typed(tree.qualifier, selectionProto(tree.name, pt, this))
466-
val res = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt)
467-
ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.pos)
468-
res
469-
}
569+
trait InlineTyping extends Typer {
470570

471571
override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = {
472572
val cond1 = typed(tree.cond, defn.BooleanType)
473573
cond1.tpe.widenTermRefExpr match {
474574
case ConstantType(Constant(condVal: Boolean)) =>
475-
val selected = typed(if (condVal) tree.thenp else tree.elsep, pt)
575+
var selected = typed(if (condVal) tree.thenp else tree.elsep, pt)
576+
if (selected.isEmpty) selected = tpd.Literal(Constant(()))
476577
if (isIdempotentExpr(cond1)) selected
477578
else Block(cond1 :: Nil, selected)
478579
case _ =>
@@ -509,6 +610,48 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
509610
}
510611
}
511612

613+
/** A full typer used for transparent methods */
614+
private class InlineTyper extends Typer with InlineTyping {
615+
616+
override def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree =
617+
tree.splice match {
618+
case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs
619+
case _ => super.typedTypedSplice(tree)
620+
}
621+
}
622+
623+
/** A re-typer used for inlined methods */
624+
private class InlineReTyper extends ReTyper with InlineTyping {
625+
626+
override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = {
627+
tpe match {
628+
case tpe @ TypeRef(pre, _) if !tpe.symbol.isAccessibleFrom(pre, superAccess) =>
629+
tpe.info match {
630+
case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos)
631+
case _ =>
632+
}
633+
case _ =>
634+
}
635+
super.ensureAccessible(tpe, superAccess, pos)
636+
}
637+
638+
override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context) =
639+
tree.asInstanceOf[tpd.Tree] match {
640+
case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs
641+
case _ => super.typedIdent(tree, pt)
642+
}
643+
644+
override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = {
645+
assert(tree.hasType, tree)
646+
val qual1 = typed(tree.qualifier, selectionProto(tree.name, pt, this))
647+
val res = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt)
648+
ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.pos)
649+
res
650+
}
651+
652+
override def newLikeThis: Typer = new InlineReTyper
653+
}
654+
512655
/** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings.
513656
* Inline def bindings that are used only once.
514657
*/

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -821,13 +821,13 @@ class Namer { typer: Typer =>
821821
else completeInCreationContext(denot)
822822
}
823823

824-
private def addInlineInfo(denot: SymDenotation) = original match {
825-
case original: untpd.DefDef if denot.isInlineableMethod =>
824+
private def addInlineInfo(sym: Symbol) = original match {
825+
case original: untpd.DefDef if sym.isInlineableMethod =>
826826
Inliner.registerInlineInfo(
827-
denot,
827+
sym,
828828
original.rhs,
829829
implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs
830-
)(localContext(denot.symbol))
830+
)(localContext(sym))
831831
case _ =>
832832
}
833833

@@ -840,7 +840,7 @@ class Namer { typer: Typer =>
840840
case original: MemberDef => addAnnotations(sym, original)
841841
case _ =>
842842
}
843-
addInlineInfo(denot)
843+
addInlineInfo(sym)
844844
denot.info = typeSig(sym)
845845
Checking.checkWellFormed(sym)
846846
denot.info = avoidPrivateLeaks(sym, sym.pos)

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

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

tests/run/inline-implicits.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(X(),Y())

0 commit comments

Comments
 (0)