From 9ed7ac581e9f1f4fcfcf45fbbc5e03b025200154 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 29 Apr 2022 15:25:44 +0200 Subject: [PATCH 1/2] Detect quoted pattern variables in alternatives Fixes #14696 --- .../tools/dotc/typer/QuotesAndSplices.scala | 19 +++++++++++-- tests/neg-macros/i14696.scala | 27 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 tests/neg-macros/i14696.scala diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 4a9102df211d..7b640e342ca5 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -20,7 +20,7 @@ import dotty.tools.dotc.typer.Implicits._ import dotty.tools.dotc.typer.Inferencing._ import dotty.tools.dotc.util.Spans._ import dotty.tools.dotc.util.Stats.record - +import dotty.tools.dotc.reporting.IllegalVariableInPatternAlternative import scala.collection.mutable @@ -243,6 +243,17 @@ trait QuotesAndSplices { res } + def checkAlternativeBinds(pat0: Tree): Unit = + def rec(pat: Tree): Unit = + pat match + case Typed(pat, _) => rec(pat) + case UnApply(_, _, pats) => pats.foreach(rec) + case pat: Bind => + report.error(IllegalVariableInPatternAlternative(pat.symbol.name), pat.withSpan(pat.nameSpan)) + rec(pat.body) + case _ => + if ctx.mode.is(Mode.InPatternAlternative) then rec(pat0) + val patBuf = new mutable.ListBuffer[Tree] val freshTypePatBuf = new mutable.ListBuffer[Tree] val freshTypeBindingsBuff = new mutable.ListBuffer[Tree] @@ -254,6 +265,7 @@ trait QuotesAndSplices { val newSplice = ref(defn.QuotedRuntime_exprSplice).appliedToType(tpt1.tpe).appliedTo(Typed(pat, exprTpt)) transform(newSplice) case Apply(TypeApply(fn, targs), Apply(sp, pat :: Nil) :: args :: Nil) if fn.symbol == defn.QuotedRuntimePatterns_patternHigherOrderHole => + checkAlternativeBinds(pat) args match // TODO support these patterns. Possibly using scala.quoted.util.Var case SeqLiteral(args, _) => for arg <- args; if arg.symbol.is(Mutable) do @@ -266,6 +278,7 @@ trait QuotesAndSplices { patBuf += pat1 } case Apply(fn, pat :: Nil) if fn.symbol.isExprSplice => + checkAlternativeBinds(pat) try ref(defn.QuotedRuntimePatterns_patternHole.termRef).appliedToType(tree.tpe).withSpan(tree.span) finally { val patType = pat.tpe.widen @@ -321,7 +334,9 @@ trait QuotesAndSplices { } private def transformTypeBindingTypeDef(nameOfSyntheticGiven: TermName, tdef: TypeDef, buff: mutable.Builder[Tree, List[Tree]])(using Context): Tree = { - if (variance == -1) + if ctx.mode.is(Mode.InPatternAlternative) then + 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 bindingTypeTpe = AppliedType(defn.QuotedTypeClass.typeRef, bindingType :: Nil) diff --git a/tests/neg-macros/i14696.scala b/tests/neg-macros/i14696.scala new file mode 100644 index 000000000000..c5c207659d69 --- /dev/null +++ b/tests/neg-macros/i14696.scala @@ -0,0 +1,27 @@ +import scala.quoted.* + +def test(a: Expr[Any])(using Quotes): Unit = a match + case '{ ${_}: String } | '{ ${_}: Int } => + case '{ $x1: String } | '{ ${_}: Int } => // error + case '{ $x: String } | '{ $x: Int } => // error // error + case '{ $x1: String } | '{ ${x2: Expr[Int]} } => // error // error + case '{ ${Str(x)}: String } | '{ ${y @ Str(z)}: Int } => // error // error // error + case '{ val x: Int = ???; ${_}(x): String } | '{ '{ val x: String = ???; ${_}(x): String }} => + case '{ val x: Int = ???; $f(x): String } | '{ val x: String = ???; ${_}(x): String } => // error + case '{ val x: Int = ???; $f(x): String } | '{ val x: String = ???; $f(x): String } => // error // error + case '{ val x: Int = ???; $f(x): String } | '{ val x: String = ???; $g(x): String } => // error // error + case '{ varargs(${_}*) } | '{ varargs(${_}*) } => + case '{ varargs($args*) } | '{ varargs(${_}*) } => // error + case '{ varargs($args*) } | '{ varargs($args*) } => // error // error + case '{ varargs($args1*) } | '{ varargs($args2*) } => // error // error + case '{ ${_}: t } | '{ ${_}: Int } => // error + case '{ ${_}: t } | '{ ${_}: t } => // error // error + case '{ ${_}: t } | '{ ${_}: u } => // error // error + case '{ type t; () } | '{ 1 } => // error + case '{ type t; () } | '{ type t; () } => // error // error + case '{ type t; () } | '{ type u; () } => // error // error + +def varargs(x: Any*): Unit = () + +object Str: + def unapply(x: Any): Option[String] = ??? From b61181ffdee33f68484ff553038ab92a2a7616b4 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 5 May 2022 14:32:57 +0200 Subject: [PATCH 2/2] Do not remove InPatternAlternative when typing subexpressions We use typedExpr to type the contents of a quoted pattern. In that case it must not have the Pattern mode mut should keep the InPatternAlternative because it will be needed in the pattern located in the splices. --- compiler/src/dotty/tools/dotc/core/Mode.scala | 2 +- .../dotty/tools/dotc/typer/QuotesAndSplices.scala | 13 ------------- tests/neg-macros/i14696.scala | 2 +- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index ec8ab0dbca15..d141cf7032ee 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -89,7 +89,7 @@ object Mode { /** Don't suppress exceptions thrown during show */ val PrintShowExceptions: Mode = newMode(18, "PrintShowExceptions") - val PatternOrTypeBits: Mode = Pattern | Type | InPatternAlternative + val PatternOrTypeBits: Mode = Pattern | Type /** We are elaborating the fully qualified name of a package clause. * In this case, identifiers should never be imported. diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 7b640e342ca5..5f14352a1fb3 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -243,17 +243,6 @@ trait QuotesAndSplices { res } - def checkAlternativeBinds(pat0: Tree): Unit = - def rec(pat: Tree): Unit = - pat match - case Typed(pat, _) => rec(pat) - case UnApply(_, _, pats) => pats.foreach(rec) - case pat: Bind => - report.error(IllegalVariableInPatternAlternative(pat.symbol.name), pat.withSpan(pat.nameSpan)) - rec(pat.body) - case _ => - if ctx.mode.is(Mode.InPatternAlternative) then rec(pat0) - val patBuf = new mutable.ListBuffer[Tree] val freshTypePatBuf = new mutable.ListBuffer[Tree] val freshTypeBindingsBuff = new mutable.ListBuffer[Tree] @@ -265,7 +254,6 @@ trait QuotesAndSplices { val newSplice = ref(defn.QuotedRuntime_exprSplice).appliedToType(tpt1.tpe).appliedTo(Typed(pat, exprTpt)) transform(newSplice) case Apply(TypeApply(fn, targs), Apply(sp, pat :: Nil) :: args :: Nil) if fn.symbol == defn.QuotedRuntimePatterns_patternHigherOrderHole => - checkAlternativeBinds(pat) args match // TODO support these patterns. Possibly using scala.quoted.util.Var case SeqLiteral(args, _) => for arg <- args; if arg.symbol.is(Mutable) do @@ -278,7 +266,6 @@ trait QuotesAndSplices { patBuf += pat1 } case Apply(fn, pat :: Nil) if fn.symbol.isExprSplice => - checkAlternativeBinds(pat) try ref(defn.QuotedRuntimePatterns_patternHole.termRef).appliedToType(tree.tpe).withSpan(tree.span) finally { val patType = pat.tpe.widen diff --git a/tests/neg-macros/i14696.scala b/tests/neg-macros/i14696.scala index c5c207659d69..bcc02aa43ebe 100644 --- a/tests/neg-macros/i14696.scala +++ b/tests/neg-macros/i14696.scala @@ -5,7 +5,7 @@ def test(a: Expr[Any])(using Quotes): Unit = a match case '{ $x1: String } | '{ ${_}: Int } => // error case '{ $x: String } | '{ $x: Int } => // error // error case '{ $x1: String } | '{ ${x2: Expr[Int]} } => // error // error - case '{ ${Str(x)}: String } | '{ ${y @ Str(z)}: Int } => // error // error // error + case '{ ${Str(x)}: String } | '{ ${y @ Str(z)}: Int } => // error // error case '{ val x: Int = ???; ${_}(x): String } | '{ '{ val x: String = ???; ${_}(x): String }} => case '{ val x: Int = ???; $f(x): String } | '{ val x: String = ???; ${_}(x): String } => // error case '{ val x: Int = ???; $f(x): String } | '{ val x: String = ???; $f(x): String } => // error // error