19 releases (1 stable)
1.0.2 | Nov 5, 2024 |
---|---|
1.0.1-v1calls | Aug 5, 2024 |
0.2.4 | Mar 26, 2024 |
0.2.4-v1calls2 | Jun 3, 2024 |
0.1.0-alpha2 | Jan 24, 2024 |
#889 in Magic Beans
159 downloads per month
Used in 6 crates
(via pendzl_lang_macro)
110KB
1.5K
SLoC
1. Code Generation Module
This module contains the implementations of macros used for code generation in the library.
-
Contains:
- Implementation of the
codegen::implementation
macro:- Implements the
implementation
macro, which injects default fn implementations (& overrides) of standard traits.
- Implements the
- Boilerplate code for implementations injecting:
- Every implementation of a trait (PSP22, PSP34, PSP22Metadata etc) is injected with the default implementation of the trait.
- Implementation of the
codegen::storage_item
macro:- Implements functionality for the
storage_item
macro, handling storage-related code generation.
- Implements functionality for the
- Implementation of the
codegen::storage_field_getter_derive
macro:- Contains the implementation for deriving the
StorageFieldGetter
trait for storage structures.
- Contains the implementation for deriving the
- Implementation of the
How does it work?
Macro Implementation: The generate
Function
The generate
function serves as the core entry point for the procedural macro, processing an ink! module, injecting default implementations for specified traits & handling overridden methods.
Parsing Attributes and Module Input
The function begins by parsing the attributes provided to the macro to determine which traits require default implementations. The attributes are expected to be a list of trait names, such as #[implementation(PSP22, PSP34)]
. These are parsed using the syn::parse2::<AttributeArgs>
function, which results in a vector of trait names (to_inject_default_impls_vec
).
Identifying the Storage Struct
The storage struct is the central data structure for an ink! contract, representing the contract's state. The extract_storage_struct_name
function iterates over the module's items to find the struct annotated with #[ink(storage)]
. Once found, it extracts the struct's identifier (name) for later use in implementing traits.
Processing Overridden Methods
Users can provide custom implementations for specific trait methods by annotating functions with #[overrider(TraitName)]
. The consume_overriders
function searches for these annotated functions, extracts them, and removes them from the module's items to prevent duplication. It collects these overrides into an OverridenFnMap
, mapping trait names to the corresponding overridden functions.
Injecting Trait Implementations
For each trait specified in the attributes (eg. for pendzl::implementation(PSP22, Pausable) would be PSP22 and Pausable), it calls the corresponding implementation function (e.g., impl_psp22
, impl_pausable
), passing impl_args
as the context. These implementation functions generate the default implementations of the traits, modify the module's items, and handle any necessary imports.
Note that all of the impl_XYZ functions follow the same pattern - injecting default implementations for internal and public traits & potentially injecting required imports. Such boilerplate is necessary for simplicity and further DX down the line.
Cleaning Up Imports
Pendzl's codegen removes unnecessary base trait imports when extended traits are present to avoiding potential conflicts or redundancy. This optimization ensures that the generated code includes only the necessary imports.
It also auto injects StorageFieldGetter
trait and any overridden trait implementations collected earlier.
Trait Implementation Functions
following section talks about the contents of implenentation.rs
file
The Pendzl codegen provides several functions to generate default implementations for specific traits, such as impl_psp22
, impl_pausable
, impl_psp22_vault
, impl_psp34
, impl_psp34_burnable
, impl_ownable
, impl_vesting
, impl_set_code_hash
... and more.
. Each of these functions follows a similar pattern:
- Context Retrieval: Accesses the storage struct name and other necessary context from
impl_args
. - Implementation Preparation: Constructs default implementations for internal traits and public traits, defining methods that either provide default behavior or delegate to internal methods.
- Override Handling: Calls
override_functions
to apply any user-provided overrides to the trait implementations. - Import Management: Adds necessary imports to
impl_args.imports
, ensuring all required types and traits are available in the generated code. - Updating Module Items: Appends the constructed implementations to
impl_args.items
.
Example: Implementing the PSP22 Trait
The impl_psp22
function injects the default implementation of the PSP22
trait into the module. It constructs the internal default implementation (PSP22InternalDefaultImpl
), the internal trait implementation (PSP22Internal
), the public default implementation (PSP22DefaultImpl
), and the public trait implementation (PSP22
). Each of these implementations defines methods that provide default behaviors or delegate to internal methods.
The function then calls override_functions
to handle any overrides provided by the user for both PSP22Internal
and PSP22
. It manages necessary imports, such as pendzl::contracts::psp22::*
, and appends the implementations to the module items.
Storage Item Handling
The storage_item
function implements the #[pendzl::storage_item]
macro, preparing structs or enums to be part of the contract's storage. It allows the use of the #[lazy]
attribute to mark fields that should be lazily loaded and wrapped in ::ink::storage::Lazy
. The macro also generates constant storage keys for every mapping or lazy field, following recommendations from ink!'s storage layout documentation.
Dependencies
~8MB
~159K SLoC