Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First steps towards rewriting from Scala2 in dotty #1154

Merged
merged 25 commits into from
Mar 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c43ae4a
Add patching functionality for migration
odersky Mar 4, 2016
c1e263b
Avoid setupMethod in Driver
odersky Mar 4, 2016
12d8955
Better encapsulation
odersky Mar 4, 2016
ad483d8
Add functionality to navigate ASTs
odersky Mar 6, 2016
2ff667a
Test language features always in phase typer.
odersky Mar 6, 2016
ec90a19
Untangle withAnnotation naming.
odersky Mar 6, 2016
54f6399
Fix desugaring of lazy patterns.
odersky Mar 6, 2016
420878d
Copy full modifiers to companions
odersky Mar 6, 2016
294d21f
Better error message when an outer path is not found.
odersky Mar 7, 2016
d822b1e
Add @volatile when rewriting lazy vals from Scala2.
odersky Mar 7, 2016
e61cd1b
Test case for #1149
odersky Mar 7, 2016
8881a98
More neg tests
odersky Mar 7, 2016
dbc06d9
Remove println
odersky Mar 7, 2016
a378a46
Disable volatile interpretation of lazy vals under -language:Scala2
odersky Mar 7, 2016
c1814a0
Maintain source position in Getters
odersky Mar 8, 2016
4db804b
More detailed diagnostic in NavigateAST
odersky Mar 8, 2016
919f268
Patch redundant `_' suffixes.
odersky Mar 8, 2016
87b30c9
Rewrite test
odersky Mar 8, 2016
d24e10c
Polish rewrite test
odersky Mar 9, 2016
c0927cf
Fix setChildPositions
odersky Mar 9, 2016
6ddc911
Fix assert in Rewrites
odersky Mar 9, 2016
bde5e4d
Add patch for variance errors
odersky Mar 9, 2016
13a376c
Fix patch for constructors with procedure syntax
odersky Mar 9, 2016
13e3d59
Fix two rewrite patches.
odersky Mar 9, 2016
6c18e37
Address reviewer comments.
odersky Mar 14, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) = {
Expand Down
38 changes: 22 additions & 16 deletions src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) =>
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -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 = {

Expand Down
83 changes: 83 additions & 0 deletions src/dotty/tools/dotc/ast/NavigateAST.scala
Original file line number Diff line number Diff line change
@@ -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)
}
}
78 changes: 45 additions & 33 deletions src/dotty/tools/dotc/ast/Positioned.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what was wrong with the previous version of this method? And was it impossible to write the new version using recursive calls? I think it'd be helpful to add some comments explaining how this algorithm work and what are the invariants for all the mutable variables.

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 _ =>
}
}
}

Expand Down Expand Up @@ -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
}
}
10 changes: 7 additions & 3 deletions src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
@@ -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 {

Expand Down Expand Up @@ -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
*/
Expand Down
9 changes: 8 additions & 1 deletion src/dotty/tools/dotc/config/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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: _*)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)))
}
}
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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? */
Expand Down
2 changes: 0 additions & 2 deletions src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading