feat(compiler): Add feature to insert map from a function result with correct type [LNG-367] (#1159)

This commit is contained in:
Dima 2024-07-01 15:58:29 +03:00 committed by GitHub
parent 4ad0655da6
commit 4d49279569
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 144 additions and 97 deletions

View File

@ -1,41 +1,14 @@
aqua StreamMapAbilities aqua StreamMapTest declares *
export streamMapAbilityTest export main
ability Streams: func foo() -> %string, u64:
stream: *string
map: %string map: %string
<- map, 42
ability Adds: func get() -> string, string:
addToStream(s: string) <- "123", "44"
addToMap(k: string, v: string)
func addToStreamClosure(str: *string) -> string -> (): func main() -> string:
cl = func (s: string): m <- foo()
str <<- s <- m.get("key")!
<- cl
func addToMapClosure(str: %string) -> string, string -> ():
cl = func (k: string, v: string):
str <<- k, v
<- cl
func addTo{Streams}() -> Adds:
addStream = addToStreamClosure(Streams.stream)
addMap = addToMapClosure(Streams.map)
adds = Adds(addToStream = addStream, addToMap = addMap)
<- adds
func add{Adds}(s: string, k: string):
Adds.addToStream(s)
Adds.addToMap(k, k)
func streamMapAbilityTest() -> []string, []string:
stream: *string
map: %string
ab = Streams(stream = stream, map = map)
adds <- addTo{ab}()
add{adds}("one", "1")
add{adds}("two", "2")
add{adds}("three", "3")
<- stream, map.keys()

View File

@ -30,9 +30,12 @@ func addToStreamClosure(str: *string) -> string -> ():
str <<- s str <<- s
<- cl <- cl
func toMap(k: string, v: string) -> string, string:
<- k, v
func addToMapClosure(str: %string) -> string, string -> (): func addToMapClosure(str: %string) -> string, string -> ():
cl = func (k: string, v: string): cl = func (k: string, v: string):
str <<- k, v str <- toMap(k, v)
<- cl <- cl
func addTo{Streams}() -> Adds: func addTo{Streams}() -> Adds:

View File

@ -1,7 +1,7 @@
aqua StreamMapTest declares * aqua StreamMapTest declares *
export testGetFunc, testGetStreamFunc, testKeysFunc, testKeysStreamFunc export testGetFunc, testGetStreamFunc, testKeysFunc, testKeysStreamFunc
export testContainsFunc, testForFunc, testParSeqMap, testForTupleFunc export testContainsFunc, testForFunc, testParSeqMap, testForTupleFunc, testInsertMapFromFunc
import "@fluencelabs/aqua-lib/builtin.aqua" import "@fluencelabs/aqua-lib/builtin.aqua"
@ -127,4 +127,18 @@ func testForTupleFunc() -> []string, []string, []string:
for k, v <- streamMap: for k, v <- streamMap:
streamThird <<- streamMap.get(k)! streamThird <<- streamMap.get(k)!
<- streamFirst, streamSecond, streamThird <- streamFirst, streamSecond, streamThird
func foo() -> string, u64:
<- "123", 42
func create() -> %u64:
map: %u64
map <- foo()
<- map
func testInsertMapFromFunc() -> []string:
map <- create()
map <- foo()
<- map.keys()

View File

@ -85,7 +85,8 @@ import {
testContainsFuncCall, testContainsFuncCall,
testForFuncCall, testForFuncCall,
testForTupleFuncCall, testForTupleFuncCall,
testParSeqMapCall testParSeqMapCall,
testInsertMapFromFuncCall
} from "../examples/streamMapCall.js"; } from "../examples/streamMapCall.js";
import { import {
topologyBug205Call, topologyBug205Call,
@ -965,6 +966,11 @@ describe("Testing examples", () => {
expect(res).toEqual("ok"); expect(res).toEqual("ok");
}); });
it("streamMap.aqua insert from func", async () => {
const res = await testInsertMapFromFuncCall();
expect(res).toEqual(["123", "123"]);
});
it("stream.aqua", async () => { it("stream.aqua", async () => {
const streamResult = await streamCall(); const streamResult = await streamCall();
expect(streamResult).toEqual([ expect(streamResult).toEqual([

View File

@ -16,7 +16,7 @@
import { import {
testGetFunc, testGetStreamFunc, testKeysFunc, testKeysStreamFunc, testContainsFunc, testGetFunc, testGetStreamFunc, testKeysFunc, testKeysStreamFunc, testContainsFunc,
testForFunc, testParSeqMap, testForTupleFunc testForFunc, testParSeqMap, testForTupleFunc, testInsertMapFromFunc
} from "../compiled/examples/streamMap.js"; } from "../compiled/examples/streamMap.js";
import { config } from "../config.js"; import { config } from "../config.js";
@ -54,3 +54,7 @@ export async function testParSeqMapCall() {
return testParSeqMap(relays[3].peerId, relays[4].peerId, relays[5].peerId) return testParSeqMap(relays[3].peerId, relays[4].peerId, relays[5].peerId)
} }
export async function testInsertMapFromFuncCall() {
return testInsertMapFromFunc()
}

View File

@ -70,22 +70,35 @@ object ArrowInliner extends Logging {
RawValueInliner RawValueInliner
.valueListToModel(results) .valueListToModel(results)
.map(resolvedResults => .map(resolvedResults =>
// Fix the return values (exportTo, resolvedResults) match {
(exportTo zip resolvedResults).map { // if export is a stream map and there is two results from function and first one is not a stream map
case ( // TODO: can it be better?
case ((cexp @ CallModel.Export(n, st@StreamMapType(_))) :: Nil, (res1, desugar1) :: (res2, desugar2) :: Nil) if !Type.isStreamMapType(res1.`type`) =>
(desugar1.toList ++ desugar2.toList :+ InsertKeyValueModel(res1, res2, n, st).leaf) -> (cexp.asVar :: Nil)
case _ =>
// Fix the return values
(exportTo zip resolvedResults).map {
case (
CallModel.Export(n, StreamType(_)), CallModel.Export(n, StreamType(_)),
(res @ VarModel(_, StreamType(_), _), resDesugar) (res @ VarModel(_, StreamType(_), _), resDesugar)
) if !outsideStreamNames.contains(n) => ) if !outsideStreamNames.contains(n) =>
resDesugar.toList -> res resDesugar.toList -> res
case ( case (
CallModel.Export(n, StreamMapType(_)),
(res @ VarModel(_, StreamMapType(_), _), resDesugar)
) if !outsideStreamNames.contains(n) =>
resDesugar.toList -> res
case (
cexp @ CallModel.Export(_, StreamType(_)), cexp @ CallModel.Export(_, StreamType(_)),
(res, resDesugar) (res, resDesugar)
) => ) =>
// pass nested function results to a stream // pass nested function results to a stream
(resDesugar.toList :+ PushToStreamModel(res, cexp).leaf) -> cexp.asVar (resDesugar.toList :+ PushToStreamModel(res, cexp).leaf) -> cexp.asVar
case (_, (res, resDesugar)) => case (_, (res, resDesugar)) =>
resDesugar.toList -> res resDesugar.toList -> res
}.unzip.leftMap(_.flatten) }.unzip.leftMap(_.flatten)
}
) )
/** /**

View File

@ -377,14 +377,10 @@ object TagInliner extends Logging {
} }
} yield model.fold(TagInlined.Empty())(m => TagInlined.Single(model = m)) } yield model.fold(TagInlined.Empty())(m => TagInlined.Single(model = m))
case DeclareStreamTag(value) => case DeclareStreamTag(name, t) =>
value match for {
case VarRaw(name, t: MutableStreamType) => _ <- Exports[S].resolved(name, VarModel(name, t))
for { } yield TagInlined.Empty()
_ <- Exports[S].resolved(name, VarModel(name, t))
} yield TagInlined.Empty()
case _ =>
internalError(s"Cannot declare $value as stream, because it is not a stream type")
case ServiceIdTag(id, serviceType, name) => case ServiceIdTag(id, serviceType, name) =>
for { for {

View File

@ -151,7 +151,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside {
FuncArrow( FuncArrow(
"stream-callback", "stream-callback",
SeqTag.wrap( SeqTag.wrap(
DeclareStreamTag(streamVar).leaf, DeclareStreamTag(streamName, streamType).leaf,
CallArrowRawTag.func("cb", Call(streamVar :: Nil, Nil)).leaf CallArrowRawTag.func("cb", Call(streamVar :: Nil, Nil)).leaf
), ),
ArrowType( ArrowType(
@ -247,7 +247,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside {
FuncArrow( FuncArrow(
"call", "call",
SeqTag.wrap( SeqTag.wrap(
DeclareStreamTag(streamVar).leaf, DeclareStreamTag(streamName, streamType).leaf,
CallArrowRawTag.func(useArrow.funcName, Call(streamVar :: Nil, Nil)).leaf CallArrowRawTag.func(useArrow.funcName, Call(streamVar :: Nil, Nil)).leaf
), ),
ArrowType( ArrowType(
@ -308,7 +308,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside {
val returnNil = FuncArrow( val returnNil = FuncArrow(
"returnNil", "returnNil",
SeqTag.wrap( SeqTag.wrap(
DeclareStreamTag(someStr).leaf, DeclareStreamTag(someStr.name, streamType).leaf,
ReturnTag( ReturnTag(
NonEmptyList.one(someStr) NonEmptyList.one(someStr)
).leaf ).leaf
@ -409,7 +409,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside {
val returnStream = FuncArrow( val returnStream = FuncArrow(
"returnStream", "returnStream",
SeqTag.wrap( SeqTag.wrap(
DeclareStreamTag(streamVar).leaf, DeclareStreamTag(streamVar.name, streamType).leaf,
PushToStreamTag( PushToStreamTag(
LiteralRaw.quote("one"), LiteralRaw.quote("one"),
Call.Export(streamVar.name, streamVar.`type`) Call.Export(streamVar.name, streamVar.`type`)
@ -595,7 +595,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside {
val returnFunc = FuncArrow( val returnFunc = FuncArrow(
"return", "return",
SeqTag.wrap( SeqTag.wrap(
DeclareStreamTag(streamVar).leaf, DeclareStreamTag(streamVar.name, streamType).leaf,
PushToStreamTag( PushToStreamTag(
LiteralRaw.quote("one"), LiteralRaw.quote("one"),
Call.Export(streamVar.name, streamVar.`type`) Call.Export(streamVar.name, streamVar.`type`)
@ -893,7 +893,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside {
FuncArrow( FuncArrow(
"stream-callback", "stream-callback",
SeqTag.wrap( SeqTag.wrap(
DeclareStreamTag(streamVar).leaf, DeclareStreamTag(streamVar.name, streamType).leaf,
CallArrowRawTag.func("cb", Call(streamVarLambda :: Nil, Nil)).leaf CallArrowRawTag.func("cb", Call(streamVarLambda :: Nil, Nil)).leaf
), ),
ArrowType( ArrowType(
@ -976,7 +976,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside {
FuncArrow( FuncArrow(
"outer", "outer",
SeqTag.wrap( SeqTag.wrap(
DeclareStreamTag(recordsVar).leaf, DeclareStreamTag(recordsVar.name, streamType).leaf,
CallArrowRawTag.func(innerName, Call(recordsVar :: Nil, Nil)).leaf, CallArrowRawTag.func(innerName, Call(recordsVar :: Nil, Nil)).leaf,
CallArrowRawTag CallArrowRawTag
.service( .service(
@ -2311,7 +2311,8 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside {
it should "generate result in right order" in { it should "generate result in right order" in {
val innerName = "inner" val innerName = "inner"
val results = VarRaw("results", ScalarType.string) val results = VarRaw("results", ScalarType.string)
val resultsOut = VarRaw("results", StreamType(ScalarType.string)) val streamType = StreamType(ScalarType.string)
val resultsOut = VarRaw("results", streamType)
val inner = FuncArrow( val inner = FuncArrow(
innerName, innerName,
@ -2339,7 +2340,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside {
val outer = FuncArrow( val outer = FuncArrow(
"outer", "outer",
SeqTag.wrap( SeqTag.wrap(
DeclareStreamTag(resultsOut).leaf, DeclareStreamTag(resultsOut.name, streamType).leaf,
CallArrowRawTag CallArrowRawTag
.func(innerName, Call(Nil, Call.Export(resultsOut.name, resultsOut.baseType) :: Nil)) .func(innerName, Call(Nil, Call.Export(resultsOut.name, resultsOut.baseType) :: Nil))
.leaf .leaf

View File

@ -16,6 +16,7 @@
package aqua.raw.ops package aqua.raw.ops
import aqua.errors.Errors.internalError
import aqua.raw.arrow.FuncRaw import aqua.raw.arrow.FuncRaw
import aqua.raw.value.{CallArrowRaw, CallServiceRaw, ValueRaw, VarRaw} import aqua.raw.value.{CallArrowRaw, CallServiceRaw, ValueRaw, VarRaw}
import aqua.tree.{TreeNode, TreeNodeCompanion} import aqua.tree.{TreeNode, TreeNodeCompanion}
@ -178,10 +179,16 @@ case class NextTag(item: String) extends RawTag {
case class ForKeyValue(key: String, value: String) { case class ForKeyValue(key: String, value: String) {
def toSet: Set[String] = Set(key, value) def toSet: Set[String] = Set(key, value)
def rename(map: Map[String, String]): ForKeyValue = copy(key = map.getOrElse(key, key), value = map.getOrElse(value, value)) def rename(map: Map[String, String]): ForKeyValue =
copy(key = map.getOrElse(key, key), value = map.getOrElse(value, value))
} }
case class ForTag(item: String, iterable: ValueRaw, mode: ForTag.Mode, keyValue: Option[ForKeyValue] = None) extends SeqGroupTag { case class ForTag(
item: String,
iterable: ValueRaw,
mode: ForTag.Mode,
keyValue: Option[ForKeyValue] = None
) extends SeqGroupTag {
override def restrictsVarNames: Set[String] = Set(item) ++ keyValue.toSet.flatMap(_.toSet) override def restrictsVarNames: Set[String] = Set(item) ++ keyValue.toSet.flatMap(_.toSet)
@ -283,15 +290,18 @@ object CallArrowRawTag {
} }
case class DeclareStreamTag( case class DeclareStreamTag(
// TODO: Why is it ValueRaw and name: String,
// not just (stream name, stream type)? `type`: MutableStreamType
value: ValueRaw
) extends RawTag { ) extends RawTag {
override def exportsVarNames: Set[String] = value.varNames override def exportsVarNames: Set[String] = Set(name)
override def mapValues(f: ValueRaw => ValueRaw): RawTag = override def mapValues(f: ValueRaw => ValueRaw): RawTag =
DeclareStreamTag(value.map(f)) f(VarRaw(name, `type`)) match {
case VarRaw(name, t: MutableStreamType) => copy(name, t)
case v =>
internalError(s"DeclareStreamTag can be only VarRaw with stream type, currently: '$v' ")
}
} }
case class AssignmentTag( case class AssignmentTag(

View File

@ -23,7 +23,7 @@ import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.names.NamesAlgebra import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.{ProductType, StreamType, Type} import aqua.types.{ProductType, StreamMapType, StreamType, Type}
import cats.Monad import cats.Monad
import cats.syntax.apply.* import cats.syntax.apply.*
@ -39,13 +39,20 @@ class CallArrowSem[S[_]](val expr: CallArrowExpr[S]) extends AnyVal {
N: NamesAlgebra[S, Alg], N: NamesAlgebra[S, Alg],
T: TypesAlgebra[S, Alg] T: TypesAlgebra[S, Alg]
): Alg[List[Call.Export]] = ): Alg[List[Call.Export]] =
(variables zip codomain.toList).traverse { case (v, t) => variables.traverse(v => N.read(v, mustBeDefined = false).map(v -> _)).flatMap {
N.read(v, mustBeDefined = false).flatMap { case (v, Some(map @ StreamMapType(_))) :: Nil =>
case Some(stream @ StreamType(st)) => T.ensureTypeMatches(v, map.elementProduct, codomain)
T.ensureTypeMatches(v, st, t).as(Call.Export(v.value, stream, isExistingStream = true)) .as(Call.Export(v.value, map, isExistingStream = true) :: Nil)
case _ => case vars =>
N.define(v, t).as(Call.Export(v.value, t)) (vars zip codomain.toList).traverse { case ((v, vType), t) =>
} vType match {
case Some(stream @ StreamType(st)) =>
T.ensureTypeMatches(v, st, t)
.as(Call.Export(v.value, stream, isExistingStream = true))
case _ =>
N.define(v, t).as(Call.Export(v.value, t))
}
}
} }
private def toModel[Alg[_]: Monad](using private def toModel[Alg[_]: Monad](using
@ -56,8 +63,9 @@ class CallArrowSem[S[_]](val expr: CallArrowExpr[S]) extends AnyVal {
// TODO: Accept other expressions // TODO: Accept other expressions
callArrowRaw <- V.valueToCall(expr.callArrow) callArrowRaw <- V.valueToCall(expr.callArrow)
tag <- callArrowRaw.traverse { case (raw, at) => tag <- callArrowRaw.traverse { case (raw, at) =>
getExports(at.codomain).map(CallArrowRawTag(_, raw)) <* getExports(at.codomain).flatMap(exports =>
T.checkArrowCallResults(callArrow, at, variables) T.checkArrowCallResults(callArrow, at, variables, exports).as(CallArrowRawTag(exports, raw))
)
} }
} yield tag.map(_.funcOpLeaf) } yield tag.map(_.funcOpLeaf)

View File

@ -41,8 +41,7 @@ class DeclareStreamSem[S[_]](val expr: DeclareStreamExpr[S]) {
_ <- OptionT.withFilterF( _ <- OptionT.withFilterF(
N.define(expr.name, streamType) N.define(expr.name, streamType)
) )
valueModel = VarRaw(expr.name.value, streamType) } yield DeclareStreamTag(expr.name.value, streamType).funcOpLeaf: Raw
} yield DeclareStreamTag(valueModel).funcOpLeaf: Raw
sem.getOrElse(Raw.error(s"Name `${expr.name.value}` not defined")) sem.getOrElse(Raw.error(s"Name `${expr.name.value}` not defined"))
} }

View File

@ -17,13 +17,12 @@
package aqua.semantics.rules.types package aqua.semantics.rules.types
import aqua.parser.lexer.* import aqua.parser.lexer.*
import aqua.raw.ops.Call
import aqua.raw.value.{PropertyRaw, ValueRaw} import aqua.raw.value.{PropertyRaw, ValueRaw}
import aqua.types.* import aqua.types.*
import aqua.types.Type.* import aqua.types.Type.*
import cats.data.NonEmptyList import cats.data.{NonEmptyList, NonEmptyMap, OptionT}
import cats.data.NonEmptyMap
import cats.data.OptionT
trait TypesAlgebra[S[_], Alg[_]] { trait TypesAlgebra[S[_], Alg[_]] {
@ -35,7 +34,10 @@ trait TypesAlgebra[S[_], Alg[_]] {
def resolveArrowDef(arrowDef: ArrowTypeToken[S]): Alg[Option[ArrowType]] def resolveArrowDef(arrowDef: ArrowTypeToken[S]): Alg[Option[ArrowType]]
def resolveServiceType(name: NamedTypeToken[S], mustBeDefined: Boolean = true): Alg[Option[ServiceType]] def resolveServiceType(
name: NamedTypeToken[S],
mustBeDefined: Boolean = true
): Alg[Option[ServiceType]]
def defineAbilityType( def defineAbilityType(
name: NamedTypeToken[S], name: NamedTypeToken[S],
@ -159,7 +161,8 @@ trait TypesAlgebra[S[_], Alg[_]] {
def checkArrowCallResults( def checkArrowCallResults(
token: Token[S], token: Token[S],
arrowType: ArrowType, arrowType: ArrowType,
results: List[Name[S]] results: List[Name[S]],
exports: List[Call.Export]
): Alg[Unit] ): Alg[Unit]
def checkArgumentsNumber(token: Token[S], expected: Int, givenNum: Int): Alg[Boolean] def checkArgumentsNumber(token: Token[S], expected: Int, givenNum: Int): Alg[Boolean]

View File

@ -18,6 +18,7 @@ package aqua.semantics.rules.types
import aqua.errors.Errors.internalError import aqua.errors.Errors.internalError
import aqua.parser.lexer.* import aqua.parser.lexer.*
import aqua.raw.ops.Call
import aqua.raw.value.* import aqua.raw.value.*
import aqua.semantics.Levenshtein import aqua.semantics.Levenshtein
import aqua.semantics.rules.StackInterpreter import aqua.semantics.rules.StackInterpreter
@ -611,7 +612,8 @@ class TypesInterpreter[S[_], X](using
override def checkArrowCallResults( override def checkArrowCallResults(
token: Token[S], token: Token[S],
arrowType: ArrowType, arrowType: ArrowType,
results: List[Name[S]] results: List[Name[S]],
exports: List[Call.Export]
): State[X, Unit] = for { ): State[X, Unit] = for {
_ <- results _ <- results
.drop(arrowType.codomain.length) .drop(arrowType.codomain.length)
@ -636,9 +638,22 @@ class TypesInterpreter[S[_], X](using
case i => s"only $i are" case i => s"only $i are"
}} used" }} used"
) )
.whenA(arrowType.codomain.length > results.length) .whenA(checkNumberOfResults(arrowType, results, exports))
} yield () } yield ()
private def checkNumberOfResults(
at: ArrowType,
results: List[Name[S]],
exports: List[Call.Export]
): Boolean = {
val checkLength = at.codomain.length > results.length
val isOneStreamMapInExport =
exports.headOption.exists(e => isStreamMapType(e.`type`)) && exports.length == 1
val twoResultsToStreamMap = isOneStreamMapInExport && at.codomain.length == 2
!twoResultsToStreamMap && checkLength
}
override def checkArgumentsNumber( override def checkArgumentsNumber(
token: Token[S], token: Token[S],
expected: Int, expected: Int,

View File

@ -158,7 +158,7 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
val stream = VarRaw(name, streamType) val stream = VarRaw(name, streamType)
SeqTag.wrap( SeqTag.wrap(
DeclareStreamTag(stream).leaf, DeclareStreamTag(stream.name, streamType).leaf,
PushToStreamTag( PushToStreamTag(
LiteralRaw.quote(value), LiteralRaw.quote(value),
Call.Export(name, streamType) Call.Export(name, streamType)

View File

@ -414,6 +414,8 @@ case class StreamMapType(override val element: DataType) extends MutableStreamTy
StructType(name, NonEmptyMap.of("key" -> ScalarType.string, "value" -> element)) StructType(name, NonEmptyMap.of("key" -> ScalarType.string, "value" -> element))
def toCanon: ImmutableCollectionType = CanonStreamMapType(element) def toCanon: ImmutableCollectionType = CanonStreamMapType(element)
def elementProduct: ProductType = ProductType(ScalarType.string :: element :: Nil)
} }
object StreamMapType { object StreamMapType {