aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2016-09-01 11:59:13 +0200
committerMartin Odersky <odersky@gmail.com>2016-10-02 16:07:00 +0200
commitc87a9dd1f34cd7afe3fba0edfa1463019eaa78bd (patch)
tree7500f567d08b96ccbf3a26dd02b76c0ca42aadda
parent56f01cdf9081b0b221a01e5ae1a9f10e4aa62da9 (diff)
downloaddotty-c87a9dd1f34cd7afe3fba0edfa1463019eaa78bd.tar.gz
dotty-c87a9dd1f34cd7afe3fba0edfa1463019eaa78bd.tar.bz2
dotty-c87a9dd1f34cd7afe3fba0edfa1463019eaa78bd.zip
First version of inline scheme
To be done: outer accessors To be done: error positions
-rw-r--r--src/dotty/annotation/inline.scala8
-rw-r--r--src/dotty/tools/dotc/config/Printers.scala1
-rw-r--r--src/dotty/tools/dotc/config/ScalaSettings.scala1
-rw-r--r--src/dotty/tools/dotc/core/Contexts.scala4
-rw-r--r--src/dotty/tools/dotc/core/Definitions.scala2
-rw-r--r--src/dotty/tools/dotc/core/NameOps.scala2
-rw-r--r--src/dotty/tools/dotc/core/SymDenotations.scala17
-rw-r--r--src/dotty/tools/dotc/transform/PostTyper.scala4
-rw-r--r--src/dotty/tools/dotc/typer/Inliner.scala200
-rw-r--r--src/dotty/tools/dotc/typer/Namer.scala7
-rw-r--r--src/dotty/tools/dotc/typer/ReTyper.scala4
-rw-r--r--src/dotty/tools/dotc/typer/Typer.scala18
-rw-r--r--src/dotty/tools/dotc/util/SourceFile.scala1
-rw-r--r--tests/neg/inlineAccess.scala15
-rw-r--r--tests/run/inlineTest.scala53
15 files changed, 325 insertions, 12 deletions
diff --git a/src/dotty/annotation/inline.scala b/src/dotty/annotation/inline.scala
new file mode 100644
index 000000000..ff4ca08b6
--- /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 322bc82d9..4168cf5a6 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 c090a5515..d05ae0803 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 cd76fe88b..d8beb4b5c 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 cb83fda04..c9a3ef4de 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 f5e0eb8cd..ea255e5b3 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 ab45550a4..160d3bc30 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 6af225035..51851a589 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 000000000..852689a75
--- /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 6eca9be41..06e798bdb 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 9750957bf..03b415a6f 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 562af75f6..007eaa468 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 6b547203e..1d71552c0 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("<no source>", 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 000000000..cfb1cc06f
--- /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 000000000..feaa43fbb
--- /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)
+ }
+
+}