Skip to content

Commit

Permalink
daml-lf: introduce Numeric in internal AST (digital-asset#2608)
Browse files Browse the repository at this point in the history
* daml-lf: introduce numeric in daml-lf AST
* daml-lf: add test for command preprocessor
* interface-reader: fix for Numerics
* Address Gerolf's review
  • Loading branch information
remyhaemmerle-da authored Aug 23, 2019
1 parent 67c2e2a commit 9a4dff6
Showing 32 changed files with 648 additions and 365 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -20,14 +20,15 @@ class DecodeV1Spec extends WordSpec with Matchers with Inside with OptionValues

"The keys of primTypeTable correspond to Protobuf DamlLf1.PrimType" in {

(DecodeV1.primTypeTable.keySet + DamlLf1.PrimType.UNRECOGNIZED) shouldBe
(Set(DamlLf1.PrimType.UNRECOGNIZED, DamlLf1.PrimType.DECIMAL) ++
DecodeV1.builtinTypeInfos.map(_.proto)) shouldBe
DamlLf1.PrimType.values().toSet

}

"The keys of builtinFunctionMap correspond to Protobuf DamlLf1.BuiltinFunction" in {

(DecodeV1.builtinFunctionMap.keySet + DamlLf1.BuiltinFunction.UNRECOGNIZED) shouldBe
(Set(DamlLf1.BuiltinFunction.UNRECOGNIZED) ++ DecodeV1.builtinFunctionInfos.map(_.proto)) shouldBe
DamlLf1.BuiltinFunction.values().toSet

}
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
import Encode._
import Name.ordering

private val version = LV(LV.Major.V1, minor)
private val languageVersion = LV(LV.Major.V1, minor)

def encodePackage(pkgId: PackageId, pkg: Package): PLF.Package = {
val moduleEncoder = new ModuleEncoder(pkgId)
@@ -125,19 +125,15 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
.build()
case KStar =>
star
case KNat =>
assertSince(LV.Features.numeric, "Kind.KNat")
// FixMe: https://github.com/digital-asset/daml/issues/2289
throw EncodeError("Numeric not available in protoBuf")
}

/** * Encoding of types ***/
private val builtinTypeMap =
DecodeV1.primTypeTable.map {
case (proto, (scala, sinceVersion)) => scala -> (proto -> sinceVersion)
}

private implicit def encodeBuiltinType(bType: BuiltinType): PLF.PrimType = {
val (builtin, minVersion) = builtinTypeMap(bType)
assertSince(minVersion, bType.toString)
builtin
}
private val builtinTypeInfoMap =
DecodeV1.builtinTypeInfos.map(info => info.bTyp -> info).toMap

@inline
private implicit def encodeTypeBinder(binder: (String, Kind)): PLF.TypeVarWithKind = {
@@ -158,6 +154,14 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
case TApp(fun, arg) => fun -> arg
})

private def ignoreFirstTNatForDecimalLegacy(typs: ImmArray[Type]): ImmArray[Type] =
// BTNumeric must be applied to a TNat that we should ignore
typs match {
case ImmArrayCons(TNat(_), tail) => tail
case _ =>
sys.error("cannot encode the archive in LF <= 1.6")
}

private implicit def encodeType(typ: Type): PLF.Type =
encodeTypeBuilder(typ).build()

@@ -172,22 +176,31 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
case TVar(varName) =>
builder.setVar(
PLF.Type.Var.newBuilder().setVar(varName).accumulateLeft(args)(_ addArgs _))
case TNat(_) =>
assertSince(LV.Features.numeric, "Type.TNat")
// FixMe: https://github.com/digital-asset/daml/issues/2289
throw EncodeError("Nat type not available in protoBuf")
case TTyCon(tycon) =>
builder.setCon(
PLF.Type.Con.newBuilder().setTycon(tycon).accumulateLeft(args)(_ addArgs _))
case TBuiltin(bType) =>
if (bType == BTArrow && LV.ordering.lt(version, LV.Features.arrowType)) {
args match {
case ImmArraySnoc(firsts, last) =>
builder.setFun(
PLF.Type.Fun.newBuilder().accumulateLeft(firsts)(_ addParams _).setResult(last))
case _ =>
sys.error("unexpected errors")
}
} else {
builder.setPrim(
PLF.Type.Prim.newBuilder().setPrim(bType).accumulateLeft(args)(_ addArgs _))
case TBuiltin(BTArrow) if versionIsOlderThan(LV.Features.arrowType) =>
args match {
case ImmArraySnoc(firsts, last) =>
builder.setFun(
PLF.Type.Fun.newBuilder().accumulateLeft(firsts)(_ addParams _).setResult(last))
case _ =>
sys.error("unexpected errors")
}
case TBuiltin(bType) =>
val (proto, typs) =
// FixMe: https://github.com/digital-asset/daml/issues/2289
// enable the following check once it is possible to encode numeric in the proto
if (bType == BTNumeric /*&& versionIsOlderThan(LV.Features.numeric)*/ )
PLF.PrimType.DECIMAL -> ignoreFirstTNatForDecimalLegacy(args)
else
builtinTypeInfoMap(bType).proto -> args
builder.setPrim(
PLF.Type.Prim.newBuilder().setPrim(proto).accumulateLeft(typs)(_ addArgs _))
case TApp(_, _) =>
sys.error("unexpected error")
case TForalls(binders, body) =>
@@ -202,13 +215,11 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {

/** * Encoding Expression ***/
private val builtinFunctionMap =
DecodeV1.builtinFunctionMap.map {
case (proto, (scala, sinceVersion)) => scala -> (proto -> sinceVersion)
}
DecodeV1.builtinFunctionInfos.map(info => info.builtin -> info).toMap

@inline
private implicit def encodeBuiltins(builtinFunction: BuiltinFunction): PLF.BuiltinFunction =
builtinFunctionMap(builtinFunction)._1
builtinFunctionMap(builtinFunction).proto

private implicit def encodeTyConApp(tyCon: TypeConApp): PLF.Type.Con =
PLF.Type.Con
@@ -334,7 +345,10 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
val builder = PLF.PrimLit.newBuilder()
primLit match {
case PLInt64(value) => builder.setInt64(value)
case PLDecimal(value) => builder.setDecimal(Numeric.toUnscaledString(value))
// FixMe: https://github.com/digital-asset/daml/issues/2289
// enable the following check once it is possible to encode numeric in the proto
case PLNumeric(value) /*if versionIsOlderThan(LV.Features.numeric) */ =>
builder.setDecimal(Numeric.toUnscaledString(value))
case PLText(value) => builder.setText(value)
case PLTimestamp(value) => builder.setTimestamp(value.micros)
case PLParty(party) => builder.setParty(party)
@@ -383,6 +397,12 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
case ETyAbs(binder, body) => binder -> body
})

private def isLegacyDecimalBuiltin(expr: Expr) =
expr match {
case EBuiltin(f) => builtinFunctionMap(f).handleLegacyDecimal
case _ => false
}

private def encodeExprBuilder(expr0: Expr): PLF.Expr.Builder = {
def newBuilder = PLF.Expr.newBuilder()

@@ -431,6 +451,14 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {
PLF.Expr.TupleUpd.newBuilder().setField(field).setTuple(tuple).setUpdate(update))
case EApps(fun, args) =>
newBuilder.setApp(PLF.Expr.App.newBuilder().setFun(fun).accumulateLeft(args)(_ addArgs _))
case ETyApps(expr, typs1) =>
val typs: ImmArray[Type] =
if (isLegacyDecimalBuiltin(expr) && versionIsOlderThan(LV.Features.numeric))
ignoreFirstTNatForDecimalLegacy(typs1)
else
typs1
newBuilder.setTyApp(
PLF.Expr.TyApp.newBuilder().setExpr(expr).accumulateLeft(typs)(_ addTypes _))
case ETyApps(expr, typs) =>
newBuilder.setTyApp(
PLF.Expr.TyApp.newBuilder().setExpr(expr).accumulateLeft(typs)(_ addTypes _))
@@ -555,8 +583,11 @@ private[digitalasset] class EncodeV1(val minor: LV.Minor) {

}

private def versionIsOlderThan(minVersion: LV): Boolean =
LV.ordering.lt(languageVersion, minVersion)

private def assertSince(minVersion: LV, description: String): Unit =
if (LV.ordering.lt(version, minVersion))
if (versionIsOlderThan(minVersion))
throw EncodeError(s"$description is not supported by DAML-LF 1.$minor")

}
2 changes: 1 addition & 1 deletion daml-lf/encoder/src/test/lf/since_1.0/Decimal.lf
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@

module Decimal {

record @serializable Box = { x: Decimal, party: Party } ;
record @serializable Box = { x: Numeric 10, party: Party } ;

template (this : Box) = {
precondition True,
2 changes: 1 addition & 1 deletion daml-lf/encoder/src/test/lf/since_1.0/Record.lf
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ module Record {

record @serializable Pair (a:*) (b: *) = { fst: a, snd: b };

record @serializable Box = { x: Record:Pair Int64 Decimal, party: Party } ;
record @serializable Box = { x: Record:Pair Int64 (Numeric 10), party: Party } ;

template (this : Box) = {
precondition True,
2 changes: 1 addition & 1 deletion daml-lf/encoder/src/test/lf/since_1.0/Variant.lf
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ module Variant {

variant @serializable Either (a:*) (b: *) = Left: a | Right: b ;

record @serializable Box = { x: Variant:Either Int64 Decimal, party: Party } ;
record @serializable Box = { x: Variant:Either Int64 (Numeric 10), party: Party } ;

template (this : Box) = {
precondition True,
1 change: 1 addition & 0 deletions daml-lf/engine/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -53,6 +53,7 @@ da_scala_test(
"//daml-lf/data",
"//daml-lf/interpreter",
"//daml-lf/language",
"//daml-lf/parser",
"//daml-lf/scenario-interpreter",
"//daml-lf/transaction",
],
Original file line number Diff line number Diff line change
@@ -58,8 +58,7 @@ private[engine] class CommandPreprocessor(compiledPackages: ConcurrentCompiledPa
fail(s"Got out of bounds type variable $v when replacing parameters")
case Some(ty) => ty
}
case tycon: TTyCon => tycon
case bltin: TBuiltin => bltin
case TTyCon(_) | TBuiltin(_) | TNat(_) => typ
case TApp(tyfun, arg) => TApp(go(tyfun), go(arg))
case forall: TForall =>
fail(
@@ -98,21 +97,21 @@ private[engine] class CommandPreprocessor(compiledPackages: ConcurrentCompiledPa
val newNesting = nesting + 1
(ty, value) match {
// simple values
case (TBuiltin(BTUnit), ValueUnit) =>
case (TUnit, ValueUnit) =>
ResultDone(SUnit(()))
case (TBuiltin(BTBool), ValueBool(b)) =>
case (TBool, ValueBool(b)) =>
ResultDone(SBool(b))
case (TBuiltin(BTInt64), ValueInt64(i)) =>
case (TInt64, ValueInt64(i)) =>
ResultDone(SInt64(i))
case (TBuiltin(BTTimestamp), ValueTimestamp(t)) =>
case (TTimestamp, ValueTimestamp(t)) =>
ResultDone(STimestamp(t))
case (TBuiltin(BTDate), ValueDate(t)) =>
case (TDate, ValueDate(t)) =>
ResultDone(SDate(t))
case (TBuiltin(BTText), ValueText(t)) =>
case (TText, ValueText(t)) =>
ResultDone(SText(t))
case (TBuiltin(BTDecimal), ValueNumeric(d)) =>
case (TNumeric(TNat(10)), ValueNumeric(d)) =>
Numeric.fromBigDecimal(Decimal.scale, d).fold(fail, d => ResultDone(SNumeric(d)))
case (TBuiltin(BTParty), ValueParty(p)) =>
case (TParty, ValueParty(p)) =>
ResultDone(SParty(p))
case (TContractId(typ), ValueContractId(c)) =>
typ match {
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) 2019 The DAML Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.digitalasset.daml
package lf
package engine

import com.digitalasset.daml.lf.data._
import com.digitalasset.daml.lf.language.Ast.{TNat, TTyCon}
import com.digitalasset.daml.lf.language.Util._
import com.digitalasset.daml.lf.testing.parser.Implicits._
import com.digitalasset.daml.lf.value.Value._
import com.digitalasset.daml.lf.value.ValueVersion
import org.scalatest.prop.TableDrivenPropertyChecks
import org.scalatest.{Matchers, WordSpec}

import scala.language.implicitConversions

@SuppressWarnings(Array("org.wartremover.warts.Any"))
class CommandPreprocessorSpec extends WordSpec with Matchers with TableDrivenPropertyChecks {

import defaultParserParameters.{defaultPackageId => pkgId}

private implicit def toName(s: String): Ref.Name = Ref.Name.assertFromString(s)

val recordCon = Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Module:Record"))
val variantCon = Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Module:Variant"))
val enumCon = Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Module:Enum"))

val pkg =
p"""
module Module {

record Record = { field : Int64 };
variant Variant = variant1 : Text | variant2 : Int64 ;
enum Enum = value1 | value2;

}

"""

"translateValue" should {

val testCases = Table(
"type" -> "value",
TUnit ->
ValueUnit,
TBool ->
ValueBool(true),
TInt64 ->
ValueInt64(42),
TTimestamp ->
ValueTimestamp(Time.Timestamp.assertFromString("1969-07-20T20:17:00Z")),
TDate ->
ValueDate(Time.Date.assertFromString("1879-03-14")),
TText ->
ValueText("daml"),
TNumeric(TNat(10)) ->
ValueNumeric(Numeric.assertFromString("10.0000000000")),
// TNumeric(TNat(9)) ->
// ValueNumeric(Numeric.assertFromString("9.000000000")),
TParty ->
ValueParty(Ref.Party.assertFromString("Alice")),
TContractId(TTyCon(recordCon)) ->
ValueContractId(AbsoluteContractId(Ref.ContractIdString.assertFromString("contractId"))),
TList(TText) ->
ValueList(FrontStack(ValueText("a"), ValueText("b"))),
TMap(TBool) ->
ValueMap(SortedLookupList(Map("0" -> ValueBool(true), "1" -> ValueBool(false)))),
TOptional(TText) ->
ValueOptional(Some(ValueText("text"))),
TTyCon(recordCon) ->
ValueRecord(None, ImmArray(Some[Ref.Name]("field") -> ValueInt64(33))),
TTyCon(variantCon) ->
ValueVariant(None, "variant1", ValueText("some test")),
TTyCon(enumCon) ->
ValueEnum(None, "value1"),
)

val compiledPackage = ConcurrentCompiledPackages()
assert(compiledPackage.addPackage(pkgId, pkg) == ResultDone(()))
val preprocessor = CommandPreprocessor(compiledPackage)
import preprocessor.translateValue
val valueVersion = ValueVersion("last")

"succeeds on well type values" in {
forAll(testCases) { (typ, value) =>
translateValue(typ, VersionedValue(valueVersion, value)) shouldBe a[ResultDone[_]]
}
}

"fails on non-well type values" in {
forAll(testCases) { (typ1, value1) =>
forAll(testCases) { (_, value2) =>
if (value1 != value2)
translateValue(typ1, VersionedValue(valueVersion, value2)) shouldBe a[ResultError]
}
}
}
}

}
Loading
Oops, something went wrong.

0 comments on commit 9a4dff6

Please sign in to comment.