mirror of
https://github.com/digital-asset/daml.git
synced 2024-11-09 15:37:05 +03:00
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:
parent
652d56999b
commit
b2988bc79a
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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."))))
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user