diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-01-27 22:32:19 -0800 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-01-27 22:32:19 -0800 |
commit | 1f24a11d3c3d450f8d27dd34952b8912ed9ced12 (patch) | |
tree | ed74d5d6066e683e723a76ce8da9de76ea324a03 /src | |
parent | da6fc7aad705d96491e57dab1771be0cb7623206 (diff) | |
parent | 11329c34ad16e9fcdf2f2607f5a7bf39649e93a8 (diff) | |
download | scala-1f24a11d3c3d450f8d27dd34952b8912ed9ced12.tar.gz scala-1f24a11d3c3d450f8d27dd34952b8912ed9ced12.tar.bz2 scala-1f24a11d3c3d450f8d27dd34952b8912ed9ced12.zip |
Merge pull request #1857 from retronym/ticket/6443-2.10.x
SI-6443 Widen dependent param types in uncurry
Diffstat (limited to 'src')
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/UnCurry.scala | 119 |
1 files changed, 112 insertions, 7 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 9908bd689e..e05df09aaf 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -748,15 +748,22 @@ abstract class UnCurry extends InfoTransform } case dd @ DefDef(_, _, _, vparamss0, _, rhs0) => - val vparamss1 = vparamss0 match { - case _ :: Nil => vparamss0 - case _ => vparamss0.flatten :: Nil - } + val (newParamss, newRhs): (List[List[ValDef]], Tree) = + if (dependentParamTypeErasure isDependent dd) + dependentParamTypeErasure erase dd + else { + val vparamss1 = vparamss0 match { + case _ :: Nil => vparamss0 + case _ => vparamss0.flatten :: Nil + } + (vparamss1, rhs0) + } + val flatdd = copyDefDef(dd)( - vparamss = vparamss1, + vparamss = newParamss, rhs = nonLocalReturnKeys get dd.symbol match { - case Some(k) => atPos(rhs0.pos)(nonLocalReturnTry(rhs0, k, dd.symbol)) - case None => rhs0 + case Some(k) => atPos(newRhs.pos)(nonLocalReturnTry(newRhs, k, dd.symbol)) + case None => newRhs } ) addJavaVarargsForwarders(dd, flatdd) @@ -782,6 +789,104 @@ abstract class UnCurry extends InfoTransform } } + /** + * When we concatenate parameter lists, formal parameter types that were dependent + * on prior parameter values will no longer be correctly scoped. + * + * For example: + * + * {{{ + * def foo(a: A)(b: a.B): a.type = {b; b} + * // after uncurry + * def foo(a: A, b: a/* NOT IN SCOPE! */.B): a.B = {b; b} + * }}} + * + * This violates the principle that each compiler phase should produce trees that + * can be retyped (see [[scala.tools.nsc.typechecker.TreeCheckers]]), and causes + * a practical problem in `erasure`: it is not able to correctly determine if + * such a signature overrides a corresponding signature in a parent. (SI-6443). + * + * This transformation erases the dependent method types by: + * - Widening the formal parameter type to existentially abstract + * over the prior parameters (using `packSymbols`) + * - Inserting casts in the method body to cast to the original, + * precise type. + * + * For the example above, this results in: + * + * {{{ + * def foo(a: A, b: a.B forSome { val a: A }): a.B = { val b$1 = b.asInstanceOf[a.B]; b$1; b$1 } + * }}} + */ + private object dependentParamTypeErasure { + sealed abstract class ParamTransform { + def param: ValDef + } + final case class Identity(param: ValDef) extends ParamTransform + final case class Packed(param: ValDef, tempVal: ValDef) extends ParamTransform + + def isDependent(dd: DefDef): Boolean = + beforeUncurry { + val methType = dd.symbol.info + methType.isDependentMethodType && mexists(methType.paramss)(_.info exists (_.isImmediatelyDependent)) + } + + /** + * @return (newVparamss, newRhs) + */ + def erase(dd: DefDef): (List[List[ValDef]], Tree) = { + import dd.{ vparamss, rhs } + val vparamSyms = vparamss flatMap (_ map (_.symbol)) + + val paramTransforms: List[ParamTransform] = + vparamss.flatten.map { p => + val declaredType = p.symbol.info + // existentially abstract over value parameters + val packedType = typer.packSymbols(vparamSyms, declaredType) + if (packedType =:= declaredType) Identity(p) + else { + // Change the type of the param symbol + p.symbol updateInfo packedType + + // Create a new param tree + val newParam: ValDef = copyValDef(p)(tpt = TypeTree(packedType)) + + // Within the method body, we'll cast the parameter to the originally + // declared type and assign this to a synthetic val. Later, we'll patch + // the method body to refer to this, rather than the parameter. + val tempVal: ValDef = { + val tempValName = unit freshTermName (p.name + "$") + val newSym = dd.symbol.newTermSymbol(tempValName, p.pos, SYNTHETIC).setInfo(declaredType) + atPos(p.pos)(ValDef(newSym, gen.mkAttributedCast(Ident(p.symbol), declaredType))) + } + Packed(newParam, tempVal) + } + } + + val allParams = paramTransforms map (_.param) + val (packedParams, tempVals) = paramTransforms.collect { + case Packed(param, tempVal) => (param, tempVal) + }.unzip + + val rhs1 = localTyper.typedPos(rhs.pos) { + // Patch the method body to refer to the temp vals + val rhsSubstituted = rhs.substituteSymbols(packedParams map (_.symbol), tempVals map (_.symbol)) + // The new method body: { val p$1 = p.asInstanceOf[<dependent type>]; ...; <rhsSubstituted> } + Block(tempVals, rhsSubstituted) + } + + // update the type of the method after uncurry. + dd.symbol updateInfo { + val GenPolyType(tparams, tp) = dd.symbol.info + logResult("erased dependent param types for ${dd.symbol.info}") { + GenPolyType(tparams, MethodType(allParams map (_.symbol), tp.finalResultType)) + } + } + (allParams :: Nil, rhs1) + } + } + + /* Analyzes repeated params if method is annotated as `varargs`. * If the repeated params exist, it saves them into the `repeatedParams` map, * which is used later. |