Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tasty Reader support 3.0.0 final #9617

Merged
merged 12 commits into from May 14, 2021
25 changes: 23 additions & 2 deletions build.sbt
Expand Up @@ -616,7 +616,7 @@ lazy val tastytest = configureAsSubproject(project)
.settings(
name := "scala-tastytest",
description := "Scala TASTy Integration Testing Tool",
libraryDependencies ++= List(diffUtilsDep, TastySupport.scala3Compiler),
libraryDependencies += diffUtilsDep,
Compile / scalacOptions ++= Seq("-feature", "-Xlint"),
)

Expand Down Expand Up @@ -730,7 +730,7 @@ lazy val tasty = project.in(file("test") / "tasty")
.settings(publish / skip := true)
.settings(
Test / fork := true,
libraryDependencies += junitInterfaceDep,
libraryDependencies ++= Seq(junitInterfaceDep, TastySupport.scala3Library),
testOptions += Tests.Argument(TestFrameworks.JUnit, "-a", "-v"),
Test / testOptions += Tests.Argument(
s"-Dtastytest.src=${baseDirectory.value}",
Expand All @@ -739,6 +739,27 @@ lazy val tasty = project.in(file("test") / "tasty")
Compile / unmanagedSourceDirectories := Nil,
Test / unmanagedSourceDirectories := List(baseDirectory.value/"test"),
)
.configs(TastySupport.CompilerClasspath, TastySupport.LibraryClasspath)
.settings(
inConfig(TastySupport.CompilerClasspath)(Defaults.configSettings),
inConfig(TastySupport.LibraryClasspath)(Defaults.configSettings),
libraryDependencies ++= Seq(
TastySupport.scala3Compiler % TastySupport.CompilerClasspath,
TastySupport.scala3Library % TastySupport.LibraryClasspath,
),
javaOptions ++= {
import java.io.File.pathSeparator
val scalaLibrary = (library / Compile / classDirectory).value.getAbsoluteFile()
val scalaReflect = (reflect / Compile / classDirectory).value.getAbsoluteFile()
val dottyCompiler = (TastySupport.CompilerClasspath / managedClasspath).value.seq.map(_.data) :+ scalaLibrary
val dottyLibrary = (TastySupport.LibraryClasspath / managedClasspath).value.seq.map(_.data) :+ scalaLibrary
Seq(
s"-Dtastytest.classpaths.dottyCompiler=${dottyCompiler.mkString(pathSeparator)}",
s"-Dtastytest.classpaths.dottyLibrary=${dottyLibrary.mkString(pathSeparator)}",
s"-Dtastytest.classpaths.scalaReflect=$scalaReflect",
)
},
)

lazy val scalacheck = project.in(file("test") / "scalacheck")
.dependsOn(library, reflect, compiler, scaladoc)
Expand Down
8 changes: 6 additions & 2 deletions project/DottySupport.scala
Expand Up @@ -12,8 +12,12 @@ import sbt.librarymanagement.{
* Settings to support validation of TastyUnpickler against the release of dotty with the matching TASTy version
*/
object TastySupport {
val supportedTASTyRelease = "3.0.0-RC1" // TASTy version 28.0.1
val scala3Compiler = "org.scala-lang" % "scala3-compiler_3.0.0-RC1" % supportedTASTyRelease
val supportedTASTyRelease = "3.0.0-RC3" // TASTy version 28.0.3
val scala3Compiler = "org.scala-lang" % "scala3-compiler_3.0.0-RC3" % supportedTASTyRelease
val scala3Library = "org.scala-lang" % "scala3-library_3.0.0-RC3" % supportedTASTyRelease
bishabosha marked this conversation as resolved.
Show resolved Hide resolved

val CompilerClasspath = Configuration.of("TastySupport.CompilerClasspath", "TastySupport.CompilerClasspath")
val LibraryClasspath = Configuration.of("TastySupport.LibraryClasspath", "TastySupport.LibraryClasspath")
}

/** Settings needed to compile with Dotty,
Expand Down
11 changes: 5 additions & 6 deletions src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
Expand Up @@ -953,12 +953,11 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
mbt.descriptor
)
}
module.attachments.get[DottyEnumSingleton] match { // TODO [tasty]: dotty enum singletons are not modules.
case Some(enumAttach) =>
val enumCompanion = symInfoTK(module.originalOwner).asClassBType
visitAccess(enumCompanion, enumAttach.name)

case _ => visitAccess(mbt, strMODULE_INSTANCE_FIELD)
if (module.isScala3Defined && module.hasAttachment[DottyEnumSingleton.type]) { // TODO [tasty]: dotty enum singletons are not modules.
val enumCompanion = symInfoTK(module.originalOwner).asClassBType
visitAccess(enumCompanion, module.rawname.toString)
} else {
visitAccess(mbt, strMODULE_INSTANCE_FIELD)
}
}
}
Expand Down
122 changes: 70 additions & 52 deletions src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala
Expand Up @@ -36,7 +36,6 @@ class TreeUnpickler[Tasty <: TastyUniverse](
nameAtRef: NameRef => TastyName)(implicit
val tasty: Tasty) { self =>
import tasty._
import FlagSets._
import TreeUnpickler._
import MaybeCycle._
import TastyModes._
Expand Down Expand Up @@ -70,8 +69,12 @@ class TreeUnpickler[Tasty <: TastyUniverse](

//---------------- unpickling trees ----------------------------------------------------------------------------------

private def registerSym(addr: Addr, sym: Symbol)(implicit ctx: Context) = {
ctx.log(s"$addr registered ${showSym(sym)} in ${location(sym.owner)}")
private def registerSym(addr: Addr, sym: Symbol, rejected: Boolean)(implicit ctx: Context) = {
assert(!(rejected && isSymbol(sym)), "expected no symbol when rejected")
ctx.log(
if (isSymbol(sym)) s"$addr registered ${showSym(sym)} in ${location(sym.owner)}"
else s"$addr registering symbol was rejected"
)
symAtAddr(addr) = sym
}

Expand Down Expand Up @@ -415,20 +418,23 @@ class TreeUnpickler[Tasty <: TastyUniverse](
if (isType) prior.toTypeName else prior
}

private def normalizeFlags(tag: Int, tastyFlags: TastyFlagSet, name: TastyName, isAbsType: Boolean, isClass: Boolean, rhsIsEmpty: Boolean)(implicit ctx: Context): TastyFlagSet = {
private def addInferredFlags(tag: Int, tastyFlags: TastyFlagSet, name: TastyName, isAbsType: Boolean, isClass: Boolean, rhsIsEmpty: Boolean)(implicit ctx: Context): TastyFlagSet = {
var flags = tastyFlags
val lacksDefinition =
rhsIsEmpty &&
name.isTermName && !name.isConstructorName && !flags.isOneOf(TermParamOrAccessor) ||
name.isTermName && !name.isConstructorName && !flags.isOneOf(FlagSets.TermParamOrAccessor) ||
isAbsType ||
flags.is(Opaque) && !isClass
if (lacksDefinition && tag != PARAM) flags |= Deferred
if (isClass && flags.is(Trait)) flags |= Abstract
if (tag === DEFDEF) flags |= Method
if (tag === VALDEF) {
if (flags.is(Inline) || ctx.owner.is(Trait)) flags |= FieldAccessor
if (flags.not(Mutable)) flags |= Stable
if (flags.is(SingletonEnumFlags)) flags |= Object // we will encode dotty enum constants as objects (this needs to be corrected in bytecode)
if (flags.is(Inline) || ctx.owner.is(Trait))
flags |= FieldAccessor
if (flags.not(Mutable))
flags |= Stable
if (flags.is(Case | Static | Enum)) // singleton enum case
flags |= Object | Stable // encode as a module (this needs to be corrected in bytecode)
}
if (ctx.owner.isClass) {
if (tag === TYPEPARAM) flags |= Param
Expand All @@ -439,7 +445,7 @@ class TreeUnpickler[Tasty <: TastyUniverse](
}
}
else if (isParamTag(tag)) flags |= Param
if (flags.is(Object)) flags |= (if (tag === VALDEF) ObjectCreationFlags else ObjectClassCreationFlags)
if (flags.is(Object)) flags |= (if (tag === VALDEF) FlagSets.Creation.ObjectDef else FlagSets.Creation.ObjectClassDef)
flags
}

Expand All @@ -462,7 +468,7 @@ class TreeUnpickler[Tasty <: TastyUniverse](
createMemberSymbol()
case TEMPLATE =>
val localDummy = ctx.newLocalDummy
registerSym(currentAddr, localDummy)
registerSym(currentAddr, localDummy, rejected = false)
localDummy
case tag =>
assert(tag != BIND, "bind pattern symbol creation from TASTy")
Expand All @@ -473,12 +479,23 @@ class TreeUnpickler[Tasty <: TastyUniverse](
* @return the created symbol
*/
def createMemberSymbol()(implicit ctx: Context): Symbol = {

def rejectSymbol(owner: Symbol, name: TastyName, flags: TastyFlagSet): Boolean = {
def isPureMixinCtor =
name == TastyName.MixinConstructor && owner.isTrait && flags.is(Stable)
def isInvisible =
flags.is(Invisible)

isPureMixinCtor || isInvisible
}

val start = currentAddr
val tag = readByte()
def isTypeTag = tag === TYPEDEF || tag === TYPEPARAM
val end = readEnd()
val parsedName: TastyName = readTastyName()
ctx.log(s"$start ::: => create ${astTagToString(tag)} ${parsedName.debug}")
def debugSymCreate: String = s"${astTagToString(tag)} ${parsedName.debug}"
ctx.log(s"$start ::: => create $debugSymCreate")
skipParams()
val ttag = nextUnsharedTag
val isAbsType = isAbstractType(ttag)
Expand All @@ -487,13 +504,11 @@ class TreeUnpickler[Tasty <: TastyUniverse](
skipTree() // tpt
val rhsIsEmpty = nothingButMods(end)
if (!rhsIsEmpty) skipTree()
val (name, flags, annotations, privateWithin) = {
val (parsedFlags, annotations, privateWithin) =
readModifiers(end, readTypedAnnot, readTypedWithin, noSymbol)
val name = normalizeName(isTypeTag, parsedName)
val flags = normalizeFlags(tag, parsedFlags, name, isAbsType, isClass, rhsIsEmpty)
(name, flags, annotations, privateWithin)
}
val (parsedFlags0, annotations, privateWithin) =
readModifiers(end, readTypedAnnot, readTypedWithin, noSymbol)
val name = normalizeName(isTypeTag, parsedName)
val flags = addInferredFlags(tag, parsedFlags0, name, isAbsType, isClass, rhsIsEmpty)
def mkCompleter = new Completer(isClass, subReader(start, end), flags)(ctx.retractMode(IndexScopedStats))
def isTypeParameter = flags.is(Param) && isTypeTag
def canEnterInClass = !isTypeParameter
ctx.log {
Expand All @@ -507,34 +522,46 @@ class TreeUnpickler[Tasty <: TastyUniverse](
}
s"""$start parsed flags $debugFlags"""
}
val rejected = rejectSymbol(ctx.owner, name, flags)
val sym = {
if (tag === TYPEPARAM && ctx.owner.isConstructor) {
// TASTy encodes type parameters for constructors
// nsc only has class type parameters
ctx.findOuterClassTypeParameter(name.toTypeName)
}
else {
val completer = new Completer(isClass, subReader(start, end), flags)(ctx.retractMode(IndexScopedStats))
ctx.findRootSymbol(roots, name) match {
case Some(rootd) =>
ctx.adjustSymbol(rootd, flags, completer, privateWithin) // dotty "removes one completion" here from the flags, which is not possible in nsc
ctx.log(s"$start replaced info of ${showSym(rootd)}")
rootd
roots -= rootd
if (rejected) {
ctx.evict(rootd)
noSymbol
}
else {
ctx.redefineSymbol(rootd, flags, mkCompleter, privateWithin)
ctx.log(s"$start replaced info of ${showSym(rootd)}")
rootd
}
case _ =>
if (isClass) ctx.delayClassCompletion(ctx.owner, name.toTypeName, completer, privateWithin)
else ctx.delayCompletion(ctx.owner, name, completer, privateWithin)
if (rejected) noSymbol
else if (isClass) ctx.delayClassCompletion(ctx.owner, name.toTypeName, mkCompleter, privateWithin)
else ctx.delayCompletion(ctx.owner, name, mkCompleter, privateWithin)
}
}
}.ensuring(isSymbol(_), s"${ctx.classRoot}: Could not create symbol at $start")
if (tag == VALDEF && flags.is(SingletonEnumFlags))
ctx.markAsEnumSingleton(sym)
registerSym(start, sym)
if (canEnterInClass && ctx.owner.isClass)
ctx.enterIfUnseen(sym)
if (isClass) {
val localCtx = ctx.withOwner(sym)
forkAt(templateStart).indexTemplateParams()(localCtx)
}
registerSym(start, sym, rejected)
if (isSymbol(sym)) {
if (tag == VALDEF && flags.is(FlagSets.SingletonEnum))
ctx.markAsEnumSingleton(sym)
if (canEnterInClass && ctx.owner.isClass)
ctx.enterIfUnseen(sym)
if (isClass) {
val localCtx = ctx.withOwner(sym)
forkAt(templateStart).indexTemplateParams()(localCtx)
}
ctx.adjustAnnotations(sym, annotations)
}
goto(start)
ctx.adjustAnnotations(sym, annotations)
sym
}

Expand All @@ -554,7 +581,6 @@ class TreeUnpickler[Tasty <: TastyUniverse](
}
nextByte match {
case PRIVATE => addFlag(Private)
case INTERNAL => addFlag(Internal)
case PROTECTED => addFlag(Protected)
case ABSTRACT =>
readByte()
Expand Down Expand Up @@ -595,6 +621,7 @@ class TreeUnpickler[Tasty <: TastyUniverse](
case PARAMalias => addFlag(ParamAlias)
case EXPORTED => addFlag(Exported)
case OPEN => addFlag(Open)
case INVISIBLE => addFlag(Invisible)
case PRIVATEqualified =>
readByte()
privateWithin = readWithin(ctx)
Expand Down Expand Up @@ -759,18 +786,9 @@ class TreeUnpickler[Tasty <: TastyUniverse](
checkUnsupportedFlags(repr.tastyOnlyFlags &~ (Enum | Extension | Exported))
val tpe = readTpt()(localCtx).tpe
ctx.setInfo(sym,
if (repr.originalFlagSet.is(SingletonEnumFlags)) {
val enumClass = sym.objectImplementation
val selfTpe = defn.SingleType(sym.owner.thisPrefix, sym)
val ctor = ctx.unsafeNewSymbol(
owner = enumClass,
name = TastyName.Constructor,
flags = Method,
info = defn.DefDefType(Nil, Nil :: Nil, selfTpe)
)
enumClass.typeOfThis = selfTpe
ctx.setInfo(enumClass, defn.ClassInfoType(intersectionParts(tpe), ctor :: Nil, enumClass))
prefixedRef(sym.owner.thisPrefix, enumClass)
if (repr.originalFlagSet.is(FlagSets.SingletonEnum)) {
ctx.completeEnumSingleton(sym, tpe)
prefixedRef(sym.owner.thisPrefix, sym.objectImplementation)
}
else if (sym.isFinal && isConstantType(tpe)) defn.InlineExprType(tpe)
else if (sym.isMethod) defn.ExprType(tpe)
Expand Down Expand Up @@ -1001,7 +1019,7 @@ class TreeUnpickler[Tasty <: TastyUniverse](
(tag: @switch) match {
case SELECTin =>
val name = readTastyName()
val qual = readTerm()
val qual = readTerm()
if (inParentCtor) {
assert(name.isSignedConstructor, s"Parent of ${ctx.owner} is not a constructor.")
skipTree()
Expand Down Expand Up @@ -1032,7 +1050,7 @@ class TreeUnpickler[Tasty <: TastyUniverse](
tpd.SeqLiteral(until(end)(readTerm()), elemtpt)
case REFINEDtpt =>
val refineCls = symAtAddr.getOrElse(start, ctx.newRefinementClassSymbol)
registerSym(start, refineCls)
registerSym(start, refineCls, rejected = false)
typeAtAddr(start) = refineCls.ref
val parent = readTpt()
ctx.withOwner(refineCls).enterRefinement(parent.tpe) { refinedCtx =>
Expand Down Expand Up @@ -1081,7 +1099,7 @@ class TreeUnpickler[Tasty <: TastyUniverse](
case UNAPPLY => unsupportedTermTreeError("unapply pattern")
case INLINED => unsupportedTermTreeError("inlined expression")
case SELECTouter => metaprogrammingIsUnsupported // only within inline
case HOLE => assertNoMacroHole
case HOLE => abortMacroHole
case _ => readPathTerm()
}
assert(currentAddr === end, s"$start $currentAddr $end ${astTagToString(tag)}")
Expand All @@ -1098,7 +1116,7 @@ class TreeUnpickler[Tasty <: TastyUniverse](
forkAt(readAddr()).readTpt()
case BLOCK => // BLOCK appears in type position when quoting a type, but only in the body of a method
metaprogrammingIsUnsupported
case HOLE => assertNoMacroHole
case HOLE => abortMacroHole
case tag =>
if (isTypeTreeTag(tag)) readTerm()(ctx.retractMode(OuterTerm))
else {
Expand All @@ -1112,7 +1130,7 @@ class TreeUnpickler[Tasty <: TastyUniverse](
/**
* A HOLE should never appear in TASTy for a top level class, only in quotes.
*/
private def assertNoMacroHole[T]: T = assertError("Scala 3 macro hole in pickled TASTy")
private def abortMacroHole[T]: T = abortWith(msg = "Scala 3 macro hole in pickled TASTy")

private def metaprogrammingIsUnsupported[T](implicit ctx: Context): T =
unsupportedError("Scala 3 metaprogramming features")
Expand Down