diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 7fecdd2c5eae..644b9d8cfd88 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1735,6 +1735,23 @@ object Parsers { /** The block in a quote or splice */ def stagedBlock() = inBraces(block(simplify = true)) + /** TypeBlock ::= {TypeBlockStat semi} Type + * TypeBlockStat ::= ‘type’ {nl} TypeDcl + */ + def typeBlock(): Tree = + quotePatternTypeVars() match + case Nil => typ() + case tDefs => Block(tDefs, typ()) + + def quotePatternTypeVars(): List[Tree] = + val tDefs = new ListBuffer[Tree] + while in.token == TYPE do + val mods = defAnnotsMods(BitSet()) + tDefs += typeDefOrDcl(in.offset, in.skipToken(mods)) + if in.token == SEMI then in.nextToken() + if in.isNewLine then in.nextToken() + tDefs.toList + /** ExprSplice ::= ‘$’ spliceId -- if inside quoted block * | ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern * | ‘$’ ‘{’ Pattern ‘}’ -- when inside quoted pattern @@ -2496,7 +2513,7 @@ object Parsers { atSpan(in.skipToken()) { withinStaged(StageKind.Quoted | (if (location.inPattern) StageKind.QuotedPattern else 0)) { val body = - if (in.token == LBRACKET) inBrackets(typ()) + if (in.token == LBRACKET) inBrackets(typeBlock()) else stagedBlock() Quote(body) } @@ -3762,6 +3779,8 @@ object Parsers { else makeTypeDef(bounds) case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF => makeTypeDef(typeBounds()) + case _ if (staged & StageKind.QuotedPattern) != 0 => // is in a quoted pattern + 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 72b92d125950..432bf99ac2cf 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -213,11 +213,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) @@ -408,6 +408,17 @@ trait QuotesAndSplices { } val (untpdTypeVariables, quoted0) = desugar.quotedPatternTypeVariables(desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt)))) + for untpdTypeVariable <- untpdTypeVariables do untpdTypeVariable.rhs match + case _: TypeBoundsTree => // ok + case LambdaTypeTree(_, body: TypeBoundsTree) => // ok + case _ => report.error("Quote type variable definition cannot be an alias", untpdTypeVariable.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) @@ -418,7 +429,7 @@ trait QuotesAndSplices { addQuotedPatternTypeVariable(typeVariable.symbol) val pattern = - if quoted.isType then typedType(quoted0, WildcardType) + if quoted.isType then typedType(quoted0, WildcardType)(using patternCtx) else typedExpr(quoted0, WildcardType) if untpdTypeVariables.isEmpty then pattern diff --git a/docs/_docs/reference/metaprogramming/macros.md b/docs/_docs/reference/metaprogramming/macros.md index 1ed89422eee5..f9a94d5fd116 100644 --- a/docs/_docs/reference/metaprogramming/macros.md +++ b/docs/_docs/reference/metaprogramming/macros.md @@ -530,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 higer-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 dd2e7ce118c4..72949948ee0d 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-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/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] + } => +}