summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--spec/06-expressions.md28
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala27
-rw-r--r--src/reflect/scala/reflect/internal/Definitions.scala24
-rw-r--r--src/reflect/scala/reflect/internal/Symbols.scala2
-rw-r--r--test/files/neg/sammy_restrictions.check58
-rw-r--r--test/files/neg/sammy_restrictions.scala64
7 files changed, 122 insertions, 83 deletions
diff --git a/spec/06-expressions.md b/spec/06-expressions.md
index 2b93842a25..bf1a6acf9a 100644
--- a/spec/06-expressions.md
+++ b/spec/06-expressions.md
@@ -1358,18 +1358,26 @@ then the selection is rewritten according to the rules for
###### SAM conversion
An expression `(p1, ..., pN) => body` of function type `(T1, ..., TN) => T` is sam-convertible to the expected type `S` if the following holds:
- - `S` declares an abstract method `m` with signature `(p1: A1, ..., pN: AN): R`;
- - besides `m`, `S` must not declare other deferred value members;
- - the method `m` must have a single argument list (thus, implicit argument lists are not allowed);
- - there must be a type `U` that is a subtype of `S`, so that the expression `new U { final def m(p1: A1, ..., pN: AN): R = body }` is well-typed (`S` need not be fully defined -- the expression will have type `U`).
+ - the class `C` of `S` declares an abstract method `m` with signature `(p1: A1, ..., pN: AN): R`;
+ - besides `m`, `C` must not declare or inherit any other deferred value members;
+ - the method `m` must have a single argument list;
+ - there must be a type `U` that is a subtype of `S`, so that the expression
+ `new U { final def m(p1: A1, ..., pN: AN): R = body }` is well-typed (conforming to the expected type `S`);
+ - for the purpose of scoping, `m` should be considered a static member (`U`'s members are not in scope in `body`);
+ - `(A1, ..., AN) => R` is a subtype of `(T1, ..., TN) => T` (satisfying this condition drives type inference of unknown type parameters in `S`);
-It follows that:
- - the type `S` must have an accessible, no-argument, constructor;
- - the class of `S` must not be nested or local (it must not capture its environment, as that precludes a zero-argument constructor).
+Note that a function literal that targets a SAM is not necessarily compiled to the above instance creation expression. This is platform-dependent.
-Additionally (the following are implementation restrictions):
- - `S`'s [erases](03-types.html#type-erasure) to a trait (this allows for a more efficient encoding when the JVM is the underlying platform);
- - the class of `S` must not be `@specialized`.
+It follows that:
+ - if class `C` defines a constructor, it must be accessible and must define exactly one, empty, argument list;
+ - `m` cannot be polymorphic;
+ - it must be possible to derive a fully-defined type `U` from `S` by inferring any unknown type parameters of `C`.
+
+Finally, we impose some implementation restrictions (these may be lifted in future releases):
+ - `C` must not be nested or local (it must not capture its environment, as that results in a zero-argument constructor)
+ - `C`'s constructor must not have an implicit argument list (this simplifies type inference);
+ - `C` must not declare a self type (this simplifies type inference);
+ - `C` must not be `@specialized`.
### Method Conversions
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
index 32ab52203e..7ccaec2f50 100644
--- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
+++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
@@ -239,7 +239,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
exitingErasure(target.info.paramTypes).map(reboxValueClass) :+ reboxValueClass(exitingErasure(target.info.resultType))).toTypeName
val isSpecialized = specializedName != funSym.name
- val functionalInterface =
+ val functionalInterface = // TODO: this is no longer needed, right? we can just use the regular function classes
if (isSpecialized) currentRun.runDefinitions.Scala_Java8_CompatPackage.info.decl(specializedName.prepend("J"))
else currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction(originalFunction.vparams.length)
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index 7e9e0e2a92..afa0fc92ff 100644
--- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala
+++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -74,7 +74,30 @@ abstract class UnCurry extends InfoTransform
private val newMembers = mutable.Map[Symbol, mutable.Buffer[Tree]]()
// Expand `Function`s in constructors to class instance creation (SI-6666, SI-8363)
- private def mustExpandFunction = forceExpandFunction || inConstructorFlag != 0
+ // We use Java's LambdaMetaFactory (LMF), which requires an interface for the sam's owner
+ private def mustExpandFunction(fun: Function) = forceExpandFunction || {
+ // (TODO: Can't use isInterface, yet, as it hasn't been updated for the new trait encoding)
+ val canUseLambdaMetaFactory = inConstructorFlag == 0 && (fun.attachments.get[SAMFunction].map(_.samTp) match {
+ case Some(userDefinedSamTp) =>
+ val tpSym = erasure.javaErasure(userDefinedSamTp).typeSymbol // we only care about what ends up in the bytecode
+ (
+ // LMF only supports interfaces
+ (tpSym.isJavaInterface || tpSym.isTrait)
+ // Unless tpSym.isStatic, even if the constructor is zero-argument now, it may acquire arguments in explicit outer or lambdalift.
+ // This is an impl restriction to simplify the decision of whether to expand the SAM during uncurry
+ // (when we don't yet know whether it will receive an outer pointer in explicit outer or whether lambda lift will add proxies for captures).
+ // When we delay sam expansion until after explicit outer & lambda lift, we could decide there whether
+ // to expand sam at compile time or use LMF, and this implementation restriction could be lifted.
+ && tpSym.isStatic
+ // impl restriction -- we currently use the boxed apply, so not really useful to allow specialized sam types (https://github.com/scala/scala/pull/4971#issuecomment-198119167)
+ && !tpSym.isSpecialized
+ )
+
+ case _ => true // our built-in FunctionN's are suitable for LambdaMetaFactory by construction
+ })
+
+ !canUseLambdaMetaFactory
+ }
/** Add a new synthetic member for `currentOwner` */
private def addNewMember(t: Tree): Unit =
@@ -191,7 +214,7 @@ abstract class UnCurry extends InfoTransform
def transformFunction(fun: Function): Tree =
// Undo eta expansion for parameterless and nullary methods
if (fun.vparams.isEmpty && isByNameRef(fun.body)) { noApply += fun.body ; fun.body }
- else if (mustExpandFunction) gen.expandFunction(localTyper)(fun, inConstructorFlag)
+ else if (mustExpandFunction(fun)) gen.expandFunction(localTyper)(fun, inConstructorFlag)
else {
// method definition with the same arguments, return type, and body as the original lambda
val liftedMethod = gen.mkLiftedFunctionBodyMethod(localTyper)(fun.symbol.owner, fun)
diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala
index ef9a76f9c4..81071e763d 100644
--- a/src/reflect/scala/reflect/internal/Definitions.scala
+++ b/src/reflect/scala/reflect/internal/Definitions.scala
@@ -848,21 +848,15 @@ trait Definitions extends api.StandardDefinitions {
// (e.g., an alias type or intersection type is fine as long as the intersection dominator compiles to an interface)
val tpSym = erasure.javaErasure(tp).typeSymbol
- if (tpSym.exists
- // We use Java's MetaLambdaFactory, which requires an interface for the sam's owner
- // (TODO: Can't use isInterface, yet, as it hasn't been updated for the new trait encoding)
- && (tpSym.isJavaInterface || tpSym.isTrait)
- // explicit outer precludes no-arg ctor
- && tpSym.isStatic
- // impl restriction -- we currently use the boxed apply, so not really useful to allow specialized sam types (https://github.com/scala/scala/pull/4971#issuecomment-198119167)
- && !tpSym.isSpecialized) {
-
- // this does not apply yet, since traits don't have constructors during type checking
- // if tp has a constructor, it must be public and must not take any arguments
- // (not even an implicit argument list -- to keep it simple for now)
- // && { val ctor = tpSym.primaryConstructor
- // !ctor.exists || (!ctor.isOverloaded && ctor.isPublic && ctor.info.params.isEmpty && ctor.info.paramSectionCount <= 1)
- // }
+ if (tpSym.exists && tpSym.isClass
+ // if tp has a constructor (its class is not a trait), it must be public and must not take any arguments
+ // (implementation restriction: implicit argument lists are excluded to simplify type inference in adaptToSAM)
+ && { val ctor = tpSym.primaryConstructor
+ !ctor.exists || (!ctor.isOverloaded && ctor.isPublic && ctor.info.params.isEmpty && ctor.info.paramSectionCount <= 1)}
+ // we won't be able to create an instance of tp if it doesn't correspond to its self type
+ // (checking conformance gets complicated when tp is not fully defined, so let's just rule out self types entirely)
+ && !tpSym.hasSelfType
+ ) {
// find the single abstract member, if there is one
// don't go out requiring DEFERRED members, as you will get them even if there's a concrete override:
diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala
index 0cbb45d12d..ee763df849 100644
--- a/src/reflect/scala/reflect/internal/Symbols.scala
+++ b/src/reflect/scala/reflect/internal/Symbols.scala
@@ -2000,7 +2000,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
*/
def thisSym: Symbol = this
- def hasSelfType = thisSym.tpeHK != this.tpeHK
+ def hasSelfType = (thisSym ne this) && (typeOfThis.typeConstructor ne typeConstructor)
/** The type of `this` in a class, or else the type of the symbol itself. */
def typeOfThis = thisSym.tpe_*
diff --git a/test/files/neg/sammy_restrictions.check b/test/files/neg/sammy_restrictions.check
index 5fd2c858c2..09579cbe21 100644
--- a/test/files/neg/sammy_restrictions.check
+++ b/test/files/neg/sammy_restrictions.check
@@ -1,37 +1,51 @@
-sammy_restrictions.scala:37: error: type mismatch;
+sammy_restrictions.scala:35: error: type mismatch;
found : () => Int
required: NoAbstract
- (() => 0) : NoAbstract // error expected
+ (() => 0) : NoAbstract
^
-sammy_restrictions.scala:38: error: type mismatch;
+sammy_restrictions.scala:36: error: type mismatch;
found : Int => Int
required: TwoAbstract
- ((x: Int) => 0): TwoAbstract // error expected
+ ((x: Int) => 0): TwoAbstract
^
-sammy_restrictions.scala:41: error: type mismatch;
+sammy_restrictions.scala:37: error: type mismatch;
+ found : Int => Int
+ required: NoEmptyConstructor
+ ((x: Int) => 0): NoEmptyConstructor
+ ^
+sammy_restrictions.scala:38: error: type mismatch;
+ found : Int => Int
+ required: MultipleConstructorLists
+ ((x: Int) => 0): MultipleConstructorLists
+ ^
+sammy_restrictions.scala:39: error: type mismatch;
+ found : Int => Int
+ required: OneEmptySecondaryConstructor
+ ((x: Int) => 0): OneEmptySecondaryConstructor // derived class must have an empty *primary* to call.
+ ^
+sammy_restrictions.scala:40: error: type mismatch;
found : Int => Int
required: MultipleMethodLists
- ((x: Int) => 0): MultipleMethodLists // error expected
+ ((x: Int) => 0): MultipleMethodLists
+ ^
+sammy_restrictions.scala:41: error: type mismatch;
+ found : Int => Int
+ required: ImplicitConstructorParam
+ ((x: Int) => 0): ImplicitConstructorParam
^
sammy_restrictions.scala:42: error: type mismatch;
found : Int => Int
required: ImplicitMethodParam
- ((x: Int) => 0): ImplicitMethodParam // error expected
+ ((x: Int) => 0): ImplicitMethodParam
^
-sammy_restrictions.scala:45: error: type mismatch;
+sammy_restrictions.scala:43: error: type mismatch;
found : Int => Int
required: PolyMethod
- ((x: Int) => 0): PolyMethod // error expected
- ^
-sammy_restrictions.scala:47: error: missing parameter type
- (x => x + 1): NotAnInterface[Int, Int] // error expected (not an interface)
- ^
-sammy_restrictions.scala:48: error: type mismatch;
- found : String => Int
- required: A[Object,Int]
- ((x: String) => 1): A[Object, Int] // error expected (type mismatch)
- ^
-sammy_restrictions.scala:51: error: missing parameter type
- n.app(1)(x => List(x)) // error expected: n.F is not a SAM type (it does not have a no-arg ctor since it has an outer pointer)
- ^
-8 errors found
+ ((x: Int) => 0): PolyMethod
+ ^
+sammy_restrictions.scala:44: error: type mismatch;
+ found : Int => Int
+ required: SelfTp
+ ((x: Int) => 0): SelfTp
+ ^
+10 errors found
diff --git a/test/files/neg/sammy_restrictions.scala b/test/files/neg/sammy_restrictions.scala
index ed8cf35aa4..ff2c16b679 100644
--- a/test/files/neg/sammy_restrictions.scala
+++ b/test/files/neg/sammy_restrictions.scala
@@ -1,52 +1,52 @@
-trait NoAbstract
+abstract class NoAbstract
-trait TwoAbstract { def ap(a: Int): Int; def pa(a: Int): Int }
+abstract class TwoAbstract { def ap(a: Int): Int; def pa(a: Int): Int }
-trait Base // check that the super class constructor isn't considered.
+abstract class Base // check that the super class constructor isn't considered.
+abstract class NoEmptyConstructor(a: Int) extends Base { def this(a: String) = this(0); def ap(a: Int): Int }
-trait MultipleMethodLists { def ap(a: Int)(): Int }
+abstract class OneEmptyConstructor() { def this(a: Int) = this(); def ap(a: Int): Int }
-trait ImplicitMethodParam { def ap(a: Int)(implicit b: String): Int }
+abstract class OneEmptySecondaryConstructor(a: Int) { def this() = this(0); def ap(a: Int): Int }
-trait PolyClass[T] { def ap(a: T): T }
+abstract class MultipleConstructorLists()() { def ap(a: Int): Int }
-trait PolyMethod { def ap[T](a: T): T }
+abstract class MultipleMethodLists()() { def ap(a: Int)(): Int }
-trait OneAbstract { def ap(a: Int): Any }
-trait DerivedOneAbstract extends OneAbstract
+abstract class ImplicitConstructorParam()(implicit a: String) { def ap(a: Int): Int }
-// restrictions
+abstract class ImplicitMethodParam() { def ap(a: Int)(implicit b: String): Int }
-// must be an interface
-abstract class NotAnInterface[T, R]{ def apply(x: T): R }
+abstract class PolyClass[T] { def ap(a: T): T }
-trait A[T, R]{ def apply(x: T): R }
+abstract class PolyMethod { def ap[T](a: T): T }
-// must not capture
-class Nested {
- trait F[T, U] { def apply(x: T): U }
-
- def app[T, U](x: T)(f: F[T, U]): U = f(x)
-}
+abstract class OneAbstract { def ap(a: Int): Any }
+abstract class DerivedOneAbstract extends OneAbstract
+abstract class SelfTp { self: NoAbstract => def ap(a: Int): Any }
+abstract class SelfVar { self => def ap(a: Int): Any }
object Test {
implicit val s: String = ""
type NonClassType = DerivedOneAbstract with OneAbstract
- (() => 0) : NoAbstract // error expected
- ((x: Int) => 0): TwoAbstract // error expected
+ // errors:
+ (() => 0) : NoAbstract
+ ((x: Int) => 0): TwoAbstract
+ ((x: Int) => 0): NoEmptyConstructor
+ ((x: Int) => 0): MultipleConstructorLists
+ ((x: Int) => 0): OneEmptySecondaryConstructor // derived class must have an empty *primary* to call.
+ ((x: Int) => 0): MultipleMethodLists
+ ((x: Int) => 0): ImplicitConstructorParam
+ ((x: Int) => 0): ImplicitMethodParam
+ ((x: Int) => 0): PolyMethod
+ ((x: Int) => 0): SelfTp
+
+ // allowed:
+ ((x: Int) => 0): OneEmptyConstructor
((x: Int) => 0): DerivedOneAbstract
- ((x: Int) => 0): NonClassType
- ((x: Int) => 0): MultipleMethodLists // error expected
- ((x: Int) => 0): ImplicitMethodParam // error expected
-
+ ((x: Int) => 0): NonClassType // we also allow type aliases in instantiation expressions, if they resolve to a class type
((x: Int) => 0): PolyClass[Int]
- ((x: Int) => 0): PolyMethod // error expected
-
- (x => x + 1): NotAnInterface[Int, Int] // error expected (not an interface)
- ((x: String) => 1): A[Object, Int] // error expected (type mismatch)
-
- val n = new Nested
- n.app(1)(x => List(x)) // error expected: n.F is not a SAM type (it does not have a no-arg ctor since it has an outer pointer)
+ ((x: Int) => 0): SelfVar
}