diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 399215420e65..e884e7282d1b 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -363,6 +363,31 @@ object desugar { adaptToExpectedTpt(tree) } + /** Split out the quoted pattern type variable definition from the pattern. + * + * Type variable definitions are all the `type t` defined at the start of a quoted pattern. + * Where name `t` is a pattern type variable name (i.e. lower case letters). + * + * ``` + * type t1; ...; type tn; + * ``` + * is split into + * ``` + * (List(; ...; ), ) + * ``` + */ + def quotedPatternTypeVariables(tree: untpd.Tree)(using Context): (List[untpd.TypeDef], untpd.Tree) = + tree match + case untpd.Block(stats, expr) => + val (untpdTypeVariables, otherStats) = stats.span { + case tdef @ untpd.TypeDef(name, _) => name.isVarPattern + case _ => false + } + val pattern = if otherStats.isEmpty then expr else untpd.cpy.Block(tree)(otherStats, expr) + (untpdTypeVariables.asInstanceOf[List[untpd.TypeDef]], pattern) + case _ => + (Nil, tree) + /** Add all evidence parameters in `params` as implicit parameters to `meth`. * If the parameters of `meth` end in an implicit parameter list or using clause, * evidence parameters are added in front of that list. Otherwise they are added diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 3079b26df6cd..67dd15de5542 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1737,8 +1737,39 @@ object Parsers { }) else t - /** The block in a quote or splice */ - def stagedBlock() = inBraces(block(simplify = true)) + /** TypeBlock ::= {TypeBlockStat semi} Type + */ + def typeBlock(): Tree = + typeBlockStats() match + case Nil => typ() + case tdefs => Block(tdefs, typ()) + + def typeBlockStats(): List[Tree] = + val tdefs = new ListBuffer[Tree] + while in.token == TYPE do tdefs += typeBlockStat() + tdefs.toList + + /** TypeBlockStat ::= ‘type’ {nl} TypeDcl + */ + def typeBlockStat(): Tree = + val mods = defAnnotsMods(BitSet()) + val tdef = typeDefOrDcl(in.offset, in.skipToken(mods)) + if in.token == SEMI then in.nextToken() + if in.isNewLine then in.nextToken() + tdef + + /** Quoted ::= ‘'’ ‘{’ Block ‘}’ + * | ‘'’ ‘[’ TypeBlock ‘]’ + */ + def quote(inPattern: Boolean): Tree = + atSpan(in.skipToken()) { + withinStaged(StageKind.Quoted | (if (inPattern) StageKind.QuotedPattern else 0)) { + val body = + if (in.token == LBRACKET) inBrackets(typeBlock()) + else inBraces(block(simplify = true)) + Quote(body, Nil) + } + } /** ExprSplice ::= ‘$’ spliceId -- if inside quoted block * | ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern @@ -1754,7 +1785,8 @@ object Parsers { val expr = if (in.name.length == 1) { in.nextToken() - withinStaged(StageKind.Spliced)(if (inPattern) inBraces(pattern()) else stagedBlock()) + val inPattern = (staged & StageKind.QuotedPattern) != 0 + withinStaged(StageKind.Spliced)(inBraces(if inPattern then pattern() else block(simplify = true))) } else atSpan(in.offset + 1) { val id = Ident(in.name.drop(1)) @@ -2477,14 +2509,7 @@ object Parsers { canApply = false blockExpr() case QUOTE => - atSpan(in.skipToken()) { - withinStaged(StageKind.Quoted | (if (location.inPattern) StageKind.QuotedPattern else 0)) { - val body = - if (in.token == LBRACKET) inBrackets(typ()) - else stagedBlock() - Quote(body, Nil) - } - } + quote(location.inPattern) case NEW => canApply = false newExpr() @@ -3758,6 +3783,8 @@ object Parsers { else makeTypeDef(bounds) case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF => makeTypeDef(typeBounds()) + case _ if (staged & StageKind.QuotedPattern) != 0 => + makeTypeDef(typeBounds()) case _ => syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token)) return EmptyTree // return to avoid setting the span to EmptyTree diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index a499bdb983f6..d3becfd6e9b7 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -20,17 +20,18 @@ import dotty.tools.dotc.transform.SymUtils._ import dotty.tools.dotc.typer.ErrorReporting.errorTree import dotty.tools.dotc.typer.Implicits._ import dotty.tools.dotc.typer.Inferencing._ +import dotty.tools.dotc.util.Property import dotty.tools.dotc.util.Spans._ import dotty.tools.dotc.util.Stats.record import dotty.tools.dotc.reporting.IllegalVariableInPatternAlternative import scala.collection.mutable - /** Type quotes `'{ ... }` and splices `${ ... }` */ trait QuotesAndSplices { self: Typer => - import tpd._ + import tpd.* + import QuotesAndSplices.* /** Translate `'{ e }` into `scala.quoted.Expr.apply(e)` and `'[T]` into `scala.quoted.Type.apply[T]` * while tracking the quotation level in the context. @@ -155,19 +156,30 @@ trait QuotesAndSplices { * The resulting pattern is the split in `splitQuotePattern`. */ def typedQuotedTypeVar(tree: untpd.Ident, pt: Type)(using Context): Tree = - def spliceOwner(ctx: Context): Symbol = - if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner - val name = tree.name.toTypeName - val nameOfSyntheticGiven = PatMatGivenVarName.fresh(tree.name.toTermName) - val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven) val typeSymInfo = pt match case pt: TypeBounds => pt case _ => TypeBounds.empty - val typeSym = newSymbol(spliceOwner(ctx), name, EmptyFlags, typeSymInfo, NoSymbol, tree.span) - typeSym.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span))) - val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))( - using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx))) - pat.select(tpnme.Underlying) + getQuotedPatternTypeVariable(tree.name.asTypeName) match + case Some(typeSym) => + checkExperimentalFeature( + "support for multiple references to the same type (without backticks) in quoted type patterns (SIP-53)", + tree.srcPos, + "\n\nSIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html") + if !(typeSymInfo =:= TypeBounds.empty) && !(typeSym.info <:< typeSymInfo) then + report.warning(em"Ignored bound$typeSymInfo\n\nConsider defining bounds explicitly `'{ $typeSym${typeSym.info & typeSymInfo}; ... }`", tree.srcPos) + ref(typeSym) + case None => + def spliceOwner(ctx: Context): Symbol = + if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner + val name = tree.name.toTypeName + val nameOfSyntheticGiven = PatMatGivenVarName.fresh(tree.name.toTermName) + val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven) + val typeSym = newSymbol(spliceOwner(ctx), name, EmptyFlags, typeSymInfo, NoSymbol, tree.span) + typeSym.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span))) + addQuotedPatternTypeVariable(typeSym) + val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))( + using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx))) + pat.select(tpnme.Underlying) private def checkSpliceOutsideQuote(tree: untpd.Tree)(using Context): Unit = if (level == 0 && !ctx.owner.ownersIterator.exists(_.isInlineMethod)) @@ -385,11 +397,35 @@ trait QuotesAndSplices { case Some(argPt: ValueType) => argPt // excludes TypeBounds case _ => defn.AnyType } - val quoted0 = desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt))) - val quoteCtx = quoteContext.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern) - val quoted1 = - if quoted.isType then typedType(quoted0, WildcardType)(using quoteCtx) - else typedExpr(quoted0, WildcardType)(using quoteCtx) + val (untpdTypeVariables, quoted0) = desugar.quotedPatternTypeVariables(desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt)))) + + for tdef @ untpd.TypeDef(_, rhs) <- untpdTypeVariables do rhs match + case _: TypeBoundsTree => // ok + case LambdaTypeTree(_, body: TypeBoundsTree) => // ok + case _ => report.error("Quote type variable definition cannot be an alias", tdef.srcPos) + + if quoted.isType && untpdTypeVariables.nonEmpty then + checkExperimentalFeature( + "explicit type variable declarations quoted type patterns (SIP-53)", + untpdTypeVariables.head.srcPos, + "\n\nSIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html") + + val (typeTypeVariables, patternCtx) = + val quoteCtx = quotePatternContext() + if untpdTypeVariables.isEmpty then (Nil, quoteCtx) + else typedBlockStats(untpdTypeVariables)(using quoteCtx) + + val quoted1 = inContext(patternCtx) { + for typeVariable <- typeTypeVariables do + addQuotedPatternTypeVariable(typeVariable.symbol) + + val pattern = + if quoted.isType then typedType(quoted0, WildcardType)(using patternCtx) + else typedExpr(quoted0, WildcardType) + + if untpdTypeVariables.isEmpty then pattern + else tpd.Block(typeTypeVariables, pattern) + } val (typeBindings, shape, splices) = splitQuotePattern(quoted1) @@ -446,3 +482,24 @@ trait QuotesAndSplices { proto = quoteClass.typeRef.appliedTo(replaceBindings(quoted1.tpe))) } } + +object QuotesAndSplices { + import tpd._ + + /** Key for mapping from quoted pattern type variable names into their symbol */ + private val TypeVariableKey = new Property.Key[collection.mutable.Map[TypeName, Symbol]] + + /** Get the symbol for the quoted pattern type variable if it exists */ + def getQuotedPatternTypeVariable(name: TypeName)(using Context): Option[Symbol] = + ctx.property(TypeVariableKey).get.get(name) + + /** Get the symbol for the quoted pattern type variable if it exists */ + def addQuotedPatternTypeVariable(sym: Symbol)(using Context): Unit = + ctx.property(TypeVariableKey).get.update(sym.name.asTypeName, sym) + + /** Context used to type the contents of a quoted */ + def quotePatternContext()(using Context): Context = + quoteContext.fresh.setNewScope + .addMode(Mode.QuotedPattern).retractMode(Mode.Pattern) + .setProperty(TypeVariableKey, collection.mutable.Map.empty) +} diff --git a/docs/_docs/reference/metaprogramming/macros.md b/docs/_docs/reference/metaprogramming/macros.md index a91e69d985f0..87ab7d66d67d 100644 --- a/docs/_docs/reference/metaprogramming/macros.md +++ b/docs/_docs/reference/metaprogramming/macros.md @@ -504,18 +504,22 @@ def let(x: Expr[Any])(using Quotes): Expr[Any] = let('{1}) // will return a `Expr[Any]` that contains an `Expr[Int]]` ``` +It is also possible to refer to the same type variable multiple times in a pattern. + +```scala + case '{ $x: (t, t) } => +``` + While we can define the type variable in the middle of the pattern, their normal form is to define them as a `type` with a lower case name at the start of the pattern. -We use the Scala backquote `` `t` `` naming convention which interprets the string within the backquote as a literal name identifier. -This is typically used when we have names that contain special characters that are not allowed for normal Scala identifiers. -But we use it to explicitly state that this is a reference to that name and not the introduction of a new variable. + ```scala - case '{ type t; $x: `t` } => + case '{ type t; $x: t } => ``` -This is a bit more verbose but has some expressivity advantages such as allowing to define bounds on the variables and be able to refer to them several times in any scope of the pattern. + +This is a bit more verbose but has some expressivity advantages such as allowing to define bounds on the variables. ```scala - case '{ type t >: List[Int] <: Seq[Int]; $x: `t` } => - case '{ type t; $x: (`t`, `t`) } => + case '{ type t >: List[Int] <: Seq[Int]; $x: t } => ``` @@ -526,15 +530,24 @@ It works the same way as a quoted pattern but is restricted to contain a type. Type variables can be used in quoted type patterns to extract a type. ```scala -def empty[T: Type]: Expr[T] = +def empty[T: Type](using Quotes): Expr[T] = Type.of[T] match case '[String] => '{ "" } case '[List[t]] => '{ List.empty[t] } + case '[type t <: Option[Int]; List[t]] => '{ List.empty[t] } ... ``` - `Type.of[T]` is used to summon the given instance of `Type[T]` in scope, it is equivalent to `summon[Type[T]]`. +It is possible to match against a higher-kinded type using appropriate type bounds on type variables. +```scala +def empty[K <: AnyKind : Type](using Quotes): Type[?] = + Type.of[K] match + case '[type f[X]; f] => Type.of[f] + case '[type f[X <: Int, Y]; f] => Type.of[f] + case '[type k <: AnyKind; k ] => Type.of[k] +``` + #### Type testing and casting It is important to note that instance checks and casts on `Expr`, such as `isInstanceOf[Expr[T]]` and `asInstanceOf[Expr[T]]`, will only check if the instance is of the class `Expr` but will not be able to check the `T` argument. These cases will issue a warning at compile-time, but if they are ignored, they can result in unexpected behavior. diff --git a/docs/_docs/reference/syntax.md b/docs/_docs/reference/syntax.md index a705c5a3fd79..b4b3aab6b02f 100644 --- a/docs/_docs/reference/syntax.md +++ b/docs/_docs/reference/syntax.md @@ -274,7 +274,7 @@ ColonArgument ::= colon [LambdaStart] LambdaStart ::= FunParams (‘=>’ | ‘?=>’) | HkTypeParamClause ‘=>’ Quoted ::= ‘'’ ‘{’ Block ‘}’ - | ‘'’ ‘[’ Type ‘]’ + | ‘'’ ‘[’ TypeBlock ‘]’ ExprSplice ::= spliceId -- if inside quoted block | ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern | ‘$’ ‘{’ Pattern ‘}’ -- when inside quoted pattern @@ -293,6 +293,8 @@ BlockStat ::= Import | Extension | Expr1 | EndMarker +TypeBlock ::= {TypeBlockStat semi} Type +TypeBlockStat ::= ‘type’ {nl} TypeDcl ForExpr ::= ‘for’ ‘(’ Enumerators0 ‘)’ {nl} [‘do‘ | ‘yield’] Expr | ‘for’ ‘{’ Enumerators0 ‘}’ {nl} [‘do‘ | ‘yield’] Expr diff --git a/tests/neg-custom-args/no-experimental/sip-53-exprimental-a.scala b/tests/neg-custom-args/no-experimental/sip-53-exprimental-a.scala new file mode 100644 index 000000000000..33710d76d3ae --- /dev/null +++ b/tests/neg-custom-args/no-experimental/sip-53-exprimental-a.scala @@ -0,0 +1,9 @@ +import scala.quoted.* + +def foo(using Quotes): Unit = + (??? : Type[?]) match + case '[ (t, t, t) ] => // error // error + '{ ??? : Any } match + case '{ type u; $x: u } => // error + case '{ type u; ($ls: List[u]).map($f: u => Int) } => // error // error + diff --git a/tests/neg-custom-args/no-experimental/sip-53-exprimental-b.scala b/tests/neg-custom-args/no-experimental/sip-53-exprimental-b.scala new file mode 100644 index 000000000000..2ec597995bd1 --- /dev/null +++ b/tests/neg-custom-args/no-experimental/sip-53-exprimental-b.scala @@ -0,0 +1,8 @@ +import scala.quoted.* + +def empty[K <: AnyKind : Type](using Quotes): Type[?] = + Type.of[K] match + case '[type t; `t`] => Type.of[t] // error + case '[type f[X]; `f`] => Type.of[f] // error + case '[type f[X <: Int, Y]; `f`] => Type.of[f] // error + case '[type k <: AnyKind; `k` ] => Type.of[k] // error diff --git a/tests/neg-macros/quote-pattern-type-var-bounds.scala b/tests/neg-macros/quote-pattern-type-var-bounds.scala new file mode 100644 index 000000000000..b97b21552a1e --- /dev/null +++ b/tests/neg-macros/quote-pattern-type-var-bounds.scala @@ -0,0 +1,22 @@ +import scala.quoted.* +def types(t: Type[?])(using Quotes) = t match { + case '[ type t; Int ] => + case '[ type t <: Int; Int ] => + case '[ type t >: 1 <: Int; Int ] => + case '[ type t = Int; Int ] => // error + case '[ type t = scala.Int; Int ] => // error + case '[ type f[t] <: List[Any]; Int ] => + case '[ type f[t <: Int] <: List[Any]; Int ] => + case '[ type f[t] = List[Any]; Int ] => // error +} + +def expressions(x: Expr[Any])(using Quotes) = x match { + case '{ type t; () } => + case '{ type t <: Int; () } => + case '{ type t >: 1 <: Int; () } => + case '{ type t = Int; () } => // error + case '{ type t = scala.Int; () } => // error + case '{ type f[t] <: List[Any]; () } => + case '{ type f[t <: Int] <: List[Any]; () } => + case '{ type f[t] = List[Any]; () } => // error +} diff --git a/tests/neg-macros/quote-type-variable-no-inference.check b/tests/neg-macros/quote-type-variable-no-inference.check new file mode 100644 index 000000000000..7e425e932117 --- /dev/null +++ b/tests/neg-macros/quote-type-variable-no-inference.check @@ -0,0 +1,12 @@ +-- Warning: tests/neg-macros/quote-type-variable-no-inference.scala:5:17 ----------------------------------------------- +5 | case '[ F[t, t] ] => // warn // 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:15 ---------------------------- +5 | case '[ F[t, t] ] => // warn // error + | ^ + | Type argument t does not conform to upper bound Double + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-macros/quote-type-variable-no-inference.scala b/tests/neg-macros/quote-type-variable-no-inference.scala new file mode 100644 index 000000000000..de03f4445302 --- /dev/null +++ b/tests/neg-macros/quote-type-variable-no-inference.scala @@ -0,0 +1,8 @@ +import scala.quoted.* + +def test(x: Type[?])(using Quotes) = + x match + case '[ F[t, t] ] => // warn // error + case '[ type u <: Int & Double; F[u, u] ] => + +type F[x <: Int, y <: Double] diff --git a/tests/neg-macros/quotedPatterns-5.scala b/tests/neg-macros/quotedPatterns-5.scala index 4030604d19f5..9c47fd31dab4 100644 --- a/tests/neg-macros/quotedPatterns-5.scala +++ b/tests/neg-macros/quotedPatterns-5.scala @@ -2,7 +2,7 @@ import scala.quoted.* object Test { def test(x: quoted.Expr[Int])(using Quotes): Unit = x match { case '{ type t; 4 } => Type.of[t] - case '{ type t; poly[t]($x); 4 } => // error: duplicate pattern variable: t + case '{ type t; poly[t]($x); 4 } => case '{ type `t`; poly[`t`]($x); 4 } => Type.of[t] // error case _ => diff --git a/tests/pos-macros/i10864/Macro_1.scala b/tests/pos-macros/i10864/Macro_1.scala new file mode 100644 index 000000000000..7cf1e1850a76 --- /dev/null +++ b/tests/pos-macros/i10864/Macro_1.scala @@ -0,0 +1,15 @@ +import scala.quoted._ + +case class T(t: Type[_]) + +object T { + def impl[T <: AnyKind](using tt: Type[T])(using Quotes): Expr[Unit] = { + val t = T(tt) + t.t match + case '[type x <: AnyKind; x] => // ok + case _ => quotes.reflect.report.error("not ok :(") + '{} + } + + inline def run[T <: AnyKind] = ${ impl[T] } +} diff --git a/tests/pos-macros/i10864/Test_2.scala b/tests/pos-macros/i10864/Test_2.scala new file mode 100644 index 000000000000..e93fa1302221 --- /dev/null +++ b/tests/pos-macros/i10864/Test_2.scala @@ -0,0 +1,4 @@ +def test = + T.run[List] + T.run[Map] + T.run[Tuple22] diff --git a/tests/pos-macros/i10864a/Macro_1.scala b/tests/pos-macros/i10864a/Macro_1.scala new file mode 100644 index 000000000000..67cac5f85abd --- /dev/null +++ b/tests/pos-macros/i10864a/Macro_1.scala @@ -0,0 +1,21 @@ +import scala.quoted._ + +case class T(t: Type[_]) + +object T { + def impl[T <: AnyKind](using tt: Type[T])(using Quotes): Expr[Unit] = { + val t = T(tt) + t.t match + case '[type x; x] => + assert(Type.show[x] == "scala.Int", Type.show[x]) + case '[type f[X]; f] => + assert(Type.show[f] == "[A >: scala.Nothing <: scala.Any] => scala.collection.immutable.List[A]", Type.show[f]) + case '[type f[X <: Int]; f] => + assert(Type.show[f] == "[T >: scala.Nothing <: scala.Int] => C[T]", Type.show[f]) + case '[type f <: AnyKind; f] => + assert(Type.show[f] == "[K >: scala.Nothing <: scala.Any, V >: scala.Nothing <: scala.Any] => scala.collection.immutable.Map[K, V]", Type.show[f]) + '{} + } + + inline def run[T <: AnyKind] = ${ impl[T] } +} diff --git a/tests/pos-macros/i10864a/Test_2.scala b/tests/pos-macros/i10864a/Test_2.scala new file mode 100644 index 000000000000..7a1596d0fa41 --- /dev/null +++ b/tests/pos-macros/i10864a/Test_2.scala @@ -0,0 +1,8 @@ +@main +def run = + T.run[Int] + T.run[C] + T.run[List] + T.run[Map] + +class C[T <: Int] diff --git a/tests/pos-macros/i11738.scala b/tests/pos-macros/i11738.scala new file mode 100644 index 000000000000..e1213a5dee6d --- /dev/null +++ b/tests/pos-macros/i11738.scala @@ -0,0 +1,8 @@ +import scala.quoted.* + +def blah[A](using Quotes, Type[A]): Expr[Unit] = + Type.of[A] match + case '[h *: t] => println(s"h = ${Type.show[h]}, t = ${Type.show[t]}") // ok + case '[type f[X]; f[a]] => println(s"f = ${Type.show[f]}, a = ${Type.show[a]}") // error + case _ => + '{()} diff --git a/tests/pos-macros/i7264.scala b/tests/pos-macros/i7264.scala index c87409561bee..82264402c768 100644 --- a/tests/pos-macros/i7264.scala +++ b/tests/pos-macros/i7264.scala @@ -3,5 +3,6 @@ class Foo { def f[T2](t: Type[T2])(using Quotes) = t match { case '[ *:[Int, t2] ] => Type.of[ *:[Int, t2] ] + case '[ type t <: Tuple; *:[t, t] ] => } } diff --git a/tests/pos-macros/multiline-quote-patterns.scala b/tests/pos-macros/multiline-quote-patterns.scala new file mode 100644 index 000000000000..a1f1649b6059 --- /dev/null +++ b/tests/pos-macros/multiline-quote-patterns.scala @@ -0,0 +1,62 @@ +import scala.quoted.* +def types(t: Type[?])(using Quotes) = t match { + case '[ + type t; + t + ] => + + case '[ + type t + t + ] => + + case '[ + type t + List[t] + ] => + + case '[ + type t; + type u; + Map[t, u] + ] => + + case '[ + type t + type u + Map[t, u] + ] => + + case '[ + type t; type u + t => u + ] => +} + +def expressions(x: Expr[Any])(using Quotes) = x match { + case '{ + type t; + $x: t + } => + + case '{ + type t + $x: t + } => + + case '{ + type t; + List() + } => + + case '{ + type t + List() + } => + + case '{ + type t + type u + Map.empty[t, u] + } => +} diff --git a/tests/pos-macros/quote-pattern-type-variable-no-escape.scala b/tests/pos-macros/quote-pattern-type-variable-no-escape.scala new file mode 100644 index 000000000000..06a53b68e793 --- /dev/null +++ b/tests/pos-macros/quote-pattern-type-variable-no-escape.scala @@ -0,0 +1,12 @@ +import scala.quoted.* + +def foo[T: Type](expr: Expr[Any])(using Quotes): Any = + expr match + case '{ $x: Map[t, t] } => + case '{ type t; $x: Any } => + case '{ type t; $x: Map[t, t] } => + case '{ ($x: Set[t]).toSet[t] } => + + Type.of[T] match + case '[Map[t, t]] => + case '[(t, t, t, t, t, t, t)] =>