From 27f4f2c1a72bcc5d29407fd782ef37665ddf85f6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 22 Jan 2020 22:02:40 +0100 Subject: [PATCH] Fix #8069: Disallow parameter dependent case classes This avoids the crash but intorduces an implementation restriction. I have opened #8073, which suggests a full implementation of parameter dependent case classes so that the restriction can be dropped. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 25 ++++++++++++++----- compiler/src/dotty/tools/dotc/ast/untpd.scala | 8 ++++++ .../src/dotty/tools/dotc/typer/Namer.scala | 9 +++++-- tests/neg/i8069.scala | 8 ++++++ tests/pos/i8069.scala | 12 +++++++++ 5 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 tests/neg/i8069.scala create mode 100644 tests/pos/i8069.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index c1e31140f411..c0c679c6f972 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -683,15 +683,28 @@ object desugar { val mods = constr1.mods mods.is(Private) || (!mods.is(Protected) && mods.hasPrivateWithin) } + + /** Does one of the parameter's types (in the first param clause) + * mention a preceding parameter? + */ + def isParamDependent = constrVparamss match + case vparams :: _ => + val paramNames = vparams.map(_.name).toSet + vparams.exists(_.tpt.existsSubTree { + case Ident(name: TermName) => paramNames.contains(name) + case _ => false + }) + case _ => false val companionParent = - if (constrTparams.nonEmpty || - constrVparamss.length > 1 || - mods.is(Abstract) || - restrictedAccess || - isEnumCase) anyRef + if constrTparams.nonEmpty + || constrVparamss.length > 1 + || mods.is(Abstract) + || restrictedAccess + || isParamDependent + || isEnumCase + then anyRef else - // todo: also use anyRef if constructor has a dependent method type (or rule that out)! constrVparamss.foldRight(classTypeRef)((vparams, restpe) => Function(vparams map (_.tpt), restpe)) def widenedCreatorExpr = widenDefs.foldLeft(creatorExpr)((rhs, meth) => Apply(Ident(meth.name), rhs :: Nil)) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index eab6cccc4ff4..bacff24d1a4d 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -747,4 +747,12 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { class UntypedDeepFolder[X](f: (X, Tree) => X) extends UntypedTreeAccumulator[X] { def apply(x: X, tree: Tree)(implicit ctx: Context): X = foldOver(f(x, tree), tree) } + + /** Is there a subtree of this tree that satisfies predicate `p`? */ + def (tree: Tree).existsSubTree(p: Tree => Boolean)(implicit ctx: Context): Boolean = { + val acc = new UntypedTreeAccumulator[Boolean] { + def apply(x: Boolean, t: Tree)(implicit ctx: Context) = x || p(t) || foldOver(x, t) + } + acc(false, tree) + } } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index a74f6d210b3e..fbc65fa6871c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1130,8 +1130,13 @@ class Namer { typer: Typer => index(constr) index(rest)(localCtx) - symbolOfTree(constr).ensureCompleted() - + symbolOfTree(constr).info.stripPoly match // Completes constr symbol as a side effect + case mt: MethodType if cls.is(Case) && mt.isParamDependent => + // See issue #8073 for background + ctx.error( + i"""Implementation restriction: case classes cannot have dependencies between parameters""", + cls.sourcePos) + case _ => denot.info = savedInfo } diff --git a/tests/neg/i8069.scala b/tests/neg/i8069.scala new file mode 100644 index 000000000000..a457d7785bd9 --- /dev/null +++ b/tests/neg/i8069.scala @@ -0,0 +1,8 @@ +trait A + type B + +enum Test + case Test(a: A, b: a.B) // error: Implementation restriction: case classes cannot have dependencies between parameters + +case class Test2(a: A, b: a.B) // error: Implementation restriction: case classes cannot have dependencies between parameters + diff --git a/tests/pos/i8069.scala b/tests/pos/i8069.scala new file mode 100644 index 000000000000..9454e1b0c520 --- /dev/null +++ b/tests/pos/i8069.scala @@ -0,0 +1,12 @@ +class A { + class B +} + +class C(val a: A, val b: a.B) + +@main def Test = + val a = A() + val b = a.B() + val c = C(a, b) + val d = c.b + val d1: c.a.B = d