diff --git a/api.specification b/api.specification index b4d177b66..210bc07e3 100644 --- a/api.specification +++ b/api.specification @@ -234,6 +234,7 @@ Modifiers isImplicit: Boolean isLazy: Boolean isSynthetic: Boolean + isMacro: Boolean }}} {{{ diff --git a/compile/api/APIUtil.scala b/compile/api/APIUtil.scala index 4a7c800a2..c05db8bfa 100644 --- a/compile/api/APIUtil.scala +++ b/compile/api/APIUtil.scala @@ -9,11 +9,11 @@ object APIUtil val modifiersToByte = (m: Modifiers) => { import m._ def x(b: Boolean, bit: Int) = if(b) 1 << bit else 0 - ( x(isAbstract, 0) | x(isOverride, 1) | x(isFinal, 2) | x(isSealed, 3) | x(isImplicit, 4) | x(isLazy, 5) ).toByte + ( x(isAbstract, 0) | x(isOverride, 1) | x(isFinal, 2) | x(isSealed, 3) | x(isImplicit, 4) | x(isLazy, 5) | x(isMacro, 6) ).toByte } val byteToModifiers = (b: Byte) => { def x(bit: Int) = (b & (1 << bit)) != 0 - new Modifiers( x(0), x(1), x(2), x(3), x(4), x(5) ) + new Modifiers( x(0), x(1), x(2), x(3), x(4), x(5), x(6) ) } def verifyTypeParameters(s: SourceAPI): Boolean = @@ -43,6 +43,25 @@ object APIUtil super.visitParameterRef(ref) } } + + def hasMacro(s: SourceAPI): Boolean = + { + val check = new HasMacro + check.visitAPI(s) + check.hasMacro + } + + private[this] class HasMacro extends Visit + { + var hasMacro = false + + override def visitModifiers(m: Modifiers) + { + hasMacro ||= m.isMacro + super.visitModifiers(m) + } + } + def minimize(api: SourceAPI): SourceAPI = new SourceAPI(api.packages, minimizeDefinitions(api.definitions)) def minimizeDefinitions(ds: Array[Definition]): Array[Definition] = diff --git a/compile/api/ClassToAPI.scala b/compile/api/ClassToAPI.scala index 07366e71c..10c863361 100644 --- a/compile/api/ClassToAPI.scala +++ b/compile/api/ClassToAPI.scala @@ -166,7 +166,7 @@ object ClassToAPI def modifiers(i: Int): api.Modifiers = { import Modifier.{isAbstract, isFinal} - new api.Modifiers( isAbstract(i), false, isFinal(i), false, false, false) + new api.Modifiers( isAbstract(i), false, isFinal(i), false, false, false, false) } def access(i: Int, pkg: Option[String]): api.Access = { diff --git a/compile/api/SameAPI.scala b/compile/api/SameAPI.scala index e9fff1962..7ace3cfbd 100644 --- a/compile/api/SameAPI.scala +++ b/compile/api/SameAPI.scala @@ -43,7 +43,9 @@ object TopLevel /** Checks the API of two source files for equality.*/ object SameAPI { - def apply(a: Source, b: Source): Boolean = a.apiHash == b.apiHash && (a.hash.length > 0 && b.hash.length > 0) && apply(a.api, b.api) + def apply(a: Source, b: Source): Boolean = + a.apiHash == b.apiHash && (a.hash.length > 0 && b.hash.length > 0) && apply(a.api, b.api) + def apply(a: SourceAPI, b: SourceAPI): Boolean = { val start = System.currentTimeMillis @@ -208,6 +210,7 @@ class SameAPI(tagsA: TypeVars, tagsB: TypeVars, includePrivate: Boolean, include setIf(bs, isSealed, 3) setIf(bs, isImplicit, 4) setIf(bs, isLazy, 5) + setIf(bs, isMacro, 6) bs.toImmutable } def setIf(bs: mutable.BitSet, flag: Boolean, i: Int): Unit = diff --git a/compile/inc/APIs.scala b/compile/inc/APIs.scala index 5ff893f79..451aaf2ec 100644 --- a/compile/inc/APIs.scala +++ b/compile/inc/APIs.scala @@ -38,7 +38,7 @@ object APIs val emptyAPI = new xsbti.api.SourceAPI(Array(), Array()) val emptyCompilation = new xsbti.api.Compilation(-1, "") - val emptySource = new xsbti.api.Source(emptyCompilation, Array(), emptyAPI, 0) + val emptySource = new xsbti.api.Source(emptyCompilation, Array(), emptyAPI, 0, false) def getAPI[T](map: Map[T, Source], src: T): Source = map.getOrElse(src, emptySource) } diff --git a/compile/inc/Compile.scala b/compile/inc/Compile.scala index 02bf7ddc3..8baa39426 100644 --- a/compile/inc/Compile.scala +++ b/compile/inc/Compile.scala @@ -52,6 +52,8 @@ private final class AnalysisCallback(internalMap: File => Option[File], external private[this] val sourceDeps = new HashMap[File, Set[File]] private[this] val extSrcDeps = new ListBuffer[(File, String, Source)] private[this] val binaryClassName = new HashMap[File, String] + // source files containing a macro def. + private[this] val macroSources = Set[File]() private def add[A,B](map: Map[A,Set[B]], a: A, b: B): Unit = map.getOrElseUpdate(a, new HashSet[B]) += b @@ -99,7 +101,12 @@ private final class AnalysisCallback(internalMap: File => Option[File], external classToSource.put(module, source) } - def api(sourceFile: File, source: SourceAPI) { apis(sourceFile) = (xsbt.api.HashAPI(source), xsbt.api.APIUtil.minimize(source)) } + def api(sourceFile: File, source: SourceAPI) { + import xsbt.api.{APIUtil, HashAPI} + if (APIUtil.hasMacro(source)) macroSources += sourceFile + apis(sourceFile) = (HashAPI(source), APIUtil.minimize(source)) + } + def endSource(sourcePath: File): Unit = assert(apis.contains(sourcePath)) @@ -110,7 +117,9 @@ private final class AnalysisCallback(internalMap: File => Option[File], external (base /: apis) { case (a, (src, api) ) => val stamp = current.internalSource(src) val hash = stamp match { case h: Hash => h.value; case _ => new Array[Byte](0) } - val s = new xsbti.api.Source(compilation, hash, api._2, api._1) + // TODO store this in Relations, rather than Source. + val hasMacro: Boolean = macroSources.contains(src) + val s = new xsbti.api.Source(compilation, hash, api._2, api._1, hasMacro) a.addSource(src, s, stamp, sourceDeps.getOrElse(src, Nil: Iterable[File])) } def addExternals(base: Analysis): Analysis = (base /: extSrcDeps) { case (a, (source, name, api)) => a.addExternalDep(source, name, api) } diff --git a/compile/inc/Incremental.scala b/compile/inc/Incremental.scala index 211db2ce1..4c35b6b6a 100644 --- a/compile/inc/Incremental.scala +++ b/compile/inc/Incremental.scala @@ -59,7 +59,12 @@ object Incremental new APIChanges(modifiedAPIs, changedNames) } - def sameSource(a: Source, b: Source): Boolean = shortcutSameSource(a, b) || SameAPI(a,b) + def sameSource(a: Source, b: Source): Boolean = { + // Clients of a modified source file (ie, one that doesn't satisfy `shortcutSameSource`) containing macros must be recompiled. + val hasMacro = a.hasMacro || b.hasMacro + shortcutSameSource(a, b) || (!hasMacro && SameAPI(a,b)) + } + def shortcutSameSource(a: Source, b: Source): Boolean = !a.hash.isEmpty && !b.hash.isEmpty && sameCompilation(a.compilation, b.compilation) && (a.hash deepEquals b.hash) def sameCompilation(a: Compilation, b: Compilation): Boolean = a.startTime == b.startTime && a.target == b.target diff --git a/compile/interface/API.scala b/compile/interface/API.scala index 7e3b39ac9..3c174201f 100644 --- a/compile/interface/API.scala +++ b/compile/interface/API.scala @@ -275,7 +275,7 @@ final class API(val global: Global, val callback: xsbti.AnalysisCallback) extend { import Flags._ new xsbti.api.Modifiers(s.hasFlag(ABSTRACT) || s.hasFlag(DEFERRED), s.hasFlag(OVERRIDE), - s.isFinal, s.hasFlag(SEALED), isImplicit(s), s.hasFlag(LAZY)) + s.isFinal, s.hasFlag(SEALED), isImplicit(s), s.hasFlag(LAZY), hasMacro(s)) } private def isImplicit(s: Symbol) = s.hasFlag(Flags.IMPLICIT) diff --git a/compile/interface/Analyzer.scala b/compile/interface/Analyzer.scala index b4d2dd65d..32c79d764 100644 --- a/compile/interface/Analyzer.scala +++ b/compile/interface/Analyzer.scala @@ -130,12 +130,21 @@ abstract class Compat def LOCALCHILD = sourceCompatibilityOnly def NullaryMethodType = NullaryMethodTpe + + def MACRO = DummyValue } // in 2.9, NullaryMethodType was added to Type object NullaryMethodTpe { def unapply(t: Type): Option[Type] = None } + val DummyValue = 0 + def hasMacro(s: Symbol): Boolean = + { + val MACRO = Flags.MACRO // will be DummyValue for versions before 2.10 + MACRO != DummyValue && s.hasFlag(MACRO) + } + private[this] def sourceCompatibilityOnly: Nothing = throw new RuntimeException("For source compatibility only: should not get here.") private[this] final implicit def miscCompat(n: AnyRef): MiscCompat = new MiscCompat diff --git a/interface/other b/interface/other index 993c9d4f6..aeec1ae5a 100644 --- a/interface/other +++ b/interface/other @@ -3,6 +3,7 @@ Source hash: Byte* api: SourceAPI apiHash: Int + hasMacro: Boolean SourceAPI packages : Package* diff --git a/interface/src/main/java/xsbti/api/Modifiers.java b/interface/src/main/java/xsbti/api/Modifiers.java index 14737be57..575879608 100644 --- a/interface/src/main/java/xsbti/api/Modifiers.java +++ b/interface/src/main/java/xsbti/api/Modifiers.java @@ -8,13 +8,14 @@ public final class Modifiers implements java.io.Serializable private static final int SealedBit = 3; private static final int ImplicitBit = 4; private static final int LazyBit = 5; + private static final int MacroBit = 6; private static final int flag(boolean set, int bit) { return set ? (1 << bit) : 0; } - public Modifiers(boolean isAbstract, boolean isOverride, boolean isFinal, boolean isSealed, boolean isImplicit, boolean isLazy) + public Modifiers(boolean isAbstract, boolean isOverride, boolean isFinal, boolean isSealed, boolean isImplicit, boolean isLazy, boolean isMacro) { this.flags = (byte)( flag(isAbstract, AbstractBit) | @@ -22,7 +23,8 @@ public final class Modifiers implements java.io.Serializable flag(isFinal, FinalBit) | flag(isSealed, SealedBit) | flag(isImplicit, ImplicitBit) | - flag(isLazy, LazyBit) + flag(isLazy, LazyBit) | + flag(isMacro, MacroBit) ); } @@ -62,8 +64,12 @@ public final class Modifiers implements java.io.Serializable { return flag(LazyBit); } + public final boolean isMacro() + { + return flag(MacroBit); + } public String toString() { - return "Modifiers(" + "isAbstract: " + isAbstract() + ", " + "isOverride: " + isOverride() + ", " + "isFinal: " + isFinal() + ", " + "isSealed: " + isSealed() + ", " + "isImplicit: " + isImplicit() + ", " + "isLazy: " + isLazy()+ ")"; + return "Modifiers(" + "isAbstract: " + isAbstract() + ", " + "isOverride: " + isOverride() + ", " + "isFinal: " + isFinal() + ", " + "isSealed: " + isSealed() + ", " + "isImplicit: " + isImplicit() + ", " + "isLazy: " + isLazy() + ", " + "isMacro: " + isMacro()+ ")"; } } diff --git a/project/Sbt.scala b/project/Sbt.scala index a4e93d5af..f3a48ab52 100644 --- a/project/Sbt.scala +++ b/project/Sbt.scala @@ -143,7 +143,8 @@ object Sbt extends Build val loader = classpath.ClasspathUtilities.toLoader(scriptedSbtClasspath.files, scriptedSbtInstance.loader) val m = ModuleUtilities.getObject("sbt.test.ScriptedTests", loader) val r = m.getClass.getMethod("run", classOf[File], classOf[Boolean], classOf[String], classOf[String], classOf[String], classOf[Array[String]], classOf[File], classOf[Array[String]]) - try { r.invoke(m, sourcePath, true: java.lang.Boolean, v, sv, ssv, args.toArray[String], launcher, Array[String]()) } + val launcherVmOptions = Array("-XX:MaxPermSize=256M") // increased after a failure in scripted source-dependencies/macro + try { r.invoke(m, sourcePath, true: java.lang.Boolean, v, sv, ssv, args.toArray[String], launcher, launcherVmOptions) } catch { case ite: java.lang.reflect.InvocationTargetException => throw ite.getCause } } } diff --git a/sbt/src/sbt-test/source-dependencies/macro/macro-client/Client.scala b/sbt/src/sbt-test/source-dependencies/macro/macro-client/Client.scala new file mode 100644 index 000000000..90932d136 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro/macro-client/Client.scala @@ -0,0 +1,5 @@ +package macro + +object Client { + Provider.tree(0) +} diff --git a/sbt/src/sbt-test/source-dependencies/macro/macro-provider/Provider.scala b/sbt/src/sbt-test/source-dependencies/macro/macro-provider/Provider.scala new file mode 100644 index 000000000..93764e1b4 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro/macro-provider/Provider.scala @@ -0,0 +1,5 @@ +package macro + +object Provider { + def macro tree(args: Any) = reify(args) +} diff --git a/sbt/src/sbt-test/source-dependencies/macro/macro-provider/changes/Provider.scala b/sbt/src/sbt-test/source-dependencies/macro/macro-provider/changes/Provider.scala new file mode 100644 index 000000000..53a086bb8 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro/macro-provider/changes/Provider.scala @@ -0,0 +1,5 @@ +package macro + +object Provider { + def macro tree(args: Any) = sys.error("no macro for you!") +} diff --git a/sbt/src/sbt-test/source-dependencies/macro/project/build.scala b/sbt/src/sbt-test/source-dependencies/macro/project/build.scala new file mode 100644 index 000000000..1f95fbeb9 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro/project/build.scala @@ -0,0 +1,29 @@ +import sbt._ +import Keys._ + +object build extends Build { + val defaultSettings = Seq( + scalaVersion := "2.10.0-M2", + scalacOptions += "-Xmacros" + ) + + lazy val root = Project( + base = file("."), + id = "macro", + aggregate = Seq(macroProvider, macroClient), + settings = Defaults.defaultSettings ++ defaultSettings + ) + + lazy val macroProvider = Project( + base = file("macro-provider"), + id = "macro-provider", + settings = Defaults.defaultSettings ++ defaultSettings + ) + + lazy val macroClient = Project( + base = file("macro-client"), + id = "macro-client", + dependencies = Seq(macroProvider), + settings = Defaults.defaultSettings ++ defaultSettings + ) +} diff --git a/sbt/src/sbt-test/source-dependencies/macro/test b/sbt/src/sbt-test/source-dependencies/macro/test new file mode 100644 index 000000000..b3755d4ee --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro/test @@ -0,0 +1,13 @@ +> compile + +# replace macro with one that throws an error + +$ copy-file macro-provider/changes/Provider.scala macro-provider/Provider.scala + +> macro-provider/compile + +-> macro-client/compile + +> clean + +-> compile