diff --git a/modules/core/shared/src/main/scala-3.0+/eu/timepit/refined/internal/WitnessAs.scala b/modules/core/shared/src/main/scala-3.0+/eu/timepit/refined/internal/WitnessAs.scala index 29b4e9a19..0011833a0 100644 --- a/modules/core/shared/src/main/scala-3.0+/eu/timepit/refined/internal/WitnessAs.scala +++ b/modules/core/shared/src/main/scala-3.0+/eu/timepit/refined/internal/WitnessAs.scala @@ -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 @@ -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)) + } } diff --git a/modules/core/shared/src/main/scala-3.0+/eu/timepit/refined/numeric.scala b/modules/core/shared/src/main/scala-3.0+/eu/timepit/refined/numeric.scala index f6de5fcf8..465ecf545 100644 --- a/modules/core/shared/src/main/scala-3.0+/eu/timepit/refined/numeric.scala +++ b/modules/core/shared/src/main/scala-3.0+/eu/timepit/refined/numeric.scala @@ -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 diff --git a/modules/core/shared/src/main/scala-3.0-/eu/timepit/refined/internal/WitnessAs.scala b/modules/core/shared/src/main/scala-3.0-/eu/timepit/refined/internal/WitnessAs.scala index 6a8308ea1..9ebc52ec9 100644 --- a/modules/core/shared/src/main/scala-3.0-/eu/timepit/refined/internal/WitnessAs.scala +++ b/modules/core/shared/src/main/scala-3.0-/eu/timepit/refined/internal/WitnessAs.scala @@ -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 @@ -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)) +} diff --git a/modules/core/shared/src/test/scala-3.0-/eu/timepit/refined/NumericValidateSpec.scala b/modules/core/shared/src/test/scala-3.0-/eu/timepit/refined/NumericValidateSpec.scala index c84c22c15..32c83aac2 100644 --- a/modules/core/shared/src/test/scala-3.0-/eu/timepit/refined/NumericValidateSpec.scala +++ b/modules/core/shared/src/test/scala-3.0-/eu/timepit/refined/NumericValidateSpec.scala @@ -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)" } @@ -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) } @@ -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)" @@ -184,5 +202,4 @@ class NumericValidateSpec extends Properties("NumericValidate") { property("NonNaN.Double.showExpr") = secure { showExpr[NonNaN](Double.NaN) ?= "(NaN != NaN)" } - }