diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala | 118 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Typers.scala | 4 | ||||
-rw-r--r-- | src/library/scala/concurrent/Future.scala | 181 | ||||
-rw-r--r-- | test/files/jvm/scala-concurrent-tck.scala | 562 | ||||
-rw-r--r-- | test/files/neg/valueclasses-impl-restrictions.check | 8 | ||||
-rw-r--r-- | test/files/neg/valueclasses-impl-restrictions.scala | 4 | ||||
-rw-r--r-- | test/files/run/t7571.scala | 12 | ||||
-rw-r--r-- | test/files/specialized/SI-7344.scala | 53 |
8 files changed, 363 insertions, 579 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index 5ef5d4031b..7e85647592 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -844,7 +844,11 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { debuglog("%s expands to %s in %s".format(sym, specMember.name.decode, pp(env))) info(specMember) = NormalizedMember(sym) newOverload(sym, specMember, env) - owner.info.decls.enter(specMember) + // if this is a class, we insert the normalized member in scope, + // if this is a method, there's no attached scope for it (EmptyScope) + val decls = owner.info.decls + if (decls != EmptyScope) + decls.enter(specMember) specMember } } @@ -1420,7 +1424,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { val env = unify(symbol.tpe, tree.tpe, emptyEnv, false) debuglog("[specSym] checking for rerouting: %s with \n\tsym.tpe: %s, \n\ttree.tpe: %s \n\tenv: %s \n\tname: %s" .format(tree, symbol.tpe, tree.tpe, env, specializedName(symbol, env))) - if (!env.isEmpty) { // a method? + if (env.nonEmpty) { // a method? val specCandidates = qual.tpe.member(specializedName(symbol, env)) val specMember = specCandidates suchThat { s => doesConform(symbol, tree.tpe, qual.tpe.memberType(s), env) @@ -1438,6 +1442,34 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } else None } + /** Computes residual type parameters after rewiring, like "String" in the following example: + * ``` + * def specMe[@specialized T, U](t: T, u: U) = ??? + * specMe[Int, String](1, "2") => specMe$mIc$sp[String](1, "2") + * ``` + */ + def computeResidualTypeVars(baseTree: Tree, specTree: Tree, baseTargs: List[Tree], env: TypeEnv) = { + val baseSym: Symbol = baseTree.symbol + val specSym: Symbol = specTree.symbol + val residualTargs = baseSym.info.typeParams zip baseTargs collect { + case (tvar, targ) if !env.contains(tvar) || !isPrimitiveValueClass(env(tvar).typeSymbol) => targ + } + + if (specSym.info.typeParams.isEmpty && residualTargs.nonEmpty) { + log("!!! Type args to be applied, but symbol says no parameters: " + ((specSym.defString, residualTargs))) + baseTree + } + else { + ifDebug(assert(residualTargs.length == specSym.info.typeParams.length, + "residual: %s, tparams: %s, env: %s".format(residualTargs, specSym.info.typeParams, env)) + ) + + val tree1 = gen.mkTypeApply(specTree, residualTargs) + debuglog("rewrote " + tree + " to " + tree1) + localTyper.typedOperator(atPos(tree.pos)(tree1)) + } + } + curTree = tree tree match { case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => @@ -1470,43 +1502,44 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } transformSuperApply + // This rewires calls to specialized methods defined in a class (which have a receiver) + // class C { + // def foo[@specialized T](t: T): T = t + // C.this.foo(3) // TypeApply(Select(This(C), foo), List(Int)) => C.this.foo$mIc$sp(3) + // } case TypeApply(sel @ Select(qual, name), targs) - if (!specializedTypeVars(symbol.info).isEmpty && name != nme.CONSTRUCTOR) => - def transformTypeApply = { + if (specializedTypeVars(symbol.info).nonEmpty && name != nme.CONSTRUCTOR) => debuglog("checking typeapp for rerouting: " + tree + " with sym.tpe: " + symbol.tpe + " tree.tpe: " + tree.tpe) val qual1 = transform(qual) - // log(">>> TypeApply: " + tree + ", qual1: " + qual1) + log(">>> TypeApply: " + tree + ", qual1: " + qual1) specSym(qual1) match { case Some(specMember) => debuglog("found " + specMember.fullName) ifDebug(assert(symbol.info.typeParams.length == targs.length, symbol.info.typeParams + " / " + targs)) val env = typeEnv(specMember) - val residualTargs = symbol.info.typeParams zip targs collect { - case (tvar, targ) if !env.contains(tvar) || !isPrimitiveValueClass(env(tvar).typeSymbol) => targ - } - // See SI-5583. Don't know why it happens now if it didn't before. - if (specMember.info.typeParams.isEmpty && residualTargs.nonEmpty) { - log("!!! Type args to be applied, but symbol says no parameters: " + ((specMember.defString, residualTargs))) - localTyper.typed(sel) - } - else { - ifDebug(assert(residualTargs.length == specMember.info.typeParams.length, - "residual: %s, tparams: %s, env: %s".format(residualTargs, specMember.info.typeParams, env)) - ) - - val tree1 = gen.mkTypeApply(Select(qual1, specMember), residualTargs) - debuglog("rewrote " + tree + " to " + tree1) - localTyper.typedOperator(atPos(tree.pos)(tree1)) // being polymorphic, it must be a method - } + computeResidualTypeVars(tree, gen.mkAttributedSelect(qual1, specMember), targs, env) case None => treeCopy.TypeApply(tree, treeCopy.Select(sel, qual1, name), super.transformTrees(targs)) // See pos/exponential-spec.scala - can't call transform on the whole tree again. // super.transform(tree) } + + // This rewires calls to specialized methods defined in the local scope. For example: + // def outerMethod = { + // def foo[@specialized T](t: T): T = t + // foo(3) // TypeApply(Ident(foo), List(Int)) => foo$mIc$sp(3) + // } + case TypeApply(sel @ Ident(name), targs) if name != nme.CONSTRUCTOR => + val env = unify(symbol.tpe, tree.tpe, emptyEnv, false) + if (env.isEmpty) super.transform(tree) + else { + overloads(symbol) find (_ matchesEnv env) match { + case Some(Overload(specMember, _)) => computeResidualTypeVars(tree, Ident(specMember), targs, env) + case _ => super.transform(tree) + } } - transformTypeApply case Select(Super(_, _), _) if illegalSpecializedInheritance(currentClass) => val pos = tree.pos @@ -1657,7 +1690,11 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { localTyper.typed(deriveDefDef(tree)(rhs => rhs)) } } - transformDefDef + expandInnerNormalizedMembers(transformDefDef) + + case ddef @ DefDef(_, _, _, _, _, _) => + val tree1 = expandInnerNormalizedMembers(tree) + super.transform(tree1) case ValDef(_, _, _, _) if symbol.hasFlag(SPECIALIZED) && !symbol.isParamAccessor => def transformValDef = { @@ -1682,6 +1719,39 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } } + /** + * This performs method specialization inside a scope other than a {class, trait, object}: could be another method + * or a value. This specialization is much simpler, since there is no need to record the new members in the class + * signature, their signatures are only visible locally. It works according to the usual logic: + * - we use normalizeMember to create the specialized symbols + * - we leave DefDef stubs in the tree that are later filled in by tree duplication and adaptation + * @see duplicateBody + */ + private def expandInnerNormalizedMembers(tree: Tree) = tree match { + case ddef @ DefDef(_, _, _, vparams :: Nil, _, rhs) + if ddef.symbol.owner.isMethod && + specializedTypeVars(ddef.symbol.info).nonEmpty && + !ddef.symbol.hasFlag(SPECIALIZED) => + + val sym = ddef.symbol + val owner = sym.owner + val norm = normalizeMember(owner, sym, emptyEnv) + + if (norm.length > 1) { + // record the body for duplication + body(sym) = rhs + parameters(sym) = vparams.map(_.symbol) + // to avoid revisiting the member, we can set the SPECIALIZED + // flag. nobody has to see this anyway :) + sym.setFlag(SPECIALIZED) + // create empty bodies for specializations + localTyper.typed(Block(norm.tail.map(sym => DefDef(sym, { vparamss => EmptyTree })), ddef)) + } else + tree + case _ => + tree + } + /** Duplicate the body of the given method `tree` to the new symbol `source`. * * Knowing that the method can be invoked only in the `castmap` type environment, diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 29cd3d4bfa..ed2963fb0f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1455,8 +1455,8 @@ trait Typers extends Modes with Adaptations with Tags { implRestriction(tree, "nested object") //see https://issues.scala-lang.org/browse/SI-6444 //see https://issues.scala-lang.org/browse/SI-6463 - case _: ClassDef => - implRestriction(tree, "nested class") + case cd: ClassDef if !cd.symbol.isAnonymousClass => // Don't warn about partial functions, etc. SI-7571 + implRestriction(tree, "nested class") // avoiding Type Tests that might check the $outer pointer. case Select(sup @ Super(qual, mix), selector) if selector != nme.CONSTRUCTOR && qual.symbol == clazz && mix != tpnme.EMPTY => //see https://issues.scala-lang.org/browse/SI-6483 implRestriction(sup, "qualified super reference") diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index 6b6ad29074..bc3a241ce7 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -14,7 +14,7 @@ import java.util.concurrent.{ ConcurrentLinkedQueue, TimeUnit, Callable } import java.util.concurrent.TimeUnit.{ NANOSECONDS => NANOS, MILLISECONDS ⇒ MILLIS } import java.lang.{ Iterable => JIterable } import java.util.{ LinkedList => JLinkedList } -import java.util.concurrent.atomic.{ AtomicReferenceFieldUpdater, AtomicInteger, AtomicBoolean } +import java.util.concurrent.atomic.{ AtomicReferenceFieldUpdater, AtomicInteger, AtomicLong, AtomicBoolean } import scala.util.control.NonFatal import scala.Option @@ -101,7 +101,7 @@ trait Future[+T] extends Awaitable[T] { // that also have an executor parameter, which // keeps us from accidentally forgetting to use // the executor parameter. - private implicit def internalExecutor: ExecutionContext = Future.InternalCallbackExecutor + private def internalExecutor = Future.InternalCallbackExecutor /* Callbacks */ @@ -116,9 +116,10 @@ trait Future[+T] extends Awaitable[T] { * $callbackInContext */ def onSuccess[U](pf: PartialFunction[T, U])(implicit executor: ExecutionContext): Unit = onComplete { - case Success(v) if pf isDefinedAt v => pf(v) + case Success(v) => + pf.applyOrElse[T, Any](v, Predef.conforms[T]) // Exploiting the cached function to avoid MatchError case _ => - }(executor) + } /** When this future is completed with a failure (i.e. with a throwable), * apply the provided callback to the throwable. @@ -134,9 +135,10 @@ trait Future[+T] extends Awaitable[T] { * $callbackInContext */ def onFailure[U](callback: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Unit = onComplete { - case Failure(t) if NonFatal(t) && callback.isDefinedAt(t) => callback(t) + case Failure(t) => + callback.applyOrElse[Throwable, Any](t, Predef.conforms[Throwable]) // Exploiting the cached function to avoid MatchError case _ => - }(executor) + } /** When this future is completed, either through an exception, or a value, * apply the provided function. @@ -186,13 +188,12 @@ trait Future[+T] extends Awaitable[T] { * and throws a corresponding exception if the original future fails. */ def failed: Future[Throwable] = { + implicit val ec = internalExecutor val p = Promise[Throwable]() - onComplete { case Failure(t) => p success t case Success(v) => p failure (new NoSuchElementException("Future.failed not completed with a throwable.")) } - p.future } @@ -203,10 +204,7 @@ trait Future[+T] extends Awaitable[T] { * * Will not be called if the future fails. */ - def foreach[U](f: T => U)(implicit executor: ExecutionContext): Unit = onComplete { - case Success(r) => f(r) - case _ => // do nothing - }(executor) + def foreach[U](f: T => U)(implicit executor: ExecutionContext): Unit = onComplete { _ foreach f } /** Creates a new future by applying the 's' function to the successful result of * this future, or the 'f' function to the failed result. If there is any non-fatal @@ -221,19 +219,11 @@ trait Future[+T] extends Awaitable[T] { */ def transform[S](s: T => S, f: Throwable => Throwable)(implicit executor: ExecutionContext): Future[S] = { val p = Promise[S]() - + // transform on Try has the wrong shape for us here onComplete { - case result => - try { - result match { - case Failure(t) => p failure f(t) - case Success(r) => p success s(r) - } - } catch { - case NonFatal(t) => p failure t - } - }(executor) - + case Success(r) => p complete Try(s(r)) + case Failure(t) => p complete Try(throw f(t)) // will throw fatal errors! + } p.future } @@ -245,19 +235,7 @@ trait Future[+T] extends Awaitable[T] { */ def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S] = { // transform(f, identity) val p = Promise[S]() - - onComplete { - case result => - try { - result match { - case Success(r) => p success f(r) - case f: Failure[_] => p complete f.asInstanceOf[Failure[S]] - } - } catch { - case NonFatal(t) => p failure t - } - }(executor) - + onComplete { v => p complete (v map f) } p.future } @@ -270,20 +248,10 @@ trait Future[+T] extends Awaitable[T] { */ def flatMap[S](f: T => Future[S])(implicit executor: ExecutionContext): Future[S] = { val p = Promise[S]() - onComplete { case f: Failure[_] => p complete f.asInstanceOf[Failure[S]] - case Success(v) => - try { - f(v).onComplete({ - case f: Failure[_] => p complete f.asInstanceOf[Failure[S]] - case Success(v) => p success v - })(internalExecutor) - } catch { - case NonFatal(t) => p failure t - } - }(executor) - + case Success(v) => try f(v) onComplete p.complete catch { case NonFatal(t) => p failure t } + } p.future } @@ -303,34 +271,14 @@ trait Future[+T] extends Awaitable[T] { * Await.result(h, Duration.Zero) // throw a NoSuchElementException * }}} */ - def filter(pred: T => Boolean)(implicit executor: ExecutionContext): Future[T] = { - val p = Promise[T]() - - onComplete { - case f: Failure[_] => p complete f.asInstanceOf[Failure[T]] - case Success(v) => - try { - if (pred(v)) p success v - else p failure new NoSuchElementException("Future.filter predicate is not satisfied") - } catch { - case NonFatal(t) => p failure t - } - }(executor) - - p.future - } + def filter(pred: T => Boolean)(implicit executor: ExecutionContext): Future[T] = + map { + r => if (pred(r)) r else throw new NoSuchElementException("Future.filter predicate is not satisfied") + } /** Used by for-comprehensions. */ final def withFilter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = filter(p)(executor) - // final def withFilter(p: T => Boolean) = new FutureWithFilter[T](this, p) - - // final class FutureWithFilter[+S](self: Future[S], p: S => Boolean) { - // def foreach(f: S => Unit): Unit = self filter p foreach f - // def map[R](f: S => R) = self filter p map f - // def flatMap[R](f: S => Future[R]) = self filter p flatMap f - // def withFilter(q: S => Boolean): FutureWithFilter[S] = new FutureWithFilter[S](self, x => p(x) && q(x)) - // } /** Creates a new future by mapping the value of the current future, if the given partial function is defined at that value. * @@ -352,22 +300,10 @@ trait Future[+T] extends Awaitable[T] { * Await.result(h, Duration.Zero) // throw a NoSuchElementException * }}} */ - def collect[S](pf: PartialFunction[T, S])(implicit executor: ExecutionContext): Future[S] = { - val p = Promise[S]() - - onComplete { - case f: Failure[_] => p complete f.asInstanceOf[Failure[S]] - case Success(v) => - try { - if (pf.isDefinedAt(v)) p success pf(v) - else p failure new NoSuchElementException("Future.collect partial function is not defined at: " + v) - } catch { - case NonFatal(t) => p failure t - } - }(executor) - - p.future - } + def collect[S](pf: PartialFunction[T, S])(implicit executor: ExecutionContext): Future[S] = + map { + r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t)) + } /** Creates a new future that will handle any matching throwable that this * future might contain. If there is no match, or if this future contains @@ -383,9 +319,7 @@ trait Future[+T] extends Awaitable[T] { */ def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U] = { val p = Promise[U]() - - onComplete { case tr => p.complete(tr recover pf) }(executor) - + onComplete { v => p complete (v recover pf) } p.future } @@ -404,17 +338,10 @@ trait Future[+T] extends Awaitable[T] { */ def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U] = { val p = Promise[U]() - onComplete { - case Failure(t) if pf isDefinedAt t => - try { - p completeWith pf(t) - } catch { - case NonFatal(t) => p failure t - } - case otherwise => p complete otherwise - }(executor) - + case Failure(t) => try pf.applyOrElse(t, (_: Throwable) => this) onComplete p.complete catch { case NonFatal(t) => p failure t } + case other => p complete other + } p.future } @@ -427,19 +354,12 @@ trait Future[+T] extends Awaitable[T] { * with the throwable stored in `that`. */ def zip[U](that: Future[U]): Future[(T, U)] = { + implicit val ec = internalExecutor val p = Promise[(T, U)]() - - this onComplete { + onComplete { case f: Failure[_] => p complete f.asInstanceOf[Failure[(T, U)]] - case Success(r) => - that onSuccess { - case r2 => p success ((r, r2)) - } - that onFailure { - case f => p failure f - } + case Success(s) => that onComplete { c => p.complete(c map { s2 => (s, s2) }) } } - p.future } @@ -458,6 +378,7 @@ trait Future[+T] extends Awaitable[T] { * }}} */ def fallbackTo[U >: T](that: Future[U]): Future[U] = { + implicit val ec = internalExecutor val p = Promise[U]() onComplete { case s @ Success(_) => p complete s @@ -470,23 +391,13 @@ trait Future[+T] extends Awaitable[T] { * that conforms to `S`'s erased type or a `ClassCastException` otherwise. */ def mapTo[S](implicit tag: ClassTag[S]): Future[S] = { - def boxedType(c: Class[_]): Class[_] = { + implicit val ec = internalExecutor + val boxedClass = { + val c = tag.runtimeClass if (c.isPrimitive) Future.toBoxed(c) else c } - - val p = Promise[S]() - - onComplete { - case f: Failure[_] => p complete f.asInstanceOf[Failure[S]] - case Success(t) => - p complete (try { - Success(boxedType(tag.runtimeClass).cast(t).asInstanceOf[S]) - } catch { - case e: ClassCastException => Failure(e) - }) - } - - p.future + require(boxedClass ne null) + map(s => boxedClass.cast(s).asInstanceOf[S]) } /** Applies the side-effecting function to the result of this future, and returns @@ -514,11 +425,9 @@ trait Future[+T] extends Awaitable[T] { */ def andThen[U](pf: PartialFunction[Try[T], U])(implicit executor: ExecutionContext): Future[T] = { val p = Promise[T]() - onComplete { - case r => try if (pf isDefinedAt r) pf(r) finally p complete r - }(executor) - + case r => try pf.applyOrElse[Try[T], Any](r, Predef.conforms[Try[T]]) finally p complete r + } p.future } @@ -579,14 +488,12 @@ object Future { } map (_.result) } - /** Returns a `Future` to the result of the first future in the list that is completed. + /** Returns a new `Future` to the result of the first future in the list that is completed. */ def firstCompletedOf[T](futures: TraversableOnce[Future[T]])(implicit executor: ExecutionContext): Future[T] = { val p = Promise[T]() - val completeFirst: Try[T] => Unit = p tryComplete _ - futures.foreach(_ onComplete completeFirst) - + futures foreach { _ onComplete completeFirst } p.future } @@ -626,7 +533,7 @@ object Future { * }}} */ def fold[T, R](futures: TraversableOnce[Future[T]])(zero: R)(foldFun: (R, T) => R)(implicit executor: ExecutionContext): Future[R] = { - if (futures.isEmpty) Promise.successful(zero).future + if (futures.isEmpty) Future.successful(zero) else sequence(futures).map(_.foldLeft(zero)(foldFun)) } @@ -638,7 +545,7 @@ object Future { * }}} */ def reduce[T, R >: T](futures: TraversableOnce[Future[T]])(op: (R, T) => R)(implicit executor: ExecutionContext): Future[R] = { - if (futures.isEmpty) Promise[R].failure(new NoSuchElementException("reduce attempted on empty collection")).future + if (futures.isEmpty) Future.failed(new NoSuchElementException("reduce attempted on empty collection")) else sequence(futures).map(_ reduceLeft op) } diff --git a/test/files/jvm/scala-concurrent-tck.scala b/test/files/jvm/scala-concurrent-tck.scala index b2b4183564..6e2b8ca033 100644 --- a/test/files/jvm/scala-concurrent-tck.scala +++ b/test/files/jvm/scala-concurrent-tck.scala @@ -15,21 +15,17 @@ import scala.reflect.{ classTag, ClassTag } import scala.tools.partest.TestUtil.intercept trait TestBase { - - def once(body: (() => Unit) => Unit) { - val sv = new SyncVar[Boolean] - body(() => sv put true) - sv.take(2000) + trait Done { def apply(proof: => Boolean): Unit } + def once(body: Done => Unit) { + import java.util.concurrent.{ LinkedBlockingQueue, TimeUnit } + val q = new LinkedBlockingQueue[Try[Boolean]] + body(new Done { + def apply(proof: => Boolean): Unit = q offer Try(proof) + }) + assert(q.poll(2000, TimeUnit.MILLISECONDS).get) + // Check that we don't get more than one completion + assert(q.poll(50, TimeUnit.MILLISECONDS) eq null) } - - // def assert(cond: => Boolean) { - // try { - // Predef.assert(cond) - // } catch { - // case e => e.printStackTrace() - // } - // } - } @@ -39,99 +35,52 @@ trait FutureCallbacks extends TestBase { def testOnSuccess(): Unit = once { done => var x = 0 - val f = future { - x = 1 - } - f onSuccess { - case _ => - done() - assert(x == 1) - } + val f = future { x = 1 } + f onSuccess { case _ => done(x == 1) } } def testOnSuccessWhenCompleted(): Unit = once { done => var x = 0 - val f = future { - x = 1 - } + val f = future { x = 1 } f onSuccess { - case _ => - assert(x == 1) + case _ if x == 1 => x = 2 - f onSuccess { - case _ => - assert(x == 2) - done() - } + f onSuccess { case _ => done(x == 2) } } } def testOnSuccessWhenFailed(): Unit = once { done => - val f = future[Unit] { - done() - throw new Exception - } - f onSuccess { - case _ => assert(false) - } + val f = future[Unit] { throw new Exception } + f onSuccess { case _ => done(false) } + f onFailure { case _ => done(true) } } def testOnFailure(): Unit = once { done => - var x = 0 - val f = future[Unit] { - x = 1 - throw new Exception - } - f onSuccess { - case _ => - done() - assert(false) - } - f onFailure { - case _ => - done() - assert(x == 1) - } + val f = future[Unit] { throw new Exception } + f onSuccess { case _ => done(false) } + f onFailure { case _ => done(true) } } def testOnFailureWhenSpecialThrowable(num: Int, cause: Throwable): Unit = once { done => - val f = future[Unit] { - throw cause - } - f onSuccess { - case _ => - done() - assert(false) - } + val f = future[Unit] { throw cause } + f onSuccess { case _ => done(false) } f onFailure { - case e: ExecutionException if (e.getCause == cause) => - done() - case _ => - done() - assert(false) + case e: ExecutionException if e.getCause == cause => done(true) + case _ => done(false) } } def testOnFailureWhenTimeoutException(): Unit = once { done => - val f = future[Unit] { - throw new TimeoutException() - } - f onSuccess { - case _ => - done() - assert(false) - } + val f = future[Unit] { throw new TimeoutException() } + f onSuccess { case _ => done(false) } f onFailure { - case e: TimeoutException => - done() - case other => - done() - assert(false) + case e: TimeoutException => done(true) + case _ => done(false) } } @@ -151,7 +100,6 @@ trait FutureCallbacks extends TestBase { //testOnFailureWhenSpecialThrowable(7, new InterruptedException) testThatNestedCallbacksDoNotYieldStackOverflow() testOnFailureWhenTimeoutException() - } @@ -162,207 +110,120 @@ trait FutureCombinators extends TestBase { done => val f = future { 5 } val g = f map { x => "result: " + x } - g onSuccess { - case s => - done() - assert(s == "result: 5") - } - g onFailure { - case _ => - done() - assert(false) - } + g onSuccess { case s => done(s == "result: 5") } + g onFailure { case _ => done(false) } } def testMapFailure(): Unit = once { done => - val f = future { - throw new Exception("exception message") - } + val f = future[Unit] { throw new Exception("exception message") } val g = f map { x => "result: " + x } - g onSuccess { - case _ => - done() - assert(false) - } - g onFailure { - case t => - done() - assert(t.getMessage() == "exception message") - } + g onSuccess { case _ => done(false) } + g onFailure { case t => done(t.getMessage() == "exception message") } } def testMapSuccessPF(): Unit = once { done => val f = future { 5 } val g = f map { case r => "result: " + r } - g onSuccess { - case s => - done() - assert(s == "result: 5") - } - g onFailure { - case _ => - done() - assert(false) - } + g onSuccess { case s => done(s == "result: 5") } + g onFailure { case _ => done(false) } } def testTransformSuccess(): Unit = once { done => val f = future { 5 } val g = f.transform(r => "result: " + r, identity) - g onSuccess { - case s => - done() - assert(s == "result: 5") - } - g onFailure { - case _ => - done() - assert(false) - } + g onSuccess { case s => done(s == "result: 5") } + g onFailure { case _ => done(false) } } def testTransformSuccessPF(): Unit = once { done => val f = future { 5 } val g = f.transform( { case r => "result: " + r }, identity) - g onSuccess { - case s => - done() - assert(s == "result: 5") - } - g onFailure { - case _ => - done() - assert(false) - } + g onSuccess { case s => done(s == "result: 5") } + g onFailure { case _ => done(false) } + } + +def testTransformFailure(): Unit = once { + done => + val transformed = new Exception("transformed") + val f = future { throw new Exception("expected") } + val g = f.transform(identity, _ => transformed) + g onSuccess { case _ => done(false) } + g onFailure { case e => done(e eq transformed) } + } + + def testTransformFailurePF(): Unit = once { + done => + val e = new Exception("expected") + val transformed = new Exception("transformed") + val f = future[Unit] { throw e } + val g = f.transform(identity, { case `e` => transformed }) + g onSuccess { case _ => done(false) } + g onFailure { case e => done(e eq transformed) } } def testFoldFailure(): Unit = once { done => - val f = future { - throw new Exception("exception message") - } + val f = future[Unit] { throw new Exception("expected") } val g = f.transform(r => "result: " + r, identity) - g onSuccess { - case _ => - done() - assert(false) - } - g onFailure { - case t => - done() - assert(t.getMessage() == "exception message") - } + g onSuccess { case _ => done(false) } + g onFailure { case t => done(t.getMessage() == "expected") } } def testFlatMapSuccess(): Unit = once { done => val f = future { 5 } val g = f flatMap { _ => future { 10 } } - g onSuccess { - case x => - done() - assert(x == 10) - } - g onFailure { - case _ => - done() - assert(false) - } + g onSuccess { case x => done(x == 10) } + g onFailure { case _ => done(false) } } def testFlatMapFailure(): Unit = once { done => - val f = future { - throw new Exception("exception message") - } + val f = future[Unit] { throw new Exception("expected") } val g = f flatMap { _ => future { 10 } } - g onSuccess { - case _ => - done() - assert(false) - } - g onFailure { - case t => - done() - assert(t.getMessage() == "exception message") - } + g onSuccess { case _ => done(false) } + g onFailure { case t => done(t.getMessage() == "expected") } } def testFilterSuccess(): Unit = once { done => val f = future { 4 } val g = f filter { _ % 2 == 0 } - g onSuccess { - case x: Int => - done() - assert(x == 4) - } - g onFailure { - case _ => - done() - assert(false) - } + g onSuccess { case x: Int => done(x == 4) } + g onFailure { case _ => done(false) } } def testFilterFailure(): Unit = once { done => val f = future { 4 } val g = f filter { _ % 2 == 1 } - g onSuccess { - case x: Int => - done() - assert(false) - } + g onSuccess { case x: Int => done(false) } g onFailure { - case e: NoSuchElementException => - done() - assert(true) - case _ => - done() - assert(false) + case e: NoSuchElementException => done(true) + case _ => done(false) } } def testCollectSuccess(): Unit = once { done => val f = future { -5 } - val g = f collect { - case x if x < 0 => -x - } - g onSuccess { - case x: Int => - done() - assert(x == 5) - } - g onFailure { - case _ => - done() - assert(false) - } + val g = f collect { case x if x < 0 => -x } + g onSuccess { case x: Int => done(x == 5) } + g onFailure { case _ => done(false) } } def testCollectFailure(): Unit = once { done => val f = future { -5 } - val g = f collect { - case x if x > 0 => x * 2 - } - g onSuccess { - case _ => - done() - assert(false) - } + val g = f collect { case x if x > 0 => x * 2 } + g onSuccess { case _ => done(false) } g onFailure { - case e: NoSuchElementException => - done() - assert(true) - case _ => - done() - assert(false) + case e: NoSuchElementException => done(true) + case _ => done(false) } } @@ -376,16 +237,8 @@ trait FutureCombinators extends TestBase { f foreach { x => p.success(x * 2) } val g = p.future - g.onSuccess { - case res: Int => - done() - assert(res == 10) - } - g.onFailure { - case _ => - done() - assert(false) - } + g.onSuccess { case res: Int => done(res == 10) } + g.onFailure { case _ => done(false) } } def testForeachFailure(): Unit = once { @@ -396,16 +249,8 @@ trait FutureCombinators extends TestBase { f onFailure { case _ => p.failure(new Exception) } val g = p.future - g.onSuccess { - case _ => - done() - assert(false) - } - g.onFailure { - case _ => - done() - assert(true) - } + g.onSuccess { case _ => done(false) } + g.onFailure { case _ => done(true) } } def testRecoverSuccess(): Unit = once { @@ -415,18 +260,9 @@ trait FutureCombinators extends TestBase { throw cause } recover { case re: RuntimeException => - "recovered" - } - f onSuccess { - case x => - done() - assert(x == "recovered") - } - f onFailure { case any => - done() - assert(false) - } - f + "recovered" } + f onSuccess { case x => done(x == "recovered") } + f onFailure { case any => done(false) } } def testRecoverFailure(): Unit = once { @@ -437,15 +273,8 @@ trait FutureCombinators extends TestBase { } recover { case te: TimeoutException => "timeout" } - f onSuccess { - case x => - done() - assert(false) - } - f onFailure { case any => - done() - assert(any == cause) - } + f onSuccess { case _ => done(false) } + f onFailure { case any => done(any == cause) } } def testRecoverWithSuccess(): Unit = once { @@ -457,15 +286,8 @@ trait FutureCombinators extends TestBase { case re: RuntimeException => future { "recovered" } } - f onSuccess { - case x => - done() - assert(x == "recovered") - } - f onFailure { case any => - done() - assert(false) - } + f onSuccess { case x => done(x == "recovered") } + f onFailure { case any => done(false) } } def testRecoverWithFailure(): Unit = once { @@ -477,15 +299,8 @@ trait FutureCombinators extends TestBase { case te: TimeoutException => future { "timeout" } } - f onSuccess { - case x => - done() - assert(false) - } - f onFailure { case any => - done() - assert(any == cause) - } + f onSuccess { case x => done(false) } + f onFailure { case any => done(any == cause) } } def testZipSuccess(): Unit = once { @@ -493,52 +308,28 @@ trait FutureCombinators extends TestBase { val f = future { 5 } val g = future { 6 } val h = f zip g - h onSuccess { - case (l: Int, r: Int) => - done() - assert(l+r == 11) - } - h onFailure { - case _ => - done() - assert(false) - } + h onSuccess { case (l: Int, r: Int) => done(l+r == 11) } + h onFailure { case _ => done(false) } } def testZipFailureLeft(): Unit = once { done => - val cause = new Exception("exception message") + val cause = new Exception("expected") val f = future { throw cause } val g = future { 6 } val h = f zip g - h onSuccess { - case _ => - done() - assert(false) - } - h onFailure { - case e: Exception => - done() - assert(e.getMessage == "exception message") - } + h onSuccess { case _ => done(false) } + h onFailure { case e: Exception => done(e.getMessage == "expected") } } def testZipFailureRight(): Unit = once { done => - val cause = new Exception("exception message") + val cause = new Exception("expected") val f = future { 5 } val g = future { throw cause } val h = f zip g - h onSuccess { - case _ => - done() - assert(false) - } - h onFailure { - case e: Exception => - done() - assert(e.getMessage == "exception message") - } + h onSuccess { case _ => done(false) } + h onFailure { case e: Exception => done(e.getMessage == "expected") } } def testFallbackTo(): Unit = once { @@ -546,17 +337,8 @@ trait FutureCombinators extends TestBase { val f = future { sys.error("failed") } val g = future { 5 } val h = f fallbackTo g - - h onSuccess { - case x: Int => - done() - assert(x == 5) - } - h onFailure { - case _ => - done() - assert(false) - } + h onSuccess { case x: Int => done(x == 5) } + h onFailure { case _ => done(false) } } def testFallbackToFailure(): Unit = once { @@ -566,16 +348,8 @@ trait FutureCombinators extends TestBase { val g = future { throw cause } val h = f fallbackTo g - h onSuccess { - case _ => - done() - assert(false) - } - h onFailure { - case e: Exception => - done() - assert(e == cause) - } + h onSuccess { case _ => done(false) } + h onFailure { case e => done(e eq cause) } } testMapSuccess() @@ -597,6 +371,8 @@ trait FutureCombinators extends TestBase { testZipFailureRight() testFallbackTo() testFallbackToFailure() + testTransformSuccess() + testTransformSuccessPF() } @@ -606,40 +382,26 @@ trait FutureProjections extends TestBase { def testFailedFailureOnComplete(): Unit = once { done => val cause = new RuntimeException - val f = future { - throw cause - } + val f = future { throw cause } f.failed onComplete { - case Success(t) => - assert(t == cause) - done() - case Failure(t) => - assert(false) + case Success(t) => done(t == cause) + case Failure(t) => done(false) } } def testFailedFailureOnSuccess(): Unit = once { done => val cause = new RuntimeException - val f = future { - throw cause - } - f.failed onSuccess { - case t => - assert(t == cause) - done() - } + val f = future { throw cause } + f.failed onSuccess { case t => done(t == cause) } } def testFailedSuccessOnComplete(): Unit = once { done => val f = future { 0 } f.failed onComplete { - case Success(t) => - assert(false) - case Failure(t) => - assert(t.isInstanceOf[NoSuchElementException]) - done() + case Failure(_: NoSuchElementException) => done(true) + case _ => done(false) } } @@ -647,19 +409,17 @@ trait FutureProjections extends TestBase { done => val f = future { 0 } f.failed onFailure { - case nsee: NoSuchElementException => - done() + case e: NoSuchElementException => done(true) + case _ => done(false) } + f.failed onSuccess { case _ => done(false) } } def testFailedFailureAwait(): Unit = once { done => val cause = new RuntimeException - val f = future { - throw cause - } - assert(Await.result(f.failed, Duration(500, "ms")) == cause) - done() + val f = future { throw cause } + done(Await.result(f.failed, Duration(500, "ms")) == cause) } def testFailedSuccessAwait(): Unit = once { @@ -667,9 +427,10 @@ trait FutureProjections extends TestBase { val f = future { 0 } try { Await.result(f.failed, Duration(500, "ms")) - assert(false) + done(false) } catch { - case nsee: NoSuchElementException => done() + case nsee: NoSuchElementException => done(true) + case _: Throwable => done(false) } } @@ -682,8 +443,8 @@ trait FutureProjections extends TestBase { Await.ready(f, Duration.Zero) Await.ready(f, Duration(500, "ms")) Await.ready(f, Duration.Inf) - done() - } onFailure { case x => throw x } + done(true) + } onFailure { case x => done(throw x) } } def testAwaitNegativeDuration(): Unit = once { done => @@ -692,8 +453,8 @@ trait FutureProjections extends TestBase { intercept[TimeoutException] { Await.ready(f, Duration.Zero) } intercept[TimeoutException] { Await.ready(f, Duration.MinusInf) } intercept[TimeoutException] { Await.ready(f, Duration(-500, "ms")) } - done() - } onFailure { case x => throw x } + done(true) + } onFailure { case x => done(throw x) } } testFailedFailureOnComplete() @@ -704,7 +465,6 @@ trait FutureProjections extends TestBase { testFailedSuccessAwait() testAwaitPositiveDuration() testAwaitNegativeDuration() - } @@ -714,33 +474,25 @@ trait Blocking extends TestBase { def testAwaitSuccess(): Unit = once { done => val f = future { 0 } - Await.result(f, Duration(500, "ms")) - done() + done(Await.result(f, Duration(500, "ms")) == 0) } def testAwaitFailure(): Unit = once { done => val cause = new RuntimeException - val f = future { - throw cause - } + val f = future { throw cause } try { Await.result(f, Duration(500, "ms")) - assert(false) + done(false) } catch { - case t => - assert(t == cause) - done() + case t: Throwable => done(t == cause) } } def testFQCNForAwaitAPI(): Unit = once { done => - - assert(classOf[CanAwait].getName == "scala.concurrent.CanAwait") - assert(Await.getClass.getName == "scala.concurrent.Await") - - done() + done(classOf[CanAwait].getName == "scala.concurrent.CanAwait" && + Await.getClass.getName == "scala.concurrent.Await") } testAwaitSuccess() @@ -813,22 +565,26 @@ trait Promises extends TestBase { val p = promise[Int]() val f = p.future - f onSuccess { - case x => - done() - assert(x == 5) - } - f onFailure { - case any => - done() - assert(false) - } + f onSuccess { case x => done(x == 5) } + f onFailure { case any => done(false) } p.success(5) } - testSuccess() + def testFailure(): Unit = once { + done => + val e = new Exception("expected") + val p = promise[Int]() + val f = p.future + + f onSuccess { case x => done(false) } + f onFailure { case any => done(any eq e) } + + p.failure(e) + } + testSuccess() + testFailure() } @@ -888,11 +644,11 @@ trait CustomExecutionContext extends TestBase { val count = countExecs { implicit ec => blocking { once { done => - val f = future({ assertNoEC() })(defaultEC) + val f = future(assertNoEC())(defaultEC) f onSuccess { case _ => assertEC() - done() + done(true) } assertNoEC() } @@ -911,7 +667,7 @@ trait CustomExecutionContext extends TestBase { f onSuccess { case _ => assertEC() - done() + done(true) } } } @@ -935,15 +691,10 @@ trait CustomExecutionContext extends TestBase { Promise.successful(x + 1).future.map(addOne).map(addOne) } onComplete { case Failure(t) => - try { - throw new AssertionError("error in test: " + t.getMessage, t) - } finally { - done() - } + done(throw new AssertionError("error in test: " + t.getMessage, t)) case Success(x) => assertEC() - assert(x == 14) - done() + done(x == 14) } assertNoEC() } @@ -999,21 +750,14 @@ trait ExecutionContextPrepare extends TestBase { done => theLocal.set("secret") val fut = future { 42 } - fut onComplete { - case _ => - assert(theLocal.get == "secret") - done() - } + fut onComplete { case _ => done(theLocal.get == "secret") } } def testMap(): Unit = once { done => theLocal.set("secret2") val fut = future { 42 } - fut map { x => - assert(theLocal.get == "secret2") - done() - } + fut map { x => done(theLocal.get == "secret2") } } testOnComplete() diff --git a/test/files/neg/valueclasses-impl-restrictions.check b/test/files/neg/valueclasses-impl-restrictions.check index 63924493aa..0af9173f74 100644 --- a/test/files/neg/valueclasses-impl-restrictions.check +++ b/test/files/neg/valueclasses-impl-restrictions.check @@ -6,12 +6,8 @@ valueclasses-impl-restrictions.scala:9: error: implementation restriction: neste This restriction is planned to be removed in subsequent releases. trait I2 { ^ -valueclasses-impl-restrictions.scala:15: error: implementation restriction: nested class is not allowed in value class -This restriction is planned to be removed in subsequent releases. - val i2 = new I2 { val q = x.s } - ^ -valueclasses-impl-restrictions.scala:21: error: implementation restriction: nested class is not allowed in value class +valueclasses-impl-restrictions.scala:23: error: implementation restriction: nested class is not allowed in value class This restriction is planned to be removed in subsequent releases. private[this] class I2(val q: String) ^ -four errors found +three errors found diff --git a/test/files/neg/valueclasses-impl-restrictions.scala b/test/files/neg/valueclasses-impl-restrictions.scala index 137f3f854c..f0577a94aa 100644 --- a/test/files/neg/valueclasses-impl-restrictions.scala +++ b/test/files/neg/valueclasses-impl-restrictions.scala @@ -12,8 +12,10 @@ class X1(val s: String) extends AnyVal { } def y(x: X1) = { - val i2 = new I2 { val q = x.s } + val i2 = new I2 { val q = x.s } // allowed as of SI-7571 i2.z + + { case x => x } : PartialFunction[Int, Int] // allowed } } diff --git a/test/files/run/t7571.scala b/test/files/run/t7571.scala new file mode 100644 index 0000000000..00b9695168 --- /dev/null +++ b/test/files/run/t7571.scala @@ -0,0 +1,12 @@ +class Foo(val a: Int) extends AnyVal { + def foo = { {case x => x + a}: PartialFunction[Int, Int]} + + def bar = (new {}).toString +} + +object Test extends App { + val x = new Foo(1).foo.apply(2) + assert(x == 3, x) + val s = new Foo(1).bar + assert(s.nonEmpty, s) +} diff --git a/test/files/specialized/SI-7344.scala b/test/files/specialized/SI-7344.scala new file mode 100644 index 0000000000..1040460bd1 --- /dev/null +++ b/test/files/specialized/SI-7344.scala @@ -0,0 +1,53 @@ +/* Test for SI-7344, where specialized methods inside the bodies of other + * methods are not specialized, although they might as well be. The name + * for the specialized method should not be different depending on the + * outside method/class' specialization. */ + +class Test[@specialized(Int, Double) X](val x: X) { + + def checkSpecialization[Y](@specialized(Int, Double) y: Y): X = { + + // checking the specialization using the method name, which we can + // extract from an exception's stack trace. We can match just the + // prefix, since the compiler will add a suffix to the method name + // during lambdalift, when it lifts the local methods outside. + def specMe[@specialized(Int, Double) T, N](t: T, n: N): Unit = checkNameStartsWith(n.toString) + + // expected to specialize: + specMe("x", "specMe") + specMe(123, "specMe$mIc$sp") + specMe(1.3, new { override def toString = "specMe$mDc$sp" }) + + x + } + + // name matching: + private[this] def checkNameStartsWith(prefix: String): Unit = { + val method = (new Exception).getStackTrace()(1).getMethodName() + assert(method.startsWith(prefix), method + ".startsWith(" + prefix + ") should be true") + } +} + +object Test extends App { + val t1 = new Test("x") + val t2 = new Test(123) + val t3 = new Test(1.3) + + // we want specialization to rewire these, + // that's why they're not in a for loop: + t1.checkSpecialization("x") + + // Prevented by SI-7579: + // The duplicator loses the @specialized annotation, + // so our tree transformation doesn't know it needs to + // specialize specMe inside the duplicated (and specialized) + // variants of the `checkSpecialization` method + // t1.checkSpecialization(123) + // t1.checkSpecialization(1.3) + // t2.checkSpecialization("x") + // t2.checkSpecialization(123) + // t2.checkSpecialization(1.3) + // t3.checkSpecialization("x") + // t3.checkSpecialization(123) + // t3.checkSpecialization(1.3) +} |