Skip to content

Typing error when inline used together with summons and implicits in imported class #16156

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
deusaquilus opened this issue Oct 9, 2022 · 4 comments

Comments

@deusaquilus
Copy link
Contributor

Compiler version

Error happens in 3.2.0 as well as latest nightly 3.2.2-RC1-bin-20221006-a6a3385-NIGHTLY.

Minimized code

Let's create a context object that has an encoder instance:

import quotes.reflect._

trait MyEncoder[T]:
  def encode: String
class Context:
  inline def summonMyEncoder[T]: String =
    ${ SummonEncoder.impl[T] }
  implicit val encoderInstance: MyEncoder[String] =
    new MyEncoder[String] { def encode = "blah" }
end Context

Then let's make the macro SummonEncoder.impl summon that instance:

import scala.quoted._

object SummonEncoder:
  def impl[T: Type](using Quotes) =
    import quotes.reflect._
    Expr.summon[MyEncoder[T]] match
      case Some(enc) => '{ $enc.encode }
      case None      => report.throwError("can't do it")

Then let's create a 'Repo' object that internally uses the context to summon an encoder for a particular type,
...and then use that summoning function.

class Repo[T]:
  val ctx = new Context
  inline def summonEncoder = { import ctx._ // change to: import ctx.{given, _} for the given example
    summonMyEncoder[T]
  }

object Use:
  val repo = new Repo[String]
  val v = repo.summonEncoder // error happens here!

Output

Long exception while typing Repo.this... error happens:

exception while typing Repo.this of class class dotty.tools.dotc.ast.Trees$This # -1
exception while typing Repo.this.ctx of class class dotty.tools.dotc.ast.Trees$Select # -1
exception while typing Repo.this.ctx.encoderInstance of class class dotty.tools.dotc.ast.Trees$Select # -1
exception while typing Repo.this.ctx.encoderInstance of class class dotty.tools.dotc.ast.Trees$Inlined # -1
exception while typing Repo.this.ctx.encoderInstance.encode of class class dotty.tools.dotc.ast.Trees$Select # -1
exception while typing Repo.this.ctx.encoderInstance.encode of class class dotty.tools.dotc.ast.Trees$Inlined # -1
exception while typing Repo.this.ctx.encoderInstance.encode:String of class class dotty.tools.dotc.ast.Trees$Typed # -1
exception while typing Repo.this.ctx.encoderInstance.encode:String of class class dotty.tools.dotc.ast.Trees$Inlined # -1
exception while typing {
  Repo.this.ctx.encoderInstance.encode:String
} of class class dotty.tools.dotc.ast.Trees$Block # -1
exception while typing {
  Repo.this.ctx.encoderInstance.encode:String
}:String of class class dotty.tools.dotc.ast.Trees$Typed # -1
exception while typing {
  val Repo_this: (org.deusaquilus.Use.repo : (): org.deusaquilus.Repo) = 
    org.deusaquilus.Use.repo
  {
    Repo.this.ctx.encoderInstance.encode:String
  }:String
} of class class dotty.tools.dotc.ast.Trees$Inlined # -1
exception while typing def v: String = 
  {
    val Repo_this: (org.deusaquilus.Use.repo : (): org.deusaquilus.Repo) = 
      org.deusaquilus.Use.repo
    {
      Repo.this.ctx.encoderInstance.encode:String
    }:String
  } of class class dotty.tools.dotc.ast.Trees$DefDef # -1
exception while typing @SourceFile("src/main/scala/org/deusaquilus/Use.scala") final module class Use()
   extends
 Object() {
  private def writeReplace(): AnyRef = 
    new scala.runtime.ModuleSerializationProxy(classOf[org.deusaquilus.Use.type]
      )
  def repo: org.deusaquilus.Repo[String] = new org.deusaquilus.Repo[String]()
  def v: String = 
    {
      val Repo_this: (org.deusaquilus.Use.repo : (): org.deusaquilus.Repo) = 
        org.deusaquilus.Use.repo
      {
        Repo.this.ctx.encoderInstance.encode:String
      }:String
    }
} of class class dotty.tools.dotc.ast.Trees$TypeDef # -1
exception while typing package org.deusaquilus {
  @SourceFile("src/main/scala/org/deusaquilus/Use.scala") class Repo[T]()
     extends
   Object() {
    private type T
    def ctx: org.deusaquilus.Context = new org.deusaquilus.Context()
    private inline def summonEncoder: String = 
      scala.compiletime.package$package.erasedValue[String]
  }
  final lazy module val Use: org.deusaquilus.Use = new org.deusaquilus.Use()
  @SourceFile("src/main/scala/org/deusaquilus/Use.scala") final module class Use
    ()
   extends Object() {
    private def writeReplace(): AnyRef = 
      new scala.runtime.ModuleSerializationProxy(
        classOf[org.deusaquilus.Use.type]
      )
    def repo: org.deusaquilus.Repo[String] = new org.deusaquilus.Repo[String]()
    def v: String = 
      {
        val Repo_this: (org.deusaquilus.Use.repo : (): org.deusaquilus.Repo) = 
          org.deusaquilus.Use.repo
        {
          Repo.this.ctx.encoderInstance.encode:String
        }:String
      }
  }
} of class class dotty.tools.dotc.ast.Trees$PackageDef # -1
[info] exception occurred while compiling /home/alexi/git/encoder-typing-reproduction/src/main/scala/org/deusaquilus/Use.scala
java.lang.AssertionError: assertion failed: asTerm called on not-a-Term val <none> while compiling /home/alexi/git/encoder-typing-reproduction/src/main/scala/org/deusaquilus/Use.scala
[error] ## Exception when compiling 3 sources to /home/alexi/git/encoder-typing-reproduction/target/scala-3.2.0/classes
[error] java.lang.AssertionError: assertion failed: asTerm called on not-a-Term val <none>
[error] scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
[error] dotty.tools.dotc.core.Symbols$Symbol.asTerm(Symbols.scala:169)

Expectation

It should work and summon the encoder property.

Github Example:

You can find an example of the codebase here:
https://github.com/deusaquilus/encoder-typing-reproduction
The exact same problem happens if you change the encoder to a given and change the import to import ctx.{given, _}.

Why this is Important

I would really like to use Scala 3 inline to be able to create DAO repository patterns. That would look something like this:

  // Some arbitrary class
  case class Person(id: Int, name: String, age: Int)

  // Outline of a generic DAO repository
  class Repo[T <: { def id: Int }](val ctx: PostgresJdbcContext[Literal]) {

    inline def getById(inline id: Int): Option[T] = { import ctx._
      run(query[T].filter(t => t.id == lift(id))).headOption
    }
    inline def insert(inline t: T): Int = { import ctx._
      run(query[T].insertValue(lift(t)).returning(_.id))
    }
    inline def searchByField(inline predicate: T => Boolean) = { import ctx._
      run(query[T].filter(p => predicate(p)))
    }
  }

  // Specialize the repo for a particular class
  class PeopleRepo(val ctx: PostgresJdbcContext[Literal]) extends Repo[Person](myContext)
  // Declare and use it:
  val peopleRepo = new PeopleRepo("testPostgresDB")
  val joe = Person(123, "Joe", 123)
  val joeId = peopleRepo.insert(joe)
  val joeNew = peopleRepo.getById(joeId)
  val allJoes = peopleRepo.searchByField(p => p.name == "Joe")

Only I can't do this because the above error will happen in my encoders and decoders.

@deusaquilus deusaquilus added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Oct 9, 2022
@deusaquilus
Copy link
Contributor Author

Also, is it necessary to do import ctx._ for every method or should this also work?

class Repo[T]:
  val ctx = new Context
  import ctx._ // move to here
  inline def summonEncoder = {
    summonMyEncoder[T]
  }

object Use:
  val repo = new Repo[String]
  val v = repo.summonEncoder

// [error] -- Error: /home/git/encoder-typing-reproduction/src/main/scala/org/deusaquilus/Use.scala:12:15 
// [error] 12 |  val v = repo.summonEncoder
// [error]    |          ^^^^^^^^^^^^^^^^^^
// [error]    |          can't do it

When I do this, no encoder is found and the report.throwError("can't do it") happens.

@deusaquilus
Copy link
Contributor Author

One other thing, when I move the context variable to be passed in via a parameter it also throws an error.

class Repo[T]:
  inline def summonEncoder(ctx: Context) = { // Pass it in as a parameter
    import ctx._                             // ... then import it!
    summonMyEncoder[T]
  }

object Use:
  val repo = new Repo[String]
  val v = repo.summonEncoder(new Context)

Then the following error happens:

java.lang.IllegalArgumentException: Could not find proxy for ctx: org.deusaquilus.Context in [parameter ctx, method summonEncoder, class Repo, package org.deusaquilus, package org, package <root>], encl = package org.deusaquilus, owners = package org.deusaquilus, package org, package <root>; enclosures = package org.deusaquilus, package org, package <root> while compiling /home/alexi/git/encoder-typing-reproduction/src/main/scala/org/deusaquilus/Use.scala
[error] ## Exception when compiling 3 sources to /home/git/encoder-typing-reproduction/target/scala-3.2.0/classes
[error] java.lang.IllegalArgumentException: Could not find proxy for ctx: org.deusaquilus.Context in [parameter ctx, method summonEncoder, class Repo, package org.deusaquilus, package org, package <root>], encl = package org.deusaquilus, owners = package org.deusaquilus, package org, package <root>; enclosures = package org.deusaquilus, package org, package <root>
[error] dotty.tools.dotc.transform.LambdaLift$Lifter.searchIn$1(LambdaLift.scala:135)
[error] dotty.tools.dotc.transform.LambdaLift$Lifter.proxy(LambdaLift.scala:148)
[error] dotty.tools.dotc.transform.LambdaLift$Lifter.proxyRef(LambdaLift.scala:166)
[error] dotty.tools.dotc.transform.LambdaLift$Lifter.addFreeArgs$$anonfun$1(LambdaLift.scala:172)

odersky added a commit to dotty-staging/dotty that referenced this issue Oct 9, 2022
Imports are supposed to be resolved already, so they need not and should not
be copied into inlined code.

Fixes scala#16156, unfortuntely in the sense that it will not work.
@anatoliykmetyuk anatoliykmetyuk added area:inline and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Oct 10, 2022
@odersky
Copy link
Contributor

odersky commented Oct 13, 2022

When inlining the repo.summonEncoder(new Context) context we first expand to

{
  import Repo_this.ctx.*
  Repo_this.ctx.summonMyEncoder[String]
}

But when we expand summonMyEncoder[String] we get this:

  {
    import Repo_this.ctx.*
    Repo.this.ctx.encoderInstance.encode
  }

It looks to me that the implicit search is made in the context of class Repo, so we get a prefix Repo.this. It should have been the proxy Repo_this instead.

@odersky
Copy link
Contributor

odersky commented Oct 13, 2022

I noted the same problem happens when the inline methods are transparent.

@Kordyjan Kordyjan added this to the Future versions milestone Dec 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants