diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index 4cbd228c02a2..059b0fcc1b7e 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -3,12 +3,14 @@ package dotc import util.SourceFile import ast.{tpd, untpd} -import tpd.{ Tree, TreeTraverser } +import dotty.tools.dotc.ast.Trees +import tpd.{Tree, TreeTraverser} import typer.PrepareInlineable.InlineAccessors import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.SymDenotations.ClassDenotation import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.transform.SymUtils._ +import dotty.tools.dotc.typer.Inliner class CompilationUnit(val source: SourceFile) { @@ -23,6 +25,11 @@ class CompilationUnit(val source: SourceFile) { /** Pickled TASTY binaries, indexed by class. */ var pickled: Map[ClassSymbol, Array[Byte]] = Map() + /** Will be reset to `true` if `tpdTree` contains a call to an inline method. The information + * is used in phase InlineCalls in order to avoid traversing an inline-less tree. + */ + var containsInlineCalls: Boolean = false + /** Will be reset to `true` if `untpdTree` contains `Quote` trees. The information * is used in phase ReifyQuotes in order to avoid traversing a quote-less tree. */ @@ -46,6 +53,7 @@ object CompilationUnit { if (forceTrees) { val force = new Force force.traverse(unit1.tpdTree) + unit1.containsInlineCalls = force.containsInline unit1.containsQuotesOrSplices = force.containsQuotes } unit1 @@ -53,10 +61,13 @@ object CompilationUnit { /** Force the tree to be loaded */ private class Force extends TreeTraverser { + var containsInline = false var containsQuotes = false def traverse(tree: Tree)(implicit ctx: Context): Unit = { if (tree.symbol.isQuote) containsQuotes = true + if (tpd.isInlineCall(tree)) + containsInline = true traverseChildren(tree) } } diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 17bf78e54a50..8df1fd154c6d 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -44,6 +44,7 @@ class Compiler { /** Phases dealing with TASTY tree pickling and unpickling */ protected def picklerPhases: List[List[Phase]] = List(new Pickler) :: // Generate TASTY info + List(new InlineCalls) :: // β-reduce inline calls List(new ReifyQuotes) :: // Turn quoted trees into explicit run-time data structures Nil diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 4f59b51a9a53..0b1bf71a18db 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -5,7 +5,7 @@ package ast import core._ import Flags._, Trees._, Types._, Contexts._ import Names._, StdNames._, NameOps._, Symbols._ -import typer.ConstFold +import typer.{ConstFold, Inliner} import reporting.trace import scala.annotation.tailrec @@ -759,6 +759,14 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => false } + /** Is this call a call to a method that is marked as Inline */ + def isInlineCall(arg: Tree)(implicit ctx: Context): Boolean = arg match { + case _: RefTree | _: GenericApply[_] => + !arg.tpe.widenDealias.isInstanceOf[MethodicType] && Inliner.isInlineable(arg) + case _ => + false + } + /** Structural tree comparison (since == on trees is reference equality). * For the moment, only Ident, Select, Literal, Apply and TypeApply are supported */ diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 453516880ab9..a4c6c25c6dd8 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -583,9 +583,8 @@ object Trees { /** A tree representing inlined code. * - * @param call Info about the original call that was inlined - * Until PostTyper, this is the full call, afterwards only - * a reference to the toplevel class from which the call was inlined. + * @param call Info about the original call that was inlined. + * Only a reference to the toplevel class from which the call was inlined. * @param bindings Bindings for proxies to be used in the inlined code * @param expansion The inlined tree, minus bindings. * diff --git a/compiler/src/dotty/tools/dotc/decompiler/TASTYDecompiler.scala b/compiler/src/dotty/tools/dotc/decompiler/TASTYDecompiler.scala index 4601ff9d1a12..eebffcd0bf6e 100644 --- a/compiler/src/dotty/tools/dotc/decompiler/TASTYDecompiler.scala +++ b/compiler/src/dotty/tools/dotc/decompiler/TASTYDecompiler.scala @@ -2,6 +2,7 @@ package dotty.tools.dotc.decompiler import dotty.tools.dotc.fromtasty._ import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.transform.InlineCalls /** Compiler from tasty to user readable high text representation * of the compiled scala code. @@ -14,7 +15,10 @@ class TASTYDecompiler extends TASTYCompiler { List(new ReadTastyTreesFromClasses) :: // Load classes from tasty Nil - override protected def picklerPhases: List[List[Phase]] = Nil + override protected def picklerPhases: List[List[Phase]] = + List(new InlineCalls) :: // TODO should we really inline for the decompiler? + Nil + override protected def transformPhases: List[List[Phase]] = Nil override protected def backendPhases: List[List[Phase]] = diff --git a/compiler/src/dotty/tools/dotc/transform/InlineCalls.scala b/compiler/src/dotty/tools/dotc/transform/InlineCalls.scala new file mode 100644 index 000000000000..837740bd928f --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/InlineCalls.scala @@ -0,0 +1,45 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.core.Types.MethodicType +import dotty.tools.dotc.transform.SymUtils._ +import dotty.tools.dotc.typer.{ConstFold, Inliner} + +/** β-reduce all calls to inline methods and perform constant folding */ +class InlineCalls extends MacroTransform { thisPhase => + import tpd._ + + override def phaseName: String = InlineCalls.name + + override def run(implicit ctx: Context): Unit = + if (ctx.compilationUnit.containsInlineCalls && !ctx.settings.YnoInline.value) super.run + + override def transformPhase(implicit ctx: Context): Phase = thisPhase.next + + protected def newTransformer(implicit ctx: Context): Transformer = + new InlineCallsTransformer + + class InlineCallsTransformer extends Transformer { + override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case tree if isInlineCall(tree) && !ctx.reporter.hasErrors => + val tree2 = super.transform(tree) // transform arguments before inlining (inline arguments and constant fold arguments) + transform(Inliner.inlineCall(tree2, tree.tpe.widen)) + case _: MemberDef => + val newTree = super.transform(tree) + newTree.symbol.defTree = newTree // update tree set in PostTyper or set for inlined members + newTree + case _ => + if (tree.symbol.isQuote || tree.symbol.isSplice) + ctx.compilationUnit.containsQuotesOrSplices = true + ConstFold(super.transform(tree)) + } + } + +} + +object InlineCalls { + final val name = "inlineCalls" +} diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 5cd8795b4d04..8add8b11c4e1 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -40,8 +40,7 @@ object PostTyper { * * (10) Adds Child annotations to all sealed classes * - * (11) Minimizes `call` fields of `Inlined` nodes to just point to the toplevel - * class from which code was inlined. + * (11) Replace RHS of `erased` (but not `inline`) members by `(???: rhs.type)` * * The reason for making this a macro transform is that some functions (in particular * super and protected accessors and instantiation checks) are naturally top-down and @@ -178,23 +177,22 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase } } - private object dropInlines extends TreeMap { - override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { - case Inlined(call, _, _) => - cpy.Inlined(tree)(call, Nil, Typed(ref(defn.Predef_undefined), TypeTree(tree.tpe))) - case _ => super.transform(tree) - } + private def handleInlineCall(sym: Symbol)(implicit ctx: Context): Unit = { + if (sym.is(Inline)) + ctx.compilationUnit.containsInlineCalls = true } override def transform(tree: Tree)(implicit ctx: Context): Tree = try tree match { case tree: Ident if !tree.isType => + handleInlineCall(tree.symbol) handleMeta(tree.symbol) tree.tpe match { case tpe: ThisType => This(tpe.cls).withPos(tree.pos) case _ => tree } case tree @ Select(qual, name) => + handleInlineCall(tree.symbol) handleMeta(tree.symbol) if (name.isTypeName) { Checking.checkRealizable(qual.tpe, qual.pos.focus) @@ -203,6 +201,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase else transformSelect(tree, Nil) case tree: Apply => + handleInlineCall(tree.symbol) val methType = tree.fun.tpe.widen val app = if (methType.isErasedMethod) @@ -210,7 +209,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase tree.fun, tree.args.map(arg => if (methType.isImplicitMethod && arg.pos.isSynthetic) ref(defn.Predef_undefined) - else dropInlines.transform(arg))) + else arg)) else tree methPart(app) match { @@ -223,6 +222,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase super.transform(app) } case tree: TypeApply => + handleInlineCall(tree.symbol) val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) if (fn.symbol != defn.ChildAnnot.primaryConstructor) { // Make an exception for ChildAnnot, which should really have AnyKind bounds @@ -236,19 +236,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase case _ => super.transform(tree1) } - case Inlined(call, bindings, expansion) if !call.isEmpty => - // Leave only a call trace consisting of - // - a reference to the top-level class from which the call was inlined, - // - the call's position - // in the call field of an Inlined node. - // The trace has enough info to completely reconstruct positions. - // The minimization is done for two reasons: - // 1. To save space (calls might contain large inline arguments, which would otherwise - // be duplicated - // 2. To enable correct pickling (calls can share symbols with the inlined code, which - // would trigger an assertion when pickling). - val callTrace = Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos) - cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)(inlineContext(call))) case tree: Template => withNoCheckNews(tree.parents.flatMap(newPart)) { val templ1 = paramFwd.forwardParamAccessors(tree) @@ -332,9 +319,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase } /** Transforms the rhs tree into a its default tree if it is in an `erased` val/def. - * Performed to shrink the tree that is known to be erased later. - */ + * Performed to shrink the tree that is known to be erased later. + */ private def normalizeErasedRhs(rhs: Tree, sym: Symbol)(implicit ctx: Context) = - if (sym.isEffectivelyErased) dropInlines.transform(rhs) else rhs + if (!sym.isEffectivelyErased || sym.isInlineMethod || !rhs.tpe.exists) rhs + else Typed(ref(defn.Predef_undefined), TypeTree(rhs.tpe)) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index c3f379ac3a78..58849305de45 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -301,7 +301,10 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { private def registerType(tpe: Type): Unit = tpe match { case tpe: ThisType if !canElideThis(tpe) && !thisProxy.contains(tpe.cls) => val proxyName = s"${tpe.cls.name}_this".toTermName - val proxyType = tpe.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner) + val proxyType = inlineCallPrefix.tpe.tryNormalize match { + case typeMatchResult if typeMatchResult.exists => typeMatchResult + case _ => tpe.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner).widenIfUnstable + } thisProxy(tpe.cls) = newSym(proxyName, InlineProxy, proxyType).termRef if (!tpe.cls.isStaticOwner) registerType(inlinedMethod.owner.thisType) // make sure we have a base from which to outer-select @@ -452,9 +455,18 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { if (inlinedMethod == defn.Typelevel_error) issueError() + // Leave only a call trace consisting of + // - a reference to the top-level class from which the call was inlined, + // - the call's position + // in the call field of an Inlined node. + // The trace has enough info to completely reconstruct positions. + // The minimization is done for the following reason: + // * To save space (calls might contain large inline arguments, which would otherwise be duplicated + val callTrace = Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos) + // Take care that only argument bindings go into `bindings`, since positions are // different for bindings from arguments and bindings from body. - tpd.Inlined(call, finalBindings, finalExpansion) + tpd.Inlined(callTrace, finalBindings, finalExpansion) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 861f9733ff62..7e9f79f69564 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2441,13 +2441,6 @@ class Typer extends Namer checkEqualityEvidence(tree, pt) tree } - else if (Inliner.isInlineable(tree) && - !ctx.settings.YnoInline.value && - !ctx.isAfterTyper && - !ctx.reporter.hasErrors && - tree.tpe <:< pt) { - readaptSimplified(Inliner.inlineCall(tree, pt)) - } else if (tree.tpe <:< pt) { if (pt.hasAnnotation(defn.InlineParamAnnot)) checkInlineConformant(tree, isFinal = false, "argument to inline parameter") diff --git a/compiler/test/dotc/pos-from-tasty.blacklist b/compiler/test/dotc/pos-from-tasty.blacklist index a48abcd017a0..ace498a34d3f 100644 --- a/compiler/test/dotc/pos-from-tasty.blacklist +++ b/compiler/test/dotc/pos-from-tasty.blacklist @@ -17,4 +17,7 @@ repeatedArgs213.scala default-super.scala # Need to implement printing of match types -matchtype.scala \ No newline at end of file +matchtype.scala + +# Fails on CI (not locally) +inline-named-typeargs.scala diff --git a/tests/pos/depfuntype.scala b/tests/pos/depfuntype.scala index 308b7aecc58d..b132b843c240 100644 --- a/tests/pos/depfuntype.scala +++ b/tests/pos/depfuntype.scala @@ -16,10 +16,8 @@ object Test { val z = depfun3(d) val z1: d.M = z - // Reproduced here because the one from DottyPredef is lacking a Tasty tree and - // therefore can't be inlined when testing non-bootstrapped. - // But inlining `implicitly` is vital to make the definition of `ifun` below work. - inline final def implicitly[T](implicit ev: T): T = ev + // Reproduced here because the one from DottyPredef is lacking a parameter dependency of the return type `ev.type` + inline final def implicitly[T](implicit ev: T): ev.type = ev type IDF = implicit (x: C) => x.M diff --git a/tests/run/tuples1a.scala b/tests/run/tuples1a.scala new file mode 100644 index 000000000000..ee1e5b9aa360 --- /dev/null +++ b/tests/run/tuples1a.scala @@ -0,0 +1,9 @@ +object Test extends App { + val t7 = '5' *: 4 *: "C" *: () + + val t7a = t7.tail + val t7b = t7a.tail + val t7c: Unit = (t7.tail: (Int, String)).tail + val t7d: Unit = (t7.tail: Int *: String *: Unit).tail + val t7e: Unit = t7.tail.tail +}