fix(compiler): Nested abilities [fixes LNG-214] (#816)

This commit is contained in:
Dima 2023-07-31 14:40:06 +03:00 committed by GitHub
parent dba12b8277
commit 4e3e70f4fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 555 additions and 255 deletions

View File

@ -1,11 +1,87 @@
aqua Main 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: service SomeService("wed"):
<- cl getStr(s: string) -> string
func b(c: u32, d: u32) -> u32: ability SomeAb:
<- c + d 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)

View File

@ -2,7 +2,7 @@ aqua Main
use DECLARE_CONST, decl_bar from "imports_exports/declare.aqua" as Declare use DECLARE_CONST, decl_bar from "imports_exports/declare.aqua" as Declare
export handleAb, SomeService export handleAb, SomeService, bug214, checkAbCalls
service SomeService("wed"): service SomeService("wed"):
getStr(s: string) -> string getStr(s: string) -> string
@ -35,3 +35,53 @@ func handleAb(fff: string) -> string, string, string, u32:
SecondAb = SecondAb(arrow = funcStr, num = 12) SecondAb = SecondAb(arrow = funcStr, num = 12)
res1, res2, res3, res4 <- handleSecAb{SomeAb, SecondAb}() res1, res2, res3, res4 <- handleSecAb{SomeAb, SecondAb}()
<- res1, res2, res3, res4 <- 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)

View File

@ -13,6 +13,7 @@ import { bugNG69Call, ifCall, ifWrapCall } from '../examples/ifCall.js';
import { parCall, testTimeoutCall } from '../examples/parCall.js'; import { parCall, testTimeoutCall } from '../examples/parCall.js';
import { complexCall } from '../examples/complex.js'; import { complexCall } from '../examples/complex.js';
import { constantsCall, particleTtlAndTimestampCall } from '../examples/constantsCall.js'; import { constantsCall, particleTtlAndTimestampCall } from '../examples/constantsCall.js';
import { abilityCall, complexAbilityCall, checkAbCallsCall } from '../examples/abilityCall.js';
import { import {
nilLengthCall, nilLengthCall,
nilLiteralCall, nilLiteralCall,
@ -77,7 +78,6 @@ export const relay2 = config.relays[1];
const relayPeerId2 = relay2.peerId; const relayPeerId2 = relay2.peerId;
import log from 'loglevel'; import log from 'loglevel';
import { abilityCall } from '../examples/abilityCall';
// log.setDefaultLevel("debug") // log.setDefaultLevel("debug")
async function start() { async function start() {
@ -354,6 +354,16 @@ describe('Testing examples', () => {
expect(result).toStrictEqual(['declare_const123', 'efre123', 'declare_const123', 12]); 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 () => { it('functors.aqua LNG-119 bug', async () => {
let result = await bugLng119Call(); let result = await bugLng119Call();
expect(result).toEqual([1]); expect(result).toEqual([1]);

View File

@ -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]> { export async function abilityCall(): Promise<[string, string, string, number]> {
registerSomeService({ registerSomeService({
@ -9,3 +9,11 @@ export async function abilityCall(): Promise<[string, string, string, number]> {
return await handleAb("some_string") 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()
}

View File

@ -6,7 +6,7 @@ import aqua.model.*
import aqua.raw.ops.RawTag import aqua.raw.ops.RawTag
import aqua.types.{AbilityType, ArrowType, BoxType, DataType, StreamType, Type} import aqua.types.{AbilityType, ArrowType, BoxType, DataType, StreamType, Type}
import aqua.raw.value.{ValueRaw, VarRaw} import aqua.raw.value.{ValueRaw, VarRaw}
import cats.Eval import cats.{Eval, Monoid}
import cats.data.{Chain, IndexedStateT, State} import cats.data.{Chain, IndexedStateT, State}
import cats.syntax.traverse.* import cats.syntax.traverse.*
import cats.syntax.apply.* 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 // Apply a callable function, get its fully resolved body & optional value, if any
private def inline[S: Mangler: Arrows: Exports]( private def inline[S: Mangler: Arrows: Exports](
fn: FuncArrow, fn: FuncArrow,
call: CallModel call: CallModel,
outsideDeclaredStreams: Set[String]
): State[S, InlineResult] = ): State[S, InlineResult] =
(Exports[S].exports, getOutsideStreamNames).flatMapN { for {
case (oldExports, outsideDeclaredStreams) => // Register captured values as available exports
// Function's internal variables will not be available outside, hence the scope _ <- Exports[S].resolved(fn.capturedValues)
Exports[S].scope( _ <- Mangler[S].forbid(fn.capturedValues.keySet)
for {
// Process renamings, prepare environment
tr <- prelude[S](fn, call, oldExports)
(tree, results) = tr
// Register captured values as available exports // Now, substitute the arrows that were received as function arguments
_ <- Exports[S].resolved(fn.capturedValues) // Use the new op tree (args are replaced with values, names are unique & safe)
_ <- Mangler[S].forbid(fn.capturedValues.keySet) 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 opsAndRets <- pushStreamResults(
// Use the new op tree (args are replaced with values, names are unique & safe) outsideDeclaredStreams,
callableFuncBodyNoTopology <- TagInliner.handleTree(tree, fn.funcName) call.exportTo,
callableFuncBody = fn.ret,
fn.capturedTopology callableFuncBody
.fold[OpModel](SeqModel)(ApplyTopologyModel.apply) )
.wrap(callableFuncBodyNoTopology) (ops, rets) = opsAndRets
opsAndRets <- pushStreamResults( exports <- Exports[S].exports
outsideDeclaredStreams, arrows <- Arrows[S].arrows
call.exportTo, // gather all arrows and variables from abilities
results, returnedFromAbilities = rets.collect { case VarModel(name, st @ AbilityType(_, _), _) =>
callableFuncBody getVarsAndArrowsFromAbilities(name, None, st, exports, arrows)
) }.foldMapA(_.bimap(_.toList, _.toList)).bimap(_.toMap, _.toMap)
(ops, rets) = opsAndRets
exports <- Exports[S].exports // find and get resolved arrows if we return them from the function
arrows <- Arrows[S].arrows returnedArrows = rets.collect { case VarModel(name, ArrowType(_, _), _) =>
// gather all arrows and variables from abilities name
returnedFromAbilities = rets.collect { case VarModel(name, st @ AbilityType(_, _), _) => }.toSet
getVarsAndArrowsFromAbilities(name, None, st, exports, arrows) arrowsToSave <- Arrows[S].pickArrows(returnedArrows)
}.foldMapA(_.bimap(_.toList, _.toList)).bimap(_.toMap, _.toMap) } yield {
val (valsFromAbilities, arrowsFromAbilities) = returnedFromAbilities
// find and get resolved arrows if we return them from the function InlineResult(
returnedArrows = rets.collect { case VarModel(name, ArrowType(_, _), _) => SeqModel.wrap(ops.reverse: _*),
name rets.reverse,
}.toSet valsFromAbilities,
arrowsToSave <- Arrows[S].pickArrows(returnedArrows) arrowsFromAbilities ++ arrowsToSave
} 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] 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. * Generate new names for all ability fields and arrows if necessary.
* Gather all fields and arrows from Arrows and Exports states * Gather all fields and arrows from Arrows and Exports states
* @param name ability name in state * @param name ability name in state
* @param vm ability variable * @param vm ability variable
* @param t ability type * @param t ability type
* @param oldExports previous Exports * @param exports previous Exports
* @param oldArrows previous Arrows * @param arrows previous Arrows
* @return names to rename, Exports and Arrows with all ability fields and arrows * @return names to rename, Exports and Arrows with all ability fields and arrows
*/ */
private def renameAndResolveAbilities[S: Mangler: Arrows: Exports]( private def renameAndResolveAbilities[S: Mangler: Arrows: Exports](
name: String, name: String,
vm: VarModel, vm: VarModel,
t: AbilityType, t: AbilityType,
oldExports: Map[String, ValueModel], exports: Map[String, ValueModel],
oldArrows: Map[String, FuncArrow] arrows: Map[String, FuncArrow]
): State[S, AbilityResolvingResult] = { ): State[S, AbilityResolvingResult] = {
for { for {
newName <- Mangler[S].findNewName(name) newName <- Mangler[S].findNewName(name)
newFieldsName = t.fields.mapBoth { case (n, t) => 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 allNewNames = newFieldsName.add((name, newName)).toSortedMap
} yield { } yield {
val (allVars, allArrows) = val (allVars, allArrows) =
getVarsAndArrowsFromAbilities(vm.name, Option(newName), t, oldExports, oldArrows) getVarsAndArrowsFromAbilities(vm.name, Option(newName), t, exports, arrows)
AbilityResolvingResult(allNewNames, allVars, allArrows) AbilityResolvingResult(allNewNames, allVars, allArrows)
} }
} }
@ -281,58 +287,50 @@ object ArrowInliner extends Logging {
* @param topOldName old name to find all fields in states * @param topOldName old name to find all fields in states
* @param topNewName new name to rename all fields in states * @param topNewName new name to rename all fields in states
* @param abilityType type of current ability * @param abilityType type of current ability
* @param oldExports where to get values * @param exports where to get values
* @param oldArrows where to get arrows * @param arrows where to get arrows
* @param valAcc accumulator for values
* @param arrowsAcc accumulator for arrows
* @return * @return
*/ */
private def getVarsAndArrowsFromAbilities( private def getVarsAndArrowsFromAbilities(
topOldName: String, topOldName: String,
topNewName: Option[String], topNewName: Option[String],
abilityType: AbilityType, abilityType: AbilityType,
oldExports: Map[String, ValueModel], exports: Map[String, ValueModel],
oldArrows: Map[String, FuncArrow], arrows: Map[String, FuncArrow]
valAcc: Map[String, ValueModel] = Map.empty,
arrowsAcc: Map[String, FuncArrow] = Map.empty
): (Map[String, ValueModel], Map[String, FuncArrow]) = { ): (Map[String, ValueModel], Map[String, FuncArrow]) = {
abilityType.fields.toSortedMap.toList.map { case (fName, fValue) => 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 // 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 { fValue match {
case nestedAbilityType @ AbilityType(_, _) => case nestedAbilityType @ AbilityType(_, _) =>
getVarsAndArrowsFromAbilities( getVarsAndArrowsFromAbilities(
currentOldName, currentOldName,
currentNewName, currentNewName,
nestedAbilityType, nestedAbilityType,
oldExports, exports,
oldArrows, arrows
valAcc,
arrowsAcc
) )
case ArrowType(_, _) => case ArrowType(_, _) =>
oldExports Exports
.get(currentOldName) .getLastValue(currentOldName, exports)
.flatMap { .flatMap { case vm @ VarModel(name, _, _) =>
case vm @ VarModel(name, _, _) => arrows
oldArrows .get(name)
.get(name) .map(fa =>
.map(fa => (
( Map(currentNewName.getOrElse(currentOldName) -> vm),
valAcc.updated(currentNewName.getOrElse(currentOldName), vm), Map(name -> fa)
arrowsAcc.updated(name, fa)
)
) )
case _ => None )
} }
.getOrElse((valAcc, arrowsAcc)) .getOrElse((Map.empty, Map.empty))
case _ => case _ =>
oldExports Exports
.get(currentOldName) .getLastValue(currentOldName, exports)
.map(vm => (valAcc.updated(currentNewName.getOrElse(currentOldName), vm), arrowsAcc)) .map(vm => (Map(currentNewName.getOrElse(currentOldName) -> vm), Map.empty))
.getOrElse((valAcc, arrowsAcc)) .getOrElse((Map.empty, Map.empty))
} }
}.foldMapA(_.bimap(_.toList, _.toList)).bimap(_.toMap, _.toMap) }.foldMapA(_.bimap(_.toList, _.toList)).bimap(_.toMap, _.toMap)
} }
@ -352,7 +350,8 @@ object ArrowInliner extends Logging {
private def prelude[S: Mangler: Arrows: Exports]( private def prelude[S: Mangler: Arrows: Exports](
fn: FuncArrow, fn: FuncArrow,
call: CallModel, call: CallModel,
oldExports: Map[String, ValueModel] oldExports: Map[String, ValueModel],
arrows: Map[String, FuncArrow]
): State[S, (RawTag.Tree, List[ValueRaw])] = ): State[S, (RawTag.Tree, List[ValueRaw])] =
for { for {
// Collect all arguments: what names are used inside the function, what values are received // 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 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)) => 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)(_ ++ _) absRenames = abilityResolvingResult.namesToRename
absVars = abilityResolvingResult.map(_.renamedExports).fold(Map.empty)(_ ++ _) absVars = abilityResolvingResult.renamedExports
absArrows = abilityResolvingResult.map(_.renamedArrows).fold(Map.empty)(_ ++ _) absArrows = abilityResolvingResult.renamedArrows
arrowArgs = args.arrowArgs(previousArrowsState) arrowArgs = args.arrowArgs(arrows)
// Update states and rename tags // Update states and rename tags
renamedArrows <- updateArrowsAndRenameArrowArgs(arrowArgs ++ absArrows, fn, absRenames) renamedArrows <- updateArrowsAndRenameArrowArgs(arrowArgs ++ absArrows, fn, absRenames)
argsToDataShouldRename <- updateExportsAndRenameDataArgs(args.dataArgs ++ absVars, 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 // Rename all renamed arguments in the body
treeRenamed = fn.body.rename(allShouldRename) treeRenamed = fn.body.rename(allShouldRename)
treeStreamsRenamed = renameStreams(treeRenamed, args.streamArgs) treeStreamsRenamed = renameStreams(treeRenamed, args.streamArgs)
@ -415,8 +437,7 @@ object ArrowInliner extends Logging {
} yield { } yield {
sc.fields.toSortedMap.toList.flatMap { sc.fields.toSortedMap.toList.flatMap {
case (n, ArrowType(_, _)) => case (n, ArrowType(_, _)) =>
val fullName = s"$name.$n" exports.get(AbilityType.fullName(name, n)).flatMap {
exports.get(fullName).flatMap {
case VarModel(n, _, _) => arrows.get(n).map(n -> _) case VarModel(n, _, _) => arrows.get(n).map(n -> _)
case _ => None case _ => None
} }
@ -435,11 +456,22 @@ object ArrowInliner extends Logging {
.traverse(getAllArrowsFromAbility) .traverse(getAllArrowsFromAbility)
.map(_.fold(Map.empty)(_ ++ _)) .map(_.fold(Map.empty)(_ ++ _))
inlineResult <- Arrows[S].scope( exports <- Exports[S].exports
for { streams <- getOutsideStreamNames
_ <- Arrows[S].resolved(passArrows ++ arrowsFromAbilities)
inlineResult <- ArrowInliner.inline(arrow, call) inlineResult <- Exports[S].scope(
} yield inlineResult 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) _ <- Arrows[S].resolved(inlineResult.arrowsToSave)

View File

@ -329,19 +329,19 @@ object TagInliner extends Logging {
} }
case AssignmentTag(value, assignTo) => case AssignmentTag(value, assignTo) =>
(value match { for {
// if we assign collection to a stream, we must use it's name, because it is already created with 'new' // NOTE: Name <assignTo> should not exist yet
case c @ CollectionRaw(_, _: StreamType) => _ <- Mangler[S].forbidName(assignTo)
collectionToModel(c, Some(assignTo)) modelAndPrefix <- value match {
case v => // if we assign collection to a stream, we must use it's name, because it is already created with 'new'
valueToModel(v, false) case c @ CollectionRaw(_, _: StreamType) =>
}).flatMap { case (model, prefix) => collectionToModel(c, Some(assignTo))
for { case v =>
// NOTE: Name <assignTo> should not exist yet valueToModel(v, false)
_ <- Mangler[S].forbidName(assignTo) }
_ <- Exports[S].resolved(assignTo, model) (model, prefix) = modelAndPrefix
} yield TagInlined.Empty(prefix = prefix) _ <- Exports[S].resolved(assignTo, model)
} } yield TagInlined.Empty(prefix = prefix)
case ClosureTag(arrow, detach) => case ClosureTag(arrow, detach) =>
if (detach) Arrows[S].resolved(arrow, None).as(TagInlined.Empty()) if (detach) Arrows[S].resolved(arrow, None).as(TagInlined.Empty())

View File

@ -1,62 +1,21 @@
package aqua.model.inline.raw package aqua.model.inline.raw
import aqua.model.{ import aqua.model.*
CallModel, import aqua.model.ValueModel.Ability
CallServiceModel,
FlattenModel,
ForModel,
FunctorModel,
IntoFieldModel,
IntoIndexModel,
LiteralModel,
MatchMismatchModel,
NextModel,
OpModel,
PropertyModel,
PushToStreamModel,
SeqModel,
ValueModel,
VarModel,
XorModel
}
import aqua.model.inline.Inline import aqua.model.inline.Inline
import aqua.model.inline.Inline.MergeMode.* import aqua.model.inline.Inline.MergeMode.*
import aqua.model.inline.RawValueInliner.unfold import aqua.model.inline.RawValueInliner.unfold
import aqua.model.inline.state.{Arrows, Exports, Mangler} import aqua.model.inline.state.{Arrows, Exports, Mangler}
import aqua.raw.value.{ import aqua.raw.value.*
ApplyGateRaw, import aqua.types.*
ApplyPropertyRaw,
CallArrowRaw,
FunctorRaw,
IntoArrowRaw,
IntoCopyRaw,
IntoFieldRaw,
IntoIndexRaw,
LiteralRaw,
PropertyRaw,
ValueRaw,
VarRaw
}
import aqua.types.{
AbilityType,
ArrayType,
ArrowType,
BottomType,
CanonStreamType,
NilType,
ScalarType,
StreamType,
Type
}
import cats.Eval import cats.Eval
import cats.syntax.bifunctor.*
import cats.data.{Chain, IndexedStateT, State} 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.monoid.*
import cats.syntax.traverse.* import cats.syntax.traverse.*
import cats.syntax.foldable.*
import cats.Id
import cats.instances.list.*
import scribe.Logging import scribe.Logging
object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with 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]( private def unfoldAbilityProperty[S: Mangler: Exports: Arrows](
varModel: VarModel, varModel: VarModel,
scopeType: AbilityType, abilityType: AbilityType,
p: PropertyRaw p: PropertyRaw
): State[S, (VarModel, Inline)] = { ): State[S, (VarModel, Inline)] = {
p match { p match {
case IntoArrowRaw(arrowName, t, arguments) => case IntoArrowRaw(arrowName, t, arguments) =>
val arrowType = scopeType.fields val arrowType = abilityType.fields
.lookup(arrowName) .lookup(arrowName)
.collect { case at @ ArrowType(_, _) => .collect { case at @ ArrowType(_, _) =>
at at
@ -116,7 +75,13 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
} }
for { for {
callArrow <- CallArrowRawInliner( callArrow <- CallArrowRawInliner(
CallArrowRaw(None, s"${varModel.name}.$arrowName", arguments, arrowType, None) CallArrowRaw(
None,
AbilityType.fullName(varModel.name, arrowName),
arguments,
arrowType,
None
)
) )
result <- callArrow match { result <- callArrow match {
case (vm: VarModel, inl) => case (vm: VarModel, inl) =>
@ -129,21 +94,25 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
} yield { } yield {
result result
} }
case IntoFieldRaw(fieldName, at @ AbilityType(abName, fields)) =>
(VarModel(AbilityType.fullName(varModel.name, fieldName), at), Inline.empty).pure
case IntoFieldRaw(fieldName, t) => case IntoFieldRaw(fieldName, t) =>
for { for {
exports <- Exports[S].exports abilityField <- Exports[S].getAbilityField(varModel.name, fieldName)
fullName = s"${varModel.name}.$fieldName" result <- abilityField match {
result <- exports.get(fullName) match {
case Some(vm: VarModel) => case Some(vm: VarModel) =>
State.pure((vm, Inline.empty)) State.pure((vm, Inline.empty))
case Some(lm: LiteralModel) => case Some(lm: LiteralModel) =>
flatLiteralWithProperties(lm, Inline.empty, Chain.empty) flatLiteralWithProperties(lm, Inline.empty, Chain.empty)
case _ => case _ =>
logger.error( Exports[S].getKeys.flatMap { keys =>
s"Inlining, cannot find field $fullName in ability $varModel. Available: ${exports.keySet}" logger.error(
) s"Inlining, cannot find field ${AbilityType
flatLiteralWithProperties(LiteralModel.quote(""), Inline.empty, Chain.empty) .fullName(varModel.name, fieldName)} in ability $varModel. Available: $keys"
)
flatLiteralWithProperties(LiteralModel.quote(""), Inline.empty, Chain.empty)
}
} }
} yield { } yield {
result result
@ -246,8 +215,8 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
State.pure((vm, prevInline.mergeWith(optimizationInline, SeqMode))) State.pure((vm, prevInline.mergeWith(optimizationInline, SeqMode)))
) { case (state, property) => ) { case (state, property) =>
state.flatMap { state.flatMap {
case (vm @ VarModel(name, st @ AbilityType(_, _), _), leftInline) => case (vm @ Ability(name, at, _), leftInline) =>
unfoldAbilityProperty(vm, st, property.raw).map { case (vm, inl) => unfoldAbilityProperty(vm, at, property.raw).map { case (vm, inl) =>
( (
vm, vm,
Inline( Inline(

View File

@ -76,16 +76,12 @@ object CallArrowRawInliner extends RawInliner[CallArrowRaw] with Logging {
): State[S, (List[ValueModel], Inline)] = for { ): State[S, (List[ValueModel], Inline)] = for {
arrows <- Arrows[S].arrows arrows <- Arrows[S].arrows
exports <- Exports[S].exports exports <- Exports[S].exports
lastArrow <- Exports[S].getLast(funcName)
arrow = arrows arrow = arrows
.get(funcName) .get(funcName)
.orElse( .orElse(
// if there is no arrow, check if it is stored in Exports as variable and try to resolve it // if there is no arrow, check if it is stored in Exports as variable and try to resolve it
exports lastArrow.flatMap(arrows.get)
.get(funcName)
.collect { case VarModel(name, _: ArrowType, _) =>
name
}
.flatMap(arrows.get)
) )
result <- arrow.fold { result <- arrow.fold {
logger.error( logger.error(

View File

@ -1,31 +1,33 @@
package aqua.model.inline.raw 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.Inline
import aqua.model.inline.RawValueInliner.{unfold, valueToModel} import aqua.model.inline.RawValueInliner.unfold
import aqua.types.{ArrowType, ScalarType} import aqua.model.inline.state.{Arrows, Exports, Mangler}
import cats.syntax.traverse.* import aqua.model.{SeqModel, ValueModel, VarModel}
import cats.syntax.monoid.* import aqua.raw.value.AbilityRaw
import cats.syntax.functor.* import aqua.types.AbilityType
import cats.syntax.flatMap.* import aqua.model.ValueModel.Ability
import cats.syntax.apply.* import cats.Eval
import cats.data.{Chain, IndexedStateT, NonEmptyMap, State}
import cats.syntax.foldable.* import cats.syntax.foldable.*
object MakeAbilityRawInliner extends RawInliner[AbilityRaw] { 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]( override def apply[S: Mangler: Exports: Arrows](
raw: AbilityRaw, raw: AbilityRaw,
propertiesAllowed: Boolean propertiesAllowed: Boolean
@ -35,9 +37,7 @@ object MakeAbilityRawInliner extends RawInliner[AbilityRaw] {
foldedFields <- raw.fieldsAndArrows.nonEmptyTraverse(unfold(_)) foldedFields <- raw.fieldsAndArrows.nonEmptyTraverse(unfold(_))
varModel = VarModel(name, raw.baseType) varModel = VarModel(name, raw.baseType)
valsInline = foldedFields.toList.foldMap { case (_, inline) => inline }.desugar valsInline = foldedFields.toList.foldMap { case (_, inline) => inline }.desugar
_ <- foldedFields.toNel.traverse { case (n, (vm, _)) => _ <- updateFields(name, foldedFields)
Exports[S].resolved(s"$name.$n", vm)
}
} yield { } yield {
( (
varModel, varModel,

View File

@ -1,9 +1,9 @@
package aqua.model.inline.state package aqua.model.inline.state
import aqua.model.ValueModel import aqua.model.{ValueModel, VarModel}
import aqua.raw.ops.Call import aqua.model.ValueModel.Ability
import aqua.raw.value.ValueRaw import aqua.types.AbilityType
import cats.data.State import cats.data.{NonEmptyList, State}
/** /**
* Exports trace values available in the scope * 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] 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 * Resolve the whole map of exports
* @param exports * @param exports
@ -29,6 +60,18 @@ trait Exports[S] extends Scoped[S] {
*/ */
def resolved(exports: Map[String, ValueModel]): State[S, Unit] 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 * 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] = override def resolved(exports: Map[String, ValueModel]): State[R, Unit] =
self.resolved(exports).transformS(f, g) 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]] = override val exports: State[R, Map[String, ValueModel]] =
self.exports.transformS(f, g) self.exports.transformS(f, g)
@ -59,18 +124,91 @@ trait Exports[S] extends Scoped[S] {
object Exports { object Exports {
def apply[S](implicit exports: Exports[S]): Exports[S] = 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]] { 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( override def resolved(
exportName: String, exportName: String,
value: ValueModel 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] = override def resolved(exports: Map[String, ValueModel]): State[Map[String, ValueModel], Unit] =
State.modify(_ ++ exports) 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]] = override val exports: State[Map[String, ValueModel], Map[String, ValueModel]] =
State.get State.get

View File

@ -51,6 +51,15 @@ object ValueModel {
case _ => ??? 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 { case class LiteralModel(value: String, `type`: Type) extends ValueModel {

View File

@ -66,6 +66,7 @@ object OptionTypeToken {
case class NamedTypeToken[F[_]: Comonad](name: F[String]) extends DataTypeToken[F] { case class NamedTypeToken[F[_]: Comonad](name: F[String]) extends DataTypeToken[F] {
override def as[T](v: T): F[T] = name.as(v) 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)) override def mapK[K[_]: Comonad](fk: F ~> K): NamedTypeToken[K] = copy(fk(name))

View File

@ -36,9 +36,9 @@ class AbilitySem[S[_]](val expr: AbilityExpr[S]) extends AnyVal {
t t
): Raw ): Raw
case false => 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]
} }
) )
} }

View File

@ -300,6 +300,7 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit
} }
// Generate CallArrowRaw for arrow in ability // Generate CallArrowRaw for arrow in ability
// WARNING: arguments are resolved at the end of the function and added to CallArrowRaw
def callAbType( def callAbType(
ab: String, ab: String,
abType: AbilityType, abType: AbilityType,
@ -307,7 +308,7 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit
): Alg[Option[CallArrowRaw]] = ): Alg[Option[CallArrowRaw]] =
abType.arrows.get(ca.funcName.value) match { abType.arrows.get(ca.funcName.value) match {
case Some(arrowType) => 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] case None => None.pure[Alg]
} }
@ -328,32 +329,38 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit
) )
) )
)(ab => )(ab =>
// TODO: Hack. Check that we have registered ability type. // Check that we have variable as ability
// If it exists - this is ability type in file, if not - imported ability N.read(ab.asName, false).flatMap {
T.getType(ab.value).flatMap { case Some(at@AbilityType(_, _)) =>
case Some(abType: AbilityType) => callAbType(ab.value, at, ca)
callAbType(ab.value, abType, ca)
case _ => case _ =>
(A.getArrow(ab, ca.funcName), A.getServiceId(ab)).mapN { // Check that we have registered ability type.
case (Some(at), Right(sid)) => // If it exists - this is ability type in file, if not - imported ability
// Service call, actually T.getType(ab.value).flatMap {
CallArrowRaw( case Some(abType: AbilityType) =>
ability = Some(ab.value), callAbType(ab.value, abType, ca)
name = ca.funcName.value, case t =>
arguments = Nil, (A.getArrow(ab, ca.funcName), A.getServiceId(ab)).mapN {
baseType = at, case (Some(at), Right(sid)) =>
serviceId = Some(sid) // Service call, actually
).some CallArrowRaw(
case (Some(at), Left(true)) => ability = Some(ab.value),
// Ability function call, actually name = ca.funcName.value,
CallArrowRaw( arguments = Nil,
ability = Some(ab.value), baseType = at,
name = ca.funcName.value, serviceId = Some(sid)
arguments = Nil, ).some
baseType = at, case (Some(at), Left(true)) =>
serviceId = None // Ability function call, actually
).some CallArrowRaw(
case _ => none ability = Some(ab.value),
name = ca.funcName.value,
arguments = Nil,
baseType = at,
serviceId = None
).some
case _ => none
}
} }
} }
) )

View File

@ -267,7 +267,11 @@ case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends
} }
override def toString: String = 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"
} }
/** /**