From 41a4277cd39bb7185db8642a28f406e790840f69 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 10 Feb 2023 16:55:57 +0100 Subject: [PATCH] Insert top level type variables before typing quote pattern ```scala case '{ type u; ($x: t, $y: t, $z: u) } ``` is desugared to ```scala case '{ type t; type u; ($x: `t`, $y: `t`, $z: `u`) } ``` Fixes #14708 --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 2 +- .../tools/dotc/typer/QuotesAndSplices.scala | 81 ++++++++++++++++++- tests/neg-macros/quotedPatterns-5.scala | 2 +- tests/pos-macros/i14708.scala | 18 +++++ tests/pos-macros/i16265.scala | 2 +- tests/pos-macros/i7264b.scala | 2 +- 6 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 tests/pos-macros/i14708.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index c0b5987c3875..f9b5c4a46f8a 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1518,7 +1518,7 @@ object Trees { paramss.mapConserve(transformParams) protected def transformMoreCases(tree: Tree)(using Context): Tree = { - assert(ctx.reporter.errorsReported) + assert(ctx.reporter.errorsReported, tree) tree } } diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 2fe5770c5b4b..f933b72e71b7 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -10,6 +10,7 @@ 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.NameOps.isVarPattern import dotty.tools.dotc.core.Names._ import dotty.tools.dotc.core.StagingContext._ import dotty.tools.dotc.core.StdNames._ @@ -24,7 +25,6 @@ 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 => @@ -394,9 +394,84 @@ trait QuotesAndSplices { } val quoted0 = desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt))) val quoteCtx = quoteContext.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern) + + def normalizeTypeBindings(quoted: untpd.Tree): untpd.Tree = + val variables = mutable.Set.empty[TypeName] // TODO use stable order + val typeNormalizer = new untpd.UntypedTreeMap { + override def transform(tpt: untpd.Tree)(using Context) = + tpt match + case untpd.Ident(tpnme.WILDCARD_STAR) => tpt + case tpt @ untpd.Ident(name) if name.isTypeName && !tpt.isBackquoted && name.isVarPattern => + variables += name.asTypeName + tpt.pushAttachment(Trees.Backquoted, ()) + tpt + case _: untpd.TypedSplice => tpt + case _ => super.transform(tpt) + } + val termNormalizer = new untpd.UntypedTreeMap { + override def transform(tree: untpd.Tree)(using Context) = + tree match + case untpd.Splice(_) => tree + case untpd.Typed(expr, tpt) => + untpd.cpy.Typed(tree)( + super.transform(expr), + typeNormalizer.transform(tpt) + ) + case untpd.TypeApply(fn, targs) => + untpd.cpy.TypeApply(tree)( + super.transform(fn), + targs.map(typeNormalizer.transform) + ) + case _ => super.transform(tree) + } + + val transformed: untpd.Tree = + if quoted.isType then quoted // FIXME: typeNormalizer.transform(quoted) + else termNormalizer.transform(quoted) + + def typeBindingDefinedInSource: List[TypeName] = + transformed match + case untpd.Block(stats, _) => + stats.takeWhile { + case untpd.TypeDef(name, _) => name.isVarPattern + case _ => false + }.map(_.asInstanceOf[untpd.TypeDef].name.asTypeName) + case _ => Nil + variables --= typeBindingDefinedInSource + + // println("==============") + // println(quoted.show) + // println(quoted) + // println("--------------") + // println(transformed.show) + // println(transformed) + // println(" ") + // println(variables) + // println(" ") + + if variables.isEmpty then transformed + else + transformed match + case untpd.Block(stats, expr) => + val typeBindings = stats.takeWhile { + case untpd.TypeDef(name, _) => name.isVarPattern + case _ => false + } + variables --= typeBindings.map(_.asInstanceOf[untpd.TypeDef].name.asTypeName) + untpd.cpy.Block(quoted)( + variables.toList.map(name => untpd.TypeDef(name, untpd.TypeBoundsTree(untpd.EmptyTree, untpd.EmptyTree, untpd.EmptyTree))) ::: stats, + expr + ) + case _ => + untpd.cpy.Block(quoted)( + variables.toList.map(name => untpd.TypeDef(name, untpd.TypeBoundsTree(untpd.EmptyTree, untpd.EmptyTree, untpd.EmptyTree))), + transformed + ) + + val quoted0normalized = normalizeTypeBindings(quoted0) val quoted1 = - if quoted.isType then typedType(quoted0, WildcardType)(using quoteCtx) - else typedExpr(quoted0, WildcardType)(using quoteCtx) + if quoted.isType then typedType(quoted0normalized, WildcardType)(using quoteCtx) + else typedExpr(quoted0normalized, WildcardType)(using quoteCtx) val (typeBindings, shape, splices) = splitQuotePattern(quoted1) 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/i14708.scala b/tests/pos-macros/i14708.scala new file mode 100644 index 000000000000..930ff32d095a --- /dev/null +++ b/tests/pos-macros/i14708.scala @@ -0,0 +1,18 @@ +import scala.quoted.* + +object Main { + def foo(a: Expr[Any])(using Quotes) = { + a match { + case '{ ($x: Set[t]).toSet } => + case '{ ($x: Set[t]).toSet[t] } => + case '{ val a = 1; a; ($x: Set[t]).toSet } => + case '{ type u; ($x: Set[`u`]).toSet } => + case '{ type t; ($x: Set[t]).toSet } => + case '{ varargs(${_}*) } => + case '{ type u; ($x: t, $y: t, $z: u) } => + case _ => + } + } +} + +def varargs(x: Any*): Unit = () diff --git a/tests/pos-macros/i16265.scala b/tests/pos-macros/i16265.scala index db75fbfa307c..6db2d661d508 100644 --- a/tests/pos-macros/i16265.scala +++ b/tests/pos-macros/i16265.scala @@ -4,6 +4,6 @@ class Foo(val value: Int) def foo(exprs: Expr[Any])(using Quotes): Any = exprs match - case '{ $tuple: (Foo *: tail) } => + case '{ type tail <: Tuple; $tuple: (Foo *: tail) } => // FIXME infer bounds of tail val x = '{ ${tuple}.head.value } ??? diff --git a/tests/pos-macros/i7264b.scala b/tests/pos-macros/i7264b.scala index e0d72ad1a27b..6e746fe8df41 100644 --- a/tests/pos-macros/i7264b.scala +++ b/tests/pos-macros/i7264b.scala @@ -1,7 +1,7 @@ import scala.quoted.* class Foo { def f[T2: Type](e: Expr[T2])(using Quotes) = e match { - case '{ $x: *:[Int, t] } => + case '{ type t <: Tuple; $x: *:[Int, t] } => // FIXME infer bounds of tail Type.of[ *:[Int, t] ] } }