Skip to content
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

WIP: Type class that combines RefType and Validate #193

Closed
wants to merge 16 commits into from
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ val commonImports = s"""
val macroCompatVersion = "1.1.1"
val macroParadiseVersion = "2.1.0"
val shapelessVersion = "2.3.1"
val scalaCheckVersion = "1.12.5"
val scalaCheckVersion = "1.13.2"
val scalazVersion = "7.2.4"
val scodecVersion = "1.10.2"

Expand Down Expand Up @@ -337,7 +337,7 @@ addCommandAlias("testJVM", allSubprojectsJVM map (_ + "/test") mkString (";", ";

val validateCommands = List(
"clean",
"mimaReportBinaryIssues",
//"mimaReportBinaryIssues",
"coverageOff",
"testJS",
"coverage",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package eu.timepit.refined
package scalacheck

import eu.timepit.refined.api.{ RefType, Validate }
import eu.timepit.refined.api.{ RefinedType, RefType }
import eu.timepit.refined.numeric._
import eu.timepit.refined.scalacheck.util.{ Adjacent, Bounded }
import org.scalacheck.{ Arbitrary, Gen }
Expand All @@ -22,12 +22,13 @@ object numeric {
*
* This is like ScalaCheck's `Gen.chooseNum` but for refined types.
*/
def chooseRefinedNum[F[_, _], T: Numeric: Choose, P](min: F[T, P], max: F[T, P])(
def chooseRefinedNum[FTP, T](min: FTP, max: FTP)(
implicit
rt: RefType[F],
v: Validate[T, P]
): Gen[F[T, P]] =
Gen.chooseNum(rt.unwrap(min), rt.unwrap(max)).filter(v.isValid).map(rt.unsafeWrap)
rt: RefinedType.AuxT[FTP, T], nt: Numeric[T], ct: Choose[T]
): Gen[FTP] =
Gen.chooseNum(rt.unwrap(min), rt.unwrap(max))
.filter(rt.validate.isValid)
.map(rt.wrapUnsafe)

///

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package eu.timepit.refined

import eu.timepit.refined.api.{ RefType, Validate }
import eu.timepit.refined.api.{ RefinedType, RefType, Validate }
import org.scalacheck.{ Arbitrary, Gen, Prop }

package object scalacheck {

def arbitraryRefType[F[_, _], T, P](gen: Gen[T])(implicit rt: RefType[F]): Arbitrary[F[T, P]] =
Arbitrary(gen.map(rt.unsafeWrap))

@deprecated("", "0.6.0")
def checkArbitraryRefType[F[_, _], T, P](implicit arb: Arbitrary[F[T, P]], rt: RefType[F], v: Validate[T, P]): Prop =
Prop.forAll((tp: F[T, P]) => v.isValid(rt.unwrap(tp)))

def checkArbitraryRefinedType[FTP](implicit arb: Arbitrary[FTP], rt: RefinedType[FTP]): Prop =
Prop.forAll((tp: FTP) => rt.validate.isValid(rt.unwrap(tp)))
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,25 @@ import org.scalacheck.Properties
class CharArbitrarySpec extends Properties("CharArbitrarySpec") {

property("Digit") =
checkArbitraryRefType[Refined, Char, Digit]
checkArbitraryRefinedType[Char Refined Digit]

property("Letter") =
checkArbitraryRefType[Refined, Char, Letter]
checkArbitraryRefinedType[Char Refined Letter]

property("LowerCase") =
checkArbitraryRefType[Refined, Char, LowerCase]
checkArbitraryRefinedType[Char Refined LowerCase]

property("UpperCase") =
checkArbitraryRefType[Refined, Char, UpperCase]
checkArbitraryRefinedType[Char Refined UpperCase]

property("Whitespace") =
checkArbitraryRefType[Refined, Char, Whitespace]
checkArbitraryRefinedType[Char Refined Whitespace]

property("LetterOrDigit") =
checkArbitraryRefType[Refined, Char, LetterOrDigit]
checkArbitraryRefinedType[Char Refined LetterOrDigit]

property("HexDigit") = {
type HexDigit = Digit Or Interval.Closed[W.`'a'`.T, W.`'f'`.T]
checkArbitraryRefType[Refined, Char, HexDigit]
checkArbitraryRefinedType[Char Refined HexDigit]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ import org.scalacheck.Properties
class GenericArbitrarySpec extends Properties("GenericArbitrary") {

property("Equal") =
checkArbitraryRefType[Refined, Int, Equal[W.`100`.T]]
checkArbitraryRefinedType[Int Refined Equal[W.`100`.T]]
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,76 +13,76 @@ import shapeless.nat._
class NumericArbitrarySpec extends Properties("NumericArbitrary") {

property("Less.positive") =
checkArbitraryRefType[Refined, Int, Less[W.`100`.T]]
checkArbitraryRefinedType[Int Refined Less[W.`100`.T]]

property("Less.negative") =
checkArbitraryRefType[Refined, Int, Less[W.`-100`.T]]
checkArbitraryRefinedType[Int Refined Less[W.`-100`.T]]

property("Less.Nat") =
checkArbitraryRefType[Refined, Long, Less[_10]]
checkArbitraryRefinedType[Long Refined Less[_10]]

property("LessEqual.positive") =
checkArbitraryRefType[Refined, Int, LessEqual[W.`42`.T]]
checkArbitraryRefinedType[Int Refined LessEqual[W.`42`.T]]

property("LessEqual.negative") =
checkArbitraryRefType[Refined, Int, LessEqual[W.`-42`.T]]
checkArbitraryRefinedType[Int Refined LessEqual[W.`-42`.T]]

property("LessEqual.Nat") =
checkArbitraryRefType[Refined, Long, LessEqual[_10]]
checkArbitraryRefinedType[Long Refined LessEqual[_10]]

property("Greater.positive") =
checkArbitraryRefType[Refined, Int, Greater[W.`100`.T]]
checkArbitraryRefinedType[Int Refined Greater[W.`100`.T]]

property("Greater.negative") =
checkArbitraryRefType[Refined, Int, Greater[W.`-100`.T]]
checkArbitraryRefinedType[Int Refined Greater[W.`-100`.T]]

property("Greater.Nat") =
checkArbitraryRefType[Refined, Long, Greater[_10]]
checkArbitraryRefinedType[Long Refined Greater[_10]]

property("GreaterEqual.positive") =
checkArbitraryRefType[Refined, Int, GreaterEqual[W.`123456`.T]]
checkArbitraryRefinedType[Int Refined GreaterEqual[W.`123456`.T]]

property("GreaterEqual.negative") =
checkArbitraryRefType[Refined, Int, GreaterEqual[W.`-123456`.T]]
checkArbitraryRefinedType[Int Refined GreaterEqual[W.`-123456`.T]]

property("GreaterEqual.Nat") =
checkArbitraryRefType[Refined, Int, GreaterEqual[_10]]
checkArbitraryRefinedType[Int Refined GreaterEqual[_10]]

property("Positive") =
checkArbitraryRefType[Refined, Float, Positive]
checkArbitraryRefinedType[Float Refined Positive]

property("NonPositive") =
checkArbitraryRefType[Refined, Short, NonPositive]
checkArbitraryRefinedType[Short Refined NonPositive]

property("Negative") =
checkArbitraryRefType[Refined, Double, Negative]
checkArbitraryRefinedType[Double Refined Negative]

property("NonNegative") =
checkArbitraryRefType[Refined, Long, NonNegative]
checkArbitraryRefinedType[Long Refined NonNegative]

property("Interval.Open") =
checkArbitraryRefType[Refined, Int, Interval.Open[W.`-23`.T, W.`42`.T]]
checkArbitraryRefinedType[Int Refined Interval.Open[W.`-23`.T, W.`42`.T]]

property("Interval.Open (0.554, 0.556)") =
checkArbitraryRefType[Refined, Double, Interval.Open[W.`0.554`.T, W.`0.556`.T]]
checkArbitraryRefinedType[Double Refined Interval.Open[W.`0.554`.T, W.`0.556`.T]]

property("Interval.OpenClosed") =
checkArbitraryRefType[Refined, Double, Interval.OpenClosed[W.`2.71828`.T, W.`3.14159`.T]]
checkArbitraryRefinedType[Double Refined Interval.OpenClosed[W.`2.71828`.T, W.`3.14159`.T]]

property("Interval.OpenClosed (0.54, 0.56]") =
checkArbitraryRefType[Refined, Float, Interval.OpenClosed[W.`0.54F`.T, W.`0.56F`.T]]
checkArbitraryRefinedType[Float Refined Interval.OpenClosed[W.`0.54F`.T, W.`0.56F`.T]]

property("Interval.ClosedOpen") =
checkArbitraryRefType[Refined, Double, Interval.ClosedOpen[W.`-2.71828`.T, W.`3.14159`.T]]
checkArbitraryRefinedType[Double Refined Interval.ClosedOpen[W.`-2.71828`.T, W.`3.14159`.T]]

property("Interval.ClosedOpen [0.4, 0.6)") =
checkArbitraryRefType[Refined, Float, Interval.ClosedOpen[W.`0.4F`.T, W.`0.6F`.T]]
checkArbitraryRefinedType[Float Refined Interval.ClosedOpen[W.`0.4F`.T, W.`0.6F`.T]]

property("Interval.Closed") =
checkArbitraryRefType[Refined, Int, Interval.Closed[W.`23`.T, W.`42`.T]]
checkArbitraryRefinedType[Int Refined Interval.Closed[W.`23`.T, W.`42`.T]]

property("Interval.alias") =
forAll { m: Minute => m >= 0 && m <= 59 }
checkArbitraryRefinedType[Minute]

property("chooseRefinedNum") = {
type Natural = Int Refined NonNegative
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ import org.scalacheck.Properties
class StringArbitrarySpec extends Properties("StringArbitrary") {

property("EndsWith") =
checkArbitraryRefType[Refined, String, EndsWith[W.`"abc"`.T]]
checkArbitraryRefinedType[String Refined EndsWith[W.`"abc"`.T]]

property("MatchesRegex") =
checkArbitraryRefType[Refined, String, MatchesRegex[W.`".{2,}"`.T]]
checkArbitraryRefinedType[String Refined MatchesRegex[W.`".{2,}"`.T]]

property("StartsWith") =
checkArbitraryRefType[Refined, String, StartsWith[W.`"abc"`.T]]
checkArbitraryRefinedType[String Refined StartsWith[W.`"abc"`.T]]

// collection predicates

property("NonEmpty") =
checkArbitraryRefType[Refined, String, NonEmpty]
checkArbitraryRefinedType[String Refined NonEmpty]
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,27 @@ package eu.timepit.refined

import _root_.scodec._
import _root_.scodec.bits.BitVector
import eu.timepit.refined.api.{ RefType, Validate }
import eu.timepit.refined.api.RefinedType

package object scodec {

implicit def refTypeCodec[F[_, _], T, P](
implicit def refTypeCodec[FTP, T](
implicit
codec: Codec[T], refType: RefType[F], validate: Validate[T, P]
): Codec[F[T, P]] =
new Codec[F[T, P]] {
codec: Codec[T], rt: RefinedType.AuxT[FTP, T]
): Codec[FTP] =
new Codec[FTP] {
override def sizeBound: SizeBound =
codec.sizeBound

override def decode(bits: BitVector): Attempt[DecodeResult[F[T, P]]] =
override def decode(bits: BitVector): Attempt[DecodeResult[FTP]] =
codec.decode(bits).flatMap { t =>
refType.refine[P](t.value) match {
rt.refine(t.value) match {
case Right(tp) => Attempt.successful(DecodeResult(tp, t.remainder))
case Left(err) => Attempt.failure(Err(err))
}
}

override def encode(value: F[T, P]): Attempt[BitVector] =
codec.encode(refType.unwrap(value))
override def encode(value: FTP): Attempt[BitVector] =
codec.encode(rt.unwrap(value))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package eu.timepit.refined
package api

trait RefinedType[FTP] {
type F[_, _]
type T
type P

val refType: RefType[F]

val validate: Validate[T, P]

val alias: F[T, P] =:= FTP

val unalias: FTP =:= F[T, P]

def refine(t: T): Either[String, FTP] = {
val res = validate.validate(t)
if (res.isPassed) Right(wrapUnsafe(t))
else Left(validate.showResult(t, res))
}

def refineUnsafe(t: T): FTP =
refine(t).fold(err => throw new IllegalArgumentException(err), identity)

def refineM(t: T)(
implicit
rt: RefinedType.AuxT[FTP, T]
): FTP = macro macros.RefineMacro.refineImpl[FTP, T, P]

def unwrap(tp: FTP): T =
refType.unwrap(unalias(tp))

def wrapUnsafe(t: T): FTP =
alias(refType.unsafeWrap(t))
}

object RefinedType {

def apply[FTP](implicit rt: RefinedType[FTP]): Aux[FTP, rt.F, rt.T, rt.P] = rt

type Aux[FTP, F0[_, _], T0, P0] = RefinedType[FTP] {
type F[x, y] = F0[x, y]
type T = T0
type P = P0
}

type AuxT[FTP, T0] = RefinedType[FTP] {
type T = T0
}

implicit def instance[F0[_, _], T0, P0](
implicit
rt: RefType[F0], v: Validate[T0, P0]
): Aux[F0[T0, P0], F0, T0, P0] =
new RefinedType[F0[T0, P0]] {
override type F[x, y] = F0[x, y]
override type T = T0
override type P = P0

override val refType: RefType[F] = rt
override val validate: Validate[T, P] = v
override val alias: F[T, P] =:= F0[T, P0] = implicitly
override val unalias: F0[T0, P0] =:= F[T, P] = implicitly
}
}
10 changes: 5 additions & 5 deletions core/shared/src/main/scala/eu/timepit/refined/auto.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package eu.timepit.refined

import eu.timepit.refined.api.{ Refined, RefType, Validate }
import eu.timepit.refined.api.{ Refined, RefinedType, RefType }
import eu.timepit.refined.api.Inference.==>
import eu.timepit.refined.macros.{ InferMacro, RefineMacro }
import shapeless.tag.@@
Expand Down Expand Up @@ -52,8 +52,8 @@ object auto {
*/
implicit def autoRefineV[T, P](t: T)(
implicit
rt: RefType[Refined], v: Validate[T, P]
): Refined[T, P] = macro RefineMacro.impl[Refined, T, P]
rt: RefinedType.AuxT[Refined[T, P], T]
): Refined[T, P] = macro RefineMacro.refineImpl[Refined[T, P], T, P]

/**
* Implicitly tags (at compile-time) a value of type `T` with `P` if `t`
Expand All @@ -64,6 +64,6 @@ object auto {
*/
implicit def autoRefineT[T, P](t: T)(
implicit
rt: RefType[@@], v: Validate[T, P]
): T @@ P = macro RefineMacro.impl[@@, T, P]
rt: RefinedType.AuxT[T @@ P, T]
): T @@ P = macro RefineMacro.refineImpl[T @@ P, T, P]
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package eu.timepit.refined
package internal

import eu.timepit.refined.api.{ RefType, Validate }
import eu.timepit.refined.api.RefinedType

/**
* Helper class that allows the types `F`, `T`, and `P` to be inferred
Expand All @@ -12,8 +12,8 @@ import eu.timepit.refined.api.{ RefType, Validate }
*/
final class ApplyRefMPartiallyApplied[FTP] {

def apply[F[_, _], T, P](t: T)(
def apply[T](t: T)(
implicit
ev: F[T, P] =:= FTP, rt: RefType[F], v: Validate[T, P]
): FTP = macro macros.RefineMacro.implApplyRef[FTP, F, T, P]
rt: RefinedType.AuxT[FTP, T]
): FTP = macro macros.RefineMacro.refineImpl[FTP, T, rt.P]
}
Loading