Skip to content

Commit

Permalink
Merge pull request #976 from wavesplatform/NODE-666-ScalaJS-TypeTag
Browse files Browse the repository at this point in the history
NODE-666 ScalaJS TypeTag[_] issue
  • Loading branch information
ismagin authored Apr 4, 2018
2 parents 2548c40 + c80e168 commit b885970
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ env:
- secure: NYdeuFBQVVjN5gon2KQEDfNfyp9Bk6AZJ7V1tfLJW54y5Dd/4PHdWGWeOyhCMIqwX3UaUmWExY1KcKXpbDMLwTmahNLjiDc3e3rCqxghL5cDgqs6OHcwNGw+CwNiw3tufo5PZ9mhK/ZJh0qgOHWyETs/ifq4VyacvgYH9IZzK4YdKYdwf+T9c3Q7VO3KmzOXiHkvPvLfkc8I8cSZlT1kJFCGawRdE/0nygPd8HYfCxiEFuXOKpRz0hlm8njjZdlZpMcou8TLeH5fFSvHtELIHFvSTEj/QilSTmCXgFRVUd8sd53n3uTouwtVbdCHsXNm4Nphf0lUJnN7fTzbD2xpOZN9zS+SO/YTkIxZpSgQLwsJsWzIHI+6IOlcjeNwZHbYBfsAdv1c4GMkgFfxsWWtb4r7+ooXDTbyy1qeZgVU4caGBMwoNE+l0Y60JdGYT4hrTfJ5c+GpcPzIV3CYVOUkHmoCYXRCtxnfDFXCR0mDVfYAA14osLi3v+EKdTxHDfqP6fICIW9y7oZd//38iEQrDy+k9iCyNmvOsqvDxXZithmSP0WzeGCXQDI+s3FxB5ywt8yDvzFbF5JYY74o1tnNFG7TlSF5v8EHSHxzXBeBtnE9V1g2wxYnvW2HYk5kW6WZJgYE9LCc6ubpQ2W387PRfCJjoLNjHqNlNGkEYlkPuaw=
- secure: rvBDWIiECsp1rI5wUjtw0098Gb2qE4r1uj33w+OtEnszftoZ+XgvvyyyfeykkcAAWob56pOFLox6CO1t/t3YyIyi8sXCPrf1eLBaFt4p6K5czNkWyWY6rlgd/cEXrJeE63UZhx9trN+jiZVjZ7oPpVNSNVAqXhf7dCHWOsKKu7i2gclzGYG1JeBL4eES/WJNVlgcsOdXbWG/udz9A/3+26afgN8Y5N/UMc0PJEmWUKcX7xWfLIcg4UIIvyPp5P55CT698N1jz57GHESDyYrMxMpZOO+0Q26Rjm+7A0U2+mm9XFE7cXT1JNFrJbUs/mvOpjTe0pScT34hRpH1pJcU3vppG/ffJ3IXB9/L2UfWcSemx4pNpdHuvt8Q2otdIlHxZrZpvHAOi6m/HkNBQlNYdRu9hjMVEmJUxiPtLl7npeMfbranEZ+gUtZoFV64aNBV6SUAmsi2supmj9xKmt3R0XOoMCR5zRglm/2EQY2fGhhp+OPrIWgSbGSTXXcUc5hEVM7sdTHo3taIbZsWya6NfjVJ/cxpBjc9hGpuSITU+r0WfSgTLqvEI3cgXQki/ggqVnDM80ft32KIUKIUaoaZSI7r5REps4A1F7HAP9+6VEJRk1p70Ls2IonzDifLij2LQDds1q8LbA9qOQtdj9beAGPvgKPJnBbH4rjNG5k+Iyw=
script:
- sbt -S-Xfatal-warnings ";clean;set scalafmtOnCompile in ThisBuild := false;set scalafmtTestOnCompile in ThisBuild := true;coverage;generator/compile;langJS/compile;lang/test;test;coverageReport"
- sbt -S-Xfatal-warnings ";clean;set scalafmtOnCompile in ThisBuild := false;set scalafmtTestOnCompile in ThisBuild := true;coverage;generator/compile;langJS/fastOptJS;lang/test;test;coverageReport"
after_success:
- "[[ $TRAVIS_REPO_SLUG == \"wavesplatform/Scorex\" ]] && [[ $TRAVIS_BRANCH == \"master\" ]] && { sbt publish; };"
- bash <(curl -s https://codecov.io/bash)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import cats.data.EitherT
import com.wavesplatform.lang.Common._
import com.wavesplatform.lang.Terms.Typed._
import com.wavesplatform.lang.Terms._
import com.wavesplatform.lang.TypeInfo._
import com.wavesplatform.lang.ctx._
import com.wavesplatform.lang.testing.ScriptGen
import org.scalatest.prop.PropertyChecks
import org.scalatest.{Matchers, PropSpec}
import scala.reflect.runtime.universe.TypeTag

class EvaluatorTest extends PropSpec with PropertyChecks with Matchers with ScriptGen with NoShrink {

private def ev[T: TypeTag](context: Context = Context.empty, expr: EXPR): Either[_, _] = Evaluator[T](context, expr)
private def simpleDeclarationAndUsage(i: Int) = BLOCK(Some(LET("x", CONST_LONG(i))), REF("x", LONG), LONG)
private def ev[T: TypeInfo](context: Context = Context.empty, expr: EXPR): Either[_, _] = Evaluator[T](context, expr)
private def simpleDeclarationAndUsage(i: Int) = BLOCK(Some(LET("x", CONST_LONG(i))), REF("x", LONG), LONG)

property("successful on very deep expressions (stack overflow check)") {
val term = (1 to 100000).foldLeft[EXPR](CONST_LONG(0))((acc, _) => BINARY_OP(acc, SUM_OP, CONST_LONG(1), LONG))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.wavesplatform.lang

import org.scalatest.prop.PropertyChecks
import org.scalatest.{Matchers, PropSpec}
import com.wavesplatform.lang.Common._
import com.wavesplatform.lang.TypeInfo._
import com.wavesplatform.lang.ctx._
import scala.reflect.runtime.universe.TypeTag
import org.scalatest.prop.PropertyChecks
import org.scalatest.{Matchers, PropSpec}

class IntegrationTest extends PropSpec with PropertyChecks with Matchers with NoShrink {

private def eval[T: TypeTag](code: String) = {
private def eval[T: TypeInfo](code: String) = {
val untyped = Parser(code).get.value
val ctx = Context(Map.empty, Map.empty, Map(multiplierFunction.name -> multiplierFunction))
val typed = TypeChecker(TypeChecker.TypeCheckerContext.fromContext(ctx), untyped)
Expand Down
30 changes: 14 additions & 16 deletions lang/shared/src/main/scala/com/wavesplatform/lang/Evaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,31 @@ package com.wavesplatform.lang

import cats.data.EitherT
import com.wavesplatform.lang.Terms._
import com.wavesplatform.lang.TypeInfo._
import com.wavesplatform.lang.ctx._
import monix.eval.Coeval

import scala.reflect.runtime.universe.TypeTag

import scala.util.{Failure, Success, Try}

object Evaluator {

private def r[T: TypeTag](ctx: Context, t: TrampolinedExecResult[Typed.EXPR]): TrampolinedExecResult[T] =
private def r[T: TypeInfo](ctx: Context, t: TrampolinedExecResult[Typed.EXPR]): TrampolinedExecResult[T] =
t flatMap { (typedExpr: Typed.EXPR) =>
(typedExpr match {
case Typed.BLOCK(mayBeLet, inner, blockTpe) =>
mayBeLet match {
case None => r(ctx, EitherT.pure(inner))(blockTpe.typetag)
case None => r(ctx, EitherT.pure(inner))(blockTpe.typeInfo)
case Some(Typed.LET(newVarName, newVarBlock)) =>
(ctx.letDefs.get(newVarName), ctx.functions.get(newVarName)) match {
case (Some(_), _) => EitherT.leftT[Coeval, T](s"Value '$newVarName' already defined in the scope")
case (_, Some(_)) =>
EitherT.leftT[Coeval, Typed.EXPR](s"Value '$newVarName' can't be defined because function with such name is predefined")
case (None, None) =>
val varBlockTpe = newVarBlock.tpe
val eitherTCoeval: TrampolinedExecResult[varBlockTpe.Underlying] = r(ctx, EitherT.pure(newVarBlock))(varBlockTpe.typetag)
val eitherTCoeval: TrampolinedExecResult[varBlockTpe.Underlying] = r(ctx, EitherT.pure(newVarBlock))(varBlockTpe.typeInfo)
val lz: LazyVal = LazyVal(varBlockTpe)(eitherTCoeval)
val updatedCtx: Context = ctx.copy(letDefs = ctx.letDefs.updated(newVarName, lz))
r(updatedCtx, EitherT.pure(inner))(blockTpe.typetag)
r(updatedCtx, EitherT.pure(inner))(blockTpe.typeInfo)
}
}
case Typed.REF(str, _) =>
Expand Down Expand Up @@ -61,8 +60,8 @@ object Evaluator {

case Typed.IF(cond, t1, t2, tpe) =>
r[Boolean](ctx, EitherT.pure(cond)) flatMap {
case true => r(ctx, EitherT.pure(t1))(tpe.typetag)
case false => r(ctx, EitherT.pure(t2))(tpe.typetag)
case true => r(ctx, EitherT.pure(t1))(tpe.typeInfo)
case false => r(ctx, EitherT.pure(t2))(tpe.typeInfo)
}

case Typed.BINARY_OP(t1, AND_OP, t2, BOOLEAN) =>
Expand All @@ -78,9 +77,8 @@ object Evaluator {

case Typed.BINARY_OP(it1, EQ_OP, it2, tpe) =>
for {
i1 <- r(ctx, EitherT.pure(it1))(it1.tpe.typetag)
i2 <- r(ctx, EitherT.pure(it2))(it2.tpe.typetag)

i1 <- r(ctx, EitherT.pure(it1))(it1.tpe.typeInfo)
i2 <- r(ctx, EitherT.pure(it2))(it2.tpe.typeInfo)
} yield i1 == i2
case Typed.GETTER(expr, field, _) =>
r[Obj](ctx, EitherT.pure(expr)).flatMap { (obj: Obj) =>
Expand All @@ -98,7 +96,7 @@ object Evaluator {
case Some(func) =>
val argsVector = args
.map(a =>
r(ctx, EitherT.pure(a))(a.tpe.typetag)
r(ctx, EitherT.pure(a))(a.tpe.typeInfo)
.map(_.asInstanceOf[Any]))
.toVector
val argsSequenced = argsVector.sequence[TrampolinedExecResult, Any]
Expand All @@ -113,15 +111,15 @@ object Evaluator {
case Typed.BINARY_OP(_, AND_OP | OR_OP, _, tpe) if tpe != BOOLEAN => EitherT.leftT[Coeval, Any](s"Expected BOOLEAN, but got $tpe: $t")

}).map { v =>
val tt = implicitly[TypeTag[T]]
val ti = typeInfo[T]
v match {
case x if typedExpr.tpe.typetag.tpe <:< tt.tpe => x.asInstanceOf[T]
case _ => throw new Exception(s"Bad type: expected: ${tt.tpe} actual: ${typedExpr.tpe.typetag.tpe}")
case x if typedExpr.tpe.typeInfo <:< ti => x.asInstanceOf[T]
case _ => throw new Exception(s"Bad type: expected: ${ti} actual: ${typedExpr.tpe.typeInfo}")
}
}
}

def apply[A: TypeTag](c: Context, expr: Typed.EXPR): Either[ExecutionError, A] = {
def apply[A: TypeInfo](c: Context, expr: Typed.EXPR): Either[ExecutionError, A] = {
def result = r[A](c, EitherT.pure(expr)).value.apply()
Try(result) match {
case Failure(ex) => Left(ex.toString)
Expand Down
9 changes: 4 additions & 5 deletions lang/shared/src/main/scala/com/wavesplatform/lang/Terms.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package com.wavesplatform.lang
import com.wavesplatform.lang.ctx.Obj
import scodec.bits.ByteVector

import scala.reflect.runtime.universe._

object Terms {

case class FUNCTION(args: List[TYPEPLACEHOLDER], result: TYPEPLACEHOLDER)
Expand All @@ -14,11 +12,12 @@ object Terms {

sealed trait TYPE extends TYPEPLACEHOLDER {
type Underlying
def typetag: TypeTag[Underlying]
def typeInfo: TypeInfo[Underlying]
}
sealed abstract class AUTO_TAGGED_TYPE[T](implicit override val typetag: TypeTag[T]) extends TYPE {
sealed abstract class AUTO_TAGGED_TYPE[T](implicit override val typeInfo: TypeInfo[T]) extends TYPE {
override type Underlying = T
}

case object NOTHING extends AUTO_TAGGED_TYPE[Nothing]
case object UNIT extends AUTO_TAGGED_TYPE[Unit]
case object LONG extends AUTO_TAGGED_TYPE[Long]
Expand All @@ -27,7 +26,7 @@ object Terms {
case object STRING extends AUTO_TAGGED_TYPE[String]
case class OPTION(innerType: TYPE) extends TYPE {
type Underlying = Option[innerType.Underlying]
override def typetag: TypeTag[Option[innerType.Underlying]] = typeTag[Underlying]
override def typeInfo: TypeInfo[Option[innerType.Underlying]] = TypeInfo.optionTypeInfo(innerType.typeInfo)
}
case class TYPEREF(name: String) extends AUTO_TAGGED_TYPE[Obj]

Expand Down
93 changes: 93 additions & 0 deletions lang/shared/src/main/scala/com/wavesplatform/lang/TypeInfo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.wavesplatform.lang

import cats.implicits._
import com.wavesplatform.lang.ctx.Obj
import scodec.bits.ByteVector

import scala.reflect.ClassTag

final case class TypeInfo[T](classTag: ClassTag[T],
superClass: Option[TypeInfo[_]],
typeParams: Set[TypeInfo[_]],
interfaces: Set[TypeInfo[_]]) { self =>
def <:< (other: TypeInfo[_]) = {
def loop(left: Set[TypeInfo[_]], seen: Set[TypeInfo[_]]): Boolean = {
left.nonEmpty && {
val next = left.head
val supers = next.interfaces ++ next.superClass
supers(other) || {
val xs = left ++ supers filterNot seen
loop(xs - next, seen + next)
}
}
}

if (self.classTag.runtimeClass == other.classTag.runtimeClass) {
self.typeParams == other.typeParams
} else loop(Set(self), Set.empty)
}
}

object TypeInfo {
def typeInfo[T](implicit ti: TypeInfo[T]): TypeInfo[T] = ti

private def fromAny[A: ClassTag](typeParams: Set[TypeInfo[_]] = Set.empty, interfaces: Set[TypeInfo[_]] = Set.empty): TypeInfo[A] = {
TypeInfo(reflect.classTag[A], anyTypeInfo.some, typeParams, interfaces)
}

private def fromAnyVal[A: ClassTag](typeParams: Set[TypeInfo[_]] = Set.empty, interfaces: Set[TypeInfo[_]] = Set.empty): TypeInfo[A] = {
TypeInfo(reflect.classTag[A], anyValTypeInfo.some, typeParams, interfaces)
}

implicit val anyTypeInfo: TypeInfo[Any] = {
val tag = reflect.classTag[Any]
TypeInfo(tag, None, Set.empty, Set.empty)
}

implicit val equalsTypeInfo: TypeInfo[Equals] =
fromAny()

implicit val serializableTypeInfo: TypeInfo[Serializable] =
fromAny()

implicit val productTypeInfo: TypeInfo[Product] =
fromAny(interfaces = Set(serializableTypeInfo))

implicit val anyValTypeInfo: TypeInfo[AnyVal] =
fromAny()

implicit val nothingTypeInfo: TypeInfo[Nothing] = {
val tag = reflect.classTag[Nothing]
TypeInfo[Nothing](
tag,
None,
Set.empty,
Set.empty
)
}

implicit val unitTypeInfo: TypeInfo[Unit] =
fromAnyVal()
implicit val longTypeInfo: TypeInfo[Long] =
fromAnyVal()

/**
* BitwiseOperations also should be in interfaces,
* but i dont know how to create TypeInfo with
* recursive type parameters.
*/
implicit val byteVectorTypeInfo: TypeInfo[ByteVector] =
fromAny(interfaces = Set(serializableTypeInfo))

implicit val booleanTypeInfo: TypeInfo[Boolean] =
fromAnyVal()

implicit val stringTypeInfo: TypeInfo[String] =
fromAnyVal(interfaces = Set(serializableTypeInfo))

implicit val objTypeInfo: TypeInfo[Obj] =
fromAny(interfaces = Set(productTypeInfo, serializableTypeInfo))

implicit def optionTypeInfo[A](implicit tia: TypeInfo[A]): TypeInfo[Option[A]] =
fromAny(Set(tia), Set(productTypeInfo, serializableTypeInfo))
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.wavesplatform.state2.diffs.smart.predef

import com.wavesplatform.lang.TypeInfo._
import com.wavesplatform.state2._
import com.wavesplatform.state2.diffs._
import com.wavesplatform.{NoShrink, TransactionGen}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package com.wavesplatform.state2.diffs.smart

import com.wavesplatform.lang.Evaluator
import com.wavesplatform.lang.{Evaluator, TypeInfo}
import com.wavesplatform.utils.dummyTypeCheckerContext
import fastparse.core.Parsed.Success
import monix.eval.Coeval
import scorex.transaction.Transaction
import scorex.transaction.smart.BlockchainContext
import scala.reflect.runtime.universe.TypeTag
package object predef {
val networkByte: Byte = 'u'
def runScript[T: TypeTag](script: String, tx: Transaction = null): Either[String, T] = {
def runScript[T: TypeInfo](script: String, tx: Transaction = null): Either[String, T] = {
val Success(expr, _) = com.wavesplatform.lang.Parser(script)
val Right(typedExpr) = com.wavesplatform.lang.TypeChecker(dummyTypeCheckerContext, expr)
Evaluator[T](new BlockchainContext(networkByte, Coeval(tx), Coeval(???), null).build(), typedExpr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@ package com.wavesplatform.state2.diffs.smart.scenarios

import java.nio.charset.StandardCharsets

import com.wavesplatform.{NoShrink, TransactionGen}
import com.wavesplatform.lang.{Evaluator, Parser, TypeChecker}
import com.wavesplatform.lang.TypeInfo._
import com.wavesplatform.lang.{Evaluator, Parser, TypeChecker, TypeInfo}
import com.wavesplatform.state2._
import com.wavesplatform.state2.diffs._
import com.wavesplatform.state2.diffs.smart._
import com.wavesplatform.utils._

import scala.reflect.runtime.universe.TypeTag
import com.wavesplatform.{NoShrink, TransactionGen}
import org.scalacheck.Gen
import org.scalatest.{Matchers, PropSpec}
import org.scalatest.prop.PropertyChecks
import org.scalatest.{Matchers, PropSpec}
import scorex.account.AddressScheme
import scorex.transaction.{DataTransaction, GenesisTransaction}
import scorex.transaction.assets.{SmartIssueTransaction, TransferTransaction}
import scorex.transaction.smart.Script
import scorex.transaction.{DataTransaction, GenesisTransaction}

class HackatonScenartioTest extends PropSpec with PropertyChecks with Matchers with TransactionGen with NoShrink {
val preconditions: Gen[
Expand Down Expand Up @@ -101,7 +100,7 @@ class HackatonScenartioTest extends PropSpec with PropertyChecks with Matchers w
accountBDataTransaction,
transferFromAToB)

private def eval[T: TypeTag](code: String) = {
private def eval[T: TypeInfo](code: String) = {
val untyped = Parser(code).get.value
val typed = TypeChecker(dummyTypeCheckerContext, untyped)
typed.flatMap(Evaluator[T](dummyContext, _))
Expand Down

0 comments on commit b885970

Please sign in to comment.