Skip to content

Manual cast required to preserve dependent types #12345

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

Open
japgolly opened this issue May 6, 2021 · 5 comments
Open

Manual cast required to preserve dependent types #12345

japgolly opened this issue May 6, 2021 · 5 comments

Comments

@japgolly
Copy link
Contributor

japgolly commented May 6, 2021

Compiler version

3.0.0-RC3

Minimized code

import scala.quoted.*

object C:
  def hack(using qq: Quotes): C { val q: qq.type } =
    (new C).asInstanceOf[C { val q: qq.type }] // shouldn't be necessary

class C(using val q: Quotes):
  val flags = q.reflect.Flags.Lazy

def test(using q: Quotes) =
  import q.reflect.*

  val c = new C
  c.flags : Flags

  val d = C.hack
  d.flags : Flags

Output

[error] 14 |  c.flags : Flags
[error]    |  ^^^^^^^
[error]    |  Found:    (c.flags : c.q.reflect.Flags)
[error]    |  Required: q².reflect.Flags
[error]    |
[error]    |  where:    q  is a given instance in class C
[error]    |            q² is a parameter in method test
[error] one error found

Expectation

It should compile.

@LPTK
Copy link
Contributor

LPTK commented May 6, 2021

This seems like an instance of the perennial problem that classes lose the precise singleton type of their parameters. There are already a bunch of issues opened about it, e.g., #3964. The workaround is to use a type parameter.

@nicolasstucki
Copy link
Contributor

Placing a Quotes parameter in a field is never a good idea. As a contextual parameter, it should be passed contextually which avoids most complications with path dependency.

In the example, it looks like we are trying to define an extension method the Scala 2 way. Placing the implicit in an object to then refer to it. Now that we support contextual parameters before other parameters we do not need to do this workaround and can define the method directly. See https://docs.scala-lang.org/scala3/guides/macros/reflection.html#macro-api-design.

extension (q: Quotes)
  def lazyFlags = q.reflect.Flags.Lazy

def test(using Quotes) =
  quotes.lazyFlags

or

object C:
  def lazyFlags(using Quotes) = quotes.reflect.Flags.Lazy

def test(using Quotes) =
  C.lazyFlags

Also try to avoid naming Quotes and use the contextual quotes to access it. See https://docs.scala-lang.org/scala3/guides/macros/reflection.html#how-to-use-the-api

@japgolly
Copy link
Contributor Author

japgolly commented May 6, 2021

@nicolasstucki Not that simple. In my (un-minimised) class I'm statefully storing bits of TASTY. In this case I have to store (and expose) the Quotes instance the class, don't I? Example:

class C(using val q: Quotes)(flags: q.reflect.Flags) {
  import q.reflect.*
  var stmts = Vector.empty[Statement]
}

@nicolasstucki
Copy link
Contributor

Indeed, those are a bit more complex.

One way I simplified that in the past, which was a bit more verbose was to use type parameters.

class C[Flags, Statement](flags: Flags) {
  var stmts = Vector.empty[Statement]
}

def app(using Quotes) =
  import quotes.reflect.*
  new C[Flags, Statement](flags)

@nicolasstucki
Copy link
Contributor

nicolasstucki commented May 6, 2021

I wonder if something like this would work

class C[Q <: Quotes & Singleton](using val q: Q)(flags: q.reflect.Flags) {
  import q.reflect.*
  var stmts = Vector.empty[Statement]
}

Then at call site new C(flags) should infer new C[q.type](using q)(flags).

I used this pattern in the past but I had to add explicitly the [q.type] because inference was not working properly. But maybe now it does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants