From c43ae4a31cac6363050ab07aa6ec1a9f0e9213b4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Mar 2016 11:43:57 +0100 Subject: Add patching functionality for migration Firs version of patching that can be invoked by dotty compiler itself. --- src/dotty/tools/dotc/Driver.scala | 2 + src/dotty/tools/dotc/Run.scala | 2 + src/dotty/tools/dotc/config/ScalaSettings.scala | 1 + src/dotty/tools/dotc/core/Types.scala | 2 - src/dotty/tools/dotc/parsing/Parsers.scala | 7 +- src/dotty/tools/dotc/rewrite/Patches.scala | 97 +++++++++++++++++++++++++ 6 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 src/dotty/tools/dotc/rewrite/Patches.scala (limited to 'src') diff --git a/src/dotty/tools/dotc/Driver.scala b/src/dotty/tools/dotc/Driver.scala index 887274fa8..fd82934ec 100644 --- a/src/dotty/tools/dotc/Driver.scala +++ b/src/dotty/tools/dotc/Driver.scala @@ -5,6 +5,7 @@ import config.CompilerCommand import core.Contexts.{Context, ContextBase} import util.DotClass import reporting._ +import rewrite.Patches import scala.util.control.NonFatal /** Run the Dotty compiler. @@ -43,6 +44,7 @@ abstract class Driver extends DotClass { val ctx = rootCtx.fresh val summary = CompilerCommand.distill(args)(ctx) ctx.setSettings(summary.sstate) + Patches.setup(ctx) val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired)(ctx) (fileNames, ctx) } diff --git a/src/dotty/tools/dotc/Run.scala b/src/dotty/tools/dotc/Run.scala index 9972e3e64..aba7a002d 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.Patches 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) Patches.writeBack() } private def printTree(ctx: Context) = { diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index aa264329c..17da1f8d1 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -48,6 +48,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 = BooleanSetting("-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/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/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index bb8fbe08b..47b0ae22d 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.Patches.patch object Parsers { @@ -1762,7 +1763,11 @@ object Parsers { */ def defDefOrDcl(mods: Modifiers): Tree = atPos(tokenRange) { def scala2ProcedureSyntax = - testScala2Mode("Procedure syntax no longer supported; `: Unit =' should be inserted here") + testScala2Mode("Procedure syntax no longer supported; `: Unit =' should be inserted here") && { + patch(source, Position(in.lastOffset), + if (in.token == LBRACE) ": Unit =" else ": Unit ") + true + } if (in.token == THIS) { in.nextToken() val vparamss = paramClauses(nme.CONSTRUCTOR) diff --git a/src/dotty/tools/dotc/rewrite/Patches.scala b/src/dotty/tools/dotc/rewrite/Patches.scala new file mode 100644 index 000000000..85532dad9 --- /dev/null +++ b/src/dotty/tools/dotc/rewrite/Patches.scala @@ -0,0 +1,97 @@ +package dotty.tools.dotc +package rewrite + +import util.{SourceFile, Positions} +import Positions.Position +import core.Contexts.{Context, FreshContext} +import collection.mutable + +object Patches { + + private val PropertyTag = "rewrite-patches" + + private case class Patch(pos: Position, replacement: String) { + def delta = replacement.length - (pos.end - pos.start) + } + + private class PatchedFiles extends mutable.HashMap[SourceFile, Patches] + + /** If `-rewrite` is set, enable patches in `ctx`. This means + * setting up a property in `ctx` that allows to record patches. + */ + def setup(ctx: FreshContext): Unit = + if (ctx.settings.rewrite.value(ctx)) + ctx.setProperty(PropertyTag, new PatchedFiles) + + /** If patches are enabled in `ctx`, 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 = + ctx.moreProperties.get(PropertyTag) match { + case Some(pfs: PatchedFiles) => + pfs.get(source) match { + case Some(ps) => + ps.addPatch(pos, replacement) + case None => + pfs(source) = new Patches(source) + patch(source, pos, replacement) + } + case _ => + } + + /** If patches are enabled in `ctx`, apply all patches + * and overwrite patched source files + */ + def writeBack()(implicit ctx: Context) = + ctx.moreProperties.get(PropertyTag) match { + case Some(pfs: PatchedFiles) => + for (source <- pfs.keys) { + ctx.println(s"[patched file ${source.file.path}]") + pfs(source).writeBack() + } + case _ => + } +} + +private class Patches(source: SourceFile) { + import Patches._ + + 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) + patches.iterator.sliding(2, 1).foreach(ps => + assert(ps(0).pos.end <= ps(1).pos.start, s"overlapping patches: ${ps(0)} and ${ps(1)}")) + 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.content) + val bytes = new String(chars).getBytes + out.write(bytes) + out.close() + } +} \ No newline at end of file -- cgit v1.2.3 From c1e263bebf7e73239e7faa3e48d75a0a7df45d76 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Mar 2016 12:07:59 +0100 Subject: Avoid setupMethod in Driver Driver should not know that patch functionality exists. Instead, introduce settings that can introduce their own stateful values. --- src/dotty/tools/dotc/Driver.scala | 1 - src/dotty/tools/dotc/config/ScalaSettings.scala | 6 ++++-- src/dotty/tools/dotc/config/Settings.scala | 9 ++++++++- src/dotty/tools/dotc/rewrite/Patches.scala | 22 ++++++---------------- 4 files changed, 18 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/Driver.scala b/src/dotty/tools/dotc/Driver.scala index fd82934ec..a989ad218 100644 --- a/src/dotty/tools/dotc/Driver.scala +++ b/src/dotty/tools/dotc/Driver.scala @@ -44,7 +44,6 @@ abstract class Driver extends DotClass { val ctx = rootCtx.fresh val summary = CompilerCommand.distill(args)(ctx) ctx.setSettings(summary.sstate) - Patches.setup(ctx) val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired)(ctx) (fileNames, ctx) } diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index 17da1f8d1..cdbf6ec50 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.Patches class ScalaSettings extends Settings.SettingGroup { @@ -48,7 +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 = BooleanSetting("-rewrite", "When used in conjunction with -language:Scala2 rewrites sources to migrate to new syntax") + val rewrite = OptionSetting[Patches.PatchedFiles]("-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/rewrite/Patches.scala b/src/dotty/tools/dotc/rewrite/Patches.scala index 85532dad9..7513ec7fa 100644 --- a/src/dotty/tools/dotc/rewrite/Patches.scala +++ b/src/dotty/tools/dotc/rewrite/Patches.scala @@ -8,26 +8,17 @@ import collection.mutable object Patches { - private val PropertyTag = "rewrite-patches" - private case class Patch(pos: Position, replacement: String) { def delta = replacement.length - (pos.end - pos.start) } - private class PatchedFiles extends mutable.HashMap[SourceFile, Patches] - - /** If `-rewrite` is set, enable patches in `ctx`. This means - * setting up a property in `ctx` that allows to record patches. - */ - def setup(ctx: FreshContext): Unit = - if (ctx.settings.rewrite.value(ctx)) - ctx.setProperty(PropertyTag, new PatchedFiles) + class PatchedFiles extends mutable.HashMap[SourceFile, Patches] - /** If patches are enabled in `ctx`, record a patch that replaces the range + /** 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 = - ctx.moreProperties.get(PropertyTag) match { + ctx.settings.rewrite.value match { case Some(pfs: PatchedFiles) => pfs.get(source) match { case Some(ps) => @@ -39,11 +30,10 @@ object Patches { case _ => } - /** If patches are enabled in `ctx`, apply all patches - * and overwrite patched source files + /** If -rewrite is set, apply all patches and overwrite patched source files. */ def writeBack()(implicit ctx: Context) = - ctx.moreProperties.get(PropertyTag) match { + ctx.settings.rewrite.value match { case Some(pfs: PatchedFiles) => for (source <- pfs.keys) { ctx.println(s"[patched file ${source.file.path}]") @@ -53,7 +43,7 @@ object Patches { } } -private class Patches(source: SourceFile) { +class Patches(source: SourceFile) { import Patches._ private val pbuf = new mutable.ListBuffer[Patch]() -- cgit v1.2.3 From 12d895587444d5d59e7d75dfaf7b85deb61e99e0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Mar 2016 12:23:10 +0100 Subject: Better encapsulation No more leaking ofMove PatchedFiles in a settings option. Move all patch classes into a `Rewrites` object. --- src/dotty/tools/dotc/Driver.scala | 1 - src/dotty/tools/dotc/Run.scala | 4 +- src/dotty/tools/dotc/config/ScalaSettings.scala | 4 +- src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- src/dotty/tools/dotc/rewrite/Patches.scala | 87 ---------------------- src/dotty/tools/dotc/rewrite/Rewrites.scala | 96 +++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 93 deletions(-) delete mode 100644 src/dotty/tools/dotc/rewrite/Patches.scala create mode 100644 src/dotty/tools/dotc/rewrite/Rewrites.scala (limited to 'src') diff --git a/src/dotty/tools/dotc/Driver.scala b/src/dotty/tools/dotc/Driver.scala index a989ad218..887274fa8 100644 --- a/src/dotty/tools/dotc/Driver.scala +++ b/src/dotty/tools/dotc/Driver.scala @@ -5,7 +5,6 @@ import config.CompilerCommand import core.Contexts.{Context, ContextBase} import util.DotClass import reporting._ -import rewrite.Patches import scala.util.control.NonFatal /** Run the Dotty compiler. diff --git a/src/dotty/tools/dotc/Run.scala b/src/dotty/tools/dotc/Run.scala index aba7a002d..ee808323a 100644 --- a/src/dotty/tools/dotc/Run.scala +++ b/src/dotty/tools/dotc/Run.scala @@ -8,7 +8,7 @@ import io.PlainFile import util.{SourceFile, NoSource, Stats, SimpleMap} import reporting.Reporter import transform.TreeChecker -import rewrite.Patches +import rewrite.Rewrites import java.io.{BufferedWriter, OutputStreamWriter} import scala.reflect.io.VirtualFile import scala.util.control.NonFatal @@ -65,7 +65,7 @@ class Run(comp: Compiler)(implicit ctx: Context) { foreachUnit(printTree) ctx.informTime(s"$phase ", start) } - if (!ctx.reporter.hasErrors) Patches.writeBack() + if (!ctx.reporter.hasErrors) Rewrites.writeBack() } private def printTree(ctx: Context) = { diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index cdbf6ec50..a10165be2 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -2,7 +2,7 @@ package dotty.tools.dotc package config import PathResolver.Defaults -import rewrite.Patches +import rewrite.Rewrites class ScalaSettings extends Settings.SettingGroup { @@ -50,7 +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[Patches.PatchedFiles]("-rewrite", "When used in conjunction with -language:Scala2 rewrites sources to migrate to new syntax") + 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/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index 47b0ae22d..d5ce455f3 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -21,7 +21,7 @@ import Constants._ import ScriptParsers._ import annotation.switch import util.DotClass -import rewrite.Patches.patch +import rewrite.Rewrites.patch object Parsers { diff --git a/src/dotty/tools/dotc/rewrite/Patches.scala b/src/dotty/tools/dotc/rewrite/Patches.scala deleted file mode 100644 index 7513ec7fa..000000000 --- a/src/dotty/tools/dotc/rewrite/Patches.scala +++ /dev/null @@ -1,87 +0,0 @@ -package dotty.tools.dotc -package rewrite - -import util.{SourceFile, Positions} -import Positions.Position -import core.Contexts.{Context, FreshContext} -import collection.mutable - -object Patches { - - private case class Patch(pos: Position, replacement: String) { - def delta = replacement.length - (pos.end - pos.start) - } - - class PatchedFiles extends mutable.HashMap[SourceFile, Patches] - - /** 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 = - ctx.settings.rewrite.value match { - case Some(pfs: PatchedFiles) => - pfs.get(source) match { - case Some(ps) => - ps.addPatch(pos, replacement) - case None => - pfs(source) = new Patches(source) - patch(source, pos, replacement) - } - case _ => - } - - /** If -rewrite is set, apply all patches and overwrite patched source files. - */ - def writeBack()(implicit ctx: Context) = - ctx.settings.rewrite.value match { - case Some(pfs: PatchedFiles) => - for (source <- pfs.keys) { - ctx.println(s"[patched file ${source.file.path}]") - pfs(source).writeBack() - } - case _ => - } -} - -class Patches(source: SourceFile) { - import Patches._ - - 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) - patches.iterator.sliding(2, 1).foreach(ps => - assert(ps(0).pos.end <= ps(1).pos.start, s"overlapping patches: ${ps(0)} and ${ps(1)}")) - 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.content) - val bytes = new String(chars).getBytes - out.write(bytes) - out.close() - } -} \ No newline at end of file diff --git a/src/dotty/tools/dotc/rewrite/Rewrites.scala b/src/dotty/tools/dotc/rewrite/Rewrites.scala new file mode 100644 index 000000000..27dafebcf --- /dev/null +++ b/src/dotty/tools/dotc/rewrite/Rewrites.scala @@ -0,0 +1,96 @@ +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) + patches.iterator.sliding(2, 1).foreach(ps => + assert(ps(0).pos.end <= ps(1).pos.start, s"overlapping patches: ${ps(0)} and ${ps(1)}")) + 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.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 = + ctx.settings.rewrite.value match { + case Some(rewrites: Rewrites) => + rewrites.patched.get(source) match { + case Some(ps) => + ps.addPatch(pos, replacement) + case None => + rewrites.patched(source) = new Patches(source) + patch(source, pos, replacement) + } + case _ => + } + + /** If -rewrite is set, apply all patches and overwrite patched source files. + */ + def writeBack()(implicit ctx: Context) = + ctx.settings.rewrite.value match { + case Some(rewrites: Rewrites) => + for (source <- rewrites.patched.keys) { + ctx.println(s"[patched file ${source.file.path}]") + rewrites.patched(source).writeBack() + } + case _ => + } +} + +/** A completely encapsulated class representing rewrite state, used + * as an optional setting. + */ +class Rewrites { + import Rewrites._ + private val patched = new PatchedFiles +} + + + -- cgit v1.2.3 From ad483d8c3c9dc70cad9f463efd025bab7e07b882 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Mar 2016 18:47:34 +0100 Subject: Add functionality to navigate ASTs Map typed to corresponding untyped trees. --- src/dotty/tools/dotc/ast/NavigateAST.scala | 80 ++++++++++++++++++++++++++++++ src/dotty/tools/dotc/ast/Positioned.scala | 67 +++++++++++++------------ 2 files changed, 115 insertions(+), 32 deletions(-) create mode 100644 src/dotty/tools/dotc/ast/NavigateAST.scala (limited to 'src') diff --git a/src/dotty/tools/dotc/ast/NavigateAST.scala b/src/dotty/tools/dotc/ast/NavigateAST.scala new file mode 100644 index 000000000..ed4af32ce --- /dev/null +++ b/src/dotty/tools/dotc/ast/NavigateAST.scala @@ -0,0 +1,80 @@ +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 _ => + throw new Error(i"no untyped tree for $tree, best matching path =\n${untypedPath(tree, exactMatch = false)}%\n====\n%") + } + + /** 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] = { + println(i"TEST ${p.envelope} for $pos, tree = $p") + 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..f9a775c98 100644 --- a/src/dotty/tools/dotc/ast/Positioned.scala +++ b/src/dotty/tools/dotc/ast/Positioned.scala @@ -58,17 +58,42 @@ abstract class Positioned extends DotClass with Product { * and transitively visit their children. */ 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 - while (n > 0) { - n -= 1 - deepSetPos(productElement(n)) + var elems: List[Any] = Nil + var end = pos.end + var outstanding: List[Positioned] = Nil + 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 => + } + while (true) { + var nextElem: Any = null + if (elems.nonEmpty) { + nextElem = elems.head + elems = elems.tail + } + else if (n > 0) { + n = n - 1 + nextElem = productElement(n) + } + else { + fillIn(outstanding, pos.start, end) + return + } + nextElem 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 = if (elems.isEmpty) xs else xs ::: elems + case _ => + } } } @@ -114,26 +139,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 - } } -- cgit v1.2.3 From 2ff667a6f3b9ac7170b366354d99d928b2fd0c40 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Mar 2016 18:51:33 +0100 Subject: Test language features always in phase typer. Imports are missing afterwards. --- src/dotty/tools/dotc/core/TypeOps.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') 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? */ -- cgit v1.2.3 From ec90a19852dbeeaaa10c5e95c3f97d605386e1ca Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Mar 2016 18:58:51 +0100 Subject: Untangle withAnnotation naming. --- src/dotty/tools/dotc/ast/Trees.scala | 10 +++++++--- src/dotty/tools/dotc/parsing/JavaParsers.scala | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'src') 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/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 -- cgit v1.2.3 From 54f6399b6625cb8f841c1e5965841d46a3e9230c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Mar 2016 19:14:11 +0100 Subject: Fix desugaring of lazy patterns. Selectors should be defs, not lazy vals. --- src/dotty/tools/dotc/ast/Desugar.scala | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index 8ba155097..d3dc88b84 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -498,16 +498,16 @@ 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 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. */ @@ -533,12 +533,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 +639,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 = { -- cgit v1.2.3 From 420878d609a19a01226e76e1b315db5a7eca3576 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Mar 2016 19:39:28 +0100 Subject: Copy full modifiers to companions --- src/dotty/tools/dotc/ast/Desugar.scala | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index d3dc88b84..2fb1b3ef5 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 -- cgit v1.2.3 From 294d21ff6168f1402efc04a52f8d26deaed31c2c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Mar 2016 15:08:16 +0100 Subject: Better error message when an outer path is not found. --- src/dotty/tools/dotc/transform/ExplicitOuter.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/src/dotty/tools/dotc/transform/ExplicitOuter.scala index 4cf076c45..90ce6eb01 100644 --- a/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -319,7 +319,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) -- cgit v1.2.3 From d822b1e965aa808d706b859ce90ed4dc08df8d94 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Mar 2016 15:10:25 +0100 Subject: Add @volatile when rewriting lazy vals from Scala2. --- src/dotty/tools/dotc/transform/LazyVals.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/transform/LazyVals.scala b/src/dotty/tools/dotc/transform/LazyVals.scala index 25b9afa68..88d55c576 100644 --- a/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/src/dotty/tools/dotc/transform/LazyVals.scala @@ -12,7 +12,10 @@ 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.NavigateAST._ import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.ast.{untpd, tpd} import dotty.tools.dotc.core.Constants.Constant @@ -65,11 +68,14 @@ 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 || + ctx.scala2Mode && { + if (ctx.settings.rewrite.value.isDefined) + patch(ctx.compilationUnit.source, Position(toUntyped(tree).envelope.start), "@volatile ") + true // cannot assume volatile because of problems with compilestdlib. See #1149 + } || (sym.is(Flags.Module) && !sym.is(Flags.Synthetic))) // module class is user-defined. // Should be threadsafe, to mimic safety guaranteed by global object -- cgit v1.2.3 From dbc06d9e0d1532754f8bcb9dba9bf6aac42a9b69 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Mar 2016 17:25:05 +0100 Subject: Remove println --- src/dotty/tools/dotc/ast/NavigateAST.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/ast/NavigateAST.scala b/src/dotty/tools/dotc/ast/NavigateAST.scala index ed4af32ce..9c1c17f50 100644 --- a/src/dotty/tools/dotc/ast/NavigateAST.scala +++ b/src/dotty/tools/dotc/ast/NavigateAST.scala @@ -70,11 +70,9 @@ object NavigateAST { } path } - def singlePath(p: Positioned, path: List[Positioned]): List[Positioned] = { - println(i"TEST ${p.envelope} for $pos, tree = $p") + 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 -- cgit v1.2.3 From a378a46f285dd904a5ba5d2b57a5a8c2cdd7b056 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Mar 2016 17:30:04 +0100 Subject: Disable volatile interpretation of lazy vals under -language:Scala2 Revert this commit once #1149 is fixed. --- src/dotty/tools/dotc/transform/LazyVals.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/transform/LazyVals.scala b/src/dotty/tools/dotc/transform/LazyVals.scala index 88d55c576..ed5c23f8c 100644 --- a/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/src/dotty/tools/dotc/transform/LazyVals.scala @@ -74,7 +74,7 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer with Nee ctx.scala2Mode && { if (ctx.settings.rewrite.value.isDefined) patch(ctx.compilationUnit.source, Position(toUntyped(tree).envelope.start), "@volatile ") - true // cannot assume volatile because of problems with compilestdlib. See #1149 + false // cannot assume volatile because of problems with compilestdlib. See #1149 } || (sym.is(Flags.Module) && !sym.is(Flags.Synthetic))) // module class is user-defined. -- cgit v1.2.3 From c1814a01c9a48b83174055cdc6acc74906106a92 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Mar 2016 18:05:19 +0100 Subject: Maintain source position in Getters --- src/dotty/tools/dotc/transform/Getters.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') 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 } -- cgit v1.2.3 From 4db804bc7b75038a8b4a36d4132af21e30c15ebc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Mar 2016 18:05:41 +0100 Subject: More detailed diagnostic in NavigateAST --- src/dotty/tools/dotc/ast/NavigateAST.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/ast/NavigateAST.scala b/src/dotty/tools/dotc/ast/NavigateAST.scala index 9c1c17f50..3753c6702 100644 --- a/src/dotty/tools/dotc/ast/NavigateAST.scala +++ b/src/dotty/tools/dotc/ast/NavigateAST.scala @@ -17,7 +17,12 @@ object NavigateAST { case (utree: untpd.Tree) :: _ => utree case _ => - throw new Error(i"no untyped tree for $tree, best matching path =\n${untypedPath(tree, exactMatch = false)}%\n====\n%") + 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 -- cgit v1.2.3 From 919f268cd93c3ad1c3cb629caa7c11bbb0e8af8f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Mar 2016 18:07:48 +0100 Subject: Patch redundant `_' suffixes. Scala2 allows `x _` even if `x` is not a method. Dotty disallows them. The patch removes the ` _` in these cases. --- src/dotty/tools/dotc/typer/Typer.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index fdb92a40b..b9940e367 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -33,6 +33,7 @@ import annotation.tailrec import Implicits._ import util.Stats.{track, record} import config.Printers._ +import rewrite.Rewrites.patch import language.implicitConversions object Typer { @@ -1135,13 +1136,15 @@ 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) { ctx.migrationWarning(msg, tree.pos) + patch(ctx.compilationUnit.source, Position(qual.pos.end, tree.pos.end), "") res = typed(untpd.Function(Nil, untpd.TypedSplice(res))) } else ctx.error(msg, tree.pos) @@ -1232,7 +1235,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) } -- cgit v1.2.3 From c0927cf6a8c622d38f262939266145e48e0bc092 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 9 Mar 2016 08:25:09 +0100 Subject: Fix setChildPositions Gave overlapping positions in the case of longer lists of children. --- src/dotty/tools/dotc/ast/Positioned.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/ast/Positioned.scala b/src/dotty/tools/dotc/ast/Positioned.scala index f9a775c98..cff77c919 100644 --- a/src/dotty/tools/dotc/ast/Positioned.scala +++ b/src/dotty/tools/dotc/ast/Positioned.scala @@ -91,7 +91,8 @@ abstract class Positioned extends DotClass with Product { } else outstanding = p :: outstanding case xs: List[_] => - elems = if (elems.isEmpty) xs else xs ::: elems + val newElems = xs.reverse + elems = if (elems.isEmpty) newElems else newElems ::: elems case _ => } } -- cgit v1.2.3 From 6ddc9112d0eb8a0f32b82c236244defdaeec5ce8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 9 Mar 2016 08:26:23 +0100 Subject: Fix assert in Rewrites `Iterator.sliding(2, 1)` returns a one-element result if the original iterator contains only one element, which makes it unpleasant to use for our task. Replaced by a fold. --- src/dotty/tools/dotc/rewrite/Rewrites.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/rewrite/Rewrites.scala b/src/dotty/tools/dotc/rewrite/Rewrites.scala index 27dafebcf..cda9e8565 100644 --- a/src/dotty/tools/dotc/rewrite/Rewrites.scala +++ b/src/dotty/tools/dotc/rewrite/Rewrites.scala @@ -23,8 +23,11 @@ object Rewrites { def apply(cs: Array[Char]): Array[Char] = { val delta = pbuf.map(_.delta).sum val patches = pbuf.toList.sortBy(_.pos.start) - patches.iterator.sliding(2, 1).foreach(ps => - assert(ps(0).pos.end <= ps(1).pos.start, s"overlapping patches: ${ps(0)} and ${ps(1)}")) + 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 = { -- cgit v1.2.3 From bde5e4dfeb54601755f09983e3893e1679a4b920 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 9 Mar 2016 08:28:22 +0100 Subject: Add patch for variance errors --- src/dotty/tools/dotc/typer/VarianceChecker.scala | 17 ++++++++++------- tests/pos-scala2/rewrites.scala | 6 ++++++ 2 files changed, 16 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/typer/VarianceChecker.scala b/src/dotty/tools/dotc/typer/VarianceChecker.scala index b257ee192..1769835da 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) - ctx.migrationWarning(s"According to new variance rules, this is no longer accepted; need to annotate with @uncheckedVariance:\n$msg", sym.pos) + 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, pos = ${sym.pos}", sym.pos) + patch(ctx.compilationUnit.source, 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/tests/pos-scala2/rewrites.scala b/tests/pos-scala2/rewrites.scala index 63dbdbfce..6ce1f33a4 100644 --- a/tests/pos-scala2/rewrites.scala +++ b/tests/pos-scala2/rewrites.scala @@ -27,3 +27,9 @@ object Test { } +class Stream[+A] { + + class Inner(x: A) extends Stream[A] + +} + -- cgit v1.2.3 From 13a376c80c21ddb28789530091bb54f7fa58e785 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 9 Mar 2016 09:44:17 +0100 Subject: Fix patch for constructors with procedure syntax A constructor def this() { ... } needs to be rewritten to def this() = { ... } not to def this(): Unit = { ... } --- src/dotty/tools/dotc/parsing/Parsers.scala | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index d5ce455f3..aa2058692 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1762,17 +1762,18 @@ 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") && { - patch(source, Position(in.lastOffset), - if (in.token == LBRACE) ": Unit =" else ": Unit ") + def scala2ProcedureSyntax(resultTypeStr: String) = { + val toInsert = if (in.token == LBRACE) s"$resultTypeStr =" else ": Unit " + 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) @@ -1789,7 +1790,7 @@ object Parsers { } else if (!tpt.isEmpty) EmptyTree - else if (scala2ProcedureSyntax) { + else if (scala2ProcedureSyntax(": Unit")) { tpt = scalaUnit if (in.token == LBRACE) expr() else EmptyTree -- cgit v1.2.3 From 13e3d59937ddcb9819904593cb7c6417af8eedd2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 9 Mar 2016 09:46:12 +0100 Subject: Fix two rewrite patches. 1. trailing `_`: `x _` is rewritten to `(() => x)` not to `x` 2. lazy vals: Rewrites are done in Typer, not LazyVals. Later on we are too much at risk to hit synthetically generated lazy vals. --- src/dotty/tools/dotc/transform/LazyVals.scala | 18 ++++++------------ src/dotty/tools/dotc/typer/Typer.scala | 19 +++++++++++++++++-- tests/pos-scala2/rewrites.scala | 1 + 3 files changed, 24 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/transform/LazyVals.scala b/src/dotty/tools/dotc/transform/LazyVals.scala index ed5c23f8c..50db7b3fb 100644 --- a/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/src/dotty/tools/dotc/transform/LazyVals.scala @@ -15,7 +15,6 @@ 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.NavigateAST._ import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.ast.{untpd, tpd} import dotty.tools.dotc.core.Constants.Constant @@ -71,19 +70,14 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer with Nee val isField = sym.owner.isClass if (isField) { if (sym.isVolatile || - ctx.scala2Mode && { - if (ctx.settings.rewrite.value.isDefined) - patch(ctx.compilationUnit.source, Position(toUntyped(tree).envelope.start), "@volatile ") - false // cannot assume volatile because of problems with compilestdlib. See #1149 - } || - (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*/) && !sym.is(Flags.Synthetic)) // TODO assume @voliat + // 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 b9940e367..1a0525c6d 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -34,6 +34,8 @@ import Implicits._ import util.Stats.{track, record} import config.Printers._ import rewrite.Rewrites.patch +import NavigateAST._ +import transform.SymUtils._ import language.implicitConversions object Typer { @@ -985,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(ctx.compilationUnit.source, Position(toUntyped(vdef).envelope.start), "@volatile ") } def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = track("typedDefDef") { @@ -1143,8 +1156,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit 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(ctx.compilationUnit.source, Position(qual.pos.end, tree.pos.end), "") + patch(ctx.compilationUnit.source, Position(tree.pos.start), "(() => ") + patch(ctx.compilationUnit.source, Position(qual.pos.end, tree.pos.end), ")") res = typed(untpd.Function(Nil, untpd.TypedSplice(res))) } else ctx.error(msg, tree.pos) diff --git a/tests/pos-scala2/rewrites.scala b/tests/pos-scala2/rewrites.scala index 6ce1f33a4..3987821f1 100644 --- a/tests/pos-scala2/rewrites.scala +++ b/tests/pos-scala2/rewrites.scala @@ -24,6 +24,7 @@ object Test { @deprecated private lazy val (x2, y2) = (1, 2) val yy = x1 _ + val zz: () => Int = yy } -- cgit v1.2.3 From 6c18e37886e90d217579112ccf867c22658273be Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 14 Mar 2016 10:02:16 +0100 Subject: Address reviewer comments. --- src/dotty/tools/dotc/ast/Desugar.scala | 4 +-- src/dotty/tools/dotc/ast/NavigateAST.scala | 2 +- src/dotty/tools/dotc/ast/Positioned.scala | 30 ++++++++++++++--------- src/dotty/tools/dotc/parsing/Parsers.scala | 4 ++- src/dotty/tools/dotc/rewrite/Rewrites.scala | 31 +++++++++--------------- src/dotty/tools/dotc/transform/LazyVals.scala | 5 +++- src/dotty/tools/dotc/typer/Typer.scala | 6 ++--- src/dotty/tools/dotc/typer/VarianceChecker.scala | 4 +-- 8 files changed, 46 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index 2fb1b3ef5..7a305a8b8 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -500,7 +500,7 @@ object desugar { * val/var/lazy val p = e * * Otherwise, in case there is exactly one variable x_1 in pattern - * val/var/lazy val 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/lazy p = e ==> private synthetic [lazy] val t$ = (e: @unchecked) match (case p => (x_1, ..., x_N)) @@ -508,7 +508,7 @@ object desugar { * ... * 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) => diff --git a/src/dotty/tools/dotc/ast/NavigateAST.scala b/src/dotty/tools/dotc/ast/NavigateAST.scala index 3753c6702..782866bad 100644 --- a/src/dotty/tools/dotc/ast/NavigateAST.scala +++ b/src/dotty/tools/dotc/ast/NavigateAST.scala @@ -47,7 +47,7 @@ object NavigateAST { } case _ => untypedPath(tree.pos) match { - case path @ last :: _ if last.pos == tree.pos || !exactMatch => path + case (path @ last :: _) if last.pos == tree.pos || !exactMatch => path case _ => Nil } } diff --git a/src/dotty/tools/dotc/ast/Positioned.scala b/src/dotty/tools/dotc/ast/Positioned.scala index cff77c919..e7f5de591 100644 --- a/src/dotty/tools/dotc/ast/Positioned.scala +++ b/src/dotty/tools/dotc/ast/Positioned.scala @@ -54,14 +54,23 @@ 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 = { - var n = productArity - var elems: List[Any] = Nil - var end = pos.end - var outstanding: List[Positioned] = Nil + 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)) @@ -69,20 +78,20 @@ abstract class Positioned extends DotClass with Product { case nil => } while (true) { - var nextElem: Any = null + var nextChild: Any = null // the next child to be considered if (elems.nonEmpty) { - nextElem = elems.head + nextChild = elems.head elems = elems.tail } else if (n > 0) { n = n - 1 - nextElem = productElement(n) + nextChild = productElement(n) } else { fillIn(outstanding, pos.start, end) return } - nextElem match { + nextChild match { case p: Positioned => if (p.pos.exists) { fillIn(outstanding, p.pos.end, end) @@ -91,8 +100,7 @@ abstract class Positioned extends DotClass with Product { } else outstanding = p :: outstanding case xs: List[_] => - val newElems = xs.reverse - elems = if (elems.isEmpty) newElems else newElems ::: elems + elems = elems ::: xs.reverse case _ => } } diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index aa2058692..6ec75a8b2 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1763,7 +1763,9 @@ object Parsers { */ def defDefOrDcl(mods: Modifiers): Tree = atPos(tokenRange) { def scala2ProcedureSyntax(resultTypeStr: String) = { - val toInsert = if (in.token == LBRACE) s"$resultTypeStr =" else ": Unit " + 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 diff --git a/src/dotty/tools/dotc/rewrite/Rewrites.scala b/src/dotty/tools/dotc/rewrite/Rewrites.scala index cda9e8565..7ab0e5d59 100644 --- a/src/dotty/tools/dotc/rewrite/Rewrites.scala +++ b/src/dotty/tools/dotc/rewrite/Rewrites.scala @@ -51,7 +51,7 @@ object Rewrites { def writeBack(): Unit = { val out = source.file.output - val chars = apply(source.content) + val chars = apply(source.underlying.content) val bytes = new String(chars).getBytes out.write(bytes) out.close() @@ -62,28 +62,21 @@ object Rewrites { * given by `pos` in `source` by `replacement` */ def patch(source: SourceFile, pos: Position, replacement: String)(implicit ctx: Context): Unit = - ctx.settings.rewrite.value match { - case Some(rewrites: Rewrites) => - rewrites.patched.get(source) match { - case Some(ps) => - ps.addPatch(pos, replacement) - case None => - rewrites.patched(source) = new Patches(source) - patch(source, pos, replacement) - } - case _ => - } + 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) = - ctx.settings.rewrite.value match { - case Some(rewrites: Rewrites) => - for (source <- rewrites.patched.keys) { - ctx.println(s"[patched file ${source.file.path}]") - rewrites.patched(source).writeBack() - } - case _ => + for (rewrites <- ctx.settings.rewrite.value; source <- rewrites.patched.keys) { + ctx.println(s"[patched file ${source.file.path}]") + rewrites.patched(source).writeBack() } } diff --git a/src/dotty/tools/dotc/transform/LazyVals.scala b/src/dotty/tools/dotc/transform/LazyVals.scala index 50db7b3fb..fc02e68cc 100644 --- a/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/src/dotty/tools/dotc/transform/LazyVals.scala @@ -70,7 +70,10 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer with Nee val isField = sym.owner.isClass if (isField) { if (sym.isVolatile || - (sym.is(Flags.Module)/* || ctx.scala2Mode*/) && !sym.is(Flags.Synthetic)) // TODO assume @voliat + (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) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 1a0525c6d..2069e790b 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -998,7 +998,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit if (sym.is(Lazy, butNot = Deferred | Module | Synthetic) && !sym.isVolatile && ctx.scala2Mode && ctx.settings.rewrite.value.isDefined && !ctx.isAfterTyper) - patch(ctx.compilationUnit.source, Position(toUntyped(vdef).envelope.start), "@volatile ") + patch(Position(toUntyped(vdef).envelope.start), "@volatile ") } def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = track("typedDefDef") { @@ -1158,8 +1158,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit if (ctx.scala2Mode) { // Under -rewrite, patch `x _` to `(() => x)` ctx.migrationWarning(msg, tree.pos) - patch(ctx.compilationUnit.source, Position(tree.pos.start), "(() => ") - patch(ctx.compilationUnit.source, Position(qual.pos.end, tree.pos.end), ")") + 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) diff --git a/src/dotty/tools/dotc/typer/VarianceChecker.scala b/src/dotty/tools/dotc/typer/VarianceChecker.scala index 1769835da..274218ee3 100644 --- a/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -114,8 +114,8 @@ class VarianceChecker()(implicit ctx: Context) { 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) { - ctx.migrationWarning(s"According to new variance rules, this is no longer accepted; need to annotate with @uncheckedVariance:\n$msg, pos = ${sym.pos}", sym.pos) - patch(ctx.compilationUnit.source, Position(pos.end), " @scala.annotation.unchecked.uncheckedVariance") // TODO use an import or shorten if possible + 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 => -- cgit v1.2.3