ex4 predef string weird error

This commit is contained in:
Claudio Maggioni 2024-12-22 17:33:19 +01:00
parent cbe868975b
commit efa648207f
6 changed files with 70 additions and 28 deletions

View file

@ -12,7 +12,7 @@ object GenerativeFluentAssertionsDSL:
import quotes.reflect.* import quotes.reflect.*
subject match { subject match {
case '{ $subject: Iterable[Any] } => '{ ContainsVerb($subject) } case '{ $subject: Iterable[elementType] } => '{ ContainsVerb($subject) }
case _ => report.errorAndAbort( case _ => report.errorAndAbort(
"assertion subject must be an Iterable[?] in order to use a 'contain' assertion", "assertion subject must be an Iterable[?] in order to use a 'contain' assertion",
subject subject
@ -45,6 +45,13 @@ object GenerativeFluentAssertionsDSL:
.applyDynamic($propertyNameExpr)(satisfying.notEquals[T]($obj, $ord)) .applyDynamic($propertyNameExpr)(satisfying.notEquals[T]($obj, $ord))
.$asInstanceOf$[AssertionProperty] .$asInstanceOf$[AssertionProperty]
} }
case '{ (${ typeProvider }: ContainsPropertyTypeProvider[subjectType])
.applyDynamic($propertyNameExpr: String)(satisfying)
.$asInstanceOf$[SatisfyingNotEquals[T]] } => '{
$typeProvider
.applyDynamic($propertyNameExpr)(satisfying.notEquals[T]($obj, $ord))
.$asInstanceOf$[AssertionProperty]
}
} }
/** Entrypoint. This macro method takes an expression composed of /** Entrypoint. This macro method takes an expression composed of
@ -124,7 +131,6 @@ object GenerativeFluentAssertionsDSL:
.applyDynamic($propertyNameExpr: String)($valueExpr: Condition[valueType]) .applyDynamic($propertyNameExpr: String)($valueExpr: Condition[valueType])
.$asInstanceOf$[AssertionProperty] .$asInstanceOf$[AssertionProperty]
} => } =>
println(assertionExpr.show)
val subjectExpr = '{ $typeProvider.subject } val subjectExpr = '{ $typeProvider.subject }
val negatedExpr = '{ $typeProvider.negated } val negatedExpr = '{ $typeProvider.negated }
generateShouldHaveAssertion[subjectType, valueType]( generateShouldHaveAssertion[subjectType, valueType](
@ -145,7 +151,14 @@ object GenerativeFluentAssertionsDSL:
.$asInstanceOf$[AssertionProperty] .$asInstanceOf$[AssertionProperty]
} => } =>
'{} // placeholder '{} // placeholder
case _ => case '{
(${ typeProvider }: ContainsPropertyTypeProvider[subjectType])
.applyDynamic($propertyNameExpr: String)($valueExpr: Condition[valueType])
.$asInstanceOf$[AssertionProperty]
} =>
'{} // placeholder
case a =>
println(a.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

View file

@ -1,6 +1,7 @@
package ch.usi.si.msde.edsl.lecture10 package ch.usi.si.msde.edsl.lecture10
import scala.language.dynamics import scala.language.dynamics
import scala.quoted.Expr
sealed trait AssertionProperty sealed trait AssertionProperty
case class NegatedAssertionProperty(prop: AssertionProperty) extends AssertionProperty case class NegatedAssertionProperty(prop: AssertionProperty) extends AssertionProperty
@ -67,17 +68,19 @@ case object have extends HaveVerb(false)
case object contain extends AssertionDSLVerb case object contain extends AssertionDSLVerb
case object `with` extends AssertionDSLVerb case object `with` extends AssertionDSLVerb
case class ContainsVerb[T](subject: Iterable[T]): case class ContainsVerb[T](subject: Iterable[T])
transparent inline def allElements(verb: `with`.type) = ${
ShouldContainTypeProvider.generateShouldContainTypeProvider[T]('subject, 'All) extension [T](inline containsVerb: ContainsVerb[T])
transparent inline def allElements(inline verb: `with`.type) = ${
ShouldContainTypeProvider.generateShouldContainTypeProvider[T]('containsVerb, 'All)
} }
transparent inline def someElements(verb: `with`.type) = ${ transparent inline def someElements(inline verb: `with`.type) = ${
ShouldContainTypeProvider.generateShouldContainTypeProvider[T]('subject, 'Some) ShouldContainTypeProvider.generateShouldContainTypeProvider[T]('containsVerb, 'Some)
} }
transparent inline def noElements(verb: `with`.type) = ${ transparent inline def noElements(inline verb: `with`.type) = ${
ShouldContainTypeProvider.generateShouldContainTypeProvider[T]('subject, 'None) ShouldContainTypeProvider.generateShouldContainTypeProvider[T]('containsVerb, 'None)
} }
/** Implicit class to add 'should' assertions to a generic type T. /** Implicit class to add 'should' assertions to a generic type T.
@ -88,6 +91,13 @@ case class ContainsVerb[T](subject: Iterable[T]):
extension [T](subject: T) extension [T](subject: T)
def should(property: AssertionProperty) = ??? def should(property: AssertionProperty) = ???
// we use the 'inline' modifier on subject as well to make sure that the expression
// generated by the 'shouldContainImpl' macro is a one-liner, so it does not contain
// intermediate '$proxy<n>' variable declarations that would make expression pattern
// matching impossible. Using the 'inline' modifier requires all extension methods
// to be 'inline', so we separate the original 'should be' implementation in the
// extension methods block above.
extension [T](inline subject: T)
transparent inline def should(inline verbWord: be_.type) = ${ transparent inline def should(inline verbWord: be_.type) = ${
ShouldBeTypeProvider.generateShouldBeTypeProvider[T]('subject) ShouldBeTypeProvider.generateShouldBeTypeProvider[T]('subject)
} }
@ -109,7 +119,13 @@ 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) = ${ // We define here the 'should contain' extension method. It is not possible to
GenerativeFluentAssertionsDSL.shouldContainImpl('subject) // write an extension method for Iterable[T] as that method would shadow the
// 'should be' and 'should have' assertions defined for all objects. Therefore,
// we check the subject is an instance of Iterable[T] with a macro, and we use
// report.errorAndAbort to provide a clear error message stating that an Iterable[T]
// is required for this kind of assertion.
transparent inline def should(inline verb: contain.type) = ${
GenerativeFluentAssertionsDSL.shouldContainImpl('subject)
} }

View file

@ -2,11 +2,11 @@ package ch.usi.si.msde.edsl.lecture10
// import scala.language.postfixOps // import scala.language.postfixOps
import squants.mass.MassConversions.MassConversions import ch.usi.si.msde.edsl.lecture10.GenerativeFluentAssertionsDSL.*
import squants.market.MoneyConversions.MoneyConversions
import GenerativeFluentAssertionsDSL.*
import squants.mass.Mass
import squants.market.Money import squants.market.Money
import squants.market.MoneyConversions.MoneyConversions
import squants.mass.Mass
import squants.mass.MassConversions.MassConversions
extension (value: Double) def i = Complex(0, value) extension (value: Double) def i = Complex(0, value)
@ -47,6 +47,11 @@ case class Box(label: String, weight: Mass, price: Money)
List(50, 2, 3) should have head satisfying < 100 List(50, 2, 3) should have head satisfying < 100
box should !(!have) weight satisfying <= 300.0.kg box should !(!have) weight satisfying <= 300.0.kg
// List("bar","foo","","alice") should contain someElements `with` size satisfying === 0
List(Seq(), Seq(), Seq()) should contain allElements `with` size satisfying >= 0
// List(Seq(), Seq(), Seq()) should contain allElements `with` size satisfying !== 0
List(List(1,2), List(20,1), List(3,4)) should contain noElements `with` head satisfying === 3
/* should have assertions */ /* 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

View file

@ -53,8 +53,6 @@ object ShouldBeTypeProvider:
members members
) )
// println(refinedType)
// This is the way to extract a (reflection) type to a // This is the way to extract a (reflection) type to a
// type that can be used in some quoted code. // type that can be used in some quoted code.
// it's the equivalent of .asExpr for expressions, // it's the equivalent of .asExpr for expressions,
@ -66,7 +64,6 @@ object ShouldBeTypeProvider:
val p = BePropertyTypeProvider[T]($subject) val p = BePropertyTypeProvider[T]($subject)
p.asInstanceOf[tpe] p.asInstanceOf[tpe]
} }
println(res.show)
res res
end ShouldBeTypeProvider end ShouldBeTypeProvider

View file

@ -7,11 +7,16 @@ object ShouldContainTypeProvider:
/** The type provider for the should have assertion, which generates a refined /** The type provider for the should have assertion, which generates a refined
* structural type for type T. * structural type for type T.
*/ */
def generateShouldContainTypeProvider[T](subject: Expr[Iterable[T]], kind: Expr[ContainsAssertionKind])(using Type[T])(using def generateShouldContainTypeProvider[T](containsExpr: Expr[ContainsVerb[T]], kind: Expr[ContainsAssertionKind])
Quotes (using Type[T], Quotes): Expr[Any] =
): Expr[Any] =
import quotes.reflect.* import quotes.reflect.*
println("containsExpr: " + containsExpr.show)
println("kind: " + kind.show)
val subject: Expr[Iterable[T]] = containsExpr match
case '{ ContainsVerb.apply[T]($s: Iterable[T]) } => s
// hack to get the equivalent of `TypeRepr.of[Function1[?, ?]]` (i.e. an arity-1 function type constructor). // 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, // 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 // if re-applied to some `I` input type and `O` output type would be incompatible with `TypeRepr.of[I => O]` for
@ -19,7 +24,10 @@ object ShouldContainTypeProvider:
def typeConstructor[U](using Type[U]): TypeRepr = def typeConstructor[U](using Type[U]): TypeRepr =
AppliedType.unapply(TypeRepr.of[U].asInstanceOf[AppliedType])._1 AppliedType.unapply(TypeRepr.of[U].asInstanceOf[AppliedType])._1
val subjectTypeRepr = TypeRepr.of[T] val subjectTypeRepr = TypeRepr.of[T].widen
println("subjectTypeRepr: " + subjectTypeRepr)
println("string: " + TypeRepr.of[String].simplified)
/** 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
* field of type foo: X, generates a refinement containing a method with * field of type foo: X, generates a refinement containing a method with
@ -72,12 +80,15 @@ object ShouldContainTypeProvider:
Flags.Synthetic Flags.Synthetic
) )
println("typeSymbol: " + subjectTypeRepr.typeSymbol)
println("fields: " + fields)
println("arityZeroMethods: " + arityZeroMethods)
val refinedType = refineTypeBySymbols( val refinedType = refineTypeBySymbols(
TypeRepr.of[ContainsPropertyTypeProvider[T]], TypeRepr.of[ContainsPropertyTypeProvider[T]],
fields ++ arityZeroMethods fields ++ arityZeroMethods
) )
println(refinedType.show)
// This is the way to extract a (reflection) type to a // This is the way to extract a (reflection) type to a
// type that can be used in some quoted code. // type that can be used in some quoted code.
@ -86,6 +97,8 @@ object ShouldContainTypeProvider:
// exact type is unknown until compilation time. // exact type is unknown until compilation time.
refinedType.asType match refinedType.asType match
case '[tpe] => // tpe is the exact refined type case '[tpe] => // tpe is the exact refined type
'{ ContainsPropertyTypeProvider[T]($subject, $kind).asInstanceOf[tpe] } val a = '{ ContainsPropertyTypeProvider[T]($subject, $kind).asInstanceOf[tpe] }
println(a.show)
a
end ShouldContainTypeProvider end ShouldContainTypeProvider

View file

@ -77,8 +77,6 @@ object ShouldHaveTypeProvider:
fields ++ arityZeroMethods fields ++ arityZeroMethods
) )
println(refinedType.show)
// This is the way to extract a (reflection) type to a // This is the way to extract a (reflection) type to a
// type that can be used in some quoted code. // type that can be used in some quoted code.
// it's the equivalent of .asExpr for expressions, // it's the equivalent of .asExpr for expressions,