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

Add RuntimeConstraint #175

Merged
merged 9 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions cats/src/io/github/iltotore/iron/cats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ object cats extends IronCatsInstances:
* @return a [[Right]] containing this value as [[IronType]] or a [[Left]] containing the constraint message.
* @see [[either]], [[eitherNel]].
*/
inline def eitherNec(value: A)(using inline c: Constraint[A, C]): EitherNec[String, T] = value.refineNec[C].map(_.asInstanceOf[T])
def eitherNec(value: A): EitherNec[String, T] = ops.either(value).toEitherNec

/**
* Refine the given value at runtime, resulting in an [[EitherNel]].
Expand All @@ -136,7 +136,7 @@ object cats extends IronCatsInstances:
* @return a [[Right]] containing this value as [[IronType]] or a [[Left]] containing the constraint message.
* @see [[either]], [[eitherNec]].
*/
inline def eitherNel(value: A)(using inline c: Constraint[A, C]): EitherNel[String, T] = value.refineNel[C].map(_.asInstanceOf[T])
def eitherNel(value: A): EitherNel[String, T] = ops.either(value).toEitherNel

/**
* Refine the given value at runtime, resulting in a [[Validated]].
Expand All @@ -145,7 +145,8 @@ object cats extends IronCatsInstances:
* @return a [[Valid]] containing this value as [[IronType]] or an [[Invalid]] containing the constraint message.
* @see [[validatedNec]], [[validatedNel]].
*/
inline def validated(value: A)(using inline c: Constraint[A, C]): Validated[String, T] = value.refineValidated[C].map(_.asInstanceOf[T])
def validated(value: A): Validated[String, T] =
if ops.rtc.test(value) then Validated.valid(value.asInstanceOf[T]) else Validated.invalid(ops.rtc.message)

/**
* Refine the given value applicatively at runtime, resulting in a [[ValidatedNec]].
Expand All @@ -154,8 +155,8 @@ object cats extends IronCatsInstances:
* @return a [[Valid]] containing this value as [[IronType]] or an [[Invalid]] containing a [[NonEmptyChain]] of error messages.
* @see [[validated]], [[validatedNel]].
*/
inline def validatedNec(value: A)(using inline c: Constraint[A, C]): ValidatedNec[String, T] =
value.refineValidatedNec[C].map(_.asInstanceOf[T])
def validatedNec(value: A): ValidatedNec[String, T] =
if ops.rtc.test(value) then Validated.validNec(value.asInstanceOf[T]) else Validated.invalidNec(ops.rtc.message)

/**
* Refine the given value applicatively at runtime, resulting in a [[ValidatedNel]].
Expand All @@ -164,15 +165,15 @@ object cats extends IronCatsInstances:
* @return a [[Valid]] containing this value as [[IronType]] or an [[Invalid]] containing a [[NonEmptyList]] of error messages.
* @see [[validated]], [[validatedNec]].
*/
inline def validatedNel(value: A)(using inline c: Constraint[A, C]): ValidatedNel[String, T] =
value.refineValidatedNel[C].map(_.asInstanceOf[T])
def validatedNel(value: A): ValidatedNel[String, T] =
if ops.rtc.test(value) then Validated.validNel(value.asInstanceOf[T]) else Validated.invalidNel(ops.rtc.message)

/**
* Represent all Cats' typeclass instances for Iron.
*/
private trait IronCatsInstances extends IronCatsLowPriority, RefinedTypeOpsCats:

//The `NotGiven` implicit parameter is mandatory to avoid ambiguous implicit error when both Eq[A] and Hash[A]/PartialOrder[A] exist
// The `NotGiven` implicit parameter is mandatory to avoid ambiguous implicit error when both Eq[A] and Hash[A]/PartialOrder[A] exist
inline given [A, C](using inline ev: Eq[A], notHashOrOrder: NotGiven[Hash[A] | PartialOrder[A]]): Eq[A :| C] = ev.asInstanceOf[Eq[A :| C]]

inline given [A, C](using inline ev: PartialOrder[A], notOrder: NotGiven[Order[A]]): PartialOrder[A :| C] = ev.asInstanceOf[PartialOrder[A :| C]]
Expand Down Expand Up @@ -226,14 +227,14 @@ private trait IronCatsLowPriority:

private trait RefinedTypeOpsCats extends RefinedTypeOpsCatsLowPriority:

inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Eq[mirror.IronType]): Eq[T] = ev.asInstanceOf[Eq[T]]
inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Eq[mirror.IronType]): Eq[T] = ev.asInstanceOf[Eq[T]]

inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Order[mirror.IronType]): Order[T] = ev.asInstanceOf[Order[T]]
inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Order[mirror.IronType]): Order[T] = ev.asInstanceOf[Order[T]]

inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Show[mirror.IronType]): Show[T] = ev.asInstanceOf[Show[T]]
inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Show[mirror.IronType]): Show[T] = ev.asInstanceOf[Show[T]]

inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: PartialOrder[mirror.IronType]): PartialOrder[T] = ev.asInstanceOf[PartialOrder[T]]
inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: PartialOrder[mirror.IronType]): PartialOrder[T] = ev.asInstanceOf[PartialOrder[T]]

private trait RefinedTypeOpsCatsLowPriority:

inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Hash[mirror.IronType]): Hash[T] = ev.asInstanceOf[Hash[T]]
inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Hash[mirror.IronType]): Hash[T] = ev.asInstanceOf[Hash[T]]
33 changes: 14 additions & 19 deletions main/src/io/github/iltotore/iron/RefinedTypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,13 @@ object RefinedTypeOps:
*/
type FinalType = T

trait RefinedTypeOpsImpl[A, C, T]:
trait RefinedTypeOpsImpl[A, C, T](using val rtc: RuntimeConstraint[A, C]):
inline protected given RuntimeConstraint[A, C] = rtc

/**
* Implicitly refine at compile-time the given value.
*
* @param value the value to refine.
* @param constraint the implementation of `C` to check.
* @tparam A the refined type.
* @tparam C the constraint applied to the type.
* @return the given value typed as [[IronType]]
Expand All @@ -67,17 +68,16 @@ trait RefinedTypeOpsImpl[A, C, T]:
* @return a constrained value, without performing constraint checks.
* @see [[apply]], [[applyUnsafe]].
*/
inline def assume(value: A): T = value.assume[C].asInstanceOf[T]
inline def assume(value: A): T = value.asInstanceOf[T]

/**
* Refine the given value at runtime, resulting in an [[Either]].
*
* @param constraint the constraint to test with the value to refine.
* @return a [[Right]] containing this value as [[T]] or a [[Left]] containing the constraint message.
* @see [[fromIronType]], [[option]], [[applyUnsafe]].
*/
inline def either(value: A)(using constraint: Constraint[A, C]): Either[String, T] =
Either.cond(constraint.test(value), value.asInstanceOf[T], constraint.message)
def either(value: A): Either[String, T] =
Either.cond(rtc.test(value), value.asInstanceOf[T], rtc.message)

/**
* Refine the given value at runtime, resulting in an [[Option]].
Expand All @@ -86,34 +86,29 @@ trait RefinedTypeOpsImpl[A, C, T]:
* @return an Option containing this value as [[T]] or [[None]].
* @see [[fromIronType]], [[either]], [[applyUnsafe]].
*/
inline def option(value: A)(using constraint: Constraint[A, C]): Option[T] =
Option.when(constraint.test(value))(value.asInstanceOf[T])
def option(value: A): Option[T] =
Option.when(rtc.test(value))(value.asInstanceOf[T])

/**
* Refine the given value at runtime.
*
* @param constraint the constraint to test with the value to refine.
* @return this value as [[T]].
* @throws an [[IllegalArgumentException]] if the constraint is not satisfied.
* @see [[fromIronType]], [[either]], [[option]].
*/
inline def applyUnsafe(value: A)(using Constraint[A, C]): T =
value.refine[C].asInstanceOf[T]
inline def applyUnsafe(value: A): T =
if rtc.test(value) then value.asInstanceOf[T] else throw new IllegalArgumentException(rtc.message)
Iltotore marked this conversation as resolved.
Show resolved Hide resolved

inline def unapply(value: T): Option[A :| C] = Some(value.asInstanceOf[A :| C])
def unapply(value: T): Option[A :| C] = Some(value.asInstanceOf[A :| C])

inline given RefinedTypeOps.Mirror[T] with
override type BaseType = A
override type ConstraintType = C

inline given [R]: TypeTest[T, R] = summonInline[TypeTest[A :| C, R]].asInstanceOf[TypeTest[T, R]]

inline given [L](using inline constraint: Constraint[A, C]): TypeTest[L, T] =
val test = summonInline[TypeTest[L, A]]

new TypeTest:
override def unapply(value: L): Option[value.type & T] =
test.unapply(value).filter(constraint.test(_)).asInstanceOf
given [L](using test: TypeTest[L, A]): TypeTest[L, T] with
override def unapply(value: L): Option[value.type & T] = test.unapply(value).filter(rtc.test(_)).asInstanceOf

extension (wrapper: T)
inline def value: IronType[A, C] = wrapper.asInstanceOf[IronType[A, C]]
inline def value: IronType[A, C] = wrapper.asInstanceOf[IronType[A, C]]
10 changes: 10 additions & 0 deletions main/src/io/github/iltotore/iron/RuntimeConstraint.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.github.iltotore.iron

import scala.util.NotGiven

final class RuntimeConstraint[A, C](_test: A => Boolean, val message: String):
inline def test(value: A): Boolean = _test(value)

object RuntimeConstraint:
inline given derived[A, C](using inline c: Constraint[A, C]): RuntimeConstraint[A, C] =
new RuntimeConstraint[A, C](c.test(_), c.message)
17 changes: 7 additions & 10 deletions zio/src/io/github/iltotore/iron/zio.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package io.github.iltotore.iron

import _root_.zio.NonEmptyChunk
import _root_.zio.prelude.{Debug, Equal, Hash, Ord, PartialOrd, Validation}
import _root_.zio.prelude.{Debug, Equal, Hash, Ord, Validation}

object zio extends RefinedTypeOpsZio:

extension [A](value: A)

/**
* Refine the given value applicatively at runtime, resulting in a [[Validation]].
*
Expand All @@ -16,9 +15,7 @@ object zio extends RefinedTypeOpsZio:
inline def refineValidation[C](using inline constraint: Constraint[A, C]): Validation[String, A :| C] =
Validation.fromPredicateWith(constraint.message)(value.asInstanceOf[A :| C])(constraint.test(_))


extension [A, C1](value: A :| C1)

/**
* Refine the given value again applicatively at runtime, resulting in a [[Validation]].
*
Expand All @@ -35,17 +32,17 @@ object zio extends RefinedTypeOpsZio:
* @param constraint the constraint to test with the value to refine.
* @return a [[Valid]] containing this value as [[T]] or an [[Validation.Failure]] containing a [[NonEmptyChunk]] of error messages.
*/
inline def validation(value: A)(using inline constraint: Constraint[A, C]): Validation[String, T] =
value.refineValidation[C].map(_.asInstanceOf[T])
def validation(value: A): Validation[String, T] =
Validation.fromPredicateWith(ops.rtc.message)(value)(ops.rtc.test(_)).asInstanceOf[Validation[String, T]]

private trait RefinedTypeOpsZio extends RefinedTypeOpsZioLowPriority:

inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Debug[mirror.IronType]): Debug[T] = ev.asInstanceOf[Debug[T]]
inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Debug[mirror.IronType]): Debug[T] = ev.asInstanceOf[Debug[T]]

inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Equal[mirror.IronType]): Equal[T] = ev.asInstanceOf[Equal[T]]
inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Equal[mirror.IronType]): Equal[T] = ev.asInstanceOf[Equal[T]]

inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Ord[mirror.IronType]): Ord[T] = ev.asInstanceOf[Ord[T]]
inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Ord[mirror.IronType]): Ord[T] = ev.asInstanceOf[Ord[T]]

private trait RefinedTypeOpsZioLowPriority:

inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Hash[mirror.IronType]): Hash[T] = ev.asInstanceOf[Hash[T]]
inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Hash[mirror.IronType]): Hash[T] = ev.asInstanceOf[Hash[T]]
Loading