fthomas/refined

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.