From cee444862a7cf2bea3f84e921b03328fd83fa108 Mon Sep 17 00:00:00 2001 From: InversionSpaces Date: Fri, 17 Nov 2023 11:43:13 +0100 Subject: [PATCH] feat(compiler): Generate empty calls to `responseHandlerSrv` [LNG-286] (#979) * Add noEmptyResponse * Fix tests --- api/api-npm/index.d.ts | 2 + api/api-npm/index.js | 2 + .../src/main/scala/api/types/InputTypes.scala | 9 ++-- .../main/scala/aqua/api/AquaAPIConfig.scala | 6 ++- .../aqua/compiler/AquaCompilerSpec.scala | 21 ++++----- .../aqua/model/transform/Transform.scala | 11 ++--- .../model/transform/TransformConfig.scala | 18 +++++++- .../model/transform/pre/ResultsHandler.scala | 11 +++-- .../aqua/model/transform/ModelBuilder.scala | 45 +++++++++++-------- .../aqua/model/transform/TransformSpec.scala | 8 ++-- 10 files changed, 85 insertions(+), 48 deletions(-) diff --git a/api/api-npm/index.d.ts b/api/api-npm/index.d.ts index 6c82b719..d24940a9 100644 --- a/api/api-npm/index.d.ts +++ b/api/api-npm/index.d.ts @@ -37,6 +37,8 @@ type CommonArgs = { targetType?: "ts" | "js" | "air" | undefined; /** Compile aqua in tracing mode (for debugging purposes). Default: false */ tracing?: boolean | undefined; + /** Do not generate response call if there are no returned values */ + noEmptyResponse?: boolean | undefined; }; type CodeString = { diff --git a/api/api-npm/index.js b/api/api-npm/index.js index b8f07788..bc7a7bcf 100644 --- a/api/api-npm/index.js +++ b/api/api-npm/index.js @@ -7,6 +7,7 @@ function getConfig({ noXor = false, targetType = "air", tracing = false, + noEmptyResponse = false, }) { return new AquaConfig( logLevel, @@ -19,6 +20,7 @@ function getConfig({ air: "air", }[targetType], tracing, + noEmptyResponse, ); } diff --git a/api/api/.js/src/main/scala/api/types/InputTypes.scala b/api/api/.js/src/main/scala/api/types/InputTypes.scala index f0ad685a..98ade0ee 100644 --- a/api/api/.js/src/main/scala/api/types/InputTypes.scala +++ b/api/api/.js/src/main/scala/api/types/InputTypes.scala @@ -4,9 +4,9 @@ import aqua.api.AquaAPIConfig import aqua.api.TargetType.* import aqua.js.{FunctionDefJs, ServiceDefJs} import aqua.model.transform.TransformConfig + import cats.data.Validated.{invalidNec, validNec} import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec} - import scala.scalajs.js import scala.scalajs.js.JSConverters.* import scala.scalajs.js.annotation.{JSExport, JSExportTopLevel} @@ -47,7 +47,9 @@ class AquaConfig( @JSExport val targetType: js.UndefOr[String], @JSExport - val tracing: js.UndefOr[Boolean] + val tracing: js.UndefOr[Boolean], + @JSExport + val noEmptyResponse: js.UndefOr[Boolean] ) object AquaConfig { @@ -69,7 +71,8 @@ object AquaConfig { constants = cjs.constants.map(_.toList).getOrElse(Nil), noXor = cjs.noXor.getOrElse(false), noRelay = cjs.noRelay.getOrElse(false), - tracing = cjs.tracing.getOrElse(false) + tracing = cjs.tracing.getOrElse(false), + noEmptyResponse = cjs.noEmptyResponse.getOrElse(false) ) } } diff --git a/api/api/src/main/scala/aqua/api/AquaAPIConfig.scala b/api/api/src/main/scala/aqua/api/AquaAPIConfig.scala index 18c949d2..ce0735a8 100644 --- a/api/api/src/main/scala/aqua/api/AquaAPIConfig.scala +++ b/api/api/src/main/scala/aqua/api/AquaAPIConfig.scala @@ -10,12 +10,14 @@ case class AquaAPIConfig( constants: List[String] = Nil, noXor: Boolean = false, // TODO: Remove noRelay: Boolean = false, - tracing: Boolean = false + tracing: Boolean = false, + noEmptyResponse: Boolean = false ) { def getTransformConfig: TransformConfig = { val config = TransformConfig( - tracing = Option.when(tracing)(TransformConfig.TracingConfig.default) + tracing = Option.when(tracing)(TransformConfig.TracingConfig.default), + noEmptyResponse = noEmptyResponse ) if (noRelay) config.copy(relayVarName = None) diff --git a/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala b/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala index 0f68680e..e43b5345 100644 --- a/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala +++ b/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala @@ -1,12 +1,15 @@ package aqua.compiler -import aqua.model.{CallModel, ForModel, FunctorModel, LiteralModel, ValueModel, VarModel} +import aqua.model.AquaContext +import aqua.model.CallServiceModel +import aqua.model.FlattenModel import aqua.model.transform.ModelBuilder -import aqua.model.transform.TransformConfig import aqua.model.transform.Transform -import aqua.parser.ParserError +import aqua.model.transform.TransformConfig +import aqua.model.{CallModel, ForModel, FunctorModel, LiteralModel, ValueModel, VarModel} import aqua.parser.Ast import aqua.parser.Parser +import aqua.parser.ParserError import aqua.parser.lift.Span import aqua.parser.lift.Span.S import aqua.raw.ConstantRaw @@ -18,15 +21,12 @@ import aqua.types.{ArrayType, CanonStreamType, LiteralType, ScalarType, StreamTy import cats.Id import cats.data.{Chain, NonEmptyChain, NonEmptyMap, Validated, ValidatedNec} import cats.instances.string.* -import cats.syntax.show.* -import cats.syntax.option.* import cats.syntax.either.* +import cats.syntax.option.* +import cats.syntax.show.* +import org.scalatest.Inside import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers -import org.scalatest.Inside -import aqua.model.AquaContext -import aqua.model.FlattenModel -import aqua.model.CallServiceModel class AquaCompilerSpec extends AnyFlatSpec with Matchers with Inside { import ModelBuilder.* @@ -358,7 +358,8 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers with Inside { join(VarModel(streamName, streamType), arg), decrement ) - ) + ), + emptyRespCall(transformCfg, initPeer) ), errorCall(transformCfg, 0, initPeer) ) diff --git a/model/transform/src/main/scala/aqua/model/transform/Transform.scala b/model/transform/src/main/scala/aqua/model/transform/Transform.scala index 1d57e32f..760be8cc 100644 --- a/model/transform/src/main/scala/aqua/model/transform/Transform.scala +++ b/model/transform/src/main/scala/aqua/model/transform/Transform.scala @@ -1,25 +1,25 @@ package aqua.model.transform +import aqua.model.* import aqua.model.inline.ArrowInliner import aqua.model.inline.state.InliningState +import aqua.model.transform.TransformConfig.TracingConfig import aqua.model.transform.funcop.* import aqua.model.transform.pre.* +import aqua.model.transform.pre.{CallbackErrorHandler, ErrorHandler} import aqua.model.transform.topology.Topology -import aqua.model.* import aqua.raw.ops.RawTag import aqua.raw.value.VarRaw import aqua.res.* import aqua.types.ScalarType -import aqua.model.transform.TransformConfig.TracingConfig -import aqua.model.transform.pre.{CallbackErrorHandler, ErrorHandler} import cats.Eval import cats.data.Chain import cats.free.Cofree +import cats.instances.list.* import cats.syntax.option.* import cats.syntax.show.* import cats.syntax.traverse.* -import cats.instances.list.* import scribe.Logging // API for transforming RawTag to Res @@ -90,7 +90,8 @@ object Transform extends Logging { val resultsHandler: ResultsHandler = CallbackResultsHandler( callbackSrvId = conf.callbackSrvId, - funcName = conf.respFuncName + funcName = conf.respFuncName, + noEmptyResponse = conf.noEmptyResponse ) val errorHandler: ErrorHandler = CallbackErrorHandler( diff --git a/model/transform/src/main/scala/aqua/model/transform/TransformConfig.scala b/model/transform/src/main/scala/aqua/model/transform/TransformConfig.scala index 63557bfc..313cdc94 100644 --- a/model/transform/src/main/scala/aqua/model/transform/TransformConfig.scala +++ b/model/transform/src/main/scala/aqua/model/transform/TransformConfig.scala @@ -1,19 +1,33 @@ package aqua.model.transform import aqua.model.{AquaContext, LiteralModel, ValueModel, VarModel} -import aqua.raw.{ConstantRaw, RawContext, RawPart} import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw} +import aqua.raw.{ConstantRaw, RawContext, RawPart} import aqua.types.ScalarType + import cats.data.Chain import cats.kernel.Monoid -// TODO docs +/** + * Configuration for function pre transformer + * + * @param getDataService - name of the service that provides arguments + * @param callbackService - name of the service that provides callbacks + * @param errorHandlingService - name of the service that handles errors + * @param errorFuncName - name of the function that handles errors (in errorHandlingService) + * @param respFuncName - name of the function that handles responses (in getDataService) + * @param noEmptyResponse - if true, do not generate response call if there is no return values + * @param relayVarName - name of the relay variable + * @param tracing - tracing configuration + * @param constants - list of constants + */ case class TransformConfig( getDataService: String = "getDataSrv", callbackService: String = "callbackSrv", errorHandlingService: String = "errorHandlingSrv", errorFuncName: String = "error", respFuncName: String = "response", + noEmptyResponse: Boolean = false, relayVarName: Option[String] = Some("-relay-"), tracing: Option[TransformConfig.TracingConfig] = None, constants: List[ConstantRaw] = Nil diff --git a/model/transform/src/main/scala/aqua/model/transform/pre/ResultsHandler.scala b/model/transform/src/main/scala/aqua/model/transform/pre/ResultsHandler.scala index 4345de0d..643cddf8 100644 --- a/model/transform/src/main/scala/aqua/model/transform/pre/ResultsHandler.scala +++ b/model/transform/src/main/scala/aqua/model/transform/pre/ResultsHandler.scala @@ -1,8 +1,8 @@ package aqua.model.transform.pre -import aqua.types.Type import aqua.raw.ops.{Call, CallArrowRawTag, RawTag} import aqua.raw.value.{ValueRaw, VarRaw} +import aqua.types.Type import cats.syntax.option.* @@ -10,11 +10,14 @@ trait ResultsHandler { def handleResults(results: List[(String, Type)]): Option[RawTag.Tree] } -case class CallbackResultsHandler(callbackSrvId: ValueRaw, funcName: String) - extends ResultsHandler { +case class CallbackResultsHandler( + callbackSrvId: ValueRaw, + funcName: String, + noEmptyResponse: Boolean +) extends ResultsHandler { override def handleResults(results: List[(String, Type)]): Option[RawTag.Tree] = - if (results.isEmpty) none + if (results.isEmpty && noEmptyResponse) none else { val resultVars = results.map(VarRaw.apply.tupled) val call = Call( diff --git a/model/transform/src/test/scala/aqua/model/transform/ModelBuilder.scala b/model/transform/src/test/scala/aqua/model/transform/ModelBuilder.scala index 885d49bf..e6fa26c6 100644 --- a/model/transform/src/test/scala/aqua/model/transform/ModelBuilder.scala +++ b/model/transform/src/test/scala/aqua/model/transform/ModelBuilder.scala @@ -1,22 +1,22 @@ package aqua.model.transform import aqua.model.* +import aqua.model.FailModel +import aqua.model.IntoIndexModel +import aqua.model.OnModel +import aqua.model.inline.raw.StreamGateInliner import aqua.raw.ops.Call import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw} -import aqua.{model, res} -import aqua.res.{CallRes, CallServiceRes, MakeRes} -import aqua.types.{ArrayType, LiteralType, ScalarType} -import aqua.types.StreamType -import aqua.model.IntoIndexModel -import aqua.model.inline.raw.StreamGateInliner -import aqua.model.OnModel -import aqua.model.FailModel import aqua.res.ResolvedOp +import aqua.res.{CallRes, CallServiceRes, MakeRes} +import aqua.types.StreamType +import aqua.types.{ArrayType, LiteralType, ScalarType} +import aqua.{model, res} -import scala.language.implicitConversions import cats.data.Chain import cats.data.Chain.==: import cats.syntax.option.* +import scala.language.implicitConversions object ModelBuilder { implicit def rawToValue(raw: ValueRaw): ValueModel = ValueModel.fromRaw(raw) @@ -88,15 +88,24 @@ object ModelBuilder { ) .leaf - def respCall(bc: TransformConfig, value: ValueModel, on: ValueModel = initPeer) = - res - .CallServiceRes( - ValueModel.fromRaw(bc.callbackSrvId), - bc.respFuncName, - CallRes(value :: Nil, None), - on - ) - .leaf + def respCallImpl( + config: TransformConfig, + arguments: List[ValueModel], + on: ValueModel = initPeer + ) = res + .CallServiceRes( + ValueModel.fromRaw(config.callbackSrvId), + config.respFuncName, + CallRes(arguments, None), + on + ) + .leaf + + def respCall(config: TransformConfig, value: ValueModel, on: ValueModel = initPeer) = + respCallImpl(config, value :: Nil, on) + + def emptyRespCall(config: TransformConfig, on: ValueModel = initPeer) = + respCallImpl(config, Nil) def dataCall(bc: TransformConfig, name: String, on: ValueModel = initPeer) = res diff --git a/model/transform/src/test/scala/aqua/model/transform/TransformSpec.scala b/model/transform/src/test/scala/aqua/model/transform/TransformSpec.scala index b0475e67..879104da 100644 --- a/model/transform/src/test/scala/aqua/model/transform/TransformSpec.scala +++ b/model/transform/src/test/scala/aqua/model/transform/TransformSpec.scala @@ -4,15 +4,15 @@ import aqua.model.transform.ModelBuilder import aqua.model.transform.{Transform, TransformConfig} import aqua.model.{CallModel, FuncArrow, LiteralModel, VarModel} import aqua.raw.ops.{Call, CallArrowRawTag, FuncOp, OnTag, RawTag, SeqTag} -import aqua.raw.value.{LiteralRaw, VarRaw} -import aqua.types.{ArrowType, NilType, ProductType, ScalarType} import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw} +import aqua.raw.value.{LiteralRaw, VarRaw} import aqua.res.{CallRes, CallServiceRes, MakeRes, SeqRes, XorRes} +import aqua.types.{ArrowType, NilType, ProductType, ScalarType} -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers import cats.data.Chain import cats.syntax.show.* +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers class TransformSpec extends AnyFlatSpec with Matchers {