From 7c049a15f6cb3992abc6debabe2b53b2097ffb8a Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 13 Apr 2012 15:38:50 +0200 Subject: Updating to latest version of Akka's DefaultPromise --- src/library/scala/concurrent/Future.scala | 20 ++- .../scala/concurrent/impl/AbstractPromise.java | 6 +- src/library/scala/concurrent/impl/Future.scala | 2 +- src/library/scala/concurrent/impl/Promise.scala | 135 +++++++-------------- 4 files changed, 58 insertions(+), 105 deletions(-) diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index 1463dbcebf..16432f6aac 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -511,16 +511,15 @@ trait Future[+T] extends Awaitable[T] { * Note: using this method yields nondeterministic dataflow programs. */ object Future { - - /** Starts an asynchronous computation and returns a `Future` object with the result of that computation. - * - * The result becomes available once the asynchronous computation is completed. - * - * @tparam T the type of the result - * @param body the asychronous computation - * @param execctx the execution context on which the future is run - * @return the `Future` holding the result of the computation - */ + /** Starts an asynchronous computation and returns a `Future` object with the result of that computation. + * + * The result becomes available once the asynchronous computation is completed. + * + * @tparam T the type of the result + * @param body the asychronous computation + * @param execctx the execution context on which the future is run + * @return the `Future` holding the result of the computation + */ def apply[T](body: =>T)(implicit executor: ExecutionContext): Future[T] = impl.Future(body) import scala.collection.mutable.Builder @@ -614,4 +613,3 @@ object Future { - diff --git a/src/library/scala/concurrent/impl/AbstractPromise.java b/src/library/scala/concurrent/impl/AbstractPromise.java index 5280d67854..8aac5de042 100644 --- a/src/library/scala/concurrent/impl/AbstractPromise.java +++ b/src/library/scala/concurrent/impl/AbstractPromise.java @@ -15,7 +15,7 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; abstract class AbstractPromise { - private volatile Object _ref = null; + private volatile Object _ref; protected final static AtomicReferenceFieldUpdater updater = - AtomicReferenceFieldUpdater.newUpdater(AbstractPromise.class, Object.class, "_ref"); -} + AtomicReferenceFieldUpdater.newUpdater(AbstractPromise.class, Object.class, "_ref"); +} \ No newline at end of file diff --git a/src/library/scala/concurrent/impl/Future.scala b/src/library/scala/concurrent/impl/Future.scala index a3c8ed3095..72ffa6a014 100644 --- a/src/library/scala/concurrent/impl/Future.scala +++ b/src/library/scala/concurrent/impl/Future.scala @@ -28,7 +28,7 @@ private[concurrent] trait Future[+T] extends scala.concurrent.Future[T] with Awa /** Tests whether this Future has been completed. */ - final def isCompleted: Boolean = value.isDefined + def isCompleted: Boolean /** The contained value of this Future. Before this Future is completed * the value will be None. After completion the value will be Some(Right(t)) diff --git a/src/library/scala/concurrent/impl/Promise.scala b/src/library/scala/concurrent/impl/Promise.scala index 07b6d1f278..ef87f27d63 100644 --- a/src/library/scala/concurrent/impl/Promise.scala +++ b/src/library/scala/concurrent/impl/Promise.scala @@ -74,37 +74,10 @@ private[concurrent] trait Promise[T] extends scala.concurrent.Promise[T] with Fu object Promise { - - def dur2long(dur: Duration): Long = if (dur.isFinite) dur.toNanos else Long.MaxValue - - def EmptyPending[T](): FState[T] = emptyPendingValue.asInstanceOf[FState[T]] - - /** Represents the internal state. - * - * [adriaan] it's unsound to make FState covariant (tryComplete won't type check) - */ - sealed trait FState[T] { def value: Option[Either[Throwable, T]] } - - case class Pending[T](listeners: List[Either[Throwable, T] => Any] = Nil) extends FState[T] { - def value: Option[Either[Throwable, T]] = None - } - - case class Success[T](value: Option[Either[Throwable, T]] = None) extends FState[T] { - def result: T = value.get.right.get - } - - case class Failure[T](value: Option[Either[Throwable, T]] = None) extends FState[T] { - def exception: Throwable = value.get.left.get - } - - private val emptyPendingValue = Pending[Nothing](Nil) - /** Default promise implementation. */ - class DefaultPromise[T](implicit val executor: ExecutionContext) extends AbstractPromise with Promise[T] { - self => - - updater.set(this, Promise.EmptyPending()) + class DefaultPromise[T](implicit val executor: ExecutionContext) extends AbstractPromise with Promise[T] { self => + updater.set(this, Nil) // Start at "No callbacks" //FIXME switch to Unsafe instead of ARFU protected final def tryAwait(atMost: Duration): Boolean = { @tailrec @@ -115,7 +88,7 @@ object Promise { val start = System.nanoTime() try { synchronized { - while (value.isEmpty) wait(ms, ns) + while (!isCompleted) wait(ms, ns) } } catch { case e: InterruptedException => @@ -123,93 +96,91 @@ object Promise { awaitUnsafe(waitTimeNanos - (System.nanoTime() - start)) } else - value.isDefined + isCompleted } - - blocking(Future.body2awaitable(awaitUnsafe(dur2long(atMost))), atMost) + //FIXME do not do this if there'll be no waiting + blocking(Future.body2awaitable(awaitUnsafe(if (atMost.isFinite) atMost.toNanos else Long.MaxValue)), atMost) } + @throws(classOf[TimeoutException]) def ready(atMost: Duration)(implicit permit: CanAwait): this.type = - if (value.isDefined || tryAwait(atMost)) this + if (isCompleted || tryAwait(atMost)) this else throw new TimeoutException("Futures timed out after [" + atMost.toMillis + "] milliseconds") + @throws(classOf[Exception]) def result(atMost: Duration)(implicit permit: CanAwait): T = ready(atMost).value.get match { case Left(e) => throw e case Right(r) => r } - def value: Option[Either[Throwable, T]] = getState.value + def value: Option[Either[Throwable, T]] = getState match { + case _: List[_] ⇒ None + case c: Either[_, _] ⇒ Some(c.asInstanceOf[Either[Throwable, T]]) + } + + override def isCompleted(): Boolean = getState match { // Cheaper than boxing result into Option due to "def value" + case _: Either[_, _] ⇒ true + case _ ⇒ false + } @inline - private[this] final def updater = AbstractPromise.updater.asInstanceOf[AtomicReferenceFieldUpdater[AbstractPromise, FState[T]]] + private[this] final def updater = AbstractPromise.updater.asInstanceOf[AtomicReferenceFieldUpdater[AbstractPromise, AnyRef]] @inline - protected final def updateState(oldState: FState[T], newState: FState[T]): Boolean = updater.compareAndSet(this, oldState, newState) + protected final def updateState(oldState: AnyRef, newState: AnyRef): Boolean = updater.compareAndSet(this, oldState, newState) @inline - protected final def getState: FState[T] = updater.get(this) + protected final def getState: AnyRef = updater.get(this) def tryComplete(value: Either[Throwable, T]): Boolean = { - val callbacks: List[Either[Throwable, T] => Any] = { + val callbacks: List[Either[Throwable, T] ⇒ Unit] = { try { @tailrec - def tryComplete(v: Either[Throwable, T]): List[Either[Throwable, T] => Any] = { + def tryComplete(v: Either[Throwable, T]): List[Either[Throwable, T] ⇒ Unit] = { getState match { - case cur @ Pending(listeners) => - val newState = - if (v.isLeft) Failure(Some(v.asInstanceOf[Left[Throwable, T]])) - else Success(Some(v.asInstanceOf[Right[Throwable, T]])) - - if (updateState(cur, newState)) listeners - else tryComplete(v) - case _ => null + case raw: List[_] ⇒ + val cur = raw.asInstanceOf[List[Either[Throwable, T] ⇒ Unit]] + if (updateState(cur, v)) cur else tryComplete(v) + case _ ⇒ null } } tryComplete(resolveEither(value)) } finally { - synchronized { notifyAll() } // notify any blockers from `tryAwait` + synchronized { notifyAll() } //Notify any evil blockers } } callbacks match { - case null => false - case cs if cs.isEmpty => true - case cs => - Future.dispatchFuture(executor, { - () => cs.foreach(f => notifyCompleted(f, value)) - }) - true + case null ⇒ false + case cs if cs.isEmpty ⇒ true + case cs ⇒ Future.dispatchFuture(executor, () ⇒ cs.foreach(f ⇒ notifyCompleted(f, value))); true } } - def onComplete[U](func: Either[Throwable, T] => U): this.type = { - @tailrec // Returns whether the future has already been completed or not - def tryAddCallback(): Boolean = { + def onComplete[U](func: Either[Throwable, T] ⇒ U): this.type = { + @tailrec //Returns whether the future has already been completed or not + def tryAddCallback(): Either[Throwable, T] = { val cur = getState cur match { - case _: Success[_] | _: Failure[_] => true - case p: Pending[_] => - val pt = p.asInstanceOf[Pending[T]] - if (updateState(pt, pt.copy(listeners = func :: pt.listeners))) false else tryAddCallback() + case r: Either[_, _] ⇒ r.asInstanceOf[Either[Throwable, T]] + case listeners: List[_] ⇒ if (updateState(listeners, func :: listeners)) null else tryAddCallback() } } - if (tryAddCallback()) { - val result = value.get - Future.dispatchFuture(executor, { - () => notifyCompleted(func, result) - }) + tryAddCallback() match { + case null ⇒ this + case completed ⇒ + Future.dispatchFuture(executor, () ⇒ notifyCompleted(func, completed)) + this } - - this } private final def notifyCompleted(func: Either[Throwable, T] => Any, result: Either[Throwable, T]) { try { func(result) } catch { - case e => executor.reportFailure(e) + case /*NonFatal(*/e/*)*/ => executor.reportFailure(e) } } } @@ -222,13 +193,13 @@ object Promise { val value = Some(resolveEither(suppliedValue)) + override def isCompleted(): Boolean = true + def tryComplete(value: Either[Throwable, T]): Boolean = false def onComplete[U](func: Either[Throwable, T] => U): this.type = { val completedAs = value.get - Future.dispatchFuture(executor, { - () => func(completedAs) - }) + Future.dispatchFuture(executor, () => func(completedAs)) this } @@ -241,19 +212,3 @@ object Promise { } } - - - - - - - - - - - - - - - - -- cgit v1.2.3 From a2115b2352700dfc32a99663086871a2cd192685 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 13 Apr 2012 15:57:10 +0200 Subject: Adding NonFatal and Unsafe to scala.concurrent.impl --- src/library/scala/concurrent/impl/NonFatal.scala | 36 ++++++++++++++++++++++++ src/library/scala/concurrent/impl/Unsafe.java | 32 +++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/library/scala/concurrent/impl/NonFatal.scala create mode 100644 src/library/scala/concurrent/impl/Unsafe.java diff --git a/src/library/scala/concurrent/impl/NonFatal.scala b/src/library/scala/concurrent/impl/NonFatal.scala new file mode 100644 index 0000000000..ac0cddaf1b --- /dev/null +++ b/src/library/scala/concurrent/impl/NonFatal.scala @@ -0,0 +1,36 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.concurrent.impl + +/** + * Extractor of non-fatal Throwables. Will not match fatal errors + * like VirtualMachineError (OutOfMemoryError) + * ThreadDeath, LinkageError and InterruptedException. + * StackOverflowError is matched, i.e. considered non-fatal. + * + * Usage to catch all harmless throwables: + * {{{ + * try { + * // dangerous stuff + * } catch { + * case NonFatal(e) => log.error(e, "Something not that bad") + * } + * }}} + */ +object NonFatal { + + def unapply(t: Throwable): Option[Throwable] = t match { + case e: StackOverflowError ⇒ Some(e) // StackOverflowError ok even though it is a VirtualMachineError + // VirtualMachineError includes OutOfMemoryError and other fatal errors + case _: VirtualMachineError | _: ThreadDeath | _: InterruptedException | _: LinkageError ⇒ None + case e ⇒ Some(e) + } + +} + diff --git a/src/library/scala/concurrent/impl/Unsafe.java b/src/library/scala/concurrent/impl/Unsafe.java new file mode 100644 index 0000000000..3c695c3905 --- /dev/null +++ b/src/library/scala/concurrent/impl/Unsafe.java @@ -0,0 +1,32 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + + +package scala.concurrent.impl; + +import java.lang.reflect.Field; + +public final class Unsafe { + public final static sun.misc.Unsafe instance; + static { + try { + sun.misc.Unsafe found = null; + for(Field field : sun.misc.Unsafe.class.getDeclaredFields()) { + if (field.getType() == sun.misc.Unsafe.class) { + field.setAccessible(true); + found = (sun.misc.Unsafe) field.get(null); + break; + } + } + if (found == null) throw new IllegalStateException("Can't find instance of sun.misc.Unsafe"); + else instance = found; + } catch(Throwable t) { + throw new ExceptionInInitializerError(t); + } + } +} -- cgit v1.2.3 From 828aa0aaa9288c57f4574ca267a38173d15c458f Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 13 Apr 2012 17:21:52 +0200 Subject: Making sure that the ScalaDoc for Future.apply has the right names --- src/library/scala/concurrent/Future.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index 16432f6aac..f331263d39 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -520,7 +520,7 @@ object Future { * @param execctx the execution context on which the future is run * @return the `Future` holding the result of the computation */ - def apply[T](body: =>T)(implicit executor: ExecutionContext): Future[T] = impl.Future(body) + def apply[T](body: =>T)(implicit execctx: ExecutionContext): Future[T] = impl.Future(body) import scala.collection.mutable.Builder import scala.collection.generic.CanBuildFrom -- cgit v1.2.3 From 05779d413ed8de3717307b78cccb413f0a687101 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 13 Apr 2012 17:33:34 +0200 Subject: Replacing all ⇒ with => in Promise.scala and harmonized def value to use same match approach as def isCompleted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/library/scala/concurrent/impl/Promise.scala | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/library/scala/concurrent/impl/Promise.scala b/src/library/scala/concurrent/impl/Promise.scala index ef87f27d63..140cfa93a0 100644 --- a/src/library/scala/concurrent/impl/Promise.scala +++ b/src/library/scala/concurrent/impl/Promise.scala @@ -115,13 +115,13 @@ object Promise { } def value: Option[Either[Throwable, T]] = getState match { - case _: List[_] ⇒ None - case c: Either[_, _] ⇒ Some(c.asInstanceOf[Either[Throwable, T]]) + case c: Either[_, _] => Some(c.asInstanceOf[Either[Throwable, T]]) + case _ => None } override def isCompleted(): Boolean = getState match { // Cheaper than boxing result into Option due to "def value" - case _: Either[_, _] ⇒ true - case _ ⇒ false + case _: Either[_, _] => true + case _ => false } @inline @@ -134,15 +134,15 @@ object Promise { protected final def getState: AnyRef = updater.get(this) def tryComplete(value: Either[Throwable, T]): Boolean = { - val callbacks: List[Either[Throwable, T] ⇒ Unit] = { + val callbacks: List[Either[Throwable, T] => Unit] = { try { @tailrec - def tryComplete(v: Either[Throwable, T]): List[Either[Throwable, T] ⇒ Unit] = { + def tryComplete(v: Either[Throwable, T]): List[Either[Throwable, T] => Unit] = { getState match { - case raw: List[_] ⇒ - val cur = raw.asInstanceOf[List[Either[Throwable, T] ⇒ Unit]] + case raw: List[_] => + val cur = raw.asInstanceOf[List[Either[Throwable, T] => Unit]] if (updateState(cur, v)) cur else tryComplete(v) - case _ ⇒ null + case _ => null } } tryComplete(resolveEither(value)) @@ -152,26 +152,26 @@ object Promise { } callbacks match { - case null ⇒ false - case cs if cs.isEmpty ⇒ true - case cs ⇒ Future.dispatchFuture(executor, () ⇒ cs.foreach(f ⇒ notifyCompleted(f, value))); true + case null => false + case cs if cs.isEmpty => true + case cs => Future.dispatchFuture(executor, () => cs.foreach(f => notifyCompleted(f, value))); true } } - def onComplete[U](func: Either[Throwable, T] ⇒ U): this.type = { + def onComplete[U](func: Either[Throwable, T] => U): this.type = { @tailrec //Returns whether the future has already been completed or not def tryAddCallback(): Either[Throwable, T] = { val cur = getState cur match { - case r: Either[_, _] ⇒ r.asInstanceOf[Either[Throwable, T]] - case listeners: List[_] ⇒ if (updateState(listeners, func :: listeners)) null else tryAddCallback() + case r: Either[_, _] => r.asInstanceOf[Either[Throwable, T]] + case listeners: List[_] => if (updateState(listeners, func :: listeners)) null else tryAddCallback() } } tryAddCallback() match { - case null ⇒ this - case completed ⇒ - Future.dispatchFuture(executor, () ⇒ notifyCompleted(func, completed)) + case null => this + case completed => + Future.dispatchFuture(executor, () => notifyCompleted(func, completed)) this } } -- cgit v1.2.3 From af8416bb7ff4d6816f416cd0671e26e2cdc653d7 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 13 Apr 2012 18:11:38 +0200 Subject: Adding NonFatal matching as to not catch anything that should terminate the JVM, also adding EX.reportFailure where it makes sense --- src/library/scala/concurrent/Future.scala | 11 ++++++----- src/library/scala/concurrent/impl/Future.scala | 12 ++++-------- src/library/scala/concurrent/impl/Promise.scala | 2 +- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index f331263d39..40acea91c9 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -18,6 +18,7 @@ import java.{ lang => jl } import java.util.concurrent.atomic.{ AtomicReferenceFieldUpdater, AtomicInteger, AtomicBoolean } import scala.concurrent.util.Duration +import scala.concurrent.impl.NonFatal import scala.Option import scala.annotation.tailrec @@ -203,7 +204,7 @@ trait Future[+T] extends Awaitable[T] { case Right(v) => try p success f(v) catch { - case t => p complete resolver(t) + case NonFatal(t) => p complete resolver(t) } } @@ -229,7 +230,7 @@ trait Future[+T] extends Awaitable[T] { case Right(v) => p success v } } catch { - case t: Throwable => p complete resolver(t) + case NonFatal(t) => p complete resolver(t) } } @@ -262,7 +263,7 @@ trait Future[+T] extends Awaitable[T] { if (pred(v)) p success v else p failure new NoSuchElementException("Future.filter predicate is not satisfied by: " + v) } catch { - case t: Throwable => p complete resolver(t) + case NonFatal(t) => p complete resolver(t) } } @@ -336,7 +337,7 @@ trait Future[+T] extends Awaitable[T] { onComplete { case Left(t) if pf isDefinedAt t => try { p success pf(t) } - catch { case t: Throwable => p complete resolver(t) } + catch { case NonFatal(t) => p complete resolver(t) } case otherwise => p complete otherwise } @@ -364,7 +365,7 @@ trait Future[+T] extends Awaitable[T] { try { p completeWith pf(t) } catch { - case t: Throwable => p complete resolver(t) + case NonFatal(t) => p complete resolver(t) } case otherwise => p complete otherwise } diff --git a/src/library/scala/concurrent/impl/Future.scala b/src/library/scala/concurrent/impl/Future.scala index 72ffa6a014..ca13981163 100644 --- a/src/library/scala/concurrent/impl/Future.scala +++ b/src/library/scala/concurrent/impl/Future.scala @@ -77,7 +77,9 @@ object Future { try { Right(body) } catch { - case e => scala.concurrent.resolver(e) + case NonFatal(e) => + executor.reportFailure(e) + scala.concurrent.resolver(e) } } } @@ -115,13 +117,7 @@ object Future { _taskStack set taskStack while (taskStack.nonEmpty) { val next = taskStack.pop() - try { - next.apply() - } catch { - case e => - // TODO catching all and continue isn't good for OOME - executor.reportFailure(e) - } + try next() catch { case NonFatal(e) => executor reportFailure e } } } finally { _taskStack.remove() diff --git a/src/library/scala/concurrent/impl/Promise.scala b/src/library/scala/concurrent/impl/Promise.scala index 140cfa93a0..1388c8b357 100644 --- a/src/library/scala/concurrent/impl/Promise.scala +++ b/src/library/scala/concurrent/impl/Promise.scala @@ -180,7 +180,7 @@ object Promise { try { func(result) } catch { - case /*NonFatal(*/e/*)*/ => executor.reportFailure(e) + case NonFatal(e) => executor reportFailure e } } } -- cgit v1.2.3 From 31771bdc6246c0b5c58ec8ded4d7bb411eb15fff Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 13 Apr 2012 18:23:16 +0200 Subject: Adding FIXME about putting mixed beans in the EC-basket --- src/library/scala/concurrent/impl/Future.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/scala/concurrent/impl/Future.scala b/src/library/scala/concurrent/impl/Future.scala index ca13981163..1cc9e95463 100644 --- a/src/library/scala/concurrent/impl/Future.scala +++ b/src/library/scala/concurrent/impl/Future.scala @@ -109,7 +109,7 @@ object Future { private[impl] def dispatchFuture(executor: ExecutionContext, task: () => Unit, force: Boolean = false): Unit = _taskStack.get match { - case stack if (stack ne null) && !force => stack push task + case stack if (stack ne null) && !force => stack push task // FIXME we can't mix tasks aimed for different ExecutionContexts see: https://github.com/akka/akka/blob/v2.0.1/akka-actor/src/main/scala/akka/dispatch/Future.scala#L373 case _ => executor.execute(new Runnable { def run() { try { -- cgit v1.2.3 From 412885a92e4621af33bbb2c265627f5854e30ba1 Mon Sep 17 00:00:00 2001 From: Heather Miller Date: Fri, 13 Apr 2012 18:47:04 +0200 Subject: Adds most recent iteration of the ForkJoinPool, and updates it to now run on JDK 1.6 --- .../scala/concurrent/forkjoin/ForkJoinPool.java | 414 ++++++++++----------- .../scala/concurrent/forkjoin/ForkJoinTask.java | 195 ++++++---- 2 files changed, 321 insertions(+), 288 deletions(-) diff --git a/src/forkjoin/scala/concurrent/forkjoin/ForkJoinPool.java b/src/forkjoin/scala/concurrent/forkjoin/ForkJoinPool.java index e9389e9acb..4b5b3382f5 100644 --- a/src/forkjoin/scala/concurrent/forkjoin/ForkJoinPool.java +++ b/src/forkjoin/scala/concurrent/forkjoin/ForkJoinPool.java @@ -1,5 +1,4 @@ /* - * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ @@ -12,22 +11,18 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Random; -//import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.AbstractExecutorService; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; -//import java.util.concurrent.RunnableFuture; +import java.util.concurrent.RunnableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.AbstractQueuedSynchronizer; import java.util.concurrent.locks.Condition; -interface RunnableFuture extends Runnable { - //TR placeholder for java.util.concurrent.RunnableFuture -} - /** * An {@link ExecutorService} for running {@link ForkJoinTask}s. * A {@code ForkJoinPool} provides the entry point for submissions @@ -127,7 +122,7 @@ interface RunnableFuture extends Runnable { * @since 1.7 * @author Doug Lea */ -public class ForkJoinPool /*extends AbstractExecutorService*/ { +public class ForkJoinPool extends AbstractExecutorService { /* * Implementation Overview @@ -634,7 +629,7 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { final ForkJoinPool pool; // the containing pool (may be null) final ForkJoinWorkerThread owner; // owning thread or null if shared volatile Thread parker; // == owner during call to park; else null - ForkJoinTask currentJoin; // task being joined in awaitJoin + volatile ForkJoinTask currentJoin; // task being joined in awaitJoin ForkJoinTask currentSteal; // current non-local task being executed // Heuristic padding to ameliorate unfortunate memory placements Object p00, p01, p02, p03, p04, p05, p06, p07; @@ -726,12 +721,11 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { * version of this method because it is never needed.) */ final ForkJoinTask pop() { - ForkJoinTask t; int m; - ForkJoinTask[] a = array; - if (a != null && (m = a.length - 1) >= 0) { + ForkJoinTask[] a; ForkJoinTask t; int m; + if ((a = array) != null && (m = a.length - 1) >= 0) { for (int s; (s = top - 1) - base >= 0;) { - int j = ((m & s) << ASHIFT) + ABASE; - if ((t = (ForkJoinTask)U.getObjectVolatile(a, j)) == null) + long j = ((m & s) << ASHIFT) + ABASE; + if ((t = (ForkJoinTask)U.getObject(a, j)) == null) break; if (U.compareAndSwapObject(a, j, t, null)) { top = s; @@ -834,54 +828,6 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { return false; } - /** - * If present, removes from queue and executes the given task, or - * any other cancelled task. Returns (true) immediately on any CAS - * or consistency check failure so caller can retry. - * - * @return false if no progress can be made - */ - final boolean tryRemoveAndExec(ForkJoinTask task) { - boolean removed = false, empty = true, progress = true; - ForkJoinTask[] a; int m, s, b, n; - if ((a = array) != null && (m = a.length - 1) >= 0 && - (n = (s = top) - (b = base)) > 0) { - for (ForkJoinTask t;;) { // traverse from s to b - int j = ((--s & m) << ASHIFT) + ABASE; - t = (ForkJoinTask)U.getObjectVolatile(a, j); - if (t == null) // inconsistent length - break; - else if (t == task) { - if (s + 1 == top) { // pop - if (!U.compareAndSwapObject(a, j, task, null)) - break; - top = s; - removed = true; - } - else if (base == b) // replace with proxy - removed = U.compareAndSwapObject(a, j, task, - new EmptyTask()); - break; - } - else if (t.status >= 0) - empty = false; - else if (s + 1 == top) { // pop and throw away - if (U.compareAndSwapObject(a, j, t, null)) - top = s; - break; - } - if (--n == 0) { - if (!empty && base == b) - progress = false; - break; - } - } - } - if (removed) - task.doExec(); - return progress; - } - /** * Initializes or doubles the capacity of array. Call either * by owner or with lock held -- it is OK for base, but not @@ -944,69 +890,98 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { // Execution methods /** - * Removes and runs tasks until empty, using local mode - * ordering. Normally called only after checking for apparent - * non-emptiness. + * Pops and runs tasks until empty. */ - final void runLocalTasks() { - // hoist checks from repeated pop/poll - ForkJoinTask[] a; int m; - if ((a = array) != null && (m = a.length - 1) >= 0) { - if (mode == 0) { - for (int s; (s = top - 1) - base >= 0;) { - int j = ((m & s) << ASHIFT) + ABASE; - ForkJoinTask t = - (ForkJoinTask)U.getObjectVolatile(a, j); - if (t != null) { - if (U.compareAndSwapObject(a, j, t, null)) { - top = s; - t.doExec(); - } - } - else - break; - } + private void popAndExecAll() { + // A bit faster than repeated pop calls + ForkJoinTask[] a; int m, s; long j; ForkJoinTask t; + while ((a = array) != null && (m = a.length - 1) >= 0 && + (s = top - 1) - base >= 0 && + (t = ((ForkJoinTask) + U.getObject(a, j = ((m & s) << ASHIFT) + ABASE))) + != null) { + if (U.compareAndSwapObject(a, j, t, null)) { + top = s; + t.doExec(); } - else { - for (int b; (b = base) - top < 0;) { - int j = ((m & b) << ASHIFT) + ABASE; - ForkJoinTask t = - (ForkJoinTask)U.getObjectVolatile(a, j); - if (t != null) { - if (base == b && - U.compareAndSwapObject(a, j, t, null)) { - base = b + 1; - t.doExec(); - } - } else if (base == b) { - if (b + 1 == top) + } + } + + /** + * Polls and runs tasks until empty. + */ + private void pollAndExecAll() { + for (ForkJoinTask t; (t = poll()) != null;) + t.doExec(); + } + + /** + * If present, removes from queue and executes the given task, or + * any other cancelled task. Returns (true) immediately on any CAS + * or consistency check failure so caller can retry. + * + * @return 0 if no progress can be made, else positive + * (this unusual convention simplifies use with tryHelpStealer.) + */ + final int tryRemoveAndExec(ForkJoinTask task) { + int stat = 1; + boolean removed = false, empty = true; + ForkJoinTask[] a; int m, s, b, n; + if ((a = array) != null && (m = a.length - 1) >= 0 && + (n = (s = top) - (b = base)) > 0) { + for (ForkJoinTask t;;) { // traverse from s to b + int j = ((--s & m) << ASHIFT) + ABASE; + t = (ForkJoinTask)U.getObjectVolatile(a, j); + if (t == null) // inconsistent length + break; + else if (t == task) { + if (s + 1 == top) { // pop + if (!U.compareAndSwapObject(a, j, task, null)) break; - Thread.yield(); // wait for lagging update + top = s; + removed = true; } + else if (base == b) // replace with proxy + removed = U.compareAndSwapObject(a, j, task, + new EmptyTask()); + break; + } + else if (t.status >= 0) + empty = false; + else if (s + 1 == top) { // pop and throw away + if (U.compareAndSwapObject(a, j, t, null)) + top = s; + break; + } + if (--n == 0) { + if (!empty && base == b) + stat = 0; + break; } } } + if (removed) + task.doExec(); + return stat; } /** * Executes a top-level task and any local tasks remaining * after execution. - * - * @return true unless terminating */ - final boolean runTask(ForkJoinTask t) { - boolean alive = true; + final void runTask(ForkJoinTask t) { if (t != null) { currentSteal = t; t.doExec(); - if (top != base) // conservative guard - runLocalTasks(); + if (top != base) { // process remaining local tasks + if (mode == 0) + popAndExecAll(); + else + pollAndExecAll(); + } ++nsteals; currentSteal = null; } - else if (runState < 0) // terminating - alive = false; - return alive; } /** @@ -1072,7 +1047,6 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { ASHIFT = 31 - Integer.numberOfLeadingZeros(s); } } - /** * Per-thread records for threads that submit to pools. Currently * holds only pseudo-random seed / index that is used to choose @@ -1165,7 +1139,7 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { * traversal parameters at the expense of sometimes blocking when * we could be helping. */ - private static final int MAX_HELP = 32; + private static final int MAX_HELP = 64; /** * Secondary time-based bound (in nanosecs) for helping attempts @@ -1175,7 +1149,7 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { * value should roughly approximate the time required to create * and/or activate a worker thread. */ - private static final long COMPENSATION_DELAY = 100L * 1000L; // 0.1 millisec + private static final long COMPENSATION_DELAY = 1L << 18; // ~0.25 millisec /** * Increment for seed generators. See class ThreadLocal for @@ -1326,22 +1300,28 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { * * @param w the worker's queue */ + final void registerWorker(WorkQueue w) { Mutex lock = this.lock; lock.lock(); try { WorkQueue[] ws = workQueues; if (w != null && ws != null) { // skip on shutdown/failure - int rs, n; - while ((n = ws.length) < // ensure can hold total - (parallelism + (short)(ctl >>> TC_SHIFT) << 1)) - workQueues = ws = Arrays.copyOf(ws, n << 1); - int m = n - 1; + int rs, n = ws.length, m = n - 1; int s = nextSeed += SEED_INCREMENT; // rarely-colliding sequence w.seed = (s == 0) ? 1 : s; // ensure non-zero seed int r = (s << 1) | 1; // use odd-numbered indices - while (ws[r &= m] != null) // step by approx half size - r += ((n >>> 1) & SQMASK) + 2; + if (ws[r &= m] != null) { // collision + int probes = 0; // step by approx half size + int step = (n <= 4) ? 2 : ((n >>> 1) & SQMASK) + 2; + while (ws[r = (r + step) & m] != null) { + if (++probes >= n) { + workQueues = ws = Arrays.copyOf(ws, n <<= 1); + m = n - 1; + probes = 0; + } + } + } w.eventCount = w.poolIndex = r; // establish before recording ws[r] = w; // also update seq runState = ((rs = runState) & SHUTDOWN) | ((rs + 2) & ~SHUTDOWN); @@ -1488,7 +1468,6 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { } } - // Scanning for tasks /** @@ -1496,7 +1475,7 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { */ final void runWorker(WorkQueue w) { w.growArray(false); // initialize queue array in this thread - do {} while (w.runTask(scan(w))); + do { w.runTask(scan(w)); } while (w.runState >= 0); } /** @@ -1559,8 +1538,7 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { q.base = b + 1; // specialization of pollAt return t; } - else if ((t != null || b + 1 != q.top) && - (ec < 0 || j <= m)) { + else if (ec < 0 || j <= m) { rs = 0; // mark scan as imcomplete break; // caller can retry after release } @@ -1568,6 +1546,7 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { if (--j < 0) break; } + long c = ctl; int e = (int)c, a = (int)(c >> AC_SHIFT), nr, ns; if (e < 0) // decode ctl on empty scan w.runState = -1; // pool is terminating @@ -1635,7 +1614,7 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { */ private void idleAwaitWork(WorkQueue w, long currentCtl, long prevCtl) { if (w.eventCount < 0 && !tryTerminate(false, false) && - (int)prevCtl != 0 && ctl == currentCtl) { + (int)prevCtl != 0 && !hasQueuedSubmissions() && ctl == currentCtl) { Thread wt = Thread.currentThread(); Thread.yield(); // yield before block while (ctl == currentCtl) { @@ -1670,70 +1649,79 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { * leaves hints in workers to speed up subsequent calls. The * implementation is very branchy to cope with potential * inconsistencies or loops encountering chains that are stale, - * unknown, or so long that they are likely cyclic. All of these - * cases are dealt with by just retrying by caller. + * unknown, or so long that they are likely cyclic. * * @param joiner the joining worker * @param task the task to join - * @return true if found or ran a task (and so is immediately retryable) - */ - private boolean tryHelpStealer(WorkQueue joiner, ForkJoinTask task) { - WorkQueue[] ws; - int m, depth = MAX_HELP; // remaining chain depth - boolean progress = false; - if ((ws = workQueues) != null && (m = ws.length - 1) > 0 && - task.status >= 0) { - ForkJoinTask subtask = task; // current target - outer: for (WorkQueue j = joiner;;) { - WorkQueue stealer = null; // find stealer of subtask - WorkQueue v = ws[j.stealHint & m]; // try hint - if (v != null && v.currentSteal == subtask) - stealer = v; - else { // scan - for (int i = 1; i <= m; i += 2) { - if ((v = ws[i]) != null && v.currentSteal == subtask && - v != joiner) { - stealer = v; - j.stealHint = i; // save hint - break; - } + * @return 0 if no progress can be made, negative if task + * known complete, else positive + */ + private int tryHelpStealer(WorkQueue joiner, ForkJoinTask task) { + int stat = 0, steps = 0; // bound to avoid cycles + if (joiner != null && task != null) { // hoist null checks + restart: for (;;) { + ForkJoinTask subtask = task; // current target + for (WorkQueue j = joiner, v;;) { // v is stealer of subtask + WorkQueue[] ws; int m, s, h; + if ((s = task.status) < 0) { + stat = s; + break restart; } - if (stealer == null) - break; - } - - for (WorkQueue q = stealer;;) { // try to help stealer - ForkJoinTask[] a; ForkJoinTask t; int b; - if (task.status < 0) - break outer; - if ((b = q.base) - q.top < 0 && (a = q.array) != null) { - progress = true; - int i = (((a.length - 1) & b) << ASHIFT) + ABASE; - t = (ForkJoinTask)U.getObjectVolatile(a, i); - if (subtask.status < 0) // must recheck before taking - break outer; - if (t != null && - q.base == b && - U.compareAndSwapObject(a, i, t, null)) { - q.base = b + 1; - joiner.runSubtask(t); + if ((ws = workQueues) == null || (m = ws.length - 1) <= 0) + break restart; // shutting down + if ((v = ws[h = (j.stealHint | 1) & m]) == null || + v.currentSteal != subtask) { + for (int origin = h;;) { // find stealer + if (((h = (h + 2) & m) & 15) == 1 && + (subtask.status < 0 || j.currentJoin != subtask)) + continue restart; // occasional staleness check + if ((v = ws[h]) != null && + v.currentSteal == subtask) { + j.stealHint = h; // save hint + break; + } + if (h == origin) + break restart; // cannot find stealer } - else if (q.base == b) - break outer; // possibly stalled } - else { // descend - ForkJoinTask next = stealer.currentJoin; - if (--depth <= 0 || subtask.status < 0 || - next == null || next == subtask) - break outer; // stale, dead-end, or cyclic - subtask = next; - j = stealer; - break; + for (;;) { // help stealer or descend to its stealer + ForkJoinTask[] a; int b; + if (subtask.status < 0) // surround probes with + continue restart; // consistency checks + if ((b = v.base) - v.top < 0 && (a = v.array) != null) { + int i = (((a.length - 1) & b) << ASHIFT) + ABASE; + ForkJoinTask t = + (ForkJoinTask)U.getObjectVolatile(a, i); + if (subtask.status < 0 || j.currentJoin != subtask || + v.currentSteal != subtask) + continue restart; // stale + stat = 1; // apparent progress + if (t != null && v.base == b && + U.compareAndSwapObject(a, i, t, null)) { + v.base = b + 1; // help stealer + joiner.runSubtask(t); + } + else if (v.base == b && ++steps == MAX_HELP) + break restart; // v apparently stalled + } + else { // empty -- try to descend + ForkJoinTask next = v.currentJoin; + if (subtask.status < 0 || j.currentJoin != subtask || + v.currentSteal != subtask) + continue restart; // stale + else if (next == null || ++steps == MAX_HELP) + break restart; // dead-end or maybe cyclic + else { + subtask = next; + j = v; + break; + } + } } } } } - return progress; + return stat; } /** @@ -1833,44 +1821,50 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { * @return task status on exit */ final int awaitJoin(WorkQueue joiner, ForkJoinTask task) { - ForkJoinTask prevJoin = joiner.currentJoin; - joiner.currentJoin = task; - long startTime = 0L; - for (int k = 0, s; ; ++k) { - if ((joiner.isEmpty() ? // try to help - !tryHelpStealer(joiner, task) : - !joiner.tryRemoveAndExec(task))) { - if (k == 0) { - startTime = System.nanoTime(); - tryPollForAndExec(joiner, task); // check uncommon case - } - else if ((k & (MAX_HELP - 1)) == 0 && - System.nanoTime() - startTime >= COMPENSATION_DELAY && - tryCompensate(task, null)) { - if (task.trySetSignal() && task.status >= 0) { - synchronized (task) { - if (task.status >= 0) { - try { // see ForkJoinTask - task.wait(); // for explanation - } catch (InterruptedException ie) { + int s; + if ((s = task.status) >= 0) { + ForkJoinTask prevJoin = joiner.currentJoin; + joiner.currentJoin = task; + long startTime = 0L; + for (int k = 0;;) { + if ((s = (joiner.isEmpty() ? // try to help + tryHelpStealer(joiner, task) : + joiner.tryRemoveAndExec(task))) == 0 && + (s = task.status) >= 0) { + if (k == 0) { + startTime = System.nanoTime(); + tryPollForAndExec(joiner, task); // check uncommon case + } + else if ((k & (MAX_HELP - 1)) == 0 && + System.nanoTime() - startTime >= + COMPENSATION_DELAY && + tryCompensate(task, null)) { + if (task.trySetSignal()) { + synchronized (task) { + if (task.status >= 0) { + try { // see ForkJoinTask + task.wait(); // for explanation + } catch (InterruptedException ie) { + } } + else + task.notifyAll(); } - else - task.notifyAll(); } + long c; // re-activate + do {} while (!U.compareAndSwapLong + (this, CTL, c = ctl, c + AC_UNIT)); } - long c; // re-activate - do {} while (!U.compareAndSwapLong - (this, CTL, c = ctl, c + AC_UNIT)); } + if (s < 0 || (s = task.status) < 0) { + joiner.currentJoin = prevJoin; + break; + } + else if ((k++ & (MAX_HELP - 1)) == MAX_HELP >>> 1) + Thread.yield(); // for politeness } - if ((s = task.status) < 0) { - joiner.currentJoin = prevJoin; - return s; - } - else if ((k & (MAX_HELP - 1)) == MAX_HELP >>> 1) - Thread.yield(); // for politeness } + return s; } /** @@ -1887,7 +1881,7 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { while ((s = task.status) >= 0 && (joiner.isEmpty() ? tryHelpStealer(joiner, task) : - joiner.tryRemoveAndExec(task))) + joiner.tryRemoveAndExec(task)) != 0) ; return s; } @@ -1919,6 +1913,7 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { } } + /** * Runs tasks until {@code isQuiescent()}. We piggyback on * active count ctl maintenance, but rather than blocking @@ -1927,8 +1922,9 @@ public class ForkJoinPool /*extends AbstractExecutorService*/ { */ final void helpQuiescePool(WorkQueue w) { for (boolean active = true;;) { - if (w.base - w.top < 0) - w.runLocalTasks(); // exhaust local queue + ForkJoinTask localTask; // exhaust local queue + while ((localTask = w.nextLocalTask()) != null) + localTask.doExec(); WorkQueue q = findNonEmptyStealQueue(w); if (q != null) { ForkJoinTask t; int b; diff --git a/src/forkjoin/scala/concurrent/forkjoin/ForkJoinTask.java b/src/forkjoin/scala/concurrent/forkjoin/ForkJoinTask.java index 344f6887a6..2ba146c4da 100644 --- a/src/forkjoin/scala/concurrent/forkjoin/ForkJoinTask.java +++ b/src/forkjoin/scala/concurrent/forkjoin/ForkJoinTask.java @@ -16,7 +16,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; -//import java.util.concurrent.RunnableFuture; +import java.util.concurrent.RunnableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantLock; @@ -115,18 +115,19 @@ import java.lang.reflect.Constructor; *

The ForkJoinTask class is not usually directly subclassed. * Instead, you subclass one of the abstract classes that support a * particular style of fork/join processing, typically {@link - * RecursiveAction} for computations that do not return results, or - * {@link RecursiveTask} for those that do. Normally, a concrete - * ForkJoinTask subclass declares fields comprising its parameters, - * established in a constructor, and then defines a {@code compute} - * method that somehow uses the control methods supplied by this base - * class. While these methods have {@code public} access (to allow - * instances of different task subclasses to call each other's - * methods), some of them may only be called from within other - * ForkJoinTasks (as may be determined using method {@link - * #inForkJoinPool}). Attempts to invoke them in other contexts - * result in exceptions or errors, possibly including - * {@code ClassCastException}. + * RecursiveAction} for most computations that do not return results, + * {@link RecursiveTask} for those that do, and {@link + * CountedCompleter} for those in which completed actions trigger + * other actions. Normally, a concrete ForkJoinTask subclass declares + * fields comprising its parameters, established in a constructor, and + * then defines a {@code compute} method that somehow uses the control + * methods supplied by this base class. While these methods have + * {@code public} access (to allow instances of different task + * subclasses to call each other's methods), some of them may only be + * called from within other ForkJoinTasks (as may be determined using + * method {@link #inForkJoinPool}). Attempts to invoke them in other + * contexts result in exceptions or errors, possibly including {@code + * ClassCastException}. * *

Method {@link #join} and its variants are appropriate for use * only when completion dependencies are acyclic; that is, the @@ -137,17 +138,17 @@ import java.lang.reflect.Constructor; * {@link Phaser}, {@link #helpQuiesce}, and {@link #complete}) that * may be of use in constructing custom subclasses for problems that * are not statically structured as DAGs. To support such usages a - * ForkJoinTask may be atomically marked using {@link - * #markForkJoinTask} and checked for marking using {@link - * #isMarkedForkJoinTask}. The ForkJoinTask implementation does not - * use these {@code protected} methods or marks for any purpose, but + * ForkJoinTask may be atomically tagged with a {@code + * short} value using {@link #setForkJoinTaskTag} or {@link + * #compareAndSetForkJoinTaskTag} and checked using {@link + * #getForkJoinTaskTag}. The ForkJoinTask implementation does not + * use these {@code protected} methods or tags for any purpose, but * they may be of use in the construction of specialized subclasses. * For example, parallel graph traversals can use the supplied methods * to avoid revisiting nodes/tasks that have already been processed. - * Also, completion based designs can use them to record that one - * subtask has completed. (Method names for marking are bulky in part - * to encourage definition of methods that reflect their usage - * patterns.) + * Also, completion based designs can use them to record that subtasks + * have completed. (Method names for tagging are bulky in part to + * encourage definition of methods that reflect their usage patterns.) * *

Most base support methods are {@code final}, to prevent * overriding of implementations that are intrinsically tied to the @@ -213,6 +214,10 @@ public abstract class ForkJoinTask implements Future, Serializable { * thin-lock techniques, so use some odd coding idioms that tend * to avoid them, mainly by arranging that every synchronized * block performs a wait, notifyAll or both. + * + * These control bits occupy only (some of) the upper half (16 + * bits) of status field. The lower bits are used for user-defined + * tags. */ /** The run status of this task */ @@ -221,13 +226,12 @@ public abstract class ForkJoinTask implements Future, Serializable { static final int NORMAL = 0xf0000000; // must be negative static final int CANCELLED = 0xc0000000; // must be < NORMAL static final int EXCEPTIONAL = 0x80000000; // must be < CANCELLED - static final int SIGNAL = 0x00000001; - static final int MARKED = 0x00000002; + static final int SIGNAL = 0x00010000; // must be >= 1 << 16 + static final int SMASK = 0x0000ffff; // short bits for tags /** * Marks completion and wakes up threads waiting to join this - * task. A specialization for NORMAL completion is in method - * doExec. + * task. * * @param completion one of NORMAL, CANCELLED, EXCEPTIONAL * @return completion status on exit @@ -237,7 +241,7 @@ public abstract class ForkJoinTask implements Future, Serializable { if ((s = status) < 0) return s; if (U.compareAndSwapInt(this, STATUS, s, s | completion)) { - if ((s & SIGNAL) != 0) + if ((s >>> 16) != 0) synchronized (this) { notifyAll(); } return completion; } @@ -259,26 +263,22 @@ public abstract class ForkJoinTask implements Future, Serializable { } catch (Throwable rex) { return setExceptionalCompletion(rex); } - while ((s = status) >= 0 && completed) { - if (U.compareAndSwapInt(this, STATUS, s, s | NORMAL)) { - if ((s & SIGNAL) != 0) - synchronized (this) { notifyAll(); } - return NORMAL; - } - } + if (completed) + s = setCompletion(NORMAL); } return s; } /** - * Tries to set SIGNAL status. Used by ForkJoinPool. Other - * variants are directly incorporated into externalAwaitDone etc. + * Tries to set SIGNAL status unless already completed. Used by + * ForkJoinPool. Other variants are directly incorporated into + * externalAwaitDone etc. * * @return true if successful */ final boolean trySetSignal() { - int s; - return U.compareAndSwapInt(this, STATUS, s = status, s | SIGNAL); + int s = status; + return s >= 0 && U.compareAndSwapInt(this, STATUS, s, s | SIGNAL); } /** @@ -328,7 +328,6 @@ public abstract class ForkJoinTask implements Future, Serializable { return s; } - /** * Implementation for join, get, quietlyJoin. Directly handles * only cases of already-completed, external wait, and @@ -417,25 +416,39 @@ public abstract class ForkJoinTask implements Future, Serializable { * @return status on exit */ private int setExceptionalCompletion(Throwable ex) { - int h = System.identityHashCode(this); - final ReentrantLock lock = exceptionTableLock; - lock.lock(); - try { - expungeStaleExceptions(); - ExceptionNode[] t = exceptionTable; - int i = h & (t.length - 1); - for (ExceptionNode e = t[i]; ; e = e.next) { - if (e == null) { - t[i] = new ExceptionNode(this, ex, t[i]); - break; + int s; + if ((s = status) >= 0) { + int h = System.identityHashCode(this); + final ReentrantLock lock = exceptionTableLock; + lock.lock(); + try { + expungeStaleExceptions(); + ExceptionNode[] t = exceptionTable; + int i = h & (t.length - 1); + for (ExceptionNode e = t[i]; ; e = e.next) { + if (e == null) { + t[i] = new ExceptionNode(this, ex, t[i]); + break; + } + if (e.get() == this) // already present + break; } - if (e.get() == this) // already present - break; + } finally { + lock.unlock(); } - } finally { - lock.unlock(); + s = setCompletion(EXCEPTIONAL); } - return setCompletion(EXCEPTIONAL); + ForkJoinTask p = internalGetCompleter(); // propagate + if (p != null && p.status >= 0) + p.setExceptionalCompletion(ex); + return s; + } + + /** + * Exception propagation support for tasks with completers. + */ + ForkJoinTask internalGetCompleter() { + return null; } /** @@ -517,7 +530,7 @@ public abstract class ForkJoinTask implements Future, Serializable { Throwable ex; if (e == null || (ex = e.ex) == null) return null; - if (e.thrower != Thread.currentThread().getId()) { + if (false && e.thrower != Thread.currentThread().getId()) { Class ec = ex.getClass(); try { Constructor noArgCtor = null; @@ -906,6 +919,18 @@ public abstract class ForkJoinTask implements Future, Serializable { setCompletion(NORMAL); } + /** + * Completes this task normally without setting a value. The most + * recent value established by {@link #setRawResult} (or {@code + * null} by default) will be returned as the result of subsequent + * invocations of {@code join} and related operations. + * + * @since 1.8 + */ + public final void quietlyComplete() { + setCompletion(NORMAL); + } + /** * Waits if necessary for the computation to complete, and then * retrieves its result. @@ -1225,15 +1250,18 @@ public abstract class ForkJoinTask implements Future, Serializable { protected abstract void setRawResult(V value); /** - * Immediately performs the base action of this task. This method - * is designed to support extensions, and should not in general be - * called otherwise. The return value controls whether this task - * is considered to be done normally. It may return false in + * Immediately performs the base action of this task and returns + * true if, upon return from this method, this task is guaranteed + * to have completed normally. This method may return false + * otherwise, to indicate that this task is not necessarily + * complete (or is not known to be complete), for example in * asynchronous actions that require explicit invocations of - * {@link #complete} to become joinable. It may also throw an - * (unchecked) exception to indicate abnormal exit. + * completion methods. This method may also throw an (unchecked) + * exception to indicate abnormal exit. This method is designed to + * support extensions, and should not in general be called + * otherwise. * - * @return {@code true} if completed normally + * @return {@code true} if this task is known to have completed normally */ protected abstract boolean exec(); @@ -1302,44 +1330,53 @@ public abstract class ForkJoinTask implements Future, Serializable { return wt.pool.nextTaskFor(wt.workQueue); } - // Mark-bit operations + // tag operations /** - * Returns true if this task is marked. + * Returns the tag for this task. * - * @return true if this task is marked + * @return the tag for this task * @since 1.8 */ - public final boolean isMarkedForkJoinTask() { - return (status & MARKED) != 0; + public final short getForkJoinTaskTag() { + return (short)status; } /** - * Atomically sets the mark on this task. + * Atomically sets the tag value for this task. * - * @return true if this task was previously unmarked + * @param tag the tag value + * @return the previous value of the tag * @since 1.8 */ - public final boolean markForkJoinTask() { + public final short setForkJoinTaskTag(short tag) { for (int s;;) { - if (((s = status) & MARKED) != 0) - return false; - if (U.compareAndSwapInt(this, STATUS, s, s | MARKED)) - return true; + if (U.compareAndSwapInt(this, STATUS, s = status, + (s & ~SMASK) | (tag & SMASK))) + return (short)s; } } /** - * Atomically clears the mark on this task. + * Atomically conditionally sets the tag value for this task. + * Among other applications, tags can be used as visit markers + * in tasks operating on graphs, as in methods that check: {@code + * if (task.compareAndSetForkJoinTaskTag((short)0, (short)1))} + * before processing, otherwise exiting because the node has + * already been visited. * - * @return true if this task was previously marked + * @param e the expected tag value + * @param tag the new tag value + * @return true if successful; i.e., the current value was + * equal to e and is now tag. * @since 1.8 */ - public final boolean unmarkForkJoinTask() { + public final boolean compareAndSetForkJoinTaskTag(short e, short tag) { for (int s;;) { - if (((s = status) & MARKED) == 0) + if ((short)(s = status) != e) return false; - if (U.compareAndSwapInt(this, STATUS, s, s & ~MARKED)) + if (U.compareAndSwapInt(this, STATUS, s, + (s & ~SMASK) | (tag & SMASK))) return true; } } -- cgit v1.2.3 From 1246f69185d958f12ebbff047fc3a72766cfd384 Mon Sep 17 00:00:00 2001 From: Heather Miller Date: Sat, 14 Apr 2012 18:35:43 +0200 Subject: Changes to access modifiers on Unsafe and NonFatal, adds/fixes doc comments --- src/library/scala/concurrent/Future.scala | 8 ++++---- src/library/scala/concurrent/impl/NonFatal.scala | 5 +++-- src/library/scala/concurrent/impl/Promise.scala | 2 +- src/library/scala/concurrent/impl/Unsafe.java | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index 40acea91c9..89b04e6248 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -117,7 +117,7 @@ trait Future[+T] extends Awaitable[T] { case Right(v) => // do nothing } - /** When this future is completed, either through an exception, a timeout, or a value, + /** When this future is completed, either through an exception, or a value, * apply the provided function. * * If the future has already been completed, @@ -242,7 +242,7 @@ trait Future[+T] extends Awaitable[T] { * If the current future contains a value which satisfies the predicate, the new future will also hold that value. * Otherwise, the resulting future will fail with a `NoSuchElementException`. * - * If the current future fails or times out, the resulting future also fails or times out, respectively. + * If the current future fails, then the resulting future also fails. * * Example: * {{{ @@ -282,12 +282,12 @@ trait Future[+T] extends Awaitable[T] { // def withFilter(q: S => Boolean): FutureWithFilter[S] = new FutureWithFilter[S](self, x => p(x) && q(x)) // } - /** Creates a new future by mapping the value of the current future if the given partial function is defined at that value. + /** Creates a new future by mapping the value of the current future, if the given partial function is defined at that value. * * If the current future contains a value for which the partial function is defined, the new future will also hold that value. * Otherwise, the resulting future will fail with a `NoSuchElementException`. * - * If the current future fails or times out, the resulting future also fails or times out, respectively. + * If the current future fails, then the resulting future also fails. * * Example: * {{{ diff --git a/src/library/scala/concurrent/impl/NonFatal.scala b/src/library/scala/concurrent/impl/NonFatal.scala index ac0cddaf1b..bc509e664c 100644 --- a/src/library/scala/concurrent/impl/NonFatal.scala +++ b/src/library/scala/concurrent/impl/NonFatal.scala @@ -6,7 +6,8 @@ ** |/ ** \* */ -package scala.concurrent.impl +package scala.concurrent +package impl /** * Extractor of non-fatal Throwables. Will not match fatal errors @@ -23,7 +24,7 @@ package scala.concurrent.impl * } * }}} */ -object NonFatal { +private[concurrent] object NonFatal { def unapply(t: Throwable): Option[Throwable] = t match { case e: StackOverflowError ⇒ Some(e) // StackOverflowError ok even though it is a VirtualMachineError diff --git a/src/library/scala/concurrent/impl/Promise.scala b/src/library/scala/concurrent/impl/Promise.scala index 1388c8b357..ee1841aaff 100644 --- a/src/library/scala/concurrent/impl/Promise.scala +++ b/src/library/scala/concurrent/impl/Promise.scala @@ -159,7 +159,7 @@ object Promise { } def onComplete[U](func: Either[Throwable, T] => U): this.type = { - @tailrec //Returns whether the future has already been completed or not + @tailrec //Returns the future's results if it has already been completed, or null otherwise. def tryAddCallback(): Either[Throwable, T] = { val cur = getState cur match { diff --git a/src/library/scala/concurrent/impl/Unsafe.java b/src/library/scala/concurrent/impl/Unsafe.java index 3c695c3905..21f7e638e5 100644 --- a/src/library/scala/concurrent/impl/Unsafe.java +++ b/src/library/scala/concurrent/impl/Unsafe.java @@ -7,11 +7,11 @@ \* */ -package scala.concurrent.impl; +package scala.concurrent; import java.lang.reflect.Field; -public final class Unsafe { +final class Unsafe { public final static sun.misc.Unsafe instance; static { try { -- cgit v1.2.3 From 23aa1a8d7b08b767f90657baf9bc13a355682671 Mon Sep 17 00:00:00 2001 From: phaller Date: Sat, 14 Apr 2012 20:37:14 +0200 Subject: Fix issues with exception handling in futures. Review by @heathermiller --- src/library/scala/concurrent/Future.scala | 2 +- src/library/scala/concurrent/impl/ExecutionContextImpl.scala | 6 +++++- src/library/scala/concurrent/impl/Future.scala | 8 ++++++-- test/files/jvm/scala-concurrent-tck.scala | 11 +++++++---- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index 89b04e6248..04f56950f1 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -312,7 +312,7 @@ trait Future[+T] extends Awaitable[T] { if (pf.isDefinedAt(v)) p success pf(v) else p failure new NoSuchElementException("Future.collect partial function is not defined at: " + v) } catch { - case t: Throwable => p complete resolver(t) + case NonFatal(t) => p complete resolver(t) } } diff --git a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala index c308a59297..2b929d91ab 100644 --- a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala +++ b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala @@ -93,8 +93,12 @@ private[scala] class ExecutionContextImpl(es: AnyRef) extends ExecutionContext w } def reportFailure(t: Throwable) = t match { + /*TODO: clarify that resolver should wrap Errors, in which case we do not want + Error to be re-thrown here */ case e: Error => throw e // rethrow serious errors - case t => t.printStackTrace() + case t => + //println(t.toString) + t.printStackTrace() } } diff --git a/src/library/scala/concurrent/impl/Future.scala b/src/library/scala/concurrent/impl/Future.scala index 1cc9e95463..9743c37403 100644 --- a/src/library/scala/concurrent/impl/Future.scala +++ b/src/library/scala/concurrent/impl/Future.scala @@ -71,14 +71,18 @@ object Future { def apply[T](body: =>T)(implicit executor: ExecutionContext): Future[T] = { val promise = new Promise.DefaultPromise[T]() + + //TODO: shouldn't the following be: + //dispatchFuture(executor, () => { promise complete Right(body) }) + executor.execute(new Runnable { def run = { promise complete { try { Right(body) } catch { - case NonFatal(e) => - executor.reportFailure(e) + case e => + //executor.reportFailure(e) scala.concurrent.resolver(e) } } diff --git a/test/files/jvm/scala-concurrent-tck.scala b/test/files/jvm/scala-concurrent-tck.scala index f0ca438774..8fcaceb3f0 100644 --- a/test/files/jvm/scala-concurrent-tck.scala +++ b/test/files/jvm/scala-concurrent-tck.scala @@ -64,7 +64,7 @@ trait FutureCallbacks extends TestBase { } } } - + def testOnSuccessWhenFailed(): Unit = once { done => val f = future[Unit] { @@ -94,7 +94,7 @@ trait FutureCallbacks extends TestBase { assert(x == 1) } } - + def testOnFailureWhenSpecialThrowable(num: Int, cause: Throwable): Unit = once { done => val f = future[Unit] { @@ -289,6 +289,9 @@ trait FutureCombinators extends TestBase { } } + /* TODO: Test for NonFatal in collect (more of a regression test at this point). + */ + def testForeachSuccess(): Unit = once { done => val p = promise[Int]() @@ -473,8 +476,8 @@ trait FutureCombinators extends TestBase { def testFallbackToFailure(): Unit = once { done => val cause = new Exception - val f = future { throw cause } - val g = future { sys.error("failed") } + val f = future { /*throw cause*/ sys.error("failed") } + val g = future { /*sys.error("failed")*/ throw cause } val h = f fallbackTo g h onSuccess { -- cgit v1.2.3 From 6ecb8263bff937ac545163260c7a4d4c473d996a Mon Sep 17 00:00:00 2001 From: phaller Date: Sun, 15 Apr 2012 16:12:04 +0200 Subject: Clean ups in futures based on review by @heathermiller and review by @viktorklang --- src/library/scala/concurrent/Future.scala | 19 +++++++++++++++- .../concurrent/impl/ExecutionContextImpl.scala | 10 ++++----- src/library/scala/concurrent/impl/Future.scala | 25 +++------------------- test/files/jvm/scala-concurrent-tck.scala | 7 +++--- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index f5f0c5cdfd..9aaf05dbd6 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -426,13 +426,30 @@ trait Future[+T] extends Awaitable[T] { * that conforms to `S`'s erased type or a `ClassCastException` otherwise. */ def mapTo[S](implicit m: Manifest[S]): Future[S] = { + import java.{ lang => jl } + val toBoxed = Map[Class[_], Class[_]]( + classOf[Boolean] -> classOf[jl.Boolean], + classOf[Byte] -> classOf[jl.Byte], + classOf[Char] -> classOf[jl.Character], + classOf[Short] -> classOf[jl.Short], + classOf[Int] -> classOf[jl.Integer], + classOf[Long] -> classOf[jl.Long], + classOf[Float] -> classOf[jl.Float], + classOf[Double] -> classOf[jl.Double], + classOf[Unit] -> classOf[scala.runtime.BoxedUnit] + ) + + def boxedType(c: Class[_]): Class[_] = { + if (c.isPrimitive) toBoxed(c) else c + } + val p = newPromise[S] onComplete { case l: Left[Throwable, _] => p complete l.asInstanceOf[Either[Throwable, S]] case Right(t) => p complete (try { - Right(impl.Future.boxedType(m.erasure).cast(t).asInstanceOf[S]) + Right(boxedType(m.erasure).cast(t).asInstanceOf[S]) } catch { case e: ClassCastException => Left(e) }) diff --git a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala index 2b929d91ab..ad98331241 100644 --- a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala +++ b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala @@ -93,12 +93,10 @@ private[scala] class ExecutionContextImpl(es: AnyRef) extends ExecutionContext w } def reportFailure(t: Throwable) = t match { - /*TODO: clarify that resolver should wrap Errors, in which case we do not want - Error to be re-thrown here */ - case e: Error => throw e // rethrow serious errors - case t => - //println(t.toString) - t.printStackTrace() + // `Error`s are currently wrapped by `resolver`. + // Also, re-throwing `Error`s here causes an exception handling test to fail. + //case e: Error => throw e + case t => t.printStackTrace() } } diff --git a/src/library/scala/concurrent/impl/Future.scala b/src/library/scala/concurrent/impl/Future.scala index 9743c37403..548524c9fe 100644 --- a/src/library/scala/concurrent/impl/Future.scala +++ b/src/library/scala/concurrent/impl/Future.scala @@ -42,20 +42,6 @@ private[concurrent] trait Future[+T] extends scala.concurrent.Future[T] with Awa } object Future { - import java.{ lang => jl } - - private val toBoxed = Map[Class[_], Class[_]]( - classOf[Boolean] -> classOf[jl.Boolean], - classOf[Byte] -> classOf[jl.Byte], - classOf[Char] -> classOf[jl.Character], - classOf[Short] -> classOf[jl.Short], - classOf[Int] -> classOf[jl.Integer], - classOf[Long] -> classOf[jl.Long], - classOf[Float] -> classOf[jl.Float], - classOf[Double] -> classOf[jl.Double], - classOf[Unit] -> classOf[scala.runtime.BoxedUnit] - ) - /** Wraps a block of code into an awaitable object. */ private[concurrent] def body2awaitable[T](body: =>T) = new Awaitable[T] { def ready(atMost: Duration)(implicit permit: CanAwait) = { @@ -65,23 +51,18 @@ object Future { def result(atMost: Duration)(implicit permit: CanAwait) = body } - def boxedType(c: Class[_]): Class[_] = { - if (c.isPrimitive) toBoxed(c) else c - } - def apply[T](body: =>T)(implicit executor: ExecutionContext): Future[T] = { val promise = new Promise.DefaultPromise[T]() - //TODO: shouldn't the following be: - //dispatchFuture(executor, () => { promise complete Right(body) }) - + //TODO: use `dispatchFuture`? executor.execute(new Runnable { def run = { promise complete { try { Right(body) } catch { - case e => + case NonFatal(e) => + // Commenting out reporting for now, since it produces too much output in the tests //executor.reportFailure(e) scala.concurrent.resolver(e) } diff --git a/test/files/jvm/scala-concurrent-tck.scala b/test/files/jvm/scala-concurrent-tck.scala index 8fcaceb3f0..fce1a25bb6 100644 --- a/test/files/jvm/scala-concurrent-tck.scala +++ b/test/files/jvm/scala-concurrent-tck.scala @@ -139,7 +139,8 @@ trait FutureCallbacks extends TestBase { testOnFailure() testOnFailureWhenSpecialThrowable(5, new Error) testOnFailureWhenSpecialThrowable(6, new scala.util.control.ControlThrowable { }) - testOnFailureWhenSpecialThrowable(7, new InterruptedException) + //TODO: this test is currently problematic, because NonFatal does not match InterruptedException + //testOnFailureWhenSpecialThrowable(7, new InterruptedException) testOnFailureWhenTimeoutException() } @@ -476,8 +477,8 @@ trait FutureCombinators extends TestBase { def testFallbackToFailure(): Unit = once { done => val cause = new Exception - val f = future { /*throw cause*/ sys.error("failed") } - val g = future { /*sys.error("failed")*/ throw cause } + val f = future { sys.error("failed") } + val g = future { throw cause } val h = f fallbackTo g h onSuccess { -- cgit v1.2.3 From a1c36ea5fcebdad36b77b85310f262e0cd381976 Mon Sep 17 00:00:00 2001 From: phaller Date: Sun, 15 Apr 2012 17:06:09 +0200 Subject: Resolve feature and deprecation warnings in scala.concurrent --- src/library/scala/concurrent/ManagedBlocker.scala | 1 - src/library/scala/concurrent/util/Duration.scala | 8 ++++---- src/library/scala/concurrent/util/duration/package.scala | 3 ++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/library/scala/concurrent/ManagedBlocker.scala b/src/library/scala/concurrent/ManagedBlocker.scala index 0b6d82e76f..9c6f4d51d6 100644 --- a/src/library/scala/concurrent/ManagedBlocker.scala +++ b/src/library/scala/concurrent/ManagedBlocker.scala @@ -12,7 +12,6 @@ package scala.concurrent * * @author Philipp Haller */ -@deprecated("Not used.", "2.10.0") trait ManagedBlocker { /** diff --git a/src/library/scala/concurrent/util/Duration.scala b/src/library/scala/concurrent/util/Duration.scala index 15a546de10..c4e5fa491a 100644 --- a/src/library/scala/concurrent/util/Duration.scala +++ b/src/library/scala/concurrent/util/Duration.scala @@ -297,7 +297,7 @@ class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration { def toMinutes = unit.toMinutes(length) def toHours = unit.toHours(length) def toDays = unit.toDays(length) - def toUnit(u: TimeUnit) = long2double(toNanos) / NANOSECONDS.convert(1, u) + def toUnit(u: TimeUnit) = toNanos.toDouble / NANOSECONDS.convert(1, u) override def toString = this match { case Duration(1, DAYS) ⇒ "1 day" @@ -341,11 +341,11 @@ class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration { } } - def *(factor: Double) = fromNanos(long2double(toNanos) * factor) + def *(factor: Double) = fromNanos(toNanos.toDouble * factor) - def /(factor: Double) = fromNanos(long2double(toNanos) / factor) + def /(factor: Double) = fromNanos(toNanos.toDouble / factor) - def /(other: Duration) = if (other.finite_?) long2double(toNanos) / other.toNanos else 0 + def /(other: Duration) = if (other.finite_?) toNanos.toDouble / other.toNanos else 0 def unary_- = Duration(-length, unit) diff --git a/src/library/scala/concurrent/util/duration/package.scala b/src/library/scala/concurrent/util/duration/package.scala index 25625054ee..e3cf229c61 100644 --- a/src/library/scala/concurrent/util/duration/package.scala +++ b/src/library/scala/concurrent/util/duration/package.scala @@ -1,6 +1,7 @@ package scala.concurrent.util import java.util.concurrent.TimeUnit +import language.implicitConversions package object duration { @@ -27,4 +28,4 @@ package object duration { implicit def intMult(i: Int) = new IntMult(i) implicit def longMult(l: Long) = new LongMult(l) implicit def doubleMult(f: Double) = new DoubleMult(f) -} \ No newline at end of file +} -- cgit v1.2.3 From a3d0544987eeead887eb77881eb79aeb3177a267 Mon Sep 17 00:00:00 2001 From: Heather Miller Date: Sun, 15 Apr 2012 18:13:17 +0200 Subject: Updates forkjoin.jar sha1 --- lib/forkjoin.jar.desired.sha1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/forkjoin.jar.desired.sha1 b/lib/forkjoin.jar.desired.sha1 index d37b84d8c7..d31539daf4 100644 --- a/lib/forkjoin.jar.desired.sha1 +++ b/lib/forkjoin.jar.desired.sha1 @@ -1 +1 @@ -996fc132b05046112b9d4dc62e2d2c9057d836bc ?forkjoin.jar +b5baf94dff8d3ca991d44a59add7bcbf6640379b ?forkjoin.jar -- cgit v1.2.3 From 4a6f54b5f16b3179b23a44f2f1d83f080e218f72 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 14 Apr 2012 06:16:32 +0100 Subject: New facility: TypeDestructurers. Would prefer to bake a little longer, but, scala days. More elaboration to come. --- .../scala/reflect/internal/Definitions.scala | 6 +- .../scala/reflect/internal/SymbolCreations.scala | 1 - .../scala/reflect/internal/SymbolFlags.scala | 1 - src/compiler/scala/reflect/internal/Symbols.scala | 10 +- src/compiler/scala/reflect/internal/Types.scala | 5 +- .../scala/tools/nsc/CompilationUnits.scala | 2 +- src/compiler/scala/tools/nsc/Global.scala | 7 +- .../scala/tools/nsc/ast/parser/Parsers.scala | 9 +- .../scala/tools/nsc/interpreter/CodeHandlers.scala | 12 +- .../scala/tools/nsc/interpreter/ExprTyper.scala | 117 +++++------- .../scala/tools/nsc/interpreter/ILoop.scala | 178 +++++++++++------- .../scala/tools/nsc/interpreter/IMain.scala | 81 +++++--- .../tools/nsc/interpreter/JLineCompletion.scala | 9 +- .../tools/nsc/interpreter/MemberHandlers.scala | 3 + .../scala/tools/nsc/interpreter/Power.scala | 4 +- .../scala/tools/nsc/interpreter/ReplGlobal.scala | 57 ++++++ .../scala/tools/nsc/interpreter/ReplReporter.scala | 4 + .../scala/tools/nsc/interpreter/ReplVals.scala | 7 +- .../scala/tools/nsc/interpreter/RichClass.scala | 5 +- .../scala/tools/nsc/interpreter/TypeStrings.scala | 136 ++++++++++++++ .../tools/nsc/typechecker/ContextErrors.scala | 4 +- .../tools/nsc/typechecker/DestructureTypes.scala | 206 +++++++++++++++++++++ src/library/scala/collection/SeqExtractors.scala | 3 + test/files/jvm/interpreter.check | 5 +- test/files/run/repl-colon-type.check | 173 ++++++++++++++++- test/files/run/repl-colon-type.scala | 8 + test/files/run/repl-power.check | 1 - test/files/run/repl-type-verbose.check | 186 +++++++++++++++++++ test/files/run/repl-type-verbose.scala | 20 ++ 29 files changed, 1067 insertions(+), 193 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/interpreter/ReplGlobal.scala create mode 100644 src/compiler/scala/tools/nsc/typechecker/DestructureTypes.scala create mode 100644 test/files/run/repl-type-verbose.check create mode 100644 test/files/run/repl-type-verbose.scala diff --git a/src/compiler/scala/reflect/internal/Definitions.scala b/src/compiler/scala/reflect/internal/Definitions.scala index 72fca5da12..66b505c0d1 100644 --- a/src/compiler/scala/reflect/internal/Definitions.scala +++ b/src/compiler/scala/reflect/internal/Definitions.scala @@ -292,7 +292,7 @@ trait Definitions extends reflect.api.StandardDefinitions { sealed abstract class BottomClassSymbol(name: TypeName, parent: Symbol) extends ClassSymbol(ScalaPackageClass, NoPosition, name) { locally { - this initFlags ABSTRACT | TRAIT | FINAL + this initFlags ABSTRACT | FINAL this setInfoAndEnter ClassInfoType(List(parent.tpe), newScope, this) } final override def isBottomClass = true @@ -1014,10 +1014,10 @@ trait Definitions extends reflect.api.StandardDefinitions { val flatname = nme.flattenedName(owner.name, name) getMember(pkg, flatname) } else { - throw new FatalError(owner + " does not have a member " + name) - } + throw new FatalError(owner + " does not have a member " + name) } } + } def getMemberIfDefined(owner: Symbol, name: Name): Symbol = owner.info.nonPrivateMember(name) diff --git a/src/compiler/scala/reflect/internal/SymbolCreations.scala b/src/compiler/scala/reflect/internal/SymbolCreations.scala index a1163b0f57..ac82ffe62a 100644 --- a/src/compiler/scala/reflect/internal/SymbolCreations.scala +++ b/src/compiler/scala/reflect/internal/SymbolCreations.scala @@ -11,7 +11,6 @@ import scala.collection.mutable.ListBuffer import util.Statistics._ import Flags._ import api.Modifier -import scala.tools.util.StringOps.{ ojoin } trait SymbolCreations { self: SymbolTable => diff --git a/src/compiler/scala/reflect/internal/SymbolFlags.scala b/src/compiler/scala/reflect/internal/SymbolFlags.scala index febcec8c7c..7741d700b9 100644 --- a/src/compiler/scala/reflect/internal/SymbolFlags.scala +++ b/src/compiler/scala/reflect/internal/SymbolFlags.scala @@ -11,7 +11,6 @@ import scala.collection.mutable.ListBuffer import util.Statistics._ import Flags._ import api.Modifier -import scala.tools.util.StringOps.{ ojoin } trait SymbolFlags { self: SymbolTable => diff --git a/src/compiler/scala/reflect/internal/Symbols.scala b/src/compiler/scala/reflect/internal/Symbols.scala index c9947c3c09..6eaae7f1ee 100644 --- a/src/compiler/scala/reflect/internal/Symbols.scala +++ b/src/compiler/scala/reflect/internal/Symbols.scala @@ -707,7 +707,10 @@ trait Symbols extends api.Symbols { self: SymbolTable => || isAnonOrRefinementClass // has uninteresting or prefix || nme.isReplWrapperName(name) // has ugly $iw. prefix (doesn't call isInterpreterWrapper due to nesting) ) - def isFBounded = info.baseTypeSeq exists (_ contains this) + def isFBounded = info match { + case TypeBounds(_, _) => info.baseTypeSeq exists (_ contains this) + case _ => false + } /** Is symbol a monomorphic type? * assumption: if a type starts out as monomorphic, it will not acquire @@ -1205,8 +1208,9 @@ trait Symbols extends api.Symbols { self: SymbolTable => /** Set new info valid from start of this phase. */ def updateInfo(info: Type): Symbol = { - assert(phaseId(infos.validFrom) <= phase.id) - if (phaseId(infos.validFrom) == phase.id) infos = infos.prev + val pid = phaseId(infos.validFrom) + assert(pid <= phase.id, (pid, phase.id)) + if (pid == phase.id) infos = infos.prev infos = TypeHistory(currentPeriod, info, infos) _validTo = if (info.isComplete) currentPeriod else NoPeriod this diff --git a/src/compiler/scala/reflect/internal/Types.scala b/src/compiler/scala/reflect/internal/Types.scala index afb1d8061e..fc57a130d1 100644 --- a/src/compiler/scala/reflect/internal/Types.scala +++ b/src/compiler/scala/reflect/internal/Types.scala @@ -2082,7 +2082,7 @@ trait Types extends api.Types { self: SymbolTable => // TODO: is there another way a typeref's symbol can refer to a symbol defined in its pre? case _ => sym } - + override def kind = "AliasTypeRef" } trait AbstractTypeRef extends NonClassTypeRef { @@ -2135,6 +2135,7 @@ trait Types extends api.Types { self: SymbolTable => override def bounds = thisInfo.bounds // def transformInfo(tp: Type): Type = appliedType(tp.asSeenFrom(pre, sym.owner), typeArgsOrDummies) override protected[Types] def baseTypeSeqImpl: BaseTypeSeq = transform(bounds.hi).baseTypeSeq prepend this + override def kind = "AbstractTypeRef" } /** A class for named types of the form @@ -2579,7 +2580,7 @@ trait Types extends api.Types { self: SymbolTable => !(qset contains sym) && !isQuantified(pre) case _ => false - } + } } override def safeToString: String = { diff --git a/src/compiler/scala/tools/nsc/CompilationUnits.scala b/src/compiler/scala/tools/nsc/CompilationUnits.scala index 92a0efff1e..ce6a7ed6af 100644 --- a/src/compiler/scala/tools/nsc/CompilationUnits.scala +++ b/src/compiler/scala/tools/nsc/CompilationUnits.scala @@ -5,7 +5,7 @@ package scala.tools.nsc -import util.{ FreshNameCreator, Position, NoPosition, SourceFile, NoSourceFile } +import util.{ FreshNameCreator, Position, NoPosition, BatchSourceFile, SourceFile, NoSourceFile } import scala.collection.mutable import scala.collection.mutable.{ LinkedHashSet, ListBuffer } diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 403b5717b0..9169f80e1c 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -950,6 +950,11 @@ class Global(var currentSettings: Settings, var reporter: NscReporter) extends S reporter.warning(NoPosition, "there were %d %s warnings; re-run with %s for details".format(warnings.size, what, option.name)) } + def newUnitParser(code: String) = new syntaxAnalyzer.UnitParser(newCompilationUnit(code)) + def newUnitScanner(code: String) = new syntaxAnalyzer.UnitScanner(newCompilationUnit(code)) + def newCompilationUnit(code: String) = new CompilationUnit(newSourceFile(code)) + def newSourceFile(code: String) = new BatchSourceFile("", code) + /** A Run is a single execution of the compiler on a sets of units */ class Run { @@ -1275,7 +1280,7 @@ class Global(var currentSettings: Settings, var reporter: NscReporter) extends S // nothing to compile, but we should still report use of deprecated options if (sources.isEmpty) { - checkDeprecatedSettings(new CompilationUnit(new BatchSourceFile("", ""))) + checkDeprecatedSettings(newCompilationUnit("")) reportCompileErrors() return } diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index eb4deeeee2..bca1cc4596 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -317,7 +317,7 @@ self => * by compilationUnit(). */ def scriptBody(): Tree = { - val stmts = templateStatSeq(false)._2 + val stmts = templateStats() accept(EOF) def mainModuleName = newTermName(settings.script.value) @@ -2869,6 +2869,13 @@ self => stats.toList } + /** Informal - for the repl and other direct parser accessors. + */ + def templateStats(): List[Tree] = templateStatSeq(false)._2 match { + case Nil => List(EmptyTree) + case stats => stats + } + /** {{{ * TemplateStatSeq ::= [id [`:' Type] `=>'] TemplateStat {semi TemplateStat} * TemplateStat ::= Import diff --git a/src/compiler/scala/tools/nsc/interpreter/CodeHandlers.scala b/src/compiler/scala/tools/nsc/interpreter/CodeHandlers.scala index 42a47896a2..453b106808 100644 --- a/src/compiler/scala/tools/nsc/interpreter/CodeHandlers.scala +++ b/src/compiler/scala/tools/nsc/interpreter/CodeHandlers.scala @@ -17,9 +17,7 @@ import scala.util.control.ControlThrowable trait CodeHandlers[T] { self => - // Expressions are composed of operators and operands. - def expr(code: String): T - +/* // A declaration introduces names and assigns them types. // It can form part of a class definition (§5.1) or of a refinement in a compound type (§3.2.7). // (Ed: aka abstract members.) @@ -34,6 +32,10 @@ trait CodeHandlers[T] { // ‘val’ PatDef | ‘var’ VarDef | ‘def’ FunDef | ‘type’ {nl} TypeDef | // [‘case’] ‘class’ ClassDef | [‘case’] ‘object’ ObjectDef | ‘trait’ TraitDef def defn(code: String): T +*/ + + // Expressions are composed of operators and operands. + def expr(code: String): T // An import clause has the form import p.I where p is a stable identifier (§3.1) and I is an import expression. def impt(code: String): T @@ -52,9 +54,9 @@ trait CodeHandlers[T] { case _: NoSuccess => Nil } + // def decl(code: String) = try Some(self.decl(code)) catch handler + // def defn(code: String) = try Some(self.defn(code)) catch handler def expr(code: String) = try Some(self.expr(code)) catch handler - def decl(code: String) = try Some(self.decl(code)) catch handler - def defn(code: String) = try Some(self.defn(code)) catch handler def impt(code: String) = try Some(self.impt(code)) catch handler def stmt(code: String) = try Some(self.stmt(code)) catch handler def stmts(code: String) = try (self.stmts(code) map (x => Some(x))) catch handlerSeq diff --git a/src/compiler/scala/tools/nsc/interpreter/ExprTyper.scala b/src/compiler/scala/tools/nsc/interpreter/ExprTyper.scala index a2ce8439de..0cd918b6a5 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ExprTyper.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ExprTyper.scala @@ -22,8 +22,7 @@ trait ExprTyper { object codeParser extends { val global: repl.global.type = repl.global } with CodeHandlers[Tree] { def applyRule[T](code: String, rule: UnitParser => T): T = { reporter.reset() - val unit = new CompilationUnit(new BatchSourceFile("", code)) - val scanner = new UnitParser(unit) + val scanner = newUnitParser(code) val result = rule(scanner) if (!reporter.hasErrors) @@ -33,23 +32,17 @@ trait ExprTyper { } def tokens(code: String) = { reporter.reset() - val unit = new CompilationUnit(new BatchSourceFile("", code)) - val in = new UnitScanner(unit) + val in = newUnitScanner(code) in.init() - new Tokenizer(in) tokenIterator } - def decl(code: String) = CodeHandlers.fail("todo") - def defn(code: String) = CodeHandlers.fail("todo") + def defns(code: String) = stmts(code) collect { case x: DefTree => x } def expr(code: String) = applyRule(code, _.expr()) def impt(code: String) = applyRule(code, _.importExpr()) def impts(code: String) = applyRule(code, _.importClause()) - def stmts(code: String) = applyRule(code, _.templateStatSeq(false)._2) - def stmt(code: String) = stmts(code) match { - case List(t) => t - case xs => CodeHandlers.fail("Not a single statement: " + xs.mkString(", ")) - } + def stmts(code: String) = applyRule(code, _.templateStats()) + def stmt(code: String) = stmts(code).last // guaranteed nonempty } /** Parse a line into a sequence of trees. Returns None if the input is incomplete. */ @@ -62,77 +55,69 @@ trait ExprTyper { else Some(trees) } } - def tokens(line: String) = beQuietDuring(codeParser.tokens(line)) + // def parsesAsExpr(line: String) = { + // import codeParser._ + // (opt expr line).isDefined + // } - // TODO: integrate these into a CodeHandler[Type]. + def symbolOfLine(code: String): Symbol = { + def asExpr(): Symbol = { + val name = freshInternalVarName() + // Typing it with a lazy val would give us the right type, but runs + // into compiler bugs with things like existentials, so we compile it + // behind a def and strip the NullaryMethodType which wraps the expr. + val line = "def " + name + " = {\n" + code + "\n}" + + interpretSynthetic(line) match { + case IR.Success => + val sym0 = symbolOfTerm(name) + // drop NullaryMethodType + val sym = sym0.cloneSymbol setInfo afterTyper(sym0.info.finalResultType) + if (sym.info.typeSymbol eq UnitClass) NoSymbol + else sym + case _ => NoSymbol + } + } + def asDefn(): Symbol = { + val old = repl.definedSymbolList.toSet + + interpretSynthetic(code) match { + case IR.Success => + repl.definedSymbolList filterNot old match { + case Nil => NoSymbol + case sym :: Nil => sym + case syms => NoSymbol.newOverloaded(NoPrefix, syms) + } + case _ => NoSymbol + } + } + beQuietDuring(asExpr()) orElse beQuietDuring(asDefn()) + } - // XXX literals. - // 1) Identifiers defined in the repl. - // 2) A path loadable via getModule. - // 3) Try interpreting it as an expression. private var typeOfExpressionDepth = 0 def typeOfExpression(expr: String, silent: Boolean = true): Type = { - repltrace("typeOfExpression(" + expr + ")") if (typeOfExpressionDepth > 2) { repldbg("Terminating typeOfExpression recursion for expression: " + expr) return NoType } - - def asQualifiedImport: Type = { - val name = expr.takeWhile(_ != '.') - typeOfExpression(importedTermNamed(name).fullName + expr.drop(name.length), true) - } - def asModule: Type = getModuleIfDefined(expr).tpe - def asExpr: Type = { - val lhs = freshInternalVarName() - val line = "lazy val " + lhs + " =\n" + expr - - interpret(line, true) match { - case IR.Success => typeOfExpression(lhs, true) - case _ => NoType - } - } - - def evaluate(): Type = { - typeOfExpressionDepth += 1 - try typeOfTerm(expr) orElse asModule orElse asExpr orElse asQualifiedImport - finally typeOfExpressionDepth -= 1 - } - + typeOfExpressionDepth += 1 // Don't presently have a good way to suppress undesirable success output // while letting errors through, so it is first trying it silently: if there // is an error, and errors are desired, then it re-evaluates non-silently // to induce the error message. - beSilentDuring(evaluate()) orElse beSilentDuring(typeOfDeclaration(expr)) orElse { - if (!silent) - evaluate() - - NoType + try beSilentDuring(symbolOfLine(expr).tpe) match { + case NoType if !silent => symbolOfLine(expr).tpe // generate error + case tpe => tpe } + finally typeOfExpressionDepth -= 1 } - // Since people will be giving us ":t def foo = 5" even though that is not an - // expression, we have a means of typing declarations too. - private def typeOfDeclaration(code: String): Type = { - repltrace("typeOfDeclaration(" + code + ")") - val obname = freshInternalVarName() - interpret("object " + obname + " {\n" + code + "\n}\n", true) match { - case IR.Success => - val sym = symbolOfTerm(obname) - if (sym == NoSymbol) NoType else { - // TODO: bitmap$n is not marked synthetic. - val decls = sym.tpe.decls.toList filterNot (x => x.isConstructor || x.isPrivate || (x.name.toString contains "$")) - repltrace("decls: " + decls) - if (decls.isEmpty) NoType - else cleanMemberDecl(sym, decls.last.name) - } - case _ => - NoType - } - } + def tokens(line: String) = beQuietDuring(codeParser.tokens(line)) + + // In the todo column + // // def compileAndTypeExpr(expr: String): Option[Typer] = { // class TyperRun extends Run { // override def stopPhase(name: String) = name == "superaccessors" // } - // } } diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala index bf386d8a12..9279d37464 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -49,12 +49,60 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) var settings: Settings = _ var intp: IMain = _ - override def echoCommandMessage(msg: String): Unit = - intp.reporter.printMessage(msg) + /** Having inherited the difficult "var-ness" of the repl instance, + * I'm trying to work around it by moving operations into a class from + * which it will appear a stable prefix. + */ + private def onIntp[T](f: IMain => T): T = f(intp) + + class IMainOps[T <: IMain](val intp: T) { + import intp._ + import global._ + + def printAfterTyper(msg: => String) = + intp.reporter printUntruncatedMessage afterTyper(msg) + + /** Strip NullaryMethodType artifacts. */ + private def replInfo(sym: Symbol) = { + sym.info match { + case NullaryMethodType(restpe) if sym.isAccessor => restpe + case info => info + } + } + def echoTypeStructure(sym: Symbol) = + printAfterTyper("" + deconstruct.show(replInfo(sym))) + + def echoTypeSignature(sym: Symbol, verbose: Boolean) = { + if (verbose) ILoop.this.echo("// Type signature") + printAfterTyper("" + replInfo(sym)) + + if (verbose) { + ILoop.this.echo("\n// Internal Type structure") + echoTypeStructure(sym) + } + } + } + implicit def stabilizeIMain(intp: IMain) = new IMainOps[intp.type](intp) + + /** TODO - + * -n normalize + * -l label with case class parameter names + * -c complete - leave nothing out + */ + private def typeCommandInternal(expr: String, verbose: Boolean): Result = { + onIntp { intp => + val sym = intp.symbolOfLine(expr) + if (sym.exists) intp.echoTypeSignature(sym, verbose) + else "" + } + } + + override def echoCommandMessage(msg: String) { + intp.reporter printUntruncatedMessage msg + } def isAsync = !settings.Yreplsync.value lazy val power = new Power(intp, new StdReplVals(this)) - lazy val NoType = intp.global.NoType // TODO // object opt extends AestheticSettings @@ -249,7 +297,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) // nullary("reset", "reset the interpreter, forgetting session values but retaining session types", replay), shCommand, nullary("silent", "disable/enable automatic printing of results", verbosity), - cmd("type", "", "display the type of an expression without evaluating it", typeCommand), + cmd("type", "[-v] ", "display the type of an expression without evaluating it", typeCommand), nullary("warnings", "show the suppressed warnings from the most recent line which had any", warningsCommand) ) @@ -322,10 +370,9 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } } - private def implicitsCommand(line: String): Result = { - val intp = ILoop.this.intp + private def implicitsCommand(line: String): Result = onIntp { intp => import intp._ - import global.{ Symbol, afterTyper } + import global._ def p(x: Any) = intp.reporter.printMessage("" + x) @@ -436,14 +483,14 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) private lazy val javap = substituteAndLog[Javap]("javap", NoJavap)(newJavap()) // Still todo: modules. - private def typeCommand(line: String): Result = { - if (line.trim == "") ":type " - else { - val tp = intp.typeOfExpression(line, false) - if (tp == NoType) "" // the error message was already printed - else intp.global.afterTyper(tp.toString) + private def typeCommand(line0: String): Result = { + line0.trim match { + case "" => ":type [-v] " + case s if s startsWith "-v " => typeCommandInternal(s stripPrefix "-v " trim, true) + case s => typeCommandInternal(s, false) } } + private def warningsCommand(): Result = { intp.lastWarnings foreach { case (pos, msg) => intp.reporter.warning(pos, msg) } } @@ -471,33 +518,34 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } private def wrapCommand(line: String): Result = { def failMsg = "Argument to :wrap must be the name of a method with signature [T](=> T): T" - val intp = ILoop.this.intp - val g: intp.global.type = intp.global - import g._ - - words(line) match { - case Nil => - intp.executionWrapper match { - case "" => "No execution wrapper is set." - case s => "Current execution wrapper: " + s - } - case "clear" :: Nil => - intp.executionWrapper match { - case "" => "No execution wrapper is set." - case s => intp.clearExecutionWrapper() ; "Cleared execution wrapper." - } - case wrapper :: Nil => - intp.typeOfExpression(wrapper) match { - case PolyType(List(targ), MethodType(List(arg), restpe)) => - intp setExecutionWrapper intp.pathToTerm(wrapper) - "Set wrapper to '" + wrapper + "'" - case tp => - failMsg + ( - if (tp == g.NoType) "\nFound: " - else "\nFound: " - ) - } - case _ => failMsg + onIntp { intp => + import intp._ + import global._ + + words(line) match { + case Nil => + intp.executionWrapper match { + case "" => "No execution wrapper is set." + case s => "Current execution wrapper: " + s + } + case "clear" :: Nil => + intp.executionWrapper match { + case "" => "No execution wrapper is set." + case s => intp.clearExecutionWrapper() ; "Cleared execution wrapper." + } + case wrapper :: Nil => + intp.typeOfExpression(wrapper) match { + case PolyType(List(targ), MethodType(List(arg), restpe)) => + intp setExecutionWrapper intp.pathToTerm(wrapper) + "Set wrapper to '" + wrapper + "'" + case tp => + failMsg + ( + if (tp == NoType) "\nFound: " + else "\nFound: " + ) + } + case _ => failMsg + } } } @@ -798,6 +846,10 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) }) } + def runCompletion = + try in.completion execute code map (intp bindSyntheticValue _) + catch { case ex: Exception => None } + /** Here we place ourselves between the user and the interpreter and examine * the input they are ostensibly submitting. We intervene in several cases: * @@ -815,30 +867,28 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) else if (Completion.looksLikeInvocation(code) && intp.mostRecentVar != "") { interpretStartingWith(intp.mostRecentVar + code) } - else { - def runCompletion = - try in.completion execute code map (intp bindValue _) - catch { case ex: Exception => None } - - /** Due to my accidentally letting file completion execution sneak ahead - * of actual parsing this now operates in such a way that the scala - * interpretation always wins. However to avoid losing useful file - * completion I let it fail and then check the others. So if you - * type /tmp it will echo a failure and then give you a Directory object. - * It's not pretty: maybe I'll implement the silence bits I need to avoid - * echoing the failure. - */ - if (intp isParseable code) { - val (code, result) = reallyInterpret - if (power != null && code == IR.Error) - runCompletion - - result - } - else runCompletion match { - case Some(_) => None // completion hit: avoid the latent error - case _ => reallyInterpret._2 // trigger the latent error - } + else if (code.trim startsWith "//") { + // line comment, do nothing + None + } + /** Due to my accidentally letting file completion execution sneak ahead + * of actual parsing this now operates in such a way that the scala + * interpretation always wins. However to avoid losing useful file + * completion I let it fail and then check the others. So if you + * type /tmp it will echo a failure and then give you a Directory object. + * It's not pretty: maybe I'll implement the silence bits I need to avoid + * echoing the failure. + */ + else if (intp isParseable code) { + val (code, result) = reallyInterpret + if (power != null && code == IR.Error) + runCompletion + + result + } + else runCompletion match { + case Some(_) => None // completion hit: avoid the latent error + case _ => reallyInterpret._2 // trigger the latent error } } diff --git a/src/compiler/scala/tools/nsc/interpreter/IMain.scala b/src/compiler/scala/tools/nsc/interpreter/IMain.scala index 43e01bebfd..13124e6afc 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IMain.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -24,6 +24,7 @@ import scala.collection.{ mutable, immutable } import scala.util.control.Exception.{ ultimately } import IMain._ import java.util.concurrent.Future +import typechecker.Analyzer import language.implicitConversions /** directory to save .class files to */ @@ -140,7 +141,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends lazy val formatting: Formatting = new Formatting { val prompt = Properties.shellPromptString } - lazy val reporter: ConsoleReporter = new ReplReporter(this) + lazy val reporter: ReplReporter = new ReplReporter(this) import formatting._ import reporter.{ printMessage, withoutTruncating } @@ -156,6 +157,8 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends } catch AbstractOrMissingHandler() } + private def tquoted(s: String) = "\"\"\"" + s + "\"\"\"" + // argument is a thunk to execute after init is done def initialize(postInitSignal: => Unit) { synchronized { @@ -226,6 +229,10 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends } import naming._ + object deconstruct extends { + val global: imain.global.type = imain.global + } with StructuredTypeStrings + // object dossiers extends { // val intp: imain.type = imain // } with Dossiers { } @@ -275,11 +282,10 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends protected def createLineManager(classLoader: ClassLoader): Line.Manager = new Line.Manager(classLoader) /** Instantiate a compiler. Overridable. */ - protected def newCompiler(settings: Settings, reporter: NscReporter) = { + protected def newCompiler(settings: Settings, reporter: NscReporter): ReplGlobal = { settings.outputDirs setSingleOutput virtualDirectory settings.exposeEmptyPackage.value = true - - Global(settings, reporter) + new Global(settings, reporter) with ReplGlobal } /** Parent classloader. Overridable. */ @@ -521,7 +527,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends trees.last match { case _:Assign => // we don't want to include assignments case _:TermTree | _:Ident | _:Select => // ... but do want other unnamed terms. - val varName = if (synthetic) freshInternalVarName() else ("" + freshUserTermName()) + val varName = if (synthetic) freshInternalVarName() else freshUserVarName() val rewrittenLine = ( // In theory this would come out the same without the 1-specific test, but // it's a cushion against any more sneaky parse-tree position vs. code mismatches: @@ -587,6 +593,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends * e.g. that there were no parse errors. */ def interpret(line: String): IR.Result = interpret(line, false) + def interpretSynthetic(line: String): IR.Result = interpret(line, true) def interpret(line: String, synthetic: Boolean): IR.Result = { def loadAndRunReq(req: Request) = { val (result, succeeded) = req.loadAndRun @@ -679,7 +686,8 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends def quietBind(p: NamedParam): IR.Result = beQuietDuring(bind(p)) def bind(p: NamedParam): IR.Result = bind(p.name, p.tpe, p.value) def bind[T: Manifest](name: String, value: T): IR.Result = bind((name, value)) - def bindValue(x: Any): IR.Result = bindValue("" + freshUserTermName(), x) + def bindSyntheticValue(x: Any): IR.Result = bindValue(freshInternalVarName(), x) + def bindValue(x: Any): IR.Result = bindValue(freshUserVarName(), x) def bindValue(name: String, x: Any): IR.Result = bind(name, TypeStrings.fromValue(x), x) /** Reset this interpreter, forgetting all user-specified requests. */ @@ -852,6 +860,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends /** handlers for each tree in this request */ val handlers: List[MemberHandler] = trees map (memberHandlers chooseHandler _) + def defHandlers = handlers collect { case x: MemberDefHandler => x } /** all (public) names defined by these statements */ val definedNames = handlers flatMap (_.definedNames) @@ -863,6 +872,10 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends def termNames = handlers flatMap (_.definesTerm) def typeNames = handlers flatMap (_.definesType) def definedOrImported = handlers flatMap (_.definedOrImported) + def definedSymbolList = defHandlers flatMap (_.definedSymbols) + + def definedTypeSymbol(name: String) = definedSymbols(newTypeName(name)) + def definedTermSymbol(name: String) = definedSymbols(newTermName(name)) /** Code to import bound names from previous lines - accessPath is code to * append to objectName to access anything bound by request. @@ -915,8 +928,6 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends val generate = (m: MemberHandler) => m extraCodeToEvaluate Request.this } - def tquoted(s: String) = "\"\"\"" + s + "\"\"\"" - private object ResultObjectSourceCode extends CodeAssembler[MemberHandler] { /** We only want to generate this code when the result * is a value which can be referred to as-is. @@ -970,6 +981,16 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends typeOf typesOfDefinedTerms + // Assign symbols to the original trees + // TODO - just use the new trees. + defHandlers foreach { dh => + val name = dh.member.name + definedSymbols get name foreach { sym => + dh.member setSymbol sym + repldbg("Set symbol of " + name + " to " + sym.defString) + } + } + // compile the result-extraction object beQuietDuring { savingSettings(_.nowarn.value = true) { @@ -991,17 +1012,17 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends } /** Types of variables defined by this request. */ - lazy val compilerTypeOf = typeMap[Type](x => x) + lazy val compilerTypeOf = typeMap[Type](x => x) withDefaultValue NoType /** String representations of same. */ lazy val typeOf = typeMap[String](tp => afterTyper(tp.toString)) // lazy val definedTypes: Map[Name, Type] = { // typeNames map (x => x -> afterTyper(resultSymbol.info.nonPrivateDecl(x).tpe)) toMap // } - lazy val definedSymbols: Map[Name, Symbol] = ( + lazy val definedSymbols = ( termNames.map(x => x -> applyToResultMember(x, x => x)) ++ - typeNames.map(x => x -> compilerTypeOf.get(x).map(_.typeSymbol).getOrElse(NoSymbol)) - ).toMap + typeNames.map(x => x -> compilerTypeOf(x).typeSymbol) + ).toMap[Name, Symbol] withDefaultValue NoSymbol lazy val typesOfDefinedTerms: Map[Name, Type] = termNames map (x => x -> applyToResultMember(x, _.tpe)) toMap @@ -1075,18 +1096,21 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends } def valueOfTerm(id: String): Option[AnyRef] = - requestForIdent(id) flatMap (_.getEval) + requestForName(newTermName(id)) flatMap (_.getEval) def classOfTerm(id: String): Option[JClass] = valueOfTerm(id) map (_.getClass) def typeOfTerm(id: String): Type = newTermName(id) match { case nme.ROOTPKG => definitions.RootClass.tpe - case name => requestForName(name) flatMap (_.compilerTypeOf get name) getOrElse NoType + case name => requestForName(name).fold(NoType: Type)(_ compilerTypeOf name) } + def symbolOfType(id: String): Symbol = + requestForName(newTypeName(id)).fold(NoSymbol: Symbol)(_ definedTypeSymbol id) + def symbolOfTerm(id: String): Symbol = - requestForIdent(id) flatMap (_.definedSymbols get newTermName(id)) getOrElse NoSymbol + requestForIdent(newTermName(id)).fold(NoSymbol: Symbol)(_ definedTermSymbol id) def runtimeClassAndTypeOfTerm(id: String): Option[(JClass, Type)] = { classOfTerm(id) flatMap { clazz => @@ -1120,11 +1144,15 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends val global: imain.global.type = imain.global } with ReplTokens { } - private object exprTyper extends { + object exprTyper extends { val repl: IMain.this.type = imain } with ExprTyper { } def parse(line: String): Option[List[Tree]] = exprTyper.parse(line) + + def symbolOfLine(code: String): Symbol = + exprTyper.symbolOfLine(code) + def typeOfExpression(expr: String, silent: Boolean = true): Type = exprTyper.typeOfExpression(expr, silent) @@ -1134,14 +1162,15 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends protected def onlyTerms(xs: List[Name]) = xs collect { case x: TermName => x } protected def onlyTypes(xs: List[Name]) = xs collect { case x: TypeName => x } - def definedTerms = onlyTerms(allDefinedNames) filterNot isInternalTermName - def definedTypes = onlyTypes(allDefinedNames) - def definedSymbols = prevRequests.toSet flatMap ((x: Request) => x.definedSymbols.values) + def definedTerms = onlyTerms(allDefinedNames) filterNot isInternalTermName + def definedTypes = onlyTypes(allDefinedNames) + def definedSymbols = prevRequestList.flatMap(_.definedSymbols.values).toSet[Symbol] + def definedSymbolList = prevRequestList flatMap (_.definedSymbolList) filterNot (s => isInternalTermName(s.name)) // Terms with user-given names (i.e. not res0 and not synthetic) def namedDefinedTerms = definedTerms filterNot (x => isUserVarName("" + x) || directlyBoundNames(x)) - private def findName(name: Name) = definedSymbols find (_.name == name) + private def findName(name: Name) = definedSymbols find (_.name == name) getOrElse NoSymbol /** Translate a repl-defined identifier into a Symbol. */ @@ -1150,16 +1179,19 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends def types(name: String): Symbol = { val tpname = newTypeName(name) - findName(tpname) getOrElse getClassIfDefined(tpname) + findName(tpname) orElse getClassIfDefined(tpname) } def terms(name: String): Symbol = { val termname = newTypeName(name) - findName(termname) getOrElse getModuleIfDefined(termname) + findName(termname) orElse getModuleIfDefined(termname) } def types[T: ClassManifest] : Symbol = types(classManifest[T].erasure.getName) def terms[T: ClassManifest] : Symbol = terms(classManifest[T].erasure.getName) def apply[T: ClassManifest] : Symbol = apply(classManifest[T].erasure.getName) + def classSymbols = allDefSymbols collect { case x: ClassSymbol => x } + def methodSymbols = allDefSymbols collect { case x: MethodSymbol => x } + /** the previous requests this interpreter has processed */ private var executingRequest: Request = _ private val prevRequests = mutable.ListBuffer[Request]() @@ -1167,7 +1199,10 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends private val definedNameMap = mutable.Map[Name, Request]() private val directlyBoundNames = mutable.Set[Name]() - private def allHandlers = prevRequestList flatMap (_.handlers) + def allHandlers = prevRequestList flatMap (_.handlers) + def allDefHandlers = allHandlers collect { case x: MemberDefHandler => x } + def allDefSymbols = allDefHandlers map (_.symbol) filter (_ ne NoSymbol) + def lastRequest = if (prevRequests.isEmpty) null else prevRequests.last def prevRequestList = prevRequests.toList def allSeenTypes = prevRequestList flatMap (_.typeOf.values.toList) distinct diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala index a86462ad5f..795b2e3678 100644 --- a/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala @@ -194,14 +194,7 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput // literal Ints, Strings, etc. object literals extends CompletionAware { - def simpleParse(code: String): Tree = { - val unit = new CompilationUnit(new util.BatchSourceFile("", code)) - val scanner = new syntaxAnalyzer.UnitParser(unit) - val tss = scanner.templateStatSeq(false)._2 - - if (tss.size == 1) tss.head else EmptyTree - } - + def simpleParse(code: String): Tree = newUnitParser(code).templateStats().last def completions(verbosity: Int) = Nil override def follow(id: String) = simpleParse(id) match { diff --git a/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala b/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala index 40993eb916..fcede04aaf 100644 --- a/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala +++ b/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala @@ -64,6 +64,7 @@ trait MemberHandlers { } sealed abstract class MemberDefHandler(override val member: MemberDef) extends MemberHandler(member) { + def symbol = if (member.symbol eq null) NoSymbol else member.symbol def name: Name = member.name def mods: Modifiers = member.mods def keyword = member.keyword @@ -72,6 +73,7 @@ trait MemberHandlers { override def definesImplicit = member.mods.isImplicit override def definesTerm: Option[TermName] = Some(name.toTermName) filter (_ => name.isTermName) override def definesType: Option[TypeName] = Some(name.toTypeName) filter (_ => name.isTypeName) + override def definedSymbols = if (symbol eq NoSymbol) Nil else List(symbol) } /** Class to handle one member among all the members included @@ -89,6 +91,7 @@ trait MemberHandlers { def importedNames = List[Name]() def definedNames = definesTerm.toList ++ definesType.toList def definedOrImported = definedNames ++ importedNames + def definedSymbols = List[Symbol]() def extraCodeToEvaluate(req: Request): String = "" def resultExtractionCode(req: Request): String = "" diff --git a/src/compiler/scala/tools/nsc/interpreter/Power.scala b/src/compiler/scala/tools/nsc/interpreter/Power.scala index ecf9dd219b..2cb034f7ab 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Power.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Power.scala @@ -410,8 +410,8 @@ class Power[ReplValsImpl <: ReplVals : Manifest](val intp: IMain, replVals: Repl lazy val phased: Phased = new { val global: intp.global.type = intp.global } with Phased { } def context(code: String) = analyzer.rootContext(unit(code)) - def source(code: String) = new BatchSourceFile("", code) - def unit(code: String) = new CompilationUnit(source(code)) + def source(code: String) = newSourceFile(code) + def unit(code: String) = newCompilationUnit(code) def trees(code: String) = parse(code) getOrElse Nil def typeOf(id: String) = intp.typeOfExpression(id) diff --git a/src/compiler/scala/tools/nsc/interpreter/ReplGlobal.scala b/src/compiler/scala/tools/nsc/interpreter/ReplGlobal.scala new file mode 100644 index 0000000000..05321dd7e6 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/ReplGlobal.scala @@ -0,0 +1,57 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +import reporters._ +import typechecker.Analyzer + +/** A layer on top of Global so I can guarantee some extra + * functionality for the repl. It doesn't do much yet. + */ +trait ReplGlobal extends Global { + // This exists mostly because using the reporter too early leads to deadlock. + private def echo(msg: String) { Console println msg } + + override def abort(msg: String): Nothing = { + echo("ReplGlobal.abort: " + msg) + super.abort(msg) + } + + override lazy val analyzer = new { + val global: ReplGlobal.this.type = ReplGlobal.this + } with Analyzer { + override def newTyper(context: Context): Typer = new Typer(context) { + override def typed(tree: Tree, mode: Int, pt: Type): Tree = { + val res = super.typed(tree, mode, pt) + tree match { + case Ident(name) if !tree.symbol.hasPackageFlag && !name.toString.startsWith("$") => + repldbg("typed %s: %s".format(name, res.tpe)) + case _ => + } + res + } + } + } + + object replPhase extends SubComponent { + val global: ReplGlobal.this.type = ReplGlobal.this + val phaseName = "repl" + val runsAfter = List[String]("typer") + val runsRightAfter = None + def newPhase(_prev: Phase): StdPhase = new StdPhase(_prev) { + def apply(unit: CompilationUnit) { + repldbg("Running replPhase on " + unit.body) + // newNamer(rootContext(unit)).enterSym(unit.body) + } + } + } + + override protected def computePhaseDescriptors: List[SubComponent] = { + addToPhasesSet(replPhase, "repl") + super.computePhaseDescriptors + } +} diff --git a/src/compiler/scala/tools/nsc/interpreter/ReplReporter.scala b/src/compiler/scala/tools/nsc/interpreter/ReplReporter.scala index 130af990ad..fb61dfb672 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ReplReporter.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ReplReporter.scala @@ -9,7 +9,11 @@ package interpreter import reporters._ import IMain._ +/** Like ReplGlobal, a layer for ensuring extra functionality. + */ class ReplReporter(intp: IMain) extends ConsoleReporter(intp.settings, Console.in, new ReplStrippingWriter(intp)) { + def printUntruncatedMessage(msg: String) = withoutTruncating(printMessage(msg)) + override def printMessage(msg: String) { // Avoiding deadlock if the compiler starts logging before // the lazy val is complete. diff --git a/src/compiler/scala/tools/nsc/interpreter/ReplVals.scala b/src/compiler/scala/tools/nsc/interpreter/ReplVals.scala index 8b891e0010..4efab7e260 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ReplVals.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ReplVals.scala @@ -27,7 +27,8 @@ class StdReplVals(final val r: ILoop) extends ReplVals { final lazy val phased = power.phased final lazy val analyzer = global.analyzer - final lazy val treedsl = new { val global: intp.global.type = intp.global } with ast.TreeDSL { } + object treedsl extends { val global: intp.global.type = intp.global } with ast.TreeDSL { } + final lazy val typer = analyzer.newTyper( analyzer.rootContext( power.unit("").asInstanceOf[analyzer.global.CompilationUnit] @@ -35,13 +36,15 @@ class StdReplVals(final val r: ILoop) extends ReplVals { ) def lastRequest = intp.lastRequest - final lazy val replImplicits = new power.Implicits2 { + class ReplImplicits extends power.Implicits2 { import intp.global._ private val manifestFn = ReplVals.mkManifestToType[intp.global.type](global) implicit def mkManifestToType(sym: Symbol) = manifestFn(sym) } + final lazy val replImplicits = new ReplImplicits + def typed[T <: analyzer.global.Tree](tree: T): T = typer.typed(tree).asInstanceOf[T] } diff --git a/src/compiler/scala/tools/nsc/interpreter/RichClass.scala b/src/compiler/scala/tools/nsc/interpreter/RichClass.scala index 59a7b9b5d2..b1bee6ce93 100644 --- a/src/compiler/scala/tools/nsc/interpreter/RichClass.scala +++ b/src/compiler/scala/tools/nsc/interpreter/RichClass.scala @@ -13,7 +13,10 @@ class RichClass[T](val clazz: Class[T]) { // Sadly isAnonymousClass does not return true for scala anonymous // classes because our naming scheme is not doing well against the // jvm's many assumptions. - def isScalaAnonymous = clazz.isAnonymousClass || (clazz.getName contains "$anon$") + def isScalaAnonymous = ( + try clazz.isAnonymousClass || (clazz.getName contains "$anon$") + catch { case _: java.lang.InternalError => false } // good ol' "Malformed class name" + ) /** It's not easy... to be... me... */ def supermans: List[Manifest[_]] = supers map (_.toManifest) diff --git a/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala b/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala index 872ac00bfd..9ba75d9166 100644 --- a/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala +++ b/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala @@ -11,6 +11,142 @@ import r.TypeVariable import scala.reflect.NameTransformer import NameTransformer._ import scala.reflect.{mirror => rm} +import typechecker.DestructureTypes +import scala.tools.util.StringOps.ojoin + +/** A more principled system for turning types into strings. + */ +trait StructuredTypeStrings extends DestructureTypes { + val global: Global + import global._ + import definitions._ + + case class LabelAndType(label: String, typeName: String) { } + object LabelAndType { + val empty = LabelAndType("", "") + } + case class Grouping(ldelim: String, mdelim: String, rdelim: String, labels: Boolean) { + def join(elems: String*): String = ( + if (elems.isEmpty) "" + else elems.mkString(ldelim, mdelim, rdelim) + ) + } + val NoGrouping = Grouping("", "", "", false) + val ListGrouping = Grouping("(", ", ", ")", false) + val ProductGrouping = Grouping("(", ", ", ")", true) + val ParamGrouping = Grouping("(", ", ", ")", true) + val BlockGrouping = Grouping(" { ", "; ", "}", false) + + private implicit def lowerName(n: Name): String = "" + n + private def str(level: Int)(body: => String): String = " " * level + body + private def block(level: Int, grouping: Grouping)(name: String, nodes: List[TypeNode]): String = { + val l1 = str(level)(name + grouping.ldelim) + val l2 = nodes.map(_ show level + 1) + val l3 = str(level)(grouping.rdelim) + + l1 +: l2 :+ l3 mkString "\n" + } + private def maybeBlock(level: Int, grouping: Grouping)(name: String, nodes: List[TypeNode]): String = { + import grouping._ + val threshold = 70 + + val try1 = str(level)(name + grouping.join(nodes map (_.show(0, grouping.labels)): _*)) + if (try1.length < threshold) try1 + else block(level, grouping)(name, nodes) + } + private def shortClass(x: Any) = { + if (opt.debug) { + val name = (x.getClass.getName split '.').last + val isAnon = name.reverse takeWhile (_ != '$') forall (_.isDigit) + val str = if (isAnon) name else (name split '$').last + + " // " + str + } + else "" + } + + sealed abstract class TypeNode { + def grouping: Grouping + def nodes: List[TypeNode] + + def show(indent: Int, showLabel: Boolean): String = maybeBlock(indent, grouping)(mkPrefix(showLabel), nodes) + def show(indent: Int): String = show(indent, true) + def show(): String = show(0) + + def withLabel(l: String): this.type = modifyNameInfo(_.copy(label = l)) + def withType(t: String): this.type = modifyNameInfo(_.copy(typeName = t)) + + def label = nameInfo.label + def typeName = nameInfo.typeName + + protected def mkPrefix(showLabel: Boolean) = { + val pre = if (showLabel && label != "") label + " = " else "" + pre + typeName + } + override def toString = show() // + "(toString)" + private var nameInfo: LabelAndType = LabelAndType.empty + private def modifyNameInfo(f: LabelAndType => LabelAndType): this.type = { + nameInfo = f(nameInfo) + this + } + } + case class TypeAtom[T](atom: T) extends TypeNode { + def grouping = NoGrouping + def nodes = Nil + override protected def mkPrefix(showLabel: Boolean) = + super.mkPrefix(showLabel) + atom + shortClass(atom) + } + case class TypeProduct(nodes: List[TypeNode]) extends TypeNode { + def grouping: Grouping = ProductGrouping + def emptyTypeName = "" + override def typeName = if (nodes.isEmpty) emptyTypeName else super.typeName + } + + /** For a NullaryMethod, in = TypeEmpty; for MethodType(Nil, _) in = TypeNil */ + class NullaryFunction(out: TypeNode) extends TypeProduct(List(out)) { + override def typeName = "NullaryMethodType" + } + class MonoFunction(in: TypeNode, out: TypeNode) extends TypeProduct(List(in, out)) { + override def typeName = "MethodType" + } + class PolyFunction(in: TypeNode, out: TypeNode) extends TypeProduct(List(in, out)) { + override def typeName = "PolyType" + } + + class TypeList(nodes: List[TypeNode]) extends TypeProduct(nodes) { + override def grouping = ListGrouping + override def emptyTypeName = "Nil" + override def typeName = "List" + } + class TypeScope(nodes: List[TypeNode]) extends TypeProduct(nodes) { + override def grouping = BlockGrouping + override def typeName = "Scope" + override def emptyTypeName = "EmptyScope" + } + + object TypeEmpty extends TypeNode { + override def grouping = NoGrouping + override def nodes = Nil + override def label = "" + override def typeName = "" + override def show(indent: Int, showLabel: Boolean) = "" + } + + object intoNodes extends DestructureType[TypeNode] { + def withLabel(node: TypeNode, label: String): TypeNode = node withLabel label + def withType(node: TypeNode, typeName: String): TypeNode = node withType typeName + + def wrapEmpty = TypeEmpty + def wrapSequence(nodes: List[TypeNode]) = new TypeList(nodes) + def wrapProduct(nodes: List[TypeNode]) = new TypeProduct(nodes) + def wrapPoly(in: TypeNode, out: TypeNode) = new PolyFunction(in, out) + def wrapMono(in: TypeNode, out: TypeNode) = if (in == wrapEmpty) new NullaryFunction(out) else new MonoFunction(in, out) + def wrapAtom[U](value: U) = new TypeAtom(value) + } + + def show(tp: Type): String = intoNodes(tp).show +} + /** Logic for turning a type into a String. The goal is to be * able to take some arbitrary object 'x' and obtain the most precise diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index e9c3bef737..8d26f66370 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -129,11 +129,11 @@ trait ContextErrors { val retyped = typed (tree.duplicate setType null) val foundDecls = retyped.tpe.decls filter (sym => !sym.isConstructor && !sym.isSynthetic) - if (foundDecls.isEmpty) found + if (foundDecls.isEmpty || (found.typeSymbol eq NoSymbol)) found else { // The members arrive marked private, presumably because there was no // expected type and so they're considered members of an anon class. - foundDecls foreach (_ resetFlag (PRIVATE | PROTECTED)) + foundDecls foreach (_.makePublic) // TODO: if any of the found parents match up with required parents after normalization, // print the error so that they match. The major beneficiary there would be // java.lang.Object vs. AnyRef. diff --git a/src/compiler/scala/tools/nsc/typechecker/DestructureTypes.scala b/src/compiler/scala/tools/nsc/typechecker/DestructureTypes.scala new file mode 100644 index 0000000000..0b414801d6 --- /dev/null +++ b/src/compiler/scala/tools/nsc/typechecker/DestructureTypes.scala @@ -0,0 +1,206 @@ +/* NSC -- new Scala compiler +* Copyright 2005-2012 LAMP/EPFL +* @author Paul Phillips +*/ + +package scala.tools.nsc +package typechecker + +/** A generic means of breaking down types into their subcomponents. + * Types are decomposed top down, and recognizable substructure is + * dispatched via self-apparently named methods. Those methods can + * be overridden for custom behavior, but only the abstract methods + * require implementations, each of which must create some unknown + * "Node" type from its inputs. + * + * - wrapProduct create Node from a product of Nodes + * - wrapSequence create Node from a sequence of Nodes + * - wrapAtom create Node from an arbitrary value + * + * This is a work in progress. + */ +trait DestructureTypes { + val global: Global + import global._ + import definitions.{ NothingClass, AnyClass } + + trait DestructureType[Node] extends (Type => Node) { + def withLabel(node: Node, label: String): Node + def withType(node: Node, typeName: String): Node + + def wrapEmpty: Node + def wrapPoly(in: Node, out: Node): Node + def wrapMono(in: Node, out: Node): Node + def wrapProduct(nodes: List[Node]): Node + def wrapSequence(nodes: List[Node]): Node + def wrapAtom[U](value: U): Node + + private implicit def liftToTerm(name: String): TermName = newTermName(name) + + private val openSymbols = collection.mutable.Set[Symbol]() + + private def nodeList[T](elems: List[T], mkNode: T => Node): Node = + if (elems.isEmpty) wrapEmpty else list(elems map mkNode) + + private def scopeMemberList(elems: List[Symbol]): Node = nodeList(elems, wrapAtom) + private def typeList(elems: List[Type]): Node = nodeList(elems, this) + private def symbolList(elems: List[Symbol]): Node = nodeList(elems, wrapSymbolInfo) + private def treeList(elems: List[Tree]): Node = nodeList(elems, wrapTree) + private def annotationList(annots: List[AnnotationInfo]): Node = nodeList(annots, annotation) + + private def assocsNode(ann: AnnotationInfo): Node = { + val (names, args) = ann.assocs.toIndexedSeq.unzip + if (names.isEmpty) wrapEmpty + else node("assocs", nodeList(names.indices.toList, (i: Int) => atom(names(i).toString, args(i)))) + } + private def typeTypeName(tp: Type) = tp match { + case mt @ MethodType(_, _) if mt.isImplicit => "ImplicitMethodType" + case TypeRef(_, sym, _) => typeRefType(sym) + case _ => tp.kind + } + + def wrapTree(tree: Tree): Node = withType( + tree match { + case x: NameTree => atom(x.name.toString, x) + case _ => wrapAtom(tree) + }, + tree.printingPrefix + ) + def wrapSymbol(label: String, sym: Symbol): Node = { + if (sym eq NoSymbol) wrapEmpty + else atom(label, sym) + } + def wrapInfo(sym: Symbol) = sym.info match { + case TypeBounds(lo, hi) => typeBounds(lo, hi) + case PolyType(tparams, restpe) => polyFunction(tparams, restpe) + case _ => wrapEmpty + } + def wrapSymbolInfo(sym: Symbol): Node = { + if ((sym eq NoSymbol) || openSymbols(sym)) wrapEmpty + else { + openSymbols += sym + try product(symbolType(sym), wrapAtom(sym.defString)) + finally openSymbols -= sym + } + } + + def list(nodes: List[Node]): Node = wrapSequence(nodes) + def product(tp: Type, nodes: Node*): Node = product(typeTypeName(tp), nodes: _*) + def product(typeName: String, nodes: Node*): Node = ( + nodes.toList filterNot (_ == wrapEmpty) match { + case Nil => wrapEmpty + case xs => withType(wrapProduct(xs), typeName) + } + ) + + def atom[U](label: String, value: U): Node = node(label, wrapAtom(value)) + def constant(label: String, const: Constant): Node = atom(label, const) + + def scope(decls: Scope): Node = node("decls", scopeMemberList(decls.toList)) + def const[T](named: (String, T)): Node = constant(named._1, Constant(named._2)) + + def resultType(restpe: Type): Node = this("resultType", restpe) + def typeParams(tps: List[Symbol]): Node = node("typeParams", symbolList(tps)) + def valueParams(params: List[Symbol]): Node = node("params", symbolList(params)) + def typeArgs(tps: List[Type]): Node = node("args", typeList(tps)) + def parentList(tps: List[Type]): Node = node("parents", typeList(tps)) + + def polyFunction(tparams: List[Symbol], restpe: Type): Node = wrapPoly(typeParams(tparams), resultType(restpe)) + def monoFunction(params: List[Symbol], restpe: Type): Node = wrapMono(valueParams(params), resultType(restpe)) + def nullaryFunction(restpe: Type): Node = wrapMono(wrapEmpty, this(restpe)) + + def prefix(pre: Type): Node = pre match { + case NoPrefix => wrapEmpty + case _ => this("pre", pre) + } + def typeBounds(lo0: Type, hi0: Type): Node = { + val lo = if ((lo0 eq WildcardType) || (lo0.typeSymbol eq NothingClass)) wrapEmpty else this("lo", lo0) + val hi = if ((hi0 eq WildcardType) || (hi0.typeSymbol eq AnyClass)) wrapEmpty else this("hi", hi0) + + product("TypeBounds", lo, hi) + } + + def annotation(ann: AnnotationInfo): Node = product( + "AnnotationInfo", + this("atp", ann.atp), + node("args", treeList(ann.args)), + assocsNode(ann) + ) + def typeConstraint(constr: TypeConstraint): Node = product( + "TypeConstraint", + node("lo", typeList(constr.loBounds)), + node("hi", typeList(constr.hiBounds)), + this("inst", constr.inst) + ) + def annotatedType(annotations: List[AnnotationInfo], underlying: Type) = product( + "AnnotatedType", + node("annotations", annotationList(annotations)), + this("underlying", underlying) + ) + + /** This imposes additional structure beyond that which is visible in + * the case class hierarchy. In particular, (too) many different constructs + * are encoded in TypeRefs; here they are partitioned somewhat before + * being dispatched. + * + * For example, a typical type parameter is encoded as TypeRef(NoPrefix, sym, Nil) + * with its upper and lower bounds stored in the info of the symbol. Viewing the + * TypeRef naively we are treated to both too much information (useless prefix, usually + * empty args) and too little (bounds hidden behind indirection.) So drop the prefix + * and promote the bounds. + */ + def typeRef(tp: TypeRef) = { + val TypeRef(pre, sym, args) = tp + // Filtered down to elements with "interesting" content + product( + tp, + if (sym.isDefinedInPackage) wrapEmpty else prefix(pre), + wrapSymbolInfo(sym), + typeArgs(args), + if (tp ne tp.normalize) this("normalize", tp.normalize) else wrapEmpty + ) + } + + def symbolType(sym: Symbol) = ( + if (sym.isRefinementClass) "Refinement" + else if (sym.isAliasType) "Alias" + else if (sym.isTypeSkolem) "TypeSkolem" + else if (sym.isTypeParameter) "TypeParam" + else if (sym.isAbstractType) "AbstractType" + else if (sym.isType) "TypeSymbol" + else "TermSymbol" + ) + def typeRefType(sym: Symbol) = ( + if (sym.isRefinementClass) "RefinementTypeRef" + else if (sym.isAliasType) "AliasTypeRef" + else if (sym.isTypeSkolem) "SkolemTypeRef" + else if (sym.isTypeParameter) "TypeParamTypeRef" + else if (sym.isAbstractType) "AbstractTypeRef" + else "TypeRef" + ) + ( if (sym.isFBounded) "(F-Bounded)" else "" ) + + def node(label: String, node: Node): Node = withLabel(node, label) + def apply(label: String, tp: Type): Node = withLabel(this(tp), label) + + def apply(tp: Type): Node = tp match { + case AntiPolyType(pre, targs) => product(tp, prefix(pre), typeArgs(targs)) + case ClassInfoType(parents, decls, clazz) => product(tp, parentList(parents), scope(decls), wrapAtom(clazz)) + case ConstantType(const) => product(tp, constant("value", const)) + case DeBruijnIndex(level, index, args) => product(tp, const("level" -> level), const("index" -> index), typeArgs(args)) + case OverloadedType(pre, alts) => product(tp, prefix(pre), node("alts", typeList(alts map pre.memberType))) + case RefinedType(parents, decls) => product(tp, parentList(parents), scope(decls)) + case SingleType(pre, sym) => product(tp, prefix(pre), wrapAtom(sym)) + case SuperType(thistp, supertp) => product(tp, this("this", thistp), this("super", supertp)) + case ThisType(clazz) => product(tp, wrapAtom(clazz)) + case TypeVar(inst, constr) => product(tp, this("inst", inst), typeConstraint(constr)) + case AnnotatedType(annotations, underlying, _) => annotatedType(annotations, underlying) + case ExistentialType(tparams, underlying) => polyFunction(tparams, underlying) + case PolyType(tparams, restpe) => polyFunction(tparams, restpe) + case MethodType(params, restpe) => monoFunction(params, restpe) + case NullaryMethodType(restpe) => nullaryFunction(restpe) + case TypeBounds(lo, hi) => typeBounds(lo, hi) + case tr @ TypeRef(pre, sym, args) => typeRef(tr) + case _ => wrapAtom(tp) // XXX see what this is + } + } +} diff --git a/src/library/scala/collection/SeqExtractors.scala b/src/library/scala/collection/SeqExtractors.scala index cb3cb27f18..cbb09a0a90 100644 --- a/src/library/scala/collection/SeqExtractors.scala +++ b/src/library/scala/collection/SeqExtractors.scala @@ -19,3 +19,6 @@ object :+ { if(t.isEmpty) None else Some(t.init -> t.last) } + +// Dummy to fool ant +private abstract class SeqExtractors \ No newline at end of file diff --git a/test/files/jvm/interpreter.check b/test/files/jvm/interpreter.check index b9ff6afa2b..292d2fc4cb 100644 --- a/test/files/jvm/interpreter.check +++ b/test/files/jvm/interpreter.check @@ -316,9 +316,8 @@ scala> /* */ */ -scala> -scala> +You typed two blank lines. Starting a new command. scala> // multi-line string @@ -326,7 +325,7 @@ scala> """ hello there """ -res9: String = +res12: String = " hello there diff --git a/test/files/run/repl-colon-type.check b/test/files/run/repl-colon-type.check index 66c2fcc77f..cb0b9a6c8b 100644 --- a/test/files/run/repl-colon-type.check +++ b/test/files/run/repl-colon-type.check @@ -7,6 +7,12 @@ scala> :type List[1, 2, 3] :2: error: identifier expected but integer literal found. List[1, 2, 3] ^ +:3: error: ']' expected but '}' found. + } + ^ +:1: error: identifier expected but integer literal found. + List[1, 2, 3] + ^ scala> :type List(1, 2, 3) @@ -25,7 +31,7 @@ scala> :type def f[T >: Null, U <: String](x: T, y: U) = Set(x, y) [T >: Null, U <: String](x: T, y: U)scala.collection.immutable.Set[Any] scala> :type def x = 1 ; def bar[T >: Null <: AnyRef](xyz: T) = 5 -[T >: Null <: AnyRef](xyz: T)Int +=> Int [T >: Null <: AnyRef](xyz: T)Int scala> @@ -39,10 +45,19 @@ scala> :type lazy val f = 5 Int scala> :type protected lazy val f = 5 -Int +:2: error: illegal start of statement (no modifiers allowed here) + protected lazy val f = 5 + ^ +:5: error: lazy value f cannot be accessed in object $iw + Access to protected value f not permitted because + enclosing object $eval in package $line19 is not a subclass of + object $iw where target is defined + lazy val $result = `f` + ^ + scala> :type def f = 5 -Int +=> Int scala> :type def f() = 5 ()Int @@ -54,4 +69,156 @@ scala> :type def g[T](xs: Set[_ <: T]) = Some(xs.head) scala> +scala> // verbose! + +scala> :type -v List(1,2,3) filter _ +// Type signature +(Int => Boolean) => List[Int] + +// Internal Type structure +TypeRef( + TypeSymbol(abstract trait Function1[-T1, +R] extends Object) + args = List( + TypeRef( + TypeSymbol(abstract trait Function1[-T1, +R] extends Object) + args = List( + TypeRef(TypeSymbol(final class Int extends AnyVal)) + TypeRef(TypeSymbol(final class Boolean extends AnyVal)) + ) + ) + TypeRef( + TypeSymbol( + sealed abstract class List[+A] extends AbstractSeq[A] with LinearSeq[A] with Product with GenericTraversableTemplate[A,List] with LinearSeqOptimized[A,List[A]] + + ) + args = List( + TypeRef(TypeSymbol(final class Int extends AnyVal)) + ) + ) + ) +) + +scala> :type -v def f[T >: Null, U <: String](x: T, y: U) = Set(x, y) +// Type signature +[T >: Null, U <: String](x: T, y: U)scala.collection.immutable.Set[Any] + +// Internal Type structure +PolyType( + typeParams = List(TypeParam(T >: Null), TypeParam(U <: String)) + resultType = MethodType( + params = List(TermSymbol(x: T), TermSymbol(y: U)) + resultType = TypeRef( + TypeSymbol( + abstract trait Set[A] extends Iterable[A] with Set[A] with GenericSetTemplate[A,scala.collection.immutable.Set] with SetLike[A,scala.collection.immutable.Set[A]] with Parallelizable[A,scala.collection.parallel.immutable.ParSet[A]] + + ) + args = List(TypeRef(TypeSymbol(abstract class Any extends ))) + ) + ) +) + +scala> :type -v def x = 1 ; def bar[T >: Null <: AnyRef](xyz: T) = 5 +// Type signature +=> Int [T >: Null <: AnyRef](xyz: T)Int + +// Internal Type structure +OverloadedType( + alts = List( + NullaryMethodType( + TypeRef(TypeSymbol(final class Int extends AnyVal)) + ) + PolyType( + typeParams = List(TypeParam(T >: Null <: AnyRef)) + resultType = MethodType( + params = List(TermSymbol(xyz: T)) + resultType = TypeRef( + TypeSymbol(final class Int extends AnyVal) + ) + ) + ) + ) +) + +scala> :type -v Nil.combinations _ +// Type signature +Int => Iterator[List[Nothing]] + +// Internal Type structure +TypeRef( + TypeSymbol(abstract trait Function1[-T1, +R] extends Object) + args = List( + TypeRef(TypeSymbol(final class Int extends AnyVal)) + TypeRef( + TypeSymbol( + abstract trait Iterator[+A] extends TraversableOnce[A] + ) + args = List( + TypeRef( + TypeSymbol( + sealed abstract class List[+A] extends AbstractSeq[A] with LinearSeq[A] with Product with GenericTraversableTemplate[A,List] with LinearSeqOptimized[A,List[A]] + + ) + args = List( + TypeRef( + TypeSymbol(final abstract class Nothing extends Any) + ) + ) + ) + ) + ) + ) +) + +scala> :type -v def f[T <: AnyVal] = List[T]().combinations _ +// Type signature +[T <: AnyVal]=> Int => Iterator[List[T]] + +// Internal Type structure +PolyType( + typeParams = List(TypeParam(T <: AnyVal)) + resultType = NullaryMethodType( + TypeRef( + TypeSymbol(abstract trait Function1[-T1, +R] extends Object) + args = List( + TypeRef(TypeSymbol(final class Int extends AnyVal)) + TypeRef( + TypeSymbol( + abstract trait Iterator[+A] extends TraversableOnce[A] + ) + args = List( + TypeRef( + TypeSymbol( + sealed abstract class List[+A] extends AbstractSeq[A] with LinearSeq[A] with Product with GenericTraversableTemplate[A,List] with LinearSeqOptimized[A,List[A]] + + ) + args = List(TypeParamTypeRef(TypeParam(T <: AnyVal))) + ) + ) + ) + ) + ) + ) +) + +scala> :type -v def f[T, U >: T](x: T, y: List[U]) = x :: y +// Type signature +[T, U >: T](x: T, y: List[U])List[U] + +// Internal Type structure +PolyType( + typeParams = List(TypeParam(T), TypeParam(U >: T)) + resultType = MethodType( + params = List(TermSymbol(x: T), TermSymbol(y: List[U])) + resultType = TypeRef( + TypeSymbol( + sealed abstract class List[+A] extends AbstractSeq[A] with LinearSeq[A] with Product with GenericTraversableTemplate[A,List] with LinearSeqOptimized[A,List[A]] + + ) + args = List(TypeParamTypeRef(TypeParam(U >: T))) + ) + ) +) + +scala> + scala> diff --git a/test/files/run/repl-colon-type.scala b/test/files/run/repl-colon-type.scala index 39ab580d2a..c055b215c2 100644 --- a/test/files/run/repl-colon-type.scala +++ b/test/files/run/repl-colon-type.scala @@ -18,6 +18,14 @@ object Test extends ReplTest { |:type def f() = 5 | |:type def g[T](xs: Set[_ <: T]) = Some(xs.head) + | + |// verbose! + |:type -v List(1,2,3) filter _ + |:type -v def f[T >: Null, U <: String](x: T, y: U) = Set(x, y) + |:type -v def x = 1 ; def bar[T >: Null <: AnyRef](xyz: T) = 5 + |:type -v Nil.combinations _ + |:type -v def f[T <: AnyVal] = List[T]().combinations _ + |:type -v def f[T, U >: T](x: T, y: List[U]) = x :: y """.stripMargin } diff --git a/test/files/run/repl-power.check b/test/files/run/repl-power.check index e439a2a7f4..1e7b6f0cd8 100644 --- a/test/files/run/repl-power.check +++ b/test/files/run/repl-power.check @@ -14,7 +14,6 @@ scala> global.emptyValDef // "it is imported twice in the same scope by ..." res0: $r.global.emptyValDef.type = private val _ = _ scala> val tp = ArrayClass[scala.util.Random] // magic with manifests -warning: there were 2 feature warnings; re-run with -feature for details tp: $r.global.Type = Array[scala.util.Random] scala> tp.memberType(Array_apply) // evidence diff --git a/test/files/run/repl-type-verbose.check b/test/files/run/repl-type-verbose.check new file mode 100644 index 0000000000..103ac3e64d --- /dev/null +++ b/test/files/run/repl-type-verbose.check @@ -0,0 +1,186 @@ +Type in expressions to have them evaluated. +Type :help for more information. + +scala> + +scala> // verbose! + +scala> :type -v def f = 5 +// Type signature +=> Int + +// Internal Type structure +NullaryMethodType( + TypeRef(TypeSymbol(final class Int extends AnyVal)) +) + +scala> :type -v def f() = 5 +// Type signature +()Int + +// Internal Type structure +NullaryMethodType( + resultType = TypeRef(TypeSymbol(final class Int extends AnyVal)) +) + +scala> :type -v def f[T] = 5 +// Type signature +[T]=> Int + +// Internal Type structure +PolyType( + typeParams = List(TypeParam(T)) + resultType = NullaryMethodType( + TypeRef(TypeSymbol(final class Int extends AnyVal)) + ) +) + +scala> :type -v def f[T >: Null] = 5 +// Type signature +[T >: Null]=> Int + +// Internal Type structure +PolyType( + typeParams = List(TypeParam(T >: Null)) + resultType = NullaryMethodType( + TypeRef(TypeSymbol(final class Int extends AnyVal)) + ) +) + +scala> :type -v def f[T <: String] = 5 +// Type signature +[T <: String]=> Int + +// Internal Type structure +PolyType( + typeParams = List(TypeParam(T <: String)) + resultType = NullaryMethodType( + TypeRef(TypeSymbol(final class Int extends AnyVal)) + ) +) + +scala> :type -v def f[T]() = 5 +// Type signature +[T]()Int + +// Internal Type structure +PolyType( + typeParams = List(TypeParam(T)) + resultType = NullaryMethodType( + resultType = TypeRef(TypeSymbol(final class Int extends AnyVal)) + ) +) + +scala> :type -v def f[T, U]() = 5 +// Type signature +[T, U]()Int + +// Internal Type structure +PolyType( + typeParams = List(TypeParam(T), TypeParam(U)) + resultType = NullaryMethodType( + resultType = TypeRef(TypeSymbol(final class Int extends AnyVal)) + ) +) + +scala> :type -v def f[T, U]()() = 5 +// Type signature +[T, U]()()Int + +// Internal Type structure +PolyType( + typeParams = List(TypeParam(T), TypeParam(U)) + resultType = NullaryMethodType( + resultType = NullaryMethodType( + resultType = TypeRef( + TypeSymbol(final class Int extends AnyVal) + ) + ) + ) +) + +scala> :type -v def f[T, U <: T] = 5 +// Type signature +[T, U <: T]=> Int + +// Internal Type structure +PolyType( + typeParams = List(TypeParam(T), TypeParam(U <: T)) + resultType = NullaryMethodType( + TypeRef(TypeSymbol(final class Int extends AnyVal)) + ) +) + +scala> :type -v def f[T, U <: T](x: T)(y: U) = 5 +// Type signature +[T, U <: T](x: T)(y: U)Int + +// Internal Type structure +PolyType( + typeParams = List(TypeParam(T), TypeParam(U <: T)) + resultType = MethodType( + params = List(TermSymbol(x: T)) + resultType = MethodType( + params = List(TermSymbol(y: U)) + resultType = TypeRef( + TypeSymbol(final class Int extends AnyVal) + ) + ) + ) +) + +scala> :type -v def f[T: Ordering] = 5 +// Type signature +[T](implicit evidence$1: Ordering[T])Int + +// Internal Type structure +PolyType( + typeParams = List(TypeParam(T)) + resultType = MethodType( + params = List(TermSymbol(implicit evidence$1: Ordering[T])) + resultType = TypeRef(TypeSymbol(final class Int extends AnyVal)) + ) +) + +scala> :type -v def f[T: Ordering] = implicitly[Ordering[T]] +// Type signature +[T](implicit evidence$1: Ordering[T])Ordering[T] + +// Internal Type structure +PolyType( + typeParams = List(TypeParam(T)) + resultType = MethodType( + params = List(TermSymbol(implicit evidence$1: Ordering[T])) + resultType = AliasTypeRef( + Alias(type Ordering[T] = scala.math.Ordering[T]) + args = List(TypeParamTypeRef(TypeParam(T))) + normalize = TypeRef( + TypeSymbol( + abstract trait Ordering[T] extends Comparator[T] with PartialOrdering[T] with Serializable + + ) + args = List(TypeParamTypeRef(TypeParam(T))) + ) + ) + ) +) + +scala> :type -v def f[T <: { type Bippy = List[Int] ; def g(): Bippy }] = 5 +// Type signature +[T <: AnyRef{type Bippy = List[Int]; def g(): this.Bippy}]=> Int + +// Internal Type structure +PolyType( + typeParams = List( + TypeParam( + T <: AnyRef{type Bippy = List[Int]; def g(): this.Bippy} + ) + ) + resultType = NullaryMethodType( + TypeRef(TypeSymbol(final class Int extends AnyVal)) + ) +) + +scala> + +scala> diff --git a/test/files/run/repl-type-verbose.scala b/test/files/run/repl-type-verbose.scala new file mode 100644 index 0000000000..10c390550a --- /dev/null +++ b/test/files/run/repl-type-verbose.scala @@ -0,0 +1,20 @@ +import scala.tools.partest.ReplTest + +object Test extends ReplTest { + def code = """ + |// verbose! + |:type -v def f = 5 + |:type -v def f() = 5 + |:type -v def f[T] = 5 + |:type -v def f[T >: Null] = 5 + |:type -v def f[T <: String] = 5 + |:type -v def f[T]() = 5 + |:type -v def f[T, U]() = 5 + |:type -v def f[T, U]()() = 5 + |:type -v def f[T, U <: T] = 5 + |:type -v def f[T, U <: T](x: T)(y: U) = 5 + |:type -v def f[T: Ordering] = 5 + |:type -v def f[T: Ordering] = implicitly[Ordering[T]] + |:type -v def f[T <: { type Bippy = List[Int] ; def g(): Bippy }] = 5 + """.stripMargin +} -- cgit v1.2.3