Merge branch 'chore/use-fcli-for-nox' into renovate/fluencelabs-js-client-0.x

# Conflicts:
#	.github/workflows/tests.yml
This commit is contained in:
Valery Antopol 2024-03-04 19:54:37 +04:00
commit 9b4865f8a8
27 changed files with 714 additions and 295 deletions

View File

@ -1,134 +0,0 @@
networks:
nox:
driver: bridge
ipam:
config:
- subnet: 10.50.10.0/24
services:
nox-1:
image: ${NOX_IMAGE}
ports:
- 7771:7771
- 9991:9991
command:
- --aqua-pool-size=2
- -t=7771
- -w=9991
- -x=10.50.10.10
- --external-maddrs
- /dns4/nox-1/tcp/7771
- /dns4/nox-1/tcp/9991/ws
- --allow-private-ips
- --local
# - --bootstraps=/dns/nox-1/tcp/7771
# 12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR
- -k=hK62afickoeP2uZbmSkAYXxxqP8ozq16VRN7qfTP719EHC5V5tjrtW57BSjUr8GvsEXmJRbtejUWyPZ2rZMyQdq
networks:
nox:
ipv4_address: 10.50.10.10
nox-2:
image: ${NOX_IMAGE}
ports:
- 7772:7772
- 9992:9992
command:
- --aqua-pool-size=2
- -t=7772
- -w=9992
- -x=10.50.10.20
- --external-maddrs
- /dns4/nox-2/tcp/7772
- /dns4/nox-2/tcp/9992/ws
- --allow-private-ips
- --bootstraps=/dns/nox-1/tcp/7771
# 12D3KooWQdpukY3p2DhDfUfDgphAqsGu5ZUrmQ4mcHSGrRag6gQK
- -k=2WijTVdhVRzyZamWjqPx4V4iNMrajegNMwNa2PmvPSZV6RRpo5M2fsPWdQr22HVRubuJhhSw8BrWiGt6FPhFAuXy
networks:
nox:
ipv4_address: 10.50.10.20
nox-3:
image: ${NOX_IMAGE}
ports:
- 7773:7773
- 9993:9993
command:
- --aqua-pool-size=2
- -t=7773
- -w=9993
- -x=10.50.10.30
- --external-maddrs
- /dns4/nox-3/tcp/7773
- /dns4/nox-3/tcp/9993/ws
- --allow-private-ips
- --bootstraps=/dns/nox-1/tcp/7771
# 12D3KooWRT8V5awYdEZm6aAV9HWweCEbhWd7df4wehqHZXAB7yMZ
- -k=2n2wBVanBeu2GWtvKBdrYK9DJAocgG3PrTUXMharq6TTfxqTL4sLdXL9BF23n6rsnkAY5pR9vBtx2uWYDQAiZdrX
networks:
nox:
ipv4_address: 10.50.10.30
nox-4:
image: ${NOX_IMAGE}
ports:
- 7774:7774
- 9994:9994
command:
- --aqua-pool-size=2
- -t=7774
- -w=9994
- -x=10.50.10.40
- --external-maddrs
- /dns4/nox-4/tcp/7774
- /dns4/nox-4/tcp/9994/ws
- --allow-private-ips
- --bootstraps=/dns/nox-1/tcp/7771
# 12D3KooWBzLSu9RL7wLP6oUowzCbkCj2AGBSXkHSJKuq4wwTfwof
- -k=4zp8ucAikkjB8CmkufYiFBW4QCDUCbQG7yMjviX7W8bMyN5rfChQ2Pi5QCWThrCTbAm9uq5nbFbxtFcNZq3De4dX
networks:
nox:
ipv4_address: 10.50.10.40
nox-5:
image: ${NOX_IMAGE}
ports:
- 7775:7775
- 9995:9995
command:
- --aqua-pool-size=2
- -t=7775
- -w=9995
- -x=10.50.10.50
- --external-maddrs
- /dns4/nox-5/tcp/7775
- /dns4/nox-5/tcp/9995/ws
- --allow-private-ips
- --bootstraps=/dns/nox-1/tcp/7771
# 12D3KooWBf6hFgrnXwHkBnwPGMysP3b1NJe5HGtAWPYfwmQ2MBiU
- -k=3ry26rm5gkJXvdqRH4FoM3ezWq4xVVsBQF7wtKq4E4pbuaa6p1F84tNqifUS7DdfJL9hs2gcdW64Wc342vHZHMUp
networks:
nox:
ipv4_address: 10.50.10.50
nox-6:
image: ${NOX_IMAGE}
ports:
- 7776:7776
- 9996:9996
command:
- --aqua-pool-size=2
- -t=7776
- -w=9996
- --bootstraps=/dns/nox-1/tcp/7771
- -x=10.50.10.60
- --external-maddrs
- /dns4/nox-6/tcp/7776
- /dns4/nox-6/tcp/9996/ws
- --allow-private-ips
# 12D3KooWPisGn7JhooWhggndz25WM7vQ2JmA121EV8jUDQ5xMovJ
- -k=5Qh8bB1sF28uLPwr3HTvEksCeC6mAWQvebCfcgv9y6j4qKwSzNKm2tzLUg4nACUEo2KZpBw11gNCnwaAdM7o1pEn
networks:
nox:
ipv4_address: 10.50.10.60

View File

@ -1,3 +1,3 @@
{ {
".": "0.14.2" ".": "0.14.4"
} }

View File

@ -32,3 +32,4 @@ jobs:
uses: ./.github/workflows/tests.yml uses: ./.github/workflows/tests.yml
with: with:
ref: ${{ github.ref }} ref: ${{ github.ref }}
nox-image: "docker.fluence.dev/nox:renovate-avm_4905_1"

View File

@ -22,8 +22,8 @@ on:
env: env:
FORCE_COLOR: true FORCE_COLOR: true
NOX_IMAGE: "${{ inputs.nox-image }}"
FLUENCE_ENV: "${{ inputs.fluence-env }}" FLUENCE_ENV: "${{ inputs.fluence-env }}"
FCLI_V_NOX: "${{ inputs.nox-image }}"
jobs: jobs:
aqua: aqua:
@ -64,14 +64,17 @@ jobs:
repository: fluencelabs/aqua repository: fluencelabs/aqua
ref: ${{ inputs.ref }} ref: ${{ inputs.ref }}
- name: Pull nox image - name: Setup fcli
run: docker pull docker.fluence.dev/nox:feat-vm-425-aquavm-mem-limits-from-config-2_4983_1@sha256:71bc65e096931ad1abc92b166b0033f01a718084d0f275d070ea495353da2d89 uses: fluencelabs/setup-fluence@v1
- name: Run nox
uses: isbang/compose-action@v1.5.1
with: with:
compose-file: ".github/e2e/docker-compose.yml" artifact: fcli
down-flags: "--volumes" version: unstable
- name: Init local env with fcli
run: fluence local init --no-input
- name: Run nox network
run: fluence local up
- name: Cache Scala - name: Cache Scala
uses: coursier/cache-action@v6 uses: coursier/cache-action@v6

View File

@ -1,5 +1,21 @@
# Changelog # Changelog
## [0.14.4](https://github.com/fluencelabs/aqua/compare/aqua-v0.14.3...aqua-v0.14.4) (2024-02-29)
### Bug Fixes
* **compiler:** Bug in renaming [LNG-346] ([#1094](https://github.com/fluencelabs/aqua/issues/1094)) ([d7fef3d](https://github.com/fluencelabs/aqua/commit/d7fef3db5fda6c2dfeacac84c3cff2188538a35a))
* **compiler:** Import abilities with `use` [LNG-324] ([#1077](https://github.com/fluencelabs/aqua/issues/1077)) ([a6c8e75](https://github.com/fluencelabs/aqua/commit/a6c8e75c270efd358908f285310f1f1d177a65fb))
## [0.14.3](https://github.com/fluencelabs/aqua/compare/aqua-v0.14.2...aqua-v0.14.3) (2024-02-29)
### Bug Fixes
* **compiler:** Change `noEmptyResponse` default to `true` ([#1093](https://github.com/fluencelabs/aqua/issues/1093)) ([23aba18](https://github.com/fluencelabs/aqua/commit/23aba18c7d7fb86acd2470474f776f00a78ee9c6))
* **language-server:** Name clashing in LSP [LNG-342] ([#1089](https://github.com/fluencelabs/aqua/issues/1089)) ([3e9d385](https://github.com/fluencelabs/aqua/commit/3e9d3856685f74a6be5d0aa288cd7e4f95010901))
## [0.14.2](https://github.com/fluencelabs/aqua/compare/aqua-v0.14.1...aqua-v0.14.2) (2024-02-21) ## [0.14.2](https://github.com/fluencelabs/aqua/compare/aqua-v0.14.1...aqua-v0.14.2) (2024-02-21)

View File

@ -7,7 +7,7 @@ function getConfig({
noXor = false, noXor = false,
targetType = "air", targetType = "air",
tracing = false, tracing = false,
noEmptyResponse = false, noEmptyResponse = true,
}) { }) {
return new AquaConfig( return new AquaConfig(
logLevel, logLevel,

View File

@ -1,6 +1,6 @@
{ {
"name": "@fluencelabs/aqua-api", "name": "@fluencelabs/aqua-api",
"version": "0.14.2", "version": "0.14.4",
"description": "Aqua API", "description": "Aqua API",
"type": "module", "type": "module",
"main": "index.js", "main": "index.js",

View File

@ -72,7 +72,7 @@ object AquaConfig {
noXor = cjs.noXor.getOrElse(false), noXor = cjs.noXor.getOrElse(false),
noRelay = cjs.noRelay.getOrElse(false), noRelay = cjs.noRelay.getOrElse(false),
tracing = cjs.tracing.getOrElse(false), tracing = cjs.tracing.getOrElse(false),
noEmptyResponse = cjs.noEmptyResponse.getOrElse(false) noEmptyResponse = cjs.noEmptyResponse.getOrElse(true)
) )
} }
} }

View File

@ -1,12 +1,48 @@
aqua Job declares * aqua Main
import someString from "hack" export bugLNG346
use timeout, someString from "hack.aqua" as P
export timeout ability Promise:
yield() -> string
func timeout() -> string: func done_nil() -> string:
res <- P.timeout() <- ""
b = someString()
a = P.someString() func done() -> Promise:
<- res <- Promise(yield = done_nil)
ability Compute:
yield() -> string
alias WorkerYield: -> string
alias Yield: WorkerYield -> Promise
func wait_for() -> Yield:
wait = func (cb: -> string) -> Promise:
yield = func () -> string:
e <- cb()
<- e
<- Promise(yield = yield)
<- wait
ability Function:
run(dealId: string) -> string
func simple{Compute}(yield: Yield) -> Function:
deal_run = func () -> string:
c_yield = func () -> string:
<- Compute.yield()
yieeld <- yield(c_yield)
res <- yieeld.yield()
<- res
<- Function(run = deal_run)
func bugLNG346() -> string:
res: *string
yieeld = func () -> string:
res <<- "hello"
<- ""
c = Compute(yield = yieeld)
fn = simple{c}(wait_for())
r <- fn.run("")
<- res!

View File

@ -1,4 +1,4 @@
module DeclareModule declares decl_foo, decl_bar, SuperFoo, DECLARE_CONST, DECLARE_CONST2 aqua DeclareModule declares decl_foo, decl_bar, SuperFoo, DECLARE_CONST, DECLARE_CONST2
export SuperFoo export SuperFoo
const DECLARE_CONST = "declare_const" const DECLARE_CONST = "declare_const"

View File

@ -1,6 +1,6 @@
import BundleJS.* import BundleJS.*
val aquaVersion = "0.14.2" val aquaVersion = "0.14.4"
val scalaV = "3.4.0" val scalaV = "3.4.0"
val catsV = "2.10.0" val catsV = "2.10.0"

View File

@ -87,6 +87,25 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers with Inside {
inside(funcs)(test) 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 { "aqua compiler" should "compile a simple snippet to the right context" in {
val src = Map( 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 { it should "create right topology" in {
val src = Map( val src = Map(
"index.aqua" -> "index.aqua" ->
@ -502,4 +502,385 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers with Inside {
main.body.equalsOrShowDiff(expected) should be(true) 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

@ -0,0 +1,48 @@
aqua AbilitiesClosureRename
export bugLNG346
ability Promise:
yield() -> string
func done_nil() -> string:
<- ""
func done() -> Promise:
<- Promise(yield = done_nil)
ability Compute:
yield() -> string
alias WorkerYield: -> string
alias Yield: WorkerYield -> Promise
func wait_for() -> Yield:
wait = func (cb: -> string) -> Promise:
yield = func () -> string:
e <- cb()
<- e
<- Promise(yield = yield)
<- wait
ability Function:
run(dealId: string) -> string
func simple{Compute}(yield: Yield) -> Function:
deal_run = func () -> string:
c_yield = func () -> string:
<- Compute.yield()
yieeld <- yield(c_yield)
res <- yieeld.yield()
<- res
<- Function(run = deal_run)
func bugLNG346() -> string:
res: *string
yieeld = func () -> string:
res <<- "hello"
<- ""
c = Compute(yield = yieeld)
fn = simple{c}(wait_for())
r <- fn.run("")
<- res!

View File

@ -29,7 +29,7 @@
}, },
"prettier": {}, "prettier": {},
"devDependencies": { "devDependencies": {
"@fluencelabs/aqua-api": "0.14.2", "@fluencelabs/aqua-api": "0.14.4",
"@fluencelabs/aqua-lib": "0.10.2", "@fluencelabs/aqua-lib": "0.10.2",
"@types/jest": "29.5.11", "@types/jest": "29.5.11",
"@types/node": "18.19.10", "@types/node": "18.19.10",

View File

@ -41,6 +41,7 @@ import {
returnSrvAsAbilityCall, returnSrvAsAbilityCall,
} from "../examples/abilityCall.js"; } from "../examples/abilityCall.js";
import { bugLNG314Call, bugLNG338Call } from "../examples/abilityClosureCall.js"; import { bugLNG314Call, bugLNG338Call } from "../examples/abilityClosureCall.js";
import { bugLNG346Call } from "../examples/abilityClosureRenameCall.js";
import { import {
nilLengthCall, nilLengthCall,
nilLiteralCall, nilLiteralCall,
@ -670,6 +671,11 @@ describe("Testing examples", () => {
expect(result).toEqual("job done"); expect(result).toEqual("job done");
}); });
it("abilitiesClosureRename.aqua bug LNG-346", async () => {
let result = await bugLNG346Call();
expect(result).toEqual("hello");
});
it("functors.aqua LNG-119 bug", async () => { it("functors.aqua LNG-119 bug", async () => {
let result = await bugLng119Call(); let result = await bugLng119Call();
expect(result).toEqual([1]); expect(result).toEqual([1]);

View File

@ -0,0 +1,8 @@
import {
bugLNG346
} from "../compiled/examples/abilitiesClosureRename.js";
export async function bugLNG346Call(): Promise<string> {
return await bugLNG346();
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@fluencelabs/aqua-language-server-api", "name": "@fluencelabs/aqua-language-server-api",
"version": "0.14.2", "version": "0.14.4",
"description": "Aqua Language Server API", "description": "Aqua Language Server API",
"type": "commonjs", "type": "commonjs",
"files": [ "files": [

View File

@ -507,9 +507,7 @@ object ArrowInliner extends Logging {
* are prohibited because they are used inside **this function**. * are prohibited because they are used inside **this function**.
*/ */
defineNames <- StateT.liftF( defineNames <- StateT.liftF(
fn.body.definesVarNames.map( fn.body.definesVarNames
_ -- argNames -- capturedNames
)
) )
defineRenames <- Mangler[S].findAndForbidNames(defineNames) defineRenames <- Mangler[S].findAndForbidNames(defineNames)
canonStreamsWithNames <- canonStreamVariables(args) canonStreamsWithNames <- canonStreamVariables(args)

View File

@ -35,6 +35,11 @@ enum NamedArg[F[_]] extends Token[F] {
case Full(name, value) => Full(name.mapK(fk), value.mapK(fk)) case Full(name, value) => Full(name.mapK(fk), value.mapK(fk))
case Short(variable) => Short(variable.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 { object NamedArg {

View File

@ -1,20 +1,20 @@
package aqua.parser.lexer package aqua.parser.lexer
import aqua.parser.lexer.CallArrowToken.CallBraces
import aqua.parser.lexer.NamedArg.namedArgs
import aqua.parser.lexer.Token.* import aqua.parser.lexer.Token.*
import aqua.parser.lift.LiftParser import aqua.parser.lift.LiftParser
import aqua.parser.lift.LiftParser.* import aqua.parser.lift.LiftParser.*
import aqua.parser.lift.Span import aqua.parser.lift.Span
import aqua.parser.lift.Span.{P0ToSpan, PToSpan} import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
import aqua.types.LiteralType 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.data.{NonEmptyList, NonEmptyMap}
import cats.parse.{Numbers, Parser as P, Parser0 as P0} import cats.parse.{Numbers, Parser as P, Parser0 as P0}
import cats.syntax.comonad.* import cats.syntax.comonad.*
import cats.syntax.functor.* import cats.syntax.functor.*
import cats.{Comonad, Functor} import cats.{Comonad, Functor}
import cats.~>
import scala.language.postfixOps import scala.language.postfixOps
sealed trait PropertyOp[F[_]] extends Token[F] { 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)) 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 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 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 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]( case class IntoCopy[F[_]: Comonad](
@ -56,6 +58,27 @@ case class IntoCopy[F[_]: Comonad](
override def mapK[K[_]: Comonad](fk: F ~> K): IntoCopy[K] = override def mapK[K[_]: Comonad](fk: F ~> K): IntoCopy[K] =
copy(fk(point), args.map(_.mapK(fk))) 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 { 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]] = 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]]] = val ops: P[NonEmptyList[PropertyOp[Span.S]]] =
parseOp.rep parseOp.rep

View File

@ -121,9 +121,11 @@ case class ArrowTypeToken[S[_]: Comonad](
args.map { case (n, t) => (n.map(_.mapK(fk)), t.mapK(fk)) }, args.map { case (n, t) => (n.map(_.mapK(fk)), t.mapK(fk)) },
res.map(_.mapK(fk)), res.map(_.mapK(fk)),
abilities.map(_.mapK(fk)) abilities.map(_.mapK(fk))
) )
def argTypes: List[TypeToken[S]] = abilities ++ args.map(_._2) 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 { object ArrowTypeToken {
@ -136,9 +138,9 @@ object ArrowTypeToken {
).map(_.toList) ).map(_.toList)
// {SomeAb, SecondAb} for NamedTypeToken // {SomeAb, SecondAb} for NamedTypeToken
def abilities(): P0[List[NamedTypeToken[S]]] = def abilities(): P0[List[NamedTypeToken[S]]] = (
(`{` *> comma(`Class`.surroundedBy(`/s*`).lift.map(NamedTypeToken(_))) `{` *> comma(NamedTypeToken.dotted).map(_.toList) <* `}`
.map(_.toList) <* `}`).?.map(_.getOrElse(List.empty)) ).?.map(_.getOrElse(List.empty))
def `arrowdef`(argTypeP: P[TypeToken[Span.S]]): P[ArrowTypeToken[Span.S]] = def `arrowdef`(argTypeP: P[TypeToken[Span.S]]): P[ArrowTypeToken[Span.S]] =
((abilities() ~ comma0(argTypeP)).with1 ~ ` -> `.lift ~ ((abilities() ~ comma0(argTypeP)).with1 ~ ` -> `.lift ~

View File

@ -42,112 +42,105 @@ case class PropertyToken[F[_]: Comonad](
name.forall(c => !c.isLetter || c.isUpper) name.forall(c => !c.isLetter || c.isUpper)
/** /**
* This method tries to convert property token to * Try to transform this token to imported ability access
* property token with dotted var name inside value token. * e.g. in `Some.Imported.Module.Ab.innerAb.call(...)`:
* * - `Some.Imported.Module` is imported module name
* Next properties pattern is untouched: * - `Ab.innerAb.call(...)` is ability access
* Class (field)* * so it should be handled as `(Some.Imported.Module.Ab).innerAb.call(...)`
* * ^^^^^^^^^^^^^^^^^^^^^^^
* Next properties pattern is transformed: * ability name inside `VarToken` as one string
* (Class)* (CONST | field) ..props.. * 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
* this part is transformed to dotted name. * 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 { def toAbility: List[(NamedTypeToken[F], ValueToken[F])] =
case VarToken(name) => value match {
// Pattern `Class (field)*` is ability access // NOTE: guard against recursion: if dot is alredy in the name, do not transform
// and should not be transformed case VarToken(name) if !name.value.contains(".") =>
val isAbility = isClass(name.value) && properties.forall { val fields = properties.toList.takeWhile {
case f @ IntoField(_) => isField(f.value) case IntoField(_) => true
case _ => true case _ => false
} }.collect { case f @ IntoField(_) => f.value }.toList
val names = name.value +: fields
if (isAbility) none fields.inits
else { // do not use the last field
// Gather prefix of properties that are IntoField // those cases are handled in `toCallArrow` and `toNamedValue`
val props = name.value +: properties.toList.view.map { .drop(1)
case IntoField(name) => name.extract.some .map { init =>
case _ => none // Length of the import name
}.takeWhile(_.isDefined).flatten.toList 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 propsWithIndex = props.zipWithIndex val varToken = VarToken(newName)
val token = newProps.fold(varToken)(ps => PropertyToken(varToken, ps))
// Find first property that is not Class importAbility -> token
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 } .toList
// test shorter prefixes first
.reverse
case _ => Nil
}
lastSuitable.map(last => /**
val newProps = NonEmptyList.fromList( * Try to convert this token into `CallArrowToken`
properties.toList.drop(last + 1) * 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
}.map { fields =>
val imported = name
.rename(
(name.value +: fields).mkString(".")
) )
val newName = props.take(last + 1).mkString(".") .asTypeToken
val varToken = VarToken(name.rename(newName))
newProps.fold(varToken)(props => PropertyToken(varToken, props)) CallArrowToken(
imported.some,
funcName,
args
) )
} }
case _ => none case _ => none
} }
/** /**
* This method tries to convert property token to * Try to convert this token into `NamedValueToken`,
* call arrow token. * e.g. `Some.Imported.Module.DefinedAbility(...)`
* * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Next properties pattern is transformed: * type name
* (Class)+ arrow()
* ^^^^^^^
* this part is transformed to ability name.
*/ */
private def toCallArrow: Option[CallArrowToken[F]] = value match { def toNamedValue: Option[NamedValueToken[F]] =
case VarToken(name) => (value, properties.last) match {
val ability = properties.init.traverse { case (v @ VarToken(name), IntoApply(args)) =>
case f @ IntoField(_) => f.value.some properties.init.traverse {
case _ => none case IntoField(name) => name.extract.some
}.map( case _ => none
name.value +: _ }.map { props =>
).filter( val typeName = name
_.forall(isClass) .rename(
).map(props => name.rename(props.mkString("."))) (name.value +: props).mkString(".")
)
.asTypeToken
(properties.last, ability) match { NamedValueToken(typeName, args.extract)
case (IntoArrow(funcName, args), Some(ability)) => }
CallArrowToken(
ability.asTypeToken.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
*/
def adjust: Option[ValueToken[F]] =
toCallArrow.orElse(toDottedName)
lazy val leadingName: Option[NamedTypeToken[F]] =
value match {
case VarToken(name) => name.asTypeToken.some
case _ => none case _ => none
} }
} }

2
pnpm-lock.yaml generated
View File

@ -37,7 +37,7 @@ importers:
version: 1.9.1 version: 1.9.1
devDependencies: devDependencies:
'@fluencelabs/aqua-api': '@fluencelabs/aqua-api':
specifier: 0.14.2 specifier: 0.14.4
version: link:../api/api-npm version: link:../api/api-npm
'@fluencelabs/aqua-lib': '@fluencelabs/aqua-lib':
specifier: 0.10.2 specifier: 0.10.2

View File

@ -72,7 +72,7 @@ class HeaderHandler[S[_]: Comonad, C](using
.toValidNec( .toValidNec(
error( error(
tkn, 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 package aqua.semantics.rules
import aqua.errors.Errors.internalError
import aqua.helpers.syntax.optiont.* import aqua.helpers.syntax.optiont.*
import aqua.parser.lexer.* import aqua.parser.lexer.*
import aqua.parser.lexer.InfixToken.{BoolOp, CmpOp, EqOp, MathOp, Op as InfOp} 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)) idx <- OptionT(op.idx.fold(LiteralRaw.Zero.some.pure)(valueToRaw))
valueType <- OptionT(T.resolveIntoIndex(op, rootType, idx.`type`)) valueType <- OptionT(T.resolveIntoIndex(op, rootType, idx.`type`))
} yield IntoIndexRaw(idx, valueType)).value } 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]] = 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 * so here we try to differentiate them and adjust property
* token accordingly. * token accordingly.
*/ */
prop.leadingName.fold(default)(name =>
A.isDefinedAbility(name) val callArrow = OptionT
.flatMap(isDefined => .fromOption(prop.toCallArrow)
prop.adjust .filterF(ca =>
.filter(_ => isDefined) ca.ability.fold(false.pure)(
.fold(default)(valueToRaw) 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) => case dvt @ NamedValueToken(typeName, fields) =>
(for { (for {

View File

@ -19,7 +19,7 @@ trait TypesAlgebra[S[_], Alg[_]] {
def resolveArrowDef(arrowDef: ArrowTypeToken[S]): Alg[Option[ArrowType]] 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( def defineAbilityType(
name: NamedTypeToken[S], name: NamedTypeToken[S],

View File

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