From bb8c2ca02b0fabe377cd4a0997deaa400c4801ce Mon Sep 17 00:00:00 2001 From: Yoonjae Jeon Date: Sun, 20 Apr 2025 15:44:40 +0900 Subject: [PATCH 1/2] Disallow value classes extending type aliases of AnyVal --- .../tools/dotc/reporting/ErrorMessageID.scala | 1 + .../dotty/tools/dotc/reporting/messages.scala | 6 ++++++ .../tools/dotc/transform/TreeChecker.scala | 2 +- .../src/dotty/tools/dotc/typer/Checking.scala | 19 +++++++++++++++---- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/neg/i21918.scala | 2 ++ 6 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 tests/neg/i21918.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 150df4646d86..13361a9e99ef 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -223,6 +223,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case IllegalUnrollPlacementID // errorNumber: 207 case ExtensionHasDefaultID // errorNumber: 208 case FormatInterpolationErrorID // errorNumber: 209 + case ValueClassCannotExtendAliasOfAnyValID // errorNumber: 210 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 130178dc8aa8..6ddd76c5f915 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1813,6 +1813,12 @@ class ValueClassParameterMayNotBeCallByName(valueClass: Symbol, param: Symbol)(u def explain(using Context) = "" } +class ValueClassCannotExtendAliasOfAnyVal(valueClass: Symbol, alias: Symbol)(using Context) + extends SyntaxMsg(ValueClassCannotExtendAliasOfAnyValID) { + def msg(using Context) = i"""A value class cannot extend a type alias ($alias) of ${hl("AnyVal")}""" + def explain(using Context) = "" +} + class SuperCallsNotAllowedInlineable(symbol: Symbol)(using Context) extends SyntaxMsg(SuperCallsNotAllowedInlineableID) { def msg(using Context) = i"Super call not allowed in inlineable $symbol" diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 90262bc5da85..2f4ad3a83e4e 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -244,7 +244,7 @@ object TreeChecker { private val everDefinedSyms = MutableSymbolMap[untpd.Tree]() // don't check value classes after typer, as the constraint about constructors doesn't hold after transform - override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(using Context): Unit = () + override def checkDerivedValueClass(cdef: untpd.TypeDef, clazz: Symbol, stats: List[Tree])(using Context): Unit = () def withDefinedSyms[T](trees: List[untpd.Tree])(op: => T)(using Context): T = { var locally = List.empty[Symbol] diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 2eec6579492b..ce1ed5d1e85e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -743,7 +743,7 @@ object Checking { } /** Verify classes extending AnyVal meet the requirements */ - def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(using Context): Unit = { + def checkDerivedValueClass(cdef: untpd.TypeDef, clazz: Symbol, stats: List[Tree])(using Context): Unit = { def checkValueClassMember(stat: Tree) = stat match { case _: TypeDef if stat.symbol.isClass => report.error(ValueClassesMayNotDefineInner(clazz, stat.symbol), stat.srcPos) @@ -760,6 +760,15 @@ object Checking { // enum extending a value class type (AnyVal or an alias of it) // The error message 'EnumMayNotBeValueClassesID' will take care of generating the error message (See #22236) if (clazz.isDerivedValueClass && !clazz.isEnumAnonymClass) { + val parentOpt = cdef.rhs match { + case impl: Template => + impl.parents.headOption + case _ => None + } + val isExtendingAliasOfAnyVal = parentOpt.exists { parent => + parent.symbol.isAliasType && parent.tpe.nn.dealias =:= defn.AnyValType + } + if (clazz.is(Trait)) report.error(CannotExtendAnyVal(clazz), clazz.srcPos) if clazz.is(Module) then @@ -770,6 +779,8 @@ object Checking { report.error(ValueClassesMayNotBeAbstract(clazz), clazz.srcPos) if (!clazz.isStatic) report.error(ValueClassesMayNotBeContainted(clazz), clazz.srcPos) + if (isExtendingAliasOfAnyVal) + report.error(ValueClassCannotExtendAliasOfAnyVal(clazz, parentOpt.get.symbol), clazz.srcPos) if (isDerivedValueClass(underlyingOfValueClass(clazz.asClass).classSymbol)) report.error(ValueClassesMayNotWrapAnotherValueClass(clazz), clazz.srcPos) else { @@ -1307,8 +1318,8 @@ trait Checking { else tpt /** Verify classes extending AnyVal meet the requirements */ - def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(using Context): Unit = - Checking.checkDerivedValueClass(clazz, stats) + def checkDerivedValueClass(cdef: untpd.TypeDef, clazz: Symbol, stats: List[Tree])(using Context): Unit = + Checking.checkDerivedValueClass(cdef, clazz, stats) /** Check that case classes are not inherited by case classes. */ @@ -1689,7 +1700,7 @@ trait NoChecking extends ReChecking { override def checkNoTargetNameConflict(stats: List[Tree])(using Context): Unit = () override def checkParentCall(call: Tree, caller: ClassSymbol)(using Context): Unit = () override def checkSimpleKinded(tpt: Tree)(using Context): Tree = tpt - override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(using Context): Unit = () + override def checkDerivedValueClass(cdef: untpd.TypeDef, clazz: Symbol, stats: List[Tree])(using Context): Unit = () override def checkCaseInheritance(parentSym: Symbol, caseCls: ClassSymbol, pos: SrcPos)(using Context): Unit = () override def checkNoForwardDependencies(vparams: List[ValDef])(using Context): Unit = () override def checkMembersOK(tp: Type, pos: SrcPos)(using Context): Type = tp diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6b7b840e7606..79aaf367851a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3243,7 +3243,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer checkNonCyclicInherited(cls.thisType, cls.info.parents, cls.info.decls, cdef.srcPos) // check value class constraints - checkDerivedValueClass(cls, body1) + checkDerivedValueClass(cdef, cls, body1) val effectiveOwner = cls.owner.skipWeakOwner if cls.is(ModuleClass) diff --git a/tests/neg/i21918.scala b/tests/neg/i21918.scala new file mode 100644 index 000000000000..bd3361b38c38 --- /dev/null +++ b/tests/neg/i21918.scala @@ -0,0 +1,2 @@ +type AliasToAnyVal = AnyVal +class Foo(a: Int) extends AliasToAnyVal // error From 25aa91d849c2d70fc6a9176ec21dc1a5ad12de65 Mon Sep 17 00:00:00 2001 From: Yoonjae Jeon Date: Wed, 23 Apr 2025 21:22:25 +0900 Subject: [PATCH 2/2] less allocation --- .../src/dotty/tools/dotc/typer/Checking.scala | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index ce1ed5d1e85e..cc43e950ec10 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -756,19 +756,18 @@ object Checking { case _ => report.error(ValueClassesMayNotContainInitalization(clazz), stat.srcPos) } + inline def checkParentIsNotAnyValAlias(): Unit = + cdef.rhs match { + case impl: Template => + val parent = impl.parents.head + if parent.symbol.isAliasType && parent.typeOpt.dealias =:= defn.AnyValType then + report.error(ValueClassCannotExtendAliasOfAnyVal(clazz, parent.symbol), cdef.srcPos) + case _ => () + } // We don't check synthesised enum anonymous classes that are generated from // enum extending a value class type (AnyVal or an alias of it) // The error message 'EnumMayNotBeValueClassesID' will take care of generating the error message (See #22236) if (clazz.isDerivedValueClass && !clazz.isEnumAnonymClass) { - val parentOpt = cdef.rhs match { - case impl: Template => - impl.parents.headOption - case _ => None - } - val isExtendingAliasOfAnyVal = parentOpt.exists { parent => - parent.symbol.isAliasType && parent.tpe.nn.dealias =:= defn.AnyValType - } - if (clazz.is(Trait)) report.error(CannotExtendAnyVal(clazz), clazz.srcPos) if clazz.is(Module) then @@ -779,8 +778,9 @@ object Checking { report.error(ValueClassesMayNotBeAbstract(clazz), clazz.srcPos) if (!clazz.isStatic) report.error(ValueClassesMayNotBeContainted(clazz), clazz.srcPos) - if (isExtendingAliasOfAnyVal) - report.error(ValueClassCannotExtendAliasOfAnyVal(clazz, parentOpt.get.symbol), clazz.srcPos) + + checkParentIsNotAnyValAlias() + if (isDerivedValueClass(underlyingOfValueClass(clazz.asClass).classSymbol)) report.error(ValueClassesMayNotWrapAnotherValueClass(clazz), clazz.srcPos) else {