From 56f01cdf9081b0b221a01e5ae1a9f10e4aa62da9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 29 Aug 2016 15:55:44 +0200 Subject: [PATCH 01/77] Namer refactoing - DRY - Refactor out special path operations --- src/dotty/tools/dotc/typer/Namer.scala | 61 +++++++++++++------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index cfd49fd8704d..6eca9be41740 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -403,37 +403,28 @@ class Namer { typer: Typer => /** Create top-level symbols for all statements in the expansion of this statement and * enter them into symbol table */ - def indexExpanded(stat: Tree)(implicit ctx: Context): Context = expanded(stat) match { - case pcl: PackageDef => - val pkg = createPackageSymbol(pcl.pid) - index(pcl.stats)(ctx.fresh.setOwner(pkg.moduleClass)) - invalidateCompanions(pkg, Trees.flatten(pcl.stats map expanded)) - setDocstring(pkg, stat) - ctx - case imp: Import => - importContext(createSymbol(imp), imp.selectors) - case mdef: DefTree => - val sym = enterSymbol(createSymbol(mdef)) - setDocstring(sym, stat) - - // add java enum constants - mdef match { - case vdef: ValDef if (isEnumConstant(vdef)) => - val enumClass = sym.owner.linkedClass - if (!(enumClass is Flags.Sealed)) enumClass.setFlag(Flags.AbstractSealed) - enumClass.addAnnotation(Annotation.makeChild(sym)) - case _ => - } - - ctx - case stats: Thicket => - for (tree <- stats.toList) { - val sym = enterSymbol(createSymbol(tree)) + def indexExpanded(stat: Tree)(implicit ctx: Context): Context = { + def recur(stat: Tree): Context = stat match { + case pcl: PackageDef => + val pkg = createPackageSymbol(pcl.pid) + index(pcl.stats)(ctx.fresh.setOwner(pkg.moduleClass)) + invalidateCompanions(pkg, Trees.flatten(pcl.stats map expanded)) + setDocstring(pkg, stat) + ctx + case imp: Import => + importContext(createSymbol(imp), imp.selectors) + case mdef: DefTree => + val sym = enterSymbol(createSymbol(mdef)) setDocstring(sym, stat) - } - ctx - case _ => - ctx + addEnumConstants(mdef, sym) + ctx + case stats: Thicket => + stats.toList.foreach(recur) + ctx + case _ => + ctx + } + recur(expanded(stat)) } /** Determines whether this field holds an enum constant. @@ -454,6 +445,16 @@ class Namer { typer: Typer => vd.mods.is(allOf(Enum, Stable, JavaStatic, JavaDefined)) // && ownerHasEnumFlag } + /** Add java enum constants */ + def addEnumConstants(mdef: DefTree, sym: Symbol)(implicit ctx: Context): Unit = mdef match { + case vdef: ValDef if (isEnumConstant(vdef)) => + val enumClass = sym.owner.linkedClass + if (!(enumClass is Flags.Sealed)) enumClass.setFlag(Flags.AbstractSealed) + enumClass.addAnnotation(Annotation.makeChild(sym)) + case _ => + } + + def setDocstring(sym: Symbol, tree: Tree)(implicit ctx: Context) = tree match { case t: MemberDef => ctx.docbase.addDocstring(sym, t.rawComment) case _ => () From c87a9dd1f34cd7afe3fba0edfa1463019eaa78bd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 1 Sep 2016 11:59:13 +0200 Subject: [PATCH 02/77] First version of inline scheme To be done: outer accessors To be done: error positions --- src/dotty/annotation/inline.scala | 8 + src/dotty/tools/dotc/config/Printers.scala | 1 + .../tools/dotc/config/ScalaSettings.scala | 1 + src/dotty/tools/dotc/core/Contexts.scala | 4 + src/dotty/tools/dotc/core/Definitions.scala | 2 + src/dotty/tools/dotc/core/NameOps.scala | 2 +- .../tools/dotc/core/SymDenotations.scala | 17 +- .../tools/dotc/transform/PostTyper.scala | 4 + src/dotty/tools/dotc/typer/Inliner.scala | 200 ++++++++++++++++++ src/dotty/tools/dotc/typer/Namer.scala | 7 + src/dotty/tools/dotc/typer/ReTyper.scala | 4 +- src/dotty/tools/dotc/typer/Typer.scala | 18 +- src/dotty/tools/dotc/util/SourceFile.scala | 1 + tests/neg/inlineAccess.scala | 15 ++ tests/run/inlineTest.scala | 53 +++++ 15 files changed, 325 insertions(+), 12 deletions(-) create mode 100644 src/dotty/annotation/inline.scala create mode 100644 src/dotty/tools/dotc/typer/Inliner.scala create mode 100644 tests/neg/inlineAccess.scala create mode 100644 tests/run/inlineTest.scala diff --git a/src/dotty/annotation/inline.scala b/src/dotty/annotation/inline.scala new file mode 100644 index 000000000000..ff4ca08b6d05 --- /dev/null +++ b/src/dotty/annotation/inline.scala @@ -0,0 +1,8 @@ +package dotty.annotation + +import scala.annotation.Annotation + +/** Unlike scala.inline, this one forces inlining in the Typer + * Should be replaced by keyword when we switch over completely to dotty + */ +class inline extends Annotation \ No newline at end of file diff --git a/src/dotty/tools/dotc/config/Printers.scala b/src/dotty/tools/dotc/config/Printers.scala index 322bc82d905e..4168cf5a62b3 100644 --- a/src/dotty/tools/dotc/config/Printers.scala +++ b/src/dotty/tools/dotc/config/Printers.scala @@ -30,4 +30,5 @@ object Printers { val completions: Printer = noPrinter val cyclicErrors: Printer = noPrinter val pickling: Printer = noPrinter + val inlining: Printer = new Printer } diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index c090a551563f..d05ae08038b7 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -67,6 +67,7 @@ class ScalaSettings extends Settings.SettingGroup { val genPhaseGraph = StringSetting("-Xgenerate-phase-graph", "file", "Generate the phase graphs (outputs .dot files) to fileX.dot.", "") val XlogImplicits = BooleanSetting("-Xlog-implicits", "Show more detail on why some implicits are not applicable.") val XminImplicitSearchDepth = IntSetting("-Xmin-implicit-search-depth", "Set number of levels of implicit searches undertaken before checking for divergence.", 5) + val xmaxInlines = IntSetting("-Xmax-inlines", "Maximal number of successive inlines", 70) val logImplicitConv = BooleanSetting("-Xlog-implicit-conversions", "Print a message whenever an implicit conversion is inserted.") val logReflectiveCalls = BooleanSetting("-Xlog-reflective-calls", "Print a message when a reflective method call is generated") val logFreeTerms = BooleanSetting("-Xlog-free-terms", "Print a message when reification creates a free term.") diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index cd76fe88bb45..d8beb4b5c405 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -667,6 +667,10 @@ object Contexts { */ private[dotty] var unsafeNonvariant: RunId = NoRunId + // Typer state + + private[dotty] var inlineCount = 0 + // Phases state private[core] var phasesPlan: List[List[Phase]] = _ diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index cb83fda0414a..c9a3ef4dee62 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -466,6 +466,8 @@ class Definitions { def DeprecatedAnnot(implicit ctx: Context) = DeprecatedAnnotType.symbol.asClass lazy val ImplicitNotFoundAnnotType = ctx.requiredClassRef("scala.annotation.implicitNotFound") def ImplicitNotFoundAnnot(implicit ctx: Context) = ImplicitNotFoundAnnotType.symbol.asClass + lazy val InlineAnnotType = ctx.requiredClassRef("dotty.annotation.inline") + def InlineAnnot(implicit ctx: Context) = InlineAnnotType.symbol.asClass lazy val InvariantBetweenAnnotType = ctx.requiredClassRef("dotty.annotation.internal.InvariantBetween") def InvariantBetweenAnnot(implicit ctx: Context) = InvariantBetweenAnnotType.symbol.asClass lazy val MigrationAnnotType = ctx.requiredClassRef("scala.annotation.migration") diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index f5e0eb8cd46e..ea255e5b3de6 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -166,7 +166,7 @@ object NameOps { // Hack to make super accessors from traits work. They would otherwise fail because of #765 // TODO: drop this once we have more robust name handling - if (name.slice(idx - FalseSuperLength, idx) == FalseSuper) + if (idx > FalseSuperLength && name.slice(idx - FalseSuperLength, idx) == FalseSuper) idx -= FalseSuper.length if (idx < 0) name else (name drop (idx + nme.EXPAND_SEPARATOR.length)).asInstanceOf[N] diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index ab45550a4771..160d3bc3037c 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -670,9 +670,9 @@ object SymDenotations { val cls = owner.enclosingSubClass if (!cls.exists) fail( - i""" Access to protected $this not permitted because - | enclosing ${ctx.owner.enclosingClass.showLocated} is not a subclass of - | ${owner.showLocated} where target is defined""") + i""" + | Access to protected $this not permitted because enclosing ${ctx.owner.enclosingClass.showLocated} + | is not a subclass of ${owner.showLocated} where target is defined""") else if ( !( isType // allow accesses to types from arbitrary subclasses fixes #4737 || pre.baseTypeRef(cls).exists // ??? why not use derivesFrom ??? @@ -680,9 +680,9 @@ object SymDenotations { || (owner is ModuleClass) // don't perform this check for static members )) fail( - i""" Access to protected ${symbol.show} not permitted because - | prefix type ${pre.widen.show} does not conform to - | ${cls.showLocated} where the access takes place""") + i""" + | Access to protected ${symbol.show} not permitted because prefix type ${pre.widen.show} + | does not conform to ${cls.showLocated} where the access takes place""") else true } @@ -744,6 +744,11 @@ object SymDenotations { // def isOverridable: Boolean = !!! need to enforce that classes cannot be redefined def isSkolem: Boolean = name == nme.SKOLEM + def isInlineMethod(implicit ctx: Context): Boolean = + is(Method, butNot = Accessor) && + !isCompleting && // don't force method type; recursive inlines are ignored anyway. + hasAnnotation(defn.InlineAnnot) + // ------ access to related symbols --------------------------------- /* Modules and module classes are represented as follows: diff --git a/src/dotty/tools/dotc/transform/PostTyper.scala b/src/dotty/tools/dotc/transform/PostTyper.scala index 6af22503573a..51851a5899c8 100644 --- a/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/src/dotty/tools/dotc/transform/PostTyper.scala @@ -207,6 +207,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran } case tree: Select => transformSelect(paramFwd.adaptRef(fixSignature(tree)), Nil) + case tree: Super => + if (ctx.owner.enclosingMethod.isInlineMethod) + ctx.error(em"super not allowed in inline ${ctx.owner}", tree.pos) + super.transform(tree) case tree: TypeApply => val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType]) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala new file mode 100644 index 000000000000..852689a75822 --- /dev/null +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -0,0 +1,200 @@ +package dotty.tools +package dotc +package typer + +import dotty.tools.dotc.ast.Trees.NamedArg +import dotty.tools.dotc.ast.{Trees, untpd, tpd, TreeTypeMap} +import Trees._ +import core._ +import Flags._ +import Symbols._ +import Types._ +import Decorators._ +import StdNames.nme +import Contexts.Context +import Names.Name +import SymDenotations.SymDenotation +import Annotations.Annotation +import transform.ExplicitOuter +import config.Printers.inlining +import ErrorReporting.errorTree +import util.Attachment +import collection.mutable + +object Inliner { + import tpd._ + + private class InlinedBody(tree: => Tree) { + lazy val body = tree + } + + private val InlinedBody = new Attachment.Key[InlinedBody] + + def attachBody(inlineAnnot: Annotation, tree: => Tree)(implicit ctx: Context): Unit = + inlineAnnot.tree.putAttachment(InlinedBody, new InlinedBody(tree)) + + def inlinedBody(sym: SymDenotation)(implicit ctx: Context): Tree = + sym.getAnnotation(defn.InlineAnnot).get.tree + .attachment(InlinedBody).body + + private class Typer extends ReTyper { + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { + val acc = tree.symbol + super.typedSelect(tree, pt) match { + case res @ Select(qual, name) => + if (name.endsWith(nme.OUTER)) { + val outerAcc = tree.symbol + println(i"selecting $tree / ${acc} / ${qual.tpe.normalizedPrefix}") + res.withType(qual.tpe.widen.normalizedPrefix) + } + else { + ensureAccessible(res.tpe, qual.isInstanceOf[Super], tree.pos) + res + } + case res => res + } + } + } + + def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { + if (ctx.inlineCount < ctx.settings.xmaxInlines.value) { + ctx.inlineCount += 1 + val rhs = inlinedBody(tree.symbol) + val inlined = new Inliner(tree, rhs).inlined + try new Typer().typedUnadapted(inlined, pt) + finally ctx.inlineCount -= 1 + } else errorTree(tree, + i"""Maximal number of successive inlines (${ctx.settings.xmaxInlines.value}) exceeded, + | Maybe this is caused by a recursive inline method? + | You can use -Xmax:inlines to change the limit.""") + } +} + +class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { + import tpd._ + + private val meth = call.symbol + + private def decomposeCall(tree: Tree): (Tree, List[Tree], List[List[Tree]]) = tree match { + case Apply(fn, args) => + val (meth, targs, argss) = decomposeCall(fn) + (meth, targs, argss :+ args) + case TypeApply(fn, targs) => + val (meth, Nil, Nil) = decomposeCall(fn) + (meth, targs, Nil) + case _ => + (tree, Nil, Nil) + } + + private val (methPart, targs, argss) = decomposeCall(call) + + private lazy val prefix = methPart match { + case Select(qual, _) => qual + case _ => tpd.This(ctx.owner.enclosingClass.asClass) + } + + private val replacement = new mutable.HashMap[Type, NamedType] + + private val paramBindings = paramBindingsOf(meth.info, targs, argss) + + private def paramBindingsOf(tp: Type, targs: List[Tree], argss: List[List[Tree]]): List[MemberDef] = tp match { + case tp: PolyType => + val bindings = + (tp.paramNames, targs).zipped.map { (name, arg) => + val tparam = newSym(name, EmptyFlags, TypeAlias(arg.tpe.stripTypeVar)).asType + TypeDef(tparam) + } + bindings ::: paramBindingsOf(tp.resultType, Nil, argss) + case tp: MethodType => + val bindings = + (tp.paramNames, tp.paramTypes, argss.head).zipped.map { (name, paramtp, arg) => + def isByName = paramtp.dealias.isInstanceOf[ExprType] + val (paramFlags, paramType) = + if (isByName) (Method, ExprType(arg.tpe)) else (EmptyFlags, arg.tpe) + val vparam = newSym(name, paramFlags, paramType).asTerm + if (isByName) DefDef(vparam, arg) else ValDef(vparam, arg) + } + bindings ::: paramBindingsOf(tp.resultType, targs, argss.tail) + case _ => + assert(targs.isEmpty) + assert(argss.isEmpty) + Nil + } + + private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = + ctx.newSymbol(ctx.owner, name, flags, info, coord = call.pos) + + private def registerType(tpe: Type): Unit = + if (!replacement.contains(tpe)) tpe match { + case tpe: ThisType => + if (!ctx.owner.isContainedIn(tpe.cls) && !tpe.cls.is(Package)) + if (tpe.cls.isStaticOwner) + replacement(tpe) = tpe.cls.sourceModule.termRef + else { + def outerDistance(cls: Symbol): Int = { + assert(cls.exists, i"not encl: ${meth.owner.enclosingClass} ${tpe.cls}") + if (tpe.cls eq cls) 0 + else outerDistance(cls.owner.enclosingClass) + 1 + } + val n = outerDistance(meth.owner) + replacement(tpe) = newSym(nme.SELF ++ n.toString, EmptyFlags, tpe.widen).termRef + } + case tpe: NamedType if tpe.symbol.is(Param) && tpe.symbol.owner == meth => + val Some(binding) = paramBindings.find(_.name == tpe.name) + replacement(tpe) = + if (tpe.name.isTypeName) binding.symbol.typeRef else binding.symbol.termRef + case _ => + } + + private def registerLeaf(tree: Tree): Unit = tree match { + case _: This | _: Ident => registerType(tree.tpe) + case _ => + } + + private def outerLevel(sym: Symbol) = sym.name.drop(nme.SELF.length).toString.toInt + + val inlined = { + rhs.foreachSubTree(registerLeaf) + + val accessedSelfSyms = + (for ((tp: ThisType, ref) <- replacement) yield ref.symbol.asTerm).toSeq.sortBy(outerLevel) + + val outerBindings = new mutable.ListBuffer[MemberDef] + for (selfSym <- accessedSelfSyms) { + val rhs = + if (outerBindings.isEmpty) prefix + else { + val lastSelf = outerBindings.last.symbol + val outerDelta = outerLevel(selfSym) - outerLevel(lastSelf) + def outerSelect(ref: Tree, dummy: Int): Tree = ??? + //ref.select(ExplicitOuter.outerAccessorTBD(ref.tpe.widen.classSymbol.asClass)) + (ref(lastSelf) /: (0 until outerDelta))(outerSelect) + } + outerBindings += ValDef(selfSym, rhs.ensureConforms(selfSym.info)) + } + outerBindings ++= paramBindings + + val typeMap = new TypeMap { + def apply(t: Type) = t match { + case _: SingletonType => replacement.getOrElse(t, t) + case _ => mapOver(t) + } + } + + def treeMap(tree: Tree) = tree match { + case _: This | _: Ident => + replacement.get(tree.tpe) match { + case Some(t) => ref(t) + case None => tree + } + case _ => tree + } + + val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil) + + val result = inliner(Block(outerBindings.toList, rhs)).withPos(call.pos) + + inlining.println(i"inlining $call\n --> \n$result") + result + } +} diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 6eca9be41740..06e798bdbd04 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -566,10 +566,17 @@ class Namer { typer: Typer => val cls = typedAheadAnnotation(annotTree) val ann = Annotation.deferred(cls, implicit ctx => typedAnnotation(annotTree)) denot.addAnnotation(ann) + if (cls == defn.InlineAnnot) addInlineInfo(ann, original) } case _ => } + private def addInlineInfo(inlineAnnot: Annotation, original: untpd.Tree) = original match { + case original: untpd.DefDef => + Inliner.attachBody(inlineAnnot, typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs) + case _ => + } + /** Intentionally left without `implicit ctx` parameter. We need * to pick up the context at the point where the completer was created. */ diff --git a/src/dotty/tools/dotc/typer/ReTyper.scala b/src/dotty/tools/dotc/typer/ReTyper.scala index 9750957bf8e6..03b415a6fce3 100644 --- a/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/src/dotty/tools/dotc/typer/ReTyper.scala @@ -10,7 +10,7 @@ import typer.ProtoTypes._ import ast.{tpd, untpd} import ast.Trees._ import scala.util.control.NonFatal -import config.Printers +import config.Printers.typr /** A version of Typer that keeps all symbols defined and referenced in a * previously typed tree. @@ -87,7 +87,7 @@ class ReTyper extends Typer { try super.typedUnadapted(tree, pt) catch { case NonFatal(ex) => - println(i"exception while typing $tree of class ${tree.getClass} # ${tree.uniqueId}") + typr.println(i"exception while typing $tree of class ${tree.getClass} # ${tree.uniqueId}") throw ex } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 562af75f6e2a..007eaa468e69 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -885,12 +885,15 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit (EmptyTree, WildcardType) } else if (owner != cx.outer.owner && owner.isRealMethod) { - if (owner.isCompleted) { + if (owner.isInlineMethod) + (EmptyTree, errorType(em"no explicit return allowed from inline $owner", tree.pos)) + else if (!owner.isCompleted) + (EmptyTree, errorType(em"$owner has return statement; needs result type", tree.pos)) + else { val from = Ident(TermRef(NoPrefix, owner.asTerm)) val proto = returnProto(owner, cx.scope) (from, proto) } - else (EmptyTree, errorType(em"$owner has return statement; needs result type", tree.pos)) } else enclMethInfo(cx.outer) } @@ -1154,6 +1157,13 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit rhsCtx.gadt.setBounds(tdef.symbol, TypeAlias(tparam.typeRef))) } val rhs1 = typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx) + + // Overwrite inline body to make sure it is not evaluated twice + sym.getAnnotation(defn.InlineAnnot) match { + case Some(ann) => Inliner.attachBody(ann, rhs1) + case _ => + } + if (sym.isAnonymousFunction) { // If we define an anonymous function, make sure the return type does not // refer to parameters. This is necessary because closure types are @@ -1773,7 +1783,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit tree } else if (tree.tpe <:< pt) - if (ctx.typeComparer.GADTused && pt.isValueType) + if (tree.symbol.isInlineMethod && !ctx.owner.ownersIterator.exists(_.isInlineMethod)) + adapt(Inliner.inlineCall(tree, pt), pt) + else if (ctx.typeComparer.GADTused && pt.isValueType) // Insert an explicit cast, so that -Ycheck in later phases succeeds. // I suspect, but am not 100% sure that this might affect inferred types, // if the expected type is a supertype of the GADT bound. It would be good to come diff --git a/src/dotty/tools/dotc/util/SourceFile.scala b/src/dotty/tools/dotc/util/SourceFile.scala index 6b547203e1f9..1d71552c0713 100644 --- a/src/dotty/tools/dotc/util/SourceFile.scala +++ b/src/dotty/tools/dotc/util/SourceFile.scala @@ -139,5 +139,6 @@ case class SourceFile(file: AbstractFile, content: Array[Char]) extends interfac @sharable object NoSource extends SourceFile("", Nil) { override def exists = false + override def atPos(pos: Position): SourcePosition = NoSourcePosition } diff --git a/tests/neg/inlineAccess.scala b/tests/neg/inlineAccess.scala new file mode 100644 index 000000000000..cfb1cc06fd6b --- /dev/null +++ b/tests/neg/inlineAccess.scala @@ -0,0 +1,15 @@ +package p { +class C { + protected def f(): Unit = () + + @dotty.annotation.inline + def inl() = f() // error (when inlined): not accessible +} +} + +object Test { + def main(args: Array[String]) = { + val c = new p.C() + c.inl() + } +} diff --git a/tests/run/inlineTest.scala b/tests/run/inlineTest.scala new file mode 100644 index 000000000000..feaa43fbb37e --- /dev/null +++ b/tests/run/inlineTest.scala @@ -0,0 +1,53 @@ +import collection.mutable + +object Test { + + final val monitored = false + + @dotty.annotation.inline + def f(x: Int): Int = x * x + + val hits = new mutable.HashMap[String, Int] { + override def default(key: String): Int = 0 + } + + def record(fn: String, n: Int = 1) = { + if (monitored) { + val name = if (fn.startsWith("member-")) "member" else fn + hits(name) += n + } + } + + @volatile private var stack: List[String] = Nil + + @dotty.annotation.inline + def track[T](fn: String)(op: => T) = + if (monitored) { + stack = fn :: stack + record(fn) + try op + finally stack = stack.tail + } else op + + class Outer { + def f = "Outer.f" + class Inner { + val msg = " Inner" + @dotty.annotation.inline def g = f + @dotty.annotation.inline def h = f ++ this.msg + } + } + + def main(args: Array[String]): Unit = { + println(f(10)) + println(f(f(10))) + + track("hello") { println("") } + + val o = new Outer + val i = new o.Inner + println(i.g) + //println(i.h) + } + +} From d63a6ba8540fb8ea7d01350ea68ff1ef0b53c5d4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 1 Sep 2016 15:59:52 +0200 Subject: [PATCH 03/77] Make Context#moreProperties strongly typed To do this, factor out Key from Attachment into a new type, Property.Key. --- src/dotty/tools/dotc/ast/Desugar.scala | 4 ++-- src/dotty/tools/dotc/ast/Trees.scala | 6 +++--- src/dotty/tools/dotc/ast/untpd.scala | 10 +++++----- src/dotty/tools/dotc/core/Contexts.scala | 15 ++++++++++----- .../tools/dotc/transform/ExplicitOuter.scala | 4 ++-- src/dotty/tools/dotc/typer/Inliner.scala | 4 ++-- src/dotty/tools/dotc/typer/Namer.scala | 8 ++++---- src/dotty/tools/dotc/util/Attachment.scala | 4 +--- src/dotty/tools/dotc/util/Property.scala | 10 ++++++++++ 9 files changed, 39 insertions(+), 26 deletions(-) create mode 100644 src/dotty/tools/dotc/util/Property.scala diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index edd6da5c90b8..4e27da2cab3c 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -8,7 +8,7 @@ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._ import Decorators._ import language.higherKinds import collection.mutable.ListBuffer -import util.Attachment +import util.Property object desugar { import untpd._ @@ -16,7 +16,7 @@ object desugar { /** Tags a .withFilter call generated by desugaring a for expression. * Such calls can alternatively be rewritten to use filter. */ - val MaybeFilter = new Attachment.Key[Unit] + val MaybeFilter = new Property.Key[Unit] /** Info of a variable in a pattern: The named tree and its type */ private type VarInfo = (NameTree, Tree) diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index bb6fbd5baa38..3b5de93084c6 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -12,7 +12,7 @@ import collection.immutable.IndexedSeq import collection.mutable.ListBuffer import parsing.Tokens.Token import printing.Printer -import util.{Stats, Attachment, DotClass} +import util.{Stats, Attachment, Property, DotClass} import annotation.unchecked.uncheckedVariance import language.implicitConversions import parsing.Scanners.Comment @@ -30,8 +30,8 @@ object Trees { /** The total number of created tree nodes, maintained if Stats.enabled */ @sharable var ntrees = 0 - /** Attachment key for trees with documentation strings attached */ - val DocComment = new Attachment.Key[Comment] + /** Property key for trees with documentation strings attached */ + val DocComment = new Property.Key[Comment] @sharable private var nextId = 0 // for debugging diff --git a/src/dotty/tools/dotc/ast/untpd.scala b/src/dotty/tools/dotc/ast/untpd.scala index 61c3a79a4a16..32e27e9c7e71 100644 --- a/src/dotty/tools/dotc/ast/untpd.scala +++ b/src/dotty/tools/dotc/ast/untpd.scala @@ -6,7 +6,7 @@ import core._ import util.Positions._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._ import Denotations._, SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._ import Decorators._ -import util.Attachment +import util.Property import language.higherKinds import collection.mutable.ListBuffer @@ -161,17 +161,17 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def derivedType(originalSym: Symbol)(implicit ctx: Context): Type } - /** Attachment key containing TypeTrees whose type is computed + /** Property key containing TypeTrees whose type is computed * from the symbol in this type. These type trees have marker trees * TypeRefOfSym or InfoOfSym as their originals. */ - val References = new Attachment.Key[List[Tree]] + val References = new Property.Key[List[Tree]] - /** Attachment key for TypeTrees marked with TypeRefOfSym or InfoOfSym + /** Property key for TypeTrees marked with TypeRefOfSym or InfoOfSym * which contains the symbol of the original tree from which this * TypeTree is derived. */ - val OriginalSymbol = new Attachment.Key[Symbol] + val OriginalSymbol = new Property.Key[Symbol] // ------ Creation methods for untyped only ----------------- diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index d8beb4b5c405..6f9d19c40a9b 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -30,6 +30,7 @@ import config.{Settings, ScalaSettings, Platform, JavaPlatform, SJSPlatform} import language.implicitConversions import DenotTransformers.DenotTransformer import parsing.Scanners.Comment +import util.Property.Key import xsbti.AnalysisCallback object Contexts { @@ -177,9 +178,12 @@ object Contexts { def freshName(prefix: Name): String = freshName(prefix.toString) /** A map in which more contextual properties can be stored */ - private var _moreProperties: Map[String, Any] = _ - protected def moreProperties_=(moreProperties: Map[String, Any]) = _moreProperties = moreProperties - def moreProperties: Map[String, Any] = _moreProperties + private var _moreProperties: Map[Key[Any], Any] = _ + protected def moreProperties_=(moreProperties: Map[Key[Any], Any]) = _moreProperties = moreProperties + def moreProperties: Map[Key[Any], Any] = _moreProperties + + def property[T](key: Key[T]): Option[T] = + moreProperties.get(key).asInstanceOf[Option[T]] private var _typeComparer: TypeComparer = _ protected def typeComparer_=(typeComparer: TypeComparer) = _typeComparer = typeComparer @@ -459,9 +463,10 @@ object Contexts { def setTypeComparerFn(tcfn: Context => TypeComparer): this.type = { this.typeComparer = tcfn(this); this } def setSearchHistory(searchHistory: SearchHistory): this.type = { this.searchHistory = searchHistory; this } def setFreshNames(freshNames: FreshNameCreator): this.type = { this.freshNames = freshNames; this } - def setMoreProperties(moreProperties: Map[String, Any]): this.type = { this.moreProperties = moreProperties; this } + def setMoreProperties(moreProperties: Map[Key[Any], Any]): this.type = { this.moreProperties = moreProperties; this } - def setProperty(prop: (String, Any)): this.type = setMoreProperties(moreProperties + prop) + def setProperty[T](key: Key[T], value: T): this.type = + setMoreProperties(moreProperties.updated(key, value)) def setPhase(pid: PhaseId): this.type = setPeriod(Period(runId, pid)) def setPhase(phase: Phase): this.type = setPeriod(Period(runId, phase.start, phase.end)) diff --git a/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/src/dotty/tools/dotc/transform/ExplicitOuter.scala index 6a52b128c141..60ef1b3061da 100644 --- a/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -15,7 +15,7 @@ import ast.Trees._ import SymUtils._ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Phases.Phase -import util.Attachment +import util.Property import collection.mutable /** This phase adds outer accessors to classes and traits that need them. @@ -36,7 +36,7 @@ class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransf import ExplicitOuter._ import ast.tpd._ - val Outer = new Attachment.Key[Tree] + val Outer = new Property.Key[Tree] override def phaseName: String = "explicitOuter" diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 852689a75822..4eb8588a68df 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -18,7 +18,7 @@ import Annotations.Annotation import transform.ExplicitOuter import config.Printers.inlining import ErrorReporting.errorTree -import util.Attachment +import util.Property import collection.mutable object Inliner { @@ -28,7 +28,7 @@ object Inliner { lazy val body = tree } - private val InlinedBody = new Attachment.Key[InlinedBody] + private val InlinedBody = new Property.Key[InlinedBody] // to be used as attachment def attachBody(inlineAnnot: Annotation, tree: => Tree)(implicit ctx: Context): Unit = inlineAnnot.tree.putAttachment(InlinedBody, new InlinedBody(tree)) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 06e798bdbd04..4dff02018d41 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -9,7 +9,7 @@ import Contexts._, Symbols._, Types._, SymDenotations._, Names._, NameOps._, Fla import ast.desugar, ast.desugar._ import ProtoTypes._ import util.Positions._ -import util.{Attachment, SourcePosition, DotClass} +import util.{Property, SourcePosition, DotClass} import collection.mutable import annotation.tailrec import ErrorReporting._ @@ -160,9 +160,9 @@ class Namer { typer: Typer => import untpd._ - val TypedAhead = new Attachment.Key[tpd.Tree] - val ExpandedTree = new Attachment.Key[Tree] - val SymOfTree = new Attachment.Key[Symbol] + val TypedAhead = new Property.Key[tpd.Tree] + val ExpandedTree = new Property.Key[Tree] + val SymOfTree = new Property.Key[Symbol] /** A partial map from unexpanded member and pattern defs and to their expansions. * Populated during enterSyms, emptied during typer. diff --git a/src/dotty/tools/dotc/util/Attachment.scala b/src/dotty/tools/dotc/util/Attachment.scala index 8088b4cd09c5..20facfd97544 100644 --- a/src/dotty/tools/dotc/util/Attachment.scala +++ b/src/dotty/tools/dotc/util/Attachment.scala @@ -4,9 +4,7 @@ package dotty.tools.dotc.util * adding, removing and lookup of attachments. Attachments are typed key/value pairs. */ object Attachment { - - /** The class of keys for attachments yielding values of type V */ - class Key[+V] + import Property.Key /** An implementation trait for attachments. * Clients should inherit from Container instead. diff --git a/src/dotty/tools/dotc/util/Property.scala b/src/dotty/tools/dotc/util/Property.scala new file mode 100644 index 000000000000..608fc88e63f0 --- /dev/null +++ b/src/dotty/tools/dotc/util/Property.scala @@ -0,0 +1,10 @@ +package dotty.tools.dotc.util + +/** Defines a key type with which to tag properties, such as attachments + * or context properties + */ +object Property { + + /** The class of keys for properties of type V */ + class Key[+V] +} \ No newline at end of file From 8a3762a62d12b7f57de27c840425184df56b2689 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 1 Sep 2016 17:31:10 +0200 Subject: [PATCH 04/77] Add Inlined tree node ... to tag inlined calls. Perform typings and transformations of inlined calls in a context that refers to the INlined node in its InlinedCall property. The idea is that we can use this to issue better error positions. This remains to be implemented. --- src/dotty/tools/dotc/Compiler.scala | 1 + src/dotty/tools/dotc/ast/Trees.scala | 15 +++++++++ src/dotty/tools/dotc/ast/tpd.scala | 3 ++ src/dotty/tools/dotc/ast/untpd.scala | 1 + .../tools/dotc/core/tasty/TreePickler.scala | 10 ++++++ .../tools/dotc/printing/RefinedPrinter.scala | 2 ++ .../tools/dotc/transform/TreeChecker.scala | 3 ++ .../tools/dotc/transform/TreeTransform.scala | 32 ++++++++++++++++++- src/dotty/tools/dotc/typer/Inliner.scala | 18 +++++++++-- src/dotty/tools/dotc/typer/Typer.scala | 14 ++++++-- tests/run/inlineTest.scala | 6 ++-- 11 files changed, 98 insertions(+), 7 deletions(-) diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 2120fa73ec6e..178cba7c4080 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -94,6 +94,7 @@ class Compiler { new SelectStatic, // get rid of selects that would be compiled into GetStatic new CollectEntryPoints, // Find classes with main methods new CollectSuperCalls, // Find classes that are called with super + new DropInlined, // Drop Inlined nodes, since backend has no use for them new MoveStatics, // Move static methods to companion classes new LabelDefs), // Converts calls to labels to jumps List(new GenSJSIR), // Generate .js code diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index 3b5de93084c6..fca1c1500730 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -503,6 +503,12 @@ object Trees { override def toString = s"JavaSeqLiteral($elems, $elemtpt)" } + /** Inlined code */ + case class Inlined[-T >: Untyped] private[ast] (call: tpd.Tree, bindings: List[MemberDef[T]], expansion: Tree[T]) + extends Tree[T] { + type ThisTree[-T >: Untyped] = Inlined[T] + } + /** A type tree that represents an existing or inferred type */ case class TypeTree[-T >: Untyped] private[ast] (original: Tree[T]) extends DenotingTree[T] with TypTree[T] { @@ -797,6 +803,7 @@ object Trees { type Try = Trees.Try[T] type SeqLiteral = Trees.SeqLiteral[T] type JavaSeqLiteral = Trees.JavaSeqLiteral[T] + type Inlined = Trees.Inlined[T] type TypeTree = Trees.TypeTree[T] type SingletonTypeTree = Trees.SingletonTypeTree[T] type AndTypeTree = Trees.AndTypeTree[T] @@ -939,6 +946,10 @@ object Trees { case tree: SeqLiteral if (elems eq tree.elems) && (elemtpt eq tree.elemtpt) => tree case _ => finalize(tree, untpd.SeqLiteral(elems, elemtpt)) } + def Inlined(tree: Tree)(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree)(implicit ctx: Context): Inlined = tree match { + case tree: Inlined if (call eq tree.call) && (bindings eq tree.bindings) && (expansion eq tree.expansion) => tree + case _ => finalize(tree, untpd.Inlined(call, bindings, expansion)) + } def TypeTree(tree: Tree)(original: Tree): TypeTree = tree match { case tree: TypeTree if original eq tree.original => tree case _ => finalize(tree, untpd.TypeTree(original)) @@ -1083,6 +1094,8 @@ object Trees { cpy.Try(tree)(transform(block), transformSub(cases), transform(finalizer)) case SeqLiteral(elems, elemtpt) => cpy.SeqLiteral(tree)(transform(elems), transform(elemtpt)) + case Inlined(call, bindings, expansion) => + cpy.Inlined(tree)(call, transformSub(bindings), transform(expansion)) case TypeTree(original) => tree case SingletonTypeTree(ref) => @@ -1185,6 +1198,8 @@ object Trees { this(this(this(x, block), handler), finalizer) case SeqLiteral(elems, elemtpt) => this(this(x, elems), elemtpt) + case Inlined(call, bindings, expansion) => + this(this(x, bindings), expansion) case TypeTree(original) => x case SingletonTypeTree(ref) => diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index f59bb7a476e2..dc0756df125a 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -116,6 +116,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def JavaSeqLiteral(elems: List[Tree], elemtpt: Tree)(implicit ctx: Context): JavaSeqLiteral = ta.assignType(new untpd.JavaSeqLiteral(elems, elemtpt), elems, elemtpt).asInstanceOf[JavaSeqLiteral] + def Inlined(call: Tree, bindings: List[MemberDef], expansion: Tree)(implicit ctx: Context): Inlined = + untpd.Inlined(call, bindings, expansion).withType(expansion.tpe) + def TypeTree(original: Tree)(implicit ctx: Context): TypeTree = TypeTree(original.tpe, original) diff --git a/src/dotty/tools/dotc/ast/untpd.scala b/src/dotty/tools/dotc/ast/untpd.scala index 32e27e9c7e71..114c13684168 100644 --- a/src/dotty/tools/dotc/ast/untpd.scala +++ b/src/dotty/tools/dotc/ast/untpd.scala @@ -197,6 +197,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def Try(expr: Tree, cases: List[CaseDef], finalizer: Tree): Try = new Try(expr, cases, finalizer) def SeqLiteral(elems: List[Tree], elemtpt: Tree): SeqLiteral = new SeqLiteral(elems, elemtpt) def JavaSeqLiteral(elems: List[Tree], elemtpt: Tree): JavaSeqLiteral = new JavaSeqLiteral(elems, elemtpt) + def Inlined(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree): Inlined = new Inlined(call, bindings, expansion) def TypeTree(original: Tree): TypeTree = new TypeTree(original) def TypeTree() = new TypeTree(EmptyTree) def SingletonTypeTree(ref: Tree): SingletonTypeTree = new SingletonTypeTree(ref) diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index e5cacfc00824..fe6a5f82825f 100644 --- a/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -7,6 +7,7 @@ import ast.Trees._ import TastyFormat._ import Contexts._, Symbols._, Types._, Names._, Constants._, Decorators._, Annotations._, StdNames.tpnme, NameOps._ import collection.mutable +import typer.Inliner import NameOps._ import TastyBuffer._ import TypeApplications._ @@ -430,6 +431,15 @@ class TreePickler(pickler: TastyPickler) { case SeqLiteral(elems, elemtpt) => writeByte(REPEATED) withLength { pickleTree(elemtpt); elems.foreach(pickleTree) } + case tree: Inlined => + // Why drop Inlined info when pickling? + // Since we never inline inside an inlined method, we know that + // any code that continas an Inlined tree is not inlined itself. + // So position information for inline expansion is no longer needed. + // The only reason to keep the inline info around would be to have fine-grained + // position information in the linker. We should come back to this + // point once we know more what we would do with such information. + pickleTree(Inliner.dropInlined(tree)) case TypeTree(original) => pickleTpt(tree) case Bind(name, body) => diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 205d2b6b93bf..53d9750f8f14 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -351,6 +351,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } case SeqLiteral(elems, elemtpt) => "[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]" + case Inlined(call, bindings, body) => + "/* inlined from " ~ toText(call) ~ "*/ " ~ blockText(bindings :+ body) case tpt: untpd.DerivedTypeTree => "" case TypeTree(orig) => diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index e7342aec9c02..074a278ca9c1 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -396,6 +396,9 @@ class TreeChecker extends Phase with SymTransformer { override def typedBlock(tree: untpd.Block, pt: Type)(implicit ctx: Context) = withDefinedSyms(tree.stats) { super.typedBlock(tree, pt) } + override def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context) = + withDefinedSyms(tree.bindings) { super.typedInlined(tree, pt) } + /** Check that all defined symbols have legal owners. * An owner is legal if it is either the same as the context's owner * or there's an owner chain of valdefs starting at the context's owner and diff --git a/src/dotty/tools/dotc/transform/TreeTransform.scala b/src/dotty/tools/dotc/transform/TreeTransform.scala index 05961508a148..40a5cb0bfa09 100644 --- a/src/dotty/tools/dotc/transform/TreeTransform.scala +++ b/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -15,6 +15,7 @@ import dotty.tools.dotc.core.Mode import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.util.DotClass +import dotty.tools.dotc.typer.Inliner import scala.annotation.tailrec import config.Printers.transforms import scala.util.control.NonFatal @@ -81,6 +82,7 @@ object TreeTransforms { def prepareForReturn(tree: Return)(implicit ctx: Context) = this def prepareForTry(tree: Try)(implicit ctx: Context) = this def prepareForSeqLiteral(tree: SeqLiteral)(implicit ctx: Context) = this + def prepareForInlined(tree: Inlined)(implicit ctx: Context) = this def prepareForTypeTree(tree: TypeTree)(implicit ctx: Context) = this def prepareForBind(tree: Bind)(implicit ctx: Context) = this def prepareForAlternative(tree: Alternative)(implicit ctx: Context) = this @@ -112,6 +114,7 @@ object TreeTransforms { def transformReturn(tree: Return)(implicit ctx: Context, info: TransformerInfo): Tree = tree def transformTry(tree: Try)(implicit ctx: Context, info: TransformerInfo): Tree = tree def transformSeqLiteral(tree: SeqLiteral)(implicit ctx: Context, info: TransformerInfo): Tree = tree + def transformInlined(tree: Inlined)(implicit ctx: Context, info: TransformerInfo): Tree = tree def transformTypeTree(tree: TypeTree)(implicit ctx: Context, info: TransformerInfo): Tree = tree def transformBind(tree: Bind)(implicit ctx: Context, info: TransformerInfo): Tree = tree def transformAlternative(tree: Alternative)(implicit ctx: Context, info: TransformerInfo): Tree = tree @@ -273,6 +276,7 @@ object TreeTransforms { nxPrepReturn = index(transformations, "prepareForReturn") nxPrepTry = index(transformations, "prepareForTry") nxPrepSeqLiteral = index(transformations, "prepareForSeqLiteral") + nxPrepInlined = index(transformations, "prepareForInlined") nxPrepTypeTree = index(transformations, "prepareForTypeTree") nxPrepBind = index(transformations, "prepareForBind") nxPrepAlternative = index(transformations, "prepareForAlternative") @@ -303,6 +307,7 @@ object TreeTransforms { nxTransReturn = index(transformations, "transformReturn") nxTransTry = index(transformations, "transformTry") nxTransSeqLiteral = index(transformations, "transformSeqLiteral") + nxTransInlined = index(transformations, "transformInlined") nxTransTypeTree = index(transformations, "transformTypeTree") nxTransBind = index(transformations, "transformBind") nxTransAlternative = index(transformations, "transformAlternative") @@ -343,6 +348,7 @@ object TreeTransforms { nxPrepReturn = indexUpdate(prev.nxPrepReturn, changedTransformationClass, transformationIndex, "prepareForReturn", copy) nxPrepTry = indexUpdate(prev.nxPrepTry, changedTransformationClass, transformationIndex, "prepareForTry", copy) nxPrepSeqLiteral = indexUpdate(prev.nxPrepSeqLiteral, changedTransformationClass, transformationIndex, "prepareForSeqLiteral", copy) + nxPrepInlined = indexUpdate(prev.nxPrepInlined, changedTransformationClass, transformationIndex, "prepareForInlined", copy) nxPrepTypeTree = indexUpdate(prev.nxPrepTypeTree, changedTransformationClass, transformationIndex, "prepareForTypeTree", copy) nxPrepBind = indexUpdate(prev.nxPrepBind, changedTransformationClass, transformationIndex, "prepareForBind", copy) nxPrepAlternative = indexUpdate(prev.nxPrepAlternative, changedTransformationClass, transformationIndex, "prepareForAlternative", copy) @@ -372,6 +378,7 @@ object TreeTransforms { nxTransReturn = indexUpdate(prev.nxTransReturn, changedTransformationClass, transformationIndex, "transformReturn", copy) nxTransTry = indexUpdate(prev.nxTransTry, changedTransformationClass, transformationIndex, "transformTry", copy) nxTransSeqLiteral = indexUpdate(prev.nxTransSeqLiteral, changedTransformationClass, transformationIndex, "transformSeqLiteral", copy) + nxTransInlined = indexUpdate(prev.nxTransInlined, changedTransformationClass, transformationIndex, "transformInlined", copy) nxTransTypeTree = indexUpdate(prev.nxTransTypeTree, changedTransformationClass, transformationIndex, "transformTypeTree", copy) nxTransBind = indexUpdate(prev.nxTransBind, changedTransformationClass, transformationIndex, "transformBind", copy) nxTransAlternative = indexUpdate(prev.nxTransAlternative, changedTransformationClass, transformationIndex, "transformAlternative", copy) @@ -407,6 +414,7 @@ object TreeTransforms { var nxPrepReturn: Array[Int] = _ var nxPrepTry: Array[Int] = _ var nxPrepSeqLiteral: Array[Int] = _ + var nxPrepInlined: Array[Int] = _ var nxPrepTypeTree: Array[Int] = _ var nxPrepBind: Array[Int] = _ var nxPrepAlternative: Array[Int] = _ @@ -437,6 +445,7 @@ object TreeTransforms { var nxTransReturn: Array[Int] = _ var nxTransTry: Array[Int] = _ var nxTransSeqLiteral: Array[Int] = _ + var nxTransInlined: Array[Int] = _ var nxTransTypeTree: Array[Int] = _ var nxTransBind: Array[Int] = _ var nxTransAlternative: Array[Int] = _ @@ -515,6 +524,7 @@ object TreeTransforms { val prepForReturn: Mutator[Return] = (trans, tree, ctx) => trans.prepareForReturn(tree)(ctx) val prepForTry: Mutator[Try] = (trans, tree, ctx) => trans.prepareForTry(tree)(ctx) val prepForSeqLiteral: Mutator[SeqLiteral] = (trans, tree, ctx) => trans.prepareForSeqLiteral(tree)(ctx) + val prepForInlined: Mutator[Inlined] = (trans, tree, ctx) => trans.prepareForInlined(tree)(ctx) val prepForTypeTree: Mutator[TypeTree] = (trans, tree, ctx) => trans.prepareForTypeTree(tree)(ctx) val prepForBind: Mutator[Bind] = (trans, tree, ctx) => trans.prepareForBind(tree)(ctx) val prepForAlternative: Mutator[Alternative] = (trans, tree, ctx) => trans.prepareForAlternative(tree)(ctx) @@ -740,6 +750,17 @@ object TreeTransforms { } else tree } + @tailrec + final private[TreeTransforms] def goInlined(tree: Inlined, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (cur < info.transformers.length) { + val trans = info.transformers(cur) + trans.transformInlined(tree)(ctx.withPhase(trans.treeTransformPhase), info) match { + case t: Inlined => goInlined(t, info.nx.nxTransInlined(cur + 1)) + case t => transformSingle(t, cur + 1) + } + } else tree + } + @tailrec final private[TreeTransforms] def goTypeTree(tree: TypeTree, cur: Int)(implicit ctx: Context, info: TransformerInfo): Tree = { if (cur < info.transformers.length) { @@ -884,7 +905,8 @@ object TreeTransforms { case tree: CaseDef => goCaseDef(tree, info.nx.nxTransCaseDef(cur)) case tree: Return => goReturn(tree, info.nx.nxTransReturn(cur)) case tree: Try => goTry(tree, info.nx.nxTransTry(cur)) - case tree: SeqLiteral => goSeqLiteral(tree, info.nx.nxTransLiteral(cur)) + case tree: SeqLiteral => goSeqLiteral(tree, info.nx.nxTransSeqLiteral(cur)) + case tree: Inlined => goInlined(tree, info.nx.nxTransInlined(cur)) case tree: TypeTree => goTypeTree(tree, info.nx.nxTransTypeTree(cur)) case tree: Alternative => goAlternative(tree, info.nx.nxTransAlternative(cur)) case tree: UnApply => goUnApply(tree, info.nx.nxTransUnApply(cur)) @@ -1090,6 +1112,14 @@ object TreeTransforms { val elemtpt = transform(tree.elemtpt, mutatedInfo, cur) goSeqLiteral(cpy.SeqLiteral(tree)(elems, elemtpt), mutatedInfo.nx.nxTransSeqLiteral(cur)) } + case tree: Inlined => + implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForInlined, info.nx.nxPrepInlined, tree, cur) + if (mutatedInfo eq null) tree + else { + val bindings = transformSubTrees(tree.bindings, mutatedInfo, cur) + val expansion = transform(tree.expansion, mutatedInfo, cur)(Inliner.inlineContext(tree)) + goInlined(cpy.Inlined(tree)(tree.call, bindings, expansion), mutatedInfo.nx.nxTransInlined(cur)) + } case tree: TypeTree => implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForTypeTree, info.nx.nxPrepTypeTree, tree, cur) if (mutatedInfo eq null) tree diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 4eb8588a68df..51efca3f46e6 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -30,6 +30,8 @@ object Inliner { private val InlinedBody = new Property.Key[InlinedBody] // to be used as attachment + val InlinedCall = new Property.Key[tpd.Inlined] // to be used in context + def attachBody(inlineAnnot: Annotation, tree: => Tree)(implicit ctx: Context): Unit = inlineAnnot.tree.putAttachment(InlinedBody, new InlinedBody(tree)) @@ -68,6 +70,17 @@ object Inliner { | Maybe this is caused by a recursive inline method? | You can use -Xmax:inlines to change the limit.""") } + + def dropInlined(tree: tpd.Inlined)(implicit ctx: Context): Tree = { + val reposition = new TreeMap { + override def transform(tree: Tree)(implicit ctx: Context): Tree = + tree.withPos(tree.pos) + } + tpd.seq(tree.bindings, reposition.transform(tree.expansion)) + } + + def inlineContext(tree: untpd.Inlined)(implicit ctx: Context): Context = + ctx.fresh.setProperty(InlinedCall, tree) } class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { @@ -191,8 +204,9 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { } val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil) - - val result = inliner(Block(outerBindings.toList, rhs)).withPos(call.pos) + val Block(bindings: List[MemberDef], expansion) = + inliner(Block(outerBindings.toList, rhs)).withPos(call.pos) + val result = tpd.Inlined(call, bindings, expansion) inlining.println(i"inlining $call\n --> \n$result") result diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 007eaa468e69..dc41cc5675ce 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -553,9 +553,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } + def typedBlockStats(stats: List[untpd.Tree])(implicit ctx: Context): (Context, List[tpd.Tree]) = + (index(stats), typedStats(stats, ctx.owner)) + def typedBlock(tree: untpd.Block, pt: Type)(implicit ctx: Context) = track("typedBlock") { - val exprCtx = index(tree.stats) - val stats1 = typedStats(tree.stats, ctx.owner) + val (exprCtx, stats1) = typedBlockStats(tree.stats) val ept = if (tree.isInstanceOf[untpd.InfixOpBlock]) // Right-binding infix operations are expanded to InfixBlocks, which may be followed by arguments. @@ -943,6 +945,13 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit assignType(cpy.SeqLiteral(tree)(elems1, elemtpt1), elems1, elemtpt1) } + def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context): Inlined = { + val (exprCtx, bindings1) = typedBlockStats(tree.bindings) + val expansion1 = typed(tree.expansion, pt)(Inliner.inlineContext(tree)(exprCtx)) + cpy.Inlined(tree)(tree.call, bindings1.asInstanceOf[List[MemberDef]], expansion1) + .withType(expansion1.tpe) + } + def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree = track("typedTypeTree") { if (tree.original.isEmpty) tree match { @@ -1429,6 +1438,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case tree: untpd.TypeApply => typedTypeApply(tree, pt) case tree: untpd.Super => typedSuper(tree, pt) case tree: untpd.SeqLiteral => typedSeqLiteral(tree, pt) + case tree: untpd.Inlined => typedInlined(tree, pt) case tree: untpd.TypeTree => typedTypeTree(tree, pt) case tree: untpd.SingletonTypeTree => typedSingletonTypeTree(tree) case tree: untpd.AndTypeTree => typedAndTypeTree(tree) diff --git a/tests/run/inlineTest.scala b/tests/run/inlineTest.scala index feaa43fbb37e..39153951e081 100644 --- a/tests/run/inlineTest.scala +++ b/tests/run/inlineTest.scala @@ -33,8 +33,9 @@ object Test { def f = "Outer.f" class Inner { val msg = " Inner" + @dotty.annotation.inline def m = msg @dotty.annotation.inline def g = f - @dotty.annotation.inline def h = f ++ this.msg + @dotty.annotation.inline def h = f ++ m } } @@ -46,7 +47,8 @@ object Test { val o = new Outer val i = new o.Inner - println(i.g) + println(i.m) + //println(i.g) //println(i.h) } From faba2b7999bf73bf10116b391efbdd751054ead0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 1 Sep 2016 17:44:27 +0200 Subject: [PATCH 05/77] Track Inlined nodes in ctx.source --- src/dotty/tools/dotc/core/Contexts.scala | 10 +++++++--- src/dotty/tools/dotc/transform/DropInlined.scala | 15 +++++++++++++++ src/dotty/tools/dotc/typer/Inliner.scala | 9 ++++++++- 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/DropInlined.scala diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index 6f9d19c40a9b..ea0ab95e6d9a 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -18,7 +18,7 @@ import util.Positions._ import ast.Trees._ import ast.untpd import util.{FreshNameCreator, SimpleMap, SourceFile, NoSource} -import typer.{Implicits, ImplicitRunInfo, ImportInfo, NamerContextOps, SearchHistory, TypeAssigner, Typer} +import typer.{Implicits, ImplicitRunInfo, ImportInfo, Inliner, NamerContextOps, SearchHistory, TypeAssigner, Typer} import Implicits.ContextualImplicits import config.Settings._ import config.Config @@ -370,8 +370,12 @@ object Contexts { /** The current source file; will be derived from current * compilation unit. */ - def source: SourceFile = - if (compilationUnit == null) NoSource else compilationUnit.source + def source: SourceFile = { + val file = Inliner.inlinedSource + if (file.exists) file + else if (compilationUnit == null) NoSource + else compilationUnit.source + } /** Does current phase use an erased types interpretation? */ def erasedTypes: Boolean = phase.erasedTypes diff --git a/src/dotty/tools/dotc/transform/DropInlined.scala b/src/dotty/tools/dotc/transform/DropInlined.scala new file mode 100644 index 000000000000..775663b5c261 --- /dev/null +++ b/src/dotty/tools/dotc/transform/DropInlined.scala @@ -0,0 +1,15 @@ +package dotty.tools.dotc +package transform + +import typer.Inliner +import core.Contexts.Context +import TreeTransforms.{MiniPhaseTransform, TransformerInfo} + +/** Drop Inlined nodes */ +class DropInlined extends MiniPhaseTransform { + import ast.tpd._ + override def phaseName = "dropInlined" + + override def transformInlined(tree: Inlined)(implicit ctx: Context, info: TransformerInfo): Tree = + Inliner.dropInlined(tree) +} diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 51efca3f46e6..28c725b33385 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -18,7 +18,7 @@ import Annotations.Annotation import transform.ExplicitOuter import config.Printers.inlining import ErrorReporting.errorTree -import util.Property +import util.{Property, SourceFile, NoSource} import collection.mutable object Inliner { @@ -81,6 +81,13 @@ object Inliner { def inlineContext(tree: untpd.Inlined)(implicit ctx: Context): Context = ctx.fresh.setProperty(InlinedCall, tree) + + def inlinedSource(implicit ctx: Context): SourceFile = ctx.property(InlinedCall) match { + case Some(inlined) => + val file = inlined.call.symbol.sourceFile + if (file.exists) new SourceFile(file) else NoSource + case _ => NoSource + } } class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { From 617be516b261524c2f0762de10f2bde376043ad7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 1 Sep 2016 19:10:48 +0200 Subject: [PATCH 06/77] Print inlining positions in error messages Error messages now print the inlined positions as well as the position of the inlined call, recursively. --- src/dotty/tools/dotc/core/Contexts.scala | 8 ++------ src/dotty/tools/dotc/core/Decorators.scala | 14 ++++++++++++-- .../dotc/reporting/ConsoleReporter.scala | 19 ++++++++++--------- src/dotty/tools/dotc/typer/Inliner.scala | 18 +++++++++++++----- .../tools/dotc/util/SourcePosition.scala | 6 +++++- 5 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index ea0ab95e6d9a..f2a1fe53e3d5 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -370,12 +370,8 @@ object Contexts { /** The current source file; will be derived from current * compilation unit. */ - def source: SourceFile = { - val file = Inliner.inlinedSource - if (file.exists) file - else if (compilationUnit == null) NoSource - else compilationUnit.source - } + def source: SourceFile = + if (compilationUnit == null) NoSource else compilationUnit.source /** Does current phase use an erased types interpretation? */ def erasedTypes: Boolean = phase.erasedTypes diff --git a/src/dotty/tools/dotc/core/Decorators.scala b/src/dotty/tools/dotc/core/Decorators.scala index 387e7e466d2c..fc546667d9d4 100644 --- a/src/dotty/tools/dotc/core/Decorators.scala +++ b/src/dotty/tools/dotc/core/Decorators.scala @@ -7,6 +7,8 @@ import Contexts._, Names._, Phases._, printing.Texts._, printing.Printer, printi import util.Positions.Position, util.SourcePosition import collection.mutable.ListBuffer import dotty.tools.dotc.transform.TreeTransforms._ +import typer.Inliner +import ast.tpd.Inlined import scala.language.implicitConversions import printing.Formatting._ @@ -148,8 +150,16 @@ object Decorators { } } - implicit def sourcePos(pos: Position)(implicit ctx: Context): SourcePosition = - ctx.source.atPos(pos) + implicit def sourcePos(pos: Position)(implicit ctx: Context): SourcePosition = { + def recur(inlineds: Stream[Inlined], pos: Position): SourcePosition = inlineds match { + case inlined #:: rest => + Inliner.sourceFile(inlined).atPos(pos) + .withOuter(recur(rest, inlined.call.pos)) + case empty => + ctx.source.atPos(pos) + } + recur(Inliner.enclosingInlineds, pos) + } implicit class StringInterpolators(val sc: StringContext) extends AnyVal { diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index f35293d8dbde..deb772db5719 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -21,11 +21,15 @@ class ConsoleReporter( /** maximal number of error messages to be printed */ protected def ErrorLimit = 100 - def printSourceLine(pos: SourcePosition) = - printMessage(pos.lineContent.stripLineEnd) - - def printColumnMarker(pos: SourcePosition) = - if (pos.exists) { printMessage(" " * pos.column + "^") } + def printPos(pos: SourcePosition): Unit = + if (pos.exists) { + printMessage(pos.lineContent.stripLineEnd) + printMessage(" " * pos.column + "^") + if (pos.outer.exists) { + printMessage(s"\n... this location is in code that was inlined at ${pos.outer}:\n") + printPos(pos.outer) + } + } /** Prints the message. */ def printMessage(msg: String): Unit = { writer.print(msg + "\n"); writer.flush() } @@ -34,10 +38,7 @@ class ConsoleReporter( def printMessageAndPos(msg: String, pos: SourcePosition)(implicit ctx: Context): Unit = { val posStr = if (pos.exists) s"$pos: " else "" printMessage(posStr + msg) - if (pos.exists) { - printSourceLine(pos) - printColumnMarker(pos) - } + printPos(pos) } override def doReport(d: Diagnostic)(implicit ctx: Context): Unit = d match { diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 28c725b33385..8d25e32502f3 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -82,11 +82,19 @@ object Inliner { def inlineContext(tree: untpd.Inlined)(implicit ctx: Context): Context = ctx.fresh.setProperty(InlinedCall, tree) - def inlinedSource(implicit ctx: Context): SourceFile = ctx.property(InlinedCall) match { - case Some(inlined) => - val file = inlined.call.symbol.sourceFile - if (file.exists) new SourceFile(file) else NoSource - case _ => NoSource + def enclosingInlineds(implicit ctx: Context): Stream[Inlined] = + ctx.property(InlinedCall) match { + case found @ Some(inlined) => + inlined #:: + enclosingInlineds( + ctx.outersIterator.dropWhile(_.property(InlinedCall) == found).next) + case _ => + Stream.Empty + } + + def sourceFile(inlined: Inlined)(implicit ctx: Context) = { + val file = inlined.call.symbol.sourceFile + if (file.exists) new SourceFile(file) else NoSource } } diff --git a/src/dotty/tools/dotc/util/SourcePosition.scala b/src/dotty/tools/dotc/util/SourcePosition.scala index 0b2b2aa0b306..68a9b64036d8 100644 --- a/src/dotty/tools/dotc/util/SourcePosition.scala +++ b/src/dotty/tools/dotc/util/SourcePosition.scala @@ -5,7 +5,8 @@ package util import Positions.{Position, NoPosition} /** A source position is comprised of a position in a source file */ -case class SourcePosition(source: SourceFile, pos: Position) extends interfaces.SourcePosition { +case class SourcePosition(source: SourceFile, pos: Position, outer: SourcePosition = NoSourcePosition) +extends interfaces.SourcePosition { def exists = pos.exists def lineContent: String = source.lineContent(point) @@ -24,6 +25,8 @@ case class SourcePosition(source: SourceFile, pos: Position) extends interfaces. def endLine: Int = source.offsetToLine(end) def endColumn: Int = source.column(end) + def withOuter(outer: SourcePosition) = new SourcePosition(source, pos, outer) + override def toString = if (source.exists) s"${source.file}:${line + 1}" else s"(no source file, offset = ${pos.point})" @@ -32,5 +35,6 @@ case class SourcePosition(source: SourceFile, pos: Position) extends interfaces. /** A sentinel for a non-existing source position */ @sharable object NoSourcePosition extends SourcePosition(NoSource, NoPosition) { override def toString = "?" + override def withOuter(outer: SourcePosition) = outer } From 729815bec8035f2d6132fde0eb928e877dd21a64 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 2 Sep 2016 13:39:48 +0200 Subject: [PATCH 07/77] Implement inline if Inline conditionals with constant conditions --- src/dotty/tools/dotc/typer/Inliner.scala | 13 +++++++++++++ src/dotty/tools/dotc/typer/Typer.scala | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 8d25e32502f3..8d9b5bf304c3 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -10,6 +10,7 @@ import Flags._ import Symbols._ import Types._ import Decorators._ +import Constants._ import StdNames.nme import Contexts.Context import Names.Name @@ -56,6 +57,18 @@ object Inliner { case res => res } } + override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { + val cond1 = typed(tree.cond, defn.BooleanType) + cond1.tpe.widenTermRefExpr match { + case ConstantType(Constant(condVal: Boolean)) => + val selected = typed(if (condVal) tree.thenp else tree.elsep, pt) + if (isIdempotentExpr(cond1)) selected + else Block(cond1 :: Nil, selected) + case _ => + val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) + super.typedIf(if1, pt) + } + } } def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index dc41cc5675ce..e809605f48cc 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -609,7 +609,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit em"local definition of ${leaks.head.name} escapes as part of expression's type ${tree.tpe}"/*; full type: ${result.tpe.toString}"*/) } - def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = track("typedIf") { + def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context): Tree = track("typedIf") { val cond1 = typed(tree.cond, defn.BooleanType) val thenp1 = typed(tree.thenp, pt.notApplied) val elsep1 = typed(tree.elsep orElse (untpd.unitLiteral withPos tree.pos), pt.notApplied) From b8a117634b636d6255e6233ae1996f6c7b90dcae Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 2 Sep 2016 13:51:20 +0200 Subject: [PATCH 08/77] Avoid simple aliases in bindings Avoid bindings such as type T = T' val x: x'.type = x' Required some refactorings in Inliner. --- src/dotty/tools/dotc/typer/Inliner.scala | 119 +++++++++++++---------- 1 file changed, 66 insertions(+), 53 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 8d9b5bf304c3..c6177eb8d8dc 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -114,8 +114,6 @@ object Inliner { class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { import tpd._ - private val meth = call.symbol - private def decomposeCall(tree: Tree): (Tree, List[Tree], List[List[Tree]]) = tree match { case Apply(fn, args) => val (meth, targs, argss) = decomposeCall(fn) @@ -128,64 +126,73 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { } private val (methPart, targs, argss) = decomposeCall(call) + private val meth = methPart.symbol private lazy val prefix = methPart match { case Select(qual, _) => qual case _ => tpd.This(ctx.owner.enclosingClass.asClass) } - private val replacement = new mutable.HashMap[Type, NamedType] + private val thisProxy = new mutable.HashMap[Type, NamedType] + private val paramProxy = new mutable.HashMap[Type, Type] + private val paramBinding = new mutable.HashMap[Name, Type] + val bindingsBuf = new mutable.ListBuffer[MemberDef] - private val paramBindings = paramBindingsOf(meth.info, targs, argss) + computeParamBindings(meth.info, targs, argss) - private def paramBindingsOf(tp: Type, targs: List[Tree], argss: List[List[Tree]]): List[MemberDef] = tp match { + private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = + ctx.newSymbol(ctx.owner, name, flags, info, coord = call.pos) + + private def computeParamBindings(tp: Type, targs: List[Tree], argss: List[List[Tree]]): Unit = tp match { case tp: PolyType => - val bindings = - (tp.paramNames, targs).zipped.map { (name, arg) => - val tparam = newSym(name, EmptyFlags, TypeAlias(arg.tpe.stripTypeVar)).asType - TypeDef(tparam) + (tp.paramNames, targs).zipped.foreach { (name, arg) => + paramBinding(name) = arg.tpe.stripTypeVar match { + case argtpe: TypeRef => argtpe + case argtpe => + val binding = newSym(name, EmptyFlags, TypeAlias(argtpe)).asType + bindingsBuf += TypeDef(binding) + binding.typeRef } - bindings ::: paramBindingsOf(tp.resultType, Nil, argss) + } + computeParamBindings(tp.resultType, Nil, argss) case tp: MethodType => - val bindings = - (tp.paramNames, tp.paramTypes, argss.head).zipped.map { (name, paramtp, arg) => - def isByName = paramtp.dealias.isInstanceOf[ExprType] - val (paramFlags, paramType) = - if (isByName) (Method, ExprType(arg.tpe)) else (EmptyFlags, arg.tpe) - val vparam = newSym(name, paramFlags, paramType).asTerm - if (isByName) DefDef(vparam, arg) else ValDef(vparam, arg) + (tp.paramNames, tp.paramTypes, argss.head).zipped.foreach { (name, paramtp, arg) => + def isByName = paramtp.dealias.isInstanceOf[ExprType] + paramBinding(name) = arg.tpe.stripTypeVar match { + case argtpe: SingletonType if isByName || isIdempotentExpr(arg) => argtpe + case argtpe => + val (bindingFlags, bindingType) = + if (isByName) (Method, ExprType(argtpe.widen)) else (EmptyFlags, argtpe.widen) + val binding = newSym(name, bindingFlags, bindingType).asTerm + bindingsBuf += + (if (isByName) DefDef(binding, arg) else ValDef(binding, arg)) + binding.termRef } - bindings ::: paramBindingsOf(tp.resultType, targs, argss.tail) + } + computeParamBindings(tp.resultType, targs, argss.tail) case _ => assert(targs.isEmpty) assert(argss.isEmpty) - Nil } - private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = - ctx.newSymbol(ctx.owner, name, flags, info, coord = call.pos) - - private def registerType(tpe: Type): Unit = - if (!replacement.contains(tpe)) tpe match { - case tpe: ThisType => - if (!ctx.owner.isContainedIn(tpe.cls) && !tpe.cls.is(Package)) - if (tpe.cls.isStaticOwner) - replacement(tpe) = tpe.cls.sourceModule.termRef - else { - def outerDistance(cls: Symbol): Int = { - assert(cls.exists, i"not encl: ${meth.owner.enclosingClass} ${tpe.cls}") - if (tpe.cls eq cls) 0 - else outerDistance(cls.owner.enclosingClass) + 1 - } - val n = outerDistance(meth.owner) - replacement(tpe) = newSym(nme.SELF ++ n.toString, EmptyFlags, tpe.widen).termRef + private def registerType(tpe: Type): Unit = tpe match { + case tpe: ThisType if !thisProxy.contains(tpe) => + if (!ctx.owner.isContainedIn(tpe.cls) && !tpe.cls.is(Package)) + if (tpe.cls.isStaticOwner) + thisProxy(tpe) = tpe.cls.sourceModule.termRef + else { + def outerDistance(cls: Symbol): Int = { + assert(cls.exists, i"not encl: ${meth.owner.enclosingClass} ${tpe.cls}") + if (tpe.cls eq cls) 0 + else outerDistance(cls.owner.enclosingClass) + 1 } - case tpe: NamedType if tpe.symbol.is(Param) && tpe.symbol.owner == meth => - val Some(binding) = paramBindings.find(_.name == tpe.name) - replacement(tpe) = - if (tpe.name.isTypeName) binding.symbol.typeRef else binding.symbol.termRef - case _ => - } + val n = outerDistance(meth.owner) + thisProxy(tpe) = newSym(nme.SELF ++ n.toString, EmptyFlags, tpe.widen).termRef + } + case tpe: NamedType if tpe.symbol.is(Param) && tpe.symbol.owner == meth && !paramProxy.contains(tpe) => + paramProxy(tpe) = paramBinding(tpe.name) + case _ => + } private def registerLeaf(tree: Tree): Unit = tree match { case _: This | _: Ident => registerType(tree.tpe) @@ -198,42 +205,48 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { rhs.foreachSubTree(registerLeaf) val accessedSelfSyms = - (for ((tp: ThisType, ref) <- replacement) yield ref.symbol.asTerm).toSeq.sortBy(outerLevel) + (for ((tp: ThisType, ref) <- thisProxy) yield ref.symbol.asTerm).toSeq.sortBy(outerLevel) - val outerBindings = new mutable.ListBuffer[MemberDef] + var lastSelf: Symbol = NoSymbol for (selfSym <- accessedSelfSyms) { val rhs = - if (outerBindings.isEmpty) prefix + if (!lastSelf.exists) prefix else { - val lastSelf = outerBindings.last.symbol val outerDelta = outerLevel(selfSym) - outerLevel(lastSelf) def outerSelect(ref: Tree, dummy: Int): Tree = ??? //ref.select(ExplicitOuter.outerAccessorTBD(ref.tpe.widen.classSymbol.asClass)) (ref(lastSelf) /: (0 until outerDelta))(outerSelect) } - outerBindings += ValDef(selfSym, rhs.ensureConforms(selfSym.info)) + bindingsBuf += ValDef(selfSym, rhs.ensureConforms(selfSym.info)) + lastSelf = selfSym } - outerBindings ++= paramBindings val typeMap = new TypeMap { def apply(t: Type) = t match { - case _: SingletonType => replacement.getOrElse(t, t) - case _ => mapOver(t) + case t: ThisType => thisProxy.getOrElse(t, t) + case t: SingletonType => paramProxy.getOrElse(t, t) + case t => mapOver(t) } } def treeMap(tree: Tree) = tree match { - case _: This | _: Ident => - replacement.get(tree.tpe) match { + case _: This => + thisProxy.get(tree.tpe) match { case Some(t) => ref(t) case None => tree } + case _: Ident => + paramProxy.get(tree.tpe) match { + case Some(t: TypeRef) => ref(t) + case Some(t: SingletonType) => singleton(t) + case None => tree + } case _ => tree } val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil) val Block(bindings: List[MemberDef], expansion) = - inliner(Block(outerBindings.toList, rhs)).withPos(call.pos) + inliner(Block(bindingsBuf.toList, rhs)).withPos(call.pos) val result = tpd.Inlined(call, bindings, expansion) inlining.println(i"inlining $call\n --> \n$result") From 6f9f29e1f90c017f30a81b0e37f994b5b8cd8507 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 2 Sep 2016 13:52:28 +0200 Subject: [PATCH 09/77] Recursive inlining tests pos/power inlines with alomst no extraneous boilerplate. neg/power gives an error that maximal numbers of inlines was exceeded. --- tests/neg/power.scala | 15 +++++++++++++++ tests/run/power.scala | 17 +++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/neg/power.scala create mode 100644 tests/run/power.scala diff --git a/tests/neg/power.scala b/tests/neg/power.scala new file mode 100644 index 000000000000..7439d8699989 --- /dev/null +++ b/tests/neg/power.scala @@ -0,0 +1,15 @@ +object Test { + + @dotty.annotation.inline + def power(x: Double, n: Int): Double = + if (n == 0) 1.0 + else if (n == 1) x + else { + val y = power(x, n / 2) // error: maximal number of inlines exceeded + if (n % 2 == 0) y * y else y * y * x + } + + def main(args: Array[String]): Unit = { + println(power(2.0, args.length)) + } +} diff --git a/tests/run/power.scala b/tests/run/power.scala new file mode 100644 index 000000000000..2bb66fa8df86 --- /dev/null +++ b/tests/run/power.scala @@ -0,0 +1,17 @@ +object Test { + + @dotty.annotation.inline + def power(x: Double, n: Int): Double = + if (n == 0) 1.0 + else if (n == 1) x + else { + val y = power(x, n / 2) + if (n % 2 == 0) y * y else y * y * x + } + + def main(args: Array[String]): Unit = { + println(power(2.0, 10)) + def x = 2.0 + println(power(x, 11)) + } +} From a75fee22fdff75dd9ebc2faa008e0ec9a82af1fb Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 2 Sep 2016 14:56:35 +0200 Subject: [PATCH 10/77] Don't expand stat before recursion in namer Dottydoc needs the unexpanded trees so that it can have access to the attached docstring --- src/dotty/tools/dotc/typer/Namer.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 4dff02018d41..c6ff64d8d9cf 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -404,7 +404,7 @@ class Namer { typer: Typer => * enter them into symbol table */ def indexExpanded(stat: Tree)(implicit ctx: Context): Context = { - def recur(stat: Tree): Context = stat match { + def recur(stat: Tree): Context = expanded(stat) match { case pcl: PackageDef => val pkg = createPackageSymbol(pcl.pid) index(pcl.stats)(ctx.fresh.setOwner(pkg.moduleClass)) @@ -424,7 +424,7 @@ class Namer { typer: Typer => case _ => ctx } - recur(expanded(stat)) + recur(stat) } /** Determines whether this field holds an enum constant. From fd7b60ce25278a53defd907ef68edb555c552705 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 2 Sep 2016 18:14:29 +0200 Subject: [PATCH 11/77] Fix stack overflow on recurs in namer --- src/dotty/tools/dotc/typer/Namer.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index c6ff64d8d9cf..034a1ff505e5 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -403,8 +403,8 @@ class Namer { typer: Typer => /** Create top-level symbols for all statements in the expansion of this statement and * enter them into symbol table */ - def indexExpanded(stat: Tree)(implicit ctx: Context): Context = { - def recur(stat: Tree): Context = expanded(stat) match { + def indexExpanded(origStat: Tree)(implicit ctx: Context): Context = { + def recur(stat: Tree): Context = stat match { case pcl: PackageDef => val pkg = createPackageSymbol(pcl.pid) index(pcl.stats)(ctx.fresh.setOwner(pkg.moduleClass)) @@ -415,7 +415,7 @@ class Namer { typer: Typer => importContext(createSymbol(imp), imp.selectors) case mdef: DefTree => val sym = enterSymbol(createSymbol(mdef)) - setDocstring(sym, stat) + setDocstring(sym, origStat) addEnumConstants(mdef, sym) ctx case stats: Thicket => @@ -424,7 +424,7 @@ class Namer { typer: Typer => case _ => ctx } - recur(stat) + recur(expanded(origStat)) } /** Determines whether this field holds an enum constant. From a47a8008023ea04ff7f8d708567fb6a2c516caaa Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 2 Sep 2016 14:06:08 +0200 Subject: [PATCH 12/77] Simplify enclosingInlineds - represent directly as a list - can replace separate inlineCount --- src/dotty/tools/dotc/core/Contexts.scala | 4 ---- src/dotty/tools/dotc/core/Decorators.scala | 7 +++---- src/dotty/tools/dotc/typer/Inliner.scala | 23 +++++++--------------- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index f2a1fe53e3d5..313ea31243ed 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -672,10 +672,6 @@ object Contexts { */ private[dotty] var unsafeNonvariant: RunId = NoRunId - // Typer state - - private[dotty] var inlineCount = 0 - // Phases state private[core] var phasesPlan: List[List[Phase]] = _ diff --git a/src/dotty/tools/dotc/core/Decorators.scala b/src/dotty/tools/dotc/core/Decorators.scala index fc546667d9d4..cd4941c724fa 100644 --- a/src/dotty/tools/dotc/core/Decorators.scala +++ b/src/dotty/tools/dotc/core/Decorators.scala @@ -151,10 +151,9 @@ object Decorators { } implicit def sourcePos(pos: Position)(implicit ctx: Context): SourcePosition = { - def recur(inlineds: Stream[Inlined], pos: Position): SourcePosition = inlineds match { - case inlined #:: rest => - Inliner.sourceFile(inlined).atPos(pos) - .withOuter(recur(rest, inlined.call.pos)) + def recur(inlineds: List[Inlined], pos: Position): SourcePosition = inlineds match { + case inlined :: rest => + Inliner.sourceFile(inlined).atPos(pos).withOuter(recur(rest, inlined.call.pos)) case empty => ctx.source.atPos(pos) } diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index c6177eb8d8dc..06eea9113503 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -31,7 +31,7 @@ object Inliner { private val InlinedBody = new Property.Key[InlinedBody] // to be used as attachment - val InlinedCall = new Property.Key[tpd.Inlined] // to be used in context + private val InlinedCall = new Property.Key[List[tpd.Inlined]] // to be used in context def attachBody(inlineAnnot: Annotation, tree: => Tree)(implicit ctx: Context): Unit = inlineAnnot.tree.putAttachment(InlinedBody, new InlinedBody(tree)) @@ -72,12 +72,10 @@ object Inliner { } def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { - if (ctx.inlineCount < ctx.settings.xmaxInlines.value) { - ctx.inlineCount += 1 + if (enclosingInlineds.length < ctx.settings.xmaxInlines.value) { val rhs = inlinedBody(tree.symbol) val inlined = new Inliner(tree, rhs).inlined - try new Typer().typedUnadapted(inlined, pt) - finally ctx.inlineCount -= 1 + new Typer().typedUnadapted(inlined, pt) } else errorTree(tree, i"""Maximal number of successive inlines (${ctx.settings.xmaxInlines.value}) exceeded, | Maybe this is caused by a recursive inline method? @@ -93,17 +91,10 @@ object Inliner { } def inlineContext(tree: untpd.Inlined)(implicit ctx: Context): Context = - ctx.fresh.setProperty(InlinedCall, tree) - - def enclosingInlineds(implicit ctx: Context): Stream[Inlined] = - ctx.property(InlinedCall) match { - case found @ Some(inlined) => - inlined #:: - enclosingInlineds( - ctx.outersIterator.dropWhile(_.property(InlinedCall) == found).next) - case _ => - Stream.Empty - } + ctx.fresh.setProperty(InlinedCall, tree :: enclosingInlineds) + + def enclosingInlineds(implicit ctx: Context): List[Inlined] = + ctx.property(InlinedCall).getOrElse(Nil) def sourceFile(inlined: Inlined)(implicit ctx: Context) = { val file = inlined.call.symbol.sourceFile From 96d057364703a9b89ca49f2ac16f2cdb140d396d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 2 Sep 2016 17:23:54 +0200 Subject: [PATCH 13/77] Support separate compilation Inline trees can now be read form TASTY. However, positions are not set correctly. This remains to be implemented. --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 7 +++++++ src/dotty/tools/dotc/typer/Inliner.scala | 6 +++--- tests/run/inline/Test_2.scala | 18 ++++++++++++++++++ .../inlines_1.scala} | 17 ++--------------- tests/run/inlinePower/Test_2.scala | 9 +++++++++ tests/run/inlinePower/power_1.scala | 13 +++++++++++++ 6 files changed, 52 insertions(+), 18 deletions(-) create mode 100644 tests/run/inline/Test_2.scala rename tests/run/{inlineTest.scala => inline/inlines_1.scala} (77%) create mode 100644 tests/run/inlinePower/Test_2.scala create mode 100644 tests/run/inlinePower/power_1.scala diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 56bb8498aa5b..11f2eddac132 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -7,6 +7,7 @@ import Contexts._, Symbols._, Types._, Scopes._, SymDenotations._, Names._, Name import StdNames._, Denotations._, Flags._, Constants._, Annotations._ import util.Positions._ import ast.{tpd, Trees, untpd} +import typer.Inliner import Trees._ import Decorators._ import TastyUnpickler._, TastyBuffer._, PositionPickler._ @@ -468,6 +469,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { val isClass = ttag == TEMPLATE val templateStart = currentAddr skipTree() // tpt + val rhsStart = currentAddr val rhsIsEmpty = noRhs(end) if (!rhsIsEmpty) skipTree() val (givenFlags, annots, privateWithin) = readModifiers(end) @@ -504,6 +506,11 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { sym.completer.withDecls(newScope) forkAt(templateStart).indexTemplateParams()(localContext(sym)) } + else (annots.find(_.symbol == defn.InlineAnnot)) match { + case Some(inlineAnnot) => + Inliner.attachBody(inlineAnnot, forkAt(rhsStart).readTerm()(localContext(sym))) + case none => + } goto(start) sym } diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 06eea9113503..68bc558601ba 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -82,12 +82,12 @@ object Inliner { | You can use -Xmax:inlines to change the limit.""") } - def dropInlined(tree: tpd.Inlined)(implicit ctx: Context): Tree = { + def dropInlined(inlined: tpd.Inlined)(implicit ctx: Context): Tree = { val reposition = new TreeMap { override def transform(tree: Tree)(implicit ctx: Context): Tree = - tree.withPos(tree.pos) + tree.withPos(inlined.call.pos) } - tpd.seq(tree.bindings, reposition.transform(tree.expansion)) + tpd.seq(inlined.bindings, reposition.transform(inlined.expansion)) } def inlineContext(tree: untpd.Inlined)(implicit ctx: Context): Context = diff --git a/tests/run/inline/Test_2.scala b/tests/run/inline/Test_2.scala new file mode 100644 index 000000000000..0d1723018d5d --- /dev/null +++ b/tests/run/inline/Test_2.scala @@ -0,0 +1,18 @@ +object Test { + + import p.inlines._ + + def main(args: Array[String]): Unit = { + println(f(10)) + println(f(f(10))) + + track("hello") { println("") } + + val o = new Outer + val i = new o.Inner + println(i.m) + //println(i.g) + //println(i.h) + } + +} diff --git a/tests/run/inlineTest.scala b/tests/run/inline/inlines_1.scala similarity index 77% rename from tests/run/inlineTest.scala rename to tests/run/inline/inlines_1.scala index 39153951e081..64adb031c9da 100644 --- a/tests/run/inlineTest.scala +++ b/tests/run/inline/inlines_1.scala @@ -1,6 +1,7 @@ +package p import collection.mutable -object Test { +object inlines { final val monitored = false @@ -38,18 +39,4 @@ object Test { @dotty.annotation.inline def h = f ++ m } } - - def main(args: Array[String]): Unit = { - println(f(10)) - println(f(f(10))) - - track("hello") { println("") } - - val o = new Outer - val i = new o.Inner - println(i.m) - //println(i.g) - //println(i.h) - } - } diff --git a/tests/run/inlinePower/Test_2.scala b/tests/run/inlinePower/Test_2.scala new file mode 100644 index 000000000000..8e16587b5653 --- /dev/null +++ b/tests/run/inlinePower/Test_2.scala @@ -0,0 +1,9 @@ +import p.pow.power +object Test { + + def main(args: Array[String]): Unit = { + println(power(2.0, 10)) + def x = 2.0 + println(power(x, 11)) + } +} diff --git a/tests/run/inlinePower/power_1.scala b/tests/run/inlinePower/power_1.scala new file mode 100644 index 000000000000..1faa10516f9f --- /dev/null +++ b/tests/run/inlinePower/power_1.scala @@ -0,0 +1,13 @@ +package p + +object pow { + + @dotty.annotation.inline + def power(x: Double, n: Int): Double = + if (n == 0) 1.0 + else if (n == 1) x + else { + val y = power(x, n / 2) + if (n % 2 == 0) y * y else y * y * x + } +} From 688cc890ceadb42f742579494a560159334c85aa Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 4 Sep 2016 14:58:01 +0200 Subject: [PATCH 14/77] Add check files --- tests/run/inline.check | 4 ++++ tests/run/inlinePower.check | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 tests/run/inline.check create mode 100644 tests/run/inlinePower.check diff --git a/tests/run/inline.check b/tests/run/inline.check new file mode 100644 index 000000000000..49d2e8c6c231 --- /dev/null +++ b/tests/run/inline.check @@ -0,0 +1,4 @@ +100 +10000 + + Inner diff --git a/tests/run/inlinePower.check b/tests/run/inlinePower.check new file mode 100644 index 000000000000..25e11563452f --- /dev/null +++ b/tests/run/inlinePower.check @@ -0,0 +1,2 @@ +1024.0 +2048.0 From 333ec27c9e503f428c86a155351d11f332f2892d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 4 Sep 2016 15:01:05 +0200 Subject: [PATCH 15/77] Set the positions of inlined trees wehn read form Tasty This required a major change in the way positions are handled, as the previous scheme did not allow to read the positions of arbitrary subtrees selectively. Fortunately, it's altogether a major simplification. Also, this fixed a bug in the previous scheme, where positions were generated before compactification, resulting in addresses being wrong. --- src/dotty/tools/dotc/FromTasty.scala | 2 +- src/dotty/tools/dotc/core/Mode.scala | 3 + .../dotc/core/tasty/DottyUnpickler.scala | 23 ++--- .../dotc/core/tasty/PositionPickler.scala | 92 ++++++++----------- .../dotc/core/tasty/PositionUnpickler.scala | 40 ++++---- .../tools/dotc/core/tasty/TastyFormat.scala | 23 ++--- .../tools/dotc/core/tasty/TastyPickler.scala | 1 - .../tools/dotc/core/tasty/TastyPrinter.scala | 4 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 76 +++++---------- src/dotty/tools/dotc/transform/Pickler.scala | 5 +- 10 files changed, 107 insertions(+), 162 deletions(-) diff --git a/src/dotty/tools/dotc/FromTasty.scala b/src/dotty/tools/dotc/FromTasty.scala index 05e97f30a5de..b060a2054090 100644 --- a/src/dotty/tools/dotc/FromTasty.scala +++ b/src/dotty/tools/dotc/FromTasty.scala @@ -86,7 +86,7 @@ object FromTasty extends Driver { case info: ClassfileLoader => info.load(clsd) match { case Some(unpickler: DottyUnpickler) => - val List(unpickled) = unpickler.body(readPositions = true) + val List(unpickled) = unpickler.body(ctx.addMode(Mode.ReadPositions)) val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.sourceFile, Seq())) unit1.tpdTree = unpickled unit1.unpicklers += (clsd.classSymbol -> unpickler.unpickler) diff --git a/src/dotty/tools/dotc/core/Mode.scala b/src/dotty/tools/dotc/core/Mode.scala index 3e9b7effe153..7a9bb0572625 100644 --- a/src/dotty/tools/dotc/core/Mode.scala +++ b/src/dotty/tools/dotc/core/Mode.scala @@ -89,5 +89,8 @@ object Mode { */ val AllowLambdaWildcardApply = newMode(15, "AllowHKApplyToWildcards") + /** Read original positions when unpickling from TASTY */ + val ReadPositions = newMode(16, "ReadPositions") + val PatternOrType = Pattern | Type } diff --git a/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala b/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala index 0ad5d69668c8..2c93819d5be9 100644 --- a/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala @@ -8,8 +8,8 @@ import dotty.tools.dotc.ast.tpd import TastyUnpickler._, TastyBuffer._ import util.Positions._ import util.{SourceFile, NoSource} -import PositionUnpickler._ import Annotations.Annotation +import core.Mode import classfile.ClassfileParser object DottyUnpickler { @@ -17,14 +17,15 @@ object DottyUnpickler { /** Exception thrown if classfile is corrupted */ class BadSignature(msg: String) extends RuntimeException(msg) - class TreeSectionUnpickler extends SectionUnpickler[TreeUnpickler]("ASTs") { + class TreeSectionUnpickler(posUnpickler: Option[PositionUnpickler]) + extends SectionUnpickler[TreeUnpickler]("ASTs") { def unpickle(reader: TastyReader, tastyName: TastyName.Table) = - new TreeUnpickler(reader, tastyName) + new TreeUnpickler(reader, tastyName, posUnpickler) } - class PositionsSectionUnpickler extends SectionUnpickler[(Position, AddrToPosition)]("Positions") { + class PositionsSectionUnpickler extends SectionUnpickler[PositionUnpickler]("Positions") { def unpickle(reader: TastyReader, tastyName: TastyName.Table) = - new PositionUnpickler(reader).unpickle() + new PositionUnpickler(reader) } } @@ -36,7 +37,8 @@ class DottyUnpickler(bytes: Array[Byte]) extends ClassfileParser.Embedded { import DottyUnpickler._ val unpickler = new TastyUnpickler(bytes) - private val treeUnpickler = unpickler.unpickle(new TreeSectionUnpickler).get + private val posUnpicklerOpt = unpickler.unpickle(new PositionsSectionUnpickler) + private val treeUnpickler = unpickler.unpickle(new TreeSectionUnpickler(posUnpicklerOpt)).get /** Enter all toplevel classes and objects into their scopes * @param roots a set of SymDenotations that should be overwritten by unpickling @@ -44,13 +46,8 @@ class DottyUnpickler(bytes: Array[Byte]) extends ClassfileParser.Embedded { def enter(roots: Set[SymDenotation])(implicit ctx: Context): Unit = treeUnpickler.enterTopLevel(roots) - /** The unpickled trees, and the source file they come from - * @param readPositions if true, trees get decorated with position information. - */ - def body(readPositions: Boolean = false)(implicit ctx: Context): List[Tree] = { - if (readPositions) - for ((totalRange, positions) <- unpickler.unpickle(new PositionsSectionUnpickler)) - treeUnpickler.usePositions(totalRange, positions) + /** The unpickled trees, and the source file they come from. */ + def body(implicit ctx: Context): List[Tree] = { treeUnpickler.unpickle() } } diff --git a/src/dotty/tools/dotc/core/tasty/PositionPickler.scala b/src/dotty/tools/dotc/core/tasty/PositionPickler.scala index b0550b70a8a2..63bb00a711c0 100644 --- a/src/dotty/tools/dotc/core/tasty/PositionPickler.scala +++ b/src/dotty/tools/dotc/core/tasty/PositionPickler.scala @@ -3,7 +3,8 @@ package dotc package core package tasty -import ast.tpd._ +import ast._ +import ast.Trees._ import ast.Trees.WithLazyField import TastyFormat._ import core._ @@ -12,64 +13,47 @@ import collection.mutable import TastyBuffer._ import util.Positions._ -object PositionPickler { - - trait DeferredPosition { - var parentPos: Position = NoPosition - } - - def traverse(x: Any, parentPos: Position, op: (Tree, Position) => Unit)(implicit ctx: Context): Unit = - if (parentPos.exists) - x match { - case x: Tree @unchecked => - op(x, parentPos) - x match { - case x: MemberDef @unchecked => traverse(x.symbol.annotations, x.pos, op) - case _ => - } - traverse(x.productIterator, x.pos, op) - case x: DeferredPosition => - x.parentPos = parentPos - case xs: TraversableOnce[_] => - xs.foreach(traverse(_, parentPos, op)) - case _ => - } -} -import PositionPickler._ - -class PositionPickler(pickler: TastyPickler, addrOfTree: Tree => Option[Addr]) { +class PositionPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr]) { val buf = new TastyBuffer(5000) pickler.newSection("Positions", buf) import buf._ + import ast.tpd._ + + def header(addrDelta: Int, hasStartDelta: Boolean, hasEndDelta: Boolean) = { + def toInt(b: Boolean) = if (b) 1 else 0 + (addrDelta << 2) | (toInt(hasStartDelta) << 1) | toInt(hasEndDelta) + } - def picklePositions(roots: List[Tree], totalRange: Position)(implicit ctx: Context) = { + def picklePositions(roots: List[Tree])(implicit ctx: Context) = { var lastIndex = 0 - def record(tree: Tree, parentPos: Position): Unit = - if (tree.pos.exists) { - def msg = s"failure to pickle $tree at ${tree.pos}, parent = $parentPos" - val endPos = tree.pos.end min parentPos.end - // end positions can be larger than their parents - // e.g. in the case of synthetic empty ranges, which are placed at the next token after - // the current construct. - val endDelta = endPos - parentPos.end - val startPos = - if (endDelta == 0) tree.pos.start max parentPos.start else tree.pos.start min endPos - // Since end positions are corrected above, start positions have to follow suit. - val startDelta = startPos - parentPos.start - if (startDelta != 0 || endDelta != 0) - for (addr <- addrOfTree(tree)) { - buf.writeInt(addr.index - lastIndex) - lastIndex = addr.index - if (startDelta != 0) buf.writeInt(startDelta) - if (endDelta != 0) { - assert(endDelta < 0, msg) - buf.writeInt(endDelta) - } else - assert(startDelta >= 0, msg) + var lastPos = Position(0, 0) + def pickleDeltas(index: Int, pos: Position) = { + val addrDelta = index - lastIndex + val startDelta = pos.start - lastPos.start + val endDelta = pos.end - lastPos.end + buf.writeInt(header(addrDelta, startDelta != 0, endDelta != 0)) + if (startDelta != 0) buf.writeInt(startDelta) + if (endDelta != 0) buf.writeInt(endDelta) + lastIndex = index + lastPos = pos + } + def traverse(x: Any, parentPos: Position): Unit = x match { + case x: Tree @unchecked => + if (x.pos.exists && x.pos.toSynthetic != parentPos.toSynthetic) { + addrOfTree(x) match { + case Some(addr) => pickleDeltas(addr.index, x.pos) + case _ => } - } - - buf.writeNat(totalRange.end) - traverse(roots, totalRange, record) + } + x match { + case x: MemberDef @unchecked => traverse(x.symbol.annotations, x.pos) + case _ => + } + traverse(x.productIterator, x.pos) + case xs: TraversableOnce[_] => + xs.foreach(traverse(_, parentPos)) + case _ => + } + traverse(roots, NoPosition) } } diff --git a/src/dotty/tools/dotc/core/tasty/PositionUnpickler.scala b/src/dotty/tools/dotc/core/tasty/PositionUnpickler.scala index fa80a276980d..c29aeba704c3 100644 --- a/src/dotty/tools/dotc/core/tasty/PositionUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/PositionUnpickler.scala @@ -6,33 +6,31 @@ package tasty import util.Positions._ import collection.mutable -import TastyBuffer.Addr - -object PositionUnpickler { - type AddrToPosition = mutable.HashMap[Addr, Position] -} +import TastyBuffer.{Addr, NoAddr} /** Unpickler for tree positions */ class PositionUnpickler(reader: TastyReader) { - import PositionUnpickler._ import reader._ - def unpickle(): (Position, AddrToPosition) = { - val positions = new mutable.HashMap[Addr, Position] // Dotty deviation: Can't use new AddrToPosition here. TODO: fix this! - val sourceLength = readNat() - def readDelta() = if (isAtEnd) 0 else readInt() - var curIndex: Addr = Addr(readDelta()) + private[tasty] lazy val positions = { + val positions = new mutable.HashMap[Addr, Position] + var curIndex = 0 + var curStart = 0 + var curEnd = 0 while (!isAtEnd) { - val delta1 = readDelta() - val delta2 = readDelta() - val (startDelta, endDelta, indexDelta) = - if (delta2 <= 0) (delta1, -delta2, readDelta()) - else if (delta1 < 0) (0, -delta1, delta2) - else (delta1, 0, delta2) - positions(curIndex) = Position(startDelta, endDelta, startDelta) - // make non-synthetic position; will be made synthetic by normalization. - curIndex += indexDelta + val header = readInt() + val addrDelta = header >> 2 + val hasStart = (header & 2) != 0 + val hasEnd = (header & 1) != 0 + curIndex += addrDelta + assert(curIndex >= 0) + if (hasStart) curStart += readInt() + if (hasEnd) curEnd += readInt() + positions(Addr(curIndex)) = Position(curStart, curEnd) } - (Position(0, sourceLength), positions) + positions } + + def posAt(addr: Addr) = positions.getOrElse(addr, NoPosition) } + diff --git a/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index e9de68e7fb55..8e8d58b47a56 100644 --- a/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -185,21 +185,16 @@ Note: Tree tags are grouped into 5 categories that determine what follows, and t Category 4 (tags 112-127): tag Nat AST Category 5 (tags 128-255): tag Length -Standard Section: "Positions" sourceLength_Nat Assoc* - - Assoc = addr_Delta offset_Delta offset_Delta? - // addr_Delta : - // Difference of address to last recorded node. - // All but the first addr_Deltas are > 0, the first is >= 0. - // 2nd offset_Delta: - // Difference of end offset of addressed node vs parent node. Always <= 0 - // 1st offset Delta, if delta >= 0 or 2nd offset delta exists - // Difference of start offset of addressed node vs parent node. - // 1st offset Delta, if delta < 0 and 2nd offset delta does not exist: - // Difference of end offset of addressed node vs parent node. - // Offsets and addresses are difference encoded. +Standard Section: "Positions" Assoc* + + Assoc = Header offset_Delta? offset_Delta? + Header = addr_Delta + // in one Nat: difference of address to last recorded node << 2 + + hasStartDiff + // one bit indicating whether there follows a start address delta << 1 + hasEndDiff // one bit indicating whether there follows an end address delta // Nodes which have the same positions as their parents are omitted. - Delta = Int // Difference between consecutive offsets / tree addresses, + // offset_Deltas give difference of start/end offset wrt to the + // same offset in the previously recorded node (or 0 for the first recorded node) + Delta = Int // Difference between consecutive offsets, **************************************************************************************/ diff --git a/src/dotty/tools/dotc/core/tasty/TastyPickler.scala b/src/dotty/tools/dotc/core/tasty/TastyPickler.scala index 83e6020d5adc..98b0dc7c6bb3 100644 --- a/src/dotty/tools/dotc/core/tasty/TastyPickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TastyPickler.scala @@ -31,7 +31,6 @@ class TastyPickler { sections += ((nameBuffer.nameIndex(name), buf)) def assembleParts(): Array[Byte] = { - treePkl.compactify() def lengthWithLength(buf: TastyBuffer) = { buf.assemble() buf.length + natSize(buf.length) diff --git a/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala b/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala index 915ae3f21dad..7fcd7c29eff1 100644 --- a/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala +++ b/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala @@ -113,8 +113,8 @@ class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) { class PositionSectionUnpickler extends SectionUnpickler[Unit]("Positions") { def unpickle(reader: TastyReader, tastyName: TastyName.Table): Unit = { print(s"${reader.endAddr.index - reader.currentAddr.index}") - val (totalRange, positions) = new PositionUnpickler(reader).unpickle() - println(s" position bytes in $totalRange:") + val positions = new PositionUnpickler(reader).positions + println(s" position bytes:") val sorted = positions.toSeq.sortBy(_._1.index) for ((addr, pos) <- sorted) println(s"${addr.index}: ${offsetToInt(pos.start)} .. ${pos.end}") } diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 11f2eddac132..60cad0445365 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -10,38 +10,23 @@ import ast.{tpd, Trees, untpd} import typer.Inliner import Trees._ import Decorators._ -import TastyUnpickler._, TastyBuffer._, PositionPickler._ +import TastyUnpickler._, TastyBuffer._ import scala.annotation.{tailrec, switch} import scala.collection.mutable.ListBuffer import scala.collection.{ mutable, immutable } import config.Printers.pickling /** Unpickler for typed trees - * @param reader the reader from which to unpickle - * @param tastyName the nametable + * @param reader the reader from which to unpickle + * @param tastyName the nametable + * @param posUNpicklerOpt the unpickler for positions, if it exists */ -class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { +class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, posUnpicklerOpt: Option[PositionUnpickler]) { import TastyFormat._ import TastyName._ import TreeUnpickler._ import tpd._ - private var readPositions = false - private var totalRange = NoPosition - private var positions: collection.Map[Addr, Position] = _ - - /** Make a subsequent call to `unpickle` return trees with positions - * @param totalRange the range position enclosing all returned trees, - * or NoPosition if positions should not be unpickled - * @param positions a map from tree addresses to their positions relative - * to positions of parent nodes. - */ - def usePositions(totalRange: Position, positions: collection.Map[Addr, Position]): Unit = { - readPositions = true - this.totalRange = totalRange - this.positions = positions - } - /** A map from addresses of definition entries to the symbols they define */ private val symAtAddr = new mutable.HashMap[Addr, Symbol] @@ -86,10 +71,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { /** The unpickled trees */ def unpickle()(implicit ctx: Context): List[Tree] = { assert(roots != null, "unpickle without previous enterTopLevel") - val stats = new TreeReader(reader) - .readTopLevel()(ctx.addMode(Mode.AllowDependentFunctions)) - normalizePos(stats, totalRange) - stats + new TreeReader(reader).readTopLevel()(ctx.addMode(Mode.AllowDependentFunctions)) } def toTermName(tname: TastyName): TermName = tname match { @@ -508,7 +490,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { } else (annots.find(_.symbol == defn.InlineAnnot)) match { case Some(inlineAnnot) => - Inliner.attachBody(inlineAnnot, forkAt(rhsStart).readTerm()(localContext(sym))) + Inliner.attachBody(inlineAnnot, + forkAt(rhsStart).readTerm()(localContext(sym).addMode(Mode.ReadPositions))) case none => } goto(start) @@ -1017,44 +1000,29 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { new LazyReader(localReader, op) } -// ------ Hooks for positions ------------------------------------------------ +// ------ Setting positions ------------------------------------------------ - /** Record address from which tree was created as a temporary position in the tree. - * The temporary position contains deltas relative to the position of the (as yet unknown) - * parent node. It is marked as a non-synthetic source position. - */ - def setPos[T <: Tree](addr: Addr, tree: T): T = { - if (readPositions) - tree.setPosUnchecked(positions.getOrElse(addr, Position(0, 0, 0))) - tree - } + /** Set position of `tree` at given `addr`. */ + def setPos[T <: Tree](addr: Addr, tree: T)(implicit ctx: Context): tree.type = + if (ctx.mode.is(Mode.ReadPositions)) { + posUnpicklerOpt match { + case Some(posUnpickler) => tree.withPos(posUnpickler.posAt(addr)) + case _ => tree + } + } + else tree } - private def setNormalized(tree: Tree, parentPos: Position): Unit = - tree.setPosUnchecked( - if (tree.pos.exists) - Position(parentPos.start + offsetToInt(tree.pos.start), parentPos.end - tree.pos.end) - else - parentPos) - - def normalizePos(x: Any, parentPos: Position)(implicit ctx: Context): Unit = - traverse(x, parentPos, setNormalized) - - class LazyReader[T <: AnyRef](reader: TreeReader, op: TreeReader => Context => T) extends Trees.Lazy[T] with DeferredPosition { + class LazyReader[T <: AnyRef](reader: TreeReader, op: TreeReader => Context => T) extends Trees.Lazy[T] { def complete(implicit ctx: Context): T = { pickling.println(i"starting to read at ${reader.reader.currentAddr}") - val res = op(reader)(ctx.addMode(Mode.AllowDependentFunctions).withPhaseNoLater(ctx.picklerPhase)) - normalizePos(res, parentPos) - res + op(reader)(ctx.addMode(Mode.AllowDependentFunctions).withPhaseNoLater(ctx.picklerPhase)) } } - class LazyAnnotationReader(sym: Symbol, reader: TreeReader) - extends LazyAnnotation(sym) with DeferredPosition { + class LazyAnnotationReader(sym: Symbol, reader: TreeReader) extends LazyAnnotation(sym) { def complete(implicit ctx: Context) = { - val res = reader.readTerm()(ctx.withPhaseNoLater(ctx.picklerPhase)) - normalizePos(res, parentPos) - res + reader.readTerm()(ctx.withPhaseNoLater(ctx.picklerPhase)) } } diff --git a/src/dotty/tools/dotc/transform/Pickler.scala b/src/dotty/tools/dotc/transform/Pickler.scala index 4bcc90a41a0f..90e62b65c82e 100644 --- a/src/dotty/tools/dotc/transform/Pickler.scala +++ b/src/dotty/tools/dotc/transform/Pickler.scala @@ -45,10 +45,11 @@ class Pickler extends Phase { unit.picklers += (cls -> pickler) val treePkl = pickler.treePkl treePkl.pickle(tree :: Nil) + treePkl.compactify() pickler.addrOfTree = treePkl.buf.addrOfTree pickler.addrOfSym = treePkl.addrOfSym if (tree.pos.exists) - new PositionPickler(pickler, treePkl.buf.addrOfTree).picklePositions(tree :: Nil, tree.pos) + new PositionPickler(pickler, treePkl.buf.addrOfTree).picklePositions(tree :: Nil) def rawBytes = // not needed right now, but useful to print raw format. pickler.assembleParts().iterator.grouped(10).toList.zipWithIndex.map { @@ -80,7 +81,7 @@ class Pickler extends Phase { } pickling.println("************* entered toplevel ***********") for ((cls, unpickler) <- unpicklers) { - val unpickled = unpickler.body(readPositions = true) + val unpickled = unpickler.body(ctx.addMode(Mode.ReadPositions)) testSame(i"$unpickled%\n%", beforePickling(cls), cls) } } From c1674dc461ee3f4bf38b7b895cfc9d7d6b41b53f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 4 Sep 2016 15:10:44 +0200 Subject: [PATCH 16/77] Neg test demonstrating inline positions under cross-compilation --- tests/neg/inlineAccess/C_1.scala | 8 ++++++++ tests/neg/inlineAccess/Test_2.scala | 7 +++++++ 2 files changed, 15 insertions(+) create mode 100644 tests/neg/inlineAccess/C_1.scala create mode 100644 tests/neg/inlineAccess/Test_2.scala diff --git a/tests/neg/inlineAccess/C_1.scala b/tests/neg/inlineAccess/C_1.scala new file mode 100644 index 000000000000..fc24f7666e21 --- /dev/null +++ b/tests/neg/inlineAccess/C_1.scala @@ -0,0 +1,8 @@ +package p { +class C { + protected def f(): Unit = () + + @dotty.annotation.inline + def inl() = f() // error (when inlined): not accessible +} +} diff --git a/tests/neg/inlineAccess/Test_2.scala b/tests/neg/inlineAccess/Test_2.scala new file mode 100644 index 000000000000..98ea7693abb7 --- /dev/null +++ b/tests/neg/inlineAccess/Test_2.scala @@ -0,0 +1,7 @@ + +object Test { + def main(args: Array[String]) = { + val c = new p.C() + c.inl() + } +} From a81070ca1af106688b03c46d96f4266934d8cfbb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 4 Sep 2016 18:18:57 +0200 Subject: [PATCH 17/77] Fix some problems in Inliner 1. Don't retypecheck the arguments of an inlined epressions. These might be very large (e.g. inlined track, or traceIndented in dotty)/ 2. Keep track of inlined calls in context instead of Inlined nodes. We only need the to compute the source file, the rest is irrelevant. 3. In Def bindings of inlined by-name parameters, change owner of right hand side. Otherwise we get incorrect owner chains. 4. In TreeTypeMap, treat Inlined in the same way as a block. --- src/dotty/tools/dotc/ast/TreeTypeMap.scala | 4 ++ src/dotty/tools/dotc/core/Decorators.scala | 8 ++-- src/dotty/tools/dotc/typer/Inliner.scala | 46 +++++++++++----------- src/dotty/tools/dotc/typer/Typer.scala | 2 +- 4 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/src/dotty/tools/dotc/ast/TreeTypeMap.scala index a35fe2e8ffe8..0593e8159046 100644 --- a/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -97,6 +97,10 @@ final class TreeTypeMap( val (tmap1, stats1) = transformDefs(stats) val expr1 = tmap1.transform(expr) cpy.Block(blk)(stats1, expr1) + case inlined @ Inlined(call, bindings, expanded) => + val (tmap1, bindings1) = transformDefs(bindings) + val expanded1 = tmap1.transform(expanded) + cpy.Inlined(inlined)(call, bindings1, expanded1) case cdef @ CaseDef(pat, guard, rhs) => val tmap = withMappedSyms(patVars(pat)) val pat1 = tmap.transform(pat) diff --git a/src/dotty/tools/dotc/core/Decorators.scala b/src/dotty/tools/dotc/core/Decorators.scala index cd4941c724fa..64f50173c979 100644 --- a/src/dotty/tools/dotc/core/Decorators.scala +++ b/src/dotty/tools/dotc/core/Decorators.scala @@ -8,7 +8,7 @@ import util.Positions.Position, util.SourcePosition import collection.mutable.ListBuffer import dotty.tools.dotc.transform.TreeTransforms._ import typer.Inliner -import ast.tpd.Inlined +import ast.tpd.Tree import scala.language.implicitConversions import printing.Formatting._ @@ -151,9 +151,9 @@ object Decorators { } implicit def sourcePos(pos: Position)(implicit ctx: Context): SourcePosition = { - def recur(inlineds: List[Inlined], pos: Position): SourcePosition = inlineds match { - case inlined :: rest => - Inliner.sourceFile(inlined).atPos(pos).withOuter(recur(rest, inlined.call.pos)) + def recur(inlinedCalls: List[Tree], pos: Position): SourcePosition = inlinedCalls match { + case inlinedCall :: rest => + Inliner.sourceFile(inlinedCall).atPos(pos).withOuter(recur(rest, inlinedCall.pos)) case empty => ctx.source.atPos(pos) } diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 68bc558601ba..88d8b0de5d2f 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -31,7 +31,7 @@ object Inliner { private val InlinedBody = new Property.Key[InlinedBody] // to be used as attachment - private val InlinedCall = new Property.Key[List[tpd.Inlined]] // to be used in context + private val InlinedCalls = new Property.Key[List[Tree]] // to be used in context def attachBody(inlineAnnot: Annotation, tree: => Tree)(implicit ctx: Context): Unit = inlineAnnot.tree.putAttachment(InlinedBody, new InlinedBody(tree)) @@ -71,16 +71,13 @@ object Inliner { } } - def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { - if (enclosingInlineds.length < ctx.settings.xmaxInlines.value) { - val rhs = inlinedBody(tree.symbol) - val inlined = new Inliner(tree, rhs).inlined - new Typer().typedUnadapted(inlined, pt) - } else errorTree(tree, + def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = + if (enclosingInlineds.length < ctx.settings.xmaxInlines.value) + new Inliner(tree, inlinedBody(tree.symbol)).inlined(pt) + else errorTree(tree, i"""Maximal number of successive inlines (${ctx.settings.xmaxInlines.value}) exceeded, | Maybe this is caused by a recursive inline method? | You can use -Xmax:inlines to change the limit.""") - } def dropInlined(inlined: tpd.Inlined)(implicit ctx: Context): Tree = { val reposition = new TreeMap { @@ -90,20 +87,21 @@ object Inliner { tpd.seq(inlined.bindings, reposition.transform(inlined.expansion)) } - def inlineContext(tree: untpd.Inlined)(implicit ctx: Context): Context = - ctx.fresh.setProperty(InlinedCall, tree :: enclosingInlineds) + def inlineContext(call: Tree)(implicit ctx: Context): Context = + ctx.fresh.setProperty(InlinedCalls, call :: enclosingInlineds) - def enclosingInlineds(implicit ctx: Context): List[Inlined] = - ctx.property(InlinedCall).getOrElse(Nil) + def enclosingInlineds(implicit ctx: Context): List[Tree] = + ctx.property(InlinedCalls).getOrElse(Nil) - def sourceFile(inlined: Inlined)(implicit ctx: Context) = { - val file = inlined.call.symbol.sourceFile + def sourceFile(call: Tree)(implicit ctx: Context) = { + val file = call.symbol.sourceFile if (file.exists) new SourceFile(file) else NoSource } } class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { import tpd._ + import Inliner._ private def decomposeCall(tree: Tree): (Tree, List[Tree], List[List[Tree]]) = tree match { case Apply(fn, args) => @@ -154,10 +152,12 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { case argtpe => val (bindingFlags, bindingType) = if (isByName) (Method, ExprType(argtpe.widen)) else (EmptyFlags, argtpe.widen) - val binding = newSym(name, bindingFlags, bindingType).asTerm - bindingsBuf += - (if (isByName) DefDef(binding, arg) else ValDef(binding, arg)) - binding.termRef + val boundSym = newSym(name, bindingFlags, bindingType).asTerm + val binding = + if (isByName) DefDef(boundSym, arg.changeOwner(ctx.owner, boundSym)) + else ValDef(boundSym, arg) + bindingsBuf += binding + boundSym.termRef } } computeParamBindings(tp.resultType, targs, argss.tail) @@ -192,7 +192,7 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { private def outerLevel(sym: Symbol) = sym.name.drop(nme.SELF.length).toString.toInt - val inlined = { + def inlined(pt: Type) = { rhs.foreachSubTree(registerLeaf) val accessedSelfSyms = @@ -236,9 +236,11 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { } val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil) - val Block(bindings: List[MemberDef], expansion) = - inliner(Block(bindingsBuf.toList, rhs)).withPos(call.pos) - val result = tpd.Inlined(call, bindings, expansion) + val bindings = bindingsBuf.toList.map(_.withPos(call.pos)) + val expansion = inliner(rhs.withPos(call.pos)) + + val expansion1 = new Typer().typed(expansion, pt)(inlineContext(call)) + val result = tpd.Inlined(call, bindings, expansion1) inlining.println(i"inlining $call\n --> \n$result") result diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index e809605f48cc..09259b361865 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -947,7 +947,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context): Inlined = { val (exprCtx, bindings1) = typedBlockStats(tree.bindings) - val expansion1 = typed(tree.expansion, pt)(Inliner.inlineContext(tree)(exprCtx)) + val expansion1 = typed(tree.expansion, pt)(Inliner.inlineContext(tree.call)(exprCtx)) cpy.Inlined(tree)(tree.call, bindings1.asInstanceOf[List[MemberDef]], expansion1) .withType(expansion1.tpe) } From 379bb24e55d88434faa32d2d324fcadb42af960d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 4 Sep 2016 18:25:43 +0200 Subject: [PATCH 18/77] Remove redundanr test Same test with separate compilation is in inlinePower --- tests/run/power.scala | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 tests/run/power.scala diff --git a/tests/run/power.scala b/tests/run/power.scala deleted file mode 100644 index 2bb66fa8df86..000000000000 --- a/tests/run/power.scala +++ /dev/null @@ -1,17 +0,0 @@ -object Test { - - @dotty.annotation.inline - def power(x: Double, n: Int): Double = - if (n == 0) 1.0 - else if (n == 1) x - else { - val y = power(x, n / 2) - if (n % 2 == 0) y * y else y * y * x - } - - def main(args: Array[String]): Unit = { - println(power(2.0, 10)) - def x = 2.0 - println(power(x, 11)) - } -} From be53234352f75aca456ed131163b45230e4638af Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 4 Sep 2016 18:39:50 +0200 Subject: [PATCH 19/77] Add comment missing from last PR --- src/dotty/tools/dotc/core/TyperState.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index 7b8867ccc120..3ed4788ee967 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -122,6 +122,18 @@ extends TyperState(r) { * type variables changes from this typer state to the current one. (2) Variables * that were temporarily instantiated in the current typer state are permanently * instantiated instead. + * + * A note on merging: A case is in isApplicableSafe.scala. It turns out that this + * requires a context merge using the new `&' operator. Sequence of actions: + * 1) Typecheck argument in typerstate 1. + * 2) Cache argument. + * 3) Evolve same typer state (to typecheck other arguments, say) + * leading to a different constraint. + * 4) Take typechecked argument in same state. + * + * It turns out that the merge is needed not just for + * isApplicableSafe but also for (e.g. erased-lubs.scala) as well as + * many parts of dotty itself. */ override def commit()(implicit ctx: Context) = { val targetState = ctx.typerState From 9a48e7bc56979c5968149e6aabdae96d33681a60 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 4 Sep 2016 18:41:22 +0200 Subject: [PATCH 20/77] Don't inline after typer. Safety measure: Inline only during typer, not when code is generated in later phases. --- src/dotty/tools/dotc/typer/Typer.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 09259b361865..3672996c87a3 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1793,7 +1793,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit tree } else if (tree.tpe <:< pt) - if (tree.symbol.isInlineMethod && !ctx.owner.ownersIterator.exists(_.isInlineMethod)) + if (tree.symbol.isInlineMethod && + !ctx.owner.ownersIterator.exists(_.isInlineMethod) && + !ctx.isAfterTyper) adapt(Inliner.inlineCall(tree, pt), pt) else if (ctx.typeComparer.GADTused && pt.isValueType) // Insert an explicit cast, so that -Ycheck in later phases succeeds. From 7cce2d463a001f5f5660a391a71bddd2cde1a15f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 4 Sep 2016 18:48:08 +0200 Subject: [PATCH 21/77] Harden Inliner.sourceFile --- src/dotty/tools/dotc/typer/Inliner.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 88d8b0de5d2f..fbb847e9d592 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -95,7 +95,7 @@ object Inliner { def sourceFile(call: Tree)(implicit ctx: Context) = { val file = call.symbol.sourceFile - if (file.exists) new SourceFile(file) else NoSource + if (file != null && file.exists) new SourceFile(file) else NoSource } } From 0bd955e7780c95d41a0b6c4b7ca221f00e3cfd92 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Sep 2016 11:48:31 +0200 Subject: [PATCH 22/77] Drop Inlined when homogenize Pickler drops Inlined nodes, so homogenize needs to do the same. --- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 53d9750f8f14..fd79b8c501b9 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -7,7 +7,7 @@ import TypeErasure.ErasedValueType import Contexts.Context, Scopes.Scope, Denotations._, SymDenotations._, Annotations.Annotation import StdNames.{nme, tpnme} import ast.{Trees, untpd, tpd} -import typer.Namer +import typer.{Namer, Inliner} import typer.ProtoTypes.{SelectionProto, ViewProto, FunProto, IgnoredProto, dummyTreeOfType} import Trees._ import TypeApplications._ @@ -36,6 +36,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } } + def homogenize(tree: Tree[_])(implicit ctx: Context) = tree match { + case tree: tpd.Inlined => Inliner.dropInlined(tree) + case _ => tree + } + private def enclDefIsClass = enclosingDef match { case owner: TypeDef[_] => owner.isClassDef case owner: untpd.ModuleDef => true @@ -277,7 +282,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (homogenizedView && pid.hasType) toTextLocal(pid.tpe) else toTextLocal(pid) - var txt: Text = tree match { + var txt: Text = homogenize(tree) match { case id: Trees.BackquotedIdent[_] if !homogenizedView => "`" ~ toText(id.name) ~ "`" case Ident(name) => From 1d932642eaed2ec9829be951f3272d32b4393a39 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Sep 2016 11:51:14 +0200 Subject: [PATCH 23/77] Handle outer this in Inliner Also, do some refactorings and fix some bugs in Inliner. --- src/dotty/tools/dotc/core/NameOps.scala | 1 + src/dotty/tools/dotc/core/StdNames.scala | 2 +- .../tools/dotc/transform/ExplicitOuter.scala | 6 ++ .../tools/dotc/transform/FirstTransform.scala | 4 +- .../tools/dotc/transform/PatternMatcher.scala | 1 + .../tools/dotc/transform/SuperAccessors.scala | 2 +- src/dotty/tools/dotc/typer/Inliner.scala | 58 +++++++++---------- tests/run/inline/Test_2.scala | 7 ++- tests/run/inline/inlines_1.scala | 1 + 9 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index ea255e5b3de6..ddb0421bb4ad 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -84,6 +84,7 @@ object NameOps { name.stripAnonNumberSuffix endsWith MODULE_VAR_SUFFIX def isSelectorName = name.startsWith(" ") && name.tail.forall(_.isDigit) def isLazyLocal = name.endsWith(nme.LAZY_LOCAL) + def isOuterSelect = name.endsWith(nme.OUTER_SELECT) /** Is name a variable name? */ def isVariableName: Boolean = name.length > 0 && { diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index f47ab17449e9..fc26e05365e8 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -252,7 +252,7 @@ object StdNames { val MODULE_INSTANCE_FIELD: N = NameTransformer.MODULE_INSTANCE_NAME // "MODULE$" val OUTER: N = "$outer" val OUTER_LOCAL: N = "$outer " - val OUTER_SYNTH: N = "" // emitted by virtual pattern matcher, replaced by outer accessor in explicitouter + val OUTER_SELECT: N = "_" // emitted by inliner, replaced by outer path in explicitouter val REFINE_CLASS: N = "" val ROOTPKG: N = "_root_" val SELECTOR_DUMMY: N = "" diff --git a/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/src/dotty/tools/dotc/transform/ExplicitOuter.scala index 60ef1b3061da..3f235dca7bcc 100644 --- a/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -57,6 +57,12 @@ class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransf override def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = sym.isClass + /** Convert a selection of the form `qual.C_` to an outer path from `qual` to `C` */ + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = + if (tree.name.isOuterSelect) + outer.path(tree.tpe.widen.classSymbol, tree.qualifier).ensureConforms(tree.tpe) + else tree + /** First, add outer accessors if a class does not have them yet and it references an outer this. * If the class has outer accessors, implement them. * Furthermore, if a parent trait might have an outer accessor, diff --git a/src/dotty/tools/dotc/transform/FirstTransform.scala b/src/dotty/tools/dotc/transform/FirstTransform.scala index 6e1fed6078cc..74dc9b9d6f5a 100644 --- a/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -72,8 +72,8 @@ class FirstTransform extends MiniPhaseTransform with InfoTransformer with Annota override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { tree match { - case Select(qual, _) if tree.symbol.exists => - assert(qual.tpe derivesFrom tree.symbol.owner, i"non member selection of ${tree.symbol.showLocated} from ${qual.tpe}") + case Select(qual, name) if !name.isOuterSelect && tree.symbol.exists => + assert(qual.tpe derivesFrom tree.symbol.owner, i"non member selection of ${tree.symbol.showLocated} from ${qual.tpe} in $tree") case _: TypeTree => case _: Import | _: NamedArg | _: TypTree => assert(false, i"illegal tree: $tree") diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 490feb7d0fb4..49c0eabec779 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -782,6 +782,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer { val expectedClass = expectedTp.dealias.classSymbol.asClass val test = codegen._asInstanceOf(testedBinder, expectedTp) + // TODO: Use nme.OUTER_SELECT, like the Inliner does? val outerAccessorTested = ctx.atPhase(ctx.explicitOuterPhase.next) { implicit ctx => ExplicitOuter.ensureOuterAccessors(expectedClass) test.select(ExplicitOuter.outerAccessor(expectedClass)).select(defn.Object_eq).appliedTo(expectedOuter) diff --git a/src/dotty/tools/dotc/transform/SuperAccessors.scala b/src/dotty/tools/dotc/transform/SuperAccessors.scala index 6af991f27fce..10be6db6507e 100644 --- a/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -148,7 +148,7 @@ class SuperAccessors(thisTransformer: DenotTransformer) { */ private def ensureProtectedAccessOK(sel: Select, targs: List[Tree])(implicit ctx: Context) = { val sym = sel.symbol - if (sym.exists && needsProtectedAccessor(sym, sel.pos)) { + if (sym.isTerm && !sel.name.isOuterSelect && needsProtectedAccessor(sym, sel.pos)) { ctx.debuglog("Adding protected accessor for " + sel) protectedAccessorCall(sel, targs) } else sel diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index fbb847e9d592..970d31e474eb 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -40,7 +40,7 @@ object Inliner { sym.getAnnotation(defn.InlineAnnot).get.tree .attachment(InlinedBody).body - private class Typer extends ReTyper { + private object InlineTyper extends ReTyper { override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { val acc = tree.symbol super.typedSelect(tree, pt) match { @@ -117,12 +117,12 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { private val (methPart, targs, argss) = decomposeCall(call) private val meth = methPart.symbol - private lazy val prefix = methPart match { + private val prefix = methPart match { case Select(qual, _) => qual case _ => tpd.This(ctx.owner.enclosingClass.asClass) } - private val thisProxy = new mutable.HashMap[Type, NamedType] + private val thisProxy = new mutable.HashMap[Type, TermRef] private val paramProxy = new mutable.HashMap[Type, Type] private val paramBinding = new mutable.HashMap[Name, Type] val bindingsBuf = new mutable.ListBuffer[MemberDef] @@ -167,20 +167,20 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { } private def registerType(tpe: Type): Unit = tpe match { - case tpe: ThisType if !thisProxy.contains(tpe) => - if (!ctx.owner.isContainedIn(tpe.cls) && !tpe.cls.is(Package)) - if (tpe.cls.isStaticOwner) - thisProxy(tpe) = tpe.cls.sourceModule.termRef - else { - def outerDistance(cls: Symbol): Int = { - assert(cls.exists, i"not encl: ${meth.owner.enclosingClass} ${tpe.cls}") - if (tpe.cls eq cls) 0 - else outerDistance(cls.owner.enclosingClass) + 1 - } - val n = outerDistance(meth.owner) - thisProxy(tpe) = newSym(nme.SELF ++ n.toString, EmptyFlags, tpe.widen).termRef - } - case tpe: NamedType if tpe.symbol.is(Param) && tpe.symbol.owner == meth && !paramProxy.contains(tpe) => + case tpe: ThisType + if !ctx.owner.isContainedIn(tpe.cls) && !tpe.cls.is(Package) && + !thisProxy.contains(tpe) => + if (tpe.cls.isStaticOwner) + thisProxy(tpe) = tpe.cls.sourceModule.termRef + else { + val proxyName = s"${tpe.cls.name}_this".toTermName + val proxyType = tpe.asSeenFrom(prefix.tpe, meth.owner) + thisProxy(tpe) = newSym(proxyName, EmptyFlags, proxyType).termRef + registerType(meth.owner.thisType) // make sure we have a base from which to outer-select + } + case tpe: NamedType + if tpe.symbol.is(Param) && tpe.symbol.owner == meth && + !paramProxy.contains(tpe) => paramProxy(tpe) = paramBinding(tpe.name) case _ => } @@ -190,25 +190,23 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { case _ => } - private def outerLevel(sym: Symbol) = sym.name.drop(nme.SELF.length).toString.toInt - def inlined(pt: Type) = { + if (!isIdempotentExpr(prefix)) registerType(meth.owner.thisType) // make sure prefix is computed rhs.foreachSubTree(registerLeaf) - val accessedSelfSyms = - (for ((tp: ThisType, ref) <- thisProxy) yield ref.symbol.asTerm).toSeq.sortBy(outerLevel) + def classOf(sym: Symbol) = sym.info.widen.classSymbol + def outerSelector(sym: Symbol) = classOf(sym).name.toTermName ++ nme.OUTER_SELECT + def outerLevel(sym: Symbol) = classOf(sym).ownersIterator.length + val accessedSelfSyms = thisProxy.values.toList.map(_.symbol).sortBy(-outerLevel(_)) var lastSelf: Symbol = NoSymbol for (selfSym <- accessedSelfSyms) { val rhs = - if (!lastSelf.exists) prefix - else { - val outerDelta = outerLevel(selfSym) - outerLevel(lastSelf) - def outerSelect(ref: Tree, dummy: Int): Tree = ??? - //ref.select(ExplicitOuter.outerAccessorTBD(ref.tpe.widen.classSymbol.asClass)) - (ref(lastSelf) /: (0 until outerDelta))(outerSelect) - } - bindingsBuf += ValDef(selfSym, rhs.ensureConforms(selfSym.info)) + if (!lastSelf.exists) + prefix + else + untpd.Select(ref(lastSelf), outerSelector(selfSym)).withType(selfSym.info) + bindingsBuf += ValDef(selfSym.asTerm, rhs) lastSelf = selfSym } @@ -239,7 +237,7 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { val bindings = bindingsBuf.toList.map(_.withPos(call.pos)) val expansion = inliner(rhs.withPos(call.pos)) - val expansion1 = new Typer().typed(expansion, pt)(inlineContext(call)) + val expansion1 = InlineTyper.typed(expansion, pt)(inlineContext(call)) val result = tpd.Inlined(call, bindings, expansion1) inlining.println(i"inlining $call\n --> \n$result") diff --git a/tests/run/inline/Test_2.scala b/tests/run/inline/Test_2.scala index 0d1723018d5d..605868c80164 100644 --- a/tests/run/inline/Test_2.scala +++ b/tests/run/inline/Test_2.scala @@ -11,8 +11,11 @@ object Test { val o = new Outer val i = new o.Inner println(i.m) - //println(i.g) - //println(i.h) + println(i.g) + println(i.h) + println(o.inner.m) + println(o.inner.g) + println(o.inner.h) } } diff --git a/tests/run/inline/inlines_1.scala b/tests/run/inline/inlines_1.scala index 64adb031c9da..36f5ac402ad9 100644 --- a/tests/run/inline/inlines_1.scala +++ b/tests/run/inline/inlines_1.scala @@ -38,5 +38,6 @@ object inlines { @dotty.annotation.inline def g = f @dotty.annotation.inline def h = f ++ m } + val inner = new Inner } } From ba6bc75fe70d0bb6a9096fe545ee63421b34ed8a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Sep 2016 11:57:42 +0200 Subject: [PATCH 24/77] Add test for pattern matching against outer --- tests/run/outerPatternMatch/Outer_1.scala | 6 ++++++ tests/run/outerPatternMatch/Test_2.scala | 14 ++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 tests/run/outerPatternMatch/Outer_1.scala create mode 100644 tests/run/outerPatternMatch/Test_2.scala diff --git a/tests/run/outerPatternMatch/Outer_1.scala b/tests/run/outerPatternMatch/Outer_1.scala new file mode 100644 index 000000000000..c3b102323cc0 --- /dev/null +++ b/tests/run/outerPatternMatch/Outer_1.scala @@ -0,0 +1,6 @@ +class Outer { + + class Inner + +} + diff --git a/tests/run/outerPatternMatch/Test_2.scala b/tests/run/outerPatternMatch/Test_2.scala new file mode 100644 index 000000000000..e46f52f20c60 --- /dev/null +++ b/tests/run/outerPatternMatch/Test_2.scala @@ -0,0 +1,14 @@ +object Test { + + def main(args: Array[String]): Unit = { + val x = new Outer + val y = new Outer + val i = new x.Inner + val j = new y.Inner + i match { + case _: y.Inner => assert(false) + case _: x.Inner => // OK + } + } + +} From b84bcbf3c948813426967095f600680736be6351 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Sep 2016 12:01:46 +0200 Subject: [PATCH 25/77] Update check file --- tests/run/inline.check | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/run/inline.check b/tests/run/inline.check index 49d2e8c6c231..5f711274b935 100644 --- a/tests/run/inline.check +++ b/tests/run/inline.check @@ -2,3 +2,8 @@ 10000 Inner +Outer.f +Outer.f Inner + Inner +Outer.f +Outer.f Inner From 29acee02f66c988cc9763057c785c98477201755 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Sep 2016 13:50:12 +0200 Subject: [PATCH 26/77] Move InlineTyper to Inliner class Keeping as a static object causes suspected dataraces. --- src/dotty/tools/dotc/typer/Inliner.scala | 62 ++++++++++++------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 970d31e474eb..a0df735b1810 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -40,37 +40,6 @@ object Inliner { sym.getAnnotation(defn.InlineAnnot).get.tree .attachment(InlinedBody).body - private object InlineTyper extends ReTyper { - override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { - val acc = tree.symbol - super.typedSelect(tree, pt) match { - case res @ Select(qual, name) => - if (name.endsWith(nme.OUTER)) { - val outerAcc = tree.symbol - println(i"selecting $tree / ${acc} / ${qual.tpe.normalizedPrefix}") - res.withType(qual.tpe.widen.normalizedPrefix) - } - else { - ensureAccessible(res.tpe, qual.isInstanceOf[Super], tree.pos) - res - } - case res => res - } - } - override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { - val cond1 = typed(tree.cond, defn.BooleanType) - cond1.tpe.widenTermRefExpr match { - case ConstantType(Constant(condVal: Boolean)) => - val selected = typed(if (condVal) tree.thenp else tree.elsep, pt) - if (isIdempotentExpr(cond1)) selected - else Block(cond1 :: Nil, selected) - case _ => - val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) - super.typedIf(if1, pt) - } - } - } - def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = if (enclosingInlineds.length < ctx.settings.xmaxInlines.value) new Inliner(tree, inlinedBody(tree.symbol)).inlined(pt) @@ -190,6 +159,37 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { case _ => } + private object InlineTyper extends ReTyper { + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { + val acc = tree.symbol + super.typedSelect(tree, pt) match { + case res @ Select(qual, name) => + if (name.endsWith(nme.OUTER)) { + val outerAcc = tree.symbol + println(i"selecting $tree / ${acc} / ${qual.tpe.normalizedPrefix}") + res.withType(qual.tpe.widen.normalizedPrefix) + } + else { + ensureAccessible(res.tpe, qual.isInstanceOf[Super], tree.pos) + res + } + case res => res + } + } + override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { + val cond1 = typed(tree.cond, defn.BooleanType) + cond1.tpe.widenTermRefExpr match { + case ConstantType(Constant(condVal: Boolean)) => + val selected = typed(if (condVal) tree.thenp else tree.elsep, pt) + if (isIdempotentExpr(cond1)) selected + else Block(cond1 :: Nil, selected) + case _ => + val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) + super.typedIf(if1, pt) + } + } + } + def inlined(pt: Type) = { if (!isIdempotentExpr(prefix)) registerType(meth.owner.thisType) // make sure prefix is computed rhs.foreachSubTree(registerLeaf) From 4dd8469391c440728fa8ee4e45d521b7933d5bd8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Sep 2016 13:51:44 +0200 Subject: [PATCH 27/77] Avoid reference to local bindings in Inlined nodes To do this, use a proper TypeAssigner for Inlined, analogous to how we type Blocks. --- src/dotty/tools/dotc/ast/tpd.scala | 2 +- src/dotty/tools/dotc/typer/TypeAssigner.scala | 8 +++++++- src/dotty/tools/dotc/typer/Typer.scala | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index dc0756df125a..54005808fc6e 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -117,7 +117,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { ta.assignType(new untpd.JavaSeqLiteral(elems, elemtpt), elems, elemtpt).asInstanceOf[JavaSeqLiteral] def Inlined(call: Tree, bindings: List[MemberDef], expansion: Tree)(implicit ctx: Context): Inlined = - untpd.Inlined(call, bindings, expansion).withType(expansion.tpe) + ta.assignType(untpd.Inlined(call, bindings, expansion), bindings, expansion) def TypeTree(original: Tree)(implicit ctx: Context): TypeTree = TypeTree(original.tpe, original) diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index ba8f35cd8ae5..0c55d977e67a 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -127,6 +127,9 @@ trait TypeAssigner { widenMap(tp) } + def avoidingType(expr: Tree, bindings: List[Tree])(implicit ctx: Context): Type = + avoid(expr.tpe, localSyms(bindings).filter(_.isTerm)) + def seqToRepeated(tree: Tree)(implicit ctx: Context): Tree = Typed(tree, TypeTree(tree.tpe.widen.translateParameterized(defn.SeqClass, defn.RepeatedParamClass))) @@ -383,7 +386,10 @@ trait TypeAssigner { tree.withType(defn.UnitType) def assignType(tree: untpd.Block, stats: List[Tree], expr: Tree)(implicit ctx: Context) = - tree.withType(avoid(expr.tpe, localSyms(stats) filter (_.isTerm))) + tree.withType(avoidingType(expr, stats)) + + def assignType(tree: untpd.Inlined, bindings: List[Tree], expansion: Tree)(implicit ctx: Context) = + tree.withType(avoidingType(expansion, bindings)) def assignType(tree: untpd.If, thenp: Tree, elsep: Tree)(implicit ctx: Context) = tree.withType(thenp.tpe | elsep.tpe) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 3672996c87a3..8bc156606fa8 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -948,8 +948,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context): Inlined = { val (exprCtx, bindings1) = typedBlockStats(tree.bindings) val expansion1 = typed(tree.expansion, pt)(Inliner.inlineContext(tree.call)(exprCtx)) - cpy.Inlined(tree)(tree.call, bindings1.asInstanceOf[List[MemberDef]], expansion1) - .withType(expansion1.tpe) + assignType(cpy.Inlined(tree)(tree.call, bindings1.asInstanceOf[List[MemberDef]], expansion1), + bindings1, expansion1) } def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree = track("typedTypeTree") { From 33545570039fa37be3327f0ba8c27d16b0d7abf6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Sep 2016 14:30:17 +0200 Subject: [PATCH 28/77] Fix bugs related to type parameter remapping - Remap typerefs - Register types in TypeTrees --- src/dotty/tools/dotc/typer/Inliner.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index a0df735b1810..631c98734b09 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -155,7 +155,7 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { } private def registerLeaf(tree: Tree): Unit = tree match { - case _: This | _: Ident => registerType(tree.tpe) + case _: This | _: Ident | _: TypeTree => registerType(tree.tpe) case _ => } @@ -213,6 +213,7 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { val typeMap = new TypeMap { def apply(t: Type) = t match { case t: ThisType => thisProxy.getOrElse(t, t) + case t: TypeRef => paramProxy.getOrElse(t, t) case t: SingletonType => paramProxy.getOrElse(t, t) case t => mapOver(t) } From ebcc2bdb8f5bacdff073868817357f23c5ce8464 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Sep 2016 14:44:08 +0200 Subject: [PATCH 29/77] Fix problem with homogenize trees --- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index fd79b8c501b9..f4a7a4dc5d5a 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -37,7 +37,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } def homogenize(tree: Tree[_])(implicit ctx: Context) = tree match { - case tree: tpd.Inlined => Inliner.dropInlined(tree) + case tree: tpd.Inlined if homogenizedView => Inliner.dropInlined(tree) case _ => tree } From 61e8ea4824e14c9884d69eec502ea73c9ebd0838 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Sep 2016 17:56:35 +0200 Subject: [PATCH 30/77] Better diagnostics for TreeChecker 1. Better formatting in TreeChecker error message 2. Re-enable printing what stack of what was checked when an error occurred. This was disabled in Retyper because we did not do it for the Inliner typer. Now we distinguish on phase instead. --- src/dotty/tools/dotc/transform/TreeChecker.scala | 5 +++-- src/dotty/tools/dotc/typer/ReTyper.scala | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index 074a278ca9c1..4b3927ccfcf9 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -426,8 +426,9 @@ class TreeChecker extends Phase with SymTransformer { !isPrimaryConstructorReturn && !pt.isInstanceOf[FunProto]) assert(tree.tpe <:< pt, - s"error at ${sourcePos(tree.pos)}\n" + - err.typeMismatchStr(tree.tpe, pt) + "\ntree = " + tree) + i"""error at ${sourcePos(tree.pos)} + |${err.typeMismatchStr(tree.tpe, pt)} + |tree = $tree""") tree } } diff --git a/src/dotty/tools/dotc/typer/ReTyper.scala b/src/dotty/tools/dotc/typer/ReTyper.scala index 03b415a6fce3..143d30f9e8a5 100644 --- a/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/src/dotty/tools/dotc/typer/ReTyper.scala @@ -87,7 +87,8 @@ class ReTyper extends Typer { try super.typedUnadapted(tree, pt) catch { case NonFatal(ex) => - typr.println(i"exception while typing $tree of class ${tree.getClass} # ${tree.uniqueId}") + if (ctx.isAfterTyper) + println(i"exception while typing $tree of class ${tree.getClass} # ${tree.uniqueId}") throw ex } From 04de4b58b75cb526506317db02d8b6b2a8aefd99 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Sep 2016 18:01:12 +0200 Subject: [PATCH 31/77] Fix problems handling types in Inliner 1. Don't generate local aliases - we can potentially run into avoidance problems later for such aliases 2. Scan all parts of leaf types for things that need to be registered cor remappings. --- src/dotty/tools/dotc/core/Types.scala | 7 +++---- src/dotty/tools/dotc/typer/Inliner.scala | 16 +++++----------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 30d1c0136e16..7a64e19c87a0 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -228,8 +228,8 @@ object Types { !existsPart(!p(_)) /** Performs operation on all parts of this type */ - final def foreachPart(p: Type => Unit)(implicit ctx: Context): Unit = - new ForeachAccumulator(p).apply((), this) + final def foreachPart(p: Type => Unit, stopAtStatic: Boolean = false)(implicit ctx: Context): Unit = + new ForeachAccumulator(p, stopAtStatic).apply((), this) /** The parts of this type which are type or term refs */ final def namedParts(implicit ctx: Context): collection.Set[NamedType] = @@ -3704,8 +3704,7 @@ object Types { x || p(tp) || (forceLazy || !tp.isInstanceOf[LazyRef]) && foldOver(x, tp) } - class ForeachAccumulator(p: Type => Unit)(implicit ctx: Context) extends TypeAccumulator[Unit] { - override def stopAtStatic = false + class ForeachAccumulator(p: Type => Unit, override val stopAtStatic: Boolean)(implicit ctx: Context) extends TypeAccumulator[Unit] { def apply(x: Unit, tp: Type): Unit = foldOver(p(tp), tp) } diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 631c98734b09..4af9ecaeccc1 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -104,13 +104,7 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { private def computeParamBindings(tp: Type, targs: List[Tree], argss: List[List[Tree]]): Unit = tp match { case tp: PolyType => (tp.paramNames, targs).zipped.foreach { (name, arg) => - paramBinding(name) = arg.tpe.stripTypeVar match { - case argtpe: TypeRef => argtpe - case argtpe => - val binding = newSym(name, EmptyFlags, TypeAlias(argtpe)).asType - bindingsBuf += TypeDef(binding) - binding.typeRef - } + paramBinding(name) = arg.tpe.stripTypeVar } computeParamBindings(tp.resultType, Nil, argss) case tp: MethodType => @@ -155,7 +149,8 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { } private def registerLeaf(tree: Tree): Unit = tree match { - case _: This | _: Ident | _: TypeTree => registerType(tree.tpe) + case _: This | _: Ident | _: TypeTree => + tree.tpe.foreachPart(registerType, stopAtStatic = true) case _ => } @@ -166,7 +161,6 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { case res @ Select(qual, name) => if (name.endsWith(nme.OUTER)) { val outerAcc = tree.symbol - println(i"selecting $tree / ${acc} / ${qual.tpe.normalizedPrefix}") res.withType(qual.tpe.widen.normalizedPrefix) } else { @@ -227,8 +221,8 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { } case _: Ident => paramProxy.get(tree.tpe) match { - case Some(t: TypeRef) => ref(t) - case Some(t: SingletonType) => singleton(t) + case Some(t: SingletonType) if tree.isTerm => singleton(t) + case Some(t) if tree.isType => TypeTree(t) case None => tree } case _ => tree From 5396a6b3a4cb152d93dbb4947af688fdad1e2508 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Sep 2016 18:05:38 +0200 Subject: [PATCH 32/77] Better error message in TreePickler Print what was pickled when failing with unresoilvced symbols. --- src/dotty/tools/dotc/core/tasty/TreePickler.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index fe6a5f82825f..dc37485f91bb 100644 --- a/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -572,12 +572,11 @@ class TreePickler(pickler: TastyPickler) { def pickle(trees: List[Tree])(implicit ctx: Context) = { trees.foreach(tree => if (!tree.isEmpty) pickleTree(tree)) - assert(forwardSymRefs.isEmpty, i"unresolved symbols: ${forwardSymRefs.keySet.toList}%, %") + assert(forwardSymRefs.isEmpty, i"unresolved symbols: ${forwardSymRefs.keySet.toList}%, % when pickling $trees%\n %") } def compactify() = { buf.compactify() - assert(forwardSymRefs.isEmpty, s"unresolved symbols: ${forwardSymRefs.keySet.toList}%, %") def updateMapWithDeltas[T](mp: collection.mutable.Map[T, Addr]) = for (key <- mp.keysIterator.toBuffer[T]) mp(key) = adjusted(mp(key)) From 5a345e7100450d39edf0034cc5b83bdb9a851cfa Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Sep 2016 18:06:36 +0200 Subject: [PATCH 33/77] Inline key operations in dotty Inlined operations are: Stats.track and all variants of Reporter.traceIndented. --- src/dotty/tools/dotc/core/SymbolLoaders.scala | 2 +- src/dotty/tools/dotc/reporting/Reporter.scala | 28 +++++++++++-------- src/dotty/tools/dotc/util/Stats.scala | 17 +++++++---- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/dotty/tools/dotc/core/SymbolLoaders.scala b/src/dotty/tools/dotc/core/SymbolLoaders.scala index 3f801bda57f2..4ae28c10bf5d 100644 --- a/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -198,7 +198,7 @@ abstract class SymbolLoader extends LazyType { try { val start = currentTime if (ctx.settings.debugTrace.value) - ctx.traceIndented(s">>>> loading ${root.debugString}", _ => s"<<<< loaded ${root.debugString}") { + ctx.doTraceIndented(s">>>> loading ${root.debugString}", _ => s"<<<< loaded ${root.debugString}") { doComplete(root) } else diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index b3d173a423d5..68ed972e1f6f 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -140,28 +140,32 @@ trait Reporting { this: Context => def debugwarn(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = if (this.settings.debug.value) warning(msg, pos) - def debugTraceIndented[T](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => T): T = + @dotty.annotation.inline + def debugTraceIndented[TD](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => TD): TD = conditionalTraceIndented(this.settings.debugTrace.value, question, printer, show)(op) - def conditionalTraceIndented[T](cond: Boolean, question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => T): T = - if (cond) traceIndented(question, printer, show)(op) + @dotty.annotation.inline + def conditionalTraceIndented[TC](cond: Boolean, question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => TC): TC = + if (cond) traceIndented[TC](question, printer, show)(op) else op - def traceIndented[T](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => T): T = { + @dotty.annotation.inline + def traceIndented[T](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => T): T = + if (printer eq config.Printers.noPrinter) op + else doTraceIndented[T](question, printer, show)(op) + + def doTraceIndented[T](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => T): T = { def resStr(res: Any): String = res match { case res: printing.Showable if show => res.show case _ => String.valueOf(res) } - if (printer eq config.Printers.noPrinter) op - else { - // Avoid evaluating question multiple time, since each evaluation - // may cause some extra logging output. - lazy val q: String = question - traceIndented[T](s"==> $q?", (res: Any) => s"<== $q = ${resStr(res)}")(op) - } + // Avoid evaluating question multiple time, since each evaluation + // may cause some extra logging output. + lazy val q: String = question + doTraceIndented[T](s"==> $q?", (res: Any) => s"<== $q = ${resStr(res)}")(op) } - def traceIndented[T](leading: => String, trailing: Any => String)(op: => T): T = + def doTraceIndented[T](leading: => String, trailing: Any => String)(op: => T): T = if (ctx.mode.is(Mode.Printing)) op else { var finalized = false diff --git a/src/dotty/tools/dotc/util/Stats.scala b/src/dotty/tools/dotc/util/Stats.scala index fdd3602c9cee..f5e711348f36 100644 --- a/src/dotty/tools/dotc/util/Stats.scala +++ b/src/dotty/tools/dotc/util/Stats.scala @@ -7,27 +7,34 @@ import collection.mutable @sharable object Stats { - final val enabled = true + final val enabled = false /** The period in ms in which stack snapshots are displayed */ final val HeartBeatPeriod = 250 + var monitored = false + @volatile private var stack: List[String] = Nil val hits = new mutable.HashMap[String, Int] { override def default(key: String): Int = 0 } - def record(fn: String, n: Int = 1) = { + @dotty.annotation.inline + def record(fn: String, n: Int = 1) = + if (enabled) doRecord(fn, n) + + def doRecord(fn: String, n: Int) = if (monitored) { val name = if (fn.startsWith("member-")) "member" else fn hits(name) += n } - } - - var monitored = false + @dotty.annotation.inline def track[T](fn: String)(op: => T) = + if (enabled) doTrack(fn)(op) else op + + def doTrack[T](fn: String)(op: => T) = if (monitored) { stack = fn :: stack record(fn) From 60b6c6a3059b23cf0cc1968cc3c7ee3886784721 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Sep 2016 18:20:25 +0200 Subject: [PATCH 34/77] Harden ReTyper so that it's fit for inlining 1. Imlement typedUnapply 2. Disable implicit view searches and searches for equality checks - these should have already happened in the first typer run. --- src/dotty/tools/dotc/typer/Applications.scala | 2 +- src/dotty/tools/dotc/typer/ReTyper.scala | 11 +++++++++++ src/dotty/tools/dotc/typer/Typer.scala | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 55d9fc990174..2c9039db1fc4 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -861,7 +861,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** A typed unapply hook, can be overridden by re any-typers between frontend * and pattern matcher. */ - def typedUnApply(tree: untpd.UnApply, selType: Type)(implicit ctx: Context) = + def typedUnApply(tree: untpd.UnApply, selType: Type)(implicit ctx: Context): UnApply = throw new UnsupportedOperationException("cannot type check an UnApply node") /** Is given method reference applicable to type arguments `targs` and argument trees `args`? diff --git a/src/dotty/tools/dotc/typer/ReTyper.scala b/src/dotty/tools/dotc/typer/ReTyper.scala index 143d30f9e8a5..1db6b54afd4e 100644 --- a/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/src/dotty/tools/dotc/typer/ReTyper.scala @@ -10,6 +10,7 @@ import typer.ProtoTypes._ import ast.{tpd, untpd} import ast.Trees._ import scala.util.control.NonFatal +import util.Positions.Position import config.Printers.typr /** A version of Typer that keeps all symbols defined and referenced in a @@ -56,6 +57,13 @@ class ReTyper extends Typer { untpd.cpy.Bind(tree)(tree.name, body1).withType(tree.typeOpt) } + override def typedUnApply(tree: untpd.UnApply, selType: Type)(implicit ctx: Context): UnApply = { + val fun1 = typedExpr(tree.fun, AnyFunctionProto) + val implicits1 = tree.implicits.map(typedExpr(_)) + val patterns1 = tree.patterns.mapconserve(pat => typed(pat, pat.tpe)) + untpd.cpy.UnApply(tree)(fun1, implicits1, patterns1).withType(tree.tpe) + } + override def localDummy(cls: ClassSymbol, impl: untpd.Template)(implicit ctx: Context) = impl.symbol override def retrieveSym(tree: untpd.Tree)(implicit ctx: Context): Symbol = tree.symbol @@ -93,4 +101,7 @@ class ReTyper extends Typer { } override def checkVariance(tree: Tree)(implicit ctx: Context) = () + override def inferView(from: Tree, to: Type)(implicit ctx: Context): Implicits.SearchResult = + Implicits.NoImplicitMatches + override def checkCanEqual(ltp: Type, rtp: Type, pos: Position)(implicit ctx: Context): Unit = () } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 8bc156606fa8..22a5095ec088 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1570,7 +1570,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } - def adapt(tree: Tree, pt: Type, original: untpd.Tree = untpd.EmptyTree)(implicit ctx: Context) = /*>|>*/ track("adapt") /*<|<*/ { + def adapt(tree: Tree, pt: Type, original: untpd.Tree = untpd.EmptyTree)(implicit ctx: Context): Tree = /*>|>*/ track("adapt") /*<|<*/ { /*>|>*/ ctx.traceIndented(i"adapting $tree of type ${tree.tpe} to $pt", typr, show = true) /*<|<*/ { if (tree.isDef) interpolateUndetVars(tree, tree.symbol) else if (!tree.tpe.widen.isInstanceOf[MethodOrPoly]) interpolateUndetVars(tree, NoSymbol) From d96cb84c35bfe47718850c1b8db435e67d512a0e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Sep 2016 18:21:20 +0200 Subject: [PATCH 35/77] Test case for closure inlining This test works, but closures are not currently inlined. That's a still to do. --- tests/run/inlinedAssign.scala | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/run/inlinedAssign.scala diff --git a/tests/run/inlinedAssign.scala b/tests/run/inlinedAssign.scala new file mode 100644 index 000000000000..735158209a6b --- /dev/null +++ b/tests/run/inlinedAssign.scala @@ -0,0 +1,18 @@ +object Test { + + @dotty.annotation.inline + def swap[T](x: T, x_= : T => Unit, y: T, y_= : T => Unit) = { + val t = x + x_=(y) + y_=(t) + } + + def main(args: Array[String]) = { + var x = 1 + var y = 2 + @dotty.annotation.inline def setX(z: Int) = x = z + @dotty.annotation.inline def setY(z: Int) = y = z + swap[Int](x, setX, y, setY) + assert(x == 2 && y == 1) + } +} From bd0660ef100ee1a41bd56267d9e95c9e6dd74d5e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Sep 2016 13:04:08 +0200 Subject: [PATCH 36/77] Juggling with close in RefinedPrinter Because of different close seqences before and after pickling we could get spurious differences, where in one file things were put on one line, and in the pther there was a linebreak. --- .../tools/dotc/printing/RefinedPrinter.scala | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index f4a7a4dc5d5a..4f3a8d272597 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -36,11 +36,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } } - def homogenize(tree: Tree[_])(implicit ctx: Context) = tree match { - case tree: tpd.Inlined if homogenizedView => Inliner.dropInlined(tree) - case _ => tree - } - private def enclDefIsClass = enclosingDef match { case owner: TypeDef[_] => owner.isClassDef case owner: untpd.ModuleDef => true @@ -159,7 +154,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } def blockText[T >: Untyped](trees: List[Tree[T]]): Text = - "{" ~ toText(trees, "\n") ~ "}" + ("{" ~ toText(trees, "\n") ~ "}").close override def toText[T >: Untyped](tree: Tree[T]): Text = controlled { @@ -282,7 +277,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (homogenizedView && pid.hasType) toTextLocal(pid.tpe) else toTextLocal(pid) - var txt: Text = homogenize(tree) match { + def toTextCore(tree: Tree): Text = tree match { case id: Trees.BackquotedIdent[_] if !homogenizedView => "`" ~ toText(id.name) ~ "`" case Ident(name) => @@ -356,8 +351,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } case SeqLiteral(elems, elemtpt) => "[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]" - case Inlined(call, bindings, body) => - "/* inlined from " ~ toText(call) ~ "*/ " ~ blockText(bindings :+ body) + case tree @ Inlined(call, bindings, body) => + if (homogenizedView) toTextCore(Inliner.dropInlined(tree.asInstanceOf[tpd.Inlined])) + else "/* inlined from " ~ toText(call) ~ "*/ " ~ blockText(bindings :+ body) case tpt: untpd.DerivedTypeTree => "" case TypeTree(orig) => @@ -521,13 +517,14 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case _ => tree.fallbackToText(this) } + var txt = toTextCore(tree) if (ctx.settings.printtypes.value && tree.hasType) { val tp = tree.typeOpt match { case tp: TermRef if tree.isInstanceOf[RefTree] && !tp.denot.isOverloaded => tp.underlying case tp => tp } if (tree.isType) txt = toText(tp) - else if (!tree.isDef) txt = "<" ~ txt ~ ":" ~ toText(tp) ~ ">" + else if (!tree.isDef) txt = ("<" ~ txt ~ ":" ~ toText(tp) ~ ">").close } if (ctx.settings.Yprintpos.value && !tree.isInstanceOf[WithoutTypeOrPos[_]]) txt = txt ~ "@" ~ tree.pos.toString From 19ab7ab10fabe7113f45063ffd2b6cc6abcc3329 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Sep 2016 13:20:39 +0200 Subject: [PATCH 37/77] Make inline annotation @scala.inline. Drop @dotty.annotation.inline. This will inline all @inline marked methods in Scala for which a body is known (i.e. that are either compiled in the same run or have Tasty trees available). Option -Yno-inline suppresses inlining. This is needed for the moment because some @inline methods access private members or members that are otherwise inaccessible at the call-site. Also fixes some problems in Inliner - make sure type arguments to inline calls re fully defined - don't forget recursive calls in typeMap - don't forget positions in treeMap - drop dead code dealing with outer. --- src/dotty/annotation/inline.scala | 8 ---- .../tools/dotc/config/ScalaSettings.scala | 1 + src/dotty/tools/dotc/core/Decorators.scala | 2 +- src/dotty/tools/dotc/core/Definitions.scala | 2 +- src/dotty/tools/dotc/reporting/Reporter.scala | 6 +-- src/dotty/tools/dotc/typer/Inliner.scala | 42 ++++++++----------- src/dotty/tools/dotc/typer/Typer.scala | 2 + src/dotty/tools/dotc/util/Stats.scala | 4 +- test/dotc/tests.scala | 4 +- tests/neg/inlineAccess.scala | 15 ------- tests/neg/inlineAccess/C_1.scala | 2 +- tests/neg/power.scala | 2 +- tests/run/inline/inlines_1.scala | 10 ++--- tests/run/inlinePower/power_1.scala | 2 +- tests/run/inlinedAssign.scala | 6 +-- 15 files changed, 41 insertions(+), 67 deletions(-) delete mode 100644 src/dotty/annotation/inline.scala delete mode 100644 tests/neg/inlineAccess.scala diff --git a/src/dotty/annotation/inline.scala b/src/dotty/annotation/inline.scala deleted file mode 100644 index ff4ca08b6d05..000000000000 --- a/src/dotty/annotation/inline.scala +++ /dev/null @@ -1,8 +0,0 @@ -package dotty.annotation - -import scala.annotation.Annotation - -/** Unlike scala.inline, this one forces inlining in the Typer - * Should be replaced by keyword when we switch over completely to dotty - */ -class inline extends Annotation \ No newline at end of file diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index d05ae08038b7..42e761c2e247 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -186,6 +186,7 @@ class ScalaSettings extends Settings.SettingGroup { val Yexplainlowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") val YnoDoubleBindings = BooleanSetting("-Yno-double-bindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).") val YshowVarBounds = BooleanSetting("-Yshow-var-bounds", "Print type variables with their bounds") + val YnoInline = BooleanSetting("-Yno-inline", "Suppress inlining.") val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying optimisations to the program") withAbbreviation "-optimize" diff --git a/src/dotty/tools/dotc/core/Decorators.scala b/src/dotty/tools/dotc/core/Decorators.scala index 64f50173c979..691e0aeba013 100644 --- a/src/dotty/tools/dotc/core/Decorators.scala +++ b/src/dotty/tools/dotc/core/Decorators.scala @@ -42,7 +42,7 @@ object Decorators { */ implicit class ListDecorator[T](val xs: List[T]) extends AnyVal { - @inline final def mapconserve[U](f: T => U): List[U] = { + final def mapconserve[U](f: T => U): List[U] = { @tailrec def loop(mapped: ListBuffer[U], unchanged: List[U], pending: List[T]): List[U] = if (pending.isEmpty) { diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index c9a3ef4dee62..8bf3403377f5 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -466,7 +466,7 @@ class Definitions { def DeprecatedAnnot(implicit ctx: Context) = DeprecatedAnnotType.symbol.asClass lazy val ImplicitNotFoundAnnotType = ctx.requiredClassRef("scala.annotation.implicitNotFound") def ImplicitNotFoundAnnot(implicit ctx: Context) = ImplicitNotFoundAnnotType.symbol.asClass - lazy val InlineAnnotType = ctx.requiredClassRef("dotty.annotation.inline") + lazy val InlineAnnotType = ctx.requiredClassRef("scala.inline") def InlineAnnot(implicit ctx: Context) = InlineAnnotType.symbol.asClass lazy val InvariantBetweenAnnotType = ctx.requiredClassRef("dotty.annotation.internal.InvariantBetween") def InvariantBetweenAnnot(implicit ctx: Context) = InvariantBetweenAnnotType.symbol.asClass diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index 68ed972e1f6f..f3777473d676 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -140,16 +140,16 @@ trait Reporting { this: Context => def debugwarn(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = if (this.settings.debug.value) warning(msg, pos) - @dotty.annotation.inline + @inline def debugTraceIndented[TD](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => TD): TD = conditionalTraceIndented(this.settings.debugTrace.value, question, printer, show)(op) - @dotty.annotation.inline + @inline def conditionalTraceIndented[TC](cond: Boolean, question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => TC): TC = if (cond) traceIndented[TC](question, printer, show)(op) else op - @dotty.annotation.inline + @inline def traceIndented[T](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => T): T = if (printer eq config.Printers.noPrinter) op else doTraceIndented[T](question, printer, show)(op) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 4af9ecaeccc1..b59c3300884c 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -17,6 +17,7 @@ import Names.Name import SymDenotations.SymDenotation import Annotations.Annotation import transform.ExplicitOuter +import Inferencing.fullyDefinedType import config.Printers.inlining import ErrorReporting.errorTree import util.{Property, SourceFile, NoSource} @@ -36,13 +37,13 @@ object Inliner { def attachBody(inlineAnnot: Annotation, tree: => Tree)(implicit ctx: Context): Unit = inlineAnnot.tree.putAttachment(InlinedBody, new InlinedBody(tree)) - def inlinedBody(sym: SymDenotation)(implicit ctx: Context): Tree = + def inlinedBody(sym: SymDenotation)(implicit ctx: Context): Option[Tree] = sym.getAnnotation(defn.InlineAnnot).get.tree - .attachment(InlinedBody).body + .getAttachment(InlinedBody).map(_.body) def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = if (enclosingInlineds.length < ctx.settings.xmaxInlines.value) - new Inliner(tree, inlinedBody(tree.symbol)).inlined(pt) + new Inliner(tree, inlinedBody(tree.symbol).get).inlined(pt) else errorTree(tree, i"""Maximal number of successive inlines (${ctx.settings.xmaxInlines.value}) exceeded, | Maybe this is caused by a recursive inline method? @@ -86,6 +87,8 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { private val (methPart, targs, argss) = decomposeCall(call) private val meth = methPart.symbol + for (targ <- targs) fullyDefinedType(targ.tpe, "inlined type argument", targ.pos) + private val prefix = methPart match { case Select(qual, _) => qual case _ => tpd.This(ctx.owner.enclosingClass.asClass) @@ -156,19 +159,9 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { private object InlineTyper extends ReTyper { override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { - val acc = tree.symbol - super.typedSelect(tree, pt) match { - case res @ Select(qual, name) => - if (name.endsWith(nme.OUTER)) { - val outerAcc = tree.symbol - res.withType(qual.tpe.widen.normalizedPrefix) - } - else { - ensureAccessible(res.tpe, qual.isInstanceOf[Super], tree.pos) - res - } - case res => res - } + val res = super.typedSelect(tree, pt) + ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.pos) + res } override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { val cond1 = typed(tree.cond, defn.BooleanType) @@ -207,26 +200,27 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { val typeMap = new TypeMap { def apply(t: Type) = t match { case t: ThisType => thisProxy.getOrElse(t, t) - case t: TypeRef => paramProxy.getOrElse(t, t) - case t: SingletonType => paramProxy.getOrElse(t, t) + case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) + case t: SingletonType => paramProxy.getOrElse(t, mapOver(t)) case t => mapOver(t) } } - def treeMap(tree: Tree) = tree match { + def treeMap(tree: Tree) = { + tree match { case _: This => thisProxy.get(tree.tpe) match { - case Some(t) => ref(t) + case Some(t) => ref(t).withPos(tree.pos) case None => tree } case _: Ident => paramProxy.get(tree.tpe) match { - case Some(t: SingletonType) if tree.isTerm => singleton(t) - case Some(t) if tree.isType => TypeTree(t) + case Some(t: SingletonType) if tree.isTerm => singleton(t).withPos(tree.pos) + case Some(t) if tree.isType => TypeTree(t).withPos(tree.pos) case None => tree } case _ => tree - } + }} val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil) val bindings = bindingsBuf.toList.map(_.withPos(call.pos)) @@ -235,7 +229,7 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { val expansion1 = InlineTyper.typed(expansion, pt)(inlineContext(call)) val result = tpd.Inlined(call, bindings, expansion1) - inlining.println(i"inlining $call\n --> \n$result") + inlining.println(i"inlined $call\n --> \n$result") result } } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 22a5095ec088..29c36805ffff 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1794,7 +1794,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } else if (tree.tpe <:< pt) if (tree.symbol.isInlineMethod && + Inliner.inlinedBody(tree.symbol).isDefined && !ctx.owner.ownersIterator.exists(_.isInlineMethod) && + !ctx.settings.YnoInline.value && !ctx.isAfterTyper) adapt(Inliner.inlineCall(tree, pt), pt) else if (ctx.typeComparer.GADTused && pt.isValueType) diff --git a/src/dotty/tools/dotc/util/Stats.scala b/src/dotty/tools/dotc/util/Stats.scala index f5e711348f36..e06695dfb907 100644 --- a/src/dotty/tools/dotc/util/Stats.scala +++ b/src/dotty/tools/dotc/util/Stats.scala @@ -20,7 +20,7 @@ import collection.mutable override def default(key: String): Int = 0 } - @dotty.annotation.inline + @inline def record(fn: String, n: Int = 1) = if (enabled) doRecord(fn, n) @@ -30,7 +30,7 @@ import collection.mutable hits(name) += n } - @dotty.annotation.inline + @inline def track[T](fn: String)(op: => T) = if (enabled) doTrack(fn)(op) else op diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 9f95a30c17c3..a12e779188c8 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -156,7 +156,7 @@ class tests extends CompilerTest { .filter(_.nonEmpty) .toList - @Test def compileStdLib = compileList("compileStdLib", stdlibFiles, "-migration" :: scala2mode) + @Test def compileStdLib = compileList("compileStdLib", stdlibFiles, "-migration" :: "-Yno-inline" :: scala2mode) @Test def compileMixed = compileLine( """tests/pos/B.scala |./scala-scala/src/library/scala/collection/immutable/Seq.scala @@ -274,7 +274,7 @@ class tests extends CompilerTest { "ClassOf.scala", "CollectEntryPoints.scala", "Constructors.scala", "CrossCastAnd.scala", "CtxLazy.scala", "ElimByName.scala", "ElimErasedValueType.scala", "ElimRepeated.scala", "ElimStaticThis.scala", "Erasure.scala", "ExpandPrivate.scala", "ExpandSAMs.scala", - "ExplicitOuter.scala", "ExplicitSelf.scala", "ExtensionMethods.scala", "FirstTransform.scala", + "ExplicitOuter.scala", "ExtensionMethods.scala", "FirstTransform.scala", "Flatten.scala", "FullParameterization.scala", "FunctionalInterfaces.scala", "GetClass.scala", "Getters.scala", "InterceptedMethods.scala", "LambdaLift.scala", "LiftTry.scala", "LinkScala2ImplClasses.scala", "MacroTransform.scala", "Memoize.scala", "Mixin.scala", "MixinOps.scala", "NonLocalReturns.scala", diff --git a/tests/neg/inlineAccess.scala b/tests/neg/inlineAccess.scala deleted file mode 100644 index cfb1cc06fd6b..000000000000 --- a/tests/neg/inlineAccess.scala +++ /dev/null @@ -1,15 +0,0 @@ -package p { -class C { - protected def f(): Unit = () - - @dotty.annotation.inline - def inl() = f() // error (when inlined): not accessible -} -} - -object Test { - def main(args: Array[String]) = { - val c = new p.C() - c.inl() - } -} diff --git a/tests/neg/inlineAccess/C_1.scala b/tests/neg/inlineAccess/C_1.scala index fc24f7666e21..6db1ea787018 100644 --- a/tests/neg/inlineAccess/C_1.scala +++ b/tests/neg/inlineAccess/C_1.scala @@ -2,7 +2,7 @@ package p { class C { protected def f(): Unit = () - @dotty.annotation.inline + @inline def inl() = f() // error (when inlined): not accessible } } diff --git a/tests/neg/power.scala b/tests/neg/power.scala index 7439d8699989..6230b4e51a21 100644 --- a/tests/neg/power.scala +++ b/tests/neg/power.scala @@ -1,6 +1,6 @@ object Test { - @dotty.annotation.inline + @inline def power(x: Double, n: Int): Double = if (n == 0) 1.0 else if (n == 1) x diff --git a/tests/run/inline/inlines_1.scala b/tests/run/inline/inlines_1.scala index 36f5ac402ad9..8189e6805477 100644 --- a/tests/run/inline/inlines_1.scala +++ b/tests/run/inline/inlines_1.scala @@ -5,7 +5,7 @@ object inlines { final val monitored = false - @dotty.annotation.inline + @inline def f(x: Int): Int = x * x val hits = new mutable.HashMap[String, Int] { @@ -21,7 +21,7 @@ object inlines { @volatile private var stack: List[String] = Nil - @dotty.annotation.inline + @inline def track[T](fn: String)(op: => T) = if (monitored) { stack = fn :: stack @@ -34,9 +34,9 @@ object inlines { def f = "Outer.f" class Inner { val msg = " Inner" - @dotty.annotation.inline def m = msg - @dotty.annotation.inline def g = f - @dotty.annotation.inline def h = f ++ m + @inline def m = msg + @inline def g = f + @inline def h = f ++ m } val inner = new Inner } diff --git a/tests/run/inlinePower/power_1.scala b/tests/run/inlinePower/power_1.scala index 1faa10516f9f..23da6009a097 100644 --- a/tests/run/inlinePower/power_1.scala +++ b/tests/run/inlinePower/power_1.scala @@ -2,7 +2,7 @@ package p object pow { - @dotty.annotation.inline + @inline def power(x: Double, n: Int): Double = if (n == 0) 1.0 else if (n == 1) x diff --git a/tests/run/inlinedAssign.scala b/tests/run/inlinedAssign.scala index 735158209a6b..b9a5d287dc4e 100644 --- a/tests/run/inlinedAssign.scala +++ b/tests/run/inlinedAssign.scala @@ -1,6 +1,6 @@ object Test { - @dotty.annotation.inline + @inline def swap[T](x: T, x_= : T => Unit, y: T, y_= : T => Unit) = { val t = x x_=(y) @@ -10,8 +10,8 @@ object Test { def main(args: Array[String]) = { var x = 1 var y = 2 - @dotty.annotation.inline def setX(z: Int) = x = z - @dotty.annotation.inline def setY(z: Int) = y = z + @inline def setX(z: Int) = x = z + @inline def setY(z: Int) = y = z swap[Int](x, setX, y, setY) assert(x == 2 && y == 1) } From f8d7500b675e21568011e8e0077f42f4a5463b4b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Sep 2016 14:55:30 +0200 Subject: [PATCH 38/77] Fix problem affecting recursive inlines The previous check whether a method was an inlined method with a body forced computation of the body, which led to problems when dealing with recursive inline methods. --- src/dotty/tools/dotc/typer/Inliner.scala | 13 +++++++++---- src/dotty/tools/dotc/typer/Typer.scala | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index b59c3300884c..763d1ee94e53 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -37,13 +37,18 @@ object Inliner { def attachBody(inlineAnnot: Annotation, tree: => Tree)(implicit ctx: Context): Unit = inlineAnnot.tree.putAttachment(InlinedBody, new InlinedBody(tree)) - def inlinedBody(sym: SymDenotation)(implicit ctx: Context): Option[Tree] = - sym.getAnnotation(defn.InlineAnnot).get.tree - .getAttachment(InlinedBody).map(_.body) + private def inlinedBodyAttachment(sym: SymDenotation)(implicit ctx: Context): Option[InlinedBody] = + sym.getAnnotation(defn.InlineAnnot).get.tree.getAttachment(InlinedBody) + + def hasInlinedBody(sym: SymDenotation)(implicit ctx: Context): Boolean = + inlinedBodyAttachment(sym).isDefined + + def inlinedBody(sym: SymDenotation)(implicit ctx: Context): Tree = + inlinedBodyAttachment(sym).get.body def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = if (enclosingInlineds.length < ctx.settings.xmaxInlines.value) - new Inliner(tree, inlinedBody(tree.symbol).get).inlined(pt) + new Inliner(tree, inlinedBody(tree.symbol)).inlined(pt) else errorTree(tree, i"""Maximal number of successive inlines (${ctx.settings.xmaxInlines.value}) exceeded, | Maybe this is caused by a recursive inline method? diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 29c36805ffff..fab1a1dd0778 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1794,7 +1794,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } else if (tree.tpe <:< pt) if (tree.symbol.isInlineMethod && - Inliner.inlinedBody(tree.symbol).isDefined && + Inliner.hasInlinedBody(tree.symbol) && !ctx.owner.ownersIterator.exists(_.isInlineMethod) && !ctx.settings.YnoInline.value && !ctx.isAfterTyper) From 7f3b9cae24f7d162f908636ffde9d5b8c431cdb2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Sep 2016 14:04:45 +0200 Subject: [PATCH 39/77] Handle Inlined blocks on ElimErasedValueType --- src/dotty/tools/dotc/transform/ElimErasedValueType.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dotty/tools/dotc/transform/ElimErasedValueType.scala b/src/dotty/tools/dotc/transform/ElimErasedValueType.scala index a3f8b56ff3b5..24c8cdc8d45e 100644 --- a/src/dotty/tools/dotc/transform/ElimErasedValueType.scala +++ b/src/dotty/tools/dotc/transform/ElimErasedValueType.scala @@ -67,6 +67,9 @@ class ElimErasedValueType extends MiniPhaseTransform with InfoTransformer { transformTypeOfTree(t) } + override def transformInlined(tree: Inlined)(implicit ctx: Context, info: TransformerInfo): Tree = + transformTypeOfTree(tree) + // FIXME: transformIf and transformBlock won't be required anymore once #444 is fixed. override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = transformTypeOfTree(tree) From aa4d49d8da3ae77a3d62d36afc87ce4acebbbe18 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Sep 2016 15:56:27 +0200 Subject: [PATCH 40/77] Suppress inlining printing --- src/dotty/tools/dotc/config/Printers.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/config/Printers.scala b/src/dotty/tools/dotc/config/Printers.scala index 4168cf5a62b3..002d0f933c96 100644 --- a/src/dotty/tools/dotc/config/Printers.scala +++ b/src/dotty/tools/dotc/config/Printers.scala @@ -30,5 +30,5 @@ object Printers { val completions: Printer = noPrinter val cyclicErrors: Printer = noPrinter val pickling: Printer = noPrinter - val inlining: Printer = new Printer + val inlining: Printer = noPrinter } From b538f8495f7bc23f3ecbcdc4bdbd1912e94b0ffa Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Sep 2016 16:27:19 +0200 Subject: [PATCH 41/77] Suppress inline in dottydoc test of standard library --- dottydoc/test/BaseTest.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/dottydoc/test/BaseTest.scala b/dottydoc/test/BaseTest.scala index 2233d03c8b53..abeb8a49eb5a 100644 --- a/dottydoc/test/BaseTest.scala +++ b/dottydoc/test/BaseTest.scala @@ -18,6 +18,7 @@ trait DottyTest { val ctx = base.initialCtx.fresh ctx.setSetting(ctx.settings.language, List("Scala2")) ctx.setSetting(ctx.settings.YkeepComments, true) + ctx.setSetting(ctx.settings.YnoInline, true) base.initialize()(ctx) ctx } From 4253578c2369708c861c40e52e41e3796a3a2717 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Sep 2016 14:48:59 +0200 Subject: [PATCH 42/77] Better toString for TermRefWithSig --- src/dotty/tools/dotc/core/Types.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 7a64e19c87a0..ac8a91868cdb 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1788,6 +1788,7 @@ object Types { false } override def computeHash = doHash((name, sig), prefix) + override def toString = super.toString ++ s"/withSig($sig)" } trait WithFixedSym extends NamedType { From bf3345a9d25e464975dd5000186c766de842f020 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Sep 2016 17:44:47 +0200 Subject: [PATCH 43/77] Fix ExplicitSelf phase After inlining we got a Ycheck error of the form: found : `this.asInstanceOf[SelfType].C` expected: `this.C` The fact that it was related inlining was coincidental I think. We fix the problem by expanding to this.asInstanceOf[SelfType & this.type].C instead. --- src/dotty/tools/dotc/transform/ExplicitSelf.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/transform/ExplicitSelf.scala b/src/dotty/tools/dotc/transform/ExplicitSelf.scala index c6a2181576e7..618a0f108541 100644 --- a/src/dotty/tools/dotc/transform/ExplicitSelf.scala +++ b/src/dotty/tools/dotc/transform/ExplicitSelf.scala @@ -16,9 +16,10 @@ import Flags._ * where `C` is a class with explicit self type and `C` is not a * subclass of the owner of `m` to * - * C.this.asInstanceOf[S].m + * C.this.asInstanceOf[S & C.this.type].m * * where `S` is the self type of `C`. + * See run/i789.scala for a test case why this is needed. */ class ExplicitSelf extends MiniPhaseTransform { thisTransform => import ast.tpd._ @@ -30,7 +31,7 @@ class ExplicitSelf extends MiniPhaseTransform { thisTransform => val cls = thiz.symbol.asClass val cinfo = cls.classInfo if (cinfo.givenSelfType.exists && !cls.derivesFrom(tree.symbol.owner)) - cpy.Select(tree)(thiz.asInstance(cinfo.selfType), name) + cpy.Select(tree)(thiz.asInstance(AndType(cinfo.selfType, thiz.tpe)), name) else tree case _ => tree } From ae67c35263287cd4cd987d4221cb030004f548e6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Sep 2016 20:11:45 +0200 Subject: [PATCH 44/77] Add accessors for non-public members accessed from inline methods This makes existsing uses of inline mostly compile. Todo: Verify that stdlib can be compiled. Todo: Implement accessors for assignments to priavte variables Todo: Figure out what to do with accesses to private types. --- src/dotty/tools/dotc/ast/TreeInfo.scala | 14 ++ src/dotty/tools/dotc/core/NameOps.scala | 3 + src/dotty/tools/dotc/core/StdNames.scala | 1 + .../tools/dotc/core/SymDenotations.scala | 8 +- src/dotty/tools/dotc/core/Types.scala | 18 +++ .../tools/dotc/core/tasty/TreePickler.scala | 2 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 4 +- src/dotty/tools/dotc/reporting/Reporter.scala | 2 +- src/dotty/tools/dotc/typer/Inliner.scala | 148 +++++++++++++++--- src/dotty/tools/dotc/typer/Namer.scala | 9 +- src/dotty/tools/dotc/typer/Typer.scala | 10 +- src/dotty/tools/dotc/util/Stats.scala | 2 +- 12 files changed, 187 insertions(+), 34 deletions(-) diff --git a/src/dotty/tools/dotc/ast/TreeInfo.scala b/src/dotty/tools/dotc/ast/TreeInfo.scala index 725838ef6d2d..0d7ebc5e17fd 100644 --- a/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -435,6 +435,20 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => } } + /** Decompose a call fn[targs](vargs_1)...(vargs_n) + * into its constituents (where targs, vargss may be empty) + */ + def decomposeCall(tree: Tree): (Tree, List[Tree], List[List[Tree]]) = tree match { + case Apply(fn, args) => + val (meth, targs, argss) = decomposeCall(fn) + (meth, targs, argss :+ args) + case TypeApply(fn, targs) => + val (meth, Nil, Nil) = decomposeCall(fn) + (meth, targs, Nil) + case _ => + (tree, Nil, Nil) + } + /** The variables defined by a pattern, in reverse order of their appearance. */ def patVars(tree: Tree)(implicit ctx: Context): List[Symbol] = { val acc = new TreeAccumulator[List[Symbol]] { diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index ddb0421bb4ad..48e823e81374 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -85,6 +85,7 @@ object NameOps { def isSelectorName = name.startsWith(" ") && name.tail.forall(_.isDigit) def isLazyLocal = name.endsWith(nme.LAZY_LOCAL) def isOuterSelect = name.endsWith(nme.OUTER_SELECT) + def isInlineAccessor = name.startsWith(nme.INLINE_ACCESSOR_PREFIX) /** Is name a variable name? */ def isVariableName: Boolean = name.length > 0 && { @@ -420,6 +421,8 @@ object NameOps { assert(name.isLazyLocal) name.dropRight(nme.LAZY_LOCAL.length) } + + def inlineAccessorName = nme.INLINE_ACCESSOR_PREFIX ++ name ++ "$" } private final val FalseSuper = "$$super".toTermName diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index fc26e05365e8..9ef7caa681a7 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -100,6 +100,7 @@ object StdNames { val EXPAND_SEPARATOR: N = "$$" val IMPL_CLASS_SUFFIX: N = "$class" val IMPORT: N = "" + val INLINE_ACCESSOR_PREFIX = "$inlineAccessor$" val INTERPRETER_IMPORT_WRAPPER: N = "$iw" val INTERPRETER_LINE_PREFIX: N = "line" val INTERPRETER_VAR_PREFIX: N = "res" diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 160d3bc3037c..977c76668ddb 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1526,7 +1526,13 @@ object SymDenotations { /** Enter a symbol in given `scope` without potentially replacing the old copy. */ def enterNoReplace(sym: Symbol, scope: MutableScope)(implicit ctx: Context): Unit = { - require((sym.denot.flagsUNSAFE is Private) || !(this is Frozen) || (scope ne this.unforcedDecls) || sym.hasAnnotation(defn.ScalaStaticAnnot)) + + require( + (sym.denot.flagsUNSAFE is Private) || + !(this is Frozen) || + (scope ne this.unforcedDecls) || + sym.hasAnnotation(defn.ScalaStaticAnnot) || + sym.name.isInlineAccessor) scope.enter(sym) if (myMemberFingerPrint != FingerPrint.unknown) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index ac8a91868cdb..0af673561cc8 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2555,6 +2555,24 @@ object Types { x => paramBounds mapConserve (_.subst(this, x).bounds), x => resType.subst(this, x)) + /** Merge nested polytypes into one polytype. nested polytypes are normally not suported + * but can arise as temporary data structures. + */ + def flatten(implicit ctx: Context): PolyType = resType match { + case that: PolyType => + val shift = new TypeMap { + def apply(t: Type) = t match { + case PolyParam(`that`, n) => PolyParam(that, n + paramNames.length) + case t => mapOver(t) + } + } + PolyType(paramNames ++ that.paramNames)( + x => this.paramBounds.mapConserve(_.subst(this, x).bounds) ++ + that.paramBounds.mapConserve(shift(_).subst(that, x).bounds), + x => shift(that.resultType).subst(that, x).subst(this, x)) + case _ => this + } + override def toString = s"PolyType($paramNames, $paramBounds, $resType)" override def computeHash = doHash(paramNames, resType, paramBounds) diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index dc37485f91bb..12ab050f68eb 100644 --- a/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -308,7 +308,7 @@ class TreePickler(pickler: TastyPickler) { if (!tree.isEmpty) pickleTree(tree) def pickleDef(tag: Int, sym: Symbol, tpt: Tree, rhs: Tree = EmptyTree, pickleParams: => Unit = ())(implicit ctx: Context) = { - assert(symRefs(sym) == NoAddr) + assert(symRefs(sym) == NoAddr, sym) registerDef(sym) writeByte(tag) withLength { diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 60cad0445365..4149f800cd13 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -490,8 +490,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, posUnpickle } else (annots.find(_.symbol == defn.InlineAnnot)) match { case Some(inlineAnnot) => - Inliner.attachBody(inlineAnnot, - forkAt(rhsStart).readTerm()(localContext(sym).addMode(Mode.ReadPositions))) + val inlineCtx = localContext(sym).addMode(Mode.ReadPositions) + Inliner.attachBody(inlineAnnot, implicit ctx => forkAt(rhsStart).readTerm())(inlineCtx) case none => } goto(start) diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index f3777473d676..75113d823880 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -154,7 +154,7 @@ trait Reporting { this: Context => if (printer eq config.Printers.noPrinter) op else doTraceIndented[T](question, printer, show)(op) - def doTraceIndented[T](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => T): T = { + private def doTraceIndented[T](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false)(op: => T): T = { def resStr(res: Any): String = res match { case res: printing.Showable if show => res.show case _ => String.valueOf(res) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 763d1ee94e53..286d971c6126 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -14,6 +14,7 @@ import Constants._ import StdNames.nme import Contexts.Context import Names.Name +import NameOps._ import SymDenotations.SymDenotation import Annotations.Annotation import transform.ExplicitOuter @@ -22,30 +23,144 @@ import config.Printers.inlining import ErrorReporting.errorTree import util.{Property, SourceFile, NoSource} import collection.mutable +import transform.TypeUtils._ object Inliner { import tpd._ - private class InlinedBody(tree: => Tree) { - lazy val body = tree + /** An attachment for inline methods, which contains + * + * - the inlined body, as a typed tree + * - + * + */ + private final class InlinedBody(treeExpr: Context => Tree, var inlineCtx: Context) { + private val inlineMethod = inlineCtx.owner + private val myAccessors = new mutable.ListBuffer[MemberDef] + private var myBody: Tree = _ + private var evaluated = false + + private def prepareForInline = new TreeMap { + def needsAccessor(sym: Symbol)(implicit ctx: Context) = + sym.is(AccessFlags) || sym.privateWithin.exists + def accessorName(implicit ctx: Context) = + ctx.freshNames.newName(inlineMethod.name.asTermName.inlineAccessorName.toString) + def accessorSymbol(tree: Tree, accessorInfo: Type)(implicit ctx: Context): Symbol = + ctx.newSymbol( + owner = inlineMethod.owner, + name = if (tree.isTerm) accessorName.toTermName else accessorName.toTypeName, + flags = if (tree.isTerm) Synthetic | Method else Synthetic, + info = accessorInfo, + coord = tree.pos).entered + override def transform(tree: Tree)(implicit ctx: Context): Tree = super.transform { + tree match { + case _: Apply | _: TypeApply | _: RefTree if needsAccessor(tree.symbol) => + if (tree.isTerm) { + val (methPart, targs, argss) = decomposeCall(tree) + val (accessorDef, accessorRef) = + if (methPart.symbol.isStatic) { + // Easy case: Reference to a static symbol + val accessorType = methPart.tpe.widen.ensureMethodic + val accessor = accessorSymbol(tree, accessorType).asTerm + val accessorDef = polyDefDef(accessor, tps => argss => + methPart.appliedToTypes(tps).appliedToArgss(argss)) + val accessorRef = ref(accessor).appliedToTypeTrees(targs).appliedToArgss(argss) + (accessorDef, accessorRef) + } + else { + // Hard case: Reference needs to go via a dyanmic prefix + val qual = qualifier(methPart) + inlining.println(i"adding inline accessor for $tree -> (${qual.tpe}, $methPart: ${methPart.getClass}, [$targs%, %], ($argss%, %))") + val dealiasMap = new TypeMap { + def apply(t: Type) = mapOver(t.dealias) + } + val qualType = dealiasMap(qual.tpe.widen) + def addQualType(tp: Type): Type = tp match { + case tp: PolyType => tp.derivedPolyType(tp.paramNames, tp.paramBounds, addQualType(tp.resultType)) + case tp: ExprType => addQualType(tp.resultType) + case tp => MethodType(qualType :: Nil, tp) + } + val localRefs = qualType.namedPartsWith(_.symbol.isContainedIn(inlineMethod)).toList + def abstractQualType(mtpe: Type): Type = + if (localRefs.isEmpty) mtpe + else PolyType.fromSymbols(localRefs.map(_.symbol), mtpe).asInstanceOf[PolyType].flatten + val accessorType = abstractQualType(addQualType(dealiasMap(methPart.tpe.widen))) + val accessor = accessorSymbol(tree, accessorType).asTerm + val accessorDef = polyDefDef(accessor, tps => argss => + argss.head.head.select(methPart.symbol) + .appliedToTypes(tps.drop(localRefs.length)) + .appliedToArgss(argss.tail)) + val accessorRef = ref(accessor) + .appliedToTypeTrees(localRefs.map(TypeTree(_)) ++ targs) + .appliedToArgss((qual :: Nil) :: argss) + (accessorDef, accessorRef) + } + myAccessors += accessorDef + inlining.println(i"added inline accessor: $accessorDef") + accessorRef + } else { + // TODO: Handle references to non-public types. + // This is quite tricky, as such types can appear anywhere, including as parts + // of types of other things. For the moment we do nothing and complain + // at the implicit expansion site if there's a reference to an inaccessible type. + // Draft code (incomplete): + // + // val accessor = accessorSymbol(tree, TypeAlias(tree.tpe)).asType + // myAccessors += TypeDef(accessor) + // ref(accessor) + // + tree + } + case _ => tree + } + } + } + + def isEvaluated = evaluated + private def ensureEvaluated()(implicit ctx: Context) = + if (!evaluated) { + evaluated = true + myBody = treeExpr(inlineCtx) + myBody = prepareForInline.transform(myBody)(inlineCtx) + inlining.println(i"inlinable body of ${inlineCtx.owner} = $myBody") + inlineCtx = null + } + + def body(implicit ctx: Context): Tree = { + ensureEvaluated() + myBody + } + + def accessors(implicit ctx: Context): List[MemberDef] = { + ensureEvaluated() + myAccessors.toList + } } private val InlinedBody = new Property.Key[InlinedBody] // to be used as attachment private val InlinedCalls = new Property.Key[List[Tree]] // to be used in context - def attachBody(inlineAnnot: Annotation, tree: => Tree)(implicit ctx: Context): Unit = - inlineAnnot.tree.putAttachment(InlinedBody, new InlinedBody(tree)) + def attachBody(inlineAnnot: Annotation, treeExpr: Context => Tree)(implicit ctx: Context): Unit = + inlineAnnot.tree.getAttachment(InlinedBody) match { + case Some(inlinedBody) if inlinedBody.isEvaluated => // keep existing attachment + case _ => + if (!ctx.isAfterTyper) + inlineAnnot.tree.putAttachment(InlinedBody, new InlinedBody(treeExpr, ctx)) + } private def inlinedBodyAttachment(sym: SymDenotation)(implicit ctx: Context): Option[InlinedBody] = sym.getAnnotation(defn.InlineAnnot).get.tree.getAttachment(InlinedBody) def hasInlinedBody(sym: SymDenotation)(implicit ctx: Context): Boolean = - inlinedBodyAttachment(sym).isDefined + sym.isInlineMethod && inlinedBodyAttachment(sym).isDefined def inlinedBody(sym: SymDenotation)(implicit ctx: Context): Tree = inlinedBodyAttachment(sym).get.body + def inlineAccessors(sym: SymDenotation)(implicit ctx: Context): List[MemberDef] = + inlinedBodyAttachment(sym).get.accessors + def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = if (enclosingInlineds.length < ctx.settings.xmaxInlines.value) new Inliner(tree, inlinedBody(tree.symbol)).inlined(pt) @@ -72,33 +187,24 @@ object Inliner { val file = call.symbol.sourceFile if (file != null && file.exists) new SourceFile(file) else NoSource } + + private def qualifier(tree: Tree)(implicit ctx: Context) = tree match { + case Select(qual, _) => qual + case SelectFromTypeTree(qual, _) => qual + case _ => This(ctx.owner.enclosingClass.asClass) + } } class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { import tpd._ import Inliner._ - private def decomposeCall(tree: Tree): (Tree, List[Tree], List[List[Tree]]) = tree match { - case Apply(fn, args) => - val (meth, targs, argss) = decomposeCall(fn) - (meth, targs, argss :+ args) - case TypeApply(fn, targs) => - val (meth, Nil, Nil) = decomposeCall(fn) - (meth, targs, Nil) - case _ => - (tree, Nil, Nil) - } - private val (methPart, targs, argss) = decomposeCall(call) private val meth = methPart.symbol + private val prefix = qualifier(methPart) for (targ <- targs) fullyDefinedType(targ.tpe, "inlined type argument", targ.pos) - private val prefix = methPart match { - case Select(qual, _) => qual - case _ => tpd.This(ctx.owner.enclosingClass.asClass) - } - private val thisProxy = new mutable.HashMap[Type, TermRef] private val paramProxy = new mutable.HashMap[Type, Type] private val paramBinding = new mutable.HashMap[Name, Type] diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 034a1ff505e5..ab16ad414c0b 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -566,14 +566,17 @@ class Namer { typer: Typer => val cls = typedAheadAnnotation(annotTree) val ann = Annotation.deferred(cls, implicit ctx => typedAnnotation(annotTree)) denot.addAnnotation(ann) - if (cls == defn.InlineAnnot) addInlineInfo(ann, original) + if (cls == defn.InlineAnnot) addInlineInfo(denot.symbol, ann, original) } case _ => } - private def addInlineInfo(inlineAnnot: Annotation, original: untpd.Tree) = original match { + private def addInlineInfo(inlineMethod: Symbol, inlineAnnot: Annotation, original: untpd.Tree) = original match { case original: untpd.DefDef => - Inliner.attachBody(inlineAnnot, typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs) + Inliner.attachBody( + inlineAnnot, + implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs + )(localContext(inlineMethod)) case _ => } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index fab1a1dd0778..b7b61e8de740 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1169,7 +1169,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit // Overwrite inline body to make sure it is not evaluated twice sym.getAnnotation(defn.InlineAnnot) match { - case Some(ann) => Inliner.attachBody(ann, rhs1) + case Some(ann) => Inliner.attachBody(ann, ctx => rhs1) case _ => } @@ -1493,7 +1493,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case Some(xtree) => traverse(xtree :: rest) case none => - buf += typed(mdef) + val mdef1 = typed(mdef) + buf += mdef1 + if (Inliner.hasInlinedBody(mdef1.symbol)) + buf ++= Inliner.inlineAccessors(mdef1.symbol) traverse(rest) } case Thicket(stats) :: rest => @@ -1793,8 +1796,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit tree } else if (tree.tpe <:< pt) - if (tree.symbol.isInlineMethod && - Inliner.hasInlinedBody(tree.symbol) && + if (Inliner.hasInlinedBody(tree.symbol) && !ctx.owner.ownersIterator.exists(_.isInlineMethod) && !ctx.settings.YnoInline.value && !ctx.isAfterTyper) diff --git a/src/dotty/tools/dotc/util/Stats.scala b/src/dotty/tools/dotc/util/Stats.scala index e06695dfb907..b7e0996f5fc1 100644 --- a/src/dotty/tools/dotc/util/Stats.scala +++ b/src/dotty/tools/dotc/util/Stats.scala @@ -24,7 +24,7 @@ import collection.mutable def record(fn: String, n: Int = 1) = if (enabled) doRecord(fn, n) - def doRecord(fn: String, n: Int) = + private def doRecord(fn: String, n: Int) = if (monitored) { val name = if (fn.startsWith("member-")) "member" else fn hits(name) += n From c9e45de0042e9a50397bf922d812d764b36e4d12 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Sep 2016 10:40:05 +0200 Subject: [PATCH 45/77] Support access for setting private vars from inlined code --- src/dotty/tools/dotc/typer/Inliner.scala | 97 ++++++++++++++---------- 1 file changed, 55 insertions(+), 42 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 286d971c6126..6ccd499c2bb1 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -41,10 +41,13 @@ object Inliner { private var evaluated = false private def prepareForInline = new TreeMap { + def needsAccessor(sym: Symbol)(implicit ctx: Context) = sym.is(AccessFlags) || sym.privateWithin.exists + def accessorName(implicit ctx: Context) = ctx.freshNames.newName(inlineMethod.name.asTermName.inlineAccessorName.toString) + def accessorSymbol(tree: Tree, accessorInfo: Type)(implicit ctx: Context): Symbol = ctx.newSymbol( owner = inlineMethod.owner, @@ -52,53 +55,59 @@ object Inliner { flags = if (tree.isTerm) Synthetic | Method else Synthetic, info = accessorInfo, coord = tree.pos).entered + + def addAccessor(tree: Tree, methPart: Tree, targs: List[Tree], argss: List[List[Tree]], + accessedType: Type, rhs: (Tree, List[Type], List[List[Tree]]) => Tree)(implicit ctx: Context): Tree = { + val (accessorDef, accessorRef) = + if (methPart.symbol.isStatic) { + // Easy case: Reference to a static symbol + val accessorType = accessedType.ensureMethodic + val accessor = accessorSymbol(tree, accessorType).asTerm + val accessorDef = polyDefDef(accessor, tps => argss => + rhs(methPart, tps, argss)) + val accessorRef = ref(accessor).appliedToTypeTrees(targs).appliedToArgss(argss) + (accessorDef, accessorRef) + } else { + // Hard case: Reference needs to go via a dyanmic prefix + val qual = qualifier(methPart) + inlining.println(i"adding inline accessor for $tree -> (${qual.tpe}, $methPart: ${methPart.getClass}, [$targs%, %], ($argss%, %))") + val dealiasMap = new TypeMap { + def apply(t: Type) = mapOver(t.dealias) + } + val qualType = dealiasMap(qual.tpe.widen) + def addQualType(tp: Type): Type = tp match { + case tp: PolyType => tp.derivedPolyType(tp.paramNames, tp.paramBounds, addQualType(tp.resultType)) + case tp: ExprType => addQualType(tp.resultType) + case tp => MethodType(qualType :: Nil, tp) + } + val localRefs = qualType.namedPartsWith(_.symbol.isContainedIn(inlineMethod)).toList + def abstractQualType(mtpe: Type): Type = + if (localRefs.isEmpty) mtpe + else PolyType.fromSymbols(localRefs.map(_.symbol), mtpe).asInstanceOf[PolyType].flatten + val accessorType = abstractQualType(addQualType(dealiasMap(accessedType))) + val accessor = accessorSymbol(tree, accessorType).asTerm + println(i"accessor: $accessorType") + val accessorDef = polyDefDef(accessor, tps => argss => + rhs(argss.head.head.select(methPart.symbol), tps.drop(localRefs.length), argss.tail)) + val accessorRef = ref(accessor) + .appliedToTypeTrees(localRefs.map(TypeTree(_)) ++ targs) + .appliedToArgss((qual :: Nil) :: argss) + (accessorDef, accessorRef) + } + myAccessors += accessorDef + inlining.println(i"added inline accessor: $accessorDef") + accessorRef + } + override def transform(tree: Tree)(implicit ctx: Context): Tree = super.transform { tree match { case _: Apply | _: TypeApply | _: RefTree if needsAccessor(tree.symbol) => if (tree.isTerm) { val (methPart, targs, argss) = decomposeCall(tree) - val (accessorDef, accessorRef) = - if (methPart.symbol.isStatic) { - // Easy case: Reference to a static symbol - val accessorType = methPart.tpe.widen.ensureMethodic - val accessor = accessorSymbol(tree, accessorType).asTerm - val accessorDef = polyDefDef(accessor, tps => argss => - methPart.appliedToTypes(tps).appliedToArgss(argss)) - val accessorRef = ref(accessor).appliedToTypeTrees(targs).appliedToArgss(argss) - (accessorDef, accessorRef) - } - else { - // Hard case: Reference needs to go via a dyanmic prefix - val qual = qualifier(methPart) - inlining.println(i"adding inline accessor for $tree -> (${qual.tpe}, $methPart: ${methPart.getClass}, [$targs%, %], ($argss%, %))") - val dealiasMap = new TypeMap { - def apply(t: Type) = mapOver(t.dealias) - } - val qualType = dealiasMap(qual.tpe.widen) - def addQualType(tp: Type): Type = tp match { - case tp: PolyType => tp.derivedPolyType(tp.paramNames, tp.paramBounds, addQualType(tp.resultType)) - case tp: ExprType => addQualType(tp.resultType) - case tp => MethodType(qualType :: Nil, tp) - } - val localRefs = qualType.namedPartsWith(_.symbol.isContainedIn(inlineMethod)).toList - def abstractQualType(mtpe: Type): Type = - if (localRefs.isEmpty) mtpe - else PolyType.fromSymbols(localRefs.map(_.symbol), mtpe).asInstanceOf[PolyType].flatten - val accessorType = abstractQualType(addQualType(dealiasMap(methPart.tpe.widen))) - val accessor = accessorSymbol(tree, accessorType).asTerm - val accessorDef = polyDefDef(accessor, tps => argss => - argss.head.head.select(methPart.symbol) - .appliedToTypes(tps.drop(localRefs.length)) - .appliedToArgss(argss.tail)) - val accessorRef = ref(accessor) - .appliedToTypeTrees(localRefs.map(TypeTree(_)) ++ targs) - .appliedToArgss((qual :: Nil) :: argss) - (accessorDef, accessorRef) - } - myAccessors += accessorDef - inlining.println(i"added inline accessor: $accessorDef") - accessorRef - } else { + addAccessor(tree, methPart, targs, argss, + accessedType = methPart.tpe.widen, + rhs = (qual, tps, argss) => qual.appliedToTypes(tps).appliedToArgss(argss)) + } else { // TODO: Handle references to non-public types. // This is quite tricky, as such types can appear anywhere, including as parts // of types of other things. For the moment we do nothing and complain @@ -111,6 +120,10 @@ object Inliner { // tree } + case Assign(lhs: RefTree, rhs) if needsAccessor(lhs.symbol) => + addAccessor(tree, lhs, Nil, (rhs :: Nil) :: Nil, + accessedType = MethodType(rhs.tpe.widen :: Nil, defn.UnitType), + rhs = (lhs, tps, argss) => lhs.becomes(argss.head.head)) case _ => tree } } From b1cda4fadcb586a82b546751d5cc426cd5382cd5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Sep 2016 18:39:49 +0200 Subject: [PATCH 46/77] Better names and documentation for Inliner. --- src/dotty/tools/dotc/ast/Trees.scala | 15 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- src/dotty/tools/dotc/typer/Inliner.scala | 256 ++++++++++++++---- src/dotty/tools/dotc/typer/Namer.scala | 2 +- src/dotty/tools/dotc/typer/Typer.scala | 6 +- 5 files changed, 222 insertions(+), 59 deletions(-) diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index fca1c1500730..7e7c74928b96 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -503,7 +503,20 @@ object Trees { override def toString = s"JavaSeqLiteral($elems, $elemtpt)" } - /** Inlined code */ + /** A tree representing inlined code. + * + * @param call The original call that was inlined + * @param bindings Bindings for proxies to be used in the inlined code + * @param expansion The inlined tree, minus bindings. + * + * The full inlined code is equivalent to + * + * { bindings; expansion } + * + * The reason to keep `bindings` separate is because they are typed in a + * different context: `bindings` represent the arguments to the inlined + * call, whereas `expansion` represents the body of the inlined function. + */ case class Inlined[-T >: Untyped] private[ast] (call: tpd.Tree, bindings: List[MemberDef[T]], expansion: Tree[T]) extends Tree[T] { type ThisTree[-T >: Untyped] = Inlined[T] diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 4149f800cd13..142aeb7dc6a0 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -491,7 +491,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, posUnpickle else (annots.find(_.symbol == defn.InlineAnnot)) match { case Some(inlineAnnot) => val inlineCtx = localContext(sym).addMode(Mode.ReadPositions) - Inliner.attachBody(inlineAnnot, implicit ctx => forkAt(rhsStart).readTerm())(inlineCtx) + Inliner.attachInlineInfo(inlineAnnot, implicit ctx => forkAt(rhsStart).readTerm())(inlineCtx) case none => } goto(start) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 6ccd499c2bb1..ca2daa4a1c06 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -13,7 +13,7 @@ import Decorators._ import Constants._ import StdNames.nme import Contexts.Context -import Names.Name +import Names.{Name, TermName} import NameOps._ import SymDenotations.SymDenotation import Annotations.Annotation @@ -30,24 +30,40 @@ object Inliner { /** An attachment for inline methods, which contains * - * - the inlined body, as a typed tree - * - + * - the body to inline, as a typed tree + * - the definitions of all needed accessors to non-public members from inlined code * + * @param treeExpr A function that computes the tree to be inlined, given a context + * This tree may still refer to non-public members. + * @param inlineCtx The context in which to compute the tree. This context needs + * to have the inlined method as owner. */ - private final class InlinedBody(treeExpr: Context => Tree, var inlineCtx: Context) { + private final class InlineInfo(treeExpr: Context => Tree, var inlineCtx: Context) { private val inlineMethod = inlineCtx.owner private val myAccessors = new mutable.ListBuffer[MemberDef] private var myBody: Tree = _ private var evaluated = false + /** A tree map which inserts accessors for all non-public term members accessed + * from inlined code. Non-public type members are currently left as they are. + * This means that references to a provate type will lead to typing failures + * on the code when it is inlined. Less than ideal, but hard to do better (see below). + */ private def prepareForInline = new TreeMap { + /** A definition needs an accessor if it is private, protected, or qualified private */ def needsAccessor(sym: Symbol)(implicit ctx: Context) = sym.is(AccessFlags) || sym.privateWithin.exists + /** The name of the next accessor to be generated */ def accessorName(implicit ctx: Context) = ctx.freshNames.newName(inlineMethod.name.asTermName.inlineAccessorName.toString) + /** A fresh accessor symbol. + * + * @param tree The tree representing the original access to the non-public member + * @param accessorInfo The type of the accessor + */ def accessorSymbol(tree: Tree, accessorInfo: Type)(implicit ctx: Context): Symbol = ctx.newSymbol( owner = inlineMethod.owner, @@ -56,39 +72,65 @@ object Inliner { info = accessorInfo, coord = tree.pos).entered - def addAccessor(tree: Tree, methPart: Tree, targs: List[Tree], argss: List[List[Tree]], + /** Add an accessor to a non-public method and replace the original access with a + * call to the accessor. + * + * @param tree The original access to the non-public symbol + * @param refPart The part that refers to the method or field of the original access + * @param targs All type arguments passed in the access, if any + * @param argss All value arguments passed in the access, if any + * @param accessedType The type of the accessed method or field, as seen from the access site. + * @param rhs A function that builds the right-hand side of the accessor, + * given a reference to the accessed symbol and any type and + * value arguments the need to be integrated. + * @return The call to the accessor method that replaces the original access. + */ + def addAccessor(tree: Tree, refPart: Tree, targs: List[Tree], argss: List[List[Tree]], accessedType: Type, rhs: (Tree, List[Type], List[List[Tree]]) => Tree)(implicit ctx: Context): Tree = { val (accessorDef, accessorRef) = - if (methPart.symbol.isStatic) { + if (refPart.symbol.isStatic) { // Easy case: Reference to a static symbol val accessorType = accessedType.ensureMethodic val accessor = accessorSymbol(tree, accessorType).asTerm val accessorDef = polyDefDef(accessor, tps => argss => - rhs(methPart, tps, argss)) + rhs(refPart, tps, argss)) val accessorRef = ref(accessor).appliedToTypeTrees(targs).appliedToArgss(argss) (accessorDef, accessorRef) } else { // Hard case: Reference needs to go via a dyanmic prefix - val qual = qualifier(methPart) - inlining.println(i"adding inline accessor for $tree -> (${qual.tpe}, $methPart: ${methPart.getClass}, [$targs%, %], ($argss%, %))") + val qual = qualifier(refPart) + inlining.println(i"adding inline accessor for $tree -> (${qual.tpe}, $refPart: ${refPart.getClass}, [$targs%, %], ($argss%, %))") + + // Need to dealias in order to catch all possible references to abstracted over types in + // substitutions val dealiasMap = new TypeMap { def apply(t: Type) = mapOver(t.dealias) } + val qualType = dealiasMap(qual.tpe.widen) + + // Add qualifier type as leading method argument to argument `tp` def addQualType(tp: Type): Type = tp match { case tp: PolyType => tp.derivedPolyType(tp.paramNames, tp.paramBounds, addQualType(tp.resultType)) case tp: ExprType => addQualType(tp.resultType) case tp => MethodType(qualType :: Nil, tp) } + + // The types that are local to the inlined method, and that therefore have + // to be abstracted out in the accessor, which is external to the inlined method val localRefs = qualType.namedPartsWith(_.symbol.isContainedIn(inlineMethod)).toList + + // Abstract accessed type over local refs def abstractQualType(mtpe: Type): Type = if (localRefs.isEmpty) mtpe else PolyType.fromSymbols(localRefs.map(_.symbol), mtpe).asInstanceOf[PolyType].flatten + val accessorType = abstractQualType(addQualType(dealiasMap(accessedType))) val accessor = accessorSymbol(tree, accessorType).asTerm - println(i"accessor: $accessorType") + val accessorDef = polyDefDef(accessor, tps => argss => - rhs(argss.head.head.select(methPart.symbol), tps.drop(localRefs.length), argss.tail)) + rhs(argss.head.head.select(refPart.symbol), tps.drop(localRefs.length), argss.tail)) + val accessorRef = ref(accessor) .appliedToTypeTrees(localRefs.map(TypeTree(_)) ++ targs) .appliedToArgss((qual :: Nil) :: argss) @@ -129,59 +171,94 @@ object Inliner { } } + /** Is the inline info evaluated? */ def isEvaluated = evaluated + private def ensureEvaluated()(implicit ctx: Context) = if (!evaluated) { - evaluated = true + evaluated = true // important to set early to prevent overwrites by attachInlineInfo in typedDefDef myBody = treeExpr(inlineCtx) myBody = prepareForInline.transform(myBody)(inlineCtx) inlining.println(i"inlinable body of ${inlineCtx.owner} = $myBody") - inlineCtx = null + inlineCtx = null // null out to avoid space leaks } + /** The body to inline */ def body(implicit ctx: Context): Tree = { ensureEvaluated() myBody } + /** The accessor defs to non-public members which need to be defined + * together with the inline method + */ def accessors(implicit ctx: Context): List[MemberDef] = { ensureEvaluated() myAccessors.toList } } - private val InlinedBody = new Property.Key[InlinedBody] // to be used as attachment + /** A key to be used in an attachment for `@inline` annotations */ + private val InlineInfo = new Property.Key[InlineInfo] + /** A key to be used in a context property that tracks enclosing inlined calls */ private val InlinedCalls = new Property.Key[List[Tree]] // to be used in context - def attachBody(inlineAnnot: Annotation, treeExpr: Context => Tree)(implicit ctx: Context): Unit = - inlineAnnot.tree.getAttachment(InlinedBody) match { - case Some(inlinedBody) if inlinedBody.isEvaluated => // keep existing attachment + /** Attach inline info to `@inline` annotation. + * + * @param treeExpr A function that computes the tree to be inlined, given a context + * This tree may still refer to non-public members. + * @param ctx The current context is the one in which the tree is computed. It needs + * to have the inlined method as owner. + */ + def attachInlineInfo(inlineAnnot: Annotation, treeExpr: Context => Tree)(implicit ctx: Context): Unit = + inlineAnnot.tree.getAttachment(InlineInfo) match { + case Some(inlineInfo) if inlineInfo.isEvaluated => // keep existing attachment case _ => if (!ctx.isAfterTyper) - inlineAnnot.tree.putAttachment(InlinedBody, new InlinedBody(treeExpr, ctx)) + inlineAnnot.tree.putAttachment(InlineInfo, new InlineInfo(treeExpr, ctx)) } - private def inlinedBodyAttachment(sym: SymDenotation)(implicit ctx: Context): Option[InlinedBody] = - sym.getAnnotation(defn.InlineAnnot).get.tree.getAttachment(InlinedBody) + /** Optionally, the inline info attached to the `@inline` annotation of `sym`. */ + private def inlineInfo(sym: SymDenotation)(implicit ctx: Context): Option[InlineInfo] = + sym.getAnnotation(defn.InlineAnnot).get.tree.getAttachment(InlineInfo) - def hasInlinedBody(sym: SymDenotation)(implicit ctx: Context): Boolean = - sym.isInlineMethod && inlinedBodyAttachment(sym).isDefined + /** Definition is an inline method with a known body to inline (note: definitions coming + * from Scala2x class files might be `@inline`, but still lack that body. + */ + def hasBodyToInline(sym: SymDenotation)(implicit ctx: Context): Boolean = + sym.isInlineMethod && inlineInfo(sym).isDefined - def inlinedBody(sym: SymDenotation)(implicit ctx: Context): Tree = - inlinedBodyAttachment(sym).get.body + /** The body to inline for method `sym`. + * @pre hasBodyToInline(sym) + */ + def bodyToInline(sym: SymDenotation)(implicit ctx: Context): Tree = + inlineInfo(sym).get.body + + /** The accessors to non-public members needed by the inlinable body of `sym`. + * @pre hasBodyToInline(sym) + */ def inlineAccessors(sym: SymDenotation)(implicit ctx: Context): List[MemberDef] = - inlinedBodyAttachment(sym).get.accessors + inlineInfo(sym).get.accessors + /** Try to inline a call to a `@inline` method. Fail with error if the maximal + * inline depth is exceeded. + * + * @param tree The call to inline + * @param pt The expected type of the call. + * @return An `Inlined` node that refers to the original call and the inlined bindings + * and body that replace it. + */ def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = if (enclosingInlineds.length < ctx.settings.xmaxInlines.value) - new Inliner(tree, inlinedBody(tree.symbol)).inlined(pt) + new Inliner(tree, bodyToInline(tree.symbol)).inlined(pt) else errorTree(tree, i"""Maximal number of successive inlines (${ctx.settings.xmaxInlines.value}) exceeded, | Maybe this is caused by a recursive inline method? | You can use -Xmax:inlines to change the limit.""") + /** Replace `Inlined` node by a block that contains its bindings and expansion */ def dropInlined(inlined: tpd.Inlined)(implicit ctx: Context): Tree = { val reposition = new TreeMap { override def transform(tree: Tree)(implicit ctx: Context): Tree = @@ -190,17 +267,27 @@ object Inliner { tpd.seq(inlined.bindings, reposition.transform(inlined.expansion)) } + /** A context derived form `ctx` that records `call` as innermost enclosing + * call for which the inlined version is currently processed. + */ def inlineContext(call: Tree)(implicit ctx: Context): Context = ctx.fresh.setProperty(InlinedCalls, call :: enclosingInlineds) + /** All enclosing calls that are currently inlined, from innermost to outermost */ def enclosingInlineds(implicit ctx: Context): List[Tree] = ctx.property(InlinedCalls).getOrElse(Nil) + /** The source file where the symbol of the `@inline` method referred to by `call` + * is defined + */ def sourceFile(call: Tree)(implicit ctx: Context) = { val file = call.symbol.sourceFile if (file != null && file.exists) new SourceFile(file) else NoSource } + /** The qualifier part of a Select, Ident, or SelectFromTypeTree tree. + * For an Ident, this is the `This` of the current class. (TODO: use elsewhere as well?) + */ private def qualifier(tree: Tree)(implicit ctx: Context) = tree match { case Select(qual, _) => qual case SelectFromTypeTree(qual, _) => qual @@ -208,6 +295,11 @@ object Inliner { } } +/** Produces an inlined version of `call` via its `inlined` method. + * + * @param call The original call to a `@inline` method + * @param rhs The body of the inline method that replaces the call. + */ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { import tpd._ import Inliner._ @@ -216,18 +308,37 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { private val meth = methPart.symbol private val prefix = qualifier(methPart) + // Make sure all type arguments to the call are fully determined for (targ <- targs) fullyDefinedType(targ.tpe, "inlined type argument", targ.pos) - private val thisProxy = new mutable.HashMap[Type, TermRef] - private val paramProxy = new mutable.HashMap[Type, Type] + /** A map from parameter names of the inline method to references of the actual arguments. + * For a type argument this is the full argument type. + * For a value argument, it is a reference to either the argument value + * (if the argument is a pure expression of singleton type), or to `val` or `def` acting + * as a proxy (if the argument is something else). + */ private val paramBinding = new mutable.HashMap[Name, Type] - val bindingsBuf = new mutable.ListBuffer[MemberDef] + + /** A map from references to (type and value) parameters of the inline method + * to their corresponding argument or proxy references, as given by `paramBinding`. + */ + private val paramProxy = new mutable.HashMap[Type, Type] + + /** A map from (direct and outer) this references in `rhs` to references of their proxies */ + private val thisProxy = new mutable.HashMap[Type, TermRef] + + /** A buffer for bindings that define proxies for actual arguments */ + val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] computeParamBindings(meth.info, targs, argss) private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = ctx.newSymbol(ctx.owner, name, flags, info, coord = call.pos) + /** Populate `paramBinding` and `bindingsBuf` by matching parameters with + * corresponding arguments. `bindingbuf` will be further extended later by + * proxies to this-references. + */ private def computeParamBindings(tp: Type, targs: List[Tree], argss: List[List[Tree]]): Unit = tp match { case tp: PolyType => (tp.paramNames, targs).zipped.foreach { (name, arg) => @@ -256,6 +367,17 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { assert(argss.isEmpty) } + /** Populate `thisProxy` and `paramProxy` as follows: + * + * 1a. If given type refers to a static this, thisProxy binds it to corresponding global reference, + * 1b. If given type refers to an instance this, create a proxy symbol and bind the thistype to + * refer to the proxy. The proxy is not yet entered in `bindingsBuf` that will come later. + * 2. If given type refers to a parameter, make `paramProxy` refer to the entry stored + * in `paramNames` under the parameter's name. This roundabout way to bind parameter + * references to proxies is done because we not known a priori what the parameter + * references of a method are (we only know the method's type, but that contains PolyParams + * and MethodParams, not TypeRefs or TermRefs. + */ private def registerType(tpe: Type): Unit = tpe match { case tpe: ThisType if !ctx.owner.isContainedIn(tpe.cls) && !tpe.cls.is(Package) && @@ -275,41 +397,34 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { case _ => } + /** Register type of leaf node */ private def registerLeaf(tree: Tree): Unit = tree match { case _: This | _: Ident | _: TypeTree => tree.tpe.foreachPart(registerType, stopAtStatic = true) case _ => } - private object InlineTyper extends ReTyper { - override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { - val res = super.typedSelect(tree, pt) - ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.pos) - res - } - override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { - val cond1 = typed(tree.cond, defn.BooleanType) - cond1.tpe.widenTermRefExpr match { - case ConstantType(Constant(condVal: Boolean)) => - val selected = typed(if (condVal) tree.thenp else tree.elsep, pt) - if (isIdempotentExpr(cond1)) selected - else Block(cond1 :: Nil, selected) - case _ => - val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) - super.typedIf(if1, pt) - } - } - } - + /** The Inlined node representing the inlined call */ def inlined(pt: Type) = { - if (!isIdempotentExpr(prefix)) registerType(meth.owner.thisType) // make sure prefix is computed + // make sure prefix is executed if it is impure + if (!isIdempotentExpr(prefix)) registerType(meth.owner.thisType) + + // Register types of all leaves of inlined body so that the `paramProxy` and `thisProxy` maps are defined. rhs.foreachSubTree(registerLeaf) - def classOf(sym: Symbol) = sym.info.widen.classSymbol - def outerSelector(sym: Symbol) = classOf(sym).name.toTermName ++ nme.OUTER_SELECT - def outerLevel(sym: Symbol) = classOf(sym).ownersIterator.length + // The class that the this-proxy `selfSym` represents + def classOf(selfSym: Symbol) = selfSym.info.widen.classSymbol + + // The name of the outer selector that computes the rhs of `selfSym` + def outerSelector(selfSym: Symbol): TermName = classOf(selfSym).name.toTermName ++ nme.OUTER_SELECT + + // The total nesting depth of the class represented by `selfSym`. + def outerLevel(selfSym: Symbol): Int = classOf(selfSym).ownersIterator.length + + // All needed this-proxies, sorted by nesting depth of the classes they represent (innermost first) val accessedSelfSyms = thisProxy.values.toList.map(_.symbol).sortBy(-outerLevel(_)) + // Compute val-definitions for all this-proxies and append them to `bindingsBuf` var lastSelf: Symbol = NoSymbol for (selfSym <- accessedSelfSyms) { val rhs = @@ -321,6 +436,8 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { lastSelf = selfSym } + // The type map to apply to the inlined tree. This maps references to this-types + // and parameters to type references of their arguments or proxies. val typeMap = new TypeMap { def apply(t: Type) = t match { case t: ThisType => thisProxy.getOrElse(t, t) @@ -330,6 +447,8 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { } } + // The tree map to apply to the inlined tree. This maps references to this-types + // and parameters to references of their arguments or their proxies. def treeMap(tree: Tree) = { tree match { case _: This => @@ -346,14 +465,45 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { case _ => tree }} + // The complete translation maps referenves to this and parameters to + // corresponding arguments or proxies on the type and term level. It also changes + // the owner from the inlined method to the current owner. val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil) + val bindings = bindingsBuf.toList.map(_.withPos(call.pos)) val expansion = inliner(rhs.withPos(call.pos)) + // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. val expansion1 = InlineTyper.typed(expansion, pt)(inlineContext(call)) val result = tpd.Inlined(call, bindings, expansion1) inlining.println(i"inlined $call\n --> \n$result") result } + + /** A typer for inlined code. Its purpose is: + * 1. Implement constant folding over inlined code + * 2. Selectively expand ifs with constant conditions + * 3. Make sure inlined code is type-correct. + * 4. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed) + */ + private object InlineTyper extends ReTyper { + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { + val res = super.typedSelect(tree, pt) + ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.pos) + res + } + override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { + val cond1 = typed(tree.cond, defn.BooleanType) + cond1.tpe.widenTermRefExpr match { + case ConstantType(Constant(condVal: Boolean)) => + val selected = typed(if (condVal) tree.thenp else tree.elsep, pt) + if (isIdempotentExpr(cond1)) selected + else Block(cond1 :: Nil, selected) + case _ => + val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) + super.typedIf(if1, pt) + } + } + } } diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index ab16ad414c0b..6f730a585b52 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -573,7 +573,7 @@ class Namer { typer: Typer => private def addInlineInfo(inlineMethod: Symbol, inlineAnnot: Annotation, original: untpd.Tree) = original match { case original: untpd.DefDef => - Inliner.attachBody( + Inliner.attachInlineInfo( inlineAnnot, implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs )(localContext(inlineMethod)) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index b7b61e8de740..8830537e3872 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1169,7 +1169,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit // Overwrite inline body to make sure it is not evaluated twice sym.getAnnotation(defn.InlineAnnot) match { - case Some(ann) => Inliner.attachBody(ann, ctx => rhs1) + case Some(ann) => Inliner.attachInlineInfo(ann, ctx => rhs1) case _ => } @@ -1495,7 +1495,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case none => val mdef1 = typed(mdef) buf += mdef1 - if (Inliner.hasInlinedBody(mdef1.symbol)) + if (Inliner.hasBodyToInline(mdef1.symbol)) buf ++= Inliner.inlineAccessors(mdef1.symbol) traverse(rest) } @@ -1796,7 +1796,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit tree } else if (tree.tpe <:< pt) - if (Inliner.hasInlinedBody(tree.symbol) && + if (Inliner.hasBodyToInline(tree.symbol) && !ctx.owner.ownersIterator.exists(_.isInlineMethod) && !ctx.settings.YnoInline.value && !ctx.isAfterTyper) From 184296f694d3623098864b8313b2ed7ccf7380f1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Sep 2016 21:41:56 +0200 Subject: [PATCH 47/77] Inline argument closures to inline methods If an argumnet to an inline method refers to a closure that is the result of eta-expanding another inline method inline the argument method. --- src/dotty/tools/dotc/ast/Desugar.scala | 10 ++++-- src/dotty/tools/dotc/typer/Inliner.scala | 39 +++++++++++++++++++++--- src/dotty/tools/dotc/typer/Typer.scala | 6 +++- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index 4e27da2cab3c..ecb6a3212f70 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -607,11 +607,17 @@ object desugar { * ==> * def $anonfun(params) = body * Closure($anonfun) + * + * If `inlineable` is true, tag $anonfun with an @inline annotation. */ - def makeClosure(params: List[ValDef], body: Tree, tpt: Tree = TypeTree()) = + def makeClosure(params: List[ValDef], body: Tree, tpt: Tree = TypeTree(), inlineable: Boolean)(implicit ctx: Context) = { + var mods = synthetic + if (inlineable) + mods = mods.withAddedAnnotation(New(ref(defn.InlineAnnotType), Nil).withPos(body.pos)) Block( - DefDef(nme.ANON_FUN, Nil, params :: Nil, tpt, body).withMods(synthetic), + DefDef(nme.ANON_FUN, Nil, params :: Nil, tpt, body).withMods(mods), Closure(Nil, Ident(nme.ANON_FUN), EmptyTree)) + } /** If `nparams` == 1, expand partial function * diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index ca2daa4a1c06..546988d2187f 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -470,29 +470,53 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { // the owner from the inlined method to the current owner. val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil) - val bindings = bindingsBuf.toList.map(_.withPos(call.pos)) val expansion = inliner(rhs.withPos(call.pos)) // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. val expansion1 = InlineTyper.typed(expansion, pt)(inlineContext(call)) - val result = tpd.Inlined(call, bindings, expansion1) + /** Does given definition bind a closure that will be inlined? */ + def bindsInlineableClosure(defn: ValOrDefDef) = Ident(defn.symbol.termRef) match { + case InlineableClosure(_) => true + case _ => false + } + + /** All bindings in `bindingsBuf` except bindings of inlineable closures */ + val bindings = bindingsBuf.toList.filterNot(bindsInlineableClosure).map(_.withPos(call.pos)) + + val result = tpd.Inlined(call, bindings, expansion1) inlining.println(i"inlined $call\n --> \n$result") result } + /** An extractor for references to closure arguments that refer to `@inline` methods */ + private object InlineableClosure { + lazy val paramProxies = paramProxy.values.toSet + def unapply(tree: Ident)(implicit ctx: Context): Option[Tree] = + if (paramProxies.contains(tree.tpe)) { + bindingsBuf.find(_.name == tree.name).get.rhs match { + case Closure(_, meth, _) if meth.symbol.isInlineMethod => Some(meth) + case Block(_, Closure(_, meth, _)) if meth.symbol.isInlineMethod => Some(meth) + case _ => None + } + } else None + } + /** A typer for inlined code. Its purpose is: * 1. Implement constant folding over inlined code * 2. Selectively expand ifs with constant conditions - * 3. Make sure inlined code is type-correct. - * 4. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed) + * 3. Inline arguments that are inlineable closures + * 4. Make sure inlined code is type-correct. + * 5. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed) */ private object InlineTyper extends ReTyper { + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { val res = super.typedSelect(tree, pt) ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.pos) res } + override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { val cond1 = typed(tree.cond, defn.BooleanType) cond1.tpe.widenTermRefExpr match { @@ -505,5 +529,12 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { super.typedIf(if1, pt) } } + + override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = tree.asInstanceOf[tpd.Tree] match { + case Apply(Select(InlineableClosure(fn), nme.apply), args) => + typed(fn.appliedToArgs(args), pt) + case _ => + super.typedApply(tree, pt) + } } } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 8830537e3872..c8362522dc41 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -744,7 +744,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case WildcardType(_) => untpd.TypeTree() case _ => untpd.TypeTree(protoResult) } - desugar.makeClosure(inferredParams, fnBody, resultTpt) + val inlineable = fnBody match { + case Apply(untpd.TypedSplice(fn), _) => Inliner.hasBodyToInline(fn.symbol) + case _ => false + } + desugar.makeClosure(inferredParams, fnBody, resultTpt, inlineable) } typed(desugared, pt) } From d1b933cac3380edcade3891aec6ed731744b2e13 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Sep 2016 23:36:26 +0200 Subject: [PATCH 48/77] Fix bug in InlineableClosure --- src/dotty/tools/dotc/typer/Inliner.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 546988d2187f..c640244c45d6 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -494,9 +494,9 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { lazy val paramProxies = paramProxy.values.toSet def unapply(tree: Ident)(implicit ctx: Context): Option[Tree] = if (paramProxies.contains(tree.tpe)) { - bindingsBuf.find(_.name == tree.name).get.rhs match { - case Closure(_, meth, _) if meth.symbol.isInlineMethod => Some(meth) - case Block(_, Closure(_, meth, _)) if meth.symbol.isInlineMethod => Some(meth) + bindingsBuf.find(_.name == tree.name).map(_.rhs) match { + case Some(Closure(_, meth, _)) if meth.symbol.isInlineMethod => Some(meth) + case Some(Block(_, Closure(_, meth, _))) if meth.symbol.isInlineMethod => Some(meth) case _ => None } } else None From 6bf7768e4e2e604f93f27efacf28d076d97ac951 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 10 Sep 2016 12:15:11 +0200 Subject: [PATCH 49/77] Refactoring for registering InlineInfo Now it's done on the symbol directly rather than its inline annotation. This simplifies client code and keeps the implementaion how inline infos should be assocated with inline methods open. --- src/dotty/tools/dotc/core/SymDenotations.scala | 9 +++++++++ src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 8 +++----- src/dotty/tools/dotc/typer/Inliner.scala | 7 +++++-- src/dotty/tools/dotc/typer/Namer.scala | 10 +++++----- src/dotty/tools/dotc/typer/Typer.scala | 6 ++---- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 977c76668ddb..9381f45ddb5c 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -281,6 +281,15 @@ object SymDenotations { case nil => None } + /** The same as getAnnotation, but without ensuring + * that the symbol carrying the annotation is completed + */ + final def unforcedAnnotation(cls: Symbol)(implicit ctx: Context): Option[Annotation] = + dropOtherAnnotations(myAnnotations, cls) match { + case annot :: _ => Some(annot) + case nil => None + } + /** Add given annotation to the annotations of this denotation */ final def addAnnotation(annot: Annotation): Unit = annotations = annot :: myAnnotations diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 142aeb7dc6a0..b148cced5953 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -488,11 +488,9 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, posUnpickle sym.completer.withDecls(newScope) forkAt(templateStart).indexTemplateParams()(localContext(sym)) } - else (annots.find(_.symbol == defn.InlineAnnot)) match { - case Some(inlineAnnot) => - val inlineCtx = localContext(sym).addMode(Mode.ReadPositions) - Inliner.attachInlineInfo(inlineAnnot, implicit ctx => forkAt(rhsStart).readTerm())(inlineCtx) - case none => + else if (annots.exists(_.symbol == defn.InlineAnnot)) { + val inlineCtx = localContext(sym).addMode(Mode.ReadPositions) + Inliner.registerInlineInfo(sym, implicit ctx => forkAt(rhsStart).readTerm())(inlineCtx) } goto(start) sym diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index c640244c45d6..e865c059678d 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -204,20 +204,23 @@ object Inliner { /** A key to be used in a context property that tracks enclosing inlined calls */ private val InlinedCalls = new Property.Key[List[Tree]] // to be used in context - /** Attach inline info to `@inline` annotation. + /** Register inline info for given inline method `sym`. * + * @param sym The symbol denotatioon of the inline method for which info is registered * @param treeExpr A function that computes the tree to be inlined, given a context * This tree may still refer to non-public members. * @param ctx The current context is the one in which the tree is computed. It needs * to have the inlined method as owner. */ - def attachInlineInfo(inlineAnnot: Annotation, treeExpr: Context => Tree)(implicit ctx: Context): Unit = + def registerInlineInfo(sym: SymDenotation, treeExpr: Context => Tree)(implicit ctx: Context): Unit = { + val inlineAnnot = sym.unforcedAnnotation(defn.InlineAnnot).get inlineAnnot.tree.getAttachment(InlineInfo) match { case Some(inlineInfo) if inlineInfo.isEvaluated => // keep existing attachment case _ => if (!ctx.isAfterTyper) inlineAnnot.tree.putAttachment(InlineInfo, new InlineInfo(treeExpr, ctx)) } + } /** Optionally, the inline info attached to the `@inline` annotation of `sym`. */ private def inlineInfo(sym: SymDenotation)(implicit ctx: Context): Option[InlineInfo] = diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 6f730a585b52..5e2ff416480a 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -566,17 +566,17 @@ class Namer { typer: Typer => val cls = typedAheadAnnotation(annotTree) val ann = Annotation.deferred(cls, implicit ctx => typedAnnotation(annotTree)) denot.addAnnotation(ann) - if (cls == defn.InlineAnnot) addInlineInfo(denot.symbol, ann, original) + if (cls == defn.InlineAnnot) addInlineInfo(denot, original) } case _ => } - private def addInlineInfo(inlineMethod: Symbol, inlineAnnot: Annotation, original: untpd.Tree) = original match { + private def addInlineInfo(denot: SymDenotation, original: untpd.Tree) = original match { case original: untpd.DefDef => - Inliner.attachInlineInfo( - inlineAnnot, + Inliner.registerInlineInfo( + denot, implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs - )(localContext(inlineMethod)) + )(localContext(denot.symbol)) case _ => } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index c8362522dc41..d1a2ad5c245f 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1172,10 +1172,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val rhs1 = typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx) // Overwrite inline body to make sure it is not evaluated twice - sym.getAnnotation(defn.InlineAnnot) match { - case Some(ann) => Inliner.attachInlineInfo(ann, ctx => rhs1) - case _ => - } + if (sym.hasAnnotation(defn.InlineAnnot)) + Inliner.registerInlineInfo(sym, ctx => rhs1) if (sym.isAnonymousFunction) { // If we define an anonymous function, make sure the return type does not From 8e66f5384837ba662eb6243e221e18e7364757ee Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 10 Sep 2016 12:15:22 +0200 Subject: [PATCH 50/77] More inline tests --- tests/run/inlineArrowAssoc.scala | 24 ++++++++++++++ tests/run/inlineLazy.scala | 6 ++++ tests/run/inlinePrivates.scala | 54 ++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 tests/run/inlineArrowAssoc.scala create mode 100644 tests/run/inlineLazy.scala create mode 100644 tests/run/inlinePrivates.scala diff --git a/tests/run/inlineArrowAssoc.scala b/tests/run/inlineArrowAssoc.scala new file mode 100644 index 000000000000..c3625609a14a --- /dev/null +++ b/tests/run/inlineArrowAssoc.scala @@ -0,0 +1,24 @@ +import scala.collection.immutable._ + +import scala.collection.mutable.{ Builder, ListBuffer } + +object Test { + + private val defaultOrdering = Map[Numeric[_], Ordering[_]]( + Numeric.BigIntIsIntegral -> Ordering.BigInt, + Numeric.IntIsIntegral -> Ordering.Int + ) + + final implicit class ArrowAssoc[A](private val self: A) extends AnyVal { + @inline def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y) + def →[B](y: B): Tuple2[A, B] = ->(y) + } + + def main(args: Array[String]): Unit = { + assert((1 -> 2) == (1, 2)) + assert((1 → 2) == (1, 2)) + } + + +} + diff --git a/tests/run/inlineLazy.scala b/tests/run/inlineLazy.scala new file mode 100644 index 000000000000..a8aa92498a79 --- /dev/null +++ b/tests/run/inlineLazy.scala @@ -0,0 +1,6 @@ +class Test { + + lazy val x: Int = 33 + + println(x) +} diff --git a/tests/run/inlinePrivates.scala b/tests/run/inlinePrivates.scala new file mode 100644 index 000000000000..ade4592dfb60 --- /dev/null +++ b/tests/run/inlinePrivates.scala @@ -0,0 +1,54 @@ +object Test { + + class C[T](private val x: T) { + + private def foo[Z](z: Z): T = x + + private var y: T = _ + + @inline def get1 = x + @inline def get2[U](c: C[U]) = c.x + + @inline def foo1(x: Int) = foo(x) + @inline def foo2[U](c: C[U]) = c.foo(x) + + @inline def set1(z: T) = { y = z; y } + @inline def set2[U](c: C[U]) = { c.y = c.x; c.y } + } + + object CC { + private val x = 3 + @inline def get1 = x + } + + def main(args: Array[String]) = { + val cc = new C(2) + assert(cc.get1 == 2) + assert(cc.get2(cc) == 2) + assert(cc.foo1(1) == 2) + assert(cc.foo2(cc) == 2) + assert(cc.set1(3) == 3) + assert(cc.set2(cc) == 2) +object Test { + + @inline + def swap[T](x: T, x_= : T => Unit, y: T, y_= : T => Unit) = { + val t = x + x_=(y) + y_=(t) + } + + def main(args: Array[String]) = { + var x = 1 + var y = 2 + @inline def setX(z: Int) = x = z + @inline def setY(z: Int) = y = z + swap[Int](x, setX, y, setY) + assert(x == 2 && y == 1) + } +} + + assert(CC.get1 == 3) + } + +} From e93b7bfe770c8950a52d17bb0aebd3e0a5e93b3c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 10 Sep 2016 18:17:36 +0200 Subject: [PATCH 51/77] Don't add inline accessors twice Make sure that inline accessors are not added twice. We got lucky so far because the fact that annotations are lazy meant that attachments did not persist. But if @inline was made into a strict annotation, inline accessors were indeed added twice. --- src/dotty/tools/dotc/typer/Inliner.scala | 10 ++++++---- src/dotty/tools/dotc/typer/Typer.scala | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index e865c059678d..cd042c476911 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -192,9 +192,11 @@ object Inliner { /** The accessor defs to non-public members which need to be defined * together with the inline method */ - def accessors(implicit ctx: Context): List[MemberDef] = { + def removeAccessors(implicit ctx: Context): List[MemberDef] = { ensureEvaluated() - myAccessors.toList + val res = myAccessors.toList + myAccessors.clear() + res } } @@ -242,8 +244,8 @@ object Inliner { * @pre hasBodyToInline(sym) */ - def inlineAccessors(sym: SymDenotation)(implicit ctx: Context): List[MemberDef] = - inlineInfo(sym).get.accessors + def removeInlineAccessors(sym: SymDenotation)(implicit ctx: Context): List[MemberDef] = + inlineInfo(sym).get.removeAccessors /** Try to inline a call to a `@inline` method. Fail with error if the maximal * inline depth is exceeded. diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index d1a2ad5c245f..dde2c866b7c2 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1498,7 +1498,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val mdef1 = typed(mdef) buf += mdef1 if (Inliner.hasBodyToInline(mdef1.symbol)) - buf ++= Inliner.inlineAccessors(mdef1.symbol) + buf ++= Inliner.removeInlineAccessors(mdef1.symbol) traverse(rest) } case Thicket(stats) :: rest => From d575e585389b025d7b8056bcb43fea67dddd15d0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 10 Sep 2016 20:58:34 +0200 Subject: [PATCH 52/77] Make inline a keyword `inline` is now a modifier keyword. To keep disruption tolerable, we still allow `@inline` as an annotation as well. Other uses of `inline` are supported only under `-language:Scala2` and are rewritten to identifiers in backticks. --- .../tools/dotc/config/ScalaSettings.scala | 1 - src/dotty/tools/dotc/core/Flags.scala | 5 ++- src/dotty/tools/dotc/core/StdNames.scala | 1 + src/dotty/tools/dotc/parsing/Parsers.scala | 19 ++++------- src/dotty/tools/dotc/parsing/Scanners.scala | 29 +++++++++++++++-- src/dotty/tools/dotc/parsing/Tokens.scala | 6 ++-- .../dotc/printing/SyntaxHighlighting.scala | 2 +- src/dotty/tools/dotc/typer/Namer.scala | 15 ++++++++- tests/neg/inlineAccess/C_1.scala | 3 +- tests/pos/rbtree.scala | 10 +++--- tests/run/inline/inlines_1.scala | 12 +++---- tests/run/inlineLazy.scala | 6 ---- tests/run/inlinePower/power_1.scala | 3 +- tests/run/inlinePrivates.scala | 32 ++++--------------- tests/run/inlinedAssign.scala | 6 ++-- 15 files changed, 80 insertions(+), 70 deletions(-) delete mode 100644 tests/run/inlineLazy.scala diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index 42e761c2e247..ff17a993991a 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -121,7 +121,6 @@ class ScalaSettings extends Settings.SettingGroup { val debugOwners = BooleanSetting("-Ydebug-owners", "Print all owners of definitions (requires -Yprint-syms)") //val doc = BooleanSetting ("-Ydoc", "Generate documentation") val termConflict = ChoiceSetting("-Yresolve-term-conflict", "strategy", "Resolve term conflicts", List("package", "object", "error"), "error") - val inline = BooleanSetting("-Yinline", "Perform inlining when possible.") val inlineHandlers = BooleanSetting("-Yinline-handlers", "Perform exception handler inlining when possible.") val YinlinerWarnings = BooleanSetting("-Yinline-warnings", "Emit inlining warnings. (Normally surpressed due to high volume)") val Ylinearizer = ChoiceSetting("-Ylinearizer", "which", "Linearizer to use", List("normal", "dfs", "rpo", "dump"), "rpo") diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index 0cdae6b98c17..bc5953a2c2e3 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -450,7 +450,7 @@ object Flags { AccessFlags | Module | Package | Deferred | Final | MethodOrHKCommon | Param | ParamAccessor | Scala2ExistentialCommon | Mutable.toCommonFlags | InSuperCall | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | ExpandedName | AccessorOrSealed | CaseAccessorOrBaseTypeArg | Fresh | Frozen | Erroneous | ImplicitCommon | Permanent | Synthetic | - LazyOrTrait | SuperAccessorOrScala2x | SelfNameOrImplClass + Inline | LazyOrTrait | SuperAccessorOrScala2x | SelfNameOrImplClass assert(FromStartFlags.isTermFlags && FromStartFlags.isTypeFlags) // TODO: Should check that FromStartFlags do not change in completion @@ -541,6 +541,9 @@ object Flags { /** A type parameter with synthesized name */ final val ExpandedTypeParam = allOf(ExpandedName, TypeParam) + /** An inline method */ + final val InlineMethod = allOf(Inline, Method) + /** A parameter or parameter accessor */ final val ParamOrAccessor = Param | ParamAccessor diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index 9ef7caa681a7..c52264637551 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -46,6 +46,7 @@ object StdNames { final val IFkw: N = kw("if") final val IMPLICITkw: N = kw("implicit") final val IMPORTkw: N = kw("import") + final val INLINEkw: N = kw("inline") final val LAZYkw: N = kw("lazy") final val MACROkw: N = kw("macro") final val MATCHkw: N = kw("match") diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index 86330f3abc1a..2fd79f999740 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -275,21 +275,12 @@ object Parsers { } finally inFunReturnType = saved } - private val isScala2Mode = - ctx.settings.language.value.contains(nme.Scala2.toString) - def migrationWarningOrError(msg: String, offset: Int = in.offset) = - if (isScala2Mode) + if (in.isScala2Mode) ctx.migrationWarning(msg, source atPos Position(offset)) else syntaxError(msg, offset) - /** Cannot use ctx.featureEnabled because accessing the context would force too much */ - private def testScala2Mode(msg: String, pos: Position = Position(in.offset)) = { - if (isScala2Mode) ctx.migrationWarning(msg, source atPos pos) - isScala2Mode - } - /* ---------- TREE CONSTRUCTION ------------------------------------------- */ /** Convert tree to formal parameter list @@ -1467,6 +1458,7 @@ object Parsers { case ABSTRACT => Abstract case FINAL => Final case IMPLICIT => ImplicitCommon + case INLINE => Inline case LAZY => Lazy case OVERRIDE => Override case PRIVATE => Private @@ -1570,7 +1562,10 @@ object Parsers { /** Annotation ::= `@' SimpleType {ParArgumentExprs} */ def annot() = - adjustStart(accept(AT)) { ensureApplied(parArgumentExprss(wrapNew(simpleType()))) } + adjustStart(accept(AT)) { + if (in.token == INLINE) in.token = BACKQUOTED_IDENT // allow for now + ensureApplied(parArgumentExprss(wrapNew(simpleType()))) + } def annotations(skipNewLines: Boolean = false): List[Tree] = { if (skipNewLines) newLineOptWhenFollowedBy(AT) @@ -1856,7 +1851,7 @@ object Parsers { val toInsert = if (in.token == LBRACE) s"$resultTypeStr =" else ": Unit " // trailing space ensures that `def f()def g()` works. - testScala2Mode(s"Procedure syntax no longer supported; `$toInsert' should be inserted here") && { + in.testScala2Mode(s"Procedure syntax no longer supported; `$toInsert' should be inserted here") && { patch(source, Position(in.lastOffset), toInsert) true } diff --git a/src/dotty/tools/dotc/parsing/Scanners.scala b/src/dotty/tools/dotc/parsing/Scanners.scala index b46ab634877f..a1a21583c5c9 100644 --- a/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/src/dotty/tools/dotc/parsing/Scanners.scala @@ -12,7 +12,7 @@ import scala.annotation.{ switch, tailrec } import scala.collection.mutable import mutable.ListBuffer import Utility.isNameStart - +import rewrite.Rewrites.patch object Scanners { @@ -108,6 +108,7 @@ object Scanners { target.token = toToken(idx) } } + def toToken(idx: Int): Token /** Clear buffer and set string */ @@ -212,8 +213,22 @@ object Scanners { /** A buffer for comments */ val commentBuf = new StringBuilder + private def handleMigration(keyword: Token): Token = + if (!isScala2Mode) keyword + else if (keyword == INLINE) treatAsIdent() + else keyword + + + private def treatAsIdent() = { + testScala2Mode(i"$name is now a keyword, put in `...` to keep as an identifier") + patch(source, Position(offset), "`") + patch(source, Position(offset + name.length), "`") + IDENTIFIER + } + def toToken(idx: Int): Token = - if (idx >= 0 && idx <= lastKeywordStart) kwArray(idx) else IDENTIFIER + if (idx >= 0 && idx <= lastKeywordStart) handleMigration(kwArray(idx)) + else IDENTIFIER private class TokenData0 extends TokenData @@ -235,6 +250,16 @@ object Scanners { */ var sepRegions: List[Token] = List() +// Scala 2 compatibility + + val isScala2Mode = ctx.settings.language.value.contains(nme.Scala2.toString) + + /** Cannot use ctx.featureEnabled because accessing the context would force too much */ + def testScala2Mode(msg: String, pos: Position = Position(offset)) = { + if (isScala2Mode) ctx.migrationWarning(msg, source atPos pos) + isScala2Mode + } + // Get next token ------------------------------------------------------------ /** Are we directly in a string interpolation expression? diff --git a/src/dotty/tools/dotc/parsing/Tokens.scala b/src/dotty/tools/dotc/parsing/Tokens.scala index b490cd1330f9..5324207dbf86 100644 --- a/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/src/dotty/tools/dotc/parsing/Tokens.scala @@ -91,6 +91,7 @@ abstract class TokensCommon { //final val LAZY = 59; enter(LAZY, "lazy") //final val THEN = 60; enter(THEN, "then") //final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate + //final val INLINE = 62; enter(INLINE, "inline") /** special symbols */ final val COMMA = 70; enter(COMMA, "','") @@ -171,6 +172,7 @@ object Tokens extends TokensCommon { final val LAZY = 59; enter(LAZY, "lazy") final val THEN = 60; enter(THEN, "then") final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate + final val INLINE = 62; enter(INLINE, "inline") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -188,7 +190,7 @@ object Tokens extends TokensCommon { /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate - final val alphaKeywords = tokenRange(IF, FORSOME) + final val alphaKeywords = tokenRange(IF, INLINE) final val symbolicKeywords = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens = tokenRange(COMMA, VIEWBOUND) final val keywords = alphaKeywords | symbolicKeywords @@ -214,7 +216,7 @@ object Tokens extends TokensCommon { final val defIntroTokens = templateIntroTokens | dclIntroTokens final val localModifierTokens = BitSet( - ABSTRACT, FINAL, SEALED, IMPLICIT, LAZY) + ABSTRACT, FINAL, SEALED, IMPLICIT, INLINE, LAZY) final val accessModifierTokens = BitSet( PRIVATE, PROTECTED) diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 67aa24243ab6..83c4289765a2 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -22,7 +22,7 @@ object SyntaxHighlighting { private def annotation(str: String) = AnnotationColor + str + NoColor private val keywords: Seq[String] = for { - index <- IF to FORSOME // All alpha keywords + index <- IF to INLINE // All alpha keywords } yield tokenString(index) private val interpolationPrefixes = diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 5e2ff416480a..a3306145366a 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -562,11 +562,24 @@ class Namer { typer: Typer => protected def addAnnotations(denot: SymDenotation): Unit = original match { case original: untpd.MemberDef => + var hasInlineAnnot = false for (annotTree <- untpd.modsDeco(original).mods.annotations) { val cls = typedAheadAnnotation(annotTree) val ann = Annotation.deferred(cls, implicit ctx => typedAnnotation(annotTree)) denot.addAnnotation(ann) - if (cls == defn.InlineAnnot) addInlineInfo(denot, original) + if (cls == defn.InlineAnnot) { + hasInlineAnnot = true + addInlineInfo(denot, original) + } + } + if (!hasInlineAnnot && denot.is(InlineMethod)) { + // create a @inline annotation. Currently, the inlining trigger + // is really the annotation, not the flag. This is done so that + // we can still compile inline methods from Scala2x. Once we stop + // being compatible with Scala2 we should revise the logic to + // be based on the flag. Then creating a separate annotation becomes unnecessary. + denot.addAnnotation(Annotation(defn.InlineAnnot)) + addInlineInfo(denot, original) } case _ => } diff --git a/tests/neg/inlineAccess/C_1.scala b/tests/neg/inlineAccess/C_1.scala index 6db1ea787018..349f5b1508dd 100644 --- a/tests/neg/inlineAccess/C_1.scala +++ b/tests/neg/inlineAccess/C_1.scala @@ -2,7 +2,6 @@ package p { class C { protected def f(): Unit = () - @inline - def inl() = f() // error (when inlined): not accessible + inline def inl() = f() // error (when inlined): not accessible } } diff --git a/tests/pos/rbtree.scala b/tests/pos/rbtree.scala index 1401a123143d..04c084596222 100644 --- a/tests/pos/rbtree.scala +++ b/tests/pos/rbtree.scala @@ -430,12 +430,12 @@ object RedBlackTree { * An alternative is to implement the these classes using plain old Java code... */ sealed abstract class Tree[A, +B]( - @(inline @getter) final val key: A, - @(inline @getter) final val value: B, - @(inline @getter) final val left: Tree[A, B], - @(inline @getter) final val right: Tree[A, B]) + @(`inline` @getter) final val key: A, + @(`inline` @getter) final val value: B, + @(`inline` @getter) final val left: Tree[A, B], + @(`inline` @getter) final val right: Tree[A, B]) extends Serializable { - @(inline @getter) final val count: Int = 1 + RedBlackTree.count(left) + RedBlackTree.count(right) + @(`inline` @getter) final val count: Int = 1 + RedBlackTree.count(left) + RedBlackTree.count(right) def black: Tree[A, B] def red: Tree[A, B] } diff --git a/tests/run/inline/inlines_1.scala b/tests/run/inline/inlines_1.scala index 8189e6805477..24f1c78fe237 100644 --- a/tests/run/inline/inlines_1.scala +++ b/tests/run/inline/inlines_1.scala @@ -5,8 +5,7 @@ object inlines { final val monitored = false - @inline - def f(x: Int): Int = x * x + inline def f(x: Int): Int = x * x val hits = new mutable.HashMap[String, Int] { override def default(key: String): Int = 0 @@ -21,8 +20,7 @@ object inlines { @volatile private var stack: List[String] = Nil - @inline - def track[T](fn: String)(op: => T) = + inline def track[T](fn: String)(op: => T) = if (monitored) { stack = fn :: stack record(fn) @@ -34,9 +32,9 @@ object inlines { def f = "Outer.f" class Inner { val msg = " Inner" - @inline def m = msg - @inline def g = f - @inline def h = f ++ m + inline def m = msg + inline def g = f + inline def h = f ++ m } val inner = new Inner } diff --git a/tests/run/inlineLazy.scala b/tests/run/inlineLazy.scala deleted file mode 100644 index a8aa92498a79..000000000000 --- a/tests/run/inlineLazy.scala +++ /dev/null @@ -1,6 +0,0 @@ -class Test { - - lazy val x: Int = 33 - - println(x) -} diff --git a/tests/run/inlinePower/power_1.scala b/tests/run/inlinePower/power_1.scala index 23da6009a097..4e96d7caa264 100644 --- a/tests/run/inlinePower/power_1.scala +++ b/tests/run/inlinePower/power_1.scala @@ -2,8 +2,7 @@ package p object pow { - @inline - def power(x: Double, n: Int): Double = + inline def power(x: Double, n: Int): Double = if (n == 0) 1.0 else if (n == 1) x else { diff --git a/tests/run/inlinePrivates.scala b/tests/run/inlinePrivates.scala index ade4592dfb60..ce438ae8d8a0 100644 --- a/tests/run/inlinePrivates.scala +++ b/tests/run/inlinePrivates.scala @@ -6,19 +6,19 @@ object Test { private var y: T = _ - @inline def get1 = x - @inline def get2[U](c: C[U]) = c.x + inline def get1 = x + inline def get2[U](c: C[U]) = c.x - @inline def foo1(x: Int) = foo(x) - @inline def foo2[U](c: C[U]) = c.foo(x) + inline def foo1(x: Int) = foo(x) + inline def foo2[U](c: C[U]) = c.foo(x) - @inline def set1(z: T) = { y = z; y } - @inline def set2[U](c: C[U]) = { c.y = c.x; c.y } + inline def set1(z: T) = { y = z; y } + inline def set2[U](c: C[U]) = { c.y = c.x; c.y } } object CC { private val x = 3 - @inline def get1 = x + inline def get1 = x } def main(args: Array[String]) = { @@ -29,24 +29,6 @@ object Test { assert(cc.foo2(cc) == 2) assert(cc.set1(3) == 3) assert(cc.set2(cc) == 2) -object Test { - - @inline - def swap[T](x: T, x_= : T => Unit, y: T, y_= : T => Unit) = { - val t = x - x_=(y) - y_=(t) - } - - def main(args: Array[String]) = { - var x = 1 - var y = 2 - @inline def setX(z: Int) = x = z - @inline def setY(z: Int) = y = z - swap[Int](x, setX, y, setY) - assert(x == 2 && y == 1) - } -} assert(CC.get1 == 3) } diff --git a/tests/run/inlinedAssign.scala b/tests/run/inlinedAssign.scala index b9a5d287dc4e..bb81aea2655f 100644 --- a/tests/run/inlinedAssign.scala +++ b/tests/run/inlinedAssign.scala @@ -1,6 +1,6 @@ object Test { - @inline + @`inline` def swap[T](x: T, x_= : T => Unit, y: T, y_= : T => Unit) = { val t = x x_=(y) @@ -10,8 +10,8 @@ object Test { def main(args: Array[String]) = { var x = 1 var y = 2 - @inline def setX(z: Int) = x = z - @inline def setY(z: Int) = y = z + @`inline` def setX(z: Int) = x = z + @`inline` def setY(z: Int) = y = z swap[Int](x, setX, y, setY) assert(x == 2 && y == 1) } From b743a9b3b98c67fd4e86c7700bf24e3c1d19e2b2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 11 Sep 2016 12:12:06 +0200 Subject: [PATCH 53/77] Make inline methods and field effectively final --- src/dotty/tools/dotc/core/Flags.scala | 2 +- src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index bc5953a2c2e3..cf3b23c91fab 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -530,7 +530,7 @@ object Flags { final val MethodOrLazyOrDeferred = Method | Lazy | Deferred /** Labeled `private` or `final` */ - final val PrivateOrFinal = Private | Final + final val PrivateOrFinalOrInline = Private | Final | Inline /** A private method */ final val PrivateMethod = allOf(Private, Method) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 9381f45ddb5c..8a9e8494ab65 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -865,7 +865,7 @@ object SymDenotations { /** A symbol is effectively final if it cannot be overridden in a subclass */ final def isEffectivelyFinal(implicit ctx: Context): Boolean = - is(PrivateOrFinal) || !owner.isClass || owner.is(ModuleOrFinal) || owner.isAnonymousClass + is(PrivateOrFinalOrInline) || !owner.isClass || owner.is(ModuleOrFinal) || owner.isAnonymousClass /** The class containing this denotation which has the given effective name. */ final def enclosingClassNamed(name: Name)(implicit ctx: Context): Symbol = { From 975d297cc42dd170fe8869474038a4204771a7a1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 11 Sep 2016 13:48:02 +0200 Subject: [PATCH 54/77] Fix problem related to accessor generation under separate compilation Accessors were multiply generated under separate compilation. To fix this, the resident body of an inlined function is now the same as the inlined body. Both use accessors where necessary. Previously, only the inlined body used accessors. --- src/dotty/tools/dotc/typer/Inliner.scala | 8 +++++++- src/dotty/tools/dotc/typer/ReTyper.scala | 1 + src/dotty/tools/dotc/typer/Typer.scala | 18 ++++++++++++++---- tests/neg/inlineAccess/C_1.scala | 12 +++++++----- tests/run/inlineAccess/C_1.scala | 7 +++++++ tests/run/inlineAccess/Test_2.scala | 7 +++++++ 6 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 tests/run/inlineAccess/C_1.scala create mode 100644 tests/run/inlineAccess/Test_2.scala diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index cd042c476911..acf50632769a 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -175,13 +175,19 @@ object Inliner { def isEvaluated = evaluated private def ensureEvaluated()(implicit ctx: Context) = - if (!evaluated) { + try if (!evaluated) { evaluated = true // important to set early to prevent overwrites by attachInlineInfo in typedDefDef myBody = treeExpr(inlineCtx) myBody = prepareForInline.transform(myBody)(inlineCtx) inlining.println(i"inlinable body of ${inlineCtx.owner} = $myBody") inlineCtx = null // null out to avoid space leaks } + else assert(myBody != null) + catch { + case ex: AssertionError => + println(i"failure while expanding $inlineMethod") + throw ex + } /** The body to inline */ def body(implicit ctx: Context): Tree = { diff --git a/src/dotty/tools/dotc/typer/ReTyper.scala b/src/dotty/tools/dotc/typer/ReTyper.scala index 1db6b54afd4e..2413c0c2235c 100644 --- a/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/src/dotty/tools/dotc/typer/ReTyper.scala @@ -104,4 +104,5 @@ class ReTyper extends Typer { override def inferView(from: Tree, to: Type)(implicit ctx: Context): Implicits.SearchResult = Implicits.NoImplicitMatches override def checkCanEqual(ltp: Type, rtp: Type, pos: Position)(implicit ctx: Context): Unit = () + override def inlineExpansion(mdef: DefDef)(implicit ctx: Context): List[Tree] = mdef :: Nil } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index dde2c866b7c2..60f04b268c4a 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1495,10 +1495,12 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case Some(xtree) => traverse(xtree :: rest) case none => - val mdef1 = typed(mdef) - buf += mdef1 - if (Inliner.hasBodyToInline(mdef1.symbol)) - buf ++= Inliner.removeInlineAccessors(mdef1.symbol) + typed(mdef) match { + case mdef1: DefDef if Inliner.hasBodyToInline(mdef1.symbol) => + buf ++= inlineExpansion(mdef1) + case mdef1 => + buf += mdef1 + } traverse(rest) } case Thicket(stats) :: rest => @@ -1512,6 +1514,14 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit traverse(stats) } + /** Given an inline method `mdef`, the method rewritten so that its body + * uses accessors to access non-public members, followed by the accessor definitions. + * Overwritten in Retyper to return `mdef` unchanged. + */ + protected def inlineExpansion(mdef: DefDef)(implicit ctx: Context): List[Tree] = + tpd.cpy.DefDef(mdef)(rhs = Inliner.bodyToInline(mdef.symbol)) :: + Inliner.removeInlineAccessors(mdef.symbol) + def typedExpr(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = typed(tree, pt)(ctx retractMode Mode.PatternOrType) def typedType(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = // todo: retract mode between Type and Pattern? diff --git a/tests/neg/inlineAccess/C_1.scala b/tests/neg/inlineAccess/C_1.scala index 349f5b1508dd..841222cf449c 100644 --- a/tests/neg/inlineAccess/C_1.scala +++ b/tests/neg/inlineAccess/C_1.scala @@ -1,7 +1,9 @@ -package p { +// error not yet recognized (independent of inlining) +package p +private class D class C { - protected def f(): Unit = () - - inline def inl() = f() // error (when inlined): not accessible -} + inline def inl(): Unit = { + val d = new D() // error (when inlined): not accessible + } } + diff --git a/tests/run/inlineAccess/C_1.scala b/tests/run/inlineAccess/C_1.scala new file mode 100644 index 000000000000..349f5b1508dd --- /dev/null +++ b/tests/run/inlineAccess/C_1.scala @@ -0,0 +1,7 @@ +package p { +class C { + protected def f(): Unit = () + + inline def inl() = f() // error (when inlined): not accessible +} +} diff --git a/tests/run/inlineAccess/Test_2.scala b/tests/run/inlineAccess/Test_2.scala new file mode 100644 index 000000000000..98ea7693abb7 --- /dev/null +++ b/tests/run/inlineAccess/Test_2.scala @@ -0,0 +1,7 @@ + +object Test { + def main(args: Array[String]) = { + val c = new p.C() + c.inl() + } +} From b559aff35d6d365284fc1e05a3ca49a17551df29 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 11 Sep 2016 14:01:58 +0200 Subject: [PATCH 55/77] Add accessibility check for type of new --- src/dotty/tools/dotc/typer/Typer.scala | 1 + tests/neg/inlineAccess/C_1.scala | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 60f04b268c4a..d99d85fbabdd 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -432,6 +432,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit typed(cpy.Block(tree)(clsDef :: Nil, New(Ident(x), Nil)), pt) case _ => var tpt1 = typedType(tree.tpt) + tpt1 = tpt1.withType(ensureAccessible(tpt1.tpe, superAccess = false, tpt1.pos)) tpt1.tpe.dealias match { case TypeApplications.EtaExpansion(tycon) => tpt1 = tpt1.withType(tycon) case _ => diff --git a/tests/neg/inlineAccess/C_1.scala b/tests/neg/inlineAccess/C_1.scala index 841222cf449c..9d34fa3f0f1a 100644 --- a/tests/neg/inlineAccess/C_1.scala +++ b/tests/neg/inlineAccess/C_1.scala @@ -1,4 +1,3 @@ -// error not yet recognized (independent of inlining) package p private class D class C { From 845b689a4a652fa79a7d0621f5ebe15bbf9225c7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Sep 2016 13:01:28 +0200 Subject: [PATCH 56/77] Add inline for vals - allow inline as an alternative to final for vals (final is retained for backwards compatibility for now) - allow inline for parameters - check that rhs of inline value has a constant type - check that arguments to inline value parameters have constant type - check that inline members are not deferred - make inline members effectively final --- docs/SyntaxSummary.txt | 5 ++-- .../annotation/internal/InlineParam.scala | 6 +++++ src/dotty/tools/dotc/core/Definitions.scala | 2 ++ src/dotty/tools/dotc/core/Flags.scala | 6 +++++ src/dotty/tools/dotc/core/Types.scala | 15 +++++++++++- src/dotty/tools/dotc/parsing/Parsers.scala | 15 ++++++++---- src/dotty/tools/dotc/typer/Checking.scala | 9 +++++++ src/dotty/tools/dotc/typer/Namer.scala | 2 +- src/dotty/tools/dotc/typer/Typer.scala | 7 +++++- tests/neg/inlinevals.scala | 24 +++++++++++++++++++ 10 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 src/dotty/annotation/internal/InlineParam.scala create mode 100644 tests/neg/inlinevals.scala diff --git a/docs/SyntaxSummary.txt b/docs/SyntaxSummary.txt index 6c83c71abece..519180775d1b 100644 --- a/docs/SyntaxSummary.txt +++ b/docs/SyntaxSummary.txt @@ -231,14 +231,15 @@ grammar. ClsParamClauses ::= {ClsParamClause} [[nl] `(' `implicit' ClsParams `)'] ClsParamClause ::= [nl] `(' [ClsParams] ')' ClsParams ::= ClsParam {`' ClsParam} - ClsParam ::= {Annotation} [{Modifier} (`val' | `var')] Param ValDef(mods, id, tpe, expr) -- point of mods on val/var + ClsParam ::= {Annotation} + [{Modifier} (`val' | `var') | `inline'] Param ValDef(mods, id, tpe, expr) -- point of mods on val/var Param ::= id `:' ParamType [`=' Expr] | INT DefParamClauses ::= {DefParamClause} [[nl] `(' `implicit' DefParams `)'] DefParamClause ::= [nl] `(' [DefParams] ')' DefParams ::= DefParam {`,' DefParam} - DefParam ::= {Annotation} Param ValDef(mods, id, tpe, expr) -- point of mods at id. + DefParam ::= {Annotation} [`inline'] Param ValDef(mods, id, tpe, expr) -- point of mods at id. Bindings ::= `(' Binding {`,' Binding `)' bindings Binding ::= (id | `_') [`:' Type] ValDef(_, id, tpe, EmptyTree) diff --git a/src/dotty/annotation/internal/InlineParam.scala b/src/dotty/annotation/internal/InlineParam.scala new file mode 100644 index 000000000000..a144f9edbdf5 --- /dev/null +++ b/src/dotty/annotation/internal/InlineParam.scala @@ -0,0 +1,6 @@ +package dotty.annotation.internal + +import scala.annotation.Annotation + +/** An annotation produced by Namer to indicate an inline parameter */ +final class InlineParam() extends Annotation diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 8bf3403377f5..12677edb68d0 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -468,6 +468,8 @@ class Definitions { def ImplicitNotFoundAnnot(implicit ctx: Context) = ImplicitNotFoundAnnotType.symbol.asClass lazy val InlineAnnotType = ctx.requiredClassRef("scala.inline") def InlineAnnot(implicit ctx: Context) = InlineAnnotType.symbol.asClass + lazy val InlineParamAnnotType = ctx.requiredClassRef("dotty.annotation.internal.InlineParam") + def InlineParamAnnot(implicit ctx: Context) = InlineParamAnnotType.symbol.asClass lazy val InvariantBetweenAnnotType = ctx.requiredClassRef("dotty.annotation.internal.InvariantBetween") def InvariantBetweenAnnot(implicit ctx: Context) = InvariantBetweenAnnotType.symbol.asClass lazy val MigrationAnnotType = ctx.requiredClassRef("scala.annotation.migration") diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index cf3b23c91fab..1a1182c7be75 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -556,6 +556,12 @@ object Flags { /** A type parameter or type parameter accessor */ final val TypeParamOrAccessor = TypeParam | TypeParamAccessor + /** A deferred member or a parameter accessor (these don't have right hand sides) */ + final val DeferredOrParamAccessor = Deferred | ParamAccessor + + /** value that's final or inline */ + final val FinalOrInline = Final | Inline + /** If symbol of a type alias has these flags, prefer the alias */ final val AliasPreferred = TypeParam | BaseTypeArg | ExpandedName diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 0af673561cc8..0c9a127016f0 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2419,7 +2419,12 @@ object Types { apply(nme.syntheticParamNames(paramTypes.length), paramTypes)(resultTypeExp) def apply(paramTypes: List[Type], resultType: Type)(implicit ctx: Context): MethodType = apply(nme.syntheticParamNames(paramTypes.length), paramTypes, resultType) + + /** Produce method type from parameter symbols, with special mappings for repeated + * and inline parameters. + */ def fromSymbols(params: List[Symbol], resultType: Type)(implicit ctx: Context) = { + /** Replace @repeated annotations on Seq or Array types by types */ def translateRepeated(tp: Type): Type = tp match { case tp @ ExprType(tp1) => tp.derivedExprType(translateRepeated(tp1)) case AnnotatedType(tp, annot) if annot matches defn.RepeatedAnnot => @@ -2429,7 +2434,15 @@ object Types { case tp => tp } - def paramInfo(param: Symbol): Type = translateRepeated(param.info) + /** Add @inlineParam to inline call-by-value parameters */ + def translateInline(tp: Type): Type = tp match { + case tp @ ExprType(tp1) => tp + case _ => AnnotatedType(tp, Annotation(defn.InlineParamAnnot)) + } + def paramInfo(param: Symbol): Type = { + val paramType = translateRepeated(param.info) + if (param.is(Inline)) translateInline(paramType) else paramType + } def transformResult(mt: MethodType) = resultType.subst(params, (0 until params.length).toList map (MethodParam(mt, _))) apply(params map (_.name.asTermName), params map paramInfo)(transformResult _) diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index 2fd79f999740..0a25bf801be2 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1641,12 +1641,13 @@ object Parsers { /** ClsParamClauses ::= {ClsParamClause} [[nl] `(' `implicit' ClsParams `)'] * ClsParamClause ::= [nl] `(' [ClsParams] ')' * ClsParams ::= ClsParam {`' ClsParam} - * ClsParam ::= {Annotation} [{Modifier} (`val' | `var')] id `:' ParamType [`=' Expr] + * ClsParam ::= {Annotation} [{Modifier} (`val' | `var') | `inline'] Param * DefParamClauses ::= {DefParamClause} [[nl] `(' `implicit' DefParams `)'] * DefParamClause ::= [nl] `(' [DefParams] ')' * DefParams ::= DefParam {`,' DefParam} - * DefParam ::= {Annotation} id `:' ParamType [`=' Expr] - */ + * DefParam ::= {Annotation} [`inline'] Param + * Param ::= id `:' ParamType [`=' Expr] + */ def paramClauses(owner: Name, ofCaseClass: Boolean = false): List[List[ValDef]] = { var implicitFlag = EmptyFlags var firstClauseOfCaseClass = ofCaseClass @@ -1665,12 +1666,16 @@ object Parsers { in.nextToken() addFlag(mods, Mutable) } else { - if (!(mods.flags &~ ParamAccessor).isEmpty) syntaxError("`val' or `var' expected") + if (!(mods.flags &~ (ParamAccessor | Inline)).isEmpty) + syntaxError("`val' or `var' expected") if (firstClauseOfCaseClass) mods else mods | PrivateLocal } } } - else mods = atPos(start) { mods | Param } + else { + if (in.token == INLINE) mods = addModifier(mods) + mods = atPos(start) { mods | Param } + } atPos(start, nameStart) { val name = ident() val tpt = diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index 415cd5d6a55e..42eede5e90b2 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -344,6 +344,7 @@ object Checking { fail(i"only classes can have declared but undefined members$varNote") checkWithDeferred(Private) checkWithDeferred(Final) + checkWithDeferred(Inline) } if (sym.isValueClass && sym.is(Trait) && !sym.isRefinementClass) fail(i"$sym cannot extend AnyVal") @@ -479,6 +480,13 @@ trait Checking { tp } + /** Check that `tree` is a pure expression of constant type */ + def checkInlineConformant(tree: Tree, what: => String)(implicit ctx: Context): Unit = + tree.tpe.widenTermRefExpr match { + case tp: ConstantType if isPureExpr(tree) => // ok + case _ => ctx.error(em"$what must be a constant expression", tree.pos) + } + /** Check that class does not define same symbol twice */ def checkNoDoubleDefs(cls: Symbol)(implicit ctx: Context): Unit = { val seen = new mutable.HashMap[Name, List[Symbol]] { @@ -543,6 +551,7 @@ trait NoChecking extends Checking { override def checkClassType(tp: Type, pos: Position, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = tp override def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = () override def checkFeasible(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp + override def checkInlineConformant(tree: Tree, what: => String)(implicit ctx: Context) = () override def checkNoDoubleDefs(cls: Symbol)(implicit ctx: Context): Unit = () override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context) = () override def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = tpt diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index a3306145366a..2e714ab6de02 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -891,7 +891,7 @@ class Namer { typer: Typer => // println(s"final inherited for $sym: ${inherited.toString}") !!! // println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}") - def isInline = sym.is(Final, butNot = Method | Mutable) + def isInline = sym.is(FinalOrInline, butNot = Method | Mutable) // Widen rhs type and approximate `|' but keep ConstantTypes if // definition is inline (i.e. final in Scala2). diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index d99d85fbabdd..58fb47f2cb19 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1140,6 +1140,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case rhs => typedExpr(rhs, tpt1.tpe) } val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) + if (sym.is(Inline, butNot = DeferredOrParamAccessor)) + checkInlineConformant(rhs1, "right-hand side of inline value") patchIfLazy(vdef1) vdef1 } @@ -1808,7 +1810,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } tree } - else if (tree.tpe <:< pt) + else if (tree.tpe <:< pt) { + if (pt.hasAnnotation(defn.InlineParamAnnot)) + checkInlineConformant(tree, "argument to inline parameter") if (Inliner.hasBodyToInline(tree.symbol) && !ctx.owner.ownersIterator.exists(_.isInlineMethod) && !ctx.settings.YnoInline.value && @@ -1822,6 +1826,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit tree.asInstance(pt) else tree + } else if (wtp.isInstanceOf[MethodType]) missingArgs else { typr.println(i"adapt to subtype ${tree.tpe} !<:< $pt") diff --git a/tests/neg/inlinevals.scala b/tests/neg/inlinevals.scala new file mode 100644 index 000000000000..184aa2168772 --- /dev/null +++ b/tests/neg/inlinevals.scala @@ -0,0 +1,24 @@ +object Test { + + def power(x: Double, inline n: Int): Double = ??? + + inline val N = 10 + def X = 20 + + inline inline val twice = 30 // error: repeated modifier + + class C(inline x: Int, private inline val y: Int) { + inline val foo: Int // error: abstract member may not be inline + inline def bar: Int // error: abstract member may not be inline + } + + power(2.0, N) // ok, since it's a by-name parameter + power(2.0, X) // error: argument to inline parameter must be a constant expression + + inline val M = X // error: rhs must be constant expression + + def byname(inline f: => String): Int = ??? // ok + + byname("hello" ++ " world") + +} From 7537cfcfa5ccf47d400e2841b9bc4ff2cac1eada Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Sep 2016 15:55:04 +0200 Subject: [PATCH 57/77] Document deviations from inline SIP --- src/dotty/tools/dotc/typer/Inliner.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index acf50632769a..c110013636ac 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -25,6 +25,13 @@ import util.{Property, SourceFile, NoSource} import collection.mutable import transform.TypeUtils._ +/** Todo wrt inline SIP: + * + * 1. According to Inline SIP, by-name parameters are not hoisted out, but we currently + * do hoist them. + * + * 2. Inline call-by-name parameters are currently ignored. Not sure what the rules should be. + */ object Inliner { import tpd._ From 4de907a313e9b85058cd9611116a1cbcf2bd3a4f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Sep 2016 18:53:53 +0200 Subject: [PATCH 58/77] Always use implicit context at the current period An implicit method might be unpickled in one run and the implicit body might be selected first in a subsequent run. In that case the inlined code was read with the original context, but that context needs to run at the current period. This resulted in denotation out of date errors in bringForward. Another problem with this design was space leaks: An context might survive multiple runs as part of an ImplicitInfo of an unpickled method. The new design avoids both problems. Implicit contexts are always up to date and leaks are avoided. --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 7 ++- src/dotty/tools/dotc/typer/Inliner.scala | 45 ++++++++++++------- src/dotty/tools/dotc/typer/Namer.scala | 4 +- src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/run/inlinedAssign.scala | 9 ++-- 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index b148cced5953..1b84341296e7 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -489,8 +489,11 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, posUnpickle forkAt(templateStart).indexTemplateParams()(localContext(sym)) } else if (annots.exists(_.symbol == defn.InlineAnnot)) { - val inlineCtx = localContext(sym).addMode(Mode.ReadPositions) - Inliner.registerInlineInfo(sym, implicit ctx => forkAt(rhsStart).readTerm())(inlineCtx) + Inliner.registerInlineInfo( + sym, + implicit ctx => forkAt(rhsStart).readTerm(), + implicit ctx => localContext(sym).addMode(Mode.ReadPositions)) + // Previous line avoids space leaks because it does not capture the current context. } goto(start) sym diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index c110013636ac..44946c37b1d5 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -42,11 +42,16 @@ object Inliner { * * @param treeExpr A function that computes the tree to be inlined, given a context * This tree may still refer to non-public members. - * @param inlineCtx The context in which to compute the tree. This context needs + * @param inlineCtxFn A function that maps the current context to the context in + * which to compute the tree. The resulting context needs * to have the inlined method as owner. + * + * The reason to use a function rather than a fixed context here + * is to avoid space leaks. InlineInfos can survive multiple runs + * because they might be created as part of an unpickled method + * and consumed only in a future run (or never). */ - private final class InlineInfo(treeExpr: Context => Tree, var inlineCtx: Context) { - private val inlineMethod = inlineCtx.owner + private final class InlineInfo(treeExpr: Context => Tree, var inlineCtxFn: Context => Context) { private val myAccessors = new mutable.ListBuffer[MemberDef] private var myBody: Tree = _ private var evaluated = false @@ -56,7 +61,8 @@ object Inliner { * This means that references to a provate type will lead to typing failures * on the code when it is inlined. Less than ideal, but hard to do better (see below). */ - private def prepareForInline = new TreeMap { + private def prepareForInline(inlineCtx: Context) = new TreeMap { + val inlineMethod = inlineCtx.owner /** A definition needs an accessor if it is private, protected, or qualified private */ def needsAccessor(sym: Symbol)(implicit ctx: Context) = @@ -182,19 +188,22 @@ object Inliner { def isEvaluated = evaluated private def ensureEvaluated()(implicit ctx: Context) = - try if (!evaluated) { + if (!evaluated) { evaluated = true // important to set early to prevent overwrites by attachInlineInfo in typedDefDef - myBody = treeExpr(inlineCtx) - myBody = prepareForInline.transform(myBody)(inlineCtx) - inlining.println(i"inlinable body of ${inlineCtx.owner} = $myBody") - inlineCtx = null // null out to avoid space leaks + val inlineCtx = inlineCtxFn(ctx) + inlineCtxFn = null // null out to avoid space leaks + try { + myBody = treeExpr(inlineCtx) + myBody = prepareForInline(inlineCtx).transform(myBody)(inlineCtx) + inlining.println(i"inlinable body of ${inlineCtx.owner} = $myBody") + } + catch { + case ex: AssertionError => + println(i"failure while expanding ${inlineCtx.owner}") + throw ex + } } else assert(myBody != null) - catch { - case ex: AssertionError => - println(i"failure while expanding $inlineMethod") - throw ex - } /** The body to inline */ def body(implicit ctx: Context): Tree = { @@ -224,16 +233,18 @@ object Inliner { * @param sym The symbol denotatioon of the inline method for which info is registered * @param treeExpr A function that computes the tree to be inlined, given a context * This tree may still refer to non-public members. - * @param ctx The current context is the one in which the tree is computed. It needs + * @param inlineCtxFn A function that maps the current context to the context in + * which to compute the tree. The resulting context needs * to have the inlined method as owner. */ - def registerInlineInfo(sym: SymDenotation, treeExpr: Context => Tree)(implicit ctx: Context): Unit = { + def registerInlineInfo( + sym: SymDenotation, treeExpr: Context => Tree, inlineCtxFn: Context => Context)(implicit ctx: Context): Unit = { val inlineAnnot = sym.unforcedAnnotation(defn.InlineAnnot).get inlineAnnot.tree.getAttachment(InlineInfo) match { case Some(inlineInfo) if inlineInfo.isEvaluated => // keep existing attachment case _ => if (!ctx.isAfterTyper) - inlineAnnot.tree.putAttachment(InlineInfo, new InlineInfo(treeExpr, ctx)) + inlineAnnot.tree.putAttachment(InlineInfo, new InlineInfo(treeExpr, inlineCtxFn)) } } diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 2e714ab6de02..9ba6a8963b30 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -588,8 +588,8 @@ class Namer { typer: Typer => case original: untpd.DefDef => Inliner.registerInlineInfo( denot, - implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs - )(localContext(denot.symbol)) + implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs, + _ => localContext(denot.symbol)) case _ => } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 58fb47f2cb19..85102841e2f1 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1176,7 +1176,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit // Overwrite inline body to make sure it is not evaluated twice if (sym.hasAnnotation(defn.InlineAnnot)) - Inliner.registerInlineInfo(sym, ctx => rhs1) + Inliner.registerInlineInfo(sym, _ => rhs1, _ => ctx) if (sym.isAnonymousFunction) { // If we define an anonymous function, make sure the return type does not diff --git a/tests/run/inlinedAssign.scala b/tests/run/inlinedAssign.scala index bb81aea2655f..f405f5073acf 100644 --- a/tests/run/inlinedAssign.scala +++ b/tests/run/inlinedAssign.scala @@ -1,7 +1,6 @@ object Test { - @`inline` - def swap[T](x: T, x_= : T => Unit, y: T, y_= : T => Unit) = { + inline def swap[T](x: T, x_= : T => Unit, y: T, y_= : T => Unit) = { val t = x x_=(y) y_=(t) @@ -10,9 +9,9 @@ object Test { def main(args: Array[String]) = { var x = 1 var y = 2 - @`inline` def setX(z: Int) = x = z - @`inline` def setY(z: Int) = y = z - swap[Int](x, setX, y, setY) + inline def setX(z: Int) = x = z + inline def setY(z: Int) = y = z + swap(x, setX, y, setY) assert(x == 2 && y == 1) } } From 65551d19b5d928f231426c016e561051d68d9c97 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Sep 2016 12:24:42 +0200 Subject: [PATCH 59/77] Don't drop inline closure bindings that are referred in the body The body might still refer to an inline closure argument without fully applying it. In that case the binding may not be dropped. --- src/dotty/tools/dotc/ast/TreeInfo.scala | 22 ++++++++++++++++------ src/dotty/tools/dotc/typer/Inliner.scala | 20 +++++++++++++++----- tests/run/inlinedAssign.scala | 4 ++++ 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/dotty/tools/dotc/ast/TreeInfo.scala b/src/dotty/tools/dotc/ast/TreeInfo.scala index 0d7ebc5e17fd..421ff5919734 100644 --- a/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -88,12 +88,6 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => case mp => mp } - /** If tree is a closure, it's body, otherwise tree itself */ - def closureBody(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { - case Block((meth @ DefDef(nme.ANON_FUN, _, _, _, _)) :: Nil, Closure(_, _, _)) => meth.rhs - case _ => tree - } - /** If this is an application, its function part, stripping all * Apply nodes (but leaving TypeApply nodes in). Otherwise the tree itself. */ @@ -449,6 +443,22 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => (tree, Nil, Nil) } + /** An extractor for closures, either contained in a block or standalone. + */ + object closure { + def unapply(tree: Tree): Option[(List[Tree], Tree, Tree)] = tree match { + case Block(_, Closure(env, meth, tpt)) => Some(env, meth, tpt) + case Closure(env, meth, tpt) => Some(env, meth, tpt) + case _ => None + } + } + + /** If tree is a closure, it's body, otherwise tree itself */ + def closureBody(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { + case Block((meth @ DefDef(nme.ANON_FUN, _, _, _, _)) :: Nil, Closure(_, _, _)) => meth.rhs + case _ => tree + } + /** The variables defined by a pattern, in reverse order of their appearance. */ def patVars(tree: Tree)(implicit ctx: Context): List[Symbol] = { val acc = new TreeAccumulator[List[Symbol]] { diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 44946c37b1d5..a78d1c029307 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -505,13 +505,13 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { val expansion1 = InlineTyper.typed(expansion, pt)(inlineContext(call)) /** Does given definition bind a closure that will be inlined? */ - def bindsInlineableClosure(defn: ValOrDefDef) = Ident(defn.symbol.termRef) match { - case InlineableClosure(_) => true + def bindsDeadClosure(defn: ValOrDefDef) = Ident(defn.symbol.termRef) match { + case InlineableClosure(_) => !InlineTyper.retainedClosures.contains(defn.symbol) case _ => false } /** All bindings in `bindingsBuf` except bindings of inlineable closures */ - val bindings = bindingsBuf.toList.filterNot(bindsInlineableClosure).map(_.withPos(call.pos)) + val bindings = bindingsBuf.toList.filterNot(bindsDeadClosure).map(_.withPos(call.pos)) val result = tpd.Inlined(call, bindings, expansion1) inlining.println(i"inlined $call\n --> \n$result") @@ -524,8 +524,7 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { def unapply(tree: Ident)(implicit ctx: Context): Option[Tree] = if (paramProxies.contains(tree.tpe)) { bindingsBuf.find(_.name == tree.name).map(_.rhs) match { - case Some(Closure(_, meth, _)) if meth.symbol.isInlineMethod => Some(meth) - case Some(Block(_, Closure(_, meth, _))) if meth.symbol.isInlineMethod => Some(meth) + case Some(closure(_, meth, _)) if meth.symbol.isInlineMethod => Some(meth) case _ => None } } else None @@ -540,6 +539,17 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { */ private object InlineTyper extends ReTyper { + var retainedClosures = Set[Symbol]() + + override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context) = { + val tree1 = super.typedIdent(tree, pt) + tree1 match { + case InlineableClosure(_) => retainedClosures += tree.symbol + case _ => + } + tree1 + } + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { val res = super.typedSelect(tree, pt) ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.pos) diff --git a/tests/run/inlinedAssign.scala b/tests/run/inlinedAssign.scala index f405f5073acf..5b73a6f0c265 100644 --- a/tests/run/inlinedAssign.scala +++ b/tests/run/inlinedAssign.scala @@ -6,6 +6,8 @@ object Test { y_=(t) } + inline def f(x: Int => Unit) = x + def main(args: Array[String]) = { var x = 1 var y = 2 @@ -13,5 +15,7 @@ object Test { inline def setY(z: Int) = y = z swap(x, setX, y, setY) assert(x == 2 && y == 1) + + val z = f(setX) // tests case where inline arg is not applied } } From 84bc770bb7bcac2fe09a13c62c24aac1e3fda582 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Sep 2016 18:12:15 +0200 Subject: [PATCH 60/77] Drop annotations from dealias We got an error when we tried t opur @inline annotations on function parameter types. It turned out that there were lots of places where annotations on a type would break a test in the compiler. So we now drop annotations by default when dealiasing. We provide dealiasKeepAnnots as an alternative that has the old behavior, i.e. rewrap annotations after dealiasing. The only place where we found we needed this was in the exhaustivity checker. --- src/dotty/tools/dotc/core/Types.scala | 32 +++++++++++++------ .../tools/dotc/transform/patmat/Space.scala | 2 +- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 0c9a127016f0..00286156a198 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -848,30 +848,42 @@ object Types { case tp => tp } - /** Follow aliases and dereferences LazyRefs and instantiated TypeVars until type - * is no longer alias type, LazyRef, or instantiated type variable. - */ - final def dealias(implicit ctx: Context): Type = this match { + private def dealias(keepAnnots: Boolean)(implicit ctx: Context): Type = this match { case tp: TypeRef => if (tp.symbol.isClass) tp else tp.info match { - case TypeAlias(tp) => tp.dealias + case TypeAlias(tp) => tp.dealias(keepAnnots) case _ => tp } case tp: TypeVar => val tp1 = tp.instanceOpt - if (tp1.exists) tp1.dealias else tp + if (tp1.exists) tp1.dealias(keepAnnots) else tp case tp: AnnotatedType => - tp.derivedAnnotatedType(tp.tpe.dealias, tp.annot) + val tp1 = tp.tpe.dealias(keepAnnots) + if (keepAnnots) tp.derivedAnnotatedType(tp1, tp.annot) else tp1 case tp: LazyRef => - tp.ref.dealias + tp.ref.dealias(keepAnnots) case app @ HKApply(tycon, args) => - val tycon1 = tycon.dealias - if (tycon1 ne tycon) app.superType.dealias + val tycon1 = tycon.dealias(keepAnnots) + if (tycon1 ne tycon) app.superType.dealias(keepAnnots) else this case _ => this } + /** Follow aliases and dereferences LazyRefs and instantiated TypeVars until type + * is no longer alias type, LazyRef, or instantiated type variable. + * Goes through annotated types and rewraps annotations on the result. + */ + final def dealiasKeepAnnots(implicit ctx: Context): Type = + dealias(keepAnnots = true) + + /** Follow aliases and dereferences LazyRefs, annotated types and instantiated + * TypeVars until type is no longer alias type, annotated type, LazyRef, + * or instantiated type variable. + */ + final def dealias(implicit ctx: Context): Type = + dealias(keepAnnots = false) + /** Perform successive widenings and dealiasings until none can be applied anymore */ final def widenDealias(implicit ctx: Context): Type = { val res = this.widen.dealias diff --git a/src/dotty/tools/dotc/transform/patmat/Space.scala b/src/dotty/tools/dotc/transform/patmat/Space.scala index d942c6853627..830d0f938b9b 100644 --- a/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -522,7 +522,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { } val Match(sel, cases) = tree - isCheckable(sel.tpe.widen.deAnonymize.dealias) + isCheckable(sel.tpe.widen.deAnonymize.dealiasKeepAnnots) } From b5132e87afe1a98467369d2f91ba4483a6a88ea4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 15 Sep 2016 12:08:42 +0200 Subject: [PATCH 61/77] Change owner as necessary when typing a TypedSplice When typing an untpd.TypedSplice it could be that the owner at the time the tree is originally typed is different from the owner at the time the tree is unwrapped. In that case the owner needs to be changed. Problem was noticed in Course-2002-02 after changing Closure to be a pure expression. This means that TypedSplices containing closures are no longer lifted out from containing closures during eta expansion, and the owner chain got corrupted. --- src/dotty/tools/dotc/ast/untpd.scala | 11 ++++++++--- src/dotty/tools/dotc/typer/Checking.scala | 3 ++- src/dotty/tools/dotc/typer/EtaExpansion.scala | 2 +- src/dotty/tools/dotc/typer/ProtoTypes.scala | 2 +- src/dotty/tools/dotc/typer/Typer.scala | 19 ++++++++++++++++--- 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/dotty/tools/dotc/ast/untpd.scala b/src/dotty/tools/dotc/ast/untpd.scala index 114c13684168..a0a353c13c8c 100644 --- a/src/dotty/tools/dotc/ast/untpd.scala +++ b/src/dotty/tools/dotc/ast/untpd.scala @@ -21,10 +21,15 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { } /** A typed subtree of an untyped tree needs to be wrapped in a TypedSlice */ - case class TypedSplice(tree: tpd.Tree) extends ProxyTree { + abstract case class TypedSplice(tree: tpd.Tree)(val owner: Symbol) extends ProxyTree { def forwardTo = tree } + object TypedSplice { + def apply(tree: tpd.Tree)(implicit ctx: Context): TypedSplice = + new TypedSplice(tree)(ctx.owner) {} + } + /** mods object name impl */ case class ModuleDef(name: TermName, impl: Template) extends MemberDef { @@ -232,7 +237,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case AppliedTypeTree(tycon, targs) => (tycon, targs) case TypedSplice(AppliedTypeTree(tycon, targs)) => - (TypedSplice(tycon), targs map TypedSplice) + (TypedSplice(tycon), targs map (TypedSplice(_))) case TypedSplice(tpt1: Tree) => val argTypes = tpt1.tpe.argTypes val tycon = tpt1.tpe.withoutArgs(argTypes) @@ -260,7 +265,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def AppliedTypeTree(tpt: Tree, arg: Tree): AppliedTypeTree = AppliedTypeTree(tpt, arg :: Nil) - def TypeTree(tpe: Type): TypedSplice = TypedSplice(TypeTree().withTypeUnchecked(tpe)) + def TypeTree(tpe: Type)(implicit ctx: Context): TypedSplice = TypedSplice(TypeTree().withTypeUnchecked(tpe)) def TypeDef(name: TypeName, tparams: List[TypeDef], rhs: Tree): TypeDef = if (tparams.isEmpty) TypeDef(name, rhs) else new PolyTypeDef(name, tparams, rhs) diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index 42eede5e90b2..b02b0ad21fc8 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -484,7 +484,8 @@ trait Checking { def checkInlineConformant(tree: Tree, what: => String)(implicit ctx: Context): Unit = tree.tpe.widenTermRefExpr match { case tp: ConstantType if isPureExpr(tree) => // ok - case _ => ctx.error(em"$what must be a constant expression", tree.pos) + case tp if defn.isFunctionType(tp) && isPureExpr(tree) => // ok + case _ => ctx.error(em"$what must be a constant expression or a function", tree.pos) } /** Check that class does not define same symbol twice */ diff --git a/src/dotty/tools/dotc/typer/EtaExpansion.scala b/src/dotty/tools/dotc/typer/EtaExpansion.scala index 7cbe70b477d6..397b6d95b244 100644 --- a/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -148,7 +148,7 @@ object EtaExpansion { case _ => } val fn = untpd.Function(params, body) - if (defs.nonEmpty) untpd.Block(defs.toList map untpd.TypedSplice, fn) else fn + if (defs.nonEmpty) untpd.Block(defs.toList map (untpd.TypedSplice(_)), fn) else fn } } diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index 80f0fd186d18..0e6697fb730a 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -299,7 +299,7 @@ object ProtoTypes { } class UnapplyFunProto(argType: Type, typer: Typer)(implicit ctx: Context) extends FunProto( - untpd.TypedSplice(dummyTreeOfType(argType)) :: Nil, WildcardType, typer) + untpd.TypedSplice(dummyTreeOfType(argType))(ctx) :: Nil, WildcardType, typer) /** A prototype for expressions [] that are type-parameterized: * diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 85102841e2f1..18b609f8dfb7 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -513,8 +513,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val rawUpdate: untpd.Tree = untpd.Select(untpd.TypedSplice(fn), nme.update) val wrappedUpdate = if (targs.isEmpty) rawUpdate - else untpd.TypeApply(rawUpdate, targs map untpd.TypedSplice) - val appliedUpdate = cpy.Apply(fn)(wrappedUpdate, (args map untpd.TypedSplice) :+ tree.rhs) + else untpd.TypeApply(rawUpdate, targs map (untpd.TypedSplice(_))) + val appliedUpdate = cpy.Apply(fn)(wrappedUpdate, (args map (untpd.TypedSplice(_))) :+ tree.rhs) typed(appliedUpdate, pt) case lhs => val lhsCore = typedUnadapted(lhs, AssignProto) @@ -1357,6 +1357,19 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } + def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = + tree.tree match { + case tree1: TypeTree => tree1 // no change owner necessary here ... + case tree1: Ident => tree1 // ... or here + case tree1 => + if (ctx.owner ne tree.owner) { + println(i"changing owner of $tree1 from ${tree.owner} to ${ctx.owner}") + tree1.changeOwner(tree.owner, ctx.owner) + } + else tree1 + } + + def typedAsFunction(tree: untpd.PostfixOp, pt: Type)(implicit ctx: Context): Tree = { val untpd.PostfixOp(qual, nme.WILDCARD) = tree val pt1 = if (defn.isFunctionType(pt)) pt else AnyFunctionProto @@ -1456,7 +1469,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case tree: untpd.Alternative => typedAlternative(tree, pt) case tree: untpd.PackageDef => typedPackageDef(tree) case tree: untpd.Annotated => typedAnnotated(tree, pt) - case tree: untpd.TypedSplice => tree.tree + case tree: untpd.TypedSplice => typedTypedSplice(tree) case tree: untpd.UnApply => typedUnApply(tree, pt) case tree @ untpd.PostfixOp(qual, nme.WILDCARD) => typedAsFunction(tree, pt) case untpd.EmptyTree => tpd.EmptyTree From 748d1d852ce28785f61f511758071c70a6137356 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Sep 2016 18:15:21 +0200 Subject: [PATCH 62/77] Generalize checkInlineConformant to functions Pure expressions with function types now are considered conforming. Necessitated a change in TreeInfo to accept closures as pure expressions. Test case in inlineForeach --- src/dotty/tools/dotc/ast/TreeInfo.scala | 5 ++- tests/run/inlineForeach.scala | 44 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/run/inlineForeach.scala diff --git a/src/dotty/tools/dotc/ast/TreeInfo.scala b/src/dotty/tools/dotc/ast/TreeInfo.scala index 421ff5919734..a6c49cffd829 100644 --- a/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -305,6 +305,8 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => if (vdef.symbol.flags is Mutable) Impure else exprPurity(vdef.rhs) case _ => Impure + // TODO: It seem like this should be exprPurity(tree) + // But if we do that the repl/vars test break. Need to figure out why that's the case. } /** The purity level of this expression. @@ -321,7 +323,8 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case EmptyTree | This(_) | Super(_, _) - | Literal(_) => + | Literal(_) + | Closure(_, _, _) => Pure case Ident(_) => refPurity(tree) diff --git a/tests/run/inlineForeach.scala b/tests/run/inlineForeach.scala new file mode 100644 index 000000000000..0c6135c6d1f3 --- /dev/null +++ b/tests/run/inlineForeach.scala @@ -0,0 +1,44 @@ +object Test { + + class Range(from: Int, end: Int) { + + inline + def foreach(inline op: Int => Unit): Unit = { + var i = from + while (i < end) { + op(i) + i += 1 + } + } + + def filter(p: Int => Boolean): List[Int] = ??? + } + + implicit class intWrapper(private val start: Int) extends AnyVal { + def until(end: Int) = new Range(start, end) + def to(limit: Int) = new Range(start, limit + 1) + }/* + + def matmul(xs: Array[Array[Double]], ys: Array[Array[Double]]): Array[Array[Double]] = { + def nrows = xs.length + def ncols = ys(0).length + def n = ys.length + assert(xs(0).length == n) + val zs = Array.ofDim[Double](nrows, ncols) + for (i <- intWrapper(0) until nrows) + for (j <- 0 until ncols) { + var x = 0.0 + for (k <- 0 until n) + x += xs(i)(k) * ys(k)(j) + zs(i)(j) = x + } + zs + } +*/ + def main(args: Array[String]) = { + /* 1.until(10).foreach(i => println(i)) + 1.until(10).foreach(println(_))*/ + 1.until(10).foreach(println) + for (i <- 1 to 10) println(i) + } +} From c9fa504161cc34ec979ae3c1b73db6798adc4872 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 15 Sep 2016 15:33:22 +0200 Subject: [PATCH 63/77] Inline function parameters Add inline function parameters. The previous concept of inlineable closure is adapted to coincide with an inline function parameter. --- src/dotty/tools/dotc/core/Flags.scala | 2 +- src/dotty/tools/dotc/typer/Inliner.scala | 39 ++++++++++++++---------- src/dotty/tools/dotc/typer/Typer.scala | 5 +-- tests/run/inlinedAssign.scala | 6 +++- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index 1a1182c7be75..b7befa6e28ef 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -431,7 +431,7 @@ object Flags { /** Flags representing source modifiers */ final val SourceModifierFlags = - commonFlags(Private, Protected, Abstract, Final, + commonFlags(Private, Protected, Abstract, Final, Inline, Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic) /** Flags representing modifiers that can appear in trees */ diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index a78d1c029307..e9c054ff2ab0 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -377,11 +377,13 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { case tp: MethodType => (tp.paramNames, tp.paramTypes, argss.head).zipped.foreach { (name, paramtp, arg) => def isByName = paramtp.dealias.isInstanceOf[ExprType] - paramBinding(name) = arg.tpe.stripTypeVar match { + paramBinding(name) = arg.tpe.stripAnnots.stripTypeVar match { case argtpe: SingletonType if isByName || isIdempotentExpr(arg) => argtpe case argtpe => + val inlineFlag = if (paramtp.hasAnnotation(defn.InlineParamAnnot)) Inline else EmptyFlags val (bindingFlags, bindingType) = - if (isByName) (Method, ExprType(argtpe.widen)) else (EmptyFlags, argtpe.widen) + if (isByName) (inlineFlag | Method, ExprType(argtpe.widen)) + else (inlineFlag, argtpe.widen) val boundSym = newSym(name, bindingFlags, bindingType).asTerm val binding = if (isByName) DefDef(boundSym, arg.changeOwner(ctx.owner, boundSym)) @@ -500,22 +502,22 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil) val expansion = inliner(rhs.withPos(call.pos)) + ctx.traceIndented(i"inlining $call\n, BINDINGS =\n${bindingsBuf.toList}%\n%\nEXPANSION =\n$expansion", inlining, show = true) { - // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. - val expansion1 = InlineTyper.typed(expansion, pt)(inlineContext(call)) + // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. + val expansion1 = InlineTyper.typed(expansion, pt)(inlineContext(call)) - /** Does given definition bind a closure that will be inlined? */ - def bindsDeadClosure(defn: ValOrDefDef) = Ident(defn.symbol.termRef) match { - case InlineableClosure(_) => !InlineTyper.retainedClosures.contains(defn.symbol) - case _ => false - } + /** Does given definition bind a closure that will be inlined? */ + def bindsDeadClosure(defn: ValOrDefDef) = Ident(defn.symbol.termRef) match { + case InlineableClosure(_) => !InlineTyper.retainedClosures.contains(defn.symbol) + case _ => false + } - /** All bindings in `bindingsBuf` except bindings of inlineable closures */ - val bindings = bindingsBuf.toList.filterNot(bindsDeadClosure).map(_.withPos(call.pos)) + /** All bindings in `bindingsBuf` except bindings of inlineable closures */ + val bindings = bindingsBuf.toList.filterNot(bindsDeadClosure).map(_.withPos(call.pos)) - val result = tpd.Inlined(call, bindings, expansion1) - inlining.println(i"inlined $call\n --> \n$result") - result + tpd.Inlined(call, bindings, expansion1) + } } /** An extractor for references to closure arguments that refer to `@inline` methods */ @@ -523,8 +525,12 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { lazy val paramProxies = paramProxy.values.toSet def unapply(tree: Ident)(implicit ctx: Context): Option[Tree] = if (paramProxies.contains(tree.tpe)) { - bindingsBuf.find(_.name == tree.name).map(_.rhs) match { - case Some(closure(_, meth, _)) if meth.symbol.isInlineMethod => Some(meth) + bindingsBuf.find(_.name == tree.name) match { + case Some(ddef: ValDef) if ddef.symbol.is(Inline) => + ddef.rhs match { + case closure(_, meth, _) => Some(meth) + case _ => None + } case _ => None } } else None @@ -571,6 +577,7 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = tree.asInstanceOf[tpd.Tree] match { case Apply(Select(InlineableClosure(fn), nme.apply), args) => + inlining.println(i"reducing $tree with closure $fn") typed(fn.appliedToArgs(args), pt) case _ => super.typedApply(tree, pt) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 18b609f8dfb7..4c7d1c50dac2 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -745,10 +745,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case WildcardType(_) => untpd.TypeTree() case _ => untpd.TypeTree(protoResult) } - val inlineable = fnBody match { - case Apply(untpd.TypedSplice(fn), _) => Inliner.hasBodyToInline(fn.symbol) - case _ => false - } + val inlineable = pt.hasAnnotation(defn.InlineParamAnnot) desugar.makeClosure(inferredParams, fnBody, resultTpt, inlineable) } typed(desugared, pt) diff --git a/tests/run/inlinedAssign.scala b/tests/run/inlinedAssign.scala index 5b73a6f0c265..f241780ed509 100644 --- a/tests/run/inlinedAssign.scala +++ b/tests/run/inlinedAssign.scala @@ -1,6 +1,6 @@ object Test { - inline def swap[T](x: T, x_= : T => Unit, y: T, y_= : T => Unit) = { + inline def swap[T](x: T, inline x_= : T => Unit, y: T, inline y_= : T => Unit) = { val t = x x_=(y) y_=(t) @@ -16,6 +16,10 @@ object Test { swap(x, setX, y, setY) assert(x == 2 && y == 1) + swap(x, x = _, y, y = _) + assert(x == 1 && y == 2) + + val z = f(setX) // tests case where inline arg is not applied } } From 5a46d19dde76b739f6672c9b6f57355cfd38159a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 15 Sep 2016 15:39:57 +0200 Subject: [PATCH 64/77] Handle inlining in inlining arguments We got unbound symbols before because a TreeTypeMap would copy a tree of an inline DefDef but would not adapt the inline body stored in the @inline annotation of the DefDef to point to the updated tree. --- src/dotty/tools/dotc/ast/TreeTypeMap.scala | 9 +++- src/dotty/tools/dotc/ast/untpd.scala | 4 +- .../tools/dotc/core/tasty/TreePickler.scala | 2 +- src/dotty/tools/dotc/typer/Inliner.scala | 47 ++++++++++++------- src/dotty/tools/dotc/typer/Typer.scala | 5 +- tests/run/inlineForeach.scala | 12 +++-- 6 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/src/dotty/tools/dotc/ast/TreeTypeMap.scala index 0593e8159046..6d0c7d8e3a58 100644 --- a/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -6,6 +6,8 @@ import core._ import Types._, Contexts._, Constants._, Names._, Flags._ import SymDenotations._, Symbols._, Annotations._, Trees._, Symbols._ import Denotations._, Decorators._ +import typer.Inliner +import config.Printers.inlining import dotty.tools.dotc.transform.SymUtils._ /** A map that applies three functions and a substitution together to a tree and @@ -92,7 +94,12 @@ final class TreeTypeMap( case ddef @ DefDef(name, tparams, vparamss, tpt, _) => val (tmap1, tparams1) = transformDefs(ddef.tparams) val (tmap2, vparamss1) = tmap1.transformVParamss(vparamss) - cpy.DefDef(ddef)(name, tparams1, vparamss1, tmap2.transform(tpt), tmap2.transform(ddef.rhs)) + val res = cpy.DefDef(ddef)(name, tparams1, vparamss1, tmap2.transform(tpt), tmap2.transform(ddef.rhs)) + if (Inliner.hasBodyToInline(res.symbol)) { + inlining.println(i"update inline body ${res.symbol}") + Inliner.updateInlineBody(res.symbol, res.rhs) + } + res case blk @ Block(stats, expr) => val (tmap1, stats1) = transformDefs(stats) val expr1 = tmap1.transform(expr) diff --git a/src/dotty/tools/dotc/ast/untpd.scala b/src/dotty/tools/dotc/ast/untpd.scala index a0a353c13c8c..cc7cefbac488 100644 --- a/src/dotty/tools/dotc/ast/untpd.scala +++ b/src/dotty/tools/dotc/ast/untpd.scala @@ -20,7 +20,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { override def isType = op.isTypeName } - /** A typed subtree of an untyped tree needs to be wrapped in a TypedSlice */ + /** A typed subtree of an untyped tree needs to be wrapped in a TypedSlice + * @param owner The current owner at the time the tree was defined + */ abstract case class TypedSplice(tree: tpd.Tree)(val owner: Symbol) extends ProxyTree { def forwardTo = tree } diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 12ab050f68eb..99a3ff85cb16 100644 --- a/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -572,7 +572,7 @@ class TreePickler(pickler: TastyPickler) { def pickle(trees: List[Tree])(implicit ctx: Context) = { trees.foreach(tree => if (!tree.isEmpty) pickleTree(tree)) - assert(forwardSymRefs.isEmpty, i"unresolved symbols: ${forwardSymRefs.keySet.toList}%, % when pickling $trees%\n %") + assert(forwardSymRefs.isEmpty, i"unresolved symbols: ${forwardSymRefs.keySet.toList}%, % when pickling ${ctx.source}") } def compactify() = { diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index e9c054ff2ab0..ef67384ffdbe 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -214,17 +214,18 @@ object Inliner { /** The accessor defs to non-public members which need to be defined * together with the inline method */ - def removeAccessors(implicit ctx: Context): List[MemberDef] = { + def accessors(implicit ctx: Context): List[MemberDef] = { ensureEvaluated() - val res = myAccessors.toList - myAccessors.clear() - res + myAccessors.toList } } /** A key to be used in an attachment for `@inline` annotations */ private val InlineInfo = new Property.Key[InlineInfo] + /** A key to be used in an attachment for `@inline` annotations */ + private val InlineBody = new Property.Key[Tree] + /** A key to be used in a context property that tracks enclosing inlined calls */ private val InlinedCalls = new Property.Key[List[Tree]] // to be used in context @@ -239,37 +240,51 @@ object Inliner { */ def registerInlineInfo( sym: SymDenotation, treeExpr: Context => Tree, inlineCtxFn: Context => Context)(implicit ctx: Context): Unit = { + val inlineAnnotTree = sym.unforcedAnnotation(defn.InlineAnnot).get.tree + if (inlineAnnotTree.getAttachment(InlineBody).isEmpty) + inlineAnnotTree.getAttachment(InlineInfo) match { + case Some(inlineInfo) if inlineInfo.isEvaluated => // keep existing attachment + case _ => + if (!ctx.isAfterTyper) + inlineAnnotTree.putAttachment(InlineInfo, new InlineInfo(treeExpr, inlineCtxFn)) + } + } + + /** Register an evaluated inline body for `sym` */ + def updateInlineBody(sym: SymDenotation, body: Tree)(implicit ctx: Context): Unit = { val inlineAnnot = sym.unforcedAnnotation(defn.InlineAnnot).get - inlineAnnot.tree.getAttachment(InlineInfo) match { - case Some(inlineInfo) if inlineInfo.isEvaluated => // keep existing attachment - case _ => - if (!ctx.isAfterTyper) - inlineAnnot.tree.putAttachment(InlineInfo, new InlineInfo(treeExpr, inlineCtxFn)) - } + assert(inlineAnnot.tree.putAttachment(InlineBody, body).isDefined) } /** Optionally, the inline info attached to the `@inline` annotation of `sym`. */ private def inlineInfo(sym: SymDenotation)(implicit ctx: Context): Option[InlineInfo] = sym.getAnnotation(defn.InlineAnnot).get.tree.getAttachment(InlineInfo) - /** Definition is an inline method with a known body to inline (note: definitions coming + /** Optionally, the inline body attached to the `@inline` annotation of `sym`. */ + private def inlineBody(sym: SymDenotation)(implicit ctx: Context): Option[Tree] = + sym.getAnnotation(defn.InlineAnnot).get.tree.getAttachment(InlineBody) + + /** Definition is an inline method with a known body to inline (note: definitions coming * from Scala2x class files might be `@inline`, but still lack that body. */ def hasBodyToInline(sym: SymDenotation)(implicit ctx: Context): Boolean = - sym.isInlineMethod && inlineInfo(sym).isDefined + sym.isInlineMethod && (inlineInfo(sym).isDefined || inlineBody(sym).isDefined) /** The body to inline for method `sym`. * @pre hasBodyToInline(sym) */ def bodyToInline(sym: SymDenotation)(implicit ctx: Context): Tree = - inlineInfo(sym).get.body + inlineInfo(sym).map(_.body).getOrElse(inlineBody(sym).get) /** The accessors to non-public members needed by the inlinable body of `sym`. * @pre hasBodyToInline(sym) */ - - def removeInlineAccessors(sym: SymDenotation)(implicit ctx: Context): List[MemberDef] = - inlineInfo(sym).get.removeAccessors + def removeInlineAccessors(sym: SymDenotation)(implicit ctx: Context): List[MemberDef] = { + val inlineAnnotTree = sym.getAnnotation(defn.InlineAnnot).get.tree + val inlineInfo = inlineAnnotTree.removeAttachment(InlineInfo).get + inlineAnnotTree.putAttachment(InlineBody, inlineInfo.body) + inlineInfo.accessors + } /** Try to inline a call to a `@inline` method. Fail with error if the maximal * inline depth is exceeded. diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 4c7d1c50dac2..885bc56d63b6 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1359,10 +1359,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case tree1: TypeTree => tree1 // no change owner necessary here ... case tree1: Ident => tree1 // ... or here case tree1 => - if (ctx.owner ne tree.owner) { - println(i"changing owner of $tree1 from ${tree.owner} to ${ctx.owner}") - tree1.changeOwner(tree.owner, ctx.owner) - } + if (ctx.owner ne tree.owner) tree1.changeOwner(tree.owner, ctx.owner) else tree1 } diff --git a/tests/run/inlineForeach.scala b/tests/run/inlineForeach.scala index 0c6135c6d1f3..1389ad6c4059 100644 --- a/tests/run/inlineForeach.scala +++ b/tests/run/inlineForeach.scala @@ -17,7 +17,7 @@ object Test { implicit class intWrapper(private val start: Int) extends AnyVal { def until(end: Int) = new Range(start, end) def to(limit: Int) = new Range(start, limit + 1) - }/* + } def matmul(xs: Array[Array[Double]], ys: Array[Array[Double]]): Array[Array[Double]] = { def nrows = xs.length @@ -34,11 +34,15 @@ object Test { } zs } -*/ + def main(args: Array[String]) = { - /* 1.until(10).foreach(i => println(i)) - 1.until(10).foreach(println(_))*/ + 1.until(10).foreach(i => println(i)) + 1.until(10).foreach(println(_)) 1.until(10).foreach(println) for (i <- 1 to 10) println(i) + + for (k1 <- 1 to 10) + for (k2 <- 1 to 10) + println(s"$k1") } } From 95e488eab2a686671b2a6ffd8fce05c043b3afab Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 15 Sep 2016 18:08:10 +0200 Subject: [PATCH 65/77] Use BodyAnnot to indicate rhs of inline method Since fundamental operations such as treeCopy have to know about inline bodies, it seems better to represent this information directly in an annotation. --- src/dotty/annotation/internal/Body.scala | 8 ++++++ src/dotty/tools/dotc/ast/TreeTypeMap.scala | 5 +--- src/dotty/tools/dotc/core/Annotations.scala | 28 +++++++++++++++++++ src/dotty/tools/dotc/core/Definitions.scala | 2 ++ .../tools/dotc/core/tasty/TreePickler.scala | 9 +++--- .../tools/dotc/core/tasty/TreeUnpickler.scala | 13 ++++----- src/dotty/tools/dotc/typer/Inliner.scala | 20 ++++++------- 7 files changed, 60 insertions(+), 25 deletions(-) create mode 100644 src/dotty/annotation/internal/Body.scala diff --git a/src/dotty/annotation/internal/Body.scala b/src/dotty/annotation/internal/Body.scala new file mode 100644 index 000000000000..7e26b02f27aa --- /dev/null +++ b/src/dotty/annotation/internal/Body.scala @@ -0,0 +1,8 @@ +package dotty.annotation.internal + +import scala.annotation.Annotation + +/** The class associated with a `BodyAnnotation`, which indicates + * an inline method's right hand side + */ +final class Body() extends Annotation diff --git a/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/src/dotty/tools/dotc/ast/TreeTypeMap.scala index 6d0c7d8e3a58..efa46debb3a2 100644 --- a/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -138,10 +138,7 @@ final class TreeTypeMap( def apply[ThisTree <: tpd.Tree](tree: ThisTree): ThisTree = transform(tree).asInstanceOf[ThisTree] - def apply(annot: Annotation): Annotation = { - val tree1 = apply(annot.tree) - if (tree1 eq annot.tree) annot else ConcreteAnnotation(tree1) - } + def apply(annot: Annotation): Annotation = annot.derivedAnnotation(apply(annot.tree)) /** The current tree map composed with a substitution [from -> to] */ def withSubstitution(from: List[Symbol], to: List[Symbol]): TreeTypeMap = diff --git a/src/dotty/tools/dotc/core/Annotations.scala b/src/dotty/tools/dotc/core/Annotations.scala index 5f96a60e649b..fde41ef769a9 100644 --- a/src/dotty/tools/dotc/core/Annotations.scala +++ b/src/dotty/tools/dotc/core/Annotations.scala @@ -42,6 +42,34 @@ object Annotations { override def symbol(implicit ctx: Context): Symbol = sym } + /** An annotation indicating the body of a right-hand side, + * typically of an inline method. Treated specially in + * pickling/unpickling and treecopies + */ + abstract class BodyAnnotation extends Annotation { + override def symbol(implicit ctx: Context) = defn.BodyAnnot + override def derivedAnnotation(tree: Tree)(implicit ctx: Context) = + if (tree eq this.tree) this else ConcreteBodyAnnotation(tree) + override def arguments(implicit ctx: Context) = Nil + } + + case class ConcreteBodyAnnotation(body: Tree) extends BodyAnnotation { + def tree(implicit ctx: Context) = body + } + + case class LazyBodyAnnotation(bodyExpr: Context => Tree) extends BodyAnnotation { + private var evaluated = false + private var myBody: Tree = _ + def tree(implicit ctx: Context) = { + if (evaluated) assert(myBody != null) + else { + evaluated = true + myBody = bodyExpr(ctx) + } + myBody + } + } + object Annotation { def apply(tree: Tree) = ConcreteAnnotation(tree) diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 12677edb68d0..75b75d3d595b 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -456,6 +456,8 @@ class Definitions { def AliasAnnot(implicit ctx: Context) = AliasAnnotType.symbol.asClass lazy val AnnotationDefaultAnnotType = ctx.requiredClassRef("dotty.annotation.internal.AnnotationDefault") def AnnotationDefaultAnnot(implicit ctx: Context) = AnnotationDefaultAnnotType.symbol.asClass + lazy val BodyAnnotType = ctx.requiredClassRef("dotty.annotation.internal.Body") + def BodyAnnot(implicit ctx: Context) = BodyAnnotType.symbol.asClass lazy val ChildAnnotType = ctx.requiredClassRef("dotty.annotation.internal.Child") def ChildAnnot(implicit ctx: Context) = ChildAnnotType.symbol.asClass lazy val CovariantBetweenAnnotType = ctx.requiredClassRef("dotty.annotation.internal.CovariantBetween") diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 99a3ff85cb16..b6f52c0ec153 100644 --- a/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -565,10 +565,11 @@ class TreePickler(pickler: TastyPickler) { sym.annotations.foreach(pickleAnnotation) } - def pickleAnnotation(ann: Annotation)(implicit ctx: Context) = { - writeByte(ANNOTATION) - withLength { pickleType(ann.symbol.typeRef); pickleTree(ann.tree) } - } + def pickleAnnotation(ann: Annotation)(implicit ctx: Context) = + if (ann.symbol != defn.BodyAnnot) { // inline bodies are reconstituted automatically when unpickling + writeByte(ANNOTATION) + withLength { pickleType(ann.symbol.typeRef); pickleTree(ann.tree) } + } def pickle(trees: List[Tree])(implicit ctx: Context) = { trees.foreach(tree => if (!tree.isEmpty) pickleTree(tree)) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 1b84341296e7..4a94e5d710e4 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -488,13 +488,12 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, posUnpickle sym.completer.withDecls(newScope) forkAt(templateStart).indexTemplateParams()(localContext(sym)) } - else if (annots.exists(_.symbol == defn.InlineAnnot)) { - Inliner.registerInlineInfo( - sym, - implicit ctx => forkAt(rhsStart).readTerm(), - implicit ctx => localContext(sym).addMode(Mode.ReadPositions)) - // Previous line avoids space leaks because it does not capture the current context. - } + else if (annots.exists(_.symbol == defn.InlineAnnot)) + sym.addAnnotation(LazyBodyAnnotation { ctx0 => + implicit val ctx: Context = localContext(sym)(ctx0).addMode(Mode.ReadPositions) + // avoids space leaks by not capturing the current context + forkAt(rhsStart).readTerm() + }) goto(start) sym } diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index ef67384ffdbe..079154f26a68 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -16,7 +16,7 @@ import Contexts.Context import Names.{Name, TermName} import NameOps._ import SymDenotations.SymDenotation -import Annotations.Annotation +import Annotations._ import transform.ExplicitOuter import Inferencing.fullyDefinedType import config.Printers.inlining @@ -223,9 +223,6 @@ object Inliner { /** A key to be used in an attachment for `@inline` annotations */ private val InlineInfo = new Property.Key[InlineInfo] - /** A key to be used in an attachment for `@inline` annotations */ - private val InlineBody = new Property.Key[Tree] - /** A key to be used in a context property that tracks enclosing inlined calls */ private val InlinedCalls = new Property.Key[List[Tree]] // to be used in context @@ -240,20 +237,22 @@ object Inliner { */ def registerInlineInfo( sym: SymDenotation, treeExpr: Context => Tree, inlineCtxFn: Context => Context)(implicit ctx: Context): Unit = { - val inlineAnnotTree = sym.unforcedAnnotation(defn.InlineAnnot).get.tree - if (inlineAnnotTree.getAttachment(InlineBody).isEmpty) + if (sym.unforcedAnnotation(defn.BodyAnnot).isEmpty) { + val inlineAnnotTree = sym.unforcedAnnotation(defn.InlineAnnot).get.tree inlineAnnotTree.getAttachment(InlineInfo) match { case Some(inlineInfo) if inlineInfo.isEvaluated => // keep existing attachment case _ => if (!ctx.isAfterTyper) inlineAnnotTree.putAttachment(InlineInfo, new InlineInfo(treeExpr, inlineCtxFn)) } + } } /** Register an evaluated inline body for `sym` */ def updateInlineBody(sym: SymDenotation, body: Tree)(implicit ctx: Context): Unit = { - val inlineAnnot = sym.unforcedAnnotation(defn.InlineAnnot).get - assert(inlineAnnot.tree.putAttachment(InlineBody, body).isDefined) + assert(sym.unforcedAnnotation(defn.BodyAnnot).isDefined) + sym.removeAnnotation(defn.BodyAnnot) + sym.addAnnotation(ConcreteBodyAnnotation(body)) } /** Optionally, the inline info attached to the `@inline` annotation of `sym`. */ @@ -262,7 +261,7 @@ object Inliner { /** Optionally, the inline body attached to the `@inline` annotation of `sym`. */ private def inlineBody(sym: SymDenotation)(implicit ctx: Context): Option[Tree] = - sym.getAnnotation(defn.InlineAnnot).get.tree.getAttachment(InlineBody) + sym.getAnnotation(defn.BodyAnnot).map(_.tree) /** Definition is an inline method with a known body to inline (note: definitions coming * from Scala2x class files might be `@inline`, but still lack that body. @@ -282,7 +281,8 @@ object Inliner { def removeInlineAccessors(sym: SymDenotation)(implicit ctx: Context): List[MemberDef] = { val inlineAnnotTree = sym.getAnnotation(defn.InlineAnnot).get.tree val inlineInfo = inlineAnnotTree.removeAttachment(InlineInfo).get - inlineAnnotTree.putAttachment(InlineBody, inlineInfo.body) + sym.addAnnotation(ConcreteBodyAnnotation(inlineInfo.body)) + assert(sym.getAnnotation(defn.BodyAnnot).isDefined) inlineInfo.accessors } From ce9efda393055060a73509d1c10b8bdd9f2f0136 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 15 Sep 2016 18:11:35 +0200 Subject: [PATCH 66/77] Add check file --- tests/run/inlineForeach.check | 137 ++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 tests/run/inlineForeach.check diff --git a/tests/run/inlineForeach.check b/tests/run/inlineForeach.check new file mode 100644 index 000000000000..3fced2fad78d --- /dev/null +++ b/tests/run/inlineForeach.check @@ -0,0 +1,137 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +1 +2 +3 +4 +5 +6 +7 +8 +9 +1 +2 +3 +4 +5 +6 +7 +8 +9 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +4 +4 +4 +4 +4 +4 +4 +4 +4 +4 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +6 +6 +6 +6 +6 +6 +6 +6 +6 +6 +7 +7 +7 +7 +7 +7 +7 +7 +7 +7 +8 +8 +8 +8 +8 +8 +8 +8 +8 +8 +9 +9 +9 +9 +9 +9 +9 +9 +9 +9 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 From bef012d32127e3d256d0ce31e1a2a27f7277f8d8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 15 Sep 2016 18:51:55 +0200 Subject: [PATCH 67/77] Move logic from InlineInfo to BodyAnnot Now that we have BodyAnnot, InlineInfo can be eliminated. --- src/dotty/tools/dotc/ast/TreeTypeMap.scala | 7 +- src/dotty/tools/dotc/core/Annotations.scala | 6 +- .../tools/dotc/core/SymDenotations.scala | 6 + .../tools/dotc/core/tasty/TreeUnpickler.scala | 1 - src/dotty/tools/dotc/typer/Inliner.scala | 151 ++++++------------ src/dotty/tools/dotc/typer/Namer.scala | 4 +- src/dotty/tools/dotc/typer/Typer.scala | 4 +- 7 files changed, 63 insertions(+), 116 deletions(-) diff --git a/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/src/dotty/tools/dotc/ast/TreeTypeMap.scala index efa46debb3a2..80499cceb7e3 100644 --- a/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -6,7 +6,6 @@ import core._ import Types._, Contexts._, Constants._, Names._, Flags._ import SymDenotations._, Symbols._, Annotations._, Trees._, Symbols._ import Denotations._, Decorators._ -import typer.Inliner import config.Printers.inlining import dotty.tools.dotc.transform.SymUtils._ @@ -95,9 +94,9 @@ final class TreeTypeMap( val (tmap1, tparams1) = transformDefs(ddef.tparams) val (tmap2, vparamss1) = tmap1.transformVParamss(vparamss) val res = cpy.DefDef(ddef)(name, tparams1, vparamss1, tmap2.transform(tpt), tmap2.transform(ddef.rhs)) - if (Inliner.hasBodyToInline(res.symbol)) { - inlining.println(i"update inline body ${res.symbol}") - Inliner.updateInlineBody(res.symbol, res.rhs) + res.symbol.transformAnnotations { + case ann: BodyAnnotation => ann.derivedAnnotation(res.rhs) + case ann => ann } res case blk @ Block(stats, expr) => diff --git a/src/dotty/tools/dotc/core/Annotations.scala b/src/dotty/tools/dotc/core/Annotations.scala index fde41ef769a9..0e8e5a1f0dab 100644 --- a/src/dotty/tools/dotc/core/Annotations.scala +++ b/src/dotty/tools/dotc/core/Annotations.scala @@ -26,6 +26,8 @@ object Annotations { } def argumentConstant(i: Int)(implicit ctx: Context): Option[Constant] = for (ConstantType(c) <- argument(i) map (_.tpe)) yield c + + def ensureCompleted(implicit ctx: Context): Unit = tree } case class ConcreteAnnotation(t: Tree) extends Annotation { @@ -44,13 +46,14 @@ object Annotations { /** An annotation indicating the body of a right-hand side, * typically of an inline method. Treated specially in - * pickling/unpickling and treecopies + * pickling/unpickling and TypeTreeMaps */ abstract class BodyAnnotation extends Annotation { override def symbol(implicit ctx: Context) = defn.BodyAnnot override def derivedAnnotation(tree: Tree)(implicit ctx: Context) = if (tree eq this.tree) this else ConcreteBodyAnnotation(tree) override def arguments(implicit ctx: Context) = Nil + override def ensureCompleted(implicit ctx: Context) = () } case class ConcreteBodyAnnotation(body: Tree) extends BodyAnnotation { @@ -68,6 +71,7 @@ object Annotations { } myBody } + def isEvaluated = evaluated } object Annotation { diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 8a9e8494ab65..969d09c3e65d 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -298,6 +298,12 @@ object SymDenotations { final def removeAnnotation(cls: Symbol)(implicit ctx: Context): Unit = annotations = myAnnotations.filterNot(_ matches cls) + /** Remove any annotations with same class as `annot`, and add `annot` */ + final def updateAnnotation(annot: Annotation)(implicit ctx: Context): Unit = { + removeAnnotation(annot.symbol) + addAnnotation(annot) + } + /** Add all given annotations to this symbol */ final def addAnnotations(annots: TraversableOnce[Annotation])(implicit ctx: Context): Unit = annots.foreach(addAnnotation) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 4a94e5d710e4..09f2c0d1fcd7 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -7,7 +7,6 @@ import Contexts._, Symbols._, Types._, Scopes._, SymDenotations._, Names._, Name import StdNames._, Denotations._, Flags._, Constants._, Annotations._ import util.Positions._ import ast.{tpd, Trees, untpd} -import typer.Inliner import Trees._ import Decorators._ import TastyUnpickler._, TastyBuffer._ diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index 079154f26a68..fd551bc39228 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -25,44 +25,28 @@ import util.{Property, SourceFile, NoSource} import collection.mutable import transform.TypeUtils._ -/** Todo wrt inline SIP: - * - * 1. According to Inline SIP, by-name parameters are not hoisted out, but we currently - * do hoist them. - * - * 2. Inline call-by-name parameters are currently ignored. Not sure what the rules should be. - */ object Inliner { import tpd._ - /** An attachment for inline methods, which contains - * - * - the body to inline, as a typed tree - * - the definitions of all needed accessors to non-public members from inlined code - * - * @param treeExpr A function that computes the tree to be inlined, given a context - * This tree may still refer to non-public members. - * @param inlineCtxFn A function that maps the current context to the context in - * which to compute the tree. The resulting context needs - * to have the inlined method as owner. - * - * The reason to use a function rather than a fixed context here - * is to avoid space leaks. InlineInfos can survive multiple runs - * because they might be created as part of an unpickled method - * and consumed only in a future run (or never). + /** A key to be used in a context property that tracks enclosing inlined calls */ + private val InlinedCalls = new Property.Key[List[Tree]] // to be used in context + + /** Adds accessors accessors for all non-public term members accessed + * from `tree`. Non-public type members are currently left as they are. + * This means that references to a private type will lead to typing failures + * on the code when it is inlined. Less than ideal, but hard to do better (see below). + * + * @return If there are accessors generated, a thicket consisting of the rewritten `tree` + * and all accessors, otherwise the original tree. */ - private final class InlineInfo(treeExpr: Context => Tree, var inlineCtxFn: Context => Context) { - private val myAccessors = new mutable.ListBuffer[MemberDef] - private var myBody: Tree = _ - private var evaluated = false + def makeInlineable(tree: Tree)(implicit ctx: Context) = { /** A tree map which inserts accessors for all non-public term members accessed - * from inlined code. Non-public type members are currently left as they are. - * This means that references to a provate type will lead to typing failures - * on the code when it is inlined. Less than ideal, but hard to do better (see below). + * from inlined code. Accesors are collected in the `accessors` buffer. */ - private def prepareForInline(inlineCtx: Context) = new TreeMap { - val inlineMethod = inlineCtx.owner + object addAccessors extends TreeMap { + val inlineMethod = ctx.owner + val accessors = new mutable.ListBuffer[MemberDef] /** A definition needs an accessor if it is private, protected, or qualified private */ def needsAccessor(sym: Symbol)(implicit ctx: Context) = @@ -149,7 +133,7 @@ object Inliner { .appliedToArgss((qual :: Nil) :: argss) (accessorDef, accessorRef) } - myAccessors += accessorDef + accessors += accessorDef inlining.println(i"added inline accessor: $accessorDef") accessorRef } @@ -184,106 +168,61 @@ object Inliner { } } - /** Is the inline info evaluated? */ - def isEvaluated = evaluated - - private def ensureEvaluated()(implicit ctx: Context) = - if (!evaluated) { - evaluated = true // important to set early to prevent overwrites by attachInlineInfo in typedDefDef - val inlineCtx = inlineCtxFn(ctx) - inlineCtxFn = null // null out to avoid space leaks - try { - myBody = treeExpr(inlineCtx) - myBody = prepareForInline(inlineCtx).transform(myBody)(inlineCtx) - inlining.println(i"inlinable body of ${inlineCtx.owner} = $myBody") - } - catch { - case ex: AssertionError => - println(i"failure while expanding ${inlineCtx.owner}") - throw ex - } - } - else assert(myBody != null) - - /** The body to inline */ - def body(implicit ctx: Context): Tree = { - ensureEvaluated() - myBody - } - - /** The accessor defs to non-public members which need to be defined - * together with the inline method - */ - def accessors(implicit ctx: Context): List[MemberDef] = { - ensureEvaluated() - myAccessors.toList - } + val tree1 = addAccessors.transform(tree) + flatTree(tree1 :: addAccessors.accessors.toList) } - /** A key to be used in an attachment for `@inline` annotations */ - private val InlineInfo = new Property.Key[InlineInfo] - - /** A key to be used in a context property that tracks enclosing inlined calls */ - private val InlinedCalls = new Property.Key[List[Tree]] // to be used in context - /** Register inline info for given inline method `sym`. * * @param sym The symbol denotatioon of the inline method for which info is registered * @param treeExpr A function that computes the tree to be inlined, given a context * This tree may still refer to non-public members. - * @param inlineCtxFn A function that maps the current context to the context in - * which to compute the tree. The resulting context needs + * @param ctx The context to use for evaluating `treeExpr`. It needs * to have the inlined method as owner. */ def registerInlineInfo( - sym: SymDenotation, treeExpr: Context => Tree, inlineCtxFn: Context => Context)(implicit ctx: Context): Unit = { - if (sym.unforcedAnnotation(defn.BodyAnnot).isEmpty) { - val inlineAnnotTree = sym.unforcedAnnotation(defn.InlineAnnot).get.tree - inlineAnnotTree.getAttachment(InlineInfo) match { - case Some(inlineInfo) if inlineInfo.isEvaluated => // keep existing attachment - case _ => - if (!ctx.isAfterTyper) - inlineAnnotTree.putAttachment(InlineInfo, new InlineInfo(treeExpr, inlineCtxFn)) - } + sym: SymDenotation, treeExpr: Context => Tree)(implicit ctx: Context): Unit = { + sym.unforcedAnnotation(defn.BodyAnnot) match { + case Some(ann: ConcreteBodyAnnotation) => + case Some(ann: LazyBodyAnnotation) if ann.isEvaluated => + case _ => + if (!ctx.isAfterTyper) { + val inlineCtx = ctx + sym.updateAnnotation(LazyBodyAnnotation { _ => + implicit val ctx: Context = inlineCtx + val tree1 = treeExpr(ctx) + makeInlineable(tree1) + }) + } } } - /** Register an evaluated inline body for `sym` */ - def updateInlineBody(sym: SymDenotation, body: Tree)(implicit ctx: Context): Unit = { - assert(sym.unforcedAnnotation(defn.BodyAnnot).isDefined) - sym.removeAnnotation(defn.BodyAnnot) - sym.addAnnotation(ConcreteBodyAnnotation(body)) - } - - /** Optionally, the inline info attached to the `@inline` annotation of `sym`. */ - private def inlineInfo(sym: SymDenotation)(implicit ctx: Context): Option[InlineInfo] = - sym.getAnnotation(defn.InlineAnnot).get.tree.getAttachment(InlineInfo) - - /** Optionally, the inline body attached to the `@inline` annotation of `sym`. */ - private def inlineBody(sym: SymDenotation)(implicit ctx: Context): Option[Tree] = - sym.getAnnotation(defn.BodyAnnot).map(_.tree) - - /** Definition is an inline method with a known body to inline (note: definitions coming + /** `sym` has an inline method with a known body to inline (note: definitions coming * from Scala2x class files might be `@inline`, but still lack that body. */ def hasBodyToInline(sym: SymDenotation)(implicit ctx: Context): Boolean = - sym.isInlineMethod && (inlineInfo(sym).isDefined || inlineBody(sym).isDefined) + sym.isInlineMethod && sym.hasAnnotation(defn.BodyAnnot) + + private def bodyAndAccessors(sym: SymDenotation)(implicit ctx: Context): (Tree, List[MemberDef]) = + sym.unforcedAnnotation(defn.BodyAnnot).get.tree match { + case Thicket(body :: accessors) => (body, accessors.asInstanceOf[List[MemberDef]]) + case body => (body, Nil) + } /** The body to inline for method `sym`. * @pre hasBodyToInline(sym) */ def bodyToInline(sym: SymDenotation)(implicit ctx: Context): Tree = - inlineInfo(sym).map(_.body).getOrElse(inlineBody(sym).get) + bodyAndAccessors(sym)._1 /** The accessors to non-public members needed by the inlinable body of `sym`. + * These accessors are dropped as a side effect of calling this method. * @pre hasBodyToInline(sym) */ def removeInlineAccessors(sym: SymDenotation)(implicit ctx: Context): List[MemberDef] = { - val inlineAnnotTree = sym.getAnnotation(defn.InlineAnnot).get.tree - val inlineInfo = inlineAnnotTree.removeAttachment(InlineInfo).get - sym.addAnnotation(ConcreteBodyAnnotation(inlineInfo.body)) - assert(sym.getAnnotation(defn.BodyAnnot).isDefined) - inlineInfo.accessors + val (body, accessors) = bodyAndAccessors(sym) + if (accessors.nonEmpty) sym.updateAnnotation(ConcreteBodyAnnotation(body)) + accessors } /** Try to inline a call to a `@inline` method. Fail with error if the maximal diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 9ba6a8963b30..2e714ab6de02 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -588,8 +588,8 @@ class Namer { typer: Typer => case original: untpd.DefDef => Inliner.registerInlineInfo( denot, - implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs, - _ => localContext(denot.symbol)) + implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs + )(localContext(denot.symbol)) case _ => } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 885bc56d63b6..f9fca117d6bd 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1118,7 +1118,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(implicit ctx: Context): Unit = { // necessary to force annotation trees to be computed. - sym.annotations.foreach(_.tree) + sym.annotations.foreach(_.ensureCompleted) val annotCtx = ctx.outersIterator.dropWhile(_.owner == sym).next // necessary in order to mark the typed ahead annotations as definitely typed: untpd.modsDeco(mdef).mods.annotations.foreach(typedAnnotation(_)(annotCtx)) @@ -1173,7 +1173,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit // Overwrite inline body to make sure it is not evaluated twice if (sym.hasAnnotation(defn.InlineAnnot)) - Inliner.registerInlineInfo(sym, _ => rhs1, _ => ctx) + Inliner.registerInlineInfo(sym, _ => rhs1) if (sym.isAnonymousFunction) { // If we define an anonymous function, make sure the return type does not From 53b165e69bb45f424184d02e76c520b67ee0c1d7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 15 Sep 2016 21:45:26 +0200 Subject: [PATCH 68/77] Cleanups Better comments and refactorings that move some things around so that less modules depend on Inliner. --- src/dotty/tools/dotc/ast/TreeTypeMap.scala | 1 - src/dotty/tools/dotc/ast/tpd.scala | 22 ++++++++++++++-- src/dotty/tools/dotc/core/Decorators.scala | 7 +++-- src/dotty/tools/dotc/core/TyperState.scala | 2 +- .../tools/dotc/transform/TreeTransform.scala | 3 +-- src/dotty/tools/dotc/typer/Inliner.scala | 26 ++----------------- src/dotty/tools/dotc/typer/Typer.scala | 2 +- 7 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/src/dotty/tools/dotc/ast/TreeTypeMap.scala index 80499cceb7e3..cf529dfdaa12 100644 --- a/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -6,7 +6,6 @@ import core._ import Types._, Contexts._, Constants._, Names._, Flags._ import SymDenotations._, Symbols._, Annotations._, Trees._, Symbols._ import Denotations._, Decorators._ -import config.Printers.inlining import dotty.tools.dotc.transform.SymUtils._ /** A map that applies three functions and a substitution together to a tree and diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index 54005808fc6e..8ba7bc54d2fb 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -10,6 +10,7 @@ import util.Positions._, Types._, Contexts._, Constants._, Names._, Flags._ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Symbols._ import Denotations._, Decorators._, DenotTransformers._ import collection.mutable +import util.{Property, SourceFile, NoSource} import typer.ErrorReporting._ import scala.annotation.tailrec @@ -921,8 +922,25 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } } - // ensure that constructors are fully applied? - // ensure that normal methods are fully applied? + /** A key to be used in a context property that tracks enclosing inlined calls */ + private val InlinedCalls = new Property.Key[List[Tree]] + /** A context derived form `ctx` that records `call` as innermost enclosing + * call for which the inlined version is currently processed. + */ + def inlineContext(call: Tree)(implicit ctx: Context): Context = + ctx.fresh.setProperty(InlinedCalls, call :: enclosingInlineds) + + /** All enclosing calls that are currently inlined, from innermost to outermost */ + def enclosingInlineds(implicit ctx: Context): List[Tree] = + ctx.property(InlinedCalls).getOrElse(Nil) + + /** The source file where the symbol of the `@inline` method referred to by `call` + * is defined + */ + def sourceFile(call: Tree)(implicit ctx: Context) = { + val file = call.symbol.sourceFile + if (file != null && file.exists) new SourceFile(file) else NoSource + } } diff --git a/src/dotty/tools/dotc/core/Decorators.scala b/src/dotty/tools/dotc/core/Decorators.scala index 691e0aeba013..3bf17730a9f9 100644 --- a/src/dotty/tools/dotc/core/Decorators.scala +++ b/src/dotty/tools/dotc/core/Decorators.scala @@ -7,8 +7,7 @@ import Contexts._, Names._, Phases._, printing.Texts._, printing.Printer, printi import util.Positions.Position, util.SourcePosition import collection.mutable.ListBuffer import dotty.tools.dotc.transform.TreeTransforms._ -import typer.Inliner -import ast.tpd.Tree +import ast.tpd._ import scala.language.implicitConversions import printing.Formatting._ @@ -153,11 +152,11 @@ object Decorators { implicit def sourcePos(pos: Position)(implicit ctx: Context): SourcePosition = { def recur(inlinedCalls: List[Tree], pos: Position): SourcePosition = inlinedCalls match { case inlinedCall :: rest => - Inliner.sourceFile(inlinedCall).atPos(pos).withOuter(recur(rest, inlinedCall.pos)) + sourceFile(inlinedCall).atPos(pos).withOuter(recur(rest, inlinedCall.pos)) case empty => ctx.source.atPos(pos) } - recur(Inliner.enclosingInlineds, pos) + recur(enclosingInlineds, pos) } implicit class StringInterpolators(val sc: StringContext) extends AnyVal { diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index 3ed4788ee967..7e332b412503 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -123,7 +123,7 @@ extends TyperState(r) { * that were temporarily instantiated in the current typer state are permanently * instantiated instead. * - * A note on merging: A case is in isApplicableSafe.scala. It turns out that this + * A note on merging: An interesting test case is isApplicableSafe.scala. It turns out that this * requires a context merge using the new `&' operator. Sequence of actions: * 1) Typecheck argument in typerstate 1. * 2) Cache argument. diff --git a/src/dotty/tools/dotc/transform/TreeTransform.scala b/src/dotty/tools/dotc/transform/TreeTransform.scala index 40a5cb0bfa09..52a3ad94e41a 100644 --- a/src/dotty/tools/dotc/transform/TreeTransform.scala +++ b/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -15,7 +15,6 @@ import dotty.tools.dotc.core.Mode import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.util.DotClass -import dotty.tools.dotc.typer.Inliner import scala.annotation.tailrec import config.Printers.transforms import scala.util.control.NonFatal @@ -1117,7 +1116,7 @@ object TreeTransforms { if (mutatedInfo eq null) tree else { val bindings = transformSubTrees(tree.bindings, mutatedInfo, cur) - val expansion = transform(tree.expansion, mutatedInfo, cur)(Inliner.inlineContext(tree)) + val expansion = transform(tree.expansion, mutatedInfo, cur)(inlineContext(tree)) goInlined(cpy.Inlined(tree)(tree.call, bindings, expansion), mutatedInfo.nx.nxTransInlined(cur)) } case tree: TypeTree => diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index fd551bc39228..f0464c7643f5 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -21,25 +21,21 @@ import transform.ExplicitOuter import Inferencing.fullyDefinedType import config.Printers.inlining import ErrorReporting.errorTree -import util.{Property, SourceFile, NoSource} import collection.mutable import transform.TypeUtils._ object Inliner { import tpd._ - /** A key to be used in a context property that tracks enclosing inlined calls */ - private val InlinedCalls = new Property.Key[List[Tree]] // to be used in context - /** Adds accessors accessors for all non-public term members accessed * from `tree`. Non-public type members are currently left as they are. * This means that references to a private type will lead to typing failures * on the code when it is inlined. Less than ideal, but hard to do better (see below). - * + * * @return If there are accessors generated, a thicket consisting of the rewritten `tree` * and all accessors, otherwise the original tree. */ - def makeInlineable(tree: Tree)(implicit ctx: Context) = { + private def makeInlineable(tree: Tree)(implicit ctx: Context) = { /** A tree map which inserts accessors for all non-public term members accessed * from inlined code. Accesors are collected in the `accessors` buffer. @@ -250,24 +246,6 @@ object Inliner { tpd.seq(inlined.bindings, reposition.transform(inlined.expansion)) } - /** A context derived form `ctx` that records `call` as innermost enclosing - * call for which the inlined version is currently processed. - */ - def inlineContext(call: Tree)(implicit ctx: Context): Context = - ctx.fresh.setProperty(InlinedCalls, call :: enclosingInlineds) - - /** All enclosing calls that are currently inlined, from innermost to outermost */ - def enclosingInlineds(implicit ctx: Context): List[Tree] = - ctx.property(InlinedCalls).getOrElse(Nil) - - /** The source file where the symbol of the `@inline` method referred to by `call` - * is defined - */ - def sourceFile(call: Tree)(implicit ctx: Context) = { - val file = call.symbol.sourceFile - if (file != null && file.exists) new SourceFile(file) else NoSource - } - /** The qualifier part of a Select, Ident, or SelectFromTypeTree tree. * For an Ident, this is the `This` of the current class. (TODO: use elsewhere as well?) */ diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index f9fca117d6bd..fb7f26f36b92 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -949,7 +949,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context): Inlined = { val (exprCtx, bindings1) = typedBlockStats(tree.bindings) - val expansion1 = typed(tree.expansion, pt)(Inliner.inlineContext(tree.call)(exprCtx)) + val expansion1 = typed(tree.expansion, pt)(inlineContext(tree.call)(exprCtx)) assignType(cpy.Inlined(tree)(tree.call, bindings1.asInstanceOf[List[MemberDef]], expansion1), bindings1, expansion1) } From eb95a04d8d7741ee392df9de05a4040a0f3535ca Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Fri, 23 Sep 2016 16:30:20 +0200 Subject: [PATCH 69/77] Remove incorrect special case for Inline purity checks This special case was added two years ago, quoting from 5428549a57b710b11e57aab4eee24e9b89b8b97c "Inlined pure values are pure even if referenced from impure prefixes (i.e. prefix need not be evaluated)" This does not match the current semantics for inline where the prefix is always evaluated. --- src/dotty/tools/dotc/ast/TreeInfo.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/ast/TreeInfo.scala b/src/dotty/tools/dotc/ast/TreeInfo.scala index a6c49cffd829..bf375acde9b1 100644 --- a/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -329,8 +329,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case Ident(_) => refPurity(tree) case Select(qual, _) => - refPurity(tree).min( - if (tree.symbol.is(Inline)) Pure else exprPurity(qual)) + refPurity(tree).min(exprPurity(qual)) case TypeApply(fn, _) => exprPurity(fn) /* From f239bdb79f57855544b313573117e5603d038383 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Fri, 30 Sep 2016 15:24:59 +0200 Subject: [PATCH 70/77] Disable scaladoc generation for dotty This makes "publishLocal" much faster which is extremely useful when working on the sbt bridge. --- project/Build.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/project/Build.scala b/project/Build.scala index ec2db8339e88..5a659fd27f01 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -64,6 +64,9 @@ object DottyBuild extends Build { lazy val dotty = project.in(file(".")). dependsOn(`dotty-interfaces`). settings( + // Disable scaladoc generation, makes publishLocal much faster + publishArtifact in packageDoc := false, + overrideScalaVersionSetting, // set sources to src/, tests to test/ and resources to resources/ From d4f00691c4a26f716cbf29878022309a741de070 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Fri, 30 Sep 2016 15:49:22 +0200 Subject: [PATCH 71/77] Fix incremental compilation when inline method signature changes --- .../sbt-test/source-dependencies/inline/A.scala | 3 +++ .../sbt-test/source-dependencies/inline/C.scala | 5 +++++ .../source-dependencies/inline/changes/B1.scala | 4 ++++ .../source-dependencies/inline/changes/B2.scala | 4 ++++ .../inline/project/DottyInjectedPlugin.scala | 17 +++++++++++++++++ .../sbt-test/source-dependencies/inline/test | 6 ++++++ .../tools/dotc/sbt/ExtractDependencies.scala | 4 ++++ 7 files changed, 43 insertions(+) create mode 100644 bridge/src/sbt-test/source-dependencies/inline/A.scala create mode 100644 bridge/src/sbt-test/source-dependencies/inline/C.scala create mode 100644 bridge/src/sbt-test/source-dependencies/inline/changes/B1.scala create mode 100644 bridge/src/sbt-test/source-dependencies/inline/changes/B2.scala create mode 100644 bridge/src/sbt-test/source-dependencies/inline/project/DottyInjectedPlugin.scala create mode 100644 bridge/src/sbt-test/source-dependencies/inline/test diff --git a/bridge/src/sbt-test/source-dependencies/inline/A.scala b/bridge/src/sbt-test/source-dependencies/inline/A.scala new file mode 100644 index 000000000000..e889eef79b68 --- /dev/null +++ b/bridge/src/sbt-test/source-dependencies/inline/A.scala @@ -0,0 +1,3 @@ +object A { + def get: Int = 1 +} diff --git a/bridge/src/sbt-test/source-dependencies/inline/C.scala b/bridge/src/sbt-test/source-dependencies/inline/C.scala new file mode 100644 index 000000000000..0370943e195d --- /dev/null +++ b/bridge/src/sbt-test/source-dependencies/inline/C.scala @@ -0,0 +1,5 @@ +object C { + def test: Unit = { + val i: Int = B.getInline + } +} diff --git a/bridge/src/sbt-test/source-dependencies/inline/changes/B1.scala b/bridge/src/sbt-test/source-dependencies/inline/changes/B1.scala new file mode 100644 index 000000000000..5685152b3819 --- /dev/null +++ b/bridge/src/sbt-test/source-dependencies/inline/changes/B1.scala @@ -0,0 +1,4 @@ +object B { + @inline def getInline: Int = + A.get +} diff --git a/bridge/src/sbt-test/source-dependencies/inline/changes/B2.scala b/bridge/src/sbt-test/source-dependencies/inline/changes/B2.scala new file mode 100644 index 000000000000..1de104357e41 --- /dev/null +++ b/bridge/src/sbt-test/source-dependencies/inline/changes/B2.scala @@ -0,0 +1,4 @@ +object B { + @inline def getInline: Double = + A.get +} diff --git a/bridge/src/sbt-test/source-dependencies/inline/project/DottyInjectedPlugin.scala b/bridge/src/sbt-test/source-dependencies/inline/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..3433779b6c66 --- /dev/null +++ b/bridge/src/sbt-test/source-dependencies/inline/project/DottyInjectedPlugin.scala @@ -0,0 +1,17 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := "0.1-SNAPSHOT", + scalaOrganization := "ch.epfl.lamp", + scalacOptions += "-language:Scala2", + scalaBinaryVersion := "2.11", + autoScalaLibrary := false, + libraryDependencies ++= Seq("org.scala-lang" % "scala-library" % "2.11.5"), + scalaCompilerBridgeSource := ("ch.epfl.lamp" % "dotty-bridge" % "0.1.1-SNAPSHOT" % "component").sources() + ) +} diff --git a/bridge/src/sbt-test/source-dependencies/inline/test b/bridge/src/sbt-test/source-dependencies/inline/test new file mode 100644 index 000000000000..ba891710fc76 --- /dev/null +++ b/bridge/src/sbt-test/source-dependencies/inline/test @@ -0,0 +1,6 @@ +$ copy-file changes/B1.scala B.scala +> compile + +$ copy-file changes/B2.scala B.scala +# Compilation of C.scala should fail because B.getInline now has type Double instead of Int +-> compile diff --git a/src/dotty/tools/dotc/sbt/ExtractDependencies.scala b/src/dotty/tools/dotc/sbt/ExtractDependencies.scala index a36b47aa8e64..229e35360401 100644 --- a/src/dotty/tools/dotc/sbt/ExtractDependencies.scala +++ b/src/dotty/tools/dotc/sbt/ExtractDependencies.scala @@ -190,6 +190,10 @@ private class ExtractDependenciesCollector(implicit val ctx: Context) extends tp addUsedName(rename) case _ => } + case Inlined(call, _, _) => + // The inlined call is normally ignored by TreeTraverser but we need to + // record it as a dependency + traverse(call) case t: TypeTree => usedTypeTraverser.traverse(t.tpe) case ref: RefTree => From 6821bac06b34c416ca94216025b864e73592cbe7 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Fri, 30 Sep 2016 18:42:34 +0200 Subject: [PATCH 72/77] Fix incremental compilation when inline method body changes Since we have no nice way of hashing a typed tree we use the pretty-printed tree of the method (as it would be printed by -Xprint:posttyper -Xprint-types) as a hacky substitute for now. --- .../source-dependencies/inline/C.scala | 2 +- .../inline/changes/B3.scala | 4 ++++ .../sbt-test/source-dependencies/inline/test | 8 ++++++++ src/dotty/tools/dotc/sbt/ExtractAPI.scala | 20 +++++++++++++++++-- 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 bridge/src/sbt-test/source-dependencies/inline/changes/B3.scala diff --git a/bridge/src/sbt-test/source-dependencies/inline/C.scala b/bridge/src/sbt-test/source-dependencies/inline/C.scala index 0370943e195d..caeb61535421 100644 --- a/bridge/src/sbt-test/source-dependencies/inline/C.scala +++ b/bridge/src/sbt-test/source-dependencies/inline/C.scala @@ -1,5 +1,5 @@ object C { - def test: Unit = { + def main(args: Array[String]): Unit = { val i: Int = B.getInline } } diff --git a/bridge/src/sbt-test/source-dependencies/inline/changes/B3.scala b/bridge/src/sbt-test/source-dependencies/inline/changes/B3.scala new file mode 100644 index 000000000000..991bd17b893a --- /dev/null +++ b/bridge/src/sbt-test/source-dependencies/inline/changes/B3.scala @@ -0,0 +1,4 @@ +object B { + @inline def getInline: Int = + sys.error("This is an expected failure when running C") +} diff --git a/bridge/src/sbt-test/source-dependencies/inline/test b/bridge/src/sbt-test/source-dependencies/inline/test index ba891710fc76..56fdb048641f 100644 --- a/bridge/src/sbt-test/source-dependencies/inline/test +++ b/bridge/src/sbt-test/source-dependencies/inline/test @@ -4,3 +4,11 @@ $ copy-file changes/B1.scala B.scala $ copy-file changes/B2.scala B.scala # Compilation of C.scala should fail because B.getInline now has type Double instead of Int -> compile + +$ copy-file changes/B1.scala B.scala +> run + +$ copy-file changes/B3.scala B.scala +# The body of B.getInline was changed so C.scala should be recompiled +# If it was recompiled, run should fail since B.getInline now throws an exception +-> run diff --git a/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 437e36bb9484..a7b18b6d6d79 100644 --- a/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -5,6 +5,7 @@ import ast.{Trees, tpd} import core._, core.Decorators._ import Contexts._, Flags._, Phases._, Trees._, Types._, Symbols._ import Names._, NameOps._, StdNames._ +import typer.Inliner import dotty.tools.io.Path import java.io.PrintWriter @@ -497,6 +498,21 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder sym.is(Implicit), sym.is(Lazy), sym.is(Macro), sym.is(SuperAccessor)) } - // TODO: Annotation support - def apiAnnotations(s: Symbol): List[api.Annotation] = Nil + // TODO: Support other annotations + def apiAnnotations(s: Symbol): List[api.Annotation] = { + val annots = new mutable.ListBuffer[api.Annotation] + + if (Inliner.hasBodyToInline(s)) { + // FIXME: If the body of an inline method changes, all the reverse + // dependencies of this method need to be recompiled. sbt has no way + // of tracking method bodies, so as a hack we include the pretty-printed + // typed tree of the method as part of the signature we send to sbt. + // To do this properly we would need a way to hash trees and types in + // dotty itself. + val printTypesCtx = ctx.fresh.setSetting(ctx.settings.printtypes, true) + annots += marker(Inliner.bodyToInline(s).show(printTypesCtx).toString) + } + + annots.toList + } } From e5119a058ff11cc0cca565c22c29f090401d794b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 2 Oct 2016 15:54:16 +0200 Subject: [PATCH 73/77] Address @smarter's review comments --- src/dotty/tools/dotc/ast/TreeInfo.scala | 2 +- src/dotty/tools/dotc/ast/Trees.scala | 2 +- src/dotty/tools/dotc/core/Flags.scala | 2 +- src/dotty/tools/dotc/core/Types.scala | 4 ++-- src/dotty/tools/dotc/parsing/Scanners.scala | 2 +- src/dotty/tools/dotc/typer/Inliner.scala | 15 +++++++++------ src/dotty/tools/dotc/typer/Typer.scala | 4 ++-- tests/run/inlinedAssign.scala | 3 +-- 8 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/dotty/tools/dotc/ast/TreeInfo.scala b/src/dotty/tools/dotc/ast/TreeInfo.scala index bf375acde9b1..7911840c6c1c 100644 --- a/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -455,7 +455,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => } } - /** If tree is a closure, it's body, otherwise tree itself */ + /** If tree is a closure, its body, otherwise tree itself */ def closureBody(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { case Block((meth @ DefDef(nme.ANON_FUN, _, _, _, _)) :: Nil, Closure(_, _, _)) => meth.rhs case _ => tree diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index 7e7c74928b96..6986e40e720e 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -520,7 +520,7 @@ object Trees { case class Inlined[-T >: Untyped] private[ast] (call: tpd.Tree, bindings: List[MemberDef[T]], expansion: Tree[T]) extends Tree[T] { type ThisTree[-T >: Untyped] = Inlined[T] - } + } /** A type tree that represents an existing or inferred type */ case class TypeTree[-T >: Untyped] private[ast] (original: Tree[T]) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index b7befa6e28ef..3f4433708078 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -529,7 +529,7 @@ object Flags { /** Either method or lazy or deferred */ final val MethodOrLazyOrDeferred = Method | Lazy | Deferred - /** Labeled `private` or `final` */ + /** Labeled `private`, `final`, or `inline` */ final val PrivateOrFinalOrInline = Private | Final | Inline /** A private method */ diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 00286156a198..2f1b6b829d45 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2448,7 +2448,7 @@ object Types { } /** Add @inlineParam to inline call-by-value parameters */ def translateInline(tp: Type): Type = tp match { - case tp @ ExprType(tp1) => tp + case _: ExprType => tp case _ => AnnotatedType(tp, Annotation(defn.InlineParamAnnot)) } def paramInfo(param: Symbol): Type = { @@ -2580,7 +2580,7 @@ object Types { x => paramBounds mapConserve (_.subst(this, x).bounds), x => resType.subst(this, x)) - /** Merge nested polytypes into one polytype. nested polytypes are normally not suported + /** Merge nested polytypes into one polytype. nested polytypes are normally not supported * but can arise as temporary data structures. */ def flatten(implicit ctx: Context): PolyType = resType match { diff --git a/src/dotty/tools/dotc/parsing/Scanners.scala b/src/dotty/tools/dotc/parsing/Scanners.scala index a1a21583c5c9..e16aa670fda5 100644 --- a/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/src/dotty/tools/dotc/parsing/Scanners.scala @@ -220,7 +220,7 @@ object Scanners { private def treatAsIdent() = { - testScala2Mode(i"$name is now a keyword, put in `...` to keep as an identifier") + testScala2Mode(i"$name is now a keyword, write `$name` instead of $name to keep it as an identifier") patch(source, Position(offset), "`") patch(source, Position(offset + name.length), "`") IDENTIFIER diff --git a/src/dotty/tools/dotc/typer/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala index f0464c7643f5..55008c0c54f1 100644 --- a/src/dotty/tools/dotc/typer/Inliner.scala +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -80,9 +80,14 @@ object Inliner { */ def addAccessor(tree: Tree, refPart: Tree, targs: List[Tree], argss: List[List[Tree]], accessedType: Type, rhs: (Tree, List[Type], List[List[Tree]]) => Tree)(implicit ctx: Context): Tree = { + val qual = qualifier(refPart) + def refIsLocal = qual match { + case qual: This => qual.symbol == refPart.symbol.owner + case _ => false + } val (accessorDef, accessorRef) = - if (refPart.symbol.isStatic) { - // Easy case: Reference to a static symbol + if (refPart.symbol.isStatic || refIsLocal) { + // Easy case: Reference to a static symbol or a symbol referenced via `this.` val accessorType = accessedType.ensureMethodic val accessor = accessorSymbol(tree, accessorType).asTerm val accessorDef = polyDefDef(accessor, tps => argss => @@ -90,8 +95,7 @@ object Inliner { val accessorRef = ref(accessor).appliedToTypeTrees(targs).appliedToArgss(argss) (accessorDef, accessorRef) } else { - // Hard case: Reference needs to go via a dyanmic prefix - val qual = qualifier(refPart) + // Hard case: Reference needs to go via a dynamic prefix inlining.println(i"adding inline accessor for $tree -> (${qual.tpe}, $refPart: ${refPart.getClass}, [$targs%, %], ($argss%, %))") // Need to dealias in order to catch all possible references to abstracted over types in @@ -246,12 +250,11 @@ object Inliner { tpd.seq(inlined.bindings, reposition.transform(inlined.expansion)) } - /** The qualifier part of a Select, Ident, or SelectFromTypeTree tree. + /** The qualifier part of a Select or Ident. * For an Ident, this is the `This` of the current class. (TODO: use elsewhere as well?) */ private def qualifier(tree: Tree)(implicit ctx: Context) = tree match { case Select(qual, _) => qual - case SelectFromTypeTree(qual, _) => qual case _ => This(ctx.owner.enclosingClass.asClass) } } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index fb7f26f36b92..e32805e0f971 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1357,7 +1357,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = tree.tree match { case tree1: TypeTree => tree1 // no change owner necessary here ... - case tree1: Ident => tree1 // ... or here + case tree1: Ident => tree1 // ... or here, since these trees cannot contain bindings case tree1 => if (ctx.owner ne tree.owner) tree1.changeOwner(tree.owner, ctx.owner) else tree1 @@ -1859,7 +1859,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit if (folded ne tree) return adaptConstant(folded, folded.tpe.asInstanceOf[ConstantType]) // drop type if prototype is Unit if (pt isRef defn.UnitClass) - return tpd.Block(tree :: Nil, Literal(Constant(()))) + return adapt(tpd.Block(tree :: Nil, Literal(Constant(()))), pt) // convert function literal to SAM closure tree match { case Closure(Nil, id @ Ident(nme.ANON_FUN), _) diff --git a/tests/run/inlinedAssign.scala b/tests/run/inlinedAssign.scala index f241780ed509..1b524f92bf24 100644 --- a/tests/run/inlinedAssign.scala +++ b/tests/run/inlinedAssign.scala @@ -1,9 +1,8 @@ object Test { inline def swap[T](x: T, inline x_= : T => Unit, y: T, inline y_= : T => Unit) = { - val t = x x_=(y) - y_=(t) + y_=(x) } inline def f(x: Int => Unit) = x From e7954376c245d42e0e70df501c73c3d5971db41e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 2 Oct 2016 20:03:27 +0200 Subject: [PATCH 74/77] Fix inline failure in the presence of unit conversion --- src/dotty/tools/dotc/typer/Typer.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index e32805e0f971..3aff69bdb4c8 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1859,7 +1859,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit if (folded ne tree) return adaptConstant(folded, folded.tpe.asInstanceOf[ConstantType]) // drop type if prototype is Unit if (pt isRef defn.UnitClass) - return adapt(tpd.Block(tree :: Nil, Literal(Constant(()))), pt) + // local adaptation makes sure every adapted tree conforms to its pt + // so will take the code path that decides on inlining + return tpd.Block(adapt(tree, WildcardType) :: Nil, Literal(Constant(()))) // convert function literal to SAM closure tree match { case Closure(Nil, id @ Ident(nme.ANON_FUN), _) From 0093c5d5ddcc4d3ce5ba24c41419a042f2e482ff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 5 Oct 2016 14:05:11 +0200 Subject: [PATCH 75/77] Add test case --- tests/pos/inlineUnit.scala | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tests/pos/inlineUnit.scala diff --git a/tests/pos/inlineUnit.scala b/tests/pos/inlineUnit.scala new file mode 100644 index 000000000000..e4f135db7deb --- /dev/null +++ b/tests/pos/inlineUnit.scala @@ -0,0 +1,7 @@ +object Test { + inline def foo: Int = 1 + + def test: Unit = { + foo + } +} From 215b97c22bd85fb807896fc03ffaab22c0748414 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 5 Oct 2016 18:00:38 +0200 Subject: [PATCH 76/77] DottyBytecodeTest: fix diffInstructions output --- test/test/DottyBytecodeTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test/DottyBytecodeTest.scala b/test/test/DottyBytecodeTest.scala index f2218d4b6796..dbf86bf8eb89 100644 --- a/test/test/DottyBytecodeTest.scala +++ b/test/test/DottyBytecodeTest.scala @@ -105,7 +105,7 @@ trait DottyBytecodeTest extends DottyTest { val a = isaPadded(line-1) val b = isbPadded(line-1) - sb append (s"""$line${" " * (lineWidth-line.toString.length)} ${if (a==b) "==" else "<>"} $a${" " * (width-a.length)} | $b""") + sb append (s"""$line${" " * (lineWidth-line.toString.length)} ${if (a==b) "==" else "<>"} $a${" " * (width-a.length)} | $b\n""") } } sb.toString From e0a14e7939eda6a7f4914831975b2ac8877696f2 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 5 Oct 2016 18:01:29 +0200 Subject: [PATCH 77/77] Add InlineBytecodeTests to check that inline really works --- test/test/InlineBytecodeTests.scala | 32 +++++++++++++++++++++++++++++ tests/pos/inlineUnit.scala | 7 ------- 2 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 test/test/InlineBytecodeTests.scala delete mode 100644 tests/pos/inlineUnit.scala diff --git a/test/test/InlineBytecodeTests.scala b/test/test/InlineBytecodeTests.scala new file mode 100644 index 000000000000..f7dc35305709 --- /dev/null +++ b/test/test/InlineBytecodeTests.scala @@ -0,0 +1,32 @@ +package test + +import org.junit.Assert._ +import org.junit.Test + +class InlineBytecodeTests extends DottyBytecodeTest { + import ASMConverters._ + @Test def inlineUnit = { + val source = """ + |class Foo { + | inline def foo: Int = 1 + | + | def meth1: Unit = foo + | def meth2: Unit = 1 + |} + """.stripMargin + + checkBCode(source) { dir => + val clsIn = dir.lookupName("Foo.class", directory = false).input + val clsNode = loadClassNode(clsIn) + val meth1 = getMethod(clsNode, "meth1") + val meth2 = getMethod(clsNode, "meth2") + + val instructions1 = instructionsFromMethod(meth1) + val instructions2 = instructionsFromMethod(meth2) + + assert(instructions1 == instructions2, + "`foo` was not properly inlined in `meth1`\n" + + diffInstructions(instructions1, instructions2)) + } + } +} diff --git a/tests/pos/inlineUnit.scala b/tests/pos/inlineUnit.scala deleted file mode 100644 index e4f135db7deb..000000000000 --- a/tests/pos/inlineUnit.scala +++ /dev/null @@ -1,7 +0,0 @@ -object Test { - inline def foo: Int = 1 - - def test: Unit = { - foo - } -}