summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdriaan Moors <adriaan.moors@typesafe.com>2014-11-06 15:21:19 +0100
committerAdriaan Moors <adriaan.moors@typesafe.com>2014-11-07 12:00:48 +0100
commita34be74e8f2630f1862a34e538cbe1162d279e5e (patch)
treea404c5fb955bf1fd1f192895cee32adb805544df
parent9c36a76171d497fe277030d2f682f927ae2657f4 (diff)
downloadscala-a34be74e8f2630f1862a34e538cbe1162d279e5e.tar.gz
scala-a34be74e8f2630f1862a34e538cbe1162d279e5e.tar.bz2
scala-a34be74e8f2630f1862a34e538cbe1162d279e5e.zip
[sammy] use correct type for method to override
Don't naively derive types for the single method's signature from the provided function's type, as it may be a subtype of the method's MethodType. Instead, once the sam class type is fully defined, determine the sam's info as seen from the class's type, and use those to generate the correct override. ``` scala> Arrays.stream(Array(1, 2, 3)).map(n => 2 * n + 1).average.ifPresent(println) 5.0 scala> IntStream.range(1, 4).forEach(println) 1 2 3 ``` Also, minimal error reporting Can't figure out how to do it properly, but some reporting is better than crashing. Right? Test case that illustrates necessity of the clumsy stop gap `if (block exists (_.isErroneous))` enclosed as `sammy_error_exist_no_crash` added TODO for repeated and by-name params
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala51
-rw-r--r--src/reflect/scala/reflect/internal/tpe/TypeMaps.scala2
-rw-r--r--test/files/neg/sammy_error_exist_no_crash.check6
-rw-r--r--test/files/neg/sammy_error_exist_no_crash.flags1
-rw-r--r--test/files/neg/sammy_error_exist_no_crash.scala6
-rw-r--r--test/files/neg/sammy_restrictions.scala28
-rw-r--r--test/files/pos/sammy_exist.scala13
-rw-r--r--test/files/pos/sammy_override.flags1
-rw-r--r--test/files/pos/sammy_override.scala8
9 files changed, 88 insertions, 28 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index c41b81aaf5..5eee60031e 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -2700,7 +2700,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* `{
* def apply$body(p1: T1, ..., pN: TN): T = body
* new S {
- * def apply(p1: T1, ..., pN: TN): T = apply$body(p1,..., pN)
+ * def apply(p1: T1', ..., pN: TN'): T' = apply$body(p1,..., pN)
* }
* }`
*
@@ -2710,6 +2710,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
*
* The `apply` method is identified by the argument `sam`; `S` corresponds to the argument `samClassTp`,
* and `resPt` is derived from `samClassTp` -- it may be fully defined, or not...
+ * If it is not fully defined, we derive `samClassTpFullyDefined` by inferring any unknown type parameters.
+ *
+ * The types T1' ... TN' and T' are derived from the method signature of the sam method,
+ * as seen from the fully defined `samClassTpFullyDefined`.
*
* The function's body is put in a method outside of the class definition to enforce scoping.
* S's members should not be in scope in `body`.
@@ -2721,6 +2725,35 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* However T must be fully defined before we type the instantiation, as it'll end up as a parent type,
* which must be fully defined. Would be nice to have some kind of mechanism to insert type vars in a block of code,
* and have the instantiation of the first occurrence propagate to the rest of the block.
+ *
+ * TODO: repeated and by-name params
+ * scala> trait LazySink { def accept(a: => Any): Unit }
+ * defined trait LazySink
+ *
+ * scala> val f: LazySink = (a) => (a, a)
+ * f: LazySink = $anonfun$1@1fb26910
+ *
+ * scala> f(println("!"))
+ * <console>:10: error: LazySink does not take parameters
+ * f(println("!"))
+ * ^
+ *
+ * scala> f.accept(println("!"))
+ * !
+ * !
+ * This looks like a bug:
+ *
+ * scala> trait RepeatedSink { def accept(a: Any*): Unit }
+ * defined trait RepeatedSink
+ *
+ * scala> val f: RepeatedSink = (a) => println(a)
+ * f: RepeatedSink = $anonfun$1@4799abc2
+ *
+ * scala> f.accept(1)
+ * WrappedArray(WrappedArray(1))
+ *
+ * scala> f.accept(1, 2)
+ * WrappedArray(WrappedArray(1, 2))
*/
def synthesizeSAMFunction(sam: Symbol, fun: Function, resPt: Type, samClassTp: Type, mode: Mode): Tree = {
// assert(fun.vparams forall (vp => isFullyDefined(vp.tpt.tpe))) -- by construction, as we take them from sam's info
@@ -2801,13 +2834,20 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
samClassTp
}
- // `final override def ${sam.name}($p1: $T1, ..., $pN: $TN): $resPt = ${sam.name}\$body'($p1, ..., $pN)`
+ // what's the signature of the method that we should actually be overriding?
+ val samMethTp = samClassTpFullyDefined memberInfo sam
+ // Before the mutation, `tp <:< vpar.tpt.tpe` should hold.
+ // TODO: error message when this is not the case, as the expansion won't type check
+ // - Ti' <:< Ti and T <: T' must hold for the samDef body to type check
+ val funArgTps = foreach2(samMethTp.paramTypes, fun.vparams)((tp, vpar) => vpar.tpt setType tp)
+
+ // `final override def ${sam.name}($p1: $T1', ..., $pN: $TN'): ${samMethTp.finalResultType} = ${sam.name}\$body'($p1, ..., $pN)`
val samDef =
DefDef(Modifiers(FINAL | OVERRIDE | SYNTHETIC),
sam.name.toTermName,
Nil,
List(fun.vparams),
- TypeTree(samBodyDef.tpt.tpe) setPos sampos.focus,
+ TypeTree(samMethTp.finalResultType) setPos sampos.focus,
Apply(Ident(bodyName), fun.vparams map (p => Ident(p.name)))
)
@@ -2838,6 +2878,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
)
}
+ // TODO: improve error reporting -- when we're in silent mode (from `silent(_.doTypedApply(tree, fun, args, mode, pt)) orElse onError`)
+ // the errors in the function don't get out...
+ if (block exists (_.isErroneous))
+ context.error(fun.pos, s"Could not derive subclass of $samClassTp\n (with SAM `def $sam$samMethTp`)\n based on: $fun.")
+
classDef.symbol addAnnotation SerialVersionUIDAnnotation
block
}
diff --git a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala
index 662306d5ab..c705ca7069 100644
--- a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala
+++ b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala
@@ -432,7 +432,7 @@ private[internal] trait TypeMaps {
object wildcardExtrapolation extends TypeMap(trackVariance = true) {
def apply(tp: Type): Type =
tp match {
- case BoundedWildcardType(TypeBounds(lo, AnyTpe)) if variance.isContravariant =>lo
+ case BoundedWildcardType(TypeBounds(lo, AnyTpe)) if variance.isContravariant => lo
case BoundedWildcardType(TypeBounds(NothingTpe, hi)) if variance.isCovariant => hi
case tp => mapOver(tp)
}
diff --git a/test/files/neg/sammy_error_exist_no_crash.check b/test/files/neg/sammy_error_exist_no_crash.check
new file mode 100644
index 0000000000..a0d2237ce0
--- /dev/null
+++ b/test/files/neg/sammy_error_exist_no_crash.check
@@ -0,0 +1,6 @@
+sammy_error_exist_no_crash.scala:5: error: Could not derive subclass of F[? >: String]
+ (with SAM `def method apply(s: String)Int`)
+ based on: ((x$1: String) => x$1.<parseInt: error>).
+ bar(_.parseInt)
+ ^
+one error found
diff --git a/test/files/neg/sammy_error_exist_no_crash.flags b/test/files/neg/sammy_error_exist_no_crash.flags
new file mode 100644
index 0000000000..e1b37447c9
--- /dev/null
+++ b/test/files/neg/sammy_error_exist_no_crash.flags
@@ -0,0 +1 @@
+-Xexperimental \ No newline at end of file
diff --git a/test/files/neg/sammy_error_exist_no_crash.scala b/test/files/neg/sammy_error_exist_no_crash.scala
new file mode 100644
index 0000000000..da7e47206f
--- /dev/null
+++ b/test/files/neg/sammy_error_exist_no_crash.scala
@@ -0,0 +1,6 @@
+abstract class F[T] { def apply(s: T): Int }
+
+object NeedsNiceError {
+ def bar(x: F[_ >: String]) = ???
+ bar(_.parseInt)
+} \ No newline at end of file
diff --git a/test/files/neg/sammy_restrictions.scala b/test/files/neg/sammy_restrictions.scala
index 5f1a04cd20..d003cfaf36 100644
--- a/test/files/neg/sammy_restrictions.scala
+++ b/test/files/neg/sammy_restrictions.scala
@@ -1,28 +1,28 @@
-class NoAbstract
+abstract class NoAbstract
-class TwoAbstract { def ap(a: Int): Int; def pa(a: Int): Int }
+abstract class TwoAbstract { def ap(a: Int): Int; def pa(a: Int): Int }
-class Base // check that the super class constructor isn't considered.
-class NoEmptyConstructor(a: Int) extends Base { def this(a: String) = this(0); def ap(a: Int): Int }
+abstract class Base // check that the super class constructor isn't considered.
+abstract class NoEmptyConstructor(a: Int) extends Base { def this(a: String) = this(0); def ap(a: Int): Int }
-class OneEmptyConstructor() { def this(a: Int) = this(); def ap(a: Int): Int }
+abstract class OneEmptyConstructor() { def this(a: Int) = this(); def ap(a: Int): Int }
-class OneEmptySecondaryConstructor(a: Int) { def this() = this(0); def ap(a: Int): Int }
+abstract class OneEmptySecondaryConstructor(a: Int) { def this() = this(0); def ap(a: Int): Int }
-class MultipleConstructorLists()() { def ap(a: Int): Int }
+abstract class MultipleConstructorLists()() { def ap(a: Int): Int }
-class MultipleMethodLists()() { def ap(a: Int)(): Int }
+abstract class MultipleMethodLists()() { def ap(a: Int)(): Int }
-class ImplicitConstructorParam()(implicit a: String) { def ap(a: Int): Int }
+abstract class ImplicitConstructorParam()(implicit a: String) { def ap(a: Int): Int }
-class ImplicitMethodParam() { def ap(a: Int)(implicit b: String): Int }
+abstract class ImplicitMethodParam() { def ap(a: Int)(implicit b: String): Int }
-class PolyClass[T] { def ap(a: T): T }
+abstract class PolyClass[T] { def ap(a: T): T }
-class PolyMethod { def ap[T](a: T): T }
+abstract class PolyMethod { def ap[T](a: T): T }
-class OneAbstract { def ap(a: Any): Any }
-class DerivedOneAbstract extends OneAbstract
+abstract class OneAbstract { def ap(a: Int): Any }
+abstract class DerivedOneAbstract extends OneAbstract
object Test {
implicit val s: String = ""
diff --git a/test/files/pos/sammy_exist.scala b/test/files/pos/sammy_exist.scala
index 95c7c7b68e..f05ae20463 100644
--- a/test/files/pos/sammy_exist.scala
+++ b/test/files/pos/sammy_exist.scala
@@ -9,16 +9,9 @@ class S[T](x: T) { def map[R](f: Fun[_ >: T, _ <: R]): R = f(x) }
class Bla { def foo: Bla = this }
+// NOTE: inferred types show unmoored skolems, should pack them to display properly as bounded wildcards
object T {
- val aBlaSAM = (new S(new Bla)).map(_.foo) // : Bla should be inferred, when running under -Xexperimental [TODO]
+ val aBlaSAM = (new S(new Bla)).map(_.foo)
val fun: Fun[Bla, Bla] = (x: Bla) => x
- val aBlaSAMX = (new S(new Bla)).map(fun) // : Bla should be inferred, when running under -Xexperimental [TODO]
+ val aBlaSAMX = (new S(new Bla)).map(fun)
}
-//
-// // or, maybe by variance-cast?
-// import annotation.unchecked.{uncheckedVariance => uv}
-// type SFun[-A, +B] = Fun[_ >: A, _ <: B @uv]
-//
-// def jf[T, R](f: T => R): SFun[T, R] = (x: T) => f(x): R
-//
-// val aBlaSAM = (new S(new Bla)).map(jf(_.foo)) // : Bla should be inferred [type checks, but existential inferred] \ No newline at end of file
diff --git a/test/files/pos/sammy_override.flags b/test/files/pos/sammy_override.flags
new file mode 100644
index 0000000000..48fd867160
--- /dev/null
+++ b/test/files/pos/sammy_override.flags
@@ -0,0 +1 @@
+-Xexperimental
diff --git a/test/files/pos/sammy_override.scala b/test/files/pos/sammy_override.scala
new file mode 100644
index 0000000000..a1d0651c39
--- /dev/null
+++ b/test/files/pos/sammy_override.scala
@@ -0,0 +1,8 @@
+trait IntConsumer {
+ def consume(x: Int): Unit
+}
+
+object Test {
+ def anyConsumer(x: Any): Unit = ???
+ val f: IntConsumer = anyConsumer
+} \ No newline at end of file