From 83c3de74c588b43b9f54fc755d113d0937d623ae Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Tue, 20 Mar 2018 22:22:15 +0100 Subject: [PATCH 1/3] Make the SAMType extractor return a type, not a denot --- compiler/src/dotty/tools/dotc/core/Types.scala | 14 ++++++++------ .../src/dotty/tools/dotc/transform/Erasure.scala | 6 +++--- .../src/dotty/tools/dotc/typer/Applications.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Typer.scala | 15 +++++++-------- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 6b68ea100c12..8b704325b430 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 { @@ -3817,14 +3817,16 @@ object Types { 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 => + Some(mt) + case _ => + None } else if (tp isRef defn.PartialFunctionClass) // To maintain compatibility with 2.x, we treat PartialFunction specially, @@ -3833,7 +3835,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 From d2fcfab560101d204184ea35e82a6ed9ae928583 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 21 Mar 2018 16:25:54 +0100 Subject: [PATCH 2/3] Relax the SAMType#isInstantiable check Narrowing the type is not necessary to check for instantiability, and it prevents valid SAM types with wildcards from being used. This commit does not contain any positive testcase because SAM types with wildcards need another fix present in the next commit to work. --- compiler/src/dotty/tools/dotc/core/Types.scala | 5 ++--- tests/neg/i2732.scala | 13 +++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 tests/neg/i2732.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8b704325b430..a89f06415996 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3811,9 +3811,8 @@ 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 } 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 +} From 6127c5b4b4afac28a182e5fbeef4fed0b8580823 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 21 Mar 2018 16:31:35 +0100 Subject: [PATCH 3/3] Fix #2732: Allow wildcards in SAM types --- .../src/dotty/tools/dotc/core/Types.scala | 36 ++++++++++++++++++- tests/pos/i2732.scala | 5 +++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i2732.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index a89f06415996..709872c1d7a0 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3823,7 +3823,41 @@ object Types { if (absMems.size == 1) absMems.head.info match { case mt: MethodType if !mt.isParamDependent => - Some(mt) + 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 } 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("") +}