Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement replace_segment attribute #197

Merged
merged 4 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.10.0] - 2023-10-20

### Added
- Implement `replace_segment` attribute [(#197)](https://github.com/paritytech/scale-info/pull/197)

## [2.9.0] - 2023-07-03

### Changed
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "scale-info"
version = "2.9.0"
version = "2.10.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
rust-version = "1.60.0"
Expand All @@ -17,7 +17,7 @@ include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"]
[dependencies]
bitvec = { version = "1", default-features = false, features = ["alloc"], optional = true }
cfg-if = "1.0"
scale-info-derive = { version = "2.9.0", path = "derive", default-features = false, optional = true }
scale-info-derive = { version = "2.10.0", path = "derive", default-features = false, optional = true }
serde = { version = "1", default-features = false, optional = true, features = ["derive", "alloc"] }
derive_more = { version = "0.99.1", default-features = false, features = ["from"] }
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
Expand Down
2 changes: 1 addition & 1 deletion derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "scale-info-derive"
version = "2.9.0"
version = "2.10.0"
authors = [
"Parity Technologies <admin@parity.io>",
"Centrality Developers <support@centrality.ai>",
Expand Down
61 changes: 51 additions & 10 deletions derive/src/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use syn::{
parse::{Parse, ParseBuffer},
punctuated::Punctuated,
spanned::Spanned,
Token,
LitStr, Token,
};

const SCALE_INFO: &str = "scale_info";
Expand All @@ -26,6 +26,7 @@ mod keywords {
syn::custom_keyword!(bounds);
syn::custom_keyword!(skip_type_params);
syn::custom_keyword!(capture_docs);
syn::custom_keyword!(replace_segment);
}

/// Parsed and validated set of `#[scale_info(...)]` attributes for an item.
Expand All @@ -34,6 +35,7 @@ pub struct Attributes {
skip_type_params: Option<SkipTypeParamsAttr>,
capture_docs: Option<CaptureDocsAttr>,
crate_path: Option<CratePathAttr>,
replace_segments: Vec<ReplaceSegment>,
}

impl Attributes {
Expand All @@ -43,6 +45,7 @@ impl Attributes {
let mut skip_type_params = None;
let mut capture_docs = None;
let mut crate_path = None;
let mut replace_segments = Vec::new();

let attributes_parser = |input: &ParseBuffer| {
let attrs: Punctuated<ScaleInfoAttr, Token![,]> =
Expand Down Expand Up @@ -86,7 +89,6 @@ impl Attributes {
}
capture_docs = Some(parsed_capture_docs);
}

ScaleInfoAttr::CratePath(parsed_crate_path) => {
if crate_path.is_some() {
return Err(syn::Error::new(
Expand All @@ -97,6 +99,9 @@ impl Attributes {

crate_path = Some(parsed_crate_path);
}
ScaleInfoAttr::ReplaceSegment(replace_segment) => {
replace_segments.push(replace_segment);
}
}
}
}
Expand Down Expand Up @@ -127,6 +132,7 @@ impl Attributes {
skip_type_params,
capture_docs,
crate_path,
replace_segments,
})
}

Expand All @@ -153,6 +159,11 @@ impl Attributes {
pub fn crate_path(&self) -> Option<&CratePathAttr> {
self.crate_path.as_ref()
}

/// Returns an iterator over the `#[scale_info(replace_segment("Hello", "world"))]` attributes.
pub fn replace_segments(&self) -> impl Iterator<Item = &ReplaceSegment> {
self.replace_segments.iter()
}
}

/// Parsed representation of the `#[scale_info(bounds(...))]` attribute.
Expand Down Expand Up @@ -266,29 +277,59 @@ impl Parse for CratePathAttr {
}
}

/// Parsed representation of the `#[scale_info(replace_segment("Hello", "world"))]` attribute.
#[derive(Clone)]
pub struct ReplaceSegment {
search: LitStr,
replace: LitStr,
}

impl ReplaceSegment {
pub fn search(&self) -> &LitStr {
&self.search
}

pub fn replace(&self) -> &LitStr {
&self.replace
}
}

impl Parse for ReplaceSegment {
fn parse(input: &ParseBuffer) -> syn::Result<Self> {
input.parse::<keywords::replace_segment>()?;
let content;
syn::parenthesized!(content in input);

let search = content.parse::<LitStr>()?;
content.parse::<Token![,]>()?;
let replace = content.parse::<LitStr>()?;

Ok(Self { search, replace })
}
}

/// Parsed representation of one of the `#[scale_info(..)]` attributes.
pub enum ScaleInfoAttr {
Bounds(BoundsAttr),
SkipTypeParams(SkipTypeParamsAttr),
CaptureDocs(CaptureDocsAttr),
CratePath(CratePathAttr),
ReplaceSegment(ReplaceSegment),
}

impl Parse for ScaleInfoAttr {
fn parse(input: &ParseBuffer) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(keywords::bounds) {
let bounds = input.parse()?;
Ok(Self::Bounds(bounds))
Ok(Self::Bounds(input.parse()?))
} else if lookahead.peek(keywords::skip_type_params) {
let skip_type_params = input.parse()?;
Ok(Self::SkipTypeParams(skip_type_params))
Ok(Self::SkipTypeParams(input.parse()?))
} else if lookahead.peek(keywords::capture_docs) {
let capture_docs = input.parse()?;
Ok(Self::CaptureDocs(capture_docs))
Ok(Self::CaptureDocs(input.parse()?))
} else if lookahead.peek(Token![crate]) {
let crate_path = input.parse()?;
Ok(Self::CratePath(crate_path))
Ok(Self::CratePath(input.parse()?))
} else if lookahead.peek(keywords::replace_segment) {
Ok(Self::ReplaceSegment(input.parse()?))
} else {
Err(lookahead.error())
}
Expand Down
14 changes: 13 additions & 1 deletion derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ impl TypeInfoImpl {

fn expand(&self) -> Result<TokenStream2> {
let ident = &self.ast.ident;
let ident_str = ident.to_string();
let scale_info = crate_path(self.attrs.crate_path())?;

let where_clause = trait_bounds::make_where_clause(
Expand Down Expand Up @@ -101,12 +102,23 @@ impl TypeInfoImpl {
};
let docs = self.generate_docs(&self.ast.attrs);

let replaces = self.attrs.replace_segments().map(|r| {
let search = r.search();
let replace = r.replace();

quote!(( #search, #replace ))
});

Ok(quote! {
impl #impl_generics #scale_info::TypeInfo for #ident #ty_generics #where_clause {
type Identity = Self;
fn type_info() -> #scale_info::Type {
#scale_info::Type::builder()
.path(#scale_info::Path::new(::core::stringify!(#ident), ::core::module_path!()))
.path(#scale_info::Path::new_with_replace(
#ident_str,
::core::module_path!(),
&[ #( #replaces ),* ]
))
.type_params(#scale_info::prelude::vec![ #( #type_params ),* ])
#docs
.#build_type
Expand Down
24 changes: 24 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,30 @@
//! }
//! ```
//!
//! #### `#[scale_info(replace_segment("search", "replace"))]`
//!
//! Specify to rename a segment in the `path` returned by the [`TypeInfo::path`] function.
//! Normally the path is generated by using the `module_path!` macro. This path includes
//! the crate name and all the modules up to the declaration of the type. Sometimes it
//! is useful to replace one of these segments to ensure that for example a renaming
//! of the crate isn't propagated to downstream users. Be aware that if a `crate-name`
//! contains an hypen, the actual segment is `crate_name`. The `replace` name needs
//! to be a valid Rust identifier. The attribute is allowed to be passed multiple
//! times to replace multiple segments.
//!
//! Example:
//! ```ignore
//! use scale_info_reexport::info::TypeInfo;
//!
//! #[derive(TypeInfo)]
//! #[scale_info(replace_segment("something", "better_name"))]
//! #[scale_info(replace_segment("TestEnum", "BetterEnumName"))]
//! enum TestEnum {
//! FirstVariant,
//! SecondVariant,
//! }
//! ```
//!
//! # Forms
//!
//! To bridge between compile-time type information and runtime the
Expand Down
4 changes: 3 additions & 1 deletion src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ cfg_if! {
sync,
time,
vec,
rc
rc,
iter,
};
} else {
pub use alloc::{
Expand All @@ -64,6 +65,7 @@ cfg_if! {
num,
ops,
time,
iter,
};
}
}
31 changes: 28 additions & 3 deletions src/ty/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use crate::prelude::{
fmt::{Display, Error as FmtError, Formatter},
iter,
vec::Vec,
};

Expand Down Expand Up @@ -84,9 +85,33 @@ impl Path<MetaForm> {
///
/// - If the type identifier or module path contain invalid Rust identifiers
pub fn new(ident: &'static str, module_path: &'static str) -> Path {
let mut segments = module_path.split("::").collect::<Vec<_>>();
segments.push(ident);
Self::from_segments(segments).expect("All path segments should be valid Rust identifiers")
let segments = module_path.split("::");
Self::from_segments(segments.chain(iter::once(ident)))
.expect("All path segments should be valid Rust identifiers")
}

/// Create a new Path
///
/// The `segment_replace` is a list of `(search, replace)` items. Every
/// `search` item that appears in the `module_path` is replaced by the
/// `replace` item. This can be used for example to replace the crate name
/// or even the name of the type in the final [`Path`].
///
/// # Panics
///
/// - If the type identifier, module path or replace contain invalid Rust identifiers
pub fn new_with_replace(
ident: &'static str,
module_path: &'static str,
segment_replace: &[(&'static str, &'static str)],
) -> Path {
let segments = module_path.split("::");
Self::from_segments(
segments
.chain(iter::once(ident))
.map(|s| segment_replace.iter().find(|r| s == r.0).map_or(s, |r| r.1)),
)
.expect("All path segments should be valid Rust identifiers")
}

/// Create a Path from the given segments
Expand Down
50 changes: 50 additions & 0 deletions test_suite/tests/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -857,3 +857,53 @@ fn strips_invisible_delimiters_from_type_name() {

assert_type!(S, expected);
}

#[test]
fn replace_segments_works() {
#[allow(unused)]
#[derive(TypeInfo)]
#[scale_info(replace_segment("derive", "hey"))]
pub struct S;

let ty = Type::builder()
.path(Path::new("S", "hey"))
.composite(Fields::unit());

assert_type!(S, ty);

mod nested {
#[allow(unused)]
#[derive(info::TypeInfo)]
#[scale_info(replace_segment("derive", "hey"))]
#[scale_info(replace_segment("nested", "what"))]
pub struct S;

#[allow(unused)]
#[derive(info::TypeInfo)]
#[scale_info(replace_segment("nested", "what"))]
pub struct R;
}

let ty = Type::builder()
.path(Path::new("S", "hey::what"))
.composite(Fields::unit());

assert_type!(nested::S, ty);

let ty = Type::builder()
.path(Path::new("R", "derive::what"))
.composite(Fields::unit());

assert_type!(nested::R, ty);

#[allow(unused)]
#[derive(info::TypeInfo)]
#[scale_info(replace_segment("R", "AWESOME"))]
pub struct R;

let ty = Type::builder()
.path(Path::new("AWESOME", "derive"))
.composite(Fields::unit());

assert_type!(R, ty);
}
16 changes: 8 additions & 8 deletions test_suite/tests/ui/fail_missing_derive.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ error[E0277]: the trait bound `PawType<u16>: TypeInfo` is not satisfied
| ^^^^^^^^^^^^^^^^^^ the trait `TypeInfo` is not implemented for `PawType<u16>`
|
= help: the following other types implement trait `TypeInfo`:
&T
&mut T
()
(A, B)
(A, B, C)
(A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
bool
char
i8
i16
i32
i64
i128
u8
and $N others
note: required for `Cat<bool, u8, u16>` to implement `TypeInfo`
--> tests/ui/fail_missing_derive.rs:8:10
Expand Down
Loading
Loading