LF: type and compile Interface fixed choices (#11175)

* LF: type and compile Interface fixed choices

CHANGELOG_BEGIN
CHANGELOG_END

* cleanup

* fix some token

* cleanup

* Address sophia's review

* add comment

* cosmetic

* fix
This commit is contained in:
Remy 2021-10-12 16:48:36 +02:00 committed by GitHub
parent 652d56999b
commit b2988bc79a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 134 additions and 141 deletions

View File

@ -1,8 +1,6 @@
-- Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE CPP #-}
module DA.Experimental.Interface(
interfaceCreate,
interfaceSignatory,
@ -12,10 +10,10 @@ module DA.Experimental.Interface(
import GHC.Types (primitive)
interfaceCreate: t -> Update (ContractId t)
interfaceCreate = primitive @"$INTERFACE_CREATE"
interfaceCreate payload = primitive @"$RESOLVE_VIRTUAL_CREATE" payload payload
interfaceSignatory: t -> [Party]
interfaceSignatory = primitive @"$INTERFACE_SIGNATORIES"
interfaceSignatory payload = primitive @"$RESOLVE_VIRTUAL_SIGNATORY" payload payload
interfaceObserver: t -> [Party]
interfaceObserver = primitive @"$INTERFACE_OBSERVERS"
interfaceObserver payload = primitive @"$RESOLVE_VIRTUAL_OBSERVER" payload payload

View File

@ -338,8 +338,11 @@ private[lf] final class Compiler(
module.interfaces.foreach { case (ifaceName, iface) =>
val identifier = Identifier(pkgId, QualifiedName(module.name, ifaceName))
addDef(compileFetchInterface(identifier))
iface.fixedChoices.values.foreach(
builder += compileFixedChoice(identifier, iface.param, _)
)
iface.virtualChoices.values.foreach(
builder += compileChoiceInterface(identifier, _)
builder += compileVirtualChoice(identifier, _)
)
}
@ -1016,22 +1019,70 @@ private[lf] final class Compiler(
}
}
private[this] def compileChoiceInterface(
// TODO https://github.com/digital-asset/daml/issues/10810:
// Try to factorise this with compileChoiceBody above.
private[this] def compileFixedChoiceBody(
ifaceId: TypeConName,
param: ExprVarName,
choice: TemplateChoice,
)(
choiceArgPos: Position,
cidPos: Position,
tokenPos: Position,
) = withEnv { _ =>
let(SBUFetchInterface(ifaceId)(svar(cidPos))) { payloadPos =>
addExprVar(param, payloadPos)
addExprVar(choice.argBinder._1, choiceArgPos)
let(
// TODO https://github.com/digital-asset/daml/issues/10810:
// Here we insert an Exercise Node with the interface Id instead of the template Id. \
// Review if that can cause issues.
SBUBeginExercise(ifaceId, choice.name, choice.consuming, byKey = false)(
svar(choiceArgPos),
svar(cidPos),
compile(choice.controllers),
choice.choiceObservers match {
case Some(observers) => compile(observers)
case None => SEValue.EmptyList
},
)
) { _ =>
addExprVar(choice.selfBinder, cidPos)
SEScopeExercise(ifaceId, app(compile(choice.update), svar(tokenPos)))
}
}
}
// TODO https://github.com/digital-asset/daml/issues/10810:
// Here we fetch twice, once by interface Id once by template Id. Try to bypass the second fetch.
private[this] def compileVirtualChoice(
ifaceId: TypeConName,
choice: InterfaceChoice,
): (SDefinitionRef, SDefinition) =
topLevelFunction(ChoiceDefRef(ifaceId, choice.name), 3) { case List(cidPos, choiceArgPos, _) =>
withEnv { _ =>
let(
SBUPreFetchInterface(ifaceId)(svar(cidPos))
) { tmplArgPos =>
SBUChoiceInterface(ifaceId, choice.name)(
topLevelFunction(ChoiceDefRef(ifaceId, choice.name), 3) {
case List(cidPos, choiceArgPos, tokenPos) =>
let(SBUFetchInterface(ifaceId)(svar(cidPos))) { payloadPos =>
SBResolveVirtualChoice(choice.name)(
svar(payloadPos),
svar(cidPos),
svar(choiceArgPos),
svar(tmplArgPos),
svar(tokenPos),
)
}
}
}
private[this] def compileFixedChoice(
ifaceId: TypeConName,
param: ExprVarName,
choice: TemplateChoice,
): (SDefinitionRef, SDefinition) =
topLevelFunction(ChoiceDefRef(ifaceId, choice.name), 3) {
case List(cidPos, choiceArgPos, tokenPos) =>
compileFixedChoiceBody(ifaceId, param, choice)(
choiceArgPos,
cidPos,
tokenPos,
)
}
private[this] def compileChoice(
@ -1425,19 +1476,14 @@ private[lf] final class Compiler(
compileFetchBody(tmplId, tmpl)(cidPos, None, tokenPos)
}
// TODO https://github.com/digital-asset/daml/issues/10810:
// Here we fetch twice, once by interface Id once by template Id. Try to bypass the second fetch.
private[this] def compileFetchInterface(
ifaceId: Identifier
): (SDefinitionRef, SDefinition) =
topLevelFunction(FetchDefRef(ifaceId), 2) { case List(cidPos, _) =>
withEnv { _ =>
let(
SBUPreFetchInterface(ifaceId)(svar(cidPos))
) { tmplArgPos =>
SBUFetchInterface(ifaceId)(
svar(cidPos),
svar(tmplArgPos),
)
}
topLevelFunction(FetchDefRef(ifaceId), 2) { case List(cidPos, tokenPos) =>
let(SBUFetchInterface(ifaceId)(svar(cidPos))) { payloadPos =>
SBResolveVirtualFetch(svar(payloadPos), svar(cidPos), svar(tokenPos))
}
}

View File

@ -1072,8 +1072,8 @@ private[lf] object SBuiltin {
}
}
// Similar to SBUFetch but doesn't perform any checks on the template id, and is never "by key".
final case class SBUPreFetchInterface(ifaceId: TypeConName) extends OnLedgerBuiltin(1) {
// Similar to SBUFetch but and is never performed "by key".
final case class SBUFetchInterface(ifaceId: TypeConName) extends OnLedgerBuiltin(1) {
override protected def execute(
args: util.ArrayList[SValue],
machine: Machine,
@ -1081,105 +1081,63 @@ private[lf] object SBuiltin {
): Unit = {
val coid = getSContractId(args, 0)
onLedger.cachedContracts.get(coid) match {
case Some(cached) => {
machine.returnValue = cached.value
}
case None => {
case Some(cached) =>
machine.compiledPackages.getDefinition(
ImplementsDefRef(cached.templateId, ifaceId)
) match {
case Some(_) =>
machine.returnValue = cached.value
case None =>
machine.ctrl = SEDamlException(
// TODO https://github.com/digital-asset/daml/issues/10810:
// Create a more specific exception.
IE.WronglyTypedContract(coid, ifaceId, cached.templateId)
)
}
case None =>
throw SpeedyHungry(
SResultNeedContract(
coid,
ifaceId, // not actually used, maybe this param should be dropped from SResultNeedContract
onLedger.committers,
{ case V.ContractInst(actualTmplId, V.VersionedValue(_, arg), _) =>
val keyExpr = SEApp(SEVal(KeyDefRef(actualTmplId)), Array(SELocS(1)))
machine.pushKont(KCacheContract(machine, actualTmplId, coid))
machine.ctrl = SELet1(
SEImportValue(Ast.TTyCon(actualTmplId), arg),
cachedContractStruct(
SELocS(1),
SEApp(SEVal(SignatoriesDefRef(actualTmplId)), Array(SELocS(1))),
SEApp(SEVal(ObserversDefRef(actualTmplId)), Array(SELocS(1))),
keyExpr,
),
)
machine.compiledPackages.getDefinition(
ImplementsDefRef(actualTmplId, ifaceId)
) match {
case Some(_) =>
machine.pushKont(KCacheContract(machine, actualTmplId, coid))
machine.ctrl = SELet1(
SEImportValue(Ast.TTyCon(actualTmplId), arg),
cachedContractStruct(
SELocS(1),
SEApp(SEVal(SignatoriesDefRef(actualTmplId)), Array(SELocS(1))),
SEApp(SEVal(ObserversDefRef(actualTmplId)), Array(SELocS(1))),
SEApp(SEVal(KeyDefRef(actualTmplId)), Array(SELocS(1))),
),
)
case None =>
machine.ctrl =
// TODO https://github.com/digital-asset/daml/issues/10810:
// Create a more specific exception.
SEDamlException(IE.WronglyTypedContract(coid, ifaceId, actualTmplId))
}
},
)
)
}
}
}
}
// SBUFetchInterface uses the contract payload obtained from SBUPreFetchInterface
// to call the proper FetchDefRef with the actual template id, and performs an
// implements check.
//
// Interfaces have a "toll-free" representation with the underlying template,
// since the template's SRecord already includes the type constructor (templateId)
// of its template, we shouldn't need to wrap or change the fetched template value
// in any way. (Unless we later need to enforce the distinction between templates
// and interfaces at the speedy value level.)
final case class SBUFetchInterface(
ifaceId: TypeConName
) extends SBuiltin(2) {
override private[speedy] def execute(
args: util.ArrayList[SValue],
machine: Machine,
): Unit = {
val coid = getSContractId(args, 0)
val SRecord(tmplId, _, _) = getSRecord(args, 1)
// After SBUPreFetchInterface, the template's package should already be loaded
// in compiledPackages, so SImplementsDefRef will be defined if the template
// implements the interface.
machine.compiledPackages.getDefinition(ImplementsDefRef(tmplId, ifaceId)) match {
case Some(_) =>
machine.ctrl = FetchDefRef(tmplId)(
SEValue(SContractId(coid)),
SEValue(SToken),
)
case None =>
machine.ctrl = SEDamlException(
IE.WronglyTypedContract(coid, ifaceId, tmplId)
// TODO https://github.com/digital-asset/daml/issues/10810:
// Maybe create a more specific exception.
)
}
}
// Return a definition matching the templateId of a given payload
sealed class SBResolveVirtual(toDef: Ref.Identifier => SDefinitionRef) extends SBuiltin(1) {
override private[speedy] def execute(args: util.ArrayList[SValue], machine: Machine): Unit =
machine.ctrl = SEVal(toDef(getSRecord(args, 0).id))
}
// Very similar to SBUFetchInterface. We use the payload from SBUPreFetchInterface
// to call the appropriate ChoiceDefRef with the actual template id, and performs
// an implements check.
final case class SBUChoiceInterface(
ifaceId: TypeConName,
choiceName: ChoiceName,
) extends SBuiltin(3) {
override private[speedy] def execute(
args: util.ArrayList[SValue],
machine: Machine,
): Unit = {
val coid = getSContractId(args, 0)
val choiceArg = args.get(1)
val SRecord(tmplId, _, _) = getSRecord(args, 2)
// After SBUPreFetchInterface, the template's package should already be loaded
// in compiledPackages, so SImplementsDefRef will be defined if the template
// implements the interface.
machine.compiledPackages.getDefinition(ImplementsDefRef(tmplId, ifaceId)) match {
case Some(_) =>
machine.ctrl = ChoiceDefRef(tmplId, choiceName)(
SEValue(SContractId(coid)),
SEValue(choiceArg),
SEValue(SToken),
)
case None =>
machine.ctrl = SEDamlException(
IE.WronglyTypedContract(coid, ifaceId, tmplId)
// TODO https://github.com/digital-asset/daml/issues/10810:
// Maybe create a more specific exception.
)
}
}
}
final case object SBResolveVirtualFetch extends SBResolveVirtual(FetchDefRef)
final case class SBResolveVirtualChoice(choiceName: ChoiceName)
extends SBResolveVirtual(ChoiceDefRef(_, choiceName))
// Convert an interface to a given template type if possible. Since interfaces have the
// same representation as the underlying template, we only need to perform a check
@ -1693,36 +1651,18 @@ private[lf] object SBuiltin {
object SBExperimental {
private sealed abstract class SBExperimental(val name: String, arity: Int)
extends SBuiltin(arity)
private object SBExperimentalAnswer extends SBExperimental("ANSWER", 1) {
override private[speedy] def execute(args: util.ArrayList[SValue], machine: Machine) = {
private object SBExperimentalAnswer extends SBuiltin(1) {
override private[speedy] def execute(args: util.ArrayList[SValue], machine: Machine) =
machine.returnValue = SInt64(42L)
}
}
private final class SBExperimentalInterfaceDef(
toSDefRef: Identifier => SDefinitionRef,
name: String,
arity: Int,
) extends SBExperimental(name, arity) {
override private[speedy] def execute(
args: util.ArrayList[SValue],
machine: Machine,
): Unit = {
val tmplId = getSRecord(args, 0).id
machine.ctrl = SEApp(SEVal(toSDefRef(tmplId)), args.asScala.view.map(SEValue(_)).toArray)
}
}
private val mapping: Map[String, SEBuiltin] =
List[SBExperimental](
SBExperimentalAnswer,
new SBExperimentalInterfaceDef(CreateDefRef, "INTERFACE_CREATE", 2),
new SBExperimentalInterfaceDef(SignatoriesDefRef, "INTERFACE_SIGNATORIES", 1),
new SBExperimentalInterfaceDef(ObserversDefRef, "INTERFACE_OBSERVERS", 1),
).map(x => x.name -> SEBuiltin(x)).toMap
private val mapping: Map[String, SExpr] =
List(
"ANSWER" -> SBExperimentalAnswer,
"RESOLVE_VIRTUAL_CREATE" -> new SBResolveVirtual(CreateDefRef),
"RESOLVE_VIRTUAL_SIGNATORY" -> new SBResolveVirtual(SignatoriesDefRef),
"RESOLVE_VIRTUAL_OBSERVER" -> new SBResolveVirtual(ObserversDefRef),
).view.map { case (name, builtin) => name -> SEBuiltin(builtin) }.toMap
def apply(name: String): SExpr =
mapping.getOrElse(name, SBError(SEValue(SText(s"experimental $name not supported."))))

View File

@ -304,12 +304,11 @@ private[validation] object Typing {
throw EExpectedExceptionableType(env.ctx, tyConName)
}
}
mod.interfaces.foreach { case (ifaceName, defInterface) =>
mod.interfaces.foreach { case (ifaceName, iface) =>
// uniquess of choice names is already checked on construction of the choice map.
val tyConName = TypeConName(pkgId, QualifiedName(mod.name, ifaceName))
val env = Env(langVersion, interface, ContextDefInterface(tyConName), Map.empty)
defInterface.virtualChoices.values.foreach(env.checkIfaceChoice(_))
defInterface.methods.values.foreach(env.checkIfaceMethod(_))
env.checkDefIface(tyConName, iface)
}
}
@ -458,6 +457,16 @@ private[validation] object Typing {
implementations.values.foreach(env.checkIfaceImplementation(tplName, _))
}
def checkDefIface(ifaceName: TypeConName, iface: DefInterface): Unit =
iface match {
case DefInterface(param, virtualChoices, fixedChoices, methods) =>
fixedChoices.values.foreach(
introExprVar(param, TTyCon(ifaceName)).checkChoice(ifaceName, _)
)
virtualChoices.values.foreach(checkIfaceChoice)
methods.values.foreach(checkIfaceMethod)
}
def checkIfaceChoice(choice: InterfaceChoice): Unit = {
checkType(choice.argType, KStar)
checkType(choice.returnType, KStar)