diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 817ff5c6c9fa..aabfdd97d7bd 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -510,7 +510,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def scalaRuntimeDot(name: Name)(using SourceFile): Select = Select(scalaDot(nme.runtime), name) def scalaUnit(implicit src: SourceFile): Select = scalaDot(tpnme.Unit) def scalaAny(implicit src: SourceFile): Select = scalaDot(tpnme.Any) - def javaDotLangDot(name: Name)(implicit src: SourceFile): Select = Select(Select(Ident(nme.java), nme.lang), name) def captureRoot(using Context): Select = Select(scalaDot(nme.caps), nme.CAPTURE_ROOT) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 01c5a44ac736..d70b56fca43d 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -13,7 +13,7 @@ import ast.{untpd, tpd} import Contexts.*, Symbols.*, Types.*, Names.*, Constants.*, Decorators.*, Annotations.*, Flags.* import Comments.{Comment, docCtx} import NameKinds.* -import StdNames.nme +import StdNames.{nme, tpnme} import config.Config import collection.mutable import reporting.{Profile, NoProfile} @@ -49,6 +49,9 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { private var profile: Profile = NoProfile + private val isOutlinePickle: Boolean = attributes.isOutline + private val isJavaPickle: Boolean = attributes.isJava + def treeAnnots(tree: untpd.MemberDef): List[Tree] = val ts = annotTrees.lookup(tree) if ts == null then Nil else ts.toList @@ -188,19 +191,19 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { def pickleExternalRef(sym: Symbol) = { val isShadowedRef = sym.isClass && tpe.prefix.member(sym.name).symbol != sym - if (sym.is(Flags.Private) || isShadowedRef) { + if sym.is(Flags.Private) || isShadowedRef then writeByte(if (tpe.isType) TYPEREFin else TERMREFin) withLength { pickleNameAndSig(sym.name, sym.signature, sym.targetName) pickleType(tpe.prefix) pickleType(sym.owner.typeRef) } - } - else { + else if isJavaPickle && sym == defn.FromJavaObjectSymbol then + pickleType(defn.ObjectType) // when unpickling Java TASTy, replace by + else writeByte(if (tpe.isType) TYPEREF else TERMREF) pickleNameAndSig(sym.name, tpe.signature, sym.targetName) pickleType(tpe.prefix) - } } if (sym.is(Flags.Package)) { writeByte(if (tpe.isType) TYPEREFpkg else TERMREFpkg) @@ -342,7 +345,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { case _: Template | _: Hole => pickleTree(tpt) case _ if tpt.isType => pickleTpt(tpt) } - if attributes.isOutline && sym.isTerm && attributes.isJava then + if isOutlinePickle && sym.isTerm && isJavaPickle then // TODO: if we introduce outline typing for Scala definitions // then we will need to update the check here pickleElidedUnlessEmpty(rhs, tpt.tpe) @@ -358,7 +361,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { else throw ex if sym.is(Method) && sym.owner.isClass then - profile.recordMethodSize(sym, currentAddr.index - addr.index, mdef.span) + profile.recordMethodSize(sym, (currentAddr.index - addr.index) max 1, mdef.span) for docCtx <- ctx.docCtx do val comment = docCtx.docstrings.lookup(sym) if comment != null then @@ -614,7 +617,17 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { } } } - pickleStats(tree.constr :: rest) + if isJavaPickle then + val rest0 = rest.dropWhile: + case stat: ValOrDefDef => stat.symbol.is(Flags.Invisible) + case _ => false + if tree.constr.symbol.is(Flags.Invisible) then + writeByte(SPLITCLAUSE) + pickleStats(rest0) + else + pickleStats(tree.constr :: rest0) + else + pickleStats(tree.constr :: rest) } case Import(expr, selectors) => writeByte(IMPORT) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index fcd0a62f3a60..b7a25cb75613 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -163,6 +163,11 @@ class TreeUnpickler(reader: TastyReader, def forkAt(start: Addr): TreeReader = new TreeReader(subReader(start, endAddr)) def fork: TreeReader = forkAt(currentAddr) + def skipParentTree(tag: Int): Unit = { + if tag == SPLITCLAUSE then () + else skipTree(tag) + } + def skipParentTree(): Unit = skipParentTree(readByte()) def skipTree(tag: Int): Unit = { if (tag >= firstLengthTreeTag) goto(readEnd()) else if (tag >= firstNatASTTreeTag) { readNat(); skipTree() } @@ -441,7 +446,11 @@ class TreeUnpickler(reader: TastyReader, readPackageRef().termRef case TYPEREF => val name = readName().toTypeName - TypeRef(readType(), name) + val pre = readType() + if unpicklingJava && name == tpnme.Object && (pre.termSymbol eq defn.JavaLangPackageVal) then + defn.FromJavaObjectType + else + TypeRef(pre, name) case TERMREF => val sname = readName() val prefix = readType() @@ -1007,7 +1016,7 @@ class TreeUnpickler(reader: TastyReader, * parsed in this way as InferredTypeTrees. */ def readParents(withArgs: Boolean)(using Context): List[Tree] = - collectWhile(nextByte != SELFDEF && nextByte != DEFDEF) { + collectWhile({val tag = nextByte; tag != SELFDEF && tag != DEFDEF && tag != SPLITCLAUSE}) { nextUnsharedTag match case APPLY | TYPEAPPLY | BLOCK => if withArgs then readTree() @@ -1034,7 +1043,8 @@ class TreeUnpickler(reader: TastyReader, val bodyFlags = { val bodyIndexer = fork // The first DEFDEF corresponds to the primary constructor - while (bodyIndexer.reader.nextByte != DEFDEF) bodyIndexer.skipTree() + while ({val tag = bodyIndexer.reader.nextByte; tag != DEFDEF && tag != SPLITCLAUSE}) do + bodyIndexer.skipParentTree() bodyIndexer.indexStats(end) } val parentReader = fork @@ -1053,7 +1063,38 @@ class TreeUnpickler(reader: TastyReader, cls.owner.thisType, cls, parentTypes, cls.unforcedDecls, selfInfo = if (self.isEmpty) NoType else self.tpt.tpe ).integrateOpaqueMembers - val constr = readIndexedDef().asInstanceOf[DefDef] + + val constr = + if nextByte == SPLITCLAUSE then + assert(unpicklingJava, s"unexpected SPLITCLAUSE at $start") + val tag = readByte() + def ta = ctx.typeAssigner + val flags = Flags.JavaDefined | Flags.PrivateLocal | Flags.Invisible + val ctorCompleter = new LazyType { + def complete(denot: SymDenotation)(using Context) = + val sym = denot.symbol + val pflags = flags | Flags.Param + val tparamRefs = tparams.map(_.symbol.asType) + lazy val derivedTparamSyms: List[TypeSymbol] = tparams.map: tdef => + val completer = new LazyType { + def complete(denot: SymDenotation)(using Context) = + denot.info = tdef.symbol.asType.info.subst(tparamRefs, derivedTparamRefs) + } + newSymbol(sym, tdef.name, Flags.JavaDefined | Flags.Param, completer, coord = cls.coord) + lazy val derivedTparamRefs: List[Type] = derivedTparamSyms.map(_.typeRef) + val vparamSym = + newSymbol(sym, nme.syntheticParamName(1), pflags, defn.UnitType, coord = cls.coord) + val vparamSymss: List[List[Symbol]] = List(vparamSym) :: Nil + val paramSymss = + if derivedTparamSyms.nonEmpty then derivedTparamSyms :: vparamSymss else vparamSymss + val res = effectiveResultType(sym, paramSymss) + denot.info = methodType(paramSymss, res) + denot.setParamss(paramSymss) + } + val ctorSym = newSymbol(ctx.owner, nme.CONSTRUCTOR, flags, ctorCompleter, coord = coordAt(start)) + tpd.DefDef(ctorSym, EmptyTree).setDefTree // fake primary constructor + else + readIndexedDef().asInstanceOf[DefDef] val mappedParents: LazyTreeList = if parents.exists(_.isInstanceOf[InferredTypeTree]) then // parents were not read fully, will need to be read again later on demand @@ -1174,6 +1215,9 @@ class TreeUnpickler(reader: TastyReader, // ------ Reading trees ----------------------------------------------------- + private def ElidedTree(tpe: Type)(using Context): Tree = + untpd.Ident(nme.WILDCARD).withType(tpe) + def readTree()(using Context): Tree = { val sctx = sourceChangeContext() if (sctx `ne` ctx) return readTree()(using sctx) @@ -1202,12 +1246,11 @@ class TreeUnpickler(reader: TastyReader, def completeSelect(name: Name, sig: Signature, target: Name): Select = val qual = readTree() - val denot0 = accessibleDenot(qual.tpe.widenIfUnstable, name, sig, target) val denot = - if unpicklingJava && name == tpnme.Object && denot0.symbol == defn.ObjectClass then - defn.FromJavaObjectType.denot + if unpicklingJava && name == tpnme.Object && qual.symbol == defn.JavaLangPackageVal then + defn.FromJavaObjectSymbol.denot else - denot0 + accessibleDenot(qual.tpe.widenIfUnstable, name, sig, target) makeSelect(qual, name, denot) def readQualId(): (untpd.Ident, TypeRef) = @@ -1228,9 +1271,10 @@ class TreeUnpickler(reader: TastyReader, 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()) + val msg = + s"Illegal elided tree in unpickler at $start without ${attributeTagToString(OUTLINEattr)}, ${ctx.source}" + report.error(msg) + ElidedTree(readType()) case IDENTtpt => untpd.Ident(readName().toTypeName).withType(readType()) case SELECT => diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index bdd29d9ec0ef..f7ef86ee5cde 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -12,6 +12,7 @@ import Scanners.Offset import Parsers.* import core.* import Contexts.* +import Symbols.defn import Names.* import Types.* import ast.Trees.* @@ -27,6 +28,9 @@ object JavaParsers { import ast.untpd.* + + val fakeFlags = Flags.JavaDefined | Flags.PrivateLocal | Flags.Invisible + class JavaParser(source: SourceFile)(using Context) extends ParserCommon(source) { val definitions: Definitions = ctx.definitions @@ -89,16 +93,16 @@ object JavaParsers { // --------- tree building ----------------------------- - def scalaAnnotationDot(name: Name): Select = Select(scalaDot(nme.annotation), name) - def javaDot(name: Name): Tree = Select(rootDot(nme.java), name) def javaLangDot(name: Name): Tree = Select(javaDot(nme.lang), name) - /** Tree representing `java.lang.Object` */ - def javaLangObject(): Tree = javaLangDot(tpnme.Object) + /** Synthetic tree representing `java.lang.Object`. + * The typer will type all references to `java.lang.Object` as `FromJavaObject`. + */ + def ObjectTpt(): Tree = TypeTree(defn.FromJavaObjectType) // javaLangDot(tpnme.Object) /** Tree representing `java.lang.Record` */ def javaLangRecord(): Tree = javaLangDot(tpnme.Record) @@ -107,6 +111,8 @@ object JavaParsers { AppliedTypeTree(scalaDot(tpnme.Array), List(tpt)) def makeTemplate(parents: List[Tree], stats: List[Tree], tparams: List[TypeDef], needsDummyConstr: Boolean): Template = { + def UnitTpt(): Tree = TypeTree(defn.UnitType) + def pullOutFirstConstr(stats: List[Tree]): (Tree, List[Tree]) = stats match { case (meth: DefDef) :: rest if meth.name == nme.CONSTRUCTOR => (meth, rest) case first :: rest => @@ -120,12 +126,12 @@ object JavaParsers { // can call it. // This also avoids clashes between the constructor parameter names and member names. if (needsDummyConstr) { - if (constr1 == EmptyTree) constr1 = makeConstructor(List(), Nil) + if (constr1 == EmptyTree) constr1 = makeConstructor(List(), Nil, Parsers.unimplementedExpr) stats1 = constr1 :: stats1 - constr1 = makeConstructor(List(scalaDot(tpnme.Unit)), tparams, Flags.JavaDefined | Flags.PrivateLocal) + constr1 = makeConstructor(List(UnitTpt()), tparams, EmptyTree, fakeFlags) } else if (constr1 == EmptyTree) { - constr1 = makeConstructor(List(), tparams) + constr1 = makeConstructor(List(), tparams, EmptyTree) } Template(constr1.asInstanceOf[DefDef], parents, Nil, EmptyValDef, stats1) } @@ -133,11 +139,11 @@ object JavaParsers { def makeSyntheticParam(count: Int, tpt: Tree): ValDef = makeParam(nme.syntheticParamName(count), tpt) def makeParam(name: TermName, tpt: Tree): ValDef = - ValDef(name, tpt, EmptyTree).withMods(Modifiers(Flags.JavaDefined | Flags.Param)) + ValDef(name, tpt, EmptyTree).withFlags(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).withMods(Modifiers(flags)) } - DefDef(nme.CONSTRUCTOR, joinParams(tparams, List(vparams)), TypeTree(), EmptyTree).withMods(Modifiers(flags)) + def makeConstructor(formals: List[Tree], tparams: List[TypeDef], body: Tree, flags: FlagSet = Flags.JavaDefined): DefDef = { + val vparams = formals.zipWithIndex.map { case (p, i) => makeSyntheticParam(i + 1, p).withAddedFlags(flags) } + DefDef(nme.CONSTRUCTOR, joinParams(tparams, List(vparams)), TypeTree(), body).withFlags(flags) } // ------------- general parsing --------------------------- @@ -306,7 +312,7 @@ object JavaParsers { if (in.token == QMARK) { val offset = in.offset in.nextToken() - val hi = if (in.token == EXTENDS) { in.nextToken() ; typ() } else javaLangObject() + val hi = if (in.token == EXTENDS) { in.nextToken() ; typ() } else ObjectTpt() val lo = if (in.token == SUPER) { in.nextToken() ; typ() } else EmptyTree atSpan(offset) { /* @@ -507,7 +513,7 @@ object JavaParsers { atSpan(in.offset) { annotations() val name = identForType() - val hi = if (in.token == EXTENDS) { in.nextToken() ; bound() } else javaLangObject() + val hi = if (in.token == EXTENDS) { in.nextToken() ; bound() } else ObjectTpt() TypeDef(name, TypeBoundsTree(EmptyTree, hi)).withMods(Modifiers(flags)) } @@ -568,7 +574,7 @@ object JavaParsers { if in.token == IDENTIFIER && in.name == jnme.RECORDid then in.token = RECORD - def termDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = { + def termDecl(start: Offset, mods: Modifiers, parentToken: Int): List[Tree] = { val inInterface = definesInterface(parentToken) val tparams = if (in.token == LT) typeParams(Flags.JavaDefined | Flags.Param) else List() val isVoid = in.token == VOID @@ -740,17 +746,17 @@ object JavaParsers { ValDef(name, tpt2, if (mods.is(Flags.Param)) EmptyTree else unimplementedExpr).withMods(mods1) } - def memberDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = in.token match + def memberDecl(start: Offset, mods: Modifiers, parentToken: Int): List[Tree] = in.token match case CLASS | ENUM | RECORD | INTERFACE | AT => typeDecl(start, if definesInterface(parentToken) then mods | Flags.JavaStatic else mods) case _ => - termDecl(start, mods, parentToken, parentTParams) + termDecl(start, mods, parentToken) def makeCompanionObject(cdef: TypeDef, statics: List[Tree]): Tree = atSpan(cdef.span) { assert(cdef.span.exists) ModuleDef(cdef.name.toTermName, - makeTemplate(List(), statics, List(), false)).withMods((cdef.mods & Flags.RetainedModuleClassFlags).toTermFlags) + makeTemplate(List(), statics, List(), needsDummyConstr = false)).withMods((cdef.mods & Flags.RetainedModuleClassFlags).toTermFlags) } def addCompanionObject(statics: List[Tree], cdef: TypeDef): List[Tree] = @@ -817,11 +823,11 @@ object JavaParsers { typ() } else - javaLangObject() + ObjectTpt() val interfaces = interfacesOpt() - val (statics, body) = typeBody(CLASS, name, tparams) + val (statics, body) = typeBody(CLASS, name) val cls = atSpan(start, nameOffset) { - TypeDef(name, makeTemplate(superclass :: interfaces, body, tparams, true)).withMods(mods) + TypeDef(name, makeTemplate(superclass :: interfaces, body, tparams, needsDummyConstr = true)).withMods(mods) } addCompanionObject(statics, cls) } @@ -834,7 +840,7 @@ object JavaParsers { val header = formalParams() val superclass = javaLangRecord() // records always extend java.lang.Record val interfaces = interfacesOpt() // records may implement interfaces - val (statics, body) = typeBody(RECORD, name, tparams) + val (statics, body) = typeBody(RECORD, name) // We need to generate accessors for every param, if no method with the same name is already defined @@ -864,7 +870,7 @@ object JavaParsers { parents = superclass :: interfaces, stats = canonicalConstructor :: accessors ::: body, tparams = tparams, - true + needsDummyConstr = true ) ).withMods(mods) } @@ -882,24 +888,24 @@ object JavaParsers { repsep(() => typ(), COMMA) } else - List(javaLangObject()) - val (statics, body) = typeBody(INTERFACE, name, tparams) + List(ObjectTpt()) + val (statics, body) = typeBody(INTERFACE, name) val iface = atSpan(start, nameOffset) { TypeDef( name, - makeTemplate(parents, body, tparams, false)).withMods(mods | Flags.JavaInterface) + makeTemplate(parents, body, tparams, needsDummyConstr = false)).withMods(mods | Flags.JavaInterface) } addCompanionObject(statics, iface) } - def typeBody(leadingToken: Int, parentName: Name, parentTParams: List[TypeDef]): (List[Tree], List[Tree]) = { + def typeBody(leadingToken: Int, parentName: Name): (List[Tree], List[Tree]) = { accept(LBRACE) - val defs = typeBodyDecls(leadingToken, parentName, parentTParams) + val defs = typeBodyDecls(leadingToken, parentName) accept(RBRACE) defs } - def typeBodyDecls(parentToken: Int, parentName: Name, parentTParams: List[TypeDef]): (List[Tree], List[Tree]) = { + def typeBodyDecls(parentToken: Int, parentName: Name): (List[Tree], List[Tree]) = { val inInterface = definesInterface(parentToken) val statics = new ListBuffer[Tree] val members = new ListBuffer[Tree] @@ -915,7 +921,7 @@ object JavaParsers { else { adaptRecordIdentifier() if (in.token == ENUM || in.token == RECORD || definesInterface(in.token)) mods |= Flags.JavaStatic - val decls = memberDecl(start, mods, parentToken, parentTParams) + val decls = memberDecl(start, mods, parentToken) (if (mods.is(Flags.JavaStatic) || inInterface && !(decls exists (_.isInstanceOf[DefDef]))) statics else @@ -925,7 +931,7 @@ object JavaParsers { (statics.toList, members.toList) } def annotationParents: List[Tree] = List( - javaLangObject(), + ObjectTpt(), Select(javaLangDot(nme.annotation), tpnme.Annotation) ) def annotationDecl(start: Offset, mods: Modifiers): List[Tree] = { @@ -933,14 +939,14 @@ object JavaParsers { accept(INTERFACE) val nameOffset = in.offset val name = identForType() - val (statics, body) = typeBody(AT, name, List()) + val (statics, body) = typeBody(AT, name) val constructorParams = body.collect { case dd: DefDef => makeParam(dd.name, dd.tpt) } val constr = DefDef(nme.CONSTRUCTOR, List(constructorParams), TypeTree(), EmptyTree).withMods(Modifiers(Flags.JavaDefined)) - val templ = makeTemplate(annotationParents, constr :: body, List(), true) + val templ = makeTemplate(annotationParents, constr :: body, List(), needsDummyConstr = true) val annot = atSpan(start, nameOffset) { TypeDef(name, templ).withMods(mods | Flags.JavaInterface | Flags.JavaAnnotation) } @@ -968,7 +974,7 @@ object JavaParsers { val (statics, body) = if (in.token == SEMI) { in.nextToken() - typeBodyDecls(ENUM, name, List()) + typeBodyDecls(ENUM, name) } else (List(), List()) @@ -992,7 +998,7 @@ object JavaParsers { Select(New(javaLangDot(tpnme.Enum)), nme.CONSTRUCTOR), List(enumType)), Nil) val enumclazz = atSpan(start, nameOffset) { TypeDef(name, - makeTemplate(superclazz :: interfaces, body, List(), true)).withMods(mods | Flags.JavaEnum) + makeTemplate(superclazz :: interfaces, body, List(), needsDummyConstr = true)).withMods(mods | Flags.JavaEnum) } addCompanionObject(consts ::: statics ::: predefs, enumclazz) } @@ -1092,7 +1098,7 @@ object JavaParsers { */ class OutlineJavaParser(source: SourceFile)(using Context) extends JavaParser(source) with OutlineParserCommon { override def skipBracesHook(): Option[Tree] = None - override def typeBody(leadingToken: Int, parentName: Name, parentTParams: List[TypeDef]): (List[Tree], List[Tree]) = { + override def typeBody(leadingToken: Int, parentName: Name): (List[Tree], List[Tree]) = { skipBraces() (List(EmptyValDef), List(EmptyTree)) } diff --git a/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala b/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala new file mode 100644 index 000000000000..cd8267355201 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala @@ -0,0 +1,56 @@ +package dotty.tools +package dotc +package printing + +import core.* +import Texts.* +import Flags.* +import NameOps.* +import StdNames.* +import Contexts.* +import Symbols.* +import ast.{Trees, untpd} +import Trees.* + +object OutlinePrinter: + def apply(_ctx: Context): Printer = new OutlinePrinter(_ctx) + +/** A printer that elides known standard tree forms from the rhs of def and val. + * Typically used for printing Java trees which elide the rhs. + * Note that there may still be some differences if you compare before and after pickling. + */ +class OutlinePrinter private (_ctx: Context) extends RefinedPrinter(_ctx) { + + /** print the symbol infos of type params for the fake java constructor */ + def shouldShowInfo(tsym: Symbol): Boolean = + tsym != NoSymbol && { + val ctor = tsym.owner + ctor.isAllOf(JavaDefined | PrivateLocal | Invisible) && ctor.isConstructor + } + + override def paramsText[T <: Untyped](params: ParamClause[T]): Text = (params: @unchecked) match + case untpd.TypeDefs(tparams) if shouldShowInfo(tparams.head.symbol) => + "[" ~ toText(tparams.map(_.symbol.info), ", ") ~ "]" + case _ => super.paramsText(params) + + /* Typical patterns seen in output of typer for Java code, plus the output of unpickling an ELIDED tree */ + def isElidableExpr[T <: Untyped](tree: Tree[T]): Boolean = tree match { + case tree if tree.isEmpty => false + case tree: Ident[T] if tree.name == nme.WILDCARD => true // `ELIDED exprType` + case tree: Literal[T] => true // e.g. `()` + case tree: Select[T] if tree.symbol == defn.Predef_undefined => true // e.g. `Predef.???` + case Apply(Select(tree: New[T], nme.CONSTRUCTOR), Nil) + if tree.tpt.typeOpt.typeSymbol.is(Module) => true // e.g. `new foo.Foo$()` (rhs of a module val) + case _ => + sys.error(s"Unexpected tree in OutlinePrinter: ${tree.show}, $tree") + false + } + + override protected def rhsValDef[T <: Untyped](tree: ValDef[T]): Text = + if isElidableExpr(tree.rhs) then " = " ~ "elided" ~ "[" ~ toText(tree.tpt) ~ "]" + else super.rhsValDef(tree) + + override protected def rhsDefDef[T <: Untyped](tree: DefDef[T]): Text = + if isElidableExpr(tree.rhs) then " = " ~ "elided" ~ "[" ~ toText(tree.tpt) ~ "]" + else super.rhsDefDef(tree) +} diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 8ad1188a3e7e..44ccf5c3c9fe 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -28,6 +28,7 @@ import config.{Config, Feature} import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef} +import dotty.tools.dotc.parsing.JavaParsers class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { @@ -920,7 +921,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { dclTextOr(tree) { modText(tree.mods, tree.symbol, keywordStr(if (tree.mods.is(Mutable)) "var" else "val"), isType = false) ~~ valDefText(nameIdText(tree)) ~ optAscription(tree.tpt) ~ - withEnclosingDef(tree) { optText(tree.rhs)(" = " ~ _) } + withEnclosingDef(tree) { rhsValDef(tree) } } } @@ -977,11 +978,19 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { coreSig ~ optAscription(tree.tpt) - ~ optText(tree.rhs)(" = " ~ keywordText("macro ").provided(tree.symbol.isScala2Macro) ~ _) + ~ rhsDefDef(tree) } } } + /** Inspect the rhs of a ValDef, overridden in OutlinePrinter */ + protected def rhsValDef[T <: Untyped](tree: ValDef[T]): Text = + optText(tree.rhs)(" = " ~ _) + + /** Inspect the rhs of a DefDef, overridden in OutlinePrinter */ + protected def rhsDefDef[T <: Untyped](tree: DefDef[T]): Text = + optText(tree.rhs)(" = " ~ keywordText("macro ").provided(tree.symbol.isScala2Macro) ~ _) + protected def toTextTemplate(impl: Template, ofNew: Boolean = false): Text = { val Template(constr @ DefDef(_, paramss, _, _), _, self, _) = impl val tparamsTxt = withEnclosingDef(constr) { @@ -1007,10 +1016,18 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val (params, rest) = impl.body partition { case stat: TypeDef => stat.symbol.is(Param) case stat: ValOrDefDef => - stat.symbol.is(ParamAccessor) && !stat.symbol.isSetter + val sym = stat.symbol + sym.is(ParamAccessor) && !sym.isSetter + || sym.isAllOf(JavaParsers.fakeFlags | Param) case _ => false } - params ::: rest + val params0 = + if constr.symbol.isAllOf(JavaParsers.fakeFlags) then + // filter out fake param accessors + params.filterNot(_.symbol.isAllOf(JavaParsers.fakeFlags | Param)) + else + params + params0 ::: rest } else impl.body diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index 1eaf0a58fda2..0be66828d58c 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -10,7 +10,7 @@ import config.Printers.{noPrinter, pickling} import config.Feature import java.io.PrintStream import io.ClassfileWriterOps -import StdNames.str +import StdNames.{str, nme} import Periods.* import Phases.* import Symbols.* @@ -20,6 +20,7 @@ import collection.mutable import util.concurrent.{Executor, Future} import compiletime.uninitialized import dotty.tools.io.JarArchive +import dotty.tools.dotc.printing.OutlinePrinter object Pickler { val name: String = "pickler" @@ -86,6 +87,10 @@ class Pickler extends Phase { Pickler.ParallelPickling && !ctx.settings.YtestPickler.value && !ctx.settings.YjavaTasty.value // disable parallel pickling when `-Yjava-tasty` is set (internal testing only) + private def printerContext(isOutline: Boolean)(using Context): Context = + if isOutline then ctx.fresh.setPrinterFn(OutlinePrinter(_)) + else ctx + override def run(using Context): Unit = { val unit = ctx.compilationUnit pickling.println(i"unpickling in run ${ctx.runId}") @@ -94,7 +99,8 @@ class Pickler extends Phase { cls <- dropCompanionModuleClasses(topLevelClasses(unit.tpdTree)) tree <- sliceTopLevel(unit.tpdTree, cls) do - if ctx.settings.YtestPickler.value then beforePickling(cls) = tree.show + if ctx.settings.YtestPickler.value then beforePickling(cls) = + tree.show(using printerContext(unit.typedAsJava)) val sourceRelativePath = val reference = ctx.settings.sourceroot.value @@ -242,11 +248,15 @@ class Pickler extends Phase { pickling.println("************* entered toplevel ***********") val rootCtx = ctx for ((cls, (unit, unpickler)) <- unpicklers) do + val testJava = unit.typedAsJava + if testJava then + if unpickler.unpickler.nameAtRef.contents.exists(_ == nme.FromJavaObject) then + report.error(em"Pickled reference to FromJavaObject in Java defined $cls in ${cls.source}") val unpickled = unpickler.rootTrees val freshUnit = CompilationUnit(rootCtx.compilationUnit.source) freshUnit.needsCaptureChecking = unit.needsCaptureChecking freshUnit.knowsPureFuns = unit.knowsPureFuns - inContext(rootCtx.fresh.setCompilationUnit(freshUnit)): + inContext(printerContext(testJava)(using rootCtx.fresh.setCompilationUnit(freshUnit))): testSame(i"$unpickled%\n%", beforePickling(cls), cls) private def testSame(unpickled: String, previous: String, cls: ClassSymbol)(using Context) = diff --git a/sbt-test/pipelining/Yjava-tasty-annotation/b-alt/.keep b/sbt-test/pipelining/Yjava-tasty-annotation/b-alt/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-annotation/build.sbt b/sbt-test/pipelining/Yjava-tasty-annotation/build.sbt index 9299d94c060d..18f6b8224968 100644 --- a/sbt-test/pipelining/Yjava-tasty-annotation/build.sbt +++ b/sbt-test/pipelining/Yjava-tasty-annotation/build.sbt @@ -11,3 +11,11 @@ lazy val b = project.in(file("b")) Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-annotation-java-tasty.jar")), scalacOptions += "-Ycheck:all", ) + +// same as b, but adds the real classes to the classpath instead of the tasty jar +lazy val bAlt = project.in(file("b-alt")) + .settings( + Compile / sources := (b / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-annotation-classes")), + scalacOptions += "-Ycheck:all", + ) diff --git a/sbt-test/pipelining/Yjava-tasty-annotation/test b/sbt-test/pipelining/Yjava-tasty-annotation/test index 6105296d455b..6f7f57e91ab1 100644 --- a/sbt-test/pipelining/Yjava-tasty-annotation/test +++ b/sbt-test/pipelining/Yjava-tasty-annotation/test @@ -1,3 +1,5 @@ > a/compile # Test depending on a java compiled annotation through TASTy > b/compile +# double check against the real java classes +> bAlt/compile diff --git a/sbt-test/pipelining/Yjava-tasty-enum/b-alt/.keep b/sbt-test/pipelining/Yjava-tasty-enum/b-alt/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-enum/build.sbt b/sbt-test/pipelining/Yjava-tasty-enum/build.sbt index 0c95d8318913..aca2391987e9 100644 --- a/sbt-test/pipelining/Yjava-tasty-enum/build.sbt +++ b/sbt-test/pipelining/Yjava-tasty-enum/build.sbt @@ -17,3 +17,15 @@ lazy val b = project.in(file("b")) fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-classes"), // make sure the java classes are visible at runtime ) + +// same as b, but adds the real classes to the classpath instead of the tasty jar +lazy val bAlt = project.in(file("b-alt")) + .settings( + Compile / sources := (b / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-classes")), + scalacOptions += "-Ycheck:all", + ) + .settings( + fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics + Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-classes"), // make sure the java classes are visible at runtime + ) diff --git a/sbt-test/pipelining/Yjava-tasty-enum/test b/sbt-test/pipelining/Yjava-tasty-enum/test index fe04b1a7c7ea..fa53c47aea59 100644 --- a/sbt-test/pipelining/Yjava-tasty-enum/test +++ b/sbt-test/pipelining/Yjava-tasty-enum/test @@ -1,3 +1,5 @@ > a/compile # test depending on a java compiled enum through TASTy > b/run +# double check against the real java classes +> bAlt/run diff --git a/sbt-test/pipelining/Yjava-tasty-from-tasty/b-alt/.keep b/sbt-test/pipelining/Yjava-tasty-from-tasty/b-alt/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-from-tasty/build.sbt b/sbt-test/pipelining/Yjava-tasty-from-tasty/build.sbt index 570e72a40c1b..e4b15d3d9c7e 100644 --- a/sbt-test/pipelining/Yjava-tasty-from-tasty/build.sbt +++ b/sbt-test/pipelining/Yjava-tasty-from-tasty/build.sbt @@ -33,3 +33,17 @@ lazy val b = project.in(file("b")) // make sure the java classes are visible at runtime Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-pre-classes"), ) + +// same as b, but adds the real classes to the classpath instead of the tasty jar +lazy val bAlt = project.in(file("b-alt")) + .settings( + scalacOptions += "-Ycheck:all", + Compile / sources := (b / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-pre-classes")), + ) + .settings( + // we have to fork the JVM if we actually want to run the code with correct failure semantics + fork := true, + // make sure the java classes are visible at runtime + Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-pre-classes"), + ) diff --git a/sbt-test/pipelining/Yjava-tasty-from-tasty/test b/sbt-test/pipelining/Yjava-tasty-from-tasty/test index 5c08ed4c4458..b4ce2965b995 100644 --- a/sbt-test/pipelining/Yjava-tasty-from-tasty/test +++ b/sbt-test/pipelining/Yjava-tasty-from-tasty/test @@ -3,3 +3,5 @@ > a_from_tasty/compile # test java tasty is still written even with -from-tasty > b/run +# double check against the real java classes +> bAlt/run diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a-check/.keep b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a-check/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/A.java b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/A.java new file mode 100644 index 000000000000..b798a9dedce9 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/A.java @@ -0,0 +1,56 @@ +// this test ensures that Object can accept Any from Scala +// see Definitions.FromJavaObjectSymbol +package a; + +public class A { + + public static class Inner extends Object { + public T field1; + public T getter1() { return field1; } + public Object field2; + public Object getter2() { return field2; } + + public Inner(T param1, Object param2) { + this.field1 = param1; + this.field2 = param2; + } + + public void meth1(T arg) {} + public void meth2(U arg) {} + } + + public static class Inner_sel extends java.lang.Object { + public T field1; + public T getter1() { return field1; } + public java.lang.Object field2; + public java.lang.Object getter2() { return field2; } + + public Inner_sel(T param1, java.lang.Object param2) { + this.field1 = param1; + this.field2 = param2; + } + + public void meth1(T arg) {} + public void meth2(U arg) {} + } + + // 1. At the top level: + public void meth1(Object arg) {} + public void meth1_sel(java.lang.Object arg) {} + public void meth2(T arg) {} // T implicitly extends Object + + // 2. In a class type parameter: + public void meth3(scala.collection.immutable.List arg) {} + public void meth3_sel(scala.collection.immutable.List arg) {} + public void meth4(scala.collection.immutable.List arg) {} + + // 3. As the type parameter of an array: + public void meth5(Object[] arg) {} + public void meth5_sel(java.lang.Object[] arg) {} + public void meth6(T[] arg) {} + + // 4. As the repeated argument of a varargs method: + public void meth7(Object... args) {} + public void meth7_sel(java.lang.Object... args) {} + public void meth8(T... args) {} +} diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/AImport.java b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/AImport.java new file mode 100644 index 000000000000..6dd2608883d8 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/AImport.java @@ -0,0 +1,35 @@ +// this test ensures that Object can accept Any from Scala +// see Definitions.FromJavaObjectSymbol +package a; + +import java.lang.Object; + +// same signatures that reference Object explicitly from A.java, but with the java.lang.Object import +public class AImport { + + public static class Inner extends Object { + public T field1; + public T getter1() { return field1; } + public Object field2; + public Object getter2() { return field2; } + + public Inner(T param1, Object param2) { + this.field1 = param1; + this.field2 = param2; + } + + public void meth1(T arg) {} + } + + // 1. At the top level: + public void meth1(Object arg) {} + + // 2. In a class type parameter: + public void meth3(scala.collection.immutable.List arg) {} + + // 3. As the type parameter of an array: + public void meth5(Object[] arg) {} + + // 4. As the repeated argument of a varargs method: + public void meth7(Object... args) {} +} diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/package.scala b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/package.scala new file mode 100644 index 000000000000..93f99e9892fe --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/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-fromjavaobject/b-alt/.keep b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b-alt/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/B.scala b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/B.scala new file mode 100644 index 000000000000..b2a1c300bfd0 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/B.scala @@ -0,0 +1,56 @@ +package b + +import a.A + +// keep in sync with Bexplicit.scala +object B { + + val newA = new A + + val newAInner = new A.Inner[Int](23, true) + val newAInner_sel = new A.Inner_sel[Int](23, true) + + @main + def test = { + newA.meth1(1) // OK + newA.meth1_sel(1) // OK + newA.meth2(1) // OK + newA.meth3(List[Int](1)) // OK + newA.meth3_sel(List[Int](1)) // OK + newA.meth4(List[Int](1)) // OK + newA.meth5(Array[Object]("abc")) // OK + newA.meth5_sel(Array[Object]("abc")) // OK + newA.meth6(Array[String]("abc")) // Ok + // newA.meth5(Array[Int](1)) // error: Array[Int] is not a subtype of Array[Object] + // newA.meth6(Array[Int](1)) // error: Array[Int] is not a subtype of Array[T & Object] + newA.meth7(1) // OK (creates a reference array) + newA.meth7_sel(1) // OK (creates a reference array) + newA.meth8(1) // OK (creates a primitive array and copies it into a reference array at Erasure) + val ai = Array[Int](1) + newA.meth7(ai: _*) // OK (will copy the array at Erasure) + newA.meth7_sel(ai: _*) // OK (will copy the array at Erasure) + newA.meth8(ai: _*) // OK (will copy the array at Erasure) + + newAInner.meth1(1) // OK + newAInner.meth2(1) // OK + newAInner_sel.meth1(1) // OK + newAInner_sel.meth2(1) // OK + + assert((newAInner.field1: Int) == 23) // OK + newAInner.field1 = 31 // OK + assert((newAInner.getter1: Int) == 31) // OK + assert(newAInner.field2 == true) // OK + newAInner.field2 = false // OK + assert(newAInner.getter2 == false) // OK + + assert((newAInner_sel.field1: Int) == 23) // OK + newAInner_sel.field1 = 31 // OK + assert((newAInner_sel.getter1: Int) == 31) // OK + assert(newAInner_sel.field2 == true) // OK + newAInner_sel.field2 = false // OK + assert(newAInner_sel.getter2 == false) // OK + + BImport.testImport() // OK + } +} + diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/BImport.scala b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/BImport.scala new file mode 100644 index 000000000000..17d3fbca1591 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/BImport.scala @@ -0,0 +1,31 @@ +package b + +import a.AImport + +object BImport { + + val newA = new AImport + + val newAInner = new AImport.Inner[Int](23, true) + + def testImport() = { + newA.meth1(1) // OK + newA.meth3(List[Int](1)) // OK + newA.meth5(Array[Object]("abc")) // OK + // newA.meth5(Array[Int](1)) // error: Array[Int] is not a subtype of Array[Object] + newA.meth7(1) // OK (creates a reference array) + val ai = Array[Int](1) + newA.meth7(ai: _*) // OK (will copy the array at Erasure) + + newAInner.meth1(1) // OK + + assert((newAInner.field1: Int) == 23) // OK + newAInner.field1 = 31 // OK + assert((newAInner.getter1: Int) == 31) // OK + + assert(newAInner.field2 == true) // OK + newAInner.field2 = false // OK + assert(newAInner.getter2 == false) // OK + } +} + diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/build.sbt b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/build.sbt new file mode 100644 index 000000000000..6738db3016fa --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/build.sbt @@ -0,0 +1,57 @@ +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", + Compile / classDirectory := ((ThisBuild / baseDirectory).value / "a-enum-classes"), // send classfiles to a different directory + ) + +// compiles the same sources as a, but with -Ytest-pickler +lazy val aCheck = project.in(file("a-check")) + .settings( + scalacOptions += "-Ytest-pickler", // check that the pickler is correct + Compile / sources := (a / Compile / sources).value, // use the same sources as a + 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-2.jar").toString), + Compile / classDirectory := ((ThisBuild / baseDirectory).value / "a-enum-classes-2"), // 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", + ) + .settings( + fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics + Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-classes"), // make sure the java classes are visible at runtime + ) + +// same as b, but adds the real classes to the classpath instead of the tasty jar +lazy val bAlt = project.in(file("b-alt")) + .settings( + Compile / sources := (b / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-classes")), + scalacOptions += "-Ycheck:all", + ) + .settings( + fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics + Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-classes"), // make sure the java classes are visible at runtime + ) + +// negative compilation tests +lazy val c = project.in(file("c")) + .settings( + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-java-tasty.jar")), + scalacOptions += "-Ycheck:all", + ) + +// same as c, but adds the real classes to the classpath instead of the tasty jar +lazy val cAlt = project.in(file("c-alt")) + .settings( + Compile / sources := (c / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-classes")), + scalacOptions += "-Ycheck:all", + ) diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c-alt/.keep b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c-alt/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c/src/main/scala/c/C.scala b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c/src/main/scala/c/C.scala new file mode 100644 index 000000000000..05dff30bd63e --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c/src/main/scala/c/C.scala @@ -0,0 +1,16 @@ +package c + +import a.A + +object C { + + val newA = new A + + @main + def test = { + newA.meth5(Array[Int](1)) // error: Array[Int] is not a subtype of Array[Object] + newA.meth5_sel(Array[Int](1)) // error: Array[Int] is not a subtype of Array[Object] + newA.meth6(Array[Int](1)) // error: Array[Int] is not a subtype of Array[T & Object] + } +} + diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c/src/main/scala/c/CImport.scala b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c/src/main/scala/c/CImport.scala new file mode 100644 index 000000000000..dc27126060b8 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c/src/main/scala/c/CImport.scala @@ -0,0 +1,14 @@ +package c + +import a.AImport + +object CImport { + + val newA = new AImport + + @main + def test = { + newA.meth5(Array[Int](1)) // error: Array[Int] is not a subtype of Array[Object] + } +} + diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/project/DottyInjectedPlugin.scala b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..69f15d168bfc --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/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-fromjavaobject/test b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/test new file mode 100644 index 000000000000..7a34a0cb5ec1 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/test @@ -0,0 +1,12 @@ +# compile Java sources, and send them to TASTy +> a/compile +# compile Java sources, and check that they are pickled correctly +> aCheck/compile +# test depending on a java compiled enum through TASTy +> b/run +# double check against the real java classes +> bAlt/run +# check that java Array T is Array T & Object +-> c/compile +# double check against the real java classes +-> cAlt/compile diff --git a/sbt-test/pipelining/Yjava-tasty-generic/b-alt/.keep b/sbt-test/pipelining/Yjava-tasty-generic/b-alt/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-generic/build.sbt b/sbt-test/pipelining/Yjava-tasty-generic/build.sbt index 0c8a5c55fe7e..07e2ea56fbaa 100644 --- a/sbt-test/pipelining/Yjava-tasty-generic/build.sbt +++ b/sbt-test/pipelining/Yjava-tasty-generic/build.sbt @@ -15,3 +15,15 @@ lazy val b = project.in(file("b")) fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-generic-classes"), // make sure the java classes are visible at runtime ) + +// same as b, but adds the real classes to the classpath instead of the tasty jar +lazy val bAlt = project.in(file("b-alt")) + .settings( + Compile / sources := (b / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-generic-classes")), + scalacOptions += "-Ycheck:all", + ) + .settings( + fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics + Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-generic-classes"), // make sure the java classes are visible at runtime + ) diff --git a/sbt-test/pipelining/Yjava-tasty-generic/test b/sbt-test/pipelining/Yjava-tasty-generic/test index cbe3e14572a8..2265d58a8262 100644 --- a/sbt-test/pipelining/Yjava-tasty-generic/test +++ b/sbt-test/pipelining/Yjava-tasty-generic/test @@ -1,3 +1,5 @@ > a/compile # Test depending on a java generic class through TASTy > b/run +# double check against the real java classes +> bAlt/run diff --git a/sbt-test/pipelining/Yjava-tasty-result-types/b-alt/.keep b/sbt-test/pipelining/Yjava-tasty-result-types/b-alt/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-result-types/build.sbt b/sbt-test/pipelining/Yjava-tasty-result-types/build.sbt index 8f9e782f8810..512344f0635b 100644 --- a/sbt-test/pipelining/Yjava-tasty-result-types/build.sbt +++ b/sbt-test/pipelining/Yjava-tasty-result-types/build.sbt @@ -15,3 +15,15 @@ lazy val b = project.in(file("b")) fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-result-types-classes"), // make sure the java classes are visible at runtime ) + +// same as b, but adds the real classes to the classpath instead of the tasty jar +lazy val bAlt = project.in(file("b-alt")) + .settings( + Compile / sources := (b / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-result-types-classes")), + scalacOptions += "-Ycheck:all", + ) + .settings( + fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics + Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-result-types-classes"), // make sure the java classes are visible at runtime + ) diff --git a/sbt-test/pipelining/Yjava-tasty-result-types/test b/sbt-test/pipelining/Yjava-tasty-result-types/test index c1cbbb1f2fe5..4a758ea9991d 100644 --- a/sbt-test/pipelining/Yjava-tasty-result-types/test +++ b/sbt-test/pipelining/Yjava-tasty-result-types/test @@ -1,3 +1,5 @@ > a/compile # Test depending on a java static final result, and method result through TASTy > b/run +# double check against the real java classes +> bAlt/run diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index adadd3267aac..ce3e1a852c74 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -81,8 +81,9 @@ Standard-Section: "ASTs" TopLevelStat* Param = TypeParam TermParam Template = TEMPLATE Length TypeParam* TermParam* parent_Term* Self? - Stat* -- [typeparams] paramss extends parents { self => stats }, where Stat* always starts with the primary constructor. + EndParents? Stat* -- [typeparams] paramss extends parents { self => stats }, where Stat* always starts with the primary constructor. Self = SELFDEF selfName_NameRef selfType_Term -- selfName : selfType + EndParents = SPLITCLAUSE -- explicitly end the template header, e.g. if there is no primary constructor Term = Path -- Paths represent both types and terms IDENT NameRef Type -- Used when term ident’s type is not a TermRef