Skip to content

Commit b6142d2

Browse files
committed
Implement value classes with phantom parameters
In this implementation the first parameter of the value class must be a subtype of scala.Any. Every other parameter must be phantom.
1 parent 9fd4d31 commit b6142d2

File tree

9 files changed

+54
-15
lines changed

9 files changed

+54
-15
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ object desugar {
503503
companionDefs(anyRef, companionMeths)
504504
else if (isValueClass) {
505505
constr0.vparamss match {
506-
case List(_ :: Nil) => companionDefs(anyRef, Nil)
506+
case (_ :: Nil) :: _ => companionDefs(anyRef, Nil)
507507
case _ => Nil // error will be emitted in typer
508508
}
509509
}

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,9 +1620,9 @@ object messages {
16201620
|"""
16211621
}
16221622

1623-
case class ValueClassNeedsExactlyOneValParam(valueClass: Symbol)(implicit ctx: Context)
1623+
case class ValueClassNeedsOneValParam(valueClass: Symbol)(implicit ctx: Context)
16241624
extends Message(ValueClassNeedsExactlyOneValParamID) {
1625-
val msg = hl"""value class needs to have exactly one ${"val"} parameter"""
1625+
val msg = hl"""value class needs one ${"val"} parameter"""
16261626
val kind = "Syntax"
16271627
val explanation = ""
16281628
}

compiler/src/dotty/tools/dotc/transform/Constructors.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { th
129129
// Produce aligned accessors and constructor parameters. We have to adjust
130130
// for any outer parameters, which are last in the sequence of original
131131
// parameter accessors but come first in the constructor parameter list.
132-
val accessors = cls.paramAccessors.filterNot(_.isSetter)
132+
val accessors = cls.paramAccessors.filterNot(x => x.isSetter || x.info.resultType.classSymbol == defn.ErasedPhantomClass)
133133
val vparamsWithOuterLast = vparams match {
134134
case vparam :: rest if vparam.name == nme.OUTER => rest ::: vparam :: Nil
135135
case _ => vparams

compiler/src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,8 @@ object Erasure {
414414
}
415415
}
416416

417-
if (tree.symbol eq defn.Phantom_assume) PhantomErasure.erasedAssume
417+
if ((origSym eq defn.Phantom_assume) || (origSym.is(Flags.ParamAccessor) && wasPhantom(pt)))
418+
PhantomErasure.erasedAssume
418419
else recur(typed(tree.qualifier, AnySelectionProto))
419420
}
420421

compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,8 @@ class SyntheticMethods(thisTransformer: DenotTransformer) {
159159
val thatAsClazz = ctx.newSymbol(ctx.owner, nme.x_0, Synthetic, clazzType, coord = ctx.owner.pos) // x$0
160160
def wildcardAscription(tp: Type) = Typed(Underscore(tp), TypeTree(tp))
161161
val pattern = Bind(thatAsClazz, wildcardAscription(clazzType)) // x$0 @ (_: C)
162-
val comparisons = accessors map (accessor =>
163-
This(clazz).select(accessor).select(defn.Any_==).appliedTo(ref(thatAsClazz).select(accessor)))
162+
val comparisons = accessors collect { case accessor if !accessor.info.isPhantom =>
163+
This(clazz).select(accessor).select(defn.Any_==).appliedTo(ref(thatAsClazz).select(accessor)) }
164164
val rhs = // this.x == this$0.x && this.y == x$0.y
165165
if (comparisons.isEmpty) Literal(Constant(true)) else comparisons.reduceLeft(_ and _)
166166
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) {
186186
* ```
187187
*/
188188
def valueHashCodeBody(implicit ctx: Context): Tree = {
189-
assert(accessors.length == 1)
189+
assert(accessors.nonEmpty)
190+
assert(accessors.tail.forall(_.info.isPhantom))
190191
ref(accessors.head).select(nme.hashCode_).ensureApplied
191192
}
192193

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -489,13 +489,17 @@ object Checking {
489489
param.isTerm && !param.is(Flags.Accessor)
490490
}
491491
clParamAccessors match {
492-
case List(param) =>
492+
case param :: params =>
493493
if (param.is(Mutable))
494494
ctx.error(ValueClassParameterMayNotBeAVar(clazz, param), param.pos)
495495
if (param.info.isPhantom)
496-
ctx.error("value class parameter must not be phantom", param.pos)
497-
case _ =>
498-
ctx.error(ValueClassNeedsExactlyOneValParam(clazz), clazz.pos)
496+
ctx.error("value class first parameter must not be phantom.", param.pos)
497+
else {
498+
for (p <- params if !p.info.isPhantom)
499+
ctx.error("value class can only have one non phantom parameter", p.pos)
500+
}
501+
case Nil =>
502+
ctx.error(ValueClassNeedsOneValParam(clazz), clazz.pos)
499503
}
500504
}
501505
stats.foreach(checkValueClassMember)

compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -772,14 +772,14 @@ class ErrorMessagesTests extends ErrorMessagesTest {
772772
assertEquals("variable i", param.show)
773773
}
774774

775-
@Test def valueClassNeedsExactlyOneVal =
775+
@Test def valueClassNeedsOneVal =
776776
checkMessagesAfter("refchecks") {
777-
"""class MyValue(var i: Int, j: Int) extends AnyVal"""
777+
"""class MyValue() extends AnyVal"""
778778
}
779779
.expect { (ictx, messages) =>
780780
implicit val ctx: Context = ictx
781781
assertMessageCount(1, messages)
782-
val ValueClassNeedsExactlyOneValParam(valueClass) :: Nil = messages
782+
val ValueClassNeedsOneValParam(valueClass) :: Nil = messages
783783
assertEquals("class MyValue", valueClass.show)
784784
}
785785

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import MyPhantom._
2+
3+
4+
class Cursed1(val p: Boo) extends AnyVal // error
5+
6+
class Cursed2(val n: Int)(val a: Int) extends AnyVal // error
7+
8+
object MyPhantom extends Phantom {
9+
type Boo <: super[MyPhantom].Any
10+
def boo: Boo = assume
11+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import MyPhantom._
2+
3+
object Test {
4+
def main(args: Array[String]): Unit = {
5+
val cursed = new Cursed(7)(boo)
6+
val cursed2 = new Cursed(7)(boo)
7+
cursed.p
8+
cursed2.p
9+
}
10+
}
11+
12+
13+
class Cursed(val n: Int)(val p: Boo) extends AnyVal
14+
15+
class Cursed2[B <: Boo](val n: Int)(val p: B) extends AnyVal
16+
17+
class Cursed3[B <: Boo](val n: Int)(val p1: Boo, val p2: B) extends AnyVal
18+
19+
object MyPhantom extends Phantom {
20+
type Boo <: super[MyPhantom].Any
21+
def boo: Boo = assume
22+
}

0 commit comments

Comments
 (0)