From 9e83a6056d5cdc99f26b8b2f06c1636acc21bc91 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 15 Feb 2021 15:20:18 +0100 Subject: [PATCH 1/4] Update tasty reader for existing tests --- project/DottySupport.scala | 4 +- .../scala/tools/nsc/tasty/TreeUnpickler.scala | 46 +++- .../tools/nsc/tasty/bridge/SymbolOps.scala | 20 +- .../scala/tools/tasty/TastyFormat.scala | 239 +++++++++++++----- .../tools/tasty/TastyHeaderUnpickler.scala | 93 ++++++- test/tasty/neg/src-2/TestDefAnnots.check | 13 +- test/tasty/neg/src-2/TestDefAnnots_fail.scala | 10 +- .../run/src-2/tastytest/TestMacroCompat.scala | 4 +- .../run/src-3/tastytest/MacroCompat.scala | 8 +- .../tools/tastytest/TastyTestJUnit.scala | 4 +- 10 files changed, 328 insertions(+), 113 deletions(-) diff --git a/project/DottySupport.scala b/project/DottySupport.scala index de191207d7d6..2669eff76e9f 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-M4-bin-20210214-80befc3-NIGHTLY" // TASTy version 28.0.1 + val scala3Compiler = "org.scala-lang" % "scala3-compiler_3.0.0-M4" % 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..99a1df8bbe47 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 @@ -704,12 +701,26 @@ class TreeUnpickler[Tasty <: TastyUniverse]( ctx.log(s"$symAddr completing ${showSym(sym)} in scope ${showSym(ctx.owner)}") - 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 readParamss(skipTypeParams: Boolean)(implicit ctx: Context): List[List[NoCycle]] = { + def readRest() = { + if (nextByte == SPLITCLAUSE) readByte() + readParamss(skipTypeParams) + } + + def emptyTypeParams() = { + while (nextByte === TYPEPARAM) skipTree() + Nil + } - case _ => Nil + nextByte match { + case PARAM => readParams[NoCycle](PARAM) :: readRest() + case TYPEPARAM => + val typarams = + if (skipTypeParams) emptyTypeParams() else readParams[NoCycle](TYPEPARAM) + typarams :: readRest() + case EMPTYCLAUSE => readByte(); Nil :: readRest() + case _ => Nil + } } def checkUnsupportedFlags(unsupported: TastyFlagSet)(implicit ctx: Context): Unit = { @@ -723,16 +734,25 @@ class TreeUnpickler[Tasty <: TastyUniverse]( val isMacro = repr.originalFlagSet.is(Erased | Macro) checkUnsupportedFlags(repr.tastyOnlyFlags &~ (Extension | Exported | Infix | optFlag(isMacro)(Erased))) val isCtor = sym.isConstructor - val typeParams = { + val paramDefss = readParamss(skipTypeParams=isCtor)(localCtx) // TODO: this can mix type and term parameters, must skip type params if ctor + val typeParams: List[Symbol] = { if (isCtor) { - skipTypeParams() + // skipTypeParams() sym.owner.typeParams } else { - readParams[NoCycle](TYPEPARAM)(localCtx).map(symFromNoCycle) + // readParams[NoCycle](TYPEPARAM)(localCtx).map(symFromNoCycle) + + // TODO: Handle extension methods with multiple param lists + val first = paramDefss.take(1).flatten.map(symFromNoCycle) + if (first.headOption.exists(_.isType)) first else Nil } } - val vparamss = readParamss(localCtx) + val vparamss: List[List[NoCycle]] = { + // TODO: Handle extension methods with multiple param lists + val droppedClauses = if (typeParams.isEmpty) 0 else 1 + paramDefss.drop(droppedClauses).ensuring(_.flatten.forall(nc => symFromNoCycle(nc).isTerm)) + } val tpt = readTpt()(localCtx) if (isMacro) { val impl = tpd.Macro(readTerm()(ctx.addMode(ReadMacro))) diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala index 8aecbd17fd0d..11625ce7f147 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala @@ -46,16 +46,16 @@ trait SymbolOps { self: TastyUniverse => implicit final class SymbolDecorator(val sym: Symbol) { - def isScala3Macro: Boolean = repr.originalFlagSet.is(Inline | Macro) - def isScala3Inline: Boolean = repr.originalFlagSet.is(Inline) - def isScala2Macro: Boolean = repr.originalFlagSet.is(Erased | Macro) + def isScala3Macro(implicit ctx: Context): Boolean = repr.originalFlagSet.is(Inline | Macro) + def isScala3Inline(implicit ctx: Context): Boolean = repr.originalFlagSet.is(Inline) + def isScala2Macro(implicit ctx: Context): Boolean = repr.originalFlagSet.is(Erased | Macro) - def isPureMixinCtor: Boolean = isMixinCtor && repr.originalFlagSet.is(Stable) + def isPureMixinCtor(implicit ctx: Context): Boolean = isMixinCtor && repr.originalFlagSet.is(Stable) def isMixinCtor: Boolean = u.nme.MIXIN_CONSTRUCTOR == sym.name && sym.owner.isTrait - def isTraitParamAccessor: Boolean = sym.owner.isTrait && repr.originalFlagSet.is(FieldAccessor|ParamSetter) + def isTraitParamAccessor(implicit ctx: Context): Boolean = sym.owner.isTrait && repr.originalFlagSet.is(FieldAccessor|ParamSetter) - def isParamGetter: Boolean = + def isParamGetter(implicit ctx: Context): Boolean = sym.isMethod && sym.repr.originalFlagSet.is(FlagSets.FieldAccessorFlags) /** A computed property that should only be called on a symbol which is known to have been initialised by the @@ -63,8 +63,12 @@ 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)} ") + def repr(implicit ctx: Context): TastyRepr = { + require(sym.rawInfo.isInstanceOf[TastyRepr], { + val raw = u.showRaw(sym.rawInfo) + val tastyRepr = u.typeOf[TastyRepr] + s"${showSym(sym)} is already completed. Expected $tastyRepr, is $raw. in ${ctx.source}" + }) sym.rawInfo.asInstanceOf[TastyRepr] } 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/run/src-2/tastytest/TestMacroCompat.scala b/test/tasty/run/src-2/tastytest/TestMacroCompat.scala index afb296256af1..fcd54bc7b8f9 100644 --- a/test/tasty/run/src-2/tastytest/TestMacroCompat.scala +++ b/test/tasty/run/src-2/tastytest/TestMacroCompat.scala @@ -8,7 +8,7 @@ object TestMacroCompat extends Suite("TestMacroCompat") { test(assert(testCase(15) === ("15", Position("TestMacroCompat.scala", 9)))) test(assert(constInt("") === 1)) - test(assert(mono === 1)) - test(assert(poly[String] === "String")) + // test(assert(mono === 1)) + // test(assert(poly[String] === "String")) } diff --git a/test/tasty/run/src-3/tastytest/MacroCompat.scala b/test/tasty/run/src-3/tastytest/MacroCompat.scala index 71ba8c03f063..9e292381f9d6 100644 --- a/test/tasty/run/src-3/tastytest/MacroCompat.scala +++ b/test/tasty/run/src-3/tastytest/MacroCompat.scala @@ -11,11 +11,11 @@ object MacroCompat { inline def constInt[T](x: T): Int = ${ Macros3.constIntImpl[T]('x) } object Bundles { - def mono: Int = macro MacroImpl.mono - inline def mono: Int = ${ Macros3.monoImpl } + // def mono: Int = macro MacroImpl.mono + // inline def mono: Int = ${ Macros3.monoImpl } - def poly[T]: String = macro MacroImpl.poly[T] - inline def poly[T]: String = ${ Macros3.polyImpl[T] } + // def poly[T]: String = macro MacroImpl.poly[T] + // inline def poly[T]: String = ${ Macros3.polyImpl[T] } } def testCase(test: => Any)(implicit pos: Position): (String, Position) = diff --git a/test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala b/test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala index bd746cacbbe0..69eb4168280f 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) => throw ReflectionUtils.unwrapThrowable(err)//fail(ReflectionUtils.unwrapThrowable(err).toString) case _ => () } } From 7927353d37e11bf92a659cd85cd8a55ed86b1c19 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 15 Feb 2021 17:34:00 +0100 Subject: [PATCH 2/4] block extension method with 2+ type parameter lists --- .../scala/tools/nsc/tasty/TreeUnpickler.scala | 13 +++++------- .../neg/src-2/TestDependentExtension.check | 4 ++++ .../src-2/TestDependentExtension_fail.scala | 15 ++++++++++++++ test/tasty/neg/src-2/TestRealFunctor.check | 4 ++++ .../neg/src-2/TestRealFunctor_fail.scala | 9 +++++++++ test/tasty/neg/src-3/DependentExtension.scala | 20 +++++++++++++++++++ test/tasty/neg/src-3/RealFunctor.scala | 7 +++++++ 7 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 test/tasty/neg/src-2/TestDependentExtension.check create mode 100644 test/tasty/neg/src-2/TestDependentExtension_fail.scala create mode 100644 test/tasty/neg/src-2/TestRealFunctor.check create mode 100644 test/tasty/neg/src-2/TestRealFunctor_fail.scala create mode 100644 test/tasty/neg/src-3/DependentExtension.scala create mode 100644 test/tasty/neg/src-3/RealFunctor.scala diff --git a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala index 99a1df8bbe47..680949260515 100644 --- a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala +++ b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala @@ -734,24 +734,21 @@ class TreeUnpickler[Tasty <: TastyUniverse]( val isMacro = repr.originalFlagSet.is(Erased | Macro) checkUnsupportedFlags(repr.tastyOnlyFlags &~ (Extension | Exported | Infix | optFlag(isMacro)(Erased))) val isCtor = sym.isConstructor - val paramDefss = readParamss(skipTypeParams=isCtor)(localCtx) // TODO: this can mix type and term parameters, must skip type params if ctor + val paramDefss = readParamss(skipTypeParams=isCtor)(localCtx) val typeParams: List[Symbol] = { if (isCtor) { - // skipTypeParams() sym.owner.typeParams } else { - // readParams[NoCycle](TYPEPARAM)(localCtx).map(symFromNoCycle) - - // TODO: Handle extension methods with multiple param lists val first = paramDefss.take(1).flatten.map(symFromNoCycle) if (first.headOption.exists(_.isType)) first else Nil } } val vparamss: List[List[NoCycle]] = { - // TODO: Handle extension methods with multiple param lists - val droppedClauses = if (typeParams.isEmpty) 0 else 1 - paramDefss.drop(droppedClauses).ensuring(_.flatten.forall(nc => symFromNoCycle(nc).isTerm)) + val valueClauses = paramDefss.drop(if (typeParams.isEmpty) 0 else 1) + val hasTypeParams = valueClauses.exists(_.exists(nc => symFromNoCycle(nc).isType)) + unsupportedWhen(hasTypeParams, s"extension method with secondary type parameter list: $tname") + valueClauses } val tpt = readTpt()(localCtx) if (isMacro) { diff --git a/test/tasty/neg/src-2/TestDependentExtension.check b/test/tasty/neg/src-2/TestDependentExtension.check new file mode 100644 index 000000000000..4a8c391801cf --- /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 secondary type parameter list: 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..f992b382783d --- /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 secondary type parameter list: 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] +} From ceb20db786e6996e1fdc601cd0be08e0a56c2fba Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 16 Feb 2021 14:09:40 +0100 Subject: [PATCH 3/4] update to Scala 3.0.0-RC1, re-enable tests --- project/DottySupport.scala | 4 ++-- test/tasty/run/src-2/tastytest/TestMacroCompat.scala | 4 ++-- test/tasty/run/src-3/tastytest/MacroCompat.scala | 8 ++++---- .../tasty/test/scala/tools/tastytest/TastyTestJUnit.scala | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/project/DottySupport.scala b/project/DottySupport.scala index 2669eff76e9f..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-M4-bin-20210214-80befc3-NIGHTLY" // TASTy version 28.0.1 - val scala3Compiler = "org.scala-lang" % "scala3-compiler_3.0.0-M4" % 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/test/tasty/run/src-2/tastytest/TestMacroCompat.scala b/test/tasty/run/src-2/tastytest/TestMacroCompat.scala index fcd54bc7b8f9..afb296256af1 100644 --- a/test/tasty/run/src-2/tastytest/TestMacroCompat.scala +++ b/test/tasty/run/src-2/tastytest/TestMacroCompat.scala @@ -8,7 +8,7 @@ object TestMacroCompat extends Suite("TestMacroCompat") { test(assert(testCase(15) === ("15", Position("TestMacroCompat.scala", 9)))) test(assert(constInt("") === 1)) - // test(assert(mono === 1)) - // test(assert(poly[String] === "String")) + test(assert(mono === 1)) + test(assert(poly[String] === "String")) } diff --git a/test/tasty/run/src-3/tastytest/MacroCompat.scala b/test/tasty/run/src-3/tastytest/MacroCompat.scala index 9e292381f9d6..71ba8c03f063 100644 --- a/test/tasty/run/src-3/tastytest/MacroCompat.scala +++ b/test/tasty/run/src-3/tastytest/MacroCompat.scala @@ -11,11 +11,11 @@ object MacroCompat { inline def constInt[T](x: T): Int = ${ Macros3.constIntImpl[T]('x) } object Bundles { - // def mono: Int = macro MacroImpl.mono - // inline def mono: Int = ${ Macros3.monoImpl } + def mono: Int = macro MacroImpl.mono + inline def mono: Int = ${ Macros3.monoImpl } - // def poly[T]: String = macro MacroImpl.poly[T] - // inline def poly[T]: String = ${ Macros3.polyImpl[T] } + def poly[T]: String = macro MacroImpl.poly[T] + inline def poly[T]: String = ${ Macros3.polyImpl[T] } } def testCase(test: => Any)(implicit pos: Position): (String, Position) = diff --git a/test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala b/test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala index 69eb4168280f..a267db9b6cc7 100644 --- a/test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala +++ b/test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala @@ -76,7 +76,7 @@ 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) => throw ReflectionUtils.unwrapThrowable(err)//fail(ReflectionUtils.unwrapThrowable(err).toString) + case Failure(err) => fail(err.toString) case _ => () } } From 78e5e051ee6c0f7457a9df467d48aea9faa8d5f1 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 16 Feb 2021 14:10:05 +0100 Subject: [PATCH 4/4] refactor --- .../scala/tools/nsc/tasty/TreeUnpickler.scala | 230 ++++++++++-------- .../tools/nsc/tasty/bridge/SymbolOps.scala | 27 +- .../neg/src-2/TestDependentExtension.check | 2 +- test/tasty/neg/src-2/TestRealFunctor.check | 2 +- 4 files changed, 138 insertions(+), 123 deletions(-) diff --git a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala index 680949260515..8a10f400b61a 100644 --- a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala +++ b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala @@ -686,38 +686,26 @@ 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) - ctx.log(s"$symAddr completing ${showSym(sym)} in scope ${showSym(ctx.owner)}") - - def readParamss(skipTypeParams: Boolean)(implicit ctx: Context): List[List[NoCycle]] = { + def readParamss()(implicit ctx: Context): List[List[NoCycle]] = { def readRest() = { if (nextByte == SPLITCLAUSE) readByte() - readParamss(skipTypeParams) - } - - def emptyTypeParams() = { - while (nextByte === TYPEPARAM) skipTree() - Nil + readParamss() } - nextByte match { case PARAM => readParams[NoCycle](PARAM) :: readRest() - case TYPEPARAM => - val typarams = - if (skipTypeParams) emptyTypeParams() else readParams[NoCycle](TYPEPARAM) - typarams :: readRest() + case TYPEPARAM => readParams[NoCycle](TYPEPARAM) :: readRest() case EMPTYCLAUSE => readByte(); Nil :: readRest() case _ => Nil } @@ -727,97 +715,123 @@ class TreeUnpickler[Tasty <: TastyUniverse]( unsupportedWhen(unsupported.hasFlags, s"${showTasty(unsupported)} ${sym.kindString} $tname") } - try { - 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 paramDefss = readParamss(skipTypeParams=isCtor)(localCtx) - val typeParams: List[Symbol] = { - if (isCtor) { - sym.owner.typeParams - } - else { - val first = paramDefss.take(1).flatten.map(symFromNoCycle) - if (first.headOption.exists(_.isType)) first else Nil - } - } - val vparamss: List[List[NoCycle]] = { - val valueClauses = paramDefss.drop(if (typeParams.isEmpty) 0 else 1) - val hasTypeParams = valueClauses.exists(_.exists(nc => symFromNoCycle(nc).isType)) - unsupportedWhen(hasTypeParams, s"extension method with secondary type parameter list: $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.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 + 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" ) - 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) + 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)) + } + + 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 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 } - 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)) + else rhs.tpe + ctx.setInfo(sym, defn.NormalisedBounds(info, sym)) + if (sym.is(Param)) sym.reset(Private | Protected) + } + } + + 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 => 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 11625ce7f147..ab931b219fbf 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala @@ -46,16 +46,16 @@ trait SymbolOps { self: TastyUniverse => implicit final class SymbolDecorator(val sym: Symbol) { - def isScala3Macro(implicit ctx: Context): Boolean = repr.originalFlagSet.is(Inline | Macro) - def isScala3Inline(implicit ctx: Context): Boolean = repr.originalFlagSet.is(Inline) - def isScala2Macro(implicit ctx: Context): Boolean = repr.originalFlagSet.is(Erased | Macro) + def isScala3Macro: Boolean = repr.originalFlagSet.is(Inline | Macro) + def isScala3Inline: Boolean = repr.originalFlagSet.is(Inline) + def isScala2Macro: Boolean = repr.originalFlagSet.is(Erased | Macro) - def isPureMixinCtor(implicit ctx: Context): Boolean = isMixinCtor && repr.originalFlagSet.is(Stable) + def isPureMixinCtor: Boolean = isMixinCtor && repr.originalFlagSet.is(Stable) def isMixinCtor: Boolean = u.nme.MIXIN_CONSTRUCTOR == sym.name && sym.owner.isTrait - def isTraitParamAccessor(implicit ctx: Context): Boolean = sym.owner.isTrait && repr.originalFlagSet.is(FieldAccessor|ParamSetter) + def isTraitParamAccessor: Boolean = sym.owner.isTrait && repr.originalFlagSet.is(FieldAccessor|ParamSetter) - def isParamGetter(implicit ctx: Context): Boolean = + def isParamGetter: Boolean = sym.isMethod && sym.repr.originalFlagSet.is(FlagSets.FieldAccessorFlags) /** A computed property that should only be called on a symbol which is known to have been initialised by the @@ -63,13 +63,14 @@ 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(implicit ctx: Context): TastyRepr = { - require(sym.rawInfo.isInstanceOf[TastyRepr], { - val raw = u.showRaw(sym.rawInfo) - val tastyRepr = u.typeOf[TastyRepr] - s"${showSym(sym)} is already completed. Expected $tastyRepr, is $raw. in ${ctx.source}" - }) - sym.rawInfo.asInstanceOf[TastyRepr] + def repr: 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/test/tasty/neg/src-2/TestDependentExtension.check b/test/tasty/neg/src-2/TestDependentExtension.check index 4a8c391801cf..d4934359e9aa 100644 --- a/test/tasty/neg/src-2/TestDependentExtension.check +++ b/test/tasty/neg/src-2/TestDependentExtension.check @@ -1,4 +1,4 @@ -TestDependentExtension_fail.scala:11: error: Unsupported Scala 3 extension method with secondary type parameter list: extract; found in trait tastytest.DependentExtension. +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/TestRealFunctor.check b/test/tasty/neg/src-2/TestRealFunctor.check index f992b382783d..e873d33048a1 100644 --- a/test/tasty/neg/src-2/TestRealFunctor.check +++ b/test/tasty/neg/src-2/TestRealFunctor.check @@ -1,4 +1,4 @@ -TestRealFunctor_fail.scala:6: error: Unsupported Scala 3 extension method with secondary type parameter list: map; found in trait tastytest.RealFunctor. +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