diff options
author | Guillaume Martres <smarter@ubuntu.com> | 2016-10-06 15:02:17 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-10-06 15:02:17 +0200 |
commit | 87a775724173bd803a0c4956408e61fd0d5812af (patch) | |
tree | c564a236f9247b085ed26c1fb007dad74ed049dd /src | |
parent | a3064622e7ce4d73ddd91de0fc6bebfe0ec23ae9 (diff) | |
parent | e0a14e7939eda6a7f4914831975b2ac8877696f2 (diff) | |
download | dotty-87a775724173bd803a0c4956408e61fd0d5812af.tar.gz dotty-87a775724173bd803a0c4956408e61fd0d5812af.tar.bz2 dotty-87a775724173bd803a0c4956408e61fd0d5812af.zip |
Merge pull request #1492 from dotty-staging/add-inline
Implement inline
Diffstat (limited to 'src')
67 files changed, 1354 insertions, 366 deletions
diff --git a/src/dotty/annotation/internal/Body.scala b/src/dotty/annotation/internal/Body.scala new file mode 100644 index 000000000..7e26b02f2 --- /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/annotation/internal/InlineParam.scala b/src/dotty/annotation/internal/InlineParam.scala new file mode 100644 index 000000000..a144f9edb --- /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/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 2120fa73e..178cba7c4 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/FromTasty.scala b/src/dotty/tools/dotc/FromTasty.scala index 05e97f30a..b060a2054 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/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index edd6da5c9..ecb6a3212 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) @@ -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/ast/TreeInfo.scala b/src/dotty/tools/dotc/ast/TreeInfo.scala index 725838ef6..7911840c6 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. */ @@ -311,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. @@ -327,13 +323,13 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case EmptyTree | This(_) | Super(_, _) - | Literal(_) => + | Literal(_) + | Closure(_, _, _) => Pure 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) /* @@ -435,6 +431,36 @@ 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) + } + + /** 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, 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 + } + /** 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/ast/TreeTypeMap.scala b/src/dotty/tools/dotc/ast/TreeTypeMap.scala index a35fe2e8f..cf529dfda 100644 --- a/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -92,11 +92,20 @@ 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)) + res.symbol.transformAnnotations { + case ann: BodyAnnotation => ann.derivedAnnotation(res.rhs) + case ann => ann + } + res case blk @ Block(stats, expr) => 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) @@ -127,10 +136,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/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index bb6fbd5ba..6986e40e7 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 @@ -503,6 +503,25 @@ object Trees { override def toString = s"JavaSeqLiteral($elems, $elemtpt)" } + /** 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] + } + /** 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 +816,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 +959,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 +1107,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 +1211,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 f59bb7a47..8ba7bc54d 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 @@ -116,6 +117,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 = + ta.assignType(untpd.Inlined(call, bindings, expansion), bindings, expansion) + def TypeTree(original: Tree)(implicit ctx: Context): TypeTree = TypeTree(original.tpe, original) @@ -918,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/ast/untpd.scala b/src/dotty/tools/dotc/ast/untpd.scala index 61c3a79a4..cc7cefbac 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 @@ -20,11 +20,18 @@ 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 */ - case class TypedSplice(tree: tpd.Tree) extends ProxyTree { + /** 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 } + 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 { @@ -161,17 +168,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 ----------------- @@ -197,6 +204,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) @@ -231,7 +239,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) @@ -259,7 +267,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/config/Printers.scala b/src/dotty/tools/dotc/config/Printers.scala index 322bc82d9..002d0f933 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 = noPrinter } diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index c090a5515..ff17a9939 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.") @@ -120,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") @@ -185,6 +185,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/Annotations.scala b/src/dotty/tools/dotc/core/Annotations.scala index 5f96a60e6..0e8e5a1f0 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 { @@ -42,6 +44,36 @@ 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 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 { + 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 + } + def isEvaluated = evaluated + } + object Annotation { def apply(tree: Tree) = ConcreteAnnotation(tree) diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index cd76fe88b..313ea3124 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 @@ -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/core/Decorators.scala b/src/dotty/tools/dotc/core/Decorators.scala index 387e7e466..3bf17730a 100644 --- a/src/dotty/tools/dotc/core/Decorators.scala +++ b/src/dotty/tools/dotc/core/Decorators.scala @@ -7,6 +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 ast.tpd._ import scala.language.implicitConversions import printing.Formatting._ @@ -40,7 +41,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) { @@ -148,8 +149,15 @@ 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(inlinedCalls: List[Tree], pos: Position): SourcePosition = inlinedCalls match { + case inlinedCall :: rest => + sourceFile(inlinedCall).atPos(pos).withOuter(recur(rest, inlinedCall.pos)) + case empty => + ctx.source.atPos(pos) + } + recur(enclosingInlineds, pos) + } implicit class StringInterpolators(val sc: StringContext) extends AnyVal { diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index cb83fda04..75b75d3d5 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") @@ -466,6 +468,10 @@ 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("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 0cdae6b98..3f4433708 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 */ @@ -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 @@ -529,8 +529,8 @@ object Flags { /** Either method or lazy or deferred */ final val MethodOrLazyOrDeferred = Method | Lazy | Deferred - /** Labeled `private` or `final` */ - final val PrivateOrFinal = Private | Final + /** Labeled `private`, `final`, or `inline` */ + final val PrivateOrFinalOrInline = Private | Final | Inline /** A private method */ final val PrivateMethod = allOf(Private, Method) @@ -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 @@ -553,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/Mode.scala b/src/dotty/tools/dotc/core/Mode.scala index 3e9b7effe..7a9bb0572 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/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index f5e0eb8cd..48e823e81 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -84,6 +84,8 @@ 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) + def isInlineAccessor = name.startsWith(nme.INLINE_ACCESSOR_PREFIX) /** Is name a variable name? */ def isVariableName: Boolean = name.length > 0 && { @@ -166,7 +168,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] @@ -419,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 f47ab1744..c52264637 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") @@ -100,6 +101,7 @@ object StdNames { val EXPAND_SEPARATOR: N = "$$" val IMPL_CLASS_SUFFIX: N = "$class" val IMPORT: N = "<import>" + val INLINE_ACCESSOR_PREFIX = "$inlineAccessor$" val INTERPRETER_IMPORT_WRAPPER: N = "$iw" val INTERPRETER_LINE_PREFIX: N = "line" val INTERPRETER_VAR_PREFIX: N = "res" @@ -252,7 +254,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 = "<outer>" // emitted by virtual pattern matcher, replaced by outer accessor in explicitouter + val OUTER_SELECT: N = "_<outer>" // emitted by inliner, replaced by outer path in explicitouter val REFINE_CLASS: N = "<refinement>" val ROOTPKG: N = "_root_" val SELECTOR_DUMMY: N = "<unapply-selector>" diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index ab45550a4..969d09c3e 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 @@ -289,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) @@ -670,9 +685,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 +695,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 +759,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: @@ -851,7 +871,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 = { @@ -1521,7 +1541,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/SymbolLoaders.scala b/src/dotty/tools/dotc/core/SymbolLoaders.scala index 3f801bda5..4ae28c10b 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/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index 7b8867ccc..7e332b412 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: 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. + * 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 diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 30d1c0136..2f1b6b829 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] = @@ -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 @@ -1788,6 +1800,7 @@ object Types { false } override def computeHash = doHash((name, sig), prefix) + override def toString = super.toString ++ s"/withSig($sig)" } trait WithFixedSym extends NamedType { @@ -2418,7 +2431,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 <repeated> 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 => @@ -2428,7 +2446,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 _: ExprType => 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 _) @@ -2554,6 +2580,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 supported + * 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) @@ -3704,8 +3748,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/core/tasty/DottyUnpickler.scala b/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala index 0ad5d6966..2c93819d5 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 b0550b70a..63bb00a71 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 fa80a2769..c29aeba70 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 e9de68e7f..8e8d58b47 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 <payload> -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 83e6020d5..98b0dc7c6 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 915ae3f21..7fcd7c29e 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/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index e5cacfc00..b6f52c0ec 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._ @@ -307,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 { @@ -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) => @@ -555,19 +565,19 @@ 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)) - assert(forwardSymRefs.isEmpty, i"unresolved symbols: ${forwardSymRefs.keySet.toList}%, %") + assert(forwardSymRefs.isEmpty, i"unresolved symbols: ${forwardSymRefs.keySet.toList}%, % when pickling ${ctx.source}") } 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)) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 56bb8498a..09f2c0d1f 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -9,38 +9,23 @@ import util.Positions._ import ast.{tpd, Trees, untpd} 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] @@ -85,10 +70,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 { @@ -468,6 +450,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 +487,12 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { sym.completer.withDecls(newScope) forkAt(templateStart).indexTemplateParams()(localContext(sym)) } + 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 } @@ -1010,44 +999,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/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index 86330f3ab..0a25bf801 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) @@ -1646,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 @@ -1670,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 = @@ -1856,7 +1856,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 b46ab6348..e16aa670f 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, write `$name` instead of $name to keep it 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 b490cd133..5324207db 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/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 205d2b6b9..4f3a8d272 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._ @@ -154,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 { @@ -277,7 +277,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (homogenizedView && pid.hasType) toTextLocal(pid.tpe) else toTextLocal(pid) - var txt: Text = tree match { + def toTextCore(tree: Tree): Text = tree match { case id: Trees.BackquotedIdent[_] if !homogenizedView => "`" ~ toText(id.name) ~ "`" case Ident(name) => @@ -351,6 +351,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } case SeqLiteral(elems, elemtpt) => "[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]" + 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 => "<derived typetree watching " ~ summarized(toText(tpt.watched)) ~ ">" case TypeTree(orig) => @@ -514,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 diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 67aa24243..83c428976 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/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index f35293d8d..deb772db5 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/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index b3d173a42..75113d823 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 = + @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) + @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 = { + @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) + + 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) } - 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/sbt/ExtractAPI.scala b/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 437e36bb9..a7b18b6d6 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 + } } diff --git a/src/dotty/tools/dotc/sbt/ExtractDependencies.scala b/src/dotty/tools/dotc/sbt/ExtractDependencies.scala index a36b47aa8..229e35360 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 => diff --git a/src/dotty/tools/dotc/transform/DropInlined.scala b/src/dotty/tools/dotc/transform/DropInlined.scala new file mode 100644 index 000000000..775663b5c --- /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/transform/ElimErasedValueType.scala b/src/dotty/tools/dotc/transform/ElimErasedValueType.scala index a3f8b56ff..24c8cdc8d 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) diff --git a/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/src/dotty/tools/dotc/transform/ExplicitOuter.scala index 6a52b128c..3f235dca7 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" @@ -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_<OUTER>` 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/ExplicitSelf.scala b/src/dotty/tools/dotc/transform/ExplicitSelf.scala index c6a218157..618a0f108 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 } diff --git a/src/dotty/tools/dotc/transform/FirstTransform.scala b/src/dotty/tools/dotc/transform/FirstTransform.scala index 6e1fed607..74dc9b9d6 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 490feb7d0..49c0eabec 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/Pickler.scala b/src/dotty/tools/dotc/transform/Pickler.scala index 4bcc90a41..90e62b65c 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) } } 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/transform/SuperAccessors.scala b/src/dotty/tools/dotc/transform/SuperAccessors.scala index 6af991f27..10be6db65 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/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index e7342aec9..4b3927ccf 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 @@ -423,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/transform/TreeTransform.scala b/src/dotty/tools/dotc/transform/TreeTransform.scala index 05961508a..52a3ad94e 100644 --- a/src/dotty/tools/dotc/transform/TreeTransform.scala +++ b/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -81,6 +81,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 +113,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 +275,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 +306,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 +347,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 +377,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 +413,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 +444,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 +523,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) @@ -741,6 +750,17 @@ object TreeTransforms { } @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) { val trans = info.transformers(cur) @@ -884,7 +904,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 +1111,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)(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/transform/patmat/Space.scala b/src/dotty/tools/dotc/transform/patmat/Space.scala index d942c6853..830d0f938 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) } diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 55d9fc990..2c9039db1 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/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index 415cd5d6a..b02b0ad21 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,14 @@ 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 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 */ def checkNoDoubleDefs(cls: Symbol)(implicit ctx: Context): Unit = { val seen = new mutable.HashMap[Name, List[Symbol]] { @@ -543,6 +552,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/EtaExpansion.scala b/src/dotty/tools/dotc/typer/EtaExpansion.scala index 7cbe70b47..397b6d95b 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/Inliner.scala b/src/dotty/tools/dotc/typer/Inliner.scala new file mode 100644 index 000000000..55008c0c5 --- /dev/null +++ b/src/dotty/tools/dotc/typer/Inliner.scala @@ -0,0 +1,521 @@ +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 Constants._ +import StdNames.nme +import Contexts.Context +import Names.{Name, TermName} +import NameOps._ +import SymDenotations.SymDenotation +import Annotations._ +import transform.ExplicitOuter +import Inferencing.fullyDefinedType +import config.Printers.inlining +import ErrorReporting.errorTree +import collection.mutable +import transform.TypeUtils._ + +object Inliner { + import tpd._ + + /** 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 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. + */ + 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) = + 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, + name = if (tree.isTerm) accessorName.toTermName else accessorName.toTypeName, + flags = if (tree.isTerm) Synthetic | Method else Synthetic, + info = accessorInfo, + coord = tree.pos).entered + + /** 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 qual = qualifier(refPart) + def refIsLocal = qual match { + case qual: This => qual.symbol == refPart.symbol.owner + case _ => false + } + val (accessorDef, accessorRef) = + 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 => + rhs(refPart, tps, argss)) + val accessorRef = ref(accessor).appliedToTypeTrees(targs).appliedToArgss(argss) + (accessorDef, accessorRef) + } else { + // 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 + // 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 + + val accessorDef = polyDefDef(accessor, tps => argss => + 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) + (accessorDef, accessorRef) + } + accessors += 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) + 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 + // 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 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 + } + } + } + + val tree1 = addAccessors.transform(tree) + flatTree(tree1 :: addAccessors.accessors.toList) + } + + /** 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 context to use for evaluating `treeExpr`. It needs + * to have the inlined method as owner. + */ + def registerInlineInfo( + 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) + }) + } + } + } + + /** `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 && 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 = + 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 (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 + * 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, 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 = + tree.withPos(inlined.call.pos) + } + tpd.seq(inlined.bindings, reposition.transform(inlined.expansion)) + } + + /** 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 _ => This(ctx.owner.enclosingClass.asClass) + } +} + +/** 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._ + + private val (methPart, targs, argss) = decomposeCall(call) + 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) + + /** 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] + + /** 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) => + paramBinding(name) = arg.tpe.stripTypeVar + } + computeParamBindings(tp.resultType, Nil, argss) + case tp: MethodType => + (tp.paramNames, tp.paramTypes, argss.head).zipped.foreach { (name, paramtp, arg) => + def isByName = paramtp.dealias.isInstanceOf[ExprType] + 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) (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)) + else ValDef(boundSym, arg) + bindingsBuf += binding + boundSym.termRef + } + } + computeParamBindings(tp.resultType, targs, argss.tail) + case _ => + assert(targs.isEmpty) + 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) && + !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 _ => + } + + /** Register type of leaf node */ + private def registerLeaf(tree: Tree): Unit = tree match { + case _: This | _: Ident | _: TypeTree => + tree.tpe.foreachPart(registerType, stopAtStatic = true) + case _ => + } + + /** The Inlined node representing the inlined call */ + def inlined(pt: Type) = { + // 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) + + // 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 = + if (!lastSelf.exists) + prefix + else + untpd.Select(ref(lastSelf), outerSelector(selfSym)).withType(selfSym.info) + bindingsBuf += ValDef(selfSym.asTerm, rhs) + 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) + case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) + case t: SingletonType => paramProxy.getOrElse(t, mapOver(t)) + case t => mapOver(t) + } + } + + // 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 => + thisProxy.get(tree.tpe) match { + 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).withPos(tree.pos) + case Some(t) if tree.isType => TypeTree(t).withPos(tree.pos) + case None => tree + } + 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 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)) + + /** 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)) + + tpd.Inlined(call, bindings, expansion1) + } + } + + /** 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) match { + case Some(ddef: ValDef) if ddef.symbol.is(Inline) => + ddef.rhs match { + case closure(_, meth, _) => Some(meth) + case _ => None + } + 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. 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 { + + 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) + 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) + } + } + + 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/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index cfd49fd87..2e714ab6d 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. @@ -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)) - setDocstring(sym, stat) - } - ctx - case _ => - ctx + 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)) + 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, origStat) + addEnumConstants(mdef, sym) + ctx + case stats: Thicket => + stats.toList.foreach(recur) + ctx + case _ => + ctx + } + recur(expanded(origStat)) } /** 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 _ => () @@ -561,11 +562,34 @@ 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) { + 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 _ => + } + + private def addInlineInfo(denot: SymDenotation, original: untpd.Tree) = original match { + case original: untpd.DefDef => + Inliner.registerInlineInfo( + denot, + implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs + )(localContext(denot.symbol)) case _ => } @@ -867,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/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index 80f0fd186..0e6697fb7 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/ReTyper.scala b/src/dotty/tools/dotc/typer/ReTyper.scala index 9750957bf..2413c0c22 100644 --- a/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/src/dotty/tools/dotc/typer/ReTyper.scala @@ -10,7 +10,8 @@ import typer.ProtoTypes._ import ast.{tpd, untpd} import ast.Trees._ import scala.util.control.NonFatal -import config.Printers +import util.Positions.Position +import config.Printers.typr /** A version of Typer that keeps all symbols defined and referenced in a * previously typed tree. @@ -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 @@ -87,9 +95,14 @@ 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}") + if (ctx.isAfterTyper) + println(i"exception while typing $tree of class ${tree.getClass} # ${tree.uniqueId}") throw ex } 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 = () + override def inlineExpansion(mdef: DefDef)(implicit ctx: Context): List[Tree] = mdef :: Nil } diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index ba8f35cd8..0c55d977e 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 562af75f6..3aff69bdb 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 _ => @@ -512,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) @@ -553,9 +554,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. @@ -607,7 +610,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) @@ -742,7 +745,8 @@ 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 = pt.hasAnnotation(defn.InlineParamAnnot) + desugar.makeClosure(inferredParams, fnBody, resultTpt, inlineable) } typed(desugared, pt) } @@ -885,12 +889,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) } @@ -940,6 +947,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)(inlineContext(tree.call)(exprCtx)) + 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") { if (tree.original.isEmpty) tree match { @@ -1104,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)) @@ -1123,6 +1137,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 } @@ -1154,6 +1170,11 @@ 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 + if (sym.hasAnnotation(defn.InlineAnnot)) + Inliner.registerInlineInfo(sym, _ => rhs1) + 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 @@ -1333,6 +1354,16 @@ 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, since these trees cannot contain bindings + case tree1 => + if (ctx.owner ne tree.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 @@ -1419,6 +1450,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) @@ -1431,7 +1463,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 @@ -1473,7 +1505,12 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case Some(xtree) => traverse(xtree :: rest) case none => - buf += typed(mdef) + typed(mdef) match { + case mdef1: DefDef if Inliner.hasBodyToInline(mdef1.symbol) => + buf ++= inlineExpansion(mdef1) + case mdef1 => + buf += mdef1 + } traverse(rest) } case Thicket(stats) :: rest => @@ -1487,6 +1524,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? @@ -1550,7 +1595,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) @@ -1772,8 +1817,15 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } tree } - else if (tree.tpe <:< pt) - if (ctx.typeComparer.GADTused && pt.isValueType) + 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 && + !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. // 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 @@ -1781,6 +1833,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") @@ -1806,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 tpd.Block(tree :: Nil, Literal(Constant(()))) + // 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), _) diff --git a/src/dotty/tools/dotc/util/Attachment.scala b/src/dotty/tools/dotc/util/Attachment.scala index 8088b4cd0..20facfd97 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 000000000..608fc88e6 --- /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 diff --git a/src/dotty/tools/dotc/util/SourceFile.scala b/src/dotty/tools/dotc/util/SourceFile.scala index 344bc253a..8bd0ecfd6 100644 --- a/src/dotty/tools/dotc/util/SourceFile.scala +++ b/src/dotty/tools/dotc/util/SourceFile.scala @@ -140,5 +140,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/src/dotty/tools/dotc/util/SourcePosition.scala b/src/dotty/tools/dotc/util/SourcePosition.scala index 0b2b2aa0b..68a9b6403 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 } diff --git a/src/dotty/tools/dotc/util/Stats.scala b/src/dotty/tools/dotc/util/Stats.scala index fdd3602c9..b7e0996f5 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) = { + @inline + def record(fn: String, n: Int = 1) = + if (enabled) doRecord(fn, n) + + private def doRecord(fn: String, n: Int) = if (monitored) { val name = if (fn.startsWith("member-")) "member" else fn hits(name) += n } - } - - var monitored = false + @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) |