diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index d5c01ae7b7b6..9f6aa3a3db70 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) } @@ -1048,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 bd172e8db6d3..81ff42d7a302 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 @@ -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]` @@ -737,13 +731,30 @@ 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] + } + /** 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 +1174,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 +1353,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 +1602,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 +1751,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..3d79c51b6b38 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -734,13 +734,21 @@ 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 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 d3becfd6e9b7..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 @@ -214,13 +218,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,13 +298,14 @@ 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) 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() @@ -337,7 +336,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 @@ -473,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..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) @@ -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, promote(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..a56fc3e088c0 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} @@ -37,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 @@ -1515,11 +1517,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 +1537,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/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] 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)