From 14748c7646dad9970058c298f8c919a04e54dfb7 Mon Sep 17 00:00:00 2001 From: Dima Date: Thu, 28 Mar 2024 14:10:13 +0300 Subject: [PATCH] fix(compiler): Create restrictions in inliner [LNG-346] (#1099) --- .../main/scala/aqua/run/CallPreparer.scala | 2 +- aqua-src/antithesis.aqua | 44 +++-- aqua-src/restrict.aqua | 4 +- aqua-src/test/rename.aqua | 4 +- .../aqua/compiler/AquaCompilerSpec.scala | 168 +++++++++--------- .../aqua/examples/closureArrowCapture.aqua | 10 +- .../aqua/examples/closureStreamScopes.aqua | 49 +++++ .../examples/recursiveStreams/multiRec.aqua | 6 +- .../aqua/examples/servicesAsAbilities.aqua | 54 +++--- .../src/__test__/examples.spec.ts | 12 ++ .../src/examples/closureArrowCapture.ts | 4 +- .../src/examples/closureStreamScopesCall.ts | 12 ++ .../recursiveStreams/multiRecStreamCall.ts | 4 +- .../src/examples/servicesAsAbilities.ts | 6 +- .../.js/src/main/scala/aqua/lsp/TypeJs.scala | 2 +- .../aqua/model/inline/ArrowInliner.scala | 30 ++-- .../scala/aqua/model/inline/TagInliner.scala | 113 ++++-------- .../aqua/model/inline/state/Arrows.scala | 41 ++--- .../aqua/model/inline/state/Exports.scala | 132 +++++++++----- .../model/inline/state/InliningState.scala | 3 +- .../aqua/model/inline/state/Scoped.scala | 34 ++-- .../aqua/model/inline/tag/ForTagInliner.scala | 54 ++++++ .../aqua/model/inline/tag/IfTagInliner.scala | 23 ++- .../model/inline/tag/StreamRestrictions.scala | 29 +++ .../aqua/model/inline/tag/TryTagInliner.scala | 11 ++ .../aqua/model/inline/ArrowInlinerSpec.scala | 143 +++++++-------- .../model/inline/RawValueInlinerSpec.scala | 5 +- .../aqua/model/inline/TagInlinerSpec.scala | 29 +-- .../main/scala/aqua/raw/arrow/FuncRaw.scala | 3 +- .../src/main/scala/aqua/raw/ops/RawTag.scala | 9 +- .../scala/aqua/raw/ops/RawTagGivens.scala | 22 ++- model/src/main/scala/aqua/model/OpModel.scala | 7 - .../transform/topology/TopologySpec.scala | 36 ++-- .../aqua/semantics/expr/func/ArrowSem.scala | 22 +-- .../aqua/semantics/expr/func/CatchSem.scala | 24 +-- .../expr/func/ElseOtherwiseSem.scala | 21 ++- .../aqua/semantics/expr/func/ForSem.scala | 59 +++--- .../aqua/semantics/expr/func/FuncOpSem.scala | 21 --- .../aqua/semantics/expr/func/IfSem.scala | 13 +- .../aqua/semantics/expr/func/ParSeqSem.scala | 38 ++-- .../aqua/semantics/expr/func/TrySem.scala | 9 +- .../semantics/rules/names/NamesAlgebra.scala | 5 +- .../rules/names/NamesInterpreter.scala | 7 - .../scala/aqua/semantics/SemanticsSpec.scala | 14 +- 44 files changed, 706 insertions(+), 632 deletions(-) create mode 100644 integration-tests/aqua/examples/closureStreamScopes.aqua create mode 100644 integration-tests/src/examples/closureStreamScopesCall.ts create mode 100644 model/inline/src/main/scala/aqua/model/inline/tag/ForTagInliner.scala create mode 100644 model/inline/src/main/scala/aqua/model/inline/tag/StreamRestrictions.scala create mode 100644 model/inline/src/main/scala/aqua/model/inline/tag/TryTagInliner.scala delete mode 100644 semantics/src/main/scala/aqua/semantics/expr/func/FuncOpSem.scala diff --git a/aqua-run/src/main/scala/aqua/run/CallPreparer.scala b/aqua-run/src/main/scala/aqua/run/CallPreparer.scala index f686d592..7b9bb304 100644 --- a/aqua-run/src/main/scala/aqua/run/CallPreparer.scala +++ b/aqua-run/src/main/scala/aqua/run/CallPreparer.scala @@ -128,7 +128,7 @@ class CallPreparer( FuncArrow( config.functionWrapperName, - SeqTag.wrap((getters.map(_.leaf) :+ body :+ finisherService.leaf): _*), + SeqTag.wrap(getters.map(_.leaf) :+ body :+ finisherService.leaf*), // no arguments and returns "ok" string ArrowType(NilType, returnCodomain), ret, diff --git a/aqua-src/antithesis.aqua b/aqua-src/antithesis.aqua index c8615422..b3d16738 100644 --- a/aqua-src/antithesis.aqua +++ b/aqua-src/antithesis.aqua @@ -1,15 +1,39 @@ -aqua Main +aqua A -export main +export streamTry, streamFor -service Srv("srv"): - call(x: i32) -> i32 +service FailureSrv("failure"): + fail(msg: string) -func main(a: i32, b: i32) -> i32: - res: *i32 - if a > b: - on "peer" via "relay": - res <- Srv.call(a) +func streamTry() -> i8: + on HOST_PEER_ID: + try: + stream: *i8 + anotherStream = stream + stream <<- 1 + anotherStream <<- 1 + FailureSrv.fail("try") + catch e: + stream = *[88,88,88] + stream <<- 2 + FailureSrv.fail("catch") + otherwise: + stream: *i8 + stream <<- 3 - <- res! + stream: *i8 + stream <<- 4 + <- stream! + +service StreamService("test-service"): + store(numbers: []u32, n: u32) + +func callService(stream: *u32, n: u32): + stream <<- 1 + StreamService.store(stream, n) + +func streamFor(): + arr = [1,2,3,4,5] + for a <- arr: + callService(*[], a) \ No newline at end of file diff --git a/aqua-src/restrict.aqua b/aqua-src/restrict.aqua index 46149053..91582f1f 100644 --- a/aqua-src/restrict.aqua +++ b/aqua-src/restrict.aqua @@ -41,11 +41,11 @@ func checkKeepArg() -> []string, []string: <- a, y -- failing Aqua code: -service TestService("test-service"): +service TestServiceRestrict("test-service"): get_records(key: string) -> []string func append_records(peer: string, srum: *[]string): - srum <- TestService.get_records(peer) + srum <- TestServiceRestrict.get_records(peer) func retrieve_records(peer: string) -> [][]string: records: *[]string diff --git a/aqua-src/test/rename.aqua b/aqua-src/test/rename.aqua index 5ed47395..da62a241 100644 --- a/aqua-src/test/rename.aqua +++ b/aqua-src/test/rename.aqua @@ -5,11 +5,11 @@ func some_string() -> string: <- "some_string_func" -service TestService("test-service"): +service TestServiceRename("test-service"): get_records(key: string) -> []string func append_records(peer: string, srum: *[]string): - srum <- TestService.get_records(peer) + srum <- TestServiceRename.get_records(peer) func retrieve_records(peer: string) -> [][]string: records: *[]string diff --git a/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala b/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala index 73ab79b2..85295f76 100644 --- a/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala +++ b/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala @@ -169,56 +169,54 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers with Inside { val expected = XorRes.wrap( - SeqRes.wrap( - getDataSrv("-relay-", "-relay-", ScalarType.string), - getDataSrv("peers", peers.name, peers.`type`), - RestrictionRes(results.name, resultsType).wrap( - SeqRes.wrap( - ParRes.wrap( - FoldRes - .lastNever(peer.name, peers) - .wrap( - ParRes.wrap( - XorRes.wrap( - // better if first relay will be outside `for` - SeqRes.wrap( - through(ValueModel.fromRaw(relay)), - CallServiceRes( - LiteralModel.fromRaw(LiteralRaw.quote("op")), - "identity", - CallRes( - LiteralModel.fromRaw(LiteralRaw.quote("hahahahah")) :: Nil, - Some(CallModel.Export(retVar.name, retVar.`type`)) - ), - peer - ).leaf, - ApRes(retVar, CallModel.Export(results.name, results.`type`)).leaf, - through(ValueModel.fromRaw(relay)), - through(initPeer) - ), - SeqRes.wrap( - through(ValueModel.fromRaw(relay)), - through(initPeer), - failErrorRes - ) + RestrictionRes("results", resultsType).wrap( + SeqRes.wrap( + getDataSrv("-relay-", "-relay-", ScalarType.string), + getDataSrv("peers", peers.name, peers.`type`), + ParRes.wrap( + FoldRes + .lastNever(peer.name, peers) + .wrap( + ParRes.wrap( + XorRes.wrap( + // better if first relay will be outside `for` + SeqRes.wrap( + through(ValueModel.fromRaw(relay)), + CallServiceRes( + LiteralModel.fromRaw(LiteralRaw.quote("op")), + "identity", + CallRes( + LiteralModel.fromRaw(LiteralRaw.quote("hahahahah")) :: Nil, + Some(CallModel.Export(retVar.name, retVar.`type`)) + ), + peer + ).leaf, + ApRes(retVar, CallModel.Export(results.name, results.`type`)).leaf, + through(ValueModel.fromRaw(relay)), + through(initPeer) ), - NextRes(peer.name).leaf - ) + SeqRes.wrap( + through(ValueModel.fromRaw(relay)), + through(initPeer), + failErrorRes + ) + ), + NextRes(peer.name).leaf ) - ), - join(results, LiteralModel.number(3)), // Compiler optimized addition - CanonRes( - results, - init, - CallModel.Export(canonResult.name, canonResult.`type`) - ).leaf, - ApRes( - canonResult, - CallModel.Export(flatResult.name, flatResult.`type`) - ).leaf - ) - ), - respCall(transformCfg, flatResult, initPeer) + ) + ), + join(results, LiteralModel.number(3)), // Compiler optimized addition + CanonRes( + results, + init, + CallModel.Export(canonResult.name, canonResult.`type`) + ).leaf, + ApRes( + canonResult, + CallModel.Export(flatResult.name, flatResult.`type`) + ).leaf, + respCall(transformCfg, flatResult, initPeer) + ) ), errorCall(transformCfg, 0, initPeer) ) @@ -352,33 +350,31 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers with Inside { val resFlatVM = VarModel("-res-flat-0", ArrayType(ScalarType.string)) val expected = XorRes.wrap( - SeqRes.wrap( - RestrictionRes(resVM.name, resStreamType).wrap( - SeqRes.wrap( - // res <- foo() - ApRes( - LiteralModel.fromRaw(LiteralRaw.quote("I am MyFooBar foo")), - CallModel.Export(resVM.name, resVM.`type`) - ).leaf, - // res <- bar() - ApRes( - LiteralModel.fromRaw(LiteralRaw.quote(" I am MyFooBar bar")), - CallModel.Export(resVM.name, resVM.`type`) - ).leaf, - // canonicalization - CanonRes( - resVM, - LiteralModel.fromRaw(ValueRaw.InitPeerId), - CallModel.Export(resCanonVM.name, resCanonVM.`type`) - ).leaf, - // flattening - ApRes( - VarModel(resCanonVM.name, resCanonVM.`type`), - CallModel.Export(resFlatVM.name, resFlatVM.`type`) - ).leaf - ) - ), - respCall(transformCfg, resFlatVM, initPeer) + RestrictionRes(resVM.name, resVM.`type`).wrap( + SeqRes.wrap( + // res <- foo() + ApRes( + LiteralModel.fromRaw(LiteralRaw.quote("I am MyFooBar foo")), + CallModel.Export(resVM.name, resVM.`type`) + ).leaf, + // res <- bar() + ApRes( + LiteralModel.fromRaw(LiteralRaw.quote(" I am MyFooBar bar")), + CallModel.Export(resVM.name, resVM.`type`) + ).leaf, + // canonicalization + CanonRes( + resVM, + LiteralModel.fromRaw(ValueRaw.InitPeerId), + CallModel.Export(resCanonVM.name, resCanonVM.`type`) + ).leaf, + // flattening + ApRes( + VarModel(resCanonVM.name, resCanonVM.`type`), + CallModel.Export(resFlatVM.name, resFlatVM.`type`) + ).leaf, + respCall(transformCfg, resFlatVM, initPeer) + ) ), errorCall(transformCfg, 0, initPeer) ) @@ -424,18 +420,16 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers with Inside { ).leaf val expected = XorRes.wrap( - SeqRes.wrap( - getDataSrv("-relay-", "-relay-", ScalarType.string), - getDataSrv("i", argName, argType), - RestrictionRes(streamName, streamType).wrap( - SeqRes.wrap( - ApRes(LiteralModel.quote("a"), CallModel.Export(streamName, streamType)).leaf, - ApRes(LiteralModel.quote("b"), CallModel.Export(streamName, streamType)).leaf, - join(VarModel(streamName, streamType), arg), - decrement - ) - ), - emptyRespCall(transformCfg, initPeer) + RestrictionRes(streamName, streamType).wrap( + SeqRes.wrap( + getDataSrv("-relay-", "-relay-", ScalarType.string), + getDataSrv("i", argName, argType), + ApRes(LiteralModel.quote("a"), CallModel.Export(streamName, streamType)).leaf, + ApRes(LiteralModel.quote("b"), CallModel.Export(streamName, streamType)).leaf, + join(VarModel(streamName, streamType), arg), + decrement, + emptyRespCall(transformCfg, initPeer) + ) ), errorCall(transformCfg, 0, initPeer) ) diff --git a/integration-tests/aqua/examples/closureArrowCapture.aqua b/integration-tests/aqua/examples/closureArrowCapture.aqua index 2719fa6e..b6313cd1 100644 --- a/integration-tests/aqua/examples/closureArrowCapture.aqua +++ b/integration-tests/aqua/examples/closureArrowCapture.aqua @@ -1,18 +1,18 @@ aqua Test -export test, TestService +export test, TestServiceClosureArrowCapture -service TestService: +service TestServiceClosureArrowCapture: call(s: string) -> string ability TestAbility: arrow(s: string) -> string func returnCapture() -> string -> string: - TestService "test-service" + TestServiceClosureArrowCapture "test-service" closure = (s: string) -> string: - <- TestService.call(s) + <- TestServiceClosureArrowCapture.call(s) closure1 = closure closure2 = closure1 @@ -26,7 +26,7 @@ func returnCapture() -> string -> string: s1 <- closure(s) -- capture closure s2 <- closure3(s1) -- capture renamed closure s3 <- Ab.arrow(s2) -- capture ability - s4 <- TestService.call(s3) -- capture service + s4 <- TestServiceClosureArrowCapture.call(s3) -- capture service <- s4 <- capture diff --git a/integration-tests/aqua/examples/closureStreamScopes.aqua b/integration-tests/aqua/examples/closureStreamScopes.aqua new file mode 100644 index 00000000..dc04397a --- /dev/null +++ b/integration-tests/aqua/examples/closureStreamScopes.aqua @@ -0,0 +1,49 @@ +aqua ClosureStreamScopes + +export simpleTest, complexTest + +ability Join: + join1() -> []string + +ability Fork: + fork() -> Join + +func simpleTest() -> []string: + + fork = () -> Join: + in: *string + join1 = () -> []string: + + in <<- "something in nested" + + <- ["result"] + in <<- "something in" + <- Join(join1) + f = Fork(fork) + + j = f.fork() + <- j.join1() + +func fork() -> Join: + in: *string + out: *string + join1 = () -> []string: + inJoin: *string + for i <- in rec: + inJoin <<- i + for o <- out rec: + inJoin <<- o + join in! + par join out! + <- inJoin + in <<- "something in INSIDE" + out <<- "something out INSIDE" + <- Join(join1) + +func complexTest() -> []string, []string: + out: *string + f = Fork(fork = fork) + j <- f.fork() + strs <- j.join1() + out <<- "something out OUTSIDE" + <- strs, out diff --git a/integration-tests/aqua/examples/recursiveStreams/multiRec.aqua b/integration-tests/aqua/examples/recursiveStreams/multiRec.aqua index 6b6f5005..e788dbc1 100644 --- a/integration-tests/aqua/examples/recursiveStreams/multiRec.aqua +++ b/integration-tests/aqua/examples/recursiveStreams/multiRec.aqua @@ -1,8 +1,8 @@ aqua MultiRec -export TestService, multiRecStream +export TestServiceMultiRec, multiRecStream -service TestService("test-srv"): +service TestServiceMultiRec("test-srv"): handle(i: i32) -> []i32 func multiRecStream(init: i32, target: i32) -> []i32: @@ -11,7 +11,7 @@ func multiRecStream(init: i32, target: i32) -> []i32: loop <<- init for l <- loop rec: - news <- TestService.handle(l) + news <- TestServiceMultiRec.handle(l) for n <- news: loop <<- n if l == target: diff --git a/integration-tests/aqua/examples/servicesAsAbilities.aqua b/integration-tests/aqua/examples/servicesAsAbilities.aqua index 651da77e..ce3acc99 100644 --- a/integration-tests/aqua/examples/servicesAsAbilities.aqua +++ b/integration-tests/aqua/examples/servicesAsAbilities.aqua @@ -1,8 +1,8 @@ aqua Test -export test, testCapture, TestService +export test, testCapture, TestServiceAsAbility -service TestService("default-id"): +service TestServiceAsAbility("default-id"): getId() -> string concatId(s: string) -> string @@ -20,14 +20,14 @@ func test() -> []string: result: *string -- Test service - result <- TestService.concatId("call") - capture = TestService.concatId + result <- TestServiceAsAbility.concatId("call") + capture = TestServiceAsAbility.concatId result <- capture("capture call") - result <- acceptClosure(TestService.concatId, "accept closure call") - result <- acceptAbility{TestService}("accept ability call") + result <- acceptClosure(TestServiceAsAbility.concatId, "accept closure call") + result <- acceptAbility{TestServiceAsAbility}("accept ability call") -- Test renamed service - Renamed = TestService + Renamed = TestServiceAsAbility result <- Renamed.concatId("call") captureRenamed = Renamed.concatId result <- captureRenamed("capture call") @@ -35,15 +35,15 @@ func test() -> []string: result <- acceptAbility{Renamed}("accept ability call") -- Test resolved service - TestService "resolved-id-1" - result <- TestService.concatId("call") - captureResolved = TestService.concatId + TestServiceAsAbility "resolved-id-1" + result <- TestServiceAsAbility.concatId("call") + captureResolved = TestServiceAsAbility.concatId result <- captureResolved("capture call") - result <- acceptClosure(TestService.concatId, "accept closure call") - result <- acceptAbility{TestService}("accept ability call") + result <- acceptClosure(TestServiceAsAbility.concatId, "accept closure call") + result <- acceptAbility{TestServiceAsAbility}("accept ability call") -- Test renamed resolved service - Renamed1 = TestService + Renamed1 = TestServiceAsAbility result <- Renamed1.concatId("call") captureRenamed1 = Renamed1.concatId result <- captureRenamed1("capture call") @@ -59,8 +59,8 @@ func test() -> []string: -- Test resolved in scope service for i <- ["iter-id-1", "iter-id-2"]: - TestService i - RenamedI = TestService + TestServiceAsAbility i + RenamedI = TestServiceAsAbility result <- RenamedI.concatId("call") captureI = RenamedI.concatId result <- captureI("capture call") @@ -68,28 +68,28 @@ func test() -> []string: result <- acceptAbility{RenamedI}("accept ability call") -- Test resolved service again (should save id) - result <- TestService.concatId("call") - captureAgain = TestService.concatId + result <- TestServiceAsAbility.concatId("call") + captureAgain = TestServiceAsAbility.concatId result <- captureAgain("capture call") - result <- acceptClosure(TestService.concatId, "accept closure call") - result <- acceptAbility{TestService}("accept ability call") + result <- acceptClosure(TestServiceAsAbility.concatId, "accept closure call") + result <- acceptAbility{TestServiceAsAbility}("accept ability call") -- Test re resolved service in same scope - TestService "resolved-id-2" - result <- TestService.concatId("call") - captureReResolved = TestService.concatId + TestServiceAsAbility "resolved-id-2" + result <- TestServiceAsAbility.concatId("call") + captureReResolved = TestServiceAsAbility.concatId result <- captureReResolved("capture call") - result <- acceptClosure(TestService.concatId, "accept closure call") - result <- acceptAbility{TestService}("accept ability call") + result <- acceptClosure(TestServiceAsAbility.concatId, "accept closure call") + result <- acceptAbility{TestServiceAsAbility}("accept ability call") <- result func callCapture{MatchingAbility}() -> string, string: - TestService "resolved-id-in-capture" - res1 <- TestService.concatId("in capture") + TestServiceAsAbility "resolved-id-in-capture" + res1 <- TestServiceAsAbility.concatId("in capture") res2 <- MatchingAbility.concatId("in capture") <- res1, res2 func testCapture() -> string, string: - res1, res2 <- callCapture{TestService}() + res1, res2 <- callCapture{TestServiceAsAbility}() <- res1, res2 \ No newline at end of file diff --git a/integration-tests/src/__test__/examples.spec.ts b/integration-tests/src/__test__/examples.spec.ts index 5d40b16a..3dc2cdb2 100644 --- a/integration-tests/src/__test__/examples.spec.ts +++ b/integration-tests/src/__test__/examples.spec.ts @@ -23,6 +23,7 @@ import { registerPrintln } from "../compiled/examples/println.js"; import { helloWorldCall } from "../examples/helloWorldCall.js"; import { foldBug499Call, foldCall } from "../examples/foldCall.js"; import { bugNG69Call, ifCall, ifWrapCall } from "../examples/ifCall.js"; +import { simpleStreamScopeCall, complexStreamScopeCall } from "../examples/closureStreamScopesCall.js"; import { ifPropagateErrorsCall } from "../examples/ifPropagateErrors.js"; import { parCall, testTimeoutCall } from "../examples/parCall.js"; import { complexCall } from "../examples/complex.js"; @@ -767,6 +768,17 @@ describe("Testing examples", () => { expect(streamCaptureResult).toEqual(["one", "two", "three"]); }); + it("closureStreamScopes.aqua simple", async () => { + let result = await simpleStreamScopeCall(); + // it is not hanging + expect(result).toEqual(["result"]); + }); + + it("closureStreamScopes.aqua complex", async () => { + let result = await complexStreamScopeCall(); + expect(result).toEqual([["something in INSIDE", "something out INSIDE"], ["something out OUTSIDE"]]); + }); + // TODO: Unskip this after LNG-226 is fixed it.skip("streamCapture.aqua return", async () => { let streamCaptureResult = await streamCaptureReturnCall(); diff --git a/integration-tests/src/examples/closureArrowCapture.ts b/integration-tests/src/examples/closureArrowCapture.ts index e3157511..fac69e8b 100644 --- a/integration-tests/src/examples/closureArrowCapture.ts +++ b/integration-tests/src/examples/closureArrowCapture.ts @@ -1,10 +1,10 @@ import { test, - registerTestService, + registerTestServiceClosureArrowCapture, } from "../compiled/examples/closureArrowCapture.js"; export async function closureArrowCaptureCall(s: string) { - registerTestService("test-service", { + registerTestServiceClosureArrowCapture("test-service", { call: (s: string) => { return "call: " + s; }, diff --git a/integration-tests/src/examples/closureStreamScopesCall.ts b/integration-tests/src/examples/closureStreamScopesCall.ts new file mode 100644 index 00000000..6b53e0cd --- /dev/null +++ b/integration-tests/src/examples/closureStreamScopesCall.ts @@ -0,0 +1,12 @@ +import { + simpleTest, + complexTest +} from "../compiled/examples/closureStreamScopes.js"; + +export async function complexStreamScopeCall(): Promise<[string[], string[]]> { + return complexTest(); +} + +export async function simpleStreamScopeCall(): Promise { + return simpleTest(); +} diff --git a/integration-tests/src/examples/recursiveStreams/multiRecStreamCall.ts b/integration-tests/src/examples/recursiveStreams/multiRecStreamCall.ts index 9d27a670..8c2881be 100644 --- a/integration-tests/src/examples/recursiveStreams/multiRecStreamCall.ts +++ b/integration-tests/src/examples/recursiveStreams/multiRecStreamCall.ts @@ -1,6 +1,6 @@ import { multiRecStream, - registerTestService, + registerTestServiceMultiRec, } from "../../compiled/examples/recursiveStreams/multiRec.js"; export async function multiRecStreamCall( @@ -8,7 +8,7 @@ export async function multiRecStreamCall( target: number, handle: (i: number) => number[], ): Promise { - registerTestService({ handle }); + registerTestServiceMultiRec({ handle }); return await multiRecStream(init, target); } diff --git a/integration-tests/src/examples/servicesAsAbilities.ts b/integration-tests/src/examples/servicesAsAbilities.ts index e3974c1c..9b40502a 100644 --- a/integration-tests/src/examples/servicesAsAbilities.ts +++ b/integration-tests/src/examples/servicesAsAbilities.ts @@ -1,7 +1,7 @@ import { test, testCapture, - registerTestService, + registerTestServiceAsAbility, } from "../compiled/examples/servicesAsAbilities.js"; const serviceIds = { @@ -37,7 +37,7 @@ export const expectedServiceResults = serviceIdsSequence.flatMap((id) => export async function servicesAsAbilitiesCall() { Object.entries(serviceIds).forEach(([_key, id], _idx) => - registerTestService(id, { + registerTestServiceAsAbility(id, { concatId: (s: string) => { return `${id}: ${s}`; }, @@ -57,7 +57,7 @@ export const expectedServiceCaptureResults = [ export async function servicesAsAbilitiesCaptureCall() { ["resolved-id-in-capture", "default-id"].forEach((id) => - registerTestService(id, { + registerTestServiceAsAbility(id, { concatId: (s: string) => { return `${id}: ${s}`; }, diff --git a/language-server/language-server-api/.js/src/main/scala/aqua/lsp/TypeJs.scala b/language-server/language-server-api/.js/src/main/scala/aqua/lsp/TypeJs.scala index 53b48dea..d20d4f3c 100644 --- a/language-server/language-server-api/.js/src/main/scala/aqua/lsp/TypeJs.scala +++ b/language-server/language-server-api/.js/src/main/scala/aqua/lsp/TypeJs.scala @@ -77,7 +77,7 @@ object TypeJs { def typeList(types: Iterable[(String, Type)]): Dictionary[TypeJs] = js.Dictionary(types.map { case (n, t) => (n, TypeJs.fromType(t)) - }.toSeq: _*) + }.toSeq*) def fromType(t: Type): TypeJs = { t match diff --git a/model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala b/model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala index 31bcce5d..0bc4d377 100644 --- a/model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala @@ -9,9 +9,11 @@ import aqua.raw.value.{ValueRaw, VarRaw} import aqua.types.* import cats.data.{Chain, IndexedStateT, State, StateT} +import cats.free.Cofree import cats.kernel.Semigroup import cats.syntax.applicative.* import cats.syntax.bifunctor.* +import cats.syntax.flatMap.* import cats.syntax.foldable.* import cats.syntax.option.* import cats.syntax.semigroup.* @@ -146,6 +148,7 @@ object ArrowInliner extends Logging { outsideDeclaredStreams: Set[String] ): State[S, InlineResult] = for { callableFuncBodyNoTopology <- TagInliner.handleTree(fn.body) + callableFuncBody = fn.capturedTopology .fold(SeqModel)(ApplyTopologyModel.apply) @@ -173,14 +176,15 @@ object ArrowInliner extends Logging { // find and get resolved arrows if we return them from the function returnedArrows = rets.collect { case VarModel(name, _: ArrowType, _) => name }.toSet - arrowsToSave <- Arrows[S].pickArrows(returnedArrows) + arrowsFromClosures <- Arrows[S].pickArrows(returnedArrows) + arrowsToSave = arrowsFromAbilities ++ arrowsFromClosures body = SeqModel.wrap(callableFuncBody :: ops) } yield InlineResult( body, rets, varsFromAbilities, - arrowsFromAbilities ++ arrowsToSave + arrowsToSave ) /** @@ -236,7 +240,7 @@ object ArrowInliner extends Logging { case arrow @ (_, ValueModel.Arrow(_, _)) => arrow.some case (_, m) => - internalError(s"($m) cannot be an arrow") + internalError(s"($m) cannot be an arrow for '$abilityName' ability") } } @@ -268,7 +272,7 @@ object ArrowInliner extends Logging { case (_, ValueModel.Arrow(vm, _)) => arrows.get(vm.name).map(vm.name -> _) case (_, m) => - internalError(s"($m) cannot be an arrow") + internalError(s"($m) cannot be an arrow for '$name' ability") } } @@ -324,14 +328,10 @@ object ArrowInliner extends Logging { * Correctly rename captured values and arrows of a function * * @param fn Function - * @param exports Exports state before calling/inlining - * @param arrows Arrows state before calling/inlining * @return Renamed values and arrows */ def renameCaptured[S: Mangler]( - fn: FuncArrow, - exports: Map[String, ValueModel], - arrows: Map[String, FuncArrow] + fn: FuncArrow ): State[S, (Renamed[ValueModel], Renamed[FuncArrow])] = { // Gather abilities related values val abilitiesValues = fn.capturedValues.collect { @@ -418,7 +418,7 @@ object ArrowInliner extends Logging { otherValues ) otherArrowsValuesRenamed = Renamed( - otherValuesRenamed.renames.filterKeys(otherArrowsValues.keySet).toMap, + otherValuesRenamed.renames.view.filterKeys(otherArrowsValues.keySet).toMap, otherArrowsValues.renamed(otherValuesRenamed.renames) ) @@ -481,9 +481,6 @@ object ArrowInliner extends Logging { ): State[S, (FuncArrow, OpModel.Tree)] = for { args <- ArgsCall(fn.arrowType.domain, call.args).pure[State[S, *]] - argNames = args.argNames - capturedNames = fn.capturedValues.keySet ++ fn.capturedArrows.keySet - /** * Substitute all arguments inside function body. * Data arguments could be passed as variables or values (expressions), @@ -497,7 +494,7 @@ object ArrowInliner extends Logging { arrowRenames = args.arrowArgsRenames abRenames = args.abilityArgsRenames - captured <- renameCaptured(fn, exports, arrows) + captured <- renameCaptured(fn) (capturedValues, capturedArrows) = captured /** @@ -523,11 +520,6 @@ object ArrowInliner extends Logging { renamedCanonStreams ++ streamRenames - /** - * TODO: Optimize resolve. - * It seems that resolving whole `exports` - * and `arrows` is not necessary. - */ arrowsResolved = arrows ++ capturedArrows.renamed exportsResolved = exports ++ data.renamed ++ capturedValues.renamed diff --git a/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala b/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala index 78bba18d..9e36aa62 100644 --- a/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala @@ -79,33 +79,48 @@ object TagInliner extends Logging { prefix: Option[OpModel.Tree] = None ) extends TagInlined[S](prefix) + /** + * Tag inlining emitted computation + * that envelopes children computation + * + * @param model computation producing model based on children computation + */ + case Around[S]( + model: Chain[State[S, OpModel.Tree]] => State[S, OpModel.Tree], + prefix: Option[OpModel.Tree] = None + ) extends TagInlined[S](prefix) + /** * Finalize inlining, construct a tree * * @param children Children results * @return Result of inlining */ - def build(children: Chain[OpModel.Tree]): State[T, OpModel.Tree] = { - def toSeqModel(tree: OpModel.Tree | Chain[OpModel.Tree]): State[T, OpModel.Tree] = { - val treeChain = tree match { - case c: Chain[OpModel.Tree] => c + def build(children: Chain[State[T, OpModel.Tree]]): State[T, OpModel.Tree] = { + def prefixSeq(sub: OpModel.Tree | Chain[OpModel.Tree]) = { + val tree = sub match { case t: OpModel.Tree => Chain.one(t) + case c: Chain[OpModel.Tree] => c } - State.pure(SeqModel.wrap(Chain.fromOption(prefix) ++ treeChain)) + SeqModel.wrap(Chain.fromOption(prefix) ++ tree) } this match { case Empty(_) => - toSeqModel(children) + children.sequence.map(prefixSeq) case Single(model, _) => - toSeqModel(model.wrap(children)) + children.sequence.map(model.wrap).map(prefixSeq) case Mapping(toModel, _) => - toSeqModel(toModel(children)) + children.sequence.map(toModel).map(prefixSeq) case After(model, _) => - model.flatMap(m => toSeqModel(m.wrap(children))) + for { + c <- children.sequence + m <- model + } yield prefixSeq(m.wrap(c)) + case Around(model, _) => + model(children).map(prefixSeq) } - } } @@ -166,26 +181,6 @@ object TagInliner extends Logging { } } - private def flatCanonStream[S: Mangler]( - canonV: VarModel, - op: Option[OpModel.Tree] - ): State[S, (ValueModel, Option[OpModel.Tree])] = { - if (canonV.properties.nonEmpty) { - val apName = canonV.name + "_flatten" - Mangler[S].findAndForbidName(apName).map { apN => - val apV = VarModel(apN, canonV.`type`) - val apOp = FlattenModel(canonV, apN).leaf - ( - apV, - combineOpsWithSeq(op, apOp.some) - ) - } - } else { - State.pure((canonV, op)) - } - - } - /** * Processes a single [[RawTag]] that may lead to many changes, including calling [[ArrowInliner]] * @@ -206,40 +201,13 @@ object TagInliner extends Logging { ) case IfTag(valueRaw) => - IfTagInliner(valueRaw).inlined.map(inlined => - TagInlined.Mapping( - toModel = inlined.toModel, - prefix = inlined.prefix - ) - ) + IfTagInliner(valueRaw).inlined - case TryTag => pure(XorModel) + case TryTag => + TryTagInliner.inlined case ForTag(item, iterable, mode) => - for { - vp <- valueToModel(iterable) - flattened <- mode match { - case ForTag.Mode.RecMode => State.pure(vp) - case _ => flat(vp._1, vp._2) - } - (v, p) = flattened - n <- Mangler[S].findAndForbidName(item) - elementType = iterable.`type` match { - case b: CollectionType => b.element - case _ => - internalError( - s"non-box type variable '$iterable' in 'for' expression." - ) - } - _ <- Exports[S].resolved(item, VarModel(n, elementType)) - modeModel = mode match { - case ForTag.Mode.SeqMode | ForTag.Mode.TryMode => ForModel.Mode.Null - case ForTag.Mode.ParMode | ForTag.Mode.RecMode => ForModel.Mode.Never - } - } yield TagInlined.Single( - model = ForModel(n, v, modeModel), - prefix = p - ) + ForTagInliner(item, iterable, mode).inlined case PushToStreamTag(operand, exportTo) => ( @@ -369,24 +337,13 @@ object TagInliner extends Logging { } } yield model.fold(TagInlined.Empty())(m => TagInlined.Single(model = m)) - case RestrictionTag(name, typ) => - // Rename restriction after children are inlined with new exports - TagInlined - .After( - for { - exps <- Exports[S].exports - model = exps.get(name).collect { case VarModel(n, _, _) => - RestrictionModel(n, typ) - } - } yield model.getOrElse(RestrictionModel(name, typ)) - ) - .pure - case DeclareStreamTag(value) => value match - case VarRaw(name, t) => - Exports[S].resolved(name, VarModel(name, t)).as(TagInlined.Empty()) - case _ => none + case VarRaw(name, t: StreamType) => + for { + _ <- Exports[S].resolved(name, VarModel(name, t)) + } yield TagInlined.Empty() + case _ => internalError(s"Cannot declare $value as stream, because it is not a stream type") case ServiceIdTag(id, serviceType, name) => for { @@ -448,7 +405,7 @@ object TagInliner extends Logging { for { headInlined <- f(cf.head) tail <- StateT.liftF(cf.tail) - children <- tail.traverse(traverseS[S](_, f)) + children = tail.map(traverseS[S](_, f)) inlined <- headInlined.build(children) } yield inlined diff --git a/model/inline/src/main/scala/aqua/model/inline/state/Arrows.scala b/model/inline/src/main/scala/aqua/model/inline/state/Arrows.scala index 34759319..e6fbf547 100644 --- a/model/inline/src/main/scala/aqua/model/inline/state/Arrows.scala +++ b/model/inline/src/main/scala/aqua/model/inline/state/Arrows.scala @@ -7,6 +7,7 @@ import aqua.types.* import cats.data.State import cats.instances.list.* +import cats.syntax.apply.* import cats.syntax.functor.* import cats.syntax.option.* import cats.syntax.show.* @@ -20,6 +21,7 @@ import cats.syntax.traverse.* */ trait Arrows[S] extends Scoped[S] { self => + def save(name: String, arrow: FuncArrow): State[S, Unit] /** @@ -44,8 +46,7 @@ trait Arrows[S] extends Scoped[S] { /** * Save arrows to the state [[S]] * - * @param arrows - * Resolved arrows, accessible by key name which could differ from arrow's name + * @param arrows Resolved arrows, accessible by key name which could differ from arrow's name */ final def resolved(arrows: Map[String, FuncArrow]): State[S, Unit] = arrows.toList.traverse(save).void @@ -53,22 +54,18 @@ trait Arrows[S] extends Scoped[S] { /** * All arrows available for use in scope */ - val arrows: State[S, Map[String, FuncArrow]] + def arrows: State[S, Map[String, FuncArrow]] /** * Pick a subset of arrows by names * - * @param names - * What arrows should be taken + * @param names What arrows should be taken */ def pickArrows(names: Set[String]): State[S, Map[String, FuncArrow]] = arrows.map(_.view.filterKeys(names).toMap) /** * Take arrows selected by the function call arguments - * - * @param args - * @return */ def argsArrows(args: ArgsCall): State[S, Map[String, FuncArrow]] = arrows.map(args.arrowArgsMap) @@ -76,26 +73,21 @@ trait Arrows[S] extends Scoped[S] { /** * Changes the [[S]] type to [[R]] * - * @param f - * Lens getter - * @param g - * Lens setter - * @tparam R - * New state type + * @param f Lens getter + * @param g Lens setter */ def transformS[R](f: R => S, g: (R, S) => R): Arrows[R] = new Arrows[R] { override def save(name: String, arrow: FuncArrow): State[R, Unit] = self.save(name, arrow).transformS(f, g) - override val arrows: State[R, Map[String, FuncArrow]] = self.arrows.transformS(f, g) + override def arrows: State[R, Map[String, FuncArrow]] = self.arrows.transformS(f, g) - override val purge: State[R, R] = + override protected def purge: State[R, R] = self.purgeR(f, g) - override protected def fill(s: R): State[R, Unit] = - self.fillR(s, f, g) - + override protected def set(r: R): State[R, Unit] = + self.setR(f, g)(r) } } @@ -119,7 +111,7 @@ object Arrows { } } - def apply[S](implicit arrows: Arrows[S]): Arrows[S] = arrows + def apply[S](using arrows: Arrows[S]): Arrows[S] = arrows // Default implementation with the most straightforward state – just a Map object Simple extends Arrows[Map[String, FuncArrow]] { @@ -127,16 +119,13 @@ object Arrows { override def save(name: String, arrow: FuncArrow): State[Map[String, FuncArrow], Unit] = State.modify(_ + (name -> arrow)) - override val arrows: State[Map[String, FuncArrow], Map[String, FuncArrow]] = + override def arrows: State[Map[String, FuncArrow], Map[String, FuncArrow]] = State.get override val purge: State[Map[String, FuncArrow], Map[String, FuncArrow]] = - for { - s <- State.get - _ <- State.set(Map.empty) - } yield s + State.get <* State.set(Map.empty) - override protected def fill(s: Map[String, FuncArrow]): State[Map[String, FuncArrow], Unit] = + override def set(s: Map[String, FuncArrow]): State[Map[String, FuncArrow], Unit] = State.set(s) } } diff --git a/model/inline/src/main/scala/aqua/model/inline/state/Exports.scala b/model/inline/src/main/scala/aqua/model/inline/state/Exports.scala index a97ffaa9..6b37bc1d 100644 --- a/model/inline/src/main/scala/aqua/model/inline/state/Exports.scala +++ b/model/inline/src/main/scala/aqua/model/inline/state/Exports.scala @@ -1,10 +1,13 @@ package aqua.model.inline.state -import aqua.model.ValueModel.Ability +import aqua.model.ValueModel.{Ability, Stream} import aqua.model.{LiteralModel, ValueModel, VarModel} +import aqua.types.StreamType import aqua.types.{AbilityType, GeneralAbilityType, NamedType} import cats.data.{NonEmptyList, State} +import cats.syntax.apply.* +import cats.syntax.traverse.* /** * Exports – trace values available in the scope @@ -49,11 +52,6 @@ trait Exports[S] extends Scoped[S] { */ def getLastVarName(name: String): State[S, Option[String]] - /** - * Rename names in variables - */ - def renameVariables(renames: Map[String, String]): State[S, Unit] - /** * Resolve the whole map of exports * @param exports @@ -76,7 +74,20 @@ trait Exports[S] extends Scoped[S] { /** * Get all the values available in the scope */ - val exports: State[S, Map[String, ValueModel]] + def exports: State[S, Map[String, ValueModel]] + + def deleteStreams(names: Set[String]): State[S, Unit] + + def streams: State[S, Map[String, StreamType]] + + def streamScope[T](inside: State[S, T]): State[S, (T, Map[String, StreamType])] = + for { + streamsBefore <- streams + tree <- inside + streamsAfter <- streams + streams = streamsAfter.removedAll(streamsBefore.keySet) + _ <- deleteStreams(streams.keySet) + } yield (tree, streams) final def gather(names: Seq[String]): State[S, Map[String, ValueModel]] = exports.map(Exports.gatherFrom(names, _)) @@ -92,6 +103,12 @@ trait Exports[S] extends Scoped[S] { override def resolved(exports: Map[String, ValueModel]): State[R, Unit] = self.resolved(exports).transformS(f, g) + override def streams: State[R, Map[String, StreamType]] = + self.streams.transformS(f, g) + + override def deleteStreams(names: Set[String]): State[R, Unit] = + self.deleteStreams(names).transformS(f, g) + override def resolveAbilityField( abilityExportName: String, fieldName: String, @@ -105,23 +122,20 @@ trait Exports[S] extends Scoped[S] { override def getLastVarName(name: String): State[R, Option[String]] = self.getLastVarName(name).transformS(f, g) - override def renameVariables(renames: Map[String, String]): State[R, Unit] = - self.renameVariables(renames).transformS(f, g) - override def getKeys: State[R, Set[String]] = self.getKeys.transformS(f, g) override def getAbilityField(name: String, field: String): State[R, Option[ValueModel]] = self.getAbilityField(name, field).transformS(f, g) - override val exports: State[R, Map[String, ValueModel]] = + override def exports: State[R, Map[String, ValueModel]] = self.exports.transformS(f, g) - override val purge: State[R, R] = + override protected def purge: State[R, R] = self.purgeR(f, g) - override protected def fill(s: R): State[R, Unit] = - self.fillR(s, f, g) + override protected def set(r: R): State[R, Unit] = + self.setR(f, g)(r) } } @@ -166,7 +180,16 @@ object Exports { } } - object Simple extends Exports[Map[String, ValueModel]] { + /** + * @param values list of all values in scope. + * @param streams list of opened streams. Merged between states. + */ + case class ExportsState( + values: Map[String, ValueModel] = Map.empty, + streams: Map[String, StreamType] = Map.empty + ) + + object Simple extends Exports[ExportsState] { // Make links from one set of abilities to another (for ability assignment) private def getAbilityPairs( @@ -192,69 +215,80 @@ object Exports { override def resolved( exportName: String, value: ValueModel - ): State[Map[String, ValueModel], Unit] = State.modify { state => + ): State[ExportsState, Unit] = State.modify { state => value match { case Ability(vm, at) if vm.properties.isEmpty => - val pairs = getAbilityPairs(vm.name, exportName, at, state) - state ++ pairs.toList.toMap + (exportName -> value) - case _ => state + (exportName -> value) + val pairs = getAbilityPairs(vm.name, exportName, at, state.values) + state.copy(values = state.values ++ pairs.toList.toMap + (exportName -> value)) + case Stream(VarModel(streamName, _, _), st) if exportName == streamName => + state.copy( + values = state.values + (exportName -> value), + streams = state.streams + (exportName -> st) + ) + case _ => state.copy(values = state.values + (exportName -> value)) } } - override def getLastVarName(name: String): State[Map[String, ValueModel], Option[String]] = - State.get.map(st => getLastValue(name, st).collect { case VarModel(name, _, _) => name }) + override def getLastVarName(name: String): State[ExportsState, Option[String]] = + State.get.map(st => + getLastValue(name, st.values).collect { case VarModel(name, _, _) => name } + ) - override def resolved(exports: Map[String, ValueModel]): State[Map[String, ValueModel], Unit] = - State.modify(_ ++ exports) + override def resolved(exports: Map[String, ValueModel]): State[ExportsState, Unit] = + State.modify(st => st.copy(values = st.values ++ exports)) + + override def streams: State[ExportsState, Map[String, StreamType]] = + State.get.map(_.streams) + + override def deleteStreams(names: Set[String]): State[ExportsState, Unit] = + State.modify(st => st.copy(streams = st.streams -- names)) override def resolveAbilityField( abilityExportName: String, fieldName: String, value: ValueModel - ): State[Map[String, ValueModel], Unit] = - State.modify(_ + (AbilityType.fullName(abilityExportName, fieldName) -> value)) + ): State[ExportsState, Unit] = + State.modify(st => + st.copy(values = st.values + (AbilityType.fullName(abilityExportName, fieldName) -> value)) + ) override def copyWithAbilityPrefix( prefix: String, newPrefix: String - ): State[Map[String, ValueModel], Unit] = + ): State[ExportsState, Unit] = State.modify { state => - state.flatMap { + val newValues = state.values.flatMap { case (k, v) if k.startsWith(prefix) => List(k.replaceFirst(prefix, newPrefix) -> v, k -> v) case (k, v) => List(k -> v) } + state.copy(values = newValues) } - override def renameVariables( - renames: Map[String, String] - ): State[Map[String, ValueModel], Unit] = - State.modify { - _.map { - case (k, vm @ VarModel(name, _, _)) if renames.contains(name) => - k -> vm.copy(name = renames.getOrElse(name, name)) - case (k, v) => k -> v - } - } - - override def getKeys: State[Map[String, ValueModel], Set[String]] = State.get.map(_.keySet) + override def getKeys: State[ExportsState, Set[String]] = State.get.map(_.values.keySet) override def getAbilityField( name: String, field: String - ): State[Map[String, ValueModel], Option[ValueModel]] = - State.get.map(_.get(AbilityType.fullName(name, field))) + ): State[ExportsState, Option[ValueModel]] = + State.get.map(_.values.get(AbilityType.fullName(name, field))) - override val exports: State[Map[String, ValueModel], Map[String, ValueModel]] = - State.get + override val exports: State[ExportsState, Map[String, ValueModel]] = + State.get.map(_.values) - override val purge: State[Map[String, ValueModel], Map[String, ValueModel]] = + override val purge: State[ExportsState, ExportsState] = for { - s <- State.get - _ <- State.set(Map.empty) - } yield s + st <- State.get + // HACK: refactor + _ <- State.modify[ExportsState](st => ExportsState(streams = st.streams)) + } yield st - override protected def fill(s: Map[String, ValueModel]): State[Map[String, ValueModel], Unit] = - State.set(s) + override def set(s: ExportsState): State[ExportsState, Unit] = { + for { + st <- State.get + // HACK: refactor + _ <- State.set(s.copy(streams = st.streams ++ s.streams)) + } yield {} + } } } diff --git a/model/inline/src/main/scala/aqua/model/inline/state/InliningState.scala b/model/inline/src/main/scala/aqua/model/inline/state/InliningState.scala index 9218c374..3eed4c20 100644 --- a/model/inline/src/main/scala/aqua/model/inline/state/InliningState.scala +++ b/model/inline/src/main/scala/aqua/model/inline/state/InliningState.scala @@ -1,6 +1,7 @@ package aqua.model.inline.state import aqua.mangler.ManglerState +import aqua.model.inline.state.Exports.ExportsState import aqua.model.inline.state.{Arrows, Counter, Exports, Mangler} import aqua.model.{FuncArrow, ValueModel} import aqua.raw.arrow.FuncRaw @@ -26,7 +27,7 @@ import scribe.Logging */ case class InliningState( noNames: ManglerState = ManglerState(), - resolvedExports: Map[String, ValueModel] = Map.empty, + resolvedExports: ExportsState = ExportsState(), resolvedArrows: Map[String, FuncArrow] = Map.empty, instructionCounter: Int = 0, config: Config.Values = Config.Values.default diff --git a/model/inline/src/main/scala/aqua/model/inline/state/Scoped.scala b/model/inline/src/main/scala/aqua/model/inline/state/Scoped.scala index 58733ce5..e5889bab 100644 --- a/model/inline/src/main/scala/aqua/model/inline/state/Scoped.scala +++ b/model/inline/src/main/scala/aqua/model/inline/state/Scoped.scala @@ -1,39 +1,39 @@ package aqua.model.inline.state import cats.data.State +import cats.syntax.apply.* +import cats.syntax.functor.* /** * Common transformations to make an isolated scope for the state [[S]] - * @tparam S State */ trait Scoped[S] { - // Remove everything from the state - val purge: State[S, S] - - // Put [[s]] to the state - protected def fill(s: S): State[S, Unit] /** * Clear the state, run [[scoped]], then recover the initial state * @param scoped What to run with empty [[S]] - * @tparam T Return type * @return Value returned by [[scoped]] */ def scope[T](scoped: State[S, T]): State[S, T] = for { r <- purge t <- scoped - _ <- fill(r) + _ <- set(r) } yield t - // For transformS - protected def purgeR[R](f: R => S, g: (R, S) => R): State[R, R] = - for { - r <- State.get[R] - s <- purge.transformS(f, g) - } yield g(r, s) + protected def purge: State[S, S] + + protected def set(s: S): State[S, Unit] + + protected final def purgeR[R](f: R => S, g: (R, S) => R): State[R, R] = + (State.get[R], purge.transformS(f, g)).mapN(g) + + protected final def setR[R](f: R => S, g: (R, S) => R)(r: R): State[R, Unit] = + set(f(r)).transformS(f, g) + + private final def get: State[S, S] = for { + s <- purge + _ <- set(s) + } yield s - // For transformS - protected def fillR[R](s: R, f: R => S, g: (R, S) => R): State[R, Unit] = - fill(f(s)).transformS(f, g) } diff --git a/model/inline/src/main/scala/aqua/model/inline/tag/ForTagInliner.scala b/model/inline/src/main/scala/aqua/model/inline/tag/ForTagInliner.scala new file mode 100644 index 00000000..56fe616e --- /dev/null +++ b/model/inline/src/main/scala/aqua/model/inline/tag/ForTagInliner.scala @@ -0,0 +1,54 @@ +package aqua.model.inline.tag + +import aqua.errors.Errors.internalError +import aqua.helpers.syntax.reader.* +import aqua.model.* +import aqua.model.ValueModel +import aqua.model.inline.Inline.parDesugarPrefixOpt +import aqua.model.inline.RawValueInliner.valueToModel +import aqua.model.inline.TagInliner.TagInlined +import aqua.model.inline.TagInliner.flat +import aqua.model.inline.state.* +import aqua.raw.ops.ForTag +import aqua.raw.value.ValueRaw +import aqua.types.CollectionType +import aqua.types.StreamType + +import cats.Eval +import cats.data.Reader +import cats.data.{Chain, State} +import cats.syntax.apply.* +import cats.syntax.flatMap.* + +final case class ForTagInliner( + item: String, + iterable: ValueRaw, + mode: ForTag.Mode +) { + + def inlined[S: Mangler: Exports: Arrows: Config]: State[S, TagInlined[S]] = for { + vp <- valueToModel(iterable) + flattened <- mode match { + case ForTag.Mode.RecMode => State.pure(vp) + case _ => flat.tupled(vp) + } + (v, p) = flattened + n <- Mangler[S].findAndForbidName(item) + elementType = iterable.`type` match { + case b: CollectionType => b.element + case _ => + internalError( + s"non-box type variable '$iterable' in 'for' expression." + ) + } + _ <- Exports[S].resolved(item, VarModel(n, elementType)) + modeModel = mode match { + case ForTag.Mode.SeqMode | ForTag.Mode.TryMode => ForModel.Mode.Null + case ForTag.Mode.ParMode | ForTag.Mode.RecMode => ForModel.Mode.Never + } + model = ForModel(n, v, modeModel) + } yield TagInlined.Around( + model = StreamRestrictions.restrictStreams(model.wrap), + prefix = p + ) +} diff --git a/model/inline/src/main/scala/aqua/model/inline/tag/IfTagInliner.scala b/model/inline/src/main/scala/aqua/model/inline/tag/IfTagInliner.scala index 3e8f5c0b..9ff2b447 100644 --- a/model/inline/src/main/scala/aqua/model/inline/tag/IfTagInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/tag/IfTagInliner.scala @@ -5,13 +5,13 @@ import aqua.model.* import aqua.model.ValueModel import aqua.model.inline.Inline.parDesugarPrefixOpt import aqua.model.inline.RawValueInliner.valueToModel -import aqua.model.inline.TagInliner.canonicalizeIfStream +import aqua.model.inline.TagInliner.{TagInlined, canonicalizeIfStream} import aqua.model.inline.state.* import aqua.raw.value.ApplyBinaryOpRaw.Op as BinOp import aqua.raw.value.{ApplyBinaryOpRaw, ValueRaw} +import aqua.types.StreamType import cats.Eval -import cats.data.Reader import cats.data.{Chain, State} import cats.syntax.apply.* import cats.syntax.flatMap.* @@ -21,8 +21,8 @@ final case class IfTagInliner( ) { import IfTagInliner.* - def inlined[S: Mangler: Exports: Arrows: Config]: State[S, IfTagInlined] = for { - cond <- (valueRaw match { + def inlined[S: Mangler: Exports: Arrows: Config]: State[S, TagInlined[S]] = for { + cond <- valueRaw match { // Optimize in case last operation is equality check case ApplyBinaryOpRaw(op @ (BinOp.Eq | BinOp.Neq), left, right, _) => ( @@ -44,13 +44,15 @@ final case class IfTagInliner( (prefix, valueModel, compareModel, shouldMatch) } - }) + } (prefix, leftValue, rightValue, shouldMatch) = cond noProp <- Config[S].noErrorPropagation.toState model = if (noProp) toModelNoProp else toModel - } yield IfTagInlined( - prefix, - model(leftValue, rightValue, shouldMatch) + modelByChildren = model(leftValue, rightValue, shouldMatch) + stateModel = StreamRestrictions.restrictStreams[S](modelByChildren) + } yield TagInlined.Around( + prefix = prefix, + model = stateModel ) private def toModelNoProp( @@ -185,11 +187,6 @@ final case class IfTagInliner( object IfTagInliner { - final case class IfTagInlined( - prefix: Option[OpModel.Tree], - toModel: Chain[OpModel.Tree] => OpModel.Tree - ) - private def restrictErrors( name: String* )(tree: OpModel.Tree): OpModel.Tree = diff --git a/model/inline/src/main/scala/aqua/model/inline/tag/StreamRestrictions.scala b/model/inline/src/main/scala/aqua/model/inline/tag/StreamRestrictions.scala new file mode 100644 index 00000000..dbed2f5b --- /dev/null +++ b/model/inline/src/main/scala/aqua/model/inline/tag/StreamRestrictions.scala @@ -0,0 +1,29 @@ +package aqua.model.inline.tag + +import aqua.model.inline.state.{Arrows, Config, Exports, Mangler} +import aqua.model.{OpModel, RestrictionModel} +import aqua.types.StreamType + +import cats.data.{Chain, State} +import cats.syntax.traverse.* + +object StreamRestrictions { + + // restrict streams that are generated in a tree + def restrictStreams[S: Mangler: Exports: Arrows: Config]( + childrenToModel: Chain[OpModel.Tree] => OpModel.Tree + )(children: Chain[State[S, OpModel.Tree]]): State[S, OpModel.Tree] = + children.traverse(restrictStreamsAround).map(childrenToModel) + + /** + * Restrict streams that are generated in a tree + */ + private def restrictStreamsAround[S: Mangler: Exports: Arrows: Config]( + getTree: State[S, OpModel.Tree] + ): State[S, OpModel.Tree] = + Exports[S].streamScope(getTree).map { case (tree, streams) => + streams.toList.foldLeft(tree) { case (acc, (name, st)) => + RestrictionModel(name, st).wrap(acc) + } + } +} diff --git a/model/inline/src/main/scala/aqua/model/inline/tag/TryTagInliner.scala b/model/inline/src/main/scala/aqua/model/inline/tag/TryTagInliner.scala new file mode 100644 index 00000000..1fa1ba9e --- /dev/null +++ b/model/inline/src/main/scala/aqua/model/inline/tag/TryTagInliner.scala @@ -0,0 +1,11 @@ +package aqua.model.inline.tag + +import aqua.model.inline.state.{Arrows, Config, Exports, Mangler} +import aqua.model.inline.TagInliner.TagInlined +import aqua.model.XorModel +import cats.data.State + +object TryTagInliner { + def inlined[S: Mangler: Exports: Arrows: Config]: State[S, TagInlined[S]] = + State.pure(TagInlined.Around(model = StreamRestrictions.restrictStreams(XorModel.wrap))) +} diff --git a/model/inline/src/test/scala/aqua/model/inline/ArrowInlinerSpec.scala b/model/inline/src/test/scala/aqua/model/inline/ArrowInlinerSpec.scala index 3914c29a..9e3d2f7d 100644 --- a/model/inline/src/test/scala/aqua/model/inline/ArrowInlinerSpec.scala +++ b/model/inline/src/test/scala/aqua/model/inline/ArrowInlinerSpec.scala @@ -134,11 +134,9 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside { .callArrow[InliningState]( FuncArrow( "stream-callback", - RestrictionTag(streamVar.name, streamType).wrap( - SeqTag.wrap( - DeclareStreamTag(streamVar).leaf, - CallArrowRawTag.func("cb", Call(streamVar :: Nil, Nil)).leaf - ) + SeqTag.wrap( + DeclareStreamTag(streamVar).leaf, + CallArrowRawTag.func("cb", Call(streamVar :: Nil, Nil)).leaf ), ArrowType( ProductType.labelled( @@ -166,23 +164,21 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside { .value model.equalsOrShowDiff( - RestrictionModel(streamVar.name, streamType).wrap( - MetaModel - .CallArrowModel("cb") - .wrap( - SeqModel.wrap( - CanonicalizeModel( - streamModel, - CallModel.Export(canonModel.name, canonModel.`type`) - ).leaf, - CallServiceModel( - LiteralModel.quote("test-service"), - "some-call", - CallModel(canonModel :: Nil, Nil) - ).leaf - ) + MetaModel + .CallArrowModel("cb") + .wrap( + SeqModel.wrap( + CanonicalizeModel( + streamModel, + CallModel.Export(canonModel.name, canonModel.`type`) + ).leaf, + CallServiceModel( + LiteralModel.quote("test-service"), + "some-call", + CallModel(canonModel :: Nil, Nil) + ).leaf ) - ) + ) ) should be(true) } @@ -283,7 +279,8 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside { * stream <<- "asd" * <- stream */ - it should "rename restricted stream correctly" in { + // IGNORED: streams are not restricted in function bodies for now + it should "rename restricted stream correctly" ignore { val streamType = StreamType(ScalarType.string) val someStr = VarRaw("someStr", streamType) @@ -319,30 +316,28 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside { val newFunc = FuncArrow( "newFunc", - RestrictionTag(streamVar.name, streamType).wrap( - SeqTag.wrap( - CallArrowRawTag - .func( - returnNil.funcName, - Call(Nil, Call.Export(streamVar.name, streamType) :: Nil) - ) - .leaf, - PushToStreamTag( - LiteralRaw.quote("asd"), - Call.Export(streamVar.name, streamVar.`type`) - ).leaf, - CanonicalizeTag( - streamVar, - Call.Export(canonStreamVar.name, canonStreamVar.`type`) - ).leaf, - FlattenTag( - canonStreamVar, - flatStreamVar.name - ).leaf, - ReturnTag( - NonEmptyList.one(flatStreamVar) - ).leaf - ) + SeqTag.wrap( + CallArrowRawTag + .func( + returnNil.funcName, + Call(Nil, Call.Export(streamVar.name, streamType) :: Nil) + ) + .leaf, + PushToStreamTag( + LiteralRaw.quote("asd"), + Call.Export(streamVar.name, streamVar.`type`) + ).leaf, + CanonicalizeTag( + streamVar, + Call.Export(canonStreamVar.name, canonStreamVar.`type`) + ).leaf, + FlattenTag( + canonStreamVar, + flatStreamVar.name + ).leaf, + ReturnTag( + NonEmptyList.one(flatStreamVar) + ).leaf ), ArrowType( ProductType(Nil), @@ -440,30 +435,28 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside { val testReturnStream = FuncArrow( "testReturnStream", - RestrictionTag(streamVar.name, streamType).wrap( - SeqTag.wrap( - CallArrowRawTag - .func( - rereturnStream.funcName, - Call(Nil, Call.Export(streamVar.name, streamType) :: Nil) - ) - .leaf, - PushToStreamTag( - LiteralRaw.quote("three"), - Call.Export(streamVar.name, streamVar.`type`) - ).leaf, - CanonicalizeTag( - streamVar, - Call.Export(canonStreamVar.name, canonStreamVar.`type`) - ).leaf, - FlattenTag( - canonStreamVar, - flatStreamVar.name - ).leaf, - ReturnTag( - NonEmptyList.one(flatStreamVar) - ).leaf - ) + SeqTag.wrap( + CallArrowRawTag + .func( + rereturnStream.funcName, + Call(Nil, Call.Export(streamVar.name, streamType) :: Nil) + ) + .leaf, + PushToStreamTag( + LiteralRaw.quote("three"), + Call.Export(streamVar.name, streamVar.`type`) + ).leaf, + CanonicalizeTag( + streamVar, + Call.Export(canonStreamVar.name, canonStreamVar.`type`) + ).leaf, + FlattenTag( + canonStreamVar, + flatStreamVar.name + ).leaf, + ReturnTag( + NonEmptyList.one(flatStreamVar) + ).leaf ), ArrowType( ProductType(Nil), @@ -883,11 +876,9 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside { .callArrow[InliningState]( FuncArrow( "stream-callback", - RestrictionTag(streamVar.name, streamType).wrap( - SeqTag.wrap( - DeclareStreamTag(streamVar).leaf, - CallArrowRawTag.func("cb", Call(streamVarLambda :: Nil, Nil)).leaf - ) + SeqTag.wrap( + DeclareStreamTag(streamVar).leaf, + CallArrowRawTag.func("cb", Call(streamVarLambda :: Nil, Nil)).leaf ), ArrowType( ProductType.labelled( @@ -1399,7 +1390,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside { .one( outterRes ) - ).leaf: _* + ).leaf* ) val outer = FuncArrow( diff --git a/model/inline/src/test/scala/aqua/model/inline/RawValueInlinerSpec.scala b/model/inline/src/test/scala/aqua/model/inline/RawValueInlinerSpec.scala index e7a25680..f095b0f4 100644 --- a/model/inline/src/test/scala/aqua/model/inline/RawValueInlinerSpec.scala +++ b/model/inline/src/test/scala/aqua/model/inline/RawValueInlinerSpec.scala @@ -3,6 +3,7 @@ package aqua.model.inline import aqua.mangler.ManglerState import aqua.model.* import aqua.model.inline.raw.StreamGateInliner +import aqua.model.inline.state.Exports.ExportsState import aqua.model.inline.state.InliningState import aqua.raw.value.* import aqua.types.* @@ -204,7 +205,7 @@ class RawValueInlinerSpec extends AnyFlatSpec with Matchers with Inside { valueToModel[InliningState](`raw res.c`) .runA( InliningState(resolvedExports = - Map("res" -> VarModel("a", aType, Chain.one(IntoFieldModel("b", bType)))) + ExportsState(Map("res" -> VarModel("a", aType, Chain.one(IntoFieldModel("b", bType))))) ) ) .value shouldBe ( @@ -608,7 +609,7 @@ class RawValueInlinerSpec extends AnyFlatSpec with Matchers with Inside { expr <- genAllExprs(perm) } { val state = InliningState( - resolvedExports = vars.map(v => v.name -> VarModel.fromVarRaw(v)).toMap + resolvedExports = ExportsState(vars.map(v => v.name -> VarModel.fromVarRaw(v)).toMap) ) val (model, inline) = valueToModel[InliningState](expr).runA(state).value diff --git a/model/inline/src/test/scala/aqua/model/inline/TagInlinerSpec.scala b/model/inline/src/test/scala/aqua/model/inline/TagInlinerSpec.scala index 447b67ae..a0a67a2c 100644 --- a/model/inline/src/test/scala/aqua/model/inline/TagInlinerSpec.scala +++ b/model/inline/src/test/scala/aqua/model/inline/TagInlinerSpec.scala @@ -1,16 +1,14 @@ package aqua.model.inline -import aqua.model.ForModel -import aqua.model.ValueModel +import aqua.model.* import aqua.model.inline.TagInliner.TagInlined +import aqua.model.inline.state.Exports.ExportsState import aqua.model.inline.state.InliningState -import aqua.model.{LiteralModel, OpModel, SeqModel} -import aqua.raw.ops.ForTag -import aqua.raw.ops.{Call, CanonicalizeTag, FlattenTag} -import aqua.raw.value.ValueRaw -import aqua.raw.value.VarRaw +import aqua.raw.ops.{Call, CanonicalizeTag, FlattenTag, ForTag} +import aqua.raw.value.{ValueRaw, VarRaw} import aqua.types.{ScalarType, StreamType} +import cats.data.{Chain, State} import cats.syntax.show.* import org.scalatest.Inside import org.scalatest.flatspec.AnyFlatSpec @@ -28,7 +26,7 @@ class TagInlinerSpec extends AnyFlatSpec with Matchers with Inside { .run(InliningState()) .value - state.resolvedExports(canonTo) shouldBe LiteralModel( + state.resolvedExports.values(canonTo) shouldBe LiteralModel( ValueRaw.Nil.value, ValueRaw.Nil.baseType ) @@ -48,7 +46,7 @@ class TagInlinerSpec extends AnyFlatSpec with Matchers with Inside { .run(InliningState()) .value - state.resolvedExports(canonTo) shouldBe LiteralModel( + state.resolvedExports.values(canonTo) shouldBe LiteralModel( ValueRaw.Nil.value, ValueRaw.Nil.baseType ) @@ -68,15 +66,20 @@ class TagInlinerSpec extends AnyFlatSpec with Matchers with Inside { .tagToModel[InliningState](tag) .run( InliningState( - resolvedExports = Map( - iterableRaw.name -> iterableModel + resolvedExports = ExportsState( + Map( + iterableRaw.name -> iterableModel + ) ) ) ) .value - inside(inlined) { case TagInlined.Single(ForModel(_, iter, ForModel.Mode.Never), _) => - iter shouldBe iterableModel + inside(inlined) { case TagInlined.Around(st, _) => + inside(st(Chain.empty).runA(state).value.head) { + case ForModel(_, iter, ForModel.Mode.Never) => + iter shouldBe iterableModel + } } } } diff --git a/model/raw/src/main/scala/aqua/raw/arrow/FuncRaw.scala b/model/raw/src/main/scala/aqua/raw/arrow/FuncRaw.scala index ca654ff5..98c17031 100644 --- a/model/raw/src/main/scala/aqua/raw/arrow/FuncRaw.scala +++ b/model/raw/src/main/scala/aqua/raw/arrow/FuncRaw.scala @@ -11,8 +11,9 @@ case class FuncRaw( override def rawPartType: Type = arrow.`type` + // vars that we capture from external space (outer functions, etc) lazy val capturedVars: Set[String] = { - val freeBodyVars = arrow.body.usesVarNames.value + val freeBodyVars: Set[String] = arrow.body.usesVarNames.value val argsNames = arrow.`type`.domain .toLabelledList() .map { case (name, _) => name } diff --git a/model/raw/src/main/scala/aqua/raw/ops/RawTag.scala b/model/raw/src/main/scala/aqua/raw/ops/RawTag.scala index 2991d0ca..cdc30774 100644 --- a/model/raw/src/main/scala/aqua/raw/ops/RawTag.scala +++ b/model/raw/src/main/scala/aqua/raw/ops/RawTag.scala @@ -4,6 +4,7 @@ import aqua.raw.arrow.FuncRaw import aqua.raw.value.{CallArrowRaw, CallServiceRaw, ValueRaw, VarRaw} import aqua.tree.{TreeNode, TreeNodeCompanion} import aqua.types.* + import cats.Show import cats.data.{Chain, NonEmptyList} import cats.free.Cofree @@ -158,14 +159,6 @@ case class NextTag(item: String) extends RawTag { override def mapValues(f: ValueRaw => ValueRaw): RawTag = this } -case class RestrictionTag(name: String, `type`: Type) extends SeqGroupTag { - - override def restrictsVarNames: Set[String] = Set(name) - - override def renameExports(map: Map[String, String]): RawTag = - copy(name = map.getOrElse(name, name)) -} - case class ForTag(item: String, iterable: ValueRaw, mode: ForTag.Mode) extends SeqGroupTag { override def restrictsVarNames: Set[String] = Set(item) diff --git a/model/raw/src/main/scala/aqua/raw/ops/RawTagGivens.scala b/model/raw/src/main/scala/aqua/raw/ops/RawTagGivens.scala index b0ff46f3..ec519fb4 100644 --- a/model/raw/src/main/scala/aqua/raw/ops/RawTagGivens.scala +++ b/model/raw/src/main/scala/aqua/raw/ops/RawTagGivens.scala @@ -1,11 +1,10 @@ package aqua.raw.ops -import aqua.raw.value.{LiteralRaw, ValueRaw} - +import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw} import cats.data.Chain import cats.free.Cofree -import cats.syntax.all.* import cats.syntax.apply.* +import cats.syntax.flatMap.* import cats.syntax.foldable.* import cats.syntax.semigroup.* import cats.{Eval, Semigroup} @@ -46,11 +45,7 @@ trait RawTagGivens { Eval.later(acc.foldLeft(tag.definesVarNames)(_ ++ _)) } - /** - * Get all variable names used by this tree - * but not exported in it (free variables). - */ - def usesVarNames: Eval[Set[String]] = + def exportedAndUsesNames: Eval[(Set[String], Set[String])] = Cofree .cata(tree)((tag, childs: Chain[(Set[String], Set[String])]) => Eval.later { @@ -60,8 +55,19 @@ trait RawTagGivens { (exports, uses) } ) + + /** + * Get all variable names used by this tree + * but not exported in it (free variables). + */ + def usesVarNames: Eval[Set[String]] = + exportedAndUsesNames .map { case (_, uses) => uses } + def exportsVarNames: Eval[Set[String]] = + exportedAndUsesNames + .map { case (exports, _) => exports } + private def collect[A](pf: PartialFunction[RawTag, A]): Eval[Chain[A]] = Cofree.cata(tree)((tag, acc: Chain[Chain[A]]) => Eval.later(Chain.fromOption(pf.lift(tag)) ++ acc.flatten) diff --git a/model/src/main/scala/aqua/model/OpModel.scala b/model/src/main/scala/aqua/model/OpModel.scala index a34fc6a7..3ca93ccc 100644 --- a/model/src/main/scala/aqua/model/OpModel.scala +++ b/model/src/main/scala/aqua/model/OpModel.scala @@ -172,13 +172,6 @@ object ForModel { ForModel(item, iterable, Mode.Null) } -// TODO how is it used? remove, if it's not -case class DeclareStreamModel(value: ValueModel) extends NoExecModel { - override def toString: String = s"declare $value" - - override def usesVarNames: Set[String] = value.usesVarNames -} - // key must be only string or number case class InsertKeyValueModel( key: ValueModel, diff --git a/model/transform/src/test/scala/aqua/model/transform/topology/TopologySpec.scala b/model/transform/src/test/scala/aqua/model/transform/topology/TopologySpec.scala index 7e2b5b51..632d3fd1 100644 --- a/model/transform/src/test/scala/aqua/model/transform/topology/TopologySpec.scala +++ b/model/transform/src/test/scala/aqua/model/transform/topology/TopologySpec.scala @@ -1,24 +1,24 @@ package aqua.model.transform.topology -import aqua.model.transform.ModelBuilder import aqua.model.* -import aqua.res.* -import aqua.raw.ops.Call -import aqua.raw.value.{IntoIndexRaw, LiteralRaw, VarRaw} -import aqua.types.{LiteralType, ScalarType, StreamType} -import aqua.types.ArrayType -import aqua.raw.ConstantRaw.initPeerId import aqua.model.ForModel +import aqua.model.transform.ModelBuilder +import aqua.raw.ConstantRaw.initPeerId +import aqua.raw.ops.Call import aqua.raw.value.ValueRaw +import aqua.raw.value.{IntoIndexRaw, LiteralRaw, VarRaw} +import aqua.res.* +import aqua.types.ArrayType +import aqua.types.{LiteralType, ScalarType, StreamType} import cats.Eval -import cats.data.{Chain, NonEmptyList} import cats.data.Chain.* +import cats.data.{Chain, NonEmptyList} import cats.free.Cofree +import cats.syntax.option.* +import cats.syntax.show.* import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers -import cats.syntax.show.* -import cats.syntax.option.* class TopologySpec extends AnyFlatSpec with Matchers { @@ -481,7 +481,7 @@ class TopologySpec extends AnyFlatSpec with Matchers { val streamRawEl = VarRaw("stream", StreamType(ScalarType.string)).withProperty( IntoIndexRaw(LiteralRaw("2", ScalarType.u32), ScalarType.string) ) - val stream = ValueModel.fromRaw(streamRaw) + val stream = VarModel(streamRaw.name, streamRaw.baseType) val streamEl = ValueModel.fromRaw(streamRawEl) val (joinModel, joinRes) = joinModelRes(streamEl) @@ -499,7 +499,6 @@ class TopologySpec extends AnyFlatSpec with Matchers { ) ) val init = SeqModel.wrap( - DeclareStreamModel(stream).leaf, OnModel(initPeer, Chain.one(relay)).wrap( foldModel +: joinModel :+ @@ -534,10 +533,7 @@ class TopologySpec extends AnyFlatSpec with Matchers { ) ) val expected = SeqRes.wrap( - Chain( - through(relay), - foldRes - ) ++ + foldRes +: joinRes :+ callRes(3, initPeer, None, stream :: Nil) ) @@ -552,13 +548,12 @@ class TopologySpec extends AnyFlatSpec with Matchers { val streamRawEl = VarRaw("stream", StreamType(ScalarType.string)).withProperty( IntoIndexRaw(LiteralRaw("2", ScalarType.u32), ScalarType.string) ) - val stream = ValueModel.fromRaw(streamRaw) + val stream = VarModel(streamRaw.name, streamRaw.baseType) val streamEl = ValueModel.fromRaw(streamRawEl) val (joinModel, joinRes) = joinModelRes(streamEl) val init = SeqModel.wrap( - DeclareStreamModel(stream).leaf, OnModel(initPeer, Chain.one(relay)).wrap( foldPar( "i", @@ -608,10 +603,7 @@ class TopologySpec extends AnyFlatSpec with Matchers { ) ) val expected = SeqRes.wrap( - Chain( - through(relay), - fold - ) ++ + fold +: joinRes :+ callRes(3, initPeer, None, stream :: Nil) ) diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/ArrowSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/ArrowSem.scala index 52f18e85..7dc2522b 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/ArrowSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/ArrowSem.scala @@ -55,26 +55,12 @@ class ArrowSem[S[_]](val expr: ArrowExpr[S]) extends AnyVal { N: NamesAlgebra[S, Alg], M: ManglerAlgebra[Alg] ): Alg[Raw] = for { - streamsInScope <- N.streamsDefinedWithinScope() retValues <- T.endArrowScope(expr.arrowTypeExpr) // TODO: wrap with local on...via... retsAndCodomain = retValues zip funcArrow.codomain.toList - (streamThatReturnAsStreamVars, streamThatReturnAsStreamNames) = retsAndCodomain.collect { - case (vr @ VarRaw(name, StreamType(_)), StreamType(_)) => (vr, name) - case (vr @ StreamRaw(_, name, _), StreamType(_)) => (vr, name) - }.unzip - // streams that return as streams and derived to another variable - derivedStreamRetValues <- N - .getDerivedFrom(streamThatReturnAsStreamVars.map(_.varNames)) - .map(_.flatten.toSet) res <- bodyGen match { case FuncOp(bodyModel) => - val streamArgNames = funcArrow.domain.labelledStreams.map { case (name, _) => name } - - // Remove arguments, and values returned as streams - val localStreams = streamsInScope -- streamArgNames -- - streamThatReturnAsStreamNames.toSet -- derivedStreamRetValues // process stream that returns as not streams and all Apply*Raw retsAndCodomain.traverse { @@ -124,13 +110,7 @@ class ArrowSem[S[_]](val expr: ArrowExpr[S]) extends AnyVal { val bodyModified = SeqTag.wrap( bodyModel +: bodyRets ) - - // wrap streams with restrictions - val bodyWithRestrictions = - localStreams.foldLeft(bodyModified) { case (bm, (streamName, streamType)) => - RestrictionTag(streamName, streamType).wrap(bm) - } - ArrowRaw(funcArrow, retVals, bodyWithRestrictions) + ArrowRaw(funcArrow, retVals, bodyModified) } case _ => Raw.error("Invalid arrow body").pure[Alg] diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/CatchSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/CatchSem.scala index 9a815542..c2a57c32 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/CatchSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/CatchSem.scala @@ -1,13 +1,14 @@ package aqua.semantics.expr.func -import aqua.raw.ops.{AssignmentTag, FuncOp, SeqTag, TryTag} import aqua.parser.expr.func.CatchExpr -import aqua.raw.value.ValueRaw import aqua.raw.Raw +import aqua.raw.ops.{AssignmentTag, FuncOp, SeqTag, TryTag} +import aqua.raw.value.ValueRaw import aqua.semantics.Prog import aqua.semantics.rules.abilities.AbilitiesAlgebra import aqua.semantics.rules.locations.LocationsAlgebra import aqua.semantics.rules.names.NamesAlgebra + import cats.Monad import cats.syntax.applicative.* import cats.syntax.flatMap.* @@ -15,7 +16,7 @@ import cats.syntax.functor.* class CatchSem[S[_]](val expr: CatchExpr[S]) extends AnyVal { - def program[Alg[_]: Monad](implicit + def program[Alg[_]: Monad](using N: NamesAlgebra[S, Alg], A: AbilitiesAlgebra[S, Alg], L: LocationsAlgebra[S, Alg] @@ -26,16 +27,15 @@ class CatchSem[S[_]](val expr: CatchExpr[S]) extends AnyVal { (_, g: Raw) => g match { case FuncOp(op) => - for { - restricted <- FuncOpSem.restrictStreamsInScope(op) - tag = TryTag.Catch - .wrap( - SeqTag.wrap( - AssignmentTag(ValueRaw.error, expr.name.value).leaf, - restricted - ) + TryTag.Catch + .wrap( + SeqTag.wrap( + AssignmentTag(ValueRaw.error, expr.name.value).leaf, + op ) - } yield tag.toFuncOp + ) + .toFuncOp + .pure case _ => Raw.error("Wrong body of the `catch` expression").pure } diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/ElseOtherwiseSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/ElseOtherwiseSem.scala index df98dd71..ce74c18b 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/ElseOtherwiseSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/ElseOtherwiseSem.scala @@ -1,16 +1,16 @@ package aqua.semantics.expr.func -import aqua.raw.ops.{FuncOp, IfTag, TryTag} import aqua.parser.expr.func.ElseOtherwiseExpr import aqua.raw.Raw +import aqua.raw.ops.{FuncOp, IfTag, TryTag} import aqua.semantics.Prog import aqua.semantics.rules.abilities.AbilitiesAlgebra import aqua.semantics.rules.locations.LocationsAlgebra import aqua.semantics.rules.names.NamesAlgebra +import cats.Monad import cats.syntax.applicative.* import cats.syntax.functor.* -import cats.Monad class ElseOtherwiseSem[S[_]](val expr: ElseOtherwiseExpr[S]) extends AnyVal { @@ -23,15 +23,14 @@ class ElseOtherwiseSem[S[_]](val expr: ElseOtherwiseExpr[S]) extends AnyVal { .after((ops: Raw) => ops match { case FuncOp(op) => - for { - restricted <- FuncOpSem.restrictStreamsInScope(op) - tag = expr.kind - .fold( - ifElse = IfTag.Else, - ifOtherwise = TryTag.Otherwise - ) - .wrap(restricted) - } yield tag.toFuncOp + expr.kind + .fold( + ifElse = IfTag.Else, + ifOtherwise = TryTag.Otherwise + ) + .wrap(op) + .toFuncOp + .pure case _ => val name = expr.kind.fold("`else`", "`otherwise`") Raw.error(s"Wrong body of the $name expression").pure diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/ForSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/ForSem.scala index 0a2f8c74..8ee6551b 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/ForSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/ForSem.scala @@ -7,7 +7,6 @@ import aqua.raw.ops.* import aqua.raw.ops.ForTag.Mode import aqua.raw.value.ValueRaw import aqua.semantics.Prog -import aqua.semantics.expr.func.FuncOpSem import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.abilities.AbilitiesAlgebra import aqua.semantics.rules.names.NamesAlgebra @@ -38,37 +37,35 @@ class ForSem[S[_]](val expr: ForExpr[S]) extends AnyVal { (iterable, ops: Raw) => (iterable, ops) match { case (Some(vm), FuncOp(op)) => - FuncOpSem.restrictStreamsInScope(op).map { restricted => - val mode = expr.mode.fold(ForTag.Mode.SeqMode) { - case ForExpr.Mode.ParMode => ForTag.Mode.ParMode - case ForExpr.Mode.TryMode => ForTag.Mode.TryMode - case ForExpr.Mode.RecMode => ForTag.Mode.RecMode - } - - val innerTag = mode match { - case ForTag.Mode.SeqMode => SeqTag - case ForTag.Mode.ParMode => ParTag - case ForTag.Mode.TryMode => TryTag - case ForTag.Mode.RecMode => ParTag - } - - val forTag = ForTag(expr.item.value, vm, mode).wrap( - innerTag.wrap( - restricted, - NextTag(expr.item.value).leaf - ) - ) - - // Fix: continue execution after fold par immediately, - // without finding a path out from par branches - val result = mode match { - case ForTag.Mode.ParMode | ForTag.Mode.RecMode => - ParTag.Detach.wrap(forTag) - case _ => forTag - } - - result.toFuncOp + val mode = expr.mode.fold(ForTag.Mode.SeqMode) { + case ForExpr.Mode.ParMode => ForTag.Mode.ParMode + case ForExpr.Mode.TryMode => ForTag.Mode.TryMode + case ForExpr.Mode.RecMode => ForTag.Mode.RecMode } + + val innerTag = mode match { + case ForTag.Mode.SeqMode => SeqTag + case ForTag.Mode.ParMode => ParTag + case ForTag.Mode.TryMode => TryTag + case ForTag.Mode.RecMode => ParTag + } + + val forTag = ForTag(expr.item.value, vm, mode).wrap( + innerTag.wrap( + op, + NextTag(expr.item.value).leaf + ) + ) + + // Fix: continue execution after fold par immediately, + // without finding a path out from par branches + val result = mode match { + case ForTag.Mode.ParMode | ForTag.Mode.RecMode => + ParTag.Detach.wrap(forTag) + case _ => forTag + } + + result.toFuncOp.pure case _ => Raw.error("Wrong body of the `for` expression").pure[F] } ) diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/FuncOpSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/FuncOpSem.scala deleted file mode 100644 index 28daaa45..00000000 --- a/semantics/src/main/scala/aqua/semantics/expr/func/FuncOpSem.scala +++ /dev/null @@ -1,21 +0,0 @@ -package aqua.semantics.expr.func - -import aqua.raw.ops.{RawTag, RestrictionTag} -import aqua.semantics.rules.names.NamesAlgebra -import cats.{FlatMap, Functor, Monad} -import cats.syntax.functor.* -import cats.syntax.flatMap.* - -object FuncOpSem { - - def restrictStreamsInScope[S[_], Alg[_]: Monad]( - tree: RawTag.Tree - )(using N: NamesAlgebra[S, Alg]): Alg[RawTag.Tree] = N - .streamsDefinedWithinScope() - .map(streams => - streams.toList - .foldLeft(tree) { case (tree, (streamName, streamType)) => - RestrictionTag(streamName, streamType).wrap(tree) - } - ) -} diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/IfSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/IfSem.scala index bd25334b..006a3cd3 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/IfSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/IfSem.scala @@ -1,24 +1,24 @@ package aqua.semantics.expr.func -import aqua.raw.ops.{FuncOp, IfTag} import aqua.parser.expr.func.IfExpr -import aqua.raw.value.ValueRaw import aqua.raw.Raw +import aqua.raw.ops.{FuncOp, IfTag} +import aqua.raw.value.ValueRaw import aqua.semantics.Prog import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.abilities.AbilitiesAlgebra import aqua.semantics.rules.locations.LocationsAlgebra import aqua.semantics.rules.names.NamesAlgebra import aqua.semantics.rules.types.TypesAlgebra +import aqua.types.ScalarType import aqua.types.Type import cats.Monad import cats.syntax.applicative.* +import cats.syntax.apply.* import cats.syntax.flatMap.* import cats.syntax.functor.* -import cats.syntax.apply.* import cats.syntax.traverse.* -import aqua.types.ScalarType class IfSem[S[_]](val expr: IfExpr[S]) extends AnyVal { @@ -46,10 +46,7 @@ class IfSem[S[_]](val expr: IfExpr[S]) extends AnyVal { (value, ops: Raw) => (value, ops) match { case (Some(vr), FuncOp(op)) => - for { - restricted <- FuncOpSem.restrictStreamsInScope(op) - tag = IfTag(vr).wrap(restricted) - } yield tag.toFuncOp + IfTag(vr).wrap(op).toFuncOp.pure case (None, _) => Raw.error("`if` expression errored in matching types").pure case _ => Raw.error("Wrong body of the `if` expression").pure } diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/ParSeqSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/ParSeqSem.scala index 852f9502..ac9829f0 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/ParSeqSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/ParSeqSem.scala @@ -56,26 +56,26 @@ class ParSeqSem[S[_]](val expr: ParSeqExpr[S]) extends AnyVal { ): F[Raw] = V.valueToRaw(expr.peerId).map((_, iterableVM, ops)).flatMap { case (Some(peerId), Some(vm), FuncOp(op)) => - for { - restricted <- FuncOpSem.restrictStreamsInScope(op) - onTag = OnTag( - peerId = peerId, - via = Chain.fromSeq(viaVM), - strategy = OnTag.ReturnStrategy.Relay.some - ) - /** - * `parseq` => par (`never` as `last` in `fold`) - * So that peer initiating `parseq` would not continue execution past it - */ - tag = ForTag - .par(expr.item.value, vm) - .wrap( - ParTag.wrap( - onTag.wrap(restricted), - NextTag(expr.item.value).leaf - ) + val onTag = OnTag( + peerId = peerId, + via = Chain.fromSeq(viaVM), + strategy = OnTag.ReturnStrategy.Relay.some + ) + + /** + * `parseq` => par (`never` as `last` in `fold`) + * So that peer initiating `parseq` would not continue execution past it + */ + ForTag + .par(expr.item.value, vm) + .wrap( + ParTag.wrap( + onTag.wrap(op), + NextTag(expr.item.value).leaf ) - } yield tag.toFuncOp + ) + .toFuncOp + .pure case (None, _, _) => Raw.error("ParSeqSem: could not resolve `peerId`").pure case (_, None, _) => Raw.error("ParSeqSem: could not resolve `iterable`").pure case (_, _, _) => Raw.error("ParSeqSem: wrong body of `parseq` block").pure diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/TrySem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/TrySem.scala index d5b286a3..d7718ba1 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/TrySem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/TrySem.scala @@ -1,8 +1,8 @@ package aqua.semantics.expr.func -import aqua.raw.ops.{FuncOp, TryTag} import aqua.parser.expr.func.TryExpr import aqua.raw.Raw +import aqua.raw.ops.{FuncOp, TryTag} import aqua.semantics.Prog import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.abilities.AbilitiesAlgebra @@ -10,9 +10,9 @@ import aqua.semantics.rules.locations.LocationsAlgebra import aqua.semantics.rules.names.NamesAlgebra import aqua.semantics.rules.types.TypesAlgebra +import cats.Monad import cats.syntax.applicative.* import cats.syntax.functor.* -import cats.Monad class TrySem[S[_]](val expr: TryExpr[S]) extends AnyVal { @@ -27,10 +27,7 @@ class TrySem[S[_]](val expr: TryExpr[S]) extends AnyVal { .after((ops: Raw) => ops match { case FuncOp(op) => - for { - restricted <- FuncOpSem.restrictStreamsInScope(op) - tag = TryTag.wrap(restricted) - } yield tag.toFuncOp + TryTag.wrap(op).toFuncOp.pure case _ => Raw.error("Wrong body of the `try` expression").pure[Alg] } diff --git a/semantics/src/main/scala/aqua/semantics/rules/names/NamesAlgebra.scala b/semantics/src/main/scala/aqua/semantics/rules/names/NamesAlgebra.scala index 44bd6ce0..9bf0d2bc 100644 --- a/semantics/src/main/scala/aqua/semantics/rules/names/NamesAlgebra.scala +++ b/semantics/src/main/scala/aqua/semantics/rules/names/NamesAlgebra.scala @@ -2,12 +2,13 @@ package aqua.semantics.rules.names import aqua.parser.lexer.{LiteralToken, Name, Token, ValueToken} import aqua.types.{ArrowType, StreamType, Type} + import cats.InjectK trait NamesAlgebra[S[_], Alg[_]] { def read(name: Name[S], mustBeDefined: Boolean = true): Alg[Option[Type]] - + // TODO can be implemented via read? def constantDefined(name: Name[S]): Alg[Option[Type]] @@ -25,8 +26,6 @@ trait NamesAlgebra[S[_], Alg[_]] { def defineArrow(name: Name[S], gen: ArrowType, isRoot: Boolean): Alg[Boolean] - def streamsDefinedWithinScope(): Alg[Map[String, StreamType]] - def beginScope(token: Token[S]): Alg[Unit] def endScope(): Alg[Unit] diff --git a/semantics/src/main/scala/aqua/semantics/rules/names/NamesInterpreter.scala b/semantics/src/main/scala/aqua/semantics/rules/names/NamesInterpreter.scala index d47cd85e..a2de37a7 100644 --- a/semantics/src/main/scala/aqua/semantics/rules/names/NamesInterpreter.scala +++ b/semantics/src/main/scala/aqua/semantics/rules/names/NamesInterpreter.scala @@ -168,13 +168,6 @@ class NamesInterpreter[S[_], X](using ) } - override def streamsDefinedWithinScope(): SX[Map[String, StreamType]] = - mapStackHead(Map.empty) { frame => - frame -> frame.names.collect { case (n, st @ StreamType(_)) => - n -> st - } - } - override def beginScope(token: Token[S]): SX[Unit] = stackInt.beginScope(NamesState.Frame(token)) diff --git a/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala b/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala index 67604f16..0e328f64 100644 --- a/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala +++ b/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala @@ -142,14 +142,12 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside { val streamType = StreamType(ScalarType.string) val stream = VarRaw(name, streamType) - RestrictionTag(stream.name, streamType).wrap( - SeqTag.wrap( - DeclareStreamTag(stream).leaf, - PushToStreamTag( - LiteralRaw.quote(value), - Call.Export(name, streamType) - ).leaf - ) + SeqTag.wrap( + DeclareStreamTag(stream).leaf, + PushToStreamTag( + LiteralRaw.quote(value), + Call.Export(name, streamType) + ).leaf ) }