fix(compiler): Import abilities with use [LNG-324] (#1077)

* Add IntoApply

* Savepoint

* Add backtrack

* Return ability name

* Add service resolution

* Return import ability

* Add test

* Rewrite toDottedName

* Rewrite ability resolution

* Fix offset

* Add tests

* Add test

* Add comments

* Add test

---------

Co-authored-by: Dima <dmitry.shakhtarin@fluence.ai>
This commit is contained in:
InversionSpaces 2024-02-29 14:56:05 +01:00 committed by GitHub
parent d7fef3db5f
commit a6c8e75c27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 569 additions and 132 deletions

View File

@ -87,6 +87,25 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers with Inside {
inside(funcs)(test)
)
def through(peer: ValueModel) =
MakeRes.hop(peer)
val relay = VarRaw("-relay-", ScalarType.string)
def getDataSrv(name: String, varName: String, t: Type) = {
CallServiceRes(
LiteralModel.quote("getDataSrv"),
name,
CallRes(Nil, Some(CallModel.Export(varName, t))),
LiteralModel.fromRaw(ValueRaw.InitPeerId)
).leaf
}
private val init = LiteralModel.fromRaw(ValueRaw.InitPeerId)
private def join(vm: VarModel, size: ValueModel) =
ResBuilder.join(vm, size, init)
"aqua compiler" should "compile a simple snippet to the right context" in {
val src = Map(
@ -115,25 +134,6 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers with Inside {
}
}
def through(peer: ValueModel) =
MakeRes.hop(peer)
val relay = VarRaw("-relay-", ScalarType.string)
def getDataSrv(name: String, varName: String, t: Type) = {
CallServiceRes(
LiteralModel.quote("getDataSrv"),
name,
CallRes(Nil, Some(CallModel.Export(varName, t))),
LiteralModel.fromRaw(ValueRaw.InitPeerId)
).leaf
}
private val init = LiteralModel.fromRaw(ValueRaw.InitPeerId)
private def join(vm: VarModel, size: ValueModel) =
ResBuilder.join(vm, size, init)
it should "create right topology" in {
val src = Map(
"index.aqua" ->
@ -502,4 +502,385 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers with Inside {
main.body.equalsOrShowDiff(expected) should be(true)
}
}
val moduleNames = List("Test", "Imp", "Sub", "Path").inits
.takeWhile(_.nonEmpty)
.map(_.mkString("."))
.toList
it should "import function with `use`" in {
def test(name: String, rename: Option[String]) = {
val src = Map(
"main.aqua" ->
s"""aqua Main
|export main
|use "import.aqua"${rename.fold("")(" as " + _)}
|func main() -> i32:
| <- ${rename.getOrElse(name)}.foo()
|""".stripMargin
)
val imports = Map(
"import.aqua" ->
s"""aqua $name declares foo
|func foo() -> i32:
| <- 42
|""".stripMargin
)
val transformCfg = TransformConfig(relayVarName = None)
insideRes(src, imports, transformCfg)(
"main"
) { case main :: _ =>
val expected = XorRes.wrap(
respCall(transformCfg, LiteralModel.number(42), initPeer),
errorCall(transformCfg, 0, initPeer)
)
main.body.equalsOrShowDiff(expected) should be(true)
}
}
moduleNames.foreach { name =>
val rename = "Imported"
withClue(s"Testing $name") {
test(name, None)
}
withClue(s"Testing $name as $rename") {
test(name, rename.some)
}
}
}
it should "import service with `use`" in {
def test(name: String, rename: Option[String]) = {
val srvName = rename.getOrElse(name) + ".Srv"
val src = Map(
"main.aqua" ->
s"""aqua Main
|export main
|use "import.aqua"${rename.fold("")(" as " + _)}
|
|func main() -> i32:
| a <- $srvName.call()
| $srvName "res-id"
| b <- $srvName.call()
| <- a + b
|""".stripMargin
)
val imports = Map(
"import.aqua" ->
s"""aqua $name declares *
|service Srv("def-id"):
| call() -> i32
|""".stripMargin
)
val transformCfg = TransformConfig(relayVarName = None)
insideRes(src, imports, transformCfg)(
"main"
) { case main :: _ =>
def call(id: String, exp: CallModel.Export) =
CallServiceRes(
LiteralModel.quote(id),
"call",
CallRes(Nil, Some(exp)),
initPeer
).leaf
val a = CallModel.Export("ret", ScalarType.i32)
val b = CallModel.Export("ret-0", ScalarType.i32)
val add = CallModel.Export("add", ScalarType.i32)
val expected = XorRes.wrap(
SeqRes.wrap(
call("def-id", a),
call("res-id", b),
CallServiceRes(
LiteralModel.quote("math"),
"add",
CallRes(List(a.asVar, b.asVar), Some(add)),
initPeer
).leaf,
respCall(transformCfg, add.asVar, initPeer)
),
errorCall(transformCfg, 0, initPeer)
)
main.body.equalsOrShowDiff(expected) should be(true)
}
}
moduleNames.foreach { name =>
val rename = "Imported"
withClue(s"Testing $name") {
test(name, None)
}
withClue(s"Testing $name as $rename") {
test(name, rename.some)
}
}
}
it should "import ability with `use`" in {
def test(name: String, rename: Option[String]) = {
val abName = rename.getOrElse(name) + ".Ab"
val src = Map(
"main.aqua" ->
s"""aqua Main
|export main
|use "import.aqua"${rename.fold("")(" as " + _)}
|func useAb{$abName}() -> i32:
| <- $abName.a
|
|func main() -> i32:
| ab = $abName(a = 42)
| <- useAb{ab}()
|""".stripMargin
)
val imports = Map(
"import.aqua" ->
s"""aqua $name declares *
|ability Ab:
| a: i32
|""".stripMargin
)
val transformCfg = TransformConfig(relayVarName = None)
insideRes(src, imports, transformCfg)(
"main"
) { case main :: _ =>
val ap = CallModel.Export("literal_ap", LiteralType.unsigned)
val props = ap.copy(name = "literal_props")
val expected = XorRes.wrap(
SeqRes.wrap(
// NOTE: Result of compilation is inefficient
ApRes(LiteralModel.number(42), ap).leaf,
ApRes(ap.asVar, props).leaf,
respCall(transformCfg, props.asVar, initPeer)
),
errorCall(transformCfg, 0, initPeer)
)
main.body.equalsOrShowDiff(expected) should be(true)
}
}
moduleNames.foreach { name =>
val rename = "Imported"
withClue(s"Testing $name") {
test(name, None)
}
withClue(s"Testing $name as $rename") {
test(name, rename.some)
}
}
}
it should "import ability (nested) with `use`" in {
def test(name: String, rename: Option[String]) = {
val impName = rename.getOrElse(name)
val abName = impName + ".Ab"
val src = Map(
"main.aqua" ->
s"""aqua Main
|export main
|use "import.aqua"${rename.fold("")(" as " + _)}
|func useAb{$abName}() -> i32:
| <- $abName.ab1.ab0.call($abName.ab1.ab0.a)
|
|func main() -> i32:
| id = (x: i32) -> i32:
| <- x
| ab0 = $impName.Ab0(a = 42, call = id)
| ab1 = $impName.Ab1(ab0 = ab0)
| ab = $abName(ab1 = ab1)
| <- useAb{ab}()
|""".stripMargin
)
val imports = Map(
"import.aqua" ->
s"""aqua $name declares *
|ability Ab0:
| a: i32
| call(x: i32) -> i32
|
|ability Ab1:
| ab0: Ab0
|
|ability Ab:
| ab1: Ab1
|""".stripMargin
)
val transformCfg = TransformConfig(relayVarName = None)
insideRes(src, imports, transformCfg)(
"main"
) { case main :: _ =>
val ap = CallModel.Export("literal_ap", LiteralType.unsigned)
val props = ap.copy(name = "literal_props")
val expected = XorRes.wrap(
SeqRes.wrap(
// NOTE: Result of compilation is inefficient
ApRes(LiteralModel.number(42), ap).leaf,
ApRes(ap.asVar, props).leaf,
respCall(transformCfg, props.asVar, initPeer)
),
errorCall(transformCfg, 0, initPeer)
)
main.body.equalsOrShowDiff(expected) should be(true)
}
}
moduleNames.foreach { name =>
val rename = "Imported"
withClue(s"Testing $name") {
test(name, None)
}
withClue(s"Testing $name as $rename") {
test(name, rename.some)
}
}
}
it should "import abilities in chain of imports" in {
case class Imp(header: String, rename: Option[String] = None) {
val as = rename.fold("")(" as " + _)
val name = rename.getOrElse(header)
override def toString: String = s"$header$as"
}
def test(hierarchy: List[Imp]) = {
hierarchy.nonEmpty should be(true)
def genImp(header: String, imported: Imp, path: String): String = {
s"""aqua ${header} declares *
|use "${path}"${imported.as}
|
|ability Inner:
| ab: ${imported.name}.Outer
|
|ability Outer:
| ab: Inner
|
|func create{${imported.name}.Outer}() -> Inner:
| ab = Inner(ab = ${imported.name}.Outer)
| <- ab
|""".stripMargin
}
val base = Imp("Base")
val basePath = "base.aqua"
val baseCode = s"""aqua Base declares *
|
|ability Outer:
| a: i32
| call(x: i32) -> i32
|""".stripMargin
val withPaths = hierarchy.zipWithIndex.map { case (imp, idx) =>
imp -> s"import$idx.aqua"
}
val nexts = withPaths.tail :+ (base -> basePath)
val imports = withPaths
.zip(nexts)
.map { case ((curImp, curPath), (nextImp, nextPath)) =>
curPath -> genImp(curImp.header, nextImp, nextPath)
}
.appended(basePath -> baseCode)
.toMap
val importStmts = withPaths.map { case (imp, path) =>
s"use \"${path}\"${imp.as}"
}.prepended(s"use \"${basePath}\"")
val createStmts = hierarchy.reverse.zipWithIndex.flatMap { case (imp, idx) =>
s"abIn${idx + 1} = ${imp.name}.create{abOut$idx}()" ::
s"abOut${idx + 1} = ${imp.name}.Outer(ab = abIn${idx + 1})" ::
Nil
}.prepended("abOut0 = Base.Outer(a = 42, call = id)")
val lastAb = s"abOut${hierarchy.size}" + ".ab".repeat(hierarchy.size * 2)
val main = s"""aqua Main
|export main
|${importStmts.mkString("\n")}
|
|func main() -> i32:
| id = (x: i32) -> i32:
| <- x
| ${createStmts.mkString("\n ")}
| <- $lastAb.call($lastAb.a)
|""".stripMargin
val src = Map(
"main.aqua" -> main
)
val transformCfg = TransformConfig(relayVarName = None)
insideRes(src, imports, transformCfg)(
"main"
) { case main :: _ =>
val ap = CallModel.Export("literal_ap", LiteralType.unsigned)
val props = ap.copy(name = "literal_props")
val expected = XorRes.wrap(
SeqRes.wrap(
// NOTE: Result of compilation is inefficient
ApRes(LiteralModel.number(42), ap).leaf,
ApRes(ap.asVar, props).leaf,
respCall(transformCfg, props.asVar, initPeer)
),
errorCall(transformCfg, 0, initPeer)
)
main.body.equalsOrShowDiff(expected) should be(true)
}
}
// Simple
(1 to 10).map(i => (1 to i).map(n => Imp(s"Imp$n")).toList).foreach { h =>
withClue(s"Testing ${h.mkString(" -> ")}") {
test(h)
}
}
// With renaming
(1 to 10)
.map(i =>
(1 to i)
.map(n =>
// Rename every second one
Imp(s"Imp$n", s"Renamed$n".some.filter(_ => n % 2 == 0))
)
.toList
)
.foreach { h =>
withClue(s"Testing ${h.mkString(" -> ")}") {
test(h)
}
}
// With subpath
(1 to 10)
.map(i =>
(1 to i)
.map(n => s"Imp$n")
.inits
.takeWhile(_.nonEmpty)
.map(p => Imp(p.mkString(".")))
.toList
)
.foreach { h =>
withClue(s"Testing ${h.mkString(" -> ")}") {
test(h)
}
}
}
}

View File

@ -35,6 +35,11 @@ enum NamedArg[F[_]] extends Token[F] {
case Full(name, value) => Full(name.mapK(fk), value.mapK(fk))
case Short(variable) => Short(variable.mapK(fk))
}
override def toString: String = this match {
case Full(name, value) => s"$name = $value"
case Short(variable) => variable.toString
}
}
object NamedArg {

View File

@ -1,20 +1,20 @@
package aqua.parser.lexer
import aqua.parser.lexer.CallArrowToken.CallBraces
import aqua.parser.lexer.NamedArg.namedArgs
import aqua.parser.lexer.Token.*
import aqua.parser.lift.LiftParser
import aqua.parser.lift.LiftParser.*
import aqua.parser.lift.Span
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
import aqua.types.LiteralType
import aqua.parser.lexer.CallArrowToken.CallBraces
import aqua.parser.lexer.NamedArg.namedArgs
import cats.~>
import cats.data.{NonEmptyList, NonEmptyMap}
import cats.parse.{Numbers, Parser as P, Parser0 as P0}
import cats.syntax.comonad.*
import cats.syntax.functor.*
import cats.{Comonad, Functor}
import cats.~>
import scala.language.postfixOps
sealed trait PropertyOp[F[_]] extends Token[F] {
@ -36,7 +36,7 @@ case class IntoField[F[_]: Comonad](name: F[String]) extends PropertyOp[F] {
override def mapK[K[_]: Comonad](fk: F ~> K): PropertyOp[K] = copy(fk(name))
def value: String = name.extract
lazy val value: String = name.extract
override def toString: String = name.extract
}
@ -46,6 +46,8 @@ case class IntoIndex[F[_]: Comonad](point: F[Unit], idx: Option[ValueToken[F]])
override def as[T](v: T): F[T] = point.as(v)
override def mapK[K[_]: Comonad](fk: F ~> K): IntoIndex[K] = copy(fk(point), idx.map(_.mapK(fk)))
override def toString: String = s"[$idx]"
}
case class IntoCopy[F[_]: Comonad](
@ -56,6 +58,27 @@ case class IntoCopy[F[_]: Comonad](
override def mapK[K[_]: Comonad](fk: F ~> K): IntoCopy[K] =
copy(fk(point), args.map(_.mapK(fk)))
override def toString: String = s".copy(${args.map(_.toString).toList.mkString(", ")})"
}
/**
* WARNING: This is parsed when we have parens after a name, but `IntoArrow` failed to parse.
* This is a case of imported named type, e.g. `Some.Imported.Module.DefinedAbility(...)`
* It is transformed into `NamedTypeValue` in `ValuesAlgebra`
* TODO: Eliminate `IntoArrow`, unify it with this property
*/
case class IntoApply[F[_]: Comonad](
argsF: F[NonEmptyList[NamedArg[F]]]
) extends PropertyOp[F] {
lazy val args: NonEmptyList[NamedArg[F]] = argsF.extract
override def as[T](v: T): F[T] = argsF.as(v)
override def mapK[K[_]: Comonad](fk: F ~> K): IntoApply[K] =
copy(fk(argsF.map(_.map(_.mapK(fk)))))
override def toString: String = s"(${args.map(_.toString).toList.mkString(", ")})"
}
object PropertyOp {
@ -87,8 +110,19 @@ object PropertyOp {
}
}
private val parseApply: P[PropertyOp[Span.S]] =
namedArgs.lift.map(IntoApply.apply)
private val parseOp: P[PropertyOp[Span.S]] =
P.oneOf(parseCopy.backtrack :: parseArrow.backtrack :: parseField :: parseIdx :: Nil)
P.oneOf(
// NOTE: order is important here
// intoApply has lower priority than intoArrow
parseCopy.backtrack ::
parseArrow.backtrack ::
parseField ::
parseIdx ::
parseApply.backtrack :: Nil
)
val ops: P[NonEmptyList[PropertyOp[Span.S]]] =
parseOp.rep

View File

@ -123,7 +123,9 @@ case class ArrowTypeToken[S[_]: Comonad](
abilities.map(_.mapK(fk))
)
def argTypes: List[TypeToken[S]] = abilities ++ args.map(_._2)
lazy val absWithArgs: List[(Option[Name[S]], TypeToken[S])] = abilities.map(n => Some(n.asName) -> n) ++ args
lazy val absWithArgs: List[(Option[Name[S]], TypeToken[S])] =
abilities.map(n => Some(n.asName) -> n) ++ args
}
object ArrowTypeToken {
@ -136,9 +138,9 @@ object ArrowTypeToken {
).map(_.toList)
// {SomeAb, SecondAb} for NamedTypeToken
def abilities(): P0[List[NamedTypeToken[S]]] =
(`{` *> comma(`Class`.surroundedBy(`/s*`).lift.map(NamedTypeToken(_)))
.map(_.toList) <* `}`).?.map(_.getOrElse(List.empty))
def abilities(): P0[List[NamedTypeToken[S]]] = (
`{` *> comma(NamedTypeToken.dotted).map(_.toList) <* `}`
).?.map(_.getOrElse(List.empty))
def `arrowdef`(argTypeP: P[TypeToken[Span.S]]): P[ArrowTypeToken[Span.S]] =
((abilities() ~ comma0(argTypeP)).with1 ~ ` -> `.lift ~

View File

@ -42,112 +42,105 @@ case class PropertyToken[F[_]: Comonad](
name.forall(c => !c.isLetter || c.isUpper)
/**
* This method tries to convert property token to
* property token with dotted var name inside value token.
*
* Next properties pattern is untouched:
* Class (field)*
*
* Next properties pattern is transformed:
* (Class)* (CONST | field) ..props..
* ^^^^^^^^^^^^^^^^^^^^^^^^
* this part is transformed to dotted name.
* Try to transform this token to imported ability access
* e.g. in `Some.Imported.Module.Ab.innerAb.call(...)`:
* - `Some.Imported.Module` is imported module name
* - `Ab.innerAb.call(...)` is ability access
* so it should be handled as `(Some.Imported.Module.Ab).innerAb.call(...)`
* ^^^^^^^^^^^^^^^^^^^^^^^
* ability name inside `VarToken` as one string
* but we don't know this in advance, so this method returns
* a list of all possible (imported module name, ability access value token) pairs
* so calling code can check what prefix is valid imported module name and
* handle the corresponding ability access value token.
*/
private def toDottedName: Option[ValueToken[F]] = value match {
case VarToken(name) =>
// Pattern `Class (field)*` is ability access
// and should not be transformed
val isAbility = isClass(name.value) && properties.forall {
case f @ IntoField(_) => isField(f.value)
case _ => true
def toAbility: List[(NamedTypeToken[F], ValueToken[F])] =
value match {
// NOTE: guard against recursion: if dot is alredy in the name, do not transform
case VarToken(name) if !name.value.contains(".") =>
val fields = properties.toList.takeWhile {
case IntoField(_) => true
case _ => false
}.collect { case f @ IntoField(_) => f.value }.toList
val names = name.value +: fields
fields.inits
// do not use the last field
// those cases are handled in `toCallArrow` and `toNamedValue`
.drop(1)
.map { init =>
// Length of the import name
val importLength = init.length + 1
// Length of the imported name
val nameLength = importLength + 1
val newProps = NonEmptyList.fromList(
properties.toList.drop(importLength)
)
val newName = name.rename(names.take(nameLength).mkString("."))
val importAbility = name.rename(names.take(importLength).mkString(".")).asTypeToken
val varToken = VarToken(newName)
val token = newProps.fold(varToken)(ps => PropertyToken(varToken, ps))
importAbility -> token
}
.toList
// test shorter prefixes first
.reverse
case _ => Nil
}
if (isAbility) none
else {
// Gather prefix of properties that are IntoField
val props = name.value +: properties.toList.view.map {
/**
* Try to convert this token into `CallArrowToken`
* e.g. `Some.Imported.Module.call(...)`
* ^^^^^^^^^^^^^^^^^^^^ ^^^^
* ability name function name
*/
def toCallArrow: Option[CallArrowToken[F]] = (
value,
properties.last
) match {
case (VarToken(name), IntoArrow(funcName, args)) =>
properties.init.traverse {
case IntoField(name) => name.extract.some
case _ => none
}.takeWhile(_.isDefined).flatten.toList
val propsWithIndex = props.zipWithIndex
// Find first property that is not Class
val classesTill = propsWithIndex.find { case (name, _) =>
!isClass(name)
}.collect { case (_, idx) =>
idx
}.getOrElse(props.length)
// Find last property after classes
// that is CONST or field
val lastSuitable = propsWithIndex
.take(classesTill)
.findLast { case (name, _) =>
isConst(name) || isField(name)
}
.collect { case (_, idx) => idx }
lastSuitable.map(last =>
val newProps = NonEmptyList.fromList(
properties.toList.drop(last + 1)
}.map { fields =>
val imported = name
.rename(
(name.value +: fields).mkString(".")
)
val newName = props.take(last + 1).mkString(".")
val varToken = VarToken(name.rename(newName))
.asTypeToken
newProps.fold(varToken)(props => PropertyToken(varToken, props))
)
}
case _ => none
}
/**
* This method tries to convert property token to
* call arrow token.
*
* Next properties pattern is transformed:
* (Class)+ arrow()
* ^^^^^^^
* this part is transformed to ability name.
*/
private def toCallArrow: Option[CallArrowToken[F]] = value match {
case VarToken(name) =>
val ability = properties.init.traverse {
case f @ IntoField(_) => f.value.some
case _ => none
}.map(
name.value +: _
).filter(
_.forall(isClass)
).map(props => name.rename(props.mkString(".")))
(properties.last, ability) match {
case (IntoArrow(funcName, args), Some(ability)) =>
CallArrowToken(
ability.asTypeToken.some,
imported.some,
funcName,
args
).some
case _ => none
)
}
case _ => none
}
/**
* This is a hacky method to adjust parsing result
* to format that was used previously.
* This method tries to convert property token to
* call arrow token or property token with
* dotted var name inside value token.
*
* @return Some(token) if token was adjusted, None otherwise
* Try to convert this token into `NamedValueToken`,
* e.g. `Some.Imported.Module.DefinedAbility(...)`
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* type name
*/
def adjust: Option[ValueToken[F]] =
toCallArrow.orElse(toDottedName)
def toNamedValue: Option[NamedValueToken[F]] =
(value, properties.last) match {
case (v @ VarToken(name), IntoApply(args)) =>
properties.init.traverse {
case IntoField(name) => name.extract.some
case _ => none
}.map { props =>
val typeName = name
.rename(
(name.value +: props).mkString(".")
)
.asTypeToken
lazy val leadingName: Option[NamedTypeToken[F]] =
value match {
case VarToken(name) => name.asTypeToken.some
NamedValueToken(typeName, args.extract)
}
case _ => none
}
}

View File

@ -72,7 +72,7 @@ class HeaderHandler[S[_]: Comonad, C](using
.toValidNec(
error(
tkn,
s"Used module has no `module` header. Please add `module` header or use ... as ModuleName, or switch to import"
s"Used module has no `aqua` header. Please add `aqua` header or use ... as ModuleName, or switch to import"
)
)

View File

@ -1,5 +1,6 @@
package aqua.semantics.rules
import aqua.errors.Errors.internalError
import aqua.helpers.syntax.optiont.*
import aqua.parser.lexer.*
import aqua.parser.lexer.InfixToken.{BoolOp, CmpOp, EqOp, MathOp, Op as InfOp}
@ -85,6 +86,8 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
idx <- OptionT(op.idx.fold(LiteralRaw.Zero.some.pure)(valueToRaw))
valueType <- OptionT(T.resolveIntoIndex(op, rootType, idx.`type`))
} yield IntoIndexRaw(idx, valueType)).value
case op: IntoApply[S] =>
internalError("Unexpected. `IntoApply` expected to be transformed into `NamedValueToken`")
}
def valueToRaw(v: ValueToken[S]): Alg[Option[ValueRaw]] =
@ -129,14 +132,29 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
* so here we try to differentiate them and adjust property
* token accordingly.
*/
prop.leadingName.fold(default)(name =>
A.isDefinedAbility(name)
.flatMap(isDefined =>
prop.adjust
.filter(_ => isDefined)
.fold(default)(valueToRaw)
val callArrow = OptionT
.fromOption(prop.toCallArrow)
.filterF(ca =>
ca.ability.fold(false.pure)(
A.isDefinedAbility
)
)
.widen[ValueToken[S]]
val ability = OptionT(
prop.toAbility.findM { case (ab, _) =>
// Test if name is an import
A.isDefinedAbility(ab)
}
).map { case (_, token) => token }
val namedValue = OptionT
.fromOption(prop.toNamedValue)
.filterF(nv => T.resolveType(nv.typeName, mustBeDefined = false).map(_.isDefined))
.widen[ValueToken[S]]
callArrow.orElse(ability).orElse(namedValue).foldF(default)(valueToRaw)
case dvt @ NamedValueToken(typeName, fields) =>
(for {

View File

@ -19,7 +19,7 @@ trait TypesAlgebra[S[_], Alg[_]] {
def resolveArrowDef(arrowDef: ArrowTypeToken[S]): Alg[Option[ArrowType]]
def resolveServiceType(name: NamedTypeToken[S]): Alg[Option[ServiceType]]
def resolveServiceType(name: NamedTypeToken[S], mustBeDefined: Boolean = true): Alg[Option[ServiceType]]
def defineAbilityType(
name: NamedTypeToken[S],

View File

@ -76,14 +76,18 @@ class TypesInterpreter[S[_], X](using
}.as(none)
}
override def resolveServiceType(name: NamedTypeToken[S]): State[X, Option[ServiceType]] =
resolveType(name).flatMap {
override def resolveServiceType(
name: NamedTypeToken[S],
mustBeDefined: Boolean = true
): State[X, Option[ServiceType]] =
resolveType(name, mustBeDefined).flatMap {
case Some(serviceType: ServiceType) =>
serviceType.some.pure
case Some(t) =>
case Some(t) if mustBeDefined =>
report.error(name, s"Type `$t` is not a service").as(none)
case None =>
case None if mustBeDefined =>
report.error(name, s"Type `${name.value}` is not defined").as(none)
case _ => none.pure
}
override def defineAbilityType(