mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-04 06:30:17 +00:00
fix(compiler): Unknown service method call is ignored [LNG-273] (#957)
* Remove property token adjustment
* Revert "Remove property token adjustment"
This reverts commit 27d72f2bff
.
* Remove scope word
* Refactor
* Refactor, add error
* Add unit tests
* Fix specifier for struct
This commit is contained in:
parent
077dc8ff13
commit
5a3c5e6666
@ -9,6 +9,8 @@ import aqua.semantics.rules.names.NamesAlgebra
|
||||
import aqua.semantics.rules.report.ReportAlgebra
|
||||
import aqua.semantics.rules.types.TypesAlgebra
|
||||
import aqua.types.*
|
||||
import aqua.helpers.syntax.optiont.*
|
||||
|
||||
import cats.Monad
|
||||
import cats.data.{NonEmptyList, OptionT}
|
||||
import cats.instances.list.*
|
||||
@ -336,92 +338,113 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
|
||||
def ensureIsString(v: ValueToken[S]): Alg[Boolean] =
|
||||
valueToStringRaw(v).map(_.isDefined)
|
||||
|
||||
private def callArrowFromAbility(
|
||||
private def abilityArrow(
|
||||
ab: Name[S],
|
||||
at: NamedType,
|
||||
funcName: Name[S]
|
||||
): Option[CallArrowRaw] = at.arrows
|
||||
.get(funcName.value)
|
||||
.map(arrowType =>
|
||||
CallArrowRaw.ability(
|
||||
ab.value,
|
||||
funcName.value,
|
||||
arrowType
|
||||
): OptionT[Alg, CallArrowRaw] =
|
||||
OptionT
|
||||
.fromOption(
|
||||
at.arrows.get(funcName.value)
|
||||
)
|
||||
.map(arrowType =>
|
||||
CallArrowRaw.ability(
|
||||
ab.value,
|
||||
funcName.value,
|
||||
arrowType
|
||||
)
|
||||
)
|
||||
.flatTapNone(
|
||||
report.error(
|
||||
funcName,
|
||||
s"Function `${funcName.value}` is not defined " +
|
||||
s"in `${ab.value}` of type `${at.fullName}`, " +
|
||||
s"available functions: ${at.arrows.keys.mkString(", ")}"
|
||||
)
|
||||
)
|
||||
|
||||
private def callArrowFromFunc(
|
||||
funcName: Name[S]
|
||||
): OptionT[Alg, CallArrowRaw] =
|
||||
OptionT(
|
||||
N.readArrow(funcName)
|
||||
).map(arrowType =>
|
||||
CallArrowRaw.func(
|
||||
funcName = funcName.value,
|
||||
baseType = arrowType
|
||||
)
|
||||
)
|
||||
|
||||
private def callArrowFromAbility(
|
||||
ab: NamedTypeToken[S],
|
||||
funcName: Name[S]
|
||||
): OptionT[Alg, CallArrowRaw] = {
|
||||
lazy val nameTypeFromAbility = OptionT(
|
||||
N.read(ab.asName, mustBeDefined = false)
|
||||
).collect { case nt: (AbilityType | ServiceType) => ab.asName -> nt }
|
||||
|
||||
lazy val nameTypeFromService = for {
|
||||
st <- OptionT(
|
||||
T.getType(ab.value)
|
||||
).collect { case st: ServiceType => st }
|
||||
rename <- OptionT(
|
||||
A.getServiceRename(ab)
|
||||
)
|
||||
renamed = ab.asName.rename(rename)
|
||||
} yield renamed -> st
|
||||
|
||||
lazy val nameType = nameTypeFromAbility orElse nameTypeFromService.widen
|
||||
|
||||
lazy val fromArrow = OptionT(
|
||||
A.getArrow(ab, funcName)
|
||||
).map(at =>
|
||||
CallArrowRaw
|
||||
.ability(
|
||||
abilityName = ab.value,
|
||||
funcName = funcName.value,
|
||||
baseType = at
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* If we have a name and a type, get function from ability.
|
||||
* Otherwise, get function from arrow.
|
||||
*
|
||||
* It is done like so to not report irrelevant errors.
|
||||
*/
|
||||
nameType.flatTransformT {
|
||||
case Some((name, nt)) => abilityArrow(name, nt, funcName)
|
||||
case _ => fromArrow
|
||||
}
|
||||
}
|
||||
|
||||
private def callArrowToRaw(
|
||||
callArrow: CallArrowToken[S]
|
||||
): Alg[Option[CallArrowRaw]] =
|
||||
for {
|
||||
raw <- callArrow.ability.fold(
|
||||
for {
|
||||
myabeArrowType <- N.readArrow(callArrow.funcName)
|
||||
} yield myabeArrowType
|
||||
.map(arrowType =>
|
||||
CallArrowRaw.func(
|
||||
funcName = callArrow.funcName.value,
|
||||
baseType = arrowType
|
||||
(for {
|
||||
raw <- callArrow.ability
|
||||
.fold(callArrowFromFunc(callArrow.funcName))(ab =>
|
||||
callArrowFromAbility(ab, callArrow.funcName)
|
||||
)
|
||||
domain = raw.baseType.domain
|
||||
_ <- OptionT.withFilterF(
|
||||
T.checkArgumentsNumber(
|
||||
callArrow.funcName,
|
||||
domain.length,
|
||||
callArrow.args.length
|
||||
)
|
||||
)
|
||||
args <- callArrow.args
|
||||
.zip(domain.toList)
|
||||
.traverse { case (tkn, tp) =>
|
||||
for {
|
||||
valueRaw <- OptionT(valueToRaw(tkn))
|
||||
_ <- OptionT.withFilterF(
|
||||
T.ensureTypeMatches(tkn, tp, valueRaw.`type`)
|
||||
)
|
||||
)
|
||||
)(ab =>
|
||||
N.read(ab.asName, mustBeDefined = false).flatMap {
|
||||
case Some(nt: (AbilityType | ServiceType)) =>
|
||||
callArrowFromAbility(ab.asName, nt, callArrow.funcName).pure
|
||||
case _ =>
|
||||
T.getType(ab.value).flatMap {
|
||||
case Some(st: ServiceType) =>
|
||||
OptionT(A.getServiceRename(ab))
|
||||
.subflatMap(rename =>
|
||||
callArrowFromAbility(
|
||||
ab.asName.rename(rename),
|
||||
st,
|
||||
callArrow.funcName
|
||||
)
|
||||
)
|
||||
.value
|
||||
case _ =>
|
||||
A.getArrow(ab, callArrow.funcName).map {
|
||||
case Some(at) =>
|
||||
CallArrowRaw
|
||||
.ability(
|
||||
abilityName = ab.value,
|
||||
funcName = callArrow.funcName.value,
|
||||
baseType = at
|
||||
)
|
||||
.some
|
||||
case _ => none
|
||||
}
|
||||
}
|
||||
} yield valueRaw
|
||||
}
|
||||
)
|
||||
result <- raw.flatTraverse(r =>
|
||||
val arr = r.baseType
|
||||
for {
|
||||
argsCheck <- T.checkArgumentsNumber(
|
||||
callArrow.funcName,
|
||||
arr.domain.length,
|
||||
callArrow.args.length
|
||||
)
|
||||
args <- Option
|
||||
.when(argsCheck)(callArrow.args zip arr.domain.toList)
|
||||
.traverse(
|
||||
_.flatTraverse { case (tkn, tp) =>
|
||||
for {
|
||||
maybeValueRaw <- valueToRaw(tkn)
|
||||
checked <- maybeValueRaw.flatTraverse(v =>
|
||||
T.ensureTypeMatches(tkn, tp, v.`type`)
|
||||
.map(Option.when(_)(v))
|
||||
)
|
||||
} yield checked.toList
|
||||
}
|
||||
)
|
||||
result = args
|
||||
.filter(_.length == arr.domain.length)
|
||||
.map(args => r.copy(arguments = args))
|
||||
} yield result
|
||||
)
|
||||
} yield result
|
||||
} yield raw.copy(arguments = args)).value
|
||||
|
||||
}
|
||||
|
||||
|
@ -241,7 +241,7 @@ class TypesInterpreter[S[_], X](using
|
||||
report
|
||||
.error(
|
||||
op,
|
||||
s"Expected scope type to resolve an arrow '${op.name.value}' or a type with this property. Got: $rootT"
|
||||
s"Expected type to resolve an arrow '${op.name.value}' or a type with this property. Got: $rootT"
|
||||
)
|
||||
.as(None)
|
||||
)(t => State.pure(Some(FunctorRaw(op.name.value, t))))
|
||||
|
@ -796,4 +796,47 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
it should "report an error on unknown service methods" in {
|
||||
val script = """
|
||||
|service Test("test"):
|
||||
| call(i: i32) -> i32
|
||||
|
|
||||
|func test():
|
||||
| Test.unknown("test")
|
||||
|""".stripMargin
|
||||
|
||||
insideSemErrors(script) { errors =>
|
||||
errors.toChain.toList.exists {
|
||||
case RulesViolated(_, messages) =>
|
||||
messages.exists(_.contains("not defined")) &&
|
||||
messages.exists(_.contains("unknown"))
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "report an error on unknown ability arrows" in {
|
||||
val script = """
|
||||
|ability Test:
|
||||
| call(i: i32) -> i32
|
||||
|
|
||||
|func test():
|
||||
| call = (i: i32) -> i32:
|
||||
| <- i
|
||||
|
|
||||
| t = Test(call)
|
||||
|
|
||||
| t.unknown("test")
|
||||
|""".stripMargin
|
||||
|
||||
insideSemErrors(script) { errors =>
|
||||
errors.toChain.toList.exists {
|
||||
case RulesViolated(_, messages) =>
|
||||
messages.exists(_.contains("not defined")) &&
|
||||
messages.exists(_.contains("unknown"))
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -286,7 +286,12 @@ case class OptionType(element: Type) extends BoxType {
|
||||
}
|
||||
|
||||
sealed trait NamedType extends Type {
|
||||
|
||||
def specifier: String
|
||||
def name: String
|
||||
|
||||
final def fullName: String = s"$specifier $name"
|
||||
|
||||
def fields: NonEmptyMap[String, Type]
|
||||
|
||||
/**
|
||||
@ -363,8 +368,10 @@ sealed trait NamedType extends Type {
|
||||
case class StructType(name: String, fields: NonEmptyMap[String, Type])
|
||||
extends DataType with NamedType {
|
||||
|
||||
override val specifier: String = "data"
|
||||
|
||||
override def toString: String =
|
||||
s"$name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"
|
||||
s"$fullName{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"
|
||||
}
|
||||
|
||||
case class StreamMapType(element: Type) extends DataType {
|
||||
@ -378,15 +385,19 @@ object StreamMapType {
|
||||
|
||||
case class ServiceType(name: String, fields: NonEmptyMap[String, ArrowType]) extends NamedType {
|
||||
|
||||
override val specifier: String = "service"
|
||||
|
||||
override def toString: String =
|
||||
s"service $name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"
|
||||
s"$fullName{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"
|
||||
}
|
||||
|
||||
// Ability is an unordered collection of labelled types and arrows
|
||||
case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends NamedType {
|
||||
|
||||
override val specifier: String = "ability"
|
||||
|
||||
override def toString: String =
|
||||
s"ability $name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"
|
||||
s"$fullName{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"
|
||||
}
|
||||
|
||||
object AbilityType {
|
||||
|
31
utils/helpers/src/main/scala/aqua/syntax/optiont.scala
Normal file
31
utils/helpers/src/main/scala/aqua/syntax/optiont.scala
Normal file
@ -0,0 +1,31 @@
|
||||
package aqua.helpers.syntax
|
||||
|
||||
import cats.{Functor, Monad}
|
||||
import cats.data.OptionT
|
||||
import cats.syntax.functor.*
|
||||
|
||||
object optiont {
|
||||
|
||||
extension (o: OptionT.type) {
|
||||
|
||||
/**
|
||||
* Lifts a `F[Boolean]` into a `OptionT[F, Unit]` that is `None` if the
|
||||
* condition is `false` and `Some(())` otherwise.
|
||||
*
|
||||
* This is useful for filtering a `OptionT[F, A]` inside a for-comprehension.
|
||||
*/
|
||||
def withFilterF[F[_]: Functor](fb: F[Boolean]): OptionT[F, Unit] =
|
||||
OptionT.liftF(fb).filter(identity).void
|
||||
}
|
||||
|
||||
extension [F[_], A](o: OptionT[F, A]) {
|
||||
|
||||
/**
|
||||
* Like `flatTransform` but the transformation function returns a `OptionT[F, B]`.
|
||||
*/
|
||||
def flatTransformT[B](
|
||||
f: Option[A] => OptionT[F, B]
|
||||
)(using F: Monad[F]): OptionT[F, B] =
|
||||
o.flatTransform(f.andThen(_.value))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user