From 1080da80769cdc2bef2f06977094caff625b4a15 Mon Sep 17 00:00:00 2001 From: Den Shabalin Date: Thu, 14 Nov 2013 14:51:58 +0100 Subject: refactor out fresh name prefix extraction logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. refactor out FreshNameExtractor out of Quasiquotes cake into SymbolTable (can’t put it outside due to the fact that names are path-dependent) 2. add optional parameter to the fresh name creator to cover additional qq$ prefix needed for quasiquotes 3. add unit tests --- .../scala/tools/reflect/quasiquotes/Parsers.scala | 20 +-------- .../scala/reflect/internal/FreshNames.scala | 35 ++++++++++++++++ .../scala/reflect/internal/SymbolTable.scala | 6 +-- .../reflect/internal/util/FreshNameCreator.scala | 7 ++-- .../scala/reflect/runtime/JavaUniverseForce.scala | 1 + .../quasiquotes/QuasiquoteProperties.scala | 13 +++--- .../tools/nsc/symtab/FreshNameExtractorTest.scala | 47 ++++++++++++++++++++++ 7 files changed, 96 insertions(+), 33 deletions(-) create mode 100644 src/reflect/scala/reflect/internal/FreshNames.scala create mode 100644 test/junit/scala/tools/nsc/symtab/FreshNameExtractorTest.scala diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala index 3901184c25..126c14ac81 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala @@ -55,9 +55,7 @@ trait Parsers { self: Quasiquotes => def isHole(name: Name): Boolean = holeMap.contains(name) - override implicit def fresh: FreshNameCreator = new FreshNameCreator { - override def newName(prefix: String) = super.newName(nme.QUASIQUOTE_PREFIX + prefix) - } + override implicit def fresh: FreshNameCreator = new FreshNameCreator(nme.QUASIQUOTE_PREFIX) override val treeBuilder = new ParserTreeBuilder { override implicit def fresh: FreshNameCreator = parser.fresh @@ -189,19 +187,5 @@ trait Parsers { self: Quasiquotes => } } - // Extractor that matches names which were generated by call to - // freshTermName or freshTypeName within quasiquotes. Such names - // have qq$some$random$prefix$0 shape where qq$ part is added - // by modified fresh name creator in QuasiquoteParser. - object FreshName { - def unapply(name: Name): Option[String] = - name.toString.split("\\$").toSeq match { - case qq +: (middle :+ last) - if qq + "$" == nme.QUASIQUOTE_PREFIX - && Try(last.toInt).isSuccess && middle.nonEmpty => - Some(middle.mkString("", "$", "$")) - case _ => - None - } - } + object FreshName extends FreshNameExtractor(nme.QUASIQUOTE_PREFIX) } \ No newline at end of file diff --git a/src/reflect/scala/reflect/internal/FreshNames.scala b/src/reflect/scala/reflect/internal/FreshNames.scala new file mode 100644 index 0000000000..ab9e488fa7 --- /dev/null +++ b/src/reflect/scala/reflect/internal/FreshNames.scala @@ -0,0 +1,35 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + */ + +package scala +package reflect +package internal + +import scala.reflect.internal.util.FreshNameCreator + +trait FreshNames { self: SymbolTable => + // default fresh name creator used to abstract over currentUnit.fresh and runtime fresh name creator + def currentFreshNameCreator: FreshNameCreator + + // create fresh term/type name using implicit fresh name creator + def freshTermName(prefix: String = "x$")(implicit creator: FreshNameCreator): TermName = newTermName(creator.newName(prefix)) + def freshTypeName(prefix: String)(implicit creator: FreshNameCreator): TypeName = newTypeName(creator.newName(prefix)) + + // Extractor that matches names which were generated by some + // FreshNameCreator with known prefix. Extracts user-specified + // prefix that was used as a parameter to newName by stripping + // global creator prefix and unique number in the end of the name. + class FreshNameExtractor(creatorPrefix: String = "") { + // quote prefix so that it can be used with replaceFirst + // which expects regExp rather than simple string + val quotedCreatorPrefix = java.util.regex.Pattern.quote(creatorPrefix) + + def unapply(name: Name): Option[String] = { + val sname = name.toString + // name should start with creatorPrefix and end with number + if (!sname.startsWith(creatorPrefix) || !sname.matches("^.*\\d*$")) None + else Some(NameTransformer.decode(sname.replaceFirst(quotedCreatorPrefix, "").replaceAll("\\d*$", ""))) + } + } +} \ No newline at end of file diff --git a/src/reflect/scala/reflect/internal/SymbolTable.scala b/src/reflect/scala/reflect/internal/SymbolTable.scala index 4998580a7d..c3f3e35fb3 100644 --- a/src/reflect/scala/reflect/internal/SymbolTable.scala +++ b/src/reflect/scala/reflect/internal/SymbolTable.scala @@ -42,6 +42,7 @@ abstract class SymbolTable extends macros.Universe with BuildUtils with PrivateWithin with pickling.Translations + with FreshNames { val gen = new TreeGen { val global: SymbolTable.this.type = SymbolTable.this } @@ -398,11 +399,6 @@ abstract class SymbolTable extends macros.Universe * Adds the `sm` String interpolator to a [[scala.StringContext]]. */ implicit val StringContextStripMarginOps: StringContext => StringContextStripMarginOps = util.StringContextStripMarginOps - - // fresh name creation - def currentFreshNameCreator: FreshNameCreator - def freshTermName(prefix: String = "x$")(implicit creator: FreshNameCreator): TermName = newTermName(creator.newName(prefix)) - def freshTypeName(prefix: String)(implicit creator: FreshNameCreator): TypeName = newTypeName(creator.newName(prefix)) } object SymbolTableStats { diff --git a/src/reflect/scala/reflect/internal/util/FreshNameCreator.scala b/src/reflect/scala/reflect/internal/util/FreshNameCreator.scala index 3e54de8e1e..8442c1015f 100644 --- a/src/reflect/scala/reflect/internal/util/FreshNameCreator.scala +++ b/src/reflect/scala/reflect/internal/util/FreshNameCreator.scala @@ -11,7 +11,7 @@ import java.util.concurrent.atomic.AtomicLong import scala.collection.mutable import scala.reflect.NameTransformer -class FreshNameCreator { +class FreshNameCreator(creatorPrefix: String = "") { protected val counters = new ConcurrentHashMap[String, AtomicLong]() /** @@ -21,7 +21,8 @@ class FreshNameCreator { */ def newName(prefix: String): String = { val safePrefix = NameTransformer.encode(prefix) - counters.putIfAbsent(safePrefix, new AtomicLong(0)); - safePrefix + counters.get(safePrefix).incrementAndGet(); + counters.putIfAbsent(safePrefix, new AtomicLong(0)) + val idx = counters.get(safePrefix).incrementAndGet() + s"$creatorPrefix$safePrefix$idx" } } diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index bce506ee0a..344f7682c1 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -51,6 +51,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => // inaccessible: this.SimpleNameOrdering this.traceSymbols this.perRunCaches + this.FreshNameExtractor this.FixedMirrorTreeCreator this.FixedMirrorTypeCreator this.CompoundTypeTreeOriginalAttachment diff --git a/test/files/scalacheck/quasiquotes/QuasiquoteProperties.scala b/test/files/scalacheck/quasiquotes/QuasiquoteProperties.scala index b2bce124ee..d398c254fb 100644 --- a/test/files/scalacheck/quasiquotes/QuasiquoteProperties.scala +++ b/test/files/scalacheck/quasiquotes/QuasiquoteProperties.scala @@ -18,13 +18,12 @@ trait Helpers { object simplify extends Transformer { object SimplifiedName { - def unapply[T <: Name](name: T): Option[T] = - name.toString.split("\\$").toSeq match { - case first :+ last if scala.util.Try(last.toInt).isSuccess && first.nonEmpty => - val value = first.mkString("", "$", "$") - Some((if (name.isTermName) TermName(value) else TypeName(value)).asInstanceOf[T]) - case _ => None - } + val st = scala.reflect.runtime.universe.asInstanceOf[scala.reflect.internal.SymbolTable] + val FreshName = new st.FreshNameExtractor + def unapply[T <: Name](name: T): Option[T] = name.asInstanceOf[st.Name] match { + case FreshName(prefix) => + Some((if (name.isTermName) TermName(prefix) else TypeName(prefix)).asInstanceOf[T]) + } } override def transform(tree: Tree): Tree = tree match { diff --git a/test/junit/scala/tools/nsc/symtab/FreshNameExtractorTest.scala b/test/junit/scala/tools/nsc/symtab/FreshNameExtractorTest.scala new file mode 100644 index 0000000000..cf09abdfff --- /dev/null +++ b/test/junit/scala/tools/nsc/symtab/FreshNameExtractorTest.scala @@ -0,0 +1,47 @@ +package scala.tools.nsc +package symtab + +import org.junit.Assert._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +import scala.tools.testing.AssertUtil.assertThrows +import scala.reflect.internal.util.FreshNameCreator + +@RunWith(classOf[JUnit4]) +class FreshNameExtractorTest { + object symbolTable extends SymbolTableForUnitTesting + import symbolTable._ + + val prefixes = List("foo$", "x$", "bar", "bippy$baz$") + + @Test + def extractionPreservesPrefix = + ("" :: prefixes).foreach { creatorPrefix => + prefixes.foreach { newPrefix => + val Creator = new FreshNameCreator(creatorPrefix) + val Extractor = new FreshNameExtractor(creatorPrefix) + val Extractor(extractedPrefix) = TermName(Creator.newName(newPrefix)) + assertEquals(newPrefix, extractedPrefix) + } + } + + @Test + def extractionFailsOnCreatorPrefixMismatch = { + val Creator = new FreshNameCreator(prefixes.head) + val Extractor = new FreshNameExtractor(prefixes.tail.head) + assertThrows[MatchError] { + val Extractor(_) = TermName(Creator.newName("foo")) + } + } + + @Test + def extractionsFailsIfNameDoesntEndWithNumber = { + val Creator = new FreshNameCreator(prefixes.head) + val Extractor = new FreshNameExtractor(prefixes.head) + assertThrows[MatchError] { + val Extractor(_) = TermName(Creator.newName("foo") + "bar") + } + } +} \ No newline at end of file -- cgit v1.2.3