Skip to content

Fix #3984: SAM closures returning Unit may need adaptation #3988

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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("")
}
}