kubuszok / refined-compat   0.1.0

Apache License 2.0 GitHub

Scala Refined types macros ported to Scala 3

Scala versions: 3.x 2.13
Scala.js versions: 1.x
Scala Native versions: 0.5

refined-compat

Compile-time refinement validation for refined types on Scala 3, powered by Hearth's semiEval.

Problem

Refined's import eu.timepit.refined.auto._ provides implicit macros that validate literal assignments at compile time on Scala 2. These macros rely on c.eval which was never ported to Scala 3 (refined#932), so code like this fails on Scala 3:

import eu.timepit.refined.auto._
val x: Int Refined Positive = 5 // works on Scala 2, fails on Scala 3

Solution

This library provides import hearth.refined.auto._ as a drop-in replacement that works on both Scala 2.13 and Scala 3:

import hearth.refined.auto._
val x: Int Refined Positive = 5    // compiles on both Scala 2 and 3
val y: String Refined NonEmpty = "" // fails at compile time on both

Setup

// build.sbt
libraryDependencies += "com.kubuszok" %% "refined-compat" % "<version>"

Replace import eu.timepit.refined.auto._ with import hearth.refined.auto._.

Provided API

auto._ — implicit conversions

Method What it does
autoRefineV Implicit TRefined[T, P] with compile-time validation
autoInfer Implicit Refined[T, A]Refined[T, B] when Inference[A, B] holds
autoUnwrap Implicit F[T, P]T (unwrapping)

refineMV[P](value) — explicit compile-time validation

import hearth.refined.refineMV
import eu.timepit.refined.numeric.Positive

val x = refineMV[Positive](42)   // compiles
val y = refineMV[Positive](-1)   // compilation error

RefinedTypeOpsM[FTP, T] — custom refined type companions

import eu.timepit.refined.api.Refined
import eu.timepit.refined.numeric.Positive
import hearth.refined.RefinedTypeOpsM

type PosInt = Int Refined Positive
object PosInt extends RefinedTypeOpsM[PosInt, Int]

val x = PosInt(42)   // compiles, validated at compile time
val y = PosInt(-1)   // compilation error

How it works

  1. Uses Hearth's semiEval to extract the value from the expression AST at compile time
  2. Uses semiEval to evaluate the Validate[T, P] (or Inference[A, B]) instance — this works for any predicate whose Validate instance can be reconstructed from the classpath, including predicates that use lambdas, blocks, and inherited methods
  3. Calls validate.validate(value) on the reconstructed instance at macro expansion time
  4. If validation passes, emits Refined.unsafeApply(value); if it fails, aborts compilation with an error message

License

Apache 2.0