summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrzegorz Kossakowski <grzegorz.kossakowski@gmail.com>2013-07-31 14:13:30 -0700
committerGrzegorz Kossakowski <grzegorz.kossakowski@gmail.com>2013-07-31 14:13:30 -0700
commit46616ea2e94fa6ac7100b1cde66295f68338e18e (patch)
tree9039f4b1bce71fa1b5a5ed721d0ee27d0751c015
parent59e21f37cb80215ada0db60363fb9f30adf6a0cd (diff)
parent8eb2853e19e03532c61963c87f3bf9f09cba5413 (diff)
downloadscala-46616ea2e94fa6ac7100b1cde66295f68338e18e.tar.gz
scala-46616ea2e94fa6ac7100b1cde66295f68338e18e.tar.bz2
scala-46616ea2e94fa6ac7100b1cde66295f68338e18e.zip
Merge pull request #2783 from gkossakowski/merge-2.10.x
Merge 2.10.x into master
-rw-r--r--src/compiler/scala/reflect/reify/phases/Reshape.scala16
-rw-r--r--src/compiler/scala/tools/ant/templates/tool-windows.tmpl2
-rw-r--r--src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala16
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/RefChecks.scala4
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala7
-rw-r--r--src/library/scala/concurrent/Future.scala9
-rw-r--r--src/library/scala/concurrent/impl/Promise.scala210
-rw-r--r--src/library/scala/util/Properties.scala35
-rw-r--r--test/files/neg/macro-override-macro-overrides-abstract-method-b.check16
-rw-r--r--test/files/neg/macro-override-macro-overrides-abstract-method-b/Impls_Macros_1.scala17
-rw-r--r--test/files/neg/macro-override-macro-overrides-abstract-method-b/Test_2.scala6
-rw-r--r--test/files/neg/t7636.check10
-rw-r--r--test/files/neg/t7636.scala7
-rw-r--r--test/files/pos/t7649.flags1
-rw-r--r--test/files/pos/t7649.scala20
-rw-r--r--test/files/run/t7265.scala27
-rw-r--r--test/files/run/t7336.scala31
-rw-r--r--test/files/run/t7455.check4
-rw-r--r--test/files/run/t7455/Outer.java31
-rw-r--r--test/files/run/t7455/Test.scala30
-rw-r--r--test/files/run/t7617a.check2
-rw-r--r--test/files/run/t7617a/Macros_1.scala22
-rw-r--r--test/files/run/t7617a/Test_2.scala5
-rw-r--r--test/files/run/t7617b.check1
-rw-r--r--test/files/run/t7617b/Macros_1.scala8
-rw-r--r--test/files/run/t7617b/Test_2.scala11
-rw-r--r--test/files/run/t7657.check3
-rw-r--r--test/files/run/t7657/Macros_1.scala8
-rw-r--r--test/files/run/t7657/Test_2.scala6
-rw-r--r--test/junit/scala/concurrent/impl/DefaultPromiseTest.scala344
-rw-r--r--test/pending/junit/scala/util/t7265.scala46
31 files changed, 888 insertions, 67 deletions
diff --git a/src/compiler/scala/reflect/reify/phases/Reshape.scala b/src/compiler/scala/reflect/reify/phases/Reshape.scala
index ae4a03d232..7578def687 100644
--- a/src/compiler/scala/reflect/reify/phases/Reshape.scala
+++ b/src/compiler/scala/reflect/reify/phases/Reshape.scala
@@ -91,20 +91,20 @@ trait Reshape {
private def undoMacroExpansion(tree: Tree): Tree =
tree.attachments.get[analyzer.MacroExpansionAttachment] match {
case Some(analyzer.MacroExpansionAttachment(original, _)) =>
+ def mkImplicitly(tp: Type) = atPos(tree.pos)(
+ gen.mkNullaryCall(Predef_implicitly, List(tp))
+ )
+ val sym = original.symbol
original match {
// this hack is necessary until I fix implicit macros
// so far tag materialization is implemented by sneaky macros hidden in scala-compiler.jar
// hence we cannot reify references to them, because noone will be able to see them later
// when implicit macros are fixed, these sneaky macros will move to corresponding companion objects
// of, say, ClassTag or TypeTag
- case Apply(TypeApply(_, List(tt)), _) if original.symbol == materializeClassTag =>
- gen.mkNullaryCall(Predef_implicitly, List(appliedType(ClassTagClass, tt.tpe)))
- case Apply(TypeApply(_, List(tt)), List(pre)) if original.symbol == materializeWeakTypeTag =>
- gen.mkNullaryCall(Predef_implicitly, List(typeRef(pre.tpe, WeakTypeTagClass, List(tt.tpe))))
- case Apply(TypeApply(_, List(tt)), List(pre)) if original.symbol == materializeTypeTag =>
- gen.mkNullaryCall(Predef_implicitly, List(typeRef(pre.tpe, TypeTagClass, List(tt.tpe))))
- case _ =>
- original
+ case Apply(TypeApply(_, List(tt)), _) if sym == materializeClassTag => mkImplicitly(appliedType(ClassTagClass, tt.tpe))
+ case Apply(TypeApply(_, List(tt)), List(pre)) if sym == materializeWeakTypeTag => mkImplicitly(typeRef(pre.tpe, WeakTypeTagClass, List(tt.tpe)))
+ case Apply(TypeApply(_, List(tt)), List(pre)) if sym == materializeTypeTag => mkImplicitly(typeRef(pre.tpe, TypeTagClass, List(tt.tpe)))
+ case _ => original
}
case _ => tree
}
diff --git a/src/compiler/scala/tools/ant/templates/tool-windows.tmpl b/src/compiler/scala/tools/ant/templates/tool-windows.tmpl
index bd6cf561b9..a3a95ffd37 100644
--- a/src/compiler/scala/tools/ant/templates/tool-windows.tmpl
+++ b/src/compiler/scala/tools/ant/templates/tool-windows.tmpl
@@ -89,4 +89,4 @@ goto :eof
@@endlocal
REM exit code fix, see http://stackoverflow.com/questions/4632891/exiting-batch-with-exit-b-x-where-x-1-acts-as-if-command-completed-successfu
-@@%COMSPEC% /C exit %errorlevel% >nul
+@@"%COMSPEC%" /C exit %errorlevel% >nul
diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
index 4c0c16690f..e4e3862bcd 100644
--- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
+++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
@@ -607,9 +607,8 @@ abstract class ClassfileParser {
if (name == nme.CONSTRUCTOR)
sawPrivateConstructor = true
in.skip(2); skipAttributes()
- }
- else {
- if ((sflags & PRIVATE) != 0L && optimized) {
+ } else {
+ if ((sflags & PRIVATE) != 0L && optimized) { // TODO this should be !optimized, no? See c4181f656d.
in.skip(4); skipAttributes()
} else {
val name = readName()
@@ -619,7 +618,7 @@ abstract class ClassfileParser {
info match {
case MethodType(params, restpe) =>
// if this is a non-static inner class, remove the explicit outer parameter
- val newParams = innerClasses getEntry currentClass match {
+ val paramsNoOuter = innerClasses getEntry currentClass match {
case Some(entry) if !isScalaRaw && !entry.jflags.isStatic =>
/* About `clazz.owner.isPackage` below: SI-5957
* For every nested java class A$B, there are two symbols in the scala compiler.
@@ -633,6 +632,15 @@ abstract class ClassfileParser {
case _ =>
params
}
+ val newParams = paramsNoOuter match {
+ case (init :+ tail) if jflags.isSynthetic =>
+ // SI-7455 strip trailing dummy argument ("access constructor tag") from synthetic constructors which
+ // are added when an inner class needs to access a private constructor.
+ init
+ case _ =>
+ paramsNoOuter
+ }
+
info = MethodType(newParams, clazz.tpe)
}
// Note: the info may be overrwritten later with a generic signature
diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
index c9849eebb5..1b6963b598 100644
--- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
@@ -249,7 +249,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
* 1.8.1 M's type is a subtype of O's type, or
* 1.8.2 M is of type []S, O is of type ()T and S <: T, or
* 1.8.3 M is of type ()S, O is of type []T and S <: T, or
- * 1.9. If M is a macro def, O cannot be deferred.
+ * 1.9. If M is a macro def, O cannot be deferred unless there's a concrete method overriding O.
* 1.10. If M is not a macro def, O cannot be a macro def.
* 2. Check that only abstract classes have deferred members
* 3. Check that concrete classes do not have deferred definitions
@@ -440,7 +440,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
} else if (other.isValue && other.isLazy && !other.isSourceMethod && !other.isDeferred &&
member.isValue && !member.isLazy) {
overrideError("must be declared lazy to override a concrete lazy value")
- } else if (other.isDeferred && member.isTermMacro) { // (1.9)
+ } else if (other.isDeferred && member.isTermMacro && member.extendedOverriddenSymbols.forall(_.isDeferred)) { // (1.9)
overrideError("cannot be used here - term macros cannot override abstract methods")
} else if (other.isTermMacro && !member.isTermMacro) { // (1.10)
overrideError("cannot be used here - only term macros can override term macros")
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index f9e34106ec..d2ff47626d 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -2103,6 +2103,8 @@ trait Typers extends Adaptations with Tags with TypersTracking {
// !!! This method is redundant with other, less buggy ones.
def decompose(call: Tree): (Tree, List[Tree]) = call match {
+ case _ if call.isErrorTyped => // e.g. SI-7636
+ (call, Nil)
case Apply(fn, args) =>
// an object cannot be allowed to pass a reference to itself to a superconstructor
// because of initialization issues; SI-473, SI-3913, SI-6928.
@@ -4012,6 +4014,7 @@ trait Typers extends Adaptations with Tags with TypersTracking {
findSelection(cxTree) match {
case Some((opName, treeInfo.Applied(_, targs, _))) =>
val fun = gen.mkTypeApply(Select(qual, opName), targs)
+ if (opName == nme.updateDynamic) suppressMacroExpansion(fun) // SI-7617
atPos(qual.pos)(Apply(fun, Literal(Constant(name.decode)) :: Nil))
case _ =>
setError(tree)
@@ -4176,7 +4179,9 @@ trait Typers extends Adaptations with Tags with TypersTracking {
}
def typedAssign(lhs: Tree, rhs: Tree): Tree = {
- val lhs1 = typed(lhs, EXPRmode | LHSmode)
+ // see SI-7617 for an explanation of why macro expansion is suppressed
+ def typedLhs(lhs: Tree) = typed(lhs, EXPRmode | LHSmode)
+ val lhs1 = unsuppressMacroExpansion(typedLhs(suppressMacroExpansion(lhs)))
val varsym = lhs1.symbol
// see #2494 for double error message example
diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala
index b072cd653b..411b89701b 100644
--- a/src/library/scala/concurrent/Future.scala
+++ b/src/library/scala/concurrent/Future.scala
@@ -247,10 +247,15 @@ trait Future[+T] extends Awaitable[T] {
* $forComprehensionExamples
*/
def flatMap[S](f: T => Future[S])(implicit executor: ExecutionContext): Future[S] = {
- val p = Promise[S]()
+ import impl.Promise.DefaultPromise
+ val p = new DefaultPromise[S]()
onComplete {
case f: Failure[_] => p complete f.asInstanceOf[Failure[S]]
- case Success(v) => try f(v) onComplete p.complete catch { case NonFatal(t) => p failure t }
+ case Success(v) => try f(v) match {
+ // If possible, link DefaultPromises to avoid space leaks
+ case dp: DefaultPromise[_] => dp.asInstanceOf[DefaultPromise[S]].linkRootOf(p)
+ case fut => fut onComplete p.complete
+ } catch { case NonFatal(t) => p failure t }
}
p.future
}
diff --git a/src/library/scala/concurrent/impl/Promise.scala b/src/library/scala/concurrent/impl/Promise.scala
index ffaea8de96..35511856ee 100644
--- a/src/library/scala/concurrent/impl/Promise.scala
+++ b/src/library/scala/concurrent/impl/Promise.scala
@@ -56,7 +56,9 @@ private[concurrent] object Promise {
case t => Failure(t)
}
- /*
+ /**
+ * Latch used to implement waiting on a DefaultPromise's result.
+ *
* Inspired by: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/locks/AbstractQueuedSynchronizer.java
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
@@ -73,10 +75,122 @@ private[concurrent] object Promise {
/** Default promise implementation.
+ *
+ * A DefaultPromise has three possible states. It can be:
+ *
+ * 1. Incomplete, with an associated list of callbacks waiting on completion.
+ * 2. Complete, with a result.
+ * 3. Linked to another DefaultPromise.
+ *
+ * If a DefaultPromise is linked it another DefaultPromise then it will
+ * delegate all its operations to that other promise. This means that two
+ * DefaultPromises that are linked will appear, to external callers, to have
+ * exactly the same state and behaviour. E.g. they will both appear to be
+ * either complete or incomplete, and with the same values.
+ *
+ * A DefaultPromise stores its state entirely in the AnyRef cell exposed by
+ * AbstractPromise. The type of object stored in the cell fully describes the
+ * current state of the promise.
+ *
+ * 1. List[CallbackRunnable] - The promise is incomplete and has zero or more callbacks
+ * to call when it is eventually completed.
+ * 2. Try[T] - The promise is complete and now contains its value.
+ * 3. DefaultPromise[T] - The promise is linked to another promise.
+ *
+ * The ability to link DefaultPromises is needed to prevent memory leaks when
+ * using Future.flatMap. The previous implementation of Future.flatMap used
+ * onComplete handlers to propagate the ultimate value of a flatMap operation
+ * to its promise. Recursive calls to flatMap built a chain of onComplete
+ * handlers and promises. Unfortunately none of the handlers or promises in
+ * the chain could be collected until the handlers had been called and
+ * detached, which only happened when the final flatMap future was completed.
+ * (In some situations, such as infinite streams, this would never actually
+ * happen.) Because of the fact that the promise implementation internally
+ * created references between promises, and these references were invisible to
+ * user code, it was easy for user code to accidentally build large chains of
+ * promises and thereby leak memory.
+ *
+ * The problem of leaks is solved by automatically breaking these chains of
+ * promises, so that promises don't refer to each other in a long chain. This
+ * allows each promise to be individually collected. The idea is to "flatten"
+ * the chain of promises, so that instead of each promise pointing to its
+ * neighbour, they instead point directly the promise at the root of the
+ * chain. This means that only the root promise is referenced, and all the
+ * other promises are available for garbage collection as soon as they're no
+ * longer referenced by user code.
+ *
+ * To make the chains flattenable, the concept of linking promises together
+ * needed to become an explicit feature of the DefaultPromise implementation,
+ * so that the implementation to navigate and rewire links as needed. The idea
+ * of linking promises is based on the [[Twitter promise implementation
+ * https://github.com/twitter/util/blob/master/util-core/src/main/scala/com/twitter/util/Promise.scala]].
+ *
+ * In practice, flattening the chain cannot always be done perfectly. When a
+ * promise is added to the end of the chain, it scans the chain and links
+ * directly to the root promise. This prevents the chain from growing forwards
+ * But the root promise for a chain can change, causing the chain to grow
+ * backwards, and leaving all previously-linked promise pointing at a promise
+ * which is no longer the root promise.
+ *
+ * To mitigate the problem of the root promise changing, whenever a promise's
+ * methods are called, and it needs a reference to its root promise it calls
+ * the `compressedRoot()` method. This method re-scans the promise chain to
+ * get the root promise, and also compresses its links so that it links
+ * directly to whatever the current root promise is. This ensures that the
+ * chain is flattened whenever `compressedRoot()` is called. And since
+ * `compressedRoot()` is called at every possible opportunity (when getting a
+ * promise's value, when adding an onComplete handler, etc), this will happen
+ * frequently. Unfortunately, even this eager relinking doesn't absolutely
+ * guarantee that the chain will be flattened and that leaks cannot occur.
+ * However eager relinking does greatly reduce the chance that leaks will
+ * occur.
+ *
+ * Future.flatMap links DefaultPromises together by calling the `linkRootOf`
+ * method. This is the only externally visible interface to linked
+ * DefaultPromises, and `linkedRootOf` is currently only designed to be called
+ * by Future.flatMap.
*/
class DefaultPromise[T] extends AbstractPromise with Promise[T] { self =>
- updateState(null, Nil) // Start at "No callbacks"
+ updateState(null, Nil) // The promise is incomplete and has no callbacks
+
+ /** Get the root promise for this promise, compressing the link chain to that
+ * promise if necessary.
+ *
+ * For promises that are not linked, the result of calling
+ * `compressedRoot()` will the promise itself. However for linked promises,
+ * this method will traverse each link until it locates the root promise at
+ * the base of the link chain.
+ *
+ * As a side effect of calling this method, the link from this promise back
+ * to the root promise will be updated ("compressed") to point directly to
+ * the root promise. This allows intermediate promises in the link chain to
+ * be garbage collected. Also, subsequent calls to this method should be
+ * faster as the link chain will be shorter.
+ */
+ @tailrec
+ private def compressedRoot(): DefaultPromise[T] = {
+ getState match {
+ case linked: DefaultPromise[_] =>
+ val target = linked.asInstanceOf[DefaultPromise[T]].root
+ if (linked eq target) target else if (updateState(linked, target)) target else compressedRoot()
+ case _ => this
+ }
+ }
+ /** Get the promise at the root of the chain of linked promises. Used by `compressedRoot()`.
+ * The `compressedRoot()` method should be called instead of this method, as it is important
+ * to compress the link chain whenever possible.
+ */
+ @tailrec
+ private def root: DefaultPromise[T] = {
+ getState match {
+ case linked: DefaultPromise[_] => linked.asInstanceOf[DefaultPromise[T]].root
+ case _ => this
+ }
+ }
+
+ /** Try waiting for this promise to be completed.
+ */
protected final def tryAwait(atMost: Duration): Boolean = if (!isCompleted) {
import Duration.Undefined
import scala.concurrent.Future.InternalCallbackExecutor
@@ -108,42 +222,96 @@ private[concurrent] object Promise {
def result(atMost: Duration)(implicit permit: CanAwait): T =
ready(atMost).value.get.get // ready throws TimeoutException if timeout so value.get is safe here
- def value: Option[Try[T]] = getState match {
+ def value: Option[Try[T]] = value0
+
+ @tailrec
+ private def value0: Option[Try[T]] = getState match {
case c: Try[_] => Some(c.asInstanceOf[Try[T]])
+ case _: DefaultPromise[_] => compressedRoot().value0
case _ => None
}
- override def isCompleted: Boolean = getState.isInstanceOf[Try[_]]
+ override def isCompleted: Boolean = isCompleted0
+
+ @tailrec
+ private def isCompleted0: Boolean = getState match {
+ case _: Try[_] => true
+ case _: DefaultPromise[_] => compressedRoot().isCompleted0
+ case _ => false
+ }
def tryComplete(value: Try[T]): Boolean = {
val resolved = resolveTry(value)
- @tailrec
- def tryComplete(v: Try[T]): List[CallbackRunnable[T]] = {
- getState match {
- case raw: List[_] =>
- val cur = raw.asInstanceOf[List[CallbackRunnable[T]]]
- if (updateState(cur, v)) cur else tryComplete(v)
- case _ => null
- }
- }
- tryComplete(resolved) match {
+ tryCompleteAndGetListeners(resolved) match {
case null => false
case rs if rs.isEmpty => true
case rs => rs.foreach(r => r.executeWithValue(resolved)); true
}
}
+ /** Called by `tryComplete` to store the resolved value and get the list of
+ * listeners, or `null` if it is already completed.
+ */
+ @tailrec
+ private def tryCompleteAndGetListeners(v: Try[T]): List[CallbackRunnable[T]] = {
+ getState match {
+ case raw: List[_] =>
+ val cur = raw.asInstanceOf[List[CallbackRunnable[T]]]
+ if (updateState(cur, v)) cur else tryCompleteAndGetListeners(v)
+ case _: DefaultPromise[_] =>
+ compressedRoot().tryCompleteAndGetListeners(v)
+ case _ => null
+ }
+ }
+
def onComplete[U](func: Try[T] => U)(implicit executor: ExecutionContext): Unit = {
val preparedEC = executor.prepare()
val runnable = new CallbackRunnable[T](preparedEC, func)
+ dispatchOrAddCallback(runnable)
+ }
+
+ /** Tries to add the callback, if already completed, it dispatches the callback to be executed.
+ * Used by `onComplete()` to add callbacks to a promise and by `link()` to transfer callbacks
+ * to the root promise when linking two promises togehter.
+ */
+ @tailrec
+ private def dispatchOrAddCallback(runnable: CallbackRunnable[T]): Unit = {
+ getState match {
+ case r: Try[_] => runnable.executeWithValue(r.asInstanceOf[Try[T]])
+ case _: DefaultPromise[_] => compressedRoot().dispatchOrAddCallback(runnable)
+ case listeners: List[_] => if (updateState(listeners, runnable :: listeners)) () else dispatchOrAddCallback(runnable)
+ }
+ }
- @tailrec //Tries to add the callback, if already completed, it dispatches the callback to be executed
- def dispatchOrAddCallback(): Unit =
- getState match {
- case r: Try[_] => runnable.executeWithValue(r.asInstanceOf[Try[T]])
- case listeners: List[_] => if (updateState(listeners, runnable :: listeners)) () else dispatchOrAddCallback()
- }
- dispatchOrAddCallback()
+ /** Link this promise to the root of another promise using `link()`. Should only be
+ * be called by Future.flatMap.
+ */
+ protected[concurrent] final def linkRootOf(target: DefaultPromise[T]): Unit = link(target.compressedRoot())
+
+ /** Link this promise to another promise so that both promises share the same
+ * externally-visible state. Depending on the current state of this promise, this
+ * may involve different things. For example, any onComplete listeners will need
+ * to be transferred.
+ *
+ * If this promise is already completed, then the same effect as linking -
+ * sharing the same completed value - is achieved by simply sending this
+ * promise's result to the target promise.
+ */
+ @tailrec
+ private def link(target: DefaultPromise[T]): Unit = if (this ne target) {
+ getState match {
+ case r: Try[_] =>
+ if (!target.tryComplete(r.asInstanceOf[Try[T]])) {
+ // Currently linking is done from Future.flatMap, which should ensure only
+ // one promise can be completed. Therefore this situation is unexpected.
+ throw new IllegalStateException("Cannot link completed promises together")
+ }
+ case _: DefaultPromise[_] =>
+ compressedRoot().link(target)
+ case listeners: List[_] => if (updateState(listeners, target)) {
+ if (!listeners.isEmpty) listeners.asInstanceOf[List[CallbackRunnable[T]]].foreach(target.dispatchOrAddCallback(_))
+ } else link(target)
+ }
}
}
diff --git a/src/library/scala/util/Properties.scala b/src/library/scala/util/Properties.scala
index fef9cef246..02c461f3c6 100644
--- a/src/library/scala/util/Properties.scala
+++ b/src/library/scala/util/Properties.scala
@@ -131,6 +131,10 @@ private[scala] trait PropertiesTrait {
def javaVmName = propOrEmpty("java.vm.name")
def javaVmVendor = propOrEmpty("java.vm.vendor")
def javaVmVersion = propOrEmpty("java.vm.version")
+ // this property must remain less-well-known until 2.11
+ private def javaSpecVersion = propOrEmpty("java.specification.version")
+ //private def javaSpecVendor = propOrEmpty("java.specification.vendor")
+ //private def javaSpecName = propOrEmpty("java.specification.name")
def osName = propOrEmpty("os.name")
def scalaHome = propOrEmpty("scala.home")
def tmpDir = propOrEmpty("java.io.tmpdir")
@@ -158,18 +162,29 @@ private[scala] trait PropertiesTrait {
def scalaCmd = if (isWin) "scala.bat" else "scala"
def scalacCmd = if (isWin) "scalac.bat" else "scalac"
- /** Can the java version be determined to be at least as high as the argument?
- * Hard to properly future proof this but at the rate 1.7 is going we can leave
- * the issue for our cyborg grandchildren to solve.
+ /** Compares the given specification version to the specification version of the platform.
+ *
+ * @param version a specification version of the form "major.minor"
+ * @return `true` iff the specification version of the current runtime
+ * is equal to or higher than the version denoted by the given string.
+ * @throws NumberFormatException if the given string is not a version string
+ *
+ * @example {{{
+ * // In this example, the runtime's Java specification is assumed to be at version 1.7.
+ * isJavaAtLeast("1.6") // true
+ * isJavaAtLeast("1.7") // true
+ * isJavaAtLeast("1.8") // false
+ * }}
*/
- def isJavaAtLeast(version: String) = {
- val okVersions = version match {
- case "1.5" => List("1.5", "1.6", "1.7")
- case "1.6" => List("1.6", "1.7")
- case "1.7" => List("1.7")
- case _ => Nil
+ def isJavaAtLeast(version: String): Boolean = {
+ def parts(x: String) = {
+ val i = x.indexOf('.')
+ if (i < 0) throw new NumberFormatException("Not a version: " + x)
+ (x.substring(0, i), x.substring(i+1, x.length))
}
- okVersions exists (javaVersion startsWith _)
+ val (v, _v) = parts(version)
+ val (s, _s) = parts(javaSpecVersion)
+ s.toInt >= v.toInt && _s.toInt >= _v.toInt
}
// provide a main method so version info can be obtained by running this
diff --git a/test/files/neg/macro-override-macro-overrides-abstract-method-b.check b/test/files/neg/macro-override-macro-overrides-abstract-method-b.check
index 8c8f039225..c733555549 100644
--- a/test/files/neg/macro-override-macro-overrides-abstract-method-b.check
+++ b/test/files/neg/macro-override-macro-overrides-abstract-method-b.check
@@ -1,5 +1,11 @@
-Impls_Macros_1.scala:12: error: overriding method foo in trait Foo of type (x: Int)Int;
- macro method foo cannot be used here - term macros cannot override abstract methods
- def foo(x: Int) = macro Impls.impl
- ^
-one error found
+Test_2.scala:3: error: <$anon: C with A> inherits conflicting members:
+ macro method t in trait C of type ()Unit and
+ method t in trait A of type ()Unit
+(Note: this can be resolved by declaring an override in <$anon: C with A>.)
+ val c2 = new C with A {}
+ ^
+Test_2.scala:5: error: overriding macro method t in trait C of type ()Unit;
+ method t cannot be used here - only term macros can override term macros
+ val c4 = new C with A { override def t(): Unit = () }
+ ^
+two errors found
diff --git a/test/files/neg/macro-override-macro-overrides-abstract-method-b/Impls_Macros_1.scala b/test/files/neg/macro-override-macro-overrides-abstract-method-b/Impls_Macros_1.scala
index e43264f52f..f5b2555aa5 100644
--- a/test/files/neg/macro-override-macro-overrides-abstract-method-b/Impls_Macros_1.scala
+++ b/test/files/neg/macro-override-macro-overrides-abstract-method-b/Impls_Macros_1.scala
@@ -1,13 +1,8 @@
-import scala.reflect.macros.{Context => Ctx}
+import scala.reflect.macros.Context
+import language.experimental.macros
-object Impls {
- def impl(c: Ctx)(x: c.Expr[Int]) = x
-}
+trait T { def t(): Unit }
+trait A { def t(): Unit = () }
-trait Foo {
- def foo(x: Int): Int
-}
-
-object Macros extends Foo {
- def foo(x: Int) = macro Impls.impl
-}
+object Macro { def t(c: Context)(): c.Expr[Unit] = c.universe.reify(()) }
+trait C extends T { self: A => override def t(): Unit = macro Macro.t }
diff --git a/test/files/neg/macro-override-macro-overrides-abstract-method-b/Test_2.scala b/test/files/neg/macro-override-macro-overrides-abstract-method-b/Test_2.scala
index 08fff30baf..9b4c8e35f0 100644
--- a/test/files/neg/macro-override-macro-overrides-abstract-method-b/Test_2.scala
+++ b/test/files/neg/macro-override-macro-overrides-abstract-method-b/Test_2.scala
@@ -1,4 +1,6 @@
object Test extends App {
- val designator: Foo = Macros
- designator.foo(42)
+ val c1 = new A with C {}
+ val c2 = new C with A {}
+ val c3 = new C with A { override def t(): Unit = macro Macro.t }
+ val c4 = new C with A { override def t(): Unit = () }
} \ No newline at end of file
diff --git a/test/files/neg/t7636.check b/test/files/neg/t7636.check
new file mode 100644
index 0000000000..f70d50bee3
--- /dev/null
+++ b/test/files/neg/t7636.check
@@ -0,0 +1,10 @@
+t7636.scala:3: error: illegal inheritance;
+ self-type Main.C does not conform to Main.ResultTable[_$3]'s selftype Main.ResultTable[_$3]
+ class C extends ResultTable(Left(5):Either[_,_])(5)
+ ^
+t7636.scala:3: error: type mismatch;
+ found : Either[_$2,_$3(in constructor C)] where type _$3(in constructor C), type _$2
+ required: Either[_, _$3(in object Main)] where type _$3(in object Main)
+ class C extends ResultTable(Left(5):Either[_,_])(5)
+ ^
+two errors found
diff --git a/test/files/neg/t7636.scala b/test/files/neg/t7636.scala
new file mode 100644
index 0000000000..a7b1b90151
--- /dev/null
+++ b/test/files/neg/t7636.scala
@@ -0,0 +1,7 @@
+object Main extends App{
+ class ResultTable[E]( query : Either[_,E] )( columns : Int )
+ class C extends ResultTable(Left(5):Either[_,_])(5)
+}
+// Inference of the existential type for the parent type argument
+// E still fails. That looks tricky to fix, see the comments in SI-7636.
+// But we at least prevent a cascading NPE. \ No newline at end of file
diff --git a/test/files/pos/t7649.flags b/test/files/pos/t7649.flags
new file mode 100644
index 0000000000..fcf951d907
--- /dev/null
+++ b/test/files/pos/t7649.flags
@@ -0,0 +1 @@
+-Yrangepos \ No newline at end of file
diff --git a/test/files/pos/t7649.scala b/test/files/pos/t7649.scala
new file mode 100644
index 0000000000..a1b02f63f1
--- /dev/null
+++ b/test/files/pos/t7649.scala
@@ -0,0 +1,20 @@
+object Test {
+ val c: reflect.macros.Context = ???
+ import c.universe._
+ reify {
+ // The lookup of the implicit WeakTypeTag[Any]
+ // was triggering an unpositioned tree.
+ c.Expr[Any](Literal(Constant(0))).splice
+ }
+
+ import scala.reflect.ClassTag
+ def ct[A: ClassTag]: Expr[A] = ???
+ def tt[A: TypeTag]: Expr[A] = ???
+ def wtt[A: WeakTypeTag]: Expr[A] = ???
+
+ reify {
+ ct[String].splice
+ tt[String].splice
+ wtt[String].splice
+ }
+}
diff --git a/test/files/run/t7265.scala b/test/files/run/t7265.scala
new file mode 100644
index 0000000000..c556930303
--- /dev/null
+++ b/test/files/run/t7265.scala
@@ -0,0 +1,27 @@
+
+import scala.util.Properties._
+
+object Test extends App {
+
+ setProp("java.specification.version", "1.7")
+
+ assert( isJavaAtLeast("1.5"))
+ assert( isJavaAtLeast("1.6"))
+ assert( isJavaAtLeast("1.7"))
+ assert(!isJavaAtLeast("1.8"))
+ assert(!isJavaAtLeast("1.71"))
+
+ failing(isJavaAtLeast("1.a"))
+ failing(isJavaAtLeast("1"))
+ failing(isJavaAtLeast(""))
+ failing(isJavaAtLeast("."))
+ failing(isJavaAtLeast(".5"))
+ failing(isJavaAtLeast("1.7.1"))
+
+ def failing(u: =>Unit) = try {
+ u
+ assert(false, "Expected Exception")
+ } catch {
+ case _: NumberFormatException =>
+ }
+}
diff --git a/test/files/run/t7336.scala b/test/files/run/t7336.scala
new file mode 100644
index 0000000000..ace83f2c1f
--- /dev/null
+++ b/test/files/run/t7336.scala
@@ -0,0 +1,31 @@
+import scala.concurrent.Await
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.Future
+import scala.concurrent.duration.Duration
+
+/** This test uses recursive calls to Future.flatMap to create arrays whose
+ * combined size is slightly greater than the JVM heap size. A previous
+ * implementation of Future.flatMap would retain references to each array,
+ * resulting in a speedy OutOfMemoryError. Now, each array should be freed soon
+ * after it is created and the test should complete without problems.
+ */
+object Test {
+ def main(args: Array[String]) {
+ def loop(i: Int, arraySize: Int): Future[Unit] = {
+ val array = new Array[Byte](arraySize)
+ Future.successful(i).flatMap { i =>
+ if (i == 0) {
+ Future.successful(())
+ } else {
+ array.size // Force closure to refer to array
+ loop(i - 1, arraySize)
+ }
+
+ }
+ }
+
+ val arraySize = 1000000
+ val tooManyArrays = (Runtime.getRuntime().totalMemory() / arraySize).toInt + 1
+ Await.ready(loop(tooManyArrays, arraySize), Duration.Inf)
+ }
+} \ No newline at end of file
diff --git a/test/files/run/t7455.check b/test/files/run/t7455.check
new file mode 100644
index 0000000000..0eb9342888
--- /dev/null
+++ b/test/files/run/t7455.check
@@ -0,0 +1,4 @@
+private[package <empty>] def <init>(x$1: String): Outer[E]
+private[package <empty>] def <init>(): Outer$PrivateInner
+private[package <empty>] def <init>(): Outer$PrivateStaticInner
+private[package <empty>] def <init>(x$2: String): Outer$PublicInner
diff --git a/test/files/run/t7455/Outer.java b/test/files/run/t7455/Outer.java
new file mode 100644
index 0000000000..10c97a9150
--- /dev/null
+++ b/test/files/run/t7455/Outer.java
@@ -0,0 +1,31 @@
+public class Outer<E> {
+ public void elements() {
+ new C<E>() {
+ };
+ }
+
+ private Outer(String a) {}
+
+ static class SubSelf extends Outer<String> {
+ public SubSelf() { super(""); }
+ }
+
+ private class PrivateInner {
+ }
+ class SubPrivateInner extends PrivateInner {
+ }
+
+ private class PublicInner {
+ private PublicInner(String a) {}
+ }
+ class SubPublicInner extends PublicInner {
+ public SubPublicInner() { super(""); }
+ }
+
+ private static class PrivateStaticInner {
+ }
+ public static class SubPrivateStaticInner extends PrivateStaticInner {
+ }
+}
+
+class C<E> {}
diff --git a/test/files/run/t7455/Test.scala b/test/files/run/t7455/Test.scala
new file mode 100644
index 0000000000..b23a724c78
--- /dev/null
+++ b/test/files/run/t7455/Test.scala
@@ -0,0 +1,30 @@
+import scala.tools.partest._
+
+// javac adds dummy parameters of type Outer$1 to synthetic access constructors
+// This test shows that we strip them from the signatures. If we don't, we trigger
+// parsing of Outer$1 which can fail if it references type parameters of the Outer.
+//
+// OLD OUTPUT:
+// private[package <empty>] def <init>(x$2: Outer$1): Outer$PrivateInner
+// error: error while loading Outer$1, class file 't7455-run.obj/Outer$1.class' is broken
+// (class java.util.NoSuchElementException/key not found: E)
+// ...
+object Test extends DirectTest {
+ override def code = ""
+
+ def show {
+ val classpath = List(sys.props("partest.lib"), testOutput.path) mkString sys.props("path.separator")
+ val compiler = newCompiler("-cp", classpath, "-d", testOutput.path)
+ import compiler._, definitions._
+ new compiler.Run
+
+ for {
+ name <- Seq("Outer", "Outer$PrivateInner", "Outer$PrivateStaticInner", "Outer$PublicInner")
+ clazz = compiler.rootMirror.staticClass(name)
+ constr <- clazz.info.member(nme.CONSTRUCTOR).alternatives
+ } {
+ println(constr.defString)
+ fullyInitializeSymbol(constr)
+ }
+ }
+}
diff --git a/test/files/run/t7617a.check b/test/files/run/t7617a.check
new file mode 100644
index 0000000000..94954abda4
--- /dev/null
+++ b/test/files/run/t7617a.check
@@ -0,0 +1,2 @@
+hello
+world
diff --git a/test/files/run/t7617a/Macros_1.scala b/test/files/run/t7617a/Macros_1.scala
new file mode 100644
index 0000000000..f9772c83c0
--- /dev/null
+++ b/test/files/run/t7617a/Macros_1.scala
@@ -0,0 +1,22 @@
+import scala.reflect.macros.Context
+import language.experimental.macros
+
+object Macros {
+ def getValueImpl[T](c: Context): c.Expr[T] = {
+ import c.universe._
+ c.Expr[T](Apply(Select(c.prefix.tree, newTermName("getVal")), Nil))
+ }
+ def setValueImpl[T](c: Context)(value: c.Expr[T]): c.Expr[Unit] = {
+ import c.universe._
+ c.Expr[Unit](Apply(Select(c.prefix.tree, newTermName("setVal")), List(value.tree)))
+ }
+}
+
+object Module {
+ private var _val: String = "hello"
+ def setVal(value: String): Unit = this._val = value
+ def getVal(): String = this._val
+
+ def value: String = macro Macros.getValueImpl[String]
+ def value_=(value: String): Unit = macro Macros.setValueImpl[String]
+}
diff --git a/test/files/run/t7617a/Test_2.scala b/test/files/run/t7617a/Test_2.scala
new file mode 100644
index 0000000000..da6e34e09d
--- /dev/null
+++ b/test/files/run/t7617a/Test_2.scala
@@ -0,0 +1,5 @@
+object Test extends App {
+ println(Module.value)
+ Module.value = "world"
+ println(Module.value)
+} \ No newline at end of file
diff --git a/test/files/run/t7617b.check b/test/files/run/t7617b.check
new file mode 100644
index 0000000000..81ec7e8b74
--- /dev/null
+++ b/test/files/run/t7617b.check
@@ -0,0 +1 @@
+foo = 2
diff --git a/test/files/run/t7617b/Macros_1.scala b/test/files/run/t7617b/Macros_1.scala
new file mode 100644
index 0000000000..bc919935c9
--- /dev/null
+++ b/test/files/run/t7617b/Macros_1.scala
@@ -0,0 +1,8 @@
+import scala.reflect.macros.Context
+
+object Macros {
+ def impl(c: Context)(name: c.Expr[String])(value: c.Expr[Any]) = {
+ import c.universe._
+ reify(println(s"${name.splice} = ${value.splice}"))
+ }
+} \ No newline at end of file
diff --git a/test/files/run/t7617b/Test_2.scala b/test/files/run/t7617b/Test_2.scala
new file mode 100644
index 0000000000..e27f650e80
--- /dev/null
+++ b/test/files/run/t7617b/Test_2.scala
@@ -0,0 +1,11 @@
+import scala.language.dynamics
+import language.experimental.macros
+
+class C extends Dynamic {
+ def updateDynamic(name: String)(value: Any) = macro Macros.impl
+}
+
+object Test extends App {
+ val c = new C
+ c.foo = 2
+} \ No newline at end of file
diff --git a/test/files/run/t7657.check b/test/files/run/t7657.check
new file mode 100644
index 0000000000..c25d8d1c1b
--- /dev/null
+++ b/test/files/run/t7657.check
@@ -0,0 +1,3 @@
+()
+()
+()
diff --git a/test/files/run/t7657/Macros_1.scala b/test/files/run/t7657/Macros_1.scala
new file mode 100644
index 0000000000..b1e31aa2dd
--- /dev/null
+++ b/test/files/run/t7657/Macros_1.scala
@@ -0,0 +1,8 @@
+import scala.reflect.macros.Context
+import language.experimental.macros
+
+trait T { def t(): Unit }
+abstract class A extends T { override def t(): Unit = () }
+
+object Macro { def t(c: Context)(): c.Expr[Unit] = c.universe.reify(()) }
+class C extends A { override def t(): Unit = macro Macro.t }
diff --git a/test/files/run/t7657/Test_2.scala b/test/files/run/t7657/Test_2.scala
new file mode 100644
index 0000000000..5cc46b6aa1
--- /dev/null
+++ b/test/files/run/t7657/Test_2.scala
@@ -0,0 +1,6 @@
+object Test extends App {
+ val c = new C()
+ println(c.t())
+ println((c: T).t())
+ println((c: A).t())
+} \ No newline at end of file
diff --git a/test/junit/scala/concurrent/impl/DefaultPromiseTest.scala b/test/junit/scala/concurrent/impl/DefaultPromiseTest.scala
new file mode 100644
index 0000000000..f3a75e24d0
--- /dev/null
+++ b/test/junit/scala/concurrent/impl/DefaultPromiseTest.scala
@@ -0,0 +1,344 @@
+package scala.concurrent.impl
+
+import java.util.concurrent.ConcurrentLinkedQueue
+import java.util.concurrent.CountDownLatch
+import org.junit.Assert._
+import org.junit.{ After, Before, Test }
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import scala.annotation.tailrec
+import scala.concurrent.ExecutionContext
+import scala.concurrent.impl.Promise.DefaultPromise
+import scala.util.{ Failure, Success, Try }
+import scala.util.control.NonFatal
+
+/** Tests for the private class DefaultPromise */
+@RunWith(classOf[JUnit4])
+class DefaultPromiseTest {
+
+ // Many tests in this class use a helper class, Tester, to track the state of
+ // promises and to ensure they behave correctly, particularly the complex behaviour
+ // of linking.
+
+ type Result = Int
+ type PromiseId = Int
+ type HandlerId = Int
+ type ChainId = Int
+
+ /** The state of a set of set of linked promises. */
+ case class Chain(
+ promises: Set[PromiseId],
+ state: Either[Set[HandlerId],Try[Result]]
+ )
+
+ /** A helper class that provides methods for creating, linking, completing and
+ * adding handlers to promises. With each operation it verifies that handlers
+ * are called, any expected exceptions are thrown, and that all promises have
+ * the expected value.
+ *
+ * The links between promises are not tracked precisely. Instead, linked promises
+ * are placed in the same Chain object. Each link in the same chain will share
+ * the same value.
+ */
+ class Tester {
+ var promises = Map.empty[PromiseId, DefaultPromise[Result]]
+ var chains = Map.empty[ChainId, Chain]
+
+ private var counter = 0
+ private def freshId(): Int = {
+ val id = counter
+ counter += 1
+ id
+ }
+
+ /** Handlers report their activity on this queue */
+ private val handlerQueue = new ConcurrentLinkedQueue[(Try[Result], HandlerId)]()
+
+ /** Get the chain for a given promise */
+ private def promiseChain(p: PromiseId): Option[(ChainId, Chain)] = {
+ val found: Iterable[(ChainId, Chain)] = for ((cid, c) <- chains; p0 <- c.promises; if (p0 == p)) yield ((cid, c))
+ found.toList match {
+ case Nil => None
+ case x::Nil => Some(x)
+ case _ => throw new IllegalStateException(s"Promise $p found in more than one chain")
+ }
+ }
+
+ /** Passed to `checkEffect` to indicate the expected effect of an operation */
+ sealed trait Effect
+ case object NoEffect extends Effect
+ case class HandlersFired(result: Try[Result], handlers: Set[HandlerId]) extends Effect
+ case object MaybeIllegalThrown extends Effect
+ case object IllegalThrown extends Effect
+
+ /** Runs an operation while verifying that the operation has the expected effect */
+ private def checkEffect(expected: Effect)(f: => Any) {
+ assert(handlerQueue.isEmpty()) // Should have been cleared by last usage
+ val result = Try(f)
+
+ var fireCounts = Map.empty[(Try[Result], HandlerId), Int]
+ while (!handlerQueue.isEmpty()) {
+ val key = handlerQueue.poll()
+ val newCount = fireCounts.getOrElse(key, 0) + 1
+ fireCounts = fireCounts.updated(key, newCount)
+ }
+
+ def assertIllegalResult = result match {
+ case Failure(e: IllegalStateException) => ()
+ case _ => fail(s"Expected IllegalStateException: $result")
+ }
+
+ expected match {
+ case NoEffect =>
+ assertTrue(s"Shouldn't throw exception: $result", result.isSuccess)
+ assertEquals(Map.empty[(Try[Result], HandlerId), Int], fireCounts)
+ case HandlersFired(firingResult, handlers) =>
+ assert(result.isSuccess)
+ val expectedCounts = handlers.foldLeft(Map.empty[(Try[Result], HandlerId), Int]) {
+ case (map, hid) => map.updated((firingResult, hid), 1)
+ }
+ assertEquals(expectedCounts, fireCounts)
+ case MaybeIllegalThrown =>
+ if (result.isFailure) assertIllegalResult
+ assertEquals(Map.empty, fireCounts)
+ case IllegalThrown =>
+ assertIllegalResult
+ assertEquals(Map.empty, fireCounts)
+ }
+ }
+
+ /** Check each promise has the expected value. */
+ private def assertPromiseValues() {
+ for ((cid, chain) <- chains; p <- chain.promises) {
+ chain.state match {
+ case Right(result) => assertEquals(Some(result), promises(p).value)
+ case Left(_) => ()
+ }
+ }
+ }
+
+ /** Create a promise, returning a handle. */
+ def newPromise(): PromiseId = {
+ val pid = freshId()
+ val cid = freshId()
+ promises = promises.updated(pid, new DefaultPromise[Result]())
+ chains = chains.updated(cid, Chain(Set(pid), Left(Set.empty)))
+ assertPromiseValues()
+ pid
+ }
+
+ /** Complete a promise */
+ def complete(p: PromiseId) {
+ val r = Success(freshId())
+ val (cid, chain) = promiseChain(p).get
+ val (completionEffect, newState) = chain.state match {
+ case Left(handlers) => (HandlersFired(r, handlers), Right(r))
+ case Right(completion) => (IllegalThrown, chain.state)
+ }
+ checkEffect(completionEffect) { promises(p).complete(r) }
+ chains = chains.updated(cid, chain.copy(state = newState))
+ assertPromiseValues()
+ }
+
+ /** Attempt to link two promises together */
+ def link(a: PromiseId, b: PromiseId): (ChainId, ChainId) = {
+ val promiseA = promises(a)
+ val promiseB = promises(b)
+ val (cidA, chainA) = promiseChain(a).get
+ val (cidB, chainB) = promiseChain(b).get
+
+ // Examine the state of each promise's chain to work out
+ // the effect of linking the promises, and to work out
+ // if the two chains should be merged.
+
+ sealed trait MergeOp
+ case object NoMerge extends MergeOp
+ case class Merge(state: Either[Set[HandlerId],Try[Result]]) extends MergeOp
+
+ val (linkEffect, mergeOp) = (chainA.state, chainB.state) match {
+ case (Left(handlers1), Left(handlers2)) =>
+ (NoEffect, Merge(Left(handlers1 ++ handlers2)))
+ case (Left(handlers), Right(result)) =>
+ (HandlersFired(result, handlers), Merge(Right(result)))
+ case (Right(result), Left(handlers)) =>
+ (HandlersFired(result, handlers), Merge(Right(result)))
+ case (Right(_), Right(_)) if (cidA == cidB) =>
+ (MaybeIllegalThrown, NoMerge) // Won't be thrown if happen to link a promise to itself
+ case (Right(_), Right(_)) =>
+ (IllegalThrown, NoMerge)
+ }
+
+ // Perform the linking and merge the chains, if appropriate
+
+ checkEffect(linkEffect) { promiseA.linkRootOf(promiseB) }
+
+ val (newCidA, newCidB) = mergeOp match {
+ case NoMerge => (cidA, cidB)
+ case Merge(newState) => {
+ chains = chains - cidA
+ chains = chains - cidB
+ val newCid = freshId()
+ chains = chains.updated(newCid, Chain(chainA.promises ++ chainB.promises, newState))
+ (newCid, newCid)
+ }
+ }
+ assertPromiseValues()
+ (newCidA, newCidB)
+ }
+
+ /** Attach an onComplete handler. When called, the handler will
+ * place an entry into `handlerQueue` with the handler's identity.
+ * This allows verification of handler calling semantics.
+ */
+ def attachHandler(p: PromiseId): HandlerId = {
+ val hid = freshId()
+ val promise = promises(p)
+ val (cid, chain) = promiseChain(p).get
+ val (attachEffect, newState) = chain.state match {
+ case Left(handlers) =>
+ (NoEffect, Left(handlers + hid))
+ case Right(result) =>
+ (HandlersFired(result, Set(hid)), Right(result))
+ }
+ implicit val ec = new ExecutionContext {
+ def execute(r: Runnable) { r.run() }
+ def reportFailure(t: Throwable) { t.printStackTrace() }
+ }
+
+ checkEffect(attachEffect) { promise.onComplete(result => handlerQueue.add((result, hid))) }
+ chains = chains.updated(cid, chain.copy(state = newState))
+ assertPromiseValues()
+ hid
+ }
+ }
+
+ // Some methods and objects that build a list of promise
+ // actions to test and then execute them
+
+ type PromiseKey = Int
+
+ sealed trait Action
+ case class Complete(p: PromiseKey) extends Action
+ case class Link(a: PromiseKey, b: PromiseKey) extends Action
+ case class AttachHandler(p: PromiseKey) extends Action
+
+ /** Tests a sequence of actions on a Tester. Creates promises as needed. */
+ private def testActions(actions: Seq[Action]) {
+ val t = new Tester()
+ var pMap = Map.empty[PromiseKey, PromiseId]
+ def byKey(key: PromiseKey): PromiseId = {
+ if (!pMap.contains(key)) {
+ pMap = pMap.updated(key, t.newPromise())
+ }
+ pMap(key)
+ }
+
+ actions foreach { action =>
+ action match {
+ case Complete(p) => t.complete(byKey(p))
+ case Link(a, b) => t.link(byKey(a), byKey(b))
+ case AttachHandler(p) => t.attachHandler(byKey(p))
+ }
+ }
+ }
+
+ /** Tests all permutations of actions for `count` promises */
+ private def testPermutations(count: Int) {
+ val ps = (0 until count).toList
+ val pPairs = for (a <- ps; b <- ps) yield (a, b)
+
+ var allActions = ps.map(Complete(_)) ++ pPairs.map { case (a, b) => Link(a, b) } ++ ps.map(AttachHandler(_))
+ for ((permutation, i) <- allActions.permutations.zipWithIndex) {
+ testActions(permutation)
+ }
+ }
+
+ /** Test all permutations of actions with a single promise */
+ @Test
+ def testPermutations1 {
+ testPermutations(1)
+ }
+
+ /** Test all permutations of actions with two promises - about 40 thousand */
+ @Test
+ def testPermutations2 {
+ testPermutations(2)
+ }
+
+ /** Link promises in different orders, using the same link structure as is
+ * used in Future.flatMap */
+ @Test
+ def simulateFlatMapLinking {
+ val random = new scala.util.Random(1)
+ for (_ <- 0 until 10) {
+ val t = new Tester()
+ val flatMapCount = 100
+
+ sealed trait FlatMapEvent
+ case class Link(a: PromiseId, b: PromiseId) extends FlatMapEvent
+ case class Complete(p: PromiseId) extends FlatMapEvent
+
+ @tailrec
+ def flatMapEvents(count: Int, p1: PromiseId, acc: List[FlatMapEvent]): List[FlatMapEvent] = {
+ if (count == 0) {
+ Complete(p1)::acc
+ } else {
+ val p2 = t.newPromise()
+ flatMapEvents(count - 1, p2, Link(p2, p1)::acc)
+ }
+ }
+
+ val events = flatMapEvents(flatMapCount, t.newPromise(), Nil)
+ assertEquals(flatMapCount + 1, t.chains.size) // All promises are unlinked
+ val shuffled = random.shuffle(events)
+ shuffled foreach {
+ case Link(a, b) => t.link(a, b)
+ case Complete(p) => t.complete(p)
+ }
+ // All promises should be linked together, no matter the order of their linking
+ assertEquals(1, t.chains.size)
+ }
+ }
+
+ /** Link promises together on more than one thread, using the same link
+ * structure as is used in Future.flatMap */
+ @Test
+ def testFlatMapLinking {
+ for (_ <- 0 until 100) {
+ val flatMapCount = 100
+ val startLatch = new CountDownLatch(1)
+ val doneLatch = new CountDownLatch(flatMapCount + 1)
+ def execute(f: => Unit) {
+ val ec = ExecutionContext.global
+ ec.execute(new Runnable {
+ def run() {
+ try {
+ startLatch.await()
+ f
+ doneLatch.countDown()
+ } catch {
+ case NonFatal(e) => ec.reportFailure(e)
+ }
+ }
+ })
+ }
+ @tailrec
+ def flatMapTimes(count: Int, p1: DefaultPromise[Int]) {
+ if (count == 0) {
+ execute { p1.success(1) }
+ } else {
+ val p2 = new DefaultPromise[Int]()
+ execute { p2.linkRootOf(p1) }
+ flatMapTimes(count - 1, p2)
+ }
+ }
+
+ val p = new DefaultPromise[Int]()
+ flatMapTimes(flatMapCount, p)
+ startLatch.countDown()
+ doneLatch.await()
+ assertEquals(Some(Success(1)), p.value)
+ }
+ }
+
+}
diff --git a/test/pending/junit/scala/util/t7265.scala b/test/pending/junit/scala/util/t7265.scala
new file mode 100644
index 0000000000..3b8fa80dbe
--- /dev/null
+++ b/test/pending/junit/scala/util/t7265.scala
@@ -0,0 +1,46 @@
+
+package scala.util
+package test
+
+import org.junit.Assert._
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+import scala.util.PropertiesTrait
+
+/** The java version property uses the spec version
+ * and must work for all "major.minor" and fail otherwise.
+ */
+@RunWith(classOf[JUnit4])
+class SpecVersionTest {
+ val sut = new PropertiesTrait {
+ override def javaSpecVersion = "1.7"
+
+ override protected def pickJarBasedOn: Class[_] = ???
+ override protected def propCategory: String = "test"
+
+ // override because of vals like releaseVersion
+ override lazy val scalaProps = new java.util.Properties
+ }
+
+ @Test
+ def comparesCorrectly(): Unit = {
+ assert(sut isJavaAtLeast "1.5")
+ assert(sut isJavaAtLeast "1.6")
+ assert(sut isJavaAtLeast "1.7")
+ assert(!(sut isJavaAtLeast "1.8"))
+ }
+ @Test(expected = classOf[NumberFormatException])
+ def badVersion(): Unit = {
+ sut isJavaAtLeast "1.a"
+ }
+ @Test(expected = classOf[NumberFormatException])
+ def missingVersion(): Unit = {
+ sut isJavaAtLeast "1"
+ }
+ @Test(expected = classOf[NumberFormatException])
+ def notASpec(): Unit = {
+ sut isJavaAtLeast "1.7.1"
+ }
+}