Skip to content

Cannot Pass Mirror to Helper Object #7849

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

Closed
deusaquilus opened this issue Dec 25, 2019 · 3 comments
Closed

Cannot Pass Mirror to Helper Object #7849

deusaquilus opened this issue Dec 25, 2019 · 3 comments
Assignees

Comments

@deusaquilus
Copy link
Contributor

minimized code

Say that I create a simple CSV encoder like this:

trait CsvEncoder[T] {
  def encode(elem: T): String
}

object CsvEncoder {
  import scala.compiletime.{erasedValue, summonFrom}
  import compiletime._
  import scala.deriving._

  inline def encodeElem[T](elem: T): String = summonFrom {
    case encoder: CsvEncoder[T] => encoder.encode(elem)
  }

  inline def encodeElems[Elems <: Tuple](idx: Int)(value: Any): List[String] =
    inline erasedValue[Elems] match {
      case _: (elem *: elems1) => 
        encodeElem[elem](productElement[elem](value, idx)) :: encodeElems[elems1](idx + 1)(value)
      case _ => Nil
    }

  inline def derived[T](implicit ev: Mirror.Of[T]): CsvEncoder[T] = new CsvEncoder[T] {
    def encode(value: T): String = 
      inline ev match {
        case m: Mirror.ProductOf[T] =>
          encodeElems[m.MirroredElemTypes](0)(value).mkString(", ")
      }
  }

  given intEncoder: CsvEncoder[Int] { def encode(value: Int) = value + "" }
  given stringEncoder: CsvEncoder[String] { def encode(value: String) = value }
}

I want to be able to use this encoder in a simple macro that will summon whatever mirror is needed from the environment:

object SummonCsvEncoderTest {
  inline def summonMirrorAndUseEncoder[T](value: =>T): String = {
    val mirror = summonFrom {
      case m :Mirror.ProductOf[T] => m
    }
    CsvEncoder.derived(mirror).encode(value)
  }
}

... but then when I use it, it just returns an empty string:

case class ThePerson(name:String, age:Int)
println( SummonCsvEncoderTest.summonMirrorAndUseEncoder[ThePerson](ThePerson("Joe", 123)) )
// Returns ""

expectation

Now, let's say I'll just write the derive/encodeElems/encodeElem method directly in my summoning macro (I'm just copy-pasting those methods from above):

object SummonTest {
  inline def encodeElem[T](elem: T): String = summonFrom {
    case encoder: JsonEncoder[T] => encoder.encode(elem)
  }

  inline def encodeElems[Elems <: Tuple](idx: Int)(value: Any): List[String] =
    inline erasedValue[Elems] match {
      case _: (elem *: elems1) => 
        encodeElem[elem](productElement[elem](value, idx)) :: encodeElems[elems1](idx + 1)(value)
      case _ => Nil
    }

  inline def derived[T](implicit ev: Mirror.Of[T]): CsvEncoder[T] = new CsvEncoder[T] {
      def encode(value: T): String = 
        inline ev match {
          case m: Mirror.ProductOf[T] =>
            encodeElems[m.MirroredElemTypes](0)(value).mkString(", ")
        }
    }

  inline def summonMirrorAndManuallyEncode[T](value: =>T): String = {
    summonFrom {
      case m: Mirror.ProductOf[T] => derived(m).encode(value)
      case _ => "cannot get mirror"
    }
  }
}

Then when I call it, it actually returns the right thing:

println( SummonTest.summonMirrorAndManuallyEncode[ThePerson](ThePerson("Joe", 123)) )
// Returns: "Joe, 123"

How come it works normally here but when I try to pass it into the EncodeCsv helper it returns an empty record???

@milessabin
Copy link
Contributor

Could you try and minimize this for me please?

@deusaquilus
Copy link
Contributor Author

Please give me a few days and I will reproduce in Scastie

@deusaquilus
Copy link
Contributor Author

Closing in favor of #7974

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

2 participants