aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorodersky <odersky@gmail.com>2016-03-18 11:16:50 +0100
committerodersky <odersky@gmail.com>2016-03-18 11:16:50 +0100
commitcdbc1631d1eda5a1a3eaa708b585a6a052852646 (patch)
tree060bc8fbe8d44ae9ba202f94fd98c9e7904180eb
parent16f0bea7e4169ab89eddf2e7bae1de08ded3c84b (diff)
parent6c18e37886e90d217579112ccf867c22658273be (diff)
downloaddotty-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
-rw-r--r--src/dotty/tools/dotc/Run.scala2
-rw-r--r--src/dotty/tools/dotc/ast/Desugar.scala38
-rw-r--r--src/dotty/tools/dotc/ast/NavigateAST.scala83
-rw-r--r--src/dotty/tools/dotc/ast/Positioned.scala78
-rw-r--r--src/dotty/tools/dotc/ast/Trees.scala10
-rw-r--r--src/dotty/tools/dotc/config/ScalaSettings.scala5
-rw-r--r--src/dotty/tools/dotc/config/Settings.scala9
-rw-r--r--src/dotty/tools/dotc/core/TypeOps.scala2
-rw-r--r--src/dotty/tools/dotc/core/Types.scala2
-rw-r--r--src/dotty/tools/dotc/parsing/JavaParsers.scala2
-rw-r--r--src/dotty/tools/dotc/parsing/Parsers.scala16
-rw-r--r--src/dotty/tools/dotc/rewrite/Rewrites.scala92
-rw-r--r--src/dotty/tools/dotc/transform/ExplicitOuter.scala7
-rw-r--r--src/dotty/tools/dotc/transform/Getters.scala4
-rw-r--r--src/dotty/tools/dotc/transform/LazyVals.scala19
-rw-r--r--src/dotty/tools/dotc/typer/Typer.scala26
-rw-r--r--src/dotty/tools/dotc/typer/VarianceChecker.scala15
-rw-r--r--test/dotc/tests.scala2
-rw-r--r--test/test/CompilerTest.scala18
-rw-r--r--tests/neg/classOf.scala11
-rw-r--r--tests/neg/tate.scala11
-rw-r--r--tests/pending/neg/tate.scala11
-rw-r--r--tests/pending/pos/lazyvals.scala18
-rw-r--r--tests/pos-scala2/rewrites.scala36
24 files changed, 433 insertions, 84 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) =>
diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala
index 2d4ed289c..457116feb 100644
--- a/test/dotc/tests.scala
+++ b/test/dotc/tests.scala
@@ -104,6 +104,8 @@ class tests extends CompilerTest {
@Test def pos_scala2_all = compileFiles(posScala2Dir, scala2mode)
+ @Test def rewrites = compileFile(posScala2Dir, "rewrites", "-rewrite" :: scala2mode)
+
@Test def pos_859 = compileFile(posSpecialDir, "i859", scala2mode)(allowDeepSubtypes)
@Test def new_all = compileFiles(newDir, twice)
diff --git a/test/test/CompilerTest.scala b/test/test/CompilerTest.scala
index 678ef74b1..ef2f719fc 100644
--- a/test/test/CompilerTest.scala
+++ b/test/test/CompilerTest.scala
@@ -10,6 +10,7 @@ import scala.reflect.io.{ Path, Directory, File => SFile, AbstractFile }
import scala.tools.partest.nest.{ FileManager, NestUI }
import scala.annotation.tailrec
import java.io.{ RandomAccessFile, File => JFile }
+import dotty.tools.io.PlainFile
import org.junit.Test
@@ -89,7 +90,20 @@ abstract class CompilerTest {
if (!generatePartestFiles || !partestableFile(prefix, fileName, extension, args ++ defaultOptions)) {
if (runTest)
log(s"WARNING: run tests can only be run by partest, JUnit just verifies compilation: $prefix$fileName$extension")
- compileArgs((s"$filePath" :: args).toArray, expErrors)
+ if (args.contains("-rewrite")) {
+ val file = new PlainFile(filePath)
+ val data = file.toByteArray
+ // compile with rewrite
+ compileArgs((filePath :: args).toArray, expErrors)
+ // compile again, check that file now compiles without -language:Scala2
+ val plainArgs = args.filter(arg => arg != "-rewrite" && arg != "-language:Scala2")
+ compileFile(prefix, fileName, plainArgs, extension, runTest)
+ // restore original test file
+ val out = file.output
+ out.write(data)
+ out.close()
+ }
+ else compileArgs((filePath :: args).toArray, expErrors)
} else {
val kind = testKind(prefix, runTest)
log(s"generating partest files for test file: $prefix$fileName$extension of kind $kind")
@@ -191,6 +205,8 @@ abstract class CompilerTest {
}
}
+
+
// ========== HELPERS =============
private def expectedErrors(filePaths: List[String]): List[ErrorsInFile] = if (filePaths.exists(isNegTest(_))) filePaths.map(getErrors(_)) else Nil
diff --git a/tests/neg/classOf.scala b/tests/neg/classOf.scala
new file mode 100644
index 000000000..e13cf71c4
--- /dev/null
+++ b/tests/neg/classOf.scala
@@ -0,0 +1,11 @@
+object Test {
+
+ class C { type I }
+ type A = C
+
+ def f1[T] = classOf[T] // error
+ def f2[T <: String] = classOf[T] // error
+ val x = classOf[Test.type] // error
+ val y = classOf[C { type I = String }] // error
+ val z = classOf[A] // ok
+}
diff --git a/tests/neg/tate.scala b/tests/neg/tate.scala
new file mode 100644
index 000000000..acf7ee7e3
--- /dev/null
+++ b/tests/neg/tate.scala
@@ -0,0 +1,11 @@
+ object unsound {
+ trait Bound[A, B <: A]
+ trait Bind[A] {
+ def bad[B <: A](bound: Bound[A, B], b: B) = b
+ }
+ def coerce[T, U](t: T): U = {
+ lazy val bound: Bound[U, _ >: T] = ??? // error: >: T does not conform to upper bound
+ def bind = new Bind[U] {}
+ bind.bad(bound, t)
+ }
+ }
diff --git a/tests/pending/neg/tate.scala b/tests/pending/neg/tate.scala
new file mode 100644
index 000000000..d626ccd3f
--- /dev/null
+++ b/tests/pending/neg/tate.scala
@@ -0,0 +1,11 @@
+trait Out[+T]
+
+object Test {
+
+ def foo[T <% AnyRef](x: T) = ???
+
+ var x: Out[_ >: String] = ???
+ var y: Out[String] = ???
+ x = y // should give error, but currently masked by covariant alias representation
+ // y = x
+}
diff --git a/tests/pending/pos/lazyvals.scala b/tests/pending/pos/lazyvals.scala
new file mode 100644
index 000000000..93a82cd0c
--- /dev/null
+++ b/tests/pending/pos/lazyvals.scala
@@ -0,0 +1,18 @@
+
+
+trait Iterator {
+
+ def span() = {
+ val self: Int = 33
+ class Leading {
+ def finish(): Unit = println("finished")
+ }
+ val leading = new Leading
+
+ class Trailing {
+ @volatile lazy val it = leading.finish()
+ }
+ val trailing = new Trailing
+ (leading, trailing)
+ }
+}
diff --git a/tests/pos-scala2/rewrites.scala b/tests/pos-scala2/rewrites.scala
new file mode 100644
index 000000000..3987821f1
--- /dev/null
+++ b/tests/pos-scala2/rewrites.scala
@@ -0,0 +1,36 @@
+trait Test {
+
+ def baz() {}
+
+ def bar()
+
+ def foo() {
+ println("hi")
+ }
+
+ lazy val x: Int
+}
+
+object Test {
+
+ lazy val x = 1
+
+ @deprecated lazy val y = 2
+
+ @deprecated private lazy val z = 2
+
+ lazy val (x1, y1) = (1, 2)
+
+ @deprecated private lazy val (x2, y2) = (1, 2)
+
+ val yy = x1 _
+ val zz: () => Int = yy
+
+}
+
+class Stream[+A] {
+
+ class Inner(x: A) extends Stream[A]
+
+}
+