Skip to content

Commit

Permalink
Allow case classes with up to 254 parameters (#16501)
Browse files Browse the repository at this point in the history
Fixes #16500
  • Loading branch information
dwijnand authored Dec 12, 2022
2 parents a4a1cbc + a48ba99 commit 7b7c055
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 25 deletions.
11 changes: 11 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Decorators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,17 @@ object Decorators {
/** Union on lists seen as sets */
def setUnion (ys: List[T]): List[T] = xs ::: ys.filterNot(xs contains _)

/** Reduce left with `op` as long as list `xs` is not longer than `seqLimit`.
* Otherwise, split list in two half, reduce each, and combine with `op`.
*/
def reduceBalanced(op: (T, T) => T, seqLimit: Int = 100): T =
val len = xs.length
if len > seqLimit then
val (leading, trailing) = xs.splitAt(len / 2)
op(leading.reduceBalanced(op, seqLimit), trailing.reduceBalanced(op, seqLimit))
else
xs.reduceLeft(op)

extension [T, U](xss: List[List[T]])
def nestedMap(f: T => U): List[List[U]] = xss match
case xs :: xss1 => xs.map(f) :: xss1.nestedMap(f)
Expand Down
28 changes: 15 additions & 13 deletions compiler/src/dotty/tools/dotc/core/TypeErrors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -96,21 +96,23 @@ end RecursionOverflow
*/
// Beware: Since this object is only used when handling a StackOverflow, this code
// cannot consume significant amounts of stack.
object handleRecursive {
object handleRecursive:
inline def underlyingStackOverflowOrNull(exc: Throwable): Throwable | Null =
var e: Throwable | Null = exc
while e != null && !e.isInstanceOf[StackOverflowError] do e = e.getCause
e

def apply(op: String, details: => String, exc: Throwable, weight: Int = 1)(using Context): Nothing =
if (ctx.settings.YnoDecodeStacktraces.value)
if ctx.settings.YnoDecodeStacktraces.value then
throw exc
else
exc match {
case _: RecursionOverflow =>
throw new RecursionOverflow(op, details, exc, weight)
case _ =>
var e: Throwable | Null = exc
while (e != null && !e.isInstanceOf[StackOverflowError]) e = e.getCause
if (e != null) throw new RecursionOverflow(op, details, e, weight)
else throw exc
}
}
else exc match
case _: RecursionOverflow =>
throw new RecursionOverflow(op, details, exc, weight)
case _ =>
val so = underlyingStackOverflowOrNull(exc)
if so != null then throw new RecursionOverflow(op, details, so, weight)
else throw exc
end handleRecursive

/**
* This TypeError signals that completing denot encountered a cycle: it asked for denot.info (or similar),
Expand Down
43 changes: 32 additions & 11 deletions compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ import collection.mutable
import reporting.{Profile, NoProfile}
import dotty.tools.tasty.TastyFormat.ASTsSection

object TreePickler:
class StackSizeExceeded(val mdef: tpd.MemberDef) extends Exception

class TreePickler(pickler: TastyPickler) {
val buf: TreeBuffer = new TreeBuffer
pickler.newSection(ASTsSection, buf)
import buf._
import pickler.nameBuffer.nameIndex
import tpd._
import TreePickler.*

private val symRefs = Symbols.MutableSymbolMap[Addr](256)
private val forwardSymRefs = Symbols.MutableSymbolMap[List[Addr]]()
Expand All @@ -53,7 +56,7 @@ class TreePickler(pickler: TastyPickler) {
def docString(tree: untpd.MemberDef): Option[Comment] =
Option(docStrings.lookup(tree))

private def withLength(op: => Unit) = {
private inline def withLength(inline op: Unit) = {
val lengthAddr = reserveRef(relative = true)
op
fillRef(lengthAddr, currentAddr, relative = true)
Expand Down Expand Up @@ -328,16 +331,24 @@ class TreePickler(pickler: TastyPickler) {
registerDef(sym)
writeByte(tag)
val addr = currentAddr
withLength {
pickleName(sym.name)
pickleParams
tpt match {
case _: Template | _: Hole => pickleTree(tpt)
case _ if tpt.isType => pickleTpt(tpt)
try
withLength {
pickleName(sym.name)
pickleParams
tpt match {
case _: Template | _: Hole => pickleTree(tpt)
case _ if tpt.isType => pickleTpt(tpt)
}
pickleTreeUnlessEmpty(rhs)
pickleModifiers(sym, mdef)
}
pickleTreeUnlessEmpty(rhs)
pickleModifiers(sym, mdef)
}
catch
case ex: Throwable =>
if !ctx.settings.YnoDecodeStacktraces.value
&& handleRecursive.underlyingStackOverflowOrNull(ex) != null then
throw StackSizeExceeded(mdef)
else
throw ex
if sym.is(Method) && sym.owner.isClass then
profile.recordMethodSize(sym, currentAddr.index - addr.index, mdef.span)
for
Expand Down Expand Up @@ -784,7 +795,17 @@ class TreePickler(pickler: TastyPickler) {

def pickle(trees: List[Tree])(using Context): Unit = {
profile = Profile.current
trees.foreach(tree => if (!tree.isEmpty) pickleTree(tree))
for tree <- trees do
try
if !tree.isEmpty then pickleTree(tree)
catch case ex: StackSizeExceeded =>
report.error(
em"""Recursion limit exceeded while pickling ${ex.mdef}
|in ${ex.mdef.symbol.showLocated}.
|You could try to increase the stacksize using the -Xss JVM option.
|For the unprocessed stack trace, compile with -Yno-decode-stacktraces.""",
ex.mdef.srcPos)

def missing = forwardSymRefs.keysIterator
.map(sym => i"${sym.showLocated} (line ${sym.srcPos.line}) #${sym.id}")
.toList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
val comparisons = sortedAccessors.map { accessor =>
This(clazz).withSpan(ctx.owner.span.focus).select(accessor).equal(ref(thatAsClazz).select(accessor)) }
var rhs = // this.x == this$0.x && this.y == x$0.y && that.canEqual(this)
if comparisons.isEmpty then Literal(Constant(true)) else comparisons.reduceLeft(_ and _)
if comparisons.isEmpty then Literal(Constant(true)) else comparisons.reduceBalanced(_ and _)
val canEqualMeth = existingDef(defn.Product_canEqual, clazz)
if !clazz.is(Final) || canEqualMeth.exists && !canEqualMeth.is(Synthetic) then
rhs = rhs.and(
Expand Down
54 changes: 54 additions & 0 deletions tests/pos/i16500.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
type I = Int
case class BigClass254(
udef00:I, udef01:I, udef02:I, udef03:I, udef04:I, udef05:I, udef06:I, udef07:I,
udef08:I, udef09:I, udef0a:I, udef0b:I, udef0c:I, udef0d:I, udef0e:I, udef0f:I,
udef10:I, udef11:I, udef12:I, udef13:I, udef14:I, udef15:I, udef16:I, udef17:I,
udef18:I, udef19:I, udef1a:I, udef1b:I, udef1c:I, udef1d:I, udef1e:I, udef1f:I,
udef20:I, udef21:I, udef22:I, udef23:I, udef24:I, udef25:I, udef26:I, udef27:I,
udef28:I, udef29:I, udef2a:I, udef2b:I, udef2c:I, udef2d:I, udef2e:I, udef2f:I,
udef30:I, udef31:I, udef32:I, udef33:I, udef34:I, udef35:I, udef36:I, udef37:I,
udef38:I, udef39:I, udef3a:I, udef3b:I, udef3c:I, udef3d:I, udef3e:I, udef3f:I,
udef40:I, udef41:I, udef42:I, udef43:I, udef44:I, udef45:I, udef46:I, udef47:I,
udef48:I, udef49:I, udef4a:I, udef4b:I, udef4c:I, udef4d:I, udef4e:I, udef4f:I,
udef50:I, udef51:I, udef52:I, udef53:I, udef54:I, udef55:I, udef56:I, udef57:I,
udef58:I, udef59:I, udef5a:I, udef5b:I, udef5c:I, udef5d:I, udef5e:I, udef5f:I,
udef60:I, udef61:I, udef62:I, udef63:I, udef64:I, udef65:I, udef66:I, udef67:I,
udef68:I, udef69:I, udef6a:I, udef6b:I, udef6c:I, udef6d:I, udef6e:I, udef6f:I,
udef70:I, udef71:I, udef72:I, udef73:I, udef74:I, udef75:I, udef76:I, udef77:I,
udef78:I, udef79:I, udef7a:I, udef7b:I, udef7c:I, udef7d:I, udef7e:I, udef7f:I,
udef80:I, udef81:I, udef82:I, udef83:I, udef84:I, udef85:I, udef86:I, udef87:I,
udef88:I, udef89:I, udef8a:I, udef8b:I, udef8c:I, udef8d:I, udef8e:I, udef8f:I,
udef90:I, udef91:I, udef92:I, udef93:I, udef94:I, udef95:I, udef96:I, udef97:I,
udef98:I, udef99:I, udef9a:I, udef9b:I, udef9c:I, udef9d:I, udef9e:I, udef9f:I,
udefa0:I, udefa1:I, udefa2:I, udefa3:I, udefa4:I, udefa5:I, udefa6:I, udefa7:I,
udefa8:I, udefa9:I, udefaa:I, udefab:I, udefac:I, udefad:I, udefae:I, udefaf:I,
udefb0:I, udefb1:I, udefb2:I, udefb3:I, udefb4:I, udefb5:I, udefb6:I, udefb7:I,
udefb8:I, udefb9:I, udefba:I, udefbb:I, udefbc:I, udefbd:I, udefbe:I, udefbf:I,
udefc0:I, udefc1:I, udefc2:I, udefc3:I, udefc4:I, udefc5:I, udefc6:I, udefc7:I,
udefc8:I, udefc9:I, udefca:I, udefcb:I, udefcc:I, udefcd:I, udefce:I, udefcf:I,
udefd0:I, udefd1:I, udefd2:I, udefd3:I, udefd4:I, udefd5:I, udefd6:I, udefd7:I,
udefd8:I, udefd9:I, udefda:I, udefdb:I, udefdc:I, udefdd:I, udefde:I, udefdf:I,
udefe0:I, udefe1:I, udefe2:I, udefe3:I, udefe4:I, udefe5:I, udefe6:I, udefe7:I,
udefe8:I, udefe9:I, udefea:I, udefeb:I, udefec:I, udefed:I, udefee:I, udefef:I,
udeff0:I, udeff1:I, udeff2:I, udeff3:I, udeff4:I, udeff5:I, udeff6:I, udeff7:I,
udeff8:I, udeff9:I, udeffa:I, udeffb:I, udeffc:I, udeffd:I,
)
@main def Test =
BigClass254(
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
)

0 comments on commit 7b7c055

Please sign in to comment.