diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 6fc01fcee7c7..66dc5b1973a6 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -37,6 +37,7 @@ class Compiler { /** Phases dealing with the frontend up to trees ready for TASTY pickling */ protected def frontendPhases: List[List[Phase]] = List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer + List(new YCheckPositions) :: // YCheck positions List(new Staging) :: // Check PCP, heal quoted types and expand macros List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks List(new PostTyper) :: // Additional checks and cleanups after type checking diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index eeabe135b573..b3a9fe523872 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -481,6 +481,7 @@ object Contexts { } def typerPhase: Phase = base.typerPhase + def postTyperPhase: Phase = base.postTyperPhase def sbtExtractDependenciesPhase: Phase = base.sbtExtractDependenciesPhase def picklerPhase: Phase = base.picklerPhase def reifyQuotesPhase: Phase = base.reifyQuotesPhase diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index b361802e3a77..796ebcb5a0fc 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -217,6 +217,7 @@ object Phases { } private[this] var myTyperPhase: Phase = _ + private[this] var myPostTyperPhase: Phase = _ private[this] var mySbtExtractDependenciesPhase: Phase = _ private[this] var myPicklerPhase: Phase = _ private[this] var myReifyQuotesPhase: Phase = _ @@ -234,6 +235,7 @@ object Phases { private[this] var myGenBCodePhase: Phase = _ final def typerPhase: Phase = myTyperPhase + final def postTyperPhase: Phase = myPostTyperPhase final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase final def picklerPhase: Phase = myPicklerPhase final def reifyQuotesPhase: Phase = myReifyQuotesPhase @@ -254,6 +256,7 @@ object Phases { def phaseOfClass(pclass: Class[_]) = phases.find(pclass.isInstance).getOrElse(NoPhase) myTyperPhase = phaseOfClass(classOf[FrontEnd]) + myPostTyperPhase = phaseOfClass(classOf[PostTyper]) mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies]) myPicklerPhase = phaseOfClass(classOf[Pickler]) myReifyQuotesPhase = phaseOfClass(classOf[ReifyQuotes]) diff --git a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala index 657c053f1369..92054cbcc355 100644 --- a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala +++ b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala @@ -33,9 +33,13 @@ import scala.annotation.constructorOnly class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) { import tpd._ - override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { - case tree: DefDef if tree.symbol.is(Inline) && level > 0 => EmptyTree - case _ => checkLevel(super.transform(tree)) + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + if (tree.source != ctx.source && tree.source.exists) + transform(tree)(ctx.withSource(tree.source)) + else tree match { + case tree: DefDef if tree.symbol.is(Inline) && level > 0 => EmptyTree + case _ => checkLevel(super.transform(tree)) + } } /** Transform quoted trees while maintaining phase correctness */ diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index ddfb98629b95..df6f73e623ea 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -306,7 +306,14 @@ class ReifyQuotes extends MacroTransform { val tpe = MethodType(defn.SeqType.appliedTo(defn.AnyType) :: Nil, tree.tpe.widen) val meth = ctx.newSymbol(lambdaOwner, UniqueName.fresh(nme.ANON_FUN), Synthetic | Method, tpe) - Closure(meth, tss => body(tss.head.head)(ctx.withOwner(meth)).changeOwner(ctx.owner, meth)) + val closure = Closure(meth, tss => body(tss.head.head)(ctx.withOwner(meth)).changeOwner(ctx.owner, meth)).withSpan(tree.span) + + enclosingInlineds match { + case enclosingInline :: _ => + // In case a tree was inlined inside of the quote and we this closure corresponds to code within it we need to keep the inlined node. + Inlined(enclosingInline, Nil, closure)(ctx.withSource(lambdaOwner.topLevelClass.source)) + case Nil => closure + } } private def transformWithCapturer(tree: Tree)(capturer: mutable.Map[Symbol, Tree] => Tree => Tree)(implicit ctx: Context): Tree = { @@ -346,8 +353,10 @@ class ReifyQuotes extends MacroTransform { Hole(idx, splices).withType(tpe).asInstanceOf[Hole] } - override def transform(tree: Tree)(implicit ctx: Context): Tree = - reporting.trace(i"Reifier.transform $tree at $level", show = true) { + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + if (tree.source != ctx.source && tree.source.exists) + transform(tree)(ctx.withSource(tree.source)) + else reporting.trace(i"Reifier.transform $tree at $level", show = true) { tree match { case tree: RefTree if isCaptured(tree.symbol, level) => val body = capturers(tree.symbol).apply(tree) @@ -366,6 +375,7 @@ class ReifyQuotes extends MacroTransform { super.transform(tree) } } + } private def liftList(list: List[Tree], tpe: Type)(implicit ctx: Context): Tree = { list.foldRight[Tree](ref(defn.NilModule)) { (x, acc) => diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 77d699b36fd0..6d839ba2160a 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -14,8 +14,7 @@ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.tasty.TreePickler.Hole import dotty.tools.dotc.core.Types._ -import dotty.tools.dotc.util.SourcePosition -import dotty.tools.dotc.util.Spans._ +import dotty.tools.dotc.util.{SourceFile, SourcePosition} import dotty.tools.dotc.transform.SymUtils._ import dotty.tools.dotc.transform.TreeMapWithStages._ import dotty.tools.dotc.typer.Implicits.SearchFailureType diff --git a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala index 6e44cfcc8b40..c1f98a5a42c5 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala @@ -30,6 +30,7 @@ import scala.annotation.constructorOnly * @param levels a stacked map from symbols to the levels in which they were defined */ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMapWithImplicits { + import tpd._ import TreeMapWithStages._ @@ -68,7 +69,9 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap protected def transformSplice(body: Tree, splice: Tree)(implicit ctx: Context): Tree override def transform(tree: Tree)(implicit ctx: Context): Tree = { - reporting.trace(i"StagingTransformer.transform $tree at $level", show = true) { + if (tree.source != ctx.source && tree.source.exists) + transform(tree)(ctx.withSource(tree.source)) + else reporting.trace(i"StagingTransformer.transform $tree at $level", show = true) { def mapOverTree(lastEntered: List[Symbol]) = try super.transform(tree) finally diff --git a/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala b/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala new file mode 100644 index 000000000000..12ac4b6240c0 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala @@ -0,0 +1,64 @@ +package dotty.tools.dotc +package transform + +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.{tpd, untpd} +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.Phases +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.util.{SourceFile, SourcePosition} + +/** Ycheck inlined positions */ +class YCheckPositions extends Phases.Phase { + import tpd._ + + def phaseName: String = "inlinedPositions" + + override def run(implicit ctx: Context): Unit = () // YCheck only + + override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { + tree match { + case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass => + new TreeTraverser { + private[this] var sources: List[SourceFile] = ctx.source :: Nil + def traverse(tree: tpd.Tree)(implicit ctx: Context): Unit = { + + // Check current context is correct + assert(ctx.source == sources.head) + if (!tree.isEmpty && !tree.isInstanceOf[untpd.TypedSplice] && ctx.typerState.isGlobalCommittable) { + if (!tree.isType) { // TODO also check types, currently we do not add Inlined(EmptyTree, _, _) for types. We should. + val currentSource = sources.head + assert(tree.source == currentSource, i"wrong source set for $tree # ${tree.uniqueId} of ${tree.getClass}, set to ${tree.source} but context had $currentSource") + } + } + + // Recursivlely check children while keeping track of current source + tree match { + case Inlined(EmptyTree, bindings, expansion) => + assert(bindings.isEmpty) + val old = sources + sources = old.tail + traverse(expansion)(inlineContext(EmptyTree)) + sources = old + case Inlined(call, bindings, expansion) => + bindings.foreach(traverse(_)) + sources = call.symbol.topLevelClass.source :: sources + if (!isMacro(call)) // FIXME macro implementations can drop Inlined nodes. We should reinsert them after macro expansion based on the positions of the trees + traverse(expansion)(inlineContext(call)) + sources = sources.tail + case _ => traverseChildren(tree) + } + } + }.traverse(tree) + case _ => + } + } + + private def isMacro(call: Tree)(implicit ctx: Context) = { + if (ctx.phase <= ctx.typerPhase.next) call.symbol.is(Macro) + else (call.symbol.unforcedDecls.exists(_.is(Macro)) || call.symbol.unforcedDecls.toList.exists(_.is(Macro))) + } + +} diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 8bb3e70207ff..15902b90ad20 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1587,7 +1587,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)) + tpd.ref(vsymMap(id.symbol)).withSpan(id.span) case tree => tree }) val nrhss = rhss.map(rhsMap(_)) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 3c97d184e4d1..772668f897e1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -372,12 +372,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { * from its `originalOwner`, and, if it comes from outside the inlined method * itself, it has to be marked as an inlined argument. */ - def integrate(tree: Tree, originalOwner: Symbol)(implicit ctx: Context): Tree = { - val result = tree.changeOwner(originalOwner, ctx.owner) - if (!originalOwner.isContainedIn(inlinedMethod)) - Inlined(EmptyTree, Nil, result).withSpan(tree.span) - else result - } + def integrate(tree: Tree, originalOwner: Symbol)(implicit ctx: Context): Tree = + tree.changeOwner(originalOwner, ctx.owner) def tryConstValue: Tree = ctx.typeComparer.constValue(callTypeArgs.head.tpe) match { @@ -418,6 +414,9 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope + def inlinedFromOutside(tree: Tree)(span: Span): Tree = + Inlined(EmptyTree, Nil, tree)(ctx.withSource(inlinedMethod.topLevelClass.source)).withSpan(span) + // A tree type map to prepare the inlined body for typechecked. // The translation maps references to `this` and parameters to // corresponding arguments or proxies on the type and term level. It also changes @@ -438,7 +437,9 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { tree.tpe match { case thistpe: ThisType => thisProxy.get(thistpe.cls) match { - case Some(t) => ref(t).withSpan(tree.span) + case Some(t) => + val thisRef = ref(t).withSpan(call.span) + inlinedFromOutside(thisRef)(tree.span) case None => tree } case _ => tree @@ -449,12 +450,11 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { if (tree.name == nme.WILDCARD) tree.span // From type match else if (tree.symbol.isTypeParam && tree.symbol.owner.isClass) tree.span // TODO is this the correct span? else paramSpan(tree.name) + val inlinedCtx = ctx.withSource(inlinedMethod.topLevelClass.source) paramProxy.get(tree.tpe) match { case Some(t) if tree.isTerm && t.isSingleton => - t.dealias match { - case tp: ConstantType => Inlined(EmptyTree, Nil, singleton(tp).withSpan(argSpan)).withSpan(tree.span) - case tp => singleton(tp).withSpan(argSpan) - } + val inlinedSingleton = singleton(t).withSpan(argSpan) + inlinedFromOutside(inlinedSingleton)(tree.span) case Some(t) if tree.isType => TypeTree(t).withSpan(argSpan) case _ => tree @@ -467,7 +467,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { // Apply inliner to `rhsToInline`, split off any implicit bindings from result, and // make them part of `bindingsBuf`. The expansion is then the tree that remains. - val expansion = inliner.transform(rhsToInline).withSpan(call.span) + val expansion = inliner.transform(rhsToInline) def issueError() = callValueArgss match { case (msgArg :: rest) :: Nil => @@ -1159,8 +1159,17 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { assert(level == 0) val inlinedFrom = enclosingInlineds.last val evaluatedSplice = Splicer.splice(body, inlinedFrom.sourcePos, MacroClassLoader.fromContext)(ctx.withSource(inlinedFrom.source)) + + val inlinedNormailizer = new TreeMap { + override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { + case Inlined(EmptyTree, Nil, expr) if enclosingInlineds.isEmpty => transform(expr) + case _ => super.transform(tree) + } + } + val normalizedSplice = inlinedNormailizer.transform(evaluatedSplice) + if (ctx.reporter.hasErrors) EmptyTree - else evaluatedSplice.withSpan(span) + else normalizedSplice.withSpan(span) } } diff --git a/tests/run/i4803/App_2.scala b/tests/run/i4803/App_2.scala index ed7f5caaecae..e99832c65b43 100644 --- a/tests/run/i4803/App_2.scala +++ b/tests/run/i4803/App_2.scala @@ -12,9 +12,9 @@ object Test { println(n.power(5)) val n2 = new Num2(1.5) - println(n.power(0)) - println(n.power(1)) - println(n.power(2)) - println(n.power(5)) + println(n2.power(0)) + println(n2.power(1)) + println(n2.power(2)) + println(n2.power(5)) } }