Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SI-7296 Lifting the limit on case class arity #2305

Merged
merged 2 commits into from Mar 29, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 0 additions & 3 deletions src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
Expand Up @@ -1058,9 +1058,6 @@ trait ContextErrors {
issueSymbolTypeError(currentSym, prevSym.name + " is already defined as " + s2 + s3 + where)
}

def MaxParametersCaseClassError(tree: Tree) =
issueNormalTypeError(tree, "Implementation restriction: case classes cannot have more than " + definitions.MaxFunctionArity + " parameters.")

def MissingParameterOrValTypeError(vparam: Tree) =
issueNormalTypeError(vparam, "missing parameter type")

Expand Down
7 changes: 3 additions & 4 deletions src/compiler/scala/tools/nsc/typechecker/Namers.scala
Expand Up @@ -654,9 +654,6 @@ trait Namers extends MethodSynthesis {
tree.symbol setInfo completerOf(tree)

if (mods.isCase) {
if (primaryConstructorArity > MaxFunctionArity)
MaxParametersCaseClassError(tree)

val m = ensureCompanionObject(tree, caseModuleDef)
m.moduleClass.updateAttachment(new ClassForCaseCompanionAttachment(tree))
}
Expand Down Expand Up @@ -1373,7 +1370,9 @@ trait Namers extends MethodSynthesis {
if (!cdef.symbol.hasAbstractFlag)
namer.enterSyntheticSym(caseModuleApplyMeth(cdef))

namer.enterSyntheticSym(caseModuleUnapplyMeth(cdef))
val primaryConstructorArity = treeInfo.firstConstructorArgs(cdef.impl.body).size
if (primaryConstructorArity <= MaxTupleArity)
namer.enterSyntheticSym(caseModuleUnapplyMeth(cdef))
}

def addCopyMethod(cdef: ClassDef, namer: Namer) {
Expand Down
106 changes: 57 additions & 49 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Expand Up @@ -944,6 +944,57 @@ trait Typers extends Adaptations with Tags {
// due to wrapClassTagUnapply, but when we support parameterized extractors, it will become
// more common place)
val extractor = overloadedExtractorOfObject orElse unapplyMember(tree.tpe)
def convertToCaseConstructor(clazz: Symbol): TypeTree = {
// convert synthetic unapply of case class to case class constructor
val prefix = tree.tpe.prefix
val tree1 = TypeTree(clazz.primaryConstructor.tpe.asSeenFrom(prefix, clazz.owner))
.setOriginal(tree)

val skolems = new mutable.ListBuffer[TypeSymbol]
object variantToSkolem extends TypeMap(trackVariance = true) {
def apply(tp: Type) = mapOver(tp) match {
// !!! FIXME - skipping this when variance.isInvariant allows unsoundness, see SI-5189
case TypeRef(NoPrefix, tpSym, Nil) if !variance.isInvariant && tpSym.isTypeParameterOrSkolem && tpSym.owner.isTerm =>
// must initialize or tpSym.tpe might see random type params!!
// without this, we'll get very weird types inferred in test/scaladoc/run/SI-5933.scala
// TODO: why is that??
tpSym.initialize
val bounds = if (variance.isPositive) TypeBounds.upper(tpSym.tpe) else TypeBounds.lower(tpSym.tpe)
// origin must be the type param so we can deskolemize
val skolem = context.owner.newGADTSkolem(unit.freshTypeName("?"+tpSym.name), tpSym, bounds)
// println("mapping "+ tpSym +" to "+ skolem + " : "+ bounds +" -- pt= "+ pt +" in "+ context.owner +" at "+ context.tree )
skolems += skolem
skolem.tpe
case tp1 => tp1
}
}

// have to open up the existential and put the skolems in scope
// can't simply package up pt in an ExistentialType, because that takes us back to square one (List[_ <: T] == List[T] due to covariance)
val ptSafe = variantToSkolem(pt) // TODO: pt.skolemizeExistential(context.owner, tree) ?
val freeVars = skolems.toList

// use "tree" for the context, not context.tree: don't make another CaseDef context,
// as instantiateTypeVar's bounds would end up there
val ctorContext = context.makeNewScope(tree, context.owner)
freeVars foreach ctorContext.scope.enter
newTyper(ctorContext).infer.inferConstructorInstance(tree1, clazz.typeParams, ptSafe)

// simplify types without losing safety,
// so that we get rid of unnecessary type slack, and so that error messages don't unnecessarily refer to skolems
val extrapolate = new ExistentialExtrapolation(freeVars) extrapolate (_: Type)
val extrapolated = tree1.tpe match {
case MethodType(ctorArgs, res) => // ctorArgs are actually in a covariant position, since this is the type of the subpatterns of the pattern represented by this Apply node
ctorArgs foreach (p => p.info = extrapolate(p.info)) // no need to clone, this is OUR method type
copyMethodType(tree1.tpe, ctorArgs, extrapolate(res))
case tp => tp
}

// once the containing CaseDef has been type checked (see typedCase),
// tree1's remaining type-slack skolems will be deskolemized (to the method type parameter skolems)
tree1 setType extrapolated
}

if (extractor != NoSymbol) {
// if we did some ad-hoc overloading resolution, update the tree's symbol
// do not update the symbol if the tree's symbol's type does not define an unapply member
Expand All @@ -959,59 +1010,16 @@ trait Typers extends Adaptations with Tags {
val clazz = unapplyParameterType(unapply)

if (unapply.isCase && clazz.isCase) {
// convert synthetic unapply of case class to case class constructor
val prefix = tree.tpe.prefix
val tree1 = TypeTree(clazz.primaryConstructor.tpe.asSeenFrom(prefix, clazz.owner))
.setOriginal(tree)

val skolems = new mutable.ListBuffer[TypeSymbol]
object variantToSkolem extends TypeMap(trackVariance = true) {
def apply(tp: Type) = mapOver(tp) match {
// !!! FIXME - skipping this when variance.isInvariant allows unsoundness, see SI-5189
case TypeRef(NoPrefix, tpSym, Nil) if !variance.isInvariant && tpSym.isTypeParameterOrSkolem && tpSym.owner.isTerm =>
// must initialize or tpSym.tpe might see random type params!!
// without this, we'll get very weird types inferred in test/scaladoc/run/SI-5933.scala
// TODO: why is that??
tpSym.initialize
val bounds = if (variance.isPositive) TypeBounds.upper(tpSym.tpe) else TypeBounds.lower(tpSym.tpe)
// origin must be the type param so we can deskolemize
val skolem = context.owner.newGADTSkolem(unit.freshTypeName("?"+tpSym.name), tpSym, bounds)
// println("mapping "+ tpSym +" to "+ skolem + " : "+ bounds +" -- pt= "+ pt +" in "+ context.owner +" at "+ context.tree )
skolems += skolem
skolem.tpe
case tp1 => tp1
}
}

// have to open up the existential and put the skolems in scope
// can't simply package up pt in an ExistentialType, because that takes us back to square one (List[_ <: T] == List[T] due to covariance)
val ptSafe = variantToSkolem(pt) // TODO: pt.skolemizeExistential(context.owner, tree) ?
val freeVars = skolems.toList

// use "tree" for the context, not context.tree: don't make another CaseDef context,
// as instantiateTypeVar's bounds would end up there
val ctorContext = context.makeNewScope(tree, context.owner)
freeVars foreach ctorContext.scope.enter
newTyper(ctorContext).infer.inferConstructorInstance(tree1, clazz.typeParams, ptSafe)

// simplify types without losing safety,
// so that we get rid of unnecessary type slack, and so that error messages don't unnecessarily refer to skolems
val extrapolate = new ExistentialExtrapolation(freeVars) extrapolate (_: Type)
val extrapolated = tree1.tpe match {
case MethodType(ctorArgs, res) => // ctorArgs are actually in a covariant position, since this is the type of the subpatterns of the pattern represented by this Apply node
ctorArgs foreach (p => p.info = extrapolate(p.info)) // no need to clone, this is OUR method type
copyMethodType(tree1.tpe, ctorArgs, extrapolate(res))
case tp => tp
}

// once the containing CaseDef has been type checked (see typedCase),
// tree1's remaining type-slack skolems will be deskolemized (to the method type parameter skolems)
tree1 setType extrapolated
convertToCaseConstructor(clazz)
} else {
tree
}
} else {
CaseClassConstructorError(tree)
val clazz = tree.tpe.typeSymbol.linkedClassOfClass
if (clazz.isCase)
convertToCaseConstructor(clazz)
else
CaseClassConstructorError(tree)
}
}

Expand Down
15 changes: 10 additions & 5 deletions src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
Expand Up @@ -115,11 +115,16 @@ trait Unapplies extends ast.TreeDSL
/** The module corresponding to a case class; overrides toString to show the module's name
*/
def caseModuleDef(cdef: ClassDef): ModuleDef = {
// > MaxFunctionArity is caught in Namers, but for nice error reporting instead of
// an abrupt crash we trim the list here.
def primaries = constrParamss(cdef).head take MaxFunctionArity map (_.tpt)
def inheritFromFun = !cdef.mods.hasAbstractFlag && cdef.tparams.isEmpty && constrParamss(cdef).length == 1
def createFun = gen.scalaFunctionConstr(primaries, toIdent(cdef), abstractFun = true)
val params = constrParamss(cdef)
def inheritFromFun = !cdef.mods.hasAbstractFlag && cdef.tparams.isEmpty && (params match {
case List(ps) if ps.length <= MaxFunctionArity => true
case _ => false
})
def createFun = {
def primaries = params.head map (_.tpt)
gen.scalaFunctionConstr(primaries, toIdent(cdef), abstractFun = true)
}

def parents = if (inheritFromFun) List(createFun) else Nil
def toString = DefDef(
Modifiers(OVERRIDE | FINAL | SYNTHETIC),
Expand Down
4 changes: 0 additions & 4 deletions test/files/neg/t3631.check

This file was deleted.

File renamed without changes.
6 changes: 6 additions & 0 deletions test/files/pos/t7296.scala
@@ -0,0 +1,6 @@
object Test {
type A = Int
// Emits the implementation restriction but then proceeds to crash
// when creating the Foo.unapply.
case class Foo(a: A, b: A, c: A, d: A, e: A, f: A, g: A, h: A, i: A, j: A, k: A, l: A, m: A, n: A, o: A, p: A, q: A, r: A, s: A, t: A, u: A, v: A, w: A, x: A, y: A, Z: A)
}
2 changes: 2 additions & 0 deletions test/files/run/case-class-23.check
@@ -0,0 +1,2 @@
23
(1,23)
33 changes: 33 additions & 0 deletions test/files/run/case-class-23.scala
@@ -0,0 +1,33 @@
case class TwentyThree(
_1: Int,
_2: Int,
_3: Int,
_4: Int,
_5: Int,
_6: Int,
_7: Int,
_8: Int,
_9: Int,
_10: Int,
_11: Int,
_12: Int,
_13: Int,
_14: Int,
_15: Int,
_16: Int,
_17: Int,
_18: Int,
_19: Int,
_20: Int,
_21: Int,
_22: Int,
_23: Int
)

object Test extends App {
val x = new TwentyThree(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
println(x._23)
assert(x.copy(_1 = 1) == x)
val TwentyThree(a, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, b) = x
println((a, b))
}