From 862e91607a58feaf02d260417241dac1aa6fcc15 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 13 Dec 2022 18:44:43 -0800 Subject: [PATCH 01/37] Start full implementation of `use` for WIT This commit is the first in what is likely to be a number of commits to implement the full proposal for `use` in `wit-parser` and `wit-component`. The immediate goal of this commit was to get a basic structure in place for resolution and an AST for `wit-parser` to have. To that end a new `UnresolvedPackage` container is added with a `Resolve` container which can have unresolved packages added to it. The main focus here is to get the `wit-parser` crate into a position where most of its tricky bits are implemented and out of the way. This includes resolution of multi-file packages, resolution of `use` within a package, etc. This additionally includes new features such as automatically topologically sorting everything inside containers and full elaboration of world imports/exports with. It's intended that the test suite of `wit-parser` is expanded to cover all of this new functionality, errors and success cases. To that end the tests here are: * Existing tests all become "single file WIT packages". They are packages with one document and no sibling documents. * A new test format, a directory, was added to the `tests/ui` folder which could house multi-file WIT packages which have optional package dependencies in a sub-`deps` folder. * The JSON output of a successful parse was removed due to it getting unwieldy to continue updating and in general not being too interesting. The `wit-component` crate is broken as a result of this commit, and the next step is to update that to using `Resolve` primarily. --- Cargo.toml | 1 + .../interfaces/diamond-disambiguate/join.wit | 10 + .../diamond-disambiguate/shared1.wit | 3 + .../diamond-disambiguate/shared2.wit | 3 + .../tests/interfaces/diamond.wat | 88 ++ .../tests/interfaces/diamond.wit | 29 + .../tests/interfaces/diamond.wit.print | 31 + crates/wit-parser/Cargo.toml | 5 +- crates/wit-parser/fuzz/.gitignore | 2 + crates/wit-parser/fuzz/Cargo.toml | 22 + crates/wit-parser/fuzz/fuzz_targets/parse.rs | 14 + crates/wit-parser/src/abi.rs | 98 +- crates/wit-parser/src/ast.rs | 429 +++++--- crates/wit-parser/src/ast/lex.rs | 159 +-- crates/wit-parser/src/ast/resolve.rs | 958 ++++++++++++------ crates/wit-parser/src/ast/toposort.rs | 149 +++ crates/wit-parser/src/lib.rs | 457 +++++---- crates/wit-parser/src/merge.rs | 214 ---- crates/wit-parser/src/resolve.rs | 629 ++++++++++++ crates/wit-parser/src/sizealign.rs | 13 +- crates/wit-parser/tests/all.rs | 276 +---- .../wit-parser/tests/ui/comments.wit.result | 22 - .../tests/ui/disambiguate-diamond/shared1.wit | 3 + .../tests/ui/disambiguate-diamond/shared2.wit | 3 + .../tests/ui/disambiguate-diamond/world.wit | 11 + .../tests/ui/embedded.wit.md.result | 24 - crates/wit-parser/tests/ui/empty.wit.result | 1 - .../deps/another-pkg/other-doc.wit | 1 + .../deps/different-pkg/the-doc.wit | 1 + .../deps/fastly/compute-at-edge.wit | 2 + .../foreign-deps/deps/foreign-pkg/the-doc.wit | 3 + .../foreign-deps/deps/some-pkg/some-doc.wit | 11 + .../ui/foreign-deps/deps/wasi/clocks.wit | 3 + .../ui/foreign-deps/deps/wasi/filesystem.wit | 5 + .../wit-parser/tests/ui/foreign-deps/root.wit | 31 + .../wit-parser/tests/ui/functions.wit.result | 100 -- crates/wit-parser/tests/ui/multi-file/bar.wit | 19 + crates/wit-parser/tests/ui/multi-file/foo.wit | 15 + .../ui/parse-fail/alias-no-type.wit.result | 2 +- .../ui/parse-fail/bad-diamond.wit.result | 8 + .../tests/ui/parse-fail/bad-diamond/a.wit | 9 + .../tests/ui/parse-fail/bad-diamond/b.wit | 9 + .../tests/ui/parse-fail/bad-diamond/join.wit | 4 + .../tests/ui/parse-fail/bad-function.wit | 5 + .../ui/parse-fail/bad-function.wit.result | 5 + .../tests/ui/parse-fail/bad-function2.wit | 5 + .../ui/parse-fail/bad-function2.wit.result | 5 + .../tests/ui/parse-fail/bad-pkg1.wit.result | 5 + .../tests/ui/parse-fail/bad-pkg1/root.wit | 3 + .../tests/ui/parse-fail/bad-pkg2.wit.result | 5 + .../ui/parse-fail/bad-pkg2/deps/bar/.gitkeep | 0 .../tests/ui/parse-fail/bad-pkg2/root.wit | 3 + .../tests/ui/parse-fail/bad-pkg3.wit.result | 5 + .../ui/parse-fail/bad-pkg3/deps/bar/.gitkeep | 0 .../ui/parse-fail/bad-pkg3/deps/bar/baz.wit | 0 .../tests/ui/parse-fail/bad-pkg3/root.wit | 3 + .../tests/ui/parse-fail/bad-pkg4.wit.result | 5 + .../ui/parse-fail/bad-pkg4/deps/bar/.gitkeep | 0 .../ui/parse-fail/bad-pkg4/deps/bar/baz.wit | 3 + .../tests/ui/parse-fail/bad-pkg4/root.wit | 3 + .../tests/ui/parse-fail/bad-pkg5.wit.result | 5 + .../ui/parse-fail/bad-pkg5/deps/bar/.gitkeep | 0 .../ui/parse-fail/bad-pkg5/deps/bar/baz.wit | 2 + .../tests/ui/parse-fail/bad-pkg5/root.wit | 3 + .../tests/ui/parse-fail/bad-pkg6.wit.result | 5 + .../ui/parse-fail/bad-pkg6/deps/bar/.gitkeep | 0 .../ui/parse-fail/bad-pkg6/deps/bar/baz.wit | 0 .../tests/ui/parse-fail/bad-pkg6/root.wit | 3 + .../tests/ui/parse-fail/cycle.wit.result | 6 +- .../tests/ui/parse-fail/cycle2.wit.result | 6 +- .../tests/ui/parse-fail/cycle3.wit.result | 6 +- .../tests/ui/parse-fail/cycle4.wit.result | 6 +- .../tests/ui/parse-fail/cycle5.wit.result | 6 +- .../ui/parse-fail/default-interface1.wit | 3 + .../parse-fail/default-interface1.wit.result | 5 + .../tests/ui/parse-fail/default-world1.wit | 3 + .../ui/parse-fail/default-world1.wit.result | 5 + .../parse-fail/duplicate-functions.wit.result | 2 +- .../parse-fail/duplicate-interface.wit.result | 2 +- .../ui/parse-fail/duplicate-type.wit.result | 2 +- .../ui/parse-fail/invalid-type-reference.wit | 8 + .../invalid-type-reference.wit.result | 5 + .../ui/parse-fail/invalid-type-reference2.wit | 4 + .../invalid-type-reference2.wit.result | 5 + .../tests/ui/parse-fail/invalid@filename.wit | 0 .../ui/parse-fail/invalid@filename.wit.result | 1 + .../tests/ui/parse-fail/pkg-cycle.wit.result | 5 + .../ui/parse-fail/pkg-cycle/deps/a1/root.wit | 3 + .../tests/ui/parse-fail/pkg-cycle/root.wit | 3 + .../tests/ui/parse-fail/pkg-cycle2.wit.result | 5 + .../ui/parse-fail/pkg-cycle2/deps/a1/root.wit | 3 + .../ui/parse-fail/pkg-cycle2/deps/a2/root.wit | 3 + .../tests/ui/parse-fail/pkg-cycle2/root.wit | 3 + .../ui/parse-fail/undefined-typed.wit.result | 2 +- .../tests/ui/parse-fail/unknown-interface.wit | 2 +- .../parse-fail/unknown-interface.wit.result | 8 +- ...default1.wit => unresolved-interface1.wit} | 2 +- .../unresolved-interface1.wit.result | 5 + .../ui/parse-fail/unresolved-interface2.wit | 6 + .../unresolved-interface2.wit.result | 5 + .../ui/parse-fail/unresolved-interface3.wit | 5 + .../unresolved-interface3.wit.result | 5 + .../ui/parse-fail/unresolved-interface4.wit | 5 + .../unresolved-interface4.wit.result | 5 + .../ui/parse-fail/unresolved-interface5.wit | 5 + .../unresolved-interface5.wit.result | 5 + .../tests/ui/parse-fail/unresolved-use1.wit | 5 + .../ui/parse-fail/unresolved-use1.wit.result | 5 + .../ui/parse-fail/unresolved-use10.wit.result | 8 + .../ui/parse-fail/unresolved-use10/bar.wit | 3 + .../ui/parse-fail/unresolved-use10/foo.wit | 2 + .../ui/parse-fail/unresolved-use11.wit.result | 8 + .../ui/parse-fail/unresolved-use11/bar.wit | 3 + .../ui/parse-fail/unresolved-use11/foo.wit | 2 + .../tests/ui/parse-fail/unresolved-use2.wit | 8 + .../ui/parse-fail/unresolved-use2.wit.result | 5 + .../tests/ui/parse-fail/unresolved-use3.wit | 9 + .../ui/parse-fail/unresolved-use3.wit.result | 5 + .../tests/ui/parse-fail/unresolved-use4.wit | 5 + .../ui/parse-fail/unresolved-use4.wit.result | 5 + .../tests/ui/parse-fail/unresolved-use5.wit | 6 + .../ui/parse-fail/unresolved-use5.wit.result | 5 + .../tests/ui/parse-fail/unresolved-use6.wit | 5 + .../ui/parse-fail/unresolved-use6.wit.result | 5 + .../tests/ui/parse-fail/unresolved-use7.wit | 8 + .../ui/parse-fail/unresolved-use7.wit.result | 5 + .../tests/ui/parse-fail/unresolved-use8.wit | 7 + .../ui/parse-fail/unresolved-use8.wit.result | 5 + .../tests/ui/parse-fail/unresolved-use9.wit | 7 + .../ui/parse-fail/unresolved-use9.wit.result | 5 + .../tests/ui/parse-fail/use-conflict.wit | 9 + .../ui/parse-fail/use-conflict.wit.result | 5 + .../tests/ui/parse-fail/use-conflict2.wit | 11 + .../ui/parse-fail/use-conflict2.wit.result | 5 + .../tests/ui/parse-fail/use-conflict3.wit | 11 + .../ui/parse-fail/use-conflict3.wit.result | 5 + .../tests/ui/parse-fail/use-cycle1.wit | 5 + .../tests/ui/parse-fail/use-cycle1.wit.result | 5 + .../tests/ui/parse-fail/use-cycle2.wit | 5 + .../tests/ui/parse-fail/use-cycle2.wit.result | 5 + .../tests/ui/parse-fail/use-cycle3.wit | 6 + .../tests/ui/parse-fail/use-cycle3.wit.result | 5 + .../tests/ui/parse-fail/use-cycle4.wit | 13 + .../tests/ui/parse-fail/use-cycle4.wit.result | 5 + .../ui/parse-fail/use-from-package-world.wit | 8 + .../use-from-package-world.wit.result | 5 + .../use-from-package-world2.wit.result | 8 + .../use-from-package-world2/bar.wit | 3 + .../use-from-package-world2/foo.wit | 2 + .../ui/parse-fail/world-default1.wit.result | 5 - .../tests/ui/parse-fail/world-default2.wit | 6 - .../ui/parse-fail/world-default2.wit.result | 5 - .../tests/ui/parse-fail/world-default3.wit | 8 - .../ui/parse-fail/world-default3.wit.result | 5 - .../ui/parse-fail/world-implicit-import1.wit | 10 + .../world-implicit-import1.wit.result | 5 + .../ui/parse-fail/world-interface-clash.wit | 2 + .../world-interface-clash.wit.result | 5 + .../tests/ui/parse-fail/world-same-fields.wit | 4 +- .../parse-fail/world-same-fields.wit.result | 4 +- .../parse-fail/world-same-fields2.wit.result | 2 +- .../ui/parse-fail/world-same-fields3.wit | 6 + .../parse-fail/world-same-fields3.wit.result | 5 + .../tests/ui/parse-fail/world-same-import.wit | 6 + .../parse-fail/world-same-import.wit.result | 5 + crates/wit-parser/tests/ui/shared-types.wit | 2 +- .../tests/ui/shared-types.wit.result | 52 - .../tests/ui/type-then-eof.wit.result | 16 - crates/wit-parser/tests/ui/types.wit.result | 418 -------- crates/wit-parser/tests/ui/use.wit | 31 + crates/wit-parser/tests/ui/wasi.wit.result | 108 -- crates/wit-parser/tests/ui/world-default.wit | 7 - .../tests/ui/world-default.wit.result | 20 - crates/wit-parser/tests/ui/world-diamond.wit | 20 + crates/wit-parser/tests/ui/worlds.wit | 13 +- crates/wit-parser/tests/ui/worlds.wit.result | 67 -- 176 files changed, 3038 insertions(+), 2161 deletions(-) create mode 100644 crates/wit-component/tests/interfaces/diamond-disambiguate/join.wit create mode 100644 crates/wit-component/tests/interfaces/diamond-disambiguate/shared1.wit create mode 100644 crates/wit-component/tests/interfaces/diamond-disambiguate/shared2.wit create mode 100644 crates/wit-component/tests/interfaces/diamond.wat create mode 100644 crates/wit-component/tests/interfaces/diamond.wit create mode 100644 crates/wit-component/tests/interfaces/diamond.wit.print create mode 100644 crates/wit-parser/fuzz/.gitignore create mode 100644 crates/wit-parser/fuzz/Cargo.toml create mode 100644 crates/wit-parser/fuzz/fuzz_targets/parse.rs create mode 100644 crates/wit-parser/src/ast/toposort.rs delete mode 100644 crates/wit-parser/src/merge.rs create mode 100644 crates/wit-parser/src/resolve.rs delete mode 100644 crates/wit-parser/tests/ui/comments.wit.result create mode 100644 crates/wit-parser/tests/ui/disambiguate-diamond/shared1.wit create mode 100644 crates/wit-parser/tests/ui/disambiguate-diamond/shared2.wit create mode 100644 crates/wit-parser/tests/ui/disambiguate-diamond/world.wit delete mode 100644 crates/wit-parser/tests/ui/embedded.wit.md.result delete mode 100644 crates/wit-parser/tests/ui/empty.wit.result create mode 100644 crates/wit-parser/tests/ui/foreign-deps/deps/another-pkg/other-doc.wit create mode 100644 crates/wit-parser/tests/ui/foreign-deps/deps/different-pkg/the-doc.wit create mode 100644 crates/wit-parser/tests/ui/foreign-deps/deps/fastly/compute-at-edge.wit create mode 100644 crates/wit-parser/tests/ui/foreign-deps/deps/foreign-pkg/the-doc.wit create mode 100644 crates/wit-parser/tests/ui/foreign-deps/deps/some-pkg/some-doc.wit create mode 100644 crates/wit-parser/tests/ui/foreign-deps/deps/wasi/clocks.wit create mode 100644 crates/wit-parser/tests/ui/foreign-deps/deps/wasi/filesystem.wit create mode 100644 crates/wit-parser/tests/ui/foreign-deps/root.wit delete mode 100644 crates/wit-parser/tests/ui/functions.wit.result create mode 100644 crates/wit-parser/tests/ui/multi-file/bar.wit create mode 100644 crates/wit-parser/tests/ui/multi-file/foo.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-diamond.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-diamond/a.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-diamond/b.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-diamond/join.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-function.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-function2.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg1/root.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg2.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg2/deps/bar/.gitkeep create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg2/root.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg3.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg3/deps/bar/.gitkeep create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg3/deps/bar/baz.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg3/root.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg4.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg4/deps/bar/.gitkeep create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg4/deps/bar/baz.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg4/root.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg5.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg5/deps/bar/.gitkeep create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg5/deps/bar/baz.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg5/root.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg6/deps/bar/.gitkeep create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg6/deps/bar/baz.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-pkg6/root.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/default-interface1.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/default-interface1.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/default-world1.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/default-world1.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/invalid-type-reference.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/invalid-type-reference.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/pkg-cycle.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/pkg-cycle/deps/a1/root.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/pkg-cycle/root.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/pkg-cycle2.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/deps/a1/root.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/deps/a2/root.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/root.wit rename crates/wit-parser/tests/ui/parse-fail/{world-default1.wit => unresolved-interface1.wit} (55%) create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-interface1.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-interface5.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-interface5.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use10/bar.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use10/foo.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use11.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use11/bar.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use11/foo.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use3.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use3.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use4.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use4.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use5.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use5.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use6.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use6.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use8.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use8.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-conflict.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-conflict.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-conflict2.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-conflict2.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-conflict3.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-conflict3.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-cycle2.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-cycle2.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-cycle3.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-cycle3.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-from-package-world.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-from-package-world.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-from-package-world2.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-from-package-world2/bar.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-from-package-world2/foo.wit delete mode 100644 crates/wit-parser/tests/ui/parse-fail/world-default1.wit.result delete mode 100644 crates/wit-parser/tests/ui/parse-fail/world-default2.wit delete mode 100644 crates/wit-parser/tests/ui/parse-fail/world-default2.wit.result delete mode 100644 crates/wit-parser/tests/ui/parse-fail/world-default3.wit delete mode 100644 crates/wit-parser/tests/ui/parse-fail/world-default3.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/world-interface-clash.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/world-interface-clash.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/world-same-import.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/world-same-import.wit.result delete mode 100644 crates/wit-parser/tests/ui/shared-types.wit.result delete mode 100644 crates/wit-parser/tests/ui/type-then-eof.wit.result delete mode 100644 crates/wit-parser/tests/ui/types.wit.result create mode 100644 crates/wit-parser/tests/ui/use.wit delete mode 100644 crates/wit-parser/tests/ui/wasi.wit.result delete mode 100644 crates/wit-parser/tests/ui/world-default.wit delete mode 100644 crates/wit-parser/tests/ui/world-default.wit.result create mode 100644 crates/wit-parser/tests/ui/world-diamond.wit delete mode 100644 crates/wit-parser/tests/ui/worlds.wit.result diff --git a/Cargo.toml b/Cargo.toml index ec771f9ccd..b8600f12ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ 'crates/wasm-mutate-stats', 'fuzz', 'crates/wit-component/fuzz', + 'crates/wit-parser/fuzz', ] [workspace.package] diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate/join.wit b/crates/wit-component/tests/interfaces/diamond-disambiguate/join.wit new file mode 100644 index 0000000000..f8bc80c84c --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate/join.wit @@ -0,0 +1,10 @@ +world w1 { + import shared1: pkg.shared1 + + import foo: interface { + use pkg.shared1.{t1} + } + import bar: interface { + use pkg.shared2.{t2} + } +} diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate/shared1.wit b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared1.wit new file mode 100644 index 0000000000..962bb48cf8 --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared1.wit @@ -0,0 +1,3 @@ +default interface shared { + type t1 = u8 +} diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate/shared2.wit b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared2.wit new file mode 100644 index 0000000000..e12498eaeb --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared2.wit @@ -0,0 +1,3 @@ +default interface shared { + type t2 = u8 +} diff --git a/crates/wit-component/tests/interfaces/diamond.wat b/crates/wit-component/tests/interfaces/diamond.wat new file mode 100644 index 0000000000..2f7a8dd001 --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond.wat @@ -0,0 +1,88 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (enum "a")) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (export (;0;) "shared" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (enum "a")) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (import "shared" "pkg:/diamond/shared" (instance (type 0))) + (alias export 0 "the-enum" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (import "foo" (instance (type 2))) + (type (;3;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (import "bar" (instance (type 3))) + ) + ) + (export (;0;) "w1" (component (type 1))) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) (enum "a")) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (import "shared" "pkg:/diamond/shared" (instance (type 0))) + (alias export 0 "the-enum" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (import "foo" (instance (type 2))) + (type (;3;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (export (;0;) "bar" (instance (type 3))) + ) + ) + (export (;1;) "w2" (component (type 2))) + (type (;3;) + (component + (type (;0;) + (instance + (type (;0;) (enum "a")) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (import "shared" "pkg:/diamond/shared" (instance (type 0))) + (alias export 0 "the-enum" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (export (;0;) "bar" (instance (type 2))) + ) + ) + (export (;2;) "w3" (component (type 3))) + ) + ) + (export (;1;) "diamond" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/diamond.wit b/crates/wit-component/tests/interfaces/diamond.wit new file mode 100644 index 0000000000..b5d718b3ae --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond.wit @@ -0,0 +1,29 @@ +interface shared { + enum the-enum { + a + } +} + +world w1 { + import foo: interface { + use self.shared.{the-enum} + } + import bar: interface { + use self.shared.{the-enum} + } +} + +world w2 { + import foo: interface { + use self.shared.{the-enum} + } + export bar: interface { + use self.shared.{the-enum} + } +} + +world w3 { + export bar: interface { + use self.shared.{the-enum} + } +} diff --git a/crates/wit-component/tests/interfaces/diamond.wit.print b/crates/wit-component/tests/interfaces/diamond.wit.print new file mode 100644 index 0000000000..305f426f49 --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond.wit.print @@ -0,0 +1,31 @@ +interface shared { + enum the-enum { + a, + } + +} + +world w1 { + import shared: self.shared + import foo: interface { + use self.shared.{the-enum} + } + import bar: interface { + use self.shared.{the-enum} + } +} +world w2 { + import shared: self.shared + import foo: interface { + use self.shared.{the-enum} + } + export bar: interface { + use self.shared.{the-enum} + } +} +world w3 { + import shared: self.shared + export bar: interface { + use self.shared.{the-enum} + } +} diff --git a/crates/wit-parser/Cargo.toml b/crates/wit-parser/Cargo.toml index b4f71b0046..550d1dddd2 100644 --- a/crates/wit-parser/Cargo.toml +++ b/crates/wit-parser/Cargo.toml @@ -18,11 +18,12 @@ anyhow = { workspace = true } indexmap = { workspace = true } pulldown-cmark = { version = "0.8", default-features = false } unicode-xid = "0.2.2" +log = { workspace = true } [dev-dependencies] rayon = "1" -serde_json = "1" -serde = { version = "1", features = ['derive'] } +env_logger = { workspace = true } +pretty_assertions = "1.3.0" [[test]] name = "all" diff --git a/crates/wit-parser/fuzz/.gitignore b/crates/wit-parser/fuzz/.gitignore new file mode 100644 index 0000000000..ff2738685d --- /dev/null +++ b/crates/wit-parser/fuzz/.gitignore @@ -0,0 +1,2 @@ +artifacts +corpus diff --git a/crates/wit-parser/fuzz/Cargo.toml b/crates/wit-parser/fuzz/Cargo.toml new file mode 100644 index 0000000000..6be0c5262c --- /dev/null +++ b/crates/wit-parser/fuzz/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "wit-parser-fuzz" +version = "0.0.1" +publish = false +edition.workspace = true + +[package.metadata] +cargo-fuzz = true + +[dependencies] +arbitrary = { workspace = true, features = ['derive'] } +env_logger = { workspace = true } +libfuzzer-sys = { workspace = true } +log = { workspace = true } +wasmprinter = { workspace = true } +wit-parser = { workspace = true } + +[[bin]] +name = "parse" +path = "fuzz_targets/parse.rs" +test = false +doc = false diff --git a/crates/wit-parser/fuzz/fuzz_targets/parse.rs b/crates/wit-parser/fuzz/fuzz_targets/parse.rs new file mode 100644 index 0000000000..4bc47fb839 --- /dev/null +++ b/crates/wit-parser/fuzz/fuzz_targets/parse.rs @@ -0,0 +1,14 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + drop(env_logger::try_init()); + + let data = match std::str::from_utf8(data) { + Ok(s) => s, + Err(_) => return, + }; + + drop(wit_parser::UnresolvedPackage::parse("foo".as_ref(), &data)); +}); diff --git a/crates/wit-parser/src/abi.rs b/crates/wit-parser/src/abi.rs index c6c8bbb14f..6caf5f497f 100644 --- a/crates/wit-parser/src/abi.rs +++ b/crates/wit-parser/src/abi.rs @@ -1,6 +1,6 @@ use crate::sizealign::align_to; use crate::{ - Document, Enum, Flags, FlagsRepr, Function, Int, Record, Result_, Results, Tuple, Type, + Enum, Flags, FlagsRepr, Function, Int, Record, Resolve, Result_, Results, Tuple, Type, TypeDefKind, TypeId, Union, Variant, }; @@ -651,7 +651,7 @@ pub trait Bindgen { /// push the appropriate number of results or binding generation will panic. fn emit( &mut self, - doc: &Document, + resolve: &Resolve, inst: &Instruction<'_>, operands: &mut Vec, results: &mut Vec, @@ -694,10 +694,10 @@ pub trait Bindgen { /// Returns whether or not the specified element type is represented in a /// "canonical" form for lists. This dictates whether the `ListCanonLower` /// and `ListCanonLift` instructions are used or not. - fn is_list_canonical(&self, doc: &Document, element: &Type) -> bool; + fn is_list_canonical(&self, resolve: &Resolve, element: &Type) -> bool; } -impl Document { +impl Resolve { /// Get the WebAssembly type signature for this interface function /// /// The first entry returned is the list of parameters and the second entry @@ -824,6 +824,8 @@ impl Document { TypeDefKind::Stream(_) => { result.push(WasmType::I32); } + + TypeDefKind::Unknown => unreachable!(), }, } } @@ -912,6 +914,7 @@ impl Document { .any(|t| self.needs_post_return(t)), TypeDefKind::Flags(_) | TypeDefKind::Enum(_) => false, TypeDefKind::Future(_) | TypeDefKind::Stream(_) => unimplemented!(), + TypeDefKind::Unknown => unreachable!(), }, Type::Bool @@ -950,7 +953,7 @@ struct Generator<'a, B: Bindgen> { variant: AbiVariant, lift_lower: LiftLower, bindgen: &'a mut B, - doc: &'a Document, + resolve: &'a Resolve, operands: Vec, results: Vec, stack: Vec, @@ -959,13 +962,13 @@ struct Generator<'a, B: Bindgen> { impl<'a, B: Bindgen> Generator<'a, B> { fn new( - doc: &'a Document, + resolve: &'a Resolve, variant: AbiVariant, lift_lower: LiftLower, bindgen: &'a mut B, ) -> Generator<'a, B> { Generator { - doc, + resolve, variant, lift_lower, bindgen, @@ -977,7 +980,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { } fn call(&mut self, func: &Function) { - let sig = self.doc.wasm_signature(self.variant, func); + let sig = self.resolve.wasm_signature(self.variant, func); match self.lift_lower { LiftLower::LowerArgsLiftResults => { @@ -1082,7 +1085,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { let mut temp = Vec::new(); for (_, ty) in func.params.iter() { temp.truncate(0); - self.doc.push_wasm(self.variant, ty, &mut temp); + self.resolve.push_wasm(self.variant, ty, &mut temp); for _ in 0..temp.len() { self.emit(&Instruction::GetArg { nth: offset }); offset += 1; @@ -1176,7 +1179,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { } fn post_return(&mut self, func: &Function) { - let sig = self.doc.wasm_signature(self.variant, func); + let sig = self.resolve.wasm_signature(self.variant, func); // Currently post-return is only used for lists and lists are always // returned indirectly through memory due to their flat representation @@ -1218,7 +1221,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.results.reserve(inst.results_len()); self.bindgen - .emit(self.doc, inst, &mut self.operands, &mut self.results); + .emit(self.resolve, inst, &mut self.operands, &mut self.results); assert_eq!( self.results.len(), @@ -1266,11 +1269,11 @@ impl<'a, B: Bindgen> Generator<'a, B> { let realloc = self.list_realloc(); self.emit(&StringLower { realloc }); } - Type::Id(id) => match &self.doc.types[id].kind { + Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.lower(t), TypeDefKind::List(element) => { let realloc = self.list_realloc(); - if self.bindgen.is_list_canonical(self.doc, element) { + if self.bindgen.is_list_canonical(self.resolve, element) { self.emit(&ListCanonLower { element, realloc }); } else { self.push_block(); @@ -1286,7 +1289,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&RecordLower { record, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); let values = self .stack @@ -1313,7 +1316,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&FlagsLower { flags, ty: id, - name: self.doc.types[id].name.as_ref().unwrap(), + name: self.resolve.types[id].name.as_ref().unwrap(), }); } @@ -1324,14 +1327,14 @@ impl<'a, B: Bindgen> Generator<'a, B> { variant: v, ty: id, results: &results, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Enum(enum_) => { self.emit(&EnumLower { enum_, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Option(t) => { @@ -1357,11 +1360,12 @@ impl<'a, B: Bindgen> Generator<'a, B> { union, ty: id, results: &results, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Future(_) => todo!("lower future"), TypeDefKind::Stream(_) => todo!("lower stream"), + TypeDefKind::Unknown => unreachable!(), }, } } @@ -1375,7 +1379,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { let mut results = Vec::new(); let mut temp = Vec::new(); let mut casts = Vec::new(); - self.doc.push_wasm(self.variant, ty, &mut results); + self.resolve.push_wasm(self.variant, ty, &mut results); for (i, ty) in cases.into_iter().enumerate() { self.push_block(); self.emit(&VariantPayloadName); @@ -1392,7 +1396,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // pushed, and record how many. If we pushed too few // then we'll need to push some zeros after this. temp.truncate(0); - self.doc.push_wasm(self.variant, ty, &mut temp); + self.resolve.push_wasm(self.variant, ty, &mut temp); pushed += temp.len(); // For all the types pushed we may need to insert some @@ -1450,10 +1454,12 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Float32 => self.emit(&Float32FromF32), Type::Float64 => self.emit(&Float64FromF64), Type::String => self.emit(&StringLift), - Type::Id(id) => match &self.doc.types[id].kind { + Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.lift(t), TypeDefKind::List(element) => { - if self.is_char(element) || self.bindgen.is_list_canonical(self.doc, element) { + if self.is_char(element) + || self.bindgen.is_list_canonical(self.resolve, element) + { self.emit(&ListCanonLift { element, ty: id }); } else { self.push_block(); @@ -1466,33 +1472,33 @@ impl<'a, B: Bindgen> Generator<'a, B> { } TypeDefKind::Record(record) => { let mut temp = Vec::new(); - self.doc.push_wasm(self.variant, ty, &mut temp); + self.resolve.push_wasm(self.variant, ty, &mut temp); let mut args = self .stack .drain(self.stack.len() - temp.len()..) .collect::>(); for field in record.fields.iter() { temp.truncate(0); - self.doc.push_wasm(self.variant, &field.ty, &mut temp); + self.resolve.push_wasm(self.variant, &field.ty, &mut temp); self.stack.extend(args.drain(..temp.len())); self.lift(&field.ty); } self.emit(&RecordLift { record, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Tuple(tuple) => { let mut temp = Vec::new(); - self.doc.push_wasm(self.variant, ty, &mut temp); + self.resolve.push_wasm(self.variant, ty, &mut temp); let mut args = self .stack .drain(self.stack.len() - temp.len()..) .collect::>(); for ty in tuple.types.iter() { temp.truncate(0); - self.doc.push_wasm(self.variant, ty, &mut temp); + self.resolve.push_wasm(self.variant, ty, &mut temp); self.stack.extend(args.drain(..temp.len())); self.lift(ty); } @@ -1502,7 +1508,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&FlagsLift { flags, ty: id, - name: self.doc.types[id].name.as_ref().unwrap(), + name: self.resolve.types[id].name.as_ref().unwrap(), }); } @@ -1511,7 +1517,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&VariantLift { variant: v, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } @@ -1519,7 +1525,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&EnumLift { enum_, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } @@ -1538,12 +1544,13 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&UnionLift { union, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Future(_) => todo!("lift future"), TypeDefKind::Stream(_) => todo!("lift stream"), + TypeDefKind::Unknown => unreachable!(), }, } } @@ -1556,7 +1563,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { let mut params = Vec::new(); let mut temp = Vec::new(); let mut casts = Vec::new(); - self.doc.push_wasm(self.variant, ty, &mut params); + self.resolve.push_wasm(self.variant, ty, &mut params); let block_inputs = self .stack .drain(self.stack.len() + 1 - params.len()..) @@ -1567,7 +1574,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // Push only the values we need for this variant onto // the stack. temp.truncate(0); - self.doc.push_wasm(self.variant, ty, &mut temp); + self.resolve.push_wasm(self.variant, ty, &mut temp); self.stack .extend(block_inputs[..temp.len()].iter().cloned()); @@ -1606,7 +1613,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Float64 => self.lower_and_emit(ty, addr, &F64Store { offset }), Type::String => self.write_list_to_memory(ty, addr, offset), - Type::Id(id) => match &self.doc.types[id].kind { + Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.write_to_memory(t, addr, offset), TypeDefKind::List(_) => self.write_list_to_memory(ty, addr, offset), @@ -1616,7 +1623,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&RecordLower { record, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); self.write_fields_to_memory(record.fields.iter().map(|f| &f.ty), addr, offset); } @@ -1662,7 +1669,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { variant: v, ty: id, results: &[], - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } @@ -1706,12 +1713,13 @@ impl<'a, B: Bindgen> Generator<'a, B> { union, ty: id, results: &[], - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Future(_) => todo!("write future to memory"), TypeDefKind::Stream(_) => todo!("write stream to memory"), + TypeDefKind::Unknown => unreachable!(), }, } } @@ -1803,7 +1811,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Float64 => self.emit_and_lift(ty, addr, &F64Load { offset }), Type::String => self.read_list_from_memory(ty, addr, offset), - Type::Id(id) => match &self.doc.types[id].kind { + Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.read_from_memory(t, addr, offset), TypeDefKind::List(_) => self.read_list_from_memory(ty, addr, offset), @@ -1816,7 +1824,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&RecordLift { record, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Tuple(tuple) => { @@ -1860,7 +1868,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&VariantLift { variant, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } @@ -1895,12 +1903,13 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&UnionLift { union, ty: id, - name: self.doc.types[id].name.as_deref().unwrap(), + name: self.resolve.types[id].name.as_deref().unwrap(), }); } TypeDefKind::Future(_) => todo!("read future from memory"), TypeDefKind::Stream(_) => todo!("read stream from memory"), + TypeDefKind::Unknown => unreachable!(), }, } } @@ -1977,7 +1986,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { fn is_char(&self, ty: &Type) -> bool { match ty { Type::Char => true, - Type::Id(id) => match &self.doc.types[*id].kind { + Type::Id(id) => match &self.resolve.types[*id].kind { TypeDefKind::Type(t) => self.is_char(t), _ => false, }, @@ -1990,7 +1999,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // No need to execute any instructions if this type itself doesn't // require any form of post-return. - if !self.doc.needs_post_return(ty) { + if !self.resolve.needs_post_return(ty) { return; } @@ -2016,7 +2025,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { | Type::Float32 | Type::Float64 => {} - Type::Id(id) => match &self.doc.types[id].kind { + Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.deallocate(t, addr, offset), TypeDefKind::List(element) => { @@ -2084,6 +2093,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { TypeDefKind::Future(_) => todo!("read future from memory"), TypeDefKind::Stream(_) => todo!("read stream from memory"), + TypeDefKind::Unknown => unreachable!(), }, } } diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index b08efa04be..1ab94a38cd 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -1,13 +1,16 @@ -use anyhow::Result; +use crate::Error; +use anyhow::{bail, Result}; use lex::{Span, Token, Tokenizer}; use std::borrow::Cow; use std::convert::TryFrom; use std::fmt; +use std::path::{Path, PathBuf}; pub mod lex; pub use resolve::Resolver; mod resolve; +pub mod toposort; pub use lex::validate_id; @@ -25,18 +28,45 @@ impl<'a> Ast<'a> { Ok(Self { items }) } - pub fn worlds(&self) -> impl Iterator> { - self.items.iter().filter_map(|item| match item { - AstItem::World(item) => Some(item), - AstItem::Interface(_) => None, - }) - } - - pub fn interfaces(&self) -> impl Iterator> { - self.items.iter().filter_map(|item| match item { - AstItem::Interface(item) => Some(item), - AstItem::World(_) => None, - }) + fn foreach_path<'b>( + &'b self, + mut f: impl FnMut(Option<&'b Id<'a>>, &'b UsePath<'a>, Option<&[UseName<'a>]>) -> Result<()>, + ) -> Result<()> { + for item in self.items.iter() { + match item { + AstItem::World(world) => { + for item in world.items.iter() { + match item { + // WorldItem::Use(u) => f(None, &u.from, Some(&u.names))?, + WorldItem::Import(Import { kind, .. }) + | WorldItem::Export(Export { kind, .. }) => match kind { + ExternKind::Interface(_, items) => { + for item in items { + match item { + InterfaceItem::Use(u) => { + f(None, &u.from, Some(&u.names))? + } + _ => {} + } + } + } + ExternKind::Path(path) => f(None, path, None)?, + // ExternKind::Func(_) => {} + }, + } + } + } + AstItem::Interface(i) => { + for item in i.items.iter() { + match item { + InterfaceItem::Use(u) => f(Some(&i.name), &u.from, Some(&u.names))?, + _ => {} + } + } + } + } + } + Ok(()) } } @@ -47,7 +77,12 @@ pub enum AstItem<'a> { impl<'a> AstItem<'a> { fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { - match tokens.clone().next()? { + let mut clone = tokens.clone(); + let token = match clone.next()? { + Some((_span, Token::Default)) => clone.next()?, + other => other, + }; + match token { Some((_span, Token::Interface)) => Interface::parse(tokens, docs).map(Self::Interface), Some((_span, Token::World)) => World::parse(tokens, docs).map(Self::World), other => Err(err_expected(tokens, "`default`, `world` or `interface`", other).into()), @@ -59,14 +94,21 @@ pub struct World<'a> { docs: Docs<'a>, name: Id<'a>, items: Vec>, + default: bool, } impl<'a> World<'a> { fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + let default = tokens.eat(Token::Default)?; tokens.expect(Token::World)?; let name = parse_id(tokens)?; let items = Self::parse_items(tokens)?; - Ok(World { docs, name, items }) + Ok(World { + docs, + name, + items, + default, + }) } fn parse_items(tokens: &mut Tokenizer<'a>) -> Result>> { @@ -85,7 +127,7 @@ impl<'a> World<'a> { pub enum WorldItem<'a> { Import(Import<'a>), Export(Export<'a>), - ExportDefault(ExternKind<'a>), + // Use(Use<'a>), } impl<'a> WorldItem<'a> { @@ -93,11 +135,8 @@ impl<'a> WorldItem<'a> { match tokens.clone().next()? { Some((_span, Token::Import)) => Import::parse(tokens).map(WorldItem::Import), Some((_span, Token::Export)) => Export::parse(tokens).map(WorldItem::Export), - Some((_span, Token::Default)) => { - tokens.expect(Token::Default)?; - tokens.expect(Token::Export)?; - ExternKind::parse(tokens).map(WorldItem::ExportDefault) - } + // TODO: should parse this when it's implemented + // Some((_span, Token::Use)) => Use::parse(tokens).map(WorldItem::Use), other => Err(err_expected(tokens, "`import` or `export`", other).into()), } } @@ -135,44 +174,47 @@ impl<'a> Export<'a> { pub enum ExternKind<'a> { Interface(Span, Vec>), - Id(Id<'a>), + Path(UsePath<'a>), + // Func(Func<'a>), } impl<'a> ExternKind<'a> { fn parse(tokens: &mut Tokenizer<'a>) -> Result> { match tokens.clone().next()? { - Some((_span, Token::Id | Token::StrLit | Token::ExplicitId)) => { - parse_id(tokens).map(ExternKind::Id) + Some((_span, Token::Id | Token::ExplicitId | Token::Pkg | Token::Self_)) => { + UsePath::parse(tokens).map(ExternKind::Path) } Some((_span, Token::Interface)) => { let span = tokens.expect(Token::Interface)?; let items = Interface::parse_items(tokens)?; Ok(ExternKind::Interface(span, items)) } + // TODO: should parse this when it's implemented + // Some((_span, Token::Func)) => Ok(ExternKind::Func(Func::parse(tokens)?)), other => Err(err_expected(tokens, "path, value, or interface", other).into()), } } - - fn span(&self) -> Span { - match self { - ExternKind::Interface(span, _) => *span, - ExternKind::Id(id) => id.span, - } - } } pub struct Interface<'a> { docs: Docs<'a>, name: Id<'a>, items: Vec>, + default: bool, } impl<'a> Interface<'a> { fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + let default = tokens.eat(Token::Default)?; tokens.expect(Token::Interface)?; let name = parse_id(tokens)?; let items = Self::parse_items(tokens)?; - Ok(Interface { docs, name, items }) + Ok(Interface { + docs, + name, + items, + default, + }) } pub(super) fn parse_items(tokens: &mut Tokenizer<'a>) -> Result>> { @@ -192,24 +234,106 @@ impl<'a> Interface<'a> { pub enum InterfaceItem<'a> { TypeDef(TypeDef<'a>), Value(Value<'a>), + Use(Use<'a>), } -pub struct Id<'a> { - pub name: Cow<'a, str>, - pub span: Span, +pub struct Use<'a> { + pub from: UsePath<'a>, + pub names: Vec>, } -impl<'a> From<&'a str> for Id<'a> { - fn from(s: &'a str) -> Id<'a> { - Id { - name: s.into(), - span: Span { start: 0, end: 0 }, +pub enum UsePath<'a> { + Self_(Id<'a>), + Package { + doc: Id<'a>, + iface: Option>, + }, + Dependency { + dep: Id<'a>, + doc: Id<'a>, + iface: Option>, + }, +} + +pub struct UseName<'a> { + pub name: Id<'a>, + pub as_: Option>, +} + +impl<'a> Use<'a> { + fn parse(tokens: &mut Tokenizer<'a>) -> Result { + tokens.expect(Token::Use)?; + let from = UsePath::parse(tokens)?; + tokens.expect(Token::Period)?; + tokens.expect(Token::LeftBrace)?; + + let mut names = Vec::new(); + while !tokens.eat(Token::RightBrace)? { + let mut name = UseName { + name: parse_id(tokens)?, + as_: None, + }; + if tokens.eat(Token::As)? { + name.as_ = Some(parse_id(tokens)?); + } + names.push(name); + if !tokens.eat(Token::Comma)? { + tokens.expect(Token::RightBrace)?; + break; + } } + Ok(Use { from, names }) } } -impl<'a> From for Id<'a> { - fn from(s: String) -> Id<'a> { +impl<'a> UsePath<'a> { + fn parse(tokens: &mut Tokenizer<'a>) -> Result { + match tokens.clone().next()? { + Some((_span, Token::Self_)) => { + tokens.expect(Token::Self_)?; + tokens.expect(Token::Period)?; + let name = parse_id(tokens)?; + Ok(UsePath::Self_(name)) + } + Some((_span, Token::Pkg)) => { + tokens.expect(Token::Pkg)?; + tokens.expect(Token::Period)?; + let doc = parse_id(tokens)?; + let mut clone = tokens.clone(); + let iface = if clone.eat(Token::Period)? && !clone.eat(Token::LeftBrace)? { + tokens.expect(Token::Period)?; + Some(parse_id(tokens)?) + } else { + None + }; + Ok(UsePath::Package { doc, iface }) + } + Some((_span, Token::Id | Token::ExplicitId)) => { + let dep = parse_id(tokens)?; + tokens.expect(Token::Period)?; + let doc = parse_id(tokens)?; + let mut clone = tokens.clone(); + let iface = if clone.eat(Token::Period)? && !clone.eat(Token::LeftBrace)? { + tokens.expect(Token::Period)?; + Some(parse_id(tokens)?) + } else { + None + }; + Ok(UsePath::Dependency { dep, doc, iface }) + } + other => return Err(err_expected(tokens, "`self`, `pkg`, or identifier", other).into()), + } + } +} + +#[derive(Debug, Clone)] +pub struct Id<'a> { + pub name: &'a str, + pub span: Span, +} + +impl<'a> From<&'a str> for Id<'a> { + fn from(s: &'a str) -> Id<'a> { Id { name: s.into(), span: Span { start: 0, end: 0 }, @@ -333,7 +457,7 @@ enum ValueKind<'a> { Func(Func<'a>), } -struct Func<'a> { +pub struct Func<'a> { params: ParamList<'a>, results: ResultList<'a>, } @@ -352,6 +476,7 @@ impl<'a> Func<'a> { }) } + tokens.expect(Token::Func)?; let params = parse_params(tokens, true)?; let results = if tokens.eat(Token::RArrow)? { // If we eat a '(', parse the remainder of the named @@ -372,7 +497,6 @@ impl<'a> Func<'a> { impl<'a> ValueKind<'a> { fn parse(tokens: &mut Tokenizer<'a>) -> Result> { - tokens.eat(Token::Func)?; Func::parse(tokens).map(ValueKind::Func) } } @@ -399,6 +523,7 @@ impl<'a> InterfaceItem<'a> { Some((_span, Token::Id)) | Some((_span, Token::ExplicitId)) => { Value::parse(tokens, docs).map(InterfaceItem::Value) } + Some((_span, Token::Use)) => Use::parse(tokens).map(InterfaceItem::Use), other => Err(err_expected(tokens, "`type` or `func`", other).into()), } } @@ -522,16 +647,12 @@ impl<'a> Value<'a> { fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> Result> { match tokens.next()? { - Some((span, Token::StrLit)) => Ok(Id { - name: tokens.parse_str(span)?.into(), - span, - }), Some((span, Token::Id)) => Ok(Id { - name: tokens.parse_id(span)?.into(), + name: tokens.parse_id(span)?, span, }), Some((span, Token::ExplicitId)) => Ok(Id { - name: tokens.parse_explicit_id(span)?.into(), + name: tokens.parse_explicit_id(span)?, span, }), other => Err(err_expected(tokens, "an identifier or string", other).into()), @@ -729,79 +850,157 @@ fn err_expected( } } -#[derive(Debug)] -struct Error { - span: Span, - msg: String, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.msg.fmt(f) - } -} - -impl std::error::Error for Error {} - -pub fn rewrite_error(err: &mut anyhow::Error, file: &str, contents: &str) { - let parse = match err.downcast_mut::() { - Some(err) => err, - None => return lex::rewrite_error(err, file, contents), - }; - let msg = highlight_err( - parse.span.start as usize, - Some(parse.span.end as usize), - file, - contents, - &parse.msg, - ); - *err = anyhow::anyhow!("{}", msg); -} - -fn highlight_err( - start: usize, - end: Option, - file: &str, - input: &str, - err: impl fmt::Display, -) -> String { - let (line, col) = linecol_in(start, input); - let snippet = input.lines().nth(line).unwrap_or(""); - let mut msg = format!( - "\ +#[derive(Clone, Default)] +pub struct SourceMap { + sources: Vec<(u32, PathBuf, String)>, + offset: u32, +} + +impl SourceMap { + pub fn push(&mut self, path: &Path, contents: impl Into) { + let mut contents = contents.into(); + if path.extension().and_then(|s| s.to_str()) == Some("md") { + log::debug!("automatically unwrapping markdown container"); + contents = unwrap_md(&contents); + } + let new_offset = self.offset + u32::try_from(contents.len()).unwrap(); + self.sources + .push((self.offset, path.to_path_buf(), contents)); + self.offset = new_offset; + + fn unwrap_md(contents: &str) -> String { + use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag}; + + let mut wit = String::new(); + let mut last_pos = 0; + let mut in_wit_code_block = false; + Parser::new_ext(contents, Options::empty()) + .into_offset_iter() + .for_each(|(event, range)| match (event, range) { + ( + Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed( + "wit", + )))), + _, + ) => { + in_wit_code_block = true; + } + (Event::Text(text), range) if in_wit_code_block => { + // Ensure that offsets are correct by inserting newlines to + // cover the Markdown content outside of wit code blocks. + for _ in contents[last_pos..range.start].lines() { + wit.push('\n'); + } + wit.push_str(&text); + last_pos = range.end; + } + ( + Event::End(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed("wit")))), + _, + ) => { + in_wit_code_block = false; + } + _ => {} + }); + wit + } + } + + pub fn tokenizers(&self) -> impl Iterator)>> { + let mut srcs = self + .sources + .iter() + .map(|(offset, path, contents)| Tokenizer::new(contents, *offset).map(|t| (&**path, t))) + .collect::>(); + srcs.sort_by_key(|file| file.as_ref().ok().map(|(path, _)| path.to_path_buf())); + srcs.into_iter() + } + + pub fn rewrite_error(&self, f: F) -> Result + where + F: FnOnce() -> Result, + { + let err = match f() { + Ok(t) => return Ok(t), + Err(e) => e, + }; + if let Some(parse) = err.downcast_ref::() { + let msg = self.highlight_err(parse.span.start, Some(parse.span.end), parse); + bail!("{msg}") + } + + if let Some(lex) = err.downcast_ref::() { + let pos = match lex { + lex::Error::Unexpected(at, _) + | lex::Error::UnterminatedComment(at) + | lex::Error::Wanted { at, .. } + | lex::Error::InvalidCharInId(at, _) + | lex::Error::IdPartEmpty(at) + | lex::Error::InvalidEscape(at, _) => *at, + }; + let msg = self.highlight_err(pos, None, lex); + bail!("{msg}") + } + + if let Some(sort) = err.downcast_ref::() { + let span = match sort { + toposort::Error::NonexistentDep { span, .. } + | toposort::Error::Cycle { span, .. } => *span, + }; + let msg = self.highlight_err(span.start, Some(span.end), sort); + bail!("{msg}") + } + + Err(err) + } + + fn highlight_err(&self, start: u32, end: Option, err: impl fmt::Display) -> String { + let i = match self + .sources + .binary_search_by_key(&start, |(offset, _, _)| *offset) + { + Ok(i) => i, + Err(i) => i - 1, + }; + let (offset, file, contents) = &self.sources[i]; + let start = usize::try_from(start - *offset).unwrap(); + let end = end.map(|end| usize::try_from(end - *offset).unwrap()); + let (line, col) = linecol_in(start, contents); + let snippet = contents.lines().nth(line).unwrap_or(""); + let mut msg = format!( + "\ {err} --> {file}:{line}:{col} | {line:4} | {snippet} | {marker:>0$}", - col + 1, - file = file, - line = line + 1, - col = col + 1, - err = err, - snippet = snippet, - marker = "^", - ); - if let Some(end) = end { - if let Some(s) = input.get(start..end) { - for _ in s.chars().skip(1) { - msg.push('-'); + col + 1, + file = file.display(), + line = line + 1, + col = col + 1, + marker = "^", + ); + if let Some(end) = end { + if let Some(s) = contents.get(start..end) { + for _ in s.chars().skip(1) { + msg.push('-'); + } } } - } - return msg; - - fn linecol_in(pos: usize, text: &str) -> (usize, usize) { - let mut cur = 0; - // Use split_terminator instead of lines so that if there is a `\r`, - // it is included in the offset calculation. The `+1` values below - // account for the `\n`. - for (i, line) in text.split_terminator('\n').enumerate() { - if cur + line.len() + 1 > pos { - return (i, pos - cur); + return msg; + + fn linecol_in(pos: usize, text: &str) -> (usize, usize) { + let mut cur = 0; + // Use split_terminator instead of lines so that if there is a `\r`, + // it is included in the offset calculation. The `+1` values below + // account for the `\n`. + for (i, line) in text.split_terminator('\n').enumerate() { + if cur + line.len() + 1 > pos { + return (i, pos - cur); + } + cur += line.len() + 1; } - cur += line.len() + 1; + (text.lines().count(), 0) } - (text.lines().count(), 0) } } diff --git a/crates/wit-parser/src/ast/lex.rs b/crates/wit-parser/src/ast/lex.rs index cee13ac267..5023a854a9 100644 --- a/crates/wit-parser/src/ast/lex.rs +++ b/crates/wit-parser/src/ast/lex.rs @@ -10,6 +10,7 @@ use self::Token::*; #[derive(Clone)] pub struct Tokenizer<'a> { input: &'a str, + span_offset: u32, chars: CrlfFold<'a>, } @@ -35,6 +36,7 @@ pub enum Token { Equals, Comma, Colon, + Period, Semicolon, LeftParen, RightParen, @@ -82,38 +84,35 @@ pub enum Token { Export, World, Default, + Pkg, + Self_, Id, ExplicitId, - StrLit, } #[derive(Eq, PartialEq, Debug)] #[allow(dead_code)] pub enum Error { - InvalidCharInString(usize, char), - InvalidCharInId(usize, char), - IdPartEmpty(usize), - InvalidEscape(usize, char), - // InvalidHexEscape(usize, char), - // InvalidEscapeValue(usize, u32), - Unexpected(usize, char), - UnterminatedComment(usize), - UnterminatedString(usize), - NewlineInString(usize), + InvalidCharInId(u32, char), + IdPartEmpty(u32), + InvalidEscape(u32, char), + Unexpected(u32, char), + UnterminatedComment(u32), Wanted { - at: usize, + at: u32, expected: &'static str, found: &'static str, }, } impl<'a> Tokenizer<'a> { - pub fn new(input: &'a str) -> Result> { + pub fn new(input: &'a str, span_offset: u32) -> Result> { detect_invalid_input(input)?; let mut t = Tokenizer { input, + span_offset, chars: CrlfFold { chars: input.char_indices(), }, @@ -128,33 +127,22 @@ impl<'a> Tokenizer<'a> { } pub fn get_span(&self, span: Span) -> &'a str { - &self.input[span.start as usize..span.end as usize] + let start = usize::try_from(span.start - self.span_offset).unwrap(); + let end = usize::try_from(span.end - self.span_offset).unwrap(); + &self.input[start..end] } - pub fn parse_id(&self, span: Span) -> Result { - let ret = self.get_span(span).to_owned(); - validate_id(span.start as usize, &ret)?; + pub fn parse_id(&self, span: Span) -> Result<&'a str> { + let ret = self.get_span(span); + validate_id(span.start, &ret)?; Ok(ret) } - // This is going to be used shortly for a new feature. - #[allow(dead_code)] - pub fn parse_str(&self, span: Span) -> Result { - let mut ret = String::new(); - let s = self.get_span(span); - let mut l = Tokenizer::new(s)?; - assert!(matches!(l.chars.next(), Some((_, '"')))); - while let Some(c) = l.eat_str_char(0).unwrap() { - ret.push(c); - } - Ok(ret) - } - - pub fn parse_explicit_id(&self, span: Span) -> Result { + pub fn parse_explicit_id(&self, span: Span) -> Result<&'a str> { let token = self.get_span(span); let id_part = token.strip_prefix('%').unwrap(); - validate_id(span.start as usize, id_part)?; - Ok(id_part.to_owned()) + validate_id(span.start, id_part)?; + Ok(id_part) } pub fn next(&mut self) -> Result, Error> { @@ -167,10 +155,11 @@ impl<'a> Tokenizer<'a> { } pub fn next_raw(&mut self) -> Result, Error> { - let (start, ch) = match self.chars.next() { + let (str_start, ch) = match self.chars.next() { Some(pair) => pair, None => return Ok(None), }; + let start = self.span_offset + u32::try_from(str_start).unwrap(); let token = match ch { '\n' | '\t' | ' ' => { // Eat all contiguous whitespace tokens @@ -208,6 +197,7 @@ impl<'a> Tokenizer<'a> { '=' => Equals, ',' => Comma, ':' => Colon, + '.' => Period, ';' => Semicolon, '(' => LeftParen, ')' => RightParen, @@ -223,10 +213,6 @@ impl<'a> Tokenizer<'a> { return Err(Error::Unexpected(start, '-')); } } - '"' => { - while let Some(_ch) = self.eat_str_char(start)? {} - StrLit - } '%' => { let mut iter = self.chars.clone(); if let Some((_, ch)) = iter.next() { @@ -251,8 +237,9 @@ impl<'a> Tokenizer<'a> { } self.chars = iter.clone(); } - let end = start + ch.len_utf8() + (remaining - self.chars.chars.as_str().len()); - match &self.input[start..end] { + let str_end = + str_start + ch.len_utf8() + (remaining - self.chars.chars.as_str().len()); + match &self.input[str_start..str_end] { "use" => Use, "type" => Type, "func" => Func, @@ -290,6 +277,8 @@ impl<'a> Tokenizer<'a> { "import" => Import, "export" => Export, "default" => Default, + "pkg" => Pkg, + "self" => Self_, _ => Id, } } @@ -300,8 +289,7 @@ impl<'a> Tokenizer<'a> { None => self.input.len(), }; - let start = u32::try_from(start).unwrap(); - let end = u32::try_from(end).unwrap(); + let end = self.span_offset + u32::try_from(end).unwrap(); Ok(Some((Span { start, end }, token))) } @@ -324,36 +312,14 @@ impl<'a> Tokenizer<'a> { Ok(span) } else { Err(Error::Wanted { - at: usize::try_from(span.start).unwrap(), - expected: expected.describe(), - found: found.describe(), - }) - } - } - None => Err(Error::Wanted { - at: self.input.len(), - expected: expected.describe(), - found: "eof", - }), - } - } - - #[allow(dead_code)] // TODO - pub fn expect_raw(&mut self, expected: Token) -> Result { - match self.next_raw()? { - Some((span, found)) => { - if expected == found { - Ok(span) - } else { - Err(Error::Wanted { - at: usize::try_from(span.start).unwrap(), + at: span.start, expected: expected.describe(), found: found.describe(), }) } } None => Err(Error::Wanted { - at: self.input.len(), + at: self.span_offset + u32::try_from(self.input.len()).unwrap(), expected: expected.describe(), found: "eof", }), @@ -370,32 +336,6 @@ impl<'a> Tokenizer<'a> { _ => false, } } - - fn eat_str_char(&mut self, start: usize) -> Result, Error> { - let ch = match self.chars.next() { - Some((_, '"')) => return Ok(None), - Some((_, '\\')) => match self.chars.next() { - Some((_, '"')) => '"', - Some((_, '\'')) => '\'', - Some((_, 't')) => '\t', - Some((_, 'n')) => '\n', - Some((_, 'r')) => '\r', - Some((_, '\\')) => '\\', - Some((i, c)) => return Err(Error::InvalidEscape(i, c)), - None => return Err(Error::UnterminatedString(start)), - }, - Some((_, ch)) - if ch == '\u{09}' - || (('\u{20}'..='\u{10ffff}').contains(&ch) && ch != '\u{7f}') => - { - ch - } - Some((i, '\n')) => return Err(Error::NewlineInString(i)), - Some((i, ch)) => return Err(Error::InvalidCharInString(i, ch)), - None => return Err(Error::UnterminatedString(start)), - }; - Ok(Some(ch)) - } } impl<'a> Iterator for CrlfFold<'a> { @@ -478,7 +418,7 @@ fn is_keylike_continue(ch: char) -> bool { UnicodeXID::is_xid_continue(ch) || ch == '-' } -pub fn validate_id(start: usize, id: &str) -> Result<(), Error> { +pub fn validate_id(start: u32, id: &str) -> Result<(), Error> { // IDs must have at least one part. if id.is_empty() { return Err(Error::IdPartEmpty(start)); @@ -525,6 +465,7 @@ impl Token { Equals => "'='", Comma => "','", Colon => "':'", + Period => "'.'", Semicolon => "';'", LeftParen => "'('", RightParen => "')'", @@ -561,7 +502,6 @@ impl Token { Underscore => "keyword `_`", Id => "an identifier", ExplicitId => "an '%' identifier", - StrLit => "a string", RArrow => "`->`", Star => "`*`", As => "keyword `as`", @@ -574,6 +514,8 @@ impl Token { Export => "keyword `export`", World => "keyword `world`", Default => "keyword `default`", + Self_ => "keyword `self`", + Pkg => "keyword `pkg`", } } } @@ -588,9 +530,6 @@ impl fmt::Display for Error { Error::Wanted { expected, found, .. } => write!(f, "expected {}, found {}", expected, found), - Error::UnterminatedString(_) => write!(f, "unterminated string literal"), - Error::NewlineInString(_) => write!(f, "newline in string literal"), - Error::InvalidCharInString(_, ch) => write!(f, "invalid character in string {:?}", ch), Error::InvalidCharInId(_, ch) => write!(f, "invalid character in identifier {:?}", ch), Error::IdPartEmpty(_) => write!(f, "identifiers must have characters between '-'s"), Error::InvalidEscape(_, ch) => write!(f, "invalid escape in string {:?}", ch), @@ -598,26 +537,6 @@ impl fmt::Display for Error { } } -pub fn rewrite_error(err: &mut anyhow::Error, file: &str, contents: &str) { - let lex = match err.downcast_mut::() { - Some(err) => err, - None => return, - }; - let pos = match lex { - Error::Unexpected(at, _) - | Error::UnterminatedComment(at) - | Error::Wanted { at, .. } - | Error::UnterminatedString(at) - | Error::NewlineInString(at) - | Error::InvalidCharInString(at, _) - | Error::InvalidCharInId(at, _) - | Error::IdPartEmpty(at) - | Error::InvalidEscape(at, _) => *at, - }; - let msg = super::highlight_err(pos, None, file, contents, lex); - *err = anyhow::anyhow!("{}", msg); -} - #[test] fn test_validate_id() { validate_id(0, "apple").unwrap(); @@ -679,7 +598,7 @@ fn test_validate_id() { #[test] fn test_tokenizer() { fn collect(s: &str) -> Result> { - let mut t = Tokenizer::new(s)?; + let mut t = Tokenizer::new(s, 0)?; let mut tokens = Vec::new(); while let Some(token) = t.next()? { tokens.push(token.1); @@ -730,10 +649,6 @@ fn test_tokenizer() { ] ); - assert_eq!(collect("\"a\"").unwrap(), vec![Token::StrLit]); - assert_eq!(collect("\"a-a\"").unwrap(), vec![Token::StrLit]); - assert_eq!(collect("\"bool\"").unwrap(), vec![Token::StrLit]); - assert!(collect("\u{149}").is_err(), "strongly discouraged"); assert!(collect("\u{673}").is_err(), "strongly discouraged"); assert!(collect("\u{17a3}").is_err(), "strongly discouraged"); diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 0b9a943963..4882f8dee7 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1,18 +1,41 @@ -use super::{Error, Func, InterfaceItem, ParamList, ResultList, Span, Value, ValueKind, WorldItem}; +use super::{Error, Func, ParamList, ResultList, ValueKind}; +use crate::ast::toposort::toposort; use crate::*; -use anyhow::Result; +use anyhow::{bail, Result}; use indexmap::IndexMap; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::mem; #[derive(Default)] -pub struct Resolver { - type_lookup: IndexMap, +pub struct Resolver<'a> { + asts: IndexMap<&'a str, ast::Ast<'a>>, types: Arena, anon_types: HashMap, - functions: Vec, interfaces: Arena, + documents: Arena, worlds: Arena, + document_lookup: IndexMap<&'a str, DocumentId>, + document_interfaces: Vec>, + interface_types: Vec>, + foreign_deps: IndexMap<&'a str, IndexMap<&'a str, DocumentId>>, + + unknown_type_spans: Vec, + world_spans: Vec<(Vec, Vec)>, + document_spans: Vec, + interface_spans: Vec, + foreign_dep_spans: Vec, +} + +#[derive(Copy, Clone)] +enum DocumentItem { + Interface(InterfaceId), + World(WorldId), +} + +#[derive(Copy, Clone)] +enum InterfaceItem { + Type(TypeId), + Func, } #[derive(PartialEq, Eq, Hash)] @@ -30,205 +53,616 @@ enum Key { Stream(Option, Option), } -impl Resolver { - pub(crate) fn resolve(&mut self, ast: &ast::Ast<'_>) -> Result { - let mut interface_map = IndexMap::new(); +impl<'a> Resolver<'a> { + pub(crate) fn push(&mut self, path: &'a Path, ast: ast::Ast<'a>) -> Result<()> { + let filename = match path.file_name().and_then(|s| s.to_str()) { + Some(stem) => stem, + None => bail!("no filename for {path:?}"), + }; + let name = match filename.find('.') { + Some(i) => &filename[..i], + None => filename, + }; + crate::validate_id(name).map_err(|e| { + anyhow::anyhow!("filename was not a valid WIT identifier for {path:?}: {e}") + })?; + let prev = self.asts.insert(name, ast); + if prev.is_some() { + bail!("document `{name}` defined twice"); + } + Ok(()) + } + + pub(crate) fn resolve(&mut self) -> Result { + self.populate_foreign_deps(); + + // Determine the dependencies between documents in the current package + // we're parsing to perform a topological sort and how to visit the + // documents in order. + let mut doc_deps = IndexMap::new(); + for (name, ast) in self.asts.iter() { + let mut deps = Vec::new(); + ast.foreach_path(|_, path, _names| { + let doc = match path { + ast::UsePath::Package { doc, iface: _ } => doc, + _ => return Ok(()), + }; + // If this document imports from itself using `pkg` syntax + // that's ok and skip this dependency to prevent it from + // otherwise being flagged as cyclic. + if doc.name == *name { + return Ok(()); + } + deps.push(doc.clone()); + Ok(()) + }) + .unwrap(); + + let prev = doc_deps.insert(*name, deps); + assert!(prev.is_none()); + } + + let order = toposort("document", &doc_deps)?; + log::debug!("toposort for documents is {order:?}"); + let mut asts = mem::take(&mut self.asts); + for name in order { + let ast = asts.remove(&name).unwrap(); + self.resolve_document(name, &ast)?; + } + + Ok(UnresolvedPackage { + worlds: mem::take(&mut self.worlds), + types: mem::take(&mut self.types), + interfaces: mem::take(&mut self.interfaces), + documents: mem::take(&mut self.documents), + foreign_deps: self + .foreign_deps + .iter() + .map(|(name, deps)| { + ( + name.to_string(), + deps.iter() + .map(|(name, id)| (name.to_string(), *id)) + .collect(), + ) + }) + .collect(), + unknown_type_spans: mem::take(&mut self.unknown_type_spans), + world_spans: mem::take(&mut self.world_spans), + document_spans: mem::take(&mut self.document_spans), + interface_spans: mem::take(&mut self.interface_spans), + foreign_dep_spans: mem::take(&mut self.foreign_dep_spans), + source_map: SourceMap::default(), + }) + } + + /// Populate "unknown" for all types referenced from foreign packages as + /// well as inserting interfaces for referenced interfaces. + /// + /// This will populate the initial set of documents/interfaces/types with + /// everything referenced from foreign packages, or those through + /// `UsePath::Dependency`. The items created here are extracted later via + /// `resolve_path`. + fn populate_foreign_deps(&mut self) { + for (_, ast) in self.asts.iter() { + ast.foreach_path(|_, path, names| { + let (dep, doc, iface) = match path { + ast::UsePath::Dependency { dep, doc, iface } => (dep, doc, iface), + _ => return Ok(()), + }; - for interface in ast.interfaces() { - let name = &interface.name.name; - let instance = self.resolve_interface(name, &interface.items, &interface.docs)?; + let deps = self.foreign_deps.entry(dep.name).or_insert_with(|| { + self.foreign_dep_spans.push(dep.span); + IndexMap::new() + }); + let doc_span = doc.span; + let doc = *deps.entry(doc.name).or_insert_with(|| { + log::trace!("creating a document for foreign dep: {}", dep.name); + self.document_interfaces.push(IndexMap::new()); + self.document_spans.push(doc_span); + self.documents.alloc(Document { + name: doc.name.to_string(), + default_interface: None, + default_world: None, + interfaces: IndexMap::new(), + worlds: Vec::new(), + }) + }); + + let iface = match iface { + Some(iface) => { + let item = self.document_interfaces[doc.index()] + .entry(iface.name) + .or_insert_with(|| { + self.interface_types.push(IndexMap::new()); + self.interface_spans.push(iface.span); + let id = self.interfaces.alloc(Interface { + name: Some(iface.name.to_string()), + url: None, + types: IndexMap::new(), + docs: Docs::default(), + document: doc, + functions: Vec::new(), + }); + DocumentItem::Interface(id) + }); + match item { + DocumentItem::Interface(id) => *id, + _ => unreachable!(), + } + } + None => *self.documents[doc] + .default_interface + .get_or_insert_with(|| { + self.interface_types.push(IndexMap::new()); + self.interface_spans.push(doc_span); + self.interfaces.alloc(Interface { + name: None, + url: None, + types: IndexMap::new(), + docs: Docs::default(), + document: doc, + functions: Vec::new(), + }) + }), + }; - if interface_map.insert(name.to_string(), instance).is_some() { + let names = match names { + Some(names) => names, + None => return Ok(()), + }; + let lookup = &mut self.interface_types[iface.index()]; + for name in names { + // If this name has already been defined then use that prior + // definition, otherwise create a new type with an unknown + // representation and insert it into the various maps. + if lookup.contains_key(name.name.name) { + continue; + } + let id = self.types.alloc(TypeDef { + docs: Docs::default(), + kind: TypeDefKind::Unknown, + name: Some(name.name.name.to_string()), + owner: TypeOwner::Interface(iface), + }); + self.unknown_type_spans.push(name.name.span); + lookup.insert(name.name.name, InterfaceItem::Type(id)); + self.interfaces[iface] + .types + .insert(name.name.name.to_string(), id); + } + + Ok(()) + }) + .unwrap(); + } + } + + fn resolve_document(&mut self, name: &'a str, ast: &ast::Ast<'a>) -> Result { + // Verify all top-level names in this document are unique, and save off + // the name of the default interface for drawing topological + // dependencies in a moment. + let mut names = HashMap::new(); + let mut default_interface_name = None; + let mut deps = IndexMap::new(); + for item in ast.items.iter() { + let name = match item { + ast::AstItem::Interface(i) => { + if i.default && default_interface_name.is_none() { + default_interface_name = Some(i.name.name); + } + &i.name + } + ast::AstItem::World(w) => &w.name, + }; + deps.insert(name.name, Vec::new()); + if names.insert(name.name, item).is_some() { return Err(Error { - span: interface.name.span, - msg: format!("interface `{name}` is defined more than once"), + span: name.span, + msg: format!("name `{}` previously defined in document", name.name), } .into()); } } - for w in ast.worlds() { - let mut world = World { - name: w.name.name.to_string(), - docs: self.docs(&w.docs), - imports: Default::default(), - exports: Default::default(), - default: None, + // Walk all `UsePath` entries in this AST and record dependencies + // between interfaces. These are dependencies specified via `use + // self...` or via `use pkg.this-doc...`. + ast.foreach_path(|iface, path, _names| { + // If this import isn't contained within an interface then it's in a + // world and it doesn't need to participate in our topo-sort. + let iface = match iface { + Some(name) => name, + None => return Ok(()), }; - - for item in w.items.iter() { - match item { - WorldItem::Import(import) => { - let ast::Import { name, kind } = import; - self.insert_extern( - name, - kind, - "import", - &mut world.imports, - &interface_map, - )?; + let deps = &mut deps[iface.name]; + match path { + // Self-deps are what we're mostly interested in here. + ast::UsePath::Self_(name) => deps.push(name.clone()), + + // Self-deps via the package happen when the `doc` name listed + // is the same as the name of the document being defined. + ast::UsePath::Package { doc, iface } => { + if doc.name != name { + return Ok(()); } - WorldItem::Export(export) => { - let ast::Export { name, kind } = export; - self.insert_extern( - name, - kind, - "export", - &mut world.exports, - &interface_map, - )?; - } - WorldItem::ExportDefault(iface) => { - if world.default.is_some() { - return Err(Error { - span: iface.span(), - msg: "more than one default interface was defined".to_string(), + let name = match iface { + Some(name) => name.clone(), + None => { + let name = default_interface_name.ok_or_else(|| Error { + span: doc.span, + msg: format!("no `default` interface in document to use from"), + })?; + ast::Id { + span: doc.span, + name, } - .into()); } + }; + deps.push(name); + } - let iface = self.resolve_extern(iface, &interface_map)?; - world.default = Some(iface); + // Dependencies on other packages don't participate in this + // topological ordering. + ast::UsePath::Dependency { .. } => {} + } + Ok(()) + })?; + let order = toposort("interface", &deps)?; + log::debug!("toposort for interfaces in `{name}` is {order:?}"); + + // Allocate a document ID and then start processing all of the + // interfaces as defined by our `order` to insert new interfaces into + // this. + let document_id = self.documents.alloc(Document { + name: name.to_string(), + default_interface: None, + default_world: None, + interfaces: IndexMap::new(), + worlds: Vec::new(), + }); + self.document_interfaces.push(IndexMap::new()); + self.document_lookup.insert(name, document_id); + + let mut worlds = Vec::new(); + for name in order { + let interface = match names.remove(&name).unwrap() { + ast::AstItem::Interface(i) => i, + ast::AstItem::World(world) => { + worlds.push((name, world)); + continue; + } + }; + let id = self.resolve_interface( + document_id, + Some(interface.name.name), + &interface.items, + &interface.docs, + )?; + if interface.default { + let prev = &mut self.documents[document_id].default_interface; + if prev.is_some() { + return Err(Error { + span: interface.name.span, + msg: format!("cannot specify more than one `default` interface"), } + .into()); } + *prev = Some(id); } + let prev = self.documents[document_id] + .interfaces + .insert(name.to_string(), id); + assert!(prev.is_none()); + } - self.worlds.alloc(world); + // After all interfaces are defined then process all worlds as all items + // they import from should now be available. + for (_name, world) in worlds { + let id = self.resolve_world(document_id, world)?; + if world.default { + let prev = &mut self.documents[document_id].default_world; + if prev.is_some() { + return Err(Error { + span: world.name.span, + msg: format!("cannot specify more than one `default` world"), + } + .into()); + } + *prev = Some(id); + } + self.documents[document_id].worlds.push(id); } - Ok(Document { - worlds: mem::take(&mut self.worlds), - interfaces: mem::take(&mut self.interfaces), - types: mem::take(&mut self.types), - }) + Ok(document_id) } - fn insert_extern( - &mut self, - id: &ast::Id<'_>, - kind: &ast::ExternKind<'_>, - direction: &str, - resolved: &mut IndexMap, - lookup: &IndexMap, - ) -> Result<()> { - let interface = self.resolve_extern(kind, lookup)?; - if resolved.insert(id.name.to_string(), interface).is_some() { - return Err(Error { - span: id.span, - msg: format!("duplicate {direction} {}", id.name), - } - .into()); + fn resolve_world(&mut self, document: DocumentId, world: &ast::World<'a>) -> Result { + let docs = self.docs(&world.docs); + let world_id = self.worlds.alloc(World { + docs, + document, + name: world.name.name.to_string(), + exports: IndexMap::new(), + imports: IndexMap::new(), + }); + self.document_interfaces[document.index()] + .insert(world.name.name, DocumentItem::World(world_id)); + + let mut imported_interfaces = HashMap::new(); + let mut exported_interfaces = HashMap::new(); + let mut import_spans = Vec::new(); + let mut export_spans = Vec::new(); + for item in world.items.iter() { + match item { + // ast::WorldItem::Use(_) => todo!(), + ast::WorldItem::Import(import) => { + let item = self.resolve_world_item(document, &import.kind)?; + if let WorldItem::Interface(id) = item { + if imported_interfaces.insert(id, import.name.name).is_some() { + return Err(Error { + span: import.name.span, + msg: format!("cannot import same interface twice"), + } + .into()); + } + } + let imports = &mut self.worlds[world_id].imports; + let prev = imports.insert(import.name.name.to_string(), item); + if prev.is_some() { + return Err(Error { + span: import.name.span, + msg: format!("name imported twice"), + } + .into()); + } + import_spans.push(import.name.span); + } + ast::WorldItem::Export(export) => { + let item = self.resolve_world_item(document, &export.kind)?; + if let WorldItem::Interface(id) = item { + if exported_interfaces.insert(id, export.name.name).is_some() { + return Err(Error { + span: export.name.span, + msg: format!("cannot export same interface twice"), + } + .into()); + } + } + let exports = &mut self.worlds[world_id].exports; + let prev = exports.insert(export.name.name.to_string(), item); + if prev.is_some() { + return Err(Error { + span: export.name.span, + msg: format!("name exported twice"), + } + .into()); + } + export_spans.push(export.name.span); + } + } } - Ok(()) + self.world_spans.push((import_spans, export_spans)); + + Ok(world_id) } - fn resolve_extern( + fn resolve_world_item( &mut self, - kind: &ast::ExternKind<'_>, - lookup: &IndexMap, - ) -> Result { + document: DocumentId, + kind: &ast::ExternKind<'a>, + ) -> Result { match kind { ast::ExternKind::Interface(_span, items) => { - self.resolve_interface("", items, &Default::default()) + let id = self.resolve_interface(document, None, items, &Default::default())?; + Ok(WorldItem::Interface(id)) } - ast::ExternKind::Id(id) => lookup.get(&*id.name).cloned().ok_or_else(|| { - Error { - span: id.span, - msg: format!("{} not defined", id.name), - } - .into() - }), + ast::ExternKind::Path(path) => { + let id = self.resolve_path(path)?; + Ok(WorldItem::Interface(id)) + } // ast::ExternKind::Func(_) => todo!(), } } - pub(crate) fn resolve_interface( + fn resolve_interface( &mut self, - name: &str, - fields: &[InterfaceItem<'_>], - docs: &super::Docs<'_>, + document: DocumentId, + name: Option<&'a str>, + fields: &[ast::InterfaceItem<'a>], + docs: &super::Docs<'a>, ) -> Result { - // ... then register our own names - self.register_names(fields)?; - - // With all names registered we can now fully expand and translate all - // types. - for field in fields { - let t = match field { - InterfaceItem::TypeDef(t) => t, - _ => continue, - }; - let id = self.type_lookup[&*t.name.name]; - let kind = self.resolve_type_def(&t.ty)?; - self.types.get_mut(id).unwrap().kind = kind; + let docs = self.docs(docs); + let interface_id = self.interfaces.alloc(Interface { + docs, + document, + name: name.map(|s| s.to_string()), + url: None, + functions: Vec::new(), + types: IndexMap::new(), + }); + assert_eq!(interface_id.index(), self.interface_types.len()); + self.interface_types.push(IndexMap::new()); + if let Some(name) = name { + self.document_interfaces[document.index()] + .insert(name, DocumentItem::Interface(interface_id)); } - // And finally we can resolve all type references in functions/globals - // and additionally validate that types thesmelves are not recursive - let mut valid_types = HashSet::new(); - let mut visiting = HashSet::new(); + // First, populate our namespace with `use` statements for field in fields { match field { - InterfaceItem::Value(v) => self.resolve_value(v)?, - InterfaceItem::TypeDef(t) => { - self.validate_type_not_recursive( - t.name.span, - self.type_lookup[&*t.name.name], - &mut visiting, - &mut valid_types, - )?; + ast::InterfaceItem::Use(u) => { + let use_from = self.resolve_path(&u.from)?; + for name in u.names.iter() { + let lookup = &self.interface_types[use_from.index()]; + let id = match lookup.get(name.name.name) { + Some(InterfaceItem::Type(id)) => *id, + Some(InterfaceItem::Func) => { + bail!(Error { + span: name.name.span, + msg: format!("cannot import function `{}`", name.name.name), + }) + } + None => bail!(Error { + span: name.name.span, + msg: format!("name `{}` is not defined", name.name.name), + }), + }; + let name = name.as_.as_ref().unwrap_or(&name.name); + let id = self.types.alloc(TypeDef { + docs: Docs::default(), + kind: TypeDefKind::Type(Type::Id(id)), + name: Some(name.name.to_string()), + owner: TypeOwner::Interface(interface_id), + }); + self.define_interface_name(name, InterfaceItem::Type(id))?; + self.interfaces[interface_id] + .types + .insert(name.name.to_string(), id); + } } + ast::InterfaceItem::TypeDef(_) | ast::InterfaceItem::Value(_) => {} } } - let iface = Interface { - name: name.to_string(), - url: None, - docs: self.docs(docs), - types: mem::take(&mut self.type_lookup) - .into_iter() - .map(|(_, ty)| ty) - .collect(), - functions: mem::take(&mut self.functions), - }; - Ok(self.interfaces.alloc(iface)) - } - - fn register_names(&mut self, fields: &[InterfaceItem<'_>]) -> Result<()> { - let mut values = HashSet::new(); + // Next determine dependencies between types, perform a topological + // sort, and then define all types. This will define types in a + // topological fashion, forbid cycles, and weed out references to + // undefined types all in one go. + let mut type_deps = IndexMap::new(); + let mut type_defs = IndexMap::new(); for field in fields { match field { - InterfaceItem::TypeDef(t) => { - let docs = self.docs(&t.docs); - let id = self.types.alloc(TypeDef { - docs, - // a dummy kind is used for now which will get filled in - // later with the actual desired contents. - kind: TypeDefKind::List(Type::U8), - name: Some(t.name.name.to_string()), - interface: Some(self.interfaces.next_id()), - }); - self.define_type(&t.name.name, t.name.span, id)?; - } - InterfaceItem::Value(f) => { - if !values.insert(&f.name.name) { + ast::InterfaceItem::TypeDef(t) => { + let prev = type_defs.insert(t.name.name, t); + if prev.is_some() { return Err(Error { - span: f.name.span, - msg: format!("`{}` is defined more than once", f.name.name), + span: t.name.span, + msg: format!("name `{}` is defined more than once", t.name.name), } .into()); } + let mut deps = Vec::new(); + collect_deps(&t.ty, &mut deps); + type_deps.insert(t.name.name, deps); } + ast::InterfaceItem::Use(_) | ast::InterfaceItem::Value(_) => {} } } + let order = toposort("type", &type_deps)?; + for ty in order { + let def = type_defs.remove(&ty).unwrap(); + let docs = self.docs(&def.docs); + let kind = self.resolve_type_def(&def.ty)?; + let id = self.types.alloc(TypeDef { + docs, + kind, + name: Some(def.name.name.to_string()), + owner: TypeOwner::Interface(interface_id), + }); + self.define_interface_name(&def.name, InterfaceItem::Type(id))?; + self.interfaces[interface_id] + .types + .insert(def.name.name.to_string(), id); + } - Ok(()) + // Finally process all function definitions now that all types are + // defined. + for field in fields { + match field { + ast::InterfaceItem::Value(value) => { + let docs = self.docs(&value.docs); + match &value.kind { + ValueKind::Func(Func { params, results }) => { + self.define_interface_name(&value.name, InterfaceItem::Func)?; + let params = self.resolve_params(params)?; + let results = self.resolve_results(results)?; + self.interfaces[interface_id].functions.push(Function { + docs, + name: value.name.name.to_string(), + kind: FunctionKind::Freestanding, + params, + results, + }); + } + } + } + ast::InterfaceItem::Use(_) | ast::InterfaceItem::TypeDef(_) => {} + } + } + + Ok(interface_id) } - fn define_type(&mut self, name: &str, span: Span, id: TypeId) -> Result<()> { - if self.type_lookup.insert(name.to_string(), id).is_some() { + fn resolve_path(&self, path: &ast::UsePath<'a>) -> Result { + match path { + ast::UsePath::Self_(iface) => { + match self.document_interfaces.last().unwrap().get(iface.name) { + Some(DocumentItem::Interface(id)) => Ok(*id), + Some(DocumentItem::World(_)) => bail!(Error { + span: iface.span, + msg: format!( + "name `{}` is defined as a world, not an interface", + iface.name + ), + }), + None => bail!(Error { + span: iface.span, + msg: format!("interface does not exist"), + }), + } + } + ast::UsePath::Package { doc, iface } => { + let doc_id = self.document_lookup[doc.name]; + match iface { + // If `iface` was provided then it must have been previously + // processed + Some(id) => { + let lookup = &self.document_interfaces[doc_id.index()]; + match lookup.get(id.name) { + Some(DocumentItem::Interface(id)) => Ok(*id), + Some(DocumentItem::World(_)) => Err(Error { + span: id.span, + msg: format!("cannot import from world `{}`", id.name), + } + .into()), + None => bail!(Error { + span: id.span, + msg: format!("interface does not exist"), + }), + } + } + None => self.documents[doc_id].default_interface.ok_or_else(|| { + Error { + span: doc.span, + msg: format!("document does not specify a default interface"), + } + .into() + }), + } + } + ast::UsePath::Dependency { dep, doc, iface } => { + let doc = self.foreign_deps[dep.name][doc.name]; + match iface { + Some(name) => match self.document_interfaces[doc.index()][name.name] { + DocumentItem::Interface(id) => Ok(id), + DocumentItem::World(_) => unreachable!(), + }, + None => Ok(self.documents[doc].default_interface.unwrap()), + } + } + } + } + + fn define_interface_name(&mut self, name: &ast::Id<'a>, item: InterfaceItem) -> Result<()> { + let prev = self + .interface_types + .last_mut() + .unwrap() + .insert(name.name, item); + if prev.is_some() { Err(Error { - span, - msg: format!("type `{}` is defined more than once", name), + span: name.span, + msg: format!("name `{}` is defined more than once", name.name), } .into()) } else { @@ -236,39 +670,41 @@ impl Resolver { } } - fn resolve_type_def(&mut self, ty: &super::Type<'_>) -> Result { + fn resolve_type_def(&mut self, ty: &ast::Type<'_>) -> Result { Ok(match ty { - super::Type::Bool => TypeDefKind::Type(Type::Bool), - super::Type::U8 => TypeDefKind::Type(Type::U8), - super::Type::U16 => TypeDefKind::Type(Type::U16), - super::Type::U32 => TypeDefKind::Type(Type::U32), - super::Type::U64 => TypeDefKind::Type(Type::U64), - super::Type::S8 => TypeDefKind::Type(Type::S8), - super::Type::S16 => TypeDefKind::Type(Type::S16), - super::Type::S32 => TypeDefKind::Type(Type::S32), - super::Type::S64 => TypeDefKind::Type(Type::S64), - super::Type::Float32 => TypeDefKind::Type(Type::Float32), - super::Type::Float64 => TypeDefKind::Type(Type::Float64), - super::Type::Char => TypeDefKind::Type(Type::Char), - super::Type::String => TypeDefKind::Type(Type::String), - super::Type::Name(name) => { - let id = match self.type_lookup.get(&*name.name) { - Some(id) => *id, - None => { - return Err(Error { - span: name.span, - msg: format!("no type named `{}`", name.name), - } - .into()) - } + ast::Type::Bool => TypeDefKind::Type(Type::Bool), + ast::Type::U8 => TypeDefKind::Type(Type::U8), + ast::Type::U16 => TypeDefKind::Type(Type::U16), + ast::Type::U32 => TypeDefKind::Type(Type::U32), + ast::Type::U64 => TypeDefKind::Type(Type::U64), + ast::Type::S8 => TypeDefKind::Type(Type::S8), + ast::Type::S16 => TypeDefKind::Type(Type::S16), + ast::Type::S32 => TypeDefKind::Type(Type::S32), + ast::Type::S64 => TypeDefKind::Type(Type::S64), + ast::Type::Float32 => TypeDefKind::Type(Type::Float32), + ast::Type::Float64 => TypeDefKind::Type(Type::Float64), + ast::Type::Char => TypeDefKind::Type(Type::Char), + ast::Type::String => TypeDefKind::Type(Type::String), + ast::Type::Name(name) => { + let interface_types = self.interface_types.last().unwrap(); + let id = match interface_types.get(name.name) { + Some(InterfaceItem::Type(id)) => *id, + Some(InterfaceItem::Func) => bail!(Error { + span: name.span, + msg: format!("cannot use a function as a type"), + }), + None => bail!(Error { + span: name.span, + msg: format!("name is not defined"), + }), }; TypeDefKind::Type(Type::Id(id)) } - super::Type::List(list) => { + ast::Type::List(list) => { let ty = self.resolve_type(list)?; TypeDefKind::List(ty) } - super::Type::Record(record) => { + ast::Type::Record(record) => { let fields = record .fields .iter() @@ -282,7 +718,7 @@ impl Resolver { .collect::>>()?; TypeDefKind::Record(Record { fields }) } - super::Type::Flags(flags) => { + ast::Type::Flags(flags) => { let flags = flags .flags .iter() @@ -293,14 +729,14 @@ impl Resolver { .collect::>(); TypeDefKind::Flags(Flags { flags }) } - super::Type::Tuple(types) => { + ast::Type::Tuple(types) => { let types = types .iter() .map(|ty| self.resolve_type(ty)) .collect::>>()?; TypeDefKind::Tuple(Tuple { types }) } - super::Type::Variant(variant) => { + ast::Type::Variant(variant) => { if variant.cases.is_empty() { return Err(Error { span: variant.span, @@ -321,7 +757,7 @@ impl Resolver { .collect::>>()?; TypeDefKind::Variant(Variant { cases }) } - super::Type::Enum(e) => { + ast::Type::Enum(e) => { if e.cases.is_empty() { return Err(Error { span: e.span, @@ -341,12 +777,12 @@ impl Resolver { .collect::>>()?; TypeDefKind::Enum(Enum { cases }) } - super::Type::Option(ty) => TypeDefKind::Option(self.resolve_type(ty)?), - super::Type::Result(r) => TypeDefKind::Result(Result_ { + ast::Type::Option(ty) => TypeDefKind::Option(self.resolve_type(ty)?), + ast::Type::Result(r) => TypeDefKind::Result(Result_ { ok: self.resolve_optional_type(r.ok.as_deref())?, err: self.resolve_optional_type(r.err.as_deref())?, }), - super::Type::Union(e) => { + ast::Type::Union(e) => { if e.cases.is_empty() { return Err(Error { span: e.span, @@ -366,10 +802,8 @@ impl Resolver { .collect::>>()?; TypeDefKind::Union(Union { cases }) } - super::Type::Future(t) => { - TypeDefKind::Future(self.resolve_optional_type(t.as_deref())?) - } - super::Type::Stream(s) => TypeDefKind::Stream(Stream { + ast::Type::Future(t) => TypeDefKind::Future(self.resolve_optional_type(t.as_deref())?), + ast::Type::Stream(s) => TypeDefKind::Stream(Stream { element: self.resolve_optional_type(s.element.as_deref())?, end: self.resolve_optional_type(s.end.as_deref())?, }), @@ -382,7 +816,7 @@ impl Resolver { kind, name: None, docs: Docs::default(), - interface: None, + owner: TypeOwner::None, })) } @@ -421,6 +855,7 @@ impl Resolver { TypeDefKind::Union(u) => Key::Union(u.cases.iter().map(|c| c.ty).collect()), TypeDefKind::Future(ty) => Key::Future(*ty), TypeDefKind::Stream(s) => Key::Stream(s.element, s.end), + TypeDefKind::Unknown => unreachable!(), }; let types = &mut self.types; let id = self @@ -455,24 +890,6 @@ impl Resolver { Docs { contents: docs } } - fn resolve_value(&mut self, value: &Value<'_>) -> Result<()> { - let docs = self.docs(&value.docs); - match &value.kind { - ValueKind::Func(Func { params, results }) => { - let params = self.resolve_params(params)?; - let results = self.resolve_results(results)?; - self.functions.push(Function { - docs, - name: value.name.name.to_string(), - kind: FunctionKind::Freestanding, - params, - results, - }); - } - } - Ok(()) - } - fn resolve_params(&mut self, params: &ParamList<'_>) -> Result { params .iter() @@ -486,93 +903,70 @@ impl Resolver { ResultList::Anon(ty) => Ok(Results::Anon(self.resolve_type(ty)?)), } } +} - fn validate_type_not_recursive( - &self, - span: Span, - ty: TypeId, - visiting: &mut HashSet, - valid: &mut HashSet, - ) -> Result<()> { - if valid.contains(&ty) { - return Ok(()); - } - if !visiting.insert(ty) { - return Err(Error { - span, - msg: "type can recursively refer to itself".to_string(), +fn collect_deps<'a>(ty: &ast::Type<'a>, deps: &mut Vec>) { + match ty { + ast::Type::Bool + | ast::Type::U8 + | ast::Type::U16 + | ast::Type::U32 + | ast::Type::U64 + | ast::Type::S8 + | ast::Type::S16 + | ast::Type::S32 + | ast::Type::S64 + | ast::Type::Float32 + | ast::Type::Float64 + | ast::Type::Char + | ast::Type::String + | ast::Type::Flags(_) + | ast::Type::Enum(_) => {} + ast::Type::Name(name) => deps.push(name.clone()), + ast::Type::List(list) => collect_deps(list, deps), + ast::Type::Record(record) => { + for field in record.fields.iter() { + collect_deps(&field.ty, deps); } - .into()); } - - match &self.types[ty].kind { - TypeDefKind::List(Type::Id(id)) | TypeDefKind::Type(Type::Id(id)) => { - self.validate_type_not_recursive(span, *id, visiting, valid)? - } - TypeDefKind::Variant(v) => { - for case in v.cases.iter() { - if let Some(Type::Id(id)) = case.ty { - self.validate_type_not_recursive(span, id, visiting, valid)?; - } - } + ast::Type::Tuple(types) => { + for ty in types { + collect_deps(ty, deps); } - TypeDefKind::Record(r) => { - for case in r.fields.iter() { - if let Type::Id(id) = case.ty { - self.validate_type_not_recursive(span, id, visiting, valid)?; - } + } + ast::Type::Variant(variant) => { + for case in variant.cases.iter() { + if let Some(ty) = &case.ty { + collect_deps(ty, deps); } } - TypeDefKind::Tuple(t) => { - for ty in t.types.iter() { - if let Type::Id(id) = *ty { - self.validate_type_not_recursive(span, id, visiting, valid)?; - } - } + } + ast::Type::Option(ty) => collect_deps(ty, deps), + ast::Type::Result(r) => { + if let Some(ty) = &r.ok { + collect_deps(ty, deps); } - - TypeDefKind::Option(t) => { - if let Type::Id(id) = *t { - self.validate_type_not_recursive(span, id, visiting, valid)? - } + if let Some(ty) = &r.err { + collect_deps(ty, deps); } - TypeDefKind::Result(r) => { - if let Some(Type::Id(id)) = r.ok { - self.validate_type_not_recursive(span, id, visiting, valid)? - } - if let Some(Type::Id(id)) = r.err { - self.validate_type_not_recursive(span, id, visiting, valid)? - } + } + ast::Type::Union(e) => { + for case in e.cases.iter() { + collect_deps(&case.ty, deps); } - TypeDefKind::Future(t) => { - if let Some(Type::Id(id)) = *t { - self.validate_type_not_recursive(span, id, visiting, valid)? - } + } + ast::Type::Future(t) => { + if let Some(t) = t { + collect_deps(t, deps) } - TypeDefKind::Stream(s) => { - if let Some(Type::Id(id)) = s.element { - self.validate_type_not_recursive(span, id, visiting, valid)? - } - if let Some(Type::Id(id)) = s.end { - self.validate_type_not_recursive(span, id, visiting, valid)? - } + } + ast::Type::Stream(s) => { + if let Some(t) = &s.element { + collect_deps(t, deps); } - TypeDefKind::Union(u) => { - for c in u.cases.iter() { - if let Type::Id(id) = c.ty { - self.validate_type_not_recursive(span, id, visiting, valid)? - } - } + if let Some(t) = &s.end { + collect_deps(t, deps); } - - TypeDefKind::Flags(_) - | TypeDefKind::List(_) - | TypeDefKind::Type(_) - | TypeDefKind::Enum(_) => {} } - - valid.insert(ty); - visiting.remove(&ty); - Ok(()) } } diff --git a/crates/wit-parser/src/ast/toposort.rs b/crates/wit-parser/src/ast/toposort.rs new file mode 100644 index 0000000000..c009d24f14 --- /dev/null +++ b/crates/wit-parser/src/ast/toposort.rs @@ -0,0 +1,149 @@ +use crate::ast::{Id, Span}; +use anyhow::Result; +use indexmap::{IndexMap, IndexSet}; +use std::collections::HashSet; +use std::fmt; + +pub fn toposort<'a>( + kind: &str, + deps: &IndexMap<&'a str, Vec>>, +) -> Result, Error> { + // First make sure that all dependencies actually point to other valid items + // that are known. + for (_, names) in deps { + for name in names { + deps.get(name.name).ok_or_else(|| Error::NonexistentDep { + span: name.span, + name: name.name.to_string(), + kind: kind.to_string(), + })?; + } + } + + // Then recursively visit all dependencies building up the topological order + // as we go and guarding against cycles with a separate visitation set. + let mut order = IndexSet::new(); + let mut visiting = HashSet::new(); + for dep in deps.keys() { + visit(dep, deps, &mut order, &mut visiting, kind)?; + } + Ok(order.into_iter().collect()) +} + +fn visit<'a>( + dep: &'a str, + deps: &IndexMap<&'a str, Vec>>, + order: &mut IndexSet<&'a str>, + visiting: &mut HashSet<&'a str>, + kind: &str, +) -> Result<(), Error> { + if order.contains(dep) { + return Ok(()); + } + + for dep in deps[dep].iter() { + if !visiting.insert(dep.name) { + return Err(Error::Cycle { + span: dep.span, + name: dep.name.to_string(), + kind: kind.to_string(), + }); + } + visit(dep.name, deps, order, visiting, kind)?; + assert!(visiting.remove(&dep.name)); + } + + assert!(order.insert(dep)); + Ok(()) +} + +#[derive(Debug)] +pub enum Error { + NonexistentDep { + span: Span, + name: String, + kind: String, + }, + Cycle { + span: Span, + name: String, + kind: String, + }, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::NonexistentDep { kind, name, .. } => { + write!(f, "{kind} `{name}` does not exist") + } + Error::Cycle { kind, name, .. } => { + write!(f, "{kind} `{name}` depends on itself") + } + } + } +} + +impl std::error::Error for Error {} + +#[cfg(test)] +mod tests { + use super::*; + + fn id(name: &str) -> Id<'_> { + Id { + name, + span: Span { start: 0, end: 0 }, + } + } + + #[test] + fn smoke() { + let empty: Vec<&str> = Vec::new(); + assert_eq!(toposort("", &IndexMap::new()).unwrap(), empty); + + let mut nonexistent = IndexMap::new(); + nonexistent.insert("a", vec![id("b")]); + assert!(matches!( + toposort("", &nonexistent), + Err(Error::NonexistentDep { .. }) + )); + + let mut one = IndexMap::new(); + one.insert("a", vec![]); + assert_eq!(toposort("", &one).unwrap(), ["a"]); + + let mut two = IndexMap::new(); + two.insert("a", vec![]); + two.insert("b", vec![id("a")]); + assert_eq!(toposort("", &two).unwrap(), ["a", "b"]); + + let mut two = IndexMap::new(); + two.insert("a", vec![id("b")]); + two.insert("b", vec![]); + assert_eq!(toposort("", &two).unwrap(), ["b", "a"]); + } + + #[test] + fn cycles() { + let mut cycle = IndexMap::new(); + cycle.insert("a", vec![id("a")]); + let mut set = IndexSet::new(); + set.insert("a"); + assert!(matches!(toposort("", &cycle), Err(Error::Cycle { .. }))); + + let mut cycle = IndexMap::new(); + cycle.insert("a", vec![id("b")]); + cycle.insert("b", vec![id("c")]); + cycle.insert("c", vec![id("a")]); + assert!(matches!(toposort("", &cycle), Err(Error::Cycle { .. }))); + } + + #[test] + fn depend_twice() { + let mut two = IndexMap::new(); + two.insert("b", vec![id("a"), id("a")]); + two.insert("a", vec![]); + assert_eq!(toposort("", &two).unwrap(), ["a", "b"]); + } +} diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 3be1769e33..7c08a71652 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -1,18 +1,17 @@ -use anyhow::{bail, Context, Result}; -use ast::lex::Tokenizer; +use anyhow::{Context, Result}; use id_arena::{Arena, Id}; use indexmap::IndexMap; -use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag}; use std::borrow::Cow; -use std::collections::HashSet; +use std::fmt; use std::path::Path; pub mod abi; mod ast; +use ast::{lex::Span, Ast, Resolver, SourceMap}; mod sizealign; pub use sizealign::*; -mod merge; -pub use merge::*; +mod resolve; +pub use resolve::{Package, PackageId, Resolve}; /// Checks if the given string is a legal identifier in wit. pub fn validate_id(s: &str) -> Result<()> { @@ -20,260 +19,254 @@ pub fn validate_id(s: &str) -> Result<()> { Ok(()) } -fn unwrap_md(contents: &str) -> String { - let mut wit = String::new(); - let mut last_pos = 0; - let mut in_wit_code_block = false; - Parser::new_ext(contents, Options::empty()) - .into_offset_iter() - .for_each(|(event, range)| match (event, range) { - (Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed("wit")))), _) => { - in_wit_code_block = true; - } - (Event::Text(text), range) if in_wit_code_block => { - // Ensure that offsets are correct by inserting newlines to - // cover the Markdown content outside of wit code blocks. - for _ in contents[last_pos..range.start].lines() { - wit.push('\n'); - } - wit.push_str(&text); - last_pos = range.end; - } - (Event::End(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed("wit")))), _) => { - in_wit_code_block = false; - } - _ => {} - }); - wit -} - -/// Represents the result of parsing a wit document. -#[derive(Default, Clone)] -pub struct Document { - /// The worlds contained in the document. - pub worlds: Arena, - /// The top-level interfaces contained in the document. - pub interfaces: Arena, - /// All types in all interfaces - pub types: Arena, -} - pub type WorldId = Id; pub type InterfaceId = Id; pub type TypeId = Id; +pub type DocumentId = Id; + +/// Representation of a parsed WIT package which has not resolved external +/// dependencies yet. +/// +/// This representation has performed internal resolution of the WIT package +/// itself, ensuring that all references internally are valid and the WIT was +/// syntactically valid and such. +/// +/// The fields of this structure represent a flat list of arrays unioned from +/// all documents within the WIT package. This means, for example, that all +/// types from all documents are located in `self.types`. The fields of each +/// item can help splitting back out into packages/interfaces/etc as necessary. +/// +/// Note that an `UnresolvedPackage` cannot be queried in general about +/// information such as size or alignment as that would require resolution of +/// foreign dependencies. Translations such as to-binary additionally are not +/// supported on an `UnresolvedPackage` due to the lack of knowledge about the +/// foreign types. This is intended to be an intermediate state which can be +/// inspected by embedders, if necessary, before quickly transforming to a +/// `Resolve` to fully work with a WIT package. +// +// TODO: implement and add docs about converting to a `Resolve` +#[derive(Default, Clone)] +pub struct UnresolvedPackage { + /// All worlds from all documents within this package. + /// + /// Each world lists the document that it is from. + pub worlds: Arena, -impl Document { - /// Parses the given string as a wit document. + /// All interfaces from all documents within this package. /// - /// The `path` argument is used for error reporting. - pub fn parse(path: &Path, contents: &str) -> Result { - Self::_parse(path, contents) - } + /// Each interface lists the document that it is from. Interfaces are listed + /// in topological order as well so iteration through this arena will only + /// reference prior elements already visited when working with recursive + /// references. + pub interfaces: Arena, - /// Parses the given string as a wit document. + /// All types from all documents within this package. /// - /// The `path` argument is used for error reporting. - pub fn parse_file(path: &Path) -> Result { - let contents = std::fs::read_to_string(path) - .with_context(|| format!("failed to read file {path:?}"))?; - Self::_parse(path, &contents) - } + /// Each type lists the interface or world that defined it, or nothing if + /// it's an anonymous type. Types are listed in this arena in topological + /// order to ensure that iteration through this arena will only reference + /// other types transitively that are already iterated over. + pub types: Arena, - // This will eventually grow a closure which is "go read some other file" - // for `use` statements - fn _parse(path: &Path, contents: &str) -> Result { - // If we have a ".md" file, it's a wit file wrapped in a markdown file; - // parse the markdown to extract the `wit` code blocks. - let contents: Cow<'_, str> = if path.extension().and_then(|s| s.to_str()) == Some("md") { - unwrap_md(contents).into() - } else { - contents.into() - }; - - Self::rewrite_error(path, &contents, || { - let mut lexer = Tokenizer::new(&contents)?; - let ast = ast::Ast::parse(&mut lexer)?; - ast::Resolver::default().resolve(&ast) - }) - } + /// All documents found within this package. + /// + /// Documents are sorted topologically in this arena with respect to imports + /// between them. + pub documents: Arena, - /// Converts the document into a single world definition. + /// All foreign dependencies that this package depends on. /// - /// Returns an error if there were no worlds defined in the document or - /// if there were multiple worlds defined. - pub fn default_world(&self) -> Result { - match self.worlds.len() { - 0 => bail!("no worlds were defined in the document"), - 1 => Ok(self.worlds.iter().next().unwrap().0), - _ => bail!("more than one world was defined in the document",), - } + /// These foreign dependencies must be resolved to convert this unresolved + /// package into a `Resolve`. The map here is keyed by the name of the + /// foreign package that this depends on, and the sub-map is keyed by a + /// document name followed by the identifier within `self.documents`. The + /// fields of `self.documents` describes the required types, interfaces, + /// etc, that are required from each foreign package. + pub foreign_deps: IndexMap>, + + unknown_type_spans: Vec, + world_spans: Vec<(Vec, Vec)>, + document_spans: Vec, + interface_spans: Vec, + foreign_dep_spans: Vec, + source_map: SourceMap, +} + +#[derive(Debug)] +struct Error { + span: Span, + msg: String, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.msg.fmt(f) } +} - /// Converts the document into a single interface definition. - /// - /// Returns an error if there were no worlds defined in the document or - /// if there were multiple worlds defined. - pub fn default_interface(&self) -> Result { - if self.worlds.len() > 0 { - bail!("a world may not be defined in an interface definition"); - } +impl std::error::Error for Error {} - match self.interfaces.len() { - 0 => bail!("no interfaces were defined in the document"), - 1 => Ok(self.interfaces.iter().next().unwrap().0), - _ => bail!("more than one interface was defined in the document"), - } +impl UnresolvedPackage { + /// Parses the given string as a wit document. + /// + /// The `path` argument is used for error reporting. The `contents` provided + /// will not be able to use `pkg` use paths to other documents. + pub fn parse(path: &Path, contents: &str) -> Result { + let mut map = SourceMap::default(); + map.push(path, contents); + Self::_parse(map) } - fn rewrite_error(path: &Path, contents: &str, f: F) -> Result - where - F: FnOnce() -> Result, - { - match f() { - Ok(t) => Ok(t), - Err(mut e) => { - let file = path.display().to_string(); - ast::rewrite_error(&mut e, &file, contents); - Err(e) - } + /// Parse a WIT package at the provided path. + /// + /// The path provided is inferred whether it's a file or a directory. A file + /// is parsed with [`UnresolvedPackage::parse_file`] and a directory is + /// parsed with [`UnresolvedPackage::parse_dir`]. + pub fn parse_path(path: &Path) -> Result { + if path.is_dir() { + UnresolvedPackage::parse_dir(path) + } else { + UnresolvedPackage::parse_file(path) } } - pub fn topological_types(&self) -> Vec { - let mut ret = Vec::new(); - let mut visited = HashSet::new(); - for (id, _) in self.types.iter() { - self.topo_visit(id, &mut ret, &mut visited); - } - ret + /// Parses a WIT package from the file provided. + /// + /// The WIT package returned will be a single-document package and will not + /// be able to use `pkg` paths to other documents. + pub fn parse_file(path: &Path) -> Result { + let contents = std::fs::read_to_string(path) + .with_context(|| format!("failed to read file {path:?}"))?; + Self::parse(path, &contents) } - fn topo_visit(&self, id: TypeId, list: &mut Vec, visited: &mut HashSet) { - if !visited.insert(id) { - return; - } - match &self.types[id].kind { - TypeDefKind::Flags(_) | TypeDefKind::Enum(_) => {} - TypeDefKind::Type(t) | TypeDefKind::List(t) => self.topo_visit_ty(t, list, visited), - TypeDefKind::Record(r) => { - for f in r.fields.iter() { - self.topo_visit_ty(&f.ty, list, visited); - } - } - TypeDefKind::Tuple(t) => { - for t in t.types.iter() { - self.topo_visit_ty(t, list, visited); - } - } - TypeDefKind::Variant(v) => { - for v in v.cases.iter() { - if let Some(ty) = v.ty { - self.topo_visit_ty(&ty, list, visited); - } - } - } - TypeDefKind::Option(ty) => self.topo_visit_ty(ty, list, visited), - TypeDefKind::Result(r) => { - if let Some(ok) = r.ok { - self.topo_visit_ty(&ok, list, visited); - } - if let Some(err) = r.err { - self.topo_visit_ty(&err, list, visited); - } + /// Parses a WIT package from the directory provided. + /// + /// All files with the extension `*.wit` or `*.wit.md` will be loaded from + /// `path` into the returned package. + pub fn parse_dir(path: &Path) -> Result { + let mut map = SourceMap::default(); + let cx = || format!("failed to read directory {path:?}"); + for entry in path.read_dir().with_context(&cx)? { + let entry = entry.with_context(&cx)?; + let path = entry.path(); + let ty = entry.file_type().with_context(&cx)?; + if ty.is_dir() { + continue; } - TypeDefKind::Union(u) => { - for t in u.cases.iter() { - self.topo_visit_ty(&t.ty, list, visited); + if ty.is_symlink() { + if path.is_dir() { + continue; } } - TypeDefKind::Future(ty) => { - if let Some(ty) = ty { - self.topo_visit_ty(ty, list, visited); - } - } - TypeDefKind::Stream(s) => { - if let Some(element) = s.element { - self.topo_visit_ty(&element, list, visited); - } - if let Some(end) = s.end { - self.topo_visit_ty(&end, list, visited); - } + let filename = match path.file_name().and_then(|s| s.to_str()) { + Some(name) => name, + None => continue, + }; + if !filename.ends_with(".wit") && !filename.ends_with(".wit.md") { + continue; } + let contents = std::fs::read_to_string(&path) + .with_context(|| format!("failed to read file {path:?}"))?; + map.push(&path, contents); } - list.push(id); + Self::_parse(map) } - fn topo_visit_ty(&self, ty: &Type, list: &mut Vec, visited: &mut HashSet) { - if let Type::Id(id) = ty { - self.topo_visit(*id, list, visited); - } + fn _parse(map: SourceMap) -> Result { + let mut doc = map.rewrite_error(|| { + let mut resolver = Resolver::default(); + for file in map.tokenizers() { + let (path, mut tokens) = file?; + let ast = Ast::parse(&mut tokens)?; + resolver.push(path, ast)?; + } + resolver.resolve() + })?; + doc.source_map = map; + Ok(doc) } +} - pub fn all_bits_valid(&self, ty: &Type) -> bool { - match ty { - Type::U8 - | Type::S8 - | Type::U16 - | Type::S16 - | Type::U32 - | Type::S32 - | Type::U64 - | Type::S64 - | Type::Float32 - | Type::Float64 => true, - - Type::Bool | Type::Char | Type::String => false, - - Type::Id(id) => match &self.types[*id].kind { - TypeDefKind::List(_) - | TypeDefKind::Variant(_) - | TypeDefKind::Enum(_) - | TypeDefKind::Option(_) - | TypeDefKind::Result(_) - | TypeDefKind::Future(_) - | TypeDefKind::Stream(_) - | TypeDefKind::Union(_) => false, - TypeDefKind::Type(t) => self.all_bits_valid(t), - TypeDefKind::Record(r) => r.fields.iter().all(|f| self.all_bits_valid(&f.ty)), - TypeDefKind::Tuple(t) => t.types.iter().all(|t| self.all_bits_valid(t)), - - // FIXME: this could perhaps be `true` for multiples-of-32 but - // seems better to probably leave this as unconditionally - // `false` for now, may want to reconsider later? - TypeDefKind::Flags(_) => false, - }, - } - } +/// Represents the result of parsing a wit document. +#[derive(Debug, Clone)] +pub struct Document { + pub name: String, + + /// The top-level interfaces contained in the document. + /// + /// The interfaces here are listed in topological order of the + /// dependencies between them. + pub interfaces: IndexMap, + + /// The worlds contained in the document. + pub worlds: Vec, + + /// The default interface of this document, if any. + /// + /// This interface will also be listed in `self.interfaces` + pub default_interface: Option, + + /// The default world of this document, if any. + /// + /// This will also be listed in `self.worlds`. + pub default_world: Option, } -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Debug, Clone)] pub struct World { + /// The WIT identifier name of this world. pub name: String, + + /// Documentation associated with this world declaration. pub docs: Docs, - pub default: Option, - pub imports: IndexMap, - pub exports: IndexMap, -} - -impl World { - /// Returns an iterator which visits all the exported interfaces, both named - /// and default. The second entry in each pair the export name of the - /// interface, or `None` if it's the default export interface. - pub fn exports(&self) -> impl Iterator)> + '_ { - self.exports - .iter() - .map(|(name, i)| (*i, Some(name.as_str()))) - .chain(self.default.iter().map(|i| (*i, None))) - } + + /// All imported items into this interface, both worlds and functions. + pub imports: IndexMap, + + /// All exported items from this interface, both worlds and functions. + pub exports: IndexMap, + + /// The document that owns this world. + pub document: DocumentId, } -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Debug, Clone)] +pub enum WorldItem { + /// An interface is being imported or exported from a world, indicating that + /// it's a namespace of functions. + Interface(InterfaceId), + + /// A function is being directly imported or exported from this world. + Function(Function), +} + +#[derive(Debug, Clone)] pub struct Interface { - pub name: String, + /// Optionally listed name of this interface. + /// + /// This is `None` for inline interfaces in worlds. + pub name: Option, + + /// Optionally listed URL for an interface. + /// + /// NB: this isn't super well managed at this point. pub url: Option, + + /// Documentation associated with this interface. pub docs: Docs, - pub types: Vec, + + /// Exported types from this interface. + /// + /// Export names are listed within the types themselves. Note that the + /// export name here matches the name listed in the `TypeDef`. + pub types: IndexMap, + + /// Exported functions from this interface. pub functions: Vec, + + /// The document that this interface belongs to. + pub document: DocumentId, } #[derive(Debug, Clone, PartialEq)] @@ -281,7 +274,7 @@ pub struct TypeDef { pub docs: Docs, pub kind: TypeDefKind, pub name: Option, - pub interface: Option, + pub owner: TypeOwner, } #[derive(Debug, Clone, PartialEq)] @@ -298,6 +291,24 @@ pub enum TypeDefKind { Future(Option), Stream(Stream), Type(Type), + + /// This represents a type of unknown structure imported from a foreign + /// interface. + /// + /// This variant is only used during the creation of `UnresolvedPackage` but + /// by the time a `Resolve` is created then this will not exist. + Unknown, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum TypeOwner { + /// This type was defined within a `world` block. + World(WorldId), + /// This type was defined within an `interface` block. + Interface(InterfaceId), + /// This type wasn't inherently defined anywhere, such as a `list`, which + /// doesn't need an owner. + None, } #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] @@ -512,12 +523,12 @@ impl Results { } } - pub fn throws<'a>(&self, doc: &'a Document) -> Option<(Option<&'a Type>, Option<&'a Type>)> { + pub fn throws<'a>(&self, resolve: &'a Resolve) -> Option<(Option<&'a Type>, Option<&'a Type>)> { if self.len() != 1 { return None; } match self.iter_types().next().unwrap() { - Type::Id(id) => match &doc.types[*id].kind { + Type::Id(id) => match &resolve.types[*id].kind { TypeDefKind::Result(r) => Some((r.ok.as_ref(), r.err.as_ref())), _ => None, }, diff --git a/crates/wit-parser/src/merge.rs b/crates/wit-parser/src/merge.rs deleted file mode 100644 index 01183664ab..0000000000 --- a/crates/wit-parser/src/merge.rs +++ /dev/null @@ -1,214 +0,0 @@ -use super::*; - -impl Document { - /// Merges a different wit document into this one. - /// - /// This method is an infallible operation that will append all of the - /// contents of `other` into `self`. All types, interfaces, and worlds are - /// moved over en-masse. The return value is a structure which can be used to - /// remap old within `Document` to new ids. - // - // Note that this should eventually not error on duplicate interfaces but - // rather attempt to perform a deep merge on duplicate interfaces. - pub fn merge(&mut self, other: Document) -> Merge { - let mut merge = Merge::default(); - merge.merge(self, other); - merge - } - - /// Merges the world `from` into the world `into`. - /// - /// This will attempt to merge one world into another, unioning all of its - /// imports and exports together. This is an operation performed by - /// `wit-component`, for example where two different worlds from two - /// different libraries were linked into the same core wasm file and are - /// producing a singular world that will be the final component's - /// interface. - /// - /// This operation can fail if the imports/exports overlap. - // - // TODO: overlap shouldn't be a hard error here, there should be some form - // of comparing names/urls/deep merging or such to get this working. - pub fn merge_worlds(&mut self, from: WorldId, into: WorldId) -> Result<()> { - let mut new_imports = Vec::new(); - let mut new_exports = Vec::new(); - - let from_world = &self.worlds[from]; - let into_world = &self.worlds[into]; - for (name, import) in from_world.imports.iter() { - match into_world.imports.get(name) { - Some(_) => bail!("duplicate import found for interface `{name}`"), - None => new_imports.push((name.clone(), *import)), - } - } - for (name, export) in from_world.exports.iter() { - match into_world.exports.get(name) { - Some(_) => bail!("duplicate export found for interface `{name}`"), - None => new_exports.push((name.clone(), *export)), - } - } - let from_world_default = from_world.default; - - let into = &mut self.worlds[into]; - for (name, import) in new_imports { - let prev = into.imports.insert(name, import); - assert!(prev.is_none()); - } - for (name, import) in new_exports { - let prev = into.exports.insert(name, import); - assert!(prev.is_none()); - } - if let Some(i) = from_world_default { - if into.default.is_some() { - bail!("duplicate default export found") - } - into.default = Some(i); - } - - Ok(()) - } -} - -#[derive(Default)] -#[allow(missing_docs)] -pub struct Merge { - pub type_map: Vec>, - pub iface_map: Vec, - pub world_map: Vec, -} - -impl Merge { - fn merge(&mut self, into: &mut Document, other: Document) { - let mut topo_map = vec![0; other.types.len()]; - for (i, id) in other.topological_types().into_iter().enumerate() { - topo_map[id.index()] = i; - } - let Document { - worlds, - interfaces, - types, - } = other; - let mut types = types.into_iter().collect::>(); - types.sort_by_key(|(id, _)| topo_map[id.index()]); - self.type_map.extend((0..types.len()).map(|_| None)); - for (id, mut ty) in types { - self.update_typedef(&mut ty); - let new_id = into.types.alloc(ty); - self.type_map[id.index()] = Some(new_id); - } - - for (id, mut iface) in interfaces { - self.update_interface(&mut iface); - let new_id = into.interfaces.alloc(iface); - assert_eq!(self.iface_map.len(), id.index()); - self.iface_map.push(new_id); - } - - for (id, mut world) in worlds { - self.update_world(&mut world); - let new_id = into.worlds.alloc(world); - assert_eq!(self.world_map.len(), id.index()); - self.world_map.push(new_id); - } - - // Update the `interface` field of `TypeDef` now that all interfaces - // have been processed. - for ty in self.type_map.iter() { - let ty = ty.unwrap(); - if let Some(id) = &mut into.types[ty].interface { - *id = self.iface_map[id.index()]; - } - } - } - - fn update_typedef(&self, typedef: &mut TypeDef) { - self.update_typedef_kind(&mut typedef.kind); - // Note that the interface isn't updated here, it's updated as a - // final pass. - } - - fn update_typedef_kind(&self, typedef: &mut TypeDefKind) { - use TypeDefKind::*; - match typedef { - Record(r) => { - for field in r.fields.iter_mut() { - self.update_ty(&mut field.ty); - } - } - Tuple(t) => { - for ty in t.types.iter_mut() { - self.update_ty(ty); - } - } - Variant(v) => { - for case in v.cases.iter_mut() { - if let Some(t) = &mut case.ty { - self.update_ty(t); - } - } - } - Option(t) => self.update_ty(t), - Result(r) => { - if let Some(ty) = &mut r.ok { - self.update_ty(ty); - } - if let Some(ty) = &mut r.err { - self.update_ty(ty); - } - } - Union(u) => { - for case in u.cases.iter_mut() { - self.update_ty(&mut case.ty); - } - } - List(t) => self.update_ty(t), - Future(Some(t)) => self.update_ty(t), - Stream(t) => { - if let Some(ty) = &mut t.element { - self.update_ty(ty); - } - if let Some(ty) = &mut t.end { - self.update_ty(ty); - } - } - Type(t) => self.update_ty(t), - - // nothing to do for these as they're just names or empty - Flags(_) | Enum(_) | Future(None) => {} - } - } - - fn update_ty(&self, ty: &mut Type) { - if let Type::Id(id) = ty { - *id = self.type_map[id.index()].expect("should visit in topo-order"); - } - } - - fn update_interface(&self, iface: &mut Interface) { - for ty in iface.types.iter_mut() { - *ty = self.type_map[ty.index()].expect("types should all be visited"); - } - for func in iface.functions.iter_mut() { - for (_, ty) in func.params.iter_mut() { - self.update_ty(ty); - } - match &mut func.results { - Results::Named(named) => { - for (_, ty) in named.iter_mut() { - self.update_ty(ty); - } - } - Results::Anon(ty) => self.update_ty(ty), - } - } - } - - fn update_world(&self, world: &mut World) { - if let Some(i) = &mut world.default { - *i = self.iface_map[i.index()]; - } - for (_, i) in world.imports.iter_mut().chain(&mut world.exports) { - *i = self.iface_map[i.index()]; - } - } -} diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs new file mode 100644 index 0000000000..24b184154b --- /dev/null +++ b/crates/wit-parser/src/resolve.rs @@ -0,0 +1,629 @@ +use crate::ast::lex::Span; +use crate::{ + Document, DocumentId, Error, Interface, InterfaceId, Results, Type, TypeDef, TypeDefKind, + TypeId, TypeOwner, UnresolvedPackage, World, WorldId, WorldItem, +}; +use anyhow::{bail, Context, Result}; +use id_arena::{Arena, Id}; +use indexmap::{IndexMap, IndexSet}; +use std::collections::{HashMap, HashSet}; +use std::fmt::Write; +use std::mem; +use std::path::{Path, PathBuf}; + +/// Representation of a fully resolved set of WIT packages. +/// +/// This structure contains a graph of WIT packages and all of their contents +/// merged together into the contained arenas. All items are sorted +/// topologically and everything here is fully resolved, so with a `Resolve` no +/// name lookups are necessary and instead everything is index-based. +/// +/// Working with a WIT package requires inserting it into a `Resolve` to ensure +/// that all of its dependencies are satisfied. This will give the full picture +/// of that package's types and such. +/// +/// Each item in a `Resolve` has a parent link to trace it back to the original +/// package as necessary. +#[derive(Default, Clone)] +pub struct Resolve { + pub worlds: Arena, + pub interfaces: Arena, + pub types: Arena, + pub documents: Arena, + pub packages: Arena, +} + +#[derive(Clone)] +pub struct Package { + /// Documents contained within this package, organized by name. + pub documents: IndexMap, +} + +pub type PackageId = Id; + +impl Resolve { + /// Creates a new [`Resolve`] with no packages/items inside of it. + pub fn new() -> Resolve { + Resolve::default() + } + + /// Parses the filesystem directory at `path` as a WIT package and returns + /// the fully resolved [`PackageId`] as a result. + /// + /// This method is intended for testing and convenience for now and isn't + /// the only way to push packages into this [`Resolve`]. This will + /// interpret `path` as a directory where all `*.wit` files in that + /// directory are members of the package. + /// + /// Dependenies referenced by the WIT package at `path` will be loaded from + /// a `deps/$name` directory under `path` where `$name` is the name of the + /// dependency loaded. If `deps/$name` does not exist then an error will be + /// returned indicating that the dependency is not defined. All dependencies + /// are listed in a flat namespace under `$path/deps` so they can refer to + /// each other. + pub fn push_dir(&mut self, path: &Path) -> Result { + // Maintain a `to_parse` stack of packages that have yet to be parsed + // along with an `enqueued` set of all the prior parsed packages and + // packges enqueued to be parsed. These are then used to fill the + // `packages` map with parsed, but unresolved, packages. The `pkg_deps` + // map then tracks dependencies between packages. + let mut to_parse = Vec::new(); + let mut enqueued = HashSet::new(); + let mut packages = IndexMap::new(); + let mut pkg_deps = IndexMap::new(); + to_parse.push(path.to_path_buf()); + enqueued.insert(path.to_path_buf()); + while let Some(pkg_root) = to_parse.pop() { + let pkg = UnresolvedPackage::parse_dir(&pkg_root) + .with_context(|| format!("failed to parse package at {path:?}"))?; + + let mut deps = Vec::new(); + pkg.source_map.rewrite_error(|| { + for (i, (dep, _)) in pkg.foreign_deps.iter().enumerate() { + let path = path.join("deps").join(dep); + let span = pkg.foreign_dep_spans[i]; + // If this is the first request to parse `path` then push it + // onto our stack, otherwise it's already there so skip it. + if enqueued.insert(path.clone()) { + if !path.is_dir() { + bail!(Error { + span, + msg: format!("dependency on `{dep}` doesn't exist at {path:?}"), + }) + } + to_parse.push(path.clone()); + } + deps.push((path, span)); + } + Ok(()) + })?; + + let prev = packages.insert(pkg_root.clone(), pkg); + assert!(prev.is_none()); + pkg_deps.insert(pkg_root, deps); + } + + // Perform a simple topological sort which will bail out on cycles + // and otherwise determine the order that packages must be added to + // this `Resolve`. + let mut order = IndexSet::new(); + let mut visiting = HashSet::new(); + for (dep, _) in pkg_deps.iter() { + visit(dep, &pkg_deps, &packages, &mut order, &mut visiting)?; + } + + // Using the topological ordering insert each package incrementally. + // Additionally note that the last item visited here is the root + // package, which is the one returned here. + let mut package_ids = IndexMap::new(); + let mut last = None; + for path in order { + let pkg = packages.remove(path).unwrap(); + let mut deps = HashMap::new(); + for ((dep, _), (path, _span)) in pkg.foreign_deps.iter().zip(&pkg_deps[path]) { + deps.insert(dep.clone(), package_ids[&**path]); + } + let pkgid = self.push(pkg, &deps)?; + package_ids.insert(path, pkgid); + last = Some(pkgid); + } + + return Ok(last.unwrap()); + + fn visit<'a>( + path: &'a Path, + deps: &'a IndexMap>, + pkgs: &IndexMap, + order: &mut IndexSet<&'a Path>, + visiting: &mut HashSet<&'a Path>, + ) -> Result<()> { + if order.contains(path) { + return Ok(()); + } + pkgs[path].source_map.rewrite_error(|| { + for (dep, span) in deps[path].iter() { + if !visiting.insert(dep) { + bail!(Error { + span: *span, + msg: format!("package depends on itself"), + }); + } + visit(dep, deps, pkgs, order, visiting)?; + assert!(visiting.remove(&**dep)); + } + assert!(order.insert(path)); + Ok(()) + }) + } + } + + /// Appends a new [`UnresolvedPackage`] to this [`Resolve`], creating a + /// fully resolved package with no dangling references. + /// + /// The `deps` argument indicates that the named dependencies in + /// `unresolved` to packages are resolved by the mapping specified. + /// + /// Any dependency resolution error or otherwise world-elaboration error + /// will be returned here. If successful a package identifier is returned. + pub fn push( + &mut self, + mut unresolved: UnresolvedPackage, + deps: &HashMap, + ) -> Result { + let source_map = mem::take(&mut unresolved.source_map); + source_map.rewrite_error(|| Remap::default().append(self, unresolved, deps)) + } + + pub fn all_bits_valid(&self, ty: &Type) -> bool { + match ty { + Type::U8 + | Type::S8 + | Type::U16 + | Type::S16 + | Type::U32 + | Type::S32 + | Type::U64 + | Type::S64 + | Type::Float32 + | Type::Float64 => true, + + Type::Bool | Type::Char | Type::String => false, + + Type::Id(id) => match &self.types[*id].kind { + TypeDefKind::List(_) + | TypeDefKind::Variant(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Option(_) + | TypeDefKind::Result(_) + | TypeDefKind::Future(_) + | TypeDefKind::Stream(_) + | TypeDefKind::Union(_) => false, + TypeDefKind::Type(t) => self.all_bits_valid(t), + TypeDefKind::Record(r) => r.fields.iter().all(|f| self.all_bits_valid(&f.ty)), + TypeDefKind::Tuple(t) => t.types.iter().all(|t| self.all_bits_valid(t)), + + // FIXME: this could perhaps be `true` for multiples-of-32 but + // seems better to probably leave this as unconditionally + // `false` for now, may want to reconsider later? + TypeDefKind::Flags(_) => false, + + TypeDefKind::Unknown => unreachable!(), + }, + } + } +} + +#[derive(Default)] +struct Remap { + types: Vec, + interfaces: Vec, + documents: Vec, + worlds: Vec, +} + +impl Remap { + fn append( + &mut self, + resolve: &mut Resolve, + unresolved: UnresolvedPackage, + deps: &HashMap, + ) -> Result { + self.process_foreign_deps(resolve, &unresolved, deps)?; + + let foreign_types = self.types.len(); + let foreign_interfaces = self.interfaces.len(); + let foreign_documents = self.documents.len(); + let foreign_worlds = self.worlds.len(); + + // Copy over all types first, updating any intra-type references. Note + // that types are sorted topologically which means this iteration + // order should be sufficient. Also note though that the interface + // owner of a type isn't updated here due to interfaces not being known + // yet. + for (id, mut ty) in unresolved.types.into_iter().skip(foreign_types) { + self.update_typedef(&mut ty); + let new_id = resolve.types.alloc(ty); + assert_eq!(self.types.len(), id.index()); + self.types.push(new_id); + } + + // Next transfer all interfaces into `Resolve`, updating type ids + // referenced along the way. + for (id, mut iface) in unresolved.interfaces.into_iter().skip(foreign_interfaces) { + self.update_interface(&mut iface); + let new_id = resolve.interfaces.alloc(iface); + assert_eq!(self.interfaces.len(), id.index()); + self.interfaces.push(new_id); + } + + // Now that interfaces are identified go back through the types and + // update their interface owners. + for id in self.types.iter().skip(foreign_types) { + match &mut resolve.types[*id].owner { + TypeOwner::Interface(id) => *id = self.interfaces[id.index()], + TypeOwner::World(_) => unimplemented!(), + TypeOwner::None => {} + } + } + + // Perform a weighty step of full resolution of worlds. This will fully + // expand imports/exports for a world and create the topological + // ordering necessary for this. + // + // This is done after types/interfaces are fully settled so the + // transitive relation between interfaces, through types, is understood + // here. + assert_eq!(unresolved.worlds.len(), unresolved.world_spans.len()); + for ((id, mut world), (import_spans, export_spans)) in unresolved + .worlds + .into_iter() + .skip(foreign_worlds) + .zip(unresolved.world_spans) + { + self.update_world(&mut world, resolve, &import_spans, &export_spans)?; + let new_id = resolve.worlds.alloc(world); + assert_eq!(self.worlds.len(), id.index()); + self.worlds.push(new_id); + } + + // And the final major step is transferring documents to `Resolve` + // which is just updating a few identifiers here and there. + for (id, mut doc) in unresolved.documents.into_iter().skip(foreign_documents) { + self.update_document(&mut doc); + let new_id = resolve.documents.alloc(doc); + assert_eq!(self.documents.len(), id.index()); + self.documents.push(new_id); + } + + // Fixup "parent" ids now that everything has been identifier + for id in self.interfaces.iter().skip(foreign_interfaces) { + let doc = &mut resolve.interfaces[*id].document; + *doc = self.documents[doc.index()]; + } + for id in self.worlds.iter().skip(foreign_worlds) { + let doc = &mut resolve.worlds[*id].document; + *doc = self.documents[doc.index()]; + } + let mut documents = IndexMap::new(); + for id in self.documents.iter().skip(foreign_documents) { + let prev = documents.insert(resolve.documents[*id].name.clone(), *id); + assert!(prev.is_none()); + } + + Ok(resolve.packages.alloc(Package { documents })) + } + + fn process_foreign_deps( + &mut self, + resolve: &mut Resolve, + unresolved: &UnresolvedPackage, + deps: &HashMap, + ) -> Result<()> { + // First, connect all references to foreign documents to actual + // documents within `resolve`, building up the initial entries of + // the `self.documents` mapping. + for (i, (pkg, docs)) in unresolved.foreign_deps.iter().enumerate() { + let pkgid = *deps.get(pkg).ok_or_else(|| Error { + span: unresolved.foreign_dep_spans[i], + msg: format!("no package dependency specified for `{pkg}`"), + })?; + let package = &resolve.packages[pkgid]; + for (doc, unresolved_doc_id) in docs { + let span = unresolved.document_spans[unresolved_doc_id.index()]; + let docid = *package.documents.get(doc).ok_or_else(|| Error { + span, + msg: format!("package `{pkg}` does not define document `{doc}`"), + })?; + + assert_eq!(self.documents.len(), unresolved_doc_id.index()); + self.documents.push(docid); + } + } + + // Next, for all documents that are referenced in this `Resolve` + // determine the mapping of all interfaces that they refer to. + for (unresolved_iface_id, unresolved_iface) in unresolved.interfaces.iter() { + let doc_id = match self.documents.get(unresolved_iface.document.index()) { + Some(i) => *i, + // All foreign interfaces are defined first, so the first one + // which is defined in a non-foreign document means that all + // futher interfaces will be non-foreign as well. + None => break, + }; + + // Functions can't be imported so this should be empty. + assert!(unresolved_iface.functions.is_empty()); + + let document = &resolve.documents[doc_id]; + let span = unresolved.interface_spans[unresolved_iface_id.index()]; + let iface_id = match &unresolved_iface.name { + Some(name) => *document.interfaces.get(name).ok_or_else(|| Error { + span, + msg: format!("interface not defined in document"), + })?, + None => document.default_interface.ok_or_else(|| Error { + span, + msg: format!("default interface not specified in document"), + })?, + }; + assert_eq!(self.interfaces.len(), unresolved_iface_id.index()); + self.interfaces.push(iface_id); + } + + // And finally iterate over all foreign-defined types and determine + // what they map to. + for (unresolved_type_id, unresolved_ty) in unresolved.types.iter() { + // All "Unknown" types should appear first so once we're no longer + // in unknown territory it's package-defined types so break out of + // this loop. + match unresolved_ty.kind { + TypeDefKind::Unknown => {} + _ => break, + } + let unresolved_iface_id = match unresolved_ty.owner { + TypeOwner::Interface(id) => id, + _ => unreachable!(), + }; + let iface_id = self.interfaces[unresolved_iface_id.index()]; + let name = unresolved_ty.name.as_ref().unwrap(); + let span = unresolved.unknown_type_spans[unresolved_type_id.index()]; + let type_id = *resolve.interfaces[iface_id] + .types + .get(name) + .ok_or_else(|| Error { + span, + msg: format!("type not defined in interface"), + })?; + assert_eq!(self.types.len(), unresolved_type_id.index()); + self.types.push(type_id); + } + + Ok(()) + } + + fn update_typedef(&self, ty: &mut TypeDef) { + // NB: note that `ty.owner` is not updated here since interfaces + // haven't been mapped yet and that's done in a separate step. + use crate::TypeDefKind::*; + match &mut ty.kind { + Record(r) => { + for field in r.fields.iter_mut() { + self.update_ty(&mut field.ty); + } + } + Tuple(t) => { + for ty in t.types.iter_mut() { + self.update_ty(ty); + } + } + Variant(v) => { + for case in v.cases.iter_mut() { + if let Some(t) = &mut case.ty { + self.update_ty(t); + } + } + } + Option(t) => self.update_ty(t), + Result(r) => { + if let Some(ty) = &mut r.ok { + self.update_ty(ty); + } + if let Some(ty) = &mut r.err { + self.update_ty(ty); + } + } + Union(u) => { + for case in u.cases.iter_mut() { + self.update_ty(&mut case.ty); + } + } + List(t) => self.update_ty(t), + Future(Some(t)) => self.update_ty(t), + Stream(t) => { + if let Some(ty) = &mut t.element { + self.update_ty(ty); + } + if let Some(ty) = &mut t.end { + self.update_ty(ty); + } + } + Type(t) => self.update_ty(t), + + // nothing to do for these as they're just names or empty + Flags(_) | Enum(_) | Future(None) => {} + + Unknown => unreachable!(), + } + } + + fn update_ty(&self, ty: &mut Type) { + if let Type::Id(id) = ty { + *id = self.types[id.index()]; + } + } + + fn update_interface(&self, iface: &mut Interface) { + // NB: note that `iface.doc` is not updated here since interfaces + // haven't been mapped yet and that's done in a separate step. + for (_name, ty) in iface.types.iter_mut() { + *ty = self.types[ty.index()]; + } + for func in iface.functions.iter_mut() { + for (_, ty) in func.params.iter_mut() { + self.update_ty(ty); + } + match &mut func.results { + Results::Named(named) => { + for (_, ty) in named.iter_mut() { + self.update_ty(ty); + } + } + Results::Anon(ty) => self.update_ty(ty), + } + } + } + + fn update_world( + &self, + world: &mut World, + resolve: &Resolve, + import_spans: &[Span], + export_spans: &[Span], + ) -> Result<()> { + // NB: this function is more more complicated than the prior versions + // of merging an item because this is the location that elaboration of + // imports/exports of a world are fully resolved. With full transitive + // knowledge of all interfaces a worlds imports, for example, are + // expanded fully to ensure that all transitive items are necessarily + // imported. + assert_eq!(world.imports.len(), import_spans.len()); + assert_eq!(world.exports.len(), export_spans.len()); + + // First up, process all the `imports` of the world. Note that this + // starts by gutting the list of imports stored in `world` to get + // rebuilt iteratively below. + // + // Here each import of an interface is recorded and then additionally + // explicitly named imports of interfaces are recorded as well for + // determining names later on. + let mut explicit_import_names = HashMap::new(); + let mut imports = Vec::new(); + for ((name, item), span) in mem::take(&mut world.imports).into_iter().zip(import_spans) { + match item { + WorldItem::Interface(id) => { + let id = self.interfaces[id.index()]; + imports.push((id, *span)); + let prev = explicit_import_names.insert(id, name); + assert!(prev.is_none()); + } + WorldItem::Function(_) => unimplemented!(), + } + } + + // Next all imports and their transitive imports are processed. This + // is done through a `stack` of `Action` items which is processed in + // LIFO order, meaning that an action of processing the dependencies + // is pushed after processing the node itself. The dependency processing + // will push more items onto the stack as necessary. + // + // Throughout this an `imports_processed` set is maintained to ensure + // that multiple dependencies on the same interface won't push it twice + // to the import list as it's needed only once. + // + // Note that at this point all interfaces are known to be acyclic so + // there's no cycle detection here. + enum Action { + Import(InterfaceId, Span), + Deps(InterfaceId, Span), + } + + let mut stack = Vec::new(); + let mut resolving_stack = Vec::new(); + let mut imports_processed = HashSet::new(); + let name_of = |id: InterfaceId| { + explicit_import_names + .get(&id) + .unwrap_or_else(|| resolve.interfaces[id].name.as_ref().unwrap()) + }; + for (id, span) in imports { + if !imports_processed.insert(id) { + continue; + } + stack.push(Action::Import(id, span)); + stack.push(Action::Deps(id, span)); + + while let Some(action) = stack.pop() { + match action { + Action::Import(id, span) => { + let name = name_of(id); + let prev = world.imports.insert(name.clone(), WorldItem::Interface(id)); + if prev.is_none() { + assert_eq!(resolving_stack.pop(), Some(id)); + continue; + } + + if explicit_import_names.contains_key(&id) { + bail!(Error { + span, + msg: format!( + "import name `{name}` conflicts with implicitly imported interface" + ), + }) + } + + let mut msg = format!("import of `{}`\n", name_of(resolving_stack[0])); + for i in resolving_stack.iter().skip(1) { + writeln!(msg, " .. which depends on `{}`", name_of(*i)).unwrap(); + } + writeln!( + msg, + "conflicts with a previously imported interface \ + using the name `{name}`", + ) + .unwrap(); + bail!(Error { span, msg }) + } + Action::Deps(id, span) => { + resolving_stack.push(id); + for (_, ty) in resolve.interfaces[id].types.iter() { + let ty = match resolve.types[*ty].kind { + TypeDefKind::Type(Type::Id(id)) => id, + _ => continue, + }; + match resolve.types[ty].owner { + TypeOwner::None => {} + TypeOwner::Interface(other) => { + if !imports_processed.insert(other) { + continue; + } + stack.push(Action::Import(other, span)); + stack.push(Action::Deps(other, span)); + } + TypeOwner::World(_) => unreachable!(), + } + } + } + } + } + } + + log::trace!("{:?}", world.imports); + + Ok(()) + } + + fn update_document(&self, doc: &mut Document) { + for (_name, iface) in doc.interfaces.iter_mut() { + *iface = self.interfaces[iface.index()]; + } + for world in doc.worlds.iter_mut() { + *world = self.worlds[world.index()]; + } + if let Some(default) = &mut doc.default_interface { + *default = self.interfaces[default.index()]; + } + if let Some(default) = &mut doc.default_world { + *default = self.worlds[default.index()]; + } + } +} diff --git a/crates/wit-parser/src/sizealign.rs b/crates/wit-parser/src/sizealign.rs index a06600ea7d..c1aac71de5 100644 --- a/crates/wit-parser/src/sizealign.rs +++ b/crates/wit-parser/src/sizealign.rs @@ -1,4 +1,4 @@ -use crate::{Document, FlagsRepr, Int, Type, TypeDef, TypeDefKind}; +use crate::{FlagsRepr, Int, Resolve, Type, TypeDef, TypeDefKind}; #[derive(Default)] pub struct SizeAlign { @@ -6,11 +6,11 @@ pub struct SizeAlign { } impl SizeAlign { - pub fn fill(&mut self, doc: &Document) { - self.map = vec![(0, 0); doc.types.len()]; - for ty in doc.topological_types() { - let pair = self.calculate(&doc.types[ty]); - self.map[ty.index()] = pair; + pub fn fill(&mut self, resolve: &Resolve) { + self.map = Vec::new(); + for (_, ty) in resolve.types.iter() { + let pair = self.calculate(ty); + self.map.push(pair); } } @@ -34,6 +34,7 @@ impl SizeAlign { TypeDefKind::Future(_) => (4, 4), // A stream is represented as an index. TypeDefKind::Stream(_) => (4, 4), + TypeDefKind::Unknown => unreachable!(), } } diff --git a/crates/wit-parser/tests/all.rs b/crates/wit-parser/tests/all.rs index bc31faeb65..173624b483 100644 --- a/crates/wit-parser/tests/all.rs +++ b/crates/wit-parser/tests/all.rs @@ -8,8 +8,8 @@ //! cargo test --test all foo.wit use anyhow::{bail, Context, Result}; +use pretty_assertions::StrComparison; use rayon::prelude::*; -use serde::Serialize; use std::env; use std::ffi::OsStr; use std::fs; @@ -20,6 +20,7 @@ use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; use wit_parser::*; fn main() { + env_logger::init(); let tests = find_tests(); let filter = std::env::args().nth(1); @@ -33,8 +34,7 @@ fn main() { } } } - let contents = fs::read(test).unwrap(); - Some((test, contents)) + Some(test) }) .collect::>(); @@ -43,9 +43,9 @@ fn main() { let ntests = AtomicUsize::new(0); let errors = tests .par_iter() - .filter_map(|(test, contents)| { + .filter_map(|test| { Runner { ntests: &ntests } - .run(test, contents) + .run(test) .context(format!("test {:?} failed", test)) .err() }) @@ -70,23 +70,28 @@ fn main() { fn find_tests() -> Vec { let mut tests = Vec::new(); find_tests("tests/ui".as_ref(), &mut tests); + find_tests("tests/ui/parse-fail".as_ref(), &mut tests); tests.sort(); return tests; fn find_tests(path: &Path, tests: &mut Vec) { for f in path.read_dir().unwrap() { let f = f.unwrap(); + let path = f.path(); + if path.file_name().unwrap().to_str().unwrap() == "parse-fail" { + continue; + } if f.file_type().unwrap().is_dir() { - find_tests(&f.path(), tests); + tests.push(path); continue; } - match f.path().extension().and_then(|s| s.to_str()) { + match path.extension().and_then(|s| s.to_str()) { Some("md") => {} Some("wit") => {} _ => continue, } - tests.push(f.path()); + tests.push(path); } } } @@ -96,12 +101,15 @@ struct Runner<'a> { } impl Runner<'_> { - fn run(&mut self, test: &Path, contents: &[u8]) -> Result<()> { - let contents = str::from_utf8(contents)?; - - let result = Document::parse(test, contents); + fn run(&mut self, test: &Path) -> Result<()> { + let mut resolve = Resolve::new(); + let result = if test.is_dir() { + resolve.push_dir(test) + } else { + UnresolvedPackage::parse_file(test).and_then(|p| resolve.push(p, &Default::default())) + }; - let result = if contents.contains("// parse-fail") { + let result = if test.iter().any(|s| s == "parse-fail") { match result { Ok(_) => bail!("expected test to not parse but it did"), Err(mut e) => { @@ -115,9 +123,8 @@ impl Runner<'_> { } } } else { - let document = result?; - test_document(&document); - to_json(&document) + result?; + return Ok(()); }; // "foo.wit" => "foo.wit.result" @@ -142,9 +149,8 @@ impl Runner<'_> { let expected = normalize(test, &expected); if expected != result { bail!( - "failed test: expected `{:?}` but found `{:?}`", - expected, - result + "failed test: result is not as expected:{}", + StrComparison::new(&expected, &result), ); } } @@ -165,235 +171,3 @@ impl Runner<'_> { self.ntests.fetch_add(1, SeqCst); } } - -fn to_json(doc: &Document) -> String { - #[derive(Serialize)] - struct Document { - #[serde(skip_serializing_if = "Vec::is_empty")] - worlds: Vec, - #[serde(skip_serializing_if = "Vec::is_empty")] - interfaces: Vec, - #[serde(skip_serializing_if = "Vec::is_empty")] - types: Vec, - } - - #[derive(Serialize)] - struct World { - name: String, - #[serde(skip_serializing_if = "Option::is_none")] - default: Option, - #[serde(skip_serializing_if = "Vec::is_empty")] - imports: Vec<(String, String)>, - #[serde(skip_serializing_if = "Vec::is_empty")] - exports: Vec<(String, String)>, - } - - #[derive(Serialize)] - struct Interface { - name: String, - #[serde(skip_serializing_if = "Vec::is_empty")] - types: Vec, - #[serde(skip_serializing_if = "Vec::is_empty")] - functions: Vec, - } - - #[derive(Serialize)] - struct Resource { - name: String, - #[serde(skip_serializing_if = "Option::is_none")] - supertype: Option, - #[serde(skip_serializing_if = "Option::is_none")] - foreign_module: Option, - } - - #[derive(Serialize)] - struct TypeDef { - idx: usize, - #[serde(skip_serializing_if = "Option::is_none")] - name: Option, - #[serde(flatten)] - ty: Type, - #[serde(skip_serializing_if = "Option::is_none")] - foreign_module: Option, - } - - #[derive(Serialize)] - #[serde(rename_all = "kebab-case")] - enum Type { - Primitive(String), - Record { - fields: Vec<(String, String)>, - }, - Flags { - flags: Vec, - }, - Enum { - cases: Vec, - }, - Variant { - cases: Vec<(String, Option)>, - }, - Tuple { - types: Vec, - }, - Option(String), - Result { - ok: Option, - err: Option, - }, - Future(Option), - Stream { - element: Option, - end: Option, - }, - List(String), - Union { - cases: Vec, - }, - } - - #[derive(Serialize)] - struct Function { - name: String, - params: Vec, - results: Vec, - } - - #[derive(Serialize)] - struct Global { - name: String, - ty: String, - } - - let document = Document { - worlds: doc - .worlds - .iter() - .map(|(_, world)| translate_world(world)) - .collect(), - interfaces: doc - .interfaces - .iter() - .map(|(_, interface)| translate_interface(interface)) - .collect(), - types: doc - .types - .iter() - .map(|(_, ty)| translate_typedef(ty)) - .collect(), - }; - - return serde_json::to_string_pretty(&document).unwrap(); - - fn translate_world(w: &wit_parser::World) -> World { - World { - name: w.name.clone(), - default: w.default.map(|i| format!("i{}", i.index())), - imports: w - .imports - .iter() - .map(|(name, iface)| (name.clone(), format!("i{}", iface.index()))) - .collect(), - exports: w - .exports - .iter() - .map(|(name, iface)| (name.clone(), format!("i{}", iface.index()))) - .collect(), - } - } - - fn translate_interface(i: &wit_parser::Interface) -> Interface { - let types = i - .types - .iter() - .map(|i| format!("type-{}", i.index())) - .collect::>(); - let functions = i - .functions - .iter() - .map(|f| Function { - name: f.name.clone(), - params: f.params.iter().map(|(_, ty)| translate_type(ty)).collect(), - results: f.results.iter_types().map(translate_type).collect(), - }) - .collect::>(); - - Interface { - name: i.name.clone(), - types, - functions, - } - } - - fn translate_typedef(ty: &wit_parser::TypeDef) -> Type { - match &ty.kind { - TypeDefKind::Type(t) => Type::Primitive(translate_type(t)), - TypeDefKind::Record(r) => Type::Record { - fields: r - .fields - .iter() - .map(|f| (f.name.clone(), translate_type(&f.ty))) - .collect(), - }, - TypeDefKind::Tuple(t) => Type::Tuple { - types: t.types.iter().map(translate_type).collect(), - }, - TypeDefKind::Flags(r) => Type::Flags { - flags: r.flags.iter().map(|f| f.name.clone()).collect(), - }, - TypeDefKind::Enum(r) => Type::Enum { - cases: r.cases.iter().map(|f| f.name.clone()).collect(), - }, - TypeDefKind::Variant(v) => Type::Variant { - cases: v - .cases - .iter() - .map(|f| (f.name.clone(), translate_optional_type(f.ty.as_ref()))) - .collect(), - }, - TypeDefKind::Option(t) => Type::Option(translate_type(t)), - TypeDefKind::Result(r) => Type::Result { - ok: translate_optional_type(r.ok.as_ref()), - err: translate_optional_type(r.err.as_ref()), - }, - TypeDefKind::Future(t) => Type::Future(translate_optional_type(t.as_ref())), - TypeDefKind::Stream(s) => Type::Stream { - element: translate_optional_type(s.element.as_ref()), - end: translate_optional_type(s.end.as_ref()), - }, - TypeDefKind::List(ty) => Type::List(translate_type(ty)), - TypeDefKind::Union(u) => Type::Union { - cases: u.cases.iter().map(|c| translate_type(&c.ty)).collect(), - }, - } - } - - fn translate_type(ty: &wit_parser::Type) -> String { - use wit_parser::Type; - match ty { - Type::Bool => "bool".to_string(), - Type::U8 => "u8".to_string(), - Type::U16 => "u16".to_string(), - Type::U32 => "u32".to_string(), - Type::U64 => "u64".to_string(), - Type::S8 => "s8".to_string(), - Type::S16 => "s16".to_string(), - Type::S32 => "s32".to_string(), - Type::S64 => "s64".to_string(), - Type::Float32 => "float32".to_string(), - Type::Float64 => "float64".to_string(), - Type::Char => "char".to_string(), - Type::String => "string".to_string(), - Type::Id(id) => format!("type-{}", id.index()), - } - } - - fn translate_optional_type(ty: Option<&wit_parser::Type>) -> Option { - ty.map(translate_type) - } -} - -fn test_document(document: &Document) { - let mut sizes = SizeAlign::default(); - sizes.fill(document); -} diff --git a/crates/wit-parser/tests/ui/comments.wit.result b/crates/wit-parser/tests/ui/comments.wit.result deleted file mode 100644 index 7548639a38..0000000000 --- a/crates/wit-parser/tests/ui/comments.wit.result +++ /dev/null @@ -1,22 +0,0 @@ -{ - "interfaces": [ - { - "name": "foo", - "types": [ - "type-0", - "type-1" - ] - } - ], - "types": [ - { - "primitive": "u32" - }, - { - "stream": { - "element": "type-0", - "end": null - } - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/disambiguate-diamond/shared1.wit b/crates/wit-parser/tests/ui/disambiguate-diamond/shared1.wit new file mode 100644 index 0000000000..e65165ac2d --- /dev/null +++ b/crates/wit-parser/tests/ui/disambiguate-diamond/shared1.wit @@ -0,0 +1,3 @@ +default interface shared { + type the-type = u32 +} diff --git a/crates/wit-parser/tests/ui/disambiguate-diamond/shared2.wit b/crates/wit-parser/tests/ui/disambiguate-diamond/shared2.wit new file mode 100644 index 0000000000..e65165ac2d --- /dev/null +++ b/crates/wit-parser/tests/ui/disambiguate-diamond/shared2.wit @@ -0,0 +1,3 @@ +default interface shared { + type the-type = u32 +} diff --git a/crates/wit-parser/tests/ui/disambiguate-diamond/world.wit b/crates/wit-parser/tests/ui/disambiguate-diamond/world.wit new file mode 100644 index 0000000000..80ab9be490 --- /dev/null +++ b/crates/wit-parser/tests/ui/disambiguate-diamond/world.wit @@ -0,0 +1,11 @@ +world foo { + import foo: interface { + use pkg.shared1.{the-type} + } + import bar: interface { + use pkg.shared2.{the-type} + } + + import shared1: pkg.shared1 + import shared2: pkg.shared2 +} diff --git a/crates/wit-parser/tests/ui/embedded.wit.md.result b/crates/wit-parser/tests/ui/embedded.wit.md.result deleted file mode 100644 index 69871db16d..0000000000 --- a/crates/wit-parser/tests/ui/embedded.wit.md.result +++ /dev/null @@ -1,24 +0,0 @@ -{ - "interfaces": [ - { - "name": "foo", - "functions": [ - { - "name": "x", - "params": [], - "results": [] - }, - { - "name": "y", - "params": [], - "results": [] - }, - { - "name": "z", - "params": [], - "results": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/empty.wit.result b/crates/wit-parser/tests/ui/empty.wit.result deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/crates/wit-parser/tests/ui/empty.wit.result +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/another-pkg/other-doc.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/another-pkg/other-doc.wit new file mode 100644 index 0000000000..04933294d2 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/another-pkg/other-doc.wit @@ -0,0 +1 @@ +interface other-interface {} diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/different-pkg/the-doc.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/different-pkg/the-doc.wit new file mode 100644 index 0000000000..f192837fb6 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/different-pkg/the-doc.wit @@ -0,0 +1 @@ +default interface i {} diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/fastly/compute-at-edge.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/fastly/compute-at-edge.wit new file mode 100644 index 0000000000..7716e06eef --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/fastly/compute-at-edge.wit @@ -0,0 +1,2 @@ +default interface compute-at-edge { +} diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/foreign-pkg/the-doc.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/foreign-pkg/the-doc.wit new file mode 100644 index 0000000000..1fb9bad8de --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/foreign-pkg/the-doc.wit @@ -0,0 +1,3 @@ +default interface the-default { + type some-type = u32 +} diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/some-pkg/some-doc.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/some-pkg/some-doc.wit new file mode 100644 index 0000000000..8228c1d6f0 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/some-pkg/some-doc.wit @@ -0,0 +1,11 @@ +default interface the-default { + type from-default = string +} + +interface some-interface { + type another-type = u32 +} + +interface another-interface { + type yet-another-type = u8 +} diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/wasi/clocks.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/wasi/clocks.wit new file mode 100644 index 0000000000..18a3575649 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/wasi/clocks.wit @@ -0,0 +1,3 @@ +default interface wasi-clocks { + type timestamp = u64 +} diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/wasi/filesystem.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/wasi/filesystem.wit new file mode 100644 index 0000000000..b0a97a6cda --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/wasi/filesystem.wit @@ -0,0 +1,5 @@ +default interface wasi-filesystem { + record stat { + ino: u64 + } +} diff --git a/crates/wit-parser/tests/ui/foreign-deps/root.wit b/crates/wit-parser/tests/ui/foreign-deps/root.wit new file mode 100644 index 0000000000..79ef1c8a17 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/root.wit @@ -0,0 +1,31 @@ +interface foo { + use wasi.clocks.{timestamp} + use wasi.filesystem.{stat} +} + +world my-world { + import wasi-fs: wasi.filesystem + import wasi-clocks: wasi.clocks + + export fastly: fastly.compute-at-edge +} + +interface bar { + use some-pkg.some-doc.{from-default} + use some-pkg.some-doc.some-interface.{another-type} + use some-pkg.some-doc.some-interface.{} + use some-pkg.some-doc.another-interface.{yet-another-type} + use different-pkg.the-doc.{} +} + +world bars-world { + import foo: some-pkg.some-doc + import bar: another-pkg.other-doc.other-interface +} + +interface use1 { + use foreign-pkg.the-doc.{some-type} +} +interface use2 { + use foreign-pkg.the-doc.{some-type} +} diff --git a/crates/wit-parser/tests/ui/functions.wit.result b/crates/wit-parser/tests/ui/functions.wit.result deleted file mode 100644 index 341cbaa9ba..0000000000 --- a/crates/wit-parser/tests/ui/functions.wit.result +++ /dev/null @@ -1,100 +0,0 @@ -{ - "interfaces": [ - { - "name": "functions", - "functions": [ - { - "name": "f1", - "params": [], - "results": [] - }, - { - "name": "f2", - "params": [ - "u32" - ], - "results": [] - }, - { - "name": "f3", - "params": [ - "u32" - ], - "results": [] - }, - { - "name": "f4", - "params": [], - "results": [ - "u32" - ] - }, - { - "name": "f6", - "params": [], - "results": [ - "type-0" - ] - }, - { - "name": "f7", - "params": [ - "float32", - "float32" - ], - "results": [ - "type-0" - ] - }, - { - "name": "f8", - "params": [ - "type-1" - ], - "results": [ - "type-2" - ] - }, - { - "name": "f9", - "params": [], - "results": [ - "u32", - "float32" - ] - }, - { - "name": "f10", - "params": [], - "results": [ - "u32" - ] - }, - { - "name": "f11", - "params": [], - "results": [] - } - ] - } - ], - "types": [ - { - "tuple": { - "types": [ - "u32", - "u32" - ] - } - }, - { - "option": "u32" - }, - { - "result": { - "ok": "u32", - "err": "float32" - } - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/multi-file/bar.wit b/crates/wit-parser/tests/ui/multi-file/bar.wit new file mode 100644 index 0000000000..9946d4ba33 --- /dev/null +++ b/crates/wit-parser/tests/ui/multi-file/bar.wit @@ -0,0 +1,19 @@ +default interface irrelevant-name { + record a-name {} +} + +interface depends-on-later-item { + use self.depend-on-me.{x} +} + +interface depend-on-me { + type x = u32 +} + +world more-depends-on-later-things { + import foo: self.later-interface + export bar: self.later-interface +} + +interface later-interface { +} diff --git a/crates/wit-parser/tests/ui/multi-file/foo.wit b/crates/wit-parser/tests/ui/multi-file/foo.wit new file mode 100644 index 0000000000..cf9e621b29 --- /dev/null +++ b/crates/wit-parser/tests/ui/multi-file/foo.wit @@ -0,0 +1,15 @@ +interface foo { + type x = u32 +} + +default interface something-else { + type y = u64 +} + +interface bar { + use self.foo.{x} + use pkg.foo.foo.{x as x2} + use pkg.foo.{y} + use self.something-else.{y as y2} + use pkg.bar.{a-name} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/alias-no-type.wit.result b/crates/wit-parser/tests/ui/parse-fail/alias-no-type.wit.result index fcbdb7de60..6b9c20d947 100644 --- a/crates/wit-parser/tests/ui/parse-fail/alias-no-type.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/alias-no-type.wit.result @@ -1,4 +1,4 @@ -no type named `bar` +type `bar` does not exist --> tests/ui/parse-fail/alias-no-type.wit:3:14 | 3 | type foo = bar diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-diamond.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-diamond.wit.result new file mode 100644 index 0000000000..9e25b154f4 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-diamond.wit.result @@ -0,0 +1,8 @@ +import of `b` + .. which depends on `shared` +conflicts with a previously imported interface using the name `shared` + + --> tests/ui/parse-fail/bad-diamond/join.wit:3:10 + | + 3 | import b: pkg.b + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-diamond/a.wit b/crates/wit-parser/tests/ui/parse-fail/bad-diamond/a.wit new file mode 100644 index 0000000000..0c33f97761 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-diamond/a.wit @@ -0,0 +1,9 @@ +interface shared { + type foo = u32 +} + +default interface a { + use self.shared.{foo} + + a: func() -> foo +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-diamond/b.wit b/crates/wit-parser/tests/ui/parse-fail/bad-diamond/b.wit new file mode 100644 index 0000000000..3aeda1af16 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-diamond/b.wit @@ -0,0 +1,9 @@ +interface shared { + type foo = u32 +} + +default interface b { + use self.shared.{foo} + + a: func() -> foo +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-diamond/join.wit b/crates/wit-parser/tests/ui/parse-fail/bad-diamond/join.wit new file mode 100644 index 0000000000..289cf6df5a --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-diamond/join.wit @@ -0,0 +1,4 @@ +world foo { + import a: pkg.a + import b: pkg.b +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-function.wit b/crates/wit-parser/tests/ui/parse-fail/bad-function.wit new file mode 100644 index 0000000000..202abb5a72 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-function.wit @@ -0,0 +1,5 @@ +// parse-fail + +interface foo { + x: func(param: nonexistent) +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result new file mode 100644 index 0000000000..756df8495b --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result @@ -0,0 +1,5 @@ +name is not defined + --> tests/ui/parse-fail/bad-function.wit:4:18 + | + 4 | x: func(param: nonexistent) + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit b/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit new file mode 100644 index 0000000000..c80e3e670f --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit @@ -0,0 +1,5 @@ +// parse-fail + +interface foo { + x: func() -> nonexistent +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result new file mode 100644 index 0000000000..1e4617af52 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result @@ -0,0 +1,5 @@ +name is not defined + --> tests/ui/parse-fail/bad-function2.wit:4:16 + | + 4 | x: func() -> nonexistent + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result new file mode 100644 index 0000000000..7b01397158 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result @@ -0,0 +1,5 @@ +dependency on `nonexistent` doesn't exist at "tests/ui/parse-fail/bad-pkg1/deps/nonexistent" + --> tests/ui/parse-fail/bad-pkg1/root.wit:2:7 + | + 2 | use nonexistent.foo.{} + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg1/root.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1/root.wit new file mode 100644 index 0000000000..7b0d8a0ff8 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1/root.wit @@ -0,0 +1,3 @@ +interface foo { + use nonexistent.foo.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg2.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg2.wit.result new file mode 100644 index 0000000000..9cd96bd756 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg2.wit.result @@ -0,0 +1,5 @@ +package `bar` does not define document `nonexistent` + --> tests/ui/parse-fail/bad-pkg2/root.wit:2:11 + | + 2 | use bar.nonexistent.{} + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg2/deps/bar/.gitkeep b/crates/wit-parser/tests/ui/parse-fail/bad-pkg2/deps/bar/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg2/root.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg2/root.wit new file mode 100644 index 0000000000..eee9664002 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg2/root.wit @@ -0,0 +1,3 @@ +interface foo { + use bar.nonexistent.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg3.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg3.wit.result new file mode 100644 index 0000000000..432a7dffeb --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg3.wit.result @@ -0,0 +1,5 @@ +default interface not specified in document + --> tests/ui/parse-fail/bad-pkg3/root.wit:2:11 + | + 2 | use bar.baz.{} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg3/deps/bar/.gitkeep b/crates/wit-parser/tests/ui/parse-fail/bad-pkg3/deps/bar/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg3/deps/bar/baz.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg3/deps/bar/baz.wit new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg3/root.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg3/root.wit new file mode 100644 index 0000000000..5bc31123f5 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg3/root.wit @@ -0,0 +1,3 @@ +interface foo { + use bar.baz.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg4.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4.wit.result new file mode 100644 index 0000000000..cdf2fba8c3 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4.wit.result @@ -0,0 +1,5 @@ +type not defined in interface + --> tests/ui/parse-fail/bad-pkg4/root.wit:2:16 + | + 2 | use bar.baz.{a-name} + | ^----- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/deps/bar/.gitkeep b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/deps/bar/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/deps/bar/baz.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/deps/bar/baz.wit new file mode 100644 index 0000000000..baa2185796 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/deps/bar/baz.wit @@ -0,0 +1,3 @@ +default interface irrelevant-name { + a-name: func() +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/root.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/root.wit new file mode 100644 index 0000000000..f1af5052ee --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4/root.wit @@ -0,0 +1,3 @@ +interface foo { + use bar.baz.{a-name} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg5.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5.wit.result new file mode 100644 index 0000000000..da07d92785 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5.wit.result @@ -0,0 +1,5 @@ +type not defined in interface + --> tests/ui/parse-fail/bad-pkg5/root.wit:2:16 + | + 2 | use bar.baz.{nonexistent} + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/deps/bar/.gitkeep b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/deps/bar/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/deps/bar/baz.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/deps/bar/baz.wit new file mode 100644 index 0000000000..65ffc3ac20 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/deps/bar/baz.wit @@ -0,0 +1,2 @@ +default interface irrelevant-name { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/root.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/root.wit new file mode 100644 index 0000000000..0f19c0cce9 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5/root.wit @@ -0,0 +1,3 @@ +interface foo { + use bar.baz.{nonexistent} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result new file mode 100644 index 0000000000..91c24db610 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result @@ -0,0 +1,5 @@ +interface not defined in document + --> tests/ui/parse-fail/bad-pkg6/root.wit:2:15 + | + 2 | use bar.baz.nonexistent.{} + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg6/deps/bar/.gitkeep b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6/deps/bar/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg6/deps/bar/baz.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6/deps/bar/baz.wit new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg6/root.wit b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6/root.wit new file mode 100644 index 0000000000..7c244a4d42 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6/root.wit @@ -0,0 +1,3 @@ +interface foo { + use bar.baz.nonexistent.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/cycle.wit.result b/crates/wit-parser/tests/ui/parse-fail/cycle.wit.result index 7c273d7b1e..fbb1a6457a 100644 --- a/crates/wit-parser/tests/ui/parse-fail/cycle.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/cycle.wit.result @@ -1,5 +1,5 @@ -type can recursively refer to itself - --> tests/ui/parse-fail/cycle.wit:4:8 +type `foo` depends on itself + --> tests/ui/parse-fail/cycle.wit:4:14 | 4 | type foo = foo - | ^-- \ No newline at end of file + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/cycle2.wit.result b/crates/wit-parser/tests/ui/parse-fail/cycle2.wit.result index 5852e59ac0..946175ab6b 100644 --- a/crates/wit-parser/tests/ui/parse-fail/cycle2.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/cycle2.wit.result @@ -1,5 +1,5 @@ -type can recursively refer to itself - --> tests/ui/parse-fail/cycle2.wit:4:8 +type `bar` depends on itself + --> tests/ui/parse-fail/cycle2.wit:4:14 | 4 | type foo = bar - | ^-- \ No newline at end of file + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/cycle3.wit.result b/crates/wit-parser/tests/ui/parse-fail/cycle3.wit.result index 308f29b091..facc6386e7 100644 --- a/crates/wit-parser/tests/ui/parse-fail/cycle3.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/cycle3.wit.result @@ -1,5 +1,5 @@ -type can recursively refer to itself - --> tests/ui/parse-fail/cycle3.wit:4:8 +type `bar` depends on itself + --> tests/ui/parse-fail/cycle3.wit:4:14 | 4 | type foo = bar - | ^-- \ No newline at end of file + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/cycle4.wit.result b/crates/wit-parser/tests/ui/parse-fail/cycle4.wit.result index 8ae0b3745c..45e9c17a0f 100644 --- a/crates/wit-parser/tests/ui/parse-fail/cycle4.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/cycle4.wit.result @@ -1,5 +1,5 @@ -type can recursively refer to itself - --> tests/ui/parse-fail/cycle4.wit:4:8 +type `bar` depends on itself + --> tests/ui/parse-fail/cycle4.wit:4:14 | 4 | type foo = bar - | ^-- \ No newline at end of file + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/cycle5.wit.result b/crates/wit-parser/tests/ui/parse-fail/cycle5.wit.result index 077957e5e3..0fd4f5c82b 100644 --- a/crates/wit-parser/tests/ui/parse-fail/cycle5.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/cycle5.wit.result @@ -1,5 +1,5 @@ -type can recursively refer to itself - --> tests/ui/parse-fail/cycle5.wit:4:8 +type `bar` depends on itself + --> tests/ui/parse-fail/cycle5.wit:4:14 | 4 | type foo = bar - | ^-- \ No newline at end of file + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/default-interface1.wit b/crates/wit-parser/tests/ui/parse-fail/default-interface1.wit new file mode 100644 index 0000000000..c6c4238802 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/default-interface1.wit @@ -0,0 +1,3 @@ +// parse-fail +default interface foo {} +default interface bar {} diff --git a/crates/wit-parser/tests/ui/parse-fail/default-interface1.wit.result b/crates/wit-parser/tests/ui/parse-fail/default-interface1.wit.result new file mode 100644 index 0000000000..a98ae7d335 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/default-interface1.wit.result @@ -0,0 +1,5 @@ +cannot specify more than one `default` interface + --> tests/ui/parse-fail/default-interface1.wit:3:19 + | + 3 | default interface bar {} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/default-world1.wit b/crates/wit-parser/tests/ui/parse-fail/default-world1.wit new file mode 100644 index 0000000000..df2d36e299 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/default-world1.wit @@ -0,0 +1,3 @@ +// parse-fail +default world foo {} +default world bar {} diff --git a/crates/wit-parser/tests/ui/parse-fail/default-world1.wit.result b/crates/wit-parser/tests/ui/parse-fail/default-world1.wit.result new file mode 100644 index 0000000000..e46b2538f2 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/default-world1.wit.result @@ -0,0 +1,5 @@ +cannot specify more than one `default` world + --> tests/ui/parse-fail/default-world1.wit:3:15 + | + 3 | default world bar {} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/duplicate-functions.wit.result b/crates/wit-parser/tests/ui/parse-fail/duplicate-functions.wit.result index 4b31f5dd84..c0ab243fa6 100644 --- a/crates/wit-parser/tests/ui/parse-fail/duplicate-functions.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/duplicate-functions.wit.result @@ -1,4 +1,4 @@ -`foo` is defined more than once +name `foo` is defined more than once --> tests/ui/parse-fail/duplicate-functions.wit:5:3 | 5 | foo: func() diff --git a/crates/wit-parser/tests/ui/parse-fail/duplicate-interface.wit.result b/crates/wit-parser/tests/ui/parse-fail/duplicate-interface.wit.result index c51063819e..293005fe64 100644 --- a/crates/wit-parser/tests/ui/parse-fail/duplicate-interface.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/duplicate-interface.wit.result @@ -1,4 +1,4 @@ -interface `foo` is defined more than once +name `foo` previously defined in document --> tests/ui/parse-fail/duplicate-interface.wit:4:11 | 4 | interface foo {} diff --git a/crates/wit-parser/tests/ui/parse-fail/duplicate-type.wit.result b/crates/wit-parser/tests/ui/parse-fail/duplicate-type.wit.result index 3b966ae586..9efd0d2c4f 100644 --- a/crates/wit-parser/tests/ui/parse-fail/duplicate-type.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/duplicate-type.wit.result @@ -1,4 +1,4 @@ -type `foo` is defined more than once +name `foo` is defined more than once --> tests/ui/parse-fail/duplicate-type.wit:5:8 | 5 | type foo = s32 diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference.wit b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference.wit new file mode 100644 index 0000000000..da9286726d --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference.wit @@ -0,0 +1,8 @@ +interface foo { + x: func() + + record foo { + // TODO: this should have a better error message referring to `x: func()` + a: x + } +} diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference.wit.result b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference.wit.result new file mode 100644 index 0000000000..1c872a1c05 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference.wit.result @@ -0,0 +1,5 @@ +type `x` does not exist + --> tests/ui/parse-fail/invalid-type-reference.wit:6:8 + | + 6 | a: x + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit new file mode 100644 index 0000000000..c2f7393c61 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit @@ -0,0 +1,4 @@ +interface foo { + x: func() + y: func() -> x +} diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit.result b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit.result new file mode 100644 index 0000000000..22f7a8f361 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit.result @@ -0,0 +1,5 @@ +cannot use a function as a type + --> tests/ui/parse-fail/invalid-type-reference2.wit:3:16 + | + 3 | y: func() -> x + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit b/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result b/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result new file mode 100644 index 0000000000..d634a8fa6e --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result @@ -0,0 +1 @@ +filename was not a valid WIT identifier for "tests/ui/parse-fail/invalid@filename.wit": invalid character in identifier '@' \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle.wit.result b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle.wit.result new file mode 100644 index 0000000000..d7fbbe1104 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle.wit.result @@ -0,0 +1,5 @@ +package depends on itself + --> tests/ui/parse-fail/pkg-cycle/deps/a1/root.wit:2:7 + | + 2 | use a1.foo.{} + | ^- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle/deps/a1/root.wit b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle/deps/a1/root.wit new file mode 100644 index 0000000000..9a23dbfd35 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle/deps/a1/root.wit @@ -0,0 +1,3 @@ +interface foo { + use a1.foo.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle/root.wit b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle/root.wit new file mode 100644 index 0000000000..9a23dbfd35 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle/root.wit @@ -0,0 +1,3 @@ +interface foo { + use a1.foo.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2.wit.result b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2.wit.result new file mode 100644 index 0000000000..b7ccf5a080 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2.wit.result @@ -0,0 +1,5 @@ +package depends on itself + --> tests/ui/parse-fail/pkg-cycle2/deps/a2/root.wit:2:7 + | + 2 | use a1.foo.{} + | ^- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/deps/a1/root.wit b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/deps/a1/root.wit new file mode 100644 index 0000000000..9e8ae2bbfd --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/deps/a1/root.wit @@ -0,0 +1,3 @@ +interface foo { + use a2.foo.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/deps/a2/root.wit b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/deps/a2/root.wit new file mode 100644 index 0000000000..9a23dbfd35 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/deps/a2/root.wit @@ -0,0 +1,3 @@ +interface foo { + use a1.foo.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/root.wit b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/root.wit new file mode 100644 index 0000000000..9a23dbfd35 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2/root.wit @@ -0,0 +1,3 @@ +interface foo { + use a1.foo.{} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/undefined-typed.wit.result b/crates/wit-parser/tests/ui/parse-fail/undefined-typed.wit.result index 25cde3f7c2..aba06b4bc2 100644 --- a/crates/wit-parser/tests/ui/parse-fail/undefined-typed.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/undefined-typed.wit.result @@ -1,4 +1,4 @@ -no type named `bar` +type `bar` does not exist --> tests/ui/parse-fail/undefined-typed.wit:4:14 | 4 | type foo = bar diff --git a/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit b/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit index a6c680b4e1..8fe521bfb5 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit +++ b/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit @@ -1,5 +1,5 @@ // parse-fail world foo { - import bar: bar + import bar: self.bar } diff --git a/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit.result b/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit.result index 592fc09f93..143852253c 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit.result @@ -1,5 +1,5 @@ -bar not defined - --> tests/ui/parse-fail/unknown-interface.wit:4:15 +interface does not exist + --> tests/ui/parse-fail/unknown-interface.wit:4:20 | - 4 | import bar: bar - | ^-- \ No newline at end of file + 4 | import bar: self.bar + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-default1.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface1.wit similarity index 55% rename from crates/wit-parser/tests/ui/parse-fail/world-default1.wit rename to crates/wit-parser/tests/ui/parse-fail/unresolved-interface1.wit index a20da5d4ba..cc1b6ae35f 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-default1.wit +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface1.wit @@ -1,5 +1,5 @@ // parse-fail world foo { - default export bar + import foo: self.foo } diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface1.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface1.wit.result new file mode 100644 index 0000000000..dc6cea3c5d --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface1.wit.result @@ -0,0 +1,5 @@ +name `foo` is defined as a world, not an interface + --> tests/ui/parse-fail/unresolved-interface1.wit:4:20 + | + 4 | import foo: self.foo + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit new file mode 100644 index 0000000000..8a67d92f00 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit @@ -0,0 +1,6 @@ +// parse-fail + +world foo { + import foo: self.bar +} + diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit.result new file mode 100644 index 0000000000..f230df6eb8 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit.result @@ -0,0 +1,5 @@ +interface does not exist + --> tests/ui/parse-fail/unresolved-interface2.wit:4:20 + | + 4 | import foo: self.bar + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit new file mode 100644 index 0000000000..61683dde44 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit @@ -0,0 +1,5 @@ +// parse-fail + +world foo { + import foo: pkg.foo.bar +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit.result new file mode 100644 index 0000000000..b7c4ba676c --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit.result @@ -0,0 +1,5 @@ +document `foo` does not exist + --> tests/ui/parse-fail/unresolved-interface3.wit:4:19 + | + 4 | import foo: pkg.foo.bar + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit new file mode 100644 index 0000000000..29817a23be --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit @@ -0,0 +1,5 @@ +// parse-fail + +world foo { + import foo: pkg.unresolved-interface4 +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit.result new file mode 100644 index 0000000000..e3f55f5ba0 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit.result @@ -0,0 +1,5 @@ +document does not specify a default interface + --> tests/ui/parse-fail/unresolved-interface4.wit:4:19 + | + 4 | import foo: pkg.unresolved-interface4 + | ^-------------------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface5.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface5.wit new file mode 100644 index 0000000000..fa943c2a59 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface5.wit @@ -0,0 +1,5 @@ +// parse-fail + +world foo { + import foo: pkg.unresolved-interface5.bar +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface5.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface5.wit.result new file mode 100644 index 0000000000..fc5a01ae26 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface5.wit.result @@ -0,0 +1,5 @@ +interface does not exist + --> tests/ui/parse-fail/unresolved-interface5.wit:4:41 + | + 4 | import foo: pkg.unresolved-interface5.bar + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit new file mode 100644 index 0000000000..f4b7dd5bef --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit @@ -0,0 +1,5 @@ +// parse-fail + +interface foo { + use self.bar.{x} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result new file mode 100644 index 0000000000..33708bc6c9 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result @@ -0,0 +1,5 @@ +interface `bar` does not exist + --> tests/ui/parse-fail/unresolved-use1.wit:4:12 + | + 4 | use self.bar.{x} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result new file mode 100644 index 0000000000..46b214db21 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result @@ -0,0 +1,8 @@ +failed to parse package at "tests/ui/parse-fail/unresolved-use10" + +Caused by: + document does not specify a default interface + --> tests/ui/parse-fail/unresolved-use10/bar.wit:2:11 + | + 2 | use pkg.foo.{thing} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use10/bar.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10/bar.wit new file mode 100644 index 0000000000..82806f689e --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10/bar.wit @@ -0,0 +1,3 @@ +interface bar { + use pkg.foo.{thing} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use10/foo.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10/foo.wit new file mode 100644 index 0000000000..849a1eb3c9 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10/foo.wit @@ -0,0 +1,2 @@ +interface foo { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use11.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use11.wit.result new file mode 100644 index 0000000000..aafc35c3fe --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use11.wit.result @@ -0,0 +1,8 @@ +failed to parse package at "tests/ui/parse-fail/unresolved-use11" + +Caused by: + name `thing` is not defined + --> tests/ui/parse-fail/unresolved-use11/bar.wit:2:16 + | + 2 | use pkg.foo.{thing} + | ^---- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use11/bar.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use11/bar.wit new file mode 100644 index 0000000000..82806f689e --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use11/bar.wit @@ -0,0 +1,3 @@ +interface bar { + use pkg.foo.{thing} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use11/foo.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use11/foo.wit new file mode 100644 index 0000000000..3e6a7e9ced --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use11/foo.wit @@ -0,0 +1,2 @@ +default interface foo { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit new file mode 100644 index 0000000000..402c72b61c --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit @@ -0,0 +1,8 @@ +// parse-fail + +interface foo { + use self.bar.{x} +} + +interface bar { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit.result new file mode 100644 index 0000000000..3a43a8feb3 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit.result @@ -0,0 +1,5 @@ +name `x` is not defined + --> tests/ui/parse-fail/unresolved-use2.wit:4:17 + | + 4 | use self.bar.{x} + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use3.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use3.wit new file mode 100644 index 0000000000..6b34d6cac4 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use3.wit @@ -0,0 +1,9 @@ +// parse-fail + +interface foo { + use self.bar.{x} +} + +interface bar { + x: func() +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use3.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use3.wit.result new file mode 100644 index 0000000000..3d82f08104 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use3.wit.result @@ -0,0 +1,5 @@ +cannot import function `x` + --> tests/ui/parse-fail/unresolved-use3.wit:4:17 + | + 4 | use self.bar.{x} + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use4.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use4.wit new file mode 100644 index 0000000000..a82a94b450 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use4.wit @@ -0,0 +1,5 @@ +// parse-fail + +interface foo { + use pkg.something-that-does-not-exist.{x} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use4.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use4.wit.result new file mode 100644 index 0000000000..43bffddede --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use4.wit.result @@ -0,0 +1,5 @@ +document `something-that-does-not-exist` does not exist + --> tests/ui/parse-fail/unresolved-use4.wit:4:11 + | + 4 | use pkg.something-that-does-not-exist.{x} + | ^---------------------------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use5.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use5.wit new file mode 100644 index 0000000000..f349f39f0d --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use5.wit @@ -0,0 +1,6 @@ +// parse-fail + +interface foo { + use pkg.unresolved-use5.{x} +} + diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use5.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use5.wit.result new file mode 100644 index 0000000000..e361c989ae --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use5.wit.result @@ -0,0 +1,5 @@ +no `default` interface in document to use from + --> tests/ui/parse-fail/unresolved-use5.wit:4:11 + | + 4 | use pkg.unresolved-use5.{x} + | ^-------------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use6.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use6.wit new file mode 100644 index 0000000000..125b2e3dcf --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use6.wit @@ -0,0 +1,5 @@ +// parse-fail + +interface foo { + use pkg.unresolved-use6.nonexistent.{x} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use6.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use6.wit.result new file mode 100644 index 0000000000..e174fa5426 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use6.wit.result @@ -0,0 +1,5 @@ +interface `nonexistent` does not exist + --> tests/ui/parse-fail/unresolved-use6.wit:4:27 + | + 4 | use pkg.unresolved-use6.nonexistent.{x} + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit new file mode 100644 index 0000000000..c08f490950 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit @@ -0,0 +1,8 @@ +// parse-fail + +interface foo { + use pkg.unresolved-use7.bar.{x} +} + +interface bar { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit.result new file mode 100644 index 0000000000..418dd27c65 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit.result @@ -0,0 +1,5 @@ +name `x` is not defined + --> tests/ui/parse-fail/unresolved-use7.wit:4:32 + | + 4 | use pkg.unresolved-use7.bar.{x} + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use8.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use8.wit new file mode 100644 index 0000000000..dad0d2eb28 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use8.wit @@ -0,0 +1,7 @@ +// parse-fail + +world foo { + import foo: interface { + use self.foo.{i32} + } +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use8.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use8.wit.result new file mode 100644 index 0000000000..8ed307231f --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use8.wit.result @@ -0,0 +1,5 @@ +name `foo` is defined as a world, not an interface + --> tests/ui/parse-fail/unresolved-use8.wit:5:14 + | + 5 | use self.foo.{i32} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit b/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit new file mode 100644 index 0000000000..83ba81ad7c --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit @@ -0,0 +1,7 @@ +// parse-fail + +world foo { + import foo: interface { + use self.bar.{i32} + } +} diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit.result new file mode 100644 index 0000000000..7bcfad2e5a --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit.result @@ -0,0 +1,5 @@ +interface does not exist + --> tests/ui/parse-fail/unresolved-use9.wit:5:14 + | + 5 | use self.bar.{i32} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-conflict.wit b/crates/wit-parser/tests/ui/parse-fail/use-conflict.wit new file mode 100644 index 0000000000..20e158e133 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-conflict.wit @@ -0,0 +1,9 @@ +// parse-fail + +interface foo { + type x = u32 +} + +interface bar { + use self.foo.{x, x} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-conflict.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-conflict.wit.result new file mode 100644 index 0000000000..2cbe95d2e2 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-conflict.wit.result @@ -0,0 +1,5 @@ +name `x` is defined more than once + --> tests/ui/parse-fail/use-conflict.wit:8:20 + | + 8 | use self.foo.{x, x} + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-conflict2.wit b/crates/wit-parser/tests/ui/parse-fail/use-conflict2.wit new file mode 100644 index 0000000000..11a16519bd --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-conflict2.wit @@ -0,0 +1,11 @@ +// parse-fail + +interface foo { + type x = u32 +} + +interface bar { + use self.foo.{x} + + type x = s64 +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-conflict2.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-conflict2.wit.result new file mode 100644 index 0000000000..3c70f63445 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-conflict2.wit.result @@ -0,0 +1,5 @@ +name `x` is defined more than once + --> tests/ui/parse-fail/use-conflict2.wit:10:8 + | + 10 | type x = s64 + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-conflict3.wit b/crates/wit-parser/tests/ui/parse-fail/use-conflict3.wit new file mode 100644 index 0000000000..53a85b0318 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-conflict3.wit @@ -0,0 +1,11 @@ +// parse-fail + +interface foo { + type x = u32 +} + +interface bar { + use self.foo.{x} + + x: func() +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-conflict3.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-conflict3.wit.result new file mode 100644 index 0000000000..c9e7e41e21 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-conflict3.wit.result @@ -0,0 +1,5 @@ +name `x` is defined more than once + --> tests/ui/parse-fail/use-conflict3.wit:10:3 + | + 10 | x: func() + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit b/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit new file mode 100644 index 0000000000..a1abaeca50 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit @@ -0,0 +1,5 @@ +// parse-fail + +interface foo { + use self.foo.{bar} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit.result new file mode 100644 index 0000000000..8c7950ed91 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit.result @@ -0,0 +1,5 @@ +interface `foo` depends on itself + --> tests/ui/parse-fail/use-cycle1.wit:4:12 + | + 4 | use self.foo.{bar} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle2.wit b/crates/wit-parser/tests/ui/parse-fail/use-cycle2.wit new file mode 100644 index 0000000000..7d416e30eb --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle2.wit @@ -0,0 +1,5 @@ +// parse-fail + +interface foo { + use pkg.use-cycle2.foo.{bar} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle2.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-cycle2.wit.result new file mode 100644 index 0000000000..8af25543a8 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle2.wit.result @@ -0,0 +1,5 @@ +interface `foo` depends on itself + --> tests/ui/parse-fail/use-cycle2.wit:4:22 + | + 4 | use pkg.use-cycle2.foo.{bar} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle3.wit b/crates/wit-parser/tests/ui/parse-fail/use-cycle3.wit new file mode 100644 index 0000000000..7edf782278 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle3.wit @@ -0,0 +1,6 @@ +// parse-fail + +default interface foo { + // TODO: this could use a better error message + use pkg.use-cycle3.{bar} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle3.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-cycle3.wit.result new file mode 100644 index 0000000000..ff6d459c48 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle3.wit.result @@ -0,0 +1,5 @@ +interface `foo` depends on itself + --> tests/ui/parse-fail/use-cycle3.wit:5:11 + | + 5 | use pkg.use-cycle3.{bar} + | ^--------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit b/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit new file mode 100644 index 0000000000..9fe1fbae34 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit @@ -0,0 +1,13 @@ +// parse-fail + +interface foo { + use self.bar.{y} + + type x = u32 +} + +interface bar { + use self.foo.{x} + + type y = u32 +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit.result new file mode 100644 index 0000000000..65b739418c --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit.result @@ -0,0 +1,5 @@ +interface `bar` depends on itself + --> tests/ui/parse-fail/use-cycle4.wit:4:12 + | + 4 | use self.bar.{y} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-from-package-world.wit b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world.wit new file mode 100644 index 0000000000..5fdf01a23e --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world.wit @@ -0,0 +1,8 @@ +// parse-fail + +world foo { +} + +interface bar { + use pkg.use-from-package-world.foo.{x} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-from-package-world.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world.wit.result new file mode 100644 index 0000000000..e1dc970fa6 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world.wit.result @@ -0,0 +1,5 @@ +interface does not exist + --> tests/ui/parse-fail/use-from-package-world.wit:7:34 + | + 7 | use pkg.use-from-package-world.foo.{x} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2.wit.result new file mode 100644 index 0000000000..afd167522a --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2.wit.result @@ -0,0 +1,8 @@ +failed to parse package at "tests/ui/parse-fail/use-from-package-world2" + +Caused by: + cannot import from world `foo` + --> tests/ui/parse-fail/use-from-package-world2/bar.wit:2:15 + | + 2 | use pkg.foo.foo.{thing} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2/bar.wit b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2/bar.wit new file mode 100644 index 0000000000..078c0c348f --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2/bar.wit @@ -0,0 +1,3 @@ +interface bar { + use pkg.foo.foo.{thing} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2/foo.wit b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2/foo.wit new file mode 100644 index 0000000000..c2e6c53e73 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2/foo.wit @@ -0,0 +1,2 @@ +world foo { +} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-default1.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-default1.wit.result deleted file mode 100644 index c46b73ae56..0000000000 --- a/crates/wit-parser/tests/ui/parse-fail/world-default1.wit.result +++ /dev/null @@ -1,5 +0,0 @@ -bar not defined - --> tests/ui/parse-fail/world-default1.wit:4:18 - | - 4 | default export bar - | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-default2.wit b/crates/wit-parser/tests/ui/parse-fail/world-default2.wit deleted file mode 100644 index 202ff1f5e1..0000000000 --- a/crates/wit-parser/tests/ui/parse-fail/world-default2.wit +++ /dev/null @@ -1,6 +0,0 @@ -// parse-fail - -world foo { - default export interface {} - default export interface {} -} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-default2.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-default2.wit.result deleted file mode 100644 index c79e04163e..0000000000 --- a/crates/wit-parser/tests/ui/parse-fail/world-default2.wit.result +++ /dev/null @@ -1,5 +0,0 @@ -more than one default interface was defined - --> tests/ui/parse-fail/world-default2.wit:5:18 - | - 5 | default export interface {} - | ^-------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-default3.wit b/crates/wit-parser/tests/ui/parse-fail/world-default3.wit deleted file mode 100644 index 85751f90f9..0000000000 --- a/crates/wit-parser/tests/ui/parse-fail/world-default3.wit +++ /dev/null @@ -1,8 +0,0 @@ -// parse-fail - -interface foo {} - -world foo { - default export foo - default export foo -} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-default3.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-default3.wit.result deleted file mode 100644 index d6eae69175..0000000000 --- a/crates/wit-parser/tests/ui/parse-fail/world-default3.wit.result +++ /dev/null @@ -1,5 +0,0 @@ -more than one default interface was defined - --> tests/ui/parse-fail/world-default3.wit:7:18 - | - 7 | default export foo - | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit b/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit new file mode 100644 index 0000000000..cbaedc8bc9 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit @@ -0,0 +1,10 @@ +interface foo { + type a = u32 +} + +world the-world { + import bar: interface { + use self.foo.{a} + } + import foo: interface {} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit.result new file mode 100644 index 0000000000..6a9fbfb005 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit.result @@ -0,0 +1,5 @@ +import name `foo` conflicts with implicitly imported interface + --> tests/ui/parse-fail/world-implicit-import1.wit:9:10 + | + 9 | import foo: interface {} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-interface-clash.wit b/crates/wit-parser/tests/ui/parse-fail/world-interface-clash.wit new file mode 100644 index 0000000000..edc358f041 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-interface-clash.wit @@ -0,0 +1,2 @@ +interface foo {} +world foo {} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-interface-clash.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-interface-clash.wit.result new file mode 100644 index 0000000000..396ada61ad --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-interface-clash.wit.result @@ -0,0 +1,5 @@ +name `foo` previously defined in document + --> tests/ui/parse-fail/world-interface-clash.wit:2:7 + | + 2 | world foo {} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit b/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit index c55e74ab88..8906f0d526 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit @@ -4,6 +4,6 @@ interface foo {} interface bar {} world a { - import foo: foo - import foo: bar + import foo: self.foo + import foo: self.bar } diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit.result index 0cdbf6248e..c7db2c75c8 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit.result @@ -1,5 +1,5 @@ -duplicate import foo +name imported twice --> tests/ui/parse-fail/world-same-fields.wit:8:10 | - 8 | import foo: bar + 8 | import foo: self.bar | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields2.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-same-fields2.wit.result index 14cfbec88c..a03be44087 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-same-fields2.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields2.wit.result @@ -1,4 +1,4 @@ -duplicate import foo +name imported twice --> tests/ui/parse-fail/world-same-fields2.wit:5:10 | 5 | import foo: interface {} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit b/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit new file mode 100644 index 0000000000..e48a2aedb5 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit @@ -0,0 +1,6 @@ +// parse-fail + +world a { + export foo: interface {} + export foo: interface {} +} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit.result new file mode 100644 index 0000000000..25482b2183 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit.result @@ -0,0 +1,5 @@ +name exported twice + --> tests/ui/parse-fail/world-same-fields3.wit:5:10 + | + 5 | export foo: interface {} + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit b/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit new file mode 100644 index 0000000000..fedaaa76ca --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit @@ -0,0 +1,6 @@ +interface foo {} + +world bar { + import foo: self.foo + import bar: self.foo +} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit.result new file mode 100644 index 0000000000..27d6845da6 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit.result @@ -0,0 +1,5 @@ +cannot import same interface twice + --> tests/ui/parse-fail/world-same-import.wit:5:10 + | + 5 | import bar: self.foo + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/shared-types.wit b/crates/wit-parser/tests/ui/shared-types.wit index 362e78719b..90caaddfa0 100644 --- a/crates/wit-parser/tests/ui/shared-types.wit +++ b/crates/wit-parser/tests/ui/shared-types.wit @@ -2,7 +2,7 @@ world foo { import foo: interface { a: func() -> list } - default export interface { + export foo: interface { a: func() -> tuple> } } diff --git a/crates/wit-parser/tests/ui/shared-types.wit.result b/crates/wit-parser/tests/ui/shared-types.wit.result deleted file mode 100644 index df992cf66c..0000000000 --- a/crates/wit-parser/tests/ui/shared-types.wit.result +++ /dev/null @@ -1,52 +0,0 @@ -{ - "worlds": [ - { - "name": "foo", - "default": "i1", - "imports": [ - [ - "foo", - "i0" - ] - ] - } - ], - "interfaces": [ - { - "name": "", - "functions": [ - { - "name": "a", - "params": [], - "results": [ - "type-0" - ] - } - ] - }, - { - "name": "", - "functions": [ - { - "name": "a", - "params": [], - "results": [ - "type-1" - ] - } - ] - } - ], - "types": [ - { - "list": "u8" - }, - { - "tuple": { - "types": [ - "type-0" - ] - } - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/type-then-eof.wit.result b/crates/wit-parser/tests/ui/type-then-eof.wit.result deleted file mode 100644 index 2817773224..0000000000 --- a/crates/wit-parser/tests/ui/type-then-eof.wit.result +++ /dev/null @@ -1,16 +0,0 @@ -{ - "interfaces": [ - { - "name": "foo", - "functions": [ - { - "name": "foo", - "params": [], - "results": [ - "string" - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/types.wit.result b/crates/wit-parser/tests/ui/types.wit.result deleted file mode 100644 index f412903d22..0000000000 --- a/crates/wit-parser/tests/ui/types.wit.result +++ /dev/null @@ -1,418 +0,0 @@ -{ - "interfaces": [ - { - "name": "types", - "types": [ - "type-0", - "type-1", - "type-2", - "type-3", - "type-4", - "type-5", - "type-6", - "type-7", - "type-8", - "type-9", - "type-10", - "type-11", - "type-12", - "type-13", - "type-14", - "type-15", - "type-16", - "type-17", - "type-18", - "type-19", - "type-20", - "type-21", - "type-22", - "type-23", - "type-24", - "type-25", - "type-26", - "type-27", - "type-28", - "type-29", - "type-30", - "type-31", - "type-32", - "type-33", - "type-34", - "type-35", - "type-36", - "type-37", - "type-38", - "type-39", - "type-40", - "type-41", - "type-42", - "type-43", - "type-44", - "type-45", - "type-46", - "type-47", - "type-48", - "type-49", - "type-50", - "type-51", - "type-52", - "type-53", - "type-54" - ] - } - ], - "types": [ - { - "primitive": "u8" - }, - { - "primitive": "u16" - }, - { - "primitive": "u32" - }, - { - "primitive": "u64" - }, - { - "primitive": "s8" - }, - { - "primitive": "s16" - }, - { - "primitive": "s32" - }, - { - "primitive": "s64" - }, - { - "primitive": "float32" - }, - { - "primitive": "float64" - }, - { - "primitive": "char" - }, - { - "list": "char" - }, - { - "primitive": "string" - }, - { - "option": "u32" - }, - { - "result": { - "ok": "u32", - "err": "u32" - } - }, - { - "result": { - "ok": null, - "err": "u32" - } - }, - { - "result": { - "ok": "u32", - "err": null - } - }, - { - "result": { - "ok": null, - "err": null - } - }, - { - "record": { - "fields": [] - } - }, - { - "record": { - "fields": [ - [ - "a", - "u32" - ] - ] - } - }, - { - "record": { - "fields": [ - [ - "a", - "u32" - ] - ] - } - }, - { - "record": { - "fields": [ - [ - "a", - "u32" - ], - [ - "b", - "u64" - ] - ] - } - }, - { - "record": { - "fields": [ - [ - "a", - "u32" - ], - [ - "b", - "u64" - ] - ] - } - }, - { - "record": { - "fields": [ - [ - "x", - "u32" - ] - ] - } - }, - { - "record": { - "fields": [] - } - }, - { - "tuple": { - "types": [] - } - }, - { - "tuple": { - "types": [ - "u32" - ] - } - }, - { - "tuple": { - "types": [ - "u32" - ] - } - }, - { - "tuple": { - "types": [ - "u32", - "u64" - ] - } - }, - { - "flags": { - "flags": [] - } - }, - { - "flags": { - "flags": [ - "a", - "b", - "c" - ] - } - }, - { - "flags": { - "flags": [ - "a", - "b", - "c" - ] - } - }, - { - "variant": { - "cases": [ - [ - "a", - null - ] - ] - } - }, - { - "variant": { - "cases": [ - [ - "a", - null - ], - [ - "b", - null - ] - ] - } - }, - { - "variant": { - "cases": [ - [ - "a", - null - ], - [ - "b", - null - ] - ] - } - }, - { - "variant": { - "cases": [ - [ - "a", - null - ], - [ - "b", - "u32" - ] - ] - } - }, - { - "variant": { - "cases": [ - [ - "a", - null - ], - [ - "b", - "type-55" - ] - ] - } - }, - { - "union": { - "cases": [ - "u32", - "u64" - ] - } - }, - { - "union": { - "cases": [ - "u32", - "type-55" - ] - } - }, - { - "union": { - "cases": [ - "u32", - "type-55" - ] - } - }, - { - "enum": { - "cases": [ - "a", - "b", - "c" - ] - } - }, - { - "enum": { - "cases": [ - "a", - "b", - "c" - ] - } - }, - { - "primitive": "bool" - }, - { - "primitive": "string" - }, - { - "list": "type-57" - }, - { - "primitive": "type-43" - }, - { - "primitive": "type-43" - }, - { - "stream": { - "element": "u32", - "end": "u32" - } - }, - { - "stream": { - "element": null, - "end": "u32" - } - }, - { - "stream": { - "element": "u32", - "end": null - } - }, - { - "stream": { - "element": null, - "end": null - } - }, - { - "future": "u32" - }, - { - "future": null - }, - { - "primitive": "type-54" - }, - { - "primitive": "u32" - }, - { - "option": "u32" - }, - { - "list": "type-31" - }, - { - "list": "type-56" - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/use.wit b/crates/wit-parser/tests/ui/use.wit new file mode 100644 index 0000000000..11d67325c4 --- /dev/null +++ b/crates/wit-parser/tests/ui/use.wit @@ -0,0 +1,31 @@ +interface foo { + use self.bar.{the-type} +} + +interface bar { + type the-type = u32 +} + +interface baz { + use self.foo.{the-type} + use self.bar.{the-type as test} +} + +interface empty { +} + +interface use-from-empty { + use self.empty.{} + use pkg.%use.empty.{} +} + + +interface use-multiple { + use self.baz.{the-type, test} + + some-function: func(x: the-type) -> test +} + +interface trailing-comma { + use self.foo.{the-type,} +} diff --git a/crates/wit-parser/tests/ui/wasi.wit.result b/crates/wit-parser/tests/ui/wasi.wit.result deleted file mode 100644 index 3357f47bb5..0000000000 --- a/crates/wit-parser/tests/ui/wasi.wit.result +++ /dev/null @@ -1,108 +0,0 @@ -{ - "interfaces": [ - { - "name": "wasi", - "types": [ - "type-0", - "type-1", - "type-2" - ] - } - ], - "types": [ - { - "enum": { - "cases": [ - "realtime", - "monotonic" - ] - } - }, - { - "primitive": "u64" - }, - { - "enum": { - "cases": [ - "success", - "toobig", - "access", - "addrinuse", - "addrnotavail", - "afnosupport", - "again", - "already", - "badf", - "badmsg", - "busy", - "canceled", - "child", - "connaborted", - "connrefused", - "connreset", - "deadlk", - "destaddrreq", - "dom", - "dquot", - "exist", - "fault", - "fbig", - "hostunreach", - "idrm", - "ilseq", - "inprogress", - "intr", - "inval", - "io", - "isconn", - "isdir", - "loop", - "mfile", - "mlink", - "msgsize", - "multihop", - "nametoolong", - "netdown", - "netreset", - "netunreach", - "nfile", - "nobufs", - "nodev", - "noent", - "noexec", - "nolck", - "nolink", - "nomem", - "nomsg", - "noprotoopt", - "nospc", - "nosys", - "notconn", - "notdir", - "notempty", - "notrecoverable", - "notsock", - "notsup", - "notty", - "nxio", - "overflow", - "ownerdead", - "perm", - "pipe", - "proto", - "protonosupport", - "prototype", - "range", - "rofs", - "spipe", - "srch", - "stale", - "timedout", - "txtbsy", - "xdev", - "notcapable" - ] - } - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/world-default.wit b/crates/wit-parser/tests/ui/world-default.wit deleted file mode 100644 index 0f15c22b62..0000000000 --- a/crates/wit-parser/tests/ui/world-default.wit +++ /dev/null @@ -1,7 +0,0 @@ -interface foo { - a: func() - } - -world bar { - default export foo -} diff --git a/crates/wit-parser/tests/ui/world-default.wit.result b/crates/wit-parser/tests/ui/world-default.wit.result deleted file mode 100644 index 67ccdedab4..0000000000 --- a/crates/wit-parser/tests/ui/world-default.wit.result +++ /dev/null @@ -1,20 +0,0 @@ -{ - "worlds": [ - { - "name": "bar", - "default": "i0" - } - ], - "interfaces": [ - { - "name": "foo", - "functions": [ - { - "name": "a", - "params": [], - "results": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/world-diamond.wit b/crates/wit-parser/tests/ui/world-diamond.wit new file mode 100644 index 0000000000..5de78b35c5 --- /dev/null +++ b/crates/wit-parser/tests/ui/world-diamond.wit @@ -0,0 +1,20 @@ +interface shared { + type foo = u32 +} + +interface i1 { + use self.shared.{foo} + + a: func() -> foo +} + +interface i2 { + use self.shared.{foo} + + a: func() -> foo +} + +world the-world { + import i1: self.i1 + import i2: self.i2 +} diff --git a/crates/wit-parser/tests/ui/worlds.wit b/crates/wit-parser/tests/ui/worlds.wit index 61f61cd75c..16eeba201c 100644 --- a/crates/wit-parser/tests/ui/worlds.wit +++ b/crates/wit-parser/tests/ui/worlds.wit @@ -2,17 +2,18 @@ interface foo {} interface bar {} world the-world { - import foo: foo - import bar: bar + import foo: self.foo + import bar: self.bar import baz: interface { foo: func() } - export foo: foo - export bar: bar + export foo: self.foo + export bar: self.bar export baz: interface { foo: func() } +} - default export interface { - } +default world a-different-world { + import foo: self.foo } diff --git a/crates/wit-parser/tests/ui/worlds.wit.result b/crates/wit-parser/tests/ui/worlds.wit.result deleted file mode 100644 index 560a74a7b7..0000000000 --- a/crates/wit-parser/tests/ui/worlds.wit.result +++ /dev/null @@ -1,67 +0,0 @@ -{ - "worlds": [ - { - "name": "the-world", - "default": "i4", - "imports": [ - [ - "foo", - "i0" - ], - [ - "bar", - "i1" - ], - [ - "baz", - "i2" - ] - ], - "exports": [ - [ - "foo", - "i0" - ], - [ - "bar", - "i1" - ], - [ - "baz", - "i3" - ] - ] - } - ], - "interfaces": [ - { - "name": "foo" - }, - { - "name": "bar" - }, - { - "name": "", - "functions": [ - { - "name": "foo", - "params": [], - "results": [] - } - ] - }, - { - "name": "", - "functions": [ - { - "name": "foo", - "params": [], - "results": [] - } - ] - }, - { - "name": "" - } - ] -} \ No newline at end of file From 2fcb7b3fbae8cbff435fd9b954d219f76458f83b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 16 Dec 2022 09:38:16 -0800 Subject: [PATCH 02/37] Implement elaboration of exports --- crates/wit-parser/src/resolve.rs | 213 +++++++++++------- .../ui/parse-fail/bad-diamond.wit.result | 4 +- .../world-implicit-import1.wit.result | 3 +- .../ui/parse-fail/world-same-fields4.wit | 11 + .../parse-fail/world-same-fields4.wit.result | 8 + .../ui/parse-fail/worlds-same-fields5.wit | 16 ++ .../parse-fail/worlds-same-fields5.wit.result | 9 + crates/wit-parser/tests/ui/worlds.wit | 21 ++ 8 files changed, 203 insertions(+), 82 deletions(-) create mode 100644 crates/wit-parser/tests/ui/parse-fail/world-same-fields4.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/world-same-fields4.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/worlds-same-fields5.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/worlds-same-fields5.wit.result diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 24b184154b..63a4c10b27 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -507,7 +507,9 @@ impl Remap { // explicitly named imports of interfaces are recorded as well for // determining names later on. let mut explicit_import_names = HashMap::new(); + let mut explicit_export_names = HashMap::new(); let mut imports = Vec::new(); + let mut exports = Vec::new(); for ((name, item), span) in mem::take(&mut world.imports).into_iter().zip(import_spans) { match item { WorldItem::Interface(id) => { @@ -519,95 +521,41 @@ impl Remap { WorldItem::Function(_) => unimplemented!(), } } + for ((name, item), span) in mem::take(&mut world.exports).into_iter().zip(export_spans) { + match item { + WorldItem::Interface(id) => { + let id = self.interfaces[id.index()]; + exports.push((id, *span)); + let prev = explicit_export_names.insert(id, name); + assert!(prev.is_none()); + } + WorldItem::Function(_) => unimplemented!(), + } + } // Next all imports and their transitive imports are processed. This // is done through a `stack` of `Action` items which is processed in // LIFO order, meaning that an action of processing the dependencies // is pushed after processing the node itself. The dependency processing // will push more items onto the stack as necessary. - // - // Throughout this an `imports_processed` set is maintained to ensure - // that multiple dependencies on the same interface won't push it twice - // to the import list as it's needed only once. - // - // Note that at this point all interfaces are known to be acyclic so - // there's no cycle detection here. - enum Action { - Import(InterfaceId, Span), - Deps(InterfaceId, Span), - } - - let mut stack = Vec::new(); - let mut resolving_stack = Vec::new(); - let mut imports_processed = HashSet::new(); - let name_of = |id: InterfaceId| { - explicit_import_names - .get(&id) - .unwrap_or_else(|| resolve.interfaces[id].name.as_ref().unwrap()) + let mut elaborate = WorldElaborator { + resolve, + world, + imports_processed: Default::default(), + exports_processed: Default::default(), + resolving_stack: Default::default(), + explicit_import_names: &explicit_import_names, + explicit_export_names: &explicit_export_names, }; for (id, span) in imports { - if !imports_processed.insert(id) { - continue; - } - stack.push(Action::Import(id, span)); - stack.push(Action::Deps(id, span)); - - while let Some(action) = stack.pop() { - match action { - Action::Import(id, span) => { - let name = name_of(id); - let prev = world.imports.insert(name.clone(), WorldItem::Interface(id)); - if prev.is_none() { - assert_eq!(resolving_stack.pop(), Some(id)); - continue; - } - - if explicit_import_names.contains_key(&id) { - bail!(Error { - span, - msg: format!( - "import name `{name}` conflicts with implicitly imported interface" - ), - }) - } - - let mut msg = format!("import of `{}`\n", name_of(resolving_stack[0])); - for i in resolving_stack.iter().skip(1) { - writeln!(msg, " .. which depends on `{}`", name_of(*i)).unwrap(); - } - writeln!( - msg, - "conflicts with a previously imported interface \ - using the name `{name}`", - ) - .unwrap(); - bail!(Error { span, msg }) - } - Action::Deps(id, span) => { - resolving_stack.push(id); - for (_, ty) in resolve.interfaces[id].types.iter() { - let ty = match resolve.types[*ty].kind { - TypeDefKind::Type(Type::Id(id)) => id, - _ => continue, - }; - match resolve.types[ty].owner { - TypeOwner::None => {} - TypeOwner::Interface(other) => { - if !imports_processed.insert(other) { - continue; - } - stack.push(Action::Import(other, span)); - stack.push(Action::Deps(other, span)); - } - TypeOwner::World(_) => unreachable!(), - } - } - } - } - } + elaborate.import(id, span)?; + } + for (id, span) in exports { + elaborate.export(id, span)?; } - log::trace!("{:?}", world.imports); + log::trace!("imports = {:?}", world.imports); + log::trace!("exports = {:?}", world.exports); Ok(()) } @@ -627,3 +575,110 @@ impl Remap { } } } + +struct WorldElaborator<'a, 'b> { + resolve: &'a Resolve, + world: &'b mut World, + explicit_import_names: &'a HashMap, + explicit_export_names: &'a HashMap, + + /// Set of imports which are either imported into the world already or in + /// the `stack` to get processed, used to ensure the same dependency isn't + /// pushed multiple times into the stack. + imports_processed: HashSet, + exports_processed: HashSet, + + /// Dependency chain of why we're importing the top of `stack`, used to + /// print an error message. + resolving_stack: Vec<(InterfaceId, bool)>, +} + +impl<'a> WorldElaborator<'a, '_> { + fn import(&mut self, id: InterfaceId, span: Span) -> Result<()> { + self.recurse(id, span, true) + } + + fn export(&mut self, id: InterfaceId, span: Span) -> Result<()> { + self.recurse(id, span, false) + } + + fn recurse(&mut self, id: InterfaceId, span: Span, import: bool) -> Result<()> { + let processed = if import { + &mut self.imports_processed + } else { + &mut self.exports_processed + }; + if !processed.insert(id) { + return Ok(()); + } + + self.resolving_stack.push((id, import)); + for (_, ty) in self.resolve.interfaces[id].types.iter() { + let ty = match self.resolve.types[*ty].kind { + TypeDefKind::Type(Type::Id(id)) => id, + _ => continue, + }; + let dep = match self.resolve.types[ty].owner { + TypeOwner::None => continue, + TypeOwner::Interface(other) => other, + TypeOwner::World(_) => unreachable!(), + }; + let import = import || !self.explicit_export_names.contains_key(&dep); + + self.recurse(dep, span, import)?; + } + assert_eq!(self.resolving_stack.pop(), Some((id, import))); + + let name = self.name_of(id, import); + let set = if import { + &mut self.world.imports + } else { + &mut self.world.exports + }; + let prev = set.insert(name.clone(), WorldItem::Interface(id)); + if prev.is_none() { + return Ok(()); + } + + let desc = |import: bool| { + if import { + "import" + } else { + "export" + } + }; + + let mut msg = format!("{} of `{}`", desc(import), self.name_of(id, import)); + if self.resolving_stack.is_empty() { + msg.push_str(" "); + } else { + msg.push_str("\n"); + } + for (i, import) in self.resolving_stack.iter().rev() { + writeln!( + msg, + " .. which is depended on by {} `{}`", + desc(*import), + self.name_of(*i, *import) + ) + .unwrap(); + } + writeln!( + msg, + "conflicts with a previously imported interface \ + using the name `{name}`", + ) + .unwrap(); + bail!(Error { span, msg }) + } + + fn name_of(&self, id: InterfaceId, import: bool) -> &'a String { + let set = if import { + &self.explicit_import_names + } else { + &self.explicit_export_names + }; + set.get(&id) + .unwrap_or_else(|| self.resolve.interfaces[id].name.as_ref().unwrap()) + } +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-diamond.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-diamond.wit.result index 9e25b154f4..42409143d5 100644 --- a/crates/wit-parser/tests/ui/parse-fail/bad-diamond.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/bad-diamond.wit.result @@ -1,5 +1,5 @@ -import of `b` - .. which depends on `shared` +import of `shared` + .. which is depended on by import `b` conflicts with a previously imported interface using the name `shared` --> tests/ui/parse-fail/bad-diamond/join.wit:3:10 diff --git a/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit.result index 6a9fbfb005..e048165bb4 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/world-implicit-import1.wit.result @@ -1,4 +1,5 @@ -import name `foo` conflicts with implicitly imported interface +import of `foo` conflicts with a previously imported interface using the name `foo` + --> tests/ui/parse-fail/world-implicit-import1.wit:9:10 | 9 | import foo: interface {} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields4.wit b/crates/wit-parser/tests/ui/parse-fail/world-same-fields4.wit new file mode 100644 index 0000000000..766d29fbc3 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields4.wit @@ -0,0 +1,11 @@ +interface shared { + type a = u32 +} + +world foo { + import shared: interface {} + export a-name: interface { + use self.shared.{a} + } +} + diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields4.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-same-fields4.wit.result new file mode 100644 index 0000000000..0f6b9bb33a --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields4.wit.result @@ -0,0 +1,8 @@ +import of `shared` + .. which is depended on by export `a-name` +conflicts with a previously imported interface using the name `shared` + + --> tests/ui/parse-fail/world-same-fields4.wit:7:10 + | + 7 | export a-name: interface { + | ^----- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/worlds-same-fields5.wit b/crates/wit-parser/tests/ui/parse-fail/worlds-same-fields5.wit new file mode 100644 index 0000000000..3faea41f1b --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/worlds-same-fields5.wit @@ -0,0 +1,16 @@ +interface i1 { + type t = u32 +} +interface i2 { + use self.i1.{t} +} +interface i3 { + use self.i2.{t} +} + +world test { + import i1: interface {} + + export i1: self.i1 + export i3: self.i3 +} diff --git a/crates/wit-parser/tests/ui/parse-fail/worlds-same-fields5.wit.result b/crates/wit-parser/tests/ui/parse-fail/worlds-same-fields5.wit.result new file mode 100644 index 0000000000..2e13df1a99 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/worlds-same-fields5.wit.result @@ -0,0 +1,9 @@ +import of `i1` + .. which is depended on by import `i2` + .. which is depended on by export `i3` +conflicts with a previously imported interface using the name `i1` + + --> tests/ui/parse-fail/worlds-same-fields5.wit:15:10 + | + 15 | export i3: self.i3 + | ^- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/worlds.wit b/crates/wit-parser/tests/ui/worlds.wit index 16eeba201c..3cd125117d 100644 --- a/crates/wit-parser/tests/ui/worlds.wit +++ b/crates/wit-parser/tests/ui/worlds.wit @@ -17,3 +17,24 @@ world the-world { default world a-different-world { import foo: self.foo } + +interface i1 { + type t = u32 +} +interface i2 { + use self.i1.{t} +} +interface i3 { + use self.i2.{t} +} + +world test { + import i3: self.i3 + + export i1: self.i1 + + // This should insert an implicit dependency on `i2` as an import, and then + // i2's dependency on i1 should be wired up to i3's implicit imported + // dependency on i1. + export i3: self.i3 +} From 6c85adb3ed8acc3a2517fef81b1831b68175c4b2 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 3 Jan 2023 12:10:24 -0800 Subject: [PATCH 03/37] First pass at updating wit-component Currently the crate compiles but most of it is commented out. This commit specifically works on updating the printing portion as well as the decoding portion from binary. Basic-to-semi-advanced binaries can be decoded and printed now. No tests are currently written. Printing is now centered around a document within a `Resolve`. The CLI print currently requires that a document be specified if there's more than one document. The plan is to also add something like `--out-dir` to print out all documents at once. --- Cargo.toml | 1 + crates/wasmparser/Cargo.toml | 2 +- crates/wasmparser/src/validator/types.rs | 6 + crates/wit-component/Cargo.toml | 1 + crates/wit-component/src/decoding.rs | 1033 ++++++++++++---------- crates/wit-component/src/lib.rs | 14 +- crates/wit-component/src/metadata.rs | 14 +- crates/wit-component/src/printing.rs | 262 +++--- crates/wit-parser/src/ast/resolve.rs | 5 +- crates/wit-parser/src/lib.rs | 26 +- crates/wit-parser/src/resolve.rs | 14 +- src/bin/wasm-tools/component.rs | 229 ++--- 12 files changed, 914 insertions(+), 693 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b8600f12ce..599a6bc537 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ rand = { version = "0.8.4", features = ["small_rng"] } rayon = "1.3" serde = { version = "1.0.137", features = ["derive"] } wasmtime = { version = "3.0.0", default-features = false, features = ['cranelift'] } +url = "2.3.1" wasm-encoder = { version = "0.20.0", path = "crates/wasm-encoder"} wasm-compose = { version = "0.2.1", path = "crates/wasm-compose"} diff --git a/crates/wasmparser/Cargo.toml b/crates/wasmparser/Cargo.toml index 6be2467b76..734a3d22ac 100644 --- a/crates/wasmparser/Cargo.toml +++ b/crates/wasmparser/Cargo.toml @@ -14,7 +14,7 @@ exclude = ["benches/*.wasm"] [dependencies] indexmap = { workspace = true } -url = "2.3.1" +url = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs index 1cb6179625..f2f205d3db 100644 --- a/crates/wasmparser/src/validator/types.rs +++ b/crates/wasmparser/src/validator/types.rs @@ -218,6 +218,12 @@ impl fmt::Display for KebabString { } } +impl From for String { + fn from(s: KebabString) -> String { + s.0 + } +} + /// A simple alloc-free list of types used for calculating lowered function signatures. pub(crate) struct LoweredTypes { types: [ValType; MAX_LOWERED_TYPES], diff --git a/crates/wit-component/Cargo.toml b/crates/wit-component/Cargo.toml index 9d4584e0f1..9a10e74bc7 100644 --- a/crates/wit-component/Cargo.toml +++ b/crates/wit-component/Cargo.toml @@ -20,6 +20,7 @@ anyhow = { workspace = true } log = "0.4.17" bitflags = "1.3.2" indexmap = { workspace = true } +url = { workspace = true } [dev-dependencies] wasmprinter = { workspace = true } diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index 914fb5de5d..8f8bb87737 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -1,10 +1,11 @@ -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; use indexmap::IndexMap; +use std::collections::HashMap; use std::hash::{Hash, Hasher}; +use url::Url; use wasmparser::{ - types::{self, KebabString}, - ComponentExport, ComponentImport, ComponentTypeRef, Parser, Payload, PrimitiveValType, - ValidPayload, Validator, WasmFeatures, + types, ComponentExport, ComponentExternalKind, ComponentImport, Parser, Payload, + PrimitiveValType, ValidPayload, Validator, WasmFeatures, }; use wit_parser::*; @@ -69,117 +70,101 @@ impl<'a> ComponentInfo<'a> { exports, }) } -} -/// Decode the world described by the given component bytes. -/// -/// This function takes a binary component as input and will infer the -/// `World` representation of its imports and exports. The binary component at -/// this time is either a "types only" component produced by `wit-component` or -/// an actual output of `wit-component`. -/// -/// The returned world represents the description of imports and exports -/// from the component. -/// -/// This can fail if the input component is invalid or otherwise isn't of the -/// expected shape. At this time not all component shapes are supported here. -pub fn decode_world(name: &str, bytes: &[u8]) -> Result<(Document, WorldId)> { - let info = ComponentInfo::new(bytes)?; - let mut imports = IndexMap::new(); - let mut exports = IndexMap::new(); - let mut ret = Document::default(); - let mut decoder = InterfaceDecoder::new(&info, &mut ret); - - for (name, import) in info.imports.iter() { - // Imports right now are only supported if they're an import of an - // instance. The instance is expected to export only functions and types - // where types are named types used in functions. - let ty = match import.ty { - ComponentTypeRef::Instance(i) => match info.types.type_at(i, false).unwrap() { - types::Type::ComponentInstance(i) => i, - _ => unreachable!(), + fn is_wit_package(&self) -> bool { + // wit packages only export component types + if !self.imports.is_empty() { + return false; + } + + // all wit package exports must be component types + self.exports.iter().all(|(_, export)| match export.kind { + ComponentExternalKind::Type => match self.types.type_at(export.index, false) { + Some(types::Type::Component(_)) => true, + _ => false, }, - _ => bail!("unsupported non-instance import `{name}`"), - }; - let id = decoder.decode( - Some(*name), - Some(import.url), - ty.exports(info.types.as_ref()) - .map(|(n, _, ty)| (n.as_str(), ty)), - )?; - imports.insert(name.to_string(), id); + _ => false, + }) } - let mut default = IndexMap::new(); - for (name, export) in info.exports.iter() { - // Get a `ComponentEntityType` which describes the type of the item - // being exported here. If a type itself is being exported then "peel" - // it to feign an actual entity being exported here to handle both - // type-only and normal components produced by `wit-component`. - let mut ty = info - .types - .component_entity_type_from_export(export) - .unwrap(); - if let types::ComponentEntityType::Type(id) = ty { - match info.types.type_from_id(id).unwrap() { - types::Type::ComponentInstance(_) => ty = types::ComponentEntityType::Instance(id), - types::Type::ComponentFunc(_) => ty = types::ComponentEntityType::Func(id), - _ => {} - } + fn decode_wit_package(&self, name: &str) -> Result<(Resolve, PackageId)> { + assert!(self.is_wit_package()); + let mut resolve = Resolve::default(); + let package = resolve.packages.alloc(Package { + name: name.to_string(), + documents: Default::default(), + }); + let mut decoder = WitPackageDecoder { + resolve, + package, + info: self, + url_to_package: HashMap::default(), + type_map: HashMap::new(), + type_src_map: HashMap::new(), + }; + + for (doc, export) in self.exports.iter() { + let ty = match self.types.type_at(export.index, false) { + Some(types::Type::Component(ty)) => ty, + _ => unreachable!(), + }; + decoder + .decode_document(doc, ty) + .with_context(|| format!("failed to decode document `{doc}`"))?; } + Ok((decoder.resolve, package)) + } +} - match ty { - // If an instance is being exported then that means this is an - // interface being exported, so decode the interface here and - // register an export. - types::ComponentEntityType::Instance(ty) => { - let ty = info - .types - .type_from_id(ty) - .unwrap() - .as_component_instance_type() - .unwrap(); - let id = decoder.decode( - Some(*name), - Some(export.url), - ty.exports(info.types.as_ref()) - .map(|(n, _, t)| (n.as_str(), t)), - )?; - exports.insert(name.to_string(), id); - } +/// Result of the [`decode`] function. +pub enum DecodedWasm { + /// The input to [`decode`] was a binary-encoded WIT package. + /// + /// The full resolve graph is here plus the identifier of the package that + /// was encoded. Note that other packages may be within the resolve if this + /// package refers to foreign packages. + WitPackage(Resolve, PackageId), + + /// The input to [`decode`] was a component and its interface is specified + /// by the world here. + Component(Resolve, WorldId), +} - // Otherwise assume everything else is part of the "default" export. - ty => { - default.insert(*name, ty); - } +impl DecodedWasm { + /// Returns the [`Resolve`] for WIT types contained. + pub fn resolve(&self) -> &Resolve { + match self { + DecodedWasm::WitPackage(resolve, _) => resolve, + DecodedWasm::Component(resolve, _) => resolve, } } +} - let default = if default.is_empty() { - None +/// Decodes an in-memory WebAssembly binary into a WIT [`Resolve`] and +/// associated metadata. +/// +/// The WebAssembly binary provided here can either be a +/// WIT-package-encoded-as-binary or an actual component itself. A [`Resolve`] +/// is always created and the return value indicates which was detected. +pub fn decode(name: &str, bytes: &[u8]) -> Result { + let info = ComponentInfo::new(bytes)?; + + if info.is_wit_package() { + let (resolve, pkg) = info.decode_wit_package(name)?; + Ok(DecodedWasm::WitPackage(resolve, pkg)) } else { - Some(decoder.decode(None, None, default.iter().map(|(n, t)| (*n, *t)))?) - }; - let world = ret.worlds.alloc(World { - name: name.to_string(), - docs: Default::default(), - imports, - exports, - default, - }); - Ok((ret, world)) + unimplemented!() + } } -/// Represents an interface decoder for WebAssembly components. -struct InterfaceDecoder<'a, 'doc> { +struct WitPackageDecoder<'a> { + resolve: Resolve, info: &'a ComponentInfo<'a>, - doc: &'doc mut Document, - - /// Names learned prior about each type id, if it was exported. - name_map: IndexMap, + package: PackageId, + url_to_package: HashMap, /// A map from a type id to what it's been translated to. - type_map: IndexMap, + type_map: HashMap, /// A second map, similar to `type_map`, which is keyed off a pointer hash /// instead of `TypeId`. @@ -189,420 +174,552 @@ struct InterfaceDecoder<'a, 'doc> { /// structure, so the second layer of map here ensures that types are /// only defined once and the second `TypeId` referring to a type will end /// up as an alias and/or import. - type_src_map: IndexMap, Type>, + type_src_map: HashMap, Type>, } -impl<'a, 'doc> InterfaceDecoder<'a, 'doc> { - /// Creates a new interface decoder for the given component information. - fn new(info: &'a ComponentInfo<'a>, doc: &'doc mut Document) -> Self { - Self { - info, - doc, - name_map: IndexMap::new(), - type_map: IndexMap::new(), - type_src_map: IndexMap::new(), - } - } - - /// Consumes the decoder and returns the interface representation assuming - /// that the interface is made of the specified exports. - pub fn decode( - &mut self, - name: Option<&str>, - url: Option<&str>, - exports: impl ExactSizeIterator + Clone, - ) -> Result { - let mut interface = Interface::default(); - if let Some(name) = name { - interface.name = name.to_string(); - } - if let Some(url) = url { - interface.url = Some(url.to_string()); - } - // Populate names in the name map first - let mut types = Vec::new(); - let mut funcs = Vec::new(); - for (name, ty) in exports { - let id = match ty { - types::ComponentEntityType::Type(id) => { - types.push(id); - id - } - types::ComponentEntityType::Func(ty) => { - funcs.push((name, ty)); - continue; +impl WitPackageDecoder<'_> { + fn decode_document(&mut self, name: &str, ty: &types::ComponentType) -> Result<()> { + // Process all imports for this document first, where imports are either + // importing interfaces from previously defined documents or from remote + // packages. Note that the URL must be specified here for these + // reconstruction purposes. + for (name, (url, ty)) in ty.imports.iter() { + let url = match url { + Some(url) => url, + None => bail!("no url specified for import `{name}`"), + }; + let ty = match ty { + types::ComponentEntityType::Instance(idx) => { + match self.info.types.type_from_id(*idx) { + Some(types::Type::ComponentInstance(ty)) => ty, + _ => unreachable!(), + } } - _ => bail!("expected function or type export"), + _ => bail!("import `{name}` is not an instance"), }; - - let prev = self.name_map.insert(id, name); - assert!(prev.is_none()); + self.register_import(url, ty) + .with_context(|| format!("failed to process import `{name}`"))?; } - // Process all types first which should show show up in topological - // order of the types as defined in the original component. This will - // ensure that type aliases are resolved correctly for now. - for id in types { - assert!(matches!( - self.info.types.type_from_id(id).unwrap(), - types::Type::Defined(_) - )); - let ty = self.decode_type(&types::ComponentValType::Type(id))?; - match ty { - Type::Id(id) => { - interface.types.push(id); - } - _ => unreachable!(), + let doc = self.resolve.documents.alloc(Document { + name: name.to_string(), + interfaces: IndexMap::new(), + worlds: Vec::new(), + default_interface: None, + default_world: None, + package: Some(self.package), + }); + let prev = self.resolve.packages[self.package] + .documents + .insert(name.to_string(), doc); + assert!(prev.is_none()); + + for (name, (url, ty)) in ty.exports.iter() { + if url.is_some() { + bail!("component type export `{name}` should not have a url") } - } - // Afterwards process all functions which should mostly use the types - // previously decoded. - for (name, ty) in funcs { - match self.info.types.type_from_id(ty).unwrap() { - types::Type::ComponentFunc(ty) => { - let func = self.function(name, ty)?; - interface.functions.push(func); + match ty { + types::ComponentEntityType::Instance(idx) => { + let ty = match self.info.types.type_from_id(*idx) { + Some(types::Type::ComponentInstance(ty)) => ty, + _ => unreachable!(), + }; + let id = self + .register_interface(doc, Some(name), ty) + .with_context(|| format!("failed to process export `{name}`"))?; + let prev = self.resolve.documents[doc] + .interfaces + .insert(name.to_string(), id); + assert!(prev.is_none()); + } + types::ComponentEntityType::Component(idx) => { + let ty = match self.info.types.type_from_id(*idx) { + Some(types::Type::Component(ty)) => ty, + _ => unreachable!(), + }; + let id = self + .register_world(doc, name, ty) + .with_context(|| format!("failed to process export `{name}`"))?; + self.resolve.documents[doc].worlds.push(id); } - _ => unimplemented!(), + _ => bail!("component export `{name}` is not an instance or component"), } } + Ok(()) + } - // Reset the `name_map` for the next interface, but notably persist the - // `type_map` which is required to get `use` of types across interfaces - // working since in the component it will be an alias to a type defined - // in a previous interface. - self.name_map.clear(); + fn register_import(&mut self, url: &Url, ty: &types::ComponentInstanceType) -> Result<()> { + let interface = self.extract_url_interface(url)?; - Ok(self.doc.interfaces.alloc(interface)) - } + for (name, export_url, ty) in ty.exports(self.info.types.as_ref()) { + if export_url.is_some() { + bail!("instance type export `{name}` should not have a url") + } - fn decode_params(&mut self, ps: &[(KebabString, types::ComponentValType)]) -> Result { - ps.iter() - .map(|(n, t)| Ok((n.to_string(), self.decode_type(t)?))) - .collect::>() - } + let ty = match ty { + types::ComponentEntityType::Type(ty) => ty, + _ => bail!("instance type export `{name}` is not a type"), + }; + let def = match self.info.types.type_from_id(ty) { + Some(types::Type::Defined(ty)) => ty, + _ => unreachable!(), + }; - fn decode_results( - &mut self, - ps: &[(Option, types::ComponentValType)], - ) -> Result { - let results: Vec<(Option, Type)> = ps - .iter() - .map(|(n, t)| Ok((n.as_ref().map(KebabString::to_string), self.decode_type(t)?))) - .collect::>()?; - - // Results must be either - // - A single anonymous type - // - Any number of named types - match results.len() { - 1 => { - // We either have a single anonymous type or a single - // named type. Either is valid. - let (name, ty) = results.into_iter().next().unwrap(); - match name { - Some(name) => Ok(Results::Named(vec![(name, ty)])), - None => Ok(Results::Anon(ty)), + // If the interface already defines this type then that's to be + // expected. + // + // TODO: comments + // + // TODO: should ideally verify the preexisting type matches `ty`. + let id = match self.resolve.interfaces[interface].types.get(name.as_str()) { + Some(id) => *id, + None => { + // If this is a pkg-local dependency then that means it can only + // refer to previously defined types, so error at this point. + if url.scheme() == "pkg" { + bail!("instance type export `{name}` not defined in interface"); + } + + // ... otherwise this is a lazily defined type for a foreign + // dependency. + let kind = self.convert_defined(def)?; + let id = self.resolve.types.alloc(TypeDef { + name: Some(name.to_string()), + kind, + docs: Default::default(), + owner: TypeOwner::Interface(interface), + }); + let prev = self.resolve.interfaces[interface] + .types + .insert(name.to_string(), id); + assert!(prev.is_none()); + id } - } - _ => { - // Otherwise, all types must be named; unwrap the names. - Ok(Results::Named( - results.into_iter().map(|(n, t)| (n.unwrap(), t)).collect(), - )) - } + }; + let prev = self.type_map.insert(ty, Type::Id(id)); + assert!(prev.is_none()); + let prev = self.type_src_map.insert(PtrHash(def), Type::Id(id)); + assert!(prev.is_none()); } - } - fn function(&mut self, func_name: &str, ty: &types::ComponentFuncType) -> Result { - let params = self.decode_params(&ty.params)?; - let results = self.decode_results(&ty.results)?; + Ok(()) + } - Ok(Function { - docs: Docs::default(), - name: func_name.to_string(), - kind: FunctionKind::Freestanding, - params, - results, + fn extract_url_interface(&mut self, url: &Url) -> Result { + Ok(if url.scheme() == "pkg" { + self.extract_pkg_interface(url) + .with_context(|| format!("failed to parse url: {url}"))? + } else { + self.extract_dep_interface(url) + .with_context(|| format!("failed to parse url: {url}"))? }) } - fn decode_type(&mut self, ty: &types::ComponentValType) -> Result { - Ok(match ty { - types::ComponentValType::Primitive(ty) => self.decode_primitive(*ty)?, - types::ComponentValType::Type(id) => { - // If this precise `TypeId` has already been decoded, then - // return that same result. - if let Some(ty) = self.type_map.get(id) { - return Ok(*ty); - } + fn extract_pkg_interface(&self, url: &Url) -> Result { + let mut segments = url.path_segments().ok_or_else(|| anyhow!("invalid url"))?; + let document = segments.next().ok_or_else(|| anyhow!("invalid url"))?; + let interface = segments.next().ok_or_else(|| anyhow!("invalid url"))?; + if segments.next().is_some() { + bail!("invalid url") + } + let doc = *self.resolve.packages[self.package] + .documents + .get(document) + .ok_or_else(|| anyhow!("document `{document}` not previously defined"))?; + let doc = &self.resolve.documents[doc]; + let interface = *doc.interfaces.get(interface).ok_or_else(|| { + anyhow!("interface `{interface}` not defined in document `{document}`") + })?; + Ok(interface) + } - let name = self.name_map.get(id).map(ToString::to_string); - let ty = self.info.types.type_from_id(*id).unwrap(); - let key = PtrHash(ty); - let ty = match self.type_src_map.get(&key) { - // If this `TypeId` points to a type which has previously - // been defined then a second `TypeId` pointing at it is - // indicative of an alias. Inject the alias here. - Some(prev) => { - let id = self.doc.types.alloc(TypeDef { - docs: Default::default(), - kind: TypeDefKind::Type(*prev), - name, - interface: Some(self.doc.interfaces.next_id()), - }); - Type::Id(id) - } + fn extract_dep_interface(&mut self, url: &Url) -> Result { + // Extract the interface and the document from the url + let mut segments = url.path_segments().ok_or_else(|| anyhow!("invalid url"))?; + let interface = segments.next_back().ok_or_else(|| anyhow!("invalid url"))?; + let document = segments.next_back().ok_or_else(|| anyhow!("invalid url"))?; + let package_name = segments.next_back().ok_or_else(|| anyhow!("invalid url"))?; + + // Then drop the two path segments from the url as a key to lookup the + // dependency package by url. + let mut url = url.clone(); + url.path_segments_mut().unwrap().pop().pop(); + + // Lazily create a `Package` as necessary, along with the document and + // interface. + let package = *self.url_to_package.entry(url).or_insert_with(|| { + self.resolve.packages.alloc(Package { + name: package_name.to_string(), + documents: Default::default(), + }) + }); + let doc = *self.resolve.packages[package] + .documents + .entry(document.to_string()) + .or_insert_with(|| { + self.resolve.documents.alloc(Document { + name: document.to_string(), + interfaces: IndexMap::new(), + worlds: Vec::new(), + default_interface: None, + default_world: None, + package: Some(package), + }) + }); + let interface = *self.resolve.documents[doc] + .interfaces + .entry(interface.to_string()) + .or_insert_with(|| { + self.resolve.interfaces.alloc(Interface { + name: Some(interface.to_string()), + url: None, + docs: Default::default(), + types: IndexMap::default(), + functions: Vec::new(), + document: doc, + }) + }); + Ok(interface) + } - // ... or this `TypeId`'s source definition has never been - // seen before, so declare the full type. - None => { - let ty = self.decode_defined_type(name, ty)?; - let prev = self.type_src_map.insert(key, ty); - assert!(prev.is_none()); - ty - } - }; + fn register_interface( + &mut self, + doc: DocumentId, + name: Option<&str>, + ty: &types::ComponentInstanceType, + ) -> Result { + let mut interface = Interface { + name: name.map(|n| n.to_string()), + url: None, + docs: Default::default(), + types: IndexMap::default(), + functions: Vec::new(), + document: doc, + }; - // Record the result of translation with the `TypeId` we have. - let prev = self.type_map.insert(*id, ty); - assert!(prev.is_none()); - ty + for (name, export_url, ty) in ty.exports(self.info.types.as_ref()) { + if export_url.is_some() { + bail!("instance type export `{name}` should not have a url") } - }) - } - fn decode_defined_type(&mut self, name: Option, ty: &'a types::Type) -> Result { - match ty { - types::Type::Defined(ty) => match ty { - types::ComponentDefinedType::Primitive(ty) => self.decode_named_primitive(name, ty), - types::ComponentDefinedType::Record(r) => self.decode_record(name, r.fields.iter()), - types::ComponentDefinedType::Variant(v) => { - self.decode_variant(name, v.cases.iter()) - } - types::ComponentDefinedType::List(ty) => { - let inner = self.decode_type(ty)?; - Ok(Type::Id(self.alloc_type(name, TypeDefKind::List(inner)))) + match ty { + types::ComponentEntityType::Type(id) => { + let ty = match self.info.types.type_from_id(id) { + Some(types::Type::Defined(ty)) => ty, + _ => unreachable!(), + }; + let key = PtrHash(ty); + let (kind, insert_src) = match self.type_src_map.get(&key) { + // If this `TypeId` points to a type which has + // previously been defined then a second `TypeId` + // pointing at it is indicative of an alias. Inject the + // alias here. + Some(prev) => (TypeDefKind::Type(*prev), false), + + // ... or this `TypeId`'s source definition has never + // been seen before, so declare the full type. + None => { + let ty = self + .convert_defined(ty) + .with_context(|| format!("failed to decode type `{name}`"))?; + (ty, true) + } + }; + let ty = self.resolve.types.alloc(TypeDef { + docs: Default::default(), + kind, + name: Some(name.to_string()), + owner: TypeOwner::Interface(self.resolve.interfaces.next_id()), + }); + + if insert_src { + self.type_src_map.insert(key, Type::Id(ty)); + } + let prev = self.type_map.insert(id, Type::Id(ty)); + assert!(prev.is_none()); + let prev = interface.types.insert(name.to_string(), ty); + assert!(prev.is_none()); } - types::ComponentDefinedType::Tuple(t) => self.decode_tuple(name, &t.types), - types::ComponentDefinedType::Flags(names) => self.decode_flags(name, names.iter()), - types::ComponentDefinedType::Enum(names) => self.decode_enum(name, names.iter()), - types::ComponentDefinedType::Union(u) => self.decode_union(name, &u.types), - types::ComponentDefinedType::Option(ty) => self.decode_option(name, ty), - types::ComponentDefinedType::Result { ok, err } => { - self.decode_result(name, ok.as_ref(), err.as_ref()) + + types::ComponentEntityType::Func(ty) => { + let ty = match self.info.types.type_from_id(ty) { + Some(types::Type::ComponentFunc(ty)) => ty, + _ => unreachable!(), + }; + let params = ty + .params + .iter() + .map(|(name, ty)| Ok((name.to_string(), self.convert_valtype(ty)?))) + .collect::>>()?; + let results = if ty.results.len() == 1 { + Results::Anon(self.convert_valtype(&ty.results[0].1)?) + } else { + Results::Named( + ty.results + .iter() + .map(|(name, ty)| { + Ok(( + name.as_ref().unwrap().to_string(), + self.convert_valtype(ty)?, + )) + }) + .collect::>>()?, + ) + }; + interface.functions.push(Function { + docs: Default::default(), + kind: FunctionKind::Freestanding, + name: name.to_string(), + params, + results, + }); } - }, - _ => unreachable!(), + _ => bail!("instance type export `{name}` is not a type or function"), + }; } + Ok(self.resolve.interfaces.alloc(interface)) } - fn decode_optional_type( + fn register_world( &mut self, - ty: Option<&types::ComponentValType>, - ) -> Result> { - match ty { - Some(ty) => self.decode_type(ty).map(Some), - None => Ok(None), + document: DocumentId, + name: &str, + ty: &types::ComponentType, + ) -> Result { + let mut world = World { + name: name.to_string(), + docs: Default::default(), + imports: Default::default(), + exports: Default::default(), + document, + }; + for (name, (url, ty)) in ty.imports.iter() { + let item = match ty { + types::ComponentEntityType::Instance(idx) => { + let ty = match self.info.types.type_from_id(*idx) { + Some(types::Type::ComponentInstance(ty)) => ty, + _ => unreachable!(), + }; + let id = match url { + Some(url) => { + // TODO: walk over types in the interface and + // register them with + // `self.{type_map,type_src_map}`. + if true { + panic!() + } + self.extract_url_interface(url)? + } + None => self.register_interface(document, None, ty)?, + }; + WorldItem::Interface(id) + } + _ => bail!("component import `{name}` is not an instance"), + }; + world.imports.insert(name.to_string(), item); } - } - - fn decode_named_primitive( - &mut self, - name: Option, - ty: &PrimitiveValType, - ) -> Result { - let mut ty = self.decode_primitive(*ty)?; - if let Some(name) = name { - ty = Type::Id(self.alloc_type(Some(name), TypeDefKind::Type(ty))); + for (name, (url, ty)) in ty.exports.iter() { + let item = match ty { + types::ComponentEntityType::Instance(idx) => { + let ty = match self.info.types.type_from_id(*idx) { + Some(types::Type::ComponentInstance(ty)) => ty, + _ => unreachable!(), + }; + let id = match url { + Some(url) => self.extract_url_interface(url)?, + None => self.register_interface(document, None, ty)?, + }; + WorldItem::Interface(id) + } + _ => bail!("component export `{name}` is not an instance"), + }; + world.exports.insert(name.to_string(), item); } - - Ok(ty) - } - - fn decode_primitive(&mut self, ty: PrimitiveValType) -> Result { - Ok(match ty { - PrimitiveValType::Bool => Type::Bool, - PrimitiveValType::S8 => Type::S8, - PrimitiveValType::U8 => Type::U8, - PrimitiveValType::S16 => Type::S16, - PrimitiveValType::U16 => Type::U16, - PrimitiveValType::S32 => Type::S32, - PrimitiveValType::U32 => Type::U32, - PrimitiveValType::S64 => Type::S64, - PrimitiveValType::U64 => Type::U64, - PrimitiveValType::Float32 => Type::Float32, - PrimitiveValType::Float64 => Type::Float64, - PrimitiveValType::Char => Type::Char, - PrimitiveValType::String => Type::String, - }) + Ok(self.resolve.worlds.alloc(world)) } - fn decode_record( - &mut self, - record_name: Option, - fields: impl ExactSizeIterator, - ) -> Result { - let record_name = - record_name.ok_or_else(|| anyhow!("interface has an unnamed record type"))?; - - let record = Record { - fields: fields - .map(|(name, ty)| { - Ok(Field { - docs: Docs::default(), - name: name.to_string(), - ty: self.decode_type(ty)?, - }) - }) - .collect::>()?, + fn convert_valtype(&mut self, ty: &types::ComponentValType) -> Result { + let id = match ty { + types::ComponentValType::Primitive(ty) => return Ok(self.convert_primitive(*ty)), + types::ComponentValType::Type(id) => *id, }; - Ok(Type::Id(self.alloc_type( - Some(record_name), - TypeDefKind::Record(record), - ))) - } + // Don't create duplicate types for anything previously created. + if let Some(ret) = self.type_map.get(&id) { + return Ok(*ret); + } - fn decode_variant( - &mut self, - variant_name: Option, - cases: impl ExactSizeIterator, - ) -> Result { - let variant_name = - variant_name.ok_or_else(|| anyhow!("interface has an unnamed variant type"))?; - - let variant = Variant { - cases: cases - .map(|(name, case)| { - Ok(Case { - docs: Docs::default(), - name: name.to_string(), - ty: self.decode_optional_type(case.ty.as_ref())?, - }) - }) - .collect::>()?, + // Otherwise create a new `TypeDef` without a name since this is an + // anonymous valtype. Note that this is invalid for some types so return + // errors on those types, but eventually the `bail!` here is + // more-or-less unreachable due to expected validation to be added to + // the component model binary format itself. + let ty = match self.info.types.type_from_id(id) { + Some(types::Type::Defined(ty)) => ty, + _ => unreachable!(), }; - - Ok(Type::Id(self.alloc_type( - Some(variant_name), - TypeDefKind::Variant(variant), - ))) + let kind = self.convert_defined(ty)?; + match &kind { + TypeDefKind::Type(_) + | TypeDefKind::List(_) + | TypeDefKind::Tuple(_) + | TypeDefKind::Option(_) + | TypeDefKind::Result(_) => {} + + TypeDefKind::Record(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Variant(_) + | TypeDefKind::Union(_) + | TypeDefKind::Flags(_) + | TypeDefKind::Future(_) + | TypeDefKind::Stream(_) => { + bail!("unexpected unnamed type"); + } + TypeDefKind::Unknown => unreachable!(), + } + let ty = self.resolve.types.alloc(TypeDef { + name: None, + docs: Default::default(), + owner: TypeOwner::None, + kind, + }); + let prev = self.type_map.insert(id, Type::Id(ty)); + assert!(prev.is_none()); + Ok(Type::Id(ty)) } - fn decode_tuple( - &mut self, - name: Option, - tys: &[types::ComponentValType], - ) -> Result { - let tuple = Tuple { - types: tys - .iter() - .map(|ty| self.decode_type(ty)) - .collect::>()?, - }; + /// Converts a wasmparser `ComponentDefinedType`, the definition of a type + /// in the component model, to a WIT `TypeDefKind` to get inserted into the + /// types arena by the caller. + fn convert_defined(&mut self, ty: &types::ComponentDefinedType) -> Result { + match ty { + types::ComponentDefinedType::Primitive(t) => { + Ok(TypeDefKind::Type(self.convert_primitive(*t))) + } - Ok(Type::Id(self.alloc_type(name, TypeDefKind::Tuple(tuple)))) - } + types::ComponentDefinedType::List(t) => { + let t = self.convert_valtype(t)?; + Ok(TypeDefKind::List(t)) + } - fn decode_flags( - &mut self, - flags_name: Option, - names: impl ExactSizeIterator, - ) -> Result { - let flags_name = - flags_name.ok_or_else(|| anyhow!("interface has an unnamed flags type"))?; - - let flags = Flags { - flags: names - .map(|name| { - Ok(Flag { - docs: Docs::default(), - name: name.to_string(), - }) - }) - .collect::>()?, - }; + types::ComponentDefinedType::Tuple(t) => { + let types = t + .types + .iter() + .map(|t| self.convert_valtype(t)) + .collect::>()?; + Ok(TypeDefKind::Tuple(Tuple { types })) + } - Ok(Type::Id( - self.alloc_type(Some(flags_name), TypeDefKind::Flags(flags)), - )) - } + types::ComponentDefinedType::Option(t) => { + let t = self.convert_valtype(t)?; + Ok(TypeDefKind::Option(t)) + } - fn decode_enum( - &mut self, - enum_name: Option, - names: impl ExactSizeIterator, - ) -> Result { - let enum_name = enum_name.ok_or_else(|| anyhow!("interface has an unnamed enum type"))?; - let enum_ = Enum { - cases: names - .map(|name| { - Ok(EnumCase { - docs: Docs::default(), - name: name.to_string(), - }) - }) - .collect::>()?, - }; + types::ComponentDefinedType::Result { ok, err } => { + let ok = match ok { + Some(t) => Some(self.convert_valtype(t)?), + None => None, + }; + let err = match err { + Some(t) => Some(self.convert_valtype(t)?), + None => None, + }; + Ok(TypeDefKind::Result(Result_ { ok, err })) + } - Ok(Type::Id( - self.alloc_type(Some(enum_name), TypeDefKind::Enum(enum_)), - )) - } + types::ComponentDefinedType::Record(r) => { + let fields = r + .fields + .iter() + .map(|(name, ty)| { + Ok(Field { + name: name.to_string(), + ty: self.convert_valtype(ty)?, + docs: Default::default(), + }) + }) + .collect::>()?; + Ok(TypeDefKind::Record(Record { fields })) + } - fn decode_union( - &mut self, - name: Option, - tys: &[types::ComponentValType], - ) -> Result { - let union = Union { - cases: tys - .iter() - .map(|ty| { - Ok(UnionCase { - docs: Docs::default(), - ty: self.decode_type(ty)?, + types::ComponentDefinedType::Variant(v) => { + let cases = v + .cases + .iter() + .map(|(name, case)| { + if case.refines.is_some() { + bail!("unimplemented support for `refines`"); + } + Ok(Case { + name: name.to_string(), + ty: match &case.ty { + Some(ty) => Some(self.convert_valtype(ty)?), + None => None, + }, + docs: Default::default(), + }) }) - }) - .collect::>()?, - }; + .collect::>()?; + Ok(TypeDefKind::Variant(Variant { cases })) + } - Ok(Type::Id(self.alloc_type(name, TypeDefKind::Union(union)))) - } + types::ComponentDefinedType::Flags(f) => { + let flags = f + .iter() + .map(|name| Flag { + name: name.to_string(), + docs: Default::default(), + }) + .collect(); + Ok(TypeDefKind::Flags(Flags { flags })) + } - fn decode_option( - &mut self, - name: Option, - payload: &types::ComponentValType, - ) -> Result { - let payload = self.decode_type(payload)?; - Ok(Type::Id( - self.alloc_type(name, TypeDefKind::Option(payload)), - )) - } + types::ComponentDefinedType::Union(u) => { + let cases = u + .types + .iter() + .map(|ty| { + Ok(UnionCase { + ty: self.convert_valtype(ty)?, + docs: Default::default(), + }) + }) + .collect::>()?; + Ok(TypeDefKind::Union(Union { cases })) + } - fn decode_result( - &mut self, - name: Option, - ok: Option<&types::ComponentValType>, - err: Option<&types::ComponentValType>, - ) -> Result { - let ok = self.decode_optional_type(ok)?; - let err = self.decode_optional_type(err)?; - Ok(Type::Id(self.alloc_type( - name, - TypeDefKind::Result(Result_ { ok, err }), - ))) + types::ComponentDefinedType::Enum(e) => { + let cases = e + .iter() + .cloned() + .map(|name| EnumCase { + name: name.into(), + docs: Default::default(), + }) + .collect(); + Ok(TypeDefKind::Enum(Enum { cases })) + } + } } - fn alloc_type(&mut self, name: Option, kind: TypeDefKind) -> TypeId { - self.doc.types.alloc(TypeDef { - docs: Docs::default(), - kind, - name, - interface: Some(self.doc.interfaces.next_id()), - }) + fn convert_primitive(&self, ty: PrimitiveValType) -> Type { + match ty { + PrimitiveValType::U8 => Type::U8, + PrimitiveValType::S8 => Type::S8, + PrimitiveValType::U16 => Type::U16, + PrimitiveValType::S16 => Type::S16, + PrimitiveValType::U32 => Type::U32, + PrimitiveValType::S32 => Type::S32, + PrimitiveValType::U64 => Type::U64, + PrimitiveValType::S64 => Type::S64, + PrimitiveValType::Bool => Type::Bool, + PrimitiveValType::Char => Type::Char, + PrimitiveValType::String => Type::String, + PrimitiveValType::Float32 => Type::Float32, + PrimitiveValType::Float64 => Type::Float64, + } } } diff --git a/crates/wit-component/src/lib.rs b/crates/wit-component/src/lib.rs index 109b61b3b1..997011187f 100644 --- a/crates/wit-component/src/lib.rs +++ b/crates/wit-component/src/lib.rs @@ -7,18 +7,18 @@ use std::fmt::Display; use std::str::FromStr; use wasm_encoder::CanonicalOption; -mod builder; +// mod builder; mod decoding; -mod encoding; -mod gc; +// mod encoding; +// mod gc; mod printing; -mod validation; +// mod validation; -pub use decoding::decode_world; -pub use encoding::ComponentEncoder; +pub use decoding::{decode, DecodedWasm}; +// pub use encoding::ComponentEncoder; pub use printing::*; -pub mod metadata; +// pub mod metadata; /// Supported string encoding formats. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index d1790099cd..5511676d64 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -34,9 +34,9 @@ use anyhow::{bail, Context, Result}; use indexmap::IndexMap; use wasm_encoder::Encode; use wasmparser::BinaryReader; -use wit_parser::{Document, World, WorldId}; +use wit_parser::{Resolve, World, WorldId}; -const CURRENT_VERSION: u8 = 0x01; +const CURRENT_VERSION: u8 = 0x02; /// The result of decoding binding information from a WebAssembly binary. /// @@ -44,7 +44,7 @@ const CURRENT_VERSION: u8 = 0x01; /// WebAssembly binary. pub struct Bindgen { /// Interface and type information for this binary. - pub doc: Document, + pub resolve: Resolve, /// The world that was bound. pub world: WorldId, /// Metadata about this specific module that was bound. @@ -53,10 +53,10 @@ pub struct Bindgen { impl Default for Bindgen { fn default() -> Bindgen { - let mut doc = Document::default(); - let world = doc.worlds.alloc(World::default()); + let mut resolve = Resolve::default(); + let world = resolve.worlds.alloc(World::default()); Bindgen { - doc, + resolve, world, metadata: ModuleMetadata::default(), } @@ -120,7 +120,7 @@ pub fn decode(wasm: &[u8]) -> Result<(Vec, Bindgen)> { /// into the final core wasm binary. The core wasm binary is later fed /// through `wit-component` to produce the actual component where this returned /// section will be decoded. -pub fn encode(doc: &Document, world: WorldId, encoding: StringEncoding) -> Vec { +pub fn encode(resolve: &Resolve, world: WorldId, encoding: StringEncoding) -> Vec { let component = ComponentEncoder::default() .types_only(true) .document(doc.clone(), encoding) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 15954afcff..e0cb970310 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -1,6 +1,5 @@ use anyhow::{anyhow, bail, Result}; use indexmap::{IndexMap, IndexSet}; -use std::collections::HashSet; use std::fmt::{self, Write}; use wit_parser::*; @@ -9,79 +8,48 @@ use wit_parser::*; pub struct DocumentPrinter { output: Output, declared: IndexSet, - iface_names: Vec, } impl DocumentPrinter { /// Print the given `*.wit` document to a string. - pub fn print(&mut self, doc: &Document) -> Result { - let mut all_names = HashSet::new(); - for (id, iface) in doc.interfaces.iter() { - let name = name(&iface.name, &mut all_names); + pub fn print(&mut self, resolve: &Resolve, docid: DocumentId) -> Result { + let doc = &resolve.documents[docid]; + for (name, id) in doc.interfaces.iter() { writeln!(&mut self.output, "interface {name} {{")?; - self.print_interface(doc, id)?; + self.print_interface(resolve, *id)?; writeln!(&mut self.output, "}}\n")?; - self.iface_names.push(name); } - for (_id, world) in doc.worlds.iter() { - let name = name(&world.name, &mut all_names); + for id in doc.worlds.iter() { + let world = &resolve.worlds[*id]; + let name = &world.name; writeln!(&mut self.output, "world {name} {{")?; for (name, import) in world.imports.iter() { - let iface_name = &self.iface_names[import.index()]; - writeln!(&mut self.output, "import {name}: {iface_name}")?; + self.print_world_item(resolve, name, import, docid, "import")?; } for (name, export) in world.exports.iter() { - let iface_name = &self.iface_names[export.index()]; - writeln!(&mut self.output, "export {name}: {iface_name}")?; + self.print_world_item(resolve, name, export, docid, "export")?; } - if let Some(default) = &world.default { - let iface_name = &self.iface_names[default.index()]; - writeln!(&mut self.output, "default export {iface_name}")?; - } - writeln!(&mut self.output, "}}")?; } self.declared.clear(); - self.iface_names.clear(); - return Ok(std::mem::take(&mut self.output).into()); - - fn name(name: &str, all_names: &mut HashSet) -> String { - if !name.is_empty() && all_names.insert(name.to_string()) { - return name.to_string(); - } - for i in 0.. { - let name = if name.is_empty() { - format!("interface{i}") - } else { - format!("{name}{i}") - }; - if all_names.insert(name.clone()) { - return name; - } - } - unreachable!() - } + Ok(std::mem::take(&mut self.output).into()) } /// Print the given WebAssembly interface to a string. - fn print_interface(&mut self, doc: &Document, id: InterfaceId) -> Result<()> { - let interface = &doc.interfaces[id]; + fn print_interface(&mut self, resolve: &Resolve, id: InterfaceId) -> Result<()> { + let interface = &resolve.interfaces[id]; // Partition types defined in this interface into either those imported // from foreign interfaces or those defined locally. let mut types_to_declare = Vec::new(); let mut types_to_import = IndexMap::new(); - for ty_id in &interface.types { - let ty = &doc.types[*ty_id]; - let name = ty - .name - .as_ref() - .ok_or_else(|| anyhow!("unnamed type exported from interface"))?; + for (name, ty_id) in &interface.types { + let ty = &resolve.types[*ty_id]; if let TypeDefKind::Type(Type::Id(other)) = ty.kind { - let other = &doc.types[other]; - if let Some(other_iface) = other.interface { + let other = &resolve.types[other]; + if let TypeOwner::Interface(other_iface) = other.owner { if other_iface != id { let other_name = other .name @@ -100,8 +68,11 @@ impl DocumentPrinter { } // Generate a `use` statement for all imported types. + let my_doc = resolve.interfaces[id].document; for (id, tys) in types_to_import { - write!(&mut self.output, "use {{ ")?; + write!(&mut self.output, "use ")?; + self.print_path_to_interface(resolve, id, my_doc)?; + write!(&mut self.output, ".{{")?; for (i, (my_name, other_name)) in tys.into_iter().enumerate() { if i > 0 { write!(&mut self.output, ", ")?; @@ -112,13 +83,12 @@ impl DocumentPrinter { write!(&mut self.output, "{other_name} as {my_name}")?; } } - let iface_name = &self.iface_names[id.index()]; - writeln!(&mut self.output, " }} from {iface_name}")?; + writeln!(&mut self.output, "}}")?; } // Declare all local types for id in types_to_declare { - self.declare_type(doc, &Type::Id(id))?; + self.declare_type(resolve, &Type::Id(id))?; } for (i, func) in interface.functions.iter().enumerate() { @@ -131,7 +101,7 @@ impl DocumentPrinter { self.output.push_str(", "); } write!(&mut self.output, "{}: ", name)?; - self.print_type_name(doc, ty)?; + self.print_type_name(resolve, ty)?; } self.output.push_str(")"); @@ -140,7 +110,7 @@ impl DocumentPrinter { 0 => (), 1 => { self.output.push_str(" -> "); - self.print_type_name(doc, &rs[0].1)?; + self.print_type_name(resolve, &rs[0].1)?; } _ => { self.output.push_str(" -> ("); @@ -149,14 +119,14 @@ impl DocumentPrinter { self.output.push_str(", "); } write!(&mut self.output, "{name}: ")?; - self.print_type_name(doc, ty)?; + self.print_type_name(resolve, ty)?; } self.output.push_str(")"); } }, Results::Anon(ty) => { self.output.push_str(" -> "); - self.print_type_name(doc, ty)?; + self.print_type_name(resolve, ty)?; } } @@ -166,7 +136,56 @@ impl DocumentPrinter { Ok(()) } - fn print_type_name(&mut self, doc: &Document, ty: &Type) -> Result<()> { + fn print_world_item( + &mut self, + resolve: &Resolve, + name: &str, + item: &WorldItem, + cur_doc: DocumentId, + desc: &str, + ) -> Result<()> { + write!(&mut self.output, "{desc} {name}: ")?; + match item { + WorldItem::Interface(id) => { + if resolve.interfaces[*id].name.is_some() { + self.print_path_to_interface(resolve, *id, cur_doc)?; + self.output.push_str("\n"); + } else { + writeln!(self.output, "{desc} {name}: interface {{")?; + self.print_interface(resolve, *id)?; + writeln!(self.output, "}}")?; + } + } + WorldItem::Function(f) => { + drop(f); + panic!(); + } + } + Ok(()) + } + + fn print_path_to_interface( + &mut self, + resolve: &Resolve, + interface: InterfaceId, + cur_doc: DocumentId, + ) -> Result<()> { + let cur_pkg = resolve.documents[cur_doc].package; + let iface = &resolve.interfaces[interface]; + let iface_doc = &resolve.documents[iface.document]; + if iface.document == cur_doc { + write!(&mut self.output, "self")?; + } else if cur_pkg == iface_doc.package { + write!(&mut self.output, "pkg.{}", iface_doc.name)?; + } else { + let iface_pkg = &resolve.packages[iface_doc.package.unwrap()]; + write!(&mut self.output, "{}.{}", iface_pkg.name, iface_doc.name)?; + } + write!(&mut self.output, ".{}", iface.name.as_ref().unwrap())?; + Ok(()) + } + + fn print_type_name(&mut self, resolve: &Resolve, ty: &Type) -> Result<()> { match ty { Type::Bool => self.output.push_str("bool"), Type::U8 => self.output.push_str("u8"), @@ -183,7 +202,7 @@ impl DocumentPrinter { Type::String => self.output.push_str("string"), Type::Id(id) => { - let ty = &doc.types[*id]; + let ty = &resolve.types[*id]; if let Some(name) = &ty.name { self.output.push_str(name); return Ok(()); @@ -191,41 +210,42 @@ impl DocumentPrinter { match &ty.kind { TypeDefKind::Tuple(t) => { - self.print_tuple_type(doc, t)?; + self.print_tuple_type(resolve, t)?; } TypeDefKind::Option(t) => { - self.print_option_type(doc, t)?; + self.print_option_type(resolve, t)?; } TypeDefKind::Result(t) => { - self.print_result_type(doc, t)?; + self.print_result_type(resolve, t)?; } TypeDefKind::Record(_) => { - bail!("doc has an unnamed record type"); + bail!("resolve has an unnamed record type"); } TypeDefKind::Flags(_) => { - bail!("doc has unnamed flags type") + bail!("resolve has unnamed flags type") } TypeDefKind::Enum(_) => { - bail!("doc has unnamed enum type") + bail!("resolve has unnamed enum type") } TypeDefKind::Variant(_) => { - bail!("doc has unnamed variant type") + bail!("resolve has unnamed variant type") } TypeDefKind::Union(_) => { bail!("document has unnamed union type") } TypeDefKind::List(ty) => { self.output.push_str("list<"); - self.print_type_name(doc, ty)?; + self.print_type_name(resolve, ty)?; self.output.push_str(">"); } - TypeDefKind::Type(ty) => self.print_type_name(doc, ty)?, + TypeDefKind::Type(ty) => self.print_type_name(resolve, ty)?, TypeDefKind::Future(_) => { todo!("document has an unnamed future type") } TypeDefKind::Stream(_) => { todo!("document has an unnamed stream type") } + TypeDefKind::Unknown => unreachable!(), } } } @@ -233,36 +253,36 @@ impl DocumentPrinter { Ok(()) } - fn print_tuple_type(&mut self, doc: &Document, tuple: &Tuple) -> Result<()> { + fn print_tuple_type(&mut self, resolve: &Resolve, tuple: &Tuple) -> Result<()> { self.output.push_str("tuple<"); for (i, ty) in tuple.types.iter().enumerate() { if i > 0 { self.output.push_str(", "); } - self.print_type_name(doc, ty)?; + self.print_type_name(resolve, ty)?; } self.output.push_str(">"); Ok(()) } - fn print_option_type(&mut self, doc: &Document, payload: &Type) -> Result<()> { + fn print_option_type(&mut self, resolve: &Resolve, payload: &Type) -> Result<()> { self.output.push_str("option<"); - self.print_type_name(doc, payload)?; + self.print_type_name(resolve, payload)?; self.output.push_str(">"); Ok(()) } - fn print_result_type(&mut self, doc: &Document, result: &Result_) -> Result<()> { + fn print_result_type(&mut self, resolve: &Resolve, result: &Result_) -> Result<()> { match result { Result_ { ok: Some(ok), err: Some(err), } => { self.output.push_str("result<"); - self.print_type_name(doc, ok)?; + self.print_type_name(resolve, ok)?; self.output.push_str(", "); - self.print_type_name(doc, err)?; + self.print_type_name(resolve, err)?; self.output.push_str(">"); } Result_ { @@ -270,7 +290,7 @@ impl DocumentPrinter { err: Some(err), } => { self.output.push_str("result<_, "); - self.print_type_name(doc, err)?; + self.print_type_name(resolve, err)?; self.output.push_str(">"); } Result_ { @@ -278,7 +298,7 @@ impl DocumentPrinter { err: None, } => { self.output.push_str("result<"); - self.print_type_name(doc, ok)?; + self.print_type_name(resolve, ok)?; self.output.push_str(">"); } Result_ { @@ -291,7 +311,7 @@ impl DocumentPrinter { Ok(()) } - fn declare_type(&mut self, doc: &Document, ty: &Type) -> Result<()> { + fn declare_type(&mut self, resolve: &Resolve, ty: &Type) -> Result<()> { match ty { Type::Bool | Type::U8 @@ -312,29 +332,38 @@ impl DocumentPrinter { return Ok(()); } - let ty = &doc.types[*id]; + let ty = &resolve.types[*id]; match &ty.kind { - TypeDefKind::Record(r) => self.declare_record(doc, ty.name.as_deref(), r)?, - TypeDefKind::Tuple(t) => self.declare_tuple(doc, ty.name.as_deref(), t)?, + TypeDefKind::Record(r) => { + self.declare_record(resolve, ty.name.as_deref(), r)? + } + TypeDefKind::Tuple(t) => self.declare_tuple(resolve, ty.name.as_deref(), t)?, TypeDefKind::Flags(f) => self.declare_flags(ty.name.as_deref(), f)?, - TypeDefKind::Variant(v) => self.declare_variant(doc, ty.name.as_deref(), v)?, - TypeDefKind::Union(u) => self.declare_union(doc, ty.name.as_deref(), u)?, - TypeDefKind::Option(t) => self.declare_option(doc, ty.name.as_deref(), t)?, - TypeDefKind::Result(r) => self.declare_result(doc, ty.name.as_deref(), r)?, + TypeDefKind::Variant(v) => { + self.declare_variant(resolve, ty.name.as_deref(), v)? + } + TypeDefKind::Union(u) => self.declare_union(resolve, ty.name.as_deref(), u)?, + TypeDefKind::Option(t) => { + self.declare_option(resolve, ty.name.as_deref(), t)? + } + TypeDefKind::Result(r) => { + self.declare_result(resolve, ty.name.as_deref(), r)? + } TypeDefKind::Enum(e) => self.declare_enum(ty.name.as_deref(), e)?, TypeDefKind::List(inner) => { - self.declare_list(doc, ty.name.as_deref(), inner)? + self.declare_list(resolve, ty.name.as_deref(), inner)? } TypeDefKind::Type(inner) => match ty.name.as_deref() { Some(name) => { write!(&mut self.output, "type {} = ", name)?; - self.print_type_name(doc, inner)?; + self.print_type_name(resolve, inner)?; self.output.push_str("\n\n"); } None => bail!("unnamed type in document"), }, TypeDefKind::Future(_) => todo!("declare future"), TypeDefKind::Stream(_) => todo!("declare stream"), + TypeDefKind::Unknown => unreachable!(), } } } @@ -343,12 +372,12 @@ impl DocumentPrinter { fn declare_record( &mut self, - doc: &Document, + resolve: &Resolve, name: Option<&str>, record: &Record, ) -> Result<()> { for field in record.fields.iter() { - self.declare_type(doc, &field.ty)?; + self.declare_type(resolve, &field.ty)?; } match name { @@ -356,8 +385,8 @@ impl DocumentPrinter { writeln!(&mut self.output, "record {} {{", name)?; for field in &record.fields { write!(&mut self.output, "{}: ", field.name)?; - self.declare_type(doc, &field.ty)?; - self.print_type_name(doc, &field.ty)?; + self.declare_type(resolve, &field.ty)?; + self.print_type_name(resolve, &field.ty)?; self.output.push_str(",\n"); } self.output.push_str("}\n\n"); @@ -367,14 +396,19 @@ impl DocumentPrinter { } } - fn declare_tuple(&mut self, doc: &Document, name: Option<&str>, tuple: &Tuple) -> Result<()> { + fn declare_tuple( + &mut self, + resolve: &Resolve, + name: Option<&str>, + tuple: &Tuple, + ) -> Result<()> { for ty in tuple.types.iter() { - self.declare_type(doc, ty)?; + self.declare_type(resolve, ty)?; } if let Some(name) = name { write!(&mut self.output, "type {} = ", name)?; - self.print_tuple_type(doc, tuple)?; + self.print_tuple_type(resolve, tuple)?; self.output.push_str("\n\n"); } Ok(()) @@ -396,13 +430,13 @@ impl DocumentPrinter { fn declare_variant( &mut self, - doc: &Document, + resolve: &Resolve, name: Option<&str>, variant: &Variant, ) -> Result<()> { for case in variant.cases.iter() { if let Some(ty) = case.ty { - self.declare_type(doc, &ty)?; + self.declare_type(resolve, &ty)?; } } @@ -415,7 +449,7 @@ impl DocumentPrinter { write!(&mut self.output, "{}", case.name)?; if let Some(ty) = case.ty { self.output.push_str("("); - self.print_type_name(doc, &ty)?; + self.print_type_name(resolve, &ty)?; self.output.push_str(")"); } self.output.push_str(",\n"); @@ -424,9 +458,14 @@ impl DocumentPrinter { Ok(()) } - fn declare_union(&mut self, doc: &Document, name: Option<&str>, union: &Union) -> Result<()> { + fn declare_union( + &mut self, + resolve: &Resolve, + name: Option<&str>, + union: &Union, + ) -> Result<()> { for case in union.cases.iter() { - self.declare_type(doc, &case.ty)?; + self.declare_type(resolve, &case.ty)?; } let name = match name { @@ -436,19 +475,24 @@ impl DocumentPrinter { writeln!(&mut self.output, "union {} {{", name)?; for case in &union.cases { self.output.push_str(""); - self.print_type_name(doc, &case.ty)?; + self.print_type_name(resolve, &case.ty)?; self.output.push_str(",\n"); } self.output.push_str("}\n\n"); Ok(()) } - fn declare_option(&mut self, doc: &Document, name: Option<&str>, payload: &Type) -> Result<()> { - self.declare_type(doc, payload)?; + fn declare_option( + &mut self, + resolve: &Resolve, + name: Option<&str>, + payload: &Type, + ) -> Result<()> { + self.declare_type(resolve, payload)?; if let Some(name) = name { write!(&mut self.output, "type {} = ", name)?; - self.print_option_type(doc, payload)?; + self.print_option_type(resolve, payload)?; self.output.push_str("\n\n"); } Ok(()) @@ -456,20 +500,20 @@ impl DocumentPrinter { fn declare_result( &mut self, - doc: &Document, + resolve: &Resolve, name: Option<&str>, result: &Result_, ) -> Result<()> { if let Some(ok) = result.ok { - self.declare_type(doc, &ok)?; + self.declare_type(resolve, &ok)?; } if let Some(err) = result.err { - self.declare_type(doc, &err)?; + self.declare_type(resolve, &err)?; } if let Some(name) = name { write!(&mut self.output, "type {} = ", name)?; - self.print_result_type(doc, result)?; + self.print_result_type(resolve, result)?; self.output.push_str("\n\n"); } Ok(()) @@ -488,12 +532,12 @@ impl DocumentPrinter { Ok(()) } - fn declare_list(&mut self, doc: &Document, name: Option<&str>, ty: &Type) -> Result<()> { - self.declare_type(doc, ty)?; + fn declare_list(&mut self, resolve: &Resolve, name: Option<&str>, ty: &Type) -> Result<()> { + self.declare_type(resolve, ty)?; if let Some(name) = name { write!(&mut self.output, "type {} = list<", name)?; - self.print_type_name(doc, ty)?; + self.print_type_name(resolve, ty)?; self.output.push_str(">\n\n"); return Ok(()); } diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 4882f8dee7..c0a5dc6d19 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -73,7 +73,7 @@ impl<'a> Resolver<'a> { Ok(()) } - pub(crate) fn resolve(&mut self) -> Result { + pub(crate) fn resolve(&mut self, name: &str) -> Result { self.populate_foreign_deps(); // Determine the dependencies between documents in the current package @@ -111,6 +111,7 @@ impl<'a> Resolver<'a> { } Ok(UnresolvedPackage { + name: name.to_string(), worlds: mem::take(&mut self.worlds), types: mem::take(&mut self.types), interfaces: mem::take(&mut self.interfaces), @@ -166,6 +167,7 @@ impl<'a> Resolver<'a> { default_world: None, interfaces: IndexMap::new(), worlds: Vec::new(), + package: None, }) }); @@ -320,6 +322,7 @@ impl<'a> Resolver<'a> { default_world: None, interfaces: IndexMap::new(), worlds: Vec::new(), + package: None, }); self.document_interfaces.push(IndexMap::new()); self.document_lookup.insert(name, document_id); diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 7c08a71652..ec48859e08 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use id_arena::{Arena, Id}; use indexmap::IndexMap; use std::borrow::Cow; @@ -45,8 +45,11 @@ pub type DocumentId = Id; /// `Resolve` to fully work with a WIT package. // // TODO: implement and add docs about converting to a `Resolve` -#[derive(Default, Clone)] +#[derive(Clone)] pub struct UnresolvedPackage { + /// Local name for this package. + pub name: String, + /// All worlds from all documents within this package. /// /// Each world lists the document that it is from. @@ -113,8 +116,12 @@ impl UnresolvedPackage { /// will not be able to use `pkg` use paths to other documents. pub fn parse(path: &Path, contents: &str) -> Result { let mut map = SourceMap::default(); + let name = path + .file_stem() + .and_then(|s| s.to_str()) + .ok_or_else(|| anyhow!("path doesn't end in a valid package name {path:?}"))?; map.push(path, contents); - Self::_parse(map) + Self::_parse(name, map) } /// Parse a WIT package at the provided path. @@ -146,6 +153,10 @@ impl UnresolvedPackage { /// `path` into the returned package. pub fn parse_dir(path: &Path) -> Result { let mut map = SourceMap::default(); + let name = path + .file_name() + .and_then(|s| s.to_str()) + .ok_or_else(|| anyhow!("path doesn't end in a valid package name {path:?}"))?; let cx = || format!("failed to read directory {path:?}"); for entry in path.read_dir().with_context(&cx)? { let entry = entry.with_context(&cx)?; @@ -170,10 +181,10 @@ impl UnresolvedPackage { .with_context(|| format!("failed to read file {path:?}"))?; map.push(&path, contents); } - Self::_parse(map) + Self::_parse(name, map) } - fn _parse(map: SourceMap) -> Result { + fn _parse(name: &str, map: SourceMap) -> Result { let mut doc = map.rewrite_error(|| { let mut resolver = Resolver::default(); for file in map.tokenizers() { @@ -181,7 +192,7 @@ impl UnresolvedPackage { let ast = Ast::parse(&mut tokens)?; resolver.push(path, ast)?; } - resolver.resolve() + resolver.resolve(name) })?; doc.source_map = map; Ok(doc) @@ -211,6 +222,9 @@ pub struct Document { /// /// This will also be listed in `self.worlds`. pub default_world: Option, + + /// The package that this document belongs to. + pub package: Option, } #[derive(Debug, Clone)] diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 63a4c10b27..634d1a94ef 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -35,6 +35,9 @@ pub struct Resolve { #[derive(Clone)] pub struct Package { + /// Locally-known name of this package. + pub name: String, + /// Documents contained within this package, organized by name. pub documents: IndexMap, } @@ -309,8 +312,14 @@ impl Remap { let prev = documents.insert(resolve.documents[*id].name.clone(), *id); assert!(prev.is_none()); } - - Ok(resolve.packages.alloc(Package { documents })) + let pkgid = resolve.packages.alloc(Package { + name: unresolved.name, + documents, + }); + for (_, id) in resolve.packages[pkgid].documents.iter() { + resolve.documents[*id].package = Some(pkgid); + } + Ok(pkgid) } fn process_foreign_deps( @@ -573,6 +582,7 @@ impl Remap { if let Some(default) = &mut doc.default_world { *default = self.worlds[default.index()]; } + assert!(doc.package.is_none()); } } diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index b604489839..a6f1328e5e 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -1,23 +1,24 @@ //! The WebAssembly component tool command line interface. -use anyhow::{Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use clap::Parser; use std::path::{Path, PathBuf}; use wasm_tools::Output; -use wit_component::{decode_world, ComponentEncoder, DocumentPrinter, StringEncoding}; +// use wit_component::{decode_world, ComponentEncoder, DocumentPrinter, StringEncoding}; +use wit_component::{DecodedWasm, DocumentPrinter}; use wit_parser::Document; /// WebAssembly wit-based component tooling. #[derive(Parser)] pub enum Opts { - New(NewOpts), + // New(NewOpts), Wit(WitOpts), } impl Opts { pub fn run(self) -> Result<()> { match self { - Opts::New(new) => new.run(), + // Opts::New(new) => new.run(), Opts::Wit(wit) => wit.run(), } } @@ -45,102 +46,102 @@ fn parse_adapter(s: &str) -> Result<(String, Vec)> { Ok((name.to_string(), wasm)) } -/// WebAssembly component encoder from an input core wasm binary. -/// -/// This subcommand will create a new component `*.wasm` file from an input core -/// wasm binary. The input core wasm binary is expected to be compiled with -/// `wit-component` or derivative projects which encodes component-based type -/// information into the input core wasm binary's custom sections. The `--wit` -/// option can also be used to specify the interface manually too. -#[derive(Parser)] -pub struct NewOpts { - /// The path to an adapter module to satisfy imports. - /// - /// An adapter module can be used to translate the `wasi_snapshot_preview1` - /// ABI, for example, to one that uses the component model. The first - /// `[NAME=]` specified in the argument is inferred from the name of file - /// specified by `MODULE` if not present and is the name of the import - /// module that's being implemented (e.g. `wasi_snapshot_preview1.wasm`. - /// - /// The second part of this argument, optionally specified, is the interface - /// that this adapter module imports. If not specified then the interface - /// imported is inferred from the adapter module itself. - #[clap(long = "adapt", value_name = "[NAME=]MODULE", value_parser = parse_adapter)] - adapters: Vec<(String, Vec)>, - - #[clap(flatten)] - io: wasm_tools::InputOutput, - - /// The "world" that the input binary implements. - /// - /// This argument is a `*.wit` file which describes the imports and exports - /// of the core wasm module. Users of `wit-bindgen` don't need this as those - /// generators already embed this information into the input core wasm - /// binary. - #[clap(long, value_name = "PATH")] - wit: Option, - - /// Skip validation of the output component. - #[clap(long)] - skip_validation: bool, - - /// The expected string encoding format for the component. - /// - /// Supported values are: `utf8` (default), `utf16`, and `compact-utf16`. - /// This is only applicable to the `--wit` argument to describe the string - /// encoding of the functions in that world. - #[clap(long, value_name = "ENCODING")] - encoding: Option, - - /// Print the output in the WebAssembly text format instead of binary. - #[clap(long, short = 't')] - wat: bool, - - /// Generate a "types only" component which is a binary encoding of the - /// input wit file or the wit already encoded into the module. - #[clap(long)] - types_only: bool, -} - -impl NewOpts { - /// Executes the application. - fn run(self) -> Result<()> { - let wasm = if self.types_only { - self.io.init_logger(); - None - } else { - Some(self.io.parse_input_wasm()?) - }; - let mut encoder = ComponentEncoder::default() - .validate(!self.skip_validation) - .types_only(self.types_only); - - if let Some(wasm) = wasm { - encoder = encoder.module(&wasm)?; - } - - if let Some(wit) = &self.wit { - let encoding = self.encoding.unwrap_or(StringEncoding::UTF8); - let doc = Document::parse_file(wit)?; - encoder = encoder.document(doc, encoding)?; - } - - for (name, wasm) in self.adapters.iter() { - encoder = encoder.adapter(name, wasm)?; - } - - let bytes = encoder - .encode() - .with_context(|| format!("failed to encode a component from module ",))?; - - self.io.output(Output::Wasm { - bytes: &bytes, - wat: self.wat, - })?; - - Ok(()) - } -} +///// WebAssembly component encoder from an input core wasm binary. +///// +///// This subcommand will create a new component `*.wasm` file from an input core +///// wasm binary. The input core wasm binary is expected to be compiled with +///// `wit-component` or derivative projects which encodes component-based type +///// information into the input core wasm binary's custom sections. The `--wit` +///// option can also be used to specify the interface manually too. +//#[derive(Parser)] +//pub struct NewOpts { +// /// The path to an adapter module to satisfy imports. +// /// +// /// An adapter module can be used to translate the `wasi_snapshot_preview1` +// /// ABI, for example, to one that uses the component model. The first +// /// `[NAME=]` specified in the argument is inferred from the name of file +// /// specified by `MODULE` if not present and is the name of the import +// /// module that's being implemented (e.g. `wasi_snapshot_preview1.wasm`. +// /// +// /// The second part of this argument, optionally specified, is the interface +// /// that this adapter module imports. If not specified then the interface +// /// imported is inferred from the adapter module itself. +// #[clap(long = "adapt", value_name = "[NAME=]MODULE", value_parser = parse_adapter)] +// adapters: Vec<(String, Vec)>, + +// #[clap(flatten)] +// io: wasm_tools::InputOutput, + +// /// The "world" that the input binary implements. +// /// +// /// This argument is a `*.wit` file which describes the imports and exports +// /// of the core wasm module. Users of `wit-bindgen` don't need this as those +// /// generators already embed this information into the input core wasm +// /// binary. +// #[clap(long, value_name = "PATH")] +// wit: Option, + +// /// Skip validation of the output component. +// #[clap(long)] +// skip_validation: bool, + +// /// The expected string encoding format for the component. +// /// +// /// Supported values are: `utf8` (default), `utf16`, and `compact-utf16`. +// /// This is only applicable to the `--wit` argument to describe the string +// /// encoding of the functions in that world. +// #[clap(long, value_name = "ENCODING")] +// encoding: Option, + +// /// Print the output in the WebAssembly text format instead of binary. +// #[clap(long, short = 't')] +// wat: bool, + +// /// Generate a "types only" component which is a binary encoding of the +// /// input wit file or the wit already encoded into the module. +// #[clap(long)] +// types_only: bool, +//} + +//impl NewOpts { +// /// Executes the application. +// fn run(self) -> Result<()> { +// let wasm = if self.types_only { +// self.io.init_logger(); +// None +// } else { +// Some(self.io.parse_input_wasm()?) +// }; +// let mut encoder = ComponentEncoder::default() +// .validate(!self.skip_validation) +// .types_only(self.types_only); + +// if let Some(wasm) = wasm { +// encoder = encoder.module(&wasm)?; +// } + +// if let Some(wit) = &self.wit { +// let encoding = self.encoding.unwrap_or(StringEncoding::UTF8); +// let doc = Document::parse_file(wit)?; +// encoder = encoder.document(doc, encoding)?; +// } + +// for (name, wasm) in self.adapters.iter() { +// encoder = encoder.adapter(name, wasm)?; +// } + +// let bytes = encoder +// .encode() +// .with_context(|| format!("failed to encode a component from module ",))?; + +// self.io.output(Output::Wasm { +// bytes: &bytes, +// wat: self.wat, +// })?; + +// Ok(()) +// } +//} /// WebAssembly interface printer. /// @@ -154,6 +155,10 @@ pub struct WitOpts { /// filename. #[clap(long)] name: Option, + + /// TODO + #[clap(short, long)] + document: Option, } impl WitOpts { @@ -168,9 +173,29 @@ impl WitOpts { }, }; - let (doc, _world) = decode_world(name, &bytes).context("failed to decode world")?; let mut printer = DocumentPrinter::default(); - let output = printer.print(&doc)?; + let output = match wit_component::decode(name, &bytes).context("failed to decode world")? { + DecodedWasm::WitPackage(resolve, package) => { + let pkg = &resolve.packages[package]; + let doc = match &self.document { + Some(name) => *pkg + .documents + .get(name) + .ok_or_else(|| anyhow!("no document named `{name}` found in package"))?, + None => match pkg.documents.len() { + 1 => *pkg.documents.iter().next().unwrap().1, + _ => bail!( + "more than document found in package, specify \ + which to print with `-d name`" + ), + }, + }; + + printer.print(&resolve, doc)? + } + // TODO + DecodedWasm::Component(..) => panic!(), + }; self.io.output(Output::Wat(&output))?; Ok(()) From 21c2513de54650212a82dbdb6e9382e287cd2f75 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 5 Jan 2023 15:12:41 -0800 Subject: [PATCH 04/37] Implement scaffolding of `wasm-tools component wit` This commit implements the new `wasm-tools component wit` subcommand which is a swiss-army-knife of working with: * WIT textual documents as a single file-package * WIT textual packages as a directory * WIT packages encoded as a WebAssembly binary Inter-conversion between all of these should now be available via all the various flags and such on the CLI. This is all intended to help with debugging, inspecting, and otherwise working with the low-level parts of the component model. In other words this is trying to expose as much functionality as possible implemented as libraries through a CLI for when it's needed as a CLI is more quickly accessible than writing a custom wrapper. --- Cargo.toml | 3 +- crates/wit-component/src/encoding.rs | 2485 +++++++++++----------- crates/wit-component/src/encoding/wit.rs | 7 + crates/wit-component/src/lib.rs | 4 +- src/bin/wasm-tools/component.rs | 280 ++- src/lib.rs | 4 + 6 files changed, 1495 insertions(+), 1288 deletions(-) create mode 100644 crates/wit-component/src/encoding/wit.rs diff --git a/Cargo.toml b/Cargo.toml index 599a6bc537..e43ef2db78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,6 +101,7 @@ cpp_demangle = { version = "0.4.0", optional = true } # Dependencies of `component` wit-component = { workspace = true, optional = true } wit-parser = { workspace = true, optional = true } +wast = { workspace = true, optional = true } [dev-dependencies] serde_json = "1.0" @@ -146,4 +147,4 @@ objdump = ['wasmparser'] strip = ['wasm-encoder', 'wasmparser', 'regex'] compose = ['wasm-compose'] demangle = ['rustc-demangle', 'cpp_demangle', 'wasmparser', 'wasm-encoder'] -component = ['wit-component', 'wit-parser'] +component = ['wit-component', 'wit-parser', 'wast'] diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index a012680f46..b408936041 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -52,1244 +52,1247 @@ //! otherwise there's no way to run a `wasi_snapshot_preview1` module within the //! component model. -use crate::builder::ComponentBuilder; -use crate::metadata::{self, Bindgen, ModuleMetadata}; -use crate::{ - validation::{ValidatedModule, MAIN_MODULE_IMPORT_NAME}, - StringEncoding, -}; -use anyhow::{anyhow, bail, Context, Result}; -use indexmap::IndexMap; -use std::collections::HashMap; -use std::hash::Hash; -use wasm_encoder::*; -use wasmparser::{Validator, WasmFeatures}; -use wit_parser::{ - abi::{AbiVariant, WasmSignature, WasmType}, - Document, Function, InterfaceId, Type, TypeDefKind, TypeId, WorldId, -}; - -const INDIRECT_TABLE_NAME: &str = "$imports"; - -mod types; -use types::{InstanceTypeEncoder, RootTypeEncoder, ValtypeEncoder}; -mod world; -use world::{ComponentWorld, ImportedInterface}; - -fn to_val_type(ty: &WasmType) -> ValType { - match ty { - WasmType::I32 => ValType::I32, - WasmType::I64 => ValType::I64, - WasmType::F32 => ValType::F32, - WasmType::F64 => ValType::F64, - } -} - -bitflags::bitflags! { - /// Options in the `canon lower` or `canon lift` required for a particular - /// function. - pub struct RequiredOptions: u8 { - /// A memory must be specified, typically the "main module"'s memory - /// export. - const MEMORY = 1 << 0; - /// A `realloc` function must be specified, typically named - /// `cabi_realloc`. - const REALLOC = 1 << 1; - /// A string encoding must be specified, which is always utf-8 for now - /// today. - const STRING_ENCODING = 1 << 2; - } -} - -impl RequiredOptions { - fn for_import(doc: &Document, func: &Function) -> RequiredOptions { - let sig = doc.wasm_signature(AbiVariant::GuestImport, func); - let mut ret = RequiredOptions::empty(); - // Lift the params and lower the results for imports - ret.add_lift(TypeContents::for_types( - doc, - func.params.iter().map(|(_, t)| t), - )); - ret.add_lower(TypeContents::for_types(doc, func.results.iter_types())); - - // If anything is indirect then `memory` will be required to read the - // indirect values. - if sig.retptr || sig.indirect_params { - ret |= RequiredOptions::MEMORY; - } - ret - } - - fn for_export(doc: &Document, func: &Function) -> RequiredOptions { - let sig = doc.wasm_signature(AbiVariant::GuestExport, func); - let mut ret = RequiredOptions::empty(); - // Lower the params and lift the results for exports - ret.add_lower(TypeContents::for_types( - doc, - func.params.iter().map(|(_, t)| t), - )); - ret.add_lift(TypeContents::for_types(doc, func.results.iter_types())); - - // If anything is indirect then `memory` will be required to read the - // indirect values, but if the arguments are indirect then `realloc` is - // additionally required to allocate space for the parameters. - if sig.retptr || sig.indirect_params { - ret |= RequiredOptions::MEMORY; - if sig.indirect_params { - ret |= RequiredOptions::REALLOC; - } - } - ret - } - - fn add_lower(&mut self, types: TypeContents) { - // If lists/strings are lowered into wasm then memory is required as - // usual but `realloc` is also required to allow the external caller to - // allocate space in the destination for the list/string. - if types.contains(TypeContents::LIST) { - *self |= RequiredOptions::MEMORY | RequiredOptions::REALLOC; - } - if types.contains(TypeContents::STRING) { - *self |= RequiredOptions::MEMORY - | RequiredOptions::STRING_ENCODING - | RequiredOptions::REALLOC; - } - } - - fn add_lift(&mut self, types: TypeContents) { - // Unlike for `lower` when lifting a string/list all that's needed is - // memory, since the string/list already resides in memory `realloc` - // isn't needed. - if types.contains(TypeContents::LIST) { - *self |= RequiredOptions::MEMORY; - } - if types.contains(TypeContents::STRING) { - *self |= RequiredOptions::MEMORY | RequiredOptions::STRING_ENCODING; - } - } - - fn into_iter( - self, - encoding: StringEncoding, - memory_index: Option, - realloc_index: Option, - ) -> Result> { - #[derive(Default)] - struct Iter { - options: [Option; 3], - current: usize, - count: usize, - } - - impl Iter { - fn push(&mut self, option: CanonicalOption) { - assert!(self.count < self.options.len()); - self.options[self.count] = Some(option); - self.count += 1; - } - } - - impl Iterator for Iter { - type Item = CanonicalOption; - - fn next(&mut self) -> Option { - if self.current == self.count { - return None; - } - let option = self.options[self.current]; - self.current += 1; - option - } - - fn size_hint(&self) -> (usize, Option) { - (self.count - self.current, Some(self.count - self.current)) - } - } - - impl ExactSizeIterator for Iter {} - - let mut iter = Iter::default(); - - if self.contains(RequiredOptions::MEMORY) { - iter.push(CanonicalOption::Memory(memory_index.ok_or_else(|| { - anyhow!("module does not export a memory named `memory`") - })?)); - } - - if self.contains(RequiredOptions::REALLOC) { - iter.push(CanonicalOption::Realloc(realloc_index.ok_or_else( - || anyhow!("module does not export a function named `cabi_realloc`"), - )?)); - } - - if self.contains(RequiredOptions::STRING_ENCODING) { - iter.push(encoding.into()); - } - - Ok(iter) - } -} - -bitflags::bitflags! { - /// Flags about what kinds of types are present within the recursive - /// structure of a type. - struct TypeContents: u8 { - const STRING = 1 << 0; - const LIST = 1 << 1; - } -} - -impl TypeContents { - fn for_types<'a>(doc: &Document, types: impl Iterator) -> Self { - let mut cur = TypeContents::empty(); - for ty in types { - cur |= Self::for_type(doc, ty); - } - cur - } - - fn for_optional_types<'a>( - doc: &Document, - types: impl Iterator>, - ) -> Self { - Self::for_types(doc, types.flatten()) - } - - fn for_optional_type(doc: &Document, ty: Option<&Type>) -> Self { - match ty { - Some(ty) => Self::for_type(doc, ty), - None => Self::empty(), - } - } - - fn for_type(doc: &Document, ty: &Type) -> Self { - match ty { - Type::Id(id) => match &doc.types[*id].kind { - TypeDefKind::Record(r) => Self::for_types(doc, r.fields.iter().map(|f| &f.ty)), - TypeDefKind::Tuple(t) => Self::for_types(doc, t.types.iter()), - TypeDefKind::Flags(_) => Self::empty(), - TypeDefKind::Option(t) => Self::for_type(doc, t), - TypeDefKind::Result(r) => { - Self::for_optional_type(doc, r.ok.as_ref()) - | Self::for_optional_type(doc, r.err.as_ref()) - } - TypeDefKind::Variant(v) => { - Self::for_optional_types(doc, v.cases.iter().map(|c| c.ty.as_ref())) - } - TypeDefKind::Union(v) => Self::for_types(doc, v.cases.iter().map(|c| &c.ty)), - TypeDefKind::Enum(_) => Self::empty(), - TypeDefKind::List(t) => Self::for_type(doc, t) | Self::LIST, - TypeDefKind::Type(t) => Self::for_type(doc, t), - TypeDefKind::Future(_) => todo!("encoding for future"), - TypeDefKind::Stream(_) => todo!("encoding for stream"), - }, - Type::String => Self::STRING, - _ => Self::empty(), - } - } -} - -/// State relating to encoding a component. -pub struct EncodingState<'a> { - /// The component being encoded. - component: ComponentBuilder, - /// The index into the core module index space for the inner core module. - /// - /// If `None`, the core module has not been encoded. - module_index: Option, - /// The index into the core instance index space for the inner core module. - /// - /// If `None`, the core module has not been instantiated. - instance_index: Option, - /// The index in the core memory index space for the exported memory. - /// - /// If `None`, then the memory has not yet been aliased. - memory_index: Option, - /// The index in the core function index space for the realloc function. - /// - /// If `None`, then the realloc function has not yet been aliased. - realloc_index: Option, - /// The index of the shim instance used for lowering imports into the core instance. - /// - /// If `None`, then the shim instance how not yet been encoded. - shim_instance_index: Option, - /// The index of the fixups module to instantiate to fill in the lowered imports. - /// - /// If `None`, then a fixup module has not yet been encoded. - fixups_module_index: Option, - - /// A map of named adapter modules and the index that the module was defined - /// at. - adapter_modules: IndexMap<&'a str, u32>, - /// A map of adapter module instances and the index of their instance. - adapter_instances: IndexMap<&'a str, u32>, - /// A map of the index of the aliased realloc function for each adapter - /// module. Note that adapters have two realloc functions, one for imports - /// and one for exports. - adapter_import_reallocs: IndexMap<&'a str, Option>, - adapter_export_reallocs: IndexMap<&'a str, Option>, - - /// Imported instances and what index they were imported as. - imported_instances: IndexMap, - - /// Map of types defined within the component's root index space. - type_map: HashMap, - /// Map of function types defined within the component's root index space. - func_type_map: HashMap, u32>, - - /// Metadata about the world inferred from the input to `ComponentEncoder`. - info: &'a ComponentWorld<'a>, -} - -impl<'a> EncodingState<'a> { - fn encode_core_modules(&mut self) { - assert!(self.module_index.is_none()); - let idx = self.component.core_module_raw(&self.info.encoder.module); - self.module_index = Some(idx); - - for (name, (_, wasm)) in self.info.adapters.iter() { - let idx = self.component.core_module_raw(wasm); - let prev = self.adapter_modules.insert(name, idx); - assert!(prev.is_none()); - } - } - - fn root_type_encoder(&mut self, interface: InterfaceId) -> RootTypeEncoder<'_, 'a> { - RootTypeEncoder { - state: self, - type_exports: Vec::new(), - interface, - } - } - - fn instance_type_encoder(&mut self, interface: InterfaceId) -> InstanceTypeEncoder<'_, 'a> { - InstanceTypeEncoder { - state: self, - interface, - type_map: Default::default(), - func_type_map: Default::default(), - ty: Default::default(), - } - } - - fn encode_imports(&mut self) -> Result<()> { - let doc = &self.info.encoder.metadata.doc; - for (name, info) in self.info.import_map.iter() { - let interface = &doc.interfaces[info.interface]; - log::trace!("encoding imports for `{name}` as {:?}", info.interface); - let ty = { - let mut encoder = self.instance_type_encoder(info.interface); - - // Encode all required functions from this imported interface - // into the instance type. - for func in interface.functions.iter() { - if !info.required.contains(func.name.as_str()) { - continue; - } - log::trace!("encoding function type for `{}`", func.name); - let idx = encoder.encode_func_type(doc, func)?; - - encoder - .ty - .export(&func.name, "", ComponentTypeRef::Func(idx)); - } - - // If there were any live types from this instance which weren't - // otherwise reached through the above function types then this - // will forward them through. - if let Some(live) = encoder.state.info.live_types.get(&info.interface) { - for ty in live { - log::trace!("encoding extra type {ty:?}"); - encoder.encode_valtype(doc, &Type::Id(*ty))?; - } - } - - encoder.ty - }; - - // Don't encode empty instance types since they're not - // meaningful to the runtime of the component anyway. - if ty.is_empty() { - continue; - } - let instance_type_idx = self.component.instance_type(&ty); - let instance_idx = self.component.import( - name, - &info.url, - ComponentTypeRef::Instance(instance_type_idx), - ); - let prev = self.imported_instances.insert(info.interface, instance_idx); - assert!(prev.is_none()); - } - Ok(()) - } - - fn index_of_type_export(&mut self, id: TypeId) -> u32 { - // Using the original `interface` definition of `id` and its name create - // an alias which refers to the type export of that instance which must - // have previously been imported. - let ty = &self.info.encoder.metadata.doc.types[id]; - let interface = ty - .interface - .expect("cannot import anonymous type across interfaces"); - let name = ty - .name - .as_ref() - .expect("cannot import anonymous type across interfaces"); - let instance = self.imported_instances[&interface]; - self.component.alias_type_export(instance, name) - } - - fn encode_core_instantiation(&mut self) -> Result<()> { - let info = self.info.info.as_ref().unwrap(); - // Encode a shim instantiation if needed - let shims = self.encode_shim_instantiation(); - - // For each instance import into the main module create a - // pseudo-core-wasm-module via a bag-of-exports. - let mut args = Vec::new(); - for name in info.required_imports.keys() { - let index = self.import_instance_to_lowered_core_instance( - CustomModule::Main, - *name, - &shims, - info.metadata, - ); - args.push((*name, ModuleArg::Instance(index))); - } - - // For each adapter module instance imported into the core wasm module - // the appropriate shim is packaged up into a bag-of-exports instance. - // Note that adapter modules currently don't deal with - // indirect-vs-direct lowerings, everything is indirect. - for (adapter, funcs) in info.adapters_required.iter() { - let shim_instance = self - .shim_instance_index - .expect("shim should be instantiated"); - let mut exports = Vec::new(); - - for (func, _ty) in funcs { - let index = self.component.alias_core_item( - shim_instance, - ExportKind::Func, - &shims.shim_names[&ShimKind::Adapter { adapter, func }], - ); - exports.push((*func, ExportKind::Func, index)); - } - - let index = self.component.instantiate_core_exports(exports); - args.push((*adapter, ModuleArg::Instance(index))); - } - - // Instantiate the main module now that all of its arguments have been - // prepared. With this we know have the main linear memory for - // liftings/lowerings later on as well as the adapter modules, if any, - // instantiated after the core wasm module. - self.instantiate_core_module(args, info); - self.instantiate_adapter_modules(&shims); - - // With all the core wasm instances in play now the original shim - // module, if present, can be filled in with lowerings/adapters/etc. - self.encode_indirect_lowerings(shims) - } - - /// Lowers a named imported interface a core wasm instances suitable to - /// provide as an instantiation argument to another core wasm module. - /// - /// * `for_module` the module that this instance is being created for, or - /// otherwise which `realloc` option is used for the lowerings. - /// * `name` - the name of the imported interface that's being lowered. - /// * `imports` - the list of all imports known for this encoding. - /// * `shims` - the indirect/adapter shims created prior, if any. - fn import_instance_to_lowered_core_instance( - &mut self, - for_module: CustomModule<'_>, - name: &str, - shims: &Shims<'_>, - metadata: &ModuleMetadata, - ) -> u32 { - let import = &self.info.import_map[name]; - let instance_index = self.imported_instances[&import.interface]; - let mut exports = Vec::with_capacity(import.direct.len() + import.indirect.len()); - - // Add an entry for all indirect lowerings which come as an export of - // the shim module. - for (i, lowering) in import.indirect.iter().enumerate() { - let encoding = - metadata.import_encodings[&(name.to_string(), lowering.name.to_string())]; - let index = self.component.alias_core_item( - self.shim_instance_index - .expect("shim should be instantiated"), - ExportKind::Func, - &shims.shim_names[&ShimKind::IndirectLowering { - interface: name, - indirect_index: i, - realloc: for_module, - encoding, - }], - ); - exports.push((lowering.name, ExportKind::Func, index)); - } - - // All direct lowerings can be `canon lower`'d here immediately and - // passed as arguments. - for lowering in &import.direct { - let func_index = self.component.alias_func(instance_index, lowering.name); - let core_func_index = self.component.lower_func(func_index, []); - exports.push((lowering.name, ExportKind::Func, core_func_index)); - } - - self.component.instantiate_core_exports(exports) - } - - fn encode_exports(&mut self, opts: &'a ComponentEncoder, module: CustomModule) -> Result<()> { - let doc = &opts.metadata.doc; - let metadata = match module { - CustomModule::Main => &opts.metadata.metadata, - CustomModule::Adapter(name) => &opts.adapters[name].1, - }; - let world = match module { - CustomModule::Main => opts.metadata.world, - CustomModule::Adapter(name) => opts.adapters[name].2, - }; - let world = &doc.worlds[world]; - for (export, export_name) in world.exports() { - let mut interface_exports = Vec::new(); - - // All types are encoded into the root component here using this - // encoder which keeps track of type exports to determine how to - // export them later as well. - let mut enc = self.root_type_encoder(export); - for func in &doc.interfaces[export].functions { - let name = func.core_export_name(export_name); - let instance_index = match module { - CustomModule::Main => enc.state.instance_index.expect("instantiated by now"), - CustomModule::Adapter(name) => enc.state.adapter_instances[name], - }; - let core_func_index = enc.state.component.alias_core_item( - instance_index, - ExportKind::Func, - name.as_ref(), - ); - - let ty = enc.encode_func_type(doc, func)?; - let options = RequiredOptions::for_export(doc, func); - - let encoding = metadata.export_encodings[&name[..]]; - // TODO: This realloc detection should probably be improved with - // some sort of scheme to have per-function reallocs like - // `cabi_realloc_{name}` or something like that. - let realloc_index = match module { - CustomModule::Main => enc.state.realloc_index, - CustomModule::Adapter(name) => enc.state.adapter_export_reallocs[name], - }; - let mut options = options - .into_iter(encoding, enc.state.memory_index, realloc_index)? - .collect::>(); - - // TODO: This should probe for the existence of - // `cabi_post_{name}` but not require its existence. - if doc.guest_export_needs_post_return(func) { - let post_return = enc.state.component.alias_core_item( - instance_index, - ExportKind::Func, - &format!("cabi_post_{name}"), - ); - options.push(CanonicalOption::PostReturn(post_return)); - } - let func_index = enc.state.component.lift_func(core_func_index, ty, options); - interface_exports.push((func.name.as_str(), ComponentExportKind::Func, func_index)); - } - - // Extend the interface exports to be created with type exports - // found during encoding of function types. - interface_exports.extend( - enc.type_exports - .into_iter() - .map(|(idx, name)| (name, ComponentExportKind::Type, idx)), - ); - - if interface_exports.is_empty() { - continue; - } - - // The default exported interface has all of its items exported - // directly but otherwise an instance type is created and then - // exported. - match export_name { - Some(export_name) => { - if export_name.is_empty() { - bail!("cannot export an unnamed interface"); - } - - let instance_index = self.component.instantiate_exports(interface_exports); - self.component.export( - export_name, - doc.interfaces[export].url.as_deref().unwrap_or(""), - ComponentExportKind::Instance, - instance_index, - ); - } - None => { - for (name, kind, idx) in interface_exports { - self.component.export(name, "", kind, idx); - } - } - } - } - - Ok(()) - } - - fn encode_shim_instantiation(&mut self) -> Shims<'a> { - let mut signatures = Vec::new(); - let mut ret = Shims::default(); - let info = self.info.info.as_ref().unwrap(); - - // For all interfaces imported into the main module record all of their - // indirect lowerings into `Shims`. - for name in info.required_imports.keys() { - let import = &self.info.import_map[name]; - ret.append_indirect( - name, - CustomModule::Main, - import, - info.metadata, - &mut signatures, - ); - } - - // For all required adapter modules a shim is created for each required - // function and additionally a set of shims are created for the - // interface imported into the shim module itself. - for (adapter, funcs) in info.adapters_required.iter() { - let (info, _wasm) = &self.info.adapters[adapter]; - for (name, _) in info.required_imports.iter() { - let import = &self.info.import_map[name]; - ret.append_indirect( - name, - CustomModule::Adapter(adapter), - import, - info.metadata, - &mut signatures, - ); - } - for (func, ty) in funcs { - let name = ret.list.len().to_string(); - log::debug!("shim {name} is adapter `{adapter}::{func}`"); - signatures.push(WasmSignature { - params: ty.params().iter().map(to_wasm_type).collect(), - results: ty.results().iter().map(to_wasm_type).collect(), - indirect_params: false, - retptr: false, - }); - ret.list.push(Shim { - name, - debug_name: format!("adapt-{adapter}-{func}"), - // Pessimistically assume that all adapters require memory - // in one form or another. While this isn't technically true - // it's true enough for WASI. - options: RequiredOptions::MEMORY, - kind: ShimKind::Adapter { adapter, func }, - }); - } - } - if ret.list.is_empty() { - return ret; - } - - for shim in ret.list.iter() { - ret.shim_names.insert(shim.kind, shim.name.clone()); - } - - assert!(self.shim_instance_index.is_none()); - assert!(self.fixups_module_index.is_none()); - - // This function encodes two modules: - // - A shim module that defines a table and exports functions - // that indirectly call through the table. - // - A fixup module that imports that table and a set of functions - // and populates the imported table via active element segments. The - // fixup module is used to populate the shim's table once the - // imported functions have been lowered. - - let mut types = TypeSection::new(); - let mut tables = TableSection::new(); - let mut functions = FunctionSection::new(); - let mut exports = ExportSection::new(); - let mut code = CodeSection::new(); - let mut sigs = IndexMap::new(); - let mut imports_section = ImportSection::new(); - let mut elements = ElementSection::new(); - let mut func_indexes = Vec::new(); - let mut func_names = NameMap::new(); - - for (i, (sig, shim)) in signatures.iter().zip(&ret.list).enumerate() { - let i = i as u32; - let type_index = *sigs.entry(sig).or_insert_with(|| { - let index = types.len(); - types.function( - sig.params.iter().map(to_val_type), - sig.results.iter().map(to_val_type), - ); - index - }); - - functions.function(type_index); - Self::encode_shim_function(type_index, i, &mut code, sig.params.len() as u32); - exports.export(&shim.name, ExportKind::Func, i); - - imports_section.import("", &shim.name, EntityType::Function(type_index)); - func_indexes.push(i); - func_names.append(i, &shim.debug_name); - } - let mut names = NameSection::new(); - names.functions(&func_names); - - let table_type = TableType { - element_type: ValType::FuncRef, - minimum: signatures.len() as u32, - maximum: Some(signatures.len() as u32), - }; - - tables.table(table_type); - - exports.export(INDIRECT_TABLE_NAME, ExportKind::Table, 0); - imports_section.import("", INDIRECT_TABLE_NAME, table_type); - - elements.active( - None, - &ConstExpr::i32_const(0), - ValType::FuncRef, - Elements::Functions(&func_indexes), - ); - - let mut shim = Module::new(); - shim.section(&types); - shim.section(&functions); - shim.section(&tables); - shim.section(&exports); - shim.section(&code); - shim.section(&names); - - let mut fixups = Module::default(); - fixups.section(&types); - fixups.section(&imports_section); - fixups.section(&elements); - - let shim_module_index = self.component.core_module(&shim); - self.fixups_module_index = Some(self.component.core_module(&fixups)); - self.shim_instance_index = Some(self.component.instantiate(shim_module_index, [])); - - return ret; - - fn to_wasm_type(ty: &wasmparser::ValType) -> WasmType { - match ty { - wasmparser::ValType::I32 => WasmType::I32, - wasmparser::ValType::I64 => WasmType::I64, - wasmparser::ValType::F32 => WasmType::F32, - wasmparser::ValType::F64 => WasmType::F64, - _ => unreachable!(), - } - } - } - - fn encode_shim_function( - type_index: u32, - func_index: u32, - code: &mut CodeSection, - param_count: u32, - ) { - let mut func = wasm_encoder::Function::new(std::iter::empty()); - for i in 0..param_count { - func.instruction(&Instruction::LocalGet(i)); - } - func.instruction(&Instruction::I32Const(func_index as i32)); - func.instruction(&Instruction::CallIndirect { - ty: type_index, - table: 0, - }); - func.instruction(&Instruction::End); - code.function(&func); - } - - fn encode_indirect_lowerings(&mut self, shims: Shims<'_>) -> Result<()> { - if shims.list.is_empty() { - return Ok(()); - } - - let shim_instance_index = self - .shim_instance_index - .expect("must have an instantiated shim"); - - let table_index = self.component.alias_core_item( - shim_instance_index, - ExportKind::Table, - INDIRECT_TABLE_NAME, - ); - - let mut exports = Vec::new(); - exports.push((INDIRECT_TABLE_NAME, ExportKind::Table, table_index)); - - for shim in shims.list.iter() { - let core_func_index = match &shim.kind { - // Indirect lowerings are a `canon lower`'d function with - // options specified from a previously instantiated instance. - // This previous instance could either be the main module or an - // adapter module, which affects the `realloc` option here. - // Currently only one linear memory is supported so the linear - // memory always comes from the main module. - ShimKind::IndirectLowering { - interface, - indirect_index, - realloc, - encoding, - } => { - let interface = &self.info.import_map[interface]; - let instance_index = self.imported_instances[&interface.interface]; - let func_index = self - .component - .alias_func(instance_index, interface.indirect[*indirect_index].name); - - let realloc = match realloc { - CustomModule::Main => self.realloc_index, - CustomModule::Adapter(name) => self.adapter_import_reallocs[name], - }; - - self.component.lower_func( - func_index, - shim.options - .into_iter(*encoding, self.memory_index, realloc)?, - ) - } - - // Adapter shims are defined by an export from and adapter - // instance, so use the specified name here and the previously - // created instances to get the core item that represents the - // shim. - ShimKind::Adapter { adapter, func } => self.component.alias_core_item( - self.adapter_instances[adapter], - ExportKind::Func, - func, - ), - }; - - exports.push((shim.name.as_str(), ExportKind::Func, core_func_index)); - } - - let instance_index = self.component.instantiate_core_exports(exports); - self.component.instantiate( - self.fixups_module_index.expect("must have fixup module"), - [("", ModuleArg::Instance(instance_index))], - ); - Ok(()) - } - - fn instantiate_core_module<'b, A>(&mut self, args: A, info: &ValidatedModule<'_>) - where - A: IntoIterator, - A::IntoIter: ExactSizeIterator, - { - assert!(self.instance_index.is_none()); - - let instance_index = self - .component - .instantiate(self.module_index.expect("core module encoded"), args); - - if info.has_memory { - self.memory_index = Some(self.component.alias_core_item( - instance_index, - ExportKind::Memory, - "memory", - )); - } - - if let Some(name) = &info.realloc { - self.realloc_index = Some(self.component.alias_core_item( - instance_index, - ExportKind::Func, - name, - )); - } - - self.instance_index = Some(instance_index); - } - - /// This function will instantiate all required adapter modules required by - /// the main module (specified by `info`). - /// - /// Each adapter here is instantiated with its required imported interface, - /// if any. - fn instantiate_adapter_modules(&mut self, shims: &Shims<'_>) { - for (name, (info, _wasm)) in self.info.adapters.iter() { - let mut args = Vec::new(); - - let mut core_exports = Vec::new(); - for export_name in info.needs_core_exports.iter() { - let index = self.component.alias_core_item( - self.instance_index - .expect("adaptee index set at this point"), - ExportKind::Func, - export_name, - ); - core_exports.push((export_name.as_str(), ExportKind::Func, index)); - } - if !core_exports.is_empty() { - let instance = self.component.instantiate_core_exports(core_exports); - args.push((MAIN_MODULE_IMPORT_NAME, ModuleArg::Instance(instance))); - } - // If the adapter module requires a `memory` import then specify - // that here. For now assume that the module name of the memory is - // different from the imported interface. That's true enough for now - // since it's `env::memory`. - if let Some((module, name)) = &info.needs_memory { - for (import_name, _) in info.required_imports.iter() { - assert!(module != import_name); - } - assert!(module != name); - let memory = self.memory_index.unwrap(); - let instance = self.component.instantiate_core_exports([( - name.as_str(), - ExportKind::Memory, - memory, - )]); - args.push((module.as_str(), ModuleArg::Instance(instance))); - } - for (import_name, _) in info.required_imports.iter() { - let instance = self.import_instance_to_lowered_core_instance( - CustomModule::Adapter(name), - import_name, - shims, - info.metadata, - ); - args.push((import_name, ModuleArg::Instance(instance))); - } - let instance = self.component.instantiate(self.adapter_modules[name], args); - self.adapter_instances.insert(name, instance); - - let realloc = info.export_realloc.as_ref().map(|name| { - self.component - .alias_core_item(instance, ExportKind::Func, name) - }); - self.adapter_export_reallocs.insert(name, realloc); - let realloc = info.import_realloc.as_ref().map(|name| { - self.component - .alias_core_item(instance, ExportKind::Func, name) - }); - self.adapter_import_reallocs.insert(name, realloc); - } - } -} - -/// A list of "shims" which start out during the component instantiation process -/// as functions which immediately trap due to a `call_indirect`-to-`null` but -/// will get filled in by the time the component instantiation process -/// completes. -/// -/// Shims currently include: -/// -/// * "Indirect functions" lowered from imported instances where the lowering -/// requires an item exported from the main module. These are indirect due to -/// the circular dependency between the module needing an import and the -/// import needing the module. -/// -/// * Adapter modules which convert from a historical ABI to the component -/// model's ABI (e.g. wasi preview1 to preview2) get a shim since the adapters -/// are currently indicated as always requiring the memory of the main module. -/// -/// This structure is created by `encode_shim_instantiation`. -#[derive(Default)] -struct Shims<'a> { - /// The list of all shims that a module will require. - list: Vec>, - - /// A map from a shim to the name of the shim in the shim instance. - shim_names: IndexMap, String>, -} - -struct Shim<'a> { - /// Canonical ABI options required by this shim, used during `canon lower` - /// operations. - options: RequiredOptions, - - /// The name, in the shim instance, of this shim. - /// - /// Currently this is `"0"`, `"1"`, ... - name: String, - - /// A human-readable debugging name for this shim, used in a core wasm - /// `name` section. - debug_name: String, - - /// Precise information about what this shim is a lowering of. - kind: ShimKind<'a>, -} - -#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] -enum ShimKind<'a> { - /// This shim is a late indirect lowering of an imported function in a - /// component which is only possible after prior core wasm modules are - /// instantiated so their memories and functions are available. - IndirectLowering { - /// The name of the interface that's being lowered. - interface: &'a str, - /// The index within the `indirect` array of the function being lowered. - indirect_index: usize, - /// Which instance to pull the `realloc` function from, if necessary. - realloc: CustomModule<'a>, - /// The string encoding that this lowering is going to use. - encoding: StringEncoding, - }, - /// This shim is a core wasm function defined in an adapter module but isn't - /// available until the adapter module is itself instantiated. - Adapter { - /// The name of the adapter module this shim comes from. - adapter: &'a str, - /// The name of the export in the adapter module this shim points to. - func: &'a str, - }, -} - -/// Indicator for which module is being used for a lowering or where options -/// like `realloc` are drawn from. -/// -/// This is necessary for situations such as an imported function being lowered -/// into the main module and additionally into an adapter module. For example an -/// adapter might adapt from preview1 to preview2 for the standard library of a -/// programming language but the main module's custom application code may also -/// explicitly import from preview2. These two different lowerings of a preview2 -/// function are parameterized by this enumeration. -#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] -enum CustomModule<'a> { - /// This points to the "main module" which is generally the "output of LLVM" - /// or what a user wrote. - Main, - /// This is selecting an adapter module, identified by name here, where - /// something is being lowered into. - Adapter(&'a str), -} - -impl<'a> Shims<'a> { - /// Adds all shims necessary for the `import` provided, namely iterating - /// over its indirect lowerings and appending a shim per lowering. - fn append_indirect( - &mut self, - name: &'a str, - for_module: CustomModule<'a>, - import: &ImportedInterface<'a>, - metadata: &ModuleMetadata, - sigs: &mut Vec, - ) { - for (indirect_index, lowering) in import.indirect.iter().enumerate() { - let shim_name = self.list.len().to_string(); - log::debug!( - "shim {shim_name} is import `{name}` lowering {indirect_index} `{}`", - lowering.name - ); - sigs.push(lowering.sig.clone()); - let encoding = - metadata.import_encodings[&(name.to_string(), lowering.name.to_string())]; - self.list.push(Shim { - name: shim_name, - debug_name: format!("indirect-{name}-{}", lowering.name), - options: lowering.options, - kind: ShimKind::IndirectLowering { - interface: name, - indirect_index, - realloc: for_module, - encoding, - }, - }); - } - } -} - -/// An encoder of components based on `wit` interface definitions. -#[derive(Default)] -pub struct ComponentEncoder { - module: Vec, - metadata: Bindgen, - validate: bool, - types_only: bool, - - // This is a map from the name of the adapter to a pair of: - // - // * The wasm of the adapter itself, with `component-type` sections - // stripped. - // * the metadata for the adapter, verified to have no exports and only - // imports. - // * The world within `self.metadata.doc` which the adapter works with. - adapters: IndexMap, ModuleMetadata, WorldId)>, -} - -impl ComponentEncoder { - /// Set the core module to encode as a component. - /// This method will also parse any component type information stored in custom sections - /// inside the module, and add them as the interface, imports, and exports. - pub fn module(mut self, module: &[u8]) -> Result { - let (wasm, metadata) = metadata::decode(module)?; - self.module = wasm; - self.metadata.merge(metadata)?; - Ok(self) - } - - /// Sets whether or not the encoder will validate its output. - pub fn validate(mut self, validate: bool) -> Self { - self.validate = validate; - self - } - - /// Add a document to this encoder as a manual specification of what's being - /// imported/exported. - /// - /// The string encoding of the specified world is supplied here as - /// well. - /// - /// Note that this can also be inferred from the `module` input if the - /// module embeds its own metadata. This is otherwise required to describe - /// imports/exports that aren't otherwise self-descried in `module`. - pub fn document(mut self, doc: Document, encoding: StringEncoding) -> Result { - let world = doc.default_world()?; - self.metadata.merge(Bindgen { - metadata: ModuleMetadata::new(&doc, world, encoding), - doc, - world, - })?; - Ok(self) - } - - /// Specifies a new adapter which is used to translate from a historical - /// wasm ABI to the canonical ABI and the `interface` provided. - /// - /// This is primarily used to polyfill, for example, - /// `wasi_snapshot_preview1` with a component-model using interface. The - /// `name` provided is the module name of the adapter that is being - /// polyfilled, for example `"wasi_snapshot_preview1"`. - /// - /// The `bytes` provided is a core wasm module which implements the `name` - /// interface in terms of the `interface` interface. This core wasm module - /// is severely restricted in its shape, for example it cannot have any data - /// segments or element segments. - /// - /// The `interface` provided is the component-model-using-interface that the - /// wasm module specified by `bytes` imports. The `bytes` will then import - /// `interface` and export functions to get imported from the module `name` - /// in the core wasm that's being wrapped. - pub fn adapter(mut self, name: &str, bytes: &[u8]) -> Result { - let (wasm, metadata) = metadata::decode(bytes)?; - // Merge the adapter's document into our own document to have one large - // document, but the adapter's world isn't merged in to our world so - // retain it separately. - let world = self.metadata.doc.merge(metadata.doc).world_map[metadata.world.index()]; - self.adapters - .insert(name.to_string(), (wasm, metadata.metadata, world)); - Ok(self) - } - - /// Indicates whether this encoder is only encoding types and does not - /// require a `module` as input. - pub fn types_only(mut self, only: bool) -> Self { - self.types_only = only; - self - } - - /// Encode the component and return the bytes. - pub fn encode(&self) -> Result> { - let doc = &self.metadata.doc; - let world = ComponentWorld::new(self)?; - let mut state = EncodingState { - component: ComponentBuilder::default(), - module_index: None, - instance_index: None, - memory_index: None, - realloc_index: None, - shim_instance_index: None, - fixups_module_index: None, - adapter_modules: IndexMap::new(), - adapter_instances: IndexMap::new(), - adapter_import_reallocs: IndexMap::new(), - adapter_export_reallocs: IndexMap::new(), - type_map: HashMap::new(), - func_type_map: HashMap::new(), - imported_instances: Default::default(), - info: &world, - }; - let world = &doc.worlds[self.metadata.world]; - state.encode_imports()?; - - if self.types_only { - if !self.module.is_empty() { - bail!("a module cannot be specified for a types-only encoding"); - } - - // In types-only mode there's no actual items to export so skip - // shims/adapters etc. Instead instance types are exported to - // represent exported instances and exported types represent the - // default export. - for (id, name) in world.exports() { - let interface = &doc.interfaces[id]; - let url = interface.url.as_deref().unwrap_or(""); - match name { - Some(name) => { - let mut ty = state.instance_type_encoder(id); - for func in interface.functions.iter() { - let idx = ty.encode_func_type(doc, func)?; - ty.ty.export(&func.name, "", ComponentTypeRef::Func(idx)); - } - let ty = ty.ty; - if ty.is_empty() { - continue; - } - let index = state.component.instance_type(&ty); - state - .component - .export(name, url, ComponentExportKind::Type, index); - } - None => { - let mut ty = state.root_type_encoder(id); - for func in interface.functions.iter() { - let idx = ty.encode_func_type(doc, func)?; - ty.state.component.export( - &func.name, - "", - ComponentExportKind::Type, - idx, - ); - } - for (idx, name) in ty.type_exports { - ty.state - .component - .export(name, "", ComponentExportKind::Type, idx); - } - } - } - } - } else { - if self.module.is_empty() { - bail!("a module is required when encoding a component"); - } - - state.encode_core_modules(); - state.encode_core_instantiation()?; - state.encode_exports(self, CustomModule::Main)?; - for name in self.adapters.keys() { - state.encode_exports(self, CustomModule::Adapter(name))?; - } - } - - let bytes = state.component.finish(); - - if self.validate { - let mut validator = Validator::new_with_features(WasmFeatures { - component_model: true, - ..Default::default() - }); - - validator - .validate_all(&bytes) - .context("failed to validate component output")?; - } - - Ok(bytes) - } -} +//use crate::builder::ComponentBuilder; +//use crate::metadata::{self, Bindgen, ModuleMetadata}; +//use crate::{ +// validation::{ValidatedModule, MAIN_MODULE_IMPORT_NAME}, +// StringEncoding, +//}; +//use anyhow::{anyhow, bail, Context, Result}; +//use indexmap::IndexMap; +//use std::collections::HashMap; +//use std::hash::Hash; +//use wasm_encoder::*; +//use wasmparser::{Validator, WasmFeatures}; +//use wit_parser::{ +// abi::{AbiVariant, WasmSignature, WasmType}, +// Document, Function, InterfaceId, Type, TypeDefKind, TypeId, WorldId, +//}; + +//const INDIRECT_TABLE_NAME: &str = "$imports"; + +mod wit; +pub use wit::encode; + +//mod types; +//use types::{InstanceTypeEncoder, RootTypeEncoder, ValtypeEncoder}; +//mod world; +//use world::{ComponentWorld, ImportedInterface}; + +//fn to_val_type(ty: &WasmType) -> ValType { +// match ty { +// WasmType::I32 => ValType::I32, +// WasmType::I64 => ValType::I64, +// WasmType::F32 => ValType::F32, +// WasmType::F64 => ValType::F64, +// } +//} + +//bitflags::bitflags! { +// /// Options in the `canon lower` or `canon lift` required for a particular +// /// function. +// pub struct RequiredOptions: u8 { +// /// A memory must be specified, typically the "main module"'s memory +// /// export. +// const MEMORY = 1 << 0; +// /// A `realloc` function must be specified, typically named +// /// `cabi_realloc`. +// const REALLOC = 1 << 1; +// /// A string encoding must be specified, which is always utf-8 for now +// /// today. +// const STRING_ENCODING = 1 << 2; +// } +//} + +//impl RequiredOptions { +// fn for_import(doc: &Document, func: &Function) -> RequiredOptions { +// let sig = doc.wasm_signature(AbiVariant::GuestImport, func); +// let mut ret = RequiredOptions::empty(); +// // Lift the params and lower the results for imports +// ret.add_lift(TypeContents::for_types( +// doc, +// func.params.iter().map(|(_, t)| t), +// )); +// ret.add_lower(TypeContents::for_types(doc, func.results.iter_types())); + +// // If anything is indirect then `memory` will be required to read the +// // indirect values. +// if sig.retptr || sig.indirect_params { +// ret |= RequiredOptions::MEMORY; +// } +// ret +// } + +// fn for_export(doc: &Document, func: &Function) -> RequiredOptions { +// let sig = doc.wasm_signature(AbiVariant::GuestExport, func); +// let mut ret = RequiredOptions::empty(); +// // Lower the params and lift the results for exports +// ret.add_lower(TypeContents::for_types( +// doc, +// func.params.iter().map(|(_, t)| t), +// )); +// ret.add_lift(TypeContents::for_types(doc, func.results.iter_types())); + +// // If anything is indirect then `memory` will be required to read the +// // indirect values, but if the arguments are indirect then `realloc` is +// // additionally required to allocate space for the parameters. +// if sig.retptr || sig.indirect_params { +// ret |= RequiredOptions::MEMORY; +// if sig.indirect_params { +// ret |= RequiredOptions::REALLOC; +// } +// } +// ret +// } + +// fn add_lower(&mut self, types: TypeContents) { +// // If lists/strings are lowered into wasm then memory is required as +// // usual but `realloc` is also required to allow the external caller to +// // allocate space in the destination for the list/string. +// if types.contains(TypeContents::LIST) { +// *self |= RequiredOptions::MEMORY | RequiredOptions::REALLOC; +// } +// if types.contains(TypeContents::STRING) { +// *self |= RequiredOptions::MEMORY +// | RequiredOptions::STRING_ENCODING +// | RequiredOptions::REALLOC; +// } +// } + +// fn add_lift(&mut self, types: TypeContents) { +// // Unlike for `lower` when lifting a string/list all that's needed is +// // memory, since the string/list already resides in memory `realloc` +// // isn't needed. +// if types.contains(TypeContents::LIST) { +// *self |= RequiredOptions::MEMORY; +// } +// if types.contains(TypeContents::STRING) { +// *self |= RequiredOptions::MEMORY | RequiredOptions::STRING_ENCODING; +// } +// } + +// fn into_iter( +// self, +// encoding: StringEncoding, +// memory_index: Option, +// realloc_index: Option, +// ) -> Result> { +// #[derive(Default)] +// struct Iter { +// options: [Option; 3], +// current: usize, +// count: usize, +// } + +// impl Iter { +// fn push(&mut self, option: CanonicalOption) { +// assert!(self.count < self.options.len()); +// self.options[self.count] = Some(option); +// self.count += 1; +// } +// } + +// impl Iterator for Iter { +// type Item = CanonicalOption; + +// fn next(&mut self) -> Option { +// if self.current == self.count { +// return None; +// } +// let option = self.options[self.current]; +// self.current += 1; +// option +// } + +// fn size_hint(&self) -> (usize, Option) { +// (self.count - self.current, Some(self.count - self.current)) +// } +// } + +// impl ExactSizeIterator for Iter {} + +// let mut iter = Iter::default(); + +// if self.contains(RequiredOptions::MEMORY) { +// iter.push(CanonicalOption::Memory(memory_index.ok_or_else(|| { +// anyhow!("module does not export a memory named `memory`") +// })?)); +// } + +// if self.contains(RequiredOptions::REALLOC) { +// iter.push(CanonicalOption::Realloc(realloc_index.ok_or_else( +// || anyhow!("module does not export a function named `cabi_realloc`"), +// )?)); +// } + +// if self.contains(RequiredOptions::STRING_ENCODING) { +// iter.push(encoding.into()); +// } + +// Ok(iter) +// } +//} + +//bitflags::bitflags! { +// /// Flags about what kinds of types are present within the recursive +// /// structure of a type. +// struct TypeContents: u8 { +// const STRING = 1 << 0; +// const LIST = 1 << 1; +// } +//} + +//impl TypeContents { +// fn for_types<'a>(doc: &Document, types: impl Iterator) -> Self { +// let mut cur = TypeContents::empty(); +// for ty in types { +// cur |= Self::for_type(doc, ty); +// } +// cur +// } + +// fn for_optional_types<'a>( +// doc: &Document, +// types: impl Iterator>, +// ) -> Self { +// Self::for_types(doc, types.flatten()) +// } + +// fn for_optional_type(doc: &Document, ty: Option<&Type>) -> Self { +// match ty { +// Some(ty) => Self::for_type(doc, ty), +// None => Self::empty(), +// } +// } + +// fn for_type(doc: &Document, ty: &Type) -> Self { +// match ty { +// Type::Id(id) => match &doc.types[*id].kind { +// TypeDefKind::Record(r) => Self::for_types(doc, r.fields.iter().map(|f| &f.ty)), +// TypeDefKind::Tuple(t) => Self::for_types(doc, t.types.iter()), +// TypeDefKind::Flags(_) => Self::empty(), +// TypeDefKind::Option(t) => Self::for_type(doc, t), +// TypeDefKind::Result(r) => { +// Self::for_optional_type(doc, r.ok.as_ref()) +// | Self::for_optional_type(doc, r.err.as_ref()) +// } +// TypeDefKind::Variant(v) => { +// Self::for_optional_types(doc, v.cases.iter().map(|c| c.ty.as_ref())) +// } +// TypeDefKind::Union(v) => Self::for_types(doc, v.cases.iter().map(|c| &c.ty)), +// TypeDefKind::Enum(_) => Self::empty(), +// TypeDefKind::List(t) => Self::for_type(doc, t) | Self::LIST, +// TypeDefKind::Type(t) => Self::for_type(doc, t), +// TypeDefKind::Future(_) => todo!("encoding for future"), +// TypeDefKind::Stream(_) => todo!("encoding for stream"), +// }, +// Type::String => Self::STRING, +// _ => Self::empty(), +// } +// } +//} + +///// State relating to encoding a component. +//pub struct EncodingState<'a> { +// /// The component being encoded. +// component: ComponentBuilder, +// /// The index into the core module index space for the inner core module. +// /// +// /// If `None`, the core module has not been encoded. +// module_index: Option, +// /// The index into the core instance index space for the inner core module. +// /// +// /// If `None`, the core module has not been instantiated. +// instance_index: Option, +// /// The index in the core memory index space for the exported memory. +// /// +// /// If `None`, then the memory has not yet been aliased. +// memory_index: Option, +// /// The index in the core function index space for the realloc function. +// /// +// /// If `None`, then the realloc function has not yet been aliased. +// realloc_index: Option, +// /// The index of the shim instance used for lowering imports into the core instance. +// /// +// /// If `None`, then the shim instance how not yet been encoded. +// shim_instance_index: Option, +// /// The index of the fixups module to instantiate to fill in the lowered imports. +// /// +// /// If `None`, then a fixup module has not yet been encoded. +// fixups_module_index: Option, + +// /// A map of named adapter modules and the index that the module was defined +// /// at. +// adapter_modules: IndexMap<&'a str, u32>, +// /// A map of adapter module instances and the index of their instance. +// adapter_instances: IndexMap<&'a str, u32>, +// /// A map of the index of the aliased realloc function for each adapter +// /// module. Note that adapters have two realloc functions, one for imports +// /// and one for exports. +// adapter_import_reallocs: IndexMap<&'a str, Option>, +// adapter_export_reallocs: IndexMap<&'a str, Option>, + +// /// Imported instances and what index they were imported as. +// imported_instances: IndexMap, + +// /// Map of types defined within the component's root index space. +// type_map: HashMap, +// /// Map of function types defined within the component's root index space. +// func_type_map: HashMap, u32>, + +// /// Metadata about the world inferred from the input to `ComponentEncoder`. +// info: &'a ComponentWorld<'a>, +//} + +//impl<'a> EncodingState<'a> { +// fn encode_core_modules(&mut self) { +// assert!(self.module_index.is_none()); +// let idx = self.component.core_module_raw(&self.info.encoder.module); +// self.module_index = Some(idx); + +// for (name, (_, wasm)) in self.info.adapters.iter() { +// let idx = self.component.core_module_raw(wasm); +// let prev = self.adapter_modules.insert(name, idx); +// assert!(prev.is_none()); +// } +// } + +// fn root_type_encoder(&mut self, interface: InterfaceId) -> RootTypeEncoder<'_, 'a> { +// RootTypeEncoder { +// state: self, +// type_exports: Vec::new(), +// interface, +// } +// } + +// fn instance_type_encoder(&mut self, interface: InterfaceId) -> InstanceTypeEncoder<'_, 'a> { +// InstanceTypeEncoder { +// state: self, +// interface, +// type_map: Default::default(), +// func_type_map: Default::default(), +// ty: Default::default(), +// } +// } + +// fn encode_imports(&mut self) -> Result<()> { +// let doc = &self.info.encoder.metadata.doc; +// for (name, info) in self.info.import_map.iter() { +// let interface = &doc.interfaces[info.interface]; +// log::trace!("encoding imports for `{name}` as {:?}", info.interface); +// let ty = { +// let mut encoder = self.instance_type_encoder(info.interface); + +// // Encode all required functions from this imported interface +// // into the instance type. +// for func in interface.functions.iter() { +// if !info.required.contains(func.name.as_str()) { +// continue; +// } +// log::trace!("encoding function type for `{}`", func.name); +// let idx = encoder.encode_func_type(doc, func)?; + +// encoder +// .ty +// .export(&func.name, "", ComponentTypeRef::Func(idx)); +// } + +// // If there were any live types from this instance which weren't +// // otherwise reached through the above function types then this +// // will forward them through. +// if let Some(live) = encoder.state.info.live_types.get(&info.interface) { +// for ty in live { +// log::trace!("encoding extra type {ty:?}"); +// encoder.encode_valtype(doc, &Type::Id(*ty))?; +// } +// } + +// encoder.ty +// }; + +// // Don't encode empty instance types since they're not +// // meaningful to the runtime of the component anyway. +// if ty.is_empty() { +// continue; +// } +// let instance_type_idx = self.component.instance_type(&ty); +// let instance_idx = self.component.import( +// name, +// &info.url, +// ComponentTypeRef::Instance(instance_type_idx), +// ); +// let prev = self.imported_instances.insert(info.interface, instance_idx); +// assert!(prev.is_none()); +// } +// Ok(()) +// } + +// fn index_of_type_export(&mut self, id: TypeId) -> u32 { +// // Using the original `interface` definition of `id` and its name create +// // an alias which refers to the type export of that instance which must +// // have previously been imported. +// let ty = &self.info.encoder.metadata.doc.types[id]; +// let interface = ty +// .interface +// .expect("cannot import anonymous type across interfaces"); +// let name = ty +// .name +// .as_ref() +// .expect("cannot import anonymous type across interfaces"); +// let instance = self.imported_instances[&interface]; +// self.component.alias_type_export(instance, name) +// } + +// fn encode_core_instantiation(&mut self) -> Result<()> { +// let info = self.info.info.as_ref().unwrap(); +// // Encode a shim instantiation if needed +// let shims = self.encode_shim_instantiation(); + +// // For each instance import into the main module create a +// // pseudo-core-wasm-module via a bag-of-exports. +// let mut args = Vec::new(); +// for name in info.required_imports.keys() { +// let index = self.import_instance_to_lowered_core_instance( +// CustomModule::Main, +// *name, +// &shims, +// info.metadata, +// ); +// args.push((*name, ModuleArg::Instance(index))); +// } + +// // For each adapter module instance imported into the core wasm module +// // the appropriate shim is packaged up into a bag-of-exports instance. +// // Note that adapter modules currently don't deal with +// // indirect-vs-direct lowerings, everything is indirect. +// for (adapter, funcs) in info.adapters_required.iter() { +// let shim_instance = self +// .shim_instance_index +// .expect("shim should be instantiated"); +// let mut exports = Vec::new(); + +// for (func, _ty) in funcs { +// let index = self.component.alias_core_item( +// shim_instance, +// ExportKind::Func, +// &shims.shim_names[&ShimKind::Adapter { adapter, func }], +// ); +// exports.push((*func, ExportKind::Func, index)); +// } + +// let index = self.component.instantiate_core_exports(exports); +// args.push((*adapter, ModuleArg::Instance(index))); +// } + +// // Instantiate the main module now that all of its arguments have been +// // prepared. With this we know have the main linear memory for +// // liftings/lowerings later on as well as the adapter modules, if any, +// // instantiated after the core wasm module. +// self.instantiate_core_module(args, info); +// self.instantiate_adapter_modules(&shims); + +// // With all the core wasm instances in play now the original shim +// // module, if present, can be filled in with lowerings/adapters/etc. +// self.encode_indirect_lowerings(shims) +// } + +// /// Lowers a named imported interface a core wasm instances suitable to +// /// provide as an instantiation argument to another core wasm module. +// /// +// /// * `for_module` the module that this instance is being created for, or +// /// otherwise which `realloc` option is used for the lowerings. +// /// * `name` - the name of the imported interface that's being lowered. +// /// * `imports` - the list of all imports known for this encoding. +// /// * `shims` - the indirect/adapter shims created prior, if any. +// fn import_instance_to_lowered_core_instance( +// &mut self, +// for_module: CustomModule<'_>, +// name: &str, +// shims: &Shims<'_>, +// metadata: &ModuleMetadata, +// ) -> u32 { +// let import = &self.info.import_map[name]; +// let instance_index = self.imported_instances[&import.interface]; +// let mut exports = Vec::with_capacity(import.direct.len() + import.indirect.len()); + +// // Add an entry for all indirect lowerings which come as an export of +// // the shim module. +// for (i, lowering) in import.indirect.iter().enumerate() { +// let encoding = +// metadata.import_encodings[&(name.to_string(), lowering.name.to_string())]; +// let index = self.component.alias_core_item( +// self.shim_instance_index +// .expect("shim should be instantiated"), +// ExportKind::Func, +// &shims.shim_names[&ShimKind::IndirectLowering { +// interface: name, +// indirect_index: i, +// realloc: for_module, +// encoding, +// }], +// ); +// exports.push((lowering.name, ExportKind::Func, index)); +// } + +// // All direct lowerings can be `canon lower`'d here immediately and +// // passed as arguments. +// for lowering in &import.direct { +// let func_index = self.component.alias_func(instance_index, lowering.name); +// let core_func_index = self.component.lower_func(func_index, []); +// exports.push((lowering.name, ExportKind::Func, core_func_index)); +// } + +// self.component.instantiate_core_exports(exports) +// } + +// fn encode_exports(&mut self, opts: &'a ComponentEncoder, module: CustomModule) -> Result<()> { +// let doc = &opts.metadata.doc; +// let metadata = match module { +// CustomModule::Main => &opts.metadata.metadata, +// CustomModule::Adapter(name) => &opts.adapters[name].1, +// }; +// let world = match module { +// CustomModule::Main => opts.metadata.world, +// CustomModule::Adapter(name) => opts.adapters[name].2, +// }; +// let world = &doc.worlds[world]; +// for (export, export_name) in world.exports() { +// let mut interface_exports = Vec::new(); + +// // All types are encoded into the root component here using this +// // encoder which keeps track of type exports to determine how to +// // export them later as well. +// let mut enc = self.root_type_encoder(export); +// for func in &doc.interfaces[export].functions { +// let name = func.core_export_name(export_name); +// let instance_index = match module { +// CustomModule::Main => enc.state.instance_index.expect("instantiated by now"), +// CustomModule::Adapter(name) => enc.state.adapter_instances[name], +// }; +// let core_func_index = enc.state.component.alias_core_item( +// instance_index, +// ExportKind::Func, +// name.as_ref(), +// ); + +// let ty = enc.encode_func_type(doc, func)?; +// let options = RequiredOptions::for_export(doc, func); + +// let encoding = metadata.export_encodings[&name[..]]; +// // TODO: This realloc detection should probably be improved with +// // some sort of scheme to have per-function reallocs like +// // `cabi_realloc_{name}` or something like that. +// let realloc_index = match module { +// CustomModule::Main => enc.state.realloc_index, +// CustomModule::Adapter(name) => enc.state.adapter_export_reallocs[name], +// }; +// let mut options = options +// .into_iter(encoding, enc.state.memory_index, realloc_index)? +// .collect::>(); + +// // TODO: This should probe for the existence of +// // `cabi_post_{name}` but not require its existence. +// if doc.guest_export_needs_post_return(func) { +// let post_return = enc.state.component.alias_core_item( +// instance_index, +// ExportKind::Func, +// &format!("cabi_post_{name}"), +// ); +// options.push(CanonicalOption::PostReturn(post_return)); +// } +// let func_index = enc.state.component.lift_func(core_func_index, ty, options); +// interface_exports.push((func.name.as_str(), ComponentExportKind::Func, func_index)); +// } + +// // Extend the interface exports to be created with type exports +// // found during encoding of function types. +// interface_exports.extend( +// enc.type_exports +// .into_iter() +// .map(|(idx, name)| (name, ComponentExportKind::Type, idx)), +// ); + +// if interface_exports.is_empty() { +// continue; +// } + +// // The default exported interface has all of its items exported +// // directly but otherwise an instance type is created and then +// // exported. +// match export_name { +// Some(export_name) => { +// if export_name.is_empty() { +// bail!("cannot export an unnamed interface"); +// } + +// let instance_index = self.component.instantiate_exports(interface_exports); +// self.component.export( +// export_name, +// doc.interfaces[export].url.as_deref().unwrap_or(""), +// ComponentExportKind::Instance, +// instance_index, +// ); +// } +// None => { +// for (name, kind, idx) in interface_exports { +// self.component.export(name, "", kind, idx); +// } +// } +// } +// } + +// Ok(()) +// } + +// fn encode_shim_instantiation(&mut self) -> Shims<'a> { +// let mut signatures = Vec::new(); +// let mut ret = Shims::default(); +// let info = self.info.info.as_ref().unwrap(); + +// // For all interfaces imported into the main module record all of their +// // indirect lowerings into `Shims`. +// for name in info.required_imports.keys() { +// let import = &self.info.import_map[name]; +// ret.append_indirect( +// name, +// CustomModule::Main, +// import, +// info.metadata, +// &mut signatures, +// ); +// } + +// // For all required adapter modules a shim is created for each required +// // function and additionally a set of shims are created for the +// // interface imported into the shim module itself. +// for (adapter, funcs) in info.adapters_required.iter() { +// let (info, _wasm) = &self.info.adapters[adapter]; +// for (name, _) in info.required_imports.iter() { +// let import = &self.info.import_map[name]; +// ret.append_indirect( +// name, +// CustomModule::Adapter(adapter), +// import, +// info.metadata, +// &mut signatures, +// ); +// } +// for (func, ty) in funcs { +// let name = ret.list.len().to_string(); +// log::debug!("shim {name} is adapter `{adapter}::{func}`"); +// signatures.push(WasmSignature { +// params: ty.params().iter().map(to_wasm_type).collect(), +// results: ty.results().iter().map(to_wasm_type).collect(), +// indirect_params: false, +// retptr: false, +// }); +// ret.list.push(Shim { +// name, +// debug_name: format!("adapt-{adapter}-{func}"), +// // Pessimistically assume that all adapters require memory +// // in one form or another. While this isn't technically true +// // it's true enough for WASI. +// options: RequiredOptions::MEMORY, +// kind: ShimKind::Adapter { adapter, func }, +// }); +// } +// } +// if ret.list.is_empty() { +// return ret; +// } + +// for shim in ret.list.iter() { +// ret.shim_names.insert(shim.kind, shim.name.clone()); +// } + +// assert!(self.shim_instance_index.is_none()); +// assert!(self.fixups_module_index.is_none()); + +// // This function encodes two modules: +// // - A shim module that defines a table and exports functions +// // that indirectly call through the table. +// // - A fixup module that imports that table and a set of functions +// // and populates the imported table via active element segments. The +// // fixup module is used to populate the shim's table once the +// // imported functions have been lowered. + +// let mut types = TypeSection::new(); +// let mut tables = TableSection::new(); +// let mut functions = FunctionSection::new(); +// let mut exports = ExportSection::new(); +// let mut code = CodeSection::new(); +// let mut sigs = IndexMap::new(); +// let mut imports_section = ImportSection::new(); +// let mut elements = ElementSection::new(); +// let mut func_indexes = Vec::new(); +// let mut func_names = NameMap::new(); + +// for (i, (sig, shim)) in signatures.iter().zip(&ret.list).enumerate() { +// let i = i as u32; +// let type_index = *sigs.entry(sig).or_insert_with(|| { +// let index = types.len(); +// types.function( +// sig.params.iter().map(to_val_type), +// sig.results.iter().map(to_val_type), +// ); +// index +// }); + +// functions.function(type_index); +// Self::encode_shim_function(type_index, i, &mut code, sig.params.len() as u32); +// exports.export(&shim.name, ExportKind::Func, i); + +// imports_section.import("", &shim.name, EntityType::Function(type_index)); +// func_indexes.push(i); +// func_names.append(i, &shim.debug_name); +// } +// let mut names = NameSection::new(); +// names.functions(&func_names); + +// let table_type = TableType { +// element_type: ValType::FuncRef, +// minimum: signatures.len() as u32, +// maximum: Some(signatures.len() as u32), +// }; + +// tables.table(table_type); + +// exports.export(INDIRECT_TABLE_NAME, ExportKind::Table, 0); +// imports_section.import("", INDIRECT_TABLE_NAME, table_type); + +// elements.active( +// None, +// &ConstExpr::i32_const(0), +// ValType::FuncRef, +// Elements::Functions(&func_indexes), +// ); + +// let mut shim = Module::new(); +// shim.section(&types); +// shim.section(&functions); +// shim.section(&tables); +// shim.section(&exports); +// shim.section(&code); +// shim.section(&names); + +// let mut fixups = Module::default(); +// fixups.section(&types); +// fixups.section(&imports_section); +// fixups.section(&elements); + +// let shim_module_index = self.component.core_module(&shim); +// self.fixups_module_index = Some(self.component.core_module(&fixups)); +// self.shim_instance_index = Some(self.component.instantiate(shim_module_index, [])); + +// return ret; + +// fn to_wasm_type(ty: &wasmparser::ValType) -> WasmType { +// match ty { +// wasmparser::ValType::I32 => WasmType::I32, +// wasmparser::ValType::I64 => WasmType::I64, +// wasmparser::ValType::F32 => WasmType::F32, +// wasmparser::ValType::F64 => WasmType::F64, +// _ => unreachable!(), +// } +// } +// } + +// fn encode_shim_function( +// type_index: u32, +// func_index: u32, +// code: &mut CodeSection, +// param_count: u32, +// ) { +// let mut func = wasm_encoder::Function::new(std::iter::empty()); +// for i in 0..param_count { +// func.instruction(&Instruction::LocalGet(i)); +// } +// func.instruction(&Instruction::I32Const(func_index as i32)); +// func.instruction(&Instruction::CallIndirect { +// ty: type_index, +// table: 0, +// }); +// func.instruction(&Instruction::End); +// code.function(&func); +// } + +// fn encode_indirect_lowerings(&mut self, shims: Shims<'_>) -> Result<()> { +// if shims.list.is_empty() { +// return Ok(()); +// } + +// let shim_instance_index = self +// .shim_instance_index +// .expect("must have an instantiated shim"); + +// let table_index = self.component.alias_core_item( +// shim_instance_index, +// ExportKind::Table, +// INDIRECT_TABLE_NAME, +// ); + +// let mut exports = Vec::new(); +// exports.push((INDIRECT_TABLE_NAME, ExportKind::Table, table_index)); + +// for shim in shims.list.iter() { +// let core_func_index = match &shim.kind { +// // Indirect lowerings are a `canon lower`'d function with +// // options specified from a previously instantiated instance. +// // This previous instance could either be the main module or an +// // adapter module, which affects the `realloc` option here. +// // Currently only one linear memory is supported so the linear +// // memory always comes from the main module. +// ShimKind::IndirectLowering { +// interface, +// indirect_index, +// realloc, +// encoding, +// } => { +// let interface = &self.info.import_map[interface]; +// let instance_index = self.imported_instances[&interface.interface]; +// let func_index = self +// .component +// .alias_func(instance_index, interface.indirect[*indirect_index].name); + +// let realloc = match realloc { +// CustomModule::Main => self.realloc_index, +// CustomModule::Adapter(name) => self.adapter_import_reallocs[name], +// }; + +// self.component.lower_func( +// func_index, +// shim.options +// .into_iter(*encoding, self.memory_index, realloc)?, +// ) +// } + +// // Adapter shims are defined by an export from and adapter +// // instance, so use the specified name here and the previously +// // created instances to get the core item that represents the +// // shim. +// ShimKind::Adapter { adapter, func } => self.component.alias_core_item( +// self.adapter_instances[adapter], +// ExportKind::Func, +// func, +// ), +// }; + +// exports.push((shim.name.as_str(), ExportKind::Func, core_func_index)); +// } + +// let instance_index = self.component.instantiate_core_exports(exports); +// self.component.instantiate( +// self.fixups_module_index.expect("must have fixup module"), +// [("", ModuleArg::Instance(instance_index))], +// ); +// Ok(()) +// } + +// fn instantiate_core_module<'b, A>(&mut self, args: A, info: &ValidatedModule<'_>) +// where +// A: IntoIterator, +// A::IntoIter: ExactSizeIterator, +// { +// assert!(self.instance_index.is_none()); + +// let instance_index = self +// .component +// .instantiate(self.module_index.expect("core module encoded"), args); + +// if info.has_memory { +// self.memory_index = Some(self.component.alias_core_item( +// instance_index, +// ExportKind::Memory, +// "memory", +// )); +// } + +// if let Some(name) = &info.realloc { +// self.realloc_index = Some(self.component.alias_core_item( +// instance_index, +// ExportKind::Func, +// name, +// )); +// } + +// self.instance_index = Some(instance_index); +// } + +// /// This function will instantiate all required adapter modules required by +// /// the main module (specified by `info`). +// /// +// /// Each adapter here is instantiated with its required imported interface, +// /// if any. +// fn instantiate_adapter_modules(&mut self, shims: &Shims<'_>) { +// for (name, (info, _wasm)) in self.info.adapters.iter() { +// let mut args = Vec::new(); + +// let mut core_exports = Vec::new(); +// for export_name in info.needs_core_exports.iter() { +// let index = self.component.alias_core_item( +// self.instance_index +// .expect("adaptee index set at this point"), +// ExportKind::Func, +// export_name, +// ); +// core_exports.push((export_name.as_str(), ExportKind::Func, index)); +// } +// if !core_exports.is_empty() { +// let instance = self.component.instantiate_core_exports(core_exports); +// args.push((MAIN_MODULE_IMPORT_NAME, ModuleArg::Instance(instance))); +// } +// // If the adapter module requires a `memory` import then specify +// // that here. For now assume that the module name of the memory is +// // different from the imported interface. That's true enough for now +// // since it's `env::memory`. +// if let Some((module, name)) = &info.needs_memory { +// for (import_name, _) in info.required_imports.iter() { +// assert!(module != import_name); +// } +// assert!(module != name); +// let memory = self.memory_index.unwrap(); +// let instance = self.component.instantiate_core_exports([( +// name.as_str(), +// ExportKind::Memory, +// memory, +// )]); +// args.push((module.as_str(), ModuleArg::Instance(instance))); +// } +// for (import_name, _) in info.required_imports.iter() { +// let instance = self.import_instance_to_lowered_core_instance( +// CustomModule::Adapter(name), +// import_name, +// shims, +// info.metadata, +// ); +// args.push((import_name, ModuleArg::Instance(instance))); +// } +// let instance = self.component.instantiate(self.adapter_modules[name], args); +// self.adapter_instances.insert(name, instance); + +// let realloc = info.export_realloc.as_ref().map(|name| { +// self.component +// .alias_core_item(instance, ExportKind::Func, name) +// }); +// self.adapter_export_reallocs.insert(name, realloc); +// let realloc = info.import_realloc.as_ref().map(|name| { +// self.component +// .alias_core_item(instance, ExportKind::Func, name) +// }); +// self.adapter_import_reallocs.insert(name, realloc); +// } +// } +//} + +///// A list of "shims" which start out during the component instantiation process +///// as functions which immediately trap due to a `call_indirect`-to-`null` but +///// will get filled in by the time the component instantiation process +///// completes. +///// +///// Shims currently include: +///// +///// * "Indirect functions" lowered from imported instances where the lowering +///// requires an item exported from the main module. These are indirect due to +///// the circular dependency between the module needing an import and the +///// import needing the module. +///// +///// * Adapter modules which convert from a historical ABI to the component +///// model's ABI (e.g. wasi preview1 to preview2) get a shim since the adapters +///// are currently indicated as always requiring the memory of the main module. +///// +///// This structure is created by `encode_shim_instantiation`. +//#[derive(Default)] +//struct Shims<'a> { +// /// The list of all shims that a module will require. +// list: Vec>, + +// /// A map from a shim to the name of the shim in the shim instance. +// shim_names: IndexMap, String>, +//} + +//struct Shim<'a> { +// /// Canonical ABI options required by this shim, used during `canon lower` +// /// operations. +// options: RequiredOptions, + +// /// The name, in the shim instance, of this shim. +// /// +// /// Currently this is `"0"`, `"1"`, ... +// name: String, + +// /// A human-readable debugging name for this shim, used in a core wasm +// /// `name` section. +// debug_name: String, + +// /// Precise information about what this shim is a lowering of. +// kind: ShimKind<'a>, +//} + +//#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +//enum ShimKind<'a> { +// /// This shim is a late indirect lowering of an imported function in a +// /// component which is only possible after prior core wasm modules are +// /// instantiated so their memories and functions are available. +// IndirectLowering { +// /// The name of the interface that's being lowered. +// interface: &'a str, +// /// The index within the `indirect` array of the function being lowered. +// indirect_index: usize, +// /// Which instance to pull the `realloc` function from, if necessary. +// realloc: CustomModule<'a>, +// /// The string encoding that this lowering is going to use. +// encoding: StringEncoding, +// }, +// /// This shim is a core wasm function defined in an adapter module but isn't +// /// available until the adapter module is itself instantiated. +// Adapter { +// /// The name of the adapter module this shim comes from. +// adapter: &'a str, +// /// The name of the export in the adapter module this shim points to. +// func: &'a str, +// }, +//} + +///// Indicator for which module is being used for a lowering or where options +///// like `realloc` are drawn from. +///// +///// This is necessary for situations such as an imported function being lowered +///// into the main module and additionally into an adapter module. For example an +///// adapter might adapt from preview1 to preview2 for the standard library of a +///// programming language but the main module's custom application code may also +///// explicitly import from preview2. These two different lowerings of a preview2 +///// function are parameterized by this enumeration. +//#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +//enum CustomModule<'a> { +// /// This points to the "main module" which is generally the "output of LLVM" +// /// or what a user wrote. +// Main, +// /// This is selecting an adapter module, identified by name here, where +// /// something is being lowered into. +// Adapter(&'a str), +//} + +//impl<'a> Shims<'a> { +// /// Adds all shims necessary for the `import` provided, namely iterating +// /// over its indirect lowerings and appending a shim per lowering. +// fn append_indirect( +// &mut self, +// name: &'a str, +// for_module: CustomModule<'a>, +// import: &ImportedInterface<'a>, +// metadata: &ModuleMetadata, +// sigs: &mut Vec, +// ) { +// for (indirect_index, lowering) in import.indirect.iter().enumerate() { +// let shim_name = self.list.len().to_string(); +// log::debug!( +// "shim {shim_name} is import `{name}` lowering {indirect_index} `{}`", +// lowering.name +// ); +// sigs.push(lowering.sig.clone()); +// let encoding = +// metadata.import_encodings[&(name.to_string(), lowering.name.to_string())]; +// self.list.push(Shim { +// name: shim_name, +// debug_name: format!("indirect-{name}-{}", lowering.name), +// options: lowering.options, +// kind: ShimKind::IndirectLowering { +// interface: name, +// indirect_index, +// realloc: for_module, +// encoding, +// }, +// }); +// } +// } +//} + +///// An encoder of components based on `wit` interface definitions. +//#[derive(Default)] +//pub struct ComponentEncoder { +// module: Vec, +// metadata: Bindgen, +// validate: bool, +// types_only: bool, + +// // This is a map from the name of the adapter to a pair of: +// // +// // * The wasm of the adapter itself, with `component-type` sections +// // stripped. +// // * the metadata for the adapter, verified to have no exports and only +// // imports. +// // * The world within `self.metadata.doc` which the adapter works with. +// adapters: IndexMap, ModuleMetadata, WorldId)>, +//} + +//impl ComponentEncoder { +// /// Set the core module to encode as a component. +// /// This method will also parse any component type information stored in custom sections +// /// inside the module, and add them as the interface, imports, and exports. +// pub fn module(mut self, module: &[u8]) -> Result { +// let (wasm, metadata) = metadata::decode(module)?; +// self.module = wasm; +// self.metadata.merge(metadata)?; +// Ok(self) +// } + +// /// Sets whether or not the encoder will validate its output. +// pub fn validate(mut self, validate: bool) -> Self { +// self.validate = validate; +// self +// } + +// /// Add a document to this encoder as a manual specification of what's being +// /// imported/exported. +// /// +// /// The string encoding of the specified world is supplied here as +// /// well. +// /// +// /// Note that this can also be inferred from the `module` input if the +// /// module embeds its own metadata. This is otherwise required to describe +// /// imports/exports that aren't otherwise self-descried in `module`. +// pub fn document(mut self, doc: Document, encoding: StringEncoding) -> Result { +// let world = doc.default_world()?; +// self.metadata.merge(Bindgen { +// metadata: ModuleMetadata::new(&doc, world, encoding), +// doc, +// world, +// })?; +// Ok(self) +// } + +// /// Specifies a new adapter which is used to translate from a historical +// /// wasm ABI to the canonical ABI and the `interface` provided. +// /// +// /// This is primarily used to polyfill, for example, +// /// `wasi_snapshot_preview1` with a component-model using interface. The +// /// `name` provided is the module name of the adapter that is being +// /// polyfilled, for example `"wasi_snapshot_preview1"`. +// /// +// /// The `bytes` provided is a core wasm module which implements the `name` +// /// interface in terms of the `interface` interface. This core wasm module +// /// is severely restricted in its shape, for example it cannot have any data +// /// segments or element segments. +// /// +// /// The `interface` provided is the component-model-using-interface that the +// /// wasm module specified by `bytes` imports. The `bytes` will then import +// /// `interface` and export functions to get imported from the module `name` +// /// in the core wasm that's being wrapped. +// pub fn adapter(mut self, name: &str, bytes: &[u8]) -> Result { +// let (wasm, metadata) = metadata::decode(bytes)?; +// // Merge the adapter's document into our own document to have one large +// // document, but the adapter's world isn't merged in to our world so +// // retain it separately. +// let world = self.metadata.doc.merge(metadata.doc).world_map[metadata.world.index()]; +// self.adapters +// .insert(name.to_string(), (wasm, metadata.metadata, world)); +// Ok(self) +// } + +// /// Indicates whether this encoder is only encoding types and does not +// /// require a `module` as input. +// pub fn types_only(mut self, only: bool) -> Self { +// self.types_only = only; +// self +// } + +// /// Encode the component and return the bytes. +// pub fn encode(&self) -> Result> { +// let doc = &self.metadata.doc; +// let world = ComponentWorld::new(self)?; +// let mut state = EncodingState { +// component: ComponentBuilder::default(), +// module_index: None, +// instance_index: None, +// memory_index: None, +// realloc_index: None, +// shim_instance_index: None, +// fixups_module_index: None, +// adapter_modules: IndexMap::new(), +// adapter_instances: IndexMap::new(), +// adapter_import_reallocs: IndexMap::new(), +// adapter_export_reallocs: IndexMap::new(), +// type_map: HashMap::new(), +// func_type_map: HashMap::new(), +// imported_instances: Default::default(), +// info: &world, +// }; +// let world = &doc.worlds[self.metadata.world]; +// state.encode_imports()?; + +// if self.types_only { +// if !self.module.is_empty() { +// bail!("a module cannot be specified for a types-only encoding"); +// } + +// // In types-only mode there's no actual items to export so skip +// // shims/adapters etc. Instead instance types are exported to +// // represent exported instances and exported types represent the +// // default export. +// for (id, name) in world.exports() { +// let interface = &doc.interfaces[id]; +// let url = interface.url.as_deref().unwrap_or(""); +// match name { +// Some(name) => { +// let mut ty = state.instance_type_encoder(id); +// for func in interface.functions.iter() { +// let idx = ty.encode_func_type(doc, func)?; +// ty.ty.export(&func.name, "", ComponentTypeRef::Func(idx)); +// } +// let ty = ty.ty; +// if ty.is_empty() { +// continue; +// } +// let index = state.component.instance_type(&ty); +// state +// .component +// .export(name, url, ComponentExportKind::Type, index); +// } +// None => { +// let mut ty = state.root_type_encoder(id); +// for func in interface.functions.iter() { +// let idx = ty.encode_func_type(doc, func)?; +// ty.state.component.export( +// &func.name, +// "", +// ComponentExportKind::Type, +// idx, +// ); +// } +// for (idx, name) in ty.type_exports { +// ty.state +// .component +// .export(name, "", ComponentExportKind::Type, idx); +// } +// } +// } +// } +// } else { +// if self.module.is_empty() { +// bail!("a module is required when encoding a component"); +// } + +// state.encode_core_modules(); +// state.encode_core_instantiation()?; +// state.encode_exports(self, CustomModule::Main)?; +// for name in self.adapters.keys() { +// state.encode_exports(self, CustomModule::Adapter(name))?; +// } +// } + +// let bytes = state.component.finish(); + +// if self.validate { +// let mut validator = Validator::new_with_features(WasmFeatures { +// component_model: true, +// ..Default::default() +// }); + +// validator +// .validate_all(&bytes) +// .context("failed to validate component output")?; +// } + +// Ok(bytes) +// } +//} diff --git a/crates/wit-component/src/encoding/wit.rs b/crates/wit-component/src/encoding/wit.rs new file mode 100644 index 0000000000..7001a8a390 --- /dev/null +++ b/crates/wit-component/src/encoding/wit.rs @@ -0,0 +1,7 @@ +use anyhow::Result; +use wit_parser::*; + +/// TODO +pub fn encode(resolve: &Resolve, package: PackageId) -> Result> { + panic!() +} diff --git a/crates/wit-component/src/lib.rs b/crates/wit-component/src/lib.rs index 997011187f..383a939ed9 100644 --- a/crates/wit-component/src/lib.rs +++ b/crates/wit-component/src/lib.rs @@ -9,13 +9,13 @@ use wasm_encoder::CanonicalOption; // mod builder; mod decoding; -// mod encoding; +mod encoding; // mod gc; mod printing; // mod validation; pub use decoding::{decode, DecodedWasm}; -// pub use encoding::ComponentEncoder; +pub use encoding::encode; pub use printing::*; // pub mod metadata; diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index a6f1328e5e..c3b68eb853 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -2,11 +2,12 @@ use anyhow::{anyhow, bail, Context, Result}; use clap::Parser; -use std::path::{Path, PathBuf}; +use std::io::Read; +use std::path::PathBuf; use wasm_tools::Output; -// use wit_component::{decode_world, ComponentEncoder, DocumentPrinter, StringEncoding}; use wit_component::{DecodedWasm, DocumentPrinter}; -use wit_parser::Document; +use wit_parser::{Resolve, UnresolvedPackage}; +// use wit_component::{decode_world, ComponentEncoder, DocumentPrinter, StringEncoding}; /// WebAssembly wit-based component tooling. #[derive(Parser)] @@ -24,27 +25,27 @@ impl Opts { } } -fn parse_optionally_name_file(s: &str) -> (&str, &str) { - let mut parts = s.splitn(2, '='); - let name_or_path = parts.next().unwrap(); - match parts.next() { - Some(path) => (name_or_path, path), - None => { - let name = Path::new(name_or_path) - .file_stem() - .unwrap() - .to_str() - .unwrap(); - (name, name_or_path) - } - } -} +// fn parse_optionally_name_file(s: &str) -> (&str, &str) { +// let mut parts = s.splitn(2, '='); +// let name_or_path = parts.next().unwrap(); +// match parts.next() { +// Some(path) => (name_or_path, path), +// None => { +// let name = Path::new(name_or_path) +// .file_stem() +// .unwrap() +// .to_str() +// .unwrap(); +// (name, name_or_path) +// } +// } +// } -fn parse_adapter(s: &str) -> Result<(String, Vec)> { - let (name, path) = parse_optionally_name_file(s); - let wasm = wat::parse_file(path)?; - Ok((name.to_string(), wasm)) -} +// fn parse_adapter(s: &str) -> Result<(String, Vec)> { +// let (name, path) = parse_optionally_name_file(s); +// let wasm = wat::parse_file(path)?; +// Ok((name.to_string(), wasm)) +// } ///// WebAssembly component encoder from an input core wasm binary. ///// @@ -143,41 +144,209 @@ fn parse_adapter(s: &str) -> Result<(String, Vec)> { // } //} -/// WebAssembly interface printer. +/// Tool for working with the WIT text format for components. /// -/// Decodes a `*.wit` file from a binary WebAssembly component. +/// This subcommand can be used to inspect and debug the WIT text or binary +/// format with either WIT packages or binary components. Using this subcommand +/// a WIT package can be translated to binary, a WIT binary can be translated +/// back to text, and a WIT document can be extracted from a component binary to +/// inspect its interfaces. #[derive(Parser)] pub struct WitOpts { #[clap(flatten)] - io: wasm_tools::InputOutput, + verbosity: wasm_tools::Verbosity, + + /// Input file or directory to process. + /// + /// The file specified can be a `*.wit` file parsed as a single-document + /// package. It can be a directory to be parsed as a WIT package. It can be + /// a `*.wat` or `*.wasm` file for either the binary representation of a WIT + /// package or a component itself to extract the interface from. The type of + /// input is inferred from the contents of the path specified. + /// + /// If not provided or if this is `-` then stdin is read entirely and + /// processed. + input: Option, - /// The name of the world to generate, inferred by default from the input - /// filename. + #[clap(flatten)] + output: wasm_tools::OutputArg, + + /// If a WIT package is being parsed, then this is the optionally specified + /// name of the WIT package. If not specified this is automatically inferred + /// from the filename. #[clap(long)] name: Option, - /// TODO + /// When printing a WIT package, the default mode, this option is used to + /// indicate which document is printed within the package if more than one + /// document is present. #[clap(short, long)] document: Option, + + /// Emit a full WIT package into the specified directory when printing the + /// text form. + /// + /// This is incompatible with `-o`. + #[clap(long)] + out_dir: Option, + + /// Emit the WIT binary format, a WebAssembly component, instead of the WIT + /// text format. + #[clap(short, long)] + wasm: bool, + + /// When combined with `--wasm` this will print the WebAssembly text format + /// instead of the WebAssembly binary format. + #[clap(short = 't', long)] + wat: bool, } impl WitOpts { /// Executes the application. fn run(self) -> Result<()> { - let bytes = self.io.parse_input_wasm()?; let name = match &self.name { Some(name) => name.as_str(), - None => match self.io.input_path() { + None => match &self.input { Some(path) => path.file_stem().unwrap().to_str().unwrap(), None => "component", }, }; - let mut printer = DocumentPrinter::default(); - let output = match wit_component::decode(name, &bytes).context("failed to decode world")? { - DecodedWasm::WitPackage(resolve, package) => { - let pkg = &resolve.packages[package]; - let doc = match &self.document { + // First up determine the actual `DecodedWasm` as the input. This could + // come from a number of sources: + // + // * If a `*.wat` or `*.wasm` is specified, use `wit_component::decode` + // * If a directory is specified, parse it as a `Resolve`-oriented + // package with a `deps` directory optionally available. + // * If a file is specified then it's just a normal wit package where + // deps can't be resolved. + // * If no file is specified then parse the input as either `*.wat`, + // `*.wasm`, or `*.wit` and do as above. + // + // Eventually there will want to be more flags for things like + // specifying a directory but specifying the WIT dependencies are + // located elsewhere. This should be sufficient for now though. + let decoded = match &self.input { + Some(input) => match input.extension().and_then(|s| s.to_str()) { + Some("wat") | Some("wasm") => { + let bytes = wat::parse_file(&input)?; + wit_component::decode(name, &bytes).context("failed to decode WIT document")? + } + _ => { + let mut resolve = Resolve::default(); + let id = if input.is_dir() { + resolve.push_dir(&input)? + } else { + let pkg = UnresolvedPackage::parse_file(&input)?; + resolve.push(pkg, &Default::default())? + }; + DecodedWasm::WitPackage(resolve, id) + } + }, + None => { + let mut stdin = Vec::new(); + std::io::stdin() + .read_to_end(&mut stdin) + .context("failed to read ")?; + + if is_wasm(&stdin) { + let bytes = wat::parse_bytes(&stdin).map_err(|mut e| { + e.set_path(""); + e + })?; + + wit_component::decode(name, &bytes).context("failed to decode WIT document")? + } else { + let stdin = match std::str::from_utf8(&stdin) { + Ok(s) => s, + Err(_) => bail!("stdin was not valid utf-8"), + }; + let mut resolve = Resolve::default(); + let pkg = UnresolvedPackage::parse("".as_ref(), stdin)?; + let id = resolve.push(pkg, &Default::default())?; + DecodedWasm::WitPackage(resolve, id) + } + } + }; + + // Now that the WIT document has been decoded, it's time to emit it. + // This interprets all of the output options and performs such a task. + match &self.out_dir { + Some(dir) => { + if self.output.output_path().is_some() { + bail!("cannot specify both `--out-dir` and `--output`"); + } + if self.wasm { + bail!("cannot specify both `--out-dir` and `--wasm`"); + } + if self.wat { + bail!("cannot specify both `--out-dir` and `--wat`"); + } + if self.document.is_some() { + bail!("cannot specify both `--out-dir` and `--document`"); + } + let package = match &decoded { + DecodedWasm::WitPackage(_, package) => *package, + + DecodedWasm::Component(resolve, world) => { + let doc = resolve.worlds[*world].document; + resolve.documents[doc].package.unwrap() + } + }; + let resolve = decoded.resolve(); + std::fs::create_dir_all(&dir).context(format!("failed to create {dir:?}"))?; + for (name, doc) in resolve.packages[package].documents.iter() { + let output = DocumentPrinter::default().print(&resolve, *doc)?; + let path = dir.join(format!("{name}.wit")); + std::fs::write(&path, output).context(format!("failed to write {path:?}"))?; + } + } + None => { + if self.wasm { + self.emit_wasm(&decoded)?; + } else { + self.emit_wit(&decoded)?; + } + } + } + Ok(()) + } + + fn emit_wasm(&self, decoded: &DecodedWasm) -> Result<()> { + assert!(self.wasm); + assert!(self.out_dir.is_none()); + if self.document.is_some() { + bail!("cannot specify `--document` with `--wasm`"); + } + + let pkg = match decoded { + DecodedWasm::WitPackage(_resolve, pkg) => *pkg, + DecodedWasm::Component(resolve, world) => { + let doc = resolve.worlds[*world].document; + resolve.documents[doc].package.unwrap() + } + }; + + let resolve = decoded.resolve(); + let bytes = wit_component::encode(&resolve, pkg)?; + self.output.output(Output::Wasm { + bytes: &bytes, + wat: self.wat, + })?; + Ok(()) + } + + fn emit_wit(&self, decoded: &DecodedWasm) -> Result<()> { + assert!(!self.wasm); + assert!(self.out_dir.is_none()); + if self.wat { + bail!("the `--wat` option can only be combined with `--wasm`"); + } + + let doc = match decoded { + DecodedWasm::WitPackage(resolve, pkg) => { + let pkg = &resolve.packages[*pkg]; + match &self.document { Some(name) => *pkg .documents .get(name) @@ -185,19 +354,42 @@ impl WitOpts { None => match pkg.documents.len() { 1 => *pkg.documents.iter().next().unwrap().1, _ => bail!( - "more than document found in package, specify \ - which to print with `-d name`" + "more than document found in package, \ + specify which to print with `-d name`" ), }, - }; - - printer.print(&resolve, doc)? + } } - // TODO - DecodedWasm::Component(..) => panic!(), + DecodedWasm::Component(resolve, world) => resolve.worlds[*world].document, }; - self.io.output(Output::Wat(&output))?; + let output = DocumentPrinter::default().print(decoded.resolve(), doc)?; + self.output.output(Output::Wat(&output))?; Ok(()) } } + +/// Test to see if a string is probably a `*.wat` text syntax. +/// +/// This briefly lexes past whitespace and comments as a `*.wat` file to see if +/// we can find a left-paren. If that fails then it's probably `*.wit` instead. +fn is_wasm(bytes: &[u8]) -> bool { + use wast::lexer::{Lexer, Token}; + + let text = match std::str::from_utf8(bytes) { + Ok(s) => s, + Err(_) => return true, + }; + + let mut lexer = Lexer::new(text); + + while let Some(next) = lexer.next() { + match next { + Ok(Token::Whitespace(_)) | Ok(Token::BlockComment(_)) | Ok(Token::LineComment(_)) => {} + Ok(Token::LParen(_)) => return true, + _ => break, + } + } + + false +} diff --git a/src/lib.rs b/src/lib.rs index afad2a5d9f..32a61f502f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -146,6 +146,10 @@ impl OutputArg { Ok(()) } + pub fn output_path(&self) -> Option<&Path> { + self.output.as_deref() + } + pub fn output_writer(&self) -> Result> { match &self.output { Some(output) => Ok(Box::new(BufWriter::new(File::create(&output)?))), From f1e8523033c986b2a8f2a049767661d069fdb24d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 5 Jan 2023 17:09:10 -0800 Subject: [PATCH 05/37] Implement roundtripping WIT through wasm This commit implements all the necessary infrastructure to roundtrip a WIT package through the WebAssembly component binary format. The `wasm-tools component wit` subcommand is the tooling for this mode and the `tests/interfaces` directory for `wit-component` has all been updated. Tests are now either a single-file wit package or a directory which is a wit-package. Tests have a `$name.wat` assertion for the expected output and a `$name.wit.print` assertion for each document for the machine-printed WIT as-decoded-from-wasm. The `tests/interfaces` test also tests round-trip-ness to ensure that everything stays stable. I've taken a number of liberties throughout the implementation of encoding/decoding, primarily around the handling of URLs. Additionally much of the encoding code related to creating an actual component from a core wasm module is commented out here since I'll handle that in subsequent commits. Tests were all updated here to the new syntax and additionally all of the examples from the upstream component-model PR have been added here as tests. --- crates/wasmparser/src/validator.rs | 3 +- crates/wasmparser/src/validator/component.rs | 14 +- crates/wit-component/src/builder.rs | 6 + crates/wit-component/src/decoding.rs | 230 +++++--- crates/wit-component/src/encoding.rs | 4 +- crates/wit-component/src/encoding/types.rs | 283 +++++----- crates/wit-component/src/encoding/wit.rs | 233 +++++++- crates/wit-component/src/lib.rs | 2 +- crates/wit-component/src/printing.rs | 6 +- crates/wit-component/test-helpers/src/lib.rs | 56 +- crates/wit-component/tests/interfaces.rs | 148 +++-- .../tests/interfaces/console.wat | 14 + .../tests/interfaces/console.wit | 3 + .../tests/interfaces/console.wit.print | 4 + .../tests/interfaces/default/component.wat | 23 - .../tests/interfaces/default/types_only.wat | 4 - .../tests/interfaces/default/world.wit | 7 - .../tests/interfaces/diamond-disambiguate.wat | 66 +++ .../diamond-disambiguate/join.wit.print | 10 + .../diamond-disambiguate/shared1.wit.print | 5 + .../diamond-disambiguate/shared2.wit.print | 5 + .../wit-component/tests/interfaces/empty.wat | 28 + .../wit-component/tests/interfaces/empty.wit | 8 + .../tests/interfaces/empty.wit.print | 9 + .../tests/interfaces/empty/component.wat | 14 - .../tests/interfaces/empty/types_only.wat | 1 - .../tests/interfaces/empty/world.wit | 6 - .../tests/interfaces/exports.wat | 30 + .../{exports/world.wit => exports.wit} | 2 +- .../tests/interfaces/exports.wit.print | 11 + .../tests/interfaces/exports/component.wat | 32 -- .../tests/interfaces/exports/types_only.wat | 11 - .../wit-component/tests/interfaces/flags.wat | 78 +++ .../interfaces/{flags/world.wit => flags.wit} | 2 +- .../tests/interfaces/flags.wit.print | 167 ++++++ .../tests/interfaces/flags/component.wat | 112 ---- .../tests/interfaces/flags/types_only.wat | 35 -- .../wit-component/tests/interfaces/floats.wat | 38 ++ .../wit-component/tests/interfaces/floats.wit | 10 + .../{floats/world.wit => floats.wit.print} | 2 +- .../tests/interfaces/floats/component.wat | 52 -- .../tests/interfaces/floats/types_only.wat | 15 - .../tests/interfaces/import-and-export.wat | 40 ++ .../world.wit => import-and-export.wit} | 4 +- .../interfaces/import-and-export.wit.print | 12 + .../import-and-export/component.wat | 42 -- .../import-and-export/types_only.wat | 16 - .../tests/interfaces/integers.wat | 100 ++++ .../tests/interfaces/integers.wit | 25 + .../world.wit => integers.wit.print} | 2 +- .../tests/interfaces/integers/component.wat | 181 ------ .../tests/interfaces/integers/types_only.wat | 46 -- .../wit-component/tests/interfaces/lists.wat | 202 +++++++ .../interfaces/{lists/world.wit => lists.wit} | 2 +- .../tests/interfaces/lists.wit.print | 94 ++++ .../tests/interfaces/lists/component.wat | 527 ------------------ .../tests/interfaces/lists/types_only.wat | 97 ---- .../tests/interfaces/multi-doc.wat | 58 ++ .../tests/interfaces/multi-doc/a.wit | 7 + .../tests/interfaces/multi-doc/a.wit.print | 8 + .../tests/interfaces/multi-doc/b.wit | 7 + .../tests/interfaces/multi-doc/b.wit.print | 10 + .../tests/interfaces/records.wat | 132 +++++ .../tests/interfaces/records.wit | 60 ++ .../tests/interfaces/records.wit.print | 60 ++ .../tests/interfaces/records/component.wat | 290 ---------- .../tests/interfaces/records/types_only.wat | 86 --- .../tests/interfaces/records/world.wit | 120 ---- .../interfaces/reference-out-of-order.wat | 54 ++ .../interfaces/reference-out-of-order.wit | 26 + ...d.wit => reference-out-of-order.wit.print} | 2 +- .../reference-out-of-order/component.wat | 101 ---- .../reference-out-of-order/types_only.wat | 23 - .../tests/interfaces/simple-deps.wat | 22 + .../simple-deps/deps/some-dep/types.wit | 3 + .../tests/interfaces/simple-deps/foo.wit | 3 + .../interfaces/simple-deps/foo.wit.print | 4 + .../tests/interfaces/simple-multi.wat | 20 + .../tests/interfaces/simple-multi/bar.wit | 1 + .../interfaces/simple-multi/bar.wit.print | 3 + .../tests/interfaces/simple-multi/foo.wit | 1 + .../interfaces/simple-multi/foo.wit.print | 3 + .../tests/interfaces/simple-use.wat | 24 + .../tests/interfaces/simple-use.wit | 11 + .../tests/interfaces/simple-use.wit.print | 13 + .../tests/interfaces/simple-world.wat | 26 + .../tests/interfaces/simple-world.wit | 7 + .../tests/interfaces/simple-world.wit.print | 7 + .../tests/interfaces/type-alias.wat | 43 ++ .../tests/interfaces/type-alias.wit | 11 + .../tests/interfaces/type-alias.wit.print | 12 + .../tests/interfaces/type-alias/component.wat | 50 -- .../interfaces/type-alias/types_only.wat | 24 - .../tests/interfaces/type-alias/world.wit | 20 - .../tests/interfaces/type-alias2.wat | 32 ++ .../world.wit => type-alias2.wit} | 2 +- .../tests/interfaces/type-alias2.wit.print | 13 + .../interfaces/type-alias2/component.wat | 30 - .../interfaces/type-alias2/types_only.wat | 13 - .../tests/interfaces/variants.wat | 202 +++++++ .../tests/interfaces/variants.wit | 83 +++ .../world.wit => variants.wit.print} | 2 +- .../tests/interfaces/variants/component.wat | 379 ------------- .../tests/interfaces/variants/types_only.wat | 97 ---- .../tests/interfaces/wasi-http.wat | 95 ++++ .../wasi-http/deps/wasi-logging/backend.wit | 8 + .../tests/interfaces/wasi-http/handler.wit | 4 + .../interfaces/wasi-http/handler.wit.print | 5 + .../tests/interfaces/wasi-http/proxy.wit | 5 + .../interfaces/wasi-http/proxy.wit.print | 6 + .../tests/interfaces/wasi-http/types.wit | 4 + .../interfaces/wasi-http/types.wit.print | 9 + .../interfaces/world-inline-interface.wat | 20 + .../interfaces/world-inline-interface.wit | 4 + .../world-inline-interface.wit.print | 6 + crates/wit-parser/src/ast/resolve.rs | 27 +- crates/wit-parser/src/lib.rs | 19 +- crates/wit-parser/src/live.rs | 116 ++++ crates/wit-parser/src/resolve.rs | 17 +- src/bin/wasm-tools/component.rs | 12 + 120 files changed, 2919 insertions(+), 2822 deletions(-) create mode 100644 crates/wit-component/tests/interfaces/console.wat create mode 100644 crates/wit-component/tests/interfaces/console.wit create mode 100644 crates/wit-component/tests/interfaces/console.wit.print delete mode 100644 crates/wit-component/tests/interfaces/default/component.wat delete mode 100644 crates/wit-component/tests/interfaces/default/types_only.wat delete mode 100644 crates/wit-component/tests/interfaces/default/world.wit create mode 100644 crates/wit-component/tests/interfaces/diamond-disambiguate.wat create mode 100644 crates/wit-component/tests/interfaces/diamond-disambiguate/join.wit.print create mode 100644 crates/wit-component/tests/interfaces/diamond-disambiguate/shared1.wit.print create mode 100644 crates/wit-component/tests/interfaces/diamond-disambiguate/shared2.wit.print create mode 100644 crates/wit-component/tests/interfaces/empty.wat create mode 100644 crates/wit-component/tests/interfaces/empty.wit create mode 100644 crates/wit-component/tests/interfaces/empty.wit.print delete mode 100644 crates/wit-component/tests/interfaces/empty/component.wat delete mode 100644 crates/wit-component/tests/interfaces/empty/types_only.wat delete mode 100644 crates/wit-component/tests/interfaces/empty/world.wit create mode 100644 crates/wit-component/tests/interfaces/exports.wat rename crates/wit-component/tests/interfaces/{exports/world.wit => exports.wit} (84%) create mode 100644 crates/wit-component/tests/interfaces/exports.wit.print delete mode 100644 crates/wit-component/tests/interfaces/exports/component.wat delete mode 100644 crates/wit-component/tests/interfaces/exports/types_only.wat create mode 100644 crates/wit-component/tests/interfaces/flags.wat rename crates/wit-component/tests/interfaces/{flags/world.wit => flags.wit} (98%) create mode 100644 crates/wit-component/tests/interfaces/flags.wit.print delete mode 100644 crates/wit-component/tests/interfaces/flags/component.wat delete mode 100644 crates/wit-component/tests/interfaces/flags/types_only.wat create mode 100644 crates/wit-component/tests/interfaces/floats.wat create mode 100644 crates/wit-component/tests/interfaces/floats.wit rename crates/wit-component/tests/interfaces/{floats/world.wit => floats.wit.print} (86%) delete mode 100644 crates/wit-component/tests/interfaces/floats/component.wat delete mode 100644 crates/wit-component/tests/interfaces/floats/types_only.wat create mode 100644 crates/wit-component/tests/interfaces/import-and-export.wat rename crates/wit-component/tests/interfaces/{import-and-export/world.wit => import-and-export.wit} (67%) create mode 100644 crates/wit-component/tests/interfaces/import-and-export.wit.print delete mode 100644 crates/wit-component/tests/interfaces/import-and-export/component.wat delete mode 100644 crates/wit-component/tests/interfaces/import-and-export/types_only.wat create mode 100644 crates/wit-component/tests/interfaces/integers.wat create mode 100644 crates/wit-component/tests/interfaces/integers.wit rename crates/wit-component/tests/interfaces/{integers/world.wit => integers.wit.print} (94%) delete mode 100644 crates/wit-component/tests/interfaces/integers/component.wat delete mode 100644 crates/wit-component/tests/interfaces/integers/types_only.wat create mode 100644 crates/wit-component/tests/interfaces/lists.wat rename crates/wit-component/tests/interfaces/{lists/world.wit => lists.wit} (98%) create mode 100644 crates/wit-component/tests/interfaces/lists.wit.print delete mode 100644 crates/wit-component/tests/interfaces/lists/component.wat delete mode 100644 crates/wit-component/tests/interfaces/lists/types_only.wat create mode 100644 crates/wit-component/tests/interfaces/multi-doc.wat create mode 100644 crates/wit-component/tests/interfaces/multi-doc/a.wit create mode 100644 crates/wit-component/tests/interfaces/multi-doc/a.wit.print create mode 100644 crates/wit-component/tests/interfaces/multi-doc/b.wit create mode 100644 crates/wit-component/tests/interfaces/multi-doc/b.wit.print create mode 100644 crates/wit-component/tests/interfaces/records.wat create mode 100644 crates/wit-component/tests/interfaces/records.wit create mode 100644 crates/wit-component/tests/interfaces/records.wit.print delete mode 100644 crates/wit-component/tests/interfaces/records/component.wat delete mode 100644 crates/wit-component/tests/interfaces/records/types_only.wat delete mode 100644 crates/wit-component/tests/interfaces/records/world.wit create mode 100644 crates/wit-component/tests/interfaces/reference-out-of-order.wat create mode 100644 crates/wit-component/tests/interfaces/reference-out-of-order.wit rename crates/wit-component/tests/interfaces/{reference-out-of-order/world.wit => reference-out-of-order.wit.print} (92%) delete mode 100644 crates/wit-component/tests/interfaces/reference-out-of-order/component.wat delete mode 100644 crates/wit-component/tests/interfaces/reference-out-of-order/types_only.wat create mode 100644 crates/wit-component/tests/interfaces/simple-deps.wat create mode 100644 crates/wit-component/tests/interfaces/simple-deps/deps/some-dep/types.wit create mode 100644 crates/wit-component/tests/interfaces/simple-deps/foo.wit create mode 100644 crates/wit-component/tests/interfaces/simple-deps/foo.wit.print create mode 100644 crates/wit-component/tests/interfaces/simple-multi.wat create mode 100644 crates/wit-component/tests/interfaces/simple-multi/bar.wit create mode 100644 crates/wit-component/tests/interfaces/simple-multi/bar.wit.print create mode 100644 crates/wit-component/tests/interfaces/simple-multi/foo.wit create mode 100644 crates/wit-component/tests/interfaces/simple-multi/foo.wit.print create mode 100644 crates/wit-component/tests/interfaces/simple-use.wat create mode 100644 crates/wit-component/tests/interfaces/simple-use.wit create mode 100644 crates/wit-component/tests/interfaces/simple-use.wit.print create mode 100644 crates/wit-component/tests/interfaces/simple-world.wat create mode 100644 crates/wit-component/tests/interfaces/simple-world.wit create mode 100644 crates/wit-component/tests/interfaces/simple-world.wit.print create mode 100644 crates/wit-component/tests/interfaces/type-alias.wat create mode 100644 crates/wit-component/tests/interfaces/type-alias.wit create mode 100644 crates/wit-component/tests/interfaces/type-alias.wit.print delete mode 100644 crates/wit-component/tests/interfaces/type-alias/component.wat delete mode 100644 crates/wit-component/tests/interfaces/type-alias/types_only.wat delete mode 100644 crates/wit-component/tests/interfaces/type-alias/world.wit create mode 100644 crates/wit-component/tests/interfaces/type-alias2.wat rename crates/wit-component/tests/interfaces/{type-alias2/world.wit => type-alias2.wit} (80%) create mode 100644 crates/wit-component/tests/interfaces/type-alias2.wit.print delete mode 100644 crates/wit-component/tests/interfaces/type-alias2/component.wat delete mode 100644 crates/wit-component/tests/interfaces/type-alias2/types_only.wat create mode 100644 crates/wit-component/tests/interfaces/variants.wat create mode 100644 crates/wit-component/tests/interfaces/variants.wit rename crates/wit-component/tests/interfaces/{variants/world.wit => variants.wit.print} (98%) delete mode 100644 crates/wit-component/tests/interfaces/variants/component.wat delete mode 100644 crates/wit-component/tests/interfaces/variants/types_only.wat create mode 100644 crates/wit-component/tests/interfaces/wasi-http.wat create mode 100644 crates/wit-component/tests/interfaces/wasi-http/deps/wasi-logging/backend.wit create mode 100644 crates/wit-component/tests/interfaces/wasi-http/handler.wit create mode 100644 crates/wit-component/tests/interfaces/wasi-http/handler.wit.print create mode 100644 crates/wit-component/tests/interfaces/wasi-http/proxy.wit create mode 100644 crates/wit-component/tests/interfaces/wasi-http/proxy.wit.print create mode 100644 crates/wit-component/tests/interfaces/wasi-http/types.wit create mode 100644 crates/wit-component/tests/interfaces/wasi-http/types.wit.print create mode 100644 crates/wit-component/tests/interfaces/world-inline-interface.wat create mode 100644 crates/wit-component/tests/interfaces/world-inline-interface.wit create mode 100644 crates/wit-component/tests/interfaces/world-inline-interface.wit.print create mode 100644 crates/wit-parser/src/live.rs diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs index 95cd5b40e4..05dc4462ef 100644 --- a/crates/wasmparser/src/validator.rs +++ b/crates/wasmparser/src/validator.rs @@ -1169,7 +1169,7 @@ impl Validator { current.exports.reserve(count as usize); Ok(()) }, - |components, _, _, export, offset| { + |components, types, _, export, offset| { let current = components.last_mut().unwrap(); let ty = current.export_to_entity_type(&export, offset)?; current.add_export( @@ -1178,6 +1178,7 @@ impl Validator { ty, offset, false, /* checked above */ + types, ) }, ) diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index f3fcd23fe6..e421d6707f 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -284,13 +284,21 @@ impl ComponentState { &mut self, name: &str, url: &str, - ty: ComponentEntityType, + mut ty: ComponentEntityType, offset: usize, check_limit: bool, + types: &mut TypeAlloc, ) -> Result<()> { if check_limit { check_max(self.exports.len(), 1, MAX_WASM_EXPORTS, "exports", offset)?; } + // Assign a unique index to this type id as it gets pushed onto the + // internal type list because this is creating an alias and this TypeId + // should uniquely indicate that it's a distinct reference to the same + // underlying actual type information. + if let ComponentEntityType::Type(id) = &mut ty { + *id = types.with_unique(*id); + } self.add_entity(ty, true, offset)?; let name = to_kebab_str(name, "export", offset)?; @@ -809,7 +817,7 @@ impl ComponentState { crate::ComponentTypeDeclaration::Export { name, url, ty } => { let current = components.last_mut().unwrap(); let ty = current.check_type_ref(&ty, types, offset)?; - current.add_export(name, url, ty, offset, true)?; + current.add_export(name, url, ty, offset, true, types)?; } crate::ComponentTypeDeclaration::Import(import) => { components @@ -852,7 +860,7 @@ impl ComponentState { crate::InstanceTypeDeclaration::Export { name, url, ty } => { let current = components.last_mut().unwrap(); let ty = current.check_type_ref(&ty, types, offset)?; - current.add_export(name, url, ty, offset, true)?; + current.add_export(name, url, ty, offset, true, types)?; } crate::InstanceTypeDeclaration::Alias(alias) => { Self::add_alias(components, alias, types, offset)?; diff --git a/crates/wit-component/src/builder.rs b/crates/wit-component/src/builder.rs index 7956f19f95..81f223965a 100644 --- a/crates/wit-component/src/builder.rs +++ b/crates/wit-component/src/builder.rs @@ -147,6 +147,12 @@ impl ComponentBuilder { ret } + pub fn component_type(&mut self, ty: &ComponentType) -> u32 { + let ret = inc(&mut self.types); + self.types().component(ty); + ret + } + pub fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { (inc(&mut self.types), self.types().defined_type()) } diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index 8f8bb87737..4090da30b7 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -93,6 +93,7 @@ impl<'a> ComponentInfo<'a> { let package = resolve.packages.alloc(Package { name: name.to_string(), documents: Default::default(), + url: None, }); let mut decoder = WitPackageDecoder { resolve, @@ -138,6 +139,17 @@ impl DecodedWasm { DecodedWasm::Component(resolve, _) => resolve, } } + + /// Returns the main package of what was decoded. + pub fn package(&self) -> PackageId { + match self { + DecodedWasm::WitPackage(_, id) => *id, + DecodedWasm::Component(resolve, world) => { + let doc = resolve.worlds[*world].document; + resolve.documents[doc].package.unwrap() + } + } + } } /// Decodes an in-memory WebAssembly binary into a WIT [`Resolve`] and @@ -249,7 +261,11 @@ impl WitPackageDecoder<'_> { Ok(()) } - fn register_import(&mut self, url: &Url, ty: &types::ComponentInstanceType) -> Result<()> { + fn register_import( + &mut self, + url: &Url, + ty: &types::ComponentInstanceType, + ) -> Result { let interface = self.extract_url_interface(url)?; for (name, export_url, ty) in ty.exports(self.info.types.as_ref()) { @@ -257,53 +273,98 @@ impl WitPackageDecoder<'_> { bail!("instance type export `{name}` should not have a url") } - let ty = match ty { - types::ComponentEntityType::Type(ty) => ty, - _ => bail!("instance type export `{name}` is not a type"), - }; - let def = match self.info.types.type_from_id(ty) { - Some(types::Type::Defined(ty)) => ty, - _ => unreachable!(), - }; + match ty { + types::ComponentEntityType::Type(ty) => { + let def = match self.info.types.type_from_id(ty) { + Some(types::Type::Defined(ty)) => ty, + _ => unreachable!(), + }; - // If the interface already defines this type then that's to be - // expected. - // - // TODO: comments - // - // TODO: should ideally verify the preexisting type matches `ty`. - let id = match self.resolve.interfaces[interface].types.get(name.as_str()) { - Some(id) => *id, - None => { - // If this is a pkg-local dependency then that means it can only - // refer to previously defined types, so error at this point. - if url.scheme() == "pkg" { - bail!("instance type export `{name}` not defined in interface"); + let id = match self.resolve.interfaces[interface].types.get(name.as_str()) { + // If this name is already defined as a type in the + // specified interface then that's ok. For package-local + // interfaces that's expected since the interface was + // fully defined. For remote interfaces it means we're + // using something that was already used elsewhere. In + // both cases continue along. + // + // TODO: ideally this would verify that `def` matches + // the structure of `id`. + Some(id) => *id, + + // If the name is not defined, however, then there's two + // possibilities: + // + // * For package-local interfaces this is an error + // because the package-local interface defined + // everything already and this is referencing + // something that isn't defined. + // + // * For remote interfaces they're never fully declared + // so it's lazily filled in here. This means that the + // view of remote interfaces ends up being the minimal + // slice needed for this resolve, which is what's + // intended. + None => { + if url.scheme() == "pkg" { + bail!("instance type export `{name}` not defined in interface"); + } + let kind = self.convert_defined(def)?; + let id = self.resolve.types.alloc(TypeDef { + name: Some(name.to_string()), + kind, + docs: Default::default(), + owner: TypeOwner::Interface(interface), + }); + let prev = self.resolve.interfaces[interface] + .types + .insert(name.to_string(), id); + assert!(prev.is_none()); + id + } + }; + + // Register the `types::TypeId` with our resolve `TypeId` + // for ensuring type information remains correct throughout + // decoding. + let prev = self.type_map.insert(ty, Type::Id(id)); + assert!(prev.is_none()); + if !self.type_src_map.contains_key(&PtrHash(def)) { + self.type_src_map.insert(PtrHash(def), Type::Id(id)); } + } - // ... otherwise this is a lazily defined type for a foreign - // dependency. - let kind = self.convert_defined(def)?; - let id = self.resolve.types.alloc(TypeDef { - name: Some(name.to_string()), - kind, - docs: Default::default(), - owner: TypeOwner::Interface(interface), - }); + // This has similar logic to types above where we lazily fill in + // functions for remote dependencies and otherwise assert + // they're already defined for local dependencies. + types::ComponentEntityType::Func(ty) => { + let def = match self.info.types.type_from_id(ty) { + Some(types::Type::ComponentFunc(ty)) => ty, + _ => unreachable!(), + }; + if self.resolve.interfaces[interface] + .functions + .contains_key(name.as_str()) + { + // TODO: should ideally verify that function signatures + // match. + continue; + } + if url.scheme() == "pkg" { + bail!("instance function export `{name}` not defined in interface"); + } + let func = self.convert_function(name, def)?; let prev = self.resolve.interfaces[interface] - .types - .insert(name.to_string(), id); + .functions + .insert(name.to_string(), func); assert!(prev.is_none()); - id } - }; - let prev = self.type_map.insert(ty, Type::Id(id)); - assert!(prev.is_none()); - let prev = self.type_src_map.insert(PtrHash(def), Type::Id(id)); - assert!(prev.is_none()); + + _ => bail!("instance type export `{name}` is not a type"), + } } - Ok(()) + Ok(interface) } fn extract_url_interface(&mut self, url: &Url) -> Result { @@ -348,10 +409,11 @@ impl WitPackageDecoder<'_> { // Lazily create a `Package` as necessary, along with the document and // interface. - let package = *self.url_to_package.entry(url).or_insert_with(|| { + let package = *self.url_to_package.entry(url.clone()).or_insert_with(|| { self.resolve.packages.alloc(Package { name: package_name.to_string(), documents: Default::default(), + url: Some(url.to_string()), }) }); let doc = *self.resolve.packages[package] @@ -376,7 +438,7 @@ impl WitPackageDecoder<'_> { url: None, docs: Default::default(), types: IndexMap::default(), - functions: Vec::new(), + functions: IndexMap::new(), document: doc, }) }); @@ -394,7 +456,7 @@ impl WitPackageDecoder<'_> { url: None, docs: Default::default(), types: IndexMap::default(), - functions: Vec::new(), + functions: IndexMap::new(), document: doc, }; @@ -447,33 +509,9 @@ impl WitPackageDecoder<'_> { Some(types::Type::ComponentFunc(ty)) => ty, _ => unreachable!(), }; - let params = ty - .params - .iter() - .map(|(name, ty)| Ok((name.to_string(), self.convert_valtype(ty)?))) - .collect::>>()?; - let results = if ty.results.len() == 1 { - Results::Anon(self.convert_valtype(&ty.results[0].1)?) - } else { - Results::Named( - ty.results - .iter() - .map(|(name, ty)| { - Ok(( - name.as_ref().unwrap().to_string(), - self.convert_valtype(ty)?, - )) - }) - .collect::>>()?, - ) - }; - interface.functions.push(Function { - docs: Default::default(), - kind: FunctionKind::Freestanding, - name: name.to_string(), - params, - results, - }); + let func = self.convert_function(&name, ty)?; + let prev = interface.functions.insert(name.to_string(), func); + assert!(prev.is_none()); } _ => bail!("instance type export `{name}` is not a type or function"), }; @@ -494,6 +532,9 @@ impl WitPackageDecoder<'_> { exports: Default::default(), document, }; + + // Imports in this component type represent all of the imported items + // into the world itself, so all imports get registered. for (name, (url, ty)) in ty.imports.iter() { let item = match ty { types::ComponentEntityType::Instance(idx) => { @@ -502,15 +543,15 @@ impl WitPackageDecoder<'_> { _ => unreachable!(), }; let id = match url { - Some(url) => { - // TODO: walk over types in the interface and - // register them with - // `self.{type_map,type_src_map}`. - if true { - panic!() - } - self.extract_url_interface(url)? - } + // If a URL is specified then the import is either to a + // package-local or foreign interface, and both + // situations are handled in `register_import`. + Some(url) => self.register_import(url, ty)?, + + // Without a URL this indicates an inline interface that + // wasn't declared explicitly elsewhere with a name, and + // `register_interface` will create a new `Interface` + // with no name. None => self.register_interface(document, None, ty)?, }; WorldItem::Interface(id) @@ -519,6 +560,7 @@ impl WitPackageDecoder<'_> { }; world.imports.insert(name.to_string(), item); } + for (name, (url, ty)) in ty.exports.iter() { let item = match ty { types::ComponentEntityType::Instance(idx) => { @@ -539,6 +581,36 @@ impl WitPackageDecoder<'_> { Ok(self.resolve.worlds.alloc(world)) } + fn convert_function(&mut self, name: &str, ty: &types::ComponentFuncType) -> Result { + let params = ty + .params + .iter() + .map(|(name, ty)| Ok((name.to_string(), self.convert_valtype(ty)?))) + .collect::>>()?; + let results = if ty.results.len() == 1 { + Results::Anon(self.convert_valtype(&ty.results[0].1)?) + } else { + Results::Named( + ty.results + .iter() + .map(|(name, ty)| { + Ok(( + name.as_ref().unwrap().to_string(), + self.convert_valtype(ty)?, + )) + }) + .collect::>>()?, + ) + }; + Ok(Function { + docs: Default::default(), + kind: FunctionKind::Freestanding, + name: name.to_string(), + params, + results, + }) + } + fn convert_valtype(&mut self, ty: &types::ComponentValType) -> Result { let id = match ty { types::ComponentValType::Primitive(ty) => return Ok(self.convert_primitive(*ty)), diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index b408936041..86b8a63e4a 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -74,8 +74,8 @@ mod wit; pub use wit::encode; -//mod types; -//use types::{InstanceTypeEncoder, RootTypeEncoder, ValtypeEncoder}; +mod types; +// use types::{InstanceTypeEncoder, RootTypeEncoder, ValtypeEncoder}; //mod world; //use world::{ComponentWorld, ImportedInterface}; diff --git a/crates/wit-component/src/encoding/types.rs b/crates/wit-component/src/encoding/types.rs index 454b7a75f8..d74b448c2f 100644 --- a/crates/wit-component/src/encoding/types.rs +++ b/crates/wit-component/src/encoding/types.rs @@ -1,10 +1,10 @@ -use super::EncodingState; +// use super::EncodingState; use anyhow::Result; use std::collections::HashMap; use wasm_encoder::*; use wit_parser::{ - Document, Enum, Flags, Function, InterfaceId, Params, Record, Result_, Results, Tuple, Type, - TypeDefKind, TypeId, Union, Variant, + Enum, Flags, Function, Params, Record, Resolve, Result_, Results, Tuple, Type, TypeDefKind, + TypeId, Union, Variant, }; /// Represents a key type for interface function definitions. @@ -33,11 +33,8 @@ pub trait ValtypeEncoder<'a> { /// place its results. fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>); - /// Aliases the provided type as a new type using an outer depth of 0. - fn define_type_alias_self(&mut self, ty: u32) -> u32; - /// Creates an export item for the specified type index. - fn export_type(&mut self, index: u32, name: &'a str); + fn export_type(&mut self, index: u32, name: &'a str) -> u32; /// Returns a map of all types previously defined in this type index space. fn type_map(&mut self) -> &mut HashMap; @@ -54,7 +51,7 @@ pub trait ValtypeEncoder<'a> { /// Encodes a new function type which is defined within the provided /// document. - fn encode_func_type(&mut self, doc: &'a Document, func: &'a Function) -> Result { + fn encode_func_type(&mut self, resolve: &'a Resolve, func: &'a Function) -> Result { let key = FunctionKey { params: &func.params, results: &func.results, @@ -64,7 +61,7 @@ pub trait ValtypeEncoder<'a> { } // Encode all referenced parameter types from this function. - let params: Vec<_> = self.encode_params(doc, &func.params)?; + let params: Vec<_> = self.encode_params(resolve, &func.params)?; enum EncodedResults<'a> { Named(Vec<(&'a str, ComponentValType)>), @@ -72,8 +69,8 @@ pub trait ValtypeEncoder<'a> { } let results = match &func.results { - Results::Named(rs) => EncodedResults::Named(self.encode_params(doc, rs)?), - Results::Anon(ty) => EncodedResults::Anon(self.encode_valtype(doc, ty)?), + Results::Named(rs) => EncodedResults::Named(self.encode_params(resolve, rs)?), + Results::Anon(ty) => EncodedResults::Anon(self.encode_valtype(resolve, ty)?), }; // Encode the function type @@ -90,21 +87,21 @@ pub trait ValtypeEncoder<'a> { fn encode_params( &mut self, - doc: &'a Document, + resolve: &'a Resolve, params: &'a Params, ) -> Result> { params .iter() - .map(|(name, ty)| Ok((name.as_str(), self.encode_valtype(doc, ty)?))) + .map(|(name, ty)| Ok((name.as_str(), self.encode_valtype(resolve, ty)?))) .collect::>() } - /// Encodes the `ty`, defined within `doc`, into this encoder and returns + /// Encodes the `ty`, defined within `resolve`, into this encoder and returns /// the corresponding `ComponentValType` that it represents. /// /// This will recursively define the entire structure of `ty` within `self` /// if necessary. - fn encode_valtype(&mut self, doc: &'a Document, ty: &Type) -> Result { + fn encode_valtype(&mut self, resolve: &'a Resolve, ty: &Type) -> Result { Ok(match *ty { Type::Bool => ComponentValType::Primitive(PrimitiveValType::Bool), Type::U8 => ComponentValType::Primitive(PrimitiveValType::U8), @@ -134,34 +131,26 @@ pub trait ValtypeEncoder<'a> { } // ... and failing all that insert the type export. - let ty = &doc.types[id]; + let ty = &resolve.types[id]; let mut encoded = match &ty.kind { - TypeDefKind::Record(r) => self.encode_record(doc, r)?, - TypeDefKind::Tuple(t) => self.encode_tuple(doc, t)?, + TypeDefKind::Record(r) => self.encode_record(resolve, r)?, + TypeDefKind::Tuple(t) => self.encode_tuple(resolve, t)?, TypeDefKind::Flags(r) => self.encode_flags(r)?, - TypeDefKind::Variant(v) => self.encode_variant(doc, v)?, - TypeDefKind::Union(u) => self.encode_union(doc, u)?, - TypeDefKind::Option(t) => self.encode_option(doc, t)?, - TypeDefKind::Result(r) => self.encode_result(doc, r)?, + TypeDefKind::Variant(v) => self.encode_variant(resolve, v)?, + TypeDefKind::Union(u) => self.encode_union(resolve, u)?, + TypeDefKind::Option(t) => self.encode_option(resolve, t)?, + TypeDefKind::Result(r) => self.encode_result(resolve, r)?, TypeDefKind::Enum(e) => self.encode_enum(e)?, TypeDefKind::List(ty) => { - let ty = self.encode_valtype(doc, ty)?; + let ty = self.encode_valtype(resolve, ty)?; let (index, encoder) = self.defined_type(); encoder.list(ty); ComponentValType::Type(index) } - TypeDefKind::Type(ty) => { - match self.encode_valtype(doc, ty)? { - // This is `type a = b` which is encoded as an - // `outer` alias of depth 0 - ComponentValType::Type(index) => { - ComponentValType::Type(self.define_type_alias_self(index)) - } - t @ ComponentValType::Primitive(_) => t, - } - } + TypeDefKind::Type(ty) => self.encode_valtype(resolve, ty)?, TypeDefKind::Future(_) => todo!("encoding for future type"), TypeDefKind::Stream(_) => todo!("encoding for stream type"), + TypeDefKind::Unknown => unreachable!(), }; if let Some(name) = &ty.name { @@ -175,7 +164,7 @@ pub trait ValtypeEncoder<'a> { index } }; - self.export_type(index, name); + let index = self.export_type(index, name); encoded = ComponentValType::Type(index); } @@ -191,20 +180,20 @@ pub trait ValtypeEncoder<'a> { fn encode_optional_valtype( &mut self, - doc: &'a Document, + resolve: &'a Resolve, ty: Option<&Type>, ) -> Result> { match ty { - Some(ty) => self.encode_valtype(doc, ty).map(Some), + Some(ty) => self.encode_valtype(resolve, ty).map(Some), None => Ok(None), } } - fn encode_record(&mut self, doc: &'a Document, record: &Record) -> Result { + fn encode_record(&mut self, resolve: &'a Resolve, record: &Record) -> Result { let fields = record .fields .iter() - .map(|f| Ok((f.name.as_str(), self.encode_valtype(doc, &f.ty)?))) + .map(|f| Ok((f.name.as_str(), self.encode_valtype(resolve, &f.ty)?))) .collect::>>()?; let (index, encoder) = self.defined_type(); @@ -212,11 +201,11 @@ pub trait ValtypeEncoder<'a> { Ok(ComponentValType::Type(index)) } - fn encode_tuple(&mut self, doc: &'a Document, tuple: &Tuple) -> Result { + fn encode_tuple(&mut self, resolve: &'a Resolve, tuple: &Tuple) -> Result { let tys = tuple .types .iter() - .map(|ty| self.encode_valtype(doc, ty)) + .map(|ty| self.encode_valtype(resolve, ty)) .collect::>>()?; let (index, encoder) = self.defined_type(); encoder.tuple(tys); @@ -229,14 +218,18 @@ pub trait ValtypeEncoder<'a> { Ok(ComponentValType::Type(index)) } - fn encode_variant(&mut self, doc: &'a Document, variant: &Variant) -> Result { + fn encode_variant( + &mut self, + resolve: &'a Resolve, + variant: &Variant, + ) -> Result { let cases = variant .cases .iter() .map(|c| { Ok(( c.name.as_str(), - self.encode_optional_valtype(doc, c.ty.as_ref())?, + self.encode_optional_valtype(resolve, c.ty.as_ref())?, None, // TODO: support defaulting case values in the future )) }) @@ -247,11 +240,11 @@ pub trait ValtypeEncoder<'a> { Ok(ComponentValType::Type(index)) } - fn encode_union(&mut self, doc: &'a Document, union: &Union) -> Result { + fn encode_union(&mut self, resolve: &'a Resolve, union: &Union) -> Result { let tys = union .cases .iter() - .map(|c| self.encode_valtype(doc, &c.ty)) + .map(|c| self.encode_valtype(resolve, &c.ty)) .collect::>>()?; let (index, encoder) = self.defined_type(); @@ -259,16 +252,20 @@ pub trait ValtypeEncoder<'a> { Ok(ComponentValType::Type(index)) } - fn encode_option(&mut self, doc: &'a Document, payload: &Type) -> Result { - let ty = self.encode_valtype(doc, payload)?; + fn encode_option(&mut self, resolve: &'a Resolve, payload: &Type) -> Result { + let ty = self.encode_valtype(resolve, payload)?; let (index, encoder) = self.defined_type(); encoder.option(ty); Ok(ComponentValType::Type(index)) } - fn encode_result(&mut self, doc: &'a Document, result: &Result_) -> Result { - let ok = self.encode_optional_valtype(doc, result.ok.as_ref())?; - let error = self.encode_optional_valtype(doc, result.err.as_ref())?; + fn encode_result( + &mut self, + resolve: &'a Resolve, + result: &Result_, + ) -> Result { + let ok = self.encode_optional_valtype(resolve, result.ok.as_ref())?; + let error = self.encode_optional_valtype(resolve, result.err.as_ref())?; let (index, encoder) = self.defined_type(); encoder.result(ok, error); Ok(ComponentValType::Type(index)) @@ -281,97 +278,97 @@ pub trait ValtypeEncoder<'a> { } } -pub struct RootTypeEncoder<'state, 'a> { - pub state: &'state mut EncodingState<'a>, - pub type_exports: Vec<(u32, &'a str)>, - pub interface: InterfaceId, -} - -impl<'a> ValtypeEncoder<'a> for RootTypeEncoder<'_, 'a> { - fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { - self.state.component.defined_type() - } - fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { - self.state.component.function_type() - } - fn define_type_alias_self(&mut self, idx: u32) -> u32 { - self.state.component.alias_outer_type(0, idx) - } - fn export_type(&mut self, idx: u32, name: &'a str) { - self.type_exports.push((idx, name)); - } - fn type_map(&mut self) -> &mut HashMap { - &mut self.state.type_map - } - fn maybe_import_type(&mut self, id: TypeId) -> Option { - // If this `id` is anonymous or belongs to this interface there's - // nothing to import, it needs defining. Otherwise alias the type from - // an import into this component's root namespace. - let other = self.state.info.encoder.metadata.doc.types[id].interface?; - if other == self.interface { - return None; - } - // TODO: this doesn't work for importing types from other exports. That - // just trips an assertion here. - Some(self.state.index_of_type_export(id)) - } - fn func_type_map(&mut self) -> &mut HashMap, u32> { - &mut self.state.func_type_map - } -} - -pub struct InstanceTypeEncoder<'state, 'a> { - pub state: &'state mut EncodingState<'a>, - pub interface: InterfaceId, - pub type_map: HashMap, - pub func_type_map: HashMap, u32>, - pub ty: InstanceType, -} - -impl<'a> ValtypeEncoder<'a> for InstanceTypeEncoder<'_, 'a> { - fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { - (self.ty.type_count(), self.ty.ty().defined_type()) - } - fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { - (self.ty.type_count(), self.ty.ty().function()) - } - fn define_type_alias_self(&mut self, idx: u32) -> u32 { - let ret = self.ty.type_count(); - self.ty.alias(Alias::Outer { - count: 0, - index: idx, - kind: ComponentOuterAliasKind::Type, - }); - ret - } - fn export_type(&mut self, idx: u32, name: &str) { - self.ty - .export(name, "", ComponentTypeRef::Type(TypeBounds::Eq, idx)); - } - fn type_map(&mut self) -> &mut HashMap { - &mut self.type_map - } - fn maybe_import_type(&mut self, id: TypeId) -> Option { - // If this `id` is anonymous or belongs to this interface - // there's nothing to import, it needs defining. Otherwise - // perform the importing process with an outer alias to the - // parent component. - let other = self.state.info.encoder.metadata.doc.types[id].interface?; - if other == self.interface { - return None; - } - - let outer_idx = self.state.index_of_type_export(id); - let ret = self.ty.type_count(); - self.type_map.insert(id, ret); - self.ty.alias(Alias::Outer { - count: 1, - index: outer_idx, - kind: ComponentOuterAliasKind::Type, - }); - Some(ret) - } - fn func_type_map(&mut self) -> &mut HashMap, u32> { - &mut self.func_type_map - } -} +// pub struct RootTypeEncoder<'state, 'a> { +// pub state: &'state mut EncodingState<'a>, +// pub type_exports: Vec<(u32, &'a str)>, +// pub interface: InterfaceId, +// } + +// impl<'a> ValtypeEncoder<'a> for RootTypeEncoder<'_, 'a> { +// fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { +// self.state.component.defined_type() +// } +// fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { +// self.state.component.function_type() +// } +// fn define_type_alias_self(&mut self, idx: u32) -> u32 { +// self.state.component.alias_outer_type(0, idx) +// } +// fn export_type(&mut self, idx: u32, name: &'a str) { +// self.type_exports.push((idx, name)); +// } +// fn type_map(&mut self) -> &mut HashMap { +// &mut self.state.type_map +// } +// fn maybe_import_type(&mut self, id: TypeId) -> Option { +// // If this `id` is anonymous or belongs to this interface there's +// // nothing to import, it needs defining. Otherwise alias the type from +// // an import into this component's root namespace. +// let other = self.state.info.encoder.metadata.resolve.types[id].interface?; +// if other == self.interface { +// return None; +// } +// // TODO: this doesn't work for importing types from other exports. That +// // just trips an assertion here. +// Some(self.state.index_of_type_export(id)) +// } +// fn func_type_map(&mut self) -> &mut HashMap, u32> { +// &mut self.state.func_type_map +// } +// } + +// pub struct InstanceTypeEncoder<'state, 'a> { +// pub state: &'state mut EncodingState<'a>, +// pub interface: InterfaceId, +// pub type_map: HashMap, +// pub func_type_map: HashMap, u32>, +// pub ty: InstanceType, +// } + +// impl<'a> ValtypeEncoder<'a> for InstanceTypeEncoder<'_, 'a> { +// fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { +// (self.ty.type_count(), self.ty.ty().defined_type()) +// } +// fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { +// (self.ty.type_count(), self.ty.ty().function()) +// } +// fn define_type_alias_self(&mut self, idx: u32) -> u32 { +// let ret = self.ty.type_count(); +// self.ty.alias(Alias::Outer { +// count: 0, +// index: idx, +// kind: ComponentOuterAliasKind::Type, +// }); +// ret +// } +// fn export_type(&mut self, idx: u32, name: &str) { +// self.ty +// .export(name, "", ComponentTypeRef::Type(TypeBounds::Eq, idx)); +// } +// fn type_map(&mut self) -> &mut HashMap { +// &mut self.type_map +// } +// fn maybe_import_type(&mut self, id: TypeId) -> Option { +// // If this `id` is anonymous or belongs to this interface +// // there's nothing to import, it needs defining. Otherwise +// // perform the importing process with an outer alias to the +// // parent component. +// let other = self.state.info.encoder.metadata.resolve.types[id].interface?; +// if other == self.interface { +// return None; +// } + +// let outer_idx = self.state.index_of_type_export(id); +// let ret = self.ty.type_count(); +// self.type_map.insert(id, ret); +// self.ty.alias(Alias::Outer { +// count: 1, +// index: outer_idx, +// kind: ComponentOuterAliasKind::Type, +// }); +// Some(ret) +// } +// fn func_type_map(&mut self) -> &mut HashMap, u32> { +// &mut self.func_type_map +// } +// } diff --git a/crates/wit-component/src/encoding/wit.rs b/crates/wit-component/src/encoding/wit.rs index 7001a8a390..62a7bd82b1 100644 --- a/crates/wit-component/src/encoding/wit.rs +++ b/crates/wit-component/src/encoding/wit.rs @@ -1,7 +1,238 @@ +use crate::builder::ComponentBuilder; +use crate::encoding::types::{FunctionKey, ValtypeEncoder}; use anyhow::Result; +use indexmap::IndexMap; +use std::collections::HashMap; +use std::mem; +use url::Url; +use wasm_encoder::*; use wit_parser::*; /// TODO pub fn encode(resolve: &Resolve, package: PackageId) -> Result> { - panic!() + let mut encoder = Encoder { + component: ComponentBuilder::default(), + resolve, + package, + }; + encoder.run()?; + Ok(encoder.component.finish()) +} + +struct Encoder<'a> { + component: ComponentBuilder, + resolve: &'a Resolve, + package: PackageId, +} + +impl Encoder<'_> { + fn run(&mut self) -> Result<()> { + for (name, doc) in self.resolve.packages[self.package].documents.iter() { + let ty = self.encode_document(*doc)?; + self.component + .export(name, "", ComponentExportKind::Type, ty); + } + Ok(()) + } + + fn encode_document(&mut self, doc: DocumentId) -> Result { + let mut set = LiveTypes::default(); + + // Note that worlds within `doc` are explicitly excluded here since + // worlds have their own scope and import everything themselves, this + // transitive import set is only required for the interfaces defined in + // the document. + for (_, id) in self.resolve.documents[doc].interfaces.iter() { + set.add_interface(self.resolve, *id); + } + + let mut imported_interfaces = IndexMap::new(); + for ty in set.iter() { + let owner = match &self.resolve.types[ty].owner { + TypeOwner::Interface(i) => *i, + _ => continue, + }; + if self.resolve.interfaces[owner].document == doc { + continue; + } + imported_interfaces + .entry(owner) + .or_insert(Vec::new()) + .push(ty); + } + + let mut encoder = InterfaceEncoder::new(self.resolve); + for (owner, ids) in imported_interfaces { + let owner_iface = &self.resolve.interfaces[owner]; + for id in ids { + encoder.encode_valtype(self.resolve, &Type::Id(id))?; + } + let instance = encoder.take_instance(); + let idx = encoder.outer.type_count(); + encoder.outer.ty().instance(&instance); + encoder.import_map.insert(owner, encoder.instances); + encoder.instances += 1; + + let import_name = owner_iface.name.as_ref().unwrap(); + let url = self.url_of(owner); + encoder + .outer + .import(import_name, &url, ComponentTypeRef::Instance(idx)); + } + + let doc = &self.resolve.documents[doc]; + for (name, interface) in doc.interfaces.iter() { + let idx = encoder.encode_instance(*interface)?; + encoder + .outer + .export(name, "", ComponentTypeRef::Instance(idx)); + } + + for world in doc.worlds.iter() { + let world = &self.resolve.worlds[*world]; + let mut component = InterfaceEncoder::new(self.resolve); + for (name, import) in world.imports.iter() { + let (url, ty) = match import { + WorldItem::Interface(i) => { + let idx = component.encode_instance(*i)?; + (self.url_of(*i), ComponentTypeRef::Instance(idx)) + } + WorldItem::Function(_) => panic!(), + }; + component.outer.import(name, &url, ty); + } + for (name, export) in world.exports.iter() { + let (url, ty) = match export { + WorldItem::Interface(i) => { + let idx = component.encode_instance(*i)?; + (self.url_of(*i), ComponentTypeRef::Instance(idx)) + } + WorldItem::Function(_) => panic!(), + }; + component.outer.export(name, &url, ty); + } + let idx = encoder.outer.type_count(); + encoder.outer.ty().component(&component.outer); + encoder + .outer + .export(&world.name, "", ComponentTypeRef::Component(idx)); + } + + Ok(self.component.component_type(&encoder.outer)) + } + + fn url_of(&self, interface: InterfaceId) -> String { + let iface = &self.resolve.interfaces[interface]; + let iface_name = match &iface.name { + Some(name) => name, + None => return String::new(), + }; + let doc = &self.resolve.documents[iface.document]; + let pkg = doc.package.unwrap(); + let mut base = if pkg == self.package { + Url::parse("pkg:/").unwrap() + } else { + let pkg = &self.resolve.packages[pkg]; + Url::parse(pkg.url.as_ref().unwrap()).unwrap() + }; + let mut segments = base.path_segments_mut().unwrap(); + segments.push(&doc.name); + segments.push(iface_name); + drop(segments); + base.to_string() + } +} + +struct InterfaceEncoder<'a> { + resolve: &'a Resolve, + outer: ComponentType, + ty: InstanceType, + func_type_map: HashMap, u32>, + type_map: HashMap, + import_map: HashMap, + outer_type_map: HashMap, + instances: u32, +} + +impl InterfaceEncoder<'_> { + fn new(resolve: &Resolve) -> InterfaceEncoder<'_> { + InterfaceEncoder { + resolve, + outer: ComponentType::new(), + ty: InstanceType::new(), + type_map: Default::default(), + func_type_map: Default::default(), + import_map: Default::default(), + outer_type_map: Default::default(), + instances: 0, + } + } + + fn encode_instance(&mut self, interface: InterfaceId) -> Result { + let iface = &self.resolve.interfaces[interface]; + for (_, id) in iface.types.iter() { + self.encode_valtype(self.resolve, &Type::Id(*id))?; + } + for (name, func) in iface.functions.iter() { + let ty = self.encode_func_type(self.resolve, func)?; + self.ty.export(name, "", ComponentTypeRef::Func(ty)); + } + let instance = self.take_instance(); + let idx = self.outer.type_count(); + self.outer.ty().instance(&instance); + self.import_map.insert(interface, self.instances); + self.instances += 1; + Ok(idx) + } + + fn take_instance(&mut self) -> InstanceType { + self.func_type_map.clear(); + self.type_map.clear(); + mem::take(&mut self.ty) + } +} + +impl<'a> ValtypeEncoder<'a> for InterfaceEncoder<'a> { + fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { + (self.ty.type_count(), self.ty.ty().defined_type()) + } + fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { + (self.ty.type_count(), self.ty.ty().function()) + } + fn export_type(&mut self, index: u32, name: &'a str) -> u32 { + let ret = self.ty.type_count(); + self.ty + .export(name, "", ComponentTypeRef::Type(TypeBounds::Eq, index)); + ret + } + fn type_map(&mut self) -> &mut HashMap { + &mut self.type_map + } + fn maybe_import_type(&mut self, id: TypeId) -> Option { + let ty = &self.resolve.types[id]; + let owner = match ty.owner { + TypeOwner::Interface(i) => i, + _ => return None, + }; + let instance = *self.import_map.get(&owner)?; + let outer_idx = *self.outer_type_map.entry(id).or_insert_with(|| { + let ret = self.outer.type_count(); + self.outer.alias(Alias::InstanceExport { + instance, + name: ty.name.as_ref().unwrap(), + kind: ComponentExportKind::Type, + }); + ret + }); + let ret = self.ty.type_count(); + self.ty.alias(Alias::Outer { + count: 1, + index: outer_idx, + kind: ComponentOuterAliasKind::Type, + }); + Some(ret) + } + fn func_type_map(&mut self) -> &mut HashMap, u32> { + &mut self.func_type_map + } } diff --git a/crates/wit-component/src/lib.rs b/crates/wit-component/src/lib.rs index 383a939ed9..91e4255c15 100644 --- a/crates/wit-component/src/lib.rs +++ b/crates/wit-component/src/lib.rs @@ -7,7 +7,7 @@ use std::fmt::Display; use std::str::FromStr; use wasm_encoder::CanonicalOption; -// mod builder; +mod builder; mod decoding; mod encoding; // mod gc; diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index e0cb970310..669d676f12 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -91,11 +91,11 @@ impl DocumentPrinter { self.declare_type(resolve, &Type::Id(id))?; } - for (i, func) in interface.functions.iter().enumerate() { + for (i, (name, func)) in interface.functions.iter().enumerate() { if i > 0 { self.output.push_str("\n"); } - write!(&mut self.output, "{}: func(", func.name)?; + write!(&mut self.output, "{name}: func(")?; for (i, (name, ty)) in func.params.iter().enumerate() { if i > 0 { self.output.push_str(", "); @@ -151,7 +151,7 @@ impl DocumentPrinter { self.print_path_to_interface(resolve, *id, cur_doc)?; self.output.push_str("\n"); } else { - writeln!(self.output, "{desc} {name}: interface {{")?; + writeln!(self.output, "interface {{")?; self.print_interface(resolve, *id)?; writeln!(self.output, "}}")?; } diff --git a/crates/wit-component/test-helpers/src/lib.rs b/crates/wit-component/test-helpers/src/lib.rs index d5947e4862..ccea6cb5ad 100644 --- a/crates/wit-component/test-helpers/src/lib.rs +++ b/crates/wit-component/test-helpers/src/lib.rs @@ -1,32 +1,44 @@ use wit_parser::abi::{AbiVariant, WasmType}; -use wit_parser::{Document, Function}; +use wit_parser::{Function, Resolve, WorldId, WorldItem}; -pub fn dummy_module(doc: &Document) -> Vec { - let world = doc.default_world().unwrap(); - let world = &doc.worlds[world]; +pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec { + let world = &resolve.worlds[world]; let mut wat = String::new(); wat.push_str("(module\n"); for (name, import) in world.imports.iter() { - for func in doc.interfaces[*import].functions.iter() { - let sig = doc.wasm_signature(AbiVariant::GuestImport, func); + match import { + WorldItem::Function(func) => { + let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); - wat.push_str(&format!("(import \"{name}\" \"{}\" (func", func.name)); - push_tys(&mut wat, "param", &sig.params); - push_tys(&mut wat, "result", &sig.results); - wat.push_str("))\n"); - } - } + wat.push_str(&format!("(import \"\" \"{}\" (func", func.name)); + push_tys(&mut wat, "param", &sig.params); + push_tys(&mut wat, "result", &sig.results); + wat.push_str("))\n"); + } + WorldItem::Interface(import) => { + for (_, func) in resolve.interfaces[*import].functions.iter() { + let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); - for (name, export) in world.exports.iter() { - for func in doc.interfaces[*export].functions.iter() { - let name = func.core_export_name(Some(name)); - push_func(&mut wat, &name, doc, func); + wat.push_str(&format!("(import \"{name}\" \"{}\" (func", func.name)); + push_tys(&mut wat, "param", &sig.params); + push_tys(&mut wat, "result", &sig.results); + wat.push_str("))\n"); + } + } } } - if let Some(default) = world.default { - for func in doc.interfaces[default].functions.iter() { - push_func(&mut wat, &func.name, doc, func); + for (name, export) in world.exports.iter() { + match export { + WorldItem::Function(func) => { + push_func(&mut wat, &func.name, resolve, func); + } + WorldItem::Interface(export) => { + for (_, func) in resolve.interfaces[*export].functions.iter() { + let name = func.core_export_name(Some(name)); + push_func(&mut wat, &name, resolve, func); + } + } } } @@ -38,14 +50,14 @@ pub fn dummy_module(doc: &Document) -> Vec { return wat::parse_str(&wat).unwrap(); - fn push_func(wat: &mut String, name: &str, doc: &Document, func: &Function) { - let sig = doc.wasm_signature(AbiVariant::GuestExport, func); + fn push_func(wat: &mut String, name: &str, resolve: &Resolve, func: &Function) { + let sig = resolve.wasm_signature(AbiVariant::GuestExport, func); wat.push_str(&format!("(func (export \"{name}\")")); push_tys(wat, "param", &sig.params); push_tys(wat, "result", &sig.results); wat.push_str(" unreachable)\n"); - if doc.guest_export_needs_post_return(func) { + if resolve.guest_export_needs_post_return(func) { wat.push_str(&format!("(func (export \"cabi_post_{name}\")")); push_tys(wat, "param", &sig.results); wat.push_str(")\n"); diff --git a/crates/wit-component/tests/interfaces.rs b/crates/wit-component/tests/interfaces.rs index 99bb237ede..20ee8f816b 100644 --- a/crates/wit-component/tests/interfaces.rs +++ b/crates/wit-component/tests/interfaces.rs @@ -2,113 +2,97 @@ use anyhow::{Context, Result}; use pretty_assertions::assert_eq; use std::fs; use std::path::Path; -use wit_component::{ComponentEncoder, StringEncoding}; -use wit_parser::Document; +use wit_component::DocumentPrinter; +use wit_parser::{Resolve, UnresolvedPackage}; -/// Tests the encoding of the "types only" mode of `wit-component`. +/// Tests the encoding of a WIT package as a WebAssembly binary. /// -/// This test looks in the `interfaces/` directory for test cases in a similar -/// format to those in the `components/` where there's a `world.wit` which is -/// encoded as `types_only.wat` and additionally encoded in normal mode with a -/// dummy module to verify that works as well. +/// This test looks in the `interfaces/` directory for test cases. Each test +/// case is a `*.wit` file or a folder which contains `*.wit` files as part of a +/// multi-file package. Each tests `foo.wit` is accompanied with a `foo.wat` for +/// the WebAssembly text format encoding of the package. Additionally each test +/// has a `foo.print.wit` which is the machine-printed version of the WIT +/// document as decoded from the binary encoded interface. /// /// Run the test with the environment variable `BLESS` set to update -/// the wat baseline file. +/// the baseline files. #[test] fn interface_encoding() -> Result<()> { for entry in fs::read_dir("tests/interfaces")? { let path = entry?.path(); - if !path.is_dir() { - continue; + let name = match path.file_name().and_then(|s| s.to_str()) { + Some(s) => s, + None => continue, + }; + let is_dir = path.is_dir(); + let is_test = is_dir || name.ends_with(".wit"); + if is_test { + run_test(&path, is_dir).context(format!("failed test `{}`", path.display()))?; } - run_test(&path).context(format!("failed test `{}`", path.display()))?; } Ok(()) } -fn run_test(path: &Path) -> Result<()> { - let test_case = path.file_stem().unwrap().to_str().unwrap(); - println!("test {test_case}"); - let wit_path = path.join("world.wit"); - let doc = Document::parse_file(&wit_path)?; - let world = &doc.worlds[doc.default_world()?]; - let world_name = world.name.clone(); - - let assert_output = |wasm: &[u8], wat: &Path| -> Result<()> { - let output = wasmprinter::print_bytes(wasm)?; - - if std::env::var_os("BLESS").is_some() { - fs::write(wat, output)?; - } else { - assert_eq!( - fs::read_to_string(wat)?.replace("\r\n", "\n").trim(), - output.trim(), - "encoding of `{test_case}` did not match the expected wat file `{}`", - wat.display(), - ); - } - - let (decoded_doc, decoded_world) = wit_component::decode_world(&world_name, wasm) - .context(format!("failed to decode bytes for test `{test_case}`"))?; - - if test_case == "empty" { - return Ok(()); - } - - let decoded_world = &decoded_doc.worlds[decoded_world]; - assert_eq!(decoded_world.imports.len(), world.imports.len()); - assert_eq!(decoded_world.exports.len(), world.exports.len()); - assert_eq!(decoded_world.default.is_some(), world.default.is_some()); - - assert_wit(&wit_path, &decoded_doc)?; - Ok(()) +fn run_test(path: &Path, is_dir: bool) -> Result<()> { + println!("running test at {path:?}"); + let mut resolve = Resolve::new(); + let package = if is_dir { + resolve.push_dir(path)? + } else { + resolve.push(UnresolvedPackage::parse_file(path)?, &Default::default())? }; - // Test a types-only component. This ensures that in "types only" mode we - // can recover all the original `*.wit` interfaces from the generated - // artifact. - - println!("testing types only"); - let bytes = ComponentEncoder::default() - .types_only(true) - .validate(true) - .document(doc.clone(), StringEncoding::UTF8)? - .encode() - .with_context(|| { - format!("failed to encode a types-only component for test case `{test_case}`") - })?; - assert_output(&bytes, &path.join("types_only.wat"))?; + let features = wasmparser::WasmFeatures { + component_model: true, + ..Default::default() + }; - // Test a full component with a dummy module as the implementation. This - // tests a different path through `wit-component` to ensure that we can - // recover the original `*.wit` interfaces from the component output. + // First convert the WIT package to a binary WebAssembly output, then + // convert that binary wasm to textual wasm, then assert it matches the + // expectation. + let wasm = wit_component::encode(&resolve, package)?; + let wat = wasmprinter::print_bytes(&wasm)?; + assert_output(&path.with_extension("wat"), &wat)?; + wasmparser::Validator::new_with_features(features) + .validate_all(&wasm) + .context("failed to validate wasm output")?; + + // Next decode a fresh WIT package from the WebAssembly generated. Print + // this package's documents and assert they all match the expectations. + let name = &resolve.packages[package].name; + let decoded = wit_component::decode(name, &wasm)?; + let resolve = decoded.resolve(); + for (name, doc) in resolve.packages[decoded.package()].documents.iter() { + let expected = if is_dir { + path.join(format!("{name}.wit.print")) + } else { + path.with_extension("wit.print") + }; + let output = DocumentPrinter::default().print(&resolve, *doc)?; + assert_output(&expected, &output)?; + } - println!("test dummy module"); - let module = test_helpers::dummy_module(&doc); - let bytes = ComponentEncoder::default() - .module(&module)? - .validate(true) - .document(doc.clone(), StringEncoding::UTF8)? - .encode() - .with_context(|| format!("failed to encode a component for test case `{test_case}`"))?; - assert_output(&bytes, &path.join("component.wat"))?; + // Finally convert the decoded package to wasm again and make sure it + // matches the prior wasm. + let wasm2 = wit_component::encode(resolve, decoded.package())?; + if wasm != wasm2 { + let wat2 = wasmprinter::print_bytes(&wasm)?; + assert_eq!(wat, wat2, "document did not roundtrip correctly"); + } Ok(()) } -fn assert_wit(wit_path: &Path, doc: &Document) -> Result<()> { - let mut printer = wit_component::DocumentPrinter::default(); - let output = printer.print(doc).context("failed to print interface")?; - +fn assert_output(expected: &Path, actual: &str) -> Result<()> { if std::env::var_os("BLESS").is_some() { - fs::write(&wit_path, output)?; + fs::write(&expected, actual)?; } else { assert_eq!( - fs::read_to_string(&wit_path)?.replace("\r\n", "\n"), - output, - "encoding of wit file `{}` did not match the the decoded interface", - wit_path.display(), + fs::read_to_string(&expected)?.replace("\r\n", "\n"), + actual, + "expectation `{}` did not match actual", + expected.display(), ); } Ok(()) diff --git a/crates/wit-component/tests/interfaces/console.wat b/crates/wit-component/tests/interfaces/console.wat new file mode 100644 index 0000000000..4e139a0694 --- /dev/null +++ b/crates/wit-component/tests/interfaces/console.wat @@ -0,0 +1,14 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "arg" string))) + (export (;0;) "log" (func (type 0))) + ) + ) + (export (;0;) "console" (instance (type 0))) + ) + ) + (export (;1;) "console" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/console.wit b/crates/wit-component/tests/interfaces/console.wit new file mode 100644 index 0000000000..f558b2b3ca --- /dev/null +++ b/crates/wit-component/tests/interfaces/console.wit @@ -0,0 +1,3 @@ +interface console { + log: func(arg: string) +} diff --git a/crates/wit-component/tests/interfaces/console.wit.print b/crates/wit-component/tests/interfaces/console.wit.print new file mode 100644 index 0000000000..81a8066f62 --- /dev/null +++ b/crates/wit-component/tests/interfaces/console.wit.print @@ -0,0 +1,4 @@ +interface console { + log: func(arg: string) +} + diff --git a/crates/wit-component/tests/interfaces/default/component.wat b/crates/wit-component/tests/interfaces/default/component.wat deleted file mode 100644 index d8c7697049..0000000000 --- a/crates/wit-component/tests/interfaces/default/component.wat +++ /dev/null @@ -1,23 +0,0 @@ -(component - (core module (;0;) - (type (;0;) (func (param i32))) - (type (;1;) (func (param i32 i32 i32 i32) (result i32))) - (func (;0;) (type 0) (param i32) - unreachable - ) - (func (;1;) (type 1) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "foo" (func 0)) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 1)) - ) - (core instance (;0;) (instantiate 0)) - (alias core export 0 "memory" (core memory (;0;))) - (alias core export 0 "cabi_realloc" (core func (;0;))) - (alias core export 0 "foo" (core func (;1;))) - (type (;0;) (func (param "a" u32))) - (func (;0;) (type 0) (canon lift (core func 1))) - (export (;1;) "foo" (func 0)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/default/types_only.wat b/crates/wit-component/tests/interfaces/default/types_only.wat deleted file mode 100644 index d406ff29b4..0000000000 --- a/crates/wit-component/tests/interfaces/default/types_only.wat +++ /dev/null @@ -1,4 +0,0 @@ -(component - (type (;0;) (func (param "a" u32))) - (export (;1;) "foo" (type 0)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/default/world.wit b/crates/wit-component/tests/interfaces/default/world.wit deleted file mode 100644 index aafc89063c..0000000000 --- a/crates/wit-component/tests/interfaces/default/world.wit +++ /dev/null @@ -1,7 +0,0 @@ -interface interface0 { - foo: func(a: u32) -} - -world default-world { - default export interface0 -} diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate.wat b/crates/wit-component/tests/interfaces/diamond-disambiguate.wat new file mode 100644 index 0000000000..b60b68a4e3 --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate.wat @@ -0,0 +1,66 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) u8) + (export (;1;) "t1" (type (eq 0))) + ) + ) + (export (;0;) "shared" (instance (type 0))) + ) + ) + (export (;1;) "shared1" (type 0)) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) u8) + (export (;1;) "t2" (type (eq 0))) + ) + ) + (export (;0;) "shared" (instance (type 0))) + ) + ) + (export (;3;) "shared2" (type 2)) + (type (;4;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) u8) + (export (;1;) "t1" (type (eq 0))) + ) + ) + (import "shared1" "pkg:/shared1/shared" (instance (type 0))) + (alias export 0 "t1" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "t1" (type (eq 0))) + ) + ) + (import "foo" (instance (type 2))) + (type (;3;) + (instance + (type (;0;) u8) + (export (;1;) "t2" (type (eq 0))) + ) + ) + (import "shared" "pkg:/shared2/shared" (instance (type 3))) + (alias export 2 "t2" (type (;4;))) + (type (;5;) + (instance + (alias outer 1 4 (type (;0;))) + (export (;1;) "t2" (type (eq 0))) + ) + ) + (import "bar" (instance (type 5))) + ) + ) + (export (;0;) "w1" (component (type 0))) + ) + ) + (export (;5;) "join" (type 4)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate/join.wit.print b/crates/wit-component/tests/interfaces/diamond-disambiguate/join.wit.print new file mode 100644 index 0000000000..3c17d5670f --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate/join.wit.print @@ -0,0 +1,10 @@ +world w1 { + import shared1: pkg.shared1.shared + import foo: interface { + use pkg.shared1.shared.{t1} + } + import shared: pkg.shared2.shared + import bar: interface { + use pkg.shared2.shared.{t2} + } +} diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate/shared1.wit.print b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared1.wit.print new file mode 100644 index 0000000000..5ad822561b --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared1.wit.print @@ -0,0 +1,5 @@ +interface shared { + type t1 = u8 + +} + diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate/shared2.wit.print b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared2.wit.print new file mode 100644 index 0000000000..032fea72d2 --- /dev/null +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate/shared2.wit.print @@ -0,0 +1,5 @@ +interface shared { + type t2 = u8 + +} + diff --git a/crates/wit-component/tests/interfaces/empty.wat b/crates/wit-component/tests/interfaces/empty.wat new file mode 100644 index 0000000000..ead69fbd61 --- /dev/null +++ b/crates/wit-component/tests/interfaces/empty.wat @@ -0,0 +1,28 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance) + ) + (export (;0;) "empty" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance) + ) + (import "empty" "pkg:/empty/empty" (instance (type 0))) + (type (;1;) + (instance) + ) + (export (;0;) "empty" "pkg:/empty/empty" (instance (type 1))) + ) + ) + (export (;0;) "empty-world" (component (type 1))) + (type (;2;) + (component) + ) + (export (;1;) "actually-empty-world" (component (type 2))) + ) + ) + (export (;1;) "empty" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/empty.wit b/crates/wit-component/tests/interfaces/empty.wit new file mode 100644 index 0000000000..00342e31dc --- /dev/null +++ b/crates/wit-component/tests/interfaces/empty.wit @@ -0,0 +1,8 @@ +interface empty {} + +world empty-world { + import empty: self.empty + export empty: self.empty +} + +world actually-empty-world {} diff --git a/crates/wit-component/tests/interfaces/empty.wit.print b/crates/wit-component/tests/interfaces/empty.wit.print new file mode 100644 index 0000000000..ed1f9ff70f --- /dev/null +++ b/crates/wit-component/tests/interfaces/empty.wit.print @@ -0,0 +1,9 @@ +interface empty { +} + +world empty-world { + import empty: self.empty + export empty: self.empty +} +world actually-empty-world { +} diff --git a/crates/wit-component/tests/interfaces/empty/component.wat b/crates/wit-component/tests/interfaces/empty/component.wat deleted file mode 100644 index cacb66da59..0000000000 --- a/crates/wit-component/tests/interfaces/empty/component.wat +++ /dev/null @@ -1,14 +0,0 @@ -(component - (core module (;0;) - (type (;0;) (func (param i32 i32 i32 i32) (result i32))) - (func (;0;) (type 0) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 0)) - ) - (core instance (;0;) (instantiate 0)) - (alias core export 0 "memory" (core memory (;0;))) - (alias core export 0 "cabi_realloc" (core func (;0;))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/empty/types_only.wat b/crates/wit-component/tests/interfaces/empty/types_only.wat deleted file mode 100644 index 04743950ba..0000000000 --- a/crates/wit-component/tests/interfaces/empty/types_only.wat +++ /dev/null @@ -1 +0,0 @@ -(component) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/empty/world.wit b/crates/wit-component/tests/interfaces/empty/world.wit deleted file mode 100644 index 8e0c7a6e43..0000000000 --- a/crates/wit-component/tests/interfaces/empty/world.wit +++ /dev/null @@ -1,6 +0,0 @@ -interface empty {} - -world empty-world { - import empty: empty - export empty: empty -} diff --git a/crates/wit-component/tests/interfaces/exports.wat b/crates/wit-component/tests/interfaces/exports.wat new file mode 100644 index 0000000000..4b155d3896 --- /dev/null +++ b/crates/wit-component/tests/interfaces/exports.wat @@ -0,0 +1,30 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (record (field "a" u32))) + (export (;1;) "my-struct" (type (eq 0))) + (type (;2;) (func (param "a" 1) (result string))) + (export (;0;) "my-function" (func (type 2))) + ) + ) + (export (;0;) "foo" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (record (field "a" u32))) + (export (;1;) "my-struct" (type (eq 0))) + (type (;2;) (func (param "a" 1) (result string))) + (export (;0;) "my-function" (func (type 2))) + ) + ) + (export (;0;) "foo" "pkg:/exports/foo" (instance (type 0))) + ) + ) + (export (;0;) "export-foo" (component (type 1))) + ) + ) + (export (;1;) "exports" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/exports/world.wit b/crates/wit-component/tests/interfaces/exports.wit similarity index 84% rename from crates/wit-component/tests/interfaces/exports/world.wit rename to crates/wit-component/tests/interfaces/exports.wit index 1ccb2cc808..c29cb7bdba 100644 --- a/crates/wit-component/tests/interfaces/exports/world.wit +++ b/crates/wit-component/tests/interfaces/exports.wit @@ -7,5 +7,5 @@ interface foo { } world export-foo { - export foo: foo + export foo: self.foo } diff --git a/crates/wit-component/tests/interfaces/exports.wit.print b/crates/wit-component/tests/interfaces/exports.wit.print new file mode 100644 index 0000000000..c29cb7bdba --- /dev/null +++ b/crates/wit-component/tests/interfaces/exports.wit.print @@ -0,0 +1,11 @@ +interface foo { + record my-struct { + a: u32, + } + + my-function: func(a: my-struct) -> string +} + +world export-foo { + export foo: self.foo +} diff --git a/crates/wit-component/tests/interfaces/exports/component.wat b/crates/wit-component/tests/interfaces/exports/component.wat deleted file mode 100644 index 235c3cd572..0000000000 --- a/crates/wit-component/tests/interfaces/exports/component.wat +++ /dev/null @@ -1,32 +0,0 @@ -(component - (core module (;0;) - (type (;0;) (func (param i32) (result i32))) - (type (;1;) (func (param i32))) - (type (;2;) (func (param i32 i32 i32 i32) (result i32))) - (func (;0;) (type 0) (param i32) (result i32) - unreachable - ) - (func (;1;) (type 1) (param i32)) - (func (;2;) (type 2) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "foo#my-function" (func 0)) - (export "cabi_post_foo#my-function" (func 1)) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 2)) - ) - (core instance (;0;) (instantiate 0)) - (alias core export 0 "memory" (core memory (;0;))) - (alias core export 0 "cabi_realloc" (core func (;0;))) - (alias core export 0 "foo#my-function" (core func (;1;))) - (type (;0;) (record (field "a" u32))) - (type (;1;) (func (param "a" 0) (result string))) - (alias core export 0 "cabi_post_foo#my-function" (core func (;2;))) - (func (;0;) (type 1) (canon lift (core func 1) (memory 0) string-encoding=utf8 (post-return 2))) - (instance (;0;) - (export "my-function" (func 0)) - (export "my-struct" (type 0)) - ) - (export (;1;) "foo" (instance 0)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/exports/types_only.wat b/crates/wit-component/tests/interfaces/exports/types_only.wat deleted file mode 100644 index d6f3ddcff0..0000000000 --- a/crates/wit-component/tests/interfaces/exports/types_only.wat +++ /dev/null @@ -1,11 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (record (field "a" u32))) - (export (;1;) "my-struct" (type (eq 0))) - (type (;2;) (func (param "a" 0) (result string))) - (export (;0;) "my-function" (func (type 2))) - ) - ) - (export (;1;) "foo" (type 0)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/flags.wat b/crates/wit-component/tests/interfaces/flags.wat new file mode 100644 index 0000000000..79148f0632 --- /dev/null +++ b/crates/wit-component/tests/interfaces/flags.wat @@ -0,0 +1,78 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (flags "b0")) + (export (;1;) "flag1" (type (eq 0))) + (type (;2;) (flags "b0" "b1")) + (export (;3;) "flag2" (type (eq 2))) + (type (;4;) (flags "b0" "b1" "b2" "b3")) + (export (;5;) "flag4" (type (eq 4))) + (type (;6;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7")) + (export (;7;) "flag8" (type (eq 6))) + (type (;8;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15")) + (export (;9;) "flag16" (type (eq 8))) + (type (;10;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31")) + (export (;11;) "flag32" (type (eq 10))) + (type (;12;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31" "b32" "b33" "b34" "b35" "b36" "b37" "b38" "b39" "b40" "b41" "b42" "b43" "b44" "b45" "b46" "b47" "b48" "b49" "b50" "b51" "b52" "b53" "b54" "b55" "b56" "b57" "b58" "b59" "b60" "b61" "b62" "b63")) + (export (;13;) "flag64" (type (eq 12))) + (type (;14;) (func (param "x" 1) (result 1))) + (export (;0;) "roundtrip-flag1" (func (type 14))) + (type (;15;) (func (param "x" 3) (result 3))) + (export (;1;) "roundtrip-flag2" (func (type 15))) + (type (;16;) (func (param "x" 5) (result 5))) + (export (;2;) "roundtrip-flag4" (func (type 16))) + (type (;17;) (func (param "x" 7) (result 7))) + (export (;3;) "roundtrip-flag8" (func (type 17))) + (type (;18;) (func (param "x" 9) (result 9))) + (export (;4;) "roundtrip-flag16" (func (type 18))) + (type (;19;) (func (param "x" 11) (result 11))) + (export (;5;) "roundtrip-flag32" (func (type 19))) + (type (;20;) (func (param "x" 13) (result 13))) + (export (;6;) "roundtrip-flag64" (func (type 20))) + ) + ) + (export (;0;) "imports" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (flags "b0")) + (export (;1;) "flag1" (type (eq 0))) + (type (;2;) (flags "b0" "b1")) + (export (;3;) "flag2" (type (eq 2))) + (type (;4;) (flags "b0" "b1" "b2" "b3")) + (export (;5;) "flag4" (type (eq 4))) + (type (;6;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7")) + (export (;7;) "flag8" (type (eq 6))) + (type (;8;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15")) + (export (;9;) "flag16" (type (eq 8))) + (type (;10;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31")) + (export (;11;) "flag32" (type (eq 10))) + (type (;12;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31" "b32" "b33" "b34" "b35" "b36" "b37" "b38" "b39" "b40" "b41" "b42" "b43" "b44" "b45" "b46" "b47" "b48" "b49" "b50" "b51" "b52" "b53" "b54" "b55" "b56" "b57" "b58" "b59" "b60" "b61" "b62" "b63")) + (export (;13;) "flag64" (type (eq 12))) + (type (;14;) (func (param "x" 1) (result 1))) + (export (;0;) "roundtrip-flag1" (func (type 14))) + (type (;15;) (func (param "x" 3) (result 3))) + (export (;1;) "roundtrip-flag2" (func (type 15))) + (type (;16;) (func (param "x" 5) (result 5))) + (export (;2;) "roundtrip-flag4" (func (type 16))) + (type (;17;) (func (param "x" 7) (result 7))) + (export (;3;) "roundtrip-flag8" (func (type 17))) + (type (;18;) (func (param "x" 9) (result 9))) + (export (;4;) "roundtrip-flag16" (func (type 18))) + (type (;19;) (func (param "x" 11) (result 11))) + (export (;5;) "roundtrip-flag32" (func (type 19))) + (type (;20;) (func (param "x" 13) (result 13))) + (export (;6;) "roundtrip-flag64" (func (type 20))) + ) + ) + (import "imports" "pkg:/flags/imports" (instance (type 0))) + ) + ) + (export (;0;) "flags-world" (component (type 1))) + ) + ) + (export (;1;) "flags" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/flags/world.wit b/crates/wit-component/tests/interfaces/flags.wit similarity index 98% rename from crates/wit-component/tests/interfaces/flags/world.wit rename to crates/wit-component/tests/interfaces/flags.wit index c1eedf0b35..634968911d 100644 --- a/crates/wit-component/tests/interfaces/flags/world.wit +++ b/crates/wit-component/tests/interfaces/flags.wit @@ -163,5 +163,5 @@ interface imports { } world flags-world { - import imports: imports + import imports: self.imports } diff --git a/crates/wit-component/tests/interfaces/flags.wit.print b/crates/wit-component/tests/interfaces/flags.wit.print new file mode 100644 index 0000000000..634968911d --- /dev/null +++ b/crates/wit-component/tests/interfaces/flags.wit.print @@ -0,0 +1,167 @@ +interface imports { + flags flag1 { + b0, + } + + flags flag2 { + b0, + b1, + } + + flags flag4 { + b0, + b1, + b2, + b3, + } + + flags flag8 { + b0, + b1, + b2, + b3, + b4, + b5, + b6, + b7, + } + + flags flag16 { + b0, + b1, + b2, + b3, + b4, + b5, + b6, + b7, + b8, + b9, + b10, + b11, + b12, + b13, + b14, + b15, + } + + flags flag32 { + b0, + b1, + b2, + b3, + b4, + b5, + b6, + b7, + b8, + b9, + b10, + b11, + b12, + b13, + b14, + b15, + b16, + b17, + b18, + b19, + b20, + b21, + b22, + b23, + b24, + b25, + b26, + b27, + b28, + b29, + b30, + b31, + } + + flags flag64 { + b0, + b1, + b2, + b3, + b4, + b5, + b6, + b7, + b8, + b9, + b10, + b11, + b12, + b13, + b14, + b15, + b16, + b17, + b18, + b19, + b20, + b21, + b22, + b23, + b24, + b25, + b26, + b27, + b28, + b29, + b30, + b31, + b32, + b33, + b34, + b35, + b36, + b37, + b38, + b39, + b40, + b41, + b42, + b43, + b44, + b45, + b46, + b47, + b48, + b49, + b50, + b51, + b52, + b53, + b54, + b55, + b56, + b57, + b58, + b59, + b60, + b61, + b62, + b63, + } + + roundtrip-flag1: func(x: flag1) -> flag1 + + roundtrip-flag2: func(x: flag2) -> flag2 + + roundtrip-flag4: func(x: flag4) -> flag4 + + roundtrip-flag8: func(x: flag8) -> flag8 + + roundtrip-flag16: func(x: flag16) -> flag16 + + roundtrip-flag32: func(x: flag32) -> flag32 + + roundtrip-flag64: func(x: flag64) -> flag64 +} + +world flags-world { + import imports: self.imports +} diff --git a/crates/wit-component/tests/interfaces/flags/component.wat b/crates/wit-component/tests/interfaces/flags/component.wat deleted file mode 100644 index f69be18f33..0000000000 --- a/crates/wit-component/tests/interfaces/flags/component.wat +++ /dev/null @@ -1,112 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (flags "b0")) - (export (;1;) "flag1" (type (eq 0))) - (type (;2;) (func (param "x" 0) (result 0))) - (export (;0;) "roundtrip-flag1" (func (type 2))) - (type (;3;) (flags "b0" "b1")) - (export (;4;) "flag2" (type (eq 3))) - (type (;5;) (func (param "x" 3) (result 3))) - (export (;1;) "roundtrip-flag2" (func (type 5))) - (type (;6;) (flags "b0" "b1" "b2" "b3")) - (export (;7;) "flag4" (type (eq 6))) - (type (;8;) (func (param "x" 6) (result 6))) - (export (;2;) "roundtrip-flag4" (func (type 8))) - (type (;9;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7")) - (export (;10;) "flag8" (type (eq 9))) - (type (;11;) (func (param "x" 9) (result 9))) - (export (;3;) "roundtrip-flag8" (func (type 11))) - (type (;12;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15")) - (export (;13;) "flag16" (type (eq 12))) - (type (;14;) (func (param "x" 12) (result 12))) - (export (;4;) "roundtrip-flag16" (func (type 14))) - (type (;15;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31")) - (export (;16;) "flag32" (type (eq 15))) - (type (;17;) (func (param "x" 15) (result 15))) - (export (;5;) "roundtrip-flag32" (func (type 17))) - (type (;18;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31" "b32" "b33" "b34" "b35" "b36" "b37" "b38" "b39" "b40" "b41" "b42" "b43" "b44" "b45" "b46" "b47" "b48" "b49" "b50" "b51" "b52" "b53" "b54" "b55" "b56" "b57" "b58" "b59" "b60" "b61" "b62" "b63")) - (export (;19;) "flag64" (type (eq 18))) - (type (;20;) (func (param "x" 18) (result 18))) - (export (;6;) "roundtrip-flag64" (func (type 20))) - ) - ) - (import "imports" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32) (result i32))) - (type (;1;) (func (param i32 i32 i32))) - (type (;2;) (func (param i32 i32 i32 i32) (result i32))) - (import "imports" "roundtrip-flag1" (func (;0;) (type 0))) - (import "imports" "roundtrip-flag2" (func (;1;) (type 0))) - (import "imports" "roundtrip-flag4" (func (;2;) (type 0))) - (import "imports" "roundtrip-flag8" (func (;3;) (type 0))) - (import "imports" "roundtrip-flag16" (func (;4;) (type 0))) - (import "imports" "roundtrip-flag32" (func (;5;) (type 0))) - (import "imports" "roundtrip-flag64" (func (;6;) (type 1))) - (func (;7;) (type 2) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 7)) - ) - (core module (;1;) - (type (;0;) (func (param i32 i32 i32))) - (func $indirect-imports-roundtrip-flag64 (;0;) (type 0) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 0 - call_indirect (type 0) - ) - (table (;0;) 1 1 funcref) - (export "0" (func $indirect-imports-roundtrip-flag64)) - (export "$imports" (table 0)) - ) - (core module (;2;) - (type (;0;) (func (param i32 i32 i32))) - (import "" "0" (func (;0;) (type 0))) - (import "" "$imports" (table (;0;) 1 1 funcref)) - (elem (;0;) (i32.const 0) func 0) - ) - (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias export 0 "roundtrip-flag1" (func (;0;))) - (core func (;1;) (canon lower (func 0))) - (alias export 0 "roundtrip-flag2" (func (;1;))) - (core func (;2;) (canon lower (func 1))) - (alias export 0 "roundtrip-flag4" (func (;2;))) - (core func (;3;) (canon lower (func 2))) - (alias export 0 "roundtrip-flag8" (func (;3;))) - (core func (;4;) (canon lower (func 3))) - (alias export 0 "roundtrip-flag16" (func (;4;))) - (core func (;5;) (canon lower (func 4))) - (alias export 0 "roundtrip-flag32" (func (;5;))) - (core func (;6;) (canon lower (func 5))) - (core instance (;1;) - (export "roundtrip-flag64" (func 0)) - (export "roundtrip-flag1" (func 1)) - (export "roundtrip-flag2" (func 2)) - (export "roundtrip-flag4" (func 3)) - (export "roundtrip-flag8" (func 4)) - (export "roundtrip-flag16" (func 5)) - (export "roundtrip-flag32" (func 6)) - ) - (core instance (;2;) (instantiate 0 - (with "imports" (instance 1)) - ) - ) - (alias core export 2 "memory" (core memory (;0;))) - (alias core export 2 "cabi_realloc" (core func (;7;))) - (alias core export 0 "$imports" (core table (;0;))) - (alias export 0 "roundtrip-flag64" (func (;6;))) - (core func (;8;) (canon lower (func 6) (memory 0))) - (core instance (;3;) - (export "$imports" (table 0)) - (export "0" (func 8)) - ) - (core instance (;4;) (instantiate 2 - (with "" (instance 3)) - ) - ) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/flags/types_only.wat b/crates/wit-component/tests/interfaces/flags/types_only.wat deleted file mode 100644 index 6669d38e6f..0000000000 --- a/crates/wit-component/tests/interfaces/flags/types_only.wat +++ /dev/null @@ -1,35 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (flags "b0")) - (export (;1;) "flag1" (type (eq 0))) - (type (;2;) (func (param "x" 0) (result 0))) - (export (;0;) "roundtrip-flag1" (func (type 2))) - (type (;3;) (flags "b0" "b1")) - (export (;4;) "flag2" (type (eq 3))) - (type (;5;) (func (param "x" 3) (result 3))) - (export (;1;) "roundtrip-flag2" (func (type 5))) - (type (;6;) (flags "b0" "b1" "b2" "b3")) - (export (;7;) "flag4" (type (eq 6))) - (type (;8;) (func (param "x" 6) (result 6))) - (export (;2;) "roundtrip-flag4" (func (type 8))) - (type (;9;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7")) - (export (;10;) "flag8" (type (eq 9))) - (type (;11;) (func (param "x" 9) (result 9))) - (export (;3;) "roundtrip-flag8" (func (type 11))) - (type (;12;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15")) - (export (;13;) "flag16" (type (eq 12))) - (type (;14;) (func (param "x" 12) (result 12))) - (export (;4;) "roundtrip-flag16" (func (type 14))) - (type (;15;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31")) - (export (;16;) "flag32" (type (eq 15))) - (type (;17;) (func (param "x" 15) (result 15))) - (export (;5;) "roundtrip-flag32" (func (type 17))) - (type (;18;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31" "b32" "b33" "b34" "b35" "b36" "b37" "b38" "b39" "b40" "b41" "b42" "b43" "b44" "b45" "b46" "b47" "b48" "b49" "b50" "b51" "b52" "b53" "b54" "b55" "b56" "b57" "b58" "b59" "b60" "b61" "b62" "b63")) - (export (;19;) "flag64" (type (eq 18))) - (type (;20;) (func (param "x" 18) (result 18))) - (export (;6;) "roundtrip-flag64" (func (type 20))) - ) - ) - (import "imports" (instance (;0;) (type 0))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/floats.wat b/crates/wit-component/tests/interfaces/floats.wat new file mode 100644 index 0000000000..160b877649 --- /dev/null +++ b/crates/wit-component/tests/interfaces/floats.wat @@ -0,0 +1,38 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "x" float32))) + (export (;0;) "float32-param" (func (type 0))) + (type (;1;) (func (param "x" float64))) + (export (;1;) "float64-param" (func (type 1))) + (type (;2;) (func (result float32))) + (export (;2;) "float32-result" (func (type 2))) + (type (;3;) (func (result float64))) + (export (;3;) "float64-result" (func (type 3))) + ) + ) + (export (;0;) "floats" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "x" float32))) + (export (;0;) "float32-param" (func (type 0))) + (type (;1;) (func (param "x" float64))) + (export (;1;) "float64-param" (func (type 1))) + (type (;2;) (func (result float32))) + (export (;2;) "float32-result" (func (type 2))) + (type (;3;) (func (result float64))) + (export (;3;) "float64-result" (func (type 3))) + ) + ) + (import "floats" "pkg:/floats/floats" (instance (type 0))) + ) + ) + (export (;0;) "floats-world" (component (type 1))) + ) + ) + (export (;1;) "floats" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/floats.wit b/crates/wit-component/tests/interfaces/floats.wit new file mode 100644 index 0000000000..8235b096ed --- /dev/null +++ b/crates/wit-component/tests/interfaces/floats.wit @@ -0,0 +1,10 @@ +interface floats { + float32-param: func(x: float32) + float64-param: func(x: float64) + float32-result: func() -> float32 + float64-result: func() -> float64 +} + +world floats-world { + import floats: self.floats +} diff --git a/crates/wit-component/tests/interfaces/floats/world.wit b/crates/wit-component/tests/interfaces/floats.wit.print similarity index 86% rename from crates/wit-component/tests/interfaces/floats/world.wit rename to crates/wit-component/tests/interfaces/floats.wit.print index 8a0ba3e7b4..c3a6f3384a 100644 --- a/crates/wit-component/tests/interfaces/floats/world.wit +++ b/crates/wit-component/tests/interfaces/floats.wit.print @@ -9,5 +9,5 @@ interface floats { } world floats-world { - import floats: floats + import floats: self.floats } diff --git a/crates/wit-component/tests/interfaces/floats/component.wat b/crates/wit-component/tests/interfaces/floats/component.wat deleted file mode 100644 index dbaa2aba96..0000000000 --- a/crates/wit-component/tests/interfaces/floats/component.wat +++ /dev/null @@ -1,52 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (func (param "x" float32))) - (export (;0;) "float32-param" (func (type 0))) - (type (;1;) (func (param "x" float64))) - (export (;1;) "float64-param" (func (type 1))) - (type (;2;) (func (result float32))) - (export (;2;) "float32-result" (func (type 2))) - (type (;3;) (func (result float64))) - (export (;3;) "float64-result" (func (type 3))) - ) - ) - (import "floats" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param f32))) - (type (;1;) (func (param f64))) - (type (;2;) (func (result f32))) - (type (;3;) (func (result f64))) - (type (;4;) (func (param i32 i32 i32 i32) (result i32))) - (import "floats" "float32-param" (func (;0;) (type 0))) - (import "floats" "float64-param" (func (;1;) (type 1))) - (import "floats" "float32-result" (func (;2;) (type 2))) - (import "floats" "float64-result" (func (;3;) (type 3))) - (func (;4;) (type 4) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 4)) - ) - (alias export 0 "float32-param" (func (;0;))) - (core func (;0;) (canon lower (func 0))) - (alias export 0 "float64-param" (func (;1;))) - (core func (;1;) (canon lower (func 1))) - (alias export 0 "float32-result" (func (;2;))) - (core func (;2;) (canon lower (func 2))) - (alias export 0 "float64-result" (func (;3;))) - (core func (;3;) (canon lower (func 3))) - (core instance (;0;) - (export "float32-param" (func 0)) - (export "float64-param" (func 1)) - (export "float32-result" (func 2)) - (export "float64-result" (func 3)) - ) - (core instance (;1;) (instantiate 0 - (with "floats" (instance 0)) - ) - ) - (alias core export 1 "memory" (core memory (;0;))) - (alias core export 1 "cabi_realloc" (core func (;4;))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/floats/types_only.wat b/crates/wit-component/tests/interfaces/floats/types_only.wat deleted file mode 100644 index 7b82c4442c..0000000000 --- a/crates/wit-component/tests/interfaces/floats/types_only.wat +++ /dev/null @@ -1,15 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (func (param "x" float32))) - (export (;0;) "float32-param" (func (type 0))) - (type (;1;) (func (param "x" float64))) - (export (;1;) "float64-param" (func (type 1))) - (type (;2;) (func (result float32))) - (export (;2;) "float32-result" (func (type 2))) - (type (;3;) (func (result float64))) - (export (;3;) "float64-result" (func (type 3))) - ) - ) - (import "floats" (instance (;0;) (type 0))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/import-and-export.wat b/crates/wit-component/tests/interfaces/import-and-export.wat new file mode 100644 index 0000000000..a490405223 --- /dev/null +++ b/crates/wit-component/tests/interfaces/import-and-export.wat @@ -0,0 +1,40 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (func)) + (export (;0;) "foo" (func (type 0))) + ) + ) + (export (;0;) "foo" (instance (type 0))) + (type (;1;) + (instance + (type (;0;) (func)) + (export (;0;) "bar" (func (type 0))) + ) + ) + (export (;1;) "bar" (instance (type 1))) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) (func)) + (export (;0;) "foo" (func (type 0))) + ) + ) + (import "foo" "pkg:/import-and-export/foo" (instance (type 0))) + (type (;1;) + (instance + (type (;0;) (func)) + (export (;0;) "bar" (func (type 0))) + ) + ) + (export (;0;) "bar" "pkg:/import-and-export/bar" (instance (type 1))) + ) + ) + (export (;0;) "import-and-export" (component (type 2))) + ) + ) + (export (;1;) "import-and-export" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/import-and-export/world.wit b/crates/wit-component/tests/interfaces/import-and-export.wit similarity index 67% rename from crates/wit-component/tests/interfaces/import-and-export/world.wit rename to crates/wit-component/tests/interfaces/import-and-export.wit index 08761a292b..a6f028317c 100644 --- a/crates/wit-component/tests/interfaces/import-and-export/world.wit +++ b/crates/wit-component/tests/interfaces/import-and-export.wit @@ -7,6 +7,6 @@ interface bar { } world import-and-export { - import foo: foo - export bar: bar + import foo: self.foo + export bar: self.bar } diff --git a/crates/wit-component/tests/interfaces/import-and-export.wit.print b/crates/wit-component/tests/interfaces/import-and-export.wit.print new file mode 100644 index 0000000000..a6f028317c --- /dev/null +++ b/crates/wit-component/tests/interfaces/import-and-export.wit.print @@ -0,0 +1,12 @@ +interface foo { + foo: func() +} + +interface bar { + bar: func() +} + +world import-and-export { + import foo: self.foo + export bar: self.bar +} diff --git a/crates/wit-component/tests/interfaces/import-and-export/component.wat b/crates/wit-component/tests/interfaces/import-and-export/component.wat deleted file mode 100644 index 49ebaa5d52..0000000000 --- a/crates/wit-component/tests/interfaces/import-and-export/component.wat +++ /dev/null @@ -1,42 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (func)) - (export (;0;) "foo" (func (type 0))) - ) - ) - (import "foo" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func)) - (type (;1;) (func (param i32 i32 i32 i32) (result i32))) - (import "foo" "foo" (func (;0;) (type 0))) - (func (;1;) (type 0) - unreachable - ) - (func (;2;) (type 1) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "bar#bar" (func 1)) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 2)) - ) - (alias export 0 "foo" (func (;0;))) - (core func (;0;) (canon lower (func 0))) - (core instance (;0;) - (export "foo" (func 0)) - ) - (core instance (;1;) (instantiate 0 - (with "foo" (instance 0)) - ) - ) - (alias core export 1 "memory" (core memory (;0;))) - (alias core export 1 "cabi_realloc" (core func (;1;))) - (alias core export 1 "bar#bar" (core func (;2;))) - (type (;1;) (func)) - (func (;1;) (type 1) (canon lift (core func 2))) - (instance (;1;) - (export "bar" (func 1)) - ) - (export (;2;) "bar" (instance 1)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/import-and-export/types_only.wat b/crates/wit-component/tests/interfaces/import-and-export/types_only.wat deleted file mode 100644 index c62264a4c2..0000000000 --- a/crates/wit-component/tests/interfaces/import-and-export/types_only.wat +++ /dev/null @@ -1,16 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (func)) - (export (;0;) "foo" (func (type 0))) - ) - ) - (import "foo" (instance (;0;) (type 0))) - (type (;1;) - (instance - (type (;0;) (func)) - (export (;0;) "bar" (func (type 0))) - ) - ) - (export (;2;) "bar" (type 1)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/integers.wat b/crates/wit-component/tests/interfaces/integers.wat new file mode 100644 index 0000000000..705988ec35 --- /dev/null +++ b/crates/wit-component/tests/interfaces/integers.wat @@ -0,0 +1,100 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "x" u8))) + (export (;0;) "a1" (func (type 0))) + (type (;1;) (func (param "x" s8))) + (export (;1;) "a2" (func (type 1))) + (type (;2;) (func (param "x" u16))) + (export (;2;) "a3" (func (type 2))) + (type (;3;) (func (param "x" s16))) + (export (;3;) "a4" (func (type 3))) + (type (;4;) (func (param "x" u32))) + (export (;4;) "a5" (func (type 4))) + (type (;5;) (func (param "x" s32))) + (export (;5;) "a6" (func (type 5))) + (type (;6;) (func (param "x" u64))) + (export (;6;) "a7" (func (type 6))) + (type (;7;) (func (param "x" s64))) + (export (;7;) "a8" (func (type 7))) + (type (;8;) (func (param "p1" u8) (param "p2" s8) (param "p3" u16) (param "p4" s16) (param "p5" u32) (param "p6" s32) (param "p7" u64) (param "p8" s64))) + (export (;8;) "a9" (func (type 8))) + (type (;9;) (func (result u8))) + (export (;9;) "r1" (func (type 9))) + (type (;10;) (func (result s8))) + (export (;10;) "r2" (func (type 10))) + (type (;11;) (func (result u16))) + (export (;11;) "r3" (func (type 11))) + (type (;12;) (func (result s16))) + (export (;12;) "r4" (func (type 12))) + (type (;13;) (func (result u32))) + (export (;13;) "r5" (func (type 13))) + (type (;14;) (func (result s32))) + (export (;14;) "r6" (func (type 14))) + (type (;15;) (func (result u64))) + (export (;15;) "r7" (func (type 15))) + (type (;16;) (func (result s64))) + (export (;16;) "r8" (func (type 16))) + (type (;17;) (tuple s64 u8)) + (type (;18;) (func (result 17))) + (export (;17;) "pair-ret" (func (type 18))) + (type (;19;) (func (result "a" s64) (result "b" u8))) + (export (;18;) "multi-ret" (func (type 19))) + ) + ) + (export (;0;) "integers" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "x" u8))) + (export (;0;) "a1" (func (type 0))) + (type (;1;) (func (param "x" s8))) + (export (;1;) "a2" (func (type 1))) + (type (;2;) (func (param "x" u16))) + (export (;2;) "a3" (func (type 2))) + (type (;3;) (func (param "x" s16))) + (export (;3;) "a4" (func (type 3))) + (type (;4;) (func (param "x" u32))) + (export (;4;) "a5" (func (type 4))) + (type (;5;) (func (param "x" s32))) + (export (;5;) "a6" (func (type 5))) + (type (;6;) (func (param "x" u64))) + (export (;6;) "a7" (func (type 6))) + (type (;7;) (func (param "x" s64))) + (export (;7;) "a8" (func (type 7))) + (type (;8;) (func (param "p1" u8) (param "p2" s8) (param "p3" u16) (param "p4" s16) (param "p5" u32) (param "p6" s32) (param "p7" u64) (param "p8" s64))) + (export (;8;) "a9" (func (type 8))) + (type (;9;) (func (result u8))) + (export (;9;) "r1" (func (type 9))) + (type (;10;) (func (result s8))) + (export (;10;) "r2" (func (type 10))) + (type (;11;) (func (result u16))) + (export (;11;) "r3" (func (type 11))) + (type (;12;) (func (result s16))) + (export (;12;) "r4" (func (type 12))) + (type (;13;) (func (result u32))) + (export (;13;) "r5" (func (type 13))) + (type (;14;) (func (result s32))) + (export (;14;) "r6" (func (type 14))) + (type (;15;) (func (result u64))) + (export (;15;) "r7" (func (type 15))) + (type (;16;) (func (result s64))) + (export (;16;) "r8" (func (type 16))) + (type (;17;) (tuple s64 u8)) + (type (;18;) (func (result 17))) + (export (;17;) "pair-ret" (func (type 18))) + (type (;19;) (func (result "a" s64) (result "b" u8))) + (export (;18;) "multi-ret" (func (type 19))) + ) + ) + (import "integers" "pkg:/integers/integers" (instance (type 0))) + ) + ) + (export (;0;) "integers-world" (component (type 1))) + ) + ) + (export (;1;) "integers" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/integers.wit b/crates/wit-component/tests/interfaces/integers.wit new file mode 100644 index 0000000000..ef67ac4a94 --- /dev/null +++ b/crates/wit-component/tests/interfaces/integers.wit @@ -0,0 +1,25 @@ +interface integers { + a1: func(x: u8) + a2: func(x: s8) + a3: func(x: u16) + a4: func(x: s16) + a5: func(x: u32) + a6: func(x: s32) + a7: func(x: u64) + a8: func(x: s64) + a9: func(p1: u8, p2: s8, p3: u16, p4: s16, p5: u32, p6: s32, p7: u64, p8: s64) + r1: func() -> u8 + r2: func() -> s8 + r3: func() -> u16 + r4: func() -> s16 + r5: func() -> u32 + r6: func() -> s32 + r7: func() -> u64 + r8: func() -> s64 + pair-ret: func() -> tuple + multi-ret: func() -> (a: s64, b: u8) +} + +world integers-world { + import integers: self.integers +} diff --git a/crates/wit-component/tests/interfaces/integers/world.wit b/crates/wit-component/tests/interfaces/integers.wit.print similarity index 94% rename from crates/wit-component/tests/interfaces/integers/world.wit rename to crates/wit-component/tests/interfaces/integers.wit.print index 7de51e03eb..d8d88ea2c6 100644 --- a/crates/wit-component/tests/interfaces/integers/world.wit +++ b/crates/wit-component/tests/interfaces/integers.wit.print @@ -39,5 +39,5 @@ interface integers { } world integers-world { - import integers: integers + import integers: self.integers } diff --git a/crates/wit-component/tests/interfaces/integers/component.wat b/crates/wit-component/tests/interfaces/integers/component.wat deleted file mode 100644 index 6c7261028f..0000000000 --- a/crates/wit-component/tests/interfaces/integers/component.wat +++ /dev/null @@ -1,181 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (func (param "x" u8))) - (export (;0;) "a1" (func (type 0))) - (type (;1;) (func (param "x" s8))) - (export (;1;) "a2" (func (type 1))) - (type (;2;) (func (param "x" u16))) - (export (;2;) "a3" (func (type 2))) - (type (;3;) (func (param "x" s16))) - (export (;3;) "a4" (func (type 3))) - (type (;4;) (func (param "x" u32))) - (export (;4;) "a5" (func (type 4))) - (type (;5;) (func (param "x" s32))) - (export (;5;) "a6" (func (type 5))) - (type (;6;) (func (param "x" u64))) - (export (;6;) "a7" (func (type 6))) - (type (;7;) (func (param "x" s64))) - (export (;7;) "a8" (func (type 7))) - (type (;8;) (func (param "p1" u8) (param "p2" s8) (param "p3" u16) (param "p4" s16) (param "p5" u32) (param "p6" s32) (param "p7" u64) (param "p8" s64))) - (export (;8;) "a9" (func (type 8))) - (type (;9;) (func (result u8))) - (export (;9;) "r1" (func (type 9))) - (type (;10;) (func (result s8))) - (export (;10;) "r2" (func (type 10))) - (type (;11;) (func (result u16))) - (export (;11;) "r3" (func (type 11))) - (type (;12;) (func (result s16))) - (export (;12;) "r4" (func (type 12))) - (type (;13;) (func (result u32))) - (export (;13;) "r5" (func (type 13))) - (type (;14;) (func (result s32))) - (export (;14;) "r6" (func (type 14))) - (type (;15;) (func (result u64))) - (export (;15;) "r7" (func (type 15))) - (type (;16;) (func (result s64))) - (export (;16;) "r8" (func (type 16))) - (type (;17;) (tuple s64 u8)) - (type (;18;) (func (result 17))) - (export (;17;) "pair-ret" (func (type 18))) - (type (;19;) (func (result "a" s64) (result "b" u8))) - (export (;18;) "multi-ret" (func (type 19))) - ) - ) - (import "integers" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32))) - (type (;1;) (func (param i64))) - (type (;2;) (func (param i32 i32 i32 i32 i32 i32 i64 i64))) - (type (;3;) (func (result i32))) - (type (;4;) (func (result i64))) - (type (;5;) (func (param i32 i32 i32 i32) (result i32))) - (import "integers" "a1" (func (;0;) (type 0))) - (import "integers" "a2" (func (;1;) (type 0))) - (import "integers" "a3" (func (;2;) (type 0))) - (import "integers" "a4" (func (;3;) (type 0))) - (import "integers" "a5" (func (;4;) (type 0))) - (import "integers" "a6" (func (;5;) (type 0))) - (import "integers" "a7" (func (;6;) (type 1))) - (import "integers" "a8" (func (;7;) (type 1))) - (import "integers" "a9" (func (;8;) (type 2))) - (import "integers" "r1" (func (;9;) (type 3))) - (import "integers" "r2" (func (;10;) (type 3))) - (import "integers" "r3" (func (;11;) (type 3))) - (import "integers" "r4" (func (;12;) (type 3))) - (import "integers" "r5" (func (;13;) (type 3))) - (import "integers" "r6" (func (;14;) (type 3))) - (import "integers" "r7" (func (;15;) (type 4))) - (import "integers" "r8" (func (;16;) (type 4))) - (import "integers" "pair-ret" (func (;17;) (type 0))) - (import "integers" "multi-ret" (func (;18;) (type 0))) - (func (;19;) (type 5) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 19)) - ) - (core module (;1;) - (type (;0;) (func (param i32))) - (func $indirect-integers-pair-ret (;0;) (type 0) (param i32) - local.get 0 - i32.const 0 - call_indirect (type 0) - ) - (func $indirect-integers-multi-ret (;1;) (type 0) (param i32) - local.get 0 - i32.const 1 - call_indirect (type 0) - ) - (table (;0;) 2 2 funcref) - (export "0" (func $indirect-integers-pair-ret)) - (export "1" (func $indirect-integers-multi-ret)) - (export "$imports" (table 0)) - ) - (core module (;2;) - (type (;0;) (func (param i32))) - (import "" "0" (func (;0;) (type 0))) - (import "" "1" (func (;1;) (type 0))) - (import "" "$imports" (table (;0;) 2 2 funcref)) - (elem (;0;) (i32.const 0) func 0 1) - ) - (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias core export 0 "1" (core func (;1;))) - (alias export 0 "a1" (func (;0;))) - (core func (;2;) (canon lower (func 0))) - (alias export 0 "a2" (func (;1;))) - (core func (;3;) (canon lower (func 1))) - (alias export 0 "a3" (func (;2;))) - (core func (;4;) (canon lower (func 2))) - (alias export 0 "a4" (func (;3;))) - (core func (;5;) (canon lower (func 3))) - (alias export 0 "a5" (func (;4;))) - (core func (;6;) (canon lower (func 4))) - (alias export 0 "a6" (func (;5;))) - (core func (;7;) (canon lower (func 5))) - (alias export 0 "a7" (func (;6;))) - (core func (;8;) (canon lower (func 6))) - (alias export 0 "a8" (func (;7;))) - (core func (;9;) (canon lower (func 7))) - (alias export 0 "a9" (func (;8;))) - (core func (;10;) (canon lower (func 8))) - (alias export 0 "r1" (func (;9;))) - (core func (;11;) (canon lower (func 9))) - (alias export 0 "r2" (func (;10;))) - (core func (;12;) (canon lower (func 10))) - (alias export 0 "r3" (func (;11;))) - (core func (;13;) (canon lower (func 11))) - (alias export 0 "r4" (func (;12;))) - (core func (;14;) (canon lower (func 12))) - (alias export 0 "r5" (func (;13;))) - (core func (;15;) (canon lower (func 13))) - (alias export 0 "r6" (func (;14;))) - (core func (;16;) (canon lower (func 14))) - (alias export 0 "r7" (func (;15;))) - (core func (;17;) (canon lower (func 15))) - (alias export 0 "r8" (func (;16;))) - (core func (;18;) (canon lower (func 16))) - (core instance (;1;) - (export "pair-ret" (func 0)) - (export "multi-ret" (func 1)) - (export "a1" (func 2)) - (export "a2" (func 3)) - (export "a3" (func 4)) - (export "a4" (func 5)) - (export "a5" (func 6)) - (export "a6" (func 7)) - (export "a7" (func 8)) - (export "a8" (func 9)) - (export "a9" (func 10)) - (export "r1" (func 11)) - (export "r2" (func 12)) - (export "r3" (func 13)) - (export "r4" (func 14)) - (export "r5" (func 15)) - (export "r6" (func 16)) - (export "r7" (func 17)) - (export "r8" (func 18)) - ) - (core instance (;2;) (instantiate 0 - (with "integers" (instance 1)) - ) - ) - (alias core export 2 "memory" (core memory (;0;))) - (alias core export 2 "cabi_realloc" (core func (;19;))) - (alias core export 0 "$imports" (core table (;0;))) - (alias export 0 "pair-ret" (func (;17;))) - (core func (;20;) (canon lower (func 17) (memory 0))) - (alias export 0 "multi-ret" (func (;18;))) - (core func (;21;) (canon lower (func 18) (memory 0))) - (core instance (;3;) - (export "$imports" (table 0)) - (export "0" (func 20)) - (export "1" (func 21)) - ) - (core instance (;4;) (instantiate 2 - (with "" (instance 3)) - ) - ) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/integers/types_only.wat b/crates/wit-component/tests/interfaces/integers/types_only.wat deleted file mode 100644 index 17b8e6ce4d..0000000000 --- a/crates/wit-component/tests/interfaces/integers/types_only.wat +++ /dev/null @@ -1,46 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (func (param "x" u8))) - (export (;0;) "a1" (func (type 0))) - (type (;1;) (func (param "x" s8))) - (export (;1;) "a2" (func (type 1))) - (type (;2;) (func (param "x" u16))) - (export (;2;) "a3" (func (type 2))) - (type (;3;) (func (param "x" s16))) - (export (;3;) "a4" (func (type 3))) - (type (;4;) (func (param "x" u32))) - (export (;4;) "a5" (func (type 4))) - (type (;5;) (func (param "x" s32))) - (export (;5;) "a6" (func (type 5))) - (type (;6;) (func (param "x" u64))) - (export (;6;) "a7" (func (type 6))) - (type (;7;) (func (param "x" s64))) - (export (;7;) "a8" (func (type 7))) - (type (;8;) (func (param "p1" u8) (param "p2" s8) (param "p3" u16) (param "p4" s16) (param "p5" u32) (param "p6" s32) (param "p7" u64) (param "p8" s64))) - (export (;8;) "a9" (func (type 8))) - (type (;9;) (func (result u8))) - (export (;9;) "r1" (func (type 9))) - (type (;10;) (func (result s8))) - (export (;10;) "r2" (func (type 10))) - (type (;11;) (func (result u16))) - (export (;11;) "r3" (func (type 11))) - (type (;12;) (func (result s16))) - (export (;12;) "r4" (func (type 12))) - (type (;13;) (func (result u32))) - (export (;13;) "r5" (func (type 13))) - (type (;14;) (func (result s32))) - (export (;14;) "r6" (func (type 14))) - (type (;15;) (func (result u64))) - (export (;15;) "r7" (func (type 15))) - (type (;16;) (func (result s64))) - (export (;16;) "r8" (func (type 16))) - (type (;17;) (tuple s64 u8)) - (type (;18;) (func (result 17))) - (export (;17;) "pair-ret" (func (type 18))) - (type (;19;) (func (result "a" s64) (result "b" u8))) - (export (;18;) "multi-ret" (func (type 19))) - ) - ) - (import "integers" (instance (;0;) (type 0))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/lists.wat b/crates/wit-component/tests/interfaces/lists.wat new file mode 100644 index 0000000000..ce460a94e1 --- /dev/null +++ b/crates/wit-component/tests/interfaces/lists.wat @@ -0,0 +1,202 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (list u8)) + (type (;1;) (record (field "a1" u32) (field "a2" u64) (field "a3" s32) (field "a4" s64) (field "b" string) (field "c" 0))) + (export (;2;) "other-record" (type (eq 1))) + (type (;3;) (record (field "x" string) (field "y" 2) (field "c1" u32) (field "c2" u64) (field "c3" s32) (field "c4" s64))) + (export (;4;) "some-record" (type (eq 3))) + (type (;5;) (variant (case "a") (case "b" u32) (case "c" string))) + (export (;6;) "other-variant" (type (eq 5))) + (type (;7;) (list 6)) + (type (;8;) (variant (case "a" string) (case "b") (case "c" u32) (case "d" 7))) + (export (;9;) "some-variant" (type (eq 8))) + (type (;10;) (tuple string u8 s8 u16 s16 u32 s32 u64 s64 float32 float64 char)) + (type (;11;) (list 10)) + (export (;12;) "load-store-all-sizes" (type (eq 11))) + (type (;13;) (func (param "x" 0))) + (export (;0;) "list-u8-param" (func (type 13))) + (type (;14;) (list u16)) + (type (;15;) (func (param "x" 14))) + (export (;1;) "list-u16-param" (func (type 15))) + (type (;16;) (list u32)) + (type (;17;) (func (param "x" 16))) + (export (;2;) "list-u32-param" (func (type 17))) + (type (;18;) (list u64)) + (type (;19;) (func (param "x" 18))) + (export (;3;) "list-u64-param" (func (type 19))) + (type (;20;) (list s8)) + (type (;21;) (func (param "x" 20))) + (export (;4;) "list-s8-param" (func (type 21))) + (type (;22;) (list s16)) + (type (;23;) (func (param "x" 22))) + (export (;5;) "list-s16-param" (func (type 23))) + (type (;24;) (list s32)) + (type (;25;) (func (param "x" 24))) + (export (;6;) "list-s32-param" (func (type 25))) + (type (;26;) (list s64)) + (type (;27;) (func (param "x" 26))) + (export (;7;) "list-s64-param" (func (type 27))) + (type (;28;) (list float32)) + (type (;29;) (func (param "x" 28))) + (export (;8;) "list-float32-param" (func (type 29))) + (type (;30;) (list float64)) + (type (;31;) (func (param "x" 30))) + (export (;9;) "list-float64-param" (func (type 31))) + (type (;32;) (func (result 0))) + (export (;10;) "list-u8-ret" (func (type 32))) + (type (;33;) (func (result 14))) + (export (;11;) "list-u16-ret" (func (type 33))) + (type (;34;) (func (result 16))) + (export (;12;) "list-u32-ret" (func (type 34))) + (type (;35;) (func (result 18))) + (export (;13;) "list-u64-ret" (func (type 35))) + (type (;36;) (func (result 20))) + (export (;14;) "list-s8-ret" (func (type 36))) + (type (;37;) (func (result 22))) + (export (;15;) "list-s16-ret" (func (type 37))) + (type (;38;) (func (result 24))) + (export (;16;) "list-s32-ret" (func (type 38))) + (type (;39;) (func (result 26))) + (export (;17;) "list-s64-ret" (func (type 39))) + (type (;40;) (func (result 28))) + (export (;18;) "list-float32-ret" (func (type 40))) + (type (;41;) (func (result 30))) + (export (;19;) "list-float64-ret" (func (type 41))) + (type (;42;) (tuple u8 s8)) + (type (;43;) (list 42)) + (type (;44;) (tuple s64 u32)) + (type (;45;) (list 44)) + (type (;46;) (func (param "x" 43) (result 45))) + (export (;20;) "tuple-list" (func (type 46))) + (type (;47;) (list string)) + (type (;48;) (func (param "a" 47))) + (export (;21;) "string-list-arg" (func (type 48))) + (type (;49;) (func (result 47))) + (export (;22;) "string-list-ret" (func (type 49))) + (type (;50;) (tuple u8 string)) + (type (;51;) (list 50)) + (type (;52;) (tuple string u8)) + (type (;53;) (list 52)) + (type (;54;) (func (param "x" 51) (result 53))) + (export (;23;) "tuple-string-list" (func (type 54))) + (type (;55;) (func (param "x" 47) (result 47))) + (export (;24;) "string-list" (func (type 55))) + (type (;56;) (list 4)) + (type (;57;) (list 2)) + (type (;58;) (func (param "x" 56) (result 57))) + (export (;25;) "record-list" (func (type 58))) + (type (;59;) (list 9)) + (type (;60;) (func (param "x" 59) (result 7))) + (export (;26;) "variant-list" (func (type 60))) + (type (;61;) (func (param "a" 12) (result 12))) + (export (;27;) "load-store-everything" (func (type 61))) + ) + ) + (export (;0;) "lists" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (list u8)) + (type (;1;) (record (field "a1" u32) (field "a2" u64) (field "a3" s32) (field "a4" s64) (field "b" string) (field "c" 0))) + (export (;2;) "other-record" (type (eq 1))) + (type (;3;) (record (field "x" string) (field "y" 2) (field "c1" u32) (field "c2" u64) (field "c3" s32) (field "c4" s64))) + (export (;4;) "some-record" (type (eq 3))) + (type (;5;) (variant (case "a") (case "b" u32) (case "c" string))) + (export (;6;) "other-variant" (type (eq 5))) + (type (;7;) (list 6)) + (type (;8;) (variant (case "a" string) (case "b") (case "c" u32) (case "d" 7))) + (export (;9;) "some-variant" (type (eq 8))) + (type (;10;) (tuple string u8 s8 u16 s16 u32 s32 u64 s64 float32 float64 char)) + (type (;11;) (list 10)) + (export (;12;) "load-store-all-sizes" (type (eq 11))) + (type (;13;) (func (param "x" 0))) + (export (;0;) "list-u8-param" (func (type 13))) + (type (;14;) (list u16)) + (type (;15;) (func (param "x" 14))) + (export (;1;) "list-u16-param" (func (type 15))) + (type (;16;) (list u32)) + (type (;17;) (func (param "x" 16))) + (export (;2;) "list-u32-param" (func (type 17))) + (type (;18;) (list u64)) + (type (;19;) (func (param "x" 18))) + (export (;3;) "list-u64-param" (func (type 19))) + (type (;20;) (list s8)) + (type (;21;) (func (param "x" 20))) + (export (;4;) "list-s8-param" (func (type 21))) + (type (;22;) (list s16)) + (type (;23;) (func (param "x" 22))) + (export (;5;) "list-s16-param" (func (type 23))) + (type (;24;) (list s32)) + (type (;25;) (func (param "x" 24))) + (export (;6;) "list-s32-param" (func (type 25))) + (type (;26;) (list s64)) + (type (;27;) (func (param "x" 26))) + (export (;7;) "list-s64-param" (func (type 27))) + (type (;28;) (list float32)) + (type (;29;) (func (param "x" 28))) + (export (;8;) "list-float32-param" (func (type 29))) + (type (;30;) (list float64)) + (type (;31;) (func (param "x" 30))) + (export (;9;) "list-float64-param" (func (type 31))) + (type (;32;) (func (result 0))) + (export (;10;) "list-u8-ret" (func (type 32))) + (type (;33;) (func (result 14))) + (export (;11;) "list-u16-ret" (func (type 33))) + (type (;34;) (func (result 16))) + (export (;12;) "list-u32-ret" (func (type 34))) + (type (;35;) (func (result 18))) + (export (;13;) "list-u64-ret" (func (type 35))) + (type (;36;) (func (result 20))) + (export (;14;) "list-s8-ret" (func (type 36))) + (type (;37;) (func (result 22))) + (export (;15;) "list-s16-ret" (func (type 37))) + (type (;38;) (func (result 24))) + (export (;16;) "list-s32-ret" (func (type 38))) + (type (;39;) (func (result 26))) + (export (;17;) "list-s64-ret" (func (type 39))) + (type (;40;) (func (result 28))) + (export (;18;) "list-float32-ret" (func (type 40))) + (type (;41;) (func (result 30))) + (export (;19;) "list-float64-ret" (func (type 41))) + (type (;42;) (tuple u8 s8)) + (type (;43;) (list 42)) + (type (;44;) (tuple s64 u32)) + (type (;45;) (list 44)) + (type (;46;) (func (param "x" 43) (result 45))) + (export (;20;) "tuple-list" (func (type 46))) + (type (;47;) (list string)) + (type (;48;) (func (param "a" 47))) + (export (;21;) "string-list-arg" (func (type 48))) + (type (;49;) (func (result 47))) + (export (;22;) "string-list-ret" (func (type 49))) + (type (;50;) (tuple u8 string)) + (type (;51;) (list 50)) + (type (;52;) (tuple string u8)) + (type (;53;) (list 52)) + (type (;54;) (func (param "x" 51) (result 53))) + (export (;23;) "tuple-string-list" (func (type 54))) + (type (;55;) (func (param "x" 47) (result 47))) + (export (;24;) "string-list" (func (type 55))) + (type (;56;) (list 4)) + (type (;57;) (list 2)) + (type (;58;) (func (param "x" 56) (result 57))) + (export (;25;) "record-list" (func (type 58))) + (type (;59;) (list 9)) + (type (;60;) (func (param "x" 59) (result 7))) + (export (;26;) "variant-list" (func (type 60))) + (type (;61;) (func (param "a" 12) (result 12))) + (export (;27;) "load-store-everything" (func (type 61))) + ) + ) + (import "lists" "pkg:/lists/lists" (instance (type 0))) + ) + ) + (export (;0;) "lists-world" (component (type 1))) + ) + ) + (export (;1;) "lists" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/lists/world.wit b/crates/wit-component/tests/interfaces/lists.wit similarity index 98% rename from crates/wit-component/tests/interfaces/lists/world.wit rename to crates/wit-component/tests/interfaces/lists.wit index 749e1079ad..38a84ecab8 100644 --- a/crates/wit-component/tests/interfaces/lists/world.wit +++ b/crates/wit-component/tests/interfaces/lists.wit @@ -90,5 +90,5 @@ interface lists { } world lists-world { - import lists: lists + import lists: self.lists } diff --git a/crates/wit-component/tests/interfaces/lists.wit.print b/crates/wit-component/tests/interfaces/lists.wit.print new file mode 100644 index 0000000000..38a84ecab8 --- /dev/null +++ b/crates/wit-component/tests/interfaces/lists.wit.print @@ -0,0 +1,94 @@ +interface lists { + record other-record { + a1: u32, + a2: u64, + a3: s32, + a4: s64, + b: string, + c: list, + } + + record some-record { + x: string, + y: other-record, + c1: u32, + c2: u64, + c3: s32, + c4: s64, + } + + variant other-variant { + a, + b(u32), + c(string), + } + + variant some-variant { + a(string), + b, + c(u32), + d(list), + } + + type load-store-all-sizes = list> + + list-u8-param: func(x: list) + + list-u16-param: func(x: list) + + list-u32-param: func(x: list) + + list-u64-param: func(x: list) + + list-s8-param: func(x: list) + + list-s16-param: func(x: list) + + list-s32-param: func(x: list) + + list-s64-param: func(x: list) + + list-float32-param: func(x: list) + + list-float64-param: func(x: list) + + list-u8-ret: func() -> list + + list-u16-ret: func() -> list + + list-u32-ret: func() -> list + + list-u64-ret: func() -> list + + list-s8-ret: func() -> list + + list-s16-ret: func() -> list + + list-s32-ret: func() -> list + + list-s64-ret: func() -> list + + list-float32-ret: func() -> list + + list-float64-ret: func() -> list + + tuple-list: func(x: list>) -> list> + + string-list-arg: func(a: list) + + string-list-ret: func() -> list + + tuple-string-list: func(x: list>) -> list> + + string-list: func(x: list) -> list + + record-list: func(x: list) -> list + + variant-list: func(x: list) -> list + + load-store-everything: func(a: load-store-all-sizes) -> load-store-all-sizes +} + +world lists-world { + import lists: self.lists +} diff --git a/crates/wit-component/tests/interfaces/lists/component.wat b/crates/wit-component/tests/interfaces/lists/component.wat deleted file mode 100644 index be22b6e12a..0000000000 --- a/crates/wit-component/tests/interfaces/lists/component.wat +++ /dev/null @@ -1,527 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (list u8)) - (type (;1;) (func (param "x" 0))) - (export (;0;) "list-u8-param" (func (type 1))) - (type (;2;) (list u16)) - (type (;3;) (func (param "x" 2))) - (export (;1;) "list-u16-param" (func (type 3))) - (type (;4;) (list u32)) - (type (;5;) (func (param "x" 4))) - (export (;2;) "list-u32-param" (func (type 5))) - (type (;6;) (list u64)) - (type (;7;) (func (param "x" 6))) - (export (;3;) "list-u64-param" (func (type 7))) - (type (;8;) (list s8)) - (type (;9;) (func (param "x" 8))) - (export (;4;) "list-s8-param" (func (type 9))) - (type (;10;) (list s16)) - (type (;11;) (func (param "x" 10))) - (export (;5;) "list-s16-param" (func (type 11))) - (type (;12;) (list s32)) - (type (;13;) (func (param "x" 12))) - (export (;6;) "list-s32-param" (func (type 13))) - (type (;14;) (list s64)) - (type (;15;) (func (param "x" 14))) - (export (;7;) "list-s64-param" (func (type 15))) - (type (;16;) (list float32)) - (type (;17;) (func (param "x" 16))) - (export (;8;) "list-float32-param" (func (type 17))) - (type (;18;) (list float64)) - (type (;19;) (func (param "x" 18))) - (export (;9;) "list-float64-param" (func (type 19))) - (type (;20;) (func (result 0))) - (export (;10;) "list-u8-ret" (func (type 20))) - (type (;21;) (func (result 2))) - (export (;11;) "list-u16-ret" (func (type 21))) - (type (;22;) (func (result 4))) - (export (;12;) "list-u32-ret" (func (type 22))) - (type (;23;) (func (result 6))) - (export (;13;) "list-u64-ret" (func (type 23))) - (type (;24;) (func (result 8))) - (export (;14;) "list-s8-ret" (func (type 24))) - (type (;25;) (func (result 10))) - (export (;15;) "list-s16-ret" (func (type 25))) - (type (;26;) (func (result 12))) - (export (;16;) "list-s32-ret" (func (type 26))) - (type (;27;) (func (result 14))) - (export (;17;) "list-s64-ret" (func (type 27))) - (type (;28;) (func (result 16))) - (export (;18;) "list-float32-ret" (func (type 28))) - (type (;29;) (func (result 18))) - (export (;19;) "list-float64-ret" (func (type 29))) - (type (;30;) (tuple u8 s8)) - (type (;31;) (list 30)) - (type (;32;) (tuple s64 u32)) - (type (;33;) (list 32)) - (type (;34;) (func (param "x" 31) (result 33))) - (export (;20;) "tuple-list" (func (type 34))) - (type (;35;) (list string)) - (type (;36;) (func (param "a" 35))) - (export (;21;) "string-list-arg" (func (type 36))) - (type (;37;) (func (result 35))) - (export (;22;) "string-list-ret" (func (type 37))) - (type (;38;) (tuple u8 string)) - (type (;39;) (list 38)) - (type (;40;) (tuple string u8)) - (type (;41;) (list 40)) - (type (;42;) (func (param "x" 39) (result 41))) - (export (;23;) "tuple-string-list" (func (type 42))) - (type (;43;) (func (param "x" 35) (result 35))) - (export (;24;) "string-list" (func (type 43))) - (type (;44;) (record (field "a1" u32) (field "a2" u64) (field "a3" s32) (field "a4" s64) (field "b" string) (field "c" 0))) - (export (;45;) "other-record" (type (eq 44))) - (type (;46;) (record (field "x" string) (field "y" 44) (field "c1" u32) (field "c2" u64) (field "c3" s32) (field "c4" s64))) - (export (;47;) "some-record" (type (eq 46))) - (type (;48;) (list 46)) - (type (;49;) (list 44)) - (type (;50;) (func (param "x" 48) (result 49))) - (export (;25;) "record-list" (func (type 50))) - (type (;51;) (variant (case "a") (case "b" u32) (case "c" string))) - (export (;52;) "other-variant" (type (eq 51))) - (type (;53;) (list 51)) - (type (;54;) (variant (case "a" string) (case "b") (case "c" u32) (case "d" 53))) - (export (;55;) "some-variant" (type (eq 54))) - (type (;56;) (list 54)) - (type (;57;) (func (param "x" 56) (result 53))) - (export (;26;) "variant-list" (func (type 57))) - (type (;58;) (tuple string u8 s8 u16 s16 u32 s32 u64 s64 float32 float64 char)) - (type (;59;) (list 58)) - (export (;60;) "load-store-all-sizes" (type (eq 59))) - (type (;61;) (func (param "a" 59) (result 59))) - (export (;27;) "load-store-everything" (func (type 61))) - ) - ) - (import "lists" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32 i32))) - (type (;1;) (func (param i32))) - (type (;2;) (func (param i32 i32 i32))) - (type (;3;) (func (param i32 i32 i32 i32) (result i32))) - (import "lists" "list-u8-param" (func (;0;) (type 0))) - (import "lists" "list-u16-param" (func (;1;) (type 0))) - (import "lists" "list-u32-param" (func (;2;) (type 0))) - (import "lists" "list-u64-param" (func (;3;) (type 0))) - (import "lists" "list-s8-param" (func (;4;) (type 0))) - (import "lists" "list-s16-param" (func (;5;) (type 0))) - (import "lists" "list-s32-param" (func (;6;) (type 0))) - (import "lists" "list-s64-param" (func (;7;) (type 0))) - (import "lists" "list-float32-param" (func (;8;) (type 0))) - (import "lists" "list-float64-param" (func (;9;) (type 0))) - (import "lists" "list-u8-ret" (func (;10;) (type 1))) - (import "lists" "list-u16-ret" (func (;11;) (type 1))) - (import "lists" "list-u32-ret" (func (;12;) (type 1))) - (import "lists" "list-u64-ret" (func (;13;) (type 1))) - (import "lists" "list-s8-ret" (func (;14;) (type 1))) - (import "lists" "list-s16-ret" (func (;15;) (type 1))) - (import "lists" "list-s32-ret" (func (;16;) (type 1))) - (import "lists" "list-s64-ret" (func (;17;) (type 1))) - (import "lists" "list-float32-ret" (func (;18;) (type 1))) - (import "lists" "list-float64-ret" (func (;19;) (type 1))) - (import "lists" "tuple-list" (func (;20;) (type 2))) - (import "lists" "string-list-arg" (func (;21;) (type 0))) - (import "lists" "string-list-ret" (func (;22;) (type 1))) - (import "lists" "tuple-string-list" (func (;23;) (type 2))) - (import "lists" "string-list" (func (;24;) (type 2))) - (import "lists" "record-list" (func (;25;) (type 2))) - (import "lists" "variant-list" (func (;26;) (type 2))) - (import "lists" "load-store-everything" (func (;27;) (type 2))) - (func (;28;) (type 3) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 28)) - ) - (core module (;1;) - (type (;0;) (func (param i32 i32))) - (type (;1;) (func (param i32))) - (type (;2;) (func (param i32 i32 i32))) - (func $indirect-lists-list-u8-param (;0;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 0 - call_indirect (type 0) - ) - (func $indirect-lists-list-u16-param (;1;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 1 - call_indirect (type 0) - ) - (func $indirect-lists-list-u32-param (;2;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 2 - call_indirect (type 0) - ) - (func $indirect-lists-list-u64-param (;3;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 3 - call_indirect (type 0) - ) - (func $indirect-lists-list-s8-param (;4;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 4 - call_indirect (type 0) - ) - (func $indirect-lists-list-s16-param (;5;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 5 - call_indirect (type 0) - ) - (func $indirect-lists-list-s32-param (;6;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 6 - call_indirect (type 0) - ) - (func $indirect-lists-list-s64-param (;7;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 7 - call_indirect (type 0) - ) - (func $indirect-lists-list-float32-param (;8;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 8 - call_indirect (type 0) - ) - (func $indirect-lists-list-float64-param (;9;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 9 - call_indirect (type 0) - ) - (func $indirect-lists-list-u8-ret (;10;) (type 1) (param i32) - local.get 0 - i32.const 10 - call_indirect (type 1) - ) - (func $indirect-lists-list-u16-ret (;11;) (type 1) (param i32) - local.get 0 - i32.const 11 - call_indirect (type 1) - ) - (func $indirect-lists-list-u32-ret (;12;) (type 1) (param i32) - local.get 0 - i32.const 12 - call_indirect (type 1) - ) - (func $indirect-lists-list-u64-ret (;13;) (type 1) (param i32) - local.get 0 - i32.const 13 - call_indirect (type 1) - ) - (func $indirect-lists-list-s8-ret (;14;) (type 1) (param i32) - local.get 0 - i32.const 14 - call_indirect (type 1) - ) - (func $indirect-lists-list-s16-ret (;15;) (type 1) (param i32) - local.get 0 - i32.const 15 - call_indirect (type 1) - ) - (func $indirect-lists-list-s32-ret (;16;) (type 1) (param i32) - local.get 0 - i32.const 16 - call_indirect (type 1) - ) - (func $indirect-lists-list-s64-ret (;17;) (type 1) (param i32) - local.get 0 - i32.const 17 - call_indirect (type 1) - ) - (func $indirect-lists-list-float32-ret (;18;) (type 1) (param i32) - local.get 0 - i32.const 18 - call_indirect (type 1) - ) - (func $indirect-lists-list-float64-ret (;19;) (type 1) (param i32) - local.get 0 - i32.const 19 - call_indirect (type 1) - ) - (func $indirect-lists-tuple-list (;20;) (type 2) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 20 - call_indirect (type 2) - ) - (func $indirect-lists-string-list-arg (;21;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 21 - call_indirect (type 0) - ) - (func $indirect-lists-string-list-ret (;22;) (type 1) (param i32) - local.get 0 - i32.const 22 - call_indirect (type 1) - ) - (func $indirect-lists-tuple-string-list (;23;) (type 2) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 23 - call_indirect (type 2) - ) - (func $indirect-lists-string-list (;24;) (type 2) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 24 - call_indirect (type 2) - ) - (func $indirect-lists-record-list (;25;) (type 2) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 25 - call_indirect (type 2) - ) - (func $indirect-lists-variant-list (;26;) (type 2) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 26 - call_indirect (type 2) - ) - (func $indirect-lists-load-store-everything (;27;) (type 2) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 27 - call_indirect (type 2) - ) - (table (;0;) 28 28 funcref) - (export "0" (func $indirect-lists-list-u8-param)) - (export "1" (func $indirect-lists-list-u16-param)) - (export "2" (func $indirect-lists-list-u32-param)) - (export "3" (func $indirect-lists-list-u64-param)) - (export "4" (func $indirect-lists-list-s8-param)) - (export "5" (func $indirect-lists-list-s16-param)) - (export "6" (func $indirect-lists-list-s32-param)) - (export "7" (func $indirect-lists-list-s64-param)) - (export "8" (func $indirect-lists-list-float32-param)) - (export "9" (func $indirect-lists-list-float64-param)) - (export "10" (func $indirect-lists-list-u8-ret)) - (export "11" (func $indirect-lists-list-u16-ret)) - (export "12" (func $indirect-lists-list-u32-ret)) - (export "13" (func $indirect-lists-list-u64-ret)) - (export "14" (func $indirect-lists-list-s8-ret)) - (export "15" (func $indirect-lists-list-s16-ret)) - (export "16" (func $indirect-lists-list-s32-ret)) - (export "17" (func $indirect-lists-list-s64-ret)) - (export "18" (func $indirect-lists-list-float32-ret)) - (export "19" (func $indirect-lists-list-float64-ret)) - (export "20" (func $indirect-lists-tuple-list)) - (export "21" (func $indirect-lists-string-list-arg)) - (export "22" (func $indirect-lists-string-list-ret)) - (export "23" (func $indirect-lists-tuple-string-list)) - (export "24" (func $indirect-lists-string-list)) - (export "25" (func $indirect-lists-record-list)) - (export "26" (func $indirect-lists-variant-list)) - (export "27" (func $indirect-lists-load-store-everything)) - (export "$imports" (table 0)) - ) - (core module (;2;) - (type (;0;) (func (param i32 i32))) - (type (;1;) (func (param i32))) - (type (;2;) (func (param i32 i32 i32))) - (import "" "0" (func (;0;) (type 0))) - (import "" "1" (func (;1;) (type 0))) - (import "" "2" (func (;2;) (type 0))) - (import "" "3" (func (;3;) (type 0))) - (import "" "4" (func (;4;) (type 0))) - (import "" "5" (func (;5;) (type 0))) - (import "" "6" (func (;6;) (type 0))) - (import "" "7" (func (;7;) (type 0))) - (import "" "8" (func (;8;) (type 0))) - (import "" "9" (func (;9;) (type 0))) - (import "" "10" (func (;10;) (type 1))) - (import "" "11" (func (;11;) (type 1))) - (import "" "12" (func (;12;) (type 1))) - (import "" "13" (func (;13;) (type 1))) - (import "" "14" (func (;14;) (type 1))) - (import "" "15" (func (;15;) (type 1))) - (import "" "16" (func (;16;) (type 1))) - (import "" "17" (func (;17;) (type 1))) - (import "" "18" (func (;18;) (type 1))) - (import "" "19" (func (;19;) (type 1))) - (import "" "20" (func (;20;) (type 2))) - (import "" "21" (func (;21;) (type 0))) - (import "" "22" (func (;22;) (type 1))) - (import "" "23" (func (;23;) (type 2))) - (import "" "24" (func (;24;) (type 2))) - (import "" "25" (func (;25;) (type 2))) - (import "" "26" (func (;26;) (type 2))) - (import "" "27" (func (;27;) (type 2))) - (import "" "$imports" (table (;0;) 28 28 funcref)) - (elem (;0;) (i32.const 0) func 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27) - ) - (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias core export 0 "1" (core func (;1;))) - (alias core export 0 "2" (core func (;2;))) - (alias core export 0 "3" (core func (;3;))) - (alias core export 0 "4" (core func (;4;))) - (alias core export 0 "5" (core func (;5;))) - (alias core export 0 "6" (core func (;6;))) - (alias core export 0 "7" (core func (;7;))) - (alias core export 0 "8" (core func (;8;))) - (alias core export 0 "9" (core func (;9;))) - (alias core export 0 "10" (core func (;10;))) - (alias core export 0 "11" (core func (;11;))) - (alias core export 0 "12" (core func (;12;))) - (alias core export 0 "13" (core func (;13;))) - (alias core export 0 "14" (core func (;14;))) - (alias core export 0 "15" (core func (;15;))) - (alias core export 0 "16" (core func (;16;))) - (alias core export 0 "17" (core func (;17;))) - (alias core export 0 "18" (core func (;18;))) - (alias core export 0 "19" (core func (;19;))) - (alias core export 0 "20" (core func (;20;))) - (alias core export 0 "21" (core func (;21;))) - (alias core export 0 "22" (core func (;22;))) - (alias core export 0 "23" (core func (;23;))) - (alias core export 0 "24" (core func (;24;))) - (alias core export 0 "25" (core func (;25;))) - (alias core export 0 "26" (core func (;26;))) - (alias core export 0 "27" (core func (;27;))) - (core instance (;1;) - (export "list-u8-param" (func 0)) - (export "list-u16-param" (func 1)) - (export "list-u32-param" (func 2)) - (export "list-u64-param" (func 3)) - (export "list-s8-param" (func 4)) - (export "list-s16-param" (func 5)) - (export "list-s32-param" (func 6)) - (export "list-s64-param" (func 7)) - (export "list-float32-param" (func 8)) - (export "list-float64-param" (func 9)) - (export "list-u8-ret" (func 10)) - (export "list-u16-ret" (func 11)) - (export "list-u32-ret" (func 12)) - (export "list-u64-ret" (func 13)) - (export "list-s8-ret" (func 14)) - (export "list-s16-ret" (func 15)) - (export "list-s32-ret" (func 16)) - (export "list-s64-ret" (func 17)) - (export "list-float32-ret" (func 18)) - (export "list-float64-ret" (func 19)) - (export "tuple-list" (func 20)) - (export "string-list-arg" (func 21)) - (export "string-list-ret" (func 22)) - (export "tuple-string-list" (func 23)) - (export "string-list" (func 24)) - (export "record-list" (func 25)) - (export "variant-list" (func 26)) - (export "load-store-everything" (func 27)) - ) - (core instance (;2;) (instantiate 0 - (with "lists" (instance 1)) - ) - ) - (alias core export 2 "memory" (core memory (;0;))) - (alias core export 2 "cabi_realloc" (core func (;28;))) - (alias core export 0 "$imports" (core table (;0;))) - (alias export 0 "list-u8-param" (func (;0;))) - (core func (;29;) (canon lower (func 0) (memory 0))) - (alias export 0 "list-u16-param" (func (;1;))) - (core func (;30;) (canon lower (func 1) (memory 0))) - (alias export 0 "list-u32-param" (func (;2;))) - (core func (;31;) (canon lower (func 2) (memory 0))) - (alias export 0 "list-u64-param" (func (;3;))) - (core func (;32;) (canon lower (func 3) (memory 0))) - (alias export 0 "list-s8-param" (func (;4;))) - (core func (;33;) (canon lower (func 4) (memory 0))) - (alias export 0 "list-s16-param" (func (;5;))) - (core func (;34;) (canon lower (func 5) (memory 0))) - (alias export 0 "list-s32-param" (func (;6;))) - (core func (;35;) (canon lower (func 6) (memory 0))) - (alias export 0 "list-s64-param" (func (;7;))) - (core func (;36;) (canon lower (func 7) (memory 0))) - (alias export 0 "list-float32-param" (func (;8;))) - (core func (;37;) (canon lower (func 8) (memory 0))) - (alias export 0 "list-float64-param" (func (;9;))) - (core func (;38;) (canon lower (func 9) (memory 0))) - (alias export 0 "list-u8-ret" (func (;10;))) - (core func (;39;) (canon lower (func 10) (memory 0) (realloc 28))) - (alias export 0 "list-u16-ret" (func (;11;))) - (core func (;40;) (canon lower (func 11) (memory 0) (realloc 28))) - (alias export 0 "list-u32-ret" (func (;12;))) - (core func (;41;) (canon lower (func 12) (memory 0) (realloc 28))) - (alias export 0 "list-u64-ret" (func (;13;))) - (core func (;42;) (canon lower (func 13) (memory 0) (realloc 28))) - (alias export 0 "list-s8-ret" (func (;14;))) - (core func (;43;) (canon lower (func 14) (memory 0) (realloc 28))) - (alias export 0 "list-s16-ret" (func (;15;))) - (core func (;44;) (canon lower (func 15) (memory 0) (realloc 28))) - (alias export 0 "list-s32-ret" (func (;16;))) - (core func (;45;) (canon lower (func 16) (memory 0) (realloc 28))) - (alias export 0 "list-s64-ret" (func (;17;))) - (core func (;46;) (canon lower (func 17) (memory 0) (realloc 28))) - (alias export 0 "list-float32-ret" (func (;18;))) - (core func (;47;) (canon lower (func 18) (memory 0) (realloc 28))) - (alias export 0 "list-float64-ret" (func (;19;))) - (core func (;48;) (canon lower (func 19) (memory 0) (realloc 28))) - (alias export 0 "tuple-list" (func (;20;))) - (core func (;49;) (canon lower (func 20) (memory 0) (realloc 28))) - (alias export 0 "string-list-arg" (func (;21;))) - (core func (;50;) (canon lower (func 21) (memory 0) string-encoding=utf8)) - (alias export 0 "string-list-ret" (func (;22;))) - (core func (;51;) (canon lower (func 22) (memory 0) (realloc 28) string-encoding=utf8)) - (alias export 0 "tuple-string-list" (func (;23;))) - (core func (;52;) (canon lower (func 23) (memory 0) (realloc 28) string-encoding=utf8)) - (alias export 0 "string-list" (func (;24;))) - (core func (;53;) (canon lower (func 24) (memory 0) (realloc 28) string-encoding=utf8)) - (alias export 0 "record-list" (func (;25;))) - (core func (;54;) (canon lower (func 25) (memory 0) (realloc 28) string-encoding=utf8)) - (alias export 0 "variant-list" (func (;26;))) - (core func (;55;) (canon lower (func 26) (memory 0) (realloc 28) string-encoding=utf8)) - (alias export 0 "load-store-everything" (func (;27;))) - (core func (;56;) (canon lower (func 27) (memory 0) (realloc 28) string-encoding=utf8)) - (core instance (;3;) - (export "$imports" (table 0)) - (export "0" (func 29)) - (export "1" (func 30)) - (export "2" (func 31)) - (export "3" (func 32)) - (export "4" (func 33)) - (export "5" (func 34)) - (export "6" (func 35)) - (export "7" (func 36)) - (export "8" (func 37)) - (export "9" (func 38)) - (export "10" (func 39)) - (export "11" (func 40)) - (export "12" (func 41)) - (export "13" (func 42)) - (export "14" (func 43)) - (export "15" (func 44)) - (export "16" (func 45)) - (export "17" (func 46)) - (export "18" (func 47)) - (export "19" (func 48)) - (export "20" (func 49)) - (export "21" (func 50)) - (export "22" (func 51)) - (export "23" (func 52)) - (export "24" (func 53)) - (export "25" (func 54)) - (export "26" (func 55)) - (export "27" (func 56)) - ) - (core instance (;4;) (instantiate 2 - (with "" (instance 3)) - ) - ) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/lists/types_only.wat b/crates/wit-component/tests/interfaces/lists/types_only.wat deleted file mode 100644 index 8595288322..0000000000 --- a/crates/wit-component/tests/interfaces/lists/types_only.wat +++ /dev/null @@ -1,97 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (list u8)) - (type (;1;) (func (param "x" 0))) - (export (;0;) "list-u8-param" (func (type 1))) - (type (;2;) (list u16)) - (type (;3;) (func (param "x" 2))) - (export (;1;) "list-u16-param" (func (type 3))) - (type (;4;) (list u32)) - (type (;5;) (func (param "x" 4))) - (export (;2;) "list-u32-param" (func (type 5))) - (type (;6;) (list u64)) - (type (;7;) (func (param "x" 6))) - (export (;3;) "list-u64-param" (func (type 7))) - (type (;8;) (list s8)) - (type (;9;) (func (param "x" 8))) - (export (;4;) "list-s8-param" (func (type 9))) - (type (;10;) (list s16)) - (type (;11;) (func (param "x" 10))) - (export (;5;) "list-s16-param" (func (type 11))) - (type (;12;) (list s32)) - (type (;13;) (func (param "x" 12))) - (export (;6;) "list-s32-param" (func (type 13))) - (type (;14;) (list s64)) - (type (;15;) (func (param "x" 14))) - (export (;7;) "list-s64-param" (func (type 15))) - (type (;16;) (list float32)) - (type (;17;) (func (param "x" 16))) - (export (;8;) "list-float32-param" (func (type 17))) - (type (;18;) (list float64)) - (type (;19;) (func (param "x" 18))) - (export (;9;) "list-float64-param" (func (type 19))) - (type (;20;) (func (result 0))) - (export (;10;) "list-u8-ret" (func (type 20))) - (type (;21;) (func (result 2))) - (export (;11;) "list-u16-ret" (func (type 21))) - (type (;22;) (func (result 4))) - (export (;12;) "list-u32-ret" (func (type 22))) - (type (;23;) (func (result 6))) - (export (;13;) "list-u64-ret" (func (type 23))) - (type (;24;) (func (result 8))) - (export (;14;) "list-s8-ret" (func (type 24))) - (type (;25;) (func (result 10))) - (export (;15;) "list-s16-ret" (func (type 25))) - (type (;26;) (func (result 12))) - (export (;16;) "list-s32-ret" (func (type 26))) - (type (;27;) (func (result 14))) - (export (;17;) "list-s64-ret" (func (type 27))) - (type (;28;) (func (result 16))) - (export (;18;) "list-float32-ret" (func (type 28))) - (type (;29;) (func (result 18))) - (export (;19;) "list-float64-ret" (func (type 29))) - (type (;30;) (tuple u8 s8)) - (type (;31;) (list 30)) - (type (;32;) (tuple s64 u32)) - (type (;33;) (list 32)) - (type (;34;) (func (param "x" 31) (result 33))) - (export (;20;) "tuple-list" (func (type 34))) - (type (;35;) (list string)) - (type (;36;) (func (param "a" 35))) - (export (;21;) "string-list-arg" (func (type 36))) - (type (;37;) (func (result 35))) - (export (;22;) "string-list-ret" (func (type 37))) - (type (;38;) (tuple u8 string)) - (type (;39;) (list 38)) - (type (;40;) (tuple string u8)) - (type (;41;) (list 40)) - (type (;42;) (func (param "x" 39) (result 41))) - (export (;23;) "tuple-string-list" (func (type 42))) - (type (;43;) (func (param "x" 35) (result 35))) - (export (;24;) "string-list" (func (type 43))) - (type (;44;) (record (field "a1" u32) (field "a2" u64) (field "a3" s32) (field "a4" s64) (field "b" string) (field "c" 0))) - (export (;45;) "other-record" (type (eq 44))) - (type (;46;) (record (field "x" string) (field "y" 44) (field "c1" u32) (field "c2" u64) (field "c3" s32) (field "c4" s64))) - (export (;47;) "some-record" (type (eq 46))) - (type (;48;) (list 46)) - (type (;49;) (list 44)) - (type (;50;) (func (param "x" 48) (result 49))) - (export (;25;) "record-list" (func (type 50))) - (type (;51;) (variant (case "a") (case "b" u32) (case "c" string))) - (export (;52;) "other-variant" (type (eq 51))) - (type (;53;) (list 51)) - (type (;54;) (variant (case "a" string) (case "b") (case "c" u32) (case "d" 53))) - (export (;55;) "some-variant" (type (eq 54))) - (type (;56;) (list 54)) - (type (;57;) (func (param "x" 56) (result 53))) - (export (;26;) "variant-list" (func (type 57))) - (type (;58;) (tuple string u8 s8 u16 s16 u32 s32 u64 s64 float32 float64 char)) - (type (;59;) (list 58)) - (export (;60;) "load-store-all-sizes" (type (eq 59))) - (type (;61;) (func (param "a" 59) (result 59))) - (export (;27;) "load-store-everything" (func (type 61))) - ) - ) - (import "lists" (instance (;0;) (type 0))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/multi-doc.wat b/crates/wit-component/tests/interfaces/multi-doc.wat new file mode 100644 index 0000000000..d755d8da0e --- /dev/null +++ b/crates/wit-component/tests/interfaces/multi-doc.wat @@ -0,0 +1,58 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (record)) + (export (;1;) "the-type" (type (eq 0))) + ) + ) + (export (;0;) "b" (instance (type 0))) + (alias export 0 "the-type" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-type" (type (eq 0))) + ) + ) + (export (;1;) "a" (instance (type 2))) + ) + ) + (export (;1;) "b" (type 0)) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) (record)) + (export (;1;) "the-type" (type (eq 0))) + ) + ) + (import "b" "pkg:/b/b" (instance (type 0))) + (alias export 0 "the-type" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-type" (type (eq 0))) + ) + ) + (import "a" "pkg:/b/a" (instance (type 2))) + (alias export 1 "the-type" (type (;3;))) + (type (;4;) + (instance + (alias outer 1 3 (type (;0;))) + (export (;1;) "the-type" (type (eq 0))) + ) + ) + (export (;0;) "b" (instance (type 4))) + (alias export 2 "the-type" (type (;5;))) + (type (;6;) + (instance + (alias outer 1 5 (type (;0;))) + (export (;1;) "the-type" (type (eq 0))) + ) + ) + (export (;1;) "a" (instance (type 6))) + ) + ) + (export (;3;) "a" (type 2)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/multi-doc/a.wit b/crates/wit-component/tests/interfaces/multi-doc/a.wit new file mode 100644 index 0000000000..71fb0ce245 --- /dev/null +++ b/crates/wit-component/tests/interfaces/multi-doc/a.wit @@ -0,0 +1,7 @@ +default interface a { + use self.b.{the-type} +} + +interface b { + use pkg.b.{the-type} +} diff --git a/crates/wit-component/tests/interfaces/multi-doc/a.wit.print b/crates/wit-component/tests/interfaces/multi-doc/a.wit.print new file mode 100644 index 0000000000..a6c65878ba --- /dev/null +++ b/crates/wit-component/tests/interfaces/multi-doc/a.wit.print @@ -0,0 +1,8 @@ +interface b { + use pkg.b.b.{the-type} +} + +interface a { + use pkg.b.b.{the-type} +} + diff --git a/crates/wit-component/tests/interfaces/multi-doc/b.wit b/crates/wit-component/tests/interfaces/multi-doc/b.wit new file mode 100644 index 0000000000..da21c2e976 --- /dev/null +++ b/crates/wit-component/tests/interfaces/multi-doc/b.wit @@ -0,0 +1,7 @@ +default interface a { + use self.b.{the-type} +} + +interface b { + record the-type {} +} diff --git a/crates/wit-component/tests/interfaces/multi-doc/b.wit.print b/crates/wit-component/tests/interfaces/multi-doc/b.wit.print new file mode 100644 index 0000000000..4fa21babb6 --- /dev/null +++ b/crates/wit-component/tests/interfaces/multi-doc/b.wit.print @@ -0,0 +1,10 @@ +interface b { + record the-type { + } + +} + +interface a { + use self.b.{the-type} +} + diff --git a/crates/wit-component/tests/interfaces/records.wat b/crates/wit-component/tests/interfaces/records.wat new file mode 100644 index 0000000000..58baf8d2bc --- /dev/null +++ b/crates/wit-component/tests/interfaces/records.wat @@ -0,0 +1,132 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (record)) + (export (;1;) "empty" (type (eq 0))) + (type (;2;) (record (field "a" u32) (field "b" u32))) + (export (;3;) "scalars" (type (eq 2))) + (type (;4;) (flags "a" "b" "c" "d" "e" "f" "g" "h" "i")) + (export (;5;) "really-flags" (type (eq 4))) + (type (;6;) (record (field "a" 3) (field "b" u32) (field "c" 1) (field "d" string) (field "e" 5))) + (export (;7;) "aggregates" (type (eq 6))) + (type (;8;) s32) + (export (;9;) "int-typedef" (type (eq 8))) + (type (;10;) (tuple 9)) + (export (;11;) "tuple-typedef2" (type (eq 10))) + (type (;12;) (tuple char u32)) + (type (;13;) (func (param "x" 12))) + (export (;0;) "tuple-arg" (func (type 13))) + (type (;14;) (func (result 12))) + (export (;1;) "tuple-result" (func (type 14))) + (type (;15;) (func (param "x" 1))) + (export (;2;) "empty-arg" (func (type 15))) + (type (;16;) (func (result 1))) + (export (;3;) "empty-result" (func (type 16))) + (type (;17;) (func (param "x" 3))) + (export (;4;) "scalar-arg" (func (type 17))) + (type (;18;) (func (result 3))) + (export (;5;) "scalar-result" (func (type 18))) + (type (;19;) (func (param "x" 5))) + (export (;6;) "flags-arg" (func (type 19))) + (type (;20;) (func (result 5))) + (export (;7;) "flags-result" (func (type 20))) + (type (;21;) (func (param "x" 7))) + (export (;8;) "aggregate-arg" (func (type 21))) + (type (;22;) (func (result 7))) + (export (;9;) "aggregate-result" (func (type 22))) + (type (;23;) (func (param "e" 11) (result s32))) + (export (;10;) "typedef-inout" (func (type 23))) + ) + ) + (export (;0;) "records" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (record)) + (export (;1;) "empty" (type (eq 0))) + (type (;2;) (record (field "a" u32) (field "b" u32))) + (export (;3;) "scalars" (type (eq 2))) + (type (;4;) (flags "a" "b" "c" "d" "e" "f" "g" "h" "i")) + (export (;5;) "really-flags" (type (eq 4))) + (type (;6;) (record (field "a" 3) (field "b" u32) (field "c" 1) (field "d" string) (field "e" 5))) + (export (;7;) "aggregates" (type (eq 6))) + (type (;8;) s32) + (export (;9;) "int-typedef" (type (eq 8))) + (type (;10;) (tuple 9)) + (export (;11;) "tuple-typedef2" (type (eq 10))) + (type (;12;) (tuple char u32)) + (type (;13;) (func (param "x" 12))) + (export (;0;) "tuple-arg" (func (type 13))) + (type (;14;) (func (result 12))) + (export (;1;) "tuple-result" (func (type 14))) + (type (;15;) (func (param "x" 1))) + (export (;2;) "empty-arg" (func (type 15))) + (type (;16;) (func (result 1))) + (export (;3;) "empty-result" (func (type 16))) + (type (;17;) (func (param "x" 3))) + (export (;4;) "scalar-arg" (func (type 17))) + (type (;18;) (func (result 3))) + (export (;5;) "scalar-result" (func (type 18))) + (type (;19;) (func (param "x" 5))) + (export (;6;) "flags-arg" (func (type 19))) + (type (;20;) (func (result 5))) + (export (;7;) "flags-result" (func (type 20))) + (type (;21;) (func (param "x" 7))) + (export (;8;) "aggregate-arg" (func (type 21))) + (type (;22;) (func (result 7))) + (export (;9;) "aggregate-result" (func (type 22))) + (type (;23;) (func (param "e" 11) (result s32))) + (export (;10;) "typedef-inout" (func (type 23))) + ) + ) + (import "records" "pkg:/records/records" (instance (type 0))) + (alias export 0 "empty" (type (;1;))) + (alias export 0 "scalars" (type (;2;))) + (alias export 0 "really-flags" (type (;3;))) + (alias export 0 "aggregates" (type (;4;))) + (alias export 0 "int-typedef" (type (;5;))) + (alias export 0 "tuple-typedef2" (type (;6;))) + (type (;7;) + (instance + (alias outer 1 1 (type (;0;))) + (alias outer 1 2 (type (;1;))) + (alias outer 1 3 (type (;2;))) + (alias outer 1 4 (type (;3;))) + (alias outer 1 5 (type (;4;))) + (alias outer 1 6 (type (;5;))) + (type (;6;) (tuple char u32)) + (type (;7;) (func (param "x" 6))) + (export (;0;) "tuple-arg" (func (type 7))) + (type (;8;) (func (result 6))) + (export (;1;) "tuple-result" (func (type 8))) + (type (;9;) (func (param "x" 0))) + (export (;2;) "empty-arg" (func (type 9))) + (type (;10;) (func (result 0))) + (export (;3;) "empty-result" (func (type 10))) + (type (;11;) (func (param "x" 1))) + (export (;4;) "scalar-arg" (func (type 11))) + (type (;12;) (func (result 1))) + (export (;5;) "scalar-result" (func (type 12))) + (type (;13;) (func (param "x" 2))) + (export (;6;) "flags-arg" (func (type 13))) + (type (;14;) (func (result 2))) + (export (;7;) "flags-result" (func (type 14))) + (type (;15;) (func (param "x" 3))) + (export (;8;) "aggregate-arg" (func (type 15))) + (type (;16;) (func (result 3))) + (export (;9;) "aggregate-result" (func (type 16))) + (type (;17;) (func (param "e" 5) (result s32))) + (export (;10;) "typedef-inout" (func (type 17))) + ) + ) + (export (;0;) "records" "pkg:/records/records" (instance (type 7))) + ) + ) + (export (;0;) "records-world" (component (type 1))) + ) + ) + (export (;1;) "records" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/records.wit b/crates/wit-component/tests/interfaces/records.wit new file mode 100644 index 0000000000..79fbf3b7ff --- /dev/null +++ b/crates/wit-component/tests/interfaces/records.wit @@ -0,0 +1,60 @@ +interface records { + record empty { + } + + record scalars { + a: u32, + b: u32, + } + + flags really-flags { + a, + b, + c, + d, + e, + f, + g, + h, + i, + } + + record aggregates { + a: scalars, + b: u32, + c: empty, + d: string, + e: really-flags, + } + + type int-typedef = s32 + + type tuple-typedef2 = tuple + + tuple-arg: func(x: tuple) + + tuple-result: func() -> tuple + + empty-arg: func(x: empty) + + empty-result: func() -> empty + + scalar-arg: func(x: scalars) + + scalar-result: func() -> scalars + + flags-arg: func(x: really-flags) + + flags-result: func() -> really-flags + + aggregate-arg: func(x: aggregates) + + aggregate-result: func() -> aggregates + + typedef-inout: func(e: tuple-typedef2) -> s32 +} + +world records-world { + import records: self.records + export records: self.records +} diff --git a/crates/wit-component/tests/interfaces/records.wit.print b/crates/wit-component/tests/interfaces/records.wit.print new file mode 100644 index 0000000000..79fbf3b7ff --- /dev/null +++ b/crates/wit-component/tests/interfaces/records.wit.print @@ -0,0 +1,60 @@ +interface records { + record empty { + } + + record scalars { + a: u32, + b: u32, + } + + flags really-flags { + a, + b, + c, + d, + e, + f, + g, + h, + i, + } + + record aggregates { + a: scalars, + b: u32, + c: empty, + d: string, + e: really-flags, + } + + type int-typedef = s32 + + type tuple-typedef2 = tuple + + tuple-arg: func(x: tuple) + + tuple-result: func() -> tuple + + empty-arg: func(x: empty) + + empty-result: func() -> empty + + scalar-arg: func(x: scalars) + + scalar-result: func() -> scalars + + flags-arg: func(x: really-flags) + + flags-result: func() -> really-flags + + aggregate-arg: func(x: aggregates) + + aggregate-result: func() -> aggregates + + typedef-inout: func(e: tuple-typedef2) -> s32 +} + +world records-world { + import records: self.records + export records: self.records +} diff --git a/crates/wit-component/tests/interfaces/records/component.wat b/crates/wit-component/tests/interfaces/records/component.wat deleted file mode 100644 index 12c610f09e..0000000000 --- a/crates/wit-component/tests/interfaces/records/component.wat +++ /dev/null @@ -1,290 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (tuple char u32)) - (type (;1;) (func (param "x" 0))) - (export (;0;) "tuple-arg" (func (type 1))) - (type (;2;) (func (result 0))) - (export (;1;) "tuple-result" (func (type 2))) - (type (;3;) (record)) - (export (;4;) "empty" (type (eq 3))) - (type (;5;) (func (param "x" 3))) - (export (;2;) "empty-arg" (func (type 5))) - (type (;6;) (func (result 3))) - (export (;3;) "empty-result" (func (type 6))) - (type (;7;) (record (field "a" u32) (field "b" u32))) - (export (;8;) "scalars" (type (eq 7))) - (type (;9;) (func (param "x" 7))) - (export (;4;) "scalar-arg" (func (type 9))) - (type (;10;) (func (result 7))) - (export (;5;) "scalar-result" (func (type 10))) - (type (;11;) (flags "a" "b" "c" "d" "e" "f" "g" "h" "i")) - (export (;12;) "really-flags" (type (eq 11))) - (type (;13;) (func (param "x" 11))) - (export (;6;) "flags-arg" (func (type 13))) - (type (;14;) (func (result 11))) - (export (;7;) "flags-result" (func (type 14))) - (type (;15;) (record (field "a" 7) (field "b" u32) (field "c" 3) (field "d" string) (field "e" 11))) - (export (;16;) "aggregates" (type (eq 15))) - (type (;17;) (func (param "x" 15))) - (export (;8;) "aggregate-arg" (func (type 17))) - (type (;18;) (func (result 15))) - (export (;9;) "aggregate-result" (func (type 18))) - (type (;19;) s32) - (export (;20;) "int-typedef" (type (eq 19))) - (type (;21;) (tuple 19)) - (export (;22;) "tuple-typedef2" (type (eq 21))) - (type (;23;) (func (param "e" 21) (result s32))) - (export (;10;) "typedef-inout" (func (type 23))) - ) - ) - (import "records" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32 i32))) - (type (;1;) (func (param i32))) - (type (;2;) (func)) - (type (;3;) (func (result i32))) - (type (;4;) (func (param i32 i32 i32 i32 i32 i32))) - (type (;5;) (func (param i32) (result i32))) - (type (;6;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32))) - (type (;7;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32))) - (type (;8;) (func (param i32 i32 i32 i32) (result i32))) - (import "records" "tuple-arg" (func (;0;) (type 0))) - (import "records" "tuple-result" (func (;1;) (type 1))) - (import "records" "empty-arg" (func (;2;) (type 2))) - (import "records" "empty-result" (func (;3;) (type 2))) - (import "records" "scalar-arg" (func (;4;) (type 0))) - (import "records" "scalar-result" (func (;5;) (type 1))) - (import "records" "flags-arg" (func (;6;) (type 1))) - (import "records" "flags-result" (func (;7;) (type 3))) - (import "records" "aggregate-arg" (func (;8;) (type 4))) - (import "records" "aggregate-result" (func (;9;) (type 1))) - (import "records" "typedef-inout" (func (;10;) (type 5))) - (func (;11;) (type 0) (param i32 i32) - unreachable - ) - (func (;12;) (type 3) (result i32) - unreachable - ) - (func (;13;) (type 2) - unreachable - ) - (func (;14;) (type 2) - unreachable - ) - (func (;15;) (type 0) (param i32 i32) - unreachable - ) - (func (;16;) (type 3) (result i32) - unreachable - ) - (func (;17;) (type 6) (param i32 i32 i32 i32 i32 i32 i32 i32 i32) - unreachable - ) - (func (;18;) (type 3) (result i32) - unreachable - ) - (func (;19;) (type 7) (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) - unreachable - ) - (func (;20;) (type 3) (result i32) - unreachable - ) - (func (;21;) (type 1) (param i32)) - (func (;22;) (type 5) (param i32) (result i32) - unreachable - ) - (func (;23;) (type 3) (result i32) - unreachable - ) - (func (;24;) (type 8) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "records#tuple-arg" (func 11)) - (export "records#tuple-result" (func 12)) - (export "records#empty-arg" (func 13)) - (export "records#empty-result" (func 14)) - (export "records#scalar-arg" (func 15)) - (export "records#scalar-result" (func 16)) - (export "records#flags-arg" (func 17)) - (export "records#flags-result" (func 18)) - (export "records#aggregate-arg" (func 19)) - (export "records#aggregate-result" (func 20)) - (export "cabi_post_records#aggregate-result" (func 21)) - (export "records#typedef-inout" (func 22)) - (export "records#tuple-tupledef-user" (func 23)) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 24)) - ) - (core module (;1;) - (type (;0;) (func (param i32))) - (type (;1;) (func (param i32 i32 i32 i32 i32 i32))) - (func $indirect-records-tuple-result (;0;) (type 0) (param i32) - local.get 0 - i32.const 0 - call_indirect (type 0) - ) - (func $indirect-records-scalar-result (;1;) (type 0) (param i32) - local.get 0 - i32.const 1 - call_indirect (type 0) - ) - (func $indirect-records-aggregate-arg (;2;) (type 1) (param i32 i32 i32 i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - local.get 4 - local.get 5 - i32.const 2 - call_indirect (type 1) - ) - (func $indirect-records-aggregate-result (;3;) (type 0) (param i32) - local.get 0 - i32.const 3 - call_indirect (type 0) - ) - (table (;0;) 4 4 funcref) - (export "0" (func $indirect-records-tuple-result)) - (export "1" (func $indirect-records-scalar-result)) - (export "2" (func $indirect-records-aggregate-arg)) - (export "3" (func $indirect-records-aggregate-result)) - (export "$imports" (table 0)) - ) - (core module (;2;) - (type (;0;) (func (param i32))) - (type (;1;) (func (param i32 i32 i32 i32 i32 i32))) - (import "" "0" (func (;0;) (type 0))) - (import "" "1" (func (;1;) (type 0))) - (import "" "2" (func (;2;) (type 1))) - (import "" "3" (func (;3;) (type 0))) - (import "" "$imports" (table (;0;) 4 4 funcref)) - (elem (;0;) (i32.const 0) func 0 1 2 3) - ) - (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias core export 0 "1" (core func (;1;))) - (alias core export 0 "2" (core func (;2;))) - (alias core export 0 "3" (core func (;3;))) - (alias export 0 "tuple-arg" (func (;0;))) - (core func (;4;) (canon lower (func 0))) - (alias export 0 "empty-arg" (func (;1;))) - (core func (;5;) (canon lower (func 1))) - (alias export 0 "empty-result" (func (;2;))) - (core func (;6;) (canon lower (func 2))) - (alias export 0 "scalar-arg" (func (;3;))) - (core func (;7;) (canon lower (func 3))) - (alias export 0 "flags-arg" (func (;4;))) - (core func (;8;) (canon lower (func 4))) - (alias export 0 "flags-result" (func (;5;))) - (core func (;9;) (canon lower (func 5))) - (alias export 0 "typedef-inout" (func (;6;))) - (core func (;10;) (canon lower (func 6))) - (core instance (;1;) - (export "tuple-result" (func 0)) - (export "scalar-result" (func 1)) - (export "aggregate-arg" (func 2)) - (export "aggregate-result" (func 3)) - (export "tuple-arg" (func 4)) - (export "empty-arg" (func 5)) - (export "empty-result" (func 6)) - (export "scalar-arg" (func 7)) - (export "flags-arg" (func 8)) - (export "flags-result" (func 9)) - (export "typedef-inout" (func 10)) - ) - (core instance (;2;) (instantiate 0 - (with "records" (instance 1)) - ) - ) - (alias core export 2 "memory" (core memory (;0;))) - (alias core export 2 "cabi_realloc" (core func (;11;))) - (alias core export 0 "$imports" (core table (;0;))) - (alias export 0 "tuple-result" (func (;7;))) - (core func (;12;) (canon lower (func 7) (memory 0))) - (alias export 0 "scalar-result" (func (;8;))) - (core func (;13;) (canon lower (func 8) (memory 0))) - (alias export 0 "aggregate-arg" (func (;9;))) - (core func (;14;) (canon lower (func 9) (memory 0) string-encoding=utf8)) - (alias export 0 "aggregate-result" (func (;10;))) - (core func (;15;) (canon lower (func 10) (memory 0) (realloc 11) string-encoding=utf8)) - (core instance (;3;) - (export "$imports" (table 0)) - (export "0" (func 12)) - (export "1" (func 13)) - (export "2" (func 14)) - (export "3" (func 15)) - ) - (core instance (;4;) (instantiate 2 - (with "" (instance 3)) - ) - ) - (alias core export 2 "records#tuple-arg" (core func (;16;))) - (type (;1;) (tuple char u32)) - (type (;2;) (func (param "x" 1))) - (func (;11;) (type 2) (canon lift (core func 16))) - (alias core export 2 "records#tuple-result" (core func (;17;))) - (type (;3;) (func (result 1))) - (func (;12;) (type 3) (canon lift (core func 17) (memory 0))) - (alias core export 2 "records#empty-arg" (core func (;18;))) - (type (;4;) (record)) - (type (;5;) (func (param "x" 4))) - (func (;13;) (type 5) (canon lift (core func 18))) - (alias core export 2 "records#empty-result" (core func (;19;))) - (type (;6;) (func (result 4))) - (func (;14;) (type 6) (canon lift (core func 19))) - (alias core export 2 "records#scalar-arg" (core func (;20;))) - (type (;7;) (record (field "a" u32) (field "b" u32))) - (type (;8;) (func (param "x" 7))) - (func (;15;) (type 8) (canon lift (core func 20))) - (alias core export 2 "records#scalar-result" (core func (;21;))) - (type (;9;) (func (result 7))) - (func (;16;) (type 9) (canon lift (core func 21) (memory 0))) - (alias core export 2 "records#flags-arg" (core func (;22;))) - (type (;10;) (record (field "a" bool) (field "b" bool) (field "c" bool) (field "d" bool) (field "e" bool) (field "f" bool) (field "g" bool) (field "h" bool) (field "i" bool))) - (type (;11;) (func (param "x" 10))) - (func (;17;) (type 11) (canon lift (core func 22))) - (alias core export 2 "records#flags-result" (core func (;23;))) - (type (;12;) (func (result 10))) - (func (;18;) (type 12) (canon lift (core func 23) (memory 0))) - (alias core export 2 "records#aggregate-arg" (core func (;24;))) - (type (;13;) (record (field "a" 7) (field "b" u32) (field "c" 4) (field "d" string) (field "e" 10))) - (type (;14;) (func (param "x" 13))) - (func (;19;) (type 14) (canon lift (core func 24) (memory 0) (realloc 11) string-encoding=utf8)) - (alias core export 2 "records#aggregate-result" (core func (;25;))) - (type (;15;) (func (result 13))) - (alias core export 2 "cabi_post_records#aggregate-result" (core func (;26;))) - (func (;20;) (type 15) (canon lift (core func 25) (memory 0) string-encoding=utf8 (post-return 26))) - (alias core export 2 "records#typedef-inout" (core func (;27;))) - (type (;16;) s32) - (type (;17;) (tuple 16)) - (type (;18;) (func (param "e" 17) (result s32))) - (func (;21;) (type 18) (canon lift (core func 27))) - (alias core export 2 "records#tuple-tupledef-user" (core func (;28;))) - (type (;19;) (tuple s32)) - (type (;20;) (func (result 19))) - (func (;22;) (type 20) (canon lift (core func 28))) - (instance (;1;) - (export "tuple-arg" (func 11)) - (export "tuple-result" (func 12)) - (export "empty-arg" (func 13)) - (export "empty-result" (func 14)) - (export "scalar-arg" (func 15)) - (export "scalar-result" (func 16)) - (export "flags-arg" (func 17)) - (export "flags-result" (func 18)) - (export "aggregate-arg" (func 19)) - (export "aggregate-result" (func 20)) - (export "typedef-inout" (func 21)) - (export "tuple-tupledef-user" (func 22)) - (export "empty" (type 4)) - (export "scalars" (type 7)) - (export "really-flags" (type 10)) - (export "aggregates" (type 13)) - (export "int-typedef" (type 16)) - (export "tuple-typedef2" (type 17)) - (export "tuple-typedef" (type 19)) - ) - (export (;2;) "records" (instance 1)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/records/types_only.wat b/crates/wit-component/tests/interfaces/records/types_only.wat deleted file mode 100644 index 017ad2221d..0000000000 --- a/crates/wit-component/tests/interfaces/records/types_only.wat +++ /dev/null @@ -1,86 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (tuple char u32)) - (type (;1;) (func (param "x" 0))) - (export (;0;) "tuple-arg" (func (type 1))) - (type (;2;) (func (result 0))) - (export (;1;) "tuple-result" (func (type 2))) - (type (;3;) (record)) - (export (;4;) "empty" (type (eq 3))) - (type (;5;) (func (param "x" 3))) - (export (;2;) "empty-arg" (func (type 5))) - (type (;6;) (func (result 3))) - (export (;3;) "empty-result" (func (type 6))) - (type (;7;) (record (field "a" u32) (field "b" u32))) - (export (;8;) "scalars" (type (eq 7))) - (type (;9;) (func (param "x" 7))) - (export (;4;) "scalar-arg" (func (type 9))) - (type (;10;) (func (result 7))) - (export (;5;) "scalar-result" (func (type 10))) - (type (;11;) (flags "a" "b" "c" "d" "e" "f" "g" "h" "i")) - (export (;12;) "really-flags" (type (eq 11))) - (type (;13;) (func (param "x" 11))) - (export (;6;) "flags-arg" (func (type 13))) - (type (;14;) (func (result 11))) - (export (;7;) "flags-result" (func (type 14))) - (type (;15;) (record (field "a" 7) (field "b" u32) (field "c" 3) (field "d" string) (field "e" 11))) - (export (;16;) "aggregates" (type (eq 15))) - (type (;17;) (func (param "x" 15))) - (export (;8;) "aggregate-arg" (func (type 17))) - (type (;18;) (func (result 15))) - (export (;9;) "aggregate-result" (func (type 18))) - (type (;19;) s32) - (export (;20;) "int-typedef" (type (eq 19))) - (type (;21;) (tuple 19)) - (export (;22;) "tuple-typedef2" (type (eq 21))) - (type (;23;) (func (param "e" 21) (result s32))) - (export (;10;) "typedef-inout" (func (type 23))) - ) - ) - (import "records" (instance (;0;) (type 0))) - (type (;1;) - (instance - (type (;0;) (tuple char u32)) - (type (;1;) (func (param "x" 0))) - (export (;0;) "tuple-arg" (func (type 1))) - (type (;2;) (func (result 0))) - (export (;1;) "tuple-result" (func (type 2))) - (type (;3;) (record)) - (export (;4;) "empty" (type (eq 3))) - (type (;5;) (func (param "x" 3))) - (export (;2;) "empty-arg" (func (type 5))) - (type (;6;) (func (result 3))) - (export (;3;) "empty-result" (func (type 6))) - (type (;7;) (record (field "a" u32) (field "b" u32))) - (export (;8;) "scalars" (type (eq 7))) - (type (;9;) (func (param "x" 7))) - (export (;4;) "scalar-arg" (func (type 9))) - (type (;10;) (func (result 7))) - (export (;5;) "scalar-result" (func (type 10))) - (type (;11;) (record (field "a" bool) (field "b" bool) (field "c" bool) (field "d" bool) (field "e" bool) (field "f" bool) (field "g" bool) (field "h" bool) (field "i" bool))) - (export (;12;) "really-flags" (type (eq 11))) - (type (;13;) (func (param "x" 11))) - (export (;6;) "flags-arg" (func (type 13))) - (type (;14;) (func (result 11))) - (export (;7;) "flags-result" (func (type 14))) - (type (;15;) (record (field "a" 7) (field "b" u32) (field "c" 3) (field "d" string) (field "e" 11))) - (export (;16;) "aggregates" (type (eq 15))) - (type (;17;) (func (param "x" 15))) - (export (;8;) "aggregate-arg" (func (type 17))) - (type (;18;) (func (result 15))) - (export (;9;) "aggregate-result" (func (type 18))) - (type (;19;) s32) - (export (;20;) "int-typedef" (type (eq 19))) - (type (;21;) (tuple 19)) - (export (;22;) "tuple-typedef2" (type (eq 21))) - (type (;23;) (func (param "e" 21) (result s32))) - (export (;10;) "typedef-inout" (func (type 23))) - (type (;24;) (tuple s32)) - (export (;25;) "tuple-typedef" (type (eq 24))) - (type (;26;) (func (result 24))) - (export (;11;) "tuple-tupledef-user" (func (type 26))) - ) - ) - (export (;2;) "records" (type 1)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/records/world.wit b/crates/wit-component/tests/interfaces/records/world.wit deleted file mode 100644 index 34a42ac276..0000000000 --- a/crates/wit-component/tests/interfaces/records/world.wit +++ /dev/null @@ -1,120 +0,0 @@ -interface records { - record empty { - } - - record scalars { - a: u32, - b: u32, - } - - flags really-flags { - a, - b, - c, - d, - e, - f, - g, - h, - i, - } - - record aggregates { - a: scalars, - b: u32, - c: empty, - d: string, - e: really-flags, - } - - type int-typedef = s32 - - type tuple-typedef2 = tuple - - tuple-arg: func(x: tuple) - - tuple-result: func() -> tuple - - empty-arg: func(x: empty) - - empty-result: func() -> empty - - scalar-arg: func(x: scalars) - - scalar-result: func() -> scalars - - flags-arg: func(x: really-flags) - - flags-result: func() -> really-flags - - aggregate-arg: func(x: aggregates) - - aggregate-result: func() -> aggregates - - typedef-inout: func(e: tuple-typedef2) -> s32 -} - -interface records0 { - record empty { - } - - record scalars { - a: u32, - b: u32, - } - - record really-flags { - a: bool, - b: bool, - c: bool, - d: bool, - e: bool, - f: bool, - g: bool, - h: bool, - i: bool, - } - - record aggregates { - a: scalars, - b: u32, - c: empty, - d: string, - e: really-flags, - } - - type int-typedef = s32 - - type tuple-typedef2 = tuple - - type tuple-typedef = tuple - - tuple-arg: func(x: tuple) - - tuple-result: func() -> tuple - - empty-arg: func(x: empty) - - empty-result: func() -> empty - - scalar-arg: func(x: scalars) - - scalar-result: func() -> scalars - - flags-arg: func(x: really-flags) - - flags-result: func() -> really-flags - - aggregate-arg: func(x: aggregates) - - aggregate-result: func() -> aggregates - - typedef-inout: func(e: tuple-typedef2) -> s32 - - tuple-tupledef-user: func() -> tuple-typedef -} - -world records-world { - import records: records - export records: records0 -} diff --git a/crates/wit-component/tests/interfaces/reference-out-of-order.wat b/crates/wit-component/tests/interfaces/reference-out-of-order.wat new file mode 100644 index 0000000000..1cec5b27b8 --- /dev/null +++ b/crates/wit-component/tests/interfaces/reference-out-of-order.wat @@ -0,0 +1,54 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (record (field "s" string))) + (export (;1;) "r" (type (eq 0))) + (type (;2;) (variant (case "s" string))) + (export (;3;) "v" (type (eq 2))) + (type (;4;) (record (field "s" u32))) + (export (;5;) "r-no-string" (type (eq 4))) + (type (;6;) (variant (case "s" u32))) + (export (;7;) "v-no-string" (type (eq 6))) + (type (;8;) (func (param "x" 1))) + (export (;0;) "a" (func (type 8))) + (type (;9;) (func (param "x" 3))) + (export (;1;) "b" (func (type 9))) + (type (;10;) (func (param "x" 5))) + (export (;2;) "c" (func (type 10))) + (type (;11;) (func (param "x" 7))) + (export (;3;) "d" (func (type 11))) + ) + ) + (export (;0;) "foo" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (record (field "s" string))) + (export (;1;) "r" (type (eq 0))) + (type (;2;) (variant (case "s" string))) + (export (;3;) "v" (type (eq 2))) + (type (;4;) (record (field "s" u32))) + (export (;5;) "r-no-string" (type (eq 4))) + (type (;6;) (variant (case "s" u32))) + (export (;7;) "v-no-string" (type (eq 6))) + (type (;8;) (func (param "x" 1))) + (export (;0;) "a" (func (type 8))) + (type (;9;) (func (param "x" 3))) + (export (;1;) "b" (func (type 9))) + (type (;10;) (func (param "x" 5))) + (export (;2;) "c" (func (type 10))) + (type (;11;) (func (param "x" 7))) + (export (;3;) "d" (func (type 11))) + ) + ) + (import "foo" "pkg:/reference-out-of-order/foo" (instance (type 0))) + ) + ) + (export (;0;) "foo-world" (component (type 1))) + ) + ) + (export (;1;) "reference-out-of-order" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/reference-out-of-order.wit b/crates/wit-component/tests/interfaces/reference-out-of-order.wit new file mode 100644 index 0000000000..82a7aaf9c4 --- /dev/null +++ b/crates/wit-component/tests/interfaces/reference-out-of-order.wit @@ -0,0 +1,26 @@ +interface foo { + a: func(x: r) + b: func(x: v) + c: func(x: r-no-string) + d: func(x: v-no-string) + + record r { + s: string, + } + + variant v { + s(string), + } + + record r-no-string { + s: u32, + } + + variant v-no-string { + s(u32), + } +} + +world foo-world { + import foo: self.foo +} diff --git a/crates/wit-component/tests/interfaces/reference-out-of-order/world.wit b/crates/wit-component/tests/interfaces/reference-out-of-order.wit.print similarity index 92% rename from crates/wit-component/tests/interfaces/reference-out-of-order/world.wit rename to crates/wit-component/tests/interfaces/reference-out-of-order.wit.print index 09a0e13aa5..a7979f3827 100644 --- a/crates/wit-component/tests/interfaces/reference-out-of-order/world.wit +++ b/crates/wit-component/tests/interfaces/reference-out-of-order.wit.print @@ -25,5 +25,5 @@ interface foo { } world foo-world { - import foo: foo + import foo: self.foo } diff --git a/crates/wit-component/tests/interfaces/reference-out-of-order/component.wat b/crates/wit-component/tests/interfaces/reference-out-of-order/component.wat deleted file mode 100644 index 3a4a5ddfd0..0000000000 --- a/crates/wit-component/tests/interfaces/reference-out-of-order/component.wat +++ /dev/null @@ -1,101 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (record (field "s" string))) - (export (;1;) "r" (type (eq 0))) - (type (;2;) (func (param "x" 0))) - (export (;0;) "a" (func (type 2))) - (type (;3;) (variant (case "s" string))) - (export (;4;) "v" (type (eq 3))) - (type (;5;) (func (param "x" 3))) - (export (;1;) "b" (func (type 5))) - (type (;6;) (record (field "s" u32))) - (export (;7;) "r-no-string" (type (eq 6))) - (type (;8;) (func (param "x" 6))) - (export (;2;) "c" (func (type 8))) - (type (;9;) (variant (case "s" u32))) - (export (;10;) "v-no-string" (type (eq 9))) - (type (;11;) (func (param "x" 9))) - (export (;3;) "d" (func (type 11))) - ) - ) - (import "foo" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32 i32))) - (type (;1;) (func (param i32 i32 i32))) - (type (;2;) (func (param i32))) - (type (;3;) (func (param i32 i32 i32 i32) (result i32))) - (import "foo" "a" (func (;0;) (type 0))) - (import "foo" "b" (func (;1;) (type 1))) - (import "foo" "c" (func (;2;) (type 2))) - (import "foo" "d" (func (;3;) (type 0))) - (func (;4;) (type 3) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 4)) - ) - (core module (;1;) - (type (;0;) (func (param i32 i32))) - (type (;1;) (func (param i32 i32 i32))) - (func $indirect-foo-a (;0;) (type 0) (param i32 i32) - local.get 0 - local.get 1 - i32.const 0 - call_indirect (type 0) - ) - (func $indirect-foo-b (;1;) (type 1) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 1 - call_indirect (type 1) - ) - (table (;0;) 2 2 funcref) - (export "0" (func $indirect-foo-a)) - (export "1" (func $indirect-foo-b)) - (export "$imports" (table 0)) - ) - (core module (;2;) - (type (;0;) (func (param i32 i32))) - (type (;1;) (func (param i32 i32 i32))) - (import "" "0" (func (;0;) (type 0))) - (import "" "1" (func (;1;) (type 1))) - (import "" "$imports" (table (;0;) 2 2 funcref)) - (elem (;0;) (i32.const 0) func 0 1) - ) - (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias core export 0 "1" (core func (;1;))) - (alias export 0 "c" (func (;0;))) - (core func (;2;) (canon lower (func 0))) - (alias export 0 "d" (func (;1;))) - (core func (;3;) (canon lower (func 1))) - (core instance (;1;) - (export "a" (func 0)) - (export "b" (func 1)) - (export "c" (func 2)) - (export "d" (func 3)) - ) - (core instance (;2;) (instantiate 0 - (with "foo" (instance 1)) - ) - ) - (alias core export 2 "memory" (core memory (;0;))) - (alias core export 2 "cabi_realloc" (core func (;4;))) - (alias core export 0 "$imports" (core table (;0;))) - (alias export 0 "a" (func (;2;))) - (core func (;5;) (canon lower (func 2) (memory 0) string-encoding=utf8)) - (alias export 0 "b" (func (;3;))) - (core func (;6;) (canon lower (func 3) (memory 0) string-encoding=utf8)) - (core instance (;3;) - (export "$imports" (table 0)) - (export "0" (func 5)) - (export "1" (func 6)) - ) - (core instance (;4;) (instantiate 2 - (with "" (instance 3)) - ) - ) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/reference-out-of-order/types_only.wat b/crates/wit-component/tests/interfaces/reference-out-of-order/types_only.wat deleted file mode 100644 index 5795b97d1c..0000000000 --- a/crates/wit-component/tests/interfaces/reference-out-of-order/types_only.wat +++ /dev/null @@ -1,23 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (record (field "s" string))) - (export (;1;) "r" (type (eq 0))) - (type (;2;) (func (param "x" 0))) - (export (;0;) "a" (func (type 2))) - (type (;3;) (variant (case "s" string))) - (export (;4;) "v" (type (eq 3))) - (type (;5;) (func (param "x" 3))) - (export (;1;) "b" (func (type 5))) - (type (;6;) (record (field "s" u32))) - (export (;7;) "r-no-string" (type (eq 6))) - (type (;8;) (func (param "x" 6))) - (export (;2;) "c" (func (type 8))) - (type (;9;) (variant (case "s" u32))) - (export (;10;) "v-no-string" (type (eq 9))) - (type (;11;) (func (param "x" 9))) - (export (;3;) "d" (func (type 11))) - ) - ) - (import "foo" (instance (;0;) (type 0))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/simple-deps.wat b/crates/wit-component/tests/interfaces/simple-deps.wat new file mode 100644 index 0000000000..f0da7f575d --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-deps.wat @@ -0,0 +1,22 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) u8) + (export (;1;) "some-type" (type (eq 0))) + ) + ) + (import "types" "path:/some-dep/types/types" (instance (type 0))) + (alias export 0 "some-type" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "some-type" (type (eq 0))) + ) + ) + (export (;0;) "foo" (instance (type 2))) + ) + ) + (export (;1;) "foo" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/simple-deps/deps/some-dep/types.wit b/crates/wit-component/tests/interfaces/simple-deps/deps/some-dep/types.wit new file mode 100644 index 0000000000..1fd776144d --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-deps/deps/some-dep/types.wit @@ -0,0 +1,3 @@ +default interface types { + type some-type = u8 +} diff --git a/crates/wit-component/tests/interfaces/simple-deps/foo.wit b/crates/wit-component/tests/interfaces/simple-deps/foo.wit new file mode 100644 index 0000000000..0b034021cb --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-deps/foo.wit @@ -0,0 +1,3 @@ +interface foo { + use some-dep.types.{some-type} +} diff --git a/crates/wit-component/tests/interfaces/simple-deps/foo.wit.print b/crates/wit-component/tests/interfaces/simple-deps/foo.wit.print new file mode 100644 index 0000000000..eaf5632f3c --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-deps/foo.wit.print @@ -0,0 +1,4 @@ +interface foo { + use some-dep.types.types.{some-type} +} + diff --git a/crates/wit-component/tests/interfaces/simple-multi.wat b/crates/wit-component/tests/interfaces/simple-multi.wat new file mode 100644 index 0000000000..5f00f6de11 --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-multi.wat @@ -0,0 +1,20 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance) + ) + (export (;0;) "bar" (instance (type 0))) + ) + ) + (export (;1;) "bar" (type 0)) + (type (;2;) + (component + (type (;0;) + (instance) + ) + (export (;0;) "foo" (instance (type 0))) + ) + ) + (export (;3;) "foo" (type 2)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/simple-multi/bar.wit b/crates/wit-component/tests/interfaces/simple-multi/bar.wit new file mode 100644 index 0000000000..4e0eb61bca --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-multi/bar.wit @@ -0,0 +1 @@ +interface bar {} diff --git a/crates/wit-component/tests/interfaces/simple-multi/bar.wit.print b/crates/wit-component/tests/interfaces/simple-multi/bar.wit.print new file mode 100644 index 0000000000..1f15fd4e7b --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-multi/bar.wit.print @@ -0,0 +1,3 @@ +interface bar { +} + diff --git a/crates/wit-component/tests/interfaces/simple-multi/foo.wit b/crates/wit-component/tests/interfaces/simple-multi/foo.wit new file mode 100644 index 0000000000..27947d8a47 --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-multi/foo.wit @@ -0,0 +1 @@ +interface foo {} diff --git a/crates/wit-component/tests/interfaces/simple-multi/foo.wit.print b/crates/wit-component/tests/interfaces/simple-multi/foo.wit.print new file mode 100644 index 0000000000..80bb52742a --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-multi/foo.wit.print @@ -0,0 +1,3 @@ +interface foo { +} + diff --git a/crates/wit-component/tests/interfaces/simple-use.wat b/crates/wit-component/tests/interfaces/simple-use.wat new file mode 100644 index 0000000000..bba8f9df55 --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-use.wat @@ -0,0 +1,24 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (enum "info" "debug")) + (export (;1;) "level" (type (eq 0))) + ) + ) + (export (;0;) "types" (instance (type 0))) + (alias export 0 "level" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "level" (type (eq 0))) + (type (;2;) (func (param "level" 1) (param "msg" string))) + (export (;0;) "log" (func (type 2))) + ) + ) + (export (;1;) "console" (instance (type 2))) + ) + ) + (export (;1;) "simple-use" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/simple-use.wit b/crates/wit-component/tests/interfaces/simple-use.wit new file mode 100644 index 0000000000..c09566dbe0 --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-use.wit @@ -0,0 +1,11 @@ +interface types { + enum level { + info, + debug, + } +} + +interface console { + use self.types.{level} + log: func(level: level, msg: string) +} diff --git a/crates/wit-component/tests/interfaces/simple-use.wit.print b/crates/wit-component/tests/interfaces/simple-use.wit.print new file mode 100644 index 0000000000..a71079d3d4 --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-use.wit.print @@ -0,0 +1,13 @@ +interface types { + enum level { + info, + debug, + } + +} + +interface console { + use self.types.{level} + log: func(level: level, msg: string) +} + diff --git a/crates/wit-component/tests/interfaces/simple-world.wat b/crates/wit-component/tests/interfaces/simple-world.wat new file mode 100644 index 0000000000..ee8ae0b2b7 --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-world.wat @@ -0,0 +1,26 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "arg" string))) + (export (;0;) "log" (func (type 0))) + ) + ) + (export (;0;) "console" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (func (param "arg" string))) + (export (;0;) "log" (func (type 0))) + ) + ) + (import "console" "pkg:/simple-world/console" (instance (type 0))) + ) + ) + (export (;0;) "the-world" (component (type 1))) + ) + ) + (export (;1;) "simple-world" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/simple-world.wit b/crates/wit-component/tests/interfaces/simple-world.wit new file mode 100644 index 0000000000..c5d1ef2bb7 --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-world.wit @@ -0,0 +1,7 @@ +world the-world { + import console: self.console +} + +interface console { + log: func(arg: string) +} diff --git a/crates/wit-component/tests/interfaces/simple-world.wit.print b/crates/wit-component/tests/interfaces/simple-world.wit.print new file mode 100644 index 0000000000..e059d63baf --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-world.wit.print @@ -0,0 +1,7 @@ +interface console { + log: func(arg: string) +} + +world the-world { + import console: self.console +} diff --git a/crates/wit-component/tests/interfaces/type-alias.wat b/crates/wit-component/tests/interfaces/type-alias.wat new file mode 100644 index 0000000000..1ee3b44abf --- /dev/null +++ b/crates/wit-component/tests/interfaces/type-alias.wat @@ -0,0 +1,43 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) u8) + (export (;1;) "a" (type (eq 0))) + (export (;2;) "b" (type (eq 1))) + (type (;3;) (func (param "a" 1) (result 2))) + (export (;0;) "f" (func (type 3))) + ) + ) + (export (;0;) "foo" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) u8) + (export (;1;) "a" (type (eq 0))) + (export (;2;) "b" (type (eq 1))) + (type (;3;) (func (param "a" 1) (result 2))) + (export (;0;) "f" (func (type 3))) + ) + ) + (import "foo" "pkg:/type-alias/foo" (instance (type 0))) + (alias export 0 "a" (type (;1;))) + (alias export 0 "b" (type (;2;))) + (type (;3;) + (instance + (alias outer 1 1 (type (;0;))) + (alias outer 1 2 (type (;1;))) + (type (;2;) (func (param "a" 0) (result 1))) + (export (;0;) "f" (func (type 2))) + ) + ) + (export (;0;) "foo" "pkg:/type-alias/foo" (instance (type 3))) + ) + ) + (export (;0;) "my-world" (component (type 1))) + ) + ) + (export (;1;) "type-alias" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/type-alias.wit b/crates/wit-component/tests/interfaces/type-alias.wit new file mode 100644 index 0000000000..f8dc235017 --- /dev/null +++ b/crates/wit-component/tests/interfaces/type-alias.wit @@ -0,0 +1,11 @@ +interface foo { + type a = u8 + type b = a + + f: func(a: a) -> b +} + +world my-world { + import foo: self.foo + export foo: self.foo +} diff --git a/crates/wit-component/tests/interfaces/type-alias.wit.print b/crates/wit-component/tests/interfaces/type-alias.wit.print new file mode 100644 index 0000000000..64049b2474 --- /dev/null +++ b/crates/wit-component/tests/interfaces/type-alias.wit.print @@ -0,0 +1,12 @@ +interface foo { + type a = u8 + + type b = a + + f: func(a: a) -> b +} + +world my-world { + import foo: self.foo + export foo: self.foo +} diff --git a/crates/wit-component/tests/interfaces/type-alias/component.wat b/crates/wit-component/tests/interfaces/type-alias/component.wat deleted file mode 100644 index 1a93d48b88..0000000000 --- a/crates/wit-component/tests/interfaces/type-alias/component.wat +++ /dev/null @@ -1,50 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) u8) - (export (;1;) "a" (type (eq 0))) - (alias outer 0 0 (type (;2;))) - (export (;3;) "b" (type (eq 2))) - (type (;4;) (func (param "a" 0) (result 2))) - (export (;0;) "f" (func (type 4))) - ) - ) - (import "foo" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32) (result i32))) - (type (;1;) (func (param i32 i32 i32 i32) (result i32))) - (import "foo" "f" (func (;0;) (type 0))) - (func (;1;) (type 0) (param i32) (result i32) - unreachable - ) - (func (;2;) (type 1) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "foo#f" (func 1)) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 2)) - ) - (alias export 0 "f" (func (;0;))) - (core func (;0;) (canon lower (func 0))) - (core instance (;0;) - (export "f" (func 0)) - ) - (core instance (;1;) (instantiate 0 - (with "foo" (instance 0)) - ) - ) - (alias core export 1 "memory" (core memory (;0;))) - (alias core export 1 "cabi_realloc" (core func (;1;))) - (alias core export 1 "foo#f" (core func (;2;))) - (type (;1;) u8) - (alias outer 0 1 (type (;2;))) - (type (;3;) (func (param "a" 1) (result 2))) - (func (;1;) (type 3) (canon lift (core func 2))) - (instance (;1;) - (export "f" (func 1)) - (export "a" (type 1)) - (export "b" (type 2)) - ) - (export (;2;) "foo" (instance 1)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/type-alias/types_only.wat b/crates/wit-component/tests/interfaces/type-alias/types_only.wat deleted file mode 100644 index c7eb03fdba..0000000000 --- a/crates/wit-component/tests/interfaces/type-alias/types_only.wat +++ /dev/null @@ -1,24 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) u8) - (export (;1;) "a" (type (eq 0))) - (alias outer 0 0 (type (;2;))) - (export (;3;) "b" (type (eq 2))) - (type (;4;) (func (param "a" 0) (result 2))) - (export (;0;) "f" (func (type 4))) - ) - ) - (import "foo" (instance (;0;) (type 0))) - (type (;1;) - (instance - (type (;0;) u8) - (export (;1;) "a" (type (eq 0))) - (alias outer 0 0 (type (;2;))) - (export (;3;) "b" (type (eq 2))) - (type (;4;) (func (param "a" 0) (result 2))) - (export (;0;) "f" (func (type 4))) - ) - ) - (export (;2;) "foo" (type 1)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/type-alias/world.wit b/crates/wit-component/tests/interfaces/type-alias/world.wit deleted file mode 100644 index 0a727a2b35..0000000000 --- a/crates/wit-component/tests/interfaces/type-alias/world.wit +++ /dev/null @@ -1,20 +0,0 @@ -interface foo { - type a = u8 - - type b = a - - f: func(a: a) -> b -} - -interface foo0 { - type a = u8 - - type b = a - - f: func(a: a) -> b -} - -world my-world { - import foo: foo - export foo: foo0 -} diff --git a/crates/wit-component/tests/interfaces/type-alias2.wat b/crates/wit-component/tests/interfaces/type-alias2.wat new file mode 100644 index 0000000000..29dd18cac8 --- /dev/null +++ b/crates/wit-component/tests/interfaces/type-alias2.wat @@ -0,0 +1,32 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (flags "b")) + (export (;1;) "a" (type (eq 0))) + (export (;2;) "b" (type (eq 1))) + (type (;3;) (func (result 2))) + (export (;0;) "f" (func (type 3))) + ) + ) + (export (;0;) "foo" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (flags "b")) + (export (;1;) "a" (type (eq 0))) + (export (;2;) "b" (type (eq 1))) + (type (;3;) (func (result 2))) + (export (;0;) "f" (func (type 3))) + ) + ) + (export (;0;) "foo" "pkg:/type-alias2/foo" (instance (type 0))) + ) + ) + (export (;0;) "my-world" (component (type 1))) + ) + ) + (export (;1;) "type-alias2" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/type-alias2/world.wit b/crates/wit-component/tests/interfaces/type-alias2.wit similarity index 80% rename from crates/wit-component/tests/interfaces/type-alias2/world.wit rename to crates/wit-component/tests/interfaces/type-alias2.wit index 1f18d6b5f6..0af05e5896 100644 --- a/crates/wit-component/tests/interfaces/type-alias2/world.wit +++ b/crates/wit-component/tests/interfaces/type-alias2.wit @@ -9,5 +9,5 @@ interface foo { } world my-world { - export foo: foo + export foo: self.foo } diff --git a/crates/wit-component/tests/interfaces/type-alias2.wit.print b/crates/wit-component/tests/interfaces/type-alias2.wit.print new file mode 100644 index 0000000000..0af05e5896 --- /dev/null +++ b/crates/wit-component/tests/interfaces/type-alias2.wit.print @@ -0,0 +1,13 @@ +interface foo { + flags a { + b, + } + + type b = a + + f: func() -> b +} + +world my-world { + export foo: self.foo +} diff --git a/crates/wit-component/tests/interfaces/type-alias2/component.wat b/crates/wit-component/tests/interfaces/type-alias2/component.wat deleted file mode 100644 index 35b47d6f4e..0000000000 --- a/crates/wit-component/tests/interfaces/type-alias2/component.wat +++ /dev/null @@ -1,30 +0,0 @@ -(component - (core module (;0;) - (type (;0;) (func (result i32))) - (type (;1;) (func (param i32 i32 i32 i32) (result i32))) - (func (;0;) (type 0) (result i32) - unreachable - ) - (func (;1;) (type 1) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "foo#f" (func 0)) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 1)) - ) - (core instance (;0;) (instantiate 0)) - (alias core export 0 "memory" (core memory (;0;))) - (alias core export 0 "cabi_realloc" (core func (;0;))) - (alias core export 0 "foo#f" (core func (;1;))) - (type (;0;) (flags "b")) - (alias outer 0 0 (type (;1;))) - (type (;2;) (func (result 1))) - (func (;0;) (type 2) (canon lift (core func 1))) - (instance (;0;) - (export "f" (func 0)) - (export "a" (type 0)) - (export "b" (type 1)) - ) - (export (;1;) "foo" (instance 0)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/type-alias2/types_only.wat b/crates/wit-component/tests/interfaces/type-alias2/types_only.wat deleted file mode 100644 index 6962ffedcc..0000000000 --- a/crates/wit-component/tests/interfaces/type-alias2/types_only.wat +++ /dev/null @@ -1,13 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (flags "b")) - (export (;1;) "a" (type (eq 0))) - (alias outer 0 0 (type (;2;))) - (export (;3;) "b" (type (eq 2))) - (type (;4;) (func (result 2))) - (export (;0;) "f" (func (type 4))) - ) - ) - (export (;1;) "foo" (type 0)) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/variants.wat b/crates/wit-component/tests/interfaces/variants.wat new file mode 100644 index 0000000000..49f343b2a7 --- /dev/null +++ b/crates/wit-component/tests/interfaces/variants.wat @@ -0,0 +1,202 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (enum "a")) + (export (;1;) "e1" (type (eq 0))) + (type (;2;) (union u32 float32)) + (export (;3;) "u1" (type (eq 2))) + (type (;4;) (record)) + (export (;5;) "empty" (type (eq 4))) + (type (;6;) (variant (case "a") (case "b" 3) (case "c" 1) (case "d" string) (case "e" 5) (case "f") (case "g" u32))) + (export (;7;) "v1" (type (eq 6))) + (type (;8;) (variant (case "a" s32) (case "b" float32))) + (export (;9;) "casts1" (type (eq 8))) + (type (;10;) (variant (case "a" float64) (case "b" float32))) + (export (;11;) "casts2" (type (eq 10))) + (type (;12;) (variant (case "a" float64) (case "b" u64))) + (export (;13;) "casts3" (type (eq 12))) + (type (;14;) (variant (case "a" u32) (case "b" s64))) + (export (;15;) "casts4" (type (eq 14))) + (type (;16;) (variant (case "a" float32) (case "b" s64))) + (export (;17;) "casts5" (type (eq 16))) + (type (;18;) (tuple float32 u32)) + (type (;19;) (tuple u32 u32)) + (type (;20;) (variant (case "a" 18) (case "b" 19))) + (export (;21;) "casts6" (type (eq 20))) + (type (;22;) (enum "bad1" "bad2")) + (export (;23;) "my-errno" (type (eq 22))) + (type (;24;) (func (param "x" 1))) + (export (;0;) "e1-arg" (func (type 24))) + (type (;25;) (func (result 1))) + (export (;1;) "e1-result" (func (type 25))) + (type (;26;) (func (param "x" 3))) + (export (;2;) "u1-arg" (func (type 26))) + (type (;27;) (func (result 3))) + (export (;3;) "u1-result" (func (type 27))) + (type (;28;) (func (param "x" 7))) + (export (;4;) "v1-arg" (func (type 28))) + (type (;29;) (func (result 7))) + (export (;5;) "v1-result" (func (type 29))) + (type (;30;) (func (param "x" bool))) + (export (;6;) "bool-arg" (func (type 30))) + (type (;31;) (func (result bool))) + (export (;7;) "bool-result" (func (type 31))) + (type (;32;) (option bool)) + (type (;33;) (tuple)) + (type (;34;) (option 33)) + (type (;35;) (option u32)) + (type (;36;) (option 1)) + (type (;37;) (option float32)) + (type (;38;) (option 3)) + (type (;39;) (option 32)) + (type (;40;) (func (param "a" 32) (param "b" 34) (param "c" 35) (param "d" 36) (param "e" 37) (param "f" 38) (param "g" 39))) + (export (;8;) "option-arg" (func (type 40))) + (type (;41;) (tuple 32 34 35 36 37 38 39)) + (type (;42;) (func (result 41))) + (export (;9;) "option-result" (func (type 42))) + (type (;43;) (tuple 9 11 13 15 17 21)) + (type (;44;) (func (param "a" 9) (param "b" 11) (param "c" 13) (param "d" 15) (param "e" 17) (param "f" 21) (result 43))) + (export (;10;) "casts" (func (type 44))) + (type (;45;) (result)) + (type (;46;) (result (error 1))) + (type (;47;) (result 1)) + (type (;48;) (result 33 (error 33))) + (type (;49;) (result u32 (error 7))) + (type (;50;) (list u8)) + (type (;51;) (result string (error 50))) + (type (;52;) (func (param "a" 45) (param "b" 46) (param "c" 47) (param "d" 48) (param "e" 49) (param "f" 51))) + (export (;11;) "expected-arg" (func (type 52))) + (type (;53;) (tuple 45 46 47 48 49 51)) + (type (;54;) (func (result 53))) + (export (;12;) "expected-result" (func (type 54))) + (type (;55;) (result s32 (error 23))) + (type (;56;) (func (result 55))) + (export (;13;) "return-expected-sugar" (func (type 56))) + (type (;57;) (result (error 23))) + (type (;58;) (func (result 57))) + (export (;14;) "return-expected-sugar2" (func (type 58))) + (type (;59;) (result 23 (error 23))) + (type (;60;) (func (result 59))) + (export (;15;) "return-expected-sugar3" (func (type 60))) + (type (;61;) (tuple s32 u32)) + (type (;62;) (result 61 (error 23))) + (type (;63;) (func (result 62))) + (export (;16;) "return-expected-sugar4" (func (type 63))) + (type (;64;) (option s32)) + (type (;65;) (func (result 64))) + (export (;17;) "return-option-sugar" (func (type 65))) + (type (;66;) (option 23)) + (type (;67;) (func (result 66))) + (export (;18;) "return-option-sugar2" (func (type 67))) + (type (;68;) (result u32 (error s32))) + (type (;69;) (func (result 68))) + (export (;19;) "expected-simple" (func (type 69))) + ) + ) + (export (;0;) "variants" (instance (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (type (;0;) (enum "a")) + (export (;1;) "e1" (type (eq 0))) + (type (;2;) (union u32 float32)) + (export (;3;) "u1" (type (eq 2))) + (type (;4;) (record)) + (export (;5;) "empty" (type (eq 4))) + (type (;6;) (variant (case "a") (case "b" 3) (case "c" 1) (case "d" string) (case "e" 5) (case "f") (case "g" u32))) + (export (;7;) "v1" (type (eq 6))) + (type (;8;) (variant (case "a" s32) (case "b" float32))) + (export (;9;) "casts1" (type (eq 8))) + (type (;10;) (variant (case "a" float64) (case "b" float32))) + (export (;11;) "casts2" (type (eq 10))) + (type (;12;) (variant (case "a" float64) (case "b" u64))) + (export (;13;) "casts3" (type (eq 12))) + (type (;14;) (variant (case "a" u32) (case "b" s64))) + (export (;15;) "casts4" (type (eq 14))) + (type (;16;) (variant (case "a" float32) (case "b" s64))) + (export (;17;) "casts5" (type (eq 16))) + (type (;18;) (tuple float32 u32)) + (type (;19;) (tuple u32 u32)) + (type (;20;) (variant (case "a" 18) (case "b" 19))) + (export (;21;) "casts6" (type (eq 20))) + (type (;22;) (enum "bad1" "bad2")) + (export (;23;) "my-errno" (type (eq 22))) + (type (;24;) (func (param "x" 1))) + (export (;0;) "e1-arg" (func (type 24))) + (type (;25;) (func (result 1))) + (export (;1;) "e1-result" (func (type 25))) + (type (;26;) (func (param "x" 3))) + (export (;2;) "u1-arg" (func (type 26))) + (type (;27;) (func (result 3))) + (export (;3;) "u1-result" (func (type 27))) + (type (;28;) (func (param "x" 7))) + (export (;4;) "v1-arg" (func (type 28))) + (type (;29;) (func (result 7))) + (export (;5;) "v1-result" (func (type 29))) + (type (;30;) (func (param "x" bool))) + (export (;6;) "bool-arg" (func (type 30))) + (type (;31;) (func (result bool))) + (export (;7;) "bool-result" (func (type 31))) + (type (;32;) (option bool)) + (type (;33;) (tuple)) + (type (;34;) (option 33)) + (type (;35;) (option u32)) + (type (;36;) (option 1)) + (type (;37;) (option float32)) + (type (;38;) (option 3)) + (type (;39;) (option 32)) + (type (;40;) (func (param "a" 32) (param "b" 34) (param "c" 35) (param "d" 36) (param "e" 37) (param "f" 38) (param "g" 39))) + (export (;8;) "option-arg" (func (type 40))) + (type (;41;) (tuple 32 34 35 36 37 38 39)) + (type (;42;) (func (result 41))) + (export (;9;) "option-result" (func (type 42))) + (type (;43;) (tuple 9 11 13 15 17 21)) + (type (;44;) (func (param "a" 9) (param "b" 11) (param "c" 13) (param "d" 15) (param "e" 17) (param "f" 21) (result 43))) + (export (;10;) "casts" (func (type 44))) + (type (;45;) (result)) + (type (;46;) (result (error 1))) + (type (;47;) (result 1)) + (type (;48;) (result 33 (error 33))) + (type (;49;) (result u32 (error 7))) + (type (;50;) (list u8)) + (type (;51;) (result string (error 50))) + (type (;52;) (func (param "a" 45) (param "b" 46) (param "c" 47) (param "d" 48) (param "e" 49) (param "f" 51))) + (export (;11;) "expected-arg" (func (type 52))) + (type (;53;) (tuple 45 46 47 48 49 51)) + (type (;54;) (func (result 53))) + (export (;12;) "expected-result" (func (type 54))) + (type (;55;) (result s32 (error 23))) + (type (;56;) (func (result 55))) + (export (;13;) "return-expected-sugar" (func (type 56))) + (type (;57;) (result (error 23))) + (type (;58;) (func (result 57))) + (export (;14;) "return-expected-sugar2" (func (type 58))) + (type (;59;) (result 23 (error 23))) + (type (;60;) (func (result 59))) + (export (;15;) "return-expected-sugar3" (func (type 60))) + (type (;61;) (tuple s32 u32)) + (type (;62;) (result 61 (error 23))) + (type (;63;) (func (result 62))) + (export (;16;) "return-expected-sugar4" (func (type 63))) + (type (;64;) (option s32)) + (type (;65;) (func (result 64))) + (export (;17;) "return-option-sugar" (func (type 65))) + (type (;66;) (option 23)) + (type (;67;) (func (result 66))) + (export (;18;) "return-option-sugar2" (func (type 67))) + (type (;68;) (result u32 (error s32))) + (type (;69;) (func (result 68))) + (export (;19;) "expected-simple" (func (type 69))) + ) + ) + (import "variants" "pkg:/variants/variants" (instance (type 0))) + ) + ) + (export (;0;) "variants-world" (component (type 1))) + ) + ) + (export (;1;) "variants" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/variants.wit b/crates/wit-component/tests/interfaces/variants.wit new file mode 100644 index 0000000000..bb642626f6 --- /dev/null +++ b/crates/wit-component/tests/interfaces/variants.wit @@ -0,0 +1,83 @@ +interface variants { + enum e1 { + a, + } + + union u1 { + u32, + float32, + } + + record empty { + } + + variant v1 { + a, + b(u1), + c(e1), + d(string), + e(empty), + f, + g(u32), + } + + variant casts1 { + a(s32), + b(float32), + } + + variant casts2 { + a(float64), + b(float32), + } + + variant casts3 { + a(float64), + b(u64), + } + + variant casts4 { + a(u32), + b(s64), + } + + variant casts5 { + a(float32), + b(s64), + } + + variant casts6 { + a(tuple), + b(tuple), + } + + enum my-errno { + bad1, + bad2, + } + + e1-arg: func(x: e1) + e1-result: func() -> e1 + u1-arg: func(x: u1) + u1-result: func() -> u1 + v1-arg: func(x: v1) + v1-result: func() -> v1 + bool-arg: func(x: bool) + bool-result: func() -> bool + option-arg: func(a: option, b: option>, c: option, d: option, e: option, f: option, g: option>) + option-result: func() -> tuple, option>, option, option, option, option, option>> + casts: func(a: casts1, b: casts2, c: casts3, d: casts4, e: casts5, f: casts6) -> tuple + expected-arg: func(a: result, b: result<_, e1>, c: result, d: result, tuple<>>, e: result, f: result>) + expected-result: func() -> tuple, result, result, tuple<>>, result, result>> + return-expected-sugar: func() -> result + return-expected-sugar2: func() -> result<_, my-errno> + return-expected-sugar3: func() -> result + return-expected-sugar4: func() -> result, my-errno> + return-option-sugar: func() -> option + return-option-sugar2: func() -> option + expected-simple: func() -> result +} + +world variants-world { + import variants: self.variants +} diff --git a/crates/wit-component/tests/interfaces/variants/world.wit b/crates/wit-component/tests/interfaces/variants.wit.print similarity index 98% rename from crates/wit-component/tests/interfaces/variants/world.wit rename to crates/wit-component/tests/interfaces/variants.wit.print index f32fa7f260..d0cc686215 100644 --- a/crates/wit-component/tests/interfaces/variants/world.wit +++ b/crates/wit-component/tests/interfaces/variants.wit.print @@ -98,5 +98,5 @@ interface variants { } world variants-world { - import variants: variants + import variants: self.variants } diff --git a/crates/wit-component/tests/interfaces/variants/component.wat b/crates/wit-component/tests/interfaces/variants/component.wat deleted file mode 100644 index 90530ef7c1..0000000000 --- a/crates/wit-component/tests/interfaces/variants/component.wat +++ /dev/null @@ -1,379 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (enum "a")) - (export (;1;) "e1" (type (eq 0))) - (type (;2;) (func (param "x" 0))) - (export (;0;) "e1-arg" (func (type 2))) - (type (;3;) (func (result 0))) - (export (;1;) "e1-result" (func (type 3))) - (type (;4;) (union u32 float32)) - (export (;5;) "u1" (type (eq 4))) - (type (;6;) (func (param "x" 4))) - (export (;2;) "u1-arg" (func (type 6))) - (type (;7;) (func (result 4))) - (export (;3;) "u1-result" (func (type 7))) - (type (;8;) (record)) - (export (;9;) "empty" (type (eq 8))) - (type (;10;) (variant (case "a") (case "b" 4) (case "c" 0) (case "d" string) (case "e" 8) (case "f") (case "g" u32))) - (export (;11;) "v1" (type (eq 10))) - (type (;12;) (func (param "x" 10))) - (export (;4;) "v1-arg" (func (type 12))) - (type (;13;) (func (result 10))) - (export (;5;) "v1-result" (func (type 13))) - (type (;14;) (func (param "x" bool))) - (export (;6;) "bool-arg" (func (type 14))) - (type (;15;) (func (result bool))) - (export (;7;) "bool-result" (func (type 15))) - (type (;16;) (option bool)) - (type (;17;) (tuple)) - (type (;18;) (option 17)) - (type (;19;) (option u32)) - (type (;20;) (option 0)) - (type (;21;) (option float32)) - (type (;22;) (option 4)) - (type (;23;) (option 16)) - (type (;24;) (func (param "a" 16) (param "b" 18) (param "c" 19) (param "d" 20) (param "e" 21) (param "f" 22) (param "g" 23))) - (export (;8;) "option-arg" (func (type 24))) - (type (;25;) (tuple 16 18 19 20 21 22 23)) - (type (;26;) (func (result 25))) - (export (;9;) "option-result" (func (type 26))) - (type (;27;) (variant (case "a" s32) (case "b" float32))) - (export (;28;) "casts1" (type (eq 27))) - (type (;29;) (variant (case "a" float64) (case "b" float32))) - (export (;30;) "casts2" (type (eq 29))) - (type (;31;) (variant (case "a" float64) (case "b" u64))) - (export (;32;) "casts3" (type (eq 31))) - (type (;33;) (variant (case "a" u32) (case "b" s64))) - (export (;34;) "casts4" (type (eq 33))) - (type (;35;) (variant (case "a" float32) (case "b" s64))) - (export (;36;) "casts5" (type (eq 35))) - (type (;37;) (tuple float32 u32)) - (type (;38;) (tuple u32 u32)) - (type (;39;) (variant (case "a" 37) (case "b" 38))) - (export (;40;) "casts6" (type (eq 39))) - (type (;41;) (tuple 27 29 31 33 35 39)) - (type (;42;) (func (param "a" 27) (param "b" 29) (param "c" 31) (param "d" 33) (param "e" 35) (param "f" 39) (result 41))) - (export (;10;) "casts" (func (type 42))) - (type (;43;) (result)) - (type (;44;) (result (error 0))) - (type (;45;) (result 0)) - (type (;46;) (result 17 (error 17))) - (type (;47;) (result u32 (error 10))) - (type (;48;) (list u8)) - (type (;49;) (result string (error 48))) - (type (;50;) (func (param "a" 43) (param "b" 44) (param "c" 45) (param "d" 46) (param "e" 47) (param "f" 49))) - (export (;11;) "expected-arg" (func (type 50))) - (type (;51;) (tuple 43 44 45 46 47 49)) - (type (;52;) (func (result 51))) - (export (;12;) "expected-result" (func (type 52))) - (type (;53;) (enum "bad1" "bad2")) - (export (;54;) "my-errno" (type (eq 53))) - (type (;55;) (result s32 (error 53))) - (type (;56;) (func (result 55))) - (export (;13;) "return-expected-sugar" (func (type 56))) - (type (;57;) (result (error 53))) - (type (;58;) (func (result 57))) - (export (;14;) "return-expected-sugar2" (func (type 58))) - (type (;59;) (result 53 (error 53))) - (type (;60;) (func (result 59))) - (export (;15;) "return-expected-sugar3" (func (type 60))) - (type (;61;) (tuple s32 u32)) - (type (;62;) (result 61 (error 53))) - (type (;63;) (func (result 62))) - (export (;16;) "return-expected-sugar4" (func (type 63))) - (type (;64;) (option s32)) - (type (;65;) (func (result 64))) - (export (;17;) "return-option-sugar" (func (type 65))) - (type (;66;) (option 53)) - (type (;67;) (func (result 66))) - (export (;18;) "return-option-sugar2" (func (type 67))) - (type (;68;) (result u32 (error s32))) - (type (;69;) (func (result 68))) - (export (;19;) "expected-simple" (func (type 69))) - ) - ) - (import "variants" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32))) - (type (;1;) (func (result i32))) - (type (;2;) (func (param i32 i32))) - (type (;3;) (func (param i32 i32 i32))) - (type (;4;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 f32 i32 i32 i32 i32 i32 i32))) - (type (;5;) (func (param i32 i32 i32 i64 i32 i64 i32 i64 i32 i64 i32 i32 i32 i32))) - (type (;6;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32))) - (type (;7;) (func (param i32 i32 i32 i32) (result i32))) - (import "variants" "e1-arg" (func (;0;) (type 0))) - (import "variants" "e1-result" (func (;1;) (type 1))) - (import "variants" "u1-arg" (func (;2;) (type 2))) - (import "variants" "u1-result" (func (;3;) (type 0))) - (import "variants" "v1-arg" (func (;4;) (type 3))) - (import "variants" "v1-result" (func (;5;) (type 0))) - (import "variants" "bool-arg" (func (;6;) (type 0))) - (import "variants" "bool-result" (func (;7;) (type 1))) - (import "variants" "option-arg" (func (;8;) (type 4))) - (import "variants" "option-result" (func (;9;) (type 0))) - (import "variants" "casts" (func (;10;) (type 5))) - (import "variants" "expected-arg" (func (;11;) (type 6))) - (import "variants" "expected-result" (func (;12;) (type 0))) - (import "variants" "return-expected-sugar" (func (;13;) (type 0))) - (import "variants" "return-expected-sugar2" (func (;14;) (type 0))) - (import "variants" "return-expected-sugar3" (func (;15;) (type 0))) - (import "variants" "return-expected-sugar4" (func (;16;) (type 0))) - (import "variants" "return-option-sugar" (func (;17;) (type 0))) - (import "variants" "return-option-sugar2" (func (;18;) (type 0))) - (import "variants" "expected-simple" (func (;19;) (type 0))) - (func (;20;) (type 7) (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "cabi_realloc" (func 20)) - ) - (core module (;1;) - (type (;0;) (func (param i32))) - (type (;1;) (func (param i32 i32 i32))) - (type (;2;) (func (param i32 i32 i32 i64 i32 i64 i32 i64 i32 i64 i32 i32 i32 i32))) - (type (;3;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32))) - (func $indirect-variants-u1-result (;0;) (type 0) (param i32) - local.get 0 - i32.const 0 - call_indirect (type 0) - ) - (func $indirect-variants-v1-arg (;1;) (type 1) (param i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - i32.const 1 - call_indirect (type 1) - ) - (func $indirect-variants-v1-result (;2;) (type 0) (param i32) - local.get 0 - i32.const 2 - call_indirect (type 0) - ) - (func $indirect-variants-option-result (;3;) (type 0) (param i32) - local.get 0 - i32.const 3 - call_indirect (type 0) - ) - (func $indirect-variants-casts (;4;) (type 2) (param i32 i32 i32 i64 i32 i64 i32 i64 i32 i64 i32 i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - local.get 4 - local.get 5 - local.get 6 - local.get 7 - local.get 8 - local.get 9 - local.get 10 - local.get 11 - local.get 12 - local.get 13 - i32.const 4 - call_indirect (type 2) - ) - (func $indirect-variants-expected-arg (;5;) (type 3) (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - local.get 4 - local.get 5 - local.get 6 - local.get 7 - local.get 8 - local.get 9 - local.get 10 - local.get 11 - local.get 12 - i32.const 5 - call_indirect (type 3) - ) - (func $indirect-variants-expected-result (;6;) (type 0) (param i32) - local.get 0 - i32.const 6 - call_indirect (type 0) - ) - (func $indirect-variants-return-expected-sugar (;7;) (type 0) (param i32) - local.get 0 - i32.const 7 - call_indirect (type 0) - ) - (func $indirect-variants-return-expected-sugar2 (;8;) (type 0) (param i32) - local.get 0 - i32.const 8 - call_indirect (type 0) - ) - (func $indirect-variants-return-expected-sugar3 (;9;) (type 0) (param i32) - local.get 0 - i32.const 9 - call_indirect (type 0) - ) - (func $indirect-variants-return-expected-sugar4 (;10;) (type 0) (param i32) - local.get 0 - i32.const 10 - call_indirect (type 0) - ) - (func $indirect-variants-return-option-sugar (;11;) (type 0) (param i32) - local.get 0 - i32.const 11 - call_indirect (type 0) - ) - (func $indirect-variants-return-option-sugar2 (;12;) (type 0) (param i32) - local.get 0 - i32.const 12 - call_indirect (type 0) - ) - (func $indirect-variants-expected-simple (;13;) (type 0) (param i32) - local.get 0 - i32.const 13 - call_indirect (type 0) - ) - (table (;0;) 14 14 funcref) - (export "0" (func $indirect-variants-u1-result)) - (export "1" (func $indirect-variants-v1-arg)) - (export "2" (func $indirect-variants-v1-result)) - (export "3" (func $indirect-variants-option-result)) - (export "4" (func $indirect-variants-casts)) - (export "5" (func $indirect-variants-expected-arg)) - (export "6" (func $indirect-variants-expected-result)) - (export "7" (func $indirect-variants-return-expected-sugar)) - (export "8" (func $indirect-variants-return-expected-sugar2)) - (export "9" (func $indirect-variants-return-expected-sugar3)) - (export "10" (func $indirect-variants-return-expected-sugar4)) - (export "11" (func $indirect-variants-return-option-sugar)) - (export "12" (func $indirect-variants-return-option-sugar2)) - (export "13" (func $indirect-variants-expected-simple)) - (export "$imports" (table 0)) - ) - (core module (;2;) - (type (;0;) (func (param i32))) - (type (;1;) (func (param i32 i32 i32))) - (type (;2;) (func (param i32 i32 i32 i64 i32 i64 i32 i64 i32 i64 i32 i32 i32 i32))) - (type (;3;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32))) - (import "" "0" (func (;0;) (type 0))) - (import "" "1" (func (;1;) (type 1))) - (import "" "2" (func (;2;) (type 0))) - (import "" "3" (func (;3;) (type 0))) - (import "" "4" (func (;4;) (type 2))) - (import "" "5" (func (;5;) (type 3))) - (import "" "6" (func (;6;) (type 0))) - (import "" "7" (func (;7;) (type 0))) - (import "" "8" (func (;8;) (type 0))) - (import "" "9" (func (;9;) (type 0))) - (import "" "10" (func (;10;) (type 0))) - (import "" "11" (func (;11;) (type 0))) - (import "" "12" (func (;12;) (type 0))) - (import "" "13" (func (;13;) (type 0))) - (import "" "$imports" (table (;0;) 14 14 funcref)) - (elem (;0;) (i32.const 0) func 0 1 2 3 4 5 6 7 8 9 10 11 12 13) - ) - (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias core export 0 "1" (core func (;1;))) - (alias core export 0 "2" (core func (;2;))) - (alias core export 0 "3" (core func (;3;))) - (alias core export 0 "4" (core func (;4;))) - (alias core export 0 "5" (core func (;5;))) - (alias core export 0 "6" (core func (;6;))) - (alias core export 0 "7" (core func (;7;))) - (alias core export 0 "8" (core func (;8;))) - (alias core export 0 "9" (core func (;9;))) - (alias core export 0 "10" (core func (;10;))) - (alias core export 0 "11" (core func (;11;))) - (alias core export 0 "12" (core func (;12;))) - (alias core export 0 "13" (core func (;13;))) - (alias export 0 "e1-arg" (func (;0;))) - (core func (;14;) (canon lower (func 0))) - (alias export 0 "e1-result" (func (;1;))) - (core func (;15;) (canon lower (func 1))) - (alias export 0 "u1-arg" (func (;2;))) - (core func (;16;) (canon lower (func 2))) - (alias export 0 "bool-arg" (func (;3;))) - (core func (;17;) (canon lower (func 3))) - (alias export 0 "bool-result" (func (;4;))) - (core func (;18;) (canon lower (func 4))) - (alias export 0 "option-arg" (func (;5;))) - (core func (;19;) (canon lower (func 5))) - (core instance (;1;) - (export "u1-result" (func 0)) - (export "v1-arg" (func 1)) - (export "v1-result" (func 2)) - (export "option-result" (func 3)) - (export "casts" (func 4)) - (export "expected-arg" (func 5)) - (export "expected-result" (func 6)) - (export "return-expected-sugar" (func 7)) - (export "return-expected-sugar2" (func 8)) - (export "return-expected-sugar3" (func 9)) - (export "return-expected-sugar4" (func 10)) - (export "return-option-sugar" (func 11)) - (export "return-option-sugar2" (func 12)) - (export "expected-simple" (func 13)) - (export "e1-arg" (func 14)) - (export "e1-result" (func 15)) - (export "u1-arg" (func 16)) - (export "bool-arg" (func 17)) - (export "bool-result" (func 18)) - (export "option-arg" (func 19)) - ) - (core instance (;2;) (instantiate 0 - (with "variants" (instance 1)) - ) - ) - (alias core export 2 "memory" (core memory (;0;))) - (alias core export 2 "cabi_realloc" (core func (;20;))) - (alias core export 0 "$imports" (core table (;0;))) - (alias export 0 "u1-result" (func (;6;))) - (core func (;21;) (canon lower (func 6) (memory 0))) - (alias export 0 "v1-arg" (func (;7;))) - (core func (;22;) (canon lower (func 7) (memory 0) string-encoding=utf8)) - (alias export 0 "v1-result" (func (;8;))) - (core func (;23;) (canon lower (func 8) (memory 0) (realloc 20) string-encoding=utf8)) - (alias export 0 "option-result" (func (;9;))) - (core func (;24;) (canon lower (func 9) (memory 0))) - (alias export 0 "casts" (func (;10;))) - (core func (;25;) (canon lower (func 10) (memory 0))) - (alias export 0 "expected-arg" (func (;11;))) - (core func (;26;) (canon lower (func 11) (memory 0) string-encoding=utf8)) - (alias export 0 "expected-result" (func (;12;))) - (core func (;27;) (canon lower (func 12) (memory 0) (realloc 20) string-encoding=utf8)) - (alias export 0 "return-expected-sugar" (func (;13;))) - (core func (;28;) (canon lower (func 13) (memory 0))) - (alias export 0 "return-expected-sugar2" (func (;14;))) - (core func (;29;) (canon lower (func 14) (memory 0))) - (alias export 0 "return-expected-sugar3" (func (;15;))) - (core func (;30;) (canon lower (func 15) (memory 0))) - (alias export 0 "return-expected-sugar4" (func (;16;))) - (core func (;31;) (canon lower (func 16) (memory 0))) - (alias export 0 "return-option-sugar" (func (;17;))) - (core func (;32;) (canon lower (func 17) (memory 0))) - (alias export 0 "return-option-sugar2" (func (;18;))) - (core func (;33;) (canon lower (func 18) (memory 0))) - (alias export 0 "expected-simple" (func (;19;))) - (core func (;34;) (canon lower (func 19) (memory 0))) - (core instance (;3;) - (export "$imports" (table 0)) - (export "0" (func 21)) - (export "1" (func 22)) - (export "2" (func 23)) - (export "3" (func 24)) - (export "4" (func 25)) - (export "5" (func 26)) - (export "6" (func 27)) - (export "7" (func 28)) - (export "8" (func 29)) - (export "9" (func 30)) - (export "10" (func 31)) - (export "11" (func 32)) - (export "12" (func 33)) - (export "13" (func 34)) - ) - (core instance (;4;) (instantiate 2 - (with "" (instance 3)) - ) - ) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/variants/types_only.wat b/crates/wit-component/tests/interfaces/variants/types_only.wat deleted file mode 100644 index 22bcc35c1a..0000000000 --- a/crates/wit-component/tests/interfaces/variants/types_only.wat +++ /dev/null @@ -1,97 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (enum "a")) - (export (;1;) "e1" (type (eq 0))) - (type (;2;) (func (param "x" 0))) - (export (;0;) "e1-arg" (func (type 2))) - (type (;3;) (func (result 0))) - (export (;1;) "e1-result" (func (type 3))) - (type (;4;) (union u32 float32)) - (export (;5;) "u1" (type (eq 4))) - (type (;6;) (func (param "x" 4))) - (export (;2;) "u1-arg" (func (type 6))) - (type (;7;) (func (result 4))) - (export (;3;) "u1-result" (func (type 7))) - (type (;8;) (record)) - (export (;9;) "empty" (type (eq 8))) - (type (;10;) (variant (case "a") (case "b" 4) (case "c" 0) (case "d" string) (case "e" 8) (case "f") (case "g" u32))) - (export (;11;) "v1" (type (eq 10))) - (type (;12;) (func (param "x" 10))) - (export (;4;) "v1-arg" (func (type 12))) - (type (;13;) (func (result 10))) - (export (;5;) "v1-result" (func (type 13))) - (type (;14;) (func (param "x" bool))) - (export (;6;) "bool-arg" (func (type 14))) - (type (;15;) (func (result bool))) - (export (;7;) "bool-result" (func (type 15))) - (type (;16;) (option bool)) - (type (;17;) (tuple)) - (type (;18;) (option 17)) - (type (;19;) (option u32)) - (type (;20;) (option 0)) - (type (;21;) (option float32)) - (type (;22;) (option 4)) - (type (;23;) (option 16)) - (type (;24;) (func (param "a" 16) (param "b" 18) (param "c" 19) (param "d" 20) (param "e" 21) (param "f" 22) (param "g" 23))) - (export (;8;) "option-arg" (func (type 24))) - (type (;25;) (tuple 16 18 19 20 21 22 23)) - (type (;26;) (func (result 25))) - (export (;9;) "option-result" (func (type 26))) - (type (;27;) (variant (case "a" s32) (case "b" float32))) - (export (;28;) "casts1" (type (eq 27))) - (type (;29;) (variant (case "a" float64) (case "b" float32))) - (export (;30;) "casts2" (type (eq 29))) - (type (;31;) (variant (case "a" float64) (case "b" u64))) - (export (;32;) "casts3" (type (eq 31))) - (type (;33;) (variant (case "a" u32) (case "b" s64))) - (export (;34;) "casts4" (type (eq 33))) - (type (;35;) (variant (case "a" float32) (case "b" s64))) - (export (;36;) "casts5" (type (eq 35))) - (type (;37;) (tuple float32 u32)) - (type (;38;) (tuple u32 u32)) - (type (;39;) (variant (case "a" 37) (case "b" 38))) - (export (;40;) "casts6" (type (eq 39))) - (type (;41;) (tuple 27 29 31 33 35 39)) - (type (;42;) (func (param "a" 27) (param "b" 29) (param "c" 31) (param "d" 33) (param "e" 35) (param "f" 39) (result 41))) - (export (;10;) "casts" (func (type 42))) - (type (;43;) (result)) - (type (;44;) (result (error 0))) - (type (;45;) (result 0)) - (type (;46;) (result 17 (error 17))) - (type (;47;) (result u32 (error 10))) - (type (;48;) (list u8)) - (type (;49;) (result string (error 48))) - (type (;50;) (func (param "a" 43) (param "b" 44) (param "c" 45) (param "d" 46) (param "e" 47) (param "f" 49))) - (export (;11;) "expected-arg" (func (type 50))) - (type (;51;) (tuple 43 44 45 46 47 49)) - (type (;52;) (func (result 51))) - (export (;12;) "expected-result" (func (type 52))) - (type (;53;) (enum "bad1" "bad2")) - (export (;54;) "my-errno" (type (eq 53))) - (type (;55;) (result s32 (error 53))) - (type (;56;) (func (result 55))) - (export (;13;) "return-expected-sugar" (func (type 56))) - (type (;57;) (result (error 53))) - (type (;58;) (func (result 57))) - (export (;14;) "return-expected-sugar2" (func (type 58))) - (type (;59;) (result 53 (error 53))) - (type (;60;) (func (result 59))) - (export (;15;) "return-expected-sugar3" (func (type 60))) - (type (;61;) (tuple s32 u32)) - (type (;62;) (result 61 (error 53))) - (type (;63;) (func (result 62))) - (export (;16;) "return-expected-sugar4" (func (type 63))) - (type (;64;) (option s32)) - (type (;65;) (func (result 64))) - (export (;17;) "return-option-sugar" (func (type 65))) - (type (;66;) (option 53)) - (type (;67;) (func (result 66))) - (export (;18;) "return-option-sugar2" (func (type 67))) - (type (;68;) (result u32 (error s32))) - (type (;69;) (func (result 68))) - (export (;19;) "expected-simple" (func (type 69))) - ) - ) - (import "variants" (instance (;0;) (type 0))) -) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/wasi-http.wat b/crates/wit-component/tests/interfaces/wasi-http.wat new file mode 100644 index 0000000000..949a77943b --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http.wat @@ -0,0 +1,95 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (record)) + (export (;1;) "request" (type (eq 0))) + (type (;2;) (record)) + (export (;3;) "response" (type (eq 2))) + ) + ) + (export (;0;) "types" (instance (type 0))) + ) + ) + (export (;1;) "types" (type 0)) + (type (;2;) + (component + (type (;0;) + (instance + (type (;0;) (record)) + (export (;1;) "response" (type (eq 0))) + (type (;2;) (record)) + (export (;3;) "request" (type (eq 2))) + ) + ) + (import "types" "pkg:/types/types" (instance (type 0))) + (alias export 0 "request" (type (;1;))) + (alias export 0 "response" (type (;2;))) + (type (;3;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "request" (type (eq 0))) + (alias outer 1 2 (type (;2;))) + (export (;3;) "response" (type (eq 2))) + (type (;4;) (func (param "request" 1) (result 3))) + (export (;0;) "handle" (func (type 4))) + ) + ) + (export (;0;) "handler" (instance (type 3))) + ) + ) + (export (;3;) "handler" (type 2)) + (type (;4;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (enum "info" "debug")) + (export (;1;) "level" (type (eq 0))) + (type (;2;) (func (param "level" 1) (param "msg" string))) + (export (;0;) "log" (func (type 2))) + ) + ) + (import "console" "path:/wasi-logging/backend/backend" (instance (type 0))) + (type (;1;) + (instance + (type (;0;) (record)) + (export (;1;) "request" (type (eq 0))) + (type (;2;) (record)) + (export (;3;) "response" (type (eq 2))) + ) + ) + (import "types" "pkg:/types/types" (instance (type 1))) + (alias export 1 "request" (type (;2;))) + (alias export 1 "response" (type (;3;))) + (type (;4;) + (instance + (alias outer 1 2 (type (;0;))) + (export (;1;) "request" (type (eq 0))) + (alias outer 1 3 (type (;2;))) + (export (;3;) "response" (type (eq 2))) + (type (;4;) (func (param "request" 1) (result 3))) + (export (;0;) "handle" (func (type 4))) + ) + ) + (import "origin" "pkg:/handler/handler" (instance (type 4))) + (alias export 2 "request" (type (;5;))) + (alias export 2 "response" (type (;6;))) + (type (;7;) + (instance + (alias outer 1 5 (type (;0;))) + (alias outer 1 6 (type (;1;))) + (type (;2;) (func (param "request" 0) (result 1))) + (export (;0;) "handle" (func (type 2))) + ) + ) + (export (;0;) "handler" "pkg:/handler/handler" (instance (type 7))) + ) + ) + (export (;0;) "proxy" (component (type 0))) + ) + ) + (export (;5;) "proxy" (type 4)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/wasi-http/deps/wasi-logging/backend.wit b/crates/wit-component/tests/interfaces/wasi-http/deps/wasi-logging/backend.wit new file mode 100644 index 0000000000..e3293d5115 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/deps/wasi-logging/backend.wit @@ -0,0 +1,8 @@ +default interface backend { + enum level { + info, + debug, + } + + log: func(level: level, msg: string) +} diff --git a/crates/wit-component/tests/interfaces/wasi-http/handler.wit b/crates/wit-component/tests/interfaces/wasi-http/handler.wit new file mode 100644 index 0000000000..873938d7f4 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/handler.wit @@ -0,0 +1,4 @@ +default interface handler { + use pkg.types.{request, response} + handle: func(request: request) -> response +} diff --git a/crates/wit-component/tests/interfaces/wasi-http/handler.wit.print b/crates/wit-component/tests/interfaces/wasi-http/handler.wit.print new file mode 100644 index 0000000000..70836e2955 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/handler.wit.print @@ -0,0 +1,5 @@ +interface handler { + use pkg.types.types.{request, response} + handle: func(request: request) -> response +} + diff --git a/crates/wit-component/tests/interfaces/wasi-http/proxy.wit b/crates/wit-component/tests/interfaces/wasi-http/proxy.wit new file mode 100644 index 0000000000..2651e5fec4 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/proxy.wit @@ -0,0 +1,5 @@ +default world proxy { + import console: wasi-logging.backend + import origin: pkg.handler + export handler: pkg.handler +} diff --git a/crates/wit-component/tests/interfaces/wasi-http/proxy.wit.print b/crates/wit-component/tests/interfaces/wasi-http/proxy.wit.print new file mode 100644 index 0000000000..9d2d081ca0 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/proxy.wit.print @@ -0,0 +1,6 @@ +world proxy { + import console: wasi-logging.backend.backend + import types: pkg.types.types + import origin: pkg.handler.handler + export handler: pkg.handler.handler +} diff --git a/crates/wit-component/tests/interfaces/wasi-http/types.wit b/crates/wit-component/tests/interfaces/wasi-http/types.wit new file mode 100644 index 0000000000..9f55e779c0 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/types.wit @@ -0,0 +1,4 @@ +default interface types { + record request { } + record response { } +} diff --git a/crates/wit-component/tests/interfaces/wasi-http/types.wit.print b/crates/wit-component/tests/interfaces/wasi-http/types.wit.print new file mode 100644 index 0000000000..2419c2f452 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/types.wit.print @@ -0,0 +1,9 @@ +interface types { + record request { + } + + record response { + } + +} + diff --git a/crates/wit-component/tests/interfaces/world-inline-interface.wat b/crates/wit-component/tests/interfaces/world-inline-interface.wat new file mode 100644 index 0000000000..ac40b9499a --- /dev/null +++ b/crates/wit-component/tests/interfaces/world-inline-interface.wat @@ -0,0 +1,20 @@ +(component + (type (;0;) + (component + (type (;0;) + (component + (type (;0;) + (instance) + ) + (import "foo" (instance (type 0))) + (type (;1;) + (instance) + ) + (export (;0;) "bar" (instance (type 1))) + ) + ) + (export (;0;) "has-inline" (component (type 0))) + ) + ) + (export (;1;) "world-inline-interface" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/world-inline-interface.wit b/crates/wit-component/tests/interfaces/world-inline-interface.wit new file mode 100644 index 0000000000..9940392eca --- /dev/null +++ b/crates/wit-component/tests/interfaces/world-inline-interface.wit @@ -0,0 +1,4 @@ +world has-inline { + import foo: interface {} + export bar: interface {} +} diff --git a/crates/wit-component/tests/interfaces/world-inline-interface.wit.print b/crates/wit-component/tests/interfaces/world-inline-interface.wit.print new file mode 100644 index 0000000000..33ec54b02d --- /dev/null +++ b/crates/wit-component/tests/interfaces/world-inline-interface.wit.print @@ -0,0 +1,6 @@ +world has-inline { + import foo: interface { + } + export bar: interface { + } +} diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index c0a5dc6d19..adf0d69d88 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -73,7 +73,7 @@ impl<'a> Resolver<'a> { Ok(()) } - pub(crate) fn resolve(&mut self, name: &str) -> Result { + pub(crate) fn resolve(&mut self, name: &str, url: Option<&str>) -> Result { self.populate_foreign_deps(); // Determine the dependencies between documents in the current package @@ -112,6 +112,7 @@ impl<'a> Resolver<'a> { Ok(UnresolvedPackage { name: name.to_string(), + url: url.map(|s| s.to_string()), worlds: mem::take(&mut self.worlds), types: mem::take(&mut self.types), interfaces: mem::take(&mut self.interfaces), @@ -184,7 +185,7 @@ impl<'a> Resolver<'a> { types: IndexMap::new(), docs: Docs::default(), document: doc, - functions: Vec::new(), + functions: IndexMap::new(), }); DocumentItem::Interface(id) }); @@ -204,7 +205,7 @@ impl<'a> Resolver<'a> { types: IndexMap::new(), docs: Docs::default(), document: doc, - functions: Vec::new(), + functions: IndexMap::new(), }) }), }; @@ -480,7 +481,7 @@ impl<'a> Resolver<'a> { document, name: name.map(|s| s.to_string()), url: None, - functions: Vec::new(), + functions: IndexMap::new(), types: IndexMap::new(), }); assert_eq!(interface_id.index(), self.interface_types.len()); @@ -579,13 +580,17 @@ impl<'a> Resolver<'a> { self.define_interface_name(&value.name, InterfaceItem::Func)?; let params = self.resolve_params(params)?; let results = self.resolve_results(results)?; - self.interfaces[interface_id].functions.push(Function { - docs, - name: value.name.name.to_string(), - kind: FunctionKind::Freestanding, - params, - results, - }); + let prev = self.interfaces[interface_id].functions.insert( + value.name.name.to_string(), + Function { + docs, + name: value.name.name.to_string(), + kind: FunctionKind::Freestanding, + params, + results, + }, + ); + assert!(prev.is_none()); } } } diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index ec48859e08..472c1d97a1 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -12,6 +12,8 @@ mod sizealign; pub use sizealign::*; mod resolve; pub use resolve::{Package, PackageId, Resolve}; +mod live; +pub use live::LiveTypes; /// Checks if the given string is a legal identifier in wit. pub fn validate_id(s: &str) -> Result<()> { @@ -50,6 +52,13 @@ pub struct UnresolvedPackage { /// Local name for this package. pub name: String, + /// Optionally-specified URL for this package. + /// + /// Must be specified for non-local dependencies. Note that this is never + /// automatically set from [`UnresolvedPackage::parse`] methods, and it must + /// be manually configured in the return value. + pub url: Option, + /// All worlds from all documents within this package. /// /// Each world lists the document that it is from. @@ -121,7 +130,7 @@ impl UnresolvedPackage { .and_then(|s| s.to_str()) .ok_or_else(|| anyhow!("path doesn't end in a valid package name {path:?}"))?; map.push(path, contents); - Self::_parse(name, map) + Self::_parse(name, None, map) } /// Parse a WIT package at the provided path. @@ -181,10 +190,10 @@ impl UnresolvedPackage { .with_context(|| format!("failed to read file {path:?}"))?; map.push(&path, contents); } - Self::_parse(name, map) + Self::_parse(name, None, map) } - fn _parse(name: &str, map: SourceMap) -> Result { + fn _parse(name: &str, url: Option<&str>, map: SourceMap) -> Result { let mut doc = map.rewrite_error(|| { let mut resolver = Resolver::default(); for file in map.tokenizers() { @@ -192,7 +201,7 @@ impl UnresolvedPackage { let ast = Ast::parse(&mut tokens)?; resolver.push(path, ast)?; } - resolver.resolve(name) + resolver.resolve(name, url) })?; doc.source_map = map; Ok(doc) @@ -277,7 +286,7 @@ pub struct Interface { pub types: IndexMap, /// Exported functions from this interface. - pub functions: Vec, + pub functions: IndexMap, /// The document that this interface belongs to. pub document: DocumentId, diff --git a/crates/wit-parser/src/live.rs b/crates/wit-parser/src/live.rs new file mode 100644 index 0000000000..a4324eff71 --- /dev/null +++ b/crates/wit-parser/src/live.rs @@ -0,0 +1,116 @@ +use crate::{ + DocumentId, Function, InterfaceId, Resolve, Type, TypeDefKind, TypeId, WorldId, WorldItem, +}; +use indexmap::IndexSet; + +#[derive(Default)] +pub struct LiveTypes { + set: IndexSet, +} + +impl LiveTypes { + pub fn iter(&self) -> impl Iterator + '_ { + // Note the reverse iteration order to ensure that everything is visited + // in a topological order. + self.set.iter().rev().copied() + } + + pub fn add_document(&mut self, resolve: &Resolve, doc: DocumentId) { + let doc = &resolve.documents[doc]; + for (_, id) in doc.interfaces.iter() { + self.add_interface(resolve, *id); + } + for id in doc.worlds.iter() { + self.add_world(resolve, *id); + } + } + + pub fn add_interface(&mut self, resolve: &Resolve, iface: InterfaceId) { + let iface = &resolve.interfaces[iface]; + for (_, id) in iface.types.iter() { + self.add_type_id(resolve, *id); + } + for (_, func) in iface.functions.iter() { + self.add_func(resolve, func); + } + } + + pub fn add_world(&mut self, resolve: &Resolve, world: WorldId) { + let world = &resolve.worlds[world]; + for (_, item) in world.imports.iter().chain(world.exports.iter()) { + match item { + WorldItem::Interface(id) => self.add_interface(resolve, *id), + WorldItem::Function(f) => self.add_func(resolve, f), + } + } + } + + pub fn add_func(&mut self, resolve: &Resolve, func: &Function) { + for (_, ty) in func.params.iter() { + self.add_type(resolve, ty); + } + for ty in func.results.iter_types() { + self.add_type(resolve, ty); + } + // .. + } + + pub fn add_type_id(&mut self, resolve: &Resolve, ty: TypeId) { + if !self.set.insert(ty) { + return; + } + match &resolve.types[ty].kind { + TypeDefKind::Type(t) + | TypeDefKind::List(t) + | TypeDefKind::Option(t) + | TypeDefKind::Future(Some(t)) => self.add_type(resolve, t), + TypeDefKind::Record(r) => { + for field in r.fields.iter() { + self.add_type(resolve, &field.ty); + } + } + TypeDefKind::Tuple(r) => { + for ty in r.types.iter() { + self.add_type(resolve, ty); + } + } + TypeDefKind::Variant(v) => { + for case in v.cases.iter() { + if let Some(ty) = &case.ty { + self.add_type(resolve, ty); + } + } + } + TypeDefKind::Union(u) => { + for case in u.cases.iter() { + self.add_type(resolve, &case.ty); + } + } + TypeDefKind::Result(r) => { + if let Some(ty) = &r.ok { + self.add_type(resolve, ty); + } + if let Some(ty) = &r.err { + self.add_type(resolve, ty); + } + } + TypeDefKind::Stream(s) => { + if let Some(ty) = &s.element { + self.add_type(resolve, ty); + } + if let Some(ty) = &s.end { + self.add_type(resolve, ty); + } + } + TypeDefKind::Flags(_) | TypeDefKind::Enum(_) | TypeDefKind::Future(None) => {} + TypeDefKind::Unknown => unreachable!(), + } + } + + pub fn add_type(&mut self, resolve: &Resolve, ty: &Type) { + match ty { + Type::Id(id) => self.add_type_id(resolve, *id), + _ => {} + } + } +} diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 634d1a94ef..a54ad2f70b 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -38,6 +38,10 @@ pub struct Package { /// Locally-known name of this package. pub name: String, + /// Optionally-specified URL of this package, must be specified for remote + /// dependencies. + pub url: Option, + /// Documents contained within this package, organized by name. pub documents: IndexMap, } @@ -74,11 +78,12 @@ impl Resolve { let mut enqueued = HashSet::new(); let mut packages = IndexMap::new(); let mut pkg_deps = IndexMap::new(); - to_parse.push(path.to_path_buf()); + to_parse.push((path.to_path_buf(), None)); enqueued.insert(path.to_path_buf()); - while let Some(pkg_root) = to_parse.pop() { - let pkg = UnresolvedPackage::parse_dir(&pkg_root) + while let Some((pkg_root, url)) = to_parse.pop() { + let mut pkg = UnresolvedPackage::parse_dir(&pkg_root) .with_context(|| format!("failed to parse package at {path:?}"))?; + pkg.url = url; let mut deps = Vec::new(); pkg.source_map.rewrite_error(|| { @@ -94,7 +99,8 @@ impl Resolve { msg: format!("dependency on `{dep}` doesn't exist at {path:?}"), }) } - to_parse.push(path.clone()); + let url = Some(format!("path:/{dep}")); + to_parse.push((path.clone(), url)); } deps.push((path, span)); } @@ -314,6 +320,7 @@ impl Remap { } let pkgid = resolve.packages.alloc(Package { name: unresolved.name, + url: unresolved.url, documents, }); for (_, id) in resolve.packages[pkgid].documents.iter() { @@ -477,7 +484,7 @@ impl Remap { for (_name, ty) in iface.types.iter_mut() { *ty = self.types[ty.index()]; } - for func in iface.functions.iter_mut() { + for (_, func) in iface.functions.iter_mut() { for (_, ty) in func.params.iter_mut() { self.update_ty(ty); } diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index c3b68eb853..b4f519cafb 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -199,6 +199,11 @@ pub struct WitOpts { /// instead of the WebAssembly binary format. #[clap(short = 't', long)] wat: bool, + + /// When specifying `--wasm` the output wasm binary is validated by default, + /// and this can be used to skip that step. + #[clap(long)] + skip_validation: bool, } impl WitOpts { @@ -329,6 +334,13 @@ impl WitOpts { let resolve = decoded.resolve(); let bytes = wit_component::encode(&resolve, pkg)?; + if !self.skip_validation { + wasmparser::Validator::new_with_features(wasmparser::WasmFeatures { + component_model: true, + ..Default::default() + }) + .validate_all(&bytes)?; + } self.output.output(Output::Wasm { bytes: &bytes, wat: self.wat, From 504469ca8968cad74c05b4853af0dd7961d639f2 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 9 Jan 2023 07:56:03 -0800 Subject: [PATCH 06/37] Reimplement skeleton of `wasm-tools component new` This commit gets the CLI command for `wasm-tools component new` working again. In doing this a number of changes were made to the CLI: * The `--types-only` flag was dropped as this is entirely subsumed, or will be, by `wasm-tools component wit` * The `--wit` and `--encoding` flags were dropped. These were moved to a separate `wasm-tools component embed` subcommand. These aren't too useful in general and are primarily intended for developers working on raw internals. They also got more complicated with the current structure of WIT as a WIT package is now being specified with the `*.wit` file and then within that package a world needs to be selected. This logic all now lives in a separate subcommand The new `embed` subcommand is fully implemented here and its internals will be used for the eventual `components.rs` test to come in a bit. --- Cargo.toml | 2 +- crates/wit-component/src/encoding.rs | 355 ++++++++++++------------ crates/wit-component/src/lib.rs | 4 +- crates/wit-component/src/metadata.rs | 386 ++++++++++++++------------- src/bin/wasm-tools/component.rs | 327 ++++++++++++++--------- 5 files changed, 561 insertions(+), 513 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e43ef2db78..2699dffb5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -147,4 +147,4 @@ objdump = ['wasmparser'] strip = ['wasm-encoder', 'wasmparser', 'regex'] compose = ['wasm-compose'] demangle = ['rustc-demangle', 'cpp_demangle', 'wasmparser', 'wasm-encoder'] -component = ['wit-component', 'wit-parser', 'wast'] +component = ['wit-component', 'wit-parser', 'wast', 'wasm-encoder'] diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 86b8a63e4a..a70908b0a0 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -58,7 +58,7 @@ // validation::{ValidatedModule, MAIN_MODULE_IMPORT_NAME}, // StringEncoding, //}; -//use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; //use indexmap::IndexMap; //use std::collections::HashMap; //use std::hash::Hash; @@ -1107,192 +1107,167 @@ mod types; // } //} -///// An encoder of components based on `wit` interface definitions. -//#[derive(Default)] -//pub struct ComponentEncoder { -// module: Vec, -// metadata: Bindgen, -// validate: bool, -// types_only: bool, - -// // This is a map from the name of the adapter to a pair of: -// // -// // * The wasm of the adapter itself, with `component-type` sections -// // stripped. -// // * the metadata for the adapter, verified to have no exports and only -// // imports. -// // * The world within `self.metadata.doc` which the adapter works with. -// adapters: IndexMap, ModuleMetadata, WorldId)>, -//} - -//impl ComponentEncoder { -// /// Set the core module to encode as a component. -// /// This method will also parse any component type information stored in custom sections -// /// inside the module, and add them as the interface, imports, and exports. -// pub fn module(mut self, module: &[u8]) -> Result { -// let (wasm, metadata) = metadata::decode(module)?; -// self.module = wasm; -// self.metadata.merge(metadata)?; -// Ok(self) -// } - -// /// Sets whether or not the encoder will validate its output. -// pub fn validate(mut self, validate: bool) -> Self { -// self.validate = validate; -// self -// } - -// /// Add a document to this encoder as a manual specification of what's being -// /// imported/exported. -// /// -// /// The string encoding of the specified world is supplied here as -// /// well. -// /// -// /// Note that this can also be inferred from the `module` input if the -// /// module embeds its own metadata. This is otherwise required to describe -// /// imports/exports that aren't otherwise self-descried in `module`. -// pub fn document(mut self, doc: Document, encoding: StringEncoding) -> Result { -// let world = doc.default_world()?; -// self.metadata.merge(Bindgen { -// metadata: ModuleMetadata::new(&doc, world, encoding), -// doc, -// world, -// })?; -// Ok(self) -// } - -// /// Specifies a new adapter which is used to translate from a historical -// /// wasm ABI to the canonical ABI and the `interface` provided. -// /// -// /// This is primarily used to polyfill, for example, -// /// `wasi_snapshot_preview1` with a component-model using interface. The -// /// `name` provided is the module name of the adapter that is being -// /// polyfilled, for example `"wasi_snapshot_preview1"`. -// /// -// /// The `bytes` provided is a core wasm module which implements the `name` -// /// interface in terms of the `interface` interface. This core wasm module -// /// is severely restricted in its shape, for example it cannot have any data -// /// segments or element segments. -// /// -// /// The `interface` provided is the component-model-using-interface that the -// /// wasm module specified by `bytes` imports. The `bytes` will then import -// /// `interface` and export functions to get imported from the module `name` -// /// in the core wasm that's being wrapped. -// pub fn adapter(mut self, name: &str, bytes: &[u8]) -> Result { -// let (wasm, metadata) = metadata::decode(bytes)?; -// // Merge the adapter's document into our own document to have one large -// // document, but the adapter's world isn't merged in to our world so -// // retain it separately. -// let world = self.metadata.doc.merge(metadata.doc).world_map[metadata.world.index()]; -// self.adapters -// .insert(name.to_string(), (wasm, metadata.metadata, world)); -// Ok(self) -// } - -// /// Indicates whether this encoder is only encoding types and does not -// /// require a `module` as input. -// pub fn types_only(mut self, only: bool) -> Self { -// self.types_only = only; -// self -// } - -// /// Encode the component and return the bytes. -// pub fn encode(&self) -> Result> { -// let doc = &self.metadata.doc; -// let world = ComponentWorld::new(self)?; -// let mut state = EncodingState { -// component: ComponentBuilder::default(), -// module_index: None, -// instance_index: None, -// memory_index: None, -// realloc_index: None, -// shim_instance_index: None, -// fixups_module_index: None, -// adapter_modules: IndexMap::new(), -// adapter_instances: IndexMap::new(), -// adapter_import_reallocs: IndexMap::new(), -// adapter_export_reallocs: IndexMap::new(), -// type_map: HashMap::new(), -// func_type_map: HashMap::new(), -// imported_instances: Default::default(), -// info: &world, -// }; -// let world = &doc.worlds[self.metadata.world]; -// state.encode_imports()?; - -// if self.types_only { -// if !self.module.is_empty() { -// bail!("a module cannot be specified for a types-only encoding"); -// } - -// // In types-only mode there's no actual items to export so skip -// // shims/adapters etc. Instead instance types are exported to -// // represent exported instances and exported types represent the -// // default export. -// for (id, name) in world.exports() { -// let interface = &doc.interfaces[id]; -// let url = interface.url.as_deref().unwrap_or(""); -// match name { -// Some(name) => { -// let mut ty = state.instance_type_encoder(id); -// for func in interface.functions.iter() { -// let idx = ty.encode_func_type(doc, func)?; -// ty.ty.export(&func.name, "", ComponentTypeRef::Func(idx)); -// } -// let ty = ty.ty; -// if ty.is_empty() { -// continue; -// } -// let index = state.component.instance_type(&ty); -// state -// .component -// .export(name, url, ComponentExportKind::Type, index); -// } -// None => { -// let mut ty = state.root_type_encoder(id); -// for func in interface.functions.iter() { -// let idx = ty.encode_func_type(doc, func)?; -// ty.state.component.export( -// &func.name, -// "", -// ComponentExportKind::Type, -// idx, -// ); -// } -// for (idx, name) in ty.type_exports { -// ty.state -// .component -// .export(name, "", ComponentExportKind::Type, idx); -// } -// } -// } -// } -// } else { -// if self.module.is_empty() { -// bail!("a module is required when encoding a component"); -// } - -// state.encode_core_modules(); -// state.encode_core_instantiation()?; -// state.encode_exports(self, CustomModule::Main)?; -// for name in self.adapters.keys() { -// state.encode_exports(self, CustomModule::Adapter(name))?; -// } -// } - -// let bytes = state.component.finish(); - -// if self.validate { -// let mut validator = Validator::new_with_features(WasmFeatures { -// component_model: true, -// ..Default::default() -// }); - -// validator -// .validate_all(&bytes) -// .context("failed to validate component output")?; -// } - -// Ok(bytes) -// } -//} +/// An encoder of components based on `wit` interface definitions. +#[derive(Default)] +pub struct ComponentEncoder { + module: Vec, + // metadata: Bindgen, + validate: bool, + // This is a map from the name of the adapter to a pair of: + // + // * The wasm of the adapter itself, with `component-type` sections + // stripped. + // * the metadata for the adapter, verified to have no exports and only + // imports. + // * The world within `self.metadata.doc` which the adapter works with. + // adapters: IndexMap, ModuleMetadata, WorldId)>, +} + +impl ComponentEncoder { + /// Set the core module to encode as a component. + /// This method will also parse any component type information stored in custom sections + /// inside the module, and add them as the interface, imports, and exports. + pub fn module(mut self, module: &[u8]) -> Result { + panic!() + // let (wasm, metadata) = metadata::decode(module)?; + // self.module = wasm; + // self.metadata.merge(metadata)?; + // Ok(self) + } + + /// Sets whether or not the encoder will validate its output. + pub fn validate(mut self, validate: bool) -> Self { + self.validate = validate; + self + } + + /// Specifies a new adapter which is used to translate from a historical + /// wasm ABI to the canonical ABI and the `interface` provided. + /// + /// This is primarily used to polyfill, for example, + /// `wasi_snapshot_preview1` with a component-model using interface. The + /// `name` provided is the module name of the adapter that is being + /// polyfilled, for example `"wasi_snapshot_preview1"`. + /// + /// The `bytes` provided is a core wasm module which implements the `name` + /// interface in terms of the `interface` interface. This core wasm module + /// is severely restricted in its shape, for example it cannot have any data + /// segments or element segments. + /// + /// The `interface` provided is the component-model-using-interface that the + /// wasm module specified by `bytes` imports. The `bytes` will then import + /// `interface` and export functions to get imported from the module `name` + /// in the core wasm that's being wrapped. + pub fn adapter(mut self, name: &str, bytes: &[u8]) -> Result { + panic!() + // let (wasm, metadata) = metadata::decode(bytes)?; + // // Merge the adapter's document into our own document to have one large + // // document, but the adapter's world isn't merged in to our world so + // // retain it separately. + // let world = self.metadata.doc.merge(metadata.doc).world_map[metadata.world.index()]; + // self.adapters + // .insert(name.to_string(), (wasm, metadata.metadata, world)); + // Ok(self) + } + + /// Encode the component and return the bytes. + pub fn encode(&self) -> Result> { + panic!() + // let doc = &self.metadata.doc; + // let world = ComponentWorld::new(self)?; + // let mut state = EncodingState { + // component: ComponentBuilder::default(), + // module_index: None, + // instance_index: None, + // memory_index: None, + // realloc_index: None, + // shim_instance_index: None, + // fixups_module_index: None, + // adapter_modules: IndexMap::new(), + // adapter_instances: IndexMap::new(), + // adapter_import_reallocs: IndexMap::new(), + // adapter_export_reallocs: IndexMap::new(), + // type_map: HashMap::new(), + // func_type_map: HashMap::new(), + // imported_instances: Default::default(), + // info: &world, + // }; + // let world = &doc.worlds[self.metadata.world]; + // state.encode_imports()?; + + // if self.types_only { + // if !self.module.is_empty() { + // bail!("a module cannot be specified for a types-only encoding"); + // } + + // // In types-only mode there's no actual items to export so skip + // // shims/adapters etc. Instead instance types are exported to + // // represent exported instances and exported types represent the + // // default export. + // for (id, name) in world.exports() { + // let interface = &doc.interfaces[id]; + // let url = interface.url.as_deref().unwrap_or(""); + // match name { + // Some(name) => { + // let mut ty = state.instance_type_encoder(id); + // for func in interface.functions.iter() { + // let idx = ty.encode_func_type(doc, func)?; + // ty.ty.export(&func.name, "", ComponentTypeRef::Func(idx)); + // } + // let ty = ty.ty; + // if ty.is_empty() { + // continue; + // } + // let index = state.component.instance_type(&ty); + // state + // .component + // .export(name, url, ComponentExportKind::Type, index); + // } + // None => { + // let mut ty = state.root_type_encoder(id); + // for func in interface.functions.iter() { + // let idx = ty.encode_func_type(doc, func)?; + // ty.state.component.export( + // &func.name, + // "", + // ComponentExportKind::Type, + // idx, + // ); + // } + // for (idx, name) in ty.type_exports { + // ty.state + // .component + // .export(name, "", ComponentExportKind::Type, idx); + // } + // } + // } + // } + // } else { + // if self.module.is_empty() { + // bail!("a module is required when encoding a component"); + // } + + // state.encode_core_modules(); + // state.encode_core_instantiation()?; + // state.encode_exports(self, CustomModule::Main)?; + // for name in self.adapters.keys() { + // state.encode_exports(self, CustomModule::Adapter(name))?; + // } + // } + + // let bytes = state.component.finish(); + + // if self.validate { + // let mut validator = Validator::new_with_features(WasmFeatures { + // component_model: true, + // ..Default::default() + // }); + + // validator + // .validate_all(&bytes) + // .context("failed to validate component output")?; + // } + + // Ok(bytes) + } +} diff --git a/crates/wit-component/src/lib.rs b/crates/wit-component/src/lib.rs index 91e4255c15..9883e010ec 100644 --- a/crates/wit-component/src/lib.rs +++ b/crates/wit-component/src/lib.rs @@ -15,10 +15,10 @@ mod printing; // mod validation; pub use decoding::{decode, DecodedWasm}; -pub use encoding::encode; +pub use encoding::{encode, ComponentEncoder}; pub use printing::*; -// pub mod metadata; +pub mod metadata; /// Supported string encoding formats. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index 5511676d64..2c1f08beba 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -29,7 +29,7 @@ //! * Afterwards a "types only" component encoding of a `World` //! package through the `ComponentEncoder::types_only` configuration. -use crate::{decode_world, ComponentEncoder, StringEncoding}; +use crate::StringEncoding; use anyhow::{bail, Context, Result}; use indexmap::IndexMap; use wasm_encoder::Encode; @@ -38,81 +38,81 @@ use wit_parser::{Resolve, World, WorldId}; const CURRENT_VERSION: u8 = 0x02; -/// The result of decoding binding information from a WebAssembly binary. -/// -/// This structure is returned by [`decode`] and represents the interface of a -/// WebAssembly binary. -pub struct Bindgen { - /// Interface and type information for this binary. - pub resolve: Resolve, - /// The world that was bound. - pub world: WorldId, - /// Metadata about this specific module that was bound. - pub metadata: ModuleMetadata, -} +///// The result of decoding binding information from a WebAssembly binary. +///// +///// This structure is returned by [`decode`] and represents the interface of a +///// WebAssembly binary. +//pub struct Bindgen { +// /// Interface and type information for this binary. +// pub resolve: Resolve, +// /// The world that was bound. +// pub world: WorldId, +// /// Metadata about this specific module that was bound. +// pub metadata: ModuleMetadata, +//} -impl Default for Bindgen { - fn default() -> Bindgen { - let mut resolve = Resolve::default(); - let world = resolve.worlds.alloc(World::default()); - Bindgen { - resolve, - world, - metadata: ModuleMetadata::default(), - } - } -} +//impl Default for Bindgen { +// fn default() -> Bindgen { +// let mut resolve = Resolve::default(); +// let world = resolve.worlds.alloc(World::default()); +// Bindgen { +// resolve, +// world, +// metadata: ModuleMetadata::default(), +// } +// } +//} -/// Module-level metadata that's specific to one core WebAssembly module. This -/// is extracted with a [`Bindgen`]. -#[derive(Default)] -pub struct ModuleMetadata { - /// Per-function options imported into the core wasm module, currently only - /// related to string encoding. - pub import_encodings: IndexMap<(String, String), StringEncoding>, - - /// Per-function options exported from the core wasm module, currently only - /// related to string encoding. - pub export_encodings: IndexMap, -} +///// Module-level metadata that's specific to one core WebAssembly module. This +///// is extracted with a [`Bindgen`]. +//#[derive(Default)] +//pub struct ModuleMetadata { +// /// Per-function options imported into the core wasm module, currently only +// /// related to string encoding. +// pub import_encodings: IndexMap<(String, String), StringEncoding>, -/// This function will parse the `wasm` binary given as input and return a -/// [`Bindgen`] which extracts the custom sections describing component-level -/// types from within the binary itself. -/// -/// This is used to parse the output of `wit-bindgen`-generated modules and is -/// one of the earliest phases in transitioning such a module to a component. -/// The extraction here provides the metadata necessary to continue the process -/// later on. -/// -/// Note that a "stripped" binary where `component-type` sections are removed -/// is returned as well to embed within a component. -pub fn decode(wasm: &[u8]) -> Result<(Vec, Bindgen)> { - let mut ret = Bindgen::default(); - let mut new_module = wasm_encoder::Module::new(); - - for payload in wasmparser::Parser::new(0).parse_all(wasm) { - let payload = payload.context("decoding item in module")?; - match payload { - wasmparser::Payload::CustomSection(cs) if cs.name().starts_with("component-type") => { - let data = Bindgen::decode(cs.data()) - .with_context(|| format!("decoding custom section {}", cs.name()))?; - ret.merge(data) - .with_context(|| format!("updating metadata for section {}", cs.name()))?; - } - _ => { - if let Some((id, range)) = payload.as_section() { - new_module.section(&wasm_encoder::RawSection { - id, - data: &wasm[range], - }); - } - } - } - } - - Ok((new_module.finish(), ret)) -} +// /// Per-function options exported from the core wasm module, currently only +// /// related to string encoding. +// pub export_encodings: IndexMap, +//} + +///// This function will parse the `wasm` binary given as input and return a +///// [`Bindgen`] which extracts the custom sections describing component-level +///// types from within the binary itself. +///// +///// This is used to parse the output of `wit-bindgen`-generated modules and is +///// one of the earliest phases in transitioning such a module to a component. +///// The extraction here provides the metadata necessary to continue the process +///// later on. +///// +///// Note that a "stripped" binary where `component-type` sections are removed +///// is returned as well to embed within a component. +//pub fn decode(wasm: &[u8]) -> Result<(Vec, Bindgen)> { +// let mut ret = Bindgen::default(); +// let mut new_module = wasm_encoder::Module::new(); + +// for payload in wasmparser::Parser::new(0).parse_all(wasm) { +// let payload = payload.context("decoding item in module")?; +// match payload { +// wasmparser::Payload::CustomSection(cs) if cs.name().starts_with("component-type") => { +// let data = Bindgen::decode(cs.data()) +// .with_context(|| format!("decoding custom section {}", cs.name()))?; +// ret.merge(data) +// .with_context(|| format!("updating metadata for section {}", cs.name()))?; +// } +// _ => { +// if let Some((id, range)) = payload.as_section() { +// new_module.section(&wasm_encoder::RawSection { +// id, +// data: &wasm[range], +// }); +// } +// } +// } +// } + +// Ok((new_module.finish(), ret)) +//} /// Creates a `component-type*` custom section to be decoded by `decode` above. /// @@ -120,13 +120,19 @@ pub fn decode(wasm: &[u8]) -> Result<(Vec, Bindgen)> { /// into the final core wasm binary. The core wasm binary is later fed /// through `wit-component` to produce the actual component where this returned /// section will be decoded. -pub fn encode(resolve: &Resolve, world: WorldId, encoding: StringEncoding) -> Vec { - let component = ComponentEncoder::default() - .types_only(true) - .document(doc.clone(), encoding) - .unwrap() - .encode() - .unwrap(); +pub fn encode(resolve: &Resolve, world: WorldId, encoding: StringEncoding) -> Result> { + let world = &resolve.worlds[world]; + let doc = &resolve.documents[world.document]; + let pkg = &resolve.packages[doc.package.unwrap()]; + + assert!( + resolve + .packages + .iter() + .filter(|(_, p)| p.name == pkg.name) + .count() + == 1 + ); let mut ret = Vec::new(); ret.push(CURRENT_VERSION); @@ -135,115 +141,117 @@ pub fn encode(resolve: &Resolve, world: WorldId, encoding: StringEncoding) -> Ve StringEncoding::UTF16 => 0x01, StringEncoding::CompactUTF16 => 0x02, }); - doc.worlds[world].name.encode(&mut ret); - ret.extend(component); - ret + pkg.name.encode(&mut ret); + doc.name.encode(&mut ret); + world.name.encode(&mut ret); + ret.extend(crate::encoding::encode(resolve, doc.package.unwrap())?); + Ok(ret) } -impl Bindgen { - fn decode(data: &[u8]) -> Result { - let mut reader = BinaryReader::new(data); - let version = reader.read_u8()?; - if version != CURRENT_VERSION { - bail!("component-type version {version} does not match supported version {CURRENT_VERSION}"); - } - let encoding = match reader.read_u8()? { - 0x00 => StringEncoding::UTF8, - 0x01 => StringEncoding::UTF16, - 0x02 => StringEncoding::CompactUTF16, - byte => bail!("invalid string encoding {byte:#x}"), - }; - let name = reader.read_string()?; - - let (doc, world) = decode_world(name, &data[reader.original_position()..])?; - let metadata = ModuleMetadata::new(&doc, world, encoding); - Ok(Bindgen { - doc, - world, - metadata, - }) - } - - /// Merges another `BindgenMetadata` into this one. - /// - /// This operation is intended to be akin to "merging worlds" when the - /// abstraction level for that is what we're working at here. For now the - /// merge operation only succeeds if the two metadata descriptions are - /// entirely disjoint. - /// - /// Note that at this time there's no support for changing string encodings - /// between metadata. - pub fn merge(&mut self, other: Bindgen) -> Result<()> { - let Bindgen { - doc, - world, - metadata: - ModuleMetadata { - import_encodings, - export_encodings, - }, - } = other; - - let world = self.doc.merge(doc).world_map[world.index()]; - self.doc - .merge_worlds(world, self.world) - .context("failed to merge worlds from two documents")?; - - for (name, encoding) in export_encodings { - let prev = self - .metadata - .export_encodings - .insert(name.clone(), encoding); - if let Some(prev) = prev { - if prev != encoding { - bail!("conflicting string encodings specified for export `{name}`"); - } - } - } - for ((module, name), encoding) in import_encodings { - let prev = self - .metadata - .import_encodings - .insert((module.clone(), name.clone()), encoding); - if let Some(prev) = prev { - if prev != encoding { - bail!("conflicting string encodings specified for import `{module}::{name}`"); - } - } - } - - Ok(()) - } -} +//impl Bindgen { +// fn decode(data: &[u8]) -> Result { +// let mut reader = BinaryReader::new(data); +// let version = reader.read_u8()?; +// if version != CURRENT_VERSION { +// bail!("component-type version {version} does not match supported version {CURRENT_VERSION}"); +// } +// let encoding = match reader.read_u8()? { +// 0x00 => StringEncoding::UTF8, +// 0x01 => StringEncoding::UTF16, +// 0x02 => StringEncoding::CompactUTF16, +// byte => bail!("invalid string encoding {byte:#x}"), +// }; +// let name = reader.read_string()?; -impl ModuleMetadata { - /// Creates a new `ModuleMetadata` instance holding the given set of - /// interfaces which are expected to all use the `encoding` specified. - pub fn new(doc: &Document, world: WorldId, encoding: StringEncoding) -> ModuleMetadata { - let mut ret = ModuleMetadata::default(); - - if let Some(iface) = doc.worlds[world].default { - for func in doc.interfaces[iface].functions.iter() { - let name = func.core_export_name(None); - let prev = ret.export_encodings.insert(name.to_string(), encoding); - assert!(prev.is_none()); - } - } - - for (name, import) in doc.worlds[world].imports.iter() { - for func in doc.interfaces[*import].functions.iter() { - let key = (name.clone(), func.name.clone()); - let prev = ret.import_encodings.insert(key, encoding); - assert!(prev.is_none()); - } - } - for (name, export) in doc.worlds[world].exports.iter() { - for func in doc.interfaces[*export].functions.iter() { - let name = func.core_export_name(Some(name)); - let prev = ret.export_encodings.insert(name.to_string(), encoding); - assert!(prev.is_none()); - } - } - ret - } -} +// let (doc, world) = decode_world(name, &data[reader.original_position()..])?; +// let metadata = ModuleMetadata::new(&doc, world, encoding); +// Ok(Bindgen { +// doc, +// world, +// metadata, +// }) +// } + +// /// Merges another `BindgenMetadata` into this one. +// /// +// /// This operation is intended to be akin to "merging worlds" when the +// /// abstraction level for that is what we're working at here. For now the +// /// merge operation only succeeds if the two metadata descriptions are +// /// entirely disjoint. +// /// +// /// Note that at this time there's no support for changing string encodings +// /// between metadata. +// pub fn merge(&mut self, other: Bindgen) -> Result<()> { +// let Bindgen { +// doc, +// world, +// metadata: +// ModuleMetadata { +// import_encodings, +// export_encodings, +// }, +// } = other; + +// let world = self.doc.merge(doc).world_map[world.index()]; +// self.doc +// .merge_worlds(world, self.world) +// .context("failed to merge worlds from two documents")?; + +// for (name, encoding) in export_encodings { +// let prev = self +// .metadata +// .export_encodings +// .insert(name.clone(), encoding); +// if let Some(prev) = prev { +// if prev != encoding { +// bail!("conflicting string encodings specified for export `{name}`"); +// } +// } +// } +// for ((module, name), encoding) in import_encodings { +// let prev = self +// .metadata +// .import_encodings +// .insert((module.clone(), name.clone()), encoding); +// if let Some(prev) = prev { +// if prev != encoding { +// bail!("conflicting string encodings specified for import `{module}::{name}`"); +// } +// } +// } + +// Ok(()) +// } +//} + +//impl ModuleMetadata { +// /// Creates a new `ModuleMetadata` instance holding the given set of +// /// interfaces which are expected to all use the `encoding` specified. +// pub fn new(doc: &Document, world: WorldId, encoding: StringEncoding) -> ModuleMetadata { +// let mut ret = ModuleMetadata::default(); + +// if let Some(iface) = doc.worlds[world].default { +// for func in doc.interfaces[iface].functions.iter() { +// let name = func.core_export_name(None); +// let prev = ret.export_encodings.insert(name.to_string(), encoding); +// assert!(prev.is_none()); +// } +// } + +// for (name, import) in doc.worlds[world].imports.iter() { +// for func in doc.interfaces[*import].functions.iter() { +// let key = (name.clone(), func.name.clone()); +// let prev = ret.import_encodings.insert(key, encoding); +// assert!(prev.is_none()); +// } +// } +// for (name, export) in doc.worlds[world].exports.iter() { +// for func in doc.interfaces[*export].functions.iter() { +// let name = func.core_export_name(Some(name)); +// let prev = ret.export_encodings.insert(name.to_string(), encoding); +// assert!(prev.is_none()); +// } +// } +// ret +// } +//} diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index b4f519cafb..42203ab256 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -3,146 +3,206 @@ use anyhow::{anyhow, bail, Context, Result}; use clap::Parser; use std::io::Read; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use wasm_encoder::Encode; use wasm_tools::Output; -use wit_component::{DecodedWasm, DocumentPrinter}; -use wit_parser::{Resolve, UnresolvedPackage}; -// use wit_component::{decode_world, ComponentEncoder, DocumentPrinter, StringEncoding}; +use wit_component::{ComponentEncoder, DecodedWasm, DocumentPrinter, StringEncoding}; +use wit_parser::{PackageId, Resolve, UnresolvedPackage}; /// WebAssembly wit-based component tooling. #[derive(Parser)] pub enum Opts { - // New(NewOpts), + New(NewOpts), Wit(WitOpts), + Embed(EmbedOpts), } impl Opts { pub fn run(self) -> Result<()> { match self { - // Opts::New(new) => new.run(), + Opts::New(new) => new.run(), Opts::Wit(wit) => wit.run(), + Opts::Embed(embed) => embed.run(), } } } -// fn parse_optionally_name_file(s: &str) -> (&str, &str) { -// let mut parts = s.splitn(2, '='); -// let name_or_path = parts.next().unwrap(); -// match parts.next() { -// Some(path) => (name_or_path, path), -// None => { -// let name = Path::new(name_or_path) -// .file_stem() -// .unwrap() -// .to_str() -// .unwrap(); -// (name, name_or_path) -// } -// } -// } - -// fn parse_adapter(s: &str) -> Result<(String, Vec)> { -// let (name, path) = parse_optionally_name_file(s); -// let wasm = wat::parse_file(path)?; -// Ok((name.to_string(), wasm)) -// } - -///// WebAssembly component encoder from an input core wasm binary. -///// -///// This subcommand will create a new component `*.wasm` file from an input core -///// wasm binary. The input core wasm binary is expected to be compiled with -///// `wit-component` or derivative projects which encodes component-based type -///// information into the input core wasm binary's custom sections. The `--wit` -///// option can also be used to specify the interface manually too. -//#[derive(Parser)] -//pub struct NewOpts { -// /// The path to an adapter module to satisfy imports. -// /// -// /// An adapter module can be used to translate the `wasi_snapshot_preview1` -// /// ABI, for example, to one that uses the component model. The first -// /// `[NAME=]` specified in the argument is inferred from the name of file -// /// specified by `MODULE` if not present and is the name of the import -// /// module that's being implemented (e.g. `wasi_snapshot_preview1.wasm`. -// /// -// /// The second part of this argument, optionally specified, is the interface -// /// that this adapter module imports. If not specified then the interface -// /// imported is inferred from the adapter module itself. -// #[clap(long = "adapt", value_name = "[NAME=]MODULE", value_parser = parse_adapter)] -// adapters: Vec<(String, Vec)>, - -// #[clap(flatten)] -// io: wasm_tools::InputOutput, - -// /// The "world" that the input binary implements. -// /// -// /// This argument is a `*.wit` file which describes the imports and exports -// /// of the core wasm module. Users of `wit-bindgen` don't need this as those -// /// generators already embed this information into the input core wasm -// /// binary. -// #[clap(long, value_name = "PATH")] -// wit: Option, - -// /// Skip validation of the output component. -// #[clap(long)] -// skip_validation: bool, - -// /// The expected string encoding format for the component. -// /// -// /// Supported values are: `utf8` (default), `utf16`, and `compact-utf16`. -// /// This is only applicable to the `--wit` argument to describe the string -// /// encoding of the functions in that world. -// #[clap(long, value_name = "ENCODING")] -// encoding: Option, - -// /// Print the output in the WebAssembly text format instead of binary. -// #[clap(long, short = 't')] -// wat: bool, - -// /// Generate a "types only" component which is a binary encoding of the -// /// input wit file or the wit already encoded into the module. -// #[clap(long)] -// types_only: bool, -//} - -//impl NewOpts { -// /// Executes the application. -// fn run(self) -> Result<()> { -// let wasm = if self.types_only { -// self.io.init_logger(); -// None -// } else { -// Some(self.io.parse_input_wasm()?) -// }; -// let mut encoder = ComponentEncoder::default() -// .validate(!self.skip_validation) -// .types_only(self.types_only); - -// if let Some(wasm) = wasm { -// encoder = encoder.module(&wasm)?; -// } - -// if let Some(wit) = &self.wit { -// let encoding = self.encoding.unwrap_or(StringEncoding::UTF8); -// let doc = Document::parse_file(wit)?; -// encoder = encoder.document(doc, encoding)?; -// } - -// for (name, wasm) in self.adapters.iter() { -// encoder = encoder.adapter(name, wasm)?; -// } - -// let bytes = encoder -// .encode() -// .with_context(|| format!("failed to encode a component from module ",))?; - -// self.io.output(Output::Wasm { -// bytes: &bytes, -// wat: self.wat, -// })?; - -// Ok(()) -// } -//} +fn parse_optionally_name_file(s: &str) -> (&str, &str) { + let mut parts = s.splitn(2, '='); + let name_or_path = parts.next().unwrap(); + match parts.next() { + Some(path) => (name_or_path, path), + None => { + let name = Path::new(name_or_path) + .file_stem() + .unwrap() + .to_str() + .unwrap(); + (name, name_or_path) + } + } +} + +fn parse_adapter(s: &str) -> Result<(String, Vec)> { + let (name, path) = parse_optionally_name_file(s); + let wasm = wat::parse_file(path)?; + Ok((name.to_string(), wasm)) +} + +/// WebAssembly component encoder from an input core wasm binary. +/// +/// This subcommand will create a new component `*.wasm` file from an input core +/// wasm binary. The input core wasm binary is expected to be compiled with +/// `wit-component` or derivative projects which encodes component-based type +/// information into the input core wasm binary's custom sections. The `--wit` +/// option can also be used to specify the interface manually too. +#[derive(Parser)] +pub struct NewOpts { + /// The path to an adapter module to satisfy imports. + /// + /// An adapter module can be used to translate the `wasi_snapshot_preview1` + /// ABI, for example, to one that uses the component model. The first + /// `[NAME=]` specified in the argument is inferred from the name of file + /// specified by `MODULE` if not present and is the name of the import + /// module that's being implemented (e.g. `wasi_snapshot_preview1.wasm`. + /// + /// The second part of this argument, optionally specified, is the interface + /// that this adapter module imports. If not specified then the interface + /// imported is inferred from the adapter module itself. + #[clap(long = "adapt", value_name = "[NAME=]MODULE", value_parser = parse_adapter)] + adapters: Vec<(String, Vec)>, + + #[clap(flatten)] + io: wasm_tools::InputOutput, + + /// Skip validation of the output component. + #[clap(long)] + skip_validation: bool, + + /// Print the output in the WebAssembly text format instead of binary. + #[clap(long, short = 't')] + wat: bool, +} + +impl NewOpts { + /// Executes the application. + fn run(self) -> Result<()> { + let wasm = self.io.parse_input_wasm()?; + let mut encoder = ComponentEncoder::default() + .validate(!self.skip_validation) + .module(&wasm)?; + + for (name, wasm) in self.adapters.iter() { + encoder = encoder.adapter(name, wasm)?; + } + + let bytes = encoder + .encode() + .with_context(|| format!("failed to encode a component from module "))?; + + self.io.output(Output::Wasm { + bytes: &bytes, + wat: self.wat, + })?; + + Ok(()) + } +} + +/// Embeds metadata for a component inside of a core wasm module. +/// +/// This subcommand is a convenience tool provided for producing core wasm +/// binaries which will get consumed by `wasm-tools component new`. This will +/// embed metadata for a component within a core wasm binary as a custom +/// section. +/// +/// This metadata describe the imports and exports of a core wasm module with a +/// WIT package's `world`. The metadata will be used when creating a full +/// component. +/// +/// Note that this subcommand may not be required most of the time since most +/// language tooling will already embed this metadata in the final wasm binary +/// for you. This is primarily intended for one-off testing or for developers +/// working with text format wasm. +#[derive(Parser)] +pub struct EmbedOpts { + /// The WIT package where the `world` that the core wasm module implements + /// lives. + /// + /// This can either be a directory or a path to a single `*.wit` file. + wit: PathBuf, + + #[clap(flatten)] + io: wasm_tools::InputOutput, + + /// The expected string encoding format for the component. + /// + /// Supported values are: `utf8` (default), `utf16`, and `compact-utf16`. + /// This is only applicable to the `--wit` argument to describe the string + /// encoding of the functions in that world. + #[clap(long, value_name = "ENCODING")] + encoding: Option, + + /// The world that the component uses. + /// + /// This is the path, within the `WIT` package provided as a positional + /// argument, to the `world` that the core wasm module works with. This can + /// either be a bare string which a document name that has a `default + /// world`, or it can be a `foo/bar` name where `foo` names a document and + /// `bar` names a world within that document. + #[clap(short, long)] + world: String, +} + +impl EmbedOpts { + /// Executes the application. + fn run(self) -> Result<()> { + let mut wasm = self.io.parse_input_wasm()?; + let (resolve, id) = parse_wit(&self.wit)?; + + let mut parts = self.world.split('/'); + let doc = match parts.next() { + Some(name) => match resolve.packages[id].documents.get(name) { + Some(doc) => *doc, + None => bail!("no document named `{name}` in package"), + }, + None => bail!("invalid `--world` argument"), + }; + let world = match parts.next() { + Some(name) => match resolve.documents[doc] + .worlds + .iter() + .find(|w| resolve.worlds[**w].name == name) + { + Some(world) => *world, + None => bail!("no world named `{name}` in document"), + }, + None => match resolve.documents[doc].default_world { + Some(world) => world, + None => bail!("no default world found in document"), + }, + }; + + let encoded = wit_component::metadata::encode( + &resolve, + world, + self.encoding.unwrap_or(StringEncoding::UTF8), + )?; + + wasm_encoder::CustomSection { + name: "component-type", + data: &encoded, + } + .encode(&mut wasm); + + self.io.output(Output::Wasm { + bytes: &wasm, + wat: false, + })?; + + Ok(()) + } +} /// Tool for working with the WIT text format for components. /// @@ -238,13 +298,7 @@ impl WitOpts { wit_component::decode(name, &bytes).context("failed to decode WIT document")? } _ => { - let mut resolve = Resolve::default(); - let id = if input.is_dir() { - resolve.push_dir(&input)? - } else { - let pkg = UnresolvedPackage::parse_file(&input)?; - resolve.push(pkg, &Default::default())? - }; + let (resolve, id) = parse_wit(input)?; DecodedWasm::WitPackage(resolve, id) } }, @@ -381,6 +435,17 @@ impl WitOpts { } } +fn parse_wit(path: &Path) -> Result<(Resolve, PackageId)> { + let mut resolve = Resolve::default(); + let id = if path.is_dir() { + resolve.push_dir(&path)? + } else { + let pkg = UnresolvedPackage::parse_file(&path)?; + resolve.push(pkg, &Default::default())? + }; + Ok((resolve, id)) +} + /// Test to see if a string is probably a `*.wat` text syntax. /// /// This briefly lexes past whitespace and comments as a `*.wat` file to see if From ce19a5d2aadac4fd26ce8bf17b2f56f04b4c2349 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 9 Jan 2023 08:25:48 -0800 Subject: [PATCH 07/37] Get all core-wasm-to-component tests working again This gets the rest of `wit-component` compiling again, updating it to the new AST representation inside of the `wit-parser` crate. --- TODO.md | 2 + crates/wit-component/src/builder.rs | 9 - crates/wit-component/src/decoding.rs | 29 +- crates/wit-component/src/encoding.rs | 2288 ++++++++--------- crates/wit-component/src/encoding/types.rs | 195 +- crates/wit-component/src/encoding/wit.rs | 75 +- crates/wit-component/src/encoding/world.rs | 309 ++- crates/wit-component/src/lib.rs | 4 +- crates/wit-component/src/metadata.rs | 381 +-- crates/wit-component/src/printing.rs | 76 +- crates/wit-component/src/validation.rs | 197 +- crates/wit-component/tests/components.rs | 150 +- .../{adapt-old-world.wit => adapt-old.wit} | 2 +- .../adapt-empty-interface/module.wit | 1 + .../adapt-empty-interface/world.wit | 1 - .../adapt-export-default/adapt-old-world.wit | 5 - .../adapt-export-default/adapt-old.wit | 3 + .../adapt-export-default/component.wat | 2 +- .../adapt-export-default/module.wit | 1 + .../components/adapt-export-default/world.wit | 1 - .../adapt-old-world.wit | 7 - .../adapt-export-namespaced/adapt-old.wit | 7 + .../adapt-export-namespaced/component.wat | 2 +- .../adapt-export-namespaced/module.wit | 1 + .../adapt-export-namespaced/world.wit | 1 - .../adapt-export-reallocs/adapt-old-world.wit | 8 - .../adapt-export-reallocs/adapt-old.wit | 6 + .../adapt-export-reallocs/component.wat | 2 +- .../adapt-export-reallocs/module.wit | 1 + .../adapt-export-reallocs/world.wit | 1 - .../adapt-old-world.wit | 5 - .../adapt-export-save-args/adapt-old.wit | 3 + .../adapt-export-save-args/component.wat | 2 +- .../adapt-export-save-args/module.wit | 1 + .../adapt-export-save-args/world.wit | 1 - .../{adapt-old-world.wit => adapt-old.wit} | 2 +- .../components/adapt-inject-stack/module.wit | 1 + .../components/adapt-inject-stack/world.wit | 1 - .../{adapt-old-world.wit => adapt-old.wit} | 2 +- .../components/adapt-list-return/module.wit | 1 + .../components/adapt-list-return/world.wit | 1 - .../adapt-old.wit} | 2 +- .../components/adapt-memory-simple/module.wit | 1 + .../components/adapt-memory-simple/world.wit | 1 - .../{adapt-old-world.wit => adapt-old.wit} | 2 +- .../adapt-missing-memory/module.wit | 1 + .../components/adapt-missing-memory/world.wit | 1 - .../{adapt-foo-world.wit => adapt-foo.wit} | 2 +- .../components/adapt-multiple/module.wit | 1 + .../tests/components/adapt-multiple/world.wit | 1 - ...1.wat => adapt-wasi-snapshot-preview1.wat} | 0 ...d.wit => adapt-wasi-snapshot-preview1.wit} | 4 +- .../components/adapt-preview1/component.wat | 14 +- .../components/adapt-preview1/module.wat | 4 +- .../adapt-preview1/{world.wit => module.wit} | 2 +- .../adapt-old.wit} | 2 +- .../tests/components/adapt-unused/module.wit | 1 + .../tests/components/adapt-unused/world.wit | 1 - .../default-export-sig-mismatch/error.txt | 2 +- .../default-export-sig-mismatch/module.wat | 2 +- .../default-export-sig-mismatch/module.wit | 3 + .../default-export-sig-mismatch/world.wit | 5 - .../components/empty-module-import/error.txt | 2 +- .../components/empty-module-import/module.wit | 1 + .../components/empty-module-import/world.wit | 1 - .../tests/components/empty/module.wit | 1 + .../tests/components/empty/world.wit | 1 - .../ensure-default-type-exports/component.wat | 12 +- .../ensure-default-type-exports/module.wit | 15 + .../ensure-default-type-exports/world.wit | 22 - .../components/export-sig-mismatch/error.txt | 5 +- .../{world.wit => module.wit} | 2 +- .../tests/components/exports/component.wat | 22 +- .../exports/{world.wit => module.wit} | 10 +- .../import-conflict/{world.wit => module.wit} | 8 +- .../{world.wit => module.wit} | 2 +- .../components/import-export/component.wat | 6 +- .../import-export/{world.wit => module.wit} | 7 +- .../components/import-sig-mismatch/error.txt | 5 +- .../{world.wit => module.wit} | 2 +- .../tests/components/imports/component.wat | 4 +- .../imports/{world.wit => module.wit} | 2 +- .../invalid-module-import/module.wit | 1 + .../invalid-module-import/world.wit | 1 - .../components/lift-options/component.wat | 123 +- .../tests/components/lift-options/module.wat | 40 +- .../lift-options/{world.wit => module.wit} | 4 +- .../components/lower-options/component.wat | 16 +- .../lower-options/{world.wit => module.wit} | 4 +- .../missing-default-export/module.wit | 3 + .../missing-default-export/world.wit | 5 - .../tests/components/missing-export/error.txt | 5 +- .../missing-export/{world.wit => module.wit} | 2 +- .../components/missing-import-func/error.txt | 5 +- .../components/missing-import-func/module.wit | 7 + .../components/missing-import-func/world.wit | 7 - .../components/missing-import/module.wit | 1 + .../tests/components/missing-import/world.wit | 1 - .../{world.wit => module.wit} | 2 +- .../components/post-return/component.wat | 2 +- .../tests/components/post-return/module.wit | 3 + .../tests/components/post-return/world.wit | 5 - .../tests/components/simple/component.wat | 22 +- .../tests/components/simple/module.wit | 5 + .../tests/components/simple/world.wit | 10 - .../tests/components/unused-import/module.wit | 10 + .../tests/components/unused-import/world.wit | 10 - .../tests/interfaces/world-top-level.wat | 42 + .../tests/interfaces/world-top-level.wit | 18 + .../interfaces/world-top-level.wit.print | 16 + crates/wit-parser/src/ast.rs | 7 +- crates/wit-parser/src/ast/resolve.rs | 58 +- crates/wit-parser/src/lib.rs | 2 +- crates/wit-parser/src/live.rs | 14 +- crates/wit-parser/src/resolve.rs | 194 +- .../ui/parse-fail/world-top-level-func.wit | 4 + .../world-top-level-func.wit.result | 5 + .../ui/parse-fail/world-top-level-func2.wit | 3 + .../world-top-level-func2.wit.result | 5 + .../tests/ui/world-top-level-funcs.wit | 7 + src/bin/wasm-tools/component.rs | 15 +- 121 files changed, 2472 insertions(+), 2181 deletions(-) create mode 100644 TODO.md rename crates/wit-component/tests/components/adapt-empty-interface/{adapt-old-world.wit => adapt-old.wit} (77%) create mode 100644 crates/wit-component/tests/components/adapt-empty-interface/module.wit delete mode 100644 crates/wit-component/tests/components/adapt-empty-interface/world.wit delete mode 100644 crates/wit-component/tests/components/adapt-export-default/adapt-old-world.wit create mode 100644 crates/wit-component/tests/components/adapt-export-default/adapt-old.wit create mode 100644 crates/wit-component/tests/components/adapt-export-default/module.wit delete mode 100644 crates/wit-component/tests/components/adapt-export-default/world.wit delete mode 100644 crates/wit-component/tests/components/adapt-export-namespaced/adapt-old-world.wit create mode 100644 crates/wit-component/tests/components/adapt-export-namespaced/adapt-old.wit create mode 100644 crates/wit-component/tests/components/adapt-export-namespaced/module.wit delete mode 100644 crates/wit-component/tests/components/adapt-export-namespaced/world.wit delete mode 100644 crates/wit-component/tests/components/adapt-export-reallocs/adapt-old-world.wit create mode 100644 crates/wit-component/tests/components/adapt-export-reallocs/adapt-old.wit create mode 100644 crates/wit-component/tests/components/adapt-export-reallocs/module.wit delete mode 100644 crates/wit-component/tests/components/adapt-export-reallocs/world.wit delete mode 100644 crates/wit-component/tests/components/adapt-export-save-args/adapt-old-world.wit create mode 100644 crates/wit-component/tests/components/adapt-export-save-args/adapt-old.wit create mode 100644 crates/wit-component/tests/components/adapt-export-save-args/module.wit delete mode 100644 crates/wit-component/tests/components/adapt-export-save-args/world.wit rename crates/wit-component/tests/components/adapt-inject-stack/{adapt-old-world.wit => adapt-old.wit} (69%) create mode 100644 crates/wit-component/tests/components/adapt-inject-stack/module.wit delete mode 100644 crates/wit-component/tests/components/adapt-inject-stack/world.wit rename crates/wit-component/tests/components/adapt-list-return/{adapt-old-world.wit => adapt-old.wit} (65%) create mode 100644 crates/wit-component/tests/components/adapt-list-return/module.wit delete mode 100644 crates/wit-component/tests/components/adapt-list-return/world.wit rename crates/wit-component/tests/components/{adapt-unused/adapt-old-world.wit => adapt-memory-simple/adapt-old.wit} (64%) create mode 100644 crates/wit-component/tests/components/adapt-memory-simple/module.wit delete mode 100644 crates/wit-component/tests/components/adapt-memory-simple/world.wit rename crates/wit-component/tests/components/adapt-missing-memory/{adapt-old-world.wit => adapt-old.wit} (64%) create mode 100644 crates/wit-component/tests/components/adapt-missing-memory/module.wit delete mode 100644 crates/wit-component/tests/components/adapt-missing-memory/world.wit rename crates/wit-component/tests/components/adapt-multiple/{adapt-foo-world.wit => adapt-foo.wit} (76%) create mode 100644 crates/wit-component/tests/components/adapt-multiple/module.wit delete mode 100644 crates/wit-component/tests/components/adapt-multiple/world.wit rename crates/wit-component/tests/components/adapt-preview1/{adapt-wasi_snapshot_preview1.wat => adapt-wasi-snapshot-preview1.wat} (100%) rename crates/wit-component/tests/components/adapt-preview1/{adapt-wasi_snapshot_preview1-world.wit => adapt-wasi-snapshot-preview1.wit} (82%) rename crates/wit-component/tests/components/adapt-preview1/{world.wit => module.wit} (65%) rename crates/wit-component/tests/components/{adapt-memory-simple/adapt-old-world.wit => adapt-unused/adapt-old.wit} (64%) create mode 100644 crates/wit-component/tests/components/adapt-unused/module.wit delete mode 100644 crates/wit-component/tests/components/adapt-unused/world.wit create mode 100644 crates/wit-component/tests/components/default-export-sig-mismatch/module.wit delete mode 100644 crates/wit-component/tests/components/default-export-sig-mismatch/world.wit create mode 100644 crates/wit-component/tests/components/empty-module-import/module.wit delete mode 100644 crates/wit-component/tests/components/empty-module-import/world.wit create mode 100644 crates/wit-component/tests/components/empty/module.wit delete mode 100644 crates/wit-component/tests/components/empty/world.wit create mode 100644 crates/wit-component/tests/components/ensure-default-type-exports/module.wit delete mode 100644 crates/wit-component/tests/components/ensure-default-type-exports/world.wit rename crates/wit-component/tests/components/export-sig-mismatch/{world.wit => module.wit} (76%) rename crates/wit-component/tests/components/exports/{world.wit => module.wit} (63%) rename crates/wit-component/tests/components/import-conflict/{world.wit => module.wit} (59%) rename crates/wit-component/tests/components/import-empty-interface/{world.wit => module.wit} (59%) rename crates/wit-component/tests/components/import-export/{world.wit => module.wit} (54%) rename crates/wit-component/tests/components/import-sig-mismatch/{world.wit => module.wit} (74%) rename crates/wit-component/tests/components/imports/{world.wit => module.wit} (95%) create mode 100644 crates/wit-component/tests/components/invalid-module-import/module.wit delete mode 100644 crates/wit-component/tests/components/invalid-module-import/world.wit rename crates/wit-component/tests/components/lift-options/{world.wit => module.wit} (90%) rename crates/wit-component/tests/components/lower-options/{world.wit => module.wit} (91%) create mode 100644 crates/wit-component/tests/components/missing-default-export/module.wit delete mode 100644 crates/wit-component/tests/components/missing-default-export/world.wit rename crates/wit-component/tests/components/missing-export/{world.wit => module.wit} (64%) create mode 100644 crates/wit-component/tests/components/missing-import-func/module.wit delete mode 100644 crates/wit-component/tests/components/missing-import-func/world.wit create mode 100644 crates/wit-component/tests/components/missing-import/module.wit delete mode 100644 crates/wit-component/tests/components/missing-import/world.wit rename crates/wit-component/tests/components/no-realloc-required/{world.wit => module.wit} (74%) create mode 100644 crates/wit-component/tests/components/post-return/module.wit delete mode 100644 crates/wit-component/tests/components/post-return/world.wit create mode 100644 crates/wit-component/tests/components/simple/module.wit delete mode 100644 crates/wit-component/tests/components/simple/world.wit create mode 100644 crates/wit-component/tests/components/unused-import/module.wit delete mode 100644 crates/wit-component/tests/components/unused-import/world.wit create mode 100644 crates/wit-component/tests/interfaces/world-top-level.wat create mode 100644 crates/wit-component/tests/interfaces/world-top-level.wit create mode 100644 crates/wit-component/tests/interfaces/world-top-level.wit.print create mode 100644 crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result create mode 100644 crates/wit-parser/tests/ui/world-top-level-funcs.wit diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000000..bb4fd52955 --- /dev/null +++ b/TODO.md @@ -0,0 +1,2 @@ +* test `import foo: bar` +* add support for `import foo: func()` and export diff --git a/crates/wit-component/src/builder.rs b/crates/wit-component/src/builder.rs index 81f223965a..d7465a8251 100644 --- a/crates/wit-component/src/builder.rs +++ b/crates/wit-component/src/builder.rs @@ -169,15 +169,6 @@ impl ComponentBuilder { }); inc(&mut self.types) } - - pub fn alias_outer_type(&mut self, count: u32, index: u32) -> u32 { - self.aliases().alias(Alias::Outer { - count, - kind: ComponentOuterAliasKind::Type, - index, - }); - inc(&mut self.types) - } } // Helper macro to generate methods on `ComponentBuilder` to get specific diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index 4090da30b7..0d2b7f24a0 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -216,7 +216,7 @@ impl WitPackageDecoder<'_> { let doc = self.resolve.documents.alloc(Document { name: name.to_string(), interfaces: IndexMap::new(), - worlds: Vec::new(), + worlds: IndexMap::new(), default_interface: None, default_world: None, package: Some(self.package), @@ -253,7 +253,10 @@ impl WitPackageDecoder<'_> { let id = self .register_world(doc, name, ty) .with_context(|| format!("failed to process export `{name}`"))?; - self.resolve.documents[doc].worlds.push(id); + let prev = self.resolve.documents[doc] + .worlds + .insert(name.to_string(), id); + assert!(prev.is_none()); } _ => bail!("component export `{name}` is not an instance or component"), } @@ -423,7 +426,7 @@ impl WitPackageDecoder<'_> { self.resolve.documents.alloc(Document { name: document.to_string(), interfaces: IndexMap::new(), - worlds: Vec::new(), + worlds: IndexMap::new(), default_interface: None, default_world: None, package: Some(package), @@ -556,6 +559,16 @@ impl WitPackageDecoder<'_> { }; WorldItem::Interface(id) } + + types::ComponentEntityType::Func(idx) => { + let ty = match self.info.types.type_from_id(*idx) { + Some(types::Type::ComponentFunc(ty)) => ty, + _ => unreachable!(), + }; + let func = self.convert_function(name, ty)?; + WorldItem::Function(func) + } + _ => bail!("component import `{name}` is not an instance"), }; world.imports.insert(name.to_string(), item); @@ -574,6 +587,16 @@ impl WitPackageDecoder<'_> { }; WorldItem::Interface(id) } + + types::ComponentEntityType::Func(idx) => { + let ty = match self.info.types.type_from_id(*idx) { + Some(types::Type::ComponentFunc(ty)) => ty, + _ => unreachable!(), + }; + let func = self.convert_function(name, ty)?; + WorldItem::Function(func) + } + _ => bail!("component export `{name}` is not an instance"), }; world.exports.insert(name.to_string(), item); diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index a70908b0a0..bb415d7f18 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -52,1067 +52,1089 @@ //! otherwise there's no way to run a `wasi_snapshot_preview1` module within the //! component model. -//use crate::builder::ComponentBuilder; -//use crate::metadata::{self, Bindgen, ModuleMetadata}; -//use crate::{ -// validation::{ValidatedModule, MAIN_MODULE_IMPORT_NAME}, -// StringEncoding, -//}; +use crate::builder::ComponentBuilder; +use crate::metadata::{self, Bindgen, ModuleMetadata}; +use crate::validation::{ValidatedModule, MAIN_MODULE_IMPORT_NAME}; +use crate::StringEncoding; use anyhow::{anyhow, bail, Context, Result}; -//use indexmap::IndexMap; -//use std::collections::HashMap; -//use std::hash::Hash; -//use wasm_encoder::*; -//use wasmparser::{Validator, WasmFeatures}; -//use wit_parser::{ -// abi::{AbiVariant, WasmSignature, WasmType}, -// Document, Function, InterfaceId, Type, TypeDefKind, TypeId, WorldId, -//}; - -//const INDIRECT_TABLE_NAME: &str = "$imports"; +use indexmap::IndexMap; +use std::collections::HashMap; +use std::hash::Hash; +use wasm_encoder::*; +use wasmparser::{Validator, WasmFeatures}; +use wit_parser::{ + abi::{AbiVariant, WasmSignature, WasmType}, + Function, InterfaceId, Resolve, Type, TypeDefKind, TypeId, TypeOwner, WorldId, WorldItem, +}; + +const INDIRECT_TABLE_NAME: &str = "$imports"; mod wit; pub use wit::encode; mod types; -// use types::{InstanceTypeEncoder, RootTypeEncoder, ValtypeEncoder}; -//mod world; -//use world::{ComponentWorld, ImportedInterface}; - -//fn to_val_type(ty: &WasmType) -> ValType { -// match ty { -// WasmType::I32 => ValType::I32, -// WasmType::I64 => ValType::I64, -// WasmType::F32 => ValType::F32, -// WasmType::F64 => ValType::F64, -// } -//} - -//bitflags::bitflags! { -// /// Options in the `canon lower` or `canon lift` required for a particular -// /// function. -// pub struct RequiredOptions: u8 { -// /// A memory must be specified, typically the "main module"'s memory -// /// export. -// const MEMORY = 1 << 0; -// /// A `realloc` function must be specified, typically named -// /// `cabi_realloc`. -// const REALLOC = 1 << 1; -// /// A string encoding must be specified, which is always utf-8 for now -// /// today. -// const STRING_ENCODING = 1 << 2; -// } -//} - -//impl RequiredOptions { -// fn for_import(doc: &Document, func: &Function) -> RequiredOptions { -// let sig = doc.wasm_signature(AbiVariant::GuestImport, func); -// let mut ret = RequiredOptions::empty(); -// // Lift the params and lower the results for imports -// ret.add_lift(TypeContents::for_types( -// doc, -// func.params.iter().map(|(_, t)| t), -// )); -// ret.add_lower(TypeContents::for_types(doc, func.results.iter_types())); - -// // If anything is indirect then `memory` will be required to read the -// // indirect values. -// if sig.retptr || sig.indirect_params { -// ret |= RequiredOptions::MEMORY; -// } -// ret -// } - -// fn for_export(doc: &Document, func: &Function) -> RequiredOptions { -// let sig = doc.wasm_signature(AbiVariant::GuestExport, func); -// let mut ret = RequiredOptions::empty(); -// // Lower the params and lift the results for exports -// ret.add_lower(TypeContents::for_types( -// doc, -// func.params.iter().map(|(_, t)| t), -// )); -// ret.add_lift(TypeContents::for_types(doc, func.results.iter_types())); - -// // If anything is indirect then `memory` will be required to read the -// // indirect values, but if the arguments are indirect then `realloc` is -// // additionally required to allocate space for the parameters. -// if sig.retptr || sig.indirect_params { -// ret |= RequiredOptions::MEMORY; -// if sig.indirect_params { -// ret |= RequiredOptions::REALLOC; -// } -// } -// ret -// } - -// fn add_lower(&mut self, types: TypeContents) { -// // If lists/strings are lowered into wasm then memory is required as -// // usual but `realloc` is also required to allow the external caller to -// // allocate space in the destination for the list/string. -// if types.contains(TypeContents::LIST) { -// *self |= RequiredOptions::MEMORY | RequiredOptions::REALLOC; -// } -// if types.contains(TypeContents::STRING) { -// *self |= RequiredOptions::MEMORY -// | RequiredOptions::STRING_ENCODING -// | RequiredOptions::REALLOC; -// } -// } - -// fn add_lift(&mut self, types: TypeContents) { -// // Unlike for `lower` when lifting a string/list all that's needed is -// // memory, since the string/list already resides in memory `realloc` -// // isn't needed. -// if types.contains(TypeContents::LIST) { -// *self |= RequiredOptions::MEMORY; -// } -// if types.contains(TypeContents::STRING) { -// *self |= RequiredOptions::MEMORY | RequiredOptions::STRING_ENCODING; -// } -// } - -// fn into_iter( -// self, -// encoding: StringEncoding, -// memory_index: Option, -// realloc_index: Option, -// ) -> Result> { -// #[derive(Default)] -// struct Iter { -// options: [Option; 3], -// current: usize, -// count: usize, -// } - -// impl Iter { -// fn push(&mut self, option: CanonicalOption) { -// assert!(self.count < self.options.len()); -// self.options[self.count] = Some(option); -// self.count += 1; -// } -// } - -// impl Iterator for Iter { -// type Item = CanonicalOption; - -// fn next(&mut self) -> Option { -// if self.current == self.count { -// return None; -// } -// let option = self.options[self.current]; -// self.current += 1; -// option -// } - -// fn size_hint(&self) -> (usize, Option) { -// (self.count - self.current, Some(self.count - self.current)) -// } -// } - -// impl ExactSizeIterator for Iter {} - -// let mut iter = Iter::default(); - -// if self.contains(RequiredOptions::MEMORY) { -// iter.push(CanonicalOption::Memory(memory_index.ok_or_else(|| { -// anyhow!("module does not export a memory named `memory`") -// })?)); -// } - -// if self.contains(RequiredOptions::REALLOC) { -// iter.push(CanonicalOption::Realloc(realloc_index.ok_or_else( -// || anyhow!("module does not export a function named `cabi_realloc`"), -// )?)); -// } - -// if self.contains(RequiredOptions::STRING_ENCODING) { -// iter.push(encoding.into()); -// } - -// Ok(iter) -// } -//} - -//bitflags::bitflags! { -// /// Flags about what kinds of types are present within the recursive -// /// structure of a type. -// struct TypeContents: u8 { -// const STRING = 1 << 0; -// const LIST = 1 << 1; -// } -//} - -//impl TypeContents { -// fn for_types<'a>(doc: &Document, types: impl Iterator) -> Self { -// let mut cur = TypeContents::empty(); -// for ty in types { -// cur |= Self::for_type(doc, ty); -// } -// cur -// } - -// fn for_optional_types<'a>( -// doc: &Document, -// types: impl Iterator>, -// ) -> Self { -// Self::for_types(doc, types.flatten()) -// } - -// fn for_optional_type(doc: &Document, ty: Option<&Type>) -> Self { -// match ty { -// Some(ty) => Self::for_type(doc, ty), -// None => Self::empty(), -// } -// } - -// fn for_type(doc: &Document, ty: &Type) -> Self { -// match ty { -// Type::Id(id) => match &doc.types[*id].kind { -// TypeDefKind::Record(r) => Self::for_types(doc, r.fields.iter().map(|f| &f.ty)), -// TypeDefKind::Tuple(t) => Self::for_types(doc, t.types.iter()), -// TypeDefKind::Flags(_) => Self::empty(), -// TypeDefKind::Option(t) => Self::for_type(doc, t), -// TypeDefKind::Result(r) => { -// Self::for_optional_type(doc, r.ok.as_ref()) -// | Self::for_optional_type(doc, r.err.as_ref()) -// } -// TypeDefKind::Variant(v) => { -// Self::for_optional_types(doc, v.cases.iter().map(|c| c.ty.as_ref())) -// } -// TypeDefKind::Union(v) => Self::for_types(doc, v.cases.iter().map(|c| &c.ty)), -// TypeDefKind::Enum(_) => Self::empty(), -// TypeDefKind::List(t) => Self::for_type(doc, t) | Self::LIST, -// TypeDefKind::Type(t) => Self::for_type(doc, t), -// TypeDefKind::Future(_) => todo!("encoding for future"), -// TypeDefKind::Stream(_) => todo!("encoding for stream"), -// }, -// Type::String => Self::STRING, -// _ => Self::empty(), -// } -// } -//} - -///// State relating to encoding a component. -//pub struct EncodingState<'a> { -// /// The component being encoded. -// component: ComponentBuilder, -// /// The index into the core module index space for the inner core module. -// /// -// /// If `None`, the core module has not been encoded. -// module_index: Option, -// /// The index into the core instance index space for the inner core module. -// /// -// /// If `None`, the core module has not been instantiated. -// instance_index: Option, -// /// The index in the core memory index space for the exported memory. -// /// -// /// If `None`, then the memory has not yet been aliased. -// memory_index: Option, -// /// The index in the core function index space for the realloc function. -// /// -// /// If `None`, then the realloc function has not yet been aliased. -// realloc_index: Option, -// /// The index of the shim instance used for lowering imports into the core instance. -// /// -// /// If `None`, then the shim instance how not yet been encoded. -// shim_instance_index: Option, -// /// The index of the fixups module to instantiate to fill in the lowered imports. -// /// -// /// If `None`, then a fixup module has not yet been encoded. -// fixups_module_index: Option, - -// /// A map of named adapter modules and the index that the module was defined -// /// at. -// adapter_modules: IndexMap<&'a str, u32>, -// /// A map of adapter module instances and the index of their instance. -// adapter_instances: IndexMap<&'a str, u32>, -// /// A map of the index of the aliased realloc function for each adapter -// /// module. Note that adapters have two realloc functions, one for imports -// /// and one for exports. -// adapter_import_reallocs: IndexMap<&'a str, Option>, -// adapter_export_reallocs: IndexMap<&'a str, Option>, - -// /// Imported instances and what index they were imported as. -// imported_instances: IndexMap, - -// /// Map of types defined within the component's root index space. -// type_map: HashMap, -// /// Map of function types defined within the component's root index space. -// func_type_map: HashMap, u32>, - -// /// Metadata about the world inferred from the input to `ComponentEncoder`. -// info: &'a ComponentWorld<'a>, -//} - -//impl<'a> EncodingState<'a> { -// fn encode_core_modules(&mut self) { -// assert!(self.module_index.is_none()); -// let idx = self.component.core_module_raw(&self.info.encoder.module); -// self.module_index = Some(idx); - -// for (name, (_, wasm)) in self.info.adapters.iter() { -// let idx = self.component.core_module_raw(wasm); -// let prev = self.adapter_modules.insert(name, idx); -// assert!(prev.is_none()); -// } -// } - -// fn root_type_encoder(&mut self, interface: InterfaceId) -> RootTypeEncoder<'_, 'a> { -// RootTypeEncoder { -// state: self, -// type_exports: Vec::new(), -// interface, -// } -// } - -// fn instance_type_encoder(&mut self, interface: InterfaceId) -> InstanceTypeEncoder<'_, 'a> { -// InstanceTypeEncoder { -// state: self, -// interface, -// type_map: Default::default(), -// func_type_map: Default::default(), -// ty: Default::default(), -// } -// } - -// fn encode_imports(&mut self) -> Result<()> { -// let doc = &self.info.encoder.metadata.doc; -// for (name, info) in self.info.import_map.iter() { -// let interface = &doc.interfaces[info.interface]; -// log::trace!("encoding imports for `{name}` as {:?}", info.interface); -// let ty = { -// let mut encoder = self.instance_type_encoder(info.interface); - -// // Encode all required functions from this imported interface -// // into the instance type. -// for func in interface.functions.iter() { -// if !info.required.contains(func.name.as_str()) { -// continue; -// } -// log::trace!("encoding function type for `{}`", func.name); -// let idx = encoder.encode_func_type(doc, func)?; - -// encoder -// .ty -// .export(&func.name, "", ComponentTypeRef::Func(idx)); -// } - -// // If there were any live types from this instance which weren't -// // otherwise reached through the above function types then this -// // will forward them through. -// if let Some(live) = encoder.state.info.live_types.get(&info.interface) { -// for ty in live { -// log::trace!("encoding extra type {ty:?}"); -// encoder.encode_valtype(doc, &Type::Id(*ty))?; -// } -// } - -// encoder.ty -// }; - -// // Don't encode empty instance types since they're not -// // meaningful to the runtime of the component anyway. -// if ty.is_empty() { -// continue; -// } -// let instance_type_idx = self.component.instance_type(&ty); -// let instance_idx = self.component.import( -// name, -// &info.url, -// ComponentTypeRef::Instance(instance_type_idx), -// ); -// let prev = self.imported_instances.insert(info.interface, instance_idx); -// assert!(prev.is_none()); -// } -// Ok(()) -// } - -// fn index_of_type_export(&mut self, id: TypeId) -> u32 { -// // Using the original `interface` definition of `id` and its name create -// // an alias which refers to the type export of that instance which must -// // have previously been imported. -// let ty = &self.info.encoder.metadata.doc.types[id]; -// let interface = ty -// .interface -// .expect("cannot import anonymous type across interfaces"); -// let name = ty -// .name -// .as_ref() -// .expect("cannot import anonymous type across interfaces"); -// let instance = self.imported_instances[&interface]; -// self.component.alias_type_export(instance, name) -// } - -// fn encode_core_instantiation(&mut self) -> Result<()> { -// let info = self.info.info.as_ref().unwrap(); -// // Encode a shim instantiation if needed -// let shims = self.encode_shim_instantiation(); - -// // For each instance import into the main module create a -// // pseudo-core-wasm-module via a bag-of-exports. -// let mut args = Vec::new(); -// for name in info.required_imports.keys() { -// let index = self.import_instance_to_lowered_core_instance( -// CustomModule::Main, -// *name, -// &shims, -// info.metadata, -// ); -// args.push((*name, ModuleArg::Instance(index))); -// } - -// // For each adapter module instance imported into the core wasm module -// // the appropriate shim is packaged up into a bag-of-exports instance. -// // Note that adapter modules currently don't deal with -// // indirect-vs-direct lowerings, everything is indirect. -// for (adapter, funcs) in info.adapters_required.iter() { -// let shim_instance = self -// .shim_instance_index -// .expect("shim should be instantiated"); -// let mut exports = Vec::new(); - -// for (func, _ty) in funcs { -// let index = self.component.alias_core_item( -// shim_instance, -// ExportKind::Func, -// &shims.shim_names[&ShimKind::Adapter { adapter, func }], -// ); -// exports.push((*func, ExportKind::Func, index)); -// } - -// let index = self.component.instantiate_core_exports(exports); -// args.push((*adapter, ModuleArg::Instance(index))); -// } - -// // Instantiate the main module now that all of its arguments have been -// // prepared. With this we know have the main linear memory for -// // liftings/lowerings later on as well as the adapter modules, if any, -// // instantiated after the core wasm module. -// self.instantiate_core_module(args, info); -// self.instantiate_adapter_modules(&shims); - -// // With all the core wasm instances in play now the original shim -// // module, if present, can be filled in with lowerings/adapters/etc. -// self.encode_indirect_lowerings(shims) -// } - -// /// Lowers a named imported interface a core wasm instances suitable to -// /// provide as an instantiation argument to another core wasm module. -// /// -// /// * `for_module` the module that this instance is being created for, or -// /// otherwise which `realloc` option is used for the lowerings. -// /// * `name` - the name of the imported interface that's being lowered. -// /// * `imports` - the list of all imports known for this encoding. -// /// * `shims` - the indirect/adapter shims created prior, if any. -// fn import_instance_to_lowered_core_instance( -// &mut self, -// for_module: CustomModule<'_>, -// name: &str, -// shims: &Shims<'_>, -// metadata: &ModuleMetadata, -// ) -> u32 { -// let import = &self.info.import_map[name]; -// let instance_index = self.imported_instances[&import.interface]; -// let mut exports = Vec::with_capacity(import.direct.len() + import.indirect.len()); - -// // Add an entry for all indirect lowerings which come as an export of -// // the shim module. -// for (i, lowering) in import.indirect.iter().enumerate() { -// let encoding = -// metadata.import_encodings[&(name.to_string(), lowering.name.to_string())]; -// let index = self.component.alias_core_item( -// self.shim_instance_index -// .expect("shim should be instantiated"), -// ExportKind::Func, -// &shims.shim_names[&ShimKind::IndirectLowering { -// interface: name, -// indirect_index: i, -// realloc: for_module, -// encoding, -// }], -// ); -// exports.push((lowering.name, ExportKind::Func, index)); -// } - -// // All direct lowerings can be `canon lower`'d here immediately and -// // passed as arguments. -// for lowering in &import.direct { -// let func_index = self.component.alias_func(instance_index, lowering.name); -// let core_func_index = self.component.lower_func(func_index, []); -// exports.push((lowering.name, ExportKind::Func, core_func_index)); -// } - -// self.component.instantiate_core_exports(exports) -// } - -// fn encode_exports(&mut self, opts: &'a ComponentEncoder, module: CustomModule) -> Result<()> { -// let doc = &opts.metadata.doc; -// let metadata = match module { -// CustomModule::Main => &opts.metadata.metadata, -// CustomModule::Adapter(name) => &opts.adapters[name].1, -// }; -// let world = match module { -// CustomModule::Main => opts.metadata.world, -// CustomModule::Adapter(name) => opts.adapters[name].2, -// }; -// let world = &doc.worlds[world]; -// for (export, export_name) in world.exports() { -// let mut interface_exports = Vec::new(); - -// // All types are encoded into the root component here using this -// // encoder which keeps track of type exports to determine how to -// // export them later as well. -// let mut enc = self.root_type_encoder(export); -// for func in &doc.interfaces[export].functions { -// let name = func.core_export_name(export_name); -// let instance_index = match module { -// CustomModule::Main => enc.state.instance_index.expect("instantiated by now"), -// CustomModule::Adapter(name) => enc.state.adapter_instances[name], -// }; -// let core_func_index = enc.state.component.alias_core_item( -// instance_index, -// ExportKind::Func, -// name.as_ref(), -// ); - -// let ty = enc.encode_func_type(doc, func)?; -// let options = RequiredOptions::for_export(doc, func); - -// let encoding = metadata.export_encodings[&name[..]]; -// // TODO: This realloc detection should probably be improved with -// // some sort of scheme to have per-function reallocs like -// // `cabi_realloc_{name}` or something like that. -// let realloc_index = match module { -// CustomModule::Main => enc.state.realloc_index, -// CustomModule::Adapter(name) => enc.state.adapter_export_reallocs[name], -// }; -// let mut options = options -// .into_iter(encoding, enc.state.memory_index, realloc_index)? -// .collect::>(); - -// // TODO: This should probe for the existence of -// // `cabi_post_{name}` but not require its existence. -// if doc.guest_export_needs_post_return(func) { -// let post_return = enc.state.component.alias_core_item( -// instance_index, -// ExportKind::Func, -// &format!("cabi_post_{name}"), -// ); -// options.push(CanonicalOption::PostReturn(post_return)); -// } -// let func_index = enc.state.component.lift_func(core_func_index, ty, options); -// interface_exports.push((func.name.as_str(), ComponentExportKind::Func, func_index)); -// } - -// // Extend the interface exports to be created with type exports -// // found during encoding of function types. -// interface_exports.extend( -// enc.type_exports -// .into_iter() -// .map(|(idx, name)| (name, ComponentExportKind::Type, idx)), -// ); - -// if interface_exports.is_empty() { -// continue; -// } - -// // The default exported interface has all of its items exported -// // directly but otherwise an instance type is created and then -// // exported. -// match export_name { -// Some(export_name) => { -// if export_name.is_empty() { -// bail!("cannot export an unnamed interface"); -// } - -// let instance_index = self.component.instantiate_exports(interface_exports); -// self.component.export( -// export_name, -// doc.interfaces[export].url.as_deref().unwrap_or(""), -// ComponentExportKind::Instance, -// instance_index, -// ); -// } -// None => { -// for (name, kind, idx) in interface_exports { -// self.component.export(name, "", kind, idx); -// } -// } -// } -// } - -// Ok(()) -// } - -// fn encode_shim_instantiation(&mut self) -> Shims<'a> { -// let mut signatures = Vec::new(); -// let mut ret = Shims::default(); -// let info = self.info.info.as_ref().unwrap(); - -// // For all interfaces imported into the main module record all of their -// // indirect lowerings into `Shims`. -// for name in info.required_imports.keys() { -// let import = &self.info.import_map[name]; -// ret.append_indirect( -// name, -// CustomModule::Main, -// import, -// info.metadata, -// &mut signatures, -// ); -// } - -// // For all required adapter modules a shim is created for each required -// // function and additionally a set of shims are created for the -// // interface imported into the shim module itself. -// for (adapter, funcs) in info.adapters_required.iter() { -// let (info, _wasm) = &self.info.adapters[adapter]; -// for (name, _) in info.required_imports.iter() { -// let import = &self.info.import_map[name]; -// ret.append_indirect( -// name, -// CustomModule::Adapter(adapter), -// import, -// info.metadata, -// &mut signatures, -// ); -// } -// for (func, ty) in funcs { -// let name = ret.list.len().to_string(); -// log::debug!("shim {name} is adapter `{adapter}::{func}`"); -// signatures.push(WasmSignature { -// params: ty.params().iter().map(to_wasm_type).collect(), -// results: ty.results().iter().map(to_wasm_type).collect(), -// indirect_params: false, -// retptr: false, -// }); -// ret.list.push(Shim { -// name, -// debug_name: format!("adapt-{adapter}-{func}"), -// // Pessimistically assume that all adapters require memory -// // in one form or another. While this isn't technically true -// // it's true enough for WASI. -// options: RequiredOptions::MEMORY, -// kind: ShimKind::Adapter { adapter, func }, -// }); -// } -// } -// if ret.list.is_empty() { -// return ret; -// } - -// for shim in ret.list.iter() { -// ret.shim_names.insert(shim.kind, shim.name.clone()); -// } - -// assert!(self.shim_instance_index.is_none()); -// assert!(self.fixups_module_index.is_none()); - -// // This function encodes two modules: -// // - A shim module that defines a table and exports functions -// // that indirectly call through the table. -// // - A fixup module that imports that table and a set of functions -// // and populates the imported table via active element segments. The -// // fixup module is used to populate the shim's table once the -// // imported functions have been lowered. - -// let mut types = TypeSection::new(); -// let mut tables = TableSection::new(); -// let mut functions = FunctionSection::new(); -// let mut exports = ExportSection::new(); -// let mut code = CodeSection::new(); -// let mut sigs = IndexMap::new(); -// let mut imports_section = ImportSection::new(); -// let mut elements = ElementSection::new(); -// let mut func_indexes = Vec::new(); -// let mut func_names = NameMap::new(); - -// for (i, (sig, shim)) in signatures.iter().zip(&ret.list).enumerate() { -// let i = i as u32; -// let type_index = *sigs.entry(sig).or_insert_with(|| { -// let index = types.len(); -// types.function( -// sig.params.iter().map(to_val_type), -// sig.results.iter().map(to_val_type), -// ); -// index -// }); - -// functions.function(type_index); -// Self::encode_shim_function(type_index, i, &mut code, sig.params.len() as u32); -// exports.export(&shim.name, ExportKind::Func, i); - -// imports_section.import("", &shim.name, EntityType::Function(type_index)); -// func_indexes.push(i); -// func_names.append(i, &shim.debug_name); -// } -// let mut names = NameSection::new(); -// names.functions(&func_names); - -// let table_type = TableType { -// element_type: ValType::FuncRef, -// minimum: signatures.len() as u32, -// maximum: Some(signatures.len() as u32), -// }; - -// tables.table(table_type); - -// exports.export(INDIRECT_TABLE_NAME, ExportKind::Table, 0); -// imports_section.import("", INDIRECT_TABLE_NAME, table_type); - -// elements.active( -// None, -// &ConstExpr::i32_const(0), -// ValType::FuncRef, -// Elements::Functions(&func_indexes), -// ); - -// let mut shim = Module::new(); -// shim.section(&types); -// shim.section(&functions); -// shim.section(&tables); -// shim.section(&exports); -// shim.section(&code); -// shim.section(&names); - -// let mut fixups = Module::default(); -// fixups.section(&types); -// fixups.section(&imports_section); -// fixups.section(&elements); - -// let shim_module_index = self.component.core_module(&shim); -// self.fixups_module_index = Some(self.component.core_module(&fixups)); -// self.shim_instance_index = Some(self.component.instantiate(shim_module_index, [])); - -// return ret; - -// fn to_wasm_type(ty: &wasmparser::ValType) -> WasmType { -// match ty { -// wasmparser::ValType::I32 => WasmType::I32, -// wasmparser::ValType::I64 => WasmType::I64, -// wasmparser::ValType::F32 => WasmType::F32, -// wasmparser::ValType::F64 => WasmType::F64, -// _ => unreachable!(), -// } -// } -// } - -// fn encode_shim_function( -// type_index: u32, -// func_index: u32, -// code: &mut CodeSection, -// param_count: u32, -// ) { -// let mut func = wasm_encoder::Function::new(std::iter::empty()); -// for i in 0..param_count { -// func.instruction(&Instruction::LocalGet(i)); -// } -// func.instruction(&Instruction::I32Const(func_index as i32)); -// func.instruction(&Instruction::CallIndirect { -// ty: type_index, -// table: 0, -// }); -// func.instruction(&Instruction::End); -// code.function(&func); -// } - -// fn encode_indirect_lowerings(&mut self, shims: Shims<'_>) -> Result<()> { -// if shims.list.is_empty() { -// return Ok(()); -// } - -// let shim_instance_index = self -// .shim_instance_index -// .expect("must have an instantiated shim"); - -// let table_index = self.component.alias_core_item( -// shim_instance_index, -// ExportKind::Table, -// INDIRECT_TABLE_NAME, -// ); - -// let mut exports = Vec::new(); -// exports.push((INDIRECT_TABLE_NAME, ExportKind::Table, table_index)); - -// for shim in shims.list.iter() { -// let core_func_index = match &shim.kind { -// // Indirect lowerings are a `canon lower`'d function with -// // options specified from a previously instantiated instance. -// // This previous instance could either be the main module or an -// // adapter module, which affects the `realloc` option here. -// // Currently only one linear memory is supported so the linear -// // memory always comes from the main module. -// ShimKind::IndirectLowering { -// interface, -// indirect_index, -// realloc, -// encoding, -// } => { -// let interface = &self.info.import_map[interface]; -// let instance_index = self.imported_instances[&interface.interface]; -// let func_index = self -// .component -// .alias_func(instance_index, interface.indirect[*indirect_index].name); - -// let realloc = match realloc { -// CustomModule::Main => self.realloc_index, -// CustomModule::Adapter(name) => self.adapter_import_reallocs[name], -// }; - -// self.component.lower_func( -// func_index, -// shim.options -// .into_iter(*encoding, self.memory_index, realloc)?, -// ) -// } - -// // Adapter shims are defined by an export from and adapter -// // instance, so use the specified name here and the previously -// // created instances to get the core item that represents the -// // shim. -// ShimKind::Adapter { adapter, func } => self.component.alias_core_item( -// self.adapter_instances[adapter], -// ExportKind::Func, -// func, -// ), -// }; - -// exports.push((shim.name.as_str(), ExportKind::Func, core_func_index)); -// } - -// let instance_index = self.component.instantiate_core_exports(exports); -// self.component.instantiate( -// self.fixups_module_index.expect("must have fixup module"), -// [("", ModuleArg::Instance(instance_index))], -// ); -// Ok(()) -// } - -// fn instantiate_core_module<'b, A>(&mut self, args: A, info: &ValidatedModule<'_>) -// where -// A: IntoIterator, -// A::IntoIter: ExactSizeIterator, -// { -// assert!(self.instance_index.is_none()); - -// let instance_index = self -// .component -// .instantiate(self.module_index.expect("core module encoded"), args); - -// if info.has_memory { -// self.memory_index = Some(self.component.alias_core_item( -// instance_index, -// ExportKind::Memory, -// "memory", -// )); -// } - -// if let Some(name) = &info.realloc { -// self.realloc_index = Some(self.component.alias_core_item( -// instance_index, -// ExportKind::Func, -// name, -// )); -// } - -// self.instance_index = Some(instance_index); -// } - -// /// This function will instantiate all required adapter modules required by -// /// the main module (specified by `info`). -// /// -// /// Each adapter here is instantiated with its required imported interface, -// /// if any. -// fn instantiate_adapter_modules(&mut self, shims: &Shims<'_>) { -// for (name, (info, _wasm)) in self.info.adapters.iter() { -// let mut args = Vec::new(); - -// let mut core_exports = Vec::new(); -// for export_name in info.needs_core_exports.iter() { -// let index = self.component.alias_core_item( -// self.instance_index -// .expect("adaptee index set at this point"), -// ExportKind::Func, -// export_name, -// ); -// core_exports.push((export_name.as_str(), ExportKind::Func, index)); -// } -// if !core_exports.is_empty() { -// let instance = self.component.instantiate_core_exports(core_exports); -// args.push((MAIN_MODULE_IMPORT_NAME, ModuleArg::Instance(instance))); -// } -// // If the adapter module requires a `memory` import then specify -// // that here. For now assume that the module name of the memory is -// // different from the imported interface. That's true enough for now -// // since it's `env::memory`. -// if let Some((module, name)) = &info.needs_memory { -// for (import_name, _) in info.required_imports.iter() { -// assert!(module != import_name); -// } -// assert!(module != name); -// let memory = self.memory_index.unwrap(); -// let instance = self.component.instantiate_core_exports([( -// name.as_str(), -// ExportKind::Memory, -// memory, -// )]); -// args.push((module.as_str(), ModuleArg::Instance(instance))); -// } -// for (import_name, _) in info.required_imports.iter() { -// let instance = self.import_instance_to_lowered_core_instance( -// CustomModule::Adapter(name), -// import_name, -// shims, -// info.metadata, -// ); -// args.push((import_name, ModuleArg::Instance(instance))); -// } -// let instance = self.component.instantiate(self.adapter_modules[name], args); -// self.adapter_instances.insert(name, instance); - -// let realloc = info.export_realloc.as_ref().map(|name| { -// self.component -// .alias_core_item(instance, ExportKind::Func, name) -// }); -// self.adapter_export_reallocs.insert(name, realloc); -// let realloc = info.import_realloc.as_ref().map(|name| { -// self.component -// .alias_core_item(instance, ExportKind::Func, name) -// }); -// self.adapter_import_reallocs.insert(name, realloc); -// } -// } -//} - -///// A list of "shims" which start out during the component instantiation process -///// as functions which immediately trap due to a `call_indirect`-to-`null` but -///// will get filled in by the time the component instantiation process -///// completes. -///// -///// Shims currently include: -///// -///// * "Indirect functions" lowered from imported instances where the lowering -///// requires an item exported from the main module. These are indirect due to -///// the circular dependency between the module needing an import and the -///// import needing the module. -///// -///// * Adapter modules which convert from a historical ABI to the component -///// model's ABI (e.g. wasi preview1 to preview2) get a shim since the adapters -///// are currently indicated as always requiring the memory of the main module. -///// -///// This structure is created by `encode_shim_instantiation`. -//#[derive(Default)] -//struct Shims<'a> { -// /// The list of all shims that a module will require. -// list: Vec>, - -// /// A map from a shim to the name of the shim in the shim instance. -// shim_names: IndexMap, String>, -//} - -//struct Shim<'a> { -// /// Canonical ABI options required by this shim, used during `canon lower` -// /// operations. -// options: RequiredOptions, - -// /// The name, in the shim instance, of this shim. -// /// -// /// Currently this is `"0"`, `"1"`, ... -// name: String, - -// /// A human-readable debugging name for this shim, used in a core wasm -// /// `name` section. -// debug_name: String, - -// /// Precise information about what this shim is a lowering of. -// kind: ShimKind<'a>, -//} - -//#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] -//enum ShimKind<'a> { -// /// This shim is a late indirect lowering of an imported function in a -// /// component which is only possible after prior core wasm modules are -// /// instantiated so their memories and functions are available. -// IndirectLowering { -// /// The name of the interface that's being lowered. -// interface: &'a str, -// /// The index within the `indirect` array of the function being lowered. -// indirect_index: usize, -// /// Which instance to pull the `realloc` function from, if necessary. -// realloc: CustomModule<'a>, -// /// The string encoding that this lowering is going to use. -// encoding: StringEncoding, -// }, -// /// This shim is a core wasm function defined in an adapter module but isn't -// /// available until the adapter module is itself instantiated. -// Adapter { -// /// The name of the adapter module this shim comes from. -// adapter: &'a str, -// /// The name of the export in the adapter module this shim points to. -// func: &'a str, -// }, -//} - -///// Indicator for which module is being used for a lowering or where options -///// like `realloc` are drawn from. -///// -///// This is necessary for situations such as an imported function being lowered -///// into the main module and additionally into an adapter module. For example an -///// adapter might adapt from preview1 to preview2 for the standard library of a -///// programming language but the main module's custom application code may also -///// explicitly import from preview2. These two different lowerings of a preview2 -///// function are parameterized by this enumeration. -//#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] -//enum CustomModule<'a> { -// /// This points to the "main module" which is generally the "output of LLVM" -// /// or what a user wrote. -// Main, -// /// This is selecting an adapter module, identified by name here, where -// /// something is being lowered into. -// Adapter(&'a str), -//} - -//impl<'a> Shims<'a> { -// /// Adds all shims necessary for the `import` provided, namely iterating -// /// over its indirect lowerings and appending a shim per lowering. -// fn append_indirect( -// &mut self, -// name: &'a str, -// for_module: CustomModule<'a>, -// import: &ImportedInterface<'a>, -// metadata: &ModuleMetadata, -// sigs: &mut Vec, -// ) { -// for (indirect_index, lowering) in import.indirect.iter().enumerate() { -// let shim_name = self.list.len().to_string(); -// log::debug!( -// "shim {shim_name} is import `{name}` lowering {indirect_index} `{}`", -// lowering.name -// ); -// sigs.push(lowering.sig.clone()); -// let encoding = -// metadata.import_encodings[&(name.to_string(), lowering.name.to_string())]; -// self.list.push(Shim { -// name: shim_name, -// debug_name: format!("indirect-{name}-{}", lowering.name), -// options: lowering.options, -// kind: ShimKind::IndirectLowering { -// interface: name, -// indirect_index, -// realloc: for_module, -// encoding, -// }, -// }); -// } -// } -//} +use types::{InstanceTypeEncoder, RootTypeEncoder, ValtypeEncoder}; +mod world; +use world::{ComponentWorld, ImportedInterface}; + +fn to_val_type(ty: &WasmType) -> ValType { + match ty { + WasmType::I32 => ValType::I32, + WasmType::I64 => ValType::I64, + WasmType::F32 => ValType::F32, + WasmType::F64 => ValType::F64, + } +} + +bitflags::bitflags! { + /// Options in the `canon lower` or `canon lift` required for a particular + /// function. + pub struct RequiredOptions: u8 { + /// A memory must be specified, typically the "main module"'s memory + /// export. + const MEMORY = 1 << 0; + /// A `realloc` function must be specified, typically named + /// `cabi_realloc`. + const REALLOC = 1 << 1; + /// A string encoding must be specified, which is always utf-8 for now + /// today. + const STRING_ENCODING = 1 << 2; + } +} + +impl RequiredOptions { + fn for_import(resolve: &Resolve, func: &Function) -> RequiredOptions { + let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); + let mut ret = RequiredOptions::empty(); + // Lift the params and lower the results for imports + ret.add_lift(TypeContents::for_types( + resolve, + func.params.iter().map(|(_, t)| t), + )); + ret.add_lower(TypeContents::for_types(resolve, func.results.iter_types())); + + // If anything is indirect then `memory` will be required to read the + // indirect values. + if sig.retptr || sig.indirect_params { + ret |= RequiredOptions::MEMORY; + } + ret + } + + fn for_export(resolve: &Resolve, func: &Function) -> RequiredOptions { + let sig = resolve.wasm_signature(AbiVariant::GuestExport, func); + let mut ret = RequiredOptions::empty(); + // Lower the params and lift the results for exports + ret.add_lower(TypeContents::for_types( + resolve, + func.params.iter().map(|(_, t)| t), + )); + ret.add_lift(TypeContents::for_types(resolve, func.results.iter_types())); + + // If anything is indirect then `memory` will be required to read the + // indirect values, but if the arguments are indirect then `realloc` is + // additionally required to allocate space for the parameters. + if sig.retptr || sig.indirect_params { + ret |= RequiredOptions::MEMORY; + if sig.indirect_params { + ret |= RequiredOptions::REALLOC; + } + } + ret + } + + fn add_lower(&mut self, types: TypeContents) { + // If lists/strings are lowered into wasm then memory is required as + // usual but `realloc` is also required to allow the external caller to + // allocate space in the destination for the list/string. + if types.contains(TypeContents::LIST) { + *self |= RequiredOptions::MEMORY | RequiredOptions::REALLOC; + } + if types.contains(TypeContents::STRING) { + *self |= RequiredOptions::MEMORY + | RequiredOptions::STRING_ENCODING + | RequiredOptions::REALLOC; + } + } + + fn add_lift(&mut self, types: TypeContents) { + // Unlike for `lower` when lifting a string/list all that's needed is + // memory, since the string/list already resides in memory `realloc` + // isn't needed. + if types.contains(TypeContents::LIST) { + *self |= RequiredOptions::MEMORY; + } + if types.contains(TypeContents::STRING) { + *self |= RequiredOptions::MEMORY | RequiredOptions::STRING_ENCODING; + } + } + + fn into_iter( + self, + encoding: StringEncoding, + memory_index: Option, + realloc_index: Option, + ) -> Result> { + #[derive(Default)] + struct Iter { + options: [Option; 3], + current: usize, + count: usize, + } + + impl Iter { + fn push(&mut self, option: CanonicalOption) { + assert!(self.count < self.options.len()); + self.options[self.count] = Some(option); + self.count += 1; + } + } + + impl Iterator for Iter { + type Item = CanonicalOption; + + fn next(&mut self) -> Option { + if self.current == self.count { + return None; + } + let option = self.options[self.current]; + self.current += 1; + option + } + + fn size_hint(&self) -> (usize, Option) { + (self.count - self.current, Some(self.count - self.current)) + } + } + + impl ExactSizeIterator for Iter {} + + let mut iter = Iter::default(); + + if self.contains(RequiredOptions::MEMORY) { + iter.push(CanonicalOption::Memory(memory_index.ok_or_else(|| { + anyhow!("module does not export a memory named `memory`") + })?)); + } + + if self.contains(RequiredOptions::REALLOC) { + iter.push(CanonicalOption::Realloc(realloc_index.ok_or_else( + || anyhow!("module does not export a function named `cabi_realloc`"), + )?)); + } + + if self.contains(RequiredOptions::STRING_ENCODING) { + iter.push(encoding.into()); + } + + Ok(iter) + } +} + +bitflags::bitflags! { + /// Flags about what kinds of types are present within the recursive + /// structure of a type. + struct TypeContents: u8 { + const STRING = 1 << 0; + const LIST = 1 << 1; + } +} + +impl TypeContents { + fn for_types<'a>(resolve: &Resolve, types: impl Iterator) -> Self { + let mut cur = TypeContents::empty(); + for ty in types { + cur |= Self::for_type(resolve, ty); + } + cur + } + + fn for_optional_types<'a>( + resolve: &Resolve, + types: impl Iterator>, + ) -> Self { + Self::for_types(resolve, types.flatten()) + } + + fn for_optional_type(resolve: &Resolve, ty: Option<&Type>) -> Self { + match ty { + Some(ty) => Self::for_type(resolve, ty), + None => Self::empty(), + } + } + + fn for_type(resolve: &Resolve, ty: &Type) -> Self { + match ty { + Type::Id(id) => match &resolve.types[*id].kind { + TypeDefKind::Record(r) => Self::for_types(resolve, r.fields.iter().map(|f| &f.ty)), + TypeDefKind::Tuple(t) => Self::for_types(resolve, t.types.iter()), + TypeDefKind::Flags(_) => Self::empty(), + TypeDefKind::Option(t) => Self::for_type(resolve, t), + TypeDefKind::Result(r) => { + Self::for_optional_type(resolve, r.ok.as_ref()) + | Self::for_optional_type(resolve, r.err.as_ref()) + } + TypeDefKind::Variant(v) => { + Self::for_optional_types(resolve, v.cases.iter().map(|c| c.ty.as_ref())) + } + TypeDefKind::Union(v) => Self::for_types(resolve, v.cases.iter().map(|c| &c.ty)), + TypeDefKind::Enum(_) => Self::empty(), + TypeDefKind::List(t) => Self::for_type(resolve, t) | Self::LIST, + TypeDefKind::Type(t) => Self::for_type(resolve, t), + TypeDefKind::Future(_) => todo!("encoding for future"), + TypeDefKind::Stream(_) => todo!("encoding for stream"), + TypeDefKind::Unknown => unreachable!(), + }, + Type::String => Self::STRING, + _ => Self::empty(), + } + } +} + +/// State relating to encoding a component. +pub struct EncodingState<'a> { + /// The component being encoded. + component: ComponentBuilder, + /// The index into the core module index space for the inner core module. + /// + /// If `None`, the core module has not been encoded. + module_index: Option, + /// The index into the core instance index space for the inner core module. + /// + /// If `None`, the core module has not been instantiated. + instance_index: Option, + /// The index in the core memory index space for the exported memory. + /// + /// If `None`, then the memory has not yet been aliased. + memory_index: Option, + /// The index in the core function index space for the realloc function. + /// + /// If `None`, then the realloc function has not yet been aliased. + realloc_index: Option, + /// The index of the shim instance used for lowering imports into the core instance. + /// + /// If `None`, then the shim instance how not yet been encoded. + shim_instance_index: Option, + /// The index of the fixups module to instantiate to fill in the lowered imports. + /// + /// If `None`, then a fixup module has not yet been encoded. + fixups_module_index: Option, + + /// A map of named adapter modules and the index that the module was defined + /// at. + adapter_modules: IndexMap<&'a str, u32>, + /// A map of adapter module instances and the index of their instance. + adapter_instances: IndexMap<&'a str, u32>, + /// A map of the index of the aliased realloc function for each adapter + /// module. Note that adapters have two realloc functions, one for imports + /// and one for exports. + adapter_import_reallocs: IndexMap<&'a str, Option>, + adapter_export_reallocs: IndexMap<&'a str, Option>, + + /// Imported instances and what index they were imported as. + imported_instances: IndexMap, + + /// Map of types defined within the component's root index space. + type_map: HashMap, + /// Map of function types defined within the component's root index space. + func_type_map: HashMap, u32>, + + /// Metadata about the world inferred from the input to `ComponentEncoder`. + info: &'a ComponentWorld<'a>, +} + +impl<'a> EncodingState<'a> { + fn encode_core_modules(&mut self) { + assert!(self.module_index.is_none()); + let idx = self.component.core_module_raw(&self.info.encoder.module); + self.module_index = Some(idx); + + for (name, (_, wasm)) in self.info.adapters.iter() { + let idx = self.component.core_module_raw(wasm); + let prev = self.adapter_modules.insert(name, idx); + assert!(prev.is_none()); + } + } + + fn root_type_encoder(&mut self, interface: Option) -> RootTypeEncoder<'_, 'a> { + RootTypeEncoder { + state: self, + type_exports: Vec::new(), + interface, + } + } + + fn instance_type_encoder(&mut self, interface: InterfaceId) -> InstanceTypeEncoder<'_, 'a> { + InstanceTypeEncoder { + state: self, + interface, + type_map: Default::default(), + func_type_map: Default::default(), + ty: Default::default(), + } + } + + fn encode_imports(&mut self) -> Result<()> { + let resolve = &self.info.encoder.metadata.resolve; + for (name, info) in self.info.import_map.iter() { + let name = match name { + Some(name) => name, + None => unimplemented!("top-level imports"), + }; + let (interface_id, url) = info.interface.unwrap(); + let interface = &resolve.interfaces[interface_id]; + log::trace!("encoding imports for `{name}` as {:?}", interface_id); + let ty = { + let mut encoder = self.instance_type_encoder(interface_id); + + // Encode all required functions from this imported interface + // into the instance type. + for (_, func) in interface.functions.iter() { + if !info.required.contains(func.name.as_str()) { + continue; + } + log::trace!("encoding function type for `{}`", func.name); + let idx = encoder.encode_func_type(resolve, func)?; + + encoder + .ty + .export(&func.name, "", ComponentTypeRef::Func(idx)); + } + + // If there were any live types from this instance which weren't + // otherwise reached through the above function types then this + // will forward them through. + if let Some(live) = encoder.state.info.live_types.get(&interface_id) { + for ty in live { + log::trace!("encoding extra type {ty:?}"); + encoder.encode_valtype(resolve, &Type::Id(*ty))?; + } + } + + encoder.ty + }; + + // Don't encode empty instance types since they're not + // meaningful to the runtime of the component anyway. + if ty.is_empty() { + continue; + } + let instance_type_idx = self.component.instance_type(&ty); + let instance_idx = + self.component + .import(name, url, ComponentTypeRef::Instance(instance_type_idx)); + let prev = self.imported_instances.insert(interface_id, instance_idx); + assert!(prev.is_none()); + } + Ok(()) + } + + fn index_of_type_export(&mut self, id: TypeId) -> u32 { + // Using the original `interface` definition of `id` and its name create + // an alias which refers to the type export of that instance which must + // have previously been imported. + let ty = &self.info.encoder.metadata.resolve.types[id]; + let interface = match ty.owner { + TypeOwner::Interface(id) => id, + _ => panic!("cannot import anonymous type across interfaces"), + }; + let name = ty + .name + .as_ref() + .expect("cannot import anonymous type across interfaces"); + let instance = self.imported_instances[&interface]; + self.component.alias_type_export(instance, name) + } + + fn encode_core_instantiation(&mut self) -> Result<()> { + let info = &self.info.info; + // Encode a shim instantiation if needed + let shims = self.encode_shim_instantiation(); + + // For each instance import into the main module create a + // pseudo-core-wasm-module via a bag-of-exports. + let mut args = Vec::new(); + for name in info.required_imports.keys() { + let index = self.import_instance_to_lowered_core_instance( + CustomModule::Main, + *name, + &shims, + info.metadata, + ); + args.push((*name, ModuleArg::Instance(index))); + } + + // For each adapter module instance imported into the core wasm module + // the appropriate shim is packaged up into a bag-of-exports instance. + // Note that adapter modules currently don't deal with + // indirect-vs-direct lowerings, everything is indirect. + for (adapter, funcs) in info.adapters_required.iter() { + let shim_instance = self + .shim_instance_index + .expect("shim should be instantiated"); + let mut exports = Vec::new(); + + for (func, _ty) in funcs { + let index = self.component.alias_core_item( + shim_instance, + ExportKind::Func, + &shims.shim_names[&ShimKind::Adapter { adapter, func }], + ); + exports.push((*func, ExportKind::Func, index)); + } + + let index = self.component.instantiate_core_exports(exports); + args.push((*adapter, ModuleArg::Instance(index))); + } + + // Instantiate the main module now that all of its arguments have been + // prepared. With this we know have the main linear memory for + // liftings/lowerings later on as well as the adapter modules, if any, + // instantiated after the core wasm module. + self.instantiate_core_module(args, info); + self.instantiate_adapter_modules(&shims); + + // With all the core wasm instances in play now the original shim + // module, if present, can be filled in with lowerings/adapters/etc. + self.encode_indirect_lowerings(shims) + } + + /// Lowers a named imported interface a core wasm instances suitable to + /// provide as an instantiation argument to another core wasm module. + /// + /// * `for_module` the module that this instance is being created for, or + /// otherwise which `realloc` option is used for the lowerings. + /// * `name` - the name of the imported interface that's being lowered. + /// * `imports` - the list of all imports known for this encoding. + /// * `shims` - the indirect/adapter shims created prior, if any. + fn import_instance_to_lowered_core_instance( + &mut self, + for_module: CustomModule<'_>, + name: &str, + shims: &Shims<'_>, + metadata: &ModuleMetadata, + ) -> u32 { + let import = &self.info.import_map[&Some(name)]; + let (interface, _url) = import.interface.unwrap(); + let instance_index = self.imported_instances[&interface]; + let mut exports = Vec::with_capacity(import.direct.len() + import.indirect.len()); + + // Add an entry for all indirect lowerings which come as an export of + // the shim module. + for (i, lowering) in import.indirect.iter().enumerate() { + let encoding = + metadata.import_encodings[&(name.to_string(), lowering.name.to_string())]; + let index = self.component.alias_core_item( + self.shim_instance_index + .expect("shim should be instantiated"), + ExportKind::Func, + &shims.shim_names[&ShimKind::IndirectLowering { + interface: name, + indirect_index: i, + realloc: for_module, + encoding, + }], + ); + exports.push((lowering.name, ExportKind::Func, index)); + } + + // All direct lowerings can be `canon lower`'d here immediately and + // passed as arguments. + for lowering in &import.direct { + let func_index = self.component.alias_func(instance_index, lowering.name); + let core_func_index = self.component.lower_func(func_index, []); + exports.push((lowering.name, ExportKind::Func, core_func_index)); + } + + self.component.instantiate_core_exports(exports) + } + + fn encode_exports(&mut self, opts: &'a ComponentEncoder, module: CustomModule) -> Result<()> { + let resolve = &opts.metadata.resolve; + let world = match module { + CustomModule::Main => opts.metadata.world, + CustomModule::Adapter(name) => opts.adapters[name].2, + }; + let world = &resolve.worlds[world]; + for (export_name, export) in world.exports.iter() { + match export { + WorldItem::Function(func) => { + let mut enc = self.root_type_encoder(None); + let ty = enc.encode_func_type(resolve, func)?; + for (idx, name) in enc.type_exports { + self.component + .export(name, "", ComponentExportKind::Type, idx); + } + let core_name = func.core_export_name(None); + let idx = self.encode_lift(opts, module, &core_name, func, ty)?; + self.component + .export(export_name, "", ComponentExportKind::Func, idx); + } + WorldItem::Interface(export) => { + let mut interface_exports = Vec::new(); + + // All types are encoded into the root component here using this + // encoder which keeps track of type exports to determine how to + // export them later as well. + let mut enc = self.root_type_encoder(Some(*export)); + for (_, func) in &resolve.interfaces[*export].functions { + let core_name = func.core_export_name(Some(export_name)); + let ty = enc.encode_func_type(resolve, func)?; + let func_index = + enc.state.encode_lift(opts, module, &core_name, func, ty)?; + interface_exports.push(( + func.name.as_str(), + ComponentExportKind::Func, + func_index, + )); + } + + // Extend the interface exports to be created with type exports + // found during encoding of function types. + interface_exports.extend( + enc.type_exports + .into_iter() + .map(|(idx, name)| (name, ComponentExportKind::Type, idx)), + ); + + if export_name.is_empty() { + bail!("cannot export an unnamed interface"); + } + + let instance_index = self.component.instantiate_exports(interface_exports); + self.component.export( + export_name, + resolve.interfaces[*export].url.as_deref().unwrap_or(""), + ComponentExportKind::Instance, + instance_index, + ); + } + } + } + + Ok(()) + } + + fn encode_lift( + &mut self, + opts: &'a ComponentEncoder, + module: CustomModule<'_>, + core_name: &str, + func: &Function, + ty: u32, + ) -> Result { + let resolve = &opts.metadata.resolve; + let metadata = match module { + CustomModule::Main => &opts.metadata.metadata, + CustomModule::Adapter(name) => &opts.adapters[name].1, + }; + let instance_index = match module { + CustomModule::Main => self.instance_index.expect("instantiated by now"), + CustomModule::Adapter(name) => self.adapter_instances[name], + }; + let core_func_index = + self.component + .alias_core_item(instance_index, ExportKind::Func, core_name); + + let options = RequiredOptions::for_export(resolve, func); + + let encoding = metadata.export_encodings[core_name]; + // TODO: This realloc detection should probably be improved with + // some sort of scheme to have per-function reallocs like + // `cabi_realloc_{name}` or something like that. + let realloc_index = match module { + CustomModule::Main => self.realloc_index, + CustomModule::Adapter(name) => self.adapter_export_reallocs[name], + }; + let mut options = options + .into_iter(encoding, self.memory_index, realloc_index)? + .collect::>(); + + // TODO: This should probe for the existence of + // `cabi_post_{name}` but not require its existence. + if resolve.guest_export_needs_post_return(func) { + let post_return = self.component.alias_core_item( + instance_index, + ExportKind::Func, + &format!("cabi_post_{core_name}"), + ); + options.push(CanonicalOption::PostReturn(post_return)); + } + let func_index = self.component.lift_func(core_func_index, ty, options); + Ok(func_index) + } + + fn encode_shim_instantiation(&mut self) -> Shims<'a> { + let mut signatures = Vec::new(); + let mut ret = Shims::default(); + let info = &self.info.info; + + // For all interfaces imported into the main module record all of their + // indirect lowerings into `Shims`. + for name in info.required_imports.keys() { + let import = &self.info.import_map[&Some(*name)]; + ret.append_indirect( + name, + CustomModule::Main, + import, + info.metadata, + &mut signatures, + ); + } + + // For all required adapter modules a shim is created for each required + // function and additionally a set of shims are created for the + // interface imported into the shim module itself. + for (adapter, funcs) in info.adapters_required.iter() { + let (info, _wasm) = &self.info.adapters[adapter]; + for (name, _) in info.required_imports.iter() { + let import = &self.info.import_map[&Some(*name)]; + ret.append_indirect( + name, + CustomModule::Adapter(adapter), + import, + info.metadata, + &mut signatures, + ); + } + for (func, ty) in funcs { + let name = ret.list.len().to_string(); + log::debug!("shim {name} is adapter `{adapter}::{func}`"); + signatures.push(WasmSignature { + params: ty.params().iter().map(to_wasm_type).collect(), + results: ty.results().iter().map(to_wasm_type).collect(), + indirect_params: false, + retptr: false, + }); + ret.list.push(Shim { + name, + debug_name: format!("adapt-{adapter}-{func}"), + // Pessimistically assume that all adapters require memory + // in one form or another. While this isn't technically true + // it's true enough for WASI. + options: RequiredOptions::MEMORY, + kind: ShimKind::Adapter { adapter, func }, + }); + } + } + if ret.list.is_empty() { + return ret; + } + + for shim in ret.list.iter() { + ret.shim_names.insert(shim.kind, shim.name.clone()); + } + + assert!(self.shim_instance_index.is_none()); + assert!(self.fixups_module_index.is_none()); + + // This function encodes two modules: + // - A shim module that defines a table and exports functions + // that indirectly call through the table. + // - A fixup module that imports that table and a set of functions + // and populates the imported table via active element segments. The + // fixup module is used to populate the shim's table once the + // imported functions have been lowered. + + let mut types = TypeSection::new(); + let mut tables = TableSection::new(); + let mut functions = FunctionSection::new(); + let mut exports = ExportSection::new(); + let mut code = CodeSection::new(); + let mut sigs = IndexMap::new(); + let mut imports_section = ImportSection::new(); + let mut elements = ElementSection::new(); + let mut func_indexes = Vec::new(); + let mut func_names = NameMap::new(); + + for (i, (sig, shim)) in signatures.iter().zip(&ret.list).enumerate() { + let i = i as u32; + let type_index = *sigs.entry(sig).or_insert_with(|| { + let index = types.len(); + types.function( + sig.params.iter().map(to_val_type), + sig.results.iter().map(to_val_type), + ); + index + }); + + functions.function(type_index); + Self::encode_shim_function(type_index, i, &mut code, sig.params.len() as u32); + exports.export(&shim.name, ExportKind::Func, i); + + imports_section.import("", &shim.name, EntityType::Function(type_index)); + func_indexes.push(i); + func_names.append(i, &shim.debug_name); + } + let mut names = NameSection::new(); + names.functions(&func_names); + + let table_type = TableType { + element_type: ValType::FuncRef, + minimum: signatures.len() as u32, + maximum: Some(signatures.len() as u32), + }; + + tables.table(table_type); + + exports.export(INDIRECT_TABLE_NAME, ExportKind::Table, 0); + imports_section.import("", INDIRECT_TABLE_NAME, table_type); + + elements.active( + None, + &ConstExpr::i32_const(0), + ValType::FuncRef, + Elements::Functions(&func_indexes), + ); + + let mut shim = Module::new(); + shim.section(&types); + shim.section(&functions); + shim.section(&tables); + shim.section(&exports); + shim.section(&code); + shim.section(&names); + + let mut fixups = Module::default(); + fixups.section(&types); + fixups.section(&imports_section); + fixups.section(&elements); + + let shim_module_index = self.component.core_module(&shim); + self.fixups_module_index = Some(self.component.core_module(&fixups)); + self.shim_instance_index = Some(self.component.instantiate(shim_module_index, [])); + + return ret; + + fn to_wasm_type(ty: &wasmparser::ValType) -> WasmType { + match ty { + wasmparser::ValType::I32 => WasmType::I32, + wasmparser::ValType::I64 => WasmType::I64, + wasmparser::ValType::F32 => WasmType::F32, + wasmparser::ValType::F64 => WasmType::F64, + _ => unreachable!(), + } + } + } + + fn encode_shim_function( + type_index: u32, + func_index: u32, + code: &mut CodeSection, + param_count: u32, + ) { + let mut func = wasm_encoder::Function::new(std::iter::empty()); + for i in 0..param_count { + func.instruction(&Instruction::LocalGet(i)); + } + func.instruction(&Instruction::I32Const(func_index as i32)); + func.instruction(&Instruction::CallIndirect { + ty: type_index, + table: 0, + }); + func.instruction(&Instruction::End); + code.function(&func); + } + + fn encode_indirect_lowerings(&mut self, shims: Shims<'_>) -> Result<()> { + if shims.list.is_empty() { + return Ok(()); + } + + let shim_instance_index = self + .shim_instance_index + .expect("must have an instantiated shim"); + + let table_index = self.component.alias_core_item( + shim_instance_index, + ExportKind::Table, + INDIRECT_TABLE_NAME, + ); + + let mut exports = Vec::new(); + exports.push((INDIRECT_TABLE_NAME, ExportKind::Table, table_index)); + + for shim in shims.list.iter() { + let core_func_index = match &shim.kind { + // Indirect lowerings are a `canon lower`'d function with + // options specified from a previously instantiated instance. + // This previous instance could either be the main module or an + // adapter module, which affects the `realloc` option here. + // Currently only one linear memory is supported so the linear + // memory always comes from the main module. + ShimKind::IndirectLowering { + interface, + indirect_index, + realloc, + encoding, + } => { + let interface = &self.info.import_map[&Some(*interface)]; + let (interface_id, _url) = interface.interface.unwrap(); + let instance_index = self.imported_instances[&interface_id]; + let func_index = self + .component + .alias_func(instance_index, interface.indirect[*indirect_index].name); + + let realloc = match realloc { + CustomModule::Main => self.realloc_index, + CustomModule::Adapter(name) => self.adapter_import_reallocs[name], + }; + + self.component.lower_func( + func_index, + shim.options + .into_iter(*encoding, self.memory_index, realloc)?, + ) + } + + // Adapter shims are defined by an export from and adapter + // instance, so use the specified name here and the previously + // created instances to get the core item that represents the + // shim. + ShimKind::Adapter { adapter, func } => self.component.alias_core_item( + self.adapter_instances[adapter], + ExportKind::Func, + func, + ), + }; + + exports.push((shim.name.as_str(), ExportKind::Func, core_func_index)); + } + + let instance_index = self.component.instantiate_core_exports(exports); + self.component.instantiate( + self.fixups_module_index.expect("must have fixup module"), + [("", ModuleArg::Instance(instance_index))], + ); + Ok(()) + } + + fn instantiate_core_module<'b, A>(&mut self, args: A, info: &ValidatedModule<'_>) + where + A: IntoIterator, + A::IntoIter: ExactSizeIterator, + { + assert!(self.instance_index.is_none()); + + let instance_index = self + .component + .instantiate(self.module_index.expect("core module encoded"), args); + + if info.has_memory { + self.memory_index = Some(self.component.alias_core_item( + instance_index, + ExportKind::Memory, + "memory", + )); + } + + if let Some(name) = &info.realloc { + self.realloc_index = Some(self.component.alias_core_item( + instance_index, + ExportKind::Func, + name, + )); + } + + self.instance_index = Some(instance_index); + } + + /// This function will instantiate all required adapter modules required by + /// the main module (specified by `info`). + /// + /// Each adapter here is instantiated with its required imported interface, + /// if any. + fn instantiate_adapter_modules(&mut self, shims: &Shims<'_>) { + for (name, (info, _wasm)) in self.info.adapters.iter() { + let mut args = Vec::new(); + + let mut core_exports = Vec::new(); + for export_name in info.needs_core_exports.iter() { + let index = self.component.alias_core_item( + self.instance_index + .expect("adaptee index set at this point"), + ExportKind::Func, + export_name, + ); + core_exports.push((export_name.as_str(), ExportKind::Func, index)); + } + if !core_exports.is_empty() { + let instance = self.component.instantiate_core_exports(core_exports); + args.push((MAIN_MODULE_IMPORT_NAME, ModuleArg::Instance(instance))); + } + // If the adapter module requires a `memory` import then specify + // that here. For now assume that the module name of the memory is + // different from the imported interface. That's true enough for now + // since it's `env::memory`. + if let Some((module, name)) = &info.needs_memory { + for (import_name, _) in info.required_imports.iter() { + assert!(module != import_name); + } + assert!(module != name); + let memory = self.memory_index.unwrap(); + let instance = self.component.instantiate_core_exports([( + name.as_str(), + ExportKind::Memory, + memory, + )]); + args.push((module.as_str(), ModuleArg::Instance(instance))); + } + for (import_name, _) in info.required_imports.iter() { + let instance = self.import_instance_to_lowered_core_instance( + CustomModule::Adapter(name), + import_name, + shims, + info.metadata, + ); + args.push((import_name, ModuleArg::Instance(instance))); + } + let instance = self.component.instantiate(self.adapter_modules[name], args); + self.adapter_instances.insert(name, instance); + + let realloc = info.export_realloc.as_ref().map(|name| { + self.component + .alias_core_item(instance, ExportKind::Func, name) + }); + self.adapter_export_reallocs.insert(name, realloc); + let realloc = info.import_realloc.as_ref().map(|name| { + self.component + .alias_core_item(instance, ExportKind::Func, name) + }); + self.adapter_import_reallocs.insert(name, realloc); + } + } +} + +/// A list of "shims" which start out during the component instantiation process +/// as functions which immediately trap due to a `call_indirect`-to-`null` but +/// will get filled in by the time the component instantiation process +/// completes. +/// +/// Shims currently include: +/// +/// * "Indirect functions" lowered from imported instances where the lowering +/// requires an item exported from the main module. These are indirect due to +/// the circular dependency between the module needing an import and the +/// import needing the module. +/// +/// * Adapter modules which convert from a historical ABI to the component +/// model's ABI (e.g. wasi preview1 to preview2) get a shim since the adapters +/// are currently indicated as always requiring the memory of the main module. +/// +/// This structure is created by `encode_shim_instantiation`. +#[derive(Default)] +struct Shims<'a> { + /// The list of all shims that a module will require. + list: Vec>, + + /// A map from a shim to the name of the shim in the shim instance. + shim_names: IndexMap, String>, +} + +struct Shim<'a> { + /// Canonical ABI options required by this shim, used during `canon lower` + /// operations. + options: RequiredOptions, + + /// The name, in the shim instance, of this shim. + /// + /// Currently this is `"0"`, `"1"`, ... + name: String, + + /// A human-readable debugging name for this shim, used in a core wasm + /// `name` section. + debug_name: String, + + /// Precise information about what this shim is a lowering of. + kind: ShimKind<'a>, +} + +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +enum ShimKind<'a> { + /// This shim is a late indirect lowering of an imported function in a + /// component which is only possible after prior core wasm modules are + /// instantiated so their memories and functions are available. + IndirectLowering { + /// The name of the interface that's being lowered. + interface: &'a str, + /// The index within the `indirect` array of the function being lowered. + indirect_index: usize, + /// Which instance to pull the `realloc` function from, if necessary. + realloc: CustomModule<'a>, + /// The string encoding that this lowering is going to use. + encoding: StringEncoding, + }, + /// This shim is a core wasm function defined in an adapter module but isn't + /// available until the adapter module is itself instantiated. + Adapter { + /// The name of the adapter module this shim comes from. + adapter: &'a str, + /// The name of the export in the adapter module this shim points to. + func: &'a str, + }, +} + +/// Indicator for which module is being used for a lowering or where options +/// like `realloc` are drawn from. +/// +/// This is necessary for situations such as an imported function being lowered +/// into the main module and additionally into an adapter module. For example an +/// adapter might adapt from preview1 to preview2 for the standard library of a +/// programming language but the main module's custom application code may also +/// explicitly import from preview2. These two different lowerings of a preview2 +/// function are parameterized by this enumeration. +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +enum CustomModule<'a> { + /// This points to the "main module" which is generally the "output of LLVM" + /// or what a user wrote. + Main, + /// This is selecting an adapter module, identified by name here, where + /// something is being lowered into. + Adapter(&'a str), +} + +impl<'a> Shims<'a> { + /// Adds all shims necessary for the `import` provided, namely iterating + /// over its indirect lowerings and appending a shim per lowering. + fn append_indirect( + &mut self, + name: &'a str, + for_module: CustomModule<'a>, + import: &ImportedInterface<'a>, + metadata: &ModuleMetadata, + sigs: &mut Vec, + ) { + for (indirect_index, lowering) in import.indirect.iter().enumerate() { + let shim_name = self.list.len().to_string(); + log::debug!( + "shim {shim_name} is import `{name}` lowering {indirect_index} `{}`", + lowering.name + ); + sigs.push(lowering.sig.clone()); + let encoding = + metadata.import_encodings[&(name.to_string(), lowering.name.to_string())]; + self.list.push(Shim { + name: shim_name, + debug_name: format!("indirect-{name}-{}", lowering.name), + options: lowering.options, + kind: ShimKind::IndirectLowering { + interface: name, + indirect_index, + realloc: for_module, + encoding, + }, + }); + } + } +} /// An encoder of components based on `wit` interface definitions. #[derive(Default)] pub struct ComponentEncoder { module: Vec, - // metadata: Bindgen, + metadata: Bindgen, validate: bool, + // This is a map from the name of the adapter to a pair of: // // * The wasm of the adapter itself, with `component-type` sections @@ -1120,7 +1142,7 @@ pub struct ComponentEncoder { // * the metadata for the adapter, verified to have no exports and only // imports. // * The world within `self.metadata.doc` which the adapter works with. - // adapters: IndexMap, ModuleMetadata, WorldId)>, + adapters: IndexMap, ModuleMetadata, WorldId)>, } impl ComponentEncoder { @@ -1128,11 +1150,10 @@ impl ComponentEncoder { /// This method will also parse any component type information stored in custom sections /// inside the module, and add them as the interface, imports, and exports. pub fn module(mut self, module: &[u8]) -> Result { - panic!() - // let (wasm, metadata) = metadata::decode(module)?; - // self.module = wasm; - // self.metadata.merge(metadata)?; - // Ok(self) + let (wasm, metadata) = metadata::decode(module)?; + self.module = wasm; + self.metadata.merge(metadata)?; + Ok(self) } /// Sets whether or not the encoder will validate its output. @@ -1159,115 +1180,60 @@ impl ComponentEncoder { /// `interface` and export functions to get imported from the module `name` /// in the core wasm that's being wrapped. pub fn adapter(mut self, name: &str, bytes: &[u8]) -> Result { - panic!() - // let (wasm, metadata) = metadata::decode(bytes)?; - // // Merge the adapter's document into our own document to have one large - // // document, but the adapter's world isn't merged in to our world so - // // retain it separately. - // let world = self.metadata.doc.merge(metadata.doc).world_map[metadata.world.index()]; - // self.adapters - // .insert(name.to_string(), (wasm, metadata.metadata, world)); - // Ok(self) + let (wasm, metadata) = metadata::decode(bytes)?; + // Merge the adapter's document into our own document to have one large + // document, but the adapter's world isn't merged in to our world so + // retain it separately. + let world = self.metadata.resolve.merge(metadata.resolve).worlds[metadata.world.index()]; + self.adapters + .insert(name.to_string(), (wasm, metadata.metadata, world)); + Ok(self) } /// Encode the component and return the bytes. pub fn encode(&self) -> Result> { - panic!() - // let doc = &self.metadata.doc; - // let world = ComponentWorld::new(self)?; - // let mut state = EncodingState { - // component: ComponentBuilder::default(), - // module_index: None, - // instance_index: None, - // memory_index: None, - // realloc_index: None, - // shim_instance_index: None, - // fixups_module_index: None, - // adapter_modules: IndexMap::new(), - // adapter_instances: IndexMap::new(), - // adapter_import_reallocs: IndexMap::new(), - // adapter_export_reallocs: IndexMap::new(), - // type_map: HashMap::new(), - // func_type_map: HashMap::new(), - // imported_instances: Default::default(), - // info: &world, - // }; - // let world = &doc.worlds[self.metadata.world]; - // state.encode_imports()?; - - // if self.types_only { - // if !self.module.is_empty() { - // bail!("a module cannot be specified for a types-only encoding"); - // } - - // // In types-only mode there's no actual items to export so skip - // // shims/adapters etc. Instead instance types are exported to - // // represent exported instances and exported types represent the - // // default export. - // for (id, name) in world.exports() { - // let interface = &doc.interfaces[id]; - // let url = interface.url.as_deref().unwrap_or(""); - // match name { - // Some(name) => { - // let mut ty = state.instance_type_encoder(id); - // for func in interface.functions.iter() { - // let idx = ty.encode_func_type(doc, func)?; - // ty.ty.export(&func.name, "", ComponentTypeRef::Func(idx)); - // } - // let ty = ty.ty; - // if ty.is_empty() { - // continue; - // } - // let index = state.component.instance_type(&ty); - // state - // .component - // .export(name, url, ComponentExportKind::Type, index); - // } - // None => { - // let mut ty = state.root_type_encoder(id); - // for func in interface.functions.iter() { - // let idx = ty.encode_func_type(doc, func)?; - // ty.state.component.export( - // &func.name, - // "", - // ComponentExportKind::Type, - // idx, - // ); - // } - // for (idx, name) in ty.type_exports { - // ty.state - // .component - // .export(name, "", ComponentExportKind::Type, idx); - // } - // } - // } - // } - // } else { - // if self.module.is_empty() { - // bail!("a module is required when encoding a component"); - // } - - // state.encode_core_modules(); - // state.encode_core_instantiation()?; - // state.encode_exports(self, CustomModule::Main)?; - // for name in self.adapters.keys() { - // state.encode_exports(self, CustomModule::Adapter(name))?; - // } - // } - - // let bytes = state.component.finish(); - - // if self.validate { - // let mut validator = Validator::new_with_features(WasmFeatures { - // component_model: true, - // ..Default::default() - // }); - - // validator - // .validate_all(&bytes) - // .context("failed to validate component output")?; - // } - - // Ok(bytes) + if self.module.is_empty() { + bail!("a module is required when encoding a component"); + } + + let world = ComponentWorld::new(self)?; + let mut state = EncodingState { + component: ComponentBuilder::default(), + module_index: None, + instance_index: None, + memory_index: None, + realloc_index: None, + shim_instance_index: None, + fixups_module_index: None, + adapter_modules: IndexMap::new(), + adapter_instances: IndexMap::new(), + adapter_import_reallocs: IndexMap::new(), + adapter_export_reallocs: IndexMap::new(), + type_map: HashMap::new(), + func_type_map: HashMap::new(), + imported_instances: Default::default(), + info: &world, + }; + state.encode_imports()?; + state.encode_core_modules(); + state.encode_core_instantiation()?; + state.encode_exports(self, CustomModule::Main)?; + for name in self.adapters.keys() { + state.encode_exports(self, CustomModule::Adapter(name))?; + } + let bytes = state.component.finish(); + + if self.validate { + let mut validator = Validator::new_with_features(WasmFeatures { + component_model: true, + ..Default::default() + }); + + validator + .validate_all(&bytes) + .context("failed to validate component output")?; + } + + Ok(bytes) } } diff --git a/crates/wit-component/src/encoding/types.rs b/crates/wit-component/src/encoding/types.rs index d74b448c2f..89e9af35d4 100644 --- a/crates/wit-component/src/encoding/types.rs +++ b/crates/wit-component/src/encoding/types.rs @@ -1,10 +1,10 @@ -// use super::EncodingState; +use super::EncodingState; use anyhow::Result; use std::collections::HashMap; use wasm_encoder::*; use wit_parser::{ - Enum, Flags, Function, Params, Record, Resolve, Result_, Results, Tuple, Type, TypeDefKind, - TypeId, Union, Variant, + Enum, Flags, Function, InterfaceId, Params, Record, Resolve, Result_, Results, Tuple, Type, + TypeDefKind, TypeId, TypeOwner, Union, Variant, }; /// Represents a key type for interface function definitions. @@ -34,7 +34,7 @@ pub trait ValtypeEncoder<'a> { fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>); /// Creates an export item for the specified type index. - fn export_type(&mut self, index: u32, name: &'a str) -> u32; + fn export_type(&mut self, index: u32, name: &'a str) -> Option; /// Returns a map of all types previously defined in this type index space. fn type_map(&mut self) -> &mut HashMap; @@ -164,7 +164,7 @@ pub trait ValtypeEncoder<'a> { index } }; - let index = self.export_type(index, name); + let index = self.export_type(index, name).unwrap_or(index); encoded = ComponentValType::Type(index); } @@ -278,97 +278,94 @@ pub trait ValtypeEncoder<'a> { } } -// pub struct RootTypeEncoder<'state, 'a> { -// pub state: &'state mut EncodingState<'a>, -// pub type_exports: Vec<(u32, &'a str)>, -// pub interface: InterfaceId, -// } - -// impl<'a> ValtypeEncoder<'a> for RootTypeEncoder<'_, 'a> { -// fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { -// self.state.component.defined_type() -// } -// fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { -// self.state.component.function_type() -// } -// fn define_type_alias_self(&mut self, idx: u32) -> u32 { -// self.state.component.alias_outer_type(0, idx) -// } -// fn export_type(&mut self, idx: u32, name: &'a str) { -// self.type_exports.push((idx, name)); -// } -// fn type_map(&mut self) -> &mut HashMap { -// &mut self.state.type_map -// } -// fn maybe_import_type(&mut self, id: TypeId) -> Option { -// // If this `id` is anonymous or belongs to this interface there's -// // nothing to import, it needs defining. Otherwise alias the type from -// // an import into this component's root namespace. -// let other = self.state.info.encoder.metadata.resolve.types[id].interface?; -// if other == self.interface { -// return None; -// } -// // TODO: this doesn't work for importing types from other exports. That -// // just trips an assertion here. -// Some(self.state.index_of_type_export(id)) -// } -// fn func_type_map(&mut self) -> &mut HashMap, u32> { -// &mut self.state.func_type_map -// } -// } - -// pub struct InstanceTypeEncoder<'state, 'a> { -// pub state: &'state mut EncodingState<'a>, -// pub interface: InterfaceId, -// pub type_map: HashMap, -// pub func_type_map: HashMap, u32>, -// pub ty: InstanceType, -// } - -// impl<'a> ValtypeEncoder<'a> for InstanceTypeEncoder<'_, 'a> { -// fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { -// (self.ty.type_count(), self.ty.ty().defined_type()) -// } -// fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { -// (self.ty.type_count(), self.ty.ty().function()) -// } -// fn define_type_alias_self(&mut self, idx: u32) -> u32 { -// let ret = self.ty.type_count(); -// self.ty.alias(Alias::Outer { -// count: 0, -// index: idx, -// kind: ComponentOuterAliasKind::Type, -// }); -// ret -// } -// fn export_type(&mut self, idx: u32, name: &str) { -// self.ty -// .export(name, "", ComponentTypeRef::Type(TypeBounds::Eq, idx)); -// } -// fn type_map(&mut self) -> &mut HashMap { -// &mut self.type_map -// } -// fn maybe_import_type(&mut self, id: TypeId) -> Option { -// // If this `id` is anonymous or belongs to this interface -// // there's nothing to import, it needs defining. Otherwise -// // perform the importing process with an outer alias to the -// // parent component. -// let other = self.state.info.encoder.metadata.resolve.types[id].interface?; -// if other == self.interface { -// return None; -// } - -// let outer_idx = self.state.index_of_type_export(id); -// let ret = self.ty.type_count(); -// self.type_map.insert(id, ret); -// self.ty.alias(Alias::Outer { -// count: 1, -// index: outer_idx, -// kind: ComponentOuterAliasKind::Type, -// }); -// Some(ret) -// } -// fn func_type_map(&mut self) -> &mut HashMap, u32> { -// &mut self.func_type_map -// } -// } +pub struct RootTypeEncoder<'state, 'a> { + pub state: &'state mut EncodingState<'a>, + pub type_exports: Vec<(u32, &'a str)>, + pub interface: Option, +} + +impl<'a> ValtypeEncoder<'a> for RootTypeEncoder<'_, 'a> { + fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { + self.state.component.defined_type() + } + fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { + self.state.component.function_type() + } + fn export_type(&mut self, idx: u32, name: &'a str) -> Option { + self.type_exports.push((idx, name)); + None + } + fn type_map(&mut self) -> &mut HashMap { + &mut self.state.type_map + } + fn maybe_import_type(&mut self, id: TypeId) -> Option { + // If this `id` is anonymous or belongs to this interface there's + // nothing to import, it needs defining. Otherwise alias the type from + // an import into this component's root namespace. + let other = match self.state.info.encoder.metadata.resolve.types[id].owner { + TypeOwner::Interface(id) => id, + _ => return None, + }; + if Some(other) == self.interface { + return None; + } + // TODO: this doesn't work for importing types from other exports. That + // just trips an assertion here. + Some(self.state.index_of_type_export(id)) + } + fn func_type_map(&mut self) -> &mut HashMap, u32> { + &mut self.state.func_type_map + } +} + +pub struct InstanceTypeEncoder<'state, 'a> { + pub state: &'state mut EncodingState<'a>, + pub interface: InterfaceId, + pub type_map: HashMap, + pub func_type_map: HashMap, u32>, + pub ty: InstanceType, +} + +impl<'a> ValtypeEncoder<'a> for InstanceTypeEncoder<'_, 'a> { + fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { + (self.ty.type_count(), self.ty.ty().defined_type()) + } + fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { + (self.ty.type_count(), self.ty.ty().function()) + } + fn export_type(&mut self, idx: u32, name: &str) -> Option { + let ret = self.ty.type_count(); + self.ty + .export(name, "", ComponentTypeRef::Type(TypeBounds::Eq, idx)); + Some(ret) + } + fn type_map(&mut self) -> &mut HashMap { + &mut self.type_map + } + fn maybe_import_type(&mut self, id: TypeId) -> Option { + // If this `id` is anonymous or belongs to this interface + // there's nothing to import, it needs defining. Otherwise + // perform the importing process with an outer alias to the + // parent component. + let other = match self.state.info.encoder.metadata.resolve.types[id].owner { + TypeOwner::Interface(id) => id, + _ => return None, + }; + if other == self.interface { + return None; + } + + let outer_idx = self.state.index_of_type_export(id); + let ret = self.ty.type_count(); + self.type_map.insert(id, ret); + self.ty.alias(Alias::Outer { + count: 1, + index: outer_idx, + kind: ComponentOuterAliasKind::Type, + }); + Some(ret) + } + fn func_type_map(&mut self) -> &mut HashMap, u32> { + &mut self.func_type_map + } +} diff --git a/crates/wit-component/src/encoding/wit.rs b/crates/wit-component/src/encoding/wit.rs index 62a7bd82b1..bd24eec83f 100644 --- a/crates/wit-component/src/encoding/wit.rs +++ b/crates/wit-component/src/encoding/wit.rs @@ -64,10 +64,11 @@ impl Encoder<'_> { let mut encoder = InterfaceEncoder::new(self.resolve); for (owner, ids) in imported_interfaces { let owner_iface = &self.resolve.interfaces[owner]; + encoder.push_instance(); for id in ids { encoder.encode_valtype(self.resolve, &Type::Id(id))?; } - let instance = encoder.take_instance(); + let instance = encoder.pop_instance(); let idx = encoder.outer.type_count(); encoder.outer.ty().instance(&instance); encoder.import_map.insert(owner, encoder.instances); @@ -88,7 +89,7 @@ impl Encoder<'_> { .export(name, "", ComponentTypeRef::Instance(idx)); } - for world in doc.worlds.iter() { + for (_name, world) in doc.worlds.iter() { let world = &self.resolve.worlds[*world]; let mut component = InterfaceEncoder::new(self.resolve); for (name, import) in world.imports.iter() { @@ -97,7 +98,10 @@ impl Encoder<'_> { let idx = component.encode_instance(*i)?; (self.url_of(*i), ComponentTypeRef::Instance(idx)) } - WorldItem::Function(_) => panic!(), + WorldItem::Function(f) => { + let idx = component.encode_func_type(self.resolve, f)?; + (String::new(), ComponentTypeRef::Func(idx)) + } }; component.outer.import(name, &url, ty); } @@ -107,7 +111,10 @@ impl Encoder<'_> { let idx = component.encode_instance(*i)?; (self.url_of(*i), ComponentTypeRef::Instance(idx)) } - WorldItem::Function(_) => panic!(), + WorldItem::Function(f) => { + let idx = component.encode_func_type(self.resolve, f)?; + (String::new(), ComponentTypeRef::Func(idx)) + } }; component.outer.export(name, &url, ty); } @@ -146,7 +153,7 @@ impl Encoder<'_> { struct InterfaceEncoder<'a> { resolve: &'a Resolve, outer: ComponentType, - ty: InstanceType, + ty: Option, func_type_map: HashMap, u32>, type_map: HashMap, import_map: HashMap, @@ -159,7 +166,7 @@ impl InterfaceEncoder<'_> { InterfaceEncoder { resolve, outer: ComponentType::new(), - ty: InstanceType::new(), + ty: None, type_map: Default::default(), func_type_map: Default::default(), import_map: Default::default(), @@ -169,15 +176,19 @@ impl InterfaceEncoder<'_> { } fn encode_instance(&mut self, interface: InterfaceId) -> Result { + self.push_instance(); let iface = &self.resolve.interfaces[interface]; for (_, id) in iface.types.iter() { self.encode_valtype(self.resolve, &Type::Id(*id))?; } for (name, func) in iface.functions.iter() { let ty = self.encode_func_type(self.resolve, func)?; - self.ty.export(name, "", ComponentTypeRef::Func(ty)); + self.ty + .as_mut() + .unwrap() + .export(name, "", ComponentTypeRef::Func(ty)); } - let instance = self.take_instance(); + let instance = self.pop_instance(); let idx = self.outer.type_count(); self.outer.ty().instance(&instance); self.import_map.insert(interface, self.instances); @@ -185,25 +196,47 @@ impl InterfaceEncoder<'_> { Ok(idx) } - fn take_instance(&mut self) -> InstanceType { + fn push_instance(&mut self) { + assert!(self.ty.is_none()); + self.func_type_map.clear(); + self.type_map.clear(); + self.ty = Some(InstanceType::default()); + } + + fn pop_instance(&mut self) -> InstanceType { self.func_type_map.clear(); self.type_map.clear(); - mem::take(&mut self.ty) + mem::take(&mut self.ty).unwrap() } } impl<'a> ValtypeEncoder<'a> for InterfaceEncoder<'a> { fn defined_type(&mut self) -> (u32, ComponentDefinedTypeEncoder<'_>) { - (self.ty.type_count(), self.ty.ty().defined_type()) + match &mut self.ty { + Some(ty) => (ty.type_count(), ty.ty().defined_type()), + None => (self.outer.type_count(), self.outer.ty().defined_type()), + } } fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) { - (self.ty.type_count(), self.ty.ty().function()) + match &mut self.ty { + Some(ty) => (ty.type_count(), ty.ty().function()), + None => (self.outer.type_count(), self.outer.ty().function()), + } } - fn export_type(&mut self, index: u32, name: &'a str) -> u32 { - let ret = self.ty.type_count(); - self.ty - .export(name, "", ComponentTypeRef::Type(TypeBounds::Eq, index)); - ret + fn export_type(&mut self, index: u32, name: &'a str) -> Option { + match &mut self.ty { + Some(ty) => { + let ret = ty.type_count(); + ty.export(name, "", ComponentTypeRef::Type(TypeBounds::Eq, index)); + Some(ret) + } + None => { + let ret = self.outer.type_count(); + self.outer + .export(name, "", ComponentTypeRef::Type(TypeBounds::Eq, index)); + Some(ret) + } + } } fn type_map(&mut self) -> &mut HashMap { &mut self.type_map @@ -224,8 +257,12 @@ impl<'a> ValtypeEncoder<'a> for InterfaceEncoder<'a> { }); ret }); - let ret = self.ty.type_count(); - self.ty.alias(Alias::Outer { + let ty = match &mut self.ty { + Some(ty) => ty, + None => unimplemented!(), + }; + let ret = ty.type_count(); + ty.alias(Alias::Outer { count: 1, index: outer_idx, kind: ComponentOuterAliasKind::Type, diff --git a/crates/wit-component/src/encoding/world.rs b/crates/wit-component/src/encoding/world.rs index 63513ae6f9..03093df383 100644 --- a/crates/wit-component/src/encoding/world.rs +++ b/crates/wit-component/src/encoding/world.rs @@ -5,11 +5,10 @@ use crate::validation::{ use anyhow::{Context, Result}; use indexmap::{IndexMap, IndexSet}; use std::collections::HashSet; -use std::mem; use wasmparser::FuncType; use wit_parser::{ abi::{AbiVariant, WasmSignature, WasmType}, - Document, Function, InterfaceId, Type, TypeDefKind, TypeId, WorldId, + Function, InterfaceId, LiveTypes, Resolve, TypeId, TypeOwner, WorldId, WorldItem, }; /// Metadata discovered from the state configured in a `ComponentEncoder`. @@ -22,12 +21,12 @@ pub struct ComponentWorld<'a> { pub encoder: &'a ComponentEncoder, /// Validation information of the input module, or `None` in `--types-only` /// mode. - pub info: Option>, + pub info: ValidatedModule<'a>, /// Validation information about adapters populated only for required /// adapters. Additionally stores the gc'd wasm for each adapter. pub adapters: IndexMap<&'a str, (ValidatedAdapter<'a>, Vec)>, /// Map of all imports and descriptions of what they're importing. - pub import_map: IndexMap<&'a str, ImportedInterface<'a>>, + pub import_map: IndexMap, ImportedInterface<'a>>, /// Set of all live types which must be exported either because they're /// directly used or because they're transitively used. pub live_types: IndexMap>, @@ -35,13 +34,12 @@ pub struct ComponentWorld<'a> { #[derive(Debug)] pub struct ImportedInterface<'a> { - pub url: &'a str, pub direct: Vec>, pub indirect: Vec>, /// Required functions on the interface, or the filter on the functions list /// in `interface`. pub required: HashSet<&'a str>, - pub interface: InterfaceId, + pub interface: Option<(InterfaceId, &'a str)>, } #[derive(Debug)] @@ -58,20 +56,12 @@ pub struct IndirectLowering<'a> { impl<'a> ComponentWorld<'a> { pub fn new(encoder: &'a ComponentEncoder) -> Result { - let info = if !encoder.module.is_empty() { - let adapters = encoder - .adapters - .keys() - .map(|s| s.as_str()) - .collect::>(); - Some(validate_module( - &encoder.module, - &encoder.metadata, - &adapters, - )?) - } else { - None - }; + let adapters = encoder + .adapters + .keys() + .map(|s| s.as_str()) + .collect::>(); + let info = validate_module(&encoder.module, &encoder.metadata, &adapters)?; let mut ret = ComponentWorld { encoder, @@ -93,19 +83,16 @@ impl<'a> ComponentWorld<'a> { /// adapter itself, either because the functions are imported by the /// main module or they're part of the adapter's exports. fn process_adapters(&mut self) -> Result<()> { - let doc = &self.encoder.metadata.doc; + let resolve = &self.encoder.metadata.resolve; for (name, (wasm, metadata, world)) in self.encoder.adapters.iter() { - let required_by_import = self - .info - .as_ref() - .and_then(|info| info.adapters_required.get(name.as_str())); - let required = self.required_adapter_exports(doc, *world, required_by_import); + let required_by_import = self.info.adapters_required.get(name.as_str()); + let required = self.required_adapter_exports(resolve, *world, required_by_import); if required.is_empty() { continue; } let wasm = crate::gc::run(wasm, &required) .context("failed to reduce input adapter module to its minimal size")?; - let info = validate_adapter_module(&wasm, doc, *world, metadata, &required) + let info = validate_adapter_module(&wasm, resolve, *world, metadata, &required) .context("failed to validate the imports of the minimized adapter module")?; self.adapters.insert(name, (info, wasm)); } @@ -117,7 +104,7 @@ impl<'a> ComponentWorld<'a> { /// they're required as an import to the main module. fn required_adapter_exports( &self, - doc: &Document, + resolve: &Resolve, world: WorldId, required_by_import: Option<&IndexMap<&str, FuncType>>, ) -> IndexMap { @@ -129,18 +116,26 @@ impl<'a> ComponentWorld<'a> { required.insert(name.to_string(), ty.clone()); } } - for (interface, name) in doc.worlds[world].exports() { - for func in doc.interfaces[interface].functions.iter() { - let name = func.core_export_name(name); - let ty = doc.wasm_signature(AbiVariant::GuestExport, func); - let prev = required.insert( - name.into_owned(), - wasmparser::FuncType::new( - ty.params.iter().map(to_valty), - ty.results.iter().map(to_valty), - ), - ); - assert!(prev.is_none()); + let mut add_func = |func: &Function, name: Option<&str>| { + let name = func.core_export_name(name); + let ty = resolve.wasm_signature(AbiVariant::GuestExport, func); + let prev = required.insert( + name.into_owned(), + wasmparser::FuncType::new( + ty.params.iter().map(to_valty), + ty.results.iter().map(to_valty), + ), + ); + assert!(prev.is_none()); + }; + for (name, item) in resolve.worlds[world].exports.iter() { + match item { + WorldItem::Function(func) => add_func(func, None), + WorldItem::Interface(id) => { + for (_, func) in resolve.interfaces[*id].functions.iter() { + add_func(func, Some(name)); + } + } } } return required; @@ -159,68 +154,90 @@ impl<'a> ComponentWorld<'a> { /// functions from all imports. This additionally classifies imported /// functions into direct or indirect lowerings for managing shims. fn process_imports(&mut self) -> Result<()> { - let doc = &self.encoder.metadata.doc; + let resolve = &self.encoder.metadata.resolve; let world = self.encoder.metadata.world; - let empty = IndexSet::new(); - for (name, interface) in doc.worlds[world].imports.iter() { - let required = match &self.info { - Some(info) => Some(info.required_imports.get(name.as_str()).unwrap_or(&empty)), - None => None, - }; - add_interface(&mut self.import_map, doc, name, *interface, required)?; + for (name, item) in resolve.worlds[world].imports.iter() { + add_item( + &mut self.import_map, + resolve, + name, + item, + &self.info.required_imports, + )?; } for (adapter_name, (info, _wasm)) in self.adapters.iter() { - for (name, required) in info.required_imports.iter() { - let (_, _, world) = self.encoder.adapters[*adapter_name]; - let interface = doc.worlds[world].imports[*name]; - add_interface(&mut self.import_map, doc, name, interface, Some(required))?; + let (_, _, world) = self.encoder.adapters[*adapter_name]; + for (name, item) in resolve.worlds[world].imports.iter() { + add_item( + &mut self.import_map, + resolve, + name, + item, + &info.required_imports, + )?; } } return Ok(()); - fn add_interface<'a>( - import_map: &mut IndexMap<&'a str, ImportedInterface<'a>>, - doc: &'a Document, + fn add_item<'a>( + import_map: &mut IndexMap, ImportedInterface<'a>>, + resolve: &'a Resolve, name: &'a str, - id: InterfaceId, - required: Option<&IndexSet<&str>>, + item: &'a WorldItem, + required: &IndexMap<&str, IndexSet<&str>>, ) -> Result<()> { - import_map.entry(name).or_insert_with(|| ImportedInterface { - interface: id, - url: doc.interfaces[id].url.as_deref().unwrap_or(""), - direct: Default::default(), - indirect: Default::default(), - required: Default::default(), - }); - for func in doc.interfaces[id].functions.iter() { - // If this function isn't actually required then skip it - if let Some(required) = required { - if !required.contains(func.name.as_str()) { - continue; + let empty = IndexSet::new(); + match item { + WorldItem::Function(func) => { + let required = required.get("").unwrap_or(&empty); + // If this function isn't actually required then skip it + if !required.contains(name) { + return Ok(()); } + let interface = import_map.entry(None).or_insert_with(|| ImportedInterface { + interface: None, + direct: Default::default(), + indirect: Default::default(), + required: Default::default(), + }); + add_import(interface, resolve, func) + } + WorldItem::Interface(id) => { + let required = required.get(name).unwrap_or(&empty); + let url = resolve.interfaces[*id].url.as_deref().unwrap_or(""); + let interface = + import_map + .entry(Some(name)) + .or_insert_with(|| ImportedInterface { + interface: Some((*id, url)), + direct: Default::default(), + indirect: Default::default(), + required: Default::default(), + }); + for (_name, func) in resolve.interfaces[*id].functions.iter() { + // If this function isn't actually required then skip it + if required.contains(func.name.as_str()) { + add_import(interface, resolve, func)?; + } + } + Ok(()) } - add_import(import_map, doc, name, id, func)?; } - Ok(()) } fn add_import<'a>( - import_map: &mut IndexMap<&'a str, ImportedInterface<'a>>, - doc: &'a Document, - name: &'a str, - id: InterfaceId, + interface: &mut ImportedInterface<'a>, + resolve: &'a Resolve, func: &'a Function, ) -> Result<()> { - let interface = import_map.get_mut(name).unwrap(); - assert_eq!(interface.interface, id); if !interface.required.insert(func.name.as_str()) { return Ok(()); } - let options = RequiredOptions::for_import(doc, func); + let options = RequiredOptions::for_import(resolve, func); if options.is_empty() { interface.direct.push(DirectLowering { name: &func.name }); } else { - let sig = doc.wasm_signature(AbiVariant::GuestImport, func); + let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); interface.indirect.push(IndirectLowering { name: &func.name, sig, @@ -236,102 +253,68 @@ impl<'a> ComponentWorld<'a> { /// individual interface by walking over the set of live functions in /// imports and recursively walking types. fn process_live_types(&mut self) { - let mut live_types = mem::take(&mut self.live_types); - let doc = &self.encoder.metadata.doc; - for (_name, info) in self.import_map.iter() { - let interface = &doc.interfaces[info.interface]; - for func in interface.functions.iter() { - if !info.required.contains(func.name.as_str()) { - continue; - } - self.add_live_func(func, &mut live_types); - } - } - for (id, _) in doc.worlds[self.encoder.metadata.world].exports() { - let interface = &doc.interfaces[id]; - for func in interface.functions.iter() { - self.add_live_func(func, &mut live_types); - } + let mut live = LiveTypes::default(); + let resolve = &self.encoder.metadata.resolve; + let world = &resolve.worlds[self.encoder.metadata.world]; + self.add_live_imports( + self.encoder.metadata.world, + &self.info.required_imports, + &mut live, + ); + for (_, item) in world.exports.iter() { + live.add_world_item(resolve, item); } - for (_, (_, _, world)) in self.encoder.adapters.iter() { - for (id, _) in doc.worlds[*world].exports() { - let interface = &doc.interfaces[id]; - for func in interface.functions.iter() { - self.add_live_func(func, &mut live_types); - } + for (adapter_name, (info, _wasm)) in self.adapters.iter() { + let (_, _, world) = self.encoder.adapters[*adapter_name]; + self.add_live_imports(world, &info.required_imports, &mut live); + for (_, item) in resolve.worlds[world].exports.iter() { + live.add_world_item(resolve, item); } } - self.live_types = live_types; + for live in live.iter() { + let owner = match resolve.types[live].owner { + TypeOwner::Interface(id) => id, + _ => continue, + }; + self.live_types + .entry(owner) + .or_insert(Default::default()) + .insert(live); + } } - fn add_live_func( + fn add_live_imports( &self, - func: &Function, - live_types: &mut IndexMap>, + world: WorldId, + required: &IndexMap<&str, IndexSet<&str>>, + live: &mut LiveTypes, ) { - for ty in func - .params - .iter() - .map(|(_, t)| t) - .chain(func.results.iter_types()) - { - self.add_live(ty, live_types); - } - } - - fn add_live(&self, ty: &Type, live_types: &mut IndexMap>) { - let id = match *ty { - Type::Id(id) => id, - _ => return, - }; - let ty = &self.encoder.metadata.doc.types[id]; - let interface = match ty.interface { - Some(id) => id, - None => return, - }; - let set = live_types.entry(interface).or_insert_with(Default::default); - if !set.insert(id) { - return; - } - log::trace!("live {id:?} in {interface:?}"); - match &ty.kind { - TypeDefKind::Record(t) => { - for f in t.fields.iter() { - self.add_live(&f.ty, live_types); - } - } - TypeDefKind::Tuple(t) => { - for ty in t.types.iter() { - self.add_live(ty, live_types); - } - } - TypeDefKind::Union(t) => { - for c in t.cases.iter() { - self.add_live(&c.ty, live_types); - } - } - TypeDefKind::Variant(t) => { - for c in t.cases.iter() { - if let Some(ty) = &c.ty { - self.add_live(ty, live_types); + let resolve = &self.encoder.metadata.resolve; + for (name, item) in resolve.worlds[world].imports.iter() { + match item { + WorldItem::Function(func) => { + let required = match required.get("") { + Some(set) => set, + None => return, + }; + if !required.contains(name.as_str()) { + return; } + live.add_func(resolve, func); } - } - TypeDefKind::Result(t) => { - if let Some(t) = &t.ok { - self.add_live(t, live_types); - } - if let Some(t) = &t.err { - self.add_live(t, live_types); + WorldItem::Interface(id) => { + let required = match required.get(name.as_str()) { + Some(set) => set, + None => return, + }; + for (name, func) in resolve.interfaces[*id].functions.iter() { + if required.contains(name.as_str()) { + live.add_func(resolve, func); + } + } } } - TypeDefKind::Option(t) | TypeDefKind::Type(t) | TypeDefKind::List(t) => { - self.add_live(t, live_types); - } - TypeDefKind::Enum(_) | TypeDefKind::Flags(_) => {} - TypeDefKind::Stream(_) => todo!(), - TypeDefKind::Future(_) => todo!(), } } } diff --git a/crates/wit-component/src/lib.rs b/crates/wit-component/src/lib.rs index 9883e010ec..0e3e8987df 100644 --- a/crates/wit-component/src/lib.rs +++ b/crates/wit-component/src/lib.rs @@ -10,9 +10,9 @@ use wasm_encoder::CanonicalOption; mod builder; mod decoding; mod encoding; -// mod gc; +mod gc; mod printing; -// mod validation; +mod validation; pub use decoding::{decode, DecodedWasm}; pub use encoding::{encode, ComponentEncoder}; diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index 2c1f08beba..6b2e9d0292 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -29,90 +29,109 @@ //! * Afterwards a "types only" component encoding of a `World` //! package through the `ComponentEncoder::types_only` configuration. -use crate::StringEncoding; +use crate::{DecodedWasm, StringEncoding}; use anyhow::{bail, Context, Result}; use indexmap::IndexMap; use wasm_encoder::Encode; use wasmparser::BinaryReader; -use wit_parser::{Resolve, World, WorldId}; +use wit_parser::{Document, Package, Resolve, World, WorldId, WorldItem}; const CURRENT_VERSION: u8 = 0x02; -///// The result of decoding binding information from a WebAssembly binary. -///// -///// This structure is returned by [`decode`] and represents the interface of a -///// WebAssembly binary. -//pub struct Bindgen { -// /// Interface and type information for this binary. -// pub resolve: Resolve, -// /// The world that was bound. -// pub world: WorldId, -// /// Metadata about this specific module that was bound. -// pub metadata: ModuleMetadata, -//} +/// The result of decoding binding information from a WebAssembly binary. +/// +/// This structure is returned by [`decode`] and represents the interface of a +/// WebAssembly binary. +pub struct Bindgen { + /// Interface and type information for this binary. + pub resolve: Resolve, + /// The world that was bound. + pub world: WorldId, + /// Metadata about this specific module that was bound. + pub metadata: ModuleMetadata, +} -//impl Default for Bindgen { -// fn default() -> Bindgen { -// let mut resolve = Resolve::default(); -// let world = resolve.worlds.alloc(World::default()); -// Bindgen { -// resolve, -// world, -// metadata: ModuleMetadata::default(), -// } -// } -//} +impl Default for Bindgen { + fn default() -> Bindgen { + let mut resolve = Resolve::default(); + let package = resolve.packages.alloc(Package { + name: "root".to_string(), + url: None, + documents: Default::default(), + }); + let document = resolve.documents.alloc(Document { + name: "root".to_string(), + interfaces: Default::default(), + worlds: Default::default(), + default_world: None, + default_interface: None, + package: Some(package), + }); + let world = resolve.worlds.alloc(World { + name: "root".to_string(), + docs: Default::default(), + imports: Default::default(), + exports: Default::default(), + document, + }); + Bindgen { + resolve, + world, + metadata: ModuleMetadata::default(), + } + } +} -///// Module-level metadata that's specific to one core WebAssembly module. This -///// is extracted with a [`Bindgen`]. -//#[derive(Default)] -//pub struct ModuleMetadata { -// /// Per-function options imported into the core wasm module, currently only -// /// related to string encoding. -// pub import_encodings: IndexMap<(String, String), StringEncoding>, +/// Module-level metadata that's specific to one core WebAssembly module. This +/// is extracted with a [`Bindgen`]. +#[derive(Default)] +pub struct ModuleMetadata { + /// Per-function options imported into the core wasm module, currently only + /// related to string encoding. + pub import_encodings: IndexMap<(String, String), StringEncoding>, -// /// Per-function options exported from the core wasm module, currently only -// /// related to string encoding. -// pub export_encodings: IndexMap, -//} + /// Per-function options exported from the core wasm module, currently only + /// related to string encoding. + pub export_encodings: IndexMap, +} -///// This function will parse the `wasm` binary given as input and return a -///// [`Bindgen`] which extracts the custom sections describing component-level -///// types from within the binary itself. -///// -///// This is used to parse the output of `wit-bindgen`-generated modules and is -///// one of the earliest phases in transitioning such a module to a component. -///// The extraction here provides the metadata necessary to continue the process -///// later on. -///// -///// Note that a "stripped" binary where `component-type` sections are removed -///// is returned as well to embed within a component. -//pub fn decode(wasm: &[u8]) -> Result<(Vec, Bindgen)> { -// let mut ret = Bindgen::default(); -// let mut new_module = wasm_encoder::Module::new(); +/// This function will parse the `wasm` binary given as input and return a +/// [`Bindgen`] which extracts the custom sections describing component-level +/// types from within the binary itself. +/// +/// This is used to parse the output of `wit-bindgen`-generated modules and is +/// one of the earliest phases in transitioning such a module to a component. +/// The extraction here provides the metadata necessary to continue the process +/// later on. +/// +/// Note that a "stripped" binary where `component-type` sections are removed +/// is returned as well to embed within a component. +pub fn decode(wasm: &[u8]) -> Result<(Vec, Bindgen)> { + let mut ret = Bindgen::default(); + let mut new_module = wasm_encoder::Module::new(); -// for payload in wasmparser::Parser::new(0).parse_all(wasm) { -// let payload = payload.context("decoding item in module")?; -// match payload { -// wasmparser::Payload::CustomSection(cs) if cs.name().starts_with("component-type") => { -// let data = Bindgen::decode(cs.data()) -// .with_context(|| format!("decoding custom section {}", cs.name()))?; -// ret.merge(data) -// .with_context(|| format!("updating metadata for section {}", cs.name()))?; -// } -// _ => { -// if let Some((id, range)) = payload.as_section() { -// new_module.section(&wasm_encoder::RawSection { -// id, -// data: &wasm[range], -// }); -// } -// } -// } -// } + for payload in wasmparser::Parser::new(0).parse_all(wasm) { + let payload = payload.context("decoding item in module")?; + match payload { + wasmparser::Payload::CustomSection(cs) if cs.name().starts_with("component-type") => { + let data = Bindgen::decode(cs.data()) + .with_context(|| format!("decoding custom section {}", cs.name()))?; + ret.merge(data) + .with_context(|| format!("updating metadata for section {}", cs.name()))?; + } + _ => { + if let Some((id, range)) = payload.as_section() { + new_module.section(&wasm_encoder::RawSection { + id, + data: &wasm[range], + }); + } + } + } + } -// Ok((new_module.finish(), ret)) -//} + Ok((new_module.finish(), ret)) +} /// Creates a `component-type*` custom section to be decoded by `decode` above. /// @@ -148,110 +167,132 @@ pub fn encode(resolve: &Resolve, world: WorldId, encoding: StringEncoding) -> Re Ok(ret) } -//impl Bindgen { -// fn decode(data: &[u8]) -> Result { -// let mut reader = BinaryReader::new(data); -// let version = reader.read_u8()?; -// if version != CURRENT_VERSION { -// bail!("component-type version {version} does not match supported version {CURRENT_VERSION}"); -// } -// let encoding = match reader.read_u8()? { -// 0x00 => StringEncoding::UTF8, -// 0x01 => StringEncoding::UTF16, -// 0x02 => StringEncoding::CompactUTF16, -// byte => bail!("invalid string encoding {byte:#x}"), -// }; -// let name = reader.read_string()?; +impl Bindgen { + fn decode(data: &[u8]) -> Result { + let mut reader = BinaryReader::new(data); + let version = reader.read_u8()?; + if version != CURRENT_VERSION { + bail!("component-type version {version} does not match supported version {CURRENT_VERSION}"); + } + let encoding = match reader.read_u8()? { + 0x00 => StringEncoding::UTF8, + 0x01 => StringEncoding::UTF16, + 0x02 => StringEncoding::CompactUTF16, + byte => bail!("invalid string encoding {byte:#x}"), + }; + let pkg_name = reader.read_string()?; + let doc_name = reader.read_string()?; + let world_name = reader.read_string()?; -// let (doc, world) = decode_world(name, &data[reader.original_position()..])?; -// let metadata = ModuleMetadata::new(&doc, world, encoding); -// Ok(Bindgen { -// doc, -// world, -// metadata, -// }) -// } + let (resolve, pkg) = match crate::decode(pkg_name, &data[reader.original_position()..])? { + DecodedWasm::WitPackage(resolve, pkg) => (resolve, pkg), + DecodedWasm::Component(..) => bail!("expected an encoded wit package"), + }; + let doc = resolve.packages[pkg].documents[doc_name]; + let world = resolve.documents[doc].worlds[world_name]; + let metadata = ModuleMetadata::new(&resolve, world, encoding); + Ok(Bindgen { + resolve, + world, + metadata, + }) + } -// /// Merges another `BindgenMetadata` into this one. -// /// -// /// This operation is intended to be akin to "merging worlds" when the -// /// abstraction level for that is what we're working at here. For now the -// /// merge operation only succeeds if the two metadata descriptions are -// /// entirely disjoint. -// /// -// /// Note that at this time there's no support for changing string encodings -// /// between metadata. -// pub fn merge(&mut self, other: Bindgen) -> Result<()> { -// let Bindgen { -// doc, -// world, -// metadata: -// ModuleMetadata { -// import_encodings, -// export_encodings, -// }, -// } = other; + /// Merges another `BindgenMetadata` into this one. + /// + /// This operation is intended to be akin to "merging worlds" when the + /// abstraction level for that is what we're working at here. For now the + /// merge operation only succeeds if the two metadata descriptions are + /// entirely disjoint. + /// + /// Note that at this time there's no support for changing string encodings + /// between metadata. + pub fn merge(&mut self, other: Bindgen) -> Result<()> { + let Bindgen { + resolve, + world, + metadata: + ModuleMetadata { + import_encodings, + export_encodings, + }, + } = other; -// let world = self.doc.merge(doc).world_map[world.index()]; -// self.doc -// .merge_worlds(world, self.world) -// .context("failed to merge worlds from two documents")?; + let world = self.resolve.merge(resolve).worlds[world.index()]; + self.resolve + .merge_worlds(world, self.world) + .context("failed to merge worlds from two documents")?; -// for (name, encoding) in export_encodings { -// let prev = self -// .metadata -// .export_encodings -// .insert(name.clone(), encoding); -// if let Some(prev) = prev { -// if prev != encoding { -// bail!("conflicting string encodings specified for export `{name}`"); -// } -// } -// } -// for ((module, name), encoding) in import_encodings { -// let prev = self -// .metadata -// .import_encodings -// .insert((module.clone(), name.clone()), encoding); -// if let Some(prev) = prev { -// if prev != encoding { -// bail!("conflicting string encodings specified for import `{module}::{name}`"); -// } -// } -// } + for (name, encoding) in export_encodings { + let prev = self + .metadata + .export_encodings + .insert(name.clone(), encoding); + if let Some(prev) = prev { + if prev != encoding { + bail!("conflicting string encodings specified for export `{name}`"); + } + } + } + for ((module, name), encoding) in import_encodings { + let prev = self + .metadata + .import_encodings + .insert((module.clone(), name.clone()), encoding); + if let Some(prev) = prev { + if prev != encoding { + bail!("conflicting string encodings specified for import `{module}::{name}`"); + } + } + } -// Ok(()) -// } -//} + Ok(()) + } +} -//impl ModuleMetadata { -// /// Creates a new `ModuleMetadata` instance holding the given set of -// /// interfaces which are expected to all use the `encoding` specified. -// pub fn new(doc: &Document, world: WorldId, encoding: StringEncoding) -> ModuleMetadata { -// let mut ret = ModuleMetadata::default(); +impl ModuleMetadata { + /// Creates a new `ModuleMetadata` instance holding the given set of + /// interfaces which are expected to all use the `encoding` specified. + pub fn new(resolve: &Resolve, world: WorldId, encoding: StringEncoding) -> ModuleMetadata { + let mut ret = ModuleMetadata::default(); -// if let Some(iface) = doc.worlds[world].default { -// for func in doc.interfaces[iface].functions.iter() { -// let name = func.core_export_name(None); -// let prev = ret.export_encodings.insert(name.to_string(), encoding); -// assert!(prev.is_none()); -// } -// } + let world = &resolve.worlds[world]; + for (name, item) in world.imports.iter() { + match item { + WorldItem::Function(_) => { + let prev = ret + .import_encodings + .insert((String::new(), name.clone()), encoding); + assert!(prev.is_none()); + } + WorldItem::Interface(i) => { + for (func, _) in resolve.interfaces[*i].functions.iter() { + let prev = ret + .import_encodings + .insert((name.clone(), func.clone()), encoding); + assert!(prev.is_none()); + } + } + } + } -// for (name, import) in doc.worlds[world].imports.iter() { -// for func in doc.interfaces[*import].functions.iter() { -// let key = (name.clone(), func.name.clone()); -// let prev = ret.import_encodings.insert(key, encoding); -// assert!(prev.is_none()); -// } -// } -// for (name, export) in doc.worlds[world].exports.iter() { -// for func in doc.interfaces[*export].functions.iter() { -// let name = func.core_export_name(Some(name)); -// let prev = ret.export_encodings.insert(name.to_string(), encoding); -// assert!(prev.is_none()); -// } -// } -// ret -// } -//} + for (name, item) in world.exports.iter() { + match item { + WorldItem::Function(func) => { + let name = func.core_export_name(None).into_owned(); + let prev = ret.export_encodings.insert(name, encoding); + assert!(prev.is_none()); + } + WorldItem::Interface(i) => { + for (_, func) in resolve.interfaces[*i].functions.iter() { + let name = func.core_export_name(Some(name)).into_owned(); + let prev = ret.export_encodings.insert(name, encoding); + assert!(prev.is_none()); + } + } + } + } + + ret + } +} diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 669d676f12..833814df1b 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -20,9 +20,8 @@ impl DocumentPrinter { writeln!(&mut self.output, "}}\n")?; } - for id in doc.worlds.iter() { + for (name, id) in doc.worlds.iter() { let world = &resolve.worlds[*id]; - let name = &world.name; writeln!(&mut self.output, "world {name} {{")?; for (name, import) in world.imports.iter() { self.print_world_item(resolve, name, import, docid, "import")?; @@ -95,44 +94,49 @@ impl DocumentPrinter { if i > 0 { self.output.push_str("\n"); } - write!(&mut self.output, "{name}: func(")?; - for (i, (name, ty)) in func.params.iter().enumerate() { - if i > 0 { - self.output.push_str(", "); - } - write!(&mut self.output, "{}: ", name)?; - self.print_type_name(resolve, ty)?; + write!(&mut self.output, "{name}: ")?; + self.print_function(resolve, func)?; + self.output.push_str("\n"); + } + + Ok(()) + } + + fn print_function(&mut self, resolve: &Resolve, func: &Function) -> Result<()> { + self.output.push_str("func("); + for (i, (name, ty)) in func.params.iter().enumerate() { + if i > 0 { + self.output.push_str(", "); } - self.output.push_str(")"); - - match &func.results { - Results::Named(rs) => match rs.len() { - 0 => (), - 1 => { - self.output.push_str(" -> "); - self.print_type_name(resolve, &rs[0].1)?; - } - _ => { - self.output.push_str(" -> ("); - for (i, (name, ty)) in rs.iter().enumerate() { - if i > 0 { - self.output.push_str(", "); - } - write!(&mut self.output, "{name}: ")?; - self.print_type_name(resolve, ty)?; + write!(&mut self.output, "{}: ", name)?; + self.print_type_name(resolve, ty)?; + } + self.output.push_str(")"); + + match &func.results { + Results::Named(rs) => match rs.len() { + 0 => (), + 1 => { + self.output.push_str(" -> "); + self.print_type_name(resolve, &rs[0].1)?; + } + _ => { + self.output.push_str(" -> ("); + for (i, (name, ty)) in rs.iter().enumerate() { + if i > 0 { + self.output.push_str(", "); } - self.output.push_str(")"); + write!(&mut self.output, "{name}: ")?; + self.print_type_name(resolve, ty)?; } - }, - Results::Anon(ty) => { - self.output.push_str(" -> "); - self.print_type_name(resolve, ty)?; + self.output.push_str(")"); } + }, + Results::Anon(ty) => { + self.output.push_str(" -> "); + self.print_type_name(resolve, ty)?; } - - self.output.push_str("\n"); } - Ok(()) } @@ -157,8 +161,8 @@ impl DocumentPrinter { } } WorldItem::Function(f) => { - drop(f); - panic!(); + self.print_function(resolve, f)?; + self.output.push_str("\n"); } } Ok(()) diff --git a/crates/wit-component/src/validation.rs b/crates/wit-component/src/validation.rs index a66a93ae01..2e8c72f5f9 100644 --- a/crates/wit-component/src/validation.rs +++ b/crates/wit-component/src/validation.rs @@ -1,5 +1,5 @@ use crate::metadata::{Bindgen, ModuleMetadata}; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; use indexmap::{map::Entry, IndexMap, IndexSet}; use wasmparser::{ types::Types, Encoding, ExternalKind, FuncType, Parser, Payload, TypeRef, ValType, @@ -7,7 +7,7 @@ use wasmparser::{ }; use wit_parser::{ abi::{AbiVariant, WasmSignature, WasmType}, - Document, InterfaceId, WorldId, + Function, InterfaceId, Resolve, WorldId, WorldItem, }; fn is_canonical_function(name: &str) -> bool { @@ -154,17 +154,24 @@ pub fn validate_module<'a>( } let types = types.unwrap(); - let world = &metadata.doc.worlds[metadata.world]; + let world = &metadata.resolve.worlds[metadata.world]; for (name, funcs) in &import_funcs { + // An empty module name is indicative of the top-level import namespace, + // so look for top-level functions here. if name.is_empty() { - bail!("module imports from an empty module name"); + validate_imports_top_level(&metadata.resolve, metadata.world, funcs, &types)?; + let funcs = funcs.keys().cloned().collect(); + let prev = ret.required_imports.insert("", funcs); + assert!(prev.is_none()); + continue; } match world.imports.get(*name) { - Some(interface) => { - validate_imported_interface(&metadata.doc, *interface, name, funcs, &types)?; - let funcs = funcs.into_iter().map(|(f, _ty)| *f).collect(); + Some(WorldItem::Interface(interface)) => { + let funcs = + validate_imported_interface(&metadata.resolve, *interface, name, funcs, &types) + .with_context(|| format!("failed to validate import interface `{name}`"))?; let prev = ret.required_imports.insert(name, funcs); assert!(prev.is_none()); } @@ -175,20 +182,14 @@ pub fn validate_module<'a>( map.insert(func, ty.clone()); } } - None => bail!("module requires an import interface named `{}`", name), + None | Some(WorldItem::Function(_)) => { + bail!("module requires an import interface named `{}`", name) + } } } - if let Some(interface) = world.default { - validate_exported_interface(&metadata.doc, interface, None, &export_funcs, &types)?; - } - - for (name, interface) in world.exports.iter() { - if name.is_empty() { - bail!("cannot export an interface with an empty name"); - } - - validate_exported_interface(&metadata.doc, *interface, Some(name), &export_funcs, &types)?; + for (name, item) in world.exports.iter() { + validate_exported_item(&metadata.resolve, item, name, &export_funcs, &types)?; } Ok(ret) @@ -242,7 +243,7 @@ pub struct ValidatedAdapter<'a> { /// didn't accidentally break the wasm module. pub fn validate_adapter_module<'a>( bytes: &[u8], - doc: &'a Document, + resolve: &'a Resolve, world: WorldId, metadata: &'a ModuleMetadata, required: &IndexMap, @@ -339,15 +340,46 @@ pub fn validate_adapter_module<'a>( if name == MAIN_MODULE_IMPORT_NAME { ret.needs_core_exports .extend(funcs.iter().map(|(name, _ty)| name.to_string())); - } else { - let interface = *doc.worlds[world] + continue; + } + + // An empty module name is indicative of the top-level import namespace, + // so look for top-level functions here. + if name.is_empty() { + validate_imports_top_level(&resolve, world, &funcs, &types)?; + let funcs = resolve.worlds[world] .imports - .get(name) - .ok_or_else(|| anyhow!("adapter module imports unknown module `{name}`"))?; - let required_funcs = validate_imported_interface(doc, interface, name, &funcs, &types)?; - let iface_name = &doc.interfaces[interface].name; - assert_eq!(iface_name, name); - ret.required_imports.insert(iface_name, required_funcs); + .iter() + .filter_map(|(name, item)| match item { + WorldItem::Function(_) if funcs.contains_key(name.as_str()) => { + Some(name.as_str()) + } + _ => None, + }) + .collect(); + ret.required_imports.insert("", funcs); + continue; + } + + match resolve.worlds[world].imports.get_full(name) { + Some((_, name, WorldItem::Interface(interface))) => { + validate_imported_interface(resolve, *interface, name, &funcs, &types) + .with_context(|| format!("failed to validate import interface `{name}`"))?; + let funcs = resolve.interfaces[*interface] + .functions + .keys() + .map(|s| s.as_str()) + .filter(|s| funcs.contains_key(s)) + .collect(); + let prev = ret.required_imports.insert(name, funcs); + assert!(prev.is_none()); + } + None | Some((_, _, WorldItem::Function(_))) => { + bail!( + "adapter module requires an import interface named `{}`", + name + ) + } } } @@ -375,8 +407,26 @@ pub fn validate_adapter_module<'a>( Ok(ret) } +fn validate_imports_top_level<'a>( + resolve: &Resolve, + world: WorldId, + funcs: &IndexMap<&'a str, u32>, + types: &Types, +) -> Result<()> { + for (name, ty) in funcs { + let func = match resolve.worlds[world].imports.get(*name) { + Some(WorldItem::Function(func)) => func, + Some(_) => bail!("expected world top-level import `{name}` to be a function"), + None => bail!("no top-level imported function `{name}` specified"), + }; + let ty = types.func_type_at(*ty).unwrap(); + validate_func(resolve, ty, func, AbiVariant::GuestImport)?; + } + Ok(()) +} + fn validate_imported_interface<'a>( - doc: &'a Document, + resolve: &'a Resolve, interface: InterfaceId, name: &str, imports: &IndexMap<&str, u32>, @@ -384,10 +434,9 @@ fn validate_imported_interface<'a>( ) -> Result> { let mut funcs = IndexSet::new(); for (func_name, ty) in imports { - let f = doc.interfaces[interface] + let f = resolve.interfaces[interface] .functions - .iter() - .find(|f| f.name == *func_name) + .get(*func_name) .ok_or_else(|| { anyhow!( "import interface `{}` is missing function `{}` that is required by the module", @@ -396,19 +445,8 @@ fn validate_imported_interface<'a>( ) })?; - let expected = wasm_sig_to_func_type(doc.wasm_signature(AbiVariant::GuestImport, f)); let ty = types.func_type_at(*ty).unwrap(); - if ty != &expected { - bail!( - "type mismatch for function `{}` on imported interface `{}`: expected `{:?} -> {:?}` but found `{:?} -> {:?}`", - f.name, - name, - expected.params(), - expected.results(), - ty.params(), - ty.results() - ); - } + validate_func(resolve, ty, f, AbiVariant::GuestImport)?; funcs.insert(f.name.as_str()); } @@ -416,53 +454,56 @@ fn validate_imported_interface<'a>( Ok(funcs) } -fn validate_exported_interface( - doc: &Document, - interface: InterfaceId, - export_name: Option<&str>, +fn validate_func( + resolve: &Resolve, + ty: &wasmparser::FuncType, + func: &Function, + abi: AbiVariant, +) -> Result<()> { + let expected = wasm_sig_to_func_type(resolve.wasm_signature(abi, func)); + if ty != &expected { + bail!( + "type mismatch for function `{}`: expected `{:?} -> {:?}` but found `{:?} -> {:?}`", + func.name, + expected.params(), + expected.results(), + ty.params(), + ty.results() + ); + } + + Ok(()) +} + +fn validate_exported_item( + resolve: &Resolve, + item: &WorldItem, + export_name: &str, exports: &IndexMap<&str, u32>, types: &Types, ) -> Result<()> { - for f in &doc.interfaces[interface].functions { - let expected_export_name = f.core_export_name(export_name); + let validate = |func: &Function, name: Option<&str>| { + let expected_export_name = func.core_export_name(name); match exports.get(expected_export_name.as_ref()) { Some(func_index) => { - let expected_ty = - wasm_sig_to_func_type(doc.wasm_signature(AbiVariant::GuestExport, f)); let ty = types.function_at(*func_index).unwrap(); - if ty == &expected_ty { - continue; - } - match export_name { - Some(name) => { - bail!( - "type mismatch for function `{}` from exported interface `{name}`: \ - expected `{:?} -> {:?}` but found `{:?} -> {:?}`", - f.name, - expected_ty.params(), - expected_ty.results(), - ty.params(), - ty.results() - ); - } - None => { - bail!( - "type mismatch for default interface function `{}`: \ - expected `{:?} -> {:?}` but found `{:?} -> {:?}`", - f.name, - expected_ty.params(), - expected_ty.results(), - ty.params(), - ty.results() - ); - } - } + validate_func(resolve, ty, func, AbiVariant::GuestExport) } None => bail!( "module does not export required function `{}`", expected_export_name ), } + }; + match item { + WorldItem::Function(func) => validate(func, None)?, + WorldItem::Interface(interface) => { + for (_, f) in &resolve.interfaces[*interface].functions { + validate(f, Some(export_name)).with_context(|| { + format!("failed to validate exported interface `{export_name}`") + })?; + } + } } Ok(()) diff --git a/crates/wit-component/tests/components.rs b/crates/wit-component/tests/components.rs index 754ef901eb..f595d2e102 100644 --- a/crates/wit-component/tests/components.rs +++ b/crates/wit-component/tests/components.rs @@ -1,22 +1,9 @@ -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use pretty_assertions::assert_eq; use std::{fs, path::Path}; use wasm_encoder::{Encode, Section}; use wit_component::{ComponentEncoder, StringEncoding}; -use wit_parser::Document; - -fn read_adapters(dir: &Path) -> Result, Document)>> { - glob::glob(dir.join("adapt-*.wat").to_str().unwrap())? - .map(|p| { - let p = p?; - let adapter = - wat::parse_file(&p).with_context(|| format!("expected file `{}`", p.display()))?; - let stem = p.file_stem().unwrap().to_str().unwrap(); - let doc = read_document(dir, &format!("{stem}-"))?; - Ok((stem.trim_start_matches("adapt-").to_string(), adapter, doc)) - }) - .collect::>() -} +use wit_parser::{Resolve, UnresolvedPackage}; /// Tests the encoding of components. /// @@ -24,21 +11,25 @@ fn read_adapters(dir: &Path) -> Result, Document)>> { /// /// The expected input files for a test case are: /// -/// * [required] `module.wat` - contains the core module definition to be encoded -/// as a component. -/// * [optional] `default.wit` - represents the component's default interface. -/// * [optional] `export-.wit` - represents an interface exported by the component. -/// * [optional] `import-.wit` - represents an interface imported by the component. +/// * [required] `module.wat` - contains the core module definition to be +/// encoded as a component. +/// * [required] `module.wit` - WIT package describing the interface of +/// `module.wat`. Must have a `default world` +/// * [optional] `adapt-$name.wat` - optional adapter for the module name +/// `$name`, can be specified for multiple `$name`s +/// * [optional] `adapt-$name.wit` - required for each `*.wat` adapter to +/// describe imports/exports of the adapter. /// /// And the output files are one of the following: /// -/// * `component.wat` - the expected encoded component in text format if the encoding -/// is expected to succeed. -/// * `error.txt` - the expected error message if the encoding is expected to fail. +/// * `component.wat` - the expected encoded component in text format if the +/// encoding is expected to succeed. +/// * `error.txt` - the expected error message if the encoding is expected to +/// fail. /// -/// The test encodes a component based on the input files. If the encoding succeeds, -/// it expects the output to match `component.wat`. If the encoding fails, it expects -/// the output to match `error.txt`. +/// The test encodes a component based on the input files. If the encoding +/// succeeds, it expects the output to match `component.wat`. If the encoding +/// fails, it expects the output to match `error.txt`. /// /// Run the test with the environment variable `BLESS` set to update /// either `component.wat` or `error.txt` depending on the outcome of the encoding. @@ -58,85 +49,54 @@ fn component_encoding_via_flags() -> Result<()> { let module_path = path.join("module.wat"); let component_path = path.join("component.wat"); let error_path = path.join("error.txt"); - let module = wat::parse_file(&module_path) - .with_context(|| format!("expected file `{}`", module_path.display()))?; - let document = read_document(&path, "")?; - - // Test the generated component using the `.document(...)` method - { - println!("test using `.document(...)`"); - let mut encoder = ComponentEncoder::default() - .module(&module)? - .validate(true) - .document(document.clone(), StringEncoding::UTF8)?; - encoder = add_adapters(encoder, &path)?; - assert_output(test_case, &encoder, &component_path, &error_path)?; - } - - // Test the generated component by embedding the component type - // information in a custom section. - { - println!("test using custom section"); - let mut module = module.clone(); - let contents = wit_component::metadata::encode( - &document, - document.default_world()?, - StringEncoding::UTF8, - ); - let section = wasm_encoder::CustomSection { - name: "component-type", - data: &contents, - }; - module.push(section.id()); - section.encode(&mut module); - - // Now parse run the `module` alone through the encoder without extra - // information about interfaces to ensure it still works as before. - let mut encoder = ComponentEncoder::default().module(&module)?.validate(true); - encoder = add_adapters(encoder, &path)?; - assert_output(test_case, &encoder, &component_path, &error_path)?; - } - - // Test the `--types-only` component is valid - { - println!("test using --types-only"); - let mut encoder = ComponentEncoder::default() - .validate(true) - .types_only(true) - .document(document.clone(), StringEncoding::UTF8)?; - encoder = add_adapters(encoder, &path)?; - encoder.encode()?; - } + let module = read_core_module(&module_path)?; + let mut encoder = ComponentEncoder::default().module(&module)?.validate(true); + encoder = add_adapters(encoder, &path)?; + assert_output(test_case, &encoder, &component_path, &error_path)?; } Ok(()) } -fn read_document(path: &Path, prefix: &str) -> Result { - let wit_path = path.join(&format!("{prefix}world.wit")); - Document::parse_file(&wit_path) -} - fn add_adapters(mut encoder: ComponentEncoder, path: &Path) -> Result { - for (name, mut wasm, doc) in read_adapters(path)? { - // Create a `component-type` custom section by slurping up `imports` as - // a "world" and encoding it. - let contents = - wit_component::metadata::encode(&doc, doc.default_world()?, StringEncoding::UTF8); - let section = wasm_encoder::CustomSection { - name: "component-type", - data: &contents, - }; - wasm.push(section.id()); - section.encode(&mut wasm); - - // Then register our new wasm blob which has the necessary custom - // section. + for adapter in glob::glob(path.join("adapt-*.wat").to_str().unwrap())? { + let adapter = adapter?; + let wasm = read_core_module(&adapter)?; + let stem = adapter.file_stem().unwrap().to_str().unwrap(); + let name = stem.trim_start_matches("adapt-"); encoder = encoder.adapter(&name, &wasm)?; } Ok(encoder) } +/// Parses the core wasm module at `path`, expected as a `*.wat` file. +/// +/// Additionally expects a sibling `*.wit` file which will be used to encode +/// metadata into the binary returned here. +fn read_core_module(path: &Path) -> Result> { + let mut wasm = wat::parse_file(path)?; + let interface = path.with_extension("wit"); + let mut resolve = Resolve::default(); + let pkg = resolve.push( + UnresolvedPackage::parse_file(&interface)?, + &Default::default(), + )?; + let doc = *resolve.packages[pkg].documents.iter().next().unwrap().1; + let doc = &resolve.documents[doc]; + let world = doc + .default_world + .ok_or_else(|| anyhow!("no default world specified"))?; + let encoded = wit_component::metadata::encode(&resolve, world, StringEncoding::UTF8)?; + + let section = wasm_encoder::CustomSection { + name: "component-type", + data: &encoded, + }; + wasm.push(section.id()); + section.encode(&mut wasm); + Ok(wasm) +} + fn assert_output( test_case: &str, encoder: &ComponentEncoder, @@ -148,7 +108,7 @@ fn assert_output( let (output, baseline_path) = if error_path.is_file() { match r { Ok(_) => bail!("encoding should fail for test case `{}`", test_case), - Err(e) => (e.to_string(), &error_path), + Err(e) => (format!("{e:?}"), &error_path), } } else { ( diff --git a/crates/wit-component/tests/components/adapt-empty-interface/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-empty-interface/adapt-old.wit similarity index 77% rename from crates/wit-component/tests/components/adapt-empty-interface/adapt-old-world.wit rename to crates/wit-component/tests/components/adapt-empty-interface/adapt-old.wit index 462f31119c..e38000c63e 100644 --- a/crates/wit-component/tests/components/adapt-empty-interface/adapt-old-world.wit +++ b/crates/wit-component/tests/components/adapt-empty-interface/adapt-old.wit @@ -1,4 +1,4 @@ -world new { +default world new { import new: interface { thunk-that-is-not-called: func() } diff --git a/crates/wit-component/tests/components/adapt-empty-interface/module.wit b/crates/wit-component/tests/components/adapt-empty-interface/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-empty-interface/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-empty-interface/world.wit b/crates/wit-component/tests/components/adapt-empty-interface/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-empty-interface/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-export-default/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-export-default/adapt-old-world.wit deleted file mode 100644 index e1b55d8198..0000000000 --- a/crates/wit-component/tests/components/adapt-export-default/adapt-old-world.wit +++ /dev/null @@ -1,5 +0,0 @@ -world new { - default export interface { - entrypoint: func() - } -} diff --git a/crates/wit-component/tests/components/adapt-export-default/adapt-old.wit b/crates/wit-component/tests/components/adapt-export-default/adapt-old.wit new file mode 100644 index 0000000000..ad8e637513 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-default/adapt-old.wit @@ -0,0 +1,3 @@ +default world new { + export entrypoint: func() +} diff --git a/crates/wit-component/tests/components/adapt-export-default/component.wat b/crates/wit-component/tests/components/adapt-export-default/component.wat index 443cf11ba8..833335c7e2 100644 --- a/crates/wit-component/tests/components/adapt-export-default/component.wat +++ b/crates/wit-component/tests/components/adapt-export-default/component.wat @@ -18,8 +18,8 @@ (with "__main_module__" (instance 1)) ) ) - (alias core export 2 "entrypoint" (core func (;1;))) (type (;0;) (func)) + (alias core export 2 "entrypoint" (core func (;1;))) (func (;0;) (type 0) (canon lift (core func 1))) (export (;1;) "entrypoint" (func 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/adapt-export-default/module.wit b/crates/wit-component/tests/components/adapt-export-default/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-default/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-export-default/world.wit b/crates/wit-component/tests/components/adapt-export-default/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-export-default/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-export-namespaced/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-export-namespaced/adapt-old-world.wit deleted file mode 100644 index 179ed9b1bb..0000000000 --- a/crates/wit-component/tests/components/adapt-export-namespaced/adapt-old-world.wit +++ /dev/null @@ -1,7 +0,0 @@ -interface new { - entrypoint: func() -} - -world brave-new-world { - export new: new -} diff --git a/crates/wit-component/tests/components/adapt-export-namespaced/adapt-old.wit b/crates/wit-component/tests/components/adapt-export-namespaced/adapt-old.wit new file mode 100644 index 0000000000..4e4ff9c6bf --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-namespaced/adapt-old.wit @@ -0,0 +1,7 @@ +interface new { + entrypoint: func() +} + +default world brave-new-world { + export new: self.new +} diff --git a/crates/wit-component/tests/components/adapt-export-namespaced/component.wat b/crates/wit-component/tests/components/adapt-export-namespaced/component.wat index 0871a2bc80..20cd105de8 100644 --- a/crates/wit-component/tests/components/adapt-export-namespaced/component.wat +++ b/crates/wit-component/tests/components/adapt-export-namespaced/component.wat @@ -18,8 +18,8 @@ (with "__main_module__" (instance 1)) ) ) - (alias core export 2 "new#entrypoint" (core func (;1;))) (type (;0;) (func)) + (alias core export 2 "new#entrypoint" (core func (;1;))) (func (;0;) (type 0) (canon lift (core func 1))) (instance (;0;) (export "entrypoint" (func 0)) diff --git a/crates/wit-component/tests/components/adapt-export-namespaced/module.wit b/crates/wit-component/tests/components/adapt-export-namespaced/module.wit new file mode 100644 index 0000000000..234a91911c --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-namespaced/module.wit @@ -0,0 +1 @@ +default world foo {} diff --git a/crates/wit-component/tests/components/adapt-export-namespaced/world.wit b/crates/wit-component/tests/components/adapt-export-namespaced/world.wit deleted file mode 100644 index 911d40a020..0000000000 --- a/crates/wit-component/tests/components/adapt-export-namespaced/world.wit +++ /dev/null @@ -1 +0,0 @@ -world foo {} diff --git a/crates/wit-component/tests/components/adapt-export-reallocs/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-export-reallocs/adapt-old-world.wit deleted file mode 100644 index c1703a3cbe..0000000000 --- a/crates/wit-component/tests/components/adapt-export-reallocs/adapt-old-world.wit +++ /dev/null @@ -1,8 +0,0 @@ -world brave-new-world { - import new: interface { - read: func(amt: u32) -> list - } - default export interface { - entrypoint: func(args: list) - } -} diff --git a/crates/wit-component/tests/components/adapt-export-reallocs/adapt-old.wit b/crates/wit-component/tests/components/adapt-export-reallocs/adapt-old.wit new file mode 100644 index 0000000000..d342fe79d2 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-reallocs/adapt-old.wit @@ -0,0 +1,6 @@ +default world brave-new-world { + import new: interface { + read: func(amt: u32) -> list + } + export entrypoint: func(args: list) +} diff --git a/crates/wit-component/tests/components/adapt-export-reallocs/component.wat b/crates/wit-component/tests/components/adapt-export-reallocs/component.wat index d5ad819db4..aaa69c4899 100644 --- a/crates/wit-component/tests/components/adapt-export-reallocs/component.wat +++ b/crates/wit-component/tests/components/adapt-export-reallocs/component.wat @@ -110,9 +110,9 @@ (with "" (instance 5)) ) ) - (alias core export 4 "entrypoint" (core func (;6;))) (type (;1;) (list string)) (type (;2;) (func (param "args" 1))) + (alias core export 4 "entrypoint" (core func (;6;))) (func (;1;) (type 2) (canon lift (core func 6) (memory 0) (realloc 2) string-encoding=utf8)) (export (;2;) "entrypoint" (func 1)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/adapt-export-reallocs/module.wit b/crates/wit-component/tests/components/adapt-export-reallocs/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-reallocs/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-export-reallocs/world.wit b/crates/wit-component/tests/components/adapt-export-reallocs/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-export-reallocs/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-export-save-args/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-export-save-args/adapt-old-world.wit deleted file mode 100644 index ae5567533a..0000000000 --- a/crates/wit-component/tests/components/adapt-export-save-args/adapt-old-world.wit +++ /dev/null @@ -1,5 +0,0 @@ -world brave-new-world { - default export interface { - entrypoint: func(nargs: u32) - } -} diff --git a/crates/wit-component/tests/components/adapt-export-save-args/adapt-old.wit b/crates/wit-component/tests/components/adapt-export-save-args/adapt-old.wit new file mode 100644 index 0000000000..3f6bd6a293 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-save-args/adapt-old.wit @@ -0,0 +1,3 @@ +default world brave-new-world { + export entrypoint: func(nargs: u32) +} diff --git a/crates/wit-component/tests/components/adapt-export-save-args/component.wat b/crates/wit-component/tests/components/adapt-export-save-args/component.wat index 233601f396..c2f2da94f6 100644 --- a/crates/wit-component/tests/components/adapt-export-save-args/component.wat +++ b/crates/wit-component/tests/components/adapt-export-save-args/component.wat @@ -66,8 +66,8 @@ (with "" (instance 5)) ) ) - (alias core export 4 "entrypoint" (core func (;3;))) (type (;0;) (func (param "nargs" u32))) + (alias core export 4 "entrypoint" (core func (;3;))) (func (;0;) (type 0) (canon lift (core func 3))) (export (;1;) "entrypoint" (func 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/adapt-export-save-args/module.wit b/crates/wit-component/tests/components/adapt-export-save-args/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-save-args/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-export-save-args/world.wit b/crates/wit-component/tests/components/adapt-export-save-args/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-export-save-args/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-inject-stack/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-inject-stack/adapt-old.wit similarity index 69% rename from crates/wit-component/tests/components/adapt-inject-stack/adapt-old-world.wit rename to crates/wit-component/tests/components/adapt-inject-stack/adapt-old.wit index 45a9913be5..7a56eb164a 100644 --- a/crates/wit-component/tests/components/adapt-inject-stack/adapt-old-world.wit +++ b/crates/wit-component/tests/components/adapt-inject-stack/adapt-old.wit @@ -1,4 +1,4 @@ -world brave-new-world { +default world brave-new-world { import new: interface { get-two: func() -> (a: u32, b: u32) } diff --git a/crates/wit-component/tests/components/adapt-inject-stack/module.wit b/crates/wit-component/tests/components/adapt-inject-stack/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-inject-stack/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-inject-stack/world.wit b/crates/wit-component/tests/components/adapt-inject-stack/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-inject-stack/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-list-return/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-list-return/adapt-old.wit similarity index 65% rename from crates/wit-component/tests/components/adapt-list-return/adapt-old-world.wit rename to crates/wit-component/tests/components/adapt-list-return/adapt-old.wit index 658b39af1d..d321768c2f 100644 --- a/crates/wit-component/tests/components/adapt-list-return/adapt-old-world.wit +++ b/crates/wit-component/tests/components/adapt-list-return/adapt-old.wit @@ -1,4 +1,4 @@ -world brave-new-world { +default world brave-new-world { import new: interface { read: func() -> list } diff --git a/crates/wit-component/tests/components/adapt-list-return/module.wit b/crates/wit-component/tests/components/adapt-list-return/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-list-return/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-list-return/world.wit b/crates/wit-component/tests/components/adapt-list-return/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-list-return/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-unused/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-memory-simple/adapt-old.wit similarity index 64% rename from crates/wit-component/tests/components/adapt-unused/adapt-old-world.wit rename to crates/wit-component/tests/components/adapt-memory-simple/adapt-old.wit index b16de0ffd0..5a86aec124 100644 --- a/crates/wit-component/tests/components/adapt-unused/adapt-old-world.wit +++ b/crates/wit-component/tests/components/adapt-memory-simple/adapt-old.wit @@ -1,4 +1,4 @@ -world brave-new-world { +default world brave-new-world { import new: interface { log: func(s: string) } diff --git a/crates/wit-component/tests/components/adapt-memory-simple/module.wit b/crates/wit-component/tests/components/adapt-memory-simple/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-memory-simple/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-memory-simple/world.wit b/crates/wit-component/tests/components/adapt-memory-simple/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-memory-simple/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-missing-memory/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-missing-memory/adapt-old.wit similarity index 64% rename from crates/wit-component/tests/components/adapt-missing-memory/adapt-old-world.wit rename to crates/wit-component/tests/components/adapt-missing-memory/adapt-old.wit index b16de0ffd0..5a86aec124 100644 --- a/crates/wit-component/tests/components/adapt-missing-memory/adapt-old-world.wit +++ b/crates/wit-component/tests/components/adapt-missing-memory/adapt-old.wit @@ -1,4 +1,4 @@ -world brave-new-world { +default world brave-new-world { import new: interface { log: func(s: string) } diff --git a/crates/wit-component/tests/components/adapt-missing-memory/module.wit b/crates/wit-component/tests/components/adapt-missing-memory/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-missing-memory/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-missing-memory/world.wit b/crates/wit-component/tests/components/adapt-missing-memory/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-missing-memory/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-multiple/adapt-foo-world.wit b/crates/wit-component/tests/components/adapt-multiple/adapt-foo.wit similarity index 76% rename from crates/wit-component/tests/components/adapt-multiple/adapt-foo-world.wit rename to crates/wit-component/tests/components/adapt-multiple/adapt-foo.wit index bd18fe09fa..bf449c18ec 100644 --- a/crates/wit-component/tests/components/adapt-multiple/adapt-foo-world.wit +++ b/crates/wit-component/tests/components/adapt-multiple/adapt-foo.wit @@ -1,4 +1,4 @@ -world new-foo-world { +default world new-foo-world { import other1: interface { foo: func() } diff --git a/crates/wit-component/tests/components/adapt-multiple/module.wit b/crates/wit-component/tests/components/adapt-multiple/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-multiple/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-multiple/world.wit b/crates/wit-component/tests/components/adapt-multiple/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-multiple/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/adapt-preview1/adapt-wasi_snapshot_preview1.wat b/crates/wit-component/tests/components/adapt-preview1/adapt-wasi-snapshot-preview1.wat similarity index 100% rename from crates/wit-component/tests/components/adapt-preview1/adapt-wasi_snapshot_preview1.wat rename to crates/wit-component/tests/components/adapt-preview1/adapt-wasi-snapshot-preview1.wat diff --git a/crates/wit-component/tests/components/adapt-preview1/adapt-wasi_snapshot_preview1-world.wit b/crates/wit-component/tests/components/adapt-preview1/adapt-wasi-snapshot-preview1.wit similarity index 82% rename from crates/wit-component/tests/components/adapt-preview1/adapt-wasi_snapshot_preview1-world.wit rename to crates/wit-component/tests/components/adapt-preview1/adapt-wasi-snapshot-preview1.wit index 7a7ea2370b..65035733e0 100644 --- a/crates/wit-component/tests/components/adapt-preview1/adapt-wasi_snapshot_preview1-world.wit +++ b/crates/wit-component/tests/components/adapt-preview1/adapt-wasi-snapshot-preview1.wit @@ -7,6 +7,6 @@ interface my-wasi { something-not-used: func() } -world adapter { - import my-wasi: my-wasi +default world adapter { + import my-wasi: self.my-wasi } diff --git a/crates/wit-component/tests/components/adapt-preview1/component.wat b/crates/wit-component/tests/components/adapt-preview1/component.wat index 6ec2d54179..8b961de798 100644 --- a/crates/wit-component/tests/components/adapt-preview1/component.wat +++ b/crates/wit-component/tests/components/adapt-preview1/component.wat @@ -18,8 +18,8 @@ (type (;1;) (func (param i32))) (type (;2;) (func (param i32 i32) (result i32))) (import "foo" "foo" (func (;0;) (type 0))) - (import "wasi_snapshot_preview1" "proc_exit" (func (;1;) (type 1))) - (import "wasi_snapshot_preview1" "random_get" (func (;2;) (type 2))) + (import "wasi-snapshot-preview1" "proc_exit" (func (;1;) (type 1))) + (import "wasi-snapshot-preview1" "random_get" (func (;2;) (type 2))) (memory (;0;) 1) (export "memory" (memory 0)) ) @@ -40,20 +40,20 @@ (core module (;2;) (type (;0;) (func (param i32))) (type (;1;) (func (param i32 i32) (result i32))) - (func $adapt-wasi_snapshot_preview1-proc_exit (;0;) (type 0) (param i32) + (func $adapt-wasi-snapshot-preview1-proc_exit (;0;) (type 0) (param i32) local.get 0 i32.const 0 call_indirect (type 0) ) - (func $adapt-wasi_snapshot_preview1-random_get (;1;) (type 1) (param i32 i32) (result i32) + (func $adapt-wasi-snapshot-preview1-random_get (;1;) (type 1) (param i32 i32) (result i32) local.get 0 local.get 1 i32.const 1 call_indirect (type 1) ) (table (;0;) 2 2 funcref) - (export "0" (func $adapt-wasi_snapshot_preview1-proc_exit)) - (export "1" (func $adapt-wasi_snapshot_preview1-random_get)) + (export "0" (func $adapt-wasi-snapshot-preview1-proc_exit)) + (export "1" (func $adapt-wasi-snapshot-preview1-random_get)) (export "$imports" (table 0)) ) (core module (;3;) @@ -78,7 +78,7 @@ ) (core instance (;3;) (instantiate 0 (with "foo" (instance 1)) - (with "wasi_snapshot_preview1" (instance 2)) + (with "wasi-snapshot-preview1" (instance 2)) ) ) (alias core export 3 "memory" (core memory (;0;))) diff --git a/crates/wit-component/tests/components/adapt-preview1/module.wat b/crates/wit-component/tests/components/adapt-preview1/module.wat index 370ede67ac..70449b7e35 100644 --- a/crates/wit-component/tests/components/adapt-preview1/module.wat +++ b/crates/wit-component/tests/components/adapt-preview1/module.wat @@ -3,8 +3,8 @@ (import "foo" "foo" (func)) ;; import some wasi functions - (import "wasi_snapshot_preview1" "proc_exit" (func (param i32))) - (import "wasi_snapshot_preview1" "random_get" (func (param i32 i32) (result i32))) + (import "wasi-snapshot-preview1" "proc_exit" (func (param i32))) + (import "wasi-snapshot-preview1" "random_get" (func (param i32 i32) (result i32))) ;; required by wasi (memory (export "memory") 1) diff --git a/crates/wit-component/tests/components/adapt-preview1/world.wit b/crates/wit-component/tests/components/adapt-preview1/module.wit similarity index 65% rename from crates/wit-component/tests/components/adapt-preview1/world.wit rename to crates/wit-component/tests/components/adapt-preview1/module.wit index 126383780e..b35cf9a1ec 100644 --- a/crates/wit-component/tests/components/adapt-preview1/world.wit +++ b/crates/wit-component/tests/components/adapt-preview1/module.wit @@ -1,4 +1,4 @@ -world my-world { +default world my-world { import foo: interface { foo: func() } diff --git a/crates/wit-component/tests/components/adapt-memory-simple/adapt-old-world.wit b/crates/wit-component/tests/components/adapt-unused/adapt-old.wit similarity index 64% rename from crates/wit-component/tests/components/adapt-memory-simple/adapt-old-world.wit rename to crates/wit-component/tests/components/adapt-unused/adapt-old.wit index b16de0ffd0..5a86aec124 100644 --- a/crates/wit-component/tests/components/adapt-memory-simple/adapt-old-world.wit +++ b/crates/wit-component/tests/components/adapt-unused/adapt-old.wit @@ -1,4 +1,4 @@ -world brave-new-world { +default world brave-new-world { import new: interface { log: func(s: string) } diff --git a/crates/wit-component/tests/components/adapt-unused/module.wit b/crates/wit-component/tests/components/adapt-unused/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-unused/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/adapt-unused/world.wit b/crates/wit-component/tests/components/adapt-unused/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/adapt-unused/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/default-export-sig-mismatch/error.txt b/crates/wit-component/tests/components/default-export-sig-mismatch/error.txt index fdb207a3b3..b7a8e003cd 100644 --- a/crates/wit-component/tests/components/default-export-sig-mismatch/error.txt +++ b/crates/wit-component/tests/components/default-export-sig-mismatch/error.txt @@ -1 +1 @@ -type mismatch for default interface function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []` \ No newline at end of file +type mismatch for function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []` \ No newline at end of file diff --git a/crates/wit-component/tests/components/default-export-sig-mismatch/module.wat b/crates/wit-component/tests/components/default-export-sig-mismatch/module.wat index 420a370fa9..38ea217899 100644 --- a/crates/wit-component/tests/components/default-export-sig-mismatch/module.wat +++ b/crates/wit-component/tests/components/default-export-sig-mismatch/module.wat @@ -1,3 +1,3 @@ (module (func (export "a") unreachable) -) \ No newline at end of file +) diff --git a/crates/wit-component/tests/components/default-export-sig-mismatch/module.wit b/crates/wit-component/tests/components/default-export-sig-mismatch/module.wit new file mode 100644 index 0000000000..b8fa3e1312 --- /dev/null +++ b/crates/wit-component/tests/components/default-export-sig-mismatch/module.wit @@ -0,0 +1,3 @@ +default world my-world { + export a: func(x: string) -> string +} diff --git a/crates/wit-component/tests/components/default-export-sig-mismatch/world.wit b/crates/wit-component/tests/components/default-export-sig-mismatch/world.wit deleted file mode 100644 index 6099af4fce..0000000000 --- a/crates/wit-component/tests/components/default-export-sig-mismatch/world.wit +++ /dev/null @@ -1,5 +0,0 @@ -world my-world { - default export interface { - a: func(x: string) -> string - } -} diff --git a/crates/wit-component/tests/components/empty-module-import/error.txt b/crates/wit-component/tests/components/empty-module-import/error.txt index fd0bd9faf8..3fc2443a8a 100644 --- a/crates/wit-component/tests/components/empty-module-import/error.txt +++ b/crates/wit-component/tests/components/empty-module-import/error.txt @@ -1 +1 @@ -module imports from an empty module name \ No newline at end of file +no top-level imported function `foo` specified \ No newline at end of file diff --git a/crates/wit-component/tests/components/empty-module-import/module.wit b/crates/wit-component/tests/components/empty-module-import/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/empty-module-import/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/empty-module-import/world.wit b/crates/wit-component/tests/components/empty-module-import/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/empty-module-import/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/empty/module.wit b/crates/wit-component/tests/components/empty/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/empty/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/empty/world.wit b/crates/wit-component/tests/components/empty/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/empty/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/ensure-default-type-exports/component.wat b/crates/wit-component/tests/components/ensure-default-type-exports/component.wat index 4b236cbc1d..f603758a01 100644 --- a/crates/wit-component/tests/components/ensure-default-type-exports/component.wat +++ b/crates/wit-component/tests/components/ensure-default-type-exports/component.wat @@ -3,9 +3,9 @@ (instance (type (;0;) u8) (export (;1;) "foo" (type (eq 0))) - (type (;2;) (record (field "x" 0))) + (type (;2;) (record (field "x" 1))) (export (;3;) "bar" (type (eq 2))) - (type (;4;) (func (param "b" 2))) + (type (;4;) (func (param "b" 3))) (export (;0;) "a" (func (type 4))) ) ) @@ -36,12 +36,8 @@ ) (alias core export 1 "memory" (core memory (;0;))) (alias core export 1 "cabi_realloc" (core func (;1;))) + (type (;1;) (func (param "b" u8))) (alias core export 1 "a" (core func (;2;))) - (type (;1;) u8) - (type (;2;) (record (field "x" 1))) - (type (;3;) (func (param "b" 2))) - (func (;1;) (type 3) (canon lift (core func 2))) + (func (;1;) (type 1) (canon lift (core func 2))) (export (;2;) "a" (func 1)) - (export (;4;) "foo" (type 1)) - (export (;5;) "bar" (type 2)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/ensure-default-type-exports/module.wit b/crates/wit-component/tests/components/ensure-default-type-exports/module.wit new file mode 100644 index 0000000000..c66cd9c4df --- /dev/null +++ b/crates/wit-component/tests/components/ensure-default-type-exports/module.wit @@ -0,0 +1,15 @@ +interface foo { + type foo = u8 + + record bar { + x: foo + } + + a: func(b: bar) +} + +default world my-world { + import foo: self.foo + + export a: func(b: u8) +} diff --git a/crates/wit-component/tests/components/ensure-default-type-exports/world.wit b/crates/wit-component/tests/components/ensure-default-type-exports/world.wit deleted file mode 100644 index 9d9f7c29a6..0000000000 --- a/crates/wit-component/tests/components/ensure-default-type-exports/world.wit +++ /dev/null @@ -1,22 +0,0 @@ -interface foo { - type foo = u8 - - record bar { - x: foo - } - - a: func(b: bar) -} - -world my-world { - import foo: foo - default export interface { - type foo = u8 - - record bar { - x: foo - } - - a: func(b: bar) - } -} diff --git a/crates/wit-component/tests/components/export-sig-mismatch/error.txt b/crates/wit-component/tests/components/export-sig-mismatch/error.txt index 724247f7c8..d2226ac3d9 100644 --- a/crates/wit-component/tests/components/export-sig-mismatch/error.txt +++ b/crates/wit-component/tests/components/export-sig-mismatch/error.txt @@ -1 +1,4 @@ -type mismatch for function `a` from exported interface `foo`: expected `[I32, I32] -> [I32]` but found `[] -> []` \ No newline at end of file +failed to validate exported interface `foo` + +Caused by: + type mismatch for function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []` \ No newline at end of file diff --git a/crates/wit-component/tests/components/export-sig-mismatch/world.wit b/crates/wit-component/tests/components/export-sig-mismatch/module.wit similarity index 76% rename from crates/wit-component/tests/components/export-sig-mismatch/world.wit rename to crates/wit-component/tests/components/export-sig-mismatch/module.wit index 3c24047fb4..8e7ef0bbb6 100644 --- a/crates/wit-component/tests/components/export-sig-mismatch/world.wit +++ b/crates/wit-component/tests/components/export-sig-mismatch/module.wit @@ -1,4 +1,4 @@ -world foo { +default world foo { export foo: interface { a: func(x: string) -> string } diff --git a/crates/wit-component/tests/components/exports/component.wat b/crates/wit-component/tests/components/exports/component.wat index 4bcc98a501..d3b0031700 100644 --- a/crates/wit-component/tests/components/exports/component.wat +++ b/crates/wit-component/tests/components/exports/component.wat @@ -57,25 +57,25 @@ (core instance (;0;) (instantiate 0)) (alias core export 0 "memory" (core memory (;0;))) (alias core export 0 "cabi_realloc" (core func (;0;))) - (alias core export 0 "bar#a" (core func (;1;))) (type (;0;) (flags "a" "b" "c")) (type (;1;) (func (param "x" 0))) + (alias core export 0 "bar#a" (core func (;1;))) (func (;0;) (type 1) (canon lift (core func 1))) (instance (;0;) (export "a" (func 0)) (export "x" (type 0)) ) (export (;1;) "bar" (instance 0)) - (alias core export 0 "foo#a" (core func (;2;))) (type (;2;) (func)) + (alias core export 0 "foo#a" (core func (;2;))) (func (;1;) (type 2) (canon lift (core func 2))) - (alias core export 0 "foo#b" (core func (;3;))) (type (;3;) (variant (case "a") (case "b" string) (case "c" s64))) (type (;4;) (func (param "x" string) (result 3))) + (alias core export 0 "foo#b" (core func (;3;))) (alias core export 0 "cabi_post_foo#b" (core func (;4;))) (func (;2;) (type 4) (canon lift (core func 3) (memory 0) (realloc 0) string-encoding=utf8 (post-return 4))) - (alias core export 0 "foo#c" (core func (;5;))) (type (;5;) (func (param "x" 3) (result string))) + (alias core export 0 "foo#c" (core func (;5;))) (alias core export 0 "cabi_post_foo#c" (core func (;6;))) (func (;3;) (type 5) (canon lift (core func 5) (memory 0) (realloc 0) string-encoding=utf8 (post-return 6))) (instance (;2;) @@ -87,15 +87,15 @@ (export (;3;) "foo" (instance 2)) (alias core export 0 "a" (core func (;7;))) (func (;4;) (type 2) (canon lift (core func 7))) - (alias core export 0 "b" (core func (;8;))) + (export (;5;) "a" (func 4)) (type (;6;) (func (param "a" s8) (param "b" s16) (param "c" s32) (param "d" s64) (result string))) + (alias core export 0 "b" (core func (;8;))) (alias core export 0 "cabi_post_b" (core func (;9;))) - (func (;5;) (type 6) (canon lift (core func 8) (memory 0) string-encoding=utf8 (post-return 9))) - (alias core export 0 "c" (core func (;10;))) + (func (;6;) (type 6) (canon lift (core func 8) (memory 0) string-encoding=utf8 (post-return 9))) + (export (;7;) "b" (func 6)) (type (;7;) (tuple s8 s16 s32 s64)) (type (;8;) (func (result 7))) - (func (;6;) (type 8) (canon lift (core func 10) (memory 0))) - (export (;7;) "a" (func 4)) - (export (;8;) "b" (func 5)) - (export (;9;) "c" (func 6)) + (alias core export 0 "c" (core func (;10;))) + (func (;8;) (type 8) (canon lift (core func 10) (memory 0))) + (export (;9;) "c" (func 8)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/exports/world.wit b/crates/wit-component/tests/components/exports/module.wit similarity index 63% rename from crates/wit-component/tests/components/exports/world.wit rename to crates/wit-component/tests/components/exports/module.wit index 897dbfda67..478e6876ca 100644 --- a/crates/wit-component/tests/components/exports/world.wit +++ b/crates/wit-component/tests/components/exports/module.wit @@ -1,9 +1,7 @@ -world my-world { - default export interface { - a: func() - b: func(a: s8, b: s16, c: s32, d: s64) -> string - c: func() -> tuple - } +default world my-world { + export a: func() + export b: func(a: s8, b: s16, c: s32, d: s64) -> string + export c: func() -> tuple export bar: interface { flags x { diff --git a/crates/wit-component/tests/components/import-conflict/world.wit b/crates/wit-component/tests/components/import-conflict/module.wit similarity index 59% rename from crates/wit-component/tests/components/import-conflict/world.wit rename to crates/wit-component/tests/components/import-conflict/module.wit index 9b351f1968..4c23d0a296 100644 --- a/crates/wit-component/tests/components/import-conflict/world.wit +++ b/crates/wit-component/tests/components/import-conflict/module.wit @@ -10,8 +10,8 @@ interface baz { baz: func(x: list) -> list } -world my-world { - import bar: bar - import baz: baz - import foo: foo +default world my-world { + import bar: self.bar + import baz: self.baz + import foo: self.foo } diff --git a/crates/wit-component/tests/components/import-empty-interface/world.wit b/crates/wit-component/tests/components/import-empty-interface/module.wit similarity index 59% rename from crates/wit-component/tests/components/import-empty-interface/world.wit rename to crates/wit-component/tests/components/import-empty-interface/module.wit index 0860d64f89..b92bce2a81 100644 --- a/crates/wit-component/tests/components/import-empty-interface/world.wit +++ b/crates/wit-component/tests/components/import-empty-interface/module.wit @@ -1,3 +1,3 @@ -world foo { +default world foo { import foo: interface {} } diff --git a/crates/wit-component/tests/components/import-export/component.wat b/crates/wit-component/tests/components/import-export/component.wat index e5e5c5b8e3..2bc6e56a55 100644 --- a/crates/wit-component/tests/components/import-export/component.wat +++ b/crates/wit-component/tests/components/import-export/component.wat @@ -79,11 +79,11 @@ (with "" (instance 3)) ) ) - (alias core export 2 "bar#a" (core func (;3;))) (type (;1;) (func)) + (alias core export 2 "bar#a" (core func (;3;))) (func (;1;) (type 1) (canon lift (core func 3))) - (alias core export 2 "bar#b" (core func (;4;))) (type (;2;) (func (result string))) + (alias core export 2 "bar#b" (core func (;4;))) (alias core export 2 "cabi_post_bar#b" (core func (;5;))) (func (;2;) (type 2) (canon lift (core func 4) (memory 0) string-encoding=utf8 (post-return 5))) (instance (;1;) @@ -91,9 +91,9 @@ (export "b" (func 2)) ) (export (;2;) "bar" (instance 1)) - (alias core export 2 "a" (core func (;6;))) (type (;3;) (tuple string u32 string)) (type (;4;) (func (param "x" string) (result 3))) + (alias core export 2 "a" (core func (;6;))) (alias core export 2 "cabi_post_a" (core func (;7;))) (func (;3;) (type 4) (canon lift (core func 6) (memory 0) (realloc 1) string-encoding=utf8 (post-return 7))) (export (;4;) "a" (func 3)) diff --git a/crates/wit-component/tests/components/import-export/world.wit b/crates/wit-component/tests/components/import-export/module.wit similarity index 54% rename from crates/wit-component/tests/components/import-export/world.wit rename to crates/wit-component/tests/components/import-export/module.wit index d009ee8e28..3b29a2b865 100644 --- a/crates/wit-component/tests/components/import-export/world.wit +++ b/crates/wit-component/tests/components/import-export/module.wit @@ -1,5 +1,4 @@ - -world my-world { +default world my-world { import foo: interface { a: func() -> string } @@ -9,7 +8,5 @@ world my-world { b: func() -> string } - default export interface { - a: func(x: string) -> tuple - } + export a: func(x: string) -> tuple } diff --git a/crates/wit-component/tests/components/import-sig-mismatch/error.txt b/crates/wit-component/tests/components/import-sig-mismatch/error.txt index b8d68a1bc0..7a6fd7d4e0 100644 --- a/crates/wit-component/tests/components/import-sig-mismatch/error.txt +++ b/crates/wit-component/tests/components/import-sig-mismatch/error.txt @@ -1 +1,4 @@ -type mismatch for function `bar` on imported interface `foo`: expected `[I32, I32] -> []` but found `[] -> []` \ No newline at end of file +failed to validate import interface `foo` + +Caused by: + type mismatch for function `bar`: expected `[I32, I32] -> []` but found `[] -> []` \ No newline at end of file diff --git a/crates/wit-component/tests/components/import-sig-mismatch/world.wit b/crates/wit-component/tests/components/import-sig-mismatch/module.wit similarity index 74% rename from crates/wit-component/tests/components/import-sig-mismatch/world.wit rename to crates/wit-component/tests/components/import-sig-mismatch/module.wit index 3d9643fc72..3e06ced973 100644 --- a/crates/wit-component/tests/components/import-sig-mismatch/world.wit +++ b/crates/wit-component/tests/components/import-sig-mismatch/module.wit @@ -1,4 +1,4 @@ -world foo { +default world foo { import foo: interface { bar: func(s: string) } diff --git a/crates/wit-component/tests/components/imports/component.wat b/crates/wit-component/tests/components/imports/component.wat index f67024ea48..422b2d7cf5 100644 --- a/crates/wit-component/tests/components/imports/component.wat +++ b/crates/wit-component/tests/components/imports/component.wat @@ -5,7 +5,7 @@ (export (;0;) "bar1" (func (type 0))) (type (;1;) (record (field "a" u8))) (export (;2;) "x" (type (eq 1))) - (type (;3;) (func (param "x" 1))) + (type (;3;) (func (param "x" 2))) (export (;1;) "bar2" (func (type 3))) ) ) @@ -19,7 +19,7 @@ (export (;1;) "baz2" (func (type 2))) (type (;3;) s8) (export (;4;) "x" (type (eq 3))) - (type (;5;) (func (param "x" 3))) + (type (;5;) (func (param "x" 4))) (export (;2;) "baz3" (func (type 5))) ) ) diff --git a/crates/wit-component/tests/components/imports/world.wit b/crates/wit-component/tests/components/imports/module.wit similarity index 95% rename from crates/wit-component/tests/components/imports/world.wit rename to crates/wit-component/tests/components/imports/module.wit index bf20e73eab..c5fad1d34d 100644 --- a/crates/wit-component/tests/components/imports/world.wit +++ b/crates/wit-component/tests/components/imports/module.wit @@ -1,4 +1,4 @@ -world my-world { +default world my-world { import bar: interface { record x { a: u8 diff --git a/crates/wit-component/tests/components/invalid-module-import/module.wit b/crates/wit-component/tests/components/invalid-module-import/module.wit new file mode 100644 index 0000000000..234a91911c --- /dev/null +++ b/crates/wit-component/tests/components/invalid-module-import/module.wit @@ -0,0 +1 @@ +default world foo {} diff --git a/crates/wit-component/tests/components/invalid-module-import/world.wit b/crates/wit-component/tests/components/invalid-module-import/world.wit deleted file mode 100644 index 911d40a020..0000000000 --- a/crates/wit-component/tests/components/invalid-module-import/world.wit +++ /dev/null @@ -1 +0,0 @@ -world foo {} diff --git a/crates/wit-component/tests/components/lift-options/component.wat b/crates/wit-component/tests/components/lift-options/component.wat index cb1ca7f8b5..d8732fe55d 100644 --- a/crates/wit-component/tests/components/lift-options/component.wat +++ b/crates/wit-component/tests/components/lift-options/component.wat @@ -72,110 +72,113 @@ (memory (;0;) 1) (export "memory" (memory 0)) (export "cabi_realloc" (func 0)) - (export "a" (func 1)) - (export "b" (func 2)) - (export "c" (func 3)) - (export "d" (func 4)) - (export "e" (func 5)) - (export "f" (func 6)) - (export "g" (func 7)) - (export "h" (func 8)) - (export "i" (func 9)) - (export "j" (func 10)) - (export "k" (func 11)) - (export "l" (func 12)) - (export "cabi_post_l" (func 13)) - (export "m" (func 14)) - (export "cabi_post_m" (func 15)) - (export "n" (func 16)) - (export "o" (func 17)) - (export "cabi_post_o" (func 18)) - (export "p" (func 19)) - (export "cabi_post_p" (func 20)) + (export "foo#a" (func 1)) + (export "foo#b" (func 2)) + (export "foo#c" (func 3)) + (export "foo#d" (func 4)) + (export "foo#e" (func 5)) + (export "foo#f" (func 6)) + (export "foo#g" (func 7)) + (export "foo#h" (func 8)) + (export "foo#i" (func 9)) + (export "foo#j" (func 10)) + (export "foo#k" (func 11)) + (export "foo#l" (func 12)) + (export "cabi_post_foo#l" (func 13)) + (export "foo#m" (func 14)) + (export "cabi_post_foo#m" (func 15)) + (export "foo#n" (func 16)) + (export "foo#o" (func 17)) + (export "cabi_post_foo#o" (func 18)) + (export "foo#p" (func 19)) + (export "cabi_post_foo#p" (func 20)) ) (core instance (;0;) (instantiate 0)) (alias core export 0 "memory" (core memory (;0;))) (alias core export 0 "cabi_realloc" (core func (;0;))) - (alias core export 0 "a" (core func (;1;))) (type (;0;) (func)) + (alias core export 0 "foo#a" (core func (;1;))) (func (;0;) (type 0) (canon lift (core func 1))) - (alias core export 0 "b" (core func (;2;))) (type (;1;) (list string)) (type (;2;) (func (param "x" 1))) + (alias core export 0 "foo#b" (core func (;2;))) (func (;1;) (type 2) (canon lift (core func 2) (memory 0) (realloc 0) string-encoding=utf8)) - (alias core export 0 "c" (core func (;3;))) (type (;3;) (record (field "s" string))) (type (;4;) (func (param "x" 3))) + (alias core export 0 "foo#c" (core func (;3;))) (func (;2;) (type 4) (canon lift (core func 3) (memory 0) (realloc 0) string-encoding=utf8)) - (alias core export 0 "d" (core func (;4;))) (type (;5;) (variant (case "s" string))) (type (;6;) (func (param "x" 5))) + (alias core export 0 "foo#d" (core func (;4;))) (func (;3;) (type 6) (canon lift (core func 4) (memory 0) (realloc 0) string-encoding=utf8)) - (alias core export 0 "e" (core func (;5;))) (type (;7;) (record (field "s" u32))) (type (;8;) (func (param "x" 7))) + (alias core export 0 "foo#e" (core func (;5;))) (func (;4;) (type 8) (canon lift (core func 5))) - (alias core export 0 "f" (core func (;6;))) (type (;9;) (variant (case "s" u32))) (type (;10;) (func (param "x" 9))) + (alias core export 0 "foo#f" (core func (;6;))) (func (;5;) (type 10) (canon lift (core func 6))) - (alias core export 0 "g" (core func (;7;))) (type (;11;) (list 3)) (type (;12;) (func (param "x" 11))) + (alias core export 0 "foo#g" (core func (;7;))) (func (;6;) (type 12) (canon lift (core func 7) (memory 0) (realloc 0) string-encoding=utf8)) - (alias core export 0 "h" (core func (;8;))) (type (;13;) (list 5)) (type (;14;) (func (param "x" 13))) + (alias core export 0 "foo#h" (core func (;8;))) (func (;7;) (type 14) (canon lift (core func 8) (memory 0) (realloc 0) string-encoding=utf8)) - (alias core export 0 "i" (core func (;9;))) (type (;15;) (list u32)) (type (;16;) (func (param "x" 15))) + (alias core export 0 "foo#i" (core func (;9;))) (func (;8;) (type 16) (canon lift (core func 9) (memory 0) (realloc 0))) - (alias core export 0 "j" (core func (;10;))) (type (;17;) (func (param "x" u32))) + (alias core export 0 "foo#j" (core func (;10;))) (func (;9;) (type 17) (canon lift (core func 10))) - (alias core export 0 "k" (core func (;11;))) (type (;18;) (tuple u32 u32)) (type (;19;) (func (result 18))) + (alias core export 0 "foo#k" (core func (;11;))) (func (;10;) (type 19) (canon lift (core func 11) (memory 0))) - (alias core export 0 "l" (core func (;12;))) (type (;20;) (func (result string))) - (alias core export 0 "cabi_post_l" (core func (;13;))) + (alias core export 0 "foo#l" (core func (;12;))) + (alias core export 0 "cabi_post_foo#l" (core func (;13;))) (func (;11;) (type 20) (canon lift (core func 12) (memory 0) string-encoding=utf8 (post-return 13))) - (alias core export 0 "m" (core func (;14;))) (type (;21;) (func (result 15))) - (alias core export 0 "cabi_post_m" (core func (;15;))) + (alias core export 0 "foo#m" (core func (;14;))) + (alias core export 0 "cabi_post_foo#m" (core func (;15;))) (func (;12;) (type 21) (canon lift (core func 14) (memory 0) (post-return 15))) - (alias core export 0 "n" (core func (;16;))) (type (;22;) (func (result u32))) + (alias core export 0 "foo#n" (core func (;16;))) (func (;13;) (type 22) (canon lift (core func 16))) - (alias core export 0 "o" (core func (;17;))) (type (;23;) (func (result 5))) - (alias core export 0 "cabi_post_o" (core func (;18;))) + (alias core export 0 "foo#o" (core func (;17;))) + (alias core export 0 "cabi_post_foo#o" (core func (;18;))) (func (;14;) (type 23) (canon lift (core func 17) (memory 0) string-encoding=utf8 (post-return 18))) - (alias core export 0 "p" (core func (;19;))) (type (;24;) (list 9)) (type (;25;) (func (result 24))) - (alias core export 0 "cabi_post_p" (core func (;20;))) + (alias core export 0 "foo#p" (core func (;19;))) + (alias core export 0 "cabi_post_foo#p" (core func (;20;))) (func (;15;) (type 25) (canon lift (core func 19) (memory 0) (post-return 20))) - (export (;16;) "a" (func 0)) - (export (;17;) "b" (func 1)) - (export (;18;) "c" (func 2)) - (export (;19;) "d" (func 3)) - (export (;20;) "e" (func 4)) - (export (;21;) "f" (func 5)) - (export (;22;) "g" (func 6)) - (export (;23;) "h" (func 7)) - (export (;24;) "i" (func 8)) - (export (;25;) "j" (func 9)) - (export (;26;) "k" (func 10)) - (export (;27;) "l" (func 11)) - (export (;28;) "m" (func 12)) - (export (;29;) "n" (func 13)) - (export (;30;) "o" (func 14)) - (export (;31;) "p" (func 15)) - (export (;26;) "r" (type 3)) - (export (;27;) "v" (type 5)) - (export (;28;) "r-no-string" (type 7)) - (export (;29;) "v-no-string" (type 9)) + (instance (;0;) + (export "a" (func 0)) + (export "b" (func 1)) + (export "c" (func 2)) + (export "d" (func 3)) + (export "e" (func 4)) + (export "f" (func 5)) + (export "g" (func 6)) + (export "h" (func 7)) + (export "i" (func 8)) + (export "j" (func 9)) + (export "k" (func 10)) + (export "l" (func 11)) + (export "m" (func 12)) + (export "n" (func 13)) + (export "o" (func 14)) + (export "p" (func 15)) + (export "r" (type 3)) + (export "v" (type 5)) + (export "r-no-string" (type 7)) + (export "v-no-string" (type 9)) + ) + (export (;1;) "foo" (instance 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/lift-options/module.wat b/crates/wit-component/tests/components/lift-options/module.wat index c1191b0bb5..aa6a7acb5b 100644 --- a/crates/wit-component/tests/components/lift-options/module.wat +++ b/crates/wit-component/tests/components/lift-options/module.wat @@ -1,24 +1,24 @@ (module (memory (export "memory") 1) (func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) - (func (export "a") unreachable) - (func (export "b") (param i32 i32) unreachable) - (func (export "c") (param i32 i32) unreachable) - (func (export "d") (param i32 i32 i32) unreachable) - (func (export "e") (param i32) unreachable) - (func (export "f") (param i32 i32) unreachable) - (func (export "g") (param i32 i32) unreachable) - (func (export "h") (param i32 i32) unreachable) - (func (export "i") (param i32 i32) unreachable) - (func (export "j") (param i32) unreachable) - (func (export "k") (result i32) unreachable) - (func (export "l") (result i32) unreachable) - (func (export "cabi_post_l") (param i32) unreachable) - (func (export "m") (result i32) unreachable) - (func (export "cabi_post_m") (param i32) unreachable) - (func (export "n") (result i32) unreachable) - (func (export "o") (result i32) unreachable) - (func (export "cabi_post_o") (param i32) unreachable) - (func (export "p") (result i32) unreachable) - (func (export "cabi_post_p") (param i32) unreachable) + (func (export "foo#a") unreachable) + (func (export "foo#b") (param i32 i32) unreachable) + (func (export "foo#c") (param i32 i32) unreachable) + (func (export "foo#d") (param i32 i32 i32) unreachable) + (func (export "foo#e") (param i32) unreachable) + (func (export "foo#f") (param i32 i32) unreachable) + (func (export "foo#g") (param i32 i32) unreachable) + (func (export "foo#h") (param i32 i32) unreachable) + (func (export "foo#i") (param i32 i32) unreachable) + (func (export "foo#j") (param i32) unreachable) + (func (export "foo#k") (result i32) unreachable) + (func (export "foo#l") (result i32) unreachable) + (func (export "cabi_post_foo#l") (param i32) unreachable) + (func (export "foo#m") (result i32) unreachable) + (func (export "cabi_post_foo#m") (param i32) unreachable) + (func (export "foo#n") (result i32) unreachable) + (func (export "foo#o") (result i32) unreachable) + (func (export "cabi_post_foo#o") (param i32) unreachable) + (func (export "foo#p") (result i32) unreachable) + (func (export "cabi_post_foo#p") (param i32) unreachable) ) diff --git a/crates/wit-component/tests/components/lift-options/world.wit b/crates/wit-component/tests/components/lift-options/module.wit similarity index 90% rename from crates/wit-component/tests/components/lift-options/world.wit rename to crates/wit-component/tests/components/lift-options/module.wit index 28d9a0ccec..b1d4b6db11 100644 --- a/crates/wit-component/tests/components/lift-options/world.wit +++ b/crates/wit-component/tests/components/lift-options/module.wit @@ -33,6 +33,6 @@ interface my-default { p: func() -> list } -world my-world { - default export my-default +default world my-world { + export foo: self.my-default } diff --git a/crates/wit-component/tests/components/lower-options/component.wat b/crates/wit-component/tests/components/lower-options/component.wat index 6fc2d1171a..a629dedf29 100644 --- a/crates/wit-component/tests/components/lower-options/component.wat +++ b/crates/wit-component/tests/components/lower-options/component.wat @@ -8,24 +8,24 @@ (export (;1;) "b" (func (type 2))) (type (;3;) (record (field "s" string))) (export (;4;) "r" (type (eq 3))) - (type (;5;) (func (param "x" 3))) + (type (;5;) (func (param "x" 4))) (export (;2;) "c" (func (type 5))) (type (;6;) (variant (case "s" string))) (export (;7;) "v" (type (eq 6))) - (type (;8;) (func (param "x" 6))) + (type (;8;) (func (param "x" 7))) (export (;3;) "d" (func (type 8))) (type (;9;) (record (field "s" u32))) (export (;10;) "r-no-string" (type (eq 9))) - (type (;11;) (func (param "x" 9))) + (type (;11;) (func (param "x" 10))) (export (;4;) "e" (func (type 11))) (type (;12;) (variant (case "s" u32))) (export (;13;) "v-no-string" (type (eq 12))) - (type (;14;) (func (param "x" 12))) + (type (;14;) (func (param "x" 13))) (export (;5;) "f" (func (type 14))) - (type (;15;) (list 3)) + (type (;15;) (list 4)) (type (;16;) (func (param "x" 15))) (export (;6;) "g" (func (type 16))) - (type (;17;) (list 6)) + (type (;17;) (list 7)) (type (;18;) (func (param "x" 17))) (export (;7;) "h" (func (type 18))) (type (;19;) (list u32)) @@ -42,9 +42,9 @@ (export (;12;) "m" (func (type 25))) (type (;26;) (func (result u32))) (export (;13;) "n" (func (type 26))) - (type (;27;) (func (result 6))) + (type (;27;) (func (result 7))) (export (;14;) "o" (func (type 27))) - (type (;28;) (list 12)) + (type (;28;) (list 13)) (type (;29;) (func (result 28))) (export (;15;) "p" (func (type 29))) ) diff --git a/crates/wit-component/tests/components/lower-options/world.wit b/crates/wit-component/tests/components/lower-options/module.wit similarity index 91% rename from crates/wit-component/tests/components/lower-options/world.wit rename to crates/wit-component/tests/components/lower-options/module.wit index 6072011f8f..d4dae943f7 100644 --- a/crates/wit-component/tests/components/lower-options/world.wit +++ b/crates/wit-component/tests/components/lower-options/module.wit @@ -33,6 +33,6 @@ interface foo { p: func() -> list } -world my-world { - import foo: foo +default world my-world { + import foo: self.foo } diff --git a/crates/wit-component/tests/components/missing-default-export/module.wit b/crates/wit-component/tests/components/missing-default-export/module.wit new file mode 100644 index 0000000000..4e607cdba0 --- /dev/null +++ b/crates/wit-component/tests/components/missing-default-export/module.wit @@ -0,0 +1,3 @@ +default world my-world { + export a: func() +} diff --git a/crates/wit-component/tests/components/missing-default-export/world.wit b/crates/wit-component/tests/components/missing-default-export/world.wit deleted file mode 100644 index 66ea8bd1c1..0000000000 --- a/crates/wit-component/tests/components/missing-default-export/world.wit +++ /dev/null @@ -1,5 +0,0 @@ -world my-world { - default export interface { - a: func() - } -} diff --git a/crates/wit-component/tests/components/missing-export/error.txt b/crates/wit-component/tests/components/missing-export/error.txt index 821563ee2a..afcfb4e22f 100644 --- a/crates/wit-component/tests/components/missing-export/error.txt +++ b/crates/wit-component/tests/components/missing-export/error.txt @@ -1 +1,4 @@ -module does not export required function `foo#a` \ No newline at end of file +failed to validate exported interface `foo` + +Caused by: + module does not export required function `foo#a` \ No newline at end of file diff --git a/crates/wit-component/tests/components/missing-export/world.wit b/crates/wit-component/tests/components/missing-export/module.wit similarity index 64% rename from crates/wit-component/tests/components/missing-export/world.wit rename to crates/wit-component/tests/components/missing-export/module.wit index 4e07fc1d9c..6b1871aead 100644 --- a/crates/wit-component/tests/components/missing-export/world.wit +++ b/crates/wit-component/tests/components/missing-export/module.wit @@ -1,4 +1,4 @@ -world my-world { +default world my-world { export foo: interface { a: func() } diff --git a/crates/wit-component/tests/components/missing-import-func/error.txt b/crates/wit-component/tests/components/missing-import-func/error.txt index a79587d066..ea9b14277d 100644 --- a/crates/wit-component/tests/components/missing-import-func/error.txt +++ b/crates/wit-component/tests/components/missing-import-func/error.txt @@ -1 +1,4 @@ -import interface `foo` is missing function `bar` that is required by the module \ No newline at end of file +failed to validate import interface `foo` + +Caused by: + import interface `foo` is missing function `bar` that is required by the module \ No newline at end of file diff --git a/crates/wit-component/tests/components/missing-import-func/module.wit b/crates/wit-component/tests/components/missing-import-func/module.wit new file mode 100644 index 0000000000..d0e7916515 --- /dev/null +++ b/crates/wit-component/tests/components/missing-import-func/module.wit @@ -0,0 +1,7 @@ +interface foo { + a: func() +} + +default world my-world { + import foo: self.foo +} diff --git a/crates/wit-component/tests/components/missing-import-func/world.wit b/crates/wit-component/tests/components/missing-import-func/world.wit deleted file mode 100644 index 39a2a8c43f..0000000000 --- a/crates/wit-component/tests/components/missing-import-func/world.wit +++ /dev/null @@ -1,7 +0,0 @@ -interface foo { - a: func() -} - -world my-world { - import foo: foo -} diff --git a/crates/wit-component/tests/components/missing-import/module.wit b/crates/wit-component/tests/components/missing-import/module.wit new file mode 100644 index 0000000000..1f99081f58 --- /dev/null +++ b/crates/wit-component/tests/components/missing-import/module.wit @@ -0,0 +1 @@ +default world empty {} diff --git a/crates/wit-component/tests/components/missing-import/world.wit b/crates/wit-component/tests/components/missing-import/world.wit deleted file mode 100644 index 48c1b8597d..0000000000 --- a/crates/wit-component/tests/components/missing-import/world.wit +++ /dev/null @@ -1 +0,0 @@ -world empty {} diff --git a/crates/wit-component/tests/components/no-realloc-required/world.wit b/crates/wit-component/tests/components/no-realloc-required/module.wit similarity index 74% rename from crates/wit-component/tests/components/no-realloc-required/world.wit rename to crates/wit-component/tests/components/no-realloc-required/module.wit index acf1566794..558f5ac83f 100644 --- a/crates/wit-component/tests/components/no-realloc-required/world.wit +++ b/crates/wit-component/tests/components/no-realloc-required/module.wit @@ -1,4 +1,4 @@ -world foo { +default world foo { import foo: interface { log: func(s: string) } diff --git a/crates/wit-component/tests/components/post-return/component.wat b/crates/wit-component/tests/components/post-return/component.wat index 32e11a4238..d4f2069075 100644 --- a/crates/wit-component/tests/components/post-return/component.wat +++ b/crates/wit-component/tests/components/post-return/component.wat @@ -21,8 +21,8 @@ (core instance (;0;) (instantiate 0)) (alias core export 0 "memory" (core memory (;0;))) (alias core export 0 "cabi_realloc" (core func (;0;))) - (alias core export 0 "a" (core func (;1;))) (type (;0;) (func (result string))) + (alias core export 0 "a" (core func (;1;))) (alias core export 0 "cabi_post_a" (core func (;2;))) (func (;0;) (type 0) (canon lift (core func 1) (memory 0) string-encoding=utf8 (post-return 2))) (export (;1;) "a" (func 0)) diff --git a/crates/wit-component/tests/components/post-return/module.wit b/crates/wit-component/tests/components/post-return/module.wit new file mode 100644 index 0000000000..1d99c42228 --- /dev/null +++ b/crates/wit-component/tests/components/post-return/module.wit @@ -0,0 +1,3 @@ +default world foo { + export a: func() -> string +} diff --git a/crates/wit-component/tests/components/post-return/world.wit b/crates/wit-component/tests/components/post-return/world.wit deleted file mode 100644 index 8eb847f411..0000000000 --- a/crates/wit-component/tests/components/post-return/world.wit +++ /dev/null @@ -1,5 +0,0 @@ -world foo { - default export interface { - a: func() -> string - } -} diff --git a/crates/wit-component/tests/components/simple/component.wat b/crates/wit-component/tests/components/simple/component.wat index 0a313fe437..940bda28a3 100644 --- a/crates/wit-component/tests/components/simple/component.wat +++ b/crates/wit-component/tests/components/simple/component.wat @@ -40,24 +40,18 @@ (core instance (;0;) (instantiate 0)) (alias core export 0 "memory" (core memory (;0;))) (alias core export 0 "cabi_realloc" (core func (;0;))) - (alias core export 0 "a" (core func (;1;))) (type (;0;) (func)) + (alias core export 0 "a" (core func (;1;))) (func (;0;) (type 0) (canon lift (core func 1))) - (alias core export 0 "b" (core func (;2;))) + (export (;1;) "a" (func 0)) (type (;1;) (func (result string))) + (alias core export 0 "b" (core func (;2;))) (alias core export 0 "cabi_post_b" (core func (;3;))) - (func (;1;) (type 1) (canon lift (core func 2) (memory 0) string-encoding=utf8 (post-return 3))) - (alias core export 0 "c" (core func (;4;))) + (func (;2;) (type 1) (canon lift (core func 2) (memory 0) string-encoding=utf8 (post-return 3))) + (export (;3;) "b" (func 2)) (type (;2;) (func (param "x" string) (result string))) + (alias core export 0 "c" (core func (;4;))) (alias core export 0 "cabi_post_c" (core func (;5;))) - (func (;2;) (type 2) (canon lift (core func 4) (memory 0) (realloc 0) string-encoding=utf8 (post-return 5))) - (alias core export 0 "d" (core func (;6;))) - (type (;3;) (list string)) - (type (;4;) (func (param "x" 3))) - (func (;3;) (type 4) (canon lift (core func 6) (memory 0) (realloc 0) string-encoding=utf8)) - (export (;4;) "a" (func 0)) - (export (;5;) "b" (func 1)) - (export (;6;) "c" (func 2)) - (export (;7;) "d" (func 3)) - (export (;5;) "x" (type 3)) + (func (;4;) (type 2) (canon lift (core func 4) (memory 0) (realloc 0) string-encoding=utf8 (post-return 5))) + (export (;5;) "c" (func 4)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/simple/module.wit b/crates/wit-component/tests/components/simple/module.wit new file mode 100644 index 0000000000..af939cdbe4 --- /dev/null +++ b/crates/wit-component/tests/components/simple/module.wit @@ -0,0 +1,5 @@ +default world foo { + export a: func() + export b: func() -> string + export c: func(x: string) -> string +} diff --git a/crates/wit-component/tests/components/simple/world.wit b/crates/wit-component/tests/components/simple/world.wit deleted file mode 100644 index 622050de9b..0000000000 --- a/crates/wit-component/tests/components/simple/world.wit +++ /dev/null @@ -1,10 +0,0 @@ -world foo { - default export interface { - type x = list - - a: func() - b: func() -> string - c: func(x: string) -> string - d: func(x: x) - } -} diff --git a/crates/wit-component/tests/components/unused-import/module.wit b/crates/wit-component/tests/components/unused-import/module.wit new file mode 100644 index 0000000000..1d978499e3 --- /dev/null +++ b/crates/wit-component/tests/components/unused-import/module.wit @@ -0,0 +1,10 @@ +interface bar {} + +interface foo { + name: func(x: bool) +} + +default world my-world { + import unused: self.bar + import foo: self.foo +} diff --git a/crates/wit-component/tests/components/unused-import/world.wit b/crates/wit-component/tests/components/unused-import/world.wit deleted file mode 100644 index dd26f6213f..0000000000 --- a/crates/wit-component/tests/components/unused-import/world.wit +++ /dev/null @@ -1,10 +0,0 @@ -interface bar {} - -interface foo { - name: func(x: bool) -} - -world my-world { - import unused: bar - import foo: foo -} diff --git a/crates/wit-component/tests/interfaces/world-top-level.wat b/crates/wit-component/tests/interfaces/world-top-level.wat new file mode 100644 index 0000000000..39d9790837 --- /dev/null +++ b/crates/wit-component/tests/interfaces/world-top-level.wat @@ -0,0 +1,42 @@ +(component + (type (;0;) + (component + (type (;0;) + (component + (type (;0;) + (instance) + ) + (import "some-interface" (instance (type 0))) + (type (;1;) (func)) + (import "foo" (func (type 1))) + (type (;2;) (func (param "arg" u32))) + (import "bar" (func (type 2))) + (type (;3;) + (instance) + ) + (export (;0;) "another-interface" (instance (type 3))) + (type (;4;) (func)) + (export (;0;) "foo" (func (type 4))) + (type (;5;) (func (result u32))) + (export (;1;) "bar" (func (type 5))) + ) + ) + (export (;0;) "foo" (component (type 0))) + (type (;1;) + (component + (type (;0;) (func)) + (import "foo" (func (type 0))) + ) + ) + (export (;1;) "just-import" (component (type 1))) + (type (;2;) + (component + (type (;0;) (func)) + (export (;0;) "foo" (func (type 0))) + ) + ) + (export (;2;) "just-export" (component (type 2))) + ) + ) + (export (;1;) "world-top-level" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/world-top-level.wit b/crates/wit-component/tests/interfaces/world-top-level.wit new file mode 100644 index 0000000000..6333efb7c8 --- /dev/null +++ b/crates/wit-component/tests/interfaces/world-top-level.wit @@ -0,0 +1,18 @@ +world foo { + import foo: func() + export foo: func() + + import bar: func(arg: u32) + export bar: func() -> u32 + + import some-interface: interface {} + export another-interface: interface {} +} + +world just-import { + import foo: func() +} + +world just-export { + export foo: func() +} diff --git a/crates/wit-component/tests/interfaces/world-top-level.wit.print b/crates/wit-component/tests/interfaces/world-top-level.wit.print new file mode 100644 index 0000000000..c9871e04fb --- /dev/null +++ b/crates/wit-component/tests/interfaces/world-top-level.wit.print @@ -0,0 +1,16 @@ +world foo { + import some-interface: interface { + } + import foo: func() + import bar: func(arg: u32) + export another-interface: interface { + } + export foo: func() + export bar: func() -> u32 +} +world just-import { + import foo: func() +} +world just-export { + export foo: func() +} diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 1ab94a38cd..33e0e5a54a 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -51,7 +51,7 @@ impl<'a> Ast<'a> { } } ExternKind::Path(path) => f(None, path, None)?, - // ExternKind::Func(_) => {} + ExternKind::Func(_) => {} }, } } @@ -175,7 +175,7 @@ impl<'a> Export<'a> { pub enum ExternKind<'a> { Interface(Span, Vec>), Path(UsePath<'a>), - // Func(Func<'a>), + Func(Func<'a>), } impl<'a> ExternKind<'a> { @@ -189,8 +189,7 @@ impl<'a> ExternKind<'a> { let items = Interface::parse_items(tokens)?; Ok(ExternKind::Interface(span, items)) } - // TODO: should parse this when it's implemented - // Some((_span, Token::Func)) => Ok(ExternKind::Func(Func::parse(tokens)?)), + Some((_span, Token::Func)) => Ok(ExternKind::Func(Func::parse(tokens)?)), other => Err(err_expected(tokens, "path, value, or interface", other).into()), } } diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index adf0d69d88..1ea44ae551 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1,4 +1,4 @@ -use super::{Error, Func, ParamList, ResultList, ValueKind}; +use super::{Error, ParamList, ResultList, ValueKind}; use crate::ast::toposort::toposort; use crate::*; use anyhow::{bail, Result}; @@ -167,7 +167,7 @@ impl<'a> Resolver<'a> { default_interface: None, default_world: None, interfaces: IndexMap::new(), - worlds: Vec::new(), + worlds: IndexMap::new(), package: None, }) }); @@ -322,7 +322,7 @@ impl<'a> Resolver<'a> { default_interface: None, default_world: None, interfaces: IndexMap::new(), - worlds: Vec::new(), + worlds: IndexMap::new(), package: None, }); self.document_interfaces.push(IndexMap::new()); @@ -362,7 +362,7 @@ impl<'a> Resolver<'a> { // After all interfaces are defined then process all worlds as all items // they import from should now be available. - for (_name, world) in worlds { + for (name, world) in worlds { let id = self.resolve_world(document_id, world)?; if world.default { let prev = &mut self.documents[document_id].default_world; @@ -375,7 +375,10 @@ impl<'a> Resolver<'a> { } *prev = Some(id); } - self.documents[document_id].worlds.push(id); + let prev = self.documents[document_id] + .worlds + .insert(name.to_string(), id); + assert!(prev.is_none()); } Ok(document_id) @@ -401,7 +404,7 @@ impl<'a> Resolver<'a> { match item { // ast::WorldItem::Use(_) => todo!(), ast::WorldItem::Import(import) => { - let item = self.resolve_world_item(document, &import.kind)?; + let item = self.resolve_world_item(import.name.name, document, &import.kind)?; if let WorldItem::Interface(id) = item { if imported_interfaces.insert(id, import.name.name).is_some() { return Err(Error { @@ -423,7 +426,7 @@ impl<'a> Resolver<'a> { import_spans.push(import.name.span); } ast::WorldItem::Export(export) => { - let item = self.resolve_world_item(document, &export.kind)?; + let item = self.resolve_world_item(export.name.name, document, &export.kind)?; if let WorldItem::Interface(id) = item { if exported_interfaces.insert(id, export.name.name).is_some() { return Err(Error { @@ -453,6 +456,7 @@ impl<'a> Resolver<'a> { fn resolve_world_item( &mut self, + name: &str, document: DocumentId, kind: &ast::ExternKind<'a>, ) -> Result { @@ -464,7 +468,15 @@ impl<'a> Resolver<'a> { ast::ExternKind::Path(path) => { let id = self.resolve_path(path)?; Ok(WorldItem::Interface(id)) - } // ast::ExternKind::Func(_) => todo!(), + } + ast::ExternKind::Func(func) => { + // Push a dummy type lookup which will get used during type + // resolution which is empty. + self.interface_types.push(IndexMap::default()); + let func = self.resolve_function(Docs::default(), name, func)?; + self.interface_types.pop(); + Ok(WorldItem::Function(func)) + } } } @@ -576,20 +588,12 @@ impl<'a> Resolver<'a> { ast::InterfaceItem::Value(value) => { let docs = self.docs(&value.docs); match &value.kind { - ValueKind::Func(Func { params, results }) => { + ValueKind::Func(func) => { self.define_interface_name(&value.name, InterfaceItem::Func)?; - let params = self.resolve_params(params)?; - let results = self.resolve_results(results)?; - let prev = self.interfaces[interface_id].functions.insert( - value.name.name.to_string(), - Function { - docs, - name: value.name.name.to_string(), - kind: FunctionKind::Freestanding, - params, - results, - }, - ); + let func = self.resolve_function(docs, value.name.name, func)?; + let prev = self.interfaces[interface_id] + .functions + .insert(value.name.name.to_string(), func); assert!(prev.is_none()); } } @@ -601,6 +605,18 @@ impl<'a> Resolver<'a> { Ok(interface_id) } + fn resolve_function(&mut self, docs: Docs, name: &str, func: &ast::Func) -> Result { + let params = self.resolve_params(&func.params)?; + let results = self.resolve_results(&func.results)?; + Ok(Function { + docs, + name: name.to_string(), + kind: FunctionKind::Freestanding, + params, + results, + }) + } + fn resolve_path(&self, path: &ast::UsePath<'a>) -> Result { match path { ast::UsePath::Self_(iface) => { diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 472c1d97a1..40f3ff8f58 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -220,7 +220,7 @@ pub struct Document { pub interfaces: IndexMap, /// The worlds contained in the document. - pub worlds: Vec, + pub worlds: IndexMap, /// The default interface of this document, if any. /// diff --git a/crates/wit-parser/src/live.rs b/crates/wit-parser/src/live.rs index a4324eff71..3b86ba20e0 100644 --- a/crates/wit-parser/src/live.rs +++ b/crates/wit-parser/src/live.rs @@ -20,7 +20,7 @@ impl LiveTypes { for (_, id) in doc.interfaces.iter() { self.add_interface(resolve, *id); } - for id in doc.worlds.iter() { + for (_, id) in doc.worlds.iter() { self.add_world(resolve, *id); } } @@ -38,10 +38,14 @@ impl LiveTypes { pub fn add_world(&mut self, resolve: &Resolve, world: WorldId) { let world = &resolve.worlds[world]; for (_, item) in world.imports.iter().chain(world.exports.iter()) { - match item { - WorldItem::Interface(id) => self.add_interface(resolve, *id), - WorldItem::Function(f) => self.add_func(resolve, f), - } + self.add_world_item(resolve, item); + } + } + + pub fn add_world_item(&mut self, resolve: &Resolve, item: &WorldItem) { + match item { + WorldItem::Interface(id) => self.add_interface(resolve, *id), + WorldItem::Function(f) => self.add_func(resolve, f), } } diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index a54ad2f70b..b4b4197f98 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -1,7 +1,7 @@ use crate::ast::lex::Span; use crate::{ - Document, DocumentId, Error, Interface, InterfaceId, Results, Type, TypeDef, TypeDefKind, - TypeId, TypeOwner, UnresolvedPackage, World, WorldId, WorldItem, + Document, DocumentId, Error, Function, Interface, InterfaceId, Results, Type, TypeDef, + TypeDefKind, TypeId, TypeOwner, UnresolvedPackage, World, WorldId, WorldItem, }; use anyhow::{bail, Context, Result}; use id_arena::{Arena, Id}; @@ -220,14 +220,140 @@ impl Resolve { }, } } + + /// Merges all the contents of a different `Resolve` into this one. The + /// `Remap` structure returned provides a mapping from all old indices to + /// new indices + pub fn merge(&mut self, resolve: Resolve) -> Remap { + let mut remap = Remap::default(); + let Resolve { + types, + worlds, + interfaces, + documents, + packages, + } = resolve; + + for (id, mut ty) in types { + remap.update_typedef(&mut ty); + let new_id = self.types.alloc(ty); + assert_eq!(remap.types.len(), id.index()); + remap.types.push(new_id); + } + + for (id, mut iface) in interfaces { + remap.update_interface(&mut iface); + let new_id = self.interfaces.alloc(iface); + assert_eq!(remap.interfaces.len(), id.index()); + remap.interfaces.push(new_id); + } + + for (id, mut world) in worlds { + for (_, item) in world.imports.iter_mut().chain(&mut world.exports) { + match item { + WorldItem::Function(f) => remap.update_function(f), + WorldItem::Interface(i) => *i = remap.interfaces[i.index()], + } + } + let new_id = self.worlds.alloc(world); + assert_eq!(remap.worlds.len(), id.index()); + remap.worlds.push(new_id); + } + + for (id, mut doc) in documents { + remap.update_document(&mut doc); + let new_id = self.documents.alloc(doc); + assert_eq!(remap.documents.len(), id.index()); + remap.documents.push(new_id); + } + + for (id, mut pkg) in packages { + for (_, doc) in pkg.documents.iter_mut() { + *doc = remap.documents[doc.index()]; + } + let new_id = self.packages.alloc(pkg); + assert_eq!(remap.packages.len(), id.index()); + remap.packages.push(new_id); + } + + // Fixup all "parent" links now + for id in remap.documents.iter().copied() { + if let Some(pkg) = &mut self.documents[id].package { + *pkg = remap.packages[pkg.index()]; + } + } + for id in remap.worlds.iter().copied() { + let doc = &mut self.worlds[id].document; + *doc = remap.documents[doc.index()]; + } + for id in remap.interfaces.iter().copied() { + let doc = &mut self.interfaces[id].document; + *doc = remap.documents[doc.index()]; + } + for id in remap.types.iter().copied() { + match &mut self.types[id].owner { + TypeOwner::Interface(id) => *id = remap.interfaces[id.index()], + TypeOwner::World(id) => *id = remap.worlds[id.index()], + TypeOwner::None => {} + } + } + + remap + } + + /// Merges the world `from` into the world `into`. + /// + /// This will attempt to merge one world into another, unioning all of its + /// imports and exports together. This is an operation performed by + /// `wit-component`, for example where two different worlds from two + /// different libraries were linked into the same core wasm file and are + /// producing a singular world that will be the final component's + /// interface. + /// + /// This operation can fail if the imports/exports overlap. + // + // TODO: overlap shouldn't be a hard error here, there should be some form + // of comparing names/urls/deep merging or such to get this working. + pub fn merge_worlds(&mut self, from: WorldId, into: WorldId) -> Result<()> { + let mut new_imports = Vec::new(); + let mut new_exports = Vec::new(); + + let from_world = &self.worlds[from]; + let into_world = &self.worlds[into]; + for (name, import) in from_world.imports.iter() { + match into_world.imports.get(name) { + Some(_) => bail!("duplicate import found for interface `{name}`"), + None => new_imports.push((name.clone(), import.clone())), + } + } + for (name, export) in from_world.exports.iter() { + match into_world.exports.get(name) { + Some(_) => bail!("duplicate export found for interface `{name}`"), + None => new_exports.push((name.clone(), export.clone())), + } + } + + let into = &mut self.worlds[into]; + for (name, import) in new_imports { + let prev = into.imports.insert(name, import); + assert!(prev.is_none()); + } + for (name, import) in new_exports { + let prev = into.exports.insert(name, import); + assert!(prev.is_none()); + } + + Ok(()) + } } #[derive(Default)] -struct Remap { - types: Vec, - interfaces: Vec, - documents: Vec, - worlds: Vec, +pub struct Remap { + pub types: Vec, + pub interfaces: Vec, + pub worlds: Vec, + pub documents: Vec, + pub packages: Vec, } impl Remap { @@ -485,17 +611,21 @@ impl Remap { *ty = self.types[ty.index()]; } for (_, func) in iface.functions.iter_mut() { - for (_, ty) in func.params.iter_mut() { - self.update_ty(ty); - } - match &mut func.results { - Results::Named(named) => { - for (_, ty) in named.iter_mut() { - self.update_ty(ty); - } + self.update_function(func); + } + } + + fn update_function(&self, func: &mut Function) { + for (_, ty) in func.params.iter_mut() { + self.update_ty(ty); + } + match &mut func.results { + Results::Named(named) => { + for (_, ty) in named.iter_mut() { + self.update_ty(ty); } - Results::Anon(ty) => self.update_ty(ty), } + Results::Anon(ty) => self.update_ty(ty), } } @@ -526,6 +656,8 @@ impl Remap { let mut explicit_export_names = HashMap::new(); let mut imports = Vec::new(); let mut exports = Vec::new(); + let mut import_funcs = Vec::new(); + let mut export_funcs = Vec::new(); for ((name, item), span) in mem::take(&mut world.imports).into_iter().zip(import_spans) { match item { WorldItem::Interface(id) => { @@ -534,7 +666,10 @@ impl Remap { let prev = explicit_import_names.insert(id, name); assert!(prev.is_none()); } - WorldItem::Function(_) => unimplemented!(), + WorldItem::Function(mut f) => { + self.update_function(&mut f); + import_funcs.push((name, f)); + } } } for ((name, item), span) in mem::take(&mut world.exports).into_iter().zip(export_spans) { @@ -545,7 +680,10 @@ impl Remap { let prev = explicit_export_names.insert(id, name); assert!(prev.is_none()); } - WorldItem::Function(_) => unimplemented!(), + WorldItem::Function(mut f) => { + self.update_function(&mut f); + export_funcs.push((name, f)); + } } } @@ -570,6 +708,23 @@ impl Remap { elaborate.export(id, span)?; } + for (name, func) in import_funcs { + let prev = world + .imports + .insert(name.clone(), WorldItem::Function(func)); + if prev.is_some() { + bail!("import of function `{name}` shadows previously imported interface"); + } + } + for (name, func) in export_funcs { + let prev = world + .exports + .insert(name.clone(), WorldItem::Function(func)); + if prev.is_some() { + bail!("export of function `{name}` shadows previously exported interface"); + } + } + log::trace!("imports = {:?}", world.imports); log::trace!("exports = {:?}", world.exports); @@ -580,7 +735,7 @@ impl Remap { for (_name, iface) in doc.interfaces.iter_mut() { *iface = self.interfaces[iface.index()]; } - for world in doc.worlds.iter_mut() { + for (_name, world) in doc.worlds.iter_mut() { *world = self.worlds[world.index()]; } if let Some(default) = &mut doc.default_interface { @@ -589,7 +744,6 @@ impl Remap { if let Some(default) = &mut doc.default_world { *default = self.worlds[default.index()]; } - assert!(doc.package.is_none()); } } diff --git a/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit new file mode 100644 index 0000000000..7bf7e5fce9 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit @@ -0,0 +1,4 @@ +world foo { + import foo: func() + import foo: func() +} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit.result new file mode 100644 index 0000000000..ed79d83f61 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit.result @@ -0,0 +1,5 @@ +name imported twice + --> tests/ui/parse-fail/world-top-level-func.wit:3:10 + | + 3 | import foo: func() + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit new file mode 100644 index 0000000000..d872f06792 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit @@ -0,0 +1,3 @@ +world foo { + import foo: func(a: b) +} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result new file mode 100644 index 0000000000..f1ccfb38bc --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result @@ -0,0 +1,5 @@ +name is not defined + --> tests/ui/parse-fail/world-top-level-func2.wit:2:23 + | + 2 | import foo: func(a: b) + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/world-top-level-funcs.wit b/crates/wit-parser/tests/ui/world-top-level-funcs.wit new file mode 100644 index 0000000000..9d89a682ed --- /dev/null +++ b/crates/wit-parser/tests/ui/world-top-level-funcs.wit @@ -0,0 +1,7 @@ +world foo { + import foo: func() + export foo: func() + + import bar: func(arg: list) + export some-name: func() -> list> +} diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index 42203ab256..b144207f6e 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -4,7 +4,7 @@ use anyhow::{anyhow, bail, Context, Result}; use clap::Parser; use std::io::Read; use std::path::{Path, PathBuf}; -use wasm_encoder::Encode; +use wasm_encoder::{Encode, Section}; use wasm_tools::Output; use wit_component::{ComponentEncoder, DecodedWasm, DocumentPrinter, StringEncoding}; use wit_parser::{PackageId, Resolve, UnresolvedPackage}; @@ -169,11 +169,7 @@ impl EmbedOpts { None => bail!("invalid `--world` argument"), }; let world = match parts.next() { - Some(name) => match resolve.documents[doc] - .worlds - .iter() - .find(|w| resolve.worlds[**w].name == name) - { + Some(name) => match resolve.documents[doc].worlds.get(name) { Some(world) => *world, None => bail!("no world named `{name}` in document"), }, @@ -189,11 +185,12 @@ impl EmbedOpts { self.encoding.unwrap_or(StringEncoding::UTF8), )?; - wasm_encoder::CustomSection { + let section = wasm_encoder::CustomSection { name: "component-type", data: &encoded, - } - .encode(&mut wasm); + }; + wasm.push(section.id()); + section.encode(&mut wasm); self.io.output(Output::Wasm { bytes: &wasm, From 7659be202c738fc60154e5ef41ea85af368bcb46 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 9 Jan 2023 20:08:55 -0800 Subject: [PATCH 08/37] Simplify normalizing for tests --- crates/wit-parser/tests/all.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/wit-parser/tests/all.rs b/crates/wit-parser/tests/all.rs index 173624b483..85f6dabfd1 100644 --- a/crates/wit-parser/tests/all.rs +++ b/crates/wit-parser/tests/all.rs @@ -119,7 +119,7 @@ impl Runner<'_> { "some generic platform-agnostic error message", ); } - normalize(test, &format!("{:?}", e)) + normalize(&format!("{:?}", e)) } } } else { @@ -146,7 +146,7 @@ impl Runner<'_> { "failed to read test expectation file {:?}\nthis can be fixed with BLESS=1", result_file ))?; - let expected = normalize(test, &expected); + let expected = normalize(&expected); if expected != result { bail!( "failed test: result is not as expected:{}", @@ -157,13 +157,10 @@ impl Runner<'_> { self.bump_ntests(); return Ok(()); - fn normalize(test: &Path, s: &str) -> String { - s.replace( - &test.display().to_string(), - &test.display().to_string().replace('\\', "/"), - ) - .replace("\\parse-fail\\", "/parse-fail/") - .replace("\r\n", "\n") + fn normalize(s: &str) -> String { + s.replace('\\', "/") + .replace("\\parse-fail\\", "/parse-fail/") + .replace("\r\n", "\n") } } From cfac00194324fc83cd2d702e88e7e0caa6e870d7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 9 Jan 2023 20:24:19 -0800 Subject: [PATCH 09/37] Further simplification --- crates/wit-parser/tests/all.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/wit-parser/tests/all.rs b/crates/wit-parser/tests/all.rs index 85f6dabfd1..7300a375e5 100644 --- a/crates/wit-parser/tests/all.rs +++ b/crates/wit-parser/tests/all.rs @@ -158,9 +158,7 @@ impl Runner<'_> { return Ok(()); fn normalize(s: &str) -> String { - s.replace('\\', "/") - .replace("\\parse-fail\\", "/parse-fail/") - .replace("\r\n", "\n") + s.replace('\\', "/").replace("\r\n", "\n") } } From 4d5ee5943af6a648773d54a451128c1535b57f31 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 9 Jan 2023 20:34:02 -0800 Subject: [PATCH 10/37] Fix windows compat --- crates/wit-parser/src/resolve.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index b4b4197f98..b4ae4936e0 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -96,7 +96,10 @@ impl Resolve { if !path.is_dir() { bail!(Error { span, - msg: format!("dependency on `{dep}` doesn't exist at {path:?}"), + msg: format!( + "dependency on `{dep}` doesn't exist at: {}", + path.display() + ), }) } let url = Some(format!("path:/{dep}")); From 2f51fbaa06eced0483001583bb00709db26d09f3 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 9 Jan 2023 20:46:11 -0800 Subject: [PATCH 11/37] Fix test expectation --- crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result index 7b01397158..b55b5e3631 100644 --- a/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result @@ -1,4 +1,4 @@ -dependency on `nonexistent` doesn't exist at "tests/ui/parse-fail/bad-pkg1/deps/nonexistent" +dependency on `nonexistent` doesn't exist at: tests/ui/parse-fail/bad-pkg1/deps/nonexistent --> tests/ui/parse-fail/bad-pkg1/root.wit:2:7 | 2 | use nonexistent.foo.{} From bb56cbc07b0ae986c0f86aba3063f4a905addd68 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 9 Jan 2023 20:54:24 -0800 Subject: [PATCH 12/37] Add a test for renaming imports --- .../rename-import-interface/component.wat | 22 +++++++++++++++++++ .../rename-import-interface/module.wat | 3 +++ .../rename-import-interface/module.wit | 7 ++++++ src/bin/wasm-tools/component.rs | 3 +++ 4 files changed, 35 insertions(+) create mode 100644 crates/wit-component/tests/components/rename-import-interface/component.wat create mode 100644 crates/wit-component/tests/components/rename-import-interface/module.wat create mode 100644 crates/wit-component/tests/components/rename-import-interface/module.wit diff --git a/crates/wit-component/tests/components/rename-import-interface/component.wat b/crates/wit-component/tests/components/rename-import-interface/component.wat new file mode 100644 index 0000000000..cc9b4875e9 --- /dev/null +++ b/crates/wit-component/tests/components/rename-import-interface/component.wat @@ -0,0 +1,22 @@ +(component + (type (;0;) + (instance + (type (;0;) (func)) + (export (;0;) "the-func" (func (type 0))) + ) + ) + (import "bar" (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func)) + (import "bar" "the-func" (func (;0;) (type 0))) + ) + (alias export 0 "the-func" (func (;0;))) + (core func (;0;) (canon lower (func 0))) + (core instance (;0;) + (export "the-func" (func 0)) + ) + (core instance (;1;) (instantiate 0 + (with "bar" (instance 0)) + ) + ) +) \ No newline at end of file diff --git a/crates/wit-component/tests/components/rename-import-interface/module.wat b/crates/wit-component/tests/components/rename-import-interface/module.wat new file mode 100644 index 0000000000..e639cf3752 --- /dev/null +++ b/crates/wit-component/tests/components/rename-import-interface/module.wat @@ -0,0 +1,3 @@ +(module + (import "bar" "the-func" (func)) +) diff --git a/crates/wit-component/tests/components/rename-import-interface/module.wit b/crates/wit-component/tests/components/rename-import-interface/module.wit new file mode 100644 index 0000000000..15eee29f5b --- /dev/null +++ b/crates/wit-component/tests/components/rename-import-interface/module.wit @@ -0,0 +1,7 @@ +interface foo { + the-func: func() +} + +default world the-world { + import bar: self.foo +} diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index b144207f6e..e542300383 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -450,6 +450,9 @@ fn parse_wit(path: &Path) -> Result<(Resolve, PackageId)> { fn is_wasm(bytes: &[u8]) -> bool { use wast::lexer::{Lexer, Token}; + if bytes.starts_with(b"\0asm") { + return true; + } let text = match std::str::from_utf8(bytes) { Ok(s) => s, Err(_) => return true, From 485c6e966ad160360ea5f6802899992865ed4f95 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 9 Jan 2023 20:54:40 -0800 Subject: [PATCH 13/37] Remove stray TODO file --- TODO.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 TODO.md diff --git a/TODO.md b/TODO.md deleted file mode 100644 index bb4fd52955..0000000000 --- a/TODO.md +++ /dev/null @@ -1,2 +0,0 @@ -* test `import foo: bar` -* add support for `import foo: func()` and export From 6466afcbb74530c083f18a1dc760e9d1659c90c9 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 11 Jan 2023 06:50:13 -0800 Subject: [PATCH 14/37] Fixup some more windows compat --- crates/wit-parser/src/ast/resolve.rs | 5 ++++- crates/wit-parser/src/resolve.rs | 2 +- .../tests/ui/parse-fail/invalid@filename.wit.result | 2 +- .../tests/ui/parse-fail/unresolved-use10.wit.result | 2 +- .../tests/ui/parse-fail/unresolved-use11.wit.result | 2 +- .../tests/ui/parse-fail/use-from-package-world2.wit.result | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 1ea44ae551..660aaab3e5 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -64,7 +64,10 @@ impl<'a> Resolver<'a> { None => filename, }; crate::validate_id(name).map_err(|e| { - anyhow::anyhow!("filename was not a valid WIT identifier for {path:?}: {e}") + anyhow::anyhow!( + "filename was not a valid WIT identifier for {}: {e}", + path.display() + ) })?; let prev = self.asts.insert(name, ast); if prev.is_some() { diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index b4ae4936e0..064fdc30a4 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -82,7 +82,7 @@ impl Resolve { enqueued.insert(path.to_path_buf()); while let Some((pkg_root, url)) = to_parse.pop() { let mut pkg = UnresolvedPackage::parse_dir(&pkg_root) - .with_context(|| format!("failed to parse package at {path:?}"))?; + .with_context(|| format!("failed to parse package: {}", path.display()))?; pkg.url = url; let mut deps = Vec::new(); diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result b/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result index d634a8fa6e..1eaa15971e 100644 --- a/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result @@ -1 +1 @@ -filename was not a valid WIT identifier for "tests/ui/parse-fail/invalid@filename.wit": invalid character in identifier '@' \ No newline at end of file +filename was not a valid WIT identifier for tests/ui/parse-fail/invalid@filename.wit: invalid character in identifier '@' \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result index 46b214db21..bb1504e286 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result @@ -1,4 +1,4 @@ -failed to parse package at "tests/ui/parse-fail/unresolved-use10" +failed to parse package: tests/ui/parse-fail/unresolved-use10 Caused by: document does not specify a default interface diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use11.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use11.wit.result index aafc35c3fe..afdbcf6f66 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unresolved-use11.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use11.wit.result @@ -1,4 +1,4 @@ -failed to parse package at "tests/ui/parse-fail/unresolved-use11" +failed to parse package: tests/ui/parse-fail/unresolved-use11 Caused by: name `thing` is not defined diff --git a/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2.wit.result index afd167522a..95608cfb58 100644 --- a/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/use-from-package-world2.wit.result @@ -1,4 +1,4 @@ -failed to parse package at "tests/ui/parse-fail/use-from-package-world2" +failed to parse package: tests/ui/parse-fail/use-from-package-world2 Caused by: cannot import from world `foo` From 880f3113d9231ef6ccb00efe3cc0c78fb6d25bdf Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 11 Jan 2023 07:33:19 -0800 Subject: [PATCH 15/37] Add some comments here and there --- crates/wit-component/src/encoding/wit.rs | 19 ++++++++++++++++++- crates/wit-parser/src/lib.rs | 12 ++++++++---- crates/wit-parser/src/resolve.rs | 2 ++ src/bin/wasm-tools/component.rs | 15 ++++++++++----- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/crates/wit-component/src/encoding/wit.rs b/crates/wit-component/src/encoding/wit.rs index bd24eec83f..4088cb8b43 100644 --- a/crates/wit-component/src/encoding/wit.rs +++ b/crates/wit-component/src/encoding/wit.rs @@ -8,7 +8,24 @@ use url::Url; use wasm_encoder::*; use wit_parser::*; -/// TODO +/// Encodes the given `package` within `resolve` to a binary WebAssembly +/// representation. +/// +/// This function is the root of the implementation of serializing a WIT package +/// into a WebAssembly representation. The wasm representation serves two +/// purposes: +/// +/// * One is to be a binary encoding of a WIT document which is ideally more +/// stable than the WIT textual format itself. +/// * Another is to provide a clear mapping of all WIT features into the +/// component model through use of its binary representation. +/// +/// The `resolve` provided is a "world" of packages and types and such and the +/// `package` argument is an ID within the world provided. The documents within +/// `package` will all be encoded into the binary returned. +/// +/// The binary returned can be [`decode`d](crate::decode) to recover the WIT +/// package provided. pub fn encode(resolve: &Resolve, package: PackageId) -> Result> { let mut encoder = Encoder { component: ComponentBuilder::default(), diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 40f3ff8f58..f57cf9777c 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -11,7 +11,7 @@ use ast::{lex::Span, Ast, Resolver, SourceMap}; mod sizealign; pub use sizealign::*; mod resolve; -pub use resolve::{Package, PackageId, Resolve}; +pub use resolve::{Package, PackageId, Remap, Resolve}; mod live; pub use live::LiveTypes; @@ -44,9 +44,13 @@ pub type DocumentId = Id; /// supported on an `UnresolvedPackage` due to the lack of knowledge about the /// foreign types. This is intended to be an intermediate state which can be /// inspected by embedders, if necessary, before quickly transforming to a -/// `Resolve` to fully work with a WIT package. -// -// TODO: implement and add docs about converting to a `Resolve` +/// [`Resolve`] to fully work with a WIT package. +/// +/// After an [`UnresolvedPackage`] is parsed it can be fully resolved with +/// [`Resolve::push`]. During this operation a dependency map is specified which +/// will connect the `foreign_deps` field of this structure to packages +/// previously inserted within the [`Resolve`]. Embedders are responsible for +/// performing this resolution themselves. #[derive(Clone)] pub struct UnresolvedPackage { /// Local name for this package. diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 064fdc30a4..9c44538f48 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -350,6 +350,8 @@ impl Resolve { } } +/// Structure returned by [`Resolve::merge`] which contains mappings from +/// old-ids to new-ids after the merge. #[derive(Default)] pub struct Remap { pub types: Vec, diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index e542300383..754a621d49 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -52,13 +52,18 @@ fn parse_adapter(s: &str) -> Result<(String, Vec)> { /// WebAssembly component encoder from an input core wasm binary. /// /// This subcommand will create a new component `*.wasm` file from an input core -/// wasm binary. The input core wasm binary is expected to be compiled with -/// `wit-component` or derivative projects which encodes component-based type -/// information into the input core wasm binary's custom sections. The `--wit` -/// option can also be used to specify the interface manually too. +/// wasm binary. The input core wasm binary must have metadata embedded within +/// it about the component-types used during its compilation. This is done +/// automatically for `wit-bindgen`-based projects, for example, and can be +/// manually done through the `wasm-tools component embed` subcommand. +/// +/// This command will perform translation by collecting all type information +/// used during compilation of the core wasm module and will produce a component +/// with all of this type information resolved. #[derive(Parser)] pub struct NewOpts { - /// The path to an adapter module to satisfy imports. + /// The path to an adapter module to satisfy imports not otherwise bound to + /// WIT interfaces. /// /// An adapter module can be used to translate the `wasi_snapshot_preview1` /// ABI, for example, to one that uses the component model. The first From d0e703488d1c4bf7d5ff0bd5c68b426f2619913c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 11 Jan 2023 07:33:46 -0800 Subject: [PATCH 16/37] Rely less on the precise structure of URLs This commit updates the WIT decoding process to not actually rely on the structure of `pkg:/...` URLs at all, instead purely maintaining a hash map of them as opaque identifiers. The purpose of this is to hopefully future-proof against changes in the URL field which only requires that they be unique. This still relies on the URL structure, however, for foreign dependencies. I'm not sure how to get around that at this time because there's no other way to organize just imported interfaces back into packages and documents. --- crates/wit-component/src/decoding.rs | 49 ++++++++++--------- crates/wit-component/src/encoding/wit.rs | 11 +++-- .../tests/interfaces/console.wat | 4 +- .../tests/interfaces/diamond-disambiguate.wat | 12 ++--- .../tests/interfaces/diamond.wat | 10 ++-- .../wit-component/tests/interfaces/empty.wat | 8 +-- .../tests/interfaces/exports.wat | 6 +-- .../wit-component/tests/interfaces/flags.wat | 6 +-- .../wit-component/tests/interfaces/floats.wat | 6 +-- .../tests/interfaces/import-and-export.wat | 8 +-- .../tests/interfaces/integers.wat | 6 +-- .../wit-component/tests/interfaces/lists.wat | 6 +-- .../tests/interfaces/multi-doc.wat | 12 ++--- .../tests/interfaces/records.wat | 6 +-- .../interfaces/reference-out-of-order.wat | 6 +-- .../tests/interfaces/simple-deps.wat | 4 +- .../tests/interfaces/simple-multi.wat | 8 +-- .../tests/interfaces/simple-use.wat | 6 +-- .../tests/interfaces/simple-world.wat | 6 +-- .../tests/interfaces/type-alias.wat | 6 +-- .../tests/interfaces/type-alias2.wat | 6 +-- .../tests/interfaces/variants.wat | 6 +-- .../tests/interfaces/wasi-http.wat | 12 ++--- .../interfaces/world-inline-interface.wat | 4 +- .../tests/interfaces/world-top-level.wat | 8 +-- 25 files changed, 113 insertions(+), 109 deletions(-) diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index 0d2b7f24a0..3e92cf7227 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -102,6 +102,7 @@ impl<'a> ComponentInfo<'a> { url_to_package: HashMap::default(), type_map: HashMap::new(), type_src_map: HashMap::new(), + url_to_interface: HashMap::new(), }; for (doc, export) in self.exports.iter() { @@ -174,6 +175,7 @@ struct WitPackageDecoder<'a> { info: &'a ComponentInfo<'a>, package: PackageId, url_to_package: HashMap, + url_to_interface: HashMap, /// A map from a type id to what it's been translated to. type_map: HashMap, @@ -227,10 +229,6 @@ impl WitPackageDecoder<'_> { assert!(prev.is_none()); for (name, (url, ty)) in ty.exports.iter() { - if url.is_some() { - bail!("component type export `{name}` should not have a url") - } - match ty { types::ComponentEntityType::Instance(idx) => { let ty = match self.info.types.type_from_id(*idx) { @@ -244,6 +242,10 @@ impl WitPackageDecoder<'_> { .interfaces .insert(name.to_string(), id); assert!(prev.is_none()); + if let Some(url) = url { + let prev = self.url_to_interface.insert(url.clone(), id); + assert!(prev.is_none()); + } } types::ComponentEntityType::Component(idx) => { let ty = match self.info.types.type_from_id(*idx) { @@ -372,32 +374,31 @@ impl WitPackageDecoder<'_> { fn extract_url_interface(&mut self, url: &Url) -> Result { Ok(if url.scheme() == "pkg" { - self.extract_pkg_interface(url) - .with_context(|| format!("failed to parse url: {url}"))? + self.url_to_interface + .get(url) + .copied() + .ok_or_else(|| anyhow!("no previously defined interface with url: {url}"))? } else { self.extract_dep_interface(url) .with_context(|| format!("failed to parse url: {url}"))? }) } - fn extract_pkg_interface(&self, url: &Url) -> Result { - let mut segments = url.path_segments().ok_or_else(|| anyhow!("invalid url"))?; - let document = segments.next().ok_or_else(|| anyhow!("invalid url"))?; - let interface = segments.next().ok_or_else(|| anyhow!("invalid url"))?; - if segments.next().is_some() { - bail!("invalid url") - } - let doc = *self.resolve.packages[self.package] - .documents - .get(document) - .ok_or_else(|| anyhow!("document `{document}` not previously defined"))?; - let doc = &self.resolve.documents[doc]; - let interface = *doc.interfaces.get(interface).ok_or_else(|| { - anyhow!("interface `{interface}` not defined in document `{document}`") - })?; - Ok(interface) - } - + /// TODO: Ideally this function should not need to exist. + /// + /// This function parses the `url` provided and requires it to have a + /// particular structure. That's not really great, however, since otherwise + /// there's no need to impose structure on the url field of imports/exports. + /// + /// Note that this is only used for foreign dependencies of which the binary + /// encoding does not currently reflect the package/document/interface + /// organization. Instead foreign dependencies simply have their interfaces + /// imported, and from this interface import we need to somehow translate + /// back into a package/document structure as well. + /// + /// Resolving this may require changing the binary format for components, or + /// otherwise encoding more pieces into the binary encoding of a WIT + /// document. In any case this is "good enough" for now hopefully. fn extract_dep_interface(&mut self, url: &Url) -> Result { // Extract the interface and the document from the url let mut segments = url.path_segments().ok_or_else(|| anyhow!("invalid url"))?; diff --git a/crates/wit-component/src/encoding/wit.rs b/crates/wit-component/src/encoding/wit.rs index 4088cb8b43..8f6ae89929 100644 --- a/crates/wit-component/src/encoding/wit.rs +++ b/crates/wit-component/src/encoding/wit.rs @@ -46,8 +46,9 @@ impl Encoder<'_> { fn run(&mut self) -> Result<()> { for (name, doc) in self.resolve.packages[self.package].documents.iter() { let ty = self.encode_document(*doc)?; + let url = format!("pkg:/{name}"); self.component - .export(name, "", ComponentExportKind::Type, ty); + .export(name, &url, ComponentExportKind::Type, ty); } Ok(()) } @@ -101,12 +102,13 @@ impl Encoder<'_> { let doc = &self.resolve.documents[doc]; for (name, interface) in doc.interfaces.iter() { let idx = encoder.encode_instance(*interface)?; + let url = format!("pkg:/{}/{name}", doc.name); encoder .outer - .export(name, "", ComponentTypeRef::Instance(idx)); + .export(name, &url, ComponentTypeRef::Instance(idx)); } - for (_name, world) in doc.worlds.iter() { + for (name, world) in doc.worlds.iter() { let world = &self.resolve.worlds[*world]; let mut component = InterfaceEncoder::new(self.resolve); for (name, import) in world.imports.iter() { @@ -137,9 +139,10 @@ impl Encoder<'_> { } let idx = encoder.outer.type_count(); encoder.outer.ty().component(&component.outer); + let url = format!("pkg:/{}/{name}", doc.name); encoder .outer - .export(&world.name, "", ComponentTypeRef::Component(idx)); + .export(&name, &url, ComponentTypeRef::Component(idx)); } Ok(self.component.component_type(&encoder.outer)) diff --git a/crates/wit-component/tests/interfaces/console.wat b/crates/wit-component/tests/interfaces/console.wat index 4e139a0694..61b1f312d5 100644 --- a/crates/wit-component/tests/interfaces/console.wat +++ b/crates/wit-component/tests/interfaces/console.wat @@ -7,8 +7,8 @@ (export (;0;) "log" (func (type 0))) ) ) - (export (;0;) "console" (instance (type 0))) + (export (;0;) "console" "pkg:/console/console" (instance (type 0))) ) ) - (export (;1;) "console" (type 0)) + (export (;1;) "console" "pkg:/console" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/diamond-disambiguate.wat b/crates/wit-component/tests/interfaces/diamond-disambiguate.wat index b60b68a4e3..01021a108b 100644 --- a/crates/wit-component/tests/interfaces/diamond-disambiguate.wat +++ b/crates/wit-component/tests/interfaces/diamond-disambiguate.wat @@ -7,10 +7,10 @@ (export (;1;) "t1" (type (eq 0))) ) ) - (export (;0;) "shared" (instance (type 0))) + (export (;0;) "shared" "pkg:/shared1/shared" (instance (type 0))) ) ) - (export (;1;) "shared1" (type 0)) + (export (;1;) "shared1" "pkg:/shared1" (type 0)) (type (;2;) (component (type (;0;) @@ -19,10 +19,10 @@ (export (;1;) "t2" (type (eq 0))) ) ) - (export (;0;) "shared" (instance (type 0))) + (export (;0;) "shared" "pkg:/shared2/shared" (instance (type 0))) ) ) - (export (;3;) "shared2" (type 2)) + (export (;3;) "shared2" "pkg:/shared2" (type 2)) (type (;4;) (component (type (;0;) @@ -59,8 +59,8 @@ (import "bar" (instance (type 5))) ) ) - (export (;0;) "w1" (component (type 0))) + (export (;0;) "w1" "pkg:/join/w1" (component (type 0))) ) ) - (export (;5;) "join" (type 4)) + (export (;5;) "join" "pkg:/join" (type 4)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/diamond.wat b/crates/wit-component/tests/interfaces/diamond.wat index 2f7a8dd001..a321b31ce1 100644 --- a/crates/wit-component/tests/interfaces/diamond.wat +++ b/crates/wit-component/tests/interfaces/diamond.wat @@ -7,7 +7,7 @@ (export (;1;) "the-enum" (type (eq 0))) ) ) - (export (;0;) "shared" (instance (type 0))) + (export (;0;) "shared" "pkg:/diamond/shared" (instance (type 0))) (type (;1;) (component (type (;0;) @@ -34,7 +34,7 @@ (import "bar" (instance (type 3))) ) ) - (export (;0;) "w1" (component (type 1))) + (export (;0;) "w1" "pkg:/diamond/w1" (component (type 1))) (type (;2;) (component (type (;0;) @@ -61,7 +61,7 @@ (export (;0;) "bar" (instance (type 3))) ) ) - (export (;1;) "w2" (component (type 2))) + (export (;1;) "w2" "pkg:/diamond/w2" (component (type 2))) (type (;3;) (component (type (;0;) @@ -81,8 +81,8 @@ (export (;0;) "bar" (instance (type 2))) ) ) - (export (;2;) "w3" (component (type 3))) + (export (;2;) "w3" "pkg:/diamond/w3" (component (type 3))) ) ) - (export (;1;) "diamond" (type 0)) + (export (;1;) "diamond" "pkg:/diamond" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/empty.wat b/crates/wit-component/tests/interfaces/empty.wat index ead69fbd61..fc16b8128c 100644 --- a/crates/wit-component/tests/interfaces/empty.wat +++ b/crates/wit-component/tests/interfaces/empty.wat @@ -4,7 +4,7 @@ (type (;0;) (instance) ) - (export (;0;) "empty" (instance (type 0))) + (export (;0;) "empty" "pkg:/empty/empty" (instance (type 0))) (type (;1;) (component (type (;0;) @@ -17,12 +17,12 @@ (export (;0;) "empty" "pkg:/empty/empty" (instance (type 1))) ) ) - (export (;0;) "empty-world" (component (type 1))) + (export (;0;) "empty-world" "pkg:/empty/empty-world" (component (type 1))) (type (;2;) (component) ) - (export (;1;) "actually-empty-world" (component (type 2))) + (export (;1;) "actually-empty-world" "pkg:/empty/actually-empty-world" (component (type 2))) ) ) - (export (;1;) "empty" (type 0)) + (export (;1;) "empty" "pkg:/empty" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/exports.wat b/crates/wit-component/tests/interfaces/exports.wat index 4b155d3896..841a18e13d 100644 --- a/crates/wit-component/tests/interfaces/exports.wat +++ b/crates/wit-component/tests/interfaces/exports.wat @@ -9,7 +9,7 @@ (export (;0;) "my-function" (func (type 2))) ) ) - (export (;0;) "foo" (instance (type 0))) + (export (;0;) "foo" "pkg:/exports/foo" (instance (type 0))) (type (;1;) (component (type (;0;) @@ -23,8 +23,8 @@ (export (;0;) "foo" "pkg:/exports/foo" (instance (type 0))) ) ) - (export (;0;) "export-foo" (component (type 1))) + (export (;0;) "export-foo" "pkg:/exports/export-foo" (component (type 1))) ) ) - (export (;1;) "exports" (type 0)) + (export (;1;) "exports" "pkg:/exports" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/flags.wat b/crates/wit-component/tests/interfaces/flags.wat index 79148f0632..c7026c0b4d 100644 --- a/crates/wit-component/tests/interfaces/flags.wat +++ b/crates/wit-component/tests/interfaces/flags.wat @@ -33,7 +33,7 @@ (export (;6;) "roundtrip-flag64" (func (type 20))) ) ) - (export (;0;) "imports" (instance (type 0))) + (export (;0;) "imports" "pkg:/flags/imports" (instance (type 0))) (type (;1;) (component (type (;0;) @@ -71,8 +71,8 @@ (import "imports" "pkg:/flags/imports" (instance (type 0))) ) ) - (export (;0;) "flags-world" (component (type 1))) + (export (;0;) "flags-world" "pkg:/flags/flags-world" (component (type 1))) ) ) - (export (;1;) "flags" (type 0)) + (export (;1;) "flags" "pkg:/flags" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/floats.wat b/crates/wit-component/tests/interfaces/floats.wat index 160b877649..08de2b549d 100644 --- a/crates/wit-component/tests/interfaces/floats.wat +++ b/crates/wit-component/tests/interfaces/floats.wat @@ -13,7 +13,7 @@ (export (;3;) "float64-result" (func (type 3))) ) ) - (export (;0;) "floats" (instance (type 0))) + (export (;0;) "floats" "pkg:/floats/floats" (instance (type 0))) (type (;1;) (component (type (;0;) @@ -31,8 +31,8 @@ (import "floats" "pkg:/floats/floats" (instance (type 0))) ) ) - (export (;0;) "floats-world" (component (type 1))) + (export (;0;) "floats-world" "pkg:/floats/floats-world" (component (type 1))) ) ) - (export (;1;) "floats" (type 0)) + (export (;1;) "floats" "pkg:/floats" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/import-and-export.wat b/crates/wit-component/tests/interfaces/import-and-export.wat index a490405223..66d779b1a2 100644 --- a/crates/wit-component/tests/interfaces/import-and-export.wat +++ b/crates/wit-component/tests/interfaces/import-and-export.wat @@ -7,14 +7,14 @@ (export (;0;) "foo" (func (type 0))) ) ) - (export (;0;) "foo" (instance (type 0))) + (export (;0;) "foo" "pkg:/import-and-export/foo" (instance (type 0))) (type (;1;) (instance (type (;0;) (func)) (export (;0;) "bar" (func (type 0))) ) ) - (export (;1;) "bar" (instance (type 1))) + (export (;1;) "bar" "pkg:/import-and-export/bar" (instance (type 1))) (type (;2;) (component (type (;0;) @@ -33,8 +33,8 @@ (export (;0;) "bar" "pkg:/import-and-export/bar" (instance (type 1))) ) ) - (export (;0;) "import-and-export" (component (type 2))) + (export (;0;) "import-and-export" "pkg:/import-and-export/import-and-export" (component (type 2))) ) ) - (export (;1;) "import-and-export" (type 0)) + (export (;1;) "import-and-export" "pkg:/import-and-export" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/integers.wat b/crates/wit-component/tests/interfaces/integers.wat index 705988ec35..63eda7d67a 100644 --- a/crates/wit-component/tests/interfaces/integers.wat +++ b/crates/wit-component/tests/interfaces/integers.wat @@ -44,7 +44,7 @@ (export (;18;) "multi-ret" (func (type 19))) ) ) - (export (;0;) "integers" (instance (type 0))) + (export (;0;) "integers" "pkg:/integers/integers" (instance (type 0))) (type (;1;) (component (type (;0;) @@ -93,8 +93,8 @@ (import "integers" "pkg:/integers/integers" (instance (type 0))) ) ) - (export (;0;) "integers-world" (component (type 1))) + (export (;0;) "integers-world" "pkg:/integers/integers-world" (component (type 1))) ) ) - (export (;1;) "integers" (type 0)) + (export (;1;) "integers" "pkg:/integers" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/lists.wat b/crates/wit-component/tests/interfaces/lists.wat index ce460a94e1..8f18a3d755 100644 --- a/crates/wit-component/tests/interfaces/lists.wat +++ b/crates/wit-component/tests/interfaces/lists.wat @@ -95,7 +95,7 @@ (export (;27;) "load-store-everything" (func (type 61))) ) ) - (export (;0;) "lists" (instance (type 0))) + (export (;0;) "lists" "pkg:/lists/lists" (instance (type 0))) (type (;1;) (component (type (;0;) @@ -195,8 +195,8 @@ (import "lists" "pkg:/lists/lists" (instance (type 0))) ) ) - (export (;0;) "lists-world" (component (type 1))) + (export (;0;) "lists-world" "pkg:/lists/lists-world" (component (type 1))) ) ) - (export (;1;) "lists" (type 0)) + (export (;1;) "lists" "pkg:/lists" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/multi-doc.wat b/crates/wit-component/tests/interfaces/multi-doc.wat index d755d8da0e..1280107929 100644 --- a/crates/wit-component/tests/interfaces/multi-doc.wat +++ b/crates/wit-component/tests/interfaces/multi-doc.wat @@ -7,7 +7,7 @@ (export (;1;) "the-type" (type (eq 0))) ) ) - (export (;0;) "b" (instance (type 0))) + (export (;0;) "b" "pkg:/b/b" (instance (type 0))) (alias export 0 "the-type" (type (;1;))) (type (;2;) (instance @@ -15,10 +15,10 @@ (export (;1;) "the-type" (type (eq 0))) ) ) - (export (;1;) "a" (instance (type 2))) + (export (;1;) "a" "pkg:/b/a" (instance (type 2))) ) ) - (export (;1;) "b" (type 0)) + (export (;1;) "b" "pkg:/b" (type 0)) (type (;2;) (component (type (;0;) @@ -43,7 +43,7 @@ (export (;1;) "the-type" (type (eq 0))) ) ) - (export (;0;) "b" (instance (type 4))) + (export (;0;) "b" "pkg:/a/b" (instance (type 4))) (alias export 2 "the-type" (type (;5;))) (type (;6;) (instance @@ -51,8 +51,8 @@ (export (;1;) "the-type" (type (eq 0))) ) ) - (export (;1;) "a" (instance (type 6))) + (export (;1;) "a" "pkg:/a/a" (instance (type 6))) ) ) - (export (;3;) "a" (type 2)) + (export (;3;) "a" "pkg:/a" (type 2)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/records.wat b/crates/wit-component/tests/interfaces/records.wat index 58baf8d2bc..2f98a0bdf9 100644 --- a/crates/wit-component/tests/interfaces/records.wat +++ b/crates/wit-component/tests/interfaces/records.wat @@ -40,7 +40,7 @@ (export (;10;) "typedef-inout" (func (type 23))) ) ) - (export (;0;) "records" (instance (type 0))) + (export (;0;) "records" "pkg:/records/records" (instance (type 0))) (type (;1;) (component (type (;0;) @@ -125,8 +125,8 @@ (export (;0;) "records" "pkg:/records/records" (instance (type 7))) ) ) - (export (;0;) "records-world" (component (type 1))) + (export (;0;) "records-world" "pkg:/records/records-world" (component (type 1))) ) ) - (export (;1;) "records" (type 0)) + (export (;1;) "records" "pkg:/records" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/reference-out-of-order.wat b/crates/wit-component/tests/interfaces/reference-out-of-order.wat index 1cec5b27b8..bab0d67963 100644 --- a/crates/wit-component/tests/interfaces/reference-out-of-order.wat +++ b/crates/wit-component/tests/interfaces/reference-out-of-order.wat @@ -21,7 +21,7 @@ (export (;3;) "d" (func (type 11))) ) ) - (export (;0;) "foo" (instance (type 0))) + (export (;0;) "foo" "pkg:/reference-out-of-order/foo" (instance (type 0))) (type (;1;) (component (type (;0;) @@ -47,8 +47,8 @@ (import "foo" "pkg:/reference-out-of-order/foo" (instance (type 0))) ) ) - (export (;0;) "foo-world" (component (type 1))) + (export (;0;) "foo-world" "pkg:/reference-out-of-order/foo-world" (component (type 1))) ) ) - (export (;1;) "reference-out-of-order" (type 0)) + (export (;1;) "reference-out-of-order" "pkg:/reference-out-of-order" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/simple-deps.wat b/crates/wit-component/tests/interfaces/simple-deps.wat index f0da7f575d..7d25c30d1d 100644 --- a/crates/wit-component/tests/interfaces/simple-deps.wat +++ b/crates/wit-component/tests/interfaces/simple-deps.wat @@ -15,8 +15,8 @@ (export (;1;) "some-type" (type (eq 0))) ) ) - (export (;0;) "foo" (instance (type 2))) + (export (;0;) "foo" "pkg:/foo/foo" (instance (type 2))) ) ) - (export (;1;) "foo" (type 0)) + (export (;1;) "foo" "pkg:/foo" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/simple-multi.wat b/crates/wit-component/tests/interfaces/simple-multi.wat index 5f00f6de11..4ea016cd98 100644 --- a/crates/wit-component/tests/interfaces/simple-multi.wat +++ b/crates/wit-component/tests/interfaces/simple-multi.wat @@ -4,17 +4,17 @@ (type (;0;) (instance) ) - (export (;0;) "bar" (instance (type 0))) + (export (;0;) "bar" "pkg:/bar/bar" (instance (type 0))) ) ) - (export (;1;) "bar" (type 0)) + (export (;1;) "bar" "pkg:/bar" (type 0)) (type (;2;) (component (type (;0;) (instance) ) - (export (;0;) "foo" (instance (type 0))) + (export (;0;) "foo" "pkg:/foo/foo" (instance (type 0))) ) ) - (export (;3;) "foo" (type 2)) + (export (;3;) "foo" "pkg:/foo" (type 2)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/simple-use.wat b/crates/wit-component/tests/interfaces/simple-use.wat index bba8f9df55..54c217edeb 100644 --- a/crates/wit-component/tests/interfaces/simple-use.wat +++ b/crates/wit-component/tests/interfaces/simple-use.wat @@ -7,7 +7,7 @@ (export (;1;) "level" (type (eq 0))) ) ) - (export (;0;) "types" (instance (type 0))) + (export (;0;) "types" "pkg:/simple-use/types" (instance (type 0))) (alias export 0 "level" (type (;1;))) (type (;2;) (instance @@ -17,8 +17,8 @@ (export (;0;) "log" (func (type 2))) ) ) - (export (;1;) "console" (instance (type 2))) + (export (;1;) "console" "pkg:/simple-use/console" (instance (type 2))) ) ) - (export (;1;) "simple-use" (type 0)) + (export (;1;) "simple-use" "pkg:/simple-use" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/simple-world.wat b/crates/wit-component/tests/interfaces/simple-world.wat index ee8ae0b2b7..3c408281a7 100644 --- a/crates/wit-component/tests/interfaces/simple-world.wat +++ b/crates/wit-component/tests/interfaces/simple-world.wat @@ -7,7 +7,7 @@ (export (;0;) "log" (func (type 0))) ) ) - (export (;0;) "console" (instance (type 0))) + (export (;0;) "console" "pkg:/simple-world/console" (instance (type 0))) (type (;1;) (component (type (;0;) @@ -19,8 +19,8 @@ (import "console" "pkg:/simple-world/console" (instance (type 0))) ) ) - (export (;0;) "the-world" (component (type 1))) + (export (;0;) "the-world" "pkg:/simple-world/the-world" (component (type 1))) ) ) - (export (;1;) "simple-world" (type 0)) + (export (;1;) "simple-world" "pkg:/simple-world" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/type-alias.wat b/crates/wit-component/tests/interfaces/type-alias.wat index 1ee3b44abf..d6ad881a26 100644 --- a/crates/wit-component/tests/interfaces/type-alias.wat +++ b/crates/wit-component/tests/interfaces/type-alias.wat @@ -10,7 +10,7 @@ (export (;0;) "f" (func (type 3))) ) ) - (export (;0;) "foo" (instance (type 0))) + (export (;0;) "foo" "pkg:/type-alias/foo" (instance (type 0))) (type (;1;) (component (type (;0;) @@ -36,8 +36,8 @@ (export (;0;) "foo" "pkg:/type-alias/foo" (instance (type 3))) ) ) - (export (;0;) "my-world" (component (type 1))) + (export (;0;) "my-world" "pkg:/type-alias/my-world" (component (type 1))) ) ) - (export (;1;) "type-alias" (type 0)) + (export (;1;) "type-alias" "pkg:/type-alias" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/type-alias2.wat b/crates/wit-component/tests/interfaces/type-alias2.wat index 29dd18cac8..acccc8e42c 100644 --- a/crates/wit-component/tests/interfaces/type-alias2.wat +++ b/crates/wit-component/tests/interfaces/type-alias2.wat @@ -10,7 +10,7 @@ (export (;0;) "f" (func (type 3))) ) ) - (export (;0;) "foo" (instance (type 0))) + (export (;0;) "foo" "pkg:/type-alias2/foo" (instance (type 0))) (type (;1;) (component (type (;0;) @@ -25,8 +25,8 @@ (export (;0;) "foo" "pkg:/type-alias2/foo" (instance (type 0))) ) ) - (export (;0;) "my-world" (component (type 1))) + (export (;0;) "my-world" "pkg:/type-alias2/my-world" (component (type 1))) ) ) - (export (;1;) "type-alias2" (type 0)) + (export (;1;) "type-alias2" "pkg:/type-alias2" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/variants.wat b/crates/wit-component/tests/interfaces/variants.wat index 49f343b2a7..bba5ea7757 100644 --- a/crates/wit-component/tests/interfaces/variants.wat +++ b/crates/wit-component/tests/interfaces/variants.wat @@ -95,7 +95,7 @@ (export (;19;) "expected-simple" (func (type 69))) ) ) - (export (;0;) "variants" (instance (type 0))) + (export (;0;) "variants" "pkg:/variants/variants" (instance (type 0))) (type (;1;) (component (type (;0;) @@ -195,8 +195,8 @@ (import "variants" "pkg:/variants/variants" (instance (type 0))) ) ) - (export (;0;) "variants-world" (component (type 1))) + (export (;0;) "variants-world" "pkg:/variants/variants-world" (component (type 1))) ) ) - (export (;1;) "variants" (type 0)) + (export (;1;) "variants" "pkg:/variants" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/wasi-http.wat b/crates/wit-component/tests/interfaces/wasi-http.wat index 949a77943b..3229974168 100644 --- a/crates/wit-component/tests/interfaces/wasi-http.wat +++ b/crates/wit-component/tests/interfaces/wasi-http.wat @@ -9,10 +9,10 @@ (export (;3;) "response" (type (eq 2))) ) ) - (export (;0;) "types" (instance (type 0))) + (export (;0;) "types" "pkg:/types/types" (instance (type 0))) ) ) - (export (;1;) "types" (type 0)) + (export (;1;) "types" "pkg:/types" (type 0)) (type (;2;) (component (type (;0;) @@ -36,10 +36,10 @@ (export (;0;) "handle" (func (type 4))) ) ) - (export (;0;) "handler" (instance (type 3))) + (export (;0;) "handler" "pkg:/handler/handler" (instance (type 3))) ) ) - (export (;3;) "handler" (type 2)) + (export (;3;) "handler" "pkg:/handler" (type 2)) (type (;4;) (component (type (;0;) @@ -88,8 +88,8 @@ (export (;0;) "handler" "pkg:/handler/handler" (instance (type 7))) ) ) - (export (;0;) "proxy" (component (type 0))) + (export (;0;) "proxy" "pkg:/proxy/proxy" (component (type 0))) ) ) - (export (;5;) "proxy" (type 4)) + (export (;5;) "proxy" "pkg:/proxy" (type 4)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/world-inline-interface.wat b/crates/wit-component/tests/interfaces/world-inline-interface.wat index ac40b9499a..782ecd441a 100644 --- a/crates/wit-component/tests/interfaces/world-inline-interface.wat +++ b/crates/wit-component/tests/interfaces/world-inline-interface.wat @@ -13,8 +13,8 @@ (export (;0;) "bar" (instance (type 1))) ) ) - (export (;0;) "has-inline" (component (type 0))) + (export (;0;) "has-inline" "pkg:/world-inline-interface/has-inline" (component (type 0))) ) ) - (export (;1;) "world-inline-interface" (type 0)) + (export (;1;) "world-inline-interface" "pkg:/world-inline-interface" (type 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/world-top-level.wat b/crates/wit-component/tests/interfaces/world-top-level.wat index 39d9790837..88cc8e3cfb 100644 --- a/crates/wit-component/tests/interfaces/world-top-level.wat +++ b/crates/wit-component/tests/interfaces/world-top-level.wat @@ -21,22 +21,22 @@ (export (;1;) "bar" (func (type 5))) ) ) - (export (;0;) "foo" (component (type 0))) + (export (;0;) "foo" "pkg:/world-top-level/foo" (component (type 0))) (type (;1;) (component (type (;0;) (func)) (import "foo" (func (type 0))) ) ) - (export (;1;) "just-import" (component (type 1))) + (export (;1;) "just-import" "pkg:/world-top-level/just-import" (component (type 1))) (type (;2;) (component (type (;0;) (func)) (export (;0;) "foo" (func (type 0))) ) ) - (export (;2;) "just-export" (component (type 2))) + (export (;2;) "just-export" "pkg:/world-top-level/just-export" (component (type 2))) ) ) - (export (;1;) "world-top-level" (type 0)) + (export (;1;) "world-top-level" "pkg:/world-top-level" (type 0)) ) \ No newline at end of file From 5016962d5e551cb4f2548121ab38c651db161d83 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 11 Jan 2023 07:46:01 -0800 Subject: [PATCH 17/37] Remove duplicate `url` field from `Interface` Try to reduce the amount of URLs floating around by only having one optional one on `Package` for now and see if we can get away with that. It may not be possible to get away with this but it would sort of be nice if we could! --- crates/wit-component/src/decoding.rs | 2 -- crates/wit-component/src/encoding.rs | 14 ++++++++------ crates/wit-component/src/encoding/world.rs | 4 ++-- crates/wit-parser/Cargo.toml | 1 + crates/wit-parser/src/ast/resolve.rs | 3 --- crates/wit-parser/src/lib.rs | 5 ----- crates/wit-parser/src/resolve.rs | 21 +++++++++++++++++++++ 7 files changed, 32 insertions(+), 18 deletions(-) diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index 3e92cf7227..72be7f83a1 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -439,7 +439,6 @@ impl WitPackageDecoder<'_> { .or_insert_with(|| { self.resolve.interfaces.alloc(Interface { name: Some(interface.to_string()), - url: None, docs: Default::default(), types: IndexMap::default(), functions: IndexMap::new(), @@ -457,7 +456,6 @@ impl WitPackageDecoder<'_> { ) -> Result { let mut interface = Interface { name: name.map(|n| n.to_string()), - url: None, docs: Default::default(), types: IndexMap::default(), functions: IndexMap::new(), diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index bb415d7f18..3470bbc425 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -381,7 +381,8 @@ impl<'a> EncodingState<'a> { Some(name) => name, None => unimplemented!("top-level imports"), }; - let (interface_id, url) = info.interface.unwrap(); + let (interface_id, url) = info.interface.as_ref().unwrap(); + let interface_id = *interface_id; let interface = &resolve.interfaces[interface_id]; log::trace!("encoding imports for `{name}` as {:?}", interface_id); let ty = { @@ -515,8 +516,8 @@ impl<'a> EncodingState<'a> { metadata: &ModuleMetadata, ) -> u32 { let import = &self.info.import_map[&Some(name)]; - let (interface, _url) = import.interface.unwrap(); - let instance_index = self.imported_instances[&interface]; + let (interface, _url) = import.interface.as_ref().unwrap(); + let instance_index = self.imported_instances[interface]; let mut exports = Vec::with_capacity(import.direct.len() + import.indirect.len()); // Add an entry for all indirect lowerings which come as an export of @@ -602,9 +603,10 @@ impl<'a> EncodingState<'a> { } let instance_index = self.component.instantiate_exports(interface_exports); + let url = resolve.url_of(*export).unwrap_or(String::new()); self.component.export( export_name, - resolve.interfaces[*export].url.as_deref().unwrap_or(""), + &url, ComponentExportKind::Instance, instance_index, ); @@ -869,8 +871,8 @@ impl<'a> EncodingState<'a> { encoding, } => { let interface = &self.info.import_map[&Some(*interface)]; - let (interface_id, _url) = interface.interface.unwrap(); - let instance_index = self.imported_instances[&interface_id]; + let (interface_id, _url) = interface.interface.as_ref().unwrap(); + let instance_index = self.imported_instances[interface_id]; let func_index = self .component .alias_func(instance_index, interface.indirect[*indirect_index].name); diff --git a/crates/wit-component/src/encoding/world.rs b/crates/wit-component/src/encoding/world.rs index 03093df383..9123ea4660 100644 --- a/crates/wit-component/src/encoding/world.rs +++ b/crates/wit-component/src/encoding/world.rs @@ -39,7 +39,7 @@ pub struct ImportedInterface<'a> { /// Required functions on the interface, or the filter on the functions list /// in `interface`. pub required: HashSet<&'a str>, - pub interface: Option<(InterfaceId, &'a str)>, + pub interface: Option<(InterfaceId, String)>, } #[derive(Debug)] @@ -204,7 +204,7 @@ impl<'a> ComponentWorld<'a> { } WorldItem::Interface(id) => { let required = required.get(name).unwrap_or(&empty); - let url = resolve.interfaces[*id].url.as_deref().unwrap_or(""); + let url = resolve.url_of(*id).unwrap_or(String::new()); let interface = import_map .entry(Some(name)) diff --git a/crates/wit-parser/Cargo.toml b/crates/wit-parser/Cargo.toml index 550d1dddd2..6bde1ff2fc 100644 --- a/crates/wit-parser/Cargo.toml +++ b/crates/wit-parser/Cargo.toml @@ -19,6 +19,7 @@ indexmap = { workspace = true } pulldown-cmark = { version = "0.8", default-features = false } unicode-xid = "0.2.2" log = { workspace = true } +url = { workspace = true } [dev-dependencies] rayon = "1" diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 660aaab3e5..4cc835b362 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -184,7 +184,6 @@ impl<'a> Resolver<'a> { self.interface_spans.push(iface.span); let id = self.interfaces.alloc(Interface { name: Some(iface.name.to_string()), - url: None, types: IndexMap::new(), docs: Docs::default(), document: doc, @@ -204,7 +203,6 @@ impl<'a> Resolver<'a> { self.interface_spans.push(doc_span); self.interfaces.alloc(Interface { name: None, - url: None, types: IndexMap::new(), docs: Docs::default(), document: doc, @@ -495,7 +493,6 @@ impl<'a> Resolver<'a> { docs, document, name: name.map(|s| s.to_string()), - url: None, functions: IndexMap::new(), types: IndexMap::new(), }); diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index f57cf9777c..da3ab40671 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -275,11 +275,6 @@ pub struct Interface { /// This is `None` for inline interfaces in worlds. pub name: Option, - /// Optionally listed URL for an interface. - /// - /// NB: this isn't super well managed at this point. - pub url: Option, - /// Documentation associated with this interface. pub docs: Docs, diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 9c44538f48..139a7fa180 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -10,6 +10,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt::Write; use std::mem; use std::path::{Path, PathBuf}; +use url::Url; /// Representation of a fully resolved set of WIT packages. /// @@ -348,6 +349,26 @@ impl Resolve { Ok(()) } + + /// Returns the URL of the specified `interface`, if available. + /// + /// This currently creates a URL based on the URL of the package that + /// `interface` resides in. If the package owner of `interface` does not + /// specify a URL then `None` will be returned. + /// + /// If the `interface` specified does not have a name then `None` will be + /// returned as well. + pub fn url_of(&self, interface: InterfaceId) -> Option { + let interface = &self.interfaces[interface]; + let doc = &self.documents[interface.document]; + let package = &self.packages[doc.package.unwrap()]; + let mut base = Url::parse(package.url.as_ref()?).unwrap(); + base.path_segments_mut() + .unwrap() + .push(&doc.name) + .push(interface.name.as_ref()?); + Some(base.to_string()) + } } /// Structure returned by [`Resolve::merge`] which contains mappings from From 76d96a926eadbe4c9a069f8a8f8c3380e0a45156 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 11 Jan 2023 08:25:39 -0800 Subject: [PATCH 18/37] Make `wat` an optional dep of `wit-component` It's a relatively large dependency relative to `wit-component`, so don't have it on-by-default. --- Cargo.toml | 2 +- crates/wit-component/Cargo.toml | 6 +++++- crates/wit-component/src/lib.rs | 7 +++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2699dffb5b..8b0b291da4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,7 @@ rustc-demangle = { version = "0.1.21", optional = true } cpp_demangle = { version = "0.4.0", optional = true } # Dependencies of `component` -wit-component = { workspace = true, optional = true } +wit-component = { workspace = true, optional = true, features = ['dummy-module'] } wit-parser = { workspace = true, optional = true } wast = { workspace = true, optional = true } diff --git a/crates/wit-component/Cargo.toml b/crates/wit-component/Cargo.toml index 43c5caa345..6e8994bf09 100644 --- a/crates/wit-component/Cargo.toml +++ b/crates/wit-component/Cargo.toml @@ -21,10 +21,14 @@ log = "0.4.17" bitflags = "1.3.2" indexmap = { workspace = true } url = { workspace = true } -wat = { workspace = true } +wat = { workspace = true, optional = true } [dev-dependencies] wasmprinter = { workspace = true } glob = "0.3.0" pretty_assertions = "1.3.0" env_logger = { workspace = true } +wat = { workspace = true } + +[features] +dummy-module = ['dep:wat'] diff --git a/crates/wit-component/src/lib.rs b/crates/wit-component/src/lib.rs index 41e7c70d50..466d15db28 100644 --- a/crates/wit-component/src/lib.rs +++ b/crates/wit-component/src/lib.rs @@ -9,7 +9,6 @@ use wasm_encoder::CanonicalOption; mod builder; mod decoding; -mod dummy; mod encoding; mod gc; mod printing; @@ -19,9 +18,13 @@ pub use decoding::{decode, DecodedWasm}; pub use encoding::{encode, ComponentEncoder}; pub use printing::*; -pub use dummy::dummy_module; pub mod metadata; +#[cfg(feature = "dummy-module")] +pub use dummy::dummy_module; +#[cfg(feature = "dummy-module")] +mod dummy; + /// Supported string encoding formats. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum StringEncoding { From 9f171e74204ff0a3b0e5aa2fdb01b123f0514bf7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 11 Jan 2023 09:27:11 -0800 Subject: [PATCH 19/37] Implement inferring WIT from a component This implements the logic necessary to extract a WIT document from a compiled component, allowing hopefully easy inspection and debugging of what the types of imports and exports are. --- crates/wit-component/src/decoding.rs | 108 +++++++++++++++++- crates/wit-component/src/encoding.rs | 9 +- crates/wit-component/src/printing.rs | 6 + crates/wit-component/tests/components.rs | 71 +++++------- .../adapt-empty-interface/component.wit | 2 + .../adapt-export-default/component.wit | 3 + .../adapt-export-namespaced/component.wit | 7 ++ .../adapt-export-reallocs/component.wit | 8 ++ .../adapt-export-save-args/component.wit | 3 + .../adapt-inject-stack/component.wit | 7 ++ .../adapt-list-return/component.wit | 7 ++ .../adapt-memory-simple/component.wit | 7 ++ .../components/adapt-multiple/component.wit | 12 ++ .../components/adapt-preview1/component.wit | 12 ++ .../components/adapt-unused/component.wit | 2 + .../tests/components/empty/component.wit | 2 + .../ensure-default-type-exports/component.wit | 14 +++ .../tests/components/exports/component.wat | 4 +- .../tests/components/exports/component.wit | 31 +++++ .../components/import-conflict/component.wit | 17 +++ .../import-empty-interface/component.wit | 2 + .../components/import-export/component.wit | 15 +++ .../tests/components/imports/component.wit | 33 ++++++ .../components/lift-options/component.wat | 8 +- .../components/lift-options/component.wit | 53 +++++++++ .../components/lower-options/component.wit | 53 +++++++++ .../no-realloc-required/component.wit | 7 ++ .../components/post-return/component.wit | 3 + .../rename-import-interface/component.wit | 7 ++ .../tests/components/simple/component.wit | 5 + .../components/unused-import/component.wit | 7 ++ 31 files changed, 470 insertions(+), 55 deletions(-) create mode 100644 crates/wit-component/tests/components/adapt-empty-interface/component.wit create mode 100644 crates/wit-component/tests/components/adapt-export-default/component.wit create mode 100644 crates/wit-component/tests/components/adapt-export-namespaced/component.wit create mode 100644 crates/wit-component/tests/components/adapt-export-reallocs/component.wit create mode 100644 crates/wit-component/tests/components/adapt-export-save-args/component.wit create mode 100644 crates/wit-component/tests/components/adapt-inject-stack/component.wit create mode 100644 crates/wit-component/tests/components/adapt-list-return/component.wit create mode 100644 crates/wit-component/tests/components/adapt-memory-simple/component.wit create mode 100644 crates/wit-component/tests/components/adapt-multiple/component.wit create mode 100644 crates/wit-component/tests/components/adapt-preview1/component.wit create mode 100644 crates/wit-component/tests/components/adapt-unused/component.wit create mode 100644 crates/wit-component/tests/components/empty/component.wit create mode 100644 crates/wit-component/tests/components/ensure-default-type-exports/component.wit create mode 100644 crates/wit-component/tests/components/exports/component.wit create mode 100644 crates/wit-component/tests/components/import-conflict/component.wit create mode 100644 crates/wit-component/tests/components/import-empty-interface/component.wit create mode 100644 crates/wit-component/tests/components/import-export/component.wit create mode 100644 crates/wit-component/tests/components/imports/component.wit create mode 100644 crates/wit-component/tests/components/lift-options/component.wit create mode 100644 crates/wit-component/tests/components/lower-options/component.wit create mode 100644 crates/wit-component/tests/components/no-realloc-required/component.wit create mode 100644 crates/wit-component/tests/components/post-return/component.wit create mode 100644 crates/wit-component/tests/components/rename-import-interface/component.wit create mode 100644 crates/wit-component/tests/components/simple/component.wit create mode 100644 crates/wit-component/tests/components/unused-import/component.wit diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index 72be7f83a1..14d49a2eea 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -4,8 +4,8 @@ use std::collections::HashMap; use std::hash::{Hash, Hasher}; use url::Url; use wasmparser::{ - types, ComponentExport, ComponentExternalKind, ComponentImport, Parser, Payload, - PrimitiveValType, ValidPayload, Validator, WasmFeatures, + types, ComponentExport, ComponentExternalKind, ComponentImport, ComponentTypeRef, Parser, + Payload, PrimitiveValType, ValidPayload, Validator, WasmFeatures, }; use wit_parser::*; @@ -72,8 +72,8 @@ impl<'a> ComponentInfo<'a> { } fn is_wit_package(&self) -> bool { - // wit packages only export component types - if !self.imports.is_empty() { + // wit packages only export component types and must export at least one + if !self.imports.is_empty() || self.exports.is_empty() { return false; } @@ -116,6 +116,103 @@ impl<'a> ComponentInfo<'a> { } Ok((decoder.resolve, package)) } + + fn decode_component(&self, name: &str) -> Result<(Resolve, WorldId)> { + assert!(!self.is_wit_package()); + let mut resolve = Resolve::default(); + let package = resolve.packages.alloc(Package { + name: name.to_string(), + documents: Default::default(), + url: None, + }); + let doc = resolve.documents.alloc(Document { + name: "root".to_string(), + interfaces: Default::default(), + worlds: Default::default(), + default_interface: None, + default_world: None, + package: Some(package), + }); + let world = resolve.worlds.alloc(World { + name: name.to_string(), + docs: Default::default(), + imports: Default::default(), + exports: Default::default(), + document: doc, + }); + resolve.documents[doc] + .worlds + .insert(name.to_string(), world); + resolve.documents[doc].default_world = Some(world); + let mut decoder = WitPackageDecoder { + resolve, + package, + info: self, + url_to_package: HashMap::default(), + type_map: HashMap::new(), + type_src_map: HashMap::new(), + url_to_interface: HashMap::new(), + }; + + for (name, import) in self.imports.iter() { + let item = match import.ty { + ComponentTypeRef::Instance(i) => { + let ty = match self.types.type_at(i, false) { + Some(types::Type::ComponentInstance(ty)) => ty, + _ => unreachable!(), + }; + let id = decoder + .register_interface(doc, Some(name), ty) + .with_context(|| format!("failed to decode WIT from import `{name}`"))?; + decoder.resolve.documents[doc] + .interfaces + .insert(name.to_string(), id); + WorldItem::Interface(id) + } + ComponentTypeRef::Func(i) => { + let ty = match self.types.type_at(i, false) { + Some(types::Type::ComponentFunc(ty)) => ty, + _ => unreachable!(), + }; + let func = decoder.convert_function(name, ty).with_context(|| { + format!("failed to decode function from import `{name}`") + })?; + WorldItem::Function(func) + } + _ => bail!("component import `{name}` was neither a function nor instance"), + }; + decoder.resolve.worlds[world] + .imports + .insert(name.to_string(), item); + } + for (name, export) in self.exports.iter() { + let item = match export.kind { + ComponentExternalKind::Func => { + let ty = self.types.component_function_at(export.index).unwrap(); + let func = decoder.convert_function(name, ty).with_context(|| { + format!("failed to decode function from export `{name}`") + })?; + + WorldItem::Function(func) + } + ComponentExternalKind::Instance => { + let ty = self.types.component_instance_at(export.index).unwrap(); + let id = decoder + .register_interface(doc, Some(name), ty) + .with_context(|| format!("failed to decode WIT from export `{name}`"))?; + decoder.resolve.documents[doc] + .interfaces + .insert(name.to_string(), id); + WorldItem::Interface(id) + } + _ => bail!("component export `{name}` was neither a function nor instance"), + }; + decoder.resolve.worlds[world] + .exports + .insert(name.to_string(), item); + } + Ok((decoder.resolve, world)) + } } /// Result of the [`decode`] function. @@ -166,7 +263,8 @@ pub fn decode(name: &str, bytes: &[u8]) -> Result { let (resolve, pkg) = info.decode_wit_package(name)?; Ok(DecodedWasm::WitPackage(resolve, pkg)) } else { - unimplemented!() + let (resolve, world) = info.decode_component(name)?; + Ok(DecodedWasm::Component(resolve, world)) } } diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 3470bbc425..1e4ec83f2f 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -590,9 +590,12 @@ impl<'a> EncodingState<'a> { )); } - // Extend the interface exports to be created with type exports - // found during encoding of function types. - interface_exports.extend( + // Place type imports first for now. The lifted function + // types in theory should reference the indices of these + // exports but that's not possible in the binary encoding + // right now. + interface_exports.splice( + 0..0, enc.type_exports .into_iter() .map(|(idx, name)| (name, ComponentExportKind::Type, idx)), diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 833814df1b..e477509416 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -15,12 +15,18 @@ impl DocumentPrinter { pub fn print(&mut self, resolve: &Resolve, docid: DocumentId) -> Result { let doc = &resolve.documents[docid]; for (name, id) in doc.interfaces.iter() { + if Some(*id) == doc.default_interface { + self.output.push_str("default "); + } writeln!(&mut self.output, "interface {name} {{")?; self.print_interface(resolve, *id)?; writeln!(&mut self.output, "}}\n")?; } for (name, id) in doc.worlds.iter() { + if Some(*id) == doc.default_world { + self.output.push_str("default "); + } let world = &resolve.worlds[*id]; writeln!(&mut self.output, "world {name} {{")?; for (name, import) in world.imports.iter() { diff --git a/crates/wit-component/tests/components.rs b/crates/wit-component/tests/components.rs index f595d2e102..98e2b7b7a0 100644 --- a/crates/wit-component/tests/components.rs +++ b/crates/wit-component/tests/components.rs @@ -1,8 +1,8 @@ -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{anyhow, Result}; use pretty_assertions::assert_eq; use std::{fs, path::Path}; use wasm_encoder::{Encode, Section}; -use wit_component::{ComponentEncoder, StringEncoding}; +use wit_component::{ComponentEncoder, DecodedWasm, DocumentPrinter, StringEncoding}; use wit_parser::{Resolve, UnresolvedPackage}; /// Tests the encoding of components. @@ -24,6 +24,8 @@ use wit_parser::{Resolve, UnresolvedPackage}; /// /// * `component.wat` - the expected encoded component in text format if the /// encoding is expected to succeed. +/// * `component.wit` - if `component.wat` exists this is the inferred interface +/// of the component. /// * `error.txt` - the expected error message if the encoding is expected to /// fail. /// @@ -47,12 +49,28 @@ fn component_encoding_via_flags() -> Result<()> { println!("testing {test_case}"); let module_path = path.join("module.wat"); - let component_path = path.join("component.wat"); - let error_path = path.join("error.txt"); let module = read_core_module(&module_path)?; let mut encoder = ComponentEncoder::default().module(&module)?.validate(true); encoder = add_adapters(encoder, &path)?; - assert_output(test_case, &encoder, &component_path, &error_path)?; + let component_path = path.join("component.wat"); + let component_wit_path = path.join("component.wit"); + let error_path = path.join("error.txt"); + + let bytes = match encoder.encode() { + Ok(bytes) => bytes, + Err(err) => { + assert_output(&format!("{err:?}"), &error_path)?; + continue; + } + }; + let wat = wasmprinter::print_bytes(&bytes)?; + assert_output(&wat, &component_path)?; + let (doc, resolve) = match wit_component::decode("component", &bytes)? { + DecodedWasm::WitPackage(..) => unreachable!(), + DecodedWasm::Component(resolve, world) => (resolve.worlds[world].document, resolve), + }; + let wit = DocumentPrinter::default().print(&resolve, doc)?; + assert_output(&wit, &component_wit_path)?; } Ok(()) @@ -97,45 +115,16 @@ fn read_core_module(path: &Path) -> Result> { Ok(wasm) } -fn assert_output( - test_case: &str, - encoder: &ComponentEncoder, - component_path: &Path, - error_path: &Path, -) -> Result<()> { - let r = encoder.encode(); - - let (output, baseline_path) = if error_path.is_file() { - match r { - Ok(_) => bail!("encoding should fail for test case `{}`", test_case), - Err(e) => (format!("{e:?}"), &error_path), - } - } else { - ( - wasmprinter::print_bytes( - &r.with_context(|| format!("failed to encode for test case `{}`", test_case))?, - ) - .with_context(|| { - format!( - "failed to print component bytes for test case `{}`", - test_case - ) - })?, - &component_path, - ) - }; - +fn assert_output(contents: &str, path: &Path) -> Result<()> { + let contents = contents.replace("\r\n", "\n"); if std::env::var_os("BLESS").is_some() { - fs::write(&baseline_path, output)?; + fs::write(path, contents)?; } else { assert_eq!( - fs::read_to_string(&baseline_path)? - .replace("\r\n", "\n") - .trim(), - output.trim(), - "failed baseline comparison for test case `{}` ({})", - test_case, - baseline_path.display(), + fs::read_to_string(path)?.replace("\r\n", "\n").trim(), + contents.trim(), + "failed baseline comparison ({})", + path.display(), ); } Ok(()) diff --git a/crates/wit-component/tests/components/adapt-empty-interface/component.wit b/crates/wit-component/tests/components/adapt-empty-interface/component.wit new file mode 100644 index 0000000000..da09ea6eeb --- /dev/null +++ b/crates/wit-component/tests/components/adapt-empty-interface/component.wit @@ -0,0 +1,2 @@ +default world component { +} diff --git a/crates/wit-component/tests/components/adapt-export-default/component.wit b/crates/wit-component/tests/components/adapt-export-default/component.wit new file mode 100644 index 0000000000..9368d1d606 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-default/component.wit @@ -0,0 +1,3 @@ +default world component { + export entrypoint: func() +} diff --git a/crates/wit-component/tests/components/adapt-export-namespaced/component.wit b/crates/wit-component/tests/components/adapt-export-namespaced/component.wit new file mode 100644 index 0000000000..90160cd85d --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-namespaced/component.wit @@ -0,0 +1,7 @@ +interface new { + entrypoint: func() +} + +default world component { + export new: self.new +} diff --git a/crates/wit-component/tests/components/adapt-export-reallocs/component.wit b/crates/wit-component/tests/components/adapt-export-reallocs/component.wit new file mode 100644 index 0000000000..49897fff21 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-reallocs/component.wit @@ -0,0 +1,8 @@ +interface new { + read: func(amt: u32) -> list +} + +default world component { + import new: self.new + export entrypoint: func(args: list) +} diff --git a/crates/wit-component/tests/components/adapt-export-save-args/component.wit b/crates/wit-component/tests/components/adapt-export-save-args/component.wit new file mode 100644 index 0000000000..e5a72119ea --- /dev/null +++ b/crates/wit-component/tests/components/adapt-export-save-args/component.wit @@ -0,0 +1,3 @@ +default world component { + export entrypoint: func(nargs: u32) +} diff --git a/crates/wit-component/tests/components/adapt-inject-stack/component.wit b/crates/wit-component/tests/components/adapt-inject-stack/component.wit new file mode 100644 index 0000000000..d570b2929b --- /dev/null +++ b/crates/wit-component/tests/components/adapt-inject-stack/component.wit @@ -0,0 +1,7 @@ +interface new { + get-two: func() -> (a: u32, b: u32) +} + +default world component { + import new: self.new +} diff --git a/crates/wit-component/tests/components/adapt-list-return/component.wit b/crates/wit-component/tests/components/adapt-list-return/component.wit new file mode 100644 index 0000000000..cdcc76911c --- /dev/null +++ b/crates/wit-component/tests/components/adapt-list-return/component.wit @@ -0,0 +1,7 @@ +interface new { + read: func() -> list +} + +default world component { + import new: self.new +} diff --git a/crates/wit-component/tests/components/adapt-memory-simple/component.wit b/crates/wit-component/tests/components/adapt-memory-simple/component.wit new file mode 100644 index 0000000000..dc408de3cb --- /dev/null +++ b/crates/wit-component/tests/components/adapt-memory-simple/component.wit @@ -0,0 +1,7 @@ +interface new { + log: func(s: string) +} + +default world component { + import new: self.new +} diff --git a/crates/wit-component/tests/components/adapt-multiple/component.wit b/crates/wit-component/tests/components/adapt-multiple/component.wit new file mode 100644 index 0000000000..5b111ba21f --- /dev/null +++ b/crates/wit-component/tests/components/adapt-multiple/component.wit @@ -0,0 +1,12 @@ +interface other1 { + foo: func() +} + +interface other2 { + bar: func() +} + +default world component { + import other1: self.other1 + import other2: self.other2 +} diff --git a/crates/wit-component/tests/components/adapt-preview1/component.wit b/crates/wit-component/tests/components/adapt-preview1/component.wit new file mode 100644 index 0000000000..25d767a1d2 --- /dev/null +++ b/crates/wit-component/tests/components/adapt-preview1/component.wit @@ -0,0 +1,12 @@ +interface foo { + foo: func() +} + +interface my-wasi { + proc-exit: func(code: u32) +} + +default world component { + import foo: self.foo + import my-wasi: self.my-wasi +} diff --git a/crates/wit-component/tests/components/adapt-unused/component.wit b/crates/wit-component/tests/components/adapt-unused/component.wit new file mode 100644 index 0000000000..da09ea6eeb --- /dev/null +++ b/crates/wit-component/tests/components/adapt-unused/component.wit @@ -0,0 +1,2 @@ +default world component { +} diff --git a/crates/wit-component/tests/components/empty/component.wit b/crates/wit-component/tests/components/empty/component.wit new file mode 100644 index 0000000000..da09ea6eeb --- /dev/null +++ b/crates/wit-component/tests/components/empty/component.wit @@ -0,0 +1,2 @@ +default world component { +} diff --git a/crates/wit-component/tests/components/ensure-default-type-exports/component.wit b/crates/wit-component/tests/components/ensure-default-type-exports/component.wit new file mode 100644 index 0000000000..a7b39a50c9 --- /dev/null +++ b/crates/wit-component/tests/components/ensure-default-type-exports/component.wit @@ -0,0 +1,14 @@ +interface foo { + type foo = u8 + + record bar { + x: foo, + } + + a: func(b: bar) +} + +default world component { + import foo: self.foo + export a: func(b: u8) +} diff --git a/crates/wit-component/tests/components/exports/component.wat b/crates/wit-component/tests/components/exports/component.wat index d3b0031700..7a2283ac25 100644 --- a/crates/wit-component/tests/components/exports/component.wat +++ b/crates/wit-component/tests/components/exports/component.wat @@ -62,8 +62,8 @@ (alias core export 0 "bar#a" (core func (;1;))) (func (;0;) (type 1) (canon lift (core func 1))) (instance (;0;) - (export "a" (func 0)) (export "x" (type 0)) + (export "a" (func 0)) ) (export (;1;) "bar" (instance 0)) (type (;2;) (func)) @@ -79,10 +79,10 @@ (alias core export 0 "cabi_post_foo#c" (core func (;6;))) (func (;3;) (type 5) (canon lift (core func 5) (memory 0) (realloc 0) string-encoding=utf8 (post-return 6))) (instance (;2;) + (export "x" (type 3)) (export "a" (func 1)) (export "b" (func 2)) (export "c" (func 3)) - (export "x" (type 3)) ) (export (;3;) "foo" (instance 2)) (alias core export 0 "a" (core func (;7;))) diff --git a/crates/wit-component/tests/components/exports/component.wit b/crates/wit-component/tests/components/exports/component.wit new file mode 100644 index 0000000000..1e3cf130cb --- /dev/null +++ b/crates/wit-component/tests/components/exports/component.wit @@ -0,0 +1,31 @@ +interface bar { + flags x { + a, + b, + c, + } + + a: func(x: x) +} + +interface foo { + variant x { + a, + b(string), + c(s64), + } + + a: func() + + b: func(x: string) -> x + + c: func(x: x) -> string +} + +default world component { + export bar: self.bar + export foo: self.foo + export a: func() + export b: func(a: s8, b: s16, c: s32, d: s64) -> string + export c: func() -> tuple +} diff --git a/crates/wit-component/tests/components/import-conflict/component.wit b/crates/wit-component/tests/components/import-conflict/component.wit new file mode 100644 index 0000000000..d2726dc7e5 --- /dev/null +++ b/crates/wit-component/tests/components/import-conflict/component.wit @@ -0,0 +1,17 @@ +interface bar { + a: func(x: u64, y: string) +} + +interface baz { + baz: func(x: list) -> list +} + +interface foo { + a: func() +} + +default world component { + import bar: self.bar + import baz: self.baz + import foo: self.foo +} diff --git a/crates/wit-component/tests/components/import-empty-interface/component.wit b/crates/wit-component/tests/components/import-empty-interface/component.wit new file mode 100644 index 0000000000..da09ea6eeb --- /dev/null +++ b/crates/wit-component/tests/components/import-empty-interface/component.wit @@ -0,0 +1,2 @@ +default world component { +} diff --git a/crates/wit-component/tests/components/import-export/component.wit b/crates/wit-component/tests/components/import-export/component.wit new file mode 100644 index 0000000000..04294f3da4 --- /dev/null +++ b/crates/wit-component/tests/components/import-export/component.wit @@ -0,0 +1,15 @@ +interface foo { + a: func() -> string +} + +interface bar { + a: func() + + b: func() -> string +} + +default world component { + import foo: self.foo + export bar: self.bar + export a: func(x: string) -> tuple +} diff --git a/crates/wit-component/tests/components/imports/component.wit b/crates/wit-component/tests/components/imports/component.wit new file mode 100644 index 0000000000..cbb6b2de1c --- /dev/null +++ b/crates/wit-component/tests/components/imports/component.wit @@ -0,0 +1,33 @@ +interface bar { + record x { + a: u8, + } + + bar1: func(x: string) + + bar2: func(x: x) +} + +interface baz { + type x = s8 + + baz1: func(x: list) + + baz2: func() + + baz3: func(x: x) +} + +interface foo { + foo1: func() + + foo2: func(x: u8) + + foo3: func(x: float32) +} + +default world component { + import bar: self.bar + import baz: self.baz + import foo: self.foo +} diff --git a/crates/wit-component/tests/components/lift-options/component.wat b/crates/wit-component/tests/components/lift-options/component.wat index d8732fe55d..37404753f8 100644 --- a/crates/wit-component/tests/components/lift-options/component.wat +++ b/crates/wit-component/tests/components/lift-options/component.wat @@ -159,6 +159,10 @@ (alias core export 0 "cabi_post_foo#p" (core func (;20;))) (func (;15;) (type 25) (canon lift (core func 19) (memory 0) (post-return 20))) (instance (;0;) + (export "r" (type 3)) + (export "v" (type 5)) + (export "r-no-string" (type 7)) + (export "v-no-string" (type 9)) (export "a" (func 0)) (export "b" (func 1)) (export "c" (func 2)) @@ -175,10 +179,6 @@ (export "n" (func 13)) (export "o" (func 14)) (export "p" (func 15)) - (export "r" (type 3)) - (export "v" (type 5)) - (export "r-no-string" (type 7)) - (export "v-no-string" (type 9)) ) (export (;1;) "foo" (instance 0)) ) \ No newline at end of file diff --git a/crates/wit-component/tests/components/lift-options/component.wit b/crates/wit-component/tests/components/lift-options/component.wit new file mode 100644 index 0000000000..ef941fcf95 --- /dev/null +++ b/crates/wit-component/tests/components/lift-options/component.wit @@ -0,0 +1,53 @@ +interface foo { + record r { + s: string, + } + + variant v { + s(string), + } + + record r-no-string { + s: u32, + } + + variant v-no-string { + s(u32), + } + + a: func() + + b: func(x: list) + + c: func(x: r) + + d: func(x: v) + + e: func(x: r-no-string) + + f: func(x: v-no-string) + + g: func(x: list) + + h: func(x: list) + + i: func(x: list) + + j: func(x: u32) + + k: func() -> tuple + + l: func() -> string + + m: func() -> list + + n: func() -> u32 + + o: func() -> v + + p: func() -> list +} + +default world component { + export foo: self.foo +} diff --git a/crates/wit-component/tests/components/lower-options/component.wit b/crates/wit-component/tests/components/lower-options/component.wit new file mode 100644 index 0000000000..b16a03105d --- /dev/null +++ b/crates/wit-component/tests/components/lower-options/component.wit @@ -0,0 +1,53 @@ +interface foo { + record r { + s: string, + } + + variant v { + s(string), + } + + record r-no-string { + s: u32, + } + + variant v-no-string { + s(u32), + } + + a: func() + + b: func(x: list) + + c: func(x: r) + + d: func(x: v) + + e: func(x: r-no-string) + + f: func(x: v-no-string) + + g: func(x: list) + + h: func(x: list) + + i: func(x: list) + + j: func(x: u32) + + k: func() -> tuple + + l: func() -> string + + m: func() -> list + + n: func() -> u32 + + o: func() -> v + + p: func() -> list +} + +default world component { + import foo: self.foo +} diff --git a/crates/wit-component/tests/components/no-realloc-required/component.wit b/crates/wit-component/tests/components/no-realloc-required/component.wit new file mode 100644 index 0000000000..eddb9f98d8 --- /dev/null +++ b/crates/wit-component/tests/components/no-realloc-required/component.wit @@ -0,0 +1,7 @@ +interface foo { + log: func(s: string) +} + +default world component { + import foo: self.foo +} diff --git a/crates/wit-component/tests/components/post-return/component.wit b/crates/wit-component/tests/components/post-return/component.wit new file mode 100644 index 0000000000..5fb868d11a --- /dev/null +++ b/crates/wit-component/tests/components/post-return/component.wit @@ -0,0 +1,3 @@ +default world component { + export a: func() -> string +} diff --git a/crates/wit-component/tests/components/rename-import-interface/component.wit b/crates/wit-component/tests/components/rename-import-interface/component.wit new file mode 100644 index 0000000000..d2808cbc8e --- /dev/null +++ b/crates/wit-component/tests/components/rename-import-interface/component.wit @@ -0,0 +1,7 @@ +interface bar { + the-func: func() +} + +default world component { + import bar: self.bar +} diff --git a/crates/wit-component/tests/components/simple/component.wit b/crates/wit-component/tests/components/simple/component.wit new file mode 100644 index 0000000000..be8388ec0c --- /dev/null +++ b/crates/wit-component/tests/components/simple/component.wit @@ -0,0 +1,5 @@ +default world component { + export a: func() + export b: func() -> string + export c: func(x: string) -> string +} diff --git a/crates/wit-component/tests/components/unused-import/component.wit b/crates/wit-component/tests/components/unused-import/component.wit new file mode 100644 index 0000000000..6ad8ced63d --- /dev/null +++ b/crates/wit-component/tests/components/unused-import/component.wit @@ -0,0 +1,7 @@ +interface foo { + name: func(x: bool) +} + +default world component { + import foo: self.foo +} From d872d3d1bbab5e6eb8c9b9dcbabd2e3a25c498dd Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 12 Jan 2023 08:12:33 -0800 Subject: [PATCH 20/37] Make `TypeOwner` a `Copy` type --- crates/wit-parser/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index da3ab40671..9b3e7f9240 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -322,7 +322,7 @@ pub enum TypeDefKind { Unknown, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum TypeOwner { /// This type was defined within a `world` block. World(WorldId), From ae1d7c9913cfdc8ac7587cd4888ebd208dddcd7d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 12 Jan 2023 09:23:36 -0800 Subject: [PATCH 21/37] Add APIs to learn about files parsed This'll get used by the Rust procedural macro to know what files to include to trigger reruns of the macro. --- crates/wit-component/tests/interfaces.rs | 2 +- crates/wit-parser/src/ast.rs | 4 ++++ crates/wit-parser/src/lib.rs | 6 ++++++ crates/wit-parser/src/resolve.rs | 10 ++++++++-- crates/wit-parser/tests/all.rs | 2 +- src/bin/wasm-tools/component.rs | 2 +- 6 files changed, 21 insertions(+), 5 deletions(-) diff --git a/crates/wit-component/tests/interfaces.rs b/crates/wit-component/tests/interfaces.rs index 20ee8f816b..508799367b 100644 --- a/crates/wit-component/tests/interfaces.rs +++ b/crates/wit-component/tests/interfaces.rs @@ -38,7 +38,7 @@ fn run_test(path: &Path, is_dir: bool) -> Result<()> { println!("running test at {path:?}"); let mut resolve = Resolve::new(); let package = if is_dir { - resolve.push_dir(path)? + resolve.push_dir(path)?.0 } else { resolve.push(UnresolvedPackage::parse_file(path)?, &Default::default())? }; diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 33e0e5a54a..ade9269460 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -1002,4 +1002,8 @@ impl SourceMap { (text.lines().count(), 0) } } + + pub fn source_files(&self) -> impl Iterator { + self.sources.iter().map(|(_, path, _)| path.as_path()) + } } diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 9b3e7f9240..863c84ca46 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -210,6 +210,12 @@ impl UnresolvedPackage { doc.source_map = map; Ok(doc) } + + /// Returns an iterator over the list of source files that were read when + /// parsing this package. + pub fn source_files(&self) -> impl Iterator { + self.source_map.source_files() + } } /// Represents the result of parsing a wit document. diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 139a7fa180..ca8e6d9522 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -69,7 +69,11 @@ impl Resolve { /// returned indicating that the dependency is not defined. All dependencies /// are listed in a flat namespace under `$path/deps` so they can refer to /// each other. - pub fn push_dir(&mut self, path: &Path) -> Result { + /// + /// This function returns the [`PackageId`] of the root parsed package at + /// `path`, along with a list of all paths that were consumed during parsing + /// for the root package and all dependency packages. + pub fn push_dir(&mut self, path: &Path) -> Result<(PackageId, Vec)> { // Maintain a `to_parse` stack of packages that have yet to be parsed // along with an `enqueued` set of all the prior parsed packages and // packges enqueued to be parsed. These are then used to fill the @@ -130,18 +134,20 @@ impl Resolve { // package, which is the one returned here. let mut package_ids = IndexMap::new(); let mut last = None; + let mut files = Vec::new(); for path in order { let pkg = packages.remove(path).unwrap(); let mut deps = HashMap::new(); for ((dep, _), (path, _span)) in pkg.foreign_deps.iter().zip(&pkg_deps[path]) { deps.insert(dep.clone(), package_ids[&**path]); } + files.extend(pkg.source_files().map(|p| p.to_path_buf())); let pkgid = self.push(pkg, &deps)?; package_ids.insert(path, pkgid); last = Some(pkgid); } - return Ok(last.unwrap()); + return Ok((last.unwrap(), files)); fn visit<'a>( path: &'a Path, diff --git a/crates/wit-parser/tests/all.rs b/crates/wit-parser/tests/all.rs index 7300a375e5..8e41fc5b91 100644 --- a/crates/wit-parser/tests/all.rs +++ b/crates/wit-parser/tests/all.rs @@ -104,7 +104,7 @@ impl Runner<'_> { fn run(&mut self, test: &Path) -> Result<()> { let mut resolve = Resolve::new(); let result = if test.is_dir() { - resolve.push_dir(test) + resolve.push_dir(test).map(|(id, _)| id) } else { UnresolvedPackage::parse_file(test).and_then(|p| resolve.push(p, &Default::default())) }; diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index 3da5fe686a..c9db61e67e 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -457,7 +457,7 @@ impl WitOpts { fn parse_wit(path: &Path) -> Result<(Resolve, PackageId)> { let mut resolve = Resolve::default(); let id = if path.is_dir() { - resolve.push_dir(&path)? + resolve.push_dir(&path)?.0 } else { let pkg = UnresolvedPackage::parse_file(&path)?; resolve.push(pkg, &Default::default())? From e402db14baf1e190d8836e21e7b369d861185cbc Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 13 Jan 2023 07:27:45 -0800 Subject: [PATCH 22/37] Review comments --- crates/wit-component/src/decoding.rs | 10 +++++----- crates/wit-component/src/metadata.rs | 13 ++++++++----- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index 14d49a2eea..65cdb72f79 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -432,9 +432,9 @@ impl WitPackageDecoder<'_> { // decoding. let prev = self.type_map.insert(ty, Type::Id(id)); assert!(prev.is_none()); - if !self.type_src_map.contains_key(&PtrHash(def)) { - self.type_src_map.insert(PtrHash(def), Type::Id(id)); - } + self.type_src_map + .entry(PtrHash(def)) + .or_insert(Type::Id(id)); } // This has similar logic to types above where we lazily fill in @@ -666,7 +666,7 @@ impl WitPackageDecoder<'_> { WorldItem::Function(func) } - _ => bail!("component import `{name}` is not an instance"), + _ => bail!("component import `{name}` is not an instance or function"), }; world.imports.insert(name.to_string(), item); } @@ -694,7 +694,7 @@ impl WitPackageDecoder<'_> { WorldItem::Function(func) } - _ => bail!("component export `{name}` is not an instance"), + _ => bail!("component export `{name}` is not an instance or function"), }; world.exports.insert(name.to_string(), item); } diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index 6b2e9d0292..4a1e824c67 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -16,9 +16,7 @@ //! per-language-binding-generation and consumed by slurping up all the //! sections during the component creation process. //! -//! The custom section here contains `World`, the interpretation of a "world" -//! of a component, along with how strings are encoded for all the specified -//! interfaces. Currently the encoding is: +//! Currently the encoding of this custom section is: //! //! * First, a version byte (`CURRENT_VERSION`). This is intended to detect //! mismatches between different versions of the binding generator and @@ -26,8 +24,13 @@ //! //! * Next a string encoding byte. //! -//! * Afterwards a "types only" component encoding of a `World` -//! package through the `ComponentEncoder::types_only` configuration. +//! * Next, three strings are encoded. These are the names of the root package, +//! document, and world that the bindings were generated for. These strings +//! are used as lookups into the next field. +//! +//! * Finally the Wasm-encoded representation of a `Resolve` is included in its +//! binary form. This is the encoding of a package into wasm, and the bound +//! world for the bindings is specified from the prior strings. use crate::{DecodedWasm, StringEncoding}; use anyhow::{bail, Context, Result}; From d21c7b63618cdc58cf98acfd3068bde959fb9514 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 13 Jan 2023 07:55:00 -0800 Subject: [PATCH 23/37] Export the `SourceMap` type from `wit-parser` This should allow more fine-grained control over precisely how a package is structured on the filesystem and additionally allow creating multi-document packages entirely in-memory without using the filesystem. --- crates/wit-parser/src/ast.rs | 111 +++++++++++++----- crates/wit-parser/src/ast/resolve.rs | 25 ++-- crates/wit-parser/src/lib.rs | 33 ++---- .../ui/parse-fail/invalid@filename.wit.result | 5 +- 4 files changed, 108 insertions(+), 66 deletions(-) diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index ade9269460..545da5500e 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -1,5 +1,5 @@ -use crate::Error; -use anyhow::{bail, Result}; +use crate::{Error, UnresolvedPackage}; +use anyhow::{bail, Context, Result}; use lex::{Span, Token, Tokenizer}; use std::borrow::Cow; use std::convert::TryFrom; @@ -849,22 +849,68 @@ fn err_expected( } } +/// A listing of source files which are used to get parsed into an +/// [`UnresolvedPackage`]. #[derive(Clone, Default)] pub struct SourceMap { - sources: Vec<(u32, PathBuf, String)>, + sources: Vec, offset: u32, } +#[derive(Clone)] +struct Source { + offset: u32, + path: PathBuf, + name: String, + contents: String, +} + impl SourceMap { - pub fn push(&mut self, path: &Path, contents: impl Into) { + /// Creates a new empty source map. + pub fn new() -> SourceMap { + SourceMap::default() + } + + /// Reads the file `path` on the filesystem and appends its contents to this + /// [`SourceMap`]. + /// + /// This method pushes a new document into the source map. The name of the + /// document is derived from the filename of the `path` provided. + pub fn push_file(&mut self, path: &Path) -> Result<()> { + let contents = std::fs::read_to_string(path) + .with_context(|| format!("failed to read file {path:?}"))?; + let filename = match path.file_name().and_then(|s| s.to_str()) { + Some(stem) => stem, + None => bail!("no filename for {path:?}"), + }; + let name = match filename.find('.') { + Some(i) => &filename[..i], + None => filename, + }; + self.push(path, name, contents); + Ok(()) + } + + /// Appends the given contents with the given path into this source map. + /// + /// Each path added to a [`SourceMap`] will become a document in the final + /// package. The `path` provided is not read from the filesystem and is + /// instead only used during error messages. The `name` provided is the name + /// of the document within the WIT package and must be a valid WIT + /// identifier. + pub fn push(&mut self, path: &Path, name: &str, contents: impl Into) { let mut contents = contents.into(); if path.extension().and_then(|s| s.to_str()) == Some("md") { log::debug!("automatically unwrapping markdown container"); contents = unwrap_md(&contents); } let new_offset = self.offset + u32::try_from(contents.len()).unwrap(); - self.sources - .push((self.offset, path.to_path_buf(), contents)); + self.sources.push(Source { + offset: self.offset, + path: path.to_path_buf(), + contents, + name: name.to_string(), + }); self.offset = new_offset; fn unwrap_md(contents: &str) -> String { @@ -905,17 +951,30 @@ impl SourceMap { } } - pub fn tokenizers(&self) -> impl Iterator)>> { - let mut srcs = self - .sources - .iter() - .map(|(offset, path, contents)| Tokenizer::new(contents, *offset).map(|t| (&**path, t))) - .collect::>(); - srcs.sort_by_key(|file| file.as_ref().ok().map(|(path, _)| path.to_path_buf())); - srcs.into_iter() + /// Parses the files added to this source map into an [`UnresolvedPackage`]. + /// + /// All files previously added are considered documents of the package to be + /// returned. + pub fn parse(self, name: &str, url: Option<&str>) -> Result { + let mut doc = self.rewrite_error(|| { + let mut resolver = Resolver::default(); + let mut srcs = self.sources.iter().collect::>(); + srcs.sort_by_key(|src| &src.name); + for src in srcs { + let mut tokens = Tokenizer::new(&src.contents, src.offset) + .with_context(|| format!("failed to tokenize path: {}", src.path.display()))?; + let ast = Ast::parse(&mut tokens)?; + resolver.push(&src.name, ast).with_context(|| { + format!("failed to start resolving path: {}", src.path.display()) + })?; + } + resolver.resolve(name, url) + })?; + doc.source_map = self; + Ok(doc) } - pub fn rewrite_error(&self, f: F) -> Result + pub(crate) fn rewrite_error(&self, f: F) -> Result where F: FnOnce() -> Result, { @@ -954,18 +1013,15 @@ impl SourceMap { } fn highlight_err(&self, start: u32, end: Option, err: impl fmt::Display) -> String { - let i = match self - .sources - .binary_search_by_key(&start, |(offset, _, _)| *offset) - { + let i = match self.sources.binary_search_by_key(&start, |src| src.offset) { Ok(i) => i, Err(i) => i - 1, }; - let (offset, file, contents) = &self.sources[i]; - let start = usize::try_from(start - *offset).unwrap(); - let end = end.map(|end| usize::try_from(end - *offset).unwrap()); - let (line, col) = linecol_in(start, contents); - let snippet = contents.lines().nth(line).unwrap_or(""); + let src = &self.sources[i]; + let start = usize::try_from(start - src.offset).unwrap(); + let end = end.map(|end| usize::try_from(end - src.offset).unwrap()); + let (line, col) = linecol_in(start, &src.contents); + let snippet = src.contents.lines().nth(line).unwrap_or(""); let mut msg = format!( "\ {err} @@ -974,13 +1030,13 @@ impl SourceMap { {line:4} | {snippet} | {marker:>0$}", col + 1, - file = file.display(), + file = src.path.display(), line = line + 1, col = col + 1, marker = "^", ); if let Some(end) = end { - if let Some(s) = contents.get(start..end) { + if let Some(s) = src.contents.get(start..end) { for _ in s.chars().skip(1) { msg.push('-'); } @@ -1003,7 +1059,8 @@ impl SourceMap { } } + /// Returns an iterator over all filenames added to this source map. pub fn source_files(&self) -> impl Iterator { - self.sources.iter().map(|(_, path, _)| path.as_path()) + self.sources.iter().map(|src| src.path.as_path()) } } diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 4cc835b362..8d816b8c13 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1,7 +1,7 @@ use super::{Error, ParamList, ResultList, ValueKind}; use crate::ast::toposort::toposort; use crate::*; -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; use indexmap::IndexMap; use std::collections::HashMap; use std::mem; @@ -54,21 +54,14 @@ enum Key { } impl<'a> Resolver<'a> { - pub(crate) fn push(&mut self, path: &'a Path, ast: ast::Ast<'a>) -> Result<()> { - let filename = match path.file_name().and_then(|s| s.to_str()) { - Some(stem) => stem, - None => bail!("no filename for {path:?}"), - }; - let name = match filename.find('.') { - Some(i) => &filename[..i], - None => filename, - }; - crate::validate_id(name).map_err(|e| { - anyhow::anyhow!( - "filename was not a valid WIT identifier for {}: {e}", - path.display() - ) - })?; + pub(crate) fn push(&mut self, name: &'a str, ast: ast::Ast<'a>) -> Result<()> { + // Note that this specifically uses `map_err` instead of `with_context` + // since the error returned from `validate_id` is an `Error` which has a + // filename and a line number, but there's no filename or line number + // associated with this error so we just want the string message. + crate::validate_id(name) + .map_err(|e| anyhow!("name of document isn't a valid WIT identifier `{name}`: {e}"))?; + let prev = self.asts.insert(name, ast); if prev.is_some() { bail!("document `{name}` defined twice"); diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 863c84ca46..5e246256a2 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -7,7 +7,8 @@ use std::path::Path; pub mod abi; mod ast; -use ast::{lex::Span, Ast, Resolver, SourceMap}; +use ast::lex::Span; +pub use ast::SourceMap; mod sizealign; pub use sizealign::*; mod resolve; @@ -130,11 +131,15 @@ impl UnresolvedPackage { pub fn parse(path: &Path, contents: &str) -> Result { let mut map = SourceMap::default(); let name = path - .file_stem() + .file_name() .and_then(|s| s.to_str()) .ok_or_else(|| anyhow!("path doesn't end in a valid package name {path:?}"))?; - map.push(path, contents); - Self::_parse(name, None, map) + let name = match name.find('.') { + Some(i) => &name[..i], + None => name, + }; + map.push(path, name, contents); + map.parse(name, None) } /// Parse a WIT package at the provided path. @@ -190,25 +195,9 @@ impl UnresolvedPackage { if !filename.ends_with(".wit") && !filename.ends_with(".wit.md") { continue; } - let contents = std::fs::read_to_string(&path) - .with_context(|| format!("failed to read file {path:?}"))?; - map.push(&path, contents); + map.push_file(&path)?; } - Self::_parse(name, None, map) - } - - fn _parse(name: &str, url: Option<&str>, map: SourceMap) -> Result { - let mut doc = map.rewrite_error(|| { - let mut resolver = Resolver::default(); - for file in map.tokenizers() { - let (path, mut tokens) = file?; - let ast = Ast::parse(&mut tokens)?; - resolver.push(path, ast)?; - } - resolver.resolve(name, url) - })?; - doc.source_map = map; - Ok(doc) + map.parse(name, None) } /// Returns an iterator over the list of source files that were read when diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result b/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result index 1eaa15971e..461cca9d53 100644 --- a/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/invalid@filename.wit.result @@ -1 +1,4 @@ -filename was not a valid WIT identifier for tests/ui/parse-fail/invalid@filename.wit: invalid character in identifier '@' \ No newline at end of file +failed to start resolving path: tests/ui/parse-fail/invalid@filename.wit + +Caused by: + name of document isn't a valid WIT identifier `invalid@filename`: invalid character in identifier '@' \ No newline at end of file From 5267c9621476248142059a88f193882a2ee9ef86 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 13 Jan 2023 10:50:26 -0800 Subject: [PATCH 24/37] Fix decoding and printing one named return value --- crates/wit-component/src/decoding.rs | 2 +- crates/wit-component/src/printing.rs | 4 ---- .../tests/interfaces/single-named-result.wat | 14 ++++++++++++++ .../tests/interfaces/single-named-result.wit | 3 +++ .../tests/interfaces/single-named-result.wit.print | 4 ++++ 5 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 crates/wit-component/tests/interfaces/single-named-result.wat create mode 100644 crates/wit-component/tests/interfaces/single-named-result.wit create mode 100644 crates/wit-component/tests/interfaces/single-named-result.wit.print diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index 65cdb72f79..a2c6f6097f 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -707,7 +707,7 @@ impl WitPackageDecoder<'_> { .iter() .map(|(name, ty)| Ok((name.to_string(), self.convert_valtype(ty)?))) .collect::>>()?; - let results = if ty.results.len() == 1 { + let results = if ty.results.len() == 1 && ty.results[0].0.is_none() { Results::Anon(self.convert_valtype(&ty.results[0].1)?) } else { Results::Named( diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index e477509416..0a217a67f9 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -122,10 +122,6 @@ impl DocumentPrinter { match &func.results { Results::Named(rs) => match rs.len() { 0 => (), - 1 => { - self.output.push_str(" -> "); - self.print_type_name(resolve, &rs[0].1)?; - } _ => { self.output.push_str(" -> ("); for (i, (name, ty)) in rs.iter().enumerate() { diff --git a/crates/wit-component/tests/interfaces/single-named-result.wat b/crates/wit-component/tests/interfaces/single-named-result.wat new file mode 100644 index 0000000000..6c8616bc14 --- /dev/null +++ b/crates/wit-component/tests/interfaces/single-named-result.wat @@ -0,0 +1,14 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (func (result "a" u32))) + (export (;0;) "a" (func (type 0))) + ) + ) + (export (;0;) "foo" "pkg:/single-named-result/foo" (instance (type 0))) + ) + ) + (export (;1;) "single-named-result" "pkg:/single-named-result" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/single-named-result.wit b/crates/wit-component/tests/interfaces/single-named-result.wit new file mode 100644 index 0000000000..13afc00d1b --- /dev/null +++ b/crates/wit-component/tests/interfaces/single-named-result.wit @@ -0,0 +1,3 @@ +interface foo { + a: func() -> (a: u32) +} diff --git a/crates/wit-component/tests/interfaces/single-named-result.wit.print b/crates/wit-component/tests/interfaces/single-named-result.wit.print new file mode 100644 index 0000000000..cd6cb560dc --- /dev/null +++ b/crates/wit-component/tests/interfaces/single-named-result.wit.print @@ -0,0 +1,4 @@ +interface foo { + a: func() -> (a: u32) +} + From 492a8d842ad7ac5387e41f3d4d2b2997d7e99b45 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 13 Jan 2023 10:51:44 -0800 Subject: [PATCH 25/37] Fix printing of WIT where names are keywords --- crates/wit-component/src/printing.rs | 111 +++++++++++++----- .../tests/interfaces/print-keyword.wat | 17 +++ .../tests/interfaces/print-keyword.wit | 8 ++ .../tests/interfaces/print-keyword.wit.print | 11 ++ 4 files changed, 120 insertions(+), 27 deletions(-) create mode 100644 crates/wit-component/tests/interfaces/print-keyword.wat create mode 100644 crates/wit-component/tests/interfaces/print-keyword.wit create mode 100644 crates/wit-component/tests/interfaces/print-keyword.wit.print diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 0a217a67f9..3fdd035eeb 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -18,7 +18,9 @@ impl DocumentPrinter { if Some(*id) == doc.default_interface { self.output.push_str("default "); } - writeln!(&mut self.output, "interface {name} {{")?; + self.output.push_str("interface "); + self.print_name(name); + self.output.push_str(" {\n"); self.print_interface(resolve, *id)?; writeln!(&mut self.output, "}}\n")?; } @@ -28,7 +30,9 @@ impl DocumentPrinter { self.output.push_str("default "); } let world = &resolve.worlds[*id]; - writeln!(&mut self.output, "world {name} {{")?; + self.output.push_str("world "); + self.print_name(name); + self.output.push_str(" {\n"); for (name, import) in world.imports.iter() { self.print_world_item(resolve, name, import, docid, "import")?; } @@ -83,9 +87,11 @@ impl DocumentPrinter { write!(&mut self.output, ", ")?; } if my_name == other_name { - write!(&mut self.output, "{my_name}")?; + self.print_name(my_name); } else { - write!(&mut self.output, "{other_name} as {my_name}")?; + self.print_name(other_name); + self.output.push_str(" as "); + self.print_name(my_name); } } writeln!(&mut self.output, "}}")?; @@ -100,7 +106,8 @@ impl DocumentPrinter { if i > 0 { self.output.push_str("\n"); } - write!(&mut self.output, "{name}: ")?; + self.print_name(name); + self.output.push_str(": "); self.print_function(resolve, func)?; self.output.push_str("\n"); } @@ -114,7 +121,8 @@ impl DocumentPrinter { if i > 0 { self.output.push_str(", "); } - write!(&mut self.output, "{}: ", name)?; + self.print_name(name); + self.output.push_str(": "); self.print_type_name(resolve, ty)?; } self.output.push_str(")"); @@ -128,7 +136,8 @@ impl DocumentPrinter { if i > 0 { self.output.push_str(", "); } - write!(&mut self.output, "{name}: ")?; + self.print_name(name); + self.output.push_str(": "); self.print_type_name(resolve, ty)?; } self.output.push_str(")"); @@ -150,7 +159,10 @@ impl DocumentPrinter { cur_doc: DocumentId, desc: &str, ) -> Result<()> { - write!(&mut self.output, "{desc} {name}: ")?; + self.output.push_str(desc); + self.output.push_str(" "); + self.print_name(name); + self.output.push_str(": "); match item { WorldItem::Interface(id) => { if resolve.interfaces[*id].name.is_some() { @@ -180,14 +192,18 @@ impl DocumentPrinter { let iface = &resolve.interfaces[interface]; let iface_doc = &resolve.documents[iface.document]; if iface.document == cur_doc { - write!(&mut self.output, "self")?; + self.output.push_str("self"); } else if cur_pkg == iface_doc.package { - write!(&mut self.output, "pkg.{}", iface_doc.name)?; + self.output.push_str("pkg."); + self.print_name(&iface_doc.name); } else { let iface_pkg = &resolve.packages[iface_doc.package.unwrap()]; - write!(&mut self.output, "{}.{}", iface_pkg.name, iface_doc.name)?; + self.print_name(&iface_pkg.name); + self.output.push_str("."); + self.print_name(&iface_doc.name); } - write!(&mut self.output, ".{}", iface.name.as_ref().unwrap())?; + self.output.push_str("."); + self.print_name(iface.name.as_ref().unwrap()); Ok(()) } @@ -210,7 +226,7 @@ impl DocumentPrinter { Type::Id(id) => { let ty = &resolve.types[*id]; if let Some(name) = &ty.name { - self.output.push_str(name); + self.print_name(name); return Ok(()); } @@ -361,7 +377,9 @@ impl DocumentPrinter { } TypeDefKind::Type(inner) => match ty.name.as_deref() { Some(name) => { - write!(&mut self.output, "type {} = ", name)?; + self.output.push_str("type "); + self.print_name(name); + self.output.push_str(" = "); self.print_type_name(resolve, inner)?; self.output.push_str("\n\n"); } @@ -388,9 +406,12 @@ impl DocumentPrinter { match name { Some(name) => { - writeln!(&mut self.output, "record {} {{", name)?; + self.output.push_str("record "); + self.print_name(name); + self.output.push_str(" {\n"); for field in &record.fields { - write!(&mut self.output, "{}: ", field.name)?; + self.print_name(&field.name); + self.output.push_str(": "); self.declare_type(resolve, &field.ty)?; self.print_type_name(resolve, &field.ty)?; self.output.push_str(",\n"); @@ -413,7 +434,9 @@ impl DocumentPrinter { } if let Some(name) = name { - write!(&mut self.output, "type {} = ", name)?; + self.output.push_str("type "); + self.print_name(name); + self.output.push_str(" = "); self.print_tuple_type(resolve, tuple)?; self.output.push_str("\n\n"); } @@ -423,9 +446,12 @@ impl DocumentPrinter { fn declare_flags(&mut self, name: Option<&str>, flags: &Flags) -> Result<()> { match name { Some(name) => { - writeln!(&mut self.output, "flags {} {{", name)?; + self.output.push_str("flags "); + self.print_name(name); + self.output.push_str(" {\n"); for flag in &flags.flags { - writeln!(&mut self.output, "{},", flag.name)?; + self.print_name(&flag.name); + self.output.push_str(",\n"); } self.output.push_str("}\n\n"); } @@ -450,9 +476,11 @@ impl DocumentPrinter { Some(name) => name, None => bail!("document has unnamed union type"), }; - writeln!(&mut self.output, "variant {} {{", name)?; + self.output.push_str("variant "); + self.print_name(name); + self.output.push_str(" {\n"); for case in &variant.cases { - write!(&mut self.output, "{}", case.name)?; + self.print_name(&case.name); if let Some(ty) = case.ty { self.output.push_str("("); self.print_type_name(resolve, &ty)?; @@ -478,7 +506,9 @@ impl DocumentPrinter { Some(name) => name, None => bail!("document has unnamed union type"), }; - writeln!(&mut self.output, "union {} {{", name)?; + self.output.push_str("union "); + self.print_name(name); + self.output.push_str(" {\n"); for case in &union.cases { self.output.push_str(""); self.print_type_name(resolve, &case.ty)?; @@ -497,7 +527,9 @@ impl DocumentPrinter { self.declare_type(resolve, payload)?; if let Some(name) = name { - write!(&mut self.output, "type {} = ", name)?; + self.output.push_str("type "); + self.print_name(name); + self.output.push_str(" = "); self.print_option_type(resolve, payload)?; self.output.push_str("\n\n"); } @@ -518,7 +550,9 @@ impl DocumentPrinter { } if let Some(name) = name { - write!(&mut self.output, "type {} = ", name)?; + self.output.push_str("type "); + self.print_name(name); + self.output.push_str(" = "); self.print_result_type(resolve, result)?; self.output.push_str("\n\n"); } @@ -530,9 +564,12 @@ impl DocumentPrinter { Some(name) => name, None => bail!("document has unnamed enum type"), }; - writeln!(&mut self.output, "enum {} {{", name)?; + self.output.push_str("enum "); + self.print_name(name); + self.output.push_str(" {\n"); for case in &enum_.cases { - writeln!(&mut self.output, "{},", case.name)?; + self.print_name(&case.name); + self.output.push_str(",\n"); } self.output.push_str("}\n\n"); Ok(()) @@ -542,7 +579,9 @@ impl DocumentPrinter { self.declare_type(resolve, ty)?; if let Some(name) = name { - write!(&mut self.output, "type {} = list<", name)?; + self.output.push_str("type "); + self.print_name(name); + self.output.push_str(" = list<"); self.print_type_name(resolve, ty)?; self.output.push_str(">\n\n"); return Ok(()); @@ -550,6 +589,24 @@ impl DocumentPrinter { Ok(()) } + + fn print_name(&mut self, name: &str) { + if is_keyword(name) { + self.output.push_str("%"); + } + self.output.push_str(name); + } +} + +fn is_keyword(name: &str) -> bool { + match name { + "use" | "type" | "func" | "u8" | "u16" | "u32" | "u64" | "s8" | "s16" | "s32" | "s64" + | "float32" | "float64" | "char" | "record" | "flags" | "variant" | "enum" | "union" + | "bool" | "string" | "option" | "result" | "future" | "stream" | "list" | "_" | "as" + | "from" | "static" | "interface" | "tuple" | "implements" | "world" | "import" + | "export" | "default" | "pkg" | "self" => true, + _ => false, + } } /// Helper structure to help maintain an indentation level when printing source, diff --git a/crates/wit-component/tests/interfaces/print-keyword.wat b/crates/wit-component/tests/interfaces/print-keyword.wat new file mode 100644 index 0000000000..8f5268005b --- /dev/null +++ b/crates/wit-component/tests/interfaces/print-keyword.wat @@ -0,0 +1,17 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) u32) + (export (;1;) "type" (type (eq 0))) + (export (;2;) "world" (type (eq 1))) + (type (;3;) (record (field "variant" 2))) + (export (;4;) "record" (type (eq 3))) + ) + ) + (export (;0;) "interface" "pkg:/print-keyword/interface" (instance (type 0))) + ) + ) + (export (;1;) "print-keyword" "pkg:/print-keyword" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/print-keyword.wit b/crates/wit-component/tests/interfaces/print-keyword.wit new file mode 100644 index 0000000000..9d938a98e8 --- /dev/null +++ b/crates/wit-component/tests/interfaces/print-keyword.wit @@ -0,0 +1,8 @@ +interface %interface { + type %type = u32 + type %world = %type + + record %record { + %variant: %world + } +} diff --git a/crates/wit-component/tests/interfaces/print-keyword.wit.print b/crates/wit-component/tests/interfaces/print-keyword.wit.print new file mode 100644 index 0000000000..7d930bdcde --- /dev/null +++ b/crates/wit-component/tests/interfaces/print-keyword.wit.print @@ -0,0 +1,11 @@ +interface %interface { + type %type = u32 + + type %world = %type + + record %record { + %variant: %world, + } + +} + From 5b8489abf535faf783ae47c0467b5ff9b4b53eda Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 13 Jan 2023 13:04:04 -0800 Subject: [PATCH 26/37] Fix a build error for just the `component` subcommand --- .github/workflows/main.yml | 1 + Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 95d4508a7d..36dfbc9d85 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -87,6 +87,7 @@ jobs: - run: cargo check --no-default-features --features strip - run: cargo check --no-default-features --features compose - run: cargo check --no-default-features --features demangle + - run: cargo check --no-default-features --features component doc: runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 8b0b291da4..604d477edf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -147,4 +147,4 @@ objdump = ['wasmparser'] strip = ['wasm-encoder', 'wasmparser', 'regex'] compose = ['wasm-compose'] demangle = ['rustc-demangle', 'cpp_demangle', 'wasmparser', 'wasm-encoder'] -component = ['wit-component', 'wit-parser', 'wast', 'wasm-encoder'] +component = ['wit-component', 'wit-parser', 'wast', 'wasm-encoder', 'wasmparser'] From 8aa75e575175a94899e024367b6c20c74f26319b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 13 Jan 2023 13:04:49 -0800 Subject: [PATCH 27/37] Better handle `TypeId` and exported types This commit updates the `ComponentEntityType::Type` variant to contain both the `TypeId` for the type that was exported as well the identifier for the exported type (or imported). This allows decoders like `wit-component` to understand the chain from what got exported to the id of the export itself to fully understand alias chains. --- crates/wasm-compose/src/encoding.rs | 6 +- crates/wasm-compose/src/graph.rs | 2 +- crates/wasmparser/src/validator.rs | 3 +- crates/wasmparser/src/validator/component.rs | 54 ++++++----- crates/wasmparser/src/validator/types.rs | 90 +++++++------------ crates/wit-component/src/decoding.rs | 41 ++++++--- .../tests/interfaces/use-chain.wat | 16 ++++ .../tests/interfaces/use-chain.wit | 5 ++ .../tests/interfaces/use-chain.wit.print | 9 ++ 9 files changed, 130 insertions(+), 96 deletions(-) create mode 100644 crates/wit-component/tests/interfaces/use-chain.wat create mode 100644 crates/wit-component/tests/interfaces/use-chain.wit create mode 100644 crates/wit-component/tests/interfaces/use-chain.wit.print diff --git a/crates/wasm-compose/src/encoding.rs b/crates/wasm-compose/src/encoding.rs index 5313e40a6c..2b2cbe185a 100644 --- a/crates/wasm-compose/src/encoding.rs +++ b/crates/wasm-compose/src/encoding.rs @@ -21,7 +21,7 @@ fn type_ref_to_export_kind(ty: wasmparser::ComponentTypeRef) -> ComponentExportK wasmparser::ComponentTypeRef::Module(_) => ComponentExportKind::Module, wasmparser::ComponentTypeRef::Func(_) => ComponentExportKind::Func, wasmparser::ComponentTypeRef::Value(_) => ComponentExportKind::Value, - wasmparser::ComponentTypeRef::Type(_, _) => ComponentExportKind::Type, + wasmparser::ComponentTypeRef::Type { .. } => ComponentExportKind::Type, wasmparser::ComponentTypeRef::Instance(_) => ComponentExportKind::Instance, wasmparser::ComponentTypeRef::Component(_) => ComponentExportKind::Component, } @@ -255,8 +255,8 @@ impl<'a> TypeEncoder<'a> { wasmparser::types::ComponentEntityType::Value(ty) => { ComponentTypeRef::Value(self.component_val_type(encodable, types, ty)) } - wasmparser::types::ComponentEntityType::Type(id) => { - ComponentTypeRef::Type(TypeBounds::Eq, self.ty(encodable, types, id)) + wasmparser::types::ComponentEntityType::Type { created, .. } => { + ComponentTypeRef::Type(TypeBounds::Eq, self.ty(encodable, types, created)) } wasmparser::types::ComponentEntityType::Instance(id) => { ComponentTypeRef::Instance(self.component_instance_type(encodable, types, id)) diff --git a/crates/wasm-compose/src/graph.rs b/crates/wasm-compose/src/graph.rs index 462a70d173..bcf2c5d3b0 100644 --- a/crates/wasm-compose/src/graph.rs +++ b/crates/wasm-compose/src/graph.rs @@ -21,7 +21,7 @@ pub(crate) fn type_desc(item: ComponentEntityType) -> &'static str { ComponentEntityType::Module(_) => "module", ComponentEntityType::Func(_) => "function", ComponentEntityType::Value(_) => "value", - ComponentEntityType::Type(_) => "type", + ComponentEntityType::Type { .. } => "type", ComponentEntityType::Component(_) => "component", } } diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs index 05dc4462ef..cb4aca61f2 100644 --- a/crates/wasmparser/src/validator.rs +++ b/crates/wasmparser/src/validator.rs @@ -1171,14 +1171,13 @@ impl Validator { }, |components, types, _, export, offset| { let current = components.last_mut().unwrap(); - let ty = current.export_to_entity_type(&export, offset)?; + let ty = current.export_to_entity_type(&export, types, offset)?; current.add_export( export.name, export.url, ty, offset, false, /* checked above */ - types, ) }, ) diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index e421d6707f..79087b28ec 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -211,7 +211,7 @@ impl ComponentState { pub fn add_import( &mut self, import: crate::ComponentImport, - types: &TypeList, + types: &mut TypeAlloc, offset: usize, ) -> Result<()> { let entity = self.check_type_ref(&import.ty, types, offset)?; @@ -270,8 +270,8 @@ impl ComponentState { self.values.push((ty, value_used)); (self.values.len(), MAX_WASM_VALUES, "values") } - ComponentEntityType::Type(id) => { - self.types.push(id); + ComponentEntityType::Type { created, .. } => { + self.types.push(created); (self.types.len(), MAX_WASM_TYPES, "types") } }; @@ -284,21 +284,13 @@ impl ComponentState { &mut self, name: &str, url: &str, - mut ty: ComponentEntityType, + ty: ComponentEntityType, offset: usize, check_limit: bool, - types: &mut TypeAlloc, ) -> Result<()> { if check_limit { check_max(self.exports.len(), 1, MAX_WASM_EXPORTS, "exports", offset)?; } - // Assign a unique index to this type id as it gets pushed onto the - // internal type list because this is creating an alias and this TypeId - // should uniquely indicate that it's a distinct reference to the same - // underlying actual type information. - if let ComponentEntityType::Type(id) = &mut ty { - *id = types.with_unique(*id); - } self.add_entity(ty, true, offset)?; let name = to_kebab_str(name, "export", offset)?; @@ -662,10 +654,10 @@ impl ComponentState { Ok(()) } - pub fn check_type_ref( + fn check_type_ref( &self, ty: &ComponentTypeRef, - types: &TypeList, + types: &mut TypeAlloc, offset: usize, ) -> Result { Ok(match ty { @@ -693,7 +685,12 @@ impl ComponentState { ComponentEntityType::Value(ty) } ComponentTypeRef::Type(TypeBounds::Eq, index) => { - ComponentEntityType::Type(self.type_at(*index, false, offset)?) + let referenced = self.type_at(*index, false, offset)?; + let created = types.with_unique(referenced); + ComponentEntityType::Type { + referenced, + created, + } } ComponentTypeRef::Instance(index) => { let id = self.type_at(*index, false, offset)?; @@ -715,6 +712,7 @@ impl ComponentState { pub fn export_to_entity_type( &mut self, export: &crate::ComponentExport, + types: &mut TypeAlloc, offset: usize, ) -> Result { Ok(match export.kind { @@ -728,7 +726,12 @@ impl ComponentState { ComponentEntityType::Value(*self.value_at(export.index, offset)?) } ComponentExternalKind::Type => { - ComponentEntityType::Type(self.type_at(export.index, false, offset)?) + let referenced = self.type_at(export.index, false, offset)?; + let created = types.with_unique(referenced); + ComponentEntityType::Type { + referenced, + created, + } } ComponentExternalKind::Instance => { ComponentEntityType::Instance(self.instance_at(export.index, offset)?) @@ -817,7 +820,7 @@ impl ComponentState { crate::ComponentTypeDeclaration::Export { name, url, ty } => { let current = components.last_mut().unwrap(); let ty = current.check_type_ref(&ty, types, offset)?; - current.add_export(name, url, ty, offset, true, types)?; + current.add_export(name, url, ty, offset, true)?; } crate::ComponentTypeDeclaration::Import(import) => { components @@ -860,7 +863,7 @@ impl ComponentState { crate::InstanceTypeDeclaration::Export { name, url, ty } => { let current = components.last_mut().unwrap(); let ty = current.check_type_ref(&ty, types, offset)?; - current.add_export(name, url, ty, offset, true, types)?; + current.add_export(name, url, ty, offset, true)?; } crate::InstanceTypeDeclaration::Alias(alias) => { Self::add_alias(components, alias, types, offset)?; @@ -1250,9 +1253,18 @@ impl ComponentState { )?; } ComponentExternalKind::Type => { + let ty = self.type_at(export.index, false, offset)?; insert_export( export.name, - ComponentEntityType::Type(self.type_at(export.index, false, offset)?), + ComponentEntityType::Type { + referenced: ty, + // The created type index here isn't used anywhere + // in index spaces because a "bag of exports" + // doesn't build up its own index spaces. Just fill + // in the same index here in this case as what's + // referenced. + created: ty, + }, &mut inst_exports, &mut type_size, offset, @@ -1501,8 +1513,8 @@ impl ComponentState { ComponentExternalKind::Type => { check_max(self.type_count(), 1, MAX_WASM_TYPES, "types", offset)?; match self.instance_export(instance_index, name, types, offset)? { - ComponentEntityType::Type(ty) => { - let id = types.with_unique(*ty); + ComponentEntityType::Type { referenced, .. } => { + let id = types.with_unique(*referenced); self.types.push(id); Ok(()) } diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs index f2f205d3db..945cb758c8 100644 --- a/crates/wasmparser/src/validator/types.rs +++ b/crates/wasmparser/src/validator/types.rs @@ -2,9 +2,8 @@ use super::{component::ComponentState, core::Module}; use crate::{ - ComponentExport, ComponentExternalKind, ComponentImport, ComponentTypeRef, Export, - ExternalKind, FuncType, GlobalType, Import, MemoryType, PrimitiveValType, TableType, TypeRef, - ValType, + ComponentExport, ComponentImport, Export, ExternalKind, FuncType, GlobalType, Import, + MemoryType, PrimitiveValType, TableType, TypeRef, ValType, }; use indexmap::{IndexMap, IndexSet}; use std::{ @@ -740,7 +739,18 @@ pub enum ComponentEntityType { /// The entity is a value. Value(ComponentValType), /// The entity is a type. - Type(TypeId), + Type { + /// This is the identifier of the type that was referenced when this + /// entity was created. + referenced: TypeId, + /// This is the identifier of the type that was created when this type + /// was imported or exported from the component. + /// + /// Note that the underlying type information for the `referenced` + /// field and for this `created` field is the same, but these two types + /// will hash to different values. + created: TypeId, + }, /// The entity is a component instance. Instance(TypeId), /// The entity is a component. @@ -770,12 +780,14 @@ impl ComponentEntityType { (Self::Value(a), Self::Value(b)) => { ComponentValType::internal_is_subtype_of(a, at, b, bt) } - (Self::Type(a), Self::Type(b)) => ComponentDefinedType::internal_is_subtype_of( - at[*a].as_defined_type().unwrap(), - at, - bt[*b].as_defined_type().unwrap(), - bt, - ), + (Self::Type { referenced: a, .. }, Self::Type { referenced: b, .. }) => { + ComponentDefinedType::internal_is_subtype_of( + at[*a].as_defined_type().unwrap(), + at, + bt[*b].as_defined_type().unwrap(), + bt, + ) + } (Self::Instance(a), Self::Instance(b)) => { ComponentInstanceType::internal_is_subtype_of( at[*a].as_component_instance_type().unwrap(), @@ -799,7 +811,7 @@ impl ComponentEntityType { Self::Module(_) => "module", Self::Func(_) => "function", Self::Value(_) => "value", - Self::Type(_) => "type", + Self::Type { .. } => "type", Self::Instance(_) => "instance", Self::Component(_) => "component", } @@ -809,7 +821,7 @@ impl ComponentEntityType { match self { Self::Module(ty) | Self::Func(ty) - | Self::Type(ty) + | Self::Type { referenced: ty, .. } | Self::Instance(ty) | Self::Component(ty) => ty.type_size, Self::Value(ty) => ty.type_size(), @@ -1638,29 +1650,10 @@ impl<'a> TypesRef<'a> { ) -> Option { match &self.kind { TypesRefKind::Module(_) => None, - TypesRefKind::Component(component) => Some(match import.ty { - ComponentTypeRef::Module(idx) => { - ComponentEntityType::Module(*component.core_types.get(idx as usize)?) - } - ComponentTypeRef::Func(idx) => { - ComponentEntityType::Func(*component.types.get(idx as usize)?) - } - ComponentTypeRef::Value(ty) => ComponentEntityType::Value(match ty { - crate::ComponentValType::Primitive(ty) => ComponentValType::Primitive(ty), - crate::ComponentValType::Type(idx) => { - ComponentValType::Type(*component.types.get(idx as usize)?) - } - }), - ComponentTypeRef::Type(_, idx) => { - ComponentEntityType::Type(*component.types.get(idx as usize)?) - } - ComponentTypeRef::Instance(idx) => { - ComponentEntityType::Instance(*component.types.get(idx as usize)?) - } - ComponentTypeRef::Component(idx) => { - ComponentEntityType::Component(*component.types.get(idx as usize)?) - } - }), + TypesRefKind::Component(component) => { + let key = KebabStr::new(import.name)?; + Some(component.imports.get(key)?.1) + } } } @@ -1671,29 +1664,10 @@ impl<'a> TypesRef<'a> { ) -> Option { match &self.kind { TypesRefKind::Module(_) => None, - TypesRefKind::Component(component) => Some(match export.kind { - ComponentExternalKind::Module => { - ComponentEntityType::Module(*component.core_modules.get(export.index as usize)?) - } - ComponentExternalKind::Func => { - ComponentEntityType::Func(*component.funcs.get(export.index as usize)?) - } - ComponentExternalKind::Value => ComponentEntityType::Value( - component - .values - .get(export.index as usize) - .map(|(r, _)| *r)?, - ), - ComponentExternalKind::Type => { - ComponentEntityType::Type(*component.types.get(export.index as usize)?) - } - ComponentExternalKind::Instance => { - ComponentEntityType::Instance(*component.instances.get(export.index as usize)?) - } - ComponentExternalKind::Component => ComponentEntityType::Component( - *component.components.get(export.index as usize)?, - ), - }), + TypesRefKind::Component(component) => { + let key = KebabStr::new(export.name)?; + Some(component.exports.get(key)?.1) + } } } } diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index a2c6f6097f..cbf7285c9f 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -377,8 +377,11 @@ impl WitPackageDecoder<'_> { } match ty { - types::ComponentEntityType::Type(ty) => { - let def = match self.info.types.type_from_id(ty) { + types::ComponentEntityType::Type { + referenced, + created, + } => { + let def = match self.info.types.type_from_id(referenced) { Some(types::Type::Defined(ty)) => ty, _ => unreachable!(), }; @@ -430,7 +433,7 @@ impl WitPackageDecoder<'_> { // Register the `types::TypeId` with our resolve `TypeId` // for ensuring type information remains correct throughout // decoding. - let prev = self.type_map.insert(ty, Type::Id(id)); + let prev = self.type_map.insert(created, Type::Id(id)); assert!(prev.is_none()); self.type_src_map .entry(PtrHash(def)) @@ -566,17 +569,32 @@ impl WitPackageDecoder<'_> { } match ty { - types::ComponentEntityType::Type(id) => { - let ty = match self.info.types.type_from_id(id) { + types::ComponentEntityType::Type { + referenced, + created, + } => { + let ty = match self.info.types.type_from_id(referenced) { Some(types::Type::Defined(ty)) => ty, _ => unreachable!(), }; let key = PtrHash(ty); - let (kind, insert_src) = match self.type_src_map.get(&key) { + + // Note that first the `type_map` is consulted for the + // referenced type id here, meaning if this is a reexport of + // another type in this interface then we're guaranteed to + // get that precise link. + // + // Failing that, though, the `type_src_map` is consulted to + // find the item, if present, from an alias of an import or + // other export, representing a cross-interface `use`. + let (kind, insert_src) = match self + .type_map + .get(&referenced) + .or_else(|| self.type_src_map.get(&key)) + { // If this `TypeId` points to a type which has - // previously been defined then a second `TypeId` - // pointing at it is indicative of an alias. Inject the - // alias here. + // previously been defined, meaning we're aliasing a + // prior definition. Some(prev) => (TypeDefKind::Type(*prev), false), // ... or this `TypeId`'s source definition has never @@ -596,9 +614,10 @@ impl WitPackageDecoder<'_> { }); if insert_src { - self.type_src_map.insert(key, Type::Id(ty)); + let prev = self.type_src_map.insert(key, Type::Id(ty)); + assert!(prev.is_none()); } - let prev = self.type_map.insert(id, Type::Id(ty)); + let prev = self.type_map.insert(created, Type::Id(ty)); assert!(prev.is_none()); let prev = interface.types.insert(name.to_string(), ty); assert!(prev.is_none()); diff --git a/crates/wit-component/tests/interfaces/use-chain.wat b/crates/wit-component/tests/interfaces/use-chain.wat new file mode 100644 index 0000000000..4a923636f4 --- /dev/null +++ b/crates/wit-component/tests/interfaces/use-chain.wat @@ -0,0 +1,16 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) u32) + (export (;1;) "a" (type (eq 0))) + (export (;2;) "b" (type (eq 1))) + (export (;3;) "c" (type (eq 2))) + ) + ) + (export (;0;) "foo" "pkg:/use-chain/foo" (instance (type 0))) + ) + ) + (export (;1;) "use-chain" "pkg:/use-chain" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/use-chain.wit b/crates/wit-component/tests/interfaces/use-chain.wit new file mode 100644 index 0000000000..d4c1ca53af --- /dev/null +++ b/crates/wit-component/tests/interfaces/use-chain.wit @@ -0,0 +1,5 @@ +interface foo { + type a = u32 + type b = a + type c = b +} diff --git a/crates/wit-component/tests/interfaces/use-chain.wit.print b/crates/wit-component/tests/interfaces/use-chain.wit.print new file mode 100644 index 0000000000..64b0f45c0b --- /dev/null +++ b/crates/wit-component/tests/interfaces/use-chain.wit.print @@ -0,0 +1,9 @@ +interface foo { + type a = u32 + + type b = a + + type c = b + +} + From 2a10307a89e8ba02015824a1281dc50844c84cad Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 17 Jan 2023 07:23:05 -0800 Subject: [PATCH 28/37] Review comments --- crates/wit-parser/src/ast.rs | 2 +- crates/wit-parser/src/ast/resolve.rs | 32 ++++++---- crates/wit-parser/src/live.rs | 1 - crates/wit-parser/src/resolve.rs | 6 +- .../tests/ui/foreign-deps/deps/corp/saas.wit | 2 + .../deps/fastly/compute-at-edge.wit | 2 - .../wit-parser/tests/ui/foreign-deps/root.wit | 2 +- .../ui/parse-fail/bad-function.wit.result | 2 +- .../ui/parse-fail/bad-function2.wit.result | 2 +- .../invalid-type-reference2.wit.result | 2 +- .../parse-fail/world-same-fields.wit.result | 2 +- .../parse-fail/world-same-fields2.wit.result | 2 +- .../parse-fail/world-same-fields3.wit.result | 2 +- .../parse-fail/world-same-import.wit.result | 2 +- .../world-top-level-func.wit.result | 2 +- .../world-top-level-func2.wit.result | 2 +- src/bin/wasm-tools/component.rs | 58 +++++++++---------- 17 files changed, 65 insertions(+), 58 deletions(-) create mode 100644 crates/wit-parser/tests/ui/foreign-deps/deps/corp/saas.wit delete mode 100644 crates/wit-parser/tests/ui/foreign-deps/deps/fastly/compute-at-edge.wit diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 545da5500e..0495bd1bb0 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -28,7 +28,7 @@ impl<'a> Ast<'a> { Ok(Self { items }) } - fn foreach_path<'b>( + fn for_each_path<'b>( &'b self, mut f: impl FnMut(Option<&'b Id<'a>>, &'b UsePath<'a>, Option<&[UseName<'a>]>) -> Result<()>, ) -> Result<()> { diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 8d816b8c13..6bb5c3a696 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -64,7 +64,7 @@ impl<'a> Resolver<'a> { let prev = self.asts.insert(name, ast); if prev.is_some() { - bail!("document `{name}` defined twice"); + bail!("document `{name}` is defined more than once"); } Ok(()) } @@ -78,7 +78,7 @@ impl<'a> Resolver<'a> { let mut doc_deps = IndexMap::new(); for (name, ast) in self.asts.iter() { let mut deps = Vec::new(); - ast.foreach_path(|_, path, _names| { + ast.for_each_path(|_, path, _names| { let doc = match path { ast::UsePath::Package { doc, iface: _ } => doc, _ => return Ok(()), @@ -143,7 +143,7 @@ impl<'a> Resolver<'a> { /// `resolve_path`. fn populate_foreign_deps(&mut self) { for (_, ast) in self.asts.iter() { - ast.foreach_path(|_, path, names| { + ast.for_each_path(|_, path, names| { let (dep, doc, iface) = match path { ast::UsePath::Dependency { dep, doc, iface } => (dep, doc, iface), _ => return Ok(()), @@ -265,7 +265,7 @@ impl<'a> Resolver<'a> { // Walk all `UsePath` entries in this AST and record dependencies // between interfaces. These are dependencies specified via `use // self...` or via `use pkg.this-doc...`. - ast.foreach_path(|iface, path, _names| { + ast.for_each_path(|iface, path, _names| { // If this import isn't contained within an interface then it's in a // world and it doesn't need to participate in our topo-sort. let iface = match iface { @@ -403,7 +403,10 @@ impl<'a> Resolver<'a> { if imported_interfaces.insert(id, import.name.name).is_some() { return Err(Error { span: import.name.span, - msg: format!("cannot import same interface twice"), + msg: format!( + "interface `{name}` imported more than once", + name = import.name.name + ), } .into()); } @@ -413,7 +416,10 @@ impl<'a> Resolver<'a> { if prev.is_some() { return Err(Error { span: import.name.span, - msg: format!("name imported twice"), + msg: format!( + "name `{name}` imported more than once", + name = import.name.name + ), } .into()); } @@ -425,7 +431,10 @@ impl<'a> Resolver<'a> { if exported_interfaces.insert(id, export.name.name).is_some() { return Err(Error { span: export.name.span, - msg: format!("cannot export same interface twice"), + msg: format!( + "interface `{name}` cannot be exported more than once", + name = export.name.name + ), } .into()); } @@ -435,7 +444,10 @@ impl<'a> Resolver<'a> { if prev.is_some() { return Err(Error { span: export.name.span, - msg: format!("name exported twice"), + msg: format!( + "name `{name}` cannot be exported more than once", + name = export.name.name + ), } .into()); } @@ -708,11 +720,11 @@ impl<'a> Resolver<'a> { Some(InterfaceItem::Type(id)) => *id, Some(InterfaceItem::Func) => bail!(Error { span: name.span, - msg: format!("cannot use a function as a type"), + msg: format!("cannot use function `{name}` as a type", name = name.name), }), None => bail!(Error { span: name.span, - msg: format!("name is not defined"), + msg: format!("name `{name}` is not defined", name = name.name), }), }; TypeDefKind::Type(Type::Id(id)) diff --git a/crates/wit-parser/src/live.rs b/crates/wit-parser/src/live.rs index 3b86ba20e0..e5300dbda1 100644 --- a/crates/wit-parser/src/live.rs +++ b/crates/wit-parser/src/live.rs @@ -56,7 +56,6 @@ impl LiveTypes { for ty in func.results.iter_types() { self.add_type(resolve, ty); } - // .. } pub fn add_type_id(&mut self, resolve: &Resolve, ty: TypeId) { diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index ca8e6d9522..122dfbfdb6 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -63,7 +63,7 @@ impl Resolve { /// interpret `path` as a directory where all `*.wit` files in that /// directory are members of the package. /// - /// Dependenies referenced by the WIT package at `path` will be loaded from + /// Dependencies referenced by the WIT package at `path` will be loaded from /// a `deps/$name` directory under `path` where `$name` is the name of the /// dependency loaded. If `deps/$name` does not exist then an error will be /// returned indicating that the dependency is not defined. All dependencies @@ -76,7 +76,7 @@ impl Resolve { pub fn push_dir(&mut self, path: &Path) -> Result<(PackageId, Vec)> { // Maintain a `to_parse` stack of packages that have yet to be parsed // along with an `enqueued` set of all the prior parsed packages and - // packges enqueued to be parsed. These are then used to fill the + // packages enqueued to be parsed. These are then used to fill the // `packages` map with parsed, but unresolved, packages. The `pkg_deps` // map then tracks dependencies between packages. let mut to_parse = Vec::new(); @@ -521,7 +521,7 @@ impl Remap { Some(i) => *i, // All foreign interfaces are defined first, so the first one // which is defined in a non-foreign document means that all - // futher interfaces will be non-foreign as well. + // further interfaces will be non-foreign as well. None => break, }; diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/corp/saas.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/corp/saas.wit new file mode 100644 index 0000000000..5a2b10d0d0 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps/deps/corp/saas.wit @@ -0,0 +1,2 @@ +default interface saas { +} diff --git a/crates/wit-parser/tests/ui/foreign-deps/deps/fastly/compute-at-edge.wit b/crates/wit-parser/tests/ui/foreign-deps/deps/fastly/compute-at-edge.wit deleted file mode 100644 index 7716e06eef..0000000000 --- a/crates/wit-parser/tests/ui/foreign-deps/deps/fastly/compute-at-edge.wit +++ /dev/null @@ -1,2 +0,0 @@ -default interface compute-at-edge { -} diff --git a/crates/wit-parser/tests/ui/foreign-deps/root.wit b/crates/wit-parser/tests/ui/foreign-deps/root.wit index 79ef1c8a17..d2325e855a 100644 --- a/crates/wit-parser/tests/ui/foreign-deps/root.wit +++ b/crates/wit-parser/tests/ui/foreign-deps/root.wit @@ -7,7 +7,7 @@ world my-world { import wasi-fs: wasi.filesystem import wasi-clocks: wasi.clocks - export fastly: fastly.compute-at-edge + export saas: corp.saas } interface bar { diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result index 756df8495b..a5c199de70 100644 --- a/crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result @@ -1,4 +1,4 @@ -name is not defined +name `nonexistent` is not defined --> tests/ui/parse-fail/bad-function.wit:4:18 | 4 | x: func(param: nonexistent) diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result index 1e4617af52..e2ea68da10 100644 --- a/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result @@ -1,4 +1,4 @@ -name is not defined +name `nonexistent` is not defined --> tests/ui/parse-fail/bad-function2.wit:4:16 | 4 | x: func() -> nonexistent diff --git a/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit.result b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit.result index 22f7a8f361..6637a36a23 100644 --- a/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/invalid-type-reference2.wit.result @@ -1,4 +1,4 @@ -cannot use a function as a type +cannot use function `x` as a type --> tests/ui/parse-fail/invalid-type-reference2.wit:3:16 | 3 | y: func() -> x diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit.result index c7db2c75c8..38f1a05b43 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields.wit.result @@ -1,4 +1,4 @@ -name imported twice +name `foo` imported more than once --> tests/ui/parse-fail/world-same-fields.wit:8:10 | 8 | import foo: self.bar diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields2.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-same-fields2.wit.result index a03be44087..b9ce13bd85 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-same-fields2.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields2.wit.result @@ -1,4 +1,4 @@ -name imported twice +name `foo` imported more than once --> tests/ui/parse-fail/world-same-fields2.wit:5:10 | 5 | import foo: interface {} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit.result index 25482b2183..5496909e55 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-fields3.wit.result @@ -1,4 +1,4 @@ -name exported twice +name `foo` cannot be exported more than once --> tests/ui/parse-fail/world-same-fields3.wit:5:10 | 5 | export foo: interface {} diff --git a/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit.result index 27d6845da6..e512366c6d 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/world-same-import.wit.result @@ -1,4 +1,4 @@ -cannot import same interface twice +interface `bar` imported more than once --> tests/ui/parse-fail/world-same-import.wit:5:10 | 5 | import bar: self.foo diff --git a/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit.result index ed79d83f61..3876a43df6 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func.wit.result @@ -1,4 +1,4 @@ -name imported twice +name `foo` imported more than once --> tests/ui/parse-fail/world-top-level-func.wit:3:10 | 3 | import foo: func() diff --git a/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result index f1ccfb38bc..8e845ca0c3 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result @@ -1,4 +1,4 @@ -name is not defined +name `b` is not defined --> tests/ui/parse-fail/world-top-level-func2.wit:2:23 | 2 | import foo: func(a: b) diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index c9db61e67e..6f79f5f47d 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -103,7 +103,7 @@ impl NewOpts { let bytes = encoder .encode() - .with_context(|| format!("failed to encode a component from module "))?; + .context("failed to encode a component from module")?; self.io.output(Output::Wasm { bytes: &bytes, @@ -259,28 +259,33 @@ pub struct WitOpts { /// When printing a WIT package, the default mode, this option is used to /// indicate which document is printed within the package if more than one /// document is present. - #[clap(short, long)] + #[clap(short, long, conflicts_with = "wasm", conflicts_with = "wat")] document: Option, /// Emit a full WIT package into the specified directory when printing the /// text form. /// /// This is incompatible with `-o`. - #[clap(long)] + #[clap( + long, + conflicts_with = "output", + conflicts_with = "wasm", + conflicts_with = "wat", + conflicts_with = "document" + )] out_dir: Option, - /// Emit the WIT binary format, a WebAssembly component, instead of the WIT - /// text format. - #[clap(short, long)] + /// Emit a WebAssembly binary representation instead of the WIT text format. + #[clap(short, long, conflicts_with = "wat")] wasm: bool, - /// When combined with `--wasm` this will print the WebAssembly text format - /// instead of the WebAssembly binary format. - #[clap(short = 't', long)] + /// Emit a WebAssembly textual representation instead of the WIT text + /// format. + #[clap(short = 't', long, conflicts_with = "wasm")] wat: bool, - /// When specifying `--wasm` the output wasm binary is validated by default, - /// and this can be used to skip that step. + /// Skips the validation performed when using the `--wasm` and `--wat` + /// options. #[clap(long)] skip_validation: bool, } @@ -351,18 +356,9 @@ impl WitOpts { // This interprets all of the output options and performs such a task. match &self.out_dir { Some(dir) => { - if self.output.output_path().is_some() { - bail!("cannot specify both `--out-dir` and `--output`"); - } - if self.wasm { - bail!("cannot specify both `--out-dir` and `--wasm`"); - } - if self.wat { - bail!("cannot specify both `--out-dir` and `--wat`"); - } - if self.document.is_some() { - bail!("cannot specify both `--out-dir` and `--document`"); - } + assert!(self.output.output_path().is_none()); + assert!(!self.wasm && !self.wat); + assert!(self.document.is_none()); let package = match &decoded { DecodedWasm::WitPackage(_, package) => *package, @@ -372,15 +368,17 @@ impl WitOpts { } }; let resolve = decoded.resolve(); - std::fs::create_dir_all(&dir).context(format!("failed to create {dir:?}"))?; + std::fs::create_dir_all(&dir) + .with_context(|| format!("failed to create {dir:?}"))?; for (name, doc) in resolve.packages[package].documents.iter() { let output = DocumentPrinter::default().print(&resolve, *doc)?; let path = dir.join(format!("{name}.wit")); - std::fs::write(&path, output).context(format!("failed to write {path:?}"))?; + std::fs::write(&path, output) + .with_context(|| format!("failed to write {path:?}"))?; } } None => { - if self.wasm { + if self.wasm || self.wat { self.emit_wasm(&decoded)?; } else { self.emit_wit(&decoded)?; @@ -391,11 +389,9 @@ impl WitOpts { } fn emit_wasm(&self, decoded: &DecodedWasm) -> Result<()> { - assert!(self.wasm); + assert!(self.wasm || self.wat); assert!(self.out_dir.is_none()); - if self.document.is_some() { - bail!("cannot specify `--document` with `--wasm`"); - } + assert!(self.document.is_none()); let pkg = match decoded { DecodedWasm::WitPackage(_resolve, pkg) => *pkg, @@ -422,7 +418,7 @@ impl WitOpts { } fn emit_wit(&self, decoded: &DecodedWasm) -> Result<()> { - assert!(!self.wasm); + assert!(!self.wasm && !self.wat); assert!(self.out_dir.is_none()); if self.wat { bail!("the `--wat` option can only be combined with `--wasm`"); From 1d8bb369233cc3a19db25db625cf72ada69ed1dd Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 17 Jan 2023 08:10:30 -0800 Subject: [PATCH 29/37] Decode packages in a wit document in topological order This'll help any embedders iterating over the resulting documents to always iterate in a topological order, as is typically expected. --- crates/wit-component/src/decoding.rs | 40 +++++++++++++++------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index cbf7285c9f..57771b978a 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -89,15 +89,9 @@ impl<'a> ComponentInfo<'a> { fn decode_wit_package(&self, name: &str) -> Result<(Resolve, PackageId)> { assert!(self.is_wit_package()); - let mut resolve = Resolve::default(); - let package = resolve.packages.alloc(Package { - name: name.to_string(), - documents: Default::default(), - url: None, - }); + let resolve = Resolve::default(); let mut decoder = WitPackageDecoder { resolve, - package, info: self, url_to_package: HashMap::default(), type_map: HashMap::new(), @@ -105,16 +99,32 @@ impl<'a> ComponentInfo<'a> { url_to_interface: HashMap::new(), }; + let mut docs = Vec::new(); for (doc, export) in self.exports.iter() { let ty = match self.types.type_at(export.index, false) { Some(types::Type::Component(ty)) => ty, _ => unreachable!(), }; - decoder + let id = decoder .decode_document(doc, ty) .with_context(|| format!("failed to decode document `{doc}`"))?; + docs.push((doc, id)); } - Ok((decoder.resolve, package)) + + let mut resolve = decoder.resolve; + let package = resolve.packages.alloc(Package { + name: name.to_string(), + documents: docs + .iter() + .map(|(name, d)| (name.to_string(), *d)) + .collect(), + url: None, + }); + for (_, doc) in docs.iter() { + resolve.documents[*doc].package = Some(package); + } + + Ok((resolve, package)) } fn decode_component(&self, name: &str) -> Result<(Resolve, WorldId)> { @@ -146,7 +156,6 @@ impl<'a> ComponentInfo<'a> { resolve.documents[doc].default_world = Some(world); let mut decoder = WitPackageDecoder { resolve, - package, info: self, url_to_package: HashMap::default(), type_map: HashMap::new(), @@ -271,7 +280,6 @@ pub fn decode(name: &str, bytes: &[u8]) -> Result { struct WitPackageDecoder<'a> { resolve: Resolve, info: &'a ComponentInfo<'a>, - package: PackageId, url_to_package: HashMap, url_to_interface: HashMap, @@ -290,7 +298,7 @@ struct WitPackageDecoder<'a> { } impl WitPackageDecoder<'_> { - fn decode_document(&mut self, name: &str, ty: &types::ComponentType) -> Result<()> { + fn decode_document(&mut self, name: &str, ty: &types::ComponentType) -> Result { // Process all imports for this document first, where imports are either // importing interfaces from previously defined documents or from remote // packages. Note that the URL must be specified here for these @@ -319,12 +327,8 @@ impl WitPackageDecoder<'_> { worlds: IndexMap::new(), default_interface: None, default_world: None, - package: Some(self.package), + package: None, }); - let prev = self.resolve.packages[self.package] - .documents - .insert(name.to_string(), doc); - assert!(prev.is_none()); for (name, (url, ty)) in ty.exports.iter() { match ty { @@ -361,7 +365,7 @@ impl WitPackageDecoder<'_> { _ => bail!("component export `{name}` is not an instance or component"), } } - Ok(()) + Ok(doc) } fn register_import( From e6c3849138bf9925c01e569d5911c6605c61a4db Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 17 Jan 2023 08:24:40 -0800 Subject: [PATCH 30/37] Correctly decode exported interfaces of foreign packages Previously the exported interface was accidentally not filled in. This commit fills it in in the same manner as imports --- crates/wit-component/src/decoding.rs | 8 ++++- crates/wit-component/tests/interfaces.rs | 30 ++++++++++++------- .../export-other-packages-interface.wat | 19 ++++++++++++ .../deps/the-dep/the-doc.wit | 7 +++++ .../deps/the-dep/the-doc.wit.print | 5 ++++ .../export-other-packages-interface/foo.wit | 3 ++ .../foo.wit.print | 3 ++ .../simple-deps/deps/some-dep/types.wit.print | 5 ++++ .../deps/wasi-logging/backend.wit.print | 9 ++++++ 9 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 crates/wit-component/tests/interfaces/export-other-packages-interface.wat create mode 100644 crates/wit-component/tests/interfaces/export-other-packages-interface/deps/the-dep/the-doc.wit create mode 100644 crates/wit-component/tests/interfaces/export-other-packages-interface/deps/the-dep/the-doc.wit.print create mode 100644 crates/wit-component/tests/interfaces/export-other-packages-interface/foo.wit create mode 100644 crates/wit-component/tests/interfaces/export-other-packages-interface/foo.wit.print create mode 100644 crates/wit-component/tests/interfaces/simple-deps/deps/some-dep/types.wit.print create mode 100644 crates/wit-component/tests/interfaces/wasi-http/deps/wasi-logging/backend.wit.print diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index 57771b978a..3222bfaa7c 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -702,7 +702,13 @@ impl WitPackageDecoder<'_> { _ => unreachable!(), }; let id = match url { - Some(url) => self.extract_url_interface(url)?, + // Note that despite this being an export this is + // calling `register_import`. With a URL this interface + // must have been previously defined so this will + // trigger the logic of either filling in a remotely + // defined interface or connecting items to local + // definitions of our own interface. + Some(url) => self.register_import(url, ty)?, None => self.register_interface(document, None, ty)?, }; WorldItem::Interface(id) diff --git a/crates/wit-component/tests/interfaces.rs b/crates/wit-component/tests/interfaces.rs index 508799367b..76dbbbb8a1 100644 --- a/crates/wit-component/tests/interfaces.rs +++ b/crates/wit-component/tests/interfaces.rs @@ -63,14 +63,22 @@ fn run_test(path: &Path, is_dir: bool) -> Result<()> { let name = &resolve.packages[package].name; let decoded = wit_component::decode(name, &wasm)?; let resolve = decoded.resolve(); - for (name, doc) in resolve.packages[decoded.package()].documents.iter() { - let expected = if is_dir { - path.join(format!("{name}.wit.print")) - } else { - path.with_extension("wit.print") - }; - let output = DocumentPrinter::default().print(&resolve, *doc)?; - assert_output(&expected, &output)?; + + for (id, pkg) in resolve.packages.iter() { + for (name, doc) in pkg.documents.iter() { + let root = if id == decoded.package() { + path.to_path_buf() + } else { + path.join("deps").join(&pkg.name) + }; + let expected = if is_dir { + root.join(format!("{name}.wit.print")) + } else { + root.with_extension("wit.print") + }; + let output = DocumentPrinter::default().print(&resolve, *doc)?; + assert_output(&expected, &output)?; + } } // Finally convert the decoded package to wasm again and make sure it @@ -86,10 +94,12 @@ fn run_test(path: &Path, is_dir: bool) -> Result<()> { fn assert_output(expected: &Path, actual: &str) -> Result<()> { if std::env::var_os("BLESS").is_some() { - fs::write(&expected, actual)?; + fs::write(&expected, actual).with_context(|| format!("failed to write {expected:?}"))?; } else { assert_eq!( - fs::read_to_string(&expected)?.replace("\r\n", "\n"), + fs::read_to_string(&expected) + .with_context(|| format!("failed to read {expected:?}"))? + .replace("\r\n", "\n"), actual, "expectation `{}` did not match actual", expected.display(), diff --git a/crates/wit-component/tests/interfaces/export-other-packages-interface.wat b/crates/wit-component/tests/interfaces/export-other-packages-interface.wat new file mode 100644 index 0000000000..2d8a295812 --- /dev/null +++ b/crates/wit-component/tests/interfaces/export-other-packages-interface.wat @@ -0,0 +1,19 @@ +(component + (type (;0;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) u8) + (export (;1;) "t" (type (eq 0))) + ) + ) + (export (;0;) "foo" "path:/the-dep/the-doc/the-interface" (instance (type 0))) + ) + ) + (export (;0;) "foo" "pkg:/foo/foo" (component (type 0))) + ) + ) + (export (;1;) "foo" "pkg:/foo" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/export-other-packages-interface/deps/the-dep/the-doc.wit b/crates/wit-component/tests/interfaces/export-other-packages-interface/deps/the-dep/the-doc.wit new file mode 100644 index 0000000000..c26eb475e7 --- /dev/null +++ b/crates/wit-component/tests/interfaces/export-other-packages-interface/deps/the-dep/the-doc.wit @@ -0,0 +1,7 @@ +default interface the-interface { + type t = u8 +} + +interface unused-interface { + type u = u32 +} diff --git a/crates/wit-component/tests/interfaces/export-other-packages-interface/deps/the-dep/the-doc.wit.print b/crates/wit-component/tests/interfaces/export-other-packages-interface/deps/the-dep/the-doc.wit.print new file mode 100644 index 0000000000..19e977561b --- /dev/null +++ b/crates/wit-component/tests/interfaces/export-other-packages-interface/deps/the-dep/the-doc.wit.print @@ -0,0 +1,5 @@ +interface the-interface { + type t = u8 + +} + diff --git a/crates/wit-component/tests/interfaces/export-other-packages-interface/foo.wit b/crates/wit-component/tests/interfaces/export-other-packages-interface/foo.wit new file mode 100644 index 0000000000..e4ea8aadb2 --- /dev/null +++ b/crates/wit-component/tests/interfaces/export-other-packages-interface/foo.wit @@ -0,0 +1,3 @@ +world foo { + export foo: the-dep.the-doc +} diff --git a/crates/wit-component/tests/interfaces/export-other-packages-interface/foo.wit.print b/crates/wit-component/tests/interfaces/export-other-packages-interface/foo.wit.print new file mode 100644 index 0000000000..46cee8925d --- /dev/null +++ b/crates/wit-component/tests/interfaces/export-other-packages-interface/foo.wit.print @@ -0,0 +1,3 @@ +world foo { + export foo: the-dep.the-doc.the-interface +} diff --git a/crates/wit-component/tests/interfaces/simple-deps/deps/some-dep/types.wit.print b/crates/wit-component/tests/interfaces/simple-deps/deps/some-dep/types.wit.print new file mode 100644 index 0000000000..0c1b0124fc --- /dev/null +++ b/crates/wit-component/tests/interfaces/simple-deps/deps/some-dep/types.wit.print @@ -0,0 +1,5 @@ +interface types { + type some-type = u8 + +} + diff --git a/crates/wit-component/tests/interfaces/wasi-http/deps/wasi-logging/backend.wit.print b/crates/wit-component/tests/interfaces/wasi-http/deps/wasi-logging/backend.wit.print new file mode 100644 index 0000000000..9833d6f7f5 --- /dev/null +++ b/crates/wit-component/tests/interfaces/wasi-http/deps/wasi-logging/backend.wit.print @@ -0,0 +1,9 @@ +interface backend { + enum level { + info, + debug, + } + + log: func(level: level, msg: string) +} + From 7c961952ab497408ef06a2ea91662f115cd74361 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 17 Jan 2023 10:14:58 -0800 Subject: [PATCH 31/37] Update resolution of foreign deps This commit updates how foreign deps are resolved during `Resolve::push` to remove the restriction that the `unresolved.foreign_deps` field lists documents in the same order as they are within the main arena. This will be difficult to maintain during decoding and otherwise isn't all that necessary. Additionally this commit adds further asserts that an unresolved package's items are indeed paritioned into remote and local parts since much of the indexing code here relies on that. --- crates/wit-parser/src/resolve.rs | 50 +++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 122dfbfdb6..41d72f88a7 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -496,22 +496,40 @@ impl Remap { // First, connect all references to foreign documents to actual // documents within `resolve`, building up the initial entries of // the `self.documents` mapping. + let mut document_to_package = HashMap::new(); for (i, (pkg, docs)) in unresolved.foreign_deps.iter().enumerate() { + for (doc, unresolved_doc_id) in docs { + let prev = document_to_package.insert( + *unresolved_doc_id, + (pkg, doc, unresolved.foreign_dep_spans[i]), + ); + assert!(prev.is_none()); + } + } + for (unresolved_doc_id, _doc) in unresolved.documents.iter() { + let (pkg, doc, span) = match document_to_package.get(&unresolved_doc_id) { + Some(items) => *items, + None => break, + }; let pkgid = *deps.get(pkg).ok_or_else(|| Error { - span: unresolved.foreign_dep_spans[i], + span, msg: format!("no package dependency specified for `{pkg}`"), })?; let package = &resolve.packages[pkgid]; - for (doc, unresolved_doc_id) in docs { - let span = unresolved.document_spans[unresolved_doc_id.index()]; - let docid = *package.documents.get(doc).ok_or_else(|| Error { - span, - msg: format!("package `{pkg}` does not define document `{doc}`"), - })?; - assert_eq!(self.documents.len(), unresolved_doc_id.index()); - self.documents.push(docid); - } + let docid = *package.documents.get(doc).ok_or_else(|| Error { + span: unresolved.document_spans[unresolved_doc_id.index()], + msg: format!("package `{pkg}` does not define document `{doc}`"), + })?; + + assert_eq!(self.documents.len(), unresolved_doc_id.index()); + self.documents.push(docid); + } + for (id, _) in unresolved.documents.iter().skip(self.documents.len()) { + assert!( + document_to_package.get(&id).is_none(), + "found foreign document after local documents" + ); } // Next, for all documents that are referenced in this `Resolve` @@ -544,6 +562,12 @@ impl Remap { self.interfaces.push(iface_id); } + for (_, iface) in unresolved.interfaces.iter().skip(self.interfaces.len()) { + if self.documents.get(iface.document.index()).is_some() { + panic!("found foreign interface after local interfaces"); + } + } + // And finally iterate over all foreign-defined types and determine // what they map to. for (unresolved_type_id, unresolved_ty) in unresolved.types.iter() { @@ -572,6 +596,12 @@ impl Remap { self.types.push(type_id); } + for (_, ty) in unresolved.types.iter().skip(self.types.len()) { + if let TypeDefKind::Unknown = ty.kind { + panic!("unknown type after defined type"); + } + } + Ok(()) } From ded0221c2b1cc3f6b1e48950bc4bfb0294780712 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 17 Jan 2023 10:27:09 -0800 Subject: [PATCH 32/37] Preserve type reexports in foreign documents Fix a case in decoding where a type reexport accidentally redefined the type. --- crates/wit-component/src/decoding.rs | 25 ++++++++++--------- .../interfaces/preserve-foreign-reexport.wat | 20 +++++++++++++++ .../deps/my-dep/my-doc.wit | 4 +++ .../deps/my-dep/my-doc.wit.print | 8 ++++++ .../preserve-foreign-reexport/foo.wit | 3 +++ .../preserve-foreign-reexport/foo.wit.print | 3 +++ 6 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 crates/wit-component/tests/interfaces/preserve-foreign-reexport.wat create mode 100644 crates/wit-component/tests/interfaces/preserve-foreign-reexport/deps/my-dep/my-doc.wit create mode 100644 crates/wit-component/tests/interfaces/preserve-foreign-reexport/deps/my-dep/my-doc.wit.print create mode 100644 crates/wit-component/tests/interfaces/preserve-foreign-reexport/foo.wit create mode 100644 crates/wit-component/tests/interfaces/preserve-foreign-reexport/foo.wit.print diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index 3222bfaa7c..5baefea088 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -284,7 +284,7 @@ struct WitPackageDecoder<'a> { url_to_interface: HashMap, /// A map from a type id to what it's been translated to. - type_map: HashMap, + type_map: HashMap, /// A second map, similar to `type_map`, which is keyed off a pointer hash /// instead of `TypeId`. @@ -294,7 +294,7 @@ struct WitPackageDecoder<'a> { /// structure, so the second layer of map here ensures that types are /// only defined once and the second `TypeId` referring to a type will end /// up as an alias and/or import. - type_src_map: HashMap, Type>, + type_src_map: HashMap, TypeId>, } impl WitPackageDecoder<'_> { @@ -419,7 +419,10 @@ impl WitPackageDecoder<'_> { if url.scheme() == "pkg" { bail!("instance type export `{name}` not defined in interface"); } - let kind = self.convert_defined(def)?; + let kind = match self.type_map.get(&referenced).copied() { + Some(id) => TypeDefKind::Type(Type::Id(id)), + None => self.convert_defined(def)?, + }; let id = self.resolve.types.alloc(TypeDef { name: Some(name.to_string()), kind, @@ -437,11 +440,9 @@ impl WitPackageDecoder<'_> { // Register the `types::TypeId` with our resolve `TypeId` // for ensuring type information remains correct throughout // decoding. - let prev = self.type_map.insert(created, Type::Id(id)); + let prev = self.type_map.insert(created, id); assert!(prev.is_none()); - self.type_src_map - .entry(PtrHash(def)) - .or_insert(Type::Id(id)); + self.type_src_map.entry(PtrHash(def)).or_insert(id); } // This has similar logic to types above where we lazily fill in @@ -599,7 +600,7 @@ impl WitPackageDecoder<'_> { // If this `TypeId` points to a type which has // previously been defined, meaning we're aliasing a // prior definition. - Some(prev) => (TypeDefKind::Type(*prev), false), + Some(prev) => (TypeDefKind::Type(Type::Id(*prev)), false), // ... or this `TypeId`'s source definition has never // been seen before, so declare the full type. @@ -618,10 +619,10 @@ impl WitPackageDecoder<'_> { }); if insert_src { - let prev = self.type_src_map.insert(key, Type::Id(ty)); + let prev = self.type_src_map.insert(key, ty); assert!(prev.is_none()); } - let prev = self.type_map.insert(created, Type::Id(ty)); + let prev = self.type_map.insert(created, ty); assert!(prev.is_none()); let prev = interface.types.insert(name.to_string(), ty); assert!(prev.is_none()); @@ -768,7 +769,7 @@ impl WitPackageDecoder<'_> { // Don't create duplicate types for anything previously created. if let Some(ret) = self.type_map.get(&id) { - return Ok(*ret); + return Ok(Type::Id(*ret)); } // Otherwise create a new `TypeDef` without a name since this is an @@ -805,7 +806,7 @@ impl WitPackageDecoder<'_> { owner: TypeOwner::None, kind, }); - let prev = self.type_map.insert(id, Type::Id(ty)); + let prev = self.type_map.insert(id, ty); assert!(prev.is_none()); Ok(Type::Id(ty)) } diff --git a/crates/wit-component/tests/interfaces/preserve-foreign-reexport.wat b/crates/wit-component/tests/interfaces/preserve-foreign-reexport.wat new file mode 100644 index 0000000000..30f021ad37 --- /dev/null +++ b/crates/wit-component/tests/interfaces/preserve-foreign-reexport.wat @@ -0,0 +1,20 @@ +(component + (type (;0;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (record)) + (export (;1;) "foo" (type (eq 0))) + (export (;2;) "bar" (type (eq 1))) + ) + ) + (export (;0;) "foo" "path:/my-dep/my-doc/my-interface" (instance (type 0))) + ) + ) + (export (;0;) "foo" "pkg:/foo/foo" (component (type 0))) + ) + ) + (export (;1;) "foo" "pkg:/foo" (type 0)) +) \ No newline at end of file diff --git a/crates/wit-component/tests/interfaces/preserve-foreign-reexport/deps/my-dep/my-doc.wit b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/deps/my-dep/my-doc.wit new file mode 100644 index 0000000000..2eeed9cd16 --- /dev/null +++ b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/deps/my-dep/my-doc.wit @@ -0,0 +1,4 @@ +default interface my-interface { + record foo {} + type bar = foo +} diff --git a/crates/wit-component/tests/interfaces/preserve-foreign-reexport/deps/my-dep/my-doc.wit.print b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/deps/my-dep/my-doc.wit.print new file mode 100644 index 0000000000..0c86fc6cc5 --- /dev/null +++ b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/deps/my-dep/my-doc.wit.print @@ -0,0 +1,8 @@ +interface my-interface { + record foo { + } + + type bar = foo + +} + diff --git a/crates/wit-component/tests/interfaces/preserve-foreign-reexport/foo.wit b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/foo.wit new file mode 100644 index 0000000000..ec472fe210 --- /dev/null +++ b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/foo.wit @@ -0,0 +1,3 @@ +world foo { + export foo: my-dep.my-doc +} diff --git a/crates/wit-component/tests/interfaces/preserve-foreign-reexport/foo.wit.print b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/foo.wit.print new file mode 100644 index 0000000000..f9406dd11a --- /dev/null +++ b/crates/wit-component/tests/interfaces/preserve-foreign-reexport/foo.wit.print @@ -0,0 +1,3 @@ +world foo { + export foo: my-dep.my-doc.my-interface +} From 257be093d6e11710adbac59a8879b201aa044d17 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 17 Jan 2023 10:28:26 -0800 Subject: [PATCH 33/37] Update the roundtrip-wit fuzzer This commit is an overhaul and reimplementation for the `roundtrip-wit` fuzzer in the `wit-component` package. This updates the AST generation to instead generate a textual format with a string which is then parsed. Various properties about round-tripping are then tested as well. about this document. Additionally this lifts the fuzzer into the top-level directory to run on oss-fuzz since this has run long enough locally without bugs and I think it would be good to suss out bugs sooner rather than later via oss-fuzz. --- Cargo.toml | 1 - crates/wit-component/fuzz/.gitignore | 2 - crates/wit-component/fuzz/Cargo.toml | 23 - .../fuzz/fuzz_targets/roundtrip-wit.rs | 502 ------------ fuzz/Cargo.toml | 8 + fuzz/fuzz_targets/roundtrip-wit.rs | 716 ++++++++++++++++++ 6 files changed, 724 insertions(+), 528 deletions(-) delete mode 100644 crates/wit-component/fuzz/.gitignore delete mode 100644 crates/wit-component/fuzz/Cargo.toml delete mode 100644 crates/wit-component/fuzz/fuzz_targets/roundtrip-wit.rs create mode 100644 fuzz/fuzz_targets/roundtrip-wit.rs diff --git a/Cargo.toml b/Cargo.toml index 604d477edf..dcc797b182 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ members = [ 'crates/fuzz-stats', 'crates/wasm-mutate-stats', 'fuzz', - 'crates/wit-component/fuzz', 'crates/wit-parser/fuzz', ] diff --git a/crates/wit-component/fuzz/.gitignore b/crates/wit-component/fuzz/.gitignore deleted file mode 100644 index ff2738685d..0000000000 --- a/crates/wit-component/fuzz/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -artifacts -corpus diff --git a/crates/wit-component/fuzz/Cargo.toml b/crates/wit-component/fuzz/Cargo.toml deleted file mode 100644 index 59be356b2a..0000000000 --- a/crates/wit-component/fuzz/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "wit-component-fuzz" -version = "0.0.1" -publish = false -edition.workspace = true - -[package.metadata] -cargo-fuzz = true - -[dependencies] -arbitrary = { workspace = true, features = ['derive'] } -env_logger = { workspace = true } -libfuzzer-sys = { workspace = true } -log = { workspace = true } -wasmprinter = { workspace = true } -wit-component = { workspace = true } -wit-parser = { workspace = true } - -[[bin]] -name = "roundtrip-wit" -path = "fuzz_targets/roundtrip-wit.rs" -test = false -doc = false diff --git a/crates/wit-component/fuzz/fuzz_targets/roundtrip-wit.rs b/crates/wit-component/fuzz/fuzz_targets/roundtrip-wit.rs deleted file mode 100644 index 46619c910d..0000000000 --- a/crates/wit-component/fuzz/fuzz_targets/roundtrip-wit.rs +++ /dev/null @@ -1,502 +0,0 @@ -#![no_main] - -use libfuzzer_sys::fuzz_target; -use std::path::Path; -use wit_component::*; - -fuzz_target!(|data: &[u8]| { - drop(env_logger::try_init()); - - let mut u = arbitrary::Unstructured::new(data); - let doc = match generate::document(&mut u) { - Ok(doc) => doc, - Err(_) => return, - }; - let text = DocumentPrinter::default().print(&doc).unwrap(); - write_file("doc.wit", &text); - let world = match doc.default_world() { - Ok(world) => world, - Err(_) => return, - }; - - let types_only_binary = ComponentEncoder::default() - .validate(true) - .types_only(true) - .document(doc.clone(), StringEncoding::UTF8) - .unwrap() - .encode() - .unwrap(); - write_file("doc.types-only.wasm", &types_only_binary); - let (types_only_doc, _) = decode_world(&doc.worlds[world].name, &types_only_binary).unwrap(); - let text_from_types_only = DocumentPrinter::default().print(&types_only_doc).unwrap(); - write_file("doc.types-only.wit", &text_from_types_only); - - let dummy = dummy::dummy_module(&doc); - write_file("doc.dummy.wasm", &dummy); - let normal_binary = ComponentEncoder::default() - .validate(true) - .module(&dummy) - .unwrap() - .document(doc.clone(), StringEncoding::UTF8) - .unwrap() - .encode() - .unwrap(); - write_file("doc.normal.wasm", &normal_binary); - let (normal_doc, _) = decode_world(&doc.worlds[world].name, &normal_binary).unwrap(); - let text_from_normal = DocumentPrinter::default().print(&normal_doc).unwrap(); - write_file("doc.normal.wit", &text_from_normal); - - if text_from_normal != text_from_types_only { - panic!("text not equal"); - } -}); - -fn write_file(path: &str, contents: impl AsRef<[u8]>) { - if !log::log_enabled!(log::Level::Debug) { - return; - } - log::debug!("writing file {path}"); - let contents = contents.as_ref(); - let path = Path::new(path); - std::fs::write(path, contents).unwrap(); - if path.extension().and_then(|s| s.to_str()) == Some("wasm") { - let path = path.with_extension("wat"); - log::debug!("writing file {}", path.display()); - std::fs::write(path, wasmprinter::print_bytes(&contents).unwrap()).unwrap(); - } -} - -mod generate { - use arbitrary::{Arbitrary, Result, Unstructured}; - use std::collections::HashSet; - use std::str; - use wit_parser::*; - - #[derive(Default)] - struct Generator { - doc: Document, - type_sizes: Vec, - unique_names: HashSet, - types_in_interface: Vec, - named_types: Vec, - } - - pub fn document(u: &mut Unstructured<'_>) -> Result { - let mut gen = Generator::default(); - gen.gen(u)?; - Ok(gen.doc) - } - - impl Generator { - fn gen(&mut self, u: &mut Unstructured<'_>) -> Result<()> { - #[derive(Arbitrary)] - enum Generate { - World, - Interface, - Done, - } - - while !u.is_empty() { - match u.arbitrary()? { - Generate::World => { - self.gen_world(u)?; - } - Generate::Interface => { - self.gen_interface(u)?; - } - Generate::Done => break, - } - } - Ok(()) - } - - fn gen_world(&mut self, u: &mut Unstructured<'_>) -> Result { - let mut world = World::default(); - world.name = self.gen_name(u)?; - - let mut interfaces = self - .doc - .interfaces - .iter() - .map(|(id, _)| id) - .collect::>(); - - #[derive(Arbitrary)] - enum Generate { - Import, - // TODO: this is buggy right now due to use-through-export not - // being implemented. Should fix and re-enable this. - // Export, - } - - let mut imports = Vec::new(); - let mut exports = Vec::new(); - while interfaces.len() > 0 && u.arbitrary()? { - let dst = match u.arbitrary()? { - Generate::Import => &mut imports, - // Generate::Export => &mut exports, - }; - if dst.len() > 10 { - continue; - } - let i = u.int_in_range(0..=interfaces.len() - 1)?; - let interface = interfaces.swap_remove(i); - dst.push(interface); - } - if interfaces.len() > 0 && u.arbitrary()? { - world.default = Some(*u.choose(&interfaces)?); - } - - // TODO: most of this probably doesn't make sense. One hope is that - // this all goes away and can be replaced with the text-based - // resolution pass by generating a text file instead of an AST - // directly. Unsure how to best clean this up. - let mut visited = HashSet::new(); - let mut import_order = Vec::new(); - for id in imports { - self.topo_visit(id, &mut visited, &mut import_order); - } - for id in exports.iter() { - self.topo_visit(*id, &mut visited, &mut import_order); - import_order.pop(); - } - if let Some(default) = world.default { - self.topo_visit(default, &mut visited, &mut import_order); - import_order.pop(); - } - - for i in import_order { - world.imports.insert(self.gen_unique_name(u)?, i); - } - for e in exports { - world.exports.insert(self.gen_unique_name(u)?, e); - } - - self.unique_names.clear(); - return Ok(self.doc.worlds.alloc(world)); - } - - fn topo_visit( - &self, - id: InterfaceId, - visited: &mut HashSet, - order: &mut Vec, - ) { - if !visited.insert(id) { - return; - } - let interface = &self.doc.interfaces[id]; - for ty in interface.types.iter() { - let other_id = match self.doc.types[*ty].kind { - TypeDefKind::Type(Type::Id(id)) => id, - _ => continue, - }; - let other_interface = match self.doc.types[other_id].interface { - Some(i) => i, - None => continue, - }; - if other_interface == id { - continue; - } - self.topo_visit(other_interface, visited, order); - } - order.push(id); - } - - fn gen_interface(&mut self, u: &mut Unstructured<'_>) -> Result { - let mut iface = Interface::default(); - iface.name = self.gen_name(u)?; - - #[derive(Arbitrary)] - enum Generate { - Type, - Function, - } - - while u.arbitrary()? { - match u.arbitrary()? { - Generate::Type => { - let (size, typedef) = self.gen_typedef(u)?; - let id = self.doc.types.alloc(typedef); - self.type_sizes.push(size); - if self.doc.types[id].name.is_some() { - iface.types.push(id); - self.named_types.push(id); - } - self.types_in_interface.push(id); - } - Generate::Function => { - let f = self.gen_func(u)?; - iface.functions.push(f); - } - } - } - - self.types_in_interface.clear(); - Ok(self.doc.interfaces.alloc(iface)) - } - - fn gen_typedef(&mut self, u: &mut Unstructured<'_>) -> Result<(usize, TypeDef)> { - const MAX_PARTS: usize = 5; - - #[derive(Arbitrary)] - pub enum Kind { - Record, - Tuple, - Flags, - Variant, - Enum, - Option, - Result, - Union, - List, - Type, - } - - let mut size = 1; - let kind = match u.arbitrary()? { - Kind::Record => TypeDefKind::Record(Record { - fields: (0..u.int_in_range(0..=MAX_PARTS)?) - .map(|_| { - Ok(Field { - docs: Docs::default(), - name: self.gen_unique_name(u)?, - ty: self.gen_type(u, &mut size)?, - }) - }) - .collect::>()?, - }), - Kind::Variant => TypeDefKind::Variant(Variant { - cases: (0..u.int_in_range(1..=MAX_PARTS)?) - .map(|_| { - Ok(Case { - docs: Docs::default(), - name: self.gen_unique_name(u)?, - ty: if u.arbitrary()? { - Some(self.gen_type(u, &mut size)?) - } else { - None - }, - }) - }) - .collect::>()?, - }), - Kind::Tuple => TypeDefKind::Tuple(Tuple { - types: (0..u.int_in_range(0..=MAX_PARTS)?) - .map(|_| self.gen_type(u, &mut size)) - .collect::>()?, - }), - Kind::Union => TypeDefKind::Union(Union { - cases: (0..u.int_in_range(1..=MAX_PARTS)?) - .map(|_| { - Ok(UnionCase { - docs: Docs::default(), - ty: self.gen_type(u, &mut size)?, - }) - }) - .collect::>()?, - }), - Kind::List => TypeDefKind::List(self.gen_type(u, &mut size)?), - Kind::Type => TypeDefKind::Type(self.gen_type_allow_use(u, true, &mut size)?), - Kind::Option => TypeDefKind::Option(self.gen_type(u, &mut size)?), - Kind::Result => TypeDefKind::Result(Result_ { - ok: if u.arbitrary()? { - Some(self.gen_type(u, &mut size)?) - } else { - None - }, - err: if u.arbitrary()? { - Some(self.gen_type(u, &mut size)?) - } else { - None - }, - }), - Kind::Flags => TypeDefKind::Flags(Flags { - flags: (0..u.int_in_range(0..=MAX_PARTS)?) - .map(|_| { - Ok(Flag { - name: self.gen_unique_name(u)?, - docs: Docs::default(), - }) - }) - .collect::>()?, - }), - Kind::Enum => TypeDefKind::Enum(Enum { - cases: (0..u.int_in_range(1..=MAX_PARTS)?) - .map(|_| { - Ok(EnumCase { - name: self.gen_unique_name(u)?, - docs: Docs::default(), - }) - }) - .collect::>()?, - }), - }; - - // Determine if this kind is allowed to be anonymous or not, and if - // it can be optionally annotate it with an interface and a name. - // Note that non-anonymous types must have an interface and a name. - let can_be_anonymous = match kind { - TypeDefKind::Type(_) - | TypeDefKind::Record(_) - | TypeDefKind::Enum(_) - | TypeDefKind::Flags(_) - | TypeDefKind::Variant(_) - | TypeDefKind::Future(_) - | TypeDefKind::Stream(_) - | TypeDefKind::Union(_) => false, - TypeDefKind::Tuple(_) - | TypeDefKind::Option(_) - | TypeDefKind::List(_) - | TypeDefKind::Result(_) => true, - }; - - // Choose a name, either because we are forced to or because the - // fuzz input says we should name this. - let name = if !can_be_anonymous || u.arbitrary()? { - Some(self.gen_unique_name(u)?) - } else { - None - }; - - // Determine if the `interface` field will be set. This is required - // for named or non-anonymous types since they're attached to an - // interface. Otherwise let the fuzz input determine that. - let interface = if name.is_some() || !can_be_anonymous || u.arbitrary()? { - Some(self.doc.interfaces.next_id()) - } else { - None - }; - - Ok(( - size, - TypeDef { - docs: Docs::default(), - kind, - name, - interface, - }, - )) - } - - fn gen_type(&mut self, u: &mut Unstructured<'_>, size: &mut usize) -> Result { - self.gen_type_allow_use(u, false, size) - } - - fn gen_type_allow_use( - &mut self, - u: &mut Unstructured<'_>, - allow_use: bool, - size: &mut usize, - ) -> Result { - const MAX_SIZE: usize = 100; - - #[derive(Arbitrary)] - enum Kind { - Bool, - U8, - U16, - U32, - U64, - S8, - S16, - S32, - S64, - Float32, - Float64, - Char, - String, - Id, - } - - *size += 1; - loop { - break match u.arbitrary()? { - Kind::Bool => Ok(Type::Bool), - Kind::U8 => Ok(Type::U8), - Kind::S8 => Ok(Type::S8), - Kind::U16 => Ok(Type::U16), - Kind::S16 => Ok(Type::S16), - Kind::U32 => Ok(Type::U32), - Kind::S32 => Ok(Type::S32), - Kind::U64 => Ok(Type::U64), - Kind::S64 => Ok(Type::S64), - Kind::Float32 => Ok(Type::Float32), - Kind::Float64 => Ok(Type::Float64), - Kind::Char => Ok(Type::Char), - Kind::String => Ok(Type::String), - Kind::Id => { - let types = if allow_use { - &self.named_types - } else { - &self.types_in_interface - }; - if types.is_empty() { - continue; - } - let id = *u.choose(&types)?; - if *size + self.type_sizes[id.index()] > MAX_SIZE { - continue; - } - *size += self.type_sizes[id.index()]; - Ok(Type::Id(id)) - } - }; - } - } - - fn gen_func(&mut self, u: &mut Unstructured<'_>) -> Result { - Ok(Function { - docs: Docs::default(), - name: self.gen_unique_name(u)?, - kind: FunctionKind::Freestanding, - params: self.gen_params(u)?, - results: if u.arbitrary()? { - Results::Anon(self.gen_type(u, &mut 1)?) - } else { - Results::Named(self.gen_params(u)?) - }, - }) - } - - fn gen_params(&mut self, u: &mut Unstructured<'_>) -> Result> { - (0..u.int_in_range(0..=5)?) - .map(|_| Ok((self.gen_unique_name(u)?, self.gen_type(u, &mut 1)?))) - .collect() - } - - fn gen_name(&self, u: &mut Unstructured<'_>) -> Result { - let size = u.arbitrary_len::()?; - let size = std::cmp::min(size, 20); - let name = match str::from_utf8(u.peek_bytes(size).unwrap()) { - Ok(s) => { - u.bytes(size).unwrap(); - s.to_string() - } - Err(e) => { - let i = e.valid_up_to(); - let valid = u.bytes(i).unwrap(); - str::from_utf8(valid).unwrap().to_string() - } - }; - let name = name - .chars() - .map(|x| if x.is_ascii_lowercase() { x } else { 'x' }) - .collect::(); - Ok(if name.is_empty() { - "name".to_string() - } else { - name - }) - } - - fn gen_unique_name(&mut self, u: &mut Unstructured<'_>) -> Result { - use std::fmt::Write; - let mut name = self.gen_name(u)?; - while !self.unique_names.insert(name.clone()) { - write!(&mut name, "{}", self.unique_names.len()).unwrap(); - } - Ok(name) - } - } -} diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index c1d9e28a7e..8c44d97d75 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,6 +21,8 @@ wasmprinter = { path = "../crates/wasmprinter" } wasmtime = { workspace = true, optional = true } wast = { path = "../crates/wast" } wat = { path = "../crates/wat" } +wit-parser = { path = "../crates/wit-parser" } +wit-component = { path = "../crates/wit-component" } [lib] test = false @@ -75,3 +77,9 @@ name = "no-traps" path = "fuzz_targets/no-traps.rs" test = false doc = false + +[[bin]] +name = "roundtrip-wit" +path = "fuzz_targets/roundtrip-wit.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/roundtrip-wit.rs b/fuzz/fuzz_targets/roundtrip-wit.rs new file mode 100644 index 0000000000..f12d5e50fd --- /dev/null +++ b/fuzz/fuzz_targets/roundtrip-wit.rs @@ -0,0 +1,716 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use std::collections::HashMap; +use std::path::Path; +use wit_component::*; +use wit_parser::{PackageId, Resolve, SourceMap}; + +fuzz_target!(|data: &[u8]| { + drop(env_logger::try_init()); + + let mut u = arbitrary::Unstructured::new(data); + let pkgs = match generate::packages(&mut u) { + Ok(doc) => doc, + Err(_) => return, + }; + let mut resolve = Resolve::default(); + let mut deps = HashMap::new(); + let mut last = None; + for pkg in pkgs { + let mut unresolved = pkg.sources.parse(&pkg.name, None).unwrap(); + unresolved.url = Some(format!("my-scheme:/{}", pkg.name)); + let id = resolve.push(unresolved, &deps).unwrap(); + let prev = deps.insert(pkg.name, id); + assert!(prev.is_none()); + last = Some(id); + } + let pkg = last.unwrap(); + let wasm = roundtrip_through_printing("doc1", &resolve, pkg); + + let name = &resolve.packages[pkg].name; + let (resolve2, pkg2) = match wit_component::decode(&name, &wasm).unwrap() { + DecodedWasm::WitPackage(resolve, pkg) => (resolve, pkg), + DecodedWasm::Component(..) => unreachable!(), + }; + + let wasm2 = roundtrip_through_printing("doc2", &resolve2, pkg2); + + if wasm != wasm2 { + panic!("roundtrip wasm didn't match"); + } +}); + +fn roundtrip_through_printing(file: &str, resolve: &Resolve, pkg: PackageId) -> Vec { + // Encode `resolve` to wasm as the baseline expectation + let wasm = wit_component::encode(resolve, pkg).unwrap(); + write_file(&format!("{file}.wasm"), &wasm); + wasmparser::Validator::new_with_features(wasmparser::WasmFeatures { + component_model: true, + ..Default::default() + }) + .validate_all(&wasm) + .unwrap(); + + // For all packages in `resolve` print them all to a string, then re-parse + // them and insert them into a `new_resolve`. + let mut new_deps = HashMap::new(); + let mut new_resolve = Resolve::default(); + let mut last = None; + for (_, pkg) in resolve.packages.iter() { + let mut map = SourceMap::new(); + let pkg_name = &pkg.name; + for (name, doc) in pkg.documents.iter() { + let doc = DocumentPrinter::default().print(resolve, *doc).unwrap(); + write_file(&format!("{file}-{pkg_name}-{name}.wit"), &doc); + map.push(format!("{name}.wit").as_ref(), &name, doc); + } + let mut unresolved = map.parse(&pkg.name, None).unwrap(); + unresolved.url = pkg.url.clone(); + let id = new_resolve.push(unresolved, &new_deps).unwrap(); + new_deps.insert(pkg.name.clone(), id); + last = Some(id); + } + + // Finally encode the `new_resolve` which should be the exact same as + // before. + let wasm2 = wit_component::encode(&new_resolve, last.unwrap()).unwrap(); + write_file(&format!("{file}-reencoded.wasm"), &wasm2); + if wasm != wasm2 { + panic!("failed to roundtrip through text printing"); + } + + wasm +} + +fn write_file(path: &str, contents: impl AsRef<[u8]>) { + if !log::log_enabled!(log::Level::Debug) { + return; + } + log::debug!("writing file {path}"); + let contents = contents.as_ref(); + let path = Path::new(path); + std::fs::write(path, contents).unwrap(); + if path.extension().and_then(|s| s.to_str()) == Some("wasm") { + let path = path.with_extension("wat"); + log::debug!("writing file {}", path.display()); + std::fs::write(path, wasmprinter::print_bytes(&contents).unwrap()).unwrap(); + } +} + +mod generate { + use arbitrary::{Arbitrary, Result, Unstructured}; + use std::collections::HashSet; + use std::mem; + use std::str; + use wit_parser::*; + + const MAX_PARTS: usize = 5; + + #[derive(Default)] + struct Generator { + packages: PackageList, + documents: DocumentList, + interfaces: InterfaceList, + next_interface_id: u32, + } + + type TypeList = Vec<(String, usize)>; + type InterfaceList = Vec<(String, u32, bool, TypeList)>; + type DocumentList = Vec<(String, InterfaceList)>; + type PackageList = Vec<(String, DocumentList)>; + + struct InterfaceGenerator<'a> { + gen: &'a Generator, + unique_names: HashSet, + types_in_interface: Vec<(String, usize)>, + } + + pub struct Package { + pub name: String, + pub sources: SourceMap, + } + + pub fn packages(u: &mut Unstructured<'_>) -> Result> { + Generator::default().gen(u) + } + + impl Generator { + fn gen(&mut self, u: &mut Unstructured<'_>) -> Result> { + let mut packages = Vec::new(); + let mut names = HashSet::new(); + while packages.is_empty() || u.arbitrary()? { + let name = gen_unique_name(u, &mut names)?; + let (sources, documents) = self.gen_package(&name, u)?; + self.packages.push((name.clone(), documents)); + packages.push(Package { name, sources }); + } + Ok(packages) + } + + fn gen_package( + &mut self, + pkg: &str, + u: &mut Unstructured<'_>, + ) -> Result<(SourceMap, DocumentList)> { + let mut map = SourceMap::new(); + let mut count = 0; + let mut names = HashSet::new(); + + while count == 0 || u.arbitrary()? { + let name = gen_unique_name(u, &mut names)?; + let (doc, interfaces) = self.gen_document(u)?; + super::write_file(format!("orig-{pkg}-{name}.wit").as_ref(), &doc); + map.push(format!("{name}.wit").as_ref(), &name, doc); + count += 1; + self.documents.push((name, interfaces)); + } + + Ok((map, mem::take(&mut self.documents))) + } + + fn gen_document(&mut self, u: &mut Unstructured<'_>) -> Result<(String, InterfaceList)> { + #[derive(Arbitrary)] + enum Generate { + World, + Interface, + Done, + } + + let mut pieces = Vec::new(); + let mut has_default_interface = false; + let mut has_default_world = false; + let mut names = HashSet::new(); + while !u.is_empty() { + let name = gen_unique_name(u, &mut names)?; + match u.arbitrary()? { + Generate::World => { + pieces.push(self.gen_world(u, &name, &mut has_default_world)?) + } + Generate::Interface => { + let id = self.next_interface_id; + self.next_interface_id += 1; + let (src, types) = + self.gen_interface(u, Some(&name), &mut has_default_interface)?; + self.interfaces + .push((name, id, src.starts_with("default"), types)); + pieces.push(src); + } + Generate::Done => break, + } + } + shuffle(u, &mut pieces)?; + let mut ret = String::new(); + for piece in pieces { + ret.push_str(&piece); + ret.push_str("\n\n"); + } + Ok((ret, mem::take(&mut self.interfaces))) + } + + fn gen_world( + &mut self, + u: &mut Unstructured<'_>, + name: &str, + has_default: &mut bool, + ) -> Result { + let mut ret = String::new(); + if !*has_default && u.arbitrary()? { + *has_default = true; + ret.push_str("default "); + } + ret.push_str("world %"); + ret.push_str(name); + ret.push_str(" {\n"); + + #[derive(Arbitrary)] + enum Direction { + Import, + Export, + } + + #[derive(Arbitrary)] + enum ItemKind { + Func, + Interface, + AnonInterface, + } + + let mut parts = Vec::new(); + let mut imports = HashSet::new(); + let mut exports = HashSet::new(); + let mut imported_interfaces = HashSet::new(); + let mut exported_interfaces = HashSet::new(); + + while !u.is_empty() && u.arbitrary()? { + let mut part = String::new(); + let (desc, names, interfaces) = match u.arbitrary()? { + Direction::Import => ("import", &mut imports, &mut imported_interfaces), + Direction::Export => ("export", &mut exports, &mut exported_interfaces), + }; + part.push_str(desc); + part.push_str(" %"); + part.push_str(&gen_unique_name(u, names)?); + part.push_str(": "); + + match u.arbitrary()? { + ItemKind::Func => { + InterfaceGenerator::new(self).gen_func_sig(u, &mut part)?; + } + ItemKind::Interface => { + let id = match self.gen_path(u, &mut part)? { + Some((id, _types)) => id, + // If an interface couldn't be chosen or wasn't + // chosen then skip this import. A unique name was + // selecteed above but we just sort of leave that + // floating in the wild to get handled by some other + // test case. + None => continue, + }; + + // If this interface has already been imported or + // exported this document can't do so again. Throw out + // this item in that situation. + if !interfaces.insert(id) { + continue; + } + } + ItemKind::AnonInterface => { + let iface = InterfaceGenerator::new(self).gen(u, None, &mut true)?; + part.push_str(&iface); + } + } + parts.push(part); + } + + shuffle(u, &mut parts)?; + + for part in parts { + ret.push_str(&part); + ret.push_str("\n"); + } + + ret.push_str("}"); + + Ok(ret) + } + + fn gen_interface( + &mut self, + u: &mut Unstructured<'_>, + name: Option<&str>, + has_default: &mut bool, + ) -> Result<(String, TypeList)> { + let mut gen = InterfaceGenerator::new(self); + let ret = gen.gen(u, name, has_default)?; + Ok((ret, gen.types_in_interface)) + } + + fn gen_path( + &self, + u: &mut Unstructured<'_>, + dst: &mut String, + ) -> Result> { + Ok(if !self.interfaces.is_empty() && u.arbitrary()? { + dst.push_str("self."); + let (name, id, _default, types) = u.choose(&self.interfaces)?; + dst.push_str("%"); + dst.push_str(name); + Some((*id, types)) + } else if !self.documents.is_empty() && u.arbitrary()? { + dst.push_str("pkg."); + let (name, ifaces) = u.choose(&self.documents)?; + dst.push_str("%"); + dst.push_str(name); + let (name, id, default, types) = u.choose(ifaces)?; + if !*default || !u.arbitrary()? { + dst.push_str("."); + dst.push_str("%"); + dst.push_str(name); + } + Some((*id, types)) + } else if !self.packages.is_empty() && u.arbitrary()? { + let (name, docs) = u.choose(&self.packages)?; + dst.push_str("%"); + dst.push_str(name); + dst.push_str("."); + let (name, ifaces) = u.choose(docs)?; + dst.push_str("%"); + dst.push_str(name); + let (name, id, default, types) = u.choose(ifaces)?; + if !*default || !u.arbitrary()? { + dst.push_str("."); + dst.push_str("%"); + dst.push_str(name); + } + Some((*id, types)) + } else { + None + }) + } + } + + impl<'a> InterfaceGenerator<'a> { + fn new(gen: &'a mut Generator) -> InterfaceGenerator<'a> { + InterfaceGenerator { + gen, + types_in_interface: Vec::new(), + unique_names: HashSet::new(), + } + } + + fn gen( + &mut self, + u: &mut Unstructured<'_>, + name: Option<&str>, + has_default: &mut bool, + ) -> Result { + let mut ret = String::new(); + if !*has_default && u.arbitrary()? { + *has_default = true; + ret.push_str("default "); + } + ret.push_str("interface "); + if let Some(name) = name { + ret.push_str("%"); + ret.push_str(name); + ret.push_str(" "); + } + ret.push_str("{\n"); + + #[derive(Arbitrary)] + enum Generate { + Use, + Type, + Function, + } + + let mut parts = Vec::new(); + while u.arbitrary()? { + match u.arbitrary()? { + Generate::Use => { + let mut path = String::new(); + let (_id, types) = match self.gen.gen_path(u, &mut path)? { + Some(types) => types, + None => continue, + }; + ret.push_str("use "); + ret.push_str(&path); + ret.push_str(".{"); + let (name, size) = u.choose(types)?; + ret.push_str("%"); + ret.push_str(name); + let name = if self.unique_names.contains(name) || u.arbitrary()? { + ret.push_str(" as %"); + let name = self.gen_unique_name(u)?; + ret.push_str(&name); + name + } else { + name.clone() + }; + self.types_in_interface.push((name, *size)); + ret.push_str("}"); + } + Generate::Type => { + let name = self.gen_unique_name(u)?; + let (size, typedef) = self.gen_typedef(u, &name)?; + parts.push(typedef); + self.types_in_interface.push((name, size)); + } + Generate::Function => { + parts.push(self.gen_func(u)?); + } + } + } + + shuffle(u, &mut parts)?; + for part in parts { + ret.push_str(&part); + ret.push_str("\n\n"); + } + + self.types_in_interface.clear(); + ret.push_str("}"); + Ok(ret) + } + + fn gen_typedef(&mut self, u: &mut Unstructured<'_>, name: &str) -> Result<(usize, String)> { + #[derive(Arbitrary)] + pub enum Kind { + Record, + Flags, + Variant, + Enum, + Union, + Anonymous, + } + + let mut size = 1; + let mut ret = String::new(); + match u.arbitrary()? { + Kind::Record => { + ret.push_str("record %"); + ret.push_str(name); + ret.push_str(" {\n"); + for _ in 0..u.int_in_range(0..=MAX_PARTS)? { + ret.push_str(" %"); + ret.push_str(&self.gen_unique_name(u)?); + ret.push_str(": "); + self.gen_type(u, &mut size, &mut ret)?; + ret.push_str(",\n"); + } + ret.push_str("}"); + } + Kind::Variant => { + ret.push_str("variant %"); + ret.push_str(name); + ret.push_str(" {\n"); + for _ in 0..u.int_in_range(1..=MAX_PARTS)? { + ret.push_str(" %"); + ret.push_str(&self.gen_unique_name(u)?); + if u.arbitrary()? { + ret.push_str("("); + self.gen_type(u, &mut size, &mut ret)?; + ret.push_str(")"); + } + ret.push_str(",\n"); + } + ret.push_str("}"); + } + Kind::Union => { + ret.push_str("union %"); + ret.push_str(name); + ret.push_str(" {\n"); + for _ in 0..u.int_in_range(1..=MAX_PARTS)? { + ret.push_str(" "); + self.gen_type(u, &mut size, &mut ret)?; + ret.push_str(",\n"); + } + ret.push_str("}"); + } + Kind::Enum => { + ret.push_str("enum %"); + ret.push_str(name); + ret.push_str(" {\n"); + for _ in 0..u.int_in_range(1..=MAX_PARTS)? { + ret.push_str(" %"); + ret.push_str(&self.gen_unique_name(u)?); + ret.push_str(",\n"); + } + ret.push_str("}"); + } + Kind::Flags => { + ret.push_str("flags %"); + ret.push_str(name); + ret.push_str(" {\n"); + for _ in 0..u.int_in_range(0..=MAX_PARTS)? { + ret.push_str(" %"); + ret.push_str(&self.gen_unique_name(u)?); + ret.push_str(",\n"); + } + ret.push_str("}"); + } + Kind::Anonymous => { + ret.push_str("type %"); + ret.push_str(name); + ret.push_str(" = "); + self.gen_type(u, &mut size, &mut ret)?; + } + } + + Ok((size, ret)) + } + + fn gen_type( + &mut self, + u: &mut Unstructured<'_>, + size: &mut usize, + dst: &mut String, + ) -> Result<()> { + const MAX_SIZE: usize = 100; + + #[derive(Arbitrary)] + enum Kind { + Bool, + U8, + U16, + U32, + U64, + S8, + S16, + S32, + S64, + Float32, + Float64, + Char, + String, + Id, + Tuple, + Option, + Result, + List, + } + + *size += 1; + loop { + break match u.arbitrary()? { + Kind::Bool => dst.push_str("bool"), + Kind::U8 => dst.push_str("u8"), + Kind::S8 => dst.push_str("s8"), + Kind::U16 => dst.push_str("u16"), + Kind::S16 => dst.push_str("s16"), + Kind::U32 => dst.push_str("u32"), + Kind::S32 => dst.push_str("s32"), + Kind::U64 => dst.push_str("u64"), + Kind::S64 => dst.push_str("s64"), + Kind::Float32 => dst.push_str("float32"), + Kind::Float64 => dst.push_str("float64"), + Kind::Char => dst.push_str("char"), + Kind::String => dst.push_str("string"), + Kind::Id => { + if self.types_in_interface.is_empty() { + continue; + } + let (name, type_size) = u.choose(&self.types_in_interface)?; + if *size + *type_size > MAX_SIZE { + continue; + } + *size += *type_size; + dst.push_str("%"); + dst.push_str(name); + } + Kind::Tuple => { + dst.push_str("tuple<"); + for i in 0..u.int_in_range(0..=MAX_PARTS)? { + if i > 0 { + dst.push_str(", "); + } + self.gen_type(u, size, dst)?; + } + dst.push_str(">"); + } + Kind::Option => { + dst.push_str("option<"); + self.gen_type(u, size, dst)?; + dst.push_str(">"); + } + Kind::List => { + dst.push_str("list<"); + self.gen_type(u, size, dst)?; + dst.push_str(">"); + } + Kind::Result => { + dst.push_str("result"); + let ok = u.arbitrary()?; + let err = u.arbitrary()?; + match (ok, err) { + (true, true) => { + dst.push_str("<"); + self.gen_type(u, size, dst)?; + dst.push_str(", "); + self.gen_type(u, size, dst)?; + dst.push_str(">"); + } + (true, false) => { + dst.push_str("<"); + self.gen_type(u, size, dst)?; + dst.push_str(">"); + } + (false, true) => { + dst.push_str("<_, "); + self.gen_type(u, size, dst)?; + dst.push_str(">"); + } + (false, false) => {} + } + } + }; + } + + Ok(()) + } + + fn gen_func(&mut self, u: &mut Unstructured<'_>) -> Result { + let mut ret = "%".to_string(); + ret.push_str(&self.gen_unique_name(u)?); + ret.push_str(": "); + self.gen_func_sig(u, &mut ret)?; + Ok(ret) + } + + fn gen_func_sig(&mut self, u: &mut Unstructured<'_>, dst: &mut String) -> Result<()> { + dst.push_str("func"); + self.gen_params(u, dst)?; + if u.arbitrary()? { + dst.push_str(" -> "); + self.gen_params(u, dst)?; + } else if u.arbitrary()? { + dst.push_str(" -> "); + self.gen_type(u, &mut 1, dst)?; + } + Ok(()) + } + + fn gen_params(&mut self, u: &mut Unstructured<'_>, dst: &mut String) -> Result<()> { + dst.push_str("("); + for i in 0..u.int_in_range(0..=MAX_PARTS)? { + if i > 0 { + dst.push_str(", "); + } + dst.push_str("%"); + dst.push_str(&self.gen_unique_name(u)?); + dst.push_str(": "); + self.gen_type(u, &mut 1, dst)?; + } + dst.push_str(")"); + Ok(()) + } + + fn gen_unique_name(&mut self, u: &mut Unstructured<'_>) -> Result { + gen_unique_name(u, &mut self.unique_names) + } + } + + fn gen_unique_name(u: &mut Unstructured<'_>, set: &mut HashSet) -> Result { + use std::fmt::Write; + let mut name = gen_name(u)?; + while !set.insert(name.clone()) { + write!(&mut name, "{}", set.len()).unwrap(); + } + Ok(name) + } + + fn gen_name(u: &mut Unstructured<'_>) -> Result { + let size = u.arbitrary_len::()?; + let size = std::cmp::min(size, 20); + let name = match str::from_utf8(u.peek_bytes(size).unwrap()) { + Ok(s) => { + u.bytes(size).unwrap(); + s.to_string() + } + Err(e) => { + let i = e.valid_up_to(); + let valid = u.bytes(i).unwrap(); + str::from_utf8(valid).unwrap().to_string() + } + }; + let name = name + .chars() + .map(|x| if x.is_ascii_lowercase() { x } else { 'x' }) + .collect::(); + Ok(if name.is_empty() { + "name".to_string() + } else { + name + }) + } + + fn shuffle(u: &mut Unstructured<'_>, mut slice: &mut [T]) -> Result<()> { + while slice.len() > 0 { + let pos = u.int_in_range(0..=slice.len() - 1)?; + slice.swap(0, pos); + slice = &mut slice[1..]; + } + Ok(()) + } +} From c42cfd2ab471e20dc78812131e05c3211d5e474a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 17 Jan 2023 10:39:52 -0800 Subject: [PATCH 34/37] Add limits on the size of wit documents generated Don't let the fuzzer go wild and blow the hardcoded limits in the wasmparser crate just yet. --- fuzz/fuzz_targets/roundtrip-wit.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fuzz/fuzz_targets/roundtrip-wit.rs b/fuzz/fuzz_targets/roundtrip-wit.rs index f12d5e50fd..e4c2122b4f 100644 --- a/fuzz/fuzz_targets/roundtrip-wit.rs +++ b/fuzz/fuzz_targets/roundtrip-wit.rs @@ -139,7 +139,7 @@ mod generate { fn gen(&mut self, u: &mut Unstructured<'_>) -> Result> { let mut packages = Vec::new(); let mut names = HashSet::new(); - while packages.is_empty() || u.arbitrary()? { + while packages.len() < 10 && (packages.is_empty() || u.arbitrary()?) { let name = gen_unique_name(u, &mut names)?; let (sources, documents) = self.gen_package(&name, u)?; self.packages.push((name.clone(), documents)); @@ -157,7 +157,7 @@ mod generate { let mut count = 0; let mut names = HashSet::new(); - while count == 0 || u.arbitrary()? { + while self.documents.len() < 10 && (count == 0 || u.arbitrary()?) { let name = gen_unique_name(u, &mut names)?; let (doc, interfaces) = self.gen_document(u)?; super::write_file(format!("orig-{pkg}-{name}.wit").as_ref(), &doc); @@ -181,7 +181,7 @@ mod generate { let mut has_default_interface = false; let mut has_default_world = false; let mut names = HashSet::new(); - while !u.is_empty() { + while pieces.len() < 10 && !u.is_empty() { let name = gen_unique_name(u, &mut names)?; match u.arbitrary()? { Generate::World => { @@ -242,7 +242,7 @@ mod generate { let mut imported_interfaces = HashSet::new(); let mut exported_interfaces = HashSet::new(); - while !u.is_empty() && u.arbitrary()? { + while parts.len() < 10 && !u.is_empty() && u.arbitrary()? { let mut part = String::new(); let (desc, names, interfaces) = match u.arbitrary()? { Direction::Import => ("import", &mut imports, &mut imported_interfaces), @@ -386,7 +386,7 @@ mod generate { } let mut parts = Vec::new(); - while u.arbitrary()? { + while parts.len() < 20 && u.arbitrary()? { match u.arbitrary()? { Generate::Use => { let mut path = String::new(); From 977cb67c0ed1e71c4fc6dad046cb2b47be2268f1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 17 Jan 2023 11:09:52 -0800 Subject: [PATCH 35/37] Preserve order of documents when round-tripping Previously the toposort of doc deps would be different because printing would list imports before exports but visitation would visit them in the textual order. Now the print order is the same as the visit order which should preserve the order of deps within the AST. --- crates/wit-parser/src/ast.rs | 42 ++++++++++++++++++++---------- fuzz/fuzz_targets/roundtrip-wit.rs | 7 +++-- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 0495bd1bb0..9c282d49c9 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -35,25 +35,39 @@ impl<'a> Ast<'a> { for item in self.items.iter() { match item { AstItem::World(world) => { + // Visit imports here first before exports to help preserve + // round-tripping of documents because printing a world puts + // imports first but textually they can be listed with + // exports first. + let mut imports = Vec::new(); + let mut exports = Vec::new(); for item in world.items.iter() { match item { // WorldItem::Use(u) => f(None, &u.from, Some(&u.names))?, - WorldItem::Import(Import { kind, .. }) - | WorldItem::Export(Export { kind, .. }) => match kind { - ExternKind::Interface(_, items) => { - for item in items { - match item { - InterfaceItem::Use(u) => { - f(None, &u.from, Some(&u.names))? - } - _ => {} - } - } + WorldItem::Import(Import { kind, .. }) => imports.push(kind), + WorldItem::Export(Export { kind, .. }) => exports.push(kind), + } + } + + let mut visit_kind = |kind: &'b ExternKind<'a>| match kind { + ExternKind::Interface(_, items) => { + for item in items { + match item { + InterfaceItem::Use(u) => f(None, &u.from, Some(&u.names))?, + _ => {} } - ExternKind::Path(path) => f(None, path, None)?, - ExternKind::Func(_) => {} - }, + } + Ok(()) } + ExternKind::Path(path) => f(None, path, None), + ExternKind::Func(_) => Ok(()), + }; + + for kind in imports { + visit_kind(kind)?; + } + for kind in exports { + visit_kind(kind)?; } } AstItem::Interface(i) => { diff --git a/fuzz/fuzz_targets/roundtrip-wit.rs b/fuzz/fuzz_targets/roundtrip-wit.rs index e4c2122b4f..07dd19445e 100644 --- a/fuzz/fuzz_targets/roundtrip-wit.rs +++ b/fuzz/fuzz_targets/roundtrip-wit.rs @@ -18,8 +18,8 @@ fuzz_target!(|data: &[u8]| { let mut deps = HashMap::new(); let mut last = None; for pkg in pkgs { - let mut unresolved = pkg.sources.parse(&pkg.name, None).unwrap(); - unresolved.url = Some(format!("my-scheme:/{}", pkg.name)); + let url = format!("my-scheme:/{}", pkg.name); + let unresolved = pkg.sources.parse(&pkg.name, Some(&url)).unwrap(); let id = resolve.push(unresolved, &deps).unwrap(); let prev = deps.insert(pkg.name, id); assert!(prev.is_none()); @@ -65,8 +65,7 @@ fn roundtrip_through_printing(file: &str, resolve: &Resolve, pkg: PackageId) -> write_file(&format!("{file}-{pkg_name}-{name}.wit"), &doc); map.push(format!("{name}.wit").as_ref(), &name, doc); } - let mut unresolved = map.parse(&pkg.name, None).unwrap(); - unresolved.url = pkg.url.clone(); + let unresolved = map.parse(&pkg.name, pkg.url.as_deref()).unwrap(); let id = new_resolve.push(unresolved, &new_deps).unwrap(); new_deps.insert(pkg.name.clone(), id); last = Some(id); From a6c80d830bdfd8c01b2b7176ca6795b08f6e8b8b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 17 Jan 2023 11:33:34 -0800 Subject: [PATCH 36/37] Limit the size of anonymous types Don't let the tuple/option/etc types subvert the size bound accidentally. --- fuzz/fuzz_targets/roundtrip-wit.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fuzz/fuzz_targets/roundtrip-wit.rs b/fuzz/fuzz_targets/roundtrip-wit.rs index 07dd19445e..57fe7d98da 100644 --- a/fuzz/fuzz_targets/roundtrip-wit.rs +++ b/fuzz/fuzz_targets/roundtrip-wit.rs @@ -551,6 +551,10 @@ mod generate { } *size += 1; + if *size >= MAX_SIZE { + dst.push_str("bool"); + return Ok(()); + } loop { break match u.arbitrary()? { Kind::Bool => dst.push_str("bool"), From ae840dd812963dc961017434012443ad3efabc53 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 17 Jan 2023 11:56:16 -0800 Subject: [PATCH 37/37] Refactor literal values into named constants --- fuzz/fuzz_targets/roundtrip-wit.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/fuzz/fuzz_targets/roundtrip-wit.rs b/fuzz/fuzz_targets/roundtrip-wit.rs index 57fe7d98da..5bf6b9ef2d 100644 --- a/fuzz/fuzz_targets/roundtrip-wit.rs +++ b/fuzz/fuzz_targets/roundtrip-wit.rs @@ -105,6 +105,11 @@ mod generate { use wit_parser::*; const MAX_PARTS: usize = 5; + const MAX_PACKAGES: usize = 10; + const MAX_DOCUMENTS: usize = 10; + const MAX_DOC_ITEMS: usize = 10; + const MAX_WORLD_ITEMS: usize = 10; + const MAX_INTERFACE_ITEMS: usize = 10; #[derive(Default)] struct Generator { @@ -138,7 +143,7 @@ mod generate { fn gen(&mut self, u: &mut Unstructured<'_>) -> Result> { let mut packages = Vec::new(); let mut names = HashSet::new(); - while packages.len() < 10 && (packages.is_empty() || u.arbitrary()?) { + while packages.len() < MAX_PACKAGES && (packages.is_empty() || u.arbitrary()?) { let name = gen_unique_name(u, &mut names)?; let (sources, documents) = self.gen_package(&name, u)?; self.packages.push((name.clone(), documents)); @@ -156,7 +161,7 @@ mod generate { let mut count = 0; let mut names = HashSet::new(); - while self.documents.len() < 10 && (count == 0 || u.arbitrary()?) { + while self.documents.len() < MAX_DOCUMENTS && (count == 0 || u.arbitrary()?) { let name = gen_unique_name(u, &mut names)?; let (doc, interfaces) = self.gen_document(u)?; super::write_file(format!("orig-{pkg}-{name}.wit").as_ref(), &doc); @@ -180,7 +185,7 @@ mod generate { let mut has_default_interface = false; let mut has_default_world = false; let mut names = HashSet::new(); - while pieces.len() < 10 && !u.is_empty() { + while pieces.len() < MAX_DOC_ITEMS && !u.is_empty() { let name = gen_unique_name(u, &mut names)?; match u.arbitrary()? { Generate::World => { @@ -241,7 +246,7 @@ mod generate { let mut imported_interfaces = HashSet::new(); let mut exported_interfaces = HashSet::new(); - while parts.len() < 10 && !u.is_empty() && u.arbitrary()? { + while parts.len() < MAX_WORLD_ITEMS && !u.is_empty() && u.arbitrary()? { let mut part = String::new(); let (desc, names, interfaces) = match u.arbitrary()? { Direction::Import => ("import", &mut imports, &mut imported_interfaces), @@ -385,7 +390,7 @@ mod generate { } let mut parts = Vec::new(); - while parts.len() < 20 && u.arbitrary()? { + while parts.len() < MAX_INTERFACE_ITEMS && u.arbitrary()? { match u.arbitrary()? { Generate::Use => { let mut path = String::new();