Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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