diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 6b68ea100c12..709872c1d7a0 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -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 { @@ -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, @@ -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 diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index fa185896ecfc..a95181bada7d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -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: // @@ -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) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 11a77ec449ad..83f8b88d86df 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 754ccddefed2..422048565d94 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 => @@ -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) @@ -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 diff --git a/tests/neg/i2732.scala b/tests/neg/i2732.scala new file mode 100644 index 000000000000..35fd064b2f18 --- /dev/null +++ b/tests/neg/i2732.scala @@ -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 +} diff --git a/tests/pos/i2732.scala b/tests/pos/i2732.scala new file mode 100644 index 000000000000..a37634485409 --- /dev/null +++ b/tests/pos/i2732.scala @@ -0,0 +1,5 @@ +object Test { + val f: java.util.function.Function[_ >: String, _ <: Int] = str => 1 + + val i: Int = f("") +}