diff options
Diffstat (limited to 'src')
162 files changed, 2752 insertions, 2317 deletions
diff --git a/src/actors/scala/actors/AbstractActor.scala b/src/actors/scala/actors/AbstractActor.scala index 80b1e76b30..f97a1c3e2a 100644 --- a/src/actors/scala/actors/AbstractActor.scala +++ b/src/actors/scala/actors/AbstractActor.scala @@ -19,6 +19,8 @@ package scala.actors */ trait AbstractActor extends OutputChannel[Any] with CanReply[Any, Any] { + type Future[+R] = scala.actors.Future[R] + private[actors] def exiting: Boolean = false private[actors] def linkTo(to: AbstractActor): Unit diff --git a/src/actors/scala/actors/Actor.scala b/src/actors/scala/actors/Actor.scala index ccd60f666c..4e245f27cf 100644 --- a/src/actors/scala/actors/Actor.scala +++ b/src/actors/scala/actors/Actor.scala @@ -12,7 +12,6 @@ package scala.actors import scala.util.control.ControlThrowable import java.util.{Timer, TimerTask} -import java.util.concurrent.{ExecutionException, Callable} /** * The <code>Actor</code> object provides functions for the definition of @@ -117,17 +116,17 @@ object Actor extends Combinators { } /** - * <p>This is a factory method for creating actors.</p> + * This is a factory method for creating actors. * - * <p>The following example demonstrates its usage:</p> + * The following example demonstrates its usage: * - * <pre> + * {{{ * import scala.actors.Actor._ * ... * val a = actor { * ... * } - * </pre> + * }}} * * @param body the code block to be executed by the newly created actor * @return the newly created actor. Note that it is automatically started. @@ -142,14 +141,12 @@ object Actor extends Combinators { } /** - * <p> * This is a factory method for creating actors whose - * body is defined using a <code>Responder</code>. - * </p> + * body is defined using a `Responder`. * - * <p>The following example demonstrates its usage:</p> + * The following example demonstrates its usage: * - * <pre> + * {{{ * import scala.actors.Actor._ * import Responder.exec * ... @@ -159,9 +156,9 @@ object Actor extends Combinators { * if exec(println("result: "+res)) * } yield {} * } - * </pre> + * }}} * - * @param body the <code>Responder</code> to be executed by the newly created actor + * @param body the `Responder` to be executed by the newly created actor * @return the newly created actor. Note that it is automatically started. */ def reactor(body: => Responder[Unit]): Actor = { @@ -275,20 +272,18 @@ object Actor extends Combinators { def mailboxSize: Int = rawSelf.mailboxSize /** - * <p> * Converts a synchronous event-based operation into - * an asynchronous <code>Responder</code>. - * </p> + * an asynchronous `Responder`. * - * <p>The following example demonstrates its usage:</p> + * The following example demonstrates its usage: * - * <pre> + * {{{ * val adder = reactor { * for { * _ <- respondOn(react) { case Add(a, b) => reply(a+b) } * } yield {} * } - * </pre> + * }}} */ def respondOn[A, B](fun: PartialFunction[A, Unit] => Nothing): PartialFunction[A, B] => Responder[B] = @@ -325,7 +320,7 @@ object Actor extends Combinators { * * @param from the actor to unlink from */ - def unlink(from: Actor): Unit = self.unlink(from) + def unlink(from: AbstractActor): Unit = self.unlink(from) /** * <p> @@ -413,6 +408,43 @@ trait Actor extends AbstractActor with ReplyReactor with ActorCanReply with Inpu } } else super.startSearch(msg, replyTo, handler) + // we override this method to check `shouldExit` before suspending + private[actors] override def searchMailbox(startMbox: MQueue[Any], + handler: PartialFunction[Any, Any], + resumeOnSameThread: Boolean) { + var tmpMbox = startMbox + var done = false + while (!done) { + val qel = tmpMbox.extractFirst((msg: Any, replyTo: OutputChannel[Any]) => { + senders = List(replyTo) + handler.isDefinedAt(msg) + }) + if (tmpMbox ne mailbox) + tmpMbox.foreach((m, s) => mailbox.append(m, s)) + if (null eq qel) { + synchronized { + // in mean time new stuff might have arrived + if (!sendBuffer.isEmpty) { + tmpMbox = new MQueue[Any]("Temp") + drainSendBuffer(tmpMbox) + // keep going + } else { + // very important to check for `shouldExit` at this point + // since linked actors might have set it after we checked + // last time (e.g., at the beginning of `react`) + if (shouldExit) exit() + waitingFor = handler + // see Reactor.searchMailbox + throw Actor.suspendException + } + } + } else { + resumeReceiver((qel.msg, qel.session), handler, resumeOnSameThread) + done = true + } + } + } + private[actors] override def makeReaction(fun: () => Unit, handler: PartialFunction[Any, Any], msg: Any): Runnable = new ActorTask(this, fun, handler, msg) @@ -695,37 +727,51 @@ trait Actor extends AbstractActor with ReplyReactor with ActorCanReply with Inpu * <code>reason != 'normal</code>. * </p> */ - protected[actors] def exit(reason: AnyRef): Nothing = synchronized { - exitReason = reason + protected[actors] def exit(reason: AnyRef): Nothing = { + synchronized { + exitReason = reason + } exit() } /** * Terminates with exit reason <code>'normal</code>. */ - protected[actors] override def exit(): Nothing = synchronized { - if (!links.isEmpty) - exitLinked() + protected[actors] override def exit(): Nothing = { + val todo = synchronized { + if (!links.isEmpty) + exitLinked() + else + () => {} + } + todo() super.exit() } // Assume !links.isEmpty // guarded by this - private[actors] def exitLinked() { + private[actors] def exitLinked(): () => Unit = { _state = Actor.State.Terminated + // reset waitingFor, otherwise getState returns Suspended + waitingFor = Reactor.waitingForNone // remove this from links val mylinks = links.filterNot(this.==) - // exit linked processes - mylinks.foreach((linked: AbstractActor) => { - unlink(linked) - if (!linked.exiting) - linked.exit(this, exitReason) - }) + // unlink actors + mylinks.foreach(unlinkFrom(_)) + // return closure that locks linked actors + () => { + mylinks.foreach((linked: AbstractActor) => { + linked.synchronized { + if (!linked.exiting) + linked.exit(this, exitReason) + } + }) + } } // Assume !links.isEmpty // guarded by this - private[actors] def exitLinked(reason: AnyRef) { + private[actors] def exitLinked(reason: AnyRef): () => Unit = { exitReason = reason exitLinked() } @@ -745,6 +791,8 @@ trait Actor extends AbstractActor with ReplyReactor with ActorCanReply with Inpu if (isSuspended) resumeActor() else if (waitingFor ne Reactor.waitingForNone) { + waitingFor = Reactor.waitingForNone + // it doesn't matter what partial function we are passing here scheduleActor(waitingFor, null) /* Here we should not throw a SuspendActorControl, since the current method is called from an actor that diff --git a/src/actors/scala/actors/ActorCanReply.scala b/src/actors/scala/actors/ActorCanReply.scala index fdc3833ec4..a6a81815c1 100644 --- a/src/actors/scala/actors/ActorCanReply.scala +++ b/src/actors/scala/actors/ActorCanReply.scala @@ -10,8 +10,6 @@ package scala.actors -import java.util.concurrent.ExecutionException - /** * The `ActorCanReply` trait provides message send operations that * may result in a response from the receiver. @@ -19,19 +17,17 @@ import java.util.concurrent.ExecutionException * @author Philipp Haller */ private[actors] trait ActorCanReply extends ReactorCanReply { - thiz: AbstractActor with ReplyReactor => + this: AbstractActor with ReplyReactor => override def !?(msg: Any): Any = { - val replyCh = new Channel[Any](Actor.self(thiz.scheduler)) - thiz.send(msg, replyCh) - replyCh.receive { - case x => x - } + val replyCh = new Channel[Any](Actor.self(scheduler)) + send(msg, replyCh) + replyCh.? } override def !?(msec: Long, msg: Any): Option[Any] = { - val replyCh = new Channel[Any](Actor.self(thiz.scheduler)) - thiz.send(msg, replyCh) + val replyCh = new Channel[Any](Actor.self(scheduler)) + send(msg, replyCh) replyCh.receiveWithin(msec) { case TIMEOUT => None case x => Some(x) @@ -39,8 +35,8 @@ private[actors] trait ActorCanReply extends ReactorCanReply { } override def !![A](msg: Any, handler: PartialFunction[Any, A]): Future[A] = { - val ftch = new Channel[A](Actor.self(thiz.scheduler)) - thiz.send(msg, new OutputChannel[Any] { + val ftch = new Channel[A](Actor.self(scheduler)) + send(msg, new OutputChannel[Any] { def !(msg: Any) = ftch ! handler(msg) def send(msg: Any, replyTo: OutputChannel[Any]) = @@ -54,85 +50,8 @@ private[actors] trait ActorCanReply extends ReactorCanReply { } override def !!(msg: Any): Future[Any] = { - val ftch = new Channel[Any](Actor.self(thiz.scheduler)) - val linkedChannel = new AbstractActor { - def !(msg: Any) = { - ftch ! msg - thiz unlinkFrom this - } - def send(msg: Any, replyTo: OutputChannel[Any]) = { - ftch.send(msg, replyTo) - thiz unlinkFrom this - } - def forward(msg: Any) = { - ftch.forward(msg) - thiz unlinkFrom this - } - def receiver = - ftch.receiver - def linkTo(to: AbstractActor) { /* do nothing */ } - def unlinkFrom(from: AbstractActor) { /* do nothing */ } - def exit(from: AbstractActor, reason: AnyRef) { - ftch.send(Exit(from, reason), thiz) - thiz unlinkFrom this - } - // should never be invoked; return dummy value - def !?(msg: Any) = msg - // should never be invoked; return dummy value - def !?(msec: Long, msg: Any): Option[Any] = Some(msg) - // should never be invoked; return dummy value - override def !!(msg: Any): Future[Any] = { - val someChan = new Channel[Any](Actor.self(thiz.scheduler)) - Futures.fromInputChannel(someChan) - } - // should never be invoked; return dummy value - override def !![A](msg: Any, f: PartialFunction[Any, A]): Future[A] = { - val someChan = new Channel[A](Actor.self(thiz.scheduler)) - Futures.fromInputChannel(someChan) - } - } - thiz linkTo linkedChannel - thiz.send(msg, linkedChannel) - new Future[Any](ftch) { - var exitReason: Option[Any] = None - val handleReply: PartialFunction[Any, Unit] = { - case Exit(from, reason) => - exitReason = Some(reason) - case any => - fvalue = Some(any) - } - - def apply(): Any = - if (isSet) { - if (!fvalue.isEmpty) - fvalue.get - else if (!exitReason.isEmpty) { - val reason = exitReason.get - if (reason.isInstanceOf[Throwable]) - throw new ExecutionException(reason.asInstanceOf[Throwable]) - else - throw new ExecutionException(new Exception(reason.toString())) - } - } else inputChannel.receive(handleReply andThen { _ => apply() }) - - def respond(k: Any => Unit): Unit = - if (isSet) - apply() - else - inputChannel.react(handleReply andThen { _ => k(apply()) }) - - def isSet = (fvalue match { - case None => - val handleTimeout: PartialFunction[Any, Boolean] = { - case TIMEOUT => - false - } - val whatToDo = - handleTimeout orElse (handleReply andThen { _ => true }) - inputChannel.receiveWithin(0)(whatToDo) - case Some(_) => true - }) || !exitReason.isEmpty - } + val noTransform: PartialFunction[Any, Any] = { case x => x } + this !! (msg, noTransform) } } diff --git a/src/actors/scala/actors/ActorTask.scala b/src/actors/scala/actors/ActorTask.scala index 2fa24f93af..249c3784a4 100644 --- a/src/actors/scala/actors/ActorTask.scala +++ b/src/actors/scala/actors/ActorTask.scala @@ -25,24 +25,31 @@ private[actors] class ActorTask(actor: Actor, protected override def beginExecution() { super.beginExecution() - if (actor.shouldExit) - actor.exit() + actor.synchronized { // shouldExit guarded by actor + if (actor.shouldExit) + actor.exit() + } } - protected override def terminateExecution(e: Exception) { + protected override def terminateExecution(e: Throwable) { val senderInfo = try { Some(actor.sender) } catch { case _: Exception => None } - val uncaught = new UncaughtException(actor, - if (msg != null) Some(msg) else None, - senderInfo, - currentThread, - e) + val uncaught = UncaughtException(actor, + if (msg != null) Some(msg) else None, + senderInfo, + currentThread, + e) - actor.synchronized { + val todo = actor.synchronized { if (!actor.links.isEmpty) - actor exitLinked uncaught + actor.exitLinked(uncaught) + else { + super.terminateExecution(e) + () => {} + } } + todo() } } diff --git a/src/actors/scala/actors/CanReply.scala b/src/actors/scala/actors/CanReply.scala index 99e1169900..034eea8479 100644 --- a/src/actors/scala/actors/CanReply.scala +++ b/src/actors/scala/actors/CanReply.scala @@ -19,6 +19,8 @@ package scala.actors */ trait CanReply[-T, +R] { + type Future[+P] <: () => P + /** * Sends <code>msg</code> to this $actor and * awaits reply (synchronous). @@ -47,8 +49,7 @@ trait CanReply[-T, +R] { * @param msg the message to be sent * @return the future */ - def !!(msg: T): () => R = - () => this !? msg + def !!(msg: T): Future[R] /** * Sends <code>msg</code> to this $actor and @@ -61,7 +62,6 @@ trait CanReply[-T, +R] { * @param handler the function to be applied to the response * @return the future */ - def !![P](msg: T, handler: PartialFunction[R, P]): () => P = - () => handler(this !? msg) + def !![P](msg: T, handler: PartialFunction[R, P]): Future[P] } diff --git a/src/actors/scala/actors/Channel.scala b/src/actors/scala/actors/Channel.scala index e40a804e4a..bf5d9261db 100644 --- a/src/actors/scala/actors/Channel.scala +++ b/src/actors/scala/actors/Channel.scala @@ -11,19 +11,18 @@ package scala.actors -/** <p> - * This class is used to pattern match on values that were sent - * to some channel <code>Chan<sub>n</sub></code> by the current - * actor <code>self</code>. - * </p> - * <p> - * The following example demonstrates its usage: - * </p><pre> +/** + * This class is used to pattern match on values that were sent + * to some channel <code>Chan<sub>n</sub></code> by the current + * actor <code>self</code>. + * + * The following example demonstrates its usage: + * {{{ * receive { * <b>case</b> Chan1 ! msg1 => ... * <b>case</b> Chan2 ! msg2 => ... * } - * </pre> + * }}} * * @author Philipp Haller */ @@ -41,6 +40,8 @@ case class ! [a](ch: Channel[a], msg: a) */ class Channel[Msg](val receiver: Actor) extends InputChannel[Msg] with OutputChannel[Msg] with CanReply[Msg, Any] { + type Future[+P] = scala.actors.Future[P] + def this() = this(Actor.self) def !(msg: Msg) { @@ -109,4 +110,24 @@ class Channel[Msg](val receiver: Actor) extends InputChannel[Msg] with OutputCha } } + def !![A](msg: Msg, handler: PartialFunction[Any, A]): Future[A] = { + val ftch = new Channel[A](Actor.self(receiver.scheduler)) + receiver.send(scala.actors.!(this, msg), new OutputChannel[Any] { + def !(msg: Any) = + ftch ! handler(msg) + def send(msg: Any, replyTo: OutputChannel[Any]) = + ftch.send(handler(msg), replyTo) + def forward(msg: Any) = + ftch.forward(handler(msg)) + def receiver = + ftch.receiver + }) + Futures.fromInputChannel(ftch) + } + + def !!(msg: Msg): Future[Any] = { + val noTransform: PartialFunction[Any, Any] = { case x => x } + this !! (msg, noTransform) + } + } diff --git a/src/actors/scala/actors/Reactor.scala b/src/actors/scala/actors/Reactor.scala index 85dcd57189..fe00e216d3 100644 --- a/src/actors/scala/actors/Reactor.scala +++ b/src/actors/scala/actors/Reactor.scala @@ -84,6 +84,10 @@ trait Reactor[Msg >: Null] extends OutputChannel[Msg] with Combinators { */ def act(): Unit + /** + * This partial function is applied to exceptions that propagate out of + * this $actor's body. + */ protected[actors] def exceptionHandler: PartialFunction[Exception, Unit] = Map() diff --git a/src/actors/scala/actors/ReactorCanReply.scala b/src/actors/scala/actors/ReactorCanReply.scala index 9002a55b87..e279845c9b 100644 --- a/src/actors/scala/actors/ReactorCanReply.scala +++ b/src/actors/scala/actors/ReactorCanReply.scala @@ -19,6 +19,8 @@ package scala.actors private[actors] trait ReactorCanReply extends CanReply[Any, Any] { _: ReplyReactor => + override type Future[+P] = scala.actors.Future[P] + def !?(msg: Any): Any = (this !! msg)() @@ -39,10 +41,10 @@ private[actors] trait ReactorCanReply extends CanReply[Any, Any] { res.get(msec) } - override def !!(msg: Any): Future[Any] = + def !!(msg: Any): Future[Any] = this !! (msg, { case x => x }) - override def !![A](msg: Any, handler: PartialFunction[Any, A]): Future[A] = { + def !![A](msg: Any, handler: PartialFunction[Any, A]): Future[A] = { val myself = Actor.rawSelf(this.scheduler) val ftch = new ReactChannel[A](myself) val res = new scala.concurrent.SyncVar[A] diff --git a/src/actors/scala/actors/ReactorTask.scala b/src/actors/scala/actors/ReactorTask.scala index 3f9ebb6fa7..c379334f2f 100644 --- a/src/actors/scala/actors/ReactorTask.scala +++ b/src/actors/scala/actors/ReactorTask.scala @@ -31,17 +31,16 @@ private[actors] class ReactorTask[Msg >: Null](var reactor: Reactor[Msg], try { beginExecution() try { - try { - if (fun eq null) - handler(msg) - else - fun() - } catch { - case e: Exception if (reactor.exceptionHandler.isDefinedAt(e)) => - reactor.exceptionHandler(e) - } + if (fun eq null) + handler(msg) + else + fun() } catch { case _: KillActorControl => + // do nothing + + case e: Exception if reactor.exceptionHandler.isDefinedAt(e) => + reactor.exceptionHandler(e) } reactor.kill() } @@ -49,17 +48,11 @@ private[actors] class ReactorTask[Msg >: Null](var reactor: Reactor[Msg], case _: SuspendActorControl => // do nothing (continuation is already saved) - case e: Exception => - // print message on default error stream - val msgException = "Uncaught exception in "+reactor+"\n" - val msgMessage = if (msg != null) "Message: "+msg+"\n" else "" - Debug.doWarning { - Console.err.print(msgException + msgMessage) - e.printStackTrace() - } - + case e: Throwable => terminateExecution(e) reactor.terminated() + if (!e.isInstanceOf[Exception]) + throw e } finally { suspendExecution() this.reactor = null @@ -77,6 +70,9 @@ private[actors] class ReactorTask[Msg >: Null](var reactor: Reactor[Msg], protected def suspendExecution() {} - protected def terminateExecution(e: Exception) {} + protected def terminateExecution(e: Throwable) { + Console.err.println(reactor+": caught "+e) + e.printStackTrace() + } } diff --git a/src/actors/scala/actors/UncaughtException.scala b/src/actors/scala/actors/UncaughtException.scala index 54c28f66cf..2b61b1ad7a 100644 --- a/src/actors/scala/actors/UncaughtException.scala +++ b/src/actors/scala/actors/UncaughtException.scala @@ -13,17 +13,18 @@ package scala.actors * * @param actor the actor that threw the exception * @param message the message the actor was processing, or None if no message (e.g. on initial startup) + * @param sender the sender of the most recent message * @param thread the thread on which the actor was running * @param cause the uncaught exception * * @author Philipp Haller * @author Erik Engbrecht */ -class UncaughtException[Msg >: Null](val actor: Reactor[Msg], - val message: Option[Msg], - val sender: Option[OutputChannel[Any]], - val thread: Thread, - cause: Exception) +case class UncaughtException(actor: Actor, + message: Option[Any], + sender: Option[OutputChannel[Any]], + thread: Thread, + cause: Throwable) extends Exception(cause) { override def toString() = diff --git a/src/actors/scala/actors/remote/Proxy.scala b/src/actors/scala/actors/remote/Proxy.scala index f9a6cd8fed..ac5951ed85 100644 --- a/src/actors/scala/actors/remote/Proxy.scala +++ b/src/actors/scala/actors/remote/Proxy.scala @@ -20,8 +20,6 @@ import scala.collection.mutable.HashMap private[remote] class Proxy(node: Node, name: Symbol, @transient var kernel: NetKernel) extends AbstractActor { import java.io.{IOException, ObjectOutputStream, ObjectInputStream} - type Future[+R] = scala.actors.Future[R] - @transient private[remote] var del: Actor = null startDelegate() @@ -66,10 +64,10 @@ private[remote] class Proxy(node: Node, name: Symbol, @transient var kernel: Net def !?(msec: Long, msg: Any): Option[Any] = del !? (msec, msg) - override def !!(msg: Any): Future[Any] = + def !!(msg: Any): Future[Any] = del !! msg - override def !![A](msg: Any, f: PartialFunction[Any, A]): Future[A] = + def !![A](msg: Any, f: PartialFunction[Any, A]): Future[A] = del !! (msg, f) def linkTo(to: AbstractActor): Unit = diff --git a/src/actors/scala/actors/remote/RemoteActor.scala b/src/actors/scala/actors/remote/RemoteActor.scala index 860ff3bfe3..78361ec0fb 100644 --- a/src/actors/scala/actors/remote/RemoteActor.scala +++ b/src/actors/scala/actors/remote/RemoteActor.scala @@ -13,33 +13,30 @@ package scala.actors package remote -/** <p> - * This object provides methods for creating, registering, and - * selecting remotely accessible actors. - * </p> - * <p> - * A remote actor is typically created like this: - * </p><pre> +/** + * This object provides methods for creating, registering, and + * selecting remotely accessible actors. + * + * A remote actor is typically created like this: + * {{{ * actor { * alive(9010) * register('myName, self) * * // behavior * } - * </pre> - * <p> - * It can be accessed by an actor running on a (possibly) - * different node by selecting it in the following way: - * </p><pre> + * }}} + * It can be accessed by an actor running on a (possibly) + * different node by selecting it in the following way: + * {{{ * actor { * // ... - * <b>val</b> c = select(Node("127.0.0.1", 9010), 'myName) + * val c = select(Node("127.0.0.1", 9010), 'myName) * c ! msg * // ... * } - * </pre> + * }}} * - * @version 0.9.18 * @author Philipp Haller */ object RemoteActor { diff --git a/src/build/pack.xml b/src/build/pack.xml index fa6c4ade20..d75e36c337 100644 --- a/src/build/pack.xml +++ b/src/build/pack.xml @@ -120,9 +120,10 @@ MAIN DISTRIBUTION SBAZ <binset dir="${basedir}/test" includes="clitest,diff/diff.*,diff/lib*.dll,partest,partest.bat"/> <miscset dir="${basedir}/test" - includes="files/**/*.args,files/**/*.check,files/**/*.dll,files/**/*.jar,files/**/*.java,files/**/*.scala,files/**/*.flags,files/cli/**/*.check.*,files/jvm/*.so,files/shootout/*.javaopts,files/shootout/*.runner,files/shootout/*.txt"/> + includes="files/**/*.args,files/**/*.check,files/**/*.dll,files/**/*.jar,files/**/*.java,files/**/*.scala,files/**/*.flags,files/cli/**/*.check.*,files/jvm/*.so,files/shootout/*.javaopts,files/shootout/*.runner,files/shootout/*.txt,files/**/*.test"/> <!-- <srcset dir="${dist.dir}/src" includes="scala-partest-src.jar"/> --> <libset dir="${dist.dir}/lib" includes="scala-partest.jar"/> + <libset dir="${lib.dir}" includes="scalacheck.jar"/> </sbaz> </target> diff --git a/src/compiler/scala/tools/cmd/CommandLine.scala b/src/compiler/scala/tools/cmd/CommandLine.scala index 8cb4c00b14..9b8bef4a9a 100644 --- a/src/compiler/scala/tools/cmd/CommandLine.scala +++ b/src/compiler/scala/tools/cmd/CommandLine.scala @@ -8,18 +8,19 @@ package cmd import scala.collection.mutable.ListBuffer +trait CommandLineConfig { + def enforceArity: Boolean = true + def onlyKnownOptions: Boolean = true +} + /** An instance of a command line, parsed according to a Spec. */ -class CommandLine(val spec: Reference, val originalArgs: List[String]) { +class CommandLine(val spec: Reference, val originalArgs: List[String]) extends CommandLineConfig { def this(spec: Reference, line: String) = this(spec, Parser tokenize line) def this(spec: Reference, args: Array[String]) = this(spec, args.toList) import spec.{ isAnyOption, isUnaryOption, isBinaryOption, isExpandOption } - def assumeBinary = true - def enforceArity = true - def onlyKnownOptions = false - val Terminator = "--" val ValueForUnaryOption = "true" // so if --opt is given, x(--opt) = true @@ -32,13 +33,6 @@ class CommandLine(val spec: Reference, val originalArgs: List[String]) { lazy val (argMap, residualArgs) = { val residualBuffer = new ListBuffer[String] - def isOption(s: String) = isAnyOption(s) || ((s startsWith "-") && !onlyKnownOptions) - - def unknownOption(opt: String) = - errorFn("Option '%s' not recognized.".format(opt)) - def missingArg(opt: String, what: String) = - errorFn("Option '%s' requires argument, found %s instead.".format(opt, what)) - def loop(args: List[String]): Map[String, String] = { def residual(xs: List[String]) = { residualBuffer ++= xs ; Map[String, String]() } @@ -54,22 +48,32 @@ class CommandLine(val spec: Reference, val originalArgs: List[String]) { else None } + /** Assumes known options have all been ruled out already. */ + def isUnknown(opt: String) = + onlyKnownOptions && (opt startsWith "-") && { + errorFn("Option '%s' not recognized.".format(opt)) + true + } + args match { case Nil => Map() case Terminator :: xs => residual(xs) case x :: Nil => expand(x) foreach (exp => return loop(exp)) if (isBinaryOption(x) && enforceArity) - missingArg(x, "EOF") + errorFn("Option '%s' requires argument, found EOF instead.".format(x)) if (isUnaryOption(x)) mapForUnary(x) + else if (isUnknown(x)) Map() else residual(args) + case x1 :: x2 :: xs => expand(x1) foreach (exp => return loop(exp ++ args.tail)) if (x2 == Terminator) mapForUnary(x1) ++ residual(xs) else if (isUnaryOption(x1)) mapForUnary(x1) ++ loop(args.tail) else if (isBinaryOption(x1)) Map(x1 -> x2) ++ loop(xs) + else if (isUnknown(x1)) loop(args.tail) else residual(List(x1)) ++ loop(args.tail) } } @@ -85,23 +89,3 @@ class CommandLine(val spec: Reference, val originalArgs: List[String]) { override def toString() = argMap.toString + " " + residualArgs.toString } - -object CommandLine { - def apply(args: List[String], unary: List[String], binary: List[String]) = { - /** XXX Temporarily assembling a fake spec so we can continue to - * do ad-hoc parsing based on a list of unary and binary args. - * Should either clean this up or do it differently. - */ - object NoSpec extends Reference { - unary foreach (_ --? ) - binary foreach (_ --| ) - - protected def creator(args: List[String]) = error("No Spec") - def programInfo = Spec.Names("", "") - lazy val referenceSpec = this - } - - new CommandLine(NoSpec, args) - } -} - diff --git a/src/compiler/scala/tools/cmd/Demo.scala b/src/compiler/scala/tools/cmd/Demo.scala index 7014e6b4d7..22cf50bd58 100644 --- a/src/compiler/scala/tools/cmd/Demo.scala +++ b/src/compiler/scala/tools/cmd/Demo.scala @@ -10,7 +10,7 @@ package cmd * First take advantage of the meta-options: * * // this command creates an executable runner script "demo" - * % scala scala.tools.cmd.Demo --generate-runner demo + * % scala scala.tools.cmd.Demo --self-update demo * * // this one creates and sources a completion file - note backticks * % `./demo --bash` @@ -19,13 +19,13 @@ package cmd * % ./demo --<tab> * --action --defint --int * --bash --defstr --str - * --defenv --generate-runner --unary + * --defenv --self-update --unary * * The normal option configuration is plausibly self-explanatory. */ trait DemoSpec extends Spec with Meta.StdOpts with Interpolation { lazy val referenceSpec = DemoSpec - lazy val programInfo = Spec.Names("demo", "scala.tools.cmd.Demo") + lazy val programInfo = Spec.Info("demo", "Usage: demo [<options>]", "scala.tools.cmd.Demo") help("""Usage: demo [<options>]""") heading("Unary options:") @@ -48,7 +48,6 @@ object DemoSpec extends DemoSpec with Property { type ThisCommandLine = SpecCommandLine def creator(args: List[String]) = new SpecCommandLine(args) { - override def onlyKnownOptions = true override def errorFn(msg: String) = { println("Error: " + msg) ; System.exit(0) } } } diff --git a/src/compiler/scala/tools/cmd/Instance.scala b/src/compiler/scala/tools/cmd/Instance.scala index 4d319b98cc..3c0dbbaa1f 100644 --- a/src/compiler/scala/tools/cmd/Instance.scala +++ b/src/compiler/scala/tools/cmd/Instance.scala @@ -16,7 +16,8 @@ trait Instance extends Spec { protected def help(str: => String): Unit = () def isSet(s: String) = parsed isSet toOpt(s) - def originalArgs = parsed.originalArgs + def originalArgs = parsed.originalArgs // the full original list + def residualArgs = parsed.residualArgs // only args which were not options or args to options type OptionMagic = Opt.Instance protected implicit def optionMagicAdditions(name: String) = new Opt.Instance(programInfo, parsed, name) diff --git a/src/compiler/scala/tools/cmd/Interpolation.scala b/src/compiler/scala/tools/cmd/Interpolation.scala index 6b86a35bb9..a326d48f64 100644 --- a/src/compiler/scala/tools/cmd/Interpolation.scala +++ b/src/compiler/scala/tools/cmd/Interpolation.scala @@ -52,8 +52,6 @@ object Interpolation { |#!/bin/sh |# | - |scala @@MAINCLASS@@ $* - | - """.stripMargin + |scala @@MAINCLASS@@ "$@" + |""".stripMargin.trim + "\n" } - diff --git a/src/compiler/scala/tools/cmd/Meta.scala b/src/compiler/scala/tools/cmd/Meta.scala index 5a09766b13..8609db3d50 100644 --- a/src/compiler/scala/tools/cmd/Meta.scala +++ b/src/compiler/scala/tools/cmd/Meta.scala @@ -22,11 +22,11 @@ object Meta { trait StdOpts { self: Spec with Interpolation => - Bash.name --> runAndExit(Bash.action()) - val runnerFileName = Runner.name --| ; + Bash.name --> runAndExit(Bash.action()) + val selfUpdateName = SelfUpdate.name --| ; - if (runnerFileName.isDefined) - runAndExit(Runner.action()) + if (selfUpdateName.isDefined) + runAndExit(SelfUpdate.action()) /** I think we're as close as we can get to bundling completion with * the program given the constraints imposed by bash. This outputs @@ -47,12 +47,17 @@ object Meta { } } - /** A very basic runner script. + /** Generates a very basic runner script. It's called SelfUpdate + * because once it exists you can do something like + * + * tools/scmp --self-update tools/scmp + * + * and it will overwrite itself with the current version. */ - object Runner extends Opt { - val name = "generate-runner" + object SelfUpdate extends Opt { + val name = "self-update" val action = () => { - val file = File(runnerFileName.get) + val file = File(selfUpdateName.get) file writeAll interpolate(runnerTemplate) file setExecutable true () diff --git a/src/compiler/scala/tools/cmd/Opt.scala b/src/compiler/scala/tools/cmd/Opt.scala index 9e3c324deb..beea590492 100644 --- a/src/compiler/scala/tools/cmd/Opt.scala +++ b/src/compiler/scala/tools/cmd/Opt.scala @@ -7,7 +7,7 @@ package scala.tools package cmd import nsc.Properties.envOrElse -import Spec.Names +import Spec.Info /** Machinery for what amounts to a command line specification DSL. * It is designed so the same specification trait can be used for @@ -25,7 +25,7 @@ object Opt { trait Implicit { def name: String - def programInfo: Names + def programInfo: Info protected def opt = toOpt(name) def --? : Boolean // --opt is set @@ -47,7 +47,7 @@ object Opt { def /(descr: String): String // --opt has help description 'descr' } - class Reference(val programInfo: Names, val options: Reference.Accumulators, val name: String) extends Implicit { + class Reference(val programInfo: Info, val options: Reference.Accumulators, val name: String) extends Implicit { import options._ def --? = { addUnary(opt) ; false } @@ -63,7 +63,7 @@ object Opt { def /(descr: String) = returning(name)(_ => addHelp(() => helpFormatStr.format(opt, descr))) } - class Instance(val programInfo: Names, val parsed: CommandLine, val name: String) extends Implicit with Error { + class Instance(val programInfo: Info, val parsed: CommandLine, val name: String) extends Implicit with Error { def --? = parsed isSet opt def --> (body: => Unit) = if (parsed isSet opt) body def --| = parsed get opt diff --git a/src/compiler/scala/tools/cmd/Reference.scala b/src/compiler/scala/tools/cmd/Reference.scala index 695868191b..3f3712766b 100644 --- a/src/compiler/scala/tools/cmd/Reference.scala +++ b/src/compiler/scala/tools/cmd/Reference.scala @@ -32,7 +32,8 @@ trait Reference extends Spec { protected def help(str: => String) = addHelp(() => str) - type ThisCommandLine <: SpecCommandLine + type ThisCommandLine <: CommandLine + class SpecCommandLine(args: List[String]) extends CommandLine(Reference.this, args) { } protected def creator(args: List[String]): ThisCommandLine final def apply(args: String*): ThisCommandLine = creator(propertyArgs ++ args flatMap expandArg) diff --git a/src/compiler/scala/tools/cmd/Spec.scala b/src/compiler/scala/tools/cmd/Spec.scala index c8283165d9..794bb3303f 100644 --- a/src/compiler/scala/tools/cmd/Spec.scala +++ b/src/compiler/scala/tools/cmd/Spec.scala @@ -12,7 +12,7 @@ package cmd */ trait Spec { def referenceSpec: Reference - def programInfo: Spec.Names + def programInfo: Spec.Info protected def help(str: => String): Unit protected def heading(str: => String): Unit = help("\n " + str) @@ -22,7 +22,14 @@ trait Spec { } object Spec { - case class Names(runner: String, mainClass: String) { } + class Info( + val runner: String, + val usage: String, + val mainClass: String + ) + object Info { + def apply(runner: String, help: String, mainClass: String): Info = new Info(runner, help, mainClass) + } class Accumulator[T: FromString]() { private var _buf: List[T] = Nil diff --git a/src/compiler/scala/tools/cmd/program/Scmp.scala b/src/compiler/scala/tools/cmd/program/Scmp.scala new file mode 100644 index 0000000000..ff4fa11eaf --- /dev/null +++ b/src/compiler/scala/tools/cmd/program/Scmp.scala @@ -0,0 +1,59 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd +package program + +import nsc.io._ + +object Scmp { + private val scmpUsage = """ + |Usage: scmp [options] <cmd line> + |Example: scmp --p1 '-no-specialization -Ydebug' scalac src/library/scala/Function1.scala + | + |Note: the command line must start with a path to scalac. + |""".stripMargin + private val scmpOptions = List( + "p1" -> "options for the first run only", + "p2" -> "options for the second run only" + ) + private val scmpInfo = Simple.scalaProgramInfo("scmp", scmpUsage) + lazy val ScmpSpec = Simple(scmpInfo, Nil, scmpOptions, x => returning(x)(_.onlyKnownOptions = false)) + + def main(args0: Array[String]): Unit = { + if (args0.isEmpty) + return println(scmpUsage) + + val runner = ScmpSpec instance args0 + import runner._ + + val p1args = parsed.getOrElse("--p1", "") + val p2args = parsed.getOrElse("--p2", "") + + if (p1args.isEmpty && p2args.isEmpty) + return println("At least one of --p1 and --p2 must be given.") + if (residualArgs.isEmpty) + return println("There is no command to run.") + + def createCmd(extras: String) = + fromArgs(residualArgs.patch(1, toArgs(extras), 0)) + + def runCmd(cmd: String) = { + val output = Process(cmd, redirect = true).slurp() + + returning(File.makeTemp())(_ writeAll output) + } + + val cmds = List(p1args, p2args) map createCmd + println(cmds.mkString("Running command lines:\n ", "\n ", "")) + + val files = cmds map runCmd map (_.path) + val diff = Process("diff %s %s".format(files: _*)).slurp() + + if (diff.isEmpty) println("No differences.") + else println(diff) + } +} diff --git a/src/compiler/scala/tools/cmd/program/Simple.scala b/src/compiler/scala/tools/cmd/program/Simple.scala new file mode 100644 index 0000000000..641be31c9e --- /dev/null +++ b/src/compiler/scala/tools/cmd/program/Simple.scala @@ -0,0 +1,81 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd +package program + +import Spec.Info + +/** A boilerplate reducer for commands with simple requirements. For examples, + * see Scmp and Tokens in this package. + */ +object Simple { + type CommandLineTransform = SimpleCommandLine => SimpleCommandLine + + abstract class SimpleSpec(val programInfo: Info) extends Spec with Meta.StdOpts with Interpolation + + trait SimpleInstance extends SimpleSpec with Instance { + val parsed: CommandLine + } + + class SimpleReference( + programInfo: Info, + unary: List[(String, String)] = Nil, + binary: List[(String, String)] = Nil, + postCreation: CommandLineTransform = null + ) extends SimpleSpec(programInfo) with Reference { + + spec => + + if (programInfo.usage != "") help(programInfo.usage) + unary foreach { case (option, help) => option / help --? } + binary foreach { case (option, help) => option / help --| } + + type ThisCommandLine = SimpleCommandLine + + def creator(args: List[String]) = new SimpleCommandLine(spec, args) + def instance(args: Array[String]): SimpleInstance = instance(args.toList) + def instance(args: List[String]): SimpleInstance = + new { + val parsed = spec(args: _*) + } with SimpleSpec(programInfo) with SimpleInstance { + lazy val referenceSpec = spec + } + + lazy val referenceSpec = spec + } + + def apply(info: Info, unary: List[(String, String)], binary: List[(String, String)], postCreation: CommandLineTransform): SimpleReference = { + new SimpleReference(info, unary, binary, postCreation) { + override def creator(args: List[String]) = { + val obj = super.creator(args) + if (postCreation == null) obj + else postCreation(obj) + } + } + } + + def scalaProgramInfo(name: String, help: String) = + Spec.Info(name, help, "scala.tools.cmd.program." + name.capitalize) + + /** You can't override a def with a var unless a setter exists. We cleverly + * sidestep this by mixing in a trait with dummy setters which will be + * inaccessible due to the overriding var. + */ + trait Ticket2338WontFixWorkaround { + def enforceArity_=(x: Boolean): Unit = error("unreachable") + def onlyKnownOptions_=(x: Boolean): Unit = error("unreachable") + } + + /** Configurability simplicity achieved by turning defs into vars and letting + * the spec creator apply a transformation. This way there's no need to create + * custom subclasses of CommandLine. + */ + class SimpleCommandLine(spec: Reference, args: List[String]) extends CommandLine(spec, args) with Ticket2338WontFixWorkaround { + override var enforceArity: Boolean = true + override var onlyKnownOptions: Boolean = true + } +} diff --git a/src/compiler/scala/tools/cmd/program/Tokens.scala b/src/compiler/scala/tools/cmd/program/Tokens.scala new file mode 100644 index 0000000000..36786aa2b7 --- /dev/null +++ b/src/compiler/scala/tools/cmd/program/Tokens.scala @@ -0,0 +1,100 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd +package program + +import nsc._ +import util.Chars.char2uescape +import io._ +import ast.parser.Tokens._ + +/** Given paths on the command line, tokenizes any scala files found + * and prints one token per line. + */ +object Tokens { + private val tokensUsage = "Usage: tokens [options] <path1 path2 ...>\n\nOptions:" + private val tokensUnary = List( + "verbose" -> "be more verbose", + "freq" -> "combine token lists and sort by frequency", + "stats" -> "output some stats" + ) + private val tokensBinary = List( + "sliding" -> "print tokens in groups of given size" + ) + private val tokensInfo = Simple.scalaProgramInfo("tokens", tokensUsage) + private lazy val TokensSpec = Simple(tokensInfo, tokensUnary, tokensBinary, null) + + def sanitize(x: Any): String = sanitize(x.toString) + def sanitize(str: String): String = str flatMap (x => if (x.isControl) char2uescape(x) else x.toString) + + def main(args0: Array[String]): Unit = { + if (args0.isEmpty) + return println(TokensSpec.helpMsg) + + val runner = TokensSpec instance args0 + import runner._ + + val files = (residualArgs flatMap walk).distinct + if (parsed isSet "--verbose") + println("Tokenizing: " + (files map (_.name) mkString " ")) + + if (parsed isSet "--stats") + println("Stats not yet implemented.") + + def raw = files flatMap fromScalaSource + def tokens: List[Any] = + if (parsed isSet "--sliding") raw sliding parsed("--sliding").toInt map (_ map sanitize mkString " ") toList + else raw + + def output = + if (parsed isSet "--freq") + (tokens groupBy (x => x) mapValues (_.length)).toList sortBy (-_._2) map (x => x._2 + " " + x._1) + else + tokens + + output foreach println + } + + def fromPaths(paths: String*): List[Any] = + (paths.toList flatMap walk).distinct flatMap fromScalaSource + + /** Given a path, returns all .scala files underneath it. + */ + private def walk(arg: String): List[File] = { + def traverse = Path(arg) ifDirectory (_.deepList()) getOrElse Iterator(File(arg)) + + Path onlyFiles traverse filter (_ hasExtension "scala") toList + } + + /** Tokenizes a single scala file. + */ + def fromScalaSource(file: Path): List[Any] = fromScalaSource(file.path) + def fromScalaSource(file: String): List[Any] = { + val global = new Global(new Settings()) + import global._ + import syntaxAnalyzer.{ UnitScanner, token2string } + + val in = new UnitScanner(new CompilationUnit(getSourceFile(file))) + in.init() + + Iterator continually { + val token = in.token match { + case IDENTIFIER | BACKQUOTED_IDENT => in.name + case CHARLIT | INTLIT | LONGLIT => in.intVal + case DOUBLELIT | FLOATLIT => in.floatVal + case STRINGLIT => "\"" + in.strVal + "\"" + case SEMI | NEWLINE => ";" + case NEWLINES => ";;" + case COMMA => "," + case EOF => null + case x => token2string(x) + } + in.nextToken() + token + } takeWhile (_ != null) toList + } +} diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 16bff5d399..0ec36cf9ab 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -137,7 +137,7 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable def error(msg: String) = reporter.error(NoPosition, msg) def warning(msg: String) = - if (settings.Ywarnfatal.value) reporter.error(NoPosition, msg) + if (settings.Xwarnfatal.value) reporter.error(NoPosition, msg) else reporter.warning(NoPosition, msg) def inform(msg: String) = reporter.info(NoPosition, msg, true) def inform[T](msg: String, value: T): T = { inform(msg+value); value } @@ -714,7 +714,7 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable /** Compile list of source files */ def compileSources(_sources: List[SourceFile]) { val depSources = dependencyAnalysis.filter(_sources.distinct) // bug #1268, scalac confused by duplicated filenames - val sources = scalaObjectFirst(depSources) + val sources = coreClassesFirst(depSources) if (reporter.hasErrors) return // there is a problem already, e.g. a // plugin was passed a bad option @@ -871,14 +871,43 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable if (!pclazz.isRoot) resetPackageClass(pclazz.owner) } - private def scalaObjectFirst(files: List[SourceFile]) = { + /** + * Re-orders the source files to + * 1. ScalaObject + * 2. LowPriorityImplicits / StandardEmbeddings (i.e. parents of Predef) + * 3. the rest + * + * 1 is to avoid cyclic reference errors. + * 2 is due to the following. When completing "Predef" (*), typedIdent is called + * for its parents (e.g. "LowPriorityImplicits"). typedIdent checks wethter + * the symbol reallyExists, which tests if the type of the symbol after running + * its completer is != NoType. + * If the "namer" phase has not yet run for "LowPriorityImplicits", the symbol + * has a SourcefileLoader as type. Calling "doComplete" on it does nothing at + * all, because the source file is part of the files to be compiled anyway. + * So the "reallyExists" test will return "false". + * Only after the namer, the symbol has a lazy type which actually computes + * the info, and "reallyExists" behaves as expected. + * So we need to make sure that the "namer" phase is run on predef's parents + * before running it on predef. + * + * (*) Predef is completed early when calling "mkAttributedRef" during the + * addition of "import Predef._" to sourcefiles. So this situation can't + * happen for user classes. + * + */ + private def coreClassesFirst(files: List[SourceFile]) = { def inScalaFolder(f: SourceFile) = f.file.container.name == "scala" + var scalaObject: Option[SourceFile] = None val res = new ListBuffer[SourceFile] for (file <- files) file.file.name match { - case "ScalaObject.scala" if inScalaFolder(file) => file +=: res + case "ScalaObject.scala" if inScalaFolder(file) => scalaObject = Some(file) + case "LowPriorityImplicits.scala" if inScalaFolder(file) => file +=: res + case "StandardEmbeddings.scala" if inScalaFolder(file) => file +=: res case _ => res += file } + for (so <- scalaObject) so +=: res res.toList } } // class Run diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala index 0cc0f65640..556a3107cd 100644 --- a/src/compiler/scala/tools/nsc/Interpreter.scala +++ b/src/compiler/scala/tools/nsc/Interpreter.scala @@ -27,7 +27,7 @@ import scala.util.control.Exception.{ Catcher, catching, ultimately, unwrapping import io.{ PlainFile, VirtualDirectory } import reporters.{ ConsoleReporter, Reporter } import symtab.{ Flags, Names } -import util.{ SourceFile, BatchSourceFile, ClassPath } +import util.{ SourceFile, BatchSourceFile, ClassPath, Chars } import scala.reflect.NameTransformer import scala.tools.nsc.{ InterpreterResults => IR } import interpreter._ @@ -74,6 +74,8 @@ import Interpreter._ * @author Lex Spoon */ class Interpreter(val settings: Settings, out: PrintWriter) { + repl => + /** construct an interpreter that reports to Console */ def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) def this() = this(new Settings()) @@ -236,6 +238,12 @@ class Interpreter(val settings: Settings, out: PrintWriter) { private val boundNameMap = new HashMap[Name, Request]() private def allHandlers = prevRequests.toList flatMap (_.handlers) + def printAllTypeOf = { + prevRequests foreach { req => + req.typeOf foreach { case (k, v) => Console.println(k + " => " + v) } + } + } + /** Most recent tree handled which wasn't wholly synthetic. */ private def mostRecentlyHandledTree: Option[Tree] = { for { @@ -487,42 +495,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } } - /** For :power - create trees and type aliases from code snippets. */ - def mkContext(code: String = "") = compiler.analyzer.rootContext(mkUnit(code)) - def mkAlias(name: String, what: String) = interpret("type %s = %s".format(name, what)) - def mkSourceFile(code: String) = new BatchSourceFile("<console>", code) - def mkUnit(code: String) = new CompilationUnit(mkSourceFile(code)) - - def mkTree(code: String): Tree = mkTrees(code).headOption getOrElse EmptyTree - def mkTrees(code: String): List[Tree] = parse(code) getOrElse Nil - def mkTypedTrees(code: String*): List[compiler.Tree] = { - class TyperRun extends compiler.Run { - override def stopPhase(name: String) = name == "superaccessors" - } - - reporter.reset - val run = new TyperRun - run compileSources (code.toList.zipWithIndex map { - case (s, i) => new BatchSourceFile("<console %d>".format(i), s) - }) - run.units.toList map (_.body) - } - def mkTypedTree(code: String) = mkTypedTrees(code).head - - def mkType(id: String): compiler.Type = { - // if it's a recognized identifier, the type of that; otherwise treat the - // String like it is itself a type (e.g. scala.collection.Map) . - val typeName = typeForIdent(id) getOrElse id - - try definitions.getClass(newTermName(typeName)).tpe - catch { case _: Throwable => NoType } - } - - private[nsc] val powerMkImports = List( - "mkContext", "mkTree", "mkTrees", "mkAlias", "mkSourceFile", "mkUnit", "mkType", "mkTypedTree", "mkTypedTrees" - // , "treeWrapper" - ) - /** Compile an nsc SourceFile. Returns true if there are * no compilation errors, or false otherwise. */ @@ -796,16 +768,32 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } private class ImportHandler(imp: Import) extends MemberHandler(imp) { + lazy val Import(expr, selectors) = imp + def targetType = stringToCompilerType(expr.toString) match { + case NoType => None + case x => Some(x) + } + + private def selectorWild = selectors filter (_.name == USCOREkw) // wildcard imports, e.g. import foo._ + private def selectorMasked = selectors filter (_.rename == USCOREkw) // masking imports, e.g. import foo.{ bar => _ } + private def selectorNames = selectors map (_.name) + private def selectorRenames = selectors map (_.rename) filterNot (_ == null) + /** Whether this import includes a wildcard import */ - val importsWildcard = imp.selectors map (_.name) contains USCOREkw + val importsWildcard = selectorWild.nonEmpty + + /** Complete list of names imported by a wildcard */ + def wildcardImportedNames: List[Name] = ( + for (tpe <- targetType ; if importsWildcard) yield + tpe.nonPrivateMembers filter (x => x.isMethod && x.isPublic) map (_.name) distinct + ).toList.flatten /** The individual names imported by this statement */ - val importedNames: List[Name] = ( - imp.selectors - . map (x => x.rename) - . filter (x => x != null && x != USCOREkw) - . flatMap (x => List(x.toTypeName, x.toTermName)) - ) + /** XXX come back to this and see what can be done with wildcards now that + * we know how to enumerate the identifiers. + */ + val importedNames: List[Name] = + selectorRenames filterNot (_ == USCOREkw) flatMap (x => List(x.toTypeName, x.toTermName)) override def resultExtractionCode(req: Request, code: PrintWriter) = code println codegenln(imp.toString) @@ -830,9 +818,10 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** def and val names */ def defNames = partialFlatMap(handlers) { case x: DefHandler => x.boundNames } - def valAndVarNames = partialFlatMap(handlers) { + def valueNames = partialFlatMap(handlers) { case x: AssignHandler => List(x.helperName) case x: ValHandler => boundNames + case x: ModuleHandler => List(x.name) } /** Code to import bound names from previous lines - accessPath is code to @@ -940,7 +929,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) { !reporter.hasErrors } - def atNextPhase[T](op: => T): T = compiler.atPhase(objRun.typerPhase.next)(op) /** The outermost wrapper object */ @@ -972,7 +960,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } } - getTypes(valAndVarNames, nme.getterToLocal(_)) ++ getTypes(defNames, identity) + getTypes(valueNames, nme.getterToLocal(_)) ++ getTypes(defNames, identity) } /** load and run the code using reflection */ @@ -999,43 +987,64 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } } - /** These methods are exposed so REPL commands can access them. - * The command infrastructure is in InterpreterLoop. + /** A container class for methods to be injected into the repl + * in power mode. */ - def dumpState(xs: List[String]): String = """ - | Names used: %s - | - | Identifiers: %s - | - | synthvars: %d - """.stripMargin.format( - allUsedNames mkString " ", - unqualifiedIds mkString " ", - allBoundNames filter isSynthVarName size - ) - - // def dumpTrees(xs: List[String]): String = { - // val treestrs = (xs map requestForIdent).flatten flatMap (_.trees) - // - // if (treestrs.isEmpty) "No trees found." - // else treestrs.map(t => t.toString + " (" + t.getClass.getSimpleName + ")\n").mkString - // } + object power { + lazy val compiler: repl.compiler.type = repl.compiler + import compiler.{ phaseNames, atPhase, currentRun } + + def mkContext(code: String = "") = compiler.analyzer.rootContext(mkUnit(code)) + def mkAlias(name: String, what: String) = interpret("type %s = %s".format(name, what)) + def mkSourceFile(code: String) = new BatchSourceFile("<console>", code) + def mkUnit(code: String) = new CompilationUnit(mkSourceFile(code)) + + def mkTree(code: String): Tree = mkTrees(code).headOption getOrElse EmptyTree + def mkTrees(code: String): List[Tree] = parse(code) getOrElse Nil + def mkTypedTrees(code: String*): List[compiler.Tree] = { + class TyperRun extends compiler.Run { + override def stopPhase(name: String) = name == "superaccessors" + } - def powerUser(): String = { - beQuietDuring { - this.bind("repl", "scala.tools.nsc.Interpreter", this) - this.bind("global", "scala.tools.nsc.Global", compiler) - interpret("import repl.{ %s, eval }".format(powerMkImports mkString ", "), false) + reporter.reset + val run = new TyperRun + run compileSources (code.toList.zipWithIndex map { + case (s, i) => new BatchSourceFile("<console %d>".format(i), s) + }) + run.units.toList map (_.body) } + def mkTypedTree(code: String) = mkTypedTrees(code).head + def mkType(id: String): compiler.Type = stringToCompilerType(id) + + def dump(): String = ( + ("Names used: " :: allUsedNames) ++ + ("\nIdentifiers: " :: unqualifiedIds) + ) mkString " " + + lazy val allPhases: List[Phase] = phaseNames map (currentRun phaseNamed _) + def atAllPhases[T](op: => T): List[(String, T)] = allPhases map (ph => (ph.name, atPhase(ph)(op))) + def showAtAllPhases(op: => Any): Unit = + atAllPhases(op.toString) foreach { case (ph, op) => Console.println("%15s -> %s".format(ph, op take 240)) } + } - """** Power User mode enabled - BEEP BOOP ** - |** New vals! Try repl, global ** - |** New cmds! :help to discover them ** - |** New defs! Give these a whirl: ** - |** mkAlias("Fn", "(String, Int) => Int") ** - |** mkTree("def f(x: Int, y: Int) = x+y") **""".stripMargin + def unleash(): Unit = beQuietDuring { + interpret("import scala.tools.nsc._") + repl.bind("repl", "scala.tools.nsc.Interpreter", this) + interpret("val global: repl.compiler.type = repl.compiler") + interpret("val power: repl.power.type = repl.power") + // interpret("val replVars = repl.replVars") } + /** Artificial object demonstrating completion */ + // lazy val replVars = CompletionAware( + // Map[String, CompletionAware]( + // "ids" -> CompletionAware(() => unqualifiedIds, completionAware _), + // "synthVars" -> CompletionAware(() => allBoundNames filter isSynthVarName map (_.toString)), + // "types" -> CompletionAware(() => allSeenTypes map (_.toString)), + // "implicits" -> CompletionAware(() => allImplicits map (_.toString)) + // ) + // ) + /** Returns the name of the most recent interpreter result. * Mostly this exists so you can conveniently invoke methods on * the previous result. @@ -1052,11 +1061,21 @@ class Interpreter(val settings: Settings, out: PrintWriter) { private def requestForName(name: Name): Option[Request] = prevRequests.reverse find (_.boundNames contains name) - private def requestForIdent(line: String): Option[Request] = - requestForName(newTermName(line)) + private def requestForIdent(line: String): Option[Request] = requestForName(newTermName(line)) + + def stringToCompilerType(id: String): compiler.Type = { + // if it's a recognized identifier, the type of that; otherwise treat the + // String like a value (e.g. scala.collection.Map) . + def findType = typeForIdent(id) match { + case Some(x) => definitions.getClass(newTermName(x)).tpe + case _ => definitions.getModule(newTermName(id)).tpe + } + + try findType catch { case _: MissingRequirementError => NoType } + } def typeForIdent(id: String): Option[String] = - requestForIdent(id) map (_ typeOf newTermName(id)) + requestForIdent(id) flatMap (x => x.typeOf get newTermName(id)) def methodsOf(name: String) = evalExpr[List[String]](methodsCode(name)) map (x => NameTransformer.decode(getOriginalName(x))) @@ -1160,6 +1179,22 @@ class Interpreter(val settings: Settings, out: PrintWriter) { case x: ImportHandler => x.importedNames } filterNot isSynthVarName + /** Types which have been wildcard imported, such as: + * val x = "abc" ; import x._ // type java.lang.String + * import java.lang.String._ // object java.lang.String + * + * Used by tab completion. + * + * XXX right now this gets import x._ and import java.lang.String._, + * but doesn't figure out import String._. There's a lot of ad hoc + * scope twiddling which should be swept away in favor of digging + * into the compiler scopes. + */ + def wildcardImportedTypes(): List[Type] = { + val xs = allHandlers collect { case x: ImportHandler if x.importsWildcard => x.targetType } + xs.flatten.reverse.distinct + } + /** Another entry point for tab-completion, ids in scope */ def unqualifiedIds() = (unqualifiedIdNames() map (_.toString)).distinct.sorted @@ -1169,16 +1204,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** Parse the ScalaSig to find type aliases */ def aliasForType(path: String) = ByteCode.aliasForType(path) - /** Artificial object demonstrating completion */ - def replVarsObject() = CompletionAware( - Map[String, CompletionAware]( - "ids" -> CompletionAware(() => unqualifiedIds, completionAware _), - "synthVars" -> CompletionAware(() => allBoundNames filter isSynthVarName map (_.toString)), - "types" -> CompletionAware(() => allSeenTypes map (_.toString)), - "implicits" -> CompletionAware(() => allImplicits map (_.toString)) - ) - ) - // Coming soon // implicit def string2liftedcode(s: String): LiftedCode = new LiftedCode(s) // case class LiftedCode(code: String) { @@ -1191,6 +1216,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) { // debugging def isReplDebug = settings.Yrepldebug.value + def isCompletionDebug = settings.Ycompletion.value def DBG(s: String) = if (isReplDebug) out println s else () } @@ -1275,21 +1301,10 @@ object Interpreter { * This requires replacing all special characters by escape * codes. It does not add the surrounding " marks. */ def string2code(str: String): String = { - /** Convert a character to a backslash-u escape */ - def char2uescape(c: Char): String = { - var rest = c.toInt - val buf = new StringBuilder - for (i <- 1 to 4) { - buf ++= (rest % 16).toHexString - rest = rest / 16 - } - "\\u" + buf.toString.reverse - } - val res = new StringBuilder for (c <- str) c match { case '"' | '\'' | '\\' => res += '\\' ; res += c - case _ if c.isControl => res ++= char2uescape(c) + case _ if c.isControl => res ++= Chars.char2uescape(c) case _ => res += c } res.toString diff --git a/src/compiler/scala/tools/nsc/InterpreterLoop.scala b/src/compiler/scala/tools/nsc/InterpreterLoop.scala index 9d568418f8..4e8a04de44 100644 --- a/src/compiler/scala/tools/nsc/InterpreterLoop.scala +++ b/src/compiler/scala/tools/nsc/InterpreterLoop.scala @@ -211,15 +211,12 @@ class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWrite } /** Power user commands */ - // XXX - why does a third argument like "interpreter dumpState(_)" throw an NPE - // while the version below works? var powerUserOn = false val powerCommands: List[Command] = { import CommandImplicits._ List( - VarArgs("dump", "displays a view of the interpreter's internal state", - (xs: List[String]) => interpreter dumpState xs), - OneArg("search", "search the classpath for classes matching regex", search) + OneArg("completions", "generate list of completions for a given String", completions), + NoArgs("dump", "displays a view of the interpreter's internal state", () => interpreter.power.dump()) // VarArgs("tree", "displays ASTs for specified identifiers", // (xs: List[String]) => interpreter dumpTrees xs) @@ -331,45 +328,26 @@ class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWrite else out.println("The path '" + f + "' doesn't seem to exist.") } - /** This isn't going to win any efficiency awards, but it's only - * available in power mode so I'm unconcerned for the moment. - */ - def search(arg: String) { - val MAX_RESULTS = 40 - if (in.completion.isEmpty) return println("No classpath data available") - val comp = in.completion.get - - import java.util.regex.PatternSyntaxException - import comp.pkgs.agent._ - import scala.collection.JavaConversions._ - - try { - val regex = arg.r - val matches = ( - for ((k, vs) <- dottedPaths) yield { - val pkgs = if (regex findFirstMatchIn k isDefined) List("package " + k) else Nil - val classes = vs filter (regex findFirstMatchIn _.visibleName isDefined) map (" class " + k + "." + _.visibleName) - - pkgs ::: classes - } - ).flatten + def completions(arg: String): Unit = { + val comp = in.completion getOrElse { return println("Completion unavailable.") } + val xs = comp completions arg - matches take MAX_RESULTS foreach println - } - catch { - case _: PatternSyntaxException => - return println("Invalid regular expression: you must use java.util.regex.Pattern syntax.") - } + injectAndName(xs) } def power() { - powerUserOn = true - out println interpreter.powerUser() - if (in.history.isDefined) - interpreter.quietBind("history", "scala.collection.immutable.List[String]", in.historyList) + val powerUserBanner = + """** Power User mode enabled - BEEP BOOP ** + |** scala.tools.nsc._ has been imported ** + |** New vals! Try repl, global, power ** + |** New cmds! :help to discover them ** + |** New defs! Type power.<tab> to reveal **""".stripMargin - if (in.completion.isDefined) - interpreter.quietBind("replHelper", "scala.tools.nsc.interpreter.CompletionAware", interpreter.replVarsObject()) + powerUserOn = true + interpreter.unleash() + injectOne("history", in.historyList) + in.completion foreach (x => injectOne("completion", x)) + out println powerUserBanner } def verbosity() = { @@ -496,6 +474,9 @@ class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWrite * to be recorded for replay, if any. */ def interpretStartingWith(code: String): Option[String] = { + // signal completion non-completion input has been received + in.completion foreach (_.resetVerbosity()) + def reallyInterpret = { interpreter.interpret(code) match { case IR.Error => None @@ -527,7 +508,7 @@ class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWrite interpretAsPastedTranscript(List(code)) None } - else if (Completion.looksLikeInvocation(code)) { + else if (Completion.looksLikeInvocation(code) && interpreter.mostRecentVar != "") { interpretStartingWith(interpreter.mostRecentVar + code) } else { diff --git a/src/compiler/scala/tools/nsc/ast/TreeInfo.scala b/src/compiler/scala/tools/nsc/ast/TreeInfo.scala index 2e543a0960..430967298c 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeInfo.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeInfo.scala @@ -102,17 +102,20 @@ abstract class TreeInfo { case _ => false } - def isVariableOrGetter(tree: Tree) = tree match { - case Ident(_) => - tree.symbol.isVariable - case Select(qual, _) => - tree.symbol.isVariable || - (mayBeVarGetter(tree.symbol) && - tree.symbol.owner.info.member(nme.getterToSetter(tree.symbol.name)) != NoSymbol) - case Apply(Select(qual, nme.apply), _) => - qual.tpe.member(nme.update) != NoSymbol - case _ => - false + def isVariableOrGetter(tree: Tree) = { + def sym = tree.symbol + def isVar = sym.isVariable + def isGetter = mayBeVarGetter(sym) && sym.owner.info.member(nme.getterToSetter(sym.name)) != NoSymbol + + tree match { + case Ident(_) => isVar + case Select(_, _) => isVar || isGetter + case _ => + methPart(tree) match { + case Select(qual, nme.apply) => qual.tpe.member(nme.update) != NoSymbol + case _ => false + } + } } /** Is tree a self constructor call? @@ -295,10 +298,10 @@ abstract class TreeInfo { /** The method part of an application node */ def methPart(tree: Tree): Tree = tree match { - case Apply(fn, _) => methPart(fn) - case TypeApply(fn, _) => methPart(fn) + case Apply(fn, _) => methPart(fn) + case TypeApply(fn, _) => methPart(fn) case AppliedTypeTree(fn, _) => methPart(fn) - case _ => tree + case _ => tree } def firstArgument(tree: Tree): Tree = tree match { @@ -325,18 +328,19 @@ abstract class TreeInfo { false } - /** Compilation unit is the predef object + /** Compilation unit is class or object 'name' in package 'scala' */ def isUnitInScala(tree: Tree, name: Name) = tree match { - case PackageDef(Ident(nme.scala_), defs) => isObject(defs, name) + case PackageDef(Ident(nme.scala_), defs) => isImplDef(defs, name) case _ => false } - private def isObject(trees: List[Tree], name: Name): Boolean = trees match { - case Import(_, _) :: xs => isObject(xs, name) - case DocDef(_, tree1) :: Nil => isObject(List(tree1), name) - case Annotated(_, tree1) :: Nil => isObject(List(tree1), name) + private def isImplDef(trees: List[Tree], name: Name): Boolean = trees match { + case Import(_, _) :: xs => isImplDef(xs, name) + case DocDef(_, tree1) :: Nil => isImplDef(List(tree1), name) + case Annotated(_, tree1) :: Nil => isImplDef(List(tree1), name) case ModuleDef(_, `name`, _) :: Nil => true + case ClassDef(_, `name`, _, _) :: Nil => true case _ => false } diff --git a/src/compiler/scala/tools/nsc/ast/Trees.scala b/src/compiler/scala/tools/nsc/ast/Trees.scala index 3640b6825b..b40f286680 100644 --- a/src/compiler/scala/tools/nsc/ast/Trees.scala +++ b/src/compiler/scala/tools/nsc/ast/Trees.scala @@ -221,9 +221,8 @@ trait Trees extends reflect.generic.Trees { self: SymbolTable => var vparamss1 = vparamss map (vps => vps.map { vd => atPos(vd.pos.focus) { - val pa = if (vd.hasFlag(PRIVATE | LOCAL)) 0L else PARAMACCESSOR ValDef( - Modifiers(vd.mods.flags & (IMPLICIT | DEFAULTPARAM | BYNAMEPARAM) | PARAM | pa) withAnnotations vd.mods.annotations, + Modifiers(vd.mods.flags & (IMPLICIT | DEFAULTPARAM | BYNAMEPARAM) | PARAM | PARAMACCESSOR) withAnnotations vd.mods.annotations, vd.name, vd.tpt.duplicate, vd.rhs.duplicate) }}) val (edefs, rest) = body span treeInfo.isEarlyDef @@ -260,7 +259,7 @@ trait Trees extends reflect.generic.Trees { self: SymbolTable => } // println("typed template, gvdefs = "+gvdefs+", parents = "+parents+", constrs = "+constrs) constrs foreach (ensureNonOverlapping(_, parents ::: gvdefs)) - // remove defaults + // vparamss2 are used as field definitions for the class. remove defaults val vparamss2 = vparamss map (vps => vps map { vd => treeCopy.ValDef(vd, vd.mods &~ DEFAULTPARAM, vd.name, vd.tpt, EmptyTree) }) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 1cfee481bc..497cfc398b 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -2630,7 +2630,8 @@ self => } else if (isStatSep) { in.nextToken() } else { - syntaxErrorOrIncomplete("illegal start of statement", true) + val addendum = if (isModifier) " (no modifiers allowed here)" else "" + syntaxErrorOrIncomplete("illegal start of statement" + addendum, true) } } stats.toList diff --git a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala index 87f2641f4f..fb97587ec4 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala @@ -19,6 +19,8 @@ abstract class SyntaxAnalyzer extends SubComponent with Parsers with MarkupParse class ParserPhase(prev: scala.tools.nsc.Phase) extends StdPhase(prev) { override val checkable = false + override val keepsTypeParams = false + def apply(unit: global.CompilationUnit) { global.informProgress("parsing " + unit) unit.body = diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index 5318c76311..dc193b03db 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -851,7 +851,7 @@ abstract class GenJVM extends SubComponent { def addForwarders(jclass: JClass, module: Symbol) { addForwarders(jclass, module, _ => true) } def addForwarders(jclass: JClass, module: Symbol, cond: (Symbol) => Boolean) { def conflictsIn(cls: Symbol, name: Name) = - cls.info.nonPrivateMembers.exists(_.name == name) + cls.info.members exists (_.name == name) /** List of parents shared by both class and module, so we don't add forwarders * for methods defined there - bug #1804 */ @@ -874,7 +874,7 @@ abstract class GenJVM extends SubComponent { atPhase(currentRun.picklerPhase) ( m.owner != definitions.ObjectClass && m.isMethod - && !m.hasFlag(Flags.CASE | Flags.PROTECTED | Flags.DEFERRED | Flags.SPECIALIZED) + && !m.hasFlag(Flags.CASE | Flags.PRIVATE | Flags.PROTECTED | Flags.DEFERRED | Flags.SPECIALIZED) && !m.isConstructor && !m.isStaticMember && !(m.owner == definitions.AnyClass) diff --git a/src/compiler/scala/tools/nsc/backend/msil/GenMSIL.scala b/src/compiler/scala/tools/nsc/backend/msil/GenMSIL.scala index 285e09295d..554dcd4e6d 100644 --- a/src/compiler/scala/tools/nsc/backend/msil/GenMSIL.scala +++ b/src/compiler/scala/tools/nsc/backend/msil/GenMSIL.scala @@ -1053,7 +1053,7 @@ abstract class GenMSIL extends SubComponent { } var doEmit = true - types.get(msym.owner) match { + getTypeOpt(msym.owner) match { case Some(typ) if (typ.IsEnum) => { def negBool = { mcode.Emit(OpCodes.Ldc_I4_0) @@ -1577,9 +1577,9 @@ abstract class GenMSIL extends SubComponent { mf = mf | FieldAttributes.Static else { mf = mf | MethodAttributes.Virtual - if (sym.isFinal && !types(sym.owner).IsInterface) + if (sym.isFinal && !getType(sym.owner).IsInterface) mf = mf | MethodAttributes.Final - if (sym.hasFlag(Flags.DEFERRED) || types(sym.owner).IsInterface) + if (sym.hasFlag(Flags.DEFERRED) || getType(sym.owner).IsInterface) mf = mf | MethodAttributes.Abstract } } @@ -1679,8 +1679,14 @@ abstract class GenMSIL extends SubComponent { sym.tpe.paramTypes.map(msilType).toArray } - def getType(sym: Symbol): MsilType = types.get(sym) match { - case Some(typ) => typ + def getType(sym: Symbol) = getTypeOpt(sym).getOrElse(abort(showsym(sym))) + + /** + * Get an MSIL type form a symbol. First look in the clrTypes.types map, then + * lookup the name using clrTypes.getType + */ + def getTypeOpt(sym: Symbol): Option[MsilType] = types.get(sym) match { + case typ @ Some(_) => typ case None => def typeString(sym: Symbol): String = { val s = if (sym.isNestedClass) typeString(sym.owner) +"+"+ sym.simpleName @@ -1690,10 +1696,10 @@ abstract class GenMSIL extends SubComponent { val name = typeString(sym) val typ = clrTypes.getType(name) if (typ == null) - abort(showsym(sym) + " with name " + name) + None else { - clrTypes.types(sym) = typ - typ + types(sym) = typ + Some(typ) } } @@ -1703,10 +1709,20 @@ abstract class GenMSIL extends SubComponent { } def createTypeBuilder(iclass: IClass) { + /** + * First look in the clrTypes.types map, then see if it's a class we're + * currently compiling by looking at the icodes.classes map, then finally + * lookup the name using clrTypes.getType (by calling getType). + */ def msilTypeFromSym(sym: Symbol): MsilType = { - types.get(sym) match { - case Some(mtype) => mtype - case None => createTypeBuilder(classes(sym)); types(sym) + types.get(sym).getOrElse { + classes.get(sym) match { + case Some(iclass) => + createTypeBuilder(iclass) + types (sym) + case None => + getType(sym) + } } } diff --git a/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala b/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala index 4c7b1977bb..aa784e9c87 100644 --- a/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala +++ b/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala @@ -1,6 +1,6 @@ package scala.tools.nsc -package dependencies; -import util.SourceFile; +package dependencies +import util.SourceFile import io.AbstractFile import collection._ import symtab.Flags @@ -8,11 +8,11 @@ import symtab.Flags trait DependencyAnalysis extends SubComponent with Files { import global._ - val phaseName = "dependencyAnalysis"; + val phaseName = "dependencyAnalysis" def off = settings.make.value == "all" - def newPhase(prev : Phase) = new AnalysisPhase(prev) + def newPhase(prev: Phase) = new AnalysisPhase(prev) lazy val maxDepth = settings.make.value match { case "changed" => 0 @@ -25,7 +25,7 @@ trait DependencyAnalysis extends SubComponent with Files { // todo: order insensible checking and, also checking timestamp? def validateClasspath(cp1: String, cp2: String): Boolean = cp1 == cp2 - def nameToFile(src: AbstractFile, name : String) = + def nameToFile(src: AbstractFile, name: String) = settings.outputDirs.outputDirFor(src) .lookupPathUnchecked(name.toString.replace(".", java.io.File.separator) + ".class", false) @@ -39,7 +39,7 @@ trait DependencyAnalysis extends SubComponent with Files { def dependenciesFile: Option[AbstractFile] = depFile def classpath = settings.classpath.value - def newDeps = new FileDependencies(classpath); + def newDeps = new FileDependencies(classpath) var dependencies = newDeps @@ -48,19 +48,19 @@ trait DependencyAnalysis extends SubComponent with Files { /** Top level definitions per source file. */ val definitions: mutable.Map[AbstractFile, List[Symbol]] = new mutable.HashMap[AbstractFile, List[Symbol]] { - override def default(f : AbstractFile) = Nil + override def default(f: AbstractFile) = Nil } /** External references used by source file. */ val references: mutable.Map[AbstractFile, immutable.Set[String]] = new mutable.HashMap[AbstractFile, immutable.Set[String]] { - override def default(f : AbstractFile) = immutable.Set() + override def default(f: AbstractFile) = immutable.Set() } /** External references for inherited members used in the source file */ val inherited: mutable.Map[AbstractFile, immutable.Set[Inherited]] = new mutable.HashMap[AbstractFile, immutable.Set[Inherited]] { - override def default(f : AbstractFile) = immutable.Set() + override def default(f: AbstractFile) = immutable.Set() } /** Write dependencies to the current file. */ @@ -71,15 +71,14 @@ trait DependencyAnalysis extends SubComponent with Files { /** Load dependencies from the given file and save the file reference for * future saves. */ - def loadFrom(f: AbstractFile, toFile: String => AbstractFile) : Boolean = { + def loadFrom(f: AbstractFile, toFile: String => AbstractFile): Boolean = { dependenciesFile = f FileDependencies.readFrom(f, toFile) match { case Some(fd) => val success = if (shouldCheckClasspath) validateClasspath(fd.classpath, classpath) else true dependencies = if (success) fd else { - if (settings.debug.value) { - println("Classpath has changed. Nuking dependencies"); - } + if (settings.debug.value) + println("Classpath has changed. Nuking dependencies") newDeps } @@ -88,15 +87,13 @@ trait DependencyAnalysis extends SubComponent with Files { } } - def filter(files : List[SourceFile]) : List[SourceFile] = + def filter(files: List[SourceFile]): List[SourceFile] = if (off) files - else if (dependencies.isEmpty){ - if(settings.debug.value){ - println("No known dependencies. Compiling everything"); - } + else if (dependencies.isEmpty) { + println("No known dependencies. Compiling " + + (if (settings.debug.value) files.mkString(", ") else "everything")) files - } - else { + } else { val (direct, indirect) = dependencies.invalidatedFiles(maxDepth); val filtered = files.filter(x => { val f = x.file.absolute @@ -105,8 +102,7 @@ trait DependencyAnalysis extends SubComponent with Files { filtered match { case Nil => println("No changes to recompile"); case x => println("Recompiling " + ( - if(settings.debug.value) x.mkString(", ") - else x.length + " files") + if(settings.debug.value) x.mkString(", ") else x.length + " files") ) } filtered @@ -114,13 +110,13 @@ trait DependencyAnalysis extends SubComponent with Files { case class Inherited(qualifier: String, member: Name) - class AnalysisPhase(prev : Phase) extends StdPhase(prev){ + class AnalysisPhase(prev: Phase) extends StdPhase(prev) { override def cancelled(unit: CompilationUnit) = super.cancelled(unit) && !unit.isJava def apply(unit : global.CompilationUnit) { - val f = unit.source.file.file; + val f = unit.source.file.file // When we're passed strings by the interpreter // they have no source file. We simply ignore this case // as irrelevant to dependency analysis. @@ -145,7 +141,7 @@ trait DependencyAnalysis extends SubComponent with Files { dependencies.reset(source) for (d <- unit.depends; if (d.sourceFile != null)){ - dependencies.depends(source, d.sourceFile); + dependencies.depends(source, d.sourceFile) } } diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala index ca72f6581b..7fd6538566 100644 --- a/src/compiler/scala/tools/nsc/doc/DocFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/DocFactory.scala @@ -52,7 +52,7 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor compiler.addSourceless assert(settings.docformat.value == "html") if (!reporter.hasErrors) { - val modelFactory = (new model.ModelFactory(compiler, settings)) + val modelFactory = (new model.ModelFactory(compiler, settings) with model.comment.CommentFactory) val docModel = modelFactory.makeModel println("model contains " + modelFactory.templatesCount + " documentable templates") (new html.HtmlFactory(docModel)) generate docModel diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index 3d02689605..4897d78488 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -32,4 +32,6 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) { // working around issue described in r18708. suppressVTWarn.value = true + + // TODO: add a new setting for whether or not to document sourceless entities (e.g., Any, Unit, etc) } diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala index ae98ecf1f6..31b932ac53 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala @@ -215,4 +215,10 @@ abstract class HtmlPage { thisPage => case tpl :: tpls => templateToHtml(tpl) ++ sep ++ templatesToHtml(tpls, sep) } + def docEntityKindToString(ety: DocTemplateEntity) = + if (ety.isTrait) "trait" + else if (ety.isClass) "class" + else if (ety.isObject) "object" + else if (ety.isPackage) "package" + else "class" // FIXME: an entity *should* fall into one of the above categories, but AnyRef is somehow not } diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala index 8f1d537c43..784a92f1ff 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala @@ -63,18 +63,41 @@ class Index(universe: Universe) extends HtmlPage { <ol class="templates">{ val tpls: Map[String, Seq[DocTemplateEntity]] = (pack.templates filter (t => !t.isPackage && !isExcluded(t) )) groupBy (_.name) + + val placeholderSeq: NodeSeq = <div class="placeholder"></div> + + def createLink(entity: DocTemplateEntity, includePlaceholder: Boolean, includeText: Boolean) = { + val entityType = docEntityKindToString(entity) + val linkContent = ( + { if (includePlaceholder) placeholderSeq else NodeSeq.Empty } + ++ + { if (includeText) <span class="tplLink">{ Text(packageQualifiedName(entity)) }</span> else NodeSeq.Empty } + ) + <a class="tplshow" href={ relativeLinkTo(entity) }><span class={ entityType }>({ Text(entityType) })</span>{ linkContent }</a> + } + for (tn <- tpls.keySet.toSeq sortBy (_.toLowerCase)) yield { - val entries = tpls(tn) sortWith { (less, more) => less.isTrait || more.isObject } - def doEntry(ety: DocTemplateEntity, firstEty: Boolean): NodeSeq = { - val etyTpe = - if (ety.isTrait) "trait" else if (ety.isClass) "class" else if (ety.isObject) "object" else "package" - <a class="tplshow" href={ relativeLinkTo(ety) }> - { if (firstEty) Text(packageQualifiedName(ety)) else NodeSeq.Empty } - <span class={ etyTpe }>({ Text(etyTpe) })</span> - </a> - } - <li title={ entries.head.qualifiedName }>{ - doEntry(entries.head, true) ++ (entries.tail map (doEntry(_, false))) + val entities = tpls(tn) + val row = (entities find (e => e.isPackage || e.isObject), entities find (e => e.isTrait || e.isClass)) + + val itemContents = row match { + case (Some(obj), None) => createLink(obj, includePlaceholder = true, includeText = true) + + case (maybeObj, Some(template)) => + val firstLink = maybeObj match { + case Some(obj) => createLink(obj, includePlaceholder = false, includeText = false) + case None => placeholderSeq + } + + firstLink ++ createLink(template, includePlaceholder = false, includeText = true) + + case _ => // FIXME: this default case should not be necessary. For some reason AnyRef is not a package, object, trait, or class + val entry = entities.head + placeholderSeq ++ createLink(entry, includePlaceholder = false, includeText = true) + } + + <li title={ entities.head.qualifiedName }>{ + itemContents }</li> } }</ol> diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index 41a7de93bd..12d24a7953 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -60,13 +60,13 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage { <div id="mbrsel"> { if (tpl.linearization.isEmpty) NodeSeq.Empty else <div id="ancestors"> - <h3>Inherited</h3> + <span class="filtertype">Inherited</span> <ol><li class="hideall">Hide All</li><li class="showall">Show all</li></ol> <ol id="linearization">{ tpl.linearization map { wte => <li class="in" name={ wte.qualifiedName }>{ wte.name }</li> } }</ol> </div> } <div id="visbl"> - <h3>Visibility</h3> + <span class="filtertype">Visibility</span> <ol><li class="public in">Public</li><li class="all out">All</li></ol> </div> </div> @@ -263,8 +263,7 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage { } def kindToString(mbr: MemberEntity): String = mbr match { - case tpl: DocTemplateEntity => - if (tpl.isPackage) "package" else if (tpl.isClass) "class" else if (tpl.isTrait) "trait" else "object" + case tpl: DocTemplateEntity => docEntityKindToString(tpl) case ctor: Constructor => "new" case tme: MemberEntity => ( if (tme.isImplicit) "implicit " else "" ) + diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.css b/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.css index 0bae7dbc3a..fc3f6d4c29 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.css +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.css @@ -145,10 +145,21 @@ h1 { } #tpl ol > li .icon { + padding-right: 5px; bottom: -2px; position: relative; } +#tpl .templates div.placeholder { + padding-right: 5px; + width: 13px; + display: inline-block; +} + +#tpl .templates span.tplLink { + padding-left: 8px; +} + #content { border-left-width: 1px; border-left-color: black; diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css index f315a283b6..92de97f619 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css @@ -198,13 +198,11 @@ div.members > ol > li { .cmt code { font-family: monospace; -/* font-size: small;*/ } .cmt pre { display: block; font-family: monospace; -/* font-size: small;*/ margin: 2px 0 2px 0; } @@ -329,10 +327,12 @@ div.fullcomment dl.paramcmts > dd + dt + dd { margin-bottom: 10px; } -#mbrsel > div > h3 { +#mbrsel > div > span.filtertype { padding: 4px; - display: inline; + float: left; + display: inline-block; color: white; + width: 4.5em; } #mbrsel > div > ol { @@ -346,7 +346,6 @@ div.fullcomment dl.paramcmts > dd + dt + dd { #mbrsel > div > ol > li { padding: 4px 8px 4px 8px; -/* font-weight: bold;*/ background-color: white; display: inline-block; cursor: crosshair; diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index d7e1e2fe1a..e2a25d7ea4 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -13,7 +13,7 @@ import symtab.Flags import model.{ RootPackage => RootPackageEntity } /** This trait extracts all required information for documentation from compilation units */ -class ModelFactory(val global: Global, val settings: doc.Settings) extends CommentFactory { thisFactory => +class ModelFactory(val global: Global, val settings: doc.Settings) { thisFactory: ModelFactory with CommentFactory => import global._ import definitions.{ ObjectClass, ScalaObjectClass, RootPackage, EmptyPackage, NothingClass, AnyClass, AnyRefClass } @@ -502,9 +502,11 @@ class ModelFactory(val global: Global, val settings: doc.Settings) extends Comme val name = optimize(nameBuffer.toString) } - def templateShouldDocument(aSym: Symbol): Boolean = - (aSym.isPackageClass || (aSym.sourceFile != null)) && localShouldDocument(aSym) && + def templateShouldDocument(aSym: Symbol): Boolean = { + // TODO: document sourceless entities (e.g., Any, etc), based on a new Setting to be added + (aSym.isPackageClass || (aSym.sourceFile != null)) && localShouldDocument(aSym) && ( aSym.owner == NoSymbol || templateShouldDocument(aSym.owner) ) + } def localShouldDocument(aSym: Symbol): Boolean = !aSym.isPrivate && (aSym.isProtected || aSym.privateWithin == NoSymbol) && !aSym.isSynthetic diff --git a/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala b/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala index a998f9dfc8..a42b8347a7 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala @@ -10,7 +10,8 @@ import java.io.File import java.lang.reflect import java.util.jar.{ JarEntry, JarFile } import java.util.concurrent.ConcurrentHashMap -import util.ScalaClassLoader.getSystemLoader +import util.ScalaClassLoader +import ScalaClassLoader.getSystemLoader object ByteCode { /** Until I figure out why I can't get scalap onto the classpath such @@ -34,8 +35,8 @@ object ByteCode { def scalaSigBytesForPath(path: String) = for { module <- DECODER - method <- decoderMethod("scalaSigBytes", classOf[String], classOf[ClassLoader]) - names <- method.invoke(module, path, this.getClass.getClassLoader).asInstanceOf[Option[Array[Byte]]] + method <- decoderMethod("scalaSigAnnotationBytes", classOf[String]) + names <- method.invoke(module, path).asInstanceOf[Option[Array[Byte]]] } yield names diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala index b90da5bf98..fddb1ee928 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala @@ -3,35 +3,14 @@ * @author Paul Phillips */ -// -// TODO, if practical: -// -// 1) Types: val s: String = x.<tab> should only show members which result in a String. -// Possible approach: evaluate buffer as if current identifier is -// 2) Implicits: x.<tab> should show not only x's members but those of anything for which -// there is an implicit conversion from x. -// 4) Imports: after import scala.collection.mutable._, HashMap should be among -// my top level identifiers. -// 5) Caching: parsing the jars every startup seems wasteful, but experimentally -// there is little to no gain from caching. package scala.tools.nsc package interpreter import jline._ -import java.net.URL import java.util.{ List => JList } -import java.lang.reflect -import scala.tools.util.PathResolver -import io.{ Path, Directory } object Completion { - // methods to leave out of completion - val excludeMethods = List("hashCode", "equals", "wait", "notify", "notifyAll") - - // strings to look for an exclude by default - val excludeStrings = List("$$super", "MODULE$") - def looksLikeInvocation(code: String) = ( (code != null) && (code startsWith ".") @@ -40,80 +19,231 @@ object Completion { && !(code startsWith "..") ) - trait Forwarder extends CompletionAware { - def forwardTo: Option[CompletionAware] - - override def completions() = forwardTo map (_.completions()) getOrElse Nil - override def follow(s: String) = forwardTo flatMap (_ follow s) + object Forwarder { + def apply(forwardTo: () => Option[CompletionAware]): CompletionAware = new CompletionAware { + def completions(verbosity: Int) = forwardTo() map (_ completions verbosity) getOrElse Nil + override def follow(s: String) = forwardTo() flatMap (_ follow s) + } } } import Completion._ // REPL completor - queries supplied interpreter for valid // completions based on current contents of buffer. -class Completion(repl: Interpreter) { - self => +class Completion(val repl: Interpreter) extends CompletionOutput { + // verbosity goes up with consecutive tabs + private var verbosity: Int = 0 + def resetVerbosity() = verbosity = 0 - private lazy val classPath = repl.compilerClasspath + def isCompletionDebug = repl.isCompletionDebug + def DBG(msg: => Any) = if (isCompletionDebug) println(msg.toString) + def debugging[T](msg: String): T => T = (res: T) => returning[T](res)(x => DBG(msg + x)) - // the unqualified vals/defs/etc visible in the repl - val ids = new IdentCompletion(repl) - // the top level packages we know about - val pkgs = new PackageCompletion(classPath) - // members of Predef - val predef = new StaticCompletion(classOf[scala.Predef$]) { - override def filterNotFunction(s: String) = ( - (s contains "2") || - (s startsWith "wrap") || - (s endsWith "Wrapper") || - (s endsWith "Ops") - ) + lazy val global: repl.compiler.type = repl.compiler + import global._ + import definitions.{ PredefModule, RootClass, AnyClass, AnyRefClass, ScalaPackage, JavaLangPackage } + + // XXX not yet used. + lazy val dottedPaths = { + def walk(tp: Type): scala.List[Symbol] = { + val pkgs = tp.nonPrivateMembers filter (_.isPackage) + pkgs ++ (pkgs map (_.tpe) flatMap walk) + } + walk(RootClass.tpe) } - // members of scala.* - val scalalang = new pkgs.SubCompletor("scala") with Forwarder { - def forwardTo = pkgs follow "scala" - val arityClasses = { - val names = List("Tuple", "Product", "Function") - val expanded = for (name <- names ; index <- 0 to 22 ; dollar <- List("", "$")) yield name + index + dollar - Set(expanded: _*) + def getType(name: String, isModule: Boolean) = { + val f = if (isModule) definitions.getModule(_: Name) else definitions.getClass(_: Name) + try Some(f(name).tpe) + catch { case _: MissingRequirementError => None } + } + + def typeOf(name: String) = getType(name, false) + def moduleOf(name: String) = getType(name, true) + + trait CompilerCompletion { + def tp: Type + def effectiveTp = tp match { + case MethodType(Nil, resType) => resType + case PolyType(Nil, resType) => resType + case _ => tp } - override def filterNotFunction(s: String) = { - val simple = s.reverse takeWhile (_ != '.') reverse + // for some reason any's members don't show up in subclasses, which + // we need so 5.<tab> offers asInstanceOf etc. + private def anyMembers = AnyClass.tpe.nonPrivateMembers + def anyRefMethodsToShow = List("isInstanceOf", "asInstanceOf", "toString") + + def tos(sym: Symbol) = sym.name.decode.toString + def memberNamed(s: String) = members find (x => tos(x) == s) + def hasMethod(s: String) = methods exists (x => tos(x) == s) + + // XXX we'd like to say "filterNot (_.isDeprecated)" but this causes the + // compiler to crash for reasons not yet known. + def members = (effectiveTp.nonPrivateMembers ++ anyMembers) filter (_.isPublic) + def methods = members filter (_.isMethod) + def packages = members filter (_.isPackage) + def aliases = members filter (_.isAliasType) - (arityClasses contains simple) || - (s endsWith "Exception") || - (s endsWith "Error") + def memberNames = members map tos + def methodNames = methods map tos + def packageNames = packages map tos + def aliasNames = aliases map tos + } + + object TypeMemberCompletion { + def apply(tp: Type): TypeMemberCompletion = { + if (tp.typeSymbol.isPackageClass) new PackageCompletion(tp) + else new TypeMemberCompletion(tp) } + def imported(tp: Type) = new ImportCompletion(tp) } - // members of java.lang.* - val javalang = new pkgs.SubCompletor("java.lang") with Forwarder { - def forwardTo = pkgs follow "java.lang" - import reflect.Modifier.isPublic - private def existsAndPublic(s: String): Boolean = { - val name = if (s contains ".") s else "java.lang." + s - val clazz = classForName(name) getOrElse (return false) - - isPublic(clazz.getModifiers) + + class TypeMemberCompletion(val tp: Type) extends CompletionAware with CompilerCompletion { + def excludeEndsWith: List[String] = Nil + def excludeStartsWith: List[String] = List("<") // <byname>, <repeated>, etc. + def excludeNames: List[String] = anyref.methodNames -- anyRefMethodsToShow ++ List("_root_") + + def methodSignatureString(sym: Symbol) = { + def asString = new MethodSymbolOutput(sym).methodString() + + if (isCompletionDebug) + repl.power.showAtAllPhases(asString) + + atPhase(currentRun.typerPhase)(asString) + } + + def exclude(name: String): Boolean = ( + (name contains "$") || + (excludeNames contains name) || + (excludeEndsWith exists (name endsWith _)) || + (excludeStartsWith exists (name startsWith _)) + ) + def filtered(xs: List[String]) = xs filterNot exclude distinct + + def completions(verbosity: Int) = + debugging(tp + " completions ==> ")(filtered(memberNames)) + + override def follow(s: String): Option[CompletionAware] = + debugging(tp + " -> '" + s + "' ==> ")(memberNamed(s) map (x => TypeMemberCompletion(x.tpe))) + + override def alternativesFor(id: String): List[String] = + debugging(id + " alternatives ==> ") { + val alts = members filter (x => x.isMethod && tos(x) == id) map methodSignatureString + + if (alts.nonEmpty) "" :: alts else Nil + } + + override def toString = "TypeMemberCompletion(%s)".format(tp) + } + + class PackageCompletion(tp: Type) extends TypeMemberCompletion(tp) { + override def excludeNames = anyref.methodNames + } + + class LiteralCompletion(lit: Literal) extends TypeMemberCompletion(lit.value.tpe) { + override def completions(verbosity: Int) = verbosity match { + case 0 => filtered(memberNames) + case _ => memberNames + } + } + + class ImportCompletion(tp: Type) extends TypeMemberCompletion(tp) { + override def completions(verbosity: Int) = verbosity match { + case 0 => filtered(members filterNot (_.isSetter) map tos) + case _ => super.completions(verbosity) + } + } + + // not for completion but for excluding + object anyref extends TypeMemberCompletion(AnyRefClass.tpe) { } + + // the unqualified vals/defs/etc visible in the repl + object ids extends CompletionAware { + override def completions(verbosity: Int) = repl.unqualifiedIds ::: List("classOf") + // we try to use the compiler and fall back on reflection if necessary + // (which at present is for anything defined in the repl session.) + override def follow(id: String) = + if (completions(0) contains id) { + for (clazz <- repl clazzForIdent id) yield { + (typeOf(clazz.getName) map TypeMemberCompletion.apply) getOrElse new InstanceCompletion(clazz) + } + } + else None + } + + // wildcard imports in the repl like "import global._" or "import String._" + private def imported = repl.wildcardImportedTypes map TypeMemberCompletion.imported + + // literal Ints, Strings, etc. + object literals extends CompletionAware { + def simpleParse(code: String): Tree = { + val unit = new CompilationUnit(new util.BatchSourceFile("<console>", code)) + val scanner = new syntaxAnalyzer.UnitParser(unit) + val tss = scanner.templateStatSeq(false)._2 + + if (tss.size == 1) tss.head else EmptyTree } - override def filterNotFunction(s: String) = { - (s endsWith "Exception") || - (s endsWith "Error") || - (s endsWith "Impl") || - (s startsWith "CharacterData") + + def completions(verbosity: Int) = Nil + + override def follow(id: String) = simpleParse(id) match { + case x: Literal => Some(new LiteralCompletion(x)) + case _ => None } - override def completions() = super.completions() filter existsAndPublic } - val literals = new LiteralCompletion { - lazy val global = repl.compiler - val parent = self + + // top level packages + object rootClass extends TypeMemberCompletion(RootClass.tpe) { } + // members of Predef + object predef extends TypeMemberCompletion(PredefModule.tpe) { + override def excludeEndsWith = super.excludeEndsWith ++ List("Wrapper", "ArrayOps") + override def excludeStartsWith = super.excludeStartsWith ++ List("wrap") + override def excludeNames = anyref.methodNames + + override def exclude(name: String) = super.exclude(name) || ( + (name contains "2") + ) + + override def completions(verbosity: Int) = verbosity match { + case 0 => Nil + case _ => super.completions(verbosity) + } } + // members of scala.* + object scalalang extends PackageCompletion(ScalaPackage.tpe) { + def arityClasses = List("Product", "Tuple", "Function") + def skipArity(name: String) = arityClasses exists (x => name != x && (name startsWith x)) + override def exclude(name: String) = super.exclude(name) || ( + skipArity(name) + ) - def lastResult = new Forwarder { - def forwardTo = ids follow repl.mostRecentVar + override def completions(verbosity: Int) = verbosity match { + case 0 => filtered(packageNames ++ aliasNames) + case _ => super.completions(verbosity) + } + } + // members of java.lang.* + object javalang extends PackageCompletion(JavaLangPackage.tpe) { + override lazy val excludeEndsWith = super.excludeEndsWith ++ List("Exception", "Error") + override lazy val excludeStartsWith = super.excludeStartsWith ++ List("CharacterData") + + override def completions(verbosity: Int) = verbosity match { + case 0 => filtered(packageNames) + case _ => super.completions(verbosity) + } } + // the list of completion aware objects which should be consulted + lazy val topLevelBase: List[CompletionAware] = List(ids, rootClass, predef, scalalang, javalang, literals) + def topLevel = topLevelBase ++ imported + + // the first tier of top level objects (doesn't include file completion) + def topLevelFor(parsed: Parsed) = topLevel flatMap (_ completionsFor parsed) + + // the most recent result + def lastResult = Forwarder(() => ids follow repl.mostRecentVar) + def lastResultFor(parsed: Parsed) = { /** The logic is a little tortured right now because normally '.' is * ignored as a delimiter, but on .<tab> it needs to be propagated. @@ -122,12 +252,6 @@ class Completion(repl: Interpreter) { if (parsed.isEmpty) xs map ("." + _) else xs } - // the list of completion aware objects which should be consulted - val topLevel: List[CompletionAware] = List(ids, pkgs, predef, scalalang, javalang, literals) - - // the first tier of top level objects (doesn't include file completion) - def topLevelFor(parsed: Parsed) = topLevel flatMap (_ completionsFor parsed) - // chasing down results which won't parse def execute(line: String): Option[Any] = { val parsed = Parsed(line) @@ -136,34 +260,43 @@ class Completion(repl: Interpreter) { if (noDotOrSlash) None // we defer all unqualified ids to the repl. else { (ids executionFor parsed) orElse - (pkgs executionFor parsed) orElse + (rootClass executionFor parsed) orElse (FileCompletion executionFor line) } } - // override if history is available - def lastCommand: Option[String] = None + // generic interface for querying (e.g. interpreter loop, testing) + def completions(buf: String): List[String] = + topLevelFor(Parsed.dotted(buf + ".", buf.length + 1)) // jline's entry point lazy val jline: ArgumentCompletor = returning(new ArgumentCompletor(new JLineCompletion, new JLineDelimiter))(_ setStrict false) + /** This gets a little bit hairy. It's no small feat delegating everything + * and also keeping track of exactly where the cursor is and where it's supposed + * to end up. The alternatives mechanism is a little hacky: if there is an empty + * string in the list of completions, that means we are expanding a unique + * completion, so don't update the "last" buffer because it'll be wrong. + */ class JLineCompletion extends Completor { // For recording the buffer on the last tab hit - private var lastTab: (String, String) = (null, null) + private var lastBuf: String = "" + private var lastCursor: Int = -1 // Does this represent two consecutive tabs? - def isConsecutiveTabs(buf: String) = (buf, lastCommand orNull) == lastTab + def isConsecutiveTabs(buf: String, cursor: Int) = cursor == lastCursor && buf == lastBuf - // verbosity goes up with consecutive tabs - // TODO - actually implement. - private var verbosity = 0 + // Longest common prefix + def commonPrefix(xs: List[String]) = + if (xs.isEmpty) "" + else xs.reduceLeft(_ zip _ takeWhile (x => x._1 == x._2) map (_._1) mkString) // This is jline's entry point for completion. - override def complete(buf: String, cursor: Int, candidates: JList[String]): Int = { - // println("complete: buf = %s, cursor = %d".format(buf, cursor)) - verbosity = if (isConsecutiveTabs(buf)) verbosity + 1 else 0 - lastTab = (buf, lastCommand orNull) + override def complete(_buf: String, cursor: Int, candidates: JList[String]): Int = { + val buf = onull(_buf) + verbosity = if (isConsecutiveTabs(buf, cursor)) verbosity + 1 else 0 + DBG("complete(%s, %d) last = (%s, %d), verbosity: %s".format(buf, cursor, lastBuf, lastCursor, verbosity)) // we don't try lower priority completions unless higher ones return no results. def tryCompletion(p: Parsed, completionFunction: Parsed => List[String]): Option[Int] = { @@ -172,17 +305,30 @@ class Completion(repl: Interpreter) { case xs => // modify in place and return the position xs foreach (candidates add _) - Some(p.position) + + // update the last buffer unless this is an alternatives list + if (xs contains "") Some(p.cursor) + else { + val advance = commonPrefix(xs) + lastCursor = p.position + advance.length + lastBuf = (buf take p.position) + advance + + DBG("tryCompletion(%s, _) lastBuf = %s, lastCursor = %s, p.position = %s".format(p, lastBuf, lastCursor, p.position)) + Some(p.position) + } } } + def mkDotted = Parsed.dotted(buf, cursor) withVerbosity verbosity + def mkUndelimited = Parsed.undelimited(buf, cursor) withVerbosity verbosity + // a single dot is special cased to completion on the previous result def lastResultCompletion = if (!looksLikeInvocation(buf)) None else tryCompletion(Parsed.dotted(buf drop 1, cursor), lastResultFor) - def regularCompletion = tryCompletion(Parsed.dotted(buf, cursor), topLevelFor) - def fileCompletion = tryCompletion(Parsed.undelimited(buf, cursor), FileCompletion completionsFor _.buffer) + def regularCompletion = tryCompletion(mkDotted, topLevelFor) + def fileCompletion = tryCompletion(mkUndelimited, FileCompletion completionsFor _.buffer) (lastResultCompletion orElse regularCompletion orElse fileCompletion) getOrElse cursor } diff --git a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala index 7e94b687bf..cfd3b5e05f 100644 --- a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala +++ b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala @@ -21,8 +21,7 @@ trait CompletionAware { /** The complete list of unqualified Strings to which this * object will complete. */ - def completions(): List[String] - def completions(start: String): List[String] = completions filter (_ startsWith start) + def completions(verbosity: Int): List[String] /** Default filter to apply to completions. */ @@ -47,6 +46,19 @@ trait CompletionAware { */ def execute(id: String): Option[Any] = None + /** A list of useful information regarding a specific uniquely + * identified completion. This is specifically written for the + * following situation, but should be useful elsewhere too: + * + * x.y.z.methodName<tab> + * + * If "methodName" is among z's completions, and verbosity > 0 + * indicating tab has been pressed twice consecutively, then we + * call alternativesFor and show a list of overloaded method + * signatures. + */ + def alternativesFor(id: String): List[String] = Nil + /** Given string 'buf', return a list of all the strings * to which it can complete. This may involve delegating * to other CompletionAware objects. @@ -54,12 +66,16 @@ trait CompletionAware { def completionsFor(parsed: Parsed): List[String] = { import parsed._ - val cs = - if (isEmpty) completions() - else if (isUnqualified && !isLastDelimiter) completions(buffer) + val comps = completions(verbosity) filter (_ startsWith buffer) + val results = + if (isEmpty) comps + else if (isUnqualified && !isLastDelimiter) { + if (verbosity > 0 && (comps contains buffer)) alternativesFor(buffer) + else comps + } else follow(bufferHead) map (_ completionsFor bufferTail) getOrElse Nil - cs filterNot filterNotFunction map mapFunction sortWith (sortFunction _) + results filterNot filterNotFunction map mapFunction sortWith (sortFunction _) } /** TODO - unify this and completionsFor under a common traverser. @@ -67,14 +83,14 @@ trait CompletionAware { def executionFor(parsed: Parsed): Option[Any] = { import parsed._ - if (isUnqualified && !isLastDelimiter && (completions contains buffer)) execute(buffer) + if (isUnqualified && !isLastDelimiter && (completions(verbosity) contains buffer)) execute(buffer) else if (!isQualified) None else follow(bufferHead) flatMap (_ executionFor bufferTail) } } object CompletionAware { - val Empty = new CompletionAware { val completions = Nil } + val Empty = new CompletionAware { def completions(verbosity: Int) = Nil } // class Forwarder(underlying: CompletionAware) extends CompletionAware { // override def completions() = underlying.completions() @@ -101,6 +117,7 @@ object CompletionAware { def apply(terms: () => List[String], followFunction: String => Option[CompletionAware]): CompletionAware = new CompletionAware { def completions = terms() + def completions(verbosity: Int) = completions override def follow(id: String) = followFunction(id) } diff --git a/src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala b/src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala new file mode 100644 index 0000000000..9b9d9a36f1 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala @@ -0,0 +1,88 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +/** This has a lot of duplication with other methods in Symbols and Types, + * but repl completion utility is very sensitive to precise output. Best + * thing would be to abstract an interface for how such things are printed, + * as is also in progress with error messages. + */ +trait CompletionOutput { + self: Completion => + + import global._ + import definitions.{ NothingClass, AnyClass, isTupleType, isFunctionType, isRepeatedParamType } + + /** Reducing fully qualified noise for some common packages. + */ + val typeTransforms = List( + "java.lang." -> "", + "scala.collection.immutable." -> "immutable.", + "scala.collection.mutable." -> "mutable.", + "scala.collection.generic." -> "generic." + ) + + def quietString(tp: String): String = + typeTransforms.foldLeft(tp) { + case (str, (prefix, replacement)) => + if (str startsWith prefix) replacement + (str stripPrefix prefix) + else str + } + + class MethodSymbolOutput(method: Symbol) { + val pkg = method.ownerChain find (_.isPackageClass) map (_.fullName) getOrElse "" + + def relativize(str: String): String = quietString(str stripPrefix (pkg + ".")) + def relativize(tp: Type): String = relativize(tp.normalize.toString) + def relativize(sym: Symbol): String = relativize(sym.info) + + def braceList(tparams: List[String]) = if (tparams.isEmpty) "" else (tparams map relativize).mkString("[", ", ", "]") + def parenList(params: List[Any]) = params.mkString("(", ", ", ")") + + def methodTypeToString(mt: MethodType) = + (mt.paramss map paramsString mkString "") + ": " + relativize(mt.finalResultType) + + def typeToString(tp: Type): String = relativize( + tp match { + case x if isFunctionType(x) => functionString(x) + case x if isTupleType(x) => tupleString(x) + case x if isRepeatedParamType(x) => typeToString(x.typeArgs.head) + "*" + case mt @ MethodType(_, _) => methodTypeToString(mt) + case x => x.toString + } + ) + + def tupleString(tp: Type) = parenList(tp.normalize.typeArgs map relativize) + def functionString(tp: Type) = tp.normalize.typeArgs match { + case List(t, r) => t + " => " + r + case xs => parenList(xs.init) + " => " + xs.last + } + + def tparamsString(tparams: List[Symbol]) = braceList(tparams map (_.defString)) + def paramsString(params: List[Symbol]) = { + def paramNameString(sym: Symbol) = if (sym.isSynthetic) "" else sym.nameString + ": " + def paramString(sym: Symbol) = paramNameString(sym) + typeToString(sym.info.normalize) + + val isImplicit = params.nonEmpty && params.head.isImplicit + val strs = (params map paramString) match { + case x :: xs if isImplicit => ("implicit " + x) :: xs + case xs => xs + } + parenList(strs) + } + + def methodString() = + method.keyString + " " + method.nameString + (method.info.normalize match { + case PolyType(Nil, resType) => ": " + typeToString(resType) // nullary method + case PolyType(tparams, resType) => tparamsString(tparams) + typeToString(resType) + case mt @ MethodType(_, _) => methodTypeToString(mt) + case x => + DBG("methodString(): %s / %s".format(x.getClass, x)) + x.toString + }) + } +} diff --git a/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala deleted file mode 100644 index b0152dbbc6..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala +++ /dev/null @@ -1,25 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -/** Top level identifiers visible in the repl. It immediately - * delegates to an InstanceCompletion. - */ -class IdentCompletion(repl: Interpreter) extends CompletionAware { - val INTERPRETER_VAR_PREFIX = "res" - - def completions() = repl.unqualifiedIds ::: List("classOf") - override def follow(id: String) = - // XXX this will be nice but needs solidifying. - // (repl completionAwareImplicit id) orElse - if (completions contains id) { - (repl completionAware id) orElse { - repl clazzForIdent id map (x => new InstanceCompletion(x)) - } - } - else None -} diff --git a/src/compiler/scala/tools/nsc/interpreter/LiteralCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/LiteralCompletion.scala deleted file mode 100644 index 3b74549d27..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/LiteralCompletion.scala +++ /dev/null @@ -1,50 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -import util.BatchSourceFile - -/** Literals, so we can pretend they are objects with methods. - */ -abstract class LiteralCompletion extends CompletionAware { - val parent: Completion - val global: Global - - import global._ - - // TODO - figure out how to enumerate available implicit conversions. - // def richInt = new InstanceCompletion(classOf[scala.runtime.RichInt]) - - class PrimitiveCompletion(x: Type) extends CompletionAware { - lazy val completions = x.nonPrivateMembers map (_.name.toString) - override def follow(s: String) = { - val member = x.nonPrivateMembers find (_.name.toString == s) - member flatMap (m => Option(m.tpe)) map (_.resultType) map (x => new PrimitiveCompletion(x)) - } - } - - def simpleParse(code: String): Tree = { - val unit = new CompilationUnit(new BatchSourceFile("<console>", code)) - val scanner = new syntaxAnalyzer.UnitParser(unit) - - // only single statements - scanner.templateStatSeq(false) match { - case (_, List(t)) => t - case (_, x) => EmptyTree - } - } - - def completions() = Nil - override def follow(id: String) = simpleParse(id) match { - case Literal(c @ Constant(_)) => Some(new PrimitiveCompletion(c.tpe)) - // TODO - more AST trees. - // case Apply(fn @ Ident(name), args) => - // classForName(name.toString) map (x => new StaticCompletion(x)) - // None - case x => None - } -} diff --git a/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala deleted file mode 100644 index 26ae4106c6..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala +++ /dev/null @@ -1,187 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -import java.net.URL -import java.lang.reflect -import java.util.concurrent.ConcurrentHashMap -import io.{ Path, Directory, File, Streamable } -import scala.tools.util.PathResolver.Defaults.scalaHomeDir -import scala.concurrent.DelayedLazyVal -import scala.reflect.NameTransformer.{ decode, encode } -import PackageCompletion._ - -/** Completion among all known packages. It examines the jars in a - * separate thread so as not to slow down startup. If it arrives at - * an object, it delegates to StaticCompletion for that object. - */ -class PackageCompletion(classpath: List[URL]) extends CompletionAware { - // it takes a little while to look through the jars so we use a future and a concurrent map - class CompletionAgent { - val dottedPaths: ConcurrentHashMap[String, List[CompletionInfo]] = new ConcurrentHashMap[String, List[CompletionInfo]] - val topLevelPackages = new DelayedLazyVal( - () => enumToList(dottedPaths.keys) filterNot (_ contains '.'), - getDottedPaths(dottedPaths, classpath) - ) - } - val agent = new CompletionAgent - import agent._ - - def completions() = topLevelPackages() - override def follow(id: String) = - if (dottedPaths containsKey id) Some(new SubCompletor(id)) - else None - - class SubCompletor(root: String) extends CompletionAware { - // Look for a type alias - private def aliasCompletor(path: String): Option[CompletionAware] = - for (name <- ByteCode aliasForType path ; clazz <- classForName(name + "$")) yield - new StaticCompletion(clazz) - - lazy val pkgObject = classForName(root + ".package$") map (x => new PackageObjectCompletion(x)) - def pkgObjectMembers = pkgObject map (_ completionsFor Parsed("")) getOrElse Nil - - private def infos = Option(dottedPaths get root) getOrElse Nil - def completions() = { - val xs = infos map (_.visibleName) filterNot (_ == "package") - xs ::: pkgObjectMembers - } - - override def follow(segment: String): Option[CompletionAware] = { - PackageCompletion.this.follow(root + "." + segment) orElse { - for (CompletionInfo(`segment`, className) <- infos ; clazz <- classForName(className)) { - return Some(new StaticCompletion(clazz)) - } - - aliasCompletor(root + "." + segment) - } - } - override def toString = "SubCompletor(%s)" format root - } -} - -object PackageCompletion { - import java.util.jar.{ JarEntry, JarFile } - - val EXPAND_SEPARATOR_STRING = "$$" - val ANON_CLASS_NAME = "$anon" - val TRAIT_SETTER_SEPARATOR_STRING = "$_setter_$" - val IMPL_CLASS_SUFFIX ="$class" - - def ignoreClassName(x: String) = - (x contains EXPAND_SEPARATOR_STRING) || - (x contains ANON_CLASS_NAME) || - (x contains TRAIT_SETTER_SEPARATOR_STRING) || - (x endsWith IMPL_CLASS_SUFFIX) || - (x matches """.*\$\d+$""") - - def enumToList[T](e: java.util.Enumeration[T]): List[T] = enumToListInternal(e, Nil) - private def enumToListInternal[T](e: java.util.Enumeration[T], xs: List[T]): List[T] = - if (e == null || !e.hasMoreElements) xs else enumToListInternal(e, e.nextElement :: xs) - - private def isClass(s: String) = s endsWith ".class" - private def processNames(xs: List[String]) = xs map (_ dropRight 6) filterNot ignoreClassName distinct - - def getDirClassFiles(dir: Directory): List[String] = - processNames(dir.deepList() map (dir relativize _ path) filter isClass toList) - - def getJarClassFiles(jar: File): List[String] = - if (!jar.exists) Nil - else processNames(enumToList(new JarFile(jar.path).entries) map (_.getName) filter isClass) - - object CompletionInfo { - def unapply(that: Any) = that match { - case x: CompletionInfo => Some((x.visibleName, x.className)) - case _ => None - } - } - - abstract class CompletionInfo { - def visibleName: String - def className: String - def getBytes(): Array[Byte] - - override def hashCode = visibleName.hashCode - override def equals(other: Any) = other match { - case x: CompletionInfo => visibleName == x.visibleName - case _ => false - } - } - - case class DirCompletionInfo(visibleName: String, className: String, dir: Directory) extends CompletionInfo { - lazy val file = dir / File(className) - - def getBytes(): Array[Byte] = try file.toByteArray() catch { case _: Exception => Array() } - } - - case class JarCompletionInfo(visibleName: String, className: String, jar: File) extends CompletionInfo { - lazy val jarfile = new JarFile(jar.path) - lazy val entry = jarfile getEntry className - - def getBytes(): Array[Byte] = { - if (entry == null) Array() else { - val x = new Streamable.Bytes { def inputStream() = jarfile getInputStream entry } - x.toByteArray() - } - } - } - - // all the dotted path to classfiles we can find by poking through the jars - def getDottedPaths(map: ConcurrentHashMap[String, List[CompletionInfo]], classpath: List[URL]): Unit = { - val cp = classpath.distinct map (x => Path(x.getPath)) - val jars = cp filter (_ hasExtension "jar") map (_.toFile) - - /** If we process all dirs uncritically, someone who has '.' in their classpath and - * runs scala from the filesystem root directory will induce a traversal of their - * entire filesystem. We could apply some heuristics to avoid this, but for now we - * will look only in the scalaHome directories, which is most of what we want. - */ - def isUnderScalaHome(d: Directory) = d.parents exists (_ == scalaHomeDir) - val dirs = cp collect { case x: Directory => x } filter isUnderScalaHome - - // for e.g. foo.bar.baz.C, returns (foo -> bar), (foo.bar -> baz), (foo.bar.baz -> C) - // and scala.Range$BigInt needs to go scala -> Range -> BigInt - def subpaths(s: String): List[(String, String)] = { - val segs = decode(s).split("""[/.]""") - val components = segs dropRight 1 - - (1 to components.length).toList flatMap { i => - val k = components take i mkString "." - if (segs(i) contains "$") { - val dollarsegs = segs(i).split("$").toList - for (j <- 1 to (dollarsegs.length - 1) toList) yield { - val newk = k + "." + (dollarsegs take j mkString ".") - (k -> dollarsegs(j)) - } - } - else List(k -> segs(i)) - } - } - - def addToMap(key: String, info: CompletionInfo) = { - if (map containsKey key) { - val vs = map.get(key) - if (vs contains info) () - else map.put(key, info :: vs) - } - else map.put(key, List(info)) - } - - def oneDir(dir: Directory) { - for (cl <- getDirClassFiles(dir) ; (k, v) <- subpaths(cl)) - addToMap(k, DirCompletionInfo(v, cl, dir)) - } - - def oneJar(jar: File) { - for (cl <- getJarClassFiles(jar) ; (k, v) <- subpaths(cl)) - addToMap(k, JarCompletionInfo(v, cl, jar)) - } - - jars foreach oneJar - dirs foreach oneDir - } -}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala index b130396cc6..0b92608d88 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala @@ -15,18 +15,22 @@ class Parsed private ( val cursor: Int, val delimited: Char => Boolean ) extends Delimited { - def isEmpty = buffer == "" + def isEmpty = args.isEmpty def isUnqualified = args.size == 1 def isQualified = args.size > 1 def isAtStart = cursor <= 0 + private var _verbosity = 0 + def verbosity = _verbosity + def withVerbosity(v: Int): this.type = returning[this.type](this)(_ => _verbosity = v) + def args = toArgs(buffer take cursor).toList def bufferHead = args.head def headLength = bufferHead.length + 1 - def bufferTail = new Parsed(buffer drop headLength, cursor - headLength, delimited) + def bufferTail = new Parsed(buffer drop headLength, cursor - headLength, delimited) withVerbosity verbosity - def prev = new Parsed(buffer, cursor - 1, delimited) - def next = new Parsed(buffer, cursor + 1, delimited) + def prev = new Parsed(buffer, cursor - 1, delimited) withVerbosity verbosity + def next = new Parsed(buffer, cursor + 1, delimited) withVerbosity verbosity def currentChar = buffer(cursor) def currentArg = args.last def position = @@ -52,7 +56,7 @@ class Parsed private ( object Parsed { def apply(s: String): Parsed = apply(onull(s), onull(s).length) - def apply(s: String, cursor: Int): Parsed = apply(onull(s), cursor, "(){},`; \t" contains _) + def apply(s: String, cursor: Int): Parsed = apply(onull(s), cursor, "{},`; \t" contains _) def apply(s: String, cursor: Int, delimited: Char => Boolean): Parsed = new Parsed(onull(s), cursor, delimited) diff --git a/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala index 2aaa6114c2..6c066580ae 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala @@ -8,6 +8,7 @@ package interpreter class SeqCompletion[T](elems: Seq[T]) extends CompletionAware { lazy val completions = elems.indices.toList map ("(%d)" format _) + def completions(verbosity: Int) = completions private def elemAt(name: String) = if (completions contains name) Some(elems(name drop 1 dropRight 1 toInt)) else None @@ -27,6 +28,7 @@ class ProductCompletion(root: Product) extends CompletionAware { } lazy val completions = caseNames + def completions(verbosity: Int) = completions override def execute(name: String) = fieldForName(name) override def follow(name: String) = fieldForName(name) map (x => ProductCompletion(x)) } diff --git a/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala index 89490119ff..f9ff894d59 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala @@ -12,7 +12,6 @@ import Modifier.{ isPrivate, isProtected, isStatic } import scala.reflect.NameTransformer import scala.collection.mutable.HashMap import ReflectionCompletion._ -import Completion.{ excludeMethods } trait ReflectionCompletion extends CompletionAware { def clazz: Class[_] @@ -30,12 +29,6 @@ trait ReflectionCompletion extends CompletionAware { case x => error(x.toString) } - override def filterNotFunction(s: String): Boolean = { - (excludeMethods contains s) || - (s contains "$$super") || - (s == "MODULE$") - } - lazy val (staticMethods, instanceMethods) = clazz.getMethods.toList partition (x => isStatic(x.getModifiers)) lazy val (staticFields, instanceFields) = clazz.getFields.toList partition (x => isStatic(x.getModifiers)) @@ -53,15 +46,6 @@ trait ReflectionCompletion extends CompletionAware { } } -/** An instance completion which hides a few useless members. - */ -class PackageObjectCompletion(clazz: Class[_]) extends InstanceCompletion(clazz) { - override lazy val completions = memberCompletions - override def filterNotFunction(s: String) = { - super.filterNotFunction(s) || (s == "getClass") || (s == "toString") - } -} - /** A completion aware object representing a single instance of some class. * It completes to instance fields and methods, and delegates to another * InstanceCompletion object if it can determine the result type of the element. @@ -70,6 +54,7 @@ class InstanceCompletion(val clazz: Class[_]) extends ReflectionCompletion { protected def visibleMembers = instanceMethods ::: instanceFields def extras = List("isInstanceOf", "asInstanceOf", "toString") lazy val completions = memberCompletions ::: extras + def completions(verbosity: Int) = completions val (zeroArg, otherArg) = instanceMethods partition (_.getParameterTypes.size == 0) override def follow(id: String) = { @@ -85,6 +70,7 @@ class InstanceCompletion(val clazz: Class[_]) extends ReflectionCompletion { class StaticCompletion(val clazz: Class[_]) extends ReflectionCompletion { protected def visibleMembers = whichMethods ::: whichFields lazy val completions = memberCompletions + def completions(verbosity: Int) = completions private def aliasForPath(path: String) = ByteCode aliasForType path flatMap (x => classForName(x + "$")) def className = clazz.getName diff --git a/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala index 67063192bd..f2af57cc36 100644 --- a/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala @@ -33,6 +33,7 @@ class XMLCompletion(root: Node) extends CompletionAware { s :: res }).sorted } + def completions(verbosity: Int) = completions override def execute(id: String) = getNode(id) override def follow(id: String) = getNode(id) map (x => new XMLCompletion(x)) diff --git a/src/compiler/scala/tools/nsc/io/Process.scala b/src/compiler/scala/tools/nsc/io/Process.scala index 698082d19e..ebd7937f33 100644 --- a/src/compiler/scala/tools/nsc/io/Process.scala +++ b/src/compiler/scala/tools/nsc/io/Process.scala @@ -153,9 +153,8 @@ class Process(processCreator: () => JProcess) extends Iterable[String] { private val reader = new BufferedReader(new InputStreamReader(in)) private def finish() { - // make sure this thread is complete, and close the process's stdin + // make sure this thread is complete join() - _in.close() } def slurp(): String = { @@ -171,14 +170,19 @@ class Process(processCreator: () => JProcess) extends Iterable[String] { def next = it.next } } - @tailrec override final def run() { - reader.readLine match { - case null => - reader.close() - case x => - queue put x - run() + override final def run() { + @tailrec def loop() { + reader.readLine match { + case null => + reader.close() + case x => + queue put x + loop() + } } + + try loop() + catch { case _: IOException => () } } } diff --git a/src/compiler/scala/tools/nsc/reporters/AbstractReporter.scala b/src/compiler/scala/tools/nsc/reporters/AbstractReporter.scala index ce10f560e9..e35843fc9c 100644 --- a/src/compiler/scala/tools/nsc/reporters/AbstractReporter.scala +++ b/src/compiler/scala/tools/nsc/reporters/AbstractReporter.scala @@ -29,7 +29,7 @@ abstract class AbstractReporter extends Reporter { protected def info0(pos: Position, msg: String, _severity: Severity, force: Boolean) { val severity = - if (settings.Ywarnfatal.value && _severity == WARNING) ERROR + if (settings.Xwarnfatal.value && _severity == WARNING) ERROR else _severity severity match { diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala index b32796e829..e8443d11c1 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -8,7 +8,7 @@ package scala.tools package nsc package settings -import io.AbstractFile +import io.{AbstractFile, VirtualDirectory} import scala.tools.util.StringOps import scala.collection.mutable.ListBuffer @@ -291,7 +291,11 @@ class MutableSettings(val errorFn: String => Unit) extends AbsSettings with Scal classFile.path.startsWith(outDir.path) singleOutDir match { - case Some(d) => Nil + case Some(d) => + d match { + case _: VirtualDirectory => Nil + case _ => List(d.lookupPathUnchecked(srcPath, false)) + } case None => (outputs filter (isBelow _).tupled) match { case Nil => Nil diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 1b6b8297ef..51b47f87d6 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -27,7 +27,6 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings { * Temporary Settings */ val suppressVTWarn = BooleanSetting ("-Ysuppress-vt-typer-warnings", "Suppress warnings from the typer when testing the virtual class encoding, NOT FOR FINAL!") - val javaignorecp = BooleanSetting ("-javaignorecp", "Does nothing - is being removed.") // !!! marked for death, but need new starr. /** * Standard settings @@ -80,6 +79,9 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings { val Xshowobj = StringSetting ("-Xshow-object", "object", "Show object info", "") val showPhases = BooleanSetting ("-Xshow-phases", "Print a synopsis of compiler phases") val sourceReader = StringSetting ("-Xsource-reader", "classname", "Specify a custom method for reading source files", "scala.tools.nsc.io.SourceReader") + val Xwarnfatal = BooleanSetting ("-Xfatal-warnings", "Fail the compilation if there are any warnings.") + val Xwarninit = BooleanSetting ("-Xwarninit", "Warn about possible changes in initialization semantics") + val Xchecknull = BooleanSetting ("-Xcheck-null", "Emit warning on selection of nullable reference") /** Compatibility stubs for options whose value name did * not previously match the option name. @@ -133,21 +135,13 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings { val Ytyperdebug = BooleanSetting ("-Ytyper-debug", "Trace all type assignements") val Ypmatdebug = BooleanSetting ("-Ypmat-debug", "Trace all pattern matcher activity.") val Yrepldebug = BooleanSetting ("-Yrepl-debug", "Trace all repl activity.") + val Ycompletion = BooleanSetting ("-Ycompletion-debug", "Trace all tab completion activity.") val Ypmatnaive = BooleanSetting ("-Ypmat-naive", "Desugar matches as naively as possible..") - val Ytailrec = BooleanSetting ("-Ytailrecommend", "Alert methods which would be tail-recursive if private or final.") val Yjenkins = BooleanSetting ("-Yjenkins-hashCodes", "Use jenkins hash algorithm for case class generated hashCodes.") // Warnings - val Ywarnfatal = BooleanSetting ("-Yfatal-warnings", "Fail the compilation if there are any warnings.") - val Xwarninit = BooleanSetting ("-Xwarninit", "Warn about possible changes in initialization semantics") - val Xchecknull = BooleanSetting ("-Xcheck-null", "Emit warning on selection of nullable reference") - val Xwarndeadcode = BooleanSetting ("-Ywarn-dead-code", "Emit warnings for dead code") - val YwarnShadow = BooleanSetting ("-Ywarn-shadowing", "Emit warnings about possible variable shadowing.") - val YwarnCatches = BooleanSetting ("-Ywarn-catches", "Emit warnings about catch blocks which catch everything.") - val Xwarnings = BooleanSetting ("-Xstrict-warnings", "Emit warnings about lots of things.") . - withPostSetHook(_ => - List(YwarnShadow, YwarnCatches, Xwarndeadcode, Xwarninit) foreach (_.value = true) - ) + val Ywarndeadcode = BooleanSetting ("-Ywarn-dead-code", "Emit warnings for dead code") + /** * "fsc-specific" settings. */ diff --git a/src/compiler/scala/tools/nsc/symtab/Definitions.scala b/src/compiler/scala/tools/nsc/symtab/Definitions.scala index a2382063c3..db48189b6b 100644 --- a/src/compiler/scala/tools/nsc/symtab/Definitions.scala +++ b/src/compiler/scala/tools/nsc/symtab/Definitions.scala @@ -449,7 +449,8 @@ trait Definitions extends reflect.generic.StandardDefinitions { lazy val BooleanBeanPropertyAttr: Symbol = getClass(sn.BooleanBeanProperty) lazy val AnnotationDefaultAttr: Symbol = { - val attr = newClass(RootClass, nme.AnnotationDefaultATTR, List(AnnotationClass.typeConstructor)) + val RuntimePackageClass = getModule("scala.runtime").tpe.typeSymbol + val attr = newClass(RuntimePackageClass, nme.AnnotationDefaultATTR, List(AnnotationClass.typeConstructor)) // This attribute needs a constructor so that modifiers in parsed Java code make sense attr.info.decls enter (attr newConstructor NoPosition setInfo MethodType(Nil, attr.tpe)) attr @@ -815,7 +816,7 @@ trait Definitions extends reflect.generic.StandardDefinitions { StringClass, "+", anyparam, stringtype) setFlag FINAL val forced = List( // force initialization of every symbol that is entered as a side effect - AnnotationDefaultAttr, + AnnotationDefaultAttr, // #2264 RepeatedParamClass, JavaRepeatedParamClass, ByNameParamClass, @@ -839,9 +840,6 @@ trait Definitions extends reflect.generic.StandardDefinitions { Object_asInstanceOf ) - // #2264 - var tmp = AnnotationDefaultAttr - tmp = RepeatedParamClass // force initialization if (forMSIL) { val intType = IntClass.typeConstructor val intParam = List(intType) diff --git a/src/compiler/scala/tools/nsc/symtab/StdNames.scala b/src/compiler/scala/tools/nsc/symtab/StdNames.scala index 5c7e7925ea..9133228768 100644 --- a/src/compiler/scala/tools/nsc/symtab/StdNames.scala +++ b/src/compiler/scala/tools/nsc/symtab/StdNames.scala @@ -95,6 +95,7 @@ trait StdNames extends reflect.generic.StdNames { self: SymbolTable => val SELECTOR_DUMMY = newTermName("<unapply-selector>") val MODULE_INSTANCE_FIELD = newTermName("MODULE$") + val SPECIALIZED_INSTANCE = newTermName("specInstance$") def isLocalName(name: Name) = name.endsWith(LOCAL_SUFFIX) def isSetterName(name: Name) = name.endsWith(SETTER_SUFFIX) @@ -122,6 +123,26 @@ trait StdNames extends reflect.generic.StdNames { self: SymbolTable => } else name } + /** Return the original name and the types on which this name + * is specialized. For example, + * {{{ + * splitSpecializedName("foo$mIcD$sp") == ('foo', "I", "D") + * }}} + * `foo$mIcD$sp` is the name of a method specialized on two type + * parameters, the first one belonging to the method itself, on Int, + * and another one belonging to the enclosing class, on Double. + */ + def splitSpecializedName(name: Name): (Name, String, String) = + if (name.endsWith("$sp")) { + val name1 = name.subName(0, name.length - 3) + val idxC = name1.lastPos('c') + val idxM = name1.lastPos('m', idxC) + (name1.subName(0, idxM - 1).toString, + name1.subName(idxC + 1, name1.length).toString, + name1.subName(idxM + 1, idxC).toString) + } else + (name, "", "") + def localToGetter(name: Name): Name = { assert(isLocalName(name))//debug name.subName(0, name.length - LOCAL_SUFFIX.length) diff --git a/src/compiler/scala/tools/nsc/symtab/Symbols.scala b/src/compiler/scala/tools/nsc/symtab/Symbols.scala index 9c6d6662e3..ebab5d9be7 100644 --- a/src/compiler/scala/tools/nsc/symtab/Symbols.scala +++ b/src/compiler/scala/tools/nsc/symtab/Symbols.scala @@ -606,6 +606,8 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => supersym == NoSymbol || supersym.isIncompleteIn(base) } + // Does not always work if the rawInfo is a SourcefileLoader, see comment + // in "def coreClassesFirst" in Global. final def exists: Boolean = this != NoSymbol && (!owner.isPackageClass || { rawInfo.load(this); rawInfo != NoType }) @@ -910,8 +912,11 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => else { val current = phase try { - while (phase.keepsTypeParams && (phase.prev ne NoPhase)) phase = phase.prev + while ((phase.prev ne NoPhase) && phase.prev.keepsTypeParams) phase = phase.prev +// while (phase.keepsTypeParams && (phase.prev ne NoPhase)) phase = phase.prev if (phase ne current) phase = phase.next + if (settings.debug.value && (phase ne current)) + log("checking unsafeTypeParams(" + this + ") at: " + current + " reading at: " + phase) rawInfo.typeParams } finally { phase = current @@ -1538,7 +1543,7 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => "package object "+owner.nameString else compose(List(kindString, - if (isClassConstructor) owner.simpleName+idString else nameString)) + if (isClassConstructor) owner.simpleName.decode+idString else nameString)) /** If owner is a package object, its owner, else the normal owner. */ @@ -1742,20 +1747,22 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => private var mtpePeriod = NoPeriod private var mtpePre: Type = _ private var mtpeResult: Type = _ + private var mtpeInfo: Type = _ override def cloneSymbolImpl(owner: Symbol): Symbol = new MethodSymbol(owner, pos, name).copyAttrsFrom(this) def typeAsMemberOf(pre: Type): Type = { if (mtpePeriod == currentPeriod) { - if (mtpePre eq pre) return mtpeResult + if ((mtpePre eq pre) && (mtpeInfo eq info)) return mtpeResult } else if (isValid(mtpePeriod)) { mtpePeriod = currentPeriod - if (mtpePre eq pre) return mtpeResult + if ((mtpePre eq pre) && (mtpeInfo eq info)) return mtpeResult } val res = pre.computeMemberType(this) mtpePeriod = currentPeriod mtpePre = pre + mtpeInfo = info mtpeResult = res res } diff --git a/src/compiler/scala/tools/nsc/symtab/Types.scala b/src/compiler/scala/tools/nsc/symtab/Types.scala index 863ad7bec4..8f1ece5b9e 100644 --- a/src/compiler/scala/tools/nsc/symtab/Types.scala +++ b/src/compiler/scala/tools/nsc/symtab/Types.scala @@ -1205,7 +1205,18 @@ trait Types extends reflect.generic.Types { self: SymbolTable => } else { incCounter(compoundBaseTypeSeqCount) baseTypeSeqCache = undetBaseTypeSeq - baseTypeSeqCache = memo(compoundBaseTypeSeq(this))(_.baseTypeSeq updateHead typeSymbol.tpe) + baseTypeSeqCache = if (typeSymbol.isRefinementClass) + memo(compoundBaseTypeSeq(this))(_.baseTypeSeq updateHead typeSymbol.tpe) + else + compoundBaseTypeSeq(this) + // [Martin] suppressing memo-ization solves the problem with "same type after erasure" errors + // when compiling with + // scalac scala.collection.IterableViewLike.scala scala.collection.IterableLike.scala + // I have not yet figured out precisely why this is the case. + // My current assumption is that taking memos forces baseTypeSeqs to be computed + // at stale types (i.e. the underlying typeSymbol has already another type). + // I do not yet see precisely why this would cause a problem, but it looks + // fishy in any case. } } //Console.println("baseTypeSeq(" + typeSymbol + ") = " + baseTypeSeqCache.toList);//DEBUG diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index c78664bc19..fc635874a6 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -876,6 +876,7 @@ abstract class ClassfileParser { val srcfileLeaf = pool.getName(in.nextChar).toString.trim val srcpath = sym.enclosingPackage match { case NoSymbol => srcfileLeaf + case definitions.EmptyPackage => srcfileLeaf case pkg => pkg.fullName(File.separatorChar)+File.separator+srcfileLeaf } srcfile0 = settings.outputDirs.srcFilesFor(in.file, srcpath).find(_.exists) diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 7169516560..ad88b783b4 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -1,4 +1,4 @@ -/* NSC -- new Scala compiler +/* NSC -- new Scala compiler * Copyright 2005-2010 LAMP/EPFL * @author */ @@ -25,12 +25,18 @@ abstract class Constructors extends Transform with ast.TreeDSL { new ConstructorTransformer(unit) class ConstructorTransformer(unit: CompilationUnit) extends Transformer { + import collection.mutable + + private val guardedCtorStats: mutable.Map[Symbol, List[Tree]] = new mutable.HashMap[Symbol, List[Tree]] def transformClassTemplate(impl: Template): Template = { val clazz = impl.symbol.owner // the transformed class val stats = impl.body // the transformed template body val localTyper = typer.atOwner(impl, clazz) + val specializedFlag: Symbol = clazz.info.decl(nme.SPECIALIZED_INSTANCE) + val shouldGuard = (specializedFlag != NoSymbol) && !clazz.hasFlag(SPECIALIZED) + var constr: DefDef = null // The primary constructor var constrParams: List[Symbol] = null // ... and its parameters var constrBody: Block = null // ... and its body @@ -68,6 +74,7 @@ abstract class Constructors extends Transform with ast.TreeDSL { } var thisRefSeen: Boolean = false + var usesSpecializedField: Boolean = false // A transformer for expressions that go into the constructor val intoConstructorTransformer = new Transformer { @@ -87,6 +94,8 @@ abstract class Constructors extends Transform with ast.TreeDSL { gen.mkAttributedIdent(parameter(tree.symbol)) setPos tree.pos case Select(_, _) => thisRefSeen = true + if (specializeTypes.specializedTypeVars(tree.symbol).nonEmpty) + usesSpecializedField = true super.transform(tree) case This(_) => thisRefSeen = true @@ -275,12 +284,106 @@ abstract class Constructors extends Transform with ast.TreeDSL { copyParam(acc, parameter(acc)) } + /** Return a single list of statements, merging the generic class constructor with the + * specialized stats. The original statements are retyped in the current class, and + * assignments to generic fields that have a corresponding specialized assignment in + * `specializedStats` are replaced by the specialized assignment. + */ + def mergeConstructors(genericClazz: Symbol, originalStats: List[Tree], specializedStats: List[Tree]): List[Tree] = { + val specBuf = new ListBuffer[Tree] + specBuf ++= specializedStats + + def specializedAssignFor(sym: Symbol): Option[Tree] = + specializedStats.find { + case Assign(sel @ Select(This(_), _), rhs) if sel.symbol.hasFlag(SPECIALIZED) => + val (generic, _, _) = nme.splitSpecializedName(nme.localToGetter(sel.symbol.name)) + generic == nme.localToGetter(sym.name) + case _ => false + } + + log("merging: " + originalStats.mkString("\n") + " : " + specializedStats.mkString("\n")) + val res = for (s <- originalStats; val stat = s.duplicate) yield { + log("merge: looking at " + stat) + val stat1 = stat match { + case Assign(sel @ Select(This(_), field), _) => + specializedAssignFor(sel.symbol).getOrElse(stat) + case _ => stat + } + if (stat1 ne stat) { + log("replaced " + stat + " with " + stat1) + specBuf -= stat1 + } + + if (stat1 eq stat) { + // statements coming from the original class need retyping in the current context + if (settings.debug.value) log("retyping " + stat1) + val d = new specializeTypes.Duplicator + d.retyped(localTyper.context1.asInstanceOf[d.Context], + stat1, + genericClazz, + clazz, + Map.empty) + } else + stat1 + } + if (specBuf.nonEmpty) + println("residual specialized constructor statements: " + specBuf) + res + } + + /** Add an 'if' around the statements coming after the super constructor. This + * guard is necessary if the code uses specialized fields. A specialized field is + * initialized in the subclass constructor, but the accessors are (already) overridden + * and pointing to the (empty) fields. To fix this, a class with specialized fields + * will not run its constructor statements if the instance is specialized. The specialized + * subclass includes a copy of those constructor statements, and runs them. To flag that a class + * has specialized fields, and their initialization should be deferred to the subclass, method + * 'specInstance$' is added in phase specialize. + */ + def guardSpecializedInitializer(stats0: List[Tree]): List[Tree] = if (settings.nospecialization.value) stats0 else { + // split the statements in presuper and postsuper + var (prefix, postfix) = stats0.span(tree => !((tree.symbol ne null) && tree.symbol.isConstructor)) + if (postfix.nonEmpty) { + prefix = prefix :+ postfix.head + postfix = postfix.tail + } + + if (usesSpecializedField && shouldGuard && postfix.nonEmpty) { + // save them for duplication in the specialized subclass + guardedCtorStats(clazz) = postfix + + val tree = + If( + Apply( + Select( + Apply(gen.mkAttributedRef(specializedFlag), List()), + definitions.getMember(definitions.BooleanClass, nme.UNARY_!)), + List()), + Block(postfix, Literal(())), + EmptyTree) + + prefix ::: List(localTyper.typed(tree)) + } else if (clazz.hasFlag(SPECIALIZED)) { + // add initialization from its generic class constructor + val (genericName, _, _) = nme.splitSpecializedName(clazz.name) + val genericClazz = clazz.owner.info.decl(genericName.toTypeName) + assert(genericClazz != NoSymbol) + + guardedCtorStats.get(genericClazz) match { + case Some(stats1) => + val merged = mergeConstructors(genericClazz, stats1, postfix) + prefix ::: merged + case None => stats0 + } + } else stats0 + } + // Assemble final constructor defBuf += treeCopy.DefDef( constr, constr.mods, constr.name, constr.tparams, constr.vparamss, constr.tpt, treeCopy.Block( constrBody, - paramInits ::: constrPrefixBuf.toList ::: constrStatBuf.toList, + paramInits ::: constrPrefixBuf.toList ::: guardSpecializedInitializer(constrStatBuf.toList), constrBody.expr)); // Unlink all fields that can be dropped from class scope diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 94d52bc1cb..88a7e13d80 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -152,7 +152,7 @@ abstract class Erasure extends AddInterfaces with typechecker.Analyzer with ast. if (unboundedGenericArrayLevel(tp) == 1) ObjectClass.tpe else if (args.head.typeSymbol == NothingClass || args.head.typeSymbol == NullClass) arrayType(ObjectClass.tpe) else typeRef(apply(pre), sym, args map this) - else if (sym == AnyClass || sym == AnyValClass || sym == SingletonClass) erasedTypeRef(ObjectClass) + else if (sym == AnyClass || sym == AnyValClass || sym == SingletonClass || sym == NotNullClass) erasedTypeRef(ObjectClass) else if (sym == UnitClass) erasedTypeRef(BoxedUnitClass) else if (sym.isRefinementClass) apply(intersectionDominator(tp.parents)) else if (sym.isClass) typeRef(apply(rebindInnerClass(pre, sym)), sym, List()) // #2585 diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index d5d7ca254e..1ab310282d 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -119,6 +119,8 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { * type bounds of other @specialized type parameters (and not in its result type). */ def degenerate = false + + def isAccessor = false } /** Symbol is a special overloaded method of 'original', in the environment env. */ @@ -132,11 +134,16 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } /** Symbol is a specialized accessor for the `target' field. */ - case class SpecializedAccessor(target: Symbol) extends SpecializedInfo + case class SpecializedAccessor(target: Symbol) extends SpecializedInfo { + override def isAccessor = true + } /** Symbol is a specialized method whose body should be the target's method body. */ case class Implementation(target: Symbol) extends SpecializedInfo + /** Symbol is a specialized override paired with `target'. */ + case class SpecialOverride(target: Symbol) extends SpecializedInfo + /** An Inner class that specializes on a type parameter of the enclosing class. */ case class SpecializedInnerClass(target: Symbol, env: TypeEnv) extends SpecializedInfo @@ -217,18 +224,6 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { * specialization on method type parameters, the second on outer environment. */ private def specializedName(name: Name, types1: List[Type], types2: List[Type]): Name = { - def split: (String, String, String) = { - if (name.endsWith("$sp")) { - val name1 = name.subName(0, name.length - 3) - val idxC = name1.lastPos('c') - val idxM = name1.lastPos('m', idxC) - (name1.subName(0, idxM - 1).toString, - name1.subName(idxC + 1, name1.length).toString, - name1.subName(idxM + 1, idxC).toString) - } else - (name.toString, "", "") - } - if (nme.INITIALIZER == name || (types1.isEmpty && types2.isEmpty)) name else if (nme.isSetterName(name)) @@ -236,8 +231,8 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { else if (nme.isLocalName(name)) nme.getterToLocal(specializedName(nme.localToGetter(name), types1, types2)) else { - val (base, cs, ms) = split - newTermName(base + "$" + val (base, cs, ms) = nme.splitSpecializedName(name) + newTermName(base.toString + "$" + "m" + ms + types1.map(t => definitions.abbrvTag(t.typeSymbol)).mkString("", "", "") + "c" + cs + types2.map(t => definitions.abbrvTag(t.typeSymbol)).mkString("", "", "$sp")) } @@ -319,21 +314,20 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { })) - private def specializedTypeVars(tpe: List[Type]): immutable.Set[Symbol] = + def specializedTypeVars(tpe: List[Type]): immutable.Set[Symbol] = tpe.foldLeft(immutable.ListSet.empty[Symbol]: immutable.Set[Symbol]) { (s, tp) => s ++ specializedTypeVars(tp) } - private def specializedTypeVars(sym: Symbol): immutable.Set[Symbol] = + def specializedTypeVars(sym: Symbol): immutable.Set[Symbol] = specializedTypeVars(atPhase(currentRun.typerPhase)(sym.info)) /** Return the set of @specialized type variables mentioned by the given type. */ - private def specializedTypeVars(tpe: Type): immutable.Set[Symbol] = tpe match { + def specializedTypeVars(tpe: Type): immutable.Set[Symbol] = tpe match { case TypeRef(pre, sym, args) => if (sym.isTypeParameter && sym.hasAnnotation(SpecializedClass)) specializedTypeVars(args) + sym else if (sym.isTypeSkolem && sym.deSkolemize.hasAnnotation(SpecializedClass)) { - println("cought skolem without @specialized") specializedTypeVars(args) + sym } else specializedTypeVars(args) @@ -680,48 +674,51 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { */ private def specialOverrides(clazz: Symbol): List[Symbol] = { log("specialOverrides(" + clazz + ")") - val opc = new overridingPairs.Cursor(clazz) val oms = new mutable.ListBuffer[Symbol] - while (opc.hasNext) { + for (overriding <- clazz.info.decls; + val allOverridden = overriding.allOverriddenSymbols + if !allOverridden.isEmpty; + val overridden = allOverridden.head) { if (settings.debug.value) - log("\toverriding pairs: " + opc.overridden.fullName + ": " + opc.overridden.info - + " overriden by " + opc.overriding.fullName + ": " + opc.overriding.info) - if (opc.overriding.owner == clazz && !specializedTypeVars(opc.overridden.info).isEmpty) { - if (settings.debug.value) log("\t\tspecializedTVars: " + specializedTypeVars(opc.overridden.info)) - val env = unify(opc.overridden.info, opc.overriding.info, emptyEnv) + log("\toverriding pairs: " + overridden.fullName + ": " + overridden.info + + " overriden by " + overriding.fullName + ": " + overriding.info) + if (overriding.owner == clazz && !specializedTypeVars(overridden.info).isEmpty) { + if (settings.debug.value) log("\t\tspecializedTVars: " + specializedTypeVars(overridden.info)) + val env = unify(overridden.info, overriding.info, emptyEnv) if (settings.debug.value) log("\t\tenv: " + env + "isValid: " - + TypeEnv.isValid(env, opc.overridden) - + " exists: " + opc.overridden.owner.info.decl(specializedName(opc.overridden, env))) + + TypeEnv.isValid(env, overridden) + + " looking for: " + specializedName(overridden, env) + " in:\n" + + atPhase(phase.next)(overridden.owner.info.decls) + + "found: " + atPhase(phase.next)(overridden.owner.info.decl(specializedName(overridden, env)))) if (!env.isEmpty - && TypeEnv.isValid(env, opc.overridden) - && opc.overridden.owner.info.decl(specializedName(opc.overridden, env)) != NoSymbol) { - log("Added specialized overload for " + opc.overriding.fullName + " in env: " + env) - val om = specializedOverload(clazz, opc.overridden, env) + && TypeEnv.isValid(env, overridden) + && atPhase(phase.next)(overridden.owner.info.decl(specializedName(overridden, env))) != NoSymbol) { + log("Added specialized overload for " + overriding.fullName + " in env: " + env) + val om = specializedOverload(clazz, overridden, env) typeEnv(om) = env - if (!opc.overriding.isDeferred) { - concreteSpecMethods += opc.overriding + if (!overriding.isDeferred) { + concreteSpecMethods += overriding // if the override is a normalized member, 'om' gets the implementation from // its original target, and adds the environment of the normalized member (that is, // any specialized /method/ type parameter bindings) - info(om) = info.get(opc.overriding) match { + info(om) = info.get(overriding) match { case Some(NormalizedMember(target)) => - typeEnv(om) = env ++ typeEnv(opc.overriding) - Implementation(target) - case _ => Implementation(opc.overriding) + typeEnv(om) = env ++ typeEnv(overriding) + SpecialOverride(target) + case _ => SpecialOverride(overriding) } - info(opc.overriding) = Forward(om) + info(overriding) = Forward(om) log("typeEnv(om) = " + typeEnv(om)) - om setPos opc.overriding.pos // set the position of the concrete, overriding member + om setPos overriding.pos // set the position of the concrete, overriding member } - overloads(opc.overriding) = Overload(om, env) :: overloads(opc.overriding) + overloads(overriding) = Overload(om, env) :: overloads(overriding) oms += om atPhase(phase.next)( - assert(opc.overridden.owner.info.decl(om.name) != NoSymbol, - "Could not find " + om.name + " in " + opc.overridden.owner.info.decls)) + assert(overridden.owner.info.decl(om.name) != NoSymbol, + "Could not find " + om.name + " in " + overridden.owner.info.decls)) } } - opc.next } oms.toList } @@ -827,13 +824,19 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { case PolyType(targs, ClassInfoType(base, decls, clazz)) => val parents = base map specializedType log("transformInfo (poly) " + clazz + " with parents1: " + parents + " ph: " + phase) - PolyType(targs, ClassInfoType(parents, new Scope(specializeClass(clazz, typeEnv(clazz))), clazz)) +// if (clazz.name.toString == "$colon$colon") +// (new Throwable).printStackTrace + PolyType(targs, ClassInfoType(parents, + new Scope(specializeClass(clazz, typeEnv(clazz)) ::: specialOverrides(clazz)), + clazz)) case ClassInfoType(base, decls, clazz) if !clazz.isPackageClass => atPhase(phase.next)(base.map(_.typeSymbol.info)) val parents = base map specializedType log("transformInfo " + clazz + " with parents1: " + parents + " ph: " + phase) - val res = ClassInfoType(base map specializedType, new Scope(specializeClass(clazz, typeEnv(clazz))), clazz) + val res = ClassInfoType(base map specializedType, + new Scope(specializeClass(clazz, typeEnv(clazz)) ::: specialOverrides(clazz)), + clazz) res case _ => @@ -893,16 +896,12 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { satisfiable(env, silent) } - import java.io.PrintWriter - /*************************** Term transformation ************************************/ class Duplicator extends { val global: SpecializeTypes.this.global.type = SpecializeTypes.this.global } with typechecker.Duplicators - import global.typer.typed - def specializeCalls(unit: CompilationUnit) = new TypingTransformer(unit) { /** Map a specializable method to it's rhs, when not deferred. */ val body: mutable.Map[Symbol, Tree] = new mutable.HashMap @@ -928,8 +927,6 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } } - import posAssigner._ - override def transform(tree: Tree): Tree = { val symbol = tree.symbol @@ -1036,7 +1033,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { case (tpe, idx) => TypeTree(tpe) setPos parents(idx).pos } treeCopy.Template(tree, - parents1 /*currentOwner.info.parents.map(tpe => TypeTree(tpe) setPos parents.head.pos)*/, + parents1 /*currentOwner.info.parents.map(tpe => TypeTree(tpe) setPos parents.head.pos)*/ , self, atOwner(currentOwner)(transformTrees(body ::: specMembers))) @@ -1091,6 +1088,18 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { treeCopy.DefDef(tree1, mods, name, tparams, vparamss, tpt, transform(rhs)) } + case SpecialOverride(target) => + assert(body.isDefinedAt(target), "sym: " + symbol.fullName + " target: " + target.fullName) + if (settings.debug.value) log("moving implementation: " + body(target)) + // we have an rhs, specialize it + val tree1 = addBody(ddef, target) + (new ChangeOwnerTraverser(target, tree1.symbol))(tree1.rhs) + if (settings.debug.value) + println("changed owners, now: " + tree1) + val DefDef(mods, name, tparams, vparamss, tpt, rhs) = tree1 + treeCopy.DefDef(tree1, mods, name, tparams, vparamss, tpt, transform(rhs)) + + case SpecialOverload(original, env) => log("completing specialized " + symbol.fullName + " calling " + original) val t = DefDef(symbol, { vparamss => @@ -1118,7 +1127,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { localTyper.typed(treeCopy.DefDef(tree, mods, name, tparams, vparamss, tpt, rhs1)) } - case ValDef(mods, name, tpt, rhs) if symbol.hasFlag(SPECIALIZED) => + case ValDef(mods, name, tpt, rhs) if symbol.hasFlag(SPECIALIZED) && !symbol.hasFlag(PARAMACCESSOR) => assert(body.isDefinedAt(symbol.alias)) val tree1 = treeCopy.ValDef(tree, mods, name, tpt, body(symbol.alias).duplicate) if (settings.debug.value) log("now typing: " + tree1 + " in " + tree.symbol.owner.fullName) @@ -1128,8 +1137,20 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { symbol.alias.enclClass, symbol.enclClass, typeEnv(symbol.alias) ++ typeEnv(tree.symbol)) +// val tree1 = +// treeCopy.ValDef(tree, mods, name, tpt, +// localTyper.typed( +// Apply(Select(Super(currentClass, nme.EMPTY), symbol.alias.getter(symbol.alias.owner)), +// List()))) +// if (settings.debug.value) log("replaced ValDef: " + tree1 + " in " + tree.symbol.owner.fullName) +// tree1 + + case Apply(sel @ Select(sup @ Super(qual, name), name1), args) + if (sup.symbol.info.parents != atPhase(phase.prev)(sup.symbol.info.parents)) => + + def parents = sup.symbol.info.parents + log(tree + " parents changed from: " + atPhase(phase.prev)(parents) + " to: " + parents) - case Apply(sel @ Select(sup @ Super(qual, name), name1), args) => val res = localTyper.typed( Apply(Select(Super(qual, name) setPos sup.pos, name1) setPos sel.pos, transformTrees(args)) setPos tree.pos) log("retyping call to super, from: " + symbol + " to " + res.symbol) @@ -1146,11 +1167,31 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { (tparams1, tparams map (_.symbol)) } - private def duplicateBody(tree: DefDef, target: Symbol): Tree = { + + private def duplicateBody(tree: DefDef, source: Symbol) = { + val symbol = tree.symbol + val meth = addBody(tree, source) + log("now typing: " + meth + " in " + symbol.owner.fullName) + val d = new Duplicator + d.retyped(localTyper.context1.asInstanceOf[d.Context], + meth, + source.enclClass, + symbol.enclClass, + typeEnv(source) ++ typeEnv(symbol)) + } + + + /** Put the body of 'source' as the right hand side of the method 'tree'. + * The destination method gets fresh symbols for type and value parameters, + * and the body is updated to the new symbols, and owners adjusted accordingly. + * However, if the same source tree is used in more than one place, full re-typing + * is necessary. @see method duplicateBody + */ + private def addBody(tree: DefDef, source: Symbol): DefDef = { val symbol = tree.symbol if (settings.debug.value) log("specializing body of" + symbol.fullName + ": " + symbol.info) val DefDef(mods, name, tparams, vparamss, tpt, _) = tree - val (_, origtparams) = splitParams(target.typeParams) + val (_, origtparams) = splitParams(source.typeParams) if (settings.debug.value) log("substituting " + origtparams + " for " + symbol.typeParams) // skolemize type parameters @@ -1163,21 +1204,12 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { // replace value and type parameters of the old method with the new ones val symSubstituter = new ImplementationAdapter( - parameters(target).flatten ::: origtparams, + parameters(source).flatten ::: origtparams, vparamss1.flatten.map(_.symbol) ::: newtparams) - val adapter = new AdaptSpecializedValues - val tmp = symSubstituter(adapter(body(target).duplicate)) + val tmp = symSubstituter(body(source).duplicate) tpt.tpe = tpt.tpe.substSym(oldtparams, newtparams) - val meth = treeCopy.DefDef(tree, mods, name, tparams, vparamss1, tpt, tmp) - - log("now typing: " + meth + " in " + symbol.owner.fullName) - val d = new Duplicator - d.retyped(localTyper.context1.asInstanceOf[d.Context], - meth, - target.enclClass, - symbol.enclClass, - typeEnv(target) ++ typeEnv(symbol)) + treeCopy.DefDef(tree, mods, name, tparams, vparamss1, tpt, tmp) } /** A tree symbol substituter that substitutes on type skolems. @@ -1201,10 +1233,12 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { */ override def transform(tree: Tree): Tree = tree match { case Select(qual, name) => - if (tree.symbol.hasFlag(PRIVATE | PROTECTED)) { - log("changing private flag of " + tree.symbol + " privateWithin: " + tree.symbol.privateWithin) + val sym = tree.symbol + if (sym.hasFlag(PRIVATE | PROTECTED) && !nme.isLocalName(sym.name) + && (currentClass != sym.owner.enclClass)) { + log("changing private flag of " + sym) // tree.symbol.resetFlag(PRIVATE).setFlag(PROTECTED) - tree.symbol.makeNotPrivate(tree.symbol.owner) + sym.makeNotPrivate(sym.owner) // tree.symbol.resetFlag(PRIVATE | PROTECTED) // tree.symbol.privateWithin = NoSymbol } @@ -1215,36 +1249,6 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } } - /** Does the given tree need a cast to a type parameter's upper bound? - * A cast is needed for values of type A, where A is a specialized type - * variable with a non-trivial upper bound. When A is specialized, its - * specialization may not satisfy the upper bound. We generate casts to - * be able to type check code. Such methods will never be called, as they - * are not visible to the user. The compiler will insert such calls only when - * the bounds are satisfied. - */ - private class AdaptSpecializedValues extends Transformer { - private def needsCast(tree: Tree): Boolean = { - val sym = tree.tpe.typeSymbol - (sym.isTypeParameterOrSkolem - && sym.hasAnnotation(SpecializedClass) - && sym.info.bounds.hi != definitions.AnyClass.tpe - /*&& !(tree.tpe <:< sym.info.bounds.hi)*/) - } - - override def transform(tree: Tree): Tree = { - val tree1 = super.transform(tree) - if (needsCast(tree1)) { -// log("inserting cast for " + tree1 + " tpe: " + tree1.tpe) -// val tree2 = gen.mkAsInstanceOf(tree1, tree1.tpe.typeSymbol.info.bounds.hi) -// log(" casted to: " + tree2) - tree1 - } else - tree1 - } - def apply(t: Tree): Tree = transform(t) - } - def warn(clazz: Symbol)(pos: Position, err: String) = if (!clazz.hasFlag(SPECIALIZED)) unit.warning(pos, err) @@ -1254,9 +1258,10 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { */ private def makeSpecializedMembers(cls: Symbol): List[Tree] = { // add special overrides first - if (!cls.hasFlag(SPECIALIZED)) - for (m <- specialOverrides(cls)) cls.info.decls.enter(m) +// if (!cls.hasFlag(SPECIALIZED)) +// for (m <- specialOverrides(cls)) cls.info.decls.enter(m) val mbrs = new mutable.ListBuffer[Tree] + var hasSpecializedFields = false for (m <- cls.info.decls.toList if m.hasFlag(SPECIALIZED) @@ -1264,6 +1269,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { && satisfiable(typeEnv(m), warn(cls))) { log("creating tree for " + m.fullName) if (m.isMethod) { + if (info(m).target.isGetterOrSetter) hasSpecializedFields = true if (m.isClassConstructor) { val origParamss = parameters(info(m).target) assert(origParamss.length == 1) // we are after uncurry @@ -1294,6 +1300,14 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { // log("created synthetic class: " + m.fullName) } } + if (hasSpecializedFields) { + val sym = cls.newMethod(nme.SPECIALIZED_INSTANCE, cls.pos) + .setInfo(MethodType(Nil, definitions.BooleanClass.tpe)) + cls.info.decls.enter(sym) + mbrs += atPos(sym.pos) { + DefDef(sym, Literal(cls.hasFlag(SPECIALIZED)).setType(sym.tpe.finalResultType)).setType(NoType) + } + } mbrs.toList } @@ -1372,6 +1386,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } class SpecializationTransformer(unit: CompilationUnit) extends Transformer { + log("specializing " + unit) override def transform(tree: Tree) = atPhase(phase.next) { val res = specializeCalls(unit).transform(tree) diff --git a/src/compiler/scala/tools/nsc/transform/TailCalls.scala b/src/compiler/scala/tools/nsc/transform/TailCalls.scala index adeab550ee..9fff8534d3 100644 --- a/src/compiler/scala/tools/nsc/transform/TailCalls.scala +++ b/src/compiler/scala/tools/nsc/transform/TailCalls.scala @@ -39,9 +39,6 @@ abstract class TailCalls extends Transform } } - /** The @tailrec annotation indicates TCO is mandatory */ - private def tailrecRequired(defdef: DefDef) = defdef.symbol hasAnnotation TailrecClass - /** * A Tail Call Transformer * @@ -105,6 +102,9 @@ abstract class TailCalls extends Transform /** Tells whether we are in a (possible) tail position */ var tailPos = false + /** The reason this method could not be optimized. */ + var tailrecFailReason = "it contains a recursive call not in tail position" + /** Is the label accessed? */ var accessed = false @@ -138,7 +138,8 @@ abstract class TailCalls extends Transform t } - private var ctx: Context = new Context() + private var ctx: Context = new Context() + private def enclosingType = ctx.currentMethod.enclClass.typeOfThis /** Rewrite this tree to contain no tail recursive calls */ def transform(tree: Tree, nctx: Context): Tree = { @@ -150,11 +151,50 @@ abstract class TailCalls extends Transform } override def transform(tree: Tree): Tree = { + /** A possibly polymorphic apply to be considered for tail call transformation. + */ + def rewriteApply(target: Tree, fun: Tree, targs: List[Tree], args: List[Tree]) = { + def isRecursiveCall = ctx.currentMethod eq fun.symbol + def isMandatory = ctx.currentMethod hasAnnotation TailrecClass + def isEligible = ctx.currentMethod.isEffectivelyFinal + def transformArgs = transformTrees(args, mkContext(ctx, false)) + def matchesTypeArgs = ctx.tparams sameElements (targs map (_.tpe.typeSymbol)) + def defaultTree = treeCopy.Apply(tree, target, transformArgs) + + def sameTypeOfThis(receiver: Tree) = + receiver.tpe.widen =:= enclosingType.widen + + /** Records failure reason in Context for reporting. + */ + def cannotRewrite(reason: String) = { + if (isMandatory) + ctx.tailrecFailReason = reason + + defaultTree + } + def rewriteTailCall(receiver: Tree, otherArgs: List[Tree]): Tree = { + log("Rewriting tail recursive method call at: " + fun.pos) + + ctx.accessed = true + typed { atPos(fun.pos)(Apply(Ident(ctx.label), receiver :: otherArgs)) } + } + + if (!isRecursiveCall) defaultTree + else if (!isEligible) cannotRewrite("it is neither private nor final so can be overridden") + else if (!ctx.tailPos) cannotRewrite("it contains a recursive call not in tail position") + else if (!matchesTypeArgs) cannotRewrite("it is called recursively with different type arguments") + else fun match { + case Select(_, _) if forMSIL => cannotRewrite("it cannot be optimized on MSIL") + case Select(qual, _) if !sameTypeOfThis(qual) => cannotRewrite("it changes type of 'this' on a polymorphic recursive call") + case Select(qual, _) => rewriteTailCall(qual, transformArgs) + case _ => rewriteTailCall(This(currentClass), transformArgs) + } + } + tree match { case dd @ DefDef(mods, name, tparams, vparams, tpt, rhs) => log("Entering DefDef: " + name) - var isTransformed = false val newCtx = mkContext(ctx) newCtx.currentMethod = tree.symbol newCtx.makeLabel() @@ -162,16 +202,10 @@ abstract class TailCalls extends Transform newCtx.label.setInfo(MethodType(currentClassParam :: tree.symbol.tpe.params, tree.symbol.tpe.finalResultType)) newCtx.tailPos = true - val isEligible = newCtx.currentMethod.isEffectivelyFinal || (newCtx.currentMethod.enclClass hasFlag Flags.MODULE) - // If -Ytailrecommend is given, we speculatively try transforming ineligible methods and - // report where we would have been successful. - val recommend = settings.Ytailrec.value - val savedFlags: Option[Long] = if (recommend) Some(newCtx.currentMethod.flags) else None - - if (isEligible || recommend) { - if (recommend) - newCtx.currentMethod.flags |= Flags.FINAL + val isEligible = newCtx.currentMethod.isEffectivelyFinal + val isMandatory = dd.symbol.hasAnnotation(TailrecClass) && !forMSIL // @tailrec annotation indicates mandatory transformation + if (isEligible) { newCtx.tparams = Nil log(" Considering " + name + " for tailcalls") tree.symbol.tpe match { @@ -185,12 +219,10 @@ abstract class TailCalls extends Transform val t1 = treeCopy.DefDef(tree, mods, name, tparams, vparams, tpt, { val transformed = transform(rhs, newCtx) - savedFlags foreach (newCtx.currentMethod.flags = _) transformed match { case newRHS if isEligible && newCtx.accessed => log("Rewrote def " + newCtx.currentMethod) - isTransformed = true val newThis = newCtx.currentMethod . newValue (tree.pos, nme.THIS) . setInfo (currentClass.typeOfThis) @@ -200,19 +232,14 @@ abstract class TailCalls extends Transform List(ValDef(newThis, This(currentClass))), LabelDef(newCtx.label, newThis :: (vparams.flatten map (_.symbol)), newRHS) ))) - case _ if recommend => - if (newCtx.accessed) - unit.warning(dd.pos, "method is tailrecommended") - // transform with the original flags restored - transform(rhs, newCtx) + case rhs => + if (isMandatory) + unit.error(dd.pos, "could not optimize @tailrec annotated method: " + newCtx.tailrecFailReason) - case rhs => rhs + rhs } }) - if (!forMSIL && !isTransformed && tailrecRequired(dd)) - unit.error(dd.pos, "could not optimize @tailrec annotated method") - log("Leaving DefDef: " + name) t1 @@ -269,50 +296,16 @@ abstract class TailCalls extends Transform case Typed(expr, tpt) => super.transform(tree) case Apply(tapply @ TypeApply(fun, targs), vargs) => - lazy val defaultTree = treeCopy.Apply(tree, tapply, transformTrees(vargs, mkContext(ctx, false))) - if ( ctx.currentMethod.isEffectivelyFinal && - ctx.tailPos && - isSameTypes(ctx.tparams, targs map (_.tpe.typeSymbol)) && - isRecursiveCall(fun)) { - fun match { - case Select(receiver, _) => - val recTpe = receiver.tpe.widen - val enclTpe = ctx.currentMethod.enclClass.typeOfThis - // make sure the type of 'this' doesn't change through this polymorphic recursive call - if (!forMSIL && - (receiver.tpe.typeParams.isEmpty || - (receiver.tpe.widen == ctx.currentMethod.enclClass.typeOfThis))) - rewriteTailCall(fun, receiver :: transformTrees(vargs, mkContext(ctx, false))) - else - defaultTree - case _ => rewriteTailCall(fun, This(currentClass) :: transformTrees(vargs, mkContext(ctx, false))) - } - } else - defaultTree + rewriteApply(tapply, fun, targs, vargs) case TypeApply(fun, args) => super.transform(tree) - case Apply(fun, args) if (fun.symbol == definitions.Boolean_or || - fun.symbol == definitions.Boolean_and) => - treeCopy.Apply(tree, fun, transformTrees(args)) - case Apply(fun, args) => - lazy val defaultTree = treeCopy.Apply(tree, fun, transformTrees(args, mkContext(ctx, false))) - if (ctx.currentMethod.isEffectivelyFinal && - ctx.tailPos && - isRecursiveCall(fun)) { - fun match { - case Select(receiver, _) => - if (!forMSIL) - rewriteTailCall(fun, receiver :: transformTrees(args, mkContext(ctx, false))) - else - defaultTree - case _ => rewriteTailCall(fun, This(currentClass) :: transformTrees(args, mkContext(ctx, false))) - } - } else - defaultTree - + if (fun.symbol == Boolean_or || fun.symbol == Boolean_and) + treeCopy.Apply(tree, fun, transformTrees(args)) + else + rewriteApply(fun, fun, Nil, args) case Super(qual, mix) => tree @@ -333,28 +326,5 @@ abstract class TailCalls extends Transform def transformTrees(trees: List[Tree], nctx: Context): List[Tree] = trees map ((tree) => transform(tree, nctx)) - - private def rewriteTailCall(fun: Tree, args: List[Tree]): Tree = { - log("Rewriting tail recursive method call at: " + - (fun.pos)) - ctx.accessed = true - //println("fun: " + fun + " args: " + args) - val t = atPos(fun.pos)(Apply(Ident(ctx.label), args)) - // println("TAIL: "+t) - typed(t) - } - - private def isSameTypes(ts1: List[Symbol], ts2: List[Symbol]) = ts1 sameElements ts2 - - /** Returns <code>true</code> if the fun tree refers to the same method as - * the one saved in <code>ctx</code>. - * - * @param fun the expression that is applied - * @return <code>true</code> if the tree symbol refers to the innermost - * enclosing method - */ - private def isRecursiveCall(fun: Tree): Boolean = - (fun.symbol eq ctx.currentMethod) } - } diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index e339560837..bad98193b0 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -659,11 +659,6 @@ abstract class UnCurry extends InfoTransform with TypingTransformers { } treeCopy.DefDef(tree, mods, name, tparams, List(vparamss.flatten), tpt, rhs1) case Try(body, catches, finalizer) => - // If warnings are enabled, alert about promiscuously catching cases. - if (settings.YwarnCatches.value) - for (cd <- catches find treeInfo.catchesThrowable) - unit.warning(cd.pos, "catch clause swallows everything: not advised.") - if (catches forall treeInfo.isCatchCase) tree else { val exname = unit.fresh.newName(tree.pos, "ex$") diff --git a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala index 635608520d..70bf55e661 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala @@ -22,6 +22,7 @@ trait Analyzer extends AnyRef with SyntheticMethods with Unapplies with NamesDefaults + with TypeDiagnostics { val global : Global import global._ diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index becb4b069f..8758dd834c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -256,23 +256,25 @@ trait Contexts { self: Analyzer => if (diagnostic.isEmpty) "" else diagnostic.mkString("\n","\n", "") - def error(pos: Position, err: Throwable) { - val msg = err.getMessage() + diagString - if (reportGeneralErrors) - unit.error(pos, if (checking) "**** ERROR DURING INTERNAL CHECKING ****\n" + msg else msg) - else - throw err + private def addDiagString(msg: String) = { + val ds = diagString + if (msg endsWith ds) msg else msg + ds } - def error(pos: Position, msg: String) { - val msg1 = msg + diagString - if (reportGeneralErrors) - unit.error(pos, if (checking) "**** ERROR DURING INTERNAL CHECKING ****\n" + msg1 else msg1) - else - throw new TypeError(pos, msg1) + private def unitError(pos: Position, msg: String) = + unit.error(pos, if (checking) "**** ERROR DURING INTERNAL CHECKING ****\n" + msg else msg) + + def error(pos: Position, err: Throwable) = + if (reportGeneralErrors) unitError(pos, addDiagString(err.getMessage())) + else throw err + + def error(pos: Position, msg: String) = { + val msg1 = addDiagString(msg) + if (reportGeneralErrors) unitError(pos, msg1) + else throw new TypeError(pos, msg1) } - def warning(pos: Position, msg: String) { + def warning(pos: Position, msg: String) = { if (reportGeneralErrors) unit.warning(pos, msg) } @@ -484,6 +486,19 @@ trait Contexts { self: Analyzer => } implicitsCache } + + def lookup(name: Name, expectedOwner: Symbol) = { + var res: Symbol = NoSymbol + var ctx = this + while(res == NoSymbol && ctx.outer != ctx) { + val s = ctx.scope.lookup(name) + if (s != NoSymbol && s.owner == expectedOwner) + res = s + else + ctx = ctx.outer + } + res + } } class ImportInfo(val tree: Import, val depth: Int) { /** The prefix expression */ diff --git a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala index 1dbeb0afd9..de9318d6b6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala @@ -94,6 +94,15 @@ abstract class Duplicators extends Analyzer { } else super.mapOver(tpe) + case ThisType(sym) => + val sym1 = updateSym(sym) + if (sym1 ne sym) { + log("fixing " + sym + " -> " + sym1) + ThisType(sym1) + } else + super.mapOver(tpe) + + case _ => super.mapOver(tpe) } @@ -136,7 +145,7 @@ abstract class Duplicators extends Analyzer { ldef.symbol = newsym log("newsym: " + newsym + " info: " + newsym.info) - case DefDef(_, _, tparams, vparamss, _, rhs) => + case DefDef(_, name, tparams, vparamss, _, rhs) => // invalidate parameters invalidate(tparams ::: vparamss.flatten) tree.symbol = NoSymbol @@ -244,6 +253,11 @@ abstract class Duplicators extends Analyzer { if (settings.debug.value) log("mapped " + tree + " to " + tree1) super.typed(atPos(tree.pos)(tree1), mode, pt) + case This(_) => + tree.symbol = updateSym(tree.symbol) + tree.tpe = null + super.typed(tree, mode, pt) + case Super(qual, mix) if (oldClassOwner ne null) && (tree.symbol == oldClassOwner) => val tree1 = Super(qual, mix) log("changed " + tree + " to " + tree1) diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 4c07e50da9..9a6c4cc401 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -639,9 +639,9 @@ self: Analyzer => val applicable = applicableInfos(implicitInfoss, isLocal, invalidImplicits) if (applicable.isEmpty && !invalidImplicits.isEmpty) { - infer.setAddendum(tree.pos, () => + setAddendum(tree.pos, () => "\n Note: implicit "+invalidImplicits.head+" is not applicable here"+ - "\n because it comes after the application point and it lacks an explicit result type") + " because it comes after the application point and it lacks an explicit result type") } val start = startCounter(subtypeImprovCount) @@ -784,7 +784,6 @@ self: Analyzer => inferImplicit(tree, appliedType(manifestClass.typeConstructor, List(tp)), true, false, context).tree def findSubManifest(tp: Type) = findManifest(tp, if (full) FullManifestClass else OptManifestClass) - def findElemManifest(tp: Type) = findManifest(tp, if (full) FullManifestClass else PartialManifestClass) def mot(tp0: Type): Tree = { val tp1 = tp0.normalize @@ -801,39 +800,26 @@ self: Analyzer => } else if (sym == RepeatedParamClass || sym == ByNameParamClass) { EmptyTree } else if (sym == ArrayClass && args.length == 1) { - manifestFactoryCall("arrayType", args.head, findElemManifest(args.head)) + manifestFactoryCall("arrayType", args.head, findManifest(args.head)) } else if (sym.isClass) { - val suffix = gen.mkClassOf(tp1) :: (args map findSubManifest) + val classarg0 = gen.mkClassOf(tp1) + val classarg = tp match { + case ExistentialType(_, _) => + TypeApply(Select(classarg0, Any_asInstanceOf), + List(TypeTree(appliedType(ClassClass.typeConstructor, List(tp))))) + case _ => + classarg0 + } + val suffix = classarg :: (args map findSubManifest) manifestFactoryCall( "classType", tp, (if ((pre eq NoPrefix) || pre.typeSymbol.isStaticOwner) suffix else findSubManifest(pre) :: suffix): _*) - } else { - EmptyTree -/* the following is dropped because it is dangerous - * - if (sym.isAbstractType) { - if (sym.isExistentiallyBound) - EmptyTree // todo: change to existential parameter manifest - else if (sym.isTypeParameterOrSkolem) - EmptyTree // a manifest should have been found by normal searchImplicit - else { - // The following is tricky! We want to find the parameterized version of - // what will become the erasure of the upper bound. - // But there is a case where the erasure is not a superclass of the current type: - // Any erases to Object. So an abstract type having Any as upper bound will not see - // Object as a baseType. That's why we do the basetype trick only when we must, - // i.e. when the baseclass is parameterized. - var era = erasure.erasure(tp1) - if (era.typeSymbol.typeParams.nonEmpty) - era = tp1.baseType(era.typeSymbol) - manifestFactoryCall( - "abstractType", tp, - findSubManifest(pre) :: Literal(sym.name.toString) :: gen.mkClassOf(era) :: (args map findSubManifest): _*) - } + } else if (sym.isExistentiallyBound && full) { + manifestFactoryCall("wildcardType", tp, + findManifest(tp.bounds.lo), findManifest(tp.bounds.hi)) } else { EmptyTree // a manifest should have been found by normal searchImplicit -*/ } case RefinedType(parents, decls) => // refinement is not generated yet @@ -841,10 +827,7 @@ self: Analyzer => else if (full) manifestFactoryCall("intersectionType", tp, parents map (findSubManifest(_)): _*) else mot(erasure.erasure.intersectionDominator(parents)) case ExistentialType(tparams, result) => - existentialAbstraction(tparams, result) match { - case ExistentialType(_, _) => mot(result) - case t => mot(t) - } + mot(tp1.skolemizeExistential) case _ => EmptyTree } diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index c8d3308077..1ee1604319 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -120,7 +120,7 @@ trait Infer { case NoPrefix | ThisType(_) | ConstantType(_) => true case TypeRef(pre, sym, args) => - isFullyDefined(pre) && (args.isEmpty || (args forall isFullyDefined)) + isFullyDefined(pre) && (args forall isFullyDefined) case SingleType(pre, sym) => isFullyDefined(pre) case RefinedType(ts, decls) => @@ -197,72 +197,17 @@ trait Infer { /** The context-dependent inferencer part */ class Inferencer(context: Context) { - /* -- Error Messages --------------------------------------------------- */ - - private var addendumPos: Position = NoPosition - private var addendum: () => String = _ - - def setAddendum(pos: Position, msg: () => String) = { - addendumPos = pos - addendum = msg - } - def setError[T <: Tree](tree: T): T = { - if (tree.hasSymbol) - if (context.reportGeneralErrors) { - val name = newTermName("<error: " + tree.symbol + ">") - tree.setSymbol( - if (tree.isType) context.owner.newErrorClass(name.toTypeName) - else context.owner.newErrorValue(name)) - } else { - tree.setSymbol(if (tree.isType) stdErrorClass else stdErrorValue) - } - tree.setType(ErrorType) - } - - def decode(name: Name): String = - (if (name.isTypeName) "type " else "value ") + name.decode + def name = newTermName("<error: " + tree.symbol + ">") + def errorClass = if (context.reportGeneralErrors) context.owner.newErrorClass(name.toTypeName) else stdErrorClass + def errorValue = if (context.reportGeneralErrors) context.owner.newErrorValue(name) else stdErrorValue + def errorSym = if (tree.isType) errorClass else errorValue - def treeSymTypeMsg(tree: Tree): String = - if (tree.symbol eq null) - "expression of type " + tree.tpe - else if (tree.symbol.hasFlag(OVERLOADED)) - "overloaded method " + tree.symbol + " with alternatives " + tree.tpe - else - tree.symbol.toString() + - (if (tree.symbol.isModule) "" - else if (tree.tpe.paramSectionCount > 0) ": "+tree.tpe - else " of type "+tree.tpe) + - (if (tree.symbol.name == nme.apply) tree.symbol.locationString else "") - - def applyErrorMsg(tree: Tree, msg: String, argtpes: List[Type], pt: Type) = - treeSymTypeMsg(tree) + msg + argtpes.mkString("(", ",", ")") + - (if (isWildcard(pt)) "" else " with expected result type " + pt) - - // todo: use also for other error messages - private def existentialContext(tp: Type) = tp.existentialSkolems match { - case List() => "" - case skolems => - def disambiguate(ss: List[String]) = ss match { - case List() => ss - case s :: ss1 => s :: (ss1 map (s1 => if (s1 == s) "(some other)"+s1 else s1)) - } - " where "+(disambiguate(skolems map (_.existentialToString)) mkString ", ") - } - - def foundReqMsg(found: Type, req: Type): String = - withDisambiguation(found, req) { - ";\n found : " + found.toLongString + existentialContext(found) + - "\n required: " + req + existentialContext(req) - } + if (tree.hasSymbol) + tree setSymbol errorSym - def typeErrorMsg(found: Type, req: Type) = { - //println(found.baseTypeSeq) - "type mismatch" + foundReqMsg(found, req) + - (if ((found.resultApprox ne found) && isWeaklyCompatible(found.resultApprox, req)) - "\n possible cause: missing arguments for method or constructor" - else "") + tree setType ErrorType } def error(pos: Position, msg: String) { @@ -276,67 +221,27 @@ trait Infer { def typeError(pos: Position, found: Type, req: Type) { if (!found.isErroneous && !req.isErroneous) { - error(pos, - typeErrorMsg(found, req)+ - (if (pos != NoPosition && pos == addendumPos) addendum() - else "")) - if (settings.explaintypes.value) explainTypes(found, req) + error(pos, withAddendum(pos)(typeErrorMsg(found, req))) + + if (settings.explaintypes.value) + explainTypes(found, req) } } + def typeErrorMsg(found: Type, req: Type) = { + def isPossiblyMissingArgs = (found.resultApprox ne found) && isWeaklyCompatible(found.resultApprox, req) + def missingArgsMsg = if (isPossiblyMissingArgs) "\n possible cause: missing arguments for method or constructor" else "" + + "type mismatch" + foundReqMsg(found, req) + missingArgsMsg + } + def typeErrorTree(tree: Tree, found: Type, req: Type): Tree = { typeError(tree.pos, found, req) setError(tree) } def explainTypes(tp1: Type, tp2: Type) = - withDisambiguation(tp1, tp2) { global.explainTypes(tp1, tp2) } - - /** If types `tp1' `tp2' contain different type variables with same name - * differentiate the names by including owner information. Also, if the - * type error is because of a conflict between two identically named - * classes and one is in package scala, fully qualify the name so one - * need not deduce why "java.util.Iterator" and "Iterator" don't match. - */ - private def withDisambiguation[T](tp1: Type, tp2: Type)(op: => T): T = { - - def explainName(sym: Symbol) = { - if (!sym.name.toString.endsWith(")")) { - sym.name = newTypeName(sym.name.toString+"(in "+sym.owner+")") - } - } - - val patches = new ListBuffer[(Symbol, Symbol, Name)] - for { - t1 @ TypeRef(_, sym1, _) <- tp1 - t2 @ TypeRef(_, sym2, _) <- tp2 - if sym1 != sym2 - } { - if (t1.toString == t2.toString) { // type variable collisions - val name = sym1.name - explainName(sym1) - explainName(sym2) - if (sym1.owner == sym2.owner) sym2.name = newTypeName("(some other)"+sym2.name) - patches += ((sym1, sym2, name)) - } - else if (sym1.name == sym2.name) { // symbol name collisions where one is in scala._ - val name = sym1.name - def scalaQualify(s: Symbol) = - if (s.owner.isScalaPackageClass) s.name = newTypeName("scala." + s.name) - List(sym1, sym2) foreach scalaQualify - patches += ((sym1, sym2, name)) - } - } - - val result = op - - for ((sym1, sym2, name) <- patches) { - sym1.name = name - sym2.name = name - } - - result - } + withDisambiguation(tp1, tp2)(global.explainTypes(tp1, tp2)) /* -- Tests & Checks---------------------------------------------------- */ @@ -1591,7 +1496,7 @@ trait Infer { } def checkDead(tree: Tree): Tree = { - if (settings.Xwarndeadcode.value && tree.tpe != null && tree.tpe.typeSymbol == NothingClass) + if (settings.Ywarndeadcode.value && tree.tpe != null && tree.tpe.typeSymbol == NothingClass) context.warning (tree.pos, "dead code following this construct") tree } diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index a78ee61ee2..a7f573f98b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -607,6 +607,17 @@ trait Namers { self: Analyzer => vparamss.map(_.map(enterValueParam)) } + /** + * Finds the companion module of a class symbol. Calling .companionModule + * does not work for classes defined inside methods. + */ + private def companionModuleOf(clazz: Symbol) = { + var res = clazz.companionModule + if (res == NoSymbol) + res = context.lookup(clazz.name.toTermName, clazz.owner) + res + } + private def templateSig(templ: Template): Type = { val clazz = context.owner def checkParent(tpt: Tree): Type = { @@ -712,29 +723,36 @@ trait Namers { self: Analyzer => // add apply and unapply methods to companion objects of case classes, // unless they exist already; here, "clazz" is the module class - Namers.this.caseClassOfModuleClass get clazz match { - case Some(cdef) => - addApplyUnapply(cdef, templateNamer) - caseClassOfModuleClass -= clazz - case None => + if (clazz.isModuleClass) { + Namers.this.caseClassOfModuleClass get clazz match { + case Some(cdef) => + addApplyUnapply(cdef, templateNamer) + caseClassOfModuleClass -= clazz + case None => + } } // add the copy method to case classes; this needs to be done here, not in SyntheticMethods, because // the namer phase must traverse this copy method to create default getters for its parameters. - Namers.this.caseClassOfModuleClass get clazz.companionModule.moduleClass match { - case Some(cdef) => - def hasCopy(decls: Scope) = { - decls.iterator exists (_.name == nme.copy) - } - if (!hasCopy(decls) && - !parents.exists(p => hasCopy(p.typeSymbol.info.decls)) && - !parents.flatMap(_.baseClasses).distinct.exists(bc => hasCopy(bc.info.decls))) - addCopyMethod(cdef, templateNamer) - case None => + // here, clazz is the ClassSymbol of the case class (not the module). + // @check: this seems to work only if the type completer of the class runs before the one of the + // module class: the one from the module class removes the entry form caseClassOfModuleClass (see above). + if (clazz.isClass && !clazz.hasFlag(MODULE)) { + Namers.this.caseClassOfModuleClass get companionModuleOf(clazz).moduleClass match { + case Some(cdef) => + def hasCopy(decls: Scope) = { + decls.iterator exists (_.name == nme.copy) + } + if (!hasCopy(decls) && + !parents.exists(p => hasCopy(p.typeSymbol.info.decls)) && + !parents.flatMap(_.baseClasses).distinct.exists(bc => hasCopy(bc.info.decls))) + addCopyMethod(cdef, templateNamer) + case None => + } } - // if default getters (for constructor defaults) need to be added to that module, - // here's the namer to use + // if default getters (for constructor defaults) need to be added to that module, here's the namer + // to use. clazz is the ModuleClass. sourceModule works also for classes defined in methods. val module = clazz.sourceModule if (classAndNamerOfModule contains module) { val (cdef, _) = classAndNamerOfModule(module) @@ -947,6 +965,7 @@ trait Namers { self: Analyzer => if (vparamss == List(Nil) && baseParamss.isEmpty) baseParamss = List(Nil) assert(!overrides || vparamss.length == baseParamss.length, ""+ meth.fullName + ", "+ overridden.fullName) + // cache the namer used for entering the default getter symbols var ownerNamer: Option[Namer] = None var moduleNamer: Option[(ClassDef, Namer)] = None @@ -978,7 +997,7 @@ trait Namers { self: Analyzer => val parentNamer = if (isConstr) { val (cdef, nmr) = moduleNamer.getOrElse { - val module = meth.owner.companionModule + val module = companionModuleOf(meth.owner) module.initialize // call type completer (typedTemplate), adds the // module's templateNamer to classAndNamerOfModule val (cdef, nmr) = classAndNamerOfModule(module) @@ -1029,7 +1048,8 @@ trait Namers { self: Analyzer => Modifiers(meth.flags & (PRIVATE | PROTECTED | FINAL)) | SYNTHETIC | DEFAULTPARAM | oflag, name, deftParams, defvParamss, defTpt, defRhs) } - meth.owner.resetFlag(INTERFACE) // there's a concrete member now + if (!isConstr) + meth.owner.resetFlag(INTERFACE) // there's a concrete member now val default = parentNamer.enterSyntheticSym(defaultTree) } else if (baseHasDefault) { // the parameter does not have a default itself, but the corresponding parameter diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala index 89b4f910e5..18102a8bb4 100644 --- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala +++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala @@ -187,10 +187,21 @@ trait NamesDefaults { self: Analyzer => baseFun1 match { // constructor calls - case Select(New(TypeTree()), _) if isConstr => - blockWithoutQualifier(None) - case Select(TypeApply(New(TypeTree()), _), _) if isConstr => - blockWithoutQualifier(None) + case Select(New(tp @ TypeTree()), _) if isConstr => + // fixes #3338. Same qualifier for selecting the companion object as for the class. + val dq = tp.tpe match { + case TypeRef(pre, _, _) if (!pre.typeSymbol.isEmptyPackageClass) => + moduleQual(tp.pos, mod => gen.mkAttributedSelect(gen.mkAttributedQualifier(pre), mod)) + case _ => None + } + blockWithoutQualifier(dq) + case Select(TypeApply(New(tp @ TypeTree()), _), _) if isConstr => + val dq = tp.tpe match { + case TypeRef(pre, _, _) if (!pre.typeSymbol.isEmptyPackageClass) => + moduleQual(tp.pos, mod => gen.mkAttributedSelect(gen.mkAttributedQualifier(pre), mod)) + case _ => None + } + blockWithoutQualifier(dq) case Select(New(Ident(_)), _) if isConstr => blockWithoutQualifier(None) @@ -350,8 +361,11 @@ trait NamesDefaults { self: Analyzer => if (missing forall (_.hasFlag(DEFAULTPARAM))) { val defaultArgs = missing map (p => { var default1 = qual match { - case Some(q) => gen.mkAttributedSelect(q.duplicate, defaultGetter(p, context)) - case None => gen.mkAttributedRef(defaultGetter(p, context)) + case Some(q) => gen.mkAttributedSelect(q.duplicate, defaultGetter(p, context)._1) + case None => + val (m, q) = defaultGetter(p, context) + if (q.isDefined) gen.mkAttributedSelect(q.get, m) + else gen.mkAttributedRef(m) } default1 = if (targs.isEmpty) default1 else TypeApply(default1, targs.map(_.duplicate)) @@ -369,35 +383,32 @@ trait NamesDefaults { self: Analyzer => /** * For a parameter with default argument, find the method symbol of - * the default getter. + * the default getter. Can return a qualifier tree for the selecting + * the method's symbol (part of #3334 fix). */ - def defaultGetter(param: Symbol, context: Context) = { + def defaultGetter(param: Symbol, context: Context): (Symbol, Option[Tree]) = { val i = param.owner.paramss.flatten.findIndexOf(p => p.name == param.name) + 1 if (i > 0) { if (param.owner.isConstructor) { val defGetterName = "init$default$"+ i - param.owner.owner.companionModule.info.member(defGetterName) + var mod = param.owner.owner.companionModule + // if the class's owner is a method, .companionModule does not work + if (mod == NoSymbol) + mod = context.lookup(param.owner.owner.name.toTermName, param.owner.owner.owner) + (mod.info.member(defGetterName), Some(gen.mkAttributedRef(mod))) } else { val defGetterName = param.owner.name +"$default$"+ i + // isClass also works for methods in objects, owner is the ModuleClassSymbol if (param.owner.owner.isClass) { // .toInterface: otherwise we get the method symbol of the impl class - param.owner.owner.toInterface.info.member(defGetterName) + (param.owner.owner.toInterface.info.member(defGetterName), None) } else { // the owner of the method is another method. find the default // getter in the context. - var res: Symbol = NoSymbol - var ctx = context - while(res == NoSymbol && ctx.outer != ctx) { - val s = ctx.scope.lookup(defGetterName) - if (s != NoSymbol && s.owner == param.owner.owner) - res = s - else - ctx = ctx.outer - } - res + (context.lookup(defGetterName, param.owner.owner), None) } } - } else NoSymbol + } else (NoSymbol, None) } /** diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 003a173892..bd8482cd67 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -971,17 +971,20 @@ abstract class RefChecks extends InfoTransform { private def checkAnnotations(tpes: List[Type], pos: Position) = tpes foreach (tp => checkTypeRef(tp, pos)) private def doTypeTraversal(tree: Tree)(f: Type => Unit) = if (!inPattern) tree.tpe foreach f - private def applyRefchecksToAnnotations(tree: Tree) = tree match { - case m: MemberDef => - checkAnnotations(m.symbol.annotations map (_.atp), tree.pos) - transformTrees(m.symbol.annotations.flatMap(_.args)) - case TypeTree() => doTypeTraversal(tree) { - case AnnotatedType(annots, _, _) => - checkAnnotations(annots map (_.atp), tree.pos) - transformTrees(annots.flatMap(_.args)) + private def applyRefchecksToAnnotations(tree: Tree) = { + def applyChecks(annots: List[AnnotationInfo]) = { + checkAnnotations(annots map (_.atp), tree.pos) + transformTrees(annots flatMap (_.args)) + } + + tree match { + case m: MemberDef => applyChecks(m.symbol.annotations) + case TypeTree() => doTypeTraversal(tree) { + case AnnotatedType(annots, _, _) => applyChecks(annots) + case _ => + } case _ => } - case _ => } private def transformCaseApply(tree: Tree, ifNot: => Unit) = { diff --git a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala index 6f5175c0ea..8a7f4b0958 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala @@ -54,7 +54,11 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT private def checkPackedConforms(tree: Tree, pt: Type): Tree = { if (tree.tpe exists (_.typeSymbol.isExistentialSkolem)) { val packed = localTyper.packedType(tree, NoSymbol) - if (!(packed <:< pt)) localTyper.infer.typeError(tree.pos, packed, pt) + if (!(packed <:< pt)) { + val errorContext = localTyper.context.make(localTyper.context.tree) + errorContext.reportGeneralErrors = true + analyzer.newTyper(errorContext).infer.typeError(tree.pos, packed, pt) + } } tree } diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala new file mode 100644 index 0000000000..1166f62ddb --- /dev/null +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -0,0 +1,268 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package typechecker + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer +import scala.util.control.ControlThrowable +import scala.util.control.Exception.ultimately +import symtab.Flags._ +import PartialFunction._ + +/** An interface to enable higher configurability of diagnostic messages + * regarding type errors. This is barely a beginning as error messages are + * distributed far and wide across the codebase. The plan is to partition + * error messages into some broad groups and provide some mechanism for + * being more or less verbose on a selective basis. Possible groups include + * such examples as + * + * arity errors + * kind errors + * variance errors + * ambiguity errors + * volatility/stability errors + * implementation restrictions + * + * And more, and there is plenty of overlap, so it'll be a process. + * + * @author Paul Phillips + * @version 1.0 + */ +trait TypeDiagnostics { + self: Analyzer => + + import global._ + import definitions._ + import global.typer.infer + + /** It can be quite difficult to know which of the many functions called "error" + * is being called at any given point in the compiler. To alleviate this I am + * renaming such functions inside this trait based on where it originated. + */ + def inferError(pos: Position, msg: String) = infer.error(pos, msg) + + /** The common situation of making sure nothing is erroneous could be + * nicer if Symbols, Types, and Trees all implemented some common interface + * in which isErroneous and similar would be placed. + */ + def noErroneousTypes(tps: Type*) = tps forall (x => !x.isErroneous) + def noErroneousSyms(syms: Symbol*) = syms forall (x => !x.isErroneous) + def noErroneousTrees(trees: Tree*) = trees forall (x => !x.isErroneous) + + /** A map of Positions to addendums - if an error involves a position in + * the map, the addendum should also be printed. + */ + private var addendums = mutable.Map[Position, () => String]() + + def setAddendum(pos: Position, msg: () => String) = + if (pos != NoPosition) + addendums(pos) = msg + + def withAddendum(pos: Position) = (_: String) + addendums.getOrElse(pos, () => "")() + + def decodeWithNamespace(name: Name): String = { + val prefix = if (name.isTypeName) "type " else "value " + prefix + name.decode + } + + /** Does the positioned line assigned to t1 precede that of t2? + */ + def linePrecedes(t1: Tree, t2: Tree) = t1.pos.isDefined && t1.pos.isDefined && t1.pos.line < t2.pos.line + + def notAMember(sel: Tree, qual: Tree, name: Name) = { + def decoded = decodeWithNamespace(name) + + def msg: String = name match { + case nme.CONSTRUCTOR => qual.tpe.widen+" does not have a constructor" + case _ => + def memberOf = if (qual.tpe.typeSymbol.isTypeParameterOrSkolem) "type parameter " else "" + def possibleCause = + if (linePrecedes(qual, sel)) + "\npossible cause: maybe a semicolon is missing before `"+decoded+"'?" + else + "" + + decoded+" is not a member of "+ memberOf + qual.tpe.widen + possibleCause + } + inferError(sel.pos, withAddendum(qual.pos)(msg)) + } + + /** Only prints the parameter names if they're not synthetic, + * since "x$1: Int" does not offer any more information than "Int". + */ + private def methodTypeErrorString(tp: Type) = tp match { + case mt @ MethodType(params, resultType) => + def forString = + if (params exists (_.isSynthetic)) params map (_.tpe) + else params map (_.defString) + + forString.mkString("(", ",", ")") + resultType + case x => x.toString + } + + def alternatives(tree: Tree): List[Type] = tree.tpe match { + case OverloadedType(pre, alternatives) => alternatives map pre.memberType + case _ => Nil + } + def alternativesString(tree: Tree) = + alternatives(tree) map (x => " " + methodTypeErrorString(x)) mkString ("", " <and>\n", "\n") + + def missingParameterTypeError(fun: Tree, vparam: ValDef) = { + val suffix = if (vparam.mods.isSynthetic) " for expanded function "+fun else "" + + inferError(vparam.pos, "missing parameter type" + suffix) + ErrorType + } + + def treeSymTypeMsg(tree: Tree): String = { + val sym = tree.symbol + def hasParams = tree.tpe.paramSectionCount > 0 + def preResultString = if (hasParams) ": " else " of type " + + def nullMessage = "expression of type " + tree.tpe + def overloadedMessage = "overloaded method " + sym + " with alternatives:\n" + alternativesString(tree) + def moduleMessage = "" + sym + def defaultMessage = moduleMessage + preResultString + tree.tpe + def applyMessage = defaultMessage + tree.symbol.locationString + + if (sym == null) nullMessage + else if (sym.isOverloaded) overloadedMessage + else if (sym.isModule) moduleMessage + else if (sym.name == nme.apply) applyMessage + else defaultMessage + } + + def applyErrorMsg(tree: Tree, msg: String, argtpes: List[Type], pt: Type) = { + def asParams(xs: List[Any]) = xs.mkString("(", ", ", ")") + + def resType = if (isWildcard(pt)) "" else " with expected result type " + pt + def allTypes = (alternatives(tree) flatMap (_.paramTypes)) ++ argtpes :+ pt + + withDisambiguation(allTypes: _*) { + treeSymTypeMsg(tree) + msg + asParams(argtpes) + resType + } + } + + def disambiguate(ss: List[String]) = ss match { + case Nil => Nil + case s :: ss => s :: (ss map { case `s` => "(some other)"+s ; case x => x }) + } + + // todo: use also for other error messages + def existentialContext(tp: Type) = tp.existentialSkolems match { + case Nil => "" + case xs => " where " + (disambiguate(xs map (_.existentialToString)) mkString ", ") + } + + def foundReqMsg(found: Type, req: Type): String = + withDisambiguation(found, req) { + ";\n found : " + found.toLongString + existentialContext(found) + + "\n required: " + req + existentialContext(req) + } + + /** If two given types contain different type variables with the same name + * differentiate the names by including owner information. Also, if the + * type error is because of a conflict between two identically named + * classes and one is in package scala, fully qualify the name so one + * need not deduce why "java.util.Iterator" and "Iterator" don't match. + * Another disambiguation performed is to address the confusion present + * in the following snippet: + * def f[Int](x: Int) = x + 5. + */ + def withDisambiguation[T](types: Type*)(op: => T): T = { + object SymExtractor { + def unapply(x: Any) = x match { + case t @ TypeRef(_, sym, _) => Some(t -> sym) + case t @ ConstantType(value) => Some(t -> t.underlying.typeSymbol) + case _ => None + } + } + val typerefs = + for (tp <- types.toList ; SymExtractor(t, sym) <- tp) yield + t -> sym + + val savedNames = typerefs map { case (_, sym) => sym -> sym.name } toMap + def restoreNames = savedNames foreach { case (sym, name) => sym.name = name } + + def isAlreadyAltered(sym: Symbol) = sym.name != savedNames(sym) + + def modifyName(sym: Symbol)(f: String => String): Unit = + sym.name = newTypeName(f(sym.name.toString)) + + def scalaQualify(sym: Symbol) = + if (sym.owner.isScalaPackageClass) + modifyName(sym)("scala." + _) + + def explainName(sym: Symbol) = { + scalaQualify(sym) + + if (!isAlreadyAltered(sym)) + modifyName(sym)(_ + "(in " + sym.owner + ")") + } + + ultimately(restoreNames) { + for ((t1, sym1) <- typerefs ; (t2, sym2) <- typerefs ; if sym1 != sym2 && (sym1 isLess sym2)) { + + if (t1.toString == t2.toString) { // type variable collisions + List(sym1, sym2) foreach explainName + if (sym1.owner == sym2.owner) + sym2.name = newTypeName("(some other)"+sym2.name) + } + else if (sym1.name == sym2.name) { // symbol name collisions + List(sym1, sym2) foreach { x => + if (x.owner.isScalaPackageClass) + modifyName(x)("scala." + _) + else if (x.isTypeParameterOrSkolem) + explainName(x) + } + } + } + + // performing the actual operation + op + } + } + + trait TyperDiagnostics { + self: Typer => + + private def contextError(pos: Position, msg: String) = context.error(pos, msg) + private def contextError(pos: Position, err: Throwable) = context.error(pos, err) + + def symWasOverloaded(sym: Symbol) = sym.owner.isClass && sym.owner.info.member(sym.name).isOverloaded + def cyclicAdjective(sym: Symbol) = if (symWasOverloaded(sym)) "overloaded" else "recursive" + + /** Returns Some(msg) if the given tree is untyped apparently due + * to a cyclic reference, and None otherwise. + */ + def cyclicReferenceMessage(sym: Symbol, tree: Tree) = condOpt(tree) { + case ValDef(_, _, tpt, _) if tpt.tpe == null => "recursive "+sym+" needs type" + case DefDef(_, _, _, _, tpt, _) if tpt.tpe == null => List(cyclicAdjective(sym), sym, "needs result type") mkString " " + } + + /** Report a type error. + * + * @param pos0 The position where to report the error + * @param ex The exception that caused the error + */ + def reportTypeError(pos: Position, ex: TypeError) { + if (ex.pos == NoPosition) ex.pos = pos + if (!context.reportGeneralErrors) throw ex + if (settings.debug.value) ex.printStackTrace() + + ex match { + case CyclicReference(sym, info: TypeCompleter) => + contextError(ex.pos, cyclicReferenceMessage(sym, info.tree) getOrElse ex.getMessage()) + + if (sym == ObjectClass) + throw new FatalError("cannot redefine root "+sym) + case _ => + contextError(ex.pos, ex) + } + } + } +} diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 3fe937f241..92dd368ac5 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -161,7 +161,7 @@ trait Typers { self: Analyzer => else mode } - abstract class Typer(context0: Context) { + abstract class Typer(context0: Context) extends TyperDiagnostics { import context0.unit val infer = new Inferencer(context0) { @@ -252,35 +252,6 @@ trait Typers { self: Analyzer => private[typechecker] var context = context0 def context1 = context - /** Report a type error. - * - * @param pos0 The position where to report the error - * @param ex The exception that caused the error - */ - def reportTypeError(pos: Position, ex: TypeError) { - if (ex.pos == NoPosition) ex.pos = pos - if (!context.reportGeneralErrors) throw ex - if (settings.debug.value) ex.printStackTrace() - ex match { - case CyclicReference(sym, info: TypeCompleter) => - val msg = - info.tree match { - case ValDef(_, _, tpt, _) if (tpt.tpe eq null) => - "recursive "+sym+" needs type" - case DefDef(_, _, _, _, tpt, _) if (tpt.tpe eq null) => - (if (sym.owner.isClass && sym.owner.info.member(sym.name).hasFlag(OVERLOADED)) "overloaded " - else "recursive ")+sym+" needs result type" - case _ => - ex.getMessage() - } - context.error(ex.pos, msg) - if (sym == ObjectClass) - throw new FatalError("cannot redefine root "+sym) - case _ => - context.error(ex.pos, ex) - } - } - /** Check that <code>tree</code> is a stable expression. * * @param tree ... @@ -397,7 +368,7 @@ trait Typers { self: Analyzer => error(pos, "methods with `*'-parameters cannot be converted to function values"); */ if (restpe.isDependent) - error(pos, "method with dependent type "+tpe+" cannot be converted to function value"); + error(pos, "method with dependent type "+tpe+" cannot be converted to function value") checkParamsConvertible(pos, restpe) case _ => } @@ -1230,7 +1201,7 @@ trait Typers { self: Analyzer => if (!ps.isEmpty && !superclazz.isSubClass(ps.head.typeSymbol)) error(parent.pos, "illegal inheritance; super"+superclazz+ "\n is not a subclass of the super"+ps.head.typeSymbol+ - "\n of the mixin " + psym); + "\n of the mixin " + psym) } else { error(parent.pos, psym+" needs to be a trait to be mixed in") } @@ -1352,6 +1323,7 @@ trait Typers { self: Analyzer => */ def addGetterSetter(stat: Tree): List[Tree] = stat match { case ValDef(mods, name, tpt, rhs) + // PRIVATE | LOCAL are fields generated for primary constructor arguments if (mods.flags & (PRIVATE | LOCAL)) != (PRIVATE | LOCAL).toLong && !stat.symbol.isModuleVar => val isDeferred = mods hasFlag DEFERRED @@ -1592,7 +1564,7 @@ trait Typers { self: Analyzer => * @param rhs ... */ def computeParamAliases(clazz: Symbol, vparamss: List[List[ValDef]], rhs: Tree) { - if (settings.debug.value) log("computing param aliases for "+clazz+":"+clazz.primaryConstructor.tpe+":"+rhs);//debug + if (settings.debug.value) log("computing param aliases for "+clazz+":"+clazz.primaryConstructor.tpe+":"+rhs)//debug def decompose(call: Tree): (Tree, List[Tree]) = call match { case Apply(fn, args) => val (superConstr, args1) = decompose(fn) @@ -1600,7 +1572,7 @@ trait Typers { self: Analyzer => val args2 = if (params.isEmpty || !isRepeatedParamType(params.last.tpe)) args else args.take(params.length - 1) ::: List(EmptyTree) if (args2.length != params.length) - assert(false, "mismatch " + clazz + " " + (params map (_.tpe)) + " " + args2);//debug + assert(false, "mismatch " + clazz + " " + (params map (_.tpe)) + " " + args2)//debug (superConstr, args1 ::: args2) case Block(stats, expr) if !stats.isEmpty => decompose(stats.last) @@ -1639,7 +1611,7 @@ trait Typers { self: Analyzer => ownAcc = ownAcc.accessed if (!ownAcc.isVariable && !alias.accessed.isVariable) { if (settings.debug.value) - log("" + ownAcc + " has alias "+alias + alias.locationString);//debug + log("" + ownAcc + " has alias "+alias + alias.locationString) //debug ownAcc.asInstanceOf[TermSymbol].setAlias(alias) } } @@ -1676,77 +1648,6 @@ trait Typers { self: Analyzer => } } - /** does given name name an identifier visible at this point? - * - * @param name the given name - * @return <code>true</code> if an identifier with the given name is visible. - */ - def namesSomeIdent(name: Name): Boolean = namesWhatIdent(name).isDefined - - /** If this name returns a visible identifier, return its symbol. - * - * @param name the given name - * @return <code>Some(sym)</code> if an ident is visible, None otherwise. - */ - def namesWhatIdent(name: Name): Option[Symbol] = { - var cx = context - while (cx != NoContext) { - val pre = cx.enclClass.prefix - val defEntry = cx.scope.lookupEntry(name) - if ((defEntry ne null) && reallyExists(defEntry.sym)) - return Some(defEntry.sym) - - cx = cx.enclClass - (pre member name filter (sym => reallyExists(sym) && context.isAccessible(sym, pre, false))) match { - case NoSymbol => cx = cx.outer - case other => return Some(other) - } - } - context.imports map (_ importedSymbol name) find (_ != NoSymbol) - } - - /** Does this tree declare a val or def with the same name as one in scope? - * This only catches identifiers in the same file, so more work is needed. - * - * @param tree the given tree - * @param filt filter for any conflicting symbols found -- false means ignore - */ - def checkShadowings(tree: Tree, filt: (Symbol) => Boolean = _ => true) { - def sameFile(other: Symbol) = - (tree.symbol != null) && tree.symbol.sourceFile == other.sourceFile - def inFile(other: Symbol) = - if (sameFile(other)) "" - else if (other.sourceFile != null) "in %s ".format(other.sourceFile) - else "" - - def positionStr(other: Symbol) = other.pos match { - case NoPosition => inFile(other) match { case "" => "(location unknown) " ; case x => x } - case pos => "%sat line %s\n%s".format(inFile(other), pos.line, pos.lineContent) + """ /* is shadowed by */""" - } - def include(v: ValOrDefDef, other: Symbol) = { - // shadowing on the same line is a good bet for noise - (v.pos == NoPosition || other.pos == NoPosition || v.pos.line != other.pos.line) && - // not likely we'll shadow a whole package without realizing it - !other.isPackage && - // (v.symbol == null || !v.symbol.hasTransOwner(other)) && - filt(other) - } - - tree match { - // while I try to figure out how to limit the noise far enough to make this - // genuinely useful, I'm setting minimum identifier length to 3 to omit all - // those x's and i's we so enjoy reusing. - case v: ValOrDefDef if v.name.toString.length > 2 => - namesWhatIdent(v.name) map { other => - if (include(v, other) && unit != null) { - val fstr = "%s (%s) shadows usage %s" - unit.warning(v.pos, fstr.format(v.name, v.tpt, positionStr(other))) - } - } - case _ => - } - } - def typedUseCase(useCase: UseCase) { def stringParser(str: String): syntaxAnalyzer.Parser = { val file = new BatchSourceFile(context.unit.source.file, str) { @@ -1793,15 +1694,6 @@ trait Typers { self: Analyzer => def typedDefDef(ddef: DefDef): DefDef = { val meth = ddef.symbol - // If warnings are enabled, attempt to alert about variable shadowing. This only - // catches method parameters shadowing identifiers declared in the same file, so more - // work is needed. Most of the code here is to filter out false positives. - def isAuxConstructor(sym: Symbol) = sym.isConstructor && !sym.isPrimaryConstructor - if (settings.YwarnShadow.value && !isAuxConstructor(ddef.symbol)) { - for (v <- ddef.vparamss.flatten ; if v.symbol != null && !(v.symbol hasFlag SYNTHETIC)) - checkShadowings(v, (sym => !sym.isDeferred && !sym.isMethod)) - } - reenterTypeParams(ddef.tparams) reenterValueParams(ddef.vparamss) @@ -1933,7 +1825,6 @@ trait Typers { self: Analyzer => while ((e ne null) && (e.sym ne stat.symbol)) e = e.tail if (e eq null) context.scope.enter(stat.symbol) } - if (settings.YwarnShadow.value) checkShadowings(stat) enterLabelDef(stat) } if (phaseId(currentPeriod) <= currentRun.typerPhase.id) { @@ -2155,18 +2046,18 @@ trait Typers { self: Analyzer => !(accessed hasFlag ACCESSOR) && accessed.isPrivateLocal def checkNoDoubleDefsAndAddSynthetics(stats: List[Tree]): List[Tree] = { - val scope = if (inBlock) context.scope else context.owner.info.decls; + val scope = if (inBlock) context.scope else context.owner.info.decls val newStats = new ListBuffer[Tree] var needsCheck = true var moreToAdd = true while (moreToAdd) { val initSize = scope.size - var e = scope.elems; + var e = scope.elems while ((e ne null) && e.owner == scope) { // check no double def if (needsCheck) { - var e1 = scope.lookupNextEntry(e); + var e1 = scope.lookupNextEntry(e) while ((e1 ne null) && e1.owner == scope) { if (!accesses(e.sym, e1.sym) && !accesses(e1.sym, e.sym) && (e.sym.isType || inBlock || (e.sym.tpe matches e1.sym.tpe))) @@ -2394,7 +2285,8 @@ trait Typers { self: Analyzer => val (allArgs, missing) = addDefaults(args, qual, targs, previousArgss, params, fun.pos.focus, context) if (allArgs.length == formals.length) { // useful when a default doesn't match parameter type, e.g. def f[T](x:T="a"); f[Int]() - context.diagnostic = "Error occured in an application involving default arguments." :: context.diagnostic + val note = "Error occurred in an application involving default arguments." + if (!(context.diagnostic contains note)) context.diagnostic = note :: context.diagnostic doTypedApply(tree, if (blockIsEmpty) fun else fun1, allArgs, mode, pt) } else { tryTupleApply.getOrElse { @@ -2466,7 +2358,7 @@ trait Typers { self: Analyzer => inferExprInstance(fun, tparams, WildcardType, true) doTypedApply(tree, fun, args, mode, pt) } else { - assert((mode & PATTERNmode) == 0); // this case cannot arise for patterns + assert((mode & PATTERNmode) == 0) // this case cannot arise for patterns val lenientTargs = protoTypeArgs(tparams, formals, mt.resultApprox, pt) val strictTargs = (lenientTargs, tparams).zipped map ((targ, tparam) => if (targ == WildcardType) tparam.tpe else targ) //@M TODO: should probably be .tpeHK @@ -2490,7 +2382,7 @@ trait Typers { self: Analyzer => val args1 = (args, formals).zipped map typedArgToPoly if (args1 exists (_.tpe.isError)) setError(tree) else { - if (settings.debug.value) log("infer method inst "+fun+", tparams = "+tparams+", args = "+args1.map(_.tpe)+", pt = "+pt+", lobounds = "+tparams.map(_.tpe.bounds.lo)+", parambounds = "+tparams.map(_.info));//debug + if (settings.debug.value) log("infer method inst "+fun+", tparams = "+tparams+", args = "+args1.map(_.tpe)+", pt = "+pt+", lobounds = "+tparams.map(_.tpe.bounds.lo)+", parambounds = "+tparams.map(_.info)) //debug // define the undetparams which have been fixed by this param list, replace the corresponding symbols in "fun" // returns those undetparams which have not been instantiated. val undetparams = inferMethodInstance(fun, tparams, args1, pt) @@ -3016,6 +2908,8 @@ trait Typers { self: Analyzer => * @return ... */ protected def typed1(tree: Tree, mode: Int, pt: Type): Tree = { + def isPatternMode = (mode & PATTERNmode) != 0 + //Console.println("typed1("+tree.getClass()+","+Integer.toHexString(mode)+","+pt+")") def ptOrLub(tps: List[Type]) = if (isFullyDefined(pt)) (pt, false) else weakLub(tps map (_.deconst)) @@ -3114,13 +3008,6 @@ trait Typers { self: Analyzer => if (vble == NoSymbol) vble = context.owner.newValue(tree.pos, name) if (vble.name.toTermName != nme.WILDCARD) { -/* - if (namesSomeIdent(vble.name)) - context.warning(tree.pos, - "pattern variable"+vble.name+" shadows a value visible in the environment;\n"+ - "use backquotes `"+vble.name+"` if you mean to match against that value;\n" + - "or rename the variable or use an explicit bind "+vble.name+"@_ to avoid this warning.") -*/ if ((mode & ALTmode) != 0) error(tree.pos, "illegal variable in pattern alternative") vble = namer.enterInScope(vble) @@ -3143,32 +3030,36 @@ trait Typers { self: Analyzer => } def typedAssign(lhs: Tree, rhs: Tree): Tree = { - val lhs1 = typed(lhs, EXPRmode | LHSmode, WildcardType) - val varsym = lhs1.symbol - if ((varsym ne null) && treeInfo.mayBeVarGetter(varsym)) + val lhs1 = typed(lhs, EXPRmode | LHSmode, WildcardType) + val varsym = lhs1.symbol + def failMsg = + if (varsym != null && varsym.isValue) "reassignment to val" + else "assignment to non variable" + + def fail = { + if (!lhs1.tpe.isError) + error(tree.pos, failMsg) + + setError(tree) + } + if (varsym == null) + return fail + + if (treeInfo.mayBeVarGetter(varsym)) { lhs1 match { case Select(qual, name) => - return typed( - Apply( - Select(qual, nme.getterToSetter(name)) setPos lhs.pos, - List(rhs)) setPos tree.pos, - mode, pt) + val sel = Select(qual, nme.getterToSetter(name)) setPos lhs.pos + val app = Apply(sel, List(rhs)) setPos tree.pos + return typed(app, mode, pt) case _ => - } - if ((varsym ne null) && (varsym.isVariable || varsym.isValue && phase.erasedTypes)) { + } + if (varsym.isVariable || varsym.isValue && phase.erasedTypes) { val rhs1 = typed(rhs, EXPRmode | BYVALmode, lhs1.tpe) treeCopy.Assign(tree, lhs1, checkDead(rhs1)) setType UnitClass.tpe - } else { - if (!lhs1.tpe.isError) { - //println(lhs1+" = "+rhs+" "+varsym+" "+mayBeVarGetter(varsym)+" "+varsym.ownerChain+" "+varsym.info)// DEBUG - error(tree.pos, - if ((varsym ne null) && varsym.isValue) "reassignment to val" - else "assignment to non variable") - } - setError(tree) } + else fail } def typedIf(cond: Tree, thenp: Tree, elsep: Tree) = { @@ -3269,7 +3160,7 @@ trait Typers { self: Analyzer => pt != WildcardType && pt != ErrorType && isSubType(pt, DelegateClass.tpe)) => - val scalaCaller = newScalaCaller(pt); + val scalaCaller = newScalaCaller(pt) addScalaCallerInfo(scalaCaller, expr1.symbol) val n: Name = scalaCaller.name val del = Ident(DelegateClass) setType DelegateClass.tpe @@ -3277,7 +3168,7 @@ trait Typers { self: Analyzer => //val f1 = TypeApply(f, List(Ident(pt.symbol) setType pt)) val args: List[Tree] = if(expr1.symbol.isStatic) List(Literal(Constant(null))) else List(qual) // where the scala-method is located - val rhs = Apply(f, args); + val rhs = Apply(f, args) typed(rhs) case _ => adapt(expr1, mode, functionType(formals map (t => WildcardType), WildcardType)) @@ -3314,19 +3205,19 @@ trait Typers { self: Analyzer => t case ex: TypeError => stopTimer(failedApplyNanos, start) - def errorInResult(tree: Tree): Boolean = tree.pos == ex.pos || { - tree match { - case Block(_, r) => errorInResult(r) - case Match(_, cases) => cases exists errorInResult - case CaseDef(_, _, r) => errorInResult(r) - case Annotated(_, r) => errorInResult(r) - case If(_, t, e) => errorInResult(t) || errorInResult(e) - case Try(b, catches, _) => errorInResult(b) || (catches exists errorInResult) - case Typed(r, Function(List(), EmptyTree)) => errorInResult(r) - case _ => false - } - } - if (errorInResult(fun) || (args exists errorInResult) || errorInResult(tree)) { + def treesInResult(tree: Tree): List[Tree] = tree :: (tree match { + case Block(_, r) => treesInResult(r) + case Match(_, cases) => cases + case CaseDef(_, _, r) => treesInResult(r) + case Annotated(_, r) => treesInResult(r) + case If(_, t, e) => treesInResult(t) ++ treesInResult(e) + case Try(b, catches, _) => treesInResult(b) ++ catches + case Typed(r, Function(Nil, EmptyTree)) => treesInResult(r) + case _ => Nil + }) + def errorInResult(tree: Tree) = treesInResult(tree) exists (_.pos == ex.pos) + + if (fun :: tree :: args exists errorInResult) { if (printTypings) println("second try for: "+fun+" and "+args) val Select(qual, name) = fun val args1 = tryTypedArgs(args, argMode(fun, mode), ex) @@ -3347,11 +3238,11 @@ trait Typers { self: Analyzer => def typedApply(fun: Tree, args: List[Tree]) = { val stableApplication = (fun.symbol ne null) && fun.symbol.isMethod && fun.symbol.isStable - if (stableApplication && (mode & PATTERNmode) != 0) { + if (stableApplication && isPatternMode) { // treat stable function applications f() as expressions. typed1(tree, mode & ~PATTERNmode | EXPRmode, pt) } else { - val funpt = if ((mode & PATTERNmode) != 0) pt else WildcardType + val funpt = if (isPatternMode) pt else WildcardType val appStart = startTimer(failedApplyNanos) val opeqStart = startTimer(failedOpEqNanos) silent(_.typed(fun, funMode(mode), funpt)) match { @@ -3392,7 +3283,7 @@ trait Typers { self: Analyzer => case ex: TypeError => fun match { case Select(qual, name) - if (mode & PATTERNmode) == 0 && nme.isOpAssignmentName(name.decode) => + if !isPatternMode && nme.isOpAssignmentName(name.decode) => val qual1 = typedQualifier(qual) if (treeInfo.isVariableOrGetter(qual1)) { stopTimer(failedOpEqNanos, opeqStart) @@ -3422,31 +3313,41 @@ trait Typers { self: Analyzer => Apply( Select(vble.duplicate, prefix) setPos fun.pos.focus, args) setPos tree.pos.makeTransparent ) setPos tree.pos + + def mkUpdate(table: Tree, indices: List[Tree]) = { + gen.evalOnceAll(table :: indices, context.owner, context.unit) { ts => + val tab = ts.head + val is = ts.tail + Apply( + Select(tab(), nme.update) setPos table.pos, + ((is map (i => i())) ::: List( + Apply( + Select( + Apply( + Select(tab(), nme.apply) setPos table.pos, + is map (i => i())) setPos qual.pos, + prefix) setPos fun.pos, + args) setPos tree.pos) + ) + ) setPos tree.pos + } + } + val tree1 = qual match { + case Ident(_) => + mkAssign(qual) + case Select(qualqual, vname) => gen.evalOnce(qualqual, context.owner, context.unit) { qq => val qq1 = qq() mkAssign(Select(qq1, vname) setPos qual.pos) } - case Apply(Select(table, nme.apply), indices) => - gen.evalOnceAll(table :: indices, context.owner, context.unit) { ts => - val tab = ts.head - val is = ts.tail - Apply( - Select(tab(), nme.update) setPos table.pos, - ((is map (i => i())) ::: List( - Apply( - Select( - Apply( - Select(tab(), nme.apply) setPos table.pos, - is map (i => i())) setPos qual.pos, - prefix) setPos fun.pos, - args) setPos tree.pos) - ) - ) setPos tree.pos - } - case Ident(_) => - mkAssign(qual) + + case Apply(fn, indices) => + treeInfo.methPart(fn) match { + case Select(table, nme.apply) => mkUpdate(table, indices) + case _ => errorTree(qual, "Unexpected tree during assignment conversion.") + } } typed1(tree1, mode, pt) /* @@ -3559,19 +3460,9 @@ trait Typers { self: Analyzer => if (name == nme.ERROR && onlyPresentation) return makeErrorTree - if (!qual.tpe.widen.isErroneous) { - error(tree.pos, - if (name == nme.CONSTRUCTOR) - qual.tpe.widen+" does not have a constructor" - else - decode(name)+" is not a member of "+ - (if (qual.tpe.typeSymbol.isTypeParameterOrSkolem) "type parameter " else "") + - qual.tpe.widen + - (if ((context.unit ne null) && // Martin: why is this condition needed? - qual.pos.isDefined && tree.pos.isDefined && qual.pos.line < tree.pos.line) - "\npossible cause: maybe a semicolon is missing before `"+decode(name)+"'?" - else "")) - } + if (!qual.tpe.widen.isErroneous) + notAMember(tree, qual, name) + if (onlyPresentation) makeErrorTree else setError(tree) } else { val tree1 = tree match { @@ -3647,8 +3538,8 @@ trait Typers { self: Analyzer => val symDepth = if (defEntry eq null) cx.depth else cx.depth - (cx.scope.nestingLevel - defEntry.owner.nestingLevel) - var impSym: Symbol = NoSymbol; // the imported symbol - var imports = context.imports; // impSym != NoSymbol => it is imported from imports.head + var impSym: Symbol = NoSymbol // the imported symbol + var imports = context.imports // impSym != NoSymbol => it is imported from imports.head while (!reallyExists(impSym) && !imports.isEmpty && imports.head.depth > symDepth) { impSym = imports.head.importedSymbol(name) if (!impSym.exists) imports = imports.tail @@ -3711,7 +3602,7 @@ trait Typers { self: Analyzer => if (settings.debug.value) { log(context.imports)//debug } - error(tree.pos, "not found: "+decode(name)) + error(tree.pos, "not found: "+decodeWithNamespace(name)) defSym = context.owner.newErrorSymbol(name) } } @@ -3779,7 +3670,7 @@ trait Typers { self: Analyzer => errorTree(tree, tpt1.tpe+" does not take type parameters") } else { //Console.println("\{tpt1}:\{tpt1.symbol}:\{tpt1.symbol.info}") - if (settings.debug.value) Console.println(tpt1+":"+tpt1.symbol+":"+tpt1.symbol.info);//debug + if (settings.debug.value) Console.println(tpt1+":"+tpt1.symbol+":"+tpt1.symbol.info)//debug errorTree(tree, "wrong number of type arguments for "+tpt1.tpe+", should be "+tparams.length) } } @@ -3948,7 +3839,7 @@ trait Typers { self: Analyzer => val tpt1 = typedType(tpt, mode) val expr1 = typed(expr, mode & stickyModes, tpt1.tpe.deconst) val owntype = - if ((mode & PATTERNmode) != 0) inferTypedPattern(tpt1.pos, tpt1.tpe, pt) + if (isPatternMode) inferTypedPattern(tpt1.pos, tpt1.tpe, pt) else tpt1.tpe //Console.println(typed pattern: "+tree+":"+", tp = "+tpt1.tpe+", pt = "+pt+" ==> "+owntype)//DEBUG treeCopy.Typed(tree, expr1, tpt1) setType owntype @@ -4146,15 +4037,15 @@ trait Typers { self: Analyzer => tree.tpe = null if (tree.hasSymbol) tree.symbol = NoSymbol } - if (printTypings) println("typing "+tree+", pt = "+pt+", undetparams = "+context.undetparams+", implicits-enabled = "+context.implicitsEnabled+", silent = "+context.reportGeneralErrors); //DEBUG + if (printTypings) println("typing "+tree+", pt = "+pt+", undetparams = "+context.undetparams+", implicits-enabled = "+context.implicitsEnabled+", silent = "+context.reportGeneralErrors) //DEBUG var tree1 = if (tree.tpe ne null) tree else typed1(tree, mode, dropExistential(pt)) - if (printTypings) println("typed "+tree1+":"+tree1.tpe+(if (isSingleType(tree1.tpe)) " with underlying "+tree1.tpe.widen else "")+", undetparams = "+context.undetparams+", pt = "+pt); //DEBUG + if (printTypings) println("typed "+tree1+":"+tree1.tpe+(if (isSingleType(tree1.tpe)) " with underlying "+tree1.tpe.widen else "")+", undetparams = "+context.undetparams+", pt = "+pt) //DEBUG tree1.tpe = addAnnotations(tree1, tree1.tpe) val result = if (tree1.isEmpty) tree1 else adapt(tree1, mode, pt, tree) - if (printTypings) println("adapted "+tree1+":"+tree1.tpe.widen+" to "+pt+", "+context.undetparams); //DEBUG + if (printTypings) println("adapted "+tree1+":"+tree1.tpe.widen+" to "+pt+", "+context.undetparams) //DEBUG // for (t <- tree1.tpe) assert(t != WildcardType) // if ((mode & TYPEmode) != 0) println("type: "+tree1+" has type "+tree1.tpe) if (phase.id <= currentRun.typerPhase.id) signalDone(context.asInstanceOf[analyzer.Context], tree, result) @@ -4162,7 +4053,7 @@ trait Typers { self: Analyzer => } catch { case ex: TypeError => tree.tpe = null - if (printTypings) println("caught "+ex+" in typed: "+tree);//DEBUG + if (printTypings) println("caught "+ex+" in typed: "+tree) //DEBUG reportTypeError(tree.pos, ex) setError(tree) case ex: Exception => @@ -4170,7 +4061,7 @@ trait Typers { self: Analyzer => Console.println("exception when typing "+tree+", pt = "+pt) if ((context ne null) && (context.unit ne null) && (context.unit.source ne null) && (tree ne null)) - logError("AT: " + (tree.pos).dbgString, ex); + logError("AT: " + (tree.pos).dbgString, ex) throw ex } finally { diff --git a/src/compiler/scala/tools/nsc/util/Chars.scala b/src/compiler/scala/tools/nsc/util/Chars.scala index 5a64f36eb4..562806b6eb 100755 --- a/src/compiler/scala/tools/nsc/util/Chars.scala +++ b/src/compiler/scala/tools/nsc/util/Chars.scala @@ -34,6 +34,17 @@ object Chars { -1 } + /** Convert a character to a backslash-u escape */ + def char2uescape(c: Char): String = { + var rest = c.toInt + val buf = new StringBuilder + for (i <- 1 to 4) { + buf ++= (rest % 16).toHexString + rest = rest / 16 + } + "\\u" + buf.toString.reverse + } + /** Is character a line break? */ @inline def isLineBreakChar(c: Char) = (c: @switch) match { case LF|FF|CR|SU => true diff --git a/src/compiler/scala/tools/nsc/util/ShowPickled.scala b/src/compiler/scala/tools/nsc/util/ShowPickled.scala index 740e42192b..b87d603570 100644 --- a/src/compiler/scala/tools/nsc/util/ShowPickled.scala +++ b/src/compiler/scala/tools/nsc/util/ShowPickled.scala @@ -13,6 +13,7 @@ import java.lang.Long.toHexString import java.lang.Float.intBitsToFloat import java.lang.Double.longBitsToDouble +import cmd.program.Simple import symtab.{ Flags, Names } import scala.reflect.generic.{ PickleBuffer, PickleFormat } import interpreter.ByteCode.scalaSigBytesForPath @@ -288,15 +289,23 @@ object ShowPickled extends Names { printFile(pickle, Console.out, bare) } + private lazy val ShowPickledSpec = + Simple( + Simple.scalaProgramInfo("showPickled", "Usage: showPickled [--bare] <classname>"), + List("--bare" -> "suppress numbers in output"), + Nil, + null + ) + /** Option --bare suppresses numbers so the output can be diffed. */ def main(args: Array[String]) { - val parsed = cmd.CommandLine(args.toList, List("--bare"), Nil) - def isBare = parsed isSet "--bare" + val runner = ShowPickledSpec instance args + import runner._ - parsed.residualArgs foreach { arg => + residualArgs foreach { arg => (fromFile(arg) orElse fromName(arg)) match { - case Some(pb) => show(arg, pb, isBare) + case Some(pb) => show(arg, pb, parsed isSet "--bare") case _ => Console.println("Cannot read " + arg) } } diff --git a/src/compiler/scala/tools/nsc/util/SourceFile.scala b/src/compiler/scala/tools/nsc/util/SourceFile.scala index 57d2cc782f..f9f3c5e5fe 100644 --- a/src/compiler/scala/tools/nsc/util/SourceFile.scala +++ b/src/compiler/scala/tools/nsc/util/SourceFile.scala @@ -163,7 +163,6 @@ extends BatchSourceFile(name, contents) override def positionInUltimateSource(position: Position) = { if (!position.isDefined) super.positionInUltimateSource(position) else { - println("!!!") var off = position.point var compsLeft = components // the search here has to be against the length of the files underlying the @@ -171,7 +170,6 @@ extends BatchSourceFile(name, contents) // less than the underlying length.) Otherwise we can and will overshoot the // correct component and return a garbage position. while (compsLeft.head.underlyingLength-1 <= off && !compsLeft.tail.isEmpty) { - println("discarding "+compsLeft.head) off = off - compsLeft.head.underlyingLength + 1 compsLeft = compsLeft.tail } diff --git a/src/library/scala/Array.scala b/src/library/scala/Array.scala index f89e8b48a5..c9d929b88c 100644 --- a/src/library/scala/Array.scala +++ b/src/library/scala/Array.scala @@ -207,6 +207,7 @@ object Array extends FallbackArrayBuilding { */ def fill[T: ClassManifest](n: Int)(elem: => T): Array[T] = { val b = newBuilder[T] + b.sizeHint(n) var i = 0 while (i < n) { b += elem @@ -270,6 +271,7 @@ object Array extends FallbackArrayBuilding { */ def tabulate[T: ClassManifest](n: Int)(f: Int => T): Array[T] = { val b = newBuilder[T] + b.sizeHint(n) var i = 0 while (i < n) { b += f(i) @@ -343,6 +345,8 @@ object Array extends FallbackArrayBuilding { def range(start: Int, end: Int, step: Int): Array[Int] = { if (step == 0) throw new IllegalArgumentException("zero step") val b = newBuilder[Int] + b.sizeHint(Range.count(start, end, step, false)) + var i = start while (if (step < 0) end < i else i < end) { b += i @@ -360,6 +364,7 @@ object Array extends FallbackArrayBuilding { */ def iterate[T: ClassManifest](start: T, len: Int)(f: T => T): Array[T] = { val b = newBuilder[T] + b.sizeHint(len) var acc = start var i = 0 while (i < len) { diff --git a/src/library/scala/collection/BitSet.scala b/src/library/scala/collection/BitSet.scala index b43b681888..e362271f70 100644 --- a/src/library/scala/collection/BitSet.scala +++ b/src/library/scala/collection/BitSet.scala @@ -27,6 +27,8 @@ trait BitSet extends Set[Int] */ object BitSet extends BitSetFactory[BitSet] { val empty: BitSet = immutable.BitSet.empty + def newBuilder = immutable.BitSet.newBuilder + /** $canBuildFromInfo */ implicit def canBuildFrom: CanBuildFrom[BitSet, Int, BitSet] = bitsetCanBuildFrom } diff --git a/src/library/scala/collection/BitSetLike.scala b/src/library/scala/collection/BitSetLike.scala index 3028cca6eb..b4e9682d88 100644 --- a/src/library/scala/collection/BitSetLike.scala +++ b/src/library/scala/collection/BitSetLike.scala @@ -25,7 +25,7 @@ import mutable.StringBuilder * * @define bitsetinfo * Bitsets are sets of non-negative integers which are represented as - * variable-size arrays of bits packed into 64-bit words. The size of a bitset is + * variable-size arrays of bits packed into 64-bit words. The memory footprint of a bitset is * determined by the largest number stored in it. * @author Martin Odersky * @version 2.8 diff --git a/src/library/scala/collection/Iterator.scala b/src/library/scala/collection/Iterator.scala index 24c447b24b..5eca806933 100644 --- a/src/library/scala/collection/Iterator.scala +++ b/src/library/scala/collection/Iterator.scala @@ -11,8 +11,7 @@ package scala.collection -import mutable.{Buffer, ArrayBuffer, ListBuffer, StringBuilder} -import immutable.{List, Stream} +import mutable.ArrayBuffer import annotation.{ tailrec, migration } /** The `Iterator` object provides various functions for @@ -110,9 +109,15 @@ object Iterator { * @return the iterator producing the infinite sequence of values `start, f(start), f(f(start)), ...` */ def iterate[T](start: T)(f: T => T): Iterator[T] = new Iterator[T] { + private[this] var first = true private[this] var acc = start def hasNext: Boolean = true - def next(): T = { val res = acc ; acc = f(acc) ; res } + def next(): T = { + if (first) first = false + else acc = f(acc) + + acc + } } /** Creates an infinite-length iterator which returns successive values from some start value. diff --git a/src/library/scala/collection/SeqLike.scala b/src/library/scala/collection/SeqLike.scala index b9ac28cb2d..d80539d0b0 100644 --- a/src/library/scala/collection/SeqLike.scala +++ b/src/library/scala/collection/SeqLike.scala @@ -20,6 +20,7 @@ import generic._ object SeqLike { /** A KMP implementation, based on the undoubtedly reliable wikipedia entry. + * * @author paulp * @since 2.8 */ @@ -72,7 +73,23 @@ object SeqLike { None } - /** Waiting for a doc comment from Paul + /** Finds a particular index at which one sequence occurs in another sequence. + * Both the source sequence and the target sequence are expressed in terms + * other sequences S' and T' with offset and length parameters. This + * function is designed to wrap the KMP machinery in a sufficiently general + * way that all library sequence searches can use it. It is unlikely you + * have cause to call it directly: prefer functions such as StringBuilder#indexOf + * and Seq#lastIndexOf. + * + * @param source the sequence to search in + * @param sourceOffset the starting offset in source + * @param sourceCount the length beyond sourceOffset to search + * @param target the sequence being searched for + * @param targetOffset the starting offset in target + * @param targetCount the length beyond targetOffset which makes up the target string + * @param fromIndex the smallest index at which the target sequence may start + * + * @return the applicable index in source where target exists, or -1 if not found */ def indexOf[B]( source: Seq[B], sourceOffset: Int, sourceCount: Int, @@ -83,7 +100,10 @@ object SeqLike { case Some(x) => x + fromIndex } - /** Waiting for a doc comment from Paul + /** Finds a particular index at which one sequence occurs in another sequence. + * Like indexOf, but finds the latest occurrence rather than earliest. + * + * @see SeqLike#indexOf */ def lastIndexOf[B]( source: Seq[B], sourceOffset: Int, sourceCount: Int, diff --git a/src/library/scala/collection/Set.scala b/src/library/scala/collection/Set.scala index 034d9f1705..f1c1e43731 100644 --- a/src/library/scala/collection/Set.scala +++ b/src/library/scala/collection/Set.scala @@ -37,6 +37,7 @@ trait Set[A] extends (A => Boolean) * @define Coll Set */ object Set extends SetFactory[Set] { + def newBuilder[A] = immutable.Set.newBuilder[A] override def empty[A]: Set[A] = immutable.Set.empty[A] implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Set[A]] = setCanBuildFrom[A] } diff --git a/src/library/scala/collection/generic/BitSetFactory.scala b/src/library/scala/collection/generic/BitSetFactory.scala index 5679b25351..08cf7bd0e0 100644 --- a/src/library/scala/collection/generic/BitSetFactory.scala +++ b/src/library/scala/collection/generic/BitSetFactory.scala @@ -13,7 +13,7 @@ package scala.collection package generic import scala.collection._ -import mutable.{Builder, AddingBuilder} +import mutable.Builder /** @define coll collection * @define Coll Traversable @@ -28,8 +28,8 @@ import mutable.{Builder, AddingBuilder} * The standard `CanBuildFrom` instance for bitsets. */ trait BitSetFactory[Coll <: BitSet with BitSetLike[Coll]] { - def newBuilder: Builder[Int, Coll] = new AddingBuilder[Int, Coll](empty) def empty: Coll + def newBuilder: Builder[Int, Coll] def apply(elems: Int*): Coll = (empty /: elems) (_ + _) def bitsetCanBuildFrom = new CanBuildFrom[Coll, Int, Coll] { def apply(from: Coll) = newBuilder diff --git a/src/library/scala/concurrent/AsyncInvokable.scala b/src/library/scala/collection/generic/ImmutableSetFactory.scala index ac465c7fe5..a551786f25 100644 --- a/src/library/scala/concurrent/AsyncInvokable.scala +++ b/src/library/scala/collection/generic/ImmutableSetFactory.scala @@ -6,19 +6,13 @@ ** |/ ** \* */ -// $Id$ +package scala.collection +package generic +import mutable.{ Builder, AddingBuilder } -package scala.concurrent - -/** The <code>AsyncInvokable</code> trait... - * - * @author Philipp Haller - */ -trait AsyncInvokable[-T, +R] { - - type Future[+S] <: () => S - - def !!(task: T): Future[R] +abstract class ImmutableSetFactory[CC[X] <: immutable.Set[X] with SetLike[X, CC[X]]] + extends SetFactory[CC] { + def newBuilder[A]: Builder[A, CC[A]] = new AddingBuilder[A, CC[A]](empty[A]) } diff --git a/src/library/scala/collection/generic/MutableSetFactory.scala b/src/library/scala/collection/generic/MutableSetFactory.scala new file mode 100644 index 0000000000..28b5fdd897 --- /dev/null +++ b/src/library/scala/collection/generic/MutableSetFactory.scala @@ -0,0 +1,18 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2010, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.collection +package generic + +import mutable.{ Builder, GrowingBuilder } + +abstract class MutableSetFactory[CC[X] <: mutable.Set[X] with mutable.SetLike[X, CC[X]]] + extends SetFactory[CC] { + + def newBuilder[A]: Builder[A, CC[A]] = new GrowingBuilder[A, CC[A]](empty[A]) +} diff --git a/src/library/scala/collection/generic/SetFactory.scala b/src/library/scala/collection/generic/SetFactory.scala index 1c4ec3e7e3..d43664bd39 100644 --- a/src/library/scala/collection/generic/SetFactory.scala +++ b/src/library/scala/collection/generic/SetFactory.scala @@ -12,7 +12,7 @@ package scala.collection package generic -import mutable.{Builder, AddingBuilder} +import mutable.Builder /** A template for companion objects of `Set` and subclasses thereof. * @@ -34,7 +34,7 @@ import mutable.{Builder, AddingBuilder} abstract class SetFactory[CC[X] <: Set[X] with SetLike[X, CC[X]]] extends GenericCompanion[CC] { - def newBuilder[A]: Builder[A, CC[A]] = new AddingBuilder[A, CC[A]](empty[A]) + def newBuilder[A]: Builder[A, CC[A]] /** $setCanBuildFromInfo */ diff --git a/src/library/scala/collection/generic/TraversableFactory.scala b/src/library/scala/collection/generic/TraversableFactory.scala index b7fda70922..ffc4f16466 100644 --- a/src/library/scala/collection/generic/TraversableFactory.scala +++ b/src/library/scala/collection/generic/TraversableFactory.scala @@ -62,6 +62,10 @@ abstract class TraversableFactory[CC[X] <: Traversable[X] with GenericTraversabl */ def concat[A](xss: Traversable[A]*): CC[A] = { val b = newBuilder[A] + // At present we're using IndexedSeq as a proxy for "has a cheap size method". + if (xss forall (_.isInstanceOf[IndexedSeq[_]])) + b.sizeHint(xss map (_.size) sum) + for (xs <- xss) b ++= xs b.result } @@ -73,6 +77,7 @@ abstract class TraversableFactory[CC[X] <: Traversable[X] with GenericTraversabl */ def fill[A](n: Int)(elem: => A): CC[A] = { val b = newBuilder[A] + b.sizeHint(n) var i = 0 while (i < n) { b += elem @@ -130,6 +135,7 @@ abstract class TraversableFactory[CC[X] <: Traversable[X] with GenericTraversabl */ def tabulate[A](n: Int)(f: Int => A): CC[A] = { val b = newBuilder[A] + b.sizeHint(n) var i = 0 while (i < n) { b += f(i) @@ -201,6 +207,7 @@ abstract class TraversableFactory[CC[X] <: Traversable[X] with GenericTraversabl def range(start: Int, end: Int, step: Int): CC[Int] = { if (step == 0) throw new IllegalArgumentException("zero step") val b = newBuilder[Int] + b.sizeHint(Range.count(start, end, step, false)) var i = start while (if (step < 0) end < i else i < end) { b += i @@ -218,6 +225,7 @@ abstract class TraversableFactory[CC[X] <: Traversable[X] with GenericTraversabl */ def iterate[A](start: A, len: Int)(f: A => A): CC[A] = { val b = newBuilder[A] + b.sizeHint(len) var acc = start var i = 0 while (i < len) { diff --git a/src/library/scala/collection/immutable/BitSet.scala b/src/library/scala/collection/immutable/BitSet.scala index 9b801f26cb..8fa23c3ddf 100644 --- a/src/library/scala/collection/immutable/BitSet.scala +++ b/src/library/scala/collection/immutable/BitSet.scala @@ -14,6 +14,7 @@ package immutable import generic._ import BitSetLike.{LogWL, updateArray} +import mutable.{ Builder, AddingBuilder } /** A class for immutable bitsets. * $bitsetinfo @@ -60,10 +61,12 @@ abstract class BitSet extends Set[Int] * @define coll immutable bitset */ object BitSet extends BitSetFactory[BitSet] { - /** The empty bitset */ val empty: BitSet = new BitSet1(0L) + /** An adding builder for immutable Sets. */ + def newBuilder: Builder[Int, BitSet] = new AddingBuilder[Int, BitSet](empty) + /** $bitsetCanBuildFrom */ implicit def canBuildFrom: CanBuildFrom[BitSet, Int, BitSet] = bitsetCanBuildFrom diff --git a/src/library/scala/collection/immutable/DefaultMap.scala b/src/library/scala/collection/immutable/DefaultMap.scala index 667d86d352..4f36679119 100755 --- a/src/library/scala/collection/immutable/DefaultMap.scala +++ b/src/library/scala/collection/immutable/DefaultMap.scala @@ -50,7 +50,7 @@ trait DefaultMap[A, +B] extends Map[A, B] { self => */ override def - (key: A): Map[A, B] = { val b = newBuilder - b ++= this filter (key !=) + for (kv <- this ; if kv._1 != key) b += kv b.result } } diff --git a/src/library/scala/collection/immutable/HashSet.scala b/src/library/scala/collection/immutable/HashSet.scala index 68da0cef50..481b1c3204 100644 --- a/src/library/scala/collection/immutable/HashSet.scala +++ b/src/library/scala/collection/immutable/HashSet.scala @@ -88,7 +88,7 @@ class HashSet[A] extends Set[A] * @define mayNotTerminateInf * @define willNotTerminateInf */ -object HashSet extends SetFactory[HashSet] { +object HashSet extends ImmutableSetFactory[HashSet] { /** $setCanBuildFromInfo */ implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, HashSet[A]] = setCanBuildFrom[A] override def empty[A]: HashSet[A] = EmptyHashSet.asInstanceOf[HashSet[A]] diff --git a/src/library/scala/collection/immutable/List.scala b/src/library/scala/collection/immutable/List.scala index 4ce441ca55..f9937f6925 100644 --- a/src/library/scala/collection/immutable/List.scala +++ b/src/library/scala/collection/immutable/List.scala @@ -19,9 +19,9 @@ import annotation.tailrec /** A class for immutable linked lists representing ordered collections * of elements of type. * - * This class comes with two implementing case classes `scala.Nil` - * and `scala.::` that implement the abstract members `isEmpty`, - * `head` and `tail`. + * This class comes with two implementing case classes `scala.Nil` + * and `scala.::` that implement the abstract members `isEmpty`, + * `head` and `tail`. * * @author Martin Odersky and others * @version 2.8 diff --git a/src/library/scala/collection/immutable/ListSet.scala b/src/library/scala/collection/immutable/ListSet.scala index 2a202df9ef..979fdff552 100644 --- a/src/library/scala/collection/immutable/ListSet.scala +++ b/src/library/scala/collection/immutable/ListSet.scala @@ -19,7 +19,7 @@ import generic._ * @define coll immutable list set * @since 1 */ -object ListSet extends SetFactory[ListSet] { +object ListSet extends ImmutableSetFactory[ListSet] { /** setCanBuildFromInfo */ implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, ListSet[A]] = setCanBuildFrom[A] override def empty[A] = new ListSet[A] diff --git a/src/library/scala/collection/immutable/Queue.scala b/src/library/scala/collection/immutable/Queue.scala index 7a903cb201..7ffceb6be8 100644 --- a/src/library/scala/collection/immutable/Queue.scala +++ b/src/library/scala/collection/immutable/Queue.scala @@ -14,6 +14,7 @@ package immutable import generic._ import mutable.{ Builder, ListBuffer } +import annotation.tailrec /** `Queue` objects implement data structures that allow to * insert and retrieve elements in a first-in-first-out (FIFO) manner. @@ -29,9 +30,9 @@ import mutable.{ Builder, ListBuffer } @serializable @SerialVersionUID(-7622936493364270175L) class Queue[+A] protected(protected val in: List[A], protected val out: List[A]) - extends Seq[A] + extends LinearSeq[A] with GenericTraversableTemplate[A, Queue] - with SeqLike[A, Queue[A]] { + with LinearSeqLike[A, Queue[A]] { override def companion: GenericCompanion[Queue] = Queue @@ -42,7 +43,7 @@ class Queue[+A] protected(protected val in: List[A], protected val out: List[A]) * @return the element at position `n` in this queue. * @throws Predef.NoSuchElementException if the queue is too short. */ - def apply(n: Int): A = { + override def apply(n: Int): A = { val len = out.length if (n < len) out.apply(n) else { @@ -62,9 +63,19 @@ class Queue[+A] protected(protected val in: List[A], protected val out: List[A]) */ override def isEmpty: Boolean = in.isEmpty && out.isEmpty + override def head: A = + if (out.nonEmpty) out.head + else if (in.nonEmpty) in.last + else throw new NoSuchElementException("head on empty queue") + + override def tail: Queue[A] = + if (out.nonEmpty) new Queue(in, out.tail) + else if (in.nonEmpty) new Queue(Nil, in.reverse.tail) + else throw new NoSuchElementException("tail on empty queue") + /** Returns the length of the queue. */ - def length = in.length + out.length + override def length = in.length + out.length /** Creates a new queue with element added at the end * of the old queue. @@ -101,7 +112,7 @@ class Queue[+A] protected(protected val in: List[A], protected val out: List[A]) * @param iter an iterable object */ def enqueue[B >: A](iter: Iterable[B]) = - new Queue(iter.iterator.toList.reverse ::: in, out) + new Queue(iter.toList.reverse ::: in, out) /** Returns a tuple with the first element in the queue, * and a new queue with this element removed. @@ -121,10 +132,7 @@ class Queue[+A] protected(protected val in: List[A], protected val out: List[A]) * @throws Predef.NoSuchElementException * @return the first element. */ - def front: A = - if (!out.isEmpty) out.head - else if (!in.isEmpty) in.last - else throw new NoSuchElementException("front on empty queue") + def front: A = head /** Returns a string representation of this queue. */ diff --git a/src/library/scala/collection/immutable/Range.scala b/src/library/scala/collection/immutable/Range.scala index 8f1f7d58a6..0f20b1ea04 100644 --- a/src/library/scala/collection/immutable/Range.scala +++ b/src/library/scala/collection/immutable/Range.scala @@ -219,6 +219,23 @@ class Range(val start: Int, val end: Int, val step: Int) extends IndexedSeq[Int] object Range { private[immutable] val MAX_PRINT = 512 // some arbitrary value + /** Calculates the number of elements in a range given start, end, step, and + * whether or not it is inclusive. Returns -1 if parameters are invalid. + */ + def count(start: Int, end: Int, step: Int): Int = count(start, end, step, false) + def count(start: Int, end: Int, step: Int, isInclusive: Boolean): Int = { + def last = + if (isInclusive && step < 0) end - 1 + else if (isInclusive && step > 0) end + 1 + else end + + if (step == 0) -1 + else if (start == end) { if (isInclusive) 1 else 0 } + else if (end > start != step > 0) -1 + else if (step == 1 || step == -1) last - start + else ((last - start - 1) / step) + 1 + } + class Inclusive(start: Int, end: Int, step: Int) extends Range(start, end, step) { override def isInclusive = true override protected def copy(start: Int, end: Int, step: Int): Range = new Inclusive(start, end, step) diff --git a/src/library/scala/collection/immutable/Set.scala b/src/library/scala/collection/immutable/Set.scala index 769ee37f6b..745b0034e8 100644 --- a/src/library/scala/collection/immutable/Set.scala +++ b/src/library/scala/collection/immutable/Set.scala @@ -36,7 +36,7 @@ trait Set[A] extends Iterable[A] * @define Coll immutable.Set * @define coll immutable set */ -object Set extends SetFactory[Set] { +object Set extends ImmutableSetFactory[Set] { /** $setCanBuildFromInfo */ implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Set[A]] = setCanBuildFrom[A] override def empty[A]: Set[A] = EmptySet.asInstanceOf[Set[A]] diff --git a/src/library/scala/collection/immutable/Stack.scala b/src/library/scala/collection/immutable/Stack.scala index 0323db7211..c90ab379af 100644 --- a/src/library/scala/collection/immutable/Stack.scala +++ b/src/library/scala/collection/immutable/Stack.scala @@ -48,7 +48,8 @@ object Stack extends SeqFactory[Stack] { * @define willNotTerminateInf */ @serializable @SerialVersionUID(1976480595012942526L) -class Stack[+A] protected (protected val elems: List[A]) extends LinearSeq[A] +class Stack[+A] protected (protected val elems: List[A]) + extends LinearSeq[A] with GenericTraversableTemplate[A, Stack] with LinearSeqOptimized[A, Stack[A]] { override def companion: GenericCompanion[Stack] = Stack diff --git a/src/library/scala/collection/immutable/Stream.scala b/src/library/scala/collection/immutable/Stream.scala index 50fa07c2cf..fbda0b918b 100644 --- a/src/library/scala/collection/immutable/Stream.scala +++ b/src/library/scala/collection/immutable/Stream.scala @@ -104,6 +104,16 @@ self => loop(this, "") } + override def length: Int = { + var len = 0 + var left = this + while (!left.isEmpty) { + len += 1 + left = left.tail + } + len + } + /** It's an imperfect world, but at least we can bottle up the * imperfection in a capsule. */ diff --git a/src/library/scala/collection/immutable/TreeSet.scala b/src/library/scala/collection/immutable/TreeSet.scala index 7b1f71df5b..18c65dd373 100644 --- a/src/library/scala/collection/immutable/TreeSet.scala +++ b/src/library/scala/collection/immutable/TreeSet.scala @@ -13,7 +13,7 @@ package scala.collection package immutable import generic._ -import mutable.{Builder, AddingBuilder} +import mutable.{ Builder, AddingBuilder } /** $factoryInfo * @define Coll immutable.TreeSet diff --git a/src/library/scala/collection/mutable/AddingBuilder.scala b/src/library/scala/collection/mutable/AddingBuilder.scala index 14636c9feb..54137b6a26 100644 --- a/src/library/scala/collection/mutable/AddingBuilder.scala +++ b/src/library/scala/collection/mutable/AddingBuilder.scala @@ -17,9 +17,12 @@ import generic._ * which adds an element to the collection. * * Collections are built from their empty element using this `+` method. - * @param empty the empty element of the collection. - * @tparam Elem the type of elements that get added to the builder. - * @tparam To the type of the built collection. + * @param empty the empty element of the collection. + * @tparam Elem the type of elements that get added to the builder. + * @tparam To the type of the built collection. + * + * @note "efficient `+`" is not idle talk. Do not use this on mutable collections or any others + * for which `+` may perform an unshared copy! See GrowingBuilder comments for more. * * @author Martin Odersky * @version 2.8 diff --git a/src/library/scala/collection/mutable/BitSet.scala b/src/library/scala/collection/mutable/BitSet.scala index 5d0f5863ca..da3e95fce7 100644 --- a/src/library/scala/collection/mutable/BitSet.scala +++ b/src/library/scala/collection/mutable/BitSet.scala @@ -114,6 +114,10 @@ class BitSet(protected var elems: Array[Long]) extends Set[Int] */ object BitSet extends BitSetFactory[BitSet] { def empty: BitSet = new BitSet + + /** A growing builder for mutable Sets. */ + def newBuilder: Builder[Int, BitSet] = new GrowingBuilder[Int, BitSet](empty) + /** $bitsetCanBuildFrom */ implicit def canBuildFrom: CanBuildFrom[BitSet, Int, BitSet] = bitsetCanBuildFrom } diff --git a/src/library/scala/collection/mutable/GrowingBuilder.scala b/src/library/scala/collection/mutable/GrowingBuilder.scala index cd1c8bec8c..781753c24d 100644 --- a/src/library/scala/collection/mutable/GrowingBuilder.scala +++ b/src/library/scala/collection/mutable/GrowingBuilder.scala @@ -18,6 +18,11 @@ import generic._ * interacting surprisingly with any2stringadd thus driving '+' out of the `Seq` * hierarchy. The tendrils of original sin should never be underestimated. * + * Addendum: of even greater significance is that '+' on mutable collections now + * creates a new collection. This means using AddingBuilder on them will create + * a new intermediate collection for every element given to the builder, taking + * '+' from an O(1) to O(n) operation. + * * @author Paul Phillips * @version 2.8 * @since 2.8 diff --git a/src/library/scala/collection/mutable/HashSet.scala b/src/library/scala/collection/mutable/HashSet.scala index 3d9dc3e55d..41f9a17dd6 100644 --- a/src/library/scala/collection/mutable/HashSet.scala +++ b/src/library/scala/collection/mutable/HashSet.scala @@ -79,7 +79,7 @@ class HashSet[A] extends Set[A] * @define Coll mutable.HashSet * @define coll mutable hash set */ -object HashSet extends SetFactory[HashSet] { +object HashSet extends MutableSetFactory[HashSet] { implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, HashSet[A]] = setCanBuildFrom[A] override def empty[A]: HashSet[A] = new HashSet[A] } diff --git a/src/library/scala/collection/mutable/IndexedSeqLike.scala b/src/library/scala/collection/mutable/IndexedSeqLike.scala index 0c2d3f17bb..5e932effe0 100644 --- a/src/library/scala/collection/mutable/IndexedSeqLike.scala +++ b/src/library/scala/collection/mutable/IndexedSeqLike.scala @@ -45,7 +45,7 @@ trait IndexedSeqLike[A, +Repr] extends scala.collection.IndexedSeqLike[A, Repr] /** Replaces element at given index with a new value. * * @param n the index of the element to replace. - * @param lem the new value. + * @param elem the new value. * @throws IndexOutofBoundsException if the index is not valid. */ def update(idx: Int, elem: A) diff --git a/src/library/scala/collection/mutable/LinkedHashSet.scala b/src/library/scala/collection/mutable/LinkedHashSet.scala index 7ecb71e23b..e91b03048d 100644 --- a/src/library/scala/collection/mutable/LinkedHashSet.scala +++ b/src/library/scala/collection/mutable/LinkedHashSet.scala @@ -87,7 +87,7 @@ class LinkedHashSet[A] extends Set[A] * @define Coll LinkedHashSet * @define coll linked hash set */ -object LinkedHashSet extends SetFactory[LinkedHashSet] { +object LinkedHashSet extends MutableSetFactory[LinkedHashSet] { implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, LinkedHashSet[A]] = setCanBuildFrom[A] override def empty[A]: LinkedHashSet[A] = new LinkedHashSet[A] } diff --git a/src/library/scala/collection/mutable/MapLike.scala b/src/library/scala/collection/mutable/MapLike.scala index 676a325c9b..f9c0f7232c 100644 --- a/src/library/scala/collection/mutable/MapLike.scala +++ b/src/library/scala/collection/mutable/MapLike.scala @@ -191,9 +191,10 @@ trait MapLike[A, B, +This <: MapLike[A, B, This] with Map[A, B]] * * @param p The test predicate */ - @deprecated("cannot be type inferred because of retain in Iterable.") def retain(p: (A, B) => Boolean): this.type = { - for ((k, v) <- this) if (!p(k, v)) -=(k) + for ((k, v) <- this ; if !p(k, v)) + this -= k + this } diff --git a/src/library/scala/collection/mutable/Set.scala b/src/library/scala/collection/mutable/Set.scala index 2e7f1cbc6b..bebf66157a 100644 --- a/src/library/scala/collection/mutable/Set.scala +++ b/src/library/scala/collection/mutable/Set.scala @@ -32,7 +32,7 @@ trait Set[A] extends Iterable[A] * @define coll mutable set * @define Coll mutable.Set */ -object Set extends SetFactory[Set] { +object Set extends MutableSetFactory[Set] { implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Set[A]] = setCanBuildFrom[A] override def empty[A]: Set[A] = HashSet.empty[A] } diff --git a/src/library/scala/collection/mutable/StringBuilder.scala b/src/library/scala/collection/mutable/StringBuilder.scala index 40e7e991ee..f43acdc796 100644 --- a/src/library/scala/collection/mutable/StringBuilder.scala +++ b/src/library/scala/collection/mutable/StringBuilder.scala @@ -15,14 +15,17 @@ package mutable import generic._ import compat.Platform.arraycopy import scala.reflect.Manifest +import annotation.migration +import StringBuilder._ -/** A builder for mutable sequence of characters. This class provides an API compatible - * with <a class="java/lang/StringBuilder" href="" target="_top">`java.lang.StringBuilder`</a>. +/** A builder for mutable sequence of characters. This class provides an API + * mostly compatible with java.lang.StringBuilder, except where there are conflicts + * with the Scala collections API (such as the `reverse` method.) * * @author Stephane Micheloud * @author Martin Odersky * @version 2.8 - * @since 2.8 + * @since 2.7 */ @serializable @SerialVersionUID(0 - 8525408645367278351L) @@ -70,71 +73,50 @@ final class StringBuilder(initCapacity: Int, private val initValue: String) */ def clear(): Unit = setLength(0) - /** Sets the length of the character sequence. + /** Sets the length of the character sequence. If the current sequence + * is shorter than the given length, it is padded with nulls; if it is + * longer, it is truncated. * - * @param newLength the new length - * @throws IndexOutOfBoundsException if the <code>n</code> argument is negative. + * @param len the new length + * @throws IndexOutOfBoundsException if the argument is negative. */ - def setLength(n: Int) { - require(n >= 0, n) - while (count < n) append('\0') - count = n + def setLength(len: Int) { + require(len >= 0, len) + while (count < len) append('\0') + count = len } - /** Returns the current capacity. The capacity is the amount of storage - * available for newly inserted characters, beyond which an allocation - * will occur. + /** Returns the current capacity, which is the size of the underlying array. + * A new array will be allocated if the current capacity is exceeded. * - * @return the current capacity + * @return the capacity */ def capacity: Int = array.length - /** Same as <code>ensureCapacity</code>. */ - @deprecated("use `ensureCapacity' instead. An assignment is misleading because\n"+ + @deprecated("Use `ensureCapacity' instead. An assignment is misleading because\n"+ "it can never decrease the capacity.") def capacity_=(n: Int) { ensureCapacity(n) } - /** <p> - * Ensures that the capacity is at least equal to the specified minimum. - * If the current capacity is less than the argument, then a new internal - * array is allocated with greater capacity. The new capacity is the larger of: - * </p> - * <ul> - * <li>The <code>n</code> argument. - * <li>Twice the old capacity, plus <code>2</code>. - * </ul> - * <p> - * If the <code>n</code> argument is non-positive, this - * method takes no action and simply returns. - * </p> - * - * @param n the minimum desired capacity. - */ - def ensureCapacity(n: Int) { - if (n > array.length) { - var newsize = (array.length * 2) max 1 - while (n > newsize) - newsize = newsize * 2 - val newar = new Array[Char](newsize) - arraycopy(array, 0, newar, 0, count) - array = newar + /** Ensure that the capacity is at least the given argument. + * If the argument is greater than the current capacity, new + * storage will be allocated with size equal to the given + * argument or to (2 * capacity + 2), whichever is larger. + * + * @param newCapacity the minimum desired capacity. + */ + def ensureCapacity(newCapacity: Int): Unit = + if (newCapacity > array.length) { + val newSize = (array.length * 2 + 2) max newCapacity + val newArray = new Array[Char](newSize) + arraycopy(array, 0, newArray, 0, count) + array = newArray } - } - /** <p> - * Returns the <code>Char</code> value in this sequence at the specified index. - * The first <code>Char</code> value is at index <code>0</code>, the next at index - * <code>1</code>, and so on, as in array indexing. - * </p> - * <p> - * The index argument must be greater than or equal to - * <code>0</code>, and less than the length of this sequence. - * </p> - * - * @param index the index of the desired <code>Char</code> value. - * @return the <code>Char</code> value at the specified index. - * @throws IndexOutOfBoundsException if <code>index</code> is - * negative or greater than or equal to <code>length()</code>. + /** Returns the Char at the specified index, counting from 0 as in Arrays. + * + * @param index the index to look up + * @return the Char at the given index. + * @throws IndexOutOfBoundsException if the index is out of bounds. */ def charAt(index: Int): Char = { if (index < 0 || index >= count) @@ -142,18 +124,16 @@ final class StringBuilder(initCapacity: Int, private val initValue: String) array(index) } - /** Same as <code>charAt</code>. */ + /** Equivalent to charAt. + */ def apply(i: Int): Char = charAt(i) - /** <p> - * Removes the <code>Char</code> at the specified position in this - * sequence. This sequence is shortened by one <code>Char</code>. - * </p> + /** Removes the Char at the specified index. The sequence is + * shortened by one. * - * @param index Index of <code>Char</code> to remove - * @return This object. - * @throws StringIndexOutOfBoundsException if the <code>index</code> - * is negative or greater than or equal to <code>length()</code>. + * @param index The index to remove. + * @return This StringBuilder. + * @throws IndexOutOfBoundsException if the index is out of bounds. */ def deleteCharAt(index: Int): StringBuilder = { if (index < 0 || index >= count) @@ -163,21 +143,11 @@ final class StringBuilder(initCapacity: Int, private val initValue: String) this } - /** <p> - * The character at the specified index is set to <code>ch</code>. This - * sequence is altered to represent a new character sequence that is - * identical to the old character sequence, except that it contains the - * character <code>ch</code> at position <code>index</code>. - * </p> - * <p> - * The index argument must be greater than or equal to - * <code>0</code>, and less than the length of this sequence. - * </p> - * - * @param index the index of the character to modify. - * @param ch the new character. - * @throws IndexOutOfBoundsException if <code>index</code> is - * negative or greater than or equal to <code>length()</code>. + /** Update the sequence at the given index to hold the specified Char. + * + * @param index the index to modify. + * @param ch the new Char. + * @throws IndexOutOfBoundsException if the index is out of bounds. */ def setCharAt(index: Int, ch: Char) { if (index < 0 || index >= count) @@ -185,33 +155,32 @@ final class StringBuilder(initCapacity: Int, private val initValue: String) array(index) = ch } - /** Same as <code>setCharAt</code>. */ + /** Equivalent to setCharAt. + */ def update(i: Int, c: Char) { setCharAt(i, c) } - /** Returns a new <code>String</code> that contains a subsequence of - * characters currently contained in this character sequence. The - * substring begins at the specified index and extends to the end of - * this sequence. + /** Returns a new String made up of a subsequence of this sequence, + * beginning at the given index and extending to the end of the sequence. * - * @param start The beginning index, inclusive. - * @return The new string. - * @throws StringIndexOutOfBoundsException if <code>start</code> is - * less than zero, or greater than the length of this object. + * target.substring(start) is equivalent to target.drop(start) + * + * @param start The starting index, inclusive. + * @return The new String. + * @throws IndexOutOfBoundsException if the index is out of bounds. */ - def substring(start: Int): String = substring(start, count) + def substring(start: Int): String = substring(start, length) - /** Returns a new <code>String</code> that contains a subsequence of - * characters currently contained in this sequence. The - * substring begins at the specified <code>start</code> and - * extends to the character at index <code>end - 1</code>. + /** Returns a new String made up of a subsequence of this sequence, + * beginning at the start index (inclusive) and extending to the + * end index (exclusive). + * + * target.substring(start, end) is equivalent to target.slice(start, end).mkString * * @param start The beginning index, inclusive. * @param end The ending index, exclusive. - * @return The new string. - * @throws StringIndexOutOfBoundsException if <code>start</code> - * or <code>end</code> are negative or greater than - * <code>length()</code>, or <code>start</code> is - * greater than <code>end</code>. + * @return The new String. + * @throws StringIndexOutOfBoundsException If either index is out of bounds, + * or if start > end. */ def substring(start: Int, end: Int): String = { if (start < 0) @@ -225,36 +194,29 @@ final class StringBuilder(initCapacity: Int, private val initValue: String) def subSequence(start: Int, end: Int): java.lang.CharSequence = substring(start, end) - /* Appends the string representation of the <code>Any</code> argument. + /** Appends the given Char to the end of the sequence. */ def +=(x: Char): this.type = { append(x); this } + /** !!! This should create a new sequence. + */ def +(x: Char): this.type = { +=(x); this } - - /** <p> - * Appends the string representation of the <code>Any</code> - * argument. - * </p> - * <p> - * The argument is converted to a string as if by the method - * <code>String.valueOf</code>, and the characters of that - * string are then appended to this sequence. - * </p> + /** Appends the string representation of the given argument, + * which is converted to a String with String.valueOf. * * @param x an <code>Any</code> object. - * @return a reference to this object. + * @return this StringBuilder. */ - def append(x: Any): StringBuilder = - append(String.valueOf(x)) + def append(x: Any): StringBuilder = append(String.valueOf(x)) - /** Appends the specified string to this character sequence. + /** Appends the given String to this sequence. * - * @param s a string. - * @return a reference to this object. + * @param s a String. + * @return this StringBuilder. */ def append(s: String): StringBuilder = { - val str = if (s == null) "null" else s + val str = onull(s) val len = str.length ensureCapacity(count + len) str.getChars(0, len, array, count) @@ -278,92 +240,48 @@ final class StringBuilder(initCapacity: Int, private val initValue: String) this } - /** <p> - * Appends the string representation of the <code>Char</code> sequence - * argument to this sequence. - * </p> - * <p> - * The characters of the sequence argument are appended, in order, - * to the contents of this sequence. The length of this sequence - * increases by the length of the argument. - * </p> + /** Appends all the Chars in the given Seq[Char] to this sequence. * - * @param x the characters to be appended. - * @return a reference to this object. + * @param xs the characters to be appended. + * @return this StringBuilder. */ - def appendAll(x: Seq[Char]): StringBuilder = - appendAll(x.toArray, 0, x.length) + def appendAll(xs: Seq[Char]): StringBuilder = appendAll(xs.toArray, 0, xs.length) - @deprecated("use appendAll instead. This method is deprecated because of the\n"+ - "possible confusion with `append(Any)'.") - def append(x: Seq[Char]): StringBuilder = - appendAll(x) - - /** <p> - * Appends the string representation of the <code>Char</code> array - * argument to this sequence. - * </p> - * <p> - * The characters of the array argument are appended, in order, to - * the contents of this sequence. The length of this sequence - * increases by the length of the argument. - * </p> + /** Appends all the Chars in the given Array[Char] to this sequence. * - * @param x the characters to be appended. - * @return a reference to this object. + * @param xs the characters to be appended. + * @return a reference to this object. */ - def appendAll(x: Array[Char]): StringBuilder = - appendAll(x, 0, x.length) + def appendAll(xs: Array[Char]): StringBuilder = appendAll(xs, 0, xs.length) - @deprecated("use appendAll instead. This method is deprecated because\n"+ - "of the possible confusion with `append(Any)'.") - def append(x: Array[Char]): StringBuilder = - appendAll(x) - - /** <p> - * Appends the string representation of a subarray of the - * <code>char</code> array argument to this sequence. - * </p> - * <p> - * Characters of the <code>Char</code> array <code>x</code>, starting at - * index <code>offset</code>, are appended, in order, to the contents - * of this sequence. The length of this sequence increases - * by the value of <code>len</code>. - * </p> - * - * @param x the characters to be appended. - * @param offset the index of the first <code>Char</code> to append. - * @param len the number of <code>Char</code>s to append. - * @return a reference to this object. - */ - def appendAll(x: Array[Char], offset: Int, len: Int): StringBuilder = { + /** Appends a portion of the given Array[Char] to this sequence. + * + * @param xs the Array containing Chars to be appended. + * @param offset the index of the first Char to append. + * @param len the numbers of Chars to append. + * @return this StringBuilder. + */ + def appendAll(xs: Array[Char], offset: Int, len: Int): StringBuilder = { ensureCapacity(count + len) - arraycopy(x, offset, array, count, len) + arraycopy(xs, offset, array, count, len) count += len this } - @deprecated("use appendAll instead. This method is deprecated because\n"+ - "of the possible confusion with `append(Any, Int, Int)'.") - def append(x: Array[Char], offset: Int, len: Int): StringBuilder = - appendAll(x, offset, len) - - /** <p> - * Appends the string representation of the <code>Boolean</code> - * argument to the sequence. - * </p> - * <p> - * The argument is converted to a string as if by the method - * <code>String.valueOf</code>, and the characters of that - * string are then appended to this sequence. - * </p> - * - * @param x a <code>Boolean</code>. - * @return a reference to this object. + /** Append the String representation of the given primitive type + * to this sequence. The argument is converted to a String with + * String.valueOf. + * + * @param x a primitive value + * @return This StringBuilder. */ def append(x: Boolean): StringBuilder = append(String.valueOf(x)) def append(x: Byte): StringBuilder = append(String.valueOf(x)) - + def append(x: Short): StringBuilder = append(String.valueOf(x)) + def append(x: Int): StringBuilder = append(String.valueOf(x)) + def append(x: Long): StringBuilder = append(String.valueOf(x)) + def append(x: Float): StringBuilder = append(String.valueOf(x)) + def append(x: Double): StringBuilder = append(String.valueOf(x)) def append(x: Char): StringBuilder = { ensureCapacity(count + 1) array(count) = x @@ -371,33 +289,14 @@ final class StringBuilder(initCapacity: Int, private val initValue: String) this } - def append(x: Short): StringBuilder = - append(String.valueOf(x)) - - def append(x: Int): StringBuilder = - append(String.valueOf(x)) - - def append(x: Long): StringBuilder = - append(String.valueOf(x)) - - def append(x: Float): StringBuilder = - append(String.valueOf(x)) - - def append(x: Double): StringBuilder = - append(String.valueOf(x)) - - /** Removes the characters in a substring of this sequence. - * The substring begins at the specified <code>start</code> and extends to - * the character at index <code>end - 1</code> or to the end of the - * sequence if no such character exists. If - * <code>start</code> is equal to <code>end</code>, no changes are made. + /** Remove a subsequence of Chars from this sequence, starting at the + * given start index (inclusive) and extending to the end index (exclusive) + * or to the end of the String, whichever comes first. * * @param start The beginning index, inclusive. * @param end The ending index, exclusive. - * @return This object. - * @throws StringIndexOutOfBoundsException if <code>start</code> - * is negative, greater than <code>length()</code>, or - * greater than <code>end</code>. + * @return This StringBuilder. + * @throws StringIndexOutOfBoundsException if start < 0 || start > end */ def delete(start: Int, end: Int): StringBuilder = { if (start < 0 || start > end) @@ -411,20 +310,14 @@ final class StringBuilder(initCapacity: Int, private val initValue: String) this } - /** Replaces the characters in a substring of this sequence - * with characters in the specified <code>String</code>. The substring - * begins at the specified <code>start</code> and extends to the character - * at index <code>end - 1</code> or to the end of the sequence if no such - * character exists. First the characters in the substring are removed and - * then the specified <code>String</code> is inserted at <code>start</code>. + /** Replaces a subsequence of Chars with the given String. The semantics + * are as in delete, with the String argument then inserted at index 'start'. * * @param start The beginning index, inclusive. * @param end The ending index, exclusive. - * @param str String that will replace previous contents. - * @return This object. - * @throws StringIndexOutOfBoundsException if <code>start</code> - * is negative, greater than <code>length()</code>, or - * greater than <code>end</code>. + * @param str The String to be inserted at the start index. + * @return This StringBuilder. + * @throws StringIndexOutOfBoundsException if start < 0, start > length, or start > end */ def replace(start: Int, end: Int, str: String) { if (start < 0 || start > count || start > end) @@ -441,25 +334,17 @@ final class StringBuilder(initCapacity: Int, private val initValue: String) this } - /** Inserts the string representation of a subarray of the <code>str</code> - * array argument into this sequence. The subarray begins at the specified - * <code>offset</code> and extends <code>len</code> <code>char</code>s. - * The characters of the subarray are inserted into this sequence at - * the position indicated by <code>index</code>. The length of this - * sequence increases by <code>len</code> <code>Char</code>s. - * - * @param index position at which to insert subarray. - * @param str a <code>Char</code> array. - * @param offset the index of the first <code>char</code> in subarray to - * be inserted. - * @param len the number of <code>Char</code>s in the subarray to - * be inserted. - * @return This object - * @throws StringIndexOutOfBoundsException if <code>index</code> - * is negative or greater than <code>length()</code>, or - * <code>offset</code> or <code>len</code> are negative, or - * <code>(offset+len)</code> is greater than - * <code>str.length</code>. + /** Inserts a subarray of the given Array[Char] at the given index + * of this sequence. + * + * @param index index at which to insert the subarray. + * @param str the Array from which Chars will be taken. + * @param offset the index of the first Char to insert. + * @param len the number of Chars from 'str' to insert. + * @return This StringBuilder. + * + * @throws StringIndexOutOfBoundsException if index < 0, index > length, + * offset < 0, len < 0, or (offset + len) > str.length. */ def insertAll(index: Int, str: Array[Char], offset: Int, len: Int): StringBuilder = { if (index < 0 || index > count) @@ -475,368 +360,172 @@ final class StringBuilder(initCapacity: Int, private val initValue: String) this } - @deprecated("use insertAll instead. This method is deprecated because of the\n"+ - "possible confusion with `insert(Int, Any, Int, Int)'.") - def insert(index: Int, str: Array[Char], offset: Int, len: Int): StringBuilder = - insertAll(index, str, offset, len) - - /** <p> - * Inserts the string representation of the <code>Any</code> - * argument into this character sequence. - * </p> - * <p> - * The second argument is converted to a string as if by the method - * <code>String.valueOf</code>, and the characters of that - * string are then inserted into this sequence at the indicated - * offset. - * </p> - * <p> - * The offset argument must be greater than or equal to - * <code>0</code>, and less than or equal to the length of this - * sequence. - * </p> - * - * @param offset the offset. - * @param x an <code>Any</code> value. - * @return a reference to this object. - * @throws StringIndexOutOfBoundsException if the offset is invalid. - */ - def insert(at: Int, x: Any): StringBuilder = - insert(at, String.valueOf(x)) - - /** Inserts the string into this character sequence. - * - * @param at the offset position. - * @param x a string. - * @return a reference to this object. - * @throws StringIndexOutOfBoundsException if the offset is invalid. + /** Inserts the String representation (via String.valueOf) of the given + * argument into this sequence at the given index. + * + * @param index the index at which to insert. + * @param x a value. + * @return this StringBuilder. + * @throws StringIndexOutOfBoundsException if the index is out of bounds. */ - def insert(at: Int, x: String): StringBuilder = { - if (at < 0 || at > count) - throw new StringIndexOutOfBoundsException(at) - val str = if (x == null) "null" else x - val len = str.length - ensureCapacity(count + len) - arraycopy(array, at, array, at + len, count - at) - str.getChars(0, len, array, at) - count += len - this - } + def insert(index: Int, x: Any): StringBuilder = insert(index, String.valueOf(x)) - /** Inserts the string representation of the <code>Char</code> sequence - * argument into this sequence. + /** Inserts the String into this character sequence. * - * @param at the offset position. - * @param x a character sequence. - * @return a reference to this object. - * @throws StringIndexOutOfBoundsException if the offset is invalid. + * @param index the index at which to insert. + * @param x a String. + * @return this StringBuilder. + * @throws StringIndexOutOfBoundsException if the index is out of bounds. */ - def insertAll(at: Int, x: Seq[Char]): StringBuilder = - insertAll(at, x.toArray) + def insert(index: Int, x: String): StringBuilder = insertAll(index, x.toArray) - @deprecated("use insertAll instead. This method is deprecated because of\n"+ - "the possible confusion with `insert(Int, Any)'.") - def insert(at: Int, x: Seq[Char]): StringBuilder = - insertAll(at, x) + /** Inserts the given Seq[Char] into this sequence at the given index. + * + * @param index the index at which to insert. + * @param xs the Seq[Char]. + * @return this StringBuilder. + * @throws StringIndexOutOfBoundsException if the index is out of bounds. + */ + def insertAll(index: Int, xs: Seq[Char]): StringBuilder = insertAll(index, xs.toArray) - /** Inserts the string representation of the <code>Char</code> array - * argument into this sequence. + /** Inserts the given Array[Char] into this sequence at the given index. * - * @param at the offset position. - * @param x a character array. - * @return a reference to this object. - * @throws StringIndexOutOfBoundsException if the offset is invalid. + * @param index the index at which to insert. + * @param xs the Array[Char]. + * @return this StringBuilder. + * @throws StringIndexOutOfBoundsException if the index is out of bounds. */ - def insertAll(at: Int, x: Array[Char]): StringBuilder = { - if (at < 0 || at > count) - throw new StringIndexOutOfBoundsException(at) - val len = x.length + def insertAll(index: Int, xs: Array[Char]): StringBuilder = { + if (index < 0 || index > count) + throw new StringIndexOutOfBoundsException(index) + val len = xs.length ensureCapacity(count + len) - arraycopy(array, at, array, at + len, count - at) - arraycopy(x, 0, array, at, len) + arraycopy(array, index, array, index + len, count - index) + arraycopy(xs, 0, array, index, len) count += len this } + /** Calls String.valueOf on the given primitive value, and inserts the + * String at the given index. + * + * @param index the offset position. + * @param x a primitive value. + * @return this StringBuilder. + */ + def insert(index: Int, x: Boolean): StringBuilder = insert(index, String.valueOf(x)) + def insert(index: Int, x: Byte): StringBuilder = insert(index, String.valueOf(x)) + def insert(index: Int, x: Short): StringBuilder = insert(index, String.valueOf(x)) + def insert(index: Int, x: Int): StringBuilder = insert(index, String.valueOf(x)) + def insert(index: Int, x: Long): StringBuilder = insert(index, String.valueOf(x)) + def insert(index: Int, x: Float): StringBuilder = insert(index, String.valueOf(x)) + def insert(index: Int, x: Double): StringBuilder = insert(index, String.valueOf(x)) + def insert(index: Int, x: Char): StringBuilder = { + if (index < 0 || index > count) + throw new StringIndexOutOfBoundsException(index) + ensureCapacity(count + 1) + arraycopy(array, index, array, index + 1, count - index) + array(index) = x + count += 1 + this + } + + @deprecated("Use appendAll instead. This method is deprecated because of the\n"+ + "possible confusion with `append(Any)'.") + def append(x: Seq[Char]): StringBuilder = appendAll(x) + + @deprecated("use appendAll instead. This method is deprecated because\n"+ + "of the possible confusion with `append(Any)'.") + def append(x: Array[Char]): StringBuilder = appendAll(x) + + @deprecated("use appendAll instead. This method is deprecated because\n"+ + "of the possible confusion with `append(Any, Int, Int)'.") + def append(x: Array[Char], offset: Int, len: Int): StringBuilder = appendAll(x, offset, len) + + @deprecated("use insertAll instead. This method is deprecated because of the\n"+ + "possible confusion with `insert(Int, Any, Int, Int)'.") + def insert(index: Int, str: Array[Char], offset: Int, len: Int): StringBuilder = + insertAll(index, str, offset, len) + + @deprecated("use insertAll instead. This method is deprecated because of\n"+ + "the possible confusion with `insert(Int, Any)'.") + def insert(at: Int, x: Seq[Char]): StringBuilder = insertAll(at, x) + @deprecated("use insertAll instead. This method is deprecated because of\n"+ "the possible confusion with `insert(Int, Any)'.") def insert(at: Int, x: Array[Char]): StringBuilder = insertAll(at, x) - /** <p> - * Inserts the string representation of the <code>Boolean</code> argument - * into this sequence. - * </p> - * <p> - * The offset argument must be greater than or equal to 0, and less than - * or equal to the length of this sequence. - * </p> - * - * @param at the offset position. - * @param x a <code>Boolean</code> value. - * @return a reference to this object. - */ - def insert(at: Int, x: Boolean): StringBuilder = - insert(at, String.valueOf(x)) - - /** <p> - * Inserts the string representation of the <code>Byte</code> argument - * into this sequence. - * </p> - * <p> - * The offset argument must be greater than or equal to 0, and less than - * or equal to the length of this sequence. - * </p> + /** Finds the index of the first occurrence of the specified substring. * - * @param at the offset position. - * @param x a <code>Byte</code> value. - * @return a reference to this object. + * @param str the target string to search for + * @return the first applicable index where target occurs, or -1 if not found. */ - def insert(at: Int, x: Byte): StringBuilder = - insert(at, String.valueOf(x)) + def indexOf(str: String): Int = indexOf(str, 0) - /** <p> - * Inserts the string representation of the <code>Char</code> argument - * into this sequence. - * </p> - * <p> - * The offset argument must be greater than or equal to 0, and less than - * or equal to the length of this sequence. - * </p> + /** Finds the index of the first occurrence of the specified substring. * - * @param at the offset position. - * @param x a <code>Char</code> value. - * @return a reference to this object. - */ - def insert(at: Int, x: Char): StringBuilder = { - if (at < 0 || at > count) - throw new StringIndexOutOfBoundsException(at) - ensureCapacity(count + 1) - arraycopy(array, at, array, at + 1, count - at) - array(at) = x - count += 1 - this - } - - /** <p> - * Inserts the string representation of the <code>Short</code> argument - * into this sequence. - * </p> - * <p> - * The offset argument must be greater than or equal to 0, and less than - * or equal to the length of this sequence. - * </p> - * - * @param at the offset position. - * @param x a <code>Short</code> value. - * @return a reference to this object. + * @param str the target string to search for + * @param fromIndex the smallest index in the source string to consider + * @return the first applicable index where target occurs, or -1 if not found. */ - def insert(at: Int, x: Short): StringBuilder = - insert(at, String.valueOf(x)) + def indexOf(str: String, fromIndex: Int): Int = indexOfSlice(str.toIndexedSeq, fromIndex) - /** <p> - * Inserts the string representation of the <code>Int</code> argument - * into this sequence. - * </p> - * <p> - * The offset argument must be greater than or equal to 0, and less than - * or equal to the length of this sequence. - * </p> + /** Finds the index of the last occurrence of the specified substring. * - * @param at the offset position. - * @param x a <code>Int</code> value. - * @return a reference to this object. + * @param str the target string to search for + * @return the last applicable index where target occurs, or -1 if not found. */ - def insert(at: Int, x: Int): StringBuilder = - insert(at, String.valueOf(x)) + def lastIndexOf(str: String): Int = lastIndexOf(str, count) - /** <p> - * Inserts the string representation of the <code>Long</code> argument - * into this sequence. - * </p> - * <p> - * The offset argument must be greater than or equal to 0, and less than - * or equal to the length of this sequence. - * </p> + /** Finds the index of the last occurrence of the specified substring. * - * @param at the offset position. - * @param x a <code>Long</code> value. - * @return a reference to this object. + * @param str the target string to search for + * @param fromIndex the smallest index in the source string to consider + * @return the last applicable index where target occurs, or -1 if not found. */ - def insert(at: Int, x: Long): StringBuilder = - insert(at, String.valueOf(x)) + def lastIndexOf(str: String, fromIndex: Int): Int = lastIndexOfSlice(str.toIndexedSeq, fromIndex) - /** <p> - * Inserts the string representation of the <code>Float</code> argument - * into this sequence. - * </p> - * <p> - * The offset argument must be greater than or equal to 0, and less than - * or equal to the length of this sequence. - * </p> + /** Creates a new StringBuilder with the reversed contents of this one. + * If surrogate pairs are present, they are treated as indivisible units: each + * pair will appear in the same order in the updated sequence. * - * @param at the offset position. - * @param x a <code>Float</code> value. - * @return a reference to this object. + * @return the reversed StringBuilder */ - def insert(at: Int, x: Float): StringBuilder = - insert(at, String.valueOf(x)) + @migration(2, 8, "Since 2.8 reverse returns a new instance. Use 'reverseContents' to update in place.") + override def reverse: StringBuilder = new StringBuilder(this.toString).reverseContents() - /** <p> - * Inserts the string representation of the <code>Double</code> argument - * into this sequence. - * </p> - * <p> - * The offset argument must be greater than or equal to 0, and less than - * or equal to the length of this sequence. - * </p> + /** Like reverse, but destructively updates the target StringBuilder. * - * @param at the offset position. - * @param x a <code>Double</code> value. - * @return a reference to this object. - */ - def insert(at: Int, x: Double): StringBuilder = - insert(at, String.valueOf(x)) - - /** <p> - * Returns the index within this string of the first occurrence of the - * specified substring. The integer returned is the smallest value - * <i>k</i> such that: - * </p> - * <blockquote><pre> - * this.toString().startsWith(str, <i>k</i>)</pre> - * </blockquote> - * <p> - * is <code>true</code>. - * </p> - * - * @param str any string. - * @return if the string argument occurs as a substring within this - * object, then the index of the first character of the first - * such substring is returned; if it does not occur as a - * substring, <code>-1</code> is returned. - * @throws NullPointerException if <code>str</code> is <code>null</code>. + * @return the reversed StringBuilder (same as the target StringBuilder) */ - def indexOf(str: String): Int = indexOf(str, 0) + def reverseContents(): StringBuilder = { + // record of indices of pairs which need to be swapped + val surrogates = new ListBuffer[(Int, Int)] + val half = count / 2 - /** <p> - * Returns the index within this string of the first occurrence of the - * specified substring, starting at the specified index. The integer - * returned is the smallest value <code>k</code> for which: - * </p><pre> - * k >= math.min(fromIndex, str.length()) && - * this.toString().startsWith(str, k)</pre> - * <p> - * If no such value of <code>k</code> exists, then <code>-1</code> - * is returned. - * </p> - * - * @param str the substring for which to search. - * @param fromIndex the index from which to start the search. - * @return the index within this string of the first occurrence - * of the specified substring, starting at the specified index. - */ - def indexOf(str: String, fromIndex: Int): Int = indexOfSlice(str.toIndexedSeq, fromIndex) - - /** <p> - * Returns the index within this string of the rightmost occurrence - * of the specified substring. The rightmost empty string "" is - * considered to occur at the index value <code>this.length()</code>. - * The returned index is the largest value <i>k</i> such that - * </p> - * <blockquote><pre> - * this.toString().startsWith(str, k)</pre> - * </blockquote> - * <p> - * is true. - * </p> - * - * @param str the substring to search for. - * @return if the string argument occurs one or more times as a substring - * within this object, then the index of the first character of - * the last such substring is returned. If it does not occur as - * a substring, <code>-1</code> is returned. - * @throws NullPointerException if <code>str</code> is <code>null</code>. - */ - def lastIndexOf(str: String): Int = lastIndexOf(str, count) + def mirror(x: Int) = count - 1 - x + def swap(i1: Int, i2: Int) { + val tmp = array(i2) + array(i2) = array(i1) + array(i1) = tmp + } - /** <p> - * Returns the index within this string of the last occurrence of the - * specified substring. The integer returned is the largest value - * <code>k</code> such that: - * </p><pre>val - * k <= math.min(fromIndex, str.length()) && - * this.toString().startsWith(str, k)</pre> - * <p> - * If no such value of <code>k</code> exists, then <code>-1</code> - * is returned. - * </p> - * - * @param str the substring to search for. - * @param fromIndex the index to start the search from. - * @return the index within this sequence of the last occurrence - * of the specified substring. - */ - def lastIndexOf(str: String, fromIndex: Int): Int = lastIndexOfSlice(str.toIndexedSeq, fromIndex) + for ((i, j) <- 0 until half zip (count - 1 to half by -1)) { + if (array(i).isSurrogate && array(i + 1).isSurrogate) + surrogates += ((j - 1, j)) + if (array(j).isSurrogate && array(j - 1).isSurrogate) + surrogates += ((i, i + 1)) - /** <p> - * Causes this character sequence to be replaced by the reverse of the - * sequence. If there are any surrogate pairs included in the sequence, - * these are treated as single characters for the reverse operation. - * Thus, the order of the high-low surrogates is never reversed. - * </p> - * <p> - * Let <i>n</i> be the character length of this character sequence - * (not the length in <code>Char</code> values) just prior to - * execution of the <code>reverse</code> method. Then the - * character at index <i>k</i> in the new character sequence is - * equal to the character at index <i>n-k-1</i> in the old - * character sequence. - * </p> - * - * @return a reference to this object. - */ - override def reverse(): StringBuilder = { - var hasSurrogate = false - val n = count - 1 - var j = (n-1) >> 1 - while (j >= 0) { - val temp = array(j) - val temp2 = array(n - j) - if (!hasSurrogate) - hasSurrogate = - (temp >= Character.MIN_HIGH_SURROGATE && temp <= Character.MAX_LOW_SURROGATE) || - (temp2 >= Character.MIN_HIGH_SURROGATE && temp2 <= Character.MAX_LOW_SURROGATE) - array(j) = temp2 - array(n - j) = temp - j -= 1 - } - if (hasSurrogate) { - // Reverse back all valid surrogate pairs - var i = 0 - while (i < count - 1) { - val c2 = array(i) - if (Character.isLowSurrogate(c2)) { - val c1 = array(i + 1) - if (Character.isHighSurrogate(c1)) { - array(i) = c1; i += 1 - array(i) = c2 - } - } - i += 1 - } + swap(i, j) } + surrogates foreach (swap _).tupled this } - /** Returns a string representing the data in this sequence. - * A new <code>String</code> object is allocated and initialized to - * contain the character sequence currently represented by this - * object. This <code>String</code> is then returned. Subsequent - * changes to this sequence do not affect the contents of the - * <code>String</code>. + /** Returns a new String representing the data in this sequence. * - * @return a string representation of this sequence of characters. + * @return the current contents of this sequence as a String */ override def toString: String = new String(array, 0, count) @@ -852,4 +541,7 @@ object StringBuilder arraycopy(src, 0, dest, 0, src.length min newLength) dest } + + // for mimicking java's propensity to make null into "null" + private def onull(s: String): String = if (s == null) "null" else s } diff --git a/src/library/scala/concurrent/TaskRunner.scala b/src/library/scala/concurrent/TaskRunner.scala index 0853007a28..f7d3002b83 100644 --- a/src/library/scala/concurrent/TaskRunner.scala +++ b/src/library/scala/concurrent/TaskRunner.scala @@ -25,9 +25,4 @@ trait TaskRunner { def shutdown(): Unit - /** If expression computed successfully return it in <code>Right</code>, - * otherwise return exception in <code>Left</code>. - */ - protected def tryCatch[A](body: => A): Either[Exception, A] = - ops tryCatchEx body } diff --git a/src/library/scala/concurrent/ThreadRunner.scala b/src/library/scala/concurrent/ThreadRunner.scala index 13bf20ff2a..5cfc076699 100644 --- a/src/library/scala/concurrent/ThreadRunner.scala +++ b/src/library/scala/concurrent/ThreadRunner.scala @@ -25,6 +25,14 @@ class ThreadRunner extends FutureTaskRunner { implicit def functionAsTask[S](fun: () => S): Task[S] = fun implicit def futureAsFunction[S](x: Future[S]): () => S = x + /* If expression computed successfully return it in `Right`, + * otherwise return exception in `Left`. + */ + private def tryCatch[A](body: => A): Either[Exception, A] = + try Right(body) catch { + case ex: Exception => Left(ex) + } + def execute[S](task: Task[S]) { val runnable = new Runnable { def run() { tryCatch(task()) } @@ -38,7 +46,7 @@ class ThreadRunner extends FutureTaskRunner { def run() { result set tryCatch(task()) } } (new Thread(runnable)).start() - () => ops getOrThrow result.get + () => result.get.fold[S](throw _, identity _) } def managedBlock(blocker: ManagedBlocker) { diff --git a/src/library/scala/concurrent/ops.scala b/src/library/scala/concurrent/ops.scala index 6537a47258..49be1b882c 100644 --- a/src/library/scala/concurrent/ops.scala +++ b/src/library/scala/concurrent/ops.scala @@ -23,21 +23,13 @@ object ops val defaultRunner: FutureTaskRunner = TaskRunners.threadRunner /** - * If expression computed successfully return it in <code>Right</code>, - * otherwise return exception in <code>Left</code>. + * If expression computed successfully return it in `Right`, + * otherwise return exception in `Left`. */ - //TODO: make private - def tryCatch[A](body: => A): Either[Throwable, A] = + private def tryCatch[A](body: => A): Either[Throwable, A] = allCatch[A] either body - //TODO: make private - def tryCatchEx[A](body: => A): Either[Exception, A] = - try Right(body) catch { - case ex: Exception => Left(ex) - } - - //TODO: make private - def getOrThrow[T <: Throwable, A](x: Either[T, A]): A = + private def getOrThrow[T <: Throwable, A](x: Either[T, A]): A = x.fold[A](throw _, identity _) /** Evaluates an expression asynchronously. diff --git a/src/library/scala/math/BigDecimal.scala b/src/library/scala/math/BigDecimal.scala index c4cccd1f52..67604e8b78 100644 --- a/src/library/scala/math/BigDecimal.scala +++ b/src/library/scala/math/BigDecimal.scala @@ -19,24 +19,23 @@ import scala.collection.immutable.NumericRange * @version 1.0 * @since 2.7 */ -object BigDecimal -{ - @serializable - object RoundingMode extends Enumeration(java.math.RoundingMode.values map (_.toString) : _*) { - type RoundingMode = Value - val UP, DOWN, CEILING, FLOOR, HALF_UP, HALF_DOWN, HALF_EVEN, UNNECESSARY = Value - } - +object BigDecimal { private val minCached = -512 private val maxCached = 512 - val MinLong = BigDecimal(Long.MinValue) - val MaxLong = BigDecimal(Long.MaxValue) + val defaultMathContext = MathContext.UNLIMITED + + val MinLong = new BigDecimal(BigDec valueOf Long.MinValue, defaultMathContext) + val MaxLong = new BigDecimal(BigDec valueOf Long.MaxValue, defaultMathContext) /** Cache ony for defaultMathContext using BigDecimals in a small range. */ private lazy val cache = new Array[BigDecimal](maxCached - minCached + 1) - val defaultMathContext = MathContext.UNLIMITED + @serializable + object RoundingMode extends Enumeration(java.math.RoundingMode.values map (_.toString) : _*) { + type RoundingMode = Value + val UP, DOWN, CEILING, FLOOR, HALF_UP, HALF_DOWN, HALF_EVEN, UNNECESSARY = Value + } /** Constructs a <code>BigDecimal</code> using the java BigDecimal static * valueOf constructor. diff --git a/src/library/scala/reflect/Manifest.scala b/src/library/scala/reflect/Manifest.scala index b7cb86e1bd..d718011ff9 100644 --- a/src/library/scala/reflect/Manifest.scala +++ b/src/library/scala/reflect/Manifest.scala @@ -245,16 +245,15 @@ object Manifest { override def toString = prefix.toString+"#"+name+argString } - /** Manifest for the abstract type `prefix # name'. `upperBound' is not - * strictly necessary as it could be obtained by reflection. It was - * added so that erasure can be calculated without reflection. - * todo: remove after next bootstrap + /** Manifest for the unknown type `_ >: L <: U' in an existential. */ - def abstractType[T](prefix: Manifest[_], name: String, upperbound: ClassManifest[_], args: Manifest[_]*): Manifest[T] = + def wildcardType[T](lowerBound: Manifest[_], upperBound: Manifest[_]): Manifest[T] = new (Manifest[T] @serializable) { - def erasure = upperbound.erasure - override val typeArguments = args.toList - override def toString = prefix.toString+"#"+name+argString + def erasure = upperBound.erasure + override def toString = + "_" + + (if (lowerBound eq Nothing) "" else " >: "+lowerBound) + + (if (upperBound eq Nothing) "" else " <: "+upperBound) } /** Manifest for the intersection type `parents_0 with ... with parents_n'. */ diff --git a/src/library/scala/reflect/generic/Flags.scala b/src/library/scala/reflect/generic/Flags.scala index f0f1f14ade..c8ef529bc1 100755 --- a/src/library/scala/reflect/generic/Flags.scala +++ b/src/library/scala/reflect/generic/Flags.scala @@ -53,9 +53,10 @@ class Flags { final val ACCESSOR = 0x08000000 // a value or variable accessor (getter or setter) final val SUPERACCESSOR = 0x10000000 // a super accessor - final val PARAMACCESSOR = 0x20000000 // for value definitions: is an access method - // for a final val parameter - // for parameters: is a val parameter + final val PARAMACCESSOR = 0x20000000 // for field definitions generated for primary constructor + // parameters (no matter if it's a 'val' parameter or not) + // for parameters of a primary constructor ('val' or not) + // for the accessor methods generated for 'val' or 'var' parameters final val MODULEVAR = 0x40000000 // for variables: is the variable caching a module value final val SYNTHETICMETH = 0x40000000 // for methods: synthetic method, but without SYNTHETIC flag final val MONOMORPHIC = 0x40000000 // for type symbols: does not have type parameters diff --git a/src/library/scala/reflect/generic/Trees.scala b/src/library/scala/reflect/generic/Trees.scala index df93d157e3..c880a335de 100755 --- a/src/library/scala/reflect/generic/Trees.scala +++ b/src/library/scala/reflect/generic/Trees.scala @@ -37,6 +37,7 @@ trait Trees { self: Universe => def isProtected = hasFlag(PROTECTED) def isPublic = !isPrivate && !isProtected def isSealed = hasFlag(SEALED ) + def isSynthetic = hasFlag(SYNTHETIC) def isTrait = hasFlag(TRAIT ) def isVariable = hasFlag(MUTABLE ) diff --git a/src/library/scala/runtime/RichChar.scala b/src/library/scala/runtime/RichChar.scala index f5e2625fd8..1c95186558 100644 --- a/src/library/scala/runtime/RichChar.scala +++ b/src/library/scala/runtime/RichChar.scala @@ -48,6 +48,7 @@ final class RichChar(x: Char) extends Proxy with Ordered[Char] { def isSpaceChar: Boolean = Character.isSpaceChar(x) def isHighSurrogate: Boolean = Character.isHighSurrogate(x) def isLowSurrogate: Boolean = Character.isLowSurrogate(x) + def isSurrogate: Boolean = isHighSurrogate || isLowSurrogate def isUnicodeIdentifierStart: Boolean = Character.isUnicodeIdentifierStart(x) def isUnicodeIdentifierPart: Boolean = Character.isUnicodeIdentifierPart(x) def isIdentifierIgnorable: Boolean = Character.isIdentifierIgnorable(x) diff --git a/src/library/scala/transient.scala b/src/library/scala/transient.scala index d8b8ee4d86..aea7ba7b5f 100644 --- a/src/library/scala/transient.scala +++ b/src/library/scala/transient.scala @@ -11,4 +11,7 @@ package scala +import annotation.target._ + +@field class transient extends StaticAnnotation diff --git a/src/library/scala/util/parsing/combinator/RegexParsers.scala b/src/library/scala/util/parsing/combinator/RegexParsers.scala index 89c5050431..fd8ee60db0 100644 --- a/src/library/scala/util/parsing/combinator/RegexParsers.scala +++ b/src/library/scala/util/parsing/combinator/RegexParsers.scala @@ -67,6 +67,25 @@ trait RegexParsers extends Parsers { } } + /** `positioned' decorates a parser's result with the start position of the input it consumed. + * If whitespace is being skipped, then it is skipped before the start position is recorded. + * + * @param p a `Parser' whose result conforms to `Positional'. + * @return A parser that has the same behaviour as `p', but which marks its result with the + * start position of the input it consumed after whitespace has been skipped, if it + * didn't already have a position. + */ + override def positioned[T <: Positional](p: => Parser[T]): Parser[T] = { + val pp = super.positioned(p) + new Parser[T] { + def apply(in: Input) = { + val offset = in.offset + val start = handleWhiteSpace(in.source, offset) + pp(in.drop (start - offset)) + } + } + } + override def phrase[T](p: Parser[T]): Parser[T] = super.phrase(p <~ opt("""\z""".r)) diff --git a/src/library/scala/volatile.scala b/src/library/scala/volatile.scala index dda0493bf9..005cd6628c 100644 --- a/src/library/scala/volatile.scala +++ b/src/library/scala/volatile.scala @@ -11,4 +11,7 @@ package scala +import annotation.target._ + +@field class volatile extends StaticAnnotation diff --git a/src/library/scala/xml/NodeSeq.scala b/src/library/scala/xml/NodeSeq.scala index 3b56ba25e4..0cab5422b6 100644 --- a/src/library/scala/xml/NodeSeq.scala +++ b/src/library/scala/xml/NodeSeq.scala @@ -73,16 +73,18 @@ abstract class NodeSeq extends immutable.Seq[Node] with SeqLike[Node, NodeSeq] w case _ => false } - /** Projection function. Similar to XPath, use <code>this \ "foo"</code> - * to get a list of all elements of this sequence that are labelled with - * <code>"foo"</code>. Use <code>\ "_"</code> as a wildcard. Use - * <code>ns \ "@foo"</code> to get the unprefixed attribute "foo". - * Use <code>ns \ "@{uri}foo"</code> to get the prefixed attribute - * "pre:foo" whose prefix "pre" is resolved to the namespace "uri". - * For attribute projections, the resulting NodeSeq attribute values are - * wrapped in a Group. - * There is no support for searching a prefixed attribute by - * its literal prefix. + /** Projection function, which returns elements of `this` sequence based on the string `that`. Use: + * - `this \ "foo"` to get a list of all elements that are labelled with `"foo"`; + * - `\ "_"` to get a list of all elements (wildcard); + * - `ns \ "@foo"` to get the unprefixed attribute `"foo"`; + * - `ns \ "@{uri}foo"` to get the prefixed attribute `"pre:foo"` whose prefix `"pre"` is resolved to the + * namespace `"uri"`. + * + * For attribute projections, the resulting [[scala.xml.NodeSeq]] attribute values are wrapped in a + * [[scala.xml.Group]]. + * + * There is no support for searching a prefixed attribute by its literal prefix. + * * The document order is preserved. * * @param that ... @@ -120,16 +122,19 @@ abstract class NodeSeq extends immutable.Seq[Node] with SeqLike[Node, NodeSeq] w } } - /** projection function. Similar to XPath, use <code>this \\ 'foo</code> - * to get a list of all elements of this sequence that are labelled with - * <code>"foo"</code>. Use <code>\\ "_"</code> as a wildcard. Use - * <code>ns \\ "@foo"</code> to get the unprefixed attribute "foo". - * Use <code>ns \\ "@{uri}foo"</code> to get each prefixed attribute - * "pre:foo" whose prefix "pre" is resolved to the namespace "uri". - * For attribute projections, the resulting NodeSeq attribute values are - * wrapped in a Group. - * There is no support for searching a prefixed attribute by - * its literal prefix. + /** Projection function, which returns elements of `this` sequence and of all its subsequences, based on + * the string `that`. Use: + * - `this \\ 'foo` to get a list of all elements that are labelled with `"foo"`; + * - `\\ "_"` to get a list of all elements (wildcard); + * - `ns \\ "@foo"` to get the unprefixed attribute `"foo"`; + * - `ns \\ "@{uri}foo"` to get each prefixed attribute `"pre:foo"` whose prefix `"pre"` is resolved to the + * namespace `"uri"`. + * + * For attribute projections, the resulting [[scala.xml.NodeSeq]] attribute values are wrapped in a + * [[scala.xml.Group]]. + * + * There is no support for searching a prefixed attribute by its literal prefix. + * * The document order is preserved. * * @param that ... diff --git a/src/library/scala/xml/Utility.scala b/src/library/scala/xml/Utility.scala index 48a23dc389..8637148489 100644 --- a/src/library/scala/xml/Utility.scala +++ b/src/library/scala/xml/Utility.scala @@ -194,22 +194,24 @@ object Utility extends AnyRef with parsing.TokenTests minimizeTags: Boolean = false): StringBuilder = { x match { - case c: Comment if !stripComments => c buildString sb - case x: SpecialNode => x buildString sb - case g: Group => for (c <- g.nodes) toXML(c, x.scope, sb) ; sb + case c: Comment => if (!stripComments) c buildString sb else sb + case x: SpecialNode => x buildString sb + case g: Group => + g.nodes foreach {toXML(_, x.scope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)} + sb case _ => // print tag with namespace declarations sb.append('<') x.nameToString(sb) if (x.attributes ne null) x.attributes.buildString(sb) x.scope.buildString(sb, pscope) - if (x.child.isEmpty && minimizeTags) + if (x.child.isEmpty && minimizeTags) { // no children, so use short form: <xyz .../> sb.append(" />") - else { + } else { // children, so use long form: <xyz ...>...</xyz> sb.append('>') - sequenceToXML(x.child, x.scope, sb, stripComments) + sequenceToXML(x.child, x.scope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) sb.append("</") x.nameToString(sb) sb.append('>') @@ -221,20 +223,23 @@ object Utility extends AnyRef with parsing.TokenTests children: Seq[Node], pscope: NamespaceBinding = TopScope, sb: StringBuilder = new StringBuilder, - stripComments: Boolean = false): Unit = + stripComments: Boolean = false, + decodeEntities: Boolean = true, + preserveWhitespace: Boolean = false, + minimizeTags: Boolean = false): Unit = { if (children.isEmpty) return else if (children forall isAtomAndNotText) { // add space val it = children.iterator val f = it.next - toXML(f, pscope, sb) + toXML(f, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) while (it.hasNext) { val x = it.next sb.append(' ') - toXML(x, pscope, sb) + toXML(x, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) } } - else children foreach { toXML(_, pscope, sb) } + else children foreach { toXML(_, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) } } /** diff --git a/src/library/scala/xml/Xhtml.scala b/src/library/scala/xml/Xhtml.scala index 744fe260c2..162ea5696a 100644 --- a/src/library/scala/xml/Xhtml.scala +++ b/src/library/scala/xml/Xhtml.scala @@ -49,11 +49,11 @@ object Xhtml (minimizableElements contains x.label) x match { - case c: Comment if !stripComments => c buildString sb + case c: Comment => if (!stripComments) c buildString sb case er: EntityRef if decodeEntities => decode(er) case x: SpecialNode => x buildString sb case g: Group => - g.nodes foreach { toXhtml(_, x.scope, sb) } + g.nodes foreach { toXhtml(_, x.scope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) } case _ => sb.append('<') @@ -64,7 +64,7 @@ object Xhtml if (shortForm) sb.append(" />") else { sb.append('>') - sequenceToXML(x.child, x.scope, sb) + sequenceToXML(x.child, x.scope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) sb.append("</") x.nameToString(sb) sb.append('>') @@ -89,9 +89,9 @@ object Xhtml val doSpaces = children forall isAtomAndNotText // interleave spaces for (c <- children.take(children.length - 1)) { - toXhtml(c, pscope, sb) + toXhtml(c, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) if (doSpaces) sb append ' ' } - toXhtml(children.last, pscope, sb) + toXhtml(children.last, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) } } diff --git a/src/partest/scala/tools/partest/Actions.scala b/src/partest/scala/tools/partest/Actions.scala index ce554959eb..5cbe64ce23 100644 --- a/src/partest/scala/tools/partest/Actions.scala +++ b/src/partest/scala/tools/partest/Actions.scala @@ -35,11 +35,11 @@ trait Actions { val cmd = fromArgs(args) if (isVerbose) { - trace(execEnv.mkString("ENV(", "\n", "\n)")) + trace("runExec: " + execEnv.mkString("ENV(", "\n", "\n)")) execCwd foreach (x => trace("CWD(" + x + ")")) } - trace(cmd) + trace("runExec: " + cmd) isDryRun || execAndLog(cmd) } @@ -69,20 +69,19 @@ trait Actions { trait ScriptableTest { self: TestEntity => - // def customTestStep(line: String): TestStep - /** Translates a line from a .cmds file into a teststep. */ def customTestStep(line: String): TestStep = { + trace("customTestStep: " + line) val (cmd, rest) = line span (x => !Character.isWhitespace(x)) - val args = toArgs(rest) + def qualify(name: String) = sourcesDir / name path + val args = toArgs(rest) map qualify def fail: TestStep = (_: TestEntity) => error("Parse error: did not understand '%s'" format line) val f: TestEntity => Boolean = cmd match { case "scalac" => _ scalac args case "javac" => _ javac args case "scala" => _ runScala args - case "diff" => if (args.size != 2) fail else _ => diffFiles(File(args(0)), File(args(1))) == "" case _ => fail } f @@ -109,6 +108,8 @@ trait Actions { * to know exactly when and how two-pass compilation fails. */ def compile() = { + trace("compile: " + sourceFiles) + def compileJava() = javac(javaSources) def compileScala() = scalac(scalaSources) def compileAll() = scalac(allSources) @@ -123,54 +124,82 @@ trait Actions { self: TestEntity => def checkFile: File = withExtension("check").toFile - def isCheckPresent = checkFile.isFile || { - warnAndLog("A checkFile at '%s' is mandatory.\n" format checkFile.path) - false - } + def checkFileRequired = + returning(checkFile.isFile)(res => if (!res) warnAndLog("A checkFile at '%s' is mandatory.\n" format checkFile.path)) - def normalizePaths(s: String) = { - /** This accomodates slash/backslash issues by noticing when a given - * line was altered, which means it held a path, and only then converting any - * backslashes to slashes. It's not foolproof but it's as close as we - * can get in one line. - */ - val s2 = s.replaceAll("""(?m)\Q%s\E""" format (sourcesDir + File.separator), "") - if (s != s2) s2.replaceAll("""\\""", "/") else s2 - } + lazy val sourceFileNames = sourceFiles map (_.name) + + /** Given the difficulty of verifying that any selective approach works + * everywhere, the algorithm now is to look for the name of any known + * source file for this test, and if seen, remove all the non-whitespace + * preceding it. (Paths with whitespace don't work anyway.) This should + * wipe out all slashes, backslashes, C:\, cygwin/windows differences, + * and whatever else makes a simple diff not simple. + * + * The log and check file are both transformed, which I don't think is + * correct -- only the log should be -- but doing it this way until I + * can clarify martin's comments in #3283. + */ + def normalizePaths(s: String) = + sourceFileNames.foldLeft(s)((res, name) => res.replaceAll("""\S+\Q%s\E""" format name, name)) - /** The default cleanup normalizes paths relative to sourcesDir. + /** The default cleanup normalizes paths relative to sourcesDir, + * absorbs line terminator differences by going to lines and back, + * and trims leading or trailing whitespace. */ - def diffCleanup(f: File) = safeLines(f) map normalizePaths mkString ("", "\n", "\n") + def diffCleanup(f: File) = safeLines(f) map normalizePaths mkString "\n" trim + + /** diffFiles requires actual Files as arguments but the output we want + * is the post-processed versions of log/check, so we resort to tempfiles. + */ + lazy val diffOutput = { + if (!checkFile.exists) "" else { + val input = diffCleanup(checkFile) + val output = diffCleanup(logFile) + def asFile(s: String) = returning(File.makeTemp("partest-diff"))(_ writeAll s) + + if (input == output) "" + else diffFiles(asFile(input), asFile(output)) + } + } + private def checkTraceName = tracePath(checkFile) + private def logTraceName = tracePath(logFile) + private def isDiffConfirmed = checkFile.exists && (diffOutput == "") + + private def sendTraceMsg() { + def result = + if (isDryRun) "" + else if (isDiffConfirmed) " [passed]" + else if (checkFile.exists) " [failed]" + else " [unchecked]" + + trace("diff %s %s%s".format(checkTraceName, logTraceName, result)) + } /** If optional is true, a missing check file is considered * a successful diff. Necessary since many categories use * checkfiles in an ad hoc manner. */ - def runDiff(check: File, log: File) = { - def arg1 = tracePath(check) - def arg2 = tracePath(log) - def noCheck = !check.exists && returning(true)(_ => trace("diff %s %s [unchecked]".format(arg1, arg2))) - def output = diffCleanup(log) - def matches = safeSlurp(check).trim == output.trim - - def traceMsg = - if (isDryRun) "diff %s %s".format(arg1, arg2) - else "diff %s %s [%s]".format(arg1, arg2, (if (matches) "passed" else "failed")) - - noCheck || { - trace(traceMsg) - - isDryRun || matches || (isUpdateCheck && { - normal("** diff %s %s failed:\n".format(arg1, arg2)) - normal(diffOutput()) - normal("** updating %s and marking as passed.\n".format(arg1)) - check writeAll output + def runDiff() = { + sendTraceMsg() + + def updateCheck = ( + isUpdateCheck && { + val formatStr = "** diff %s %s: " + ( + if (checkFile.exists) "failed, updating '%s' and marking as passed." + else if (diffOutput == "") "not creating checkFile at '%s' as there is no output." + else "was unchecked, creating '%s' for future tests." + ) + "\n" + + normal(formatStr.format(checkTraceName, logTraceName, checkFile.path)) + if (diffOutput != "") normal(diffOutput) + + checkFile.writeAll(diffCleanup(logFile), "\n") true - }) - } - } + } + ) - private def cleanedLog = returning(File makeTemp "partest-diff")(_ writeAll diffCleanup(logFile)) - def diffOutput(): String = checkFile ifFile (f => diffFiles(f, cleanedLog)) getOrElse "" + isDryRun || isDiffConfirmed || (updateCheck || !checkFile.exists) + } } } diff --git a/src/partest/scala/tools/partest/Categories.scala b/src/partest/scala/tools/partest/Categories.scala index c7a080dafe..172cca74b4 100644 --- a/src/partest/scala/tools/partest/Categories.scala +++ b/src/partest/scala/tools/partest/Categories.scala @@ -56,7 +56,7 @@ trait Categories { * Category level or by individual tests. */ def compile: TestStep = (_: TestEntity).compile() - def isCheckPresent: TestStep = (_: TestEntity).isCheckPresent + def checkFileRequired: TestStep = (_: TestEntity).checkFileRequired def diff: TestStep = (_: TestEntity).diff() def run: TestStep = (_: TestEntity).run() def exec: TestStep = (_: TestEntity).exec() diff --git a/src/partest/scala/tools/partest/Compilable.scala b/src/partest/scala/tools/partest/Compilable.scala index 73dcdfce73..ddaa277842 100644 --- a/src/partest/scala/tools/partest/Compilable.scala +++ b/src/partest/scala/tools/partest/Compilable.scala @@ -43,18 +43,12 @@ trait PartestCompilation { def scalac(args: List[String]): Boolean = { val allArgs = assembleScalacArgs(args) val (global, files) = newGlobal(allArgs) - val foundFiles = execCwd match { - case Some(cwd) => files map (x => File(cwd / x)) - case _ => files map (x => File(x)) - } def nonFileArgs = if (isVerbose) global.settings.recreateArgs else assembleScalacArgs(Nil) - def traceArgs = fromArgs(nonFileArgs ++ (foundFiles map tracePath)) - def traceMsg = - if (isVerbose) "%s %s".format(build.scalaBin / "scalac", traceArgs) - else "scalac " + traceArgs + def traceArgs = fromArgs(nonFileArgs ++ (files map tracePath)) + def traceMsg = "scalac " + traceArgs trace(traceMsg) - isDryRun || global.partestCompile(foundFiles map (_.path), true) + isDryRun || global.partestCompile(files, true) } /** Actually running the test, post compilation. @@ -71,7 +65,7 @@ trait PartestCompilation { val cmd = fromArgs(javaCmdAndOptions ++ createPropertyString() ++ scalaCmdAndOptions) def traceMsg = if (isVerbose) cmd else fromArgs(javaCmd :: args) - trace(traceMsg) + trace("runScala: " + traceMsg) isDryRun || execAndLog(cmd) } diff --git a/src/partest/scala/tools/partest/Config.scala b/src/partest/scala/tools/partest/Config.scala index 288a3034e9..2ed096d930 100644 --- a/src/partest/scala/tools/partest/Config.scala +++ b/src/partest/scala/tools/partest/Config.scala @@ -15,7 +15,7 @@ trait Config { lazy val src = absolutize(srcDir).toDirectory lazy val build = new TestBuild() - def javaHomeEnv = envOrElse("JAVA_HOME", null) + def javaHomeEnv = envOrElse("JAVA_HOME", "") def javaCmd = envOrElse("JAVACMD", "java") def javacCmd = Option(javaHomeEnv) map (x => Path(x) / "bin" / "javac" path) getOrElse "javac" diff --git a/src/partest/scala/tools/partest/Entities.scala b/src/partest/scala/tools/partest/Entities.scala index 2339250699..bea505b594 100644 --- a/src/partest/scala/tools/partest/Entities.scala +++ b/src/partest/scala/tools/partest/Entities.scala @@ -23,7 +23,7 @@ trait Entities { def category: TestCategory lazy val label = location.stripExtension - lazy val testClasspath = returning(createClasspathString())(vtrace) + lazy val testClasspath = returning(createClasspathString())(x => vtrace("testClasspath: " + x)) /** Was this test successful? Calling this for the first time forces * lazy val "process" which actually runs the test. @@ -47,7 +47,6 @@ trait Entities { */ def argumentsToRun = List("Test", "jvm") def argumentsToExec = List(location.path) - def argumentsToDiff = ((checkFile, logFile)) /** Using a .cmds file for a custom test sequence. */ @@ -58,7 +57,7 @@ trait Entities { def run() = runScala(argumentsToRun) def exec() = runExec(argumentsToExec) - def diff() = runDiff(argumentsToDiff._1, argumentsToDiff._2) + def diff() = runDiff() // checkFile, logFile /** The memoized result of the test run. */ diff --git a/src/partest/scala/tools/partest/Partest.scala b/src/partest/scala/tools/partest/Partest.scala index 7efac63354..b3fe9a98ef 100644 --- a/src/partest/scala/tools/partest/Partest.scala +++ b/src/partest/scala/tools/partest/Partest.scala @@ -29,12 +29,8 @@ class Partest(args: List[String]) extends { lazy val testBuildDir = searchForDir(buildDir) lazy val partestDir = searchForDir(rootDir) lazy val allCategories = List(Pos, Neg, Run, Jvm, Res, Shootout, Scalap, Scalacheck, BuildManager, Script) - lazy val selectedCategories = if (isAllImplied) allCategories else specifiedCats - // Coarse validation of partest directory: holds a file called partest. - (partestDir / "partest").isFile || error("'%s' is not a valid partest directory." format partestDir) - def specifiedTests = parsed.residualArgs map (x => Path(x).normalize) def specifiedKinds = testKinds filter (x => isSet(x) || (runSets contains x)) def specifiedCats = specifiedKinds flatMap (x => allCategories find (_.kind == x)) diff --git a/src/partest/scala/tools/partest/PartestSpec.scala b/src/partest/scala/tools/partest/PartestSpec.scala index a89c30a14d..c25119b3af 100644 --- a/src/partest/scala/tools/partest/PartestSpec.scala +++ b/src/partest/scala/tools/partest/PartestSpec.scala @@ -17,7 +17,7 @@ import cmd._ */ trait PartestSpec extends Spec with Meta.StdOpts with Interpolation { def referenceSpec = PartestSpec - def programInfo = Spec.Names("partest", "scala.tools.partest.Runner") + def programInfo = Spec.Info("partest", "", "scala.tools.partest.Runner") private val kind = new Spec.Accumulator[String]() protected def testKinds = kind.get @@ -95,7 +95,6 @@ object PartestSpec extends PartestSpec with Property { type ThisCommandLine = PartestCommandLine class PartestCommandLine(args: List[String]) extends SpecCommandLine(args) { - override def onlyKnownOptions = true override def errorFn(msg: String) = printAndExit("Error: " + msg) def propertyArgs = PartestSpec.propertyArgs diff --git a/src/partest/scala/tools/partest/Results.scala b/src/partest/scala/tools/partest/Results.scala index 8078e7bf85..5d0e300136 100644 --- a/src/partest/scala/tools/partest/Results.scala +++ b/src/partest/scala/tools/partest/Results.scala @@ -53,7 +53,7 @@ trait Results { super.show(msg) if (isShowDiff || isTrace) - normal(entity.diffOutput()) + normal(entity.diffOutput) if (isShowLog || isTrace) normal(toStringTrunc(entity.failureMessage(), 1600)) diff --git a/src/partest/scala/tools/partest/category/AllCategories.scala b/src/partest/scala/tools/partest/category/AllCategories.scala index ecf0737cbe..953f80324b 100644 --- a/src/partest/scala/tools/partest/category/AllCategories.scala +++ b/src/partest/scala/tools/partest/category/AllCategories.scala @@ -14,7 +14,7 @@ trait AllCategories extends Compiler with Analysis with Runner { self: Universe => object Pos extends DirBasedCategory("pos") { lazy val testSequence: TestSequence = List(compile) } - object Neg extends DirBasedCategory("neg") { lazy val testSequence: TestSequence = List(isCheckPresent, not(compile), diff) } + object Neg extends DirBasedCategory("neg") { lazy val testSequence: TestSequence = List(checkFileRequired, not(compile), diff) } object Run extends DirBasedCategory("run") { lazy val testSequence: TestSequence = List(compile, run, diff) } object Jvm extends DirBasedCategory("jvm") { lazy val testSequence: TestSequence = List(compile, run, diff) } } diff --git a/src/partest/scala/tools/partest/category/Analysis.scala b/src/partest/scala/tools/partest/category/Analysis.scala index d05ee6bb21..2c6c208ee5 100644 --- a/src/partest/scala/tools/partest/category/Analysis.scala +++ b/src/partest/scala/tools/partest/category/Analysis.scala @@ -32,7 +32,7 @@ trait Analysis { self: Universe => object Scalap extends DirBasedCategory("scalap") { - val testSequence: TestSequence = List(isCheckPresent, compile, run, diff) + val testSequence: TestSequence = List(checkFileRequired, compile, run, diff) override def denotesTest(p: Path) = p.isDirectory && (p.toDirectory.files exists (_.name == "result.test")) override def createTest(location: Path) = new ScalapTest(location) diff --git a/src/partest/scala/tools/partest/category/Compiler.scala b/src/partest/scala/tools/partest/category/Compiler.scala index 11112509cc..49775d5031 100644 --- a/src/partest/scala/tools/partest/category/Compiler.scala +++ b/src/partest/scala/tools/partest/category/Compiler.scala @@ -19,7 +19,7 @@ trait Compiler { * $SCALAC -d dir.obj -Xresident -sourcepath . "$@" */ object Res extends DirBasedCategory("res") { - lazy val testSequence: TestSequence = List(isCheckPresent, compile, diff) + lazy val testSequence: TestSequence = List(checkFileRequired, compile, diff) override def denotesTest(p: Path) = p.isDirectory && resFile(p).isFile override def createTest(location: Path) = new ResidentTest(location.toDirectory) @@ -61,7 +61,7 @@ trait Compiler { } object BuildManager extends DirBasedCategory("buildmanager") { - lazy val testSequence: TestSequence = List(isCheckPresent, compile, diff) + lazy val testSequence: TestSequence = List(checkFileRequired, compile, diff) override def denotesTest(p: Path) = p.isDirectory && testFile(p).isFile override def createTest(location: Path) = new BuildManagerTest(location.toDirectory) @@ -123,7 +123,7 @@ trait Compiler { def sendCommand(line: String): Boolean = { val compileRegex = """^>>compile (.*)$""".r val updateRegex = """^>>update\s+(.*)""".r - trace(line drop 2) + trace("send: " + (line drop 2)) isDryRun || (line match { case compileRegex(xs) => pbm.buildManagerCompile(xs) diff --git a/src/partest/scala/tools/partest/io/Logging.scala b/src/partest/scala/tools/partest/io/Logging.scala index d244d58757..52239ffb2c 100644 --- a/src/partest/scala/tools/partest/io/Logging.scala +++ b/src/partest/scala/tools/partest/io/Logging.scala @@ -55,15 +55,17 @@ trait Logging { finally log.close() } - /** XXX needs attention. + /** What to print in a failure summary. */ - def failureMessage() = safeSlurp(logFile) + def failureMessage() = if (diffOutput != "") diffOutput else safeSlurp(logFile) /** For tracing. Outputs a line describing the next action. tracePath * is a path wrapper which prints name or full path depending on verbosity. */ - def trace(msg: String) = if (isTrace || isDryRun) System.err.println(">> [%s] %s".format(label, msg)) - def tracePath(path: Path) = if (isVerbose) path.path else path.name + def trace(msg: String) = if (isTrace || isDryRun) System.err.println(">> [%s] %s".format(label, msg)) + + def tracePath(path: Path): String = if (isVerbose) path.path else path.name + def tracePath(path: String): String = tracePath(Path(path)) /** v == verbose. */ diff --git a/src/scalap/scala/tools/scalap/Decode.scala b/src/scalap/scala/tools/scalap/Decode.scala index e9f9a390c5..5189009584 100644 --- a/src/scalap/scala/tools/scalap/Decode.scala +++ b/src/scalap/scala/tools/scalap/Decode.scala @@ -12,7 +12,10 @@ package scala.tools.scalap import scala.tools.scalap.scalax.rules.scalasig._ import scala.tools.nsc.util.ScalaClassLoader import scala.tools.nsc.util.ScalaClassLoader.getSystemLoader -import Main.SCALA_SIG +import scala.reflect.generic.ByteCodecs + +import ClassFileParser.{ ConstValueIndex, Annotation } +import Main.{ SCALA_SIG, SCALA_SIG_ANNOTATION, BYTES_VALUE } /** Temporary decoder. This would be better off in the scala.tools.nsc * but right now the compiler won't acknowledge scala.tools.scalap @@ -25,7 +28,8 @@ object Decode { case _ => NoSymbol } - /** Return the classfile bytes representing the scala sig attribute. + /** Return the classfile bytes representing the scala sig classfile attribute. + * This has been obsoleted by the switch to annotations. */ def scalaSigBytes(name: String): Option[Array[Byte]] = scalaSigBytes(name, getSystemLoader()) def scalaSigBytes(name: String, classLoader: ScalaClassLoader): Option[Array[Byte]] = { @@ -35,6 +39,25 @@ object Decode { cf.scalaSigAttribute map (_.data) } + /** Return the bytes representing the annotation + */ + def scalaSigAnnotationBytes(name: String): Option[Array[Byte]] = scalaSigAnnotationBytes(name, getSystemLoader()) + def scalaSigAnnotationBytes(name: String, classLoader: ScalaClassLoader): Option[Array[Byte]] = { + val bytes = classLoader.findBytesForClassName(name) + val byteCode = ByteCode(bytes) + val classFile = ClassFileParser.parse(byteCode) + import classFile._ + + classFile annotation SCALA_SIG_ANNOTATION map { case Annotation(_, els) => + val bytesElem = els find (x => constant(x.elementNameIndex) == BYTES_VALUE) get + val _bytes = bytesElem.elementValue match { case ConstValueIndex(x) => constantWrapped(x) } + val bytes = _bytes.asInstanceOf[StringBytesPair].bytes + val length = ByteCodecs.decode(bytes) + + bytes take length + } + } + /** private[scala] so nobody gets the idea this is a supported interface. */ private[scala] def caseParamNames(path: String): Option[List[String]] = { diff --git a/src/scalap/scala/tools/scalap/scalax/rules/scalasig/ScalaSigPrinter.scala b/src/scalap/scala/tools/scalap/scalax/rules/scalasig/ScalaSigPrinter.scala index 3cb70ec04e..354e3131e8 100644 --- a/src/scalap/scala/tools/scalap/scalax/rules/scalasig/ScalaSigPrinter.scala +++ b/src/scalap/scala/tools/scalap/scalax/rules/scalasig/ScalaSigPrinter.scala @@ -17,6 +17,7 @@ import java.util.regex.Pattern import scala.tools.scalap.scalax.util.StringUtil import reflect.NameTransformer +import java.lang.String class ScalaSigPrinter(stream: PrintStream, printPrivates: Boolean) { import stream._ @@ -130,6 +131,7 @@ class ScalaSigPrinter(stream: PrintStream, printPrivates: Boolean) { val it = c.infoType val classType = it match { case PolyType(typeRef, symbols) => PolyTypeWithCons(typeRef, symbols, defaultConstructor) + case ClassInfoType(a, b) if c.isCase => ClassInfoTypeWithCons(a, b, defaultConstructor) case _ => it } printType(classType) @@ -368,6 +370,8 @@ class ScalaSigPrinter(stream: PrintStream, printPrivates: Boolean) { } case RefinedType(classSym, typeRefs) => sep + typeRefs.map(toString).mkString("", " with ", "") case ClassInfoType(symbol, typeRefs) => sep + typeRefs.map(toString).mkString(" extends ", " with ", "") + case ClassInfoTypeWithCons(symbol, typeRefs, cons) => sep + typeRefs.map(toString). + mkString(cons + " extends ", " with ", "") case ImplicitMethodType(resultType, _) => toString(resultType, sep) case MethodType(resultType, _) => toString(resultType, sep) diff --git a/src/scalap/scala/tools/scalap/scalax/rules/scalasig/Type.scala b/src/scalap/scala/tools/scalap/scalax/rules/scalasig/Type.scala index e224525d06..c991df6c09 100644 --- a/src/scalap/scala/tools/scalap/scalax/rules/scalasig/Type.scala +++ b/src/scalap/scala/tools/scalap/scalax/rules/scalasig/Type.scala @@ -15,6 +15,7 @@ case class TypeRefType(prefix : Type, symbol : Symbol, typeArgs : Seq[Type]) ext case class TypeBoundsType(lower : Type, upper : Type) extends Type case class RefinedType(classSym : Symbol, typeRefs : List[Type]) extends Type case class ClassInfoType(symbol : Symbol, typeRefs : Seq[Type]) extends Type +case class ClassInfoTypeWithCons(symbol : Symbol, typeRefs : Seq[Type], cons: String) extends Type case class MethodType(resultType : Type, paramSymbols : Seq[Symbol]) extends Type case class PolyType(typeRef : Type, symbols : Seq[TypeSymbol]) extends Type case class PolyTypeWithCons(typeRef : Type, symbols : Seq[TypeSymbol], cons: String) extends Type diff --git a/src/swing/scala/swing/EditorPane.scala b/src/swing/scala/swing/EditorPane.scala index d90615299d..884da6a371 100644 --- a/src/swing/scala/swing/EditorPane.scala +++ b/src/swing/scala/swing/EditorPane.scala @@ -21,7 +21,7 @@ import java.awt.event._ * @see javax.swing.JEditorPane */ class EditorPane(contentType0: String, text0: String) extends TextComponent { - override lazy val peer: JEditorPane = new JEditorPane(contentType0, text0) with SuperMixin {} + override lazy val peer: JEditorPane = new JEditorPane(contentType0, text0) with SuperMixin def this() = this("text/plain", "") def contentType: String = peer.getContentType diff --git a/src/swing/scala/swing/FlowPanel.scala b/src/swing/scala/swing/FlowPanel.scala index 5b2d1950a4..65833940f5 100644 --- a/src/swing/scala/swing/FlowPanel.scala +++ b/src/swing/scala/swing/FlowPanel.scala @@ -12,6 +12,7 @@ package scala.swing import java.awt.FlowLayout +import javax.swing.JPanel object FlowPanel { object Alignment extends Enumeration { @@ -30,7 +31,8 @@ object FlowPanel { * @see java.awt.FlowLayout */ class FlowPanel(alignment: FlowPanel.Alignment.Value)(contents0: Component*) extends Panel with SequentialContainer.Wrapper { - override lazy val peer: javax.swing.JPanel = new javax.swing.JPanel(new java.awt.FlowLayout(alignment.id)) + override lazy val peer: JPanel = + new JPanel(new java.awt.FlowLayout(alignment.id)) with SuperMixin def this(contents0: Component*) = this(FlowPanel.Alignment.Center)(contents0: _*) def this() = this(FlowPanel.Alignment.Center)() diff --git a/src/swing/scala/swing/FormattedTextField.scala b/src/swing/scala/swing/FormattedTextField.scala index c82c0fe45f..1a2d1dacb4 100644 --- a/src/swing/scala/swing/FormattedTextField.scala +++ b/src/swing/scala/swing/FormattedTextField.scala @@ -33,7 +33,7 @@ object FormattedTextField { * @see javax.swing.JFormattedTextField */ class FormattedTextField(format: java.text.Format) extends TextComponent { - override lazy val peer: JFormattedTextField = new JFormattedTextField(format) + override lazy val peer: JFormattedTextField = new JFormattedTextField(format) with SuperMixin import FormattedTextField._ diff --git a/src/swing/scala/swing/GridBagPanel.scala b/src/swing/scala/swing/GridBagPanel.scala index 5402d1f686..412ec3f4f5 100644 --- a/src/swing/scala/swing/GridBagPanel.scala +++ b/src/swing/scala/swing/GridBagPanel.scala @@ -11,7 +11,7 @@ package scala.swing -import java.awt.{GridBagConstraints} +import java.awt.{GridBagConstraints, GridBagLayout} object GridBagPanel { @@ -50,10 +50,10 @@ object GridBagPanel { * @see java.awt.GridBagLayout */ class GridBagPanel extends Panel with LayoutContainer { - override lazy val peer = new javax.swing.JPanel(new java.awt.GridBagLayout) + override lazy val peer = new javax.swing.JPanel(new GridBagLayout) with SuperMixin import GridBagPanel._ - private def layoutManager = peer.getLayout.asInstanceOf[java.awt.GridBagLayout] + private def layoutManager = peer.getLayout.asInstanceOf[GridBagLayout] /** * Convenient conversion from xy-coords given as pairs to diff --git a/src/swing/scala/swing/GridPanel.scala b/src/swing/scala/swing/GridPanel.scala index b37f26221d..608b8810a2 100644 --- a/src/swing/scala/swing/GridPanel.scala +++ b/src/swing/scala/swing/GridPanel.scala @@ -21,7 +21,8 @@ object GridPanel { * @see java.awt.GridLayout */ class GridPanel(rows0: Int, cols0: Int) extends Panel with SequentialContainer.Wrapper { - override lazy val peer = new javax.swing.JPanel(new java.awt.GridLayout(rows0, cols0)) + override lazy val peer = + new javax.swing.JPanel(new java.awt.GridLayout(rows0, cols0)) with SuperMixin /*type Constraints = (Int, Int) diff --git a/src/swing/scala/swing/Label.scala b/src/swing/scala/swing/Label.scala index 2a24e8cd02..31b2b0c87c 100644 --- a/src/swing/scala/swing/Label.scala +++ b/src/swing/scala/swing/Label.scala @@ -20,7 +20,8 @@ import scala.swing.Swing._ * @see javax.swing.JLabel */ class Label(text0: String, icon0: Icon, align: Alignment.Value) extends Component { - override lazy val peer: JLabel = new JLabel(text0, toNullIcon(icon0), align.id) with SuperMixin + override lazy val peer: JLabel = + new JLabel(text0, toNullIcon(icon0), align.id) with SuperMixin def this() = this("", EmptyIcon, Alignment.Center) def this(s: String) = this(s, EmptyIcon, Alignment.Center) diff --git a/src/swing/scala/swing/ListView.scala b/src/swing/scala/swing/ListView.scala index a15471796c..9c7b7d6d19 100644 --- a/src/swing/scala/swing/ListView.scala +++ b/src/swing/scala/swing/ListView.scala @@ -143,7 +143,7 @@ object ListView { */ class ListView[A] extends Component { import ListView._ - override lazy val peer: JList = new JList + override lazy val peer: JList = new JList with SuperMixin def this(items: Seq[A]) = { this() diff --git a/src/swing/scala/swing/MainFrame.scala b/src/swing/scala/swing/MainFrame.scala index 361da6233b..ec4e74958b 100644 --- a/src/swing/scala/swing/MainFrame.scala +++ b/src/swing/scala/swing/MainFrame.scala @@ -18,5 +18,5 @@ import event._ * framework and quits the application when closed. */ class MainFrame extends Frame { - override def closeOperation { System.exit(0); } + override def closeOperation { System.exit(0) } } diff --git a/src/swing/scala/swing/Menu.scala b/src/swing/scala/swing/Menu.scala index 14855fd51f..55773320c6 100644 --- a/src/swing/scala/swing/Menu.scala +++ b/src/swing/scala/swing/Menu.scala @@ -24,7 +24,7 @@ object MenuBar { * @see javax.swing.JMenuBar */ class MenuBar extends Component with SequentialContainer.Wrapper { - override lazy val peer: JMenuBar = new JMenuBar + override lazy val peer: JMenuBar = new JMenuBar with SuperMixin def menus: Seq[Menu] = contents.filter(_.isInstanceOf[Menu]).map(_.asInstanceOf[Menu]) diff --git a/src/swing/scala/swing/PasswordField.scala b/src/swing/scala/swing/PasswordField.scala index 4b07969612..568cc3b927 100644 --- a/src/swing/scala/swing/PasswordField.scala +++ b/src/swing/scala/swing/PasswordField.scala @@ -21,7 +21,7 @@ import java.awt.event._ * @see javax.swing.JPasswordField */ class PasswordField(text0: String, columns0: Int) extends TextField(text0, columns0) { - override lazy val peer: JPasswordField = new JPasswordField(text0, columns0) + override lazy val peer: JPasswordField = new JPasswordField(text0, columns0) with SuperMixin def this(text: String) = this(text, 0) def this(columns: Int) = this("", columns) def this() = this("") diff --git a/src/swing/scala/swing/ProgressBar.scala b/src/swing/scala/swing/ProgressBar.scala index d43ddd5717..c6c2ae25d3 100644 --- a/src/swing/scala/swing/ProgressBar.scala +++ b/src/swing/scala/swing/ProgressBar.scala @@ -22,7 +22,7 @@ import event._ */ class ProgressBar extends Component with Orientable.Wrapper { override lazy val peer: javax.swing.JProgressBar = - new javax.swing.JProgressBar + new javax.swing.JProgressBar with SuperMixin def min: Int = peer.getMinimum def min_=(v: Int) { peer.setMinimum(v) } diff --git a/src/swing/scala/swing/RadioButton.scala b/src/swing/scala/swing/RadioButton.scala index ae789b077c..3b3e60816b 100644 --- a/src/swing/scala/swing/RadioButton.scala +++ b/src/swing/scala/swing/RadioButton.scala @@ -21,6 +21,6 @@ import javax.swing._ * @see javax.swing.JRadioButton */ class RadioButton(text0: String) extends ToggleButton { - override lazy val peer: JRadioButton = new JRadioButton(text0) + override lazy val peer: JRadioButton = new JRadioButton(text0) with SuperMixin def this() = this("") } diff --git a/src/swing/scala/swing/ScrollBar.scala b/src/swing/scala/swing/ScrollBar.scala index 2ae8cd5c4e..28245edda7 100644 --- a/src/swing/scala/swing/ScrollBar.scala +++ b/src/swing/scala/swing/ScrollBar.scala @@ -23,7 +23,7 @@ object ScrollBar { } class ScrollBar extends Component with Orientable.Wrapper with Adjustable.Wrapper { - override lazy val peer = new JScrollBar + override lazy val peer: JScrollBar = new JScrollBar with SuperMixin def valueIsAjusting = peer.getValueIsAdjusting def valueIsAjusting_=(b : Boolean) = peer.setValueIsAdjusting(b) diff --git a/src/swing/scala/swing/ScrollPane.scala b/src/swing/scala/swing/ScrollPane.scala index fc2e96e67a..16c4130671 100644 --- a/src/swing/scala/swing/ScrollPane.scala +++ b/src/swing/scala/swing/ScrollPane.scala @@ -43,7 +43,7 @@ object ScrollPane { class ScrollPane extends Component with Container { import ScrollPane._ - override lazy val peer: JScrollPane = new JScrollPane + override lazy val peer: JScrollPane = new JScrollPane with SuperMixin def this(c: Component) = { this() contents = c diff --git a/src/swing/scala/swing/Separator.scala b/src/swing/scala/swing/Separator.scala index cf2bfd75d0..4fdf0edb70 100644 --- a/src/swing/scala/swing/Separator.scala +++ b/src/swing/scala/swing/Separator.scala @@ -19,6 +19,6 @@ import javax.swing._ * @see javax.swing.JSeparator */ class Separator(o: Orientation.Value) extends Component with Oriented.Wrapper { - override lazy val peer: JSeparator = new JSeparator(o.id) + override lazy val peer: JSeparator = new JSeparator(o.id) with SuperMixin def this() = this(Orientation.Horizontal) } diff --git a/src/swing/scala/swing/Slider.scala b/src/swing/scala/swing/Slider.scala index 793f5eb5bb..10b79f37cc 100644 --- a/src/swing/scala/swing/Slider.scala +++ b/src/swing/scala/swing/Slider.scala @@ -24,7 +24,7 @@ import event._ * @see javax.swing.JSlider */ class Slider extends Component with Orientable.Wrapper with Publisher { - override lazy val peer: JSlider = new JSlider + override lazy val peer: JSlider = new JSlider with SuperMixin def min: Int = peer.getMinimum def min_=(v: Int) { peer.setMinimum(v) } diff --git a/src/swing/scala/swing/SplitPane.scala b/src/swing/scala/swing/SplitPane.scala index c9f1641ef7..bad3f32bed 100644 --- a/src/swing/scala/swing/SplitPane.scala +++ b/src/swing/scala/swing/SplitPane.scala @@ -23,7 +23,7 @@ import Swing._ */ class SplitPane(o: Orientation.Value, left: Component, right: Component) extends Component with Container with Orientable.Wrapper { override lazy val peer: javax.swing.JSplitPane = - new javax.swing.JSplitPane(o.id, left.peer, right.peer) + new javax.swing.JSplitPane(o.id, left.peer, right.peer) with SuperMixin def this(o: Orientation.Value) = this(o, new Component {}, new Component {}) def this() = this(Orientation.Horizontal) diff --git a/src/swing/scala/swing/TabbedPane.scala b/src/swing/scala/swing/TabbedPane.scala index b8fb1472fb..ec688a0cf0 100644 --- a/src/swing/scala/swing/TabbedPane.scala +++ b/src/swing/scala/swing/TabbedPane.scala @@ -77,7 +77,7 @@ object TabbedPane { * @see javax.swing.JTabbedPane */ class TabbedPane extends Component with Publisher { - override lazy val peer: JTabbedPane = new JTabbedPane + override lazy val peer: JTabbedPane = new JTabbedPane with SuperMixin import TabbedPane._ object pages extends BufferWrapper[Page] { diff --git a/src/swing/scala/swing/Table.scala b/src/swing/scala/swing/Table.scala index 9370ea7eb1..2993f1d84e 100644 --- a/src/swing/scala/swing/Table.scala +++ b/src/swing/scala/swing/Table.scala @@ -110,7 +110,7 @@ object Table { * @see javax.swing.JTable */ class Table extends Component with Scrollable.Wrapper { - override lazy val peer: JTable = new JTable with Table.JTableMixin { + override lazy val peer: JTable = new JTable with Table.JTableMixin with SuperMixin { def tableWrapper = Table.this override def getCellRenderer(r: Int, c: Int) = new TableCellRenderer { def getTableCellRendererComponent(table: JTable, value: AnyRef, isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int) = diff --git a/src/swing/scala/swing/TextArea.scala b/src/swing/scala/swing/TextArea.scala index d491b0b0c6..201e4ab674 100644 --- a/src/swing/scala/swing/TextArea.scala +++ b/src/swing/scala/swing/TextArea.scala @@ -20,8 +20,9 @@ import java.awt.event._ * * @see javax.swing.JTextArea */ -class TextArea(text0: String, rows0: Int, columns0: Int) extends TextComponent with TextComponent.HasColumns with TextComponent.HasRows { - override lazy val peer: JTextArea = new JTextArea(text0, rows0, columns0) +class TextArea(text0: String, rows0: Int, columns0: Int) extends TextComponent + with TextComponent.HasColumns with TextComponent.HasRows { + override lazy val peer: JTextArea = new JTextArea(text0, rows0, columns0) with SuperMixin def this(text: String) = this(text, 0, 0) def this(rows: Int, columns: Int) = this("", rows, columns) def this() = this("", 0, 0) diff --git a/src/swing/scala/swing/ToggleButton.scala b/src/swing/scala/swing/ToggleButton.scala index 506f611c95..59f310dc23 100644 --- a/src/swing/scala/swing/ToggleButton.scala +++ b/src/swing/scala/swing/ToggleButton.scala @@ -21,6 +21,6 @@ import javax.swing._ * @see javax.swing.JToggleButton */ class ToggleButton(text0: String) extends AbstractButton { - override lazy val peer: JToggleButton = new JToggleButton(text0) + override lazy val peer: JToggleButton = new JToggleButton(text0) with SuperMixin def this() = this("") } |