Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inhibit typer to insert contextual arguments when it is inside arguments of HOAS patterns #18040

Merged
merged 2 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Mode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
14 changes: 8 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4106,8 +4106,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
Expand Down
8 changes: 6 additions & 2 deletions compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 } ===>
zeptometer marked this conversation as resolved.
Show resolved Hide resolved
* /* 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
*
Expand Down Expand Up @@ -566,7 +570,7 @@ object QuoteMatcher {
* 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))
zeptometer marked this conversation as resolved.
Show resolved Hide resolved
case tree: Ident => env.get(tree.symbol).flatMap(argsMap.get).getOrElse(tree)
case tree => super.transform(tree)
}.transform(tree)
Expand Down
3 changes: 3 additions & 0 deletions tests/run-macros/i17905.check
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions tests/run-macros/i17905/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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")
6 changes: 6 additions & 0 deletions tests/run-macros/i17905/Test_2.scala
Original file line number Diff line number Diff line change
@@ -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" } )