Skip to content

Commit 9f2b5ad

Browse files
authored
Merge pull request #1682 from dotty-staging/vclass
Fix checks related to value classes
2 parents 1773b37 + 9b8aaaf commit 9f2b5ad

12 files changed

+70
-50
lines changed

compiler/src/dotty/tools/dotc/core/TypeErasure.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
438438
private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = {
439439
val cls = tref.symbol.asClass
440440
val underlying = underlyingOfValueClass(cls)
441-
if (underlying.exists) ErasedValueType(tref, valueErasure(underlying))
441+
if (underlying.exists && !isCyclic(cls)) ErasedValueType(tref, valueErasure(underlying))
442442
else NoType
443443
}
444444

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,6 @@ class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with Ful
135135
// TODO: this is state and should be per-run
136136
// todo: check that when transformation finished map is empty
137137

138-
private def checkNonCyclic(pos: Position, seen: Set[Symbol], clazz: ClassSymbol)(implicit ctx: Context): Unit =
139-
if (seen contains clazz)
140-
ctx.error("value class may not unbox to itself", pos)
141-
else {
142-
val unboxed = underlyingOfValueClass(clazz).typeSymbol
143-
if (isDerivedValueClass(unboxed)) checkNonCyclic(pos, seen + clazz, unboxed.asClass)
144-
}
145-
146138
override def transformTemplate(tree: tpd.Template)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
147139
if (isDerivedValueClass(ctx.owner)) {
148140
/* This is currently redundant since value classes may not

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,14 @@ class TreeChecker extends Phase with SymTransformer {
135135
}
136136
}
137137

138-
class Checker(phasesToCheck: Seq[Phase]) extends ReTyper {
138+
class Checker(phasesToCheck: Seq[Phase]) extends ReTyper with Checking {
139139

140140
val nowDefinedSyms = new mutable.HashSet[Symbol]
141141
val everDefinedSyms = new mutable.HashMap[Symbol, Tree]
142142

143+
// don't check value classes after typer, as the constraint about constructors doesn't hold after transform
144+
override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context) = ()
145+
143146
def withDefinedSym[T](tree: untpd.Tree)(op: => T)(implicit ctx: Context): T = tree match {
144147
case tree: DefTree =>
145148
val sym = tree.symbol

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,14 @@ object ValueClasses {
5353
def underlyingOfValueClass(d: ClassDenotation)(implicit ctx: Context): Type =
5454
valueClassUnbox(d).info.resultType
5555

56+
/** Whether a value class wraps itself */
57+
def isCyclic(cls: ClassSymbol)(implicit ctx: Context): Boolean = {
58+
def recur(seen: Set[Symbol], clazz: ClassSymbol)(implicit ctx: Context): Boolean =
59+
(seen contains clazz) || {
60+
val unboxed = underlyingOfValueClass(clazz).typeSymbol
61+
(isDerivedValueClass(unboxed)) && recur(seen + clazz, unboxed.asClass)
62+
}
63+
64+
recur(Set[Symbol](), cls)
65+
}
5666
}

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

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import ErrorReporting.{err, errorType}
2929
import config.Printers.typr
3030
import collection.mutable
3131
import SymDenotations.NoCompleter
32+
import dotty.tools.dotc.transform.ValueClasses._
3233

3334
object Checking {
3435
import tpd._
@@ -56,7 +57,7 @@ object Checking {
5657
checkBounds(args, poly.paramBounds, _.substParams(poly, _))
5758

5859
/** Check applied type trees for well-formedness. This means
59-
* - all arguments are within their corresponding bounds
60+
* - all arguments are within their corresponding bounds
6061
* - if type is a higher-kinded application with wildcard arguments,
6162
* check that it or one of its supertypes can be reduced to a normal application.
6263
* Unreducible applications correspond to general existentials, and we
@@ -88,12 +89,12 @@ object Checking {
8889
checkWildcardHKApply(tp.superType, pos)
8990
}
9091
case _ =>
91-
}
92+
}
9293
def checkValidIfHKApply(implicit ctx: Context): Unit =
9394
checkWildcardHKApply(tycon.tpe.appliedTo(args.map(_.tpe)), tree.pos)
9495
checkValidIfHKApply(ctx.addMode(Mode.AllowLambdaWildcardApply))
9596
}
96-
97+
9798
/** Check that `tp` refers to a nonAbstract class
9899
* and that the instance conforms to the self type of the created class.
99100
*/
@@ -406,6 +407,43 @@ object Checking {
406407
notPrivate.errors.foreach { case (msg, pos) => ctx.errorOrMigrationWarning(msg, pos) }
407408
info
408409
}
410+
411+
/** Verify classes extending AnyVal meet the requirements */
412+
def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context) = {
413+
def checkValueClassMember(stat: Tree) = stat match {
414+
case _: ValDef if !stat.symbol.is(ParamAccessor) =>
415+
ctx.error(s"value class may not define non-parameter field", stat.pos)
416+
case d: DefDef if d.symbol.isConstructor =>
417+
ctx.error(s"value class may not define secondary constructor", stat.pos)
418+
case _: MemberDef | _: Import | EmptyTree =>
419+
// ok
420+
case _ =>
421+
ctx.error(s"value class may not contain initialization statements", stat.pos)
422+
}
423+
if (isDerivedValueClass(clazz)) {
424+
if (clazz.is(Trait))
425+
ctx.error("Only classes (not traits) are allowed to extend AnyVal", clazz.pos)
426+
if (clazz.is(Abstract))
427+
ctx.error("`abstract' modifier cannot be used with value classes", clazz.pos)
428+
if (!clazz.isStatic)
429+
ctx.error(s"value class may not be a ${if (clazz.owner.isTerm) "local class" else "member of another class"}", clazz.pos)
430+
if (isCyclic(clazz.asClass))
431+
ctx.error("value class cannot wrap itself", clazz.pos)
432+
else {
433+
val clParamAccessors = clazz.asClass.paramAccessors.filter(_.isTerm)
434+
clParamAccessors match {
435+
case List(param) =>
436+
if (param.is(Mutable))
437+
ctx.error("value class parameter must not be a var", param.pos)
438+
439+
case _ =>
440+
ctx.error("value class needs to have exactly one val parameter", clazz.pos)
441+
}
442+
}
443+
stats.foreach(checkValueClassMember)
444+
}
445+
446+
}
409447
}
410448

411449
trait Checking {
@@ -553,6 +591,10 @@ trait Checking {
553591
errorTree(tpt, ex"Singleton type ${tpt.tpe} is not allowed $where")
554592
}
555593
else tpt
594+
595+
/** Verify classes extending AnyVal meet the requirements */
596+
def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context) =
597+
Checking.checkDerivedValueClass(clazz, stats)
556598
}
557599

558600
trait NoChecking extends Checking {
@@ -568,4 +610,5 @@ trait NoChecking extends Checking {
568610
override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context) = ()
569611
override def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = tpt
570612
override def checkNotSingleton(tpt: Tree, where: String)(implicit ctx: Context): Tree = tpt
613+
override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context) = ()
571614
}

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

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import config.{ScalaVersion, NoScalaVersion}
1818
import Decorators._
1919
import typer.ErrorReporting._
2020
import DenotTransformers._
21-
import ValueClasses.isDerivedValueClass
2221

2322
object RefChecks {
2423
import tpd._
@@ -688,39 +687,6 @@ object RefChecks {
688687
}
689688
}
690689

691-
/** Verify classes extending AnyVal meet the requirements */
692-
private def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context) = {
693-
def checkValueClassMember(stat: Tree) = stat match {
694-
case _: ValDef if !stat.symbol.is(ParamAccessor) =>
695-
ctx.error(s"value class may not define non-parameter field", stat.pos)
696-
case _: DefDef if stat.symbol.isConstructor =>
697-
ctx.error(s"value class may not define secondary constructor", stat.pos)
698-
case _: MemberDef | _: Import | EmptyTree =>
699-
// ok
700-
case _ =>
701-
ctx.error(s"value class may not contain initialization statements", stat.pos)
702-
}
703-
if (isDerivedValueClass(clazz)) {
704-
if (clazz.is(Trait))
705-
ctx.error("Only classes (not traits) are allowed to extend AnyVal", clazz.pos)
706-
if (clazz.is(Abstract))
707-
ctx.error("`abstract' modifier cannot be used with value classes", clazz.pos)
708-
if (!clazz.isStatic)
709-
ctx.error(s"value class may not be a ${if (clazz.owner.isTerm) "local class" else "member of another class"}", clazz.pos)
710-
else {
711-
val clParamAccessors = clazz.asClass.paramAccessors.filter(sym => sym.isTerm && !sym.is(Method))
712-
clParamAccessors match {
713-
case List(param) =>
714-
if (param.is(Mutable))
715-
ctx.error("value class parameter must not be a var", param.pos)
716-
case _ =>
717-
ctx.error("value class needs to have exactly one val parameter", clazz.pos)
718-
}
719-
}
720-
stats.foreach(checkValueClassMember)
721-
}
722-
}
723-
724690
type LevelAndIndex = immutable.Map[Symbol, (LevelInfo, Int)]
725691

726692
class OptLevelInfo extends DotClass {
@@ -836,7 +802,6 @@ class RefChecks extends MiniPhase { thisTransformer =>
836802
checkParents(cls)
837803
checkCompanionNameClashes(cls)
838804
checkAllOverrides(cls)
839-
checkDerivedValueClass(cls, tree.body)
840805
tree
841806
} catch {
842807
case ex: MergeError =>

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,6 +1293,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
12931293
ctx.featureWarning(nme.dynamics.toString, "extension of type scala.Dynamic", isScala2Feature = true,
12941294
cls, isRequired, cdef.pos)
12951295
}
1296+
1297+
// check value class constraints
1298+
checkDerivedValueClass(cls, body1)
1299+
12961300
cdef1
12971301

12981302
// todo later: check that

tests/neg/i1642.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class Test2(val valueVal: Test2) extends AnyVal // error: value class cannot wrap itself

tests/neg/i1670.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class A(a:Int, b:Int) extends AnyVal // error: value class needs to have exactly one val parameter

tests/neg/i705-inner-value-class2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
class Foo {
2-
class B(val a: Int) extends AnyVal
2+
class B(val a: Int) extends AnyVal // error: value class may not be a member of another class`
33
}
44

55
object Test {

tests/pickling/extmethods.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@ package extMethods
33
trait That1[A]
44
class T[A, This <: That1[A]](val x: Int) extends AnyVal {
55
self: This =>
6-
var next: This = _
76
final def loop(x: This, cnt: Int): Int = loop(x, cnt + 1)
87
}

tests/pos/i1642.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class Test1(val x: Int) extends AnyVal
2+
class Test2(val y: Test1) extends AnyVal

0 commit comments

Comments
 (0)