diff options
author | Eugene Burmako <xeno.by@gmail.com> | 2013-01-06 20:17:49 -0800 |
---|---|---|
committer | Eugene Burmako <xeno.by@gmail.com> | 2013-01-06 20:17:49 -0800 |
commit | a03e77b8670faa133c76f4d7b29bf362246d20b5 (patch) | |
tree | 4657735c741511f7703f65614cc3bbb375055435 | |
parent | 2d0fb869fc7b813f562417372120bb25cb636642 (diff) | |
parent | 1f1e36922af099d647931a61ebdf5cec97ab54ac (diff) | |
download | scala-a03e77b8670faa133c76f4d7b29bf362246d20b5.tar.gz scala-a03e77b8670faa133c76f4d7b29bf362246d20b5.tar.bz2 scala-a03e77b8670faa133c76f4d7b29bf362246d20b5.zip |
Merge pull request #1817 from scalamacros/topic/introduce-top-levelv2.11.0-M1
adds c.introduceTopLevel
38 files changed, 430 insertions, 51 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/doc/base/MemberLookupBase.scala b/src/compiler/scala/tools/nsc/doc/base/MemberLookupBase.scala index 35390adcd9..216157e19a 100755 --- a/src/compiler/scala/tools/nsc/doc/base/MemberLookupBase.scala +++ b/src/compiler/scala/tools/nsc/doc/base/MemberLookupBase.scala @@ -88,7 +88,7 @@ trait MemberLookupBase { // (4) if we still haven't found anything, create a tooltip Tooltip(query) case List(l) => l - case links => + case links => val chosen = chooseLink(links) def linkToString(link: LinkTo) = { val chosenInfo = @@ -203,11 +203,11 @@ trait MemberLookupBase { def findExternalLink(sym: Symbol, name: String): Option[LinkToExternal] = { val sym1 = if (sym == AnyClass || sym == AnyRefClass || sym == AnyValClass || sym == NothingClass) ListClass - else if (sym.isPackage) + else if (sym.isPackage) /* Get package object which has associatedFile ne null */ sym.info.member(newTermName("package")) else sym - Option(sym1.associatedFile) flatMap (_.underlyingSource) flatMap { src => + sym1.associatedFile.underlyingSource flatMap { src => val path = src.path settings.extUrlMapping get path map { url => LinkToExternal(name, url + "#" + name) diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala index bbff03f67f..129331f435 100644 --- a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala @@ -12,7 +12,7 @@ import scala.tools.nsc.util.{ ClassPath } import classfile.ClassfileParser import scala.reflect.internal.MissingRequirementError import scala.reflect.internal.util.Statistics -import scala.tools.nsc.io.{ AbstractFile } +import scala.reflect.io.{ AbstractFile, NoAbstractFile } /** This class ... * @@ -250,7 +250,7 @@ abstract class SymbolLoaders { protected def doComplete(root: Symbol) { val start = if (Statistics.canEnable) Statistics.startTimer(classReadNanos) else null classfileParser.parse(classfile, root) - if (root.associatedFile eq null) { + if (root.associatedFile eq NoAbstractFile) { root match { // In fact, the ModuleSymbol forwards its setter to the module class case _: ClassSymbol | _: ModuleSymbol => diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 27944b8767..ea03aca8c4 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -13,6 +13,7 @@ import scala.reflect.runtime.ReflectionUtils import scala.reflect.macros.runtime.AbortMacroException import scala.util.control.NonFatal import scala.tools.nsc.util.stackTraceString +import scala.reflect.io.NoAbstractFile trait ContextErrors { self: Analyzer => @@ -642,7 +643,7 @@ trait ContextErrors { val addendums = List( if (sym0.associatedFile eq sym1.associatedFile) Some("conflicting symbols both originated in file '%s'".format(sym0.associatedFile.canonicalPath)) - else if ((sym0.associatedFile ne null) && (sym1.associatedFile ne null)) + else if ((sym0.associatedFile ne NoAbstractFile) && (sym1.associatedFile ne NoAbstractFile)) Some("conflicting symbols originated in files '%s' and '%s'".format(sym0.associatedFile.canonicalPath, sym1.associatedFile.canonicalPath)) else None , if (isBug) Some("Note: this may be due to a bug in the compiler involving wildcards in package objects") else None 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/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 3d43500ef1..ae68c1bcfd 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -11,7 +11,7 @@ import scala.collection.mutable.ListBuffer import util.{ Statistics, shortClassOfInstance } import Flags._ import scala.annotation.tailrec -import scala.reflect.io.AbstractFile +import scala.reflect.io.{ AbstractFile, NoAbstractFile } trait Symbols extends api.Symbols { self: SymbolTable => import definitions._ @@ -1916,8 +1916,8 @@ trait Symbols extends api.Symbols { self: SymbolTable => (this.rawInfo ne NoType) && (this.effectiveOwner == that.effectiveOwner) && ( !this.effectiveOwner.isPackageClass - || (this.associatedFile eq null) - || (that.associatedFile eq null) + || (this.associatedFile eq NoAbstractFile) + || (that.associatedFile eq NoAbstractFile) || (this.associatedFile.path == that.associatedFile.path) // Cheap possibly wrong check, then expensive normalization || (this.associatedFile.canonicalPath == that.associatedFile.canonicalPath) ) @@ -2176,17 +2176,16 @@ trait Symbols extends api.Symbols { self: SymbolTable => case _ => NoSymbol } - /** Desire to re-use the field in ClassSymbol which stores the source - * file to also store the classfile, but without changing the behavior - * of sourceFile (which is expected at least in the IDE only to - * return actual source code.) So sourceFile has classfiles filtered out. - */ - private def sourceFileOnly(file: AbstractFile): AbstractFile = - if ((file eq null) || (file.path endsWith ".class")) null else file - - final def sourceFile: AbstractFile = sourceFileOnly(associatedFile) + // Desire to re-use the field in ClassSymbol which stores the source + // file to also store the classfile, but without changing the behavior + // of sourceFile (which is expected at least in the IDE only to + // return actual source code.) So sourceFile has classfiles filtered out. + final def sourceFile: AbstractFile = + if ((associatedFile eq NoAbstractFile) || (associatedFile.path endsWith ".class")) null else associatedFile - /** Overridden in ModuleSymbols to delegate to the module class. */ + /** Overridden in ModuleSymbols to delegate to the module class. + * Never null; if there is no associated file, returns NoAbstractFile. + */ def associatedFile: AbstractFile = enclosingTopLevelClass.associatedFile def associatedFile_=(f: AbstractFile) { abort("associatedFile_= inapplicable for " + this) } @@ -2932,7 +2931,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => if (c.isOverloaded) c.alternatives.head else c } - override def associatedFile = if (owner.isPackageClass) _associatedFile else super.associatedFile + override def associatedFile = ( + if (!owner.isPackageClass) super.associatedFile + else if (_associatedFile eq null) NoAbstractFile // guarantee not null, but save cost of initializing the var + else _associatedFile + ) override def associatedFile_=(f: AbstractFile) { _associatedFile = f } override def reset(completer: Type): this.type = { @@ -2981,9 +2984,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => clone.typeOfThis = typeOfThis clone.thisSym setName thisSym.name } - if (_associatedFile ne null) - clone.associatedFile = _associatedFile - + clone.associatedFile = _associatedFile clone } @@ -3169,7 +3170,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => override def enclosingTopLevelClass: Symbol = this override def enclosingPackageClass: Symbol = this override def enclMethod: Symbol = this - override def associatedFile = null + override def associatedFile = NoAbstractFile override def ownerChain: List[Symbol] = List() override def ownersIterator: Iterator[Symbol] = Iterator.empty override def alternatives: List[Symbol] = List() 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) } |