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 c05ab73..deb6d18 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 @@ -81,6 +81,12 @@ object GenerativeFluentAssertionsDSL: propertyNameExpr, valueExpr ) + case '{ + (${ typeProvider }: BePropertyTypeProvider[subjectType]) + .applyDynamic($propertyNameExpr)($value) + .$asInstanceOf$[AssertionProperty] + } => + '{} // placeholder case _ => report.errorAndAbort( "Invalid expression, must be a 'should be' or 'should have' assertion. ", 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 bd053e5..6241edd 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 @@ -12,6 +12,10 @@ class DynamicShouldBeProperty extends AssertionProperty with Dynamic: def applyDynamic(fieldName: String)(foo: Any*) = ??? end DynamicShouldBeProperty +class BePropertyTypeProvider[T](val subject: T) extends Selectable: + def applyDynamic(fieldName: String)(foo: Any*): AssertionProperty = ??? +end BePropertyTypeProvider + /** The class that implements the syntactic aspect of the type provider. It * extends Selectable and implements applyDynamic with arity 1, so that any * object of this type can be invoked with an arbitrary method with 1 argument, @@ -32,6 +36,7 @@ end HavePropertyTypeProvider sealed trait AssertionDSLVerb case object be extends AssertionDSLVerb with Dynamic: def selectDynamic(fieldName: String): AssertionProperty = ??? +case object make extends AssertionDSLVerb case object have extends AssertionDSLVerb /** Implicit class to add 'should' assertions to a generic type T. @@ -51,6 +56,10 @@ extension [T](subject: T) */ def should(property: AssertionProperty) = ??? + transparent inline def should(inline verbWord: make.type) = ${ + ShouldBeTypeProvider.generateShouldBeTypeProvider[T]('subject) + } + /** Specifies the 'have' assertion. * * The method is a type provider: it generates, through a macro, a refinement 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 2005b6f..4b3b853 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 @@ -25,11 +25,18 @@ case class Box(label: String, weight: Mass, price: Money) val box = Box("aBox", 30.kg, 10.CHF) assertions: + // { + // val p: BePropertyTypeProvider[Person] = new BePropertyTypeProvider[Person](person) + // p.asInstanceOf[BePropertyTypeProvider[Person] { + // def adult: AssertionProperty + // }].adult + // } + /* be.property assertions */ // assert(person.adult, ...) - person should be.adult + person should make adult true // assert(List().isEmpty, ...) - List().should(be.empty) + List() should be.empty // assert(List(1,2,3).nonEmpty) List(1, 2, 3) should be.nonEmpty diff --git a/src/main/scala/ch/usi/si/msde/edsl/lecture10/ShouldBeTypeProvider.scala b/src/main/scala/ch/usi/si/msde/edsl/lecture10/ShouldBeTypeProvider.scala new file mode 100644 index 0000000..0efb761 --- /dev/null +++ b/src/main/scala/ch/usi/si/msde/edsl/lecture10/ShouldBeTypeProvider.scala @@ -0,0 +1,67 @@ +package ch.usi.si.msde.edsl.lecture10 + +import scala.annotation.tailrec +import scala.quoted.* + +object ShouldBeTypeProvider: + + def generateShouldBeTypeProvider[T](subject: Expr[T])(using Type[T])(using Quotes): Expr[Any] = + import quotes.reflect.* + + val subjectTypeRepr = TypeRepr.of[T] + + def refineTypeBySymbol( + currentTypeRepr: TypeRepr, + symbol: Symbol + ): TypeRepr = + val methodType = MethodType(List("value"))( + _ => List(TypeRepr.of[true]), + _ => TypeRepr.of[AssertionProperty] + ) +// val methodType = TypeRepr.of[AssertionProperty] + Refinement(currentTypeRepr, symbol.name, methodType) + + @tailrec + def refineTypeBySymbols( + currentTypeRepr: TypeRepr, + fields: List[Symbol] + ): TypeRepr = + fields match + // this is pattern matching on list - like head :: rest + case symbol :: symbols => + refineTypeBySymbols( + refineTypeBySymbol(currentTypeRepr, symbol), + symbols + ) + // empty list case + case Nil => + currentTypeRepr + + val booleanType = TypeRepr.of[Boolean] + + val members = (subjectTypeRepr.typeSymbol.fieldMembers ++ subjectTypeRepr.typeSymbol.methodMembers) + .filter: fieldMember => + subjectTypeRepr.memberType(fieldMember).widen <:< booleanType + + val refinedType = refineTypeBySymbols( + TypeRepr.of[BePropertyTypeProvider[T]], + members + ) + + println(refinedType) + + // This is the way to extract a (reflection) type to a + // type that can be used in some quoted code. + // it's the equivalent of .asExpr for expressions, + // but it's more complicated because in this case the + // exact type is unknown until compilation time. + refinedType.asType match + case '[tpe] => // tpe is the exact refined type + val res = '{ + val p = BePropertyTypeProvider[T]($subject) + p.asInstanceOf[tpe] + } + println(res.show) + res + +end ShouldBeTypeProvider