-
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
interface support in the interface library #13154
Conversation
- it seems to be more convenient for interface library users to *not* treat LF interfaces as InterfaceTypes, but that's not 100% clear yet
…ace-library-with-interfaces
…ce-library-with-interfaces
…ace-library-with-interfaces
…ace-library-with-interfaces
CHANGELOG_BEGIN CHANGELOG_END
} | ||
} | ||
} | ||
lazy val itpEI = EnvironmentInterface.fromReaderInterfaces(itp).resolveChoices |
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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], |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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]( |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
* l.resolveChoices |+| r.resolveChoices | ||
* // is possibly less well-resolved than | ||
* (l |+| r).resolveChoices |
There was a problem hiding this comment.
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], |
There was a problem hiding this comment.
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
…ace-library-with-interfaces
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?" |
There was a problem hiding this 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] |
There was a problem hiding this comment.
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
.
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) |
There was a problem hiding this comment.
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)
}
There was a problem hiding this comment.
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) = |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// ^ never actually used, as far as I can tell -SC | ||
\/-(none) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok good :)
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interesting approach xD
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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], |
There was a problem hiding this comment.
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]( |
There was a problem hiding this comment.
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.
- suggested by @realvictorprm; thanks
- suggested by @realvictorprm; thanks
- suggested by @realvictorprm; thanks
…ace-library-with-interfaces
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
andEnvironmentInterface
: 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 astypeDecls
inEnvironmentInterface
; interfaces aren't included intypeDecls
because whereI
is an interface onlyContractId 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", andiface.DefInterface
is the new concept.Fixes #13036.
Pull Request Checklist
CHANGELOG_BEGIN
andCHANGELOG_END
tagsNOTE: CI is not automatically run on non-members pull-requests for security
reasons. The reviewer will have to comment with
/AzurePipelines run
totrigger the build.