Skip to content

Commit a34be74

Browse files
committed
[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
1 parent 9c36a76 commit a34be74

File tree

9 files changed

+88
-28
lines changed

9 files changed

+88
-28
lines changed

src/compiler/scala/tools/nsc/typechecker/Typers.scala

+48-3
Original file line numberDiff line numberDiff line change
@@ -2700,7 +2700,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
27002700
* `{
27012701
* def apply$body(p1: T1, ..., pN: TN): T = body
27022702
* new S {
2703-
* def apply(p1: T1, ..., pN: TN): T = apply$body(p1,..., pN)
2703+
* def apply(p1: T1', ..., pN: TN'): T' = apply$body(p1,..., pN)
27042704
* }
27052705
* }`
27062706
*
@@ -2710,6 +2710,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
27102710
*
27112711
* The `apply` method is identified by the argument `sam`; `S` corresponds to the argument `samClassTp`,
27122712
* and `resPt` is derived from `samClassTp` -- it may be fully defined, or not...
2713+
* If it is not fully defined, we derive `samClassTpFullyDefined` by inferring any unknown type parameters.
2714+
*
2715+
* The types T1' ... TN' and T' are derived from the method signature of the sam method,
2716+
* as seen from the fully defined `samClassTpFullyDefined`.
27132717
*
27142718
* The function's body is put in a method outside of the class definition to enforce scoping.
27152719
* S's members should not be in scope in `body`.
@@ -2721,6 +2725,35 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
27212725
* However T must be fully defined before we type the instantiation, as it'll end up as a parent type,
27222726
* which must be fully defined. Would be nice to have some kind of mechanism to insert type vars in a block of code,
27232727
* and have the instantiation of the first occurrence propagate to the rest of the block.
2728+
*
2729+
* TODO: repeated and by-name params
2730+
* scala> trait LazySink { def accept(a: => Any): Unit }
2731+
* defined trait LazySink
2732+
*
2733+
* scala> val f: LazySink = (a) => (a, a)
2734+
* f: LazySink = $anonfun$1@1fb26910
2735+
*
2736+
* scala> f(println("!"))
2737+
* <console>:10: error: LazySink does not take parameters
2738+
* f(println("!"))
2739+
* ^
2740+
*
2741+
* scala> f.accept(println("!"))
2742+
* !
2743+
* !
2744+
* This looks like a bug:
2745+
*
2746+
* scala> trait RepeatedSink { def accept(a: Any*): Unit }
2747+
* defined trait RepeatedSink
2748+
*
2749+
* scala> val f: RepeatedSink = (a) => println(a)
2750+
* f: RepeatedSink = $anonfun$1@4799abc2
2751+
*
2752+
* scala> f.accept(1)
2753+
* WrappedArray(WrappedArray(1))
2754+
*
2755+
* scala> f.accept(1, 2)
2756+
* WrappedArray(WrappedArray(1, 2))
27242757
*/
27252758
def synthesizeSAMFunction(sam: Symbol, fun: Function, resPt: Type, samClassTp: Type, mode: Mode): Tree = {
27262759
// 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
28012834
samClassTp
28022835
}
28032836

2804-
// `final override def ${sam.name}($p1: $T1, ..., $pN: $TN): $resPt = ${sam.name}\$body'($p1, ..., $pN)`
2837+
// what's the signature of the method that we should actually be overriding?
2838+
val samMethTp = samClassTpFullyDefined memberInfo sam
2839+
// Before the mutation, `tp <:< vpar.tpt.tpe` should hold.
2840+
// TODO: error message when this is not the case, as the expansion won't type check
2841+
// - Ti' <:< Ti and T <: T' must hold for the samDef body to type check
2842+
val funArgTps = foreach2(samMethTp.paramTypes, fun.vparams)((tp, vpar) => vpar.tpt setType tp)
2843+
2844+
// `final override def ${sam.name}($p1: $T1', ..., $pN: $TN'): ${samMethTp.finalResultType} = ${sam.name}\$body'($p1, ..., $pN)`
28052845
val samDef =
28062846
DefDef(Modifiers(FINAL | OVERRIDE | SYNTHETIC),
28072847
sam.name.toTermName,
28082848
Nil,
28092849
List(fun.vparams),
2810-
TypeTree(samBodyDef.tpt.tpe) setPos sampos.focus,
2850+
TypeTree(samMethTp.finalResultType) setPos sampos.focus,
28112851
Apply(Ident(bodyName), fun.vparams map (p => Ident(p.name)))
28122852
)
28132853

@@ -2838,6 +2878,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
28382878
)
28392879
}
28402880

2881+
// TODO: improve error reporting -- when we're in silent mode (from `silent(_.doTypedApply(tree, fun, args, mode, pt)) orElse onError`)
2882+
// the errors in the function don't get out...
2883+
if (block exists (_.isErroneous))
2884+
context.error(fun.pos, s"Could not derive subclass of $samClassTp\n (with SAM `def $sam$samMethTp`)\n based on: $fun.")
2885+
28412886
classDef.symbol addAnnotation SerialVersionUIDAnnotation
28422887
block
28432888
}

src/reflect/scala/reflect/internal/tpe/TypeMaps.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ private[internal] trait TypeMaps {
432432
object wildcardExtrapolation extends TypeMap(trackVariance = true) {
433433
def apply(tp: Type): Type =
434434
tp match {
435-
case BoundedWildcardType(TypeBounds(lo, AnyTpe)) if variance.isContravariant =>lo
435+
case BoundedWildcardType(TypeBounds(lo, AnyTpe)) if variance.isContravariant => lo
436436
case BoundedWildcardType(TypeBounds(NothingTpe, hi)) if variance.isCovariant => hi
437437
case tp => mapOver(tp)
438438
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
sammy_error_exist_no_crash.scala:5: error: Could not derive subclass of F[? >: String]
2+
(with SAM `def method apply(s: String)Int`)
3+
based on: ((x$1: String) => x$1.<parseInt: error>).
4+
bar(_.parseInt)
5+
^
6+
one error found
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xexperimental
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
abstract class F[T] { def apply(s: T): Int }
2+
3+
object NeedsNiceError {
4+
def bar(x: F[_ >: String]) = ???
5+
bar(_.parseInt)
6+
}

test/files/neg/sammy_restrictions.scala

+14-14
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
class NoAbstract
1+
abstract class NoAbstract
22

3-
class TwoAbstract { def ap(a: Int): Int; def pa(a: Int): Int }
3+
abstract class TwoAbstract { def ap(a: Int): Int; def pa(a: Int): Int }
44

5-
class Base // check that the super class constructor isn't considered.
6-
class NoEmptyConstructor(a: Int) extends Base { def this(a: String) = this(0); def ap(a: Int): Int }
5+
abstract class Base // check that the super class constructor isn't considered.
6+
abstract class NoEmptyConstructor(a: Int) extends Base { def this(a: String) = this(0); def ap(a: Int): Int }
77

8-
class OneEmptyConstructor() { def this(a: Int) = this(); def ap(a: Int): Int }
8+
abstract class OneEmptyConstructor() { def this(a: Int) = this(); def ap(a: Int): Int }
99

10-
class OneEmptySecondaryConstructor(a: Int) { def this() = this(0); def ap(a: Int): Int }
10+
abstract class OneEmptySecondaryConstructor(a: Int) { def this() = this(0); def ap(a: Int): Int }
1111

12-
class MultipleConstructorLists()() { def ap(a: Int): Int }
12+
abstract class MultipleConstructorLists()() { def ap(a: Int): Int }
1313

14-
class MultipleMethodLists()() { def ap(a: Int)(): Int }
14+
abstract class MultipleMethodLists()() { def ap(a: Int)(): Int }
1515

16-
class ImplicitConstructorParam()(implicit a: String) { def ap(a: Int): Int }
16+
abstract class ImplicitConstructorParam()(implicit a: String) { def ap(a: Int): Int }
1717

18-
class ImplicitMethodParam() { def ap(a: Int)(implicit b: String): Int }
18+
abstract class ImplicitMethodParam() { def ap(a: Int)(implicit b: String): Int }
1919

20-
class PolyClass[T] { def ap(a: T): T }
20+
abstract class PolyClass[T] { def ap(a: T): T }
2121

22-
class PolyMethod { def ap[T](a: T): T }
22+
abstract class PolyMethod { def ap[T](a: T): T }
2323

24-
class OneAbstract { def ap(a: Any): Any }
25-
class DerivedOneAbstract extends OneAbstract
24+
abstract class OneAbstract { def ap(a: Int): Any }
25+
abstract class DerivedOneAbstract extends OneAbstract
2626

2727
object Test {
2828
implicit val s: String = ""

test/files/pos/sammy_exist.scala

+3-10
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,9 @@ class S[T](x: T) { def map[R](f: Fun[_ >: T, _ <: R]): R = f(x) }
99

1010
class Bla { def foo: Bla = this }
1111

12+
// NOTE: inferred types show unmoored skolems, should pack them to display properly as bounded wildcards
1213
object T {
13-
val aBlaSAM = (new S(new Bla)).map(_.foo) // : Bla should be inferred, when running under -Xexperimental [TODO]
14+
val aBlaSAM = (new S(new Bla)).map(_.foo)
1415
val fun: Fun[Bla, Bla] = (x: Bla) => x
15-
val aBlaSAMX = (new S(new Bla)).map(fun) // : Bla should be inferred, when running under -Xexperimental [TODO]
16+
val aBlaSAMX = (new S(new Bla)).map(fun)
1617
}
17-
//
18-
// // or, maybe by variance-cast?
19-
// import annotation.unchecked.{uncheckedVariance => uv}
20-
// type SFun[-A, +B] = Fun[_ >: A, _ <: B @uv]
21-
//
22-
// def jf[T, R](f: T => R): SFun[T, R] = (x: T) => f(x): R
23-
//
24-
// val aBlaSAM = (new S(new Bla)).map(jf(_.foo)) // : Bla should be inferred [type checks, but existential inferred]

test/files/pos/sammy_override.flags

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xexperimental

test/files/pos/sammy_override.scala

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
trait IntConsumer {
2+
def consume(x: Int): Unit
3+
}
4+
5+
object Test {
6+
def anyConsumer(x: Any): Unit = ???
7+
val f: IntConsumer = anyConsumer
8+
}

0 commit comments

Comments
 (0)