From ffbd83db7e4530f251ae0a34909b9c0379869801 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 23 Aug 2021 17:44:12 +0200 Subject: [PATCH] Add TypedOrTest as super type of Typed in reflection TypeOrTest can match or construct type tests or ascriptions `x: T` in expressions or patterns. Unlike `Typed`, it contains a `Tree` instead of a `Term` which might be one of the patterns trees. Fixes #12222 --- .../quoted/runtime/impl/QuotesImpl.scala | 32 +++++++++- .../runtime/impl/printers/Extractors.scala | 2 + .../runtime/impl/printers/SourceCode.scala | 10 +++- library/src/scala/quoted/Quotes.scala | 60 +++++++++++++++---- project/MiMaFilters.scala | 3 + tests/run-staging/i5161.check | 2 +- 6 files changed, 93 insertions(+), 16 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 788db552a6f1..2d3f5490563c 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -345,6 +345,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler case x: (tpd.SeqLiteral & x.type) => Some(x) case x: (tpd.Inlined & x.type) => Some(x) case x: (tpd.NamedArg & x.type) => Some(x) + case x: (tpd.Typed & x.type) => + TypedTypeTest.unapply(x) // Matches `Typed` but not `TypedOrTest` case _ => if x.isTerm then Some(x) else None end TermTypeTest @@ -669,7 +671,10 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object TypedTypeTest extends TypeTest[Tree, Typed]: def unapply(x: Tree): Option[Typed & x.type] = x match - case x: (tpd.Typed & x.type) => Some(x) + case x: (tpd.Typed & x.type) => + x.expr match + case TermTypeTest(_) => Some(x) + case _ => None case _ => None end TypedTypeTest @@ -689,6 +694,31 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end extension end TypedMethods + type TypedOrTest = tpd.Typed + + object TypedOrTestTypeTest extends TypeTest[Tree, TypedOrTest]: + def unapply(x: Tree): Option[TypedOrTest & x.type] = x match + case x: (tpd.Typed & x.type) => Some(x) + case _ => None + end TypedOrTestTypeTest + + object TypedOrTest extends TypedOrTestModule: + def apply(expr: Term, tpt: TypeTree): Typed = + withDefaultPos(tpd.Typed(xCheckMacroValidExpr(expr), tpt)) + def copy(original: Tree)(expr: Term, tpt: TypeTree): Typed = + tpd.cpy.Typed(original)(xCheckMacroValidExpr(expr), tpt) + def unapply(x: Typed): (Term, TypeTree) = + (x.expr, x.tpt) + end TypedOrTest + + given TypedOrTestMethods: TypedOrTestMethods with + extension (self: Typed) + def tree: Tree = self.expr + def tpt: TypeTree = self.tpt + end extension + end TypedOrTestMethods + + type Assign = tpd.Assign object AssignTypeTest extends TypeTest[Tree, Assign]: diff --git a/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala b/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala index da6b32bef4f3..0bea8f0ab643 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala @@ -176,6 +176,8 @@ object Extractors { this += "Unapply(" += fun += ", " ++= implicits += ", " ++= patterns += ")" case Alternatives(patterns) => this += "Alternatives(" ++= patterns += ")" + case TypedOrTest(tree, tpt) => + this += "TypedOrTest(" += tree += ", " += tpt += ")" } def visitConstant(x: Constant): this.type = x match { diff --git a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala index 760182b396ea..77a54e23ad61 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala @@ -928,9 +928,13 @@ object SourceCode { case Alternatives(trees) => inParens(printPatterns(trees, " | ")) - case Typed(Wildcard(), tpt) => - this += "_: " - printTypeTree(tpt) + case TypedOrTest(tree1, tpt) => + tree1 match + case Wildcard() => + this += "_: " + printTypeTree(tpt) + case _ => + printPattern(tree1) case v: Term => printTree(v) diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 3a0d4b2e7ecf..e6f229676d34 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -131,7 +131,6 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * | +- Apply * | +- TypeApply * | +- Super - * | +- Typed * | +- Assign * | +- Block * | +- Closure @@ -144,7 +143,15 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * | +- Inlined * | +- SelectOuter * | +- While + * | +---+- Typed + * | / + * +- TypedOrTest +----------------ยท + * +- Bind + * +- Unapply + * +- Alternatives * | + * +- CaseDef + * +- TypeCaseDef * | * +- TypeTree ----+- Inferred * | +- TypeIdent @@ -162,13 +169,6 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * | * +- TypeBoundsTree * +- WildcardTypeTree - * | - * +- CaseDef - * | - * +- TypeCaseDef - * +- Bind - * +- Unapply - * +- Alternatives * * +- ParamClause -+- TypeParamClause * +- TermParamClause @@ -1135,8 +1135,12 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** `TypeTest` that allows testing at runtime in a pattern match if a `Tree` is a `Typed` */ given TypedTypeTest: TypeTest[Tree, Typed] - /** Tree representing a type ascription `x: T` in the source code */ - type Typed <: Term + /** Tree representing a type ascription `x: T` in the source code. + * + * Also represents a pattern that contains a term `x`. + * Other `: T` patterns use the more general `TypedOrTest`. + */ + type Typed <: Term & TypedOrTest /** Module object of `type Typed` */ val Typed: TypedModule @@ -1583,6 +1587,38 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => end extension end WhileMethods + /** `TypeTest` that allows testing at runtime in a pattern match if a `Tree` is a `TypedOrTest` */ + given TypedOrTestTypeTest: TypeTest[Tree, TypedOrTest] + + /** Tree representing a type ascription or type test pattern `x: T` in the source code. */ + type TypedOrTest <: Tree + + /** Module object of `type TypedOrTest` */ + val TypedOrTest: TypedOrTestModule + + /** Methods of the module object `val TypedOrTest` */ + trait TypedOrTestModule { this: TypedOrTest.type => + + /** Create a type ascription `: ` */ + def apply(expr: Tree, tpt: TypeTree): TypedOrTest + + def copy(original: Tree)(expr: Tree, tpt: TypeTree): TypedOrTest + + /** Matches `: ` */ + def unapply(x: TypedOrTest): (Tree, TypeTree) + } + + /** Makes extension methods on `TypedOrTest` available without any imports */ + given TypedOrTestMethods: TypedOrTestMethods + + /** Extension methods of `TypedOrTest` */ + trait TypedOrTestMethods: + extension (self: TypedOrTest) + def tree: Tree + def tpt: TypeTree + end extension + end TypedOrTestMethods + // ----- TypeTrees ------------------------------------------------ /** Type tree representing a type written in the source */ @@ -4414,6 +4450,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => Unapply.copy(pattern)(transformTerm(pattern.fun)(owner), transformSubTrees(pattern.implicits)(owner), transformTrees(pattern.patterns)(owner)) case pattern: Alternatives => Alternatives.copy(pattern)(transformTrees(pattern.patterns)(owner)) + case TypedOrTest(inner, tpt) => + TypedOrTest.copy(tree)(transformTree(inner)(owner), transformTypeTree(tpt)(owner)) } } @@ -4464,7 +4502,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => case New(tpt) => New.copy(tree)(transformTypeTree(tpt)(owner)) case Typed(expr, tpt) => - Typed.copy(tree)(/*FIXME #12222: transformTerm(expr)(owner)*/transformTree(expr)(owner).asInstanceOf[Term], transformTypeTree(tpt)(owner)) + Typed.copy(tree)(transformTerm(expr)(owner), transformTypeTree(tpt)(owner)) case tree: NamedArg => NamedArg.copy(tree)(tree.name, transformTerm(tree.value)(owner)) case Assign(lhs, rhs) => diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index ede72bc1e9ab..697f0bca662b 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -15,5 +15,8 @@ object MiMaFilters { exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SourceFileMethods.path"), exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#UnapplyModule.apply"), exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#UnapplyModule.apply"), + exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.TypedOrTestTypeTest"), + exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.TypedOrTest"), + exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.TypedOrTestMethods"), ) } diff --git a/tests/run-staging/i5161.check b/tests/run-staging/i5161.check index a178c827d633..27c72498d7f1 100644 --- a/tests/run-staging/i5161.check +++ b/tests/run-staging/i5161.check @@ -1,6 +1,6 @@ run : Some(2) show : scala.Tuple2.apply[scala.Option[scala.Int], scala.Option[scala.Int]](scala.Some.apply[scala.Int](1), scala.Some.apply[scala.Int](1)) match { - case scala.Tuple2((scala.Some(x): scala.Some[scala.Int]), (scala.Some(y): scala.Some[scala.Int])) => + case scala.Tuple2(scala.Some(x), scala.Some(y)) => scala.Some.apply[scala.Int](x.+(y)) case _ => scala.None