From ddb758cee0b4a5e87a7648cac9d16b2bbc637a00 Mon Sep 17 00:00:00 2001 From: Dima Date: Thu, 1 Jun 2023 17:54:19 +0200 Subject: [PATCH] fix(inline): Nullable value in a nested struct [LNG-160] (#724) Co-authored-by: InversionSpaces --- .../inline/raw/CollectionRawInliner.scala | 24 ++++--- .../inline/CollectionRawInlinerSpec.scala | 68 +++++++++++++++++++ 2 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 model/inline/src/test/scala/aqua/model/inline/CollectionRawInlinerSpec.scala diff --git a/model/inline/src/main/scala/aqua/model/inline/raw/CollectionRawInliner.scala b/model/inline/src/main/scala/aqua/model/inline/raw/CollectionRawInliner.scala index b415ec2c..d5aaa047 100644 --- a/model/inline/src/main/scala/aqua/model/inline/raw/CollectionRawInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/raw/CollectionRawInliner.scala @@ -41,13 +41,20 @@ object CollectionRawInliner extends RawInliner[CollectionRaw] { stream = VarModel(streamName, StreamType(raw.elementType)) streamExp = CallModel.Export(stream.name, stream.`type`) - vals <- raw.values + valsWithInlines <- raw.values .traverse(valueToModel(_)) .map(_.toList) .map(Chain.fromSeq) - .map(_.flatMap { case (v, t) => - Chain.fromOption(t) :+ PushToStreamModel(v, streamExp).leaf - }) + + // push values to the stream, that is gathering the collection + vals = valsWithInlines.map { case (v, _) => + PushToStreamModel(v, streamExp).leaf + } + + // all inlines will be added before pushing values to the stream + inlines = valsWithInlines.flatMap { case (_, t) => + Chain.fromOption(t) + } canonName <- if (raw.boxType.isStream) State.pure(streamName) @@ -61,17 +68,18 @@ object CollectionRawInliner extends RawInliner[CollectionRaw] { raw.boxType match { case ArrayType(_) => RestrictionModel(streamName, isStream = true).wrap( - SeqModel.wrap((vals :+ CanonicalizeModel(stream, canon).leaf).toList: _*) + SeqModel.wrap((inlines ++ vals :+ CanonicalizeModel(stream, canon).leaf).toList: _*) ) case OptionType(_) => RestrictionModel(streamName, isStream = true).wrap( SeqModel.wrap( - XorModel.wrap((vals :+ NullModel.leaf).toList: _*), - CanonicalizeModel(stream, canon).leaf + SeqModel.wrap(inlines.toList:_*), + XorModel.wrap((vals :+ NullModel.leaf).toList: _*), + CanonicalizeModel(stream, canon).leaf ) ) case _ => - SeqModel.wrap(vals.toList: _*) + SeqModel.wrap((inlines ++ vals).toList: _*) } ) } diff --git a/model/inline/src/test/scala/aqua/model/inline/CollectionRawInlinerSpec.scala b/model/inline/src/test/scala/aqua/model/inline/CollectionRawInlinerSpec.scala new file mode 100644 index 00000000..2619fa66 --- /dev/null +++ b/model/inline/src/test/scala/aqua/model/inline/CollectionRawInlinerSpec.scala @@ -0,0 +1,68 @@ +package aqua.model.inline + +import aqua.model.* +import aqua.model.inline.raw.{ApplyIntoCopyRawInliner, CollectionRawInliner} +import aqua.model.inline.state.InliningState +import aqua.raw.ops.* +import aqua.raw.value.{CollectionRaw, LiteralRaw, MakeStructRaw, VarRaw} +import aqua.types.{CanonStreamType, OptionType, ScalarType, StreamType, StructType} +import cats.data.{NonEmptyList, NonEmptyMap} +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class CollectionRawInlinerSpec extends AnyFlatSpec with Matchers { + + "collection inliner" should "unfold struct with nested options (bug LNG-160)" in { + + val nestedType = StructType( + "nested_type", + NonEmptyMap.of("field1" -> ScalarType.u32) + ) + + val makeStruct = + MakeStructRaw(NonEmptyMap.of("field1" -> LiteralRaw.number(3)), nestedType) + + val raw = CollectionRaw(NonEmptyList.of(makeStruct), OptionType(nestedType)) + + val (v, tree) = + RawValueInliner.valueToModel[InliningState](raw, false).run(InliningState()).value._2 + + val resultValue = VarModel("option-inline-0", CanonStreamType(nestedType)) + + v shouldBe resultValue + + tree.get.equalsOrShowDiff( + // create a stream + RestrictionModel("option-inline", true).wrap( + SeqModel.wrap( + // create an object + CallServiceModel( + "json", + "obj", + LiteralModel.fromRaw(LiteralRaw.quote("field1")) :: LiteralModel.fromRaw( + LiteralRaw.number(3) + ) :: Nil, + VarModel("nested_type_obj", nestedType) + ).leaf, + XorModel.wrap( + SeqModel.wrap( + // push object to the stream + PushToStreamModel( + VarModel("nested_type_obj", nestedType), + CallModel.Export("option-inline", StreamType(nestedType)) + ).leaf + ), + NullModel.leaf + ), + // canonicalize the stream to use it after inlining + CanonicalizeModel( + VarModel("option-inline", StreamType(nestedType)), + CallModel.Export(resultValue.name, resultValue.baseType) + ).leaf + ) + ) + ) shouldBe true + + } + +}