feat(compiler)!: Make nil option bottom [LNG-279] (#968)

* Make nil option of bottom

* Fix tests

* Make literals of data type

* Add unit tests

* Remove print
This commit is contained in:
InversionSpaces 2023-11-14 12:02:58 +01:00 committed by GitHub
parent 522d95b8e3
commit 11c8970fd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 122 additions and 32 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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`}}"

View File

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

View File

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

View File

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