Skip to content

Commit e1a18da

Browse files
authored
Merge pull request #4152 from dotty-staging/fix-2732-take-2
Fix #2732: Allow wildcards in SAM types (take 2)
2 parents c89f8fa + 6127c5b commit e1a18da

File tree

6 files changed

+73
-21
lines changed

6 files changed

+73
-21
lines changed

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3783,8 +3783,8 @@ object Types {
37833783
* and PolyType not allowed!)
37843784
* - can be instantiated without arguments or with just () as argument.
37853785
*
3786-
* The pattern `SAMType(denot)` matches a SAM type, where `denot` is the
3787-
* denotation of the single abstract method as a member of the type.
3786+
* The pattern `SAMType(sam)` matches a SAM type, where `sam` is the
3787+
* type of the single abstract method.
37883788
*/
37893789
object SAMType {
37903790
def zeroParamClass(tp: Type)(implicit ctx: Context): Type = tp match {
@@ -3811,20 +3811,55 @@ object Types {
38113811
}
38123812
def isInstantiatable(tp: Type)(implicit ctx: Context): Boolean = zeroParamClass(tp) match {
38133813
case cinfo: ClassInfo =>
3814-
val tref = tp.narrow
3815-
val selfType = cinfo.selfType.asSeenFrom(tref, cinfo.cls)
3816-
tref <:< selfType
3814+
val selfType = cinfo.selfType.asSeenFrom(tp, cinfo.cls)
3815+
tp <:< selfType
38173816
case _ =>
38183817
false
38193818
}
3820-
def unapply(tp: Type)(implicit ctx: Context): Option[SingleDenotation] =
3819+
def unapply(tp: Type)(implicit ctx: Context): Option[MethodType] =
38213820
if (isInstantiatable(tp)) {
38223821
val absMems = tp.abstractTermMembers
38233822
// println(s"absMems: ${absMems map (_.show) mkString ", "}")
38243823
if (absMems.size == 1)
38253824
absMems.head.info match {
3826-
case mt: MethodType if !mt.isParamDependent => Some(absMems.head)
3827-
case _ => None
3825+
case mt: MethodType if !mt.isParamDependent =>
3826+
val cls = tp.classSymbol
3827+
3828+
// Given a SAM type such as:
3829+
//
3830+
// import java.util.function.Function
3831+
// Function[_ >: String, _ <: Int]
3832+
//
3833+
// the single abstract method will have type:
3834+
//
3835+
// (x: Function[_ >: String, _ <: Int]#T): Function[_ >: String, _ <: Int]#R
3836+
//
3837+
// which is not implementable outside of the scope of Function.
3838+
//
3839+
// To avoid this kind of issue, we approximate references to
3840+
// parameters of the SAM type by their bounds, this way in the
3841+
// above example we get:
3842+
//
3843+
// (x: String): Int
3844+
val approxParams = new ApproximatingTypeMap {
3845+
def apply(tp: Type): Type = tp match {
3846+
case tp: TypeRef if tp.symbol.is(ClassTypeParam) && tp.symbol.owner == cls =>
3847+
tp.info match {
3848+
case TypeAlias(alias) =>
3849+
mapOver(alias)
3850+
case TypeBounds(lo, hi) =>
3851+
range(atVariance(-variance)(apply(lo)), apply(hi))
3852+
case _ =>
3853+
range(defn.NothingType, defn.AnyType) // should happen only in error cases
3854+
}
3855+
case _ =>
3856+
mapOver(tp)
3857+
}
3858+
}
3859+
val approx = approxParams(mt).asInstanceOf[MethodType]
3860+
Some(approx)
3861+
case _ =>
3862+
None
38283863
}
38293864
else if (tp isRef defn.PartialFunctionClass)
38303865
// To maintain compatibility with 2.x, we treat PartialFunction specially,
@@ -3833,7 +3868,7 @@ object Types {
38333868
// def isDefinedAt(x: T) = true
38343869
// and overwrite that method whenever the function body is a sequence of
38353870
// case clauses.
3836-
absMems.find(_.symbol.name == nme.apply)
3871+
absMems.find(_.symbol.name == nme.apply).map(_.info.asInstanceOf[MethodType])
38373872
else None
38383873
}
38393874
else None

compiler/src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -577,9 +577,9 @@ object Erasure {
577577
val implType = meth.tpe.widen.asInstanceOf[MethodType]
578578

579579
val implParamTypes = implType.paramInfos
580-
val List(samParamTypes) = sam.info.paramInfoss
580+
val List(samParamTypes) = sam.paramInfoss
581581
val implResultType = implType.resultType
582-
val samResultType = sam.info.resultType
582+
val samResultType = sam.resultType
583583

584584
// The following code:
585585
//
@@ -646,7 +646,7 @@ object Erasure {
646646
if (paramAdaptationNeeded || resultAdaptationNeeded) {
647647
val bridgeType =
648648
if (paramAdaptationNeeded) {
649-
if (resultAdaptationNeeded) sam.info
649+
if (resultAdaptationNeeded) sam
650650
else implType.derivedLambdaType(paramInfos = samParamTypes)
651651
} else implType.derivedLambdaType(resType = samResultType)
652652
val bridge = ctx.newSymbol(ctx.owner, AdaptedClosureName(meth.symbol.name.asTermName), Flags.Synthetic | Flags.Method, bridgeType)

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
482482
false
483483
case argtpe =>
484484
def SAMargOK = formal match {
485-
case SAMType(meth) => argtpe <:< meth.info.toFunctionType()
485+
case SAMType(sam) => argtpe <:< sam.toFunctionType()
486486
case _ => false
487487
}
488488
isCompatible(argtpe, formal) || ctx.mode.is(Mode.ImplicitsEnabled) && SAMargOK

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -740,11 +740,10 @@ class Typer extends Namer
740740
// this can type the greatest set of admissible closures.
741741
val funType = pt.dealias
742742
(funType.argTypesLo.init, typeTree(funType.argTypesHi.last))
743-
case SAMType(meth) =>
744-
val mt @ MethodTpe(_, formals, restpe) = meth.info
743+
case SAMType(sam @ MethodTpe(_, formals, restpe)) =>
745744
(formals,
746-
if (mt.isResultDependent)
747-
untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef)))
745+
if (sam.isResultDependent)
746+
untpd.DependentTypeTree(syms => restpe.substParams(sam, syms.map(_.termRef)))
748747
else
749748
typeTree(restpe))
750749
case tp: TypeParamRef =>
@@ -936,8 +935,8 @@ class Typer extends Namer
936935
meth1.tpe.widen match {
937936
case mt: MethodType =>
938937
pt match {
939-
case SAMType(meth)
940-
if !defn.isFunctionType(pt) && mt <:< meth.info =>
938+
case SAMType(sam)
939+
if !defn.isFunctionType(pt) && mt <:< sam =>
941940
if (!isFullyDefined(pt, ForceDegree.all))
942941
ctx.error(ex"result type of closure is an underspecified SAM type $pt", tree.pos)
943942
TypeTree(pt)
@@ -2406,8 +2405,8 @@ class Typer extends Namer
24062405
case closure(Nil, id @ Ident(nme.ANON_FUN), _)
24072406
if defn.isFunctionType(wtp) && !defn.isFunctionType(pt) =>
24082407
pt match {
2409-
case SAMType(meth)
2410-
if wtp <:< meth.info.toFunctionType() =>
2408+
case SAMType(sam)
2409+
if wtp <:< sam.toFunctionType() =>
24112410
// was ... && isFullyDefined(pt, ForceDegree.noBottom)
24122411
// but this prevents case blocks from implementing polymorphic partial functions,
24132412
// since we do not know the result parameter a priori. Have to wait until the

tests/neg/i2732.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class B {
2+
def f = 1
3+
}
4+
5+
trait A { self: B =>
6+
def g1(x: Int): Int
7+
def g2 = g1(f)
8+
}
9+
10+
object Test {
11+
// A should not be a valid SAM type because it's not instantiable
12+
val x: A = (y: Int) => y + y // error
13+
}

tests/pos/i2732.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object Test {
2+
val f: java.util.function.Function[_ >: String, _ <: Int] = str => 1
3+
4+
val i: Int = f("")
5+
}

0 commit comments

Comments
 (0)