Skip to content

Commit cdbc163

Browse files
committed
Merge pull request #1154 from dotty-staging/add-rewrite
First steps towards rewriting from Scala2 in dotty
2 parents 16f0bea + 6c18e37 commit cdbc163

24 files changed

+433
-84
lines changed

src/dotty/tools/dotc/Run.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import io.PlainFile
88
import util.{SourceFile, NoSource, Stats, SimpleMap}
99
import reporting.Reporter
1010
import transform.TreeChecker
11+
import rewrite.Rewrites
1112
import java.io.{BufferedWriter, OutputStreamWriter}
1213
import scala.reflect.io.VirtualFile
1314
import scala.util.control.NonFatal
@@ -64,6 +65,7 @@ class Run(comp: Compiler)(implicit ctx: Context) {
6465
foreachUnit(printTree)
6566
ctx.informTime(s"$phase ", start)
6667
}
68+
if (!ctx.reporter.hasErrors) Rewrites.writeBack()
6769
}
6870

6971
private def printTree(ctx: Context) = {

src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ object desugar {
228228
val tparam = cpy.TypeDef(tdef)(name = tdef.name.expandedName(ctx.owner))
229229
.withMods(tdef.mods &~ PrivateLocal | ExpandedName)
230230
val alias = cpy.TypeDef(tdef)(rhs = refOfDef(tparam), tparams = Nil)
231-
.withFlags(PrivateLocalParamAccessor | Synthetic | tdef.mods.flags & VarianceFlags)
231+
.withMods(tdef.mods & VarianceFlags | PrivateLocalParamAccessor | Synthetic)
232232
Thicket(tparam, alias)
233233
}
234234
else tdef
@@ -237,15 +237,15 @@ object desugar {
237237
@sharable private val synthetic = Modifiers(Synthetic)
238238

239239
private def toDefParam(tparam: TypeDef): TypeDef =
240-
tparam.withFlags(Param)
240+
tparam.withMods(tparam.rawMods & EmptyFlags | Param)
241241
private def toDefParam(vparam: ValDef): ValDef =
242-
vparam.withFlags(Param | vparam.rawMods.flags & Implicit)
242+
vparam.withMods(vparam.rawMods & Implicit | Param)
243243

244244
/** The expansion of a class definition. See inline comments for what is involved */
245245
def classDef(cdef: TypeDef)(implicit ctx: Context): Tree = {
246246
val TypeDef(name, impl @ Template(constr0, parents, self, _)) = cdef
247247
val mods = cdef.mods
248-
val accessFlags = (mods.flags & AccessFlags).toCommonFlags
248+
val companionMods = mods.withFlags((mods.flags & AccessFlags).toCommonFlags)
249249

250250
val (constr1, defaultGetters) = defDef(constr0, isPrimaryConstructor = true) match {
251251
case meth: DefDef => (meth, Nil)
@@ -364,7 +364,7 @@ object desugar {
364364
moduleDef(
365365
ModuleDef(
366366
name.toTermName, Template(emptyConstructor, parentTpt :: Nil, EmptyValDef, defs))
367-
.withFlags(Synthetic | accessFlags))
367+
.withMods(companionMods | Synthetic))
368368
.withPos(cdef.pos).toList
369369

370370
// The companion object definitions, if a companion is needed, Nil otherwise.
@@ -421,10 +421,9 @@ object desugar {
421421
// implicit wrapper is typechecked in same scope as constructor, so
422422
// we can reuse the constructor parameters; no derived params are needed.
423423
DefDef(name.toTermName, constrTparams, constrVparamss, classTypeRef, creatorExpr)
424-
.withFlags(Synthetic | Implicit | accessFlags)
424+
.withMods(companionMods | Synthetic | Implicit)
425425
.withPos(cdef.pos) :: Nil
426426

427-
428427
val self1 = {
429428
val selfType = if (self.tpt.isEmpty) classTypeRef else self.tpt
430429
if (self.isEmpty) self
@@ -498,18 +497,18 @@ object desugar {
498497

499498
/** If `pat` is a variable pattern,
500499
*
501-
* val/var p = e
500+
* val/var/lazy val p = e
502501
*
503502
* Otherwise, in case there is exactly one variable x_1 in pattern
504-
* val/var p = e ==> val/var x_1 = (e: @unchecked) match (case p => (x_1))
503+
* val/var/lazy val p = e ==> val/var/lazy val x_1 = (e: @unchecked) match (case p => (x_1))
505504
*
506505
* in case there are zero or more than one variables in pattern
507-
* val/var p = e ==> private synthetic val t$ = (e: @unchecked) match (case p => (x_1, ..., x_N))
508-
* val/var x_1 = t$._1
506+
* val/var/lazy p = e ==> private synthetic [lazy] val t$ = (e: @unchecked) match (case p => (x_1, ..., x_N))
507+
* val/var/def x_1 = t$._1
509508
* ...
510-
* val/var x_N = t$._N
509+
* val/var/def x_N = t$._N
511510
* If the original pattern variable carries a type annotation, so does the corresponding
512-
* ValDef.
511+
* ValDef or DefDef.
513512
*/
514513
def makePatDef(mods: Modifiers, pat: Tree, rhs: Tree)(implicit ctx: Context): Tree = pat match {
515514
case VarPattern(named, tpt) =>
@@ -533,12 +532,16 @@ object desugar {
533532
derivedValDef(named, tpt, matchExpr, mods)
534533
case _ =>
535534
val tmpName = ctx.freshName().toTermName
536-
val patFlags = mods.flags & AccessFlags | Synthetic | (mods.flags & Lazy)
537-
val firstDef = ValDef(tmpName, TypeTree(), matchExpr).withFlags(patFlags)
535+
val patMods = mods & (AccessFlags | Lazy) | Synthetic
536+
val firstDef =
537+
ValDef(tmpName, TypeTree(), matchExpr)
538+
.withPos(pat.pos.union(rhs.pos)).withMods(patMods)
538539
def selector(n: Int) = Select(Ident(tmpName), nme.selectorName(n))
539540
val restDefs =
540541
for (((named, tpt), n) <- vars.zipWithIndex)
541-
yield derivedValDef(named, tpt, selector(n), mods)
542+
yield
543+
if (mods is Lazy) derivedDefDef(named, tpt, selector(n), mods &~ Lazy)
544+
else derivedValDef(named, tpt, selector(n), mods)
542545
flatTree(firstDef :: restDefs)
543546
}
544547
}
@@ -635,6 +638,9 @@ object desugar {
635638
private def derivedValDef(named: NameTree, tpt: Tree, rhs: Tree, mods: Modifiers) =
636639
ValDef(named.name.asTermName, tpt, rhs).withMods(mods).withPos(named.pos)
637640

641+
private def derivedDefDef(named: NameTree, tpt: Tree, rhs: Tree, mods: Modifiers) =
642+
DefDef(named.name.asTermName, Nil, Nil, tpt, rhs).withMods(mods).withPos(named.pos)
643+
638644
/** Main desugaring method */
639645
def apply(tree: Tree)(implicit ctx: Context): Tree = {
640646

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package dotty.tools.dotc
2+
package ast
3+
4+
import core.Contexts.Context
5+
import core.Decorators._
6+
import util.Positions._
7+
import Trees.{MemberDef, DefTree}
8+
9+
/** Utility functions to go from typed to untyped ASTs */
10+
object NavigateAST {
11+
12+
/** The untyped tree corresponding to typed tree `tree` in the compilation
13+
* unit specified by `ctx`
14+
*/
15+
def toUntyped(tree: tpd.Tree)(implicit ctx: Context): untpd.Tree =
16+
untypedPath(tree, exactMatch = true) match {
17+
case (utree: untpd.Tree) :: _ =>
18+
utree
19+
case _ =>
20+
val loosePath = untypedPath(tree, exactMatch = false)
21+
throw new
22+
Error(i"""no untyped tree for $tree, pos = ${tree.pos}, envelope = ${tree.envelope}
23+
|best matching path =\n$loosePath%\n====\n%
24+
|path positions = ${loosePath.map(_.pos)}
25+
|path envelopes = ${loosePath.map(_.envelope)}""".stripMargin)
26+
}
27+
28+
/** The reverse path of untyped trees starting with a tree that closest matches
29+
* `tree` and ending in the untyped tree at the root of the compilation unit
30+
* specified by `ctx`.
31+
* @param exactMatch If `true`, the path must start with a node that exactly
32+
* matches `tree`, or `Nil` is returned.
33+
* If `false` the path might start with a node enclosing
34+
* the logical position of `tree`.
35+
* Note: A complication concerns member definitions. ValDefs and DefDefs
36+
* have after desugaring a position that spans just the name of the symbol being
37+
* defined and nothing else. So we look instead for an untyped tree approximating the
38+
* envelope of the definition, and declare success if we find another DefTree.
39+
*/
40+
def untypedPath(tree: tpd.Tree, exactMatch: Boolean = false)(implicit ctx: Context): List[Positioned] =
41+
tree match {
42+
case tree: MemberDef[_] =>
43+
untypedPath(tree.envelope) match {
44+
case path @ (last: DefTree[_]) :: _ => path
45+
case path if !exactMatch => path
46+
case _ => Nil
47+
}
48+
case _ =>
49+
untypedPath(tree.pos) match {
50+
case (path @ last :: _) if last.pos == tree.pos || !exactMatch => path
51+
case _ => Nil
52+
}
53+
}
54+
55+
/** The reverse part of the untyped root of the compilation unit of `ctx` to
56+
* position `pos`.
57+
*/
58+
def untypedPath(pos: Position)(implicit ctx: Context): List[Positioned] =
59+
pathTo(pos, ctx.compilationUnit.untpdTree)
60+
61+
62+
/** The reverse path from node `from` to the node that closest encloses position `pos`,
63+
* or `Nil` if no such path exists. If a non-empty path is returned it starts with
64+
* the node closest enclosing `pos` and ends with `from`.
65+
*/
66+
def pathTo(pos: Position, from: Positioned)(implicit ctx: Context): List[Positioned] = {
67+
def childPath(it: Iterator[Any], path: List[Positioned]): List[Positioned] = {
68+
while (it.hasNext) {
69+
val path1 = it.next match {
70+
case p: Positioned => singlePath(p, path)
71+
case xs: List[_] => childPath(xs.iterator, path)
72+
case _ => path
73+
}
74+
if (path1 ne path) return path1
75+
}
76+
path
77+
}
78+
def singlePath(p: Positioned, path: List[Positioned]): List[Positioned] =
79+
if (p.envelope contains pos) childPath(p.productIterator, p :: path)
80+
else path
81+
singlePath(from, Nil)
82+
}
83+
}

src/dotty/tools/dotc/ast/Positioned.scala

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,55 @@ abstract class Positioned extends DotClass with Product {
5454
*/
5555
private[dotc] def setPosUnchecked(pos: Position) = curPos = pos
5656

57-
/** If any children of this node do not have positions, set them to the given position,
57+
/** If any children of this node do not have positions,
58+
* fit their positions between the positions of the known subtrees
5859
* and transitively visit their children.
60+
* The method is likely time-critical because it is invoked on any node
61+
* we create, so we want to avoid object allocations in the common case.
62+
* The method is naturally expressed as two mutually (tail-)recursive
63+
* functions, one which computes the next element to consider or terminates if there
64+
* is none and the other which propagates the position information to that element.
65+
* But since mutual tail recursion is not supported in Scala, we express it instead
66+
* as a while loop with a termination by return in the middle.
5967
*/
6068
private def setChildPositions(pos: Position): Unit = {
61-
def deepSetPos(x: Any): Unit = x match {
62-
case p: Positioned =>
63-
if (!p.pos.exists) p.setPos(pos)
64-
case xs: List[_] =>
65-
xs foreach deepSetPos
66-
case _ =>
69+
var n = productArity // subnodes are analyzed right to left
70+
var elems: List[Any] = Nil // children in lists still to be considered, from right to left
71+
var end = pos.end // the last defined offset, fill in positions up to this offset
72+
var outstanding: List[Positioned] = Nil // nodes that need their positions filled once a start position
73+
// is known, from left to right.
74+
def fillIn(ps: List[Positioned], start: Int, end: Int): Unit = ps match {
75+
case p :: ps1 =>
76+
p.setPos(Position(start, end))
77+
fillIn(ps1, end, end)
78+
case nil =>
6779
}
68-
var n = productArity
69-
while (n > 0) {
70-
n -= 1
71-
deepSetPos(productElement(n))
80+
while (true) {
81+
var nextChild: Any = null // the next child to be considered
82+
if (elems.nonEmpty) {
83+
nextChild = elems.head
84+
elems = elems.tail
85+
}
86+
else if (n > 0) {
87+
n = n - 1
88+
nextChild = productElement(n)
89+
}
90+
else {
91+
fillIn(outstanding, pos.start, end)
92+
return
93+
}
94+
nextChild match {
95+
case p: Positioned =>
96+
if (p.pos.exists) {
97+
fillIn(outstanding, p.pos.end, end)
98+
outstanding = Nil
99+
end = p.pos.start
100+
}
101+
else outstanding = p :: outstanding
102+
case xs: List[_] =>
103+
elems = elems ::: xs.reverse
104+
case _ =>
105+
}
72106
}
73107
}
74108

@@ -114,26 +148,4 @@ abstract class Positioned extends DotClass with Product {
114148
found
115149
}
116150
}
117-
118-
/** The path from this node to `that` node, represented
119-
* as a list starting with `this`, ending with`that` where
120-
* every node is a child of its predecessor.
121-
* Nil if no such path exists.
122-
*/
123-
def pathTo(that: Positioned): List[Positioned] = {
124-
def childPath(it: Iterator[Any]): List[Positioned] =
125-
if (it.hasNext) {
126-
val cpath = it.next match {
127-
case x: Positioned => x.pathTo(that)
128-
case xs: List[_] => childPath(xs.iterator)
129-
case _ => Nil
130-
}
131-
if (cpath.nonEmpty) cpath else childPath(it)
132-
} else Nil
133-
if (this eq that) this :: Nil
134-
else if (this.envelope contains that.pos) {
135-
val cpath = childPath(productIterator)
136-
if (cpath.nonEmpty) this :: cpath else Nil
137-
} else Nil
138-
}
139151
}

src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,17 @@ object Trees {
5050
def toTypeFlags: Modifiers[T] = withFlags(flags.toTypeFlags)
5151
def toTermFlags: Modifiers[T] = withFlags(flags.toTermFlags)
5252

53-
private def withFlags(flags: FlagSet) =
53+
def withFlags(flags: FlagSet) =
5454
if (this.flags == flags) this
5555
else copy(flags = flags)
5656

57+
def withAddedAnnotation[U >: Untyped <: T](annot: Tree[U]): Modifiers[U] =
58+
if (annotations.exists(_ eq annot)) this
59+
else withAnnotations(annotations :+ annot)
60+
5761
def withAnnotations[U >: Untyped <: T](annots: List[Tree[U]]): Modifiers[U] =
58-
if (annots.isEmpty) this
59-
else copy(annotations = annotations ++ annots)
62+
if (annots eq annotations) this
63+
else copy(annotations = annots)
6064

6165
def withPrivateWithin(pw: TypeName) =
6266
if (pw.isEmpty) this

src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
package dotty.tools.dotc.config
1+
package dotty.tools.dotc
2+
package config
23

34
import PathResolver.Defaults
5+
import rewrite.Rewrites
46

57
class ScalaSettings extends Settings.SettingGroup {
68

@@ -48,6 +50,7 @@ class ScalaSettings extends Settings.SettingGroup {
4850
val d = StringSetting("-d", "directory|jar", "destination for generated classfiles.", ".")
4951
val nospecialization = BooleanSetting("-no-specialization", "Ignore @specialize annotations.")
5052
val language = MultiStringSetting("-language", "feature", "Enable one or more language features.")
53+
val rewrite = OptionSetting[Rewrites]("-rewrite", "When used in conjunction with -language:Scala2 rewrites sources to migrate to new syntax")
5154

5255
/** -X "Advanced" settings
5356
*/

src/dotty/tools/dotc/config/Settings.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ object Settings {
1919
val StringTag = ClassTag(classOf[String])
2020
val ListTag = ClassTag(classOf[List[_]])
2121
val VersionTag = ClassTag(classOf[ScalaVersion])
22+
val OptionTag = ClassTag(classOf[Option[_]])
2223

2324
class SettingsState(initialValues: Seq[Any]) {
2425
private var values = ArrayBuffer(initialValues: _*)
@@ -55,7 +56,8 @@ object Settings {
5556
choices: Seq[T] = Nil,
5657
prefix: String = "",
5758
aliases: List[String] = Nil,
58-
depends: List[(Setting[_], Any)] = Nil)(private[Settings] val idx: Int) {
59+
depends: List[(Setting[_], Any)] = Nil,
60+
propertyClass: Option[Class[_]] = None)(private[Settings] val idx: Int) {
5961

6062
def withAbbreviation(abbrv: String): Setting[T] =
6163
copy(aliases = aliases :+ abbrv)(idx)
@@ -112,6 +114,8 @@ object Settings {
112114
def doSet(argRest: String) = ((implicitly[ClassTag[T]], args): @unchecked) match {
113115
case (BooleanTag, _) =>
114116
update(true, args)
117+
case (OptionTag, _) =>
118+
update(Some(propertyClass.get.newInstance), args)
115119
case (ListTag, _) =>
116120
if (argRest.isEmpty) missingArg
117121
else update((argRest split ",").toList, args)
@@ -255,5 +259,8 @@ object Settings {
255259

256260
def VersionSetting(name: String, descr: String, default: ScalaVersion = NoScalaVersion): Setting[ScalaVersion] =
257261
publish(Setting(name, descr, default))
262+
263+
def OptionSetting[T: ClassTag](name: String, descr: String): Setting[Option[T]] =
264+
publish(Setting(name, descr, None, propertyClass = Some(implicitly[ClassTag[T]].runtimeClass)))
258265
}
259266
}

src/dotty/tools/dotc/core/TypeOps.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
546546
hasImport(c)
547547
}))
548548
def hasOption = ctx.base.settings.language.value exists (s => s == featureName || s == "_")
549-
hasImport || hasOption
549+
hasImport(ctx.withPhase(ctx.typerPhase)) || hasOption
550550
}
551551

552552
/** Is auto-tupling enabled? */

src/dotty/tools/dotc/core/Types.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2424,8 +2424,6 @@ object Types {
24242424
x => paramBounds mapConserve (_.subst(this, x).bounds),
24252425
x => resType.subst(this, x))
24262426

2427-
// need to override hashCode and equals to be object identity
2428-
// because paramNames by itself is not discriminatory enough
24292427
override def equals(other: Any) = other match {
24302428
case other: PolyType =>
24312429
other.paramNames == this.paramNames && other.paramBounds == this.paramBounds && other.resType == this.resType

0 commit comments

Comments
 (0)