Skip to content

Commit def5246

Browse files
committed
Fix interaction between by-name implicits and inlining
The construction of the dictionaries needed to support recursive by-name implicit arguments requires trees to be hoisted out of the arguments as they are elaborated. Moving trees around in this way has always been a delicate operation because of the need to maintain correct owner chains. Inlining and the position checking phase added in 69cf220 complicates this still further because we now have to make the source associated with the hoisted tree match the checkers expectations after it has been moved. This is done by reusing the repositioning logic from the inliner. The issue this commit fixes only shows up in cases where an inline method defined is used as part of a by-name implicit argument resolution in a different source file.
1 parent fbc9d0a commit def5246

File tree

4 files changed

+62
-50
lines changed

4 files changed

+62
-50
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1619,7 +1619,7 @@ final class SearchRoot extends SearchHistory {
16191619
// Substitute dictionary references into dictionary entry RHSs
16201620
val rhsMap = new TreeTypeMap(treeMap = {
16211621
case id: Ident if vsymMap.contains(id.symbol) =>
1622-
tpd.ref(vsymMap(id.symbol)).withSpan(id.span)
1622+
tpd.ref(vsymMap(id.symbol))
16231623
case tree => tree
16241624
})
16251625
val nrhss = rhss.map(rhsMap(_))
@@ -1643,7 +1643,7 @@ final class SearchRoot extends SearchHistory {
16431643

16441644
val res = resMap(tree)
16451645

1646-
val blk = Block(classDef :: inst :: Nil, res)
1646+
val blk = Inliner.reposition(Block(classDef :: inst :: Nil, res), span)
16471647

16481648
success.copy(tree = blk)(success.tstate, success.gstate)
16491649
}

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

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -119,60 +119,60 @@ object Inliner {
119119
}
120120

121121
/** Replace `Inlined` node by a block that contains its bindings and expansion */
122-
def dropInlined(inlined: Inlined)(implicit ctx: Context): Tree = {
122+
def dropInlined(inlined: Inlined)(implicit ctx: Context): Tree =
123123
if (enclosingInlineds.nonEmpty) inlined // Remove in the outer most inlined call
124-
else {
125-
val inlinedAtSpan = inlined.call.span
126-
val curSource = ctx.compilationUnit.source
127-
128-
// Tree copier that changes the source of all trees to `curSource`
129-
val cpyWithNewSource = new TypedTreeCopier {
130-
override protected def sourceFile(tree: tpd.Tree): SourceFile = curSource
131-
override protected val untpdCpy: untpd.UntypedTreeCopier = new untpd.UntypedTreeCopier {
132-
override protected def sourceFile(tree: untpd.Tree): SourceFile = curSource
133-
}
134-
}
124+
else reposition(inlined, inlined.call.span)
135125

136-
/** Removes all Inlined trees, replacing them with blocks.
137-
* Repositions all trees directly inside an inlined expansion of a non empty call to the position of the call.
138-
* Any tree directly inside an empty call (inlined in the inlined code) retains their position.
139-
*/
140-
class Reposition extends TreeMap(cpyWithNewSource) {
141-
def finalize(tree: Tree, copied: untpd.Tree) =
142-
copied.withSpan(tree.span).withAttachmentsFrom(tree).withTypeUnchecked(tree.tpe)
143-
144-
def reposition(tree: Tree)(implicit ctx: Context): Tree = enclosingInlineds match {
145-
case call :: _ if call.symbol.source != curSource =>
146-
tree match {
147-
case _: EmptyTree[_] | _: EmptyValDef[_] => tree
148-
case _ =>
149-
// Until we implement JSR-45, we cannot represent in output positions in other source files.
150-
// So, reposition inlined code from other files with the call position:
151-
tree.withSpan(inlinedAtSpan)
152-
}
153-
case _ => tree
154-
}
126+
def reposition(tree: Tree, span: Span)(implicit ctx: Context): Tree = {
127+
val curSource = ctx.compilationUnit.source
155128

156-
override def transform(tree: Tree)(implicit ctx: Context): Tree = {
157-
val transformed = reposition(tree match {
158-
case tree: Inlined =>
159-
tpd.seq(transformSub(tree.bindings), transform(tree.expansion)(inlineContext(tree.call)))(ctx.withSource(curSource)) : Tree
160-
case tree: Ident => finalize(tree, untpd.Ident(tree.name)(curSource))
161-
case tree: Literal => finalize(tree, untpd.Literal(tree.const)(curSource))
162-
case tree: This => finalize(tree, untpd.This(tree.qual)(curSource))
163-
case tree: JavaSeqLiteral => finalize(tree, untpd.JavaSeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource))
164-
case tree: SeqLiteral => finalize(tree, untpd.SeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource))
165-
case tree: TypeTree => tpd.TypeTree(tree.tpe)(ctx.withSource(curSource)).withSpan(tree.span)
166-
case tree: Bind => finalize(tree, untpd.Bind(tree.name, transform(tree.body))(curSource))
167-
case _ => super.transform(tree)
168-
})
169-
assert(transformed.isInstanceOf[EmptyTree[_]] || transformed.isInstanceOf[EmptyValDef[_]] || transformed.source == curSource)
170-
transformed
171-
}
129+
// Tree copier that changes the source of all trees to `curSource`
130+
val cpyWithNewSource = new TypedTreeCopier {
131+
override protected def sourceFile(tree: tpd.Tree): SourceFile = curSource
132+
override protected val untpdCpy: untpd.UntypedTreeCopier = new untpd.UntypedTreeCopier {
133+
override protected def sourceFile(tree: untpd.Tree): SourceFile = curSource
172134
}
135+
}
173136

174-
(new Reposition).transform(inlined)
137+
/** Removes all Inlined trees, replacing them with blocks.
138+
* Repositions all trees directly inside an inlined expansion of a non empty call to the position of the call.
139+
* Any tree directly inside an empty call (inlined in the inlined code) retains their position.
140+
*/
141+
class Reposition extends TreeMap(cpyWithNewSource) {
142+
def finalize(tree: Tree, copied: untpd.Tree) =
143+
copied.withSpan(tree.span).withAttachmentsFrom(tree).withTypeUnchecked(tree.tpe)
144+
145+
def reposition(tree: Tree)(implicit ctx: Context): Tree = enclosingInlineds match {
146+
case call :: _ if call.symbol.source != curSource =>
147+
tree match {
148+
case _: EmptyTree[_] | _: EmptyValDef[_] => tree
149+
case _ =>
150+
// Until we implement JSR-45, we cannot represent in output positions in other source files.
151+
// So, reposition inlined code from other files with the call position:
152+
tree.withSpan(span)
153+
}
154+
case _ => tree
155+
}
156+
157+
override def transform(tree: Tree)(implicit ctx: Context): Tree = {
158+
val transformed = reposition(tree match {
159+
case tree: Inlined =>
160+
tpd.seq(transformSub(tree.bindings), transform(tree.expansion)(inlineContext(tree.call)))(ctx.withSource(curSource)) : Tree
161+
case tree: Ident => finalize(tree, untpd.Ident(tree.name)(curSource))
162+
case tree: Literal => finalize(tree, untpd.Literal(tree.const)(curSource))
163+
case tree: This => finalize(tree, untpd.This(tree.qual)(curSource))
164+
case tree: JavaSeqLiteral => finalize(tree, untpd.JavaSeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource))
165+
case tree: SeqLiteral => finalize(tree, untpd.SeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource))
166+
case tree: TypeTree => tpd.TypeTree(tree.tpe)(ctx.withSource(curSource)).withSpan(tree.span)
167+
case tree: Bind => finalize(tree, untpd.Bind(tree.name, transform(tree.body))(curSource))
168+
case _ => super.transform(tree)
169+
})
170+
assert(transformed.isInstanceOf[EmptyTree[_]] || transformed.isInstanceOf[EmptyValDef[_]] || transformed.source == curSource)
171+
transformed
172+
}
175173
}
174+
175+
(new Reposition).transform(tree)
176176
}
177177

178178
/** Leave only a call trace consisting of

tests/pos/inline-separate/A_1.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object A {
2+
inline def summon[T] = implicit match {
3+
case t: T => t
4+
}
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import A._
2+
object Test extends App {
3+
class Foo(f: => Foo)
4+
inline implicit def foo(implicit f: => Foo): Foo = new Foo(summon[Foo])
5+
def summonFoo(implicit ev: Foo): Foo = ev
6+
summonFoo
7+
}

0 commit comments

Comments
 (0)