Skip to content

Commit

Permalink
Merge pull request #3988 from dotty-staging/fix-closure-unit
Browse files Browse the repository at this point in the history
Fix #3984: SAM closures returning Unit may need adaptation
  • Loading branch information
smarter authored Feb 13, 2018
2 parents afab822 + fdafdc1 commit 726dcc0
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 26 deletions.
39 changes: 25 additions & 14 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -984,28 +984,39 @@ class Definitions {
isNonDepFunctionType(tp.dropDependentRefinement)

// Specialized type parameters defined for scala.Function{0,1,2}.
private lazy val Function1SpecializedParams: collection.Set[Type] =
lazy val Function1SpecializedParamTypes: collection.Set[TypeRef] =
Set(IntType, LongType, FloatType, DoubleType)
private lazy val Function2SpecializedParams: collection.Set[Type] =
lazy val Function2SpecializedParamTypes: collection.Set[TypeRef] =
Set(IntType, LongType, DoubleType)
private lazy val Function0SpecializedReturns: collection.Set[Type] =
ScalaNumericValueTypeList.toSet[Type] + UnitType + BooleanType
private lazy val Function1SpecializedReturns: collection.Set[Type] =
lazy val Function0SpecializedReturnTypes: collection.Set[TypeRef] =
ScalaNumericValueTypeList.toSet + UnitType + BooleanType
lazy val Function1SpecializedReturnTypes: collection.Set[TypeRef] =
Set(UnitType, BooleanType, IntType, FloatType, LongType, DoubleType)
private lazy val Function2SpecializedReturns: collection.Set[Type] =
Function1SpecializedReturns
lazy val Function2SpecializedReturnTypes: collection.Set[TypeRef] =
Function1SpecializedReturnTypes

lazy val Function1SpecializedParamClasses =
new PerRun[collection.Set[Symbol]](implicit ctx => Function1SpecializedParamTypes.map(_.symbol))
lazy val Function2SpecializedParamClasses =
new PerRun[collection.Set[Symbol]](implicit ctx => Function2SpecializedParamTypes.map(_.symbol))
lazy val Function0SpecializedReturnClasses =
new PerRun[collection.Set[Symbol]](implicit ctx => Function0SpecializedReturnTypes.map(_.symbol))
lazy val Function1SpecializedReturnClasses =
new PerRun[collection.Set[Symbol]](implicit ctx => Function1SpecializedReturnTypes.map(_.symbol))
lazy val Function2SpecializedReturnClasses =
new PerRun[collection.Set[Symbol]](implicit ctx => Function2SpecializedReturnTypes.map(_.symbol))

def isSpecializableFunction(cls: ClassSymbol, paramTypes: List[Type], retType: Type)(implicit ctx: Context) =
isFunctionClass(cls) && (paramTypes match {
paramTypes.length <= 2 && cls.derivesFrom(FunctionClass(paramTypes.length)) && (paramTypes match {
case Nil =>
Function0SpecializedReturns.contains(retType)
Function0SpecializedReturnClasses().contains(retType.typeSymbol)
case List(paramType0) =>
Function1SpecializedParams.contains(paramType0) &&
Function1SpecializedReturns.contains(retType)
Function1SpecializedParamClasses().contains(paramType0.typeSymbol) &&
Function1SpecializedReturnClasses().contains(retType.typeSymbol)
case List(paramType0, paramType1) =>
Function2SpecializedParams.contains(paramType0) &&
Function2SpecializedParams.contains(paramType1) &&
Function2SpecializedReturns.contains(retType)
Function2SpecializedParamClasses().contains(paramType0.typeSymbol) &&
Function2SpecializedParamClasses().contains(paramType1.typeSymbol) &&
Function2SpecializedReturnClasses().contains(retType.typeSymbol)
case _ =>
false
})
Expand Down
36 changes: 24 additions & 12 deletions compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -620,25 +620,37 @@ object Erasure {
// def $anonfun1(x: Object): Object = $anonfun(BoxesRunTime.unboxToInt(x))
// val f: Function1 = closure($anonfun1)
//
// In general, a bridge is needed when, after Erasure:
// - one of the parameter type of the closure method is a non-reference type,
// and the corresponding type in the SAM is a reference type
// - or the result type of the closure method is an erased value type
// and the result type in the SAM isn't
// However, the following exception exists: If the SAM is replaced by
// JFunction*mc* in [[FunctionalInterfaces]], no bridge is needed: the
// SAM contains default methods to handle adaptation
// In general a bridge is needed when, after Erasure, one of the
// parameter type or the result type of the closure method has a
// different type, and we cannot rely on auto-adaptation.
//
// Auto-adaptation works in the following cases:
// - If the SAM is replaced by JFunction*mc* in
// [[FunctionalInterfaces]], no bridge is needed: the SAM contains
// default methods to handle adaptation.
// - If a result type of the closure method is a primitive value type
// different from Unit, we can rely on the auto-adaptation done by
// LMF (because it only needs to box, not unbox, so no special
// handling of null is required).
// - If the SAM is replaced by JProcedure* in
// [[DottyBackendInterface]] (this only happens when no explicit SAM
// type is given), no bridge is needed to box a Unit result type:
// the SAM contains a default method to handle that.
//
// See test cases lambda-*.scala and t8017/ for concrete examples.

def isReferenceType(tp: Type) = !tp.isPrimitiveValueType && !tp.isErasedValueType

if (!defn.isSpecializableFunction(implClosure.tpe.widen.classSymbol.asClass, implParamTypes, implResultType)) {
def autoAdaptedParam(tp: Type) = !tp.isErasedValueType && !tp.isPrimitiveValueType
val explicitSAMType = implClosure.tpt.tpe.exists
def autoAdaptedResult(tp: Type) = !tp.isErasedValueType &&
(!explicitSAMType || tp.typeSymbol != defn.UnitClass)
def sameSymbol(tp1: Type, tp2: Type) = tp1.typeSymbol == tp2.typeSymbol

val paramAdaptationNeeded =
(implParamTypes, samParamTypes).zipped.exists((implType, samType) =>
!isReferenceType(implType) && isReferenceType(samType))
!sameSymbol(implType, samType) && !autoAdaptedParam(implType))
val resultAdaptationNeeded =
implResultType.isErasedValueType && !samResultType.isErasedValueType
!sameSymbol(implResultType, samResultType) && !autoAdaptedResult(implResultType)

if (paramAdaptationNeeded || resultAdaptationNeeded) {
val bridgeType =
Expand Down
6 changes: 6 additions & 0 deletions tests/run/lambda-unit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ trait SAMUnit {
def foo(a: Object): Unit
}

trait GenericSAM[R] {
def foo(a: Object): R
}

object Test {
val fun: Object => Unit = a => assert(a == "")
val sam: SAMUnit = a => assert(a == "")
val genericSam: GenericSAM[Unit] = a => assert(a == "")

def main(args: Array[String]): Unit = {
fun("")
(fun: Object => Any)("")
sam.foo("")
genericSam.foo("")
}
}

0 comments on commit 726dcc0

Please sign in to comment.