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

Conversation

zeptometer
Copy link
Contributor

@zeptometer zeptometer commented Jun 23, 2023

This will close #17905.

Current behavior and issue

Consider the following macro:

// Macro_1.scala
import scala.quoted.*

inline def testCtxParam(inline body: Any) = ${ testCtxParamImpl('body) }
def testCtxParamImpl(body: Expr[Any])(using Quotes): Expr[String] =
  body match
    case '{ def g(using s: String) = "placeholder"; $a(g): String } =>
      '{ $a((s: String) => s"(inside ${s})") }
    case _ => Expr("not matched")
// Test_2.scala
@main def Test: Unit =
  given String = "given"
  println(testCtxParam { def f(using t: String) = "placeholder"; f + " outside" })

In theory, running this code should allow the quote '{ def f(using t: String)... } to match against the first clause of testCtxParamImpl, binding $a to '{ g => g + " outside"}. As such, we'd expect Test_2.scala to output (inside given) outside.

However, compiling Macro_1.scala results in the following error:

-- Error: macro_1.scala:6:56 ---------------------------------------------------
6 |    case '{ def g(using s: String) = "placeholder"; $a(g): String } =>
  |                                                        ^
  |    No given instance of type String was found for parameter s of method g
1 error found

The issue stems from the method symbol g in the HOAS pattern $a(g). Here, g should represent a symbol that can appear in the pattern variable $a. It's not intended to mean a method call, yet the compiler treats it as such, attempts to insert explicit contextual arguments, and fails.

Approach to fix this issue

It is Typer.adaptNoArgs that inserts explicit contextual arguments. I added the following condition !ctx.mode.is(Mode.InQuotePatternHoasArgs) to prevent it from inserting contextual arguments.

https://github.com/lampepfl/dotty/pull/18040/files#diff-8c9ece1772bd78160fc1c31e988664586c9df566a1d22ff99ef99dd6d5627a90R4064

Mode.InQuotePatternHoasArgs is a new mode for typing arguments of a HOAS pattern. This solution works, as all existing tests have passed. However, considering that the number of Modes is limited to 32 in the current implementation, it might not be the most optimal approach.

Discussion: Matching against contextual/implicit methods

An aspect to consider is how the quote pattern match should treat normal/contextual/implicit methods. For instance, consider this macro:

import scala.quoted.

inline def testMethods(inline body: Any) = ${ testMethodsImpl('body) }
def testMethodsImpl(body: Expr[Any])(using Quotes): Expr[String] =
  body match
    case '{ given i : Int = 0; def g(s: Int) = "placeholder"; g(i) } =>
      Expr("matched normal method")
    case '{ given i : Int = 0; def g(using s: Int) = "placeholder"; g } =>
      Expr("matched contextual method")
    case '{ given i : Int = 0; def g(implicit s: Int) = "placeholder"; g } =>
      Expr("matched implicit method")
    case _ => Expr("not matched")

If we run testMethods { given Int = 0; def f(implicit s: Int) = "placeholder"; f }, how should it be handled?

If pattern matching is done exactly, it should match the third pattern. However, given the similar behavior of using and implicit, it could reasonably match the second pattern. Alternatively, the pattern matcher can forget any information about context parameters, matching the first pattern -- which is the current behavior.

In the current implementation (even without this fix), testMethods { given Int = 0; def f(implicit s: Any) = "placeholder"; f(10) } expands to "matched normal method". This suggests that quote pattern matching disregards whether method parameters are contextual or not.

This behavior has its merits; it removes the need to provide different patterns to match both normal and contextual methods. However, changing this behavior could disrupt macros dependent on the current behavior, potentially breaking the backward compatibility of quote pattern matching.

The question remains: should we maintain the current behavior, or alter the quote pattern matcher to differentiate between normal and contextual methods?

@zeptometer zeptometer changed the title WIP: Inhibit typer to insert contextual arguments when it is inside arguments of HOAS patterns Inhibit typer to insert contextual arguments when it is inside arguments of HOAS patterns Jul 3, 2023
@zeptometer zeptometer marked this pull request as ready for review July 3, 2023 08:10
…ents

* This commit introduces a new mode InQuotePatternHoasArgs. We use this mode when typing arguments of HOAS patterns so that typer will not insert contextual parameters.
* This commit also Fix a bug where toExpr does not fully replace symbols.
@nicolasstucki nicolasstucki merged commit 766d1cf into scala:main Jul 13, 2023
17 checks passed
@Kordyjan Kordyjan added this to the 3.4.0 milestone Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Type error with HOAS quote pattern with contextual method capture
3 participants