-
Notifications
You must be signed in to change notification settings - Fork 205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Investigate app-runtime changes to allow templates to implement interfaces with clashing-name choices #13668
Comments
Java codegenImpactWithout changes, each overloaded choice name will get codegenned support for one of the choices, with the remainder silently discarded. Additionally, users will get invalid Java codegen takes the LF interface library as input; as the main structural invariant is lifted from the interface library, i.e. each template maps each Suggested changesRemove inherited exercise methods entirely. Where exercise-by-cid methods are currently generated directly in public interface Exercises<Cmd> extends com.daml.ledger.javaapi.data.codegen.Exercises<Cmd> {
default Cmd exerciseTestTemplate_Int(TestTemplate_Int arg) {
return makeExerciseCmd(arg.toValue());
}
default Cmd exerciseTestTemplate_Int(Long x) {
return exerciseTestTemplate_Int(new TestTemplate_Int(x));
}
// and another pair for each choice
}
For public static final class ContractId extends codegen.ContractId<Tpl> implements Exercises<ExerciseCommand> {
// remove current `to*` and `unsafeFrom*` methods
// for every implemented interface, generate
public pkg.Ifc.ContractId toInterface(pkg.Ifc.INTERFACE marker) { /* implementation elided */ }
public static ContractId unsafeFromInterface(pkg.Ifc.ContractId cid) { /* ... */ }
} We use createAndExercise redesignDeprecate every current public static final class createAnd extends codegen.CreateAnd<...> {
createAnd(...) {super(...)}
public pkg.Ifc.createAnd toInterface(pkg.Ifc.INTERFACE marker) {
return new pkg.Ifc.createAnd(...);
}
// one for each inherited interface
}
public createAnd createAnd() {return new createAnd(...);} Supply the needed interface definition changesIn interfaces themselves, we also produce We need an additional class, though, the marker class. This can serve a similar role for interfaces that public static final class INTERFACE extends codegen.InterfaceCompanion<...> {
INTERFACE() {/*...*/} // deliberately package-private
}
public static final INTERFACE INTERFACE = new INTERFACE(); We need a separate class because that is the only way to get the Will breakThis removes the new |
LF Interface libraryImpactThe interface library, in contrast with the LF decoded AST, encodes invariants about the serializable signatures in an LF archive as tightly as possible, so the codegens and JSON API have sound basis inputs. The As it will no longer be possible to invoke interface choices via the template ID on the ledger API, the representation of choice maps also needs to indicate when an interface ID needs to be passed to exercise. Suggested changesIn retrospect, managing // 1
choices: Map[Ref.ChoiceName, TemplateChoice[Ty]],
unresolvedInheritedChoices: NonEmpty[Map[Ref.ChoiceName, Ref.TypeConName]],
// 2
choices: Map[Ref.ChoiceName,
NonEmpty[Map[Option[Ref.Identifier], TemplateChoice[Ty]]]], Surprisingly, it is not necessary to change the map key from
We could still do this but for now the "drop unresolved choices on the floor" option still exists. Will breakAny code that uses template signatures from the interface library. But this is what we want. |
Typescript codegenImpactGenerated interface SomeInterfaceInterface<T> {
Split: Choice<...>
Another: Choice<...>
}
const SomeInterface: Template<...> & SomeInterfaceInterface<object>
interface ActualTemplateInterface extends SomeInterfaceInterface<ActualTemplate> {
Split: Choice<...>
Mine: Choice<...>
}
const ActualTemplate: Template<...> & ActualTemplateInterface As this is supposed to represent two incompatible Suggested changesExtend Rather than unconditionally copying the interface When generating the Currently and under this design, these are well-typed with respect to myLedger.exercise(ActualTemplate.Mine, atContractId, choicePayload)
myLedger.exercise(ActualTemplate.Another, atContractId, choicePayload)
myLedger.exercise(ActualTemplate.Split, atContractId, choicePayload) However, this is not well-typed with respect to myLedger.exercise(SomeInterface.Split, atContractId, choicePayload) We will recommend that users invoke the inherited choice (a la While JSON API can resolve ambiguities, for simplicity, codegen-driven exercises by contract ID and key will pass both the Will breakThis will not break anything for existing TypeScript codegen users on existing Daml code. |
JSON APIImpactJSON API assumes that you can pass a template ID for a gRPC exercise of an interface choice; this will no longer be allowed by gRPC, but we should keep supporting it for JSON API's own argument interface. There will be new error cases for when a choice resolution is ambiguous, similar to when package ID inference fails. Suggested changesA choice resolution additionally produces an optional interface ID; this is reflected in the interface library. When an interface ID is yielded, JSON API must exercise by interface ID; otherwise, it must exercise by template ID. For its own exercise endpoint, an optional
Note that it will continue to be required that create-and-exercise is an open question in #13653 at time of writing, so I have left that part TODO. Will breakThis will not break anything for existing JSON API users on existing Daml code. |
Trigger serviceImpactThe trigger runner's None of this complexity propagates upward to the trigger service. Suggested changesIncorporate the new trigger runner. Will breakTriggers that were written against the older trigger library may break and need to be recompiled; the new triggers will require the newly-compiled trigger service as well. There are no trigger-service-specific breaks. |
We don't have to commit to a solution right away, I'll make sure to think more about this next week. For the time being let's re-prioritize Scala codegen work. In that area we have more freedom to make changes, as long as they don't break existing code. 🕷️ |
Scala codegenImpactCodegen won't break in a way that is immediately apparent, but will have no way to generate the correct exercise method when confronted with overloaded names. It will also currently fail if an interface-contract-ID appears in any signature, even though interface-contract-ID types are serializable. Naturally, it must also adapt to changes in the LF interface library described above. Suggested changesFor every interface sealed abstract class If extends Interface[If]
object If extends InterfaceCompanion[If] {
override val id = // ...
implicit final class `If syntax`[+` ExOn`](private val id : ` ExOn`) extends AnyVal {
// exercise methods exactly like with templates
}
} Common utilities can be factored between the existing
Add to Add an implicit class to def toInterface[If](implicit ev: Implements[T, If]): ContractId[If] and likewise an
In each generated template companion, add one // exercise
cid.toInterface[If].exerciseFoo(party, args...)
// exercise by key
Tpl.key((alice, 42)).toInterface[If].exerciseFoo(party, args...)
// create-and-exercise
Tpl(alice, 1, 2).createAnd.toInterface[If].exerciseFoo(party, args...)
// coerce interface-cid to template-cid
icid.unsafeToTemplate[Tpl].exerciseBar(party, args) I am fairly certain we can elide the Will breakThis will undo the inheritance added as a workaround in #13858; all existing calls to interface exercise methods will no longer compile. As this feature has no external users, and possibly no internal users at time of writing, this is a minimal impact. |
There has been progress with regards to the Java codegen. I'll report here the outcome of the conversation I had with @S11001001. The idea is the following
This boils down to the following (the names are there only to illustrate the concept and are not to be considered final): templateContractId.toInterface(com.daml.Asset.INTERFACE).exerciseSomeChoice(arguments);
templateContractId.toInterface(com.example.Asset.INTERFACE).exerciseSomeChoice(arguments); This will work for edge cases as the one above in which there is a clash in unqualified names, choice names and arguments at the same time. Note that if you already have an interface contract ID you can invoke the choice method directly, making the experience uniform and especially well suited to focus your work around interfaces as we make them more prominent through Daml. As part of the conversation, we also mentioned the following to allow to issue a create-and-exercise like so: templateContractId.createAnd(arguments).toInterface(com.daml.Asset.INTERFACE).exerciseSomeChoice(arguments); The new method would also be available to used to run In order to make the experience smooth, we would also mark the Regardless, the approach is a breaking change with regards to the current status of Java codegen for interfaces, which is fine since we're still in the early access stage and breaking changes are allowed before we move on to GA. |
with the minor caveat payload.createAnd() |
@stefanobaghino-da I'm updating the detailed design, too, but to summarize we can split Java codegen into 3 blocks of work:
|
@stefanobaghino-da I've rewritten Java codegen to explain how to implement the new design. |
Referring to #13653, lifting this requirement will likely have a knock-on effect on runtime components where we have interface support already. The outcome of this ticket should be:
Possibly affected runtime components:
Unlikely to be affected but included for completeness:
The text was updated successfully, but these errors were encountered: