From a0ee6e996e602bf5729687d7301f60b340d57061 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 28 Jan 2013 16:36:27 +0100 Subject: SI-5082 Cycle avoidance between case companions We can synthesize the case companion unapply without forcing the info of the case class, by looking at the parameters in the `ClassDef` tree, rather than at `sym.caseFieldAccessors`. Access to non-public case class fields routed through the already-renamed case accessor methods. The renamings are conveyed via a back-channel (a per-run map, `renamedCaseAccessors`), rather than via the types to give us enough slack to avoid the cycle. Some special treatment of private[this] parameters is needed to avoid a misleading error message. Fortunately, we can determine this without forcing the info of the case class, by inspecting the parameter accessor trees. This change may allow us to resurrect the case class ProductN parentage, which was trialled but abandoned in the lead up to 2.10.0. --- .../tools/nsc/typechecker/SyntheticMethods.scala | 6 ++++ .../scala/tools/nsc/typechecker/Unapplies.scala | 33 ++++++++++++++++++---- test/files/pos/t5082.scala | 14 +++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 test/files/pos/t5082.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala index 39f6f764e7..242eb9c9fe 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala @@ -50,6 +50,10 @@ trait SyntheticMethods extends ast.TreeDSL { else if (clazz.isDerivedValueClass) valueSymbols else Nil } + private lazy val renamedCaseAccessors = perRunCaches.newMap[Symbol, mutable.Map[TermName, TermName]]() + /** Does not force the info of `caseclazz` */ + final def caseAccessorName(caseclazz: Symbol, paramName: TermName) = + (renamedCaseAccessors get caseclazz).fold(paramName)(_(paramName)) /** Add the synthetic methods to case classes. */ @@ -384,6 +388,8 @@ trait SyntheticMethods extends ast.TreeDSL { // TODO: shouldn't the next line be: `original resetFlag CASEACCESSOR`? ddef.symbol resetFlag CASEACCESSOR lb += logResult("case accessor new")(newAcc) + val renamedInClassMap = renamedCaseAccessors.getOrElseUpdate(clazz, mutable.Map() withDefault(x => x)) + renamedInClassMap(original.name.toTermName) = newAcc.symbol.name.toTermName } (lb ++= templ.body ++= synthesize()).toList diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index 8f607e9f67..e143015bbb 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -93,12 +93,33 @@ trait Unapplies extends ast.TreeDSL * @param param The name of the parameter of the unapply method, assumed to be of type C[Ts] * @param caseclazz The case class C[Ts] */ - private def caseClassUnapplyReturnValue(param: Name, caseclazz: Symbol) = { - def caseFieldAccessorValue(selector: Symbol): Tree = Ident(param) DOT selector + private def caseClassUnapplyReturnValue(param: Name, caseclazz: ClassDef) = { + def caseFieldAccessorValue(selector: ValDef): Tree = { + val accessorName = selector.name + val privateLocalParamAccessor = caseclazz.impl.body.collectFirst { + case dd: ValOrDefDef if dd.name == accessorName && dd.mods.isPrivateLocal => dd.symbol + } + privateLocalParamAccessor match { + case None => + // Selecting by name seems to be the most straight forward way here to + // avoid forcing the symbol of the case class in order to list the accessors. + val maybeRenamedAccessorName = caseAccessorName(caseclazz.symbol, accessorName) + Ident(param) DOT maybeRenamedAccessorName + case Some(sym) => + // But, that gives a misleading error message in neg/t1422.scala, where a case + // class has an illegal private[this] parameter. We can detect this by checking + // the modifiers on the param accessors. + // + // We just generate a call to that param accessor here, which gives us an inaccessible + // symbol error, as before. + Ident(param) DOT sym + } + } - caseclazz.caseFieldAccessors match { - case Nil => TRUE - case xs => SOME(xs map caseFieldAccessorValue: _*) + // Working with trees, rather than symbols, to avoid cycles like SI-5082 + constrParamss(caseclazz).take(1).flatten match { + case Nil => TRUE + case xs => SOME(xs map caseFieldAccessorValue: _*) } } @@ -157,7 +178,7 @@ trait Unapplies extends ast.TreeDSL } val cparams = List(ValDef(Modifiers(PARAM | SYNTHETIC), unapplyParamName, classType(cdef, tparams), EmptyTree)) val ifNull = if (constrParamss(cdef).head.isEmpty) FALSE else REF(NoneModule) - val body = nullSafe({ case Ident(x) => caseClassUnapplyReturnValue(x, cdef.symbol) }, ifNull)(Ident(unapplyParamName)) + val body = nullSafe({ case Ident(x) => caseClassUnapplyReturnValue(x, cdef) }, ifNull)(Ident(unapplyParamName)) atPos(cdef.pos.focus)( DefDef(caseMods, method, tparams, List(cparams), TypeTree(), body) diff --git a/test/files/pos/t5082.scala b/test/files/pos/t5082.scala new file mode 100644 index 0000000000..63eeda38ba --- /dev/null +++ b/test/files/pos/t5082.scala @@ -0,0 +1,14 @@ +trait Something[T] +object Test { class A } +case class Test() extends Something[Test.A] + +object User { + val Test() = Test() +} + +object Wrap { + trait Something[T] + object Test { class A } + case class Test(a: Int, b: Int)(c: String) extends Something[Test.A] + val Test(x, y) = Test(1, 2)(""); (x + y).toString +} -- cgit v1.2.3