From a7db0cd7713f4c675788932faede06806a96e8e2 Mon Sep 17 00:00:00 2001
From: lidaisy
Date: Thu, 20 Nov 2025 21:10:04 -0500
Subject: [PATCH] Implemented valhalla value classes.
---
.../tools/backend/jvm/BCodeSkelBuilder.scala | 3 ++
.../tools/backend/jvm/BTypesFromSymbols.scala | 3 +-
.../tools/backend/jvm/BackendUtils.scala | 2 +-
.../jvm/PostProcessorFrontendAccess.scala | 3 ++
.../src/dotty/tools/dotc/ast/Desugar.scala | 6 ++-
compiler/src/dotty/tools/dotc/ast/tpd.scala | 2 +-
.../tools/dotc/config/ScalaSettings.scala | 1 +
.../dotty/tools/dotc/core/Definitions.scala | 1 +
.../tools/dotc/core/ImplicitNullInterop.scala | 2 +-
.../src/dotty/tools/dotc/core/StdNames.scala | 1 +
.../tools/dotc/core/SymDenotations.scala | 8 +++-
.../src/dotty/tools/dotc/core/SymUtils.scala | 3 +-
.../src/dotty/tools/dotc/core/Symbols.scala | 7 +++
.../dotty/tools/dotc/core/TypeErasure.scala | 4 +-
.../src/dotty/tools/dotc/core/Types.scala | 10 +++-
.../tools/dotc/reporting/ErrorMessageID.scala | 4 ++
.../dotty/tools/dotc/reporting/messages.scala | 24 ++++++++++
.../tools/dotc/transform/PostTyper.scala | 3 +-
.../src/dotty/tools/dotc/typer/Checking.scala | 40 ++++++++++++++++
.../src/dotty/tools/dotc/typer/Namer.scala | 2 +-
.../dotty/tools/dotc/typer/Synthesizer.scala | 4 +-
.../src/dotty/tools/dotc/typer/Typer.scala | 1 +
.../dotty/tools/dotc/CompilationTests.scala | 10 ++++
docs/_docs/reference/experimental/valhalla.md | 46 +++++++++++++++++++
library/src/scala/annotation/valhalla.scala | 13 ++++++
project/Build.scala | 3 +-
tests/valhalla/neg/ExplicitSelf.scala | 9 ++++
tests/valhalla/neg/ExtendingConcreteVVC.scala | 11 +++++
tests/valhalla/neg/ExtendsAnyRef.scala | 29 ++++++++++++
tests/valhalla/neg/IncorrectDeclaration.scala | 7 +++
tests/valhalla/neg/SecondaryCtor.scala | 5 ++
tests/valhalla/neg/VVCMutableParam.scala | 7 +++
...llaExtendsNonValhallaAnyTraitWithVar.scala | 19 ++++++++
tests/valhalla/neg/ValhallaMutableField.scala | 11 +++++
tests/valhalla/neg/ValhallaWithField.scala | 11 +++++
tests/valhalla/pos/AbstractVVC.scala | 6 +++
tests/valhalla/pos/CanEqual.scala | 16 +++++++
tests/valhalla/pos/ConcreteVVC.scala | 6 +++
tests/valhalla/pos/ExplicitSelf.scala | 8 ++++
tests/valhalla/pos/ExplicitSelf2.scala | 7 +++
.../pos/IdentClassExtendsValhallaTrait.scala | 8 ++++
tests/valhalla/pos/NestedClasses.scala | 16 +++++++
tests/valhalla/pos/TraitExtendsAny.scala | 4 ++
.../valhalla/pos/VVCExtendsAbstractVVC.scala | 11 +++++
.../pos/VVCExtendsValhallaTrait.scala | 9 ++++
.../ValhallaExtendsNonValhallaAnyTrait.scala | 19 ++++++++
46 files changed, 410 insertions(+), 15 deletions(-)
create mode 100644 docs/_docs/reference/experimental/valhalla.md
create mode 100644 library/src/scala/annotation/valhalla.scala
create mode 100644 tests/valhalla/neg/ExplicitSelf.scala
create mode 100644 tests/valhalla/neg/ExtendingConcreteVVC.scala
create mode 100644 tests/valhalla/neg/ExtendsAnyRef.scala
create mode 100644 tests/valhalla/neg/IncorrectDeclaration.scala
create mode 100644 tests/valhalla/neg/SecondaryCtor.scala
create mode 100644 tests/valhalla/neg/VVCMutableParam.scala
create mode 100644 tests/valhalla/neg/ValhallaExtendsNonValhallaAnyTraitWithVar.scala
create mode 100644 tests/valhalla/neg/ValhallaMutableField.scala
create mode 100644 tests/valhalla/neg/ValhallaWithField.scala
create mode 100644 tests/valhalla/pos/AbstractVVC.scala
create mode 100644 tests/valhalla/pos/CanEqual.scala
create mode 100644 tests/valhalla/pos/ConcreteVVC.scala
create mode 100644 tests/valhalla/pos/ExplicitSelf.scala
create mode 100644 tests/valhalla/pos/ExplicitSelf2.scala
create mode 100644 tests/valhalla/pos/IdentClassExtendsValhallaTrait.scala
create mode 100644 tests/valhalla/pos/NestedClasses.scala
create mode 100644 tests/valhalla/pos/TraitExtendsAny.scala
create mode 100644 tests/valhalla/pos/VVCExtendsAbstractVVC.scala
create mode 100644 tests/valhalla/pos/VVCExtendsValhallaTrait.scala
create mode 100644 tests/valhalla/pos/ValhallaExtendsNonValhallaAnyTrait.scala
diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala
index f8185098390a..5eb7d571a710 100644
--- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala
+++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala
@@ -415,6 +415,9 @@ trait BCodeSkelBuilder extends BCodeHelpers {
)
cnode.fields.add(jfield)
emitAnnotations(jfield, f.annotations)
+
+ if(f.denot.info.isValhallaValueClassType)
+ cnode.visitLoadableDescriptors(jfield.desc)
}
} // end of method addClassFields()
diff --git a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala
index 68c6add4ef13..2125bc4b2e9b 100644
--- a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala
+++ b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala
@@ -304,7 +304,7 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I, val frontendAcce
.addFlagIf(sym.isStaticMember, ACC_STATIC)
.addFlagIf(sym.is(Bridge), ACC_BRIDGE | ACC_SYNTHETIC)
.addFlagIf(sym.is(Artifact), ACC_SYNTHETIC)
- .addFlagIf(sym.isClass && !sym.isInterface, ACC_SUPER)
+ .addFlagIf(sym.isClass && !sym.isInterface && !sym.isValhallaValueClass, ACC_SUPER)
.addFlagIf(sym.isAllOf(JavaEnum), ACC_ENUM)
.addFlagIf(sym.is(JavaVarargs), ACC_VARARGS)
.addFlagIf(sym.is(Synchronized), ACC_SYNCHRONIZED)
@@ -319,5 +319,6 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I, val frontendAcce
.addFlagIf(sym.hasAnnotation(TransientAttr), ACC_TRANSIENT)
.addFlagIf(sym.hasAnnotation(VolatileAttr), ACC_VOLATILE)
.addFlagIf(!sym.is(Mutable), ACC_FINAL)
+ .addFlagIf(sym.denot.owner.isValhallaValueClass, ACC_STRICT)
}
}
diff --git a/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala b/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala
index 4322a306f411..22fce72d948c 100644
--- a/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala
+++ b/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala
@@ -19,7 +19,7 @@ class BackendUtils(val postProcessor: PostProcessor) {
import bTypes.*
import coreBTypes.jliLambdaMetaFactoryAltMetafactoryHandle
- lazy val classfileVersion: Int = BackendUtils.classfileVersionMap(compilerSettings.target.toInt)
+ lazy val classfileVersion: Int = if compilerSettings.experimental then (69 + (65535 << 16)) else BackendUtils.classfileVersionMap(compilerSettings.target.toInt)
lazy val extraProc: Int = {
import GenBCodeOps.addFlagIf
diff --git a/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala b/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala
index 3e28d2a949cb..f1bad91abce6 100644
--- a/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala
+++ b/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala
@@ -52,6 +52,7 @@ object PostProcessorFrontendAccess {
sealed trait CompilerSettings {
def debug: Boolean
def target: String // javaOutputVersion
+ def experimental: Boolean
def dumpClassesDirectory: Option[String]
def outputDirectory: AbstractFile
@@ -120,6 +121,8 @@ object PostProcessorFrontendAccess {
release
case (None, None) => "8" // least supported version by default
+ override val experimental = s.YvalueClasses.value
+
override val debug: Boolean = ctx.debug
override val dumpClassesDirectory: Option[String] = s.Xdumpclasses.valueSetByUser
override val outputDirectory: AbstractFile = s.outputDir.value
diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala
index ab77350be26c..2c79dfd9975d 100644
--- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala
+++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala
@@ -703,6 +703,10 @@ object desugar {
def isEnumCase = mods.isEnumCase
def isNonEnumCase = !isEnumCase && (isCaseClass || isCaseObject)
val isValueClass = parents.nonEmpty && isAnyVal(parents.head)
+ val isValhallaVC = isValueClass && mods.annotations.exists{ annot => annot match
+ case Apply(Select(New(Ident(annotName)), _), _) => annotName eq tpnme.valhalla
+ case _ => false
+ }
// This is not watertight, but `extends AnyVal` will be replaced by `inline` later.
val caseClassInScala2Library = isCaseClass && Feature.shouldBehaveAsScala2
@@ -1028,7 +1032,7 @@ object desugar {
}
else if (companionMembers.nonEmpty || companionDerived.nonEmpty || isEnum)
companionDefs(anyRef, companionMembers)
- else if isValueClass && !isObject then
+ else if isValueClass && !isObject && !isValhallaVC then
companionDefs(anyRef, Nil)
else Nil
diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala
index 23dde1139c03..71d538544204 100644
--- a/compiler/src/dotty/tools/dotc/ast/tpd.scala
+++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala
@@ -552,7 +552,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def wrapArrayMethodName(elemtp: Type)(using Context): TermName = {
val elemCls = elemtp.classSymbol
if (elemCls.isPrimitiveValueClass) nme.wrapXArray(elemCls.name)
- else if (elemCls.derivesFrom(defn.ObjectClass) && !elemCls.isNotRuntimeClass) nme.wrapRefArray
+ else if ((elemCls.derivesFrom(defn.ObjectClass) || elemCls.isValhallaValueClass) && !elemCls.isNotRuntimeClass) nme.wrapRefArray
else nme.genericWrapArray
}
diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
index 129a77e7e43f..586d7fd3f55e 100644
--- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
+++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
@@ -474,6 +474,7 @@ private sealed trait YSettings:
val YccLog: Setting[Boolean] = BooleanSetting(ForkSetting, "Ycc-log", "Used in conjunction with captureChecking language import, print tracing and debug info")
val YccVerbose: Setting[Boolean] = BooleanSetting(ForkSetting, "Ycc-verbose", "Print root capabilities with more details")
val YccPrintSetup: Setting[Boolean] = BooleanSetting(ForkSetting, "Ycc-print-setup", "Used in conjunction with captureChecking language import, print trees after cc.Setup phase")
+ val YvalueClasses: Setting[Boolean] = BooleanSetting(ForkSetting, "Yvalue-classes", "value classes")
/** Area-specific debug output */
val YexplainLowlevel: Setting[Boolean] = BooleanSetting(ForkSetting, "Yexplain-lowlevel", "When explaining type errors, show types at a lower level.")
diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala
index 8d305eef16e1..8dbafd6b240d 100644
--- a/compiler/src/dotty/tools/dotc/core/Definitions.scala
+++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala
@@ -1113,6 +1113,7 @@ class Definitions {
@tu lazy val PublicInBinaryAnnot: ClassSymbol = requiredClass("scala.annotation.publicInBinary")
@tu lazy val WitnessNamesAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WitnessNames")
@tu lazy val StableNullAnnot: ClassSymbol = requiredClass("scala.annotation.stableNull")
+ @tu lazy val ValhallaAnnot: ClassSymbol = requiredClass("scala.annotation.valhalla")
@tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable")
diff --git a/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala b/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala
index 0426665eaca1..588550812dbd 100644
--- a/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala
+++ b/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala
@@ -115,7 +115,7 @@ object ImplicitNullInterop:
case tp: TypeRef =>
// We don't modify value types because they're non-nullable even in Java.
val isValueOrSpecialClass =
- tp.symbol.isValueClass
+ (tp.symbol.isValueClass && !tp.symbol.isValhallaValueClass)
|| tp.isRef(defn.NullClass)
|| tp.isRef(defn.NothingClass)
|| tp.isRef(defn.UnitClass)
diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala
index 3213be389c9d..c229b3bc026f 100644
--- a/compiler/src/dotty/tools/dotc/core/StdNames.scala
+++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala
@@ -664,6 +664,7 @@ object StdNames {
val valueOf : N = "valueOf"
val fromOrdinal: N = "fromOrdinal"
val values: N = "values"
+ val valhalla: N = "valhalla"
val view_ : N = "view"
val varargGetter : N = "varargGetter"
val wait_ : N = "wait"
diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala
index 3b198ea4dbaa..1bb30a500f69 100644
--- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala
+++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala
@@ -893,6 +893,9 @@ object SymDenotations {
/** Is this symbol a class that extends `AnyVal`? Overridden in ClassDenotation */
def isValueClass(using Context): Boolean = false
+ /** Is this symbol a class that ...? Overridden in ClassDenotation */
+ def isValhallaValueClass(using Context): Boolean = false
+
/** Is this symbol a class of which `null` is a value? */
final def isNullableClass(using Context): Boolean =
if ctx.mode.is(Mode.SafeNulls) && !ctx.phase.erasedTypes
@@ -904,7 +907,7 @@ object SymDenotations {
* but it becomes nullable after erasure.
*/
final def isNullableClassAfterErasure(using Context): Boolean =
- isClass && !isValueClass && !is(ModuleClass) && symbol != defn.NothingClass
+ isClass && (!isValueClass || isValhallaValueClass) && !is(ModuleClass) && symbol != defn.NothingClass
/** Is `pre` the same as C.this, where C is exactly the owner of this symbol,
* or, if this symbol is protected, a subclass of the owner?
@@ -2106,6 +2109,9 @@ object SymDenotations {
// after Erasure and to avoid cyclic references caused by forcing denotations
atPhase(di.validFor.firstPhaseId)(di.derivesFrom(anyVal))
+ final override def isValhallaValueClass(using Context): Boolean =
+ hasAnnotation(defn.ValhallaAnnot) && (symbol.isUniversalTrait || isValueClass)
+
/** Enter a symbol in current scope, and future scopes of same denotation.
* Note: We require that this does not happen after the first time
* someone does a findMember on a subclass.
diff --git a/compiler/src/dotty/tools/dotc/core/SymUtils.scala b/compiler/src/dotty/tools/dotc/core/SymUtils.scala
index 435320fec568..bf49c4274fbb 100644
--- a/compiler/src/dotty/tools/dotc/core/SymUtils.scala
+++ b/compiler/src/dotty/tools/dotc/core/SymUtils.scala
@@ -84,7 +84,8 @@ class SymUtils:
!d.isRefinementClass &&
d.isValueClass &&
(d.initial.symbol ne defn.AnyValClass) && // Compare the initial symbol because AnyVal does not exist after erasure
- !d.isPrimitiveValueClass
+ !d.isPrimitiveValueClass &&
+ !d.isValhallaValueClass
}
def isContextBoundCompanion(using Context): Boolean =
diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala
index 852c6ce2cc04..e5694f024a46 100644
--- a/compiler/src/dotty/tools/dotc/core/Symbols.scala
+++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala
@@ -217,6 +217,10 @@ object Symbols extends SymUtils {
final def isStatic(using Context): Boolean =
lastDenot.initial.isStatic
+ /** Overridden by ClassSymbol */
+ def isValhallaValueClass(using Context): Boolean =
+ false
+
/** This symbol entered into owner's scope (owner must be a class). */
final def entered(using Context): this.type = {
if (this.owner.isClass) {
@@ -537,6 +541,9 @@ object Symbols extends SymUtils {
mySource
}
+ override final def isValhallaValueClass(using Context): Boolean =
+ classDenot.isValhallaValueClass
+
final def classDenot(using Context): ClassDenotation =
denot.asInstanceOf[ClassDenotation]
diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala
index c29b971f1a5a..2644ddb78d40 100644
--- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala
+++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala
@@ -411,9 +411,9 @@ object TypeErasure {
def erasedLub(tp1: Type, tp2: Type)(using Context): Type = {
// We need to short-circuit the following 2 case because the regular lub logic in the else relies on
// the class hierarchy, which doesn't properly capture `Nothing`/`Null` subtyping behaviour.
- if tp1.isRef(defn.NothingClass) || (tp1.isRef(defn.NullClass) && tp2.derivesFrom(defn.ObjectClass)) then
+ if tp1.isRef(defn.NothingClass) || (tp1.isRef(defn.NullClass) && (tp2.derivesFrom(defn.ObjectClass) || tp2.isValhallaValueClassType)) then
tp2 // After erasure, Nothing | T is just T and Null | C is just C, if C is a reference type.
- else if tp2.isRef(defn.NothingClass) || (tp2.isRef(defn.NullClass) && tp1.derivesFrom(defn.ObjectClass)) then
+ else if tp2.isRef(defn.NothingClass) || (tp2.isRef(defn.NullClass) && (tp1.derivesFrom(defn.ObjectClass) || tp1.isValhallaValueClassType)) then
tp1 // After erasure, T | Nothing is just T and C | Null is just C, if C is a reference type.
else tp1 match {
case JavaArrayType(elem1) =>
diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala
index dc54d14b0d4b..3ca1b8a2953a 100644
--- a/compiler/src/dotty/tools/dotc/core/Types.scala
+++ b/compiler/src/dotty/tools/dotc/core/Types.scala
@@ -315,6 +315,14 @@ object Types extends TypeUtils {
loop(this)
}
+ def isValhallaValueClassType(using Context): Boolean =
+ this match
+ case tp: TypeRef =>
+ val sym = tp.symbol
+ if (sym.isClass) sym.isValhallaValueClass else false
+ case _ =>
+ false
+
def isFromJavaObject(using Context): Boolean =
isRef(defn.ObjectClass) && (typeSymbol eq defn.FromJavaObjectSymbol)
@@ -650,7 +658,7 @@ object Types extends TypeUtils {
def tp2Null = tp.tp2.hasClassSymbol(defn.NullClass)
if ctx.erasedTypes && (tp1Null || tp2Null) then
val otherSide = if tp1Null then tp.tp2.classSymbol else tp.tp1.classSymbol
- if otherSide.isValueClass then defn.AnyClass else otherSide
+ if (otherSide.isValueClass && !otherSide.isValhallaValueClass) then defn.AnyClass else otherSide
else
tp.join.classSymbol
case _: JavaArrayType =>
diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala
index 5f5a0c01db17..fa26ccaf863d 100644
--- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala
+++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala
@@ -236,6 +236,10 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
case DefaultShadowsGivenID // errorNumber: 220
case RecurseWithDefaultID // errorNumber: 221
case EncodedPackageNameID // errorNumber: 222
+ case ValueClassCannotExtendIdentityClassID // errorNumber: 223
+ case ValueClassMustNotExtendTraitWithMutableFieldID // errorNumber: 224
+ case IncorrectValueClassDeclarationID // errorNumber: 225
+ case ValhallaTraitsMayNotHaveSelfTypesWithVarsID // errorNumber: 226
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 159b3c9a905e..7bf5839f6e38 100644
--- a/compiler/src/dotty/tools/dotc/reporting/messages.scala
+++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala
@@ -3749,3 +3749,27 @@ final class EncodedPackageName(name: Name)(using Context) extends SyntaxMsg(Enco
|or `myfile-test.scala` can produce encoded names for the generated package objects.
|
|In this case, the name `$name` is encoded as `${name.encode}`."""
+
+class ValueClassCannotExtendIdentityClass(valueClass: Symbol, parent: Symbol)(using Context)
+ extends SyntaxMsg(ValueClassCannotExtendIdentityClassID) {
+ def msg(using Context) = i"""A Valhalla value class cannot extend Identity Class ($parent)}"""
+ def explain(using Context) = ""
+}
+
+class ValueClassMustNotExtendTraitWithMutableField(parentClass: Symbol, field: Symbol)(using Context)
+ extends SyntaxMsg(ValueClassMustNotExtendTraitWithMutableFieldID) {
+ def msg(using Context) = i"""A Valhalla value class/trait cannot extend $parentClass with mutable field ($field)"""
+ def explain(using Context) = ""
+}
+
+class IncorrectValueClassDeclaration(isClass: Boolean)(using Context)
+ extends SyntaxMsg(IncorrectValueClassDeclarationID) {
+ def msg(using Context) = i"""Incorrect Valhalla value class declaration: Valhalla ${if isClass then "value classes" else "traits"} must extend ${if isClass then "AnyVal" else "Any"}."""
+ def explain(using Context) = "Valhalla Value Classes and Traits need to extend AnyVal or Any respectively."
+}
+
+class ValhallaTraitsMayNotHaveSelfTypesWithVars(selfTypeSym: Symbol, field: Symbol)(using Context)
+ extends SyntaxMsg(ValhallaTraitsMayNotHaveSelfTypesWithVarsID) {
+ def msg(using Context) = i"""A Valhalla trait may not have a self type ($selfTypeSym) with mutable field ($field)."""
+ def explain(using Context) = ""
+}
\ No newline at end of file
diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala
index 62561ad2d467..595fbb2f629e 100644
--- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala
+++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala
@@ -444,7 +444,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
val builder =
if defn.ScalaValueClasses().contains(elemCls) then
makeBuilder(s"of${elemCls.name}")
- else if elemCls.derivesFrom(defn.ObjectClass) then
+ else if elemCls.derivesFrom(defn.ObjectClass) || elemCls.isValhallaValueClass then
makeBuilder("ofRef").appliedToType(elemType)
else
makeBuilder("generic").appliedToType(elemType)
@@ -607,6 +607,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
val reference = ctx.settings.sourceroot.value
val relativePath = util.SourceFile.relativePath(ctx.compilationUnit.source, reference)
sym.addAnnotation(Annotation(defn.SourceFileAnnot, Literal(Constants.Constant(relativePath)), tree.span))
+ Checking.checkValhallaValueClass(tree, sym, tree.rhs.asInstanceOf[Template].body)
else
if !sym.is(Param) && !sym.owner.isOneOf(AbstractOrTrait) then
Checking.checkGoodBounds(tree.symbol)
diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala
index d975e2341111..359a57fd0d16 100644
--- a/compiler/src/dotty/tools/dotc/typer/Checking.scala
+++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala
@@ -898,6 +898,46 @@ object Checking {
}
}
+ // Verify classes and traits with the valhalla annotation meet the requirements
+ def checkValhallaValueClass(cdef: TypeDef, clazz: Symbol, stats: List[Tree])(using Context): Unit = {
+ def checkValueClassMember(stat: Tree) = stat match {
+ case _: ValDef =>
+ if !stat.symbol.is(ParamAccessor) then
+ report.error(ValueClassesMayNotDefineNonParameterField(clazz, stat.symbol), stat.srcPos)
+ if stat.symbol.is(Mutable) then
+ report.error(ValueClassParameterMayNotBeAVar(clazz, stat.symbol), stat.srcPos)
+ case _: DefDef if stat.symbol.isConstructor =>
+ report.error(ValueClassesMayNotDefineASecondaryConstructor(clazz, stat.symbol), stat.srcPos)
+ case _ =>
+ // ok
+ }
+
+ inline def checkParents(): Unit = {
+ clazz.asClass.baseClasses.foreach(c => {
+ val parentSym = if(c.isConstructor) then c.owner else c
+
+ if (parentSym.asClass.baseClasses.contains(defn.ObjectClass))
+ report.error(ValueClassCannotExtendIdentityClass(clazz, parentSym), cdef.srcPos)
+ if (((clazz.isClass && (parentSym ne defn.AnyValClass)) || (clazz.is(Trait) && (parentSym ne defn.AnyClass))) && !parentSym.isValhallaValueClass)
+ parentSym.asClass.classInfo.decls.foreach(f => if f.isMutableVar then report.error(ValueClassMustNotExtendTraitWithMutableField(parentSym, f), cdef.srcPos))
+ })
+ }
+
+ inline def checkSelfType(): Unit = {
+ if(clazz.asClass.givenSelfType.exists)
+ val selfTypeSym = clazz.asClass.givenSelfType.classSymbol
+
+ if(selfTypeSym.exists && !selfTypeSym.isValhallaValueClass)
+ selfTypeSym.asClass.classInfo.decls.foreach(f => if f.isMutableVar then report.error(ValhallaTraitsMayNotHaveSelfTypesWithVars(selfTypeSym, f), cdef.srcPos))
+ }
+ if(clazz.hasAnnotation(defn.ValhallaAnnot))
+ if (clazz.asClass.parentSyms.contains(defn.ObjectClass))
+ report.error(IncorrectValueClassDeclaration(clazz.isClass), cdef.srcPos)
+ checkParents()
+ checkSelfType()
+ stats.foreach(checkValueClassMember)
+ }
+
/** Check the inline override methods only use inline parameters if they override an inline parameter. */
def checkInlineOverrideParameters(sym: Symbol)(using Context): Unit =
lazy val params = sym.paramSymss.flatten
diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala
index 82c25fbed242..b08a6bfd5ceb 100644
--- a/compiler/src/dotty/tools/dotc/typer/Namer.scala
+++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala
@@ -1771,7 +1771,7 @@ class Namer { typer: Typer =>
tempInfo = null // The temporary info can now be garbage-collected
Checking.checkWellFormed(cls)
- if (isDerivedValueClass(cls)) cls.setFlag(Final)
+ if (isDerivedValueClass(cls) || (cls.isValhallaValueClass && !cls.is(Abstract) && !cls.is(Trait))) cls.setFlag(Final)
cls.info = avoidPrivateLeaks(cls)
cls.baseClasses.foreach(_.invalidateBaseTypeCache()) // we might have looked before and found nothing
cls.invalidateMemberCaches() // we might have checked for a member when parents were not known yet.
diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
index 7bfda4e4b5fa..f5753c09f81a 100644
--- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
+++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
@@ -204,9 +204,9 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
else if cls2.isPrimitiveValueClass then
cmpWithBoxed(cls2, cls1)
else if cls1 == defn.NullClass then
- cls1 == cls2 || cls2.derivesFrom(defn.ObjectClass)
+ cls1 == cls2 || cls2.derivesFrom(defn.ObjectClass) || cls2.isValhallaValueClass
else if cls2 == defn.NullClass then
- cls1.derivesFrom(defn.ObjectClass)
+ cls1.derivesFrom(defn.ObjectClass) || cls1.isValhallaValueClass
else
cls1 == defn.NothingClass || cls2 == defn.NothingClass
end canComparePredefinedClasses
diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala
index 210104c4ab93..f8024074c333 100644
--- a/compiler/src/dotty/tools/dotc/typer/Typer.scala
+++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala
@@ -3446,6 +3446,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
if cls.is(ModuleClass)
&& effectiveOwner.is(Trait)
&& !effectiveOwner.derivesFrom(defn.ObjectClass)
+ && !effectiveOwner.isValhallaValueClass
then
report.error(em"$cls cannot be defined in universal $effectiveOwner", cdef.srcPos)
diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala
index d2f6b8265c6f..5c286ab60104 100644
--- a/compiler/test/dotty/tools/dotc/CompilationTests.scala
+++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala
@@ -377,6 +377,16 @@ class CompilationTests {
).checkRuns()
}
+
+ // Valhalla Value Classes tests
+ @Test def checkValhallaValueClasses: Unit = {
+ implicit val testGroup: TestGroup = TestGroup("checkValhallaVC")
+ val options = defaultOptions.and("-Yvalue-classes")
+ val valhallaAnnotationPath = "library/target/scala-library-nonbootstrapped/scala-library-3.8.1-RC1-bin-SNAPSHOT-nonbootstrapped.jar"
+
+ compileFilesInDir("tests/valhalla/pos", options.withClasspath(valhallaAnnotationPath)).checkCompile()
+ compileFilesInDir("tests/valhalla/neg", options.withClasspath(valhallaAnnotationPath)).checkExpectedErrors()
+ }
}
object CompilationTests extends ParallelTesting {
diff --git a/docs/_docs/reference/experimental/valhalla.md b/docs/_docs/reference/experimental/valhalla.md
new file mode 100644
index 000000000000..6e5aa8a7f7cd
--- /dev/null
+++ b/docs/_docs/reference/experimental/valhalla.md
@@ -0,0 +1,46 @@
+---
+layout: doc-page
+title: "Valhalla Value Classes and Traits"
+nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/valhalla.html
+---
+
+## Valhalla Value Classes
+
+Valhalla value classes are the Scala equivalence of Java's Project Valhalla's value classes (see [JEP 401](https://openjdk.org/jeps/401)). When used with the Project Valhalla JVM, Valhalla value classes are optimized.
+
+Valhalla value classes extend AnyVal and have a `valhalla` annotation. Valhalla value classes cannot have non-parameter fields and cannot have auxilliary constructors.
+
+Valhalla value classes do not have object identity -- two valhalla value classes are equal when their fields are the same.
+
+```scala
+import scala.annotation.valhalla
+
+@valhalla class ValhallaValueClass(val x: Int, val y: Int) extends AnyVal
+```
+
+Valhalla value classes are implicitly final and cannot be extended unless it is abstract. Its fields are immutable and cannot be lazy.
+
+Valhalla value classes can extend `AnyVal`, universal traits, or abstract valhalla value classes.
+
+## Valhalla Traits
+
+Valhalla Traits are Universal Traits (traits that extend Any) with a `valhalla` annotation.
+
+Like Valhalla value classes, any Valhalla trait must have immutable fields only.
+
+Valhalla traits can extend `Any` or universal traits.
+
+```scala
+import scala.annotation.valhalla
+
+@valhalla trait(val x: Int, val y: Int) ValhallaTrait extends Any
+
+```
+
+## Using Explicit Self with Valhalla
+
+Valhalla traits can have self-type of any trait without mutable fields.
+
+## CanEqual with Valhalla
+
+Valhalla value classes can be null, so the CanEqual of `null` and a valhalla value class returns `true`.
\ No newline at end of file
diff --git a/library/src/scala/annotation/valhalla.scala b/library/src/scala/annotation/valhalla.scala
new file mode 100644
index 000000000000..4067e0aa882b
--- /dev/null
+++ b/library/src/scala/annotation/valhalla.scala
@@ -0,0 +1,13 @@
+package scala.annotation
+
+import scala.language.`2.13`
+
+/** A class annotation which verifies that the class will be a
+ * Project Valhalla value class.
+ *
+ * If it is present, the compiler will issue an error if the class
+ * does not satisfy the requirements of the Project Valhalla value
+ * class as specified in https://openjdk.org/jeps/401.
+ */
+
+final class valhalla extends StaticAnnotation
diff --git a/project/Build.scala b/project/Build.scala
index 13c22fed541a..a70d66dfd00f 100644
--- a/project/Build.scala
+++ b/project/Build.scala
@@ -199,7 +199,8 @@ object Build {
"-language:implicitConversions",
s"--java-output-version:${Versions.minimumJVMVersion}",
"-Yexplicit-nulls",
- "-Wsafe-init"
+ "-Wsafe-init",
+ "-Yvalue-classes"
),
(Compile / compile / javacOptions) ++= Seq(
diff --git a/tests/valhalla/neg/ExplicitSelf.scala b/tests/valhalla/neg/ExplicitSelf.scala
new file mode 100644
index 000000000000..3945143d0dac
--- /dev/null
+++ b/tests/valhalla/neg/ExplicitSelf.scala
@@ -0,0 +1,9 @@
+import scala.annotation.valhalla
+
+trait RecordLabel:
+ var name: String = "Sony"
+
+@valhalla trait SoundProduction extends Any { // error
+ this: RecordLabel =>
+ val prodName = "prod " + name
+}
\ No newline at end of file
diff --git a/tests/valhalla/neg/ExtendingConcreteVVC.scala b/tests/valhalla/neg/ExtendingConcreteVVC.scala
new file mode 100644
index 000000000000..5a311d2f1296
--- /dev/null
+++ b/tests/valhalla/neg/ExtendingConcreteVVC.scala
@@ -0,0 +1,11 @@
+import scala.annotation.valhalla
+
+@valhalla
+class ConcreteVVC extends AnyVal {
+ def addOne(x: Int): Int = x + 1
+}
+
+@valhalla
+class VVC extends ConcreteVVC // error
+
+class Ident extends ConcreteVVC // error
\ No newline at end of file
diff --git a/tests/valhalla/neg/ExtendsAnyRef.scala b/tests/valhalla/neg/ExtendsAnyRef.scala
new file mode 100644
index 000000000000..815e137c7c42
--- /dev/null
+++ b/tests/valhalla/neg/ExtendsAnyRef.scala
@@ -0,0 +1,29 @@
+import scala.annotation.valhalla
+
+/**
+ * Valhalla Class Extends AnyRef Class
+ * */
+abstract class Abs {
+ def addOne(x : Int) : Int
+}
+
+@valhalla
+class ValhallaAnnotWithoutExtendingAnyVal extends Abs{ // error
+ def addOne(x : Int) : Int = x + 1
+}
+
+/**
+ * Valhalla Trait Extends AnyRef 2
+ * */
+@valhalla
+trait Uni extends Any
+
+@valhalla
+trait AnyRefTrait extends Uni // error
+
+trait Trait:
+ def add(x:Int, y:Int): Int
+
+@valhalla
+class VVC extends AnyVal with Trait: // error
+ def add(x:Int, y:Int): Int = x + y
\ No newline at end of file
diff --git a/tests/valhalla/neg/IncorrectDeclaration.scala b/tests/valhalla/neg/IncorrectDeclaration.scala
new file mode 100644
index 000000000000..f746f5afdc84
--- /dev/null
+++ b/tests/valhalla/neg/IncorrectDeclaration.scala
@@ -0,0 +1,7 @@
+import scala.annotation.valhalla
+
+@valhalla
+class A // error
+
+@valhalla
+trait T // error
\ No newline at end of file
diff --git a/tests/valhalla/neg/SecondaryCtor.scala b/tests/valhalla/neg/SecondaryCtor.scala
new file mode 100644
index 000000000000..0075c64bfe26
--- /dev/null
+++ b/tests/valhalla/neg/SecondaryCtor.scala
@@ -0,0 +1,5 @@
+class SecondaryCtor(val x: Int) extends AnyVal {
+ def this(a: Int, b: Int) = { // error
+ this(a + b)
+ }
+}
diff --git a/tests/valhalla/neg/VVCMutableParam.scala b/tests/valhalla/neg/VVCMutableParam.scala
new file mode 100644
index 000000000000..bce018a325f5
--- /dev/null
+++ b/tests/valhalla/neg/VVCMutableParam.scala
@@ -0,0 +1,7 @@
+import scala.annotation.valhalla
+
+@valhalla
+class VVCMutableField(var a: Int) extends AnyVal // error
+
+@valhalla
+trait VTMutableField(var a: Int) extends Any // error
\ No newline at end of file
diff --git a/tests/valhalla/neg/ValhallaExtendsNonValhallaAnyTraitWithVar.scala b/tests/valhalla/neg/ValhallaExtendsNonValhallaAnyTraitWithVar.scala
new file mode 100644
index 000000000000..be080ef495d9
--- /dev/null
+++ b/tests/valhalla/neg/ValhallaExtendsNonValhallaAnyTraitWithVar.scala
@@ -0,0 +1,19 @@
+import scala.annotation.valhalla
+
+/**
+ * Valhalla classes and traits cannot extend non-Valhalla classes or traits
+ */
+
+trait AnyTrait extends Any:
+ var a : Int = 2
+ def add(x:Int, y:Int): Int
+
+
+@valhalla
+trait TraitExtendsAnyTrait extends Any with AnyTrait: // error
+ def addOne(x: Int): Int
+
+@valhalla
+class VVC extends AnyVal with AnyTrait: // error
+ def add(x:Int, y:Int): Int = x + y
+ def addOne(x: Int): Int = x + 1
\ No newline at end of file
diff --git a/tests/valhalla/neg/ValhallaMutableField.scala b/tests/valhalla/neg/ValhallaMutableField.scala
new file mode 100644
index 000000000000..a907e770968c
--- /dev/null
+++ b/tests/valhalla/neg/ValhallaMutableField.scala
@@ -0,0 +1,11 @@
+import scala.annotation.valhalla
+
+@valhalla
+trait VTMutableField extends Any {
+ var a = 5 // error
+}
+
+@valhalla
+class VVCMutableField extends AnyVal {
+ var a = 5 // error
+}
\ No newline at end of file
diff --git a/tests/valhalla/neg/ValhallaWithField.scala b/tests/valhalla/neg/ValhallaWithField.scala
new file mode 100644
index 000000000000..b982c4f4e191
--- /dev/null
+++ b/tests/valhalla/neg/ValhallaWithField.scala
@@ -0,0 +1,11 @@
+import scala.annotation.valhalla
+
+@valhalla
+trait VTMutableField extends Any {
+ val a = 5 // error
+}
+
+@valhalla
+class VVCMutableField extends AnyVal {
+ val a = 5 // error
+}
\ No newline at end of file
diff --git a/tests/valhalla/pos/AbstractVVC.scala b/tests/valhalla/pos/AbstractVVC.scala
new file mode 100644
index 000000000000..7dd0cc6d4fb8
--- /dev/null
+++ b/tests/valhalla/pos/AbstractVVC.scala
@@ -0,0 +1,6 @@
+import scala.annotation.valhalla
+
+@valhalla
+abstract class AbstractVVC(val a: Int, val b: Int) extends AnyVal {
+ def addOne(x: Int):Int = x
+}
\ No newline at end of file
diff --git a/tests/valhalla/pos/CanEqual.scala b/tests/valhalla/pos/CanEqual.scala
new file mode 100644
index 000000000000..ae782d2f64af
--- /dev/null
+++ b/tests/valhalla/pos/CanEqual.scala
@@ -0,0 +1,16 @@
+import scala.language.strictEquality
+import scala.annotation.valhalla
+
+// Motivation:
+// val x = ... // of type T
+// val y = ... // of type S, but should be T
+// x == y // typechecks, will always yield false
+
+@valhalla
+class T(val a: Int) extends AnyVal
+
+class Main:
+ def main = {
+ val t = new T(1)
+ t == null
+ }
\ No newline at end of file
diff --git a/tests/valhalla/pos/ConcreteVVC.scala b/tests/valhalla/pos/ConcreteVVC.scala
new file mode 100644
index 000000000000..db52ea97394c
--- /dev/null
+++ b/tests/valhalla/pos/ConcreteVVC.scala
@@ -0,0 +1,6 @@
+import scala.annotation.valhalla
+
+@valhalla
+class ConcreteVVC(val a: Int, val b: Int) extends AnyVal {
+ def addOne(x: Int): Int = x + 1
+}
\ No newline at end of file
diff --git a/tests/valhalla/pos/ExplicitSelf.scala b/tests/valhalla/pos/ExplicitSelf.scala
new file mode 100644
index 000000000000..b38972733945
--- /dev/null
+++ b/tests/valhalla/pos/ExplicitSelf.scala
@@ -0,0 +1,8 @@
+import scala.annotation.valhalla
+
+trait Animal(val cuteness: Int):
+ val happiness = 100
+ def speak: Unit
+
+@valhalla trait Mammal(val furColour: Tuple) extends Any:
+ this: Animal =>
diff --git a/tests/valhalla/pos/ExplicitSelf2.scala b/tests/valhalla/pos/ExplicitSelf2.scala
new file mode 100644
index 000000000000..0a05bc5e1317
--- /dev/null
+++ b/tests/valhalla/pos/ExplicitSelf2.scala
@@ -0,0 +1,7 @@
+import scala.annotation.valhalla
+
+@valhalla trait Animal(val cuteness: Int) extends Any:
+ def speak: Unit
+
+@valhalla trait Mammal(val furColour: Tuple) extends Any:
+ this: Animal =>
diff --git a/tests/valhalla/pos/IdentClassExtendsValhallaTrait.scala b/tests/valhalla/pos/IdentClassExtendsValhallaTrait.scala
new file mode 100644
index 000000000000..0a50b57db23d
--- /dev/null
+++ b/tests/valhalla/pos/IdentClassExtendsValhallaTrait.scala
@@ -0,0 +1,8 @@
+import scala.annotation.valhalla
+
+@valhalla
+trait ValhallaTrait extends Any:
+ def add(x:Int, y:Int): Int
+
+class IdentClass extends ValhallaTrait:
+ def add(x:Int, y:Int): Int = x + y
\ No newline at end of file
diff --git a/tests/valhalla/pos/NestedClasses.scala b/tests/valhalla/pos/NestedClasses.scala
new file mode 100644
index 000000000000..b6e98e884e73
--- /dev/null
+++ b/tests/valhalla/pos/NestedClasses.scala
@@ -0,0 +1,16 @@
+import scala.annotation.valhalla
+
+@valhalla
+class NestedClasses extends AnyVal{
+ class inner
+
+ @valhalla
+ class innerValhalla extends AnyVal
+}
+
+class NestedIdentityClasses {
+ class inner
+
+ @valhalla
+ class innerValhalla extends AnyVal
+}
\ No newline at end of file
diff --git a/tests/valhalla/pos/TraitExtendsAny.scala b/tests/valhalla/pos/TraitExtendsAny.scala
new file mode 100644
index 000000000000..5d69d533e99b
--- /dev/null
+++ b/tests/valhalla/pos/TraitExtendsAny.scala
@@ -0,0 +1,4 @@
+import scala.annotation.valhalla
+
+@valhalla
+trait TraitExtendsAny extends Any
diff --git a/tests/valhalla/pos/VVCExtendsAbstractVVC.scala b/tests/valhalla/pos/VVCExtendsAbstractVVC.scala
new file mode 100644
index 000000000000..34c88c057ede
--- /dev/null
+++ b/tests/valhalla/pos/VVCExtendsAbstractVVC.scala
@@ -0,0 +1,11 @@
+import scala.annotation.valhalla
+
+@valhalla
+abstract class AbstractVVC extends AnyVal {
+ def addOne(x: Int): Int
+}
+
+@valhalla
+class VVC extends AbstractVVC {
+ def addOne(x: Int): Int = x + 1
+}
\ No newline at end of file
diff --git a/tests/valhalla/pos/VVCExtendsValhallaTrait.scala b/tests/valhalla/pos/VVCExtendsValhallaTrait.scala
new file mode 100644
index 000000000000..16adf8f3001b
--- /dev/null
+++ b/tests/valhalla/pos/VVCExtendsValhallaTrait.scala
@@ -0,0 +1,9 @@
+import scala.annotation.valhalla
+
+@valhalla
+trait ValhallaTrait extends Any:
+ def add(x:Int, y:Int): Int
+
+@valhalla
+class VVC extends AnyVal with ValhallaTrait:
+ def add(x:Int, y:Int): Int = x + y
\ No newline at end of file
diff --git a/tests/valhalla/pos/ValhallaExtendsNonValhallaAnyTrait.scala b/tests/valhalla/pos/ValhallaExtendsNonValhallaAnyTrait.scala
new file mode 100644
index 000000000000..52956c7a9ce2
--- /dev/null
+++ b/tests/valhalla/pos/ValhallaExtendsNonValhallaAnyTrait.scala
@@ -0,0 +1,19 @@
+import scala.annotation.valhalla
+
+/**
+ * Valhalla classes and traits cannot extend non-Valhalla classes or traits
+ */
+
+trait AnyTrait extends Any:
+ val a: Int = 2
+ def add(x:Int, y:Int): Int
+
+
+@valhalla
+trait TraitExtendsAnyTrait extends Any with AnyTrait: // error
+ def addOne(x: Int): Int
+
+@valhalla
+class VVC extends AnyVal with AnyTrait: // error
+ def add(x:Int, y:Int): Int = x + y
+ def addOne(x: Int): Int = x + 1
\ No newline at end of file