From d7ce3ff16261905c4558bb7d8e8fcec5d8874bb4 Mon Sep 17 00:00:00 2001 From: Iltotore Date: Sun, 15 Sep 2024 22:24:48 +0200 Subject: [PATCH 1/8] feat: Add expr list support --- .../io/github/iltotore/iron/compileTime.scala | 16 ++++++++++++++++ .../iltotore/iron/constraint/collection.scala | 14 ++++++++++++++ .../iltotore/iron/macros/ReflectUtil.scala | 3 +-- .../io/github/iltotore/iron/macros/package.scala | 2 +- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/main/src/io/github/iltotore/iron/compileTime.scala b/main/src/io/github/iltotore/iron/compileTime.scala index 7fcdd84f..643d5841 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 06dccafc..cc2a6807 100644 --- a/main/src/io/github/iltotore/iron/constraint/collection.scala +++ b/main/src/io/github/iltotore/iron/constraint/collection.scala @@ -179,6 +179,20 @@ object collection: inline given forAllString[C, Impl <: Constraint[Char, C]](using inline impl: Impl): ForAllString[C, Impl] = new ForAllString + private def checkIterable[I <: Iterable[?], C, Impl <: Constraint[Char, C]](expr: Expr[I], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = + ??? + /*val rflUtil = reflectUtil + import rflUtil.* + + expr.decode match + case Right(value) => + value + .map(Expr.apply) + .map(applyConstraint(_, constraintExpr)) + .foldLeft(Expr(true))((e, t) => '{ $e && $t }) + + case _ => '{ $expr.forallOptimized(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 ae1117bc..7b3ad2cc 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 2effdd24..0234a677 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 From 3b4bc2443e813d03ec7e577b2336bf33c5066479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Fromentin?= Date: Fri, 20 Sep 2024 08:12:06 +0200 Subject: [PATCH 2/8] feat: Add compile-time ForAll --- .../iltotore/iron/constraint/collection.scala | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/main/src/io/github/iltotore/iron/constraint/collection.scala b/main/src/io/github/iltotore/iron/constraint/collection.scala index cc2a6807..5b541965 100644 --- a/main/src/io/github/iltotore/iron/constraint/collection.scala +++ b/main/src/io/github/iltotore/iron/constraint/collection.scala @@ -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,19 +179,17 @@ object collection: inline given forAllString[C, Impl <: Constraint[Char, C]](using inline impl: Impl): ForAllString[C, Impl] = new ForAllString - private def checkIterable[I <: Iterable[?], C, Impl <: Constraint[Char, C]](expr: Expr[I], constraintExpr: Expr[Impl])(using Quotes): Expr[Boolean] = - ??? - /*val rflUtil = reflectUtil + 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.decode match - case Right(value) => - value - .map(Expr.apply) + expr.toExprList match + case Some(list) => + list .map(applyConstraint(_, constraintExpr)) .foldLeft(Expr(true))((e, t) => '{ $e && $t }) - - case _ => '{ $expr.forallOptimized(c => ${ applyConstraint('c, constraintExpr) }) }*/ + + 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 From 5a2667e1efdce12ac2399155d2871ac8ca65e7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Fromentin?= Date: Fri, 20 Sep 2024 08:20:20 +0200 Subject: [PATCH 3/8] feat: Add compile-time Init/Tail --- .../iltotore/iron/constraint/collection.scala | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/main/src/io/github/iltotore/iron/constraint/collection.scala b/main/src/io/github/iltotore/iron/constraint/collection.scala index 5b541965..5e2d6ab2 100644 --- a/main/src/io/github/iltotore/iron/constraint/collection.scala +++ b/main/src/io/github/iltotore/iron/constraint/collection.scala @@ -213,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 @@ -228,6 +228,19 @@ 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 + .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.* @@ -250,9 +263,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 @@ -261,10 +274,23 @@ 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 + .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.* From bbe7ce6533b20838853f98335a76d2f3c3b3f780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Fromentin?= Date: Fri, 20 Sep 2024 08:23:56 +0200 Subject: [PATCH 4/8] feat: Add compile-time Exists --- .../iltotore/iron/constraint/collection.scala | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/main/src/io/github/iltotore/iron/constraint/collection.scala b/main/src/io/github/iltotore/iron/constraint/collection.scala index 5e2d6ab2..d96e8870 100644 --- a/main/src/io/github/iltotore/iron/constraint/collection.scala +++ b/main/src/io/github/iltotore/iron/constraint/collection.scala @@ -312,7 +312,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 + ")" @@ -327,6 +327,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.* From 9c54fa3b3202ace5a374f832c7df52e0fd0bc0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Fromentin?= Date: Fri, 20 Sep 2024 08:34:15 +0200 Subject: [PATCH 5/8] feat: Add compile-time Head --- .../iltotore/iron/constraint/collection.scala | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/main/src/io/github/iltotore/iron/constraint/collection.scala b/main/src/io/github/iltotore/iron/constraint/collection.scala index d96e8870..546689ae 100644 --- a/main/src/io/github/iltotore/iron/constraint/collection.scala +++ b/main/src/io/github/iltotore/iron/constraint/collection.scala @@ -356,7 +356,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 + ")" @@ -371,6 +371,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(list.head, constraintExpr) + case None => Expr(false) + + 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.* From f6684ad99d6f376e9e055b4c856e6363fa147487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Fromentin?= Date: Fri, 20 Sep 2024 09:46:50 +0200 Subject: [PATCH 6/8] feat: Add compile-time Last --- .../iltotore/iron/constraint/collection.scala | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/main/src/io/github/iltotore/iron/constraint/collection.scala b/main/src/io/github/iltotore/iron/constraint/collection.scala index 546689ae..4e8841ac 100644 --- a/main/src/io/github/iltotore/iron/constraint/collection.scala +++ b/main/src/io/github/iltotore/iron/constraint/collection.scala @@ -378,7 +378,7 @@ object collection: expr.toExprList match case Some(list) => list.headOption match - case Some(head) => applyConstraint(list.head, constraintExpr) + case Some(head) => applyConstraint(head, constraintExpr) case None => Expr(false) case None => '{ $expr.exists(c => ${ applyConstraint('c, constraintExpr) }) } @@ -400,7 +400,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 + ")" @@ -415,6 +415,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.* From a7e1d30493de9c1dd6c19a6b94af65507c82dac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Fromentin?= Date: Fri, 20 Sep 2024 09:59:40 +0200 Subject: [PATCH 7/8] feat: Make Length usable on Iterable of any type --- main/src/io/github/iltotore/iron/constraint/collection.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/io/github/iltotore/iron/constraint/collection.scala b/main/src/io/github/iltotore/iron/constraint/collection.scala index 4e8841ac..2aaafa26 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] = From 91f4a64f01f4ed3bd646dae026b34aa4036cbe23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Fromentin?= Date: Fri, 20 Sep 2024 10:10:16 +0200 Subject: [PATCH 8/8] fix: Wrong implementation for init/tail/(runtime) head --- .../iltotore/iron/constraint/collection.scala | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/main/src/io/github/iltotore/iron/constraint/collection.scala b/main/src/io/github/iltotore/iron/constraint/collection.scala index 2aaafa26..552d5a28 100644 --- a/main/src/io/github/iltotore/iron/constraint/collection.scala +++ b/main/src/io/github/iltotore/iron/constraint/collection.scala @@ -234,10 +234,13 @@ object collection: expr.toExprList match case Some(list) => - list - .init - .map(applyConstraint(_, constraintExpr)) - .foldLeft(Expr(true))((e, t) => '{ $e && $t }) + 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) }) } @@ -284,10 +287,13 @@ object collection: expr.toExprList match case Some(list) => - list - .tail - .map(applyConstraint(_, constraintExpr)) - .foldLeft(Expr(true))((e, t) => '{ $e && $t }) + 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) }) } @@ -381,7 +387,7 @@ object collection: case Some(head) => applyConstraint(head, constraintExpr) case None => Expr(false) - case None => '{ $expr.exists(c => ${ applyConstraint('c, constraintExpr) }) } + 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