From 25c196bc45ffe0803811f732287a25a75f78f0b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 19 Oct 2023 23:04:24 +0200 Subject: [PATCH 1/4] Implement `replace_segment` attribute This implements the `replace_segment` attribute. This attribute can be used to rename one or multiple segments returned by `TypeInfo::path`. It could be for example be used to rename a changed crate name. For more info see the examples and the docs in this pr. --- Cargo.toml | 4 +-- derive/Cargo.toml | 2 +- derive/src/attr.rs | 61 +++++++++++++++++++++++++++++++------- derive/src/lib.rs | 13 +++++++- src/lib.rs | 24 +++++++++++++++ src/prelude.rs | 4 ++- src/ty/path.rs | 31 +++++++++++++++++-- test_suite/tests/derive.rs | 50 +++++++++++++++++++++++++++++++ 8 files changed, 171 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5947add..4a2d26d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scale-info" -version = "2.9.0" +version = "2.10.0" authors = ["Parity Technologies "] edition = "2021" rust-version = "1.60.0" @@ -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"] } diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 91d2cb9d..363850db 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scale-info-derive" -version = "2.9.0" +version = "2.10.0" authors = [ "Parity Technologies ", "Centrality Developers ", diff --git a/derive/src/attr.rs b/derive/src/attr.rs index 056c7736..37706e22 100644 --- a/derive/src/attr.rs +++ b/derive/src/attr.rs @@ -16,7 +16,7 @@ use syn::{ parse::{Parse, ParseBuffer}, punctuated::Punctuated, spanned::Spanned, - Token, + LitStr, Token, }; const SCALE_INFO: &str = "scale_info"; @@ -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. @@ -34,6 +35,7 @@ pub struct Attributes { skip_type_params: Option, capture_docs: Option, crate_path: Option, + replace_segments: Vec, } impl Attributes { @@ -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 = @@ -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( @@ -97,6 +99,9 @@ impl Attributes { crate_path = Some(parsed_crate_path); } + ScaleInfoAttr::ReplaceSegment(replace_segment) => { + replace_segments.push(replace_segment); + } } } } @@ -127,6 +132,7 @@ impl Attributes { skip_type_params, capture_docs, crate_path, + replace_segments, }) } @@ -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 { + self.replace_segments.iter() + } } /// Parsed representation of the `#[scale_info(bounds(...))]` attribute. @@ -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 { + input.parse::()?; + let content; + syn::parenthesized!(content in input); + + let search = content.parse::()?; + content.parse::()?; + let replace = content.parse::()?; + + 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 { 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()) } diff --git a/derive/src/lib.rs b/derive/src/lib.rs index ad2cd545..a680e3f2 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -101,12 +101,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( + ::core::stringify!(#ident), + ::core::module_path!(), + &[ #( #replaces ),* ] + )) .type_params(#scale_info::prelude::vec![ #( #type_params ),* ]) #docs .#build_type diff --git a/src/lib.rs b/src/lib.rs index 1e0b4911..c32dc995 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 diff --git a/src/prelude.rs b/src/prelude.rs index fadf05b2..2ee9b05d 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -40,7 +40,8 @@ cfg_if! { sync, time, vec, - rc + rc, + iter, }; } else { pub use alloc::{ @@ -64,6 +65,7 @@ cfg_if! { num, ops, time, + iter, }; } } diff --git a/src/ty/path.rs b/src/ty/path.rs index 112b25d3..740b058b 100644 --- a/src/ty/path.rs +++ b/src/ty/path.rs @@ -14,6 +14,7 @@ use crate::prelude::{ fmt::{Display, Error as FmtError, Formatter}, + iter, vec::Vec, }; @@ -84,9 +85,33 @@ impl Path { /// /// - 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::>(); - 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 diff --git a/test_suite/tests/derive.rs b/test_suite/tests/derive.rs index c9905060..bdd8724a 100644 --- a/test_suite/tests/derive.rs +++ b/test_suite/tests/derive.rs @@ -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); +} From a1521ba9ee0076081652f1157a446eba5d61433a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 19 Oct 2023 23:36:07 +0200 Subject: [PATCH 2/4] Update trybuild --- test_suite/tests/ui/fail_missing_derive.stderr | 16 ++++++++-------- test_suite/tests/ui/fail_unions.stderr | 16 ++++++++-------- .../ui/fail_with_invalid_scale_info_attrs.stderr | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/test_suite/tests/ui/fail_missing_derive.stderr b/test_suite/tests/ui/fail_missing_derive.stderr index 4183c20e..18aa96b8 100644 --- a/test_suite/tests/ui/fail_missing_derive.stderr +++ b/test_suite/tests/ui/fail_missing_derive.stderr @@ -5,14 +5,14 @@ error[E0277]: the trait bound `PawType: TypeInfo` is not satisfied | ^^^^^^^^^^^^^^^^^^ the trait `TypeInfo` is not implemented for `PawType` | = 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` to implement `TypeInfo` --> tests/ui/fail_missing_derive.rs:8:10 diff --git a/test_suite/tests/ui/fail_unions.stderr b/test_suite/tests/ui/fail_unions.stderr index 1ee5b728..1b300daa 100644 --- a/test_suite/tests/ui/fail_unions.stderr +++ b/test_suite/tests/ui/fail_unions.stderr @@ -15,14 +15,14 @@ error[E0277]: the trait bound `Commonwealth: TypeInfo` is not satisfied | ^^^^^^^^^^^^ the trait `TypeInfo` is not implemented for `Commonwealth` | = 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 by a bound in `assert_type_info` --> tests/ui/fail_unions.rs:11:24 diff --git a/test_suite/tests/ui/fail_with_invalid_scale_info_attrs.stderr b/test_suite/tests/ui/fail_with_invalid_scale_info_attrs.stderr index fcb310a0..0241b879 100644 --- a/test_suite/tests/ui/fail_with_invalid_scale_info_attrs.stderr +++ b/test_suite/tests/ui/fail_with_invalid_scale_info_attrs.stderr @@ -1,4 +1,4 @@ -error: expected one of: `bounds`, `skip_type_params`, `capture_docs`, `crate` +error: expected one of: `bounds`, `skip_type_params`, `capture_docs`, `crate`, `replace_segment` --> tests/ui/fail_with_invalid_scale_info_attrs.rs:6:14 | 6 | #[scale_info(foo)] From 0bb03923fe47914f440d084d8a928fc5624e03cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 20 Oct 2023 00:42:21 +0200 Subject: [PATCH 3/4] Directly pass the ident str. --- derive/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index a680e3f2..21684c0e 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -66,6 +66,7 @@ impl TypeInfoImpl { fn expand(&self) -> Result { 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( @@ -114,7 +115,7 @@ impl TypeInfoImpl { fn type_info() -> #scale_info::Type { #scale_info::Type::builder() .path(#scale_info::Path::new_with_replace( - ::core::stringify!(#ident), + #ident_str, ::core::module_path!(), &[ #( #replaces ),* ] )) From 755b4f092bfbdfef549ebb02066bcf1c70428509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 20 Oct 2023 10:03:41 +0200 Subject: [PATCH 4/4] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e49c03d1..612cd770 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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