diff options
author | Grzegorz Kossakowski <grzegorz.kossakowski@gmail.com> | 2013-10-14 07:16:34 -0700 |
---|---|---|
committer | Grzegorz Kossakowski <grzegorz.kossakowski@gmail.com> | 2013-10-14 07:16:34 -0700 |
commit | 686fb483e721ba2bef77e185afd5bf97a134d890 (patch) | |
tree | 75200d850cf9b39ec979e8eea71e1a2a6dbb7946 /src/compiler | |
parent | d068b163d36b3fbffe39f87e3dc152a14c3ca07e (diff) | |
parent | b126e5c31b2bc57df5d0d5cf3508babe1dd7a759 (diff) | |
download | scala-686fb483e721ba2bef77e185afd5bf97a134d890.tar.gz scala-686fb483e721ba2bef77e185afd5bf97a134d890.tar.bz2 scala-686fb483e721ba2bef77e185afd5bf97a134d890.zip |
Merge pull request #3037 from gkossakowski/fix-merge-3018v2.11.0-M6
[resubmit] Experimental Single Abstract Method support (sammy meets world)
Diffstat (limited to 'src/compiler')
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/UnCurry.scala | 5 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Typers.scala | 211 |
2 files changed, 199 insertions, 17 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index d97f62d5d6..ccf2266540 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -85,9 +85,6 @@ abstract class UnCurry extends InfoTransform transformFunction(result) } - private lazy val serialVersionUIDAnnotation = - AnnotationInfo(SerialVersionUIDAttr.tpe, List(Literal(Constant(0))), List()) - // 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 @@ -220,7 +217,7 @@ abstract class UnCurry extends InfoTransform case fun1 if fun1 ne fun => fun1 case _ => val parents = addSerializable(abstractFunctionForFunctionType(fun.tpe)) - val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation serialVersionUIDAnnotation + val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation anonClass setInfo ClassInfoType(parents, newScope, anonClass) val targs = fun.tpe.typeArgs diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 7bf342f475..de684e69ad 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2503,7 +2503,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper assert(pt.typeSymbol == PartialFunctionClass, s"PartialFunction synthesis for match in $tree requires PartialFunction expected type, but got $pt.") val targs = pt.dealiasWiden.typeArgs - // if targs.head isn't fully defined, we can translate --> error + // if targs.head isn't fully defined, we can't translate --> error targs match { case argTp :: _ if isFullyDefined(argTp) => // ok case _ => // uh-oh @@ -2517,9 +2517,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // targs must conform to Any for us to synthesize an applyOrElse (fallback to apply otherwise -- typically for @cps annotated targs) val targsValidParams = targs forall (_ <:< AnyTpe) - val anonClass = (context.owner - newAnonymousFunctionClass tree.pos - addAnnotation AnnotationInfo(SerialVersionUIDAttr.tpe, List(Literal(Constant(0))), List())) + val anonClass = context.owner newAnonymousFunctionClass tree.pos addAnnotation SerialVersionUIDAnnotation import CODE._ @@ -2712,17 +2710,197 @@ 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... + * + * 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. + */ + 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) + + // 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 + 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 + + // we're trying to fully define the type arguments for this type constructor + val samTyCon = samClassTp.typeSymbol.typeConstructor + + // the unknowns + val tparams = samClassTp.typeSymbol.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 + }) + + // 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) + + // 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) + + // solve constraints tracked by tvars + val targs = solvedTypes(tvars, tparams, tparams map varianceInType(sam.info), upper = false, lubDepth(sam.info :: Nil)) + + debuglog(s"sam infer: $samClassTp --> ${appliedType(samTyCon, targs)} by $actualSamType <:< $expectedSamType --> $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 + } + + // `final override def ${sam.name}($p1: $T1, ..., $pN: $TN): $resPt = ${sam.name}\$body'($p1, ..., $pN)` + val samDef = + DefDef(Modifiers(FINAL | OVERRIDE | SYNTHETIC), + sam.name.toTermName, + Nil, + List(fun.vparams), + TypeTree(samBodyDef.tpt.tpe) setPos sampos.focus, + Apply(Ident(bodyName), fun.vparams map (p => Ident(p.name))) + ) + + val serializableParentAddendum = + if (typeIsSubTypeOfSerializable(samClassTp)) Nil + else List(TypeTree(SerializableTpe)) + + val classDef = + ClassDef(Modifiers(FINAL), tpnme.ANON_FUN_NAME, tparams = Nil, + gen.mkTemplate( + parents = TypeTree(samClassTpFullyDefined) :: serializableParentAddendum, + self = emptyValDef, + constrMods = NoMods, + vparamss = ListOfNil, + body = List(samDef), + superPos = sampos.focus + ) + ) + + // 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) + ) + } + + classDef.symbol addAnnotation SerialVersionUIDAnnotation + block + } + + /** Type check a function literal. + * + * Based on the expected type pt, potentially synthesize an instance of + * - PartialFunction, + * - 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 - if (numVparams > definitions.MaxFunctionArity) - return MaxFunctionArityError(fun) + val FunctionSymbol = + if (numVparams > definitions.MaxFunctionArity) NoSymbol + else FunctionClass(numVparams) - val FunctionSymbol = FunctionClass(numVparams) - val (argpts, respt) = pt baseType FunctionSymbol match { - case TypeRef(_, FunctionSymbol, args :+ res) => (args, res) - case _ => (fun.vparams map (_ => if (pt == ErrorType) ErrorType else NoType), WildcardType) - } - if (argpts.lengthCompare(numVparams) != 0) + /* 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 (!settings.Xexperimental || pt.typeSymbol == FunctionSymbol) NoSymbol + else samOf(pt) + + /* The SAM case comes first so that this works: + * abstract class MyFun extends (Int => Int) + * (a => a): MyFun + * + * 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 (argpts, respt) = + if (samViable) { + val samInfo = pt memberInfo sam + (samInfo.paramTypes, samInfo.resultType) + } else { + pt baseType FunctionSymbol match { + case TypeRef(_, FunctionSymbol, args :+ res) => (args, res) + 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) else { foreach2(fun.vparams, argpts) { (vparam, argpt) => @@ -2733,7 +2911,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper 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, + // 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) @@ -2761,6 +2939,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (p.tpt.tpe == null) p.tpt setType outerTyper.typedType(p.tpt).tpe outerTyper.synthesizePartialFunction(p.name, p.pos, fun.body, mode, pt) + + // 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) + + // regular Function case _ => val vparamSyms = fun.vparams map { vparam => enterSym(context, vparam) |