LF: Scala serializability checker handle exceptions (#8363)

This is part of #8020.

CHANGELOG_BEGIN
CHANGELOG_END
This commit is contained in:
Remy 2021-01-04 15:25:25 +01:00 committed by GitHub
parent 2083f74cf2
commit 425fca6541
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 51 deletions

View File

@ -62,7 +62,9 @@ private[validation] object Serializability {
checkType(targ)
case TBuiltin(builtinType) =>
builtinType match {
case BTInt64 | BTText | BTTimestamp | BTDate | BTParty | BTBool | BTUnit =>
case BTInt64 | BTText | BTTimestamp | BTDate | BTParty | BTBool | BTUnit |
BTAnyException | BTGeneralError | BTArithmeticError | BTContractError =>
()
case BTNumeric =>
unserializable(URNumeric)
case BTList =>
@ -85,9 +87,6 @@ private[validation] object Serializability {
unserializable(URAny)
case BTTypeRep =>
unserializable(URTypeRep)
case BTAnyException | BTArithmeticError | BTContractError | BTGeneralError =>
// TODO https://github.com/digital-asset/daml/issues/8020
sys.error("exceptions not supported")
}
case TForall(_, _) =>
unserializable(URForall)
@ -135,6 +134,15 @@ private[validation] object Serializability {
template.key.foreach(k => Env(version, world, context, SRKey, k.typ).checkType())
}
def checkException(
version: LanguageVersion,
world: World,
tyCon: TTyCon,
): Unit = {
val context = ContextDefException(tyCon.tycon)
Env(version, world, context, SRExceptionArg, tyCon).checkType()
}
def checkModule(world: World, pkgId: PackageId, module: Module): Unit = {
val version = world.lookupPackage(NoContext, pkgId).languageVersion
module.definitions.foreach {
@ -148,5 +156,9 @@ private[validation] object Serializability {
val tyCon = TTyCon(Identifier(pkgId, QualifiedName(module.name, defName)))
checkTemplate(version, world, tyCon, template)
}
module.exceptions.keys.foreach { defName =>
val tyCon = TTyCon(Identifier(pkgId, QualifiedName(module.name, defName)))
checkException(version, world, tyCon)
}
}
}

View File

@ -267,7 +267,7 @@ private[validation] object Typing {
mod.templates.foreach {
case (dfnName, template) =>
val tyConName = TypeConName(pkgId, QualifiedName(mod.name, dfnName))
val env = Env(languageVersion, world, ContextTemplate(pkgId, mod.name, dfnName), Map.empty)
val env = Env(languageVersion, world, ContextTemplate(tyConName), Map.empty)
world.lookupDataType(env.ctx, tyConName) match {
case DDataType(_, ImmArray(), DataRecord(_)) =>
env.checkTemplate(tyConName, template)

View File

@ -55,13 +55,13 @@ final case class ContextDefDataType(tycon: TypeConName) extends Context {
def pretty: String = s"data type ${tycon.qualifiedName}"
}
final case class ContextTemplate(tycon: TypeConName) extends Context {
def pretty: String = s"data type ${tycon.qualifiedName}"
def pretty: String = s"template definition ${tycon.qualifiedName}"
}
final case class ContextDefException(tycon: TypeConName) extends Context {
def pretty: String = s"exception type ${tycon.qualifiedName}"
def pretty: String = s"exception definition ${tycon.qualifiedName}"
}
final case class ContextDefValue(ref: ValueRef) extends Context {
def pretty: String = s"value type ${ref.qualifiedName}"
def pretty: String = s"value definition ${ref.qualifiedName}"
}
final case class ContextLocation(loc: Location) extends Context {
def pretty: String =
@ -101,6 +101,9 @@ case object SRTemplateArg extends SerializabilityRequirement {
case object SRChoiceArg extends SerializabilityRequirement {
def pretty: String = "choice argument"
}
case object SRExceptionArg extends SerializabilityRequirement {
def pretty: String = "exception argument"
}
case object SRChoiceRes extends SerializabilityRequirement {
def pretty: String = "choice result"
}

View File

@ -12,8 +12,6 @@ import org.scalatest.prop.TableDrivenPropertyChecks
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import scala.util.Try
class SerializabilitySpec extends AnyWordSpec with TableDrivenPropertyChecks with Matchers {
"Serializability checking" should {
@ -164,53 +162,52 @@ class SerializabilitySpec extends AnyWordSpec with TableDrivenPropertyChecks wit
}
module PositiveTestCase1 {
record UnserializableRecord = {};
template (this : UnserializableRecord) = { // disallowed unserializable type
precondition True,
signatories Nil @Party,
observers Nil @Party,
agreement "Agreement",
choices {
choice Ch (self) (i : Mod:SerializableType) :
Mod:SerializableType, controllers $partiesAlice
to upure @Mod:SerializableType (Mod:SerializableType {})
}
} ;
}
record UnserializableRecord = {};
template (this : UnserializableRecord) = { // disallowed unserializable type
precondition True,
signatories Nil @Party,
observers Nil @Party,
agreement "Agreement",
choices {
choice Ch (self) (i : Mod:SerializableType) :
Mod:SerializableType, controllers $partiesAlice
to upure @Mod:SerializableType (Mod:SerializableType {})
}
} ;
}
module PositiveTestCase2 {
record @serializable SerializableRecord = {};
record @serializable SerializableRecord = {};
template (this : SerializableRecord) = {
precondition True,
signatories Nil @Party,
observers Nil @Party,
agreement "Agreement",
choices {
choice Ch (self) (i : Mod:UnserializableType) : // disallowed unserializable type
Unit, controllers $partiesAlice to
upure @Unit ()
}
} ;
}
template (this : SerializableRecord) = {
precondition True,
signatories Nil @Party,
observers Nil @Party,
agreement "Agreement",
choices {
choice Ch (self) (i : Mod:UnserializableType) : // disallowed unserializable type
Unit, controllers $partiesAlice to
upure @Unit ()
}
} ;
}
module PositiveTestCase3 {
record @serializable SerializableRecord = {};
record @serializable SerializableRecord = {};
template (this : SerializableRecord) = {
precondition True,
signatories Nil @Party,
observers Nil @Party,
agreement "Agreement",
choices {
choice Ch (self) (i : Mod:SerializableType) :
Mod:UnserializableType, controllers $partiesAlice to // disallowed unserializable type
upure @Mod:UnserializableType (Mod:UnserializableType {})
}
} ;
}
template (this : SerializableRecord) = {
precondition True,
signatories Nil @Party,
observers Nil @Party,
agreement "Agreement",
choices {
choice Ch (self) (i : Mod:SerializableType) :
Mod:UnserializableType, controllers $partiesAlice to // disallowed unserializable type
upure @Mod:UnserializableType (Mod:UnserializableType {})
}
} ;
}
"""
val positiveTestCases = Table(
@ -227,6 +224,33 @@ class SerializabilitySpec extends AnyWordSpec with TableDrivenPropertyChecks wit
}
"reject unserializable exception definitions" in {
val pkg =
p"""
// well-formed module
module NegativeTestCase {
record @serializable SerializableRecord = { message: Text } ;
exception SerializableRecord = {
message \(e: NegativeTestCase:SerializableRecord) -> NegativeTestCase:SerializableRecord {message} e
} ;
}
module PositiveTestCase {
record UnserializableRecord = { message: Text } ;
exception UnserializableRecord = {
message \(e: PositiveTestCase:UnserializableRecord) -> PositiveTestCase:UnserializableRecord {message} e
} ;
}
"""
check(pkg, "NegativeTestCase")
an[EExpectedSerializableType] shouldBe thrownBy(check(pkg, "PositiveTestCase"))
}
"reject unserializable contract id" in {
val pkg =
@ -331,7 +355,7 @@ class SerializabilitySpec extends AnyWordSpec with TableDrivenPropertyChecks wit
val w = world(pkg)
val longModName = DottedName.assertFromString(modName)
val mod = pkg.modules(longModName)
require(Try(Typing.checkModule(w, defaultPackageId, mod)).isSuccess)
Typing.checkModule(w, defaultPackageId, mod)
Serializability.checkModule(w, defaultPackageId, mod)
}