ex1 and ex2 done

This commit is contained in:
Claudio Maggioni 2024-12-18 14:17:26 +01:00
parent fa3972ec1b
commit 16b4367ed0
4 changed files with 22 additions and 20 deletions

View file

@ -8,6 +8,11 @@ import scala.language.postfixOps
*/ */
object GenerativeFluentAssertionsDSL: 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]) extension [T](inline property: SatisfyingNotEquals[T])
inline def `!==`(inline obj: T)(using inline ord: Ordering[T]): AssertionProperty = inline def `!==`(inline obj: T)(using inline ord: Ordering[T]): AssertionProperty =
${ notEqualsImpl('property, 'obj, 'ord) } ${ notEqualsImpl('property, 'obj, 'ord) }
@ -91,7 +96,7 @@ object GenerativeFluentAssertionsDSL:
// and the type of the property (actually... not exactly that. Why?) // and the type of the property (actually... not exactly that. Why?)
case '{ case '{
(${ typeProvider }: HavePropertyTypeProvider[subjectType]) (${ typeProvider }: HavePropertyTypeProvider[subjectType])
.applyDynamic($propertyNameExpr: String)($valueExpr: valueType => Boolean) .applyDynamic($propertyNameExpr: String)($valueExpr: Condition[valueType])
.$asInstanceOf$[AssertionProperty] .$asInstanceOf$[AssertionProperty]
} => } =>
println(assertionExpr.show) println(assertionExpr.show)
@ -162,7 +167,7 @@ object GenerativeFluentAssertionsDSL:
private def generateShouldHaveAssertion[T, R]( private def generateShouldHaveAssertion[T, R](
subjectExpr: Expr[T], subjectExpr: Expr[T],
propertyNameExpr: Expr[String], propertyNameExpr: Expr[String],
propertyValueExpr: Expr[R => Boolean] propertyValueExpr: Expr[Condition[R]]
)(using Type[T], Type[R])(using Quotes): Expr[Unit] = )(using Type[T], Type[R])(using Quotes): Expr[Unit] =
import quotes.reflect.* import quotes.reflect.*
@ -195,11 +200,11 @@ object GenerativeFluentAssertionsDSL:
*/ */
val assertion = '{ val assertion = '{
val subject = $subjectExpr val subject = $subjectExpr
val tester = $propertyValueExpr val condition = $propertyValueExpr
lazy val value = ${ Select(('subject).asTerm, candidateMethod).asExprOf[R] } lazy val value = ${ Select(('subject).asTerm, candidateMethod).asExprOf[R] }
assert( assert(
tester.apply(value), condition.test(value),
s"assertion failed for ${subject}.${$propertyNameExpr}" s"${subject}.${$propertyNameExpr} " + condition.failedMessage + " but " + value
) )
} }
report.info(assertion.show, subjectExpr.asTerm.pos) report.info(assertion.show, subjectExpr.asTerm.pos)

View file

@ -19,19 +19,17 @@ end DynamicShouldBeProperty
*/ */
sealed trait SatisfyingNotEquals[T] 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: case object satisfying:
def `===`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean = def `===`[T](toWhat: T)(using ord: Ordering[T]): Condition[T] = Condition(ord.equiv, "===", toWhat)
something => ord.equiv(something, toWhat) def notEquals[T](toWhat: T, ord: Ordering[T]): Condition[T] = Condition(!ord.equiv(_, _), "!==", toWhat)
def notEquals[T](toWhat: T, ord: Ordering[T]): T => Boolean = def `<`[T](toWhat: T)(using ord: Ordering[T]): Condition[T] = Condition(ord.lt, "<", toWhat)
something => !ord.equiv(something, toWhat) def `>`[T](toWhat: T)(using ord: Ordering[T]): Condition[T] = Condition(ord.gt, ">", toWhat)
def `<`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean = def `<=`[T](toWhat: T)(using ord: Ordering[T]): Condition[T] = Condition(ord.lteq, "<=", toWhat)
something => ord.lt(something, toWhat) def `>=`[T](toWhat: T)(using ord: Ordering[T]): Condition[T] = Condition(ord.gteq, ">=", 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)
class BePropertyTypeProvider[T](val subject: T) extends Selectable: class BePropertyTypeProvider[T](val subject: T) extends Selectable:
def selectDynamic(fieldName: String): AssertionProperty = ??? def selectDynamic(fieldName: String): AssertionProperty = ???

View file

@ -45,7 +45,7 @@ case class Box(label: String, weight: Mass, price: Money)
List() should have size satisfying >= 0 List() should have size satisfying >= 0
List(3) should have size satisfying !== 0 List(3) should have size satisfying !== 0
List(50, 2, 3) should have head satisfying < 100 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 */ /* should have assertions */
// // assert(List(1).head == 1, ...) // // assert(List(1).head == 1, ...)

View file

@ -32,9 +32,8 @@ object ShouldHaveTypeProvider:
// The type of the field, or the return type of the method. // The type of the field, or the return type of the method.
val fieldTypeRepr = subjectTypeRepr.memberType(symbol).widen val fieldTypeRepr = subjectTypeRepr.memberType(symbol).widen
val appliedType = AppliedType(typeConstructor[? => ?], List(fieldTypeRepr, TypeRepr.of[Boolean]))
val methodType = MethodType(List("arg"))( val methodType = MethodType(List("arg"))(
_ => List(appliedType), _ => List(AppliedType(typeConstructor[Condition[?]], List(fieldTypeRepr))),
_ => TypeRepr.of[AssertionProperty] _ => TypeRepr.of[AssertionProperty]
) )