Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome errors is an endless source of conflict.
https://github.com/scala/scala3/pull/23002/files


def errorNumber = ordinal - 1

Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
19 changes: 15 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
Copy link
Contributor

@som-snytt som-snytt Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid the Option (incurred for every VC), you could inline def checkSyntax(): Unit = rhs match etc error() and then use it below.

Edit: forgot to mention they have parent.typeOpt which is NoType in null case.

I haven't checked if we only need to check the first parent. I see that clazz.info does not help, and now I understand the remark that AnyVal is just syntax for inline class. The sibling tickets are for incorrectly not creating a companion (here) or creating one unnecessarily (the other ticket).

It's too bad to add this extra check or restriction. (Edit: in the sense that it is more than one LOC to address a limitation.) Oh, I wonder if the check really is that the value class has a companion. That is the bug? Right, it compiles if user adds a companion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clazz will always have a companion because of constructor proxies, but it only has a real companion if

clazz.companionModule.info.decls.exists(_.isConstructor)

So that might suffice as a check.

Speculatively, typer could add the companion tree at that point. I guess extmethods needs a tree to see the module, even though it is all symbolic fiction.


if (clazz.is(Trait))
report.error(CannotExtendAnyVal(clazz), clazz.srcPos)
if clazz.is(Module) then
Expand All @@ -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 {
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions tests/neg/i21918.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
type AliasToAnyVal = AnyVal
class Foo(a: Int) extends AliasToAnyVal // error
Loading