Skip to content

Commit a03e77b

Browse files
committed
Merge pull request scala#1817 from scalamacros/topic/introduce-top-level
adds c.introduceTopLevel
2 parents 2d0fb86 + 1f1e369 commit a03e77b

38 files changed

+430
-51
lines changed

src/compiler/scala/reflect/macros/runtime/Context.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ abstract class Context extends scala.reflect.macros.Context
1414
with Parsers
1515
with Evals
1616
with ExprUtils
17+
with Synthetics
1718
with Traces {
1819

1920
val universe: Global
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/* NSC -- new Scala compiler
2+
* Copyright 2005-2013 LAMP/EPFL
3+
*/
4+
5+
package scala.reflect.macros
6+
package runtime
7+
8+
import java.util.UUID._
9+
import scala.reflect.internal.Flags._
10+
import scala.reflect.internal.util.BatchSourceFile
11+
import scala.reflect.io.VirtualFile
12+
13+
trait Synthetics {
14+
self: Context =>
15+
16+
import global._
17+
import mirror.wrapMissing
18+
19+
// getClassIfDefined and getModuleIfDefined cannot be used here
20+
// because they don't work for stuff declared in the empty package
21+
// (as specified in SLS, code inside non-empty packages cannot see
22+
// declarations from the empty package, so compiler internals
23+
// default to ignoring contents of the empty package)
24+
// to the contrast, staticModule and staticClass are designed
25+
// to be a part of the reflection API and, therefore, they
26+
// correctly resolve all names
27+
private def topLevelSymbol(name: Name): Symbol = wrapMissing {
28+
if (name.isTermName) mirror.staticModule(name.toString)
29+
else mirror.staticClass(name.toString)
30+
}
31+
32+
def topLevelDef(name: Name): Tree =
33+
enclosingRun.units.toList.map(_.body).flatMap {
34+
// it's okay to check `stat.symbol` here, because currently macros expand strictly after namer
35+
// which means that by the earliest time one can call this method all top-level definitions will have already been entered
36+
case PackageDef(_, stats) => stats filter (stat => stat.symbol != NoSymbol && stat.symbol == topLevelSymbol(name))
37+
case _ => Nil // should never happen, but better be safe than sorry
38+
}.headOption getOrElse EmptyTree
39+
40+
def topLevelRef(name: Name): Tree = {
41+
if (topLevelDef(name).nonEmpty) gen.mkUnattributedRef(name)
42+
else EmptyTree
43+
}
44+
45+
// TODO: provide a way to specify a pretty name for debugging purposes
46+
private def randomFileName() = (
47+
"macroSynthetic-" + randomUUID().toString.replace("-", "") + ".scala"
48+
)
49+
50+
def introduceTopLevel[T: PackageSpec](packagePrototype: T, definition: universe.ImplDef): RefTree =
51+
introduceTopLevel(packagePrototype, List(definition)).head
52+
53+
def introduceTopLevel[T: PackageSpec](packagePrototype: T, definitions: universe.ImplDef*): List[RefTree] =
54+
introduceTopLevel(packagePrototype, definitions.toList)
55+
56+
private def introduceTopLevel[T: PackageSpec](packagePrototype: T, definitions: List[universe.ImplDef]): List[RefTree] = {
57+
val code @ PackageDef(pid, _) = implicitly[PackageSpec[T]].mkPackageDef(packagePrototype, definitions)
58+
val syntheticFileName = randomFileName()
59+
// compatibility with SBT
60+
// on the one hand, we need to specify some jfile here, otherwise sbt crashes with an NPE (SI-6870)
61+
// on the other hand, we can't specify the obvious enclosingUnit, because then sbt somehow fails to run tests using type macros
62+
// okay, now let's specify a guaranteedly non-existent file in an existing directory (so that we don't run into permission problems)
63+
val relatedJfile = enclosingUnit.source.file.file
64+
val fakeJfile = if (relatedJfile != null) new java.io.File(relatedJfile.getParent, syntheticFileName) else null
65+
val virtualFile = new VirtualFile(syntheticFileName) { override def file = fakeJfile }
66+
val sourceFile = new BatchSourceFile(virtualFile, code.toString)
67+
val unit = new CompilationUnit(sourceFile)
68+
unit.body = code
69+
universe.currentRun.compileLate(unit)
70+
definitions map (definition => Select(pid, definition.name))
71+
}
72+
73+
protected def mkPackageDef(name: String, stats: List[Tree]) = gen.mkPackageDef(name, stats)
74+
75+
protected def mkPackageDef(name: TermName, stats: List[Tree]) = gen.mkPackageDef(name.toString, stats)
76+
77+
protected def mkPackageDef(tree: RefTree, stats: List[Tree]) = PackageDef(tree, stats)
78+
79+
protected def mkPackageDef(sym: Symbol, stats: List[Tree]) = {
80+
assert(sym hasFlag PACKAGE, s"expected a package or package class symbol, found: $sym")
81+
gen.mkPackageDef(sym.fullName.toString, stats)
82+
}
83+
}

src/compiler/scala/tools/nsc/CompilationUnits.scala

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,31 @@ trait CompilationUnits { self: Global =>
3939
/** Note: depends now contains toplevel classes.
4040
* To get their sourcefiles, you need to dereference with .sourcefile
4141
*/
42-
val depends = mutable.HashSet[Symbol]()
42+
private[this] val _depends = mutable.HashSet[Symbol]()
43+
// SBT compatibility (SI-6875)
44+
//
45+
// imagine we have a file named A.scala, which defines a trait named Foo and a module named Main
46+
// Main contains a call to a macro, which calls c.introduceTopLevel to define a mock for Foo
47+
// c.introduceTopLevel creates a virtual file Virt35af32.scala, which contains a class named FooMock extending Foo,
48+
// and macro expansion instantiates FooMock. the stage is now set. let's see what happens next.
49+
//
50+
// without this workaround in scalac or without being patched itself, sbt will think that
51+
// * Virt35af32 depends on A (because it extends Foo from A)
52+
// * A depends on Virt35af32 (because it contains a macro expansion referring to FooMock from Virt35af32)
53+
//
54+
// after compiling A.scala, SBT will notice that it has a new source file named Virt35af32.
55+
// it will also think that this file hasn't yet been compiled and since A depends on it
56+
// it will think that A needs to be recompiled.
57+
//
58+
// recompilation will lead to another macro expansion. that another macro expansion might choose to create a fresh mock,
59+
// producing another virtual file, say, Virtee509a, which will again trick SBT into thinking that A needs a recompile,
60+
// which will lead to another macro expansion, which will produce another virtual file and so on
61+
def depends = if (exists && !source.file.isVirtual) _depends else mutable.HashSet[Symbol]()
4362

4463
/** so we can relink
4564
*/
46-
val defined = mutable.HashSet[Symbol]()
65+
private[this] val _defined = mutable.HashSet[Symbol]()
66+
def defined = if (exists && !source.file.isVirtual) _defined else mutable.HashSet[Symbol]()
4767

4868
/** Synthetic definitions generated by namer, eliminated by typer.
4969
*/

src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,14 @@ abstract class SyntaxAnalyzer extends SubComponent with Parsers with MarkupParse
2323
def apply(unit: global.CompilationUnit) {
2424
import global._
2525
informProgress("parsing " + unit)
26-
unit.body =
27-
if (unit.isJava) new JavaUnitParser(unit).parse()
28-
else if (reporter.incompleteHandled) new UnitParser(unit).parse()
29-
else new UnitParser(unit).smartParse()
26+
// if the body is already filled in, do nothing
27+
// otherwise compileLate is going to overwrite bodies of synthetic source files
28+
if (unit.body == EmptyTree) {
29+
unit.body =
30+
if (unit.isJava) new JavaUnitParser(unit).parse()
31+
else if (reporter.incompleteHandled) new UnitParser(unit).parse()
32+
else new UnitParser(unit).smartParse()
33+
}
3034

3135
if (settings.Yrangepos.value && !reporter.hasErrors)
3236
validatePositions(unit.body)

src/compiler/scala/tools/nsc/doc/base/MemberLookupBase.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ trait MemberLookupBase {
8888
// (4) if we still haven't found anything, create a tooltip
8989
Tooltip(query)
9090
case List(l) => l
91-
case links =>
91+
case links =>
9292
val chosen = chooseLink(links)
9393
def linkToString(link: LinkTo) = {
9494
val chosenInfo =
@@ -203,11 +203,11 @@ trait MemberLookupBase {
203203
def findExternalLink(sym: Symbol, name: String): Option[LinkToExternal] = {
204204
val sym1 =
205205
if (sym == AnyClass || sym == AnyRefClass || sym == AnyValClass || sym == NothingClass) ListClass
206-
else if (sym.isPackage)
206+
else if (sym.isPackage)
207207
/* Get package object which has associatedFile ne null */
208208
sym.info.member(newTermName("package"))
209209
else sym
210-
Option(sym1.associatedFile) flatMap (_.underlyingSource) flatMap { src =>
210+
sym1.associatedFile.underlyingSource flatMap { src =>
211211
val path = src.path
212212
settings.extUrlMapping get path map { url =>
213213
LinkToExternal(name, url + "#" + name)

src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import scala.tools.nsc.util.{ ClassPath }
1212
import classfile.ClassfileParser
1313
import scala.reflect.internal.MissingRequirementError
1414
import scala.reflect.internal.util.Statistics
15-
import scala.tools.nsc.io.{ AbstractFile }
15+
import scala.reflect.io.{ AbstractFile, NoAbstractFile }
1616

1717
/** This class ...
1818
*
@@ -250,7 +250,7 @@ abstract class SymbolLoaders {
250250
protected def doComplete(root: Symbol) {
251251
val start = if (Statistics.canEnable) Statistics.startTimer(classReadNanos) else null
252252
classfileParser.parse(classfile, root)
253-
if (root.associatedFile eq null) {
253+
if (root.associatedFile eq NoAbstractFile) {
254254
root match {
255255
// In fact, the ModuleSymbol forwards its setter to the module class
256256
case _: ClassSymbol | _: ModuleSymbol =>

src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import scala.reflect.runtime.ReflectionUtils
1313
import scala.reflect.macros.runtime.AbortMacroException
1414
import scala.util.control.NonFatal
1515
import scala.tools.nsc.util.stackTraceString
16+
import scala.reflect.io.NoAbstractFile
1617

1718
trait ContextErrors {
1819
self: Analyzer =>
@@ -642,7 +643,7 @@ trait ContextErrors {
642643
val addendums = List(
643644
if (sym0.associatedFile eq sym1.associatedFile)
644645
Some("conflicting symbols both originated in file '%s'".format(sym0.associatedFile.canonicalPath))
645-
else if ((sym0.associatedFile ne null) && (sym1.associatedFile ne null))
646+
else if ((sym0.associatedFile ne NoAbstractFile) && (sym1.associatedFile ne NoAbstractFile))
646647
Some("conflicting symbols originated in files '%s' and '%s'".format(sym0.associatedFile.canonicalPath, sym1.associatedFile.canonicalPath))
647648
else None ,
648649
if (isBug) Some("Note: this may be due to a bug in the compiler involving wildcards in package objects") else None

src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ trait NamesDefaults { self: Analyzer =>
394394
// TODO #3649 can create spurious errors when companion object is gone (because it becomes unlinked from scope)
395395
if (defGetter == NoSymbol) None // prevent crash in erroneous trees, #3649
396396
else {
397-
var default1 = qual match {
397+
var default1: Tree = qual match {
398398
case Some(q) => gen.mkAttributedSelect(q.duplicate, defGetter)
399399
case None => gen.mkAttributedRef(defGetter)
400400

src/reflect/scala/reflect/api/StandardNames.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ trait StandardNames {
8484
*/
8585
val ROOTPKG: NameType
8686

87+
/** The term name `<empty>`.
88+
* Represents the empty package.
89+
*/
90+
val EMPTY_PACKAGE_NAME: NameType
91+
8792
/** The string " " (a single whitespace).
8893
* `LOCAL_SUFFIX_STRING` is appended to the names of local identifiers,
8994
* when it's necessary to prevent a naming conflict. For example, underlying fields

src/reflect/scala/reflect/internal/Mirrors.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ trait Mirrors extends api.Mirrors {
207207
erasureString(classTag[T].runtimeClass)
208208
}
209209

210-
@inline private def wrapMissing(body: => Symbol): Symbol =
210+
@inline final def wrapMissing(body: => Symbol): Symbol =
211211
try body
212212
catch { case _: MissingRequirementError => NoSymbol }
213213

0 commit comments

Comments
 (0)