Skip to content

Commit

Permalink
Merge pull request #4152 from dotty-staging/fix-2732-take-2
Browse files Browse the repository at this point in the history
Fix #2732: Allow wildcards in SAM types (take 2)
  • Loading branch information
smarter authored Mar 23, 2018
2 parents c89f8fa + 6127c5b commit e1a18da
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 21 deletions.
53 changes: 44 additions & 9 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3783,8 +3783,8 @@ object Types {
* and PolyType not allowed!)
* - can be instantiated without arguments or with just () as argument.
*
* The pattern `SAMType(denot)` matches a SAM type, where `denot` is the
* denotation of the single abstract method as a member of the type.
* The pattern `SAMType(sam)` matches a SAM type, where `sam` is the
* type of the single abstract method.
*/
object SAMType {
def zeroParamClass(tp: Type)(implicit ctx: Context): Type = tp match {
Expand All @@ -3811,20 +3811,55 @@ object Types {
}
def isInstantiatable(tp: Type)(implicit ctx: Context): Boolean = zeroParamClass(tp) match {
case cinfo: ClassInfo =>
val tref = tp.narrow
val selfType = cinfo.selfType.asSeenFrom(tref, cinfo.cls)
tref <:< selfType
val selfType = cinfo.selfType.asSeenFrom(tp, cinfo.cls)
tp <:< selfType
case _ =>
false
}
def unapply(tp: Type)(implicit ctx: Context): Option[SingleDenotation] =
def unapply(tp: Type)(implicit ctx: Context): Option[MethodType] =
if (isInstantiatable(tp)) {
val absMems = tp.abstractTermMembers
// println(s"absMems: ${absMems map (_.show) mkString ", "}")
if (absMems.size == 1)
absMems.head.info match {
case mt: MethodType if !mt.isParamDependent => Some(absMems.head)
case _ => None
case mt: MethodType if !mt.isParamDependent =>
val cls = tp.classSymbol

// Given a SAM type such as:
//
// import java.util.function.Function
// Function[_ >: String, _ <: Int]
//
// the single abstract method will have type:
//
// (x: Function[_ >: String, _ <: Int]#T): Function[_ >: String, _ <: Int]#R
//
// which is not implementable outside of the scope of Function.
//
// To avoid this kind of issue, we approximate references to
// parameters of the SAM type by their bounds, this way in the
// above example we get:
//
// (x: String): Int
val approxParams = new ApproximatingTypeMap {
def apply(tp: Type): Type = tp match {
case tp: TypeRef if tp.symbol.is(ClassTypeParam) && tp.symbol.owner == cls =>
tp.info match {
case TypeAlias(alias) =>
mapOver(alias)
case TypeBounds(lo, hi) =>
range(atVariance(-variance)(apply(lo)), apply(hi))
case _ =>
range(defn.NothingType, defn.AnyType) // should happen only in error cases
}
case _ =>
mapOver(tp)
}
}
val approx = approxParams(mt).asInstanceOf[MethodType]
Some(approx)
case _ =>
None
}
else if (tp isRef defn.PartialFunctionClass)
// To maintain compatibility with 2.x, we treat PartialFunction specially,
Expand All @@ -3833,7 +3868,7 @@ object Types {
// def isDefinedAt(x: T) = true
// and overwrite that method whenever the function body is a sequence of
// case clauses.
absMems.find(_.symbol.name == nme.apply)
absMems.find(_.symbol.name == nme.apply).map(_.info.asInstanceOf[MethodType])
else None
}
else None
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -577,9 +577,9 @@ object Erasure {
val implType = meth.tpe.widen.asInstanceOf[MethodType]

val implParamTypes = implType.paramInfos
val List(samParamTypes) = sam.info.paramInfoss
val List(samParamTypes) = sam.paramInfoss
val implResultType = implType.resultType
val samResultType = sam.info.resultType
val samResultType = sam.resultType

// The following code:
//
Expand Down Expand Up @@ -646,7 +646,7 @@ object Erasure {
if (paramAdaptationNeeded || resultAdaptationNeeded) {
val bridgeType =
if (paramAdaptationNeeded) {
if (resultAdaptationNeeded) sam.info
if (resultAdaptationNeeded) sam
else implType.derivedLambdaType(paramInfos = samParamTypes)
} else implType.derivedLambdaType(resType = samResultType)
val bridge = ctx.newSymbol(ctx.owner, AdaptedClosureName(meth.symbol.name.asTermName), Flags.Synthetic | Flags.Method, bridgeType)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
false
case argtpe =>
def SAMargOK = formal match {
case SAMType(meth) => argtpe <:< meth.info.toFunctionType()
case SAMType(sam) => argtpe <:< sam.toFunctionType()
case _ => false
}
isCompatible(argtpe, formal) || ctx.mode.is(Mode.ImplicitsEnabled) && SAMargOK
Expand Down
15 changes: 7 additions & 8 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -740,11 +740,10 @@ class Typer extends Namer
// this can type the greatest set of admissible closures.
val funType = pt.dealias
(funType.argTypesLo.init, typeTree(funType.argTypesHi.last))
case SAMType(meth) =>
val mt @ MethodTpe(_, formals, restpe) = meth.info
case SAMType(sam @ MethodTpe(_, formals, restpe)) =>
(formals,
if (mt.isResultDependent)
untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef)))
if (sam.isResultDependent)
untpd.DependentTypeTree(syms => restpe.substParams(sam, syms.map(_.termRef)))
else
typeTree(restpe))
case tp: TypeParamRef =>
Expand Down Expand Up @@ -936,8 +935,8 @@ class Typer extends Namer
meth1.tpe.widen match {
case mt: MethodType =>
pt match {
case SAMType(meth)
if !defn.isFunctionType(pt) && mt <:< meth.info =>
case SAMType(sam)
if !defn.isFunctionType(pt) && mt <:< sam =>
if (!isFullyDefined(pt, ForceDegree.all))
ctx.error(ex"result type of closure is an underspecified SAM type $pt", tree.pos)
TypeTree(pt)
Expand Down Expand Up @@ -2406,8 +2405,8 @@ class Typer extends Namer
case closure(Nil, id @ Ident(nme.ANON_FUN), _)
if defn.isFunctionType(wtp) && !defn.isFunctionType(pt) =>
pt match {
case SAMType(meth)
if wtp <:< meth.info.toFunctionType() =>
case SAMType(sam)
if wtp <:< sam.toFunctionType() =>
// was ... && isFullyDefined(pt, ForceDegree.noBottom)
// but this prevents case blocks from implementing polymorphic partial functions,
// since we do not know the result parameter a priori. Have to wait until the
Expand Down
13 changes: 13 additions & 0 deletions tests/neg/i2732.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class B {
def f = 1
}

trait A { self: B =>
def g1(x: Int): Int
def g2 = g1(f)
}

object Test {
// A should not be a valid SAM type because it's not instantiable
val x: A = (y: Int) => y + y // error
}
5 changes: 5 additions & 0 deletions tests/pos/i2732.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object Test {
val f: java.util.function.Function[_ >: String, _ <: Int] = str => 1

val i: Int = f("")
}

0 comments on commit e1a18da

Please sign in to comment.