Skip to content

Commit 5a7875f

Browse files
committed
Merge pull request scala#4101 from adriaanm/sam-ex
[sammy] eta-expansion, overloading, existentials
2 parents 02c0852 + cbca494 commit 5a7875f

20 files changed

+183
-29
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,11 +295,17 @@ trait Infer extends Checkable {
295295
&& !isByNameParamType(tp)
296296
&& isCompatible(tp, dropByName(pt))
297297
)
298+
def isCompatibleSam(tp: Type, pt: Type): Boolean = {
299+
val samFun = typer.samToFunctionType(pt)
300+
(samFun ne NoType) && isCompatible(tp, samFun)
301+
}
302+
298303
val tp1 = normalize(tp)
299304

300305
( (tp1 weak_<:< pt)
301306
|| isCoercible(tp1, pt)
302307
|| isCompatibleByName(tp, pt)
308+
|| isCompatibleSam(tp, pt)
303309
)
304310
}
305311
def isCompatibleArgs(tps: List[Type], pts: List[Type]) = (tps corresponds pts)(isCompatible)

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

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,26 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
741741
case _ =>
742742
}
743743

744+
/**
745+
* Convert a SAM type to the corresponding FunctionType,
746+
* extrapolating BoundedWildcardTypes in the process
747+
* (no type precision is lost by the extrapolation,
748+
* but this facilitates dealing with the types arising from Java's use-site variance).
749+
*/
750+
def samToFunctionType(tp: Type, sam: Symbol = NoSymbol): Type = {
751+
val samSym = sam orElse samOf(tp)
752+
753+
def correspondingFunctionSymbol = {
754+
val numVparams = samSym.info.params.length
755+
if (numVparams > definitions.MaxFunctionArity) NoSymbol
756+
else FunctionClass(numVparams)
757+
}
758+
759+
if (samSym.exists && samSym.owner != correspondingFunctionSymbol) // don't treat Functions as SAMs
760+
wildcardExtrapolation(normalize(tp memberInfo samSym))
761+
else NoType
762+
}
763+
744764
/** Perform the following adaptations of expression, pattern or type `tree` wrt to
745765
* given mode `mode` and given prototype `pt`:
746766
* (-1) For expressions with annotated types, let AnnotationCheckers decide what to do
@@ -824,7 +844,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
824844
case Block(_, tree1) => tree1.symbol
825845
case _ => tree.symbol
826846
}
827-
if (!meth.isConstructor && isFunctionType(pt)) { // (4.2)
847+
if (!meth.isConstructor && (isFunctionType(pt) || samOf(pt).exists)) { // (4.2)
828848
debuglog(s"eta-expanding $tree: ${tree.tpe} to $pt")
829849
checkParamsConvertible(tree, tree.tpe)
830850
val tree0 = etaExpand(context.unit, tree, this)
@@ -2681,7 +2701,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
26812701
* `{
26822702
* def apply$body(p1: T1, ..., pN: TN): T = body
26832703
* new S {
2684-
* def apply(p1: T1, ..., pN: TN): T = apply$body(p1,..., pN)
2704+
* def apply(p1: T1', ..., pN: TN'): T' = apply$body(p1,..., pN)
26852705
* }
26862706
* }`
26872707
*
@@ -2691,6 +2711,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
26912711
*
26922712
* The `apply` method is identified by the argument `sam`; `S` corresponds to the argument `samClassTp`,
26932713
* and `resPt` is derived from `samClassTp` -- it may be fully defined, or not...
2714+
* If it is not fully defined, we derive `samClassTpFullyDefined` by inferring any unknown type parameters.
2715+
*
2716+
* The types T1' ... TN' and T' are derived from the method signature of the sam method,
2717+
* as seen from the fully defined `samClassTpFullyDefined`.
26942718
*
26952719
* The function's body is put in a method outside of the class definition to enforce scoping.
26962720
* S's members should not be in scope in `body`.
@@ -2702,6 +2726,22 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
27022726
* However T must be fully defined before we type the instantiation, as it'll end up as a parent type,
27032727
* which must be fully defined. Would be nice to have some kind of mechanism to insert type vars in a block of code,
27042728
* and have the instantiation of the first occurrence propagate to the rest of the block.
2729+
*
2730+
* TODO: by-name params
2731+
* scala> trait LazySink { def accept(a: => Any): Unit }
2732+
* defined trait LazySink
2733+
*
2734+
* scala> val f: LazySink = (a) => (a, a)
2735+
* f: LazySink = $anonfun$1@1fb26910
2736+
*
2737+
* scala> f(println("!"))
2738+
* <console>:10: error: LazySink does not take parameters
2739+
* f(println("!"))
2740+
* ^
2741+
*
2742+
* scala> f.accept(println("!"))
2743+
* !
2744+
* !
27052745
*/
27062746
def synthesizeSAMFunction(sam: Symbol, fun: Function, resPt: Type, samClassTp: Type, mode: Mode): Tree = {
27072747
// assert(fun.vparams forall (vp => isFullyDefined(vp.tpt.tpe))) -- by construction, as we take them from sam's info
@@ -2782,14 +2822,21 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
27822822
samClassTp
27832823
}
27842824

2785-
// `final override def ${sam.name}($p1: $T1, ..., $pN: $TN): $resPt = ${sam.name}\$body'($p1, ..., $pN)`
2825+
// what's the signature of the method that we should actually be overriding?
2826+
val samMethTp = samClassTpFullyDefined memberInfo sam
2827+
// Before the mutation, `tp <:< vpar.tpt.tpe` should hold.
2828+
// TODO: error message when this is not the case, as the expansion won't type check
2829+
// - Ti' <:< Ti and T <: T' must hold for the samDef body to type check
2830+
val funArgTps = foreach2(samMethTp.paramTypes, fun.vparams)((tp, vpar) => vpar.tpt setType tp)
2831+
2832+
// `final override def ${sam.name}($p1: $T1', ..., $pN: $TN'): ${samMethTp.finalResultType} = ${sam.name}\$body'($p1, ..., $pN)`
27862833
val samDef =
27872834
DefDef(Modifiers(FINAL | OVERRIDE | SYNTHETIC),
27882835
sam.name.toTermName,
27892836
Nil,
27902837
List(fun.vparams),
2791-
TypeTree(samBodyDef.tpt.tpe) setPos sampos.focus,
2792-
Apply(Ident(bodyName), fun.vparams map (p => Ident(p.name)))
2838+
TypeTree(samMethTp.finalResultType) setPos sampos.focus,
2839+
Apply(Ident(bodyName), fun.vparams map gen.paramToArg)
27932840
)
27942841

27952842
val serializableParentAddendum =
@@ -2819,6 +2866,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
28192866
)
28202867
}
28212868

2869+
// TODO: improve error reporting -- when we're in silent mode (from `silent(_.doTypedApply(tree, fun, args, mode, pt)) orElse onError`)
2870+
// the errors in the function don't get out...
2871+
if (block exists (_.isErroneous))
2872+
context.error(fun.pos, s"Could not derive subclass of $samClassTp\n (with SAM `def $sam$samMethTp`)\n based on: $fun.")
2873+
28222874
classDef.symbol addAnnotation SerialVersionUIDAnnotation
28232875
block
28242876
}
@@ -2839,7 +2891,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
28392891
* as `(a => a): Int => Int` should not (yet) get the sam treatment.
28402892
*/
28412893
val sam =
2842-
if (!settings.Xexperimental || pt.typeSymbol == FunctionSymbol) NoSymbol
2894+
if (pt.typeSymbol == FunctionSymbol) NoSymbol
28432895
else samOf(pt)
28442896

28452897
/* The SAM case comes first so that this works:
@@ -2849,15 +2901,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
28492901
* Note that the arity of the sam must correspond to the arity of the function.
28502902
*/
28512903
val samViable = sam.exists && sameLength(sam.info.params, fun.vparams)
2904+
val ptNorm = if (samViable) samToFunctionType(pt, sam) else pt
28522905
val (argpts, respt) =
2853-
if (samViable) {
2854-
val samInfo = pt memberInfo sam
2855-
(samInfo.paramTypes, samInfo.resultType)
2856-
} else {
2857-
pt baseType FunctionSymbol match {
2858-
case TypeRef(_, FunctionSymbol, args :+ res) => (args, res)
2859-
case _ => (fun.vparams map (_ => if (pt == ErrorType) ErrorType else NoType), WildcardType)
2860-
}
2906+
ptNorm baseType FunctionSymbol match {
2907+
case TypeRef(_, FunctionSymbol, args :+ res) => (args, res)
2908+
case _ => (fun.vparams map (_ => if (pt == ErrorType) ErrorType else NoType), WildcardType)
28612909
}
28622910

28632911
if (!FunctionSymbol.exists)

src/reflect/scala/reflect/internal/Definitions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,7 @@ trait Definitions extends api.StandardDefinitions {
790790
* The class defining the method is a supertype of `tp` that
791791
* has a public no-arg primary constructor.
792792
*/
793-
def samOf(tp: Type): Symbol = {
793+
def samOf(tp: Type): Symbol = if (!settings.Xexperimental) NoSymbol else {
794794
// if tp has a constructor, it must be public and must not take any arguments
795795
// (not even an implicit argument list -- to keep it simple for now)
796796
val tpSym = tp.typeSymbol

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,22 @@ private[internal] trait TypeMaps {
422422
}
423423
}
424424

425+
/**
426+
* Get rid of BoundedWildcardType where variance allows us to do so.
427+
* Invariant: `wildcardExtrapolation(tp) =:= tp`
428+
*
429+
* For example, the MethodType given by `def bla(x: (_ >: String)): (_ <: Int)`
430+
* is both a subtype and a supertype of `def bla(x: String): Int`.
431+
*/
432+
object wildcardExtrapolation extends TypeMap(trackVariance = true) {
433+
def apply(tp: Type): Type =
434+
tp match {
435+
case BoundedWildcardType(TypeBounds(lo, AnyTpe)) if variance.isContravariant => lo
436+
case BoundedWildcardType(TypeBounds(NothingTpe, hi)) if variance.isCovariant => hi
437+
case tp => mapOver(tp)
438+
}
439+
}
440+
425441
/** Might the given symbol be important when calculating the prefix
426442
* of a type? When tp.asSeenFrom(pre, clazz) is called on `tp`,
427443
* the result will be `tp` unchanged if `pre` is trivial and `clazz`

src/reflect/scala/reflect/runtime/JavaUniverseForce.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
170170
this.dropSingletonType
171171
this.abstractTypesToBounds
172172
this.dropIllegalStarTypes
173+
this.wildcardExtrapolation
173174
this.IsDependentCollector
174175
this.ApproximateDependentMap
175176
this.wildcardToTypeVarMap
Lines changed: 6 additions & 0 deletions
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
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xexperimental
Lines changed: 6 additions & 0 deletions
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

Lines changed: 14 additions & 14 deletions
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.flags

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xexperimental

0 commit comments

Comments
 (0)