mirror of
https://github.com/fluencelabs/aqua.git
synced 2025-04-25 06:52:13 +00:00
fix(compiler): Type check arrow calls on services and abilities [LNG-315] (#1037)
* Rewrite resolveIntoArrow * Refactor * Refactor resolveIntoCopy * Refactor resolveIntoIndex * Refactor resolveIntoField * Fix test * Remove package-lock.json * Add tests * Add comment
This commit is contained in:
parent
5241f522d8
commit
d46ee0347f
@ -7,7 +7,7 @@ ability WorkerJob:
|
|||||||
|
|
||||||
func disjoint_run{WorkerJob}() -> -> string:
|
func disjoint_run{WorkerJob}() -> -> string:
|
||||||
run = func () -> string:
|
run = func () -> string:
|
||||||
r <- WorkerJob.runOnSingleWorker()
|
r <- WorkerJob.runOnSingleWorker("worker")
|
||||||
<- r
|
<- r
|
||||||
<- run
|
<- run
|
||||||
|
|
||||||
|
@ -53,29 +53,38 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
|
|||||||
private def resolveSingleProperty(rootType: Type, op: PropertyOp[S]): Alg[Option[PropertyRaw]] =
|
private def resolveSingleProperty(rootType: Type, op: PropertyOp[S]): Alg[Option[PropertyRaw]] =
|
||||||
op match {
|
op match {
|
||||||
case op: IntoField[S] =>
|
case op: IntoField[S] =>
|
||||||
T.resolveField(rootType, op)
|
OptionT(T.resolveIntoField(op, rootType))
|
||||||
case op: IntoArrow[S] =>
|
.map(
|
||||||
for {
|
_.fold(
|
||||||
maybeArgs <- op.arguments.traverse(valueToRaw)
|
field = t => IntoFieldRaw(op.value, t),
|
||||||
arrowProp <- maybeArgs.sequence.flatTraverse(
|
property = t => FunctorRaw(op.value, t)
|
||||||
T.resolveArrow(rootType, op, _)
|
)
|
||||||
)
|
)
|
||||||
} yield arrowProp
|
.value
|
||||||
|
case op: IntoArrow[S] =>
|
||||||
|
(for {
|
||||||
|
args <- op.arguments.traverse(arg => OptionT(valueToRaw(arg)))
|
||||||
|
argTypes = args.map(_.`type`)
|
||||||
|
arrowType <- OptionT(T.resolveIntoArrow(op, rootType, argTypes))
|
||||||
|
} yield IntoArrowRaw(op.name.value, arrowType, args)).value
|
||||||
case op: IntoCopy[S] =>
|
case op: IntoCopy[S] =>
|
||||||
(for {
|
(for {
|
||||||
_ <- OptionT.liftF(
|
_ <- OptionT.liftF(
|
||||||
reportNamedArgsDuplicates(op.args)
|
reportNamedArgsDuplicates(op.args)
|
||||||
)
|
)
|
||||||
fields <- op.args.traverse(arg => OptionT(valueToRaw(arg.argValue)).map(arg -> _))
|
args <- op.args.traverse(arg =>
|
||||||
prop <- OptionT(T.resolveCopy(op, rootType, fields))
|
OptionT(valueToRaw(arg.argValue)).map(
|
||||||
} yield prop).value
|
arg.argName.value -> _
|
||||||
case op: IntoIndex[S] =>
|
)
|
||||||
for {
|
|
||||||
maybeIdx <- op.idx.fold(LiteralRaw.Zero.some.pure)(valueToRaw)
|
|
||||||
idxProp <- maybeIdx.flatTraverse(
|
|
||||||
T.resolveIndex(rootType, op, _)
|
|
||||||
)
|
)
|
||||||
} yield idxProp
|
argsTypes = args.map { case (_, raw) => raw.`type` }
|
||||||
|
structType <- OptionT(T.resolveIntoCopy(op, rootType, argsTypes))
|
||||||
|
} yield IntoCopyRaw(structType, args.toNem)).value
|
||||||
|
case op: IntoIndex[S] =>
|
||||||
|
(for {
|
||||||
|
idx <- OptionT(op.idx.fold(LiteralRaw.Zero.some.pure)(valueToRaw))
|
||||||
|
valueType <- OptionT(T.resolveIntoIndex(op, rootType, idx.`type`))
|
||||||
|
} yield IntoIndexRaw(idx, valueType)).value
|
||||||
}
|
}
|
||||||
|
|
||||||
def valueToRaw(v: ValueToken[S]): Alg[Option[ValueRaw]] =
|
def valueToRaw(v: ValueToken[S]): Alg[Option[ValueRaw]] =
|
||||||
|
@ -40,21 +40,74 @@ trait TypesAlgebra[S[_], Alg[_]] {
|
|||||||
|
|
||||||
def defineAlias(name: NamedTypeToken[S], target: Type): Alg[Boolean]
|
def defineAlias(name: NamedTypeToken[S], target: Type): Alg[Boolean]
|
||||||
|
|
||||||
def resolveIndex(rootT: Type, op: IntoIndex[S], idx: ValueRaw): Alg[Option[PropertyRaw]]
|
/**
|
||||||
|
* Resolve `IntoIndex` property on value with `rootT` type
|
||||||
def resolveCopy(
|
*
|
||||||
token: IntoCopy[S],
|
* @param op property to resolve
|
||||||
|
* @param rootT type of the value to which property is applied
|
||||||
|
* @param idxType type of the index
|
||||||
|
* @return type of the value at given index if property application is valid
|
||||||
|
*/
|
||||||
|
def resolveIntoIndex(
|
||||||
|
op: IntoIndex[S],
|
||||||
rootT: Type,
|
rootT: Type,
|
||||||
fields: NonEmptyList[(NamedArg[S], ValueRaw)]
|
idxType: Type
|
||||||
): Alg[Option[PropertyRaw]]
|
): Alg[Option[DataType]]
|
||||||
|
|
||||||
def resolveField(rootT: Type, op: IntoField[S]): Alg[Option[PropertyRaw]]
|
/**
|
||||||
|
* Resolve `IntoCopy` property on value with `rootT` type
|
||||||
def resolveArrow(
|
*
|
||||||
|
* @param op property to resolve
|
||||||
|
* @param rootT type of the value to which property is applied
|
||||||
|
* @param types types of arguments passed
|
||||||
|
* @return struct type if property application is valid
|
||||||
|
* @note `types` should correspond to `op.args`
|
||||||
|
*/
|
||||||
|
def resolveIntoCopy(
|
||||||
|
op: IntoCopy[S],
|
||||||
rootT: Type,
|
rootT: Type,
|
||||||
|
types: NonEmptyList[Type]
|
||||||
|
): Alg[Option[StructType]]
|
||||||
|
|
||||||
|
enum IntoFieldRes(`type`: Type) {
|
||||||
|
case Field(`type`: Type) extends IntoFieldRes(`type`)
|
||||||
|
case Property(`type`: Type) extends IntoFieldRes(`type`)
|
||||||
|
|
||||||
|
def fold[A](field: Type => A, property: Type => A): A =
|
||||||
|
this match {
|
||||||
|
case Field(t) => field(t)
|
||||||
|
case Property(t) => property(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve `IntoField` property on value with `rootT` type
|
||||||
|
*
|
||||||
|
* @param op property to resolve
|
||||||
|
* @param rootT type of the value to which property is applied
|
||||||
|
* @return if property application is valid, return
|
||||||
|
* Field(type) if it's a field of rootT (fields of structs or abilities),
|
||||||
|
* Property(type) if it's a property of rootT (functors of collections)
|
||||||
|
*/
|
||||||
|
def resolveIntoField(
|
||||||
|
op: IntoField[S],
|
||||||
|
rootT: Type
|
||||||
|
): Alg[Option[IntoFieldRes]]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve `IntoArrow` property on value with `rootT` type
|
||||||
|
*
|
||||||
|
* @param op property to resolve
|
||||||
|
* @param rootT type of the value to which property is applied
|
||||||
|
* @param types types of arguments passed
|
||||||
|
* @return arrow type if property application is valid
|
||||||
|
* @note `types` should correspond to `op.arguments`
|
||||||
|
*/
|
||||||
|
def resolveIntoArrow(
|
||||||
op: IntoArrow[S],
|
op: IntoArrow[S],
|
||||||
arguments: List[ValueRaw]
|
rootT: Type,
|
||||||
): Alg[Option[PropertyRaw]]
|
types: List[Type]
|
||||||
|
): Alg[Option[ArrowType]]
|
||||||
|
|
||||||
def ensureValuesComparable(token: Token[S], left: Type, right: Type): Alg[Boolean]
|
def ensureValuesComparable(token: Token[S], left: Type, right: Type): Alg[Boolean]
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package aqua.semantics.rules.types
|
package aqua.semantics.rules.types
|
||||||
|
|
||||||
|
import aqua.errors.Errors.internalError
|
||||||
import aqua.parser.lexer.*
|
import aqua.parser.lexer.*
|
||||||
import aqua.raw.value.*
|
import aqua.raw.value.*
|
||||||
import aqua.semantics.Levenshtein
|
import aqua.semantics.Levenshtein
|
||||||
@ -17,6 +18,7 @@ import cats.syntax.apply.*
|
|||||||
import cats.syntax.flatMap.*
|
import cats.syntax.flatMap.*
|
||||||
import cats.syntax.foldable.*
|
import cats.syntax.foldable.*
|
||||||
import cats.syntax.functor.*
|
import cats.syntax.functor.*
|
||||||
|
import cats.syntax.monad.*
|
||||||
import cats.syntax.option.*
|
import cats.syntax.option.*
|
||||||
import cats.syntax.traverse.*
|
import cats.syntax.traverse.*
|
||||||
import cats.{Applicative, ~>}
|
import cats.{Applicative, ~>}
|
||||||
@ -187,132 +189,177 @@ class TypesInterpreter[S[_], X](using
|
|||||||
.as(true)
|
.as(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def resolveField(rootT: Type, op: IntoField[S]): State[X, Option[PropertyRaw]] = {
|
override def resolveIntoField(
|
||||||
|
op: IntoField[S],
|
||||||
|
rootT: Type
|
||||||
|
): State[X, Option[IntoFieldRes]] = {
|
||||||
rootT match {
|
rootT match {
|
||||||
case nt: NamedType =>
|
case nt: NamedType =>
|
||||||
nt.fields(op.value)
|
nt.fields(op.value) match {
|
||||||
.fold(
|
case Some(t) =>
|
||||||
|
locations
|
||||||
|
.pointFieldLocation(nt.name, op.value, op)
|
||||||
|
.as(Some(IntoFieldRes.Field(t)))
|
||||||
|
case None =>
|
||||||
|
val fields = nt.fields.keys.map(k => s"`$k`").toList.mkString(", ")
|
||||||
report
|
report
|
||||||
.error(
|
.error(
|
||||||
op,
|
op,
|
||||||
s"Field `${op.value}` not found in type `${nt.name}`, available: ${nt.fields.toNel.toList.map(_._1).mkString(", ")}"
|
s"Field `${op.value}` not found in type `${nt.name}`, available: $fields"
|
||||||
)
|
)
|
||||||
.as(None)
|
.as(None)
|
||||||
) { t =>
|
}
|
||||||
locations.pointFieldLocation(nt.name, op.value, op).as(Some(IntoFieldRaw(op.value, t)))
|
|
||||||
}
|
|
||||||
case t =>
|
case t =>
|
||||||
t.properties
|
t.properties
|
||||||
.get(op.value)
|
.get(op.value) match {
|
||||||
.fold(
|
case Some(t) =>
|
||||||
|
State.pure(Some(IntoFieldRes.Property(t)))
|
||||||
|
case None =>
|
||||||
report
|
report
|
||||||
.error(
|
.error(
|
||||||
op,
|
op,
|
||||||
s"Expected data type to resolve a field '${op.value}' or a type with this property. Got: $rootT"
|
s"Property `${op.value}` not found in type `$t`"
|
||||||
)
|
)
|
||||||
.as(None)
|
.as(None)
|
||||||
)(t => State.pure(Some(FunctorRaw(op.value, t))))
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def resolveArrow(
|
override def resolveIntoArrow(
|
||||||
rootT: Type,
|
|
||||||
op: IntoArrow[S],
|
op: IntoArrow[S],
|
||||||
arguments: List[ValueRaw]
|
rootT: Type,
|
||||||
): State[X, Option[PropertyRaw]] = {
|
types: List[Type]
|
||||||
|
): State[X, Option[ArrowType]] = {
|
||||||
|
/* Safeguard to check condition on arguments */
|
||||||
|
if (op.arguments.length != types.length)
|
||||||
|
internalError(s"Invalid arguments, lists do not match: ${op.arguments} and $types")
|
||||||
|
|
||||||
|
val opName = op.name.value
|
||||||
|
|
||||||
rootT match {
|
rootT match {
|
||||||
case ab: GeneralAbilityType =>
|
case ab: GeneralAbilityType =>
|
||||||
val name = ab.name
|
val abName = ab.fullName
|
||||||
val fields = ab.fields
|
|
||||||
lazy val fieldNames = fields.toNel.toList.map(_._1).mkString(", ")
|
ab.fields.lookup(opName) match {
|
||||||
fields(op.name.value)
|
case Some(at: ArrowType) =>
|
||||||
.fold(
|
val reportNotEnoughArguments =
|
||||||
report
|
/* Report at position of arrow application */
|
||||||
.error(
|
|
||||||
op,
|
|
||||||
s"Arrow `${op.name.value}` not found in type `$name`, " +
|
|
||||||
s"available: $fieldNames"
|
|
||||||
)
|
|
||||||
.as(None)
|
|
||||||
) {
|
|
||||||
case at @ ArrowType(_, _) =>
|
|
||||||
locations
|
|
||||||
.pointFieldLocation(name, op.name.value, op)
|
|
||||||
.as(Some(IntoArrowRaw(op.name.value, at, arguments)))
|
|
||||||
case _ =>
|
|
||||||
report
|
report
|
||||||
.error(
|
.error(
|
||||||
op,
|
op,
|
||||||
s"Unexpected. `${op.name.value}` must be an arrow."
|
s"Not enough arguments for arrow `$opName` in `$abName`, " +
|
||||||
|
s"expected: ${at.domain.length}, given: ${op.arguments.length}"
|
||||||
)
|
)
|
||||||
.as(None)
|
.whenA(op.arguments.length < at.domain.length)
|
||||||
}
|
val reportTooManyArguments =
|
||||||
case t =>
|
/* Report once at position of the first extra argument */
|
||||||
t.properties
|
op.arguments.drop(at.domain.length).headOption.traverse_ { arg =>
|
||||||
.get(op.name.value)
|
report
|
||||||
.fold(
|
.error(
|
||||||
report
|
arg,
|
||||||
.error(
|
s"Too many arguments for arrow `$opName` in `$abName`, " +
|
||||||
op,
|
s"expected: ${at.domain.length}, given: ${op.arguments.length}"
|
||||||
s"Expected type to resolve an arrow '${op.name.value}' or a type with this property. Got: $rootT"
|
)
|
||||||
)
|
}
|
||||||
.as(None)
|
val checkArgumentTypes =
|
||||||
)(t => State.pure(Some(FunctorRaw(op.name.value, t))))
|
op.arguments
|
||||||
|
.zip(types)
|
||||||
|
.zip(at.domain.toList)
|
||||||
|
.forallM { case ((arg, argType), expectedType) =>
|
||||||
|
ensureTypeMatches(arg, expectedType, argType)
|
||||||
|
}
|
||||||
|
|
||||||
|
locations.pointFieldLocation(abName, opName, op) *>
|
||||||
|
reportNotEnoughArguments *>
|
||||||
|
reportTooManyArguments *>
|
||||||
|
checkArgumentTypes.map(typesMatch =>
|
||||||
|
Option.when(
|
||||||
|
typesMatch && at.domain.length == op.arguments.length
|
||||||
|
)(at)
|
||||||
|
)
|
||||||
|
|
||||||
|
case Some(t) =>
|
||||||
|
report
|
||||||
|
.error(op, s"Field `$opName` has non arrow type `$t` in `$abName`")
|
||||||
|
.as(None)
|
||||||
|
case None =>
|
||||||
|
val available = ab.arrowFields.keys.map(k => s"`$k`").mkString(", ")
|
||||||
|
report
|
||||||
|
.error(op, s"Arrow `$opName` not found in `$abName`, available: $available")
|
||||||
|
.as(None)
|
||||||
|
}
|
||||||
|
case t =>
|
||||||
|
/* NOTE: Arrows are only supported on services and abilities,
|
||||||
|
(`.copy(...)` for structs is resolved by separate method) */
|
||||||
|
report
|
||||||
|
.error(op, s"Arrow `$opName` not found in `$t`")
|
||||||
|
.as(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO actually it's stateless, exists there just for reporting needs
|
override def resolveIntoCopy(
|
||||||
override def resolveCopy(
|
op: IntoCopy[S],
|
||||||
token: IntoCopy[S],
|
|
||||||
rootT: Type,
|
rootT: Type,
|
||||||
args: NonEmptyList[(NamedArg[S], ValueRaw)]
|
types: NonEmptyList[Type]
|
||||||
): State[X, Option[PropertyRaw]] =
|
): State[X, Option[StructType]] = {
|
||||||
|
if (op.args.length != types.length)
|
||||||
|
internalError(s"Invalid arguments, lists do not match: ${op.args} and $types")
|
||||||
|
|
||||||
rootT match {
|
rootT match {
|
||||||
case st: StructType =>
|
case st: StructType =>
|
||||||
args.forallM { case (arg, value) =>
|
op.args
|
||||||
val fieldName = arg.argName.value
|
.zip(types)
|
||||||
st.fields.lookup(fieldName) match {
|
.forallM { case (arg, argType) =>
|
||||||
case Some(t) =>
|
val fieldName = arg.argName.value
|
||||||
ensureTypeMatches(arg.argValue, t, value.`type`)
|
st.fields.lookup(fieldName) match {
|
||||||
case None =>
|
case Some(fieldType) =>
|
||||||
report.error(arg.argName, s"No field with name '$fieldName' in $rootT").as(false)
|
ensureTypeMatches(arg.argValue, fieldType, argType)
|
||||||
|
case None =>
|
||||||
|
report
|
||||||
|
.error(
|
||||||
|
arg.argName,
|
||||||
|
s"No field with name '$fieldName' in `$st`"
|
||||||
|
)
|
||||||
|
.as(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.map(
|
.map(Option.when(_)(st))
|
||||||
Option.when(_)(
|
case t =>
|
||||||
IntoCopyRaw(
|
report
|
||||||
st,
|
.error(
|
||||||
args.map { case (arg, value) =>
|
op,
|
||||||
arg.argName.value -> value
|
s"Non data type `$t` does not support `.copy`"
|
||||||
}.toNem
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
.as(None)
|
||||||
|
|
||||||
case _ =>
|
|
||||||
report.error(token, s"Expected $rootT to be a data type").as(None)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO actually it's stateless, exists there just for reporting needs
|
override def resolveIntoIndex(
|
||||||
override def resolveIndex(
|
|
||||||
rootT: Type,
|
|
||||||
op: IntoIndex[S],
|
op: IntoIndex[S],
|
||||||
idx: ValueRaw
|
rootT: Type,
|
||||||
): State[X, Option[PropertyRaw]] =
|
idxType: Type
|
||||||
if (!ScalarType.i64.acceptsValueOf(idx.`type`))
|
): State[X, Option[DataType]] =
|
||||||
report.error(op, s"Expected numeric index, got $idx").as(None)
|
ensureTypeOneOf(
|
||||||
else
|
op.idx.getOrElse(op),
|
||||||
rootT match {
|
ScalarType.integer,
|
||||||
case ot: OptionType =>
|
idxType
|
||||||
op.idx.fold(
|
) *> (rootT match {
|
||||||
State.pure(Some(IntoIndexRaw(idx, ot.element)))
|
case ot: OptionType =>
|
||||||
)(v => report.error(v, s"Options might have only one element, use ! to get it").as(None))
|
op.idx.fold(State.pure(Some(ot.element)))(v =>
|
||||||
case rt: CollectionType =>
|
// TODO: Is this a right place to report this error?
|
||||||
State.pure(Some(IntoIndexRaw(idx, rt.element)))
|
// It is not a type error, but rather a syntax error
|
||||||
case _ =>
|
report.error(v, s"Options might have only one element, use ! to get it").as(None)
|
||||||
report.error(op, s"Expected $rootT to be a collection type").as(None)
|
)
|
||||||
}
|
case rt: CollectionType =>
|
||||||
|
State.pure(Some(rt.element))
|
||||||
|
case t =>
|
||||||
|
report
|
||||||
|
.error(
|
||||||
|
op,
|
||||||
|
s"Non collection type `$t` does not support indexing"
|
||||||
|
)
|
||||||
|
.as(None)
|
||||||
|
})
|
||||||
|
|
||||||
override def ensureValuesComparable(
|
override def ensureValuesComparable(
|
||||||
token: Token[S],
|
token: Token[S],
|
||||||
@ -423,7 +470,7 @@ class TypesInterpreter[S[_], X](using
|
|||||||
): State[X, Boolean] = for {
|
): State[X, Boolean] = for {
|
||||||
/* Check that required fields are present
|
/* Check that required fields are present
|
||||||
among arguments and have correct types */
|
among arguments and have correct types */
|
||||||
enough <- expected.fields.toNel.traverse { case (name, typ) =>
|
enough <- expected.fields.toNel.forallM { case (name, typ) =>
|
||||||
arguments.lookup(name) match {
|
arguments.lookup(name) match {
|
||||||
case Some(arg -> givenType) =>
|
case Some(arg -> givenType) =>
|
||||||
ensureTypeMatches(arg.argValue, typ, givenType)
|
ensureTypeMatches(arg.argValue, typ, givenType)
|
||||||
@ -435,7 +482,7 @@ class TypesInterpreter[S[_], X](using
|
|||||||
)
|
)
|
||||||
.as(false)
|
.as(false)
|
||||||
}
|
}
|
||||||
}.map(_.forall(identity))
|
}
|
||||||
expectedKeys = expected.fields.keys.toNonEmptyList
|
expectedKeys = expected.fields.keys.toNonEmptyList
|
||||||
/* Report unexpected arguments */
|
/* Report unexpected arguments */
|
||||||
_ <- arguments.toNel.traverse_ { case (name, arg -> typ) =>
|
_ <- arguments.toNel.traverse_ { case (name, arg -> typ) =>
|
||||||
|
@ -65,12 +65,30 @@ class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside {
|
|||||||
def stream(values: ValueToken[Id]*): CollectionToken[Id] =
|
def stream(values: ValueToken[Id]*): CollectionToken[Id] =
|
||||||
CollectionToken[Id](CollectionToken.Mode.StreamMode, values.toList)
|
CollectionToken[Id](CollectionToken.Mode.StreamMode, values.toList)
|
||||||
|
|
||||||
|
def serviceCall(
|
||||||
|
srv: String,
|
||||||
|
method: String,
|
||||||
|
args: List[ValueToken[Id]] = Nil
|
||||||
|
): PropertyToken[Id] =
|
||||||
|
PropertyToken(
|
||||||
|
variable(srv),
|
||||||
|
NonEmptyList.of(
|
||||||
|
IntoArrow(
|
||||||
|
Name[Id](method),
|
||||||
|
args
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def allPairs[A](list: List[A]): List[(A, A)] = for {
|
def allPairs[A](list: List[A]): List[(A, A)] = for {
|
||||||
a <- list
|
a <- list
|
||||||
b <- list
|
b <- list
|
||||||
} yield (a, b)
|
} yield (a, b)
|
||||||
|
|
||||||
def genState(vars: Map[String, Type] = Map.empty) = {
|
def genState(
|
||||||
|
vars: Map[String, Type] = Map.empty,
|
||||||
|
types: Map[String, Type] = Map.empty
|
||||||
|
) = {
|
||||||
val init = RawContext.blank.copy(
|
val init = RawContext.blank.copy(
|
||||||
parts = Chain
|
parts = Chain
|
||||||
.fromSeq(ConstantRaw.defaultConstants())
|
.fromSeq(ConstantRaw.defaultConstants())
|
||||||
@ -88,6 +106,10 @@ class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside {
|
|||||||
) :: _
|
) :: _
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
.focus(_.types)
|
||||||
|
.modify(types.foldLeft(_) { case (st, (name, t)) =>
|
||||||
|
st.defineType(NamedTypeToken(name), t)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
def valueOfType(t: Type)(
|
def valueOfType(t: Type)(
|
||||||
@ -626,4 +648,72 @@ class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside {
|
|||||||
value.`type` shouldBe OptionType(BottomType)
|
value.`type` shouldBe OptionType(BottomType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it should "type check service calls" in {
|
||||||
|
val srvName = "TestSrv"
|
||||||
|
val methodName = "testMethod"
|
||||||
|
val methodType = ArrowType(
|
||||||
|
ProductType(ScalarType.i8 :: ScalarType.string :: Nil),
|
||||||
|
ProductType(Nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test(args: List[ValueToken[Id]], vars: Map[String, Type] = Map.empty) = {
|
||||||
|
val state = genState(
|
||||||
|
vars,
|
||||||
|
types = Map(
|
||||||
|
srvName -> ServiceType(
|
||||||
|
srvName,
|
||||||
|
NonEmptyMap.of(
|
||||||
|
methodName -> methodType
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val call = serviceCall(srvName, methodName, args)
|
||||||
|
|
||||||
|
val alg = algebra()
|
||||||
|
val (st, res) = alg
|
||||||
|
.valueToRaw(call)
|
||||||
|
.run(state)
|
||||||
|
.value
|
||||||
|
|
||||||
|
res shouldBe None
|
||||||
|
atLeast(1, st.errors.toList) shouldBe a[RulesViolated[Id]]
|
||||||
|
}
|
||||||
|
|
||||||
|
// not enough arguments
|
||||||
|
// TestSrv.testMethod()
|
||||||
|
test(List.empty)
|
||||||
|
// TestSrv.testMethod(42)
|
||||||
|
test(literal("42", LiteralType.unsigned) :: Nil)
|
||||||
|
// TestSrv.testMethod(var)
|
||||||
|
test(variable("var") :: Nil, Map("var" -> ScalarType.i8))
|
||||||
|
|
||||||
|
// wrong argument type
|
||||||
|
// TestSrv.testMethod([42, var])
|
||||||
|
test(
|
||||||
|
array(literal("42", LiteralType.unsigned), variable("var")) :: Nil,
|
||||||
|
Map("var" -> ScalarType.i8)
|
||||||
|
)
|
||||||
|
// TestSrv.testMethod(42, var)
|
||||||
|
test(
|
||||||
|
literal("42", LiteralType.unsigned) :: variable("var") :: Nil,
|
||||||
|
Map("var" -> ScalarType.i64)
|
||||||
|
)
|
||||||
|
// TestSrv.testMethod("test", var)
|
||||||
|
test(
|
||||||
|
literal("test", LiteralType.string) :: variable("var") :: Nil,
|
||||||
|
Map("var" -> ScalarType.string)
|
||||||
|
)
|
||||||
|
|
||||||
|
// too many arguments
|
||||||
|
// TestSrv.testMethod(42, "test", var)
|
||||||
|
test(
|
||||||
|
literal("42", LiteralType.unsigned) ::
|
||||||
|
literal("test", LiteralType.string) ::
|
||||||
|
variable("var") :: Nil,
|
||||||
|
Map("var" -> ScalarType.string)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,6 +370,11 @@ sealed trait NamedType extends Type {
|
|||||||
|
|
||||||
def fields: NonEmptyMap[String, Type]
|
def fields: NonEmptyMap[String, Type]
|
||||||
|
|
||||||
|
def arrowFields: Map[String, ArrowType] =
|
||||||
|
fields.toSortedMap.collect { case (name, at: ArrowType) =>
|
||||||
|
name -> at
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all fields defined in this type and its fields of named type.
|
* Get all fields defined in this type and its fields of named type.
|
||||||
* Paths to fields are returned **without** type name
|
* Paths to fields are returned **without** type name
|
||||||
|
Loading…
x
Reference in New Issue
Block a user