From 40c2922a63d69ce48524231ddd7a8a39f343adf9 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 13 Feb 2023 14:46:01 +0100 Subject: [PATCH] Support type variable definitions in quoted patterns Support explicit type variable definition in quoted patterns. This allows users to set explicit bounds or use the binding twice. Previously this was only possible on quoted expression patterns case '{ ... }. ```scala case '[type x; `x`] => case '[type x; Map[`x`, `x`]] => case '[type x <: List[Any]; `x`] => case '[type f[X]; `f`] => case '[type f <: AnyKind; `f`] => ``` Fixes #10864 Fixes #11738 --- .../dotty/tools/dotc/parsing/Parsers.scala | 15 ++++++++++++++- .../tools/dotc/typer/QuotesAndSplices.scala | 11 +++++++---- .../_docs/reference/metaprogramming/macros.md | 1 + docs/_docs/reference/syntax.md | 4 +++- tests/pos-macros/i10864/Macro_1.scala | 15 +++++++++++++++ tests/pos-macros/i10864/Test_2.scala | 4 ++++ tests/pos-macros/i10864a/Macro_1.scala | 19 +++++++++++++++++++ tests/pos-macros/i10864a/Test_2.scala | 5 +++++ tests/pos-macros/i11738.scala | 8 ++++++++ tests/pos-macros/i7264.scala | 1 + 10 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 tests/pos-macros/i10864/Macro_1.scala create mode 100644 tests/pos-macros/i10864/Test_2.scala create mode 100644 tests/pos-macros/i10864a/Macro_1.scala create mode 100644 tests/pos-macros/i10864a/Test_2.scala create mode 100644 tests/pos-macros/i11738.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 15a639743c15..be1f9bef7036 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1632,6 +1632,19 @@ object Parsers { t end typ + /** TypeBlock ::= {TypeBlockStat semi} Type + * TypeBlockStat ::= ‘type’ {nl} TypeDcl + */ + def typeBlock(): Tree = + val tDefs = new ListBuffer[Tree] + while in.token == TYPE do + val mods = defAnnotsMods(modifierTokens) + tDefs += typeDefOrDcl(in.offset, in.skipToken(mods)) + acceptStatSep() + val tpt = typ() + if tDefs.isEmpty then tpt else Block(tDefs.toList, tpt) + + private def makeKindProjectorTypeDef(name: TypeName): TypeDef = { val isVarianceAnnotated = name.startsWith("+") || name.startsWith("-") // We remove the variance marker from the name without passing along the specified variance at all @@ -2495,7 +2508,7 @@ object Parsers { atSpan(in.skipToken()) { withinStaged(StageKind.Quoted | (if (location.inPattern) StageKind.QuotedPattern else 0)) { Quote { - if (in.token == LBRACKET) inBrackets(typ()) + if (in.token == LBRACKET) inBrackets(typeBlock()) else stagedBlock() } } diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 8473bd168bc5..ba4471b5751f 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -198,11 +198,11 @@ trait QuotesAndSplices { * ) * ``` */ - private def splitQuotePattern(quoted: Tree)(using Context): (Map[Symbol, Bind], Tree, List[Tree]) = { + private def splitQuotePattern(quoted: Tree)(using Context): (Map[Symbol, Tree], Tree, List[Tree]) = { val ctx0 = ctx - val typeBindings: collection.mutable.Map[Symbol, Bind] = collection.mutable.Map.empty - def getBinding(sym: Symbol): Bind = + val typeBindings: collection.mutable.Map[Symbol, Tree] = collection.mutable.Map.empty + def getBinding(sym: Symbol): Tree = typeBindings.getOrElseUpdate(sym, { val bindingBounds = sym.info val bsym = newPatternBoundSymbol(sym.name.toString.stripPrefix("$").toTypeName, bindingBounds, quoted.span) @@ -396,7 +396,10 @@ trait QuotesAndSplices { 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) + if quoted.isType then typedType(quoted0, WildcardType)(using quoteCtx) match + case quoted1 @ Block(stats, Typed(tpt, _)) => cpy.Block(quoted1)(stats, tpt) + case quoted1 => quoted1 + else typedExpr(quoted0, WildcardType)(using quoteCtx) val (typeBindings, shape, splices) = splitQuotePattern(quoted1) diff --git a/docs/_docs/reference/metaprogramming/macros.md b/docs/_docs/reference/metaprogramming/macros.md index a91e69d985f0..0c41d74b93a3 100644 --- a/docs/_docs/reference/metaprogramming/macros.md +++ b/docs/_docs/reference/metaprogramming/macros.md @@ -530,6 +530,7 @@ def empty[T: Type]: Expr[T] = Type.of[T] match case '[String] => '{ "" } case '[List[t]] => '{ List.empty[t] } + case '[type t <: Option[Int]; List[`t`]] => '{ List.empty[t] } ... ``` diff --git a/docs/_docs/reference/syntax.md b/docs/_docs/reference/syntax.md index 6abc3b2011d1..b6fd06e6a79a 100644 --- a/docs/_docs/reference/syntax.md +++ b/docs/_docs/reference/syntax.md @@ -277,7 +277,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 @@ -296,6 +296,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/pos-macros/i10864/Macro_1.scala b/tests/pos-macros/i10864/Macro_1.scala new file mode 100644 index 000000000000..5212cbda3542 --- /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..da8314d4236b --- /dev/null +++ b/tests/pos-macros/i10864a/Macro_1.scala @@ -0,0 +1,19 @@ +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 <: 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..e5e98d5af1b2 --- /dev/null +++ b/tests/pos-macros/i10864a/Test_2.scala @@ -0,0 +1,5 @@ +@main +def run = + T.run[Int] + T.run[List] + T.run[Map] diff --git a/tests/pos-macros/i11738.scala b/tests/pos-macros/i11738.scala new file mode 100644 index 000000000000..429751159e58 --- /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..f4ebee995543 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`] ] => } }