diff --git a/src/main/scala/ch/usi/si/msde/edsl/lecture10/GenerativeFluentAssertionsDSL.scala b/src/main/scala/ch/usi/si/msde/edsl/lecture10/GenerativeFluentAssertionsDSL.scala index 97fb7b9..afb8d01 100644 --- a/src/main/scala/ch/usi/si/msde/edsl/lecture10/GenerativeFluentAssertionsDSL.scala +++ b/src/main/scala/ch/usi/si/msde/edsl/lecture10/GenerativeFluentAssertionsDSL.scala @@ -8,6 +8,11 @@ import scala.language.postfixOps */ object GenerativeFluentAssertionsDSL: + /** + * Due to operator precedence, '!==' is the only comparison operator that is considered an "assignment operation" + * and thus has lower precedence than alphanumeric operators. Therefore, we must handle "!==" as the final link of + * a method chain instead of a predicate builder on `satisfying`, like the other operators. + */ extension [T](inline property: SatisfyingNotEquals[T]) inline def `!==`(inline obj: T)(using inline ord: Ordering[T]): AssertionProperty = ${ notEqualsImpl('property, 'obj, 'ord) } @@ -91,7 +96,7 @@ object GenerativeFluentAssertionsDSL: // and the type of the property (actually... not exactly that. Why?) case '{ (${ typeProvider }: HavePropertyTypeProvider[subjectType]) - .applyDynamic($propertyNameExpr: String)($valueExpr: valueType => Boolean) + .applyDynamic($propertyNameExpr: String)($valueExpr: Condition[valueType]) .$asInstanceOf$[AssertionProperty] } => println(assertionExpr.show) @@ -162,7 +167,7 @@ object GenerativeFluentAssertionsDSL: private def generateShouldHaveAssertion[T, R]( subjectExpr: Expr[T], propertyNameExpr: Expr[String], - propertyValueExpr: Expr[R => Boolean] + propertyValueExpr: Expr[Condition[R]] )(using Type[T], Type[R])(using Quotes): Expr[Unit] = import quotes.reflect.* @@ -195,11 +200,11 @@ object GenerativeFluentAssertionsDSL: */ val assertion = '{ val subject = $subjectExpr - val tester = $propertyValueExpr + val condition = $propertyValueExpr lazy val value = ${ Select(('subject).asTerm, candidateMethod).asExprOf[R] } assert( - tester.apply(value), - s"assertion failed for ${subject}.${$propertyNameExpr}" + condition.test(value), + s"${subject}.${$propertyNameExpr} " + condition.failedMessage + " but " + value ) } report.info(assertion.show, subjectExpr.asTerm.pos) diff --git a/src/main/scala/ch/usi/si/msde/edsl/lecture10/GenerativeFluentAssertionsDSLSyntax.scala b/src/main/scala/ch/usi/si/msde/edsl/lecture10/GenerativeFluentAssertionsDSLSyntax.scala index 9093084..bb3902c 100644 --- a/src/main/scala/ch/usi/si/msde/edsl/lecture10/GenerativeFluentAssertionsDSLSyntax.scala +++ b/src/main/scala/ch/usi/si/msde/edsl/lecture10/GenerativeFluentAssertionsDSLSyntax.scala @@ -19,19 +19,17 @@ end DynamicShouldBeProperty */ sealed trait SatisfyingNotEquals[T] +case class Condition[T](predicate: (T, T) => Boolean, op: String, expected: T): + def test(value: T): Boolean = predicate(value, expected) + def failedMessage: String = s"is not ${op} ${expected}" + case object satisfying: - def `===`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean = - something => ord.equiv(something, toWhat) - def notEquals[T](toWhat: T, ord: Ordering[T]): T => Boolean = - something => !ord.equiv(something, toWhat) - def `<`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean = - something => ord.lt(something, toWhat) - def `>`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean = - something => ord.gt(something, toWhat) - def `<=`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean = - something => ord.lteq(something, toWhat) - def `>=`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean = - something => ord.gteq(something, toWhat) + def `===`[T](toWhat: T)(using ord: Ordering[T]): Condition[T] = Condition(ord.equiv, "===", toWhat) + def notEquals[T](toWhat: T, ord: Ordering[T]): Condition[T] = Condition(!ord.equiv(_, _), "!==", toWhat) + def `<`[T](toWhat: T)(using ord: Ordering[T]): Condition[T] = Condition(ord.lt, "<", toWhat) + def `>`[T](toWhat: T)(using ord: Ordering[T]): Condition[T] = Condition(ord.gt, ">", toWhat) + def `<=`[T](toWhat: T)(using ord: Ordering[T]): Condition[T] = Condition(ord.lteq, "<=", toWhat) + def `>=`[T](toWhat: T)(using ord: Ordering[T]): Condition[T] = Condition(ord.gteq, ">=", toWhat) class BePropertyTypeProvider[T](val subject: T) extends Selectable: def selectDynamic(fieldName: String): AssertionProperty = ??? diff --git a/src/main/scala/ch/usi/si/msde/edsl/lecture10/Main.scala b/src/main/scala/ch/usi/si/msde/edsl/lecture10/Main.scala index c912061..1293ddc 100644 --- a/src/main/scala/ch/usi/si/msde/edsl/lecture10/Main.scala +++ b/src/main/scala/ch/usi/si/msde/edsl/lecture10/Main.scala @@ -45,7 +45,7 @@ case class Box(label: String, weight: Mass, price: Money) List() should have size satisfying >= 0 List(3) should have size satisfying !== 0 List(50, 2, 3) should have head satisfying < 100 - box should have weight satisfying >= 3.0.kg + box should have weight satisfying <= 300.0.kg /* should have assertions */ // // assert(List(1).head == 1, ...) diff --git a/src/main/scala/ch/usi/si/msde/edsl/lecture10/ShouldHaveTypeProvider.scala b/src/main/scala/ch/usi/si/msde/edsl/lecture10/ShouldHaveTypeProvider.scala index b7dc705..179e244 100644 --- a/src/main/scala/ch/usi/si/msde/edsl/lecture10/ShouldHaveTypeProvider.scala +++ b/src/main/scala/ch/usi/si/msde/edsl/lecture10/ShouldHaveTypeProvider.scala @@ -32,9 +32,8 @@ object ShouldHaveTypeProvider: // The type of the field, or the return type of the method. val fieldTypeRepr = subjectTypeRepr.memberType(symbol).widen - val appliedType = AppliedType(typeConstructor[? => ?], List(fieldTypeRepr, TypeRepr.of[Boolean])) val methodType = MethodType(List("arg"))( - _ => List(appliedType), + _ => List(AppliedType(typeConstructor[Condition[?]], List(fieldTypeRepr))), _ => TypeRepr.of[AssertionProperty] )