diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index d311ac834450..8a86b26cdf1e 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -503,7 +503,7 @@ object desugar { companionDefs(anyRef, companionMeths) else if (isValueClass) { constr0.vparamss match { - case List(_ :: Nil) => companionDefs(anyRef, Nil) + case (_ :: Nil) :: _ => companionDefs(anyRef, Nil) case _ => Nil // error will be emitted in typer } } diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 3b398d23661c..c0da98fa8946 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1620,9 +1620,9 @@ object messages { |""" } - case class ValueClassNeedsExactlyOneValParam(valueClass: Symbol)(implicit ctx: Context) + case class ValueClassNeedsOneValParam(valueClass: Symbol)(implicit ctx: Context) extends Message(ValueClassNeedsExactlyOneValParamID) { - val msg = hl"""value class needs to have exactly one ${"val"} parameter""" + val msg = hl"""value class needs one ${"val"} parameter""" val kind = "Syntax" val explanation = "" } diff --git a/compiler/src/dotty/tools/dotc/transform/Constructors.scala b/compiler/src/dotty/tools/dotc/transform/Constructors.scala index 4698930691e2..969f4f548a07 100644 --- a/compiler/src/dotty/tools/dotc/transform/Constructors.scala +++ b/compiler/src/dotty/tools/dotc/transform/Constructors.scala @@ -129,7 +129,7 @@ class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { th // Produce aligned accessors and constructor parameters. We have to adjust // for any outer parameters, which are last in the sequence of original // parameter accessors but come first in the constructor parameter list. - val accessors = cls.paramAccessors.filterNot(_.isSetter) + val accessors = cls.paramAccessors.filterNot(x => x.isSetter || x.info.resultType.classSymbol == defn.ErasedPhantomClass) val vparamsWithOuterLast = vparams match { case vparam :: rest if vparam.name == nme.OUTER => rest ::: vparam :: Nil case _ => vparams diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 5837b2b6904a..9f3b603ac66f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -414,7 +414,8 @@ object Erasure { } } - if (tree.symbol eq defn.Phantom_assume) PhantomErasure.erasedAssume + if ((origSym eq defn.Phantom_assume) || (origSym.is(Flags.ParamAccessor) && wasPhantom(pt))) + PhantomErasure.erasedAssume else recur(typed(tree.qualifier, AnySelectionProto)) } diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala index c8bef28dcd66..20a8a08b40d3 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala @@ -159,8 +159,8 @@ class SyntheticMethods(thisTransformer: DenotTransformer) { val thatAsClazz = ctx.newSymbol(ctx.owner, nme.x_0, Synthetic, clazzType, coord = ctx.owner.pos) // x$0 def wildcardAscription(tp: Type) = Typed(Underscore(tp), TypeTree(tp)) val pattern = Bind(thatAsClazz, wildcardAscription(clazzType)) // x$0 @ (_: C) - val comparisons = accessors map (accessor => - This(clazz).select(accessor).select(defn.Any_==).appliedTo(ref(thatAsClazz).select(accessor))) + val comparisons = accessors collect { case accessor if !accessor.info.isPhantom => + This(clazz).select(accessor).select(defn.Any_==).appliedTo(ref(thatAsClazz).select(accessor)) } val rhs = // this.x == this$0.x && this.y == x$0.y if (comparisons.isEmpty) Literal(Constant(true)) else comparisons.reduceLeft(_ and _) val matchingCase = CaseDef(pattern, EmptyTree, rhs) // case x$0 @ (_: C) => this.x == this$0.x && this.y == x$0.y @@ -186,7 +186,8 @@ class SyntheticMethods(thisTransformer: DenotTransformer) { * ``` */ def valueHashCodeBody(implicit ctx: Context): Tree = { - assert(accessors.length == 1) + assert(accessors.nonEmpty) + assert(accessors.tail.forall(_.info.isPhantom)) ref(accessors.head).select(nme.hashCode_).ensureApplied } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 72ce89a33942..6c03582a16a4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -489,13 +489,17 @@ object Checking { param.isTerm && !param.is(Flags.Accessor) } clParamAccessors match { - case List(param) => + case param :: params => if (param.is(Mutable)) ctx.error(ValueClassParameterMayNotBeAVar(clazz, param), param.pos) if (param.info.isPhantom) - ctx.error("value class parameter must not be phantom", param.pos) - case _ => - ctx.error(ValueClassNeedsExactlyOneValParam(clazz), clazz.pos) + ctx.error("value class first parameter must not be phantom", param.pos) + else { + for (p <- params if !p.info.isPhantom) + ctx.error("value class can only have one non phantom parameter", p.pos) + } + case Nil => + ctx.error(ValueClassNeedsOneValParam(clazz), clazz.pos) } } stats.foreach(checkValueClassMember) diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index a597ef498b07..c51151331fa7 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -772,14 +772,14 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertEquals("variable i", param.show) } - @Test def valueClassNeedsExactlyOneVal = + @Test def valueClassNeedsOneVal = checkMessagesAfter("refchecks") { - """class MyValue(var i: Int, j: Int) extends AnyVal""" + """class MyValue() extends AnyVal""" } .expect { (ictx, messages) => implicit val ctx: Context = ictx assertMessageCount(1, messages) - val ValueClassNeedsExactlyOneValParam(valueClass) :: Nil = messages + val ValueClassNeedsOneValParam(valueClass) :: Nil = messages assertEquals("class MyValue", valueClass.show) } diff --git a/tests/neg/phantom-in-value-class.scala b/tests/neg/phantom-in-value-class.scala new file mode 100644 index 000000000000..23f225767f41 --- /dev/null +++ b/tests/neg/phantom-in-value-class.scala @@ -0,0 +1,11 @@ +import MyPhantom._ + + +class Cursed1(val p: Boo) extends AnyVal // error + +class Cursed2(val n: Int)(val a: Int) extends AnyVal // error + +object MyPhantom extends Phantom { + type Boo <: super[MyPhantom].Any + def boo: Boo = assume +} diff --git a/tests/run/phantom-in-value-class.scala b/tests/run/phantom-in-value-class.scala new file mode 100644 index 000000000000..daa93a5b31a5 --- /dev/null +++ b/tests/run/phantom-in-value-class.scala @@ -0,0 +1,22 @@ +import MyPhantom._ + +object Test { + def main(args: Array[String]): Unit = { + val cursed = new Cursed(7)(boo) + val cursed2 = new Cursed(7)(boo) + cursed.p + cursed2.p + } +} + + +class Cursed(val n: Int)(val p: Boo) extends AnyVal + +class Cursed2[B <: Boo](val n: Int)(val p: B) extends AnyVal + +class Cursed3[B <: Boo](val n: Int)(val p1: Boo, val p2: B) extends AnyVal + +object MyPhantom extends Phantom { + type Boo <: super[MyPhantom].Any + def boo: Boo = assume +}