Skip to main content

dusk_forge_contract/
lib.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7//! Procedural macro for the `#[contract]` attribute.
8//!
9//! This macro is applied to a module containing a contract struct and its
10//! impl block. It extracts metadata about public methods and events, and
11//! generates a `CONTRACT_SCHEMA` constant plus extern "C" wrappers.
12//!
13//! # Pipeline
14//!
15//! 1. [`parse::analyze`] walks the user module and produces an
16//!    [`parse::Analysis`] (functions, registered events, imports, contract
17//!    identifier).
18//! 2. [`generate`] / [`data_driver`] consume the analysis and emit the contract
19//!    or data-driver bindings.
20//!
21//! `lib.rs` only orchestrates these two phases — all walking, validation,
22//! and IR construction lives in the `parse` module.
23//!
24//! # Example
25//!
26//! ```ignore
27//! #[contract]
28//! mod my_contract {
29//!     use my_crate::MyType;
30//!     use dusk_core::abi;
31//!
32//!     pub struct MyContract {
33//!         value: u64,
34//!     }
35//!
36//!     impl MyContract {
37//!         pub fn set_value(&mut self, value: MyType) {
38//!             // ...
39//!         }
40//!     }
41//! }
42//! ```
43
44#![deny(missing_docs)]
45#![deny(rustdoc::broken_intra_doc_links)]
46#![deny(unused_must_use)]
47#![deny(unused_extern_crates)]
48#![deny(clippy::pedantic)]
49#![warn(missing_debug_implementations, unreachable_pub, rustdoc::all)]
50
51mod data_driver;
52mod generate;
53mod parse;
54mod resolve;
55mod validate;
56
57use proc_macro::TokenStream;
58use syn::{ItemMod, parse_macro_input};
59
60/// The main contract proc macro.
61///
62/// Applied to a module containing a contract struct and impl block.
63/// Extracts metadata and generates schema + extern wrappers.
64///
65/// # Errors
66///
67/// This macro will produce compile errors if:
68/// - The module has no content (just a declaration like `mod foo;`)
69/// - The module contains glob imports (`use foo::*`)
70/// - The module contains relative imports (`use self::`, `use super::`, `use
71///   crate::`)
72/// - The module contains multiple `pub struct` declarations
73/// - The module contains no `pub struct`
74/// - The module contains no impl block for the contract struct
75/// - A public method has no `self` receiver (associated functions)
76/// - A public method has generic type or const parameters
77/// - A public method is async
78/// - A public method consumes `self` instead of borrowing it
79/// - A public method uses `impl Trait` in parameters or return type
80#[proc_macro_attribute]
81pub fn contract(attr: TokenStream, item: TokenStream) -> TokenStream {
82    let module = parse_macro_input!(item as ItemMod);
83
84    let registered_events = match parse::events::module_events(attr.into()) {
85        Ok(events) => events,
86        Err(e) => return e.to_compile_error().into(),
87    };
88
89    let Some((_, items)) = &module.content else {
90        return syn::Error::new_spanned(&module, "#[contract] requires a module with content")
91            .to_compile_error()
92            .into();
93    };
94
95    let analysis = match parse::analyze(&module, items, &registered_events) {
96        Ok(analysis) => analysis,
97        Err(e) => return e.to_compile_error().into(),
98    };
99
100    generate::contract_module(&module, items, &analysis).into()
101}