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