Allow type declarations at package scope - #699
Conversation
| ```wat | ||
| (component | ||
| (type (export "point") (record (field "x" u32) (field "y" u32))) | ||
| (type (export "api") (component | ||
| (export "local:demo/api" (instance | ||
| (type $point (record (field "x" u32) (field "y" u32))) | ||
| (export "move-to" (func (param "p" $point))) | ||
| )) | ||
| )) | ||
| ) | ||
| ``` |
There was a problem hiding this comment.
For myself this is the main point of question for me of how these top-level types would be encoded in WIT. This component as-is is not valid (doesn't pass wasm-tools validate), and I assume that you don't want to change the validation rules for components, so could this be updated with an encoding that's valid?
There was a problem hiding this comment.
Great question! Here's one idea I think might work:
For WIT interfaces like the above, we treat these dependencies on package-level types just like use of an interface that contains the analogous type definition, we just strip out the wrapping instance type:
(component $C
(type $Point (export "point") (record (field "x" u32) (field "y" u32)))
(type (export "api") (component
(import "local:demo/point" (type $Point' (eq $Point)))
(export "local:demo/api" (instance
(export "move-to" (func (param "p" $Point')))
))
))
)and then for the analogous WIT world (that exports move-to), we move the (import "local:demo/point" (type ...)) inside the inner component type so that it works like a type definition inside an imported interface, just not wrapped in an instance type:
(component $C
(type $Point (export "point") (record (field "x" u32) (field "y" u32)))
(type (export "api") (component
(export "local:demo/api" (component
(import "local:demo/point" (type $Point' (eq $Point)))
(export "move-to" (func (param "p" $Point')))
))
))
)f3c7c23 to
126b12a
Compare
Sharing a type vocabulary across interfaces requires inventing an interface to hold it, and that container reaches the artifact as an instance import that nothing calls. Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
126b12a to
2b91b6c
Compare
lukewagner
left a comment
There was a problem hiding this comment.
This all looks reasonable to me. I'll plan to leave this open until we get some further implementation feedback.
Co-authored-by: Luke Wagner <mail@lukewagner.name>
Align the encoding with the approved package-scope types design in WebAssembly/component-model#699. Instead of inlining a package-scope type into every interface/world instance and re-exporting it, a package-scope type is now encoded like a `use` of an interface without the wrapping instance: the definition is restated and bound with an `eq` import named by its fully-qualified `namespace:package/name`, so nothing has to be supplied to satisfy it. The referencing interface or world aliases that import into its instance/component type. A package-scope type that depends on one from another package puts the `import` on the package's own component, since there is no wrapping component-type to hold it there. Decoding recognizes those qualified type imports as package-scope dependencies and keeps ownership with the defining package, dropping the previous inline-coalescing pass. The printer emits the corresponding top-level `use` before the definitions that reference it. This makes the encoded output valid per Component Model validation and round-trips through decode/encode. Add golden fixtures mirroring the approved WIT.md examples plus unit tests covering foreign use, foreign dependency, versioned names, the export-before-use ordering, the resource rejection, and that `component new` exports the interface directly without inventing a types interface. Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
|
One point I'm personally a bit murky on: how is this expected to handle merging across packages? The componentization process routinely merges I could put this a different way which is that I don't feel I fully understand the rationale for adding this as a feature. I feel like this breaks encapsulation of type definitions within WIT packagse and makes something that should be local a global decision. Increasing the risk of not being able to merge world together gives rise to the problem of bindings work in isolation and don't work together which is a major composability hazard. Additionally this feels a bit weird to allow some types but not others. For example resources aren't allowed, and while I understand the rationale for not including them it means that this is a sort of "wart" where pieces don't compose together. |
Right now, I only need the types for the sake of #695 and since the spec can be additive, I am personally Ok focusing on types right now. Being said, better wait for @lukewagner since he has more context outside the Annotations as well. |
There might be some confusing wording that says "global" in the PR to be clarified, but what I believe is being proposed here (and shown in the WIT-to-WAT encodings) is using the existing package-scoped namespace (wherein any two As I mentioned in the design issue, I think this is a logical feature in isolation but, more importantly, if we want to do "annotations" in the style of #694 (big if! but maybe let's discuss that there) this feature is a natural basis. |
This PR allows
type,record,variant,enumandflagsdeclarations at package scope in WIT, as proposed in #694. Today, sharing a type vocabulary across interfaces requires inventing an interface to hold it, and that container reaches the artifact as an instance import that nothing calls.resourceis deliberately excluded. Resources carry identity rather than structure, so an interface is what gives a resource its identity, and package scope has nothing to offer there. That is why the grammar names apackage-typedef-itemrather than reusingtypedef-item, which already includesresource-item.