Skip to content

Commit

Permalink
Java bindings/interface support (digital-asset#13366)
Browse files Browse the repository at this point in the history
* WIP

* First working version of java codegen daml interface support

* Update language-support/java/codegen/BUILD.bazel

Co-authored-by: Remy <remy.haemmerle@daml.com>

* Fix compile errors

* Simplify code massivly, enjoy less duplication

changelog_begin

- The Java codegen now has basic support for daml interface definitions. Converting a contract id of a template implementing an interface to a contract id of the interface is possible and both executing interface choices is possible on the contract id of the interface and implementing template.

changelog_end

* Rename the test file to reflect it is a test file & enhance the inner test name & extend it further

* Fix test

* Refactor parts of TemplateClass.scala into multiple files

* Format that files!

* Minimize duplication further

* Remove unused comment

* Simplify code
Co-authored-by: Stephen Compall <stephen.compall@daml.com>

* Update language-support/java/codegen/src/main/scala/com/digitalasset/daml/lf/codegen/CodeGenRunner.scala

Co-authored-by: Stephen Compall <stephen.compall@daml.com>

* Further refactoring and renaming of the TemplateClassSpec to ContractIdClassBuilderSpec

* Fix formatting

* Add interface docs

* Remove unnecessary code generation of the Contract class for interface types

* Use less bool flags and more good function names :)

* Fix build

Co-authored-by: Remy <remy.haemmerle@daml.com>
Co-authored-by: Stephen Compall <stephen.compall@daml.com>
  • Loading branch information
3 people authored Mar 29, 2022
1 parent e724bef commit cc06073
Show file tree
Hide file tree
Showing 23 changed files with 902 additions and 415 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ final case class DefTemplate[+Ty](
choices: Map[Ref.ChoiceName, TemplateChoice[Ty]],
unresolvedInheritedChoices: Map[Ref.ChoiceName, Ref.TypeConName],
key: Option[Ty],
implementedInterfaces: Seq[Ref.TypeConName],
) extends DefTemplate.GetChoices[Ty] {
def map[B](f: Ty => B): DefTemplate[B] = Functor[DefTemplate].map(this)(f)

Expand All @@ -196,7 +197,7 @@ final case class DefTemplate[+Ty](
} yield (choiceName, tchoice)
resolution toRight pair
}
DefTemplate(choices ++ resolved, missing.toMap, key)
this.copy(choices = choices ++ resolved, unresolvedInheritedChoices = missing.toMap)
}

def getKey: j.Optional[_ <: Ty] =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.daml.lf
package iface
package com.daml.lf.iface

import java.{util => j}

Expand Down Expand Up @@ -78,22 +77,44 @@ final case class Interface(
* // but faster.
* }}}
*/
def resolveChoices(
findInterface: PartialFunction[Ref.TypeConName, DefInterface.FWT]
private def resolveChoices(
findInterface: PartialFunction[Ref.TypeConName, DefInterface.FWT],
failIfUnresolvedChoicesLeft: Boolean,
): Interface = {
val outside = findInterface.lift
def findIface(id: Identifier) =
if (id.packageId == packageId) astInterfaces get id.qualifiedName
else outside(id)
val tplFindIface = Function unlift findIface
val transformTemplate = {
def transform(ift: InterfaceType.Template) =
ift.copy(template = ift.template resolveChoices tplFindIface)
if (failIfUnresolvedChoicesLeft)
transform _ andThen (res =>
if (res.template.unresolvedInheritedChoices.isEmpty) res
else
throw new IllegalStateException(
s"Couldn't resolve all inherited choices for template $res"
)
)
else transform _
}
copy(typeDecls = typeDecls transform { (_, ift) =>
ift match {
case ift: InterfaceType.Template =>
ift.copy(template = ift.template resolveChoices tplFindIface)
case ift: InterfaceType.Template => transformTemplate(ift)
case n: InterfaceType.Normal => n
}
})
}

def resolveChoicesAndFailOnUnresolvableChoices(
findInterface: PartialFunction[Ref.TypeConName, DefInterface.FWT]
): Interface = resolveChoices(findInterface, failIfUnresolvedChoicesLeft = true)

def resolveChoicesAndIgnoreUnresolvedChoices(
findInterface: PartialFunction[Ref.TypeConName, DefInterface.FWT]
): Interface = resolveChoices(findInterface, failIfUnresolvedChoicesLeft = false)

}

object Interface {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ object InterfaceReader {
key <- dfn.key traverse (k => toIfaceType(name, k.typ))
} yield name -> (iface.InterfaceType.Template(
Record(fields),
DefTemplate(choices, dfn.inheritedChoices, key),
DefTemplate(choices, dfn.inheritedChoices, key, dfn.implements.keys),
): T)

private def visitChoice(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,9 @@ class InterfaceReaderSpec extends AnyWordSpec with Matchers with Inside {
}

"resolve choices internally" in {
foundUselessChoice(itp.main.resolveChoices(PartialFunction.empty).typeDecls get Foo)
foundUselessChoice(
itp.main.resolveChoicesAndIgnoreUnresolvedChoices(PartialFunction.empty).typeDecls get Foo
)
}
}

Expand Down
8 changes: 8 additions & 0 deletions docs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ load("@rules_pkg//:pkg.bzl", "pkg_tar")
load("@build_environment//:configuration.bzl", "mvn_version", "sdk_version")
load("@scala_version//:index.bzl", "scala_major_version")
load("//bazel_tools:scala.bzl", "da_scala_test")
load(
"//daml-lf/language:daml-lf.bzl",
"LF_VERSIONS",
"lf_version_configuration",
)

exports_files(
[
Expand Down Expand Up @@ -572,6 +577,9 @@ daml_test(
daml_test(
name = "bindings-java-daml-test",
srcs = glob(["source/app-dev/bindings-java/code-snippets/**/*.daml"]),
# FIXME: https://github.com/digital-asset/daml/issues/12051
# replace "dev" by "default", once interfaces are stable.
target = lf_version_configuration.get("dev"),
)

daml_test(
Expand Down
32 changes: 32 additions & 0 deletions docs/source/app-dev/bindings-java/code-snippets/Interfaces.daml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-- Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0

-- start snippet: interface example
module Interfaces where

interface TIf where
getOwner: Party
dup: Update (ContractId TIf)
choice Ham: ContractId TIf with
controller getOwner this
do dup this
choice Useless: ContractId TIf with
interfacely: ContractId TIf
controller getOwner this
do
dup this

template Child
with
party: Party
where
signatory party
choice Bar: () with
controller party
do
return ()

implements TIf where
getOwner = party
dup = toInterfaceContractId <$> create this
-- end snippet: interface example
83 changes: 82 additions & 1 deletion docs/source/app-dev/bindings-java/codegen.rst
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ The Java code generated for this variant is:
public static final Color fromValue(Value value$) { /* ... */ }
public final DamlEnum toValue() { /* ... */ }
public final DamlEnum toValue() { /* ... */ }
}
Expand Down Expand Up @@ -472,6 +472,87 @@ functions to convert back the container's entries.
);
Daml Interfaces
^^^^^^^^^^^^^^^
From this daml definition:
.. literalinclude:: ./code-snippets/Interfaces.daml
:language: daml
:start-after: -- start snippet: interface example
:end-before: -- end snippet: interface example
:caption: Interfaces.daml
The generated file for the interface definition can be seen below.
Effectively it is a class that contains only the inner type ContractId because one will always only be able to deal with Interfaces via their ContractId.
.. code-block:: java
:caption: interfaces/TIf.java
package interfaces
/* imports */
public final class TIf {
public static final Identifier TEMPLATE_ID = new Identifier("94fb4fa48cef1ec7d474ff3d6883a00b2f337666c302ec5e2b87e986da5c27a3", "Interfaces", "TIf");
public static final class ContractId extends com.daml.ledger.javaapi.data.codegen.ContractId<TIf> {
public ContractId(String contractId) { /* ... */ }
public ExerciseCommand exerciseUseless(Useless arg) { /* ... */ }
public ExerciseCommand exerciseHam(Ham arg) { /* ... */ }
}
}
For templates the code generation will be slightly different if a template implements interfaces.
Main difference here is that the choices from inherited interfaces are included in the class declaration.
Moreover to allow converting the ContractId of a template to an interface ContractId, an additional conversion method called `toInterfaceName` is generated.
.. code-block:: java
:caption: interfaces/Child.java
package interfaces
/* ... */
public final class Child extends Template {
/* ... */
public CreateAndExerciseCommand createAndExerciseHam(Ham arg) { /* ... */ }
public CreateAndExerciseCommand createAndExerciseHam() { /* ... */ }
public CreateAndExerciseCommand createAndExerciseUseless(Useless arg) { /* ... */ }
public CreateAndExerciseCommand createAndExerciseUseless(TIf.ContractId interfacely) { /* ... */ }
/* ... */
public static final class ContractId extends com.daml.ledger.javaapi.data.codegen.ContractId<Child> {
/* ... */
public ExerciseCommand exerciseHam(Ham arg) { /* ... */ }
public ExerciseCommand exerciseUseless(Useless arg) { /* ... */ }
public ExerciseCommand exerciseHam() { /* ... */ }
public ExerciseCommand exerciseUseless(TIf.ContractId interfacely) { /* ... */ }
public TIf.ContractId toTIf() { /* ... */ }
}
/* ... */
}
.. _Value: /app-dev/bindings-java/javadocs/com/daml/ledger/javaapi/data/Value.html
.. _Unit: /app-dev/bindings-java/javadocs/com/daml/ledger/javaapi/data/Unit.html
.. _Bool: /app-dev/bindings-java/javadocs/com/daml/ledger/javaapi/data/Bool.html
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ object UtilSpec {

val trivialEnvInterfaceGen: Gen[EnvironmentInterface] = {
val fooRec = Record(ImmArraySeq.empty)
val fooTmpl = InterfaceType.Template(fooRec, DefTemplate(Map.empty, Map.empty, None))
val fooTmpl = InterfaceType.Template(fooRec, DefTemplate(Map.empty, Map.empty, None, Seq.empty))
val fooNorm = InterfaceType.Normal(DefDataType(ImmArraySeq.empty, fooRec))
implicit val idArb: Arbitrary[Identifier] = Arbitrary(idGen)
arbitrary[Map[Identifier, Boolean]] map { ids =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ object DependencyGraphSpec {
Map.empty,
Map.empty,
Some(TypeCon(TypeConName(Ref.Identifier assertFromString "a:b:It"), ImmArraySeq.empty)),
Seq.empty,
),
),
"a:b:NoKey" -> InterfaceType.Template(fooRec, DefTemplate(Map.empty, Map.empty, None)),
"a:b:NoKey" -> InterfaceType
.Template(fooRec, DefTemplate(Map.empty, Map.empty, None, Seq.empty)),
"a:b:It" -> InterfaceType.Normal(DefDataType(ImmArraySeq.empty, fooRec)),
) mapKeys Ref.Identifier.assertFromString,
astInterfaces = Map.empty,
Expand Down
4 changes: 3 additions & 1 deletion language-support/java/codegen/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,9 @@ daml_compile(
name = "ledger-tests-model",
srcs = glob(["src/ledger-tests/daml/**/*.daml"]),
enable_scenarios = True,
target = lf_version_configuration.get("default"),
# FIXME: https://github.com/digital-asset/daml/issues/12051
# replace "dev" by "default", once interfaces are stable.
target = lf_version_configuration.get("dev"),
)

dar_to_java(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-- Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0


module Interfaces where

interface TIf where
getOwner: Party
dup: Update (ContractId TIf)
choice Ham: ContractId TIf with
controller getOwner this
do dup this
choice Useless: ContractId TIf with
interfacely: ContractId TIf
controller getOwner this
do
dup this

template Child
with
party: Party
where
signatory party
choice Bar: () with
controller party
do
return ()

implements TIf where
getOwner = party
dup = toInterfaceContractId <$> create this
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.daml

import com.daml.ledger.api.testing.utils.SuiteResourceManagementAroundAll
import com.daml.ledger.resources.TestResourceContext
import org.scalatest.flatspec.AsyncFlatSpec
import org.scalatest.matchers.should.Matchers

class Interfaces
extends AsyncFlatSpec
with SandboxTestLedger
with Matchers
with TestResourceContext
with SuiteResourceManagementAroundAll {

import TestUtil._

behavior of "Generated Java code"

it should "contain all choices of an interface in templates implementing it" in withClient {
client =>
for {
alice <- allocateParty
} yield {
sendCmd(client, alice, interfaces.Child.create(alice))
readActiveContracts(interfaces.Child.Contract.fromCreatedEvent)(client, alice).foreach {
child =>
sendCmd(client, alice, child.id.exerciseHam(new interfaces.Ham()))
}
readActiveContracts(interfaces.Child.Contract.fromCreatedEvent)(client, alice).foreach {
child =>
sendCmd(client, alice, child.id.toTIf.exerciseHam(new interfaces.Ham()))
}
succeed
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package com.daml.lf.codegen
import java.nio.file.{Files, Path, StandardOpenOption}
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.{Executors, ThreadFactory, TimeUnit}

import com.daml.lf.archive.DarParser
import com.daml.lf.codegen.backend.Backend
import com.daml.lf.codegen.backend.java.JavaBackend
Expand All @@ -18,6 +17,7 @@ import com.daml.lf.iface.{Type => _, _}
import com.typesafe.scalalogging.StrictLogging
import org.slf4j.{Logger, LoggerFactory}

import scala.collection.immutable.Map
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, ExecutionContext, Future}

Expand Down Expand Up @@ -79,10 +79,16 @@ object CodeGenRunner extends StrictLogging {
}

val interfaces = interfacesAndPrefixes.map(_._1)
val environmentInterface =
EnvironmentInterface.fromReaderInterfaces(interfaces.head, interfaces.tail: _*)
val prefixes = interfacesAndPrefixes.collect { case (_, (key, Some(value))) =>
(key, value)
}.toMap
(interfaces, prefixes)
val fullyResolvedInterfaces =
interfaces.map(
_.resolveChoicesAndFailOnUnresolvableChoices(environmentInterface.astInterfaces)
)
(fullyResolvedInterfaces, prefixes)
}

private[CodeGenRunner] def generateFile(
Expand Down Expand Up @@ -196,6 +202,7 @@ object CodeGenRunner extends StrictLogging {
_ <- Future.traverse(preprocessedInterfaceTrees.interfaceTrees)(
processInterfaceTree(_, conf, prefixes)
)

} yield ()
}

Expand Down
Loading

0 comments on commit cc06073

Please sign in to comment.