Skip to content

Commit

Permalink
Support the -Yjava-tasty flag.
Browse files Browse the repository at this point in the history
- Keep Java compilation units up to Pickler phase if -Yjava-tasty.
  Skip phases for Java when not needed.
- Add JAVAattr and OUTLINEattr TASTy attributes, ELIDED tree tag.
  ELIDED trees are pickled as rhs of java term definitions.
  ELIDED trees can only be unpickled if OUTLINEattr is present.
  For now OUTLINEattr implies JAVAattr. In the future we might
  expand OUTLINEattr to include outline Scala typing.
- write java tasty files to a special jar, set with -Yjava-tasty-output
  this option is for testing purposes only.
  • Loading branch information
bishabosha committed Nov 28, 2023
1 parent 7fa6565 commit 3e8e563
Show file tree
Hide file tree
Showing 51 changed files with 504 additions and 35 deletions.
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/CompilationUnit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ class CompilationUnit protected (val source: SourceFile, val info: CompilationUn
/** Is this the compilation unit of a Java file */
def isJava: Boolean = source.file.name.endsWith(".java")

/** Is this the compilation unit of a Java file, or TASTy derived from a Java file */
def typedAsJava = isJava || {
val infoNN = info
infoNN != null && infoNN.tastyInfo.exists(_.attributes.isJava)
}


/** The source version for this unit, as determined by a language import */
var sourceVersion: Option[SourceVersion] = None

Expand Down
15 changes: 12 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import reporting.*
import annotation.constructorOnly
import printing.Formatting.hl
import config.Printers
import parsing.Parsers

import scala.annotation.internal.sharable
import scala.annotation.threadUnsafe
Expand Down Expand Up @@ -143,8 +144,13 @@ object desugar {

/** A value definition copied from `vdef` with a tpt typetree derived from it */
def derivedTermParam(vdef: ValDef)(using Context): ValDef =
derivedTermParam(vdef, vdef.unforcedRhs)

def derivedTermParam(vdef: ValDef, rhs: LazyTree)(using Context): ValDef =
cpy.ValDef(vdef)(
tpt = DerivedFromParamTree().withSpan(vdef.tpt.span).watching(vdef))
tpt = DerivedFromParamTree().withSpan(vdef.tpt.span).watching(vdef),
rhs = rhs
)

// ----- Desugar methods -------------------------------------------------

Expand Down Expand Up @@ -544,8 +550,11 @@ object desugar {
constrTparams.zipWithConserve(impliedTparams)((tparam, impliedParam) =>
derivedTypeParam(tparam).withAnnotations(impliedParam.mods.annotations))
val derivedVparamss =
constrVparamss.nestedMap(vparam =>
derivedTermParam(vparam).withAnnotations(Nil))
constrVparamss.nestedMap: vparam =>
val derived =
if ctx.compilationUnit.isJava then derivedTermParam(vparam, Parsers.unimplementedExpr)
else derivedTermParam(vparam)
derived.withAnnotations(Nil)

val constr = cpy.DefDef(constr1)(paramss = joinParams(constrTparams, constrVparamss))

Expand Down
7 changes: 6 additions & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import dotty.tools.dotc.config.Settings.{Setting, SettingGroup}
import dotty.tools.dotc.config.SourceVersion
import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.rewrites.Rewrites
import dotty.tools.io.{AbstractFile, Directory, JDK9Reflectors, PlainDirectory}
import dotty.tools.io.{AbstractFile, Directory, JDK9Reflectors, PlainDirectory, NoAbstractFile}
import Setting.ChoiceWithHelp

import scala.util.chaining.*
Expand Down Expand Up @@ -433,4 +433,9 @@ private sealed trait YSettings:
val YforceInlineWhileTyping: Setting[Boolean] = BooleanSetting("-Yforce-inline-while-typing", "Make non-transparent inline methods inline when typing. Emulates the old inlining behavior of 3.0.0-M3.")

val YdebugMacros: Setting[Boolean] = BooleanSetting("-Ydebug-macros", "Show debug info when quote pattern match fails")

// Pipeline compilation options
val YjavaTasty: Setting[Boolean] = BooleanSetting("-Yjava-tasty", "Pickler phase should compute pickles for .java defined symbols for use by build tools")
val YjavaTastyOutput: Setting[AbstractFile] = OutputSetting("-Yjava-tasty-output", "directory|jar", "(Internal use only!) destination for generated .tasty files containing Java type signatures.", NoAbstractFile)
val YallowOutlineFromTasty: Setting[Boolean] = BooleanSetting("-Yallow-outline-from-tasty", "Allow outline TASTy to be loaded with the -from-tasty option.")
end YSettings
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 @@ -996,6 +996,7 @@ class Definitions {
@tu lazy val AnnotationClass: ClassSymbol = requiredClass("scala.annotation.Annotation")
@tu lazy val StaticAnnotationClass: ClassSymbol = requiredClass("scala.annotation.StaticAnnotation")
@tu lazy val RefiningAnnotationClass: ClassSymbol = requiredClass("scala.annotation.RefiningAnnotation")
@tu lazy val JavaAnnotationClass: ClassSymbol = requiredClass("java.lang.annotation.Annotation")

// Annotation classes
@tu lazy val AllowConversionsAnnot: ClassSymbol = requiredClass("scala.annotation.allowConversions")
Expand Down
11 changes: 10 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Phases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -333,16 +333,25 @@ object Phases {
def subPhases: List[Run.SubPhase] = Nil
final def traversals: Int = if subPhases.isEmpty then 1 else subPhases.length

/** skip the phase for a Java compilation unit, may depend on -Yjava-tasty */
def skipIfJava(using Context): Boolean = true

/** @pre `isRunnable` returns true */
def run(using Context): Unit

/** @pre `isRunnable` returns true */
def runOn(units: List[CompilationUnit])(using runCtx: Context): List[CompilationUnit] =
val buf = List.newBuilder[CompilationUnit]
// factor out typedAsJava check when not needed
val doSkipJava = ctx.settings.YjavaTasty.value && this <= picklerPhase && skipIfJava
for unit <- units do
given unitCtx: Context = runCtx.fresh.setPhase(this.start).setCompilationUnit(unit).withRootImports
if ctx.run.enterUnit(unit) then
try run
try
if doSkipJava && unit.typedAsJava then
()
else
run
catch case ex: Throwable if !ctx.run.enrichedErrorMessage =>
println(ctx.run.enrichErrorMessage(s"unhandled exception while running $phaseName on $unit"))
throw ex
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/core/tasty/Attributes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class Attributes private[tasty](
def explicitNulls: Boolean = booleanTags(EXPLICITNULLSattr)
def captureChecked: Boolean = booleanTags(CAPTURECHECKEDattr)
def withPureFuns: Boolean = booleanTags(WITHPUREFUNSattr)
def isJava: Boolean = booleanTags(JAVAattr)
def isOutline: Boolean = booleanTags(OUTLINEattr)
}

object Attributes:
Expand All @@ -19,12 +21,16 @@ object Attributes:
explicitNulls: Boolean,
captureChecked: Boolean,
withPureFuns: Boolean,
isJava: Boolean,
isOutline: Boolean,
): Attributes =
val booleanTags = BitSet.newBuilder
if scala2StandardLibrary then booleanTags += SCALA2STANDARDLIBRARYattr
if explicitNulls then booleanTags += EXPLICITNULLSattr
if captureChecked then booleanTags += CAPTURECHECKEDattr
if withPureFuns then booleanTags += WITHPUREFUNSattr
if isJava then booleanTags += JAVAattr
if isOutline then booleanTags += OUTLINEattr
new Attributes(booleanTags.result())
end apply

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ object DottyUnpickler {
def unpickle(reader: TastyReader, nameAtRef: NameTable): CommentUnpickler =
new CommentUnpickler(reader)
}

class AttributesSectionUnpickler extends SectionUnpickler[AttributeUnpickler](AttributesSection) {
def unpickle(reader: TastyReader, nameAtRef: NameTable): AttributeUnpickler =
new AttributeUnpickler(reader)
Expand Down
14 changes: 12 additions & 2 deletions compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import quoted.QuotePatterns
object TreePickler:
class StackSizeExceeded(val mdef: tpd.MemberDef) extends Exception

class TreePickler(pickler: TastyPickler) {
class TreePickler(pickler: TastyPickler, attributes: Attributes) {
val buf: TreeBuffer = new TreeBuffer
pickler.newSection(ASTsSection, buf)
import buf.*
Expand Down Expand Up @@ -322,6 +322,11 @@ class TreePickler(pickler: TastyPickler) {
if (!tree.isEmpty) pickleTree(tree)
}

def pickleElidedUnlessEmpty(tree: Tree, tp: Type)(using Context): Unit =
if !tree.isEmpty then
writeByte(ELIDED)
pickleType(tp)

def pickleDef(tag: Int, mdef: MemberDef, tpt: Tree, rhs: Tree = EmptyTree, pickleParams: => Unit = ())(using Context): Unit = {
val sym = mdef.symbol

Expand All @@ -337,7 +342,12 @@ class TreePickler(pickler: TastyPickler) {
case _: Template | _: Hole => pickleTree(tpt)
case _ if tpt.isType => pickleTpt(tpt)
}
pickleTreeUnlessEmpty(rhs)
if attributes.isOutline && sym.isTerm && attributes.isJava then
// TODO: if we introduce outline typing for Scala definitions
// then we will need to update the check here
pickleElidedUnlessEmpty(rhs, tpt.tpe)
else
pickleTreeUnlessEmpty(rhs)
pickleModifiers(sym, mdef)
}
catch
Expand Down
24 changes: 22 additions & 2 deletions compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ class TreeUnpickler(reader: TastyReader,
private val explicitNulls =
attributeUnpicklerOpt.exists(_.attributes.explicitNulls)

private val unpicklingJava =
attributeUnpicklerOpt.exists(_.attributes.isJava)

private val isOutline = attributeUnpicklerOpt.exists(_.attributes.isOutline)

private def registerSym(addr: Addr, sym: Symbol) =
symAtAddr(addr) = sym

Expand Down Expand Up @@ -609,7 +614,10 @@ class TreeUnpickler(reader: TastyReader,
val rhsIsEmpty = nothingButMods(end)
if (!rhsIsEmpty) skipTree()
val (givenFlags0, annotFns, privateWithin) = readModifiers(end)
val givenFlags = if isClass && unpicklingScala2Library then givenFlags0 | Scala2x | Scala2Tasty else givenFlags0
val givenFlags =
if isClass && unpicklingScala2Library then givenFlags0 | Scala2x | Scala2Tasty
else if unpicklingJava then givenFlags0 | JavaDefined
else givenFlags0
pickling.println(i"creating symbol $name at $start with flags ${givenFlags.flagsString}, isAbsType = $isAbsType, $ttag")
val flags = normalizeFlags(tag, givenFlags, name, isAbsType, rhsIsEmpty)
def adjustIfModule(completer: LazyType) =
Expand Down Expand Up @@ -1037,6 +1045,8 @@ class TreeUnpickler(reader: TastyReader,
val parentReader = fork
val parents = readParents(withArgs = false)(using parentCtx)
val parentTypes = parents.map(_.tpe.dealias)
if cls.is(JavaDefined) && parentTypes.exists(_.derivesFrom(defn.JavaAnnotationClass)) then
cls.setFlag(JavaAnnotation)
val self =
if (nextByte == SELFDEF) {
readByte()
Expand Down Expand Up @@ -1197,7 +1207,12 @@ class TreeUnpickler(reader: TastyReader,

def completeSelect(name: Name, sig: Signature, target: Name): Select =
val qual = readTree()
val denot = accessibleDenot(qual.tpe.widenIfUnstable, name, sig, target)
val denot0 = accessibleDenot(qual.tpe.widenIfUnstable, name, sig, target)
val denot =
if unpicklingJava && name == tpnme.Object && denot0.symbol == defn.ObjectClass then
defn.FromJavaObjectType.denot
else
denot0
makeSelect(qual, name, denot)

def readQualId(): (untpd.Ident, TypeRef) =
Expand All @@ -1216,6 +1231,11 @@ class TreeUnpickler(reader: TastyReader,
forkAt(readAddr()).readTree()
case IDENT =>
untpd.Ident(readName()).withType(readType())
case ELIDED =>
if !isOutline then
report.error(
s"Illegal elided tree in unpickler without ${attributeTagToString(OUTLINEattr)}, ${ctx.source}")
untpd.Ident(nme.WILDCARD).withType(readType())
case IDENTtpt =>
untpd.Ident(readName().toTypeName).withType(readType())
case SELECT =>
Expand Down
13 changes: 10 additions & 3 deletions compiler/src/dotty/tools/dotc/fromtasty/ReadTasty.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,16 @@ class ReadTasty extends Phase {
case unpickler: tasty.DottyUnpickler =>
if (cls.rootTree.isEmpty) None
else {
val unit = CompilationUnit(cls, cls.rootTree, forceTrees = true)
unit.pickled += (cls -> (() => unpickler.unpickler.bytes))
Some(unit)
val attributes = unpickler.tastyAttributes
if attributes.isJava && !ctx.settings.YjavaTasty.value then
// filter out Java compilation units if -Yjava-tasty is not set
None
else if attributes.isOutline && !ctx.settings.YallowOutlineFromTasty.value then
cannotUnpickle("it contains outline signatures and -Yallow-outline-from-tasty is not set.")
else
val unit = CompilationUnit(cls, cls.rootTree, forceTrees = true)
unit.pickled += (cls -> (() => unpickler.unpickler.bytes))
Some(unit)
}
case tree: Tree[?] =>
// TODO handle correctly this case correctly to get the tree or avoid it completely.
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ object JavaParsers {
ValDef(name, tpt, EmptyTree).withMods(Modifiers(Flags.JavaDefined | Flags.Param))

def makeConstructor(formals: List[Tree], tparams: List[TypeDef], flags: FlagSet = Flags.JavaDefined): DefDef = {
val vparams = formals.zipWithIndex.map { case (p, i) => makeSyntheticParam(i + 1, p) }
val vparams = formals.zipWithIndex.map { case (p, i) => makeSyntheticParam(i + 1, p).withMods(Modifiers(flags)) }
DefDef(nme.CONSTRUCTOR, joinParams(tparams, List(vparams)), TypeTree(), EmptyTree).withMods(Modifiers(flags))
}

Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ object Parsers {
private val InCond: Region => Region = Scanners.InParens(LPAREN, _)
private val InFor : Region => Region = Scanners.InBraces(_)

def unimplementedExpr(using Context): Select =
Select(scalaDot(nme.Predef), nme.???)

abstract class ParserCommon(val source: SourceFile)(using Context) {

val in: ScannerCommon
Expand Down Expand Up @@ -164,9 +167,6 @@ object Parsers {
*/
def syntaxError(msg: Message, span: Span): Unit =
report.error(msg, source.atSpan(span))

def unimplementedExpr(using Context): Select =
Select(scalaDot(nme.Predef), nme.???)
}

trait OutlineParserCommon extends ParserCommon {
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import dotty.tools.dotc.core.Flags.*
import dotty.tools.dotc.core.Mode
import dotty.tools.dotc.core.Symbols.*
import dotty.tools.dotc.core.Types.*
import dotty.tools.dotc.core.tasty.{ PositionPickler, TastyPickler, TastyPrinter, TreePickler }
import dotty.tools.dotc.core.tasty.{ PositionPickler, TastyPickler, TastyPrinter, TreePickler, Attributes }
import dotty.tools.dotc.core.tasty.DottyUnpickler
import dotty.tools.dotc.core.tasty.TreeUnpickler.UnpickleMode
import dotty.tools.dotc.report
Expand Down Expand Up @@ -217,7 +217,7 @@ object PickledQuotes {
private def pickle(tree: Tree)(using Context): Array[Byte] = {
quotePickling.println(i"**** pickling quote of\n$tree")
val pickler = new TastyPickler(defn.RootClass)
val treePkl = new TreePickler(pickler)
val treePkl = new TreePickler(pickler, Attributes.empty)
treePkl.pickle(tree :: Nil)
treePkl.compactify()
if tree.span.exists then
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class ExtractAPI extends Phase {
// Check no needed. Does not transform trees
override def isCheckable: Boolean = false

// when `-Yjava-tasty` is set we actually want to run this phase on Java sources
override def skipIfJava(using Context): Boolean = false

// SuperAccessors need to be part of the API (see the scripted test
// `trait-super` for an example where this matters), this is only the case
// after `PostTyper` (unlike `ExtractDependencies`, the simplication to trees
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class ExtractDependencies extends Phase {
// Check no needed. Does not transform trees
override def isCheckable: Boolean = false

// when `-Yjava-tasty` is set we actually want to run this phase on Java sources
override def skipIfJava(using Context): Boolean = false

// This phase should be run directly after `Frontend`, if it is run after
// `PostTyper`, some dependencies will be lost because trees get simplified.
// See the scripted test `constants` for an example where this matters.
Expand Down
Loading

0 comments on commit 3e8e563

Please sign in to comment.