Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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.
  • Loading branch information...
commit a0ee6e996e602bf5729687d7301f60b340d57061 1 parent 8d25d05
@retronym retronym authored
View
6 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
View
33 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)
View
14 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
+}
Please sign in to comment.
Something went wrong with that request. Please try again.