diff --git a/src/impls.rs b/src/impls.rs index 34623136..42e3bf17 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -17,17 +17,16 @@ use crate::prelude::{ collections::BTreeMap, marker::PhantomData, string::String, - vec, vec::Vec, }; use crate::{ build::*, - meta_type, MetaType, Path, Type, TypeDefArray, + TypeDefPhantom, TypeDefPrimitive, TypeDefSequence, TypeDefTuple, @@ -249,9 +248,6 @@ where type Identity = Self; fn type_info() -> Type { - Type::builder() - .path(Path::prelude("PhantomData")) - .type_params(vec![meta_type::()]) - .composite(Fields::unit()) + TypeDefPhantom::new(MetaType::new::()).into() } } diff --git a/src/tests.rs b/src/tests.rs index 49de287b..107da62a 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -74,13 +74,7 @@ fn prelude_items() { .variant("Err", Fields::unnamed().field_of::("E")) ) ); - assert_type!( - PhantomData, - Type::builder() - .path(Path::prelude("PhantomData")) - .type_params(tuple_meta_type!(i32)) - .composite(Fields::unit()) - ); + assert_type!(PhantomData, TypeDefPhantom::new(meta_type::())); } #[test] @@ -154,3 +148,33 @@ fn struct_with_generics() { .composite(Fields::named().field_of::>>("data", "T")); assert_type!(SelfTyped, expected_type); } + +#[test] +fn basic_struct_with_phantoms() { + #[allow(unused)] + struct SomeStruct { + a: u8, + marker: PhantomData, + } + + impl TypeInfo for SomeStruct + where + T: TypeInfo + 'static, + { + type Identity = Self; + + fn type_info() -> Type { + Type::builder() + .path(Path::new("SomeStruct", module_path!())) + .type_params(tuple_meta_type!(T)) + .composite(Fields::named().field_of::("a", "u8")) + } + } + + let struct_bool_type_info = Type::builder() + .path(Path::from_segments(vec!["scale_info", "tests", "SomeStruct"]).unwrap()) + .type_params(tuple_meta_type!(bool)) + .composite(Fields::named().field_of::("a", "u8")); + + assert_type!(SomeStruct, struct_bool_type_info); +} diff --git a/src/ty/mod.rs b/src/ty/mod.rs index 886bb918..ecda16c7 100644 --- a/src/ty/mod.rs +++ b/src/ty/mod.rs @@ -118,6 +118,12 @@ impl From for Type { } } +impl From for Type { + fn from(phantom: TypeDefPhantom) -> Self { + Self::new(Path::voldemort(), Vec::new(), phantom) + } +} + impl Type { /// Create a [`TypeBuilder`](`crate::build::TypeBuilder`) the public API for constructing a [`Type`] pub fn builder() -> TypeBuilder { @@ -181,6 +187,8 @@ pub enum TypeDef { Tuple(TypeDefTuple), /// A Rust primitive type. Primitive(TypeDefPrimitive), + /// A PhantomData type. + Phantom(TypeDefPhantom), } impl IntoPortable for TypeDef { @@ -194,6 +202,7 @@ impl IntoPortable for TypeDef { TypeDef::Array(array) => array.into_portable(registry).into(), TypeDef::Tuple(tuple) => tuple.into_portable(registry).into(), TypeDef::Primitive(primitive) => primitive.into(), + TypeDef::Phantom(phantom) => phantom.into_portable(registry).into(), } } } @@ -395,3 +404,55 @@ where &self.type_param } } + +/// A type describing a `PhantomData` type. +/// +/// In the context of SCALE encoded types, including `PhantomData` types in +/// the type info might seem surprising. The reason to include this information +/// is that there could be situations where it's useful and because removing +/// `PhantomData` items from the derive input quickly becomes a messy +/// syntax-level hack (see PR https://github.com/paritytech/scale-info/pull/31). +/// Instead we take the same approach as `parity-scale-codec` where users are +/// required to explicitly skip fields that cannot be represented in SCALE +/// encoding, using the `#[codec(skip)]` attribute. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde", + serde(bound( + serialize = "T::Type: Serialize", + deserialize = "T::Type: DeserializeOwned", + )) +)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Debug)] +pub struct TypeDefPhantom { + /// The PhantomData type parameter + #[cfg_attr(feature = "serde", serde(rename = "type"))] + type_param: T::Type, +} + +impl IntoPortable for TypeDefPhantom { + type Output = TypeDefPhantom; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + TypeDefPhantom { + type_param: registry.register_type(&self.type_param), + } + } +} + +impl TypeDefPhantom { + /// Creates a new phantom type definition. + pub fn new(type_param: MetaType) -> Self { + Self { type_param } + } +} + +impl TypeDefPhantom +where + T: Form, +{ + /// Returns the type parameter type of the phantom type. + pub fn type_param(&self) -> &T::Type { + &self.type_param + } +} diff --git a/test_suite/tests/derive.rs b/test_suite/tests/derive.rs index 6ffaf6af..74ac1052 100644 --- a/test_suite/tests/derive.rs +++ b/test_suite/tests/derive.rs @@ -13,7 +13,10 @@ // limitations under the License. #![cfg_attr(not(feature = "std"), no_std)] -use scale_info::prelude::boxed::Box; +use scale_info::prelude::{ + boxed::Box, + marker::PhantomData, +}; use pretty_assertions::assert_eq; use scale_info::{ @@ -73,6 +76,27 @@ fn struct_derive() { assert_type!(SelfTyped, self_typed_type); } +#[test] +fn phantom_data_is_part_of_the_type_info() { + #[allow(unused)] + #[derive(TypeInfo)] + struct P { + a: u8, + m: PhantomData, + } + + let ty = Type::builder() + .path(Path::new("P", "derive")) + .type_params(tuple_meta_type!(bool)) + .composite( + Fields::named() + .field_of::("a", "u8") + .field_of::>("m", "PhantomData"), + ); + + assert_type!(P, ty); +} + #[test] fn tuple_struct_derive() { #[allow(unused)] diff --git a/test_suite/tests/json.rs b/test_suite/tests/json.rs index 6b8e6ddd..afc1bed6 100644 --- a/test_suite/tests/json.rs +++ b/test_suite/tests/json.rs @@ -18,6 +18,7 @@ use scale_info::prelude::{ boxed::Box, + marker::PhantomData, string::String, vec, vec::Vec, @@ -133,13 +134,9 @@ fn test_builtins() { assert_json_for_type::(json!({ "def": { "primitive": "str" } })); assert_json_for_type::(json!({ "def": { "primitive": "str" } })); // PhantomData - assert_json_for_type::>(json!({ - "path": ["PhantomData"], - "params": [1], - "def": { - "composite": {}, - } - })) + assert_json_for_type::>( + json!({ "def": { "phantom": { "type": 1 } }, }), + ) } #[test] @@ -197,6 +194,30 @@ fn test_struct() { })); } +#[test] +fn test_struct_with_phantom() { + use scale_info::prelude::marker::PhantomData; + #[derive(TypeInfo)] + struct Struct { + a: i32, + b: PhantomData, + } + + assert_json_for_type::>(json!({ + "path": ["json", "Struct"], + "params": [1], + "def": { + "composite": { + "fields": [ + { "name": "a", "type": 2, "typeName": "i32" }, + // type 1 is the `u8` in the `PhantomData` + { "name": "b", "type": 3, "typeName": "PhantomData" }, + ], + }, + } + })); +} + #[test] fn test_clike_enum() { #[derive(TypeInfo)]