-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(node_framework): Implement FromContext and IntoContext derive ma…
…cro (#2330) ## What ❔ - Improves context interfaces: removes unneded `async` when fetching resources, and made `add_task` accept `T` instead of `Box`. - Adds previously proposed `FromContext` and `IntoContext` macro that will allow to redefine the wiring layer interface so that it doesn't have direct access to the context.⚠️ I didn't port the whole framework to using macros, since there may be changes in the macro itself during this review. Once we merge this, I will simultaneously rework the `WiringLayer` interface and will port layers. For now, I've used new macros in the `BatchStatusUpdater` layer to see how it works. Seems that it works fine 😅 ## Why ❔ Ergonomics. ## Checklist <!-- Check your PR fulfills the following items. --> <!-- For draft PRs check the boxes as you complete them. --> - [ ] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [ ] Tests for the changes have been added / updated. - [ ] Documentation comments have been added / updated. - [ ] Code has been formatted via `zk fmt` and `zk lint`.
- Loading branch information
Showing
65 changed files
with
1,089 additions
and
221 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[package] | ||
name = "zksync_node_framework_derive" | ||
version.workspace = true | ||
edition.workspace = true | ||
authors.workspace = true | ||
homepage.workspace = true | ||
repository.workspace = true | ||
license.workspace = true | ||
keywords.workspace = true | ||
categories.workspace = true | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[dependencies] | ||
syn = { workspace = true, features = ["full"] } | ||
quote.workspace = true | ||
proc-macro2.workspace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
use std::fmt; | ||
|
||
use syn::{GenericArgument, PathArguments, Type}; | ||
|
||
use crate::labels::CtxLabel; | ||
|
||
/// Representation of a single structure field. | ||
pub(crate) struct Field { | ||
/// Name of the field. | ||
pub(crate) ident: syn::Ident, | ||
/// Type of the field. | ||
pub(crate) ty: syn::Type, | ||
/// Parsed label. | ||
pub(crate) label: CtxLabel, | ||
} | ||
|
||
impl fmt::Debug for Field { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.debug_struct("Field") | ||
.field("ident", &self.ident) | ||
.field("label", &self.label) | ||
.finish() | ||
} | ||
} | ||
|
||
// Helper function to check if a field is of type Option<T> and extract T | ||
pub(crate) fn extract_option_inner_type(ty: &Type) -> Option<&Type> { | ||
if let Type::Path(type_path) = ty { | ||
// Check if the path is `Option` | ||
if type_path.path.segments.len() == 1 { | ||
let segment = &type_path.path.segments[0]; | ||
if segment.ident == "Option" { | ||
if let PathArguments::AngleBracketed(angle_bracketed_args) = &segment.arguments { | ||
if angle_bracketed_args.args.len() == 1 { | ||
if let GenericArgument::Type(inner_type) = &angle_bracketed_args.args[0] { | ||
return Some(inner_type); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
None | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
use std::fmt; | ||
|
||
use syn::{spanned::Spanned as _, Attribute, Result}; | ||
|
||
/// Context label, e.g. `ctx(crate = "crate")`. | ||
#[derive(Default)] | ||
pub(crate) struct CtxLabel { | ||
/// Special attribute that marks the derive as internal. | ||
/// Alters the path to the trait to be implemented. | ||
pub(crate) krate: Option<syn::Path>, // `crate` is a reserved keyword and cannot be a raw identifier. | ||
pub(crate) span: Option<proc_macro2::Span>, | ||
pub(crate) task: bool, | ||
pub(crate) default: bool, | ||
} | ||
|
||
impl fmt::Debug for CtxLabel { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
// For some weird reason, doc tests fail with the derived impl, stating that | ||
// `syn::Path` does not implement `Debug`. | ||
f.debug_struct("CtxLabel") | ||
.field("krate", &self.krate.as_ref().and_then(|p| p.get_ident())) | ||
.field("span", &self.span) | ||
.field("task", &self.task) | ||
.field("default", &self.default) | ||
.finish() | ||
} | ||
} | ||
|
||
impl CtxLabel { | ||
const ATTR_NAME: &'static str = "context"; | ||
const CRATE_LABEL: &'static str = "crate"; | ||
const TASK_LABEL: &'static str = "task"; | ||
const DEFAULT_LABEL: &'static str = "default"; | ||
const LABELS: &'static [&'static str] = | ||
&[Self::CRATE_LABEL, Self::TASK_LABEL, Self::DEFAULT_LABEL]; | ||
|
||
pub(crate) fn parse(attrs: &[Attribute]) -> Result<Option<Self>> { | ||
let mut self_ = Self::default(); | ||
|
||
let mut found = false; | ||
for attr in attrs { | ||
if attr.path().is_ident(Self::ATTR_NAME) { | ||
found = true; | ||
self_.span = Some(attr.span()); | ||
match attr.meta { | ||
syn::Meta::Path(_) => { | ||
// No values to parse. | ||
break; | ||
} | ||
syn::Meta::NameValue(_) => { | ||
return Err(syn::Error::new_spanned( | ||
attr, | ||
"Unexpected value, expected a list of labels", | ||
)); | ||
} | ||
syn::Meta::List(_) => { | ||
// Do nothing, parsing happens below. | ||
} | ||
} | ||
attr.parse_nested_meta(|meta| { | ||
let mut added = false; | ||
for &label in Self::LABELS { | ||
if meta.path.is_ident(label) { | ||
match label { | ||
Self::CRATE_LABEL => { | ||
let value = meta.value()?; | ||
let path: syn::Path = value.parse()?; | ||
self_.krate = Some(path); | ||
} | ||
Self::TASK_LABEL => { | ||
self_.task = true; | ||
} | ||
Self::DEFAULT_LABEL => { | ||
self_.default = true; | ||
} | ||
_ => unreachable!(), | ||
} | ||
added = true; | ||
break; | ||
} | ||
} | ||
|
||
if !added { | ||
let err_msg = | ||
format!("Unexpected token, supported labels: `{:?}`", Self::LABELS); | ||
let err = syn::Error::new_spanned(attr, err_msg); | ||
return Err(err); | ||
} | ||
Ok(()) | ||
})?; | ||
} | ||
} | ||
if !found { | ||
return Ok(None); | ||
} | ||
Ok(Some(self_)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
extern crate proc_macro; | ||
|
||
use proc_macro::TokenStream; | ||
use syn::{parse_macro_input, DeriveInput}; | ||
|
||
use crate::macro_impl::{MacroImpl, MacroKind}; | ||
|
||
pub(crate) mod helpers; | ||
mod labels; | ||
mod macro_impl; | ||
|
||
/// Derive macro for the `FromContext` trait. | ||
/// Allows to automatically fetch all the resources and tasks from the context. | ||
/// | ||
/// See the trait documentation for more details. | ||
#[proc_macro_derive(FromContext, attributes(context))] | ||
pub fn from_context_derive(input: TokenStream) -> TokenStream { | ||
// Parse the input tokens into a syntax tree | ||
let input = parse_macro_input!(input as DeriveInput); | ||
MacroImpl::parse(MacroKind::FromContext, input) | ||
.and_then(|from_context| from_context.render()) | ||
.unwrap_or_else(syn::Error::into_compile_error) | ||
.into() | ||
} | ||
|
||
/// Derive macro for the `IntoContext` trait. | ||
/// Allows to automatically insert all the resources in tasks created by the wiring layer | ||
/// into the context. | ||
/// | ||
/// See the trait documentation for more details. | ||
#[proc_macro_derive(IntoContext, attributes(context))] | ||
pub fn into_context_derive(input: TokenStream) -> TokenStream { | ||
// Parse the input tokens into a syntax tree | ||
let input = parse_macro_input!(input as DeriveInput); | ||
MacroImpl::parse(MacroKind::IntoContext, input) | ||
.and_then(|from_context| from_context.render()) | ||
.unwrap_or_else(syn::Error::into_compile_error) | ||
.into() | ||
} |
Oops, something went wrong.