From ef934492df93e0fd3d78e7a3d4f9cccaf765d4d5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 20 Sep 2012 18:22:39 +0200 Subject: Revised restrictions for value classes and unversal traits and brought compiler in line with them. One thing we can accept IMO are nested classes (nested objects are still a problem). In fact, it makes no sense to exclude nested classes from value classes but not from universal traits. A class nested in universal trait will becomes a class nested in a value class by inheritance. Note that the reflection library already contains a universal trait with a nested class (IndexedSeqLike), so we should accept them if we can. --- src/compiler/scala/tools/nsc/ast/TreeInfo.scala | 7 ---- .../scala/tools/nsc/typechecker/Typers.scala | 47 +++++++++++++--------- test/files/neg/anytrait.check | 4 +- test/files/neg/t5799.check | 2 +- test/files/neg/t5882.check | 16 ++------ test/files/neg/t5882.scala | 5 ++- test/files/neg/valueclasses.check | 4 +- test/files/run/t5882.scala | 14 +++++++ 8 files changed, 55 insertions(+), 44 deletions(-) create mode 100644 test/files/run/t5882.scala diff --git a/src/compiler/scala/tools/nsc/ast/TreeInfo.scala b/src/compiler/scala/tools/nsc/ast/TreeInfo.scala index 9e46155d14..3e1d606877 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeInfo.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeInfo.scala @@ -45,11 +45,4 @@ abstract class TreeInfo extends scala.reflect.internal.TreeInfo { def isInterface(mods: HasFlags, body: List[Tree]) = mods.isTrait && (body forall isInterfaceMember) - - def isAllowedInUniversalTrait(stat: Tree): Boolean = stat match { - case _: ValDef => false - case Import(_, _) | EmptyTree => true - case _: DefTree => true - case _ => false - } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index c25b6c3726..c73263a101 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1388,6 +1388,32 @@ trait Typers extends Modes with Adaptations with Tags { } } + private def checkEphemeral(clazz: Symbol, body: List[Tree]) = { + val isValueClass = !clazz.isTrait + for (stat <- body) { + def notAllowed(what: String) = { + val where = if (clazz.isTrait) "universal trait extending from class Any" else "value class" + unit.error(stat.pos, s"$what is not allowed in $where") + } + stat match { + case _: Import | _: TypeDef | _: ClassDef | EmptyTree => // OK + case DefDef(_, name, _, _, _, _) => + if (stat.symbol.isAuxiliaryConstructor) + notAllowed("secondary constructor") + else if (isValueClass && (name == nme.equals_ || name == nme.hashCode_)) + notAllowed(s"redefinition of $name method") + else if (stat.symbol != null && (stat.symbol hasFlag PARAMACCESSOR)) + notAllowed("additional parameter") + case _: ValDef => + notAllowed("field definition") + case _: ModuleDef => + notAllowed("nested object") + case _ => + notAllowed("this statement") + } + } + } + private def validateDerivedValueClass(clazz: Symbol, body: List[Tree]) = { if (clazz.isTrait) unit.error(clazz.pos, "only classes (not traits) are allowed to extend AnyVal") @@ -1395,7 +1421,7 @@ trait Typers extends Modes with Adaptations with Tags { unit.error(clazz.pos, "value class may not be a "+ (if (clazz.owner.isTerm) "local class" else "member of another class")) if (!clazz.isPrimitiveValueClass) { - clazz.info.decls.toList.filter(acc => acc.isMethod && (acc hasFlag PARAMACCESSOR)) match { + clazz.info.decls.toList.filter(acc => acc.isMethod && acc.isParamAccessor) match { case List(acc) => def isUnderlyingAcc(sym: Symbol) = sym == acc || acc.hasAccessorFlag && sym == acc.accessed @@ -1403,25 +1429,12 @@ trait Typers extends Modes with Adaptations with Tags { unit.error(acc.pos, "value class needs to have a publicly accessible val parameter") else if (acc.tpe.typeSymbol.isDerivedValueClass) unit.error(acc.pos, "value class may not wrap another user-defined value class") - for (stat <- body) - if (!treeInfo.isAllowedInUniversalTrait(stat) && !isUnderlyingAcc(stat.symbol)) - unit.error(stat.pos, - if (stat.symbol != null && (stat.symbol hasFlag PARAMACCESSOR)) "illegal parameter for value class" - else "this statement is not allowed in value class: " + stat) + checkEphemeral(clazz, body filterNot (stat => isUnderlyingAcc(stat.symbol))) case x => unit.error(clazz.pos, "value class needs to have exactly one public val parameter") } } - def valueClassMayNotHave(at: Tree, what: String) = unit.error(at.pos, s"value class may not have $what") - body.foreach { - case dd: DefDef if dd.symbol.isAuxiliaryConstructor => valueClassMayNotHave(dd, "secondary constructors") - case t => t.foreach { - case md: ModuleDef => valueClassMayNotHave(md, "nested module definitions") - case cd: ClassDef => valueClassMayNotHave(cd, "nested class definitions") - case _ => - } - } for (tparam <- clazz.typeParams) if (tparam hasAnnotation definitions.SpecializedClass) unit.error(tparam.pos, "type parameter of value class may not be specialized") @@ -1668,9 +1681,7 @@ trait Typers extends Modes with Adaptations with Tags { } val impl2 = finishMethodSynthesis(impl1, clazz, context) if (clazz.isTrait && clazz.info.parents.nonEmpty && clazz.info.firstParent.normalize.typeSymbol == AnyClass) - for (stat <- impl2.body) - if (!treeInfo.isAllowedInUniversalTrait(stat)) - unit.error(stat.pos, "this statement is not allowed in universal trait extending from class Any: "+stat) + checkEphemeral(clazz, impl2.body) if ((clazz != ClassfileAnnotationClass) && (clazz isNonBottomSubClass ClassfileAnnotationClass)) restrictionWarning(cdef.pos, unit, diff --git a/test/files/neg/anytrait.check b/test/files/neg/anytrait.check index 9dd970b58c..fabe74d379 100644 --- a/test/files/neg/anytrait.check +++ b/test/files/neg/anytrait.check @@ -1,7 +1,7 @@ -anytrait.scala:3: error: this statement is not allowed in universal trait extending from class Any: private[this] var x: Int = 1 +anytrait.scala:3: error: field definition is not allowed in universal trait extending from class Any var x = 1 ^ -anytrait.scala:5: error: this statement is not allowed in universal trait extending from class Any: T.this.x_=(T.this.x.+(1)) +anytrait.scala:5: error: this statement is not allowed in universal trait extending from class Any { x += 1 } ^ two errors found diff --git a/test/files/neg/t5799.check b/test/files/neg/t5799.check index 10e2658d56..3b43d06a94 100644 --- a/test/files/neg/t5799.check +++ b/test/files/neg/t5799.check @@ -1,4 +1,4 @@ -t5799.scala:2: error: value class may not have secondary constructors +t5799.scala:2: error: secondary constructor is not allowed in value class def this(s: String) = this(s.toDouble) ^ one error found diff --git a/test/files/neg/t5882.check b/test/files/neg/t5882.check index df01c7bc0a..b9ca5a1b2a 100644 --- a/test/files/neg/t5882.check +++ b/test/files/neg/t5882.check @@ -1,15 +1,7 @@ -t5882.scala:2: warning: case classes without a parameter list have been deprecated; -use either case objects or case classes with `()' as parameter list. - case class Scope - ^ -t5882.scala:2: error: value class may not have nested class definitions - case class Scope +t5882.scala:4: error: nested object is not allowed in value class + case class Scope() ^ -t5882.scala:3: error: value class may not have nested class definitions - class Foo - ^ -t5882.scala:4: error: value class may not have nested module definitions +t5882.scala:5: error: nested object is not allowed in value class object Bar ^ -one warning found -three errors found +two errors found diff --git a/test/files/neg/t5882.scala b/test/files/neg/t5882.scala index 1233eb636f..3a55abdc9a 100644 --- a/test/files/neg/t5882.scala +++ b/test/files/neg/t5882.scala @@ -1,5 +1,6 @@ +// SIP-15 was changed to allow nested classes. See run/t5882.scala + class NodeOps(val n: Any) extends AnyVal { - case class Scope - class Foo + case class Scope() object Bar } diff --git a/test/files/neg/valueclasses.check b/test/files/neg/valueclasses.check index 4f042faded..3b82a8358c 100644 --- a/test/files/neg/valueclasses.check +++ b/test/files/neg/valueclasses.check @@ -25,13 +25,13 @@ class V5(private val x: Int) extends AnyVal // fail valueclasses.scala:19: error: value class needs to have exactly one public val parameter class V6(val x: Int, val y: String) extends AnyVal // fail ^ -valueclasses.scala:20: error: illegal parameter for value class +valueclasses.scala:20: error: field definition is not allowed in value class class V7(val x: Int, private[this] val y: String) extends AnyVal // fail ^ valueclasses.scala:21: error: value class needs to have exactly one public val parameter class V8(var x: Int) extends AnyVal // fail ^ -valueclasses.scala:24: error: this statement is not allowed in value class: private[this] val y: Int = V9.this.x +valueclasses.scala:24: error: field definition is not allowed in value class val y = x // fail ^ valueclasses.scala:29: error: type parameter of value class may not be specialized diff --git a/test/files/run/t5882.scala b/test/files/run/t5882.scala new file mode 100644 index 0000000000..47996d3068 --- /dev/null +++ b/test/files/run/t5882.scala @@ -0,0 +1,14 @@ +// SIP-15 was revised to allow nested classes in value classes. +// This test checks that their basic functionality. + +class NodeOps(val n: Any) extends AnyVal { self => + class Foo() { def show = self.show(n) } + def show(x: Any) = x.toString +} + + +object Test extends App { + + val n = new NodeOps("abc") + assert(new n.Foo().show == "abc") +} -- cgit v1.2.3