diff --git a/integration-tests/aqua/examples/stream.aqua b/integration-tests/aqua/examples/stream.aqua index 020df4a46..3f1dad084 100644 --- a/integration-tests/aqua/examples/stream.aqua +++ b/integration-tests/aqua/examples/stream.aqua @@ -1,6 +1,15 @@ +aqua Stream + import "@fluencelabs/aqua-lib/builtin.aqua" import "println.aqua" +export Stringer +export checkStreams, returnStreamFromFunc +export stringEmpty, returnEmptyLiteral +export returnNilLength, stringNone +export streamFunctor, streamAssignment +export streamIntFunctor, streamJoin + service Stringer("stringer-id"): returnString: string -> string @@ -20,16 +29,18 @@ func returnStreamFromFunc() -> *u32: nums <- getStream() <- nums -func stringNil() -> *string: +func stringEmpty() -> *string: valueNil: *string <- valueNil -func returnNil() -> *string: - relayNil <- stringNil() +func returnEmpty() -> *string: + relayNil <- stringEmpty() <- relayNil -func returnNilLiteral() -> *string: - <- nil +func returnEmptyLiteral() -> *string: + empty: *string + -- TODO: return *[] here after LNG-280 + <- empty func returnNilLength() -> u32: arr = nil diff --git a/integration-tests/src/examples/streamCall.ts b/integration-tests/src/examples/streamCall.ts index 3604330e5..1a3815987 100644 --- a/integration-tests/src/examples/streamCall.ts +++ b/integration-tests/src/examples/streamCall.ts @@ -1,14 +1,14 @@ import { - checkStreams, registerStringer, + checkStreams, returnNilLength, - returnNilLiteral, + returnEmptyLiteral, returnStreamFromFunc, streamAssignment, streamFunctor, streamIntFunctor, streamJoin, - stringNil, + stringEmpty, stringNone, } from "../compiled/examples/stream.js"; @@ -23,7 +23,7 @@ export async function streamCall() { } export async function returnNilCall() { - return stringNil(); + return stringEmpty(); } export async function returnNoneCall() { @@ -47,7 +47,7 @@ export async function streamAssignmentCall() { } export async function nilLiteralCall() { - return await returnNilLiteral(); + return await returnEmptyLiteral(); } export async function nilLengthCall() { diff --git a/model/inline/src/main/scala/aqua/model/inline/raw/ApplyPropertiesRawInliner.scala b/model/inline/src/main/scala/aqua/model/inline/raw/ApplyPropertiesRawInliner.scala index f683fe013..43df1e0fd 100644 --- a/model/inline/src/main/scala/aqua/model/inline/raw/ApplyPropertiesRawInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/raw/ApplyPropertiesRawInliner.scala @@ -17,8 +17,8 @@ import cats.syntax.applicative.* import cats.syntax.bifunctor.* import cats.syntax.foldable.* import cats.syntax.monoid.* -import cats.syntax.traverse.* import cats.syntax.option.* +import cats.syntax.traverse.* import scribe.Logging object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Logging { @@ -33,19 +33,15 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi apName <- Mangler[S].findAndForbidName("literal_ap") resultName <- Mangler[S].findAndForbidName(s"literal_props") } yield { - val cleanedType = literal.`type` match { - // literals cannot be streams, use it as an array to use properties - case StreamType(el) => ArrayType(el) - case tt => tt - } - val apVar = VarModel(apName, cleanedType, properties) + val typ = literal.`type` + val apVar = VarModel(apName, typ, properties) val tree = inl |+| Inline.tree( SeqModel.wrap( - FlattenModel(literal.copy(`type` = cleanedType), apVar.name).leaf, + FlattenModel(literal.copy(`type` = typ), apVar.name).leaf, FlattenModel(apVar, resultName).leaf ) ) - VarModel(resultName, properties.lastOption.map(_.`type`).getOrElse(cleanedType)) -> tree + VarModel(resultName, properties.lastOption.map(_.`type`).getOrElse(typ)) -> tree } } diff --git a/model/raw/src/main/scala/aqua/raw/value/ValueRaw.scala b/model/raw/src/main/scala/aqua/raw/value/ValueRaw.scala index cf200f502..285e0d605 100644 --- a/model/raw/src/main/scala/aqua/raw/value/ValueRaw.scala +++ b/model/raw/src/main/scala/aqua/raw/value/ValueRaw.scala @@ -35,7 +35,7 @@ object ValueRaw { val ParticleTtl: LiteralRaw = LiteralRaw("%ttl%", ScalarType.u32) val ParticleTimestamp: LiteralRaw = LiteralRaw("%timestamp%", ScalarType.u64) - val Nil: LiteralRaw = LiteralRaw("[]", StreamType(BottomType)) + val Nil: LiteralRaw = LiteralRaw("[]", OptionType(BottomType)) /** * Type of error value @@ -125,7 +125,7 @@ case class VarRaw(name: String, baseType: Type) extends ValueRaw { override def varNames: Set[String] = Set(name) } -case class LiteralRaw(value: String, baseType: Type) extends ValueRaw { +case class LiteralRaw(value: String, baseType: DataType) extends ValueRaw { override def mapValues(f: ValueRaw => ValueRaw): ValueRaw = this override def toString: String = s"{$value: ${baseType}}" diff --git a/model/src/main/scala/aqua/model/ValueModel.scala b/model/src/main/scala/aqua/model/ValueModel.scala index 74b0106c6..79a601ed1 100644 --- a/model/src/main/scala/aqua/model/ValueModel.scala +++ b/model/src/main/scala/aqua/model/ValueModel.scala @@ -6,8 +6,8 @@ import aqua.types.* import cats.Eq import cats.data.{Chain, NonEmptyMap} -import cats.syntax.option.* import cats.syntax.apply.* +import cats.syntax.option.* import scribe.Logging sealed trait ValueModel { @@ -75,7 +75,7 @@ object ValueModel { } } -case class LiteralModel(value: String, `type`: Type) extends ValueModel { +case class LiteralModel(value: String, `type`: DataType) extends ValueModel { override def toString: String = s"{$value: ${`type`}}" diff --git a/parser/src/main/scala/aqua/parser/lexer/Token.scala b/parser/src/main/scala/aqua/parser/lexer/Token.scala index 8c44347a1..82f1f841e 100644 --- a/parser/src/main/scala/aqua/parser/lexer/Token.scala +++ b/parser/src/main/scala/aqua/parser/lexer/Token.scala @@ -4,8 +4,8 @@ import aqua.parser.lift.Span.S import cats.data.NonEmptyList import cats.parse.{Accumulator0, Parser as P, Parser0 as P0} -import cats.{~>, Comonad, Functor} import cats.syntax.functor.* +import cats.{Comonad, Functor, ~>} trait Token[F[_]] { def as[T](v: T): F[T] diff --git a/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala b/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala index 8b92e3c2f..6d7b01339 100644 --- a/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala +++ b/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala @@ -16,6 +16,7 @@ import cats.data.Validated import cats.data.{Chain, EitherNec, NonEmptyChain} import cats.free.Cofree import cats.syntax.foldable.* +import cats.syntax.option.* import cats.syntax.show.* import cats.syntax.traverse.* import cats.~> @@ -49,11 +50,13 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside { inside(semantics.process(ast, init).value.run)(test) } - def insideBody(script: String)(test: RawTag.Tree => Any): Unit = + def insideBody(script: String, func: Option[String] = None)(test: RawTag.Tree => Any): Unit = insideResult(script) { case (_, Right(ctx)) => - inside(ctx.funcs.headOption) { case Some((_, func)) => - test(func.arrow.body) - } + inside( + func.fold( + ctx.funcs.headOption.map { case (_, raw) => raw } + )(ctx.funcs.get) + ) { case Some(func) => test(func.arrow.body) } } def insideSemErrors(script: String)(test: NonEmptyChain[SemanticError[Span.S]] => Any): Unit = @@ -877,7 +880,6 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside { |""".stripMargin insideBody(script) { body => - println(body.show) matchSubtree(body) { case (CallArrowRawTag(_, ca: CallArrowRaw), _) => inside(ca.arguments) { case (c: CollectionRaw) :: Nil => c.values.exists { @@ -892,4 +894,62 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside { test("[]", "") test("?", "?") } + + it should "allow `nil` in place of an array or an option" in { + def test(p: String) = { + val script = s""" + |func length(col: ${p}string) -> u32: + | <- col.length + | + |func return() -> ${p}string: + | <- nil + | + |func test() -> u32: + | l <- length(nil) + | n <- return() + | <- l + n.length + |""".stripMargin + + insideBody(script, "test".some) { body => + matchSubtree(body) { + case (CallArrowRawTag(_, ca: CallArrowRaw), _) if ca.name == "length" => + ca.arguments.length shouldEqual 1 + } + matchSubtree(body) { + case (CallArrowRawTag(_, ca: CallArrowRaw), _) if ca.name == "return" => + ca.arguments.length shouldEqual 0 + } + } + } + + test("[]") + test("?") + } + + it should "forbid `nil` in place of a stream" in { + val scriptAccept = s""" + |func length(col: *string) -> u32: + | <- col.length + | + |func test() -> u32: + | <- length(nil) + |""".stripMargin + + val scriptReturn = s""" + |func return() -> *string: + | <- nil + | + |func test() -> u32: + | n <- return() + | <- n.length + |""".stripMargin + + insideSemErrors(scriptAccept) { errors => + atLeast(1, errors.toChain.toList) shouldBe a[RulesViolated[Span.S]] + } + + insideSemErrors(scriptReturn) { errors => + atLeast(1, errors.toChain.toList) shouldBe a[RulesViolated[Span.S]] + } + } } diff --git a/semantics/src/test/scala/aqua/semantics/ValuesAlgebraSpec.scala b/semantics/src/test/scala/aqua/semantics/ValuesAlgebraSpec.scala index 107d6bd1b..eed6f6ffa 100644 --- a/semantics/src/test/scala/aqua/semantics/ValuesAlgebraSpec.scala +++ b/semantics/src/test/scala/aqua/semantics/ValuesAlgebraSpec.scala @@ -1,6 +1,7 @@ package aqua.semantics import aqua.parser.lexer.* +import aqua.raw.ConstantRaw import aqua.raw.RawContext import aqua.raw.value.* import aqua.semantics.rules.ValuesAlgebra @@ -14,7 +15,7 @@ import aqua.semantics.rules.types.{TypesAlgebra, TypesInterpreter} import aqua.types.* import cats.Id -import cats.data.{NonEmptyList, NonEmptyMap, State} +import cats.data.{Chain, NonEmptyList, NonEmptyMap, State} import monocle.syntax.all.* import org.scalatest.Inside import org.scalatest.flatspec.AnyFlatSpec @@ -66,9 +67,15 @@ class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside { b <- list } yield (a, b) - def genState(vars: Map[String, Type] = Map.empty) = + def genState(vars: Map[String, Type] = Map.empty) = { + val init = RawContext.blank.copy( + parts = Chain + .fromSeq(ConstantRaw.defaultConstants()) + .map(const => RawContext.blank -> const) + ) + CompilerState - .init[Id](RawContext.blank) + .init[Id](init) .focus(_.names) .modify( _.focus(_.stack).modify( @@ -78,6 +85,7 @@ class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside { ) :: _ ) ) + } def valueOfType(t: Type)( varName: String, @@ -572,4 +580,19 @@ class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside { atLeast(1, st.errors.toList) shouldBe a[RulesViolated[Id]] } } + + it should "consider `nil` of type `?⊥`" in { + val nil = variable("nil") + + val alg = algebra() + + val (st, res) = alg + .valueToRaw(nil) + .run(genState()) + .value + + inside(res) { case Some(value) => + value.`type` shouldBe OptionType(BottomType) + } + } }