Skip to content

Commit

Permalink
feat: Better compile time diagnostics (#239)
Browse files Browse the repository at this point in the history
Closes #227
  • Loading branch information
Iltotore authored Jun 6, 2024
1 parent fefe057 commit 623835e
Show file tree
Hide file tree
Showing 6 changed files with 407 additions and 151 deletions.
46 changes: 31 additions & 15 deletions main/src/io/github/iltotore/iron/constraint/char.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.github.iltotore.iron.constraint
import io.github.iltotore.iron.Constraint
import io.github.iltotore.iron.compileTime.*
import io.github.iltotore.iron.constraint.any.Not
import io.github.iltotore.iron.macros.reflectUtil

import scala.quoted.*

Expand Down Expand Up @@ -47,9 +48,12 @@ object char:
override inline def message: String = "Should be a whitespace"

private def check(expr: Expr[Char])(using Quotes): Expr[Boolean] =
expr.value match
case Some(value) => Expr(value.isWhitespace)
case None => '{ $expr.isWhitespace }
val rflUtil = reflectUtil
import rflUtil.*

expr.decode match
case Right(value) => Expr(value.isWhitespace)
case _ => '{ $expr.isWhitespace }

object LowerCase:

Expand All @@ -60,9 +64,12 @@ object char:
override inline def message: String = "Should be a lower cased"

private def check(expr: Expr[Char])(using Quotes): Expr[Boolean] =
expr.value match
case Some(value) => Expr(value.isLower)
case None => '{ $expr.isLower }
val rflUtil = reflectUtil
import rflUtil.*

expr.decode match
case Right(value) => Expr(value.isLower)
case _ => '{ $expr.isLower }

object UpperCase:

Expand All @@ -73,9 +80,12 @@ object char:
override inline def message: String = "Should be a upper cased"

private def check(expr: Expr[Char])(using Quotes): Expr[Boolean] =
expr.value match
case Some(value) => Expr(value.isUpper)
case None => '{ $expr.isUpper }
val rflUtil = reflectUtil
import rflUtil.*

expr.decode match
case Right(value) => Expr(value.isUpper)
case _ => '{ $expr.isUpper }

object Digit:

Expand All @@ -86,9 +96,12 @@ object char:
override inline def message: String = "Should be a digit"

private def check(expr: Expr[Char])(using Quotes): Expr[Boolean] =
expr.value match
case Some(value) => Expr(value.isDigit)
case None => '{ $expr.isDigit }
val rflUtil = reflectUtil
import rflUtil.*

expr.decode match
case Right(value) => Expr(value.isDigit)
case _ => '{ $expr.isDigit }

object Letter:

Expand All @@ -99,6 +112,9 @@ object char:
override inline def message: String = "Should be a letter"

private def check(expr: Expr[Char])(using Quotes): Expr[Boolean] =
expr.value match
case Some(value) => Expr(value.isLetter)
case None => '{ $expr.isLetter }
val rflUtil = reflectUtil
import rflUtil.*

expr.decode match
case Right(value) => Expr(value.isLetter)
case _ => '{ $expr.isLetter }
77 changes: 43 additions & 34 deletions main/src/io/github/iltotore/iron/constraint/collection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.github.iltotore.iron.{:|, ==>, Constraint, Implication}
import io.github.iltotore.iron.compileTime.*
import io.github.iltotore.iron.constraint.any.{DescribedAs, StrictEqual}
import io.github.iltotore.iron.constraint.numeric.{GreaterEqual, LessEqual}
import io.github.iltotore.iron.macros.reflectUtil

import scala.compiletime.{constValue, summonInline}
import scala.compiletime.ops.string.Length
Expand Down Expand Up @@ -111,13 +112,12 @@ object collection:
inline given lengthString[C, Impl <: Constraint[Int, C]](using inline impl: Impl): LengthString[C, Impl] = new LengthString

private def checkString[C, Impl <: Constraint[Int, C]](expr: Expr[String], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] =
val rflUtil = reflectUtil
import rflUtil.*

import quotes.reflect.*

expr.value match
case Some(value) => applyConstraint(Expr(value.length), constraintExpr)

case None => applyConstraint('{ $expr.length }, constraintExpr)
expr.decode match
case Right(value) => applyConstraint(Expr(value.length), constraintExpr)
case _ => applyConstraint('{ $expr.length }, constraintExpr)

given [C1, C2](using C1 ==> C2): (Length[C1] ==> Length[C2]) = Implication()

Expand All @@ -135,8 +135,11 @@ object collection:
override inline def message: String = "Should contain the string " + constValue[V]

private def checkString(expr: Expr[String], partExpr: Expr[String])(using Quotes): Expr[Boolean] =
(expr.value, partExpr.value) match
case (Some(value), Some(part)) => Expr(value.contains(part))
val rflUtil = reflectUtil
import rflUtil.*

(expr.decode, partExpr.decode) match
case (Right(value), Right(part)) => Expr(value.contains(part))
case _ => '{ ${ expr }.contains($partExpr) }

object ForAll:
Expand All @@ -159,17 +162,17 @@ object collection:
inline given forAllString[C, Impl <: Constraint[Char, C]](using inline impl: Impl): ForAllString[C, Impl] = new ForAllString

private def checkString[C, Impl <: Constraint[Char, C]](expr: Expr[String], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] =
val rflUtil = reflectUtil
import rflUtil.*

import quotes.reflect.*

expr.value match
case Some(value) =>
expr.decode match
case Right(value) =>
value
.map(Expr.apply)
.map(applyConstraint(_, constraintExpr))
.foldLeft(Expr(true))((e, t) => '{ $e && $t })

case None => '{ $expr.forallOptimized(c => ${ applyConstraint('c, constraintExpr) }) }
case _ => '{ $expr.forallOptimized(c => ${ applyConstraint('c, constraintExpr) }) }

given [C1, C2](using C1 ==> C2): (ForAll[C1] ==> Exists[C2]) = Implication()
given [C1, C2](using C1 ==> C2): (ForAll[C1] ==> Last[C2]) = Implication()
Expand All @@ -196,18 +199,18 @@ object collection:
inline given initString[C, Impl <: Constraint[Char, C]](using inline impl: Impl): InitString[C, Impl] = new InitString

private def checkString[C, Impl <: Constraint[Char, C]](expr: Expr[String], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] =
val rflUtil = reflectUtil
import rflUtil.*

import quotes.reflect.*

expr.value match
case Some(value) =>
expr.decode match
case Right(value) =>
value
.init
.map(Expr.apply)
.map(applyConstraint(_, constraintExpr))
.foldLeft(Expr(true))((e, t) => '{ $e && $t })

case None => '{ $expr.init.forallOptimized(c => ${ applyConstraint('c, constraintExpr) }) }
case _ => '{ $expr.init.forallOptimized(c => ${ applyConstraint('c, constraintExpr) }) }

given [C1, C2](using C1 ==> C2): (Init[C1] ==> Exists[C2]) = Implication()

Expand All @@ -233,18 +236,18 @@ object collection:
inline given tailString[C, Impl <: Constraint[Char, C]](using inline impl: Impl): TailString[C, Impl] = new TailString

private def checkString[C, Impl <: Constraint[Char, C]](expr: Expr[String], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] =
val rflUtil = reflectUtil
import rflUtil.*

import quotes.reflect.*

expr.value match
case Some(value) =>
expr.decode match
case Right(value) =>
value
.tail
.map(Expr.apply)
.map(applyConstraint(_, constraintExpr))
.foldLeft(Expr(true))((e, t) => '{ $e && $t })

case None => '{ $expr.tail.forallOptimized(c => ${ applyConstraint('c, constraintExpr) }) }
case _ => '{ $expr.tail.forallOptimized(c => ${ applyConstraint('c, constraintExpr) }) }

given [C1, C2](using C1 ==> C2): (Tail[C1] ==> Exists[C2]) = Implication()
given [C1, C2](using C1 ==> C2): (Tail[C1] ==> Last[C2]) = Implication()
Expand All @@ -269,17 +272,17 @@ object collection:
inline given existsString[C, Impl <: Constraint[Char, C]](using inline impl: Impl): ExistsString[C, Impl] = new ExistsString

private def checkString[C, Impl <: Constraint[Char, C]](expr: Expr[String], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] =
val rflUtil = reflectUtil
import rflUtil.*

import quotes.reflect.*

expr.value match
case Some(value) =>
expr.decode match
case Right(value) =>
value
.map(Expr.apply)
.map(applyConstraint(_, constraintExpr))
.foldLeft(Expr(false))((e, t) => '{ $e || $t })

case None => '{ $expr.existsOptimized(c => ${ applyConstraint('c, constraintExpr) }) }
case _ => '{ $expr.existsOptimized(c => ${ applyConstraint('c, constraintExpr) }) }

object Head:

Expand All @@ -301,12 +304,15 @@ object collection:
inline given headString[C, Impl <: Constraint[Char, C]](using inline impl: Impl): HeadString[C, Impl] = new HeadString

private def checkString[C, Impl <: Constraint[Char, C]](expr: Expr[String], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] =
expr.value match
case Some(value) =>
val rflUtil = reflectUtil
import rflUtil.*

expr.decode match
case Right(value) =>
value.headOption match
case Some(head) => applyConstraint(Expr(head), constraintExpr)
case None => Expr(false)
case None => '{ $expr.headOption.exists(head => ${ applyConstraint('{ head }, constraintExpr) }) }
case _ => '{ $expr.headOption.exists(head => ${ applyConstraint('{ head }, constraintExpr) }) }

given [C1, C2](using C1 ==> C2): (Head[C1] ==> Exists[C2]) = Implication()

Expand All @@ -330,12 +336,15 @@ object collection:
inline given lastString[C, Impl <: Constraint[Char, C]](using inline impl: Impl): LastString[C, Impl] = new LastString

private def checkString[C, Impl <: Constraint[Char, C]](expr: Expr[String], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] =
expr.value match
case Some(value) =>
val rflUtil = reflectUtil
import rflUtil.*

expr.decode match
case Right(value) =>
value.lastOption match
case Some(last) => applyConstraint(Expr(last), constraintExpr)
case None => Expr(false)
case None => '{ $expr.lastOption.exists(last => ${ applyConstraint('{ last }, constraintExpr) }) }
case _ => '{ $expr.lastOption.exists(last => ${ applyConstraint('{ last }, constraintExpr) }) }

given [C1, C2](using C1 ==> C2): (Last[C1] ==> Exists[C2]) = Implication()

Expand Down
22 changes: 16 additions & 6 deletions main/src/io/github/iltotore/iron/constraint/string.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.github.iltotore.iron.constraint.any.*
import io.github.iltotore.iron.constraint.collection.*
import io.github.iltotore.iron.compileTime.*
import io.github.iltotore.iron.constraint.char.{Digit, Letter, LowerCase, UpperCase, Whitespace}
import io.github.iltotore.iron.macros.reflectUtil

import scala.compiletime.constValue
import scala.quoted.*
Expand Down Expand Up @@ -100,8 +101,11 @@ object string:
override inline def message: String = "Should start with " + stringValue[V]

private def check(expr: Expr[String], prefixExpr: Expr[String])(using Quotes): Expr[Boolean] =
(expr.value, prefixExpr.value) match
case (Some(value), Some(prefix)) => Expr(value.startsWith(prefix))
val rflUtil = reflectUtil
import rflUtil.*

(expr.decode, prefixExpr.decode) match
case (Right(value), Right(prefix)) => Expr(value.startsWith(prefix))
case _ => '{ $expr.startsWith($prefixExpr) }

object EndWith:
Expand All @@ -113,8 +117,11 @@ object string:
override inline def message: String = "Should end with " + stringValue[V]

private def check(expr: Expr[String], prefixExpr: Expr[String])(using Quotes): Expr[Boolean] =
(expr.value, prefixExpr.value) match
case (Some(value), Some(prefix)) => Expr(value.endsWith(prefix))
val rflUtil = reflectUtil
import rflUtil.*

(expr.decode, prefixExpr.decode) match
case (Right(value), Right(prefix)) => Expr(value.endsWith(prefix))
case _ => '{ $expr.endsWith($prefixExpr) }

object Match:
Expand All @@ -126,6 +133,9 @@ object string:
override inline def message: String = "Should match " + constValue[V]

private def check(valueExpr: Expr[String], regexExpr: Expr[String])(using Quotes): Expr[Boolean] =
(valueExpr.value, regexExpr.value) match
case (Some(value), Some(regex)) => Expr(value.matches(regex))
val rflUtil = reflectUtil
import rflUtil.*

(valueExpr.decode, regexExpr.decode) match
case (Right(value), Right(regex)) => Expr(value.matches(regex))
case _ => '{ $valueExpr.matches($regexExpr) }
5 changes: 5 additions & 0 deletions main/src/io/github/iltotore/iron/internal/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.github.iltotore.iron.internal

extension (text: String)

def colorized(color: String): String = s"$color$text${Console.RESET}"
Loading

0 comments on commit 623835e

Please sign in to comment.