This repository has been archived by the owner on Nov 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Metadata V15: Expose pallet documentation #13452
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
521108e
frame/proc: Helpers to parse pallet documentation attributes
lexnv 6915a65
frame/proc: Expand pallet with runtime metadata documentation
lexnv 771bdd7
frame/dispatch: Implement doc function getter for dispatch
lexnv 682adad
frame/tests: Check exposed runtime metadata documentation
lexnv 7e7661d
frame/tests: Add UI tests for `pallet_doc` attribute
lexnv 6d38dbb
frame/proc: Document pallet_doc attribute
lexnv 7f32c9f
Merge remote-tracking branch 'origin/master' into lexnv/md15_pallet_d…
c5c894f
frame/support: Use `derive_syn_parse`
lexnv bb8cd03
Update frame/support/procedural/src/lib.rs
lexnv bf9400b
frame/support: Improve documentation
lexnv File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
221 changes: 221 additions & 0 deletions
221
frame/support/procedural/src/pallet/expand/documentation.rs
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,221 @@ | ||
// This file is part of Substrate. | ||
|
||
// Copyright (C) Parity Technologies (UK) Ltd. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
use crate::pallet::Def; | ||
use derive_syn_parse::Parse; | ||
use proc_macro2::TokenStream; | ||
use quote::ToTokens; | ||
use syn::{ | ||
parse::{self, Parse, ParseStream}, | ||
spanned::Spanned, | ||
Attribute, Lit, | ||
}; | ||
|
||
const DOC: &'static str = "doc"; | ||
const PALLET_DOC: &'static str = "pallet_doc"; | ||
|
||
mod keywords { | ||
syn::custom_keyword!(include_str); | ||
} | ||
|
||
/// Get the documentation file path from the `pallet_doc` attribute. | ||
/// | ||
/// Supported format: | ||
/// `#[pallet_doc(PATH)]`: The path of the file from which the documentation is loaded | ||
fn parse_pallet_doc_value(attr: &Attribute) -> syn::Result<DocMetaValue> { | ||
let span = attr.span(); | ||
|
||
let meta = attr.parse_meta()?; | ||
let syn::Meta::List(metalist) = meta else { | ||
let msg = "The `pallet_doc` attribute must receive arguments as a list. Supported format: `pallet_doc(PATH)`"; | ||
return Err(syn::Error::new(span, msg)) | ||
}; | ||
|
||
let paths: Vec<_> = metalist | ||
.nested | ||
.into_iter() | ||
.map(|nested| { | ||
let syn::NestedMeta::Lit(lit) = nested else { | ||
let msg = "The `pallet_doc` received an unsupported argument. Supported format: `pallet_doc(PATH)`"; | ||
return Err(syn::Error::new(span, msg)) | ||
}; | ||
|
||
Ok(lit) | ||
}) | ||
.collect::<syn::Result<_>>()?; | ||
|
||
if paths.len() != 1 { | ||
let msg = "The `pallet_doc` attribute must receive only one argument. Supported format: `pallet_doc(PATH)`"; | ||
return Err(syn::Error::new(span, msg)) | ||
} | ||
|
||
Ok(DocMetaValue::Path(paths[0].clone())) | ||
} | ||
|
||
/// Get the value from the `doc` comment attribute: | ||
/// | ||
/// Supported formats: | ||
/// - `#[doc = "A doc string"]`: Documentation as a string literal | ||
/// - `#[doc = include_str!(PATH)]`: Documentation obtained from a path | ||
fn parse_doc_value(attr: &Attribute) -> Option<DocMetaValue> { | ||
let Some(ident) = attr.path.get_ident() else { | ||
return None | ||
}; | ||
if ident != DOC { | ||
return None | ||
} | ||
|
||
let parser = |input: ParseStream| DocParser::parse(input); | ||
let result = parse::Parser::parse2(parser, attr.tokens.clone()).ok()?; | ||
|
||
if let Some(lit) = result.lit { | ||
Some(DocMetaValue::Lit(lit)) | ||
} else if let Some(include_doc) = result.include_doc { | ||
Some(DocMetaValue::Path(include_doc.lit)) | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
/// Parse the include_str attribute. | ||
#[derive(Debug, Parse)] | ||
struct IncludeDocParser { | ||
_include_str: keywords::include_str, | ||
_eq_token: syn::token::Bang, | ||
#[paren] | ||
_paren: syn::token::Paren, | ||
#[inside(_paren)] | ||
lit: Lit, | ||
} | ||
|
||
/// Parse the doc literal. | ||
#[derive(Debug, Parse)] | ||
struct DocParser { | ||
_eq_token: syn::token::Eq, | ||
#[peek(Lit)] | ||
lit: Option<Lit>, | ||
#[parse_if(lit.is_none())] | ||
include_doc: Option<IncludeDocParser>, | ||
} | ||
|
||
/// Supported documentation tokens. | ||
#[derive(Debug)] | ||
enum DocMetaValue { | ||
/// Documentation with string literals. | ||
/// | ||
/// `#[doc = "Lit"]` | ||
Lit(Lit), | ||
/// Documentation with `include_str!` macro. | ||
/// | ||
/// The string literal represents the file `PATH`. | ||
/// | ||
/// `#[doc = include_str!(PATH)]` | ||
Path(Lit), | ||
} | ||
|
||
impl ToTokens for DocMetaValue { | ||
fn to_tokens(&self, tokens: &mut TokenStream) { | ||
match self { | ||
DocMetaValue::Lit(lit) => lit.to_tokens(tokens), | ||
DocMetaValue::Path(path_lit) => { | ||
let decl = quote::quote!(include_str!(#path_lit)); | ||
tokens.extend(decl) | ||
}, | ||
} | ||
} | ||
} | ||
|
||
/// Extract the documentation from the given pallet definition | ||
/// to include in the runtime metadata. | ||
/// | ||
/// Implement a `pallet_documentation_metadata` function to fetch the | ||
/// documentation that is included in the metadata. | ||
/// | ||
/// The documentation is placed at the top of the module similar to: | ||
/// | ||
/// ```ignore | ||
/// #[pallet] | ||
/// /// Documentation for pallet | ||
/// #[doc = "Documentation for pallet"] | ||
/// #[doc = include_str!("../README.md")] | ||
/// #[pallet_doc("../documentation1.md")] | ||
/// #[pallet_doc("../documentation2.md")] | ||
/// pub mod pallet {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here you mention that the docs are above the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for spotting this! I've picked the |
||
/// ``` | ||
/// | ||
/// # pallet_doc | ||
/// | ||
/// The `pallet_doc` attribute can only be provided with one argument, | ||
/// which is the file path that holds the documentation to be added to the metadata. | ||
/// | ||
/// Unlike the `doc` attribute, the documentation provided to the `proc_macro` attribute is | ||
/// not inserted at the beginning of the module. | ||
pub fn expand_documentation(def: &mut Def) -> proc_macro2::TokenStream { | ||
let frame_support = &def.frame_support; | ||
let type_impl_gen = &def.type_impl_generics(proc_macro2::Span::call_site()); | ||
let type_use_gen = &def.type_use_generics(proc_macro2::Span::call_site()); | ||
let pallet_ident = &def.pallet_struct.pallet; | ||
let where_clauses = &def.config.where_clause; | ||
|
||
// TODO: Use [drain_filter](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain_filter) when it is stable. | ||
|
||
// The `pallet_doc` attributes are excluded from the generation of the pallet, | ||
// but they are included in the runtime metadata. | ||
let mut pallet_docs = Vec::with_capacity(def.item.attrs.len()); | ||
let mut index = 0; | ||
while index < def.item.attrs.len() { | ||
let attr = &def.item.attrs[index]; | ||
if let Some(ident) = attr.path.get_ident() { | ||
if ident == PALLET_DOC { | ||
let elem = def.item.attrs.remove(index); | ||
pallet_docs.push(elem); | ||
// Do not increment the index, we have just removed the | ||
// element from the attributes. | ||
continue | ||
} | ||
} | ||
|
||
index += 1; | ||
} | ||
|
||
// Capture the `#[doc = include_str!("../README.md")]` and `#[doc = "Documentation"]`. | ||
let mut docs: Vec<_> = def.item.attrs.iter().filter_map(parse_doc_value).collect(); | ||
|
||
// Capture the `#[pallet_doc("../README.md")]`. | ||
let pallet_docs: Vec<_> = match pallet_docs | ||
.into_iter() | ||
.map(|attr| parse_pallet_doc_value(&attr)) | ||
.collect::<syn::Result<_>>() | ||
{ | ||
Ok(docs) => docs, | ||
Err(err) => return err.into_compile_error(), | ||
}; | ||
|
||
docs.extend(pallet_docs); | ||
|
||
quote::quote!( | ||
impl<#type_impl_gen> #pallet_ident<#type_use_gen> #where_clauses{ | ||
|
||
#[doc(hidden)] | ||
pub fn pallet_documentation_metadata() | ||
-> #frame_support::sp_std::vec::Vec<&'static str> | ||
{ | ||
#frame_support::sp_std::vec![ #( #docs ),* ] | ||
} | ||
} | ||
) | ||
} |
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
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
16 changes: 16 additions & 0 deletions
16
frame/support/test/tests/pallet_ui/pallet_doc_arg_non_path.rs
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,16 @@ | ||
#[frame_support::pallet] | ||
// Must receive a string literal pointing to a path | ||
#[pallet_doc(X)] | ||
mod pallet { | ||
#[pallet::config] | ||
pub trait Config: frame_system::Config | ||
where | ||
<Self as frame_system::Config>::Index: From<u128>, | ||
{ | ||
} | ||
|
||
#[pallet::pallet] | ||
pub struct Pallet<T>(core::marker::PhantomData<T>); | ||
} | ||
|
||
fn main() {} |
5 changes: 5 additions & 0 deletions
5
frame/support/test/tests/pallet_ui/pallet_doc_arg_non_path.stderr
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,5 @@ | ||
error: The `pallet_doc` received an unsupported argument. Supported format: `pallet_doc(PATH)` | ||
--> tests/pallet_ui/pallet_doc_arg_non_path.rs:3:1 | ||
| | ||
3 | #[pallet_doc(X)] | ||
| ^ |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basti wrote
Personally, I don't understand that by the documentation in this PR. Can you elaborate a bit why both
doc
andpallet_doc
are needed in the examples or something.It could be just me though... :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that makes sense! I've improved the documentation a bit to clarify the attributes.
From a discussion with @bkchr it would be beneficial to have:
include_str!()
at the top of the file without having to duplicate the documentation itselfpallet_doc
attribute gives developers enough control until we can deduce the file from which a macro is invoked, and not the compile time location at which the macro is declared (Tracking issue forproc_macro::Span
inspection APIs rust-lang/rust#54725)