From c11ac4298ac58f1a4d65bc607f6f679317d8e379 Mon Sep 17 00:00:00 2001 From: moana Date: Fri, 15 May 2026 14:49:07 +0200 Subject: [PATCH 1/2] macro: Add end-to-end test harness for the contract macro Cover the gap between unit tests of validation helpers and the single end-to-end shape in tests/test-contract: trybuild compile-fail fixtures pin every rejection rule in validate.rs, and a compile-pass sub-crate pins valid contract shapes the reference contract does not exercise. - Reorganize the 4 existing compile-fail fixtures under topic dirs (directives/, feature_gates/) and widen the trybuild glob to tests/compile-fail/**/*.rs. - Add 20 compile-fail fixtures (methods/, traits/, events/), one per validate.rs rejection rule, each carrying a // Pins: header that traces back to the rule identifier. - Add tests/compile-pass/ sub-crate with a topic-mirroring layout and a tests/compile_pass.rs driver that runs cargo check on it; the sub-crate is used (instead of trybuild's compile_pass glob) because the macro emits a compile_error! without a feature set and trybuild has no per-fixture feature configuration. - Add tests/README.md covering the topic taxonomy, the // Pins: header convention, the rationale for the sub-crate layout, and the absence of _dd.rs (data-driver-js) variants. Nothing in contract-macro/src/ or tests/test-contract/ is edited. --- contract-macro/tests/README.md | 76 +++++++++++++++++++ .../bare_contract_directive.rs | 2 + .../bare_contract_directive.stderr | 4 +- .../duplicate_directive_across_attributes.rs | 2 + ...plicate_directive_across_attributes.stderr | 5 ++ .../duplicate_directive_within_attribute.rs | 2 + ...uplicate_directive_within_attribute.stderr | 5 ++ ...plicate_directive_across_attributes.stderr | 5 -- ...uplicate_directive_within_attribute.stderr | 5 -- .../compile-fail/events/mut_self_no_emit.rs | 23 ++++++ .../events/mut_self_no_emit.stderr | 5 ++ .../feature_gates/missing_feature_gate.rs | 10 +++ .../missing_feature_gate.stderr | 6 +- .../compile-fail/methods/init_consume_self.rs | 23 ++++++ .../methods/init_consume_self.stderr | 5 ++ .../methods/init_immutable_self.rs | 21 +++++ .../methods/init_immutable_self.stderr | 5 ++ .../compile-fail/methods/init_no_receiver.rs | 24 ++++++ .../methods/init_no_receiver.stderr | 5 ++ .../methods/init_returns_value.rs | 24 ++++++ .../methods/init_returns_value.stderr | 5 ++ .../methods/new_constructor_has_params.rs | 21 +++++ .../methods/new_constructor_has_params.stderr | 5 ++ .../methods/new_constructor_missing.rs | 19 +++++ .../methods/new_constructor_missing.stderr | 5 ++ .../methods/new_constructor_not_const.rs | 19 +++++ .../methods/new_constructor_not_const.stderr | 5 ++ .../methods/new_constructor_wrong_return.rs | 20 +++++ .../new_constructor_wrong_return.stderr | 5 ++ .../compile-fail/methods/pub_method_async.rs | 23 ++++++ .../methods/pub_method_async.stderr | 5 ++ .../methods/pub_method_consume_self.rs | 22 ++++++ .../methods/pub_method_consume_self.stderr | 5 ++ .../methods/pub_method_generic_params.rs | 24 ++++++ .../methods/pub_method_generic_params.stderr | 5 ++ .../methods/pub_method_impl_trait_param.rs | 24 ++++++ .../pub_method_impl_trait_param.stderr | 5 ++ .../methods/pub_method_impl_trait_return.rs | 23 ++++++ .../pub_method_impl_trait_return.stderr | 5 ++ .../compile-fail/missing_feature_gate.rs | 3 - .../compile-fail/traits/trait_method_async.rs | 26 +++++++ .../traits/trait_method_async.stderr | 5 ++ .../traits/trait_method_consume_self.rs | 24 ++++++ .../traits/trait_method_consume_self.stderr | 5 ++ .../traits/trait_method_generic_params.rs | 27 +++++++ .../traits/trait_method_generic_params.stderr | 5 ++ .../traits/trait_method_impl_trait_param.rs | 26 +++++++ .../trait_method_impl_trait_param.stderr | 5 ++ .../traits/trait_method_impl_trait_return.rs | 26 +++++++ .../trait_method_impl_trait_return.stderr | 5 ++ .../trait_method_no_self_non_default.rs | 28 +++++++ .../trait_method_no_self_non_default.stderr | 5 ++ contract-macro/tests/compile-pass/Cargo.toml | 15 ++++ contract-macro/tests/compile-pass/src/lib.rs | 9 +++ .../compile-pass/src/methods/init_absent.rs | 22 ++++++ .../src/methods/init_no_params.rs | 29 +++++++ .../tests/compile-pass/src/methods/mod.rs | 3 + .../src/methods/new_returns_contract_name.rs | 23 ++++++ contract-macro/tests/compile_fail.rs | 2 +- contract-macro/tests/compile_pass.rs | 29 +++++++ 60 files changed, 805 insertions(+), 19 deletions(-) create mode 100644 contract-macro/tests/README.md rename contract-macro/tests/compile-fail/{ => directives}/bare_contract_directive.rs (91%) rename contract-macro/tests/compile-fail/{ => directives}/bare_contract_directive.stderr (50%) rename contract-macro/tests/compile-fail/{ => directives}/duplicate_directive_across_attributes.rs (89%) create mode 100644 contract-macro/tests/compile-fail/directives/duplicate_directive_across_attributes.stderr rename contract-macro/tests/compile-fail/{ => directives}/duplicate_directive_within_attribute.rs (88%) create mode 100644 contract-macro/tests/compile-fail/directives/duplicate_directive_within_attribute.stderr delete mode 100644 contract-macro/tests/compile-fail/duplicate_directive_across_attributes.stderr delete mode 100644 contract-macro/tests/compile-fail/duplicate_directive_within_attribute.stderr create mode 100644 contract-macro/tests/compile-fail/events/mut_self_no_emit.rs create mode 100644 contract-macro/tests/compile-fail/events/mut_self_no_emit.stderr create mode 100644 contract-macro/tests/compile-fail/feature_gates/missing_feature_gate.rs rename contract-macro/tests/compile-fail/{ => feature_gates}/missing_feature_gate.stderr (91%) create mode 100644 contract-macro/tests/compile-fail/methods/init_consume_self.rs create mode 100644 contract-macro/tests/compile-fail/methods/init_consume_self.stderr create mode 100644 contract-macro/tests/compile-fail/methods/init_immutable_self.rs create mode 100644 contract-macro/tests/compile-fail/methods/init_immutable_self.stderr create mode 100644 contract-macro/tests/compile-fail/methods/init_no_receiver.rs create mode 100644 contract-macro/tests/compile-fail/methods/init_no_receiver.stderr create mode 100644 contract-macro/tests/compile-fail/methods/init_returns_value.rs create mode 100644 contract-macro/tests/compile-fail/methods/init_returns_value.stderr create mode 100644 contract-macro/tests/compile-fail/methods/new_constructor_has_params.rs create mode 100644 contract-macro/tests/compile-fail/methods/new_constructor_has_params.stderr create mode 100644 contract-macro/tests/compile-fail/methods/new_constructor_missing.rs create mode 100644 contract-macro/tests/compile-fail/methods/new_constructor_missing.stderr create mode 100644 contract-macro/tests/compile-fail/methods/new_constructor_not_const.rs create mode 100644 contract-macro/tests/compile-fail/methods/new_constructor_not_const.stderr create mode 100644 contract-macro/tests/compile-fail/methods/new_constructor_wrong_return.rs create mode 100644 contract-macro/tests/compile-fail/methods/new_constructor_wrong_return.stderr create mode 100644 contract-macro/tests/compile-fail/methods/pub_method_async.rs create mode 100644 contract-macro/tests/compile-fail/methods/pub_method_async.stderr create mode 100644 contract-macro/tests/compile-fail/methods/pub_method_consume_self.rs create mode 100644 contract-macro/tests/compile-fail/methods/pub_method_consume_self.stderr create mode 100644 contract-macro/tests/compile-fail/methods/pub_method_generic_params.rs create mode 100644 contract-macro/tests/compile-fail/methods/pub_method_generic_params.stderr create mode 100644 contract-macro/tests/compile-fail/methods/pub_method_impl_trait_param.rs create mode 100644 contract-macro/tests/compile-fail/methods/pub_method_impl_trait_param.stderr create mode 100644 contract-macro/tests/compile-fail/methods/pub_method_impl_trait_return.rs create mode 100644 contract-macro/tests/compile-fail/methods/pub_method_impl_trait_return.stderr delete mode 100644 contract-macro/tests/compile-fail/missing_feature_gate.rs create mode 100644 contract-macro/tests/compile-fail/traits/trait_method_async.rs create mode 100644 contract-macro/tests/compile-fail/traits/trait_method_async.stderr create mode 100644 contract-macro/tests/compile-fail/traits/trait_method_consume_self.rs create mode 100644 contract-macro/tests/compile-fail/traits/trait_method_consume_self.stderr create mode 100644 contract-macro/tests/compile-fail/traits/trait_method_generic_params.rs create mode 100644 contract-macro/tests/compile-fail/traits/trait_method_generic_params.stderr create mode 100644 contract-macro/tests/compile-fail/traits/trait_method_impl_trait_param.rs create mode 100644 contract-macro/tests/compile-fail/traits/trait_method_impl_trait_param.stderr create mode 100644 contract-macro/tests/compile-fail/traits/trait_method_impl_trait_return.rs create mode 100644 contract-macro/tests/compile-fail/traits/trait_method_impl_trait_return.stderr create mode 100644 contract-macro/tests/compile-fail/traits/trait_method_no_self_non_default.rs create mode 100644 contract-macro/tests/compile-fail/traits/trait_method_no_self_non_default.stderr create mode 100644 contract-macro/tests/compile-pass/Cargo.toml create mode 100644 contract-macro/tests/compile-pass/src/lib.rs create mode 100644 contract-macro/tests/compile-pass/src/methods/init_absent.rs create mode 100644 contract-macro/tests/compile-pass/src/methods/init_no_params.rs create mode 100644 contract-macro/tests/compile-pass/src/methods/mod.rs create mode 100644 contract-macro/tests/compile-pass/src/methods/new_returns_contract_name.rs create mode 100644 contract-macro/tests/compile_pass.rs diff --git a/contract-macro/tests/README.md b/contract-macro/tests/README.md new file mode 100644 index 0000000..87c0194 --- /dev/null +++ b/contract-macro/tests/README.md @@ -0,0 +1,76 @@ +# `dusk-forge-contract` test harness + +End-to-end coverage of the `#[contract]` macro. Four layers: + +| Layer | What it pins | Driven by | +|---|---|---| +| Unit tests under `src/` | Pure-Rust validation / parsing helpers | `cargo test -p dusk-forge-contract` | +| `tests/compile-fail/` (+ `compile_fail.rs`) | Each rejection rule produces a diagnostic | trybuild | +| `tests/compile-pass/` (+ `compile_pass.rs`) | Valid contract shapes expand and type-check | `cargo check` on a sub-crate | +| `tests/compile-fail-both-features/` | Mutually-exclusive feature gate fires | `cargo check` (asserts failure) | + +`tests/test-contract/` (workspace member) exercises a single rich shape end-to-end into a WASM build. Fixtures here cover the *variations* that one reference contract does not. + +## Topic taxonomy + +Both `compile-fail/` and `compile-pass/src/` are nested by topic, not flat: + +| Topic dir | Scope | +|---|---| +| `methods/` | Inherent method validation: `validate::public_method`, `validate::new_constructor`, `validate::init_method` | +| `traits/` | Trait method validation: `validate::trait_method` | +| `events/` | Event-emission validation: `validate::method_emits_event` | +| `directives/` | `#[contract(...)]` directive parsing | +| `feature_gates/` | `contract` / `data-driver` cargo feature enforcement | + +Add a new topic dir only when a rule has no reasonable home in the existing ones. Topics are stable categories — they should outlive individual rule renames inside `validate.rs`. + +## `// Pins:` header convention + +Every fixture starts with a comment naming the rule it pins: + +```rust +// Pins: validate::public_method::async +// +// +``` + +The pin identifier traces back to the function and reject branch in `src/`. Audit "which fixtures pin this rule?" via `grep -r '// Pins: ' tests/`. + +If you change a rule's identifier (rename a function, restructure a module), `grep` for the old name and update every fixture header in lockstep. + +## Adding a compile-fail fixture + +Fixtures here depend on the macro crate directly: `use dusk_forge_contract::contract;`. (The compile-pass sub-crate goes through the `dusk_forge::contract` re-export instead — see below.) + +1. Pick the topic dir (see above; introduce a new one if none fits). +2. Create `.rs` with: + - A `// Pins:` header. + - A 10–25 line minimal repro. Use `pub struct MyContract;` and a `const fn new() -> Self` constructor unless the rule under test requires otherwise. + - `fn main() {}` at the bottom. +3. Run `TRYBUILD=overwrite cargo test -p dusk-forge-contract --test compile_fail` to bless the `.stderr` companion. +4. Re-run `cargo test -p dusk-forge-contract --test compile_fail` and confirm the new fixture passes. + +Earlier checks in `parse::analyze` can shadow your rule — if the diagnostic you see is for a different rule, adjust the fixture so the rule under test fires first. + +## Adding a compile-pass fixture + +`tests/compile-pass/` is a small library sub-crate (`compile-pass-fixtures`), not a trybuild glob. Each `.rs` file in `src//` contains one `#[dusk_forge::contract] pub mod my_contract { … }` that exercises one valid shape. Fixtures here invoke the macro through the `dusk_forge::contract` re-export (the sub-crate depends on `dusk-forge`, not on `dusk-forge-contract` directly). + +1. Pick the topic dir under `tests/compile-pass/src/`. +2. Create `.rs` with: + - A `// Pins:` header naming the canonical valid shape. + - One `#[dusk_forge::contract] pub mod my_contract { … }`. + - An `impl Default for MyContract` if the contract carries a `pub fn new()` (avoids `clippy::new_without_default`). +3. Register the file in `src//mod.rs` as `pub mod ;`. +4. `cargo test -p dusk-forge-contract --test compile_pass`. + +`CONTRACT_SCHEMA` is emitted at the parent scope of the macro-annotated module, so one `#[contract]` per file keeps the constants from colliding. + +## Why a sub-crate for compile-pass + +The macro emits `compile_error!` when neither `contract` nor `data-driver` is enabled in the consuming crate. trybuild has no per-fixture feature configuration, so its `compile_pass` glob cannot enable the feature that the fixtures need. The sub-crate sets `default = ["contract"]`, mirrors the topic structure of `compile-fail/`, and is driven by a single `cargo check` invocation — symmetric with `tests/compile-fail-both-features/`. + +## `_dd.rs` (data-driver-js) variants + +The plan envisioned `_dd.rs` companion fixtures where the `data-driver-js` feature changes the diagnostic or the accept/reject decision. None are present today: `data-driver-js` is a downstream cargo feature whose only effect is gating `dusk-data-driver/alloc`; it never reaches the macro's parse or validate phases. Add a `_dd.rs` variant only when a future rule starts behaving differently between the modes. diff --git a/contract-macro/tests/compile-fail/bare_contract_directive.rs b/contract-macro/tests/compile-fail/directives/bare_contract_directive.rs similarity index 91% rename from contract-macro/tests/compile-fail/bare_contract_directive.rs rename to contract-macro/tests/compile-fail/directives/bare_contract_directive.rs index 185e1a3..1ef6277 100644 --- a/contract-macro/tests/compile-fail/bare_contract_directive.rs +++ b/contract-macro/tests/compile-fail/directives/bare_contract_directive.rs @@ -1,3 +1,5 @@ +// Pins: parse::directives::bare_attribute +// // `#[contract]` without a directive list on an inner item is rejected. This // pins the strict parse stance: every inner `#[contract(...)]` attribute must // be a list, so a future refactor that loosened this check would trip this diff --git a/contract-macro/tests/compile-fail/bare_contract_directive.stderr b/contract-macro/tests/compile-fail/directives/bare_contract_directive.stderr similarity index 50% rename from contract-macro/tests/compile-fail/bare_contract_directive.stderr rename to contract-macro/tests/compile-fail/directives/bare_contract_directive.stderr index a20b046..0d55202 100644 --- a/contract-macro/tests/compile-fail/bare_contract_directive.stderr +++ b/contract-macro/tests/compile-fail/directives/bare_contract_directive.stderr @@ -1,5 +1,5 @@ error: expected attribute arguments in parentheses: `contract(...)` - --> tests/compile-fail/bare_contract_directive.rs:17:11 + --> tests/compile-fail/directives/bare_contract_directive.rs:19:11 | -17 | #[contract] +19 | #[contract] | ^^^^^^^^ diff --git a/contract-macro/tests/compile-fail/duplicate_directive_across_attributes.rs b/contract-macro/tests/compile-fail/directives/duplicate_directive_across_attributes.rs similarity index 89% rename from contract-macro/tests/compile-fail/duplicate_directive_across_attributes.rs rename to contract-macro/tests/compile-fail/directives/duplicate_directive_across_attributes.rs index 4bf5f8f..1632865 100644 --- a/contract-macro/tests/compile-fail/duplicate_directive_across_attributes.rs +++ b/contract-macro/tests/compile-fail/directives/duplicate_directive_across_attributes.rs @@ -1,3 +1,5 @@ +// Pins: parse::directives::duplicate_across_attributes +// // Duplicate directive keys across multiple `#[contract(...)]` attributes on // the same item are rejected. This pins the strict parse stance against // silent last-attr-wins. diff --git a/contract-macro/tests/compile-fail/directives/duplicate_directive_across_attributes.stderr b/contract-macro/tests/compile-fail/directives/duplicate_directive_across_attributes.stderr new file mode 100644 index 0000000..9b49706 --- /dev/null +++ b/contract-macro/tests/compile-fail/directives/duplicate_directive_across_attributes.stderr @@ -0,0 +1,5 @@ +error: duplicate `feeds` directive + --> tests/compile-fail/directives/duplicate_directive_across_attributes.rs:19:20 + | +19 | #[contract(feeds = "u32")] + | ^^^^^ diff --git a/contract-macro/tests/compile-fail/duplicate_directive_within_attribute.rs b/contract-macro/tests/compile-fail/directives/duplicate_directive_within_attribute.rs similarity index 88% rename from contract-macro/tests/compile-fail/duplicate_directive_within_attribute.rs rename to contract-macro/tests/compile-fail/directives/duplicate_directive_within_attribute.rs index dc645ad..25113f3 100644 --- a/contract-macro/tests/compile-fail/duplicate_directive_within_attribute.rs +++ b/contract-macro/tests/compile-fail/directives/duplicate_directive_within_attribute.rs @@ -1,3 +1,5 @@ +// Pins: parse::directives::duplicate_within_attribute +// // Duplicate directive keys within a single `#[contract(...)]` attribute are // rejected. This pins the strict parse stance against silent last-wins. diff --git a/contract-macro/tests/compile-fail/directives/duplicate_directive_within_attribute.stderr b/contract-macro/tests/compile-fail/directives/duplicate_directive_within_attribute.stderr new file mode 100644 index 0000000..12583cb --- /dev/null +++ b/contract-macro/tests/compile-fail/directives/duplicate_directive_within_attribute.stderr @@ -0,0 +1,5 @@ +error: duplicate `feeds` directive + --> tests/compile-fail/directives/duplicate_directive_within_attribute.rs:17:35 + | +17 | #[contract(feeds = "u64", feeds = "u32")] + | ^^^^^ diff --git a/contract-macro/tests/compile-fail/duplicate_directive_across_attributes.stderr b/contract-macro/tests/compile-fail/duplicate_directive_across_attributes.stderr deleted file mode 100644 index 00d0224..0000000 --- a/contract-macro/tests/compile-fail/duplicate_directive_across_attributes.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: duplicate `feeds` directive - --> tests/compile-fail/duplicate_directive_across_attributes.rs:17:20 - | -17 | #[contract(feeds = "u32")] - | ^^^^^ diff --git a/contract-macro/tests/compile-fail/duplicate_directive_within_attribute.stderr b/contract-macro/tests/compile-fail/duplicate_directive_within_attribute.stderr deleted file mode 100644 index a1b801d..0000000 --- a/contract-macro/tests/compile-fail/duplicate_directive_within_attribute.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: duplicate `feeds` directive - --> tests/compile-fail/duplicate_directive_within_attribute.rs:15:35 - | -15 | #[contract(feeds = "u64", feeds = "u32")] - | ^^^^^ diff --git a/contract-macro/tests/compile-fail/events/mut_self_no_emit.rs b/contract-macro/tests/compile-fail/events/mut_self_no_emit.rs new file mode 100644 index 0000000..d85c837 --- /dev/null +++ b/contract-macro/tests/compile-fail/events/mut_self_no_emit.rs @@ -0,0 +1,23 @@ +// Pins: validate::method_emits_event::mut_self_no_emit +// +// A public `&mut self` method that neither calls `abi::emit()` in its body, +// nor registers events via `#[contract(emits = [...])]`, nor opts out via +// `#[contract(no_event)]`, must be rejected: state-mutating methods are +// required to be observable. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + + pub fn touch(&mut self) {} + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/events/mut_self_no_emit.stderr b/contract-macro/tests/compile-fail/events/mut_self_no_emit.stderr new file mode 100644 index 0000000..5c17823 --- /dev/null +++ b/contract-macro/tests/compile-fail/events/mut_self_no_emit.stderr @@ -0,0 +1,5 @@ +error: public method `touch` mutates state but emits no events; add an `abi::emit()` call or suppress with `#[contract(no_event)]` + --> tests/compile-fail/events/mut_self_no_emit.rs:19:13 + | +19 | pub fn touch(&mut self) {} + | ^^^^^^^^^^^^^^^^^^^ diff --git a/contract-macro/tests/compile-fail/feature_gates/missing_feature_gate.rs b/contract-macro/tests/compile-fail/feature_gates/missing_feature_gate.rs new file mode 100644 index 0000000..39306a6 --- /dev/null +++ b/contract-macro/tests/compile-fail/feature_gates/missing_feature_gate.rs @@ -0,0 +1,10 @@ +// Pins: generate::feature_gate::missing +// +// Applying `#[contract]` without enabling either the `contract` or the +// `data-driver` cargo feature fires the generated `compile_error!` that +// guards against unconfigured WASM builds. The contract body is shared +// with `compile-fail-both-features/` via `include!`. + +include!("../../common/contract.rs"); + +fn main() {} diff --git a/contract-macro/tests/compile-fail/missing_feature_gate.stderr b/contract-macro/tests/compile-fail/feature_gates/missing_feature_gate.stderr similarity index 91% rename from contract-macro/tests/compile-fail/missing_feature_gate.stderr rename to contract-macro/tests/compile-fail/feature_gates/missing_feature_gate.stderr index 08ce041..d34d092 100644 --- a/contract-macro/tests/compile-fail/missing_feature_gate.stderr +++ b/contract-macro/tests/compile-fail/feature_gates/missing_feature_gate.stderr @@ -1,5 +1,5 @@ error: Enable either 'contract' or 'data-driver' feature for WASM builds - --> tests/compile-fail/../common/contract.rs + --> tests/compile-fail/feature_gates/../../common/contract.rs | | #[contract] | ^^^^^^^^^^^ @@ -7,7 +7,7 @@ error: Enable either 'contract' or 'data-driver' feature for WASM builds = note: this error originates in the attribute macro `contract` (in Nightly builds, run with -Z macro-backtrace for more info) warning: unexpected `cfg` condition value: `contract` - --> tests/compile-fail/../common/contract.rs + --> tests/compile-fail/feature_gates/../../common/contract.rs | | #[contract] | ^^^^^^^^^^^ @@ -21,7 +21,7 @@ warning: unexpected `cfg` condition value: `contract` = note: this warning originates in the attribute macro `contract` (in Nightly builds, run with -Z macro-backtrace for more info) warning: unexpected `cfg` condition value: `data-driver` - --> tests/compile-fail/../common/contract.rs + --> tests/compile-fail/feature_gates/../../common/contract.rs | | #[contract] | ^^^^^^^^^^^ diff --git a/contract-macro/tests/compile-fail/methods/init_consume_self.rs b/contract-macro/tests/compile-fail/methods/init_consume_self.rs new file mode 100644 index 0000000..f9047c9 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/init_consume_self.rs @@ -0,0 +1,23 @@ +// Pins: validate::init_method::consume_self +// +// A private `init` method that consumes `self` must be rejected by the +// init-specific check. (The public flavour fires the broader +// `public_method::consume_self` rule first; this fixture keeps `init` +// private so the init-specific path is exercised.) + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + + fn init(self) {} + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/methods/init_consume_self.stderr b/contract-macro/tests/compile-fail/methods/init_consume_self.stderr new file mode 100644 index 0000000..f0e5e03 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/init_consume_self.stderr @@ -0,0 +1,5 @@ +error: `MyContract::init` must take `&mut self`; initialization needs to modify contract state + --> tests/compile-fail/methods/init_consume_self.rs:19:17 + | +19 | fn init(self) {} + | ^^^^ diff --git a/contract-macro/tests/compile-fail/methods/init_immutable_self.rs b/contract-macro/tests/compile-fail/methods/init_immutable_self.rs new file mode 100644 index 0000000..026f3d3 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/init_immutable_self.rs @@ -0,0 +1,21 @@ +// Pins: validate::init_method::immutable_self +// +// An `init` method with an immutable `&self` receiver must be rejected: +// initialisation has to mutate the contract state. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + + pub fn init(&self) {} + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/methods/init_immutable_self.stderr b/contract-macro/tests/compile-fail/methods/init_immutable_self.stderr new file mode 100644 index 0000000..7b10fd9 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/init_immutable_self.stderr @@ -0,0 +1,5 @@ +error: `MyContract::init` must take `&mut self`; initialization needs to modify contract state + --> tests/compile-fail/methods/init_immutable_self.rs:17:21 + | +17 | pub fn init(&self) {} + | ^^^^^ diff --git a/contract-macro/tests/compile-fail/methods/init_no_receiver.rs b/contract-macro/tests/compile-fail/methods/init_no_receiver.rs new file mode 100644 index 0000000..25cfa74 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/init_no_receiver.rs @@ -0,0 +1,24 @@ +// Pins: validate::init_method::no_receiver +// +// An `init` method declared as an associated function (no `self`) must be +// rejected: initialisation needs access to contract state through `&mut +// self`. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + + pub fn init(seed: u64) { + let _ = seed; + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/methods/init_no_receiver.stderr b/contract-macro/tests/compile-fail/methods/init_no_receiver.stderr new file mode 100644 index 0000000..88984b9 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/init_no_receiver.stderr @@ -0,0 +1,5 @@ +error: `MyContract::init` must take `&mut self`; initialization requires access to contract state + --> tests/compile-fail/methods/init_no_receiver.rs:18:13 + | +18 | pub fn init(seed: u64) { + | ^^^^^^^^^^^^^^^^^^ diff --git a/contract-macro/tests/compile-fail/methods/init_returns_value.rs b/contract-macro/tests/compile-fail/methods/init_returns_value.rs new file mode 100644 index 0000000..e3c1a9c --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/init_returns_value.rs @@ -0,0 +1,24 @@ +// Pins: validate::init_method::returns_value +// +// An `init` method that returns a non-unit type must be rejected: +// initialisation has no caller to consume a return value, so errors must +// panic instead. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + + pub fn init(&mut self) -> bool { + true + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/methods/init_returns_value.stderr b/contract-macro/tests/compile-fail/methods/init_returns_value.stderr new file mode 100644 index 0000000..26f69ba --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/init_returns_value.stderr @@ -0,0 +1,5 @@ +error: `MyContract::init` must return `()`; use `panic!` or `assert!` for initialization errors + --> tests/compile-fail/methods/init_returns_value.rs:18:32 + | +18 | pub fn init(&mut self) -> bool { + | ^^^^^^^ diff --git a/contract-macro/tests/compile-fail/methods/new_constructor_has_params.rs b/contract-macro/tests/compile-fail/methods/new_constructor_has_params.rs new file mode 100644 index 0000000..6552d4c --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/new_constructor_has_params.rs @@ -0,0 +1,21 @@ +// Pins: validate::new_constructor::has_params +// +// The `new` constructor must take no parameters: it must produce a default +// state with no input, since the host has no way to pass arguments when +// initialising the static STATE. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new(seed: u64) -> Self { + let _ = seed; + Self + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/methods/new_constructor_has_params.stderr b/contract-macro/tests/compile-fail/methods/new_constructor_has_params.stderr new file mode 100644 index 0000000..21e2aad --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/new_constructor_has_params.stderr @@ -0,0 +1,5 @@ +error: `MyContract::new` must have no parameters; use `const fn new() -> Self` to create a default state + --> tests/compile-fail/methods/new_constructor_has_params.rs:14:26 + | +14 | pub const fn new(seed: u64) -> Self { + | ^^^^^^^^^ diff --git a/contract-macro/tests/compile-fail/methods/new_constructor_missing.rs b/contract-macro/tests/compile-fail/methods/new_constructor_missing.rs new file mode 100644 index 0000000..b91fb3f --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/new_constructor_missing.rs @@ -0,0 +1,19 @@ +// Pins: validate::new_constructor::missing +// +// A contract module without a `const fn new() -> Self` method must be +// rejected: the static STATE singleton is initialised via that constructor. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub fn get(&self) -> u64 { + 0 + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/methods/new_constructor_missing.stderr b/contract-macro/tests/compile-fail/methods/new_constructor_missing.stderr new file mode 100644 index 0000000..25ea509 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/new_constructor_missing.stderr @@ -0,0 +1,5 @@ +error: #[contract] requires `MyContract` to have a `const fn new() -> Self` method to initialize the static STATE variable + --> tests/compile-fail/methods/new_constructor_missing.rs:10:5 + | +10 | pub struct MyContract; + | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/contract-macro/tests/compile-fail/methods/new_constructor_not_const.rs b/contract-macro/tests/compile-fail/methods/new_constructor_not_const.rs new file mode 100644 index 0000000..c74a7e7 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/new_constructor_not_const.rs @@ -0,0 +1,19 @@ +// Pins: validate::new_constructor::not_const +// +// The `new` constructor must be `const fn`: it initialises a `static mut` +// in the generated WASM module, which only `const` evaluation can produce. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub fn new() -> Self { + Self + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/methods/new_constructor_not_const.stderr b/contract-macro/tests/compile-fail/methods/new_constructor_not_const.stderr new file mode 100644 index 0000000..443c630 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/new_constructor_not_const.stderr @@ -0,0 +1,5 @@ +error: `MyContract::new` must be a `const fn` to initialize the static STATE variable; add `const` to the function signature + --> tests/compile-fail/methods/new_constructor_not_const.rs:13:13 + | +13 | pub fn new() -> Self { + | ^^^^^^^^^^^^^^^^ diff --git a/contract-macro/tests/compile-fail/methods/new_constructor_wrong_return.rs b/contract-macro/tests/compile-fail/methods/new_constructor_wrong_return.rs new file mode 100644 index 0000000..e08851a --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/new_constructor_wrong_return.rs @@ -0,0 +1,20 @@ +// Pins: validate::new_constructor::wrong_return +// +// The `new` constructor must return `Self` (or the contract type name); +// any other return type makes the generated `static mut STATE` initialiser +// type-incompatible. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> u64 { + 0 + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/methods/new_constructor_wrong_return.stderr b/contract-macro/tests/compile-fail/methods/new_constructor_wrong_return.stderr new file mode 100644 index 0000000..2ce1b80 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/new_constructor_wrong_return.stderr @@ -0,0 +1,5 @@ +error: `MyContract::new` must return `Self` or `MyContract` + --> tests/compile-fail/methods/new_constructor_wrong_return.rs:14:28 + | +14 | pub const fn new() -> u64 { + | ^^^^^^ diff --git a/contract-macro/tests/compile-fail/methods/pub_method_async.rs b/contract-macro/tests/compile-fail/methods/pub_method_async.rs new file mode 100644 index 0000000..6d90363 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/pub_method_async.rs @@ -0,0 +1,23 @@ +// Pins: validate::public_method::async +// +// A public inherent method declared `async` must be rejected: WASM contracts +// run synchronously inside the host VM and have no executor to drive futures. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + + pub async fn fetch(&self) -> u64 { + 0 + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/methods/pub_method_async.stderr b/contract-macro/tests/compile-fail/methods/pub_method_async.stderr new file mode 100644 index 0000000..ec9f70d --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/pub_method_async.stderr @@ -0,0 +1,5 @@ +error: public method `fetch` cannot be async; WASM contracts do not support async execution + --> tests/compile-fail/methods/pub_method_async.rs:17:13 + | +17 | pub async fn fetch(&self) -> u64 { + | ^^^^^ diff --git a/contract-macro/tests/compile-fail/methods/pub_method_consume_self.rs b/contract-macro/tests/compile-fail/methods/pub_method_consume_self.rs new file mode 100644 index 0000000..2cd86d4 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/pub_method_consume_self.rs @@ -0,0 +1,22 @@ +// Pins: validate::public_method::consume_self +// +// A public inherent method consuming `self` must be rejected: the static +// STATE singleton cannot be moved out, only borrowed via `&self` / `&mut +// self`. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + + pub fn destroy(self) {} + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/methods/pub_method_consume_self.stderr b/contract-macro/tests/compile-fail/methods/pub_method_consume_self.stderr new file mode 100644 index 0000000..7aa41d1 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/pub_method_consume_self.stderr @@ -0,0 +1,5 @@ +error: public method `destroy` cannot consume `self`; use `&self` or `&mut self` instead + --> tests/compile-fail/methods/pub_method_consume_self.rs:18:24 + | +18 | pub fn destroy(self) {} + | ^^^^ diff --git a/contract-macro/tests/compile-fail/methods/pub_method_generic_params.rs b/contract-macro/tests/compile-fail/methods/pub_method_generic_params.rs new file mode 100644 index 0000000..9afda76 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/pub_method_generic_params.rs @@ -0,0 +1,24 @@ +// Pins: validate::public_method::generic_params +// +// A public inherent method with generic type parameters must be rejected: +// extern "C" wrappers require concrete types, so generics cannot survive +// monomorphisation into the WASM export surface. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + + pub fn process(&self, value: T) -> T { + value + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/methods/pub_method_generic_params.stderr b/contract-macro/tests/compile-fail/methods/pub_method_generic_params.stderr new file mode 100644 index 0000000..e40fa7a --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/pub_method_generic_params.stderr @@ -0,0 +1,5 @@ +error: public method `process` cannot have generic or const parameters; extern "C" wrappers require concrete types + --> tests/compile-fail/methods/pub_method_generic_params.rs:18:23 + | +18 | pub fn process(&self, value: T) -> T { + | ^^^ diff --git a/contract-macro/tests/compile-fail/methods/pub_method_impl_trait_param.rs b/contract-macro/tests/compile-fail/methods/pub_method_impl_trait_param.rs new file mode 100644 index 0000000..f322e1f --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/pub_method_impl_trait_param.rs @@ -0,0 +1,24 @@ +// Pins: validate::public_method::impl_trait_param +// +// A public inherent method using `impl Trait` in a parameter must be +// rejected: extern "C" wrappers need a concrete monomorphic type to +// deserialize into. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + + pub fn process(&self, handler: impl core::fmt::Display) { + let _ = handler; + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/methods/pub_method_impl_trait_param.stderr b/contract-macro/tests/compile-fail/methods/pub_method_impl_trait_param.stderr new file mode 100644 index 0000000..ae441a8 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/pub_method_impl_trait_param.stderr @@ -0,0 +1,5 @@ +error: public method `process` cannot use `impl Trait` in parameters; extern "C" wrappers require concrete types + --> tests/compile-fail/methods/pub_method_impl_trait_param.rs:18:40 + | +18 | pub fn process(&self, handler: impl core::fmt::Display) { + | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/contract-macro/tests/compile-fail/methods/pub_method_impl_trait_return.rs b/contract-macro/tests/compile-fail/methods/pub_method_impl_trait_return.rs new file mode 100644 index 0000000..61d6e50 --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/pub_method_impl_trait_return.rs @@ -0,0 +1,23 @@ +// Pins: validate::public_method::impl_trait_return +// +// A public inherent method returning `impl Trait` must be rejected: the +// extern "C" wrapper must serialize a concrete type, not an opaque one. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + + pub fn iter(&self) -> impl Iterator { + core::iter::empty() + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/methods/pub_method_impl_trait_return.stderr b/contract-macro/tests/compile-fail/methods/pub_method_impl_trait_return.stderr new file mode 100644 index 0000000..5ea62cf --- /dev/null +++ b/contract-macro/tests/compile-fail/methods/pub_method_impl_trait_return.stderr @@ -0,0 +1,5 @@ +error: public method `iter` cannot use `impl Trait` as return type; extern "C" wrappers require concrete types + --> tests/compile-fail/methods/pub_method_impl_trait_return.rs:17:31 + | +17 | pub fn iter(&self) -> impl Iterator { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/contract-macro/tests/compile-fail/missing_feature_gate.rs b/contract-macro/tests/compile-fail/missing_feature_gate.rs deleted file mode 100644 index deb2c34..0000000 --- a/contract-macro/tests/compile-fail/missing_feature_gate.rs +++ /dev/null @@ -1,3 +0,0 @@ -include!("../common/contract.rs"); - -fn main() {} diff --git a/contract-macro/tests/compile-fail/traits/trait_method_async.rs b/contract-macro/tests/compile-fail/traits/trait_method_async.rs new file mode 100644 index 0000000..596b876 --- /dev/null +++ b/contract-macro/tests/compile-fail/traits/trait_method_async.rs @@ -0,0 +1,26 @@ +// Pins: validate::trait_method::async +// +// A trait method exposed via `#[contract(expose = [...])]` cannot be +// declared `async`: WASM contracts run synchronously with no executor. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + } + + #[contract(expose = [fetch])] + impl AsyncTrait for MyContract { + async fn fetch(&self) -> u64 { + 0 + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/traits/trait_method_async.stderr b/contract-macro/tests/compile-fail/traits/trait_method_async.stderr new file mode 100644 index 0000000..8ce1e13 --- /dev/null +++ b/contract-macro/tests/compile-fail/traits/trait_method_async.stderr @@ -0,0 +1,5 @@ +error: trait method `AsyncTrait::fetch` cannot be async; WASM contracts do not support async execution + --> tests/compile-fail/traits/trait_method_async.rs:20:9 + | +20 | async fn fetch(&self) -> u64 { + | ^^^^^ diff --git a/contract-macro/tests/compile-fail/traits/trait_method_consume_self.rs b/contract-macro/tests/compile-fail/traits/trait_method_consume_self.rs new file mode 100644 index 0000000..e3baa92 --- /dev/null +++ b/contract-macro/tests/compile-fail/traits/trait_method_consume_self.rs @@ -0,0 +1,24 @@ +// Pins: validate::trait_method::consume_self +// +// A trait method exposed via `#[contract(expose = [...])]` cannot consume +// `self`: the static STATE singleton can only be borrowed. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + } + + #[contract(expose = [destroy])] + impl Destructible for MyContract { + fn destroy(self) {} + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/traits/trait_method_consume_self.stderr b/contract-macro/tests/compile-fail/traits/trait_method_consume_self.stderr new file mode 100644 index 0000000..da782e8 --- /dev/null +++ b/contract-macro/tests/compile-fail/traits/trait_method_consume_self.stderr @@ -0,0 +1,5 @@ +error: trait method `Destructible::destroy` cannot consume `self`; use `&self` or `&mut self` instead + --> tests/compile-fail/traits/trait_method_consume_self.rs:20:20 + | +20 | fn destroy(self) {} + | ^^^^ diff --git a/contract-macro/tests/compile-fail/traits/trait_method_generic_params.rs b/contract-macro/tests/compile-fail/traits/trait_method_generic_params.rs new file mode 100644 index 0000000..a163a63 --- /dev/null +++ b/contract-macro/tests/compile-fail/traits/trait_method_generic_params.rs @@ -0,0 +1,27 @@ +// Pins: validate::trait_method::generic_params +// +// A trait method exposed via `#[contract(expose = [...])]` cannot carry +// generic type parameters: the generated extern "C" wrapper requires +// concrete types. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + } + + #[contract(expose = [process])] + impl Processor for MyContract { + fn process(&self, value: T) { + let _ = value; + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/traits/trait_method_generic_params.stderr b/contract-macro/tests/compile-fail/traits/trait_method_generic_params.stderr new file mode 100644 index 0000000..c971b81 --- /dev/null +++ b/contract-macro/tests/compile-fail/traits/trait_method_generic_params.stderr @@ -0,0 +1,5 @@ +error: trait method `Processor::process` cannot have generic or const parameters; extern "C" wrappers require concrete types + --> tests/compile-fail/traits/trait_method_generic_params.rs:21:19 + | +21 | fn process(&self, value: T) { + | ^^^ diff --git a/contract-macro/tests/compile-fail/traits/trait_method_impl_trait_param.rs b/contract-macro/tests/compile-fail/traits/trait_method_impl_trait_param.rs new file mode 100644 index 0000000..ea07008 --- /dev/null +++ b/contract-macro/tests/compile-fail/traits/trait_method_impl_trait_param.rs @@ -0,0 +1,26 @@ +// Pins: validate::trait_method::impl_trait_param +// +// A trait method exposed via `#[contract(expose = [...])]` cannot use +// `impl Trait` in a parameter: the wrapper needs a concrete type. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + } + + #[contract(expose = [run])] + impl Runner for MyContract { + fn run(&self, handler: impl core::fmt::Display) { + let _ = handler; + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/traits/trait_method_impl_trait_param.stderr b/contract-macro/tests/compile-fail/traits/trait_method_impl_trait_param.stderr new file mode 100644 index 0000000..4e231bf --- /dev/null +++ b/contract-macro/tests/compile-fail/traits/trait_method_impl_trait_param.stderr @@ -0,0 +1,5 @@ +error: trait method `Runner::run` cannot use `impl Trait` in parameters; extern "C" wrappers require concrete types + --> tests/compile-fail/traits/trait_method_impl_trait_param.rs:20:32 + | +20 | fn run(&self, handler: impl core::fmt::Display) { + | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/contract-macro/tests/compile-fail/traits/trait_method_impl_trait_return.rs b/contract-macro/tests/compile-fail/traits/trait_method_impl_trait_return.rs new file mode 100644 index 0000000..45461d5 --- /dev/null +++ b/contract-macro/tests/compile-fail/traits/trait_method_impl_trait_return.rs @@ -0,0 +1,26 @@ +// Pins: validate::trait_method::impl_trait_return +// +// A trait method exposed via `#[contract(expose = [...])]` cannot return +// `impl Trait`: the wrapper must serialize a concrete type. + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + } + + #[contract(expose = [items])] + impl Collection for MyContract { + fn items(&self) -> impl Iterator { + core::iter::empty() + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/traits/trait_method_impl_trait_return.stderr b/contract-macro/tests/compile-fail/traits/trait_method_impl_trait_return.stderr new file mode 100644 index 0000000..4595c7b --- /dev/null +++ b/contract-macro/tests/compile-fail/traits/trait_method_impl_trait_return.stderr @@ -0,0 +1,5 @@ +error: trait method `Collection::items` cannot use `impl Trait` as return type; extern "C" wrappers require concrete types + --> tests/compile-fail/traits/trait_method_impl_trait_return.rs:20:28 + | +20 | fn items(&self) -> impl Iterator { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/contract-macro/tests/compile-fail/traits/trait_method_no_self_non_default.rs b/contract-macro/tests/compile-fail/traits/trait_method_no_self_non_default.rs new file mode 100644 index 0000000..eb02226 --- /dev/null +++ b/contract-macro/tests/compile-fail/traits/trait_method_no_self_non_default.rs @@ -0,0 +1,28 @@ +// Pins: validate::trait_method::no_self_non_default +// +// A trait method exposed via `#[contract(expose = [...])]` with a non-empty +// body (a non-default impl) must take a `self` receiver. Associated +// functions are only allowed when the body is empty `{}`, signalling +// "delegate to the trait default". + +use dusk_forge_contract::contract; + +#[contract] +mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + } + + #[contract(expose = [version])] + impl Versioned for MyContract { + fn version() -> &'static str { + "1.0" + } + } +} + +fn main() {} diff --git a/contract-macro/tests/compile-fail/traits/trait_method_no_self_non_default.stderr b/contract-macro/tests/compile-fail/traits/trait_method_no_self_non_default.stderr new file mode 100644 index 0000000..efdf933 --- /dev/null +++ b/contract-macro/tests/compile-fail/traits/trait_method_no_self_non_default.stderr @@ -0,0 +1,5 @@ +error: trait method `Versioned::version` must have a `self` receiver; for associated functions, use an empty body `{}` to expose the default impl + --> tests/compile-fail/traits/trait_method_no_self_non_default.rs:22:9 + | +22 | fn version() -> &'static str { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/contract-macro/tests/compile-pass/Cargo.toml b/contract-macro/tests/compile-pass/Cargo.toml new file mode 100644 index 0000000..400f269 --- /dev/null +++ b/contract-macro/tests/compile-pass/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "compile-pass-fixtures" +version = "0.0.0" +edition = "2024" +publish = false + +[workspace] + +[features] +default = ["contract"] +contract = [] +data-driver = [] + +[dependencies] +dusk-forge = { path = "../../../" } diff --git a/contract-macro/tests/compile-pass/src/lib.rs b/contract-macro/tests/compile-pass/src/lib.rs new file mode 100644 index 0000000..5b0a01a --- /dev/null +++ b/contract-macro/tests/compile-pass/src/lib.rs @@ -0,0 +1,9 @@ +//! Compile-pass fixtures for the contract macro. +//! +//! Each fixture pins a valid contract shape that `tests/test-contract/` +//! does **not** exercise. See `tests/README.md` for the per-fixture +//! conventions (`// Pins:` header, topic taxonomy). + +#![no_std] + +pub mod methods; diff --git a/contract-macro/tests/compile-pass/src/methods/init_absent.rs b/contract-macro/tests/compile-pass/src/methods/init_absent.rs new file mode 100644 index 0000000..251a37f --- /dev/null +++ b/contract-macro/tests/compile-pass/src/methods/init_absent.rs @@ -0,0 +1,22 @@ +// Pins: validate::init_method::absent_ok +// +// A contract without an `init` method must compile: `init` is optional, +// the host calls it only when present. `tests/test-contract/` defines an +// `init`; this fixture pins the no-`init` shape. + +#[dusk_forge::contract] +pub mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> Self { + Self + } + } + + impl Default for MyContract { + fn default() -> Self { + Self::new() + } + } +} diff --git a/contract-macro/tests/compile-pass/src/methods/init_no_params.rs b/contract-macro/tests/compile-pass/src/methods/init_no_params.rs new file mode 100644 index 0000000..9e53ed7 --- /dev/null +++ b/contract-macro/tests/compile-pass/src/methods/init_no_params.rs @@ -0,0 +1,29 @@ +// Pins: validate::init_method::no_params_ok +// +// An `init` method declared as `&mut self` with no additional parameters +// must compile. `tests/test-contract/` exercises `init(&mut self, owner)`; +// this fixture pins the parameter-less variant. + +#[dusk_forge::contract] +pub mod my_contract { + pub struct MyContract { + initialized: bool, + } + + impl MyContract { + pub const fn new() -> Self { + Self { initialized: false } + } + + #[contract(no_event)] + pub fn init(&mut self) { + self.initialized = true; + } + } + + impl Default for MyContract { + fn default() -> Self { + Self::new() + } + } +} diff --git a/contract-macro/tests/compile-pass/src/methods/mod.rs b/contract-macro/tests/compile-pass/src/methods/mod.rs new file mode 100644 index 0000000..4fe16fd --- /dev/null +++ b/contract-macro/tests/compile-pass/src/methods/mod.rs @@ -0,0 +1,3 @@ +pub mod init_absent; +pub mod init_no_params; +pub mod new_returns_contract_name; diff --git a/contract-macro/tests/compile-pass/src/methods/new_returns_contract_name.rs b/contract-macro/tests/compile-pass/src/methods/new_returns_contract_name.rs new file mode 100644 index 0000000..6f6615f --- /dev/null +++ b/contract-macro/tests/compile-pass/src/methods/new_returns_contract_name.rs @@ -0,0 +1,23 @@ +// Pins: validate::new_constructor::valid (return-by-type-name shape) +// +// The `new` constructor may name the contract type explicitly in its +// return type instead of `Self`. `tests/test-contract/` exercises the +// `-> Self` form; this fixture pins the `-> ContractName` form so the +// macro must keep accepting both. + +#[dusk_forge::contract] +pub mod my_contract { + pub struct MyContract; + + impl MyContract { + pub const fn new() -> MyContract { + MyContract + } + } + + impl Default for MyContract { + fn default() -> Self { + Self::new() + } + } +} diff --git a/contract-macro/tests/compile_fail.rs b/contract-macro/tests/compile_fail.rs index 1ad9144..9f69a1a 100644 --- a/contract-macro/tests/compile_fail.rs +++ b/contract-macro/tests/compile_fail.rs @@ -1,7 +1,7 @@ #[test] fn compile_fail_tests() { let t = trybuild::TestCases::new(); - t.compile_fail("tests/compile-fail/*.rs"); + t.compile_fail("tests/compile-fail/**/*.rs"); } #[test] diff --git a/contract-macro/tests/compile_pass.rs b/contract-macro/tests/compile_pass.rs new file mode 100644 index 0000000..95de41d --- /dev/null +++ b/contract-macro/tests/compile_pass.rs @@ -0,0 +1,29 @@ +//! Compile-pass harness. +//! +//! Builds the `tests/compile-pass/` sub-crate via `cargo check` and asserts +//! success. Each `.rs` file under `tests/compile-pass/src/` pins one valid +//! contract shape end-to-end (macro expansion + downstream type-check). +//! +//! The sub-crate pattern is used (instead of trybuild's `compile_pass` +//! glob) because the macro emits a `compile_error!` when neither +//! `contract` nor `data-driver` is enabled, and trybuild has no +//! per-fixture feature configuration. The sub-crate sets the `contract` +//! feature by default and mirrors the topic structure of +//! `tests/compile-fail/`. + +#[test] +fn compile_pass_tests() { + let output = std::process::Command::new("cargo") + .arg("check") + .arg("--all-targets") + .current_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/compile-pass")) + .output() + .expect("failed to run cargo check"); + + assert!( + output.status.success(), + "compile-pass fixtures failed to build:\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); +} From 6d946246f21895a35746089e89673217793f7088 Mon Sep 17 00:00:00 2001 From: moana Date: Fri, 15 May 2026 14:49:25 +0200 Subject: [PATCH 2/2] chore: Add changelog entry for macro test harness --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 773bbba..6d4f735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add compile-fail and compile-pass test harness for the contract macro. - Add `#[contract(emits = [...])]` method-level attribute for manual event registration, covering both trait impls with default implementations and inherent methods that delegate to helpers in other crates. - Add compile error when a public `&mut self` method emits no events. Suppress with `#[contract(no_event)]`. - Add detection of variable identifiers used as `abi::emit()` topics (warning pending `proc_macro_diagnostic` stabilisation).