diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index ea63eb6a419b..139c4a3fb52b 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -44,6 +44,14 @@ object Mode { /** Are we looking for cyclic references? */ val CheckCyclic: Mode = newMode(5, "CheckCyclic") + /** We are in arguments of HOAS pattern in quote pattern matching + * e.g. x, y, z in a quote pattern '{ ... $a(x, y, z) ... } + * + * This mode keep typer from inserting contextual parameters to a contextual method without arguments. + * (See tests/run-macros/i17905 for motivating examples) + */ + val InQuotePatternHoasArgs: Mode = newMode(6, "InQuotePatternHoasArgs") + /** We are in a pattern alternative */ val InPatternAlternative: Mode = newMode(7, "InPatternAlternative") diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 6a60dd55c352..e882d4884226 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -112,12 +112,14 @@ trait QuotesAndSplices { if isFullyDefined(pt, ForceDegree.flipBottom) then def patternOuterContext(ctx: Context): Context = if (ctx.mode.is(Mode.QuotedPattern)) patternOuterContext(ctx.outer) else ctx - val typedArgs = tree.args.map { - case arg: untpd.Ident => - typedExpr(arg) - case arg => - report.error("Open pattern expected an identifier", arg.srcPos) - EmptyTree + val typedArgs = withMode(Mode.InQuotePatternHoasArgs) { + tree.args.map { + case arg: untpd.Ident => + typedExpr(arg) + case arg => + report.error("Open pattern expected an identifier", arg.srcPos) + EmptyTree + } } for arg <- typedArgs if arg.symbol.is(Mutable) do // TODO support these patterns. Possibly using scala.quoted.util.Var report.error("References to `var`s cannot be used in higher-order pattern", arg.srcPos) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ee8aa668296d..dcd3a9f17f6a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4116,8 +4116,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer wtp match { case wtp: ExprType => readaptSimplified(tree.withType(wtp.resultType)) - case wtp: MethodType if wtp.isImplicitMethod && - ({ resMatch = constrainResult(tree.symbol, wtp, sharpenedPt); resMatch } || !functionExpected) => + case wtp: MethodType + if wtp.isImplicitMethod + && ({ resMatch = constrainResult(tree.symbol, wtp, sharpenedPt); resMatch} || !functionExpected) + && !ctx.mode.is(Mode.InQuotePatternHoasArgs) => if (resMatch || ctx.mode.is(Mode.ImplicitsEnabled)) adaptNoArgsImplicitMethod(wtp) else diff --git a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala index 90ee82ebe5bf..1ef0f480d326 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala @@ -93,7 +93,11 @@ import dotty.tools.dotc.util.optional * '{ val x: T = e1; e2 } =?= '{ val y: P = p1; p2 } ===> withEnv(x -> y)('[T] =?= '[P] &&& '{e1} =?= '{p1} &&& '{e2} =?= '{p2}) * * /* Match def */ - * '{ def x0(x1: T1, ..., xn: Tn): T0 = e1; e2 } =?= '{ def y0(y1: P1, ..., yn: Pn): P0 = p1; p2 } ===> withEnv(x0 -> y0, ..., xn -> yn)('[T0] =?= '[P0] &&& ... &&& '[Tn] =?= '[Pn] &&& '{e1} =?= '{p1} &&& '{e2} =?= '{p2}) + * '{ def x0(x1: T1, ..., xn: Tn)...(y1: U1, ..., ym: Um): T0 = e1; e2 } =?= '{ def y0(z1: P1, ..., zn: Pn)...(w1: Q1, ..., wn: Qn): P0 = p1; p2 } ===> + * /* Note that types of parameters can depend on earlier parameters */ + * withEnv(x1 -> y1, ..., zn -> zn)(...withEnv(y1 -> w1, ..., ym -> wm)( + * ('[T1] =?= '[P1] &&& ... &&&'[T1] =?= '[P1]) &&& ... &&& ('[U1] =?= '[Q1] &&& ... &&&'[Um] =?= '[Qm]) + * &&& '[T0] =?= '[P0] &&& '{e1} =?= '{p1} && '{e2} =?= '{p2})...) * * // Types * @@ -570,7 +574,7 @@ class QuoteMatcher(debug: Boolean) { * f has a method type `(x: Int): Int` and `f` maps to `g`, `p` should hold * `g.apply(0)` because the type of `g` is `Int => Int` due to eta expansion. */ - case Apply(fun, args) if env.contains(tree.symbol) => transform(fun).select(nme.apply).appliedToArgs(args) + case Apply(fun, args) if env.contains(tree.symbol) => transform(fun).select(nme.apply).appliedToArgs(args.map(transform)) case tree: Ident => env.get(tree.symbol).flatMap(argsMap.get).getOrElse(tree) case tree => super.transform(tree) }.transform(tree) diff --git a/tests/run-macros/i17905.check b/tests/run-macros/i17905.check new file mode 100644 index 000000000000..4635c4a5bf96 --- /dev/null +++ b/tests/run-macros/i17905.check @@ -0,0 +1,3 @@ +case 1: [matched 1st case] another_given outside +case 2: [matched 2nd case] given outside +case 3: [matched 1st case] another_given outside diff --git a/tests/run-macros/i17905/Macro_1.scala b/tests/run-macros/i17905/Macro_1.scala new file mode 100644 index 000000000000..31027855953e --- /dev/null +++ b/tests/run-macros/i17905/Macro_1.scala @@ -0,0 +1,10 @@ +import scala.quoted.* + +inline def testCtxParam(inline body: Any) = ${ testCtxParamImpl('body) } +def testCtxParamImpl(body: Expr[Any])(using Quotes): Expr[String] = + body match + case '{ given i: String = "given"; def g(using s: String) = "placeholder"; $a(g, i): String } => + '{ $a(((s: String) ?=> s"[matched 1st case] ${s}"), "another_given") } + case '{ def g(using s: String) = "placeholder"; $a(g): String } => + '{ $a((s: String) ?=> s"[matched 2nd case] ${s}") } + case _ => Expr("not matched") diff --git a/tests/run-macros/i17905/Test_2.scala b/tests/run-macros/i17905/Test_2.scala new file mode 100644 index 000000000000..7c45a8168e85 --- /dev/null +++ b/tests/run-macros/i17905/Test_2.scala @@ -0,0 +1,6 @@ +@main def Test: Unit = + println("case 1: " + testCtxParam { given String = "given"; def f(using t: String) = "placeholder"; f + " outside" }) + given String = "given" + println("case 2: " + testCtxParam { def f(using t: String) = "placeholder"; f + " outside" }) + /* This is expected to match the first case. The current QuoteMatcher identifies a function with a contextual function. */ + println("case 3: " + testCtxParam { given i: String = "given"; def a(x: String) = "placeholder"; a(i) + " outside" } )