Skip to content

Move ValueClass checking errors to case class scheme #2505

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

Merged
merged 2 commits into from
May 24, 2017
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ public enum ErrorMessageID {
OnlyClassesCanHaveDeclaredButUndefinedMembersID,
CannotExtendAnyValID,
CannotHaveSameNameAsID,
ValueClassesMayNotDefineInnerID,
ValueClassesMayNotDefineNonParameterFieldID,
ValueClassesMayNotDefineASecondaryConstructorID,
ValueClassesMayNotContainInitalizationID,
ValueClassesMayNotBeAbstractID,
ValueClassesMayNotBeContaintedID,
ValueClassesMayNotWrapItselfID,
ValueClassParameterMayNotBeAVarID,
ValueClassNeedsExactlyOneValParamID,
;

public int errorNumber() {
Expand Down
65 changes: 65 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1484,5 +1484,70 @@ object messages {
val explanation = ""
}

case class ValueClassesMayNotDefineInner(valueClass: Symbol, inner: Symbol)(implicit ctx: Context)
extends Message(ValueClassesMayNotDefineInnerID) {
val msg = hl"""value classes may not define an inner class"""
val kind = "Syntax"
val explanation = ""
}

case class ValueClassesMayNotDefineNonParameterField(valueClass: Symbol, field: Symbol)(implicit ctx: Context)
extends Message(ValueClassesMayNotDefineNonParameterFieldID) {
val msg = hl"""value classes may not define non-parameter field"""
val kind = "Syntax"
val explanation = ""
}

case class ValueClassesMayNotDefineASecondaryConstructor(valueClass: Symbol, constructor: Symbol)(implicit ctx: Context)
extends Message(ValueClassesMayNotDefineASecondaryConstructorID) {
val msg = hl"""value classes may not define a secondary constructor"""
val kind = "Syntax"
val explanation = ""
}

case class ValueClassesMayNotContainInitalization(valueClass: Symbol)(implicit ctx: Context)
extends Message(ValueClassesMayNotContainInitalizationID) {
val msg = hl"""value classes may not contain initialization statements"""
val kind = "Syntax"
val explanation = ""
}

case class ValueClassesMayNotBeAbstract(valueClass: Symbol)(implicit ctx: Context)
extends Message(ValueClassesMayNotBeAbstractID) {
val msg = hl"""value classes may not be ${"abstract"}"""
val kind = "Syntax"
val explanation = ""
}

case class ValueClassesMayNotBeContainted(valueClass: Symbol)(implicit ctx: Context)
extends Message(ValueClassesMayNotBeContaintedID) {
private val localOrMember = if (valueClass.owner.isTerm) "local class" else "member of another class"
val msg = s"""value classes may not be a $localOrMember"""
val kind = "Syntax"
val explanation = ""
}

case class ValueClassesMayNotWrapItself(valueClass: Symbol)(implicit ctx: Context)
extends Message(ValueClassesMayNotWrapItselfID) {
val msg = """a value class may not wrap itself"""
val kind = "Syntax"
val explanation = ""
}

case class ValueClassParameterMayNotBeAVar(valueClass: Symbol, param: Symbol)(implicit ctx: Context)
extends Message(ValueClassParameterMayNotBeAVarID) {
val msg = hl"""a value class parameter may not be a ${"var"}"""
val kind = "Syntax"
val explanation =
hl"""A value class must have exactly one ${"val"} parameter.
|"""
}

case class ValueClassNeedsExactlyOneValParam(valueClass: Symbol)(implicit ctx: Context)
extends Message(ValueClassNeedsExactlyOneValParamID) {
val msg = hl"""value class needs to have exactly one ${"val"} parameter"""
val kind = "Syntax"
val explanation = ""
}

}
20 changes: 10 additions & 10 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -439,35 +439,35 @@ object Checking {
def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context) = {
def checkValueClassMember(stat: Tree) = stat match {
case _: TypeDef if stat.symbol.isClass =>
ctx.error(s"value class may not define an inner class", stat.pos)
ctx.error(ValueClassesMayNotDefineInner(clazz, stat.symbol), stat.pos)
case _: ValDef if !stat.symbol.is(ParamAccessor) =>
ctx.error(s"value class may not define non-parameter field", stat.pos)
case d: DefDef if d.symbol.isConstructor =>
ctx.error(s"value class may not define secondary constructor", stat.pos)
ctx.error(ValueClassesMayNotDefineNonParameterField(clazz, stat.symbol), stat.pos)
case _: DefDef if stat.symbol.isConstructor =>
ctx.error(ValueClassesMayNotDefineASecondaryConstructor(clazz, stat.symbol), stat.pos)
case _: MemberDef | _: Import | EmptyTree =>
// ok
case _ =>
ctx.error(s"value class may not contain initialization statements", stat.pos)
ctx.error(ValueClassesMayNotContainInitalization(clazz), stat.pos)
}
if (isDerivedValueClass(clazz)) {
if (clazz.is(Trait))
ctx.error(CannotExtendAnyVal(clazz), clazz.pos)
if (clazz.is(Abstract))
ctx.error("`abstract' modifier cannot be used with value classes", clazz.pos)
ctx.error(ValueClassesMayNotBeAbstract(clazz), clazz.pos)
if (!clazz.isStatic)
ctx.error(s"value class may not be a ${if (clazz.owner.isTerm) "local class" else "member of another class"}", clazz.pos)
ctx.error(ValueClassesMayNotBeContainted(clazz), clazz.pos)
if (isCyclic(clazz.asClass))
ctx.error("value class cannot wrap itself", clazz.pos)
ctx.error(ValueClassesMayNotWrapItself(clazz), clazz.pos)
else {
val clParamAccessors = clazz.asClass.paramAccessors.filter(_.isTerm)
clParamAccessors match {
case List(param) =>
if (param.is(Mutable))
ctx.error("value class parameter must not be a var", param.pos)
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("value class needs to have exactly one val parameter", clazz.pos)
ctx.error(ValueClassNeedsExactlyOneValParam(clazz), clazz.pos)
}
}
stats.foreach(checkValueClassMember)
Expand Down
106 changes: 106 additions & 0 deletions compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -689,5 +689,111 @@ class ErrorMessagesTests extends ErrorMessagesTest {
assertEquals("class A", cls.show)
}

@Test def valueClassesMayNotDefineInner =
checkMessagesAfter("refchecks") {
"""class MyValue(i: Int) extends AnyVal {
| class Inner
|}
|""".stripMargin
}
.expect { (ictx, messages) =>
implicit val ctx: Context = ictx
assertMessageCount(1, messages)
val ValueClassesMayNotDefineInner(valueClass, inner) :: Nil = messages
assertEquals("class MyValue", valueClass.show)
assertEquals("class Inner", inner.show)
}

@Test def valueClassesMayNotDefineNonParameterField =
checkMessagesAfter("refchecks") {
"""class MyValue(i: Int) extends AnyVal {
| val illegal: Int
|}
|""".stripMargin
}
.expect { (ictx, messages) =>
implicit val ctx: Context = ictx
assertMessageCount(1, messages)
val ValueClassesMayNotDefineNonParameterField(valueClass, field) :: Nil = messages
assertEquals("class MyValue", valueClass.show)
assertEquals("value illegal", field.show)
}

@Test def valueClassesMayNotDefineASecondaryConstructor =
checkMessagesAfter("refchecks") {
"""class MyValue(i: Int) extends AnyVal {
| def this() = this(2)
|}
|""".stripMargin
}
.expect { (ictx, messages) =>
implicit val ctx: Context = ictx
assertMessageCount(1, messages)
val ValueClassesMayNotDefineASecondaryConstructor(valueClass, constuctor) :: Nil = messages
assertEquals("class MyValue", valueClass.show)
assertEquals("constructor MyValue", constuctor.show)
}

@Test def valueClassesMayNotContainInitalization =
checkMessagesAfter("refchecks") {
"""class MyValue(i: Int) extends AnyVal {
| println("Hallo?")
|}
|""".stripMargin
}
.expect { (ictx, messages) =>
implicit val ctx: Context = ictx
assertMessageCount(1, messages)
val ValueClassesMayNotContainInitalization(valueClass) :: Nil = messages
assertEquals("class MyValue", valueClass.show)
}

@Test def valueClassesMayNotBeContained =
checkMessagesAfter("refchecks") {
"""class Outer {
| class MyValue(i: Int) extends AnyVal
|}
|""".stripMargin
}
.expect { (ictx, messages) =>
implicit val ctx: Context = ictx
assertMessageCount(1, messages)
val ValueClassesMayNotBeContainted(valueClass) :: Nil = messages
assertEquals("class MyValue", valueClass.show)
}

@Test def valueClassesMayNotWrapItself =
checkMessagesAfter("refchecks") {
"""class MyValue(i: MyValue) extends AnyVal"""
}
.expect { (ictx, messages) =>
implicit val ctx: Context = ictx
assertMessageCount(1, messages)
val ValueClassesMayNotWrapItself(valueClass) :: Nil = messages
assertEquals("class MyValue", valueClass.show)
}

@Test @Ignore def valueClassParameterMayNotBeVar =
checkMessagesAfter("refchecks") {
"""class MyValue(var i: Int) extends AnyVal"""
}
.expect { (ictx, messages) =>
implicit val ctx: Context = ictx
assertMessageCount(1, messages)
val ValueClassParameterMayNotBeAVar(valueClass, param) :: Nil = messages
assertEquals("class MyValue", valueClass.show)
assertEquals("variable i", param.show)
}

@Test def valueClassNeedsExactlyOneVal =
checkMessagesAfter("refchecks") {
"""class MyValue(var i: Int, j: Int) extends AnyVal"""
}
.expect { (ictx, messages) =>
implicit val ctx: Context = ictx
assertMessageCount(1, messages)
val ValueClassNeedsExactlyOneValParam(valueClass) :: Nil = messages
assertEquals("class MyValue", valueClass.show)
}

}