diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index 39e94facb26a..78773a518b67 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index a08d6da650c9..3386dc7d7a6c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -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 @@ -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 ------------------------------------------------- @@ -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)) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 68c68e48caaf..a71f28f49410 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -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.* @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index c3de3ab69519..3d86b0271e02 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -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") diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index e04d829d1e60..c704846a82da 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/tasty/Attributes.scala b/compiler/src/dotty/tools/dotc/core/tasty/Attributes.scala index 7f87b7f4f2b7..0697f39f6dab 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/Attributes.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/Attributes.scala @@ -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: @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala index 033d8cd40c1f..8753017cc82f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index b4ba58c55c93..dafd6c2e8daa 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -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.* @@ -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 @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 1f551028c415..d4271d5bffaf 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -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 @@ -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) = @@ -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() @@ -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) = @@ -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 => diff --git a/compiler/src/dotty/tools/dotc/fromtasty/ReadTasty.scala b/compiler/src/dotty/tools/dotc/fromtasty/ReadTasty.scala index 4969882b7766..9cf2421c3bda 100644 --- a/compiler/src/dotty/tools/dotc/fromtasty/ReadTasty.scala +++ b/compiler/src/dotty/tools/dotc/fromtasty/ReadTasty.scala @@ -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. diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 1c1c47ad68ce..bdd29d9ec0ef 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -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)) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 306656b137c8..f3a2300913ec 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -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 @@ -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 { diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index a9b66fc056e2..6a030c424f08 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -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 @@ -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 diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index bb1d2afd04d6..dafb44d525e4 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala index 36982633e881..a35628dc52e4 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala @@ -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. diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index c361b50af819..2dc1599365cd 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -9,6 +9,8 @@ import tasty.* import config.Printers.{noPrinter, pickling} import config.Feature import java.io.PrintStream +import io.ClassfileWriterOps +import StdNames.str import Periods.* import Phases.* import Symbols.* @@ -17,6 +19,7 @@ import reporting.{ThrowingReporter, Profile, Message} import collection.mutable import util.concurrent.{Executor, Future} import compiletime.uninitialized +import dotty.tools.io.JarArchive object Pickler { val name: String = "pickler" @@ -27,6 +30,9 @@ object Pickler { * only in backend. */ inline val ParallelPickling = true + + class EarlyFileWriter(writer: ClassfileWriterOps): + export writer.{writeTasty, close} } /** This phase pickles trees */ @@ -39,7 +45,10 @@ class Pickler extends Phase { // No need to repickle trees coming from TASTY override def isRunnable(using Context): Boolean = - super.isRunnable && !ctx.settings.fromTasty.value + super.isRunnable && (!ctx.settings.fromTasty.value || ctx.settings.YjavaTasty.value) + + // when `-Yjava-tasty` is set we actually want to run this phase on Java sources + override def skipIfJava(using Context): Boolean = false private def output(name: String, msg: String) = { val s = new PrintStream(name) @@ -74,7 +83,8 @@ class Pickler extends Phase { private val executor = Executor[Array[Byte]]() private def useExecutor(using Context) = - Pickler.ParallelPickling && !ctx.settings.YtestPickler.value + Pickler.ParallelPickling && !ctx.settings.YtestPickler.value && + !ctx.settings.YjavaTasty.value // disable parallel pickling when `-Yjava-tasty` is set (internal testing only) override def run(using Context): Unit = { val unit = ctx.compilationUnit @@ -86,8 +96,22 @@ class Pickler extends Phase { do if ctx.settings.YtestPickler.value then beforePickling(cls) = tree.show + val isJavaAttr = unit.isJava // we must always set JAVAattr when pickling Java sources + if isJavaAttr then + // assert that Java sources didn't reach Pickler without `-Yjava-tasty`. + assert(ctx.settings.YjavaTasty.value, "unexpected Java source file without -Yjava-tasty") + val isOutline = isJavaAttr // TODO: later we may want outline for Scala sources too + val attributes = Attributes( + scala2StandardLibrary = ctx.settings.YcompileScala2Library.value, + explicitNulls = ctx.settings.YexplicitNulls.value, + captureChecked = Feature.ccEnabled, + withPureFuns = Feature.pureFunsEnabled, + isJava = isJavaAttr, + isOutline = isOutline + ) + val pickler = new TastyPickler(cls) - val treePkl = new TreePickler(pickler) + val treePkl = new TreePickler(pickler, attributes) treePkl.pickle(tree :: Nil) Profile.current.recordTasty(treePkl.buf.length) @@ -109,12 +133,6 @@ class Pickler extends Phase { pickler, treePkl.buf.addrOfTree, treePkl.docString, tree, scratch.commentBuffer) - val attributes = Attributes( - scala2StandardLibrary = ctx.settings.YcompileScala2Library.value, - explicitNulls = ctx.settings.YexplicitNulls.value, - captureChecked = Feature.ccEnabled, - withPureFuns = Feature.pureFunsEnabled, - ) AttributePickler.pickleAttributes(attributes, pickler, scratch.attributeBuffer) val pickled = pickler.assembleParts() @@ -154,13 +172,22 @@ class Pickler extends Phase { } override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = { - val result = - if useExecutor then - executor.start() - try super.runOn(units) - finally executor.close() + val sigWriter: Option[Pickler.EarlyFileWriter] = ctx.settings.YjavaTastyOutput.value match + case jar: JarArchive if jar.exists => + Some(Pickler.EarlyFileWriter(ClassfileWriterOps(jar))) + case _ => + None + val units0 = + if ctx.settings.fromTasty.value then + // we still run the phase for the side effect of writing the pipeline tasty files + units else - super.runOn(units) + if useExecutor then + executor.start() + try super.runOn(units) + finally executor.close() + else + super.runOn(units) if ctx.settings.YtestPickler.value then val ctx2 = ctx.fresh .setSetting(ctx.settings.YreadComments, true) @@ -171,9 +198,34 @@ class Pickler extends Phase { .setReporter(new ThrowingReporter(ctx.reporter)) .addMode(Mode.ReadPositions) ) + val result = + if ctx.settings.YjavaTasty.value then + sigWriter.foreach(writeJavaSigFiles(units0, _)) + units0.filterNot(_.typedAsJava) // remove java sources, this is the terminal phase when `-Yjava-tasty` is set + else + units0 result } + private def writeJavaSigFiles(units: List[CompilationUnit], writer: Pickler.EarlyFileWriter)(using Context): Unit = { + var count = 0 + try + for + unit <- units if unit.typedAsJava + (cls, pickled) <- unit.pickled + if cls.isDefinedInCurrentRun + do + val binaryName = cls.binaryClassName.replace('.', java.io.File.separatorChar).nn + val binaryClassName = if (cls.is(Module)) binaryName.stripSuffix(str.MODULE_SUFFIX).nn else binaryName + writer.writeTasty(binaryClassName, pickled()) + count += 1 + finally + writer.close() + if ctx.settings.verbose.value then + report.echo(s"[$count java sig files written]") + end try + } + private def testUnpickler(using Context): Unit = pickling.println(i"testing unpickler at run ${ctx.runId}") ctx.initialize() diff --git a/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala b/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala index b79235f4f819..b16447ecb15b 100644 --- a/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala +++ b/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala @@ -57,7 +57,7 @@ class TyperPhase(addRootImports: Boolean = true) extends Phase { } protected def discardAfterTyper(unit: CompilationUnit)(using Context): Boolean = - unit.isJava || unit.suspended + (unit.isJava && !ctx.settings.YjavaTasty.value) || unit.suspended override val subPhases: List[SubPhase] = List( SubPhase("indexing"), SubPhase("typechecking"), SubPhase("checkingJava")) diff --git a/compiler/src/dotty/tools/io/ClassfileWriterOps.scala b/compiler/src/dotty/tools/io/ClassfileWriterOps.scala new file mode 100644 index 000000000000..c2107ded6f51 --- /dev/null +++ b/compiler/src/dotty/tools/io/ClassfileWriterOps.scala @@ -0,0 +1,50 @@ +package dotty.tools.io + +import dotty.tools.io.* +import dotty.tools.dotc.core.Decorators.* +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.report +import scala.language.unsafeNulls +import scala.annotation.constructorOnly + + +/** Experimental usage - writes bytes to JarArchives */ +class ClassfileWriterOps(outputDir: JarArchive)(using @constructorOnly ictx: Context) { + + type InternalName = String + + // if non-null, classfiles are written to a jar instead of the output directory + private val jarWriter: JarWriter | Null = + val localCtx = ictx + outputDir.underlyingSource.map { source => + if outputDir.isEmpty then + new Jar(source.file).jarWriter() + else inContext(localCtx) { + // Writing to non-empty JAR might be an undefined behaviour, e.g. in case if other files where + // created using `AbstractFile.bufferedOutputStream`instead of JarWriter + report.warning(em"Tried to write to non-empty JAR: $source") + null + } + }.getOrElse( + inContext(localCtx) { + report.warning(em"tried to create a file writer for $outputDir, but it had no underlying source.") + null + } + ) + + def writeTasty(className: InternalName, bytes: Array[Byte]): Unit = + writeToJar(className, bytes, ".tasty") + + private def writeToJar(className: InternalName, bytes: Array[Byte], suffix: String): Unit = { + if (jarWriter == null) return + val path = className + suffix + val out = jarWriter.newOutputStream(path) + try out.write(bytes, 0, bytes.length) + finally out.flush() + } + + def close(): Unit = { + if (jarWriter != null) jarWriter.close() + outputDir.close() + } +} diff --git a/compiler/src/dotty/tools/io/JarArchive.scala b/compiler/src/dotty/tools/io/JarArchive.scala index 49b743e83074..f42f68e745ed 100644 --- a/compiler/src/dotty/tools/io/JarArchive.scala +++ b/compiler/src/dotty/tools/io/JarArchive.scala @@ -12,6 +12,7 @@ import scala.jdk.CollectionConverters.* */ class JarArchive private (root: Directory) extends PlainDirectory(root) { def close(): Unit = jpath.getFileSystem().close() + override def exists: Boolean = jpath.getFileSystem().isOpen() && super.exists def allFileNames(): Iterator[String] = java.nio.file.Files.walk(jpath).iterator().asScala.map(_.toString) } diff --git a/sbt-test/pipelining/Yjava-tasty-annotation/a/src/main/scala/a/A.java b/sbt-test/pipelining/Yjava-tasty-annotation/a/src/main/scala/a/A.java new file mode 100644 index 000000000000..49c55a7c4d9c --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-annotation/a/src/main/scala/a/A.java @@ -0,0 +1,11 @@ +// this test ensures that it is possible to read a java annotation from TASTy. +package a; + +import java.lang.annotation.*; + + +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.TYPE, ElementType.PACKAGE }) +public @interface A { +} diff --git a/sbt-test/pipelining/Yjava-tasty-annotation/a/src/main/scala/a/package.scala b/sbt-test/pipelining/Yjava-tasty-annotation/a/src/main/scala/a/package.scala new file mode 100644 index 000000000000..93f99e9892fe --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-annotation/a/src/main/scala/a/package.scala @@ -0,0 +1,2 @@ +// THIS FILE EXISTS SO THAT `A.java` WILL BE COMPILED BY SCALAC +package a diff --git a/sbt-test/pipelining/Yjava-tasty-annotation/b/src/main/scala/b/B.scala b/sbt-test/pipelining/Yjava-tasty-annotation/b/src/main/scala/b/B.scala new file mode 100644 index 000000000000..51c7322bf264 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-annotation/b/src/main/scala/b/B.scala @@ -0,0 +1,9 @@ +package b + +import a.A + +object B { + @A + val foo = 23 +} + diff --git a/sbt-test/pipelining/Yjava-tasty-annotation/build.sbt b/sbt-test/pipelining/Yjava-tasty-annotation/build.sbt new file mode 100644 index 000000000000..28b9b0e2bee9 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-annotation/build.sbt @@ -0,0 +1,13 @@ +lazy val a = project.in(file("a")) + .settings( + scalacOptions += "-Yjava-tasty", // enable pickling of java signatures + scalacOptions ++= Seq("-Yjava-tasty-output", ((ThisBuild / baseDirectory).value / "a-annotation-java-tasty.jar").toString), + scalacOptions += "-Ycheck:all", + classDirectory := ((ThisBuild / baseDirectory).value / "a-annotation-classes"), // send classfiles to a different directory + ) + +lazy val b = project.in(file("b")) + .settings( + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-annotation-java-tasty.jar")), + scalacOptions += "-Ycheck:all", + ) diff --git a/sbt-test/pipelining/Yjava-tasty-annotation/project/DottyInjectedPlugin.scala b/sbt-test/pipelining/Yjava-tasty-annotation/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..69f15d168bfc --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-annotation/project/DottyInjectedPlugin.scala @@ -0,0 +1,12 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion"), + scalacOptions += "-source:3.0-migration" + ) +} diff --git a/sbt-test/pipelining/Yjava-tasty-annotation/test b/sbt-test/pipelining/Yjava-tasty-annotation/test new file mode 100644 index 000000000000..6105296d455b --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-annotation/test @@ -0,0 +1,3 @@ +> a/compile +# Test depending on a java compiled annotation through TASTy +> b/compile diff --git a/sbt-test/pipelining/Yjava-tasty-enum/a/src/main/scala/a/A.java b/sbt-test/pipelining/Yjava-tasty-enum/a/src/main/scala/a/A.java new file mode 100644 index 000000000000..26bf8a246774 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-enum/a/src/main/scala/a/A.java @@ -0,0 +1,7 @@ +// this test ensures that ExtractAPI does not cause a crash +// when looking at sealedDescendants of a Java enum. +package a; + +public enum A { + X, Y, Z; +} diff --git a/sbt-test/pipelining/Yjava-tasty-enum/a/src/main/scala/a/package.scala b/sbt-test/pipelining/Yjava-tasty-enum/a/src/main/scala/a/package.scala new file mode 100644 index 000000000000..93f99e9892fe --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-enum/a/src/main/scala/a/package.scala @@ -0,0 +1,2 @@ +// THIS FILE EXISTS SO THAT `A.java` WILL BE COMPILED BY SCALAC +package a diff --git a/sbt-test/pipelining/Yjava-tasty-enum/b/src/main/scala/b/B.scala b/sbt-test/pipelining/Yjava-tasty-enum/b/src/main/scala/b/B.scala new file mode 100644 index 000000000000..a648bb4e83d6 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-enum/b/src/main/scala/b/B.scala @@ -0,0 +1,17 @@ +package b + +import a.A + +object B { + + def formattedEnum(e: A): String = e match { + case A.X => "X" + case A.Y => "Y" + case A.Z => "Z" + } + + @main def test = + assert(A.values.toList == List(A.X, A.Y, A.Z)) + assert(A.values.toList.map(formattedEnum) == List("X", "Y", "Z")) +} + diff --git a/sbt-test/pipelining/Yjava-tasty-enum/build.sbt b/sbt-test/pipelining/Yjava-tasty-enum/build.sbt new file mode 100644 index 000000000000..1c416c65896f --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-enum/build.sbt @@ -0,0 +1,15 @@ +lazy val a = project.in(file("a")) + .settings( + compileOrder := CompileOrder.Mixed, // ensure we send java sources to Scala compiler + scalacOptions += "-Yjava-tasty", // enable pickling of java signatures + scalacOptions ++= Seq("-Yjava-tasty-output", ((ThisBuild / baseDirectory).value / "a-enum-java-tasty.jar").toString), + scalacOptions += "-Ycheck:all", + classDirectory := ((ThisBuild / baseDirectory).value / "a-enum-classes"), // send classfiles to a different directory + ) + + +lazy val b = project.in(file("b")) + .settings( + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-java-tasty.jar")), + scalacOptions += "-Ycheck:all", + ) diff --git a/sbt-test/pipelining/Yjava-tasty-enum/project/DottyInjectedPlugin.scala b/sbt-test/pipelining/Yjava-tasty-enum/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..69f15d168bfc --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-enum/project/DottyInjectedPlugin.scala @@ -0,0 +1,12 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion"), + scalacOptions += "-source:3.0-migration" + ) +} diff --git a/sbt-test/pipelining/Yjava-tasty-enum/test b/sbt-test/pipelining/Yjava-tasty-enum/test new file mode 100644 index 000000000000..68e3c170d3b5 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-enum/test @@ -0,0 +1,3 @@ +> a/compile +# test depending on a java compiled enum through TASTy +> b/compile diff --git a/sbt-test/pipelining/Yjava-tasty-from-tasty/a/src/main/scala/a/A.java b/sbt-test/pipelining/Yjava-tasty-from-tasty/a/src/main/scala/a/A.java new file mode 100644 index 000000000000..b8a278edc32f --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-from-tasty/a/src/main/scala/a/A.java @@ -0,0 +1,5 @@ +package a; + +public class A { + public static final String VALUE = "A"; +} diff --git a/sbt-test/pipelining/Yjava-tasty-from-tasty/a/src/main/scala/a/package.scala b/sbt-test/pipelining/Yjava-tasty-from-tasty/a/src/main/scala/a/package.scala new file mode 100644 index 000000000000..8cfc7fa44d87 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-from-tasty/a/src/main/scala/a/package.scala @@ -0,0 +1,2 @@ +// THE PURPOSE OF THIS FILE IS TO MAKE SBT SEND A.java TO THE SCALA COMPILER +package a diff --git a/sbt-test/pipelining/Yjava-tasty-from-tasty/a_from_tasty/.keep b/sbt-test/pipelining/Yjava-tasty-from-tasty/a_from_tasty/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-from-tasty/b/src/main/scala/b/B.scala b/sbt-test/pipelining/Yjava-tasty-from-tasty/b/src/main/scala/b/B.scala new file mode 100644 index 000000000000..884bf1a927ff --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-from-tasty/b/src/main/scala/b/B.scala @@ -0,0 +1,5 @@ +package b + +object B { + val A: "A" = a.A.VALUE +} diff --git a/sbt-test/pipelining/Yjava-tasty-from-tasty/build.sbt b/sbt-test/pipelining/Yjava-tasty-from-tasty/build.sbt new file mode 100644 index 000000000000..dc4950ec8379 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-from-tasty/build.sbt @@ -0,0 +1,29 @@ +// `a` contains mixed java/scala sources so sbt will send java sources to Scala compiler. +lazy val a = project.in(file("a")) + .settings( + compileOrder := CompileOrder.Mixed, // ensure we send java sources to Scala compiler + scalacOptions += "-Yjava-tasty", // enable pickling of java signatures + scalacOptions ++= Seq("-Yjava-tasty-output", ((ThisBuild / baseDirectory).value / "a-pre-java-tasty.jar").toString), + scalacOptions += "-Ycheck:all", + classDirectory := ((ThisBuild / baseDirectory).value / "a-pre-classes"), // send classfiles to a different directory + ) + +// recompile `a` with `-from-tasty` flag to test idempotent read/write java signatures. +// Requires -Yjava-tasty to be set in order to read them. +lazy val a_from_tasty = project.in(file("a_from_tasty")) + .settings( + Compile / sources := Seq((ThisBuild / baseDirectory).value / "a-pre-java-tasty.jar"), + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-pre-java-tasty.jar")), + scalacOptions += "-from-tasty", // read the jar file tasties as the source files + scalacOptions += "-Yjava-tasty", + scalacOptions += "-Yallow-outline-from-tasty", // allow outline signatures to be read with -from-tasty + scalacOptions ++= Seq("-Yjava-tasty-output", ((ThisBuild / baseDirectory).value / "a_from_tasty-java-tasty.jar").toString), + scalacOptions += "-Ycheck:all", + classDirectory := ((ThisBuild / baseDirectory).value / "a_from_tasty-classes"), // send classfiles to a different directory + ) + +lazy val b = project.in(file("b")) + .settings( + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a_from_tasty-java-tasty.jar")), + scalacOptions += "-Ycheck:all", + ) diff --git a/sbt-test/pipelining/Yjava-tasty-from-tasty/project/DottyInjectedPlugin.scala b/sbt-test/pipelining/Yjava-tasty-from-tasty/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..69f15d168bfc --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-from-tasty/project/DottyInjectedPlugin.scala @@ -0,0 +1,12 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion"), + scalacOptions += "-source:3.0-migration" + ) +} diff --git a/sbt-test/pipelining/Yjava-tasty-from-tasty/test b/sbt-test/pipelining/Yjava-tasty-from-tasty/test new file mode 100644 index 000000000000..af7eced8e846 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-from-tasty/test @@ -0,0 +1,5 @@ +> a/compile +# test reading java tasty with -from-tasty +> a_from_tasty/compile +# test java tasty is still written even with -from-tasty +> b/compile diff --git a/sbt-test/pipelining/Yjava-tasty-generic/a/src/main/scala/a/A.java b/sbt-test/pipelining/Yjava-tasty-generic/a/src/main/scala/a/A.java new file mode 100644 index 000000000000..1fcb7e78ae3d --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-generic/a/src/main/scala/a/A.java @@ -0,0 +1,14 @@ +// this test ensures that it is possible to read a generic java class from TASTy. +package a; + +public abstract class A { + private final int _value; + + protected A(final int value) { + this._value = value; + } + + public int value() { + return _value; + } +} diff --git a/sbt-test/pipelining/Yjava-tasty-generic/a/src/main/scala/a/package.scala b/sbt-test/pipelining/Yjava-tasty-generic/a/src/main/scala/a/package.scala new file mode 100644 index 000000000000..93f99e9892fe --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-generic/a/src/main/scala/a/package.scala @@ -0,0 +1,2 @@ +// THIS FILE EXISTS SO THAT `A.java` WILL BE COMPILED BY SCALAC +package a diff --git a/sbt-test/pipelining/Yjava-tasty-generic/b/src/main/scala/b/B.scala b/sbt-test/pipelining/Yjava-tasty-generic/b/src/main/scala/b/B.scala new file mode 100644 index 000000000000..dcb4935860df --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-generic/b/src/main/scala/b/B.scala @@ -0,0 +1,12 @@ +package b + +import a.A + +class B[T] { + val inner = new A[T](23) {} +} + +object B { + val derived: Int = (new B[Int]).inner.value +} + diff --git a/sbt-test/pipelining/Yjava-tasty-generic/build.sbt b/sbt-test/pipelining/Yjava-tasty-generic/build.sbt new file mode 100644 index 000000000000..aa5d3099e979 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-generic/build.sbt @@ -0,0 +1,13 @@ +lazy val a = project.in(file("a")) + .settings( + scalacOptions += "-Yjava-tasty", // enable pickling of java signatures + scalacOptions ++= Seq("-Yjava-tasty-output", ((ThisBuild / baseDirectory).value / "a-generic-java-tasty.jar").toString), + scalacOptions += "-Ycheck:all", + classDirectory := ((ThisBuild / baseDirectory).value / "a-generic-classes"), // send classfiles to a different directory + ) + +lazy val b = project.in(file("b")) + .settings( + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-generic-java-tasty.jar")), + scalacOptions += "-Ycheck:all", + ) diff --git a/sbt-test/pipelining/Yjava-tasty-generic/project/DottyInjectedPlugin.scala b/sbt-test/pipelining/Yjava-tasty-generic/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..69f15d168bfc --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-generic/project/DottyInjectedPlugin.scala @@ -0,0 +1,12 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion"), + scalacOptions += "-source:3.0-migration" + ) +} diff --git a/sbt-test/pipelining/Yjava-tasty-generic/test b/sbt-test/pipelining/Yjava-tasty-generic/test new file mode 100644 index 000000000000..5abac4b5eae7 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-generic/test @@ -0,0 +1,3 @@ +> a/compile +# Test depending on a java generic class through TASTy +> b/compile diff --git a/sbt-test/pipelining/Yjava-tasty-result-types/a/src/main/scala/a/A.java b/sbt-test/pipelining/Yjava-tasty-result-types/a/src/main/scala/a/A.java new file mode 100644 index 000000000000..c48f149849a6 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-result-types/a/src/main/scala/a/A.java @@ -0,0 +1,9 @@ +package a; + +public class A { + public static final String VALUE = "A"; + + public String add(T t) { + return VALUE + t.toString(); + } +} diff --git a/sbt-test/pipelining/Yjava-tasty-result-types/a/src/main/scala/a/package.scala b/sbt-test/pipelining/Yjava-tasty-result-types/a/src/main/scala/a/package.scala new file mode 100644 index 000000000000..93f99e9892fe --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-result-types/a/src/main/scala/a/package.scala @@ -0,0 +1,2 @@ +// THIS FILE EXISTS SO THAT `A.java` WILL BE COMPILED BY SCALAC +package a diff --git a/sbt-test/pipelining/Yjava-tasty-result-types/b/src/main/scala/b/B.scala b/sbt-test/pipelining/Yjava-tasty-result-types/b/src/main/scala/b/B.scala new file mode 100644 index 000000000000..b67840f2f852 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-result-types/b/src/main/scala/b/B.scala @@ -0,0 +1,17 @@ +package b + +import a.A + +object B { + val finalResult: "A" = A.VALUE + + val a_B: String = (new A()).add("B") + val a_true: String = (new A()).add(true) + + @main def test = { + assert(finalResult == "A") + assert(a_B == "AB") + assert(a_true == "Atrue") + } +} + diff --git a/sbt-test/pipelining/Yjava-tasty-result-types/build.sbt b/sbt-test/pipelining/Yjava-tasty-result-types/build.sbt new file mode 100644 index 000000000000..f9cf8082c731 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-result-types/build.sbt @@ -0,0 +1,13 @@ +lazy val a = project.in(file("a")) + .settings( + scalacOptions += "-Yjava-tasty", // enable pickling of java signatures + scalacOptions ++= Seq("-Yjava-tasty-output", ((ThisBuild / baseDirectory).value / "a-result-types-java-tasty.jar").toString), + scalacOptions += "-Ycheck:all", + classDirectory := ((ThisBuild / baseDirectory).value / "a-result-types-classes"), // send classfiles to a different directory + ) + +lazy val b = project.in(file("b")) + .settings( + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-result-types-java-tasty.jar")), + scalacOptions += "-Ycheck:all", + ) diff --git a/sbt-test/pipelining/Yjava-tasty-result-types/project/DottyInjectedPlugin.scala b/sbt-test/pipelining/Yjava-tasty-result-types/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..69f15d168bfc --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-result-types/project/DottyInjectedPlugin.scala @@ -0,0 +1,12 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion"), + scalacOptions += "-source:3.0-migration" + ) +} diff --git a/sbt-test/pipelining/Yjava-tasty-result-types/test b/sbt-test/pipelining/Yjava-tasty-result-types/test new file mode 100644 index 000000000000..f654cb06fc16 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-result-types/test @@ -0,0 +1,3 @@ +> a/compile +# Test depending on a java static final result, and method result through TASTy +> b/compile diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index ebe94cc0a76c..6ceb82f011f4 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -89,6 +89,7 @@ Standard-Section: "ASTs" TopLevelStat* SELECTin Length possiblySigned_NameRef qual_Term owner_Type -- qual.name, referring to a symbol declared in owner that has the given signature (see note below) QUALTHIS typeIdent_Tree -- id.this, different from THIS in that it contains a qualifier ident with position. NEW clsType_Term -- new cls + ELIDED exprType_Type -- elided expression of the given type THROW throwableExpr_Term -- throw throwableExpr NAMEDARG paramName_NameRef arg_Term -- paramName = arg APPLY Length fn_Term arg_Term* -- fn(args) @@ -275,6 +276,8 @@ Standard Section: "Attributes" Attribute* EXPLICITNULLSattr CAPTURECHECKEDattr WITHPUREFUNSattr + JAVAattr + OUTLINEattr ``` **************************************************************************************/ @@ -525,6 +528,7 @@ object TastyFormat { final val SINGLETONtpt = 101 final val BOUNDED = 102 final val EXPLICITtpt = 103 + final val ELIDED = 104 // Cat. 4: tag Nat AST @@ -615,6 +619,8 @@ object TastyFormat { final val EXPLICITNULLSattr = 2 final val CAPTURECHECKEDattr = 3 final val WITHPUREFUNSattr = 4 + final val JAVAattr = 5 + final val OUTLINEattr = 6 /** Useful for debugging */ def isLegalTag(tag: Int): Boolean = @@ -622,7 +628,7 @@ object TastyFormat { firstNatTreeTag <= tag && tag <= RENAMED || firstASTTreeTag <= tag && tag <= BOUNDED || firstNatASTTreeTag <= tag && tag <= NAMEDARG || - firstLengthTreeTag <= tag && tag <= MATCHtpt || + firstLengthTreeTag <= tag && tag <= MATCHCASEtype || tag == HOLE def isParamTag(tag: Int): Boolean = tag == PARAM || tag == TYPEPARAM @@ -828,6 +834,7 @@ object TastyFormat { case PRIVATEqualified => "PRIVATEqualified" case PROTECTEDqualified => "PROTECTEDqualified" case EXPLICITtpt => "EXPLICITtpt" + case ELIDED => "ELIDED" case HOLE => "HOLE" } @@ -836,6 +843,8 @@ object TastyFormat { case EXPLICITNULLSattr => "EXPLICITNULLSattr" case CAPTURECHECKEDattr => "CAPTURECHECKEDattr" case WITHPUREFUNSattr => "WITHPUREFUNSattr" + case JAVAattr => "JAVAattr" + case OUTLINEattr => "OUTLINEattr" } /** @return If non-negative, the number of leading references (represented as nats) of a length/trees entry.