diff options
author | Lukas Rytz <lukas.rytz@gmail.com> | 2016-07-05 14:22:00 +0200 |
---|---|---|
committer | Adriaan Moors <adriaan@lightbend.com> | 2017-01-09 15:34:26 -0800 |
commit | de361dfe4fab67a01750bccf960788ef10321e4d (patch) | |
tree | 7a53bd46e157055c4f99fca884643d1b41f9a48a /src/compiler/scala | |
parent | f841dab6c2eb8d9002c286d57ea80df637a0d4bc (diff) | |
download | scala-de361dfe4fab67a01750bccf960788ef10321e4d.tar.gz scala-de361dfe4fab67a01750bccf960788ef10321e4d.tar.bz2 scala-de361dfe4fab67a01750bccf960788ef10321e4d.zip |
SI-8786 fix generic signature for @varargs forwarder methods
When generating a varargs forwarder for
def foo[T](a: T*)
the parameter type of the forwarder needs to be Array[Object]. If we
generate Array[T] in UnCurry, that would be erased to plain Object, and
the method would not be a valid varargs.
Unfortunately, setting the parameter type to Array[Object] lead to
an invalid generic signature - the generic signature should reflect the
real signature.
This change adds an attachment to the parameter symbol in the varargs
forwarder method and special-cases signature generation.
Also cleans up the code to produce the varargs forwarder. For example,
type parameter and parameter symbols in the forwarder's method type were
not clones, but the same symbols from the original method were re-used.
Backported from 0d2760dce189cdcb363e54868381175af4b2646f,
with a small tweak (checkVarargs) to make the test work on Java 6,
as well as later versions.
Diffstat (limited to 'src/compiler/scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/Erasure.scala | 14 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/UnCurry.scala | 118 |
2 files changed, 80 insertions, 52 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index a04625c9c5..a7cf6d9e8d 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -336,7 +336,18 @@ abstract class Erasure extends AddInterfaces case MethodType(params, restpe) => val buf = new StringBuffer("(") - params foreach (p => buf append jsig(p.tpe)) + params foreach (p => { + val tp = p.attachments.get[TypeParamVarargsAttachment] match { + case Some(att) => + // For @varargs forwarders, a T* parameter has type Array[Object] in the forwarder + // instead of Array[T], as the latter would erase to Object (instead of Array[Object]). + // To make the generic signature correct ("[T", not "[Object"), an attachment on the + // parameter symbol stores the type T that was replaced by Object. + buf.append("["); att.typeParamRef + case _ => p.tpe + } + buf append jsig(tp) + }) buf append ")" buf append (if (restpe.typeSymbol == UnitClass || sym0.isConstructor) VOID_TAG.toString else jsig(restpe)) buf.toString @@ -1178,4 +1189,5 @@ abstract class Erasure extends AddInterfaces } private class TypeRefAttachment(val tpe: TypeRef) + class TypeParamVarargsAttachment(val typeParamRef: Type) } diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 870c35338c..17f4471533 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -763,72 +763,88 @@ abstract class UnCurry extends InfoTransform if (!dd.symbol.hasAnnotation(VarargsClass) || !enteringUncurry(mexists(dd.symbol.paramss)(sym => definitions.isRepeatedParamType(sym.tpe)))) return flatdd - def toArrayType(tp: Type): Type = { - val arg = elementType(SeqClass, tp) - // to prevent generation of an `Object` parameter from `Array[T]` parameter later - // as this would crash the Java compiler which expects an `Object[]` array for varargs - // e.g. def foo[T](a: Int, b: T*) - // becomes def foo[T](a: Int, b: Array[Object]) - // instead of def foo[T](a: Int, b: Array[T]) ===> def foo[T](a: Int, b: Object) - arrayType( - if (arg.typeSymbol.isTypeParameterOrSkolem) ObjectTpe - else arg - ) - } + val forwSym = currentClass.newMethod(dd.name.toTermName, dd.pos, VARARGS | SYNTHETIC | flatdd.symbol.flags) - val theTyper = typer.atOwner(dd, currentClass) - val flatparams = flatdd.symbol.paramss.head val isRepeated = enteringUncurry(dd.symbol.info.paramss.flatten.map(sym => definitions.isRepeatedParamType(sym.tpe))) - // create the type - val forwformals = map2(flatparams, isRepeated) { - case (p, true) => toArrayType(p.tpe) - case (p, false)=> p.tpe - } - val forwresult = dd.symbol.tpe_*.finalResultType - val forwformsyms = map2(forwformals, flatparams)((tp, oldparam) => - currentClass.newValueParameter(oldparam.name.toTermName, oldparam.pos).setInfo(tp) - ) - def mono = MethodType(forwformsyms, forwresult) - val forwtype = dd.symbol.tpe match { - case MethodType(_, _) => mono - case PolyType(tps, _) => PolyType(tps, mono) - } + val oldPs = flatdd.symbol.paramss.head + + // see comment in method toArrayType below + val arrayTypesMappedToObject = mutable.Map.empty[Symbol, Type] - // create the symbol - val forwsym = currentClass.newMethod(dd.name.toTermName, dd.pos, VARARGS | SYNTHETIC | flatdd.symbol.flags) setInfo forwtype - def forwParams = forwsym.info.paramss.flatten - - // create the tree - val forwtree = theTyper.typedPos(dd.pos) { - val locals = map3(forwParams, flatparams, isRepeated) { - case (_, fp, false) => null - case (argsym, fp, true) => - Block(Nil, - gen.mkCast( - gen.mkWrapArray(Ident(argsym), elementType(ArrayClass, argsym.tpe)), - seqType(elementType(SeqClass, fp.tpe)) - ) - ) + val forwTpe = { + val (oldTps, tps) = dd.symbol.tpe match { + case PolyType(oldTps, _) => + val newTps = oldTps.map(_.cloneSymbol(forwSym)) + (oldTps, newTps) + + case _ => (Nil, Nil) } - val seqargs = map2(locals, forwParams) { - case (null, argsym) => Ident(argsym) - case (l, _) => l + + def toArrayType(tp: Type, newParam: Symbol): Type = { + val arg = elementType(SeqClass, tp) + val elem = if (arg.typeSymbol.isTypeParameterOrSkolem && !(arg <:< AnyRefTpe)) { + // To prevent generation of an `Object` parameter from `Array[T]` parameter later + // as this would crash the Java compiler which expects an `Object[]` array for varargs + // e.g. def foo[T](a: Int, b: T*) + // becomes def foo[T](a: Int, b: Array[Object]) + // instead of def foo[T](a: Int, b: Array[T]) ===> def foo[T](a: Int, b: Object) + // + // In order for the forwarder method to type check we need to insert a cast: + // def foo'[T'](a: Int, b: Array[Object]) = foo[T'](a, wrapRefArray(b).asInstanceOf[Seq[T']]) + // The target element type for that cast (T') is stored in the `arrayTypesMappedToObject` map. + val originalArg = arg.substSym(oldTps, tps) + arrayTypesMappedToObject(newParam) = originalArg + // Store the type parameter that was replaced by Object to emit the correct generic signature + newParam.updateAttachment(new erasure.TypeParamVarargsAttachment(originalArg)) + ObjectTpe + } else + arg + arrayType(elem) } - val end = if (forwsym.isConstructor) List(UNIT) else Nil - DefDef(forwsym, BLOCK(Apply(gen.mkAttributedRef(flatdd.symbol), seqargs) :: end : _*)) + val ps = map2(oldPs, isRepeated)((oldParam, isRep) => { + val newParam = oldParam.cloneSymbol(forwSym) + val tp = if (isRep) toArrayType(oldParam.tpe, newParam) else oldParam.tpe + newParam.setInfo(tp) + }) + + val resTp = dd.symbol.tpe_*.finalResultType.substSym(oldPs, ps) + val mt = MethodType(ps, resTp) + val r = if (tps.isEmpty) mt else PolyType(tps, mt) + r.substSym(oldTps, tps) + } + + forwSym.setInfo(forwTpe) + val newPs = forwTpe.params + + val theTyper = typer.atOwner(dd, currentClass) + val forwTree = theTyper.typedPos(dd.pos) { + val seqArgs = map3(newPs, oldPs, isRepeated)((param, oldParam, isRep) => { + if (!isRep) Ident(param) + else { + val parTp = elementType(ArrayClass, param.tpe) + val wrap = gen.mkWrapArray(Ident(param), parTp) + arrayTypesMappedToObject.get(param) match { + case Some(tp) => gen.mkCast(wrap, seqType(tp)) + case _ => wrap + } + } + }) + + val forwCall = Apply(gen.mkAttributedRef(flatdd.symbol), seqArgs) + DefDef(forwSym, if (forwSym.isConstructor) Block(List(forwCall), UNIT) else forwCall) } // check if the method with that name and those arguments already exists in the template - currentClass.info.member(forwsym.name).alternatives.find(s => s != forwsym && s.tpe.matches(forwsym.tpe)) match { + currentClass.info.member(forwSym.name).alternatives.find(s => s != forwSym && s.tpe.matches(forwSym.tpe)) match { case Some(s) => reporter.error(dd.symbol.pos, "A method with a varargs annotation produces a forwarder method with the same signature " + s.tpe + " as an existing method.") case None => // enter symbol into scope - currentClass.info.decls enter forwsym - addNewMember(forwtree) + currentClass.info.decls enter forwSym + addNewMember(forwTree) } flatdd |