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
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
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)

View File

@ -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)

View File

@ -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]);

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]> {
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()
}

View File

@ -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)

View File

@ -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 <assignTo> should not exist yet
_ <- Mangler[S].forbidName(assignTo)
_ <- Exports[S].resolved(assignTo, model)
} yield TagInlined.Empty(prefix = prefix)
}
for {
// NOTE: Name <assignTo> 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())

View File

@ -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(

View File

@ -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(

View File

@ -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,

View File

@ -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

View File

@ -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 {

View File

@ -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))

View File

@ -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]
}
)
}

View File

@ -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
}
}
}
)

View File

@ -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"
}
/**