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

Cannot summon Type[T] from Quotes #21696

Closed
tribbloid opened this issue Oct 2, 2024 · 6 comments · Fixed by #21711
Closed

Cannot summon Type[T] from Quotes #21696

tribbloid opened this issue Oct 2, 2024 · 6 comments · Fixed by #21711
Assignees
Labels
area:metaprogramming:quotes Issues related to quotes and splices better-errors Issues concerned with improving confusing/unhelpful diagnostic messages itype:enhancement
Milestone

Comments

@tribbloid
Copy link

tribbloid commented Oct 2, 2024

Compiler version

3.5.1

Minimized code

import scala.quoted.{Expr, Quotes, Type}

case class Thing[T](vs: List[String])

class BugDemo {

  object TASTyLowering {

    override protected def deserialize[T](expr: Expr[List[String]])(
        using
        qt: Quotes
    ): Expr[Thing[T]] = {

      val tt = Type.of[T](
        using
        qt
      )

      '{ Thing[T]($expr) }
    }
  }
}

Output

/home/peng/git/dottyspike/src/main/scala/com/tribbloids/spike/dotty/quoted/autolift/spike/BugDemo.scala:16:24
Reference to T within quotes requires a given scala.quoted.Type[T] in scope.


      val tt = Type.of[T](

/home/peng/git/dottyspike/src/main/scala/com/tribbloids/spike/dotty/quoted/autolift/spike/BugDemo.scala:21:16
Reference to T within quotes requires a given scala.quoted.Type[T] in scope.


      '{ Thing[T]($expr) }

the signature of Type.of[T] is:

  /** Return a quoted.Type with the given type */
  @compileTimeOnly("Reference to `scala.quoted.Type.of` was not handled by PickleQuotes")
  given of[T <: AnyKind](using Quotes): Type[T] = ???

so clearly something is wrong here

Expectation

both summoning should succeed

@tribbloid tribbloid added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Oct 2, 2024
@hamzaremmal
Copy link
Member

Hi @tribbloid,

This is actually the correct behavior. Since Scala is subject to the erasure of type parameters, we don't have enough information at runtime to be able to execute val tt = Type.of[T](using qt) or compute anything related to T. To be able to use any information on the type parameter at runtime, you should include a witness (using Type[T]) (or using the following syntax [T: Type]) that describe the type parameter T.

To fix the provided snippet, here is the change to do:

import scala.quoted.{Expr, Quotes, Type}

case class Thing[T](vs: List[String])

def deserialize[T: Type](expr: Expr[List[String]])(using Quotes): Expr[Thing[T]] = {
      '{ Thing[T]($expr) }
}

@hamzaremmal hamzaremmal added area:metaprogramming:quotes Issues related to quotes and splices and removed itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Oct 3, 2024
@hamzaremmal
Copy link
Member

I'm keeping the ticket open to see if we might benefit from a better error reporting

@hamzaremmal hamzaremmal added itype:enhancement better-errors Issues concerned with improving confusing/unhelpful diagnostic messages labels Oct 3, 2024
@tribbloid
Copy link
Author

@hamzaremmal I doubt if it could be that simple, everything defined in the example is indeed compile-time only.

But you are right it is not revealing enough, may I suggest a better example?

comparing the following 2 cases:

(error)

object Demo1 {

  object TASTyLowering {

    def deserialize[T](expr: Expr[List[String]])(
        using
        qt: Quotes,
        tt: Type[T]
    ): Expr[Thing[T]] = {

      '{ Thing[T]($expr) }
    }
  }

  def deserialize[T](expr: Expr[List[String]])(
      using
      Quotes
  ): Expr[Thing[T]] = {

    TASTyLowering.deserialize(expr)
  }
}

(success)

object Demo2 {

  object TASTyLowering {

    def deserialize[T](expr: Expr[List[String]])(
        using
        qt: Quotes,
        tt: Type[T]
    ): Expr[T] = {

      '{ ($expr).asInstanceOf[T] }
    }
  }

  def deserialize[T](expr: Expr[List[String]])(
      using
      Quotes
  ): Expr[T] = {

    TASTyLowering.deserialize(expr)
  }
}

The successful case only differs in return type of the Expr, yet Type can be summoned from Quotes using given Type.of, how did this happen?

@hamzaremmal
Copy link
Member

hamzaremmal commented Oct 4, 2024

About these two snippets, the first one is expecting to fail as I explained in my previous message, we should provide the witness (Type[T]) when calling TASTyLowering.deserialize(expr) in def deserialize[T](expr: Expr[List[String]])(using Quotes): Expr[Thing[T]]. For the second, I was surprised it worked because of the same reasons and when I inspect the type after the typer, it turns out that the type parameter T is instanciated to Nothing when calling TASTyLowering.deserialize(expr). See:

[[syntax trees at end of                     typer]] // /Users/hamzaremmal/Desktop/LAMP/playground/i21696/src/demo2.scala
package <empty> {
  import scala.quoted.*
  final lazy module val Demo2: Demo2 = new Demo2()
  final module class Demo2() extends Object() { this: Demo2.type =>
    final lazy module val TASTyLowering: Demo2.TASTyLowering =
      new Demo2.TASTyLowering()
    final module class TASTyLowering() extends Object() {
      this: Demo2.TASTyLowering.type =>
      def deserialize[T >: Nothing <: Any](expr: scala.quoted.Expr[List[String]]
        )(implicit evidence$1: scala.quoted.Type[T], x$2: scala.quoted.Quotes):
        scala.quoted.Expr[T] =
        '{
          ${
            {
              def $anonfun(using contextual$1: scala.quoted.Quotes):
                scala.quoted.Expr[List[String]] = expr
              closure($anonfun)
            }
          }.asInstanceOf[T]
        }.apply(x$2)
    }
    def deserialize[T >: Nothing <: Any](expr: scala.quoted.Expr[List[String]])(
      using x$2: scala.quoted.Quotes): scala.quoted.Expr[T] =
      Demo2.TASTyLowering.deserialize[Nothing](expr)(
        scala.quoted.Type.of[Nothing](x$2), x$2)
  }
}

Since the type is instanciated to Nothing, the compiler is also able to provide the witness Type[Nothing]. I don't think that the parameter type should collapse to Nothing here.

EDIT: It will be hard to avoid collapsing to Nothing here, one way to mitigate this is to change TASTyLowering.deserialize(expr) to TASTyLowering.deserialize[T](expr). @tribbloid does this also answer your question on why the second works ?

@hamzaremmal hamzaremmal self-assigned this Oct 4, 2024
@tribbloid
Copy link
Author

wow I never realise that,, Thanks a lot for pointing that out!

theoretically, this could also happen if Thing[?] is covariant (collapse to Nothing) or contravariant (collapse to Any). Let me try it shortly ...

@hamzaremmal
Copy link
Member

Thanks a lot for pointing that out!

😄

theoretically, this could also happen if Thing[?] is covariant (collapse to Nothing) or contravariant (collapse to Any)

Yes, it can happen and in the provided example, it will happen.

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 better-errors Issues concerned with improving confusing/unhelpful diagnostic messages itype:enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants