diff --git a/project/DottySupport.scala b/project/DottySupport.scala index de191207d7d6..11a3d0818f59 100644 --- a/project/DottySupport.scala +++ b/project/DottySupport.scala @@ -12,8 +12,8 @@ import sbt.librarymanagement.{ * Settings to support validation of TastyUnpickler against the release of dotty with the matching TASTy version */ object TastySupport { - val supportedTASTyRelease = "3.0.0-M3" // TASTy version 26.1 - val scala3Compiler = "org.scala-lang" % "scala3-compiler_3.0.0-M3" % supportedTASTyRelease + val supportedTASTyRelease = "3.0.0-RC1" // TASTy version 28.0.1 + val scala3Compiler = "org.scala-lang" % "scala3-compiler_3.0.0-RC1" % supportedTASTyRelease } /** Settings needed to compile with Dotty, diff --git a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala index aecc27ffd2b0..8a10f400b61a 100644 --- a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala +++ b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala @@ -123,12 +123,9 @@ class TreeUnpickler[Tasty <: TastyUniverse]( def skipParams(): Unit = while ({ val tag = nextByte - tag == PARAM || tag == TYPEPARAM || tag == PARAMEND + tag == PARAM || tag == TYPEPARAM || tag == EMPTYCLAUSE || tag == SPLITCLAUSE }) skipTree() - def skipTypeParams(): Unit = - while (nextByte === TYPEPARAM) skipTree() - /** Record all directly nested definitions and templates in current tree * as `OwnerTree`s in `buf`. * A complication concerns member definitions. These are lexically nested in a @@ -689,118 +686,152 @@ class TreeUnpickler[Tasty <: TastyUniverse]( case _ => val start = currentAddr cycleAtAddr(start) = Tombstone - val noCycle = readNewMember() + val noCycle = initializeMember() cycleAtAddr.remove(start) noCycle } - private def readNewMember()(implicit ctx: Context): NoCycle = { - val symAddr = currentAddr - val tag = readByte() - val end = readEnd() - val tname = readTastyName() - val sym = symAtAddr(symAddr) - val repr = sym.repr + private def initializeMember()(implicit ctx: Context): NoCycle = { + val symAddr = currentAddr + val tag = readByte() + val end = readEnd() + val tname = readTastyName() + val sym = symAtAddr(symAddr) + + def readParamss()(implicit ctx: Context): List[List[NoCycle]] = { + def readRest() = { + if (nextByte == SPLITCLAUSE) readByte() + readParamss() + } + nextByte match { + case PARAM => readParams[NoCycle](PARAM) :: readRest() + case TYPEPARAM => readParams[NoCycle](TYPEPARAM) :: readRest() + case EMPTYCLAUSE => readByte(); Nil :: readRest() + case _ => Nil + } + } - ctx.log(s"$symAddr completing ${showSym(sym)} in scope ${showSym(ctx.owner)}") + def checkUnsupportedFlags(unsupported: TastyFlagSet)(implicit ctx: Context): Unit = { + unsupportedWhen(unsupported.hasFlags, s"${showTasty(unsupported)} ${sym.kindString} $tname") + } - def readParamss(implicit ctx: Context): List[List[NoCycle/*ValDef*/]] = nextByte match { - case PARAM | PARAMEND => - readParams[NoCycle](PARAM) :: - (if (nextByte == PARAMEND) { readByte(); readParamss } else Nil) + def DefDef(repr: TastyRepr, localCtx: Context)(implicit ctx: Context): Unit = { + val isMacro = repr.originalFlagSet.is(Erased | Macro) + checkUnsupportedFlags(repr.tastyOnlyFlags &~ (Extension | Exported | Infix | optFlag(isMacro)(Erased))) + val isCtor = sym.isConstructor + val paramDefss = readParamss()(localCtx).map(_.map(symFromNoCycle)) + val typeParams = { + // A type parameter list must be non-empty and with type symbols + val first = paramDefss.take(1) + if (first.exists(_.exists(_.isType))) first.head else Nil + } + val vparamss = { + // A value parameter list may be empty, or filled with term symbols + val valueClauses = paramDefss.drop(if (typeParams.isEmpty) 0 else 1) + val hasTypeParams = valueClauses.exists(_.exists(_.isType)) + unsupportedWhen(hasTypeParams, { + val noun = ( + if (isCtor) "constructor" + else if (repr.tastyOnlyFlags.is(Extension)) "extension method" + else "method" + ) + s"$noun with unmergeable type parameters: $tname" + }) + valueClauses + } + val tpt = readTpt()(localCtx) + if (isMacro) { + val impl = tpd.Macro(readTerm()(ctx.addMode(ReadMacro))) + val annot = symbolTable.AnnotationInfo( + atp = symbolTable.definitions.MacroImplLocationAnnotation.tpe, + args = List(impl), + assocs = Nil + ) + sym.addAnnotation(annot) + } + val valueParamss = normalizeIfConstructor(vparamss, isCtor) + val resType = effectiveResultType(sym, typeParams, tpt.tpe) + ctx.setInfo(sym, defn.DefDefType(if (isCtor) Nil else typeParams, valueParamss, resType)) + } - case _ => Nil + def ValDef(repr: TastyRepr, localCtx: Context)(implicit ctx: Context): Unit = { + // valdef in TASTy is either a singleton object or a method forwarder to a local value. + checkUnsupportedFlags(repr.tastyOnlyFlags &~ (Enum | Extension | Exported)) + val tpe = readTpt()(localCtx).tpe + ctx.setInfo(sym, + if (repr.originalFlagSet.is(SingletonEnumFlags)) { + val enumClass = sym.objectImplementation + val selfTpe = defn.SingleType(sym.owner.thisPrefix, sym) + val ctor = ctx.unsafeNewSymbol( + owner = enumClass, + name = TastyName.Constructor, + flags = Method, + info = defn.DefDefType(Nil, Nil :: Nil, selfTpe) + ) + enumClass.typeOfThis = selfTpe + ctx.setInfo(enumClass, defn.ClassInfoType(intersectionParts(tpe), ctor :: Nil, enumClass)) + prefixedRef(sym.owner.thisPrefix, enumClass) + } + else if (sym.isFinal && isConstantType(tpe)) defn.InlineExprType(tpe) + else if (sym.isMethod) defn.ExprType(tpe) + else tpe + ) } - def checkUnsupportedFlags(unsupported: TastyFlagSet)(implicit ctx: Context): Unit = { - unsupportedWhen(unsupported.hasFlags, s"${showTasty(unsupported)} ${sym.kindString} $tname") + def TypeDef(repr: TastyRepr, localCtx: Context)(implicit ctx: Context): Unit = { + val allowedShared = Enum | Opaque | Infix + val allowedTypeFlags = allowedShared | Exported + val allowedClassFlags = allowedShared | Open | Transparent + if (sym.isClass) { + checkUnsupportedFlags(repr.tastyOnlyFlags &~ allowedClassFlags) + sym.owner.ensureCompleted() + readTemplate()(localCtx) + } + else { + checkUnsupportedFlags(repr.tastyOnlyFlags &~ allowedTypeFlags) + val rhs = readTpt()(if (repr.originalFlagSet.is(Opaque)) localCtx.addMode(OpaqueTypeDef) else localCtx) + val info = + if (repr.originalFlagSet.is(Opaque)) { + val (info, alias) = defn.OpaqueTypeToBounds(rhs.tpe) + ctx.markAsOpaqueType(sym, alias) + info + } + else rhs.tpe + ctx.setInfo(sym, defn.NormalisedBounds(info, sym)) + if (sym.is(Param)) sym.reset(Private | Protected) + } } - try { + def TermParam(repr: TastyRepr, localCtx: Context)(implicit ctx: Context): Unit = { + checkUnsupportedFlags(repr.tastyOnlyFlags &~ (ParamAlias | Exported)) + val tpt = readTpt()(localCtx) + ctx.setInfo(sym, + if (nothingButMods(end) && sym.not(ParamSetter)) tpt.tpe + else defn.ExprType(tpt.tpe)) + } + + def initialize()(implicit ctx: Context): Unit = { + val repr = sym.rawInfo match { + case repr: TastyRepr => repr + case _ => return () // nothing to do here (assume correctly initalised) + } + ctx.log(s"$symAddr completing ${showSym(sym)} in scope ${showSym(ctx.owner)}") val localCtx = ctx.withOwner(sym) tag match { - case DEFDEF => - val isMacro = repr.originalFlagSet.is(Erased | Macro) - checkUnsupportedFlags(repr.tastyOnlyFlags &~ (Extension | Exported | Infix | optFlag(isMacro)(Erased))) - val isCtor = sym.isConstructor - val typeParams = { - if (isCtor) { - skipTypeParams() - sym.owner.typeParams - } - else { - readParams[NoCycle](TYPEPARAM)(localCtx).map(symFromNoCycle) - } - } - val vparamss = readParamss(localCtx) - val tpt = readTpt()(localCtx) - if (isMacro) { - val impl = tpd.Macro(readTerm()(ctx.addMode(ReadMacro))) - val annot = symbolTable.AnnotationInfo( - atp = symbolTable.definitions.MacroImplLocationAnnotation.tpe, - args = List(impl), - assocs = Nil - ) - sym.addAnnotation(annot) - } - val valueParamss = normalizeIfConstructor(vparamss.map(_.map(symFromNoCycle)), isCtor) - val resType = effectiveResultType(sym, typeParams, tpt.tpe) - ctx.setInfo(sym, defn.DefDefType(if (isCtor) Nil else typeParams, valueParamss, resType)) - case VALDEF => // valdef in TASTy is either a singleton object or a method forwarder to a local value. - checkUnsupportedFlags(repr.tastyOnlyFlags &~ (Enum | Extension | Exported)) - val tpe = readTpt()(localCtx).tpe - ctx.setInfo(sym, - if (repr.originalFlagSet.is(SingletonEnumFlags)) { - val enumClass = sym.objectImplementation - val selfTpe = defn.SingleType(sym.owner.thisPrefix, sym) - val ctor = ctx.unsafeNewSymbol( - owner = enumClass, - name = TastyName.Constructor, - flags = Method, - info = defn.DefDefType(Nil, Nil :: Nil, selfTpe) - ) - enumClass.typeOfThis = selfTpe - ctx.setInfo(enumClass, defn.ClassInfoType(intersectionParts(tpe), ctor :: Nil, enumClass)) - prefixedRef(sym.owner.thisPrefix, enumClass) - } - else if (sym.isFinal && isConstantType(tpe)) defn.InlineExprType(tpe) - else if (sym.isMethod) defn.ExprType(tpe) - else tpe - ) - case TYPEDEF | TYPEPARAM => - val allowedShared = Enum | Opaque | Infix - val allowedTypeFlags = allowedShared | Exported - val allowedClassFlags = allowedShared | Open | Transparent - if (sym.isClass) { - checkUnsupportedFlags(repr.tastyOnlyFlags &~ allowedClassFlags) - sym.owner.ensureCompleted() - readTemplate()(localCtx) - } - else { - checkUnsupportedFlags(repr.tastyOnlyFlags &~ allowedTypeFlags) - val rhs = readTpt()(if (repr.originalFlagSet.is(Opaque)) localCtx.addMode(OpaqueTypeDef) else localCtx) - val info = - if (repr.originalFlagSet.is(Opaque)) { - val (info, alias) = defn.OpaqueTypeToBounds(rhs.tpe) - ctx.markAsOpaqueType(sym, alias) - info - } - else rhs.tpe - ctx.setInfo(sym, defn.NormalisedBounds(info, sym)) - if (sym.is(Param)) sym.reset(Private | Protected) - // sym.resetFlag(Provisional) - } - case PARAM => - checkUnsupportedFlags(repr.tastyOnlyFlags &~ (ParamAlias | Exported)) - val tpt = readTpt()(localCtx) - ctx.setInfo(sym, - if (nothingButMods(end) && sym.not(ParamSetter)) tpt.tpe - else defn.ExprType(tpt.tpe)) + case DEFDEF => DefDef(repr, localCtx) + case VALDEF => ValDef(repr, localCtx) + case TYPEDEF | TYPEPARAM => TypeDef(repr, localCtx) + case PARAM => TermParam(repr, localCtx) } + } + + try { + initialize() ctx.log(s"$symAddr @@@ ${showSym(sym)}.tpe =:= '[${if (sym.isType) sym.tpe else sym.info}]; owned by ${location(sym.owner)}") - goto(end) NoCycle(at = symAddr) - } catch ctx.onCompletionError(sym) + } + catch ctx.onCompletionError(sym) + finally goto(end) } private def readTemplate()(implicit ctx: Context): Unit = { diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala index 8aecbd17fd0d..ab931b219fbf 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala @@ -64,8 +64,13 @@ trait SymbolOps { self: TastyUniverse => * @todo adapt callsites and type so that this property is more safe to call (barring mutation from uncontrolled code) */ def repr: TastyRepr = { - require(sym.rawInfo.isInstanceOf[TastyRepr], s"Expected ${u.typeOf[TastyRepr]}, is ${u.showRaw(sym.rawInfo)} ") - sym.rawInfo.asInstanceOf[TastyRepr] + try sym.rawInfo.asInstanceOf[TastyRepr] + catch { + case err: ClassCastException => + val raw = u.showRaw(sym.rawInfo) + val tastyRepr = u.typeOf[TastyRepr] + throw new AssertionError(s"$sym is already completed. Expected $tastyRepr, is $raw.") + } } def ensureCompleted(): Unit = { diff --git a/src/compiler/scala/tools/tasty/TastyFormat.scala b/src/compiler/scala/tools/tasty/TastyFormat.scala index dbf679f83d34..7aae96aebc15 100644 --- a/src/compiler/scala/tools/tasty/TastyFormat.scala +++ b/src/compiler/scala/tools/tasty/TastyFormat.scala @@ -14,49 +14,166 @@ package scala.tools.tasty object TastyFormat { + /** The first four bytes of a TASTy file, followed by four values: + * - `MajorVersion: Int` - see definition in `TastyFormat` + * - `MinorVersion: Int` - see definition in `TastyFormat` + * - `ExperimentalVersion: Int` - see definition in `TastyFormat` + * - `ToolingVersion: String` - arbitrary length string representing the tool that produced the TASTy. + */ final val header: Array[Int] = Array(0x5C, 0xA1, 0xAB, 0x1F) - val MajorVersion: Int = 26 - val MinorVersion: Int = 1 + + /**Natural number. Each increment of the `MajorVersion` begins a + * new series of backward compatible TASTy versions. + * + * A TASTy file in either the preceeding or succeeding series is + * incompatible with the current value. + */ + final val MajorVersion: Int = 28 + + /**Natural number. Each increment of the `MinorVersion`, within + * a series declared by the `MajorVersion`, breaks forward + * compatibility, but remains backwards compatible, with all + * preceeding `MinorVersion`. + */ + final val MinorVersion: Int = 0 + + /**Natural Number. The `ExperimentalVersion` allows for + * experimentation with changes to TASTy without committing + * to any guarantees of compatibility. + * + * A zero value indicates that the TASTy version is from a + * stable, final release. + * + * A strictly positive value indicates that the TASTy + * version is experimental. An experimental TASTy file + * can only be read by a tool with the same version. + * However, tooling with an experimental TASTy version + * is able to read final TASTy documents if the file's + * `MinorVersion` is strictly less than the current value. + */ + final val ExperimentalVersion: Int = 1 + + /**This method implements a binary relation (`<:<`) between two TASTy versions. + * We label the lhs `file` and rhs `compiler`. + * if `file <:< compiler` then the TASTy file is valid to be read. + * + * TASTy versions have a partial order, + * for example `a <:< b` and `b <:< a` are both false if `a` and `b` have different major versions. + * + * We follow the given algorithm: + * ``` + * if file.major != compiler.major then + * return incompatible + * if compiler.experimental == 0 then + * if file.experimental != 0 then + * return incompatible + * if file.minor > compiler.minor then + * return incompatible + * else + * return compatible + * else invariant[compiler.experimental != 0] + * if file.experimental == compiler.experimental then + * if file.minor == compiler.minor then + * return compatible (all fields equal) + * else + * return incompatible + * else if file.experimental == 0, + * if file.minor < compiler.minor then + * return compatible (an experimental version can read a previous released version) + * else + * return incompatible (an experimental version cannot read its own minor version or any later version) + * else invariant[file.experimental is non-0 and different than compiler.experimental] + * return incompatible + * ``` + */ + def isVersionCompatible( + fileMajor: Int, + fileMinor: Int, + fileExperimental: Int, + compilerMajor: Int, + compilerMinor: Int, + compilerExperimental: Int + ): Boolean = ( + fileMajor == compilerMajor && ( + if (fileExperimental == compilerExperimental) { + if (compilerExperimental == 0) { + fileMinor <= compilerMinor + } + else { + fileMinor == compilerMinor + } + } + else { + fileExperimental == 0 && fileMinor < compilerMinor + } + ) + ) final val ASTsSection = "ASTs" final val PositionsSection = "Positions" final val CommentsSection = "Comments" - /** Tags used to serialize names */ + /** Tags used to serialize names, should update [[TastyFormat$.nameTagToString]] if a new constant is added */ class NameTags { - final val UTF8 = 1 // A simple name in UTF8 encoding. + final val UTF8 = 1 // A simple name in UTF8 encoding. + + final val QUALIFIED = 2 // A fully qualified name `.`. - final val QUALIFIED = 2 // A fully qualified name `.`. + final val EXPANDED = 3 // An expanded name `$$`, + // used by Scala-2 for private names. - final val EXPANDED = 3 // An expanded name `$$`, - // used by Scala-2 for private names. + final val EXPANDPREFIX = 4 // An expansion prefix `$`, + // used by Scala-2 for private names. - final val EXPANDPREFIX = 4 // An expansion prefix `$`, - // used by Scala-2 for private names. + final val UNIQUE = 10 // A unique name `$` where `` + // is used only once for each ``. - final val UNIQUE = 10 // A unique name `$` where `` - // is used only once for each ``. + final val DEFAULTGETTER = 11 // The name `$default$` + // of a default getter that returns a default argument. - final val DEFAULTGETTER = 11 // The name `$default$` - // of a default getter that returns a default argument. + final val SUPERACCESSOR = 20 // The name of a super accessor `super$name` created by SuperAccesors. - final val SUPERACCESSOR = 20 // The name of a super accessor `super$name` created by SuperAccesors. + final val INLINEACCESSOR = 21 // The name of an inline accessor `inline$name` - final val INLINEACCESSOR = 21 // The name of an inline accessor `inline$name` + final val BODYRETAINER = 22 // The name of a synthetic method that retains the runtime + // body of an inline method - final val BODYRETAINER = 22 // The name of a synthetic method that retains the runtime - // body of an inline method + final val OBJECTCLASS = 23 // The name of an object class (or: module class) `$`. - final val OBJECTCLASS = 23 // The name of an object class (or: module class) `$`. + final val SIGNED = 63 // A pair of a name and a signature, used to identify + // possibly overloaded methods. - final val TARGETSIGNED = 62 // A triple of a name, a targetname and a signature, used to identify - // possibly overloaded methods that carry a @targetName annotation. + final val TARGETSIGNED = 62 // A triple of a name, a targetname and a signature, used to identify + // possibly overloaded methods that carry a @targetName annotation. - final val SIGNED = 63 // A pair of a name and a signature, used to identify - // possibly overloaded methods. + // TODO swap SIGNED and TARGETSIGNED codes on next major version bump } object NameTags extends NameTags + /**Should be kept in sync with [[NameTags]]. Converts constants to a String representing their identifier, + * or NotANameTag(tag) if unrecognised. + * + * For debugging purposes when unpickling names in a TASTy file. + */ + def nameTagToString(tag: Int) = { + import NameTags._ + tag match { + case UTF8 => "UTF8" + case QUALIFIED => "QUALIFIED" + case EXPANDED => "EXPANDED" + case EXPANDPREFIX => "EXPANDPREFIX" + case UNIQUE => "UNIQUE" + case DEFAULTGETTER => "DEFAULTGETTER" + case SUPERACCESSOR => "SUPERACCESSOR" + case INLINEACCESSOR => "INLINEACCESSOR" + case BODYRETAINER => "BODYRETAINER" + case OBJECTCLASS => "OBJECTCLASS" + case SIGNED => "SIGNED" + case TARGETSIGNED => "TARGETSIGNED" + case id => s"NotANameTag($id)" + } + } + // Position header final val SOURCE = 4 @@ -103,46 +220,47 @@ object TastyFormat { final val PARAMsetter = 38 final val EXPORTED = 39 final val OPEN = 40 - final val PARAMEND = 41 - final val PARAMalias = 42 - final val TRANSPARENT = 43 - final val INFIX = 44 + final val PARAMalias = 41 + final val TRANSPARENT = 42 + final val INFIX = 43 + final val EMPTYCLAUSE = 44 + final val SPLITCLAUSE = 45 // Cat. 2: tag Nat - final val SHAREDterm = 50 - final val SHAREDtype = 51 - final val TERMREFdirect = 52 - final val TYPEREFdirect = 53 - final val TERMREFpkg = 54 - final val TYPEREFpkg = 55 - final val RECthis = 56 - final val BYTEconst = 57 - final val SHORTconst = 58 - final val CHARconst = 59 - final val INTconst = 60 - final val LONGconst = 61 - final val FLOATconst = 62 - final val DOUBLEconst = 63 - final val STRINGconst = 64 - final val IMPORTED = 65 - final val RENAMED = 66 + final val SHAREDterm = 60 + final val SHAREDtype = 61 + final val TERMREFdirect = 62 + final val TYPEREFdirect = 63 + final val TERMREFpkg = 64 + final val TYPEREFpkg = 65 + final val RECthis = 66 + final val BYTEconst = 67 + final val SHORTconst = 68 + final val CHARconst = 69 + final val INTconst = 70 + final val LONGconst = 71 + final val FLOATconst = 72 + final val DOUBLEconst = 73 + final val STRINGconst = 74 + final val IMPORTED = 75 + final val RENAMED = 76 // Cat. 3: tag AST - final val THIS = 80 - final val QUALTHIS = 81 - final val CLASSconst = 82 - final val BYNAMEtype = 83 - final val BYNAMEtpt = 84 - final val NEW = 85 - final val THROW = 86 - final val IMPLICITarg = 87 - final val PRIVATEqualified = 88 - final val PROTECTEDqualified = 89 - final val RECtype = 90 - final val SINGLETONtpt = 91 - final val BOUNDED = 92 + final val THIS = 90 + final val QUALTHIS = 91 + final val CLASSconst = 92 + final val BYNAMEtype = 93 + final val BYNAMEtpt = 94 + final val NEW = 95 + final val THROW = 96 + final val IMPLICITarg = 97 + final val PRIVATEqualified = 98 + final val PROTECTEDqualified = 99 + final val RECtype = 100 + final val SINGLETONtpt = 101 + final val BOUNDED = 102 // Cat. 4: tag Nat AST @@ -222,7 +340,7 @@ object TastyFormat { /** Useful for debugging */ def isLegalTag(tag: Int): Boolean = - firstSimpleTreeTag <= tag && tag <= INFIX || + firstSimpleTreeTag <= tag && tag <= SPLITCLAUSE || firstNatTreeTag <= tag && tag <= RENAMED || firstASTTreeTag <= tag && tag <= BOUNDED || firstNatASTTreeTag <= tag && tag <= NAMEDARG || @@ -313,9 +431,9 @@ object TastyFormat { case STATIC => "STATIC" case OBJECT => "OBJECT" case TRAIT => "TRAIT" - case ENUM => "ENUM" case TRANSPARENT => "TRANSPARENT" case INFIX => "INFIX" + case ENUM => "ENUM" case LOCAL => "LOCAL" case SYNTHETIC => "SYNTHETIC" case ARTIFACT => "ARTIFACT" @@ -331,8 +449,9 @@ object TastyFormat { case PARAMsetter => "PARAMsetter" case EXPORTED => "EXPORTED" case OPEN => "OPEN" - case PARAMEND => "PARAMEND" case PARAMalias => "PARAMalias" + case EMPTYCLAUSE => "EMPTYCLAUSE" + case SPLITCLAUSE => "SPLITCLAUSE" case SHAREDterm => "SHAREDterm" case SHAREDtype => "SHAREDtype" @@ -365,6 +484,7 @@ object TastyFormat { case DEFDEF => "DEFDEF" case TYPEDEF => "TYPEDEF" case IMPORT => "IMPORT" + case EXPORT => "EXPORT" case TYPEPARAM => "TYPEPARAM" case PARAM => "PARAM" case IMPORTED => "IMPORTED" @@ -405,7 +525,6 @@ object TastyFormat { case TERMREFin => "TERMREFin" case TYPEREFin => "TYPEREFin" case SELECTin => "SELECTin" - case EXPORT => "EXPORT" case REFINEDtype => "REFINEDtype" case REFINEDtpt => "REFINEDtpt" diff --git a/src/compiler/scala/tools/tasty/TastyHeaderUnpickler.scala b/src/compiler/scala/tools/tasty/TastyHeaderUnpickler.scala index b05154118d9a..783fc41bb5c5 100644 --- a/src/compiler/scala/tools/tasty/TastyHeaderUnpickler.scala +++ b/src/compiler/scala/tools/tasty/TastyHeaderUnpickler.scala @@ -14,27 +14,100 @@ package scala.tools.tasty import java.util.UUID -import TastyFormat.{MajorVersion, MinorVersion, header} +import TastyFormat.{MajorVersion, MinorVersion, ExperimentalVersion, header} class TastyHeaderUnpickler(reader: TastyReader) { + import TastyHeaderUnpickler._ import reader._ def this(bytes: Array[Byte]) = this(new TastyReader(bytes)) + /** reads and verifies the TASTy version, extracting the UUID */ def readHeader(): UUID = { - for (i <- header.indices) + + for (i <- 0 until header.length) check(readByte() == header(i), "not a TASTy file") - val major = readNat() - val minor = readNat() - check(major == MajorVersion && minor <= MinorVersion, - s"""TASTy signature has wrong version. - | expected: $MajorVersion.$MinorVersion - | found : $major.$minor""".stripMargin) - new UUID(readUncompressedLong(), readUncompressedLong()) + val fileMajor = readNat() + if (fileMajor <= 27) { // old behavior before `tasty-core` 3.0.0-RC1 + val fileMinor = readNat() + val signature = signatureString(fileMajor, fileMinor, 0) + throw new UnpickleException(signature + backIncompatAddendum + toolingAddendum) + } + else { + val fileMinor = readNat() + val fileExperimental = readNat() + val toolingLength = readNat() + val toolingStart = { + val start = currentAddr + val end = start + toolingLength + goto(end) + start + } + + val validVersion = TastyFormat.isVersionCompatible( + fileMajor = fileMajor, + fileMinor = fileMinor, + fileExperimental = fileExperimental, + compilerMajor = MajorVersion, + compilerMinor = MinorVersion, + compilerExperimental = ExperimentalVersion + ) + + check(validVersion, { + val signature = signatureString(fileMajor, fileMinor, fileExperimental) + val toolingVersion = new String(bytes, toolingStart.index, toolingLength) + val producedByAddendum = s"\nThe TASTy file was produced by $toolingVersion.$toolingAddendum" + val msg = ( + if (fileExperimental != 0) unstableAddendum + else if (fileMajor < MajorVersion) backIncompatAddendum + else forwardIncompatAddendum + ) + signature + msg + producedByAddendum + }) + + new UUID(readUncompressedLong(), readUncompressedLong()) + } } def isAtEnd: Boolean = reader.isAtEnd - private def check(cond: Boolean, msg: => String): Unit = + private def check(cond: Boolean, msg: => String): Unit = { if (!cond) throw new UnpickleException(msg) + } +} + +object TastyHeaderUnpickler { + + private def toolingAddendum = ( + if (ExperimentalVersion > 0) + "\nNote that your tooling is currently using an unstable TASTy version." + else + "" + ) + + private def signatureString(fileMajor: Int, fileMinor: Int, fileExperimental: Int) = { + def showMinorVersion(min: Int, exp: Int) = { + val expStr = if (exp == 0) "" else s" [unstable release: $exp]" + s"$min$expStr" + } + val minorVersion = showMinorVersion(MinorVersion, ExperimentalVersion) + val fileMinorVersion = showMinorVersion(fileMinor, fileExperimental) + s"""TASTy signature has wrong version. + | expected: {majorVersion: $MajorVersion, minorVersion: $minorVersion} + | found : {majorVersion: $fileMajor, minorVersion: $fileMinorVersion} + | + |""".stripMargin + } + + private def unstableAddendum = + """This TASTy file was produced by an unstable release. + |To read this TASTy file, your tooling must be at the same version.""".stripMargin + + private def backIncompatAddendum = + """This TASTy file was produced by an earlier release that is not supported anymore. + |Please recompile this TASTy with a later version.""".stripMargin + + private def forwardIncompatAddendum = + """This TASTy file was produced by a more recent, forwards incompatible release. + |To read this TASTy file, please upgrade your tooling.""".stripMargin } diff --git a/test/tasty/neg/src-2/TestDefAnnots.check b/test/tasty/neg/src-2/TestDefAnnots.check index 5f5645df1fac..9551d6747f27 100644 --- a/test/tasty/neg/src-2/TestDefAnnots.check +++ b/test/tasty/neg/src-2/TestDefAnnots.check @@ -1,16 +1,13 @@ TestDefAnnots_fail.scala:5: error: Unsupported Scala 3 throw clause in an annotation of parameter arg; note that complex trees are not yet supported for Annotations; found in method withArgAnnotThrow in object tastytest.DefAnnots. - def test1 = DefAnnots.withArgAnnotThrow(1) == 1 + def test1 = DefAnnots.withArgAnnotThrow(1) == 1 // error ^ TestDefAnnots_fail.scala:6: error: Unsupported Scala 3 loop statement in an annotation of parameter arg; note that complex trees are not yet supported for Annotations; found in method withArgAnnotLoop in object tastytest.DefAnnots. - def test2 = DefAnnots.withArgAnnotLoop(1) == 1 + def test2 = DefAnnots.withArgAnnotLoop(1) == 1 // error ^ TestDefAnnots_fail.scala:7: error: Unsupported Scala 3 assignment expression in an annotation of parameter arg; note that complex trees are not yet supported for Annotations; found in method withArgAnnotAssign in object tastytest.DefAnnots. - def test3 = DefAnnots.withArgAnnotAssign(1) == 1 + def test3 = DefAnnots.withArgAnnotAssign(1) == 1 // error ^ TestDefAnnots_fail.scala:8: error: Unsupported Scala 3 block expression in an annotation of parameter arg; note that complex trees are not yet supported for Annotations; found in method withArgAnnotLambda in object tastytest.DefAnnots. - def test4 = DefAnnots.withArgAnnotLambda(1) == 1 + def test4 = DefAnnots.withArgAnnotLambda(1) == 1 // error ^ -TestDefAnnots_fail.scala:9: error: Unsupported Scala 3 inlined expression in an annotation of parameter arg; note that complex trees are not yet supported for Annotations; found in method withArgAnnotInlined in object tastytest.DefAnnots. - def test5 = DefAnnots.withArgAnnotInlined(1) == 1 - ^ -5 errors +4 errors diff --git a/test/tasty/neg/src-2/TestDefAnnots_fail.scala b/test/tasty/neg/src-2/TestDefAnnots_fail.scala index 7056ed78c276..b9017a755bd1 100644 --- a/test/tasty/neg/src-2/TestDefAnnots_fail.scala +++ b/test/tasty/neg/src-2/TestDefAnnots_fail.scala @@ -2,10 +2,10 @@ package tastytest object TestDefAnnots { - def test1 = DefAnnots.withArgAnnotThrow(1) == 1 - def test2 = DefAnnots.withArgAnnotLoop(1) == 1 - def test3 = DefAnnots.withArgAnnotAssign(1) == 1 - def test4 = DefAnnots.withArgAnnotLambda(1) == 1 - def test5 = DefAnnots.withArgAnnotInlined(1) == 1 + def test1 = DefAnnots.withArgAnnotThrow(1) == 1 // error + def test2 = DefAnnots.withArgAnnotLoop(1) == 1 // error + def test3 = DefAnnots.withArgAnnotAssign(1) == 1 // error + def test4 = DefAnnots.withArgAnnotLambda(1) == 1 // error + def test5 = DefAnnots.withArgAnnotInlined(1) == 1 // ok - arguments to annotations are not forced } diff --git a/test/tasty/neg/src-2/TestDependentExtension.check b/test/tasty/neg/src-2/TestDependentExtension.check new file mode 100644 index 000000000000..d4934359e9aa --- /dev/null +++ b/test/tasty/neg/src-2/TestDependentExtension.check @@ -0,0 +1,4 @@ +TestDependentExtension_fail.scala:11: error: Unsupported Scala 3 extension method with unmergeable type parameters: extract; found in trait tastytest.DependentExtension. + val res = implicitly[DependentExtension].extract(box)(_ + 1) + ^ +1 error diff --git a/test/tasty/neg/src-2/TestDependentExtension_fail.scala b/test/tasty/neg/src-2/TestDependentExtension_fail.scala new file mode 100644 index 000000000000..cfd4bdee8a84 --- /dev/null +++ b/test/tasty/neg/src-2/TestDependentExtension_fail.scala @@ -0,0 +1,15 @@ +package tastytest + +object TestDependentExtension { + import DependentExtension._ + + def test = { + val box = new Box { + type Repr = Int + val value: Int = 23 + } + val res = implicitly[DependentExtension].extract(box)(_ + 1) + assert(res == 24) + } + +} diff --git a/test/tasty/neg/src-2/TestRealFunctor.check b/test/tasty/neg/src-2/TestRealFunctor.check new file mode 100644 index 000000000000..e873d33048a1 --- /dev/null +++ b/test/tasty/neg/src-2/TestRealFunctor.check @@ -0,0 +1,4 @@ +TestRealFunctor_fail.scala:6: error: Unsupported Scala 3 extension method with unmergeable type parameters: map; found in trait tastytest.RealFunctor. + def map[A, B](fa: List[A])(f: A => B): List[B] = fa.map(f) + ^ +1 error diff --git a/test/tasty/neg/src-2/TestRealFunctor_fail.scala b/test/tasty/neg/src-2/TestRealFunctor_fail.scala new file mode 100644 index 000000000000..75e868da2d67 --- /dev/null +++ b/test/tasty/neg/src-2/TestRealFunctor_fail.scala @@ -0,0 +1,9 @@ +package tastytest + +object TestRealFunctor { + // in this test, we try to implement an extension method that has two type-parameter lists + implicit object ListRealFunctor extends RealFunctor[List] { + def map[A, B](fa: List[A])(f: A => B): List[B] = fa.map(f) + } + +} diff --git a/test/tasty/neg/src-3/DependentExtension.scala b/test/tasty/neg/src-3/DependentExtension.scala new file mode 100644 index 000000000000..f076c9197246 --- /dev/null +++ b/test/tasty/neg/src-3/DependentExtension.scala @@ -0,0 +1,20 @@ +package tastytest + +import DependentExtension.Box + +// `DependentExtension` uses two type parameter lists in its `extract` method. the second type parameter list is +// dependent on the first term parameter list. It is unclear how to interpret this. +trait DependentExtension { + extension [B <: Box](b: B) def extract[R >: b.Repr, O](f: R => O): O = f(b.value) +} + +object DependentExtension { + + given DependentExtension with {} + + trait Box { + type Repr + val value: Repr + } + +} diff --git a/test/tasty/neg/src-3/RealFunctor.scala b/test/tasty/neg/src-3/RealFunctor.scala new file mode 100644 index 000000000000..ec2b3977ca56 --- /dev/null +++ b/test/tasty/neg/src-3/RealFunctor.scala @@ -0,0 +1,7 @@ +package tastytest + +// This `Functor` uses two type parameter lists in its `map` method. +// In this example, it may be safe to merge the type parameter lists. +trait RealFunctor[F[_]] { + extension [A](fa: F[A]) def map[B](f: A => B): F[B] +} diff --git a/test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala b/test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala index bd746cacbbe0..a267db9b6cc7 100644 --- a/test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala +++ b/test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala @@ -71,10 +71,12 @@ class TastyTestJUnit { } } +import scala.reflect.runtime.ReflectionUtils + object TastyTestJUnit { final implicit class TryOps(val op: Try[Unit]) extends AnyVal { def eval: Unit = op match { - case Failure(err) => fail(err.getMessage) + case Failure(err) => fail(err.toString) case _ => () } }