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

Allow Int and Double literals as universal arguments #841

Merged
merged 9 commits into from
Sep 26, 2020
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
package eu.timepit.refined.internal

import scala.compiletime.{constValue, error}
import shapeless.{Nat, Witness}

/**
* `WitnessAs[A, B]` provides the singleton value of type `A` in `fst`
* and `fst` converted to type `B` in `snd`.
*
* The purpose of this type class is to write numeric type class
* instances that work with both literal singleton types and
* `shapeless.Nat`.
* The purpose of this type class is to allow literals of other
* types than the base type to be used as arguments in numeric
* predicates.
*
* Example: {{{
* scala> import shapeless.nat._5
* scala> import eu.timepit.refined.refineV
* | import eu.timepit.refined.api.Refined
* | import eu.timepit.refined.numeric.{Greater, Less}
*
* scala> WitnessAs[5, Int]
* res1: WitnessAs[5, Int] = WitnessAs(5,5)
* scala> refineV[Greater[2.718]](BigDecimal(3.141))
* res0: Either[String, BigDecimal Refined Greater[2.718]] = Right(3.141)
*
* scala> WitnessAs[_5, Int]
* res2: WitnessAs[_5, Int] = WitnessAs(Succ(),5)
* scala> refineV[Less[1]](0.618)
* res1: Either[String, Double Refined Less[1]] = Right(0.618)
* }}}
*/
final case class WitnessAs[A, B](fst: A, snd: B)

object WitnessAs {
object WitnessAs extends WitnessAs1 {
def apply[A, B](implicit ev: WitnessAs[A, B]): WitnessAs[A, B] = ev

implicit def natWitnessAs[B, A <: Nat](implicit
Expand All @@ -32,8 +35,58 @@ object WitnessAs {
): WitnessAs[A, B] =
WitnessAs(wa.value, nb.fromInt(ta.apply()))

implicit def singletonWitnessAs[B, A <: B](implicit
wa: ValueOf[A]
): WitnessAs[A, B] =
WitnessAs(wa.value, wa.value)
inline given singletonWitnessAs[B, A <: B] as WitnessAs[A, B] = {
inline val a = constValue[A]
WitnessAs(a, a)
}
}

trait WitnessAs1 {
inline given intWitnessAsByte[A <: Int] as WitnessAs[A, Byte] =
inline constValue[A] match {
case a if a >= -128 && a <= 127 => WitnessAs(a, a.toByte)
case a => error(s"WitnessAs: $a is not in [Byte.MinValue, Byte.MaxValue]")
}

inline given intWitnessAsShort[A <: Int] as WitnessAs[A, Short] =
inline constValue[A] match {
case a if a >= -32768 && a <= 32767 => WitnessAs(a, a.toShort)
case a => error(s"WitnessAs: $a is not in [Short.MinValue, Short.MaxValue]")
}

inline given intWitnessAsLong[A <: Int] as WitnessAs[A, Long] = {
inline val a = constValue[A]
WitnessAs(a, a.toLong)
}

inline given intWitnessAsBigInt[A <: Int] as WitnessAs[A, BigInt] = {
inline val a = constValue[A]
WitnessAs(a, BigInt(a))
}

inline given intWitnessAsFloat[A <: Int] as WitnessAs[A, Float] = {
inline val a = constValue[A]
WitnessAs(a, a.toFloat)
}

inline given intWitnessAsDouble[A <: Int] as WitnessAs[A, Double] = {
inline val a = constValue[A]
WitnessAs(a, a.toDouble)
}

inline given intWitnessAsBigDecimal[A <: Int] as WitnessAs[A, BigDecimal] = {
inline val a = constValue[A]
WitnessAs(a, BigDecimal(a))
}

inline given doubleWitnessAsFloat[A <: Double] as WitnessAs[A, Float] =
inline constValue[A] match {
case a if a >= -3.4028235E38 && a <= 3.4028235E38 => WitnessAs(a, a.toFloat)
case a => error(s"WitnessAs: $a is not in [Float.MinValue, Float.MaxValue]")
}

inline given doubleWitnessAsBigDecimal[A <: Double] as WitnessAs[A, BigDecimal] = {
inline val a = constValue[A]
WitnessAs(a, BigDecimal(a))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package eu.timepit.refined
import eu.timepit.refined.api.{Inference, Validate}
import eu.timepit.refined.api.Inference.==>
import eu.timepit.refined.boolean.{And, Not}
import eu.timepit.refined.internal.ToInt
import eu.timepit.refined.internal.WitnessAs
import eu.timepit.refined.numeric._
import shapeless.Nat
import shapeless.nat.{_0, _2}
import shapeless.ops.nat.ToInt

/**
* Module for numeric predicates. Predicates that take type parameters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,25 @@ import shapeless.ops.nat.ToInt
* `WitnessAs[A, B]` provides the singleton value of type `A` in `fst`
* and `fst` converted to type `B` in `snd`.
*
* The purpose of this type class is to write numeric type class
* instances that work with both literal singleton types and
* `shapeless.Nat`.
* The purpose of this type class is to allow literals of other
* types than the base type to be used as arguments in numeric
* predicates.
*
* Example: {{{
* scala> import eu.timepit.refined.W
* | import shapeless.nat._5
* scala> import eu.timepit.refined.{refineV, W}
* | import eu.timepit.refined.api.Refined
* | import eu.timepit.refined.numeric.{Greater, Less}
*
* scala> WitnessAs[W.`5`.T, Int]
* res1: WitnessAs[W.`5`.T, Int] = WitnessAs(5,5)
* scala> refineV[Greater[W.`2.718`.T]](BigDecimal(3.141))
* res0: Either[String, BigDecimal Refined Greater[W.`2.718`.T]] = Right(3.141)
*
* scala> WitnessAs[_5, Int]
* res2: WitnessAs[_5, Int] = WitnessAs(Succ(),5)
* scala> refineV[Less[W.`1`.T]](0.618)
* res1: Either[String, Double Refined Less[W.`1`.T]] = Right(0.618)
* }}}
*/
final case class WitnessAs[A, B](fst: A, snd: B)

object WitnessAs {
object WitnessAs extends WitnessAs1 {
def apply[A, B](implicit ev: WitnessAs[A, B]): WitnessAs[A, B] = ev

implicit def natWitnessAs[B, A <: Nat](implicit
Expand All @@ -39,3 +40,56 @@ object WitnessAs {
): WitnessAs[A, B] =
WitnessAs(wa.value, wa.value)
}

trait WitnessAs1 {
implicit def intWitnessAsByte[A <: Int](implicit
wa: Witness.Aux[A]
): WitnessAs[A, Byte] =
if (wa.value >= Byte.MinValue.toInt && wa.value <= Byte.MaxValue.toInt)
WitnessAs(wa.value, wa.value.toByte)
else sys.error(s"WitnessAs: ${wa.value} is not in [Byte.MinValue, Byte.MaxValue]")

implicit def intWitnessAsShort[A <: Int](implicit
wa: Witness.Aux[A]
): WitnessAs[A, Short] =
if (wa.value >= Short.MinValue.toInt && wa.value <= Short.MaxValue.toInt)
WitnessAs(wa.value, wa.value.toShort)
else sys.error(s"WitnessAs: ${wa.value} is not in [Short.MinValue, Short.MaxValue]")

implicit def intWitnessAsLong[A <: Int](implicit
wa: Witness.Aux[A]
): WitnessAs[A, Long] =
WitnessAs(wa.value, wa.value.toLong)

implicit def intWitnessAsBigInt[A <: Int](implicit
wa: Witness.Aux[A]
): WitnessAs[A, BigInt] =
WitnessAs(wa.value, BigInt(wa.value))

implicit def intWitnessAsFloat[A <: Int](implicit
wa: Witness.Aux[A]
): WitnessAs[A, Float] =
WitnessAs(wa.value, wa.value.toFloat)

implicit def intWitnessAsDouble[A <: Int](implicit
wa: Witness.Aux[A]
): WitnessAs[A, Double] =
WitnessAs(wa.value, wa.value.toDouble)

implicit def intWitnessAsBigDecimal[A <: Int](implicit
wa: Witness.Aux[A]
): WitnessAs[A, BigDecimal] =
WitnessAs(wa.value, BigDecimal(wa.value))

implicit def doubleWitnessAsFloat[A <: Double](implicit
wa: Witness.Aux[A]
): WitnessAs[A, Float] =
if (wa.value >= Float.MinValue.toDouble && wa.value <= Float.MaxValue.toDouble)
WitnessAs(wa.value, wa.value.toFloat)
else sys.error(s"WitnessAs: ${wa.value} is not in [Float.MinValue, Float.MaxValue]")

implicit def doubleWitnessAsBigDecimal[A <: Double](implicit
wa: Witness.Aux[A]
): WitnessAs[A, BigDecimal] =
WitnessAs(wa.value, BigDecimal(wa.value))
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,19 @@ import shapeless.nat._

class NumericValidateSpec extends Properties("NumericValidate") {

property("Less.isValid") = forAll((d: Double) => isValid[Less[W.`1.0`.T]](d) ?= (d < 1.0))
property("isValid[Less[1]](d: Double)") = forAll { (d: Double) =>
isValid[Less[W.`1`.T]](d) ?= (d < 1)
}

property("isValid[Less[1.0]](d: Double)") = forAll { (d: Double) =>
isValid[Less[W.`1.0`.T]](d) ?= (d < 1.0)
}

property("isValid[Less[1.0]](d: BigDecimal)") = forAll { (d: BigDecimal) =>
isValid[Less[W.`1.0`.T]](d) ?= (d < 1.0)
}

property("Less.showExpr") = secure {
property("showExpr[Less[1.1]](0.1)") = secure {
showExpr[Less[W.`1.1`.T]](0.1) ?= "(0.1 < 1.1)"
}

Expand All @@ -30,7 +40,11 @@ class NumericValidateSpec extends Properties("NumericValidate") {
showResult[Less[_5]](i) ?= showResult[Less[W.`5`.T]](i)
}

property("Greater.isValid") = forAll { (d: Double) =>
property("isValid[Greater[1.0]](b: Byte)") = forAll { (b: Byte) =>
isValid[Greater[W.`1`.T]](b) ?= (b > 1)
}

property("isValid[Greater[1.0]](d: Double)") = forAll { (d: Double) =>
isValid[Greater[W.`1.0`.T]](d) ?= (d > 1.0)
}

Expand Down Expand Up @@ -118,13 +132,17 @@ class NumericValidateSpec extends Properties("NumericValidate") {
showExpr[NonDivisible[_2]](4) ?= "!(4 % 2 == 0)"
}

property("Even.isValid") = forAll((i: Int) => isValid[Even](i) ?= (i % 2 == 0))
property("Even.isValid") = forAll { (i: Int) =>
isValid[Even](i) ?= (i % 2 == 0)
}

property("Even.showExpr") = secure {
showExpr[Even](4) ?= "(4 % 2 == 0)"
}

property("Odd.isValid") = forAll((i: Int) => isValid[Odd](i) ?= (i % 2 != 0))
property("Odd.isValid") = forAll { (i: Int) =>
isValid[Odd](i) ?= (i % 2 != 0)
}

property("Odd.showExpr") = secure {
showExpr[Odd](4) ?= "!(4 % 2 == 0)"
Expand Down Expand Up @@ -184,5 +202,4 @@ class NumericValidateSpec extends Properties("NumericValidate") {
property("NonNaN.Double.showExpr") = secure {
showExpr[NonNaN](Double.NaN) ?= "(NaN != NaN)"
}

}