diff options
-rw-r--r-- | spec/06-expressions.md | 28 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/Delambdafy.scala | 2 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/UnCurry.scala | 27 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/Definitions.scala | 24 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/Symbols.scala | 2 | ||||
-rw-r--r-- | test/files/neg/sammy_restrictions.check | 58 | ||||
-rw-r--r-- | test/files/neg/sammy_restrictions.scala | 64 |
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 } |