solution with make-true
This commit is contained in:
parent
7dc37783d8
commit
32f106c61f
4 changed files with 91 additions and 2 deletions
|
@ -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. ",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
Loading…
Reference in a new issue