I don't even know how to ask the question.
I have a macro that creates an instance of IsEnum[T] for a type T.
I'm doing testing for it, and want to make sure that the implicit is not found for types that are not sealed, or that, in general, don't meet the requirements of an enum.
So I created this method for testing
def enumOf[T](implicit isEnum:IsEnum[T] = null) = isEnum
And then I ensure that enumOf[NotAnEnum] == null
But instead, it fails at compile time.
One thing is the macro erroring. Another thing is the macro just not applying for a given case. How to make that distinction when creating macros?
Edit: I've used c.abort and c.error, both giving me the same results.
Sounds like you didn't make your macro materializing type class IsEnum whitebox. Normally implicit macros should be whitebox.
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
trait IsEnum[T]
object IsEnum {
implicit def mkIsEnum[T]: IsEnum[T] = macro mkIsEnumImpl[T]
def mkIsEnumImpl[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val typ = weakTypeOf[T]
val classSymbol = typ.typeSymbol.asClass
if (!classSymbol.isTrait || !classSymbol.isSealed) c.abort(c.enclosingPosition, s"$typ must be sealed trait")
val symbols = classSymbol.knownDirectSubclasses
symbols.collectFirst {
case symbol if !symbol.isModuleClass || !symbol.asClass.isCaseClass =>
c.abort(c.enclosingPosition, s"${symbol.asClass.toType} must be case object")
}
q"new IsEnum[$typ] {}"
}
}
def enumOf[T](implicit isEnum: IsEnum[T] = null) = isEnum
Usage:
sealed trait A
object A {
case object B extends A
case object C extends A
case class D() extends A
}
enumOf[A] //null
sealed trait A
object A {
case object B extends A
case object C extends A
class D extends A
}
enumOf[A] //null
sealed trait A
object A {
case object B extends A
object C extends A
}
enumOf[A] //null
trait A
object A {
case object B extends A
case object C extends A
}
enumOf[A] //null
sealed trait A
object A {
case object B extends A
case object C extends A
}
enumOf[A] //App$$anon$1#47f37ef1
Runtime of macros is compile time of main code. If a blackbox macro (even implicit blackbox macro) throws an exception then it will be a compile error during compilation of main code. If a whitebox implicit macro throws an exception then during compilation of main code the implicit will be silently removed from candidates.
https://docs.scala-lang.org/overviews/macros/blackbox-whitebox.html
Related
I have this minimal example, I want to create encoders/decoders with circe semi-automatic derivation for the generic case class A[T]
import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto._
import io.circe.syntax._
sealed trait MyTrait
object MyTrait {
implicit val encoder: Encoder[MyTrait] = deriveEncoder
implicit val decoder: Decoder[MyTrait] = deriveDecoder
}
case class A[T](value: T) extends MyTrait
object A {
implicit def encoder[T: Encoder]: Encoder[A[T]] = deriveEncoder
implicit def decoder[T: Decoder]: Decoder[A[T]] = deriveDecoder
}
This codes does not compile and instead outputs this error
could not find Lazy implicit value of type io.circe.generic.encoding.DerivedAsObjectEncoder[A]
And the same for the decoder
What Am I doing wrong here and how can I get it working?
There are few issues. One is that MyTrait Encoder/Decoder will try to dispatch encodeing/decoding into codecs for the subtypes - since e thtrait is sealed all possible traits are known, so such list can be obtained by compiler.
BUT
While MyTrait trait does not take type parameters, its only implementation A takes. Which basically turns it into an existential type.
val myTrait: MyTrait = A(10)
myTrait match {
case A(x) =>
// x is of unknown type, at best you could use runtime reflection
// but codecs are generated with compile time reflection
}
Even if you wanted to make these codecs manually, you have no way of doing it
object Scope {
def aEncoder[T: Encoder]: Encoder[A[T]] = ...
val myTraitEncoder: Encoder[MyTrait] = {
case A(value) =>
// value is of unknown type, how to decide what Encoder[T]
// pass into aEncoder?
}
}
For similar reasons you couldn't manually implement Decoder.
To be able to implement codec manually (which is kind of prerequisite to being able to generate it), you can only remove type parameters as you go into subclasses, never add them.
sealed trait MyTrait[T]
case class A[T](value: T) extends MyTrait[T]
This would make it possible to know what kind of T is inside A since it would be passed from MyTrait, so you could write your codec manually and they would work.
Another problem is that reliable generation of ADT's usually require some configuration, e.g. whether or not to use discimination field. And that is provided by circe-generic-extras. Once you use it, (semi)automatic derivation is possible:
import io.circe.{Decoder, Encoder}
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.semiauto._
import io.circe.syntax._
// can be used to tweak e.g. discriminator field name
implicit val config: Configuration = Configuration.default
sealed trait MyTrait[T]
object MyTrait {
implicit def encoder[T: Encoder]: Encoder[MyTrait[A]] = deriveEncoder
implicit def decoder[T: Decoder]: Decoder[MyTrait[A]] = deriveDecoder
}
case class A[T](value: T) extends MyTrait[T]
object A {
implicit def encoder[T: Encoder]: Encoder[A[T]] = deriveEncoder
implicit def decoder[T: Decoder]: Decoder[A[T]] = deriveDecoder
}
Among other solutions to the problem, if you really didn't want to use type parameter in MyTrait but have polymorphism in A would be to replace umbound, generic A with another ADT (or sum type in Scala 3), so that list of all possible implementations would be known and enumerated.
// x is of unknown type, at best you could use runtime reflection
I'll just comment that in principle you can define Decoder with runtime reflection (runtime compilation) based on the class of value in case class A[T](value: T)
sealed trait MyTrait
case class A[T](value: T) extends MyTrait
object A {
// custom codecs
implicit val intEnc: Encoder[A[Int]] = new Encoder[A[Int]] {
override def apply(a: A[Int]): Json = Json.obj("A" -> Json.obj("value" -> Json.fromInt(a.value)))
}
implicit val strEnc: Encoder[A[String]] = new Encoder[A[String]] {
override def apply(a: A[String]): Json = Json.obj("value" -> Json.fromString(a.value))
}
implicit val boolEnc: Encoder[A[Boolean]] = new Encoder[A[Boolean]] {
override def apply(a: A[Boolean]): Json = Json.fromBoolean(a.value)
}
}
val Integer = classOf[Integer]
val Boolean = classOf[java.lang.Boolean]
def primitiveClass[T](runtimeClass: Class[_]): Class[_] = runtimeClass match {
case `Integer` => classOf[Int]
case `Boolean` => classOf[Boolean]
//...
case _ => runtimeClass
}
object MyTrait {
implicit val encoder: Encoder[MyTrait] = new Encoder[MyTrait] {
override def apply(a: MyTrait): Json = a match {
case a1#A(t) =>
tb.eval(q"implicitly[io.circe.Encoder[A[${rm.classSymbol(primitiveClass(t.getClass)).toType}]]]")
.asInstanceOf[Encoder[A[_]]]
.apply(a1)
}
}
}
(A(1): MyTrait).asJson.noSpaces // {"A":{"value":1}}
(A("a"): MyTrait).asJson.noSpaces //{"value":"a"}
(A(true): MyTrait).asJson.noSpaces //true
But for case class A[T](value: Int) such trick would be impossible.
Given a code which takes a type, takes it's known direct subclasses, filters the ones that are case classes and then takes the companion of that case class:
def firstSubclassWithCompanion[T: TypeTag]: String = {
val superclass = implicitly[TypeTag[T]].tpe.typeSymbol.asClass
val caseClass = superclass.knownDirectSubclasses.map(_.asClass).filter(_.isCaseClass).head
s"case class $caseClass has companion ${caseClass.companion}"
}
With a simple example
sealed trait With
case class WithCase() extends With
It gives the expected return
> firstSubclassWithCompanion[With]
"class WithCase has companion object WithCase"
As With trait has a WithCase subclass, which is case class that has a companion object (defined by compiler).
However, given the following example, where the subclass is defined in the companion object of the inheriting trait:
sealed trait Without
object Without {
case class WithoutCase() extends Without
}
It doesn't return the companion object
> firstSubclassWithCompanion[Without]
"class WithoutCase has companion <none>"
It works fine if it's defined in other object.
Bugs should be reported at https://github.com/scala/bug/issues
A workaround is to use caseClass.owner.typeSignature.decl(caseClass.name) instead of caseClass.companion.
Another workaround is to translate this runtime-reflection code into a macro (compile-time reflection). Since all the classes here are defined at compile time it makes sense to use a macro.
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def firstSubclassWithCompanion[T]: String = macro firstSubclassWithCompanionImpl[T]
def firstSubclassWithCompanionImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
val superclass = weakTypeOf[T].typeSymbol.asClass
val caseClass = superclass.knownDirectSubclasses.map(_.asClass).filter(_.isCaseClass).head
val res = s"case class $caseClass has companion ${caseClass.companion}"
q"$res"
}
I am trying to use an Enumeration object in a method. The enum object extends from a trait, and the method takes the trait as a parameter. Here is the sample code.
sealed trait canAdd
object DAdder extends Enumeration with canAdd
{
type DAdder = Value
val P, Q = Value
}
class ClassTypeTest extends AnyFlatSpec with Matchers
{
class AClass
{
def add(v: canAdd) = if(v.isInstanceOf[DAdder]) println("Got DAdder") else println("Got IAdder")
def note(v: canAdd) = println("Got some canAdd trait object")
}
val aobj = new AClass
val aseq: Seq[DAdder] = Seq(DAdder.P, DAdder.Q, DAdder.P)
//*** Error in below line *****
aseq.foreach(aobj.add(_))
}
The compiler gives the following error:
Error:(23, 23) type mismatch;
found : x.x.DAdder.DAdder
(which expands to) x.x.DAdder.Value
required: x.x.canAdd
aseq.map(aobj.add(_))
Shouldn't I be able to pass an object that inherits the trait in a method that takes the trait as an argument? How do I fix this?
Enumeration class doesn't allow you to extends its values functionality. It is basically:
abstract class Enumeratum {
sealed trait Value { /* utilities */ }
object Value { /* factory of Values */ }
// utilities
}
Since you have the control only on Enumeratum class you cannot extend Values which is what you ask for.
You can easily have this functionality with sealed traits and case objects
sealed trait DAdder extends canadd
object DAdder {
case object P extends DAdder
case object Q extends DAdder
}
This however misses some utilities like finding value by its name, listing all values, etc.
This problem is solved by Enumeratum library which require that you mix in some traits and cope paste one line (val values = findValues) to have all functionalities of Enumeration and more
import enumeratum._
sealed trait DAdder extends canadd
with EnumEntry // mixin here
object DAdder extends Enum[DAdder] { // and mixin here
case object P extends DAdder
case object Q extends DAdder
val values = findValues // and one line calling macro
}
(There are also specializations for enums that should store some primitives/strings as their values in enumeratum.values._).
In Scala 3 this will probably look slightly different as it introduced enum keyword
sealed trait DAdder extends canadd
object DAdder {
case object P extends DAdder
case object Q extends DAdder
}
will become
enum DAdder extends canadd {
case P, Q
}
Scala 3's enum will ordinal method defined, so libraries like Enumeratum will have to provide a little fewer functionalities. (I guess by mixing in Java's Enum trait you will have access to values so everything else is a matter of extension methods).
Given this code:
sealed trait Parent
case object GetOne extends Parent
case object GetTwo extends Parent
Is it possible in Scala to enforce those constraints:
Parent can only be extended by case object
The child case object of Parent must have their names start by Get.
Is it possible?
Parent can only be extended by case object
You may get close using Singleton.
(as mentioned by #MateuszKubuszok)
Here is an example:
sealed trait Foo extends Product with Serializable { self: Singleton => }
Then this works:
final case object A extends Foo
final case object B extends Foo
And this doesn't:
final case object A extends Foo
final case class B(blah: String) extends Foo
The child case object of Parent must have their names start by Get.
Not using standard Scala.
Maybe with macros or something like that, but really feels like a strange requisite; do you plan to get those instances through reflection? or what is the reason to wanting that?
(in any case, seems like something that may be better handled by code reviews and maybe a scalafix rule)
Try macros (with Shapeless)
import shapeless.ops.{coproduct, hlist}
import shapeless.{Coproduct, HList, LabelledGeneric}
import shapeless.ops.union.{Keys, Values}
def check[A] = new PartiallyApplied[A]
class PartiallyApplied[A] {
def apply[C <: Coproduct, K <: HList, V <: Coproduct]()(implicit
labelledGeneric: LabelledGeneric.Aux[A, C],
keys: Keys.Aux[C, K],
values: Values.Aux[C, V],
allKeysStartWithGet: hlist.LiftAll[StartsWithGet, K],
allValuesAreObjects: coproduct.LiftAll[IsObject, V]
) = null
}
import shapeless.Witness
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
trait StartsWithGet[S]
object StartsWithGet {
implicit def mkStartsWithGet[S <: Symbol]: StartsWithGet[S] = macro impl[S]
def impl[S <: Symbol : c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val typ = weakTypeOf[S]
val witness = c.inferImplicitValue(
c.typecheck(tq"_root_.shapeless.Witness.Aux[$typ]", mode = c.TYPEmode).tpe,
silent = false
)
val str = c.eval(c.Expr[Witness.Lt[scala.Symbol]](
c.untypecheck(witness.duplicate)
)).value.name
if (str.startsWith("Get"))
q"new StartsWithGet[$typ] {}"
else c.abort(c.enclosingPosition, s"$str doesn't start with Get")
}
}
trait IsObject[A]
object IsObject {
implicit def mkIsObject[A]: IsObject[A] = macro impl[A]
def impl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val typ = weakTypeOf[A]
if (typ.typeSymbol.isModuleClass)
q"new IsObject[$typ] {}"
else c.abort(c.enclosingPosition, s"$typ is not object")
}
}
sealed trait Parent
case object GetOne extends Parent
case object GetTwo extends Parent
check[Parent]() // compiles
sealed trait Parent
case object GetOne extends Parent
case object Two extends Parent
check[Parent]() // doesn't compile
sealed trait Parent
case object GetOne extends Parent
case class GetTwo() extends Parent
check[Parent]() // doesn't compile
Alternatively StartsWithGet can be defined via https://github.com/fthomas/singleton-ops
import shapeless.tag.##
import singleton.ops.{Require, StartsWith}
trait StartsWithGet[S]
object StartsWithGet {
implicit def mkStartsWithGet[S <: String](implicit
startsWith: Require[S StartsWith "Get"]
): StartsWithGet[Symbol ## S] = null
}
Parent can only be extended by case object
In Scala 3 you could define an enumeration
enum Parent {
case GetOne, GetTwo
}
which forces the members to be effectively case objects.
I have the following definitions:
sealed trait MyTrait
case object One extends MyTrait
case object Two extends MyTrait
object Test extends (MyTrait => MyTraitReturnVal) {
def apply(myTrait: MyTrait) = { ... }
def myMethod(myTrait: MyTrait) = {
...
}
}
When I call Test(One), it complains that it is expecting an interface instead of a concrete type. Any suggestions on how to get around this?
So calling:
Test(One)
Complains that it is expecting MyTrait and the actual parameter is One.type!
You are inheriting Test object from Function1 class, so you need to implement 'apply' method instead of 'myMethod'. This code compiles and runs:
sealed trait MyTrait
case object One extends MyTrait
case object Two extends MyTrait
case class MyTraitReturnVal(my: MyTrait)
object Test extends (MyTrait => MyTraitReturnVal) {
def apply(myTrait: MyTrait) =
new MyTraitReturnVal(myTrait)
}
println(Test(One))
println(Test(Two))