Skip to content

Commit

Permalink
Add support for a more straightforward alternative to import selectors
Browse files Browse the repository at this point in the history
  • Loading branch information
densh committed Feb 7, 2014
1 parent 31c5310 commit c73f309
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 35 deletions.
2 changes: 2 additions & 0 deletions src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala
Expand Up @@ -192,6 +192,8 @@ trait Reifiers { self: Quasiquotes =>
reifyBuildCall(nme.SyntacticIdent, name, isBackquoted)
case SyntacticEmptyTypeTree() =>
reifyBuildCall(nme.SyntacticEmptyTypeTree)
case SyntacticImport(expr, selectors) =>
reifyBuildCall(nme.SyntacticImport, expr, selectors)
case Q(Placeholder(Hole(tree, DotDot))) =>
mirrorBuildCall(nme.SyntacticBlock, tree)
case Q(other) =>
Expand Down
6 changes: 6 additions & 0 deletions src/reflect/scala/reflect/api/BuildUtils.scala
Expand Up @@ -290,5 +290,11 @@ private[reflect] trait BuildUtils { self: Universe =>
def apply(name: Name, isBackquoted: Boolean = false): Ident
def unapply(tree: Ident): Option[(Name, Boolean)]
}

val SyntacticImport: SyntacticImportExtractor
trait SyntacticImportExtractor {
def apply(expr: Tree, selectors: List[Tree]): Import
def unapply(imp: Import): Some[(Tree, List[Tree])]
}
}
}
142 changes: 141 additions & 1 deletion src/reflect/scala/reflect/internal/BuildUtils.scala
Expand Up @@ -143,7 +143,7 @@ trait BuildUtils { self: SymbolTable =>

def RefTree(qual: Tree, sym: Symbol) = self.RefTree(qual, sym.name) setSymbol sym

def freshTermName(prefix: String): TermName = self.freshTermName(prefix)
def freshTermName(prefix: String = nme.FRESH_TERM_NAME_PREFIX): TermName = self.freshTermName(prefix)

def freshTypeName(prefix: String): TypeName = self.freshTypeName(prefix)

Expand Down Expand Up @@ -769,6 +769,146 @@ trait BuildUtils { self: SymbolTable =>
}
def unapply(tree: Ident): Some[(Name, Boolean)] = Some((tree.name, tree.hasAttachment[BackquotedIdentifierAttachment.type]))
}

/** Facade over Imports and ImportSelectors that lets to structurally
* deconstruct/reconstruct them.
*
* Selectors are represented in the following way:
* 1. q"import foo._" <==> q"import foo.${pq"_"}"
* 2. q"import foo.bar" <==> q"import foo.${pq"bar"}"
* 3. q"import foo.{bar => baz}" <==> q"import foo.${pq"bar -> baz"}"
* 4. q"import foo.{bar => _}" <==> q"import foo.${pq"bar -> _"}"
*
* All names in selectors are TermNames despite the fact ImportSelector
* can theoretically contain TypeNames too (but they never do in practice.)
*/
object SyntacticImport extends SyntacticImportExtractor {
// construct/deconstruct {_} import selector
private object WildcardSelector {
def apply(offset: Int): ImportSelector = ImportSelector(nme.WILDCARD, offset, null, -1)
def unapply(sel: ImportSelector): Option[Int] = sel match {
case ImportSelector(nme.WILDCARD, offset, null, -1) => Some(offset)
case _ => None
}
}

// construct/deconstruct {foo} import selector
private object NameSelector {
def apply(name: TermName, offset: Int): ImportSelector = ImportSelector(name, offset, name, offset)
def unapply(sel: ImportSelector): Option[(TermName, Int)] = sel match {
case ImportSelector(name1, offset1, name2, offset2) if name1 == name2 && offset1 == offset2 =>
Some((name1.toTermName, offset1))
case _ =>
None
}
}

// construct/deconstruct {foo => bar} import selector
private object RenameSelector {
def apply(name1: TermName, offset1: Int, name2: TermName, offset2: Int): ImportSelector =
ImportSelector(name1, offset1, name2, offset2)
def unapply(sel: ImportSelector): Option[(TermName, Int, TermName, Int)] = sel match {
case ImportSelector(_, _, null | nme.WILDCARD, _) =>
None
case ImportSelector(name1, offset1, name2, offset2) if name1 != name2 =>
Some((name1.toTermName, offset1, name2.toTermName, offset2))
case _ =>
None
}
}

// construct/deconstruct {foo => _} import selector
private object UnimportSelector {
def apply(name: TermName, offset: Int): ImportSelector =
ImportSelector(name, offset, nme.WILDCARD, -1)
def unapply(sel: ImportSelector): Option[(TermName, Int)] = sel match {
case ImportSelector(name, offset, nme.WILDCARD, _) => Some((name.toTermName, offset))
case _ => None
}
}

// represent {_} import selector as pq"_"
private object WildcardSelectorRepr {
def apply(pos: Position): Tree = atPos(pos)(self.Ident(nme.WILDCARD))
def unapply(tree: Tree): Option[Position] = tree match {
case self.Ident(nme.WILDCARD) => Some(tree.pos)
case _ => None
}
}

// represent {foo} import selector as pq"foo"
private object NameSelectorRepr {
def apply(name: TermName, pos: Position): Tree = atPos(pos)(Bind(name, WildcardSelectorRepr(pos)))
def unapply(tree: Tree): Option[(TermName, Position)] = tree match {
case Bind(name, WildcardSelectorRepr(_)) => Some((name.toTermName, tree.pos))
case _ => None
}
}

// pq"left -> right"
private object Arrow {
def apply(left: Tree, right: Tree): Apply =
Apply(self.Ident(nme.MINGT), left :: right :: Nil)
def unapply(tree: Apply): Option[(Tree, Tree)] = tree match {
case Apply(self.Ident(nme.MINGT), left :: right :: Nil) => Some((left, right))
case _ => None
}
}

// represent {foo => bar} import selector as pq"foo -> bar"
private object RenameSelectorRepr {
def apply(name1: TermName, pos1: Position, name2: TermName, pos2: Position): Tree = {
val left = NameSelectorRepr(name1, pos1)
val right = NameSelectorRepr(name2, pos2)
atPos(wrappingPos(left :: right :: Nil))(Arrow(left, right))
}
def unapply(tree: Tree): Option[(TermName, Position, TermName, Position)] = tree match {
case Arrow(NameSelectorRepr(name1, pos1), NameSelectorRepr(name2, pos2)) =>
Some((name1.toTermName, pos1, name2.toTermName, pos2))
case _ =>
None
}
}

// represent {foo => _} import selector as pq"foo -> _"
private object UnimportSelectorRepr {
def apply(name: TermName, pos: Position): Tree =
atPos(pos)(Arrow(NameSelectorRepr(name, pos), WildcardSelectorRepr(pos)))
def unapply(tree: Tree): Option[(TermName, Position)] = tree match {
case Arrow(NameSelectorRepr(name, pos), WildcardSelectorRepr(_)) =>
Some((name, pos))
case _ =>
None
}
}

private def derivedPos(t: Tree, offset: Int): Position =
if (t.pos == NoPosition) NoPosition else t.pos.withPoint(offset)

private def derivedOffset(pos: Position): Int =
if (pos == NoPosition) -1 else pos.point

def apply(expr: Tree, selectors: List[Tree]): Import = {
val importSelectors = selectors.map {
case WildcardSelectorRepr(pos) => WildcardSelector(derivedOffset(pos))
case NameSelectorRepr(name, pos) => NameSelector(name, derivedOffset(pos))
case RenameSelectorRepr(name1, pos1, name2, pos2) => RenameSelector(name1, derivedOffset(pos1), name2, derivedOffset(pos2))
case UnimportSelectorRepr(name, pos) => UnimportSelector(name, derivedOffset(pos))
case tree => throw new IllegalArgumentException(s"${showRaw(tree)} doesn't correspond to import selector")
}
Import(expr, importSelectors)
}

def unapply(imp: Import): Some[(Tree, List[Tree])] = {
val selectors = imp.selectors.map {
case WildcardSelector(offset) => WildcardSelectorRepr(derivedPos(imp, offset))
case NameSelector(name, offset) => NameSelectorRepr(name, derivedPos(imp, offset))
case RenameSelector(name1, offset1, name2, offset2) => RenameSelectorRepr(name1, derivedPos(imp, offset1), name2, derivedPos(imp, offset2))
case UnimportSelector(name, offset) => UnimportSelectorRepr(name, derivedPos(imp, offset))
}
Some((imp.expr, selectors))
}
}
}

val build: BuildImpl = new BuildImpl
Expand Down
8 changes: 4 additions & 4 deletions src/reflect/scala/reflect/internal/FreshNames.scala
Expand Up @@ -8,7 +8,7 @@ package internal

import scala.reflect.internal.util.FreshNameCreator

trait FreshNames { self: Names =>
trait FreshNames { self: Names with StdNames =>
// SI-6879 Keeps track of counters that are supposed to be globally unique
// as opposed to traditional freshers that are unique to compilation units.
val globalFreshNameCreator = new FreshNameCreator
Expand All @@ -17,8 +17,8 @@ trait FreshNames { self: Names =>
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))
def freshTermName(prefix: String = nme.FRESH_TERM_NAME_PREFIX)(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
Expand All @@ -36,4 +36,4 @@ trait FreshNames { self: Names =>
else Some(NameTransformer.decode(sname.replaceFirst(quotedCreatorPrefix, "").replaceAll("\\d*$", "")))
}
}
}
}
35 changes: 19 additions & 16 deletions src/reflect/scala/reflect/internal/StdNames.scala
Expand Up @@ -295,22 +295,23 @@ trait StdNames {
protected implicit def createNameType(name: String): TermName = newTermNameCached(name)

/** Base strings from which synthetic names are derived. */
val BITMAP_PREFIX = "bitmap$"
val CHECK_IF_REFUTABLE_STRING = "check$ifrefutable$"
val DEFAULT_GETTER_STRING = "$default$"
val DEFAULT_GETTER_INIT_STRING = NameTransformer.encode("<init>") + DEFAULT_GETTER_STRING
val DO_WHILE_PREFIX = "doWhile$"
val EVIDENCE_PARAM_PREFIX = "evidence$"
val EXCEPTION_RESULT_PREFIX = "exceptionResult"
val EXPAND_SEPARATOR_STRING = "$$"
val INTERPRETER_IMPORT_WRAPPER = "$iw"
val LOCALDUMMY_PREFIX = "<local " // owner of local blocks
val PROTECTED_PREFIX = "protected$"
val PROTECTED_SET_PREFIX = PROTECTED_PREFIX + "set"
val SUPER_PREFIX_STRING = "super$"
val WHILE_PREFIX = "while$"
val FRESH_PREFIX = "fresh"
val FRESH_SUFFIX = "macro$" // uses a keyword to avoid collisions with mangled names
val BITMAP_PREFIX = "bitmap$"
val CHECK_IF_REFUTABLE_STRING = "check$ifrefutable$"
val DEFAULT_GETTER_STRING = "$default$"
val DEFAULT_GETTER_INIT_STRING = NameTransformer.encode("<init>") + DEFAULT_GETTER_STRING
val DO_WHILE_PREFIX = "doWhile$"
val EVIDENCE_PARAM_PREFIX = "evidence$"
val EXCEPTION_RESULT_PREFIX = "exceptionResult"
val EXPAND_SEPARATOR_STRING = "$$"
val FRESH_TERM_NAME_PREFIX = "x$"
val INTERPRETER_IMPORT_WRAPPER = "$iw"
val LOCALDUMMY_PREFIX = "<local " // owner of local blocks
val PROTECTED_PREFIX = "protected$"
val PROTECTED_SET_PREFIX = PROTECTED_PREFIX + "set"
val SUPER_PREFIX_STRING = "super$"
val WHILE_PREFIX = "while$"
val FRESH_PREFIX = "fresh"
val FRESH_SUFFIX = "macro$" // uses a keyword to avoid collisions with mangled names

// Compiler internal names
val ANYname: NameType = "<anyname>"
Expand Down Expand Up @@ -609,6 +610,7 @@ trait StdNames {
val SyntacticFunction: NameType = "SyntacticFunction"
val SyntacticFunctionType: NameType = "SyntacticFunctionType"
val SyntacticIdent: NameType = "SyntacticIdent"
val SyntacticImport: NameType = "SyntacticImport"
val SyntacticMatch: NameType = "SyntacticMatch"
val SyntacticNew: NameType = "SyntacticNew"
val SyntacticObjectDef: NameType = "SyntacticObjectDef"
Expand Down Expand Up @@ -839,6 +841,7 @@ trait StdNames {
val LSR = encode(">>>")
val LT = encode("<")
val MINUS = encode("-")
val MINGT = encode("->")
val MOD = encode("%")
val MUL = encode("*")
val NE = encode("!=")
Expand Down
Expand Up @@ -8,7 +8,8 @@ object DefinitionConstructionProps
with TypeDefConstruction
with ValDefConstruction
with DefConstruction
with PackageConstruction {
with PackageConstruction
with ImportConstruction {
property("SI-6842") = test {
val x: Tree = q"val x: Int"
assertEqAst(q"def f($x) = 0", "def f(x: Int) = 0")
Expand Down Expand Up @@ -363,3 +364,30 @@ trait DefConstruction { self: QuasiquoteProperties =>
assertEqAst(q"def foo(implicit ..$xs) = x1 + x2", "def foo(implicit x1: Int, x2: Long) = x1 + x2")
}
}

trait ImportConstruction { self: QuasiquoteProperties =>
property("construct wildcard import") = test {
val sel = pq"_"
assert(q"import foo.$sel"q"import foo._")
}

property("construct named import") = test {
val sel = pq"bar"
assert(q"import foo.$sel"q"import foo.bar")
}

property("construct renaming import") = test {
val sel = pq"bar -> baz"
assert(q"import foo.$sel"q"import foo.{bar => baz}")
}

property("construct unimport import") = test {
val sels = pq"poison -> _" :: pq"_" :: Nil
assert(q"import foo.{..$sels}"q"import foo.{poison => _, _}")
}

property("construct mixed import") = test {
val sels = pq"a -> b" :: pq"c -> _" :: pq"_" :: Nil
assert(q"import foo.{..$sels}"q"import foo.{a => b, c => _, _}")
}
}
Expand Up @@ -10,6 +10,7 @@ object DefinitionDeconstructionProps
with ValVarDeconstruction
with DefDeconstruction
with PackageDeconstruction
with ImportDeconstruction

trait TraitDeconstruction { self: QuasiquoteProperties =>
property("exhaustive trait matcher") = test {
Expand Down Expand Up @@ -219,3 +220,55 @@ trait DefDeconstruction { self: QuasiquoteProperties =>
assert(impl.isEmpty)
}
}

trait ImportDeconstruction { self: QuasiquoteProperties =>
property("exhaustive import matcher") = test {
def matches(line: String) = {
val q"import $ref.{..$sels}" = parse(line)
}
matches("import foo.bar")
matches("import foo.{bar, baz}")
matches("import foo.{a => b, c => d}")
matches("import foo.{poision => _, _}")
matches("import foo.bar.baz._")
}

property("extract import binding") = test {
val q"import $_.$sel" = q"import foo.bar"
val pq"bar" = sel
}

property("extract import wildcard") = test {
val q"import $_.$sel" = q"import foo._"
val pq"_" = sel
}

property("extract import rename") = test {
val q"import $_.$sel" = q"import foo.{bar => baz}"
val pq"bar -> baz" = sel
val pq"$left -> $right" = sel
val pq"bar" = left
val pq"baz" = right
}

property("extract import unimport") = test {
val q"import $_.$sel" = q"import foo.{bar => _}"
val pq"bar -> _" = sel
val pq"$left -> $right" = sel
val pq"bar" = left
val pq"_" = right
}

property("splice names into import selector") = forAll {
(expr: Tree, plain: TermName, oldname: TermName, newname: TermName, discard: TermName) =>

val Import(expr1, List(
ImportSelector(plain11, _, plain12, _),
ImportSelector(oldname1, _, newname1, _),
ImportSelector(discard1, _, wildcard, _))) =
q"import $expr.{$plain, $oldname => $newname, $discard => _}"

expr1 ≈ expr && plain11 == plain12 && plain12 == plain &&
oldname1 == oldname && newname1 == newname && discard1 == discard && wildcard == nme.WILDCARD
}
}
13 changes: 0 additions & 13 deletions test/files/scalacheck/quasiquotes/TermConstructionProps.scala
Expand Up @@ -85,19 +85,6 @@ object TermConstructionProps extends QuasiquoteProperties("term construction") {
q"$fun[..$types]" ≈ (if (types.nonEmpty) TypeApply(fun, types) else fun)
}

property("splice names into import selector") = forAll {
(expr: Tree, plain: Name, oldname: Name, newname: Name, discard: Name) =>

val Import(expr1, List(
ImportSelector(plain11, _, plain12, _),
ImportSelector(oldname1, _, newname1, _),
ImportSelector(discard1, _, wildcard, _))) =
q"import $expr.{$plain, $oldname => $newname, $discard => _}"

expr1 ≈ expr && plain11 == plain12 && plain12 == plain &&
oldname1 == oldname && newname1 == newname && discard1 == discard && wildcard == nme.WILDCARD
}

property("splice trees into while loop") = forAll { (cond: Tree, body: Tree) =>
val LabelDef(_, List(), If(cond1, Block(List(body1), Apply(_, List())), Literal(Constant(())))) = q"while($cond) $body"
body1 ≈ body && cond1 ≈ cond
Expand Down

0 comments on commit c73f309

Please sign in to comment.