diff options
author | odersky <odersky@gmail.com> | 2016-03-18 11:16:50 +0100 |
---|---|---|
committer | odersky <odersky@gmail.com> | 2016-03-18 11:16:50 +0100 |
commit | cdbc1631d1eda5a1a3eaa708b585a6a052852646 (patch) | |
tree | 060bc8fbe8d44ae9ba202f94fd98c9e7904180eb /src | |
parent | 16f0bea7e4169ab89eddf2e7bae1de08ded3c84b (diff) | |
parent | 6c18e37886e90d217579112ccf867c22658273be (diff) | |
download | dotty-cdbc1631d1eda5a1a3eaa708b585a6a052852646.tar.gz dotty-cdbc1631d1eda5a1a3eaa708b585a6a052852646.tar.bz2 dotty-cdbc1631d1eda5a1a3eaa708b585a6a052852646.zip |
Merge pull request #1154 from dotty-staging/add-rewrite
First steps towards rewriting from Scala2 in dotty
Diffstat (limited to 'src')
-rw-r--r-- | src/dotty/tools/dotc/Run.scala | 2 | ||||
-rw-r--r-- | src/dotty/tools/dotc/ast/Desugar.scala | 38 | ||||
-rw-r--r-- | src/dotty/tools/dotc/ast/NavigateAST.scala | 83 | ||||
-rw-r--r-- | src/dotty/tools/dotc/ast/Positioned.scala | 78 | ||||
-rw-r--r-- | src/dotty/tools/dotc/ast/Trees.scala | 10 | ||||
-rw-r--r-- | src/dotty/tools/dotc/config/ScalaSettings.scala | 5 | ||||
-rw-r--r-- | src/dotty/tools/dotc/config/Settings.scala | 9 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/TypeOps.scala | 2 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Types.scala | 2 | ||||
-rw-r--r-- | src/dotty/tools/dotc/parsing/JavaParsers.scala | 2 | ||||
-rw-r--r-- | src/dotty/tools/dotc/parsing/Parsers.scala | 16 | ||||
-rw-r--r-- | src/dotty/tools/dotc/rewrite/Rewrites.scala | 92 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/ExplicitOuter.scala | 7 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/Getters.scala | 4 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/LazyVals.scala | 19 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Typer.scala | 26 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/VarianceChecker.scala | 15 |
17 files changed, 327 insertions, 83 deletions
diff --git a/src/dotty/tools/dotc/Run.scala b/src/dotty/tools/dotc/Run.scala index 9972e3e64..ee808323a 100644 --- a/src/dotty/tools/dotc/Run.scala +++ b/src/dotty/tools/dotc/Run.scala @@ -8,6 +8,7 @@ import io.PlainFile import util.{SourceFile, NoSource, Stats, SimpleMap} import reporting.Reporter import transform.TreeChecker +import rewrite.Rewrites import java.io.{BufferedWriter, OutputStreamWriter} import scala.reflect.io.VirtualFile import scala.util.control.NonFatal @@ -64,6 +65,7 @@ class Run(comp: Compiler)(implicit ctx: Context) { foreachUnit(printTree) ctx.informTime(s"$phase ", start) } + if (!ctx.reporter.hasErrors) Rewrites.writeBack() } private def printTree(ctx: Context) = { diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index 8ba155097..7a305a8b8 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -228,7 +228,7 @@ object desugar { val tparam = cpy.TypeDef(tdef)(name = tdef.name.expandedName(ctx.owner)) .withMods(tdef.mods &~ PrivateLocal | ExpandedName) val alias = cpy.TypeDef(tdef)(rhs = refOfDef(tparam), tparams = Nil) - .withFlags(PrivateLocalParamAccessor | Synthetic | tdef.mods.flags & VarianceFlags) + .withMods(tdef.mods & VarianceFlags | PrivateLocalParamAccessor | Synthetic) Thicket(tparam, alias) } else tdef @@ -237,15 +237,15 @@ object desugar { @sharable private val synthetic = Modifiers(Synthetic) private def toDefParam(tparam: TypeDef): TypeDef = - tparam.withFlags(Param) + tparam.withMods(tparam.rawMods & EmptyFlags | Param) private def toDefParam(vparam: ValDef): ValDef = - vparam.withFlags(Param | vparam.rawMods.flags & Implicit) + vparam.withMods(vparam.rawMods & Implicit | Param) /** The expansion of a class definition. See inline comments for what is involved */ def classDef(cdef: TypeDef)(implicit ctx: Context): Tree = { val TypeDef(name, impl @ Template(constr0, parents, self, _)) = cdef val mods = cdef.mods - val accessFlags = (mods.flags & AccessFlags).toCommonFlags + val companionMods = mods.withFlags((mods.flags & AccessFlags).toCommonFlags) val (constr1, defaultGetters) = defDef(constr0, isPrimaryConstructor = true) match { case meth: DefDef => (meth, Nil) @@ -364,7 +364,7 @@ object desugar { moduleDef( ModuleDef( name.toTermName, Template(emptyConstructor, parentTpt :: Nil, EmptyValDef, defs)) - .withFlags(Synthetic | accessFlags)) + .withMods(companionMods | Synthetic)) .withPos(cdef.pos).toList // The companion object definitions, if a companion is needed, Nil otherwise. @@ -421,10 +421,9 @@ object desugar { // implicit wrapper is typechecked in same scope as constructor, so // we can reuse the constructor parameters; no derived params are needed. DefDef(name.toTermName, constrTparams, constrVparamss, classTypeRef, creatorExpr) - .withFlags(Synthetic | Implicit | accessFlags) + .withMods(companionMods | Synthetic | Implicit) .withPos(cdef.pos) :: Nil - val self1 = { val selfType = if (self.tpt.isEmpty) classTypeRef else self.tpt if (self.isEmpty) self @@ -498,18 +497,18 @@ object desugar { /** If `pat` is a variable pattern, * - * val/var p = e + * val/var/lazy val p = e * * Otherwise, in case there is exactly one variable x_1 in pattern - * val/var p = e ==> val/var x_1 = (e: @unchecked) match (case p => (x_1)) + * val/var/lazy val p = e ==> val/var/lazy val x_1 = (e: @unchecked) match (case p => (x_1)) * * in case there are zero or more than one variables in pattern - * val/var p = e ==> private synthetic val t$ = (e: @unchecked) match (case p => (x_1, ..., x_N)) - * val/var x_1 = t$._1 + * val/var/lazy p = e ==> private synthetic [lazy] val t$ = (e: @unchecked) match (case p => (x_1, ..., x_N)) + * val/var/def x_1 = t$._1 * ... - * val/var x_N = t$._N + * val/var/def x_N = t$._N * If the original pattern variable carries a type annotation, so does the corresponding - * ValDef. + * ValDef or DefDef. */ def makePatDef(mods: Modifiers, pat: Tree, rhs: Tree)(implicit ctx: Context): Tree = pat match { case VarPattern(named, tpt) => @@ -533,12 +532,16 @@ object desugar { derivedValDef(named, tpt, matchExpr, mods) case _ => val tmpName = ctx.freshName().toTermName - val patFlags = mods.flags & AccessFlags | Synthetic | (mods.flags & Lazy) - val firstDef = ValDef(tmpName, TypeTree(), matchExpr).withFlags(patFlags) + val patMods = mods & (AccessFlags | Lazy) | Synthetic + val firstDef = + ValDef(tmpName, TypeTree(), matchExpr) + .withPos(pat.pos.union(rhs.pos)).withMods(patMods) def selector(n: Int) = Select(Ident(tmpName), nme.selectorName(n)) val restDefs = for (((named, tpt), n) <- vars.zipWithIndex) - yield derivedValDef(named, tpt, selector(n), mods) + yield + if (mods is Lazy) derivedDefDef(named, tpt, selector(n), mods &~ Lazy) + else derivedValDef(named, tpt, selector(n), mods) flatTree(firstDef :: restDefs) } } @@ -635,6 +638,9 @@ object desugar { private def derivedValDef(named: NameTree, tpt: Tree, rhs: Tree, mods: Modifiers) = ValDef(named.name.asTermName, tpt, rhs).withMods(mods).withPos(named.pos) + private def derivedDefDef(named: NameTree, tpt: Tree, rhs: Tree, mods: Modifiers) = + DefDef(named.name.asTermName, Nil, Nil, tpt, rhs).withMods(mods).withPos(named.pos) + /** Main desugaring method */ def apply(tree: Tree)(implicit ctx: Context): Tree = { diff --git a/src/dotty/tools/dotc/ast/NavigateAST.scala b/src/dotty/tools/dotc/ast/NavigateAST.scala new file mode 100644 index 000000000..782866bad --- /dev/null +++ b/src/dotty/tools/dotc/ast/NavigateAST.scala @@ -0,0 +1,83 @@ +package dotty.tools.dotc +package ast + +import core.Contexts.Context +import core.Decorators._ +import util.Positions._ +import Trees.{MemberDef, DefTree} + +/** Utility functions to go from typed to untyped ASTs */ +object NavigateAST { + + /** The untyped tree corresponding to typed tree `tree` in the compilation + * unit specified by `ctx` + */ + def toUntyped(tree: tpd.Tree)(implicit ctx: Context): untpd.Tree = + untypedPath(tree, exactMatch = true) match { + case (utree: untpd.Tree) :: _ => + utree + case _ => + val loosePath = untypedPath(tree, exactMatch = false) + throw new + Error(i"""no untyped tree for $tree, pos = ${tree.pos}, envelope = ${tree.envelope} + |best matching path =\n$loosePath%\n====\n% + |path positions = ${loosePath.map(_.pos)} + |path envelopes = ${loosePath.map(_.envelope)}""".stripMargin) + } + + /** The reverse path of untyped trees starting with a tree that closest matches + * `tree` and ending in the untyped tree at the root of the compilation unit + * specified by `ctx`. + * @param exactMatch If `true`, the path must start with a node that exactly + * matches `tree`, or `Nil` is returned. + * If `false` the path might start with a node enclosing + * the logical position of `tree`. + * Note: A complication concerns member definitions. ValDefs and DefDefs + * have after desugaring a position that spans just the name of the symbol being + * defined and nothing else. So we look instead for an untyped tree approximating the + * envelope of the definition, and declare success if we find another DefTree. + */ + def untypedPath(tree: tpd.Tree, exactMatch: Boolean = false)(implicit ctx: Context): List[Positioned] = + tree match { + case tree: MemberDef[_] => + untypedPath(tree.envelope) match { + case path @ (last: DefTree[_]) :: _ => path + case path if !exactMatch => path + case _ => Nil + } + case _ => + untypedPath(tree.pos) match { + case (path @ last :: _) if last.pos == tree.pos || !exactMatch => path + case _ => Nil + } + } + + /** The reverse part of the untyped root of the compilation unit of `ctx` to + * position `pos`. + */ + def untypedPath(pos: Position)(implicit ctx: Context): List[Positioned] = + pathTo(pos, ctx.compilationUnit.untpdTree) + + + /** The reverse path from node `from` to the node that closest encloses position `pos`, + * or `Nil` if no such path exists. If a non-empty path is returned it starts with + * the node closest enclosing `pos` and ends with `from`. + */ + def pathTo(pos: Position, from: Positioned)(implicit ctx: Context): List[Positioned] = { + def childPath(it: Iterator[Any], path: List[Positioned]): List[Positioned] = { + while (it.hasNext) { + val path1 = it.next match { + case p: Positioned => singlePath(p, path) + case xs: List[_] => childPath(xs.iterator, path) + case _ => path + } + if (path1 ne path) return path1 + } + path + } + def singlePath(p: Positioned, path: List[Positioned]): List[Positioned] = + if (p.envelope contains pos) childPath(p.productIterator, p :: path) + else path + singlePath(from, Nil) + } +}
\ No newline at end of file diff --git a/src/dotty/tools/dotc/ast/Positioned.scala b/src/dotty/tools/dotc/ast/Positioned.scala index e0bd6c75a..e7f5de591 100644 --- a/src/dotty/tools/dotc/ast/Positioned.scala +++ b/src/dotty/tools/dotc/ast/Positioned.scala @@ -54,21 +54,55 @@ abstract class Positioned extends DotClass with Product { */ private[dotc] def setPosUnchecked(pos: Position) = curPos = pos - /** If any children of this node do not have positions, set them to the given position, + /** If any children of this node do not have positions, + * fit their positions between the positions of the known subtrees * and transitively visit their children. + * The method is likely time-critical because it is invoked on any node + * we create, so we want to avoid object allocations in the common case. + * The method is naturally expressed as two mutually (tail-)recursive + * functions, one which computes the next element to consider or terminates if there + * is none and the other which propagates the position information to that element. + * But since mutual tail recursion is not supported in Scala, we express it instead + * as a while loop with a termination by return in the middle. */ private def setChildPositions(pos: Position): Unit = { - def deepSetPos(x: Any): Unit = x match { - case p: Positioned => - if (!p.pos.exists) p.setPos(pos) - case xs: List[_] => - xs foreach deepSetPos - case _ => + var n = productArity // subnodes are analyzed right to left + var elems: List[Any] = Nil // children in lists still to be considered, from right to left + var end = pos.end // the last defined offset, fill in positions up to this offset + var outstanding: List[Positioned] = Nil // nodes that need their positions filled once a start position + // is known, from left to right. + def fillIn(ps: List[Positioned], start: Int, end: Int): Unit = ps match { + case p :: ps1 => + p.setPos(Position(start, end)) + fillIn(ps1, end, end) + case nil => } - var n = productArity - while (n > 0) { - n -= 1 - deepSetPos(productElement(n)) + while (true) { + var nextChild: Any = null // the next child to be considered + if (elems.nonEmpty) { + nextChild = elems.head + elems = elems.tail + } + else if (n > 0) { + n = n - 1 + nextChild = productElement(n) + } + else { + fillIn(outstanding, pos.start, end) + return + } + nextChild match { + case p: Positioned => + if (p.pos.exists) { + fillIn(outstanding, p.pos.end, end) + outstanding = Nil + end = p.pos.start + } + else outstanding = p :: outstanding + case xs: List[_] => + elems = elems ::: xs.reverse + case _ => + } } } @@ -114,26 +148,4 @@ abstract class Positioned extends DotClass with Product { found } } - - /** The path from this node to `that` node, represented - * as a list starting with `this`, ending with`that` where - * every node is a child of its predecessor. - * Nil if no such path exists. - */ - def pathTo(that: Positioned): List[Positioned] = { - def childPath(it: Iterator[Any]): List[Positioned] = - if (it.hasNext) { - val cpath = it.next match { - case x: Positioned => x.pathTo(that) - case xs: List[_] => childPath(xs.iterator) - case _ => Nil - } - if (cpath.nonEmpty) cpath else childPath(it) - } else Nil - if (this eq that) this :: Nil - else if (this.envelope contains that.pos) { - val cpath = childPath(productIterator) - if (cpath.nonEmpty) this :: cpath else Nil - } else Nil - } } diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index 54ace3be4..fbb93d141 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -50,13 +50,17 @@ object Trees { def toTypeFlags: Modifiers[T] = withFlags(flags.toTypeFlags) def toTermFlags: Modifiers[T] = withFlags(flags.toTermFlags) - private def withFlags(flags: FlagSet) = + def withFlags(flags: FlagSet) = if (this.flags == flags) this else copy(flags = flags) + def withAddedAnnotation[U >: Untyped <: T](annot: Tree[U]): Modifiers[U] = + if (annotations.exists(_ eq annot)) this + else withAnnotations(annotations :+ annot) + def withAnnotations[U >: Untyped <: T](annots: List[Tree[U]]): Modifiers[U] = - if (annots.isEmpty) this - else copy(annotations = annotations ++ annots) + if (annots eq annotations) this + else copy(annotations = annots) def withPrivateWithin(pw: TypeName) = if (pw.isEmpty) this diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index b1d53c90d..07a23fdb6 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -1,6 +1,8 @@ -package dotty.tools.dotc.config +package dotty.tools.dotc +package config import PathResolver.Defaults +import rewrite.Rewrites class ScalaSettings extends Settings.SettingGroup { @@ -48,6 +50,7 @@ class ScalaSettings extends Settings.SettingGroup { val d = StringSetting("-d", "directory|jar", "destination for generated classfiles.", ".") val nospecialization = BooleanSetting("-no-specialization", "Ignore @specialize annotations.") val language = MultiStringSetting("-language", "feature", "Enable one or more language features.") + val rewrite = OptionSetting[Rewrites]("-rewrite", "When used in conjunction with -language:Scala2 rewrites sources to migrate to new syntax") /** -X "Advanced" settings */ diff --git a/src/dotty/tools/dotc/config/Settings.scala b/src/dotty/tools/dotc/config/Settings.scala index 73bb056aa..eddeb83ab 100644 --- a/src/dotty/tools/dotc/config/Settings.scala +++ b/src/dotty/tools/dotc/config/Settings.scala @@ -19,6 +19,7 @@ object Settings { val StringTag = ClassTag(classOf[String]) val ListTag = ClassTag(classOf[List[_]]) val VersionTag = ClassTag(classOf[ScalaVersion]) + val OptionTag = ClassTag(classOf[Option[_]]) class SettingsState(initialValues: Seq[Any]) { private var values = ArrayBuffer(initialValues: _*) @@ -55,7 +56,8 @@ object Settings { choices: Seq[T] = Nil, prefix: String = "", aliases: List[String] = Nil, - depends: List[(Setting[_], Any)] = Nil)(private[Settings] val idx: Int) { + depends: List[(Setting[_], Any)] = Nil, + propertyClass: Option[Class[_]] = None)(private[Settings] val idx: Int) { def withAbbreviation(abbrv: String): Setting[T] = copy(aliases = aliases :+ abbrv)(idx) @@ -112,6 +114,8 @@ object Settings { def doSet(argRest: String) = ((implicitly[ClassTag[T]], args): @unchecked) match { case (BooleanTag, _) => update(true, args) + case (OptionTag, _) => + update(Some(propertyClass.get.newInstance), args) case (ListTag, _) => if (argRest.isEmpty) missingArg else update((argRest split ",").toList, args) @@ -255,5 +259,8 @@ object Settings { def VersionSetting(name: String, descr: String, default: ScalaVersion = NoScalaVersion): Setting[ScalaVersion] = publish(Setting(name, descr, default)) + + def OptionSetting[T: ClassTag](name: String, descr: String): Setting[Option[T]] = + publish(Setting(name, descr, None, propertyClass = Some(implicitly[ClassTag[T]].runtimeClass))) } } diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 734d31858..371be1586 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -546,7 +546,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. hasImport(c) })) def hasOption = ctx.base.settings.language.value exists (s => s == featureName || s == "_") - hasImport || hasOption + hasImport(ctx.withPhase(ctx.typerPhase)) || hasOption } /** Is auto-tupling enabled? */ diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 17c2ec4ca..3801f1914 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2424,8 +2424,6 @@ object Types { x => paramBounds mapConserve (_.subst(this, x).bounds), x => resType.subst(this, x)) - // need to override hashCode and equals to be object identity - // because paramNames by itself is not discriminatory enough override def equals(other: Any) = other match { case other: PolyType => other.paramNames == this.paramNames && other.paramBounds == this.paramBounds && other.resType == this.resType diff --git a/src/dotty/tools/dotc/parsing/JavaParsers.scala b/src/dotty/tools/dotc/parsing/JavaParsers.scala index be7822cdc..b4d01a0da 100644 --- a/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -511,7 +511,7 @@ object JavaParsers { atPos(offset) { New(Select(scalaDot(nme.runtime), tpnme.AnnotationDefaultATTR), Nil) } - mods1 = mods1 withAnnotations annot :: Nil + mods1 = mods1 withAddedAnnotation annot skipTo(SEMI) accept(SEMI) unimplementedExpr diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index bb8fbe08b..6ec75a8b2 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -21,6 +21,7 @@ import Constants._ import ScriptParsers._ import annotation.switch import util.DotClass +import rewrite.Rewrites.patch object Parsers { @@ -1761,13 +1762,20 @@ object Parsers { * DefSig ::= id [DefTypeParamClause] ParamClauses */ def defDefOrDcl(mods: Modifiers): Tree = atPos(tokenRange) { - def scala2ProcedureSyntax = - testScala2Mode("Procedure syntax no longer supported; `: Unit =' should be inserted here") + def scala2ProcedureSyntax(resultTypeStr: String) = { + 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") && { + patch(source, Position(in.lastOffset), toInsert) + true + } + } if (in.token == THIS) { in.nextToken() val vparamss = paramClauses(nme.CONSTRUCTOR) val rhs = { - if (!(in.token == LBRACE && scala2ProcedureSyntax)) accept(EQUALS) + if (!(in.token == LBRACE && scala2ProcedureSyntax(""))) accept(EQUALS) atPos(in.offset) { constrExpr() } } makeConstructor(Nil, vparamss, rhs).withMods(mods) @@ -1784,7 +1792,7 @@ object Parsers { } else if (!tpt.isEmpty) EmptyTree - else if (scala2ProcedureSyntax) { + else if (scala2ProcedureSyntax(": Unit")) { tpt = scalaUnit if (in.token == LBRACE) expr() else EmptyTree diff --git a/src/dotty/tools/dotc/rewrite/Rewrites.scala b/src/dotty/tools/dotc/rewrite/Rewrites.scala new file mode 100644 index 000000000..7ab0e5d59 --- /dev/null +++ b/src/dotty/tools/dotc/rewrite/Rewrites.scala @@ -0,0 +1,92 @@ +package dotty.tools.dotc +package rewrite + +import util.{SourceFile, Positions} +import Positions.Position +import core.Contexts.{Context, FreshContext} +import collection.mutable + +/** Handles rewriting of Scala2 files to Dotty */ +object Rewrites { + private class PatchedFiles extends mutable.HashMap[SourceFile, Patches] + + private case class Patch(pos: Position, replacement: String) { + def delta = replacement.length - (pos.end - pos.start) + } + + private class Patches(source: SourceFile) { + private val pbuf = new mutable.ListBuffer[Patch]() + + def addPatch(pos: Position, replacement: String): Unit = + pbuf += Patch(pos, replacement) + + def apply(cs: Array[Char]): Array[Char] = { + val delta = pbuf.map(_.delta).sum + val patches = pbuf.toList.sortBy(_.pos.start) + if (patches.nonEmpty) + patches reduceLeft {(p1, p2) => + assert(p1.pos.end <= p2.pos.start, s"overlapping patches: $p1 and $p2") + p2 + } + val ds = new Array[Char](cs.length + delta) + def loop(ps: List[Patch], inIdx: Int, outIdx: Int): Unit = { + def copy(upTo: Int): Int = { + val untouched = upTo - inIdx + Array.copy(cs, inIdx, ds, outIdx, untouched) + outIdx + untouched + } + ps match { + case patch @ Patch(pos, replacement) :: ps1 => + val outNew = copy(pos.start) + replacement.copyToArray(ds, outNew) + loop(ps1, pos.end, outNew + replacement.length) + case Nil => + val outNew = copy(cs.length) + assert(outNew == ds.length, s"$outNew != ${ds.length}") + } + } + loop(patches, 0, 0) + ds + } + + def writeBack(): Unit = { + val out = source.file.output + val chars = apply(source.underlying.content) + val bytes = new String(chars).getBytes + out.write(bytes) + out.close() + } + } + + /** If -rewrite is set, record a patch that replaces the range + * given by `pos` in `source` by `replacement` + */ + def patch(source: SourceFile, pos: Position, replacement: String)(implicit ctx: Context): Unit = + for (rewrites <- ctx.settings.rewrite.value) + rewrites.patched + .getOrElseUpdate(source, new Patches(source)) + .addPatch(pos, replacement) + + /** Patch position in `ctx.compilationUnit.source`. */ + def patch(pos: Position, replacement: String)(implicit ctx: Context): Unit = + patch(ctx.compilationUnit.source, pos, replacement) + + /** If -rewrite is set, apply all patches and overwrite patched source files. + */ + def writeBack()(implicit ctx: Context) = + for (rewrites <- ctx.settings.rewrite.value; source <- rewrites.patched.keys) { + ctx.println(s"[patched file ${source.file.path}]") + rewrites.patched(source).writeBack() + } +} + +/** A completely encapsulated class representing rewrite state, used + * as an optional setting. + */ +class Rewrites { + import Rewrites._ + private val patched = new PatchedFiles +} + + + diff --git a/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/src/dotty/tools/dotc/transform/ExplicitOuter.scala index 9170cd277..7ec0739c1 100644 --- a/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -326,7 +326,12 @@ object ExplicitOuter { val outerAccessorCtx = ctx.withPhaseNoLater(ctx.lambdaLiftPhase) // lambdalift mangles local class names, which means we cannot reliably find outer acessors anymore ctx.log(i"outer to $toCls of $tree: ${tree.tpe}, looking for ${outerAccName(treeCls.asClass)(outerAccessorCtx)} in $treeCls") if (treeCls == toCls) tree - else loop(tree.select(outerAccessor(treeCls.asClass)(outerAccessorCtx)).ensureApplied) + else { + val acc = outerAccessor(treeCls.asClass)(outerAccessorCtx) + assert(acc.exists, + i"failure to construct path from ${ctx.owner.ownersIterator.toList}%/% to `this` of ${toCls.showLocated};\n${treeCls.showLocated} does not have an outer accessor") + loop(tree.select(acc).ensureApplied) + } } ctx.log(i"computing outerpath to $toCls from ${ctx.outersIterator.map(_.owner).toList}") loop(start) diff --git a/src/dotty/tools/dotc/transform/Getters.scala b/src/dotty/tools/dotc/transform/Getters.scala index 882e42d2f..75235d0f5 100644 --- a/src/dotty/tools/dotc/transform/Getters.scala +++ b/src/dotty/tools/dotc/transform/Getters.scala @@ -68,8 +68,8 @@ class Getters extends MiniPhaseTransform with SymTransformer { thisTransform => private val NoGetterNeeded = Method | Param | JavaDefined | JavaStatic override def transformValDef(tree: ValDef)(implicit ctx: Context, info: TransformerInfo): Tree = - if (tree.symbol is Method) DefDef(tree.symbol.asTerm, tree.rhs) else tree + if (tree.symbol is Method) DefDef(tree.symbol.asTerm, tree.rhs).withPos(tree.pos) else tree override def transformAssign(tree: Assign)(implicit ctx: Context, info: TransformerInfo): Tree = - if (tree.lhs.symbol is Method) tree.lhs.becomes(tree.rhs) else tree + if (tree.lhs.symbol is Method) tree.lhs.becomes(tree.rhs).withPos(tree.pos) else tree } diff --git a/src/dotty/tools/dotc/transform/LazyVals.scala b/src/dotty/tools/dotc/transform/LazyVals.scala index 25b9afa68..fc02e68cc 100644 --- a/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/src/dotty/tools/dotc/transform/LazyVals.scala @@ -12,6 +12,8 @@ import Symbols._ import Decorators._ import NameOps._ import StdNames.nme +import rewrite.Rewrites.patch +import util.Positions.Position import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransformer, MiniPhaseTransform} import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.ast.{untpd, tpd} @@ -65,19 +67,20 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer with Nee val sym = tree.symbol if (!(sym is Flags.Lazy) || sym.owner.is(Flags.Trait) || (sym.isStatic && sym.is(Flags.Module))) tree else { - val isField = sym.owner.isClass - if (isField) { if (sym.isVolatile || - (sym.is(Flags.Module) && !sym.is(Flags.Synthetic))) - // module class is user-defined. - // Should be threadsafe, to mimic safety guaranteed by global object + (sym.is(Flags.Module)/* || ctx.scala2Mode*/) && + // TODO assume @volatile once LazyVals uses static helper constructs instead of + // ones in the companion object. + !sym.is(Flags.Synthetic)) + // module class is user-defined. + // Should be threadsafe, to mimic safety guaranteed by global object transformMemberDefVolatile(tree) - else if (sym.is(Flags.Module)) { // synthetic module + else if (sym.is(Flags.Module)) // synthetic module transformSyntheticModule(tree) - } - else transformMemberDefNonVolatile(tree) + else + transformMemberDefNonVolatile(tree) } else transformLocalDef(tree) } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index fdb92a40b..2069e790b 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -33,6 +33,9 @@ import annotation.tailrec import Implicits._ import util.Stats.{track, record} import config.Printers._ +import rewrite.Rewrites.patch +import NavigateAST._ +import transform.SymUtils._ import language.implicitConversions object Typer { @@ -984,7 +987,18 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case rhs @ Ident(nme.WILDCARD) => rhs withType tpt1.tpe case rhs => typedExpr(rhs, tpt1.tpe) } - assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) + val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) + patchIfLazy(vdef1) + vdef1 + } + + /** Add a @volitile to lazy vals when rewriting from Scala2 */ + private def patchIfLazy(vdef: ValDef)(implicit ctx: Context): Unit = { + val sym = vdef.symbol + if (sym.is(Lazy, butNot = Deferred | Module | Synthetic) && !sym.isVolatile && + ctx.scala2Mode && ctx.settings.rewrite.value.isDefined && + !ctx.isAfterTyper) + patch(Position(toUntyped(vdef).envelope.start), "@volatile ") } def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = track("typedDefDef") { @@ -1135,13 +1149,17 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } - def typedAsFunction(tree: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = { + 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 - var res = typed(tree, pt1) + var res = typed(qual, pt1) if (pt1.eq(AnyFunctionProto) && !defn.isFunctionClass(res.tpe.classSymbol)) { def msg = i"not a function: ${res.tpe}; cannot be followed by `_'" if (ctx.scala2Mode) { + // Under -rewrite, patch `x _` to `(() => x)` ctx.migrationWarning(msg, tree.pos) + patch(Position(tree.pos.start), "(() => ") + patch(Position(qual.pos.end, tree.pos.end), ")") res = typed(untpd.Function(Nil, untpd.TypedSplice(res))) } else ctx.error(msg, tree.pos) @@ -1232,7 +1250,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case tree: untpd.Annotated => typedAnnotated(tree, pt) case tree: untpd.TypedSplice => tree.tree case tree: untpd.UnApply => typedUnApply(tree, pt) - case untpd.PostfixOp(tree, nme.WILDCARD) => typedAsFunction(tree, pt) + case tree @ untpd.PostfixOp(qual, nme.WILDCARD) => typedAsFunction(tree, pt) case untpd.EmptyTree => tpd.EmptyTree case _ => typedUnadapted(desugar(tree), pt) } diff --git a/src/dotty/tools/dotc/typer/VarianceChecker.scala b/src/dotty/tools/dotc/typer/VarianceChecker.scala index b257ee192..274218ee3 100644 --- a/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -6,6 +6,8 @@ import core._ import Types._, Contexts._, Flags._, Symbols._, Annotations._, Trees._, NameOps._ import Decorators._ import Variances._ +import util.Positions._ +import rewrite.Rewrites.patch import config.Printers.variances /** Provides `check` method to check that all top-level definitions @@ -108,11 +110,13 @@ class VarianceChecker()(implicit ctx: Context) { } private object Traverser extends TreeTraverser { - def checkVariance(sym: Symbol) = Validator.validateDefinition(sym) match { + def checkVariance(sym: Symbol, pos: Position) = Validator.validateDefinition(sym) match { case Some(VarianceError(tvar, required)) => def msg = i"${varianceString(tvar.flags)} $tvar occurs in ${varianceString(required)} position in type ${sym.info} of $sym" - if (ctx.scala2Mode && sym.owner.isConstructor) + if (ctx.scala2Mode && sym.owner.isConstructor) { ctx.migrationWarning(s"According to new variance rules, this is no longer accepted; need to annotate with @uncheckedVariance:\n$msg", sym.pos) + patch(Position(pos.end), " @scala.annotation.unchecked.uncheckedVariance") // TODO use an import or shorten if possible + } else ctx.error(msg, sym.pos) case None => } @@ -128,12 +132,11 @@ class VarianceChecker()(implicit ctx: Context) { case defn: MemberDef if skip => ctx.debuglog(s"Skipping variance check of ${sym.showDcl}") case tree: TypeDef => - checkVariance(sym) - traverseChildren(tree) + checkVariance(sym, tree.envelope) case tree: ValDef => - checkVariance(sym) + checkVariance(sym, tree.envelope) case DefDef(_, tparams, vparamss, _, _) => - checkVariance(sym) + checkVariance(sym, tree.envelope) tparams foreach traverse vparamss foreach (_ foreach traverse) case Template(_, _, _, body) => |