compiles
This commit is contained in:
parent
e13b40fd3d
commit
dda2d1b61f
5 changed files with 48 additions and 36 deletions
|
@ -4,7 +4,7 @@ lazy val root = project
|
||||||
.in(file("."))
|
.in(file("."))
|
||||||
.settings(
|
.settings(
|
||||||
organization := "ch.usi.si.msde.edsl",
|
organization := "ch.usi.si.msde.edsl",
|
||||||
name := "lecture-10",
|
name := "assignment-04a",
|
||||||
version := "2024.01",
|
version := "2024.01",
|
||||||
scalaVersion := scala3Version,
|
scalaVersion := scala3Version,
|
||||||
libraryDependencies += "org.typelevel" %% "squants" % "1.8.3",
|
libraryDependencies += "org.typelevel" %% "squants" % "1.8.3",
|
||||||
|
|
|
@ -72,7 +72,14 @@ object GenerativeFluentAssertionsDSL:
|
||||||
// and the type of the property (actually... not exactly that. Why?)
|
// and the type of the property (actually... not exactly that. Why?)
|
||||||
case '{
|
case '{
|
||||||
(${ typeProvider }: HavePropertyTypeProvider[subjectType])
|
(${ typeProvider }: HavePropertyTypeProvider[subjectType])
|
||||||
.applyDynamic($propertyNameExpr)($valueExpr: valueType => Boolean)
|
.applyDynamic($propertyNameExpr: String)(satisfying)
|
||||||
|
.$asInstanceOf$[SatisfyingNotEquals[fieldType]]
|
||||||
|
.`!==`($property)
|
||||||
|
} =>
|
||||||
|
'{}
|
||||||
|
case '{
|
||||||
|
(${ typeProvider }: HavePropertyTypeProvider[subjectType])
|
||||||
|
.applyDynamic($propertyNameExpr: String)($valueExpr: valueType => Boolean)
|
||||||
.$asInstanceOf$[AssertionProperty]
|
.$asInstanceOf$[AssertionProperty]
|
||||||
} =>
|
} =>
|
||||||
val subjectExpr = '{ $typeProvider.subject }
|
val subjectExpr = '{ $typeProvider.subject }
|
||||||
|
@ -94,6 +101,7 @@ object GenerativeFluentAssertionsDSL:
|
||||||
} =>
|
} =>
|
||||||
'{} // placeholder
|
'{} // placeholder
|
||||||
case _ =>
|
case _ =>
|
||||||
|
println(assertionExpr.show)
|
||||||
report.errorAndAbort(
|
report.errorAndAbort(
|
||||||
"Invalid expression, must be a 'should be' or 'should have' assertion. ",
|
"Invalid expression, must be a 'should be' or 'should have' assertion. ",
|
||||||
assertionExpr
|
assertionExpr
|
||||||
|
|
|
@ -12,11 +12,17 @@ class DynamicShouldBeProperty extends AssertionProperty with Dynamic:
|
||||||
def applyDynamic(fieldName: String)(foo: Any*) = ???
|
def applyDynamic(fieldName: String)(foo: Any*) = ???
|
||||||
end DynamicShouldBeProperty
|
end DynamicShouldBeProperty
|
||||||
|
|
||||||
object satisfying:
|
/**
|
||||||
|
* Due to operator precedence, '!==' is the only comparison operator that is considered an "assignment operation"
|
||||||
|
* and thus has lower precedence than alphanumeric operators. Therefore, we must handle "!==" as the final link of
|
||||||
|
* a method chain instead of a predicate builder on `satisfying`, like the other operators.
|
||||||
|
*/
|
||||||
|
sealed trait SatisfyingNotEquals[T]:
|
||||||
|
def `!==`(toWhat: T): AssertionProperty = ???
|
||||||
|
|
||||||
|
case object satisfying:
|
||||||
def `===`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean =
|
def `===`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean =
|
||||||
something => ord.eq(something, toWhat)
|
something => ord.eq(something, toWhat)
|
||||||
def `!==`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean =
|
|
||||||
something => ord.ne(something, toWhat)
|
|
||||||
def `<`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean =
|
def `<`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean =
|
||||||
something => ord.lt(something, toWhat)
|
something => ord.lt(something, toWhat)
|
||||||
def `>`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean =
|
def `>`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean =
|
||||||
|
@ -25,7 +31,6 @@ object satisfying:
|
||||||
something => ord.lteq(something, toWhat)
|
something => ord.lteq(something, toWhat)
|
||||||
def `>=`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean =
|
def `>=`[T](toWhat: T)(using ord: Ordering[T]): T => Boolean =
|
||||||
something => ord.gteq(something, toWhat)
|
something => ord.gteq(something, toWhat)
|
||||||
end satisfying
|
|
||||||
|
|
||||||
class BePropertyTypeProvider[T](val subject: T) extends Selectable:
|
class BePropertyTypeProvider[T](val subject: T) extends Selectable:
|
||||||
def selectDynamic(fieldName: String): AssertionProperty = ???
|
def selectDynamic(fieldName: String): AssertionProperty = ???
|
||||||
|
@ -44,6 +49,7 @@ end BePropertyTypeProvider
|
||||||
* corresponding method def foo(arg: X): AssertionProperty.
|
* corresponding method def foo(arg: X): AssertionProperty.
|
||||||
*/
|
*/
|
||||||
class HavePropertyTypeProvider[T](val subject: T) extends Selectable:
|
class HavePropertyTypeProvider[T](val subject: T) extends Selectable:
|
||||||
|
def applyDynamic[U](fieldName: String)(arg: satisfying.type): SatisfyingNotEquals[U] = ???
|
||||||
def applyDynamic(fieldName: String)(arg: Any): AssertionProperty = ???
|
def applyDynamic(fieldName: String)(arg: Any): AssertionProperty = ???
|
||||||
end HavePropertyTypeProvider
|
end HavePropertyTypeProvider
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ package ch.usi.si.msde.edsl.lecture10
|
||||||
|
|
||||||
import squants.mass.MassConversions.MassConversions
|
import squants.mass.MassConversions.MassConversions
|
||||||
import squants.market.MoneyConversions.MoneyConversions
|
import squants.market.MoneyConversions.MoneyConversions
|
||||||
|
|
||||||
import GenerativeFluentAssertionsDSL.*
|
import GenerativeFluentAssertionsDSL.*
|
||||||
import squants.mass.Mass
|
import squants.mass.Mass
|
||||||
import squants.market.Money
|
import squants.market.Money
|
||||||
|
@ -25,13 +24,6 @@ case class Box(label: String, weight: Mass, price: Money)
|
||||||
val box = Box("aBox", 30.kg, 10.CHF)
|
val box = Box("aBox", 30.kg, 10.CHF)
|
||||||
|
|
||||||
assertions:
|
assertions:
|
||||||
// {
|
|
||||||
// val p: BePropertyTypeProvider[Person] = new BePropertyTypeProvider[Person](person)
|
|
||||||
// p.asInstanceOf[BePropertyTypeProvider[Person] {
|
|
||||||
// def adult: AssertionProperty
|
|
||||||
// }].adult
|
|
||||||
// }
|
|
||||||
|
|
||||||
/* be.property assertions */
|
/* be.property assertions */
|
||||||
// assert(person.adult, ...)
|
// assert(person.adult, ...)
|
||||||
person should be.adult
|
person should be.adult
|
||||||
|
@ -48,7 +40,14 @@ case class Box(label: String, weight: Mass, price: Money)
|
||||||
// or adult is a property but braces are needed to resolve the type provider
|
// or adult is a property but braces are needed to resolve the type provider
|
||||||
(person should be_).adult
|
(person should be_).adult
|
||||||
|
|
||||||
/* should have assertions */
|
List(1) should have head satisfying === 1
|
||||||
|
box should have weight satisfying === 3.kg
|
||||||
|
List() should have size satisfying >= 0
|
||||||
|
List(3) should have size satisfying !== 0
|
||||||
|
List(50, 2, 3) should have head satisfying < 100
|
||||||
|
box should have weight satisfying <= 3.0.kg
|
||||||
|
|
||||||
|
/* should have assertions */
|
||||||
// // assert(List(1).head == 1, ...)
|
// // assert(List(1).head == 1, ...)
|
||||||
// List(1) should have head 1
|
// List(1) should have head 1
|
||||||
// // assert(box.weight == 30.kg, ...)
|
// // assert(box.weight == 30.kg, ...)
|
||||||
|
|
|
@ -12,6 +12,13 @@ object ShouldHaveTypeProvider:
|
||||||
): Expr[Any] =
|
): Expr[Any] =
|
||||||
import quotes.reflect.*
|
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]
|
val subjectTypeRepr = TypeRepr.of[T]
|
||||||
|
|
||||||
/** Given a refinable current type, and the symbol of a arity-0 method or a
|
/** Given a refinable current type, and the symbol of a arity-0 method or a
|
||||||
|
@ -25,30 +32,22 @@ object ShouldHaveTypeProvider:
|
||||||
// The type of the field, or the return type of the method.
|
// The type of the field, or the return type of the method.
|
||||||
val fieldTypeRepr = subjectTypeRepr.memberType(symbol).widen
|
val fieldTypeRepr = subjectTypeRepr.memberType(symbol).widen
|
||||||
|
|
||||||
// hack to get the equivalent of `TypeRepr.of[Function1[?, ?]]` (i.e. an arity-1 function type constructor).
|
val appliedType = AppliedType(typeConstructor[? => ?], List(fieldTypeRepr, TypeRepr.of[Boolean]))
|
||||||
// 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
|
|
||||||
val function1TypeConstructor = AppliedType.unapply(TypeRepr.of[? => ?].asInstanceOf[AppliedType])._1
|
|
||||||
|
|
||||||
val appliedType = AppliedType(function1TypeConstructor, List(fieldTypeRepr, TypeRepr.of[Boolean]))
|
|
||||||
|
|
||||||
// Generates the "type" of the method to be generated for the refinement.
|
|
||||||
// The first parameter is the list of arguments, the second is a function returning
|
|
||||||
// the type of arguments, and the third one is a function returning the return type
|
|
||||||
// of the method.
|
|
||||||
// In this case: arg is the name of the parameter;
|
|
||||||
// _ => List(fieldTypeRepr) returns a list with the type of arg;
|
|
||||||
// _ => TypeRepr.of[AssertionProperty] returns the (reflection) type of the method.
|
|
||||||
val methodType = MethodType(List("arg"))(
|
val methodType = MethodType(List("arg"))(
|
||||||
_ => List(appliedType),
|
_ => List(appliedType),
|
||||||
_ => TypeRepr.of[AssertionProperty]
|
_ => TypeRepr.of[AssertionProperty]
|
||||||
).widen
|
)
|
||||||
// returns the refinement of currentTypeRepr -
|
|
||||||
// symbol.name is the name of the method,
|
val chainedMethodType = MethodType(List("arg"))(
|
||||||
// methodType is its type.
|
_ => List(TypeRepr.of[satisfying.type]),
|
||||||
val refinement = Refinement(currentTypeRepr, symbol.name, methodType)
|
_ => AppliedType(typeConstructor[SatisfyingNotEquals[?]], List(fieldTypeRepr))
|
||||||
refinement
|
)
|
||||||
|
|
||||||
|
Refinement(
|
||||||
|
Refinement(currentTypeRepr, symbol.name, chainedMethodType),
|
||||||
|
symbol.name,
|
||||||
|
methodType
|
||||||
|
)
|
||||||
|
|
||||||
/** Refines a type according to a list of fields or methods of arity 0.
|
/** Refines a type according to a list of fields or methods of arity 0.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue