From b86021f47fbae1c5ca54e094c2669b7f4f27371d Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 18 Mar 2016 14:55:23 -0700 Subject: sbt build targets build/ It avoids confusion with existing test/partest scripts that test the compiler in build/, while sbt it targeting build-sbt/. --- build.sbt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index 4dae2abe35..761165dfa7 100644 --- a/build.sbt +++ b/build.sbt @@ -4,8 +4,8 @@ * What you see below is very much work-in-progress. The following features are implemented: * - Compiling all classses for the compiler and library ("compile" in the respective subprojects) * - Running JUnit tests ("test") and partest ("test/it:test") - * - Creating build-sbt/quick with all compiled classes and launcher scripts ("dist/mkQuick") - * - Creating build-sbt/pack with all JARs and launcher scripts ("dist/mkPack") + * - Creating build/quick with all compiled classes and launcher scripts ("dist/mkQuick") + * - Creating build/pack with all JARs and launcher scripts ("dist/mkPack") * - Building all scaladoc sets ("doc") * - Publishing ("publishDists" and standard sbt tasks like "publish" and "publishLocal") * @@ -715,8 +715,8 @@ def configureAsSubproject(project: Project): Project = { lazy val buildDirectory = settingKey[File]("The directory where all build products go. By default ./build") lazy val mkBin = taskKey[Seq[File]]("Generate shell script (bash or Windows batch).") -lazy val mkQuick = taskKey[Unit]("Generate a full build, including scripts, in build-sbt/quick") -lazy val mkPack = taskKey[Unit]("Generate a full build, including scripts, in build-sbt/pack") +lazy val mkQuick = taskKey[Unit]("Generate a full build, including scripts, in build/quick") +lazy val mkPack = taskKey[Unit]("Generate a full build, including scripts, in build/pack") // Defining these settings is somewhat redundant as we also redefine settings that depend on them. // However, IntelliJ's project import works better when these are set correctly. @@ -775,7 +775,7 @@ def generateServiceProviderResources(services: (String, String)*): Setting[_] = } }.taskValue -buildDirectory in ThisBuild := (baseDirectory in ThisBuild).value / "build-sbt" +buildDirectory in ThisBuild := (baseDirectory in ThisBuild).value / "build" // Add tab completion to partest commands += Command("partest")(_ => PartestUtil.partestParser((baseDirectory in ThisBuild).value, (baseDirectory in ThisBuild).value / "test")) { (state, parsed) => -- cgit v1.2.3 From 4356ac7a1d2cae67d80af84c64a6c2d941a11b4b Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 18 Feb 2016 15:47:44 -0800 Subject: TypeHistory's toString time travels consistently For each history entry, run the `Type`'s `toString` at the corresponding phase, so that e.g., a method type's parameter symbols' `info`'s `toString` runs at the phase corresponding to the type history we're turning into a string. --- src/reflect/scala/reflect/internal/Symbols.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 34f9417e57..0cbb45d12d 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -3642,7 +3642,10 @@ trait Symbols extends api.Symbols { self: SymbolTable => assert((prev eq null) || phaseId(validFrom) > phaseId(prev.validFrom), this) assert(validFrom != NoPeriod, this) - private def phaseString = "%s: %s".format(phaseOf(validFrom), info) + private def phaseString = { + val phase = phaseOf(validFrom) + s"$phase: ${exitingPhase(phase)(info.toString)}" + } override def toString = toList reverseMap (_.phaseString) mkString ", " def toList: List[TypeHistory] = this :: ( if (prev eq null) Nil else prev.toList ) -- cgit v1.2.3 From 52496ba844c4a5ffd2ed1000da839f8b7aa2e915 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 8 Feb 2016 10:46:35 -0800 Subject: Remove dead code now that `genBCodeActive` is always true. --- .../scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 2 + .../scala/tools/nsc/settings/ScalaSettings.scala | 2 +- .../scala/tools/nsc/transform/Delambdafy.scala | 365 +++------------------ .../scala/tools/nsc/transform/LambdaLift.scala | 8 +- .../scala/tools/nsc/transform/UnCurry.scala | 23 +- .../scala/reflect/internal/Definitions.scala | 4 - 6 files changed, 57 insertions(+), 347 deletions(-) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 324fc10eae..c6c9545391 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -61,6 +61,8 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { assert(classSym.isClass, s"not a class: $classSym") val r = exitingPickler(classSym.isAnonymousClass) || !classSym.originalOwner.isClass if (r) { + // lambda lift renames symbols and may accidentally introduce `$lambda` into a class name, making `isDelambdafyFunction` true. + // we prevent this, see `nonAnon` in LambdaLift. // phase travel necessary: after flatten, the name includes the name of outer classes. // if some outer name contains $lambda, a non-lambda class is considered lambda. assert(exitingPickler(!classSym.isDelambdafyFunction), classSym.name) diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 982a6da41a..e924dc856a 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -109,7 +109,7 @@ trait ScalaSettings extends AbsScalaSettings val Xmigration = ScalaVersionSetting ("-Xmigration", "version", "Warn about constructs whose behavior may have changed since version.", initial = NoScalaVersion, default = Some(AnyScalaVersion)) val nouescape = BooleanSetting ("-Xno-uescape", "Disable handling of \\u unicode escapes.") val Xnojline = BooleanSetting ("-Xnojline", "Do not use JLine for editing.") - val Xverify = BooleanSetting ("-Xverify", "Verify generic signatures in generated bytecode (asm backend only.)") + val Xverify = BooleanSetting ("-Xverify", "Verify generic signatures in generated bytecode.") val plugin = MultiStringSetting ("-Xplugin", "paths", "Load a plugin from each classpath.") val disable = MultiStringSetting ("-Xplugin-disable", "plugin", "Disable plugins by name.") val showPlugins = BooleanSetting ("-Xplugin-list", "Print a synopsis of loaded plugins.") diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 67e3f67f2f..8799ed2e04 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -7,30 +7,19 @@ import scala.collection._ import scala.collection.mutable.LinkedHashMap /** - * This transformer is responsible for preparing lambdas for runtime, by either translating to anonymous classes - * or to a tree that will be converted to invokedynamic by the JVM 1.8+ backend. - * - * The main assumption it makes is that a lambda {args => body} has been turned into - * {args => liftedBody()} where lifted body is a top level method that implements the body of the lambda. - * Currently Uncurry is responsible for that transformation. - * - * From a lambda, Delambdafy will create: - * - * Under GenASM - * - * 1) a new top level class that - a) has fields and a constructor taking the captured environment (including possibly the "this" - * reference) - * b) an apply method that calls the target method - * c) if needed a bridge method for the apply method - * 2) an instantiation of the newly created class which replaces the lambda - * - * Under GenBCode: - * - * 1) An application of the captured arguments to a fictional symbol representing the lambda factory. - * This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`. - * The captured arguments include `this` if `liftedBody` is unable to be made STATIC. - */ + * This transformer is responsible for preparing Function nodes for runtime, + * by translating to a tree that will be converted to an invokedynamic by the backend. + * + * The main assumption it makes is that a Function {args => body} has been turned into + * {args => liftedBody()} where lifted body is a top level method that implements the body of the function. + * Currently Uncurry is responsible for that transformation. + * + * From this shape of Function, Delambdafy will create: + * + * An application of the captured arguments to a fictional symbol representing the lambda factory. + * This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`. + * The captured arguments include `this` if `liftedBody` is unable to be made STATIC. + */ abstract class Delambdafy extends Transform with TypingTransformers with ast.TreeDSL with TypeAdaptingTransformer { import global._ import definitions._ @@ -55,7 +44,6 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre class DelambdafyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) with TypeAdapter { private val lambdaClassDefs = new mutable.LinkedHashMap[Symbol, List[Tree]] withDefaultValue Nil - val typer = localTyper // we need to know which methods refer to the 'this' reference so that we can determine @@ -80,31 +68,12 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre referrers } - // the result of the transformFunction method. - sealed abstract class TransformedFunction - // A class definition for the lambda, an expression instantiating the lambda class - case class DelambdafyAnonClass(lambdaClassDef: ClassDef, newExpr: Tree) extends TransformedFunction - case class InvokeDynamicLambda(tree: Apply) extends TransformedFunction - private val boxingBridgeMethods = mutable.ArrayBuffer[Tree]() // here's the main entry point of the transform override def transform(tree: Tree): Tree = tree match { // the main thing we care about is lambdas - case fun @ Function(_, _) => - transformFunction(fun) match { - case DelambdafyAnonClass(lambdaClassDef, newExpr) => - // a lambda becomes a new class, an instantiation expression - val pkg = lambdaClassDef.symbol.owner - - // we'll add the lambda class to the package later - lambdaClassDefs(pkg) = lambdaClassDef :: lambdaClassDefs(pkg) - - super.transform(newExpr) - case InvokeDynamicLambda(apply) => - // ... or an invokedynamic call - super.transform(apply) - } + case fun: Function => super.transform(transformFunction(fun)) case Template(_, _, _) => try { // during this call boxingBridgeMethods will be populated from the Function case @@ -126,7 +95,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre private def optionSymbol(sym: Symbol): Option[Symbol] = if (sym.exists) Some(sym) else None // turns a lambda into a new class def, a New expression instantiating that class - private def transformFunction(originalFunction: Function): TransformedFunction = { + private def transformFunction(originalFunction: Function): Tree = { val formals = originalFunction.vparams.map(_.tpe) val restpe = originalFunction.body.tpe.deconst val oldClass = originalFunction.symbol.enclClass @@ -200,155 +169,6 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre postErasure.newTransformer(unit).transform(methDef0).asInstanceOf[DefDef] } } - /** - * Creates the apply method for the anonymous subclass of FunctionN - */ - def createApplyMethod(newClass: Symbol, fun: Function, thisProxy: Symbol): DefDef = { - val methSym = newClass.newMethod(nme.apply, fun.pos, FINAL | SYNTHETIC) - val params = fun.vparams map (_.duplicate) - - val paramSyms = map2(formals, params) { - (tp, vparam) => methSym.newSyntheticValueParam(tp, vparam.name) - } - params zip paramSyms foreach { case (valdef, sym) => valdef.symbol = sym } - params foreach (_.symbol.owner = methSym) - - val methodType = MethodType(paramSyms, restpe) - methSym setInfo methodType - - newClass.info.decls enter methSym - - val Apply(_, oldParams) = fun.body - val qual = if (thisProxy.exists) - Select(gen.mkAttributedThis(newClass), thisProxy) - else - gen.mkAttributedThis(oldClass) // sort of a lie, EmptyTree. would be more honest, but the backend chokes on that. - - val body = localTyper typed Apply(Select(qual, target), oldParams) - body.substituteSymbols(fun.vparams map (_.symbol), params map (_.symbol)) - body changeOwner (fun.symbol -> methSym) - - val methDef = DefDef(methSym, List(params), body) - - // Have to repack the type to avoid mismatches when existentials - // appear in the result - see SI-4869. - // TODO probably don't need packedType - methDef.tpt setType localTyper.packedType(body, methSym) - methDef - } - - /** - * Creates the constructor on the newly created class. It will handle - * initialization of members that represent the captured environment - */ - def createConstructor(newClass: Symbol, members: List[ValDef]): DefDef = { - val constrSym = newClass.newConstructor(originalFunction.pos, SYNTHETIC) - - val (paramSymbols, params, assigns) = (members map {member => - val paramSymbol = newClass.newVariable(member.symbol.name.toTermName, newClass.pos, 0) - paramSymbol.setInfo(member.symbol.info) - val paramVal = ValDef(paramSymbol) - val paramIdent = Ident(paramSymbol) - val assign = Assign(Select(gen.mkAttributedThis(newClass), member.symbol), paramIdent) - - (paramSymbol, paramVal, assign) - }).unzip3 - - val constrType = MethodType(paramSymbols, newClass.thisType) - constrSym setInfoAndEnter constrType - - val body = - Block( - List( - atPos(newClass.pos)(Apply(gen.mkSuperInitCall, Nil)) - ) ++ assigns, - Literal(Constant(())): Tree - ) setPos newClass.pos - - (localTyper typed DefDef(constrSym, List(params), body) setPos newClass.pos).asInstanceOf[DefDef] - } - - val pkg = oldClass.owner - - // Parent for anonymous class def - val abstractFunctionErasedType = AbstractFunctionClass(formals.length).tpe - - // anonymous subclass of FunctionN with an apply method - def makeAnonymousClass: ClassDef = { - val parents = addSerializable(abstractFunctionErasedType) - val funOwner = originalFunction.symbol.owner - - // TODO harmonize the naming of delambdafy anon-fun classes with those spun up by Uncurry - // - make `anonClass.isAnonymousClass` true. - // - use `newAnonymousClassSymbol` or push the required variations into a similar factory method - // - reinstate the assertion in `Erasure.resolveAnonymousBridgeClash` - val suffix = nme.DELAMBDAFY_LAMBDA_CLASS_NAME + "$" + ( - if (funOwner.isPrimaryConstructor) "" - else "$" + funOwner.name + "$" - ) - val oldClassPart = oldClass.name.decode - // make sure the class name doesn't contain $anon, otherwise isAnonymousClass/Function may be true - val name = unit.freshTypeName(s"$oldClassPart$suffix".replace("$anon", "$nestedInAnon")) - - val lambdaClass = pkg newClassSymbol(name, originalFunction.pos, FINAL | SYNTHETIC) addAnnotation SerialVersionUIDAnnotation - lambdaClass.associatedFile = unit.source.file - // make sure currentRun.compiles(lambdaClass) is true (AddInterfaces does the same for trait impl classes) - currentRun.symSource(lambdaClass) = funOwner.sourceFile - lambdaClass setInfo ClassInfoType(parents, newScope, lambdaClass) - assert(!lambdaClass.isAnonymousClass && !lambdaClass.isAnonymousFunction, "anonymous class name: "+ lambdaClass.name) - assert(lambdaClass.isDelambdafyFunction, "not lambda class name: " + lambdaClass.name) - - val captureProxies2 = new LinkedHashMap[Symbol, TermSymbol] - captures foreach {capture => - val sym = lambdaClass.newVariable(unit.freshTermName(capture.name.toString + "$"), capture.pos, SYNTHETIC) - sym setInfo capture.info - captureProxies2 += ((capture, sym)) - } - - // the Optional proxy that will hold a reference to the 'this' - // object used by the lambda, if any. NoSymbol if there is no this proxy - val thisProxy = { - if (isStatic) - NoSymbol - else { - val sym = lambdaClass.newVariable(nme.FAKE_LOCAL_THIS, originalFunction.pos, SYNTHETIC) - sym.setInfo(oldClass.tpe) - } - } - - val decapturify = new DeCapturifyTransformer(captureProxies2, unit, oldClass, lambdaClass, originalFunction.symbol.pos, thisProxy) - - val decapturedFunction = decapturify.transform(originalFunction).asInstanceOf[Function] - - val members = (optionSymbol(thisProxy).toList ++ (captureProxies2 map (_._2))) map {member => - lambdaClass.info.decls enter member - ValDef(member, gen.mkZero(member.tpe)) setPos decapturedFunction.pos - } - - // constructor - val constr = createConstructor(lambdaClass, members) - - // apply method with same arguments and return type as original lambda. - val applyMethodDef = createApplyMethod(lambdaClass, decapturedFunction, thisProxy) - - val bridgeMethod = createBridgeMethod(lambdaClass, originalFunction, applyMethodDef) - - def fulldef(sym: Symbol) = - if (sym == NoSymbol) sym.toString - else s"$sym: ${sym.tpe} in ${sym.owner}" - - bridgeMethod foreach (bm => - // TODO SI-6260 maybe just create the apply method with the signature (Object => Object) in all cases - // rather than the method+bridge pair. - if (bm.symbol.tpe =:= applyMethodDef.symbol.tpe) - erasure.resolveAnonymousBridgeClash(applyMethodDef.symbol, bm.symbol) - ) - - val body = members ++ List(constr, applyMethodDef) ++ bridgeMethod - - // TODO if member fields are private this complains that they're not accessible - localTyper.typedPos(decapturedFunction.pos)(ClassDef(lambdaClass, body)).asInstanceOf[ClassDef] - } val allCaptureArgs: List[Tree] = { val thisArg = if (isStatic) Nil else (gen.mkAttributedThis(oldClass) setPos originalFunction.pos) :: Nil @@ -375,106 +195,43 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre val boxedParamTypes = info.paramTypes.takeRight(arity).map(boxed) (boxedParamTypes, boxed(info.resultType)) } - val functionType = definitions.functionType(functionParamTypes, functionResultType) - val (functionalInterface, isSpecialized) = java8CompatFunctionalInterface(target, functionType) - if (functionalInterface.exists) { - // Create a symbol representing a fictional lambda factory method that accepts the captured - // arguments and returns a Function. - val msym = currentOwner.newMethod(nme.ANON_FUN_NAME, originalFunction.pos, ARTIFACT) - val argTypes: List[Type] = allCaptureArgs.map(_.tpe) - val params = msym.newSyntheticValueParams(argTypes) - msym.setInfo(MethodType(params, functionType)) - val arity = originalFunction.vparams.length - - val lambdaTarget = - if (isSpecialized) - target - else { - createBoxingBridgeMethod(functionParamTypes, functionResultType) match { - case EmptyTree => - target - case bridge => - boxingBridgeMethods += bridge - bridge.symbol - } - } - - // We then apply this symbol to the captures. - val apply = localTyper.typedPos(originalFunction.pos)(Apply(Ident(msym), allCaptureArgs)).asInstanceOf[Apply] - - // 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. - apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, arity, functionalInterface)) - InvokeDynamicLambda(apply) - } else { - val anonymousClassDef = makeAnonymousClass - pkg.info.decls enter anonymousClassDef.symbol - val newStat = Typed(New(anonymousClassDef.symbol, allCaptureArgs: _*), TypeTree(abstractFunctionErasedType)) - val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat) - DelambdafyAnonClass(anonymousClassDef, typedNewStat) - } - } + val functionType = definitions.functionType(functionParamTypes, functionResultType) + val funSym = functionType.typeSymbol + val specializedName = specializeTypes.specializedFunctionName(funSym, functionType.typeArgs).toTypeName + val isSpecialized = specializedName != funSym.name + + // The functional interface that can be used to adapt the lambda target method `target` to the + // given function type. Returns `NoSymbol` if the compiler settings are unsuitable. + val functionalInterface = + if (isSpecialized) currentRun.runDefinitions.Scala_Java8_CompatPackage.info.decl(specializedName.prepend("J")) + else currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction(arity) + + assert(functionalInterface.exists) + + val lambdaTarget = + if (isSpecialized) target + else createBoxingBridgeMethod(functionParamTypes, functionResultType) match { + case EmptyTree => target + case bridge => boxingBridgeMethods += bridge; bridge.symbol + } - /** - * Creates a bridge method if needed. The bridge method forwards from apply(x1: Object, x2: Object...xn: Object): Object to - * apply(x1: T1, x2: T2...xn: Tn): T0 using type adaptation on each input and output. The only time a bridge isn't needed - * is when the original lambda is already erased to type Object, Object, Object... => Object - */ - def createBridgeMethod(newClass:Symbol, originalFunction: Function, applyMethod: DefDef): Option[DefDef] = { - val bridgeMethSym = newClass.newMethod(nme.apply, applyMethod.pos, FINAL | SYNTHETIC | BRIDGE) - val originalParams = applyMethod.vparamss(0) - val bridgeParams = originalParams map { originalParam => - val bridgeSym = bridgeMethSym.newSyntheticValueParam(ObjectTpe, originalParam.name) - ValDef(bridgeSym) + // Create a symbol representing a fictional lambda factory method that accepts the captured + // arguments and returns a Function. + val msym = { + val meth = currentOwner.newMethod(nme.ANON_FUN_NAME, originalFunction.pos, ARTIFACT) + val capturedParams = meth.newSyntheticValueParams(allCaptureArgs.map(_.tpe)) + meth.setInfo(MethodType(capturedParams, functionType)) } - val bridgeSyms = bridgeParams map (_.symbol) - - val methodType = MethodType(bridgeSyms, ObjectTpe) - bridgeMethSym setInfo methodType + // We then apply this symbol to the captures. + val apply = localTyper.typedPos(originalFunction.pos)(Apply(Ident(msym), allCaptureArgs)).asInstanceOf[Apply] - def adapt(tree: Tree, expectedTpe: Type): (Boolean, Tree) = { - if (tree.tpe =:= expectedTpe) (false, tree) - else (true, adaptToType(tree, expectedTpe)) - } - - def adaptAndPostErase(tree: Tree, pt: Type): (Boolean, Tree) = { - val (needsAdapt, adaptedTree) = adapt(tree, pt) - val trans = postErasure.newTransformer(unit) - val postErasedTree = trans.atOwner(currentOwner)(trans.transform(adaptedTree)) // SI-8017 eliminates ErasedValueTypes - (needsAdapt, postErasedTree) - } + // 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. + apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, arity, functionalInterface)) - enteringPhase(currentRun.posterasurePhase) { - // e.g, in: - // class C(val a: Int) extends AnyVal; (x: Int) => new C(x) - // - // This type is: - // (x: Int)ErasedValueType(class C, Int) - val liftedBodyDefTpe: MethodType = { - val liftedBodySymbol = { - val Apply(method, _) = originalFunction.body - method.symbol - } - liftedBodySymbol.info.asInstanceOf[MethodType] - } - val (paramNeedsAdaptation, adaptedParams) = (bridgeSyms zip liftedBodyDefTpe.params map {case (bridgeSym, param) => adapt(Ident(bridgeSym) setType bridgeSym.tpe, param.tpe)}).unzip - // SI-8017 Before, this code used `applyMethod.symbol.info.resultType`. - // But that symbol doesn't have a type history that goes back before `delambdafy`, - // so we just see a plain `Int`, rather than `ErasedValueType(C, Int)`. - // This triggered primitive boxing, rather than value class boxing. - val resTp = liftedBodyDefTpe.finalResultType - val body = Apply(gen.mkAttributedSelect(gen.mkAttributedThis(newClass), applyMethod.symbol), adaptedParams) setType resTp - val (needsReturnAdaptation, adaptedBody) = adaptAndPostErase(body, ObjectTpe) - - val needsBridge = (paramNeedsAdaptation contains true) || needsReturnAdaptation - if (needsBridge) { - val methDef = DefDef(bridgeMethSym, List(bridgeParams), adaptedBody) - newClass.info.decls enter bridgeMethSym - Some((localTyper typed methDef).asInstanceOf[DefDef]) - } else None - } + apply } } // DelambdafyTransformer @@ -510,19 +267,6 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre } } - // A transformer that converts specified captured symbols into other symbols - // TODO this transform could look more like ThisSubstituter and TreeSymSubstituter. It's not clear that it needs that level of sophistication since the types - // at this point are always very simple flattened/erased types, but it would probably be more robust if it tried to take more complicated types into account - class DeCapturifyTransformer(captureProxies: Map[Symbol, TermSymbol], unit: CompilationUnit, oldClass: Symbol, newClass:Symbol, pos: Position, thisProxy: Symbol) extends TypingTransformer(unit) { - override def transform(tree: Tree) = tree match { - case tree@This(encl) if tree.symbol == oldClass && thisProxy.exists => - gen mkAttributedSelect (gen mkAttributedThis newClass, thisProxy) - case Ident(name) if (captureProxies contains tree.symbol) => - gen mkAttributedSelect (gen mkAttributedThis newClass, captureProxies(tree.symbol)) - case _ => super.transform(tree) - } - } - /** * Get the symbol of the target lifted lambda body method from a function. I.e. if * the function is {args => anonfun(args)} then this method returns anonfun's symbol @@ -565,21 +309,4 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre } final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol) - - // The functional interface that can be used to adapt the lambda target method `target` to the - // given function type. Returns `NoSymbol` if the compiler settings are unsuitable. - private def java8CompatFunctionalInterface(target: Symbol, functionType: Type): (Symbol, Boolean) = { - val sym = functionType.typeSymbol - val pack = currentRun.runDefinitions.Scala_Java8_CompatPackage - val name1 = specializeTypes.specializedFunctionName(sym, functionType.typeArgs) - val paramTps :+ restpe = functionType.typeArgs - val arity = paramTps.length - val isSpecialized = name1.toTypeName != sym.name - val functionalInterface = if (!isSpecialized) { - currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction(arity) - } else { - pack.info.decl(name1.toTypeName.prepend("J")) - } - (functionalInterface, isSpecialized) - } } diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index 7a5bd747c4..074acc1332 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -223,10 +223,6 @@ abstract class LambdaLift extends InfoTransform { debuglog("renaming in %s: %s => %s".format(sym.owner.fullLocationString, originalName, sym.name)) } - // make sure that the name doesn't make the symbol accidentally `isAnonymousClass` (et.al) by - // introducing `$anon` in its name. - def nonAnon(s: String) = nme.ensureNonAnon(s) - def newName(sym: Symbol): Name = { val originalName = sym.name def freshen(prefix: String): Name = @@ -235,7 +231,7 @@ abstract class LambdaLift extends InfoTransform { val join = nme.NAME_JOIN_STRING if (sym.isAnonymousFunction && sym.owner.isMethod) { - freshen(sym.name + join + nonAnon(sym.owner.name.toString) + join) + freshen(sym.name + join + nme.ensureNonAnon(sym.owner.name.toString) + join) } else { val name = freshen(sym.name + join) // SI-5652 If the lifted symbol is accessed from an inner class, it will be made public. (where?) @@ -243,7 +239,7 @@ abstract class LambdaLift extends InfoTransform { // package - subclass might have the same name), avoids a VerifyError in the case // that a sub-class happens to lifts out a method with the *same* name. if (originalName.isTermName && calledFromInner(sym)) - newTermNameCached(nonAnon(sym.enclClass.fullName('$')) + nme.EXPAND_SEPARATOR_STRING + name) + newTermNameCached(nme.ensureNonAnon(sym.enclClass.fullName('$')) + nme.EXPAND_SEPARATOR_STRING + name) else name } diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 40a988ee94..5a75332881 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -72,13 +72,8 @@ abstract class UnCurry extends InfoTransform private val noApply = mutable.HashSet[Tree]() private val newMembers = mutable.Map[Symbol, mutable.Buffer[Tree]]() - private lazy val forceSpecializationInfoTransformOfFunctionN: Unit = { - if (currentRun.specializePhase != NoPhase) { // be robust in case of -Ystop-after:uncurry - exitingSpecialize { - FunctionClass.seq.foreach(cls => cls.info) - } - } - } + // Expand `Function`s in constructors to class instance creation (SI-6666, SI-8363) + private def mustExpandFunction = inlineFunctionExpansion || (inConstructorFlag != 0) /** Add a new synthetic member for `currentOwner` */ private def addNewMember(t: Tree): Unit = @@ -230,15 +225,10 @@ abstract class UnCurry extends InfoTransform def mkMethod(owner: Symbol, name: TermName, additionalFlags: FlagSet = NoFlags): DefDef = gen.mkMethodFromFunction(localTyper)(fun, owner, name, additionalFlags) - def isSpecialized = { - forceSpecializationInfoTransformOfFunctionN - val specialized = specializeTypes.specializedType(fun.tpe) - !(specialized =:= fun.tpe) - } - - def canUseDelamdafyMethod = inConstructorFlag == 0 // Avoiding synthesizing code prone to SI-6666, SI-8363 by using old-style lambda translation - if (inlineFunctionExpansion || !canUseDelamdafyMethod) { - val parents = addSerializable(abstractFunctionForFunctionType(fun.tpe)) + if (mustExpandFunction) { + assert(isFunctionType(fun.tpe), s"Not a FunctionN? $fun: ${fun.tpe}") + val funTpArgs = fun.tpe.typeArgs + val parents = addSerializable(abstractFunctionType(funTpArgs.init, funTpArgs.last)) val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation // The original owner is used in the backend for the EnclosingMethod attribute. If fun is // nested in a value-class method, its owner was already changed to the extension method. @@ -267,7 +257,6 @@ abstract class UnCurry extends InfoTransform } } - def transformArgs(pos: Position, fun: Symbol, args: List[Tree], formals: List[Type]) = { val isJava = fun.isJavaDefined def transformVarargs(varargsElemType: Type) = { diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 9ff4e89903..48caf3081a 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -793,10 +793,6 @@ trait Definitions extends api.StandardDefinitions { private[this] var volatileRecursions: Int = 0 private[this] val pendingVolatiles = mutable.HashSet[Symbol]() - def abstractFunctionForFunctionType(tp: Type) = { - assert(isFunctionType(tp), tp) - abstractFunctionType(tp.typeArgs.init, tp.typeArgs.last) - } def functionNBaseType(tp: Type): Type = tp.baseClasses find isFunctionSymbol match { case Some(sym) => tp baseType unspecializedSymbol(sym) case _ => tp -- cgit v1.2.3 From 828105f8ca5c34399608e87968fa840044113f3f Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 24 Mar 2016 14:50:15 -0700 Subject: Refactor. Extract mkLiteralUnit and mkUnitBlock --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 2 +- src/reflect/scala/reflect/internal/TreeGen.scala | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 16d3f5134b..dd0c9fe945 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1186,7 +1186,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val savedUndetparams = context.undetparams silent(_.instantiate(tree, mode, UnitTpe)) orElse { _ => context.undetparams = savedUndetparams - val valueDiscard = atPos(tree.pos)(Block(List(instantiate(tree, mode, WildcardType)), Literal(Constant(())))) + val valueDiscard = atPos(tree.pos)(gen.mkUnitBlock(instantiate(tree, mode, WildcardType))) typed(valueDiscard, mode, UnitTpe) } } diff --git a/src/reflect/scala/reflect/internal/TreeGen.scala b/src/reflect/scala/reflect/internal/TreeGen.scala index ec6426558c..c5038fd1bb 100644 --- a/src/reflect/scala/reflect/internal/TreeGen.scala +++ b/src/reflect/scala/reflect/internal/TreeGen.scala @@ -310,13 +310,16 @@ abstract class TreeGen { /** Builds a tuple */ def mkTuple(elems: List[Tree], flattenUnary: Boolean = true): Tree = elems match { case Nil => - Literal(Constant(())) + mkLiteralUnit case tree :: Nil if flattenUnary => tree case _ => Apply(scalaDot(TupleClass(elems.length).name.toTermName), elems) } + def mkLiteralUnit: Literal = Literal(Constant(())) + def mkUnitBlock(expr: Tree): Block = Block(List(expr), mkLiteralUnit) + def mkTupleType(elems: List[Tree], flattenUnary: Boolean = true): Tree = elems match { case Nil => scalaDot(tpnme.Unit) @@ -395,7 +398,7 @@ abstract class TreeGen { if (body forall treeInfo.isInterfaceMember) None else Some( atPos(wrappingPos(superPos, lvdefs)) ( - DefDef(NoMods, nme.MIXIN_CONSTRUCTOR, Nil, ListOfNil, TypeTree(), Block(lvdefs, Literal(Constant(())))))) + DefDef(NoMods, nme.MIXIN_CONSTRUCTOR, Nil, ListOfNil, TypeTree(), Block(lvdefs, mkLiteralUnit)))) } else { // convert (implicit ... ) to ()(implicit ... ) if it's the only parameter section @@ -409,7 +412,7 @@ abstract class TreeGen { // therefore here we emit a dummy which gets populated when the template is named and typechecked Some( atPos(wrappingPos(superPos, lvdefs ::: vparamss1.flatten).makeTransparent) ( - DefDef(constrMods, nme.CONSTRUCTOR, List(), vparamss1, TypeTree(), Block(lvdefs ::: List(superCall), Literal(Constant(())))))) + DefDef(constrMods, nme.CONSTRUCTOR, List(), vparamss1, TypeTree(), Block(lvdefs ::: List(superCall), mkLiteralUnit)))) } } constr foreach (ensureNonOverlapping(_, parents ::: gvdefs, focus = false)) @@ -481,7 +484,7 @@ abstract class TreeGen { * written by end user. It's important to distinguish the two so that * quasiquotes can strip synthetic ones away. */ - def mkSyntheticUnit() = Literal(Constant(())).updateAttachment(SyntheticUnitAttachment) + def mkSyntheticUnit() = mkLiteralUnit.updateAttachment(SyntheticUnitAttachment) /** Create block of statements `stats` */ def mkBlock(stats: List[Tree], doFlatten: Boolean = true): Tree = -- cgit v1.2.3 From 66b038976d435472b2e5c9720ff2e8cc42177b1a Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 21 Jan 2016 12:55:07 -0800 Subject: Spec updates for Sammy. - Upgrade MathJax to 2.6. This fixes the vertical bar problem on Chrome (https://github.com/mathjax/MathJax/issues/1300); - Disambiguate link to Dynamic Selection; - Consolidate type relations; - Formatting, whitespace and linebreaks; - SAM conversion. --- spec/03-types.md | 99 ++++++++++++-------- spec/06-expressions.md | 222 +++++++++++++++++++------------------------- spec/08-pattern-matching.md | 3 +- spec/_layouts/default.yml | 2 +- 4 files changed, 157 insertions(+), 169 deletions(-) diff --git a/spec/03-types.md b/spec/03-types.md index 94b7916634..16c48bcf6c 100644 --- a/spec/03-types.md +++ b/spec/03-types.md @@ -778,25 +778,22 @@ These notions are defined mutually recursively as follows. ## Relations between types -We define two relations between types. +We define the following relations between types. -|Name | Symbolically |Interpretation | -|-----------------|----------------|-------------------------------------------------| -|Equivalence |$T \equiv U$ |$T$ and $U$ are interchangeable in all contexts. | -|Conformance |$T <: U$ |Type $T$ conforms to type $U$. | +| Name | Symbolically | Interpretation | +|------------------|----------------|----------------------------------------------------| +| Equivalence | $T \equiv U$ | $T$ and $U$ are interchangeable in all contexts. | +| Conformance | $T <: U$ | Type $T$ conforms to ("is a subtype of") type $U$. | +| Weak Conformance | $T <:_w U$ | Augments conformance for primitive numeric types. | +| Compatibility | | Type $T$ conforms to type $U$ after conversions. | ### Equivalence -Equivalence $(\equiv)$ between types is the smallest congruence [^congruence] such that -the following holds: +Equivalence $(\equiv)$ between types is the smallest congruence [^congruence] such that the following holds: -- If $t$ is defined by a type alias `type $t$ = $T$`, then $t$ is - equivalent to $T$. -- If a path $p$ has a singleton type `$q$.type`, then - `$p$.type $\equiv q$.type`. -- If $O$ is defined by an object definition, and $p$ is a path - consisting only of package or object selectors and ending in $O$, then - `$O$.this.type $\equiv p$.type`. +- If $t$ is defined by a type alias `type $t$ = $T$`, then $t$ is equivalent to $T$. +- If a path $p$ has a singleton type `$q$.type`, then `$p$.type $\equiv q$.type`. +- If $O$ is defined by an object definition, and $p$ is a path consisting only of package or object selectors and ending in $O$, then `$O$.this.type $\equiv p$.type`. - Two [compound types](#compound-types) are equivalent if the sequences of their component are pairwise equivalent, and occur in the same order, and their refinements are equivalent. Two refinements are equivalent if they @@ -827,14 +824,11 @@ the following holds: ### Conformance -The conformance relation $(<:)$ is the smallest -transitive relation that satisfies the following conditions. +The conformance relation $(<:)$ is the smallest transitive relation that satisfies the following conditions. - Conformance includes equivalence. If $T \equiv U$ then $T <: U$. - For every value type $T$, `scala.Nothing <: $T$ <: scala.Any`. -- For every type constructor $T$ (with any number of type parameters), - `scala.Nothing <: $T$ <: scala.Any`. - +- For every type constructor $T$ (with any number of type parameters), `scala.Nothing <: $T$ <: scala.Any`. - For every class type $T$ such that `$T$ <: scala.AnyRef` one has `scala.Null <: $T$`. - A type variable or abstract type $t$ conforms to its upper bound and its lower bound conforms to $t$. @@ -912,15 +906,12 @@ type $C'$, if one of the following holds. type declaration `type t[$T_1$ , … , $T_n$] >: L <: U` if $L <: t <: U$. -The $(<:)$ relation forms pre-order between types, -i.e. it is transitive and reflexive. _least upper bounds_ and -_greatest lower bounds_ of a set of types -are understood to be relative to that order. -###### Note -The least upper bound or greatest lower bound -of a set of types does not always exist. For instance, consider -the class definitions +#### Least upper bounds and greatest lower bounds +The $(<:)$ relation forms pre-order between types, i.e. it is transitive and reflexive. +This allows us to define _least upper bounds_ and _greatest lower bounds_ of a set of types in terms of that order. +The least upper bound or greatest lower bound of a set of types does not always exist. +For instance, consider the class definitions: ```scala class A[+T] {} @@ -949,11 +940,9 @@ free to pick any one of them. ### Weak Conformance -In some situations Scala uses a more general conformance relation. A -type $S$ _weakly conforms_ -to a type $T$, written $S <:_w -T$, if $S <: T$ or both $S$ and $T$ are primitive number types -and $S$ precedes $T$ in the following ordering. +In some situations Scala uses a more general conformance relation. +A type $S$ _weakly conforms_ to a type $T$, written $S <:_w T$, +if $S <: T$ or both $S$ and $T$ are primitive number types and $S$ precedes $T$ in the following ordering. ```scala Byte $<:_w$ Short @@ -964,15 +953,49 @@ Long $<:_w$ Float Float $<:_w$ Double ``` -A _weak least upper bound_ is a least upper bound with respect to -weak conformance. +A _weak least upper bound_ is a least upper bound with respect to weak conformance. + +### Compatibility +A type $T$ is _compatible_ to a type $U$ if $T$ (or its corresponding function type) [weakly conforms](#weak-conformance) to $U$ +after applying [eta-expansion](06-expressions.html#eta-expansion). If $T$ is a method type, it's converted to the corresponding function type. If the types do not weakly conform, the following alternatives are checked in order: + - [view application](07-implicits.html#views): there's an implicit view from $T$ to $U$; + - dropping by-name modifiers: if $U$ is of the shape `$=> U'$` (and $T$ is not), `$T <:_w U'$`; + - SAM conversion: if $T$ corresponds to a function type, and $U$ declares a single abstract method whose type [corresponds](06-expressions.html#sam-conversion) to the function type $U'$, `$T <:_w U'$`. + + + +#### Examples + +##### Function compatibility via SAM conversion + +Given the definitions + +``` +def foo(x: Int => String): Unit +def foo(x: ToString): Unit + +trait ToString { def convert(x: Int): String } +``` + +The application `foo(_.toString)` [resolves](06-expressions.html#overloading-resolution) to the first overload, +as it's more specific: + - `Int => String` is compatible to `ToString` -- when expecting a value of type `ToString`, you may pass a function literal from `Int` to `String`, as it will be SAM-converted to said function; + - `ToString` is not compatible to `Int => String` -- when expecting a function from `Int` to `String`, you may not pass a `ToString`. ## Volatile Types -Type volatility approximates the possibility that a type parameter or abstract -type instance -of a type does not have any non-null values. A value member of a volatile type -cannot appear in a [path](#paths). +Type volatility approximates the possibility that a type parameter or +abstract type instance of a type does not have any non-null values. +A value member of a volatile type cannot appear in a [path](#paths). A type is _volatile_ if it falls into one of four categories: diff --git a/spec/06-expressions.md b/spec/06-expressions.md index c24ca01c3b..f69c75bb96 100644 --- a/spec/06-expressions.md +++ b/spec/06-expressions.md @@ -81,10 +81,9 @@ evaluation is immediate. ## The _Null_ Value -The `null` value is of type `scala.Null`, and is thus -compatible with every reference type. It denotes a reference value -which refers to a special “`null`” object. This object -implements methods in class `scala.AnyRef` as follows: +The `null` value is of type `scala.Null`, and thus conforms to every reference type. +It denotes a reference value which refers to a special `null` object. +This object implements methods in class `scala.AnyRef` as follows: - `eq($x\,$)` and `==($x\,$)` return `true` iff the argument $x$ is also the "null" object. @@ -239,38 +238,21 @@ ArgumentExprs ::= `(' [Exprs] `)' Exprs ::= Expr {`,' Expr} ``` -An application `$f$($e_1 , \ldots , e_m$)` applies the -function $f$ to the argument expressions $e_1 , \ldots , e_m$. If $f$ -has a method type `($p_1$:$T_1 , \ldots , p_n$:$T_n$)$U$`, the type of -each argument expression $e_i$ is typed with the -corresponding parameter type $T_i$ as expected type. Let $S_i$ be type -type of argument $e_i$ $(i = 1 , \ldots , m)$. If $f$ is a polymorphic method, -[local type inference](#local-type-inference) is used to determine -type arguments for $f$. If $f$ has some value type, the application is taken to -be equivalent to `$f$.apply($e_1 , \ldots , e_m$)`, -i.e. the application of an `apply` method defined by $f$. - -The function $f$ must be _applicable_ to its arguments $e_1 -, \ldots , e_n$ of types $S_1 , \ldots , S_n$. - -If $f$ has a method type $(p_1:T_1 , \ldots , p_n:T_n)U$ -we say that an argument expression $e_i$ is a _named_ argument if -it has the form $x_i=e'_i$ and $x_i$ is one of the parameter names -$p_1 , \ldots , p_n$. The function $f$ is applicable if all of the following conditions -hold: - -- For every named argument $x_i=e_i'$ the type $S_i$ - is compatible with the parameter type $T_j$ whose name $p_j$ matches $x_i$. -- For every positional argument $e_i$ the type $S_i$ -is compatible with $T_i$. -- If the expected type is defined, the result type $U$ is - compatible to it. - -If $f$ is a polymorphic method it is applicable if -[local type inference](#local-type-inference) can -determine type arguments so that the instantiated method is applicable. If -$f$ has some value type it is applicable if it has a method member named -`apply` which is applicable. +An application `$f(e_1 , \ldots , e_m)$` applies the function `$f$` to the argument expressions `$e_1, \ldots , e_m$`. For this expression to be well-typed, the function must be *applicable* to its arguments, which is defined next by case analysis on $f$'s type. + +If $f$ has a method type `($p_1$:$T_1 , \ldots , p_n$:$T_n$)$U$`, each argument expression $e_i$ is typed with the corresponding parameter type $T_i$ as expected type. Let $S_i$ be the type of argument $e_i$ $(i = 1 , \ldots , m)$. The function $f$ must be _applicable_ to its arguments $e_1, \ldots , e_n$ of types $S_1 , \ldots , S_n$. We say that an argument expression $e_i$ is a _named_ argument if it has the form `$x_i=e'_i$` and `$x_i$` is one of the parameter names `$p_1, \ldots, p_n$`. + +Once the types $S_i$ have been determined, the function $f$ of the above method type is said to be applicable if all of the following conditions hold: + - for every named argument $p_j=e_i'$ the type $S_i$ is [compatible](03-types.html#compatibility) with the parameter type $T_j$; + - for every positional argument $e_i$ the type $S_i$ is [compatible](03-types.html#compatibility) with $T_i$; + - if the expected type is defined, the result type $U$ is [compatible](03-types.html#compatibility) to it. + +If $f$ is a polymorphic method, [local type inference](#local-type-inference) is used to instantiate $f$'s type parameters. +The polymorphic method is applicable if type inference can determine type arguments so that the instantiated method is applicable. + +If $f$ has some value type, the application is taken to be equivalent to `$f$.apply($e_1 , \ldots , e_m$)`, +i.e. the application of an `apply` method defined by $f$. The value `$f$` is applicable to the given arguments if `$f$.apply` is applicable. + Evaluation of `$f$($e_1 , \ldots , e_n$)` usually entails evaluation of $f$ and $e_1 , \ldots , e_n$ in that order. Each argument expression @@ -1141,11 +1123,9 @@ re-thrown. Let $\mathit{pt}$ be the expected type of the try expression. The block $b$ is expected to conform to $\mathit{pt}$. The handler $h$ -is expected conform to type -`scala.PartialFunction[scala.Throwable, $\mathit{pt}\,$]`. The -type of the try expression is the [weak least upper bound](03-types.html#weak-conformance) -of the type of $b$ -and the result type of $h$. +is expected conform to type `scala.PartialFunction[scala.Throwable, $\mathit{pt}\,$]`. +The type of the try expression is the [weak least upper bound](03-types.html#weak-conformance) +of the type of $b$ and the result type of $h$. A try expression `try { $b$ } finally $e$` evaluates the block $b$. If evaluation of $b$ does not cause an exception to be @@ -1178,26 +1158,26 @@ Bindings ::= `(' Binding {`,' Binding} `)' Binding ::= (id | `_') [`:' Type] ``` -The anonymous function `($x_1$: $T_1 , \ldots , x_n$: $T_n$) => e` -maps parameters $x_i$ of types $T_i$ to a result given -by expression $e$. The scope of each formal parameter -$x_i$ is $e$. Formal parameters must have pairwise distinct names. +The anonymous function of arity $n$, `($x_1$: $T_1 , \ldots , x_n$: $T_n$) => e` maps parameters $x_i$ of types $T_i$ to a result given by expression $e$. The scope of each formal parameter $x_i$ is $e$. Formal parameters must have pairwise distinct names. + +In the case of a single untyped formal parameter, `($x\,$) => $e$` can be abbreviated to `$x$ => $e$`. If an anonymous function `($x$: $T\,$) => $e$` with a single typed parameter appears as the result expression of a block, it can be abbreviated to `$x$: $T$ => e`. + +A formal parameter may also be a wildcard represented by an underscore `_`. In that case, a fresh name for the parameter is chosen arbitrarily. + +A named parameter of an anonymous function may be optionally preceded by an `implicit` modifier. In that case the parameter is labeled [`implicit`](07-implicits.html#implicit-parameters-and-views); however the parameter section itself does not count as an [implicit parameter section](07-implicits.html#implicit-parameters). Hence, arguments to anonymous functions always have to be given explicitly. + +### Translation +If the expected type of the anonymous function is of the shape `scala.Function$n$[$S_1 , \ldots , S_n$, $R\,$]`, or can be [SAM-converted](#sam-conversion) to such a function type, the type `$T_i$` of a parameter `$x_i$` can be omitted, as far as `$S_i$` is defined in the expected type, and `$T_i$ = $S_i$` is assumed. Furthermore, the expected type when type checking $e$ is $R$. -If the expected type of the anonymous function is of the form -`scala.Function$n$[$S_1 , \ldots , S_n$, $R\,$]`, the -expected type of $e$ is $R$ and the type $T_i$ of any of the -parameters $x_i$ can be omitted, in which -case`$T_i$ = $S_i$` is assumed. -If the expected type of the anonymous function is -some other type, all formal parameter types must be explicitly given, -and the expected type of $e$ is undefined. The type of the anonymous -function -is`scala.Function$n$[$S_1 , \ldots , S_n$, $T\,$]`, -where $T$ is the [packed type](#expression-typing) -of $e$. $T$ must be equivalent to a -type which does not refer to any of the formal parameters $x_i$. +If there is no expected type for the function literal, all formal parameter types `$T_i$` must be specified explicitly, and the expected type of $e$ is undefined. The type of the anonymous function is `scala.Function$n$[$T_1 , \ldots , T_n$, $R\,$]`, where $R$ is the [packed type](#expression-typing) of $e$. $R$ must be equivalent to a type which does not refer to any of the formal parameters $x_i$. -The anonymous function is evaluated as the instance creation expression +The eventual run-time value of an anonymous function is determined by the expected type: + - a subclass of one of the builtin function types, `scala.Function$n$[$S_1 , \ldots , S_n$, $R\,$]` (with $S_i$ and $R$ fully defined), + - a [single-abstract-method (SAM) type](#sam-conversion); + - `PartialFunction[$T$, $U$]`, if the function literal is of the shape `x => x match { $\ldots$ }` + - some other type. + +The standard anonymous function evaluates in the same way as the following instance creation expression: ```scala new scala.Function$n$[$T_1 , \ldots , T_n$, $T$] { @@ -1205,22 +1185,11 @@ new scala.Function$n$[$T_1 , \ldots , T_n$, $T$] { } ``` -In the case of a single untyped formal parameter, -`($x\,$) => $e$` -can be abbreviated to `$x$ => $e$`. If an -anonymous function `($x$: $T\,$) => $e$` with a single -typed parameter appears as the result expression of a block, it can be -abbreviated to `$x$: $T$ => e`. +The same evaluation holds for a SAM type, except that the instantiated type is given by the SAM type, and the implemented method is the single abstract method member of this type. -A formal parameter may also be a wildcard represented by an underscore `_`. -In that case, a fresh name for the parameter is chosen arbitrarily. +The underlying platform may provide more efficient ways of constructing these instances, such as Java 8's `invokedynamic` bytecode and `LambdaMetaFactory` class. -A named parameter of an anonymous function may be optionally preceded -by an `implicit` modifier. In that case the parameter is -labeled [`implicit`](07-implicits.html#implicit-parameters-and-views); however the -parameter section itself does not count as an implicit parameter -section in the sense defined [here](07-implicits.html#implicit-parameters). Hence, arguments to -anonymous functions always have to be given explicitly. +A `PartialFunction`'s value receives an additional `isDefinedAt` member, which is derived from the pattern match in the function literal, with each case's body being replaced by `true`, and an added default (if none was given) that evaluates to `false`. ###### Example Examples of anonymous functions: @@ -1290,11 +1259,9 @@ include at least the expressions of the following forms: - A string literal - A class constructed with [`Predef.classOf`](12-the-scala-standard-library.html#the-predef-object) - An element of an enumeration from the underlying platform -- A literal array, of the form - `Array$(c_1 , \ldots , c_n)$`, +- A literal array, of the form `Array$(c_1 , \ldots , c_n)$`, where all of the $c_i$'s are themselves constant expressions -- An identifier defined by a - [constant value definition](04-basic-declarations-and-definitions.html#value-declarations-and-definitions). +- An identifier defined by a [constant value definition](04-basic-declarations-and-definitions.html#value-declarations-and-definitions). ## Statements @@ -1335,10 +1302,6 @@ Implicit conversions can be applied to expressions whose type does not match their expected type, to qualifiers in selections, and to unapplied methods. The available implicit conversions are given in the next two sub-sections. -We say, a type $T$ is _compatible_ to a type $U$ if $T$ weakly conforms -to $U$ after applying [eta-expansion](#eta-expansion) and -[view applications](07-implicits.html#views). - ### Value Conversions The following seven implicit conversions can be applied to an @@ -1387,12 +1350,24 @@ If none of the previous conversions applies, and $e$'s type does not conform to the expected type $\mathit{pt}$, it is attempted to convert $e$ to the expected type with a [view](07-implicits.html#views). -###### Dynamic Member Selection +###### Selection on `Dynamic` If none of the previous conversions applies, and $e$ is a prefix of a selection $e.x$, and $e$'s type conforms to class `scala.Dynamic`, then the selection is rewritten according to the rules for [dynamic member selection](#dynamic-member-selection). +###### 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`). + +It follows that: + - the type `S` must have an accessible, no-argument, constructor; + - the class of `S` must not be `@specialized`; + - the class of `S` must not be nested or local (it must not capture its environment). + ### Method Conversions The following four implicit conversions can be applied to methods @@ -1426,34 +1401,31 @@ a function. Let $\mathscr{A}$ be the set of members referenced by $e$. Assume first that $e$ appears as a function in an application, as in `$e$($e_1 , \ldots , e_m$)`. -One first determines the set of functions that is potentially -applicable based on the _shape_ of the arguments. +One first determines the set of functions that is potentially [applicable](#function-applications) +based on the _shape_ of the arguments. -The shape of an argument expression $e$, written $\mathit{shape}(e)$, is +The *shape* of an argument expression $e$, written $\mathit{shape}(e)$, is a type that is defined as follows: + - For a function expression `($p_1$: $T_1 , \ldots , p_n$: $T_n$) => $b$: (Any $, \ldots ,$ Any) => $\mathit{shape}(b)$`, + where `Any` occurs $n$ times in the argument type. + - For a named argument `$n$ = $e$`: $\mathit{shape}(e)$. + - For all other expressions: `Nothing`. -- For a function expression `($p_1$: $T_1 , \ldots , p_n$: $T_n$) => $b$`: - `(Any $, \ldots ,$ Any) => $\mathit{shape}(b)$`, where `Any` occurs $n$ times - in the argument type. -- For a named argument `$n$ = $e$`: $\mathit{shape}(e)$. -- For all other expressions: `Nothing`. - -Let $\mathscr{B}$ be the set of alternatives in $\mathscr{A}$ that are -[_applicable_](#function-applications) -to expressions $(e_1 , \ldots , e_n)$ of types -$(\mathit{shape}(e_1) , \ldots , \mathit{shape}(e_n))$. -If there is precisely one -alternative in $\mathscr{B}$, that alternative is chosen. +Let $\mathscr{B}$ be the set of alternatives in $\mathscr{A}$ that are [_applicable_](#function-applications) +to expressions $(e_1 , \ldots , e_n)$ of types $(\mathit{shape}(e_1) , \ldots , \mathit{shape}(e_n))$. +If there is precisely one alternative in $\mathscr{B}$, that alternative is chosen. Otherwise, let $S_1 , \ldots , S_m$ be the vector of types obtained by typing each argument with an undefined expected type. For every -member $m$ in $\mathscr{B}$ one determines whether it is -applicable to expressions ($e_1 , \ldots , e_m$) of types $S_1 -, \ldots , S_m$. +member $m$ in $\mathscr{B}$ one determines whether it is applicable +to expressions ($e_1 , \ldots , e_m$) of types $S_1, \ldots , S_m$. + It is an error if none of the members in $\mathscr{B}$ is applicable. If there is one single applicable alternative, that alternative is chosen. Otherwise, let $\mathscr{CC}$ be the set of applicable alternatives which don't employ any default argument -in the application to $e_1 , \ldots , e_m$. It is again an error if $\mathscr{CC}$ is empty. +in the application to $e_1 , \ldots , e_m$. + +It is again an error if $\mathscr{CC}$ is empty. Otherwise, one chooses the _most specific_ alternative among the alternatives in $\mathscr{CC}$, according to the following definition of being "as specific as", and "more specific than": @@ -1469,21 +1441,17 @@ question: given so the method is not more specific than the value. --> -- A parameterized method $m$ of type `($p_1:T_1, \ldots , p_n:T_n$)$U$` is _as specific as_ some other - member $m'$ of type $S$ if $m'$ is applicable to arguments - `($p_1 , \ldots , p_n\,$)` of - types $T_1 , \ldots , T_n$. -- A polymorphic method of type - `[$a_1$ >: $L_1$ <: $U_1 , \ldots , a_n$ >: $L_n$ <: $U_n$]$T$` is - as specific as some other member of type $S$ if $T$ is as - specific as $S$ under the assumption that for - $i = 1 , \ldots , n$ each $a_i$ is an abstract type name +- A parameterized method $m$ of type `($p_1:T_1, \ldots , p_n:T_n$)$U$` is + _as specific as_ some other member $m'$ of type $S$ if $m'$ is [applicable](#function-applications) + to arguments `($p_1 , \ldots , p_n$)` of types $T_1 , \ldots , T_n$. +- A polymorphic method of type `[$a_1$ >: $L_1$ <: $U_1 , \ldots , a_n$ >: $L_n$ <: $U_n$]$T$` is + as specific as some other member of type $S$ if $T$ is as specific as $S$ + under the assumption that for $i = 1 , \ldots , n$ each $a_i$ is an abstract type name bounded from below by $L_i$ and from above by $U_i$. -- A member of any other type is always as specific as a parameterized method - or a polymorphic method. -- Given two members of types $T$ and $U$ which are - neither parameterized nor polymorphic method types, the member of type $T$ is as specific as - the member of type $U$ if the existential dual of $T$ conforms to the existential dual of $U$. +- A member of any other type is always as specific as a parameterized method or a polymorphic method. +- Given two members of types $T$ and $U$ which are neither parameterized nor polymorphic method types, + the member of type $T$ is as specific as the member of type $U$ if + the existential dual of $T$ conforms to the existential dual of $U$. Here, the existential dual of a polymorphic type `[$a_1$ >: $L_1$ <: $U_1 , \ldots , a_n$ >: $L_n$ <: $U_n$]$T$` is `$T$ forSome { type $a_1$ >: $L_1$ <: $U_1$ $, \ldots ,$ type $a_n$ >: $L_n$ <: $U_n$}`. @@ -1493,8 +1461,7 @@ The _relative weight_ of an alternative $A$ over an alternative $B$ is a number from 0 to 2, defined as the sum of - 1 if $A$ is as specific as $B$, 0 otherwise, and -- 1 if $A$ is defined in a class or object which is derived - from the class or object defining $B$, 0 otherwise. +- 1 if $A$ is defined in a class or object which is derived from the class or object defining $B$, 0 otherwise. A class or object $C$ is _derived_ from a class or object $D$ if one of the following holds: @@ -1517,15 +1484,13 @@ arguments in $\mathit{targs}$ are chosen. It is an error if no such alternative If there are several such alternatives, overloading resolution is applied again to the whole expression `$e$[$\mathit{targs}\,$]`. -Assume finally that $e$ does not appear as a function in either -an application or a type application. If an expected type is given, -let $\mathscr{B}$ be the set of those alternatives in $\mathscr{A}$ which are -[compatible](#implicit-conversions) to it. Otherwise, let $\mathscr{B}$ be the same -as $\mathscr{A}$. -We choose in this case the most specific alternative among all -alternatives in $\mathscr{B}$. It is an error if there is no -alternative in $\mathscr{B}$ which is more specific than all other -alternatives in $\mathscr{B}$. +Assume finally that $e$ does not appear as a function in either an application or a type application. +If an expected type is given, let $\mathscr{B}$ be the set of those alternatives +in $\mathscr{A}$ which are [compatible](03-types.html#compatibility) to it. +Otherwise, let $\mathscr{B}$ be the same as $\mathscr{A}$. +In this last case we choose the most specific alternative among all alternatives in $\mathscr{B}$. +It is an error if there is no alternative in $\mathscr{B}$ which is +more specific than all other alternatives in $\mathscr{B}$. ###### Example Consider the following definitions: @@ -1552,9 +1517,8 @@ no most specific applicable signature exists. ### Local Type Inference Local type inference infers type arguments to be passed to expressions -of polymorphic type. Say $e$ is of type [$a_1$ >: $L_1$ <: $U_1 -, \ldots , a_n$ >: $L_n$ <: $U_n$]$T$ and no explicit type parameters -are given. +of polymorphic type. Say $e$ is of type [$a_1$ >: $L_1$ <: $U_1, \ldots , a_n$ >: $L_n$ <: $U_n$]$T$ +and no explicit type parameters are given. Local type inference converts this expression to a type application `$e$[$T_1 , \ldots , T_n$]`. The choice of the diff --git a/spec/08-pattern-matching.md b/spec/08-pattern-matching.md index 7e48947639..3b481eea86 100644 --- a/spec/08-pattern-matching.md +++ b/spec/08-pattern-matching.md @@ -654,7 +654,8 @@ or `scala.PartialFunction[$S_1$, $R$]`, where the argument type(s) $S_1 , \ldots , S_k$ must be fully determined, but the result type $R$ may be undetermined. -If the expected type is `scala.Function$k$[$S_1 , \ldots , S_k$, $R$]`, +If the expected type is [SAM-convertible](06-expressions.html#sam-conversion) +to `scala.Function$k$[$S_1 , \ldots , S_k$, $R$]`, the expression is taken to be equivalent to the anonymous function: ```scala diff --git a/spec/_layouts/default.yml b/spec/_layouts/default.yml index 69791d26ad..7e205f8835 100644 --- a/spec/_layouts/default.yml +++ b/spec/_layouts/default.yml @@ -15,7 +15,7 @@ } }); - + -- cgit v1.2.3 From 4f4fb45d7bc41564c3ba0483c5a663172e063994 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 24 Sep 2015 18:48:06 +0300 Subject: SI-9415 Turn on SAM by default Initial work to change settings and test by Svyatoslav Ilinskiy Thanks! To avoid cycles during overload resolution (which showed up during bootstrapping), and to improve performance, I've guarded the detection of SAM types in `isCompatible` to cases when the LHS is potentially compatible. --- src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 2 +- src/compiler/scala/tools/nsc/typechecker/Infer.scala | 2 +- src/reflect/scala/reflect/internal/Definitions.scala | 4 +--- test/files/neg/sammy_error_exist_no_crash.flags | 1 - test/files/neg/sammy_restrictions.flags | 1 - test/files/neg/sammy_wrong_arity.flags | 1 - test/files/pos/sammy_exist.flags | 1 - test/files/pos/sammy_overload.flags | 1 - test/files/pos/sammy_override.flags | 1 - test/files/pos/sammy_poly.flags | 1 - test/files/pos/sammy_scope.flags | 1 - test/files/pos/sammy_single.flags | 1 - test/files/pos/sammy_twice.flags | 1 - test/files/pos/t8310.flags | 1 - test/files/run/sammy_java8.flags | 1 - test/files/run/sammy_repeated.flags | 1 - 16 files changed, 3 insertions(+), 18 deletions(-) delete mode 100644 test/files/neg/sammy_error_exist_no_crash.flags delete mode 100644 test/files/neg/sammy_restrictions.flags delete mode 100644 test/files/neg/sammy_wrong_arity.flags delete mode 100644 test/files/pos/sammy_exist.flags delete mode 100644 test/files/pos/sammy_overload.flags delete mode 100644 test/files/pos/sammy_override.flags delete mode 100644 test/files/pos/sammy_poly.flags delete mode 100644 test/files/pos/sammy_scope.flags delete mode 100644 test/files/pos/sammy_single.flags delete mode 100644 test/files/pos/sammy_twice.flags delete mode 100644 test/files/pos/t8310.flags delete mode 100644 test/files/run/sammy_java8.flags delete mode 100644 test/files/run/sammy_repeated.flags diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index c6c9545391..807e0cc72f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -262,7 +262,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { else { // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an // empty parameter list in later phases and would therefore be picked as SAM. - val samSym = exitingPickler(definitions.findSam(classSym.tpe)) + val samSym = exitingPickler(definitions.samOf(classSym.tpe)) if (samSym == NoSymbol) None else Some(samSym.javaSimpleName.toString + methodSymToDescriptor(samSym)) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 684cf788a4..84cf3c6475 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -295,7 +295,7 @@ trait Infer extends Checkable { && !isByNameParamType(tp) && isCompatible(tp, dropByName(pt)) ) - def isCompatibleSam(tp: Type, pt: Type): Boolean = { + def isCompatibleSam(tp: Type, pt: Type): Boolean = (definitions.isFunctionType(tp) || tp.isInstanceOf[MethodType] || tp.isInstanceOf[PolyType]) && { val samFun = typer.samToFunctionType(pt) (samFun ne NoType) && isCompatible(tp, samFun) } diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 48caf3081a..a031f4f797 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -809,9 +809,7 @@ trait Definitions extends api.StandardDefinitions { * The class defining the method is a supertype of `tp` that * has a public no-arg primary constructor. */ - def samOf(tp: Type): Symbol = if (!settings.Xexperimental) NoSymbol else findSam(tp) - - def findSam(tp: Type): Symbol = { + def samOf(tp: Type): Symbol = { // 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 tpSym = tp.typeSymbol diff --git a/test/files/neg/sammy_error_exist_no_crash.flags b/test/files/neg/sammy_error_exist_no_crash.flags deleted file mode 100644 index e1b37447c9..0000000000 --- a/test/files/neg/sammy_error_exist_no_crash.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental \ No newline at end of file diff --git a/test/files/neg/sammy_restrictions.flags b/test/files/neg/sammy_restrictions.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/neg/sammy_restrictions.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/neg/sammy_wrong_arity.flags b/test/files/neg/sammy_wrong_arity.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/neg/sammy_wrong_arity.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/sammy_exist.flags b/test/files/pos/sammy_exist.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/sammy_exist.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/sammy_overload.flags b/test/files/pos/sammy_overload.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/sammy_overload.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/sammy_override.flags b/test/files/pos/sammy_override.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/sammy_override.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/sammy_poly.flags b/test/files/pos/sammy_poly.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/sammy_poly.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/sammy_scope.flags b/test/files/pos/sammy_scope.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/sammy_scope.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/sammy_single.flags b/test/files/pos/sammy_single.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/sammy_single.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/sammy_twice.flags b/test/files/pos/sammy_twice.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/sammy_twice.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/t8310.flags b/test/files/pos/t8310.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/t8310.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/run/sammy_java8.flags b/test/files/run/sammy_java8.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/run/sammy_java8.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/run/sammy_repeated.flags b/test/files/run/sammy_repeated.flags deleted file mode 100644 index e1b37447c9..0000000000 --- a/test/files/run/sammy_repeated.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental \ No newline at end of file -- cgit v1.2.3 From 236d0e0db8b4c53837bd580e2b2022d86afde456 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 19 Jan 2016 18:47:10 -0800 Subject: Refactor typedFunction, rework synthesizeSAMFunction for sammy `typedFunction` uniformly recognizes Single Abstract Method types and built-in `FunctionN` types, type checking literals regardless of expected type. `adapt` synthesizes an anonymous subclass of the SAM type, if needed to meet the expected (non-`FunctionN`) type. (Later, we may want to carry `Function` AST nodes with SAM types through the whole pipeline until the back-end, and treat them uniformly with built-in function types there too, emitting the corresponding `invokedynamic` & `LambdaMetaFactory` bytecode. Would be faster to avoid synthesizing all this code during type checking...) Refactor `typedFunction` for performance and clarity to avoid non-local returns. A nice perk is that the error message for missing argument types now indicates with `` where they are missing (see updated check file). Allow pattern matching function literals when SAM type is expected (SI-8429). Support `return` in function body of SAM target type, by making the synthetic `sam$body` method transparent to the `enclMethod` chain, so that the `return` is interpreted in its original context. A cleaner approach to inferring unknown type params of the SAM method. Now that `synthesizeSAMFunction` operates on typed `Function` nodes, we can take the types of the parameters and the body and compare them against the function type that corresponds to the SAM method's signature. Since we are reusing the typed body, we do need to change owners for the symbols, and substitute the new method argument symbols for the function's vparam syms. Impl Notes: - The shift from typing as a regular Function for SAM types was triggered by limitation of the old approach, which deferred type checking the body until it was in the synthetic SAM type subclass, which would break if the expression was subsequently retypechecked for implicit search. Other problems related to SAM expansion in ctor args also are dodged now. - Using `<:<`, not `=:=`, in comparing `pt`, as `=:=` causes `NoInstance` exceptions when `WildcardType`s are encountered. - Can't use method type subtyping: method arguments are in invariant pos. - Can't use STATIC yet, results in illegal bytecode. It would be a better encoding, since the function body should not see members of SAM class. - This is all battle tested by running `synthesizeSAMFunction` on all `Function` nodes while bootstrapping, including those where a regular function type is expected. The only thing that didn't work was regarding Function0 and the CBN transform, which breaks outer path creation in lambdalift. --- .../scala/tools/nsc/typechecker/Contexts.scala | 3 +- .../scala/tools/nsc/typechecker/Typers.scala | 374 ++++++++++----------- .../scala/reflect/internal/Definitions.scala | 11 + src/reflect/scala/reflect/internal/StdNames.scala | 1 + test/files/neg/names-defaults-neg.check | 6 +- test/files/neg/sammy_error_exist_no_crash.check | 4 +- test/files/pos/sam_ctor_arg.scala | 4 + test/files/pos/sam_infer_argtype_subtypes.scala | 6 + test/files/pos/sam_inferargs.scala | 6 + test/files/pos/sammy_implicit.scala | 10 + test/files/pos/t8429.scala | 7 + test/files/run/sam_return.scala | 14 + 12 files changed, 251 insertions(+), 195 deletions(-) create mode 100644 test/files/pos/sam_ctor_arg.scala create mode 100644 test/files/pos/sam_infer_argtype_subtypes.scala create mode 100644 test/files/pos/sam_inferargs.scala create mode 100644 test/files/pos/sammy_implicit.scala create mode 100644 test/files/pos/t8429.scala create mode 100644 test/files/run/sam_return.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index c5a3d605b1..bdcf90681d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -478,7 +478,8 @@ trait Contexts { self: Analyzer => c(ConstructorSuffix) = !isTemplateOrPackage && c(ConstructorSuffix) // SI-8245 `isLazy` need to skip lazy getters to ensure `return` binds to the right place - c.enclMethod = if (isDefDef && !owner.isLazy) c else enclMethod + // similarly for the synthetic method that holds as a SAM's body (as synthesized by `synthesizeSAMFunction`) + c.enclMethod = if (isDefDef && !(owner.isLazy || owner.name.endsWith(nme.SAM_BODY_SUFFIX))) c else enclMethod if (tree != outer.tree) c(TypeConstructorAllowed) = false diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index dd0c9fe945..383953f27e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1055,6 +1055,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (hasUndets) return instantiate(tree, mode, pt) + // we know `!(tree.tpe <:< pt)`, try to remedy if there's a sam for pt + val sam = if (tree.isInstanceOf[Function] && !isFunctionType(pt)) samOf(pt) else NoSymbol + if (sam.exists && sameLength(sam.info.params, tree.asInstanceOf[Function].vparams)) { + // Use synthesizeSAMFunction to expand `(p1: T1, ..., pN: TN) => body` + // to an instance of the corresponding anonymous subclass of `pt`. + val samTree = synthesizeSAMFunction(sam, tree.asInstanceOf[Function], pt, mode) + if (samTree ne EmptyTree) return samTree + } + if (context.implicitsEnabled && !pt.isError && !tree.isErrorTyped) { // (14); the condition prevents chains of views debuglog("inferring view from " + tree.tpe + " to " + pt) @@ -2713,184 +2722,161 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } - /** Synthesize and type check the implementation of a type with a Single Abstract Method - * - * `{ (p1: T1, ..., pN: TN) => body } : S` - * - * expands to (where `S` is the expected type that defines a single abstract method named `apply`) - * - * `{ - * def apply$body(p1: T1, ..., pN: TN): T = body - * new S { - * def apply(p1: T1', ..., pN: TN'): T' = apply$body(p1,..., pN) - * } - * }` - * - * If 'T' is not fully defined, it is inferred by type checking - * `apply$body` without a result type before type checking the block. - * The method's inferred result type is used instead of `T`. [See test/files/pos/sammy_poly.scala] - * - * The `apply` method is identified by the argument `sam`; `S` corresponds to the argument `samClassTp`, - * and `resPt` is derived from `samClassTp` -- it may be fully defined, or not... - * If it is not fully defined, we derive `samClassTpFullyDefined` by inferring any unknown type parameters. - * - * The types T1' ... TN' and T' are derived from the method signature of the sam method, - * as seen from the fully defined `samClassTpFullyDefined`. - * - * The function's body is put in a method outside of the class definition to enforce scoping. - * S's members should not be in scope in `body`. - * - * The restriction on implicit arguments (neither S's constructor, nor sam may take an implicit argument list), - * is largely to keep the implementation of type inference (the computation of `samClassTpFullyDefined`) simple. - * - * NOTE: it would be nicer to not have to type check `apply$body` separately when `T` is not fully defined. - * However T must be fully defined before we type the instantiation, as it'll end up as a parent type, - * which must be fully defined. Would be nice to have some kind of mechanism to insert type vars in a block of code, - * and have the instantiation of the first occurrence propagate to the rest of the block. - * - * TODO: by-name params - * scala> trait LazySink { def accept(a: => Any): Unit } - * defined trait LazySink - * - * scala> val f: LazySink = (a) => (a, a) - * f: LazySink = $anonfun$1@1fb26910 - * - * scala> f(println("!")) - * :10: error: LazySink does not take parameters - * f(println("!")) - * ^ - * - * scala> f.accept(println("!")) - * ! - * ! - */ - def synthesizeSAMFunction(sam: Symbol, fun: Function, resPt: Type, samClassTp: Type, mode: Mode): Tree = { - // assert(fun.vparams forall (vp => isFullyDefined(vp.tpt.tpe))) -- by construction, as we take them from sam's info - val sampos = fun.pos - - // if the expected sam type is fully defined, use it for the method's result type - // otherwise, NoType, so that type inference will determine the method's result type - // resPt is syntactically contained in samClassTp, so if the latter is fully defined, so is the former - // ultimately, we want to fully define samClassTp as it is used as the superclass of our anonymous class - val samDefTp = if (isFullyDefined(resPt)) resPt else NoType - val bodyName = newTermName(sam.name + "$body") - - // `def '${sam.name}\$body'($p1: $T1, ..., $pN: $TN): $resPt = $body` - val samBodyDef = - DefDef(NoMods, - bodyName, - Nil, - List(fun.vparams.map(_.duplicate)), // must duplicate as we're also using them for `samDef` - TypeTree(samDefTp) setPos sampos.focus, - fun.body) + /** Synthesize and type check the implementation of a type with a Single Abstract Method. + * + * Based on a type checked Function node `{ (p1: T1, ..., pN: TN) => body } : S` + * where `S` is the expected type that defines a single abstract method (call it `apply` for the example), + * that has signature `(p1: T1', ..., pN: TN'): T'`, synthesize the instantiation of the following anonymous class + * + * ``` + * new S { + * def apply$body(p1: T1, ..., pN: TN): T = body + * def apply(p1: T1', ..., pN: TN'): T' = apply$body(p1,..., pN) + * } + * ``` + * + * The `apply` method is identified by the argument `sam`; `S` corresponds to the argument `pt`, + * If `pt` is not fully defined, we derive `samClassTpFullyDefined` by inferring any unknown type parameters. + * + * The types T1' ... TN' and T' are derived from the method signature of the sam method, + * as seen from the fully defined `samClassTpFullyDefined`. + * + * The function's body is put in a static method in the class definition to enforce scoping. + * S's members should not be in scope in `body`. (Putting it in the block outside the class runs into implementation problems described below) + * + * The restriction on implicit arguments (neither S's constructor, nor sam may take an implicit argument list), + * is largely to keep the implementation of type inference (the computation of `samClassTpFullyDefined`) simple. + * + */ + def synthesizeSAMFunction(sam: Symbol, fun: Function, pt: Type, mode: Mode): Tree = { + // `fun` has a FunctionType, but the expected type `pt` is some SAM type -- let's remedy that + // `fun` is fully attributed, so we'll have to wrangle some symbols into shape (owner change, vparam syms) + // I tried very hard to leave `fun` untyped and rework everything into the right shape and type check once, + // but couldn't make it work due to retyping that happens down the line + // (implicit conversion retries/retypechecking, CBN transform, super call arg context nesting weirdness) + val sampos = fun.pos.focus - // If we need to enter the sym for the body def before type checking the block, - // we'll create a nested context, as explained below. - var nestedTyper = this // Type check body def before classdef to fully determine samClassTp (if necessary). // As `samClassTp` determines a parent type for the class, // we can't type check `block` in one go unless `samClassTp` is fully defined. - val samClassTpFullyDefined = - if (isFullyDefined(samClassTp)) samClassTp + val ptFullyDefined = + if (isFullyDefined(pt) && !isNonRefinementClassType(unwrapToClass(pt))) pt else try { - // This creates a symbol for samBodyDef with a type completer that'll be triggered immediately below. - // The symbol is entered in the same scope used for the block below, and won't thus be reentered later. - // It has to be a new scope, though, or we'll "get ambiguous reference to overloaded definition" [pos/sammy_twice.scala] - // makeSilent: [pos/nonlocal-unchecked.scala -- when translation all functions to sams] - val nestedCtx = enterSym(context.makeNewScope(context.tree, context.owner).makeSilent(), samBodyDef) - nestedTyper = newTyper(nestedCtx) - - // NOTE: this `samBodyDef.symbol.info` runs the type completer set up by the enterSym above - val actualSamType = samBodyDef.symbol.info + val samClassSym = pt.typeSymbol // we're trying to fully define the type arguments for this type constructor - val samTyCon = samClassTp.typeSymbol.typeConstructor + val samTyCon = samClassSym.typeConstructor // the unknowns - val tparams = samClassTp.typeSymbol.typeParams + val tparams = samClassSym.typeParams // ... as typevars val tvars = tparams map freshVar - // 1. Recover partial information: - // - derive a type from samClassTp that has the corresponding tparams for type arguments that aren't fully defined - // - constrain typevars to be equal to type args that are fully defined - val samClassTpMoreDefined = appliedType(samTyCon, - (samClassTp.typeArgs, tparams, tvars).zipped map { - case (a, _, tv) if isFullyDefined(a) => tv =:= a; a - case (_, p, _) => p.typeConstructor - }) + val ptVars = appliedType(samTyCon, tvars) + + // carry over info from pt + ptVars <:< pt - // the method type we're expecting the synthesized sam to have, based on the expected sam type, - // where fully defined type args to samClassTp have been preserved, - // with the unknown args replaced by their corresponding type param - val expectedSamType = samClassTpMoreDefined.memberInfo(sam) + val samInfoWithTVars = ptVars.memberInfo(sam) - // 2. make sure the body def's actual type (formals and result) conforms to - // sam's expected type (in terms of the typevars that represent the sam's class's type params) - actualSamType <:< expectedSamType.substituteTypes(tparams, tvars) + // use function type subtyping, not method type subtyping (the latter is invariant in argument types) + fun.tpe <:< functionType(samInfoWithTVars.paramTypes, samInfoWithTVars.finalResultType) + + val variances = tparams map varianceInType(sam.info) // solve constraints tracked by tvars - val targs = solvedTypes(tvars, tparams, tparams map varianceInType(sam.info), upper = false, lubDepth(sam.info :: Nil)) + val targs = solvedTypes(tvars, tparams, variances, upper = false, lubDepth(sam.info :: Nil)) - debuglog(s"sam infer: $samClassTp --> ${appliedType(samTyCon, targs)} by $actualSamType <:< $expectedSamType --> $targs for $tparams") + debuglog(s"sam infer: $pt --> ${appliedType(samTyCon, targs)} by ${fun.tpe} <:< $samInfoWithTVars --> $targs for $tparams") // a fully defined samClassTp appliedType(samTyCon, targs) } catch { - case _: NoInstance | _: TypeError => - devWarning(sampos, s"Could not define type $samClassTp using ${samBodyDef.symbol.rawInfo} <:< ${samClassTp memberInfo sam} (for $sam)") - samClassTp + case e@(_: NoInstance | _: TypeError) => // TODO: we get here whenever pt contains a wildcardtype??? + debuglog(s"Could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)\n$e") + return EmptyTree } + debuglog(s"sam fully defined expected type: $ptFullyDefined from $pt for ${fun.tpe}") + + val samFunTp = { + val samDefinedTp = ptFullyDefined memberInfo sam + functionType(samDefinedTp.paramTypes, samDefinedTp.finalResultType) + } + if (!(fun.tpe <:< samFunTp)) return EmptyTree + + + // TODO: defer the remainder of this synthesis to the back-end, and use invokedynamic/LMF instead + // can we carry a Function node down the pipeline if it has type `ptFullyDefined`, which is not a `FunctionN`? + val bodyName = (sam.name append nme.SAM_BODY_SUFFIX).toTermName + + // drop symbol info + val samBodyDefParams = fun.vparams.map { vd => ValDef(vd.mods, vd.name, vd.tpt, vd.rhs) } + + // `def '${sam.name}\$body'($p1: $T1, ..., $pN: $TN): $resPt = $body` + // + // TODO: make this behave like a static method during type checking to enforce scoping: + // the sam class's self reference should not be in scope here. + // To survive retyping, we must package samBodyDef as a member of the sam's class, however. + // Scoping is not a problem (regardless of whether STATIC works), as the body has already been type checked in typedFunction. + // + // (Lifting samBodyDef into the surrounding block cause several problems: + // when the tree is in a ctor arg / implicitly converted in multiple tries --> lambda lift crash "Could not find proxy...") + // + // the rhs is already typed, so it doesn't matter it uses the wrong parameter symbols (must wait until block is typed before we can fix them) + val samBodyDef = + DefDef(NoMods, // TODO: Modifiers(STATIC) when the backend supports it (currently it causes VerifyErrors) + bodyName, + Nil, + List(samBodyDefParams), + TypeTree(fun.body.tpe) setPos sampos, + fun.body) + // what's the signature of the method that we should actually be overriding? - val samMethTp = samClassTpFullyDefined memberInfo sam - // Before the mutation, `tp <:< vpar.tpt.tpe` should hold. - // TODO: error message when this is not the case, as the expansion won't type check - // - Ti' <:< Ti and T <: T' must hold for the samDef body to type check - val funArgTps = foreach2(samMethTp.paramTypes, fun.vparams)((tp, vpar) => vpar.tpt setType tp) + val samMethType = ptFullyDefined memberInfo sam + + // drop symbol info, use type info from sam so we implement the right method + val samDefParams = map2(fun.vparams, samMethType.paramTypes) { (vd, tp) => ValDef(vd.mods, vd.name, TypeTree(tp), vd.rhs) } // `final override def ${sam.name}($p1: $T1', ..., $pN: $TN'): ${samMethTp.finalResultType} = ${sam.name}\$body'($p1, ..., $pN)` val samDef = DefDef(Modifiers(FINAL | OVERRIDE | SYNTHETIC), sam.name.toTermName, Nil, - List(fun.vparams), - TypeTree(samMethTp.finalResultType) setPos sampos.focus, - Apply(Ident(bodyName), fun.vparams map gen.paramToArg) + List(samDefParams), + TypeTree(samMethType.finalResultType) setPos sampos, + Apply(Ident(bodyName), fun.vparams.map(gen.paramToArg)) ) - val serializableParentAddendum = - if (typeIsSubTypeOfSerializable(samClassTp)) Nil - else List(TypeTree(SerializableTpe)) - + val samSubclassName = tpnme.ANON_FUN_NAME val classDef = - ClassDef(Modifiers(FINAL), tpnme.ANON_FUN_NAME, tparams = Nil, + ClassDef(Modifiers(FINAL), samSubclassName, tparams = Nil, gen.mkTemplate( - parents = TypeTree(samClassTpFullyDefined) :: serializableParentAddendum, + parents = TypeTree(ptFullyDefined) :: (if (typeIsSubTypeOfSerializable(pt)) Nil else List(TypeTree(SerializableTpe))), self = noSelfType, constrMods = NoMods, vparamss = ListOfNil, - body = List(samDef), - superPos = sampos.focus + body = List(samBodyDef, samDef), + superPos = sampos ) ) // type checking the whole block, so that everything is packaged together nicely // and we don't have to create any symbols by hand val block = - nestedTyper.typedPos(sampos, mode, samClassTpFullyDefined) { - Block( - samBodyDef, - classDef, - Apply(Select(New(Ident(tpnme.ANON_FUN_NAME)), nme.CONSTRUCTOR), Nil) - ) + typedPos(sampos, mode, pt) { + Block(classDef, Apply(Select(New(Ident(samSubclassName)), nme.CONSTRUCTOR), Nil)) } + // fix owner for parts of the function we reused now that samBodyDef has a symbol + samBodyDef.rhs.changeOwner((fun.symbol, samBodyDef.symbol)) + samBodyDef.rhs.substituteSymbols(fun.vparams.map(_.symbol), samBodyDef.vparamss.head.map(_.symbol)) + // TODO: improve error reporting -- when we're in silent mode (from `silent(_.doTypedApply(tree, fun, args, mode, pt)) orElse onError`) // the errors in the function don't get out... if (block exists (_.isErroneous)) - context.error(fun.pos, s"Could not derive subclass of $samClassTp\n (with SAM `def $sam$samMethTp`)\n based on: $fun.") + context.error(fun.pos, s"Could not derive subclass of $pt\n (with SAM `${sam.defStringSeenAs(ptFullyDefined)}`)\n based on: $fun.") classDef.symbol addAnnotation SerialVersionUIDAnnotation block @@ -2908,11 +2894,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (numVparams > definitions.MaxFunctionArity) NoSymbol else FunctionClass(numVparams) + val ptSym = pt.typeSymbol + /* The Single Abstract Member of pt, unless pt is the built-in function type of the expected arity, * as `(a => a): Int => Int` should not (yet) get the sam treatment. */ val sam = - if (pt.typeSymbol == FunctionSymbol) NoSymbol + if (ptSym == NoSymbol || ptSym == FunctionSymbol || ptSym == PartialFunctionClass) NoSymbol else samOf(pt) /* The SAM case comes first so that this works: @@ -2921,78 +2909,87 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * * Note that the arity of the sam must correspond to the arity of the function. */ - val samViable = sam.exists && sameLength(sam.info.params, fun.vparams) - val ptNorm = if (samViable) samToFunctionType(pt, sam) else pt + val ptNorm = + if (sam.exists && sameLength(sam.info.params, fun.vparams)) samToFunctionType(pt, sam) + else pt val (argpts, respt) = ptNorm baseType FunctionSymbol match { case TypeRef(_, FunctionSymbol, args :+ res) => (args, res) - case _ => (fun.vparams map (_ => if (pt == ErrorType) ErrorType else NoType), WildcardType) + case _ => (fun.vparams map (if (pt == ErrorType) (_ => ErrorType) else (_ => NoType)), WildcardType) } - if (!FunctionSymbol.exists) - MaxFunctionArityError(fun) - else if (argpts.lengthCompare(numVparams) != 0) - WrongNumberOfParametersError(fun, argpts) + + // if the function is `(a1: T1, ..., aN: TN) => fun(a1,..., aN)`, where Ti are not all fully defined, + // type `fun` directly + def typeUnEtaExpanded: Type = fun match { + case etaExpansion(_, fn, _) => + silent(_.typed(fn, mode.forFunMode, pt)) filter (_ => context.undetparams.isEmpty) map { fn1 => + // if context.undetparams is not empty, the function was polymorphic, + // so we need the missing arguments to infer its type. See #871 + //println("typing eta "+fun+":"+fn1.tpe+"/"+context.undetparams) + val ftpe = normalize(fn1.tpe) baseType FunctionClass(numVparams) + if (isFunctionType(ftpe) && isFullyDefined(ftpe)) ftpe + else NoType + } orElse { _ => NoType } + case _ => NoType + } + + if (!FunctionSymbol.exists) MaxFunctionArityError(fun) + else if (argpts.lengthCompare(numVparams) != 0) WrongNumberOfParametersError(fun, argpts) else { - var issuedMissingParameterTypeError = false + val paramsMissingType = mutable.ArrayBuffer.empty[ValDef] //.sizeHint(numVparams) probably useless, since initial size is 16 and max fun arity is 22 + // first, try to define param types from expected function's arg types if needed foreach2(fun.vparams, argpts) { (vparam, argpt) => - if (vparam.tpt.isEmpty) { - val vparamType = - if (isFullyDefined(argpt)) argpt - else { - fun match { - case etaExpansion(vparams, fn, args) => - silent(_.typed(fn, mode.forFunMode, pt)) filter (_ => context.undetparams.isEmpty) map { fn1 => - // if context.undetparams is not empty, the function was polymorphic, - // so we need the missing arguments to infer its type. See #871 - //println("typing eta "+fun+":"+fn1.tpe+"/"+context.undetparams) - val ftpe = normalize(fn1.tpe) baseType FunctionClass(numVparams) - if (isFunctionType(ftpe) && isFullyDefined(ftpe)) - return typedFunction(fun, mode, ftpe) - } - case _ => - } - MissingParameterTypeError(fun, vparam, pt, withTupleAddendum = !issuedMissingParameterTypeError) - issuedMissingParameterTypeError = true - ErrorType - } - vparam.tpt.setType(vparamType) + if (vparam.tpt isEmpty) { + if (isFullyDefined(argpt)) vparam.tpt setType argpt + else paramsMissingType += vparam + if (!vparam.tpt.pos.isDefined) vparam.tpt setPos vparam.pos.focus } } - fun.body match { - // translate `x => x match { }` : PartialFunction to - // `new PartialFunction { def applyOrElse(x, default) = x match { } def isDefinedAt(x) = ... }` - case Match(sel, cases) if (sel ne EmptyTree) && (pt.typeSymbol == PartialFunctionClass) => - // go to outer context -- must discard the context that was created for the Function since we're discarding the function - // thus, its symbol, which serves as the current context.owner, is not the right owner - // you won't know you're using the wrong owner until lambda lift crashes (unless you know better than to use the wrong owner) - val outerTyper = newTyper(context.outer) - val p = fun.vparams.head - if (p.tpt.tpe == null) p.tpt setType outerTyper.typedType(p.tpt).tpe + // if we had missing param types, see if we can undo eta-expansion and recover type info + val expectedFunTypeBeforeEtaExpansion = + if (paramsMissingType.isEmpty) NoType + else typeUnEtaExpanded + + if (expectedFunTypeBeforeEtaExpansion ne NoType) typedFunction(fun, mode, expectedFunTypeBeforeEtaExpansion) + else { + // we ran out of things to try, missing parameter types are an irrevocable error + var issuedMissingParameterTypeError = false + paramsMissingType.foreach { vparam => + vparam.tpt setType ErrorType + MissingParameterTypeError(fun, vparam, pt, withTupleAddendum = !issuedMissingParameterTypeError) + issuedMissingParameterTypeError = true + } - outerTyper.synthesizePartialFunction(p.name, p.pos, paramSynthetic = false, fun.body, mode, pt) + fun.body match { + // translate `x => x match { }` : PartialFunction to + // `new PartialFunction { def applyOrElse(x, default) = x match { } def isDefinedAt(x) = ... }` + case Match(sel, cases) if (sel ne EmptyTree) && (pt.typeSymbol == PartialFunctionClass) => + // go to outer context -- must discard the context that was created for the Function since we're discarding the function + // thus, its symbol, which serves as the current context.owner, is not the right owner + // you won't know you're using the wrong owner until lambda lift crashes (unless you know better than to use the wrong owner) + val outerTyper = newTyper(context.outer) + val p = fun.vparams.head + if (p.tpt.tpe == null) p.tpt setType outerTyper.typedType(p.tpt).tpe - // Use synthesizeSAMFunction to expand `(p1: T1, ..., pN: TN) => body` - // to an instance of the corresponding anonymous subclass of `pt`. - case _ if samViable => - newTyper(context.outer).synthesizeSAMFunction(sam, fun, respt, pt, mode) + outerTyper.synthesizePartialFunction(p.name, p.pos, paramSynthetic = false, fun.body, mode, pt) - // regular Function - case _ => - val vparamSyms = fun.vparams map { vparam => - enterSym(context, vparam) - if (context.retyping) context.scope enter vparam.symbol - vparam.symbol - } - val vparams = fun.vparams mapConserve typedValDef - val formals = vparamSyms map (_.tpe) - val body1 = typed(fun.body, respt) - val restpe = packedType(body1, fun.symbol).deconst.resultType - val funtpe = phasedAppliedType(FunctionSymbol, formals :+ restpe) + case _ => + val vparamSyms = fun.vparams map { vparam => + enterSym(context, vparam) + if (context.retyping) context.scope enter vparam.symbol + vparam.symbol + } + val vparams = fun.vparams mapConserve typedValDef + val formals = vparamSyms map (_.tpe) + val body1 = typed(fun.body, respt) + val restpe = packedType(body1, fun.symbol).deconst.resultType + val funtpe = phasedAppliedType(FunctionSymbol, formals :+ restpe) - treeCopy.Function(fun, vparams, body1) setType funtpe + treeCopy.Function(fun, vparams, body1) setType funtpe + } } } } @@ -4303,7 +4300,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (pt.typeSymbol == PartialFunctionClass) synthesizePartialFunction(newTermName(context.unit.fresh.newName("x")), tree.pos, paramSynthetic = true, tree, mode, pt) else { - val arity = if (isFunctionType(pt)) pt.dealiasWiden.typeArgs.length - 1 else 1 + val arity = functionArityFromType(pt) match { case -1 => 1 case arity => arity } // SI-8429: consider sam and function type equally in determining function arity + val params = for (i <- List.range(0, arity)) yield atPos(tree.pos.focusStart) { ValDef(Modifiers(PARAM | SYNTHETIC), diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index a031f4f797..425502d25f 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -675,6 +675,17 @@ trait Definitions extends api.StandardDefinitions { // Note that these call .dealiasWiden and not .normalize, the latter of which // tends to change the course of events by forcing types. def isFunctionType(tp: Type) = isFunctionTypeDirect(tp.dealiasWiden) + // the number of arguments expected by the function described by `tp` (a FunctionN or SAM type), + // or `-1` if `tp` does not represent a function type or SAM + def functionArityFromType(tp: Type) = { + val dealiased = tp.dealiasWiden + if (isFunctionTypeDirect(dealiased)) dealiased.typeArgs.length - 1 + else samOf(tp) match { + case samSym if samSym.exists => samSym.info.params.length + case _ => -1 + } + } + def isTupleType(tp: Type) = isTupleTypeDirect(tp.dealiasWiden) def tupleComponents(tp: Type) = tp.dealiasWiden.typeArgs diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 6abd7f2f19..25498b73ce 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -348,6 +348,7 @@ trait StdNames { val REIFY_FREE_THIS_SUFFIX: NameType = "$this" val REIFY_FREE_VALUE_SUFFIX: NameType = "$value" val REIFY_SYMDEF_PREFIX: NameType = "symdef$" + val SAM_BODY_SUFFIX: NameType = "$body" val QUASIQUOTE_CASE: NameType = "$quasiquote$case$" val QUASIQUOTE_EARLY_DEF: NameType = "$quasiquote$early$def$" val QUASIQUOTE_FILE: String = "" diff --git a/test/files/neg/names-defaults-neg.check b/test/files/neg/names-defaults-neg.check index 8a6aafd67a..875bc2ade0 100644 --- a/test/files/neg/names-defaults-neg.check +++ b/test/files/neg/names-defaults-neg.check @@ -130,7 +130,7 @@ names-defaults-neg.scala:102: error: unknown parameter name: m names-defaults-neg.scala:135: error: reference to var2 is ambiguous; it is both a method parameter and a variable in scope. delay(var2 = 40) ^ -names-defaults-neg.scala:138: error: missing parameter type for expanded function ((x$1) => a = x$1) +names-defaults-neg.scala:138: error: missing parameter type for expanded function ((x$1: ) => a = x$1) val taf2: Int => Unit = testAnnFun(a = _, b = get("+")) ^ names-defaults-neg.scala:138: error: not found: value a @@ -142,10 +142,10 @@ names-defaults-neg.scala:138: error: not found: value get names-defaults-neg.scala:139: error: parameter 'a' is already specified at parameter position 1 val taf3 = testAnnFun(b = _: String, a = get(8)) ^ -names-defaults-neg.scala:140: error: missing parameter type for expanded function ((x$3) => testAnnFun(x$3, ((x$4) => b = x$4))) +names-defaults-neg.scala:140: error: missing parameter type for expanded function ((x$3: ) => testAnnFun(x$3, ((x$4) => b = x$4))) val taf4: (Int, String) => Unit = testAnnFun(_, b = _) ^ -names-defaults-neg.scala:140: error: missing parameter type for expanded function ((x$4) => b = x$4) +names-defaults-neg.scala:140: error: missing parameter type for expanded function ((x$4: ) => b = x$4) val taf4: (Int, String) => Unit = testAnnFun(_, b = _) ^ names-defaults-neg.scala:140: error: not found: value b diff --git a/test/files/neg/sammy_error_exist_no_crash.check b/test/files/neg/sammy_error_exist_no_crash.check index a0d2237ce0..944b6471fd 100644 --- a/test/files/neg/sammy_error_exist_no_crash.check +++ b/test/files/neg/sammy_error_exist_no_crash.check @@ -1,6 +1,4 @@ -sammy_error_exist_no_crash.scala:5: error: Could not derive subclass of F[? >: String] - (with SAM `def method apply(s: String)Int`) - based on: ((x$1: String) => x$1.). +sammy_error_exist_no_crash.scala:5: error: value parseInt is not a member of String bar(_.parseInt) ^ one error found diff --git a/test/files/pos/sam_ctor_arg.scala b/test/files/pos/sam_ctor_arg.scala new file mode 100644 index 0000000000..3c556d59f0 --- /dev/null +++ b/test/files/pos/sam_ctor_arg.scala @@ -0,0 +1,4 @@ +trait Fun[A, B] { def apply(a: A): B } +// can't do sam expansion until the sam body def is a static method in the sam class, and not a local method in a block' +class C(f: Fun[Int, String]) +class Test extends C(s => "a") \ No newline at end of file diff --git a/test/files/pos/sam_infer_argtype_subtypes.scala b/test/files/pos/sam_infer_argtype_subtypes.scala new file mode 100644 index 0000000000..63966f879e --- /dev/null +++ b/test/files/pos/sam_infer_argtype_subtypes.scala @@ -0,0 +1,6 @@ +trait Fun[A, B] { def apply(a: A): B } + +class SamInferResult { + def foreach[U](f: Fun[String, U]): U = ??? + def foo = foreach(println) +} \ No newline at end of file diff --git a/test/files/pos/sam_inferargs.scala b/test/files/pos/sam_inferargs.scala new file mode 100644 index 0000000000..10d9b4f0dd --- /dev/null +++ b/test/files/pos/sam_inferargs.scala @@ -0,0 +1,6 @@ +trait Proc { def apply(): Unit } +class Test { + val initCode = List[Proc]() + initCode foreach { proc => proc() } + +} diff --git a/test/files/pos/sammy_implicit.scala b/test/files/pos/sammy_implicit.scala new file mode 100644 index 0000000000..c9c2519bab --- /dev/null +++ b/test/files/pos/sammy_implicit.scala @@ -0,0 +1,10 @@ +abstract class SamImplicitConvert { + trait Fun[A, B] { def apply(a: A): B } + class Lst[T] + abstract class Str { def getBytes: Array[Int] } + def flatMap[B](f: Fun[Str, Lst[B]]): List[B] = ??? + + implicit def conv(xs: Array[Int]): Lst[Int] + + val encoded = flatMap (_.getBytes) +} diff --git a/test/files/pos/t8429.scala b/test/files/pos/t8429.scala new file mode 100644 index 0000000000..a2d32637e1 --- /dev/null +++ b/test/files/pos/t8429.scala @@ -0,0 +1,7 @@ +trait Must { def musta(str: String, i: Int): Unit } + +object Mustare { + def takesM(m: Must) = ??? + takesM{ (a, b) => println } // ok + takesM{ case (a: String, b: Int) => println("") } // should also be accepted +} diff --git a/test/files/run/sam_return.scala b/test/files/run/sam_return.scala new file mode 100644 index 0000000000..e959619dd1 --- /dev/null +++ b/test/files/run/sam_return.scala @@ -0,0 +1,14 @@ +trait Fun[A, B] { def apply(a: A): B } +class PF[A, B] { def runWith[U](action: Fun[B, U]): Fun[A, Boolean] = a => {action(a.asInstanceOf[B]); true} } + +class TO[A](x: A) { + def foreach[U](f: Fun[A, U]): U = f(x) + def collectFirst[B](pf: PF[A, B]): Option[B] = { + foreach(pf.runWith(b => return Some(b))) + None + } +} + +object Test extends App { + assert(new TO("a").collectFirst(new PF[String, String]).get == "a") +} \ No newline at end of file -- cgit v1.2.3 From 0d89ab1fe4adfe7165a2e3812d76dbb586c79798 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 4 Feb 2016 15:26:57 -0800 Subject: SI-9449 sam expansion for explicitly eta-expanded method --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 12 ++++++++++-- test/files/pos/t9449.scala | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 test/files/pos/t9449.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 383953f27e..5a62c32d2e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2908,6 +2908,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * (a => a): MyFun * * Note that the arity of the sam must correspond to the arity of the function. + * TODO: handle vararg sams? */ val ptNorm = if (sam.exists && sameLength(sam.info.params, fun.vparams)) samToFunctionType(pt, sam) @@ -4407,6 +4408,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper tp } + def expectingFunctionMatchingFormals(formals: List[Symbol]) = + isFunctionType(pt) || { + val sam = samOf(pt) + // TODO: handle vararg sam here and in typedFunction + sam.exists && sameLength(sam.info.params, formals) + } + def typedEta(expr1: Tree): Tree = expr1.tpe match { case TypeRef(_, ByNameParamClass, _) => val expr2 = Function(List(), expr1) setPos expr1.pos @@ -4417,10 +4425,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper new ChangeOwnerTraverser(context.owner, expr2.symbol).traverse(expr2) typed1(expr2, mode, pt) case PolyType(_, MethodType(formals, _)) => - if (isFunctionType(pt)) expr1 + if (expectingFunctionMatchingFormals(formals)) expr1 else adapt(expr1, mode, functionTypeWildcard(expr1, formals.length)) case MethodType(formals, _) => - if (isFunctionType(pt)) expr1 + if (expectingFunctionMatchingFormals(formals)) expr1 else adapt(expr1, mode, functionTypeWildcard(expr1, formals.length)) case ErrorType => expr1 diff --git a/test/files/pos/t9449.scala b/test/files/pos/t9449.scala new file mode 100644 index 0000000000..06653cca83 --- /dev/null +++ b/test/files/pos/t9449.scala @@ -0,0 +1,19 @@ +trait II { + def apply(x: Int): Int +} + +object Test { + def ii(x: Int): Int = x + def test = { + val ii1: II = x => ii(x) // works + val ii2: II = ii // works (adapting `ii` to `II`) + val ii3: II = ii _ // fails -- should work + // typedTyped({ii : (() => )}) + // typedEta(ii, pt = II) + // adapt(ii, pt = (? => ?)) + // instantiatedToMethodType(ii, pt = (? => ?)) + // val ii3: II = ii _ // error: + // found : Int => Int + // required: II + } +} \ No newline at end of file -- cgit v1.2.3 From ad90614c391328bdc9d13795d26f339f67e7f4d5 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 5 Feb 2016 13:33:39 -0800 Subject: Refactoring. Sweep Sammy's backyard. --- src/compiler/scala/tools/nsc/ast/TreeGen.scala | 46 +++-- .../scala/tools/nsc/transform/Delambdafy.scala | 200 ++++++++++----------- .../scala/tools/nsc/transform/UnCurry.scala | 132 ++++---------- .../scala/tools/nsc/typechecker/Typers.scala | 39 ++-- .../scala/reflect/internal/Definitions.scala | 5 + 5 files changed, 190 insertions(+), 232 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index 7edac76b91..7b0216a6d7 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -278,26 +278,44 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { * @param additionalFlags flags to be put on the method in addition to FINAL */ def mkMethodFromFunction(localTyper: analyzer.Typer) - (fun: Function, owner: Symbol, name: TermName, additionalFlags: FlagSet = NoFlags) = { - val funParams = fun.vparams map (_.symbol) - val formals :+ restpe = fun.tpe.typeArgs + (owner: Symbol, fun: Function, name: TermName = nme.apply, additionalFlags: FlagSet = NoFlags) = { + val funParamSyms = fun.vparams.map(_.symbol) val methSym = owner.newMethod(name, fun.pos, FINAL | additionalFlags) + val methParamSyms = funParamSyms.map { param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName) } - val paramSyms = map2(formals, fun.vparams) { - (tp, vparam) => methSym.newSyntheticValueParam(tp, vparam.name) - } + val body = fun.body + body substituteSymbols (funParamSyms, methParamSyms) + body changeOwner ((fun.symbol, methSym)) - methSym setInfo MethodType(paramSyms, restpe.deconst) + // Have to repack the type to avoid mismatches when existentials + // appear in the result - see SI-4869. + val resTp = localTyper.packedType(body, methSym).deconst + methSym setInfo MethodType(methParamSyms, fun.tpe.typeArgs.last.deconst) // TODO: use `resTp` -- preserving wrong status quo because refactoring-only - fun.body.substituteSymbols(funParams, paramSyms) - fun.body changeOwner (fun.symbol -> methSym) + newDefDef(methSym, body)(tpt = TypeTree(resTp)) + } - val methDef = DefDef(methSym, fun.body) + def functionClassType(fun: Function): Type = + abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.tpe.typeArgs.last.deconst) // TODO: use `fun.body.tpe.deconst` -- preserving wrong status quo because refactoring-only - // Have to repack the type to avoid mismatches when existentials - // appear in the result - see SI-4869. - methDef.tpt setType localTyper.packedType(fun.body, methSym).deconst - methDef + def expandFunction(localTyper: analyzer.Typer)(fun: Function, inConstructorFlag: Long): Tree = { + val parents = addSerializable(functionClassType(fun)) + val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation + + // The original owner is used in the backend for the EnclosingMethod attribute. If fun is + // nested in a value-class method, its owner was already changed to the extension method. + // Saving the original owner allows getting the source structure from the class symbol. + defineOriginalOwner(anonClass, fun.symbol.originalOwner) + anonClass setInfo ClassInfoType(parents, newScope, anonClass) + + val applyMethodDef = mkMethodFromFunction(localTyper)(anonClass, fun) + anonClass.info.decls enter applyMethodDef.symbol + + localTyper.typedPos(fun.pos) { + Block( + ClassDef(anonClass, NoMods, ListOfNil, List(applyMethodDef), fun.pos), + Typed(New(anonClass.tpe), TypeTree(fun.tpe))) + } } } diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 8799ed2e04..43d24be4d5 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -29,6 +29,8 @@ 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) + override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = { if (settings.Ydelambdafy.value == "method") new Phase(prev) else new SkipPhase(prev) @@ -46,9 +48,9 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre val typer = localTyper - // we need to know which methods refer to the 'this' reference so that we can determine - // which lambdas need access to it - val thisReferringMethods: Set[Symbol] = { + // we need to know which methods refer to the 'this' reference so that we can determine which lambdas need access to it + // TODO: this looks expensive, so I made it a lazy val. Can we make it more pay-as-you-go / optimize for common shapes? + lazy val thisReferringMethods: Set[Symbol] = { val thisReferringMethodsTraverser = new ThisReferringMethodsTraverser() thisReferringMethodsTraverser traverse unit.body val methodReferringMap = thisReferringMethodsTraverser.liftedMethodReferences @@ -92,92 +94,79 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre super.transformStats(stats, exprOwner) ++ lambdaClassDefs.remove(exprOwner).getOrElse(Nil) } - private def optionSymbol(sym: Symbol): Option[Symbol] = if (sym.exists) Some(sym) else None + def createBoxingBridgeMethod(oldClass: Symbol, target: Symbol, functionParamTypes: List[Type], functionResultType: Type, pos: Position): Symbol = { + // Note: we bail out of this method and return EmptyTree if we find there is no adaptation required. + // If we need to improve performance, we could check the types first before creating the + // method and parameter symbols. + val methSym = oldClass.newMethod(target.name.append("$adapted").toTermName, target.pos, target.flags | FINAL | ARTIFACT) + var neededAdaptation = false + def boxedType(tpe: Type): Type = { + if (isPrimitiveValueClass(tpe.typeSymbol)) {neededAdaptation = true; ObjectTpe} + else if (enteringErasure(tpe.typeSymbol.isDerivedValueClass)) {neededAdaptation = true; ObjectTpe} + else tpe + } + val targetParams: List[Symbol] = target.paramss.head + val numCaptures = targetParams.length - functionParamTypes.length + val (targetCaptureParams, targetFunctionParams) = targetParams.splitAt(numCaptures) + val bridgeParams: List[Symbol] = + targetCaptureParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName)) ::: + map2(targetFunctionParams, functionParamTypes)((param, tp) => methSym.newSyntheticValueParam(boxedType(tp), param.name.toTermName)) + + val bridgeResultType: Type = { + if (target.info.resultType == UnitTpe && functionResultType != UnitTpe) { + neededAdaptation = true + ObjectTpe + } else + boxedType(functionResultType) + } + val methodType = MethodType(bridgeParams, bridgeResultType) + methSym setInfo methodType + if (!neededAdaptation) target + else { + val bridgeParamTrees = bridgeParams.map(ValDef(_)) + + oldClass.info.decls enter methSym + + val body = localTyper.typedPos(pos) { + val newTarget = Select(gen.mkAttributedThis(oldClass), target) + val args: List[Tree] = mapWithIndex(bridgeParams) { (param, i) => + if (i < numCaptures) { + gen.mkAttributedRef(param) + } else { + val functionParam = functionParamTypes(i - numCaptures) + val targetParam = targetParams(i) + if (enteringErasure(functionParam.typeSymbol.isDerivedValueClass)) { + val casted = cast(gen.mkAttributedRef(param), functionParam) + val unboxed = unbox(casted, ErasedValueType(functionParam.typeSymbol, targetParam.tpe)).modifyType(postErasure.elimErasedValueType) + unboxed + } else adaptToType(gen.mkAttributedRef(param), targetParam.tpe) + } + } + gen.mkMethodCall(newTarget, args) + } + val body1 = if (enteringErasure(functionResultType.typeSymbol.isDerivedValueClass)) + adaptToType(box(body.setType(ErasedValueType(functionResultType.typeSymbol, body.tpe)), "boxing lambda target"), bridgeResultType) + else adaptToType(body, bridgeResultType) + val methDef0 = DefDef(methSym, List(bridgeParamTrees), body1) + val bridge = postErasure.newTransformer(unit).transform(methDef0).asInstanceOf[DefDef] + boxingBridgeMethods += bridge + bridge.symbol + } + } // turns a lambda into a new class def, a New expression instantiating that class private def transformFunction(originalFunction: Function): Tree = { - val formals = originalFunction.vparams.map(_.tpe) - val restpe = originalFunction.body.tpe.deconst val oldClass = originalFunction.symbol.enclClass - - // find which variables are free in the lambda because those are captures that need to be - // passed into the constructor of the anonymous function class - val captures = FreeVarTraverser.freeVarsOf(originalFunction) + val arity = originalFunction.vparams.length val target = targetMethod(originalFunction) target.makeNotPrivate(target.owner) + if (!thisReferringMethods.contains(target)) target setFlag STATIC val isStatic = target.hasFlag(STATIC) - def createBoxingBridgeMethod(functionParamTypes: List[Type], functionResultType: Type): Tree = { - // Note: we bail out of this method and return EmptyTree if we find there is no adaptation required. - // If we need to improve performance, we could check the types first before creating the - // method and parameter symbols. - val methSym = oldClass.newMethod(target.name.append("$adapted").toTermName, target.pos, target.flags | FINAL | ARTIFACT) - var neededAdaptation = false - def boxedType(tpe: Type): Type = { - if (isPrimitiveValueClass(tpe.typeSymbol)) {neededAdaptation = true; ObjectTpe} - else if (enteringErasure(tpe.typeSymbol.isDerivedValueClass)) {neededAdaptation = true; ObjectTpe} - else tpe - } - val targetParams: List[Symbol] = target.paramss.head - val numCaptures = targetParams.length - functionParamTypes.length - val (targetCaptureParams, targetFunctionParams) = targetParams.splitAt(numCaptures) - val bridgeParams: List[Symbol] = - targetCaptureParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName)) ::: - map2(targetFunctionParams, functionParamTypes)((param, tp) => methSym.newSyntheticValueParam(boxedType(tp), param.name.toTermName)) - - val bridgeResultType: Type = { - if (target.info.resultType == UnitTpe && functionResultType != UnitTpe) { - neededAdaptation = true - ObjectTpe - } else - boxedType(functionResultType) - } - val methodType = MethodType(bridgeParams, bridgeResultType) - methSym setInfo methodType - if (!neededAdaptation) - EmptyTree - else { - val bridgeParamTrees = bridgeParams.map(ValDef(_)) - - oldClass.info.decls enter methSym - - val body = localTyper.typedPos(originalFunction.pos) { - val newTarget = Select(gen.mkAttributedThis(oldClass), target) - val args: List[Tree] = mapWithIndex(bridgeParams) { (param, i) => - if (i < numCaptures) { - gen.mkAttributedRef(param) - } else { - val functionParam = functionParamTypes(i - numCaptures) - val targetParam = targetParams(i) - if (enteringErasure(functionParam.typeSymbol.isDerivedValueClass)) { - val casted = cast(gen.mkAttributedRef(param), functionParam) - val unboxed = unbox(casted, ErasedValueType(functionParam.typeSymbol, targetParam.tpe)).modifyType(postErasure.elimErasedValueType) - unboxed - } else adaptToType(gen.mkAttributedRef(param), targetParam.tpe) - } - } - gen.mkMethodCall(newTarget, args) - } - val body1 = if (enteringErasure(functionResultType.typeSymbol.isDerivedValueClass)) - adaptToType(box(body.setType(ErasedValueType(functionResultType.typeSymbol, body.tpe)), "boxing lambda target"), bridgeResultType) - else adaptToType(body, bridgeResultType) - val methDef0 = DefDef(methSym, List(bridgeParamTrees), body1) - postErasure.newTransformer(unit).transform(methDef0).asInstanceOf[DefDef] - } - } - - val allCaptureArgs: List[Tree] = { - val thisArg = if (isStatic) Nil else (gen.mkAttributedThis(oldClass) setPos originalFunction.pos) :: Nil - val captureArgs = captures.iterator.map(capture => gen.mkAttributedRef(capture) setPos originalFunction.pos).toList - thisArg ::: captureArgs - } - - val arity = originalFunction.vparams.length - // Reconstruct the type of the function entering erasure. // We do this by taking the type after erasure, and re-boxing `ErasedValueType`. // @@ -211,21 +200,29 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre val lambdaTarget = if (isSpecialized) target - else createBoxingBridgeMethod(functionParamTypes, functionResultType) match { - case EmptyTree => target - case bridge => boxingBridgeMethods += bridge; bridge.symbol + else createBoxingBridgeMethod(oldClass, target, functionParamTypes, functionResultType, originalFunction.pos) + + // We then apply this symbol to the captures. + val apply = { + val allCaptureArgs: List[Tree] = { + // find which variables are free in the lambda because those are captures that need to be + // passed into the constructor of the anonymous function class + val captures = FreeVarTraverser.freeVarsOf(originalFunction) + val thisArg = if (isStatic) Nil else (gen.mkAttributedThis(oldClass) setPos originalFunction.pos) :: Nil + val captureArgs = captures.iterator.map(capture => gen.mkAttributedRef(capture) setPos originalFunction.pos).toList + thisArg ::: captureArgs } - // Create a symbol representing a fictional lambda factory method that accepts the captured - // arguments and returns a Function. - val msym = { - val meth = currentOwner.newMethod(nme.ANON_FUN_NAME, originalFunction.pos, ARTIFACT) - val capturedParams = meth.newSyntheticValueParams(allCaptureArgs.map(_.tpe)) - meth.setInfo(MethodType(capturedParams, functionType)) - } + // Create a symbol representing a fictional lambda factory method that accepts the captured + // arguments and returns a Function. + val msym = { + val meth = currentOwner.newMethod(nme.ANON_FUN_NAME, originalFunction.pos, ARTIFACT) + val capturedParams = meth.newSyntheticValueParams(allCaptureArgs.map(_.tpe)) + meth.setInfo(MethodType(capturedParams, functionType)) + } - // We then apply this symbol to the captures. - val apply = localTyper.typedPos(originalFunction.pos)(Apply(Ident(msym), allCaptureArgs)).asInstanceOf[Apply] + localTyper.typedPos(originalFunction.pos)(Apply(Ident(msym), allCaptureArgs)).asInstanceOf[Apply] + } // 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. @@ -235,6 +232,17 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre } } // DelambdafyTransformer + /** + * Get the symbol of the target lifted lambda body method from a function. I.e. if + * the function is {args => anonfun(args)} then this method returns anonfun's symbol + */ + private def targetMethod(fun: Function): Symbol = fun match { + case Function(_, Apply(target, _)) => target.symbol + case _ => + // any other shape of Function is unexpected at this point + abort(s"could not understand function with tree $fun") + } + // A traverser that finds symbols used but not defined in the given Tree // TODO freeVarTraverser in LambdaLift does a very similar task. With some // analysis this could probably be unified with it @@ -267,18 +275,6 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre } } - /** - * Get the symbol of the target lifted lambda body method from a function. I.e. if - * the function is {args => anonfun(args)} then this method returns anonfun's symbol - */ - private def targetMethod(fun: Function): Symbol = fun match { - case Function(_, Apply(target, _)) => - target.symbol - case _ => - // any other shape of Function is unexpected at this point - abort(s"could not understand function with tree $fun") - } - // finds all methods that reference 'this' class ThisReferringMethodsTraverser() extends Traverser { private var currentMethod: Symbol = NoSymbol @@ -307,6 +303,4 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre super.traverse(tree) } } - - final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol) } diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 5a75332881..c142c96e66 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -7,6 +7,7 @@ package scala package tools.nsc package transform +import scala.annotation.tailrec import scala.language.postfixOps import symtab.Flags._ @@ -65,7 +66,7 @@ abstract class UnCurry extends InfoTransform // uncurry and uncurryType expand type aliases class UnCurryTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { - private val inlineFunctionExpansion = settings.Ydelambdafy.value == "inline" + private val forceExpandFunction = settings.Ydelambdafy.value == "inline" private var needTryLift = false private var inConstructorFlag = 0L private val byNameArgs = mutable.HashSet[Tree]() @@ -73,7 +74,7 @@ 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 = inlineFunctionExpansion || (inConstructorFlag != 0) + private def mustExpandFunction = forceExpandFunction || inConstructorFlag != 0 /** Add a new synthetic member for `currentOwner` */ private def addNewMember(t: Tree): Unit = @@ -83,25 +84,17 @@ abstract class UnCurry extends InfoTransform @inline private def useNewMembers[T](owner: Symbol)(f: List[Tree] => T): T = f(newMembers.remove(owner).getOrElse(Nil).toList) - private def newFunction0(body: Tree): Tree = { - val result = localTyper.typedPos(body.pos)(Function(Nil, body)).asInstanceOf[Function] - log("Change owner from %s to %s in %s".format(currentOwner, result.symbol, result.body)) - result.body changeOwner (currentOwner -> result.symbol) - transformFunction(result) - } - // I don't have a clue why I'm catching TypeErrors here, but it's better // than spewing stack traces at end users for internal errors. Examples // which hit at this point should not be hard to come by, but the immediate // motivation can be seen in continuations-neg/t3718. - override def transform(tree: Tree): Tree = ( + override def transform(tree: Tree): Tree = try postTransform(mainTransform(tree)) catch { case ex: TypeError => reporter.error(ex.pos, ex.msg) debugStack(ex) EmptyTree } - ) /* Is tree a reference `x` to a call by name parameter that needs to be converted to * x.apply()? Note that this is not the case if `x` is used as an argument to another @@ -110,7 +103,7 @@ abstract class UnCurry extends InfoTransform def isByNameRef(tree: Tree) = ( tree.isTerm && (tree.symbol ne null) - && (isByName(tree.symbol)) + && isByName(tree.symbol) && !byNameArgs(tree) ) @@ -187,16 +180,6 @@ abstract class UnCurry extends InfoTransform // ------ Transforming anonymous functions and by-name-arguments ---------------- - /** Undo eta expansion for parameterless and nullary methods */ - def deEta(fun: Function): Tree = fun match { - case Function(List(), expr) if isByNameRef(expr) => - noApply += expr - expr - case _ => - fun - } - - /** Transform a function node (x_1,...,x_n) => body of type FunctionN[T_1, .., T_N, R] to * * class $anon() extends AbstractFunctionN[T_1, .., T_N, R] with Serializable { @@ -205,57 +188,21 @@ abstract class UnCurry extends InfoTransform * new $anon() * */ - def transformFunction(fun: Function): Tree = { - fun.tpe match { - // can happen when analyzer plugins assign refined types to functions, e.g. - // (() => Int) { def apply(): Int @typeConstraint } - case RefinedType(List(funTp), decls) => - debuglog(s"eliminate refinement from function type ${fun.tpe}") - fun.setType(funTp) - case _ => - () - } + 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 { + // method definition with the same arguments, return type, and body as the original lambda + val liftedMethod = gen.mkMethodFromFunction(localTyper)(fun.symbol.owner, fun, nme.ANON_FUN_NAME, ARTIFACT) - deEta(fun) match { - // nullary or parameterless - case fun1 if fun1 ne fun => fun1 - case _ => - def typedFunPos(t: Tree) = localTyper.typedPos(fun.pos)(t) - val funParams = fun.vparams map (_.symbol) - def mkMethod(owner: Symbol, name: TermName, additionalFlags: FlagSet = NoFlags): DefDef = - gen.mkMethodFromFunction(localTyper)(fun, owner, name, additionalFlags) - - if (mustExpandFunction) { - assert(isFunctionType(fun.tpe), s"Not a FunctionN? $fun: ${fun.tpe}") - val funTpArgs = fun.tpe.typeArgs - val parents = addSerializable(abstractFunctionType(funTpArgs.init, funTpArgs.last)) - val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation - // The original owner is used in the backend for the EnclosingMethod attribute. If fun is - // nested in a value-class method, its owner was already changed to the extension method. - // Saving the original owner allows getting the source structure from the class symbol. - defineOriginalOwner(anonClass, fun.symbol.originalOwner) - anonClass setInfo ClassInfoType(parents, newScope, anonClass) - - val applyMethodDef = mkMethod(anonClass, nme.apply) - anonClass.info.decls enter applyMethodDef.symbol - - typedFunPos { - Block( - ClassDef(anonClass, NoMods, ListOfNil, List(applyMethodDef), fun.pos), - Typed(New(anonClass.tpe), TypeTree(fun.tpe))) - } - } else { - // method definition with the same arguments, return type, and body as the original lambda - val liftedMethod = mkMethod(fun.symbol.owner, nme.ANON_FUN_NAME, additionalFlags = ARTIFACT) - - // new function whose body is just a call to the lifted method - val newFun = deriveFunction(fun)(_ => typedFunPos( - gen.mkForwarder(gen.mkAttributedRef(liftedMethod.symbol), funParams :: Nil) - )) - typedFunPos(Block(liftedMethod, super.transform(newFun))) - } - } - } + // new function whose body is just a call to the lifted method + val newFun = deriveFunction(fun)(_ => localTyper.typedPos(fun.pos)( + gen.mkForwarder(gen.mkAttributedRef(liftedMethod.symbol), (fun.vparams map (_.symbol)) :: Nil) + )) + + localTyper.typedPos(fun.pos)(Block(liftedMethod, super.transform(newFun))) + } def transformArgs(pos: Position, fun: Symbol, args: List[Tree], formals: List[Type]) = { val isJava = fun.isJavaDefined @@ -333,25 +280,22 @@ abstract class UnCurry extends InfoTransform val args1 = if (isVarArgTypes(formals)) transformVarargs(formals.last.typeArgs.head) else args map2(formals, args1) { (formal, arg) => - if (!isByNameParamType(formal)) - arg - else if (isByNameRef(arg)) { + if (!isByNameParamType(formal)) arg + else if (isByNameRef(arg)) { // thunk does not need to be forced because it's a reference to a by-name arg passed to a by-name param byNameArgs += arg arg setType functionType(Nil, arg.tpe) - } - else { + } else { log(s"Argument '$arg' at line ${arg.pos.line} is $formal from ${fun.fullName}") - def canUseDirectly(recv: Tree) = ( - recv.tpe.typeSymbol.isSubClass(FunctionClass(0)) - && treeInfo.isExprSafeToInline(recv) - ) + def canUseDirectly(qual: Tree) = qual.tpe.typeSymbol.isSubClass(FunctionClass(0)) && treeInfo.isExprSafeToInline(qual) arg match { // don't add a thunk for by-name argument if argument already is an application of // a Function0. We can then remove the application and use the existing Function0. - case Apply(Select(recv, nme.apply), Nil) if canUseDirectly(recv) => - recv - case _ => - newFunction0(arg) + case Apply(Select(qual, nme.apply), Nil) if canUseDirectly(qual) => qual + case body => + val thunkFun = localTyper.typedPos(body.pos)(Function(Nil, body)).asInstanceOf[Function] + log(s"Change owner from $currentOwner to ${thunkFun.symbol} in ${thunkFun.body}") + thunkFun.body.changeOwner((currentOwner, thunkFun.symbol)) + transformFunction(thunkFun) } } } @@ -422,9 +366,10 @@ abstract class UnCurry extends InfoTransform val sym = tree.symbol // true if the target is a lambda body that's been lifted into a method - def isLiftedLambdaBody(target: Tree) = target.symbol.isLocalToBlock && target.symbol.isArtifact && target.symbol.name.containsName(nme.ANON_FUN_NAME) + def isLiftedLambdaMethod(funSym: Symbol) = + funSym.isArtifact && funSym.name.containsName(nme.ANON_FUN_NAME) && funSym.isLocalToBlock - val result = ( + val result = if ((sym ne null) && sym.elisionLevel.exists(_ < settings.elidebelow.value)) replaceElidableTree(tree) else translateSynchronized(tree) match { @@ -477,7 +422,7 @@ abstract class UnCurry extends InfoTransform case Assign(lhs, _) if lhs.symbol.owner != currentMethod || lhs.symbol.hasFlag(LAZY | ACCESSOR) => withNeedLift(needLift = true) { super.transform(tree) } - case ret @ Return(_) if (isNonLocalReturn(ret)) => + case ret @ Return(_) if isNonLocalReturn(ret) => withNeedLift(needLift = true) { super.transform(ret) } case Try(_, Nil, _) => @@ -496,7 +441,7 @@ abstract class UnCurry extends InfoTransform treeCopy.CaseDef(tree, pat1, transform(guard), transform(body)) // if a lambda is already the right shape we don't need to transform it again - case fun @ Function(_, Apply(target, _)) if (!inlineFunctionExpansion) && isLiftedLambdaBody(target) => + case fun @ Function(_, Apply(target, _)) if !forceExpandFunction && isLiftedLambdaMethod(target.symbol) => super.transform(fun) case fun @ Function(_, _) => @@ -516,9 +461,8 @@ abstract class UnCurry extends InfoTransform } tree1 } - ) - assert(result.tpe != null, result.shortClass + " tpe is null:\n" + result) - result modifyType uncurry + + result.setType(uncurry(result.tpe)) } def postTransform(tree: Tree): Tree = exitingUncurry { @@ -537,7 +481,7 @@ abstract class UnCurry extends InfoTransform tree } - def isThrowable(pat: Tree): Boolean = pat match { + @tailrec def isThrowable(pat: Tree): Boolean = pat match { case Typed(Ident(nme.WILDCARD), tpt) => tpt.tpe =:= ThrowableTpe case Bind(_, pat) => @@ -609,7 +553,7 @@ abstract class UnCurry extends InfoTransform case Select(_, _) | TypeApply(_, _) => applyUnary() case ret @ Return(expr) if isNonLocalReturn(ret) => - log("non-local return from %s to %s".format(currentOwner.enclMethod, ret.symbol)) + log(s"non-local return from ${currentOwner.enclMethod} to ${ret.symbol}") atPos(ret.pos)(nonLocalReturnThrow(expr, ret.symbol)) case TypeTree() => tree diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 5a62c32d2e..d9d68f0773 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1055,9 +1055,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (hasUndets) return instantiate(tree, mode, pt) - // we know `!(tree.tpe <:< pt)`, try to remedy if there's a sam for pt + // we know `!(tree.tpe <:< pt)`; try to remedy if there's a sam for pt val sam = if (tree.isInstanceOf[Function] && !isFunctionType(pt)) samOf(pt) else NoSymbol - if (sam.exists && sameLength(sam.info.params, tree.asInstanceOf[Function].vparams)) { + if (sam.exists && samMatchesFunctionBasedOnArity(sam, tree.asInstanceOf[Function].vparams)) { // Use synthesizeSAMFunction to expand `(p1: T1, ..., pN: TN) => body` // to an instance of the corresponding anonymous subclass of `pt`. val samTree = synthesizeSAMFunction(sam, tree.asInstanceOf[Function], pt, mode) @@ -2800,10 +2800,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper debuglog(s"sam fully defined expected type: $ptFullyDefined from $pt for ${fun.tpe}") - val samFunTp = { - val samDefinedTp = ptFullyDefined memberInfo sam - functionType(samDefinedTp.paramTypes, samDefinedTp.finalResultType) - } + // what's the signature of the method that we should actually be overriding? + val samMethType = ptFullyDefined memberInfo sam + val samFunTp = functionType(samMethType.paramTypes, samMethType.finalResultType) + + // TODO: should we preemptively check that ptFullyDefined is a valid class type? + // (to avoid running into type errors when type checking the instantiation below) if (!(fun.tpe <:< samFunTp)) return EmptyTree @@ -2833,9 +2835,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper TypeTree(fun.body.tpe) setPos sampos, fun.body) - // what's the signature of the method that we should actually be overriding? - val samMethType = ptFullyDefined memberInfo sam - // drop symbol info, use type info from sam so we implement the right method val samDefParams = map2(fun.vparams, samMethType.paramTypes) { (vd, tp) => ValDef(vd.mods, vd.name, TypeTree(tp), vd.rhs) } @@ -2911,7 +2910,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * TODO: handle vararg sams? */ val ptNorm = - if (sam.exists && sameLength(sam.info.params, fun.vparams)) samToFunctionType(pt, sam) + if (samMatchesFunctionBasedOnArity(sam, fun.vparams)) samToFunctionType(pt, sam) else pt val (argpts, respt) = ptNorm baseType FunctionSymbol match { @@ -4402,18 +4401,16 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper treeCopy.New(tree, tpt1).setType(tp) } - def functionTypeWildcard(tree: Tree, arity: Int): Type = { - val tp = functionType(List.fill(arity)(WildcardType), WildcardType) - if (tp == NoType) MaxFunctionArityError(tree) - tp + def functionTypeWildcard(arity: Int): Type = + functionType(List.fill(arity)(WildcardType), WildcardType) + + def checkArity(tree: Tree)(tp: Type): tp.type = tp match { + case NoType => MaxFunctionArityError(tree); tp + case _ => tp } def expectingFunctionMatchingFormals(formals: List[Symbol]) = - isFunctionType(pt) || { - val sam = samOf(pt) - // TODO: handle vararg sam here and in typedFunction - sam.exists && sameLength(sam.info.params, formals) - } + isFunctionType(pt) || samMatchesFunctionBasedOnArity(samOf(pt), formals) def typedEta(expr1: Tree): Tree = expr1.tpe match { case TypeRef(_, ByNameParamClass, _) => @@ -4426,10 +4423,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper typed1(expr2, mode, pt) case PolyType(_, MethodType(formals, _)) => if (expectingFunctionMatchingFormals(formals)) expr1 - else adapt(expr1, mode, functionTypeWildcard(expr1, formals.length)) + else adapt(expr1, mode, checkArity(expr1)(functionTypeWildcard(formals.length))) case MethodType(formals, _) => if (expectingFunctionMatchingFormals(formals)) expr1 - else adapt(expr1, mode, functionTypeWildcard(expr1, formals.length)) + else adapt(expr1, mode, checkArity(expr1)(functionTypeWildcard(formals.length))) case ErrorType => expr1 case _ => diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 425502d25f..9a212fcbcb 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -686,6 +686,11 @@ trait Definitions extends api.StandardDefinitions { } } + // the SAM's parameters and the Function's formals must have the same length + // (varargs etc don't come into play, as we're comparing signatures, not checking an application) + def samMatchesFunctionBasedOnArity(sam: Symbol, formals: List[Any]): Boolean = + sam.exists && sameLength(sam.info.params, formals) + def isTupleType(tp: Type) = isTupleTypeDirect(tp.dealiasWiden) def tupleComponents(tp: Type) = tp.dealiasWiden.typeArgs -- cgit v1.2.3 From 1acfc70b038b09018d3ba1cc4b8c968e2a93d33c Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 16 Mar 2016 16:37:59 -0700 Subject: Set the scene for Sammy. Go beyond refactoring and introduce some hooks and patch some holes that will become acute when we set Sammy loose. Expanding sam requires class as first parent: `addObjectParent`. (Tested in pos/sam_ctor_arg.scala, coming next.) --- src/compiler/scala/tools/nsc/ast/TreeGen.scala | 8 +- .../scala/tools/nsc/transform/Delambdafy.scala | 2 +- .../scala/tools/nsc/transform/Erasure.scala | 7 +- .../nsc/transform/TypeAdaptingTransformer.scala | 103 ++++++++------------- .../scala/tools/nsc/typechecker/Infer.scala | 4 +- .../scala/reflect/internal/Definitions.scala | 18 ++++ src/reflect/scala/reflect/internal/Types.scala | 5 + 7 files changed, 71 insertions(+), 76 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index 7b0216a6d7..b2a54e16d3 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -300,7 +300,7 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.tpe.typeArgs.last.deconst) // TODO: use `fun.body.tpe.deconst` -- preserving wrong status quo because refactoring-only def expandFunction(localTyper: analyzer.Typer)(fun: Function, inConstructorFlag: Long): Tree = { - val parents = addSerializable(functionClassType(fun)) + val parents = addObjectParent(addSerializable(functionClassType(fun))) val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation // The original owner is used in the backend for the EnclosingMethod attribute. If fun is @@ -309,12 +309,12 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { defineOriginalOwner(anonClass, fun.symbol.originalOwner) anonClass setInfo ClassInfoType(parents, newScope, anonClass) - val applyMethodDef = mkMethodFromFunction(localTyper)(anonClass, fun) - anonClass.info.decls enter applyMethodDef.symbol + val samDef = mkMethodFromFunction(localTyper)(anonClass, fun) + anonClass.info.decls enter samDef.symbol localTyper.typedPos(fun.pos) { Block( - ClassDef(anonClass, NoMods, ListOfNil, List(applyMethodDef), fun.pos), + ClassDef(anonClass, NoMods, ListOfNil, List(samDef), fun.pos), Typed(New(anonClass.tpe), TypeTree(fun.tpe))) } } diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 43d24be4d5..254bf81999 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -145,7 +145,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre gen.mkMethodCall(newTarget, args) } val body1 = if (enteringErasure(functionResultType.typeSymbol.isDerivedValueClass)) - adaptToType(box(body.setType(ErasedValueType(functionResultType.typeSymbol, body.tpe)), "boxing lambda target"), bridgeResultType) + adaptToType(box(body.setType(ErasedValueType(functionResultType.typeSymbol, body.tpe))), bridgeResultType) else adaptToType(body, bridgeResultType) val methDef0 = DefDef(methSym, List(bridgeParamTrees), body1) val bridge = postErasure.newTransformer(unit).transform(methDef0).asInstanceOf[DefDef] diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 41f22e5669..58be6b53a0 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -647,7 +647,7 @@ abstract class Erasure extends AddInterfaces var qual1 = typedQualifier(qual) if ((isPrimitiveValueType(qual1.tpe) && !isPrimitiveValueMember(tree.symbol)) || isErasedValueType(qual1.tpe)) - qual1 = box(qual1, "owner "+tree.symbol.owner) + qual1 = box(qual1) else if (!isPrimitiveValueType(qual1.tpe) && isPrimitiveValueMember(tree.symbol)) qual1 = unbox(qual1, tree.symbol.owner.tpe) @@ -656,10 +656,9 @@ abstract class Erasure extends AddInterfaces if (isPrimitiveValueMember(tree.symbol) && !isPrimitiveValueType(qual1.tpe)) { tree.symbol = NoSymbol selectFrom(qual1) - } else if (isMethodTypeWithEmptyParams(qual1.tpe)) { + } else if (isMethodTypeWithEmptyParams(qual1.tpe)) { // see also adaptToType in TypeAdapter assert(qual1.symbol.isStable, qual1.symbol) - val applied = Apply(qual1, List()) setPos qual1.pos setType qual1.tpe.resultType - adaptMember(selectFrom(applied)) + adaptMember(selectFrom(applyMethodWithEmptyParams(qual1))) } else if (!(qual1.isInstanceOf[Super] || (qual1.tpe.typeSymbol isSubClass tree.symbol.owner))) { assert(tree.symbol.owner != ArrayClass) selectFrom(cast(qual1, tree.symbol.owner.tpe.resultType)) diff --git a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala index 1ed728247b..246d9ebf3f 100644 --- a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala +++ b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala @@ -1,6 +1,7 @@ package scala.tools.nsc package transform +import scala.annotation.tailrec import scala.tools.nsc.ast.TreeDSL /** @@ -17,11 +18,6 @@ trait TypeAdaptingTransformer { import definitions._ import CODE._ - def isMethodTypeWithEmptyParams(tpe: Type) = tpe match { - case MethodType(Nil, _) => true - case _ => false - } - private def isSafelyRemovableUnbox(fn: Tree, arg: Tree): Boolean = { currentRun.runDefinitions.isUnbox(fn.symbol) && { val cls = arg.tpe.typeSymbol @@ -30,25 +26,14 @@ trait TypeAdaptingTransformer { } private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol) - - private def isErasedValueType(tpe: Type) = tpe.isInstanceOf[ErasedValueType] - - private def isDifferentErasedValueType(tpe: Type, other: Type) = - isErasedValueType(tpe) && (tpe ne other) - def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner) - - @inline def box(tree: Tree, target: => String): Tree = { - val result = box1(tree) - if (tree.tpe =:= UnitTpe) () - else log(s"boxing ${tree.summaryString}: ${tree.tpe} into $target: ${result.tpe}") - result - } + def isMethodTypeWithEmptyParams(tpe: Type) = tpe.isInstanceOf[MethodType] && tpe.params.isEmpty + def applyMethodWithEmptyParams(qual: Tree) = Apply(qual, List()) setPos qual.pos setType qual.tpe.resultType /** Box `tree` of unboxed type */ - private def box1(tree: Tree): Tree = tree match { + def box(tree: Tree): Tree = tree match { case LabelDef(_, _, _) => - val ldef = deriveLabelDef(tree)(box1) + val ldef = deriveLabelDef(tree)(box) ldef setType ldef.rhs.tpe case _ => val tree1 = tree.tpe match { @@ -80,39 +65,19 @@ trait TypeAdaptingTransformer { typer.typedPos(tree.pos)(tree1) } - def unbox(tree: Tree, pt: Type): Tree = { - val result = unbox1(tree, pt) - log(s"unboxing ${tree.shortClass}: ${tree.tpe} as a ${result.tpe}") - result - } - /** Unbox `tree` of boxed type to expected type `pt`. * * @param tree the given tree * @param pt the expected type. * @return the unboxed tree */ - private def unbox1(tree: Tree, pt: Type): Tree = tree match { -/* - case Boxed(unboxed) => - println("unbox shorten: "+tree) // this never seems to kick in during build and test; therefore disabled. - adaptToType(unboxed, pt) - */ + def unbox(tree: Tree, pt: Type): Tree = tree match { case LabelDef(_, _, _) => val ldef = deriveLabelDef(tree)(unbox(_, pt)) ldef setType ldef.rhs.tpe case _ => val tree1 = pt match { - case ErasedValueType(clazz, underlying) => - val tree0 = - if (tree.tpe.typeSymbol == NullClass && - isPrimitiveValueClass(underlying.typeSymbol)) { - // convert `null` directly to underlying type, as going - // via the unboxed type would yield a NPE (see SI-5866) - unbox1(tree, underlying) - } else - Apply(Select(adaptToType(tree, clazz.tpe), clazz.derivedValueClassUnbox), List()) - cast(tree0, pt) + case ErasedValueType(clazz, underlying) => cast(unboxValueClass(tree, clazz, underlying), pt) case _ => pt.typeSymbol match { case UnitClass => @@ -127,11 +92,19 @@ trait TypeAdaptingTransformer { typer.typedPos(tree.pos)(tree1) } + def unboxValueClass(tree: Tree, clazz: Symbol, underlying: Type): Tree = + if (tree.tpe.typeSymbol == NullClass && isPrimitiveValueClass(underlying.typeSymbol)) { + // convert `null` directly to underlying type, as going via the unboxed type would yield a NPE (see SI-5866) + unbox(tree, underlying) + } else + Apply(Select(adaptToType(tree, clazz.tpe), clazz.derivedValueClassUnbox), List()) + /** Generate a synthetic cast operation from tree.tpe to pt. - * @pre pt eq pt.normalize + * + * @pre pt eq pt.normalize */ - def cast(tree: Tree, pt: Type): Tree = { - if ((tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) { + final def cast(tree: Tree, pt: Type): Tree = { + if (settings.debug && (tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) { def word = ( if (tree.tpe <:< pt) "upcast" else if (pt <:< tree.tpe) "downcast" @@ -159,27 +132,25 @@ trait TypeAdaptingTransformer { * @param pt the expected type * @return the adapted tree */ - def adaptToType(tree: Tree, pt: Type): Tree = { - if (settings.debug && pt != WildcardType) - log("adapting " + tree + ":" + tree.tpe + " : " + tree.tpe.parents + " to " + pt)//debug - if (tree.tpe <:< pt) - tree - else if (isDifferentErasedValueType(tree.tpe, pt)) - adaptToType(box(tree, pt.toString), pt) - else if (isDifferentErasedValueType(pt, tree.tpe)) - adaptToType(unbox(tree, pt), pt) - else if (isPrimitiveValueType(tree.tpe) && !isPrimitiveValueType(pt)) { - adaptToType(box(tree, pt.toString), pt) - } else if (isMethodTypeWithEmptyParams(tree.tpe)) { - // [H] this assert fails when trying to typecheck tree !(SomeClass.this.bitmap) for single lazy val - //assert(tree.symbol.isStable, "adapt "+tree+":"+tree.tpe+" to "+pt) - adaptToType(Apply(tree, List()) setPos tree.pos setType tree.tpe.resultType, pt) -// } else if (pt <:< tree.tpe) -// cast(tree, pt) - } else if (isPrimitiveValueType(pt) && !isPrimitiveValueType(tree.tpe)) - adaptToType(unbox(tree, pt), pt) - else - cast(tree, pt) + @tailrec final def adaptToType(tree: Tree, pt: Type): Tree = { + val tpe = tree.tpe + + if ((tpe eq pt) || tpe <:< pt) tree + else if (tpe.isInstanceOf[ErasedValueType]) adaptToType(box(tree), pt) // what if pt is an erased value type? + else if (pt.isInstanceOf[ErasedValueType]) adaptToType(unbox(tree, pt), pt) + // See corresponding case in `Eraser`'s `adaptMember` + // [H] this does not hold here, however: `assert(tree.symbol.isStable)` (when typechecking !(SomeClass.this.bitmap) for single lazy val) + else if (isMethodTypeWithEmptyParams(tpe)) adaptToType(applyMethodWithEmptyParams(tree), pt) + else { + val gotPrimitiveVC = isPrimitiveValueType(tpe) + val expectedPrimitiveVC = isPrimitiveValueType(pt) + + if (gotPrimitiveVC && !expectedPrimitiveVC) adaptToType(box(tree), pt) + else if (!gotPrimitiveVC && expectedPrimitiveVC) adaptToType(unbox(tree, pt), pt) + else if (samMatchingFunction(tree, pt).exists) { + tree setType pt + } else cast(tree, pt) + } } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 84cf3c6475..9b42841eb8 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -164,7 +164,9 @@ trait Infer extends Checkable { | was: $restpe | now""")(normalize(restpe)) case mt @ MethodType(_, restpe) if mt.isImplicit => normalize(restpe) - case mt @ MethodType(_, restpe) if !mt.isDependentMethodType => functionType(mt.paramTypes, normalize(restpe)) + case mt @ MethodType(_, restpe) if !mt.isDependentMethodType => + if (phase.erasedTypes) FunctionClass(mt.params.length).tpe + else functionType(mt.paramTypes, normalize(restpe)) case NullaryMethodType(restpe) => normalize(restpe) case ExistentialType(tparams, qtpe) => newExistentialType(tparams, normalize(qtpe)) case _ => tp // @MAT aliases already handled by subtyping diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 9a212fcbcb..2fa39bc453 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -686,11 +686,29 @@ trait Definitions extends api.StandardDefinitions { } } + // the result type of a function or corresponding SAM type + def functionResultType(tp: Type): Type = { + val dealiased = tp.dealiasWiden + if (isFunctionTypeDirect(dealiased)) dealiased.typeArgs.last + else samOf(tp) match { + case samSym if samSym.exists => tp.memberInfo(samSym).resultType.deconst + case _ => NoType + } + } + // the SAM's parameters and the Function's formals must have the same length // (varargs etc don't come into play, as we're comparing signatures, not checking an application) def samMatchesFunctionBasedOnArity(sam: Symbol, formals: List[Any]): Boolean = sam.exists && sameLength(sam.info.params, formals) + def samMatchingFunction(tree: Tree, pt: Type): Symbol = { + if (tree.isInstanceOf[Function] && !isFunctionType(pt)) { + val sam = samOf(pt) + if (samMatchesFunctionBasedOnArity(sam, tree.asInstanceOf[Function].vparams)) sam + else NoSymbol + } else NoSymbol + } + def isTupleType(tp: Type) = isTupleTypeDirect(tp.dealiasWiden) def tupleComponents(tp: Type) = tp.dealiasWiden.typeArgs diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index f385ca08c9..00df55f044 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -4553,6 +4553,11 @@ trait Types else (ps :+ SerializableTpe).toList ) + def addObjectParent(tps: List[Type]) = tps match { + case hd :: _ if hd.typeSymbol.isTrait => ObjectTpe :: tps + case _ => tps + } + /** Adds the @uncheckedBound annotation if the given `tp` has type arguments */ final def uncheckedBounds(tp: Type): Type = { if (tp.typeArgs.isEmpty || UncheckedBoundsClass == NoSymbol) tp // second condition for backwards compatibility with older scala-reflect.jar -- cgit v1.2.3 From 651d67cff7af581751257711ad99d318a5a2879a Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 16 Mar 2016 16:40:58 -0700 Subject: Review feedback from Lukas --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 5 ++++- test/files/pos/t9449.scala | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index d9d68f0773..55d49929a8 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3219,7 +3219,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // less expensive than including them in inferMethodAlternative (see below). def shapeType(arg: Tree): Type = arg match { case Function(vparams, body) => - functionType(vparams map (_ => AnyTpe), shapeType(body)) // TODO: should this be erased when retyping during erasure? + // No need for phasedAppliedType, as we don't get here during erasure -- + // overloading resolution happens during type checking. + // During erasure, the condition above (fun.symbol.isOverloaded) is false. + functionType(vparams map (_ => AnyTpe), shapeType(body)) case AssignOrNamedArg(Ident(name), rhs) => NamedType(name, shapeType(rhs)) case _ => diff --git a/test/files/pos/t9449.scala b/test/files/pos/t9449.scala index 06653cca83..3b86dc80a0 100644 --- a/test/files/pos/t9449.scala +++ b/test/files/pos/t9449.scala @@ -7,7 +7,7 @@ object Test { def test = { val ii1: II = x => ii(x) // works val ii2: II = ii // works (adapting `ii` to `II`) - val ii3: II = ii _ // fails -- should work + val ii3: II = ii _ // works (failed before the fix) // typedTyped({ii : (() => )}) // typedEta(ii, pt = II) // adapt(ii, pt = (? => ?)) -- cgit v1.2.3 From 8433b6fa0e86dfdcd3db31b97844b14d65e45359 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 8 Feb 2016 18:24:43 -0800 Subject: Treat `Function` literals uniformly, expecting SAM or FunctionN. They both compile to INDY/MetaLambdaFactory, except when they occur in a constructor call. (TODO: can we lift the ctor arg expression to a method and avoid statically synthesizing anonymous subclass altogether?) Typers: - no longer synthesize SAMs -- *adapt* a Function literal to the expected (SAM/FunctionN) type - Deal with polymorphic/existential sams (relevant tests: pos/t8310, pos/t5099.scala, pos/t4869.scala) We know where to find the result type, as all Function nodes have a FunctionN-shaped type during erasure. (Including function literals targeting a SAM type -- the sam type is tracked as the *expected* type.) Lift restriction on sam types being class types. It's enough that they dealias to one, like regular instance creation expressions. Contexts: - No longer need encl method hack for return in sam. Erasure: - erasure preserves SAM type for function nodes - Normalize sam to erased function type during erasure, otherwise we may box the function body from `$anonfun(args)` to `{$anonfun(args); ()}` because the expected type for the body is now `Object`, and thus `Unit` does not conform. Delambdafy: - must set static flag before calling createBoxingBridgeMethod - Refactored `createBoxingBridgeMethod` to wrap my head around boxing, reworked it to generalize from FunctionN's boxing needs to arbitrary LMF targets. Other refactorings: ThisReferringMethodsTraverser, TreeGen. --- src/compiler/scala/tools/nsc/ast/TreeGen.scala | 76 +++-- .../scala/tools/nsc/transform/Delambdafy.scala | 377 ++++++++++++--------- .../scala/tools/nsc/transform/UnCurry.scala | 2 +- .../scala/tools/nsc/typechecker/Contexts.scala | 3 +- .../scala/tools/nsc/typechecker/Typers.scala | 159 +++------ src/reflect/scala/reflect/internal/StdNames.scala | 1 - test/files/neg/sammy_restrictions.check | 5 +- test/files/neg/sammy_restrictions.scala | 2 +- test/files/pos/sammy_implicit.scala | 2 +- test/files/pos/sammy_poly.scala | 3 +- test/files/run/indylambda-boxing/test.scala | 7 +- .../tools/nsc/backend/jvm/IndyLambdaTest.scala | 29 +- 12 files changed, 331 insertions(+), 335 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index b2a54e16d3..27e366e725 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -261,43 +261,59 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { mkNew(Nil, noSelfType, stats1, NoPosition, NoPosition) } - /** - * Create a method based on a Function - * - * Used both to under `-Ydelambdafy:method` create a lifted function and - * under `-Ydelambdafy:inline` to create the apply method on the anonymous - * class. - * - * It creates a method definition with value params cloned from the - * original lambda. Then it calls a supplied function to create - * the body and types the result. Finally - * everything is wrapped up in a DefDef - * - * @param owner The owner for the new method - * @param name name for the new method - * @param additionalFlags flags to be put on the method in addition to FINAL - */ - def mkMethodFromFunction(localTyper: analyzer.Typer) - (owner: Symbol, fun: Function, name: TermName = nme.apply, additionalFlags: FlagSet = NoFlags) = { - val funParamSyms = fun.vparams.map(_.symbol) - val methSym = owner.newMethod(name, fun.pos, FINAL | additionalFlags) - val methParamSyms = funParamSyms.map { param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName) } + // Construct a method to implement `fun`'s single abstract method (`apply`, when `fun.tpe` is a built-in function type) + def mkMethodFromFunction(localTyper: analyzer.Typer)(owner: Symbol, fun: Function) = { + // TODO: treat FunctionN like any other SAM -- drop `&& !isFunctionType(fun.tpe)` + val sam = if (!isFunctionType(fun.tpe)) samOf(fun.tpe) else NoSymbol + if (!sam.exists) mkMethodForFunctionBody(localTyper)(owner, fun, nme.apply)() + else { + val samMethType = fun.tpe memberInfo sam + mkMethodForFunctionBody(localTyper)(owner, fun, sam.name.toTermName)(methParamProtos = samMethType.params, resTp = samMethType.resultType) + } + } + + // used to create the lifted method that holds a function's body + def mkLiftedFunctionBodyMethod(localTyper: analyzer.Typer)(owner: Symbol, fun: Function) = + mkMethodForFunctionBody(localTyper)(owner, fun, nme.ANON_FUN_NAME)(additionalFlags = ARTIFACT) + - val body = fun.body - body substituteSymbols (funParamSyms, methParamSyms) - body changeOwner ((fun.symbol, methSym)) + /** + * Lift a Function's body to a method. For use during Uncurry, where Function nodes have type FunctionN[T1, ..., Tn, R] + * + * It creates a method definition with value params derived from the original lambda + * or `methParamProtos` (used to create the correct override for sam methods). + * + * Replace the `fun.vparams` symbols by the newly created method params, + * changes owner of `fun.body` from `fun.symbol` to resulting method's symbol. + * + * @param owner The owner for the new method + * @param fun the function to take the body from + * @param name name for the new method + * @param additionalFlags flags to be put on the method in addition to FINAL + */ + private def mkMethodForFunctionBody(localTyper: analyzer.Typer) + (owner: Symbol, fun: Function, name: TermName) + (methParamProtos: List[Symbol] = fun.vparams.map(_.symbol), + resTp: Type = functionResultType(fun.tpe), + additionalFlags: FlagSet = NoFlags): DefDef = { + val methSym = owner.newMethod(name, fun.pos, FINAL | additionalFlags) + // for sams, methParamProtos is the parameter symbols for the sam's method, so that we generate the correct override (based on parmeter types) + val methParamSyms = methParamProtos.map { param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName) } + methSym setInfo MethodType(methParamSyms, resTp) - // Have to repack the type to avoid mismatches when existentials - // appear in the result - see SI-4869. - val resTp = localTyper.packedType(body, methSym).deconst - methSym setInfo MethodType(methParamSyms, fun.tpe.typeArgs.last.deconst) // TODO: use `resTp` -- preserving wrong status quo because refactoring-only + // we must rewire reference to the function's param symbols -- and not methParamProtos -- to methParamSyms + val useMethodParams = new TreeSymSubstituter(fun.vparams.map(_.symbol), methParamSyms) + // we're now owned by the method that holds the body, and not the function + val moveToMethod = new ChangeOwnerTraverser(fun.symbol, methSym) - newDefDef(methSym, body)(tpt = TypeTree(resTp)) + newDefDef(methSym, moveToMethod(useMethodParams(fun.body)))(tpt = TypeTree(resTp)) } + // TODO: the rewrite to AbstractFunction is superfluous once we compile FunctionN to a SAM type (aka functional interface) def functionClassType(fun: Function): Type = - abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.tpe.typeArgs.last.deconst) // TODO: use `fun.body.tpe.deconst` -- preserving wrong status quo because refactoring-only + if (isFunctionType(fun.tpe)) abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.body.tpe.deconst) + else fun.tpe def expandFunction(localTyper: analyzer.Typer)(fun: Function, inConstructorFlag: Long): Tree = { val parents = addObjectParent(addSerializable(functionClassType(fun))) diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 254bf81999..8a318b194f 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -31,6 +31,17 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol) + /** + * Get the symbol of the target lifted lambda body method from a function. I.e. if + * the function is {args => anonfun(args)} then this method returns anonfun's symbol + */ + private def targetMethod(fun: Function): Symbol = fun match { + case Function(_, Apply(target, _)) => target.symbol + case _ => + // any other shape of Function is unexpected at this point + abort(s"could not understand function with tree $fun") + } + override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = { if (settings.Ydelambdafy.value == "method") new Phase(prev) else new SkipPhase(prev) @@ -43,205 +54,214 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre protected def newTransformer(unit: CompilationUnit): Transformer = new DelambdafyTransformer(unit) - class DelambdafyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) with TypeAdapter { - private val lambdaClassDefs = new mutable.LinkedHashMap[Symbol, List[Tree]] withDefaultValue Nil - - val typer = localTyper - + class DelambdafyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { // we need to know which methods refer to the 'this' reference so that we can determine which lambdas need access to it // TODO: this looks expensive, so I made it a lazy val. Can we make it more pay-as-you-go / optimize for common shapes? - lazy val thisReferringMethods: Set[Symbol] = { - val thisReferringMethodsTraverser = new ThisReferringMethodsTraverser() - thisReferringMethodsTraverser traverse unit.body - val methodReferringMap = thisReferringMethodsTraverser.liftedMethodReferences - val referrers = thisReferringMethodsTraverser.thisReferringMethods - // recursively find methods that refer to 'this' directly or indirectly via references to other methods - // for each method found add it to the referrers set - def refersToThis(symbol: Symbol): Boolean = { - if (referrers contains symbol) true - else if (methodReferringMap(symbol) exists refersToThis) { - // add it early to memoize - debuglog(s"$symbol indirectly refers to 'this'") - referrers += symbol - true - } else false + private[this] lazy val methodReferencesThis: Set[Symbol] = + (new ThisReferringMethodsTraverser).methodReferencesThisIn(unit.body) + + private def mkLambdaMetaFactoryCall(fun: Function, target: Symbol, functionalInterface: 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 + // passed into the constructor of the anonymous function class + val captureArgs = FreeVarTraverser.freeVarsOf(fun).iterator.map(capture => + gen.mkAttributedRef(capture) setPos pos + ).toList + + if (target hasFlag STATIC) captureArgs // no `this` reference needed + else (gen.mkAttributedThis(fun.symbol.enclClass) setPos pos) :: captureArgs } - methodReferringMap.keys foreach refersToThis - referrers + + // Create a symbol representing a fictional lambda factory method that accepts the captured + // arguments and returns the SAM type. + val msym = { + val meth = currentOwner.newMethod(nme.ANON_FUN_NAME, pos, ARTIFACT) + val capturedParams = meth.newSyntheticValueParams(allCapturedArgRefs.map(_.tpe)) + meth.setInfo(MethodType(capturedParams, fun.tpe)) + } + + // We then apply this symbol to the captures. + val apply = localTyper.typedPos(pos)(Apply(Ident(msym), allCapturedArgRefs)) + + // 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) + + // 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. + // + // 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 } + private val boxingBridgeMethods = mutable.ArrayBuffer[Tree]() - // here's the main entry point of the transform - override def transform(tree: Tree): Tree = tree match { - // the main thing we care about is lambdas - case fun: Function => super.transform(transformFunction(fun)) - case Template(_, _, _) => - try { - // during this call boxingBridgeMethods will be populated from the Function case - val Template(parents, self, body) = super.transform(tree) - Template(parents, self, body ++ boxingBridgeMethods) - } finally boxingBridgeMethods.clear() - case _ => super.transform(tree) + private def reboxValueClass(tp: Type) = tp match { + case ErasedValueType(valueClazz, _) => TypeRef(NoPrefix, valueClazz, Nil) + case _ => tp } - // this entry point is aimed at the statements in the compilation unit. - // after working on the entire compilation until we'll have a set of - // new class definitions to add to the top level - override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = { - // Need to remove from the lambdaClassDefs map: there may be multiple PackageDef for the same - // package when defining a package object. We only add the lambda class to one. See SI-9097. - super.transformStats(stats, exprOwner) ++ lambdaClassDefs.remove(exprOwner).getOrElse(Nil) + private def valueTypeToObject(tpe: Type): Type = + if (isPrimitiveValueClass(tpe.typeSymbol) || enteringErasure(tpe.typeSymbol.isDerivedValueClass)) ObjectTpe + else tpe + + // exclude primitives and value classses, which need special boxing + private def isReferenceType(tp: Type) = !tp.isInstanceOf[ErasedValueType] && { + val sym = tp.typeSymbol + !(isPrimitiveValueClass(sym) || sym.isDerivedValueClass) } - def createBoxingBridgeMethod(oldClass: Symbol, target: Symbol, functionParamTypes: List[Type], functionResultType: Type, pos: Position): Symbol = { - // Note: we bail out of this method and return EmptyTree if we find there is no adaptation required. - // If we need to improve performance, we could check the types first before creating the - // method and parameter symbols. - val methSym = oldClass.newMethod(target.name.append("$adapted").toTermName, target.pos, target.flags | FINAL | ARTIFACT) - var neededAdaptation = false - def boxedType(tpe: Type): Type = { - if (isPrimitiveValueClass(tpe.typeSymbol)) {neededAdaptation = true; ObjectTpe} - else if (enteringErasure(tpe.typeSymbol.isDerivedValueClass)) {neededAdaptation = true; ObjectTpe} - else tpe - } - val targetParams: List[Symbol] = target.paramss.head - val numCaptures = targetParams.length - functionParamTypes.length - val (targetCaptureParams, targetFunctionParams) = targetParams.splitAt(numCaptures) - val bridgeParams: List[Symbol] = - targetCaptureParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName)) ::: - map2(targetFunctionParams, functionParamTypes)((param, tp) => methSym.newSyntheticValueParam(boxedType(tp), param.name.toTermName)) - - val bridgeResultType: Type = { - if (target.info.resultType == UnitTpe && functionResultType != UnitTpe) { - neededAdaptation = true - ObjectTpe - } else - boxedType(functionResultType) - } - val methodType = MethodType(bridgeParams, bridgeResultType) - methSym setInfo methodType - if (!neededAdaptation) target + // 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 = { + val oldClass = fun.symbol.enclClass + val pos = fun.pos + + // At erasure, there won't be any captured arguments (they are added in constructors) + 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) + + /** How to satisfy the linking invariants of https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html + * + * Given samMethodType: (U1..Un)Ru and function type T1,..., Tn => Rt (the target method created by uncurry) + * + * Do we need a bridge, or can we use the original lambda target for implMethod: ( A1..An)Ra + * (We can ignore capture here.) + * + * If, for i=1..N: + * Ai =:= Ui || (Ai <:< Ui <:< AnyRef) + * Ru =:= void || (Ra =:= Ru || (Ra <:< AnyRef, Ru <:< AnyRef)) + * + * We can use the target method as-is -- if not, we create a bridging one that uses the types closest + * to the target method that still meet the above requirements. + */ + val resTpOk = ( + samResultType =:= UnitTpe + || functionResultType =:= samResultType + || (isReferenceType(samResultType) && isReferenceType(functionResultType))) // yes, this is what the spec says -- no further correspondance required + if (resTpOk && (samParamTypes corresponds functionParamTypes){ (samParamTp, funParamTp) => + funParamTp =:= samParamTp || (isReferenceType(funParamTp) && isReferenceType(samParamTp) && funParamTp <:< samParamTp) }) target else { - val bridgeParamTrees = bridgeParams.map(ValDef(_)) + // We have to construct a new lambda target that bridges to the one created by uncurry. + // The bridge must satisfy the above invariants, while also minimizing adaptation on our end. + // LMF will insert runtime casts according to the spec at the above link. + + // we use the more precise type between samParamTp and funParamTp to minimize boxing in the bridge method + // we are constructing a method whose signature matches the sam's signature (because the original target did not) + // whenever a type in the sam's signature is (erases to) a primitive type, we must pick the sam's version, + // as we don't implement the logic regarding widening that's performed by LMF -- we require =:= for primitives + // + // We use the sam's type for the check whether we're dealin with a reference type, as it could be a generic type, + // which means the function's parameter -- even if it expects a value class -- will need to be + // boxed on the generic call to the sam method. + + val bridgeParamTypes = map2(samParamTypes, functionParamTypes){ (samParamTp, funParamTp) => + if (isReferenceType(samParamTp) && funParamTp <:< samParamTp) funParamTp + else samParamTp + } + + val bridgeResultType = + if (resTpOk && isReferenceType(samResultType) && functionResultType <:< samResultType) functionResultType + else samResultType + + val typeAdapter = new TypeAdapter { val typer = localTyper } + import typeAdapter.{box, unbox, cast, adaptToType, unboxValueClass} + val targetParams = target.paramss.head + val numCaptures = targetParams.length - functionParamTypes.length + val (targetCapturedParams, targetFunctionParams) = targetParams.splitAt(numCaptures) + + val methSym = oldClass.newMethod(target.name.append("$adapted").toTermName, target.pos, target.flags | FINAL | ARTIFACT) + val bridgeCapturedParams = targetCapturedParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName)) + val bridgeFunctionParams = + map2(targetFunctionParams, bridgeParamTypes)((param, tp) => methSym.newSyntheticValueParam(tp, param.name.toTermName)) + + val bridgeParams = bridgeCapturedParams ::: bridgeFunctionParams + + methSym setInfo MethodType(bridgeParams, bridgeResultType) oldClass.info.decls enter methSym - val body = localTyper.typedPos(pos) { - val newTarget = Select(gen.mkAttributedThis(oldClass), target) - val args: List[Tree] = mapWithIndex(bridgeParams) { (param, i) => - if (i < numCaptures) { - gen.mkAttributedRef(param) - } else { - val functionParam = functionParamTypes(i - numCaptures) - val targetParam = targetParams(i) - if (enteringErasure(functionParam.typeSymbol.isDerivedValueClass)) { - val casted = cast(gen.mkAttributedRef(param), functionParam) - val unboxed = unbox(casted, ErasedValueType(functionParam.typeSymbol, targetParam.tpe)).modifyType(postErasure.elimErasedValueType) - unboxed - } else adaptToType(gen.mkAttributedRef(param), targetParam.tpe) + val forwarderCall = localTyper.typedPos(pos) { + val capturedArgRefs = bridgeCapturedParams map gen.mkAttributedRef + val functionArgRefs = + map3(bridgeFunctionParams, functionParamTypes, targetParams.drop(numCaptures)) { (bridgeParam, functionParamTp, targetParam) => + val bridgeParamRef = gen.mkAttributedRef(bridgeParam) + val targetParamTp = targetParam.tpe + + // TODO: can we simplify this to something like `adaptToType(adaptToType(bridgeParamRef, functionParamTp), targetParamTp)`? + val unboxed = + functionParamTp match { + case ErasedValueType(clazz, underlying) => + // when the original function expected an argument of value class type, + // the original target will expect the unboxed underlying value, + // whereas the bridge will receive the boxed value (since the sam's argument type did not match and we had to adapt) + localTyper.typed(unboxValueClass(bridgeParamRef, clazz, underlying), targetParamTp) + case _ => bridgeParamRef + } + + adaptToType(unboxed, targetParamTp) } - } - gen.mkMethodCall(newTarget, args) + + gen.mkMethodCall(Select(gen.mkAttributedThis(oldClass), target), capturedArgRefs ::: functionArgRefs) } - val body1 = if (enteringErasure(functionResultType.typeSymbol.isDerivedValueClass)) - adaptToType(box(body.setType(ErasedValueType(functionResultType.typeSymbol, body.tpe))), bridgeResultType) - else adaptToType(body, bridgeResultType) - val methDef0 = DefDef(methSym, List(bridgeParamTrees), body1) - val bridge = postErasure.newTransformer(unit).transform(methDef0).asInstanceOf[DefDef] + + val bridge = postErasure.newTransformer(unit).transform(DefDef(methSym, List(bridgeParams.map(ValDef(_))), + adaptToType(forwarderCall setType functionResultType, bridgeResultType))).asInstanceOf[DefDef] + boxingBridgeMethods += bridge bridge.symbol } } - // turns a lambda into a new class def, a New expression instantiating that class private def transformFunction(originalFunction: Function): Tree = { - val oldClass = originalFunction.symbol.enclClass - val arity = originalFunction.vparams.length - val target = targetMethod(originalFunction) target.makeNotPrivate(target.owner) - if (!thisReferringMethods.contains(target)) - target setFlag STATIC - - val isStatic = target.hasFlag(STATIC) - - // Reconstruct the type of the function entering erasure. - // We do this by taking the type after erasure, and re-boxing `ErasedValueType`. - // - // Unfortunately, the more obvious `enteringErasure(target.info)` doesn't work - // as we would like, value classes in parameter position show up as the unboxed types. - val (functionParamTypes, functionResultType) = exitingErasure { - def boxed(tp: Type) = tp match { - case ErasedValueType(valueClazz, _) => TypeRef(NoPrefix, valueClazz, Nil) - case _ => tp - } - // We don't need to deeply map `boxedValueClassType` over the infos as `ErasedValueType` - // will only appear directly as a parameter type in a method signature, as shown - // https://gist.github.com/retronym/ba81dbd462282c504ff8 - val info = target.info - val boxedParamTypes = info.paramTypes.takeRight(arity).map(boxed) - (boxedParamTypes, boxed(info.resultType)) - } - - val functionType = definitions.functionType(functionParamTypes, functionResultType) - val funSym = functionType.typeSymbol - val specializedName = specializeTypes.specializedFunctionName(funSym, functionType.typeArgs).toTypeName - val isSpecialized = specializedName != funSym.name + // must be done before calling createBoxingBridgeMethod and mkLambdaMetaFactoryCall + if (!(target hasFlag STATIC) && !methodReferencesThis(target)) target setFlag STATIC - // The functional interface that can be used to adapt the lambda target method `target` to the - // given function type. Returns `NoSymbol` if the compiler settings are unsuitable. - val functionalInterface = - if (isSpecialized) currentRun.runDefinitions.Scala_Java8_CompatPackage.info.decl(specializedName.prepend("J")) - else currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction(arity) + val funSym = originalFunction.tpe.typeSymbolDirect + // The functional interface that can be used to adapt the lambda target method `target` to the given function type. + val (functionalInterface, isSpecialized) = + if (!isFunctionSymbol(funSym)) (funSym, false) + else { + val specializedName = + specializeTypes.specializedFunctionName(funSym, + exitingErasure(target.info.paramTypes).map(reboxValueClass) :+ reboxValueClass(exitingErasure(target.info.resultType))).toTypeName - assert(functionalInterface.exists) + val isSpecialized = specializedName != funSym.name + val functionalInterface = + if (isSpecialized) currentRun.runDefinitions.Scala_Java8_CompatPackage.info.decl(specializedName.prepend("J")) + else currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction(originalFunction.vparams.length) - val lambdaTarget = - if (isSpecialized) target - else createBoxingBridgeMethod(oldClass, target, functionParamTypes, functionResultType, originalFunction.pos) - - // We then apply this symbol to the captures. - val apply = { - val allCaptureArgs: List[Tree] = { - // find which variables are free in the lambda because those are captures that need to be - // passed into the constructor of the anonymous function class - val captures = FreeVarTraverser.freeVarsOf(originalFunction) - val thisArg = if (isStatic) Nil else (gen.mkAttributedThis(oldClass) setPos originalFunction.pos) :: Nil - val captureArgs = captures.iterator.map(capture => gen.mkAttributedRef(capture) setPos originalFunction.pos).toList - thisArg ::: captureArgs - } - - // Create a symbol representing a fictional lambda factory method that accepts the captured - // arguments and returns a Function. - val msym = { - val meth = currentOwner.newMethod(nme.ANON_FUN_NAME, originalFunction.pos, ARTIFACT) - val capturedParams = meth.newSyntheticValueParams(allCaptureArgs.map(_.tpe)) - meth.setInfo(MethodType(capturedParams, functionType)) + (functionalInterface, isSpecialized) } - localTyper.typedPos(originalFunction.pos)(Apply(Ident(msym), allCaptureArgs)).asInstanceOf[Apply] - } - - // 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. - apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, arity, functionalInterface)) + mkLambdaMetaFactoryCall(originalFunction, target, functionalInterface, isSpecialized) + } - apply + // here's the main entry point of the transform + override def transform(tree: Tree): Tree = tree match { + // the main thing we care about is lambdas + case fun: Function => super.transform(transformFunction(fun)) + case Template(_, _, _) => + try { + // during this call boxingBridgeMethods will be populated from the Function case + val Template(parents, self, body) = super.transform(tree) + Template(parents, self, body ++ boxingBridgeMethods) + } finally boxingBridgeMethods.clear() + case _ => super.transform(tree) } } // DelambdafyTransformer - /** - * Get the symbol of the target lifted lambda body method from a function. I.e. if - * the function is {args => anonfun(args)} then this method returns anonfun's symbol - */ - private def targetMethod(fun: Function): Symbol = fun match { - case Function(_, Apply(target, _)) => target.symbol - case _ => - // any other shape of Function is unexpected at this point - abort(s"could not understand function with tree $fun") - } // A traverser that finds symbols used but not defined in the given Tree // TODO freeVarTraverser in LambdaLift does a very similar task. With some @@ -276,12 +296,33 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre } // finds all methods that reference 'this' - class ThisReferringMethodsTraverser() extends Traverser { - private var currentMethod: Symbol = NoSymbol + class ThisReferringMethodsTraverser extends Traverser { // the set of methods that refer to this - val thisReferringMethods = mutable.Set[Symbol]() + private val thisReferringMethods = mutable.Set[Symbol]() + // the set of lifted lambda body methods that each method refers to - val liftedMethodReferences = mutable.Map[Symbol, Set[Symbol]]().withDefault(_ => mutable.Set()) + private val liftedMethodReferences = mutable.Map[Symbol, Set[Symbol]]().withDefault(_ => mutable.Set()) + + def methodReferencesThisIn(tree: Tree) = { + traverse(tree) + liftedMethodReferences.keys foreach refersToThis + + thisReferringMethods + } + + // recursively find methods that refer to 'this' directly or indirectly via references to other methods + // for each method found add it to the referrers set + private def refersToThis(symbol: Symbol): Boolean = + (thisReferringMethods contains symbol) || + (liftedMethodReferences(symbol) exists refersToThis) && { + // add it early to memoize + debuglog(s"$symbol indirectly refers to 'this'") + thisReferringMethods += symbol + true + } + + private var currentMethod: Symbol = NoSymbol + override def traverse(tree: Tree) = tree match { case DefDef(_, _, _, _, _, _) => // we don't expect defs within defs. At this phase trees should be very flat diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index c142c96e66..7e9e0e2a92 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -194,7 +194,7 @@ abstract class UnCurry extends InfoTransform else if (mustExpandFunction) gen.expandFunction(localTyper)(fun, inConstructorFlag) else { // method definition with the same arguments, return type, and body as the original lambda - val liftedMethod = gen.mkMethodFromFunction(localTyper)(fun.symbol.owner, fun, nme.ANON_FUN_NAME, ARTIFACT) + val liftedMethod = gen.mkLiftedFunctionBodyMethod(localTyper)(fun.symbol.owner, fun) // new function whose body is just a call to the lifted method val newFun = deriveFunction(fun)(_ => localTyper.typedPos(fun.pos)( diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index bdcf90681d..c5a3d605b1 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -478,8 +478,7 @@ trait Contexts { self: Analyzer => c(ConstructorSuffix) = !isTemplateOrPackage && c(ConstructorSuffix) // SI-8245 `isLazy` need to skip lazy getters to ensure `return` binds to the right place - // similarly for the synthetic method that holds as a SAM's body (as synthesized by `synthesizeSAMFunction`) - c.enclMethod = if (isDefDef && !(owner.isLazy || owner.name.endsWith(nme.SAM_BODY_SUFFIX))) c else enclMethod + c.enclMethod = if (isDefDef && !owner.isLazy) c else enclMethod if (tree != outer.tree) c(TypeConstructorAllowed) = false diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 55d49929a8..5c108c5fda 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1056,11 +1056,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper return instantiate(tree, mode, pt) // we know `!(tree.tpe <:< pt)`; try to remedy if there's a sam for pt - val sam = if (tree.isInstanceOf[Function] && !isFunctionType(pt)) samOf(pt) else NoSymbol - if (sam.exists && samMatchesFunctionBasedOnArity(sam, tree.asInstanceOf[Function].vparams)) { - // Use synthesizeSAMFunction to expand `(p1: T1, ..., pN: TN) => body` - // to an instance of the corresponding anonymous subclass of `pt`. - val samTree = synthesizeSAMFunction(sam, tree.asInstanceOf[Function], pt, mode) + val sam = samMatchingFunction(tree, pt) // this implies tree.isInstanceOf[Function] + if (sam.exists) { + val samTree = adaptToSAM(sam, tree.asInstanceOf[Function], pt, mode) if (samTree ne EmptyTree) return samTree } @@ -2748,137 +2746,62 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * is largely to keep the implementation of type inference (the computation of `samClassTpFullyDefined`) simple. * */ - def synthesizeSAMFunction(sam: Symbol, fun: Function, pt: Type, mode: Mode): Tree = { + def adaptToSAM(sam: Symbol, fun: Function, pt: Type, mode: Mode): Tree = { // `fun` has a FunctionType, but the expected type `pt` is some SAM type -- let's remedy that // `fun` is fully attributed, so we'll have to wrangle some symbols into shape (owner change, vparam syms) // I tried very hard to leave `fun` untyped and rework everything into the right shape and type check once, // but couldn't make it work due to retyping that happens down the line // (implicit conversion retries/retypechecking, CBN transform, super call arg context nesting weirdness) - val sampos = fun.pos.focus + def funTpMatchesExpected(pt: Type): Boolean = isFullyDefined(pt) && { + // what's the signature of the method that we should actually be overriding? + val samMethType = pt memberInfo sam + fun.tpe <:< functionType(samMethType.paramTypes, samMethType.resultType) + } - // Type check body def before classdef to fully determine samClassTp (if necessary). - // As `samClassTp` determines a parent type for the class, - // we can't type check `block` in one go unless `samClassTp` is fully defined. - val ptFullyDefined = - if (isFullyDefined(pt) && !isNonRefinementClassType(unwrapToClass(pt))) pt - else try { - val samClassSym = pt.typeSymbol - - // we're trying to fully define the type arguments for this type constructor - val samTyCon = samClassSym.typeConstructor - - // the unknowns - val tparams = samClassSym.typeParams - // ... as typevars - val tvars = tparams map freshVar - - val ptVars = appliedType(samTyCon, tvars) - - // carry over info from pt - ptVars <:< pt - - val samInfoWithTVars = ptVars.memberInfo(sam) - - // use function type subtyping, not method type subtyping (the latter is invariant in argument types) - fun.tpe <:< functionType(samInfoWithTVars.paramTypes, samInfoWithTVars.finalResultType) - - val variances = tparams map varianceInType(sam.info) - - // solve constraints tracked by tvars - val targs = solvedTypes(tvars, tparams, variances, upper = false, lubDepth(sam.info :: Nil)) - - debuglog(s"sam infer: $pt --> ${appliedType(samTyCon, targs)} by ${fun.tpe} <:< $samInfoWithTVars --> $targs for $tparams") + if (funTpMatchesExpected(pt)) fun.setType(pt) + else try { + val samClassSym = pt.typeSymbol - // a fully defined samClassTp - appliedType(samTyCon, targs) - } catch { - case e@(_: NoInstance | _: TypeError) => // TODO: we get here whenever pt contains a wildcardtype??? - debuglog(s"Could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)\n$e") - return EmptyTree - } + // we're trying to fully define the type arguments for this type constructor + val samTyCon = samClassSym.typeConstructor - debuglog(s"sam fully defined expected type: $ptFullyDefined from $pt for ${fun.tpe}") + // the unknowns + val tparams = samClassSym.typeParams + // ... as typevars + val tvars = tparams map freshVar - // what's the signature of the method that we should actually be overriding? - val samMethType = ptFullyDefined memberInfo sam - val samFunTp = functionType(samMethType.paramTypes, samMethType.finalResultType) + val ptVars = appliedType(samTyCon, tvars) - // TODO: should we preemptively check that ptFullyDefined is a valid class type? - // (to avoid running into type errors when type checking the instantiation below) - if (!(fun.tpe <:< samFunTp)) return EmptyTree + // carry over info from pt + ptVars <:< pt + val samInfoWithTVars = ptVars.memberInfo(sam) - // TODO: defer the remainder of this synthesis to the back-end, and use invokedynamic/LMF instead - // can we carry a Function node down the pipeline if it has type `ptFullyDefined`, which is not a `FunctionN`? - val bodyName = (sam.name append nme.SAM_BODY_SUFFIX).toTermName + // use function type subtyping, not method type subtyping (the latter is invariant in argument types) + fun.tpe <:< functionType(samInfoWithTVars.paramTypes, samInfoWithTVars.finalResultType) - // drop symbol info - val samBodyDefParams = fun.vparams.map { vd => ValDef(vd.mods, vd.name, vd.tpt, vd.rhs) } + val variances = tparams map varianceInType(sam.info) - // `def '${sam.name}\$body'($p1: $T1, ..., $pN: $TN): $resPt = $body` - // - // TODO: make this behave like a static method during type checking to enforce scoping: - // the sam class's self reference should not be in scope here. - // To survive retyping, we must package samBodyDef as a member of the sam's class, however. - // Scoping is not a problem (regardless of whether STATIC works), as the body has already been type checked in typedFunction. - // - // (Lifting samBodyDef into the surrounding block cause several problems: - // when the tree is in a ctor arg / implicitly converted in multiple tries --> lambda lift crash "Could not find proxy...") - // - // the rhs is already typed, so it doesn't matter it uses the wrong parameter symbols (must wait until block is typed before we can fix them) - val samBodyDef = - DefDef(NoMods, // TODO: Modifiers(STATIC) when the backend supports it (currently it causes VerifyErrors) - bodyName, - Nil, - List(samBodyDefParams), - TypeTree(fun.body.tpe) setPos sampos, - fun.body) - - // drop symbol info, use type info from sam so we implement the right method - val samDefParams = map2(fun.vparams, samMethType.paramTypes) { (vd, tp) => ValDef(vd.mods, vd.name, TypeTree(tp), vd.rhs) } - - // `final override def ${sam.name}($p1: $T1', ..., $pN: $TN'): ${samMethTp.finalResultType} = ${sam.name}\$body'($p1, ..., $pN)` - val samDef = - DefDef(Modifiers(FINAL | OVERRIDE | SYNTHETIC), - sam.name.toTermName, - Nil, - List(samDefParams), - TypeTree(samMethType.finalResultType) setPos sampos, - Apply(Ident(bodyName), fun.vparams.map(gen.paramToArg)) - ) + // solve constraints tracked by tvars + val targs = solvedTypes(tvars, tparams, variances, upper = false, lubDepth(sam.info :: Nil)) - val samSubclassName = tpnme.ANON_FUN_NAME - val classDef = - ClassDef(Modifiers(FINAL), samSubclassName, tparams = Nil, - gen.mkTemplate( - parents = TypeTree(ptFullyDefined) :: (if (typeIsSubTypeOfSerializable(pt)) Nil else List(TypeTree(SerializableTpe))), - self = noSelfType, - constrMods = NoMods, - vparamss = ListOfNil, - body = List(samBodyDef, samDef), - superPos = sampos - ) - ) + debuglog(s"sam infer: $pt --> ${appliedType(samTyCon, targs)} by ${fun.tpe} <:< $samInfoWithTVars --> $targs for $tparams") - // type checking the whole block, so that everything is packaged together nicely - // and we don't have to create any symbols by hand - val block = - typedPos(sampos, mode, pt) { - Block(classDef, Apply(Select(New(Ident(samSubclassName)), nme.CONSTRUCTOR), Nil)) + // a fully defined samClassTp + val ptFullyDefined = appliedType(samTyCon, targs) + if (funTpMatchesExpected(ptFullyDefined)) { + debuglog(s"sam fully defined expected type: $ptFullyDefined from $pt for ${fun.tpe}") + fun.setType(ptFullyDefined) + } else { + debuglog(s"Could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)") + EmptyTree } - - // fix owner for parts of the function we reused now that samBodyDef has a symbol - samBodyDef.rhs.changeOwner((fun.symbol, samBodyDef.symbol)) - samBodyDef.rhs.substituteSymbols(fun.vparams.map(_.symbol), samBodyDef.vparamss.head.map(_.symbol)) - - // TODO: improve error reporting -- when we're in silent mode (from `silent(_.doTypedApply(tree, fun, args, mode, pt)) orElse onError`) - // the errors in the function don't get out... - if (block exists (_.isErroneous)) - context.error(fun.pos, s"Could not derive subclass of $pt\n (with SAM `${sam.defStringSeenAs(ptFullyDefined)}`)\n based on: $fun.") - - classDef.symbol addAnnotation SerialVersionUIDAnnotation - block + } catch { + case e@(_: NoInstance | _: TypeError) => // TODO: we get here whenever pt contains a wildcardtype??? + debuglog(s"Error during SAM synthesis: could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)\n$e") + EmptyTree + } } /** Type check a function literal. diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 25498b73ce..6abd7f2f19 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -348,7 +348,6 @@ trait StdNames { val REIFY_FREE_THIS_SUFFIX: NameType = "$this" val REIFY_FREE_VALUE_SUFFIX: NameType = "$value" val REIFY_SYMDEF_PREFIX: NameType = "symdef$" - val SAM_BODY_SUFFIX: NameType = "$body" val QUASIQUOTE_CASE: NameType = "$quasiquote$case$" val QUASIQUOTE_EARLY_DEF: NameType = "$quasiquote$early$def$" val QUASIQUOTE_FILE: String = "" diff --git a/test/files/neg/sammy_restrictions.check b/test/files/neg/sammy_restrictions.check index 8cc49f9aa9..0276f3a067 100644 --- a/test/files/neg/sammy_restrictions.check +++ b/test/files/neg/sammy_restrictions.check @@ -8,9 +8,6 @@ sammy_restrictions.scala:32: error: type mismatch; required: TwoAbstract ((x: Int) => 0): TwoAbstract ^ -sammy_restrictions.scala:34: error: class type required but DerivedOneAbstract with OneAbstract found - ((x: Int) => 0): NonClassType // "class type required". I think we should avoid SAM translation here. - ^ sammy_restrictions.scala:35: error: type mismatch; found : Int => Int required: NoEmptyConstructor @@ -46,4 +43,4 @@ sammy_restrictions.scala:44: error: type mismatch; required: PolyMethod ((x: Int) => 0): PolyMethod ^ -10 errors found +9 errors found diff --git a/test/files/neg/sammy_restrictions.scala b/test/files/neg/sammy_restrictions.scala index d003cfaf36..101342ad0b 100644 --- a/test/files/neg/sammy_restrictions.scala +++ b/test/files/neg/sammy_restrictions.scala @@ -31,7 +31,7 @@ object Test { (() => 0) : NoAbstract ((x: Int) => 0): TwoAbstract ((x: Int) => 0): DerivedOneAbstract // okay - ((x: Int) => 0): NonClassType // "class type required". I think we should avoid SAM translation here. + ((x: Int) => 0): NonClassType // okay -- we also allow type aliases in instantiation expressions, if they resolve to a class type ((x: Int) => 0): NoEmptyConstructor ((x: Int) => 0): OneEmptyConstructor // okay ((x: Int) => 0): OneEmptySecondaryConstructor // derived class must have an empty *primary* to call. diff --git a/test/files/pos/sammy_implicit.scala b/test/files/pos/sammy_implicit.scala index c9c2519bab..e4b82df4cc 100644 --- a/test/files/pos/sammy_implicit.scala +++ b/test/files/pos/sammy_implicit.scala @@ -6,5 +6,5 @@ abstract class SamImplicitConvert { implicit def conv(xs: Array[Int]): Lst[Int] - val encoded = flatMap (_.getBytes) + def encoded = flatMap (_.getBytes) } diff --git a/test/files/pos/sammy_poly.scala b/test/files/pos/sammy_poly.scala index c629be7166..75ee36f654 100644 --- a/test/files/pos/sammy_poly.scala +++ b/test/files/pos/sammy_poly.scala @@ -1,7 +1,8 @@ // test synthesizeSAMFunction where the sam type is not fully defined class T { trait F[T, U] { def apply(x: T): U } +// type F[T, U] = T => U // NOTE: the f(x) desugaring for now assumes the single abstract method is called 'apply' def app[T, U](x: T)(f: F[T, U]): U = f(x) app(1)(x => List(x)) -} \ No newline at end of file +} diff --git a/test/files/run/indylambda-boxing/test.scala b/test/files/run/indylambda-boxing/test.scala index cc0a460640..82f8d2f497 100644 --- a/test/files/run/indylambda-boxing/test.scala +++ b/test/files/run/indylambda-boxing/test.scala @@ -2,15 +2,16 @@ class Capture class Test { def test1 = (i: Int) => "" def test2 = (i: VC) => i - def test3 = (i: Int) => i + def test3 = (i: Int) => i // not adapted, specialized - def test4 = {val c = new Capture; (i: Int) => {(c, Test.this.toString); 42} } + def test4 = {val c = new Capture; (i: Int) => {(c, Test.this.toString); 42} } // not adapted, specialized def test5 = {val c = new Capture; (i: VC) => (c, Test.this.toString) } def test6 = {val c = new Capture; (i: Int) => (c, Test.this.toString) } def test7 = {val vc = new Capture; (i: Int) => vc } - def test8 = {val c = 42; (s: String) => (s, c)} + def test8 = {val c = 42; (s: String) => (s, c)} // not adapted def test9 = {val c = 42; (s: String) => ()} + def test10 = {(s: List[String]) => ()} } object Test { diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala index 758566fe53..d29f6b0a13 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala @@ -31,25 +31,44 @@ class IndyLambdaTest extends ClearAfterClass { case _ => Nil }.head } + + val obj = "Ljava/lang/Object;" + val str = "Ljava/lang/String;" + // unspecialized functions that have a primitive in parameter or return position // give rise to a "boxing bridge" method (which has the suffix `$adapted`). // This is because Scala's unboxing of null values gives zero, whereas Java's throw a NPE. // 1. Here we show that we are calling the boxing bridge (the lambda bodies here are compiled into // methods of `(I)Ljava/lang/Object;` / `(I)Ljava/lang/Object;` respectively.) - assertEquals("(Ljava/lang/Object;)Ljava/lang/Object;", implMethodDescriptorFor("(x: Int) => new Object")) - assertEquals("(Ljava/lang/Object;)Ljava/lang/Object;", implMethodDescriptorFor("(x: Object) => 0")) + assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x: Int) => new Object")) + assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x: Object) => 0")) // 2a. We don't need such adaptations for parameters or return values with types that differ // from Object due to other generic substitution, LambdaMetafactory will downcast the arguments. - assertEquals("(Ljava/lang/String;)Ljava/lang/String;", implMethodDescriptorFor("(x: String) => x")) + assertEquals(s"($str)$str", implMethodDescriptorFor("(x: String) => x")) // 2b. Testing 2a. in combination with 1. - assertEquals("(Ljava/lang/Object;)Ljava/lang/String;", implMethodDescriptorFor("(x: Int) => \"\"")) - assertEquals("(Ljava/lang/String;)Ljava/lang/Object;", implMethodDescriptorFor("(x: String) => 0")) + assertEquals(s"($obj)$str", implMethodDescriptorFor("(x: Int) => \"\"")) + assertEquals(s"($str)$obj", implMethodDescriptorFor("(x: String) => 0")) // 3. Specialized functions, don't need any of this as they implement a method like `apply$mcII$sp`, // and the (un)boxing is handled in the base class in code emitted by scalac. assertEquals("(I)I", implMethodDescriptorFor("(x: Int) => x")) + + // non-builtin sams are like specialized functions + compileClasses(compiler)("class VC(private val i: Int) extends AnyVal; trait FunVC { def apply(a: VC): VC }") + assertEquals("(I)I", implMethodDescriptorFor("((x: VC) => x): FunVC")) + + compileClasses(compiler)("trait Fun1[T, U] { def apply(a: T): U }") + assertEquals(s"($obj)$str", implMethodDescriptorFor("(x => x.toString): Fun1[Int, String]")) + assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x => println(x)): Fun1[Int, Unit]")) + assertEquals(s"($obj)$str", implMethodDescriptorFor("((x: VC) => \"\") : Fun1[VC, String]")) + assertEquals(s"($str)$obj", implMethodDescriptorFor("((x: String) => new VC(0)) : Fun1[String, VC]")) + + compileClasses(compiler)("trait Coll[A, Repr] extends Any") + compileClasses(compiler)("final class ofInt(val repr: Array[Int]) extends AnyVal with Coll[Int, Array[Int]]") + + assertEquals(s"([I)$obj", implMethodDescriptorFor("((xs: Array[Int]) => new ofInt(xs)): Array[Int] => Coll[Int, Array[Int]]")) } } -- cgit v1.2.3 From 8a7919bb543ee152dab9874abc37f9bbaac7eeaf Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 17 Mar 2016 09:45:42 -0700 Subject: Refactoring. Decakify TypeAdaptingTransformer --- .../scala/tools/nsc/transform/Delambdafy.scala | 4 +- .../scala/tools/nsc/transform/Erasure.scala | 5 +- .../nsc/transform/TypeAdaptingTransformer.scala | 90 ++++++++++------------ 3 files changed, 47 insertions(+), 52 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 8a318b194f..3262dd9202 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -172,8 +172,8 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre if (resTpOk && isReferenceType(samResultType) && functionResultType <:< samResultType) functionResultType else samResultType - val typeAdapter = new TypeAdapter { val typer = localTyper } - import typeAdapter.{box, unbox, cast, adaptToType, unboxValueClass} + val typeAdapter = new TypeAdapter { def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) } + import typeAdapter.{adaptToType, unboxValueClass} val targetParams = target.paramss.head val numCaptures = targetParams.length - functionParamTypes.length diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 58be6b53a0..be6316cc57 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -578,8 +578,9 @@ abstract class Erasure extends AddInterfaces } /** The modifier typer which retypes with erased types. */ - class Eraser(_context: Context) extends Typer(_context) with TypeAdapter { - val typer = this.asInstanceOf[analyzer.Typer] + class Eraser(_context: Context) extends Typer(_context) { + val typeAdapter = new TypeAdapter { def typedPos(pos: Position)(tree: Tree): Tree = Eraser.this.typedPos(pos)(tree) } + import typeAdapter._ override protected def stabilize(tree: Tree, pre: Type, mode: Mode, pt: Type): Tree = tree diff --git a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala index 246d9ebf3f..596091f75d 100644 --- a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala +++ b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala @@ -7,62 +7,58 @@ import scala.tools.nsc.ast.TreeDSL /** * A trait usable by transforms that need to adapt trees of one type to another type */ -trait TypeAdaptingTransformer { - self: TreeDSL => - - val analyzer: typechecker.Analyzer { val global: self.global.type } - - trait TypeAdapter { - val typer: analyzer.Typer +trait TypeAdaptingTransformer { self: TreeDSL => + abstract class TypeAdapter { import global._ import definitions._ - import CODE._ + + def typedPos(pos: Position)(tree: Tree): Tree private def isSafelyRemovableUnbox(fn: Tree, arg: Tree): Boolean = { currentRun.runDefinitions.isUnbox(fn.symbol) && { val cls = arg.tpe.typeSymbol - (cls == definitions.NullClass) || isBoxedValueClass(cls) + (cls == NullClass) || isBoxedValueClass(cls) } } - private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol) - def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner) - def isMethodTypeWithEmptyParams(tpe: Type) = tpe.isInstanceOf[MethodType] && tpe.params.isEmpty - def applyMethodWithEmptyParams(qual: Tree) = Apply(qual, List()) setPos qual.pos setType qual.tpe.resultType + private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol) + final def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner) + final def isMethodTypeWithEmptyParams(tpe: Type) = tpe.isInstanceOf[MethodType] && tpe.params.isEmpty + final def applyMethodWithEmptyParams(qual: Tree) = Apply(qual, List()) setPos qual.pos setType qual.tpe.resultType + + import CODE._ /** Box `tree` of unboxed type */ - def box(tree: Tree): Tree = tree match { + final def box(tree: Tree): Tree = tree match { case LabelDef(_, _, _) => val ldef = deriveLabelDef(tree)(box) ldef setType ldef.rhs.tpe case _ => val tree1 = tree.tpe match { - case ErasedValueType(clazz, _) => - New(clazz, cast(tree, underlyingOfValueClass(clazz))) - case _ => - tree.tpe.typeSymbol match { - case UnitClass => - if (treeInfo isExprSafeToInline tree) REF(BoxedUnit_UNIT) - else BLOCK(tree, REF(BoxedUnit_UNIT)) - case NothingClass => tree // a non-terminating expression doesn't need boxing - case x => - assert(x != ArrayClass) - tree match { - /* Can't always remove a Box(Unbox(x)) combination because the process of boxing x - * may lead to throwing an exception. - * - * This is important for specialization: calls to the super constructor should not box/unbox specialized - * fields (see TupleX). (ID) - */ - case Apply(boxFun, List(arg)) if isSafelyRemovableUnbox(tree, arg) => - log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}") - arg - case _ => - (REF(currentRun.runDefinitions.boxMethod(x)) APPLY tree) setPos (tree.pos) setType ObjectTpe - } - } + case ErasedValueType(clazz, _) => New(clazz, cast(tree, underlyingOfValueClass(clazz))) + case _ => tree.tpe.typeSymbol match { + case UnitClass => + if (treeInfo isExprSafeToInline tree) REF(BoxedUnit_UNIT) + else BLOCK(tree, REF(BoxedUnit_UNIT)) + case NothingClass => tree // a non-terminating expression doesn't need boxing + case x => + assert(x != ArrayClass) + tree match { + /* Can't always remove a Box(Unbox(x)) combination because the process of boxing x + * may lead to throwing an exception. + * + * This is important for specialization: calls to the super constructor should not box/unbox specialized + * fields (see TupleX). (ID) + */ + case Apply(boxFun, List(arg)) if isSafelyRemovableUnbox(tree, arg) => + log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}") + arg + case _ => + (REF(currentRun.runDefinitions.boxMethod(x)) APPLY tree) setPos (tree.pos) setType ObjectTpe + } + } } - typer.typedPos(tree.pos)(tree1) + typedPos(tree.pos)(tree1) } /** Unbox `tree` of boxed type to expected type `pt`. @@ -71,7 +67,7 @@ trait TypeAdaptingTransformer { * @param pt the expected type. * @return the unboxed tree */ - def unbox(tree: Tree, pt: Type): Tree = tree match { + final def unbox(tree: Tree, pt: Type): Tree = tree match { case LabelDef(_, _, _) => val ldef = deriveLabelDef(tree)(unbox(_, pt)) ldef setType ldef.rhs.tpe @@ -89,10 +85,10 @@ trait TypeAdaptingTransformer { Apply(currentRun.runDefinitions.unboxMethod(pt.typeSymbol), tree) } } - typer.typedPos(tree.pos)(tree1) + typedPos(tree.pos)(tree1) } - def unboxValueClass(tree: Tree, clazz: Symbol, underlying: Type): Tree = + final def unboxValueClass(tree: Tree, clazz: Symbol, underlying: Type): Tree = if (tree.tpe.typeSymbol == NullClass && isPrimitiveValueClass(underlying.typeSymbol)) { // convert `null` directly to underlying type, as going via the unboxed type would yield a NPE (see SI-5866) unbox(tree, underlying) @@ -105,13 +101,12 @@ trait TypeAdaptingTransformer { */ final def cast(tree: Tree, pt: Type): Tree = { if (settings.debug && (tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) { - def word = ( + def word = if (tree.tpe <:< pt) "upcast" else if (pt <:< tree.tpe) "downcast" else if (pt weak_<:< tree.tpe) "coerce" else if (tree.tpe weak_<:< pt) "widen" else "cast" - ) log(s"erasure ${word}s from ${tree.tpe} to $pt") } if (pt =:= UnitTpe) { @@ -123,7 +118,8 @@ trait TypeAdaptingTransformer { 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 gen.mkAttributedCast(tree, pt) + } else if (samMatchingFunction(tree, pt).exists) tree setType pt // SAM <:< FunctionN if sam is convertible to said function + else gen.mkAttributedCast(tree, pt) } /** Adapt `tree` to expected type `pt`. @@ -147,9 +143,7 @@ trait TypeAdaptingTransformer { if (gotPrimitiveVC && !expectedPrimitiveVC) adaptToType(box(tree), pt) else if (!gotPrimitiveVC && expectedPrimitiveVC) adaptToType(unbox(tree, pt), pt) - else if (samMatchingFunction(tree, pt).exists) { - tree setType pt - } else cast(tree, pt) + else cast(tree, pt) } } } -- cgit v1.2.3 From 5f71e2e43b526c197ce4b8efaa85df101bef4fc4 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 17 Mar 2016 11:05:53 -0700 Subject: For backwards compat, sammy comes last --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 5c108c5fda..6bf2ec46c8 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1055,13 +1055,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (hasUndets) return instantiate(tree, mode, pt) - // we know `!(tree.tpe <:< pt)`; try to remedy if there's a sam for pt - val sam = samMatchingFunction(tree, pt) // this implies tree.isInstanceOf[Function] - if (sam.exists) { - val samTree = adaptToSAM(sam, tree.asInstanceOf[Function], pt, mode) - if (samTree ne EmptyTree) return samTree - } - if (context.implicitsEnabled && !pt.isError && !tree.isErrorTyped) { // (14); the condition prevents chains of views debuglog("inferring view from " + tree.tpe + " to " + pt) @@ -1082,6 +1075,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } } + + // we know `!(tree.tpe <:< pt)`; try to remedy if there's a sam for pt + val sam = samMatchingFunction(tree, pt) // this implies tree.isInstanceOf[Function] + if (sam.exists) { + val samTree = adaptToSAM(sam, tree.asInstanceOf[Function], pt, mode) + if (samTree ne EmptyTree) return samTree + } } debuglog("error tree = " + tree) -- cgit v1.2.3 From a2795ba77c0e7b56b0a522eae0cca298af0ac1f1 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 18 Mar 2016 12:47:04 -0700 Subject: Refactoring. Simplify inferImplicit's boolean levers --- .../scala/reflect/reify/codegen/GenTypes.scala | 10 +--- .../scala/tools/nsc/typechecker/Implicits.scala | 39 +++++++++++-- .../scala/tools/nsc/typechecker/Tags.scala | 13 +---- .../scala/tools/nsc/typechecker/Typers.scala | 66 +++++++++++----------- .../doc/model/ModelFactoryImplicitSupport.scala | 2 +- test/files/neg/logImplicits.check | 2 +- 6 files changed, 73 insertions(+), 59 deletions(-) diff --git a/src/compiler/scala/reflect/reify/codegen/GenTypes.scala b/src/compiler/scala/reflect/reify/codegen/GenTypes.scala index d007df75e3..b2948f8161 100644 --- a/src/compiler/scala/reflect/reify/codegen/GenTypes.scala +++ b/src/compiler/scala/reflect/reify/codegen/GenTypes.scala @@ -106,14 +106,10 @@ trait GenTypes { private def spliceAsManifest(tpe: Type): Tree = { def isSynthetic(manifest: Tree) = manifest exists (sub => sub.symbol != null && (sub.symbol == FullManifestModule || sub.symbol.owner == FullManifestModule)) def searchForManifest(typer: analyzer.Typer): Tree = - analyzer.inferImplicit( - EmptyTree, + analyzer.inferImplicitByTypeSilent( appliedType(FullManifestClass.toTypeConstructor, List(tpe)), - reportAmbiguous = false, - isView = false, - context = typer.context, - saveAmbiguousDivergent = false, - pos = defaultErrorPosition) match { + typer.context, + defaultErrorPosition) match { case success if !success.tree.isEmpty && !isSynthetic(success.tree) => val manifestInScope = success.tree // todo. write a test for this diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index a34e97b6cb..6412cc09f9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -34,14 +34,33 @@ trait Implicits { import typingStack.{ printTyping } import typeDebug._ + // standard usage + def inferImplicitFor(pt: Type, tree: Tree, context: Context, reportAmbiguous: Boolean = true): SearchResult = + inferImplicit(tree, pt, reportAmbiguous, isView = false, context, saveAmbiguousDivergent = true, tree.pos) + + // used by typer to find an implicit coercion + def inferImplicitView(from: Type, to: Type, tree: Tree, context: Context, reportAmbiguous: Boolean, saveAmbiguousDivergent: Boolean) = + inferImplicit(tree, Function1(from, to), reportAmbiguous, isView = true, context, saveAmbiguousDivergent, tree.pos) + + // used for manifests, typetags, checking language features, scaladoc + def inferImplicitByType(pt: Type, context: Context, pos: Position = NoPosition): SearchResult = + inferImplicit(EmptyTree, pt, reportAmbiguous = true, isView = false, context, saveAmbiguousDivergent = true, pos) + + def inferImplicitByTypeSilent(pt: Type, context: Context, pos: Position = NoPosition): SearchResult = + inferImplicit(EmptyTree, pt, reportAmbiguous = false, isView = false, context, saveAmbiguousDivergent = false, pos) + + @deprecated("Unused in scalac") def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context): SearchResult = inferImplicit(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent = true, tree.pos) + @deprecated("Unused in scalac") def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean): SearchResult = inferImplicit(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent, tree.pos) - /** Search for an implicit value. See the comment on `result` at the end of class `ImplicitSearch` - * for more info how the search is conducted. + /** Search for an implicit value. Consider using one of the convenience methods above. This one has many boolean levers. + * + * See the comment on `result` at the end of class `ImplicitSearch` for more info how the search is conducted. + * * @param tree The tree for which the implicit needs to be inserted. * (the inference might instantiate some of the undetermined * type parameters of that tree. @@ -92,9 +111,13 @@ trait Implicits { /** A friendly wrapper over inferImplicit to be used in macro contexts and toolboxes. */ def inferImplicit(tree: Tree, pt: Type, isView: Boolean, context: Context, silent: Boolean, withMacrosDisabled: Boolean, pos: Position, onError: (Position, String) => Unit): Tree = { - val wrapper1 = if (!withMacrosDisabled) (context.withMacrosEnabled[SearchResult] _) else (context.withMacrosDisabled[SearchResult] _) - def wrapper(inference: => SearchResult) = wrapper1(inference) - val result = wrapper(inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context = context, saveAmbiguousDivergent = !silent, pos = pos)) + val result = + if (withMacrosDisabled) context.withMacrosDisabled { + inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context, saveAmbiguousDivergent = !silent, pos) + } else context.withMacrosEnabled { + inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context, saveAmbiguousDivergent = !silent, pos) + } + if (result.isFailure && !silent) { val err = context.reporter.firstError val errPos = err.map(_.errPos).getOrElse(pos) @@ -304,6 +327,10 @@ trait Implicits { */ object Function1 { val Sym = FunctionClass(1) + val Pre = Sym.typeConstructor.prefix + + def apply(from: Type, to: Type) = TypeRef(Pre, Sym, List(from, to)) + // It is tempting to think that this should be inspecting "tp baseType Sym" // rather than tp. See test case run/t8280 and the commit message which // accompanies it for explanation why that isn't done. @@ -1207,7 +1234,7 @@ trait Implicits { /* Re-wraps a type in a manifest before calling inferImplicit on the result */ def findManifest(tp: Type, manifestClass: Symbol = if (full) FullManifestClass else PartialManifestClass) = - inferImplicit(tree, appliedType(manifestClass, tp), reportAmbiguous = true, isView = false, context).tree + inferImplicitFor(appliedType(manifestClass, tp), tree, context).tree def findSubManifest(tp: Type) = findManifest(tp, if (full) FullManifestClass else OptManifestClass) def mot(tp0: Type, from: List[Symbol], to: List[Type]): SearchResult = { diff --git a/src/compiler/scala/tools/nsc/typechecker/Tags.scala b/src/compiler/scala/tools/nsc/typechecker/Tags.scala index 56127f4026..21fdf75d66 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Tags.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Tags.scala @@ -13,16 +13,9 @@ trait Tags { private val runDefinitions = currentRun.runDefinitions private def resolveTag(pos: Position, taggedTp: Type, allowMaterialization: Boolean) = enteringTyper { - def wrapper (tree: => Tree): Tree = if (allowMaterialization) (context.withMacrosEnabled[Tree](tree)) else (context.withMacrosDisabled[Tree](tree)) - wrapper(inferImplicit( - EmptyTree, - taggedTp, - reportAmbiguous = true, - isView = false, - context, - saveAmbiguousDivergent = true, - pos - ).tree) + if (allowMaterialization) context.withMacrosEnabled{ inferImplicitByType(taggedTp, context, pos).tree } + else context.withMacrosDisabled{ inferImplicitByType(taggedTp, context, pos).tree } + } /** Finds in scope or materializes a ClassTag. diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 6bf2ec46c8..f581eab00f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -158,7 +158,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper for(ar <- argResultsBuff) paramTp = paramTp.subst(ar.subst.from, ar.subst.to) - val res = if (paramFailed || (paramTp.isErroneous && {paramFailed = true; true})) SearchFailure else inferImplicit(fun, paramTp, context.reportErrors, isView = false, context) + val res = + if (paramFailed || (paramTp.isErroneous && {paramFailed = true; true})) SearchFailure + else inferImplicitFor(paramTp, fun, context, reportAmbiguous = context.reportErrors) argResultsBuff += res if (res.isSuccess) { @@ -194,14 +196,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper !from.isError && !to.isError && context.implicitsEnabled - && (inferView(context.tree, from, to, reportAmbiguous = false, saveErrors = true) != EmptyTree) + && (inferView(context.tree, from, to, reportAmbiguous = false) != EmptyTree) // SI-8230 / SI-8463 We'd like to change this to `saveErrors = false`, but can't. // For now, we can at least pass in `context.tree` rather then `EmptyTree` so as // to avoid unpositioned type errors. ) - def inferView(tree: Tree, from: Type, to: Type, reportAmbiguous: Boolean): Tree = - inferView(tree, from, to, reportAmbiguous, saveErrors = true) /** Infer an implicit conversion (`view`) between two types. * @param tree The tree which needs to be converted. @@ -214,25 +214,23 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * during the inference of a view be put into the original buffer. * False iff we don't care about them. */ - def inferView(tree: Tree, from: Type, to: Type, reportAmbiguous: Boolean, saveErrors: Boolean): Tree = { - debuglog("infer view from "+from+" to "+to)//debug - if (isPastTyper) EmptyTree - else from match { - case MethodType(_, _) => EmptyTree - case OverloadedType(_, _) => EmptyTree - case PolyType(_, _) => EmptyTree - case _ => - def wrapImplicit(from: Type): Tree = { - val result = inferImplicit(tree, functionType(from.withoutAnnotations :: Nil, to), reportAmbiguous, isView = true, context, saveAmbiguousDivergent = saveErrors) - if (result.subst != EmptyTreeTypeSubstituter) { - result.subst traverse tree - notifyUndetparamsInferred(result.subst.from, result.subst.to) - } - result.tree - } - wrapImplicit(from) orElse wrapImplicit(byNameType(from)) + def inferView(tree: Tree, from: Type, to: Type, reportAmbiguous: Boolean = true, saveErrors: Boolean = true): Tree = + if (isPastTyper || from.isInstanceOf[MethodType] || from.isInstanceOf[OverloadedType] || from.isInstanceOf[PolyType]) EmptyTree + else { + debuglog(s"Inferring view from $from to $to for $tree (reportAmbiguous= $reportAmbiguous, saveErrors=$saveErrors)") + + val fromNoAnnot = from.withoutAnnotations + val result = inferImplicitView(fromNoAnnot, to, tree, context, reportAmbiguous, saveErrors) match { + case fail if fail.isFailure => inferImplicitView(byNameType(fromNoAnnot), to, tree, context, reportAmbiguous, saveErrors) + case ok => ok + } + + if (result.subst != EmptyTreeTypeSubstituter) { + result.subst traverse tree + notifyUndetparamsInferred(result.subst.from, result.subst.to) + } + result.tree } - } import infer._ @@ -732,7 +730,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper featureTrait.owner.ownerChain.takeWhile(_ != languageFeatureModule.moduleClass).reverse val featureName = (nestedOwners map (_.name + ".")).mkString + featureTrait.name def action(): Boolean = { - def hasImport = inferImplicit(EmptyTree: Tree, featureTrait.tpe, reportAmbiguous = true, isView = false, context).isSuccess + def hasImport = inferImplicitByType(featureTrait.tpe, context).isSuccess def hasOption = settings.language contains featureName val OK = hasImport || hasOption if (!OK) { @@ -1057,15 +1055,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (context.implicitsEnabled && !pt.isError && !tree.isErrorTyped) { // (14); the condition prevents chains of views - debuglog("inferring view from " + tree.tpe + " to " + pt) - inferView(tree, tree.tpe, pt, reportAmbiguous = true) match { - case EmptyTree => + inferView(tree, tree.tpe, pt) match { + case EmptyTree => // didn't find a view -- fall through case coercion => - def msg = "inferred view from " + tree.tpe + " to " + pt + " = " + coercion + ":" + coercion.tpe - if (settings.logImplicitConv) - context.echo(tree.pos, msg) + if (settings.debug || settings.logImplicitConv) { + val msg = s"inferred view from ${tree.tpe} to $pt via $coercion: ${coercion.tpe}" + debuglog(msg) + if (settings.logImplicitConv) context.echo(tree.pos, msg) + } - debuglog(msg) val silentContext = context.makeImplicit(context.ambiguousErrors) val res = newTyper(silentContext).typed( new ApplyImplicitView(coercion, List(tree)) setPos tree.pos, mode, pt) @@ -1254,7 +1252,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * If no conversion is found, return `qual` unchanged. * */ - def adaptToArguments(qual: Tree, name: Name, args: List[Tree], pt: Type, reportAmbiguous: Boolean, saveErrors: Boolean): Tree = { + def adaptToArguments(qual: Tree, name: Name, args: List[Tree], pt: Type, reportAmbiguous: Boolean = true, saveErrors: Boolean = true): Tree = { def doAdapt(restpe: Type) = //util.trace("adaptToArgs "+qual+", name = "+name+", argtpes = "+(args map (_.tpe))+", pt = "+pt+" = ") adaptToMember(qual, HasMethodMatching(name, args map (_.tpe), restpe), reportAmbiguous, saveErrors) @@ -1270,7 +1268,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * a method `name`. If that's ambiguous try taking arguments into * account using `adaptToArguments`. */ - def adaptToMemberWithArgs(tree: Tree, qual: Tree, name: Name, mode: Mode, reportAmbiguous: Boolean, saveErrors: Boolean): Tree = { + def adaptToMemberWithArgs(tree: Tree, qual: Tree, name: Name, mode: Mode, reportAmbiguous: Boolean = true, saveErrors: Boolean = true): Tree = { def onError(reportError: => Tree): Tree = context.tree match { case Apply(tree1, args) if (tree1 eq tree) && args.nonEmpty => ( silent (_.typedArgs(args.map(_.duplicate), mode)) @@ -4419,7 +4417,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper tryTypedArgs(args, forArgMode(fun, mode)) match { case Some(args1) if !args1.exists(arg => arg.exists(_.isErroneous)) => val qual1 = - if (!pt.isError) adaptToArguments(qual, name, args1, pt, reportAmbiguous = true, saveErrors = true) + if (!pt.isError) adaptToArguments(qual, name, args1, pt) else qual if (qual1 ne qual) { val tree1 = Apply(Select(qual1, name) setPos fun.pos, args1) setPos tree.pos @@ -4646,7 +4644,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // member. Added `| PATTERNmode` to allow enrichment in patterns (so we can add e.g., an // xml member to StringContext, which in turn has an unapply[Seq] method) if (name != nme.CONSTRUCTOR && mode.inAny(EXPRmode | PATTERNmode)) { - val qual1 = adaptToMemberWithArgs(tree, qual, name, mode, reportAmbiguous = true, saveErrors = true) + val qual1 = adaptToMemberWithArgs(tree, qual, name, mode) if ((qual1 ne qual) && !qual1.isErrorTyped) return typed(treeCopy.Select(tree, qual1, name), mode, pt) } diff --git a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index 830d902b68..e67a717257 100644 --- a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -236,7 +236,7 @@ trait ModelFactoryImplicitSupport { try { // TODO: Not sure if `owner = sym.owner` is the right thing to do -- seems similar to what scalac should be doing val silentContext = context.make(owner = sym.owner).makeSilent(reportAmbiguousErrors = false) - val search = inferImplicit(EmptyTree, tpe, false, false, silentContext, false) + val search = inferImplicitByTypeSilent(tpe, silentContext) available = Some(search.tree != EmptyTree) } catch { case _: TypeError => diff --git a/test/files/neg/logImplicits.check b/test/files/neg/logImplicits.check index df7b359767..479bf4ba2c 100644 --- a/test/files/neg/logImplicits.check +++ b/test/files/neg/logImplicits.check @@ -4,7 +4,7 @@ logImplicits.scala:2: applied implicit conversion from xs.type to ?{def size: ?} logImplicits.scala:7: applied implicit conversion from String("abc") to ?{def map: ?} = implicit def augmentString(x: String): scala.collection.immutable.StringOps def f = "abc" map (_ + 1) ^ -logImplicits.scala:15: inferred view from String("abc") to Int = C.this.convert:(p: String)Int +logImplicits.scala:15: inferred view from String("abc") to Int via C.this.convert: (p: String)Int math.max(122, x: Int) ^ logImplicits.scala:19: applied implicit conversion from Int(1) to ?{def ->: ?} = implicit def ArrowAssoc[A](self: A): ArrowAssoc[A] -- cgit v1.2.3 From 040c0434d456dd75a174147d8a0c4cab37266ba6 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 17 Mar 2016 17:16:28 -0700 Subject: More fixes based on feedback by Lukas Crucially, the fully-defined expected type must be checked for conformance to the original expected type!! The logic in adaptToSam that checks whether pt is fully defined probably needs some more thought. See pos/t8310 for a good test case. Argument type checking is a challenge, as we first check against a lenient pt (this lenient expected type has wildcards, and thus is not fully defined, but we should still consider sam adaptation a success even if we end up with wildcards for some unknown type parameters, they should be determined later). --- spec/03-types.md | 2 +- .../scala/tools/nsc/typechecker/Typers.scala | 28 +++++++++++----------- test/files/neg/sammy_expected.check | 6 +++++ test/files/neg/sammy_expected.scala | 5 ++++ test/files/neg/sammy_overload.check | 7 ++++++ test/files/neg/sammy_overload.scala | 13 ++++++++++ test/files/pos/sam_ctor_arg.scala | 4 ---- test/files/pos/sam_infer_argtype_subtypes.scala | 6 ----- test/files/pos/sam_inferargs.scala | 6 ----- test/files/pos/sammy_ctor_arg.scala | 4 ++++ test/files/pos/sammy_infer_argtype_subtypes.scala | 6 +++++ test/files/pos/sammy_inferargs.scala | 6 +++++ test/files/run/sam_return.scala | 14 ----------- test/files/run/sammy_repeated.check | 1 - test/files/run/sammy_repeated.scala | 8 ------- test/files/run/sammy_return.scala | 14 +++++++++++ test/files/run/sammy_vararg_cbn.check | 1 + test/files/run/sammy_vararg_cbn.scala | 12 ++++++++++ 18 files changed, 89 insertions(+), 54 deletions(-) create mode 100644 test/files/neg/sammy_expected.check create mode 100644 test/files/neg/sammy_expected.scala create mode 100644 test/files/neg/sammy_overload.check create mode 100644 test/files/neg/sammy_overload.scala delete mode 100644 test/files/pos/sam_ctor_arg.scala delete mode 100644 test/files/pos/sam_infer_argtype_subtypes.scala delete mode 100644 test/files/pos/sam_inferargs.scala create mode 100644 test/files/pos/sammy_ctor_arg.scala create mode 100644 test/files/pos/sammy_infer_argtype_subtypes.scala create mode 100644 test/files/pos/sammy_inferargs.scala delete mode 100644 test/files/run/sam_return.scala delete mode 100644 test/files/run/sammy_repeated.check delete mode 100644 test/files/run/sammy_repeated.scala create mode 100644 test/files/run/sammy_return.scala create mode 100644 test/files/run/sammy_vararg_cbn.check create mode 100644 test/files/run/sammy_vararg_cbn.scala diff --git a/spec/03-types.md b/spec/03-types.md index 16c48bcf6c..e2a6523dff 100644 --- a/spec/03-types.md +++ b/spec/03-types.md @@ -986,7 +986,7 @@ def foo(x: ToString): Unit trait ToString { def convert(x: Int): String } ``` -The application `foo(_.toString)` [resolves](06-expressions.html#overloading-resolution) to the first overload, +The application `foo((x: Int) => x.toString)` [resolves](06-expressions.html#overloading-resolution) to the first overload, as it's more specific: - `Int => String` is compatible to `ToString` -- when expecting a value of type `ToString`, you may pass a function literal from `Int` to `String`, as it will be SAM-converted to said function; - `ToString` is not compatible to `Int => String` -- when expecting a function from `Int` to `String`, you may not pass a `ToString`. diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index f581eab00f..4f006fe9a9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2726,7 +2726,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * * ``` * new S { - * def apply$body(p1: T1, ..., pN: TN): T = body + * def apply$body(p1: T1, ..., pN: TN): T = body * def apply(p1: T1', ..., pN: TN'): T' = apply$body(p1,..., pN) * } * ``` @@ -2737,27 +2737,28 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * The types T1' ... TN' and T' are derived from the method signature of the sam method, * as seen from the fully defined `samClassTpFullyDefined`. * - * The function's body is put in a static method in the class definition to enforce scoping. + * The function's body is put in a (static) method in the class definition to enforce scoping. * S's members should not be in scope in `body`. (Putting it in the block outside the class runs into implementation problems described below) * * The restriction on implicit arguments (neither S's constructor, nor sam may take an implicit argument list), - * is largely to keep the implementation of type inference (the computation of `samClassTpFullyDefined`) simple. + * is to keep the implementation of type inference (the computation of `samClassTpFullyDefined`) simple. + * + * Impl notes: + * - `fun` has a FunctionType, but the expected type `pt` is some SAM type -- let's remedy that + * - `fun` is fully attributed, so we'll have to wrangle some symbols into shape (owner change, vparam syms) + * - after experimentation, it works best to type check function literals fully first and then adapt to a sam type, + * as opposed to a sam-specific code paths earlier on in type checking (in typedFunction). + * For one, we want to emit the same bytecode regardless of whether the expected + * function type is a built-in FunctionN or some SAM type * */ def adaptToSAM(sam: Symbol, fun: Function, pt: Type, mode: Mode): Tree = { - // `fun` has a FunctionType, but the expected type `pt` is some SAM type -- let's remedy that - // `fun` is fully attributed, so we'll have to wrangle some symbols into shape (owner change, vparam syms) - // I tried very hard to leave `fun` untyped and rework everything into the right shape and type check once, - // but couldn't make it work due to retyping that happens down the line - // (implicit conversion retries/retypechecking, CBN transform, super call arg context nesting weirdness) - - def funTpMatchesExpected(pt: Type): Boolean = isFullyDefined(pt) && { - // what's the signature of the method that we should actually be overriding? + def fullyDefinedMeetsExpectedFunTp(pt: Type): Boolean = isFullyDefined(pt) && { val samMethType = pt memberInfo sam fun.tpe <:< functionType(samMethType.paramTypes, samMethType.resultType) } - if (funTpMatchesExpected(pt)) fun.setType(pt) + if (fullyDefinedMeetsExpectedFunTp(pt)) fun.setType(pt) else try { val samClassSym = pt.typeSymbol @@ -2786,9 +2787,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper debuglog(s"sam infer: $pt --> ${appliedType(samTyCon, targs)} by ${fun.tpe} <:< $samInfoWithTVars --> $targs for $tparams") - // a fully defined samClassTp val ptFullyDefined = appliedType(samTyCon, targs) - if (funTpMatchesExpected(ptFullyDefined)) { + if (ptFullyDefined <:< pt && fullyDefinedMeetsExpectedFunTp(ptFullyDefined)) { debuglog(s"sam fully defined expected type: $ptFullyDefined from $pt for ${fun.tpe}") fun.setType(ptFullyDefined) } else { diff --git a/test/files/neg/sammy_expected.check b/test/files/neg/sammy_expected.check new file mode 100644 index 0000000000..3b76aabdd2 --- /dev/null +++ b/test/files/neg/sammy_expected.check @@ -0,0 +1,6 @@ +sammy_expected.scala:4: error: type mismatch; + found : String => Int + required: F[Object,Int] + def wrong: F[Object, Int] = (x: String) => 1 + ^ +one error found diff --git a/test/files/neg/sammy_expected.scala b/test/files/neg/sammy_expected.scala new file mode 100644 index 0000000000..8fc1f66ff7 --- /dev/null +++ b/test/files/neg/sammy_expected.scala @@ -0,0 +1,5 @@ +trait F[A, B] { def apply(x: A): B } + +class MustMeetExpected { + def wrong: F[Object, Int] = (x: String) => 1 +} \ No newline at end of file diff --git a/test/files/neg/sammy_overload.check b/test/files/neg/sammy_overload.check new file mode 100644 index 0000000000..903d7c88f4 --- /dev/null +++ b/test/files/neg/sammy_overload.check @@ -0,0 +1,7 @@ +sammy_overload.scala:11: error: missing parameter type for expanded function ((x$1: ) => x$1.toString) + O.m(_.toString) // error expected: eta-conversion breaks down due to overloading + ^ +sammy_overload.scala:12: error: missing parameter type + O.m(x => x) // error expected: needs param type + ^ +two errors found diff --git a/test/files/neg/sammy_overload.scala b/test/files/neg/sammy_overload.scala new file mode 100644 index 0000000000..91c52cf96c --- /dev/null +++ b/test/files/neg/sammy_overload.scala @@ -0,0 +1,13 @@ +trait ToString { def convert(x: Int): String } + +class ExplicitSamType { + object O { + def m(x: Int => String): Int = 0 + def m(x: ToString): Int = 1 + } + + O.m((x: Int) => x.toString) // ok, function type takes precedence + + O.m(_.toString) // error expected: eta-conversion breaks down due to overloading + O.m(x => x) // error expected: needs param type +} diff --git a/test/files/pos/sam_ctor_arg.scala b/test/files/pos/sam_ctor_arg.scala deleted file mode 100644 index 3c556d59f0..0000000000 --- a/test/files/pos/sam_ctor_arg.scala +++ /dev/null @@ -1,4 +0,0 @@ -trait Fun[A, B] { def apply(a: A): B } -// can't do sam expansion until the sam body def is a static method in the sam class, and not a local method in a block' -class C(f: Fun[Int, String]) -class Test extends C(s => "a") \ No newline at end of file diff --git a/test/files/pos/sam_infer_argtype_subtypes.scala b/test/files/pos/sam_infer_argtype_subtypes.scala deleted file mode 100644 index 63966f879e..0000000000 --- a/test/files/pos/sam_infer_argtype_subtypes.scala +++ /dev/null @@ -1,6 +0,0 @@ -trait Fun[A, B] { def apply(a: A): B } - -class SamInferResult { - def foreach[U](f: Fun[String, U]): U = ??? - def foo = foreach(println) -} \ No newline at end of file diff --git a/test/files/pos/sam_inferargs.scala b/test/files/pos/sam_inferargs.scala deleted file mode 100644 index 10d9b4f0dd..0000000000 --- a/test/files/pos/sam_inferargs.scala +++ /dev/null @@ -1,6 +0,0 @@ -trait Proc { def apply(): Unit } -class Test { - val initCode = List[Proc]() - initCode foreach { proc => proc() } - -} diff --git a/test/files/pos/sammy_ctor_arg.scala b/test/files/pos/sammy_ctor_arg.scala new file mode 100644 index 0000000000..3c556d59f0 --- /dev/null +++ b/test/files/pos/sammy_ctor_arg.scala @@ -0,0 +1,4 @@ +trait Fun[A, B] { def apply(a: A): B } +// can't do sam expansion until the sam body def is a static method in the sam class, and not a local method in a block' +class C(f: Fun[Int, String]) +class Test extends C(s => "a") \ No newline at end of file diff --git a/test/files/pos/sammy_infer_argtype_subtypes.scala b/test/files/pos/sammy_infer_argtype_subtypes.scala new file mode 100644 index 0000000000..63966f879e --- /dev/null +++ b/test/files/pos/sammy_infer_argtype_subtypes.scala @@ -0,0 +1,6 @@ +trait Fun[A, B] { def apply(a: A): B } + +class SamInferResult { + def foreach[U](f: Fun[String, U]): U = ??? + def foo = foreach(println) +} \ No newline at end of file diff --git a/test/files/pos/sammy_inferargs.scala b/test/files/pos/sammy_inferargs.scala new file mode 100644 index 0000000000..10d9b4f0dd --- /dev/null +++ b/test/files/pos/sammy_inferargs.scala @@ -0,0 +1,6 @@ +trait Proc { def apply(): Unit } +class Test { + val initCode = List[Proc]() + initCode foreach { proc => proc() } + +} diff --git a/test/files/run/sam_return.scala b/test/files/run/sam_return.scala deleted file mode 100644 index e959619dd1..0000000000 --- a/test/files/run/sam_return.scala +++ /dev/null @@ -1,14 +0,0 @@ -trait Fun[A, B] { def apply(a: A): B } -class PF[A, B] { def runWith[U](action: Fun[B, U]): Fun[A, Boolean] = a => {action(a.asInstanceOf[B]); true} } - -class TO[A](x: A) { - def foreach[U](f: Fun[A, U]): U = f(x) - def collectFirst[B](pf: PF[A, B]): Option[B] = { - foreach(pf.runWith(b => return Some(b))) - None - } -} - -object Test extends App { - assert(new TO("a").collectFirst(new PF[String, String]).get == "a") -} \ No newline at end of file diff --git a/test/files/run/sammy_repeated.check b/test/files/run/sammy_repeated.check deleted file mode 100644 index 1cff0f067c..0000000000 --- a/test/files/run/sammy_repeated.check +++ /dev/null @@ -1 +0,0 @@ -WrappedArray(1) diff --git a/test/files/run/sammy_repeated.scala b/test/files/run/sammy_repeated.scala deleted file mode 100644 index c24dc41909..0000000000 --- a/test/files/run/sammy_repeated.scala +++ /dev/null @@ -1,8 +0,0 @@ -trait RepeatedSink { def accept(a: Any*): Unit } - -object Test { - def main(args: Array[String]): Unit = { - val f: RepeatedSink = (a) => println(a) - f.accept(1) - } -} \ No newline at end of file diff --git a/test/files/run/sammy_return.scala b/test/files/run/sammy_return.scala new file mode 100644 index 0000000000..e959619dd1 --- /dev/null +++ b/test/files/run/sammy_return.scala @@ -0,0 +1,14 @@ +trait Fun[A, B] { def apply(a: A): B } +class PF[A, B] { def runWith[U](action: Fun[B, U]): Fun[A, Boolean] = a => {action(a.asInstanceOf[B]); true} } + +class TO[A](x: A) { + def foreach[U](f: Fun[A, U]): U = f(x) + def collectFirst[B](pf: PF[A, B]): Option[B] = { + foreach(pf.runWith(b => return Some(b))) + None + } +} + +object Test extends App { + assert(new TO("a").collectFirst(new PF[String, String]).get == "a") +} \ No newline at end of file diff --git a/test/files/run/sammy_vararg_cbn.check b/test/files/run/sammy_vararg_cbn.check new file mode 100644 index 0000000000..1cff0f067c --- /dev/null +++ b/test/files/run/sammy_vararg_cbn.check @@ -0,0 +1 @@ +WrappedArray(1) diff --git a/test/files/run/sammy_vararg_cbn.scala b/test/files/run/sammy_vararg_cbn.scala new file mode 100644 index 0000000000..e5b49498ea --- /dev/null +++ b/test/files/run/sammy_vararg_cbn.scala @@ -0,0 +1,12 @@ +trait SamRepeated { def accept(a: Any*): Unit } +trait SamByName { def accept(a: => Any): (Any, Any) } + +object Test extends App { + val rep: SamRepeated = (a) => println(a) + rep.accept(1) + + val nam: SamByName = (a) => (a, a) + var v = 0 + assert(nam.accept({v += 1; v}) == (1, 2)) + assert(v == 2, "by name arg should be evaluated twice") +} -- cgit v1.2.3 From f922f367d58b3ba6bbb4cb0864ce82c5cd6f7966 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 17 Mar 2016 11:56:14 -0700 Subject: Additional SAM restrictions identified by Jason Also test roundtripping serialization of a lambda that targets a SAM that's not FunctionN (it should make no difference). --- spec/06-expressions.md | 7 ++- .../scala/reflect/internal/Definitions.scala | 25 ++++++--- test/files/neg/sammy_error_exist_no_crash.scala | 4 +- test/files/neg/sammy_restrictions.check | 55 ++++++++----------- test/files/neg/sammy_restrictions.scala | 63 ++++++++++++---------- test/files/pos/sammy_implicit.scala | 3 +- test/files/pos/sammy_overload.scala | 27 +++++++++- test/files/pos/sammy_poly.scala | 12 +++-- test/files/pos/sammy_scope.scala | 4 +- test/files/run/lambda-serialization.scala | 14 ++++- .../tools/nsc/backend/jvm/opt/CallGraphTest.scala | 2 +- .../nsc/backend/jvm/opt/ScalaInlineInfoTest.scala | 6 +-- 12 files changed, 139 insertions(+), 83 deletions(-) diff --git a/spec/06-expressions.md b/spec/06-expressions.md index f69c75bb96..2b93842a25 100644 --- a/spec/06-expressions.md +++ b/spec/06-expressions.md @@ -1365,8 +1365,11 @@ An expression `(p1, ..., pN) => body` of function type `(T1, ..., TN) => T` is s It follows that: - the type `S` must have an accessible, no-argument, constructor; - - the class of `S` must not be `@specialized`; - - the class of `S` must not be nested or local (it must not capture its environment). + - the class of `S` must not be nested or local (it must not capture its environment, as that precludes a zero-argument constructor). + +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`. ### Method Conversions diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 2fa39bc453..ef9a76f9c4 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -844,13 +844,26 @@ trait Definitions extends api.StandardDefinitions { * has a public no-arg primary constructor. */ def samOf(tp: Type): Symbol = { - // 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 tpSym = tp.typeSymbol - val ctor = tpSym.primaryConstructor - val ctorOk = !ctor.exists || (!ctor.isOverloaded && ctor.isPublic && ctor.info.params.isEmpty && ctor.info.paramSectionCount <= 1) + // look at erased type because we (only) care about what ends up in bytecode + // (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 && ctorOk) { // 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: // scala> abstract class X { def m: Int } diff --git a/test/files/neg/sammy_error_exist_no_crash.scala b/test/files/neg/sammy_error_exist_no_crash.scala index da7e47206f..667b4db763 100644 --- a/test/files/neg/sammy_error_exist_no_crash.scala +++ b/test/files/neg/sammy_error_exist_no_crash.scala @@ -1,6 +1,6 @@ -abstract class F[T] { def apply(s: T): Int } +trait F[T] { def apply(s: T): Int } object NeedsNiceError { def bar(x: F[_ >: String]) = ??? bar(_.parseInt) -} \ No newline at end of file +} diff --git a/test/files/neg/sammy_restrictions.check b/test/files/neg/sammy_restrictions.check index 0276f3a067..5fd2c858c2 100644 --- a/test/files/neg/sammy_restrictions.check +++ b/test/files/neg/sammy_restrictions.check @@ -1,46 +1,37 @@ -sammy_restrictions.scala:31: error: type mismatch; +sammy_restrictions.scala:37: error: type mismatch; found : () => Int required: NoAbstract - (() => 0) : NoAbstract + (() => 0) : NoAbstract // error expected ^ -sammy_restrictions.scala:32: error: type mismatch; - found : Int => Int - required: TwoAbstract - ((x: Int) => 0): TwoAbstract - ^ -sammy_restrictions.scala:35: error: type mismatch; - found : Int => Int - required: NoEmptyConstructor - ((x: Int) => 0): NoEmptyConstructor - ^ -sammy_restrictions.scala:37: error: type mismatch; - found : Int => Int - required: OneEmptySecondaryConstructor - ((x: Int) => 0): OneEmptySecondaryConstructor // derived class must have an empty *primary* to call. - ^ sammy_restrictions.scala:38: error: type mismatch; found : Int => Int - required: MultipleConstructorLists - ((x: Int) => 0): MultipleConstructorLists + required: TwoAbstract + ((x: Int) => 0): TwoAbstract // error expected ^ -sammy_restrictions.scala:39: error: type mismatch; +sammy_restrictions.scala:41: error: type mismatch; found : Int => Int required: MultipleMethodLists - ((x: Int) => 0): MultipleMethodLists + ((x: Int) => 0): MultipleMethodLists // error expected ^ -sammy_restrictions.scala:40: error: type mismatch; - found : Int => Int - required: ImplicitConstructorParam - ((x: Int) => 0): ImplicitConstructorParam - ^ -sammy_restrictions.scala:41: error: type mismatch; +sammy_restrictions.scala:42: error: type mismatch; found : Int => Int required: ImplicitMethodParam - ((x: Int) => 0): ImplicitMethodParam + ((x: Int) => 0): ImplicitMethodParam // error expected ^ -sammy_restrictions.scala:44: error: type mismatch; +sammy_restrictions.scala:45: error: type mismatch; found : Int => Int required: PolyMethod - ((x: Int) => 0): PolyMethod - ^ -9 errors found + ((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 diff --git a/test/files/neg/sammy_restrictions.scala b/test/files/neg/sammy_restrictions.scala index 101342ad0b..ed8cf35aa4 100644 --- a/test/files/neg/sammy_restrictions.scala +++ b/test/files/neg/sammy_restrictions.scala @@ -1,45 +1,52 @@ -abstract class NoAbstract +trait NoAbstract -abstract class TwoAbstract { def ap(a: Int): Int; def pa(a: Int): Int } +trait TwoAbstract { def ap(a: Int): Int; def pa(a: Int): Int } -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 Base // check that the super class constructor isn't considered. -abstract class OneEmptyConstructor() { def this(a: Int) = this(); def ap(a: Int): Int } +trait MultipleMethodLists { def ap(a: Int)(): Int } -abstract class OneEmptySecondaryConstructor(a: Int) { def this() = this(0); def ap(a: Int): Int } +trait ImplicitMethodParam { def ap(a: Int)(implicit b: String): Int } -abstract class MultipleConstructorLists()() { def ap(a: Int): Int } +trait PolyClass[T] { def ap(a: T): T } -abstract class MultipleMethodLists()() { def ap(a: Int)(): Int } +trait PolyMethod { def ap[T](a: T): T } -abstract class ImplicitConstructorParam()(implicit a: String) { def ap(a: Int): Int } +trait OneAbstract { def ap(a: Int): Any } +trait DerivedOneAbstract extends OneAbstract -abstract class ImplicitMethodParam() { def ap(a: Int)(implicit b: String): Int } +// restrictions -abstract class PolyClass[T] { def ap(a: T): T } +// must be an interface +abstract class NotAnInterface[T, R]{ def apply(x: T): R } -abstract class PolyMethod { def ap[T](a: T): T } +trait A[T, R]{ def apply(x: T): R } + +// 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 object Test { implicit val s: String = "" type NonClassType = DerivedOneAbstract with OneAbstract - (() => 0) : NoAbstract - ((x: Int) => 0): TwoAbstract - ((x: Int) => 0): DerivedOneAbstract // okay - ((x: Int) => 0): NonClassType // okay -- we also allow type aliases in instantiation expressions, if they resolve to a class type - ((x: Int) => 0): NoEmptyConstructor - ((x: Int) => 0): OneEmptyConstructor // okay - ((x: Int) => 0): OneEmptySecondaryConstructor // derived class must have an empty *primary* to call. - ((x: Int) => 0): MultipleConstructorLists - ((x: Int) => 0): MultipleMethodLists - ((x: Int) => 0): ImplicitConstructorParam - ((x: Int) => 0): ImplicitMethodParam - - ((x: Int) => 0): PolyClass[Int] // okay - ((x: Int) => 0): PolyMethod + (() => 0) : NoAbstract // error expected + ((x: Int) => 0): TwoAbstract // error expected + ((x: Int) => 0): DerivedOneAbstract + ((x: Int) => 0): NonClassType + ((x: Int) => 0): MultipleMethodLists // error expected + ((x: Int) => 0): ImplicitMethodParam // error expected + + ((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) } diff --git a/test/files/pos/sammy_implicit.scala b/test/files/pos/sammy_implicit.scala index e4b82df4cc..ab63fc729e 100644 --- a/test/files/pos/sammy_implicit.scala +++ b/test/files/pos/sammy_implicit.scala @@ -1,5 +1,6 @@ +trait Fun[A, B] { def apply(a: A): B } + abstract class SamImplicitConvert { - trait Fun[A, B] { def apply(a: A): B } class Lst[T] abstract class Str { def getBytes: Array[Int] } def flatMap[B](f: Fun[Str, Lst[B]]): List[B] = ??? diff --git a/test/files/pos/sammy_overload.scala b/test/files/pos/sammy_overload.scala index 5472248f4d..6a3c88ec55 100644 --- a/test/files/pos/sammy_overload.scala +++ b/test/files/pos/sammy_overload.scala @@ -6,4 +6,29 @@ object Test { def foo(x: String): Unit = ??? def foo(): Unit = ??? val f: Consumer[_ >: String] = foo -} \ No newline at end of file +} + +trait A[A, B] { def apply(a: A): B } + +class ArityDisambiguate { + object O { + def m(a: A[Int, Int]) = 0 + def m(f: (Int, Int) => Int) = 1 + } + + O.m(x => x) // ok + O.m((x, y) => x) // ok +} + +class InteractionWithImplicits { + object O { + class Ev + implicit object E1 extends Ev + implicit object E2 extends Ev + def m(a: A[Int, Int])(implicit ol: E1.type) = 0 + def m(a: A[String, Int])(implicit ol: E2.type) = 1 + } + + O.m((x: Int) => 1) // ok + O.m((x: String) => 1) // ok +} diff --git a/test/files/pos/sammy_poly.scala b/test/files/pos/sammy_poly.scala index 75ee36f654..ba10baea49 100644 --- a/test/files/pos/sammy_poly.scala +++ b/test/files/pos/sammy_poly.scala @@ -1,8 +1,12 @@ // test synthesizeSAMFunction where the sam type is not fully defined -class T { - trait F[T, U] { def apply(x: T): U } -// type F[T, U] = T => U - // NOTE: the f(x) desugaring for now assumes the single abstract method is called 'apply' +trait F[T, R]{ def apply(x: T): R } + +class PolySammy { + (x => x + 1): F[Int, Int] + ((x: Int) => x + 1): F[Int, Int] + ((x: String) => 1): F[String, Int] + ((x: Object) => 1): F[String, Int] + def app[T, U](x: T)(f: F[T, U]): U = f(x) app(1)(x => List(x)) } diff --git a/test/files/pos/sammy_scope.scala b/test/files/pos/sammy_scope.scala index 8f1fe7058e..9d35501a47 100644 --- a/test/files/pos/sammy_scope.scala +++ b/test/files/pos/sammy_scope.scala @@ -1,8 +1,8 @@ // test synthesizeSAMFunction: scope hygiene -abstract class SamFun[T1, R] { self => +trait SamFun[T1, R] { self => def apply(v1: T1): R // this should type check, as the apply ref is equivalent to self.apply // it shouldn't resolve to the sam's apply that's synthesized (that wouldn't type check, hence the pos test) def compose[A](g: SamFun[A, T1]): SamFun[A, R] = { x => apply(g(x)) } -} \ No newline at end of file +} diff --git a/test/files/run/lambda-serialization.scala b/test/files/run/lambda-serialization.scala index 46b26d7c5e..0eee1193d7 100644 --- a/test/files/run/lambda-serialization.scala +++ b/test/files/run/lambda-serialization.scala @@ -1,8 +1,11 @@ import java.io.{ByteArrayInputStream, ObjectInputStream, ObjectOutputStream, ByteArrayOutputStream} +trait IntToString { def apply(i: Int): String } + object Test { def main(args: Array[String]): Unit = { - roundTrip + roundTrip() + roundTripIndySam() } def roundTrip(): Unit = { @@ -22,6 +25,15 @@ object Test { assert(serializeDeserialize(serializeDeserialize(specializedLambda)).apply(42) == 2) } + // lambda targeting a SAM, not a FunctionN (should behave the same way) + def roundTripIndySam(): Unit = { + val lambda: IntToString = (x: Int) => "yo!" * x + val reconstituted1 = serializeDeserialize(lambda).asInstanceOf[IntToString] + val reconstituted2 = serializeDeserialize(reconstituted1).asInstanceOf[IntToString] + assert(reconstituted1.apply(2) == "yo!yo!") + assert(reconstituted1.getClass == reconstituted2.getClass) + } + def serializeDeserialize[T <: AnyRef](obj: T) = { val buffer = new ByteArrayOutputStream val out = new ObjectOutputStream(buffer) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala index 6e1ac3ba9f..b37b5efa7e 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -174,7 +174,7 @@ class CallGraphTest extends ClearAfterClass { | def t2(i: Int, f: Int => Int, z: Int) = h(f) + i - z | def t3(f: Int => Int) = h(x => f(x + 1)) |} - |abstract class D { + |trait D { | def iAmASam(x: Int): Int | def selfSamCall = iAmASam(10) |} diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala index 0ba0ecca4c..10ab006017 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala @@ -100,7 +100,7 @@ class ScalaInlineInfoTest extends ClearAfterClass { @Test def inlineInfoSam(): Unit = { val code = - """abstract class C { + """trait C { // expected to be seen as sam: g(I)I | def f = 0 | def g(x: Int): Int | val foo = "hi" @@ -108,10 +108,10 @@ class ScalaInlineInfoTest extends ClearAfterClass { |abstract class D { | val biz: Int |} - |trait T { + |trait T { // expected to be seen as sam: h(Ljava/lang/String;)I | def h(a: String): Int |} - |abstract class E extends T { + |trait E extends T { // expected to be seen as sam: h(Ljava/lang/String;)I | def hihi(x: Int) = x |} |class F extends T { -- cgit v1.2.3 From 2aa8eba5007f0e0eda3a2ef3fdffa1f468dc1fa4 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 21 Mar 2016 08:47:45 -0700 Subject: Test bytecode emitted for indy sammy Test that SAM conversion happens after implicit view application A function node is first type checked, and parameter types are inferred, regardless of whether the expected function type is one of our built-in FunctionN classes, or a user-defined Single Abstract Method type. `typedFunction` always assigns a built-in `FunctionN` type to the tree, though. Next, if the expected type is a (polymorphic) SAM type, this creates a tension between the tree's type and the expect type. This gap is closed by the adapt method, by applying one of the implicit conversion in the spec in order (e.g., numeric widening, implicit view application, and now, also SAM conversion) Thus, `adaptToSam` will assign the expected SAM type to the `Function` tree. (This may require some type inference.) The back-end will emit the right invokedynamic instruction that uses Java's LambdaMetaFactory to spin up a class that implements the target method (whether it's defined in FunctionN or some other Java functional interface). --- test/files/run/sammy_after_implicit_view.scala | 28 +++++ .../tools/nsc/backend/jvm/IndySammyTest.scala | 130 +++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 test/files/run/sammy_after_implicit_view.scala create mode 100644 test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala diff --git a/test/files/run/sammy_after_implicit_view.scala b/test/files/run/sammy_after_implicit_view.scala new file mode 100644 index 0000000000..30e3babc75 --- /dev/null +++ b/test/files/run/sammy_after_implicit_view.scala @@ -0,0 +1,28 @@ +trait MySam { def apply(x: Int): String } + +// check that SAM conversion happens after implicit view application +object Test extends App { + final val AnonFunClass = "$anon$" + final val LMFClass = "$$Lambda$" // LambdaMetaFactory names classes like this + + // if there's an implicit conversion, it takes precedence + def statusQuo() = { + import language.implicitConversions + var ok = false + implicit def fun2sam(fun: Int => String): MySam = { ok = true; new MySam { def apply(x: Int) = fun(x) } } + val className = (((x: Int) => x.toString): MySam).getClass.toString + assert(ok, "implicit conversion not called") + assert(className contains AnonFunClass, className) + assert(!(className contains LMFClass), className) + } + + // indirectly check that this sam type instance was created from a class spun up by LambdaMetaFactory + def statusIndy() = { + val className = (((x: Int) => x.toString): MySam).getClass.toString + assert(!(className contains AnonFunClass), className) + assert(className contains LMFClass, className) + } + + statusQuo() + statusIndy() +} \ No newline at end of file diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala new file mode 100644 index 0000000000..6f213e6625 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala @@ -0,0 +1,130 @@ +package scala.tools.nsc +package backend.jvm + +import org.junit.Assert.assertEquals +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test + +import scala.tools.asm.Opcodes._ +import scala.tools.asm.tree._ +import scala.tools.nsc.reporters.StoreReporter +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ + +import scala.tools.testing.ClearAfterClass + +object IndySammyTest extends ClearAfterClass.Clearable { + var _compiler = newCompiler() + + def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = + compileClasses(_compiler)(scalaCode, javaCode, allowMessage) + + def clear(): Unit = { _compiler = null } +} + +@RunWith(classOf[JUnit4]) +class IndySammyTest extends ClearAfterClass { + ClearAfterClass.stateToClear = IndySammyTest + import IndySammyTest._ + + val compiler = _compiler + + def funClassName(from: String, to: String) = s"Fun$from$to" + def classPrologue(from: String, to: String) = + "class VC(private val i: Int) extends AnyVal\n" + + s"trait ${funClassName(from, to)} { def apply(a: $from): $to}" + + def lamDef(from: String, to: String, body: String => String) = + s"""def lam = (x => ${body("x")}): ${funClassName(from, to)}""" + + def appDef(arg: String) = s"""def app = lam($arg)""" + + /* Create a lambda of type "$from => $to" (with body "$body(x)" if "x" is the argument name), + * and apply it to `arg`. + * + * Check: + * - the signature of the apply method + * - the instructions in the lambda's body (anonfun method) + * - the instructions used to create the argument for the application + * (and the return corresponding to the lambda's result type) + */ + def test(from: String, to: String, arg: String, body: String => String = x => x) + (expectedSig: String, lamBody: List[Instruction], appArgs: List[Instruction], ret: Instruction) + (allowMessage: StoreReporter#Info => Boolean = _ => false) = { + val cls = compile(s"${classPrologue(from, to)}") + val methodNodes = compileMethods(compiler)(lamDef(from, to, body) +";"+ appDef(arg), allowMessage) + + val applySig = cls.head.methods.get(0).desc + val anonfun = methodNodes.find(_.name contains "$anonfun$").map(convertMethod).get + val lamInsn = methodNodes.find(_.name == "lam").map(instructionsFromMethod).get.dropNonOp + val applyInvoke = methodNodes.find(_.name == "app").map(convertMethod).get + + assertEquals(expectedSig, applySig) + assert(lamInsn.length == 2 && lamInsn.head.isInstanceOf[InvokeDynamic], lamInsn) + assertSameCode(anonfun, lamBody) + assertSameCode(applyInvoke, List( + VarOp(ALOAD, 0), + Invoke(INVOKEVIRTUAL, "C", "lam", s"()L${funClassName(from, to)};", false)) ++ appArgs ++ List( + Invoke(INVOKEINTERFACE, funClassName(from, to), "apply", applySig, true), ret) + ) + } + + // x => x : VC => VC applied to VC(1) + @Test + def testVC_VC_VC = + test("VC", "VC", "new VC(1)")("(I)I", + List(VarOp(ILOAD, 0), Op(IRETURN)), + List(Op(ICONST_1)), + Op(IRETURN))() + + // x => new VC(x) : Int => VC applied to 1 + @Test + def testInt_VC_1 = + test("Int", "VC", "1", x => s"new VC($x)")("(I)I", + List(VarOp(ILOAD, 0), Op(IRETURN)), + List(Op(ICONST_1)), + Op(IRETURN))() + + // x => x : VC => Int applied to VC(1) + @Test + def testVC_Int_VC = + test("VC", "Int", "new VC(1)", x => "1")("(I)I", + List(Op(ICONST_1), Op(IRETURN)), + List(Op(ICONST_1)), + Op(IRETURN))() + + // x => new VC(1) : VC => Any applied to VC(1) + @Test + def testVC_Any_VC = + test("VC", "Any", "new VC(1)", x => s"new VC(1)")("(I)Ljava/lang/Object;", + List(TypeOp(NEW, "VC"), Op(DUP), Op(ICONST_1), Invoke(INVOKESPECIAL, "VC", "", "(I)V", false), Op(ARETURN)), + List(Op(ICONST_1)), + Op(ARETURN))() + + + // x => x : VC => Unit applied to VC(1) + @Test + def testVC_Unit_VC = + test("VC", "Unit", "new VC(1)")("(I)V", + List(VarOp(ILOAD, 0), Op(POP), Op(RETURN)), + List(Op(ICONST_1)), + Op(RETURN))(allowMessage = _.msg.contains("pure expression")) + + // x => new VC(x.asInstanceOf[Int]) : Any => VC applied to 1 + // + // Scala: + // def lam = (x => new VC(x.asInstanceOf[Int])): FunAny_VC + // def app = lam(1) + // Java: + // FunAny_VC lam() { return x -> BoxesRunTime.unboxToInt((Object)x); } + // int app() { lam().apply(BoxesRunTime.boxToInteger((int)1)); + @Test + def testAny_VC_1 = + test("Any", "VC", "1", x => s"new VC($x.asInstanceOf[Int])")("(Ljava/lang/Object;)I", + List(VarOp(ALOAD, 0), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "unboxToInt", "(Ljava/lang/Object;)I", false), Op(IRETURN)), + List(Op(ICONST_1), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "boxToInteger", "(I)Ljava/lang/Integer;", false)), + Op(IRETURN))() + +} -- cgit v1.2.3 From 49d946d9039a6240765abd26375883875e5ff7e8 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 21 Mar 2016 20:54:47 -0700 Subject: Refactor flag juggling. Review feedback from Jason. Sometimes booleans and a little duplication go a long way. --- .../scala/reflect/macros/contexts/Typers.scala | 35 +++++++++------- .../scala/tools/nsc/typechecker/Contexts.scala | 2 + .../scala/tools/nsc/typechecker/Implicits.scala | 13 +++--- .../scala/tools/nsc/typechecker/Tags.scala | 4 +- .../scala/tools/reflect/ToolBoxFactory.scala | 49 ++++++++++++---------- 5 files changed, 54 insertions(+), 49 deletions(-) diff --git a/src/compiler/scala/reflect/macros/contexts/Typers.scala b/src/compiler/scala/reflect/macros/contexts/Typers.scala index 28c1e3ddb3..baf066c7d9 100644 --- a/src/compiler/scala/reflect/macros/contexts/Typers.scala +++ b/src/compiler/scala/reflect/macros/contexts/Typers.scala @@ -18,22 +18,25 @@ trait Typers { * @see [[scala.tools.reflect.ToolBox.typeCheck]] */ def typecheck(tree: Tree, mode: TypecheckMode = TERMmode, pt: Type = universe.WildcardType, silent: Boolean = false, withImplicitViewsDisabled: Boolean = false, withMacrosDisabled: Boolean = false): Tree = { - macroLogVerbose("typechecking %s with expected type %s, implicit views = %s, macros = %s".format(tree, pt, !withImplicitViewsDisabled, !withMacrosDisabled)) - val context = callsiteTyper.context - val withImplicitFlag = if (!withImplicitViewsDisabled) (context.withImplicitsEnabled[Tree] _) else (context.withImplicitsDisabled[Tree] _) - val withMacroFlag = if (!withMacrosDisabled) (context.withMacrosEnabled[Tree] _) else (context.withMacrosDisabled[Tree] _) - def withContext(tree: => Tree) = withImplicitFlag(withMacroFlag(tree)) - def withWrapping(tree: Tree)(op: Tree => Tree) = if (mode == TERMmode) universe.wrappingIntoTerm(tree)(op) else op(tree) - def typecheckInternal(tree: Tree) = callsiteTyper.silent(_.typed(universe.duplicateAndKeepPositions(tree), mode, pt), reportAmbiguousErrors = false) - withWrapping(tree)(wrappedTree => withContext(typecheckInternal(wrappedTree) match { - case universe.analyzer.SilentResultValue(result) => - macroLogVerbose(result) - result - case error @ universe.analyzer.SilentTypeError(_) => - macroLogVerbose(error.err.errMsg) - if (!silent) throw new TypecheckException(error.err.errPos, error.err.errMsg) - universe.EmptyTree - })) + macroLogVerbose(s"typechecking $tree with expected type $pt, implicit views = ${!withImplicitViewsDisabled}, macros = ${!withMacrosDisabled}") + import callsiteTyper.context + def doTypecheck(wrapped: Tree): Tree = + context.withImplicits(enabled = !withImplicitViewsDisabled) { + context.withMacros(enabled = !withMacrosDisabled) { + callsiteTyper.silent(_.typed(universe.duplicateAndKeepPositions(wrapped), mode, pt), reportAmbiguousErrors = false) match { + case universe.analyzer.SilentResultValue(result) => + macroLogVerbose(result) + result + case error@universe.analyzer.SilentTypeError(_) => + macroLogVerbose(error.err.errMsg) + if (!silent) throw new TypecheckException(error.err.errPos, error.err.errMsg) + universe.EmptyTree + } + } + } + + if (mode == TERMmode) universe.wrappingIntoTerm(tree)(doTypecheck) + else doTypecheck(tree) } def inferImplicitValue(pt: Type, silent: Boolean = true, withMacrosDisabled: Boolean = false, pos: Position = enclosingPosition): Tree = { diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index c5a3d605b1..bcc1ed3e64 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -387,8 +387,10 @@ trait Contexts { self: Analyzer => @inline final def withImplicitsEnabled[T](op: => T): T = withMode(enabled = ImplicitsEnabled)(op) @inline final def withImplicitsDisabled[T](op: => T): T = withMode(disabled = ImplicitsEnabled | EnrichmentEnabled)(op) @inline final def withImplicitsDisabledAllowEnrichment[T](op: => T): T = withMode(enabled = EnrichmentEnabled, disabled = ImplicitsEnabled)(op) + @inline final def withImplicits[T](enabled: Boolean)(op: => T): T = if (enabled) withImplicitsEnabled(op) else withImplicitsDisabled(op) @inline final def withMacrosEnabled[T](op: => T): T = withMode(enabled = MacrosEnabled)(op) @inline final def withMacrosDisabled[T](op: => T): T = withMode(disabled = MacrosEnabled)(op) + @inline final def withMacros[T](enabled: Boolean)(op: => T): T = if (enabled) withMacrosEnabled(op) else withMacrosDisabled(op) @inline final def withinStarPatterns[T](op: => T): T = withMode(enabled = StarPatterns)(op) @inline final def withinSuperInit[T](op: => T): T = withMode(enabled = SuperInit)(op) @inline final def withinSecondTry[T](op: => T): T = withMode(enabled = SecondTry)(op) diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 6412cc09f9..bee2ae8e99 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -49,11 +49,11 @@ trait Implicits { def inferImplicitByTypeSilent(pt: Type, context: Context, pos: Position = NoPosition): SearchResult = inferImplicit(EmptyTree, pt, reportAmbiguous = false, isView = false, context, saveAmbiguousDivergent = false, pos) - @deprecated("Unused in scalac") + @deprecated("Unused in scalac", "2.12.0-M4") def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context): SearchResult = inferImplicit(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent = true, tree.pos) - @deprecated("Unused in scalac") + @deprecated("Unused in scalac", "2.12.0-M4") def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean): SearchResult = inferImplicit(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent, tree.pos) @@ -111,12 +111,9 @@ trait Implicits { /** A friendly wrapper over inferImplicit to be used in macro contexts and toolboxes. */ def inferImplicit(tree: Tree, pt: Type, isView: Boolean, context: Context, silent: Boolean, withMacrosDisabled: Boolean, pos: Position, onError: (Position, String) => Unit): Tree = { - val result = - if (withMacrosDisabled) context.withMacrosDisabled { - inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context, saveAmbiguousDivergent = !silent, pos) - } else context.withMacrosEnabled { - inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context, saveAmbiguousDivergent = !silent, pos) - } + val result = context.withMacros(enabled = !withMacrosDisabled) { + inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context, saveAmbiguousDivergent = !silent, pos) + } if (result.isFailure && !silent) { val err = context.reporter.firstError diff --git a/src/compiler/scala/tools/nsc/typechecker/Tags.scala b/src/compiler/scala/tools/nsc/typechecker/Tags.scala index 21fdf75d66..e29451f379 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Tags.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Tags.scala @@ -13,9 +13,7 @@ trait Tags { private val runDefinitions = currentRun.runDefinitions private def resolveTag(pos: Position, taggedTp: Type, allowMaterialization: Boolean) = enteringTyper { - if (allowMaterialization) context.withMacrosEnabled{ inferImplicitByType(taggedTp, context, pos).tree } - else context.withMacrosDisabled{ inferImplicitByType(taggedTp, context, pos).tree } - + context.withMacros(enabled = allowMaterialization) { inferImplicitByType(taggedTp, context, pos).tree } } /** Finds in scope or materializes a ClassTag. diff --git a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala index ae6a9e22b6..9c4d521336 100644 --- a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala +++ b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala @@ -117,13 +117,15 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => def transformDuringTyper(expr: Tree, mode: scala.reflect.internal.Mode, withImplicitViewsDisabled: Boolean, withMacrosDisabled: Boolean)(transform: (analyzer.Typer, Tree) => Tree): Tree = { def withWrapping(tree: Tree)(op: Tree => Tree) = if (mode == TERMmode) wrappingIntoTerm(tree)(op) else op(tree) - withWrapping(verify(expr))(expr1 => { + withWrapping(verify(expr)) { expr => // need to extract free terms, because otherwise you won't be able to typecheck macros against something that contains them - val exprAndFreeTerms = extractFreeTerms(expr1, wrapFreeTermRefs = false) - var expr2 = exprAndFreeTerms._1 - val freeTerms = exprAndFreeTerms._2 - val dummies = freeTerms.map{ case (freeTerm, name) => ValDef(NoMods, name, TypeTree(freeTerm.info), Select(Ident(PredefModule), newTermName("$qmark$qmark$qmark"))) }.toList - expr2 = Block(dummies, expr2) + val (extracted, freeTerms) = extractFreeTerms(expr, wrapFreeTermRefs = false) + val exprBound = { + val binders = freeTerms.toList.map { case (freeTerm, name) => + ValDef(NoMods, name, TypeTree(freeTerm.info), Select(Ident(PredefModule), newTermName("$qmark$qmark$qmark"))) + } + Block(binders, extracted) + } // !!! Why is this is in the empty package? If it's only to make // it inaccessible then please put it somewhere designed for that @@ -131,26 +133,29 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => // [Eugene] how can we implement that? val ownerClass = rootMirror.EmptyPackageClass.newClassSymbol(newTypeName("")) build.setInfo(ownerClass, ClassInfoType(List(ObjectTpe), newScope, ownerClass)) - val owner = ownerClass.newLocalDummy(expr2.pos) - val currentTyper = analyzer.newTyper(analyzer.rootContext(NoCompilationUnit, EmptyTree).make(expr2, owner)) - val withImplicitFlag = if (!withImplicitViewsDisabled) (currentTyper.context.withImplicitsEnabled[Tree] _) else (currentTyper.context.withImplicitsDisabled[Tree] _) - val withMacroFlag = if (!withMacrosDisabled) (currentTyper.context.withMacrosEnabled[Tree] _) else (currentTyper.context.withMacrosDisabled[Tree] _) - def withContext (tree: => Tree) = withImplicitFlag(withMacroFlag(tree)) + val owner = ownerClass.newLocalDummy(exprBound.pos) + val currentTyper = analyzer.newTyper(analyzer.rootContext(NoCompilationUnit, EmptyTree).make(exprBound, owner)) + import currentTyper.{context => currCtx} val run = new Run run.symSource(ownerClass) = NoAbstractFile // need to set file to something different from null, so that currentRun.defines works phase = run.typerPhase // need to set a phase to something <= typerPhase, otherwise implicits in typedSelect will be disabled globalPhase = run.typerPhase // amazing... looks like phase and globalPhase are different things, so we need to set them separately - currentTyper.context.initRootContext() // need to manually set context mode, otherwise typer.silent will throw exceptions + currCtx.initRootContext() // need to manually set context mode, otherwise typer.silent will throw exceptions reporter.reset() - val expr3 = withContext(transform(currentTyper, expr2)) - var (dummies1, result) = expr3 match { - case Block(dummies, result) => ((dummies, result)) - case result => ((Nil, result)) - } + val (binders, transformed) = + currCtx.withImplicits(enabled = !withImplicitViewsDisabled) { + currCtx.withMacros(enabled = !withMacrosDisabled) { + transform(currentTyper, exprBound) + } + } match { + case Block(binders, transformed) => (binders, transformed) + case transformed => (Nil, transformed) + } + val invertedIndex = freeTerms map (_.swap) - result = new Transformer { + val indexed = new Transformer { override def transform(tree: Tree): Tree = tree match { case Ident(name: TermName) if invertedIndex contains name => @@ -158,10 +163,10 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => case _ => super.transform(tree) } - }.transform(result) - new TreeTypeSubstituter(dummies1 map (_.symbol), dummies1 map (dummy => SingleType(NoPrefix, invertedIndex(dummy.symbol.name.toTermName)))).traverse(result) - result - }) + }.transform(transformed) + new TreeTypeSubstituter(binders map (_.symbol), binders map (b => SingleType(NoPrefix, invertedIndex(b.symbol.name.toTermName)))).traverse(indexed) + indexed + } } def typecheck(expr: Tree, pt: Type, mode: scala.reflect.internal.Mode, silent: Boolean, withImplicitViewsDisabled: Boolean, withMacrosDisabled: Boolean): Tree = -- cgit v1.2.3 From b0b0abab89f0c40ba3c45b4a1f7bada31040d55a Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 21 Mar 2016 21:03:11 -0700 Subject: Jason's review feedback (ThisReferringMethodTraverser) - Re-simplify logging; - Remove unused method valueTypeToObject; - Limit ThisReferringMethodTraverser to material parts of the AST Limit ThisReferringMethodTraverser's analysis to only look at template-owned anonfun method bodies, to make sure it's fairly low overhead. AFAICT, part of the complexity of this analysis stems from the desire to make all the lambda impl methods static in `() => () => 42`: https://gist.github.com/062181846c13e65490cc. It would possible to accumulate the knowledge we need during the main transform, rather than in an additional pass. We'd need to transform template bodies in such a way that we we process definitions of anonfun methods before usages, which would currently amount to transforming the stats in reverse. --- src/compiler/scala/tools/nsc/transform/Delambdafy.scala | 10 ++++------ src/compiler/scala/tools/nsc/typechecker/Typers.scala | 8 +++----- test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala | 6 ++++++ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 3262dd9202..0614b138a7 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -106,11 +106,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre case _ => tp } - private def valueTypeToObject(tpe: Type): Type = - if (isPrimitiveValueClass(tpe.typeSymbol) || enteringErasure(tpe.typeSymbol.isDerivedValueClass)) ObjectTpe - else tpe - - // exclude primitives and value classses, which need special boxing + // exclude primitives and value classes, which need special boxing private def isReferenceType(tp: Type) = !tp.isInstanceOf[ErasedValueType] && { val sym = tp.typeSymbol !(isPrimitiveValueClass(sym) || sym.isDerivedValueClass) @@ -324,7 +320,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre private var currentMethod: Symbol = NoSymbol override def traverse(tree: Tree) = tree match { - case DefDef(_, _, _, _, _, _) => + case DefDef(_, _, _, _, _, _) if tree.symbol.isDelambdafyTarget => // we don't expect defs within defs. At this phase trees should be very flat if (currentMethod.exists) devWarning("Found a def within a def at a phase where defs are expected to be flattened out.") currentMethod = tree.symbol @@ -340,6 +336,8 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre debuglog(s"$currentMethod directly refers to 'this'") thisReferringMethods add currentMethod } + case _: ClassDef if !tree.symbol.isTopLevel => + case _: DefDef => case _ => super.traverse(tree) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 4f006fe9a9..3b826ae2e5 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1058,11 +1058,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper inferView(tree, tree.tpe, pt) match { case EmptyTree => // didn't find a view -- fall through case coercion => - if (settings.debug || settings.logImplicitConv) { - val msg = s"inferred view from ${tree.tpe} to $pt via $coercion: ${coercion.tpe}" - debuglog(msg) - if (settings.logImplicitConv) context.echo(tree.pos, msg) - } + def msg = s"inferred view from ${tree.tpe} to $pt via $coercion: ${coercion.tpe}" + if (settings.logImplicitConv) context.echo(tree.pos, msg) + else debuglog(msg) val silentContext = context.makeImplicit(context.ambiguousErrors) val res = newTyper(silentContext).typed( diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala index 6f213e6625..f7218c576c 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala @@ -127,4 +127,10 @@ class IndySammyTest extends ClearAfterClass { List(Op(ICONST_1), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "boxToInteger", "(I)Ljava/lang/Integer;", false)), Op(IRETURN))() + // Tests ThisReferringMethodsTraverser + @Test + def testStaticIfNoThisReference: Unit = { + val methodNodes = compileMethods(compiler)("def foo = () => () => () => 42") + methodNodes.forall(m => !m.name.contains("anonfun") || (m.access & ACC_STATIC) == ACC_STATIC) + } } -- cgit v1.2.3 From 391e2843f420bb4686b974b18ac361c9bb49465c Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 21 Mar 2016 22:56:08 -0700 Subject: Don't adapt erroneous tree to SAM type. Do not report second error. Go straight to the exit. Based on review by Jason. --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 4 ++-- test/files/neg/sammy_error.check | 4 ++++ test/files/neg/sammy_error.scala | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 test/files/neg/sammy_error.check create mode 100644 test/files/neg/sammy_error.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 3b826ae2e5..cd5759f40f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1074,7 +1074,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // we know `!(tree.tpe <:< pt)`; try to remedy if there's a sam for pt val sam = samMatchingFunction(tree, pt) // this implies tree.isInstanceOf[Function] - if (sam.exists) { + if (sam.exists && !tree.tpe.isErroneous) { val samTree = adaptToSAM(sam, tree.asInstanceOf[Function], pt, mode) if (samTree ne EmptyTree) return samTree } @@ -2794,7 +2794,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper EmptyTree } } catch { - case e@(_: NoInstance | _: TypeError) => // TODO: we get here whenever pt contains a wildcardtype??? + case e@(_: NoInstance | _: TypeError) => debuglog(s"Error during SAM synthesis: could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)\n$e") EmptyTree } diff --git a/test/files/neg/sammy_error.check b/test/files/neg/sammy_error.check new file mode 100644 index 0000000000..f14ac7e3a2 --- /dev/null +++ b/test/files/neg/sammy_error.check @@ -0,0 +1,4 @@ +sammy_error.scala:6: error: missing parameter type + foo(x => x) // should result in only one error (the second one stemmed from adapting to SAM when the tree was erroneous) + ^ +one error found diff --git a/test/files/neg/sammy_error.scala b/test/files/neg/sammy_error.scala new file mode 100644 index 0000000000..dbddebf325 --- /dev/null +++ b/test/files/neg/sammy_error.scala @@ -0,0 +1,7 @@ +trait F1[A, B] { def apply(a: A): B } + +class Test { + def foo[A](f1: F1[A, A]) = f1 + + foo(x => x) // should result in only one error (the second one stemmed from adapting to SAM when the tree was erroneous) +} -- cgit v1.2.3 From 878e20a5243383300d3b4990146d260409bf5dfd Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 21 Mar 2016 21:54:48 -0700 Subject: 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. --- .../tools/nsc/backend/jvm/BCodeBodyBuilder.scala | 5 ++--- .../scala/tools/nsc/transform/Delambdafy.scala | 20 +++++++++++++------- .../scala/tools/nsc/transform/Erasure.scala | 6 ++++++ .../nsc/transform/TypeAdaptingTransformer.scala | 3 +-- .../scala/tools/nsc/typechecker/Typers.scala | 3 ++- .../scala/reflect/internal/StdAttachments.scala | 13 +++++++++++++ .../scala/reflect/runtime/JavaUniverseForce.scala | 1 + test/files/run/sammy_erasure_cce.scala | 22 ++++++++++++++++++++++ 8 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 test/files/run/sammy_erasure_cce.scala 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 diff --git a/test/files/run/sammy_erasure_cce.scala b/test/files/run/sammy_erasure_cce.scala new file mode 100644 index 0000000000..fb973befe4 --- /dev/null +++ b/test/files/run/sammy_erasure_cce.scala @@ -0,0 +1,22 @@ +trait F1 { + def apply(a: List[String]): String + def f1 = "f1" +} + +object Test extends App { + // Wrap the sam-targeting function in a context where the expected type is erased (identity's argument type erases to Object), + // so that Erasure can't tell that the types actually conform by looking only + // at an un-adorned Function tree and the expected type + // (because a function type needs no cast it the expected type is a SAM type), + // + // A correct implementation of Typers/Erasure tracks a Function's SAM target type directly + // (currently using an attachment for backwards compat), + // and not in the expected type (which was the case in my first attempt), + // as the expected type may lose its SAM status due to erasure. + // (In a sense, this need not be so, but erasure drops type parameters, + // so that identity's F1 type argument cannot be propagated to its argument type.) + def foo = identity[F1]((as: List[String]) => as.head) + + // check that this doesn't CCE's + foo.f1 +} -- cgit v1.2.3 From 608ac2c2b9e3f6f46489e20830d8949ee7d506cf Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 23 Mar 2016 10:52:57 -0700 Subject: Soften sam restrictions Some of the earlier proposals were too strongly linked to the requirements of the Java 8 platform, which was problematic for scala.js & friends. Instead of ruling out SAM types that we can't compile to use LambdaMetaFactory, expand those during compilation to anonymous subclasses, instead of invokedynamic + LMF. Also, self types rear their ugly heads again. Align `hasSelfType` with the implementation suggested in `thisSym`'s docs. --- spec/06-expressions.md | 28 ++++++---- .../scala/tools/nsc/transform/Delambdafy.scala | 2 +- .../scala/tools/nsc/transform/UnCurry.scala | 27 ++++++++- .../scala/reflect/internal/Definitions.scala | 24 +++----- src/reflect/scala/reflect/internal/Symbols.scala | 2 +- test/files/neg/sammy_restrictions.check | 58 ++++++++++++-------- 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 } -- cgit v1.2.3 From aa972dc100544179beecde48b52dfdb847162001 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 23 Mar 2016 17:53:19 -0700 Subject: SAM conversion precedes implicit view application (as in dotty). This reflects the majority vote on the PR. DSLs that need their implicit conversions to kick in instead of SAM conversion, will have to make their target types not be SAM types (e.g., by adding a second abstract method to them). --- spec/06-expressions.md | 22 +++++++++++----------- .../scala/tools/nsc/typechecker/Typers.scala | 16 ++++++++-------- test/files/run/sammy_after_implicit_view.scala | 20 ++++++++++---------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/spec/06-expressions.md b/spec/06-expressions.md index bf1a6acf9a..30ad73a3cd 100644 --- a/spec/06-expressions.md +++ b/spec/06-expressions.md @@ -1345,17 +1345,6 @@ If $e$ has some value type and the expected type is `Unit`, $e$ is converted to the expected type by embedding it in the term `{ $e$; () }`. -###### View Application -If none of the previous conversions applies, and $e$'s type -does not conform to the expected type $\mathit{pt}$, it is attempted to convert -$e$ to the expected type with a [view](07-implicits.html#views). - -###### Selection on `Dynamic` -If none of the previous conversions applies, and $e$ is a prefix -of a selection $e.x$, and $e$'s type conforms to class `scala.Dynamic`, -then the selection is rewritten according to the rules for -[dynamic member selection](#dynamic-member-selection). - ###### 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: - the class `C` of `S` declares an abstract method `m` with signature `(p1: A1, ..., pN: AN): R`; @@ -1379,6 +1368,17 @@ Finally, we impose some implementation restrictions (these may be lifted in futu - `C` must not declare a self type (this simplifies type inference); - `C` must not be `@specialized`. +###### View Application +If none of the previous conversions applies, and $e$'s type +does not conform to the expected type $\mathit{pt}$, it is attempted to convert +$e$ to the expected type with a [view](07-implicits.html#views). + +###### Selection on `Dynamic` +If none of the previous conversions applies, and $e$ is a prefix +of a selection $e.x$, and $e$'s type conforms to class `scala.Dynamic`, +then the selection is rewritten according to the rules for +[dynamic member selection](#dynamic-member-selection). + ### Method Conversions The following four implicit conversions can be applied to methods diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index a9d5b69e2e..52242c10b3 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1053,6 +1053,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (hasUndets) return instantiate(tree, mode, pt) + // we know `!(tree.tpe <:< pt)`; try to remedy if there's a sam for pt + 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.updateAttachment(SAMFunction(pt, sam)) + } + if (context.implicitsEnabled && !pt.isError && !tree.isErrorTyped) { // (14); the condition prevents chains of views inferView(tree, tree.tpe, pt) match { @@ -1071,14 +1079,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } } - - // we know `!(tree.tpe <:< pt)`; try to remedy if there's a sam for pt - 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.updateAttachment(SAMFunction(pt, sam)) - } } debuglog("error tree = " + tree) diff --git a/test/files/run/sammy_after_implicit_view.scala b/test/files/run/sammy_after_implicit_view.scala index 30e3babc75..a13a71e562 100644 --- a/test/files/run/sammy_after_implicit_view.scala +++ b/test/files/run/sammy_after_implicit_view.scala @@ -5,24 +5,24 @@ object Test extends App { final val AnonFunClass = "$anon$" final val LMFClass = "$$Lambda$" // LambdaMetaFactory names classes like this - // if there's an implicit conversion, it takes precedence - def statusQuo() = { + // if there's an implicit conversion, it does not takes precedence (because that's what dotty does) + def implicitSam() = { import language.implicitConversions - var ok = false - implicit def fun2sam(fun: Int => String): MySam = { ok = true; new MySam { def apply(x: Int) = fun(x) } } + var ok = true + implicit def fun2sam(fun: Int => String): MySam = { ok = false; new MySam { def apply(x: Int) = fun(x) } } val className = (((x: Int) => x.toString): MySam).getClass.toString assert(ok, "implicit conversion not called") - assert(className contains AnonFunClass, className) - assert(!(className contains LMFClass), className) + assert(!(className contains AnonFunClass), className) + assert(className contains LMFClass, className) } // indirectly check that this sam type instance was created from a class spun up by LambdaMetaFactory - def statusIndy() = { + def justSammy() = { val className = (((x: Int) => x.toString): MySam).getClass.toString assert(!(className contains AnonFunClass), className) assert(className contains LMFClass, className) } - statusQuo() - statusIndy() -} \ No newline at end of file + implicitSam() + justSammy() +} -- cgit v1.2.3 From 925b394dbf2fc8f9a0a6f64e374dc6ab5564ab37 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 24 Mar 2016 14:50:44 -0700 Subject: Refactor: simplify fallbackAfterVanillaAdapt. Trying to figure out if we can avoid adapting to SAM, and just type them once and for all in typedFunction. Looks like overload resolution requires SAM adaptation to happen in adapt. Cleaned up while I was in the area. --- .../scala/tools/nsc/typechecker/Infer.scala | 2 +- .../scala/tools/nsc/typechecker/Typers.scala | 205 +++++++++++---------- .../scala/reflect/internal/Definitions.scala | 8 - 3 files changed, 107 insertions(+), 108 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 9b42841eb8..dc91d23011 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -1220,7 +1220,7 @@ trait Infer extends Checkable { } def inferModulePattern(pat: Tree, pt: Type) = - if (!(pat.tpe <:< pt)) { + if ((pat.symbol ne null) && pat.symbol.isModule && !(pat.tpe <:< pt)) { val ptparams = freeTypeParamsOfTerms(pt) debuglog("free type params (2) = " + ptparams) val ptvars = ptparams map freshVar diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 52242c10b3..ba6a9e20ea 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -807,7 +807,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * (11) Widen numeric literals to their expected type, if necessary * (12) When in mode EXPRmode, convert E to { E; () } if expected type is scala.Unit. * (13) When in mode EXPRmode, apply AnnotationChecker conversion if expected type is annotated. - * (14) When in mode EXPRmode, apply a view + * (14) When in mode EXPRmode, do SAM conversion + * (15) When in mode EXPRmode, apply a view * If all this fails, error */ protected def adapt(tree: Tree, mode: Mode, pt: Type, original: Tree = EmptyTree): Tree = { @@ -1019,78 +1020,70 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } - def fallbackAfterVanillaAdapt(): Tree = { - def isPopulatedPattern = { - if ((tree.symbol ne null) && tree.symbol.isModule) - inferModulePattern(tree, pt) - - isPopulated(tree.tpe, approximateAbstracts(pt)) + def adaptExprNotFunMode(): Tree = { + def lastTry(err: AbsTypeError = null): Tree = { + debuglog("error tree = " + tree) + if (settings.debug && settings.explaintypes) explainTypes(tree.tpe, pt) + if (err ne null) context.issue(err) + if (tree.tpe.isErroneous || pt.isErroneous) setError(tree) + else adaptMismatchedSkolems() } - if (mode.inPatternMode && isPopulatedPattern) - return tree - val tree1 = constfold(tree, pt) // (10) (11) - if (tree1.tpe <:< pt) - return adapt(tree1, mode, pt, original) + // TODO: should we even get to fallbackAfterVanillaAdapt for an ill-typed tree? + if (mode.typingExprNotFun && !tree.tpe.isErroneous) { + @inline def tpdPos(transformed: Tree) = typedPos(tree.pos, mode, pt)(transformed) + @inline def tpd(transformed: Tree) = typed(transformed, mode, pt) - if (mode.typingExprNotFun) { - // The <: Any requirement inhibits attempts to adapt continuation types - // to non-continuation types. - if (tree.tpe <:< AnyTpe) pt.dealias match { - case TypeRef(_, UnitClass, _) => // (12) - if (!isPastTyper && settings.warnValueDiscard) - context.warning(tree.pos, "discarded non-Unit value") - return typedPos(tree.pos, mode, pt)(Block(List(tree), Literal(Constant(())))) - case TypeRef(_, sym, _) if isNumericValueClass(sym) && isNumericSubType(tree.tpe, pt) => - if (!isPastTyper && settings.warnNumericWiden) - context.warning(tree.pos, "implicit numeric widening") - return typedPos(tree.pos, mode, pt)(Select(tree, "to" + sym.name)) - case _ => - } - if (pt.dealias.annotations.nonEmpty && canAdaptAnnotations(tree, this, mode, pt)) // (13) - return typed(adaptAnnotations(tree, this, mode, pt), mode, pt) + @inline def warnValueDiscard(): Unit = + if (!isPastTyper && settings.warnValueDiscard) context.warning(tree.pos, "discarded non-Unit value") + @inline def warnNumericWiden(): Unit = + if (!isPastTyper && settings.warnNumericWiden) context.warning(tree.pos, "implicit numeric widening") - if (hasUndets) - return instantiate(tree, mode, pt) - - // we know `!(tree.tpe <:< pt)`; try to remedy if there's a sam for pt - 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.updateAttachment(SAMFunction(pt, sam)) - } + // The <: Any requirement inhibits attempts to adapt continuation types to non-continuation types. + val anyTyped = tree.tpe <:< AnyTpe - if (context.implicitsEnabled && !pt.isError && !tree.isErrorTyped) { - // (14); the condition prevents chains of views - inferView(tree, tree.tpe, pt) match { - case EmptyTree => // didn't find a view -- fall through - case coercion => - def msg = s"inferred view from ${tree.tpe} to $pt via $coercion: ${coercion.tpe}" - if (settings.logImplicitConv) context.echo(tree.pos, msg) - else debuglog(msg) - - val silentContext = context.makeImplicit(context.ambiguousErrors) - val res = newTyper(silentContext).typed( - new ApplyImplicitView(coercion, List(tree)) setPos tree.pos, mode, pt) - silentContext.reporter.firstError match { - case Some(err) => context.issue(err) - case None => return res + pt.dealias match { + case TypeRef(_, UnitClass, _) if anyTyped => // (12) + warnValueDiscard() ; tpdPos(gen.mkUnitBlock(tree)) + case TypeRef(_, numValueCls, _) if anyTyped && isNumericValueClass(numValueCls) && isNumericSubType(tree.tpe, pt) => // (10) (11) + warnNumericWiden() ; tpdPos(Select(tree, s"to${numValueCls.name}")) + case dealiased if dealiased.annotations.nonEmpty && canAdaptAnnotations(tree, this, mode, pt) => // (13) + tpd(adaptAnnotations(tree, this, mode, pt)) + case _ => + if (hasUndets) instantiate(tree, mode, pt) + else { + // (14) sam conversion + // TODO: figure out how to avoid partially duplicating typedFunction (samMatchingFunction) + // Could we infer the SAM type, assign it to the tree and add the attachment, + // all in one fell swoop at the end of typedFunction? + val samAttach = inferSamType(tree, pt, mode) + + if (samAttach.samTp ne NoType) tree.setType(samAttach.samTp).updateAttachment(samAttach) + else { // (15) implicit view application + val coercion = + if (context.implicitsEnabled) inferView(tree, tree.tpe, pt) + else EmptyTree + if (coercion ne EmptyTree) { + def msg = s"inferred view from ${tree.tpe} to $pt via $coercion: ${coercion.tpe}" + if (settings.logImplicitConv) context.echo(tree.pos, msg) + else debuglog(msg) + + val viewApplied = new ApplyImplicitView(coercion, List(tree)) setPos tree.pos + val silentContext = context.makeImplicit(context.ambiguousErrors) + val typedView = newTyper(silentContext).typed(viewApplied, mode, pt) + + silentContext.reporter.firstError match { + case None => typedView + case Some(err) => lastTry(err) + } + } else lastTry() } - } + } } - } - - debuglog("error tree = " + tree) - if (settings.debug && settings.explaintypes) - explainTypes(tree.tpe, pt) - - if (tree.tpe.isErroneous || pt.isErroneous) - setError(tree) - else - adaptMismatchedSkolems() + } else lastTry() } + def vanillaAdapt(tree: Tree) = { def applyPossible = { def applyMeth = member(adaptToName(tree, nme.apply), nme.apply) @@ -1124,8 +1117,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } else if (tree.tpe <:< pt) tree - else - fallbackAfterVanillaAdapt() + else if (mode.inPatternMode && { inferModulePattern(tree, pt); isPopulated(tree.tpe, approximateAbstracts(pt)) }) + tree + else { + val constFolded = constfold(tree, pt) + if (constFolded.tpe <:< pt) adapt(constFolded, mode, pt, original) // set stage for (0) + else adaptExprNotFunMode() // (10) -- (15) + } } // begin adapt @@ -2751,54 +2749,63 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * function type is a built-in FunctionN or some SAM type * */ - def adaptToSAM(sam: Symbol, fun: Function, pt: Type, mode: Mode): Tree = { + def inferSamType(fun: Tree, pt: Type, mode: Mode): SAMFunction = { + val sam = + if (fun.isInstanceOf[Function] && !isFunctionType(pt)) { + val sam = samOf(pt) + if (samMatchesFunctionBasedOnArity(sam, fun.asInstanceOf[Function].vparams)) sam + else NoSymbol + } else NoSymbol + def fullyDefinedMeetsExpectedFunTp(pt: Type): Boolean = isFullyDefined(pt) && { val samMethType = pt memberInfo sam fun.tpe <:< functionType(samMethType.paramTypes, samMethType.resultType) } - if (fullyDefinedMeetsExpectedFunTp(pt)) fun.setType(pt) - else try { - val samClassSym = pt.typeSymbol + SAMFunction( + if (!sam.exists) NoType + else if (fullyDefinedMeetsExpectedFunTp(pt)) pt + else try { + val samClassSym = pt.typeSymbol - // we're trying to fully define the type arguments for this type constructor - val samTyCon = samClassSym.typeConstructor + // we're trying to fully define the type arguments for this type constructor + val samTyCon = samClassSym.typeConstructor - // the unknowns - val tparams = samClassSym.typeParams - // ... as typevars - val tvars = tparams map freshVar + // the unknowns + val tparams = samClassSym.typeParams + // ... as typevars + val tvars = tparams map freshVar - val ptVars = appliedType(samTyCon, tvars) + val ptVars = appliedType(samTyCon, tvars) - // carry over info from pt - ptVars <:< pt + // carry over info from pt + ptVars <:< pt - val samInfoWithTVars = ptVars.memberInfo(sam) + val samInfoWithTVars = ptVars.memberInfo(sam) - // use function type subtyping, not method type subtyping (the latter is invariant in argument types) - fun.tpe <:< functionType(samInfoWithTVars.paramTypes, samInfoWithTVars.finalResultType) + // use function type subtyping, not method type subtyping (the latter is invariant in argument types) + fun.tpe <:< functionType(samInfoWithTVars.paramTypes, samInfoWithTVars.finalResultType) - val variances = tparams map varianceInType(sam.info) + val variances = tparams map varianceInType(sam.info) - // solve constraints tracked by tvars - val targs = solvedTypes(tvars, tparams, variances, upper = false, lubDepth(sam.info :: Nil)) + // solve constraints tracked by tvars + val targs = solvedTypes(tvars, tparams, variances, upper = false, lubDepth(sam.info :: Nil)) - debuglog(s"sam infer: $pt --> ${appliedType(samTyCon, targs)} by ${fun.tpe} <:< $samInfoWithTVars --> $targs for $tparams") + debuglog(s"sam infer: $pt --> ${appliedType(samTyCon, targs)} by ${fun.tpe} <:< $samInfoWithTVars --> $targs for $tparams") - val ptFullyDefined = appliedType(samTyCon, targs) - if (ptFullyDefined <:< pt && fullyDefinedMeetsExpectedFunTp(ptFullyDefined)) { - debuglog(s"sam fully defined expected type: $ptFullyDefined from $pt for ${fun.tpe}") - fun.setType(ptFullyDefined) - } else { - debuglog(s"Could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)") - EmptyTree - } - } catch { - case e@(_: NoInstance | _: TypeError) => - debuglog(s"Error during SAM synthesis: could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)\n$e") - EmptyTree - } + val ptFullyDefined = appliedType(samTyCon, targs) + if (ptFullyDefined <:< pt && fullyDefinedMeetsExpectedFunTp(ptFullyDefined)) { + debuglog(s"sam fully defined expected type: $ptFullyDefined from $pt for ${fun.tpe}") + ptFullyDefined + } else { + debuglog(s"Could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)") + NoType + } + } catch { + case e@(_: NoInstance | _: TypeError) => + debuglog(s"Error during SAM synthesis: could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)\n$e") + NoType + }, sam) } /** Type check a function literal. diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 81071e763d..28f6afee39 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -701,14 +701,6 @@ trait Definitions extends api.StandardDefinitions { def samMatchesFunctionBasedOnArity(sam: Symbol, formals: List[Any]): Boolean = sam.exists && sameLength(sam.info.params, formals) - def samMatchingFunction(tree: Tree, pt: Type): Symbol = { - if (tree.isInstanceOf[Function] && !isFunctionType(pt)) { - val sam = samOf(pt) - if (samMatchesFunctionBasedOnArity(sam, tree.asInstanceOf[Function].vparams)) sam - else NoSymbol - } else NoSymbol - } - def isTupleType(tp: Type) = isTupleTypeDirect(tp.dealiasWiden) def tupleComponents(tp: Type) = tp.dealiasWiden.typeArgs -- cgit v1.2.3 From a17d247a8acea2daaddb39263ebf1dcf5673bcba Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 25 Mar 2016 18:41:48 -0700 Subject: SAM conversion can be disabled using `-Xsource:2.11` For completeness, `-Xsource:2.11 -Xexperimental` does enable it. --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 2 ++ src/reflect/scala/reflect/internal/Definitions.scala | 4 +++- src/reflect/scala/reflect/internal/settings/MutableSettings.scala | 1 + src/reflect/scala/reflect/runtime/Settings.scala | 1 + test/files/neg/sammy_disabled.check | 4 ++++ test/files/neg/sammy_disabled.flags | 1 + test/files/neg/sammy_disabled.scala | 3 +++ 7 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 test/files/neg/sammy_disabled.check create mode 100644 test/files/neg/sammy_disabled.flags create mode 100644 test/files/neg/sammy_disabled.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index ba6a9e20ea..35cfc644ab 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2752,6 +2752,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def inferSamType(fun: Tree, pt: Type, mode: Mode): SAMFunction = { val sam = if (fun.isInstanceOf[Function] && !isFunctionType(pt)) { + // TODO: can we ensure there's always a SAMFunction attachment, instead of looking up the sam again??? + // seems like overloading complicates things? val sam = samOf(pt) if (samMatchesFunctionBasedOnArity(sam, fun.asInstanceOf[Function].vparams)) sam else NoSymbol diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 28f6afee39..72c56def80 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -829,13 +829,15 @@ trait Definitions extends api.StandardDefinitions { (sym eq PartialFunctionClass) || (sym eq AbstractPartialFunctionClass) } + private[this] val doSam = settings.isScala212 || (settings.isScala211 && settings.Xexperimental) + /** The single abstract method declared by type `tp` (or `NoSymbol` if it cannot be found). * * The method must be monomorphic and have exactly one parameter list. * The class defining the method is a supertype of `tp` that * has a public no-arg primary constructor. */ - def samOf(tp: Type): Symbol = { + def samOf(tp: Type): Symbol = if (!doSam) NoSymbol else { // look at erased type because we (only) care about what ends up in bytecode // (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 diff --git a/src/reflect/scala/reflect/internal/settings/MutableSettings.scala b/src/reflect/scala/reflect/internal/settings/MutableSettings.scala index 38893d8db3..e75b3dff3d 100644 --- a/src/reflect/scala/reflect/internal/settings/MutableSettings.scala +++ b/src/reflect/scala/reflect/internal/settings/MutableSettings.scala @@ -58,6 +58,7 @@ abstract class MutableSettings extends AbsSettings { def maxClassfileName: IntSetting def isScala211: Boolean + def isScala212: Boolean } object MutableSettings { diff --git a/src/reflect/scala/reflect/runtime/Settings.scala b/src/reflect/scala/reflect/runtime/Settings.scala index 27d574b1de..b1d7fde1b4 100644 --- a/src/reflect/scala/reflect/runtime/Settings.scala +++ b/src/reflect/scala/reflect/runtime/Settings.scala @@ -51,4 +51,5 @@ private[reflect] class Settings extends MutableSettings { val Yrecursion = new IntSetting(0) val maxClassfileName = new IntSetting(255) def isScala211 = true + def isScala212 = true } diff --git a/test/files/neg/sammy_disabled.check b/test/files/neg/sammy_disabled.check new file mode 100644 index 0000000000..66db9dd5f2 --- /dev/null +++ b/test/files/neg/sammy_disabled.check @@ -0,0 +1,4 @@ +sammy_disabled.scala:3: error: missing parameter type +class C { val f: F = x => "a" } + ^ +one error found diff --git a/test/files/neg/sammy_disabled.flags b/test/files/neg/sammy_disabled.flags new file mode 100644 index 0000000000..cf42e9f940 --- /dev/null +++ b/test/files/neg/sammy_disabled.flags @@ -0,0 +1 @@ +-Xsource:2.11 diff --git a/test/files/neg/sammy_disabled.scala b/test/files/neg/sammy_disabled.scala new file mode 100644 index 0000000000..12000a3e12 --- /dev/null +++ b/test/files/neg/sammy_disabled.scala @@ -0,0 +1,3 @@ +trait F { def apply(x: Int): String } + +class C { val f: F = x => "a" } -- cgit v1.2.3 From 3ae39036771acb107cbb4a37fe6113c243d89acc Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 25 Mar 2016 19:08:23 -0700 Subject: Target FunctionN, not scala/runtime/java8/JFunction. We compile FunctionN to Java 8's idea of a function now, so no need to target the artisanal JFunction and friends, except when the function is specialized, as I don't yet see how we can use LMF with the way specialization handles FunctionN: First, the working status quo -- the hand-crafted specialized versions of JFunction0. Notice how `apply$mcB$sp` is looking pretty SAMmy: ``` @FunctionalInterface public interface JFunction0$mcB$sp extends JFunction0 { @Override public byte apply$mcB$sp(); @Override default public Object apply() { return BoxesRunTime.boxToByte(this.apply$mcB$sp()); } } ``` Contrast this with our specialized standard FunctionN: ``` public interface Function0 { public R apply(); default public byte apply$mcB$sp() { return BoxesRunTime.unboxToByte(this.apply()); } } public interface Function0$mcB$sp extends Function0 { } ``` The single abstract method in `Function0$mcB$sp` is `apply`, and the method that would let us avoid boxing, if it were abstract, is `apply$mcB$sp`... TODO (after M4): - do same for specialized functions (issues with boxing?) - remove scala/runtime/java8/JFunction* (need new STARR?) --- src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala | 14 ++++++-------- .../tools/nsc/backend/jvm/analysis/BackendUtils.scala | 4 ++-- .../scala/tools/nsc/backend/jvm/opt/CopyProp.scala | 4 ++-- src/compiler/scala/tools/nsc/transform/Delambdafy.scala | 2 +- src/library/scala/collection/mutable/AnyRefMap.scala | 6 +++++- src/reflect/scala/reflect/internal/Definitions.scala | 1 - test/files/run/t8549.scala | 4 ++-- 7 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala index 696a164c56..ab9fd94a93 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala @@ -219,14 +219,12 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { // enumeration of specialized classes is temporary, while we still use the java-defined JFunctionN. // once we switch to ordinary FunctionN, we can use specializedSubclasses just like for tuples. - private def functionClasses(base: String): Set[Symbol] = { - def primitives = Iterator("B", "S", "I", "J", "C", "F", "D", "Z", "V") + private def specializedJFunctionSymbols(base: String): Seq[Symbol] = { + def primitives = Seq("B", "S", "I", "J", "C", "F", "D", "Z", "V") def ijfd = Iterator("I", "J", "F", "D") def ijfdzv = Iterator("I", "J", "F", "D", "Z", "V") def ijd = Iterator("I", "J", "D") - val classNames = Set.empty[String] ++ { - (0 to 22).map(base + _) - } ++ { + val classNames = { primitives.map(base + "0$mc" + _ + "$sp") // Function0 } ++ { // return type specializations appear first in the name string (alphabetical sorting) @@ -237,7 +235,7 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { classNames map getRequiredClass } - lazy val srJFunctionRefs: Set[InternalName] = functionClasses("scala.runtime.java8.JFunction").map(classBTypeFromSymbol(_).internalName) + lazy val functionRefs: Set[InternalName] = (FunctionClass.seq ++ specializedJFunctionSymbols("scala.runtime.java8.JFunction")).map(classBTypeFromSymbol(_).internalName).toSet lazy val typeOfArrayOp: Map[Int, BType] = { import scalaPrimitives._ @@ -343,7 +341,7 @@ trait CoreBTypesProxyGlobalIndependent[BTS <: BTypes] { def srRefConstructors : Map[InternalName, MethodNameAndType] def tupleClassConstructors : Map[InternalName, MethodNameAndType] - def srJFunctionRefs: Set[InternalName] + def functionRefs: Set[InternalName] def lambdaMetaFactoryBootstrapHandle : asm.Handle def lambdaDeserializeBootstrapHandle : asm.Handle @@ -410,7 +408,7 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: def srRefConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.srRefConstructors def tupleClassConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.tupleClassConstructors - def srJFunctionRefs: Set[InternalName] = _coreBTypes.srJFunctionRefs + def functionRefs: Set[InternalName] = _coreBTypes.functionRefs def srSymbolLiteral : ClassBType = _coreBTypes.srSymbolLiteral def srStructuralCallSite : ClassBType = _coreBTypes.srStructuralCallSite diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala index bd7d5d2608..a6b9faa933 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala @@ -125,7 +125,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { private val anonfunAdaptedName = """.*\$anonfun\$\d+\$adapted""".r def hasAdaptedImplMethod(closureInit: ClosureInstantiation): Boolean = { - isrJFunctionType(Type.getReturnType(closureInit.lambdaMetaFactoryCall.indy.desc).getInternalName) && + isBuiltinFunctionType(Type.getReturnType(closureInit.lambdaMetaFactoryCall.indy.desc).getInternalName) && anonfunAdaptedName.pattern.matcher(closureInit.lambdaMetaFactoryCall.implMethod.getName).matches } @@ -250,7 +250,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { } } - def isrJFunctionType(internalName: InternalName): Boolean = srJFunctionRefs(internalName) + def isBuiltinFunctionType(internalName: InternalName): Boolean = functionRefs(internalName) /** * Visit the class node and collect all referenced nested classes. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala index f1eaebd27c..d28565b9bc 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala @@ -296,11 +296,11 @@ class CopyProp[BT <: BTypes](val btypes: BT) { /** * Eliminate the closure value produced by `indy`. If the SAM type is known to construct - * without side-effects (e.g. scala/runtime/java8/JFunctionN), the `indy` and its inputs + * without side-effects (e.g. scala/FunctionN), the `indy` and its inputs * are eliminated, otherwise a POP is inserted. */ def handleClosureInst(indy: InvokeDynamicInsnNode): Unit = { - if (isrJFunctionType(Type.getReturnType(indy.desc).getInternalName)) { + if (isBuiltinFunctionType(Type.getReturnType(indy.desc).getInternalName)) { toRemove += indy callGraph.removeClosureInstantiation(indy, method) handleInputs(indy, Type.getArgumentTypes(indy.desc).length) diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 7ccaec2f50..76c84bd428 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -241,7 +241,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre val isSpecialized = specializedName != funSym.name 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) + else FunctionClass(originalFunction.vparams.length) (functionalInterface, isSpecialized) } diff --git a/src/library/scala/collection/mutable/AnyRefMap.scala b/src/library/scala/collection/mutable/AnyRefMap.scala index 2ed5bbea60..6ff79dd1b8 100644 --- a/src/library/scala/collection/mutable/AnyRefMap.scala +++ b/src/library/scala/collection/mutable/AnyRefMap.scala @@ -419,7 +419,11 @@ object AnyRefMap { private final val VacantBit = 0x40000000 private final val MissVacant = 0xC0000000 - private val exceptionDefault = (k: Any) => throw new NoSuchElementException(if (k == null) "(null)" else k.toString) + @SerialVersionUID(1L) + private class ExceptionDefault extends (Any => Nothing) with Serializable { + def apply(k: Any): Nothing = throw new NoSuchElementException(if (k == null) "(null)" else k.toString) + } + private val exceptionDefault = new ExceptionDefault implicit def canBuildFrom[K <: AnyRef, V, J <: AnyRef, U]: CanBuildFrom[AnyRefMap[K,V], (J, U), AnyRefMap[J,U]] = new CanBuildFrom[AnyRefMap[K,V], (J, U), AnyRefMap[J,U]] { diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 72c56def80..8074b448fe 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -1567,7 +1567,6 @@ trait Definitions extends api.StandardDefinitions { private lazy val PolySigMethods: Set[Symbol] = Set[Symbol](MethodHandle.info.decl(sn.Invoke), MethodHandle.info.decl(sn.InvokeExact)).filter(_.exists) lazy val Scala_Java8_CompatPackage = rootMirror.getPackageIfDefined("scala.runtime.java8") - lazy val Scala_Java8_CompatPackage_JFunction = (0 to MaxFunctionArity).toArray map (i => getMemberIfDefined(Scala_Java8_CompatPackage.moduleClass, TypeName("JFunction" + i))) } } } diff --git a/test/files/run/t8549.scala b/test/files/run/t8549.scala index 233a05dee1..e2d0d335b0 100644 --- a/test/files/run/t8549.scala +++ b/test/files/run/t8549.scala @@ -79,7 +79,7 @@ object Test extends App { } } - // Generated on 20150925-14:41:27 with Scala version 2.12.0-20150924-125956-fd5994f397) + // Generated on 20160328-17:47:35 with Scala version 2.12.0-20160328-174205-d46145c) overwrite.foreach(updateComment) check(Some(1))("rO0ABXNyAApzY2FsYS5Tb21lESLyaV6hi3QCAAFMAAF4dAASTGphdmEvbGFuZy9PYmplY3Q7eHIADHNjYWxhLk9wdGlvbv5pN/3bDmZ0AgAAeHBzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAQ==") @@ -176,7 +176,7 @@ object Test extends App { // check(mutable.ArrayBuffer(1, 2, 3))( "rO0ABXNyACRzY2FsYS5jb2xsZWN0aW9uLm11dGFibGUuQXJyYXlCdWZmZXIVOLBTg4KOcwIAA0kAC2luaXRpYWxTaXplSQAFc2l6ZTBbAAVhcnJheXQAE1tMamF2YS9sYW5nL09iamVjdDt4cAAAABAAAAADdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAEHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3EAfgAFAAAAAnNxAH4ABQAAAANwcHBwcHBwcHBwcHBw") // TODO SI-8576 Uninitialized field under -Xcheckinit // check(mutable.ArraySeq(1, 2, 3))( "rO0ABXNyACFzY2FsYS5jb2xsZWN0aW9uLm11dGFibGUuQXJyYXlTZXEVPD3SKEkOcwIAAkkABmxlbmd0aFsABWFycmF5dAATW0xqYXZhL2xhbmcvT2JqZWN0O3hwAAAAA3VyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAANzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNxAH4ABQAAAAJzcQB+AAUAAAAD") - check(mutable.AnyRefMap("a" -> "A"))( "rO0ABXNyACJzY2FsYS5jb2xsZWN0aW9uLm11dGFibGUuQW55UmVmTWFwAAAAAAAAAAECAAdJAAVfc2l6ZUkAB192YWNhbnRJAARtYXNrTAAMZGVmYXVsdEVudHJ5dAARTHNjYWxhL0Z1bmN0aW9uMTtbACtzY2FsYSRjb2xsZWN0aW9uJG11dGFibGUkQW55UmVmTWFwJCRfaGFzaGVzdAACW0lbAClzY2FsYSRjb2xsZWN0aW9uJG11dGFibGUkQW55UmVmTWFwJCRfa2V5c3QAE1tMamF2YS9sYW5nL09iamVjdDtbACtzY2FsYSRjb2xsZWN0aW9uJG11dGFibGUkQW55UmVmTWFwJCRfdmFsdWVzcQB+AAN4cAAAAAEAAAAAAAAAB3NyACFqYXZhLmxhbmcuaW52b2tlLlNlcmlhbGl6ZWRMYW1iZGFvYdCULCk2hQIACkkADmltcGxNZXRob2RLaW5kWwAMY2FwdHVyZWRBcmdzcQB+AANMAA5jYXB0dXJpbmdDbGFzc3QAEUxqYXZhL2xhbmcvQ2xhc3M7TAAYZnVuY3Rpb25hbEludGVyZmFjZUNsYXNzdAASTGphdmEvbGFuZy9TdHJpbmc7TAAdZnVuY3Rpb25hbEludGVyZmFjZU1ldGhvZE5hbWVxAH4AB0wAImZ1bmN0aW9uYWxJbnRlcmZhY2VNZXRob2RTaWduYXR1cmVxAH4AB0wACWltcGxDbGFzc3EAfgAHTAAOaW1wbE1ldGhvZE5hbWVxAH4AB0wAE2ltcGxNZXRob2RTaWduYXR1cmVxAH4AB0wAFmluc3RhbnRpYXRlZE1ldGhvZFR5cGVxAH4AB3hwAAAABnVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAB2cgAjc2NhbGEuY29sbGVjdGlvbi5tdXRhYmxlLkFueVJlZk1hcCTiaIeloJGDLgIABUkACUluZGV4TWFza0kACk1pc3NWYWNhbnRJAApNaXNzaW5nQml0SQAJVmFjYW50Qml0TAA0c2NhbGEkY29sbGVjdGlvbiRtdXRhYmxlJEFueVJlZk1hcCQkZXhjZXB0aW9uRGVmYXVsdHEAfgABeHB0AB5zY2FsYS9ydW50aW1lL2phdmE4L0pGdW5jdGlvbjF0AAVhcHBseXQAJihMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7dAAjc2NhbGEvY29sbGVjdGlvbi9tdXRhYmxlL0FueVJlZk1hcCR0AC5zY2FsYSRjb2xsZWN0aW9uJG11dGFibGUkQW55UmVmTWFwJCQkYW5vbmZ1biQydAAsKExqYXZhL2xhbmcvT2JqZWN0OylMc2NhbGEvcnVudGltZS9Ob3RoaW5nJDtxAH4AEnVyAAJbSU26YCZ26rKlAgAAeHAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPlJANgAAAAB1cQB+AAkAAAAIcHBwcHBwdAABYXB1cQB+AAkAAAAIcHBwcHBwdAABQXA=") + check(mutable.AnyRefMap("a" -> "A"))( "rO0ABXNyACJzY2FsYS5jb2xsZWN0aW9uLm11dGFibGUuQW55UmVmTWFwAAAAAAAAAAECAAdJAAVfc2l6ZUkAB192YWNhbnRJAARtYXNrTAAMZGVmYXVsdEVudHJ5dAARTHNjYWxhL0Z1bmN0aW9uMTtbACtzY2FsYSRjb2xsZWN0aW9uJG11dGFibGUkQW55UmVmTWFwJCRfaGFzaGVzdAACW0lbAClzY2FsYSRjb2xsZWN0aW9uJG11dGFibGUkQW55UmVmTWFwJCRfa2V5c3QAE1tMamF2YS9sYW5nL09iamVjdDtbACtzY2FsYSRjb2xsZWN0aW9uJG11dGFibGUkQW55UmVmTWFwJCRfdmFsdWVzcQB+AAN4cAAAAAEAAAAAAAAAB3NyADNzY2FsYS5jb2xsZWN0aW9uLm11dGFibGUuQW55UmVmTWFwJEV4Y2VwdGlvbkRlZmF1bHQAAAAAAAAAAQIAAHhwdXIAAltJTbpgJnbqsqUCAAB4cAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+UkA2AAAAAHVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAhwcHBwcHB0AAFhcHVxAH4ACQAAAAhwcHBwcHB0AAFBcA==") check(mutable.ArrayStack(1, 2, 3))( "rO0ABXNyACNzY2FsYS5jb2xsZWN0aW9uLm11dGFibGUuQXJyYXlTdGFja3bdxXbcnLBeAgACSQAqc2NhbGEkY29sbGVjdGlvbiRtdXRhYmxlJEFycmF5U3RhY2skJGluZGV4WwAqc2NhbGEkY29sbGVjdGlvbiRtdXRhYmxlJEFycmF5U3RhY2skJHRhYmxldAATW0xqYXZhL2xhbmcvT2JqZWN0O3hwAAAAA3VyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAANzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAA3NxAH4ABQAAAAJzcQB+AAUAAAAB") check(mutable.DoubleLinkedList(1, 2, 3))( "rO0ABXNyAClzY2FsYS5jb2xsZWN0aW9uLm11dGFibGUuRG91YmxlTGlua2VkTGlzdI73LKsKRr1RAgADTAAEZWxlbXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wABG5leHR0AB5Mc2NhbGEvY29sbGVjdGlvbi9tdXRhYmxlL1NlcTtMAARwcmV2cQB+AAJ4cHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3EAfgAAc3EAfgAEAAAAAnNxAH4AAHNxAH4ABAAAAANzcQB+AABwcQB+AAtxAH4ACXEAfgAHcQB+AANw") -- cgit v1.2.3 From 0a3362b3ea5cd7355cd9ccc529783549a4cb5c5f Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 29 Mar 2016 00:21:07 -0700 Subject: Specialization precludes use of LambdaMetaFactory for SAM When a SAM type is specialized (i.e., a specialized type parameter receives a specialized type argument), do not use LambdaMetaFactory (expand during Uncurry instead). This is an implementation restriction -- the current specialization scheme is not amenable to using LambdaMetaFactory to spin up subclasses. Since the generic method is abstract, and the specialized ones are concrete, specialization is rendered moot because we cannot implement the specialized method with the lambda using LMF. --- .../scala/tools/nsc/transform/Constructors.scala | 6 ++-- .../tools/nsc/transform/SpecializeTypes.scala | 13 +++++++++ .../scala/tools/nsc/transform/UnCurry.scala | 8 +++-- .../run/sammy_specialization_restriction.scala | 34 ++++++++++++++++++++++ .../tools/nsc/backend/jvm/IndySammyTest.scala | 24 +++++++++++++++ 5 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 test/files/run/sammy_specialization_restriction.scala diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 1e479d3f63..636fb08b89 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -501,8 +501,6 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { !sym.isSetter ) - private def possiblySpecialized(s: Symbol) = specializeTypes.specializedTypeVars(s).nonEmpty - /* * whether `sym` denotes a param-accessor (ie a field) that fulfills all of: * (a) has stationary value, ie the same value provided via the corresponding ctor-arg; and @@ -511,7 +509,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { * (b.2) the constructor in the specialized (sub-)class. * (c) isn't part of a DelayedInit subclass. */ - private def canBeSupplanted(sym: Symbol) = !isDelayedInitSubclass && isStationaryParamRef(sym) && !possiblySpecialized(sym) + private def canBeSupplanted(sym: Symbol) = !isDelayedInitSubclass && isStationaryParamRef(sym) && !specializeTypes.possiblySpecialized(sym) override def transform(tree: Tree): Tree = tree match { case Apply(Select(This(_), _), List()) => @@ -531,7 +529,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { gen.mkAttributedIdent(parameter(tree.symbol)) setPos tree.pos case Select(_, _) if guardSpecializedFieldInit => // reasoning behind this guard in the docu of `usesSpecializedField` - if (possiblySpecialized(tree.symbol)) { + if (specializeTypes.possiblySpecialized(tree.symbol)) { usesSpecializedField = true } super.transform(tree) diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index 998f0b22cb..0050d08f1b 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -285,6 +285,19 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { for ((tvar, tpe) <- sym.info.typeParams.zip(args) if !tvar.isSpecialized || !isPrimitiveValueType(tpe)) yield tpe + /** Is `member` potentially affected by specialization? This is a gross overapproximation, + * but it should be okay for use outside of specialization. + */ + def possiblySpecialized(sym: Symbol) = specializedTypeVars(sym).nonEmpty + + /** Refines possiblySpecialized taking into account the instantiation of the specialized type variables at `site` */ + def isSpecializedIn(sym: Symbol, site: Type) = + specializedTypeVars(sym) exists { tvar => + val concretes = concreteTypes(tvar) + (concretes contains AnyRefClass) || (concretes contains site.memberType(tvar)) + } + + val specializedType = new TypeMap { override def apply(tp: Type): Type = tp match { case TypeRef(pre, sym, args) if args.nonEmpty => diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index afa0fc92ff..9c4b125fc1 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -77,8 +77,8 @@ abstract class UnCurry extends InfoTransform // 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 canUseLambdaMetaFactory = inConstructorFlag == 0 && (fun.attachments.get[SAMFunction] match { + case Some(SAMFunction(userDefinedSamTp, sam)) => val tpSym = erasure.javaErasure(userDefinedSamTp).typeSymbol // we only care about what ends up in the bytecode ( // LMF only supports interfaces @@ -90,7 +90,9 @@ abstract class UnCurry extends InfoTransform // 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 + // specialization and LMF are at odds, since LMF implements the single abstract method, + // but that's the one that specialization leaves generic, whereas we need to implement the specialized one to avoid boxing + && !specializeTypes.isSpecializedIn(sam, userDefinedSamTp) ) case _ => true // our built-in FunctionN's are suitable for LambdaMetaFactory by construction diff --git a/test/files/run/sammy_specialization_restriction.scala b/test/files/run/sammy_specialization_restriction.scala new file mode 100644 index 0000000000..4487bb3ad7 --- /dev/null +++ b/test/files/run/sammy_specialization_restriction.scala @@ -0,0 +1,34 @@ +trait T[@specialized A] { def apply(a: A): A } +trait TInt extends T[Int] + +// Check that we expand the SAM of a type that is specialized. +// This is an implementation restriction -- the current specialization scheme is not +// amenable to using LambdaMetaFactory to spin up subclasses. +// Since the generic method is abstract, and the specialized ones are concrete, +// specialization is rendered moot because we cannot implement the specialized method +// with the lambda using LMF. +object Test extends App { + final val AnonFunClass = "$anonfun$" + final val LMFClass = "$$Lambda$" // LambdaMetaFactory names classes like this + + def specializedSamPrecludesLMF() = { + val className = ((x => x): T[Int]).getClass.toString + assert((className contains AnonFunClass), className) + assert(!(className contains LMFClass), className) + } + + def specializedSamSubclassPrecludesLMF() = { + val className = ((x => x): TInt).getClass.toString + assert((className contains AnonFunClass), className) + assert(!(className contains LMFClass), className) + } + + def nonSpecializedSamUsesLMF() = { + val className = ((x => x): T[String]).getClass.toString + assert(!(className contains AnonFunClass), className) + assert(className contains LMFClass, className) + } + + specializedSamPrecludesLMF() + nonSpecializedSamUsesLMF() +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala index f7218c576c..b9e45a7dc9 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala @@ -71,6 +71,23 @@ class IndySammyTest extends ClearAfterClass { ) } +// def testSpecial(lam: String, lamTp: String, arg: String)(allowMessage: StoreReporter#Info => Boolean = _ => false) = { +// val cls = compile("trait Special[@specialized A] { def apply(a: A): A}" ) +// val methodNodes = compileMethods(compiler)(s"def lam : $lamTp = $lam" +";"+ appDef(arg), allowMessage) +// +// val anonfun = methodNodes.filter(_.name contains "$anonfun$").map(convertMethod) +// val lamInsn = methodNodes.find(_.name == "lam").map(instructionsFromMethod).get.dropNonOp +// val applyInvoke = methodNodes.find(_.name == "app").map(convertMethod).get +// +// assert(lamInsn.length == 2 && lamInsn.head.isInstanceOf[InvokeDynamic], lamInsn) +// assertSameCode(anonfun, lamBody) +// assertSameCode(applyInvoke, List( +// VarOp(ALOAD, 0), +// Invoke(INVOKEVIRTUAL, "C", "lam", s"()L${funClassName(from, to)};", false)) ++ appArgs ++ List( +// Invoke(INVOKEINTERFACE, funClassName(from, to), "apply", applySig, true), ret) +// ) +// } + // x => x : VC => VC applied to VC(1) @Test def testVC_VC_VC = @@ -127,6 +144,13 @@ class IndySammyTest extends ClearAfterClass { List(Op(ICONST_1), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "boxToInteger", "(I)Ljava/lang/Integer;", false)), Op(IRETURN))() + // TODO + // x => x : Special[Int] applied to 1 +// @Test +// def testSpecial_Int_1 = +// testSpecial("x => x", "Special[Int]", "1")() + + // Tests ThisReferringMethodsTraverser @Test def testStaticIfNoThisReference: Unit = { -- cgit v1.2.3 From 63f017586f31de11bc6004dca7cea0c26ceb5ff5 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 29 Mar 2016 16:47:47 -0700 Subject: Better detection of types LMF cannot instantiate. LambdaMetaFactory can only properly instantiate Java interfaces (with one abstract method, of course). A trait always compiles to an interface, but a subclass that can be instantiated may require mixing in further members, which LMF cannot do. (Nested traits, traits with fields,... do not qualify.) Traits that cannot be instantiated by LMF are still SAM targets, we simply created anonymous subclasses as before. --- .../scala/tools/nsc/transform/Erasure.scala | 32 +++++++++++++++++ .../scala/tools/nsc/transform/UnCurry.scala | 21 ++++------- test/files/run/sammy_restrictions_LMF.scala | 42 ++++++++++++++++++++++ .../run/sammy_specialization_restriction.scala | 34 ------------------ 4 files changed, 80 insertions(+), 49 deletions(-) create mode 100644 test/files/run/sammy_restrictions_LMF.scala delete mode 100644 test/files/run/sammy_specialization_restriction.scala diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 2df4265573..cdf3e18b5a 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -1187,5 +1187,37 @@ abstract class Erasure extends AddInterfaces bridge.resetFlag(BRIDGE) } + /** Does this symbol compile to the underlying platform's notion of an interface, + * without requiring compiler magic before it can be instantiated? + * + * More specifically, we're interested in whether LambdaMetaFactory can instantiate this type, + * assuming it has a single abstract method. In other words, if we were to mix this + * trait into a class, it should not result in any compiler-generated members having to be + * implemented in ("mixed in to") this class (except for the SAM). + * + * Thus, the type must erase to a java interface, either by virtue of being defined as one, + * or by being a trait that: + * - is static (explicitouter or lambdalift may add disqualifying members) + * - extends only other traits that compile to pure interfaces (except for Any) + * - has no val/var members + * + * TODO: can we speed this up using the INTERFACE flag, or set it correctly by construction? + */ + final def compilesToPureInterface(tpSym: Symbol): Boolean = { + def ok(sym: Symbol) = + sym.isJavaInterface || + sym.isTrait && + // Unless sym.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. + sym.isStatic && + (sym.isInterface || sym.info.decls.forall(mem => mem.isMethod || mem.isType)) // TODO OPT: && {sym setFlag INTERFACE; true}) + + // we still need to check our ancestors even if the INTERFACE flag is set, as it doesn't take inheritance into account + ok(tpSym) && tpSym.ancestors.forall(sym => (sym eq AnyClass) || (sym eq ObjectClass) || ok(sym)) + } + private class TypeRefAttachment(val tpe: TypeRef) } diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 9c4b125fc1..50e413fba2 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -79,21 +79,12 @@ abstract class UnCurry extends InfoTransform // (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] match { case Some(SAMFunction(userDefinedSamTp, sam)) => - 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) - // specialization and LMF are at odds, since LMF implements the single abstract method, - // but that's the one that specialization leaves generic, whereas we need to implement the specialized one to avoid boxing - && !specializeTypes.isSpecializedIn(sam, userDefinedSamTp) - ) + // LambdaMetaFactory cannot mix in trait members for us, or instantiate classes -- only pure interfaces need apply + erasure.compilesToPureInterface(erasure.javaErasure(userDefinedSamTp).typeSymbol) && + // 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) + // specialization and LMF are at odds, since LMF implements the single abstract method, + // but that's the one that specialization leaves generic, whereas we need to implement the specialized one to avoid boxing + !specializeTypes.isSpecializedIn(sam, userDefinedSamTp) case _ => true // our built-in FunctionN's are suitable for LambdaMetaFactory by construction }) diff --git a/test/files/run/sammy_restrictions_LMF.scala b/test/files/run/sammy_restrictions_LMF.scala new file mode 100644 index 0000000000..40bb234a72 --- /dev/null +++ b/test/files/run/sammy_restrictions_LMF.scala @@ -0,0 +1,42 @@ +trait T[@specialized A] { def apply(a: A): A } +trait TInt extends T[Int] + +trait TWithVal { val x: Any = 1; def apply(x: Int): String } + +object Test extends App { + final val AnonFunClass = "$anonfun$" + final val LMFClass = "$$Lambda$" // LambdaMetaFactory names classes like this + + private def LMF(f: Any): Unit = { + val className = f.getClass.toString + assert(!(className contains AnonFunClass), className) + assert((className contains LMFClass), className) + } + + private def notLMF(f: Any): Unit = { + val className = f.getClass.toString + assert((className contains AnonFunClass), className) + assert(!(className contains LMFClass), className) + } + + // Check that we expand the SAM of a type that is specialized. + // This is an implementation restriction -- the current specialization scheme is not + // amenable to using LambdaMetaFactory to spin up subclasses. + // Since the generic method is abstract, and the specialized ones are concrete, + // specialization is rendered moot because we cannot implement the specialized method + // with the lambda using LMF. + + // not LMF if specialized at this type + notLMF((x => x): T[Int]) + // not LMF if specialized at this type (via subclass) + notLMF((x => x): TInt) + // LMF ok if not specialized at this type + LMF((x => x): T[String]) + + // traits with a val member also cannot be instantiated by LMF + val fVal: TWithVal = (x => "a") + notLMF(fVal) + assert(fVal.x == 1) + + +} diff --git a/test/files/run/sammy_specialization_restriction.scala b/test/files/run/sammy_specialization_restriction.scala deleted file mode 100644 index 4487bb3ad7..0000000000 --- a/test/files/run/sammy_specialization_restriction.scala +++ /dev/null @@ -1,34 +0,0 @@ -trait T[@specialized A] { def apply(a: A): A } -trait TInt extends T[Int] - -// Check that we expand the SAM of a type that is specialized. -// This is an implementation restriction -- the current specialization scheme is not -// amenable to using LambdaMetaFactory to spin up subclasses. -// Since the generic method is abstract, and the specialized ones are concrete, -// specialization is rendered moot because we cannot implement the specialized method -// with the lambda using LMF. -object Test extends App { - final val AnonFunClass = "$anonfun$" - final val LMFClass = "$$Lambda$" // LambdaMetaFactory names classes like this - - def specializedSamPrecludesLMF() = { - val className = ((x => x): T[Int]).getClass.toString - assert((className contains AnonFunClass), className) - assert(!(className contains LMFClass), className) - } - - def specializedSamSubclassPrecludesLMF() = { - val className = ((x => x): TInt).getClass.toString - assert((className contains AnonFunClass), className) - assert(!(className contains LMFClass), className) - } - - def nonSpecializedSamUsesLMF() = { - val className = ((x => x): T[String]).getClass.toString - assert(!(className contains AnonFunClass), className) - assert(className contains LMFClass, className) - } - - specializedSamPrecludesLMF() - nonSpecializedSamUsesLMF() -} -- cgit v1.2.3 From 62d97d7f110894a0c2f36b1ed9dd7ad59c0115fa Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 29 Mar 2016 19:25:06 -0700 Subject: LMF cannot run trait's "initializer" (constructor) Thus, rule out traits that have a constructor (which we use as a proxy for having potentially side-effecting statements), and create an anonymous subclass for them at compile time. --- src/compiler/scala/tools/nsc/transform/Erasure.scala | 4 ++++ test/files/run/sammy_restrictions_LMF.check | 2 ++ test/files/run/sammy_restrictions_LMF.scala | 8 +++++++- 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 test/files/run/sammy_restrictions_LMF.check diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index cdf3e18b5a..ebb55afca9 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -1213,6 +1213,10 @@ abstract class Erasure extends AddInterfaces // 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. sym.isStatic && + // HACK: this is to rule out traits with an effectful initializer. + // The constructor only exists if the trait's template has statements. + // Sadly, we can't be more precise without access to the tree that defines the SAM's owner. + !sym.primaryConstructor.exists && (sym.isInterface || sym.info.decls.forall(mem => mem.isMethod || mem.isType)) // TODO OPT: && {sym setFlag INTERFACE; true}) // we still need to check our ancestors even if the INTERFACE flag is set, as it doesn't take inheritance into account diff --git a/test/files/run/sammy_restrictions_LMF.check b/test/files/run/sammy_restrictions_LMF.check new file mode 100644 index 0000000000..6ed281c757 --- /dev/null +++ b/test/files/run/sammy_restrictions_LMF.check @@ -0,0 +1,2 @@ +1 +1 diff --git a/test/files/run/sammy_restrictions_LMF.scala b/test/files/run/sammy_restrictions_LMF.scala index 40bb234a72..27a3d21dad 100644 --- a/test/files/run/sammy_restrictions_LMF.scala +++ b/test/files/run/sammy_restrictions_LMF.scala @@ -3,6 +3,11 @@ trait TInt extends T[Int] trait TWithVal { val x: Any = 1; def apply(x: Int): String } +trait TImpure { def apply(x: Int): String ; println(1) } + +trait Println { println(1) } +trait TImpureSuper extends Println { def apply(x: Int): String } + object Test extends App { final val AnonFunClass = "$anonfun$" final val LMFClass = "$$Lambda$" // LambdaMetaFactory names classes like this @@ -38,5 +43,6 @@ object Test extends App { notLMF(fVal) assert(fVal.x == 1) - + notLMF((x => "a"): TImpure) + notLMF((x => "a"): TImpureSuper) } -- cgit v1.2.3 From 3904c3216c741b387d81754e55aa079ce4218d06 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 29 Mar 2016 18:18:09 -0700 Subject: LMF cannot instantiate SAM of trait with non-trait superclass Also, drop AbstractFunction for parent of anonymous subclass of function type that must have its class spun up at compile time (rather than at linkage time by LambdaMetaFactory). This revealed an old problem with typedTemplate, in which parent types may be normalized at the level of trees, while this change does not get propagated to the class's info in time for the constructor to be located when we type check the primary constructor. --- src/compiler/scala/tools/nsc/ast/TreeGen.scala | 6 +--- .../scala/tools/nsc/typechecker/Typers.scala | 36 ++++++++++++++++++---- .../scala/reflect/internal/Definitions.scala | 1 - src/reflect/scala/reflect/internal/Types.scala | 5 --- test/files/neg/t5761.check | 2 +- .../run/delambdafy_uncurry_byname_inline.check | 2 +- test/files/run/delambdafy_uncurry_inline.check | 2 +- test/files/run/sammy_restrictions_LMF.scala | 9 ++++++ test/files/run/t6028.check | 6 ++-- test/files/run/t6555.check | 2 +- 10 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index 27e366e725..227d395036 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -310,13 +310,9 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { newDefDef(methSym, moveToMethod(useMethodParams(fun.body)))(tpt = TypeTree(resTp)) } - // TODO: the rewrite to AbstractFunction is superfluous once we compile FunctionN to a SAM type (aka functional interface) - def functionClassType(fun: Function): Type = - if (isFunctionType(fun.tpe)) abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.body.tpe.deconst) - else fun.tpe def expandFunction(localTyper: analyzer.Typer)(fun: Function, inConstructorFlag: Long): Tree = { - val parents = addObjectParent(addSerializable(functionClassType(fun))) + val parents = addSerializable(fun.tpe) val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation // The original owner is used in the backend for the EnclosingMethod attribute. If fun is diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 35cfc644ab..ff0513156b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1747,17 +1747,21 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper classinfo.parents map (_.instantiateTypeParams(List(tparam), List(AnyRefTpe))), classinfo.decls, clazz) - clazz.setInfo { - clazz.info match { - case PolyType(tparams, _) => PolyType(tparams, newinfo) - case _ => newinfo - } - } + updatePolyClassInfo(clazz, newinfo) FinitaryError(tparam) } } } + private def updatePolyClassInfo(clazz: Symbol, newinfo: ClassInfoType): clazz.type = { + clazz.setInfo { + clazz.info match { + case PolyType(tparams, _) => PolyType(tparams, newinfo) + case _ => newinfo + } + } + } + def typedClassDef(cdef: ClassDef): Tree = { val clazz = cdef.symbol val typedMods = typedModifiers(cdef.mods) @@ -1866,6 +1870,26 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // please FIXME: uncommenting this line breaks everything // val templ = treeCopy.Template(templ0, templ0.body, templ0.self, templ0.parents) val clazz = context.owner + + val parentTypes = parents1.map(_.tpe) + + // The parents may have been normalized by typedParentTypes. + // We must update the info as well, or we won't find the super constructor for our now-first parent class + // Consider `class C ; trait T extends C ; trait U extends T` + // `U`'s info will start with parent `T`, but `typedParentTypes` will return `List(C, T)` (`== parents1`) + // now, the super call in the primary ctor will fail to find `C`'s ctor, since it bases its search on + // `U`'s info, not the trees. + // + // For correctness and performance, we restrict this rewrite to anonymous classes, + // as others have their parents in order already (it seems!), and we certainly + // don't want to accidentally rewire superclasses for e.g. the primitive value classes. + // + // TODO: Find an example of a named class needing this rewrite, I tried but couldn't find one. + if (clazz.isAnonymousClass && clazz.info.parents != parentTypes) { +// println(s"updating parents of $clazz from ${clazz.info.parents} to $parentTypes") + updatePolyClassInfo(clazz, ClassInfoType(parentTypes, clazz.info.decls, clazz)) + } + clazz.annotations.map(_.completeInfo()) if (templ.symbol == NoSymbol) templ setSymbol clazz.newLocalDummy(templ.pos) diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 8074b448fe..e9baa47d82 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -581,7 +581,6 @@ trait Definitions extends api.StandardDefinitions { /** Creators for TupleN, ProductN, FunctionN. */ def tupleType(elems: List[Type]) = TupleClass.specificType(elems) def functionType(formals: List[Type], restpe: Type) = FunctionClass.specificType(formals, restpe) - def abstractFunctionType(formals: List[Type], restpe: Type) = AbstractFunctionClass.specificType(formals, restpe) def wrapArrayMethodName(elemtp: Type): TermName = elemtp.typeSymbol match { case ByteClass => nme.wrapByteArray diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 00df55f044..f385ca08c9 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -4553,11 +4553,6 @@ trait Types else (ps :+ SerializableTpe).toList ) - def addObjectParent(tps: List[Type]) = tps match { - case hd :: _ if hd.typeSymbol.isTrait => ObjectTpe :: tps - case _ => tps - } - /** Adds the @uncheckedBound annotation if the given `tp` has type arguments */ final def uncheckedBounds(tp: Type): Type = { if (tp.typeArgs.isEmpty || UncheckedBoundsClass == NoSymbol) tp // second condition for backwards compatibility with older scala-reflect.jar diff --git a/test/files/neg/t5761.check b/test/files/neg/t5761.check index 2d66af26f6..15c0bc7634 100644 --- a/test/files/neg/t5761.check +++ b/test/files/neg/t5761.check @@ -13,7 +13,7 @@ Unspecified value parameter x. t5761.scala:13: error: not found: type Tread new Tread("sth") { }.run() ^ -t5761.scala:13: error: value run is not a member of AnyRef +t5761.scala:13: error: value run is not a member of new Tread("sth") { }.run() ^ 5 errors found diff --git a/test/files/run/delambdafy_uncurry_byname_inline.check b/test/files/run/delambdafy_uncurry_byname_inline.check index d96a995f44..e1ee4c29e2 100644 --- a/test/files/run/delambdafy_uncurry_byname_inline.check +++ b/test/files/run/delambdafy_uncurry_byname_inline.check @@ -7,7 +7,7 @@ package { }; def bar(x: () => Int): Int = x.apply(); def foo(): Int = Foo.this.bar({ - @SerialVersionUID(value = 0) final class $anonfun extends scala.runtime.AbstractFunction0[Int] with Serializable { + @SerialVersionUID(value = 0) final class $anonfun extends Object with () => Int with Serializable { def (): <$anon: () => Int> = { $anonfun.super.(); () diff --git a/test/files/run/delambdafy_uncurry_inline.check b/test/files/run/delambdafy_uncurry_inline.check index 5521cc4a2c..479e9409fa 100644 --- a/test/files/run/delambdafy_uncurry_inline.check +++ b/test/files/run/delambdafy_uncurry_inline.check @@ -7,7 +7,7 @@ package { }; def bar(): Unit = { val f: Int => Int = { - @SerialVersionUID(value = 0) final class $anonfun extends scala.runtime.AbstractFunction1[Int,Int] with Serializable { + @SerialVersionUID(value = 0) final class $anonfun extends Object with Int => Int with Serializable { def (): <$anon: Int => Int> = { $anonfun.super.(); () diff --git a/test/files/run/sammy_restrictions_LMF.scala b/test/files/run/sammy_restrictions_LMF.scala index 27a3d21dad..aa49e14113 100644 --- a/test/files/run/sammy_restrictions_LMF.scala +++ b/test/files/run/sammy_restrictions_LMF.scala @@ -8,6 +8,11 @@ trait TImpure { def apply(x: Int): String ; println(1) } trait Println { println(1) } trait TImpureSuper extends Println { def apply(x: Int): String } +class C +trait A extends C +trait B extends A +trait TClassParent extends B { def apply(x: Int): String } + object Test extends App { final val AnonFunClass = "$anonfun$" final val LMFClass = "$$Lambda$" // LambdaMetaFactory names classes like this @@ -45,4 +50,8 @@ object Test extends App { notLMF((x => "a"): TImpure) notLMF((x => "a"): TImpureSuper) + + val fClassParent: TClassParent = x => "a" + notLMF(fClassParent) + assert(fClassParent(1) == "a") } diff --git a/test/files/run/t6028.check b/test/files/run/t6028.check index 532d177300..f21b77fdc7 100644 --- a/test/files/run/t6028.check +++ b/test/files/run/t6028.check @@ -24,9 +24,10 @@ package { (new <$anon: Function0>(T.this, tryyParam, tryyLocal): Function0) } }; - @SerialVersionUID(value = 0) final class $anonfun$foo$1 extends scala.runtime.AbstractFunction0$mcI$sp with Serializable { + @SerialVersionUID(value = 0) final class $anonfun$foo$1 extends Object with Function0$mcI$sp with Serializable { def ($outer: T, methodParam$1: Int, methodLocal$1: Int): <$anon: Function0> = { $anonfun$foo$1.super.(); + $anonfun$foo$1.super./*Function0*/$init$(); () }; final def apply(): Int = $anonfun$foo$1.this.apply$mcI$sp(); @@ -66,9 +67,10 @@ package { T.this.MethodLocalObject$lzycompute$1(barParam$1, MethodLocalObject$module$1) else MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type](); - @SerialVersionUID(value = 0) final class $anonfun$tryy$1 extends scala.runtime.AbstractFunction0$mcV$sp with Serializable { + @SerialVersionUID(value = 0) final class $anonfun$tryy$1 extends Object with Function0$mcV$sp with Serializable { def ($outer: T, tryyParam$1: Int, tryyLocal$1: runtime.IntRef): <$anon: Function0> = { $anonfun$tryy$1.super.(); + $anonfun$tryy$1.super./*Function0*/$init$(); () }; final def apply(): Unit = $anonfun$tryy$1.this.apply$mcV$sp(); diff --git a/test/files/run/t6555.check b/test/files/run/t6555.check index e3b467ce7c..fef689a80d 100644 --- a/test/files/run/t6555.check +++ b/test/files/run/t6555.check @@ -6,7 +6,7 @@ package { () }; private[this] val f: Int => Int = { - @SerialVersionUID(value = 0) final class $anonfun extends scala.runtime.AbstractFunction1$mcII$sp with Serializable { + @SerialVersionUID(value = 0) final class $anonfun extends Object with Int => Int with Serializable { def (): <$anon: Int => Int> = { $anonfun.super.(); () -- cgit v1.2.3 From aec2b940cfa04843efe2eab00272557823fd8dd2 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 30 Mar 2016 22:44:34 +1000 Subject: Keep SAM body in anonfun method in enclosing class Rather than in implementation of the abstract method in the expanded anonymous class. This leads to more more efficient use of the constant pool, code shapes more amenable to SAM inlining, and is compatible with the old behaviour of `-Xexperimental` in Scala 2.11, which ScalaJS now relies upon. Manual test: ``` scala> :paste -raw // Entering paste mode (ctrl-D to finish) package p1; trait T { val x = 0; def apply(): Any }; class DelambdafyInline { def t: T = (() => "") } // Exiting paste mode, now interpreting. scala> :javap -c p1.DelambdafyInline Compiled from "" public class p1.DelambdafyInline { public p1.T t(); Code: 0: new #10 // class p1/DelambdafyInline$$anonfun$t$1 3: dup 4: aload_0 5: invokespecial #16 // Method p1/DelambdafyInline$$anonfun$t$1."":(Lp1/DelambdafyInline;)V 8: areturn public final java.lang.Object p1$DelambdafyInline$$$anonfun$1(); Code: 0: ldc #22 // String 2: areturn public p1.DelambdafyInline(); Code: 0: aload_0 1: invokespecial #25 // Method java/lang/Object."":()V 4: return } scala> :javap -c p1.DelambdafyInline$$anonfun$t$1 Compiled from "" public final class p1.DelambdafyInline$$anonfun$t$1 implements p1.T,scala.Serializable { public static final long serialVersionUID; public int x(); Code: 0: aload_0 1: getfield #25 // Field x:I 4: ireturn public void p1$T$_setter_$x_$eq(int); Code: 0: aload_0 1: iload_1 2: putfield #25 // Field x:I 5: return public final java.lang.Object apply(); Code: 0: aload_0 1: getfield #34 // Field $outer:Lp1/DelambdafyInline; 4: invokevirtual #37 // Method p1/DelambdafyInline.p1$DelambdafyInline$$$anonfun$1:()Ljava/lang/Object; 7: areturn public p1.DelambdafyInline$$anonfun$t$1(p1.DelambdafyInline); Code: 0: aload_1 1: ifnonnull 6 4: aconst_null 5: athrow 6: aload_0 7: aload_1 8: putfield #34 // Field $outer:Lp1/DelambdafyInline; 11: aload_0 12: invokespecial #42 // Method java/lang/Object."":()V 15: aload_0 16: invokespecial #45 // Method p1/T.$init$:()V 19: return } scala> :quit ``` Adriaan is to `git blame` for `reflection-mem-typecheck.scala`. --- src/compiler/scala/tools/nsc/transform/UnCurry.scala | 16 +++++++++++----- test/files/run/reflection-mem-typecheck.scala | 4 +++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 50e413fba2..44d2469621 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -75,9 +75,9 @@ abstract class UnCurry extends InfoTransform // Expand `Function`s in constructors to class instance creation (SI-6666, SI-8363) // We use Java's LambdaMetaFactory (LMF), which requires an interface for the sam's owner - private def mustExpandFunction(fun: Function) = forceExpandFunction || { + private def mustExpandFunction(fun: Function) = { // (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] match { + val canUseLambdaMetaFactory = (fun.attachments.get[SAMFunction] match { case Some(SAMFunction(userDefinedSamTp, sam)) => // LambdaMetaFactory cannot mix in trait members for us, or instantiate classes -- only pure interfaces need apply erasure.compilesToPureInterface(erasure.javaErasure(userDefinedSamTp).typeSymbol) && @@ -207,8 +207,10 @@ 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(fun)) gen.expandFunction(localTyper)(fun, inConstructorFlag) - else { + else if (forceExpandFunction || inConstructorFlag != 0) { + // Expand the function body into an anonymous class + 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) @@ -217,7 +219,11 @@ abstract class UnCurry extends InfoTransform gen.mkForwarder(gen.mkAttributedRef(liftedMethod.symbol), (fun.vparams map (_.symbol)) :: Nil) )) - localTyper.typedPos(fun.pos)(Block(liftedMethod, super.transform(newFun))) + val typedNewFun = localTyper.typedPos(fun.pos)(Block(liftedMethod, super.transform(newFun))) + if (mustExpandFunction(fun)) { + val Block(stats, expr : Function) = typedNewFun + treeCopy.Block(typedNewFun, stats, gen.expandFunction(localTyper)(expr, inConstructorFlag)) + } else typedNewFun } def transformArgs(pos: Position, fun: Symbol, args: List[Tree], formals: List[Type]) = { diff --git a/test/files/run/reflection-mem-typecheck.scala b/test/files/run/reflection-mem-typecheck.scala index e3cabf689d..93ec1c937a 100644 --- a/test/files/run/reflection-mem-typecheck.scala +++ b/test/files/run/reflection-mem-typecheck.scala @@ -11,7 +11,9 @@ object Test extends MemoryTest { cm.mkToolBox() } - override def maxDelta = 10 + // I'm not sure this is a great way to test for memory leaks, + // since we're also testing how good the JVM's GC is, and this is not easily reproduced between machines/over time + override def maxDelta = 12 override def calcsPerIter = 8 override def calc() { var snippet = """ -- cgit v1.2.3 From 7025be9a468419ca6076d78f8da32c6a667fc829 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 30 Mar 2016 14:02:26 -0700 Subject: Bring back AbstractFunction parent Jason points out we still need it for bytecode efficiency, due to mixin forwarders. --- src/compiler/scala/tools/nsc/ast/TreeGen.scala | 6 +++++- src/reflect/scala/reflect/internal/Definitions.scala | 1 + test/files/run/delambdafy_uncurry_byname_inline.check | 2 +- test/files/run/delambdafy_uncurry_inline.check | 2 +- test/files/run/t6028.check | 6 ++---- test/files/run/t6555.check | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index 227d395036..0786ceb7c2 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -310,9 +310,13 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { newDefDef(methSym, moveToMethod(useMethodParams(fun.body)))(tpt = TypeTree(resTp)) } + // TODO: the rewrite to AbstractFunction is superfluous once we compile FunctionN to a SAM type (aka functional interface) + def functionClassType(fun: Function): Type = + if (isFunctionType(fun.tpe)) abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.body.tpe.deconst) + else fun.tpe def expandFunction(localTyper: analyzer.Typer)(fun: Function, inConstructorFlag: Long): Tree = { - val parents = addSerializable(fun.tpe) + val parents = addSerializable(functionClassType(fun)) val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation // The original owner is used in the backend for the EnclosingMethod attribute. If fun is diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index e9baa47d82..8074b448fe 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -581,6 +581,7 @@ trait Definitions extends api.StandardDefinitions { /** Creators for TupleN, ProductN, FunctionN. */ def tupleType(elems: List[Type]) = TupleClass.specificType(elems) def functionType(formals: List[Type], restpe: Type) = FunctionClass.specificType(formals, restpe) + def abstractFunctionType(formals: List[Type], restpe: Type) = AbstractFunctionClass.specificType(formals, restpe) def wrapArrayMethodName(elemtp: Type): TermName = elemtp.typeSymbol match { case ByteClass => nme.wrapByteArray diff --git a/test/files/run/delambdafy_uncurry_byname_inline.check b/test/files/run/delambdafy_uncurry_byname_inline.check index e1ee4c29e2..d96a995f44 100644 --- a/test/files/run/delambdafy_uncurry_byname_inline.check +++ b/test/files/run/delambdafy_uncurry_byname_inline.check @@ -7,7 +7,7 @@ package { }; def bar(x: () => Int): Int = x.apply(); def foo(): Int = Foo.this.bar({ - @SerialVersionUID(value = 0) final class $anonfun extends Object with () => Int with Serializable { + @SerialVersionUID(value = 0) final class $anonfun extends scala.runtime.AbstractFunction0[Int] with Serializable { def (): <$anon: () => Int> = { $anonfun.super.(); () diff --git a/test/files/run/delambdafy_uncurry_inline.check b/test/files/run/delambdafy_uncurry_inline.check index 479e9409fa..5521cc4a2c 100644 --- a/test/files/run/delambdafy_uncurry_inline.check +++ b/test/files/run/delambdafy_uncurry_inline.check @@ -7,7 +7,7 @@ package { }; def bar(): Unit = { val f: Int => Int = { - @SerialVersionUID(value = 0) final class $anonfun extends Object with Int => Int with Serializable { + @SerialVersionUID(value = 0) final class $anonfun extends scala.runtime.AbstractFunction1[Int,Int] with Serializable { def (): <$anon: Int => Int> = { $anonfun.super.(); () diff --git a/test/files/run/t6028.check b/test/files/run/t6028.check index f21b77fdc7..532d177300 100644 --- a/test/files/run/t6028.check +++ b/test/files/run/t6028.check @@ -24,10 +24,9 @@ package { (new <$anon: Function0>(T.this, tryyParam, tryyLocal): Function0) } }; - @SerialVersionUID(value = 0) final class $anonfun$foo$1 extends Object with Function0$mcI$sp with Serializable { + @SerialVersionUID(value = 0) final class $anonfun$foo$1 extends scala.runtime.AbstractFunction0$mcI$sp with Serializable { def ($outer: T, methodParam$1: Int, methodLocal$1: Int): <$anon: Function0> = { $anonfun$foo$1.super.(); - $anonfun$foo$1.super./*Function0*/$init$(); () }; final def apply(): Int = $anonfun$foo$1.this.apply$mcI$sp(); @@ -67,10 +66,9 @@ package { T.this.MethodLocalObject$lzycompute$1(barParam$1, MethodLocalObject$module$1) else MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type](); - @SerialVersionUID(value = 0) final class $anonfun$tryy$1 extends Object with Function0$mcV$sp with Serializable { + @SerialVersionUID(value = 0) final class $anonfun$tryy$1 extends scala.runtime.AbstractFunction0$mcV$sp with Serializable { def ($outer: T, tryyParam$1: Int, tryyLocal$1: runtime.IntRef): <$anon: Function0> = { $anonfun$tryy$1.super.(); - $anonfun$tryy$1.super./*Function0*/$init$(); () }; final def apply(): Unit = $anonfun$tryy$1.this.apply$mcV$sp(); diff --git a/test/files/run/t6555.check b/test/files/run/t6555.check index fef689a80d..e3b467ce7c 100644 --- a/test/files/run/t6555.check +++ b/test/files/run/t6555.check @@ -6,7 +6,7 @@ package { () }; private[this] val f: Int => Int = { - @SerialVersionUID(value = 0) final class $anonfun extends Object with Int => Int with Serializable { + @SerialVersionUID(value = 0) final class $anonfun extends scala.runtime.AbstractFunction1$mcII$sp with Serializable { def (): <$anon: Int => Int> = { $anonfun.super.(); () -- cgit v1.2.3 From 8e32d00e1679114ee1b3f9a90f235d2ab9feba2e Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 30 Mar 2016 17:17:34 -0700 Subject: Keep Function when CBN arg thunk targets a SAM The body of `def delay[T](v: => T) = (v _): F0[T]` becomes `() => v` during `typedEta`, and then uncurry considers whether to strip the function wrapper since `v` is known to be a `Function0` thunk. Stripping is sound when the expected type is `Function0` for this expression, but that's no longer a given, since we could be expecting any nullary SAM. Also sweep up a bit around `typedEta`. Encapsulate the, erm, creative encoding of `m _` as `Typed(m, Function(Nil, EmptyTree))`. --- .../scala/tools/nsc/ast/parser/Parsers.scala | 4 +- .../scala/tools/nsc/ast/parser/TreeBuilder.scala | 3 ++ .../scala/tools/nsc/transform/UnCurry.scala | 7 ++- .../scala/tools/nsc/typechecker/Typers.scala | 56 ++++++++++++---------- test/files/run/sammy_cbn.scala | 9 ++++ 5 files changed, 50 insertions(+), 29 deletions(-) create mode 100644 test/files/run/sammy_cbn.scala diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index d4715471f6..9c0174d89b 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -1713,9 +1713,7 @@ self => } simpleExprRest(app, canApply = true) case USCORE => - atPos(t.pos.start, in.skipToken()) { - Typed(stripParens(t), Function(Nil, EmptyTree)) - } + atPos(t.pos.start, in.skipToken()) { makeMethodValue(stripParens(t)) } case _ => t } diff --git a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala index 473a40f42a..1e9a1762eb 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala @@ -35,6 +35,9 @@ abstract class TreeBuilder { def repeatedApplication(tpe: Tree): Tree = AppliedTypeTree(rootScalaDot(tpnme.REPEATED_PARAM_CLASS_NAME), List(tpe)) + // represents `expr _`, as specified in Method Values of spec/06-expressions.md + def makeMethodValue(expr: Tree): Tree = Typed(expr, Function(Nil, EmptyTree)) + def makeImportSelector(name: Name, nameOffset: Int): ImportSelector = ImportSelector(name, nameOffset, name, nameOffset) diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 44d2469621..628090dba5 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -205,8 +205,11 @@ 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 } + // Undo eta expansion for parameterless and nullary methods, EXCEPT if `fun` targets a SAM. + // Normally, we can unwrap `() => cbn` to `cbn` where `cbn` refers to a CBN argument (typically `cbn` is an Ident), + // because we know `cbn` will already be a `Function0` thunk. When we're targeting a SAM, + // the types don't align and we must preserve the function wrapper. + if (fun.vparams.isEmpty && isByNameRef(fun.body) && fun.attachments.get[SAMFunction].isEmpty) { noApply += fun.body ; fun.body } else if (forceExpandFunction || inConstructorFlag != 0) { // Expand the function body into an anonymous class gen.expandFunction(localTyper)(fun, inConstructorFlag) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index ff0513156b..d7c577e97c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2879,8 +2879,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper silent(_.typed(fn, mode.forFunMode, pt)) filter (_ => context.undetparams.isEmpty) map { fn1 => // if context.undetparams is not empty, the function was polymorphic, // so we need the missing arguments to infer its type. See #871 - //println("typing eta "+fun+":"+fn1.tpe+"/"+context.undetparams) val ftpe = normalize(fn1.tpe) baseType FunctionClass(numVparams) +// println(s"typeUnEtaExpanded $fn : ${fn1.tpe} (unwrapped $fun) --> normalized: $ftpe") + if (isFunctionType(ftpe) && isFullyDefined(ftpe)) ftpe else NoType } orElse { _ => NoType } @@ -4365,28 +4366,35 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case _ => tp } - def expectingFunctionMatchingFormals(formals: List[Symbol]) = - isFunctionType(pt) || samMatchesFunctionBasedOnArity(samOf(pt), formals) - - def typedEta(expr1: Tree): Tree = expr1.tpe match { - case TypeRef(_, ByNameParamClass, _) => - val expr2 = Function(List(), expr1) setPos expr1.pos - new ChangeOwnerTraverser(context.owner, expr2.symbol).traverse(expr2) - typed1(expr2, mode, pt) - case NullaryMethodType(restpe) => - val expr2 = Function(List(), expr1) setPos expr1.pos - new ChangeOwnerTraverser(context.owner, expr2.symbol).traverse(expr2) - typed1(expr2, mode, pt) - case PolyType(_, MethodType(formals, _)) => - if (expectingFunctionMatchingFormals(formals)) expr1 - else adapt(expr1, mode, checkArity(expr1)(functionTypeWildcard(formals.length))) - case MethodType(formals, _) => - if (expectingFunctionMatchingFormals(formals)) expr1 - else adapt(expr1, mode, checkArity(expr1)(functionTypeWildcard(formals.length))) + + /** Eta expand an expression like `m _`, where `m` denotes a method or a by-name argument + * + * The spec says: + * The expression `$e$ _` is well-formed if $e$ is of method type or if $e$ is a call-by-name parameter. + * (1) If $e$ is a method with parameters, `$e$ _` represents $e$ converted to a function type + * by [eta expansion](#eta-expansion). + * (2) If $e$ is a parameterless method or call-by-name parameter of type `=>$T$`, `$e$ _` represents + * the function of type `() => $T$`, which evaluates $e$ when it is applied to the empty parameterlist `()`. + */ + def typedEta(methodValue: Tree): Tree = methodValue.tpe match { + case tp@(MethodType(_, _) | PolyType(_, MethodType(_, _))) => // (1) + val formals = tp.params + if (isFunctionType(pt) || samMatchesFunctionBasedOnArity(samOf(pt), formals)) methodValue + else adapt(methodValue, mode, checkArity(methodValue)(functionTypeWildcard(formals.length))) + + case TypeRef(_, ByNameParamClass, _) | NullaryMethodType(_) => // (2) + val pos = methodValue.pos + // must create it here to change owner (normally done by typed's typedFunction) + val funSym = context.owner.newAnonymousFunctionValue(pos) + new ChangeOwnerTraverser(context.owner, funSym) traverse methodValue + + typed(Function(List(), methodValue) setSymbol funSym setPos pos, mode, pt) + case ErrorType => - expr1 + methodValue + case _ => - UnderscoreEtaError(expr1) + UnderscoreEtaError(methodValue) } def tryTypedArgs(args: List[Tree], mode: Mode): Option[List[Tree]] = { @@ -4430,7 +4438,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case Annotated(_, r) => treesInResult(r) case If(_, t, e) => treesInResult(t) ++ treesInResult(e) case Try(b, catches, _) => treesInResult(b) ++ catches - case Typed(r, Function(Nil, EmptyTree)) => treesInResult(r) + case Typed(r, Function(Nil, EmptyTree)) => treesInResult(r) // a method value case Select(qual, name) => treesInResult(qual) case Apply(fun, args) => treesInResult(fun) ++ args.flatMap(treesInResult) case TypeApply(fun, args) => treesInResult(fun) ++ args.flatMap(treesInResult) @@ -5070,11 +5078,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // because `expr` might contain nested macro calls (see SI-6673) // // Note: apparently `Function(Nil, EmptyTree)` is the secret parser marker - // which means trailing underscore. + // which means trailing underscore -- denoting a method value. See makeMethodValue in TreeBuilder. case Typed(expr, Function(Nil, EmptyTree)) => typed1(suppressMacroExpansion(expr), mode, pt) match { case macroDef if treeInfo.isMacroApplication(macroDef) => MacroEtaError(macroDef) - case exprTyped => typedEta(checkDead(exprTyped)) + case methodValue => typedEta(checkDead(methodValue)) } case Typed(expr, tpt) => val tpt1 = typedType(tpt, mode) // type the ascribed type first diff --git a/test/files/run/sammy_cbn.scala b/test/files/run/sammy_cbn.scala new file mode 100644 index 0000000000..b84b2fd8e5 --- /dev/null +++ b/test/files/run/sammy_cbn.scala @@ -0,0 +1,9 @@ +trait F0[T] { def apply(): T } + +object Test extends App { + def delay[T](v: => T) = (v _): F0[T] + + // should not fail with ClassCastException: $$Lambda$6279/897871870 cannot be cast to F0 + // (also, should not say boe!) + delay(println("boe!")) +} -- cgit v1.2.3 From 5d7d64482011f72596a634a58138e253b7fe3531 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 30 Mar 2016 22:44:17 -0700 Subject: typedFunction undoes eta-expansion regardless of expected type When recovering missing argument types for an eta-expanded method value, rework the expected type to a method type. --- .../scala/tools/nsc/typechecker/EtaExpansion.scala | 15 ------ .../scala/tools/nsc/typechecker/Typers.scala | 61 +++++++++++----------- test/files/pos/fun_undo_eta.scala | 10 ++++ 3 files changed, 41 insertions(+), 45 deletions(-) create mode 100644 test/files/pos/fun_undo_eta.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala b/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala index 7092f00bff..af8257c49b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala +++ b/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala @@ -15,23 +15,8 @@ import symtab.Flags._ * @version 1.0 */ trait EtaExpansion { self: Analyzer => - import global._ - object etaExpansion { - private def isMatch(vparam: ValDef, arg: Tree) = arg match { - case Ident(name) => vparam.name == name - case _ => false - } - - def unapply(tree: Tree): Option[(List[ValDef], Tree, List[Tree])] = tree match { - case Function(vparams, Apply(fn, args)) if (vparams corresponds args)(isMatch) => - Some((vparams, fn, args)) - case _ => - None - } - } - /**

* Expand partial function applications of type `type`. *

diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index d7c577e97c..8d3e9a0a91 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -2841,7 +2841,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
      *   - a type with a Single Abstract Method (under -Xexperimental for now).
      */
     private def typedFunction(fun: Function, mode: Mode, pt: Type): Tree = {
-      val numVparams = fun.vparams.length
+      val vparams = fun.vparams
+      val numVparams = vparams.length
       val FunctionSymbol =
         if (numVparams > definitions.MaxFunctionArity) NoSymbol
         else FunctionClass(numVparams)
@@ -2863,37 +2864,20 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
        * TODO: handle vararg sams?
        */
       val ptNorm =
-        if (samMatchesFunctionBasedOnArity(sam, fun.vparams)) samToFunctionType(pt, sam)
+        if (samMatchesFunctionBasedOnArity(sam, vparams)) samToFunctionType(pt, sam)
         else pt
       val (argpts, respt) =
         ptNorm baseType FunctionSymbol match {
           case TypeRef(_, FunctionSymbol, args :+ res) => (args, res)
-          case _                                       => (fun.vparams map (if (pt == ErrorType) (_ => ErrorType) else (_ => NoType)), WildcardType)
+          case _                                       => (vparams map (if (pt == ErrorType) (_ => ErrorType) else (_ => NoType)), WildcardType)
         }
 
-
-      // if the function is `(a1: T1, ..., aN: TN) => fun(a1,..., aN)`, where Ti are not all fully defined,
-      // type `fun` directly
-      def typeUnEtaExpanded: Type = fun match {
-        case etaExpansion(_, fn, _) =>
-          silent(_.typed(fn, mode.forFunMode, pt)) filter (_ => context.undetparams.isEmpty) map { fn1 =>
-            // if context.undetparams is not empty, the function was polymorphic,
-            // so we need the missing arguments to infer its type. See #871
-            val ftpe = normalize(fn1.tpe) baseType FunctionClass(numVparams)
-//            println(s"typeUnEtaExpanded $fn : ${fn1.tpe} (unwrapped $fun) --> normalized: $ftpe")
-
-            if (isFunctionType(ftpe) && isFullyDefined(ftpe)) ftpe
-            else NoType
-          } orElse { _ => NoType }
-        case _ => NoType
-      }
-
       if (!FunctionSymbol.exists) MaxFunctionArityError(fun)
       else if (argpts.lengthCompare(numVparams) != 0) WrongNumberOfParametersError(fun, argpts)
       else {
         val paramsMissingType = mutable.ArrayBuffer.empty[ValDef] //.sizeHint(numVparams) probably useless, since initial size is 16 and max fun arity is 22
         // first, try to define param types from expected function's arg types if needed
-        foreach2(fun.vparams, argpts) { (vparam, argpt) =>
+        foreach2(vparams, argpts) { (vparam, argpt) =>
           if (vparam.tpt isEmpty) {
             if (isFullyDefined(argpt)) vparam.tpt setType argpt
             else paramsMissingType += vparam
@@ -2902,12 +2886,29 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
           }
         }
 
-        // if we had missing param types, see if we can undo eta-expansion and recover type info
-        val expectedFunTypeBeforeEtaExpansion =
-          if (paramsMissingType.isEmpty) NoType
-          else typeUnEtaExpanded
+        // If we're typing `(a1: T1, ..., aN: TN) => m(a1,..., aN)`, where some Ti are not fully defined,
+        // type `m` directly (undoing eta-expansion of method m) to determine the argument types.
+        val ptUnrollingEtaExpansion =
+          if (paramsMissingType.nonEmpty && pt != ErrorType) fun.body match {
+            case Apply(meth, args) if (vparams corresponds args) { case (p, Ident(name)) => p.name == name case _ => false } =>
+              val methArgs = NoSymbol.newSyntheticValueParams(argpts map { case NoType => WildcardType case tp => tp })
+              // we're looking for a method (as indicated by FUNmode), so let's make sure our expected type is a MethodType
+              val methPt = MethodType(methArgs, respt)
+
+              silent(_.typed(meth, mode.forFunMode, methPt)) filter (_ => context.undetparams.isEmpty) map { methTyped =>
+                // if context.undetparams is not empty, the method was polymorphic,
+                // so we need the missing arguments to infer its type. See #871
+                val funPt = normalize(methTyped.tpe) baseType FunctionClass(numVparams)
+                // println(s"typeUnEtaExpanded $meth : ${methTyped.tpe} --> normalized: $funPt")
+
+                if (isFunctionType(funPt) && isFullyDefined(funPt)) funPt
+                else null
+              } orElse { _ => null }
+            case _ => null
+          } else null
+
 
-        if (expectedFunTypeBeforeEtaExpansion ne NoType) typedFunction(fun, mode, expectedFunTypeBeforeEtaExpansion)
+        if (ptUnrollingEtaExpansion ne null) typedFunction(fun, mode, ptUnrollingEtaExpansion)
         else {
           // we ran out of things to try, missing parameter types are an irrevocable error
           var issuedMissingParameterTypeError = false
@@ -2925,24 +2926,24 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
               // thus, its symbol, which serves as the current context.owner, is not the right owner
               // you won't know you're using the wrong owner until lambda lift crashes (unless you know better than to use the wrong owner)
               val outerTyper = newTyper(context.outer)
-              val p = fun.vparams.head
+              val p = vparams.head
               if (p.tpt.tpe == null) p.tpt setType outerTyper.typedType(p.tpt).tpe
 
               outerTyper.synthesizePartialFunction(p.name, p.pos, paramSynthetic = false, fun.body, mode, pt)
 
             case _ =>
-              val vparamSyms = fun.vparams map { vparam =>
+              val vparamSyms = vparams map { vparam =>
                 enterSym(context, vparam)
                 if (context.retyping) context.scope enter vparam.symbol
                 vparam.symbol
               }
-              val vparams = fun.vparams mapConserve typedValDef
+              val vparamsTyped = vparams mapConserve typedValDef
               val formals = vparamSyms map (_.tpe)
               val body1 = typed(fun.body, respt)
               val restpe = packedType(body1, fun.symbol).deconst.resultType
               val funtpe = phasedAppliedType(FunctionSymbol, formals :+ restpe)
 
-              treeCopy.Function(fun, vparams, body1) setType funtpe
+              treeCopy.Function(fun, vparamsTyped, body1) setType funtpe
           }
         }
       }
diff --git a/test/files/pos/fun_undo_eta.scala b/test/files/pos/fun_undo_eta.scala
new file mode 100644
index 0000000000..466b0e2629
--- /dev/null
+++ b/test/files/pos/fun_undo_eta.scala
@@ -0,0 +1,10 @@
+class Test {
+  def m(i: Int) = i
+
+  def expectWild[A](f: A) = ???
+  def expectFun[A](f: A => Int) = ???
+
+  expectWild((i => m(i))) // manual eta expansion
+  expectWild(m(_)) // have to undo eta expansion with wildcard expected type
+  expectFun(m(_)) // have to undo eta expansion with function expected type
+}
-- 
cgit v1.2.3


From 5e5ab186fe5b8cf047fd3da58da29dbc8f9fbd71 Mon Sep 17 00:00:00 2001
From: Adriaan Moors 
Date: Thu, 31 Mar 2016 11:36:02 -0700
Subject: Clarify how/when typedFunction unrolls eta-expansion

Jason points out the recursion will be okay if
type checking the function inside the eta-expansion provides
fully determined argument types, as the result type is
not relevant for this phase of typedFunction.
---
 .../scala/tools/nsc/typechecker/EtaExpansion.scala | 37 ++++++++++++----------
 .../scala/tools/nsc/typechecker/Typers.scala       | 25 ++++++++++++---
 2 files changed, 41 insertions(+), 21 deletions(-)

diff --git a/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala b/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala
index af8257c49b..97de2b6c85 100644
--- a/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala
@@ -17,22 +17,27 @@ import symtab.Flags._
 trait EtaExpansion { self: Analyzer =>
   import global._
 
-  /** 

- * Expand partial function applications of type `type`. - *

-   *  p.f(es_1)...(es_n)
-   *     ==>  {
-   *            private synthetic val eta$f   = p.f   // if p is not stable
-   *            ...
-   *            private synthetic val eta$e_i = e_i    // if e_i is not stable
-   *            ...
-   *            (ps_1 => ... => ps_m => eta$f([es_1])...([es_m])(ps_1)...(ps_m))
-   *          }
- *

- * tree is already attributed - *

- */ - def etaExpand(unit : CompilationUnit, tree: Tree, typer: Typer): Tree = { + /** Expand partial method application `p.f(es_1)...(es_n)`. + * + * We expand this to the following block, which evaluates + * the target of the application and its supplied arguments if needed (they are not stable), + * and then wraps a Function that abstracts over the missing arguments. + * + * ``` + * { + * private synthetic val eta$f = p.f // if p is not stable + * ... + * private synthetic val eta$e_i = e_i // if e_i is not stable + * ... + * (ps_1 => ... => ps_m => eta$f([es_1])...([es_m])(ps_1)...(ps_m)) + * } + * ``` + * + * This is called from instantiateToMethodType after type checking `tree`, + * and we realize we have a method type, where a function type (builtin or SAM) is expected. + * + **/ + def etaExpand(unit: CompilationUnit, tree: Tree, typer: Typer): Tree = { val tpe = tree.tpe var cnt = 0 // for NoPosition def freshName() = { diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 8d3e9a0a91..fdf7058ab1 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -244,6 +244,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper var context = context0 def context1 = context + // for use with silent type checking to when we can't have results with undetermined type params + // note that this captures the context var + val isMonoContext = (_: Any) => context.undetparams.isEmpty + def dropExistential(tp: Type): Type = tp match { case ExistentialType(tparams, tpe) => new SubstWildcardMap(tparams).apply(tp) @@ -2888,20 +2892,31 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // If we're typing `(a1: T1, ..., aN: TN) => m(a1,..., aN)`, where some Ti are not fully defined, // type `m` directly (undoing eta-expansion of method m) to determine the argument types. + // This tree is the result from one of: + // - manual eta-expansion with named arguments (x => f(x)); + // - wildcard-style eta expansion (`m(_, _,)`); + // - instantiateToMethodType adapting a tree of method type to a function type using etaExpand. + // + // Note that method values are a separate thing (`m _`): they have the idiosyncratic shape + // of `Typed(expr, Function(Nil, EmptyTree))` val ptUnrollingEtaExpansion = if (paramsMissingType.nonEmpty && pt != ErrorType) fun.body match { + // we can compare arguments and parameters by name because there cannot be a binder between + // the function's valdefs and the Apply's arguments case Apply(meth, args) if (vparams corresponds args) { case (p, Ident(name)) => p.name == name case _ => false } => + // We're looking for a method (as indicated by FUNmode in the silent typed below), + // so let's make sure our expected type is a MethodType val methArgs = NoSymbol.newSyntheticValueParams(argpts map { case NoType => WildcardType case tp => tp }) - // we're looking for a method (as indicated by FUNmode), so let's make sure our expected type is a MethodType - val methPt = MethodType(methArgs, respt) - - silent(_.typed(meth, mode.forFunMode, methPt)) filter (_ => context.undetparams.isEmpty) map { methTyped => + silent(_.typed(meth, mode.forFunMode, MethodType(methArgs, respt))) filter (isMonoContext) map { methTyped => // if context.undetparams is not empty, the method was polymorphic, // so we need the missing arguments to infer its type. See #871 val funPt = normalize(methTyped.tpe) baseType FunctionClass(numVparams) // println(s"typeUnEtaExpanded $meth : ${methTyped.tpe} --> normalized: $funPt") - if (isFunctionType(funPt) && isFullyDefined(funPt)) funPt + // If we are sure this function type provides all the necesarry info, so that we won't have + // any undetermined argument types, go ahead an recurse below (`typedFunction(fun, mode, ptUnrollingEtaExpansion)`) + // and rest assured we won't end up right back here (and keep recursing) + if (isFunctionType(funPt) && funPt.typeArgs.iterator.take(numVparams).forall(isFullyDefined)) funPt else null } orElse { _ => null } case _ => null -- cgit v1.2.3