From 4e3e70f4fc855a16238c4f84bd4f6a1102890904 Mon Sep 17 00:00:00 2001 From: Dima Date: Mon, 31 Jul 2023 14:40:06 +0300 Subject: [PATCH] fix(compiler): Nested abilities [fixes LNG-214] (#816) --- aqua-src/antithesis.aqua | 88 ++++++- .../aqua/examples/abilities.aqua | 52 +++- .../src/__test__/examples.spec.ts | 12 +- integration-tests/src/examples/abilityCall.ts | 10 +- .../aqua/model/inline/ArrowInliner.scala | 244 ++++++++++-------- .../scala/aqua/model/inline/TagInliner.scala | 26 +- .../raw/ApplyPropertiesRawInliner.scala | 93 +++---- .../inline/raw/CallArrowRawInliner.scala | 8 +- .../inline/raw/MakeAbilityRawInliner.scala | 48 ++-- .../aqua/model/inline/state/Exports.scala | 150 ++++++++++- .../main/scala/aqua/model/ValueModel.scala | 9 + .../scala/aqua/parser/lexer/TypeToken.scala | 1 + .../aqua/semantics/expr/AbilitySem.scala | 4 +- .../aqua/semantics/rules/ValuesAlgebra.scala | 59 +++-- types/src/main/scala/aqua/types/Type.scala | 6 +- 15 files changed, 555 insertions(+), 255 deletions(-) diff --git a/aqua-src/antithesis.aqua b/aqua-src/antithesis.aqua index 8aa4d593..deb7165c 100644 --- a/aqua-src/antithesis.aqua +++ b/aqua-src/antithesis.aqua @@ -1,11 +1,87 @@ aqua Main -export a +use DECLARE_CONST, decl_bar from "declare.aqua" as Declare -alias CL: string -> () +export SomeService, handleAb, bug214, checkAbCalls -func a(cl: string -> ()) -> CL: - <- cl +service SomeService("wed"): + getStr(s: string) -> string -func b(c: u32, d: u32) -> u32: - <- c + d \ No newline at end of file +ability SomeAb: + someArrow(s: string) -> string, string + str: string + +ability SecondAb: + arrow(s: string) -> string + num: u32 + +func funcStr(s: string) -> string, string: + strInFunc <- SomeService.getStr(Declare.DECLARE_CONST) + strInFunc2 <- SomeService.getStr(s) + <- strInFunc, strInFunc2 + +func handleSecAb {SomeAb, SecondAb}() -> string, string, string, u32: + SomeAb.someArrow("eferfrfrf") + b, c <- SomeAb.someArrow("efre") + d <- SecondAb.arrow(SomeAb.str) + <- b, c, d, SecondAb.num + +func returnAb(s: string) -> SomeAb: + SomeAb = SomeAb(someArrow = funcStr, str = s) + <- SomeAb + +func handleAb(fff: string) -> string, string, string, u32: + SomeAb = returnAb(fff) + SecondAb = SecondAb(arrow = funcStr, num = 12) + res1, res2, res3, res4 <- handleSecAb{SomeAb, SecondAb}() + <- res1, res2, res3, res4 + +data Struct: + int: i8 + +ability Simple: + st: Struct + arrow(x: i8) -> bool + +ability Complex: + simple: Simple + field: string + +func foo{Complex, Simple}() -> bool, bool: + closure = () -> bool: + <- Simple.st.int >= 0 + res <- closure() + <- Complex.simple.arrow( + Complex.simple.st.int + ), res + +func bug214() -> bool, bool: + closure = (x: i8) -> bool: + <- x > 0 + + MyComplex = Complex( + simple = Simple( + st = Struct(int = 0), + arrow = closure + ), + field = "complex" + ) + + res1, res2 <- foo{MyComplex, MyComplex.simple}() + <- res1, res2 + +ability SSS: + arrow(x: i8) -> bool + +ability CCCC: + arrow(x: i8) -> bool + simple: SSS + +func checkAbCalls() -> bool, bool: + closure = (x: i8) -> bool: + <- x > 20 + + MySSS = SSS(arrow = closure) + MyCCCC = CCCC(simple = MySSS, arrow = MySSS.arrow) + + <- MySSS.arrow(42), MyCCCC.arrow(12) diff --git a/integration-tests/aqua/examples/abilities.aqua b/integration-tests/aqua/examples/abilities.aqua index 205df256..f7752f3e 100644 --- a/integration-tests/aqua/examples/abilities.aqua +++ b/integration-tests/aqua/examples/abilities.aqua @@ -2,7 +2,7 @@ aqua Main use DECLARE_CONST, decl_bar from "imports_exports/declare.aqua" as Declare -export handleAb, SomeService +export handleAb, SomeService, bug214, checkAbCalls service SomeService("wed"): getStr(s: string) -> string @@ -35,3 +35,53 @@ func handleAb(fff: string) -> string, string, string, u32: SecondAb = SecondAb(arrow = funcStr, num = 12) res1, res2, res3, res4 <- handleSecAb{SomeAb, SecondAb}() <- res1, res2, res3, res4 + +data Struct: + int: i8 + +ability Simple: + st: Struct + arrow(x: i8) -> bool + +ability Complex: + simple: Simple + field: string + +func foo{Complex, Simple}() -> bool, bool: + closure = () -> bool: + <- Simple.st.int >= 0 + res <- closure() + <- Complex.simple.arrow( + Complex.simple.st.int + ), res + +func bug214() -> bool, bool: + closure = (x: i8) -> bool: + <- x > 0 + + MyComplex = Complex( + simple = Simple( + st = Struct(int = 0), + arrow = closure + ), + field = "complex" + ) + + res1, res2 <- foo{MyComplex, MyComplex.simple}() + <- res1, res2 + +ability SSS: + arrow(x: i8) -> bool + +ability CCCC: + arrow(x: i8) -> bool + simple: SSS + +func checkAbCalls() -> bool, bool: + closure = (x: i8) -> bool: + <- x > 20 + + MySSS = SSS(arrow = closure) + MyCCCC = CCCC(simple = MySSS, arrow = MySSS.arrow) + + <- MySSS.arrow(42), MyCCCC.arrow(12) diff --git a/integration-tests/src/__test__/examples.spec.ts b/integration-tests/src/__test__/examples.spec.ts index bf49fc09..a8cf2a85 100644 --- a/integration-tests/src/__test__/examples.spec.ts +++ b/integration-tests/src/__test__/examples.spec.ts @@ -13,6 +13,7 @@ import { bugNG69Call, ifCall, ifWrapCall } from '../examples/ifCall.js'; import { parCall, testTimeoutCall } from '../examples/parCall.js'; import { complexCall } from '../examples/complex.js'; import { constantsCall, particleTtlAndTimestampCall } from '../examples/constantsCall.js'; +import { abilityCall, complexAbilityCall, checkAbCallsCall } from '../examples/abilityCall.js'; import { nilLengthCall, nilLiteralCall, @@ -77,7 +78,6 @@ export const relay2 = config.relays[1]; const relayPeerId2 = relay2.peerId; import log from 'loglevel'; -import { abilityCall } from '../examples/abilityCall'; // log.setDefaultLevel("debug") async function start() { @@ -354,6 +354,16 @@ describe('Testing examples', () => { expect(result).toStrictEqual(['declare_const123', 'efre123', 'declare_const123', 12]); }); + it('ability.aqua complex', async () => { + let result = await complexAbilityCall(); + expect(result).toStrictEqual([false, true]); + }); + + it('ability.aqua complex', async () => { + let result = await checkAbCallsCall(); + expect(result).toStrictEqual([true, false]); + }); + it('functors.aqua LNG-119 bug', async () => { let result = await bugLng119Call(); expect(result).toEqual([1]); diff --git a/integration-tests/src/examples/abilityCall.ts b/integration-tests/src/examples/abilityCall.ts index 6de9f9c2..1e84c4cb 100644 --- a/integration-tests/src/examples/abilityCall.ts +++ b/integration-tests/src/examples/abilityCall.ts @@ -1,4 +1,4 @@ -import {handleAb, registerSomeService} from "../compiled/examples/abilities"; +import {handleAb, registerSomeService, bug214, checkAbCalls} from "../compiled/examples/abilities"; export async function abilityCall(): Promise<[string, string, string, number]> { registerSomeService({ @@ -9,3 +9,11 @@ export async function abilityCall(): Promise<[string, string, string, number]> { return await handleAb("some_string") } + +export async function complexAbilityCall(): Promise<[boolean, boolean]> { + return await bug214() +} + +export async function checkAbCallsCall(): Promise<[boolean, boolean]> { + return await checkAbCalls() +} 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 a5c62e7c..56cb7fe7 100644 --- a/model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala @@ -6,7 +6,7 @@ import aqua.model.* import aqua.raw.ops.RawTag import aqua.types.{AbilityType, ArrowType, BoxType, DataType, StreamType, Type} import aqua.raw.value.{ValueRaw, VarRaw} -import cats.Eval +import cats.{Eval, Monoid} import cats.data.{Chain, IndexedStateT, State} import cats.syntax.traverse.* import cats.syntax.apply.* @@ -91,59 +91,50 @@ object ArrowInliner extends Logging { // Apply a callable function, get its fully resolved body & optional value, if any private def inline[S: Mangler: Arrows: Exports]( fn: FuncArrow, - call: CallModel + call: CallModel, + outsideDeclaredStreams: Set[String] ): State[S, InlineResult] = - (Exports[S].exports, getOutsideStreamNames).flatMapN { - case (oldExports, outsideDeclaredStreams) => - // Function's internal variables will not be available outside, hence the scope - Exports[S].scope( - for { - // Process renamings, prepare environment - tr <- prelude[S](fn, call, oldExports) - (tree, results) = tr + for { + // Register captured values as available exports + _ <- Exports[S].resolved(fn.capturedValues) + _ <- Mangler[S].forbid(fn.capturedValues.keySet) - // Register captured values as available exports - _ <- Exports[S].resolved(fn.capturedValues) - _ <- Mangler[S].forbid(fn.capturedValues.keySet) + // Now, substitute the arrows that were received as function arguments + // Use the new op tree (args are replaced with values, names are unique & safe) + callableFuncBodyNoTopology <- TagInliner.handleTree(fn.body, fn.funcName) + callableFuncBody = + fn.capturedTopology + .fold[OpModel](SeqModel)(ApplyTopologyModel.apply) + .wrap(callableFuncBodyNoTopology) - // Now, substitute the arrows that were received as function arguments - // Use the new op tree (args are replaced with values, names are unique & safe) - callableFuncBodyNoTopology <- TagInliner.handleTree(tree, fn.funcName) - callableFuncBody = - fn.capturedTopology - .fold[OpModel](SeqModel)(ApplyTopologyModel.apply) - .wrap(callableFuncBodyNoTopology) + opsAndRets <- pushStreamResults( + outsideDeclaredStreams, + call.exportTo, + fn.ret, + callableFuncBody + ) + (ops, rets) = opsAndRets - opsAndRets <- pushStreamResults( - outsideDeclaredStreams, - call.exportTo, - results, - callableFuncBody - ) - (ops, rets) = opsAndRets + exports <- Exports[S].exports + arrows <- Arrows[S].arrows + // gather all arrows and variables from abilities + returnedFromAbilities = rets.collect { case VarModel(name, st @ AbilityType(_, _), _) => + getVarsAndArrowsFromAbilities(name, None, st, exports, arrows) + }.foldMapA(_.bimap(_.toList, _.toList)).bimap(_.toMap, _.toMap) - exports <- Exports[S].exports - arrows <- Arrows[S].arrows - // gather all arrows and variables from abilities - returnedFromAbilities = rets.collect { case VarModel(name, st @ AbilityType(_, _), _) => - getVarsAndArrowsFromAbilities(name, None, st, exports, arrows) - }.foldMapA(_.bimap(_.toList, _.toList)).bimap(_.toMap, _.toMap) - - // 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) - } yield { - val (valsFromAbilities, arrowsFromAbilities) = returnedFromAbilities - InlineResult( - SeqModel.wrap(ops.reverse: _*), - rets.reverse, - valsFromAbilities, - arrowsFromAbilities ++ arrowsToSave - ) - } - ) + // 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) + } yield { + val (valsFromAbilities, arrowsFromAbilities) = returnedFromAbilities + InlineResult( + SeqModel.wrap(ops.reverse: _*), + rets.reverse, + valsFromAbilities, + arrowsFromAbilities ++ arrowsToSave + ) } /** @@ -243,32 +234,47 @@ object ArrowInliner extends Logging { renamedArrows: Map[String, FuncArrow] ) + given Monoid[AbilityResolvingResult] with + + override val empty: AbilityResolvingResult = + AbilityResolvingResult(Map.empty, Map.empty, Map.empty) + + override def combine( + a: AbilityResolvingResult, + b: AbilityResolvingResult + ): AbilityResolvingResult = + AbilityResolvingResult( + a.namesToRename ++ b.namesToRename, + a.renamedExports ++ b.renamedExports, + a.renamedArrows ++ b.renamedArrows + ) + /** * Generate new names for all ability fields and arrows if necessary. * Gather all fields and arrows from Arrows and Exports states * @param name ability name in state * @param vm ability variable * @param t ability type - * @param oldExports previous Exports - * @param oldArrows previous Arrows + * @param exports previous Exports + * @param arrows previous Arrows * @return names to rename, Exports and Arrows with all ability fields and arrows */ private def renameAndResolveAbilities[S: Mangler: Arrows: Exports]( name: String, vm: VarModel, t: AbilityType, - oldExports: Map[String, ValueModel], - oldArrows: Map[String, FuncArrow] + exports: Map[String, ValueModel], + arrows: Map[String, FuncArrow] ): State[S, AbilityResolvingResult] = { for { newName <- Mangler[S].findNewName(name) newFieldsName = t.fields.mapBoth { case (n, t) => - s"$name.$n" -> s"$newName.$n" + AbilityType.fullName(name, n) -> AbilityType.fullName(newName, n) } allNewNames = newFieldsName.add((name, newName)).toSortedMap } yield { val (allVars, allArrows) = - getVarsAndArrowsFromAbilities(vm.name, Option(newName), t, oldExports, oldArrows) + getVarsAndArrowsFromAbilities(vm.name, Option(newName), t, exports, arrows) AbilityResolvingResult(allNewNames, allVars, allArrows) } } @@ -281,58 +287,50 @@ object ArrowInliner extends Logging { * @param topOldName old name to find all fields in states * @param topNewName new name to rename all fields in states * @param abilityType type of current ability - * @param oldExports where to get values - * @param oldArrows where to get arrows - * @param valAcc accumulator for values - * @param arrowsAcc accumulator for arrows + * @param exports where to get values + * @param arrows where to get arrows * @return */ private def getVarsAndArrowsFromAbilities( topOldName: String, topNewName: Option[String], abilityType: AbilityType, - oldExports: Map[String, ValueModel], - oldArrows: Map[String, FuncArrow], - valAcc: Map[String, ValueModel] = Map.empty, - arrowsAcc: Map[String, FuncArrow] = Map.empty + exports: Map[String, ValueModel], + arrows: Map[String, FuncArrow] ): (Map[String, ValueModel], Map[String, FuncArrow]) = { abilityType.fields.toSortedMap.toList.map { case (fName, fValue) => - val currentOldName = s"$topOldName.$fName" + val currentOldName = AbilityType.fullName(topOldName, fName) // for all nested fields, arrows and abilities only left side must be renamed - val currentNewName = topNewName.map(_ + s".$fName") + val currentNewName = topNewName.map(AbilityType.fullName(_, fName)) fValue match { case nestedAbilityType @ AbilityType(_, _) => getVarsAndArrowsFromAbilities( currentOldName, currentNewName, nestedAbilityType, - oldExports, - oldArrows, - valAcc, - arrowsAcc + exports, + arrows ) case ArrowType(_, _) => - oldExports - .get(currentOldName) - .flatMap { - case vm @ VarModel(name, _, _) => - oldArrows - .get(name) - .map(fa => - ( - valAcc.updated(currentNewName.getOrElse(currentOldName), vm), - arrowsAcc.updated(name, fa) - ) + Exports + .getLastValue(currentOldName, exports) + .flatMap { case vm @ VarModel(name, _, _) => + arrows + .get(name) + .map(fa => + ( + Map(currentNewName.getOrElse(currentOldName) -> vm), + Map(name -> fa) ) - case _ => None + ) } - .getOrElse((valAcc, arrowsAcc)) + .getOrElse((Map.empty, Map.empty)) case _ => - oldExports - .get(currentOldName) - .map(vm => (valAcc.updated(currentNewName.getOrElse(currentOldName), vm), arrowsAcc)) - .getOrElse((valAcc, arrowsAcc)) + Exports + .getLastValue(currentOldName, exports) + .map(vm => (Map(currentNewName.getOrElse(currentOldName) -> vm), Map.empty)) + .getOrElse((Map.empty, Map.empty)) } }.foldMapA(_.bimap(_.toList, _.toList)).bimap(_.toMap, _.toMap) } @@ -352,7 +350,8 @@ object ArrowInliner extends Logging { private def prelude[S: Mangler: Arrows: Exports]( fn: FuncArrow, call: CallModel, - oldExports: Map[String, ValueModel] + oldExports: Map[String, ValueModel], + arrows: Map[String, FuncArrow] ): State[S, (RawTag.Tree, List[ValueRaw])] = for { // Collect all arguments: what names are used inside the function, what values are received @@ -360,24 +359,47 @@ object ArrowInliner extends Logging { abArgs = args.abilityArgs - // Going to resolve arrows: collect them all. Names should never collide: it's semantically checked - previousArrowsState <- Arrows[S].arrows - - _ <- Arrows[S].purge - abilityResolvingResult <- abArgs.toList.traverse { case (str, (vm, sct)) => - renameAndResolveAbilities(str, vm, sct, oldExports, previousArrowsState) - } + renameAndResolveAbilities(str, vm, sct, oldExports, arrows) + }.map(_.combineAll) - absRenames = abilityResolvingResult.map(_.namesToRename).fold(Map.empty)(_ ++ _) - absVars = abilityResolvingResult.map(_.renamedExports).fold(Map.empty)(_ ++ _) - absArrows = abilityResolvingResult.map(_.renamedArrows).fold(Map.empty)(_ ++ _) + absRenames = abilityResolvingResult.namesToRename + absVars = abilityResolvingResult.renamedExports + absArrows = abilityResolvingResult.renamedArrows - arrowArgs = args.arrowArgs(previousArrowsState) + arrowArgs = args.arrowArgs(arrows) // Update states and rename tags renamedArrows <- updateArrowsAndRenameArrowArgs(arrowArgs ++ absArrows, fn, absRenames) + argsToDataShouldRename <- updateExportsAndRenameDataArgs(args.dataArgs ++ absVars, absRenames) - allShouldRename = argsToDataShouldRename ++ renamedArrows ++ absRenames + + // rename variables that store arrows + _ <- Exports[S].renameVariables(renamedArrows) + + /* + * Don't rename arrows from abilities in a body, because we link arrows by VarModel + * and they won't be called directly. + * They could intersect with arrows defined inside the body + * + * ability Simple: + * arrow() -> bool + * + * func foo{Simple}() -> bool, bool: + * closure = () -> bool: + * <- true + * <- closure(), Simple.arrow() + * + * func main() -> bool, bool: + * closure = () -> bool: + * <- false + * MySimple = Simple(arrow = closure) + * -- here we will rename arrow in Arrows[S] to 'closure-0' + * -- and link to arrow as 'Simple.arrow' -> VarModel('closure-0') + * -- and it will work well with closure with the same name 'closure' inside 'foo' + * foo{MySimple}() + */ + allShouldRename = argsToDataShouldRename ++ (renamedArrows -- absArrows.keySet) ++ absRenames + // Rename all renamed arguments in the body treeRenamed = fn.body.rename(allShouldRename) treeStreamsRenamed = renameStreams(treeRenamed, args.streamArgs) @@ -415,8 +437,7 @@ object ArrowInliner extends Logging { } yield { sc.fields.toSortedMap.toList.flatMap { case (n, ArrowType(_, _)) => - val fullName = s"$name.$n" - exports.get(fullName).flatMap { + exports.get(AbilityType.fullName(name, n)).flatMap { case VarModel(n, _, _) => arrows.get(n).map(n -> _) case _ => None } @@ -435,11 +456,22 @@ object ArrowInliner extends Logging { .traverse(getAllArrowsFromAbility) .map(_.fold(Map.empty)(_ ++ _)) - inlineResult <- Arrows[S].scope( - for { - _ <- Arrows[S].resolved(passArrows ++ arrowsFromAbilities) - inlineResult <- ArrowInliner.inline(arrow, call) - } yield inlineResult + exports <- Exports[S].exports + streams <- getOutsideStreamNames + + inlineResult <- Exports[S].scope( + Arrows[S].scope( + for { + // Process renamings, prepare environment + tr <- prelude[S](arrow, call, exports, passArrows ++ arrowsFromAbilities) + (tree, results) = tr + inlineResult <- ArrowInliner.inline( + arrow.copy(body = tree, ret = results), + call, + streams + ) + } yield inlineResult + ) ) _ <- Arrows[S].resolved(inlineResult.arrowsToSave) 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 716dcb60..5e7cf45c 100644 --- a/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala @@ -329,19 +329,19 @@ object TagInliner extends Logging { } case AssignmentTag(value, assignTo) => - (value match { - // if we assign collection to a stream, we must use it's name, because it is already created with 'new' - case c @ CollectionRaw(_, _: StreamType) => - collectionToModel(c, Some(assignTo)) - case v => - valueToModel(v, false) - }).flatMap { case (model, prefix) => - for { - // NOTE: Name should not exist yet - _ <- Mangler[S].forbidName(assignTo) - _ <- Exports[S].resolved(assignTo, model) - } yield TagInlined.Empty(prefix = prefix) - } + for { + // NOTE: Name should not exist yet + _ <- Mangler[S].forbidName(assignTo) + modelAndPrefix <- value match { + // if we assign collection to a stream, we must use it's name, because it is already created with 'new' + case c @ CollectionRaw(_, _: StreamType) => + collectionToModel(c, Some(assignTo)) + case v => + valueToModel(v, false) + } + (model, prefix) = modelAndPrefix + _ <- Exports[S].resolved(assignTo, model) + } yield TagInlined.Empty(prefix = prefix) case ClosureTag(arrow, detach) => if (detach) Arrows[S].resolved(arrow, None).as(TagInlined.Empty()) diff --git a/model/inline/src/main/scala/aqua/model/inline/raw/ApplyPropertiesRawInliner.scala b/model/inline/src/main/scala/aqua/model/inline/raw/ApplyPropertiesRawInliner.scala index 863bc797..df63c3e5 100644 --- a/model/inline/src/main/scala/aqua/model/inline/raw/ApplyPropertiesRawInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/raw/ApplyPropertiesRawInliner.scala @@ -1,62 +1,21 @@ package aqua.model.inline.raw -import aqua.model.{ - CallModel, - CallServiceModel, - FlattenModel, - ForModel, - FunctorModel, - IntoFieldModel, - IntoIndexModel, - LiteralModel, - MatchMismatchModel, - NextModel, - OpModel, - PropertyModel, - PushToStreamModel, - SeqModel, - ValueModel, - VarModel, - XorModel -} +import aqua.model.* +import aqua.model.ValueModel.Ability import aqua.model.inline.Inline import aqua.model.inline.Inline.MergeMode.* import aqua.model.inline.RawValueInliner.unfold import aqua.model.inline.state.{Arrows, Exports, Mangler} -import aqua.raw.value.{ - ApplyGateRaw, - ApplyPropertyRaw, - CallArrowRaw, - FunctorRaw, - IntoArrowRaw, - IntoCopyRaw, - IntoFieldRaw, - IntoIndexRaw, - LiteralRaw, - PropertyRaw, - ValueRaw, - VarRaw -} -import aqua.types.{ - AbilityType, - ArrayType, - ArrowType, - BottomType, - CanonStreamType, - NilType, - ScalarType, - StreamType, - Type -} - +import aqua.raw.value.* +import aqua.types.* import cats.Eval -import cats.syntax.bifunctor.* import cats.data.{Chain, IndexedStateT, State} +import cats.instances.list.* +import cats.syntax.applicative.* +import cats.syntax.bifunctor.* +import cats.syntax.foldable.* import cats.syntax.monoid.* import cats.syntax.traverse.* -import cats.syntax.foldable.* -import cats.Id -import cats.instances.list.* import scribe.Logging object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Logging { @@ -100,12 +59,12 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi private def unfoldAbilityProperty[S: Mangler: Exports: Arrows]( varModel: VarModel, - scopeType: AbilityType, + abilityType: AbilityType, p: PropertyRaw ): State[S, (VarModel, Inline)] = { p match { case IntoArrowRaw(arrowName, t, arguments) => - val arrowType = scopeType.fields + val arrowType = abilityType.fields .lookup(arrowName) .collect { case at @ ArrowType(_, _) => at @@ -116,7 +75,13 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi } for { callArrow <- CallArrowRawInliner( - CallArrowRaw(None, s"${varModel.name}.$arrowName", arguments, arrowType, None) + CallArrowRaw( + None, + AbilityType.fullName(varModel.name, arrowName), + arguments, + arrowType, + None + ) ) result <- callArrow match { case (vm: VarModel, inl) => @@ -129,21 +94,25 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi } yield { result } - + case IntoFieldRaw(fieldName, at @ AbilityType(abName, fields)) => + (VarModel(AbilityType.fullName(varModel.name, fieldName), at), Inline.empty).pure case IntoFieldRaw(fieldName, t) => for { - exports <- Exports[S].exports - fullName = s"${varModel.name}.$fieldName" - result <- exports.get(fullName) match { + abilityField <- Exports[S].getAbilityField(varModel.name, fieldName) + result <- abilityField match { case Some(vm: VarModel) => State.pure((vm, Inline.empty)) case Some(lm: LiteralModel) => flatLiteralWithProperties(lm, Inline.empty, Chain.empty) case _ => - logger.error( - s"Inlining, cannot find field $fullName in ability $varModel. Available: ${exports.keySet}" - ) - flatLiteralWithProperties(LiteralModel.quote(""), Inline.empty, Chain.empty) + Exports[S].getKeys.flatMap { keys => + logger.error( + s"Inlining, cannot find field ${AbilityType + .fullName(varModel.name, fieldName)} in ability $varModel. Available: $keys" + ) + flatLiteralWithProperties(LiteralModel.quote(""), Inline.empty, Chain.empty) + } + } } yield { result @@ -246,8 +215,8 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi State.pure((vm, prevInline.mergeWith(optimizationInline, SeqMode))) ) { case (state, property) => state.flatMap { - case (vm @ VarModel(name, st @ AbilityType(_, _), _), leftInline) => - unfoldAbilityProperty(vm, st, property.raw).map { case (vm, inl) => + case (vm @ Ability(name, at, _), leftInline) => + unfoldAbilityProperty(vm, at, property.raw).map { case (vm, inl) => ( vm, Inline( diff --git a/model/inline/src/main/scala/aqua/model/inline/raw/CallArrowRawInliner.scala b/model/inline/src/main/scala/aqua/model/inline/raw/CallArrowRawInliner.scala index da46b0d5..59e78691 100644 --- a/model/inline/src/main/scala/aqua/model/inline/raw/CallArrowRawInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/raw/CallArrowRawInliner.scala @@ -76,16 +76,12 @@ object CallArrowRawInliner extends RawInliner[CallArrowRaw] with Logging { ): State[S, (List[ValueModel], Inline)] = for { arrows <- Arrows[S].arrows exports <- Exports[S].exports + lastArrow <- Exports[S].getLast(funcName) arrow = arrows .get(funcName) .orElse( // if there is no arrow, check if it is stored in Exports as variable and try to resolve it - exports - .get(funcName) - .collect { case VarModel(name, _: ArrowType, _) => - name - } - .flatMap(arrows.get) + lastArrow.flatMap(arrows.get) ) result <- arrow.fold { logger.error( diff --git a/model/inline/src/main/scala/aqua/model/inline/raw/MakeAbilityRawInliner.scala b/model/inline/src/main/scala/aqua/model/inline/raw/MakeAbilityRawInliner.scala index affc1a38..5242beb4 100644 --- a/model/inline/src/main/scala/aqua/model/inline/raw/MakeAbilityRawInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/raw/MakeAbilityRawInliner.scala @@ -1,31 +1,33 @@ package aqua.model.inline.raw -import aqua.model.{ - CallModel, - CallServiceModel, - LiteralModel, - OpModel, - SeqModel, - ValueModel, - VarModel -} -import aqua.model.inline.raw.RawInliner -import cats.data.Chain -import aqua.model.inline.state.{Arrows, Exports, Mangler} -import aqua.raw.value.{AbilityRaw, LiteralRaw, MakeStructRaw} -import cats.data.{NonEmptyList, NonEmptyMap, State} import aqua.model.inline.Inline -import aqua.model.inline.RawValueInliner.{unfold, valueToModel} -import aqua.types.{ArrowType, ScalarType} -import cats.syntax.traverse.* -import cats.syntax.monoid.* -import cats.syntax.functor.* -import cats.syntax.flatMap.* -import cats.syntax.apply.* +import aqua.model.inline.RawValueInliner.unfold +import aqua.model.inline.state.{Arrows, Exports, Mangler} +import aqua.model.{SeqModel, ValueModel, VarModel} +import aqua.raw.value.AbilityRaw +import aqua.types.AbilityType +import aqua.model.ValueModel.Ability +import cats.Eval +import cats.data.{Chain, IndexedStateT, NonEmptyMap, State} import cats.syntax.foldable.* object MakeAbilityRawInliner extends RawInliner[AbilityRaw] { + private def updateFields[S: Mangler: Exports: Arrows]( + name: String, + fields: NonEmptyMap[String, (ValueModel, Inline)] + ): State[S, Unit] = { + for { + res <- fields.toNel.traverse { + case (n, (Ability(abilityName, _, _), _)) => + val leftName = AbilityType.fullName(name, n) + Exports[S].copyWithAbilityPrefix(abilityName, leftName) + case (n, (vm, _)) => + Exports[S].resolveAbilityField(name, n, vm) + } + } yield () + } + override def apply[S: Mangler: Exports: Arrows]( raw: AbilityRaw, propertiesAllowed: Boolean @@ -35,9 +37,7 @@ object MakeAbilityRawInliner extends RawInliner[AbilityRaw] { foldedFields <- raw.fieldsAndArrows.nonEmptyTraverse(unfold(_)) varModel = VarModel(name, raw.baseType) valsInline = foldedFields.toList.foldMap { case (_, inline) => inline }.desugar - _ <- foldedFields.toNel.traverse { case (n, (vm, _)) => - Exports[S].resolved(s"$name.$n", vm) - } + _ <- updateFields(name, foldedFields) } yield { ( varModel, 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 082ff04b..024498d8 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,9 +1,9 @@ package aqua.model.inline.state -import aqua.model.ValueModel -import aqua.raw.ops.Call -import aqua.raw.value.ValueRaw -import cats.data.State +import aqua.model.{ValueModel, VarModel} +import aqua.model.ValueModel.Ability +import aqua.types.AbilityType +import cats.data.{NonEmptyList, State} /** * Exports – trace values available in the scope @@ -22,6 +22,37 @@ trait Exports[S] extends Scoped[S] { */ def resolved(exportName: String, value: ValueModel): State[S, Unit] + /** + * [[value]] is accessible as [[abilityExportName]].[[fieldName]] + * + * @param abilityExportName + * Ability Name + * @param fieldName + * Field Name + * @param value + * Value + */ + def resolveAbilityField( + abilityExportName: String, + fieldName: String, + value: ValueModel + ): State[S, Unit] + + /** + * Rename ability prefix to new one + */ + def copyWithAbilityPrefix(prefix: String, newPrefix: String): State[S, Unit] + + /** + * Get name of last linked VarModel + */ + def getLast(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 @@ -29,6 +60,18 @@ trait Exports[S] extends Scoped[S] { */ def resolved(exports: Map[String, ValueModel]): State[S, Unit] + /** + * Get all export keys + */ + def getKeys: State[S, Set[String]] + + /** + * Get ability field from export + * @param name variable ability name + * @param field ability field + */ + def getAbilityField(name: String, field: String): State[S, Option[ValueModel]] + /** * Get all the values available in the scope */ @@ -45,6 +88,28 @@ trait Exports[S] extends Scoped[S] { override def resolved(exports: Map[String, ValueModel]): State[R, Unit] = self.resolved(exports).transformS(f, g) + override def resolveAbilityField( + abilityExportName: String, + fieldName: String, + value: ValueModel + ): State[R, Unit] = + self.resolveAbilityField(abilityExportName, fieldName, value).transformS(f, g) + + override def copyWithAbilityPrefix(prefix: String, newPrefix: String): State[R, Unit] = + self.copyWithAbilityPrefix(prefix, newPrefix).transformS(f, g) + + override def getLast(name: String): State[R, Option[String]] = + self.getLast(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]] = self.exports.transformS(f, g) @@ -59,18 +124,91 @@ trait Exports[S] extends Scoped[S] { object Exports { def apply[S](implicit exports: Exports[S]): Exports[S] = exports + // Get last linked VarModel + def getLastValue(name: String, state: Map[String, ValueModel]): Option[VarModel] = { + state.get(name) match { + case Some(vm@VarModel(n, _, _)) => + if (name == n) Option(vm) + else getLastValue(n, state).orElse(Option(vm)) + case n => + None + } + } + object Simple extends Exports[Map[String, ValueModel]] { - // Exports[Map[NonEmptyList[String], ValueModel]] + // Make links from one set of abilities to another (for ability assignment) + private def getAbilityPairs(oldName: String, newName: String, at: AbilityType, state: Map[String, ValueModel]): NonEmptyList[(String, VarModel)] = { + at.fields.toNel.flatMap { + case (n, at@AbilityType(_, _)) => + val newFullName = AbilityType.fullName(newName, n) + val oldFullName = AbilityType.fullName(oldName, n) + getAbilityPairs(oldFullName, newFullName, at, state) + case (n, t) => + val newFullName = AbilityType.fullName(newName, n) + val oldFullName = AbilityType.fullName(oldName, n) + // put link on last variable in chain + val lastVar = Exports.getLastValue(oldFullName, state) + NonEmptyList.of((newFullName, lastVar.getOrElse(VarModel(oldFullName, t)))) + } + } override def resolved( exportName: String, value: ValueModel - ): State[Map[String, ValueModel], Unit] = State.modify(_ + (exportName -> value)) + ): State[Map[String, ValueModel], Unit] = State.modify { state => + value match { + case vm@Ability(name, at, property) if property.isEmpty => + val pairs = getAbilityPairs(name, exportName, at, state) + state ++ pairs.toList.toMap + case _ => state + (exportName -> value) + } + } + + override def getLast(name: String): State[Map[String, ValueModel], Option[String]] = + State.get.map(st => getLastValue(name, st).map(_.name)) override def resolved(exports: Map[String, ValueModel]): State[Map[String, ValueModel], Unit] = State.modify(_ ++ exports) + override def resolveAbilityField( + abilityExportName: String, + fieldName: String, + value: ValueModel + ): State[Map[String, ValueModel], Unit] = + State.modify(_ + (AbilityType.fullName(abilityExportName, fieldName) -> value)) + + override def copyWithAbilityPrefix( + prefix: String, + newPrefix: String + ): State[Map[String, ValueModel], Unit] = + State.modify { state => + state.flatMap { + case (k, v) if k.startsWith(prefix) => + List(k.replaceFirst(prefix, newPrefix) -> v, k -> v) + case (k, v) => List(k -> v) + } + } + + 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 getAbilityField( + name: String, + field: String + ): State[Map[String, ValueModel], Option[ValueModel]] = + State.get.map(_.get(AbilityType.fullName(name, field))) + override val exports: State[Map[String, ValueModel], Map[String, ValueModel]] = State.get diff --git a/model/src/main/scala/aqua/model/ValueModel.scala b/model/src/main/scala/aqua/model/ValueModel.scala index b904853b..408eb742 100644 --- a/model/src/main/scala/aqua/model/ValueModel.scala +++ b/model/src/main/scala/aqua/model/ValueModel.scala @@ -51,6 +51,15 @@ object ValueModel { case _ => ??? } + object Ability { + + def unapply(vm: VarModel): Option[(String, AbilityType, Chain[PropertyModel])] = + vm match { + case VarModel(name, t@AbilityType(_, _), properties) => + (name, t, properties).some + case _ => none + } + } } case class LiteralModel(value: String, `type`: Type) extends ValueModel { diff --git a/parser/src/main/scala/aqua/parser/lexer/TypeToken.scala b/parser/src/main/scala/aqua/parser/lexer/TypeToken.scala index 6ea5cbfa..8cbea906 100644 --- a/parser/src/main/scala/aqua/parser/lexer/TypeToken.scala +++ b/parser/src/main/scala/aqua/parser/lexer/TypeToken.scala @@ -66,6 +66,7 @@ object OptionTypeToken { case class NamedTypeToken[F[_]: Comonad](name: F[String]) extends DataTypeToken[F] { override def as[T](v: T): F[T] = name.as(v) + def asName: Name[F] = Name[F](name) override def mapK[K[_]: Comonad](fk: F ~> K): NamedTypeToken[K] = copy(fk(name)) diff --git a/semantics/src/main/scala/aqua/semantics/expr/AbilitySem.scala b/semantics/src/main/scala/aqua/semantics/expr/AbilitySem.scala index d98d59e2..0de7df8b 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/AbilitySem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/AbilitySem.scala @@ -36,9 +36,9 @@ class AbilitySem[S[_]](val expr: AbilityExpr[S]) extends AnyVal { t ): Raw case false => - Raw.error("Scope types unresolved") + Raw.error("Ability types unresolved") } - case None => Raw.error("Scope types unresolved").pure[Alg] + case None => Raw.error("Ability types unresolved").pure[Alg] } ) } diff --git a/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala b/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala index bb2d7ef9..070b4434 100644 --- a/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala +++ b/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala @@ -300,6 +300,7 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit } // Generate CallArrowRaw for arrow in ability + // WARNING: arguments are resolved at the end of the function and added to CallArrowRaw def callAbType( ab: String, abType: AbilityType, @@ -307,7 +308,7 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit ): Alg[Option[CallArrowRaw]] = abType.arrows.get(ca.funcName.value) match { case Some(arrowType) => - Option(CallArrowRaw(None, s"$ab.${ca.funcName.value}", Nil, arrowType, None)).pure[Alg] + Option(CallArrowRaw(None, AbilityType.fullName(ab, ca.funcName.value), Nil, arrowType, None)).pure[Alg] case None => None.pure[Alg] } @@ -328,32 +329,38 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit ) ) )(ab => - // TODO: Hack. Check that we have registered ability type. - // If it exists - this is ability type in file, if not - imported ability - T.getType(ab.value).flatMap { - case Some(abType: AbilityType) => - callAbType(ab.value, abType, ca) + // Check that we have variable as ability + N.read(ab.asName, false).flatMap { + case Some(at@AbilityType(_, _)) => + callAbType(ab.value, at, ca) case _ => - (A.getArrow(ab, ca.funcName), A.getServiceId(ab)).mapN { - case (Some(at), Right(sid)) => - // Service call, actually - CallArrowRaw( - ability = Some(ab.value), - name = ca.funcName.value, - arguments = Nil, - baseType = at, - serviceId = Some(sid) - ).some - case (Some(at), Left(true)) => - // Ability function call, actually - CallArrowRaw( - ability = Some(ab.value), - name = ca.funcName.value, - arguments = Nil, - baseType = at, - serviceId = None - ).some - case _ => none + // Check that we have registered ability type. + // If it exists - this is ability type in file, if not - imported ability + T.getType(ab.value).flatMap { + case Some(abType: AbilityType) => + callAbType(ab.value, abType, ca) + case t => + (A.getArrow(ab, ca.funcName), A.getServiceId(ab)).mapN { + case (Some(at), Right(sid)) => + // Service call, actually + CallArrowRaw( + ability = Some(ab.value), + name = ca.funcName.value, + arguments = Nil, + baseType = at, + serviceId = Some(sid) + ).some + case (Some(at), Left(true)) => + // Ability function call, actually + CallArrowRaw( + ability = Some(ab.value), + name = ca.funcName.value, + arguments = Nil, + baseType = at, + serviceId = None + ).some + case _ => none + } } } ) diff --git a/types/src/main/scala/aqua/types/Type.scala b/types/src/main/scala/aqua/types/Type.scala index 4dfe58fd..9337c03c 100644 --- a/types/src/main/scala/aqua/types/Type.scala +++ b/types/src/main/scala/aqua/types/Type.scala @@ -267,7 +267,11 @@ case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends } override def toString: String = - s"scope $name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}" + s"ability $name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}" +} + +object AbilityType { + def fullName(name: String, field: String) = s"$name.$field" } /**