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

Type error with HOAS quote pattern with contextual method capture #17905

Closed
zeptometer opened this issue Jun 5, 2023 · 0 comments · Fixed by #18040
Closed

Type error with HOAS quote pattern with contextual method capture #17905

zeptometer opened this issue Jun 5, 2023 · 0 comments · Fixed by #18040
Assignees
Labels
area:metaprogramming:quotes Issues related to quotes and splices itype:bug
Milestone

Comments

@zeptometer
Copy link
Contributor

zeptometer commented Jun 5, 2023

Compiler version

3.2.2

Case 1: given outside quote

Minimized code

// 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" })

Output

scalac Macro_1.scala Test_2.scala gives a type 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

Expectation

Compile succeeds and scala Test outputs (inside given) outside

Case 2: given inside quote

Minimized code

// 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 '{ given i:String="given"; def g(using s: String) = "placeholder"; $a(g, i): String } =>
      '{ $a((s: String) => s"(inside ${s})", "given2") }
    case _ => Expr("not matched")
// Test_2.scala
@main def Test: Unit =
  given String = "given"
  println(testCtxParam { given String="given"; def f(using t: String) = "placeholder"; f + " outside" })

Output

scalac Macro_1.scala Test_2.scala gives a type error

-- [E007] Type Mismatch Error: macro_1.scala:7:12 ------------------------------
7 |      '{ $a((s: String) => s"(inside ${s})", "given2") }
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |            Found:    String => String
  |            Required: String
  |
  | longer explanation available when compiling with `-explain`
1 error found

Expectation

Compile succeeds and scala Test outputs (inside given2) outside

@zeptometer zeptometer added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Jun 5, 2023
@zeptometer zeptometer changed the title Type error with HOAS pattern match with contextual method capture Type error with HOAS quote pattern with contextual method capture Jun 5, 2023
@nicolasstucki nicolasstucki added area:metaprogramming:quotes Issues related to quotes and splices and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Jun 5, 2023
nicolasstucki added a commit that referenced this issue Jul 13, 2023
…nts of HOAS patterns (#18040)

This will close #17905.

## Current behavior and issue

Consider the following macro:

```scala
// 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")
```

```scala
// 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:

```scala
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?
@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
area:metaprogramming:quotes Issues related to quotes and splices itype:bug
Projects
None yet
3 participants