mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 09:17:43 +03:00
LF: Structure Preprocessing Errors (#10013)
part of #9974. CHANGELOG_BEGIN CHANGELOG_END
This commit is contained in:
parent
1852830833
commit
3532460675
@ -19,6 +19,7 @@ import java.nio.file.Files
|
||||
import com.daml.lf.language.{Interface, LanguageVersion}
|
||||
import com.daml.lf.validation.Validation
|
||||
import com.daml.lf.value.Value.ContractId
|
||||
import com.daml.nameof.NameOf
|
||||
|
||||
/** Allows for evaluating [[Commands]] and validating [[Transaction]]s.
|
||||
* <p>
|
||||
@ -237,16 +238,20 @@ class Engine(val config: EngineConfig = new EngineConfig(LanguageVersion.StableV
|
||||
|
||||
@inline
|
||||
private[lf] def runSafely[X](
|
||||
handleMissingDependencies: => Result[Unit]
|
||||
funcName: String
|
||||
)(run: => Result[X]): Result[X] = {
|
||||
def start: Result[X] =
|
||||
try {
|
||||
run
|
||||
} catch {
|
||||
case speedy.Compiler.PackageNotFound(_) =>
|
||||
handleMissingDependencies.flatMap(_ => start)
|
||||
// The two following error should be prevented by the type checking does by translateCommand
|
||||
// so it’s an internal error.
|
||||
case error: speedy.Compiler.PackageNotFound =>
|
||||
ResultError(
|
||||
Error.Preprocessing.Internal(funcName, s"CompilationError: ${error.getMessage}")
|
||||
)
|
||||
case speedy.Compiler.CompilationError(error) =>
|
||||
ResultError(Error.Preprocessing.Generic(s"CompilationError: $error"))
|
||||
ResultError(Error.Preprocessing.Internal(funcName, s"CompilationError: $error"))
|
||||
}
|
||||
start
|
||||
}
|
||||
@ -268,9 +273,7 @@ class Engine(val config: EngineConfig = new EngineConfig(LanguageVersion.StableV
|
||||
seeding: speedy.InitialSeeding,
|
||||
globalCids: Set[Value.ContractId],
|
||||
): Result[(SubmittedTransaction, Tx.Metadata)] =
|
||||
runSafely(
|
||||
loadPackages(commands.foldLeft(Set.empty[PackageId])(_ + _.templateId.packageId).toList)
|
||||
) {
|
||||
runSafely(NameOf.qualifiedNameOfCurrentFunc) {
|
||||
val sexpr = compiledPackages.compiler.unsafeCompile(commands)
|
||||
val machine = Machine(
|
||||
compiledPackages = compiledPackages,
|
||||
|
@ -5,7 +5,8 @@ package com.daml.lf
|
||||
package engine
|
||||
|
||||
import com.daml.lf.data.Ref
|
||||
import com.daml.lf.transaction.GlobalKey
|
||||
import com.daml.lf.language.Ast
|
||||
import com.daml.lf.transaction.{GlobalKey, NodeId}
|
||||
import com.daml.lf.value.Value
|
||||
|
||||
sealed abstract class Error {
|
||||
@ -68,16 +69,24 @@ object Error {
|
||||
|
||||
object Preprocessing {
|
||||
|
||||
sealed abstract class Error extends RuntimeException with scala.util.control.NoStackTrace {
|
||||
sealed abstract class Error
|
||||
extends RuntimeException
|
||||
with scala.util.control.NoStackTrace
|
||||
with Product {
|
||||
def msg: String
|
||||
|
||||
override def toString: String =
|
||||
productPrefix + productIterator.mkString("(", ",", ")")
|
||||
}
|
||||
|
||||
// TODO https://github.com/digital-asset/daml/issues/9974
|
||||
// get rid of Generic
|
||||
final case class Generic(override val msg: String) extends Error
|
||||
final case class Internal(
|
||||
nameOfFunc: String,
|
||||
override val msg: String,
|
||||
detailMsg: String = "",
|
||||
) extends Error
|
||||
|
||||
final case class Lookup(lookupError: language.LookupError) extends Error {
|
||||
def msg: String = lookupError.pretty
|
||||
override def msg: String = lookupError.pretty
|
||||
}
|
||||
|
||||
private[engine] object MissingPackage {
|
||||
@ -89,6 +98,30 @@ object Error {
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
final case class TypeMismatch(
|
||||
typ: Ast.Type,
|
||||
value: Value[Value.ContractId],
|
||||
override val msg: String,
|
||||
) extends Error
|
||||
|
||||
final case class ValueNesting(value: Value[Value.ContractId]) extends Error {
|
||||
override def msg: String =
|
||||
s"Provided value exceeds maximum nesting level of ${Value.MAXIMUM_NESTING}"
|
||||
}
|
||||
|
||||
final case class RootNode(nodeId: NodeId, override val msg: String) extends Error
|
||||
|
||||
// TODO https://github.com/digital-asset/daml/issues/9974
|
||||
// get ride of ContractIdFreshness
|
||||
final case class ContractIdFreshness(
|
||||
localContractIds: Set[Value.ContractId],
|
||||
globalContractIds: Set[Value.ContractId],
|
||||
) extends Error {
|
||||
assert(localContractIds exists globalContractIds)
|
||||
def msg: String = "Conflicting discriminators between a global and local contract ID."
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Error happening during interpretation
|
||||
|
@ -9,6 +9,7 @@ import com.daml.lf.data._
|
||||
import com.daml.lf.language.Ast
|
||||
import com.daml.lf.speedy.SValue
|
||||
import com.daml.lf.value.Value
|
||||
import com.daml.nameof.NameOf
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
@ -53,7 +54,12 @@ private[lf] final class CommandPreprocessor(compiledPackages: CompiledPackages)
|
||||
val (arg, argCids) = valueTranslator.unsafeTranslateValue(choiceArgType, argument)
|
||||
val (key, keyCids) = valueTranslator.unsafeTranslateValue(ckTtype, contractKey)
|
||||
keyCids.foreach { coid =>
|
||||
fail(s"Contract IDs are not supported in contract key of $templateId: $coid")
|
||||
// The type checking of contractKey done by unsafeTranslateValue should ensure
|
||||
// keyCids is empty
|
||||
throw Error.Preprocessing.Internal(
|
||||
NameOf.qualifiedNameOfCurrentFunc,
|
||||
s"Unexpected contract IDs in contract key of $templateId: $coid",
|
||||
)
|
||||
}
|
||||
speedy.Command.ExerciseByKey(templateId, key, choiceId, arg) -> argCids
|
||||
}
|
||||
@ -87,7 +93,12 @@ private[lf] final class CommandPreprocessor(compiledPackages: CompiledPackages)
|
||||
val ckTtype = handleLookup(interface.lookupTemplateKey(templateId)).typ
|
||||
val (key, keyCids) = valueTranslator.unsafeTranslateValue(ckTtype, contractKey)
|
||||
keyCids.foreach { coid =>
|
||||
fail(s"Contract IDs are not supported in contract keys: $coid")
|
||||
// The type checking of contractKey done by unsafeTranslateValue should ensure
|
||||
// keyCids is empty
|
||||
throw Error.Preprocessing.Internal(
|
||||
NameOf.qualifiedNameOfCurrentFunc,
|
||||
s"Unexpected contract IDs in contract key of $templateId: $coid",
|
||||
)
|
||||
}
|
||||
speedy.Command.LookupByKey(templateId, key)
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import com.daml.lf.language.{Ast, LookupError}
|
||||
import com.daml.lf.speedy.SValue
|
||||
import com.daml.lf.transaction.{GenTransaction, NodeId}
|
||||
import com.daml.lf.value.Value
|
||||
import com.daml.nameof.NameOf
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
@ -88,7 +89,11 @@ private[engine] final class Preprocessor(compiledPackages: MutableCompiledPackag
|
||||
case Ast.TTyCon(_) | Ast.TNat(_) | Ast.TBuiltin(_) | Ast.TVar(_) =>
|
||||
go(typesToProcess, tmplToProcess0, tyConAlreadySeen0, tmplsAlreadySeen0)
|
||||
case Ast.TSynApp(_, _) | Ast.TForall(_, _) | Ast.TStruct(_) =>
|
||||
ResultError(Error.Preprocessing.Generic(s"unserializable type ${typ.pretty}"))
|
||||
// We assume that getDependencies is always given serializable types
|
||||
ResultError(
|
||||
Error.Preprocessing
|
||||
.Internal(NameOf.qualifiedNameOfCurrentFunc, s"unserializable type ${typ.pretty}")
|
||||
)
|
||||
}
|
||||
case Nil =>
|
||||
tmplToProcess0 match {
|
||||
@ -160,10 +165,6 @@ private[preprocessing] object Preprocessor {
|
||||
a
|
||||
}
|
||||
|
||||
@throws[Error.Preprocessing.Error]
|
||||
def fail(s: String): Nothing =
|
||||
throw Error.Preprocessing.Generic(s)
|
||||
|
||||
@throws[Error.Preprocessing.Error]
|
||||
def handleLookup[X](either: Either[LookupError, X]): X = either match {
|
||||
case Right(v) => v
|
||||
|
@ -14,8 +14,6 @@ private[preprocessing] final class TransactionPreprocessor(
|
||||
compiledPackages: MutableCompiledPackages
|
||||
) {
|
||||
|
||||
import Preprocessor._
|
||||
|
||||
val commandPreprocessor = new CommandPreprocessor(compiledPackages)
|
||||
|
||||
// Accumulator used by unsafeTranslateTransactionRoots method.
|
||||
@ -35,6 +33,9 @@ private[preprocessing] final class TransactionPreprocessor(
|
||||
)
|
||||
}
|
||||
|
||||
private[this] def invalidRootNode(nodeId: NodeId, message: String) =
|
||||
throw Error.Preprocessing.RootNode(nodeId, message)
|
||||
|
||||
/*
|
||||
* Translates a transaction tree into a sequence of Speedy commands
|
||||
* and collects the global contract IDs.
|
||||
@ -114,14 +115,14 @@ private[preprocessing] final class TransactionPreprocessor(
|
||||
val newLocalCids = GenTransaction(tx.nodes, ImmArray(id)).localContracts.keys
|
||||
acc.update(newCids, newLocalCids, cmd)
|
||||
case _: Node.NodeFetch[_] =>
|
||||
fail(s"Transaction contains a fetch root node $id")
|
||||
invalidRootNode(id, s"Transaction contains a fetch root node $id")
|
||||
case _: Node.NodeLookupByKey[_] =>
|
||||
fail(s"Transaction contains a lookup by key root node $id")
|
||||
invalidRootNode(id, s"Transaction contains a lookup by key root node $id")
|
||||
}
|
||||
case Some(_: Node.NodeRollback[NodeId]) =>
|
||||
fail(s"invalid transaction, root refers to a rollback node $id")
|
||||
invalidRootNode(id, s"invalid transaction, root refers to a rollback node $id")
|
||||
case None =>
|
||||
fail(s"invalid transaction, root refers to non-existing node $id")
|
||||
invalidRootNode(id, s"invalid transaction, root refers to non-existing node $id")
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,7 +133,7 @@ private[preprocessing] final class TransactionPreprocessor(
|
||||
// - it catches obviously buggy transaction,
|
||||
// - it is easier to reason about "soundness" of preprocessing under the disjointness assumption.
|
||||
if (result.localCids exists result.globalCids)
|
||||
fail("Conflicting discriminators between a global and local contract ID.")
|
||||
throw Error.Preprocessing.ContractIdFreshness(result.localCids, result.globalCids)
|
||||
|
||||
result.commands.toImmArray -> result.globalCids
|
||||
}
|
||||
|
@ -18,8 +18,6 @@ private[engine] final class ValueTranslator(interface: language.Interface) {
|
||||
|
||||
import Preprocessor._
|
||||
|
||||
private[this] def fail(s: String) = throw Error.Preprocessing.Generic(s)
|
||||
|
||||
@throws[Error.Preprocessing.Error]
|
||||
private def labeledRecordToMap(
|
||||
fields: ImmArray[(Option[String], Value[ContractId])]
|
||||
@ -50,18 +48,19 @@ private[engine] final class ValueTranslator(interface: language.Interface) {
|
||||
|
||||
val cids = Set.newBuilder[Value.ContractId]
|
||||
|
||||
def go(ty0: Type, value: Value[ContractId], nesting: Int = 0): SValue =
|
||||
def go(ty0: Type, value0: Value[ContractId], nesting: Int = 0): SValue =
|
||||
if (nesting > Value.MAXIMUM_NESTING) {
|
||||
fail(s"Provided value exceeds maximum nesting level of ${Value.MAXIMUM_NESTING}")
|
||||
throw Error.Preprocessing.ValueNesting(value)
|
||||
} else {
|
||||
val newNesting = nesting + 1
|
||||
def typeMismatch = fail(s"mismatching type: $ty and value: $value")
|
||||
def typeError(msg: String = s"mismatching type: $ty and value: $value0") =
|
||||
throw Error.Preprocessing.TypeMismatch(ty, value0, msg)
|
||||
val (ty1, tyArgs) = AstUtil.destructApp(ty0)
|
||||
ty1 match {
|
||||
case TBuiltin(bt) =>
|
||||
tyArgs match {
|
||||
case Nil =>
|
||||
(bt, value) match {
|
||||
(bt, value0) match {
|
||||
case (BTUnit, ValueUnit) =>
|
||||
SValue.SUnit
|
||||
case (BTBool, ValueBool(b)) =>
|
||||
@ -77,16 +76,19 @@ private[engine] final class ValueTranslator(interface: language.Interface) {
|
||||
case (BTParty, ValueParty(p)) =>
|
||||
SValue.SParty(p)
|
||||
case _ =>
|
||||
typeMismatch
|
||||
typeError()
|
||||
}
|
||||
case typeArg0 :: Nil =>
|
||||
(bt, value) match {
|
||||
(bt, value0) match {
|
||||
case (BTNumeric, ValueNumeric(d)) =>
|
||||
typeArg0 match {
|
||||
case TNat(s) =>
|
||||
Numeric.fromBigDecimal(s, d).fold(fail, SValue.SNumeric(_))
|
||||
Numeric.fromBigDecimal(s, d) match {
|
||||
case Right(value) => SValue.SNumeric(value)
|
||||
case Left(message) => typeError(message)
|
||||
}
|
||||
case _ =>
|
||||
typeMismatch
|
||||
typeError()
|
||||
}
|
||||
case (BTContractId, ValueContractId(c)) =>
|
||||
cids += c
|
||||
@ -117,10 +119,10 @@ private[engine] final class ValueTranslator(interface: language.Interface) {
|
||||
)
|
||||
}
|
||||
case _ =>
|
||||
typeMismatch
|
||||
typeError()
|
||||
}
|
||||
case typeArg0 :: typeArg1 :: Nil =>
|
||||
(bt, value) match {
|
||||
(bt, value0) match {
|
||||
case (BTGenMap, ValueGenMap(entries)) =>
|
||||
if (entries.isEmpty) {
|
||||
SValue.SValue.EmptyGenMap
|
||||
@ -133,18 +135,18 @@ private[engine] final class ValueTranslator(interface: language.Interface) {
|
||||
)
|
||||
}
|
||||
case _ =>
|
||||
typeMismatch
|
||||
typeError()
|
||||
}
|
||||
case _ =>
|
||||
typeMismatch
|
||||
typeError()
|
||||
}
|
||||
case TTyCon(tyCon) =>
|
||||
value match {
|
||||
value0 match {
|
||||
// variant
|
||||
case ValueVariant(mbId, constructorName, val0) =>
|
||||
mbId.foreach(id =>
|
||||
if (id != tyCon)
|
||||
fail(
|
||||
typeError(
|
||||
s"Mismatching variant id, the type tells us $tyCon, but the value tells us $id"
|
||||
)
|
||||
)
|
||||
@ -160,7 +162,7 @@ private[engine] final class ValueTranslator(interface: language.Interface) {
|
||||
case ValueRecord(mbId, flds) =>
|
||||
mbId.foreach(id =>
|
||||
if (id != tyCon)
|
||||
fail(
|
||||
typeError(
|
||||
s"Mismatching record id, the type tells us $tyCon, but the value tells us $id"
|
||||
)
|
||||
)
|
||||
@ -172,7 +174,7 @@ private[engine] final class ValueTranslator(interface: language.Interface) {
|
||||
// since in JavaScript / Scala / most languages (but _not_ JSON, interestingly)
|
||||
// it's ok to do `{"a": 1, "a": 2}`, where the second occurrence would just win.
|
||||
if (recordFlds.length != flds.length) {
|
||||
fail(
|
||||
typeError(
|
||||
s"Expecting ${recordFlds.length} field for record $tyCon, but got ${flds.length}"
|
||||
)
|
||||
}
|
||||
@ -182,7 +184,9 @@ private[engine] final class ValueTranslator(interface: language.Interface) {
|
||||
(recordFlds zip flds).map { case ((lbl, typ), (mbLbl, v)) =>
|
||||
mbLbl.foreach(lbl_ =>
|
||||
if (lbl_ != lbl)
|
||||
fail(s"Mismatching record label $lbl_ (expecting $lbl) for record $tyCon")
|
||||
typeError(
|
||||
s"Mismatching record label $lbl_ (expecting $lbl) for record $tyCon"
|
||||
)
|
||||
)
|
||||
val replacedTyp = AstUtil.substitute(typ, subst)
|
||||
lbl -> go(replacedTyp, v, newNesting)
|
||||
@ -191,7 +195,7 @@ private[engine] final class ValueTranslator(interface: language.Interface) {
|
||||
recordFlds.map { case (lbl, typ) =>
|
||||
labeledRecords
|
||||
.get(lbl)
|
||||
.fold(fail(s"Missing record label $lbl for record $tyCon")) { v =>
|
||||
.fold(typeError(s"Missing record label $lbl for record $tyCon")) { v =>
|
||||
val replacedTyp = AstUtil.substitute(typ, subst)
|
||||
lbl -> go(replacedTyp, v, newNesting)
|
||||
}
|
||||
@ -206,17 +210,17 @@ private[engine] final class ValueTranslator(interface: language.Interface) {
|
||||
case ValueEnum(mbId, constructor) if tyArgs.isEmpty =>
|
||||
mbId.foreach(id =>
|
||||
if (id != tyCon)
|
||||
fail(
|
||||
typeError(
|
||||
s"Mismatching enum id, the type tells us $tyCon, but the value tells us $id"
|
||||
)
|
||||
)
|
||||
val rank = handleLookup(interface.lookupEnumConstructor(tyCon, constructor))
|
||||
SValue.SEnum(tyCon, constructor, rank)
|
||||
case _ =>
|
||||
typeMismatch
|
||||
typeError()
|
||||
}
|
||||
case _ =>
|
||||
typeMismatch
|
||||
typeError()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -333,8 +333,8 @@ class ContractDiscriminatorFreshnessCheckSpec
|
||||
.translateTransactionRoots(GenTransaction(newNodes, tx.roots))
|
||||
.consume(_ => None, pkgs, _ => None, _ => VisibleByKey.Visible)
|
||||
|
||||
inside(result) { case Left(err) =>
|
||||
err.msg should include("Conflicting discriminators")
|
||||
inside(result) { case Left(Error.Preprocessing(err)) =>
|
||||
err shouldBe a[Error.Preprocessing.ContractIdFreshness]
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -221,7 +221,9 @@ class EngineTest
|
||||
val res = preprocessor
|
||||
.preprocessCommands(ImmArray(command))
|
||||
.consume(lookupContract, lookupPackage, lookupKey, allKeysVisible)
|
||||
res shouldBe a[Left[_, _]]
|
||||
inside(res) { case Left(Error.Preprocessing(error)) =>
|
||||
error shouldBe a[Error.Preprocessing.TypeMismatch]
|
||||
}
|
||||
}
|
||||
|
||||
"translate exercise commands argument including labels" in {
|
||||
@ -298,7 +300,10 @@ class EngineTest
|
||||
val res = preprocessor
|
||||
.preprocessCommands(ImmArray(command))
|
||||
.consume(lookupContract, lookupPackage, lookupKey, allKeysVisible)
|
||||
res.left.value.msg should startWith("Missing record label n for record")
|
||||
inside(res) { case Left(Error.Preprocessing(error)) =>
|
||||
error shouldBe a[Error.Preprocessing.TypeMismatch]
|
||||
error.msg should startWith("Missing record label n for record")
|
||||
}
|
||||
}
|
||||
|
||||
"not translate exercise-by-key commands if the template specifies no key" in {
|
||||
@ -313,9 +318,9 @@ class EngineTest
|
||||
val res = preprocessor
|
||||
.preprocessCommands(ImmArray(command))
|
||||
.consume(lookupContract, lookupPackage, lookupKey, allKeysVisible)
|
||||
res.left.value.msg should startWith(
|
||||
s"template without contract key $templateId"
|
||||
)
|
||||
inside(res) { case Left(Error.Preprocessing(Error.Preprocessing.Lookup(error))) =>
|
||||
error shouldBe a[language.LookupError.TemplateKey]
|
||||
}
|
||||
}
|
||||
|
||||
"not translate exercise-by-key commands if the given key does not match the type specified in the template" in {
|
||||
@ -330,7 +335,9 @@ class EngineTest
|
||||
val res = preprocessor
|
||||
.preprocessCommands(ImmArray(command))
|
||||
.consume(lookupContract, lookupPackage, lookupKey, allKeysVisible)
|
||||
res.left.value.msg should startWith("mismatching type")
|
||||
inside(res) { case Left(Error.Preprocessing(error)) =>
|
||||
error shouldBe a[Error.Preprocessing.TypeMismatch]
|
||||
}
|
||||
}
|
||||
|
||||
"translate create-and-exercise commands argument including labels" in {
|
||||
@ -394,7 +401,9 @@ class EngineTest
|
||||
val res = preprocessor
|
||||
.preprocessCommands(ImmArray(command))
|
||||
.consume(lookupContract, lookupPackage, lookupKey, allKeysVisible)
|
||||
res shouldBe a[Left[_, _]]
|
||||
inside(res) { case Left(Error.Preprocessing(error)) =>
|
||||
error shouldBe a[Error.Preprocessing.TypeMismatch]
|
||||
}
|
||||
}
|
||||
|
||||
"not translate create-and-exercise commands argument wrong label in choice arguments" in {
|
||||
@ -413,7 +422,9 @@ class EngineTest
|
||||
val res = preprocessor
|
||||
.preprocessCommands(ImmArray(command))
|
||||
.consume(lookupContract, lookupPackage, lookupKey, allKeysVisible)
|
||||
res shouldBe a[Left[_, _]]
|
||||
inside(res) { case Left(Error.Preprocessing(error)) =>
|
||||
error shouldBe a[Error.Preprocessing.TypeMismatch]
|
||||
}
|
||||
}
|
||||
|
||||
"translate Optional values" in {
|
||||
@ -451,12 +462,15 @@ class EngineTest
|
||||
val id = Identifier(basicTestsPkgId, "BasicTests:MyRec")
|
||||
val wrongRecord =
|
||||
ValueRecord(Some(id), ImmArray(Some[Name]("wrongLbl") -> ValueText("foo")))
|
||||
translator
|
||||
val res = translator
|
||||
.translateValue(
|
||||
TTyConApp(id, ImmArray.empty),
|
||||
wrongRecord,
|
||||
)
|
||||
.consume(lookupContract, lookupPackage, lookupKey, allKeysVisible) shouldBe a[Left[_, _]]
|
||||
.consume(lookupContract, lookupPackage, lookupKey, allKeysVisible)
|
||||
inside(res) { case Left(Error.Preprocessing(error)) =>
|
||||
error shouldBe a[Error.Preprocessing.TypeMismatch]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1242,7 +1256,9 @@ class EngineTest
|
||||
)
|
||||
.consume(lookupContract, lookupPackage, lookupKey, allKeysVisible)
|
||||
|
||||
res shouldBe a[Left[_, _]]
|
||||
inside(res) { case Left(Error.Preprocessing(error)) =>
|
||||
error shouldBe a[Error.Preprocessing.TypeMismatch]
|
||||
}
|
||||
}
|
||||
|
||||
"work with fields without labels, in right order" in {
|
||||
@ -1282,7 +1298,9 @@ class EngineTest
|
||||
)
|
||||
.consume(lookupContract, lookupPackage, lookupKey, allKeysVisible)
|
||||
|
||||
res shouldBe a[Left[_, _]]
|
||||
inside(res) { case Left(Error.Preprocessing(error)) =>
|
||||
error shouldBe a[Error.Preprocessing.TypeMismatch]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,13 +12,18 @@ import com.daml.lf.speedy.SValue._
|
||||
import com.daml.lf.testing.parser.Implicits._
|
||||
import com.daml.lf.value.Value
|
||||
import com.daml.lf.value.Value._
|
||||
import org.scalatest.Inside
|
||||
import org.scalatest.prop.TableDrivenPropertyChecks
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
|
||||
import scala.language.implicitConversions
|
||||
|
||||
class PreprocessorSpec extends AnyWordSpec with Matchers with TableDrivenPropertyChecks {
|
||||
class PreprocessorSpec
|
||||
extends AnyWordSpec
|
||||
with Matchers
|
||||
with TableDrivenPropertyChecks
|
||||
with Inside {
|
||||
|
||||
import Preprocessor.ArrayList
|
||||
|
||||
@ -26,10 +31,18 @@ class PreprocessorSpec extends AnyWordSpec with Matchers with TableDrivenPropert
|
||||
|
||||
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 tricky = Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Module:Tricky"))
|
||||
private[this] val recordCon =
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Module:Record"))
|
||||
private[this] val variantCon =
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Module:Variant"))
|
||||
private[this] val enumCon =
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Module:Enum"))
|
||||
private[this] val tricky =
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Module:Tricky"))
|
||||
private[this] val myListTyCons =
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Module:MyList"))
|
||||
private[this] val myNilCons = Ref.Name.assertFromString("MyNil")
|
||||
private[this] val myConsCons = Ref.Name.assertFromString("MyCons")
|
||||
|
||||
val pkg =
|
||||
p"""
|
||||
@ -41,6 +54,9 @@ class PreprocessorSpec extends AnyWordSpec with Matchers with TableDrivenPropert
|
||||
|
||||
record Tricky (b: * -> *) = { x : b Unit };
|
||||
|
||||
record MyCons = { head : Int64, tail: Module:MyList };
|
||||
variant MyList = MyNil : Unit | MyCons: Module:MyCons ;
|
||||
|
||||
}
|
||||
|
||||
"""
|
||||
@ -133,6 +149,28 @@ class PreprocessorSpec extends AnyWordSpec with Matchers with TableDrivenPropert
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"fails on too deep values" in {
|
||||
|
||||
def mkMyList(n: Int) =
|
||||
Iterator.range(0, n).foldLeft[Value[Nothing]](ValueVariant(None, myNilCons, ValueUnit)) {
|
||||
case (v, n) =>
|
||||
ValueVariant(
|
||||
None,
|
||||
myConsCons,
|
||||
ValueRecord(None, ImmArray(None -> ValueInt64(n.toLong), None -> v)),
|
||||
)
|
||||
}
|
||||
|
||||
val notTooBig = mkMyList(49)
|
||||
val tooBig = mkMyList(50)
|
||||
|
||||
translateValue(Ast.TTyCon(myListTyCons), notTooBig) shouldBe a[ResultDone[_]]
|
||||
inside(translateValue(Ast.TTyCon(myListTyCons), tooBig)) {
|
||||
case ResultError(Error.Preprocessing(err)) =>
|
||||
err shouldBe Error.Preprocessing.ValueNesting(tooBig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user