From cbc2e9b4ebf9f701f3e014aac806811e42dec5c1 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 12 Jun 2023 08:22:06 +0200 Subject: [PATCH 1/3] Fix bounds of encoded type variables in quote patterns When we encode quote patterns into `unapply` methods, we need to create a copy of each type variable. One copy is kept within the quote(in the next stage) and the other is used in the `unapply` method to define the usual pattern type variables. When creating the latter we copied the symbols but did not update the infos. This implies that if type variables would be bounded by each other, the bounds of the copies would be the original types instead of the copies. We need to update those references. To update the info we now create all the symbols in one pass and the update all their infos in a second pass. This also implies that we cannot use the `newPatternBoundSymbol` to create the symbol as this constructor will register the info into GADT bounds. Instead we use the plain `newSymbol`. Then in the second pass, when we have updated the infos, we register the symbol into GADT bounds. Note that the code in the added test does compiles correctly, but it had the inconsistent bounds. This test is added in case we need to manually inspect the bounds latter. This test does fail to compile in https://github.com/lampepfl/dotty/pull/17935 if this fix is not applied. --- .../tools/dotc/typer/QuotesAndSplices.scala | 56 ++++++++++++++++--- .../quote-pattern-type-variable-bounds.scala | 12 ++++ 2 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 tests/pos-macros/quote-pattern-type-variable-bounds.scala diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index d3becfd6e9b7..d8bf8575aa79 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -214,13 +214,7 @@ trait QuotesAndSplices { private def splitQuotePattern(quoted: Tree)(using Context): (collection.Map[Symbol, Bind], Tree, List[Tree]) = { val ctx0 = ctx - val typeBindings: mutable.Map[Symbol, Bind] = mutable.LinkedHashMap.empty - def getBinding(sym: Symbol): Bind = - typeBindings.getOrElseUpdate(sym, { - val bindingBounds = sym.info - val bsym = newPatternBoundSymbol(sym.name.toString.stripPrefix("$").toTypeName, bindingBounds, quoted.span) - Bind(bsym, untpd.Ident(nme.WILDCARD).withType(bindingBounds)).withSpan(quoted.span) - }) + val bindSymMapping: collection.Map[Symbol, Bind] = unapplyBindingsMapping(quoted) object splitter extends tpd.TreeMap { private var variance: Int = 1 @@ -300,7 +294,7 @@ trait QuotesAndSplices { report.error(IllegalVariableInPatternAlternative(tdef.symbol.name), tdef.srcPos) if variance == -1 then tdef.symbol.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_fromAboveAnnot.typeRef)).withSpan(tdef.span))) - val bindingType = getBinding(tdef.symbol).symbol.typeRef + val bindingType = bindSymMapping(tdef.symbol).symbol.typeRef val bindingTypeTpe = AppliedType(defn.QuotedTypeClass.typeRef, bindingType :: Nil) val sym = newPatternBoundSymbol(nameOfSyntheticGiven, bindingTypeTpe, tdef.span, flags = ImplicitVal)(using ctx0) buff += Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingTypeTpe)).withSpan(tdef.span) @@ -337,7 +331,51 @@ trait QuotesAndSplices { new TreeTypeMap(typeMap = typeMap).transform(shape1) } - (typeBindings, shape2, patterns) + (bindSymMapping, shape2, patterns) + } + + /** For each type variable defined in the quote pattern we generate an equivalent + * binding that will be as type variable in the encoded `unapply` of the quote pattern. + * + * @return Mapping from type variable symbols defined in the quote pattern into + * type variable `Bind` definitions for the `unapply` of the quote pattern. + * This mapping retains the original type variable definition order. + */ + private def unapplyBindingsMapping(quoted: Tree)(using Context): collection.Map[Symbol, Bind] = { + val mapping = mutable.LinkedHashMap.empty[Symbol, Symbol] + // Collect all existing type variable bindings and create new symbols for them. + // The old info is used, it may contain references to the old symbols. + new tpd.TreeTraverser { + def traverse(tree: Tree)(using Context): Unit = tree match { + case _: SplicePattern => + case Select(pat: Bind, _) if tree.symbol.isTypeSplice => + val sym = tree.tpe.dealias.typeSymbol + if sym.exists then registerNewBindSym(sym) + case tdef: TypeDef => + if tdef.symbol.hasAnnotation(defn.QuotedRuntimePatterns_patternTypeAnnot) then + registerNewBindSym(tdef.symbol) + traverseChildren(tdef) + case _ => + traverseChildren(tree) + } + private def registerNewBindSym(sym: Symbol): Unit = + if !mapping.contains(sym) then + mapping(sym) = newSymbol(ctx.owner, sym.name.toString.stripPrefix("$").toTypeName, Case | sym.flags, sym.info, coord = quoted.span) + }.traverse(quoted) + + // Replace symbols in `mapping` in the infos of the new symbol and register GADT bounds. + // GADT bounds need to be added after the info is updated to avoid references to the old symbols. + var oldBindings: List[Symbol] = mapping.keys.toList + var newBindingsRefs: List[Type] = mapping.values.toList.map(_.typeRef) + for newBindings <- mapping.values do + newBindings.info = newBindings.info.subst(oldBindings, newBindingsRefs) + ctx.gadtState.addToConstraint(newBindings) // This must be preformed after the info has been updated + + // Map into Bind nodes retaining the original order + val mapping2: mutable.Map[Symbol, Bind] = mutable.LinkedHashMap.empty + for (oldSym, newSym) <- mapping do + mapping2(oldSym) = Bind(newSym, untpd.Ident(nme.WILDCARD).withType(newSym.info)).withSpan(quoted.span) + mapping2 } /** Type a quote pattern `case '{ } =>` qiven the a current prototype. Typing the pattern diff --git a/tests/pos-macros/quote-pattern-type-variable-bounds.scala b/tests/pos-macros/quote-pattern-type-variable-bounds.scala new file mode 100644 index 000000000000..c342783664d5 --- /dev/null +++ b/tests/pos-macros/quote-pattern-type-variable-bounds.scala @@ -0,0 +1,12 @@ +import quoted.* + +def foo(using Quotes)(x: Expr[Int]) = + x match + case '{ type t; type u <: `t`; f[`t`, `u`] } => + case '{ type u <: `t`; type t; f[`t`, `u`] } => + case '{ type t; type u <: `t`; g[F[`t`, `u`]] } => + case '{ type u <: `t`; type t; g[F[`t`, `u`]] } => + +def f[T, U <: T] = ??? +def g[T] = ??? +type F[T, U <: T] From b7b13e5c8ffa110ea3fbc5667b636e7132850fdf Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 7 Jun 2023 08:58:12 +0200 Subject: [PATCH 2/3] Add QuotePattern AST node We add the definition of the `QuotePattern` AST. In this first step, we keep the typing of quote patterns untouched and decode the resulting unapply into a `QuotePattern`. This AST is used to preform all transformations until the `staging` phase, where it is encoded into an `unapply` and a `Quote` AST. We also encode the AST into an `unapply` when we pickle the tree to keep the same pickled representation as before. --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 9 +- compiler/src/dotty/tools/dotc/ast/Trees.scala | 39 ++- compiler/src/dotty/tools/dotc/ast/tpd.scala | 3 + compiler/src/dotty/tools/dotc/ast/untpd.scala | 1 + .../dotty/tools/dotc/core/Definitions.scala | 5 +- .../tools/dotc/core/tasty/TreePickler.scala | 4 + .../tools/dotc/core/tasty/TreeUnpickler.scala | 7 +- .../tools/dotc/printing/RefinedPrinter.scala | 8 + .../tools/dotc/quoted/QuotePatterns.scala | 238 ++++++++++++++++++ .../tools/dotc/staging/CrossStageSafety.scala | 16 +- .../tools/dotc/transform/PostTyper.scala | 9 + .../tools/dotc/transform/TreeChecker.scala | 19 ++ .../tools/dotc/transform/patmat/Space.scala | 22 +- .../src/dotty/tools/dotc/typer/Checking.scala | 28 +-- .../tools/dotc/typer/QuotesAndSplices.scala | 13 +- .../src/dotty/tools/dotc/typer/ReTyper.scala | 19 ++ .../dotty/tools/dotc/typer/TypeAssigner.scala | 3 + .../src/dotty/tools/dotc/typer/Typer.scala | 1 + .../quoted/runtime/impl/QuotesImpl.scala | 16 +- .../fatal-warnings/i16649-refutable.scala | 3 + .../quote-type-variable-no-inference.check | 10 +- .../quote-type-variable-no-inference.scala | 2 +- .../Macro_1.scala | 8 + .../Test_2.scala | 4 + 24 files changed, 438 insertions(+), 49 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/quoted/QuotePatterns.scala create mode 100644 tests/run-macros/quote-match-more-that-22-splices/Macro_1.scala create mode 100644 tests/run-macros/quote-match-more-that-22-splices/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index d5c01ae7b7b6..183025da994e 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -807,12 +807,19 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => /** The variables defined by a pattern, in reverse order of their appearance. */ def patVars(tree: Tree)(using Context): List[Symbol] = { - val acc = new TreeAccumulator[List[Symbol]] { + val acc = new TreeAccumulator[List[Symbol]] { outer => def apply(syms: List[Symbol], tree: Tree)(using Context) = tree match { case Bind(_, body) => apply(tree.symbol :: syms, body) case Annotated(tree, id @ Ident(tpnme.BOUNDTYPE_ANNOT)) => apply(id.symbol :: syms, tree) + case QuotePattern(bindings, body, _) => quotePatVars(bindings.map(_.symbol) ::: syms, body) case _ => foldOver(syms, tree) } + private object quotePatVars extends TreeAccumulator[List[Symbol]] { + def apply(syms: List[Symbol], tree: Tree)(using Context) = tree match { + case SplicePattern(pat, _) => outer.apply(syms, pat) + case _ => foldOver(syms, tree) + } + } } acc(Nil, tree) } diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index bd172e8db6d3..27c90ef324c7 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -686,9 +686,9 @@ object Trees { * phases. After `pickleQuotes` phase, the only quotes that exist are in `inline` * methods. These are dropped when we remove the inline method implementations. * - * Type quotes `'[body]` from the parser are desugared into quote patterns (using a `Type.of[T]]`) - * when type checking. TASTy files will not contain type quotes. Type quotes are used again - * in the `staging` phase to represent the reification of `Type.of[T]]`. + * Type quotes `'[body]` from the parser are typed into `QuotePattern`s when type checking. + * TASTy files will not contain type quotes. Type quotes are used again in the `staging` + * phase to represent the reification of `Type.of[T]]`. * * Type tags `tags` are always empty before the `staging` phase. Tags for stage inconsistent * types are added in the `staging` phase to level 0 quotes. Tags for types that refer to @@ -737,13 +737,35 @@ object Trees { type ThisTree[+T <: Untyped] = Splice[T] } + /** A tree representing a quote pattern `'{ type binding1; ...; body }` or `'[ type binding1; ...; body ]`. + * `QuotePattern`s are created the type checker when typing an `untpd.Quote` in a pattern context. + * + * `QuotePattern`s are checked are encoded into `unapply`s in the `staging` phase. + * + * The `bindings` contain the list of quote pattern type variable definitions (`Bind`s) in the oreder in + * which they are defined in the source. + * + * @param bindings Type variable definitions (`Bind` tree) + * @param body Quoted pattern (without type variable definitions) + * @param quotes A reference to the given `Quotes` instance in scope + */ + case class QuotePattern[+T <: Untyped] private[ast] (bindings: List[Tree[T]], body: Tree[T], quotes: Tree[T])(implicit @constructorOnly src: SourceFile) + extends PatternTree[T] { + type ThisTree[+T <: Untyped] = QuotePattern[T] + + /** Type of the quoted pattern */ + def bodyType(using Context): Type = + val quoteType = typeOpt // `Expr[T]` or `Type[T]` + quoteType.argInfos.head // T + } + /** 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 are removed after typing the pattern and are not present in TASTy. + * `SplicePattern` can only be contained within a `QuotePattern`. * * @param body The tree that was spliced * @param args The arguments of the splice (the HOAS arguments) @@ -1163,6 +1185,7 @@ object Trees { type Inlined = Trees.Inlined[T] type Quote = Trees.Quote[T] type Splice = Trees.Splice[T] + type QuotePattern = Trees.QuotePattern[T] type SplicePattern = Trees.SplicePattern[T] type TypeTree = Trees.TypeTree[T] type InferredTypeTree = Trees.InferredTypeTree[T] @@ -1341,6 +1364,10 @@ object Trees { case tree: Splice if (expr eq tree.expr) => tree case _ => finalize(tree, untpd.Splice(expr)(sourceFile(tree))) } + def QuotePattern(tree: Tree)(bindings: List[Tree], body: Tree, quotes: Tree)(using Context): QuotePattern = tree match { + case tree: QuotePattern if (bindings eq tree.bindings) && (body eq tree.body) && (quotes eq tree.quotes) => tree + case _ => finalize(tree, untpd.QuotePattern(bindings, body, quotes)(sourceFile(tree))) + } def SplicePattern(tree: Tree)(body: Tree, args: List[Tree])(using Context): SplicePattern = tree match { case tree: SplicePattern if (body eq tree.body) && (args eq tree.args) => tree case _ => finalize(tree, untpd.SplicePattern(body, args)(sourceFile(tree))) @@ -1586,6 +1613,8 @@ object Trees { cpy.Quote(tree)(transform(body)(using quoteContext), transform(tags)) case tree @ Splice(expr) => cpy.Splice(tree)(transform(expr)(using spliceContext)) + case tree @ QuotePattern(bindings, body, quotes) => + cpy.QuotePattern(tree)(transform(bindings), transform(body)(using quoteContext), transform(quotes)) case tree @ SplicePattern(body, args) => cpy.SplicePattern(tree)(transform(body)(using spliceContext), transform(args)) case tree @ Hole(isTerm, idx, args, content) => @@ -1733,6 +1762,8 @@ object Trees { this(this(x, body)(using quoteContext), tags) case Splice(expr) => this(x, expr)(using spliceContext) + case QuotePattern(bindings, body, quotes) => + this(this(this(x, bindings), body)(using quoteContext), quotes) case SplicePattern(body, args) => this(this(x, body)(using spliceContext), args) case Hole(_, _, args, content) => diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 76e16cc00a90..0d3085f13f1e 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -173,6 +173,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def Quote(body: Tree, tags: List[Tree])(using Context): Quote = untpd.Quote(body, tags).withBodyType(body.tpe) + def QuotePattern(bindings: List[Tree], body: Tree, quotes: Tree, proto: Type)(using Context): QuotePattern = + ta.assignType(untpd.QuotePattern(bindings, body, quotes), proto) + def Splice(expr: Tree, tpe: Type)(using Context): Splice = untpd.Splice(expr).withType(tpe) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 53c3f1fa3d5d..4195267ef1cc 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -406,6 +406,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def Inlined(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree)(implicit src: SourceFile): Inlined = new Inlined(call, bindings, expansion) def Quote(body: Tree, tags: List[Tree])(implicit src: SourceFile): Quote = new Quote(body, tags) def Splice(expr: Tree)(implicit src: SourceFile): Splice = new Splice(expr) + def QuotePattern(bindings: List[Tree], body: Tree, quotes: Tree)(implicit src: SourceFile): QuotePattern = new QuotePattern(bindings, body, quotes) def SplicePattern(body: Tree, args: List[Tree])(implicit src: SourceFile): SplicePattern = new SplicePattern(body, args) def TypeTree()(implicit src: SourceFile): TypeTree = new TypeTree() def InferredTypeTree()(implicit src: SourceFile): TypeTree = new InferredTypeTree() diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index cef6a8c25714..3e839730c42c 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -853,9 +853,9 @@ class Definitions { @tu lazy val QuoteMatchingClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteMatching") @tu lazy val QuoteMatching_ExprMatch: Symbol = QuoteMatchingClass.requiredMethod("ExprMatch") - @tu lazy val QuoteMatching_ExprMatchModule: Symbol = QuoteMatchingClass.requiredClass("ExprMatchModule") + @tu lazy val QuoteMatching_ExprMatch_unapply: Symbol = QuoteMatchingClass.requiredClass("ExprMatchModule").requiredMethod(nme.unapply) @tu lazy val QuoteMatching_TypeMatch: Symbol = QuoteMatchingClass.requiredMethod("TypeMatch") - @tu lazy val QuoteMatching_TypeMatchModule: Symbol = QuoteMatchingClass.requiredClass("TypeMatchModule") + @tu lazy val QuoteMatching_TypeMatch_unapply: Symbol = QuoteMatchingClass.requiredClass("TypeMatchModule").requiredMethod(nme.unapply) @tu lazy val QuoteMatchingModule: Symbol = requiredModule("scala.quoted.runtime.QuoteMatching") @tu lazy val QuoteMatching_KNil: Symbol = QuoteMatchingModule.requiredType("KNil") @tu lazy val QuoteMatching_KCons: Symbol = QuoteMatchingModule.requiredType("KCons") @@ -942,6 +942,7 @@ class Definitions { def TupleXXLModule(using Context): Symbol = TupleXXLClass.companionModule def TupleXXL_fromIterator(using Context): Symbol = TupleXXLModule.requiredMethod("fromIterator") + def TupleXXL_unapplySeq(using Context): Symbol = TupleXXLModule.requiredMethod(nme.unapplySeq) @tu lazy val RuntimeTupleMirrorTypeRef: TypeRef = requiredClassRef("scala.runtime.TupleMirror") diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 645c6f81e539..46d57d62222b 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -19,6 +19,7 @@ import config.Config import collection.mutable import reporting.{Profile, NoProfile} import dotty.tools.tasty.TastyFormat.ASTsSection +import quoted.QuotePatterns object TreePickler: class StackSizeExceeded(val mdef: tpd.MemberDef) extends Exception @@ -685,6 +686,9 @@ class TreePickler(pickler: TastyPickler) { .appliedTo(expr) .withSpan(tree.span) ) + case tree: QuotePattern => + // TODO: Add QUOTEPATTERN tag to TASTy + pickleTree(QuotePatterns.encode(tree)) case Hole(_, idx, args, _) => writeByte(HOLE) withLength { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 98bd7152ff37..ca474f1be335 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -33,6 +33,7 @@ import Trees._ import Decorators._ import transform.SymUtils._ import cc.{adaptFunctionTypeUnderPureFuns, adaptByNameArgUnderPureFuns} +import dotty.tools.dotc.quoted.QuotePatterns import dotty.tools.tasty.{TastyBuffer, TastyReader} import TastyBuffer._ @@ -1419,7 +1420,11 @@ class TreeUnpickler(reader: TastyReader, } val patType = readType() val argPats = until(end)(readTree()) - UnApply(fn, implicitArgs, argPats, patType) + val unapply = UnApply(fn, implicitArgs, argPats, patType) + if fn.symbol == defn.QuoteMatching_ExprMatch_unapply + || fn.symbol == defn.QuoteMatching_TypeMatch_unapply + then QuotePatterns.decode(unapply) + else unapply case REFINEDtpt => val refineCls = symAtAddr.getOrElse(start, newRefinedClassSymbol(coordAt(start))).asClass diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 3ceb274acd43..fb23b9524ba0 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -741,6 +741,14 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case Splice(expr) => val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists) keywordStr("$") ~ spliceTypeText ~ keywordStr("{") ~ toTextGlobal(expr) ~ keywordStr("}") + case tree @ QuotePattern(bindings, body, quotes) => + val quotesText = (keywordStr("<") ~ toText(quotes) ~ keywordStr(">")).provided(printDebug) + val bindingsText = bindings.map(binding => { + keywordStr("type ") ~ toText(binding.symbol.name) ~ toText(binding.symbol.info) ~ "; " + }).reduceLeft(_ ~~ _).provided(bindings.nonEmpty) + val open = if (body.isTerm) keywordStr("{") else keywordStr("[") + val close = if (body.isTerm) keywordStr("}") else keywordStr("]") + keywordStr("'") ~ quotesText ~ open ~ bindingsText ~ toTextGlobal(body) ~ close case SplicePattern(pattern, args) => val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists) keywordStr("$") ~ spliceTypeText ~ { diff --git a/compiler/src/dotty/tools/dotc/quoted/QuotePatterns.scala b/compiler/src/dotty/tools/dotc/quoted/QuotePatterns.scala new file mode 100644 index 000000000000..c0ee8a7a5261 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/quoted/QuotePatterns.scala @@ -0,0 +1,238 @@ +package dotty.tools.dotc +package quoted + +import dotty.tools.dotc.ast.TreeTypeMap +import dotty.tools.dotc.ast.Trees.* +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.untpd +import dotty.tools.dotc.core.* +import dotty.tools.dotc.core.Annotations.* +import dotty.tools.dotc.core.Constants.* +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Decorators.* +import dotty.tools.dotc.core.Flags.* +import dotty.tools.dotc.core.NameKinds.PatMatGivenVarName +import dotty.tools.dotc.core.Names.* +import dotty.tools.dotc.core.StdNames.* +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.reporting.IllegalVariableInPatternAlternative +import dotty.tools.dotc.transform.SymUtils._ + +import scala.collection.mutable + +object QuotePatterns: + import tpd._ + + /** Encode the quote pattern into an `unapply` that the pattern matcher can handle. + * + * A quote pattern + * ``` + * '{ + * // type variables (QuotePattern.bindings Bind list) + * type t1 >: l1 <: b1 + * ... + * type tn >: ln <: bn + * // pattern (QuotePattern.body) + * ... $x1: T1 ... ${ F(x2) }: T2 ... $f(a1: A1, ..., an: An): T3 ... + * } // (using quotes) (QuotePattern.quotes) + * ``` + * + * is transformed into the pattern + * + * ``` + * quotes + * .asInstanceOf[QuoteMatching] // scala.quoted.runtime.QuoteMatching + * .ExprMatch // or TypeMatch + * .unapply[ + * KCons[t1 >: l1 <: b1, ...KCons[tn >: ln <: bn, KNil]...], // scala.quoted.runtime.{KCons, KNil} + * (T1, T2, (A1, ..., An) => T3, ...) + * ]( + * '{ + * type t1' >: l1' <: b1' + * ... + * type tn' >: ln' <: bn' + * // scala.quoted.runtime.Patterns.{patternHole, higherOrderHole} + * ... $patternHole[T1] ... $patternHole[T2] ... $higherOrderHole[T3](a1, ..., an) ... + * }, + * quotes + * ) + * + * Here ti' is a `TypeDef` that represents `ti` in the (pickled) pattern body. The type bounds + * `>: l1' <: b1` of `ti'` are the same as the type bounds `>: l1 <: b1` replacing all references + * to `tj` with `tj'`. + * ``` + */ + def encode(quotePattern: QuotePattern)(using Context): UnApply = + val quoteClass = if (quotePattern.body.isTerm) defn.QuotedExprClass else defn.QuotedTypeClass + + val matchModule = if quotePattern.body.isTerm then defn.QuoteMatching_ExprMatch else defn.QuoteMatching_TypeMatch + val unapplySym = if quotePattern.body.isTerm then defn.QuoteMatching_ExprMatch_unapply else defn.QuoteMatching_TypeMatch_unapply + val unapplyFun = quotePattern.quotes.asInstance(defn.QuoteMatchingClass.typeRef).select(matchModule).select(unapplySym) + + val typeBindingsTuple = tpd.hkNestedPairsTypeTree(quotePattern.bindings) + + val (splicePatterns, shape0) = splitQuotePattern(quotePattern.body) + + val shape1 = + if quotePattern.bindings.isEmpty then shape0 + else + val oldBindings = quotePattern.bindings.map(_.symbol) + val newBindings = quotePattern.bindings.map { binding => + val sym = binding.symbol + val typeSym = newSymbol(ctx.owner, sym.name, EmptyFlags, sym.info, NoSymbol, binding.span) + typeSym.addAnnotation(defn.QuotedRuntimePatterns_patternTypeAnnot) + for fromAbove <- sym.getAnnotation(defn.QuotedRuntimePatterns_fromAboveAnnot) do + typeSym.addAnnotation(fromAbove) + typeSym.asType + } + var newBindingsRefs = newBindings.map(_.typeRef) + for newBinding <- newBindings do + newBinding.info = newBinding.info.subst(oldBindings, newBindingsRefs) + + val patternTypes = newBindings.map(sym => TypeDef(sym).withSpan(sym.span)) + Block(patternTypes, shape0.subst(oldBindings, newBindings)) + + val quotedShape = + if (quotePattern.body.isTerm) tpd.Quote(shape1, Nil).select(nme.apply).appliedTo(quotePattern.quotes) + else ref(defn.QuotedTypeModule_of.termRef).appliedToTypeTree(shape1).appliedTo(quotePattern.quotes) + + val givenTypes = quotePattern.bindings.map { binding => + val name = binding.symbol.name.toTypeName + val nameOfSyntheticGiven = PatMatGivenVarName.fresh(name.toTermName) + val tpe = defn.QuotedTypeClass.typeRef.appliedTo(binding.symbol.typeRef) + val givenTypeSym = newPatternBoundSymbol(nameOfSyntheticGiven, tpe, binding.span, flags = Given) + Bind(givenTypeSym, untpd.Ident(nme.WILDCARD).withType(tpe)).withSpan(binding.span) + } + + val patterns = givenTypes ::: splicePatterns + val patternTypes = patterns.map(_.tpe.widenTermRefExpr) + + val splicePat = + if patterns.isEmpty then ref(defn.EmptyTupleModule.termRef) + else if patterns.size <= Definitions.MaxTupleArity then + val tupleNUnapply = + ref(defn.TupleType(patterns.size).nn.typeSymbol.companionModule) + .select(nme.unapply) + .appliedToTypes(patternTypes) + UnApply(tupleNUnapply, Nil, patterns, defn.tupleType(patternTypes)) + else + val tupleXXLUnapplySeq = ref(defn.TupleXXL_unapplySeq) + val unapply = UnApply(tupleXXLUnapplySeq, Nil, patterns, defn.tupleType(patternTypes)) + Typed(unapply, TypeTree(defn.TupleXXLClass.typeRef)) + + val patType = + val quotedTypes = + quotePattern.bindings.map(givenType => defn.QuotedTypeClass.typeRef.appliedTo(givenType.symbol.typeRef)) + val quotedExprs = + splicePatterns.map(_.tpe.widenTermRefExpr) + defn.tupleType(quotedTypes :::quotedExprs) + + UnApply( + fun = unapplyFun.appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil), + implicits = quotedShape :: Nil, + patterns = splicePat :: Nil, + quotePattern.tpe) + + /** Split a typed quoted pattern into the contents of its splices and replace them with place holders. + * + * A quote pattern + * ``` + * case '${ + * val a: T = ??? + * List[T]( + * $x, + * ${Expr(y)}, + * $f(a)) + * ) + * } => ... + * ``` + * will return + * ``` + * ( + * List( + * : Tree, + * : Tree, + * T]>: Tree) + * <'{ + * val a: T = ??? + * List[T]( + * scala.quoted.runtime.Patterns.patternHole[T], + * scala.quoted.runtime.Patterns.patternHole[T], + * scala.quoted.runtime.Patterns.higherOrderHole[T](a) + * ) + * }>: Tree, + * ) + * ``` + */ + private def splitQuotePattern(body: Tree)(using Context): (List[Tree], Tree) = { + val patBuf = new mutable.ListBuffer[Tree] + val shape = new tpd.TreeMap { + override def transform(tree: Tree)(using Context) = tree match { + case Typed(splice @ SplicePattern(pat, Nil), tpt) if !tpt.tpe.derivesFrom(defn.RepeatedParamClass) => + transform(tpt) // Collect type bindings + transform(splice) + case SplicePattern(pat, args) => + val patType = pat.tpe.widen + val patType1 = patType.translateFromRepeated(toArray = false) + val pat1 = if (patType eq patType1) pat else pat.withType(patType1) + patBuf += pat1 + if args.isEmpty then ref(defn.QuotedRuntimePatterns_patternHole.termRef).appliedToType(tree.tpe).withSpan(tree.span) + else ref(defn.QuotedRuntimePatterns_higherOrderHole.termRef).appliedToType(tree.tpe).appliedTo(SeqLiteral(args, TypeTree(defn.AnyType))).withSpan(tree.span) + case _ => + super.transform(tree) + } + }.transform(body) + (patBuf.toList, shape) + } + + + /** Decodes an encoded pattern into a QuotePattern. + * + * See the documentation of `encode`, this does the opposite transformation. + */ + def decode(tree: UnApply)(using Context): QuotePattern = + val (fun, implicits, patternTuple) = (tree: @unchecked) match + case UnApply(fun, implicits, patternTuple :: Nil) => (fun, implicits, patternTuple) + val patterns = patternTuple match + case _: Ident => Nil // EmptyTuple + case UnApply(_, _, patterns) => patterns // TupleN + case Typed(UnApply(_, _, patterns), _) => patterns // TupleXXL + val shape = (implicits: @unchecked) match + case Apply(Select(Quote(shape, _), _), _) :: Nil => shape + case List(Apply(TypeApply(_, shape :: Nil), _)) => shape + fun match + // .asInstanceOf[QuoteMatching].{ExprMatch,TypeMatch}.unapply[, ] + case TypeApply(Select(Select(TypeApply(Select(quotes, _), _), _), _), typeBindings :: resTypes :: Nil) => + val bindings = unrollBindings(typeBindings) + val addPattenSplice = new TreeMap { + private val patternIterator = patterns.iterator.filter { + case pat: Bind => !pat.symbol.name.is(PatMatGivenVarName) + case _ => true + } + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match + case TypeApply(patternHole, _) if patternHole.symbol == defn.QuotedRuntimePatterns_patternHole => + cpy.SplicePattern(tree)(patternIterator.next(), Nil) + case Apply(patternHole, SeqLiteral(args, _) :: Nil) if patternHole.symbol == defn.QuotedRuntimePatterns_higherOrderHole => + cpy.SplicePattern(tree)(patternIterator.next(), args) + case _ => super.transform(tree) + } + val body = addPattenSplice.transform(shape) match + case block @ Block((tdef: TypeDef) :: rest, expr) if tdef.symbol.hasAnnotation(defn.QuotedRuntimePatterns_patternTypeAnnot) => + val (tdefs, stats) = rest.span { + case tdef: TypeDef => tdef.symbol.hasAnnotation(defn.QuotedRuntimePatterns_patternTypeAnnot) + case _ => false + } + val shapeBindingSyms = tdef.symbol :: tdefs.map(_.symbol) + for (binding, shapeBinding) <- bindings.zip(shapeBindingSyms) do + if shapeBinding.hasAnnotation(defn.QuotedRuntimePatterns_fromAboveAnnot) then + binding.symbol.addAnnotation(defn.QuotedRuntimePatterns_fromAboveAnnot) + val body1 = if stats.isEmpty then expr else cpy.Block(block)(stats, expr) + body1.subst(shapeBindingSyms, bindings.map(_.symbol)) + case body => body + cpy.QuotePattern(tree)(bindings, body, quotes) + + private def unrollBindings(tree: Tree)(using Context): List[Tree] = tree match + case AppliedTypeTree(tupleN, bindings) if defn.isTupleClass(tupleN.symbol) => bindings // TupleN, 1 <= N <= 22 + case AppliedTypeTree(_, head :: tail :: Nil) => head :: unrollBindings(tail) // KCons or *: + case _ => Nil // KNil or EmptyTuple diff --git a/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala b/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala index 8360d8e08211..25887cedb3e5 100644 --- a/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala +++ b/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala @@ -10,8 +10,9 @@ import dotty.tools.dotc.core.NameKinds._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types._ -import dotty.tools.dotc.staging.StagingLevel.* +import dotty.tools.dotc.quoted.QuotePatterns import dotty.tools.dotc.staging.QuoteTypeTags.* +import dotty.tools.dotc.staging.StagingLevel.* import dotty.tools.dotc.util.Property import dotty.tools.dotc.util.Spans._ import dotty.tools.dotc.util.SrcPos @@ -104,6 +105,19 @@ class CrossStageSafety extends TreeMapWithStages { case _: DefDef if tree.symbol.isInlineMethod => tree + case tree: CaseDef if level == 0 => + val pat1 = new TreeMap { + // Encode all quote patterns to materialize the given `Type[ti]` bindings + // for each type binding `ti` of the quote pattern. These will be summoned + // by HealType in the right hand side of the case definition. + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match + case tree: QuotePattern if level == 0 => + super.transform(QuotePatterns.encode(tree)) + case tree => super.transform(tree) + }.transform(tree.pat) + val tree1 = cpy.CaseDef(tree)(pat1, tree.guard, tree.body) + super.transform(tree1) + case _ if !inQuoteOrSpliceScope => checkAnnotations(tree) // Check quotes in annotations super.transform(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index ea5e7db2c088..90b951a22e0b 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -488,6 +488,15 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase case _: Quote => ctx.compilationUnit.needsStaging = true super.transform(tree) + case _: QuotePattern => + if !ctx.reporter.errorsReported then + Checking.checkAppliedTypesIn(TypeTree(tree.tpe).withSpan(tree.span)) + ctx.compilationUnit.needsStaging = true + super.transform(tree) + case tree: SplicePattern => + if !ctx.reporter.errorsReported then + Checking.checkAppliedTypesIn(TypeTree(tree.tpe).withSpan(tree.span)) + super.transform(tree) case tree => super.transform(tree) } diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 2869819ecca5..5951f0405f5e 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -699,6 +699,25 @@ object TreeChecker { assert(!tree.expr.isInstanceOf[untpd.Quote] || inInlineMethod, i"missed quote cancellation in $tree") super.typedSplice(tree, pt) + override def typedQuotePattern(tree: untpd.QuotePattern, pt: Type)(using Context): Tree = + assert(ctx.mode.is(Mode.Pattern)) + for binding <- tree.bindings do + assert(binding.isInstanceOf[untpd.Bind], i"expected Bind in QuotePattern bindings but was: $binding") + super.typedQuotePattern(tree, pt) + + override def typedSplicePattern(tree: untpd.SplicePattern, pt: Type)(using Context): Tree = + assert(ctx.mode.is(Mode.QuotedPattern)) + def isAppliedIdent(rhs: untpd.Tree): Boolean = rhs match + case _: Ident => true + case rhs: GenericApply => isAppliedIdent(rhs.fun) + case _ => false + def isEtaExpandedIdent(arg: untpd.Tree): Boolean = arg match + case closureDef(ddef) => isAppliedIdent(ddef.rhs) || isEtaExpandedIdent(ddef.rhs) + case _ => false + for arg <- tree.args do + assert(arg.isInstanceOf[untpd.Ident] || isEtaExpandedIdent(arg), i"HOAS argument expected Ident or eta-expanded Ident but was: $arg") + super.typedSplicePattern(tree, pt) + override def typedHole(tree: untpd.Hole, pt: Type)(using Context): Tree = { val tree1 @ Hole(isTerm, idx, args, content) = super.typedHole(tree, pt): @unchecked diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 7238756454b3..cd8886ed0ac2 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -305,23 +305,13 @@ object SpaceEngine { } /** Is this an `'{..}` or `'[..]` irrefutable quoted patterns? - * @param unapp The unapply function tree - * @param implicits The implicits of the unapply - * @param pt The scrutinee type + * @param body The body of the quoted pattern + * @param bodyPt The scrutinee body type */ - def isIrrefutableQuotedPattern(unapp: tpd.Tree, implicits: List[tpd.Tree], pt: Type)(using Context): Boolean = { - implicits.headOption match - // pattern '{ $x: T } - case Some(tpd.Apply(tpd.Select(tpd.Quote(tpd.TypeApply(fn, List(tpt)), _), nme.apply), _)) - if unapp.symbol.owner.eq(defn.QuoteMatching_ExprMatchModule) - && fn.symbol.eq(defn.QuotedRuntimePatterns_patternHole) => - pt <:< defn.QuotedExprClass.typeRef.appliedTo(tpt.tpe) - - // pattern '[T] - case Some(tpd.Apply(tpd.TypeApply(fn, List(tpt)), _)) - if unapp.symbol.owner.eq(defn.QuoteMatching_TypeMatchModule) => - pt =:= defn.QuotedTypeClass.typeRef.appliedTo(tpt.tpe) - + def isIrrefutableQuotePattern(pat: tpd.QuotePattern, pt: Type)(using Context): Boolean = { + if pat.body.isType then pat.bindings.isEmpty && pt =:= pat.tpe + else pat.body match + case _: SplicePattern => pat.bindings.isEmpty && pt <:< pat.tpe case _ => false } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 8c532a87e9db..e3b2d119950d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -33,7 +33,7 @@ import NameOps._ import SymDenotations.{NoCompleter, NoDenotation} import Applications.unapplyArgs import Inferencing.isFullyDefined -import transform.patmat.SpaceEngine.{isIrrefutable, isIrrefutableQuotedPattern} +import transform.patmat.SpaceEngine.{isIrrefutable, isIrrefutableQuotePattern} import config.Feature import config.Feature.sourceVersion import config.SourceVersion._ @@ -854,19 +854,15 @@ trait Checking { val problem = if pat.tpe <:< reportedPt then "is more specialized than" else "does not match" em"pattern's type ${pat.tpe} $problem the right hand side expression's type $reportedPt" case RefutableExtractor => - val extractor = - val UnApply(fn, _, _) = pat: @unchecked - tpd.funPart(fn) match - case Select(id, _) => id - case _ => EmptyTree - if extractor.isEmpty then - em"pattern binding uses refutable extractor" - else if extractor.symbol eq defn.QuoteMatching_ExprMatch then - em"pattern binding uses refutable extractor `'{...}`" - else if extractor.symbol eq defn.QuoteMatching_TypeMatch then - em"pattern binding uses refutable extractor `'[...]`" - else - em"pattern binding uses refutable extractor `$extractor`" + val extractor = pat match + case UnApply(fn, _, _) => + tpd.funPart(fn) match + case Select(id, _) if !id.isEmpty => id.show + case _ => "" + case QuotePattern(_, body, _) => + if body.isTerm then "'{...}" else "'[...]" + if extractor.isEmpty then em"pattern binding uses refutable extractor" + else em"pattern binding uses refutable extractor `$extractor`" val fix = if isPatDef then "adding `: @unchecked` after the expression" @@ -905,7 +901,7 @@ trait Checking { recur(pat1, pt) case UnApply(fn, implicits, pats) => check(pat, pt) && - (isIrrefutable(fn, pats.length) || isIrrefutableQuotedPattern(fn, implicits, pt) || fail(pat, pt, Reason.RefutableExtractor)) && { + (isIrrefutable(fn, pats.length) || fail(pat, pt, Reason.RefutableExtractor)) && { val argPts = unapplyArgs(fn.tpe.widen.finalResultType, fn, pats, pat.srcPos) pats.corresponds(argPts)(recur) } @@ -915,6 +911,8 @@ trait Checking { check(pat, pt) && recur(arg, pt) case Ident(nme.WILDCARD) => true + case pat: QuotePattern => + isIrrefutableQuotePattern(pat, pt) || fail(pat, pt, Reason.RefutableExtractor) case _ => check(pat, pt) } diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index d8bf8575aa79..c89c2e90b485 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -15,6 +15,7 @@ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.inlines.PrepareInlineable +import dotty.tools.dotc.quoted.QuotePatterns import dotty.tools.dotc.staging.StagingLevel.* import dotty.tools.dotc.transform.SymUtils._ import dotty.tools.dotc.typer.ErrorReporting.errorTree @@ -102,6 +103,9 @@ trait QuotesAndSplices { case tree => tree } + def typedQuotePattern(tree: untpd.QuotePattern, pt: Type)(using Context): Tree = + throw new UnsupportedOperationException("cannot type check a Hole node") + def typedSplicePattern(tree: untpd.SplicePattern, pt: Type)(using Context): Tree = { record("typedSplicePattern") if isFullyDefined(pt, ForceDegree.flipBottom) then @@ -301,6 +305,7 @@ trait QuotesAndSplices { super.transform(tdef) } } + val shape0 = splitter.transform(quoted) val patterns = (splitter.typePatBuf.iterator ++ splitter.freshTypePatBuf.iterator ++ splitter.patBuf.iterator).toList val freshTypeBindings = splitter.freshTypeBindingsBuff.result() @@ -511,13 +516,17 @@ trait QuotesAndSplices { else ref(defn.QuotedTypeModule_of.termRef).appliedToTypeTree(shape).appliedTo(quotes) val matchModule = if quoted.isTerm then defn.QuoteMatching_ExprMatch else defn.QuoteMatching_TypeMatch - val unapplyFun = quotes.asInstance(defn.QuoteMatchingClass.typeRef).select(matchModule).select(nme.unapply) + val unapplySym = if quoted.isTerm then defn.QuoteMatching_ExprMatch_unapply else defn.QuoteMatching_TypeMatch_unapply + val unapplyFun = quotes.asInstance(defn.QuoteMatchingClass.typeRef).select(matchModule).select(unapplySym) - UnApply( + val encodedPattern = UnApply( fun = unapplyFun.appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil), implicits = quotedPattern :: Nil, patterns = splicePat :: Nil, proto = quoteClass.typeRef.appliedTo(replaceBindings(quoted1.tpe))) + + if ctx.reporter.hasErrors then encodedPattern + else QuotePatterns.decode(encodedPattern) // TODO type directly into the encoded version } } diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 9d4e25cd6577..a2f6bb1ff29e 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -111,6 +111,25 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking val expr1 = typed(tree.expr, quoteType)(using spliceContext) untpd.cpy.Splice(tree)(expr1).withType(tree.typeOpt) + override def typedQuotePattern(tree: untpd.QuotePattern, pt: Type)(using Context): Tree = + assertTyped(tree) + val bindings1 = tree.bindings.map(typed(_)) + val bodyCtx = quoteContext.retractMode(Mode.Pattern).addMode(Mode.QuotedPattern) + val body1 = typed(tree.body, tree.bodyType)(using bodyCtx) + val quotes1 = typed(tree.quotes, defn.QuotesClass.typeRef) + untpd.cpy.QuotePattern(tree)(bindings1, body1, quotes1).withType(tree.typeOpt) + + override def typedSplicePattern(tree: untpd.SplicePattern, pt: Type)(using Context): Tree = + assertTyped(tree) + val args1 = tree.args.mapconserve(typedExpr(_)) + val patternTpe = + if args1.isEmpty then tree.typeOpt + else defn.FunctionType(args1.size).appliedTo(args1.map(_.tpe) :+ tree.typeOpt) + val bodyCtx = spliceContext.addMode(Mode.Pattern).retractMode(Mode.QuotedPattern) + val body1 = typed(tree.body, defn.QuotedExprClass.typeRef.appliedTo(patternTpe))(using bodyCtx) + val args = tree.args.mapconserve(typedExpr(_)) + untpd.cpy.SplicePattern(tree)(body1, args1).withType(tree.typeOpt) + override def typedHole(tree: untpd.Hole, pt: Type)(using Context): Tree = promote(tree) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 0e3270281c9b..ab0b8cf2b872 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -512,6 +512,9 @@ trait TypeAssigner { def assignType(tree: untpd.UnApply, proto: Type)(using Context): UnApply = tree.withType(proto) + def assignType(tree: untpd.QuotePattern, proto: Type)(using Context): QuotePattern = + tree.withType(proto) + def assignType(tree: untpd.ValDef, sym: Symbol)(using Context): ValDef = tree.withType(if (sym.exists) assertExists(sym.termRef) else NoType) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1c00c276376e..9ea613e4aa0a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3152,6 +3152,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case untpd.EmptyTree => tpd.EmptyTree case tree: untpd.Quote => typedQuote(tree, pt) case tree: untpd.Splice => typedSplice(tree, pt) + case tree: untpd.QuotePattern => typedQuotePattern(tree, pt) case tree: untpd.SplicePattern => typedSplicePattern(tree, pt) case tree: untpd.MacroTree => report.error("Unexpected macro", tree.srcPos); tpd.nullLiteral // ill-formed code may reach here case tree: untpd.Hole => typedHole(tree, pt) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index b41f470ec10f..91ab3661dd81 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -16,6 +16,7 @@ import dotty.tools.dotc.core.Types import dotty.tools.dotc.NoCompilationUnit import dotty.tools.dotc.quoted.MacroExpansion import dotty.tools.dotc.quoted.PickledQuotes +import dotty.tools.dotc.quoted.QuotePatterns import dotty.tools.dotc.quoted.reflect._ import scala.quoted.runtime.{QuoteUnpickler, QuoteMatching} @@ -1515,11 +1516,12 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end extension end BindMethods - type Unapply = tpd.UnApply + type Unapply = tpd.UnApply | tpd.QuotePattern // TODO expose QuotePattern AST in Quotes object UnapplyTypeTest extends TypeTest[Tree, Unapply]: def unapply(x: Tree): Option[Unapply & x.type] = x match case x: (tpd.UnApply & x.type) => Some(x) + case x: (tpd.QuotePattern & x.type) => Some(x) // TODO expose QuotePattern AST in Quotes case _ => None end UnapplyTypeTest @@ -1534,9 +1536,15 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler given UnapplyMethods: UnapplyMethods with extension (self: Unapply) - def fun: Term = self.fun - def implicits: List[Term] = self.implicits - def patterns: List[Tree] = effectivePatterns(self.patterns) + def fun: Term = self match + case self: tpd.UnApply => self.fun + case self: tpd.QuotePattern => QuotePatterns.encode(self).fun // TODO expose QuotePattern AST in Quotes + def implicits: List[Term] = self match + case self: tpd.UnApply => self.implicits + case self: tpd.QuotePattern => QuotePatterns.encode(self).implicits // TODO expose QuotePattern AST in Quotes + def patterns: List[Tree] = self match + case self: tpd.UnApply => effectivePatterns(self.patterns) + case self: tpd.QuotePattern => effectivePatterns(QuotePatterns.encode(self).patterns) // TODO expose QuotePattern AST in Quotes end extension private def effectivePatterns(patterns: List[Tree]): List[Tree] = patterns match diff --git a/tests/neg-custom-args/fatal-warnings/i16649-refutable.scala b/tests/neg-custom-args/fatal-warnings/i16649-refutable.scala index 2a42f652e093..3254dcc410d4 100644 --- a/tests/neg-custom-args/fatal-warnings/i16649-refutable.scala +++ b/tests/neg-custom-args/fatal-warnings/i16649-refutable.scala @@ -2,3 +2,6 @@ import quoted.* def foo(using Quotes)(x: Expr[Int]) = val '{ ($y: Int) + ($z: Int) } = x // error + val '{ $a: Int } = x + val '{ $b: Any } = x + val '{ $c } = x diff --git a/tests/neg-macros/quote-type-variable-no-inference.check b/tests/neg-macros/quote-type-variable-no-inference.check index 7e425e932117..0f2f816fcac3 100644 --- a/tests/neg-macros/quote-type-variable-no-inference.check +++ b/tests/neg-macros/quote-type-variable-no-inference.check @@ -1,11 +1,17 @@ -- Warning: tests/neg-macros/quote-type-variable-no-inference.scala:5:17 ----------------------------------------------- -5 | case '[ F[t, t] ] => // warn // error +5 | case '[ F[t, t] ] => // warn // error // error | ^ | Ignored bound <: Double | | Consider defining bounds explicitly `'{ type t <: Int & Double; ... }` +-- [E057] Type Mismatch Error: tests/neg-macros/quote-type-variable-no-inference.scala:5:9 ----------------------------- +5 | case '[ F[t, t] ] => // warn // error // error + | ^ + |Type argument t does not conform to upper bound Double in subpart F[t, t] of inferred type scala.quoted.Type[F[t, t]] + | + | longer explanation available when compiling with `-explain` -- [E057] Type Mismatch Error: tests/neg-macros/quote-type-variable-no-inference.scala:5:15 ---------------------------- -5 | case '[ F[t, t] ] => // warn // error +5 | case '[ F[t, t] ] => // warn // error // error | ^ | Type argument t does not conform to upper bound Double | diff --git a/tests/neg-macros/quote-type-variable-no-inference.scala b/tests/neg-macros/quote-type-variable-no-inference.scala index de03f4445302..da348f6189db 100644 --- a/tests/neg-macros/quote-type-variable-no-inference.scala +++ b/tests/neg-macros/quote-type-variable-no-inference.scala @@ -2,7 +2,7 @@ import scala.quoted.* def test(x: Type[?])(using Quotes) = x match - case '[ F[t, t] ] => // warn // error + case '[ F[t, t] ] => // warn // error // error case '[ type u <: Int & Double; F[u, u] ] => type F[x <: Int, y <: Double] diff --git a/tests/run-macros/quote-match-more-that-22-splices/Macro_1.scala b/tests/run-macros/quote-match-more-that-22-splices/Macro_1.scala new file mode 100644 index 000000000000..02071e4744ac --- /dev/null +++ b/tests/run-macros/quote-match-more-that-22-splices/Macro_1.scala @@ -0,0 +1,8 @@ +import scala.quoted.* + +inline def f(inline x: Any) = ${ fExpr('x) } + +def fExpr(x: Expr[Any])(using Quotes): Expr[Any] = + x match + case '{ ($x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15, $x16, $x17, $x18, $x19, $x20, $x21, $x22, $x23, $x24) } => + '{ ($x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15, $x16, $x17, $x18, $x19, $x20, $x21, $x22, $x23, $x24) } diff --git a/tests/run-macros/quote-match-more-that-22-splices/Test_2.scala b/tests/run-macros/quote-match-more-that-22-splices/Test_2.scala new file mode 100644 index 000000000000..65f917ff3e35 --- /dev/null +++ b/tests/run-macros/quote-match-more-that-22-splices/Test_2.scala @@ -0,0 +1,4 @@ +@main def Test = + val t1 = f((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24)) + val t2 = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24) + assert(t1 == t2) From 351456e03e92bb0f7de9149350e5ae915ac2f06d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 26 Jun 2023 14:42:48 +0200 Subject: [PATCH 3/3] Only allow `bodyType` on typed quotes --- compiler/src/dotty/tools/dotc/ast/TreeInfo.scala | 15 +++++++++++++++ compiler/src/dotty/tools/dotc/ast/Trees.scala | 11 ----------- .../tools/dotc/printing/RefinedPrinter.scala | 2 +- compiler/src/dotty/tools/dotc/typer/ReTyper.scala | 4 ++-- .../scala/quoted/runtime/impl/QuotesImpl.scala | 1 + 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 183025da994e..9f6aa3a3db70 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -1055,6 +1055,21 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => if tree.symbol.isTypeSplice then Some(tree.qualifier) else None } + extension (tree: tpd.Quote) + /** Type of the quoted expression as seen from outside the quote */ + def bodyType(using Context): Type = + val quoteType = tree.tpe // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]` + val exprType = quoteType.argInfos.last // `Expr[T]` or `Type[T]` + exprType.argInfos.head // T + end extension + + extension (tree: tpd.QuotePattern) + /** Type of the quoted pattern */ + def bodyType(using Context): Type = + val quoteType = tree.tpe // `Expr[T]` or `Type[T]` + quoteType.argInfos.head // T + end extension + /** Extractor for not-null assertions. * A not-null assertion for reference `x` has the form `x.$asInstanceOf$[x.type & T]`. */ diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 27c90ef324c7..81ff42d7a302 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -704,12 +704,6 @@ object Trees { /** Is this a type quote `'[tpe]' */ def isTypeQuote = body.isType - /** Type of the quoted expression as seen from outside the quote */ - def bodyType(using Context): Type = - val quoteType = typeOpt // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]` - val exprType = quoteType.argInfos.last // `Expr[T]` or `Type[T]` - exprType.argInfos.head // T - /** Set the type of the body of the quote */ def withBodyType(tpe: Type)(using Context): Quote[Type] = val exprType = // `Expr[T]` or `Type[T]` @@ -752,11 +746,6 @@ object Trees { case class QuotePattern[+T <: Untyped] private[ast] (bindings: List[Tree[T]], body: Tree[T], quotes: Tree[T])(implicit @constructorOnly src: SourceFile) extends PatternTree[T] { type ThisTree[+T <: Untyped] = QuotePattern[T] - - /** Type of the quoted pattern */ - def bodyType(using Context): Type = - val quoteType = typeOpt // `Expr[T]` or `Type[T]` - quoteType.argInfos.head // T } /** A tree representing a pattern splice `${ pattern }`, `$ident` or `$ident(args*)` in a quote pattern. diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index fb23b9524ba0..3d79c51b6b38 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -734,7 +734,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { keywordStr("macro ") ~ toTextGlobal(call) case tree @ Quote(body, tags) => val tagsText = (keywordStr("<") ~ toTextGlobal(tags, ", ") ~ keywordStr(">")).provided(tree.tags.nonEmpty) - val exprTypeText = (keywordStr("[") ~ toTextGlobal(tree.bodyType) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists) + val exprTypeText = (keywordStr("[") ~ toTextGlobal(tpd.bodyType(tree.asInstanceOf[tpd.Quote])) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists) val open = if (body.isTerm) keywordStr("{") else keywordStr("[") val close = if (body.isTerm) keywordStr("}") else keywordStr("]") keywordStr("'") ~ tagsText ~ exprTypeText ~ open ~ toTextGlobal(body) ~ close diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index a2f6bb1ff29e..a1f599847114 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -97,7 +97,7 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking override def typedQuote(tree: untpd.Quote, pt: Type)(using Context): Tree = assertTyped(tree) - val body1 = typed(tree.body, tree.bodyType)(using quoteContext) + val body1 = typed(tree.body, promote(tree).bodyType)(using quoteContext) for tag <- tree.tags do assertTyped(tag) untpd.cpy.Quote(tree)(body1, tree.tags).withType(tree.typeOpt) @@ -115,7 +115,7 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking assertTyped(tree) val bindings1 = tree.bindings.map(typed(_)) val bodyCtx = quoteContext.retractMode(Mode.Pattern).addMode(Mode.QuotedPattern) - val body1 = typed(tree.body, tree.bodyType)(using bodyCtx) + val body1 = typed(tree.body, promote(tree).bodyType)(using bodyCtx) val quotes1 = typed(tree.quotes, defn.QuotesClass.typeRef) untpd.cpy.QuotePattern(tree)(bindings1, body1, quotes1).withType(tree.typeOpt) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 91ab3661dd81..a56fc3e088c0 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -38,6 +38,7 @@ object QuotesImpl { } class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler, QuoteMatching: + import tpd.* private val xCheckMacro: Boolean = ctx.settings.XcheckMacros.value