diff --git a/core/shared/src/main/scala/sigma/SigmaDsl.scala b/core/shared/src/main/scala/sigma/SigmaDsl.scala index 16331febfd..7b9773dc8c 100644 --- a/core/shared/src/main/scala/sigma/SigmaDsl.scala +++ b/core/shared/src/main/scala/sigma/SigmaDsl.scala @@ -778,5 +778,9 @@ trait SigmaDslBuilder { /** Returns a number decoded from provided big-endian bytes array. */ def fromBigEndianBytes[T](bytes: Coll[Byte])(implicit cT: RType[T]): T + + def some[T](value: T)(implicit cT: RType[T]): Option[T] + + def none[T]()(implicit cT: RType[T]): Option[T] } diff --git a/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala b/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala index 3351c5a2c6..2b2bac7f43 100644 --- a/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala +++ b/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala @@ -471,6 +471,12 @@ object ReflectionData { }, mkMethod(clazz, "fromBigEndianBytes", Array[Class[_]](cColl, classOf[RType[_]])) { (obj, args) => obj.asInstanceOf[SigmaDslBuilder].fromBigEndianBytes(args(0).asInstanceOf[Coll[Byte]])(args(1).asInstanceOf[RType[_]]) + }, + mkMethod(clazz, "some", Array[Class[_]](classOf[Object], classOf[RType[_]])) { (obj, args) => + obj.asInstanceOf[SigmaDslBuilder].some(args(0).asInstanceOf[Any])(args(1).asInstanceOf[RType[Any]]) + }, + mkMethod(clazz, "none", Array[Class[_]](classOf[RType[_]])) { (obj, args) => + obj.asInstanceOf[SigmaDslBuilder].none()(args(0).asInstanceOf[RType[_]]) } ) ) diff --git a/data/shared/src/main/scala/sigma/ast/methods.scala b/data/shared/src/main/scala/sigma/ast/methods.scala index 4cba72e28d..690732fec8 100644 --- a/data/shared/src/main/scala/sigma/ast/methods.scala +++ b/data/shared/src/main/scala/sigma/ast/methods.scala @@ -1871,13 +1871,30 @@ case object SGlobalMethods extends MonoTypeMethods { Colls.fromArray(w.toBytes) } + lazy val someMethod = SMethod(this, "some", + SFunc(Array(SGlobal, tT), SOption(tT), Array(paramT)), 8, FixedCost(JitCost(5)), Seq(tT)) + .withIRInfo(MethodCallIrBuilder, + javaMethodOf[SigmaDslBuilder, Any, RType[_]]("some"), + { mtype => Array(mtype.tRange) }) + .withInfo(MethodCall, "Wrap given input into optional value (Option()).", + ArgInfo("value", "Value to wrap into Option.")) + + lazy val noneMethod = SMethod(this, "none", + SFunc(Array(SGlobal), SOption(tT), Array(paramT)), 9, FixedCost(JitCost(5)), Seq(tT)) + .withIRInfo(MethodCallIrBuilder, + javaMethodOf[SigmaDslBuilder, RType[_]]("none"), + { mtype => Array(mtype.tRange) }) + .withInfo(MethodCall, "Returns empty Option[T] of given type T.") + protected override def getMethods() = super.getMethods() ++ { if (VersionContext.current.isV6SoftForkActivated) { Seq( groupGeneratorMethod, xorMethod, serializeMethod, - fromBigEndianBytesMethod + fromBigEndianBytesMethod, + someMethod, + noneMethod ) } else { Seq( diff --git a/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala b/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala index 2ae4f73703..2259bcca5a 100644 --- a/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala +++ b/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala @@ -245,6 +245,14 @@ class CSigmaDslBuilder extends SigmaDslBuilder { dsl => DataSerializer.serialize(value.asInstanceOf[SType#WrappedType], tpe, w) Colls.fromArray(w.toBytes) } + + override def some[T](value: T)(implicit cT: RType[T]): Option[T] = { + Some(value) + } + + override def none[T]()(implicit cT: RType[T]): Option[T] = { + None + } } /** Default singleton instance of Global object, which implements global ErgoTree functions. */ diff --git a/data/shared/src/main/scala/sigma/serialization/PropertyCallSerializer.scala b/data/shared/src/main/scala/sigma/serialization/PropertyCallSerializer.scala index 10411e21ce..072e7a7ed5 100644 --- a/data/shared/src/main/scala/sigma/serialization/PropertyCallSerializer.scala +++ b/data/shared/src/main/scala/sigma/serialization/PropertyCallSerializer.scala @@ -5,6 +5,10 @@ import sigma.serialization.CoreByteWriter.{ArgInfo, DataInfo} import sigma.ast._ import sigma.ast.syntax.SValue import SigmaByteWriter._ +import debox.cfor +import sigma.util.safeNewArray + +import scala.collection.compat.immutable.ArraySeq case class PropertyCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value[SType]], STypeSubst) => Value[SType]) extends ValueSerializer[MethodCall] { @@ -17,6 +21,10 @@ case class PropertyCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value w.put(mc.method.objType.typeId, typeCodeInfo) w.put(mc.method.methodId, methodCodeInfo) w.putValue(mc.obj, objInfo) + mc.method.explicitTypeArgs.foreach { a => + val tpe = mc.typeSubst(a) // existence is checked in MethodCall constructor + w.putType(tpe) + } } override def parse(r: SigmaByteReader): Value[SType] = { @@ -24,7 +32,22 @@ case class PropertyCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value val methodId = r.getByte() val obj = r.getValue() val method = SMethod.fromIds(typeId, methodId) - val specMethod = method.specializeFor(obj.tpe, SType.EmptySeq) - cons(obj, specMethod, Value.EmptySeq, EmptySubst) + + val (explicitTypeSubst: Map[STypeVar, SType], specMethod: SMethod) = if (method.hasExplicitTypeArgs) { + val nTypes = method.explicitTypeArgs.length + val res = safeNewArray[SType](nTypes) + cfor(0)(_ < nTypes, _ + 1) { i => + res(i) = r.getType() + } + val explicitTypes = ArraySeq.unsafeWrapArray(res) + val explicitTypeSubst = method.explicitTypeArgs.zip(explicitTypes).toMap + val specMethod = method.withConcreteTypes(explicitTypeSubst) + (explicitTypeSubst, specMethod) + } else { + val specMethod = method.specializeFor(obj.tpe, SType.EmptySeq) + (EmptySubst, specMethod) + } + + cons(obj, specMethod, Value.EmptySeq, explicitTypeSubst) } } diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala b/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala index 37550e3430..a76e1329ba 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala @@ -1184,6 +1184,13 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => val bytes = asRep[Coll[Byte]](argsV(0)) val cT = stypeToElem(method.stype.tRange.withSubstTypes(typeSubst)) g.fromBigEndianBytes(bytes)(cT) + case SGlobalMethods.someMethod.name => + val value = asRep[tT.WrappedType](argsV(0)) + val cT = stypeToElem(typeSubst.apply(tT)).asInstanceOf[Elem[tT.WrappedType]] + g.some(value)(cT) + case SGlobalMethods.noneMethod.name => + val cT = stypeToElem(typeSubst.apply(tT)).asInstanceOf[Elem[tT.WrappedType]] + g.none()(cT) case _ => throwError() } case (x: Ref[tNum], _: SNumericTypeMethods) => method.name match { diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala b/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala index 8f461e502e..a7285d6667 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala @@ -533,6 +533,12 @@ object GraphIRReflection { }, mkMethod(clazz, "fromBigEndianBytes", Array[Class[_]](classOf[Base#Ref[_]], classOf[TypeDescs#Elem[_]])) { (obj, args) => obj.asInstanceOf[ctx.SigmaDslBuilder].fromBigEndianBytes(args(0).asInstanceOf[ctx.Ref[ctx.Coll[Byte]]])(args(1).asInstanceOf[ctx.Elem[SType]]) + }, + mkMethod(clazz, "some", Array[Class[_]](classOf[Base#Ref[_]], classOf[TypeDescs#Elem[_]])) { (obj, args) => + obj.asInstanceOf[ctx.SigmaDslBuilder].some(args(0).asInstanceOf[ctx.Ref[Any]])(args(1).asInstanceOf[ctx.Elem[Any]]) + }, + mkMethod(clazz, "none", Array[Class[_]](classOf[TypeDescs#Elem[_]])) { (obj, args) => + obj.asInstanceOf[ctx.SigmaDslBuilder].none()(args(0).asInstanceOf[ctx.Elem[SType]]) } ) ) diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala index a90583a4a4..648d98a292 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala @@ -118,6 +118,8 @@ import scalan._ def xor(l: Ref[Coll[Byte]], r: Ref[Coll[Byte]]): Ref[Coll[Byte]] def serialize[T](value: Ref[T]): Ref[Coll[Byte]] def fromBigEndianBytes[T](bytes: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T] + def some[T](value: Ref[T])(implicit cT: Elem[T]): Ref[WOption[T]] + def none[T]()(implicit cT: Elem[T]): Ref[WOption[T]] }; trait CostModelCompanion; trait BigIntCompanion; diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala index 0ce72d2314..9551bef015 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala @@ -1992,8 +1992,22 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { override def fromBigEndianBytes[T](bytes: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T] = { asRep[T](mkMethodCall(self, SigmaDslBuilderClass.getMethod("fromBigEndianBytes", classOf[Sym], classOf[Elem[T]]), - Array[AnyRef](bytes, cT, Map(tT -> Evaluation.rtypeToSType(cT.sourceType))), - true, false, cT)) + Array[AnyRef](bytes, cT), + true, false, cT, Map(tT -> Evaluation.rtypeToSType(cT.sourceType)))) + } + + override def some[T](value: Ref[T])(implicit cT: Elem[T]): Ref[WOption[T]] = { + asRep[WOption[T]](mkMethodCall(self, + SigmaDslBuilderClass.getMethod("some", classOf[Sym], classOf[Elem[T]]), + Array[AnyRef](value, cT), + true, false, element[WOption[T]], Map(tT -> Evaluation.rtypeToSType(cT.sourceType)))) + } + + override def none[T]()(implicit cT: Elem[T]): Ref[WOption[T]] = { + asRep[WOption[T]](mkMethodCall(self, + SigmaDslBuilderClass.getMethod("none", classOf[Elem[T]]), + Array[AnyRef](cT), + true, false, element[WOption[T]], Map(tT -> Evaluation.rtypeToSType(cT.sourceType)))) } } @@ -2169,6 +2183,21 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { Array[AnyRef](bytes, cT), true, true, cT, Map(tT -> Evaluation.rtypeToSType(cT.sourceType)))) } + + def some[T](value: Ref[T])(implicit cT: Elem[T]): Ref[WOption[T]] = { + asRep[WOption[T]](mkMethodCall(source, + SigmaDslBuilderClass.getMethod("some", classOf[Sym], classOf[Elem[T]]), + Array[AnyRef](value, cT), + true, true, element[WOption[T]], Map(tT -> Evaluation.rtypeToSType(cT.sourceType)))) + } + + def none[T]()(implicit cT: Elem[T]): Ref[WOption[T]] = { + asRep[WOption[T]](mkMethodCall(source, + SigmaDslBuilderClass.getMethod("none", classOf[Elem[T]]), + Array[AnyRef](cT), + true, true, element[WOption[T]], Map(tT -> Evaluation.rtypeToSType(cT.sourceType)))) + } + } // entityUnref: single unref method for each type family diff --git a/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala b/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala index 15ce673332..6a9a1010cc 100644 --- a/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala +++ b/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala @@ -1950,4 +1950,30 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => ) } + property("Global.some") { + lazy val some = newFeature( + { (x: Byte) => CSigmaDslBuilder.some[Byte](x) }, + "{ (x: Byte) => Global.some[Byte](x) }", + sinceVersion = V6SoftForkVersion) + val cases = Seq( + (0.toByte, Success(Some(0.toByte))), + (1.toByte, Success(Some(1.toByte))) + ) + + testCases(cases, some) + } + + property("Global.none") { + lazy val some = newFeature( + { (x: Byte) => CSigmaDslBuilder.none[Byte]() }, + "{ (x: Byte) => Global.none[Byte]() }", + sinceVersion = V6SoftForkVersion) + val cases = Seq( + (0.toByte, Success(None)), + (1.toByte, Success(None)) + ) + + testCases(cases, some) + } + } diff --git a/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala b/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala index 1c38ca45a0..99a01d90d2 100644 --- a/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala @@ -520,7 +520,7 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit with C MInfo(1, groupGeneratorMethod), MInfo(2, xorMethod) ) ++ (if (isV6Activated) { // id = 4 reserved for deserializeTo method - Seq(MInfo(3, serializeMethod), MInfo(5, fromBigEndianBytesMethod)) // methods added in v6.0 + Seq(MInfo(3, serializeMethod), MInfo(5, fromBigEndianBytesMethod), MInfo(8, someMethod), MInfo(9, noneMethod)) // methods added in v6.0 } else { Seq.empty[MInfo] }), true) diff --git a/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala b/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala index 0eba3c9878..afa39b24cd 100644 --- a/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala @@ -26,6 +26,7 @@ import sigma.eval.EvalSettings import sigma.exceptions.InvalidType import sigma.serialization.ErgoTreeSerializer import sigma.interpreter.{ContextExtension, ProverResult} +import sigma.validation.ValidationException import sigmastate.utils.Helpers import sigmastate.utils.Helpers._ @@ -1080,7 +1081,6 @@ class BasicOpsSpecification extends CompilerTestingCommons } } - // todo: failing, needs for Header (de)serialization support from https://github.com/ScorexFoundation/sigmastate-interpreter/pull/972 property("serialize - collection of collection of headers") { val td = new SigmaTestingData {} val h1 = td.TestData.h1 @@ -1888,4 +1888,69 @@ class BasicOpsSpecification extends CompilerTestingCommons ) } + property("Global.some") { + val ext: Seq[VarBinding] = Seq( + (intVar1, IntConstant(0)) + ) + def someTest(): Assertion = { + test("some", env, ext, + """{ + | val xo = Global.some[Int](5) + | xo.get == 5 + |}""".stripMargin, + null + ) + } + + if (VersionContext.current.isV6SoftForkActivated) { + someTest() + } else { + an[sigma.validation.ValidationException] should be thrownBy someTest() + } + } + + property("Global.some - computable value") { + val ext: Seq[VarBinding] = Seq( + (intVar1, IntConstant(0)) + ) + def someTest(): Assertion = { + test("some", env, ext, + """{ + | val i = getVar[Int](1) + | val xo = Global.some[Int](i.get) + | xo == i + |}""".stripMargin, + null + ) + } + + if (VersionContext.current.isV6SoftForkActivated) { + someTest() + } else { + an[sigma.validation.ValidationException] should be thrownBy someTest() + } + } + + property("Global.none") { + val ext: Seq[VarBinding] = Seq( + (intVar1, IntConstant(0)) + ) + def someTest(): Assertion = { + test("some", env, ext, + """{ + | val xo = Global.some[Long](5L) + | val xn = Global.none[Long]() + | xn.isDefined == false && xn != xo + |}""".stripMargin, + null + ) + } + + if (VersionContext.current.isV6SoftForkActivated) { + someTest() + } else { + an[sigma.validation.ValidationException] should be thrownBy someTest() + } + } + }