summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAdriaan Moors <adriaan.moors@typesafe.com>2016-03-21 21:54:48 -0700
committerAdriaan Moors <adriaan.moors@typesafe.com>2016-03-26 22:55:02 -0700
commit878e20a5243383300d3b4990146d260409bf5dfd (patch)
treed7ed82ebf6d317ae901cc5e2317ac6029970a5da /src
parent391e2843f420bb4686b974b18ac361c9bb49465c (diff)
downloadscala-878e20a5243383300d3b4990146d260409bf5dfd.tar.gz
scala-878e20a5243383300d3b4990146d260409bf5dfd.tar.bz2
scala-878e20a5243383300d3b4990146d260409bf5dfd.zip
Track Function's SAM symbol & target type using an attachment
We cannot use the expected type to track whether a Function node targets a SAM type, as the expected type may be erased (see test for an example). Thus, the type checker attaches a SAMFunction attachment to a Function node when SAM conversion is performed in adapt. Ideally, we'd move to Dotty's Closure AST, but that will need a deprecation cycle. Thanks to Jason for catching my mistake, suggesting the fix and providing the test. Both the sam method symbol and sam target type must be tracked, as their relationship can be complicated (due to inheritance). For example, the sam method could be defined in a superclass (T) of the Function's target type (U). ``` trait T { def foo(a: Any): Any } trait U extends T { def apply = ??? } (((x: Any) => x) : U).foo("") ``` This removes some of the duplication in deriving the sam method from the expected type, but some grossness (see TODO) remains.
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala5
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala20
-rw-r--r--src/compiler/scala/tools/nsc/transform/Erasure.scala6
-rw-r--r--src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala3
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala3
-rw-r--r--src/reflect/scala/reflect/internal/StdAttachments.scala13
-rw-r--r--src/reflect/scala/reflect/runtime/JavaUniverseForce.scala1
7 files changed, 38 insertions, 13 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
index 82aa3c65aa..cff623e2b2 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
@@ -659,7 +659,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
case Apply(fun, args) if app.hasAttachment[delambdafy.LambdaMetaFactoryCapable] =>
val attachment = app.attachments.get[delambdafy.LambdaMetaFactoryCapable].get
genLoadArguments(args, paramTKs(app))
- genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface)
+ genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface, attachment.sam)
generatedType = methodBTypeFromSymbol(fun.symbol).returnType
case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) =>
@@ -1360,7 +1360,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
def genSynchronized(tree: Apply, expectedType: BType): BType
def genLoadTry(tree: Try): BType
- def genInvokeDynamicLambda(lambdaTarget: Symbol, arity: Int, functionalInterface: Symbol) {
+ def genInvokeDynamicLambda(lambdaTarget: Symbol, arity: Int, functionalInterface: Symbol, sam: Symbol) {
val isStaticMethod = lambdaTarget.hasFlag(Flags.STATIC)
def asmType(sym: Symbol) = classBTypeFromSymbol(sym).toASMType
@@ -1375,7 +1375,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val invokedType = asm.Type.getMethodDescriptor(asmType(functionalInterface), (receiver ::: capturedParams).map(sym => typeToBType(sym.info).toASMType): _*)
val constrainedType = new MethodBType(lambdaParams.map(p => typeToBType(p.tpe)), typeToBType(lambdaTarget.tpe.resultType)).toASMType
- val sam = functionalInterface.info.decls.find(_.isDeferred).getOrElse(functionalInterface.info.member(nme.apply))
val samName = sam.name.toString
val samMethodType = methodBTypeFromSymbol(sam).toASMType
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
index 0614b138a7..32ab52203e 100644
--- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
+++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
@@ -29,7 +29,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
/** the following two members override abstract members in Transform */
val phaseName: String = "delambdafy"
- final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol)
+ final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol, sam: Symbol)
/**
* Get the symbol of the target lifted lambda body method from a function. I.e. if
@@ -60,7 +60,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
private[this] lazy val methodReferencesThis: Set[Symbol] =
(new ThisReferringMethodsTraverser).methodReferencesThisIn(unit.body)
- private def mkLambdaMetaFactoryCall(fun: Function, target: Symbol, functionalInterface: Symbol, isSpecialized: Boolean): Tree = {
+ private def mkLambdaMetaFactoryCall(fun: Function, target: Symbol, functionalInterface: Symbol, samUserDefined: Symbol, isSpecialized: Boolean): Tree = {
val pos = fun.pos
val allCapturedArgRefs = {
// find which variables are free in the lambda because those are captures that need to be
@@ -84,8 +84,14 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
// We then apply this symbol to the captures.
val apply = localTyper.typedPos(pos)(Apply(Ident(msym), allCapturedArgRefs))
+ // TODO: this is a bit gross
+ val sam = samUserDefined orElse {
+ if (isSpecialized) functionalInterface.info.decls.find(_.isDeferred).get
+ else functionalInterface.info.member(nme.apply)
+ }
+
// no need for adaptation when the implemented sam is of a specialized built-in function type
- val lambdaTarget = if (isSpecialized) target else createBoxingBridgeMethodIfNeeded(fun, target, functionalInterface)
+ val lambdaTarget = if (isSpecialized) target else createBoxingBridgeMethodIfNeeded(fun, target, functionalInterface, sam)
// The backend needs to know the target of the lambda and the functional interface in order
// to emit the invokedynamic instruction. We pass this information as tree attachment.
@@ -93,7 +99,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
// see https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html
// instantiatedMethodType is derived from lambdaTarget's signature
// samMethodType is derived from samOf(functionalInterface)'s signature
- apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, fun.vparams.length, functionalInterface))
+ apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, fun.vparams.length, functionalInterface, sam))
apply
}
@@ -113,7 +119,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
}
// determine which lambda target to use with java's LMF -- create a new one if scala-specific boxing is required
- def createBoxingBridgeMethodIfNeeded(fun: Function, target: Symbol, functionalInterface: Symbol): Symbol = {
+ def createBoxingBridgeMethodIfNeeded(fun: Function, target: Symbol, functionalInterface: Symbol, sam: Symbol): Symbol = {
val oldClass = fun.symbol.enclClass
val pos = fun.pos
@@ -121,7 +127,6 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
val functionParamTypes = exitingErasure(target.info.paramTypes)
val functionResultType = exitingErasure(target.info.resultType)
- val sam = samOf(functionalInterface.tpe) orElse functionalInterface.info.member(nme.apply)
val samParamTypes = exitingErasure(sam.info.paramTypes)
val samResultType = exitingErasure(sam.info.resultType)
@@ -241,7 +246,8 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
(functionalInterface, isSpecialized)
}
- mkLambdaMetaFactoryCall(originalFunction, target, functionalInterface, isSpecialized)
+ val sam = originalFunction.attachments.get[SAMFunction].map(_.sam).getOrElse(NoSymbol)
+ mkLambdaMetaFactoryCall(originalFunction, target, functionalInterface, sam, isSpecialized)
}
// here's the main entry point of the transform
diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala
index be6316cc57..2df4265573 100644
--- a/src/compiler/scala/tools/nsc/transform/Erasure.scala
+++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala
@@ -721,6 +721,12 @@ abstract class Erasure extends AddInterfaces
if (branch == EmptyTree) branch else adaptToType(branch, tree1.tpe)
tree1 match {
+ case fun: Function =>
+ fun.attachments.get[SAMFunction] match {
+ case Some(SAMFunction(samTp, _)) => fun setType specialScalaErasure(samTp)
+ case _ => fun
+ }
+
case If(cond, thenp, elsep) =>
treeCopy.If(tree1, cond, adaptBranch(thenp), adaptBranch(elsep))
case Match(selector, cases) =>
diff --git a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala
index 596091f75d..afafdedce7 100644
--- a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala
+++ b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala
@@ -118,8 +118,7 @@ trait TypeAdaptingTransformer { self: TreeDSL =>
val needsExtraCast = isPrimitiveValueType(tree.tpe.typeArgs.head) && !isPrimitiveValueType(pt.typeArgs.head)
val tree1 = if (needsExtraCast) gen.mkRuntimeCall(nme.toObjectArray, List(tree)) else tree
gen.mkAttributedCast(tree1, pt)
- } else if (samMatchingFunction(tree, pt).exists) tree setType pt // SAM <:< FunctionN if sam is convertible to said function
- else gen.mkAttributedCast(tree, pt)
+ } else gen.mkAttributedCast(tree, pt)
}
/** Adapt `tree` to expected type `pt`.
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index cd5759f40f..a9d5b69e2e 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -1076,7 +1076,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val sam = samMatchingFunction(tree, pt) // this implies tree.isInstanceOf[Function]
if (sam.exists && !tree.tpe.isErroneous) {
val samTree = adaptToSAM(sam, tree.asInstanceOf[Function], pt, mode)
- if (samTree ne EmptyTree) return samTree
+ if (samTree ne EmptyTree)
+ return samTree.updateAttachment(SAMFunction(pt, sam))
}
}
diff --git a/src/reflect/scala/reflect/internal/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala
index 8358c1295c..0243dd48d2 100644
--- a/src/reflect/scala/reflect/internal/StdAttachments.scala
+++ b/src/reflect/scala/reflect/internal/StdAttachments.scala
@@ -38,6 +38,19 @@ trait StdAttachments {
*/
case class CompoundTypeTreeOriginalAttachment(parents: List[Tree], stats: List[Tree])
+ /** Attached to a Function node during type checking when the expected type is a SAM type (and not a built-in FunctionN).
+ *
+ * Ideally, we'd move to Dotty's Closure AST, which tracks the environment,
+ * the lifted method that has the implementation, and the target type.
+ * For backwards compatibility, an attachment is the best we can do right now.
+ *
+ * @param samTp the expected type that triggered sam conversion (may be a subtype of the type corresponding to sam's owner)
+ * @param sam the single abstract method implemented by the Function we're attaching this to
+ *
+ * @since 2.12.0-M4
+ */
+ case class SAMFunction(samTp: Type, sam: Symbol) extends PlainAttachment
+
/** When present, indicates that the host `Ident` has been created from a backquoted identifier.
*/
case object BackquotedIdentifierAttachment extends PlainAttachment
diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
index 13874916cc..4630597668 100644
--- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
+++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
@@ -37,6 +37,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
this.FixedMirrorTreeCreator
this.FixedMirrorTypeCreator
this.CompoundTypeTreeOriginalAttachment
+ this.SAMFunction
this.BackquotedIdentifierAttachment
this.ForAttachment
this.SyntheticUnitAttachment