diff --git a/main/src/io/github/iltotore/iron/compileTime.scala b/main/src/io/github/iltotore/iron/compileTime.scala index 7fcdd84..643d584 100644 --- a/main/src/io/github/iltotore/iron/compileTime.scala +++ b/main/src/io/github/iltotore/iron/compileTime.scala @@ -4,6 +4,7 @@ import scala.compiletime.constValue import scala.compiletime.ops.* import scala.compiletime.ops.any.ToString import scala.quoted.* +import scala.annotation.targetName /** * Methods and types to ease compile-time operations. @@ -199,3 +200,18 @@ object compileTime: import quotes.reflect.* Apply(Select.unique(constraintExpr.asTerm, "test"), List(expr.asTerm)).asExprOf[Boolean] + + extension [T : Type](expr: Expr[Iterable[T]]) + + def toExprList(using Quotes): Option[List[Expr[T]]] = expr match + case '{ scala.List[T](${Varargs(elems)}*) } => Some(elems.toList) + case '{ scala.List.empty[T] } => Some(Nil) + case '{ Nil } => Some(Nil) + case '{ scala.collection.immutable.List[T](${Varargs(elems)}*) } => Some(elems.toList) + case '{ scala.collection.immutable.List.empty[T] } => Some(Nil) + case '{ Set[T](${Varargs(elems)}*) } => Some(elems.toList) + case '{ Set.empty[T] } => Some(Nil) + case '{ scala.collection.immutable.List[T](${Varargs(elems)}*) } => Some(elems.toList) + case '{ scala.collection.immutable.List.empty[T] } => Some(Nil) + case _ => None + \ No newline at end of file diff --git a/main/src/io/github/iltotore/iron/constraint/collection.scala b/main/src/io/github/iltotore/iron/constraint/collection.scala index 06dccaf..552d5a2 100644 --- a/main/src/io/github/iltotore/iron/constraint/collection.scala +++ b/main/src/io/github/iltotore/iron/constraint/collection.scala @@ -117,8 +117,8 @@ object collection: val rflUtil = reflectUtil import rflUtil.* - expr.decode match - case Right(value) => applyConstraint(Expr(value.size), constraintExpr) + expr.toExprList match + case Some(list) => applyConstraint(Expr(list.size), constraintExpr) case _ => applyConstraint('{ $expr.size }, constraintExpr) private def checkString[C, Impl <: Constraint[Int, C]](expr: Expr[String], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = @@ -164,7 +164,7 @@ object collection: class ForAllIterable[A, I <: Iterable[A], C, Impl <: Constraint[A, C]](using Impl) extends Constraint[I, ForAll[C]]: - override inline def test(inline value: I): Boolean = value.forall(summonInline[Impl].test(_)) + override inline def test(inline value: I): Boolean = ${ checkIterable('value, '{ summonInline[Impl] }) } override inline def message: String = "For each element: (" + summonInline[Impl].message + ")" @@ -179,6 +179,18 @@ object collection: inline given forAllString[C, Impl <: Constraint[Char, C]](using inline impl: Impl): ForAllString[C, Impl] = new ForAllString + private def checkIterable[A : Type, I <: Iterable[A] : Type, C, Impl <: Constraint[A, C]](expr: Expr[I], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = + val rflUtil = reflectUtil + import rflUtil.* + + expr.toExprList match + case Some(list) => + list + .map(applyConstraint(_, constraintExpr)) + .foldLeft(Expr(true))((e, t) => '{ $e && $t }) + + case None => '{ $expr.forall(c => ${ applyConstraint('c, constraintExpr) }) } + private def checkString[C, Impl <: Constraint[Char, C]](expr: Expr[String], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = val rflUtil = reflectUtil import rflUtil.* @@ -201,9 +213,9 @@ object collection: class InitIterable[A, I <: Iterable[A], C, Impl <: Constraint[A, C]](using Impl) extends Constraint[I, Init[C]]: - override inline def test(inline value: I): Boolean = value.isEmpty || value.init.forall(summonInline[Impl].test(_)) + override inline def test(inline value: I): Boolean = ${ checkIterable('value, '{ summonInline[Impl] }) } - override inline def message: String = "For each element except head: (" + summonInline[Impl].message + ")" + override inline def message: String = "For each element except last: (" + summonInline[Impl].message + ")" inline given [A, I <: Iterable[A], C, Impl <: Constraint[A, C]](using inline impl: Impl): InitIterable[A, I, C, Impl] = new InitIterable @@ -216,6 +228,22 @@ object collection: inline given initString[C, Impl <: Constraint[Char, C]](using inline impl: Impl): InitString[C, Impl] = new InitString + private def checkIterable[A : Type, I <: Iterable[A] : Type, C, Impl <: Constraint[A, C]](expr: Expr[I], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = + val rflUtil = reflectUtil + import rflUtil.* + + expr.toExprList match + case Some(list) => + list match + case Nil => Expr(true) + case _ => + list + .init + .map(applyConstraint(_, constraintExpr)) + .foldLeft(Expr(true))((e, t) => '{ $e && $t }) + + case None => '{ $expr.init.forall(c => ${ applyConstraint('c, constraintExpr) }) } + private def checkString[C, Impl <: Constraint[Char, C]](expr: Expr[String], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = val rflUtil = reflectUtil import rflUtil.* @@ -238,9 +266,9 @@ object collection: class TailIterable[A, I <: Iterable[A], C, Impl <: Constraint[A, C]](using Impl) extends Constraint[I, Tail[C]]: - override inline def test(inline value: I): Boolean = value.isEmpty || value.tail.forall(summonInline[Impl].test(_)) + override inline def test(inline value: I): Boolean = ${ checkIterable('value, '{ summonInline[Impl] }) } - override inline def message: String = "For each element: (" + summonInline[Impl].message + ")" + override inline def message: String = "For each element except head: (" + summonInline[Impl].message + ")" inline given [A, I <: Iterable[A], C, Impl <: Constraint[A, C]](using inline impl: Impl): TailIterable[A, I, C, Impl] = new TailIterable @@ -249,10 +277,26 @@ object collection: override inline def test(inline value: String): Boolean = ${ checkString('value, '{ summonInline[Impl] }) } - override inline def message: String = "For each element: (" + summonInline[Impl].message + ")" + override inline def message: String = "For each element except head: (" + summonInline[Impl].message + ")" inline given tailString[C, Impl <: Constraint[Char, C]](using inline impl: Impl): TailString[C, Impl] = new TailString + private def checkIterable[A : Type, I <: Iterable[A] : Type, C, Impl <: Constraint[A, C]](expr: Expr[I], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = + val rflUtil = reflectUtil + import rflUtil.* + + expr.toExprList match + case Some(list) => + list match + case Nil => Expr(true) + case _ => + list + .tail + .map(applyConstraint(_, constraintExpr)) + .foldLeft(Expr(true))((e, t) => '{ $e && $t }) + + case None => '{ $expr.tail.forall(c => ${ applyConstraint('c, constraintExpr) }) } + private def checkString[C, Impl <: Constraint[Char, C]](expr: Expr[String], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = val rflUtil = reflectUtil import rflUtil.* @@ -274,7 +318,7 @@ object collection: class ExistsIterable[A, I <: Iterable[A], C, Impl <: Constraint[A, C]](using Impl) extends Constraint[I, Exists[C]]: - override inline def test(inline value: I): Boolean = value.exists(summonInline[Impl].test(_)) + override inline def test(inline value: I): Boolean = ${ checkIterable('value, '{ summonInline[Impl] }) } override inline def message: String = "At least one: (" + summonInline[Impl].message + ")" @@ -289,6 +333,18 @@ object collection: inline given existsString[C, Impl <: Constraint[Char, C]](using inline impl: Impl): ExistsString[C, Impl] = new ExistsString + private def checkIterable[A : Type, I <: Iterable[A] : Type, C, Impl <: Constraint[A, C]](expr: Expr[I], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = + val rflUtil = reflectUtil + import rflUtil.* + + expr.toExprList match + case Some(list) => + list + .map(applyConstraint(_, constraintExpr)) + .foldLeft(Expr(false))((e, t) => '{ $e || $t }) + + case None => '{ $expr.exists(c => ${ applyConstraint('c, constraintExpr) }) } + private def checkString[C, Impl <: Constraint[Char, C]](expr: Expr[String], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = val rflUtil = reflectUtil import rflUtil.* @@ -306,7 +362,7 @@ object collection: class HeadIterable[A, I <: Iterable[A], C, Impl <: Constraint[A, C]](using Impl) extends Constraint[I, Head[C]]: - override inline def test(inline value: I): Boolean = value.headOption.exists(summonInline[Impl].test(_)) + override inline def test(inline value: I): Boolean = ${ checkIterable('value, '{ summonInline[Impl] }) } override inline def message: String = "Head: (" + summonInline[Impl].message + ")" @@ -321,6 +377,18 @@ object collection: inline given headString[C, Impl <: Constraint[Char, C]](using inline impl: Impl): HeadString[C, Impl] = new HeadString + private def checkIterable[A : Type, I <: Iterable[A] : Type, C, Impl <: Constraint[A, C]](expr: Expr[I], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = + val rflUtil = reflectUtil + import rflUtil.* + + expr.toExprList match + case Some(list) => + list.headOption match + case Some(head) => applyConstraint(head, constraintExpr) + case None => Expr(false) + + case None => '{ $expr.headOption.exists(c => ${ applyConstraint('c, constraintExpr) }) } + private def checkString[C, Impl <: Constraint[Char, C]](expr: Expr[String], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = val rflUtil = reflectUtil import rflUtil.* @@ -338,7 +406,7 @@ object collection: class LastIterable[A, I <: Iterable[A], C, Impl <: Constraint[A, C]](using Impl) extends Constraint[I, Last[C]]: - override inline def test(inline value: I): Boolean = value.lastOption.exists(summonInline[Impl].test(_)) + override inline def test(inline value: I): Boolean = ${ checkIterable('value, '{ summonInline[Impl] }) } override inline def message: String = "Last: (" + summonInline[Impl].message + ")" @@ -353,6 +421,18 @@ object collection: inline given lastString[C, Impl <: Constraint[Char, C]](using inline impl: Impl): LastString[C, Impl] = new LastString + private def checkIterable[A : Type, I <: Iterable[A] : Type, C, Impl <: Constraint[A, C]](expr: Expr[I], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = + val rflUtil = reflectUtil + import rflUtil.* + + expr.toExprList match + case Some(list) => + list.lastOption match + case Some(last) => applyConstraint(last, constraintExpr) + case None => Expr(false) + + case None => '{ $expr.lastOption.exists(c => ${ applyConstraint('c, constraintExpr) }) } + private def checkString[C, Impl <: Constraint[Char, C]](expr: Expr[String], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = val rflUtil = reflectUtil import rflUtil.* diff --git a/main/src/io/github/iltotore/iron/macros/ReflectUtil.scala b/main/src/io/github/iltotore/iron/macros/ReflectUtil.scala index ae1117b..7b3ad2c 100644 --- a/main/src/io/github/iltotore/iron/macros/ReflectUtil.scala +++ b/main/src/io/github/iltotore/iron/macros/ReflectUtil.scala @@ -390,5 +390,4 @@ class ReflectUtil[Q <: Quotes & Singleton](using val _quotes: Q): term match case Apply(TypeApply(Select(Ident("Set"), "apply"), _), List(values)) => decodeTerm(values, definitions).as[List[?]].map(_.toSet) - case _ => Left(DecodingFailure.Unknown) - + case _ => Left(DecodingFailure.Unknown) \ No newline at end of file diff --git a/main/src/io/github/iltotore/iron/macros/package.scala b/main/src/io/github/iltotore/iron/macros/package.scala index 2effdd2..0234a67 100644 --- a/main/src/io/github/iltotore/iron/macros/package.scala +++ b/main/src/io/github/iltotore/iron/macros/package.scala @@ -84,4 +84,4 @@ def isIronTypeImpl[T: Type, C: Type](using Quotes): Expr[Boolean] = case _: ImplicitSearchSuccess => Expr(true) case _: ImplicitSearchFailure => Expr(false) - case _ => Expr(false) + case _ => Expr(false) \ No newline at end of file