use Equal[_] with case object
bwiercinski opened this issue · 9 comments
I want to achieve:
sealed trait Status extends Product with Serializable
object Status {
case object A extends Status
case object B extends Status
case object C extends Status
}
import shapeless.::
import shapeless.HNil
import eu.timepit.refined.api.Refined
import eu.timepit.refined.boolean.OneOf
import eu.timepit.refined.generic.Equal
import eu.timepit.refined.auto._
type ValidStatus = OneOf[Equal[Status.A.type] :: Equal[Status.B.type] :: HNil]
val validStatus: Status Refined ValidStatus = Status.A
and i get
<console>:22: error: compile-time refinement only works with literals
val validStatus: Status Refined ValidStatus = Status.A
when i changed to
import eu.timepit.refined.auto.refineMV
val validStatus: Status Refined ValidStatus = refineMV[ValidStatus](Status.A)
i got
<console>:20: error: implicit error;
!I v: Validate[A.type, OneOf[Equal[A.type] :: Equal[B.type] :: HNil]]
OneOf.oneOfHConsValidate invalid because
!I vt: Validate.Aux[A.type, OneOf[Equal[B.type] :: HNil], OneOf[RT]]
――OneOf.oneOfHConsValidate invalid because
!I vh: Validate.Aux[A.type, Equal[B.type], RH]
――――Equal.equalValidate invalid because
!I wu: WitnessAs[B.type, A.type]
――――――WitnessAs.natWitnessAs invalid because
!I ta: ToInt[B.type]
val validStatus: Status Refined ValidStatus = refineMV[ValidStatus](Status.A)
refineV
also not working
I think we can generalize this issue to that refineMV and refined.auto don't handle singleton objects, just literal singleton types (at least that's what I think it is).
In fact, it doesn't work on literal singleton types either, just inline literals :/
In fact, it doesn't work on literal singleton types either, just inline literals :/
@kubukoz Could you provide an example? The example below works as expected and is not using an "inline literal".
scala> val s = "Hello"
s: String = Hello
scala> refineMV[Equal[s.type]]("World")
<console>:37: error: Predicate failed: (World == Hello).
refineMV[Equal[s.type]]("World")
^
@bwiercinski refineV
works if the value you pass in is of type Status
:
scala> refineV[ValidStatus](Status.A: Status)
res5: Either[String,eu.timepit.refined.api.Refined[Status,ValidStatus]] = Right(A)
scala> refineV[ValidStatus](Status.C: Status)
res6: Either[String,eu.timepit.refined.api.Refined[Status,ValidStatus]] = Left(Predicate failed: oneOf((C == A), (C == B), false).)
It will never work with refineMV
because that macro needs to evaluate its value parameter at compile-time and that is only safe for literals. If the constructor of Status.A
would contain side-effects, evaluating it at compile-time would be a bad idea.
@fthomas but Status.A
is a singleton object, its type is fully known at compile-time.
As for the literal singletons:
type X = String Refined Equal["foo"]
@ val a: X = "foo"
a: X = foo
@ val b: "foo" = "foo"
b: String = "foo"
@ val c: X = b
cmd11.sc:1: compile-time refinement only works with literals
val c: X = b
^
Compilation Failed
which is basically the opposite way (the literal is the value I want to refine, and in your example it's the type to refine to)
Yes, but refineMV
requires the value at compile-time to decide if it satisfies the predicate. And it is not safe to evaluate Status.A
at compile-time since this would execute its constructor.
Could we inspect the type of Status.A
and use the fact that it's a singleton?
I tried doing this myself but I'm not that familiar with the scala-reflect API and couldn't figure that out soon enough...
Oh, I see. That would probably be impossible in the general case (some validations will really require an instance). Equal
is fine, but it's a special case.
However, for literal singleton types we could use the name of the type as the value.
However, for literal singleton types we could use the name of the type as the value.
You're right. But I wouldn't recommend changing RefineMacro
for this since it is 2.13-only and I'm not sure if RefineMacro
will survive the transition to Scala 3.