feat(compiler): Structural typing for data and abilities [fixes LNG-215] (#843)

This commit is contained in:
Dima 2023-08-18 15:15:20 +02:00 committed by GitHub
parent eb4cdb0dd1
commit 019611a89c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 135 additions and 45 deletions

View File

@ -1,7 +1,35 @@
service Srv("srv"):
call(x: i32) -> i32
aqua Aaa
func main() -> i32:
arr = [1, 2, 3]
a <- Srv.call(0)
<- arr[Srv.call(1)]
import "builtin.aqua"
export structuralTypingTest
data WideData:
s: string
n: u32
data ExactData:
s: string
ability ExactAbility:
s: string
arr(s: string, s2: string, s3: string, s4: string) -> string
exact: ExactData
ability WideAbility:
s: string
arr(s: string, s2: string, s3: string, s4: string) -> string
g: string
exact: WideData
func ss(s1: string, s2: string, s3: string, s4: string) -> string:
<- Op.concat_strings(Op.concat_strings(Op.concat_strings(s1, s2), s3), s4)
func main{ExactAbility}(someData: ExactData, secondData: ExactData) -> string:
<- ExactAbility.arr(someData.s, ExactAbility.exact.s, secondData.s, ExactAbility.s)
func structuralTypingTest() -> string:
wd = WideData(s = "some_string", n = 32)
WAbility = WideAbility(s = "ab_string", g = "", arr = ss, exact = wd)
<- main{WAbility}(wd, WAbility.exact)

View File

@ -0,0 +1,35 @@
aqua Aaa
import "@fluencelabs/aqua-lib/builtin.aqua"
export structuralTypingTest
data WideData:
s: string
n: u32
data ExactData:
s: string
ability ExactAbility:
s: string
arr(s: string, s2: string, s3: string, s4: string) -> string
exact: ExactData
ability WideAbility:
s: string
arr(s: string, s2: string, s3: string, s4: string) -> string
g: string
exact: WideData
func ss(s1: string, s2: string, s3: string, s4: string) -> string:
<- Op.concat_strings(Op.concat_strings(Op.concat_strings(s1, s2), s3), s4)
func main{ExactAbility}(someData: ExactData, secondData: ExactData) -> string:
<- ExactAbility.arr(someData.s, ExactAbility.exact.s, secondData.s, ExactAbility.s)
func structuralTypingTest() -> string:
wd = WideData(s = "some_string", n = 32)
WAbility = WideAbility(s = "ab_string", g = "", arr = ss, exact = wd)
<- main{WAbility}(wd, WAbility.exact)

View File

@ -80,6 +80,7 @@ export const relay2 = config.relays[1];
const relayPeerId2 = relay2.peerId;
import log from 'loglevel';
import {structuralTypingCall} from "../examples/structuralTypingCall";
// log.setDefaultLevel("debug")
async function start() {
@ -244,6 +245,11 @@ describe('Testing examples', () => {
expect(result).toEqual([]);
});
it('structuraltyping.aqua', async () => {
let result = await structuralTypingCall();
expect(result).toEqual("some_stringsome_stringsome_stringab_string");
});
it('collectionSugar array', async () => {
let result = await arraySugarCall();
expect(result).toEqual([

View File

@ -0,0 +1,5 @@
import {structuralTypingTest} from "../compiled/examples/structuraltyping";
export async function structuralTypingCall(): Promise<string> {
return await structuralTypingTest();
}

View File

@ -1,16 +1,16 @@
package aqua.model.inline
import aqua.model
import aqua.model.inline.state.{Arrows, Exports, Mangler}
import aqua.model.*
import aqua.model.inline.state.{Arrows, Exports, Mangler}
import aqua.raw.ops.RawTag
import aqua.types.{AbilityType, ArrowType, BoxType, StreamType}
import aqua.raw.value.{ValueRaw, VarRaw}
import cats.{Eval, Monoid}
import aqua.types.{AbilityType, ArrowType, BoxType, StreamType}
import cats.data.{Chain, IndexedStateT, State}
import cats.syntax.traverse.*
import cats.syntax.bifunctor.*
import cats.syntax.foldable.*
import cats.syntax.traverse.*
import cats.{Eval, Monoid}
import scribe.Logging
/**
@ -267,7 +267,7 @@ object ArrowInliner extends Logging {
): State[S, AbilityResolvingResult] = {
for {
newName <- Mangler[S].findNewName(name)
newFieldsName = t.fields.mapBoth { case (n, t) =>
newFieldsName = t.fields.mapBoth { case (n, _) =>
AbilityType.fullName(name, n) -> AbilityType.fullName(newName, n)
}
allNewNames = newFieldsName.add((name, newName)).toSortedMap
@ -313,22 +313,28 @@ object ArrowInliner extends Logging {
case ArrowType(_, _) =>
Exports
.getLastValue(currentOldName, exports)
.flatMap { case vm @ VarModel(name, _, _) =>
arrows
.get(name)
.map(fa =>
(
Map(currentNewName.getOrElse(currentOldName) -> vm),
Map(name -> fa)
.flatMap {
case vm @ VarModel(name, _, _) =>
arrows
.get(name)
.map(fa =>
(
Map(currentNewName.getOrElse(currentOldName) -> vm),
Map(name -> fa)
)
)
)
case lm @ LiteralModel(_, _) =>
logger.error(s"Unexpected. Literal '$lm' cannot be an arrow")
None
}
.getOrElse((Map.empty, Map.empty))
case _ =>
Exports
.getLastValue(currentOldName, exports)
.map(vm => (Map(currentNewName.getOrElse(currentOldName) -> vm), Map.empty))
.map { vm =>
(Map(currentNewName.getOrElse(currentOldName) -> vm), Map.empty)
}
.getOrElse((Map.empty, Map.empty))
}
}.foldMapA(_.bimap(_.toList, _.toList)).bimap(_.toMap, _.toMap)

View File

@ -76,7 +76,7 @@ object CallArrowRawInliner extends RawInliner[CallArrowRaw] with Logging {
): State[S, (List[ValueModel], Inline)] = for {
arrows <- Arrows[S].arrows
exports <- Exports[S].exports
lastArrow <- Exports[S].getLast(funcName)
lastArrow <- Exports[S].getLastVarName(funcName)
arrow = arrows
.get(funcName)
.orElse(

View File

@ -1,6 +1,6 @@
package aqua.model.inline.state
import aqua.model.{ValueModel, VarModel}
import aqua.model.{LiteralModel, ValueModel, VarModel}
import aqua.model.ValueModel.Ability
import aqua.types.AbilityType
import cats.data.{NonEmptyList, State}
@ -44,9 +44,9 @@ trait Exports[S] extends Scoped[S] {
def copyWithAbilityPrefix(prefix: String, newPrefix: String): State[S, Unit]
/**
* Get name of last linked VarModel
* Get name of last linked VarModel. If the last element is not VarModel, return None
*/
def getLast(name: String): State[S, Option[String]]
def getLastVarName(name: String): State[S, Option[String]]
/**
* Rename names in variables
@ -98,8 +98,8 @@ trait Exports[S] extends Scoped[S] {
override def copyWithAbilityPrefix(prefix: String, newPrefix: String): State[R, Unit] =
self.copyWithAbilityPrefix(prefix, newPrefix).transformS(f, g)
override def getLast(name: String): State[R, Option[String]] =
self.getLast(name).transformS(f, g)
override def getLastVarName(name: String): State[R, Option[String]] =
self.getLastVarName(name).transformS(f, g)
override def renameVariables(renames: Map[String, String]): State[R, Unit] =
self.renameVariables(renames).transformS(f, g)
@ -125,12 +125,14 @@ object Exports {
def apply[S](implicit exports: Exports[S]): Exports[S] = exports
// Get last linked VarModel
def getLastValue(name: String, state: Map[String, ValueModel]): Option[VarModel] = {
def getLastValue(name: String, state: Map[String, ValueModel]): Option[ValueModel] = {
state.get(name) match {
case Some(vm@VarModel(n, _, _)) =>
if (name == n) Option(vm)
else getLastValue(n, state).orElse(Option(vm))
case n =>
case lm@Some(LiteralModel(_, _)) =>
lm
case _ =>
None
}
}
@ -138,7 +140,7 @@ object Exports {
object Simple extends Exports[Map[String, ValueModel]] {
// Make links from one set of abilities to another (for ability assignment)
private def getAbilityPairs(oldName: String, newName: String, at: AbilityType, state: Map[String, ValueModel]): NonEmptyList[(String, VarModel)] = {
private def getAbilityPairs(oldName: String, newName: String, at: AbilityType, state: Map[String, ValueModel]): NonEmptyList[(String, ValueModel)] = {
at.fields.toNel.flatMap {
case (n, at@AbilityType(_, _)) =>
val newFullName = AbilityType.fullName(newName, n)
@ -158,15 +160,19 @@ object Exports {
value: ValueModel
): State[Map[String, ValueModel], Unit] = State.modify { state =>
value match {
case vm@Ability(name, at, property) if property.isEmpty =>
case Ability(name, at, property) if property.isEmpty =>
val pairs = getAbilityPairs(name, exportName, at, state)
state ++ pairs.toList.toMap
case _ => state + (exportName -> value)
}
}
override def getLast(name: String): State[Map[String, ValueModel], Option[String]] =
State.get.map(st => getLastValue(name, st).map(_.name))
override def getLastVarName(name: String): State[Map[String, ValueModel], Option[String]] =
State.get.map(st => getLastValue(name, st).flatMap {
case VarModel(name, _, _) => Option(name)
case LiteralModel(_, _) =>
None
})
override def resolved(exports: Map[String, ValueModel]): State[Map[String, ValueModel], Unit] =
State.modify(_ ++ exports)

View File

@ -63,19 +63,23 @@ object CompareTypes {
val lfView = lf.view
val rfView = rf.view
if (lf == rf) 0.0
else if (
lf.keys.forall(rf.contains) && compareTypesList(
lfView.values.toList,
rfView.filterKeys(lfNEM.keys.contains).values.toList
) == 1.0
) 1.0
else if (
rf.keys.forall(lf.contains) && compareTypesList(
lfView.filterKeys(rfNEM.keys.contains).values.toList,
rfView.values.toList
) == -1.0
) -1.0
else NaN
else if (lf.keys.forall(rf.contains)) {
if (
compareTypesList(
lfView.values.toList,
rfView.filterKeys(lfNEM.keys.contains).values.toList
) >= 0.0
) 1.0
else NaN
} else if (rf.keys.forall(lf.contains)) {
if (
compareTypesList(
lfView.filterKeys(rfNEM.keys.contains).values.toList,
rfView.values.toList
) <= 0
) -1.0
else NaN
} else NaN
}
private def compareProducts(l: ProductType, r: ProductType): Double = ((l, r): @unchecked) match {