@@ -104,22 +104,134 @@ object Inliner {
104104 * to have the inlined method as owner.
105105 */
106106 def registerInlineInfo (
107- sym : SymDenotation , originalBody : untpd.Tree , treeExpr : Context => Tree )(implicit ctx : Context ): Unit = {
108- sym .unforcedAnnotation(defn.BodyAnnot ) match {
107+ inlined : Symbol , originalBody : untpd.Tree , treeExpr : Context => Tree )(implicit ctx : Context ): Unit = {
108+ inlined .unforcedAnnotation(defn.BodyAnnot ) match {
109109 case Some (ann : ConcreteBodyAnnotation ) =>
110110 case Some (ann : LazyBodyAnnotation ) if ann.isEvaluated =>
111111 case _ =>
112112 if (! ctx.isAfterTyper) {
113113 val inlineCtx = ctx
114- sym .updateAnnotation(LazyBodyAnnotation { _ =>
114+ inlined .updateAnnotation(LazyBodyAnnotation { _ =>
115115 implicit val ctx = inlineCtx
116- val body = treeExpr(ctx)
117- 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
118125 })
119126 }
120127 }
121128 }
122129
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+
123235 /** `sym` has an inline method with a known body to inline (note: definitions coming
124236 * from Scala2x class files might be `@inline`, but still lack that body.
125237 */
@@ -383,12 +495,32 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
383495
384496 val inlineTyper = new InlineTyper
385497 val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope
498+
386499 // The complete translation maps references to `this` and parameters to
387500 // corresponding arguments or proxies on the type and term level. It also changes
388501 // the owner from the inlined method to the current owner.
389502 val inliner = new TreeTypeMap (typeMap, treeMap, meth :: Nil , ctx.owner :: Nil )(inlineCtx)
390503
391- 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+
392524 trace(i " inlining $call\n , BINDINGS = \n ${bindingsBuf.toList}% \n % \n EXPANSION = \n $expansion" , inlining, show = true ) {
393525
394526 // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details.
0 commit comments