From def52464c86450100490aefc039d4e6d28f047c5 Mon Sep 17 00:00:00 2001 From: Miles Sabin Date: Tue, 30 Apr 2019 14:58:32 +0100 Subject: [PATCH] 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 69cf22021c 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. --- .../dotty/tools/dotc/typer/Implicits.scala | 4 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 96 +++++++++---------- tests/pos/inline-separate/A_1.scala | 5 + tests/pos/inline-separate/Test_2.scala | 7 ++ 4 files changed, 62 insertions(+), 50 deletions(-) create mode 100644 tests/pos/inline-separate/A_1.scala create mode 100644 tests/pos/inline-separate/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index c488f915013b..e5c1cf5fe22e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1619,7 +1619,7 @@ final class SearchRoot extends SearchHistory { // Substitute dictionary references into dictionary entry RHSs val rhsMap = new TreeTypeMap(treeMap = { case id: Ident if vsymMap.contains(id.symbol) => - tpd.ref(vsymMap(id.symbol)).withSpan(id.span) + tpd.ref(vsymMap(id.symbol)) case tree => tree }) val nrhss = rhss.map(rhsMap(_)) @@ -1643,7 +1643,7 @@ final class SearchRoot extends SearchHistory { val res = resMap(tree) - val blk = Block(classDef :: inst :: Nil, res) + val blk = Inliner.reposition(Block(classDef :: inst :: Nil, res), span) success.copy(tree = blk)(success.tstate, success.gstate) } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index c0a685d191d4..193a92f0000a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -119,60 +119,60 @@ object Inliner { } /** Replace `Inlined` node by a block that contains its bindings and expansion */ - def dropInlined(inlined: Inlined)(implicit ctx: Context): Tree = { + def dropInlined(inlined: Inlined)(implicit ctx: Context): Tree = if (enclosingInlineds.nonEmpty) inlined // Remove in the outer most inlined call - else { - val inlinedAtSpan = inlined.call.span - val curSource = ctx.compilationUnit.source - - // Tree copier that changes the source of all trees to `curSource` - val cpyWithNewSource = new TypedTreeCopier { - override protected def sourceFile(tree: tpd.Tree): SourceFile = curSource - override protected val untpdCpy: untpd.UntypedTreeCopier = new untpd.UntypedTreeCopier { - override protected def sourceFile(tree: untpd.Tree): SourceFile = curSource - } - } + else reposition(inlined, inlined.call.span) - /** Removes all Inlined trees, replacing them with blocks. - * Repositions all trees directly inside an inlined expansion of a non empty call to the position of the call. - * Any tree directly inside an empty call (inlined in the inlined code) retains their position. - */ - class Reposition extends TreeMap(cpyWithNewSource) { - def finalize(tree: Tree, copied: untpd.Tree) = - copied.withSpan(tree.span).withAttachmentsFrom(tree).withTypeUnchecked(tree.tpe) - - def reposition(tree: Tree)(implicit ctx: Context): Tree = enclosingInlineds match { - case call :: _ if call.symbol.source != curSource => - tree match { - case _: EmptyTree[_] | _: EmptyValDef[_] => tree - case _ => - // Until we implement JSR-45, we cannot represent in output positions in other source files. - // So, reposition inlined code from other files with the call position: - tree.withSpan(inlinedAtSpan) - } - case _ => tree - } + def reposition(tree: Tree, span: Span)(implicit ctx: Context): Tree = { + val curSource = ctx.compilationUnit.source - override def transform(tree: Tree)(implicit ctx: Context): Tree = { - val transformed = reposition(tree match { - case tree: Inlined => - tpd.seq(transformSub(tree.bindings), transform(tree.expansion)(inlineContext(tree.call)))(ctx.withSource(curSource)) : Tree - case tree: Ident => finalize(tree, untpd.Ident(tree.name)(curSource)) - case tree: Literal => finalize(tree, untpd.Literal(tree.const)(curSource)) - case tree: This => finalize(tree, untpd.This(tree.qual)(curSource)) - case tree: JavaSeqLiteral => finalize(tree, untpd.JavaSeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource)) - case tree: SeqLiteral => finalize(tree, untpd.SeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource)) - case tree: TypeTree => tpd.TypeTree(tree.tpe)(ctx.withSource(curSource)).withSpan(tree.span) - case tree: Bind => finalize(tree, untpd.Bind(tree.name, transform(tree.body))(curSource)) - case _ => super.transform(tree) - }) - assert(transformed.isInstanceOf[EmptyTree[_]] || transformed.isInstanceOf[EmptyValDef[_]] || transformed.source == curSource) - transformed - } + // Tree copier that changes the source of all trees to `curSource` + val cpyWithNewSource = new TypedTreeCopier { + override protected def sourceFile(tree: tpd.Tree): SourceFile = curSource + override protected val untpdCpy: untpd.UntypedTreeCopier = new untpd.UntypedTreeCopier { + override protected def sourceFile(tree: untpd.Tree): SourceFile = curSource } + } - (new Reposition).transform(inlined) + /** Removes all Inlined trees, replacing them with blocks. + * Repositions all trees directly inside an inlined expansion of a non empty call to the position of the call. + * Any tree directly inside an empty call (inlined in the inlined code) retains their position. + */ + class Reposition extends TreeMap(cpyWithNewSource) { + def finalize(tree: Tree, copied: untpd.Tree) = + copied.withSpan(tree.span).withAttachmentsFrom(tree).withTypeUnchecked(tree.tpe) + + def reposition(tree: Tree)(implicit ctx: Context): Tree = enclosingInlineds match { + case call :: _ if call.symbol.source != curSource => + tree match { + case _: EmptyTree[_] | _: EmptyValDef[_] => tree + case _ => + // Until we implement JSR-45, we cannot represent in output positions in other source files. + // So, reposition inlined code from other files with the call position: + tree.withSpan(span) + } + case _ => tree + } + + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + val transformed = reposition(tree match { + case tree: Inlined => + tpd.seq(transformSub(tree.bindings), transform(tree.expansion)(inlineContext(tree.call)))(ctx.withSource(curSource)) : Tree + case tree: Ident => finalize(tree, untpd.Ident(tree.name)(curSource)) + case tree: Literal => finalize(tree, untpd.Literal(tree.const)(curSource)) + case tree: This => finalize(tree, untpd.This(tree.qual)(curSource)) + case tree: JavaSeqLiteral => finalize(tree, untpd.JavaSeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource)) + case tree: SeqLiteral => finalize(tree, untpd.SeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource)) + case tree: TypeTree => tpd.TypeTree(tree.tpe)(ctx.withSource(curSource)).withSpan(tree.span) + case tree: Bind => finalize(tree, untpd.Bind(tree.name, transform(tree.body))(curSource)) + case _ => super.transform(tree) + }) + assert(transformed.isInstanceOf[EmptyTree[_]] || transformed.isInstanceOf[EmptyValDef[_]] || transformed.source == curSource) + transformed + } } + + (new Reposition).transform(tree) } /** Leave only a call trace consisting of diff --git a/tests/pos/inline-separate/A_1.scala b/tests/pos/inline-separate/A_1.scala new file mode 100644 index 000000000000..97844e430516 --- /dev/null +++ b/tests/pos/inline-separate/A_1.scala @@ -0,0 +1,5 @@ +object A { + inline def summon[T] = implicit match { + case t: T => t + } +} diff --git a/tests/pos/inline-separate/Test_2.scala b/tests/pos/inline-separate/Test_2.scala new file mode 100644 index 000000000000..2a7c012b3771 --- /dev/null +++ b/tests/pos/inline-separate/Test_2.scala @@ -0,0 +1,7 @@ +import A._ +object Test extends App { + class Foo(f: => Foo) + inline implicit def foo(implicit f: => Foo): Foo = new Foo(summon[Foo]) + def summonFoo(implicit ev: Foo): Foo = ev + summonFoo +}