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:
|
||||
|
||||
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)
|
||||
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 = ???
|
||||
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).
|
||||
*/
|
||||
sealed trait AssertionDSLVerb
|
||||
|
@ -54,21 +64,28 @@ sealed trait HaveVerb(val negated: Boolean):
|
|||
case class HaveNegation(haveVerb: HaveVerb) extends HaveVerb(!haveVerb.negated)
|
||||
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.
|
||||
*
|
||||
* @param subject
|
||||
* the object to which perform the assertion.
|
||||
*/
|
||||
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) = ???
|
||||
|
||||
transparent inline def should(inline verbWord: be_.type) = ${
|
||||
|
@ -92,3 +109,7 @@ extension [T](subject: T)
|
|||
transparent inline def should(inline verb: HaveVerb) = ${
|
||||
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