diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index 6d4056f..4b0e35a 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -16,7 +16,7 @@ jobs: - uses: DeterminateSystems/flake-checker-action@main - name: Build Tutorial - run: nix develop --command cd book && mdbook build + run: nix develop --command mdbook build book - uses: JamesIves/github-pages-deploy-action@v4.3.0 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0fdd11e..ad4199e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,11 +29,12 @@ jobs: - name: Build TS bindings run: nix develop --command cargo make init-bindings - - name: Build VSIX - run: nix develop --command cd ide/packages/extension && vsce package - - - name: Publish VS Code Marketplace - run: nix develop --command cd ide/packages/extension && vsce publish -p ${{ secrets.VSCODE_MRKT }} --packagePath *.vsix - - - name: Publish OVSX Marketplace - run: nix develop --command cd ide/packages/extension && pnpx ovsx publish *.vsix -p ${{ secrets.OVSX_MRKT }} + - name: Depot Setup + run: nix develop --command depot setup + + - name: Publish Extension + run: | + cd ide/packages/extension + nix develop --command vsce package + nix develop --command vsce publish -p ${{ secrets.VSCODE_MRKT }} --packagePath argus-*.vsix + nix develop --command pnpx ovsx publish argus-*.vsix -p ${{ secrets.OVSX_MRKT }} diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 6640634..67f93d9 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -6,5 +6,5 @@ - [Trait Debugging 101](trait-debugging-101.md) -- [Trait Methods and Typestate]() +- [Trait Methods and Typestate](typestate.md) diff --git a/book/src/typestate.md b/book/src/typestate.md new file mode 100644 index 0000000..f384622 --- /dev/null +++ b/book/src/typestate.md @@ -0,0 +1,56 @@ +# Trait Methods and Typestate + +Every programming language cultivates its own set of patterns. One pattern common in Rust is the *builder pattern.* Some data structures are complicated to construct, they may require a large number of inputs, or have complex configuration. + +A great example of working with builders is the Diesel [`QueryDsl`](https://docs.rs/diesel/latest/diesel/prelude/trait.QueryDsl.html#). The `QueryDsl` trait exposes a number of methods to construct a valid SQL query. Each method consumes the caller, and returns a type that itself implements `QueryDsl`. As an example here's the method signature for `select` + +```rust,ignore +fn select(self, selection: Selection) -> Select + where + Selection: Expression, + Self: SelectDsl { /* ... */ } +``` + +The `QueryDsl` demonstrates the complexity allowed by the builder pattern, it ensures valid SQL queries by encoding query semantics in Rust traits. One drawback of this pattern is that error diagnostics become difficult to understand as your types get larger and the traits involved more complex. In this chapter we will walk through how to debug and understand a trait error involving the builder pattern, or as some people will call it, *typestate.* We refer to this pattern as typestate because each method returns a type in a particular state, the methods available to the resulting type depend on its state. Calling methods in the wrong order, or forgetting a method, can result in the wrong state for the next method you'd like to call. Let's walk through an example. + +```rust,ignore +{{#include ../../examples/bad-select/src/main.rs:7:30}} +``` + +Running `cargo check` produces the following verbose diagnostic. + +```text +error[E0271]: type mismatch resolving `>::Count == Once` + --> src/main.rs:29:32 + | +29 | ... .load::<(i32, String)>(con... + | ---- ^^^^ expected `Once`, found `Never` + | | + | required by a bound introduced by this call + | +note: required for `posts::columns::id` to implement `AppearsOnTable` + --> src/main.rs:16:9 + | +16 | ... id -> ... + | ^^ + = note: associated types for the current `impl` cannot be restricted in `where` clauses + = note: 2 redundant requirements hidden + = note: required for `Grouped>` to implement `AppearsOnTable` + = note: required for `WhereClause>` to implement `diesel::query_builder::where_clause::ValidWhereClause>` + = note: required for `SelectStatement, ..., ..., ...>` to implement `Query` + = note: required for `SelectStatement, ..., ..., ...>` to implement `LoadQuery<'_, _, (i32, std::string::String)>` +note: required by a bound in `diesel::RunQueryDsl::load` + --> diesel-2.1.6/src/query_dsl/mod.rs:1542:15 + | +1540 | ...fn load<'query, U>(self, conn: &mut Con... + | ---- required by a bound in this associated function +1541 | ...where +1542 | ... Self: LoadQuery<'query, Conn, +... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RunQueryDsl::load` + = note: the full name for the type has been written to 'bad_select-fa50bb6fe8eee519.long-type-16986433487391717729.txt' +``` + +As we did in the previous section, we shall demo a short workflow using Argus to gather the same information. + + diff --git a/examples/bad-select/.rustfmt.toml b/examples/bad-select/.rustfmt.toml new file mode 100644 index 0000000..575d9b5 --- /dev/null +++ b/examples/bad-select/.rustfmt.toml @@ -0,0 +1,2 @@ +# Force method chains to a newline +max_width = 40 diff --git a/examples/bad-select/Cargo.lock b/examples/bad-select/Cargo.lock new file mode 100644 index 0000000..8054bfc --- /dev/null +++ b/examples/bad-select/Cargo.lock @@ -0,0 +1,281 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bad-select" +version = "0.1.0" +dependencies = [ + "diesel", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "diesel" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff236accb9a5069572099f0b350a92e9560e8e63a9b8d546162f4a5e03026bb2" +dependencies = [ + "bitflags", + "byteorder", + "diesel_derives", + "itoa", + "libsqlite3-sys", + "mysqlclient-sys", + "percent-encoding", + "pq-sys", + "time", + "url", +] + +[[package]] +name = "diesel_derives" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14701062d6bed917b5c7103bdffaee1e4609279e240488ad24e7bd979ca6866c" +dependencies = [ + "diesel_table_macro_syntax", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diesel_table_macro_syntax" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" +dependencies = [ + "syn", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "libsqlite3-sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "mysqlclient-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61b381528ba293005c42a409dd73d034508e273bf90481f17ec2e964a6e969b" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "pq-sys" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0052426df997c0cbd30789eb44ca097e3541717a7b8fa36b1c464ee7edebd" +dependencies = [ + "vcpkg", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201fcda3845c23e8212cd466bfebf0bd20694490fc0356ae8e428e0824a915a6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" diff --git a/examples/bad-select/Cargo.toml b/examples/bad-select/Cargo.toml new file mode 100644 index 0000000..71e1d40 --- /dev/null +++ b/examples/bad-select/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "bad-select" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +diesel = {version = "2.0.0-rc.1", default-features = false, features = ["sqlite", "postgres", "mysql"]} diff --git a/examples/bad-select/src/main.rs b/examples/bad-select/src/main.rs new file mode 100644 index 0000000..7e43c2d --- /dev/null +++ b/examples/bad-select/src/main.rs @@ -0,0 +1,32 @@ +use diesel::prelude::*; + +allow_tables_to_appear_in_same_query!( + users, posts, +); + +table! { + users(id) { + id -> Integer, + name -> Text, + } +} + +table! { + posts(id) { + id -> Integer, + name -> Text, + user_id -> Integer, + } +} + +fn query(conn: &mut PgConnection) { + users::table + .filter(users::id.eq(posts::id)) + .select(( + users::id, + users::name, + )) + .load::<(i32, String)>(conn); +} + +fn main() {}