Skip to content

Commit

Permalink
Add TO_TEXT_CONTRACT_ID primitive (#7137)
Browse files Browse the repository at this point in the history
* Add TO_TEXT_CONTRACT_ID primitive

This is the first part of #7114.

This PR

* Adds the primitive to the protobuf.
* Handles decoding and encoding in Haskell and Scala.
* Handles typechecking in Haskell and Scala.
* Handles speedy compilation and interpretation in Scala.
* Updates the specification.

This PR does not yet change the standard library to make use of this
primitive.

changelog_begin
changelog_end

* Apply suggestions from code review

Co-authored-by: Remy <remy.haemmerle@daml.com>

* Avoid extra allocation

changelog_begin
changelog_end

Co-authored-by: Remy <remy.haemmerle@daml.com>
  • Loading branch information
cocreature and remyhaemmerle-da authored Aug 17, 2020
1 parent 2df0919 commit ab8b418
Show file tree
Hide file tree
Showing 19 changed files with 122 additions and 4 deletions.
1 change: 1 addition & 0 deletions compiler/daml-lf-ast/src/DA/Daml/LF/Ast/Base.hs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ data BuiltinExpr
| BEGreater !BuiltinType -- :: t -> t -> Bool, where t is the builtin type
| BEToText !BuiltinType -- :: t -> Text, where t is one of the builtin types
-- {Int64, Decimal, Text, Timestamp, Date, Party}
| BEToTextContractId -- :: forall t. ContractId t -> Option Text

-- Decimal arithmetic
| BEAddDecimal -- :: Decimal -> Decimal -> Decimal, crashes on overflow
Expand Down
1 change: 1 addition & 0 deletions compiler/daml-lf-ast/src/DA/Daml/LF/Ast/Pretty.hs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ instance Pretty BuiltinExpr where
BEGreater t -> pPrintAppKeyword lvl prec "GREATER" [TyArg (TBuiltin t)]
BEGreaterEq t -> pPrintAppKeyword lvl prec "GREATER_EQ" [TyArg (TBuiltin t)]
BEToText t -> pPrintAppKeyword lvl prec "TO_TEXT" [TyArg (TBuiltin t)]
BEToTextContractId -> "TO_TEXT_CONTRACT_ID"
BEAddDecimal -> "ADD_DECIMAL"
BESubDecimal -> "SUB_DECIMAL"
BEMulDecimal -> "MUL_DECIMAL"
Expand Down
1 change: 1 addition & 0 deletions compiler/daml-lf-proto/src/DA/Daml/LF/Proto3/DecodeV1.hs
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ decodeBuiltinFunction = pure . \case
LF1.BuiltinFunctionTO_TEXT_TIMESTAMP -> BEToText BTTimestamp
LF1.BuiltinFunctionTO_TEXT_PARTY -> BEToText BTParty
LF1.BuiltinFunctionTO_TEXT_DATE -> BEToText BTDate
LF1.BuiltinFunctionTO_TEXT_CONTRACT_ID -> BEToTextContractId
LF1.BuiltinFunctionTEXT_FROM_CODE_POINTS -> BETextFromCodePoints
LF1.BuiltinFunctionFROM_TEXT_PARTY -> BEPartyFromText
LF1.BuiltinFunctionFROM_TEXT_INT64 -> BEInt64FromText
Expand Down
1 change: 1 addition & 0 deletions compiler/daml-lf-proto/src/DA/Daml/LF/Proto3/EncodeV1.hs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ encodeBuiltinExpr = \case
BTDate -> builtin P.BuiltinFunctionTO_TEXT_DATE
BTParty -> builtin P.BuiltinFunctionTO_TEXT_PARTY
other -> error $ "BEToText unexpected type " <> show other
BEToTextContractId -> builtin P.BuiltinFunctionTO_TEXT_CONTRACT_ID
BEToTextNumeric -> builtin P.BuiltinFunctionTO_TEXT_NUMERIC
BETextFromCodePoints -> builtin P.BuiltinFunctionTEXT_FROM_CODE_POINTS
BEPartyFromText -> builtin P.BuiltinFunctionFROM_TEXT_PARTY
Expand Down
1 change: 1 addition & 0 deletions compiler/daml-lf-tools/src/DA/Daml/LF/Simplifier.hs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ safetyStep = \case
BEGreaterEq _ -> Safe 2
BEGreater _ -> Safe 2
BEToText _ -> Safe 1
BEToTextContractId -> Safe 1
BETextFromCodePoints -> Safe 1
BEAddDecimal -> Safe 1
BESubDecimal -> Safe 1
Expand Down
1 change: 1 addition & 0 deletions compiler/daml-lf-tools/src/DA/Daml/LF/TypeChecker/Check.hs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ typeOfBuiltin = \case
BEGreater btype -> pure $ tComparison btype
BEGreaterEq btype -> pure $ tComparison btype
BEToText btype -> pure $ TBuiltin btype :-> TText
BEToTextContractId -> pure $ TForall (alpha, KStar) $ TContractId tAlpha :-> TOptional TText
BETextFromCodePoints -> pure $ TList TInt64 :-> TText
BEPartyToQuotedText -> pure $ TParty :-> TText
BEPartyFromText -> pure $ TText :-> TOptional TParty
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,7 @@ enum BuiltinFunction {
FROM_TEXT_INT64 = 103; // *Available in versions >= 1.5*
FROM_TEXT_DECIMAL = 104; // *Available in versions 1.5 and 1.6
FROM_TEXT_NUMERIC = 117; // *Available in versions >= 1.7*
TO_TEXT_CONTRACT_ID = 136; // *Available in versions >= 1.dev*
SHA256_TEXT = 93; // *Available in versions >= 1.2*

DATE_TO_UNIX_DAYS = 72; // Date -> Int64
Expand Down Expand Up @@ -549,7 +550,7 @@ enum BuiltinFunction {
TEXT_FROM_CODE_POINTS = 105; // *Available in versions >= 1.6*
TEXT_TO_CODE_POINTS = 106; // *Available in versions >= 1.6*

// Next id is 136. 135 is GREATER.
// Next id is 137. 136 is TO_TEXT_CONTRACT_ID.

// EXPERIMENTAL TEXT PRIMITIVES -- these do not yet have stable numbers.
TEXT_TO_UPPER = 9901; // *Available in versions >= 1.dev*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1556,6 +1556,11 @@ private[lf] object DecodeV1 {
BuiltinFunctionInfo(TO_TEXT_TIMESTAMP, BToTextTimestamp),
BuiltinFunctionInfo(TO_TEXT_PARTY, BToTextParty, minVersion = partyTextConversions),
BuiltinFunctionInfo(TO_TEXT_TEXT, BToTextText),
BuiltinFunctionInfo(
TO_TEXT_CONTRACT_ID,
BToTextContractId,
minVersion = contractIdTextConversions,
),
BuiltinFunctionInfo(TO_QUOTED_TEXT_PARTY, BToQuotedTextParty),
BuiltinFunctionInfo(TEXT_FROM_CODE_POINTS, BToTextCodePoints, minVersion = textPacking),
BuiltinFunctionInfo(FROM_TEXT_PARTY, BFromTextParty, minVersion = partyTextConversions),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,19 @@ class DecodeV1Spec
LV.Minor.Dev,
)

private val preContractIdTextConversionVersions = Table(
"minVersion",
List(1, 4, 6, 8).map(i => LV.Minor.Stable(i.toString)): _*
)

private val postContractIdTextConversionVersions = Table(
"minVersion",
// FIXME: https://github.com/digital-asset/daml/issues/7139
// uncomment the following line once LF 1.9 is released
// LV.Minor.Stable("9"),
LV.Minor.Dev,
)

"decodeKind" should {

"reject nat kind if lf version < 1.7" in {
Expand Down Expand Up @@ -390,6 +403,11 @@ class DecodeV1Spec
DamlLf1.BuiltinFunction.APPEND_TEXT -> Ast.EBuiltin(Ast.BAppendText)
)

val contractIdTextConversionCases = Table(
"builtin" -> "expected output",
DamlLf1.BuiltinFunction.TO_TEXT_CONTRACT_ID -> Ast.EBuiltin(Ast.BToTextContractId)
)

"translate non numeric/decimal builtin as is for any version" in {
val allVersions = Table("all versions", preNumericMinVersions ++ postNumericMinVersions: _*)

Expand Down Expand Up @@ -620,6 +638,23 @@ class DecodeV1Spec
}
}

"translate contract id text conversions as is if version >= 1.9" in {
forEvery(postContractIdTextConversionVersions) { version =>
val decoder = moduleDecoder(version)
forEvery(contractIdTextConversionCases) { (proto, scala) =>
decoder.decodeExpr(toProtoExpr(proto), "test") shouldBe scala
}
}
}

"reject contract id text conversions if version < 1.9" in {
forEvery(preContractIdTextConversionVersions) { version =>
val decoder = moduleDecoder(version)
forEvery(contractIdTextConversionCases) { (proto, _) =>
a[ParseError] shouldBe thrownBy(decoder.decodeExpr(toProtoExpr(proto), "test"))
}
}
}
}

"decodeModuleRef" should {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ private[lf] final case class Compiler(
case BToTextTimestamp => SBToText
case BToTextParty => SBToText
case BToTextDate => SBToText
case BToTextContractId => SBToTextContractId
case BToQuotedTextParty => SBToQuotedTextParty
case BToTextCodePoints => SBToTextCodePoints
case BFromTextParty => SBFromTextParty
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,22 @@ private[lf] object SBuiltin {
}
}

final case object SBToTextContractId extends SBuiltin(1) {
override private[speedy] final def execute(
args: util.ArrayList[SValue],
machine: Machine): Unit = {
args.get(0) match {
case SContractId(cid) =>
if (machine.onLedger) {
machine.returnValue = SValue.SValue.None
} else {
machine.returnValue = SOptional(Some(SText(cid.coid)))
}
case _ => crash(s"type mismatch toTextContractId: $args")
}
}
}

final case object SBToTextNumeric extends SBuiltinPure(2) {
override private[speedy] final def executePure(args: util.ArrayList[SValue]): SValue = {
val x = args.get(1).asInstanceOf[SNumeric].value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ private[lf] object Speedy {
var track: Instrumentation,
/* Profile of the run when the packages haven been compiled with profiling enabled. */
var profile: Profile,
/* True if we are running on ledger building transactions, false if we
are running off-ledger code, e.g., DAML Script or
Triggers. It is safe to use on ledger for off ledger code but
not the other way around.
*/
val onLedger: Boolean,
) {

/* kont manipulation... */
Expand Down Expand Up @@ -662,6 +668,7 @@ private[lf] object Speedy {
inputValueVersions: VersionRange[ValueVersion],
outputTransactionVersions: VersionRange[TransactionVersion],
validating: Boolean = false,
onLedger: Boolean = true,
): Machine =
new Machine(
inputValueVersions = inputValueVersions,
Expand All @@ -687,6 +694,7 @@ private[lf] object Speedy {
steps = 0,
track = Instrumentation(),
profile = new Profile(),
onLedger = onLedger,
)

@throws[PackageNotFound]
Expand Down Expand Up @@ -733,6 +741,7 @@ private[lf] object Speedy {
def fromPureSExpr(
compiledPackages: CompiledPackages,
expr: SExpr,
onLedger: Boolean = true,
): Machine =
Machine(
compiledPackages = compiledPackages,
Expand All @@ -743,6 +752,7 @@ private[lf] object Speedy {
committers = Set.empty,
inputValueVersions = ValueVersions.Empty,
outputTransactionVersions = TransactionVersions.Empty,
onLedger = onLedger,
)

@throws[PackageNotFound]
Expand All @@ -751,8 +761,9 @@ private[lf] object Speedy {
def fromPureExpr(
compiledPackages: CompiledPackages,
expr: Expr,
onLedger: Boolean = true,
): Machine =
fromPureSExpr(compiledPackages, compiledPackages.compiler.unsafeCompile(expr))
fromPureSExpr(compiledPackages, compiledPackages.compiler.unsafeCompile(expr), onLedger)

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import com.daml.lf.language.Ast
import com.daml.lf.language.Ast._
import com.daml.lf.speedy.SError.{DamlEArithmeticError, SError, SErrorCrash}
import com.daml.lf.speedy.SResult.{SResultFinalValue, SResultError}
import com.daml.lf.speedy.SExpr._
import com.daml.lf.speedy.SValue._
import com.daml.lf.testing.parser.Implicits._
import com.daml.lf.value.Value
import org.scalactic.Equality
import org.scalatest.prop.TableDrivenPropertyChecks
import org.scalatest.{FreeSpec, Matchers}
Expand Down Expand Up @@ -1280,6 +1282,23 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
}
}

"TO_TEXT_CONTRACT_ID" - {
"returns None on-ledger" in {
val f = """(\(c:(ContractId Mod:T)) -> TO_TEXT_CONTRACT_ID @Mod:T c)"""
evalApp(
e"$f",
Array(SContractId(Value.ContractId.assertFromString("#abc"))),
onLedger = true) shouldEqual Right(SOptional(None))
}
"returns Some(abc) off-ledger" in {
val f = """(\(c:(ContractId Mod:T)) -> TO_TEXT_CONTRACT_ID @Mod:T c)"""
evalApp(
e"$f",
Array(SContractId(Value.ContractId.assertFromString("#abc"))),
onLedger = false) shouldEqual Right(SOptional(Some(SText("#abc"))))
}
}

"handle ridiculously huge strings" in {

val testCases = Table(
Expand Down Expand Up @@ -1451,8 +1470,16 @@ object SBuiltinTest {
val compiledPackages =
PureCompiledPackages(Map(defaultParserParameters.defaultPackageId -> pkg)).right.get

private def eval(e: Expr): Either[SError, SValue] = {
val machine = Speedy.Machine.fromPureExpr(compiledPackages, e)
private def eval(e: Expr, onLedger: Boolean = true): Either[SError, SValue] = {
evalSExpr(compiledPackages.compiler.unsafeCompile(e), onLedger)
}

private def evalApp(e: Expr, args: Array[SValue], onLedger: Boolean): Either[SError, SValue] = {
evalSExpr(SEApp(compiledPackages.compiler.unsafeCompile(e), args.map(SEValue(_))), onLedger)
}

private def evalSExpr(e: SExpr, onLedger: Boolean): Either[SError, SValue] = {
val machine = Speedy.Machine.fromPureSExpr(compiledPackages, e, onLedger)
final case class Goodbye(e: SError) extends RuntimeException("", null, false, false)
try {
val value = machine.run() match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ object Ast {
final case object BToTextTimestamp extends BuiltinFunction(1) // : Timestamp → Text
final case object BToTextParty extends BuiltinFunction(1) // : Party → Text
final case object BToTextDate extends BuiltinFunction(1) // : Date -> Text
final case object BToTextContractId extends BuiltinFunction(1) // : forall t. ContractId t -> Optional Text
final case object BToQuotedTextParty extends BuiltinFunction(1) // : Party -> Text
final case object BToTextCodePoints extends BuiltinFunction(1) // : [Int64] -> Text
final case object BFromTextParty extends BuiltinFunction(1) // : Text -> Optional Party
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ object LanguageVersion {
val genComparison = v1_dev
val genMap = v1_dev
val scenarioMustFailAtMsg = v1_dev
val contractIdTextConversions = v1_dev

/** Unstable, experimental features. This should stay in 1.dev forever.
* Features implemented with this flag should be moved to a separate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ private[parser] class ExprParser[P](parserParameters: ParserParameters[P]) {
"TO_TEXT_TIMESTAMP" -> BToTextTimestamp,
"TO_TEXT_PARTY" -> BToTextParty,
"TO_TEXT_DATE" -> BToTextDate,
"TO_TEXT_CONTRACT_ID" -> BToTextContractId,
"TO_QUOTED_TEXT_PARTY" -> BToQuotedTextParty,
"TEXT_FROM_CODE_POINTS" -> BToTextCodePoints,
"FROM_TEXT_PARTY" -> BFromTextParty,
Expand Down
9 changes: 9 additions & 0 deletions daml-lf/spec/daml-lf-1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ Version: 1.dev

+ **Add** generic map type ``GenMap``.

+ **Add** ``TO_TEXT_CONTRACT_ID`` builtin.

Abstract syntax
^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -3601,6 +3603,13 @@ ContractId functions

[*Available in versions >= 1.5*]

* ``TO_TEXT_CONTRACT_ID : ∀ (α : ⋆) . 'ContractId' α -> 'Optional' 'Text'``

Always returns ``None`` in ledger code. This function is only useful
for off-ledger code which is not covered by this specification.

[*Available in versions >= 1.dev*]

List functions
~~~~~~~~~~~~~~

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ private[validation] object Typing {
BToTextTimestamp -> (TTimestamp ->: TText),
BToTextParty -> (TParty ->: TText),
BToTextDate -> (TDate ->: TText),
BToTextContractId -> TForall(alpha.name -> KStar, TContractId(alpha) ->: TOptional(TText)),
BSHA256Text -> (TText ->: TText),
BToQuotedTextParty -> (TParty ->: TText),
BToTextCodePoints -> (TList(TInt64) ->: TText),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ class TypingSpec extends WordSpec with TableDrivenPropertyChecks with Matchers {
T"TypeRep",
E"""(( type_rep @((ContractId Mod:T) → Mod:Color) ))""" ->
T"TypeRep",
// TO_TEXT_CONTRACT_ID
E"""Λ (σ : ⋆). λ (c : (ContractId σ)) → TO_TEXT_CONTRACT_ID @σ c""" ->
T"∀ (σ : ⋆). ContractId σ → Option Text"
)

forEvery(testCases) { (exp: Expr, expectedType: Type) =>
Expand Down

0 comments on commit ab8b418

Please sign in to comment.