summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugene Burmako <xeno.by@gmail.com>2012-12-25 02:37:31 +0100
committerEugene Burmako <xeno.by@gmail.com>2013-01-05 01:39:15 +0300
commit348c8fac9f897f9661f84e32949b8a4e0c99e93a (patch)
tree810b2d7d53451ceba5e43e1687a86cdedffd39f1
parented4f4798df2ac104d9557de2241d8e89283b0662 (diff)
downloadscala-348c8fac9f897f9661f84e32949b8a4e0c99e93a.tar.gz
scala-348c8fac9f897f9661f84e32949b8a4e0c99e93a.tar.bz2
scala-348c8fac9f897f9661f84e32949b8a4e0c99e93a.zip
adds c.introduceTopLevel
The first in the family of mutators for the global symbol table, `introduceTopLevel` is capable of creating synthetic top-level classes and modules. The addition of nme.EMPTY_PACKAGE_NAME is necessary to let programmers insert definitions into the empty package. That's explicitly discouraged in the docs, but at times might come in handy. This patch introduce workarounds to avoid incompatibilities with SBT. First of all SBT doesn't like VirtualFiles having JFile set to null. Secondly SBT gets confused when someone depends on synthetic files added by c.introduceTopLevel. Strictly speaking these problems require changes to SBT, and that will be done later. However the main target of the patch is paradise/macros, which needs to be useful immediately, therefore we apply workarounds.
-rw-r--r--src/compiler/scala/reflect/macros/runtime/Context.scala1
-rw-r--r--src/compiler/scala/reflect/macros/runtime/Synthetics.scala83
-rw-r--r--src/compiler/scala/tools/nsc/CompilationUnits.scala24
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala12
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala2
-rw-r--r--src/reflect/scala/reflect/api/StandardNames.scala5
-rw-r--r--src/reflect/scala/reflect/internal/Mirrors.scala2
-rw-r--r--src/reflect/scala/reflect/internal/TreeGen.scala21
-rw-r--r--src/reflect/scala/reflect/io/AbstractFile.scala3
-rw-r--r--src/reflect/scala/reflect/io/NoAbstractFile.scala1
-rw-r--r--src/reflect/scala/reflect/io/VirtualDirectory.scala1
-rw-r--r--src/reflect/scala/reflect/io/VirtualFile.scala5
-rw-r--r--src/reflect/scala/reflect/macros/Context.scala3
-rw-r--r--src/reflect/scala/reflect/macros/Synthetics.scala106
-rw-r--r--src/reflect/scala/reflect/macros/TreeBuilder.scala16
-rw-r--r--test/files/pos/annotated-treecopy/Impls_Macros_1.scala2
-rw-r--r--test/files/pos/attachments-typed-another-ident/Impls_1.scala2
-rw-r--r--test/files/pos/attachments-typed-ident/Impls_1.scala2
-rw-r--r--test/files/run/macro-duplicate/Impls_Macros_1.scala6
-rw-r--r--test/files/run/macro-toplevel-companion-a.check0
-rw-r--r--test/files/run/macro-toplevel-companion-a.flags1
-rw-r--r--test/files/run/macro-toplevel-companion-a/Impls_Macros_1.scala14
-rw-r--r--test/files/run/macro-toplevel-companion-a/Test_2.scala8
-rw-r--r--test/files/run/macro-toplevel-companion-b.check4
-rw-r--r--test/files/run/macro-toplevel-companion-b.flags1
-rw-r--r--test/files/run/macro-toplevel-companion-b/Impls_Macros_1.scala15
-rw-r--r--test/files/run/macro-toplevel-companion-b/Test_2.scala11
-rw-r--r--test/files/run/macro-toplevel-companion-c.check3
-rw-r--r--test/files/run/macro-toplevel-companion-c.flags1
-rw-r--r--test/files/run/macro-toplevel-companion-c.scala51
-rw-r--r--test/files/run/macro-toplevel.check2
-rw-r--r--test/files/run/macro-toplevel/Macros_1.scala15
-rw-r--r--test/files/run/macro-toplevel/Test_2.scala6
-rw-r--r--test/files/run/t6548.scala2
34 files changed, 404 insertions, 27 deletions
diff --git a/src/compiler/scala/reflect/macros/runtime/Context.scala b/src/compiler/scala/reflect/macros/runtime/Context.scala
index 8e8b0fcea1..76c684f6d7 100644
--- a/src/compiler/scala/reflect/macros/runtime/Context.scala
+++ b/src/compiler/scala/reflect/macros/runtime/Context.scala
@@ -14,6 +14,7 @@ abstract class Context extends scala.reflect.macros.Context
with Parsers
with Evals
with ExprUtils
+ with Synthetics
with Traces {
val universe: Global
diff --git a/src/compiler/scala/reflect/macros/runtime/Synthetics.scala b/src/compiler/scala/reflect/macros/runtime/Synthetics.scala
new file mode 100644
index 0000000000..73f3ab8d20
--- /dev/null
+++ b/src/compiler/scala/reflect/macros/runtime/Synthetics.scala
@@ -0,0 +1,83 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2013 LAMP/EPFL
+ */
+
+package scala.reflect.macros
+package runtime
+
+import java.util.UUID._
+import scala.reflect.internal.Flags._
+import scala.reflect.internal.util.BatchSourceFile
+import scala.reflect.io.VirtualFile
+
+trait Synthetics {
+ self: Context =>
+
+ import global._
+ import mirror.wrapMissing
+
+ // getClassIfDefined and getModuleIfDefined cannot be used here
+ // because they don't work for stuff declared in the empty package
+ // (as specified in SLS, code inside non-empty packages cannot see
+ // declarations from the empty package, so compiler internals
+ // default to ignoring contents of the empty package)
+ // to the contrast, staticModule and staticClass are designed
+ // to be a part of the reflection API and, therefore, they
+ // correctly resolve all names
+ private def topLevelSymbol(name: Name): Symbol = wrapMissing {
+ if (name.isTermName) mirror.staticModule(name.toString)
+ else mirror.staticClass(name.toString)
+ }
+
+ def topLevelDef(name: Name): Tree =
+ enclosingRun.units.toList.map(_.body).flatMap {
+ // it's okay to check `stat.symbol` here, because currently macros expand strictly after namer
+ // which means that by the earliest time one can call this method all top-level definitions will have already been entered
+ case PackageDef(_, stats) => stats filter (stat => stat.symbol != NoSymbol && stat.symbol == topLevelSymbol(name))
+ case _ => Nil // should never happen, but better be safe than sorry
+ }.headOption getOrElse EmptyTree
+
+ def topLevelRef(name: Name): Tree = {
+ if (topLevelDef(name).nonEmpty) gen.mkUnattributedRef(name)
+ else EmptyTree
+ }
+
+ // TODO: provide a way to specify a pretty name for debugging purposes
+ private def randomFileName() = (
+ "macroSynthetic-" + randomUUID().toString.replace("-", "") + ".scala"
+ )
+
+ def introduceTopLevel[T: PackageSpec](packagePrototype: T, definition: universe.ImplDef): RefTree =
+ introduceTopLevel(packagePrototype, List(definition)).head
+
+ def introduceTopLevel[T: PackageSpec](packagePrototype: T, definitions: universe.ImplDef*): List[RefTree] =
+ introduceTopLevel(packagePrototype, definitions.toList)
+
+ private def introduceTopLevel[T: PackageSpec](packagePrototype: T, definitions: List[universe.ImplDef]): List[RefTree] = {
+ val code @ PackageDef(pid, _) = implicitly[PackageSpec[T]].mkPackageDef(packagePrototype, definitions)
+ val syntheticFileName = randomFileName()
+ // compatibility with SBT
+ // on the one hand, we need to specify some jfile here, otherwise sbt crashes with an NPE (SI-6870)
+ // on the other hand, we can't specify the obvious enclosingUnit, because then sbt somehow fails to run tests using type macros
+ // okay, now let's specify a guaranteedly non-existent file in an existing directory (so that we don't run into permission problems)
+ val relatedJfile = enclosingUnit.source.file.file
+ val fakeJfile = if (relatedJfile != null) new java.io.File(relatedJfile.getParent, syntheticFileName) else null
+ val virtualFile = new VirtualFile(syntheticFileName) { override def file = fakeJfile }
+ val sourceFile = new BatchSourceFile(virtualFile, code.toString)
+ val unit = new CompilationUnit(sourceFile)
+ unit.body = code
+ universe.currentRun.compileLate(unit)
+ definitions map (definition => Select(pid, definition.name))
+ }
+
+ protected def mkPackageDef(name: String, stats: List[Tree]) = gen.mkPackageDef(name, stats)
+
+ protected def mkPackageDef(name: TermName, stats: List[Tree]) = gen.mkPackageDef(name.toString, stats)
+
+ protected def mkPackageDef(tree: RefTree, stats: List[Tree]) = PackageDef(tree, stats)
+
+ protected def mkPackageDef(sym: Symbol, stats: List[Tree]) = {
+ assert(sym hasFlag PACKAGE, s"expected a package or package class symbol, found: $sym")
+ gen.mkPackageDef(sym.fullName.toString, stats)
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/CompilationUnits.scala b/src/compiler/scala/tools/nsc/CompilationUnits.scala
index a2108b8ced..15d365ab8c 100644
--- a/src/compiler/scala/tools/nsc/CompilationUnits.scala
+++ b/src/compiler/scala/tools/nsc/CompilationUnits.scala
@@ -39,11 +39,31 @@ trait CompilationUnits { self: Global =>
/** Note: depends now contains toplevel classes.
* To get their sourcefiles, you need to dereference with .sourcefile
*/
- val depends = mutable.HashSet[Symbol]()
+ private[this] val _depends = mutable.HashSet[Symbol]()
+ // SBT compatibility (SI-6875)
+ //
+ // imagine we have a file named A.scala, which defines a trait named Foo and a module named Main
+ // Main contains a call to a macro, which calls c.introduceTopLevel to define a mock for Foo
+ // c.introduceTopLevel creates a virtual file Virt35af32.scala, which contains a class named FooMock extending Foo,
+ // and macro expansion instantiates FooMock. the stage is now set. let's see what happens next.
+ //
+ // without this workaround in scalac or without being patched itself, sbt will think that
+ // * Virt35af32 depends on A (because it extends Foo from A)
+ // * A depends on Virt35af32 (because it contains a macro expansion referring to FooMock from Virt35af32)
+ //
+ // after compiling A.scala, SBT will notice that it has a new source file named Virt35af32.
+ // it will also think that this file hasn't yet been compiled and since A depends on it
+ // it will think that A needs to be recompiled.
+ //
+ // recompilation will lead to another macro expansion. that another macro expansion might choose to create a fresh mock,
+ // producing another virtual file, say, Virtee509a, which will again trick SBT into thinking that A needs a recompile,
+ // which will lead to another macro expansion, which will produce another virtual file and so on
+ def depends = if (exists && !source.file.isVirtual) _depends else mutable.HashSet[Symbol]()
/** so we can relink
*/
- val defined = mutable.HashSet[Symbol]()
+ private[this] val _defined = mutable.HashSet[Symbol]()
+ def defined = if (exists && !source.file.isVirtual) _defined else mutable.HashSet[Symbol]()
/** Synthetic definitions generated by namer, eliminated by typer.
*/
diff --git a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala
index 8a9ce8907e..f1bf590ebf 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala
@@ -23,10 +23,14 @@ abstract class SyntaxAnalyzer extends SubComponent with Parsers with MarkupParse
def apply(unit: global.CompilationUnit) {
import global._
informProgress("parsing " + unit)
- unit.body =
- if (unit.isJava) new JavaUnitParser(unit).parse()
- else if (reporter.incompleteHandled) new UnitParser(unit).parse()
- else new UnitParser(unit).smartParse()
+ // if the body is already filled in, do nothing
+ // otherwise compileLate is going to overwrite bodies of synthetic source files
+ if (unit.body == EmptyTree) {
+ unit.body =
+ if (unit.isJava) new JavaUnitParser(unit).parse()
+ else if (reporter.incompleteHandled) new UnitParser(unit).parse()
+ else new UnitParser(unit).smartParse()
+ }
if (settings.Yrangepos.value && !reporter.hasErrors)
validatePositions(unit.body)
diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala
index 14c8d85836..dfc1196f2e 100644
--- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala
@@ -394,7 +394,7 @@ trait NamesDefaults { self: Analyzer =>
// TODO #3649 can create spurious errors when companion object is gone (because it becomes unlinked from scope)
if (defGetter == NoSymbol) None // prevent crash in erroneous trees, #3649
else {
- var default1 = qual match {
+ var default1: Tree = qual match {
case Some(q) => gen.mkAttributedSelect(q.duplicate, defGetter)
case None => gen.mkAttributedRef(defGetter)
diff --git a/src/reflect/scala/reflect/api/StandardNames.scala b/src/reflect/scala/reflect/api/StandardNames.scala
index 4886e4f8f7..6c78f18716 100644
--- a/src/reflect/scala/reflect/api/StandardNames.scala
+++ b/src/reflect/scala/reflect/api/StandardNames.scala
@@ -84,6 +84,11 @@ trait StandardNames {
*/
val ROOTPKG: NameType
+ /** The term name `<empty>`.
+ * Represents the empty package.
+ */
+ val EMPTY_PACKAGE_NAME: NameType
+
/** The string " " (a single whitespace).
* `LOCAL_SUFFIX_STRING` is appended to the names of local identifiers,
* when it's necessary to prevent a naming conflict. For example, underlying fields
diff --git a/src/reflect/scala/reflect/internal/Mirrors.scala b/src/reflect/scala/reflect/internal/Mirrors.scala
index 6e76a7afb3..d9f1d90b62 100644
--- a/src/reflect/scala/reflect/internal/Mirrors.scala
+++ b/src/reflect/scala/reflect/internal/Mirrors.scala
@@ -207,7 +207,7 @@ trait Mirrors extends api.Mirrors {
erasureString(classTag[T].runtimeClass)
}
- @inline private def wrapMissing(body: => Symbol): Symbol =
+ @inline final def wrapMissing(body: => Symbol): Symbol =
try body
catch { case _: MissingRequirementError => NoSymbol }
diff --git a/src/reflect/scala/reflect/internal/TreeGen.scala b/src/reflect/scala/reflect/internal/TreeGen.scala
index 0954432c77..6a2006e56f 100644
--- a/src/reflect/scala/reflect/internal/TreeGen.scala
+++ b/src/reflect/scala/reflect/internal/TreeGen.scala
@@ -113,7 +113,7 @@ abstract class TreeGen extends macros.TreeBuilder {
}
/** Builds a reference to given symbol with given stable prefix. */
- def mkAttributedRef(pre: Type, sym: Symbol): Tree = {
+ def mkAttributedRef(pre: Type, sym: Symbol): RefTree = {
val qual = mkAttributedQualifier(pre)
qual match {
case EmptyTree => mkAttributedIdent(sym)
@@ -123,10 +123,17 @@ abstract class TreeGen extends macros.TreeBuilder {
}
/** Builds a reference to given symbol. */
- def mkAttributedRef(sym: Symbol): Tree =
+ def mkAttributedRef(sym: Symbol): RefTree =
if (sym.owner.isClass) mkAttributedRef(sym.owner.thisType, sym)
else mkAttributedIdent(sym)
+ def mkUnattributedRef(sym: Symbol): RefTree = mkUnattributedRef(sym.fullNameAsName('.'))
+
+ def mkUnattributedRef(fullName: Name): RefTree = {
+ val hd :: tl = nme.segments(fullName.toString, assumeTerm = fullName.isTermName)
+ tl.foldLeft(Ident(hd): RefTree)(Select(_,_))
+ }
+
/** Replaces tree type with a stable type if possible */
def stabilize(tree: Tree): Tree = stableTypeFor(tree) match {
case Some(tp) => tree setType tp
@@ -153,13 +160,13 @@ abstract class TreeGen extends macros.TreeBuilder {
def mkAttributedStableRef(sym: Symbol): Tree =
stabilize(mkAttributedRef(sym))
- def mkAttributedThis(sym: Symbol): Tree =
+ def mkAttributedThis(sym: Symbol): This =
This(sym.name.toTypeName) setSymbol sym setType sym.thisType
- def mkAttributedIdent(sym: Symbol): Tree =
+ def mkAttributedIdent(sym: Symbol): RefTree =
Ident(sym.name) setSymbol sym setType sym.tpeHK
- def mkAttributedSelect(qual: Tree, sym: Symbol): Tree = {
+ def mkAttributedSelect(qual: Tree, sym: Symbol): RefTree = {
// Tests involving the repl fail without the .isEmptyPackage condition.
if (qual.symbol != null && (qual.symbol.isEffectiveRoot || qual.symbol.isEmptyPackage))
mkAttributedIdent(sym)
@@ -283,4 +290,8 @@ abstract class TreeGen extends macros.TreeBuilder {
assert(ReflectRuntimeUniverse != NoSymbol)
mkAttributedRef(ReflectRuntimeUniverse) setType singleType(ReflectRuntimeUniverse.owner.thisPrefix, ReflectRuntimeUniverse)
}
+
+ def mkPackageDef(packageName: String, stats: List[Tree]): PackageDef = {
+ PackageDef(mkUnattributedRef(newTermName(packageName)), stats)
+ }
}
diff --git a/src/reflect/scala/reflect/io/AbstractFile.scala b/src/reflect/scala/reflect/io/AbstractFile.scala
index 1a8d1c4f5e..bd6c186825 100644
--- a/src/reflect/scala/reflect/io/AbstractFile.scala
+++ b/src/reflect/scala/reflect/io/AbstractFile.scala
@@ -124,6 +124,9 @@ abstract class AbstractFile extends Iterable[AbstractFile] {
/** Is this abstract file a directory? */
def isDirectory: Boolean
+ /** Does this abstract file correspond to something on-disk? */
+ def isVirtual: Boolean = false
+
/** Returns the time that this abstract file was last modified. */
def lastModified: Long
diff --git a/src/reflect/scala/reflect/io/NoAbstractFile.scala b/src/reflect/scala/reflect/io/NoAbstractFile.scala
index 8c88d3abf6..2c59fd8aae 100644
--- a/src/reflect/scala/reflect/io/NoAbstractFile.scala
+++ b/src/reflect/scala/reflect/io/NoAbstractFile.scala
@@ -22,6 +22,7 @@ object NoAbstractFile extends AbstractFile {
def file: JFile = null
def input: InputStream = null
def isDirectory: Boolean = false
+ override def isVirtual: Boolean = true
def iterator: Iterator[AbstractFile] = Iterator.empty
def lastModified: Long = 0L
def lookupName(name: String, directory: Boolean): AbstractFile = null
diff --git a/src/reflect/scala/reflect/io/VirtualDirectory.scala b/src/reflect/scala/reflect/io/VirtualDirectory.scala
index 94cb52e9b5..589076d693 100644
--- a/src/reflect/scala/reflect/io/VirtualDirectory.scala
+++ b/src/reflect/scala/reflect/io/VirtualDirectory.scala
@@ -26,6 +26,7 @@ extends AbstractFile {
def container = maybeContainer.get
def isDirectory = true
+ override def isVirtual = true
val lastModified: Long = System.currentTimeMillis
override def file = null
diff --git a/src/reflect/scala/reflect/io/VirtualFile.scala b/src/reflect/scala/reflect/io/VirtualFile.scala
index 09b977bd45..0dfa7d5473 100644
--- a/src/reflect/scala/reflect/io/VirtualFile.scala
+++ b/src/reflect/scala/reflect/io/VirtualFile.scala
@@ -37,7 +37,7 @@ class VirtualFile(val name: String, override val path: String) extends AbstractF
def absolute = this
/** Returns null. */
- final def file: JFile = null
+ def file: JFile = null
override def sizeOption: Option[Int] = Some(content.size)
@@ -57,6 +57,9 @@ class VirtualFile(val name: String, override val path: String) extends AbstractF
/** Is this abstract file a directory? */
def isDirectory: Boolean = false
+ /** @inheritdoc */
+ override def isVirtual: Boolean = true
+
/** Returns the time that this abstract file was last modified. */
private var _lastModified: Long = 0
def lastModified: Long = _lastModified
diff --git a/src/reflect/scala/reflect/macros/Context.scala b/src/reflect/scala/reflect/macros/Context.scala
index aa1c1db227..1adc6928da 100644
--- a/src/reflect/scala/reflect/macros/Context.scala
+++ b/src/reflect/scala/reflect/macros/Context.scala
@@ -36,7 +36,8 @@ trait Context extends Aliases
with Typers
with Parsers
with Evals
- with ExprUtils {
+ with ExprUtils
+ with Synthetics {
/** The compile-time universe. */
val universe: Universe
diff --git a/src/reflect/scala/reflect/macros/Synthetics.scala b/src/reflect/scala/reflect/macros/Synthetics.scala
new file mode 100644
index 0000000000..14c6c930b3
--- /dev/null
+++ b/src/reflect/scala/reflect/macros/Synthetics.scala
@@ -0,0 +1,106 @@
+package scala.reflect
+package macros
+
+/**
+ * <span class="badge badge-red" style="float: right;">EXPERIMENTAL</span>
+ *
+ * A slice of [[scala.reflect.macros.Context the Scala macros context]] that
+ * exposes functions to introduce synthetic definitions.
+ *
+ * @define TOPLEVEL_TREE Top-level tree is a tree that represents a non-inner class or object in one of the currently compiled source files.
+ * Note that top-level isn't equivalent to [[scala.reflect.api.Symbols#SymbolApi.isStatic]],
+ * because static also embraces definitions nested in static objects
+ *
+ * @define INTRODUCE_TOP_LEVEL Allowed definitions include classes (represented by `ClassDef` trees), traits (represented
+ * by `ClassDef` trees having the `TRAIT` flag set in `mods`) and objects (represented by `ModuleDef` trees).
+ *
+ * The definitions are put into the package with a prototype provided in `packagePrototype`.
+ * Supported prototypes are (see [[PackageSpec]] for more details):
+ * * Strings and names representing a fully-qualified name of the package
+ * * Trees that can work as package ids
+ * * Package or package class symbols
+ *
+ * Typical value for a package prototype is a fully-qualified name in a string.
+ * For example, to generate a class available at `foo.bar.Test`, call this method as follows:
+ *
+ * introduceTopLevel("foo.bar", ClassDef(<mods>, TypeName("Test"), <tparams>, <template>))
+ *
+ * It is possible to add definitions to the empty package by using `nme.EMPTY_PACKAGE_NAME.toString`, but
+ * that's not recommended, since such definitions cannot be seen from outside the empty package.
+ *
+ * Only the multi-parameter overload of this method can be used to introduce companions.
+ * If companions are introduced by two different calls, then they will be put into different virtual files, and `scalac`
+ * will show an error about companions being defined in different files. By the way, this also means that there's currently no way
+ * to define a companion for an existing class or module
+ */
+trait Synthetics {
+ self: Context =>
+
+ import universe._
+
+ /** Looks up a top-level definition tree with a given fully-qualified name
+ * (term name for modules, type name for classes). $TOPLEVEL_TREE.
+ * If such a tree does not exist, returns `EmptyTree`.
+ */
+ def topLevelDef(name: Name): Tree
+
+ /** Returns a reference to a top-level definition tree with a given fully-qualified name
+ * (term name for modules, type name for classes). $TOPLEVEL_TREE.
+ * If such a tree does not exist, returns `EmptyTree`.
+ */
+ def topLevelRef(name: Name): Tree
+
+ /** Adds a top-level definition to the compiler's symbol table. $INTRODUCE_TOP_LEVEL.
+ *
+ * Returns a fully-qualified reference to the introduced definition.
+ */
+ def introduceTopLevel[T: PackageSpec](packagePrototype: T, definition: ImplDef): RefTree
+
+ /** Adds a list of top-level definitions to the compiler's symbol table. $INTRODUCE_TOP_LEVEL.
+ *
+ * Returns a list of fully-qualified references to the introduced definitions.
+ */
+ def introduceTopLevel[T: PackageSpec](packagePrototype: T, definitions: ImplDef*): List[RefTree]
+
+ /** A factory which can create a package def from a prototype and a list of declarations.
+ */
+ trait PackageSpec[T] { def mkPackageDef(prototype: T, stats: List[Tree]): PackageDef }
+
+ /** Hosts supported package specs.
+ */
+ object PackageSpec {
+ /** Package def can be created from a fully-qualified name and a list of definitions.
+ * The name is converted into an Ident or a chain of Selects.
+ */
+ implicit val stringIsPackageSpec = new PackageSpec[String] {
+ def mkPackageDef(prototype: String, stats: List[Tree]): PackageDef = self.mkPackageDef(prototype, stats)
+ }
+
+ /** Package def can be created from a fully-qualified term name and a list of definitions.
+ * The name is converted into an Ident or a chain of Selects.
+ */
+ implicit val termNameIsPackageSpec = new PackageSpec[TermName] {
+ def mkPackageDef(prototype: TermName, stats: List[Tree]): PackageDef = self.mkPackageDef(prototype, stats)
+ }
+
+ /** Package def can be created from a package id tree and a list of definitions.
+ * If the tree is not a valid package id, i.e. is not a term-name ident or a chain of term-name selects,
+ * then the produced PackageDef will fail compilation at some point in the future.
+ */
+ implicit val refTreeIsPackageSpec = new PackageSpec[RefTree] {
+ def mkPackageDef(prototype: RefTree, stats: List[Tree]): PackageDef = self.mkPackageDef(prototype, stats)
+ }
+
+ /** Package def can be created from a package/package class symbol and a list of definitions.
+ * If the provided symbol is not a package symbol or a package class symbol, package construction will throw an exception.
+ */
+ implicit val SymbolIsPackageSpec = new PackageSpec[Symbol] {
+ def mkPackageDef(prototype: Symbol, stats: List[Tree]): PackageDef = self.mkPackageDef(prototype, stats)
+ }
+ }
+
+ protected def mkPackageDef(name: String, stats: List[Tree]): PackageDef
+ protected def mkPackageDef(name: TermName, stats: List[Tree]): PackageDef
+ protected def mkPackageDef(tree: RefTree, stats: List[Tree]): PackageDef
+ protected def mkPackageDef(sym: Symbol, stats: List[Tree]): PackageDef
+}
diff --git a/src/reflect/scala/reflect/macros/TreeBuilder.scala b/src/reflect/scala/reflect/macros/TreeBuilder.scala
index fbbbe13201..bdd5dc8a96 100644
--- a/src/reflect/scala/reflect/macros/TreeBuilder.scala
+++ b/src/reflect/scala/reflect/macros/TreeBuilder.scala
@@ -27,19 +27,25 @@ abstract class TreeBuilder {
def mkAttributedQualifier(tpe: Type, termSym: Symbol): Tree
/** Builds a typed reference to given symbol with given stable prefix. */
- def mkAttributedRef(pre: Type, sym: Symbol): Tree
+ def mkAttributedRef(pre: Type, sym: Symbol): RefTree
/** Builds a typed reference to given symbol. */
- def mkAttributedRef(sym: Symbol): Tree
+ def mkAttributedRef(sym: Symbol): RefTree
+
+ /** Builds an untyped reference to given symbol. Requires the symbol to be static. */
+ def mkUnattributedRef(sym: Symbol): RefTree
+
+ /** Builds an untyped reference to symbol with given name. Requires the symbol to be static. */
+ def mkUnattributedRef(fullName: Name): RefTree
/** Builds a typed This reference to given symbol. */
- def mkAttributedThis(sym: Symbol): Tree
+ def mkAttributedThis(sym: Symbol): This
/** Builds a typed Ident with an underlying symbol. */
- def mkAttributedIdent(sym: Symbol): Tree
+ def mkAttributedIdent(sym: Symbol): RefTree
/** Builds a typed Select with an underlying symbol. */
- def mkAttributedSelect(qual: Tree, sym: Symbol): Tree
+ def mkAttributedSelect(qual: Tree, sym: Symbol): RefTree
/** A creator for method calls, e.g. fn[T1, T2, ...](v1, v2, ...)
* There are a number of variations.
diff --git a/test/files/pos/annotated-treecopy/Impls_Macros_1.scala b/test/files/pos/annotated-treecopy/Impls_Macros_1.scala
index d92fbca380..cf58bc3dfd 100644
--- a/test/files/pos/annotated-treecopy/Impls_Macros_1.scala
+++ b/test/files/pos/annotated-treecopy/Impls_Macros_1.scala
@@ -21,7 +21,7 @@ object Macros {
// normalize argument name
var b1 = new Transformer {
override def transform(tree: Tree): Tree = tree match {
- case Ident(x) if (x==n) => Ident(newTermName("_arg"))
+ case Ident(x) if (x==n) => Ident(TermName("_arg"))
case tt @ TypeTree() if tt.original != null => TypeTree(tt.tpe) setOriginal transform(tt.original)
// without the fix to LazyTreeCopier.Annotated, we would need to uncomment the line below to make the macro work
// that's because the pattern match in the input expression gets expanded into Typed(<x>, TypeTree(<Int @unchecked>))
diff --git a/test/files/pos/attachments-typed-another-ident/Impls_1.scala b/test/files/pos/attachments-typed-another-ident/Impls_1.scala
index 957bafc6ae..c3f541075e 100644
--- a/test/files/pos/attachments-typed-another-ident/Impls_1.scala
+++ b/test/files/pos/attachments-typed-another-ident/Impls_1.scala
@@ -6,7 +6,7 @@ object MyAttachment
object Macros {
def impl(c: Context) = {
import c.universe._
- val ident = Ident(newTermName("bar")) updateAttachment MyAttachment
+ val ident = Ident(TermName("bar")) updateAttachment MyAttachment
assert(ident.attachments.get[MyAttachment.type].isDefined, ident.attachments)
val typed = c.typeCheck(ident)
assert(typed.attachments.get[MyAttachment.type].isDefined, typed.attachments)
diff --git a/test/files/pos/attachments-typed-ident/Impls_1.scala b/test/files/pos/attachments-typed-ident/Impls_1.scala
index cc40893a93..c382cabc59 100644
--- a/test/files/pos/attachments-typed-ident/Impls_1.scala
+++ b/test/files/pos/attachments-typed-ident/Impls_1.scala
@@ -6,7 +6,7 @@ object MyAttachment
object Macros {
def impl(c: Context) = {
import c.universe._
- val ident = Ident(newTermName("bar")) updateAttachment MyAttachment
+ val ident = Ident(TermName("bar")) updateAttachment MyAttachment
assert(ident.attachments.get[MyAttachment.type].isDefined, ident.attachments)
val typed = c.typeCheck(ident)
assert(typed.attachments.get[MyAttachment.type].isDefined, typed.attachments)
diff --git a/test/files/run/macro-duplicate/Impls_Macros_1.scala b/test/files/run/macro-duplicate/Impls_Macros_1.scala
index de81923330..af80147a90 100644
--- a/test/files/run/macro-duplicate/Impls_Macros_1.scala
+++ b/test/files/run/macro-duplicate/Impls_Macros_1.scala
@@ -10,11 +10,11 @@ object Macros {
case Template(_, _, ctor :: defs) =>
val defs1 = defs collect {
case ddef @ DefDef(mods, name, tparams, vparamss, tpt, body) =>
- val future = Select(Select(Select(Ident(newTermName("scala")), newTermName("concurrent")), newTermName("package")), newTermName("future"))
- val Future = Select(Select(Ident(newTermName("scala")), newTermName("concurrent")), newTypeName("Future"))
+ val future = Select(Select(Select(Ident(TermName("scala")), TermName("concurrent")), TermName("package")), TermName("future"))
+ val Future = Select(Select(Ident(TermName("scala")), TermName("concurrent")), TypeName("Future"))
val tpt1 = if (tpt.isEmpty) tpt else AppliedTypeTree(Future, List(tpt))
val body1 = Apply(future, List(body))
- val name1 = newTermName("async" + name.toString.capitalize)
+ val name1 = TermName("async" + name.toString.capitalize)
DefDef(mods, name1, tparams, vparamss, tpt1, body1)
}
Template(Nil, emptyValDef, ctor +: defs ::: defs1)
diff --git a/test/files/run/macro-toplevel-companion-a.check b/test/files/run/macro-toplevel-companion-a.check
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/test/files/run/macro-toplevel-companion-a.check
diff --git a/test/files/run/macro-toplevel-companion-a.flags b/test/files/run/macro-toplevel-companion-a.flags
new file mode 100644
index 0000000000..cd66464f2f
--- /dev/null
+++ b/test/files/run/macro-toplevel-companion-a.flags
@@ -0,0 +1 @@
+-language:experimental.macros \ No newline at end of file
diff --git a/test/files/run/macro-toplevel-companion-a/Impls_Macros_1.scala b/test/files/run/macro-toplevel-companion-a/Impls_Macros_1.scala
new file mode 100644
index 0000000000..23e8694ddc
--- /dev/null
+++ b/test/files/run/macro-toplevel-companion-a/Impls_Macros_1.scala
@@ -0,0 +1,14 @@
+import scala.reflect.macros.Context
+import language.experimental.macros
+
+object Macros {
+ def impl(c: Context) = {
+ import c.universe._
+ val synthetic = reify{ class C { override def toString = "C" }; object C { implicit val c = new C } }.tree
+ val defs = synthetic.asInstanceOf[Block].stats.asInstanceOf[List[ImplDef]]
+ if (c.topLevelRef(TypeName("C")).isEmpty) c.introduceTopLevel(nme.EMPTY_PACKAGE_NAME.toString, defs: _*)
+ c.literalUnit
+ }
+
+ def foo = macro impl
+} \ No newline at end of file
diff --git a/test/files/run/macro-toplevel-companion-a/Test_2.scala b/test/files/run/macro-toplevel-companion-a/Test_2.scala
new file mode 100644
index 0000000000..78b65b5b1f
--- /dev/null
+++ b/test/files/run/macro-toplevel-companion-a/Test_2.scala
@@ -0,0 +1,8 @@
+import Macros._
+
+object Test extends App {
+ foo;
+ implicitly[C];
+ foo;
+ implicitly[C];
+} \ No newline at end of file
diff --git a/test/files/run/macro-toplevel-companion-b.check b/test/files/run/macro-toplevel-companion-b.check
new file mode 100644
index 0000000000..bd30dc75d3
--- /dev/null
+++ b/test/files/run/macro-toplevel-companion-b.check
@@ -0,0 +1,4 @@
+reflective compilation has failed:
+
+Companions 'class C' and 'object C' must be defined in same file:
+ Found in <synthetic file name> and <synthetic file name>
diff --git a/test/files/run/macro-toplevel-companion-b.flags b/test/files/run/macro-toplevel-companion-b.flags
new file mode 100644
index 0000000000..cd66464f2f
--- /dev/null
+++ b/test/files/run/macro-toplevel-companion-b.flags
@@ -0,0 +1 @@
+-language:experimental.macros \ No newline at end of file
diff --git a/test/files/run/macro-toplevel-companion-b/Impls_Macros_1.scala b/test/files/run/macro-toplevel-companion-b/Impls_Macros_1.scala
new file mode 100644
index 0000000000..f30adc2965
--- /dev/null
+++ b/test/files/run/macro-toplevel-companion-b/Impls_Macros_1.scala
@@ -0,0 +1,15 @@
+import scala.reflect.macros.Context
+import language.experimental.macros
+
+object Macros {
+ def impl(c: Context) = {
+ import c.universe._
+ val Block(List(cdef: ClassDef), _) = reify{ class C }.tree
+ val classRef = c.topLevelRef(TypeName("C")) orElse c.introduceTopLevel(nme.EMPTY_PACKAGE_NAME.toString, cdef)
+ val Block(List(mdef: ModuleDef), _) = reify{ object C }.tree
+ val moduleRef = c.topLevelRef(TermName("C")) orElse c.introduceTopLevel(nme.EMPTY_PACKAGE_NAME.toString, mdef)
+ c.literalUnit
+ }
+
+ def foo = macro impl
+} \ No newline at end of file
diff --git a/test/files/run/macro-toplevel-companion-b/Test_2.scala b/test/files/run/macro-toplevel-companion-b/Test_2.scala
new file mode 100644
index 0000000000..ca202d053f
--- /dev/null
+++ b/test/files/run/macro-toplevel-companion-b/Test_2.scala
@@ -0,0 +1,11 @@
+import scala.reflect.runtime.universe._
+import scala.reflect.runtime.{universe => ru}
+import scala.reflect.runtime.{currentMirror => cm}
+import scala.tools.reflect.{ToolBox, ToolBoxError}
+import Macros._
+
+object Test extends App {
+ val tb = cm.mkToolBox()
+ try tb.compile(Select(Ident(TermName("Macros")), TermName("foo")))
+ catch { case ToolBoxError(message, _) => println("""macroSynthetic-.*?\.scala""".r.replaceAllIn(message, "<synthetic file name>")) }
+} \ No newline at end of file
diff --git a/test/files/run/macro-toplevel-companion-c.check b/test/files/run/macro-toplevel-companion-c.check
new file mode 100644
index 0000000000..8b422c2061
--- /dev/null
+++ b/test/files/run/macro-toplevel-companion-c.check
@@ -0,0 +1,3 @@
+error: Companions 'class C' and 'object C' must be defined in same file:
+ Found in <synthetic file name> and newSource1
+
diff --git a/test/files/run/macro-toplevel-companion-c.flags b/test/files/run/macro-toplevel-companion-c.flags
new file mode 100644
index 0000000000..cd66464f2f
--- /dev/null
+++ b/test/files/run/macro-toplevel-companion-c.flags
@@ -0,0 +1 @@
+-language:experimental.macros \ No newline at end of file
diff --git a/test/files/run/macro-toplevel-companion-c.scala b/test/files/run/macro-toplevel-companion-c.scala
new file mode 100644
index 0000000000..0e99903158
--- /dev/null
+++ b/test/files/run/macro-toplevel-companion-c.scala
@@ -0,0 +1,51 @@
+import scala.tools.partest._
+import java.io._
+
+object Test extends DirectTest {
+ def code = ???
+
+ def macros_1 = """
+ package test
+
+ import scala.reflect.macros.Context
+ import language.experimental.macros
+
+ object Macros {
+ def impl(c: Context) = {
+ import c.universe._
+ val Block(List(cdef: ClassDef), _) = reify{ class C }.tree
+ val ref = c.topLevelRef(TypeName("test.C")) orElse c.introduceTopLevel("test", cdef)
+ c.literalUnit
+ }
+
+ def foo = macro impl
+ }
+ """
+ def compileMacros() = {
+ val classpath = List(sys.props("partest.lib"), sys.props("partest.reflect")) mkString sys.props("path.separator")
+ compileString(newCompiler("-language:experimental.macros", "-cp", classpath, "-d", testOutput.path))(macros_1)
+ }
+
+ def test_2 = """
+ package test
+ object C { Macros.foo }
+ """
+ def compileTest() = {
+ val classpath = List(sys.props("partest.lib"), testOutput.path) mkString sys.props("path.separator")
+ compileString(newCompiler("-cp", classpath, "-d", testOutput.path))(test_2)
+ }
+
+ def show(): Unit = {
+ // redirect err to string, for logging
+ val prevErr = System.err
+ val baos = new ByteArrayOutputStream()
+ System.setErr(new PrintStream(baos))
+ log("Compiling Macros_1...")
+ if (compileMacros()) {
+ log("Compiling Test_2...")
+ if (compileTest()) log("Success!") else log("Failed...")
+ }
+ println("""macroSynthetic-.*?\.scala""".r.replaceAllIn(baos.toString, "<synthetic file name>"))
+ System.setErr(prevErr)
+ }
+} \ No newline at end of file
diff --git a/test/files/run/macro-toplevel.check b/test/files/run/macro-toplevel.check
new file mode 100644
index 0000000000..257c3764fd
--- /dev/null
+++ b/test/files/run/macro-toplevel.check
@@ -0,0 +1,2 @@
+I've been created from Macros.foo
+I've been created from Macros.foo
diff --git a/test/files/run/macro-toplevel/Macros_1.scala b/test/files/run/macro-toplevel/Macros_1.scala
new file mode 100644
index 0000000000..f681c86735
--- /dev/null
+++ b/test/files/run/macro-toplevel/Macros_1.scala
@@ -0,0 +1,15 @@
+import scala.reflect.macros.Context
+import language.experimental.macros
+
+object Macros {
+ def impl(c: Context) = {
+ import c.universe._
+ val msg = "I've been created from " + c.macroApplication
+ val Block(List(synthetic: ClassDef), _) = reify{ class SomeUniqueName { def hello = c.literal(msg).splice } }.tree
+ val ref = c.topLevelRef(synthetic.name) orElse c.introduceTopLevel(nme.EMPTY_PACKAGE_NAME.toString, synthetic)
+ c.Expr[String](Select(Apply(Select(New(ref), nme.CONSTRUCTOR), List()), TermName("hello")))
+ }
+
+ def foo = macro impl
+ def foo2 = macro impl
+}
diff --git a/test/files/run/macro-toplevel/Test_2.scala b/test/files/run/macro-toplevel/Test_2.scala
new file mode 100644
index 0000000000..eee2d6ae13
--- /dev/null
+++ b/test/files/run/macro-toplevel/Test_2.scala
@@ -0,0 +1,6 @@
+import Macros._
+
+object Test extends App {
+ println(Macros.foo)
+ println(Macros.foo2)
+} \ No newline at end of file
diff --git a/test/files/run/t6548.scala b/test/files/run/t6548.scala
index be3eb5b932..b4d09fd8f6 100644
--- a/test/files/run/t6548.scala
+++ b/test/files/run/t6548.scala
@@ -8,5 +8,5 @@ class Bean {
object Test extends App {
println(cm.staticClass("Bean").isCaseClass)
- println(typeOf[Bean].declaration(newTermName("value")).annotations)
+ println(typeOf[Bean].declaration(TermName("value")).annotations)
}