From 5d3ec42ec7cf4d2512010aa840b3eefe92e073f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 2 Oct 2024 09:51:21 +0200 Subject: [PATCH 1/2] Upgrade to Scala 3.4.3. --- build.sbt | 2 +- test-sources/src/main/scala/simple_trees/AccessModifiers.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 2138d228..d49d77e2 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ import sbt.internal.util.ManagedLogger import org.scalajs.jsenv.nodejs.NodeJSEnv -val usedScalaCompiler = "3.4.0" +val usedScalaCompiler = "3.4.3" val usedTastyRelease = usedScalaCompiler val scala2Version = "2.13.14" diff --git a/test-sources/src/main/scala/simple_trees/AccessModifiers.scala b/test-sources/src/main/scala/simple_trees/AccessModifiers.scala index 5ef7824b..e8e908b7 100644 --- a/test-sources/src/main/scala/simple_trees/AccessModifiers.scala +++ b/test-sources/src/main/scala/simple_trees/AccessModifiers.scala @@ -2,7 +2,7 @@ package simple_trees import scala.annotation.nowarn -@nowarn("msg=The \\[this\\] qualifier will be deprecated") +@nowarn("msg=Ignoring \\[this\\] qualifier") class AccessModifiers( localParam: Int, private[this] val privateThisParam: Int, From 9c02c87bdbfe944eaa0f889c2c8795a985ebe736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 2 Oct 2024 10:52:26 +0200 Subject: [PATCH 2/2] Upgrade to Scala 3.5.0 and support its TASTy format. Changes notably include: * Support `Quote` and `QuotePattern` nodes. * Support `FlexibleType`s. --- build.sbt | 7 +- .../main/scala/tastyquery/Definitions.scala | 7 ++ .../src/main/scala/tastyquery/Erasure.scala | 2 + .../src/main/scala/tastyquery/Printers.scala | 49 +++++++++++ .../src/main/scala/tastyquery/Subtyping.scala | 6 ++ .../main/scala/tastyquery/Traversers.scala | 12 +++ .../src/main/scala/tastyquery/Trees.scala | 86 +++++++++++++++++++ .../src/main/scala/tastyquery/TypeMaps.scala | 5 ++ .../src/main/scala/tastyquery/TypeOps.scala | 2 + .../src/main/scala/tastyquery/Types.scala | 20 ++++- .../reader/tasties/TastyFormat.scala | 29 +++++-- .../reader/tasties/TastyReader.scala | 8 +- .../reader/tasties/TreeUnpickler.scala | 51 ++++++++++- .../test/scala/tastyquery/PositionSuite.scala | 6 +- .../test/scala/tastyquery/ReadTreeSuite.scala | 73 +++++++++++++--- .../tastyquery/WholeClasspathSuite.scala | 13 +-- .../scala/simple_trees/QuotesAndSplices.scala | 18 +++- 17 files changed, 358 insertions(+), 36 deletions(-) diff --git a/build.sbt b/build.sbt index d49d77e2..28035d71 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ import sbt.internal.util.ManagedLogger import org.scalajs.jsenv.nodejs.NodeJSEnv -val usedScalaCompiler = "3.4.3" +val usedScalaCompiler = "3.5.0" val usedTastyRelease = usedScalaCompiler val scala2Version = "2.13.14" @@ -52,7 +52,7 @@ val strictCompileSettings = Seq( scalacOptions ++= Seq( "-Xfatal-warnings", "-Yexplicit-nulls", - "-Ysafe-init", + "-Wsafe-init", "-source:future", ), ) @@ -129,7 +129,8 @@ lazy val tastyQuery = ) }, - tastyMiMaPreviousArtifacts := mimaPreviousArtifacts.value, + // Temporarily disabled until we have a published version of tasty-query that can handle 3.4.x. + //tastyMiMaPreviousArtifacts := mimaPreviousArtifacts.value, tastyMiMaConfig ~= { prev => import tastymima.intf._ prev diff --git a/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala b/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala index 28a298f2..b4787c17 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala @@ -43,6 +43,8 @@ final class Definitions private[tastyquery] ( scalaPackage.getPackageDeclOrCreate(termName("compiletime")) private val scalaCollectionImmutablePackage = scalaCollectionPackage.getPackageDeclOrCreate(termName("immutable")) + private val scalaQuotedPackage = + scalaPackage.getPackageDeclOrCreate(termName("quoted")) private val scalaRuntimePackage = scalaPackage.getPackageDeclOrCreate(termName("runtime")) @@ -439,6 +441,8 @@ final class Definitions private[tastyquery] ( lazy val SeqClass = scalaCollectionImmutablePackage.requiredClass("Seq") lazy val Function0Class = scalaPackage.requiredClass("Function0") + private[tastyquery] lazy val ContextFunction1Class = scalaPackage.requiredClass("ContextFunction1") + def FunctionNClass(n: Int): ClassSymbol = withRestrictedContext(scalaPackage.findDecl(typeName(s"Function$n")).asClass) @@ -482,6 +486,9 @@ final class Definitions private[tastyquery] ( private[tastyquery] lazy val PolyFunctionClass = scalaPackage.optionalClass("PolyFunction") + private[tastyquery] lazy val QuotedExprClass = scalaQuotedPackage.requiredClass("Expr") + private[tastyquery] lazy val QuotesClass = scalaQuotedPackage.requiredClass("Quotes") + private[tastyquery] def isPolyFunctionSub(tpe: Type)(using Context): Boolean = PolyFunctionClass.exists(cls => tpe.baseType(cls).isDefined) diff --git a/tasty-query/shared/src/main/scala/tastyquery/Erasure.scala b/tasty-query/shared/src/main/scala/tastyquery/Erasure.scala index df6d3ee1..054ce8d7 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Erasure.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Erasure.scala @@ -111,6 +111,8 @@ private[tastyquery] object Erasure: preErase(tpe.parent, keepUnit) case tpe: RecType => preErase(tpe.parent, keepUnit) + case tpe: FlexibleType => + preErase(tpe.nonNullableType, keepUnit) case _: ByNameType => defn.Function0Class.erasure case tpe: RepeatedType => diff --git a/tasty-query/shared/src/main/scala/tastyquery/Printers.scala b/tasty-query/shared/src/main/scala/tastyquery/Printers.scala index 042ba163..82c412ed 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Printers.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Printers.scala @@ -77,6 +77,9 @@ private[tastyquery] object Printers: print("[") printCommaSeparatedList(tpe.args)(print(_)) print("]") + case tpe: FlexibleType => + print(tpe.nonNullableType) + print("?") case tpe: ByNameType => print("=> ") print(tpe.resultType) @@ -445,6 +448,32 @@ private[tastyquery] object Printers: print(c) print(">") printBlock(bindings, expr)(print(_)) + + case Quote(body, bodyType) => + print("'[") + print(bodyType) + print("]") + printBlock(Nil, body)(print(_)) + + case Splice(expr, spliceType) => + print("$[") + print(spliceType) + print("]") + printBlock(Nil, expr)(print(_)) + + case SplicePattern(pattern, targs, args, spliceType) => + print("$[") + print(spliceType) + print("]") + print(pattern) + if targs.nonEmpty then + print("[") + printCommaSeparatedList(targs)(print(_)) + print("]") + if args.nonEmpty then + print("(") + printCommaSeparatedList(args)(print(_)) + print(")") end print def print(caze: CaseDef): Unit = @@ -577,6 +606,26 @@ private[tastyquery] object Printers: case ExprPattern(expr) => print(expr) + + case QuotePattern(bindings, body, quotes, patternType) => + print("'<") + print(quotes) + print(">") + body match + case Left(termBody) => + print("{ ") + for binding <- bindings do + print(binding) + print("; ") + print(termBody) + print(" }") + case Right(typeBody) => + print("[ ") + for binding <- bindings do + print(binding) + print("; ") + print(typeBody) + print(" ]") end print def print(tree: TypeTree): Unit = tree match diff --git a/tasty-query/shared/src/main/scala/tastyquery/Subtyping.scala b/tasty-query/shared/src/main/scala/tastyquery/Subtyping.scala index 9b2d95de..21b28d67 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Subtyping.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Subtyping.scala @@ -238,6 +238,9 @@ private[tastyquery] object Subtyping: isSubType(tp1, tp2.first) || isSubType(tp1, tp2.second) || level4(tp1, tp2) + case tp2: FlexibleType => + isSubType(tp1, tp2.nullableType) + case tp2: ByNameType => tp1 match case tp1: ByNameType => isSubType(tp1.resultType, tp2.resultType) @@ -478,6 +481,9 @@ private[tastyquery] object Subtyping: case tp1: RecType => isSubType(tp1.parent, tp2) + case tp1: FlexibleType => + isSubType(tp1.nonNullableType, tp2) + case tp1: AndType => // TODO Try and simplify first isSubType(tp1.first, tp2) || isSubType(tp1.second, tp2) diff --git a/tasty-query/shared/src/main/scala/tastyquery/Traversers.scala b/tasty-query/shared/src/main/scala/tastyquery/Traversers.scala index d242760e..76842b8c 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Traversers.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Traversers.scala @@ -120,6 +120,14 @@ object Traversers: traverse(expr) traverse(caller) traverse(bindings) + case Quote(body, bodyType) => + traverse(body) + case Splice(expr, spliceType) => + traverse(expr) + case SplicePattern(pattern, targs, args, spliceType) => + traverse(pattern) + traverse(targs) + traverse(args) case _: ImportIdent | _: Ident | _: This | _: Literal => () @@ -139,6 +147,10 @@ object Traversers: traverse(expr) case WildcardPattern(tpe) => () + case QuotePattern(bindings, body, quotes, patternType) => + traverse(bindings) + body.fold(traverse(_), traverse(_)) + traverse(quotes) // TypeTree-ish case TypeIdent(tpe) => diff --git a/tasty-query/shared/src/main/scala/tastyquery/Trees.scala b/tasty-query/shared/src/main/scala/tastyquery/Trees.scala index 8158f020..1d5fca37 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Trees.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Trees.scala @@ -638,6 +638,33 @@ object Trees { override final def withPos(pos: SourcePosition): ExprPattern = ExprPattern(expr)(pos) end ExprPattern + /** A tree representing a quote pattern `'{ type binding1; ...; body }` or `'[ type binding1; ...; body ]`. + * + * The `bindings` contain the list of quote pattern type variable definitions (`TypeTreeBind`s) + * in the order in which they are defined in the source. + * + * @param bindings + * Type variable definitions + * @param body + * Quoted pattern (without type variable definitions): + * `Left(termTree)` for a term quote pattern `'{ ... }` or + * `Right(typeTree)` for a type quote pattern `'[ ... ]` + * @param quotes + * A reference to the given `Quotes` instance in scope + * @param patternType + * The type of the pattern + */ + final case class QuotePattern( + bindings: List[TypeTreeBind], + body: Either[TermTree, TypeTree], + quotes: TermTree, + patternType: Type + )(pos: SourcePosition) + extends PatternTree(pos) { + override def withPos(pos: SourcePosition): QuotePattern = + QuotePattern(bindings, body, quotes, patternType)(pos) + } + /** Seq(elems) * @param tpt The element type of the sequence. */ @@ -717,6 +744,65 @@ object Trees { override final def withPos(pos: SourcePosition): Inlined = Inlined(expr, caller, bindings)(pos) } + /** A tree representing a quote `'{ body }`. + * + * @param body + * The tree that was quoted + * @param bodyType + * Explicit type of quoted body, which is the source of truth from which we build the type of the quote + */ + final case class Quote(body: TermTree, bodyType: Type)(pos: SourcePosition) extends TermTree(pos) { + protected final def calculateType(using Context): TermType = + // `Quotes ?=> Expr[bodyType]` + defn.ContextFunction1Class.staticRef.appliedTo( + List(defn.QuotesClass.staticRef, defn.QuotedExprClass.staticRef.appliedTo(bodyType)) + ) + end calculateType + + override final def withPos(pos: SourcePosition): Quote = + Quote(body, bodyType)(pos) + } + + /** A tree representing a splice `${ expr }`. + * + * @param expr + * The tree that was spliced + */ + final case class Splice(expr: TermTree, spliceType: Type)(pos: SourcePosition) extends TermTree(pos) { + protected final def calculateType(using Context): TermType = + spliceType + + override final def withPos(pos: SourcePosition): Splice = + Splice(expr, spliceType)(pos) + } + + /** A tree representing a pattern splice `${ pattern }`, `$ident` or `$ident(args*)` in a quote pattern. + * + * Parser will only create `${ pattern }` and `$ident`, hence they will not have args. + * While typing, the `$ident(args*)` the args are identified and desugared into a `SplicePattern` + * containing them. + * + * `SplicePattern` can only be contained within a `QuotePattern`. + * + * @param pattern + * The pattern that was spliced + * @param targs + * The type arguments of the splice (the HOAS arguments) + * @param args + * The arguments of the splice (the HOAS arguments) + * @param spliceType + * The type of the splice, i.e., of this tree + */ + final case class SplicePattern(pattern: PatternTree, targs: List[TypeTree], args: List[TermTree], spliceType: Type)( + pos: SourcePosition + ) extends TermTree(pos) { + protected final def calculateType(using Context): TermType = + spliceType + + override final def withPos(pos: SourcePosition): SplicePattern = + SplicePattern(pattern, targs, args, spliceType)(pos) + } + // --- TypeTrees ------------------------------------------------------------ sealed abstract class TypeArgTree(pos: SourcePosition) extends Tree(pos): diff --git a/tasty-query/shared/src/main/scala/tastyquery/TypeMaps.scala b/tasty-query/shared/src/main/scala/tastyquery/TypeMaps.scala index a97ad105..f674eb71 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/TypeMaps.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/TypeMaps.scala @@ -53,6 +53,8 @@ private[tastyquery] object TypeMaps { tp.derivedAnnotatedType(underlying, annot) protected def derivedMatchType(tp: MatchType, bound: Type, scrutinee: Type, cases: List[MatchTypeCase]): Type = tp.derivedMatchType(bound, scrutinee, cases) + protected def derivedFlexibleType(tp: FlexibleType, nonNullableType: Type): Type = + tp.derivedFlexibleType(nonNullableType) protected def derivedByNameType(tp: ByNameType, restpe: Type): Type = tp.derivedByNameType(restpe) protected def derivedRepeatedType(tp: RepeatedType, elemType: Type): Type = @@ -123,6 +125,9 @@ private[tastyquery] object TypeMaps { case tp: TypeLambda => mapOverLambda(tp) + case tp: FlexibleType => + derivedFlexibleType(tp, this(tp.nonNullableType)) + case tp: ByNameType => derivedByNameType(tp, this(tp.resultType)) diff --git a/tasty-query/shared/src/main/scala/tastyquery/TypeOps.scala b/tasty-query/shared/src/main/scala/tastyquery/TypeOps.scala index cf4cf6b3..a7d0a0fc 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/TypeOps.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/TypeOps.scala @@ -114,6 +114,8 @@ private[tastyquery] object TypeOps: apply(z, tp.thistpe) case tp: AppliedType => tp.args.foldLeft(apply(z, tp.tycon))(this) + case tp: FlexibleType => + apply(z, tp.nonNullableType) case tp: ByNameType => apply(z, tp.resultType) case tp: RepeatedType => diff --git a/tasty-query/shared/src/main/scala/tastyquery/Types.scala b/tasty-query/shared/src/main/scala/tastyquery/Types.scala index 19919f48..9dd72f09 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Types.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Types.scala @@ -58,6 +58,7 @@ import tastyquery.Utils.* * +- AppliedType `C[T1, ..., Tn]` * +- ByNameType type of a by-name parameter `=> T` * +- ThisType `C.this` + * +- FlexibleType `U?` * +- OrType `A | B` * +- AndType `A & B` * +- TypeLambda `[T1, ..., Tn] => R` @@ -579,7 +580,8 @@ object Types { self.superType.typeParams case self: AnnotatedType => self.superType.typeParams - case _: SingletonType | _: RefinedType | _: ByNameType | _: RepeatedType | _: MatchType | _: RecType => + case _: SingletonType | _: RefinedType | _: FlexibleType | _: ByNameType | _: RepeatedType | _: MatchType | + _: RecType => // These types are always proper types Nil case _: NothingType | _: AnyKindType => @@ -1533,6 +1535,22 @@ object Types { override def toString(): String = s"AppliedType($tycon, $args)" } + /** A flexible type `T?`, with `T | Null <: T? <: T`. */ + final class FlexibleType(val nonNullableType: Type) extends TypeProxy { + private val myNullableType: Memo[Type] = uninitializedMemo + + final def nullableType(using Context): Type = memoized(myNullableType) { + OrType(nonNullableType, defn.NullType) + } + + override def underlying(using Context): Type = nonNullableType + + private[tastyquery] final def derivedFlexibleType(nonNullableType: Type): FlexibleType = + if nonNullableType eq this.nonNullableType then this else FlexibleType(nonNullableType) + + override def toString(): String = s"FlexibleType($nonNullableType)" + } + /** A by-name parameter type of the form `=> T`. */ final class ByNameType(val resultType: Type) extends TypeProxy { override def underlying(using Context): Type = resultType diff --git a/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TastyFormat.scala b/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TastyFormat.scala index 05b82938..185bc48a 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TastyFormat.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TastyFormat.scala @@ -107,10 +107,14 @@ import scala.annotation.switch * WHILE Length cond_Term body_Term -- while cond do body * REPEATED Length elem_Type elem_Term* -- Varargs argument of type `elem` * SELECTouter Length levels_Nat qual_Term underlying_Type -- Follow `levels` outer links, starting from `qual`, with given `underlying` type + * QUOTE Length body_Term bodyTpe_Type -- Quoted expression `'{ body }` of a body typed as `bodyTpe` + * SPLICE Length expr_Term tpe_Type -- Spliced expression `${ expr }` typed as `tpe` + * SPLICEPATTERN Length pat_Term tpe_Type targs_Type* args_Term* -- Pattern splice `${pat}` or `$pat[targs*](args*)` in a quoted pattern of type `tpe`. * -- patterns: * BIND Length boundName_NameRef patType_Type pat_Term -- name @ pat, wherev `patType` is the type of the bound symbol * ALTERNATIVE Length alt_Term* -- alt1 | ... | altn as a pattern * UNAPPLY Length fun_Term ImplicitArg* pat_Type pat_Term* -- Unapply node `fun(_: pat_Type)(implicitArgs)` flowing into patterns `pat`. + * QUOTEPATTERN Length body_Term quotes_Term pat_Type bindings_Term* -- Quote pattern node `'{ bindings*; body }(using quotes)` * -- type trees: * IDENTtpt NameRef Type -- Used for all type idents * SELECTtpt NameRef qual_Term -- qual.name @@ -123,6 +127,8 @@ import scala.annotation.switch * MATCHtpt Length bound_Term? sel_Term CaseDef* -- sel match { CaseDef } where `bound` is optional upper bound of all rhs * BYNAMEtpt underlying_Term -- => underlying * SHAREDterm term_ASTRef -- Link to previously serialized term + * -- pickled quote trees: -- These trees can only appear in pickled quotes. They will never be in a TASTy file. + * EXPLICITtpt tpt_Term -- Tag for a type tree that in a context where it is not explicitly known that this tree is a type. * HOLE Length idx_Nat tpe_Type arg_Tree* -- Splice hole with index `idx`, the type of the hole `tpe`, type and term arguments of the hole `arg`s * * CaseDef = CASEDEF Length pat_Term rhs_Tree guard_Tree? -- case pat if guard => rhs @@ -170,6 +176,7 @@ import scala.annotation.switch * ORtype Length left_Type right_Type -- lefgt | right * MATCHtype Length bound_Type sel_Type case_Type* -- sel match {cases} with optional upper `bound` * MATCHCASEtype Length pat_type rhs_Type -- match cases are MATCHCASEtypes or TYPELAMBDAtypes over MATCHCASEtypes + * FLEXIBLEtype Length underlying_Type -- (underlying)? * BIND Length boundName_NameRef bounds_Type Modifier* -- boundName @ bounds, for type-variables defined in a type pattern * BYNAMEtype underlying_Type -- => underlying * PARAMtype Length binder_ASTRef paramNum_Nat -- A reference to parameter # paramNum in lambda type `binder` @@ -289,7 +296,7 @@ private[tasties] object TastyFormat: * compatibility, but remains backwards compatible, with all * preceeding `MinorVersion`. */ - final val MinorVersion: Int = 4 + final val MinorVersion: Int = 5 /** Natural Number. The `ExperimentalVersion` allows for * experimentation with changes to TASTy without committing @@ -518,6 +525,8 @@ private[tasties] object TastyFormat: final val RECtype = 100 final val SINGLETONtpt = 101 final val BOUNDED = 102 + final val EXPLICITtpt = 103 + final val ELIDED = 104 // Cat. 4: tag Nat AST @@ -584,14 +593,17 @@ private[tasties] object TastyFormat: final val TYPEREFin = 175 final val SELECTin = 176 final val EXPORT = 177 - // final val ??? = 178 - // final val ??? = 179 + final val QUOTE = 178 + final val SPLICE = 179 final val METHODtype = 180 final val APPLYsigpoly = 181 + final val QUOTEPATTERN = 182 + final val SPLICEPATTERN = 183 final val MATCHtype = 190 final val MATCHtpt = 191 final val MATCHCASEtype = 192 + final val FLEXIBLEtype = 193 final val HOLE = 255 @@ -606,7 +618,7 @@ private[tasties] object TastyFormat: || firstNatTreeTag <= tag && tag <= RENAMED || firstASTTreeTag <= tag && tag <= BOUNDED || firstNatASTTreeTag <= tag && tag <= NAMEDARG - || firstLengthTreeTag <= tag && tag <= MATCHtpt + || firstLengthTreeTag <= tag && tag <= FLEXIBLEtype || tag == HOLE end isLegalTag @@ -625,7 +637,7 @@ private[tasties] object TastyFormat: def isTypeTreeTag(tag: Int): Boolean = (tag: @switch) match case IDENTtpt | SELECTtpt | SINGLETONtpt | REFINEDtpt | APPLIEDtpt | LAMBDAtpt | TYPEBOUNDStpt | ANNOTATEDtpt | - BYNAMEtpt | MATCHtpt | BIND => + BYNAMEtpt | MATCHtpt | EXPLICITtpt | BIND => true case _ => false @@ -768,9 +780,16 @@ private[tasties] object TastyFormat: case MATCHCASEtype => "MATCHCASEtype" case MATCHtpt => "MATCHtpt" case PARAMtype => "PARAMtype" + case FLEXIBLEtype => "FLEXIBLEtype" case ANNOTATION => "ANNOTATION" case PRIVATEqualified => "PRIVATEqualified" case PROTECTEDqualified => "PROTECTEDqualified" + case EXPLICITtpt => "EXPLICITtpt" + case ELIDED => "ELIDED" + case QUOTE => "QUOTE" + case SPLICE => "SPLICE" + case QUOTEPATTERN => "QUOTEPATTERN" + case SPLICEPATTERN => "SPLICEPATTERN" case HOLE => "HOLE" case _ => s"UnknownTag($tag)" diff --git a/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TastyReader.scala b/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TastyReader.scala index 06f9f14c..4482e118 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TastyReader.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TastyReader.scala @@ -131,17 +131,21 @@ private[tasties] class TastyReader(val bytes: Array[Byte], start: Int, end: Int, def goto(addr: Addr): Unit = bp = index(addr) + /** Is the current read address before the given end address? */ + def isBefore(end: Addr): Boolean = + bp < index(end) + /** Perform `op` until `end` address is reached and collect results in a list. */ def until[T](end: Addr)(op: => T): List[T] = val buf = new mutable.ListBuffer[T] - while bp < index(end) do buf += op + while isBefore(end) do buf += op assert(bp == index(end)) buf.toList end until /** If before given `end` address, the result of `op`, otherwise `default` */ def ifBefore[T](end: Addr)(op: => T, default: T): T = - if bp < index(end) then op + if isBefore(end) then op else default /** Perform `op` while cindition `cond` holds and collect results in a list. */ diff --git a/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala b/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala index d0d49249..89420eaf 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala @@ -160,7 +160,8 @@ private[tasties] class TreeUnpickler private ( reader.readNat() createSymbols(owner) case APPLY | TYPEAPPLY | SUPER | TYPED | ASSIGN | BLOCK | INLINED | LAMBDA | IF | MATCH | TRY | WHILE | REPEATED | - ALTERNATIVE | UNAPPLY | APPLIEDtpt | LAMBDAtpt | TYPEBOUNDStpt | ANNOTATEDtpt | MATCHtpt | CASEDEF => + ALTERNATIVE | UNAPPLY | APPLIEDtpt | LAMBDAtpt | TYPEBOUNDStpt | ANNOTATEDtpt | MATCHtpt | CASEDEF | QUOTE | + SPLICE | QUOTEPATTERN | SPLICEPATTERN => val end = reader.readEnd() reader.until(end)(createSymbols(owner)) case SELECTin => @@ -878,6 +879,19 @@ private[tasties] class TreeUnpickler private ( val patType = readTrueType() val patterns = reader.until(end)(readPattern) Unapply(fun, args, patterns)(spn) + case QUOTEPATTERN => + val spn = span + reader.readByte() + val end = reader.readEnd() + val body = + if reader.nextByte == EXPLICITtpt then + reader.readByte() + Right(readTypeTree) + else Left(readTerm) + val quotes = readTerm + val patternType = readTrueType() + val bindings = reader.until(end)(readTypeTree.asInstanceOf[TypeTreeBind]) + QuotePattern(bindings, body, quotes, patternType)(spn) case SHAREDterm => val spn = span reader.readByte() @@ -1080,6 +1094,32 @@ private[tasties] class TreeUnpickler private ( val caller = readInlinedCaller(end) val bindings = reader.until(end)(readValOrDefDef) Inlined(expr, caller, bindings)(spn) + case QUOTE => + val spn = span + reader.readByte() + reader.readEnd() // ignored + val body = readTerm + val bodyType = readTrueType() + Quote(body, bodyType)(spn) + case SPLICE => + val spn = span + reader.readByte() + reader.readEnd() // ignored + val expr = readTerm + val spliceType = readTrueType() + Splice(expr, spliceType)(spn) + case SPLICEPATTERN => + val spn = span + reader.readByte() + val end = reader.readEnd() + val pattern = readPattern + val spliceType = readTrueType() + val targs = reader.collectWhile(reader.isBefore(end) && reader.nextByte == EXPLICITtpt) { + reader.readByte() + readTypeTree + } + val args = reader.until(end)(readTerm) + SplicePattern(pattern, targs, args, spliceType)(spn) case SHAREDterm => val spn = span reader.readByte() @@ -1325,6 +1365,10 @@ private[tasties] class TreeUnpickler private ( val lambda = readBinderRef[ParamRefBinder]() val num = reader.readNat() lambda.paramRefs(num) + case FLEXIBLEtype => + reader.readByte() + reader.readEnd() + FlexibleType(readTrueType()) case REFINEDtype => reader.readByte() val end = reader.readEnd() @@ -1619,7 +1663,10 @@ private[tasties] class TreeUnpickler private ( reader.readByte() ByNameTypeTree(readTypeTree)(spn) case BLOCK => - // #284 See QuotesAndSplices.typeQuoteMatching + /* #284 See QuotesAndSplices.typeQuoteMatching, but before 3.5.0. + * It is unclear whether post-3.5.0 TASTy can still produce this shape, + * and therefore whether `TypeBindingsTree` is still relevant at all. + */ val spn = span reader.readByte() val end = reader.readEnd() diff --git a/tasty-query/shared/src/test/scala/tastyquery/PositionSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/PositionSuite.scala index c9540772..bf91060b 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/PositionSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/PositionSuite.scala @@ -186,11 +186,7 @@ class PositionSuite extends RestrictedUnpicklingSuite { } testUnpickle("lambda", "simple_trees.Function") { tree => - assertEquals( - collectCode[Lambda](tree), - // NoSpan is the expected behavior - Nil - ) + assertEquals(collectCode[Lambda](tree), List("(x: Int) => x + 1", "() => ()", "[T] => (x: T) => x", "x => x")) } testUnpickle("return", "simple_trees.Return") { tree => diff --git a/tasty-query/shared/src/test/scala/tastyquery/ReadTreeSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/ReadTreeSuite.scala index bbe4c29f..7902736f 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/ReadTreeSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/ReadTreeSuite.scala @@ -2308,25 +2308,74 @@ class ReadTreeSuite extends RestrictedUnpicklingSuite { } testUnpickle("quotes-and-splices", "simple_trees.QuotesAndSplices$") { tree => - val typeQuoteMatchingDef = findTree(tree) { case dd @ DefDef(SimpleName("typeQuoteMatching"), _, _, _, _) => + val termQuoteBody = findTree(tree) { case dd @ DefDef(SimpleName("termQuote"), _, _, Some(rhs), _) => + rhs + } + val termQuoteCheck: StructureCheck = { + case Quote(Literal(Constant("hello")), TypeRefInternal(_, SimpleTypeName("String"))) => + } + assert(containsSubtree(termQuoteCheck)(clue(termQuoteBody))) + + val termSpliceBody = findTree(tree) { case dd @ DefDef(SimpleName("termSplice"), _, _, Some(rhs), _) => + rhs + } + val termSpliceCheck: StructureCheck = { + case Splice(Block(List(_: DefDef), _: Lambda), TypeRefInternal(_, SimpleTypeName("String"))) => + } + assert(containsSubtree(termSpliceCheck)(clue(termSpliceBody))) + + val termQuotePatternDef = findTree(tree) { case dd @ DefDef(SimpleName("termQuotePattern"), _, _, _, _) => dd } - val typeQuoteMatchingCaseDef = findTree(typeQuoteMatchingDef) { case cd: CaseDef => + val termQuotePatternCaseDef = findTree(termQuotePatternDef) { case cd: CaseDef => cd } - val typeQuoteMatchingCheck: StructureCheck = { - case TypeBindingsTree( - List( - TypeMember(SimpleTypeName("t"), InferredTypeBoundsTree(_), tSym), - TypeMember(SimpleTypeName("u"), InferredTypeBoundsTree(_), uSym) + val termQuotePatternCheck: StructureCheck = { + case QuotePattern( + Nil, + Left( + Apply( + Select( + Typed( + SplicePattern( + Bind(SimpleName("a"), WildcardPattern(_), aSym), + Nil, + Nil, + TypeRefInternal(ScalaPackageRef(), SimpleTypeName("Int")) + ), + _ + ), + _ + ), + _ + ) ), - AppliedTypeTree( - TypeIdent(SimpleTypeName("Map")), - List(TypeWrapper(TypeRefInternal(NoPrefix, tSymRef)), TypeWrapper(TypeRefInternal(NoPrefix, uSymRef))) - ) + Ident(SimpleName("quotes")), + _ + ) => + } + assert(containsSubtree(termQuotePatternCheck)(clue(termQuotePatternCaseDef))) + + val typeQuotePatternDef = findTree(tree) { case dd @ DefDef(SimpleName("typeQuotePattern"), _, _, _, _) => + dd + } + val typeQuotePatternCaseDef = findTree(typeQuotePatternDef) { case cd: CaseDef => + cd + } + val typeQuotePatternCheck: StructureCheck = { + case QuotePattern( + List(TypeTreeBind(SimpleTypeName("t"), _, tSym), TypeTreeBind(SimpleTypeName("u"), _, uSym)), + Right( + AppliedTypeTree( + TypeIdent(SimpleTypeName("Map")), + List(TypeWrapper(TypeRefInternal(NoPrefix, tSymRef)), TypeWrapper(TypeRefInternal(NoPrefix, uSymRef))) + ) + ), + Ident(SimpleName("quotes")), + _ ) if tSymRef == tSym && uSymRef == uSym && tSym != uSym => } - assert(containsSubtree(typeQuoteMatchingCheck)(clue(typeQuoteMatchingCaseDef))) + assert(containsSubtree(typeQuotePatternCheck)(clue(typeQuotePatternCaseDef))) } testUnpickle("anon-classes-in-constructor", "simple_trees.AnonClassesInCtor") { tree => diff --git a/tasty-query/shared/src/test/scala/tastyquery/WholeClasspathSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/WholeClasspathSuite.scala index b53cc0e5..95a8f61b 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/WholeClasspathSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/WholeClasspathSuite.scala @@ -80,8 +80,8 @@ class WholeClasspathSuite extends UnrestrictedUnpicklingSuite: traverseEveryTopLevelClassDef(_ != RuntimeLazyValsClass)(traverser) tracker.finalize( - // As of this writing, there were 15293 successes - minExpectedSuccessCount = 15000, + // As of this writing, there were 11617 successes + minExpectedSuccessCount = 11000, errorTitle = "Could not resolve the `symbol` of some trees on the classpath:" ) } @@ -116,8 +116,8 @@ class WholeClasspathSuite extends UnrestrictedUnpicklingSuite: traverseEveryTopLevelClassDef(_ != RuntimeLazyValsClass)(traverser) tracker.finalize( - // As of this writing, there were 44959 successes - minExpectedSuccessCount = 44000, + // As of this writing, there were 38337 successes + minExpectedSuccessCount = 38000, errorTitle = "Could not compute the `tpe` of some term trees on the classpath:" ) } @@ -144,7 +144,10 @@ object WholeClasspathSuite: val errors = errorsBuilder.result() if errors.nonEmpty then fail(errors.mkString(errorTitle + "\n", "\n", "\n")) - assert(clue(successCount) >= clue(minExpectedSuccessCount)) + assert( + clue(successCount) >= clue(minExpectedSuccessCount), + "There were no failures, but there were not as many successes as expected." + ) end finalize end Tracker end WholeClasspathSuite diff --git a/test-sources/src/main/scala/simple_trees/QuotesAndSplices.scala b/test-sources/src/main/scala/simple_trees/QuotesAndSplices.scala index c46ec975..74508f71 100644 --- a/test-sources/src/main/scala/simple_trees/QuotesAndSplices.scala +++ b/test-sources/src/main/scala/simple_trees/QuotesAndSplices.scala @@ -3,7 +3,23 @@ package simple_trees import scala.quoted.* object QuotesAndSplices: - def typeQuoteMatching(using quotes: Quotes): Expr[Unit] = + def termQuote(using Quotes): Expr[String] = + '{ "hello" } + + def termSplice(using Quotes)(s: Expr[String]): Expr[String] = + '{ $s + "foo" } + + /* Reading only -- mostly making sure that there isn't a separate TYPESPLICE + * tag that we don't know about. + */ + def typeSplice[T: Type](using Quotes)(e: Expr[T]): Expr[T] = + '{ $e: T } + + def termQuotePattern(using quotes: Quotes)(e: Expr[Int]): Expr[Int] = + e match + case '{ ($a: Int) + 1 } => a + + def typeQuotePattern(using quotes: Quotes): Expr[Unit] = Type.of[Any] match case '[Map[t, u]] => '{ () } end QuotesAndSplices