From 733b3220c9c099fcb68e09c37251bea8023198f2 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 7 Sep 2013 11:24:11 +0200 Subject: SI-7815 Dealias before deeming method type as dependent To enable eta-expansion of method types seen from a prefix that renders the result type as independent from the parameter symbols. The enclosed test shows that we dealias types before checking dependence, and that we do this deeply (e.g. type arguments are also dealised.) An existing test, neg/error_dependentMethodTpeConversionToFunction, confirms that bona-fide dependent methods are still prohibited from eta expansion. --- test/files/pos/t7815.scala | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 test/files/pos/t7815.scala (limited to 'test') diff --git a/test/files/pos/t7815.scala b/test/files/pos/t7815.scala new file mode 100644 index 0000000000..12a434c5b0 --- /dev/null +++ b/test/files/pos/t7815.scala @@ -0,0 +1,30 @@ +import language.higherKinds + +trait Foo[A <: AnyRef] { + type Repr + def f(a: A): Repr + def g(a: A): Option[Repr] + + type M[X] + def m(a: A): M[a.type] + + type Id[X] = X + def n(a: A): Id[(Repr, M[a.type])] + +} + +object Foo { + type Aux[A <: AnyRef, B] = Foo[A] { type Repr = B; type M[X] = Int } + +} + +object Main extends App { + def mapWithFoo[A <: AnyRef, B](as: List[A])(implicit foo: Foo.Aux[A, B]) = { + // Should be Eta expandable because the result type of `f` is not + // dependant on the value, it is just `B`. + as map foo.f + as map foo.g + as map foo.m + as map foo.n + } +} -- cgit v1.2.3 From bf0f9da50983941b8575ffae284d9c90de886020 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 9 Sep 2013 10:06:00 +0200 Subject: SI-7825 Consider DEFAULTMETHOD when refchecking concreteness A class should not be required to implement a Java default method. This commit uses `isDeferredNotDefault` in place of `isDeferred` when finding unimplemented methods. The test itself does not depend on Java 8 as we use scalac's Java source parser to set things up. --- bincompat-backward.whitelist.conf | 8 +++++ bincompat-forward.whitelist.conf | 24 +++++++++++++++ .../scala/tools/nsc/typechecker/RefChecks.scala | 4 +-- src/reflect/scala/reflect/internal/HasFlags.scala | 3 ++ test/files/run/t7825.scala | 34 ++++++++++++++++++++++ 5 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 test/files/run/t7825.scala (limited to 'test') diff --git a/bincompat-backward.whitelist.conf b/bincompat-backward.whitelist.conf index 38d26c7fb7..0500ec10ab 100644 --- a/bincompat-backward.whitelist.conf +++ b/bincompat-backward.whitelist.conf @@ -303,6 +303,14 @@ filter { { matchName="scala.reflect.internal.Types.scala$reflect$internal$Types$$uniques_=" problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.HasFlags.isDeferredOrDefault" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.HasFlags.isDeferredNotDefault" + problemName=MissingMethodProblem } ] } diff --git a/bincompat-forward.whitelist.conf b/bincompat-forward.whitelist.conf index a64eb0ba5d..2ece671638 100644 --- a/bincompat-forward.whitelist.conf +++ b/bincompat-forward.whitelist.conf @@ -1407,6 +1407,30 @@ filter { { matchName="scala.reflect.internal.Types.scala$reflect$internal$Types$$uniques_=" problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.Symbols#Symbol.isDeferredOrDefault" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.Symbols#Symbol.isDeferredNotDefault" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.HasFlags.isDeferredOrDefault" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.HasFlags.isDeferredNotDefault" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.Trees#Modifiers.isDeferredOrDefault" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.Trees#Modifiers.isDeferredNotDefault" + problemName=MissingMethodProblem } ] } diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 09c4878b2f..83afb3a728 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -383,7 +383,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans overrideError("cannot be used here - classes can only override abstract types"); } else if (other.isEffectivelyFinal) { // (1.2) overrideError("cannot override final member"); - } else if (!other.isDeferred && !other.hasFlag(DEFAULTMETHOD) && !member.isAnyOverride && !member.isSynthetic) { // (*) + } else if (!other.isDeferredOrDefault && !member.isAnyOverride && !member.isSynthetic) { // (*) // (*) Synthetic exclusion for (at least) default getters, fixes SI-5178. We cannot assign the OVERRIDE flag to // the default getter: one default getter might sometimes override, sometimes not. Example in comment on ticket. if (isNeitherInClass && !(other.owner isSubClass member.owner)) @@ -565,7 +565,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans def checkNoAbstractMembers(): Unit = { // Avoid spurious duplicates: first gather any missing members. def memberList = clazz.info.nonPrivateMembersAdmitting(VBRIDGE) - val (missing, rest) = memberList partition (m => m.isDeferred && !ignoreDeferred(m)) + val (missing, rest) = memberList partition (m => m.isDeferredNotDefault && !ignoreDeferred(m)) // Group missing members by the name of the underlying symbol, // to consolidate getters and setters. val grouped = missing groupBy (sym => analyzer.underlyingSymbol(sym).name) diff --git a/src/reflect/scala/reflect/internal/HasFlags.scala b/src/reflect/scala/reflect/internal/HasFlags.scala index 4a3663b8ea..12fd3a31cd 100644 --- a/src/reflect/scala/reflect/internal/HasFlags.scala +++ b/src/reflect/scala/reflect/internal/HasFlags.scala @@ -115,6 +115,9 @@ trait HasFlags { def isSynthetic = hasFlag(SYNTHETIC) def isTrait = hasFlag(TRAIT) && !hasFlag(PARAM) + def isDeferredOrDefault = hasFlag(DEFERRED | DEFAULTMETHOD) + def isDeferredNotDefault = isDeferred && !hasFlag(DEFAULTMETHOD) + def flagBitsToString(bits: Long): String = { // Fast path for common case if (bits == 0L) "" else { diff --git a/test/files/run/t7825.scala b/test/files/run/t7825.scala new file mode 100644 index 0000000000..65ca06fdfc --- /dev/null +++ b/test/files/run/t7825.scala @@ -0,0 +1,34 @@ +import scala.tools.partest._ + +object Test extends CompilerTest { + import global._ + + override lazy val units: List[CompilationUnit] = { + // We can test this on JDK6. + javaCompilationUnits(global)(defaultMethodSource) ++ compilationUnits(global)(scalaExtendsDefault) + } + + private def defaultMethodSource = """ +public interface Iterator { + boolean hasNext(); + E next(); + default void remove() { + throw new UnsupportedOperationException("remove"); + } +} + """ + + private def scalaExtendsDefault = """ +object Test { + object X extends Iterator[String] { + def hasNext = true + def next = "!" + } +} + """ + + // We're only checking we that the Scala compilation unit passes refchecks + // No further checks are needed here. + def check(source: String, unit: global.CompilationUnit): Unit = { + } +} -- cgit v1.2.3 From a1796aa5ed280203c1bfc086ee5a368ae628611d Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 9 Sep 2013 22:32:28 +0200 Subject: SI-7398 Enable test for Java 8 source parser under Java 8 There is no need to skip it as it only depends on our changes to our JavaParser, and not on any bytecode features of Java 8. --- test/files/run/t7398.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'test') diff --git a/test/files/run/t7398.scala b/test/files/run/t7398.scala index dd59697b71..493c4dcf40 100644 --- a/test/files/run/t7398.scala +++ b/test/files/run/t7398.scala @@ -3,11 +3,9 @@ import scala.tools.partest._ object Test extends CompilerTest { import global._ - // This way we auto-pass on non-java8 since there's nothing to check - override lazy val units: List[CompilationUnit] = testUnderJavaAtLeast("1.8") { + override lazy val units: List[CompilationUnit] = { + // This test itself does not depend on JDK8. javaCompilationUnits(global)(defaultMethodSource) - } otherwise { - Nil } private def defaultMethodSource = """ -- cgit v1.2.3 From 7f4b44b612abdc62fba9810194cee17c7d0de37e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 20 Sep 2013 14:02:44 +0200 Subject: SI-7861 Don't execute internal callbacks on the user Executor Callbacks internal to the implementation of Futures should be executed with the `InternalCallbackExecutor`, rather than the user supplied `Executor`. In a refactoring da54f34a6, `recoverWith` and `flatMap` no longer played by these rules. This was noticed by a persnickety test in Play. Before this patch, the enclosed test outputs: % scala-hash v2.10.3-RC2 test/files/run/future-flatmap-exec-count.scala mapping execute() flatmapping execute() execute() recovering execute() execute() --- src/library/scala/concurrent/Future.scala | 11 ++--- test/files/run/future-flatmap-exec-count.check | 6 +++ test/files/run/future-flatmap-exec-count.scala | 61 ++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 test/files/run/future-flatmap-exec-count.check create mode 100644 test/files/run/future-flatmap-exec-count.scala (limited to 'test') diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index caf91fbb0f..39946e4472 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -96,11 +96,8 @@ trait Future[+T] extends Awaitable[T] { // of the Future trait. Note that this will // (modulo bugs) _never_ execute a callback // other than those below in this same file. - // As a nice side benefit, having this implicit - // here forces an ambiguity in those methods - // that also have an executor parameter, which - // keeps us from accidentally forgetting to use - // the executor parameter. + // + // See the documentation on `InternalCallbackExecutor` for more details. private def internalExecutor = Future.InternalCallbackExecutor /* Callbacks */ @@ -254,7 +251,7 @@ trait Future[+T] extends Awaitable[T] { case Success(v) => try f(v) match { // If possible, link DefaultPromises to avoid space leaks case dp: DefaultPromise[_] => dp.asInstanceOf[DefaultPromise[S]].linkRootOf(p) - case fut => fut onComplete p.complete + case fut => fut.onComplete(p.complete)(internalExecutor) } catch { case NonFatal(t) => p failure t } } p.future @@ -344,7 +341,7 @@ 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) => try pf.applyOrElse(t, (_: Throwable) => this) onComplete p.complete catch { case NonFatal(t) => p failure t } + case Failure(t) => try pf.applyOrElse(t, (_: Throwable) => this).onComplete(p.complete)(internalExecutor) catch { case NonFatal(t) => p failure t } case other => p complete other } p.future diff --git a/test/files/run/future-flatmap-exec-count.check b/test/files/run/future-flatmap-exec-count.check new file mode 100644 index 0000000000..dd9dce64ed --- /dev/null +++ b/test/files/run/future-flatmap-exec-count.check @@ -0,0 +1,6 @@ +mapping +execute() +flatmapping +execute() +recovering +execute() diff --git a/test/files/run/future-flatmap-exec-count.scala b/test/files/run/future-flatmap-exec-count.scala new file mode 100644 index 0000000000..86c37be938 --- /dev/null +++ b/test/files/run/future-flatmap-exec-count.scala @@ -0,0 +1,61 @@ +import scala.concurrent._ +import java.util.concurrent.atomic.AtomicInteger + +object Test { + def main(args: Array[String]) { + test() + } + + def test() = { + def await(f: Future[Any]) = + Await.result(f, duration.Duration.Inf) + + val ec = new TestExecutionContext(ExecutionContext.Implicits.global) + + { + val p = Promise[Int]() + val fp = p.future + println("mapping") + val mapped = fp.map(x => x)(ec) + p.success(0) + await(mapped) + } + + { + println("flatmapping") + val p = Promise[Int]() + val fp = p.future + val flatMapped = fp.flatMap({ (x: Int) => + Future.successful(2 * x) + })(ec) + p.success(0) + await(flatMapped) + } + + { + println("recovering") + val recovered = Future.failed(new Throwable()).recoverWith { + case _ => Future.successful(2) + }(ec) + await(recovered) + } + } + + class TestExecutionContext(delegate: ExecutionContext) extends ExecutionContext { + def execute(runnable: Runnable): Unit = ??? + + def reportFailure(t: Throwable): Unit = ??? + + override def prepare(): ExecutionContext = { + val preparedDelegate = delegate.prepare() + return new ExecutionContext { + def execute(runnable: Runnable): Unit = { + println("execute()") + preparedDelegate.execute(runnable) + } + + def reportFailure(t: Throwable): Unit = ??? + } + } + } +} -- cgit v1.2.3