ex4 started
This commit is contained in:
parent
07f5451064
commit
cbe868975b
3 changed files with 133 additions and 9 deletions
|
@ -8,6 +8,18 @@ import scala.language.postfixOps
|
||||||
*/
|
*/
|
||||||
object GenerativeFluentAssertionsDSL:
|
object GenerativeFluentAssertionsDSL:
|
||||||
|
|
||||||
|
def shouldContainImpl[T](subject: Expr[T])(using Type[T])(using Quotes): Expr[Any] = {
|
||||||
|
import quotes.reflect.*
|
||||||
|
|
||||||
|
subject match {
|
||||||
|
case '{ $subject: Iterable[Any] } => '{ ContainsVerb($subject) }
|
||||||
|
case _ => report.errorAndAbort(
|
||||||
|
"assertion subject must be an Iterable[?] in order to use a 'contain' assertion",
|
||||||
|
subject
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension (inline property: AssertionProperty)
|
extension (inline property: AssertionProperty)
|
||||||
inline def `unary_!`: AssertionProperty = NegatedAssertionProperty(property)
|
inline def `unary_!`: AssertionProperty = NegatedAssertionProperty(property)
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,16 @@ class HavePropertyTypeProvider[T](val subject: T, val negated: Boolean) extends
|
||||||
def applyDynamic(fieldName: String)(arg: Any): AssertionProperty = ???
|
def applyDynamic(fieldName: String)(arg: Any): AssertionProperty = ???
|
||||||
end HavePropertyTypeProvider
|
end HavePropertyTypeProvider
|
||||||
|
|
||||||
|
sealed trait ContainsAssertionKind
|
||||||
|
case object All extends ContainsAssertionKind
|
||||||
|
case object None extends ContainsAssertionKind
|
||||||
|
case object Some extends ContainsAssertionKind
|
||||||
|
|
||||||
|
class ContainsPropertyTypeProvider[T](val subject: Iterable[T], val kind: ContainsAssertionKind) extends Selectable:
|
||||||
|
def applyDynamic[U](fieldName: String)(arg: satisfying.type): SatisfyingNotEquals[U] = ???
|
||||||
|
def applyDynamic(fieldName: String)(arg: Any): AssertionProperty = ???
|
||||||
|
end ContainsPropertyTypeProvider
|
||||||
|
|
||||||
/** A trait for each type of assertion (be or have).
|
/** A trait for each type of assertion (be or have).
|
||||||
*/
|
*/
|
||||||
sealed trait AssertionDSLVerb
|
sealed trait AssertionDSLVerb
|
||||||
|
@ -54,21 +64,28 @@ sealed trait HaveVerb(val negated: Boolean):
|
||||||
case class HaveNegation(haveVerb: HaveVerb) extends HaveVerb(!haveVerb.negated)
|
case class HaveNegation(haveVerb: HaveVerb) extends HaveVerb(!haveVerb.negated)
|
||||||
case object have extends HaveVerb(false)
|
case object have extends HaveVerb(false)
|
||||||
|
|
||||||
|
case object contain extends AssertionDSLVerb
|
||||||
|
case object `with` extends AssertionDSLVerb
|
||||||
|
|
||||||
|
case class ContainsVerb[T](subject: Iterable[T]):
|
||||||
|
transparent inline def allElements(verb: `with`.type) = ${
|
||||||
|
ShouldContainTypeProvider.generateShouldContainTypeProvider[T]('subject, 'All)
|
||||||
|
}
|
||||||
|
|
||||||
|
transparent inline def someElements(verb: `with`.type) = ${
|
||||||
|
ShouldContainTypeProvider.generateShouldContainTypeProvider[T]('subject, 'Some)
|
||||||
|
}
|
||||||
|
|
||||||
|
transparent inline def noElements(verb: `with`.type) = ${
|
||||||
|
ShouldContainTypeProvider.generateShouldContainTypeProvider[T]('subject, 'None)
|
||||||
|
}
|
||||||
|
|
||||||
/** Implicit class to add 'should' assertions to a generic type T.
|
/** Implicit class to add 'should' assertions to a generic type T.
|
||||||
*
|
*
|
||||||
* @param subject
|
* @param subject
|
||||||
* the object to which perform the assertion.
|
* the object to which perform the assertion.
|
||||||
*/
|
*/
|
||||||
extension [T](subject: T)
|
extension [T](subject: T)
|
||||||
/** Specifies a 'be' assertion. The method is just a placeholder to fix the
|
|
||||||
* syntax, its semantics is performed by the assertions macro.
|
|
||||||
*
|
|
||||||
* @param verb
|
|
||||||
* The be word.
|
|
||||||
* @return
|
|
||||||
* a DynamicProperty object which allows to specify a further property
|
|
||||||
* name.
|
|
||||||
*/
|
|
||||||
def should(property: AssertionProperty) = ???
|
def should(property: AssertionProperty) = ???
|
||||||
|
|
||||||
transparent inline def should(inline verbWord: be_.type) = ${
|
transparent inline def should(inline verbWord: be_.type) = ${
|
||||||
|
@ -92,3 +109,7 @@ extension [T](subject: T)
|
||||||
transparent inline def should(inline verb: HaveVerb) = ${
|
transparent inline def should(inline verb: HaveVerb) = ${
|
||||||
ShouldHaveTypeProvider.generateShouldHaveTypeProvider[T]('subject, '{verb.negated})
|
ShouldHaveTypeProvider.generateShouldHaveTypeProvider[T]('subject, '{verb.negated})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transparent inline def should(inline verb: contain.type) = ${
|
||||||
|
GenerativeFluentAssertionsDSL.shouldContainImpl('subject)
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package ch.usi.si.msde.edsl.lecture10
|
||||||
|
|
||||||
|
import scala.quoted.*
|
||||||
|
|
||||||
|
object ShouldContainTypeProvider:
|
||||||
|
|
||||||
|
/** The type provider for the should have assertion, which generates a refined
|
||||||
|
* structural type for type T.
|
||||||
|
*/
|
||||||
|
def generateShouldContainTypeProvider[T](subject: Expr[Iterable[T]], kind: Expr[ContainsAssertionKind])(using Type[T])(using
|
||||||
|
Quotes
|
||||||
|
): Expr[Any] =
|
||||||
|
import quotes.reflect.*
|
||||||
|
|
||||||
|
// hack to get the equivalent of `TypeRepr.of[Function1[?, ?]]` (i.e. an arity-1 function type constructor).
|
||||||
|
// Getting it as is returns an applied type with useless bounds (Nothing to Any), and the returned TypeRepr,
|
||||||
|
// if re-applied to some `I` input type and `O` output type would be incompatible with `TypeRepr.of[I => O]` for
|
||||||
|
// some reason
|
||||||
|
def typeConstructor[U](using Type[U]): TypeRepr =
|
||||||
|
AppliedType.unapply(TypeRepr.of[U].asInstanceOf[AppliedType])._1
|
||||||
|
|
||||||
|
val subjectTypeRepr = TypeRepr.of[T]
|
||||||
|
|
||||||
|
/** Given a refinable current type, and the symbol of a arity-0 method or a
|
||||||
|
* field of type foo: X, generates a refinement containing a method with
|
||||||
|
* signature def foo(arg: X): AssertionProperty.
|
||||||
|
*/
|
||||||
|
def refineTypeBySymbol(
|
||||||
|
currentTypeRepr: TypeRepr,
|
||||||
|
symbol: Symbol
|
||||||
|
): TypeRepr =
|
||||||
|
// The type of the field, or the return type of the method.
|
||||||
|
val fieldTypeRepr = subjectTypeRepr.memberType(symbol).widen
|
||||||
|
|
||||||
|
val methodType = MethodType(List("arg"))(
|
||||||
|
_ => List(AppliedType(typeConstructor[Condition[?]], List(fieldTypeRepr))),
|
||||||
|
_ => TypeRepr.of[AssertionProperty]
|
||||||
|
)
|
||||||
|
|
||||||
|
val chainedMethodType = MethodType(List("arg"))(
|
||||||
|
_ => List(TypeRepr.of[satisfying.type]),
|
||||||
|
_ => AppliedType(typeConstructor[SatisfyingNotEquals[?]], List(fieldTypeRepr))
|
||||||
|
)
|
||||||
|
|
||||||
|
Refinement(
|
||||||
|
Refinement(currentTypeRepr, symbol.name, chainedMethodType),
|
||||||
|
symbol.name,
|
||||||
|
methodType
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Refines a type according to a list of fields or methods of arity 0.
|
||||||
|
*/
|
||||||
|
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 fields = subjectTypeRepr.typeSymbol.fieldMembers
|
||||||
|
val arityZeroMethods = subjectTypeRepr.typeSymbol.methodMembers
|
||||||
|
.filter: methodMember =>
|
||||||
|
methodMember.paramSymss.size == 0 && !methodMember.flags.is(
|
||||||
|
Flags.Synthetic
|
||||||
|
)
|
||||||
|
|
||||||
|
val refinedType = refineTypeBySymbols(
|
||||||
|
TypeRepr.of[ContainsPropertyTypeProvider[T]],
|
||||||
|
fields ++ arityZeroMethods
|
||||||
|
)
|
||||||
|
|
||||||
|
println(refinedType.show)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
'{ ContainsPropertyTypeProvider[T]($subject, $kind).asInstanceOf[tpe] }
|
||||||
|
|
||||||
|
end ShouldContainTypeProvider
|
Loading…
Reference in a new issue