Skip to content
Draft
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
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
}
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/backend/jvm/BackendUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
8 changes: 7 additions & 1 deletion compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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?
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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]

Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down
10 changes: 9 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 =>
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
24 changes: 24 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) = ""
}
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
40 changes: 40 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
10 changes: 10 additions & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading
Loading