Daml lf type safty (Decimal) (#1098)

* daml-lf: make Decimal type safe

* daml-lf: create Utf8String type

* daml-lf: cleanup in data package

* Address Stephen Comments

* daml-lf: remove UTF8String
This commit is contained in:
Remy 2019-05-14 20:10:45 +02:00 committed by mergify[bot]
parent 388a2b49f7
commit c7df212d42
48 changed files with 404 additions and 362 deletions

View File

@ -43,7 +43,7 @@ abstract class Reader[+Pkg] {
final def readArchiveAndVersion(lf: DamlLf.Archive): (Pkg, LanguageMajorVersion) = {
lf.getHashFunction match {
case DamlLf.HashFunction.SHA256 =>
val payload = lf.getPayload.toByteArray()
val payload = lf.getPayload.toByteArray
val theirHash = PackageId.fromString(lf.getHash) match {
case Right(hash) => hash
case Left(err) => throw ParseError(s"Invalid hash: $err")

View File

@ -0,0 +1,14 @@
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.daml.lf.data
import scala.reflect.ClassTag
final class ArrayFactory[T](implicit classTag: ClassTag[T]) {
def apply(xs: T*): Array[T] = xs.toArray
def ofDim(n: Int): Array[T] = Array.ofDim(n)
val empty: Array[T] = ofDim(0)
}

View File

@ -1,120 +0,0 @@
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.daml.lf.data
import java.math.MathContext
import scala.math.BigDecimal
/** The model of our floating point decimal numbers.
*
* These are numbers of precision 38 (38 decimal digits), and scale 10 (10 digits after the comma)
*/
object Decimal {
type Decimal = BigDecimal
val scale: Int = 10
val context: MathContext = new MathContext(38, java.math.RoundingMode.HALF_EVEN)
private[this] def unlimitedBigDecimal(s: String) =
BigDecimal.decimal(new java.math.BigDecimal(s), MathContext.UNLIMITED)
private[this] def unlimitedBigDecimal(x: Long) =
BigDecimal(new java.math.BigDecimal(x, MathContext.UNLIMITED))
// we use these to compare only, therefore set the precision to unlimited to make sure
// we can compare every number we're given
val max: Decimal = unlimitedBigDecimal("9999999999999999999999999999.9999999999")
val min: Decimal = unlimitedBigDecimal("-9999999999999999999999999999.9999999999")
/** Checks that a `Decimal` falls between `min` and `max`, and
* round the number according to `scale`. Note that it does _not_
* fail if the number contains data beyond `scale`.
*/
def checkWithinBoundsAndRound(x0: Decimal): Either[String, Decimal] = {
if (x0 > max || x0 < min) {
Left(s"out-of-bounds Decimal $x0")
} else {
val x1 = new BigDecimal(x0.bigDecimal, context)
val x2 = x1.setScale(scale, BigDecimal.RoundingMode.HALF_EVEN)
Right(x2)
}
}
/** Like `checkWithinBoundsAndRound`, but _fails_ if the given number contains
* any data beyond `scale`.
*/
def checkWithinBoundsAndWithinScale(x0: Decimal): Either[String, Decimal] = {
for {
x1 <- checkWithinBoundsAndRound(x0)
// if we've lost any data at all, it means that we weren't within the
// scale.
x2 <- Either.cond(x0 == x1, x1, s"out-of-bounds Decimal $x0")
} yield x2
}
def add(x: Decimal, y: Decimal): Either[String, Decimal] = checkWithinBoundsAndRound(x + y)
def div(x: Decimal, y: Decimal): Either[String, Decimal] = checkWithinBoundsAndRound(x / y)
def mult(x: Decimal, y: Decimal): Either[String, Decimal] = checkWithinBoundsAndRound(x * y)
def sub(x: Decimal, y: Decimal): Either[String, Decimal] = checkWithinBoundsAndRound(x - y)
def round(newScale: Long, x0: Decimal): Either[String, Decimal] =
// check to make sure the rounding mode is OK
checkWithinBoundsAndRound(x0).flatMap(x =>
if (newScale > scale || newScale < -27) {
Left(s"Bad scale $newScale, must be between -27 and $scale")
} else {
// we know toIntExact won't crash because we checked the scale above
// we set the scale again to make sure that every Decimal has scale 10, which
// affects equality
Right(
x.setScale(Math.toIntExact(newScale), BigDecimal.RoundingMode.HALF_EVEN)
.setScale(scale))
})
private val hasExpectedFormat =
"""[+-]?\d{1,28}(\.\d{1,10})?""".r.pattern
def fromString(s: String): Either[String, Decimal] =
if (hasExpectedFormat.matcher(s).matches())
checkWithinBoundsAndWithinScale(unlimitedBigDecimal(s))
else
Left(s"""Could not read Decimal string "$s"""")
def assertFromString(s: String): Decimal =
assert(fromString(s))
def toString(d: Decimal): String = {
// Strip the trailing zeros (which BigDecimal keeps if the string
// it was created from had them), and use the plain notation rather
// than scientific notation.
//
// Moreover, add a single trailing zero if we have no decimal part.
// this mimicks the behavior of `formatScientific Fixed Nothing` which
// we've been using in Haskell to render Decimals
// http://hackage.haskell.org/package/scientific-0.3.6.2/docs/Data-Scientific.html#v:formatScientific
val s = d.bigDecimal.stripTrailingZeros.toPlainString
if (s.contains(".")) {
s
} else {
s + ".0"
}
}
def fromLong(x: Long): Decimal =
BigDecimal(new java.math.BigDecimal(x, context)).setScale(scale)
private val toLongLowerBound = unlimitedBigDecimal(Long.MinValue) - 1
private val toLongUpperBound = unlimitedBigDecimal(Long.MaxValue) + 1
def toLong(x: Decimal): Either[String, Long] = {
if (toLongLowerBound < x && x < toLongUpperBound)
Right(x.longValue)
else
Left(s"Decimal $x does not fit into an Int64")
}
private def assert[X](either: Either[String, X]): X =
either.fold(e => throw new IllegalArgumentException(e), identity)
}

View File

@ -0,0 +1,124 @@
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.daml.lf.data
import java.math.MathContext
import scala.math.BigDecimal
/** The model of our floating point decimal numbers.
*
* These are numbers of precision 38 (38 decimal digits), and scale 10 (10 digits after the comma)
*/
abstract class DecimalModule {
type T <: BigDecimal
protected def cast(x: BigDecimal): T
val scale: Int = 10
val context: MathContext = new MathContext(38, java.math.RoundingMode.HALF_EVEN)
private def unlimitedBigDecimal(s: String): T =
cast(BigDecimal.decimal(new java.math.BigDecimal(s), MathContext.UNLIMITED))
private def unlimitedBigDecimal(x: Long): T =
cast(BigDecimal(new java.math.BigDecimal(x, MathContext.UNLIMITED)))
// we use these to compare only, therefore set the precision to unlimited to make sure
// we can compare every number we're given
val max: T = unlimitedBigDecimal("9999999999999999999999999999.9999999999")
val min: T = unlimitedBigDecimal("-9999999999999999999999999999.9999999999")
/** Checks that a `T` falls between `min` and `max`, and
* round the number according to `scale`. Note that it does _not_
* fail if the number contains data beyond `scale`.
*/
private def checkWithinBoundsAndRound(x0: BigDecimal): Either[String, T] = {
if (x0 > max || x0 < min) {
Left(s"out-of-bounds Decimal $x0")
} else {
val x1 = new BigDecimal(x0.bigDecimal, context)
val x2 = x1.setScale(scale, BigDecimal.RoundingMode.HALF_EVEN)
Right(cast(x2))
}
}
/** Like `checkWithinBoundsAndRound`, but _fails_ if the given number contains
* any data beyond `scale`.
*/
final def fromBigDecimal(x0: BigDecimal): Either[String, T] =
for {
x1 <- checkWithinBoundsAndRound(x0)
// if we've lost any data at all, it means that we weren't within the
// scale.
x2 <- Either.cond(x0 == x1, x1, s"out-of-bounds Decimal $x0")
} yield x2
final def assertFromBigDecimal(x: BigDecimal): T =
assert(fromBigDecimal(x))
final def add(x: T, y: T): Either[String, T] = checkWithinBoundsAndRound(x + y)
final def div(x: T, y: T): Either[String, T] = checkWithinBoundsAndRound(x / y)
final def mult(x: T, y: T): Either[String, T] = checkWithinBoundsAndRound(x * y)
final def sub(x: T, y: T): Either[String, T] = checkWithinBoundsAndRound(x - y)
final def round(newScale: Long, x0: T): Either[String, T] =
// check to make sure the rounding mode is OK
checkWithinBoundsAndRound(x0).flatMap(
x =>
if (newScale > scale || newScale < -27)
Left(s"Bad scale $newScale, must be between -27 and $scale")
else
// we know toIntExact won't crash because we checked the scale above
// we set the scale again to make sure that every Decimal has scale 10, which
// affects equality
Right(
cast(
x.setScale(Math.toIntExact(newScale), BigDecimal.RoundingMode.HALF_EVEN)
.setScale(scale))))
private val hasExpectedFormat =
"""[+-]?\d{1,28}(\.\d{1,10})?""".r.pattern
final def fromString(s: String): Either[String, T] =
if (hasExpectedFormat.matcher(s).matches())
fromBigDecimal(unlimitedBigDecimal(s))
else
Left(s"""Could not read Decimal string "$s"""")
@throws[IllegalArgumentException]
final def assertFromString(s: String): T =
assert(fromString(s))
final def toString(d: T): String = {
// Strip the trailing zeros (which BigDecimal keeps if the string
// it was created from had them), and use the plain notation rather
// than scientific notation.
//
// Moreover, add a single trailing zero if we have no decimal part.
// this mimicks the behavior of `formatScientific Fixed Nothing` which
// we've been using in Haskell to render Decimals
// http://hackage.haskell.org/package/scientific-0.3.6.2/docs/Data-Scientific.html#v:formatScientific
val s = d.bigDecimal.stripTrailingZeros.toPlainString
if (s.contains(".")) s else s + ".0"
}
final def fromLong(x: Long): T =
cast(BigDecimal(new java.math.BigDecimal(x, context)).setScale(scale))
private val toLongLowerBound = unlimitedBigDecimal(Long.MinValue) - 1
private val toLongUpperBound = unlimitedBigDecimal(Long.MaxValue) + 1
final def toLong(x: T): Either[String, Long] =
Either.cond(
toLongLowerBound < x && x < toLongUpperBound,
x.longValue,
s"Decimal $x does not fit into an Int64")
}

View File

@ -1,21 +1,20 @@
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.daml.lf.data
import scalaz.Equal
import scala.reflect.ClassTag
import scala.util.matching.Regex
sealed abstract class MatchingStringModule {
type T <: String
def fromString(s: String): Either[String, T]
@throws[IllegalArgumentException]
final def assertFromString(s: String): T =
fromString(s).fold(e => throw new IllegalArgumentException(e), identity)
assert(fromString(s))
def equalInstance: Equal[T]
@ -25,16 +24,6 @@ sealed abstract class MatchingStringModule {
// * https://github.com/digital-asset/daml/pull/983#discussion_r282513324
// * https://github.com/scala/bug/issues/9565
val Array: ArrayFactory[T]
}
sealed abstract class ArrayFactory[T](implicit classTag: ClassTag[T]) {
def apply(xs: T*): Array[T] = xs.toArray
def ofDim(n: Int): Array[T] = Array.ofDim(n)
val empty: Array[T] = ofDim(0)
}
object MatchingStringModule extends (Regex => MatchingStringModule) {
@ -49,7 +38,7 @@ object MatchingStringModule extends (Regex => MatchingStringModule) {
def equalInstance: Equal[T] = scalaz.std.string.stringInstance
val Array: ArrayFactory[T] = new ArrayFactory[T] {}
val Array: ArrayFactory[T] = new ArrayFactory[T]
}
}

View File

@ -55,6 +55,8 @@ object Ref {
}
object DottedName {
type T = DottedName
def fromString(s: String): Either[String, DottedName] =
if (s.isEmpty)
Left(s"Expected a non-empty string")
@ -62,7 +64,7 @@ object Ref {
fromSegments(split(s, '.').toSeq)
@throws[IllegalArgumentException]
def assertFromString(s: String): DottedName =
final def assertFromString(s: String): T =
assert(fromString(s))
def fromSegments(strings: Iterable[String]): Either[String, DottedName] = {
@ -102,6 +104,8 @@ object Ref {
def qualifiedName: String = toString
}
object QualifiedName {
type T = QualifiedName
def fromString(s: String): Either[String, QualifiedName] = {
val segments = split(s, ':')
if (segments.length != 2)
@ -115,7 +119,7 @@ object Ref {
}
@throws[IllegalArgumentException]
def assertFromString(s: String): QualifiedName =
final def assertFromString(s: String): T =
assert(fromString(s))
}
@ -153,7 +157,4 @@ object Ref {
type TypeConName = Identifier
val TypeConName = Identifier
private def assert[X](either: Either[String, X]): X =
either.fold(e => throw new IllegalArgumentException(e), identity)
}

View File

@ -4,13 +4,14 @@
package com.digitalasset.daml.lf.data
import scalaz.Equal
import scalaz.std.string._
import scalaz.std.tuple._
import scalaz.std.string._
import scalaz.syntax.equal._
import scala.collection.immutable.HashMap
/** We use this container to pass around DAML-LF maps as flat lists in various parts of the codebase. */
// Note that keys are ordered using Utf8 ordering
final class SortedLookupList[+X] private (entries: ImmArray[(String, X)]) extends Equals {
def mapValue[Y](f: X => Y) = new SortedLookupList(entries.map { case (k, v) => k -> f(v) })
@ -40,16 +41,13 @@ final class SortedLookupList[+X] private (entries: ImmArray[(String, X)]) extend
object SortedLookupList {
// Note: it's important that this ordering is the same as the DAML-LF ordering.
private implicit val keyOrdering: Ordering[String] = UTF8.ordering
def fromImmArray[X](entries: ImmArray[(String, X)]): Either[String, SortedLookupList[X]] = {
entries.toSeq
.groupBy(_._1)
.collectFirst {
case (k, l) if l.size > 1 => s"key $k duplicated when trying to build map"
}
.toLeft(new SortedLookupList(entries.toSeq.sortBy(_._1).toImmArray))
.toLeft(new SortedLookupList(entries.toSeq.sortBy(_._1)(Utf8.Ordering).toImmArray))
}
def fromSortedImmArray[X](entries: ImmArray[(String, X)]): Either[String, SortedLookupList[X]] = {
@ -58,7 +56,7 @@ object SortedLookupList {
.toSeq
.sliding(2)
.collectFirst {
case Seq(k1, k2) if keyOrdering.gteq(k1, k2) => s"the list $entries is not sorted by key"
case Seq(k1, k2) if Utf8.Ordering.gteq(k1, k2) => s"the list $entries is not sorted by key"
}
.toLeft(new SortedLookupList(entries))
}

View File

@ -23,6 +23,8 @@ object Time {
object Date {
type T = Date
private def apply(days: Int): Date =
new Date(days)
@ -59,13 +61,13 @@ object Time {
.map(_ => s"cannot interpret $str as Date")
.flatMap(fromDaysSinceEpoch)
@throws[IllegalArgumentException]
final def assertFromString(s: String): T =
assert(fromString(s))
def assertFromDaysSinceEpoch(days: Int): Date =
assert(fromDaysSinceEpoch(days))
@throws[IllegalArgumentException]
def assertFromString(str: String): Date =
assert(fromString(str))
}
case class Timestamp private (micros: Long) extends Ordered[Timestamp] {
@ -89,6 +91,8 @@ object Time {
object Timestamp {
type T = Timestamp
private def apply(micros: Long): Timestamp =
new Timestamp(micros)
@ -129,8 +133,8 @@ object Time {
.flatMap(fromLong)
@throws[IllegalArgumentException]
def assertFromString(str: String): Timestamp =
assertFromLong(assertMicrosFromString(str))
final def assertFromString(s: String): T =
assert(fromString(s))
def fromInstant(i: Instant): Either[String, Timestamp] =
Try(assertMicrosFromInstant(i)).toEither.left
@ -142,10 +146,4 @@ object Time {
}
private def assert[X](e: Either[String, X]): X =
e match {
case Left(err) => throw new IllegalArgumentException(err)
case Right(date) => date
}
}

View File

@ -1,70 +0,0 @@
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.daml.lf.data
import java.nio.charset.StandardCharsets
import scala.annotation.tailrec
import scala.collection.mutable
// This object defines functions to emulates UTF8 string
// behavior while still using underlying UTF-16 encoding.
object UTF8 {
// The DAML-LF strings are supposed to be UTF-8.
// However standard "exploding" java/scala methods like
// _.toList split in Character which are not Unicode codepoint.
def explode(s: String): ImmArray[String] = {
val len = s.length
val arr = new mutable.ArraySeq[String](s.codePointCount(0, len))
var i = 0
var j = 0
while (i < len) {
// if s(i) is a high surrogate the current codepoint uses 2 chars
val next = if (s(i).isHighSurrogate) i + 2 else i + 1
arr(j) = s.substring(i, next)
j += 1
i = next
}
ImmArray.unsafeFromArraySeq(arr)
}
// The DAML-LF should sort string according UTF-8 encoding.
// Java standard string ordering uses UTF-16, which does not match
// expected one. Note that unlike UTF-16, UTF-8 ordering matches
// Unicode ordering, so we can order by codepoints.
//
// For instance consider the two following unicode code points:
//
// (Unicode 0x00ff61, UTF-8 [0xef, 0xbd, 0xa1], , UTF-16 [0xff61])
// 😂 (Unicode 0x01f602, UTF-8 [0xf0, 0x9f, 0x98, 0x82], UTF-16 [0xd83d, 0xde02])
//
// The comparison "。" < "😂" returns false in java/scala, but it
// should return true. Note it returns True in Haskell.
//
// See https://ssl.icu-project.org/docs/papers/utf16_code_point_order.html
// for more explanations.
val ordering: Ordering[String] = new Ordering[String] {
override def compare(xs: String, ys: String): Int = {
val lim = xs.length min ys.length
@tailrec def lp(i: Int): Int =
if (i < lim) {
val x = xs(i)
val y = ys(i)
if (x != y) {
// If x is a low surrogate, then the current codepoint starts at the
// previous char, otherwise the codepoint starts at the current char.
val j = if (x.isLowSurrogate) i - 1 else i
xs.codePointAt(j) - ys.codePointAt(j)
} else lp(i + 1)
} else xs.length - ys.length
lp(0)
}
}
def getBytes(s: String): Array[Byte] =
s.getBytes(StandardCharsets.UTF_8)
}

View File

@ -0,0 +1,65 @@
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.daml.lf.data
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import scala.annotation.tailrec
// The DAML-LF strings are supposed to be UTF-8 while standard java strings are UTF16
// Note number of UTF16 operations are not Utf8 equivalent (for instance length, charAt, ordering ...)
// This module provide UTF8 emulation functions.
object Utf8 {
// The DAML-LF strings are supposed to be UTF-8.
// However standard "exploding" java/scala methods like
// _.toList split in Character which are not Unicode codepoint.
def explode(s: String): ImmArray[String] = {
val len = s.length
val arr = ImmArray.newBuilder[String]
var i = 0
var j = 0
while (i < len) {
// if s(i) is a high surrogate the current codepoint uses 2 chars
val next = if (s(i).isHighSurrogate) i + 2 else i + 1
arr += s.substring(i, next)
j += 1
i = next
}
arr.result()
}
def getBytes(s: String): Array[Byte] =
s.getBytes(StandardCharsets.UTF_8)
def sha256(s: String): String = {
val digest = MessageDigest.getInstance("SHA-256")
val array = digest.digest(getBytes(s))
array.map("%02x" format _).mkString
}
def implode(ts: ImmArray[String]): String =
ts.toSeq.mkString
val Ordering: Ordering[String] = (xs: String, ys: String) => {
val lim = xs.length min ys.length
@tailrec
def lp(i: Int): Int =
if (i < lim) {
val x = xs(i)
val y = ys(i)
if (x != y) {
// If x is a low surrogate, then the current codepoint starts at the
// previous char, otherwise the codepoint starts at the current char.
val j = if (x.isLowSurrogate) i - 1 else i
xs.codePointAt(j) - ys.codePointAt(j)
} else lp(i + 1)
} else xs.length - ys.length
lp(0)
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.daml.lf
import scala.math.BigDecimal
package object data {
val Decimal: DecimalModule = new DecimalModule {
type T = BigDecimal
protected def cast(x: BigDecimal): T = x
}
type Decimal = Decimal.T
private[data] def assert[X](either: Either[String, X]): X =
either.fold(e => throw new IllegalArgumentException(e), identity)
}

View File

@ -3,7 +3,7 @@
package com.digitalasset.daml.lf.data
import org.scalatest.prop.PropertyChecks
import org.scalatest.prop.{PropertyChecks}
import org.scalatest.{Matchers, WordSpec}
class SortedLookupListSpec extends WordSpec with Matchers with PropertyChecks {
@ -15,7 +15,8 @@ class SortedLookupListSpec extends WordSpec with Matchers with PropertyChecks {
ImmArray.empty[(String, Int)],
ImmArray("1" -> 1),
ImmArray("1" -> 1, "2" -> 2, "3" -> 3),
ImmArray("2" -> 2, "3" -> 3, "1" -> 1))
ImmArray("2" -> 2, "3" -> 3, "1" -> 1)
)
val positiveTestCases =
Table("list", ImmArray("1" -> 1, "1" -> 2), ImmArray("1" -> 1, "2" -> 2, "3" -> 3, "1" -> 2))
@ -39,7 +40,8 @@ class SortedLookupListSpec extends WordSpec with Matchers with PropertyChecks {
"list",
ImmArray("1" -> 1, "1" -> 2),
ImmArray("1" -> 1, "2" -> 2, "3" -> 3, "1" -> 2),
ImmArray("2" -> 2, "3" -> 3, "1" -> 1))
ImmArray("2" -> 2, "3" -> 3, "1" -> 1)
)
forAll(negativeTestCases)(l => SortedLookupList.fromSortedImmArray(l) shouldBe 'right)

View File

@ -12,7 +12,7 @@ import scala.collection.JavaConverters._
import scala.util.Random
@SuppressWarnings(Array("org.wartremover.warts.Any"))
class UTF8Spec extends WordSpec with Matchers {
class Utf8Spec extends WordSpec with Matchers {
private def codepointToString(cp: Int): String =
Character.toChars(cp).mkString
@ -47,7 +47,7 @@ class UTF8Spec extends WordSpec with Matchers {
"explode properly counter example" in {
"a¶‱😂".toList shouldNot be(List("a", "¶", "‱", "😂"))
UTF8.explode("a¶‱😂") shouldBe ImmArray("a", "¶", "‱", "😂")
Utf8.explode("a¶‱😂") shouldBe ImmArray("a", "¶", "‱", "😂")
}
"explode in a same way a naive implementation" in {
@ -55,7 +55,7 @@ class UTF8Spec extends WordSpec with Matchers {
ImmArray(s.codePoints().iterator().asScala.map(codepointToString(_)).toIterable)
forAll(strings) { s =>
naiveExplode(s) == UTF8.explode(s)
naiveExplode(s) == Utf8.explode(s)
}
}
@ -63,33 +63,33 @@ class UTF8Spec extends WordSpec with Matchers {
"Unicode.Ordering" should {
"do not have basic UTF16 ordering issue" in {
"do not have basic Utf16 ordering issue" in {
val s1 = "。"
val s2 = "😂"
val List(cp1) = s1.codePoints().iterator().asScala.toList
val List(cp2) = s2.codePoints().iterator().asScala.toList
Ordering.String.lt(s1, s2) shouldNot be(Ordering.Int.lt(cp1, cp2))
UTF8.ordering.lt(s1, s2) shouldBe Ordering.Int.lt(cp1, cp2)
Utf8.Ordering.lt(s1, s2) shouldBe Ordering.Int.lt(cp1, cp2)
}
"be reflexive" in {
forAll(strings) { x =>
UTF8.ordering.compare(x, x) == 0
Utf8.Ordering.compare(x, x) == 0
}
}
"consistent when flipping its arguments" in {
forAll(strings, strings) { (x, y) =>
UTF8.ordering.compare(x, y).signum == -UTF8.ordering.compare(y, x).signum
Utf8.Ordering.compare(x, y).signum == -Utf8.Ordering.compare(y, x).signum
}
}
"be transitive" in {
import UTF8.ordering.lteq
import Utf8.Ordering.lteq
forAll(strings, strings, strings) { (x_, y_, z_) =>
val List(x, y, z) = List(x_, y_, z_).sorted(UTF8.ordering)
val List(x, y, z) = List(x_, y_, z_).sorted(Utf8.Ordering)
lteq(x, y) && lteq(y, z) && lteq(x, z)
}
}
@ -98,11 +98,11 @@ class UTF8Spec extends WordSpec with Matchers {
val shuffledCodepoints = new Random(0).shuffle(validCodepoints)
// Sort according UTF16
// Sort according Utf16
val negativeCase = shuffledCodepoints.sorted
// Sort according our ad hoc ordering
val positiveCase = shuffledCodepoints.sorted(UTF8.ordering)
val positiveCase = shuffledCodepoints.sorted(Utf8.Ordering)
negativeCase shouldNot be(validCodepoints)
positiveCase shouldBe validCodepoints
@ -116,14 +116,14 @@ class UTF8Spec extends WordSpec with Matchers {
Ordering.by((s: String) => s.codePoints().toArray.toIterable)
forAll(Gen.listOfN(20, strings)) { list =>
list.sorted(naiveOrdering) == list.sorted(UTF8.ordering)
list.sorted(naiveOrdering) == list.sorted(Utf8.Ordering)
}
}
"be strict on individual codepoints" in {
(validCodepoints zip validCodepoints.tail).foreach {
case (x, y) => UTF8.ordering.compare(x, y) should be < 0
case (x, y) => Utf8.Ordering.compare(x, y) should be < 0
}
}

View File

@ -8,7 +8,7 @@ import java.io.File
import com.digitalasset.daml.bazeltools.BazelRunfiles
import com.digitalasset.daml.lf.data.Ref._
import com.digitalasset.daml.lf.data.{FrontStack, ImmArray, Ref, Time}
import com.digitalasset.daml.lf.data._
import com.digitalasset.daml.lf.lfpackage.Ast._
import com.digitalasset.daml.lf.lfpackage.Decode
import com.digitalasset.daml.lf.lfpackage.Util._

View File

@ -3,7 +3,6 @@
package com.digitalasset.daml.lf.speedy
import java.security.MessageDigest
import java.util
import com.digitalasset.daml.lf.data.Ref._
@ -156,7 +155,7 @@ object SBuiltin {
machine.ctrl = CtrlValue(
args.get(0) match {
case SText(t) =>
SList(FrontStack(UTF8.explode(t).map(SText)))
SList(FrontStack(Utf8.explode(t).map(SText)))
case _ =>
throw SErrorCrash(s"type mismatch explodeText: $args")
}
@ -174,7 +173,7 @@ object SBuiltin {
case v =>
throw SErrorCrash(s"type mismatch implodeText: expected SText, got $v")
}
SText(ts.iterator.mkString)
SText(Utf8.implode(ts.toImmArray))
case _ =>
throw SErrorCrash(s"type mismatch implodeText: $args")
}
@ -187,7 +186,7 @@ object SBuiltin {
machine.ctrl = CtrlValue(
(args.get(0), args.get(1)) match {
case (SText(head), SText(tail)) =>
SText(head ++ tail)
SText(head + tail)
case _ =>
throw SErrorCrash(s"type mismatch appendText: $args")
}
@ -237,17 +236,11 @@ object SBuiltin {
final case object SBSHA256Text extends SBuiltin(1) {
def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
machine.ctrl = CtrlValue(args.get(0) match {
case SText(t) => SText(hash(t))
case SText(t) => SText(Utf8.sha256(t))
case _ =>
throw SErrorCrash(s"type mismatch textSHA256: $args")
})
}
private def hash(t: String): String = {
val digest = MessageDigest.getInstance("SHA-256")
val array = digest.digest(UTF8.getBytes(t))
array.map("%02x" format _).mkString
}
}
final case object SBMapEmpty extends SBuiltin(0) {
@ -308,6 +301,8 @@ object SBuiltin {
final case object SBMapToList extends SBuiltin(1) {
// implicit val classTag: ClassTag[Ref.Name.T] = Ref.Name.classTag
private val entryFields =
Name.Array(Ast.keyFieldName, Ast.valueFieldName)
@ -437,7 +432,7 @@ object SBuiltin {
case (SInt64(a), SInt64(b)) => a < b
case (SDecimal(a), SDecimal(b)) => a < b
case (STimestamp(a), STimestamp(b)) => a < b
case (SText(a), SText(b)) => UTF8.ordering.lt(a, b)
case (SText(a), SText(b)) => Utf8.Ordering.lt(a, b)
case (SDate(a), SDate(b)) => a < b
case (SParty(a), SParty(b)) => a < b
case _ =>
@ -452,7 +447,7 @@ object SBuiltin {
case (SInt64(a), SInt64(b)) => a <= b
case (SDecimal(a), SDecimal(b)) => a <= b
case (STimestamp(a), STimestamp(b)) => a <= b
case (SText(a), SText(b)) => UTF8.ordering.lteq(a, b)
case (SText(a), SText(b)) => Utf8.Ordering.lteq(a, b)
case (SDate(a), SDate(b)) => a <= b
case (SParty(a), SParty(b)) => a <= b
case _ =>
@ -467,7 +462,7 @@ object SBuiltin {
case (SInt64(a), SInt64(b)) => a > b
case (SDecimal(a), SDecimal(b)) => a > b
case (STimestamp(a), STimestamp(b)) => a > b
case (SText(a), SText(b)) => UTF8.ordering.gt(a, b)
case (SText(a), SText(b)) => Utf8.Ordering.gt(a, b)
case (SDate(a), SDate(b)) => a > b
case (SParty(a), SParty(b)) => a > b
case _ =>
@ -482,7 +477,7 @@ object SBuiltin {
case (SInt64(a), SInt64(b)) => a >= b
case (SDecimal(a), SDecimal(b)) => a >= b
case (STimestamp(a), STimestamp(b)) => a >= b
case (SText(a), SText(b)) => UTF8.ordering.gteq(a, b)
case (SText(a), SText(b)) => Utf8.Ordering.gteq(a, b)
case (SDate(a), SDate(b)) => a >= b
case (SParty(a), SParty(b)) => a >= b
case _ =>
@ -1111,9 +1106,8 @@ object SBuiltin {
/** $error :: Text -> a */
final case object SBError extends SBuiltin(1) {
def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
def execute(args: util.ArrayList[SValue], machine: Machine): Unit =
throw DamlEUserError(args.get(0).asInstanceOf[SText].value)
}
}
// Helpers

View File

@ -5,9 +5,8 @@ package com.digitalasset.daml.lf.speedy
import java.util
import com.digitalasset.daml.lf.data.Decimal.Decimal
import com.digitalasset.daml.lf.data._
import com.digitalasset.daml.lf.data.Ref._
import com.digitalasset.daml.lf.data.{FrontStack, ImmArray, SortedLookupList, Time}
import com.digitalasset.daml.lf.lfpackage.Ast._
import com.digitalasset.daml.lf.speedy.SError.SErrorCrash
import com.digitalasset.daml.lf.value.{Value => V}

View File

@ -3,7 +3,7 @@
package com.digitalasset.daml.lf.interp.testing
import com.digitalasset.daml.lf.data.{Decimal}
import com.digitalasset.daml.lf.data.Decimal
import com.digitalasset.daml.lf.speedy.{SBuiltin, SValue}
import com.digitalasset.daml.lf.speedy.SValue._
import org.scalatest.{Matchers, WordSpec}
@ -18,8 +18,8 @@ class ToTextTest extends WordSpec with Matchers {
"toString" should {
"Decimal" in {
litToText(SDecimal(Decimal.fromString("123.4560000").toOption.get)) shouldBe "123.456"
litToText(SDecimal(Decimal.fromString("123.0000000").toOption.get)) shouldBe "123.0"
litToText(SDecimal(Decimal.fromString("123.4560000").toOption.get)) shouldBe ("123.456")
litToText(SDecimal(Decimal.fromString("123.0000000").toOption.get)) shouldBe ("123.0")
}
"Text" in {

View File

@ -6,7 +6,7 @@ package com.digitalasset.daml.lf.speedy
import java.util
import com.digitalasset.daml.lf.PureCompiledPackages
import com.digitalasset.daml.lf.data.{Decimal, FrontStack, Ref, Time}
import com.digitalasset.daml.lf.data._
import com.digitalasset.daml.lf.lfpackage.Ast._
import com.digitalasset.daml.lf.speedy.SError.SError
import com.digitalasset.daml.lf.speedy.SResult.{SResultContinue, SResultError}
@ -16,6 +16,7 @@ import org.scalatest.prop.TableDrivenPropertyChecks
import org.scalatest.{FreeSpec, Matchers}
import scala.collection.immutable.HashMap
import scala.language.implicitConversions
@SuppressWarnings(Array("org.wartremover.warts.Any"))
class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks {
@ -209,9 +210,9 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
"MUL_DECIMAL" - {
"throws exception in case of overflow" in {
eval(e"MUL_DECIMAL 1.1 2.2") shouldBe Right(SDecimal(2.42))
eval(e"MUL_DECIMAL 1.1 2.2") shouldBe Right(SDecimal(decimal(2.42)))
eval(e"MUL_DECIMAL $bigBigDecimal $bigBigDecimal") shouldBe 'left
eval(e"MUL_DECIMAL ${1E13} ${1E14}") shouldBe Right(SDecimal(1E27))
eval(e"MUL_DECIMAL ${1E13} ${1E14}") shouldBe Right(SDecimal(decimal(1E27)))
eval(e"MUL_DECIMAL ${1E14} ${1E14}") shouldBe 'left
}
}
@ -237,23 +238,22 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
(10, d, d)
)
forEvery(testCases) { (rounding, decimal, result) =>
eval(e"ROUND_DECIMAL $rounding $decimal") shouldBe Right(SDecimal(BigDecimal(result)))
forEvery(testCases) { (rounding, input, result) =>
eval(e"ROUND_DECIMAL $rounding $input") shouldBe Right(SDecimal(BigDecimal(result)))
}
}
}
"Decimal binary operations computes proper results" in {
"Decimal binary operations compute proper results" in {
val testCases = Table[String, (BigDecimal, BigDecimal) => Either[Any, SValue]](
def round(x: BigDecimal) = x.setScale(10, BigDecimal.RoundingMode.HALF_EVEN)
val testCases = Table[String, (Decimal, Decimal) => Either[Any, SValue]](
("builtin", "reference"),
("ADD_DECIMAL", (a, b) => Decimal.checkWithinBoundsAndRound(a + b).map(SDecimal)),
("SUB_DECIMAL", (a, b) => Decimal.checkWithinBoundsAndRound(a - b).map(SDecimal)),
("MUL_DECIMAL", (a, b) => Decimal.checkWithinBoundsAndRound(a * b).map(SDecimal)),
(
"DIV_DECIMAL",
(a, b) =>
if (b == 0) Left(()) else Decimal.checkWithinBoundsAndRound(a / b).map(SDecimal)),
("ADD_DECIMAL", (a, b) => Right(SDecimal(a + b))),
("SUB_DECIMAL", (a, b) => Right(SDecimal(a - b))),
("MUL_DECIMAL", (a, b) => Right(SDecimal(round(a * b)))),
("DIV_DECIMAL", (a, b) => Either.cond(b != 0, SDecimal(round(a / b)), ())),
("LESS_EQ_DECIMAL", (a, b) => Right(SBool(a <= b))),
("GREATER_EQ_DECIMAL", (a, b) => Right(SBool(a >= b))),
("LESS_DECIMAL", (a, b) => Right(SBool(a < b))),
@ -264,7 +264,8 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
forEvery(testCases) { (builtin, ref) =>
forEvery(decimals) { a =>
forEvery(decimals) { b =>
eval(e"$builtin $a $b").left.map(_ => ()) shouldBe ref(BigDecimal(a), BigDecimal(b))
eval(e"$builtin $a $b").left
.map(_ => ()) shouldBe ref(decimal(BigDecimal(a)), decimal(BigDecimal(b)))
}
}
}
@ -650,7 +651,7 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
val testCases = Table[Long]("Int64", 167, 11, 2, 1, 0, -1, -2, -13, -113)
forEvery(testCases) { int64 =>
eval(e"INT64_TO_DECIMAL $int64") shouldBe Right(SDecimal(int64))
eval(e"INT64_TO_DECIMAL $int64") shouldBe Right(SDecimal(decimal(int64)))
}
}
}
@ -761,7 +762,7 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
val testCases = Table[String, SValue](
"expression" -> "result",
"1" -> SInt64(1),
"1.0" -> SDecimal(1),
"1.0" -> SDecimal(decimal(1)),
"True" -> SBool(true),
"()" -> SUnit(()),
""" "text" """ -> SText("text"),
@ -827,4 +828,7 @@ object SBuiltinTest {
args.add(v)
STuple(entryFields, args)
}
private implicit def decimal(x: BigDecimal): Decimal = Decimal.assertFromBigDecimal(x)
}

View File

@ -5,9 +5,8 @@ package com.digitalasset.daml.lf.lfpackage
import com.digitalasset.daml.lf.archive.LanguageVersion
import com.digitalasset.daml.lf.archive.Reader.ParseError
import com.digitalasset.daml.lf.data.Decimal.Decimal
import com.digitalasset.daml.lf.data.Ref._
import com.digitalasset.daml.lf.data.{ImmArray, Time}
import com.digitalasset.daml.lf.data.{Decimal, ImmArray, Time}
object Ast {
//
@ -269,6 +268,7 @@ object Ast {
final case class PLInt64(override val value: Long) extends PrimLit
final case class PLDecimal(override val value: Decimal) extends PrimLit
// Text should be treated as Utf8, data.Utf8 provide emulation functions for that
final case class PLText(override val value: String) extends PrimLit
final case class PLTimestamp(override val value: Time.Timestamp) extends PrimLit
final case class PLParty(override val value: Party) extends PrimLit

View File

@ -6,7 +6,6 @@ package com.digitalasset.daml.lf.lfpackage
import com.digitalasset.daml.lf.archive.LanguageVersion
import com.digitalasset.daml.lf.data.ImmArray
import com.digitalasset.daml.lf.data.Ref.{ChoiceName, DottedName, Name}
import com.digitalasset.daml.lf.data.Ref.Name.{assertFromString => id}
import com.digitalasset.daml.lf.lfpackage.Ast._
import com.digitalasset.daml.lf.lfpackage.Decode.ParseError
import org.scalatest.prop.TableDrivenPropertyChecks
@ -37,7 +36,7 @@ class AstSpec extends WordSpec with TableDrivenPropertyChecks with Matchers {
"Module.apply" should {
val template = Template(
param = id("x"),
param = Name.assertFromString("x"),
precond = eTrue,
signatories = eParties,
agreementText = eText,
@ -154,7 +153,7 @@ class AstSpec extends WordSpec with TableDrivenPropertyChecks with Matchers {
name = name,
consuming = true,
controllers = eParties,
selfBinder = id("self"),
selfBinder = Name.assertFromString("self"),
argBinder = (None, tUnit),
returnType = tUnit,
update = EUpdate(UpdatePure(typ, expr)),
@ -166,7 +165,7 @@ class AstSpec extends WordSpec with TableDrivenPropertyChecks with Matchers {
"catch choice name collisions" in {
Template(
param = id("x"),
param = Name.assertFromString("x"),
precond = eTrue,
signatories = eParties,
agreementText = eText,
@ -181,7 +180,7 @@ class AstSpec extends WordSpec with TableDrivenPropertyChecks with Matchers {
an[ParseError] shouldBe thrownBy(
Template(
param = id("x"),
param = Name.assertFromString("x"),
precond = eTrue,
signatories = eParties,
agreementText = eText,

View File

@ -26,11 +26,14 @@ object Implicits {
Parsers.parseAll(Parsers.phrase(p), sc.standardInterpolator(identity, args.map(prettyPrint)))
}
private def toString(x: BigDecimal) =
Decimal.toString(Decimal.assertFromBigDecimal(x))
private def prettyPrint(x: Any): String =
x match {
case d: BigDecimal => Decimal.toString(d.bigDecimal)
case d: Float => Decimal.toString(d.toDouble)
case d: Double => Decimal.toString(d)
case d: BigDecimal => toString(d)
case d: Float => toString(d.toDouble)
case d: Double => toString(d)
case other: Any => other.toString
}
}

View File

@ -55,7 +55,7 @@ private[parser] object Token {
final case class ContractId(s: String) extends Token
final case class Timestamp(value: data.Time.Timestamp) extends Token
final case class Date(value: data.Time.Date) extends Token
final case class Decimal(value: data.Decimal.Decimal) extends Token
final case class Decimal(value: data.Decimal) extends Token
final case class Number(value: Long) extends Token
final case class SimpleString(s: String) extends Token
final case class Text(s: String) extends Token

View File

@ -4,8 +4,7 @@
package com.digitalasset.daml.lf.testing.parser
import com.digitalasset.daml.lf.data.Ref._
import com.digitalasset.daml.lf.data.Time
import com.digitalasset.daml.lf.data.ImmArray
import com.digitalasset.daml.lf.data.{Decimal, ImmArray, Time}
import com.digitalasset.daml.lf.lfpackage.Ast._
import com.digitalasset.daml.lf.testing.parser.Implicits._
import org.scalatest.prop.TableDrivenPropertyChecks
@ -116,9 +115,9 @@ class ParsersSpec extends WordSpec with TableDrivenPropertyChecks with Matchers
"string to parse" -> "expected literal",
"1" -> PLInt64(1),
"-2" -> PLInt64(-2),
"1.0" -> PLDecimal(1),
"1.0" -> PLDecimal(1),
"-1.0" -> PLDecimal(-1),
"1.0" -> PLDecimal(Decimal.assertFromBigDecimal(1)),
"1.0" -> PLDecimal(Decimal.assertFromBigDecimal(1)),
"-1.0" -> PLDecimal(Decimal.assertFromBigDecimal(-1)),
""""some text"""" -> PLText("some text"),
""" " \n\r\"\\ " """ -> PLText(" \n\r\"\\ "),
""" "français" """ -> PLText("français"),

View File

@ -3,7 +3,7 @@
package com.digitalasset.daml.lf.speedy
import com.digitalasset.daml.lf.data.{FrontStack, ImmArray, Ref}
import com.digitalasset.daml.lf.data._
import com.digitalasset.daml.lf.data.Ref._
import com.digitalasset.daml.lf.lfpackage.Ast._
import com.digitalasset.daml.lf.lfpackage.Decode
@ -51,13 +51,12 @@ object Main extends App {
}
def defaultCommand(possibleFile: String): Unit = {
def defaultCommand(possibleFile: String): Unit =
if (!Paths.get(possibleFile).toFile.isFile) {
usage()
System.exit(1)
} else
Repl.repl(possibleFile)
}
if (args.isEmpty) {
usage()
@ -556,7 +555,7 @@ object Repl {
QualifiedName.assertFromString("Dummy:Dummy"))
def pDecimal: Parser[Value[Nothing]] = """\d+\.\d+""".r ^^ { s =>
ValueDecimal(BigDecimal(s))
ValueDecimal(Decimal.assertFromString(s))
}
def pInt64: Parser[Value[Nothing]] = """\d+""".r ^^ { s =>
ValueInt64(s.toLong)

View File

@ -33,6 +33,7 @@ da_scala_test_suite(
scalacopts = lf_scalacopts,
deps = [
":scenario-interpreter",
"//3rdparty/jvm/org/scalaz:scalaz_core",
"//daml-lf/data",
"//daml-lf/interpreter",
"//daml-lf/lfpackage",

View File

@ -14,9 +14,9 @@ class ScenarioRunnerTest extends AsyncWordSpec with Matchers with ScalaFutures {
"ScenarioRunner" can {
"mangle party names correctly" in {
val e = Ast.EScenario(ScenarioGetParty(Ast.EPrimLit(Ast.PLText("foo-bar"))))
val e = Ast.EScenario(ScenarioGetParty(Ast.EPrimLit(Ast.PLText(("foo-bar")))))
val m = Speedy.Machine.fromExpr(e, PureCompiledPackages(Map.empty).right.get, true)
val sr = ScenarioRunner(m, (s) => s + "-XXX")
val sr = ScenarioRunner(m, _ + "-XXX")
sr.run()
m.ctrl shouldBe Speedy.CtrlValue(SValue.SParty(Ref.Party.assertFromString("foo-bar-XXX")))
}

View File

@ -7,7 +7,7 @@ import com.digitalasset.daml.lf.PureCompiledPackages
import com.digitalasset.daml.lf.command._
import com.digitalasset.daml.lf.data.Ref.{PackageId, Party, QualifiedName}
import com.digitalasset.daml.lf.data.Relation.Relation
import com.digitalasset.daml.lf.data.{FrontStack, FrontStackCons, ImmArray, Time}
import com.digitalasset.daml.lf.data._
import com.digitalasset.daml.lf.engine.Event.Events
import com.digitalasset.daml.lf.engine._
import com.digitalasset.daml.lf.lfpackage.Ast._

View File

@ -95,7 +95,7 @@ object ValueGenerators {
(1, Gen.const(Decimal.min)),
(5, bd)
)
.map(d => ValueDecimal(d))
.map(d => ValueDecimal(Decimal.assertFromBigDecimal(d)))
}
val moduleSegmentGen: Gen[String] = for {
@ -191,8 +191,9 @@ object ValueGenerators {
private def valueMapGen(nesting: Int) =
for {
list <- Gen.listOf(
for { k <- Gen.asciiPrintableStr; v <- Gen.lzy(valueGen(nesting)) } yield k -> v)
list <- Gen.listOf(for {
k <- Gen.asciiPrintableStr; v <- Gen.lzy(valueGen(nesting))
} yield k -> v)
} yield ValueMap[ContractId](SortedLookupList(Map(list: _*)))
def valueMapGen: Gen[ValueMap[ContractId]] = valueMapGen(0)
@ -220,10 +221,10 @@ object ValueGenerators {
)
val flat = List(
(sz + 1, dateGen.map(ValueDate)),
(sz + 1, Gen.alphaStr.map(ValueText)),
(sz + 1, Gen.alphaStr.map(x => ValueText(x))),
(sz + 1, decimalGen),
(sz + 1, Arbitrary.arbLong.arbitrary.map(ValueInt64)),
(sz + 1, Gen.alphaStr.map(ValueText)),
(sz + 1, Gen.alphaStr.map(x => ValueText(x))),
(sz + 1, timestampGen.map(ValueTimestamp)),
(sz + 1, coidValueGen),
(sz + 1, party.map(ValueParty)),

View File

@ -63,7 +63,7 @@ object TransactionCoder {
: Either[DecodeError, ContractInst[Val]] = {
ValueCoder.decodeIdentifier(protoCoinst.getTemplateId).flatMap { id =>
decodeVal(protoCoinst.getValue)
.map(a => ContractInst(id, a, protoCoinst.getAgreement))
.map(a => ContractInst(id, a, (protoCoinst.getAgreement)))
}
}

View File

@ -181,7 +181,8 @@ object Value {
*/
final case class ValueList[+Cid](values: FrontStack[Value[Cid]]) extends Value[Cid]
final case class ValueInt64(value: Long) extends Value[Nothing]
final case class ValueDecimal(value: BigDecimal) extends Value[Nothing]
final case class ValueDecimal(value: Decimal) extends Value[Nothing]
// Note that Text are assume to be UTF8
final case class ValueText(value: String) extends Value[Nothing]
final case class ValueTimestamp(value: Time.Timestamp) extends Value[Nothing]
final case class ValueDate(value: Time.Date) extends Value[Nothing]

View File

@ -358,7 +358,7 @@ object ValueCoder {
case proto.Value.SumCase.MAP =>
val entries = ImmArray(protoValue.getMap.getEntriesList.asScala.map(entry =>
entry.getKey -> go(newNesting, entry.getValue)))
(entry.getKey) -> go(newNesting, entry.getValue)))
val map = SortedLookupList
.fromImmArray(entries)

View File

@ -4,7 +4,7 @@
package com.digitalasset.daml.lf.transaction
import com.digitalasset.daml.lf.EitherAssertions
import com.digitalasset.daml.lf.data.ImmArray
import com.digitalasset.daml.lf.data.{ImmArray}
import com.digitalasset.daml.lf.data.Ref.{Identifier, PackageId, Party, QualifiedName}
import com.digitalasset.daml.lf.transaction.Node.{GenNode, NodeCreate, NodeExercises, NodeFetch}
import com.digitalasset.daml.lf.transaction.{Transaction => Tx, TransactionOuterClass => proto}
@ -244,7 +244,7 @@ class TransactionCoderSpec
VersionedValue(
ValueVersions.acceptedVersions.last,
ValueParty(Party.assertFromString("francesco"))),
"agreement"
("agreement")
),
None,
Set(Party.assertFromString("alice")),

View File

@ -145,7 +145,8 @@ object TransactionSpec {
PackageId.assertFromString("-dummyPkg-"),
QualifiedName.assertFromString("DummyModule:dummyName")),
V.ValueUnit,
"dummyAgreement"),
("dummyAgreement")
),
None,
Set.empty,
Set.empty,

View File

@ -5,7 +5,7 @@ package com.digitalasset.daml.lf.value
import com.digitalasset.daml.lf.EitherAssertions
import com.digitalasset.daml.lf.data.Ref.Party
import com.digitalasset.daml.lf.data.{Decimal, ImmArray, Ref, Time}
import com.digitalasset.daml.lf.data._
import com.digitalasset.daml.lf.value.Value._
import com.digitalasset.daml.lf.value.ValueCoder.DecodeError
import com.digitalasset.daml.lf.value.{ValueOuterClass => proto}
@ -45,8 +45,8 @@ class ValueCoderSpec extends WordSpec with Matchers with EitherAssertions with P
"do Decimal" in {
forAll("Decimal (BigDecimal) invariant") { d: BigDecimal =>
// we are filtering on decimals invariant under string conversion
whenever(Decimal.fromString(Decimal.toString(d)).isRight) {
val Right(dec) = Decimal.fromString(Decimal.toString(d))
whenever(Decimal.fromBigDecimal(d).isRight) {
val Right(dec) = Decimal.fromBigDecimal(d)
val value = ValueDecimal(dec)
val recoveredDecimal = ValueCoder.decodeValue[ContractId](
defaultCidDecode,

View File

@ -67,7 +67,9 @@ object JsonConverters {
implicit val mapEncoder: Encoder[SortedLookupList[LedgerValue]] = m =>
JsonObject(
"Map" ->
JsonObject.fromIterable(m.mapValue(_.asJson).toImmArray.toSeq).asJson).asJson
JsonObject
.fromIterable(m.toImmArray.map { case (k, v) => k -> v.asJson }.toSeq)
.asJson).asJson
implicit val idKeyEncoder: KeyEncoder[Identifier] = id => s"${id.packageId}@${id.name}"
implicit val idKeyDecoder: KeyDecoder[Identifier] = {

View File

@ -8,7 +8,7 @@ import api.value.Value.Sum
import RecordField._
import scalaz.{Optional => _, _}
import Scalaz._
import com.digitalasset.daml.lf.data.{SortedLookupList, ImmArray}
import com.digitalasset.daml.lf.data.{ImmArray, SortedLookupList}
sealed trait LedgerValue

View File

@ -90,7 +90,8 @@ object LfEngineToApi {
value0: LfValue[Lf.AbsoluteContractId]): Either[String, ApiValue] =
value0 match {
case Lf.ValueUnit => Right(ApiValue(ApiValue.Sum.Unit(Empty())))
case Lf.ValueDecimal(d) => Right(ApiValue(ApiValue.Sum.Decimal(Decimal.toString(d))))
case Lf.ValueDecimal(d) =>
Right(ApiValue(ApiValue.Sum.Decimal(Decimal.toString(d))))
case Lf.ValueContractId(c) => Right(ApiValue(ApiValue.Sum.ContractId(c.coid)))
case Lf.ValueBool(b) => Right(ApiValue(ApiValue.Sum.Bool(b)))
case Lf.ValueDate(d) => Right(ApiValue(ApiValue.Sum.Date(d.days)))

View File

@ -3,7 +3,8 @@
package com.digitalasset.ledger.api
import com.digitalasset.daml.lf.data.{Time, UTF8}
import com.digitalasset.daml.lf.data.Time
import com.digitalasset.daml.lf.testing.parser.Implicits._
import com.digitalasset.ledger.api.v1.value.Value.Sum
import com.digitalasset.ledger.api.v1.value.{
List => ApiList,
@ -20,7 +21,6 @@ import com.digitalasset.platform.server.api.validation.IdentifierResolver
import com.google.protobuf.empty.Empty
import org.scalatest.WordSpec
import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor1}
import com.digitalasset.daml.lf.testing.parser.Implicits._
import scala.concurrent.Future
@ -115,7 +115,7 @@ class ValueConversionRoundTripTest
val entries = List("‱", "1", "😂", "😃", "a").zipWithIndex.map {
case (k, v) => ApiMap.Entry(k, Some(Value(Sum.Int64(v.toLong))))
}
val sortedEntries = entries.sortBy(_.key)(UTF8.ordering)
val sortedEntries = entries.sortBy(_.key)
// just to be sure we did not write the entries sorted
assert(entries != sortedEntries)

View File

@ -3,7 +3,6 @@
package com.digitalasset.ledger.api.validation
import java.security.MessageDigest
import java.time.Instant
import com.digitalasset.api.util.TimestampConversion
@ -485,9 +484,11 @@ class CommandSubmissionRequestValidatorTest
"convert valid maps" in {
val entries = ImmArray(1 until 5).map { x =>
hash(x.toString) -> x.toLong
Utf8.sha256(x.toString) -> x.toLong
}
val apiEntries = entries.map {
case (k, v) => ApiMap.Entry(k, Some(Value(Sum.Int64(v))))
}
val apiEntries = entries.map { case (k, v) => ApiMap.Entry(k, Some(Value(Sum.Int64(v)))) }
val input = Value(Sum.Map(ApiMap(apiEntries.toSeq)))
val lfEntries = entries.map { case (k, v) => k -> Lf.ValueInt64(v) }
val expected =
@ -498,14 +499,16 @@ class CommandSubmissionRequestValidatorTest
"reject maps with repeated keys" in {
val entries = ImmArray(1 +: (1 until 5)).map { x =>
hash(x.toString) -> x.toLong
Utf8.sha256(x.toString) -> x.toLong
}
val apiEntries = entries.map {
case (k, v) => ApiMap.Entry(k, Some(Value(Sum.Int64(v))))
}
val apiEntries = entries.map { case (k, v) => ApiMap.Entry(k, Some(Value(Sum.Int64(v)))) }
val input = Value(Sum.Map(ApiMap(apiEntries.toSeq)))
requestMustFailWith(
sut.validateValue(input),
INVALID_ARGUMENT,
s"Invalid argument: key ${hash(1.toString)} duplicated when trying to build map")
s"Invalid argument: key ${Utf8.sha256("1")} duplicated when trying to build map")
}
"reject maps containing invalid value" in {
@ -523,9 +526,4 @@ class CommandSubmissionRequestValidatorTest
}
private def hash(t: String): String = {
val digest = MessageDigest.getInstance("SHA-256")
val array = digest.digest(UTF8.getBytes(t))
array.map("%02x" format _).mkString
}
}

View File

@ -63,8 +63,8 @@ object LedgerApiTestTool {
}
var failed = false
val runSuffix = Random.alphanumeric.take(10).mkString
val partyNameMangler = (partyText: String) => s"$partyText-$runSuffix"
val runSuffix = "-" + Random.alphanumeric.take(10).mkString
val partyNameMangler = (partyText: String) => partyText + runSuffix
val commandIdMangler: ((QualifiedName, Int, L.NodeId) => String) = (scenario, stepId, nodeId) =>
s"ledger-api-test-tool-$scenario-$stepId-$nodeId-$runSuffix"

View File

@ -6,6 +6,7 @@ package com.digitalasset.platform.sandbox.stores.ledger.sql.serialisation
import java.nio.ByteBuffer
import java.security.MessageDigest
import com.digitalasset.daml.lf.data.Utf8
import com.digitalasset.daml.lf.transaction.Node.GlobalKey
import com.digitalasset.daml.lf.value.Value
import com.digitalasset.daml.lf.value.Value.AbsoluteContractId
@ -109,9 +110,10 @@ object KeyHasher extends KeyHasher {
digest.update(ByteBuffer.allocate(8).putLong(value).array())
private[this] def putStringContent(digest: MessageDigest, value: String): Unit =
digest.update(value.getBytes("UTF8"))
digest.update(Utf8.getBytes(value))
private[this] def putString(digest: MessageDigest, value: String): Unit = {
// FixMe we probably should not use UTF16 length.
putInt(digest, value.length)
putStringContent(digest, value)
}
@ -140,6 +142,7 @@ object KeyHasher extends KeyHasher {
case HashTokenInt(v) => putInt(d, v)
case HashTokenLong(v) => putLong(d, v)
case HashTokenText(v) => putString(d, v)
// FixMe we probably should use Decimal.toString
// Java docs: "The toString() method provides a canonical representation of a BigDecimal."
case HashTokenBigDecimal(v) => putString(d, v.toString)
case HashTokenCollectionBegin(length) => putInt(d, length)

View File

@ -48,11 +48,12 @@ class EventConverterSpec
with TestHelpers
with AkkaBeforeAndAfterAll
with Inside {
private implicit def qualifiedNameStr(s: String): QualifiedName =
QualifiedName.assertFromString(s)
private implicit def party(s: String): Ref.Party = Ref.Party.assertFromString(s)
private implicit def pkgId(s: String): Ref.PackageId = Ref.PackageId.assertFromString(s)
private implicit def id(s: String): Ref.Name = Ref.Name.assertFromString(s)
private implicit def name(s: String): Ref.Name = Ref.Name.assertFromString(s)
type LfTx = com.digitalasset.daml.lf.transaction.Transaction.Transaction

View File

@ -11,6 +11,8 @@ import com.digitalasset.daml.lf.value.{Value, ValueVersion}
import org.scalatest.{Matchers, WordSpec}
import com.digitalasset.platform.sandbox.stores.ledger.sql.serialisation.KeyHasher
import scala.language.implicitConversions
class KeyHasherSpec extends WordSpec with Matchers {
private[this] def templateId(module: String, name: String) = Identifier(
PackageId.assertFromString("package"),
@ -25,8 +27,8 @@ class KeyHasherSpec extends WordSpec with Matchers {
builder += None -> ValueInt64(0)
builder += None -> ValueInt64(123456)
builder += None -> ValueInt64(-1)
builder += None -> ValueDecimal(Decimal.assertFromString("0"))
builder += None -> ValueDecimal(Decimal.assertFromString("0.3333333333"))
builder += None -> ValueDecimal(decimal(0))
builder += None -> ValueDecimal(decimal(BigDecimal("0.3333333333")))
builder += None -> ValueBool(true)
builder += None -> ValueBool(false)
builder += None -> ValueDate(Time.Date.assertFromDaysSinceEpoch(0))
@ -125,9 +127,13 @@ class KeyHasherSpec extends WordSpec with Matchers {
"not produce collision in list of decimals" in {
// Testing whether decimals are delimited: [10, 10] vs [101, 0]
val value1 =
VersionedValue(ValueVersion("4"), ValueList(FrontStack(ValueDecimal(10), ValueDecimal(10))))
VersionedValue(
ValueVersion("4"),
ValueList(FrontStack(ValueDecimal(decimal(10)), ValueDecimal(decimal(10)))))
val value2 =
VersionedValue(ValueVersion("4"), ValueList(FrontStack(ValueDecimal(101), ValueDecimal(0))))
VersionedValue(
ValueVersion("4"),
ValueList(FrontStack(ValueDecimal(decimal(101)), ValueDecimal(decimal(0)))))
val tid = templateId("module", "name")
@ -269,8 +275,8 @@ class KeyHasherSpec extends WordSpec with Matchers {
}
"not produce collision in Decimal" in {
val value1 = VersionedValue(ValueVersion("4"), ValueDecimal(0))
val value2 = VersionedValue(ValueVersion("4"), ValueDecimal(1))
val value1 = VersionedValue(ValueVersion("4"), ValueDecimal(decimal(0)))
val value2 = VersionedValue(ValueVersion("4"), ValueDecimal(decimal(1)))
val tid = templateId("module", "name")
@ -346,4 +352,7 @@ class KeyHasherSpec extends WordSpec with Matchers {
hash1.equals(hash2) shouldBe false
}
}
private implicit def decimal(x: BigDecimal): Decimal = Decimal.assertFromBigDecimal(x)
}

View File

@ -69,6 +69,8 @@ class PostgresDaoSpec
private val alice = Party.assertFromString("Alice")
private val bob = Party.assertFromString("Bob")
private val someValueText = ValueText("some text")
private val agreement = "agreement"
"Postgres Ledger DAO" should {
@ -82,8 +84,8 @@ class PostgresDaoSpec
Ref.QualifiedName(
Ref.ModuleName.assertFromString("moduleName"),
Ref.DottedName.assertFromString("name"))),
VersionedValue(ValueVersions.acceptedVersions.head, ValueText("some text")),
"agreement"
VersionedValue(ValueVersions.acceptedVersions.head, someValueText),
agreement
)
val keyWithMaintainers = KeyWithMaintainers(
VersionedValue(ValueVersions.acceptedVersions.head, ValueText("key")),
@ -144,7 +146,7 @@ class PostgresDaoSpec
entry <- ledgerDao.lookupLedgerEntry(offset)
endingOffset <- ledgerDao.lookupLedgerEnd()
} yield {
entry shouldEqual (Some(checkpoint))
entry shouldEqual Some(checkpoint)
endingOffset shouldEqual (startingOffset + 1)
}
}
@ -198,8 +200,8 @@ class PostgresDaoSpec
Ref.QualifiedName(
Ref.ModuleName.assertFromString("moduleName"),
Ref.DottedName.assertFromString("name"))),
VersionedValue(ValueVersions.acceptedVersions.head, ValueText("some text")),
"agreement"
VersionedValue(ValueVersions.acceptedVersions.head, someValueText),
agreement
)
val keyWithMaintainers = KeyWithMaintainers(
@ -265,8 +267,8 @@ class PostgresDaoSpec
val let = Instant.now
val contractInstance = ContractInst(
templateId,
VersionedValue(ValueVersions.acceptedVersions.head, ValueText("some text")),
"agreement"
VersionedValue(ValueVersions.acceptedVersions.head, someValueText),
agreement
)
val contract = Contract(
absCid,

View File

@ -57,7 +57,11 @@ object ApiCodecCompressed {
JsObject(value.fields.map(f => f.label -> apiValueToJsValue(f.value)).toMap)
def apiMapToJsValue(value: Model.ApiMap): JsValue =
JsObject(value.value.mapValue(apiValueToJsValue).toHashMap)
JsObject(
value.value.toImmArray
.map { case (k, v) => k -> apiValueToJsValue(v) }
.toSeq
.toMap)
// ------------------------------------------------------------------------------------------------------------------
// Decoding - this needs access to DAML-LF types
@ -88,7 +92,9 @@ object ApiCodecCompressed {
case None => deserializationError(s"Can't read ${value.prettyPrint} as Optional")
}
case (JsObject(a), Model.DamlLfPrimType.Map) =>
Model.ApiMap(SortedLookupList(a).mapValue(jsValueToApiType(_, prim.typArgs.head, defs)))
Model.ApiMap(SortedLookupList(a.map {
case (k, v) => k -> jsValueToApiType(v, prim.typArgs.head, defs)
}))
case _ => deserializationError(s"Can't read ${value.prettyPrint} as $prim")
}
}

View File

@ -3,7 +3,7 @@
package com.digitalasset.navigator.json
import com.digitalasset.daml.lf.data.{SortedLookupList, ImmArray}
import com.digitalasset.daml.lf.data.{ImmArray, SortedLookupList}
import com.digitalasset.navigator.{model => Model}
import com.digitalasset.navigator.json.DamlLfCodec.JsonImplicits._
import com.digitalasset.navigator.json.Util._
@ -81,7 +81,8 @@ object ApiCodecVerbose {
JsObject(
propType -> JsString(tagMap),
propValue -> JsArray(value.value.toImmArray.toSeq.toVector.map {
case (k, v) => JsObject(fieldKey -> JsString(k), fieldValue -> apiValueToJsValue(v))
case (k, v) =>
JsObject(fieldKey -> JsString(k), fieldValue -> apiValueToJsValue(v))
})
)

View File

@ -7,7 +7,7 @@ import java.time.{Instant, LocalDate}
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import com.digitalasset.daml.lf.data.{SortedLookupList, ImmArray}
import com.digitalasset.daml.lf.data.{ImmArray, SortedLookupList}
import com.digitalasset.ledger.api.{v1 => V1}
import com.digitalasset.ledger.api.refinements.ApiTypes
import com.digitalasset.navigator.{model => Model}

View File

@ -3,10 +3,9 @@
package com.digitalasset.navigator
import com.digitalasset.daml.lf.data.SortedLookupList
import com.digitalasset.daml.lf.data.{SortedLookupList, Ref => DamlLfRef}
import com.digitalasset.navigator.model._
import com.digitalasset.daml.lf.{iface => DamlLfIface}
import com.digitalasset.daml.lf.data.{Ref => DamlLfRef}
import scala.language.implicitConversions