Skip to content
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

interface support in the interface library #13154

Merged
merged 41 commits into from
Mar 15, 2022

Conversation

S11001001
Copy link
Contributor

@S11001001 S11001001 commented Mar 3, 2022

This adds the presence of interface names, as well as lists of choices and their argument/return types for each interface.

It also changes the semantics of DefTemplate and EnvironmentInterface: each may contain "unresolved choices", which are the choices inherited from interfaces for each implementing template; EnvironmentInterface#resolveChoices will copy the full type information as a concrete choice for each implementing template, which should be much simpler for codegen and json-api.

A type generator should take account of the astInterfaces as well as typeDecls in EnvironmentInterface; interfaces aren't included in typeDecls because where I is an interface only ContractId I is serializable.

Unfortunately the name of the entity "interface" inconveniently clashes with the name of the larger concept the library deals with, "environment interface", so we refer to the former as "ast interface" or "def interface". So iface.Interface is the prior "interface of a dalf", iface.EnvironmentInterface is the "interface of a whole environment/dar", and iface.DefInterface is the new concept.

Fixes #13036.

Pull Request Checklist

  • Read and understand the contribution guidelines
  • Include appropriate tests
  • Set a descriptive title and thorough description
  • Add a reference to the issue this PR will solve, if appropriate
  • Include changelog additions in one or more commit message bodies between the CHANGELOG_BEGIN and CHANGELOG_END tags
  • Normal production system change, include purpose of change in description
  • If you mean to change the status of a component, please make sure you keep the Component Status page up to date.

NOTE: CI is not automatically run on non-members pull-requests for security
reasons. The reviewer will have to comment with /AzurePipelines run to
trigger the build.

@S11001001 S11001001 self-assigned this Mar 3, 2022
@S11001001 S11001001 added the team/ledger-clients Related to the Ledger Clients team's components. label Mar 3, 2022
S11001001 added 27 commits March 3, 2022 19:35
- it seems to be more convenient for interface library users to *not*
  treat LF interfaces as InterfaceTypes, but that's not 100% clear yet
CHANGELOG_BEGIN
CHANGELOG_END
}
}
}
lazy val itpEI = EnvironmentInterface.fromReaderInterfaces(itp).resolveChoices
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should fromReaderInterfaces just go ahead and resolveChoices here?

astInterfaces: Map[Identifier, DefInterface.FWT],
) {

// TODO(SC #13154) should fail instead in case of unresolved inherited choices?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is about how codegen & json-api should detect and report (or just ignore!) unresolved inherited choices.

daml_compile(
name = "InterfaceTestPackage",
srcs = ["src/test/daml/InterfaceTestPackage.daml"],
target = lf_version_configuration.get("dev"), # TODO(SC) remove when interfaces in default
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To spin off to a separate issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final case class DefTemplate[+Ty](choices: Map[Ref.Name, TemplateChoice[Ty]], key: Option[Ty]) {
final case class DefTemplate[+Ty](
choices: Map[Ref.ChoiceName, TemplateChoice[Ty]],
inheritedChoices: Map[Ref.ChoiceName, Ref.TypeConName],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "goal" is for this to be empty; I haven't provided any way to represent that phase transition for an EnvironmentInterface at the type level in this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably call this differently i.e. unresolvedInheritedChoices.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* `astInterfaces` from an [[EnvironmentInterface]]. If the result has any
* `inheritedChoices` left, these choices were not found.
*/
def resolveChoices[O >: Ty](
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly should be private as a utility for the function of same name in EnvironmentInterface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, however I think this could solve itself with resolving the issue that inheritedChoices should actually be empty in the end for the user.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It became apparent looking at #12689 that having EnvironmentInterface only as an entry point would be really inconvenient, hence the profusion of additional accessible utilities in this PR.

Comment on lines +29 to +31
* l.resolveChoices |+| r.resolveChoices
* // is possibly less well-resolved than
* (l |+| r).resolveChoices
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moreover forall l, r, (l.resolveChoices |+| r.resolveChoices).resolveChoices = (l |+| r).resolveChoices.

@@ -15,24 +15,60 @@ import scalaz.Semigroup
final case class EnvironmentInterface(
metadata: Map[PackageId, PackageMetadata],
typeDecls: Map[Identifier, InterfaceType],
)
astInterfaces: Map[Identifier, DefInterface.FWT],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bikeshed away.

- noticed when checking how well this will go in PackageService
@S11001001
Copy link
Contributor Author

One important thing to think about when considering this library is that it is beholden to its clients, not the LF format.

It was originally the internal ADT for representing LF signatures in the Scala codegen, so its choices of what to represent and what to leave out are largely driven by the needs of something like Scala codegen.

So the way to think about it is "what is a convenient ADT for a downstream application that needs to evaluate the external signatures in an LF file?"

Copy link
Contributor

@realvictorprm realvictorprm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I've understood 95% of the changes, at least currently :D Left some comments to ensure that my understanding is correct but I'm still fine with the changes. Thanks for implementing this :)

final case class DefInterface[+Ty](choices: Map[Ref.ChoiceName, TemplateChoice[Ty]])
extends DefTemplate.GetChoices[Ty]

object DefInterface extends FWTLike[DefInterface]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far I see we just add this to have a default alias called FWT on this type where the inner type is already set to Type.

Comment on lines 50 to 60
val typeDecls = (i +: o).iterator.flatMap { case Interface(packageId, _, typeDecls, _) =>
typeDecls mapKeys (Identifier(packageId, _))
}.toMap
val metadata = (i +: o).iterator.flatMap { case Interface(packageId, metadata, _) =>
val astInterfaces = (i +: o).iterator.flatMap {
case Interface(packageId, _, _, astInterfaces) =>
astInterfaces mapKeys (Identifier(packageId, _))
}.toMap
val metadata = (i +: o).iterator.flatMap { case Interface(packageId, metadata, _, _) =>
metadata.iterator.map(md => packageId -> md)
}.toMap
EnvironmentInterface(metadata, typeDecls)
EnvironmentInterface(metadata, typeDecls, astInterfaces)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we construct the sequence multiple times if we could just make e.g. a list from it? Is lazyness the reason?

  def fromReaderInterfaces(i: Interface, o: Interface*): EnvironmentInterface = {
    val it = (i +: o).toList
    val typeDecls = it.flatMap { case Interface(packageId, _, typeDecls, _) =>
      typeDecls mapKeys (Identifier(packageId, _))
    }.toMap
    val astInterfaces = it.flatMap { case Interface(packageId, _, _, astInterfaces) =>
      astInterfaces mapKeys (Identifier(packageId, _))
    }.toMap
    val metadata = it.flatMap { case Interface(packageId, metadata, _, _) =>
      metadata.iterator.map(md => packageId -> md)
    }.toMap
    EnvironmentInterface(metadata, typeDecls, astInterfaces)
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was one, copied to two, ... 163ec7b

*/
def resolveChoices(findInterface: PartialFunction[Identifier, DefInterface.FWT]): Interface = {
val outside = findInterface.lift
def findIface(id: Identifier) =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed that resolveChoices actually expects Ref.TypeConName which however is nothing else than an alias to Identifier. Things are already difficult here due to domain we are in and this isn't making it much better 😅 Anyways wanted to note this to see what you think about it @S11001001.

Copy link
Contributor Author

@S11001001 S11001001 Mar 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can change it to be universally one or the other (e: with respect to interface names as addressed in this PR only), LMK which one you think is better.

Copy link
Contributor Author

@S11001001 S11001001 Mar 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +146 to +147
// ^ never actually used, as far as I can tell -SC
\/-(none)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the DataInterface never is within the module defs but always separately listed in module.interfaces? Weird indeed then but how can we be sure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can be sure because prior to 895fa13 I was trying to use this code path on a real dar, and it was never hit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok good :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized an alternative explanation: they are present, but marked serializable=false. Which would still mean this code path is never exercised, just for a slightly different reason.

Either way, defensively ignoring them at multiple levels works fine for the interface library's clients.

lazy val itpEI = EnvironmentInterface.fromReaderInterfaces(itp).resolveChoices

"load without errors" in {
itp shouldBe itp
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting approach xD

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side effects!

itp.main.astInterfaces(TIf).choices get Useless should ===(Some(theUselessChoice))
}

def foundUselessChoice(foo: Option[InterfaceType]) = inside(foo) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using inside for test failure if foo isn't of case Some isn't common to me but it works and I don't have a better proposal how to do this without too much boilerplate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is for sure the best way to do it, especially if you are doing further pattern matching of the contents therein (as we are here).

final case class DefTemplate[+Ty](choices: Map[Ref.Name, TemplateChoice[Ty]], key: Option[Ty]) {
final case class DefTemplate[+Ty](
choices: Map[Ref.ChoiceName, TemplateChoice[Ty]],
inheritedChoices: Map[Ref.ChoiceName, Ref.TypeConName],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably call this differently i.e. unresolvedInheritedChoices.

* `astInterfaces` from an [[EnvironmentInterface]]. If the result has any
* `inheritedChoices` left, these choices were not found.
*/
def resolveChoices[O >: Ty](
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, however I think this could solve itself with resolving the issue that inheritedChoices should actually be empty in the end for the user.

@S11001001 S11001001 enabled auto-merge (squash) March 15, 2022 18:09
@S11001001 S11001001 merged commit 0b5ad7a into main Mar 15, 2022
@S11001001 S11001001 deleted the 13036-iface-library-with-interfaces branch March 15, 2022 19:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component/daml-lf DAML-LF interfaces mvp team/ledger-clients Related to the Ledger Clients team's components.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add interface support in the //daml-lf/interface library
2 participants