ex4 started

This commit is contained in:
Claudio Maggioni 2024-12-22 16:21:16 +01:00
parent 07f5451064
commit cbe868975b
3 changed files with 133 additions and 9 deletions

View file

@ -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)

View file

@ -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)
}

View file

@ -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